dsh-unitverse 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/client.js ADDED
@@ -0,0 +1,896 @@
1
+ window.__ModuleLoader__.load({
2
+ id: "dsh-unitverse",
3
+ factory: (require) => {
4
+ var module = { exports: {} };
5
+ var exports = module.exports;
6
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
7
+
8
+ "use strict";
9
+ var __defProp = Object.defineProperty;
10
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
11
+ var __getOwnPropNames = Object.getOwnPropertyNames;
12
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
13
+ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
14
+ var __export = (target, all) => {
15
+ for (var name in all)
16
+ __defProp(target, name, { get: all[name], enumerable: true });
17
+ };
18
+ var __copyProps = (to, from, except, desc) => {
19
+ if (from && typeof from === "object" || typeof from === "function") {
20
+ for (let key of __getOwnPropNames(from))
21
+ if (!__hasOwnProp.call(to, key) && key !== except)
22
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
23
+ }
24
+ return to;
25
+ };
26
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
27
+ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
28
+
29
+ // src/client/index.tsx
30
+ var index_exports = {};
31
+ __export(index_exports, {
32
+ apply: () => apply,
33
+ inject: () => inject
34
+ });
35
+ module.exports = __toCommonJS(index_exports);
36
+
37
+ // src/client/UnitConvertView.tsx
38
+ var import_react = require("react");
39
+
40
+ // src/units.ts
41
+ var CATEGORY_ORDER = [
42
+ "length",
43
+ "area",
44
+ "volume",
45
+ "time",
46
+ "angle",
47
+ "speed",
48
+ "temperature",
49
+ "pressure",
50
+ "energy",
51
+ "power"
52
+ ];
53
+ var CATEGORIES = {
54
+ length: { id: "length", labelZh: "\u957F\u5EA6", labelEn: "Length", baseSymbol: "m" },
55
+ area: { id: "area", labelZh: "\u9762\u79EF", labelEn: "Area", baseSymbol: "m\xB2" },
56
+ volume: { id: "volume", labelZh: "\u4F53\u79EF", labelEn: "Volume", baseSymbol: "m\xB3" },
57
+ time: { id: "time", labelZh: "\u65F6\u95F4", labelEn: "Time", baseSymbol: "s" },
58
+ angle: { id: "angle", labelZh: "\u89D2\u5EA6", labelEn: "Angle", baseSymbol: "rad" },
59
+ speed: { id: "speed", labelZh: "\u901F\u5EA6", labelEn: "Speed", baseSymbol: "m/s" },
60
+ temperature: { id: "temperature", labelZh: "\u6E29\u5EA6", labelEn: "Temperature", baseSymbol: "K" },
61
+ pressure: { id: "pressure", labelZh: "\u538B\u529B", labelEn: "Pressure", baseSymbol: "Pa" },
62
+ energy: { id: "energy", labelZh: "\u70ED\u91CF/\u80FD\u91CF", labelEn: "Energy/Heat", baseSymbol: "J" },
63
+ power: { id: "power", labelZh: "\u529F\u7387", labelEn: "Power", baseSymbol: "W" }
64
+ };
65
+ var DELTA_MARKERS = ["\u0394", "delta", "diff"];
66
+ var UNITS = [
67
+ // ---------------------------------------------------------------- length
68
+ { id: "nm", category: "length", symbol: "nm", nameZh: "\u7EB3\u7C73", nameEn: "nanometer", aliases: ["nm", "nanometer", "nanometers", "\u7EB3\u7C73"], factor: 1e-9 },
69
+ { id: "um", category: "length", symbol: "\u03BCm", nameZh: "\u5FAE\u7C73", nameEn: "micrometer", aliases: ["um", "\u03BCm", "\xB5m", "micrometer", "micrometers", "micron", "microns", "\u5FAE\u7C73"], factor: 1e-6 },
70
+ { id: "mm", category: "length", symbol: "mm", nameZh: "\u6BEB\u7C73", nameEn: "millimeter", aliases: ["mm", "millimeter", "millimeters", "millimetre", "millimetres", "\u6BEB\u7C73"], factor: 1e-3 },
71
+ { id: "cm", category: "length", symbol: "cm", nameZh: "\u5398\u7C73", nameEn: "centimeter", aliases: ["cm", "centimeter", "centimeters", "centimetre", "centimetres", "\u5398\u7C73", "\u516C\u5206"], factor: 0.01 },
72
+ { id: "m", category: "length", symbol: "m", nameZh: "\u7C73", nameEn: "meter", aliases: ["m", "meter", "meters", "metre", "metres", "\u7C73", "\u516C\u5C3A"], factor: 1 },
73
+ { id: "km", category: "length", symbol: "km", nameZh: "\u5343\u7C73", nameEn: "kilometer", aliases: ["km", "kilometer", "kilometers", "kilometre", "kilometres", "\u5343\u7C73", "\u516C\u91CC"], factor: 1e3 },
74
+ { id: "in", category: "length", symbol: "in", nameZh: "\u82F1\u5BF8", nameEn: "inch", aliases: ["in", "inch", "inches", "\u82F1\u5BF8", "\u540B"], factor: 0.0254 },
75
+ { id: "ft", category: "length", symbol: "ft", nameZh: "\u82F1\u5C3A", nameEn: "foot", aliases: ["ft", "foot", "feet", "\u82F1\u5C3A"], factor: 0.3048 },
76
+ { id: "yd", category: "length", symbol: "yd", nameZh: "\u7801", nameEn: "yard", aliases: ["yd", "yard", "yards", "\u7801"], factor: 0.9144 },
77
+ { id: "mi", category: "length", symbol: "mi", nameZh: "\u82F1\u91CC", nameEn: "mile", aliases: ["mi", "mile", "miles", "\u82F1\u91CC"], factor: 1609.344 },
78
+ { id: "nmi", category: "length", symbol: "nmi", nameZh: "\u6D77\u91CC", nameEn: "nautical mile", aliases: ["nmi", "nmile", "nautical mile", "nautical miles", "\u6D77\u91CC"], factor: 1852 },
79
+ { id: "li", category: "length", symbol: "\u91CC", nameZh: "\u91CC", nameEn: "li (Chinese mile)", aliases: ["li", "\u91CC", "\u5E02\u91CC", "\u534E\u91CC"], factor: 500 },
80
+ { id: "chi", category: "length", symbol: "\u5C3A", nameZh: "\u5C3A", nameEn: "chi (Chinese foot)", aliases: ["chi", "\u5C3A", "\u5E02\u5C3A"], factor: 1 / 3 },
81
+ // ------------------------------------------------------------------ area
82
+ { id: "mm2", category: "area", symbol: "mm\xB2", nameZh: "\u5E73\u65B9\u6BEB\u7C73", nameEn: "square millimeter", aliases: ["mm2", "mm\xB2", "square millimeter", "square millimeters", "\u5E73\u65B9\u6BEB\u7C73"], factor: 1e-6 },
83
+ { id: "cm2", category: "area", symbol: "cm\xB2", nameZh: "\u5E73\u65B9\u5398\u7C73", nameEn: "square centimeter", aliases: ["cm2", "cm\xB2", "square centimeter", "square centimeters", "\u5E73\u65B9\u5398\u7C73"], factor: 1e-4 },
84
+ { id: "m2", category: "area", symbol: "m\xB2", nameZh: "\u5E73\u65B9\u7C73", nameEn: "square meter", aliases: ["m2", "m\xB2", "square meter", "square meters", "square metre", "square metres", "\u5E73\u65B9\u7C73", "\u5E73\u7C73"], factor: 1 },
85
+ { id: "km2", category: "area", symbol: "km\xB2", nameZh: "\u5E73\u65B9\u5343\u7C73", nameEn: "square kilometer", aliases: ["km2", "km\xB2", "square kilometer", "square kilometers", "\u5E73\u65B9\u5343\u7C73", "\u5E73\u65B9\u516C\u91CC"], factor: 1e6 },
86
+ { id: "ha", category: "area", symbol: "ha", nameZh: "\u516C\u9877", nameEn: "hectare", aliases: ["ha", "hectare", "hectares", "\u516C\u9877"], factor: 1e4 },
87
+ { id: "mu", category: "area", symbol: "\u4EA9", nameZh: "\u4EA9", nameEn: "mu (Chinese acre)", aliases: ["mu", "\u4EA9", "\u5E02\u4EA9"], factor: 2e3 / 3 },
88
+ { id: "in2", category: "area", symbol: "in\xB2", nameZh: "\u5E73\u65B9\u82F1\u5BF8", nameEn: "square inch", aliases: ["in2", "in\xB2", "square inch", "square inches", "\u5E73\u65B9\u82F1\u5BF8"], factor: 0.0254 ** 2 },
89
+ { id: "ft2", category: "area", symbol: "ft\xB2", nameZh: "\u5E73\u65B9\u82F1\u5C3A", nameEn: "square foot", aliases: ["ft2", "ft\xB2", "square foot", "square feet", "\u5E73\u65B9\u82F1\u5C3A"], factor: 0.3048 ** 2 },
90
+ { id: "yd2", category: "area", symbol: "yd\xB2", nameZh: "\u5E73\u65B9\u7801", nameEn: "square yard", aliases: ["yd2", "yd\xB2", "square yard", "square yards", "\u5E73\u65B9\u7801"], factor: 0.9144 ** 2 },
91
+ { id: "acre", category: "area", symbol: "acre", nameZh: "\u82F1\u4EA9", nameEn: "acre", aliases: ["acre", "acres", "\u82F1\u4EA9"], factor: 4046.8564224 },
92
+ { id: "mi2", category: "area", symbol: "mi\xB2", nameZh: "\u5E73\u65B9\u82F1\u91CC", nameEn: "square mile", aliases: ["mi2", "mi\xB2", "square mile", "square miles", "\u5E73\u65B9\u82F1\u91CC"], factor: 1609.344 ** 2 },
93
+ // ---------------------------------------------------------------- volume
94
+ { id: "ml", category: "volume", symbol: "mL", nameZh: "\u6BEB\u5347", nameEn: "milliliter", aliases: ["ml", "mL", "milliliter", "milliliters", "millilitre", "millilitres", "\u6BEB\u5347"], factor: 1e-6 },
95
+ { id: "l", category: "volume", symbol: "L", nameZh: "\u5347", nameEn: "liter", aliases: ["l", "L", "liter", "liters", "litre", "litres", "\u5347", "\u516C\u5347"], factor: 1e-3 },
96
+ { id: "m3", category: "volume", symbol: "m\xB3", nameZh: "\u7ACB\u65B9\u7C73", nameEn: "cubic meter", aliases: ["m3", "m\xB3", "cubic meter", "cubic meters", "cubic metre", "cubic metres", "\u7ACB\u65B9\u7C73", "\u65B9"], factor: 1 },
97
+ { id: "cm3", category: "volume", symbol: "cm\xB3", nameZh: "\u7ACB\u65B9\u5398\u7C73", nameEn: "cubic centimeter", aliases: ["cm3", "cm\xB3", "cc", "cubic centimeter", "cubic centimeters", "\u7ACB\u65B9\u5398\u7C73"], factor: 1e-6 },
98
+ { id: "in3", category: "volume", symbol: "in\xB3", nameZh: "\u7ACB\u65B9\u82F1\u5BF8", nameEn: "cubic inch", aliases: ["in3", "in\xB3", "cubic inch", "cubic inches", "\u7ACB\u65B9\u82F1\u5BF8"], factor: 0.0254 ** 3 },
99
+ { id: "ft3", category: "volume", symbol: "ft\xB3", nameZh: "\u7ACB\u65B9\u82F1\u5C3A", nameEn: "cubic foot", aliases: ["ft3", "ft\xB3", "cubic foot", "cubic feet", "\u7ACB\u65B9\u82F1\u5C3A"], factor: 0.3048 ** 3 },
100
+ { id: "yd3", category: "volume", symbol: "yd\xB3", nameZh: "\u7ACB\u65B9\u7801", nameEn: "cubic yard", aliases: ["yd3", "yd\xB3", "cubic yard", "cubic yards", "\u7ACB\u65B9\u7801"], factor: 0.9144 ** 3 },
101
+ { id: "gal_us", category: "volume", symbol: "gal (US)", nameZh: "\u7F8E\u5236\u52A0\u4ED1", nameEn: "US gallon", aliases: ["gal", "gallon", "gallons", "us gal", "us gallon", "us gallons", "\u7F8E\u5236\u52A0\u4ED1", "\u7F8E\u52A0\u4ED1", "\u52A0\u4ED1"], factor: 0.003785411784 },
102
+ { id: "gal_uk", category: "volume", symbol: "gal (UK)", nameZh: "\u82F1\u5236\u52A0\u4ED1", nameEn: "imperial gallon", aliases: ["imp gal", "uk gal", "imperial gallon", "imperial gallons", "british gallon", "\u82F1\u5236\u52A0\u4ED1", "\u82F1\u52A0\u4ED1"], factor: 454609e-8 },
103
+ { id: "qt_us", category: "volume", symbol: "qt (US)", nameZh: "\u7F8E\u5236\u5938\u8131", nameEn: "US quart", aliases: ["qt", "quart", "quarts", "us quart", "\u7F8E\u5236\u5938\u8131", "\u5938\u8131"], factor: 0.003785411784 / 4 },
104
+ { id: "bbl", category: "volume", symbol: "bbl", nameZh: "\u6876\uFF08\u77F3\u6CB9\uFF09", nameEn: "oil barrel", aliases: ["bbl", "barrel", "oil barrel", "petroleum barrel", "\u6876"], factor: 0.158987294928 },
105
+ // ------------------------------------------------------------------ time
106
+ { id: "ms", category: "time", symbol: "ms", nameZh: "\u6BEB\u79D2", nameEn: "millisecond", aliases: ["ms", "millisecond", "milliseconds", "\u6BEB\u79D2"], factor: 1e-3 },
107
+ { id: "s", category: "time", symbol: "s", nameZh: "\u79D2", nameEn: "second", aliases: ["s", "sec", "secs", "second", "seconds", "\u79D2"], factor: 1 },
108
+ { id: "min", category: "time", symbol: "min", nameZh: "\u5206\u949F", nameEn: "minute", aliases: ["min", "mins", "minute", "minutes", "\u5206\u949F"], factor: 60 },
109
+ { id: "h", category: "time", symbol: "h", nameZh: "\u5C0F\u65F6", nameEn: "hour", aliases: ["h", "hr", "hrs", "hour", "hours", "\u5C0F\u65F6", "\u65F6"], factor: 3600 },
110
+ { id: "d", category: "time", symbol: "d", nameZh: "\u5929", nameEn: "day", aliases: ["d", "day", "days", "\u5929", "\u65E5"], factor: 86400 },
111
+ { id: "wk", category: "time", symbol: "wk", nameZh: "\u5468", nameEn: "week", aliases: ["wk", "week", "weeks", "\u5468", "\u661F\u671F"], factor: 7 * 86400 },
112
+ { id: "mo", category: "time", symbol: "mo", nameZh: "\u6708", nameEn: "month (mean Julian)", aliases: ["mo", "month", "months", "\u6708"], factor: 365.25 / 12 * 86400 },
113
+ { id: "yr", category: "time", symbol: "yr", nameZh: "\u5E74", nameEn: "year (Julian)", aliases: ["y", "yr", "year", "years", "\u5E74"], factor: 365.25 * 86400 },
114
+ // ----------------------------------------------------------------- angle
115
+ { id: "rad", category: "angle", symbol: "rad", nameZh: "\u5F27\u5EA6", nameEn: "radian", aliases: ["rad", "radian", "radians", "\u5F27\u5EA6"], factor: 1 },
116
+ { id: "deg", category: "angle", symbol: "\xB0", nameZh: "\u5EA6", nameEn: "degree", aliases: ["deg", "degree", "degrees", "\xB0", "\u5EA6", "\u89D2\u5EA6"], factor: Math.PI / 180 },
117
+ { id: "grad", category: "angle", symbol: "grad", nameZh: "\u767E\u5206\u5EA6", nameEn: "gradian", aliases: ["grad", "gradian", "gradians", "gon", "\u767E\u5206\u5EA6"], factor: Math.PI / 200 },
118
+ { id: "arcmin", category: "angle", symbol: "\u2032", nameZh: "\u89D2\u5206", nameEn: "arcminute", aliases: ["arcmin", "arcminute", "arcminutes", "\u89D2\u5206", "\u2032"], factor: Math.PI / 10800 },
119
+ { id: "arcsec", category: "angle", symbol: "\u2033", nameZh: "\u89D2\u79D2", nameEn: "arcsecond", aliases: ["arcsec", "arcsecond", "arcseconds", "\u89D2\u79D2", "\u2033"], factor: Math.PI / 648e3 },
120
+ { id: "turn", category: "angle", symbol: "turn", nameZh: "\u5708", nameEn: "turn", aliases: ["turn", "turns", "rev", "revolution", "revolutions", "\u5708", "\u8F6C"], factor: 2 * Math.PI },
121
+ // ----------------------------------------------------------------- speed
122
+ { id: "m_s", category: "speed", symbol: "m/s", nameZh: "\u7C73\u6BCF\u79D2", nameEn: "meter per second", aliases: ["m/s", "mps", "meter per second", "meters per second", "metre per second", "metres per second", "\u7C73\u6BCF\u79D2", "\u7C73/\u79D2"], factor: 1 },
123
+ { id: "km_h", category: "speed", symbol: "km/h", nameZh: "\u5343\u7C73\u6BCF\u5C0F\u65F6", nameEn: "kilometer per hour", aliases: ["km/h", "kmh", "kmph", "kph", "kilometer per hour", "kilometers per hour", "kilometre per hour", "\u5343\u7C73\u6BCF\u5C0F\u65F6", "\u516C\u91CC\u6BCF\u5C0F\u65F6", "\u5343\u7C73/\u5C0F\u65F6", "\u516C\u91CC/\u5C0F\u65F6"], factor: 1 / 3.6 },
124
+ { id: "mph", category: "speed", symbol: "mph", nameZh: "\u82F1\u91CC\u6BCF\u5C0F\u65F6", nameEn: "mile per hour", aliases: ["mph", "mi/h", "mile per hour", "miles per hour", "\u82F1\u91CC\u6BCF\u5C0F\u65F6"], factor: 0.44704 },
125
+ { id: "kn", category: "speed", symbol: "kn", nameZh: "\u8282", nameEn: "knot", aliases: ["kn", "kt", "knot", "knots", "\u8282"], factor: 1852 / 3600 },
126
+ { id: "ft_s", category: "speed", symbol: "ft/s", nameZh: "\u82F1\u5C3A\u6BCF\u79D2", nameEn: "foot per second", aliases: ["ft/s", "fps", "foot per second", "feet per second", "\u82F1\u5C3A\u6BCF\u79D2"], factor: 0.3048 },
127
+ // ----------------------------------------------------------- temperature
128
+ { id: "celsius", category: "temperature", symbol: "\xB0C", nameZh: "\u6444\u6C0F\u5EA6", nameEn: "degree Celsius", aliases: ["c", "\xB0c", "\u2103", "celsius", "\u6444\u6C0F\u5EA6"], a: 1, b: 273.15 },
129
+ { id: "fahrenheit", category: "temperature", symbol: "\xB0F", nameZh: "\u534E\u6C0F\u5EA6", nameEn: "degree Fahrenheit", aliases: ["f", "\xB0f", "\u2109", "fahrenheit", "\u534E\u6C0F\u5EA6"], a: 5 / 9, b: 459.67 * (5 / 9) },
130
+ { id: "kelvin", category: "temperature", symbol: "K", nameZh: "\u5F00\u5C14\u6587", nameEn: "kelvin", aliases: ["k", "kelvin", "\u5F00\u5C14\u6587", "\u5F00"], a: 1, b: 0 },
131
+ { id: "rankine", category: "temperature", symbol: "\xB0R", nameZh: "\u5170\u6C0F\u5EA6", nameEn: "degree Rankine", aliases: ["r", "\xB0r", "rankine", "\u5170\u6C0F\u5EA6"], a: 5 / 9, b: 0 },
132
+ // -------------------------------------------------------------- pressure
133
+ { id: "pa", category: "pressure", symbol: "Pa", nameZh: "\u5E15\u65AF\u5361", nameEn: "pascal", aliases: ["pa", "pascal", "pascals", "\u5E15", "\u5E15\u65AF\u5361"], factor: 1 },
134
+ { id: "kpa", category: "pressure", symbol: "kPa", nameZh: "\u5343\u5E15", nameEn: "kilopascal", aliases: ["kpa", "kilopascal", "kilopascals", "\u5343\u5E15"], factor: 1e3 },
135
+ { id: "mpa", category: "pressure", symbol: "MPa", nameZh: "\u5146\u5E15", nameEn: "megapascal", aliases: ["mpa", "megapascal", "megapascals", "\u5146\u5E15"], factor: 1e6 },
136
+ { id: "hpa", category: "pressure", symbol: "hPa", nameZh: "\u767E\u5E15", nameEn: "hectopascal", aliases: ["hpa", "hectopascal", "hectopascals", "\u767E\u5E15"], factor: 100 },
137
+ { id: "bar", category: "pressure", symbol: "bar", nameZh: "\u5DF4", nameEn: "bar", aliases: ["bar", "bars", "\u5DF4"], factor: 1e5 },
138
+ { id: "atm", category: "pressure", symbol: "atm", nameZh: "\u6807\u51C6\u5927\u6C14\u538B", nameEn: "atmosphere", aliases: ["atm", "atmosphere", "atmospheres", "\u6807\u51C6\u5927\u6C14\u538B", "\u5927\u6C14\u538B"], factor: 101325 },
139
+ { id: "psi", category: "pressure", symbol: "psi", nameZh: "\u78C5\u6BCF\u5E73\u65B9\u82F1\u5BF8", nameEn: "pound per square inch", aliases: ["psi", "pound per square inch", "pounds per square inch", "\u78C5\u6BCF\u5E73\u65B9\u82F1\u5BF8"], factor: 6894.757293168361 },
140
+ { id: "torr", category: "pressure", symbol: "Torr", nameZh: "\u6258", nameEn: "torr", aliases: ["torr", "\u6258"], factor: 101325 / 760 },
141
+ { id: "mmhg", category: "pressure", symbol: "mmHg", nameZh: "\u6BEB\u7C73\u6C5E\u67F1", nameEn: "millimeter of mercury", aliases: ["mmhg", "millimeter of mercury", "millimeters of mercury", "\u6BEB\u7C73\u6C5E\u67F1"], factor: 133.322387415 },
142
+ { id: "inhg", category: "pressure", symbol: "inHg", nameZh: "\u82F1\u5BF8\u6C5E\u67F1", nameEn: "inch of mercury", aliases: ["inhg", "inch of mercury", "inches of mercury", "\u82F1\u5BF8\u6C5E\u67F1"], factor: 25.4 * 133.322387415 },
143
+ // ---------------------------------------------------------------- energy
144
+ { id: "j", category: "energy", symbol: "J", nameZh: "\u7126\u8033", nameEn: "joule", aliases: ["j", "joule", "joules", "\u7126\u8033", "\u7126"], factor: 1 },
145
+ { id: "kj", category: "energy", symbol: "kJ", nameZh: "\u5343\u7126", nameEn: "kilojoule", aliases: ["kj", "kilojoule", "kilojoules", "\u5343\u7126"], factor: 1e3 },
146
+ { id: "mj", category: "energy", symbol: "MJ", nameZh: "\u5146\u7126", nameEn: "megajoule", aliases: ["mj", "megajoule", "megajoules", "\u5146\u7126"], factor: 1e6 },
147
+ { id: "cal", category: "energy", symbol: "cal", nameZh: "\u5361\u8DEF\u91CC", nameEn: "calorie (thermochemical)", aliases: ["cal", "calorie", "calories", "\u5361\u8DEF\u91CC", "\u5361"], factor: 4.184 },
148
+ { id: "kcal", category: "energy", symbol: "kcal", nameZh: "\u5343\u5361", nameEn: "kilocalorie", aliases: ["kcal", "kilocalorie", "kilocalories", "\u5927\u5361", "\u5343\u5361"], factor: 4184 },
149
+ { id: "wh", category: "energy", symbol: "Wh", nameZh: "\u74E6\u65F6", nameEn: "watt-hour", aliases: ["wh", "watt hour", "watt hours", "\u74E6\u65F6"], factor: 3600 },
150
+ { id: "kwh", category: "energy", symbol: "kWh", nameZh: "\u5343\u74E6\u65F6", nameEn: "kilowatt-hour", aliases: ["kwh", "kilowatt hour", "kilowatt hours", "\u5343\u74E6\u65F6"], factor: 36e5 },
151
+ { id: "btu", category: "energy", symbol: "BTU", nameZh: "\u82F1\u70ED\u5355\u4F4D", nameEn: "British thermal unit", aliases: ["btu", "btus", "british thermal unit", "british thermal units", "\u82F1\u70ED\u5355\u4F4D"], factor: 1055.05585262 },
152
+ // ----------------------------------------------------------------- power
153
+ { id: "w", category: "power", symbol: "W", nameZh: "\u74E6", nameEn: "watt", aliases: ["w", "watt", "watts", "\u74E6", "\u74E6\u7279"], factor: 1 },
154
+ { id: "kw", category: "power", symbol: "kW", nameZh: "\u5343\u74E6", nameEn: "kilowatt", aliases: ["kw", "kilowatt", "kilowatts", "\u5343\u74E6"], factor: 1e3 },
155
+ { id: "mw", category: "power", symbol: "MW", nameZh: "\u5146\u74E6", nameEn: "megawatt", aliases: ["mw", "megawatt", "megawatts", "\u5146\u74E6"], factor: 1e6 },
156
+ { id: "gw", category: "power", symbol: "GW", nameZh: "\u5409\u74E6", nameEn: "gigawatt", aliases: ["gw", "gigawatt", "gigawatts", "\u5409\u74E6"], factor: 1e9 },
157
+ { id: "hp", category: "power", symbol: "hp", nameZh: "\u82F1\u5236\u9A6C\u529B", nameEn: "horsepower (mechanical)", aliases: ["hp", "horsepower", "mechanical horsepower", "\u82F1\u5236\u9A6C\u529B"], factor: 745.6998715822702 },
158
+ { id: "ps", category: "power", symbol: "PS", nameZh: "\u516C\u5236\u9A6C\u529B", nameEn: "metric horsepower", aliases: ["ps", "metric horsepower", "pferdest\xE4rke", "\u516C\u5236\u9A6C\u529B", "\u7C73\u5236\u9A6C\u529B", "\u5339"], factor: 735.49875 }
159
+ ];
160
+ function normalizeUnit(raw) {
161
+ return raw.trim().toLowerCase().replace(/\u00b2/g, "2").replace(/\u00b3/g, "3").replace(/\u03bc|\u00b5/g, "u").replace(/[℃]/g, "\xB0c").replace(/[℉]/g, "\xB0f").replace(/\s+/g, "");
162
+ }
163
+ function buildAliasIndex(units = UNITS) {
164
+ const map = /* @__PURE__ */ new Map();
165
+ const conflicts = /* @__PURE__ */ new Map();
166
+ for (const unit of units) {
167
+ const seen = /* @__PURE__ */ new Set();
168
+ const keys = [...unit.aliases, unit.id, unit.symbol, unit.nameZh, unit.nameEn];
169
+ for (const raw of keys) {
170
+ const key = normalizeUnit(raw);
171
+ if (!key || seen.has(key)) continue;
172
+ seen.add(key);
173
+ const existing = map.get(key);
174
+ if (existing) {
175
+ if (existing.id === unit.id) continue;
176
+ const list = conflicts.get(key) ?? [existing];
177
+ if (!list.includes(unit)) list.push(unit);
178
+ conflicts.set(key, list);
179
+ continue;
180
+ }
181
+ map.set(key, unit);
182
+ }
183
+ }
184
+ return { map, conflicts };
185
+ }
186
+ var ALIAS_INDEX = buildAliasIndex();
187
+ function findUnit(token) {
188
+ const key = normalizeUnit(token);
189
+ if (!key) return void 0;
190
+ return ALIAS_INDEX.map.get(key);
191
+ }
192
+ function unitsByCategory(category) {
193
+ return UNITS.filter((u) => u.category === category);
194
+ }
195
+ function stripDeltaMarker(token) {
196
+ const t = token.trim();
197
+ for (const marker of DELTA_MARKERS) {
198
+ if (t.toLowerCase().startsWith(marker.toLowerCase())) {
199
+ const base = t.slice(marker.length).trim();
200
+ return { base, delta: true };
201
+ }
202
+ }
203
+ return { base: t, delta: false };
204
+ }
205
+
206
+ // src/convert.ts
207
+ var UnitConversionError = class extends Error {
208
+ constructor(kind, message, token) {
209
+ super(message);
210
+ /** Machine-readable kind: `unknown-unit` | `category-mismatch` | `bad-value` | `mixed-delta` | `unsupported`. */
211
+ __publicField(this, "kind");
212
+ /** The offending raw token for `unknown-unit` errors. */
213
+ __publicField(this, "token");
214
+ this.name = "UnitConversionError";
215
+ this.kind = kind;
216
+ if (token !== void 0) this.token = token;
217
+ }
218
+ };
219
+ function displayNameFor(def, rawToken) {
220
+ const trimmed = rawToken.trim();
221
+ if (/[\u4e00-\u9fff]/.test(trimmed)) return trimmed;
222
+ return def.symbol;
223
+ }
224
+ function resolveUnit(token) {
225
+ const { base, delta } = stripDeltaMarker(token);
226
+ const def = findUnit(base);
227
+ if (def === void 0) {
228
+ throw new UnitConversionError(
229
+ "unknown-unit",
230
+ `unknown unit "${base}": it is not a recognized length/area/volume/time/angle/speed/temperature/pressure/energy/power unit. `,
231
+ base
232
+ );
233
+ }
234
+ if (delta && def.category !== "temperature") {
235
+ throw new UnitConversionError(
236
+ "mixed-delta",
237
+ `"${token}": the \u0394/delta marker only applies to temperature units.`,
238
+ token
239
+ );
240
+ }
241
+ return { def, delta, canonical: displayNameFor(def, base) };
242
+ }
243
+ function assertFiniteValue(value) {
244
+ if (typeof value !== "number" || !Number.isFinite(value)) {
245
+ throw new UnitConversionError("bad-value", `value must be a finite number, got ${JSON.stringify(value)}`);
246
+ }
247
+ }
248
+ function parseRequest(value, from, to) {
249
+ assertFiniteValue(value);
250
+ const fromResolved = resolveUnit(from);
251
+ const toResolved = resolveUnit(to);
252
+ if (fromResolved.def.category !== toResolved.def.category) {
253
+ throw new UnitConversionError(
254
+ "category-mismatch",
255
+ `cannot convert ${from} (${CATEGORIES[fromResolved.def.category].labelZh}) to ${to} (${CATEGORIES[toResolved.def.category].labelZh}): units must belong to the same category.`
256
+ );
257
+ }
258
+ if (fromResolved.delta !== toResolved.delta) {
259
+ throw new UnitConversionError(
260
+ "mixed-delta",
261
+ `cannot mix absolute and \u0394 temperature units: use ${fromResolved.delta ? "\u0394" : ""}${from} and ${toResolved.delta ? "\u0394" : ""}${to} consistently.`
262
+ );
263
+ }
264
+ return { value, from: fromResolved, to: toResolved };
265
+ }
266
+ function roundSignificant(value, digits = 10) {
267
+ if (!Number.isFinite(value)) return value;
268
+ if (value === 0) return 0;
269
+ if (!Number.isFinite(digits) || digits <= 0) digits = 10;
270
+ const clamped = Math.min(15, Math.max(1, Math.floor(digits)));
271
+ const rounded = Number(value.toPrecision(clamped));
272
+ return rounded === 0 ? 0 : rounded;
273
+ }
274
+ function convertRaw(value, from, to) {
275
+ const { def: f } = from;
276
+ const { def: t } = to;
277
+ if (f.category === "temperature") {
278
+ const fA = f.a;
279
+ const fB = f.b;
280
+ const tA = t.a;
281
+ const tB = t.b;
282
+ if (from.delta || to.delta) {
283
+ return value * fA / tA;
284
+ }
285
+ return (value * fA + fB - tB) / tA;
286
+ }
287
+ const fFactor = f.factor;
288
+ const tFactor = t.factor;
289
+ return value * fFactor / tFactor;
290
+ }
291
+ function convertDetailed(value, from, to, options = {}) {
292
+ const req = parseRequest(value, from, to);
293
+ const raw = convertRaw(req.value, req.from, req.to);
294
+ const sig = options.significantDigits ?? 10;
295
+ const result = roundSignificant(raw, sig);
296
+ const category = req.from.def.category;
297
+ const meta = CATEGORIES[category];
298
+ const fromName = req.from.canonical;
299
+ const toName = req.to.canonical;
300
+ const summary = `${value} ${fromName} = ${result} ${toName} (${meta.labelZh})`;
301
+ return {
302
+ value,
303
+ from: fromName,
304
+ to: toName,
305
+ result,
306
+ category,
307
+ categoryZh: meta.labelZh,
308
+ categoryEn: meta.labelEn,
309
+ fromInput: from,
310
+ toInput: to,
311
+ summary
312
+ };
313
+ }
314
+
315
+ // src/client/locales.ts
316
+ var NS = "unitverse";
317
+ var zh = {
318
+ "view.tab": "\u5355\u4F4D\u6362\u7B97",
319
+ "view.aria": "\u6253\u5F00\u5355\u4F4D\u6362\u7B97\u89C6\u56FE",
320
+ "ui.title": "\u5355\u4F4D\u6362\u7B97",
321
+ "ui.subtitle": "\u5148\u9009\u62E9\u6362\u7B97\u7C7B\u522B\uFF0C\u518D\u5728\u8BE5\u7C7B\u522B\u7684\u5355\u4F4D\u4E4B\u95F4\u6362\u7B97\u3002",
322
+ "ui.value": "\u6570\u503C",
323
+ "ui.valuePlaceholder": "\u8F93\u5165\u6570\u503C",
324
+ "ui.from": "\u6E90\u5355\u4F4D",
325
+ "ui.to": "\u76EE\u6807\u5355\u4F4D",
326
+ "ui.swap": "\u4EA4\u6362\u5355\u4F4D",
327
+ "ui.delta": "\u6E29\u5DEE \u0394",
328
+ "ui.deltaHint": "\u6E29\u5DEE\u6A21\u5F0F\uFF1A\u7ED3\u679C\u8868\u793A\u6E29\u5EA6\u5DEE\u503C\uFF0C\u800C\u4E0D\u662F\u7EDD\u5BF9\u6E29\u5EA6\u3002",
329
+ "ui.result": "\u6362\u7B97\u7ED3\u679C",
330
+ "ui.copy": "\u590D\u5236",
331
+ "ui.copied": "\u5DF2\u590D\u5236",
332
+ "ui.hint": "\u8F93\u5165\u6570\u503C\u5E76\u9009\u62E9\u5355\u4F4D\uFF0C\u7ED3\u679C\u5373\u65F6\u663E\u793A\u3002",
333
+ "ui.emptyResult": "\u8F93\u5165\u6570\u503C\u540E\u663E\u793A\u7ED3\u679C",
334
+ "ui.history": "\u672C\u7C7B\u522B\u6700\u8FD1\u6362\u7B97",
335
+ "ui.historyEmpty": "\u6682\u65E0\u8BB0\u5F55",
336
+ "ui.clear": "\u6E05\u7A7A",
337
+ "ui.categoryLabel": "\u7C7B\u522B",
338
+ "ui.errorUnknownUnit": "\u672A\u77E5\u5355\u4F4D\uFF1A{token}",
339
+ "ui.errorCategoryMismatch": "\u201C{from}\u201D\u4E0E\u201C{to}\u201D\u4E0D\u5C5E\u4E8E\u540C\u4E00\u7C7B\u522B\uFF0C\u65E0\u6CD5\u6362\u7B97",
340
+ "ui.errorMixedDelta": "\u6E29\u5DEE\u5355\u4F4D\uFF08\u0394\uFF09\u4E0D\u80FD\u4E0E\u7EDD\u5BF9\u6E29\u5EA6\u5355\u4F4D\u6DF7\u7528",
341
+ "ui.errorBadValue": "\u8BF7\u8F93\u5165\u6709\u6548\u7684\u6570\u503C",
342
+ "ui.errorGeneric": "\u6362\u7B97\u5931\u8D25\uFF1A{message}",
343
+ "catTab.length": "\u957F\u5EA6\u6362\u7B97",
344
+ "catTab.area": "\u9762\u79EF\u6362\u7B97",
345
+ "catTab.volume": "\u4F53\u79EF\u6362\u7B97",
346
+ "catTab.time": "\u65F6\u95F4\u6362\u7B97",
347
+ "catTab.angle": "\u89D2\u5EA6\u6362\u7B97",
348
+ "catTab.speed": "\u901F\u5EA6\u6362\u7B97",
349
+ "catTab.temperature": "\u6E29\u5EA6\u6362\u7B97",
350
+ "catTab.pressure": "\u538B\u529B\u6362\u7B97",
351
+ "catTab.energy": "\u70ED\u91CF\u6362\u7B97",
352
+ "catTab.power": "\u529F\u7387\u6362\u7B97"
353
+ };
354
+ var en = {
355
+ "view.tab": "Unit Converter",
356
+ "view.aria": "Open the unit converter view",
357
+ "ui.title": "Unit Converter",
358
+ "ui.subtitle": "Pick a category first, then convert between that category\u2019s units.",
359
+ "ui.value": "Value",
360
+ "ui.valuePlaceholder": "Enter a value",
361
+ "ui.from": "From",
362
+ "ui.to": "To",
363
+ "ui.swap": "Swap units",
364
+ "ui.delta": "Difference \u0394",
365
+ "ui.deltaHint": "Difference mode: the result is a temperature interval, not an absolute temperature.",
366
+ "ui.result": "Result",
367
+ "ui.copy": "Copy",
368
+ "ui.copied": "Copied",
369
+ "ui.hint": "Enter a value and pick units \u2014 the result updates instantly.",
370
+ "ui.emptyResult": "The result appears here",
371
+ "ui.history": "Recent in this category",
372
+ "ui.historyEmpty": "No conversions yet",
373
+ "ui.clear": "Clear",
374
+ "ui.categoryLabel": "Category",
375
+ "ui.errorUnknownUnit": "Unknown unit: {token}",
376
+ "ui.errorCategoryMismatch": "\u201C{from}\u201D and \u201C{to}\u201D are not in the same category",
377
+ "ui.errorMixedDelta": "A \u0394 difference unit cannot be mixed with an absolute temperature",
378
+ "ui.errorBadValue": "Enter a valid number",
379
+ "ui.errorGeneric": "Conversion failed: {message}",
380
+ "catTab.length": "Length",
381
+ "catTab.area": "Area",
382
+ "catTab.volume": "Volume",
383
+ "catTab.time": "Time",
384
+ "catTab.angle": "Angle",
385
+ "catTab.speed": "Speed",
386
+ "catTab.temperature": "Temperature",
387
+ "catTab.pressure": "Pressure",
388
+ "catTab.energy": "Heat / Energy",
389
+ "catTab.power": "Power"
390
+ };
391
+
392
+ // src/client/UnitConvertView.tsx
393
+ var import_jsx_runtime = require("react/jsx-runtime");
394
+ var HISTORY_KEY = "dsh.unitverse.history.v1";
395
+ var HISTORY_MAX = 60;
396
+ var HISTORY_SHOWN = 6;
397
+ function isCategoryId(x) {
398
+ return typeof x === "string" && CATEGORY_ORDER.includes(x);
399
+ }
400
+ function readHistory() {
401
+ try {
402
+ const raw = localStorage.getItem(HISTORY_KEY);
403
+ if (!raw) return [];
404
+ const parsed = JSON.parse(raw);
405
+ if (!Array.isArray(parsed)) return [];
406
+ return parsed.filter((x) => {
407
+ if (typeof x !== "object" || x === null) return false;
408
+ const e = x;
409
+ return isCategoryId(e.category) && typeof e.value === "string" && typeof e.fromId === "string" && typeof e.toId === "string" && typeof e.ts === "number";
410
+ }).slice(0, HISTORY_MAX);
411
+ } catch {
412
+ return [];
413
+ }
414
+ }
415
+ function writeHistory(entries) {
416
+ try {
417
+ localStorage.setItem(HISTORY_KEY, JSON.stringify(entries.slice(0, HISTORY_MAX)));
418
+ } catch {
419
+ }
420
+ }
421
+ var DEFAULTS = {
422
+ length: ["km", "mi"],
423
+ area: ["m2", "mu"],
424
+ volume: ["l", "gal_us"],
425
+ time: ["h", "min"],
426
+ angle: ["deg", "rad"],
427
+ speed: ["km_h", "m_s"],
428
+ temperature: ["celsius", "fahrenheit"],
429
+ pressure: ["atm", "kpa"],
430
+ energy: ["kcal", "kj"],
431
+ power: ["kw", "hp"]
432
+ };
433
+ var STYLE_ID = "dsh-unitverse-view-css";
434
+ var CSS = `
435
+ .uc-root {
436
+ --uc-brand: var(--dsw-alias-brand-primary, #4c8dff);
437
+ --uc-label: var(--dsw-alias-label-primary, #e6e6e6);
438
+ --uc-label-2: var(--dsw-alias-label-secondary, #c0c0c0);
439
+ --uc-label-3: var(--dsw-alias-label-tertiary, #9a9a9a);
440
+ --uc-dim: var(--dsw-alias-label-dimmed, #888);
441
+ --uc-border: var(--dsw-alias-border-l1, #2e2e2e);
442
+ --uc-border-2: var(--dsw-alias-border-l2, #3a3a3a);
443
+ --uc-layer: var(--dsw-alias-bg-layer-1, #262626);
444
+ --uc-layer-2: var(--dsw-alias-bg-layer-2, #303030);
445
+ --uc-hover: var(--dsw-alias-interactive-bg-hover, rgba(127, 127, 127, .12));
446
+ display: flex;
447
+ flex-direction: column;
448
+ gap: 16px;
449
+ box-sizing: border-box;
450
+ width: 100%;
451
+ max-width: 880px;
452
+ margin: 0 auto;
453
+ padding: 20px 24px 40px;
454
+ color: var(--uc-label);
455
+ font-size: 13px;
456
+ line-height: 20px;
457
+ }
458
+
459
+ .uc-head { display: flex; flex-direction: column; gap: 4px; }
460
+ .uc-title { font-size: 17px; line-height: 24px; font-weight: 650; letter-spacing: -.01em; }
461
+ .uc-sub { font-size: 12px; line-height: 18px; color: var(--uc-label-3); }
462
+
463
+ /* ---- module switcher ---- */
464
+ .uc-cats { display: flex; flex-wrap: wrap; gap: 8px; }
465
+ .uc-cat {
466
+ height: 30px; padding: 0 13px; border-radius: 15px;
467
+ border: 1px solid var(--uc-border-2); background: transparent;
468
+ color: var(--uc-label-2); font-size: 12.5px; font-family: inherit; line-height: 28px;
469
+ cursor: pointer; white-space: nowrap;
470
+ transition: background .12s ease, border-color .12s ease, color .12s ease;
471
+ }
472
+ .uc-cat:hover { background: var(--uc-hover); color: var(--uc-label); }
473
+ .uc-cat[data-active="true"] {
474
+ border-color: color-mix(in srgb, var(--uc-brand) 55%, transparent);
475
+ background: color-mix(in srgb, var(--uc-brand) 14%, transparent);
476
+ color: var(--uc-brand); font-weight: 600;
477
+ }
478
+ .uc-cat:focus-visible { outline: 2px solid color-mix(in srgb, var(--uc-brand) 60%, transparent); outline-offset: 2px; }
479
+
480
+ /* ---- cards ---- */
481
+ .uc-card {
482
+ display: flex; flex-direction: column; gap: 14px;
483
+ border: 1px solid var(--uc-border); border-radius: 12px;
484
+ background: var(--uc-layer); padding: 16px;
485
+ }
486
+
487
+ /* ---- form ---- */
488
+ .uc-form { display: grid; grid-template-columns: minmax(110px, .9fr) minmax(0, 1.4fr) auto minmax(0, 1.4fr); gap: 12px; align-items: end; }
489
+ .uc-field { display: flex; flex-direction: column; gap: 6px; min-width: 0; }
490
+ .uc-label { font-size: 11px; line-height: 14px; color: var(--uc-label-3); }
491
+ .uc-input, .uc-select {
492
+ box-sizing: border-box; width: 100%; height: 36px; padding: 0 11px;
493
+ border: 1px solid var(--uc-border-2); border-radius: 9px;
494
+ background: var(--uc-layer-2); color: var(--uc-label);
495
+ font-size: 13.5px; font-family: inherit; line-height: 34px; outline: none;
496
+ transition: border-color .12s ease, box-shadow .12s ease;
497
+ }
498
+ .uc-input::placeholder { color: var(--uc-dim); }
499
+ .uc-input:focus, .uc-select:focus {
500
+ border-color: var(--uc-brand);
501
+ box-shadow: 0 0 0 3px color-mix(in srgb, var(--uc-brand) 18%, transparent);
502
+ }
503
+ .uc-value { font-variant-numeric: tabular-nums; font-size: 15px; }
504
+ .uc-selectwrap { position: relative; display: block; min-width: 0; }
505
+ .uc-select { appearance: none; -webkit-appearance: none; padding-right: 30px; cursor: pointer; text-overflow: ellipsis; }
506
+ .uc-selectwrap::after {
507
+ content: ""; position: absolute; right: 12px; top: 50%; width: 7px; height: 7px;
508
+ border-right: 1.6px solid var(--uc-label-3); border-bottom: 1.6px solid var(--uc-label-3);
509
+ transform: translateY(-70%) rotate(45deg); pointer-events: none;
510
+ }
511
+ .uc-swap {
512
+ display: inline-flex; align-items: center; justify-content: center;
513
+ width: 36px; height: 36px; border-radius: 9px;
514
+ border: 1px solid var(--uc-border-2); background: var(--uc-layer-2);
515
+ color: var(--uc-label-2); font-size: 15px; line-height: 1; cursor: pointer;
516
+ transition: background .12s ease, color .12s ease, transform .18s ease;
517
+ }
518
+ .uc-swap:hover { background: var(--uc-hover); color: var(--uc-label); }
519
+ .uc-swap:active { transform: rotate(180deg); }
520
+ .uc-swap:focus-visible { outline: 2px solid color-mix(in srgb, var(--uc-brand) 60%, transparent); outline-offset: 2px; }
521
+
522
+ /* ---- options row (difference switch) ---- */
523
+ .uc-opts { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
524
+ .uc-switch { display: inline-flex; align-items: center; gap: 8px; cursor: pointer; user-select: none; color: var(--uc-label-2); font-size: 12.5px; }
525
+ .uc-switch input { position: absolute; opacity: 0; width: 0; height: 0; }
526
+ .uc-track {
527
+ position: relative; flex: none; width: 32px; height: 18px; border-radius: 9px;
528
+ background: var(--uc-border-2); transition: background .14s ease;
529
+ }
530
+ .uc-track::after {
531
+ content: ""; position: absolute; top: 2px; left: 2px; width: 14px; height: 14px; border-radius: 50%;
532
+ background: #fff; transition: transform .14s ease;
533
+ }
534
+ .uc-switch input:checked + .uc-track { background: var(--uc-brand); }
535
+ .uc-switch input:checked + .uc-track::after { transform: translateX(14px); }
536
+ .uc-switch input:focus-visible + .uc-track { box-shadow: 0 0 0 3px color-mix(in srgb, var(--uc-brand) 25%, transparent); }
537
+ .uc-switch-hint { font-size: 11.5px; line-height: 17px; color: var(--uc-label-3); }
538
+
539
+ /* ---- result hero ---- */
540
+ .uc-hero {
541
+ display: flex; flex-direction: column; gap: 8px;
542
+ border: 1px solid color-mix(in srgb, var(--uc-brand) 30%, var(--uc-border));
543
+ border-radius: 12px; padding: 16px;
544
+ background: color-mix(in srgb, var(--uc-brand) 7%, transparent);
545
+ }
546
+ .uc-hero-top { display: flex; align-items: center; justify-content: space-between; gap: 8px; }
547
+ .uc-hero-label { font-size: 11px; line-height: 14px; color: var(--uc-label-3); letter-spacing: .06em; text-transform: uppercase; }
548
+ .uc-ghost {
549
+ border: none; background: transparent; padding: 2px 6px; border-radius: 6px;
550
+ color: var(--uc-label-3); font-size: 11.5px; font-family: inherit; cursor: pointer;
551
+ transition: background .12s ease, color .12s ease;
552
+ }
553
+ .uc-ghost:hover { background: var(--uc-hover); color: var(--uc-label); }
554
+ .uc-hero-value {
555
+ font-size: 30px; line-height: 38px; font-weight: 650; letter-spacing: -.02em;
556
+ font-variant-numeric: tabular-nums; word-break: break-all;
557
+ }
558
+ .uc-hero-unit { margin-left: 8px; font-size: 15px; font-weight: 500; color: var(--uc-label-2); }
559
+ .uc-hero-eq { font-size: 13px; line-height: 20px; color: var(--uc-label-2); word-break: break-word; }
560
+ .uc-badge {
561
+ align-self: flex-start; padding: 2px 8px; border-radius: 6px;
562
+ background: var(--uc-layer-2); color: var(--uc-label-3);
563
+ font-size: 11px; line-height: 16px;
564
+ }
565
+
566
+ .uc-empty {
567
+ border: 1px dashed var(--uc-border-2); border-radius: 12px;
568
+ padding: 22px 16px; text-align: center; color: var(--uc-dim); font-size: 12.5px;
569
+ }
570
+
571
+ /* ---- error ---- */
572
+ .uc-error {
573
+ display: flex; flex-direction: column; gap: 4px;
574
+ border: 1px solid color-mix(in srgb, var(--dsw-alias-state-error-primary, #e5484d) 45%, transparent);
575
+ border-radius: 12px; padding: 12px 14px;
576
+ background: color-mix(in srgb, var(--dsw-alias-state-error-primary, #e5484d) 9%, transparent);
577
+ }
578
+ .uc-error-title { font-size: 13px; font-weight: 600; color: var(--dsw-alias-state-error-primary, #e5484d); }
579
+
580
+ /* ---- history ---- */
581
+ .uc-hist { display: flex; flex-direction: column; gap: 10px; }
582
+ .uc-hist-head { display: flex; align-items: baseline; justify-content: space-between; gap: 8px; }
583
+ .uc-hist-title { font-size: 12.5px; font-weight: 600; color: var(--uc-label-2); }
584
+ .uc-hist-list { display: flex; flex-direction: column; gap: 6px; }
585
+ .uc-row {
586
+ display: flex; align-items: center; gap: 10px; width: 100%;
587
+ padding: 9px 11px; border: 1px solid var(--uc-border); border-radius: 9px;
588
+ background: transparent; color: var(--uc-label-2);
589
+ font-family: inherit; font-size: 12.5px; line-height: 18px;
590
+ text-align: left; cursor: pointer;
591
+ transition: background .12s ease, border-color .12s ease, color .12s ease;
592
+ }
593
+ .uc-row:hover { background: var(--uc-hover); border-color: var(--uc-border-2); color: var(--uc-label); }
594
+ .uc-row-eq { font-variant-numeric: tabular-nums; white-space: nowrap; }
595
+ .uc-row-names { color: var(--uc-label-3); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
596
+ .uc-row-time { flex: none; margin-left: auto; color: var(--uc-dim); font-size: 11px; font-variant-numeric: tabular-nums; }
597
+ .uc-hint { font-size: 11.5px; line-height: 17px; color: var(--uc-label-3); }
598
+
599
+ @media (max-width: 720px) {
600
+ .uc-root { padding: 16px 14px 32px; }
601
+ .uc-form { grid-template-columns: 1fr auto; }
602
+ .uc-form .uc-field:first-child { grid-column: 1 / -1; }
603
+ .uc-hero-value { font-size: 26px; line-height: 34px; }
604
+ }
605
+ `;
606
+ function ensureStyle() {
607
+ if (typeof document === "undefined") return;
608
+ if (document.getElementById(STYLE_ID)) return;
609
+ const el = document.createElement("style");
610
+ el.id = STYLE_ID;
611
+ el.setAttribute("data-plugin-css", "dsh-unitverse");
612
+ el.textContent = CSS;
613
+ document.head.appendChild(el);
614
+ }
615
+ function UnitConvertView({ t, locale }) {
616
+ const subscribe = (0, import_react.useCallback)((onChange) => locale.subscribe(onChange), [locale]);
617
+ const getSnapshot = (0, import_react.useCallback)(() => locale.getSnapshot(), [locale]);
618
+ const snapshot = (0, import_react.useSyncExternalStore)(subscribe, getSnapshot, getSnapshot);
619
+ const lang = snapshot.active;
620
+ const [category, setCategory] = (0, import_react.useState)("length");
621
+ const [value, setValue] = (0, import_react.useState)("1");
622
+ const [fromId, setFromId] = (0, import_react.useState)(DEFAULTS.length[0]);
623
+ const [toId, setToId] = (0, import_react.useState)(DEFAULTS.length[1]);
624
+ const [delta, setDelta] = (0, import_react.useState)(false);
625
+ const [history, setHistory] = (0, import_react.useState)(() => readHistory());
626
+ const [copied, setCopied] = (0, import_react.useState)(false);
627
+ const unitName = (0, import_react.useCallback)(
628
+ (unit) => lang === "en" ? unit.nameEn : unit.nameZh,
629
+ [lang]
630
+ );
631
+ const displayToken = (0, import_react.useCallback)((unit) => {
632
+ if (lang === "en" && /[\u4e00-\u9fff]/.test(unit.symbol)) return unit.nameEn;
633
+ return unit.symbol;
634
+ }, [lang]);
635
+ const optionLabel = (0, import_react.useCallback)((unit) => {
636
+ const token = displayToken(unit);
637
+ const name = unitName(unit);
638
+ return token === name ? name : `${token} \xB7 ${name}`;
639
+ }, [displayToken, unitName]);
640
+ const units = (0, import_react.useMemo)(() => unitsByCategory(category), [category]);
641
+ const fromUnit = units.find((u) => u.id === fromId) ?? units[0];
642
+ const toUnit = units.find((u) => u.id === toId) ?? units[1] ?? units[0];
643
+ const isTemperature = category === "temperature";
644
+ const prefix = isTemperature && delta ? "\u0394" : "";
645
+ const result = (0, import_react.useMemo)(() => {
646
+ if (value.trim() === "" || !Number.isFinite(Number(value)) || !fromUnit || !toUnit) return null;
647
+ try {
648
+ return convertDetailed(Number(value), prefix + fromUnit.id, prefix + toUnit.id);
649
+ } catch {
650
+ return null;
651
+ }
652
+ }, [value, prefix, fromUnit, toUnit]);
653
+ const error = (0, import_react.useMemo)(() => {
654
+ if (!fromUnit || !toUnit || value.trim() === "") return null;
655
+ if (!Number.isFinite(Number(value))) return t("ui.errorBadValue");
656
+ try {
657
+ convertDetailed(Number(value), prefix + fromUnit.id, prefix + toUnit.id);
658
+ return null;
659
+ } catch (err) {
660
+ if (err instanceof UnitConversionError) {
661
+ switch (err.kind) {
662
+ case "unknown-unit":
663
+ return t("ui.errorUnknownUnit", { token: err.token ?? "" });
664
+ case "category-mismatch":
665
+ return t("ui.errorCategoryMismatch", { from: unitName(fromUnit), to: unitName(toUnit) });
666
+ case "mixed-delta":
667
+ return t("ui.errorMixedDelta");
668
+ case "bad-value":
669
+ return t("ui.errorBadValue");
670
+ default:
671
+ return t("ui.errorGeneric", { message: err.message });
672
+ }
673
+ }
674
+ return t("ui.errorGeneric", { message: err instanceof Error ? err.message : "" });
675
+ }
676
+ }, [value, prefix, fromUnit, toUnit, t, unitName]);
677
+ const firstRun = (0, import_react.useRef)(true);
678
+ const signature = result && fromUnit && toUnit ? `${value}|${prefix}${fromUnit.id}|${prefix}${toUnit.id}` : "";
679
+ (0, import_react.useEffect)(() => {
680
+ if (firstRun.current) {
681
+ firstRun.current = false;
682
+ return;
683
+ }
684
+ if (!result || !fromUnit || !toUnit || value.trim() === "") return;
685
+ setHistory((prev) => {
686
+ const entry = {
687
+ category,
688
+ value,
689
+ fromId: fromUnit.id,
690
+ toId: toUnit.id,
691
+ delta: isTemperature && delta,
692
+ ts: Date.now()
693
+ };
694
+ const next = [
695
+ entry,
696
+ ...prev.filter((h) => !(h.value === value && h.fromId === fromUnit.id && h.toId === toUnit.id && h.delta === entry.delta))
697
+ ].slice(0, HISTORY_MAX);
698
+ writeHistory(next);
699
+ return next;
700
+ });
701
+ }, [signature]);
702
+ const pickCategory = (next) => {
703
+ setCategory(next);
704
+ const [f, s] = DEFAULTS[next];
705
+ setFromId(f);
706
+ setToId(s);
707
+ setDelta(false);
708
+ setCopied(false);
709
+ };
710
+ const onSwap = () => {
711
+ setFromId(toId);
712
+ setToId(fromId);
713
+ };
714
+ const onCopy = () => {
715
+ if (!result || !fromUnit || !toUnit) return;
716
+ const text = `${value} ${displayToken(fromUnit)} = ${result.result} ${displayToken(toUnit)}`;
717
+ const pending = navigator.clipboard?.writeText(text);
718
+ if (!pending) return;
719
+ void pending.then(() => {
720
+ setCopied(true);
721
+ window.setTimeout(() => setCopied(false), 1400);
722
+ }).catch(() => {
723
+ });
724
+ };
725
+ const rows = (0, import_react.useMemo)(() => {
726
+ const list = unitsByCategory(category);
727
+ return history.filter((h) => h.category === category).slice(0, HISTORY_SHOWN).map((h) => {
728
+ const f = list.find((u) => u.id === h.fromId);
729
+ const s = list.find((u) => u.id === h.toId);
730
+ if (!f || !s) return null;
731
+ const p = h.delta ? "\u0394" : "";
732
+ let out;
733
+ try {
734
+ out = String(convertDetailed(Number(h.value), p + f.id, p + s.id).result);
735
+ } catch {
736
+ return null;
737
+ }
738
+ return { key: `${h.ts}-${f.id}-${s.id}`, entry: h, from: f, to: s, out };
739
+ }).filter((x) => x !== null);
740
+ }, [history, category]);
741
+ if (typeof window !== "undefined") ensureStyle();
742
+ const catLabel = lang === "en" ? CATEGORIES[category].labelEn : CATEGORIES[category].labelZh;
743
+ const rowsOfCategory = history.filter((h) => h.category === category).length;
744
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "uc-root", children: [
745
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "uc-head", children: [
746
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "uc-title", children: t("ui.title") }),
747
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "uc-sub", children: t("ui.subtitle") })
748
+ ] }),
749
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "uc-cats", role: "tablist", "aria-label": t("ui.title"), children: CATEGORY_ORDER.map((id) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
750
+ "button",
751
+ {
752
+ type: "button",
753
+ role: "tab",
754
+ className: "uc-cat",
755
+ "data-active": id === category,
756
+ "aria-selected": id === category,
757
+ onClick: () => pickCategory(id),
758
+ children: t(`catTab.${id}`)
759
+ },
760
+ id
761
+ )) }),
762
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "uc-card", children: [
763
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "uc-form", children: [
764
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("label", { className: "uc-field", children: [
765
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "uc-label", children: t("ui.value") }),
766
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
767
+ "input",
768
+ {
769
+ className: "uc-input uc-value",
770
+ value,
771
+ inputMode: "decimal",
772
+ placeholder: t("ui.valuePlaceholder"),
773
+ onChange: (e) => setValue(e.target.value)
774
+ }
775
+ )
776
+ ] }),
777
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("label", { className: "uc-field", children: [
778
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "uc-label", children: t("ui.from") }),
779
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "uc-selectwrap", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("select", { className: "uc-select", value: fromUnit?.id ?? "", onChange: (e) => setFromId(e.target.value), children: units.map((u) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: u.id, children: optionLabel(u) }, u.id)) }) })
780
+ ] }),
781
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", className: "uc-swap", title: t("ui.swap"), "aria-label": t("ui.swap"), onClick: onSwap, children: "\u21C4" }),
782
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("label", { className: "uc-field", children: [
783
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "uc-label", children: t("ui.to") }),
784
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "uc-selectwrap", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("select", { className: "uc-select", value: toUnit?.id ?? "", onChange: (e) => setToId(e.target.value), children: units.map((u) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: u.id, children: optionLabel(u) }, u.id)) }) })
785
+ ] })
786
+ ] }),
787
+ isTemperature ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "uc-opts", children: [
788
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("label", { className: "uc-switch", children: [
789
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("input", { type: "checkbox", checked: delta, onChange: (e) => setDelta(e.target.checked) }),
790
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "uc-track" }),
791
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: t("ui.delta") })
792
+ ] }),
793
+ delta && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "uc-switch-hint", children: t("ui.deltaHint") })
794
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "uc-hint", children: t("ui.hint") })
795
+ ] }),
796
+ error ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "uc-error", role: "alert", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "uc-error-title", children: error }) }) : result && fromUnit && toUnit ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "uc-hero", children: [
797
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "uc-hero-top", children: [
798
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "uc-hero-label", children: t("ui.result") }),
799
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", className: "uc-ghost", onClick: onCopy, children: copied ? t("ui.copied") : t("ui.copy") })
800
+ ] }),
801
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "uc-hero-value", children: [
802
+ result.result,
803
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "uc-hero-unit", children: displayToken(toUnit) })
804
+ ] }),
805
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "uc-hero-eq", children: [
806
+ value,
807
+ " ",
808
+ unitName(fromUnit),
809
+ " = ",
810
+ result.result,
811
+ " ",
812
+ unitName(toUnit)
813
+ ] }),
814
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "uc-badge", children: [
815
+ t("ui.categoryLabel"),
816
+ ": ",
817
+ catLabel
818
+ ] })
819
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "uc-empty", children: t("ui.emptyResult") }),
820
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "uc-hist", children: [
821
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "uc-hist-head", children: [
822
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "uc-hist-title", children: t("ui.history") }),
823
+ rowsOfCategory > 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
824
+ "button",
825
+ {
826
+ type: "button",
827
+ className: "uc-ghost",
828
+ onClick: () => {
829
+ setHistory((prev) => {
830
+ const next = prev.filter((h) => h.category !== category);
831
+ writeHistory(next);
832
+ return next;
833
+ });
834
+ },
835
+ children: t("ui.clear")
836
+ }
837
+ )
838
+ ] }),
839
+ rows.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "uc-empty", children: t("ui.historyEmpty") }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "uc-hist-list", children: rows.map((r) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
840
+ "button",
841
+ {
842
+ type: "button",
843
+ className: "uc-row",
844
+ onClick: () => {
845
+ setCategory(r.entry.category);
846
+ setValue(r.entry.value);
847
+ setFromId(r.from.id);
848
+ setToId(r.to.id);
849
+ setDelta(r.entry.delta);
850
+ },
851
+ children: [
852
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "uc-row-eq", children: [
853
+ r.entry.value,
854
+ " ",
855
+ displayToken(r.from),
856
+ " = ",
857
+ r.out,
858
+ " ",
859
+ displayToken(r.to)
860
+ ] }),
861
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "uc-row-names", children: [
862
+ unitName(r.from),
863
+ " \u2192 ",
864
+ unitName(r.to)
865
+ ] }),
866
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "uc-row-time", children: new Date(r.entry.ts).toLocaleTimeString() })
867
+ ]
868
+ },
869
+ r.key
870
+ )) })
871
+ ] })
872
+ ] });
873
+ }
874
+
875
+ // src/client/index.tsx
876
+ var inject = ["slots", "locale"];
877
+ var VIEW_ORDER = 20;
878
+ function apply(ctx) {
879
+ ctx.effect(() => ctx.locale.register(NS, { zh, en }), "unitverse: dictionaries");
880
+ const t = ctx.locale.bind(NS);
881
+ ctx.slots.inject("conversation.view", () => ctx.slots.register({
882
+ name: "conversation.view",
883
+ id: "unitverse",
884
+ order: VIEW_ORDER,
885
+ locale: NS,
886
+ label: () => t("view.tab"),
887
+ // The active-locale source is injected so the panel can localize unit and
888
+ // category names (data owned by units.ts) and re-render on a language switch.
889
+ inject: () => ({ locale: ctx.locale })
890
+ }, UnitConvertView));
891
+ }
892
+
893
+ return module.exports;
894
+ }
895
+ });
896
+