@pyreon/unistyle 0.34.0 → 0.35.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/index.d.ts +263 -141
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +317 -52
- package/lib/index.js.map +1 -1
- package/package.json +7 -7
package/lib/index.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import { nativeCompat, provide } from "@pyreon/core";
|
|
2
|
-
import { ThemeContext } from "@pyreon/styler";
|
|
1
|
+
import { h, nativeCompat, provide } from "@pyreon/core";
|
|
2
|
+
import { ThemeContext, css, normalizeCSS, sheet, useThemeAccessor } from "@pyreon/styler";
|
|
3
3
|
import { Provider as Provider$1, config, context, isEmpty, set } from "@pyreon/ui-core";
|
|
4
|
+
import { renderEffect } from "@pyreon/reactivity";
|
|
4
5
|
|
|
5
6
|
//#region src/responsive/breakpoints.ts
|
|
6
7
|
const breakpoints = {
|
|
@@ -739,54 +740,114 @@ function resolveVarPass(s, registry) {
|
|
|
739
740
|
}
|
|
740
741
|
|
|
741
742
|
//#endregion
|
|
742
|
-
//#region src/
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
const
|
|
778
|
-
let
|
|
779
|
-
for (let i = 0; i <
|
|
780
|
-
|
|
781
|
-
|
|
743
|
+
//#region src/cpse.ts
|
|
744
|
+
/**
|
|
745
|
+
* Custom-Property Style Extraction (CPSE) — Phase 0 proof-of-concept primitive.
|
|
746
|
+
*
|
|
747
|
+
* See `.claude/audits/custom-property-style-extraction-2026-06-22.md` for the
|
|
748
|
+
* full RFC. The thesis: decouple a style prop's **CSS-rule identity** from its
|
|
749
|
+
* **value identity**. Instead of baking the resolved value into the rule —
|
|
750
|
+
*
|
|
751
|
+
* gap: 2.25rem // value-DEPENDENT rule → a new rule + a
|
|
752
|
+
* // `styler.resolve` per distinct value
|
|
753
|
+
* // (cost O(distinct value tuples), proven by
|
|
754
|
+
* // styler/__tests__/static-styler-resolve-cost)
|
|
755
|
+
*
|
|
756
|
+
* — emit a value-AGNOSTIC rule that reads a custom property, and deliver the
|
|
757
|
+
* value per-instance as an inline custom property —
|
|
758
|
+
*
|
|
759
|
+
* gap: var(--u-<hash>) // ONE shared rule, resolved ONCE per
|
|
760
|
+
* // definition; cost O(component definitions)
|
|
761
|
+
* style="--u-<hash>: 2.25rem" // per-instance, no resolve / no hash /
|
|
762
|
+
* // no new rule
|
|
763
|
+
*
|
|
764
|
+
* This makes styling cost **flat in app cardinality** and gives **dynamic
|
|
765
|
+
* (signal-driven) values for free** (update the inline custom property — no
|
|
766
|
+
* `styler.resolve`, no rule churn).
|
|
767
|
+
*
|
|
768
|
+
* Phase 0 scope: a single declaration / single property, to PROVE the
|
|
769
|
+
* mechanism (1 rule + 1 resolve for N distinct values; computed-style parity
|
|
770
|
+
* with the value-baked path; nesting-safe). Phases 1-4 (RFC §5) generalize
|
|
771
|
+
* across the 170+ unistyle property mappings, responsive arrays (per-breakpoint
|
|
772
|
+
* vars + media queries), the dynamic path, and rocketstyle integration.
|
|
773
|
+
*
|
|
774
|
+
* Self-contained: depends only on unistyle's own `value()` conversion + an
|
|
775
|
+
* inline FNV-1a. Does NOT import `@pyreon/styler` (keeps the primitive
|
|
776
|
+
* layer-pure; only the measurement TESTS reach for the styler counters).
|
|
777
|
+
*/
|
|
778
|
+
const fnv1a = (s) => {
|
|
779
|
+
let h = 2166136261;
|
|
780
|
+
for (let i = 0; i < s.length; i++) {
|
|
781
|
+
h ^= s.charCodeAt(i);
|
|
782
|
+
h = Math.imul(h, 16777619);
|
|
782
783
|
}
|
|
783
|
-
return
|
|
784
|
-
};
|
|
785
|
-
const extendCss = (styles) => {
|
|
786
|
-
if (!styles) return "";
|
|
787
|
-
if (typeof styles === "function") return styles(simpleCss);
|
|
788
|
-
return styles;
|
|
784
|
+
return (h >>> 0).toString(36);
|
|
789
785
|
};
|
|
786
|
+
/**
|
|
787
|
+
* Extract one style declaration into a value-agnostic rule + a per-instance
|
|
788
|
+
* custom-property value.
|
|
789
|
+
*
|
|
790
|
+
* @param property CSS property name (already in CSS-spec form, e.g. `gap`).
|
|
791
|
+
* @param rawValue The author-supplied value (`36`, `"1rem"`, `"var(--x)"`, …).
|
|
792
|
+
* @param rootSize px→rem base (defaults to 16, matching `value()`).
|
|
793
|
+
*/
|
|
794
|
+
function extractStyleVar(property, rawValue, rootSize = 16) {
|
|
795
|
+
const varName = cpseVarName(property);
|
|
796
|
+
const converted = value(rawValue, rootSize);
|
|
797
|
+
return {
|
|
798
|
+
rule: `${property}:var(${varName})`,
|
|
799
|
+
varName,
|
|
800
|
+
varValue: converted == null ? null : String(converted)
|
|
801
|
+
};
|
|
802
|
+
}
|
|
803
|
+
/**
|
|
804
|
+
* Canonical CPSE custom-property name for a CSS property (+ optional
|
|
805
|
+
* breakpoint suffix for the responsive path). Stable + collision-free across
|
|
806
|
+
* distinct property names; shared across components for the same property
|
|
807
|
+
* (intended — every instance sets its own inline value, §nesting test).
|
|
808
|
+
*/
|
|
809
|
+
function cpseVarName(property, breakpoint) {
|
|
810
|
+
return breakpoint ? `--u-${fnv1a(property)}-${breakpoint}` : `--u-${fnv1a(property)}`;
|
|
811
|
+
}
|
|
812
|
+
const STRUCTURAL = /[{}&@]|url\(/;
|
|
813
|
+
/**
|
|
814
|
+
* Rewrite every flat `prop: value;` declaration in a resolved CSS fragment to
|
|
815
|
+
* the value-agnostic `prop: var(--u-<hash>[-bp]);` form, writing each value
|
|
816
|
+
* into `varsOut`. Returns the rewritten fragment. Fragments carrying any
|
|
817
|
+
* structure (selectors, at-rules, nesting, url()) are returned UNCHANGED.
|
|
818
|
+
*
|
|
819
|
+
* This operates on `processDescriptor`'s ALREADY-RESOLVED output, so it
|
|
820
|
+
* inherits every unit-conversion / shorthand correctness for free and stays
|
|
821
|
+
* general across the whole property map.
|
|
822
|
+
*
|
|
823
|
+
* @param frag a resolved CSS fragment, e.g. `"gap: 2.25rem;"` or
|
|
824
|
+
* `"margin: 1rem 2rem;"` (possibly several `;`-separated).
|
|
825
|
+
* @param varsOut sink: `varName → value` for the per-instance inline props.
|
|
826
|
+
* @param breakpoint optional suffix so per-breakpoint values get distinct vars.
|
|
827
|
+
*/
|
|
828
|
+
function cpseRewrite(frag, varsOut, breakpoint) {
|
|
829
|
+
if (!frag || STRUCTURAL.test(frag)) return frag;
|
|
830
|
+
let out = "";
|
|
831
|
+
for (const decl of frag.split(";")) {
|
|
832
|
+
const trimmed = decl.trim();
|
|
833
|
+
if (!trimmed) continue;
|
|
834
|
+
const colon = trimmed.indexOf(":");
|
|
835
|
+
if (colon < 1) {
|
|
836
|
+
out += `${trimmed};`;
|
|
837
|
+
continue;
|
|
838
|
+
}
|
|
839
|
+
const prop = trimmed.slice(0, colon).trim();
|
|
840
|
+
const val = trimmed.slice(colon + 1).trim();
|
|
841
|
+
if (!val || val.startsWith("var(") || !/^[a-z][a-z-]*$/.test(prop)) {
|
|
842
|
+
out += `${trimmed};`;
|
|
843
|
+
continue;
|
|
844
|
+
}
|
|
845
|
+
const varName = cpseVarName(prop, breakpoint);
|
|
846
|
+
varsOut[varName] = val;
|
|
847
|
+
out += `${prop}:var(${varName});`;
|
|
848
|
+
}
|
|
849
|
+
return out;
|
|
850
|
+
}
|
|
790
851
|
|
|
791
852
|
//#endregion
|
|
792
853
|
//#region src/styles/shorthands/borderRadius.ts
|
|
@@ -2391,7 +2452,7 @@ for (let i = 0; i < propertyMap.length; i++) {
|
|
|
2391
2452
|
* pseudo-selectors, and @layer wrapping.
|
|
2392
2453
|
*/
|
|
2393
2454
|
const _seen = /* @__PURE__ */ new Set();
|
|
2394
|
-
const styles = ({ theme: t, css, rootSize }) => {
|
|
2455
|
+
const styles = ({ theme: t, css, rootSize, extractVars, breakpoint }) => {
|
|
2395
2456
|
if (process.env.NODE_ENV !== "production") _countSink.__pyreon_count__?.("unistyle.styles");
|
|
2396
2457
|
const calc = (...params) => values(params, rootSize);
|
|
2397
2458
|
const shorthand = edge(rootSize);
|
|
@@ -2405,14 +2466,18 @@ const styles = ({ theme: t, css, rootSize }) => {
|
|
|
2405
2466
|
if (_seen.has(idx)) continue;
|
|
2406
2467
|
_seen.add(idx);
|
|
2407
2468
|
if (process.env.NODE_ENV !== "production") _countSink.__pyreon_count__?.("unistyle.descriptor");
|
|
2408
|
-
|
|
2469
|
+
let frag = processDescriptor(propertyMap[idx], t, css, calc, shorthand, borderRadiusFn);
|
|
2470
|
+
if (extractVars && typeof frag === "string") frag = cpseRewrite(frag, extractVars, breakpoint);
|
|
2471
|
+
fragments.push(frag);
|
|
2409
2472
|
}
|
|
2410
2473
|
}
|
|
2411
2474
|
if (fragments.length === 0 && Object.keys(t).length > 0) {
|
|
2412
2475
|
if (process.env.NODE_ENV !== "production") _countSink.__pyreon_count__?.("unistyle.descriptor.fallback-scan");
|
|
2413
2476
|
for (const d of propertyMap) {
|
|
2414
2477
|
if (process.env.NODE_ENV !== "production") _countSink.__pyreon_count__?.("unistyle.descriptor");
|
|
2415
|
-
|
|
2478
|
+
let frag = processDescriptor(d, t, css, calc, shorthand, borderRadiusFn);
|
|
2479
|
+
if (extractVars && typeof frag === "string") frag = cpseRewrite(frag, extractVars, breakpoint);
|
|
2480
|
+
fragments.push(frag);
|
|
2416
2481
|
}
|
|
2417
2482
|
}
|
|
2418
2483
|
return css`
|
|
@@ -2421,5 +2486,205 @@ const styles = ({ theme: t, css, rootSize }) => {
|
|
|
2421
2486
|
};
|
|
2422
2487
|
|
|
2423
2488
|
//#endregion
|
|
2424
|
-
|
|
2489
|
+
//#region src/cpse-styled.tsx
|
|
2490
|
+
const RESERVED = /* @__PURE__ */ new Set([
|
|
2491
|
+
"styles",
|
|
2492
|
+
"rootSize",
|
|
2493
|
+
"breakpoints",
|
|
2494
|
+
"class",
|
|
2495
|
+
"ref",
|
|
2496
|
+
"children"
|
|
2497
|
+
]);
|
|
2498
|
+
const DEFAULT_BREAKPOINTS = {
|
|
2499
|
+
xs: 0,
|
|
2500
|
+
sm: 576,
|
|
2501
|
+
md: 768,
|
|
2502
|
+
lg: 992,
|
|
2503
|
+
xl: 1200
|
|
2504
|
+
};
|
|
2505
|
+
/** A value is responsive if it's a mobile-first array or a breakpoint object
|
|
2506
|
+
* (a `var(--x)` / plain string is NOT — strings are scalar). */
|
|
2507
|
+
const isResponsiveValue = (v) => Array.isArray(v) || v != null && typeof v === "object";
|
|
2508
|
+
const isResponsiveTheme = (t) => {
|
|
2509
|
+
for (const k in t) if (isResponsiveValue(t[k])) return true;
|
|
2510
|
+
return false;
|
|
2511
|
+
};
|
|
2512
|
+
/** Expand a responsive theme into per-breakpoint flat themes (only the
|
|
2513
|
+
* breakpoints that actually carry a value). Mobile-first arrays map index→
|
|
2514
|
+
* sorted-breakpoint; breakpoint objects map by key; scalars land on the base. */
|
|
2515
|
+
function expandResponsive(t, sorted) {
|
|
2516
|
+
const perBp = /* @__PURE__ */ new Map();
|
|
2517
|
+
const put = (bp, prop, val) => {
|
|
2518
|
+
let m = perBp.get(bp);
|
|
2519
|
+
if (!m) {
|
|
2520
|
+
m = {};
|
|
2521
|
+
perBp.set(bp, m);
|
|
2522
|
+
}
|
|
2523
|
+
m[prop] = val;
|
|
2524
|
+
};
|
|
2525
|
+
const rec = t;
|
|
2526
|
+
for (const prop in rec) {
|
|
2527
|
+
const v = rec[prop];
|
|
2528
|
+
if (Array.isArray(v)) {
|
|
2529
|
+
for (let i = 0; i < v.length && i < sorted.length; i++) if (v[i] != null) put(sorted[i], prop, v[i]);
|
|
2530
|
+
} else if (v != null && typeof v === "object") for (const bp in v) {
|
|
2531
|
+
const bv = v[bp];
|
|
2532
|
+
if (sorted.includes(bp) && bv != null) put(bp, prop, bv);
|
|
2533
|
+
}
|
|
2534
|
+
else put(sorted[0], prop, v);
|
|
2535
|
+
}
|
|
2536
|
+
return perBp;
|
|
2537
|
+
}
|
|
2538
|
+
function cpseStyled(tag) {
|
|
2539
|
+
const classByShape = /* @__PURE__ */ new Map();
|
|
2540
|
+
/**
|
|
2541
|
+
* Compute the per-instance var map (always) and the value-agnostic className
|
|
2542
|
+
* (resolved + inserted only on the first sighting of a shape). Returns both.
|
|
2543
|
+
* `styles({ extractVars })` writes vars synchronously while building fragments,
|
|
2544
|
+
* so vars are ready without the expensive `String(...)` (→ `styler.resolve`)
|
|
2545
|
+
* + insert — those run only on a cache miss.
|
|
2546
|
+
*/
|
|
2547
|
+
const resolve = (styleTheme, rootSize, breakpoints) => {
|
|
2548
|
+
const vars = {};
|
|
2549
|
+
if (!isResponsiveTheme(styleTheme)) {
|
|
2550
|
+
const result = styles({
|
|
2551
|
+
theme: styleTheme,
|
|
2552
|
+
css,
|
|
2553
|
+
rootSize,
|
|
2554
|
+
extractVars: vars
|
|
2555
|
+
});
|
|
2556
|
+
const shapeKey = `flat:${Object.keys(styleTheme).sort().join("|")}`;
|
|
2557
|
+
let className = classByShape.get(shapeKey);
|
|
2558
|
+
if (className === void 0) {
|
|
2559
|
+
const text = normalizeCSS(String(result));
|
|
2560
|
+
className = text.length > 0 ? sheet.insert(text) : "";
|
|
2561
|
+
classByShape.set(shapeKey, className);
|
|
2562
|
+
}
|
|
2563
|
+
return {
|
|
2564
|
+
className,
|
|
2565
|
+
vars
|
|
2566
|
+
};
|
|
2567
|
+
}
|
|
2568
|
+
const sorted = sortBreakpoints(breakpoints);
|
|
2569
|
+
const perBp = expandResponsive(styleTheme, sorted);
|
|
2570
|
+
const mq = createMediaQueries({
|
|
2571
|
+
breakpoints,
|
|
2572
|
+
rootSize,
|
|
2573
|
+
css
|
|
2574
|
+
});
|
|
2575
|
+
const frags = [];
|
|
2576
|
+
const shapeParts = [];
|
|
2577
|
+
for (const bp of sorted) {
|
|
2578
|
+
const flat = perBp.get(bp);
|
|
2579
|
+
if (!flat || Object.keys(flat).length === 0) continue;
|
|
2580
|
+
const decl = String(styles({
|
|
2581
|
+
theme: flat,
|
|
2582
|
+
css,
|
|
2583
|
+
rootSize,
|
|
2584
|
+
extractVars: vars,
|
|
2585
|
+
breakpoint: bp
|
|
2586
|
+
}));
|
|
2587
|
+
frags.push(String(mq[bp]`${decl}`));
|
|
2588
|
+
shapeParts.push(`${bp}:${Object.keys(flat).sort().join(",")}`);
|
|
2589
|
+
}
|
|
2590
|
+
const shapeKey = `resp:${shapeParts.join("|")}`;
|
|
2591
|
+
let className = classByShape.get(shapeKey);
|
|
2592
|
+
if (className === void 0) {
|
|
2593
|
+
const text = normalizeCSS(frags.join("\n"));
|
|
2594
|
+
className = text.length > 0 ? sheet.insert(text) : "";
|
|
2595
|
+
classByShape.set(shapeKey, className);
|
|
2596
|
+
}
|
|
2597
|
+
return {
|
|
2598
|
+
className,
|
|
2599
|
+
vars
|
|
2600
|
+
};
|
|
2601
|
+
};
|
|
2602
|
+
return (props) => {
|
|
2603
|
+
const theme = useThemeAccessor()() ?? {};
|
|
2604
|
+
const rootSize = props.rootSize ?? theme.rootSize ?? 16;
|
|
2605
|
+
const breakpoints = props.breakpoints ?? theme.breakpoints ?? DEFAULT_BREAKPOINTS;
|
|
2606
|
+
const stylesProp = props.styles;
|
|
2607
|
+
const isDynamic = typeof stylesProp === "function";
|
|
2608
|
+
const getTheme = () => isDynamic ? stylesProp() : stylesProp ?? {};
|
|
2609
|
+
const first = resolve(getTheme(), rootSize, breakpoints);
|
|
2610
|
+
const className = props.class ? `${first.className} ${props.class}` : first.className;
|
|
2611
|
+
const rest = {};
|
|
2612
|
+
for (const k in props) if (!RESERVED.has(k)) rest[k] = props[k];
|
|
2613
|
+
let el = null;
|
|
2614
|
+
const applyVars = (vars) => {
|
|
2615
|
+
if (!el) return;
|
|
2616
|
+
for (const k in vars) el.style.setProperty(k, vars[k]);
|
|
2617
|
+
};
|
|
2618
|
+
const ref = (node) => {
|
|
2619
|
+
el = node;
|
|
2620
|
+
if (el) applyVars(first.vars);
|
|
2621
|
+
const userRef = props.ref;
|
|
2622
|
+
if (typeof userRef === "function") userRef(node);
|
|
2623
|
+
else if (userRef && typeof userRef === "object") userRef.current = node;
|
|
2624
|
+
};
|
|
2625
|
+
if (isDynamic) renderEffect(() => {
|
|
2626
|
+
applyVars(resolve(getTheme(), rootSize, breakpoints).vars);
|
|
2627
|
+
});
|
|
2628
|
+
const children = props.children != null ? [props.children] : [];
|
|
2629
|
+
return h(tag, {
|
|
2630
|
+
class: className,
|
|
2631
|
+
style: first.vars,
|
|
2632
|
+
ref,
|
|
2633
|
+
...rest
|
|
2634
|
+
}, ...children);
|
|
2635
|
+
};
|
|
2636
|
+
}
|
|
2637
|
+
|
|
2638
|
+
//#endregion
|
|
2639
|
+
//#region src/styles/alignContent.ts
|
|
2640
|
+
const ALIGN_CONTENT_MAP_SHARED = {
|
|
2641
|
+
center: "center",
|
|
2642
|
+
spaceBetween: "space-between",
|
|
2643
|
+
spaceAround: "space-around",
|
|
2644
|
+
block: "stretch"
|
|
2645
|
+
};
|
|
2646
|
+
const ALIGN_CONTENT_MAP_X = {
|
|
2647
|
+
left: "flex-start",
|
|
2648
|
+
right: "flex-end",
|
|
2649
|
+
...ALIGN_CONTENT_MAP_SHARED
|
|
2650
|
+
};
|
|
2651
|
+
const ALIGN_CONTENT_MAP_Y = {
|
|
2652
|
+
top: "flex-start",
|
|
2653
|
+
bottom: "flex-end",
|
|
2654
|
+
...ALIGN_CONTENT_MAP_SHARED
|
|
2655
|
+
};
|
|
2656
|
+
const ALIGN_CONTENT_DIRECTION = {
|
|
2657
|
+
inline: "row",
|
|
2658
|
+
reverseInline: "row-reverse",
|
|
2659
|
+
rows: "column",
|
|
2660
|
+
reverseRows: "column-reverse"
|
|
2661
|
+
};
|
|
2662
|
+
const alignContent = (attrs) => {
|
|
2663
|
+
const { direction, alignX, alignY } = attrs;
|
|
2664
|
+
if (isEmpty(attrs) || !direction || !alignX || !alignY) return null;
|
|
2665
|
+
const isReverted = direction === "inline" || direction === "reverseInline";
|
|
2666
|
+
const dir = ALIGN_CONTENT_DIRECTION[direction];
|
|
2667
|
+
const x = ALIGN_CONTENT_MAP_X[alignX];
|
|
2668
|
+
const y = ALIGN_CONTENT_MAP_Y[alignY];
|
|
2669
|
+
return `flex-direction: ${dir}; align-items: ${isReverted ? y : x}; justify-content: ${isReverted ? x : y};`;
|
|
2670
|
+
};
|
|
2671
|
+
|
|
2672
|
+
//#endregion
|
|
2673
|
+
//#region src/styles/extendCss.ts
|
|
2674
|
+
const simpleCss = (strings, ...values) => {
|
|
2675
|
+
let result = "";
|
|
2676
|
+
for (let i = 0; i < strings.length; i++) {
|
|
2677
|
+
result += strings[i];
|
|
2678
|
+
if (i < values.length) result += String(values[i] ?? "");
|
|
2679
|
+
}
|
|
2680
|
+
return result;
|
|
2681
|
+
};
|
|
2682
|
+
const extendCss = (styles) => {
|
|
2683
|
+
if (!styles) return "";
|
|
2684
|
+
if (typeof styles === "function") return styles(simpleCss);
|
|
2685
|
+
return styles;
|
|
2686
|
+
};
|
|
2687
|
+
|
|
2688
|
+
//#endregion
|
|
2689
|
+
export { ALIGN_CONTENT_DIRECTION, ALIGN_CONTENT_MAP_X, ALIGN_CONTENT_MAP_Y, CSS_VARS_DEFAULT_EXCLUDE, Provider, alignContent, breakpoints, context, cpseRewrite, cpseStyled, cpseVarName, createMediaQueries, enrichTheme, extendCss, extractStyleVar, makeItResponsive, normalizeTheme, resolveCssVarReferences, sortBreakpoints, stripUnit, styles, themeToCssVars, transformTheme, value, values };
|
|
2425
2690
|
//# sourceMappingURL=index.js.map
|