@just-web/toolkits 3.0.0 → 3.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.
Files changed (97) hide show
  1. package/dist/index.cjs +13 -2
  2. package/dist/index.d.cts +8 -2
  3. package/dist/index.d.mts +8 -2
  4. package/dist/index.mjs +7 -2
  5. package/dist/style/css-properties.d.cts +2 -1
  6. package/dist/style/css-properties.d.cts.map +1 -1
  7. package/dist/style/css-properties.d.mts +2 -1
  8. package/dist/style/css-properties.d.mts.map +1 -1
  9. package/dist/units/convert-css-unit.cjs +173 -0
  10. package/dist/units/convert-css-unit.cjs.map +1 -0
  11. package/dist/units/convert-css-unit.d.cts +25 -0
  12. package/dist/units/convert-css-unit.d.cts.map +1 -0
  13. package/dist/units/convert-css-unit.d.mts +25 -0
  14. package/dist/units/convert-css-unit.d.mts.map +1 -0
  15. package/dist/units/convert-css-unit.mjs +173 -0
  16. package/dist/units/convert-css-unit.mjs.map +1 -0
  17. package/dist/units/create-css-unit-converter.cjs +33 -0
  18. package/dist/units/create-css-unit-converter.cjs.map +1 -0
  19. package/dist/units/create-css-unit-converter.d.cts +28 -0
  20. package/dist/units/create-css-unit-converter.d.cts.map +1 -0
  21. package/dist/units/create-css-unit-converter.d.mts +28 -0
  22. package/dist/units/create-css-unit-converter.d.mts.map +1 -0
  23. package/dist/units/create-css-unit-converter.mjs +33 -0
  24. package/dist/units/create-css-unit-converter.mjs.map +1 -0
  25. package/dist/units/css-unit-converter.types.d.cts +35 -0
  26. package/dist/units/css-unit-converter.types.d.cts.map +1 -0
  27. package/dist/units/css-unit-converter.types.d.mts +35 -0
  28. package/dist/units/css-unit-converter.types.d.mts.map +1 -0
  29. package/dist/units/get-css-unit.cjs +29 -0
  30. package/dist/units/get-css-unit.cjs.map +1 -0
  31. package/dist/units/get-css-unit.d.cts +23 -0
  32. package/dist/units/get-css-unit.d.cts.map +1 -0
  33. package/dist/units/get-css-unit.d.mts +23 -0
  34. package/dist/units/get-css-unit.d.mts.map +1 -0
  35. package/dist/units/get-css-unit.mjs +29 -0
  36. package/dist/units/get-css-unit.mjs.map +1 -0
  37. package/dist/units/is-effectively-zero.cjs +35 -0
  38. package/dist/units/is-effectively-zero.cjs.map +1 -0
  39. package/dist/units/is-effectively-zero.d.cts +28 -0
  40. package/dist/units/is-effectively-zero.d.cts.map +1 -0
  41. package/dist/units/is-effectively-zero.d.mts +28 -0
  42. package/dist/units/is-effectively-zero.d.mts.map +1 -0
  43. package/dist/units/is-effectively-zero.mjs +35 -0
  44. package/dist/units/is-effectively-zero.mjs.map +1 -0
  45. package/dist/units/parse-css-number.cjs +29 -0
  46. package/dist/units/parse-css-number.cjs.map +1 -0
  47. package/dist/units/parse-css-number.d.cts +24 -0
  48. package/dist/units/parse-css-number.d.cts.map +1 -0
  49. package/dist/units/parse-css-number.d.mts +24 -0
  50. package/dist/units/parse-css-number.d.mts.map +1 -0
  51. package/dist/units/parse-css-number.mjs +29 -0
  52. package/dist/units/parse-css-number.mjs.map +1 -0
  53. package/dist/units/parse-css-value.cjs +32 -0
  54. package/dist/units/parse-css-value.cjs.map +1 -0
  55. package/dist/units/parse-css-value.d.cts +22 -0
  56. package/dist/units/parse-css-value.d.cts.map +1 -0
  57. package/dist/units/parse-css-value.d.mts +22 -0
  58. package/dist/units/parse-css-value.d.mts.map +1 -0
  59. package/dist/units/parse-css-value.mjs +31 -0
  60. package/dist/units/parse-css-value.mjs.map +1 -0
  61. package/dist/units/px-2-rem.cjs +8 -5
  62. package/dist/units/px-2-rem.cjs.map +1 -1
  63. package/dist/units/px-2-rem.d.cts +9 -7
  64. package/dist/units/px-2-rem.d.cts.map +1 -1
  65. package/dist/units/px-2-rem.d.mts +9 -7
  66. package/dist/units/px-2-rem.d.mts.map +1 -1
  67. package/dist/units/px-2-rem.mjs +8 -5
  68. package/dist/units/px-2-rem.mjs.map +1 -1
  69. package/dist/units/rem-2-px.cjs +8 -5
  70. package/dist/units/rem-2-px.cjs.map +1 -1
  71. package/dist/units/rem-2-px.d.cts +9 -7
  72. package/dist/units/rem-2-px.d.cts.map +1 -1
  73. package/dist/units/rem-2-px.d.mts +9 -7
  74. package/dist/units/rem-2-px.d.mts.map +1 -1
  75. package/dist/units/rem-2-px.mjs +8 -5
  76. package/dist/units/rem-2-px.mjs.map +1 -1
  77. package/package.json +1 -1
  78. package/src/index.ts +7 -0
  79. package/src/style/css-properties.ts +3 -1
  80. package/src/units/convert-css-unit.ts +292 -0
  81. package/src/units/create-css-unit-converter.ts +30 -0
  82. package/src/units/css-unit-converter.types.ts +49 -0
  83. package/src/units/get-css-unit.ts +24 -0
  84. package/src/units/is-effectively-zero.ts +35 -0
  85. package/src/units/parse-css-number.ts +26 -0
  86. package/src/units/parse-css-value.ts +35 -0
  87. package/src/units/px-2-num.ts +5 -4
  88. package/src/units/px-2-rem.ts +12 -8
  89. package/src/units/rem-2-px.ts +11 -9
  90. package/dist/units/px-2-num.cjs +0 -23
  91. package/dist/units/px-2-num.cjs.map +0 -1
  92. package/dist/units/px-2-num.d.cts +0 -19
  93. package/dist/units/px-2-num.d.cts.map +0 -1
  94. package/dist/units/px-2-num.d.mts +0 -19
  95. package/dist/units/px-2-num.d.mts.map +0 -1
  96. package/dist/units/px-2-num.mjs +0 -22
  97. package/dist/units/px-2-num.mjs.map +0 -1
@@ -0,0 +1,292 @@
1
+ import type { Required } from 'type-plus'
2
+ import type { ConvertCssUnitOptions, CssLengthUnit } from './css-unit-converter.types.ts'
3
+ import { getRemToPxScale } from './get-rem-to-px-scale.ts'
4
+ import { parseCssValue } from './parse-css-value.ts'
5
+
6
+ const PX_PER_IN = 96
7
+ const PT_PER_IN = 72
8
+ const PC_PER_IN = 6
9
+ const CM_PER_IN = 2.54
10
+ const MM_PER_IN = 25.4
11
+
12
+ const DEFAULT_ELEMENT_FONT_SIZE = 16
13
+ const DEFAULT_PRECISION = 4
14
+
15
+ const ABSOLUTE_UNITS: CssLengthUnit[] = ['px', 'pt', 'pc', 'in', 'cm', 'mm']
16
+ const LINE_UNITS: CssLengthUnit[] = ['lh', 'rlh']
17
+
18
+ /**
19
+ * Converts a CSS length value from one unit to another.
20
+ *
21
+ * @param value - The value to convert. Can be a number or string (e.g. '16px', '1.5rem'). Pass-through for null/undefined.
22
+ * @param toUnit - The target unit.
23
+ * @param options - Conversion context. When omitted, uses browser auto-detect for rootFontSize and viewport when available.
24
+ * @returns The converted numeric value, or null/undefined when input is null/undefined.
25
+ * @throws When required context is missing (viewport, lineHeight, percentReference) or percentReference is 0 for % conversion.
26
+ *
27
+ * @example
28
+ * ```ts
29
+ * convertCssUnit('16px', 'rem') // 1
30
+ * convertCssUnit('1rem', 'px') // 16
31
+ * convertCssUnit('50%', 'px', { percentReference: 200 }) // 100
32
+ * convertCssUnit('10vw', 'px', { viewportWidth: 375 }) // 37.5
33
+ * ```
34
+ */
35
+ export function convertCssUnit(
36
+ value: number | string | null | undefined,
37
+ toUnit: CssLengthUnit,
38
+ options?: ConvertCssUnitOptions | undefined
39
+ ): number | null | undefined {
40
+ const [num, parsedUnit] = parseCssValue(value)
41
+ if (num === null || num === undefined) return num
42
+ if (Number.isNaN(num)) {
43
+ throw new Error(`Invalid CSS value: ${value}`)
44
+ }
45
+
46
+ const fromUnit: CssLengthUnit = options?.fromUnit ?? normalizeUnit(parsedUnit) ?? 'px'
47
+
48
+ const resolved = resolveOptions(options)
49
+
50
+ if (fromUnit === toUnit) {
51
+ return Number(num.toFixed(resolved.precision))
52
+ }
53
+
54
+ let result: number
55
+
56
+ if (fromUnit === 'rem' && toUnit === 'em') {
57
+ result = remToEmDirect(num, resolved)
58
+ } else if (fromUnit === 'em' && toUnit === 'rem') {
59
+ result = emToRemDirect(num, resolved)
60
+ } else {
61
+ const px = valueToPx(num, fromUnit, resolved)
62
+ result = pxToValue(px, toUnit, resolved)
63
+ }
64
+
65
+ return Number(result.toFixed(resolved.precision))
66
+ }
67
+
68
+ function normalizeUnit(unit: string | undefined): CssLengthUnit | undefined {
69
+ if (!unit) return undefined
70
+ const u = unit.toLowerCase()
71
+ if (
72
+ u === 'px' ||
73
+ u === 'pt' ||
74
+ u === 'pc' ||
75
+ u === 'in' ||
76
+ u === 'cm' ||
77
+ u === 'mm' ||
78
+ u === 'rem' ||
79
+ u === 'em' ||
80
+ u === 'vw' ||
81
+ u === 'vh' ||
82
+ u === 'vmin' ||
83
+ u === 'vmax' ||
84
+ u === 'lh' ||
85
+ u === 'rlh' ||
86
+ u === 'ch' ||
87
+ u === '%'
88
+ ) {
89
+ return u as CssLengthUnit
90
+ }
91
+ return undefined
92
+ }
93
+
94
+ function resolveOptions(
95
+ options?: ConvertCssUnitOptions | undefined
96
+ ): Required<
97
+ Pick<
98
+ ConvertCssUnitOptions,
99
+ | 'rootFontSize'
100
+ | 'elementFontSize'
101
+ | 'viewportWidth'
102
+ | 'viewportHeight'
103
+ | 'lineHeight'
104
+ | 'chWidth'
105
+ | 'percentReference'
106
+ | 'precision'
107
+ >
108
+ > {
109
+ const rootFontSize = options?.rootFontSize ?? getRemToPxScale()
110
+ const elementFontSize = options?.elementFontSize ?? DEFAULT_ELEMENT_FONT_SIZE
111
+ const precision = options?.precision ?? DEFAULT_PRECISION
112
+
113
+ let viewportWidth = options?.viewportWidth
114
+ let viewportHeight = options?.viewportHeight
115
+ if (typeof window !== 'undefined') {
116
+ viewportWidth ??= window.innerWidth
117
+ viewportHeight ??= window.innerHeight
118
+ }
119
+
120
+ const lineHeight = options?.lineHeight
121
+ const chWidth = options?.chWidth ?? elementFontSize * 0.5
122
+ const percentReference = options?.percentReference ?? 0
123
+
124
+ return {
125
+ rootFontSize,
126
+ elementFontSize,
127
+ viewportWidth: viewportWidth ?? 0,
128
+ viewportHeight: viewportHeight ?? 0,
129
+ lineHeight: lineHeight ?? 0,
130
+ chWidth,
131
+ percentReference,
132
+ precision
133
+ }
134
+ }
135
+
136
+ function remToEmDirect(value: number, resolved: ReturnType<typeof resolveOptions>): number {
137
+ return value * (resolved.rootFontSize / resolved.elementFontSize)
138
+ }
139
+
140
+ function emToRemDirect(value: number, resolved: ReturnType<typeof resolveOptions>): number {
141
+ return value * (resolved.elementFontSize / resolved.rootFontSize)
142
+ }
143
+
144
+ function valueToPx(
145
+ value: number,
146
+ fromUnit: CssLengthUnit,
147
+ resolved: ReturnType<typeof resolveOptions>
148
+ ): number {
149
+ if (ABSOLUTE_UNITS.includes(fromUnit)) {
150
+ return toPxFromAbsolute(value, fromUnit)
151
+ }
152
+ if (fromUnit === 'rem') {
153
+ return value * (resolved.rootFontSize ?? 0)
154
+ }
155
+ if (fromUnit === 'em') {
156
+ return value * (resolved.elementFontSize ?? 0)
157
+ }
158
+ if (fromUnit === 'vw') {
159
+ if (resolved.viewportWidth === 0) {
160
+ throw new Error('viewportWidth is required for vw conversion')
161
+ }
162
+ return (value / 100) * resolved.viewportWidth
163
+ }
164
+ if (fromUnit === 'vh') {
165
+ if (resolved.viewportHeight === 0) {
166
+ throw new Error('viewportHeight is required for vh conversion')
167
+ }
168
+ return (value / 100) * resolved.viewportHeight
169
+ }
170
+ if (fromUnit === 'vmin') {
171
+ if (resolved.viewportWidth === 0 || resolved.viewportHeight === 0) {
172
+ throw new Error('viewportWidth and viewportHeight are required for vmin conversion')
173
+ }
174
+ return (value / 100) * Math.min(resolved.viewportWidth, resolved.viewportHeight)
175
+ }
176
+ if (fromUnit === 'vmax') {
177
+ if (resolved.viewportWidth === 0 || resolved.viewportHeight === 0) {
178
+ throw new Error('viewportWidth and viewportHeight are required for vmax conversion')
179
+ }
180
+ return (value / 100) * Math.max(resolved.viewportWidth, resolved.viewportHeight)
181
+ }
182
+ if (LINE_UNITS.includes(fromUnit)) {
183
+ if (resolved.lineHeight === 0) {
184
+ throw new Error('lineHeight is required for lh/rlh conversion')
185
+ }
186
+ return value * resolved.lineHeight
187
+ }
188
+ if (fromUnit === 'ch') {
189
+ return value * resolved.chWidth
190
+ }
191
+ if (fromUnit === '%') {
192
+ if (resolved.percentReference === 0) {
193
+ throw new Error('percentReference is required for % conversion')
194
+ }
195
+ return (value / 100) * resolved.percentReference
196
+ }
197
+ throw new Error(`Unsupported unit: ${fromUnit}`)
198
+ }
199
+
200
+ function pxToValue(
201
+ px: number,
202
+ toUnit: CssLengthUnit,
203
+ resolved: ReturnType<typeof resolveOptions>
204
+ ): number {
205
+ if (ABSOLUTE_UNITS.includes(toUnit)) {
206
+ return fromPxToAbsolute(px, toUnit)
207
+ }
208
+ if (toUnit === 'rem') {
209
+ return px / resolved.rootFontSize
210
+ }
211
+ if (toUnit === 'em') {
212
+ return px / resolved.elementFontSize
213
+ }
214
+ if (toUnit === 'vw') {
215
+ if (resolved.viewportWidth === 0) {
216
+ throw new Error('viewportWidth is required for vw conversion')
217
+ }
218
+ return (px / resolved.viewportWidth) * 100
219
+ }
220
+ if (toUnit === 'vh') {
221
+ if (resolved.viewportHeight === 0) {
222
+ throw new Error('viewportHeight is required for vh conversion')
223
+ }
224
+ return (px / resolved.viewportHeight) * 100
225
+ }
226
+ if (toUnit === 'vmin') {
227
+ if (resolved.viewportWidth === 0 || resolved.viewportHeight === 0) {
228
+ throw new Error('viewportWidth and viewportHeight are required for vmin conversion')
229
+ }
230
+ return (px / Math.min(resolved.viewportWidth, resolved.viewportHeight)) * 100
231
+ }
232
+ if (toUnit === 'vmax') {
233
+ if (resolved.viewportWidth === 0 || resolved.viewportHeight === 0) {
234
+ throw new Error('viewportWidth and viewportHeight are required for vmax conversion')
235
+ }
236
+ return (px / Math.max(resolved.viewportWidth, resolved.viewportHeight)) * 100
237
+ }
238
+ if (LINE_UNITS.includes(toUnit)) {
239
+ if (resolved.lineHeight === 0) {
240
+ throw new Error('lineHeight is required for lh/rlh conversion')
241
+ }
242
+ return px / resolved.lineHeight
243
+ }
244
+ if (toUnit === 'ch') {
245
+ return px / resolved.chWidth
246
+ }
247
+ if (toUnit === '%') {
248
+ if (resolved.percentReference === 0) {
249
+ throw new Error('percentReference is required and must be non-zero for conversion to %')
250
+ }
251
+ return (px / resolved.percentReference) * 100
252
+ }
253
+ throw new Error(`Unsupported unit: ${toUnit}`)
254
+ }
255
+
256
+ function toPxFromAbsolute(value: number, unit: CssLengthUnit): number {
257
+ switch (unit) {
258
+ case 'px':
259
+ return value
260
+ case 'pt':
261
+ return (value * PX_PER_IN) / PT_PER_IN
262
+ case 'pc':
263
+ return (value * PX_PER_IN) / PC_PER_IN
264
+ case 'in':
265
+ return value * PX_PER_IN
266
+ case 'cm':
267
+ return (value * PX_PER_IN) / CM_PER_IN
268
+ case 'mm':
269
+ return (value * PX_PER_IN) / MM_PER_IN
270
+ default:
271
+ return value
272
+ }
273
+ }
274
+
275
+ function fromPxToAbsolute(px: number, unit: CssLengthUnit): number {
276
+ switch (unit) {
277
+ case 'px':
278
+ return px
279
+ case 'pt':
280
+ return (px * PT_PER_IN) / PX_PER_IN
281
+ case 'pc':
282
+ return (px * PC_PER_IN) / PX_PER_IN
283
+ case 'in':
284
+ return px / PX_PER_IN
285
+ case 'cm':
286
+ return (px * CM_PER_IN) / PX_PER_IN
287
+ case 'mm':
288
+ return (px * MM_PER_IN) / PX_PER_IN
289
+ default:
290
+ return px
291
+ }
292
+ }
@@ -0,0 +1,30 @@
1
+ import { convertCssUnit } from './convert-css-unit.ts'
2
+ import type { CssLengthUnit, CssUnitConverterContext } from './css-unit-converter.types.ts'
3
+
4
+ /**
5
+ * Creates a pre-configured CSS unit converter with fixed context.
6
+ *
7
+ * @param context - Root font size, viewport, line height, etc. Omitted values use browser auto-detect when available.
8
+ * @returns A converter function that accepts value and toUnit (and optional fromUnit override).
9
+ *
10
+ * @example
11
+ * ```ts
12
+ * const convert = createCssUnitConverter({
13
+ * rootFontSize: 16,
14
+ * viewportWidth: 375,
15
+ * viewportHeight: 812,
16
+ * })
17
+ * convert('1rem', 'px') // 16
18
+ * convert('10vw', 'px') // 37.5
19
+ * convert(16, 'rem', { fromUnit: 'px' }) // 1
20
+ * ```
21
+ */
22
+ export function createCssUnitConverter(context?: CssUnitConverterContext) {
23
+ return function convert(
24
+ value: number | string,
25
+ toUnit: CssLengthUnit,
26
+ options?: { fromUnit?: CssLengthUnit | undefined } | undefined
27
+ ): number | null | undefined {
28
+ return convertCssUnit(value, toUnit, { ...context, ...options })
29
+ }
30
+ }
@@ -0,0 +1,49 @@
1
+ /**
2
+ * Supported CSS length units for conversion.
3
+ */
4
+ export type CssLengthUnit =
5
+ | 'px'
6
+ | 'pt'
7
+ | 'pc'
8
+ | 'in'
9
+ | 'cm'
10
+ | 'mm'
11
+ | 'rem'
12
+ | 'em'
13
+ | 'vw'
14
+ | 'vh'
15
+ | 'vmin'
16
+ | 'vmax'
17
+ | 'lh'
18
+ | 'rlh'
19
+ | 'ch'
20
+ | '%'
21
+
22
+ /**
23
+ * Options for convertCssUnit.
24
+ */
25
+ export interface ConvertCssUnitOptions {
26
+ /** Override when value is number or unitless string. Default: 'px'. */
27
+ fromUnit?: CssLengthUnit | undefined
28
+ /** Root font size in px for rem. Auto: getRemToPxScale() in browser. Default: 16. */
29
+ rootFontSize?: number | undefined
30
+ /** Element font size in px for em. Default: 16. */
31
+ elementFontSize?: number | undefined
32
+ /** Viewport width in px for vw, vmin, vmax. Auto: window.innerWidth in browser. */
33
+ viewportWidth?: number | undefined
34
+ /** Viewport height in px for vh, vmin, vmax. Auto: window.innerHeight in browser. */
35
+ viewportHeight?: number | undefined
36
+ /** Line height in px for lh, rlh. */
37
+ lineHeight?: number | undefined
38
+ /** Width of "0" character in px for ch. Default: ~0.5em. */
39
+ chWidth?: number | undefined
40
+ /** Value that 100% equals (for % conversions). */
41
+ percentReference?: number | undefined
42
+ /** Decimal places for output. Default: 4. */
43
+ precision?: number | undefined
44
+ }
45
+
46
+ /**
47
+ * Context for createCssUnitConverter (pre-configured values).
48
+ */
49
+ export type CssUnitConverterContext = Omit<ConvertCssUnitOptions, 'fromUnit'>
@@ -0,0 +1,24 @@
1
+ import { parseCssValue } from './parse-css-value.ts'
2
+
3
+ /**
4
+ * Extracts the unit from a CSS value string.
5
+ * Thin wrapper around parseCssValue.
6
+ *
7
+ * @param value - The CSS value to parse (e.g. '16px', '1.5rem', '100%'). Pass-through for null/undefined.
8
+ * @returns The unit string, undefined for numbers or unitless strings, or null/undefined when input is null/undefined
9
+ *
10
+ * @example
11
+ * ```ts
12
+ * getCssUnit('16px') // 'px'
13
+ * getCssUnit('1rem') // 'rem'
14
+ * getCssUnit('100%') // '%'
15
+ * getCssUnit('0') // undefined
16
+ * getCssUnit('16') // undefined
17
+ * getCssUnit(null) // null
18
+ * getCssUnit(undefined) // undefined
19
+ * ```
20
+ */
21
+ export function getCssUnit(value: number | string | null | undefined): string | null | undefined {
22
+ if (value === null || value === undefined) return value
23
+ return parseCssValue(value)[1]
24
+ }
@@ -0,0 +1,35 @@
1
+ import { parseCssNumber } from './parse-css-number.ts'
2
+
3
+ /**
4
+ * Determines if a CSS value is effectively 0 regardless of unit.
5
+ *
6
+ * @param value - The CSS value to check. Can be a number or string (e.g. '0px', '0rem', '0%'). Pass-through for null/undefined.
7
+ * @param options - Optional configuration
8
+ * @param options.epsilon - Floating-point tolerance. Default 1e-10. Use 0 for strict equality.
9
+ * @returns true if the value is effectively zero, false otherwise, or null/undefined when input is null/undefined
10
+ *
11
+ * @example
12
+ * ```ts
13
+ * isEffectivelyZero(0) // true
14
+ * isEffectivelyZero('0px') // true
15
+ * isEffectivelyZero('0rem') // true
16
+ * isEffectivelyZero('0%') // true
17
+ * isEffectivelyZero('1px') // false
18
+ * isEffectivelyZero(0.00000000001) // true (within default epsilon)
19
+ * isEffectivelyZero(0.0001, { epsilon: 0.001 }) // true
20
+ * isEffectivelyZero(null) // null
21
+ * isEffectivelyZero(undefined) // undefined
22
+ * ```
23
+ */
24
+ export function isEffectivelyZero(
25
+ value: number | string | null | undefined,
26
+ options?: { epsilon?: number | undefined } | undefined
27
+ ): boolean | null | undefined {
28
+ const parsed = parseCssNumber(value)
29
+ if (parsed === null || parsed === undefined) return parsed
30
+ if (!Number.isFinite(parsed)) {
31
+ return false
32
+ }
33
+ const epsilon = options?.epsilon ?? 1e-10
34
+ return Math.abs(parsed) <= epsilon
35
+ }
@@ -0,0 +1,26 @@
1
+ import { parseCssValue } from './parse-css-value.ts'
2
+
3
+ /**
4
+ * Extracts the numeric part from any CSS length/percentage value.
5
+ * Thin wrapper around parseCssValue.
6
+ *
7
+ * @param value - The CSS value to parse. Can be a number or string (e.g. '16px', '1.5rem', '100%')
8
+ * @returns The numeric value, or NaN for invalid input. Passes through null and undefined.
9
+ *
10
+ * @example
11
+ * ```ts
12
+ * parseCssNumber('16px') // 16
13
+ * parseCssNumber('1.5rem') // 1.5
14
+ * parseCssNumber('100%') // 100
15
+ * parseCssNumber('0lh') // 0
16
+ * parseCssNumber(16) // 16
17
+ * parseCssNumber('abc') // NaN
18
+ * parseCssNumber(null) // null
19
+ * parseCssNumber(undefined) // undefined
20
+ * ```
21
+ */
22
+ export function parseCssNumber(
23
+ value: number | string | null | undefined
24
+ ): number | null | undefined {
25
+ return parseCssValue(value)[0]
26
+ }
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Parses a CSS value in one pass and returns both the numeric part and the unit.
3
+ * Powers parseCssNumber, getCssUnit, and isEffectivelyZero.
4
+ *
5
+ * @param value - The CSS value to parse. Can be a number or string (e.g. '16px', '1.5rem', '100%')
6
+ * @returns A tuple of [number, unit | undefined]. Unit is undefined for numbers or unitless strings.
7
+ *
8
+ * @example
9
+ * ```ts
10
+ * parseCssValue('16px') // [16, 'px']
11
+ * parseCssValue('1.5rem') // [1.5, 'rem']
12
+ * parseCssValue('100%') // [100, '%']
13
+ * parseCssValue('0') // [0, undefined]
14
+ * parseCssValue(16) // [16, undefined]
15
+ * parseCssValue('abc') // [NaN, undefined]
16
+ * ```
17
+ */
18
+ export function parseCssValue(
19
+ value: number | string | null | undefined
20
+ ): [number | null | undefined, string | undefined] {
21
+ if (value === undefined || value === null) {
22
+ return [value, undefined]
23
+ }
24
+ if (typeof value === 'number') {
25
+ return [value, undefined]
26
+ }
27
+ const s = String(value).trim()
28
+ const match = s.match(/^(-?\d*\.?\d+)\s*(.*)$/)
29
+ if (!match) {
30
+ return [Number.NaN, undefined]
31
+ }
32
+ const num = Number.parseFloat(match[1] ?? '')
33
+ const unit = (match[2] ?? '').trim()
34
+ return [num, unit === '' ? undefined : unit]
35
+ }
@@ -1,8 +1,9 @@
1
1
  /**
2
2
  * Converts pixel values to numbers.
3
+ * Alias for {@link parseCssNumber}. Passes through null and undefined.
3
4
  *
4
5
  * @param px - The pixel value to convert. Can be a number or string (e.g. '16px' or '16')
5
- * @returns The numeric value
6
+ * @returns The numeric value, or null/undefined when input is null/undefined
6
7
  *
7
8
  * @example
8
9
  * ```ts
@@ -10,8 +11,8 @@
10
11
  * px2num('32px') // 32
11
12
  * px2num('12.5px') // 12.5
12
13
  * px2num('0px') // 0
14
+ * px2num(null) // null
15
+ * px2num(undefined) // undefined
13
16
  * ```
14
17
  */
15
- export function px2num(px: number | string | undefined): number {
16
- return typeof px === 'string' ? Number.parseFloat(px.replace(/px$/, '')) : Number(px)
17
- }
18
+ export { parseCssNumber as px2num } from './parse-css-number.ts'
@@ -5,20 +5,24 @@
5
5
  * @param options - Optional configuration
6
6
  * @param options.base - Base pixel value to calculate rem units from. Defaults to 16
7
7
  * @param options.precision - Number of decimal places in the output. Defaults to 4
8
- * @returns The converted value as a string with 'rem' units
8
+ * @returns The converted value, or null/undefined if input is null/undefined
9
9
  *
10
10
  * @example
11
11
  * ```ts
12
- * px2rem(16) // '1.0000'
13
- * px2rem('32px') // '2.0000'
14
- * px2rem(20, { base: 20 }) // '1.0000'
15
- * px2rem(13, { precision: 2 }) // '0.81'
12
+ * px2rem(16) // 1
13
+ * px2rem('32px') // 2
14
+ * px2rem(20, { base: 20 }) // 1
15
+ * px2rem(13, { precision: 2 }) // 0.81
16
+ * px2rem(null) // null
17
+ * px2rem(undefined) // undefined
16
18
  * ```
17
19
  */
18
20
  export function px2rem(
19
- px: number | string,
20
- options?: { base?: number | undefined; precision?: number | undefined }
21
- ): number {
21
+ px: number | string | null | undefined,
22
+ options?: { base?: number | undefined; precision?: number | undefined } | undefined
23
+ ): number | null | undefined {
24
+ if (px === null || px === undefined) return px
25
+
22
26
  const { base = 16, precision = 4 } = options ?? {}
23
27
 
24
28
  if (typeof px === 'string') {
@@ -5,26 +5,28 @@
5
5
  * @param options - Optional configuration
6
6
  * @param options.base - Base pixel value to calculate pixels from. Defaults to 16
7
7
  * @param options.precision - Number of decimal places in the output. Defaults to 4
8
- * @returns The converted value as a string with 'px' units
8
+ * @returns The converted value, or null/undefined if input is null/undefined
9
9
  *
10
10
  * @example
11
11
  * ```ts
12
- * rem2px(1) // '16.0000'
13
- * rem2px('2rem') // '32.0000'
14
- * rem2px(1, { base: 20 }) // '20.0000'
15
- * rem2px(0.8125, { precision: 2 }) // '13.00'
12
+ * rem2px(1) // 16
13
+ * rem2px('2rem') // 32
14
+ * rem2px(1, { base: 20 }) // 20
15
+ * rem2px(0.8125, { precision: 2 }) // 13
16
+ * rem2px(null) // null
17
+ * rem2px(undefined) // undefined
16
18
  * ```
17
19
  */
18
20
  export function rem2px(
19
- rem: number | string,
21
+ rem: number | string | null | undefined,
20
22
  options?: { base?: number | undefined; precision?: number | undefined }
21
- ): number {
22
- const { base = 16, precision = 4 } = options ?? {}
23
+ ): number | null | undefined {
24
+ if (rem === null || rem === undefined) return rem
23
25
 
26
+ const { base = 16, precision = 4 } = options ?? {}
24
27
  if (typeof rem === 'string') {
25
28
  rem = rem.replace(/rem$/, '')
26
29
  rem = Number.parseFloat(rem)
27
30
  }
28
-
29
31
  return Number((rem * base).toFixed(precision))
30
32
  }
@@ -1,23 +0,0 @@
1
-
2
- //#region src/units/px-2-num.ts
3
- /**
4
- * Converts pixel values to numbers.
5
- *
6
- * @param px - The pixel value to convert. Can be a number or string (e.g. '16px' or '16')
7
- * @returns The numeric value
8
- *
9
- * @example
10
- * ```ts
11
- * px2num(16) // 16
12
- * px2num('32px') // 32
13
- * px2num('12.5px') // 12.5
14
- * px2num('0px') // 0
15
- * ```
16
- */
17
- function px2num(px) {
18
- return typeof px === "string" ? Number.parseFloat(px.replace(/px$/, "")) : Number(px);
19
- }
20
-
21
- //#endregion
22
- exports.px2num = px2num;
23
- //# sourceMappingURL=px-2-num.cjs.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"px-2-num.cjs","names":[],"sources":["../../src/units/px-2-num.ts"],"sourcesContent":["/**\n * Converts pixel values to numbers.\n *\n * @param px - The pixel value to convert. Can be a number or string (e.g. '16px' or '16')\n * @returns The numeric value\n *\n * @example\n * ```ts\n * px2num(16) // 16\n * px2num('32px') // 32\n * px2num('12.5px') // 12.5\n * px2num('0px') // 0\n * ```\n */\nexport function px2num(px: number | string | undefined): number {\n\treturn typeof px === 'string' ? Number.parseFloat(px.replace(/px$/, '')) : Number(px)\n}\n"],"mappings":";;;;;;;;;;;;;;;;AAcA,SAAgB,OAAO,IAAyC;AAC/D,QAAO,OAAO,OAAO,WAAW,OAAO,WAAW,GAAG,QAAQ,OAAO,GAAG,CAAC,GAAG,OAAO,GAAG"}
@@ -1,19 +0,0 @@
1
- //#region src/units/px-2-num.d.ts
2
- /**
3
- * Converts pixel values to numbers.
4
- *
5
- * @param px - The pixel value to convert. Can be a number or string (e.g. '16px' or '16')
6
- * @returns The numeric value
7
- *
8
- * @example
9
- * ```ts
10
- * px2num(16) // 16
11
- * px2num('32px') // 32
12
- * px2num('12.5px') // 12.5
13
- * px2num('0px') // 0
14
- * ```
15
- */
16
- declare function px2num(px: number | string | undefined): number;
17
- //#endregion
18
- export { px2num };
19
- //# sourceMappingURL=px-2-num.d.cts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"px-2-num.d.cts","names":[],"sources":["../../src/units/px-2-num.ts"],"sourcesContent":[],"mappings":";;AAcA;;;;;;;;;;;;;iBAAgB,MAAA"}
@@ -1,19 +0,0 @@
1
- //#region src/units/px-2-num.d.ts
2
- /**
3
- * Converts pixel values to numbers.
4
- *
5
- * @param px - The pixel value to convert. Can be a number or string (e.g. '16px' or '16')
6
- * @returns The numeric value
7
- *
8
- * @example
9
- * ```ts
10
- * px2num(16) // 16
11
- * px2num('32px') // 32
12
- * px2num('12.5px') // 12.5
13
- * px2num('0px') // 0
14
- * ```
15
- */
16
- declare function px2num(px: number | string | undefined): number;
17
- //#endregion
18
- export { px2num };
19
- //# sourceMappingURL=px-2-num.d.mts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"px-2-num.d.mts","names":[],"sources":["../../src/units/px-2-num.ts"],"sourcesContent":[],"mappings":";;AAcA;;;;;;;;;;;;;iBAAgB,MAAA"}