@gumigumih/react-calculator-input-form 1.1.2 → 1.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -118,29 +118,6 @@ function App() {
118
118
  />
119
119
  ```
120
120
 
121
- ### Calculator オプション
122
-
123
- ```tsx
124
- <Calculator
125
- isOpen={isOpen}
126
- onClose={() => setIsOpen(false)}
127
- onCalculate={handleCalculate}
128
- // 税計算の有効/無効
129
- enableTaxCalculation={false}
130
- // 小数点以下の桁数
131
- decimalPlaces={2}
132
- // 数値フォーマットオプション
133
- numberFormatOptions={{
134
- thousandSeparator: true,
135
- allowNegative: false,
136
- allowLeadingZeros: false,
137
- decimalScale: 2,
138
- prefix: "¥",
139
- suffix: ""
140
- }}
141
- />
142
- ```
143
-
144
121
  ## 🔗 react-number-format対応
145
122
 
146
123
  このプラグインは**react-number-format**ライブラリと完全に統合されており、豊富な数値フォーマット機能を提供します。
@@ -200,8 +177,6 @@ react-number-formatの**すべてのプロパティ**が利用可能です。詳
200
177
 
201
178
  ## 📋 Props
202
179
 
203
- ### CalculatorInputForm Props
204
-
205
180
  | プロパティ | 型 | 必須 | デフォルト | 説明 |
206
181
  |------------|----|------|------------|------|
207
182
  | `value` | string | ✓ | - | 入力値 |
@@ -214,23 +189,8 @@ react-number-formatの**すべてのプロパティ**が利用可能です。詳
214
189
  | `decimalPlaces` | number | | 6 | 小数点以下の最大桁数 |
215
190
  | `numberFormatOptions` | object | | {} | 数値フォーマットの詳細設定 |
216
191
 
217
- ### Calculator Props
218
-
219
- | プロパティ | 型 | 必須 | デフォルト | 説明 |
220
- |------------|----|------|------------|------|
221
- | `isOpen` | boolean | ✓ | - | 電卓の表示/非表示 |
222
- | `onClose` | () => void | ✓ | - | 電卓を閉じる時のコールバック |
223
- | `onCalculate` | (value: string) => void | ✓ | - | 計算完了時のコールバック |
224
- | `initialValue` | string | | "" | 初期値 |
225
- | `title` | string | | "金額入力" | モーダルのタイトル |
226
- | `description` | string | | "税込・税抜や小数計算に対応" | モーダルの説明文 |
227
- | `enableTaxCalculation` | boolean | | true | 税計算機能の有効/無効 |
228
- | `decimalPlaces` | number | | 6 | 小数点以下の最大桁数 |
229
- | `numberFormatOptions` | object | | {} | 数値フォーマットの詳細設定(react-number-formatの全オプションが利用可能) |
230
-
231
192
  ## ⌨️ キーボードショートカット
232
193
 
233
- - `0-9`: 数字入力
234
194
  - `+`, `-`, `*`, `/`: 演算子
235
195
  - `Enter`, `=`: 計算実行
236
196
  - `Escape`: 電卓を閉じる
@@ -10,4 +10,4 @@ export interface CalculatorProps {
10
10
  numberFormatOptions?: any;
11
11
  placeholder?: string;
12
12
  }
13
- export declare const Calculator: ({ isOpen, onClose, onCalculate, initialValue, title, description, enableTaxCalculation, decimalPlaces, numberFormatOptions, }: CalculatorProps) => import("react").ReactPortal | null;
13
+ export declare const Calculator: ({ isOpen, onClose, onCalculate, initialValue, title, description, enableTaxCalculation, decimalPlaces, numberFormatOptions, placeholder, }: CalculatorProps) => import("react").ReactPortal | null;
package/dist/index.esm.js CHANGED
@@ -6379,7 +6379,7 @@ function NumericFormat(props) {
6379
6379
  }
6380
6380
 
6381
6381
  const CalculatorDisplay = ({ value, error, inputRef, editable, placeholder, onChange, numberFormatOptions = {} }) => {
6382
- return (jsxs(Fragment, { children: [editable ? (jsx(NumericFormat, { className: "calculator-display-input", value: value, onValueChange: (vals) => onChange?.(vals.value), placeholder: placeholder ?? '数値を入力', inputMode: "decimal", ...numberFormatOptions })) : (jsx("div", { className: "calculator-display-input", children: jsx("div", { ref: inputRef, style: { overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }, children: value ? (jsx(NumericFormat, { value: value, displayType: "text", ...numberFormatOptions })) : '' }) })), error && jsx("div", { className: "calculator-error", children: error })] }));
6382
+ return (jsxs(Fragment, { children: [editable ? (jsx(NumericFormat, { className: "calculator-display-input", value: value, onValueChange: (vals) => onChange?.(vals.value), placeholder: placeholder ?? '数値を入力', inputMode: "decimal", ...numberFormatOptions })) : (jsx("div", { className: "calculator-display-input", children: jsx("div", { ref: inputRef, style: { overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }, children: value ? (jsx(NumericFormat, { value: value, displayType: "text", ...numberFormatOptions })) : (jsx("span", { className: "text-gray-400", children: placeholder ?? '0' })) }) })), error && jsx("div", { className: "calculator-error", children: error })] }));
6383
6383
  };
6384
6384
 
6385
6385
  const CalculatorKeypad = ({ onButtonClick, onEqual, onDecide, onTaxInclude, onTaxExclude, enableTaxCalculation = true, decimalPlaces = 6 }) => {
@@ -6446,11 +6446,11 @@ function calculateExpression(expr) {
6446
6446
  return '0';
6447
6447
  }
6448
6448
  }
6449
- const Calculator = ({ isOpen, onClose, onCalculate, initialValue = '', title, description, enableTaxCalculation = false, decimalPlaces = 6, numberFormatOptions = {},
6450
- // placeholder removed - not used
6451
- }) => {
6452
- const [input, setInput] = useState(initialValue || '');
6449
+ const Calculator = ({ isOpen, onClose, onCalculate, initialValue = '', title, description, enableTaxCalculation = false, decimalPlaces = 6, numberFormatOptions = {}, placeholder, }) => {
6450
+ const [expression, setExpression] = useState(initialValue || '');
6451
+ const [displayValue, setDisplayValue] = useState(initialValue || '');
6453
6452
  const [error, setError] = useState('');
6453
+ const [isWaitingForOperand, setIsWaitingForOperand] = useState(false);
6454
6454
  const [mounted, setMounted] = useState(false);
6455
6455
  const inputRef = useRef(null);
6456
6456
  // DOM要素の存在確認
@@ -6460,7 +6460,9 @@ const Calculator = ({ isOpen, onClose, onCalculate, initialValue = '', title, de
6460
6460
  }, []);
6461
6461
  useEffect(() => {
6462
6462
  if (isOpen) {
6463
- setInput(initialValue || '');
6463
+ setExpression(initialValue || '');
6464
+ setDisplayValue(initialValue || '');
6465
+ setIsWaitingForOperand(false);
6464
6466
  setError('');
6465
6467
  }
6466
6468
  }, [isOpen, initialValue]);
@@ -6504,84 +6506,136 @@ const Calculator = ({ isOpen, onClose, onCalculate, initialValue = '', title, de
6504
6506
  };
6505
6507
  window.addEventListener('keydown', handleKeyDown);
6506
6508
  return () => window.removeEventListener('keydown', handleKeyDown);
6507
- }, [isOpen, input]);
6509
+ }, [isOpen, expression, displayValue]);
6510
+ const handleDisplayChange = (newValue) => {
6511
+ setError('');
6512
+ setDisplayValue(newValue);
6513
+ if (isWaitingForOperand) {
6514
+ // 演算子入力後の直接入力の場合、式を更新しない
6515
+ // If an operator was just entered, and user types directly,
6516
+ // the new value is the start of the next operand.
6517
+ // Append it to the expression.
6518
+ setExpression(expression + newValue);
6519
+ setIsWaitingForOperand(false); // No longer waiting for operand
6520
+ }
6521
+ else {
6522
+ // 既存の式の最後の数値を置き換える
6523
+ const newExpression = expression.replace(/[\d.]+$/, newValue);
6524
+ setExpression(newExpression);
6525
+ }
6526
+ };
6508
6527
  const handleButtonClick = (val) => {
6509
6528
  setError('');
6510
6529
  if (val === 'C') {
6511
- setInput('');
6530
+ setExpression('');
6531
+ setDisplayValue('');
6532
+ setIsWaitingForOperand(false);
6512
6533
  }
6513
6534
  else if (val === '←') {
6514
- setInput((prev) => prev.slice(0, -1));
6535
+ if (displayValue.length > 0) {
6536
+ const newDisplayValue = displayValue.slice(0, -1);
6537
+ setDisplayValue(newDisplayValue);
6538
+ setExpression((prev) => prev.slice(0, -1));
6539
+ }
6515
6540
  }
6516
6541
  else if (val === '.') {
6517
- setInput((prev) => {
6518
- if (!prev)
6519
- return '0.';
6520
- const lastSegment = prev.split(/[+\-×÷]/).pop() || '';
6521
- if (lastSegment.includes('.'))
6522
- return prev;
6523
- return prev + '.';
6524
- });
6542
+ if (!displayValue.includes('.')) {
6543
+ const newDisplayValue = displayValue ? displayValue + '.' : '0.';
6544
+ setDisplayValue(newDisplayValue);
6545
+ if (isWaitingForOperand) {
6546
+ setExpression(expression + newDisplayValue);
6547
+ setIsWaitingForOperand(false);
6548
+ }
6549
+ else {
6550
+ setExpression(expression + (displayValue ? '.' : '0.'));
6551
+ // Append decimal to the last number in the expression
6552
+ const lastNumberRegex = /([\d.]+)$/;
6553
+ setExpression(expression.replace(lastNumberRegex, (match) => match + '.'));
6554
+ }
6555
+ }
6525
6556
  }
6526
6557
  else if (['+', '-', '×', '÷'].includes(val)) {
6527
- if (!input || /[+\-×÷]$/.test(input))
6528
- return;
6529
- setInput((prev) => prev + val);
6558
+ if (expression) {
6559
+ if (/[+\-×÷]$/.test(expression)) {
6560
+ setExpression(expression.slice(0, -1) + val);
6561
+ }
6562
+ else {
6563
+ const result = calculateExpression(expression);
6564
+ setExpression(result + val);
6565
+ setDisplayValue(result);
6566
+ }
6567
+ setIsWaitingForOperand(true);
6568
+ }
6530
6569
  }
6531
6570
  else {
6532
- setInput((prev) => (prev === '0' ? val : prev + val));
6571
+ if (isWaitingForOperand) {
6572
+ setDisplayValue(val);
6573
+ setExpression(expression + val);
6574
+ setIsWaitingForOperand(false);
6575
+ }
6576
+ else {
6577
+ const newDisplayValue = displayValue === '0' ? val : displayValue + val;
6578
+ setDisplayValue(newDisplayValue);
6579
+ setExpression(expression === '0' ? val : expression + val);
6580
+ }
6533
6581
  }
6534
6582
  };
6535
6583
  const handleEqual = () => {
6536
- if (!input)
6584
+ if (!expression)
6537
6585
  return;
6538
- const result = calculateExpression(input);
6539
- setInput(result);
6586
+ const result = calculateExpression(expression);
6587
+ setExpression(result);
6588
+ setDisplayValue(result);
6589
+ setIsWaitingForOperand(false);
6540
6590
  };
6541
6591
  const handleDecide = () => {
6542
- if (!input) {
6592
+ if (!expression) {
6543
6593
  setError('金額を入力してください');
6544
6594
  return;
6545
6595
  }
6546
- const result = calculateExpression(input);
6596
+ const result = calculateExpression(expression);
6547
6597
  onCalculate(result);
6548
6598
  onClose();
6549
6599
  };
6550
6600
  const handleTaxInclude = (rate) => {
6551
6601
  if (!enableTaxCalculation)
6552
6602
  return;
6553
- if (!input) {
6603
+ if (!expression) {
6554
6604
  setError('金額を入力してください');
6555
6605
  return;
6556
6606
  }
6557
- const currentValue = parseFloat(calculateExpression(input));
6607
+ const currentValue = parseFloat(calculateExpression(expression));
6558
6608
  if (isNaN(currentValue)) {
6559
6609
  setError('有効な金額を入力してください');
6560
6610
  return;
6561
6611
  }
6562
6612
  const taxIncluded = currentValue * (1 + rate);
6563
- setInput(normalizeNumberString(taxIncluded, decimalPlaces));
6613
+ const result = normalizeNumberString(taxIncluded, decimalPlaces);
6614
+ setExpression(result);
6615
+ setDisplayValue(result);
6564
6616
  setError('');
6565
6617
  };
6566
6618
  const handleTaxExclude = (rate) => {
6567
6619
  if (!enableTaxCalculation)
6568
6620
  return;
6569
- if (!input) {
6621
+ if (!expression) {
6570
6622
  setError('金額を入力してください');
6571
6623
  return;
6572
6624
  }
6573
- const currentValue = parseFloat(calculateExpression(input));
6625
+ const currentValue = parseFloat(calculateExpression(expression));
6574
6626
  if (isNaN(currentValue)) {
6575
6627
  setError('有効な金額を入力してください');
6576
6628
  return;
6577
6629
  }
6578
6630
  const taxExcluded = currentValue / (1 + rate);
6579
- setInput(normalizeNumberString(taxExcluded, decimalPlaces));
6631
+ const result = normalizeNumberString(taxExcluded, decimalPlaces);
6632
+ setExpression(result);
6633
+ setDisplayValue(result);
6580
6634
  setError('');
6581
6635
  };
6582
6636
  if (!isOpen || !mounted)
6583
6637
  return null;
6584
- const modal = (jsx("div", { className: "calculator-overlay", children: jsxs("div", { className: "calculator-modal", children: [title || description ? (jsxs("div", { className: "calculator-header", children: [jsxs("div", { children: [title && jsx("h2", { className: "calculator-title", children: title }), description && jsx("p", { className: "calculator-subtitle", children: description })] }), jsx("button", { onClick: onClose, className: "calculator-close-button", "aria-label": "\u9589\u3058\u308B", children: jsx(Icon, { icon: faTimes, className: "w-5 h-5" }) })] })) : (jsxs("div", { className: "calculator-header", children: [jsx("div", {}), jsx("button", { onClick: onClose, className: "calculator-close-button", "aria-label": "\u9589\u3058\u308B", children: jsx(Icon, { icon: faTimes, className: "w-5 h-5" }) })] })), jsx("div", { className: "calculator-display", children: jsx(CalculatorDisplay, { value: input, error: error, inputRef: inputRef, editable: true, onChange: (v) => setInput(v), numberFormatOptions: numberFormatOptions }) }), jsx(CalculatorKeypad, { onButtonClick: handleButtonClick, onEqual: handleEqual, onDecide: handleDecide, onTaxInclude: handleTaxInclude, onTaxExclude: handleTaxExclude, enableTaxCalculation: enableTaxCalculation, decimalPlaces: decimalPlaces })] }) }));
6638
+ const modal = (jsx("div", { className: "calculator-overlay", children: jsxs("div", { className: "calculator-modal", children: [title || description ? (jsxs("div", { className: "calculator-header", children: [jsxs("div", { children: [title && jsx("h2", { className: "calculator-title", children: title }), description && jsx("p", { className: "calculator-subtitle", children: description })] }), jsx("button", { onClick: onClose, className: "calculator-close-button", "aria-label": "\u9589\u3058\u308B", children: jsx(Icon, { icon: faTimes, className: "w-5 h-5" }) })] })) : (jsxs("div", { className: "calculator-header", children: [jsx("div", {}), jsx("button", { onClick: onClose, className: "calculator-close-button", "aria-label": "\u9589\u3058\u308B", children: jsx(Icon, { icon: faTimes, className: "w-5 h-5" }) })] })), jsx("div", { className: "calculator-display", children: jsx(CalculatorDisplay, { value: displayValue, error: error, inputRef: inputRef, editable: true, onChange: handleDisplayChange, numberFormatOptions: numberFormatOptions, placeholder: placeholder }) }), jsx(CalculatorKeypad, { onButtonClick: handleButtonClick, onEqual: handleEqual, onDecide: handleDecide, onTaxInclude: handleTaxInclude, onTaxExclude: handleTaxExclude, enableTaxCalculation: enableTaxCalculation, decimalPlaces: decimalPlaces })] }) }));
6585
6639
  // DOM要素の存在確認を行ってからポータルを作成
6586
6640
  if (typeof document !== 'undefined' && document.body) {
6587
6641
  return createPortal(modal, document.body);