diff --git a/samples/calc-02/index.css b/samples/calc-02/index.css index cf2038f..6993ee7 100644 --- a/samples/calc-02/index.css +++ b/samples/calc-02/index.css @@ -137,6 +137,10 @@ body { justify-self: end; font-size: 20px; } + +.stack-value.is-editing { + letter-spacing: 0.02em; +} .display-buttons-panel { padding: 8px; min-height: 0; @@ -379,6 +383,37 @@ button:active { outline-offset: 2px; } +.stack-value.is-editing { + display: inline-flex; + align-items: center; + justify-content: flex-end; + gap: 0; +} + +.edit-text { + display: inline-block; + white-space: pre; +} + +.edit-caret { + display: inline-block; + width: 1px; + height: 1em; + margin: 0 0.12ch; + background: currentColor; + animation: caret-blink 1s steps(1, end) infinite; + transform: translateY(0.02em); +} + +@keyframes caret-blink { + 0%, 49% { + opacity: 1; + } + 50%, 100% { + opacity: 0.15; + } +} + .key-default { background: linear-gradient(180deg, var(--btnTop), var(--btnBottom)); color: #eef2f7; diff --git a/samples/calc-02/index.js b/samples/calc-02/index.js index 06ae7ab..517fed7 100644 --- a/samples/calc-02/index.js +++ b/samples/calc-02/index.js @@ -94,6 +94,7 @@ function focusInput() { } let statusTimer = null; +let editCursor = 0; function setStatus(message, isError = false, timeoutMs = 1400) { if (!statusLine) return; @@ -128,9 +129,14 @@ function getStackLine(indexFromTop) { return indexFromTop >= 0 && indexFromTop < calc.stack.length ? calc.stack[indexFromTop] : ''; } +function getEditingDisplayValue() { + if (!calc.isEditing) return ''; + return calc.inputValue; +} + function getStackDisplayValue(label) { if (label === 'X') { - return calc.isEditing ? calc.inputValue : (calc.formatNumber(getStackLine(0)) || ''); + return calc.isEditing ? getEditingDisplayValue() : (calc.formatNumber(getStackLine(0)) || ''); } if (label === 'Y') { return calc.isEditing ? (calc.formatNumber(getStackLine(0)) || '') : (calc.formatNumber(getStackLine(1)) || ''); @@ -157,14 +163,30 @@ function render() { const isPortrait = window.matchMedia('(orientation: portrait)').matches || window.innerWidth <= 860; calculatorEl?.classList.toggle('portrait', isPortrait); calculatorEl?.classList.toggle('landscape', !isPortrait); - stackEls.X.textContent = getStackDisplayValue('X'); + const xValue = getStackDisplayValue('X'); + if (calc.isEditing) { + const cursor = Math.max(0, Math.min(editCursor, calc.inputValue.length)); + stackEls.X.innerHTML = `${calc.inputValue.slice(0, cursor)}${calc.inputValue.slice(cursor)}`; + } else { + stackEls.X.textContent = xValue; + } stackEls.Y.textContent = getStackDisplayValue('Y'); stackEls.Z.textContent = getStackDisplayValue('Z'); stackEls.T.textContent = getStackDisplayValue('T'); + stackEls.X.classList.toggle('is-editing', calc.isEditing); + stackEls.X.classList.toggle('is-caret-visible', calc.isEditing); updateCopyButtons(); modeButton.textContent = calc.angleMode; } +function stopEditing(clearValue = false) { + if (clearValue) { + calc.inputValue = ''; + } + calc.isEditing = false; + editCursor = 0; +} + function pushEditingValueIfNeeded() { if (!calc.isEditing) return; if (calc.inputValue !== '') { @@ -172,20 +194,27 @@ function pushEditingValueIfNeeded() { } calc.inputValue = ''; calc.isEditing = false; + editCursor = 0; } function inputToX(value) { if (!calc.isEditing) { calc.isEditing = true; calc.inputValue = ''; + editCursor = 0; } if (value === 'Backspace') { - calc.inputValue = calc.inputValue.slice(0, -1); + if (editCursor > 0) { + calc.inputValue = `${calc.inputValue.slice(0, editCursor - 1)}${calc.inputValue.slice(editCursor)}`; + editCursor -= 1; + } } else { - calc.inputValue += value; + calc.inputValue = `${calc.inputValue.slice(0, editCursor)}${value}${calc.inputValue.slice(editCursor)}`; + editCursor += value.length; } if (calc.inputValue === '') { calc.isEditing = false; + editCursor = 0; } } @@ -217,11 +246,9 @@ function execute(name) { } } else if (name === 'clear') { calc.clear(); - calc.inputValue = ''; - calc.isEditing = false; + stopEditing(true); } else if (name === 'escape') { - calc.inputValue = ''; - calc.isEditing = false; + stopEditing(true); } else if (name === 'backspace') { if (calc.isEditing) { inputToX('Backspace'); @@ -235,7 +262,11 @@ function execute(name) { } } else if (name === 'neg') { if (calc.isEditing) { - calc.inputValue = calc.inputValue.startsWith('-') ? calc.inputValue.slice(1) : `-${calc.inputValue}`; + const hasSign = calc.inputValue.startsWith('-'); + calc.inputValue = hasSign ? calc.inputValue.slice(1) : `-${calc.inputValue}`; + if (!hasSign) editCursor += 1; + if (hasSign) editCursor = Math.max(0, editCursor - 1); + editCursor = Math.max(0, Math.min(editCursor, calc.inputValue.length)); } else { calc.command('neg'); } @@ -243,6 +274,9 @@ function execute(name) { pushEditingValueIfNeeded(); calc.command(name); } + if (!calc.isEditing) { + editCursor = 0; + } render(); } catch (error) { setStatus(error?.message || 'Operation error', true); @@ -306,15 +340,34 @@ function handleKeyboard(event) { '%': 'mod', '^': 'pow', }; - const arrowMap = { - ArrowUp: upButton, - ArrowDown: downButton, - ArrowLeft: leftButton, - ArrowRight: rightButton, - }; - if (arrowMap[key]) { + if (key === 'ArrowLeft') { event.preventDefault(); - arrowMap[key].click(); + if (calc.isEditing) { + editCursor = Math.max(0, editCursor - 1); + render(); + return; + } + leftButton.click(); + return; + } + if (key === 'ArrowRight') { + event.preventDefault(); + if (calc.isEditing) { + editCursor = Math.min(calc.inputValue.length, editCursor + 1); + render(); + return; + } + rightButton.click(); + return; + } + if (key === 'ArrowUp') { + event.preventDefault(); + upButton.click(); + return; + } + if (key === 'ArrowDown') { + event.preventDefault(); + downButton.click(); return; } if (map[key]) { @@ -459,7 +512,13 @@ constButton.addEventListener('click', (event) => { toggleConstMenu(); }); -leftButton.addEventListener('click', () => {}); +leftButton.addEventListener('click', () => { + if (calc.isEditing) { + editCursor = Math.max(0, editCursor - 1); + render(); + focusInput(); + } +}); downButton.addEventListener('click', () => { if (!calc.isEditing && calc.isValidIndex(0)) { const value = calc.stack[0]; @@ -472,6 +531,12 @@ downButton.addEventListener('click', () => { }); rightButton.addEventListener('click', () => { + if (calc.isEditing) { + editCursor = Math.min(calc.inputValue.length, editCursor + 1); + render(); + focusInput(); + return; + } execute('swap'); });