feat(calc-02): add shared popup menus for mode and constants
Refactor the calc-02 demo to use a single popup menu component for angle mode and constants, align the menus to their trigger buttons, and update the README/project notes to reflect the portrait-first demo layout and constant API.
This commit is contained in:
+50
-59
@@ -338,40 +338,49 @@ function handleKeyboard(event) {
|
||||
}
|
||||
|
||||
const modeOptions = ['deg', 'rad', 'grad'];
|
||||
let modeMenuEl = null;
|
||||
let activeMenuEl = null;
|
||||
|
||||
function closeModeMenu() {
|
||||
if (modeMenuEl) {
|
||||
modeMenuEl.remove();
|
||||
modeMenuEl = null;
|
||||
if (activeMenuEl) {
|
||||
activeMenuEl.remove();
|
||||
activeMenuEl = null;
|
||||
}
|
||||
}
|
||||
|
||||
function openMenu(anchorButton, items, onSelect) {
|
||||
const rect = anchorButton.getBoundingClientRect();
|
||||
const menu = document.createElement('div');
|
||||
menu.className = 'menu-popup';
|
||||
menu.style.top = `${rect.bottom + 6 + window.scrollY}px`;
|
||||
menu.style.left = `${rect.left + window.scrollX}px`;
|
||||
menu.style.minWidth = `${rect.width}px`;
|
||||
for (const item of items) {
|
||||
const button = document.createElement('button');
|
||||
button.type = 'button';
|
||||
button.className = `menu-popup-item${item.active ? ' is-active' : ''}`;
|
||||
button.textContent = item.label;
|
||||
button.addEventListener('click', () => onSelect(item.value));
|
||||
menu.appendChild(button);
|
||||
}
|
||||
document.body.appendChild(menu);
|
||||
return menu;
|
||||
}
|
||||
|
||||
function toggleModeMenu() {
|
||||
if (modeMenuEl) {
|
||||
if (activeMenuEl) {
|
||||
closeModeMenu();
|
||||
return;
|
||||
}
|
||||
closeConstMenu();
|
||||
const rect = modeButton.getBoundingClientRect();
|
||||
modeMenuEl = document.createElement('div');
|
||||
modeMenuEl.className = 'mode-menu';
|
||||
modeMenuEl.style.top = `${rect.bottom + 6 + window.scrollY}px`;
|
||||
modeMenuEl.style.minWidth = `${rect.width}px`;
|
||||
modeOptions.forEach((mode) => {
|
||||
const button = document.createElement('button');
|
||||
button.type = 'button';
|
||||
button.className = `mode-menu-item${mode === calc.angleMode ? ' is-active' : ''}`;
|
||||
button.textContent = mode;
|
||||
button.addEventListener('click', () => {
|
||||
calc.angleMode = mode;
|
||||
render();
|
||||
closeModeMenu();
|
||||
});
|
||||
modeMenuEl.appendChild(button);
|
||||
activeMenuEl = openMenu(modeButton, modeOptions.map((mode) => ({
|
||||
label: mode,
|
||||
value: mode,
|
||||
active: mode === calc.angleMode,
|
||||
})), (mode) => {
|
||||
calc.angleMode = mode;
|
||||
render();
|
||||
closeModeMenu();
|
||||
});
|
||||
document.body.appendChild(modeMenuEl);
|
||||
modeMenuEl.style.left = `${rect.left + window.scrollX}px`;
|
||||
}
|
||||
|
||||
modeButton.addEventListener('click', (event) => {
|
||||
@@ -396,10 +405,8 @@ window.addEventListener('click', (event) => {
|
||||
if (label) copyStackValue(label);
|
||||
return;
|
||||
}
|
||||
if (modeMenuEl && !event.target.closest('.mode-menu') && event.target !== modeButton) {
|
||||
if (activeMenuEl && !event.target.closest('.menu-popup') && event.target !== modeButton && event.target !== constButton) {
|
||||
closeModeMenu();
|
||||
}
|
||||
if (constMenuEl && !event.target.closest('.mode-menu') && event.target !== constButton) {
|
||||
closeConstMenu();
|
||||
}
|
||||
});
|
||||
@@ -431,48 +438,34 @@ const constantLabels = {
|
||||
};
|
||||
const constantOrder = ['pi', 'e', 'phi', 'g', 'c'];
|
||||
|
||||
let constMenuEl = null;
|
||||
|
||||
function closeConstMenu() {
|
||||
if (constMenuEl) {
|
||||
constMenuEl.remove();
|
||||
constMenuEl = null;
|
||||
if (activeMenuEl) {
|
||||
activeMenuEl.remove();
|
||||
activeMenuEl = null;
|
||||
}
|
||||
}
|
||||
|
||||
function toggleConstMenu() {
|
||||
if (constMenuEl) {
|
||||
if (activeMenuEl) {
|
||||
closeConstMenu();
|
||||
return;
|
||||
}
|
||||
closeModeMenu();
|
||||
const rect = constButton.getBoundingClientRect();
|
||||
constMenuEl = document.createElement('div');
|
||||
constMenuEl.className = 'mode-menu';
|
||||
constMenuEl.style.top = `${rect.bottom + 6 + window.scrollY}px`;
|
||||
constMenuEl.style.minWidth = `${rect.width}px`;
|
||||
const availableConstants = calc.listConstants();
|
||||
const keys = [...constantOrder, ...Object.keys(availableConstants).filter((name) => !constantOrder.includes(name))];
|
||||
keys.forEach((name) => {
|
||||
const button = document.createElement('button');
|
||||
button.type = 'button';
|
||||
button.className = 'mode-menu-item';
|
||||
if (!Object.prototype.hasOwnProperty.call(availableConstants, name)) {
|
||||
return;
|
||||
}
|
||||
button.textContent = constantLabels[name] ?? name;
|
||||
button.addEventListener('click', () => {
|
||||
pushEditingValueIfNeeded();
|
||||
calc.push(availableConstants[name]);
|
||||
render();
|
||||
clearStatus();
|
||||
closeConstMenu();
|
||||
focusInput();
|
||||
});
|
||||
constMenuEl.appendChild(button);
|
||||
const keys = [...constantOrder, ...Object.keys(availableConstants).filter((name) => !constantOrder.includes(name))]
|
||||
.filter((name) => Object.prototype.hasOwnProperty.call(availableConstants, name));
|
||||
activeMenuEl = openMenu(constButton, keys.map((name) => ({
|
||||
label: constantLabels[name] ?? name,
|
||||
value: name,
|
||||
})), (name) => {
|
||||
pushEditingValueIfNeeded();
|
||||
calc.push(availableConstants[name]);
|
||||
render();
|
||||
clearStatus();
|
||||
closeConstMenu();
|
||||
focusInput();
|
||||
});
|
||||
document.body.appendChild(constMenuEl);
|
||||
constMenuEl.style.left = `${rect.left + window.scrollX}px`;
|
||||
}
|
||||
|
||||
constButton.addEventListener('click', (event) => {
|
||||
@@ -481,7 +474,6 @@ constButton.addEventListener('click', (event) => {
|
||||
});
|
||||
|
||||
leftButton.addEventListener('click', () => {});
|
||||
|
||||
downButton.addEventListener('click', () => {
|
||||
if (!calc.isEditing && calc.isValidIndex(0)) {
|
||||
const value = calc.stack[0];
|
||||
@@ -503,7 +495,6 @@ window.addEventListener('pageshow', focusInput);
|
||||
window.addEventListener('focus', focusInput);
|
||||
window.addEventListener('pointerdown', focusInput, true);
|
||||
window.addEventListener('mousedown', focusInput, true);
|
||||
window.addEventListener('click', focusInput, true);
|
||||
|
||||
hiddenInput.setAttribute('inputmode', 'none');
|
||||
hiddenInput.setAttribute('readonly', 'readonly');
|
||||
@@ -518,7 +509,7 @@ hiddenInput.addEventListener('focus', () => {
|
||||
});
|
||||
|
||||
document.addEventListener('click', (event) => {
|
||||
if (!isTouchDevice && !event.target.closest('.mode-menu')) {
|
||||
if (!isTouchDevice && !event.target.closest('.menu-popup')) {
|
||||
focusInput();
|
||||
}
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user