@goadminuiux/calculator 0.0.0-stage → 1.0.0
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/CHANGELOG.md +19 -0
- package/LICENSE +21 -0
- package/README.md +223 -2
- package/dist/calculator.esm.js +1049 -0
- package/dist/calculator.js +1234 -0
- package/dist/calculator.min.js +18 -0
- package/package.json +50 -4
- package/src/calculator.js +641 -0
- package/src/formatter.js +88 -0
- package/src/index.js +434 -0
package/src/formatter.js
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @adminuiux/calculator - Formatter Module
|
|
3
|
+
* Zero-dependency numerical formatting, parsing, and symbol conversion utilities.
|
|
4
|
+
*
|
|
5
|
+
* @license MIT
|
|
6
|
+
* @copyright AdminUIUX
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
'use strict';
|
|
10
|
+
|
|
11
|
+
export const Formatter = {
|
|
12
|
+
/**
|
|
13
|
+
* Round a numeric value to a specified decimal precision, avoiding IEEE 754 floating point artifacts.
|
|
14
|
+
* @param {number} value
|
|
15
|
+
* @param {number} [precision=8]
|
|
16
|
+
* @returns {number}
|
|
17
|
+
*/
|
|
18
|
+
round(value, precision = 8) {
|
|
19
|
+
if (!isFinite(value) || isNaN(value)) return 0;
|
|
20
|
+
const p = Math.pow(10, precision);
|
|
21
|
+
return Math.round((value + Number.EPSILON) * p) / p;
|
|
22
|
+
},
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Format a number or numeric string with thousand separators and optional currency symbol.
|
|
26
|
+
* @param {number|string} num
|
|
27
|
+
* @param {Object} [options={}]
|
|
28
|
+
* @param {string} [options.currency=''] - Currency prefix (e.g., '$', '€', 'USD')
|
|
29
|
+
* @param {number} [options.precision] - Optional fixed decimal places to round to
|
|
30
|
+
* @returns {string}
|
|
31
|
+
*/
|
|
32
|
+
format(num, options = {}) {
|
|
33
|
+
if (num === null || num === undefined || num === '') return '';
|
|
34
|
+
const parsed = typeof num === 'number' ? num : parseFloat(num);
|
|
35
|
+
if (isNaN(parsed)) return 'Error';
|
|
36
|
+
|
|
37
|
+
let targetNum = num;
|
|
38
|
+
if (typeof options.precision === 'number') {
|
|
39
|
+
targetNum = this.round(parsed, options.precision);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const str = String(targetNum);
|
|
43
|
+
if (str.includes('e') || str.includes('E')) return str;
|
|
44
|
+
|
|
45
|
+
const parts = str.split('.');
|
|
46
|
+
const intPart = parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, ',');
|
|
47
|
+
const formatted = parts.length > 1 ? `${intPart}.${parts[1]}` : intPart;
|
|
48
|
+
|
|
49
|
+
if (options.currency) {
|
|
50
|
+
return `${options.currency} ${formatted}`;
|
|
51
|
+
}
|
|
52
|
+
return formatted;
|
|
53
|
+
},
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Parse formatted string (e.g. "$ 1,234.56") into a standard JavaScript number.
|
|
57
|
+
* @param {string|number} str
|
|
58
|
+
* @returns {number}
|
|
59
|
+
*/
|
|
60
|
+
parse(str) {
|
|
61
|
+
if (typeof str === 'number') return isNaN(str) ? 0 : str;
|
|
62
|
+
if (!str || typeof str !== 'string') return 0;
|
|
63
|
+
const clean = str.replace(/[^0-9.-]/g, '');
|
|
64
|
+
const val = parseFloat(clean);
|
|
65
|
+
return isNaN(val) ? 0 : val;
|
|
66
|
+
},
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Convert standard code operators to human-readable math symbols.
|
|
70
|
+
* @param {string} op
|
|
71
|
+
* @returns {string}
|
|
72
|
+
*/
|
|
73
|
+
getOperatorSymbol(op) {
|
|
74
|
+
switch (op) {
|
|
75
|
+
case '*':
|
|
76
|
+
case 'x':
|
|
77
|
+
return '×';
|
|
78
|
+
case '/':
|
|
79
|
+
return '÷';
|
|
80
|
+
case '-':
|
|
81
|
+
return '−';
|
|
82
|
+
default:
|
|
83
|
+
return op;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
export default Formatter;
|
package/src/index.js
ADDED
|
@@ -0,0 +1,434 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @adminuiux/calculator - Entry Point Module
|
|
3
|
+
* Universal export of Calculator, Formatter, StreamCalculator, and AdminUIUXCalculator.
|
|
4
|
+
*
|
|
5
|
+
* @license MIT
|
|
6
|
+
* @copyright AdminUIUX
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
'use strict';
|
|
10
|
+
|
|
11
|
+
import { Formatter } from './formatter.js';
|
|
12
|
+
import { Calculator, StreamCalculator } from './calculator.js';
|
|
13
|
+
|
|
14
|
+
export class AdminUIUXCalculator {
|
|
15
|
+
constructor(element, config = {}) {
|
|
16
|
+
if (typeof element === 'string') {
|
|
17
|
+
this.wrapper = document.querySelector(element);
|
|
18
|
+
} else {
|
|
19
|
+
this.wrapper = element;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
if (!this.wrapper) return;
|
|
23
|
+
if (this.wrapper._calculatorInstance) return;
|
|
24
|
+
this.wrapper._calculatorInstance = this;
|
|
25
|
+
|
|
26
|
+
this.config = Object.assign({
|
|
27
|
+
type: this.wrapper.getAttribute('data-calculator-type') || this.wrapper.getAttribute('data-calendar-type') || this.wrapper.getAttribute('data-calc-mode') || 'basic',
|
|
28
|
+
precision: parseInt(this.wrapper.getAttribute('data-calc-precision'), 10) || (config.precision ?? 8),
|
|
29
|
+
angleMode: 'deg',
|
|
30
|
+
currency: this.wrapper.getAttribute('data-calc-currency') || config.currency || '',
|
|
31
|
+
taxRate: config.taxRate ?? 0.18,
|
|
32
|
+
tipRates: config.tipRates ?? [10, 15, 20],
|
|
33
|
+
quickPresets: config.quickPresets ?? [50, 100, 500, 1000],
|
|
34
|
+
enableKeyboard: config.enableKeyboard ?? true,
|
|
35
|
+
showTape: config.showTape ?? false,
|
|
36
|
+
onCalculate: config.onCalculate || null
|
|
37
|
+
}, config);
|
|
38
|
+
|
|
39
|
+
this.engine = new StreamCalculator(this.config);
|
|
40
|
+
this.hasInteracted = false;
|
|
41
|
+
this.memory = 0;
|
|
42
|
+
|
|
43
|
+
// Cache DOM Elements
|
|
44
|
+
this.displayEl = this.wrapper.querySelector('.calculator-display, [data-calc-display]');
|
|
45
|
+
this.historyEl = this.wrapper.querySelector('.calculator-history, [data-calc-history]');
|
|
46
|
+
this.memoryBadge = this.wrapper.querySelector('[data-calc-memory-badge]');
|
|
47
|
+
this.angleModeBtn = this.wrapper.querySelector('[data-calc-angle-toggle]');
|
|
48
|
+
this.tapeListEl = this.wrapper.querySelector('.calculator-tape, [data-calc-tape]');
|
|
49
|
+
|
|
50
|
+
this.initEventListeners();
|
|
51
|
+
this.updateDisplay();
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
initEventListeners() {
|
|
55
|
+
this.wrapper.querySelectorAll('[data-calc-action], [data-calculator-perfom], [data-calculator-perform]').forEach(btn => {
|
|
56
|
+
btn.addEventListener('click', (e) => {
|
|
57
|
+
e.preventDefault();
|
|
58
|
+
const performExpr = btn.getAttribute('data-calculator-perfom') || btn.getAttribute('data-calculator-perform');
|
|
59
|
+
if (performExpr) {
|
|
60
|
+
this.executePerform(performExpr);
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
const action = btn.getAttribute('data-calc-action');
|
|
64
|
+
const val = btn.getAttribute('data-val');
|
|
65
|
+
this.handleAction(action, val);
|
|
66
|
+
});
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
if (this.config.enableKeyboard) {
|
|
70
|
+
this.wrapper.addEventListener('keydown', (e) => {
|
|
71
|
+
this.handleKeyDown(e);
|
|
72
|
+
});
|
|
73
|
+
if (!this.wrapper.hasAttribute('tabindex')) {
|
|
74
|
+
this.wrapper.setAttribute('tabindex', '0');
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
handleKeyDown(e) {
|
|
80
|
+
if (['input', 'textarea'].includes(document.activeElement?.tagName?.toLowerCase())) return;
|
|
81
|
+
|
|
82
|
+
const key = e.key;
|
|
83
|
+
if (!isNaN(key)) {
|
|
84
|
+
e.preventDefault();
|
|
85
|
+
this.handleAction('num', key);
|
|
86
|
+
} else if (key === '.') {
|
|
87
|
+
e.preventDefault();
|
|
88
|
+
this.handleAction('num', '.');
|
|
89
|
+
} else if (key === '+' || key === '-' || key === '*' || key === '/') {
|
|
90
|
+
e.preventDefault();
|
|
91
|
+
this.handleAction('operator', key);
|
|
92
|
+
} else if (key === 'Enter' || key === '=') {
|
|
93
|
+
e.preventDefault();
|
|
94
|
+
this.handleAction('equals');
|
|
95
|
+
} else if (key === 'Backspace') {
|
|
96
|
+
e.preventDefault();
|
|
97
|
+
this.handleAction('backspace');
|
|
98
|
+
} else if (key === 'Escape') {
|
|
99
|
+
e.preventDefault();
|
|
100
|
+
this.handleAction('clear');
|
|
101
|
+
} else if (key === '%') {
|
|
102
|
+
e.preventDefault();
|
|
103
|
+
this.handleAction('percent');
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
handleAction(action, val) {
|
|
108
|
+
this.hasInteracted = true;
|
|
109
|
+
switch (action) {
|
|
110
|
+
case 'num':
|
|
111
|
+
this.engine.inputDigit(val);
|
|
112
|
+
break;
|
|
113
|
+
case 'operator':
|
|
114
|
+
this.engine.inputOperator(val);
|
|
115
|
+
break;
|
|
116
|
+
case 'equals':
|
|
117
|
+
this.engine.inputEquals();
|
|
118
|
+
this.syncTape();
|
|
119
|
+
break;
|
|
120
|
+
case 'clear':
|
|
121
|
+
this.engine.clearAll();
|
|
122
|
+
break;
|
|
123
|
+
case 'clear-entry':
|
|
124
|
+
case 'backspace':
|
|
125
|
+
this.engine.backspace();
|
|
126
|
+
break;
|
|
127
|
+
case 'negate':
|
|
128
|
+
this.engine.toggleSign();
|
|
129
|
+
break;
|
|
130
|
+
case 'percent':
|
|
131
|
+
this.engine.applyPercent();
|
|
132
|
+
break;
|
|
133
|
+
case 'scientific':
|
|
134
|
+
this.applyScientific(val);
|
|
135
|
+
break;
|
|
136
|
+
case 'memory':
|
|
137
|
+
this.handleMemory(val);
|
|
138
|
+
break;
|
|
139
|
+
case 'angle-toggle':
|
|
140
|
+
this.toggleAngleMode();
|
|
141
|
+
break;
|
|
142
|
+
case 'preset':
|
|
143
|
+
this.applyPreset(parseFloat(val));
|
|
144
|
+
break;
|
|
145
|
+
case 'tax-add':
|
|
146
|
+
this.applyTax(true);
|
|
147
|
+
break;
|
|
148
|
+
case 'tax-sub':
|
|
149
|
+
this.applyTax(false);
|
|
150
|
+
break;
|
|
151
|
+
case 'tip':
|
|
152
|
+
this.applyTip(parseFloat(val));
|
|
153
|
+
break;
|
|
154
|
+
case 'discount':
|
|
155
|
+
this.applyDiscount(parseFloat(val));
|
|
156
|
+
break;
|
|
157
|
+
}
|
|
158
|
+
this.updateDisplay();
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
applyScientific(func) {
|
|
162
|
+
const val = this.engine.parseCurrent();
|
|
163
|
+
let res = val;
|
|
164
|
+
switch (func) {
|
|
165
|
+
case 'sin': res = Calculator.sin(val, this.config.angleMode, this.config.precision); break;
|
|
166
|
+
case 'cos': res = Calculator.cos(val, this.config.angleMode, this.config.precision); break;
|
|
167
|
+
case 'tan': res = Calculator.tan(val, this.config.angleMode, this.config.precision); break;
|
|
168
|
+
case 'asin': res = Calculator.asin(val, this.config.angleMode, this.config.precision); break;
|
|
169
|
+
case 'acos': res = Calculator.acos(val, this.config.angleMode, this.config.precision); break;
|
|
170
|
+
case 'atan': res = Calculator.atan(val, this.config.angleMode, this.config.precision); break;
|
|
171
|
+
case 'sqrt': res = Calculator.sqrt(val, this.config.precision); break;
|
|
172
|
+
case 'sqr': res = Calculator.sqr(val, this.config.precision); break;
|
|
173
|
+
case 'cube': res = Calculator.cube(val, this.config.precision); break;
|
|
174
|
+
case 'log': res = Calculator.log(val, this.config.precision); break;
|
|
175
|
+
case 'ln': res = Calculator.ln(val, this.config.precision); break;
|
|
176
|
+
case 'exp': res = Calculator.exp(val, this.config.precision); break;
|
|
177
|
+
case '1/x': res = Calculator.reciprocal(val, this.config.precision); break;
|
|
178
|
+
case 'abs': res = Calculator.abs(val); break;
|
|
179
|
+
case 'pi': res = Formatter.round(Math.PI, this.config.precision); break;
|
|
180
|
+
case 'e': res = Formatter.round(Math.E, this.config.precision); break;
|
|
181
|
+
case 'fact': res = Calculator.factorial(val); break;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
this.engine.currentInput = String(res);
|
|
185
|
+
if (this.engine.hasJustCalculated) {
|
|
186
|
+
this.engine.historyTokens = [`${func}(${Formatter.format(val)}) =`];
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
handleMemory(action) {
|
|
191
|
+
const val = this.engine.parseCurrent();
|
|
192
|
+
switch (action) {
|
|
193
|
+
case 'mc': this.memory = 0; break;
|
|
194
|
+
case 'mr':
|
|
195
|
+
this.engine.currentInput = String(this.memory);
|
|
196
|
+
this.engine.hasJustCalculated = false;
|
|
197
|
+
break;
|
|
198
|
+
case 'm+': this.memory += val; break;
|
|
199
|
+
case 'm-': this.memory -= val; break;
|
|
200
|
+
case 'ms': this.memory = val; break;
|
|
201
|
+
}
|
|
202
|
+
if (this.memoryBadge) {
|
|
203
|
+
this.memoryBadge.style.display = this.memory !== 0 ? 'inline-block' : 'none';
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
toggleAngleMode() {
|
|
208
|
+
this.config.angleMode = this.config.angleMode === 'deg' ? 'rad' : 'deg';
|
|
209
|
+
if (this.angleModeBtn) {
|
|
210
|
+
this.angleModeBtn.textContent = this.config.angleMode.toUpperCase();
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
applyPreset(amount) {
|
|
215
|
+
const current = this.engine.parseCurrent();
|
|
216
|
+
this.engine.currentInput = String(current + amount);
|
|
217
|
+
this.engine.hasJustCalculated = false;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
applyTax(isAdd) {
|
|
221
|
+
const current = this.engine.parseCurrent();
|
|
222
|
+
const rate = this.config.taxRate;
|
|
223
|
+
const result = Calculator.tax(current, rate, isAdd, this.config.precision);
|
|
224
|
+
this.engine.currentInput = String(result);
|
|
225
|
+
this.engine.historyTokens = [`${Formatter.format(current)} ${isAdd ? '+' : '-'} ${Math.round(rate * 100)}% Tax =`];
|
|
226
|
+
this.engine.hasJustCalculated = true;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
applyTip(percent) {
|
|
230
|
+
const current = this.engine.parseCurrent();
|
|
231
|
+
const result = Calculator.tip(current, percent, this.config.precision);
|
|
232
|
+
this.engine.currentInput = String(result);
|
|
233
|
+
this.engine.historyTokens = [`${Formatter.format(current)} + ${percent}% Tip =`];
|
|
234
|
+
this.engine.hasJustCalculated = true;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
applyDiscount(percent) {
|
|
238
|
+
const current = this.engine.parseCurrent();
|
|
239
|
+
const result = Calculator.discount(current, percent, this.config.precision);
|
|
240
|
+
this.engine.currentInput = String(result);
|
|
241
|
+
this.engine.historyTokens = [`${Formatter.format(current)} - ${percent}% Off =`];
|
|
242
|
+
this.engine.hasJustCalculated = true;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
executePerform(expr) {
|
|
246
|
+
if (!expr) return;
|
|
247
|
+
this.hasInteracted = true;
|
|
248
|
+
const current = this.engine.parseCurrent();
|
|
249
|
+
const res = Calculator.executePerform(current, expr);
|
|
250
|
+
this.engine.currentInput = String(res.value);
|
|
251
|
+
if (res.history) {
|
|
252
|
+
this.engine.historyTokens = [res.history];
|
|
253
|
+
}
|
|
254
|
+
this.engine.hasJustCalculated = true;
|
|
255
|
+
this.updateDisplay();
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
getOutputTargets() {
|
|
259
|
+
const selector = this.wrapper.getAttribute('data-calculator-output') ||
|
|
260
|
+
this.wrapper.getAttribute('data-output-target') ||
|
|
261
|
+
this.config.outputTarget ||
|
|
262
|
+
this.config.output;
|
|
263
|
+
if (!selector) return [];
|
|
264
|
+
try {
|
|
265
|
+
return Array.from(document.querySelectorAll(selector));
|
|
266
|
+
} catch (e) {
|
|
267
|
+
return [];
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
getCalculationTargets() {
|
|
272
|
+
const selector = this.wrapper.getAttribute('data-calculator-calculation') ||
|
|
273
|
+
this.wrapper.getAttribute('data-calculation-target') ||
|
|
274
|
+
this.config.calculationTarget ||
|
|
275
|
+
this.config.calculation;
|
|
276
|
+
if (!selector) return [];
|
|
277
|
+
try {
|
|
278
|
+
return Array.from(document.querySelectorAll(selector));
|
|
279
|
+
} catch (e) {
|
|
280
|
+
return [];
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
updateOutputTargets(displayStr, rawVal) {
|
|
285
|
+
const targets = this.getOutputTargets();
|
|
286
|
+
if (targets.length === 0) return;
|
|
287
|
+
|
|
288
|
+
targets.forEach(target => {
|
|
289
|
+
if (target.tagName === 'INPUT' || target.tagName === 'TEXTAREA') {
|
|
290
|
+
target.value = displayStr;
|
|
291
|
+
target.dispatchEvent(new Event('input', { bubbles: true }));
|
|
292
|
+
target.dispatchEvent(new Event('change', { bubbles: true }));
|
|
293
|
+
} else {
|
|
294
|
+
target.textContent = displayStr;
|
|
295
|
+
}
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
this.wrapper.dispatchEvent(new CustomEvent('calculator:change', {
|
|
299
|
+
bubbles: true,
|
|
300
|
+
detail: {
|
|
301
|
+
value: rawVal,
|
|
302
|
+
formatted: displayStr,
|
|
303
|
+
history: this.engine.getHistoryString(),
|
|
304
|
+
calculator: this
|
|
305
|
+
}
|
|
306
|
+
}));
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
updateCalculationTargets(calculationStr) {
|
|
310
|
+
const targets = this.getCalculationTargets();
|
|
311
|
+
if (targets.length === 0) return;
|
|
312
|
+
|
|
313
|
+
targets.forEach(target => {
|
|
314
|
+
if (target.tagName === 'INPUT' || target.tagName === 'TEXTAREA') {
|
|
315
|
+
target.value = calculationStr;
|
|
316
|
+
target.dispatchEvent(new Event('input', { bubbles: true }));
|
|
317
|
+
target.dispatchEvent(new Event('change', { bubbles: true }));
|
|
318
|
+
} else {
|
|
319
|
+
target.textContent = calculationStr;
|
|
320
|
+
}
|
|
321
|
+
});
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
updateDisplay() {
|
|
325
|
+
const currency = this.config.currency ? `${this.config.currency} ` : '';
|
|
326
|
+
const rawVal = this.engine.parseCurrent();
|
|
327
|
+
let finalDisplay = this.engine.getDisplayValue();
|
|
328
|
+
|
|
329
|
+
const parts = finalDisplay.split('.');
|
|
330
|
+
const formattedInt = parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, ',');
|
|
331
|
+
finalDisplay = parts.length > 1 ? `${formattedInt}.${parts[1]}` : formattedInt;
|
|
332
|
+
|
|
333
|
+
if (this.displayEl) {
|
|
334
|
+
this.displayEl.textContent = `${currency}${finalDisplay}`;
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
const historyStr = this.engine.getHistoryString();
|
|
338
|
+
if (this.historyEl) {
|
|
339
|
+
if (historyStr) {
|
|
340
|
+
this.historyEl.textContent = historyStr;
|
|
341
|
+
} else {
|
|
342
|
+
this.historyEl.innerHTML = ' ';
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
this.scrollToEnd(this.historyEl);
|
|
347
|
+
this.scrollToEnd(this.displayEl);
|
|
348
|
+
|
|
349
|
+
if (this.hasInteracted) {
|
|
350
|
+
this.updateOutputTargets(`${currency}${finalDisplay}`, rawVal);
|
|
351
|
+
this.updateCalculationTargets(historyStr || 'Result:');
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
isRTL() {
|
|
356
|
+
if (typeof document === 'undefined') return false;
|
|
357
|
+
return (
|
|
358
|
+
document.documentElement.getAttribute('dir') === 'rtl' ||
|
|
359
|
+
document.documentElement.getAttribute('data-bs-direction') === 'rtl' ||
|
|
360
|
+
document.body?.getAttribute('dir') === 'rtl' ||
|
|
361
|
+
this.wrapper?.getAttribute('dir') === 'rtl' ||
|
|
362
|
+
getComputedStyle(this.wrapper || document.documentElement).direction === 'rtl'
|
|
363
|
+
);
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
scrollToEnd(element) {
|
|
367
|
+
if (!element) return;
|
|
368
|
+
const rtl = this.isRTL();
|
|
369
|
+
element.scrollLeft = rtl ? -element.scrollWidth : element.scrollWidth;
|
|
370
|
+
if (typeof requestAnimationFrame === 'function') {
|
|
371
|
+
requestAnimationFrame(() => {
|
|
372
|
+
if (element) {
|
|
373
|
+
element.scrollLeft = rtl ? -element.scrollWidth : element.scrollWidth;
|
|
374
|
+
}
|
|
375
|
+
});
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
syncTape() {
|
|
380
|
+
if (!this.tapeListEl) return;
|
|
381
|
+
this.tapeListEl.innerHTML = '';
|
|
382
|
+
this.engine.tapeHistory.forEach((entry, idx) => {
|
|
383
|
+
const item = document.createElement('div');
|
|
384
|
+
item.className = 'd-flex justify-content-between py-1 border-bottom border-light-subtle';
|
|
385
|
+
item.innerHTML = `<span class="text-secondary">${this.engine.tapeHistory.length - idx}.</span> <span>${entry}</span>`;
|
|
386
|
+
this.tapeListEl.appendChild(item);
|
|
387
|
+
});
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
static create(target, options = {}) {
|
|
391
|
+
const el = typeof target === 'string' ? document.querySelector(target) : target;
|
|
392
|
+
if (!el) return null;
|
|
393
|
+
return new AdminUIUXCalculator(el, options);
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
static init(target, options = {}) {
|
|
397
|
+
const el = typeof target === 'string' ? document.querySelector(target) : target;
|
|
398
|
+
if (!el) return null;
|
|
399
|
+
return new AdminUIUXCalculator(el, options);
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
static initAll() {
|
|
403
|
+
if (typeof document === 'undefined') return;
|
|
404
|
+
document.querySelectorAll('.calculator, [data-calculator-type], [data-calendar-type]').forEach(el => {
|
|
405
|
+
if (!el._calculatorInstance) {
|
|
406
|
+
new AdminUIUXCalculator(el);
|
|
407
|
+
}
|
|
408
|
+
});
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
// Attach sub-modules
|
|
413
|
+
AdminUIUXCalculator.Calculator = Calculator;
|
|
414
|
+
AdminUIUXCalculator.Formatter = Formatter;
|
|
415
|
+
AdminUIUXCalculator.StreamCalculator = StreamCalculator;
|
|
416
|
+
|
|
417
|
+
// Browser global exposure & auto initialization
|
|
418
|
+
if (typeof window !== 'undefined') {
|
|
419
|
+
window.AdminUIUXCalculator = AdminUIUXCalculator;
|
|
420
|
+
window.Calculator = Calculator;
|
|
421
|
+
window.Formatter = Formatter;
|
|
422
|
+
window.StreamCalculator = StreamCalculator;
|
|
423
|
+
|
|
424
|
+
if (typeof document !== 'undefined') {
|
|
425
|
+
if (document.readyState === 'loading') {
|
|
426
|
+
document.addEventListener('DOMContentLoaded', () => AdminUIUXCalculator.initAll());
|
|
427
|
+
} else {
|
|
428
|
+
AdminUIUXCalculator.initAll();
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
export { Formatter, Calculator, StreamCalculator };
|
|
434
|
+
export default AdminUIUXCalculator;
|