Compare commits

..

21 Commits

Author SHA1 Message Date
matmoul 9abdc33713 fix(calc-02): update stack labels for navigation and move modes 2026-05-17 05:34:06 +02:00
matmoul 6dd9550890 feat: add stack navigation and reordering controls 2026-05-17 04:24:43 +02:00
matmoul f44fb8c252 docs: align calculator visual portrait table cell 2026-05-17 03:23:47 +02:00
matmoul 5cc97f754d fix(calc-02): restore edited value on cancel
Canceling stack-top editing now pushes the original value back before
leaving edit mode, including Escape and ArrowDown handling.
2026-05-17 01:31:43 +02:00
matmoul 736154110d fix: correct calc-02 function key shortcuts 2026-05-17 00:38:32 +02:00
matmoul 62a0f447c5 feat: update calculator function key labels and shortcuts
Add titles for function and trig buttons to expose keyboard hints, and remap reciprocal/power10 shortcuts to match the new key layout.
Update the portrait visual spec to reflect the revised keypad and function ordering.
2026-05-17 00:35:50 +02:00
matmoul 16b567731f docs: update calc-02 shortcut reference and key mappings 2026-05-17 00:14:24 +02:00
matmoul b710d5f0eb refactor(calc-02): simplify stack display and edit cursor handling 2026-05-16 23:43:49 +02:00
matmoul 07a4c533fb fix: preserve full calc-02 stack while limiting display to top 4
Keep the calculator stack unlimited in the demo and only constrain the rendered stack view. Also restore the edit cursor position when pulling a value into the input from the stack.
2026-05-16 23:40:13 +02:00
matmoul acc075d30c feat(calc-02): add cursor-aware editing in the X stack display 2026-05-16 23:30:50 +02:00
matmoul 6a37c7c1e8 feat: add pow10 and exp arithmetic operations 2026-05-16 23:15:22 +02:00
matmoul 5f2d18df99 fix(calc-02): constrain keypad area with internal scrolling
Wrap the keypad, function, and trig panels in a dedicated scroll area so the calculator shell can use a fixed grid layout without overflowing the viewport. Also update panel styling to preserve spacing and scrollbar behavior.
2026-05-16 22:40:23 +02:00
matmoul c69380d057 refactor(samples): reorder calculator keypad keys 2026-05-16 21:58:51 +02:00
matmoul c11a75d7f7 fix: bust calc sample stylesheet cache 2026-05-16 21:52:05 +02:00
matmoul 813fae1b61 refactor(calc-02): simplify calculator layout structure
Move the display and button panels into a single wrapper and rely on
viewport-height sizing with hidden page overflow to keep the calculator
contained. Also trim redundant panel-specific layout rules and styling.
2026-05-16 21:41:47 +02:00
matmoul 208db58fe7 fix: make calc-02 panels stretch and scroll within container 2026-05-16 20:24:43 +02:00
matmoul ea6401a006 fix: constrain calculator width on calc-02 sample 2026-05-16 20:17:19 +02:00
matmoul ea61ec17d5 fix: remove portrait class from calc-02 calculator shell 2026-05-16 19:44:09 +02:00
matmoul a4bbffe65b feat: replace calculator enter key label with symbol 2026-05-16 18:41:03 +02:00
matmoul a24142cd72 docs: remove obsolete temporary README file 2026-05-16 05:23:42 +02:00
matmoul 4df99d0738 docs: update README for default demo and command aliases 2026-05-16 05:01:17 +02:00
8 changed files with 502 additions and 168 deletions
+2 -1
View File
@@ -1,7 +1,8 @@
# State # State
- Core engine: `src/rpn-calculator.js` - Core engine: `src/rpn-calculator.js`
- Reference demo: `samples/calc-02/` (portrait-first HP48GX layout, compact mode/constants popups; Const button comes before Mode in the display row) - Reference demo: `samples/calc-02/` (portrait-first HP48GX layout; calc-02 keyboard shortcuts are the reference; compact mode/constants popups; Const button comes before Mode in the display row)
- Important UI behavior: mode button shows the current angle mode; keyboard focus stays on the hidden input on desktop; clipboard paste is supported - Important UI behavior: mode button shows the current angle mode; keyboard focus stays on the hidden input on desktop; clipboard paste is supported
- Note: keep scrolling behavior in mind for the calc-02 demo when changing the stack/UI layout
- Public API: `push`, `pop`, `clear`, `swap`, `remove`, `edit`, `isValidIndex`, `input`, `command`, `getOperationsByCategory`, `getConstants`, `listConstants`, `setConstant`, `removeConstant`, `hasConstant` - Public API: `push`, `pop`, `clear`, `swap`, `remove`, `edit`, `isValidIndex`, `input`, `command`, `getOperationsByCategory`, `getConstants`, `listConstants`, `setConstant`, `removeConstant`, `hasConstant`
- Config: `maxSize`, `base`, `angleMode`, `enabledCommands` - Config: `maxSize`, `base`, `angleMode`, `enabledCommands`
- Commands: arithmetic, stack, trigonometry, constants `pi` and `e`; arithmetic includes `root`; constants can be added or removed dynamically through the core API - Commands: arithmetic, stack, trigonometry, constants `pi` and `e`; arithmetic includes `root`; constants can be added or removed dynamically through the core API
+3 -4
View File
@@ -1,9 +1,8 @@
# State # State
- Core engine: `src/rpn-calculator.js` - Core engine: `src/rpn-calculator.js`
- Active demo: `samples/calc-02/` portrait-only HP48GX layout; display-adjacent button row stays in 4 columns and now shares the same base button styling as the function keys - Default demo: `samples/dev/` classic browser demo; `samples/calc-02/` remains the portrait-first HP48GX-inspired reference
- Mode button shows the current angle mode only; selecting a mode uses a popup menu
- Public API: `push`, `pop`, `clear`, `swap`, `remove`, `edit`, `isValidIndex`, `input`, `command`, `getOperationsByCategory`, `getConstants`, `listConstants`, `setConstant`, `removeConstant`, `hasConstant` - Public API: `push`, `pop`, `clear`, `swap`, `remove`, `edit`, `isValidIndex`, `input`, `command`, `getOperationsByCategory`, `getConstants`, `listConstants`, `setConstant`, `removeConstant`, `hasConstant`
- Config: `maxSize`, `base`, `angleMode`, `enabledCommands` - Config: `maxSize`, `base`, `angleMode`, `enabledCommands`
- Commands: arithmetic, stack, trigonometry, constants `pi`, `e`, `phi`, `g`, and `c`; arithmetic now includes `root` for y-th roots; constants can now be added or removed dynamically through the core API - Commands: arithmetic, stack, trigonometry, constants `pi`, `e`, `phi`, `g`, and `c`; arithmetic includes `root` with `y√x`, `yroot`, and `nroot` aliases; constants can be added or removed dynamically through the core API
- Demo actions: keyboard focus is kept on the hidden input on desktop so typing keeps working; the keypad layout places Enter in the bottom-left, ± in the former Enter position, and Esc before Clear for safety; paste parses clipboard text as a number before pushing it to the stack; Ctrl+V is supported via the hidden input paste event; backspace is ignored when the stack is empty; operation errors are shown as an overlay bar on top of the calculator with a shorter timeout and darker red; the display button row uses a 4-column grid with a spacer cell to preserve alignment - Demo behavior: `samples/dev/` keeps hidden input focus on desktop and supports clipboard paste; `samples/calc-02/` uses compact mode/constants popups, a 4-column display-adjacent row, stack selection/move mode, and paste through the hidden input
- Exports: browser `window.RpnCalculator`, CommonJS `module.exports` - Exports: browser `window.RpnCalculator`, CommonJS `module.exports`
+23 -19
View File
@@ -1,15 +1,16 @@
# RPN Virtual Calculator # RPN Virtual Calculator
A browser-friendly RPN calculator built around a small, generic JavaScript API. A browser-friendly RPN calculator built around a small JavaScript API.
## Overview ## Overview
This project provides: This project provides:
- a reusable JavaScript RPN engine in `src/rpn-calculator.js` - a reusable JavaScript RPN engine in `src/rpn-calculator.js`
- browser demos in `samples/dev/` and `samples/calc-02/` - a classic browser demo in `samples/dev/`
- a command system centralized in a single operation dictionary - a portrait-first HP48GX-inspired demo in `samples/calc-02/`
- a small public API focused on stack operations and generic command dispatch - a centralized command system with aliases
- a compact public API focused on stack operations, editing, and command dispatch
The main class is `RpnCalculator`. The main class is `RpnCalculator`.
@@ -19,9 +20,6 @@ The main class is `RpnCalculator`.
- `samples/dev/index.html`: browser demo entry point - `samples/dev/index.html`: browser demo entry point
- `samples/dev/index.css`: demo styles - `samples/dev/index.css`: demo styles
- `samples/dev/index.js`: demo UI and keyboard logic - `samples/dev/index.js`: demo UI and keyboard logic
- `samples/calc-01/index.html`: active browser demo entry point
- `samples/calc-01/index.css`: demo styles
- `samples/calc-01/index.js`: demo UI and keyboard logic
- `samples/calc-02/index.html`: portrait-first HP48GX-style demo entry point - `samples/calc-02/index.html`: portrait-first HP48GX-style demo entry point
- `samples/calc-02/index.css`: portrait-first demo styles - `samples/calc-02/index.css`: portrait-first demo styles
- `samples/calc-02/index.js`: portrait-first HP48GX-style demo UI and keyboard logic - `samples/calc-02/index.js`: portrait-first HP48GX-style demo UI and keyboard logic
@@ -68,10 +66,10 @@ State exposed on instances:
```js README.md ```js README.md
const calc = new RpnCalculator({ const calc = new RpnCalculator({
maxSize: 2048, maxSize: 2048,
base: 10, base: 10,
angleMode: 'deg', angleMode: 'deg',
enabledCommands: ['add', 'sub', 'mul', 'div'] enabledCommands: ['add', 'sub', 'mul', 'div']
}); });
``` ```
@@ -88,6 +86,9 @@ Available constants:
- `pi` - `pi`
- `e` - `e`
- `phi`
- `g`
- `c`
- plus any user-defined constants added through the engine API - plus any user-defined constants added through the engine API
They can be used through `command(...)`: They can be used through `command(...)`:
@@ -194,10 +195,10 @@ console.log(calc.pop()); // 5
```html README.md ```html README.md
<script src="./src/rpn-calculator.js"></script> <script src="./src/rpn-calculator.js"></script>
<script> <script>
const calc = new RpnCalculator({ angleMode: 'deg' }); const calc = new RpnCalculator({ angleMode: 'deg' });
calc.push(9); calc.push(9);
calc.command('sqrt'); calc.command('sqrt');
console.log(calc.pop()); console.log(calc.pop());
</script> </script>
``` ```
@@ -239,7 +240,7 @@ console.log(calc.pop()); // 0.5
## Demo ## Demo
The active demo lives in `samples/dev/`. The default demo lives in `samples/dev/`.
Main UI features: Main UI features:
@@ -249,7 +250,7 @@ Main UI features:
- angle mode selector for `deg`, `rad`, and `grad` - angle mode selector for `deg`, `rad`, and `grad`
- status pills for `inputValue` and `isEditing` - status pills for `inputValue` and `isEditing`
- grouped panels for `Stack`, `Arithmetic`, `Trigonometry`, and `Constants` - grouped panels for `Stack`, `Arithmetic`, `Trigonometry`, and `Constants`
- error display area - keyboard-friendly hidden input on desktop
## Calc 02 demo ## Calc 02 demo
@@ -274,12 +275,15 @@ The current demo supports:
- `Escape` - `Escape`
- `ArrowUp`, `ArrowDown`, `ArrowRight` - `ArrowUp`, `ArrowDown`, `ArrowRight`
- `+`, `-`, `*`, `/`, `%`, `^` - `+`, `-`, `*`, `/`, `%`, `^`
- `q`, `n`, `r`, `i`, `g`, `l`, `s`, `c`, `S`, `C` - `s`, `S`, `r`, `R`, `v`, `u`
- `x`, `y`, `z`, `t` - `l`, `L`, `n`, `N`, `e`, `E`
- `i`, `o`, `a`, `I`, `O`, `A`
The demo also implements stack selection and stack-item move mode in its UI layer using the public calculator methods. The demo also implements stack selection and stack-item move mode in its UI layer using the public calculator methods.
It keeps the calculator screen focused and updates the visible stack window as the selection moves. It keeps the calculator screen focused and updates the visible stack window as the selection moves.
Note: `calc-02` keyboard shortcuts are the reference for this project.
## Exports ## Exports
`RpnCalculator` is exposed in both environments: `RpnCalculator` is exposed in both environments:
+74 -27
View File
@@ -32,6 +32,7 @@
html, body { html, body {
margin: 0; margin: 0;
min-height: 100%; min-height: 100%;
overflow-y: hidden;
} }
body { body {
@@ -51,25 +52,24 @@ body {
.calculator { .calculator {
width: 100%; width: 100%;
height: auto; max-width: 460px;
display: grid; height: 100vh;
max-height: 100vh;
padding: 8px; padding: 8px;
display: grid;
grid-template-rows: auto auto minmax(0, 1fr);
gap: 8px;
border-radius: 8px; border-radius: 8px;
background: var(--panel); background: var(--panel);
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.08); box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.08);
grid-template-columns: 1fr; justify-content: stretch;
grid-template-rows: auto auto auto auto auto; align-content: start;
row-gap: 6px; overflow: hidden;
grid-template-areas:
"display"
"display-buttons"
"keypad"
"functions"
"trigo";
} }
.display-block, .display-block,
.display-panel, .display-panel,
.display-buttons-panel,
.keypad-panel, .keypad-panel,
.functions-panel, .functions-panel,
.trigo-panel { .trigo-panel {
@@ -79,15 +79,7 @@ body {
box-shadow: none; box-shadow: none;
} }
.display-buttons-panel {
border-radius: 8px;
border: 1px solid rgba(255, 255, 255, 0.06);
background: rgba(6, 10, 16, 0.16);
box-shadow: none;
}
.display-block { .display-block {
grid-area: display;
display: grid; display: grid;
gap: 0; gap: 0;
align-self: start; align-self: start;
@@ -131,10 +123,7 @@ body {
padding-block: 0; padding-block: 0;
} }
.stack-label { /* removed extra first-column styling */
text-align: right;
opacity: 0.78;
}
.stack-value { .stack-value {
min-height: 0; min-height: 0;
@@ -145,11 +134,26 @@ body {
justify-self: end; justify-self: end;
font-size: 20px; font-size: 20px;
} }
.stack-value.is-editing {
letter-spacing: 0.02em;
}
.stack-cell.is-selected {
background: transparent;
border-radius: 6px;
outline: 2px solid rgba(31, 42, 18, 0.2);
outline-offset: -2px;
}
.stack-cell.is-moving {
background: rgba(31, 42, 18, 0.18);
}
.display-buttons-panel { .display-buttons-panel {
grid-area: display-buttons;
padding: 8px; padding: 8px;
align-self: start;
min-height: 0; min-height: 0;
background: rgba(6, 10, 16, 0.16);
} }
.display-buttons-grid { .display-buttons-grid {
@@ -231,10 +235,22 @@ body {
.functions-panel, .functions-panel,
.trigo-panel { .trigo-panel {
padding: 8px; padding: 8px;
align-self: start;
min-height: 0; min-height: 0;
} }
.calculator > .display-block,
.calculator > .display-buttons-panel,
.calculator > .keypad-scroll-area {
justify-self: stretch;
}
.keypad-scroll-area {
min-height: 0;
overflow-y: auto;
scrollbar-width: thin;
scrollbar-color: rgba(46, 105, 135, 1);
}
.status-bar { .status-bar {
position: absolute; position: absolute;
top: 0; top: 0;
@@ -291,7 +307,6 @@ button {
border: 1px solid rgba(14, 18, 25, 0.85); border: 1px solid rgba(14, 18, 25, 0.85);
border-radius: 8px; border-radius: 8px;
padding: 8px 8px; padding: 8px 8px;
font: inherit; font: inherit;
font-weight: 700; font-weight: 700;
color: var(--btnText); color: var(--btnText);
@@ -326,6 +341,7 @@ button:active {
opacity: 0.7; opacity: 0.7;
} }
} }
.stack-copy-button { .stack-copy-button {
padding: 4px; padding: 4px;
min-width: 24px; min-width: 24px;
@@ -376,6 +392,37 @@ button:active {
outline-offset: 2px; 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 { .key-default {
background: linear-gradient(180deg, var(--btnTop), var(--btnBottom)); background: linear-gradient(180deg, var(--btnTop), var(--btnBottom));
color: #eef2f7; color: #eef2f7;
+34 -32
View File
@@ -4,49 +4,51 @@
<meta charset="utf-8"> <meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
<title>HP48GX RPN Calculator</title> <title>HP48GX RPN Calculator</title>
<link rel="stylesheet" href="./index.css"> <link rel="stylesheet" href="./index.css?ver=1">
</head> </head>
<body> <body>
<main class="app-shell"> <main class="app-shell">
<section class="calculator calculator-portrait" aria-label="HP48GX style RPN calculator"> <section class="calculator" aria-label="HP48GX style RPN calculator">
<div class="display-block"> <div class="display-block">
<div class="display-panel"> <div class="display-panel">
<div class="status-bar" id="statusLine" aria-live="polite"></div> <div class="status-bar" id="statusLine" aria-live="polite"></div>
<div class="display-frame"> <div class="display-frame">
<div class="display-grid"> <div class="display-grid">
<div class="stack-cell"><span class="stack-label">T:</span><button type="button" class="stack-copy-button" data-copy-stack="T" aria-label="Copy T value"><svg viewBox="0 0 24 24" aria-hidden="true" focusable="false"><path d="M15.5 4h-7A2.5 2.5 0 0 0 6 6.5V8H5.5A1.5 1.5 0 0 0 4 9.5v8A2.5 2.5 0 0 0 6.5 20H13a1 1 0 0 0 1-1v-1.5H17.5A2.5 2.5 0 0 0 20 15V6.5A2.5 2.5 0 0 0 17.5 4h-2Zm.5 2h1.5a.5.5 0 0 1 .5.5V14a.5.5 0 0 1-.5.5H13V6.5A2.5 2.5 0 0 0 15.5 6Zm-1.5 12H6.5a.5.5 0 0 1-.5-.5v-8a.5.5 0 0 1 .5-.5h7.5V18Zm-1.5-8h-4V8.5A.5.5 0 0 1 9 8h4a.5.5 0 0 1 .5.5V10Z"/></svg></button><span id="stackT" class="stack-value"></span></div> <div class="stack-cell"><span class="stack-label">T:</span><button type="button" class="stack-copy-button" data-copy-stack="T" aria-label="Copy T value"><svg viewBox="0 0 24 24" aria-hidden="true" focusable="false"><path d="M15.5 4h-7A2.5 2.5 0 0 0 6 6.5V8H5.5A1.5 1.5 0 0 0 4 9.5v8A2.5 2.5 0 0 0 6.5 20H13a1 1 0 0 0 1-1v-1.5H17.5A2.5 2.5 0 0 0 20 15V6.5A2.5 2.5 0 0 0 17.5 4h-2Zm.5 2h1.5a.5.5 0 0 1 .5.5V14a.5.5 0 0 1-.5.5H13V6.5A2.5 2.5 0 0 0 15.5 6Zm-1.5 12H6.5a.5.5 0 0 1-.5-.5v-8a.5.5 0 0 1 .5-.5h7.5V18Zm-1.5-8h-4V8.5A.5.5 0 0 1 9 8h4a.5.5 0 0 1 .5.5V10Z"/></svg></button><span id="stackT" class="stack-value"></span></div>
<div class="stack-cell"><span class="stack-label">Z:</span><button type="button" class="stack-copy-button" data-copy-stack="Z" aria-label="Copy Z value"><svg viewBox="0 0 24 24" aria-hidden="true" focusable="false"><path d="M15.5 4h-7A2.5 2.5 0 0 0 6 6.5V8H5.5A1.5 1.5 0 0 0 4 9.5v8A2.5 2.5 0 0 0 6.5 20H13a1 1 0 0 0 1-1v-1.5H17.5A2.5 2.5 0 0 0 20 15V6.5A2.5 2.5 0 0 0 17.5 4h-2Zm.5 2h1.5a.5.5 0 0 1 .5.5V14a.5.5 0 0 1-.5.5H13V6.5A2.5 2.5 0 0 0 15.5 6Zm-1.5 12H6.5a.5.5 0 0 1-.5-.5v-8a.5.5 0 0 1 .5-.5h7.5V18Zm-1.5-8h-4V8.5A.5.5 0 0 1 9 8h4a.5.5 0 0 1 .5.5V10Z"/></svg></button><span id="stackZ" class="stack-value"></span></div> <div class="stack-cell"><span class="stack-label">Z:</span><button type="button" class="stack-copy-button" data-copy-stack="Z" aria-label="Copy Z value"><svg viewBox="0 0 24 24" aria-hidden="true" focusable="false"><path d="M15.5 4h-7A2.5 2.5 0 0 0 6 6.5V8H5.5A1.5 1.5 0 0 0 4 9.5v8A2.5 2.5 0 0 0 6.5 20H13a1 1 0 0 0 1-1v-1.5H17.5A2.5 2.5 0 0 0 20 15V6.5A2.5 2.5 0 0 0 17.5 4h-2Zm.5 2h1.5a.5.5 0 0 1 .5.5V14a.5.5 0 0 1-.5.5H13V6.5A2.5 2.5 0 0 0 15.5 6Zm-1.5 12H6.5a.5.5 0 0 1-.5-.5v-8a.5.5 0 0 1 .5-.5h7.5V18Zm-1.5-8h-4V8.5A.5.5 0 0 1 9 8h4a.5.5 0 0 1 .5.5V10Z"/></svg></button><span id="stackZ" class="stack-value"></span></div>
<div class="stack-cell"><span class="stack-label">Y:</span><button type="button" class="stack-copy-button" data-copy-stack="Y" aria-label="Copy Y value"><svg viewBox="0 0 24 24" aria-hidden="true" focusable="false"><path d="M15.5 4h-7A2.5 2.5 0 0 0 6 6.5V8H5.5A1.5 1.5 0 0 0 4 9.5v8A2.5 2.5 0 0 0 6.5 20H13a1 1 0 0 0 1-1v-1.5H17.5A2.5 2.5 0 0 0 20 15V6.5A2.5 2.5 0 0 0 17.5 4h-2Zm.5 2h1.5a.5.5 0 0 1 .5.5V14a.5.5 0 0 1-.5.5H13V6.5A2.5 2.5 0 0 0 15.5 6Zm-1.5 12H6.5a.5.5 0 0 1-.5-.5v-8a.5.5 0 0 1 .5-.5h7.5V18Zm-1.5-8h-4V8.5A.5.5 0 0 1 9 8h4a.5.5 0 0 1 .5.5V10Z"/></svg></button><span id="stackY" class="stack-value"></span></div> <div class="stack-cell"><span class="stack-label">Y:</span><button type="button" class="stack-copy-button" data-copy-stack="Y" aria-label="Copy Y value"><svg viewBox="0 0 24 24" aria-hidden="true" focusable="false"><path d="M15.5 4h-7A2.5 2.5 0 0 0 6 6.5V8H5.5A1.5 1.5 0 0 0 4 9.5v8A2.5 2.5 0 0 0 6.5 20H13a1 1 0 0 0 1-1v-1.5H17.5A2.5 2.5 0 0 0 20 15V6.5A2.5 2.5 0 0 0 17.5 4h-2Zm.5 2h1.5a.5.5 0 0 1 .5.5V14a.5.5 0 0 1-.5.5H13V6.5A2.5 2.5 0 0 0 15.5 6Zm-1.5 12H6.5a.5.5 0 0 1-.5-.5v-8a.5.5 0 0 1 .5-.5h7.5V18Zm-1.5-8h-4V8.5A.5.5 0 0 1 9 8h4a.5.5 0 0 1 .5.5V10Z"/></svg></button><span id="stackY" class="stack-value"></span></div>
<div class="stack-cell"><span class="stack-label">X:</span><button type="button" class="stack-copy-button" data-copy-stack="X" aria-label="Copy X value"><svg viewBox="0 0 24 24" aria-hidden="true" focusable="false"><path d="M15.5 4h-7A2.5 2.5 0 0 0 6 6.5V8H5.5A1.5 1.5 0 0 0 4 9.5v8A2.5 2.5 0 0 0 6.5 20H13a1 1 0 0 0 1-1v-1.5H17.5A2.5 2.5 0 0 0 20 15V6.5A2.5 2.5 0 0 0 17.5 4h-2Zm.5 2h1.5a.5.5 0 0 1 .5.5V14a.5.5 0 0 1-.5.5H13V6.5A2.5 2.5 0 0 0 15.5 6Zm-1.5 12H6.5a.5.5 0 0 1-.5-.5v-8a.5.5 0 0 1 .5-.5h7.5V18Zm-1.5-8h-4V8.5A.5.5 0 0 1 9 8h4a.5.5 0 0 1 .5.5V10Z"/></svg></button><span id="stackX" class="stack-value"></span></div> <div class="stack-cell"><span class="stack-label">X:</span><button type="button" class="stack-copy-button" data-copy-stack="X" aria-label="Copy X value"><svg viewBox="0 0 24 24" aria-hidden="true" focusable="false"><path d="M15.5 4h-7A2.5 2.5 0 0 0 6 6.5V8H5.5A1.5 1.5 0 0 0 4 9.5v8A2.5 2.5 0 0 0 6.5 20H13a1 1 0 0 0 1-1v-1.5H17.5A2.5 2.5 0 0 0 20 15V6.5A2.5 2.5 0 0 0 17.5 4h-2Zm.5 2h1.5a.5.5 0 0 1 .5.5V14a.5.5 0 0 1-.5.5H13V6.5A2.5 2.5 0 0 0 15.5 6Zm-1.5 12H6.5a.5.5 0 0 1-.5-.5v-8a.5.5 0 0 1 .5-.5h7.5V18Zm-1.5-8h-4V8.5A.5.5 0 0 1 9 8h4a.5.5 0 0 1 .5.5V10Z"/></svg></button><span id="stackX" class="stack-value"></span></div>
</div>
</div> </div>
</div> </div>
</div> </div>
</div>
<div class="display-buttons-panel"> <div class="display-buttons-panel">
<div class="display-buttons-grid"> <div class="display-buttons-grid">
<button id="constButton" class="display-button">π</button> <button id="constButton" class="display-button">π</button>
<button id="pasteButton" class="display-button"><span class="display-button-symbol paste-symbol"></span></button> <button id="pasteButton" class="display-button"><span class="display-button-symbol paste-symbol"></span></button>
<button id="upButton" class="display-button"><span class="display-button-symbol arrow-symbol"></span></button> <button id="upButton" class="display-button"><span class="display-button-symbol arrow-symbol"></span></button>
<button id="modeButton" class="display-button">Mode</button> <button id="modeButton" class="display-button">Mode</button>
<div class="display-button-spacer"></div> <div class="display-button-spacer"></div>
<button id="leftButton" class="display-button display-button-offset"><span class="display-button-symbol arrow-symbol"></span></button> <button id="leftButton" class="display-button display-button-offset"><span class="display-button-symbol arrow-symbol"></span></button>
<button id="downButton" class="display-button"><span class="display-button-symbol arrow-symbol"></span></button> <button id="downButton" class="display-button"><span class="display-button-symbol arrow-symbol"></span></button>
<button id="rightButton" class="display-button"><span class="display-button-symbol arrow-symbol"></span></button> <button id="rightButton" class="display-button"><span class="display-button-symbol arrow-symbol"></span></button>
</div>
</div> </div>
</div>
<div class="keypad-panel"> <div class="keypad-scroll-area">
<div class="keypad-grid" id="keypadGrid"></div> <div class="keypad-panel">
</div> <div class="keypad-grid" id="keypadGrid"></div>
</div>
<div class="functions-panel"> <div class="functions-panel">
<div class="functions-grid" id="functionsGrid"></div> <div class="functions-grid" id="functionsGrid"></div>
</div> </div>
<div class="trigo-panel"> <div class="trigo-panel">
<div class="trigo-grid" id="trigoGrid"></div> <div class="trigo-grid" id="trigoGrid"></div>
</div> </div>
</div>
<input id="hiddenInput" class="hidden-input" type="text" autocomplete="off" aria-hidden="true" tabindex="-1"> <input id="hiddenInput" class="hidden-input" type="text" autocomplete="off" aria-hidden="true" tabindex="-1">
</section> </section>
+344 -75
View File
@@ -30,10 +30,10 @@ const calculatorEl = document.querySelector('.calculator');
const statusLine = document.getElementById('statusLine'); const statusLine = document.getElementById('statusLine');
const keypadKeys = [ const keypadKeys = [
{ label: 'ENTER', action: 'enter', className: 'key-enter' },
{ label: '⎋', action: 'escape', className: 'key-escape' },
{ label: 'C', action: 'clear', className: 'key-danger' }, { label: 'C', action: 'clear', className: 'key-danger' },
{ label: '⌫', action: 'backspace', className: 'key-danger' }, { label: '⌫', action: 'backspace', className: 'key-danger' },
{ label: '⎋', action: 'escape', className: 'key-escape' },
{ label: '⏎', action: 'enter', className: 'key-enter' },
{ label: '7', input: '7', className: 'key-default' }, { label: '7', input: '7', className: 'key-default' },
{ label: '8', input: '8', className: 'key-default' }, { label: '8', input: '8', className: 'key-default' },
{ label: '9', input: '9', className: 'key-default' }, { label: '9', input: '9', className: 'key-default' },
@@ -53,28 +53,28 @@ const keypadKeys = [
]; ];
const functionKeys = [ const functionKeys = [
{ label: 'x²', action: 'sqr', className: 'key-default' }, { label: 'x²', action: 'sqr', className: 'key-default', title: 's' },
{ label: '', action: 'pow', className: 'key-default' }, { label: '√x', action: 'sqrt', className: 'key-default', title: 'r' },
{ label: '1/x', action: 'recip', className: 'key-default' }, { label: '1/x', action: 'recip', className: 'key-default', title: 'x' },
{ label: '%', action: 'mod', className: 'key-default' }, { label: '%', action: 'mod', className: 'key-default' },
{ label: '√x', action: 'sqrt', className: 'key-default' }, { label: '', action: 'pow', className: 'key-default', title: 'S' },
{ label: 'y√x', action: 'root', className: 'key-default' }, { label: 'y√x', action: 'root', className: 'key-default', title: 'R' },
{ label: '10ˣ', action: 'pow10', className: 'key-default' }, { label: '10ˣ', action: 'pow10', className: 'key-default', title: 'd' },
{ label: '', spacer: true }, { label: '', spacer: true },
{ label: 'log', action: 'log', className: 'key-default' }, { label: 'log', action: 'log', className: 'key-default', title: 'l / L' },
{ label: 'ln', action: 'ln', className: 'key-default' }, { label: 'ln', action: 'ln', className: 'key-default', title: 'n / N' },
{ label: 'eˣ', action: 'exp', className: 'key-default' }, { label: 'eˣ', action: 'exp', className: 'key-default', title: 'e / E' },
{ label: '', spacer: true }, { label: '', spacer: true },
]; ];
const trigoKeys = [ const trigoKeys = [
{ label: 'sin', action: 'sin', className: 'key-default' }, { label: 'sin', action: 'sin', className: 'key-default', title: 'i' },
{ label: 'cos', action: 'cos', className: 'key-default' }, { label: 'cos', action: 'cos', className: 'key-default', title: 'o' },
{ label: 'tan', action: 'tan', className: 'key-default' }, { label: 'tan', action: 'tan', className: 'key-default', title: 'a' },
{ label: '', spacer: true }, { label: '', spacer: true },
{ label: 'asin', action: 'asin', className: 'key-default' }, { label: 'asin', action: 'asin', className: 'key-default', title: 'I' },
{ label: 'acos', action: 'acos', className: 'key-default' }, { label: 'acos', action: 'acos', className: 'key-default', title: 'O' },
{ label: 'atan', action: 'atan', className: 'key-default' }, { label: 'atan', action: 'atan', className: 'key-default', title: 'A' },
{ label: '', spacer: true }, { label: '', spacer: true },
]; ];
@@ -94,6 +94,11 @@ function focusInput() {
} }
let statusTimer = null; let statusTimer = null;
let editCursor = 0;
let editRestoreValue = null;
let stackMode = 'normal';
let stackSelection = 0;
let stackMoveSnapshot = null;
function setStatus(message, isError = false, timeoutMs = 1400) { function setStatus(message, isError = false, timeoutMs = 1400) {
if (!statusLine) return; if (!statusLine) return;
@@ -118,27 +123,41 @@ function clearStatus() {
statusLine.classList.remove('is-error'); statusLine.classList.remove('is-error');
} }
function normalizeStack() {
while (calc.stack.length > 4) {
calc.stack.shift();
}
}
function getStackLine(indexFromTop) { function getStackLine(indexFromTop) {
return indexFromTop >= 0 && indexFromTop < calc.stack.length ? calc.stack[indexFromTop] : ''; return indexFromTop >= 0 && indexFromTop < calc.stack.length ? calc.stack[indexFromTop] : '';
} }
function getStackDisplayValue(label) { function getStackDisplayValue(label) {
if (label === 'X') { if (label === 'X' && calc.isEditing) {
return calc.isEditing ? calc.inputValue : (calc.formatNumber(getStackLine(0)) || ''); return calc.inputValue;
} }
if (label === 'Y') { const indexMap = { X: 0, Y: 1, Z: 2, T: 3 };
return calc.isEditing ? (calc.formatNumber(getStackLine(0)) || '') : (calc.formatNumber(getStackLine(1)) || ''); const indexFromTop = calc.isEditing ? Math.max(0, indexMap[label] - 1) : indexMap[label];
return calc.formatNumber(getStackLine(indexFromTop)) || '';
}
function getVisibleStackLabel(label) {
if (stackMode === 'navigation' || stackMode === 'move') {
const indexMap = { X: 0, Y: 1, Z: 2, T: 3 };
return String(indexMap[label]);
} }
if (label === 'Z') { return label;
return calc.isEditing ? (calc.formatNumber(getStackLine(1)) || '') : (calc.formatNumber(getStackLine(2)) || ''); }
function updateStackLabels() {
const stackLabels = ['T', 'Z', 'Y', 'X'];
for (const label of stackLabels) {
const stackCell = stackEls[label].parentElement;
if (!stackCell) continue;
const labelEl = stackCell.querySelector('.stack-label');
if (labelEl) {
labelEl.textContent = `${getVisibleStackLabel(label)}:`;
}
} }
return calc.isEditing ? (calc.formatNumber(getStackLine(2)) || '') : (calc.formatNumber(getStackLine(3)) || ''); }
function stackModeToLabel(index) {
return ['X', 'Y', 'Z', 'T'][Math.max(0, Math.min(3, index))] ?? 'X';
} }
function updateCopyButtons() { function updateCopyButtons() {
@@ -152,40 +171,191 @@ function updateCopyButtons() {
} }
} }
function renderEditValue() {
const cursor = Math.max(0, Math.min(editCursor, calc.inputValue.length));
stackEls.X.innerHTML = `<span class="edit-text">${calc.inputValue.slice(0, cursor)}</span><span class="edit-caret" aria-hidden="true"></span><span class="edit-text">${calc.inputValue.slice(cursor)}</span>`;
}
function render() { function render() {
normalizeStack();
const isPortrait = window.matchMedia('(orientation: portrait)').matches || window.innerWidth <= 860; const isPortrait = window.matchMedia('(orientation: portrait)').matches || window.innerWidth <= 860;
calculatorEl?.classList.toggle('portrait', isPortrait); calculatorEl?.classList.toggle('portrait', isPortrait);
calculatorEl?.classList.toggle('landscape', !isPortrait); calculatorEl?.classList.toggle('landscape', !isPortrait);
stackEls.X.textContent = getStackDisplayValue('X'); const stackLabels = ['T', 'Z', 'Y', 'X'];
stackEls.Y.textContent = getStackDisplayValue('Y'); for (const label of stackLabels) {
stackEls.Z.textContent = getStackDisplayValue('Z'); const isSelected = stackMode !== 'normal' && stackModeToLabel(stackSelection) === label;
stackEls.T.textContent = getStackDisplayValue('T'); const value = label === 'X' && calc.isEditing ? '' : getStackDisplayValue(label);
stackEls[label].textContent = value;
stackEls[label].classList.toggle('is-editing', label === 'X' && calc.isEditing);
stackEls[label].classList.toggle('is-caret-visible', label === 'X' && calc.isEditing);
stackEls[label].parentElement?.classList.toggle('is-selected', isSelected);
stackEls[label].parentElement?.classList.toggle('is-moving', stackMode === 'move' && stackModeToLabel(stackSelection) === label);
}
if (calc.isEditing) {
renderEditValue();
}
updateStackLabels();
updateCopyButtons(); updateCopyButtons();
modeButton.textContent = calc.angleMode; modeButton.textContent = calc.angleMode;
} }
function stopEditing(clearValue = false) {
if (clearValue) {
calc.inputValue = '';
}
calc.isEditing = false;
editCursor = 0;
}
function cancelEditing() {
if (editRestoreValue !== null) {
calc.push(editRestoreValue);
}
editRestoreValue = null;
stopEditing(true);
}
function enterNavigationMode() {
if (calc.isEditing) return;
stackMode = 'navigation';
stackSelection = 0;
stackMoveSnapshot = null;
render();
}
function exitStackMode() {
stackMode = 'normal';
stackSelection = 0;
stackMoveSnapshot = null;
render();
}
function moveNavigationSelection(delta) {
const maxIndex = Math.max(0, calc.stack.length - 1);
stackSelection = Math.max(0, Math.min(maxIndex, stackSelection + delta));
render();
}
function beginStackMove() {
stackMode = 'move';
stackMoveSnapshot = calc.stack.slice();
render();
}
function restoreStackMoveSnapshot() {
if (!stackMoveSnapshot) return;
calc.stack = stackMoveSnapshot.slice();
stackMoveSnapshot = null;
render();
}
function validateStackMove() {
stackMoveSnapshot = null;
stackMode = 'normal';
render();
}
function moveSelectedStackValue(delta) {
const index = stackSelection;
const target = Math.max(0, Math.min(calc.stack.length - 1, index + delta));
if (target === index) return;
const value = calc.stack.splice(index, 1)[0];
calc.stack.splice(target, 0, value);
stackSelection = target;
render();
}
function enterNavigationMode() {
if (calc.isEditing) return;
stackMode = 'navigation';
stackSelection = 0;
render();
}
function exitStackMode() {
stackMode = 'normal';
stackSelection = 0;
stackMoveSnapshot = null;
render();
}
function moveNavigationSelection(delta) {
const maxIndex = Math.max(0, calc.stack.length - 1);
stackSelection = Math.max(0, Math.min(maxIndex, stackSelection + delta));
render();
}
function beginStackMove() {
stackMode = 'move';
stackMoveSnapshot = calc.stack.slice();
render();
}
function restoreStackMoveSnapshot() {
if (!stackMoveSnapshot) return;
calc.stack = stackMoveSnapshot.slice();
stackMoveSnapshot = null;
render();
}
function validateStackMove() {
stackMoveSnapshot = null;
stackMode = 'normal';
render();
}
function moveSelectedStackValue(delta) {
const index = stackSelection;
const target = Math.max(0, Math.min(calc.stack.length - 1, index + delta));
if (target === index) return;
const value = calc.stack.splice(index, 1)[0];
calc.stack.splice(target, 0, value);
stackSelection = target;
render();
}
function moveEditCursor(delta) {
editCursor = Math.max(0, Math.min(calc.inputValue.length, editCursor + delta));
}
function pushEditingValueIfNeeded() { function pushEditingValueIfNeeded() {
if (!calc.isEditing) return; if (!calc.isEditing) return;
if (calc.inputValue !== '') { if (calc.inputValue !== '') {
calc.push(calc.parseInputValue(calc.inputValue)); calc.push(calc.parseInputValue(calc.inputValue));
} }
editRestoreValue = null;
calc.inputValue = ''; calc.inputValue = '';
calc.isEditing = false; calc.isEditing = false;
editCursor = 0;
}
function startEditingFromStackTop() {
if (!calc.isValidIndex(0)) return false;
const value = calc.stack[0];
editRestoreValue = value;
calc.remove(0);
calc.isEditing = true;
calc.inputValue = calc.formatNumber(value);
editCursor = calc.inputValue.length;
return true;
} }
function inputToX(value) { function inputToX(value) {
if (!calc.isEditing) { if (!calc.isEditing) {
calc.isEditing = true; calc.isEditing = true;
calc.inputValue = ''; calc.inputValue = '';
editCursor = 0;
} }
if (value === 'Backspace') { 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 { } else {
calc.inputValue += value; calc.inputValue = `${calc.inputValue.slice(0, editCursor)}${value}${calc.inputValue.slice(editCursor)}`;
editCursor += value.length;
} }
if (calc.inputValue === '') { if (calc.inputValue === '') {
calc.isEditing = false; stopEditing();
} }
} }
@@ -217,11 +387,9 @@ function execute(name) {
} }
} else if (name === 'clear') { } else if (name === 'clear') {
calc.clear(); calc.clear();
calc.inputValue = ''; stopEditing(true);
calc.isEditing = false;
} else if (name === 'escape') { } else if (name === 'escape') {
calc.inputValue = ''; cancelEditing();
calc.isEditing = false;
} else if (name === 'backspace') { } else if (name === 'backspace') {
if (calc.isEditing) { if (calc.isEditing) {
inputToX('Backspace'); inputToX('Backspace');
@@ -235,35 +403,26 @@ function execute(name) {
} }
} else if (name === 'neg') { } else if (name === 'neg') {
if (calc.isEditing) { 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}`;
moveEditCursor(hasSign ? -1 : 1);
} else { } else {
calc.push(calc.pop() * -1); calc.command('neg');
} }
} else if (name === 'pow10') {
pushEditingValueIfNeeded();
const exponent = calc.stack[0];
calc.remove(0);
calc.push(10);
calc.push(exponent);
calc.command('pow');
} else if (name === 'exp') {
pushEditingValueIfNeeded();
const exponent = calc.stack[0];
calc.remove(0);
calc.push(Math.E);
calc.push(exponent);
calc.command('pow');
} else { } else {
pushEditingValueIfNeeded(); pushEditingValueIfNeeded();
calc.command(name); calc.command(name);
} }
if (!calc.isEditing) {
editCursor = 0;
}
render(); render();
} catch (error) { } catch (error) {
setStatus(error?.message || 'Operation error', true); setStatus(error?.message || 'Operation error', true);
} }
} }
function createKeyButton({ label, input, action, spacer, className }) { function createKeyButton({ label, input, action, spacer, className, title }) {
if (spacer) { if (spacer) {
const div = document.createElement('div'); const div = document.createElement('div');
return div; return div;
@@ -272,6 +431,7 @@ function createKeyButton({ label, input, action, spacer, className }) {
button.type = 'button'; button.type = 'button';
button.textContent = label; button.textContent = label;
button.className = className; button.className = className;
if (title) button.title = title;
button.addEventListener('click', () => { button.addEventListener('click', () => {
if (input) { if (input) {
inputToX(input); inputToX(input);
@@ -302,6 +462,53 @@ async function copyStackValue(label) {
function handleKeyboard(event) { function handleKeyboard(event) {
if (event.defaultPrevented) return; if (event.defaultPrevented) return;
const key = event.key; const key = event.key;
if (stackMode === 'navigation') {
if (key === 'ArrowUp') {
event.preventDefault();
moveNavigationSelection(1);
return;
}
if (key === 'ArrowDown') {
event.preventDefault();
moveNavigationSelection(-1);
return;
}
if (key === 'Escape' || key === 'ArrowLeft') {
event.preventDefault();
exitStackMode();
return;
}
if (key === 'Enter') {
event.preventDefault();
beginStackMove();
return;
}
return;
}
if (stackMode === 'move') {
if (key === 'ArrowUp') {
event.preventDefault();
moveSelectedStackValue(1);
return;
}
if (key === 'ArrowDown') {
event.preventDefault();
moveSelectedStackValue(-1);
return;
}
if (key === 'Escape') {
event.preventDefault();
restoreStackMoveSnapshot();
exitStackMode();
return;
}
if (key === 'Enter') {
event.preventDefault();
validateStackMove();
return;
}
return;
}
if (/^[0-9.]$/.test(key)) { if (/^[0-9.]$/.test(key)) {
event.preventDefault(); event.preventDefault();
inputToX(key); inputToX(key);
@@ -319,16 +526,58 @@ function handleKeyboard(event) {
'/': 'div', '/': 'div',
'%': 'mod', '%': 'mod',
'^': 'pow', '^': 'pow',
s: 'sqr',
S: 'pow',
r: 'sqrt',
R: 'root',
x: 'recip',
d: 'pow10',
l: 'log',
L: 'log',
n: 'ln',
N: 'ln',
e: 'exp',
E: 'exp',
i: 'sin',
o: 'cos',
a: 'tan',
I: 'asin',
O: 'acos',
A: 'atan',
}; };
const arrowMap = { if (key === 'ArrowLeft') {
ArrowUp: upButton,
ArrowDown: downButton,
ArrowLeft: leftButton,
ArrowRight: rightButton,
};
if (arrowMap[key]) {
event.preventDefault(); event.preventDefault();
arrowMap[key].click(); if (calc.isEditing) {
moveEditCursor(-1);
render();
return;
}
leftButton.click();
return;
}
if (key === 'ArrowRight') {
event.preventDefault();
if (calc.isEditing) {
moveEditCursor(1);
render();
return;
}
rightButton.click();
return;
}
if (key === 'ArrowUp') {
event.preventDefault();
upButton.click();
return;
}
if (key === 'ArrowDown') {
event.preventDefault();
if (calc.isEditing) {
cancelEditing();
render();
return;
}
downButton.click();
return; return;
} }
if (map[key]) { if (map[key]) {
@@ -427,14 +676,19 @@ hiddenInput.addEventListener('paste', (event) => {
pasteTextIntoStack(text); pasteTextIntoStack(text);
}); });
upButton.addEventListener('click', () => {}); upButton.addEventListener('click', () => {
if (!calc.isEditing && stackMode === 'normal') {
enterNavigationMode();
focusInput();
}
});
const constantLabels = { const constantLabels = {
pi: 'π', pi: 'π',
e: 'e', e: 'e',
phi: 'φ', phi: 'φ',
g: 'g', g: 'g',
c: 'c', c: 'C',
}; };
const constantOrder = ['pi', 'e', 'phi', 'g', 'c']; const constantOrder = ['pi', 'e', 'phi', 'g', 'c'];
@@ -473,19 +727,34 @@ constButton.addEventListener('click', (event) => {
toggleConstMenu(); toggleConstMenu();
}); });
leftButton.addEventListener('click', () => {}); leftButton.addEventListener('click', () => {
if (calc.isEditing) {
moveEditCursor(-1);
render();
focusInput();
return;
}
if (stackMode === 'navigation') {
exitStackMode();
focusInput();
}
});
downButton.addEventListener('click', () => { downButton.addEventListener('click', () => {
if (!calc.isEditing && calc.isValidIndex(0)) { if (!calc.isEditing && startEditingFromStackTop()) {
const value = calc.stack[0];
calc.remove(0);
calc.isEditing = true;
calc.inputValue = calc.formatNumber(value);
render(); render();
focusInput(); focusInput();
} }
}); });
rightButton.addEventListener('click', () => { rightButton.addEventListener('click', () => {
if (calc.isEditing) {
moveEditCursor(1);
render();
focusInput();
return;
}
execute('swap'); execute('swap');
}); });
+8 -8
View File
@@ -24,11 +24,11 @@
``` ```
┌──────────── Keypad ─────────────┐ ┌──────────── Keypad ─────────────┐
| +/- | Clear | Esc | backspace | | Clear | Backspace | Esc | Enter |
| 7 | 8 | 9 | / | | 7 | 8 | 9 | / |
| 4 | 5 | 6 | * | | 4 | 5 | 6 | * |
| 1 | 2 | 3 | - | | 1 | 2 | 3 | - |
| 0 | . | Enter | + | | 0 | . | +/- | + |
└─────────────────────────────────┘ └─────────────────────────────────┘
``` ```
@@ -36,9 +36,9 @@
``` ```
┌──────────── Functions ──────────┐ ┌──────────── Functions ──────────┐
| x^2 | y^x | 1/x | % | | x^2 | √x | 1/x | % |
| √x | y√x | 10^x | | | y^x | y√x | 10^x | |
| log | ln | | | | log | ln | e^x | |
└─────────────────────────────────┘ └─────────────────────────────────┘
``` ```
+13 -1
View File
@@ -49,6 +49,18 @@ class RpnCalculator {
aliases: ['^', 'y^x'], aliases: ['^', 'y^x'],
execute: (calc, a, b) => Math.pow(a, b), execute: (calc, a, b) => Math.pow(a, b),
}, },
pow10: {
category: 'Arithmetic',
argCount: 1,
aliases: ['10^x'],
execute: (calc, a) => Math.pow(10, a),
},
exp: {
category: 'Arithmetic',
argCount: 1,
aliases: ['e^x'],
execute: (calc, a) => Math.exp(a),
},
root: { root: {
category: 'Arithmetic', category: 'Arithmetic',
argCount: 2, argCount: 2,
@@ -195,7 +207,7 @@ class RpnCalculator {
static getOperationsByCategory() { static getOperationsByCategory() {
return { return {
Stack: ['dup', 'drop', 'swap', 'clear', 'enter'], Stack: ['dup', 'drop', 'swap', 'clear', 'enter'],
Arithmetic: ['add', 'sub', 'mul', 'div', 'mod', 'pow', 'root', 'sqr', 'neg', 'sqrt', 'recip', 'log', 'ln'], Arithmetic: ['add', 'sub', 'mul', 'div', 'mod', 'pow', 'pow10', 'exp', 'root', 'sqr', 'neg', 'sqrt', 'recip', 'log', 'ln'],
Trigonometry: ['sin', 'cos', 'tan', 'asin', 'acos', 'atan'], Trigonometry: ['sin', 'cos', 'tan', 'asin', 'acos', 'atan'],
}; };
} }