css-calipers 0.3.0 → 0.5.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/README.md CHANGED
@@ -37,7 +37,7 @@ import { m } from "css-calipers";
37
37
 
38
38
  // Declare vars
39
39
  const paddingBase = m(4); // defaults to "px" with no unit specified
40
- const rotation = m(45, "deg"); // equivalent to a dedicated helper like mDeg(45)
40
+ const rotation = m(45, "deg"); // equivalent to a dedicated helper: mDeg(45)
41
41
 
42
42
  // Do safe arithmetic
43
43
  const margins = paddingBase.add(4);
@@ -50,6 +50,8 @@ const style = {
50
50
  };
51
51
  ```
52
52
 
53
+ If you prefer, you can also import unit helpers from dedicated subpaths. For example, `mPercent` is available from the root entrypoint and from `css-calipers/units/percent`, and all unit helpers are aggregated under `css-calipers/units`.
54
+
53
55
  ---
54
56
 
55
57
  ## Features
@@ -73,7 +75,11 @@ CSS-Calipers focuses exclusively on numeric, unit-bearing CSS values. Keywords
73
75
  like `auto`, `fit-content`, or `max-content`, full shorthand strings,
74
76
  `var(--token)`, or `calc(...)` expressions should remain explicit strings or
75
77
  dedicated keyword types in your app or styling layer. Everything else stays as
76
- plain CSS (see "Philosophy & Boundaries" below for more detail).
78
+ plain CSS (see "Philosophy & Boundaries" below for more detail). For a concrete
79
+ example of this separation in a mixed-input helper, see
80
+ [examples/lineHeight-normalizer.example.ts](examples/lineHeight-normalizer.example.ts),
81
+ which keeps keywords and CSS variables as plain strings while using measurements
82
+ for numeric values.
77
83
 
78
84
  ---
79
85
 
@@ -96,7 +102,7 @@ It’s probably overkill if:
96
102
  ### Layout tokens example
97
103
 
98
104
  ```ts
99
- import { m, mPercent, mVw, mVh, mFr, assertCondition } from "css-calipers";
105
+ import { m, mPercent, mVw, mVh, assertCondition } from "css-calipers";
100
106
 
101
107
  // Token-style measurements (px by default)
102
108
  const spacing = m(8); // Defaults to px; equivalent to mPx(8)
@@ -142,7 +148,8 @@ if (process.env.NODE_ENV !== "production") {
142
148
  const cardGridStyles = {
143
149
  display: "grid",
144
150
  gap: gutter.css(),
145
- gridTemplateColumns: `repeat(${columns}, ${mFr(1).css()})`,
151
+ // Keep fraction units as plain CSS alongside measurement-derived values
152
+ gridTemplateColumns: `repeat(${columns}, 1fr)`,
146
153
  // width driven by card width + gutters
147
154
  width: cardWidth
148
155
  .multiply(columns)
@@ -152,7 +159,7 @@ const cardGridStyles = {
152
159
  minWidth: minWidthPercent.css(),
153
160
  maxWidth: maxWidthViewport.css(),
154
161
  // derived hero height based on px ratio, expressed in vh and used inside a calc() string
155
- // calc() stays plain CSS; css-calipers only provides the numeric pieces
162
+ // calc() stays plain CSS; CSS-Calipers only provides the numeric pieces
156
163
  minHeight: `calc(${heroHeight.css()} + 10vh)`,
157
164
  };
158
165
  ```
@@ -163,7 +170,7 @@ const cardGridStyles = {
163
170
 
164
171
  CSS-Calipers will happily enforce units anywhere you choose, but it stays
165
172
  unopinionated about where those guards live. Drop assertions in a component, in
166
- a theme overwrite, hardcode a debug routine, or wire a global invariant; the
173
+ a theme override, hardcode a debug routine, or wire a global invariant; the
167
174
  structure is up to you:
168
175
 
169
176
  ```ts
@@ -179,9 +186,15 @@ if (process.env.NODE_ENV !== "production") {
179
186
  }
180
187
  ```
181
188
 
182
- You can apply the same checks globally (e.g., during app bootstrap) or only
183
- inside the components that need them. CSS-Calipers gives you the tools;
184
- placement is a design decision.
189
+ You can apply the same checks globally (e.g., during app bootstrap), only
190
+ inside the components that need them, or in dedicated test helpers. For more
191
+ complete patterns, see the examples folder: the validation unit-tests example
192
+ ([examples/validation-unit-tests.example.ts](examples/validation-unit-tests.example.ts)) shows how to
193
+ enforce spacing token invariants in a test suite, and the validation and runtime
194
+ checks example ([examples/validation-and-runtime-checks.example.ts](examples/validation-and-runtime-checks.example.ts))
195
+ shows how to apply dev-only guards around shared tokens in two different
196
+ consumers (an HTML snippet and a style object) using the same line-height
197
+ measurement.
185
198
 
186
199
  ---
187
200
 
@@ -189,7 +202,7 @@ placement is a design decision.
189
202
 
190
203
  - Operations are fail-fast: when you call helpers like `add`, `divide`, `clamp`,
191
204
  `measurementMin` / `measurementMax`, or the assertion helpers with invalid
192
- input (for example, mismatched units or non-finite values), css-calipers
205
+ input (for example, mismatched units or non-finite values), CSS-Calipers
193
206
  throws a normal `Error`.
194
207
  - Error messages include the operation name (for example,
195
208
  `css-calipers.Measurement.divide` or `css-calipers.assertMatchingUnits`), the
@@ -200,34 +213,23 @@ placement is a design decision.
200
213
  (such as `if (process.env.NODE_ENV !== 'production')`) or to enforce
201
214
  invariants in tests, so checks stay cheap and predictable at runtime.
202
215
 
216
+ For concrete uses of these errors in tests and dev-only guards, see
217
+ `TESTING.md` and the validation examples in
218
+ [examples/validation-unit-tests.example.ts](examples/validation-unit-tests.example.ts) and
219
+ [examples/validation-and-runtime-checks.example.ts](examples/validation-and-runtime-checks.example.ts).
220
+
203
221
  ---
204
222
 
205
223
  ## Co-existing with other systems
206
224
 
207
- You don’t have to convert everything at once, or at all. If it fits your setup, you can write small adapters that accept existing CSS strings, css-calipers measurements, or plain numbers and turn them into CSS values. The example below is just one possible adapter pattern, not a recommendation or default.
208
-
209
- ```ts
210
- import { m, isMeasurement } from "css-calipers";
211
-
212
- type SpacingInput = string | number | ReturnType<typeof m>;
213
-
214
- const toSpacingCss = (value: SpacingInput): string => {
215
- if (typeof value === "string") {
216
- // Already a CSS value (for example, "auto" or "var(--gap)")
217
- return value;
218
- }
219
-
220
- const measurement = isMeasurement(value) ? value : m(value);
221
- return measurement.css();
222
- };
223
-
224
- // Later, callers can pass tokens, raw CSS strings, or measurements:
225
- toSpacingCss("var(--card-gap)");
226
- toSpacingCss(8); // becomes "8px"
227
- toSpacingCss(m(12, "px"));
228
- ```
229
-
230
- ---
225
+ You don’t have to convert everything at once, or at all. If it fits your setup,
226
+ you can write small adapters that accept existing CSS strings, CSS-Calipers
227
+ measurements, or plain numbers and turn them into CSS values. CSS-Calipers can
228
+ be dropped into an existing styling system or used from the ground up; it
229
+ focuses narrowly on numeric, unit-bearing values and leaves the rest of your
230
+ styling approach up to you. For a more realistic adapter pattern that
231
+ normalizes mixed inputs (including CSS variables) into a single css-like value,
232
+ see the line-height normalizer example referenced below.
231
233
 
232
234
  ## Advanced
233
235
 
@@ -272,7 +274,7 @@ keywords for symbolic CSS values, without reintroducing vague unions like
272
274
 
273
275
  - **Measurement math lives here; string composition lives elsewhere.**
274
276
  Use CSS-Calipers for unit-aware calculations, then hand results to
275
- helpers/adapters that emit CSS literals. Keep `calc()`/`clamp()` logic outside
277
+ helpers/adapters that emit CSS literals. Keep `calc()`/`linear-gradient()` logic outside
276
278
  the library so measurement objects remain pure.
277
279
 
278
280
  - **`.css()` at runtime is an edge, not a habit.**
@@ -293,6 +295,42 @@ keywords for symbolic CSS values, without reintroducing vague unions like
293
295
 
294
296
  - **CSS custom properties coexist; they don’t mix.**
295
297
  Third-party primitives exposing `var(--token)` should keep those values as raw
296
- CSS strings. Feed CSS-Calipers `.css()` output into them where possible, but
297
- don’t wrap CSS variables inside the library; treat them as parallel pipes
298
- that meet in the style layer.
298
+ CSS strings. CSS-Calipers is intentionally narrow: it works with numeric
299
+ measurements and unit-safe conversions, not tokens or CSS variables. You can
300
+ still use `var(...)` and token strings anywhere in your styling system; they
301
+ just sit outside the library. If you want those values to flow through
302
+ CSS-Calipers, first extract the numeric value and unit in your own code and
303
+ then pass that measurement into the library.
304
+
305
+ ## Using CSS-Calipers in a larger styling system
306
+
307
+ CSS is inherently flexible: the same property can accept numbers, unit-bearing
308
+ strings, keywords, and CSS variables. CSS-Calipers is one focused piece of that
309
+ ecosystem. It keeps the numeric, unit-bearing parts typed and predictable, and
310
+ lets the rest of your styling system own tokens, variables, and higher-level
311
+ APIs.
312
+
313
+ For a worked example of this pattern, see
314
+ [examples/lineHeight-normalizer.example.ts](examples/lineHeight-normalizer.example.ts). It shows a helper that accepts a
315
+ `lineHeight` value from a CMS or configuration (numbers, numeric strings with
316
+ units, keywords like `"normal"`, or CSS variables such as
317
+ `"var(--body-line-height)"`) and normalizes them into a value with a `.css()`
318
+ method. CSS-Calipers only participates when there is a concrete measurement
319
+ (numbers and units); keywords and CSS variables remain plain CSS strings owned
320
+ by your styling layer. That’s the intended scope: CSS will always be a mix of
321
+ values, but the library gives you a tight, unit-safe boundary for the numeric
322
+ parts inside a broader styling solution.
323
+
324
+ ### Further examples in this repo
325
+
326
+ The `examples/` folder contains a few non-published usage sketches:
327
+
328
+ - [examples/lineHeight-normalizer.example.ts](examples/lineHeight-normalizer.example.ts) &mdash;
329
+ mixed input normalization for `lineHeight` (numbers, strings, CSS variables)
330
+ into a single value with a `.css()` method.
331
+ - [examples/validation-unit-tests.example.ts](examples/validation-unit-tests.example.ts) &mdash;
332
+ simple unit tests that enforce spacing token invariants (shared units and
333
+ small &le; large).
334
+ - [examples/validation-and-runtime-checks.example.ts](examples/validation-and-runtime-checks.example.ts) &mdash;
335
+ dev-only validation around shared tokens in two different consumers (HTML
336
+ string and style object) using the same line-height measurement.
package/RELEASING.md CHANGED
@@ -1,4 +1,4 @@
1
- # Releasing css-calipers
1
+ # Releasing CSS-Calipers
2
2
 
3
3
  This document describes how to publish a new version of `css-calipers` to npm
4
4
  using the local release script.
@@ -0,0 +1,29 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ __exportStar(require("./percent"), exports);
18
+ __exportStar(require("./absolute"), exports);
19
+ __exportStar(require("./font-relative"), exports);
20
+ __exportStar(require("./viewport"), exports);
21
+ __exportStar(require("./viewport-small"), exports);
22
+ __exportStar(require("./viewport-large"), exports);
23
+ __exportStar(require("./viewport-dynamic"), exports);
24
+ __exportStar(require("./container"), exports);
25
+ __exportStar(require("./angle"), exports);
26
+ __exportStar(require("./time"), exports);
27
+ __exportStar(require("./frequency"), exports);
28
+ __exportStar(require("./resolution"), exports);
29
+ __exportStar(require("./grid"), exports);
@@ -0,0 +1,14 @@
1
+ export * from './percent';
2
+ export * from './absolute';
3
+ export * from './font-relative';
4
+ export * from './viewport';
5
+ export * from './viewport-small';
6
+ export * from './viewport-large';
7
+ export * from './viewport-dynamic';
8
+ export * from './container';
9
+ export * from './angle';
10
+ export * from './time';
11
+ export * from './frequency';
12
+ export * from './resolution';
13
+ export * from './grid';
14
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/units/index.ts"],"names":[],"mappings":"AAAA,cAAc,WAAW,CAAC;AAC1B,cAAc,YAAY,CAAC;AAC3B,cAAc,iBAAiB,CAAC;AAChC,cAAc,YAAY,CAAC;AAC3B,cAAc,kBAAkB,CAAC;AACjC,cAAc,kBAAkB,CAAC;AACjC,cAAc,oBAAoB,CAAC;AACnC,cAAc,aAAa,CAAC;AAC5B,cAAc,SAAS,CAAC;AACxB,cAAc,QAAQ,CAAC;AACvB,cAAc,aAAa,CAAC;AAC5B,cAAc,cAAc,CAAC;AAC7B,cAAc,QAAQ,CAAC"}
@@ -0,0 +1,13 @@
1
+ export * from './percent';
2
+ export * from './absolute';
3
+ export * from './font-relative';
4
+ export * from './viewport';
5
+ export * from './viewport-small';
6
+ export * from './viewport-large';
7
+ export * from './viewport-dynamic';
8
+ export * from './container';
9
+ export * from './angle';
10
+ export * from './time';
11
+ export * from './frequency';
12
+ export * from './resolution';
13
+ export * from './grid';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "css-calipers",
3
- "version": "0.3.0",
3
+ "version": "0.5.0",
4
4
  "description": "Compile-time unit safety for numeric, unit-bearing CSS values via typed measurements.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -42,10 +42,22 @@
42
42
  "import": "./dist/esm/index.js",
43
43
  "require": "./dist/cjs/index.js"
44
44
  },
45
+ "./units": {
46
+ "types": "./dist/esm/units/index.d.ts",
47
+ "import": "./dist/esm/units/index.js",
48
+ "require": "./dist/cjs/units/index.js"
49
+ },
50
+ "./units/*": {
51
+ "types": "./dist/esm/units/*.d.ts",
52
+ "import": "./dist/esm/units/*.js",
53
+ "require": "./dist/cjs/units/*.js"
54
+ },
45
55
  "./package.json": "./package.json"
46
56
  },
47
57
  "devDependencies": {
58
+ "@types/node": "^24.10.1",
48
59
  "@vitest/coverage-v8": "^4.0.14",
60
+ "csstype": "^3.1.3",
49
61
  "tsd": "^0.31.0",
50
62
  "typescript": "^5.6.3",
51
63
  "vitest": "^4.0.14"
@@ -59,12 +71,12 @@
59
71
  "prepublishOnly": "node -e \"if (!process.env.CSS_CALIPERS_RELEASE) { console.error('Direct npm publish is disabled; use \\\"npm run release\\\" instead.'); process.exit(1); }\"",
60
72
  "release": "node scripts/release.mjs",
61
73
  "release:dry": "node scripts/release.mjs --dry-run",
62
- "test": "vitest tests/core.test.ts",
63
- "test:core": "vitest run tests/core.test.ts",
64
- "test:cjs": "vitest run tests/core.cjs.test.ts",
65
- "test:esm": "vitest run tests/core.esm.test.ts",
74
+ "test": "vitest tests/runtime/core/core.src.test.ts",
75
+ "test:core": "vitest run tests/runtime/core/core.src.test.ts",
76
+ "test:cjs": "vitest run tests/runtime/core/core.cjs.test.ts tests/runtime/api-surface/api-surface.cjs.test.ts",
77
+ "test:esm": "vitest run tests/runtime/core/core.esm.test.ts tests/runtime/api-surface/api-surface.esm.test.ts",
66
78
  "test:dist": "npm run test:cjs && npm run test:esm",
67
- "test:all": "npm run test:core && npm run test:dist",
79
+ "test:all": "npm run test:core && npm run test:dist && npm run test:types",
68
80
  "test:types": "echo \"\n 🧪 Starting tsd type checks...\n\" && tsd --files tests/types/**/*.test-d.ts && echo \"✅ tsd type checks passed!\n\""
69
81
  }
70
82
  }