@budiauktioner/buildi-primitives 0.2.0 → 0.2.1

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.
Files changed (91) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/PARITY.md +1 -1
  3. package/dist/cjs/internal/scan-boundaries.js +26 -0
  4. package/dist/cjs/internal/scan-boundaries.js.map +7 -0
  5. package/dist/cjs/measurement/area.js +2 -1
  6. package/dist/cjs/measurement/area.js.map +2 -2
  7. package/dist/cjs/measurement/data-size.js +1 -1
  8. package/dist/cjs/measurement/data-size.js.map +1 -1
  9. package/dist/cjs/measurement/electric-charge.js +1 -1
  10. package/dist/cjs/measurement/electric-charge.js.map +1 -1
  11. package/dist/cjs/measurement/electric-current.js +1 -1
  12. package/dist/cjs/measurement/electric-current.js.map +1 -1
  13. package/dist/cjs/measurement/energy.js +1 -1
  14. package/dist/cjs/measurement/energy.js.map +1 -1
  15. package/dist/cjs/measurement/flow-rate.js +1 -1
  16. package/dist/cjs/measurement/flow-rate.js.map +1 -1
  17. package/dist/cjs/measurement/frequency.js +1 -1
  18. package/dist/cjs/measurement/frequency.js.map +1 -1
  19. package/dist/cjs/measurement/length.js +1 -1
  20. package/dist/cjs/measurement/length.js.map +1 -1
  21. package/dist/cjs/measurement/luminance.js +1 -1
  22. package/dist/cjs/measurement/luminance.js.map +1 -1
  23. package/dist/cjs/measurement/luminous-flux.js +4 -1
  24. package/dist/cjs/measurement/luminous-flux.js.map +2 -2
  25. package/dist/cjs/measurement/percentage.js +1 -1
  26. package/dist/cjs/measurement/percentage.js.map +1 -1
  27. package/dist/cjs/measurement/power.js +7 -3
  28. package/dist/cjs/measurement/power.js.map +2 -2
  29. package/dist/cjs/measurement/pressure.js +1 -1
  30. package/dist/cjs/measurement/pressure.js.map +1 -1
  31. package/dist/cjs/measurement/rotational-speed.js +1 -1
  32. package/dist/cjs/measurement/rotational-speed.js.map +1 -1
  33. package/dist/cjs/measurement/sound-level.js +5 -1
  34. package/dist/cjs/measurement/sound-level.js.map +2 -2
  35. package/dist/cjs/measurement/speed.js +1 -1
  36. package/dist/cjs/measurement/speed.js.map +1 -1
  37. package/dist/cjs/measurement/temperature.js +1 -1
  38. package/dist/cjs/measurement/temperature.js.map +2 -2
  39. package/dist/cjs/measurement/torque.js +1 -1
  40. package/dist/cjs/measurement/torque.js.map +1 -1
  41. package/dist/cjs/measurement/voltage.js +1 -1
  42. package/dist/cjs/measurement/voltage.js.map +1 -1
  43. package/dist/cjs/measurement/volume.js +1 -1
  44. package/dist/cjs/measurement/volume.js.map +1 -1
  45. package/dist/cjs/measurement/weight.js +1 -1
  46. package/dist/cjs/measurement/weight.js.map +1 -1
  47. package/dist/esm/internal/scan-boundaries.js +6 -0
  48. package/dist/esm/internal/scan-boundaries.js.map +7 -0
  49. package/dist/esm/measurement/area.js +2 -1
  50. package/dist/esm/measurement/area.js.map +2 -2
  51. package/dist/esm/measurement/data-size.js +1 -1
  52. package/dist/esm/measurement/data-size.js.map +1 -1
  53. package/dist/esm/measurement/electric-charge.js +1 -1
  54. package/dist/esm/measurement/electric-charge.js.map +1 -1
  55. package/dist/esm/measurement/electric-current.js +1 -1
  56. package/dist/esm/measurement/electric-current.js.map +1 -1
  57. package/dist/esm/measurement/energy.js +1 -1
  58. package/dist/esm/measurement/energy.js.map +1 -1
  59. package/dist/esm/measurement/flow-rate.js +1 -1
  60. package/dist/esm/measurement/flow-rate.js.map +1 -1
  61. package/dist/esm/measurement/frequency.js +1 -1
  62. package/dist/esm/measurement/frequency.js.map +1 -1
  63. package/dist/esm/measurement/length.js +1 -1
  64. package/dist/esm/measurement/length.js.map +1 -1
  65. package/dist/esm/measurement/luminance.js +1 -1
  66. package/dist/esm/measurement/luminance.js.map +1 -1
  67. package/dist/esm/measurement/luminous-flux.js +4 -1
  68. package/dist/esm/measurement/luminous-flux.js.map +2 -2
  69. package/dist/esm/measurement/percentage.js +1 -1
  70. package/dist/esm/measurement/percentage.js.map +1 -1
  71. package/dist/esm/measurement/power.js +7 -3
  72. package/dist/esm/measurement/power.js.map +2 -2
  73. package/dist/esm/measurement/pressure.js +1 -1
  74. package/dist/esm/measurement/pressure.js.map +1 -1
  75. package/dist/esm/measurement/rotational-speed.js +1 -1
  76. package/dist/esm/measurement/rotational-speed.js.map +1 -1
  77. package/dist/esm/measurement/sound-level.js +5 -1
  78. package/dist/esm/measurement/sound-level.js.map +2 -2
  79. package/dist/esm/measurement/speed.js +1 -1
  80. package/dist/esm/measurement/speed.js.map +1 -1
  81. package/dist/esm/measurement/temperature.js +1 -1
  82. package/dist/esm/measurement/temperature.js.map +2 -2
  83. package/dist/esm/measurement/torque.js +1 -1
  84. package/dist/esm/measurement/torque.js.map +1 -1
  85. package/dist/esm/measurement/voltage.js +1 -1
  86. package/dist/esm/measurement/voltage.js.map +1 -1
  87. package/dist/esm/measurement/volume.js +1 -1
  88. package/dist/esm/measurement/volume.js.map +1 -1
  89. package/dist/esm/measurement/weight.js +1 -1
  90. package/dist/esm/measurement/weight.js.map +1 -1
  91. package/package.json +1 -1
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/measurement/luminance.ts"],
4
- "sourcesContent": ["import { Decimal } from \"../internal/decimal.js\";\nimport type { MeasurementUnitIndex } from \"../internal/measurement-unit.js\";\nimport { tryParseNumberOnly } from \"../internal/measurement-unit-parser.js\";\nimport { Quantity, tryParseQuantity } from \"../internal/quantity.js\";\nimport type { PrimitiveTypeInfo } from \"../primitive-type-info.js\";\nimport { TextCandidate } from \"../text-scanning/text-candidate.js\";\nimport { TextCandidateCategory } from \"../text-scanning/text-candidate-category.js\";\nimport { TextMatchConfidence } from \"../text-scanning/text-match-confidence.js\";\nimport { LuminanceUnit } from \"./luminance-unit.js\";\n\n/** Options for {@link Luminance.format} and {@link Luminance.normalize}. */\nexport interface LuminanceFormatOptions {\n fallbackToTrimmedInputWhenInvalid?: boolean;\n /** Convert to this unit instead of preserving the parsed one. */\n unit?: LuminanceUnit;\n /** Round to this many fractional digits, away from zero. */\n decimals?: number;\n}\n\nconst SCAN_PATTERN = new RegExp(\n String.raw`(?<!\\w)(?:[+-]\\s*)?\\d+[0-9 .,]*\\s*(?:kcd/m\u00B2|kcd/m2|cd/m\u00B2|cd/m2|knits?|nits?)(?!\\w)`,\n \"giu\",\n);\n\n/**\n * A luminance stored internally in candela per square metre,\n * parsed from any supported unit and convertible between all of them.\n */\nexport class Luminance extends Quantity<LuminanceUnit> {\n static readonly typeInfo: PrimitiveTypeInfo = Object.freeze({\n englishName: \"Luminance\",\n localizedName: \"Luminans\",\n emoji: \"\uD83D\uDD06\",\n sources: Object.freeze([\n \"https://www.bipm.org/en/measurement-units/si-derived-units\",\n \"https://en.wikipedia.org/wiki/Luminance\",\n \"https://en.wikipedia.org/wiki/Candela_per_square_metre\",\n ]),\n });\n\n protected get units(): MeasurementUnitIndex<LuminanceUnit> {\n return LuminanceUnit.index;\n }\n\n protected withBaseValue(baseValue: Decimal, unit: LuminanceUnit): this {\n return new Luminance(baseValue, unit) as this;\n }\n\n get candelaPerSquareMetre(): number {\n return this.in(LuminanceUnit.CandelaPerSquareMetre);\n }\n get nits(): number {\n return this.in(LuminanceUnit.Nit);\n }\n get kilocandelaPerSquareMetre(): number {\n return this.in(LuminanceUnit.KilocandelaPerSquareMetre);\n }\n\n /** Creates a luminance from a value and unit. */\n static create(value: number | Decimal, unit: LuminanceUnit): Luminance {\n LuminanceUnit.index.assertOwns(unit);\n const amount = value instanceof Decimal ? value : Luminance.decimalOf(value);\n return new Luminance(unit.toBase(amount), unit);\n }\n\n static fromCandelaPerSquareMetre(value: number | Decimal): Luminance {\n return Luminance.create(value, LuminanceUnit.CandelaPerSquareMetre);\n }\n static fromNits(value: number | Decimal): Luminance {\n return Luminance.create(value, LuminanceUnit.Nit);\n }\n static fromKilocandelaPerSquareMetre(value: number | Decimal): Luminance {\n return Luminance.create(value, LuminanceUnit.KilocandelaPerSquareMetre);\n }\n\n /**\n * A bare number is read as candela per square metre, since screen brightness\n * is quoted that way. Negative luminance is rejected.\n */\n static tryParse(input: string | null | undefined): Luminance | null {\n const bare = tryParseNumberOnly(input);\n if (bare !== null) {\n if (bare.isNegative) return null;\n return new Luminance(bare, LuminanceUnit.CandelaPerSquareMetre);\n }\n\n const parsed = tryParseQuantity(input, LuminanceUnit.index);\n return parsed === null ? null : new Luminance(parsed.baseValue, parsed.unit);\n }\n\n static parse(input: string): Luminance {\n const result = Luminance.tryParse(input);\n if (result === null) throw new Error(\"Invalid luminance.\");\n return result;\n }\n\n static isValid(input: string | null | undefined): boolean {\n return Luminance.tryParse(input) !== null;\n }\n\n /** Display form in the parsed unit, or `null` when the input is invalid. */\n static format(input: string | null | undefined, options?: LuminanceFormatOptions): string | null {\n const parsed = Luminance.tryParse(input);\n if (parsed !== null) {\n if (options?.unit !== undefined || options?.decimals !== undefined) {\n return parsed.toStringInUnit(options.unit ?? parsed.originalUnit, options.decimals);\n }\n return parsed.toString();\n }\n return Luminance.fallback(input, options);\n }\n\n /** The value in candela per square metre, or `null` when the input is invalid. */\n static normalize(\n input: string | null | undefined,\n options?: LuminanceFormatOptions,\n ): string | null {\n const parsed = Luminance.tryParse(input);\n if (parsed !== null) return parsed.toNormalizedString();\n return Luminance.fallback(input, options);\n }\n\n static isNormalized(input: string | null | undefined): boolean {\n return input != null && Luminance.normalize(input) === input;\n }\n\n static findCandidatesInText(text: string): readonly TextCandidate<Luminance>[] {\n if (!text) return [];\n\n const out: TextCandidate<Luminance>[] = [];\n const pattern = new RegExp(SCAN_PATTERN.source, SCAN_PATTERN.flags);\n let match: RegExpExecArray | null;\n\n while ((match = pattern.exec(text)) !== null) {\n const value = Luminance.tryParse(match[0]);\n if (value === null) continue;\n out.push(\n new TextCandidate<Luminance>({\n startIndex: match.index,\n length: match[0].length,\n originalText: match[0],\n typeName: \"Luminance\",\n category: TextCandidateCategory.Measurement,\n normalizedForm: value.toNormalizedString(),\n formattedForm: value.toString(),\n maskedForm: maskLuminance(value),\n confidence: TextMatchConfidence.Medium,\n value,\n }),\n );\n }\n\n return out;\n }\n\n private static fallback(\n input: string | null | undefined,\n options?: LuminanceFormatOptions,\n ): string | null {\n if (options?.fallbackToTrimmedInputWhenInvalid !== true) return null;\n if (input == null) return null;\n const trimmed = input.trim();\n return trimmed.length > 0 ? trimmed : null;\n }\n\n private static decimalOf(value: number): Decimal {\n const parsed = Decimal.fromNumber(value);\n if (parsed === null) throw new Error(`Value cannot be represented as a luminance: ${value}`);\n return parsed;\n }\n}\n\n/** Returns a masked luminance, e.g. `10 cd/m\u00B2` becomes `*** cd/m\u00B2`. */\nexport function maskLuminance(value: Luminance): string {\n return `*** ${value.originalUnit.symbol}`;\n}\n"],
4
+ "sourcesContent": ["import { Decimal } from \"../internal/decimal.js\";\nimport type { MeasurementUnitIndex } from \"../internal/measurement-unit.js\";\nimport { tryParseNumberOnly } from \"../internal/measurement-unit-parser.js\";\nimport { Quantity, tryParseQuantity } from \"../internal/quantity.js\";\nimport type { PrimitiveTypeInfo } from \"../primitive-type-info.js\";\nimport { TextCandidate } from \"../text-scanning/text-candidate.js\";\nimport { TextCandidateCategory } from \"../text-scanning/text-candidate-category.js\";\nimport { TextMatchConfidence } from \"../text-scanning/text-match-confidence.js\";\nimport { LuminanceUnit } from \"./luminance-unit.js\";\n\n/** Options for {@link Luminance.format} and {@link Luminance.normalize}. */\nexport interface LuminanceFormatOptions {\n fallbackToTrimmedInputWhenInvalid?: boolean;\n /** Convert to this unit instead of preserving the parsed one. */\n unit?: LuminanceUnit;\n /** Round to this many fractional digits, away from zero. */\n decimals?: number;\n}\n\nconst SCAN_PATTERN = new RegExp(\n String.raw`(?<![\\p{L}\\p{N}_])(?:[+-]\\s*)?\\d+[0-9 .,]*\\s*(?:kcd/m\u00B2|kcd/m2|cd/m\u00B2|cd/m2|knits?|nits?)(?![\\p{L}\\p{N}_])`,\n \"giu\",\n);\n\n/**\n * A luminance stored internally in candela per square metre,\n * parsed from any supported unit and convertible between all of them.\n */\nexport class Luminance extends Quantity<LuminanceUnit> {\n static readonly typeInfo: PrimitiveTypeInfo = Object.freeze({\n englishName: \"Luminance\",\n localizedName: \"Luminans\",\n emoji: \"\uD83D\uDD06\",\n sources: Object.freeze([\n \"https://www.bipm.org/en/measurement-units/si-derived-units\",\n \"https://en.wikipedia.org/wiki/Luminance\",\n \"https://en.wikipedia.org/wiki/Candela_per_square_metre\",\n ]),\n });\n\n protected get units(): MeasurementUnitIndex<LuminanceUnit> {\n return LuminanceUnit.index;\n }\n\n protected withBaseValue(baseValue: Decimal, unit: LuminanceUnit): this {\n return new Luminance(baseValue, unit) as this;\n }\n\n get candelaPerSquareMetre(): number {\n return this.in(LuminanceUnit.CandelaPerSquareMetre);\n }\n get nits(): number {\n return this.in(LuminanceUnit.Nit);\n }\n get kilocandelaPerSquareMetre(): number {\n return this.in(LuminanceUnit.KilocandelaPerSquareMetre);\n }\n\n /** Creates a luminance from a value and unit. */\n static create(value: number | Decimal, unit: LuminanceUnit): Luminance {\n LuminanceUnit.index.assertOwns(unit);\n const amount = value instanceof Decimal ? value : Luminance.decimalOf(value);\n return new Luminance(unit.toBase(amount), unit);\n }\n\n static fromCandelaPerSquareMetre(value: number | Decimal): Luminance {\n return Luminance.create(value, LuminanceUnit.CandelaPerSquareMetre);\n }\n static fromNits(value: number | Decimal): Luminance {\n return Luminance.create(value, LuminanceUnit.Nit);\n }\n static fromKilocandelaPerSquareMetre(value: number | Decimal): Luminance {\n return Luminance.create(value, LuminanceUnit.KilocandelaPerSquareMetre);\n }\n\n /**\n * A bare number is read as candela per square metre, since screen brightness\n * is quoted that way. Negative luminance is rejected.\n */\n static tryParse(input: string | null | undefined): Luminance | null {\n const bare = tryParseNumberOnly(input);\n if (bare !== null) {\n if (bare.isNegative) return null;\n return new Luminance(bare, LuminanceUnit.CandelaPerSquareMetre);\n }\n\n const parsed = tryParseQuantity(input, LuminanceUnit.index);\n return parsed === null ? null : new Luminance(parsed.baseValue, parsed.unit);\n }\n\n static parse(input: string): Luminance {\n const result = Luminance.tryParse(input);\n if (result === null) throw new Error(\"Invalid luminance.\");\n return result;\n }\n\n static isValid(input: string | null | undefined): boolean {\n return Luminance.tryParse(input) !== null;\n }\n\n /** Display form in the parsed unit, or `null` when the input is invalid. */\n static format(input: string | null | undefined, options?: LuminanceFormatOptions): string | null {\n const parsed = Luminance.tryParse(input);\n if (parsed !== null) {\n if (options?.unit !== undefined || options?.decimals !== undefined) {\n return parsed.toStringInUnit(options.unit ?? parsed.originalUnit, options.decimals);\n }\n return parsed.toString();\n }\n return Luminance.fallback(input, options);\n }\n\n /** The value in candela per square metre, or `null` when the input is invalid. */\n static normalize(\n input: string | null | undefined,\n options?: LuminanceFormatOptions,\n ): string | null {\n const parsed = Luminance.tryParse(input);\n if (parsed !== null) return parsed.toNormalizedString();\n return Luminance.fallback(input, options);\n }\n\n static isNormalized(input: string | null | undefined): boolean {\n return input != null && Luminance.normalize(input) === input;\n }\n\n static findCandidatesInText(text: string): readonly TextCandidate<Luminance>[] {\n if (!text) return [];\n\n const out: TextCandidate<Luminance>[] = [];\n const pattern = new RegExp(SCAN_PATTERN.source, SCAN_PATTERN.flags);\n let match: RegExpExecArray | null;\n\n while ((match = pattern.exec(text)) !== null) {\n const value = Luminance.tryParse(match[0]);\n if (value === null) continue;\n out.push(\n new TextCandidate<Luminance>({\n startIndex: match.index,\n length: match[0].length,\n originalText: match[0],\n typeName: \"Luminance\",\n category: TextCandidateCategory.Measurement,\n normalizedForm: value.toNormalizedString(),\n formattedForm: value.toString(),\n maskedForm: maskLuminance(value),\n confidence: TextMatchConfidence.Medium,\n value,\n }),\n );\n }\n\n return out;\n }\n\n private static fallback(\n input: string | null | undefined,\n options?: LuminanceFormatOptions,\n ): string | null {\n if (options?.fallbackToTrimmedInputWhenInvalid !== true) return null;\n if (input == null) return null;\n const trimmed = input.trim();\n return trimmed.length > 0 ? trimmed : null;\n }\n\n private static decimalOf(value: number): Decimal {\n const parsed = Decimal.fromNumber(value);\n if (parsed === null) throw new Error(`Value cannot be represented as a luminance: ${value}`);\n return parsed;\n }\n}\n\n/** Returns a masked luminance, e.g. `10 cd/m\u00B2` becomes `*** cd/m\u00B2`. */\nexport function maskLuminance(value: Luminance): string {\n return `*** ${value.originalUnit.symbol}`;\n}\n"],
5
5
  "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -28,7 +28,10 @@ var import_text_candidate = require("../text-scanning/text-candidate.js");
28
28
  var import_text_candidate_category = require("../text-scanning/text-candidate-category.js");
29
29
  var import_text_match_confidence = require("../text-scanning/text-match-confidence.js");
30
30
  var import_luminous_flux_unit = require("./luminous-flux-unit.js");
31
- const SCAN_PATTERN = new RegExp(String.raw`(?<!\w)(?:[+-]\s*)?\d+[0-9 .,]*\s*(?:klm|lm)\b`, "giu");
31
+ const SCAN_PATTERN = new RegExp(
32
+ String.raw`(?<![\p{L}\p{N}_])(?:[+-]\s*)?\d+[0-9 .,]*\s*(?:klm|lm)(?![\p{L}\p{N}_])`,
33
+ "giu"
34
+ );
32
35
  class LuminousFlux extends import_quantity.Quantity {
33
36
  static typeInfo = Object.freeze({
34
37
  englishName: "Luminous Flux",
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/measurement/luminous-flux.ts"],
4
- "sourcesContent": ["import { Decimal } from \"../internal/decimal.js\";\nimport type { MeasurementUnitIndex } from \"../internal/measurement-unit.js\";\nimport { Quantity, tryParseQuantity } from \"../internal/quantity.js\";\nimport type { PrimitiveTypeInfo } from \"../primitive-type-info.js\";\nimport { TextCandidate } from \"../text-scanning/text-candidate.js\";\nimport { TextCandidateCategory } from \"../text-scanning/text-candidate-category.js\";\nimport { TextMatchConfidence } from \"../text-scanning/text-match-confidence.js\";\nimport { LuminousFluxUnit } from \"./luminous-flux-unit.js\";\n\n/** Options for {@link LuminousFlux.format} and {@link LuminousFlux.normalize}. */\nexport interface LuminousFluxFormatOptions {\n fallbackToTrimmedInputWhenInvalid?: boolean;\n /** Convert to this unit instead of preserving the parsed one. */\n unit?: LuminousFluxUnit;\n /** Round to this many fractional digits, away from zero. */\n decimals?: number;\n}\n\nconst SCAN_PATTERN = new RegExp(String.raw`(?<!\\w)(?:[+-]\\s*)?\\d+[0-9 .,]*\\s*(?:klm|lm)\\b`, \"giu\");\n\n/**\n * A luminous flux stored internally in lumens,\n * parsed from any supported unit and convertible between all of them.\n */\nexport class LuminousFlux extends Quantity<LuminousFluxUnit> {\n static readonly typeInfo: PrimitiveTypeInfo = Object.freeze({\n englishName: \"Luminous Flux\",\n localizedName: \"Ljusfl\u00F6de\",\n emoji: \"\uD83D\uDCA1\",\n sources: Object.freeze([\n \"https://www.bipm.org/en/measurement-units/si-derived-units\",\n \"https://en.wikipedia.org/wiki/Lumen_(unit)\",\n ]),\n });\n\n protected get units(): MeasurementUnitIndex<LuminousFluxUnit> {\n return LuminousFluxUnit.index;\n }\n\n protected withBaseValue(baseValue: Decimal, unit: LuminousFluxUnit): this {\n return new LuminousFlux(baseValue, unit) as this;\n }\n\n get lumens(): number {\n return this.in(LuminousFluxUnit.Lumen);\n }\n get kilolumens(): number {\n return this.in(LuminousFluxUnit.Kilolumen);\n }\n\n /** Creates a luminous flux from a value and unit. */\n static create(value: number | Decimal, unit: LuminousFluxUnit): LuminousFlux {\n LuminousFluxUnit.index.assertOwns(unit);\n const amount = value instanceof Decimal ? value : LuminousFlux.decimalOf(value);\n return new LuminousFlux(unit.toBase(amount), unit);\n }\n\n static fromLumens(value: number | Decimal): LuminousFlux {\n return LuminousFlux.create(value, LuminousFluxUnit.Lumen);\n }\n static fromKilolumens(value: number | Decimal): LuminousFlux {\n return LuminousFlux.create(value, LuminousFluxUnit.Kilolumen);\n }\n\n static tryParse(input: string | null | undefined): LuminousFlux | null {\n const parsed = tryParseQuantity(input, LuminousFluxUnit.index);\n return parsed === null ? null : new LuminousFlux(parsed.baseValue, parsed.unit);\n }\n\n static parse(input: string): LuminousFlux {\n const result = LuminousFlux.tryParse(input);\n if (result === null) throw new Error(\"Invalid luminous flux.\");\n return result;\n }\n\n static isValid(input: string | null | undefined): boolean {\n return LuminousFlux.tryParse(input) !== null;\n }\n\n /** Display form in the parsed unit, or `null` when the input is invalid. */\n static format(\n input: string | null | undefined,\n options?: LuminousFluxFormatOptions,\n ): string | null {\n const parsed = LuminousFlux.tryParse(input);\n if (parsed !== null) {\n if (options?.unit !== undefined || options?.decimals !== undefined) {\n return parsed.toStringInUnit(options.unit ?? parsed.originalUnit, options.decimals);\n }\n return parsed.toString();\n }\n return LuminousFlux.fallback(input, options);\n }\n\n /** The value in lumens, or `null` when the input is invalid. */\n static normalize(\n input: string | null | undefined,\n options?: LuminousFluxFormatOptions,\n ): string | null {\n const parsed = LuminousFlux.tryParse(input);\n if (parsed !== null) return parsed.toNormalizedString();\n return LuminousFlux.fallback(input, options);\n }\n\n static isNormalized(input: string | null | undefined): boolean {\n return input != null && LuminousFlux.normalize(input) === input;\n }\n\n static findCandidatesInText(text: string): readonly TextCandidate<LuminousFlux>[] {\n if (!text) return [];\n\n const out: TextCandidate<LuminousFlux>[] = [];\n const pattern = new RegExp(SCAN_PATTERN.source, SCAN_PATTERN.flags);\n let match: RegExpExecArray | null;\n\n while ((match = pattern.exec(text)) !== null) {\n const value = LuminousFlux.tryParse(match[0]);\n if (value === null) continue;\n out.push(\n new TextCandidate<LuminousFlux>({\n startIndex: match.index,\n length: match[0].length,\n originalText: match[0],\n typeName: \"LuminousFlux\",\n category: TextCandidateCategory.Measurement,\n normalizedForm: value.toNormalizedString(),\n formattedForm: value.toString(),\n maskedForm: maskLuminousFlux(value),\n confidence: TextMatchConfidence.Medium,\n value,\n }),\n );\n }\n\n return out;\n }\n\n private static fallback(\n input: string | null | undefined,\n options?: LuminousFluxFormatOptions,\n ): string | null {\n if (options?.fallbackToTrimmedInputWhenInvalid !== true) return null;\n if (input == null) return null;\n const trimmed = input.trim();\n return trimmed.length > 0 ? trimmed : null;\n }\n\n private static decimalOf(value: number): Decimal {\n const parsed = Decimal.fromNumber(value);\n if (parsed === null)\n throw new Error(`Value cannot be represented as a luminous flux: ${value}`);\n return parsed;\n }\n}\n\n/** Returns a masked luminous flux, e.g. `10 lm` becomes `*** lm`. */\nexport function maskLuminousFlux(value: LuminousFlux): string {\n return `*** ${value.originalUnit.symbol}`;\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import { Decimal } from \"../internal/decimal.js\";\nimport type { MeasurementUnitIndex } from \"../internal/measurement-unit.js\";\nimport { Quantity, tryParseQuantity } from \"../internal/quantity.js\";\nimport type { PrimitiveTypeInfo } from \"../primitive-type-info.js\";\nimport { TextCandidate } from \"../text-scanning/text-candidate.js\";\nimport { TextCandidateCategory } from \"../text-scanning/text-candidate-category.js\";\nimport { TextMatchConfidence } from \"../text-scanning/text-match-confidence.js\";\nimport { LuminousFluxUnit } from \"./luminous-flux-unit.js\";\n\n/** Options for {@link LuminousFlux.format} and {@link LuminousFlux.normalize}. */\nexport interface LuminousFluxFormatOptions {\n fallbackToTrimmedInputWhenInvalid?: boolean;\n /** Convert to this unit instead of preserving the parsed one. */\n unit?: LuminousFluxUnit;\n /** Round to this many fractional digits, away from zero. */\n decimals?: number;\n}\n\nconst SCAN_PATTERN = new RegExp(\n String.raw`(?<![\\p{L}\\p{N}_])(?:[+-]\\s*)?\\d+[0-9 .,]*\\s*(?:klm|lm)(?![\\p{L}\\p{N}_])`,\n \"giu\",\n);\n\n/**\n * A luminous flux stored internally in lumens,\n * parsed from any supported unit and convertible between all of them.\n */\nexport class LuminousFlux extends Quantity<LuminousFluxUnit> {\n static readonly typeInfo: PrimitiveTypeInfo = Object.freeze({\n englishName: \"Luminous Flux\",\n localizedName: \"Ljusfl\u00F6de\",\n emoji: \"\uD83D\uDCA1\",\n sources: Object.freeze([\n \"https://www.bipm.org/en/measurement-units/si-derived-units\",\n \"https://en.wikipedia.org/wiki/Lumen_(unit)\",\n ]),\n });\n\n protected get units(): MeasurementUnitIndex<LuminousFluxUnit> {\n return LuminousFluxUnit.index;\n }\n\n protected withBaseValue(baseValue: Decimal, unit: LuminousFluxUnit): this {\n return new LuminousFlux(baseValue, unit) as this;\n }\n\n get lumens(): number {\n return this.in(LuminousFluxUnit.Lumen);\n }\n get kilolumens(): number {\n return this.in(LuminousFluxUnit.Kilolumen);\n }\n\n /** Creates a luminous flux from a value and unit. */\n static create(value: number | Decimal, unit: LuminousFluxUnit): LuminousFlux {\n LuminousFluxUnit.index.assertOwns(unit);\n const amount = value instanceof Decimal ? value : LuminousFlux.decimalOf(value);\n return new LuminousFlux(unit.toBase(amount), unit);\n }\n\n static fromLumens(value: number | Decimal): LuminousFlux {\n return LuminousFlux.create(value, LuminousFluxUnit.Lumen);\n }\n static fromKilolumens(value: number | Decimal): LuminousFlux {\n return LuminousFlux.create(value, LuminousFluxUnit.Kilolumen);\n }\n\n static tryParse(input: string | null | undefined): LuminousFlux | null {\n const parsed = tryParseQuantity(input, LuminousFluxUnit.index);\n return parsed === null ? null : new LuminousFlux(parsed.baseValue, parsed.unit);\n }\n\n static parse(input: string): LuminousFlux {\n const result = LuminousFlux.tryParse(input);\n if (result === null) throw new Error(\"Invalid luminous flux.\");\n return result;\n }\n\n static isValid(input: string | null | undefined): boolean {\n return LuminousFlux.tryParse(input) !== null;\n }\n\n /** Display form in the parsed unit, or `null` when the input is invalid. */\n static format(\n input: string | null | undefined,\n options?: LuminousFluxFormatOptions,\n ): string | null {\n const parsed = LuminousFlux.tryParse(input);\n if (parsed !== null) {\n if (options?.unit !== undefined || options?.decimals !== undefined) {\n return parsed.toStringInUnit(options.unit ?? parsed.originalUnit, options.decimals);\n }\n return parsed.toString();\n }\n return LuminousFlux.fallback(input, options);\n }\n\n /** The value in lumens, or `null` when the input is invalid. */\n static normalize(\n input: string | null | undefined,\n options?: LuminousFluxFormatOptions,\n ): string | null {\n const parsed = LuminousFlux.tryParse(input);\n if (parsed !== null) return parsed.toNormalizedString();\n return LuminousFlux.fallback(input, options);\n }\n\n static isNormalized(input: string | null | undefined): boolean {\n return input != null && LuminousFlux.normalize(input) === input;\n }\n\n static findCandidatesInText(text: string): readonly TextCandidate<LuminousFlux>[] {\n if (!text) return [];\n\n const out: TextCandidate<LuminousFlux>[] = [];\n const pattern = new RegExp(SCAN_PATTERN.source, SCAN_PATTERN.flags);\n let match: RegExpExecArray | null;\n\n while ((match = pattern.exec(text)) !== null) {\n const value = LuminousFlux.tryParse(match[0]);\n if (value === null) continue;\n out.push(\n new TextCandidate<LuminousFlux>({\n startIndex: match.index,\n length: match[0].length,\n originalText: match[0],\n typeName: \"LuminousFlux\",\n category: TextCandidateCategory.Measurement,\n normalizedForm: value.toNormalizedString(),\n formattedForm: value.toString(),\n maskedForm: maskLuminousFlux(value),\n confidence: TextMatchConfidence.Medium,\n value,\n }),\n );\n }\n\n return out;\n }\n\n private static fallback(\n input: string | null | undefined,\n options?: LuminousFluxFormatOptions,\n ): string | null {\n if (options?.fallbackToTrimmedInputWhenInvalid !== true) return null;\n if (input == null) return null;\n const trimmed = input.trim();\n return trimmed.length > 0 ? trimmed : null;\n }\n\n private static decimalOf(value: number): Decimal {\n const parsed = Decimal.fromNumber(value);\n if (parsed === null)\n throw new Error(`Value cannot be represented as a luminous flux: ${value}`);\n return parsed;\n }\n}\n\n/** Returns a masked luminous flux, e.g. `10 lm` becomes `*** lm`. */\nexport function maskLuminousFlux(value: LuminousFlux): string {\n return `*** ${value.originalUnit.symbol}`;\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -33,7 +33,7 @@ const ONE = import_decimal.Decimal.parse("1");
33
33
  const HUNDRED = import_decimal.Decimal.parse("100");
34
34
  const MAX_INPUT_LENGTH = 100;
35
35
  const WORD_SUFFIX_PATTERN = /^(?<sign>[+-])?\s*(?<number>[0-9][0-9 .,]*[0-9]|[0-9])\s+(?:procent|percent)\s*$/iu;
36
- const SCAN_PATTERN = /(?<!\w)(?:[+-]\s*)?\d+[0-9 .,]*\s*%/gu;
36
+ const SCAN_PATTERN = /(?<![\p{L}\p{N}_])(?:[+-]\s*)?\d+[0-9 .,]*\s*%/gu;
37
37
  class Percentage {
38
38
  constructor(fraction) {
39
39
  this.fraction = fraction;
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/measurement/percentage.ts"],
4
- "sourcesContent": ["import { Decimal } from \"../internal/decimal.js\";\nimport { sanitizeInput } from \"../internal/input-sanitization.js\";\nimport { tryParseNumberOnly } from \"../internal/measurement-unit-parser.js\";\nimport type { PrimitiveTypeInfo } from \"../primitive-type-info.js\";\nimport { TextCandidate } from \"../text-scanning/text-candidate.js\";\nimport { TextCandidateCategory } from \"../text-scanning/text-candidate-category.js\";\nimport { TextMatchConfidence } from \"../text-scanning/text-match-confidence.js\";\n\n/** Options for {@link Percentage.format} and {@link Percentage.normalize}. */\nexport interface PercentageFormatOptions {\n fallbackToTrimmedInputWhenInvalid?: boolean;\n /** Round to this many fractional digits, away from zero. */\n decimals?: number;\n}\n\nconst ZERO = Decimal.parse(\"0\")!;\nconst ONE = Decimal.parse(\"1\")!;\nconst HUNDRED = Decimal.parse(\"100\")!;\nconst MAX_INPUT_LENGTH = 100;\n\nconst WORD_SUFFIX_PATTERN =\n /^(?<sign>[+-])?\\s*(?<number>[0-9][0-9 .,]*[0-9]|[0-9])\\s+(?:procent|percent)\\s*$/iu;\n\nconst SCAN_PATTERN = /(?<!\\w)(?:[+-]\\s*)?\\d+[0-9 .,]*\\s*%/gu;\n\n/**\n * A proportion from 0 to 100 percent, held internally as a fraction from 0 to\n * 1.\n *\n * Input without a percent sign is read on whichever scale fits: `0.85` is a\n * fraction, `85` is a percentage, and anything above 100 is rejected. With a\n * sign or the word `procent` / `percent` the percentage scale is explicit.\n */\nexport class Percentage {\n static readonly typeInfo: PrimitiveTypeInfo = Object.freeze({\n englishName: \"Percentage\",\n localizedName: \"Procent\",\n emoji: \"\uD83D\uDCCA\",\n sources: Object.freeze([\"https://www.bipm.org/en/publications/si-brochure\"]),\n });\n\n private constructor(private readonly fraction: Decimal) {}\n\n /** The value as a fraction from 0 to 1, e.g. `0.85` for 85%. */\n get value(): number {\n return this.fraction.toNumber();\n }\n\n /** The value on a 0\u2013100 scale, e.g. `85` for 85%. */\n get percent(): number {\n return this.fraction.times(HUNDRED).toNumber();\n }\n\n /** Creates a percentage from a 0\u20131 fraction, e.g. `0.85` for 85%. */\n static fromDecimal(value: number | Decimal): Percentage {\n return new Percentage(checkedFraction(toDecimal(value)));\n }\n\n /** Creates a percentage from a 0\u2013100 value, e.g. `85` for 85%. */\n static fromPercent(percent: number | Decimal): Percentage {\n const raw = toDecimal(percent);\n if (raw.compareTo(ZERO) < 0 || raw.compareTo(HUNDRED) > 0) {\n throw new Error(\"Percent must be in the range 0 to 100 inclusive.\");\n }\n return new Percentage(raw.dividedBy(HUNDRED));\n }\n\n /** Creates a percentage from a 0\u2013100 value; the same as {@link fromPercent}. */\n static create(percent: number | Decimal): Percentage {\n return Percentage.fromPercent(percent);\n }\n\n static tryParse(input: string | null | undefined): Percentage | null {\n if (input == null) return null;\n\n const trimmed = sanitizeInput(input).trim();\n if (trimmed.length === 0 || trimmed.length > MAX_INPUT_LENGTH) return null;\n\n let raw: Decimal | null;\n let isPercentScale = false;\n\n if (trimmed.endsWith(\"%\")) {\n raw = tryParseNumberOnly(trimmed.slice(0, -1).trimEnd());\n isPercentScale = true;\n } else {\n const match = WORD_SUFFIX_PATTERN.exec(trimmed);\n if (match?.groups?.[\"number\"] !== undefined) {\n const sign = match.groups[\"sign\"] ?? \"\";\n raw = tryParseNumberOnly(sign + match.groups[\"number\"]);\n isPercentScale = true;\n } else {\n raw = tryParseNumberOnly(trimmed);\n }\n }\n\n if (raw === null) return null;\n\n if (isPercentScale) {\n if (raw.compareTo(ZERO) < 0 || raw.compareTo(HUNDRED) > 0) return null;\n return new Percentage(raw.dividedBy(HUNDRED));\n }\n\n if (raw.compareTo(ZERO) < 0) return null;\n if (raw.compareTo(ONE) <= 0) return new Percentage(raw);\n if (raw.compareTo(HUNDRED) <= 0) return new Percentage(raw.dividedBy(HUNDRED));\n return null;\n }\n\n static parse(input: string): Percentage {\n const result = Percentage.tryParse(input);\n if (result === null) throw new Error(\"Invalid percentage.\");\n return result;\n }\n\n static isValid(input: string | null | undefined): boolean {\n return Percentage.tryParse(input) !== null;\n }\n\n /** Display form with a percent sign, e.g. `85%`. */\n static format(\n input: string | null | undefined,\n options?: PercentageFormatOptions,\n ): string | null {\n const parsed = Percentage.tryParse(input);\n if (parsed !== null) {\n return options?.decimals !== undefined\n ? formatPercent(parsed.fraction, options.decimals)\n : parsed.toString();\n }\n return fallback(input, options);\n }\n\n /** The fraction as an invariant decimal string, e.g. `0.85`. */\n static normalize(\n input: string | null | undefined,\n options?: PercentageFormatOptions,\n ): string | null {\n const parsed = Percentage.tryParse(input);\n if (parsed !== null) return parsed.toNormalizedString();\n return fallback(input, options);\n }\n\n static isNormalized(input: string | null | undefined): boolean {\n return input != null && Percentage.normalize(input) === input;\n }\n\n /** The fraction, e.g. `0.85`. */\n toNormalizedString(): string {\n return this.fraction.toTrimmedString();\n }\n\n /** The value with a percent sign, e.g. `85%`. */\n toString(): string {\n return formatPercent(this.fraction);\n }\n\n plus(other: Percentage): Percentage {\n return new Percentage(checkedFraction(this.fraction.plus(other.fraction)));\n }\n\n minus(other: Percentage): Percentage {\n return new Percentage(checkedFraction(this.fraction.minus(other.fraction)));\n }\n\n times(factor: number | Decimal): Percentage {\n return new Percentage(checkedFraction(this.fraction.times(toDecimal(factor))));\n }\n\n dividedBy(divisor: number | Decimal): Percentage {\n return new Percentage(checkedFraction(this.fraction.dividedBy(toDecimal(divisor))));\n }\n\n compareTo(other: Percentage | null | undefined): number {\n if (other == null) return 1;\n return this.fraction.compareTo(other.fraction);\n }\n\n equals(other: Percentage | null | undefined): boolean {\n return other != null && this.fraction.equals(other.fraction);\n }\n\n isLessThan(other: Percentage): boolean {\n return this.compareTo(other) < 0;\n }\n\n isGreaterThan(other: Percentage): boolean {\n return this.compareTo(other) > 0;\n }\n\n static findCandidatesInText(text: string): readonly TextCandidate<Percentage>[] {\n if (!text) return [];\n\n const out: TextCandidate<Percentage>[] = [];\n const pattern = new RegExp(SCAN_PATTERN.source, SCAN_PATTERN.flags);\n let match: RegExpExecArray | null;\n\n while ((match = pattern.exec(text)) !== null) {\n const value = Percentage.tryParse(match[0]);\n if (value === null) continue;\n out.push(\n new TextCandidate<Percentage>({\n startIndex: match.index,\n length: match[0].length,\n originalText: match[0],\n typeName: \"Percentage\",\n category: TextCandidateCategory.Measurement,\n normalizedForm: value.toNormalizedString(),\n formattedForm: value.toString(),\n maskedForm: maskPercentage(value),\n confidence: TextMatchConfidence.Low,\n value,\n }),\n );\n }\n\n return out;\n }\n}\n\n/** Returns a masked percentage, e.g. `85%` becomes `***%`. */\nexport function maskPercentage(percentage: Percentage): string {\n void percentage;\n return \"***%\";\n}\n\nfunction formatPercent(fraction: Decimal, decimals?: number): string {\n const percent = fraction.times(HUNDRED);\n const rounded = decimals === undefined ? percent : percent.round(decimals, \"away-from-zero\");\n return `${rounded.toTrimmedString()}%`;\n}\n\nfunction checkedFraction(fraction: Decimal): Decimal {\n if (fraction.compareTo(ZERO) < 0 || fraction.compareTo(ONE) > 0) {\n throw new Error(\"The operation produced a percentage outside the range 0% to 100%.\");\n }\n return fraction;\n}\n\nfunction toDecimal(value: number | Decimal): Decimal {\n if (value instanceof Decimal) return value;\n const parsed = Decimal.fromNumber(value);\n if (parsed === null) throw new Error(`Value cannot be represented as a percentage: ${value}`);\n return parsed;\n}\n\nfunction fallback(\n input: string | null | undefined,\n options?: PercentageFormatOptions,\n): string | null {\n if (options?.fallbackToTrimmedInputWhenInvalid !== true) return null;\n if (input == null) return null;\n const trimmed = input.trim();\n return trimmed.length > 0 ? trimmed : null;\n}\n"],
4
+ "sourcesContent": ["import { Decimal } from \"../internal/decimal.js\";\nimport { sanitizeInput } from \"../internal/input-sanitization.js\";\nimport { tryParseNumberOnly } from \"../internal/measurement-unit-parser.js\";\nimport type { PrimitiveTypeInfo } from \"../primitive-type-info.js\";\nimport { TextCandidate } from \"../text-scanning/text-candidate.js\";\nimport { TextCandidateCategory } from \"../text-scanning/text-candidate-category.js\";\nimport { TextMatchConfidence } from \"../text-scanning/text-match-confidence.js\";\n\n/** Options for {@link Percentage.format} and {@link Percentage.normalize}. */\nexport interface PercentageFormatOptions {\n fallbackToTrimmedInputWhenInvalid?: boolean;\n /** Round to this many fractional digits, away from zero. */\n decimals?: number;\n}\n\nconst ZERO = Decimal.parse(\"0\")!;\nconst ONE = Decimal.parse(\"1\")!;\nconst HUNDRED = Decimal.parse(\"100\")!;\nconst MAX_INPUT_LENGTH = 100;\n\nconst WORD_SUFFIX_PATTERN =\n /^(?<sign>[+-])?\\s*(?<number>[0-9][0-9 .,]*[0-9]|[0-9])\\s+(?:procent|percent)\\s*$/iu;\n\nconst SCAN_PATTERN = /(?<![\\p{L}\\p{N}_])(?:[+-]\\s*)?\\d+[0-9 .,]*\\s*%/gu;\n\n/**\n * A proportion from 0 to 100 percent, held internally as a fraction from 0 to\n * 1.\n *\n * Input without a percent sign is read on whichever scale fits: `0.85` is a\n * fraction, `85` is a percentage, and anything above 100 is rejected. With a\n * sign or the word `procent` / `percent` the percentage scale is explicit.\n */\nexport class Percentage {\n static readonly typeInfo: PrimitiveTypeInfo = Object.freeze({\n englishName: \"Percentage\",\n localizedName: \"Procent\",\n emoji: \"\uD83D\uDCCA\",\n sources: Object.freeze([\"https://www.bipm.org/en/publications/si-brochure\"]),\n });\n\n private constructor(private readonly fraction: Decimal) {}\n\n /** The value as a fraction from 0 to 1, e.g. `0.85` for 85%. */\n get value(): number {\n return this.fraction.toNumber();\n }\n\n /** The value on a 0\u2013100 scale, e.g. `85` for 85%. */\n get percent(): number {\n return this.fraction.times(HUNDRED).toNumber();\n }\n\n /** Creates a percentage from a 0\u20131 fraction, e.g. `0.85` for 85%. */\n static fromDecimal(value: number | Decimal): Percentage {\n return new Percentage(checkedFraction(toDecimal(value)));\n }\n\n /** Creates a percentage from a 0\u2013100 value, e.g. `85` for 85%. */\n static fromPercent(percent: number | Decimal): Percentage {\n const raw = toDecimal(percent);\n if (raw.compareTo(ZERO) < 0 || raw.compareTo(HUNDRED) > 0) {\n throw new Error(\"Percent must be in the range 0 to 100 inclusive.\");\n }\n return new Percentage(raw.dividedBy(HUNDRED));\n }\n\n /** Creates a percentage from a 0\u2013100 value; the same as {@link fromPercent}. */\n static create(percent: number | Decimal): Percentage {\n return Percentage.fromPercent(percent);\n }\n\n static tryParse(input: string | null | undefined): Percentage | null {\n if (input == null) return null;\n\n const trimmed = sanitizeInput(input).trim();\n if (trimmed.length === 0 || trimmed.length > MAX_INPUT_LENGTH) return null;\n\n let raw: Decimal | null;\n let isPercentScale = false;\n\n if (trimmed.endsWith(\"%\")) {\n raw = tryParseNumberOnly(trimmed.slice(0, -1).trimEnd());\n isPercentScale = true;\n } else {\n const match = WORD_SUFFIX_PATTERN.exec(trimmed);\n if (match?.groups?.[\"number\"] !== undefined) {\n const sign = match.groups[\"sign\"] ?? \"\";\n raw = tryParseNumberOnly(sign + match.groups[\"number\"]);\n isPercentScale = true;\n } else {\n raw = tryParseNumberOnly(trimmed);\n }\n }\n\n if (raw === null) return null;\n\n if (isPercentScale) {\n if (raw.compareTo(ZERO) < 0 || raw.compareTo(HUNDRED) > 0) return null;\n return new Percentage(raw.dividedBy(HUNDRED));\n }\n\n if (raw.compareTo(ZERO) < 0) return null;\n if (raw.compareTo(ONE) <= 0) return new Percentage(raw);\n if (raw.compareTo(HUNDRED) <= 0) return new Percentage(raw.dividedBy(HUNDRED));\n return null;\n }\n\n static parse(input: string): Percentage {\n const result = Percentage.tryParse(input);\n if (result === null) throw new Error(\"Invalid percentage.\");\n return result;\n }\n\n static isValid(input: string | null | undefined): boolean {\n return Percentage.tryParse(input) !== null;\n }\n\n /** Display form with a percent sign, e.g. `85%`. */\n static format(\n input: string | null | undefined,\n options?: PercentageFormatOptions,\n ): string | null {\n const parsed = Percentage.tryParse(input);\n if (parsed !== null) {\n return options?.decimals !== undefined\n ? formatPercent(parsed.fraction, options.decimals)\n : parsed.toString();\n }\n return fallback(input, options);\n }\n\n /** The fraction as an invariant decimal string, e.g. `0.85`. */\n static normalize(\n input: string | null | undefined,\n options?: PercentageFormatOptions,\n ): string | null {\n const parsed = Percentage.tryParse(input);\n if (parsed !== null) return parsed.toNormalizedString();\n return fallback(input, options);\n }\n\n static isNormalized(input: string | null | undefined): boolean {\n return input != null && Percentage.normalize(input) === input;\n }\n\n /** The fraction, e.g. `0.85`. */\n toNormalizedString(): string {\n return this.fraction.toTrimmedString();\n }\n\n /** The value with a percent sign, e.g. `85%`. */\n toString(): string {\n return formatPercent(this.fraction);\n }\n\n plus(other: Percentage): Percentage {\n return new Percentage(checkedFraction(this.fraction.plus(other.fraction)));\n }\n\n minus(other: Percentage): Percentage {\n return new Percentage(checkedFraction(this.fraction.minus(other.fraction)));\n }\n\n times(factor: number | Decimal): Percentage {\n return new Percentage(checkedFraction(this.fraction.times(toDecimal(factor))));\n }\n\n dividedBy(divisor: number | Decimal): Percentage {\n return new Percentage(checkedFraction(this.fraction.dividedBy(toDecimal(divisor))));\n }\n\n compareTo(other: Percentage | null | undefined): number {\n if (other == null) return 1;\n return this.fraction.compareTo(other.fraction);\n }\n\n equals(other: Percentage | null | undefined): boolean {\n return other != null && this.fraction.equals(other.fraction);\n }\n\n isLessThan(other: Percentage): boolean {\n return this.compareTo(other) < 0;\n }\n\n isGreaterThan(other: Percentage): boolean {\n return this.compareTo(other) > 0;\n }\n\n static findCandidatesInText(text: string): readonly TextCandidate<Percentage>[] {\n if (!text) return [];\n\n const out: TextCandidate<Percentage>[] = [];\n const pattern = new RegExp(SCAN_PATTERN.source, SCAN_PATTERN.flags);\n let match: RegExpExecArray | null;\n\n while ((match = pattern.exec(text)) !== null) {\n const value = Percentage.tryParse(match[0]);\n if (value === null) continue;\n out.push(\n new TextCandidate<Percentage>({\n startIndex: match.index,\n length: match[0].length,\n originalText: match[0],\n typeName: \"Percentage\",\n category: TextCandidateCategory.Measurement,\n normalizedForm: value.toNormalizedString(),\n formattedForm: value.toString(),\n maskedForm: maskPercentage(value),\n confidence: TextMatchConfidence.Low,\n value,\n }),\n );\n }\n\n return out;\n }\n}\n\n/** Returns a masked percentage, e.g. `85%` becomes `***%`. */\nexport function maskPercentage(percentage: Percentage): string {\n void percentage;\n return \"***%\";\n}\n\nfunction formatPercent(fraction: Decimal, decimals?: number): string {\n const percent = fraction.times(HUNDRED);\n const rounded = decimals === undefined ? percent : percent.round(decimals, \"away-from-zero\");\n return `${rounded.toTrimmedString()}%`;\n}\n\nfunction checkedFraction(fraction: Decimal): Decimal {\n if (fraction.compareTo(ZERO) < 0 || fraction.compareTo(ONE) > 0) {\n throw new Error(\"The operation produced a percentage outside the range 0% to 100%.\");\n }\n return fraction;\n}\n\nfunction toDecimal(value: number | Decimal): Decimal {\n if (value instanceof Decimal) return value;\n const parsed = Decimal.fromNumber(value);\n if (parsed === null) throw new Error(`Value cannot be represented as a percentage: ${value}`);\n return parsed;\n}\n\nfunction fallback(\n input: string | null | undefined,\n options?: PercentageFormatOptions,\n): string | null {\n if (options?.fallbackToTrimmedInputWhenInvalid !== true) return null;\n if (input == null) return null;\n const trimmed = input.trim();\n return trimmed.length > 0 ? trimmed : null;\n}\n"],
5
5
  "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -28,10 +28,14 @@ var import_text_candidate = require("../text-scanning/text-candidate.js");
28
28
  var import_text_candidate_category = require("../text-scanning/text-candidate-category.js");
29
29
  var import_text_match_confidence = require("../text-scanning/text-match-confidence.js");
30
30
  var import_power_unit = require("./power-unit.js");
31
- const VALUE_PREFIX = String.raw`(?<!\w)(?:[+-]\s*)?\d+[0-9 .,]*\s*`;
32
- const CASE_SENSITIVE_PATTERN = new RegExp(String.raw`${VALUE_PREFIX}(?:µW|uW|mW|MW|TW)\b`, "gu");
31
+ const VALUE_PREFIX = String.raw`(?<![\p{L}\p{N}_])(?:[+-]\s*)?\d+[0-9 .,]*\s*`;
32
+ const UNIT_SUFFIX = String.raw`(?![\p{L}\p{N}_])`;
33
+ const CASE_SENSITIVE_PATTERN = new RegExp(
34
+ String.raw`${VALUE_PREFIX}(?:µW|uW|mW|MW|TW)${UNIT_SUFFIX}`,
35
+ "gu"
36
+ );
33
37
  const CASE_INSENSITIVE_PATTERN = new RegExp(
34
- String.raw`${VALUE_PREFIX}(?:kW|GW|W|hp|horsepower|hästkraft|hästkrafter|microwatt|mikrowatt|milliwatt|megawatt|gigawatt|terawatt|kilowatt|watt)\b`,
38
+ String.raw`${VALUE_PREFIX}(?:kW|GW|W|hp|horsepower|hästkraft|hästkrafter|microwatt|mikrowatt|milliwatt|megawatt|gigawatt|terawatt|kilowatt|watt)${UNIT_SUFFIX}`,
35
39
  "giu"
36
40
  );
37
41
  class Power extends import_quantity.Quantity {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/measurement/power.ts"],
4
- "sourcesContent": ["import { Decimal } from \"../internal/decimal.js\";\nimport type { MeasurementUnitIndex } from \"../internal/measurement-unit.js\";\nimport { Quantity, tryParseQuantity } from \"../internal/quantity.js\";\nimport type { PrimitiveTypeInfo } from \"../primitive-type-info.js\";\nimport { TextCandidate } from \"../text-scanning/text-candidate.js\";\nimport { TextCandidateCategory } from \"../text-scanning/text-candidate-category.js\";\nimport { TextMatchConfidence } from \"../text-scanning/text-match-confidence.js\";\nimport { PowerUnit } from \"./power-unit.js\";\n\n/** Options for {@link Power.format} and {@link Power.normalize}. */\nexport interface PowerFormatOptions {\n fallbackToTrimmedInputWhenInvalid?: boolean;\n /** Convert to this unit instead of preserving the parsed one. */\n unit?: PowerUnit;\n /** Round to this many fractional digits, away from zero. */\n decimals?: number;\n}\n\nconst VALUE_PREFIX = String.raw`(?<!\\w)(?:[+-]\\s*)?\\d+[0-9 .,]*\\s*`;\n\n/**\n * Milliwatt and megawatt differ only by case, so their symbols must be matched\n * case-sensitively. Everything else may be written either way.\n *\n * .NET expresses this as one pattern with a scoped `(?i:...)` group. JavaScript\n * has no scoped flags, so the two halves are separate patterns and\n * {@link Power.findCandidatesInText} merges their results.\n */\nconst CASE_SENSITIVE_PATTERN = new RegExp(String.raw`${VALUE_PREFIX}(?:\u00B5W|uW|mW|MW|TW)\\b`, \"gu\");\n\nconst CASE_INSENSITIVE_PATTERN = new RegExp(\n String.raw`${VALUE_PREFIX}(?:kW|GW|W|hp|horsepower|h\u00E4stkraft|h\u00E4stkrafter|microwatt|mikrowatt|milliwatt|megawatt|gigawatt|terawatt|kilowatt|watt)\\b`,\n \"giu\",\n);\n\n/**\n * A power stored internally in watts,\n * parsed from any supported unit and convertible between all of them.\n */\nexport class Power extends Quantity<PowerUnit> {\n static readonly typeInfo: PrimitiveTypeInfo = Object.freeze({\n englishName: \"Power\",\n localizedName: \"Effekt\",\n emoji: \"\uD83D\uDCA1\",\n sources: Object.freeze([\n \"https://www.bipm.org/en/measurement-units/si-derived-units\",\n \"https://en.wikipedia.org/wiki/Horsepower\",\n ]),\n });\n\n protected get units(): MeasurementUnitIndex<PowerUnit> {\n return PowerUnit.index;\n }\n\n protected withBaseValue(baseValue: Decimal, unit: PowerUnit): this {\n return new Power(baseValue, unit) as this;\n }\n\n get microwatts(): number {\n return this.in(PowerUnit.Microwatt);\n }\n get milliwatts(): number {\n return this.in(PowerUnit.Milliwatt);\n }\n get watts(): number {\n return this.in(PowerUnit.Watt);\n }\n get kilowatts(): number {\n return this.in(PowerUnit.Kilowatt);\n }\n get megawatts(): number {\n return this.in(PowerUnit.Megawatt);\n }\n get gigawatts(): number {\n return this.in(PowerUnit.Gigawatt);\n }\n get terawatts(): number {\n return this.in(PowerUnit.Terawatt);\n }\n get horsepower(): number {\n return this.in(PowerUnit.Horsepower);\n }\n\n /** Creates a power from a value and unit. */\n static create(value: number | Decimal, unit: PowerUnit): Power {\n PowerUnit.index.assertOwns(unit);\n const amount = value instanceof Decimal ? value : Power.decimalOf(value);\n return new Power(unit.toBase(amount), unit);\n }\n\n static fromMicrowatts(value: number | Decimal): Power {\n return Power.create(value, PowerUnit.Microwatt);\n }\n static fromMilliwatts(value: number | Decimal): Power {\n return Power.create(value, PowerUnit.Milliwatt);\n }\n static fromWatts(value: number | Decimal): Power {\n return Power.create(value, PowerUnit.Watt);\n }\n static fromKilowatts(value: number | Decimal): Power {\n return Power.create(value, PowerUnit.Kilowatt);\n }\n static fromMegawatts(value: number | Decimal): Power {\n return Power.create(value, PowerUnit.Megawatt);\n }\n static fromGigawatts(value: number | Decimal): Power {\n return Power.create(value, PowerUnit.Gigawatt);\n }\n static fromTerawatts(value: number | Decimal): Power {\n return Power.create(value, PowerUnit.Terawatt);\n }\n static fromHorsepower(value: number | Decimal): Power {\n return Power.create(value, PowerUnit.Horsepower);\n }\n\n static tryParse(input: string | null | undefined): Power | null {\n const parsed = tryParseQuantity(input, PowerUnit.index, PowerUnit.tryParse);\n return parsed === null ? null : new Power(parsed.baseValue, parsed.unit);\n }\n\n static parse(input: string): Power {\n const result = Power.tryParse(input);\n if (result === null) throw new Error(\"Invalid power.\");\n return result;\n }\n\n static isValid(input: string | null | undefined): boolean {\n return Power.tryParse(input) !== null;\n }\n\n /** Display form in the parsed unit, or `null` when the input is invalid. */\n static format(input: string | null | undefined, options?: PowerFormatOptions): string | null {\n const parsed = Power.tryParse(input);\n if (parsed !== null) {\n if (options?.unit !== undefined || options?.decimals !== undefined) {\n return parsed.toStringInUnit(options.unit ?? parsed.originalUnit, options.decimals);\n }\n return parsed.toString();\n }\n return Power.fallback(input, options);\n }\n\n /** The value in watts, or `null` when the input is invalid. */\n static normalize(input: string | null | undefined, options?: PowerFormatOptions): string | null {\n const parsed = Power.tryParse(input);\n if (parsed !== null) return parsed.toNormalizedString();\n return Power.fallback(input, options);\n }\n\n static isNormalized(input: string | null | undefined): boolean {\n return input != null && Power.normalize(input) === input;\n }\n\n static findCandidatesInText(text: string): readonly TextCandidate<Power>[] {\n if (!text) return [];\n\n const out: TextCandidate<Power>[] = [];\n\n // The case-sensitive symbols run first so that `mW` and `MW` win over the\n // looser `W`, mirroring the alternation order in the .NET pattern.\n for (const pattern of [CASE_SENSITIVE_PATTERN, CASE_INSENSITIVE_PATTERN]) {\n const scanner = new RegExp(pattern.source, pattern.flags);\n let match: RegExpExecArray | null;\n\n while ((match = scanner.exec(text)) !== null) {\n const start = match.index;\n const end = start + match[0].length;\n if (out.some((c) => start < c.endIndex && c.startIndex < end)) continue;\n\n const value = Power.tryParse(match[0]);\n if (value === null) continue;\n out.push(\n new TextCandidate<Power>({\n startIndex: start,\n length: match[0].length,\n originalText: match[0],\n typeName: \"Power\",\n category: TextCandidateCategory.Measurement,\n normalizedForm: value.toNormalizedString(),\n formattedForm: value.toString(),\n maskedForm: maskPower(value),\n confidence: TextMatchConfidence.Medium,\n value,\n }),\n );\n }\n }\n\n return out.sort((a, b) => a.startIndex - b.startIndex);\n }\n\n private static fallback(\n input: string | null | undefined,\n options?: PowerFormatOptions,\n ): string | null {\n if (options?.fallbackToTrimmedInputWhenInvalid !== true) return null;\n if (input == null) return null;\n const trimmed = input.trim();\n return trimmed.length > 0 ? trimmed : null;\n }\n\n private static decimalOf(value: number): Decimal {\n const parsed = Decimal.fromNumber(value);\n if (parsed === null) throw new Error(`Value cannot be represented as a power: ${value}`);\n return parsed;\n }\n}\n\n/** Returns a masked power, e.g. `10 W` becomes `*** W`. */\nexport function maskPower(value: Power): string {\n return `*** ${value.originalUnit.symbol}`;\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import { Decimal } from \"../internal/decimal.js\";\nimport type { MeasurementUnitIndex } from \"../internal/measurement-unit.js\";\nimport { Quantity, tryParseQuantity } from \"../internal/quantity.js\";\nimport type { PrimitiveTypeInfo } from \"../primitive-type-info.js\";\nimport { TextCandidate } from \"../text-scanning/text-candidate.js\";\nimport { TextCandidateCategory } from \"../text-scanning/text-candidate-category.js\";\nimport { TextMatchConfidence } from \"../text-scanning/text-match-confidence.js\";\nimport { PowerUnit } from \"./power-unit.js\";\n\n/** Options for {@link Power.format} and {@link Power.normalize}. */\nexport interface PowerFormatOptions {\n fallbackToTrimmedInputWhenInvalid?: boolean;\n /** Convert to this unit instead of preserving the parsed one. */\n unit?: PowerUnit;\n /** Round to this many fractional digits, away from zero. */\n decimals?: number;\n}\n\nconst VALUE_PREFIX = String.raw`(?<![\\p{L}\\p{N}_])(?:[+-]\\s*)?\\d+[0-9 .,]*\\s*`;\nconst UNIT_SUFFIX = String.raw`(?![\\p{L}\\p{N}_])`;\n\n/**\n * Milliwatt and megawatt differ only by case, so their symbols must be matched\n * case-sensitively. Everything else may be written either way.\n *\n * .NET expresses this as one pattern with a scoped `(?i:...)` group. JavaScript\n * has no scoped flags, so the two halves are separate patterns and\n * {@link Power.findCandidatesInText} merges their results.\n */\nconst CASE_SENSITIVE_PATTERN = new RegExp(\n String.raw`${VALUE_PREFIX}(?:\u00B5W|uW|mW|MW|TW)${UNIT_SUFFIX}`,\n \"gu\",\n);\n\nconst CASE_INSENSITIVE_PATTERN = new RegExp(\n String.raw`${VALUE_PREFIX}(?:kW|GW|W|hp|horsepower|h\u00E4stkraft|h\u00E4stkrafter|microwatt|mikrowatt|milliwatt|megawatt|gigawatt|terawatt|kilowatt|watt)${UNIT_SUFFIX}`,\n \"giu\",\n);\n\n/**\n * A power stored internally in watts,\n * parsed from any supported unit and convertible between all of them.\n */\nexport class Power extends Quantity<PowerUnit> {\n static readonly typeInfo: PrimitiveTypeInfo = Object.freeze({\n englishName: \"Power\",\n localizedName: \"Effekt\",\n emoji: \"\uD83D\uDCA1\",\n sources: Object.freeze([\n \"https://www.bipm.org/en/measurement-units/si-derived-units\",\n \"https://en.wikipedia.org/wiki/Horsepower\",\n ]),\n });\n\n protected get units(): MeasurementUnitIndex<PowerUnit> {\n return PowerUnit.index;\n }\n\n protected withBaseValue(baseValue: Decimal, unit: PowerUnit): this {\n return new Power(baseValue, unit) as this;\n }\n\n get microwatts(): number {\n return this.in(PowerUnit.Microwatt);\n }\n get milliwatts(): number {\n return this.in(PowerUnit.Milliwatt);\n }\n get watts(): number {\n return this.in(PowerUnit.Watt);\n }\n get kilowatts(): number {\n return this.in(PowerUnit.Kilowatt);\n }\n get megawatts(): number {\n return this.in(PowerUnit.Megawatt);\n }\n get gigawatts(): number {\n return this.in(PowerUnit.Gigawatt);\n }\n get terawatts(): number {\n return this.in(PowerUnit.Terawatt);\n }\n get horsepower(): number {\n return this.in(PowerUnit.Horsepower);\n }\n\n /** Creates a power from a value and unit. */\n static create(value: number | Decimal, unit: PowerUnit): Power {\n PowerUnit.index.assertOwns(unit);\n const amount = value instanceof Decimal ? value : Power.decimalOf(value);\n return new Power(unit.toBase(amount), unit);\n }\n\n static fromMicrowatts(value: number | Decimal): Power {\n return Power.create(value, PowerUnit.Microwatt);\n }\n static fromMilliwatts(value: number | Decimal): Power {\n return Power.create(value, PowerUnit.Milliwatt);\n }\n static fromWatts(value: number | Decimal): Power {\n return Power.create(value, PowerUnit.Watt);\n }\n static fromKilowatts(value: number | Decimal): Power {\n return Power.create(value, PowerUnit.Kilowatt);\n }\n static fromMegawatts(value: number | Decimal): Power {\n return Power.create(value, PowerUnit.Megawatt);\n }\n static fromGigawatts(value: number | Decimal): Power {\n return Power.create(value, PowerUnit.Gigawatt);\n }\n static fromTerawatts(value: number | Decimal): Power {\n return Power.create(value, PowerUnit.Terawatt);\n }\n static fromHorsepower(value: number | Decimal): Power {\n return Power.create(value, PowerUnit.Horsepower);\n }\n\n static tryParse(input: string | null | undefined): Power | null {\n const parsed = tryParseQuantity(input, PowerUnit.index, PowerUnit.tryParse);\n return parsed === null ? null : new Power(parsed.baseValue, parsed.unit);\n }\n\n static parse(input: string): Power {\n const result = Power.tryParse(input);\n if (result === null) throw new Error(\"Invalid power.\");\n return result;\n }\n\n static isValid(input: string | null | undefined): boolean {\n return Power.tryParse(input) !== null;\n }\n\n /** Display form in the parsed unit, or `null` when the input is invalid. */\n static format(input: string | null | undefined, options?: PowerFormatOptions): string | null {\n const parsed = Power.tryParse(input);\n if (parsed !== null) {\n if (options?.unit !== undefined || options?.decimals !== undefined) {\n return parsed.toStringInUnit(options.unit ?? parsed.originalUnit, options.decimals);\n }\n return parsed.toString();\n }\n return Power.fallback(input, options);\n }\n\n /** The value in watts, or `null` when the input is invalid. */\n static normalize(input: string | null | undefined, options?: PowerFormatOptions): string | null {\n const parsed = Power.tryParse(input);\n if (parsed !== null) return parsed.toNormalizedString();\n return Power.fallback(input, options);\n }\n\n static isNormalized(input: string | null | undefined): boolean {\n return input != null && Power.normalize(input) === input;\n }\n\n static findCandidatesInText(text: string): readonly TextCandidate<Power>[] {\n if (!text) return [];\n\n const out: TextCandidate<Power>[] = [];\n\n // The case-sensitive symbols run first so that `mW` and `MW` win over the\n // looser `W`, mirroring the alternation order in the .NET pattern.\n for (const pattern of [CASE_SENSITIVE_PATTERN, CASE_INSENSITIVE_PATTERN]) {\n const scanner = new RegExp(pattern.source, pattern.flags);\n let match: RegExpExecArray | null;\n\n while ((match = scanner.exec(text)) !== null) {\n const start = match.index;\n const end = start + match[0].length;\n if (out.some((c) => start < c.endIndex && c.startIndex < end)) continue;\n\n const value = Power.tryParse(match[0]);\n if (value === null) continue;\n out.push(\n new TextCandidate<Power>({\n startIndex: start,\n length: match[0].length,\n originalText: match[0],\n typeName: \"Power\",\n category: TextCandidateCategory.Measurement,\n normalizedForm: value.toNormalizedString(),\n formattedForm: value.toString(),\n maskedForm: maskPower(value),\n confidence: TextMatchConfidence.Medium,\n value,\n }),\n );\n }\n }\n\n return out.sort((a, b) => a.startIndex - b.startIndex);\n }\n\n private static fallback(\n input: string | null | undefined,\n options?: PowerFormatOptions,\n ): string | null {\n if (options?.fallbackToTrimmedInputWhenInvalid !== true) return null;\n if (input == null) return null;\n const trimmed = input.trim();\n return trimmed.length > 0 ? trimmed : null;\n }\n\n private static decimalOf(value: number): Decimal {\n const parsed = Decimal.fromNumber(value);\n if (parsed === null) throw new Error(`Value cannot be represented as a power: ${value}`);\n return parsed;\n }\n}\n\n/** Returns a masked power, e.g. `10 W` becomes `*** W`. */\nexport function maskPower(value: Power): string {\n return `*** ${value.originalUnit.symbol}`;\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -29,7 +29,7 @@ var import_text_candidate_category = require("../text-scanning/text-candidate-ca
29
29
  var import_text_match_confidence = require("../text-scanning/text-match-confidence.js");
30
30
  var import_pressure_unit = require("./pressure-unit.js");
31
31
  const SCAN_PATTERN = new RegExp(
32
- String.raw`(?<!\w)(?:[+-]\s*)?\d+[0-9 .,]*\s*(?:GPa|MPa|kPa|hPa|mbar|mmHg|mm\s+Hg|lb/in²|atm|torr|bar|Pa|PSI)\b`,
32
+ String.raw`(?<![\p{L}\p{N}_])(?:[+-]\s*)?\d+[0-9 .,]*\s*(?:GPa|MPa|kPa|hPa|mbar|mmHg|mm\s+Hg|lb/in²|atm|torr|bar|Pa|PSI)(?![\p{L}\p{N}_])`,
33
33
  "giu"
34
34
  );
35
35
  class Pressure extends import_quantity.Quantity {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/measurement/pressure.ts"],
4
- "sourcesContent": ["import { Decimal } from \"../internal/decimal.js\";\nimport type { MeasurementUnitIndex } from \"../internal/measurement-unit.js\";\nimport { Quantity, tryParseQuantity } from \"../internal/quantity.js\";\nimport type { PrimitiveTypeInfo } from \"../primitive-type-info.js\";\nimport { TextCandidate } from \"../text-scanning/text-candidate.js\";\nimport { TextCandidateCategory } from \"../text-scanning/text-candidate-category.js\";\nimport { TextMatchConfidence } from \"../text-scanning/text-match-confidence.js\";\nimport { PressureUnit } from \"./pressure-unit.js\";\n\n/** Options for {@link Pressure.format} and {@link Pressure.normalize}. */\nexport interface PressureFormatOptions {\n fallbackToTrimmedInputWhenInvalid?: boolean;\n /** Convert to this unit instead of preserving the parsed one. */\n unit?: PressureUnit;\n /** Round to this many fractional digits, away from zero. */\n decimals?: number;\n}\n\nconst SCAN_PATTERN = new RegExp(\n String.raw`(?<!\\w)(?:[+-]\\s*)?\\d+[0-9 .,]*\\s*(?:GPa|MPa|kPa|hPa|mbar|mmHg|mm\\s+Hg|lb/in\u00B2|atm|torr|bar|Pa|PSI)\\b`,\n \"giu\",\n);\n\n/**\n * A pressure stored internally in pascals,\n * parsed from any supported unit and convertible between all of them.\n */\nexport class Pressure extends Quantity<PressureUnit> {\n static readonly typeInfo: PrimitiveTypeInfo = Object.freeze({\n englishName: \"Pressure\",\n localizedName: \"Tryck\",\n emoji: \"\uD83C\uDFAF\",\n sources: Object.freeze([\n \"https://www.bipm.org/en/measurement-units/si-derived-units\",\n \"https://en.wikipedia.org/wiki/Conversion_of_units\",\n ]),\n });\n\n protected get units(): MeasurementUnitIndex<PressureUnit> {\n return PressureUnit.index;\n }\n\n protected withBaseValue(baseValue: Decimal, unit: PressureUnit): this {\n return new Pressure(baseValue, unit) as this;\n }\n\n get pascals(): number {\n return this.in(PressureUnit.Pascal);\n }\n get hectopascals(): number {\n return this.in(PressureUnit.Hectopascal);\n }\n get kilopascals(): number {\n return this.in(PressureUnit.Kilopascal);\n }\n get megapascals(): number {\n return this.in(PressureUnit.Megapascal);\n }\n get gigapascals(): number {\n return this.in(PressureUnit.Gigapascal);\n }\n get bars(): number {\n return this.in(PressureUnit.Bar);\n }\n get millibars(): number {\n return this.in(PressureUnit.Millibar);\n }\n get psi(): number {\n return this.in(PressureUnit.Psi);\n }\n get atmospheres(): number {\n return this.in(PressureUnit.Atmosphere);\n }\n get millimetersOfMercury(): number {\n return this.in(PressureUnit.MillimeterOfMercury);\n }\n\n /** Creates a pressure from a value and unit. */\n static create(value: number | Decimal, unit: PressureUnit): Pressure {\n PressureUnit.index.assertOwns(unit);\n const amount = value instanceof Decimal ? value : Pressure.decimalOf(value);\n return new Pressure(unit.toBase(amount), unit);\n }\n\n static fromPascals(value: number | Decimal): Pressure {\n return Pressure.create(value, PressureUnit.Pascal);\n }\n static fromHectopascals(value: number | Decimal): Pressure {\n return Pressure.create(value, PressureUnit.Hectopascal);\n }\n static fromKilopascals(value: number | Decimal): Pressure {\n return Pressure.create(value, PressureUnit.Kilopascal);\n }\n static fromMegapascals(value: number | Decimal): Pressure {\n return Pressure.create(value, PressureUnit.Megapascal);\n }\n static fromGigapascals(value: number | Decimal): Pressure {\n return Pressure.create(value, PressureUnit.Gigapascal);\n }\n static fromBars(value: number | Decimal): Pressure {\n return Pressure.create(value, PressureUnit.Bar);\n }\n static fromMillibars(value: number | Decimal): Pressure {\n return Pressure.create(value, PressureUnit.Millibar);\n }\n static fromPsi(value: number | Decimal): Pressure {\n return Pressure.create(value, PressureUnit.Psi);\n }\n static fromAtmospheres(value: number | Decimal): Pressure {\n return Pressure.create(value, PressureUnit.Atmosphere);\n }\n static fromMillimetersOfMercury(value: number | Decimal): Pressure {\n return Pressure.create(value, PressureUnit.MillimeterOfMercury);\n }\n\n static tryParse(input: string | null | undefined): Pressure | null {\n const parsed = tryParseQuantity(input, PressureUnit.index);\n return parsed === null ? null : new Pressure(parsed.baseValue, parsed.unit);\n }\n\n static parse(input: string): Pressure {\n const result = Pressure.tryParse(input);\n if (result === null) throw new Error(\"Invalid pressure.\");\n return result;\n }\n\n static isValid(input: string | null | undefined): boolean {\n return Pressure.tryParse(input) !== null;\n }\n\n /** Display form in the parsed unit, or `null` when the input is invalid. */\n static format(input: string | null | undefined, options?: PressureFormatOptions): string | null {\n const parsed = Pressure.tryParse(input);\n if (parsed !== null) {\n if (options?.unit !== undefined || options?.decimals !== undefined) {\n return parsed.toStringInUnit(options.unit ?? parsed.originalUnit, options.decimals);\n }\n return parsed.toString();\n }\n return Pressure.fallback(input, options);\n }\n\n /** The value in pascals, or `null` when the input is invalid. */\n static normalize(\n input: string | null | undefined,\n options?: PressureFormatOptions,\n ): string | null {\n const parsed = Pressure.tryParse(input);\n if (parsed !== null) return parsed.toNormalizedString();\n return Pressure.fallback(input, options);\n }\n\n static isNormalized(input: string | null | undefined): boolean {\n return input != null && Pressure.normalize(input) === input;\n }\n\n static findCandidatesInText(text: string): readonly TextCandidate<Pressure>[] {\n if (!text) return [];\n\n const out: TextCandidate<Pressure>[] = [];\n const pattern = new RegExp(SCAN_PATTERN.source, SCAN_PATTERN.flags);\n let match: RegExpExecArray | null;\n\n while ((match = pattern.exec(text)) !== null) {\n const value = Pressure.tryParse(match[0]);\n if (value === null) continue;\n out.push(\n new TextCandidate<Pressure>({\n startIndex: match.index,\n length: match[0].length,\n originalText: match[0],\n typeName: \"Pressure\",\n category: TextCandidateCategory.Measurement,\n normalizedForm: value.toNormalizedString(),\n formattedForm: value.toString(),\n maskedForm: maskPressure(value),\n confidence: TextMatchConfidence.Medium,\n value,\n }),\n );\n }\n\n return out;\n }\n\n private static fallback(\n input: string | null | undefined,\n options?: PressureFormatOptions,\n ): string | null {\n if (options?.fallbackToTrimmedInputWhenInvalid !== true) return null;\n if (input == null) return null;\n const trimmed = input.trim();\n return trimmed.length > 0 ? trimmed : null;\n }\n\n private static decimalOf(value: number): Decimal {\n const parsed = Decimal.fromNumber(value);\n if (parsed === null) throw new Error(`Value cannot be represented as a pressure: ${value}`);\n return parsed;\n }\n}\n\n/** Returns a masked pressure, e.g. `10 Pa` becomes `*** Pa`. */\nexport function maskPressure(value: Pressure): string {\n return `*** ${value.originalUnit.symbol}`;\n}\n"],
4
+ "sourcesContent": ["import { Decimal } from \"../internal/decimal.js\";\nimport type { MeasurementUnitIndex } from \"../internal/measurement-unit.js\";\nimport { Quantity, tryParseQuantity } from \"../internal/quantity.js\";\nimport type { PrimitiveTypeInfo } from \"../primitive-type-info.js\";\nimport { TextCandidate } from \"../text-scanning/text-candidate.js\";\nimport { TextCandidateCategory } from \"../text-scanning/text-candidate-category.js\";\nimport { TextMatchConfidence } from \"../text-scanning/text-match-confidence.js\";\nimport { PressureUnit } from \"./pressure-unit.js\";\n\n/** Options for {@link Pressure.format} and {@link Pressure.normalize}. */\nexport interface PressureFormatOptions {\n fallbackToTrimmedInputWhenInvalid?: boolean;\n /** Convert to this unit instead of preserving the parsed one. */\n unit?: PressureUnit;\n /** Round to this many fractional digits, away from zero. */\n decimals?: number;\n}\n\nconst SCAN_PATTERN = new RegExp(\n String.raw`(?<![\\p{L}\\p{N}_])(?:[+-]\\s*)?\\d+[0-9 .,]*\\s*(?:GPa|MPa|kPa|hPa|mbar|mmHg|mm\\s+Hg|lb/in\u00B2|atm|torr|bar|Pa|PSI)(?![\\p{L}\\p{N}_])`,\n \"giu\",\n);\n\n/**\n * A pressure stored internally in pascals,\n * parsed from any supported unit and convertible between all of them.\n */\nexport class Pressure extends Quantity<PressureUnit> {\n static readonly typeInfo: PrimitiveTypeInfo = Object.freeze({\n englishName: \"Pressure\",\n localizedName: \"Tryck\",\n emoji: \"\uD83C\uDFAF\",\n sources: Object.freeze([\n \"https://www.bipm.org/en/measurement-units/si-derived-units\",\n \"https://en.wikipedia.org/wiki/Conversion_of_units\",\n ]),\n });\n\n protected get units(): MeasurementUnitIndex<PressureUnit> {\n return PressureUnit.index;\n }\n\n protected withBaseValue(baseValue: Decimal, unit: PressureUnit): this {\n return new Pressure(baseValue, unit) as this;\n }\n\n get pascals(): number {\n return this.in(PressureUnit.Pascal);\n }\n get hectopascals(): number {\n return this.in(PressureUnit.Hectopascal);\n }\n get kilopascals(): number {\n return this.in(PressureUnit.Kilopascal);\n }\n get megapascals(): number {\n return this.in(PressureUnit.Megapascal);\n }\n get gigapascals(): number {\n return this.in(PressureUnit.Gigapascal);\n }\n get bars(): number {\n return this.in(PressureUnit.Bar);\n }\n get millibars(): number {\n return this.in(PressureUnit.Millibar);\n }\n get psi(): number {\n return this.in(PressureUnit.Psi);\n }\n get atmospheres(): number {\n return this.in(PressureUnit.Atmosphere);\n }\n get millimetersOfMercury(): number {\n return this.in(PressureUnit.MillimeterOfMercury);\n }\n\n /** Creates a pressure from a value and unit. */\n static create(value: number | Decimal, unit: PressureUnit): Pressure {\n PressureUnit.index.assertOwns(unit);\n const amount = value instanceof Decimal ? value : Pressure.decimalOf(value);\n return new Pressure(unit.toBase(amount), unit);\n }\n\n static fromPascals(value: number | Decimal): Pressure {\n return Pressure.create(value, PressureUnit.Pascal);\n }\n static fromHectopascals(value: number | Decimal): Pressure {\n return Pressure.create(value, PressureUnit.Hectopascal);\n }\n static fromKilopascals(value: number | Decimal): Pressure {\n return Pressure.create(value, PressureUnit.Kilopascal);\n }\n static fromMegapascals(value: number | Decimal): Pressure {\n return Pressure.create(value, PressureUnit.Megapascal);\n }\n static fromGigapascals(value: number | Decimal): Pressure {\n return Pressure.create(value, PressureUnit.Gigapascal);\n }\n static fromBars(value: number | Decimal): Pressure {\n return Pressure.create(value, PressureUnit.Bar);\n }\n static fromMillibars(value: number | Decimal): Pressure {\n return Pressure.create(value, PressureUnit.Millibar);\n }\n static fromPsi(value: number | Decimal): Pressure {\n return Pressure.create(value, PressureUnit.Psi);\n }\n static fromAtmospheres(value: number | Decimal): Pressure {\n return Pressure.create(value, PressureUnit.Atmosphere);\n }\n static fromMillimetersOfMercury(value: number | Decimal): Pressure {\n return Pressure.create(value, PressureUnit.MillimeterOfMercury);\n }\n\n static tryParse(input: string | null | undefined): Pressure | null {\n const parsed = tryParseQuantity(input, PressureUnit.index);\n return parsed === null ? null : new Pressure(parsed.baseValue, parsed.unit);\n }\n\n static parse(input: string): Pressure {\n const result = Pressure.tryParse(input);\n if (result === null) throw new Error(\"Invalid pressure.\");\n return result;\n }\n\n static isValid(input: string | null | undefined): boolean {\n return Pressure.tryParse(input) !== null;\n }\n\n /** Display form in the parsed unit, or `null` when the input is invalid. */\n static format(input: string | null | undefined, options?: PressureFormatOptions): string | null {\n const parsed = Pressure.tryParse(input);\n if (parsed !== null) {\n if (options?.unit !== undefined || options?.decimals !== undefined) {\n return parsed.toStringInUnit(options.unit ?? parsed.originalUnit, options.decimals);\n }\n return parsed.toString();\n }\n return Pressure.fallback(input, options);\n }\n\n /** The value in pascals, or `null` when the input is invalid. */\n static normalize(\n input: string | null | undefined,\n options?: PressureFormatOptions,\n ): string | null {\n const parsed = Pressure.tryParse(input);\n if (parsed !== null) return parsed.toNormalizedString();\n return Pressure.fallback(input, options);\n }\n\n static isNormalized(input: string | null | undefined): boolean {\n return input != null && Pressure.normalize(input) === input;\n }\n\n static findCandidatesInText(text: string): readonly TextCandidate<Pressure>[] {\n if (!text) return [];\n\n const out: TextCandidate<Pressure>[] = [];\n const pattern = new RegExp(SCAN_PATTERN.source, SCAN_PATTERN.flags);\n let match: RegExpExecArray | null;\n\n while ((match = pattern.exec(text)) !== null) {\n const value = Pressure.tryParse(match[0]);\n if (value === null) continue;\n out.push(\n new TextCandidate<Pressure>({\n startIndex: match.index,\n length: match[0].length,\n originalText: match[0],\n typeName: \"Pressure\",\n category: TextCandidateCategory.Measurement,\n normalizedForm: value.toNormalizedString(),\n formattedForm: value.toString(),\n maskedForm: maskPressure(value),\n confidence: TextMatchConfidence.Medium,\n value,\n }),\n );\n }\n\n return out;\n }\n\n private static fallback(\n input: string | null | undefined,\n options?: PressureFormatOptions,\n ): string | null {\n if (options?.fallbackToTrimmedInputWhenInvalid !== true) return null;\n if (input == null) return null;\n const trimmed = input.trim();\n return trimmed.length > 0 ? trimmed : null;\n }\n\n private static decimalOf(value: number): Decimal {\n const parsed = Decimal.fromNumber(value);\n if (parsed === null) throw new Error(`Value cannot be represented as a pressure: ${value}`);\n return parsed;\n }\n}\n\n/** Returns a masked pressure, e.g. `10 Pa` becomes `*** Pa`. */\nexport function maskPressure(value: Pressure): string {\n return `*** ${value.originalUnit.symbol}`;\n}\n"],
5
5
  "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -30,7 +30,7 @@ var import_text_candidate_category = require("../text-scanning/text-candidate-ca
30
30
  var import_text_match_confidence = require("../text-scanning/text-match-confidence.js");
31
31
  var import_rotational_speed_unit = require("./rotational-speed-unit.js");
32
32
  const SCAN_PATTERN = new RegExp(
33
- String.raw`(?<!\w)(?:[+-]\s*)?\d+[0-9 .,]*\s*(?:rpm|r/min|rev/min|varv/min|rps|r/s|rev/s|varv/s|rad/s(?:ec)?)\b`,
33
+ String.raw`(?<![\p{L}\p{N}_])(?:[+-]\s*)?\d+[0-9 .,]*\s*(?:rpm|r/min|rev/min|varv/min|rps|r/s|rev/s|varv/s|rad/s(?:ec)?)(?![\p{L}\p{N}_])`,
34
34
  "giu"
35
35
  );
36
36
  class RotationalSpeed extends import_quantity.Quantity {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/measurement/rotational-speed.ts"],
4
- "sourcesContent": ["import { Decimal } from \"../internal/decimal.js\";\nimport type { MeasurementUnitIndex } from \"../internal/measurement-unit.js\";\nimport { tryParseNumberOnly } from \"../internal/measurement-unit-parser.js\";\nimport { Quantity, tryParseQuantity } from \"../internal/quantity.js\";\nimport type { PrimitiveTypeInfo } from \"../primitive-type-info.js\";\nimport { TextCandidate } from \"../text-scanning/text-candidate.js\";\nimport { TextCandidateCategory } from \"../text-scanning/text-candidate-category.js\";\nimport { TextMatchConfidence } from \"../text-scanning/text-match-confidence.js\";\nimport { RotationalSpeedUnit } from \"./rotational-speed-unit.js\";\n\n/** Options for {@link RotationalSpeed.format} and {@link RotationalSpeed.normalize}. */\nexport interface RotationalSpeedFormatOptions {\n fallbackToTrimmedInputWhenInvalid?: boolean;\n /** Convert to this unit instead of preserving the parsed one. */\n unit?: RotationalSpeedUnit;\n /** Round to this many fractional digits, away from zero. */\n decimals?: number;\n}\n\nconst SCAN_PATTERN = new RegExp(\n String.raw`(?<!\\w)(?:[+-]\\s*)?\\d+[0-9 .,]*\\s*(?:rpm|r/min|rev/min|varv/min|rps|r/s|rev/s|varv/s|rad/s(?:ec)?)\\b`,\n \"giu\",\n);\n\n/**\n * A rotational speed stored internally in revolutions per minute,\n * parsed from any supported unit and convertible between all of them.\n */\nexport class RotationalSpeed extends Quantity<RotationalSpeedUnit> {\n static readonly typeInfo: PrimitiveTypeInfo = Object.freeze({\n englishName: \"Rotational Speed\",\n localizedName: \"Varvtal\",\n emoji: \"\uD83D\uDD04\",\n sources: Object.freeze([\n \"https://en.wikipedia.org/wiki/Revolutions_per_minute\",\n \"https://www.bipm.org/en/measurement-units/si-derived-units\",\n ]),\n });\n\n protected get units(): MeasurementUnitIndex<RotationalSpeedUnit> {\n return RotationalSpeedUnit.index;\n }\n\n protected withBaseValue(baseValue: Decimal, unit: RotationalSpeedUnit): this {\n return new RotationalSpeed(baseValue, unit) as this;\n }\n\n get rpm(): number {\n return this.in(RotationalSpeedUnit.Rpm);\n }\n get rps(): number {\n return this.in(RotationalSpeedUnit.Rps);\n }\n get radiansPerSecond(): number {\n return this.in(RotationalSpeedUnit.RadiansPerSecond);\n }\n\n /** Creates a rotational speed from a value and unit. */\n static create(value: number | Decimal, unit: RotationalSpeedUnit): RotationalSpeed {\n RotationalSpeedUnit.index.assertOwns(unit);\n const amount = value instanceof Decimal ? value : RotationalSpeed.decimalOf(value);\n return new RotationalSpeed(unit.toBase(amount), unit);\n }\n\n static fromRpm(value: number | Decimal): RotationalSpeed {\n return RotationalSpeed.create(value, RotationalSpeedUnit.Rpm);\n }\n static fromRps(value: number | Decimal): RotationalSpeed {\n return RotationalSpeed.create(value, RotationalSpeedUnit.Rps);\n }\n static fromRadiansPerSecond(value: number | Decimal): RotationalSpeed {\n return RotationalSpeed.create(value, RotationalSpeedUnit.RadiansPerSecond);\n }\n\n /** A bare number is read as rpm, the unit rotational speed is usually quoted in. */\n static tryParse(input: string | null | undefined): RotationalSpeed | null {\n const bare = tryParseNumberOnly(input);\n if (bare !== null) return new RotationalSpeed(bare, RotationalSpeedUnit.Rpm);\n\n const parsed = tryParseQuantity(input, RotationalSpeedUnit.index);\n return parsed === null ? null : new RotationalSpeed(parsed.baseValue, parsed.unit);\n }\n\n static parse(input: string): RotationalSpeed {\n const result = RotationalSpeed.tryParse(input);\n if (result === null) throw new Error(\"Invalid rotational speed.\");\n return result;\n }\n\n static isValid(input: string | null | undefined): boolean {\n return RotationalSpeed.tryParse(input) !== null;\n }\n\n /** Display form in the parsed unit, or `null` when the input is invalid. */\n static format(\n input: string | null | undefined,\n options?: RotationalSpeedFormatOptions,\n ): string | null {\n const parsed = RotationalSpeed.tryParse(input);\n if (parsed !== null) {\n if (options?.unit !== undefined || options?.decimals !== undefined) {\n return parsed.toStringInUnit(options.unit ?? parsed.originalUnit, options.decimals);\n }\n return parsed.toString();\n }\n return RotationalSpeed.fallback(input, options);\n }\n\n /** The value in revolutions per minute, or `null` when the input is invalid. */\n static normalize(\n input: string | null | undefined,\n options?: RotationalSpeedFormatOptions,\n ): string | null {\n const parsed = RotationalSpeed.tryParse(input);\n if (parsed !== null) return parsed.toNormalizedString();\n return RotationalSpeed.fallback(input, options);\n }\n\n static isNormalized(input: string | null | undefined): boolean {\n return input != null && RotationalSpeed.normalize(input) === input;\n }\n\n static findCandidatesInText(text: string): readonly TextCandidate<RotationalSpeed>[] {\n if (!text) return [];\n\n const out: TextCandidate<RotationalSpeed>[] = [];\n const pattern = new RegExp(SCAN_PATTERN.source, SCAN_PATTERN.flags);\n let match: RegExpExecArray | null;\n\n while ((match = pattern.exec(text)) !== null) {\n const value = RotationalSpeed.tryParse(match[0]);\n if (value === null) continue;\n out.push(\n new TextCandidate<RotationalSpeed>({\n startIndex: match.index,\n length: match[0].length,\n originalText: match[0],\n typeName: \"RotationalSpeed\",\n category: TextCandidateCategory.Measurement,\n normalizedForm: value.toNormalizedString(),\n formattedForm: value.toString(),\n maskedForm: maskRotationalSpeed(value),\n confidence: TextMatchConfidence.Low,\n value,\n }),\n );\n }\n\n return out;\n }\n\n private static fallback(\n input: string | null | undefined,\n options?: RotationalSpeedFormatOptions,\n ): string | null {\n if (options?.fallbackToTrimmedInputWhenInvalid !== true) return null;\n if (input == null) return null;\n const trimmed = input.trim();\n return trimmed.length > 0 ? trimmed : null;\n }\n\n private static decimalOf(value: number): Decimal {\n const parsed = Decimal.fromNumber(value);\n if (parsed === null)\n throw new Error(`Value cannot be represented as a rotational speed: ${value}`);\n return parsed;\n }\n}\n\n/** Returns a masked rotational speed, e.g. `10 rpm` becomes `*** rpm`. */\nexport function maskRotationalSpeed(value: RotationalSpeed): string {\n return `*** ${value.originalUnit.symbol}`;\n}\n"],
4
+ "sourcesContent": ["import { Decimal } from \"../internal/decimal.js\";\nimport type { MeasurementUnitIndex } from \"../internal/measurement-unit.js\";\nimport { tryParseNumberOnly } from \"../internal/measurement-unit-parser.js\";\nimport { Quantity, tryParseQuantity } from \"../internal/quantity.js\";\nimport type { PrimitiveTypeInfo } from \"../primitive-type-info.js\";\nimport { TextCandidate } from \"../text-scanning/text-candidate.js\";\nimport { TextCandidateCategory } from \"../text-scanning/text-candidate-category.js\";\nimport { TextMatchConfidence } from \"../text-scanning/text-match-confidence.js\";\nimport { RotationalSpeedUnit } from \"./rotational-speed-unit.js\";\n\n/** Options for {@link RotationalSpeed.format} and {@link RotationalSpeed.normalize}. */\nexport interface RotationalSpeedFormatOptions {\n fallbackToTrimmedInputWhenInvalid?: boolean;\n /** Convert to this unit instead of preserving the parsed one. */\n unit?: RotationalSpeedUnit;\n /** Round to this many fractional digits, away from zero. */\n decimals?: number;\n}\n\nconst SCAN_PATTERN = new RegExp(\n String.raw`(?<![\\p{L}\\p{N}_])(?:[+-]\\s*)?\\d+[0-9 .,]*\\s*(?:rpm|r/min|rev/min|varv/min|rps|r/s|rev/s|varv/s|rad/s(?:ec)?)(?![\\p{L}\\p{N}_])`,\n \"giu\",\n);\n\n/**\n * A rotational speed stored internally in revolutions per minute,\n * parsed from any supported unit and convertible between all of them.\n */\nexport class RotationalSpeed extends Quantity<RotationalSpeedUnit> {\n static readonly typeInfo: PrimitiveTypeInfo = Object.freeze({\n englishName: \"Rotational Speed\",\n localizedName: \"Varvtal\",\n emoji: \"\uD83D\uDD04\",\n sources: Object.freeze([\n \"https://en.wikipedia.org/wiki/Revolutions_per_minute\",\n \"https://www.bipm.org/en/measurement-units/si-derived-units\",\n ]),\n });\n\n protected get units(): MeasurementUnitIndex<RotationalSpeedUnit> {\n return RotationalSpeedUnit.index;\n }\n\n protected withBaseValue(baseValue: Decimal, unit: RotationalSpeedUnit): this {\n return new RotationalSpeed(baseValue, unit) as this;\n }\n\n get rpm(): number {\n return this.in(RotationalSpeedUnit.Rpm);\n }\n get rps(): number {\n return this.in(RotationalSpeedUnit.Rps);\n }\n get radiansPerSecond(): number {\n return this.in(RotationalSpeedUnit.RadiansPerSecond);\n }\n\n /** Creates a rotational speed from a value and unit. */\n static create(value: number | Decimal, unit: RotationalSpeedUnit): RotationalSpeed {\n RotationalSpeedUnit.index.assertOwns(unit);\n const amount = value instanceof Decimal ? value : RotationalSpeed.decimalOf(value);\n return new RotationalSpeed(unit.toBase(amount), unit);\n }\n\n static fromRpm(value: number | Decimal): RotationalSpeed {\n return RotationalSpeed.create(value, RotationalSpeedUnit.Rpm);\n }\n static fromRps(value: number | Decimal): RotationalSpeed {\n return RotationalSpeed.create(value, RotationalSpeedUnit.Rps);\n }\n static fromRadiansPerSecond(value: number | Decimal): RotationalSpeed {\n return RotationalSpeed.create(value, RotationalSpeedUnit.RadiansPerSecond);\n }\n\n /** A bare number is read as rpm, the unit rotational speed is usually quoted in. */\n static tryParse(input: string | null | undefined): RotationalSpeed | null {\n const bare = tryParseNumberOnly(input);\n if (bare !== null) return new RotationalSpeed(bare, RotationalSpeedUnit.Rpm);\n\n const parsed = tryParseQuantity(input, RotationalSpeedUnit.index);\n return parsed === null ? null : new RotationalSpeed(parsed.baseValue, parsed.unit);\n }\n\n static parse(input: string): RotationalSpeed {\n const result = RotationalSpeed.tryParse(input);\n if (result === null) throw new Error(\"Invalid rotational speed.\");\n return result;\n }\n\n static isValid(input: string | null | undefined): boolean {\n return RotationalSpeed.tryParse(input) !== null;\n }\n\n /** Display form in the parsed unit, or `null` when the input is invalid. */\n static format(\n input: string | null | undefined,\n options?: RotationalSpeedFormatOptions,\n ): string | null {\n const parsed = RotationalSpeed.tryParse(input);\n if (parsed !== null) {\n if (options?.unit !== undefined || options?.decimals !== undefined) {\n return parsed.toStringInUnit(options.unit ?? parsed.originalUnit, options.decimals);\n }\n return parsed.toString();\n }\n return RotationalSpeed.fallback(input, options);\n }\n\n /** The value in revolutions per minute, or `null` when the input is invalid. */\n static normalize(\n input: string | null | undefined,\n options?: RotationalSpeedFormatOptions,\n ): string | null {\n const parsed = RotationalSpeed.tryParse(input);\n if (parsed !== null) return parsed.toNormalizedString();\n return RotationalSpeed.fallback(input, options);\n }\n\n static isNormalized(input: string | null | undefined): boolean {\n return input != null && RotationalSpeed.normalize(input) === input;\n }\n\n static findCandidatesInText(text: string): readonly TextCandidate<RotationalSpeed>[] {\n if (!text) return [];\n\n const out: TextCandidate<RotationalSpeed>[] = [];\n const pattern = new RegExp(SCAN_PATTERN.source, SCAN_PATTERN.flags);\n let match: RegExpExecArray | null;\n\n while ((match = pattern.exec(text)) !== null) {\n const value = RotationalSpeed.tryParse(match[0]);\n if (value === null) continue;\n out.push(\n new TextCandidate<RotationalSpeed>({\n startIndex: match.index,\n length: match[0].length,\n originalText: match[0],\n typeName: \"RotationalSpeed\",\n category: TextCandidateCategory.Measurement,\n normalizedForm: value.toNormalizedString(),\n formattedForm: value.toString(),\n maskedForm: maskRotationalSpeed(value),\n confidence: TextMatchConfidence.Low,\n value,\n }),\n );\n }\n\n return out;\n }\n\n private static fallback(\n input: string | null | undefined,\n options?: RotationalSpeedFormatOptions,\n ): string | null {\n if (options?.fallbackToTrimmedInputWhenInvalid !== true) return null;\n if (input == null) return null;\n const trimmed = input.trim();\n return trimmed.length > 0 ? trimmed : null;\n }\n\n private static decimalOf(value: number): Decimal {\n const parsed = Decimal.fromNumber(value);\n if (parsed === null)\n throw new Error(`Value cannot be represented as a rotational speed: ${value}`);\n return parsed;\n }\n}\n\n/** Returns a masked rotational speed, e.g. `10 rpm` becomes `*** rpm`. */\nexport function maskRotationalSpeed(value: RotationalSpeed): string {\n return `*** ${value.originalUnit.symbol}`;\n}\n"],
5
5
  "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -26,6 +26,7 @@ module.exports = __toCommonJS(sound_level_exports);
26
26
  var import_decimal = require("../internal/decimal.js");
27
27
  var import_input_sanitization = require("../internal/input-sanitization.js");
28
28
  var import_measurement_unit_parser = require("../internal/measurement-unit-parser.js");
29
+ var import_scan_boundaries = require("../internal/scan-boundaries.js");
29
30
  var import_text_candidate = require("../text-scanning/text-candidate.js");
30
31
  var import_text_candidate_category = require("../text-scanning/text-candidate-category.js");
31
32
  var import_text_match_confidence = require("../text-scanning/text-match-confidence.js");
@@ -42,7 +43,10 @@ const SoundWeighting = {
42
43
  Z: 4
43
44
  };
44
45
  const UNIT_PATTERN = /^\s*(?<sign>[+-])?\s*(?<num>[0-9][0-9 .,]*[0-9]|[0-9])\s*(?<unit>dB\s*(?:\(\s*(?<w1>[ABCZabcz])\s*\)|(?<w2>[ABCZabcz]))?)?\s*$/iu;
45
- const SCAN_PATTERN = /(?<!\w)(?:[+-]\s*)?\d+[0-9 .,]*\s*dB(?:\s*\(\s*[ABCZabcz]\s*\)|[ABCZabcz])?\b/giu;
46
+ const SCAN_PATTERN = new RegExp(
47
+ String.raw`(?<![\p{L}\p{N}_])(?:[+-]\s*)?\d+[0-9 .,]*\s*dB(?:\s*\(\s*[ABCZabcz]\s*\)|[ABCZabcz])?${import_scan_boundaries.WORD_BOUNDARY}`,
48
+ "giu"
49
+ );
46
50
  const SUFFIXES = {
47
51
  [SoundWeighting.Unweighted]: " dB",
48
52
  [SoundWeighting.A]: " dB(A)",
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/measurement/sound-level.ts"],
4
- "sourcesContent": ["import { Decimal } from \"../internal/decimal.js\";\nimport { sanitizeInput } from \"../internal/input-sanitization.js\";\nimport { tryParseNumberOnly } from \"../internal/measurement-unit-parser.js\";\nimport type { PrimitiveTypeInfo } from \"../primitive-type-info.js\";\nimport { TextCandidate } from \"../text-scanning/text-candidate.js\";\nimport { TextCandidateCategory } from \"../text-scanning/text-candidate-category.js\";\nimport { TextMatchConfidence } from \"../text-scanning/text-match-confidence.js\";\n\n/**\n * The frequency weighting curve applied to a sound measurement.\n */\nexport const SoundWeighting = {\n /** No weighting: a flat response. */\n Unweighted: 0,\n /** A-weighting, which approximates human hearing sensitivity. */\n A: 1,\n /** B-weighting, an intermediate curve that is rarely used. */\n B: 2,\n /** C-weighting, flat across most audible frequencies. */\n C: 3,\n /** Z-weighting, zero weighting, flat from 10 Hz to 20 kHz. */\n Z: 4,\n} as const;\n\nexport type SoundWeighting = (typeof SoundWeighting)[keyof typeof SoundWeighting];\n\n/** Options for {@link SoundLevel.format} and {@link SoundLevel.normalize}. */\nexport interface SoundLevelFormatOptions {\n fallbackToTrimmedInputWhenInvalid?: boolean;\n}\n\nconst UNIT_PATTERN =\n /^\\s*(?<sign>[+-])?\\s*(?<num>[0-9][0-9 .,]*[0-9]|[0-9])\\s*(?<unit>dB\\s*(?:\\(\\s*(?<w1>[ABCZabcz])\\s*\\)|(?<w2>[ABCZabcz]))?)?\\s*$/iu;\n\nconst SCAN_PATTERN =\n /(?<!\\w)(?:[+-]\\s*)?\\d+[0-9 .,]*\\s*dB(?:\\s*\\(\\s*[ABCZabcz]\\s*\\)|[ABCZabcz])?\\b/giu;\n\nconst SUFFIXES: Record<SoundWeighting, string> = {\n [SoundWeighting.Unweighted]: \" dB\",\n [SoundWeighting.A]: \" dB(A)\",\n [SoundWeighting.B]: \" dB(B)\",\n [SoundWeighting.C]: \" dB(C)\",\n [SoundWeighting.Z]: \" dB(Z)\",\n};\n\nconst WEIGHTINGS: Record<string, SoundWeighting> = {\n A: SoundWeighting.A,\n B: SoundWeighting.B,\n C: SoundWeighting.C,\n Z: SoundWeighting.Z,\n};\n\n/**\n * A sound pressure level in decibels, optionally carrying a frequency\n * weighting \u2014 `85 dB`, `69 dB(A)`, `40 dBZ`.\n *\n * The decibel unit is missing from many inputs in practice, so a bare number is\n * accepted and read as unweighted decibels.\n */\nexport class SoundLevel {\n static readonly typeInfo: PrimitiveTypeInfo = Object.freeze({\n englishName: \"Sound Level\",\n localizedName: \"Ljudniv\u00E5\",\n emoji: \"\uD83D\uDD0A\",\n sources: Object.freeze([\n \"https://en.wikipedia.org/wiki/A-weighting\",\n \"https://www.iso.org/standard/17426.html\",\n ]),\n });\n\n /** The display form, e.g. `69 dB(A)`. */\n readonly value: string;\n\n private constructor(\n private readonly decibelsValue: Decimal,\n /** The frequency weighting applied to this measurement. */\n readonly weighting: SoundWeighting,\n ) {\n this.value = `${decibelsValue.toTrimmedString()}${SUFFIXES[weighting]}`;\n }\n\n /** The numeric decibel value, e.g. `69`. */\n get decibels(): number {\n return this.decibelsValue.toNumber();\n }\n\n /** Creates a sound level from a value and weighting. */\n static create(value: number | Decimal, weighting: SoundWeighting): SoundLevel {\n return new SoundLevel(toDecimal(value), weighting);\n }\n\n /** Creates an unweighted sound level, e.g. `85 dB`. */\n static fromDecibels(value: number | Decimal): SoundLevel {\n return new SoundLevel(toDecimal(value), SoundWeighting.Unweighted);\n }\n\n /** Creates an A-weighted sound level, e.g. `69 dB(A)`. */\n static fromDecibelA(value: number | Decimal): SoundLevel {\n return new SoundLevel(toDecimal(value), SoundWeighting.A);\n }\n\n static tryParse(input: string | null | undefined): SoundLevel | null {\n if (input == null) return null;\n\n const trimmed = sanitizeInput(input).trim();\n if (trimmed.length === 0) return null;\n\n const match = UNIT_PATTERN.exec(trimmed);\n const number = match?.groups?.[\"num\"];\n if (number === undefined) return null;\n\n const parsed = tryParseNumberOnly(number);\n if (parsed === null) return null;\n\n const decibels = match?.groups?.[\"sign\"] === \"-\" ? parsed.negated() : parsed;\n const letter = match?.groups?.[\"w1\"] ?? match?.groups?.[\"w2\"];\n const weighting =\n letter === undefined\n ? SoundWeighting.Unweighted\n : (WEIGHTINGS[letter.toUpperCase()] ?? SoundWeighting.Unweighted);\n\n return new SoundLevel(decibels, weighting);\n }\n\n static parse(input: string): SoundLevel {\n const result = SoundLevel.tryParse(input);\n if (result === null) throw new Error(\"Invalid sound level.\");\n return result;\n }\n\n static isValid(input: string | null | undefined): boolean {\n return SoundLevel.tryParse(input) !== null;\n }\n\n /** Display form, e.g. `69 dB(A)`. */\n static format(\n input: string | null | undefined,\n options?: SoundLevelFormatOptions,\n ): string | null {\n const parsed = SoundLevel.tryParse(input);\n if (parsed !== null) return parsed.toString();\n return fallback(input, options);\n }\n\n /** Canonical form, e.g. `69 dB(A)`. */\n static normalize(\n input: string | null | undefined,\n options?: SoundLevelFormatOptions,\n ): string | null {\n const parsed = SoundLevel.tryParse(input);\n if (parsed !== null) return parsed.toNormalizedString();\n return fallback(input, options);\n }\n\n static isNormalized(input: string | null | undefined): boolean {\n return input != null && SoundLevel.normalize(input) === input;\n }\n\n toNormalizedString(): string {\n return this.value;\n }\n\n toString(): string {\n return this.value;\n }\n\n compareTo(other: SoundLevel | null | undefined): number {\n if (other == null) return 1;\n return this.decibelsValue.compareTo(other.decibelsValue);\n }\n\n equals(other: SoundLevel | null | undefined): boolean {\n return (\n other != null &&\n this.decibelsValue.equals(other.decibelsValue) &&\n this.weighting === other.weighting\n );\n }\n\n static findCandidatesInText(text: string): readonly TextCandidate<SoundLevel>[] {\n if (!text) return [];\n\n const out: TextCandidate<SoundLevel>[] = [];\n const pattern = new RegExp(SCAN_PATTERN.source, SCAN_PATTERN.flags);\n let match: RegExpExecArray | null;\n\n while ((match = pattern.exec(text)) !== null) {\n const value = SoundLevel.tryParse(match[0]);\n if (value === null) continue;\n out.push(\n new TextCandidate<SoundLevel>({\n startIndex: match.index,\n length: match[0].length,\n originalText: match[0],\n typeName: \"SoundLevel\",\n category: TextCandidateCategory.Measurement,\n normalizedForm: value.toNormalizedString(),\n formattedForm: value.toString(),\n maskedForm: maskSoundLevel(value),\n confidence: TextMatchConfidence.Low,\n value,\n }),\n );\n }\n\n return out;\n }\n}\n\n/** Returns a masked sound level, e.g. `69 dB(A)` becomes `*** dB(A)`. */\nexport function maskSoundLevel(soundLevel: SoundLevel): string {\n return `***${SUFFIXES[soundLevel.weighting]}`;\n}\n\nfunction toDecimal(value: number | Decimal): Decimal {\n if (value instanceof Decimal) return value;\n const parsed = Decimal.fromNumber(value);\n if (parsed === null) throw new Error(`Value cannot be represented as a sound level: ${value}`);\n return parsed;\n}\n\nfunction fallback(\n input: string | null | undefined,\n options?: SoundLevelFormatOptions,\n): string | null {\n if (options?.fallbackToTrimmedInputWhenInvalid !== true) return null;\n if (input == null) return null;\n const trimmed = input.trim();\n return trimmed.length > 0 ? trimmed : null;\n}\n"],
5
- "mappings": ";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,qBAAwB;AACxB,gCAA8B;AAC9B,qCAAmC;AAEnC,4BAA8B;AAC9B,qCAAsC;AACtC,mCAAoC;AAK7B,MAAM,iBAAiB;AAAA;AAAA,EAE5B,YAAY;AAAA;AAAA,EAEZ,GAAG;AAAA;AAAA,EAEH,GAAG;AAAA;AAAA,EAEH,GAAG;AAAA;AAAA,EAEH,GAAG;AACL;AASA,MAAM,eACJ;AAEF,MAAM,eACJ;AAEF,MAAM,WAA2C;AAAA,EAC/C,CAAC,eAAe,UAAU,GAAG;AAAA,EAC7B,CAAC,eAAe,CAAC,GAAG;AAAA,EACpB,CAAC,eAAe,CAAC,GAAG;AAAA,EACpB,CAAC,eAAe,CAAC,GAAG;AAAA,EACpB,CAAC,eAAe,CAAC,GAAG;AACtB;AAEA,MAAM,aAA6C;AAAA,EACjD,GAAG,eAAe;AAAA,EAClB,GAAG,eAAe;AAAA,EAClB,GAAG,eAAe;AAAA,EAClB,GAAG,eAAe;AACpB;AASO,MAAM,WAAW;AAAA,EAcd,YACW,eAER,WACT;AAHiB;AAER;AAET,SAAK,QAAQ,GAAG,cAAc,gBAAgB,CAAC,GAAG,SAAS,SAAS,CAAC;AAAA,EACvE;AAAA,EALmB;AAAA,EAER;AAAA,EAhBX,OAAgB,WAA8B,OAAO,OAAO;AAAA,IAC1D,aAAa;AAAA,IACb,eAAe;AAAA,IACf,OAAO;AAAA,IACP,SAAS,OAAO,OAAO;AAAA,MACrB;AAAA,MACA;AAAA,IACF,CAAC;AAAA,EACH,CAAC;AAAA;AAAA,EAGQ;AAAA;AAAA,EAWT,IAAI,WAAmB;AACrB,WAAO,KAAK,cAAc,SAAS;AAAA,EACrC;AAAA;AAAA,EAGA,OAAO,OAAO,OAAyB,WAAuC;AAC5E,WAAO,IAAI,WAAW,UAAU,KAAK,GAAG,SAAS;AAAA,EACnD;AAAA;AAAA,EAGA,OAAO,aAAa,OAAqC;AACvD,WAAO,IAAI,WAAW,UAAU,KAAK,GAAG,eAAe,UAAU;AAAA,EACnE;AAAA;AAAA,EAGA,OAAO,aAAa,OAAqC;AACvD,WAAO,IAAI,WAAW,UAAU,KAAK,GAAG,eAAe,CAAC;AAAA,EAC1D;AAAA,EAEA,OAAO,SAAS,OAAqD;AACnE,QAAI,SAAS,KAAM,QAAO;AAE1B,UAAM,cAAU,yCAAc,KAAK,EAAE,KAAK;AAC1C,QAAI,QAAQ,WAAW,EAAG,QAAO;AAEjC,UAAM,QAAQ,aAAa,KAAK,OAAO;AACvC,UAAM,SAAS,OAAO,SAAS,KAAK;AACpC,QAAI,WAAW,OAAW,QAAO;AAEjC,UAAM,aAAS,mDAAmB,MAAM;AACxC,QAAI,WAAW,KAAM,QAAO;AAE5B,UAAM,WAAW,OAAO,SAAS,MAAM,MAAM,MAAM,OAAO,QAAQ,IAAI;AACtE,UAAM,SAAS,OAAO,SAAS,IAAI,KAAK,OAAO,SAAS,IAAI;AAC5D,UAAM,YACJ,WAAW,SACP,eAAe,aACd,WAAW,OAAO,YAAY,CAAC,KAAK,eAAe;AAE1D,WAAO,IAAI,WAAW,UAAU,SAAS;AAAA,EAC3C;AAAA,EAEA,OAAO,MAAM,OAA2B;AACtC,UAAM,SAAS,WAAW,SAAS,KAAK;AACxC,QAAI,WAAW,KAAM,OAAM,IAAI,MAAM,sBAAsB;AAC3D,WAAO;AAAA,EACT;AAAA,EAEA,OAAO,QAAQ,OAA2C;AACxD,WAAO,WAAW,SAAS,KAAK,MAAM;AAAA,EACxC;AAAA;AAAA,EAGA,OAAO,OACL,OACA,SACe;AACf,UAAM,SAAS,WAAW,SAAS,KAAK;AACxC,QAAI,WAAW,KAAM,QAAO,OAAO,SAAS;AAC5C,WAAO,SAAS,OAAO,OAAO;AAAA,EAChC;AAAA;AAAA,EAGA,OAAO,UACL,OACA,SACe;AACf,UAAM,SAAS,WAAW,SAAS,KAAK;AACxC,QAAI,WAAW,KAAM,QAAO,OAAO,mBAAmB;AACtD,WAAO,SAAS,OAAO,OAAO;AAAA,EAChC;AAAA,EAEA,OAAO,aAAa,OAA2C;AAC7D,WAAO,SAAS,QAAQ,WAAW,UAAU,KAAK,MAAM;AAAA,EAC1D;AAAA,EAEA,qBAA6B;AAC3B,WAAO,KAAK;AAAA,EACd;AAAA,EAEA,WAAmB;AACjB,WAAO,KAAK;AAAA,EACd;AAAA,EAEA,UAAU,OAA8C;AACtD,QAAI,SAAS,KAAM,QAAO;AAC1B,WAAO,KAAK,cAAc,UAAU,MAAM,aAAa;AAAA,EACzD;AAAA,EAEA,OAAO,OAA+C;AACpD,WACE,SAAS,QACT,KAAK,cAAc,OAAO,MAAM,aAAa,KAC7C,KAAK,cAAc,MAAM;AAAA,EAE7B;AAAA,EAEA,OAAO,qBAAqB,MAAoD;AAC9E,QAAI,CAAC,KAAM,QAAO,CAAC;AAEnB,UAAM,MAAmC,CAAC;AAC1C,UAAM,UAAU,IAAI,OAAO,aAAa,QAAQ,aAAa,KAAK;AAClE,QAAI;AAEJ,YAAQ,QAAQ,QAAQ,KAAK,IAAI,OAAO,MAAM;AAC5C,YAAM,QAAQ,WAAW,SAAS,MAAM,CAAC,CAAC;AAC1C,UAAI,UAAU,KAAM;AACpB,UAAI;AAAA,QACF,IAAI,oCAA0B;AAAA,UAC5B,YAAY,MAAM;AAAA,UAClB,QAAQ,MAAM,CAAC,EAAE;AAAA,UACjB,cAAc,MAAM,CAAC;AAAA,UACrB,UAAU;AAAA,UACV,UAAU,qDAAsB;AAAA,UAChC,gBAAgB,MAAM,mBAAmB;AAAA,UACzC,eAAe,MAAM,SAAS;AAAA,UAC9B,YAAY,eAAe,KAAK;AAAA,UAChC,YAAY,iDAAoB;AAAA,UAChC;AAAA,QACF,CAAC;AAAA,MACH;AAAA,IACF;AAEA,WAAO;AAAA,EACT;AACF;AAGO,SAAS,eAAe,YAAgC;AAC7D,SAAO,MAAM,SAAS,WAAW,SAAS,CAAC;AAC7C;AAEA,SAAS,UAAU,OAAkC;AACnD,MAAI,iBAAiB,uBAAS,QAAO;AACrC,QAAM,SAAS,uBAAQ,WAAW,KAAK;AACvC,MAAI,WAAW,KAAM,OAAM,IAAI,MAAM,iDAAiD,KAAK,EAAE;AAC7F,SAAO;AACT;AAEA,SAAS,SACP,OACA,SACe;AACf,MAAI,SAAS,sCAAsC,KAAM,QAAO;AAChE,MAAI,SAAS,KAAM,QAAO;AAC1B,QAAM,UAAU,MAAM,KAAK;AAC3B,SAAO,QAAQ,SAAS,IAAI,UAAU;AACxC;",
4
+ "sourcesContent": ["import { Decimal } from \"../internal/decimal.js\";\nimport { sanitizeInput } from \"../internal/input-sanitization.js\";\nimport { tryParseNumberOnly } from \"../internal/measurement-unit-parser.js\";\nimport { WORD_BOUNDARY } from \"../internal/scan-boundaries.js\";\nimport type { PrimitiveTypeInfo } from \"../primitive-type-info.js\";\nimport { TextCandidate } from \"../text-scanning/text-candidate.js\";\nimport { TextCandidateCategory } from \"../text-scanning/text-candidate-category.js\";\nimport { TextMatchConfidence } from \"../text-scanning/text-match-confidence.js\";\n\n/**\n * The frequency weighting curve applied to a sound measurement.\n */\nexport const SoundWeighting = {\n /** No weighting: a flat response. */\n Unweighted: 0,\n /** A-weighting, which approximates human hearing sensitivity. */\n A: 1,\n /** B-weighting, an intermediate curve that is rarely used. */\n B: 2,\n /** C-weighting, flat across most audible frequencies. */\n C: 3,\n /** Z-weighting, zero weighting, flat from 10 Hz to 20 kHz. */\n Z: 4,\n} as const;\n\nexport type SoundWeighting = (typeof SoundWeighting)[keyof typeof SoundWeighting];\n\n/** Options for {@link SoundLevel.format} and {@link SoundLevel.normalize}. */\nexport interface SoundLevelFormatOptions {\n fallbackToTrimmedInputWhenInvalid?: boolean;\n}\n\nconst UNIT_PATTERN =\n /^\\s*(?<sign>[+-])?\\s*(?<num>[0-9][0-9 .,]*[0-9]|[0-9])\\s*(?<unit>dB\\s*(?:\\(\\s*(?<w1>[ABCZabcz])\\s*\\)|(?<w2>[ABCZabcz]))?)?\\s*$/iu;\n\nconst SCAN_PATTERN = new RegExp(\n String.raw`(?<![\\p{L}\\p{N}_])(?:[+-]\\s*)?\\d+[0-9 .,]*\\s*dB(?:\\s*\\(\\s*[ABCZabcz]\\s*\\)|[ABCZabcz])?${WORD_BOUNDARY}`,\n \"giu\",\n);\n\nconst SUFFIXES: Record<SoundWeighting, string> = {\n [SoundWeighting.Unweighted]: \" dB\",\n [SoundWeighting.A]: \" dB(A)\",\n [SoundWeighting.B]: \" dB(B)\",\n [SoundWeighting.C]: \" dB(C)\",\n [SoundWeighting.Z]: \" dB(Z)\",\n};\n\nconst WEIGHTINGS: Record<string, SoundWeighting> = {\n A: SoundWeighting.A,\n B: SoundWeighting.B,\n C: SoundWeighting.C,\n Z: SoundWeighting.Z,\n};\n\n/**\n * A sound pressure level in decibels, optionally carrying a frequency\n * weighting \u2014 `85 dB`, `69 dB(A)`, `40 dBZ`.\n *\n * The decibel unit is missing from many inputs in practice, so a bare number is\n * accepted and read as unweighted decibels.\n */\nexport class SoundLevel {\n static readonly typeInfo: PrimitiveTypeInfo = Object.freeze({\n englishName: \"Sound Level\",\n localizedName: \"Ljudniv\u00E5\",\n emoji: \"\uD83D\uDD0A\",\n sources: Object.freeze([\n \"https://en.wikipedia.org/wiki/A-weighting\",\n \"https://www.iso.org/standard/17426.html\",\n ]),\n });\n\n /** The display form, e.g. `69 dB(A)`. */\n readonly value: string;\n\n private constructor(\n private readonly decibelsValue: Decimal,\n /** The frequency weighting applied to this measurement. */\n readonly weighting: SoundWeighting,\n ) {\n this.value = `${decibelsValue.toTrimmedString()}${SUFFIXES[weighting]}`;\n }\n\n /** The numeric decibel value, e.g. `69`. */\n get decibels(): number {\n return this.decibelsValue.toNumber();\n }\n\n /** Creates a sound level from a value and weighting. */\n static create(value: number | Decimal, weighting: SoundWeighting): SoundLevel {\n return new SoundLevel(toDecimal(value), weighting);\n }\n\n /** Creates an unweighted sound level, e.g. `85 dB`. */\n static fromDecibels(value: number | Decimal): SoundLevel {\n return new SoundLevel(toDecimal(value), SoundWeighting.Unweighted);\n }\n\n /** Creates an A-weighted sound level, e.g. `69 dB(A)`. */\n static fromDecibelA(value: number | Decimal): SoundLevel {\n return new SoundLevel(toDecimal(value), SoundWeighting.A);\n }\n\n static tryParse(input: string | null | undefined): SoundLevel | null {\n if (input == null) return null;\n\n const trimmed = sanitizeInput(input).trim();\n if (trimmed.length === 0) return null;\n\n const match = UNIT_PATTERN.exec(trimmed);\n const number = match?.groups?.[\"num\"];\n if (number === undefined) return null;\n\n const parsed = tryParseNumberOnly(number);\n if (parsed === null) return null;\n\n const decibels = match?.groups?.[\"sign\"] === \"-\" ? parsed.negated() : parsed;\n const letter = match?.groups?.[\"w1\"] ?? match?.groups?.[\"w2\"];\n const weighting =\n letter === undefined\n ? SoundWeighting.Unweighted\n : (WEIGHTINGS[letter.toUpperCase()] ?? SoundWeighting.Unweighted);\n\n return new SoundLevel(decibels, weighting);\n }\n\n static parse(input: string): SoundLevel {\n const result = SoundLevel.tryParse(input);\n if (result === null) throw new Error(\"Invalid sound level.\");\n return result;\n }\n\n static isValid(input: string | null | undefined): boolean {\n return SoundLevel.tryParse(input) !== null;\n }\n\n /** Display form, e.g. `69 dB(A)`. */\n static format(\n input: string | null | undefined,\n options?: SoundLevelFormatOptions,\n ): string | null {\n const parsed = SoundLevel.tryParse(input);\n if (parsed !== null) return parsed.toString();\n return fallback(input, options);\n }\n\n /** Canonical form, e.g. `69 dB(A)`. */\n static normalize(\n input: string | null | undefined,\n options?: SoundLevelFormatOptions,\n ): string | null {\n const parsed = SoundLevel.tryParse(input);\n if (parsed !== null) return parsed.toNormalizedString();\n return fallback(input, options);\n }\n\n static isNormalized(input: string | null | undefined): boolean {\n return input != null && SoundLevel.normalize(input) === input;\n }\n\n toNormalizedString(): string {\n return this.value;\n }\n\n toString(): string {\n return this.value;\n }\n\n compareTo(other: SoundLevel | null | undefined): number {\n if (other == null) return 1;\n return this.decibelsValue.compareTo(other.decibelsValue);\n }\n\n equals(other: SoundLevel | null | undefined): boolean {\n return (\n other != null &&\n this.decibelsValue.equals(other.decibelsValue) &&\n this.weighting === other.weighting\n );\n }\n\n static findCandidatesInText(text: string): readonly TextCandidate<SoundLevel>[] {\n if (!text) return [];\n\n const out: TextCandidate<SoundLevel>[] = [];\n const pattern = new RegExp(SCAN_PATTERN.source, SCAN_PATTERN.flags);\n let match: RegExpExecArray | null;\n\n while ((match = pattern.exec(text)) !== null) {\n const value = SoundLevel.tryParse(match[0]);\n if (value === null) continue;\n out.push(\n new TextCandidate<SoundLevel>({\n startIndex: match.index,\n length: match[0].length,\n originalText: match[0],\n typeName: \"SoundLevel\",\n category: TextCandidateCategory.Measurement,\n normalizedForm: value.toNormalizedString(),\n formattedForm: value.toString(),\n maskedForm: maskSoundLevel(value),\n confidence: TextMatchConfidence.Low,\n value,\n }),\n );\n }\n\n return out;\n }\n}\n\n/** Returns a masked sound level, e.g. `69 dB(A)` becomes `*** dB(A)`. */\nexport function maskSoundLevel(soundLevel: SoundLevel): string {\n return `***${SUFFIXES[soundLevel.weighting]}`;\n}\n\nfunction toDecimal(value: number | Decimal): Decimal {\n if (value instanceof Decimal) return value;\n const parsed = Decimal.fromNumber(value);\n if (parsed === null) throw new Error(`Value cannot be represented as a sound level: ${value}`);\n return parsed;\n}\n\nfunction fallback(\n input: string | null | undefined,\n options?: SoundLevelFormatOptions,\n): string | null {\n if (options?.fallbackToTrimmedInputWhenInvalid !== true) return null;\n if (input == null) return null;\n const trimmed = input.trim();\n return trimmed.length > 0 ? trimmed : null;\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -29,7 +29,7 @@ var import_text_candidate_category = require("../text-scanning/text-candidate-ca
29
29
  var import_text_match_confidence = require("../text-scanning/text-match-confidence.js");
30
30
  var import_speed_unit = require("./speed-unit.js");
31
31
  const SCAN_PATTERN = new RegExp(
32
- String.raw`(?<!\w)(?:[+-]\s*)?\d+[0-9 .,]*\s*(?:miles\s+per\s+hour|km/h|ft/s|m/s|kph|kmh|km/t|mph|knots?|knop|kt|kn)\b`,
32
+ String.raw`(?<![\p{L}\p{N}_])(?:[+-]\s*)?\d+[0-9 .,]*\s*(?:miles\s+per\s+hour|km/h|ft/s|m/s|kph|kmh|km/t|mph|knots?|knop|kt|kn)(?![\p{L}\p{N}_])`,
33
33
  "giu"
34
34
  );
35
35
  class Speed extends import_quantity.Quantity {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/measurement/speed.ts"],
4
- "sourcesContent": ["import { Decimal } from \"../internal/decimal.js\";\nimport type { MeasurementUnitIndex } from \"../internal/measurement-unit.js\";\nimport { Quantity, tryParseQuantity } from \"../internal/quantity.js\";\nimport type { PrimitiveTypeInfo } from \"../primitive-type-info.js\";\nimport { TextCandidate } from \"../text-scanning/text-candidate.js\";\nimport { TextCandidateCategory } from \"../text-scanning/text-candidate-category.js\";\nimport { TextMatchConfidence } from \"../text-scanning/text-match-confidence.js\";\nimport { SpeedUnit } from \"./speed-unit.js\";\n\n/** Options for {@link Speed.format} and {@link Speed.normalize}. */\nexport interface SpeedFormatOptions {\n fallbackToTrimmedInputWhenInvalid?: boolean;\n /** Convert to this unit instead of preserving the parsed one. */\n unit?: SpeedUnit;\n /** Round to this many fractional digits, away from zero. */\n decimals?: number;\n}\n\nconst SCAN_PATTERN = new RegExp(\n String.raw`(?<!\\w)(?:[+-]\\s*)?\\d+[0-9 .,]*\\s*(?:miles\\s+per\\s+hour|km/h|ft/s|m/s|kph|kmh|km/t|mph|knots?|knop|kt|kn)\\b`,\n \"giu\",\n);\n\n/**\n * A speed stored internally in meters per second,\n * parsed from any supported unit and convertible between all of them.\n */\nexport class Speed extends Quantity<SpeedUnit> {\n static readonly typeInfo: PrimitiveTypeInfo = Object.freeze({\n englishName: \"Speed\",\n localizedName: \"Hastighet\",\n emoji: \"\uD83C\uDFCE\uFE0F\",\n sources: Object.freeze([\n \"https://www.bipm.org/en/measurement-units/si-base-units\",\n \"https://en.wikipedia.org/wiki/Conversion_of_units\",\n ]),\n });\n\n protected get units(): MeasurementUnitIndex<SpeedUnit> {\n return SpeedUnit.index;\n }\n\n protected withBaseValue(baseValue: Decimal, unit: SpeedUnit): this {\n return new Speed(baseValue, unit) as this;\n }\n\n get metersPerSecond(): number {\n return this.in(SpeedUnit.MetersPerSecond);\n }\n get kilometersPerHour(): number {\n return this.in(SpeedUnit.KilometersPerHour);\n }\n get milesPerHour(): number {\n return this.in(SpeedUnit.MilesPerHour);\n }\n get feetPerSecond(): number {\n return this.in(SpeedUnit.FeetPerSecond);\n }\n get knots(): number {\n return this.in(SpeedUnit.Knot);\n }\n\n /** Creates a speed from a value and unit. */\n static create(value: number | Decimal, unit: SpeedUnit): Speed {\n SpeedUnit.index.assertOwns(unit);\n const amount = value instanceof Decimal ? value : Speed.decimalOf(value);\n return new Speed(unit.toBase(amount), unit);\n }\n\n static fromMetersPerSecond(value: number | Decimal): Speed {\n return Speed.create(value, SpeedUnit.MetersPerSecond);\n }\n static fromKilometersPerHour(value: number | Decimal): Speed {\n return Speed.create(value, SpeedUnit.KilometersPerHour);\n }\n static fromMilesPerHour(value: number | Decimal): Speed {\n return Speed.create(value, SpeedUnit.MilesPerHour);\n }\n static fromFeetPerSecond(value: number | Decimal): Speed {\n return Speed.create(value, SpeedUnit.FeetPerSecond);\n }\n static fromKnots(value: number | Decimal): Speed {\n return Speed.create(value, SpeedUnit.Knot);\n }\n\n static tryParse(input: string | null | undefined): Speed | null {\n const parsed = tryParseQuantity(input, SpeedUnit.index);\n return parsed === null ? null : new Speed(parsed.baseValue, parsed.unit);\n }\n\n static parse(input: string): Speed {\n const result = Speed.tryParse(input);\n if (result === null) throw new Error(\"Invalid speed.\");\n return result;\n }\n\n static isValid(input: string | null | undefined): boolean {\n return Speed.tryParse(input) !== null;\n }\n\n /** Display form in the parsed unit, or `null` when the input is invalid. */\n static format(input: string | null | undefined, options?: SpeedFormatOptions): string | null {\n const parsed = Speed.tryParse(input);\n if (parsed !== null) {\n if (options?.unit !== undefined || options?.decimals !== undefined) {\n return parsed.toStringInUnit(options.unit ?? parsed.originalUnit, options.decimals);\n }\n return parsed.toString();\n }\n return Speed.fallback(input, options);\n }\n\n /** The value in meters per second, or `null` when the input is invalid. */\n static normalize(input: string | null | undefined, options?: SpeedFormatOptions): string | null {\n const parsed = Speed.tryParse(input);\n if (parsed !== null) return parsed.toNormalizedString();\n return Speed.fallback(input, options);\n }\n\n static isNormalized(input: string | null | undefined): boolean {\n return input != null && Speed.normalize(input) === input;\n }\n\n static findCandidatesInText(text: string): readonly TextCandidate<Speed>[] {\n if (!text) return [];\n\n const out: TextCandidate<Speed>[] = [];\n const pattern = new RegExp(SCAN_PATTERN.source, SCAN_PATTERN.flags);\n let match: RegExpExecArray | null;\n\n while ((match = pattern.exec(text)) !== null) {\n const value = Speed.tryParse(match[0]);\n if (value === null) continue;\n out.push(\n new TextCandidate<Speed>({\n startIndex: match.index,\n length: match[0].length,\n originalText: match[0],\n typeName: \"Speed\",\n category: TextCandidateCategory.Measurement,\n normalizedForm: value.toNormalizedString(),\n formattedForm: value.toString(),\n maskedForm: maskSpeed(value),\n confidence: TextMatchConfidence.Medium,\n value,\n }),\n );\n }\n\n return out;\n }\n\n private static fallback(\n input: string | null | undefined,\n options?: SpeedFormatOptions,\n ): string | null {\n if (options?.fallbackToTrimmedInputWhenInvalid !== true) return null;\n if (input == null) return null;\n const trimmed = input.trim();\n return trimmed.length > 0 ? trimmed : null;\n }\n\n private static decimalOf(value: number): Decimal {\n const parsed = Decimal.fromNumber(value);\n if (parsed === null) throw new Error(`Value cannot be represented as a speed: ${value}`);\n return parsed;\n }\n}\n\n/** Returns a masked speed, e.g. `10 m/s` becomes `*** m/s`. */\nexport function maskSpeed(value: Speed): string {\n return `*** ${value.originalUnit.symbol}`;\n}\n"],
4
+ "sourcesContent": ["import { Decimal } from \"../internal/decimal.js\";\nimport type { MeasurementUnitIndex } from \"../internal/measurement-unit.js\";\nimport { Quantity, tryParseQuantity } from \"../internal/quantity.js\";\nimport type { PrimitiveTypeInfo } from \"../primitive-type-info.js\";\nimport { TextCandidate } from \"../text-scanning/text-candidate.js\";\nimport { TextCandidateCategory } from \"../text-scanning/text-candidate-category.js\";\nimport { TextMatchConfidence } from \"../text-scanning/text-match-confidence.js\";\nimport { SpeedUnit } from \"./speed-unit.js\";\n\n/** Options for {@link Speed.format} and {@link Speed.normalize}. */\nexport interface SpeedFormatOptions {\n fallbackToTrimmedInputWhenInvalid?: boolean;\n /** Convert to this unit instead of preserving the parsed one. */\n unit?: SpeedUnit;\n /** Round to this many fractional digits, away from zero. */\n decimals?: number;\n}\n\nconst SCAN_PATTERN = new RegExp(\n String.raw`(?<![\\p{L}\\p{N}_])(?:[+-]\\s*)?\\d+[0-9 .,]*\\s*(?:miles\\s+per\\s+hour|km/h|ft/s|m/s|kph|kmh|km/t|mph|knots?|knop|kt|kn)(?![\\p{L}\\p{N}_])`,\n \"giu\",\n);\n\n/**\n * A speed stored internally in meters per second,\n * parsed from any supported unit and convertible between all of them.\n */\nexport class Speed extends Quantity<SpeedUnit> {\n static readonly typeInfo: PrimitiveTypeInfo = Object.freeze({\n englishName: \"Speed\",\n localizedName: \"Hastighet\",\n emoji: \"\uD83C\uDFCE\uFE0F\",\n sources: Object.freeze([\n \"https://www.bipm.org/en/measurement-units/si-base-units\",\n \"https://en.wikipedia.org/wiki/Conversion_of_units\",\n ]),\n });\n\n protected get units(): MeasurementUnitIndex<SpeedUnit> {\n return SpeedUnit.index;\n }\n\n protected withBaseValue(baseValue: Decimal, unit: SpeedUnit): this {\n return new Speed(baseValue, unit) as this;\n }\n\n get metersPerSecond(): number {\n return this.in(SpeedUnit.MetersPerSecond);\n }\n get kilometersPerHour(): number {\n return this.in(SpeedUnit.KilometersPerHour);\n }\n get milesPerHour(): number {\n return this.in(SpeedUnit.MilesPerHour);\n }\n get feetPerSecond(): number {\n return this.in(SpeedUnit.FeetPerSecond);\n }\n get knots(): number {\n return this.in(SpeedUnit.Knot);\n }\n\n /** Creates a speed from a value and unit. */\n static create(value: number | Decimal, unit: SpeedUnit): Speed {\n SpeedUnit.index.assertOwns(unit);\n const amount = value instanceof Decimal ? value : Speed.decimalOf(value);\n return new Speed(unit.toBase(amount), unit);\n }\n\n static fromMetersPerSecond(value: number | Decimal): Speed {\n return Speed.create(value, SpeedUnit.MetersPerSecond);\n }\n static fromKilometersPerHour(value: number | Decimal): Speed {\n return Speed.create(value, SpeedUnit.KilometersPerHour);\n }\n static fromMilesPerHour(value: number | Decimal): Speed {\n return Speed.create(value, SpeedUnit.MilesPerHour);\n }\n static fromFeetPerSecond(value: number | Decimal): Speed {\n return Speed.create(value, SpeedUnit.FeetPerSecond);\n }\n static fromKnots(value: number | Decimal): Speed {\n return Speed.create(value, SpeedUnit.Knot);\n }\n\n static tryParse(input: string | null | undefined): Speed | null {\n const parsed = tryParseQuantity(input, SpeedUnit.index);\n return parsed === null ? null : new Speed(parsed.baseValue, parsed.unit);\n }\n\n static parse(input: string): Speed {\n const result = Speed.tryParse(input);\n if (result === null) throw new Error(\"Invalid speed.\");\n return result;\n }\n\n static isValid(input: string | null | undefined): boolean {\n return Speed.tryParse(input) !== null;\n }\n\n /** Display form in the parsed unit, or `null` when the input is invalid. */\n static format(input: string | null | undefined, options?: SpeedFormatOptions): string | null {\n const parsed = Speed.tryParse(input);\n if (parsed !== null) {\n if (options?.unit !== undefined || options?.decimals !== undefined) {\n return parsed.toStringInUnit(options.unit ?? parsed.originalUnit, options.decimals);\n }\n return parsed.toString();\n }\n return Speed.fallback(input, options);\n }\n\n /** The value in meters per second, or `null` when the input is invalid. */\n static normalize(input: string | null | undefined, options?: SpeedFormatOptions): string | null {\n const parsed = Speed.tryParse(input);\n if (parsed !== null) return parsed.toNormalizedString();\n return Speed.fallback(input, options);\n }\n\n static isNormalized(input: string | null | undefined): boolean {\n return input != null && Speed.normalize(input) === input;\n }\n\n static findCandidatesInText(text: string): readonly TextCandidate<Speed>[] {\n if (!text) return [];\n\n const out: TextCandidate<Speed>[] = [];\n const pattern = new RegExp(SCAN_PATTERN.source, SCAN_PATTERN.flags);\n let match: RegExpExecArray | null;\n\n while ((match = pattern.exec(text)) !== null) {\n const value = Speed.tryParse(match[0]);\n if (value === null) continue;\n out.push(\n new TextCandidate<Speed>({\n startIndex: match.index,\n length: match[0].length,\n originalText: match[0],\n typeName: \"Speed\",\n category: TextCandidateCategory.Measurement,\n normalizedForm: value.toNormalizedString(),\n formattedForm: value.toString(),\n maskedForm: maskSpeed(value),\n confidence: TextMatchConfidence.Medium,\n value,\n }),\n );\n }\n\n return out;\n }\n\n private static fallback(\n input: string | null | undefined,\n options?: SpeedFormatOptions,\n ): string | null {\n if (options?.fallbackToTrimmedInputWhenInvalid !== true) return null;\n if (input == null) return null;\n const trimmed = input.trim();\n return trimmed.length > 0 ? trimmed : null;\n }\n\n private static decimalOf(value: number): Decimal {\n const parsed = Decimal.fromNumber(value);\n if (parsed === null) throw new Error(`Value cannot be represented as a speed: ${value}`);\n return parsed;\n }\n}\n\n/** Returns a masked speed, e.g. `10 m/s` becomes `*** m/s`. */\nexport function maskSpeed(value: Speed): string {\n return `*** ${value.originalUnit.symbol}`;\n}\n"],
5
5
  "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -34,7 +34,7 @@ const CELSIUS_OFFSET = import_decimal.Decimal.parse("273.15");
34
34
  const NINE = import_decimal.Decimal.parse("9");
35
35
  const FIVE = import_decimal.Decimal.parse("5");
36
36
  const THIRTY_TWO = import_decimal.Decimal.parse("32");
37
- const SCAN_PATTERN = /(?<!\w)(?:[+-]\s*)?\d+[0-9 .,]*\s*(?:°[CF]|[CFK])\b/giu;
37
+ const SCAN_PATTERN = /(?<![\p{L}\p{N}_])(?:[+-]\s*)?\d+[0-9 .,]*\s*(?:°[CF]|[CFK])(?![\p{L}\p{N}_])/giu;
38
38
  class Temperature {
39
39
  constructor(kelvinValue, originalUnit) {
40
40
  this.kelvinValue = kelvinValue;