Skip to content

Commit 632e895

Browse files
committed
any-decimal / anyDecimal
1 parent e5ef165 commit 632e895

4 files changed

Lines changed: 56 additions & 29 deletions

File tree

README.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -308,8 +308,11 @@ Property name same as attribute. String as attribute / Number as property.
308308
- `units` / `units` are the string units to append to the formatted text.
309309
- `locale` / `locale` is the locale string to use for formatting. If set to "" it uses the users locale: [`navigator.language`](https://developer.mozilla.org/en-US/docs/Web/API/Navigator/language). If set to null (removed), formatting is not based on locale. It uses the [`locales`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/NumberFormat/NumberFormat#locales) option, but only sets one locale.
310310
- `currency` / `currency` when set, sets [`style`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/NumberFormat/NumberFormat#style) = `"currency"` and [currency](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/NumberFormat/NumberFormat#currency_2) = the attribute value. It is only relevant when `locale` is set. [`currencyDisplay`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/NumberFormat/NumberFormat#currencydisplay) is always set to `"narrowSymbol"`.
311-
- `accounting` / `accounting` is a boolean that toggles [`currencySign`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/NumberFormat/NumberFormat#currencysign) = `"accounting"`, which encloses negative numbers in parentheses. Only relevent when `currency` and `locale` are set.
311+
- `accounting` / `accounting` is a boolean that toggles [`currencySign`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/NumberFormat/NumberFormat#currencysign) = `"accounting"`, which in many (but not all) locales encloses negative numbers in parentheses. Only relevent when `currency` and `locale` are set.
312312
- `notation` / `notation` sets [`notation`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/NumberFormat/NumberFormat#notation) to the attribute value. Defaults to `"standard"`. *Untested! I don't understand the implications of the various options.*
313+
- `any-decimal` / `anyDecimal` is a boolean. According to the [General Conference on Weights and Measures](https://www.bipm.org/en/committees/cg/cgpm/22-2003/resolution-10), across all locales the decimal marker is represented by one of only two characters: comma `,` and period `.`. Many countries officially use comma, but unofficially the de facto standard is the Anglo format of decimals as period and thousands as comma, which appears in contracts, advertisements, and everywhere else in daily life.
314+
- unset / `false` enforces the locale's decimal character (which defaults to `.` when `locale` is unset).
315+
- `""` / `true` allows either `,` or `.` regardless of locale.
313316
314317
NOTE: When you combine a currency symbol with units, it displays as currency per unit. For example:
315318
```js

base-element.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ class BaseElement extends HTMLElement {
2121
else
2222
this.#attach(template); // <template> as DocumentFragment
2323

24-
this.setAttribute(TAB_INDEX, "0"); // default value emulates <input>
24+
this.setAttribute(TAB_INDEX, "0"); // default value, emulates <input>
2525
this.#internals = this.attachInternals(); // for accessibility, labels
2626
}
2727
#attach(template) {

html-elements.xlsx

311 Bytes
Binary file not shown.

input-num.js

Lines changed: 51 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -2,19 +2,20 @@ export {InputNum};
22

33
import {VALUE, BaseElement} from "./base-element.js";
44
const
5-
MAX = "max", // DOM attributes:
5+
MAX = "max", // DOM attributes:
66
MIN = "min",
77
STEP = "step",
8-
// custom attributes: numbers and strings
8+
// custom attributes: numbers and strings first
99
DELAY = "delay", // millisecond delay between mousedown & spin
1010
INTERVAL = "interval", // millisecond interval for spin
1111
DIGITS = "digits", // Number.prototype.toFixed(digits)
1212
UNITS = "units", // units string suffix
1313
LOCALE = "locale", // locale string: "aa-AA", "" = user locale
1414
NOTATION = "notation", // Intl.NumberFormat() options notation prop
1515
CURRENCY = "currency", // ditto: currency property
16-
// booleans:
16+
// booleans:
1717
ACCOUNTING = "accounting", // {currencySign:"accounting"}
18+
ANY_DECIMAL = "any-decimal", // lets users input either comma or period
1819
BLUR_CANCEL = "blur-cancel", // Blur = <Esc> key, else Blur = <Enter> key
1920
NO_KEYS = "no-keys", // no keyboard/pad input, only spinning
2021
NO_SPIN = "no-spin", // hide spinner, no keyboard spinning
@@ -70,9 +71,9 @@ textAlign = ["text-align","right","important"],
7071
noAwait = true; // see https://github.com/sidewayss/html-elements/issues/8
7172
// =============================================================================
7273
class InputNum extends BaseElement {
73-
#attrs; #bound; #btns; #confirmIt; #ctrls; #erId; #hoverBtn; #hoverOut;
74-
#input; #isBlurring; #isLoading; #isMousing; #kbSpin; #locale; #outFocus;
75-
#padRight; #spinId; #states; #svg; #texts; #validate;
74+
#attrs; #bound; #btns; #confirmIt; #ctrls; #decimal; #erId; #hoverBtn;
75+
#hoverOut; #input; #isBlurring; #isLoading; #isMousing; #kbSpin; #locale;
76+
#outFocus; #padRight; #spinId; #states; #svg; #texts; #validate;
7677
#isBlurry; // kludge for this._dom.activeElement === null
7778

7879
static defaults = {
@@ -93,18 +94,16 @@ static observedAttributes = [
9394
constructor() {
9495
super(import.meta, InputNum, noAwait);
9596
// #attrs caches numbers for revert on NaN
96-
this.#attrs = Object.assign({}, InputNum.defaults);
97-
this.#spinId = null;
98-
this.#locale = {
99-
currencyDisplay:"narrowSymbol",
100-
maximumFractionDigits:this.#attrs[DIGITS],
101-
minimumFractionDigits:this.#attrs[DIGITS]
102-
};
103-
this.#bound = { // #swapEvents adds/removes #bound events
97+
this.#attrs = Object.assign({}, InputNum.defaults);
98+
this.#decimal = "."; // that's the universal default
99+
this.#spinId = null;
100+
this.#bound = { // #swapEvents adds/removes #bound events
104101
[mouse.down]: this.#mouseDown.bind(this),
105102
[mouse.up]: this.#mouseUp .bind(this),
106103
[mouse.click]:this.#click .bind(this)
107104
};
105+
this.#locale = {currencyDisplay:"narrowSymbol"}; // constant
106+
this.#getDigits(this.#locale);
108107
this.#addEvent(key.down, this.#keyDown);
109108
this.#addEvent(key.up, this.#keyUp);
110109
this.#addEvent(mouse.over, this.#hover);
@@ -186,10 +185,9 @@ static observedAttributes = [
186185
case DIGITS: // runs twice if #revert(), but simpler
187186
isResize = true;
188187
isUpdate = true;
188+
this.#getDigits(this.#locale, n);
189189
if (this.#input) // for noAwait
190190
this.#input.inputMode = n ? "decimal" : "numeric";
191-
this.#locale.maximumFractionDigits = n;
192-
this.#locale.minimumFractionDigits = n;
193191
if (!this.hasAttribute(STEP)) // auto-step default:
194192
this.#attrs[STEP] = this.#autoStep(n);
195193
case DELAY:
@@ -240,10 +238,17 @@ static observedAttributes = [
240238
default:
241239
isUpdate = true;
242240
switch(name) {
241+
case LOCALE: // the decimal marker:
242+
this.#decimal = (val === null)
243+
? "." // convert "" to undefined
244+
: Intl.NumberFormat(val || undefined)
245+
.formatToParts(.1)
246+
.find(p => p.type == "decimal")
247+
.value;
248+
break;
243249
case ACCOUNTING:
244250
this.#locale.currencySign = (val !== null)
245251
? "accounting" : undefined;
246-
case LOCALE:
247252
case UNITS: // strings:
248253
break;
249254
case CURRENCY:
@@ -287,7 +292,8 @@ static observedAttributes = [
287292
get autoAlign() { return !this.hasAttribute(NO_ALIGN); }
288293
get autoResize() { return !this.hasAttribute(NO_RESIZE); }
289294
get autoScale() { return !this.hasAttribute(NO_SCALE); }
290-
get blurEsc() { return this.hasAttribute(BLUR_CANCEL); }
295+
get blurCancel() { return this.hasAttribute(BLUR_CANCEL); }
296+
get anyDecimal() { return this.hasAttribute(ANY_DECIMAL); }
291297
get accounting() { return this.hasAttribute(ACCOUNTING); }
292298
get useLocale() { return this.hasAttribute(LOCALE); } // read-only
293299

@@ -319,7 +325,8 @@ static observedAttributes = [
319325
set autoAlign (val) { this._setBool(NO_ALIGN, !val); }
320326
set autoResize(val) { this._setBool(NO_RESIZE, !val); }
321327
set autoScale (val) { this._setBool(NO_SCALE, !val); }
322-
set blurEsc (val) { this._setBool(BLUR_CANCEL, val); }
328+
set blurCancel(val) { this._setBool(BLUR_CANCEL, val); }
329+
set anyDecimal(val) { this._setBool(ANY_DECIMAL, val); }
323330
set accounting(val) { this._setBool(ACCOUNTING, val); }
324331

325332
set units (val) { this.#setRemove(UNITS, val); } // strings:
@@ -420,7 +427,7 @@ static observedAttributes = [
420427
if (this.#isBlurry || this.#isMousing) return;
421428
//-------------------------------------------------
422429
this.#fur(evt, false, CONFIRM, !this.#hoverBtn,
423-
this.#attrs[VALUE].toFixed(this.digits));
430+
this.#getText(true));
424431
}
425432
#blur(evt) {
426433
if (this.#isBlurry || this.#isMousing) return;
@@ -505,10 +512,11 @@ static observedAttributes = [
505512
end = input.selectionEnd,
506513
dist = end - start,
507514
val = input.value,
515+
rxp = new RegExp(`[^\\d\-eE${this.#decimal}]`, "g"),
508516
orig = [{num:start, str:val.slice(0, start)},
509517
{num:dist, str:val.slice(start, end)}];
510518
for (obj of orig) {
511-
match = obj.str.match(/[^\d-.eE]/g);
519+
match = obj.str.match(rxp);
512520
range.push(obj.num - (match ? match.length : 0))
513521
}
514522
this.#isMousing = false; // let #focus() do it's thing, #input
@@ -839,23 +847,39 @@ static observedAttributes = [
839847
// #getText() gets the appropriate text for the #input
840848
#getText(inFocus, appendUnits) {
841849
const n = this.#attrs[VALUE];
842-
return (inFocus ? n : this.#formatNumber(n))
850+
return inFocus
851+
? this.#formatNumber(n, this.#getDigits({useGrouping:false}))
852+
: this.#formatNumber(n)
843853
+ (appendUnits && this.units ? this.#getUnits() : "");
844854
}
855+
#getDigits(obj, i = this.#attrs[DIGITS]) {
856+
obj.maximumFractionDigits = i;
857+
obj.minimumFractionDigits = i;
858+
return obj;
859+
}
845860
// #getUnits() gets the units text: currency && units displays currency per unit
846861
#getUnits() {
847862
return `${this.useLocale && this.#locale.currency ? "/" : ""}${this.units}`;
848863
}
849864
// #formatNumber() formats a number for display as text
850-
#formatNumber(n) {
865+
#formatNumber(n, options = this.#locale) {
851866
if (n !== undefined) //!!might not be necessary anymore...
852867
return this.useLocale
853-
? new Intl.NumberFormat(this.locale, this.#locale).format(n)
868+
? new Intl.NumberFormat(this.locale, options).format(n)
854869
: n.toFixed(this.#attrs[DIGITS]);
855870
}
856-
// #toNumber() converts string to number, str never equals 0
857-
#toNumber(str) { // parseFloat() is too lenient
858-
return str ? Number(str) : NaN; // Number() converts "" and null to 0
871+
// #toNumber() converts String to Number
872+
#toNumber(str) {
873+
if (!str) // str might equal "0", but never 0
874+
return NaN; // Number() converts "" and null to 0
875+
else if (this.anyDecimal) // part of the locale-friendly approach
876+
str = str.replace(",", ".");
877+
else if (this.#decimal == ",") {
878+
if (str.includes("."))
879+
return NaN;
880+
str = str.replace(",", ".");
881+
}
882+
return Number(str); // parseFloat() is too lenient
859883
}
860884
// #getButton() gets the first segment of the href id
861885
#getButton(inFocus) {

0 commit comments

Comments
 (0)