mirror of
https://github.com/FoggedLens/iD.git
synced 2026-06-03 05:28:03 +02:00
Merge branch '1ec5-number-field-format-3615' into develop
This commit is contained in:
@@ -424,5 +424,81 @@ export function coreLocalizer() {
|
||||
return code; // if not found, use the code
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns a function that formats a floating-point number in the given
|
||||
* locale.
|
||||
*/
|
||||
localizer.floatFormatter = (locale) => {
|
||||
if (!('Intl' in window && 'NumberFormat' in Intl &&
|
||||
'formatToParts' in Intl.NumberFormat.prototype)) {
|
||||
return (number, fractionDigits) => {
|
||||
return fractionDigits === undefined ? number.toString() : number.toFixed(fractionDigits);
|
||||
};
|
||||
} else {
|
||||
return (number, fractionDigits) => number.toLocaleString(locale, {
|
||||
minimumFractionDigits: fractionDigits,
|
||||
maximumFractionDigits: fractionDigits === undefined ? 20 : fractionDigits,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns a function that parses a number formatted according to the given
|
||||
* locale as a floating-point number.
|
||||
*/
|
||||
localizer.floatParser = (locale) => {
|
||||
// https://stackoverflow.com/a/55366435/4585461
|
||||
const polyfill = (string) => +string.trim();
|
||||
if (!('Intl' in window && 'NumberFormat' in Intl)) return polyfill;
|
||||
const format = new Intl.NumberFormat(locale, { maximumFractionDigits: 20 });
|
||||
if (!('formatToParts' in format)) return polyfill;
|
||||
const parts = format.formatToParts(-12345.6);
|
||||
const numerals = Array.from({ length: 10 }).map((_, i) => format.format(i));
|
||||
const index = new Map(numerals.map((d, i) => [d, i]));
|
||||
const literalPart = parts.find(d => d.type === 'literal');
|
||||
const literal = literalPart && new RegExp(`[${literalPart.value}]`, 'g');
|
||||
const groupPart = parts.find(d => d.type === 'group');
|
||||
const group = groupPart && new RegExp(`[${groupPart.value}]`, 'g');
|
||||
const decimalPart = parts.find(d => d.type === 'decimal');
|
||||
const decimal = decimalPart && new RegExp(`[${decimalPart.value}]`);
|
||||
const numeral = new RegExp(`[${numerals.join('')}]`, 'g');
|
||||
const getIndex = d => index.get(d);
|
||||
return (string) => {
|
||||
string = string.trim();
|
||||
if (literal) string = string.replace(literal, '');
|
||||
if (group) string = string.replace(group, '');
|
||||
if (decimal) string = string.replace(decimal, '.');
|
||||
string = string.replace(numeral, getIndex);
|
||||
return string ? +string : NaN;
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns a function that returns the number of decimal places in a
|
||||
* formatted number string.
|
||||
*/
|
||||
localizer.decimalPlaceCounter = (locale) => {
|
||||
var literal, group, decimal;
|
||||
if ('Intl' in window && 'NumberFormat' in Intl) {
|
||||
const format = new Intl.NumberFormat(locale, { maximumFractionDigits: 20 });
|
||||
if ('formatToParts' in format) {
|
||||
const parts = format.formatToParts(-12345.6);
|
||||
const literalPart = parts.find(d => d.type === 'literal');
|
||||
literal = literalPart && new RegExp(`[${literalPart.value}]`, 'g');
|
||||
const groupPart = parts.find(d => d.type === 'group');
|
||||
group = groupPart && new RegExp(`[${groupPart.value}]`, 'g');
|
||||
const decimalPart = parts.find(d => d.type === 'decimal');
|
||||
decimal = decimalPart && new RegExp(`[${decimalPart.value}]`);
|
||||
}
|
||||
}
|
||||
return (string) => {
|
||||
string = string.trim();
|
||||
if (literal) string = string.replace(literal, '');
|
||||
if (group) string = string.replace(group, '');
|
||||
const parts = string.split(decimal || '.');
|
||||
return parts && parts[1] && parts[1].length || 0;
|
||||
};
|
||||
};
|
||||
|
||||
return localizer;
|
||||
}
|
||||
|
||||
+38
-19
@@ -32,6 +32,9 @@ export function uiFieldText(field, context) {
|
||||
var _tags;
|
||||
var _phoneFormats = {};
|
||||
const isDirectionField = field.key.split(':').some(keyPart => keyPart === 'direction');
|
||||
const formatFloat = localizer.floatFormatter(localizer.languageCode());
|
||||
const parseLocaleFloat = localizer.floatParser(localizer.languageCode());
|
||||
const countDecimalPlaces = localizer.decimalPlaceCounter(localizer.languageCode());
|
||||
|
||||
if (field.type === 'tel') {
|
||||
fileFetcher.get('phone_formats')
|
||||
@@ -132,18 +135,19 @@ export function uiFieldText(field, context) {
|
||||
var raw_vals = input.node().value || '0';
|
||||
var vals = raw_vals.split(';');
|
||||
vals = vals.map(function(v) {
|
||||
var num = Number(v);
|
||||
v = v.trim();
|
||||
var num = parseLocaleFloat(v);
|
||||
if (isDirectionField) {
|
||||
const compassDir = cardinal[v.trim().toLowerCase()];
|
||||
const compassDir = cardinal[v.toLowerCase()];
|
||||
if (compassDir !== undefined) {
|
||||
num = compassDir;
|
||||
}
|
||||
}
|
||||
|
||||
if (!isFinite(num)) {
|
||||
// do nothing if the value is neither a number, nor a cardinal direction
|
||||
return v.trim();
|
||||
}
|
||||
// do nothing if the value is neither a number, nor a cardinal direction
|
||||
if (!isFinite(num)) return v;
|
||||
num = parseFloat(num);
|
||||
if (!isFinite(num)) return v;
|
||||
|
||||
num += d;
|
||||
// clamp to 0..359 degree range if it's a direction field
|
||||
@@ -152,8 +156,7 @@ export function uiFieldText(field, context) {
|
||||
num = ((num % 360) + 360) % 360;
|
||||
}
|
||||
// make sure no extra decimals are introduced
|
||||
const numDecimals = v.includes('.') ? v.split('.')[1].length : 0;
|
||||
return clamped(num).toFixed(numDecimals);
|
||||
return formatFloat(clamped(num), countDecimalPlaces(v));
|
||||
});
|
||||
input.node().value = vals.join(';');
|
||||
change()();
|
||||
@@ -404,17 +407,21 @@ export function uiFieldText(field, context) {
|
||||
// don't override multiple values with blank string
|
||||
if (!val && getVals(_tags).size > 1) return;
|
||||
|
||||
if (!onInput) {
|
||||
if (field.type === 'number' && val) {
|
||||
var vals = val.split(';');
|
||||
vals = vals.map(function(v) {
|
||||
var num = Number(v);
|
||||
return isFinite(num) ? clamped(num) : v.trim();
|
||||
});
|
||||
val = vals.join(';');
|
||||
}
|
||||
utilGetSetValue(input, val);
|
||||
var displayVal = val;
|
||||
if (field.type === 'number' && val) {
|
||||
var numbers = val.split(';');
|
||||
numbers = numbers.map(function(v) {
|
||||
if (/^\d+\.\d{1}$/.test(v)) {
|
||||
// ignore numbers entered in "raw" format
|
||||
return v;
|
||||
}
|
||||
var num = parseLocaleFloat(v);
|
||||
const fractionDigits = countDecimalPlaces(v);
|
||||
return isFinite(num) ? clamped(num).toFixed(fractionDigits) : v;
|
||||
});
|
||||
val = numbers.join(';');
|
||||
}
|
||||
if (!onInput) utilGetSetValue(input, displayVal);
|
||||
t[field.key] = val || undefined;
|
||||
if (field.keys) {
|
||||
// for multi-key fields with: handle alternative tag keys gracefully
|
||||
@@ -449,7 +456,19 @@ export function uiFieldText(field, context) {
|
||||
|
||||
const vals = getVals(tags);
|
||||
const isMixed = vals.size > 1;
|
||||
const val = vals.size === 1 ? [...vals][0] : '';
|
||||
var val = vals.size === 1 ? [...vals][0] : '';
|
||||
|
||||
if (field.type === 'number' && val) {
|
||||
var numbers = val.split(';');
|
||||
numbers = numbers.map(function(v) {
|
||||
v = v.trim();
|
||||
var num = Number(v);
|
||||
if (!isFinite(num) || v === '') return v;
|
||||
const fractionDigits = v.includes('.') ? v.split('.')[1].length : 0;
|
||||
return formatFloat(clamped(num), fractionDigits);
|
||||
});
|
||||
val = numbers.join(';');
|
||||
}
|
||||
utilGetSetValue(input, val)
|
||||
.attr('title', isMixed ? [...vals].join('\n') : undefined)
|
||||
.attr('placeholder', isMixed ? t('inspector.multiple_values') : (field.placeholder() || t('inspector.unknown')))
|
||||
|
||||
@@ -3,7 +3,7 @@ import { select as d3_select } from 'd3-selection';
|
||||
import * as countryCoder from '@rapideditor/country-coder';
|
||||
|
||||
import { uiCombobox } from '../combobox';
|
||||
import { t } from '../../core/localizer';
|
||||
import { t, localizer } from '../../core/localizer';
|
||||
import { utilGetSetValue, utilNoAuto, utilRebind, utilTotalExtent } from '../../util';
|
||||
|
||||
|
||||
@@ -16,6 +16,8 @@ export function uiFieldRoadheight(field, context) {
|
||||
var _entityIDs = [];
|
||||
var _tags;
|
||||
var _isImperial;
|
||||
var formatFloat = localizer.floatFormatter(localizer.languageCode());
|
||||
var parseLocaleFloat = localizer.floatParser(localizer.languageCode());
|
||||
|
||||
var primaryUnits = [
|
||||
{
|
||||
@@ -129,16 +131,23 @@ export function uiFieldRoadheight(field, context) {
|
||||
|
||||
if (!primaryValue && !secondaryValue) {
|
||||
tag[field.key] = undefined;
|
||||
} else if (isNaN(primaryValue) || isNaN(secondaryValue) || !_isImperial) {
|
||||
tag[field.key] = context.cleanTagValue(primaryValue);
|
||||
} else {
|
||||
if (primaryValue !== '') {
|
||||
primaryValue = context.cleanTagValue(primaryValue + '\'');
|
||||
var rawPrimaryValue = parseLocaleFloat(primaryValue);
|
||||
if (isNaN(rawPrimaryValue)) rawPrimaryValue = primaryValue;
|
||||
var rawSecondaryValue = parseLocaleFloat(secondaryValue);
|
||||
if (isNaN(rawSecondaryValue)) rawSecondaryValue = secondaryValue;
|
||||
|
||||
if (isNaN(rawPrimaryValue) || isNaN(rawSecondaryValue) || !_isImperial) {
|
||||
tag[field.key] = context.cleanTagValue(rawPrimaryValue);
|
||||
} else {
|
||||
if (rawPrimaryValue !== '') {
|
||||
rawPrimaryValue = rawPrimaryValue + '\'';
|
||||
}
|
||||
if (rawSecondaryValue !== '') {
|
||||
rawSecondaryValue = rawSecondaryValue + '"';
|
||||
}
|
||||
tag[field.key] = context.cleanTagValue(rawPrimaryValue + rawSecondaryValue);
|
||||
}
|
||||
if (secondaryValue !== '') {
|
||||
secondaryValue = context.cleanTagValue(secondaryValue + '"');
|
||||
}
|
||||
tag[field.key] = primaryValue + secondaryValue;
|
||||
}
|
||||
|
||||
dispatch.call('change', this, tag);
|
||||
@@ -156,26 +165,36 @@ export function uiFieldRoadheight(field, context) {
|
||||
if (primaryValue && (primaryValue.indexOf('\'') >= 0 || primaryValue.indexOf('"') >= 0)) {
|
||||
secondaryValue = primaryValue.match(/(-?[\d.]+)"/);
|
||||
if (secondaryValue !== null) {
|
||||
secondaryValue = secondaryValue[1];
|
||||
secondaryValue = formatFloat(parseFloat(secondaryValue[1]));
|
||||
}
|
||||
primaryValue = primaryValue.match(/(-?[\d.]+)'/);
|
||||
if (primaryValue !== null) {
|
||||
primaryValue = primaryValue[1];
|
||||
primaryValue = formatFloat(parseFloat(primaryValue[1]));
|
||||
}
|
||||
_isImperial = true;
|
||||
} else if (primaryValue) {
|
||||
var rawValue = primaryValue;
|
||||
primaryValue = parseFloat(rawValue);
|
||||
if (isNaN(primaryValue)) {
|
||||
primaryValue = rawValue;
|
||||
} else {
|
||||
primaryValue = formatFloat(primaryValue);
|
||||
}
|
||||
_isImperial = false;
|
||||
}
|
||||
}
|
||||
|
||||
setUnitSuggestions();
|
||||
|
||||
// If feet are specified but inches are omitted, assume zero inches.
|
||||
var inchesPlaceholder = formatFloat(0);
|
||||
|
||||
utilGetSetValue(primaryInput, typeof primaryValue === 'string' ? primaryValue : '')
|
||||
.attr('title', isMixed ? primaryValue.filter(Boolean).join('\n') : null)
|
||||
.attr('placeholder', isMixed ? t('inspector.multiple_values') : t('inspector.unknown'))
|
||||
.classed('mixed', isMixed);
|
||||
utilGetSetValue(secondaryInput, typeof secondaryValue === 'string' ? secondaryValue : '')
|
||||
.attr('placeholder', isMixed ? t('inspector.multiple_values') : (_isImperial ? '0' : null))
|
||||
.attr('placeholder', isMixed ? t('inspector.multiple_values') : (_isImperial ? inchesPlaceholder : null))
|
||||
.classed('mixed', isMixed)
|
||||
.classed('disabled', !_isImperial)
|
||||
.attr('readonly', _isImperial ? null : 'readonly');
|
||||
|
||||
@@ -3,7 +3,7 @@ import { select as d3_select } from 'd3-selection';
|
||||
import * as countryCoder from '@rapideditor/country-coder';
|
||||
|
||||
import { uiCombobox } from '../combobox';
|
||||
import { t } from '../../core/localizer';
|
||||
import { t, localizer } from '../../core/localizer';
|
||||
import { utilGetSetValue, utilNoAuto, utilRebind, utilTotalExtent } from '../../util';
|
||||
|
||||
|
||||
@@ -14,6 +14,8 @@ export function uiFieldRoadspeed(field, context) {
|
||||
var _entityIDs = [];
|
||||
var _tags;
|
||||
var _isImperial;
|
||||
var formatFloat = localizer.floatFormatter(localizer.languageCode());
|
||||
var parseLocaleFloat = localizer.floatParser(localizer.languageCode());
|
||||
|
||||
var speedCombo = uiCombobox(context, 'roadspeed');
|
||||
var unitCombo = uiCombobox(context, 'roadspeed-unit')
|
||||
@@ -91,8 +93,8 @@ export function uiFieldRoadspeed(field, context) {
|
||||
|
||||
function comboValues(d) {
|
||||
return {
|
||||
value: d.toString(),
|
||||
title: d.toString()
|
||||
value: formatFloat(d),
|
||||
title: formatFloat(d)
|
||||
};
|
||||
}
|
||||
|
||||
@@ -106,10 +108,14 @@ export function uiFieldRoadspeed(field, context) {
|
||||
|
||||
if (!value) {
|
||||
tag[field.key] = undefined;
|
||||
} else if (isNaN(value) || !_isImperial) {
|
||||
tag[field.key] = context.cleanTagValue(value);
|
||||
} else {
|
||||
tag[field.key] = context.cleanTagValue(value + ' mph');
|
||||
var rawValue = parseLocaleFloat(value);
|
||||
if (isNaN(rawValue)) rawValue = value;
|
||||
if (isNaN(rawValue) || !_isImperial) {
|
||||
tag[field.key] = context.cleanTagValue(rawValue);
|
||||
} else {
|
||||
tag[field.key] = context.cleanTagValue(rawValue + ' mph');
|
||||
}
|
||||
}
|
||||
|
||||
dispatch.call('change', this, tag);
|
||||
@@ -119,16 +125,23 @@ export function uiFieldRoadspeed(field, context) {
|
||||
roadspeed.tags = function(tags) {
|
||||
_tags = tags;
|
||||
|
||||
var value = tags[field.key];
|
||||
var rawValue = tags[field.key];
|
||||
var value = rawValue;
|
||||
var isMixed = Array.isArray(value);
|
||||
|
||||
if (!isMixed) {
|
||||
if (value && value.indexOf('mph') >= 0) {
|
||||
value = parseInt(value, 10).toString();
|
||||
if (rawValue && rawValue.indexOf('mph') >= 0) {
|
||||
_isImperial = true;
|
||||
} else if (value) {
|
||||
} else if (rawValue) {
|
||||
_isImperial = false;
|
||||
}
|
||||
|
||||
value = parseInt(value, 10);
|
||||
if (isNaN(value)) {
|
||||
value = rawValue;
|
||||
} else {
|
||||
value = formatFloat(value);
|
||||
}
|
||||
}
|
||||
|
||||
setUnitSuggestions();
|
||||
|
||||
Reference in New Issue
Block a user