Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
88 changes: 22 additions & 66 deletions package-lock.json

Large diffs are not rendered by default.

14 changes: 14 additions & 0 deletions packages/o-spreadsheet-engine/src/helpers/format/format.ts
Original file line number Diff line number Diff line change
Expand Up @@ -234,6 +234,14 @@ function applyInternalNumberFormat(value: number, format: NumberInternalFormat,
return "∞" + (format.percentSymbols ? "%" : "");
}

let power = Math.floor(Math.log10(Math.abs(value)));
if (power === -Infinity) {
power = 0;
}
if (format.scientific) {
value = value / 10 ** power;
}

const multiplier = format.percentSymbols * 2 - format.magnitude * 3;
value = value * 10 ** multiplier;

Expand All @@ -253,6 +261,12 @@ function applyInternalNumberFormat(value: number, format: NumberInternalFormat,
formattedValue += locale.decimalSeparator + applyDecimalFormat(decimalDigits || "", format);
}

if (format.scientific) {
const powerAsString = Math.abs(power).toString().padStart(2, "0");
const sign = power >= 0 ? "+" : "-";
formattedValue += "e" + sign + powerAsString;
}

return formattedValue;
}

Expand Down
14 changes: 14 additions & 0 deletions packages/o-spreadsheet-engine/src/helpers/format/format_parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
FormatToken,
PercentToken,
RepeatCharToken,
ScientificToken,
StringToken,
TextPlaceholderToken,
ThousandsSeparatorToken,
Expand Down Expand Up @@ -40,11 +41,13 @@ export interface NumberInternalFormat {
| StringToken
| CharToken
| PercentToken
| ScientificToken
| ThousandsSeparatorToken
| RepeatCharToken
)[];
readonly percentSymbols: number;
readonly thousandsSeparator: boolean;
readonly scientific: boolean;
/** A thousand separator after the last digit in the format means that we divide the number by a thousand */
readonly magnitude: number;
/**
Expand All @@ -57,6 +60,7 @@ export interface NumberInternalFormat {
| StringToken
| CharToken
| PercentToken
| ScientificToken
| ThousandsSeparatorToken
| RepeatCharToken
)[];
Expand Down Expand Up @@ -151,6 +155,7 @@ function areValidNumberFormatTokens(
| DecimalPointToken
| ThousandsSeparatorToken
| PercentToken
| ScientificToken
| StringToken
| CharToken
| RepeatCharToken
Expand All @@ -161,6 +166,7 @@ function areValidNumberFormatTokens(
token.type === "DECIMAL_POINT" ||
token.type === "THOUSANDS_SEPARATOR" ||
token.type === "PERCENT" ||
token.type === "SCIENTIFIC" ||
token.type === "STRING" ||
token.type === "CHAR" ||
token.type === "REPEATED_CHAR"
Expand Down Expand Up @@ -188,6 +194,7 @@ function parseNumberFormatTokens(

let parsedPart = integerPart;
let percentSymbols = 0;
let scientific = false;
let magnitude = 0;
let lastIndexOfDigit = tokens.findLastIndex((token) => token.type === "DIGIT");
let hasThousandSeparator = false;
Expand All @@ -212,6 +219,9 @@ function parseNumberFormatTokens(
throw new Error("Multiple decimal points in a number format");
}
break;
case "SCIENTIFIC":
scientific = true;
break;
case "REPEATED_CHAR":
case "CHAR":
case "STRING":
Expand Down Expand Up @@ -247,6 +257,7 @@ function parseNumberFormatTokens(
integerPart,
decimalPart,
percentSymbols,
scientific,
thousandsSeparator: hasThousandSeparator,
magnitude,
};
Expand Down Expand Up @@ -349,6 +360,9 @@ function numberInternalFormatToTokenList(internalFormat: NumberInternalFormat):
tokens.push({ type: "DECIMAL_POINT", value: "." });
tokens.push(...internalFormat.decimalPart);
}
if (internalFormat.scientific) {
tokens.push({ type: "SCIENTIFIC", value: "e" });
}

return tokens;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,11 @@ export interface PercentToken {
value: "%";
}

export interface ScientificToken {
type: "SCIENTIFIC";
value: "e";
}

export interface ThousandsSeparatorToken {
type: "THOUSANDS_SEPARATOR";
value: ",";
Expand All @@ -51,6 +56,7 @@ export type FormatToken =
| StringToken
| CharToken
| PercentToken
| ScientificToken
| ThousandsSeparatorToken
| TextPlaceholderToken
| DatePartToken
Expand All @@ -77,6 +83,7 @@ export function tokenizeFormat(str: string): FormatToken[][] {
tokenizeThousandsSeparator(chars) ||
tokenizeDecimalPoint(chars) ||
tokenizePercent(chars) ||
tokenizeScientific(chars) ||
tokenizeDatePart(chars) ||
tokenizeTextPlaceholder(chars) ||
tokenizeRepeatedChar(chars);
Expand Down Expand Up @@ -173,6 +180,14 @@ function tokenizePercent(chars: TokenizingChars): FormatToken | null {
return null;
}

function tokenizeScientific(chars: TokenizingChars): FormatToken | null {
if (chars.current === "e") {
chars.shift();
return { type: "SCIENTIFIC", value: "e" };
}
return null;
}

function tokenizeDigit(chars: TokenizingChars): FormatToken | null {
if (chars.current === "0" || chars.current === "#") {
const value = chars.current;
Expand Down
6 changes: 6 additions & 0 deletions src/actions/format_actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,12 @@ export const formatNumberPercent = createFormatActionSpec({
format: "0.00%",
});

export const formatNumberScientific = createFormatActionSpec({
name: _t("Scientific"),
descriptionValue: 0.1012,
format: "0.00e",
});

export const formatNumberCurrency = createFormatActionSpec({
name: _t("Currency"),
descriptionValue: 1000.12,
Expand Down
19 changes: 13 additions & 6 deletions src/components/side_panel/more_formats/more_formats_store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -125,12 +125,19 @@ export class MoreFormatsStore extends SpreadsheetStore {
}

get numberFormatProposals() {
const numberFormats = ["0.00", "0", "#,##0", "#,##0.00", "0%", "0.00%", "0.00;(0.00);-"].map(
(format) => ({
label: formatValue(-1234.56, { format, locale: this.getters.getLocale() }),
format,
})
);
const numberFormats = [
"0.00",
"0",
"#,##0",
"#,##0.00",
"0%",
"0.00%",
"0.00e",
"0.00;(0.00);-",
].map((format) => ({
label: formatValue(-1234.56, { format, locale: this.getters.getLocale() }),
format,
}));

return [
{ label: _t("Automatic"), format: undefined },
Expand Down
6 changes: 6 additions & 0 deletions src/registries/menus/number_format_menu_registry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,12 @@ numberFormatMenuRegistry
...ACTION_FORMAT.formatNumberPercent,
id: "format_number_percent",
sequence: 30,
separator: false,
})
.add("format_number_scientific", {
...ACTION_FORMAT.formatNumberScientific,
id: "format_number_scientific",
sequence: 33,
separator: true,
})
.add("format_number_currency", {
Expand Down
14 changes: 14 additions & 0 deletions tests/formats/format_helpers.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -316,6 +316,20 @@ describe("formatValue on number", () => {
expect(formatValue(-Infinity, { format: "0.0%", locale })).toBe("-∞%");
});

test("apply various scientific format", () => {
expect(formatValue(0.1234, { format: "0e", locale })).toBe("1e-01");
expect(formatValue(0.1234, { format: "0.0e", locale })).toBe("1.2e-01");
expect(formatValue(0.1234, { format: "0.00e", locale })).toBe("1.23e-01");
expect(formatValue(0.1234, { format: "0.000e", locale })).toBe("1.234e-01");
});

test("apply scientific format to various number", () => {
expect(formatValue(-0.1234, { format: "0.00e", locale })).toBe("-1.23e-01");
expect(formatValue(1234, { format: "0.00e", locale })).toBe("1.23e+03");
expect(formatValue(0, { format: "0.00e", locale })).toBe("0.00e+00");
expect(formatValue(-1234, { format: "0.00e", locale })).toBe("-1.23e+03");
});

test("can apply format with custom currencies", () => {
expect(formatValue(1234, { format: "#,##0[$TEST]", locale })).toBe("1,234TEST");
expect(formatValue(1234, { format: '#,##0 "TEST"', locale })).toBe("1,234 TEST");
Expand Down
15 changes: 15 additions & 0 deletions tests/formats/formatting_plugin.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -251,6 +251,21 @@ describe("formatting values (with formatters)", () => {
expect(getCell(model, "A1")?.format).toBe(";;;@");
});

test("SET_DECIMAL on scientific format", () => {
const model = new Model();
setCellContent(model, "A1", "1234");

setFormat(model, "A1", "0.00e");
setDecimal(model, "A1", 1);
expect(getCell(model, "A1")?.format).toBe("0.000e");
expect(getEvaluatedCell(model, "A1").formattedValue).toBe("1.234e+03");

setDecimal(model, "A1", -1);
setDecimal(model, "A1", -1);
expect(getCell(model, "A1")?.format).toBe("0.0e");
expect(getEvaluatedCell(model, "A1").formattedValue).toBe("1.2e+03");
});

test("UPDATE_CELL on long number that are truncated due to default format don't loose truncated digits", () => {
const model = new Model();
setCellContent(model, "A1", "10.123456789123");
Expand Down