@uniflowed/stylex 0.0.0-alpha.2

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 (2) hide show
  1. package/index.js +162 -0
  2. package/package.json +22 -0
package/index.js ADDED
@@ -0,0 +1,162 @@
1
+ // @flow
2
+ //
3
+ // `@uniflowed/stylex`: uf's style engine, as the thing that runs.
4
+ //
5
+ // Almost all of StyleX happens at compile time. `uf transform` rewrites every
6
+ // `stylex.create({ … })` into a plain object of class names and collects the
7
+ // rules into a stylesheet, so by the time this module is loaded there are no
8
+ // style values left — only names.
9
+ //
10
+ // What remains is `props`, and it is here because it cannot be anywhere else:
11
+ // its arguments are usually conditional (`active && styles.on`), and a compiler
12
+ // cannot fold a value it does not know. Everything else in this module exists
13
+ // so that a call the compiler failed to see fails loudly rather than silently
14
+ // rendering an application with no styles.
15
+ //
16
+ // The merge is specified in `crates/uf_stylex/src/props.rs` and modelled there
17
+ // at compile time, which is what lets its ordering be tested. This
18
+ // implementation and that model have to agree.
19
+
20
+ import { nativeRuntimeRequired } from "@uniflowed/core/native";
21
+
22
+ const MODULE = "@uniflowed/stylex";
23
+
24
+ /**
25
+ * A compiled style namespace.
26
+ *
27
+ * `$$css` marks an object the compiler produced. Every other key is a CSS
28
+ * property mapped to the class name that sets it — or to `null`, which is how
29
+ * a namespace says it deliberately unsets that property.
30
+ */
31
+ export type CompiledStyle = {
32
+ readonly $$css: true,
33
+ readonly [property: string]: string | null | true,
34
+ };
35
+
36
+ /** What a call site may pass: a namespace, something falsy, or a list. */
37
+ export type StyleArgument = mixed;
38
+
39
+ /** What `props` hands to an element. */
40
+ export type StyleProps = { readonly className?: string };
41
+
42
+ /**
43
+ * Merge compiled namespaces into a `className`, left to right.
44
+ *
45
+ * The **property** is the unit of merging: a later namespace that sets `color`
46
+ * replaces everything an earlier one said about `color`, its `:hover` value
47
+ * included. That is what a later `color:` in a stylesheet does, and it is why a
48
+ * later namespace cannot leave a stray hover state behind.
49
+ *
50
+ * Falsy arguments are skipped, because `active && styles.on` is the idiom this
51
+ * function exists for, and arrays are flattened so a list built elsewhere can
52
+ * be passed without spreading it.
53
+ *
54
+ * Returns an object rather than a string so the call site stays
55
+ * `<div {...stylex.props(a, b)} />` — the same shape whether or not anything
56
+ * survived.
57
+ */
58
+ export function props(...styles: $ReadOnlyArray<StyleArgument>): StyleProps {
59
+ const winners: { [string]: string | null } = {};
60
+ collect(styles, winners);
61
+
62
+ let className = "";
63
+ for (const property of Object.keys(winners)) {
64
+ const name = winners[property];
65
+ // `null` is a deliberate unset: the property has an owner, and that owner
66
+ // said there should be no class for it.
67
+ if (name == null) {
68
+ continue;
69
+ }
70
+ className = className === "" ? name : className + " " + name;
71
+ }
72
+
73
+ return className === "" ? {} : { className };
74
+ }
75
+
76
+ /**
77
+ * Fold arguments into `winners`, flattening arrays.
78
+ *
79
+ * Insertion order is the order a property was *first* claimed, and assigning
80
+ * over an existing key does not move it — so two namespaces that both set
81
+ * `color` produce one class in the position the first one had. The class list
82
+ * is a function of the properties involved, not of how many namespaces
83
+ * mentioned them.
84
+ */
85
+ function collect(
86
+ styles: $ReadOnlyArray<StyleArgument>,
87
+ winners: { [string]: string | null },
88
+ ): void {
89
+ for (const style of styles) {
90
+ if (style == null || style === false || style === true) {
91
+ continue;
92
+ }
93
+ if (Array.isArray(style)) {
94
+ collect(style, winners);
95
+ continue;
96
+ }
97
+ if (typeof style !== "object") {
98
+ continue;
99
+ }
100
+ const namespace = style as $FlowFixMe;
101
+ for (const property of Object.keys(namespace)) {
102
+ if (property === "$$css") {
103
+ continue;
104
+ }
105
+ const value = namespace[property];
106
+ if (typeof value === "string" || value === null) {
107
+ winners[property] = value;
108
+ }
109
+ }
110
+ }
111
+ }
112
+
113
+ /**
114
+ * Declare a set of style namespaces.
115
+ *
116
+ * Never runs. `uf transform` replaces the whole call with the object it
117
+ * computed, so reaching this means the module was loaded without going through
118
+ * uf — a bundler configured by hand, a plain `node` invocation — and the styles
119
+ * it declares are in no stylesheet. Throwing says so; returning the input would
120
+ * render an application with no styles and no explanation.
121
+ */
122
+ export function create<T extends { readonly [string]: mixed }>(styles: T): T {
123
+ return nativeRuntimeRequired(MODULE, "stylex.create");
124
+ }
125
+
126
+ /**
127
+ * Declare design tokens, and hand back the `var(--…)` references to them.
128
+ *
129
+ * Compile-time, for the same reason as `create`.
130
+ */
131
+ export function defineVars<T extends { readonly [string]: string | number }>(tokens: T): T {
132
+ return nativeRuntimeRequired(MODULE, "stylex.defineVars");
133
+ }
134
+
135
+ /**
136
+ * Override a set of tokens for a subtree.
137
+ *
138
+ * Compile-time, for the same reason as `create`.
139
+ */
140
+ export function createTheme<T extends { readonly [string]: string | number }>(tokens: T): T {
141
+ return nativeRuntimeRequired(MODULE, "stylex.createTheme");
142
+ }
143
+
144
+ /**
145
+ * The namespace form, so `stylex.create` and `stylex.props` read the way
146
+ * StyleX documents them.
147
+ *
148
+ * The named exports are the ones a bundler can drop individually; this object
149
+ * is for call sites that prefer the qualified spelling, and the compiler
150
+ * recognises both.
151
+ */
152
+ export const stylex: {
153
+ readonly create: typeof create,
154
+ readonly props: typeof props,
155
+ readonly defineVars: typeof defineVars,
156
+ readonly createTheme: typeof createTheme,
157
+ } = {
158
+ create,
159
+ props,
160
+ defineVars,
161
+ createTheme,
162
+ };
package/package.json ADDED
@@ -0,0 +1,22 @@
1
+ {
2
+ "name": "@uniflowed/stylex",
3
+ "version": "0.0.0-alpha.2",
4
+ "description": "Flow declarations for @uniflowed/stylex, part of the Unified Toolchain for Flow.",
5
+ "type": "module",
6
+ "license": "MIT",
7
+ "sideEffects": false,
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "git+https://github.com/ubugeeei-prod/uf.git",
11
+ "directory": "packages/stylex"
12
+ },
13
+ "exports": {
14
+ ".": "./index.js"
15
+ },
16
+ "files": [
17
+ "index.js"
18
+ ],
19
+ "dependencies": {
20
+ "@uniflowed/core": "0.0.0-alpha.2"
21
+ }
22
+ }