@csszyx/runtime 0.9.10 → 0.10.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.mts CHANGED
@@ -43,53 +43,6 @@ type SzInput = string | SzObject | SzInput[] | null | undefined | false;
43
43
  * ```
44
44
  */
45
45
  declare function _sz(...classes: SzInput[]): string;
46
- /**
47
- * Conditionally applies className based on a condition.
48
- *
49
- * Supports both pre-compiled strings and SzObjects for dynamic styling.
50
- * This is the recommended helper for conditional class application.
51
- *
52
- * @param {boolean} condition - Whether to apply the truthy value
53
- * @param {SzInput} truthyValue - ClassName or SzObject when condition is true
54
- * @param {SzInput} falsyValue - ClassName or SzObject when condition is false
55
- * @returns {string} The resolved className string
56
- *
57
- * @example
58
- * ```typescript
59
- * // With strings (pre-compiled)
60
- * _szIf(isActive, 'bg-green-500', 'bg-gray-500')
61
- * // Returns: "bg-green-500" if isActive, "bg-gray-500" otherwise
62
- *
63
- * // With SzObjects (runtime transform)
64
- * _szIf(isActive, { bg: 'green-500' }, { bg: 'gray-500' })
65
- * // Returns: "bg-green-500" if isActive, "bg-gray-500" otherwise
66
- *
67
- * // Without fallback
68
- * _szIf(isActive, { bg: 'green-500' })
69
- * // Returns: "bg-green-500" if isActive, "" otherwise
70
- * ```
71
- */
72
- declare function _szIf(condition: boolean, truthyValue: SzInput, falsyValue?: SzInput): string;
73
- /**
74
- * Applies className based on multiple conditions (switch-like).
75
- *
76
- * Returns the className for the first truthy condition, or the default.
77
- * Supports both strings and SzObjects.
78
- *
79
- * @param {Array<[boolean, SzInput]>} conditions - Array of [condition, value] tuples
80
- * @param {SzInput} defaultValue - Default value if no conditions match
81
- * @returns {string} The matched className or default
82
- *
83
- * @example
84
- * ```typescript
85
- * _szSwitch([
86
- * [status === 'success', { text: 'green-500' }],
87
- * [status === 'error', { text: 'red-500' }],
88
- * [status === 'warning', { text: 'yellow-500' }]
89
- * ], { text: 'gray-500' })
90
- * ```
91
- */
92
- declare function _szSwitch(conditions: Array<[boolean, SzInput]>, defaultValue?: SzInput): string;
93
46
  /**
94
47
  * Merges className strings, removing duplicates.
95
48
  *
@@ -349,19 +302,19 @@ declare function disableCSRRecovery(): void;
349
302
  */
350
303
  declare function isCSRRecoveryAllowed(): boolean;
351
304
  /**
352
- * Loads the mangle map from the DOM.
305
+ * Validate a parsed mangle map before it is used to rewrite class names. The map
306
+ * is read from the DOM (attacker-writable if the served HTML is compromised), so
307
+ * it must be a plain object of string→string with no prototype-polluting keys.
308
+ * A malformed map is rejected rather than applied.
353
309
  *
354
- * Searches for the embedded mangle map script tag and parses it.
355
- *
356
- * @returns {MangleMap | null} The mangle map or null if not found
310
+ * @param value - the `JSON.parse`d candidate.
311
+ * @returns true when `value` is a safe `MangleMap`.
312
+ */
313
+ declare function isValidMangleMap(value: unknown): value is MangleMap;
314
+ /**
315
+ * Load and validate the mangle map embedded in the DOM by the SSR pipeline.
357
316
  *
358
- * @example
359
- * ```typescript
360
- * const mangleMap = loadMangleMapFromDOM();
361
- * if (mangleMap) {
362
- * console.log('Mangle map loaded');
363
- * }
364
- * ```
317
+ * @returns the validated mangle map, or `null` if absent or invalid.
365
318
  */
366
319
  declare function loadMangleMapFromDOM(): MangleMap | null;
367
320
  /**
@@ -382,20 +335,53 @@ declare function loadMangleMapFromDOM(): MangleMap | null;
382
335
  */
383
336
  declare function verifyMangleChecksum(expectedChecksum: string): boolean;
384
337
  /**
385
- * Verifies mangle map integrity using Rust core verification.
338
+ * Recompute a mangle map's checksum the same way the Rust core does
339
+ * (`compute_checksum_internal`): SHA-256 over the entries sorted by key and
340
+ * formatted `orig:mangle`, joined by `|`, hex-encoded, first 16 chars (64 bits).
386
341
  *
387
- * Loads the mangle map from DOM and verifies its checksum using the
388
- * Rust core's `verify_mangle_checksum()` function for cryptographic-grade verification.
342
+ * Uses the Web Crypto API (`crypto.subtle`), so it works WITHOUT the WASM core —
343
+ * unlike the WASM-only sync path. Async because `crypto.subtle.digest` is async.
389
344
  *
390
- * @returns {boolean} True if mangle map integrity is verified
345
+ * @param map - the mangle map to checksum.
346
+ * @returns the 16-char hex checksum (matches the Rust output byte-for-byte for
347
+ * ASCII class names).
348
+ */
349
+ declare function computeMangleChecksumAsync(map: MangleMap): Promise<string>;
350
+ /**
351
+ * Verify a mangle map's integrity by RECOMPUTING its checksum (via Web Crypto)
352
+ * and comparing it to the expected value — real verification that works without
353
+ * the WASM core. Unlike the sync {@link verifyMangleChecksum} (a plain attribute
354
+ * compare), this recomputes from the map, so it detects a map that was altered
355
+ * without updating the checksum.
391
356
  *
392
- * @example
393
- * ```typescript
394
- * if (!verifyMangleMapIntegrity()) {
395
- * console.error('[csszyx] Mangle map integrity check failed');
396
- * abortHydration();
397
- * }
398
- * ```
357
+ * Still tamper-DETECTION, not authentication: the checksum is unsigned, so an
358
+ * attacker who can rewrite the served HTML can recompute it after editing the
359
+ * map. Use it to catch corruption / accidental drift, not as a trust boundary.
360
+ *
361
+ * @param expectedChecksum - the checksum to match (e.g. from the manifest or the
362
+ * `data-sz-checksum` attribute).
363
+ * @param map - the mangle map; loaded (and schema-validated) from the DOM when omitted.
364
+ * @returns a promise resolving true when the map is present and its recomputed
365
+ * checksum matches.
366
+ */
367
+ declare function verifyMangleChecksumAsync(expectedChecksum: string, map?: MangleMap): Promise<boolean>;
368
+ /**
369
+ * Verifies mangle map integrity using the Rust core checksum verifier.
370
+ *
371
+ * Loads the mangle map from the DOM, validates its shape, and (when the WASM
372
+ * core is present) verifies its checksum via `verify_mangle_checksum()`.
373
+ *
374
+ * IMPORTANT — this is tamper-DETECTION, not authentication. The checksum is an
375
+ * unsigned SHA-256 truncation: it catches accidental server/client drift, but an
376
+ * attacker who can rewrite the served HTML can recompute it after editing the
377
+ * map. Treat it as an integrity (corruption) check, not a trust boundary.
378
+ *
379
+ * When the WASM verifier is absent (the common no-core deployment), the checksum
380
+ * cannot be recomputed in this sync path, so verification degrades to a
381
+ * schema-validated load and emits a dev warning rather than failing closed
382
+ * (failing closed here would break every app not shipping the WASM core).
383
+ *
384
+ * @returns {boolean} True if the map is well-formed and (if verifiable) matches.
399
385
  */
400
386
  declare function verifyMangleMapIntegrity(): boolean;
401
387
  /**
@@ -612,6 +598,200 @@ declare function startHydration(): void;
612
598
  */
613
599
  declare function endHydration(): void;
614
600
 
601
+ /** Which side of the CSS box-model border a property acts on. */
602
+ type BoxRole = 'outer' | 'inner';
603
+
604
+ /**
605
+ * Route a (csszyx-emitted) className string to nested elements by CSS box-model
606
+ * role, plus a category-aware toolkit that exposes csszyx's class knowledge as
607
+ * primitives. Pure string functions — framework-agnostic, cross-platform, no
608
+ * React, no DOM. The class-token → box-role map is GENERATED from the compiler's
609
+ * property tables (see `box-role-map.generated.ts`), so it never drifts.
610
+ *
611
+ * The problem this solves: a caller passes one flat `className` (e.g. from an sz
612
+ * prop) to a component, but the component renders nested elements — the margin
613
+ * belongs on the outer frame while the padding belongs on the inner content. A
614
+ * slot recipe can't re-route a caller's flat string; only a runtime partition
615
+ * can. `splitBox` does that partition at the border line (outer = border-outward,
616
+ * inner = border-inward); the toolkit lets a project express cross-element
617
+ * dependency rules (e.g. "if the frame clips, make the scroller scroll") without
618
+ * hardcoding csszyx's class vocabulary.
619
+ */
620
+
621
+ /** The classification of a single class token. */
622
+ interface Classification {
623
+ /** Which side of the border the property acts on. */
624
+ readonly role: BoxRole;
625
+ /** Semantic group (margin, padding, border, overflow, text, …). */
626
+ readonly category: string;
627
+ }
628
+ /**
629
+ * A way to address a set of classes. One of:
630
+ * - a box-role: `'outer'` | `'inner'`
631
+ * - a box-layer alias: `'content'` (= inner) (`'margin'`/`'border'`/`'padding'`
632
+ * are also categories, so they work directly)
633
+ * - a category: `'overflow'`, `'text'`, `'bg'`, …
634
+ * - a class-prefix: `'px'`, `'bg'`, … (matches `px-2`, `bg-red-500`, …)
635
+ * - a category+value pair: `{ overflow: 'hidden' }`
636
+ */
637
+ type BoxSelector = string | Readonly<Record<string, string>>;
638
+ /** Options controlling how `splitBox` partitions a className. */
639
+ interface SplitBoxOptions {
640
+ /** Force these selectors onto the outer node, overriding the default map. */
641
+ outer?: BoxSelector[];
642
+ /** Force these selectors onto the inner node, overriding the default map. */
643
+ inner?: BoxSelector[];
644
+ /** Where an unrecognized token goes. Defaults to `'outer'`. */
645
+ fallback?: BoxRole;
646
+ }
647
+ /** The two class buckets produced by `splitBox`. */
648
+ interface SplitBoxResult {
649
+ /** Classes for the outer (border-outward) element. */
650
+ outer: string;
651
+ /** Classes for the inner (border-inward) element. */
652
+ inner: string;
653
+ }
654
+ /**
655
+ * Classify a class token by box-model role + semantic category, or `undefined`
656
+ * if it is not a csszyx-owned utility. Variant-, important- and negative-aware.
657
+ *
658
+ * @param token - A single class token to classify.
659
+ * @returns The token's role and category, or `undefined` if unowned.
660
+ */
661
+ declare function classify(token: string): Classification | undefined;
662
+ /**
663
+ * Partition a className string into `{ outer, inner }` at the CSS box-model
664
+ * border line. Every token lands in exactly one bucket (no loss, no duplication)
665
+ * and keeps its variant prefix. Overrides in `options.inner` / `options.outer`
666
+ * win over the default map; `inner` is checked first when a token matches both.
667
+ *
668
+ * @param className - The flat className string to partition.
669
+ * @param options - Overrides for forcing tokens onto a node and the fallback role.
670
+ * @returns The `{ outer, inner }` class buckets.
671
+ * @example splitBox('m-4 px-2 md:flex') // → { outer: 'm-4', inner: 'px-2 md:flex' }
672
+ */
673
+ declare function splitBox(className: string, options?: SplitBoxOptions): SplitBoxResult;
674
+ /**
675
+ * Does any token in `classes` match `selector`? Variant- and mangle-robust.
676
+ *
677
+ * @param classes - A className string to scan.
678
+ * @param selector - The selector to test tokens against.
679
+ * @returns `true` if any token matches the selector.
680
+ */
681
+ declare function has(classes: string, selector: BoxSelector): boolean;
682
+ /**
683
+ * Keep only the tokens in `classes` that match `selector`.
684
+ *
685
+ * @param classes - A className string to filter.
686
+ * @param selector - The selector tokens must match to be kept.
687
+ * @returns The matching tokens joined by spaces.
688
+ */
689
+ declare function pick(classes: string, selector: BoxSelector): string;
690
+ /**
691
+ * Drop the tokens in `classes` that match `selector`, keeping the rest.
692
+ *
693
+ * @param classes - A className string to filter.
694
+ * @param selector - The selector tokens must match to be dropped.
695
+ * @returns The non-matching tokens joined by spaces.
696
+ */
697
+ declare function omit(classes: string, selector: BoxSelector): string;
698
+ /** Options controlling how `splitBoxSz` partitions an sz object. */
699
+ interface SplitBoxSzOptions {
700
+ /** Force these selectors onto the outer object, overriding the default map. */
701
+ outer?: BoxSelector[];
702
+ /** Force these selectors onto the inner object, overriding the default map. */
703
+ inner?: BoxSelector[];
704
+ /** Where an unrecognized key goes. Defaults to `'outer'`. */
705
+ fallback?: BoxRole;
706
+ }
707
+ /** The two sz-object buckets produced by `splitBoxSz`. */
708
+ interface SplitBoxSzResult {
709
+ /** sz for the outer (border-outward) element. */
710
+ outer: SzObject;
711
+ /** sz for the inner (border-inward) element. */
712
+ inner: SzObject;
713
+ }
714
+ /**
715
+ * Classify an sz prop key by box-model role + semantic category, or `undefined`
716
+ * if it is not a csszyx-owned key. The sz-object analog of `classify` (which
717
+ * takes an emitted class token) — both read the same generated map, so
718
+ * `classifySzKey('m')` and `classify('m-4')` agree.
719
+ *
720
+ * @param key - An sz prop key (e.g. `'m'`, `'px'`, `'grow'`).
721
+ * @returns The key's role and category, or `undefined` if unowned.
722
+ */
723
+ declare function classifySzKey(key: string): Classification | undefined;
724
+ /**
725
+ * Partition an sz object (or any `SzInput`) into `{ outer, inner }` at the CSS
726
+ * box-model border line — the sz-object analog of `splitBox`. Each key routes to
727
+ * the same side its emitted class would (`splitBoxSz(x)` buckets keys to the
728
+ * roles `splitBox(compile(x))` gives the emitted classes). Arrays are flattened,
729
+ * `null`/`false` collapse to empty objects, and `options.inner`/`outer`/
730
+ * `fallback` behave like `SplitBoxOptions` (`inner` wins when a key matches both).
731
+ *
732
+ * @param sz - The sz object / input to partition.
733
+ * @param options - Overrides for forcing keys onto a node and the fallback role.
734
+ * @returns The `{ outer, inner }` sz-object buckets.
735
+ * @example splitBoxSz({ m: 4, px: 2 }) // → { outer: { m: 4 }, inner: { px: 2 } }
736
+ */
737
+ declare function splitBoxSz(sz: SzInput, options?: SplitBoxSzOptions): SplitBoxSzResult;
738
+ /**
739
+ * Does any key in `sz` match `selector` (after flattening, recursing into
740
+ * variants)? The sz-object analog of `has`.
741
+ *
742
+ * @param sz - The sz input to scan.
743
+ * @param selector - The selector to test keys against.
744
+ * @returns `true` if any key matches.
745
+ */
746
+ declare function hasSz(sz: SzInput, selector: BoxSelector): boolean;
747
+ /**
748
+ * Keep only the keys in `sz` that match `selector` (recursing into variants).
749
+ * The sz-object analog of `pick`.
750
+ *
751
+ * @param sz - The sz input to filter.
752
+ * @param selector - The selector keys must match to be kept.
753
+ * @returns A new sz object with the matching keys.
754
+ */
755
+ declare function pickSz(sz: SzInput, selector: BoxSelector): SzObject;
756
+ /**
757
+ * Drop the keys in `sz` that match `selector`, keeping the rest (recursing into
758
+ * variants). The sz-object analog of `omit`.
759
+ *
760
+ * @param sz - The sz input to filter.
761
+ * @param selector - The selector keys must match to be dropped.
762
+ * @returns A new sz object with the non-matching keys.
763
+ */
764
+ declare function omitSz(sz: SzInput, selector: BoxSelector): SzObject;
765
+
766
+ /**
767
+ * Drop the `sz` prop before a component spreads `...rest` onto a host element.
768
+ *
769
+ * The compiler rewrites `sz` to `className` at build time, so a compiled
770
+ * component never carries a leftover `sz` prop. But when a file is NOT compiled
771
+ * — e.g. a workspace package missing from `compilePackages`, or any source the
772
+ * bundler skipped — a hand-forwarded `sz` survives and React spreads it to the
773
+ * DOM as `sz="[object Object]"`. `stripSzProps` removes `sz` from the forwarded
774
+ * props so it never reaches the DOM, and in development warns once when the
775
+ * leaked `sz` is a raw object, pointing at the real cause (an uncompiled file).
776
+ *
777
+ * Pure, framework-agnostic, no React/DOM import.
778
+ *
779
+ * @example
780
+ * function Box({ sz, ...rest }: BoxProps) {
781
+ * return <div {...stripSzProps(rest)} />; // rest may still carry sz when uncompiled
782
+ * }
783
+ */
784
+ /**
785
+ * Return `props` without its `sz` key. When `sz` is absent the original object
786
+ * is returned unchanged (no allocation); otherwise a shallow copy without `sz`
787
+ * is returned. In development, a raw-object `sz` (the uncompiled-leak signature)
788
+ * triggers a one-time warning.
789
+ *
790
+ * @param props - the props a component is about to forward to a host element.
791
+ * @returns the same props without `sz`.
792
+ */
793
+ declare function stripSzProps<T extends Record<string, unknown>>(props: T): Omit<T, 'sz'>;
794
+
615
795
  /**
616
796
  * szv() — variant-based sz object factory.
617
797
  *
@@ -632,9 +812,7 @@ type VariantSchema = Record<string, Record<string, SzObject>>;
632
812
  type VariantSelection<V extends VariantSchema> = {
633
813
  [K in keyof V]?: keyof V[K] | null | undefined;
634
814
  };
635
- /**
636
- *
637
- */
815
+ /** Configuration for a variant component: base styles, variants, and defaults. */
638
816
  interface SzvConfig<V extends VariantSchema> {
639
817
  base?: SzObject;
640
818
  variants: V;
@@ -655,7 +833,7 @@ interface SzvConfig<V extends VariantSchema> {
655
833
  * import { szv } from 'csszyx';
656
834
  *
657
835
  * const buttonSz = szv({
658
- * base: { inlineFlex: true, items: 'center', rounded: 'md', fontWeight: 'medium' },
836
+ * base: { display: 'inline-flex', items: 'center', rounded: 'md', weight: 'medium' },
659
837
  * variants: {
660
838
  * variant: {
661
839
  * default: { bg: 'primary', text: 'primary-foreground' },
@@ -769,5 +947,5 @@ declare function isRuntimeInitialized(): boolean;
769
947
  */
770
948
  declare function resetRuntime(): void;
771
949
 
772
- export { DEFAULT_RUNTIME_CONFIG, VERSION, _sz, _sz2, _sz3, _szIf, _szMerge, _szSwitch, abortHydration, attemptCSRRecovery, clearHydrationErrors, disableCSRRecovery, enableCSRRecovery, endHydration, getAbortedSubtreeCount, getHydrationErrors, getRecoveryMode, getRuntimeConfig, getSSRContext, guardHydration, hasRecoveryToken, initRuntime, isCSRRecoveryAllowed, isHydrating, isHydrationAborted, isRuntimeInitialized, isSSREnvironment, isValidManifest, loadMangleMapFromDOM, loadManifestFromDOM, resetRuntime, startHydration, szv, validateHydrationClass, verifyAllTokens, verifyMangleChecksum, verifyMangleMapIntegrity, verifyRecoveryToken };
773
- export type { HydrationError, HydrationErrorType, MangleMap, RecoveryManifest, RecoveryMode, RuntimeConfig, SSRContext, SzInput, TokenData, VerificationResult };
950
+ export { DEFAULT_RUNTIME_CONFIG, VERSION, _sz, _sz2, _sz3, _szMerge, abortHydration, attemptCSRRecovery, classify, classifySzKey, clearHydrationErrors, computeMangleChecksumAsync, disableCSRRecovery, enableCSRRecovery, endHydration, getAbortedSubtreeCount, getHydrationErrors, getRecoveryMode, getRuntimeConfig, getSSRContext, guardHydration, has, hasRecoveryToken, hasSz, initRuntime, isCSRRecoveryAllowed, isHydrating, isHydrationAborted, isRuntimeInitialized, isSSREnvironment, isValidMangleMap, isValidManifest, loadMangleMapFromDOM, loadManifestFromDOM, omit, omitSz, pick, pickSz, resetRuntime, splitBox, splitBoxSz, startHydration, stripSzProps, szv, validateHydrationClass, verifyAllTokens, verifyMangleChecksum, verifyMangleChecksumAsync, verifyMangleMapIntegrity, verifyRecoveryToken };
951
+ export type { BoxRole, BoxSelector, Classification, HydrationError, HydrationErrorType, MangleMap, RecoveryManifest, RecoveryMode, RuntimeConfig, SSRContext, SplitBoxOptions, SplitBoxResult, SplitBoxSzOptions, SplitBoxSzResult, SzInput, TokenData, VerificationResult };