@vitus-labs/unistyle 2.0.0-beta.3 → 2.0.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.js +151 -0
- package/lib/vitus-labs-unistyle.native.js +151 -0
- package/package.json +2 -2
package/lib/index.js
CHANGED
|
@@ -79,6 +79,127 @@ const normalizeTheme = ({ theme, breakpoints }) => {
|
|
|
79
79
|
return result;
|
|
80
80
|
};
|
|
81
81
|
|
|
82
|
+
//#endregion
|
|
83
|
+
//#region src/responsive/optimizeBreakpointDeltas.ts
|
|
84
|
+
/** Parse a CSS string into top-level declarations and opaque blocks. */
|
|
85
|
+
const parse = (css) => {
|
|
86
|
+
const entries = [];
|
|
87
|
+
const len = css.length;
|
|
88
|
+
let depth = 0;
|
|
89
|
+
let parenDepth = 0;
|
|
90
|
+
let quote = 0;
|
|
91
|
+
let segmentStart = 0;
|
|
92
|
+
const pushSegment = (rawSegment) => {
|
|
93
|
+
const trimmed = rawSegment.trim();
|
|
94
|
+
if (!trimmed) return;
|
|
95
|
+
const text = trimmed.endsWith(";") ? trimmed.slice(0, -1) : trimmed;
|
|
96
|
+
const colonIdx = text.indexOf(":");
|
|
97
|
+
if (colonIdx <= 0) {
|
|
98
|
+
entries.push({
|
|
99
|
+
kind: "block",
|
|
100
|
+
raw: `${text};`
|
|
101
|
+
});
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
const prop = text.slice(0, colonIdx).trim();
|
|
105
|
+
const value = text.slice(colonIdx + 1).trim();
|
|
106
|
+
if (!prop || !value) {
|
|
107
|
+
entries.push({
|
|
108
|
+
kind: "block",
|
|
109
|
+
raw: `${text};`
|
|
110
|
+
});
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
entries.push({
|
|
114
|
+
kind: "decl",
|
|
115
|
+
prop,
|
|
116
|
+
value,
|
|
117
|
+
raw: `${prop}: ${value};`
|
|
118
|
+
});
|
|
119
|
+
};
|
|
120
|
+
for (let i = 0; i < len; i++) {
|
|
121
|
+
const code = css.charCodeAt(i);
|
|
122
|
+
if (quote !== 0) {
|
|
123
|
+
if (code === 92) i++;
|
|
124
|
+
else if (code === quote) quote = 0;
|
|
125
|
+
continue;
|
|
126
|
+
}
|
|
127
|
+
if (code === 34 || code === 39) {
|
|
128
|
+
quote = code;
|
|
129
|
+
continue;
|
|
130
|
+
}
|
|
131
|
+
if (code === 40) {
|
|
132
|
+
parenDepth++;
|
|
133
|
+
continue;
|
|
134
|
+
}
|
|
135
|
+
if (code === 41) {
|
|
136
|
+
if (parenDepth > 0) parenDepth--;
|
|
137
|
+
continue;
|
|
138
|
+
}
|
|
139
|
+
if (parenDepth > 0) continue;
|
|
140
|
+
if (code === 123) {
|
|
141
|
+
depth++;
|
|
142
|
+
continue;
|
|
143
|
+
}
|
|
144
|
+
if (code === 125) {
|
|
145
|
+
depth--;
|
|
146
|
+
if (depth === 0) {
|
|
147
|
+
const raw = css.slice(segmentStart, i + 1).trim();
|
|
148
|
+
if (raw) entries.push({
|
|
149
|
+
kind: "block",
|
|
150
|
+
raw
|
|
151
|
+
});
|
|
152
|
+
segmentStart = i + 1;
|
|
153
|
+
}
|
|
154
|
+
continue;
|
|
155
|
+
}
|
|
156
|
+
if (depth === 0 && code === 59) {
|
|
157
|
+
pushSegment(css.slice(segmentStart, i));
|
|
158
|
+
segmentStart = i + 1;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
if (segmentStart < len) {
|
|
162
|
+
const trailing = css.slice(segmentStart).trim();
|
|
163
|
+
if (trailing) if (depth > 0) entries.push({
|
|
164
|
+
kind: "block",
|
|
165
|
+
raw: trailing
|
|
166
|
+
});
|
|
167
|
+
else pushSegment(trailing);
|
|
168
|
+
}
|
|
169
|
+
return entries;
|
|
170
|
+
};
|
|
171
|
+
/**
|
|
172
|
+
* Apply the mobile-first cascade diff. The first entry passes through
|
|
173
|
+
* unchanged; subsequent entries are pruned to the delta vs. the running
|
|
174
|
+
* cascade (declarations by prop, blocks by exact text match).
|
|
175
|
+
*/
|
|
176
|
+
const optimizeBreakpointDeltas = (cssStrings) => {
|
|
177
|
+
if (cssStrings.length <= 1) return cssStrings;
|
|
178
|
+
const cascadeDecl = /* @__PURE__ */ new Map();
|
|
179
|
+
const cascadeBlocks = /* @__PURE__ */ new Set();
|
|
180
|
+
const out = new Array(cssStrings.length);
|
|
181
|
+
for (let i = 0; i < cssStrings.length; i++) {
|
|
182
|
+
const css = cssStrings[i];
|
|
183
|
+
if (!css) {
|
|
184
|
+
out[i] = "";
|
|
185
|
+
continue;
|
|
186
|
+
}
|
|
187
|
+
const entries = parse(css);
|
|
188
|
+
const kept = [];
|
|
189
|
+
for (const e of entries) if (e.kind === "decl") {
|
|
190
|
+
if (cascadeDecl.get(e.prop) !== e.value) {
|
|
191
|
+
kept.push(e.raw);
|
|
192
|
+
cascadeDecl.set(e.prop, e.value);
|
|
193
|
+
}
|
|
194
|
+
} else if (!cascadeBlocks.has(e.raw)) {
|
|
195
|
+
kept.push(e.raw);
|
|
196
|
+
cascadeBlocks.add(e.raw);
|
|
197
|
+
}
|
|
198
|
+
out[i] = kept.join(" ");
|
|
199
|
+
}
|
|
200
|
+
return out;
|
|
201
|
+
};
|
|
202
|
+
|
|
82
203
|
//#endregion
|
|
83
204
|
//#region src/responsive/optimizeTheme.ts
|
|
84
205
|
const shallowEqual = (a, b) => {
|
|
@@ -145,6 +266,23 @@ const transformTheme = ({ theme, breakpoints }) => {
|
|
|
145
266
|
//#endregion
|
|
146
267
|
//#region src/responsive/makeItResponsive.ts
|
|
147
268
|
/**
|
|
269
|
+
* Coerce a styles-callback result to a CSS string for delta optimization.
|
|
270
|
+
* Returns null when the engine's result type can't be stringified cleanly
|
|
271
|
+
* (e.g. Emotion / styled-components objects whose default toString() yields
|
|
272
|
+
* "[object Object]") — caller falls back to the unoptimized path.
|
|
273
|
+
*
|
|
274
|
+
* Styler's CSSResult provides toString() that resolves with empty props,
|
|
275
|
+
* so any function interpolation that needs render-time props must come from
|
|
276
|
+
* the styles-callback closure (theme is destructured at call time, not
|
|
277
|
+
* resolved later). Verified across the project's styles callbacks.
|
|
278
|
+
*/
|
|
279
|
+
const stringifyResult = (result) => {
|
|
280
|
+
if (result == null) return "";
|
|
281
|
+
if (typeof result === "string") return result;
|
|
282
|
+
const text = String(result);
|
|
283
|
+
return text.includes("[object ") ? null : text;
|
|
284
|
+
};
|
|
285
|
+
/**
|
|
148
286
|
* Core responsive engine used by every styled component in the system.
|
|
149
287
|
*
|
|
150
288
|
* Returns a styled-components interpolation function that:
|
|
@@ -190,6 +328,19 @@ const makeItResponsive = ({ theme: customTheme, key = "", css, styles, normalize
|
|
|
190
328
|
optimized: optimizedTheme
|
|
191
329
|
});
|
|
192
330
|
}
|
|
331
|
+
const renderedTexts = sortedBreakpoints.map((item) => {
|
|
332
|
+
const breakpointTheme = optimizedTheme[item];
|
|
333
|
+
if (!breakpointTheme || !media) return "";
|
|
334
|
+
return stringifyResult(renderStyles(breakpointTheme));
|
|
335
|
+
});
|
|
336
|
+
if (renderedTexts.every((t) => t !== null)) {
|
|
337
|
+
const deltas = optimizeBreakpointDeltas(renderedTexts);
|
|
338
|
+
return sortedBreakpoints.map((item, i) => {
|
|
339
|
+
const css = deltas[i];
|
|
340
|
+
if (!css || !media) return "";
|
|
341
|
+
return media[item]`${css}`;
|
|
342
|
+
});
|
|
343
|
+
}
|
|
193
344
|
return sortedBreakpoints.map((item) => {
|
|
194
345
|
const breakpointTheme = optimizedTheme[item];
|
|
195
346
|
if (!breakpointTheme || !media) return "";
|
|
@@ -79,6 +79,127 @@ const normalizeTheme = ({ theme, breakpoints }) => {
|
|
|
79
79
|
return result;
|
|
80
80
|
};
|
|
81
81
|
|
|
82
|
+
//#endregion
|
|
83
|
+
//#region src/responsive/optimizeBreakpointDeltas.ts
|
|
84
|
+
/** Parse a CSS string into top-level declarations and opaque blocks. */
|
|
85
|
+
const parse = (css) => {
|
|
86
|
+
const entries = [];
|
|
87
|
+
const len = css.length;
|
|
88
|
+
let depth = 0;
|
|
89
|
+
let parenDepth = 0;
|
|
90
|
+
let quote = 0;
|
|
91
|
+
let segmentStart = 0;
|
|
92
|
+
const pushSegment = (rawSegment) => {
|
|
93
|
+
const trimmed = rawSegment.trim();
|
|
94
|
+
if (!trimmed) return;
|
|
95
|
+
const text = trimmed.endsWith(";") ? trimmed.slice(0, -1) : trimmed;
|
|
96
|
+
const colonIdx = text.indexOf(":");
|
|
97
|
+
if (colonIdx <= 0) {
|
|
98
|
+
entries.push({
|
|
99
|
+
kind: "block",
|
|
100
|
+
raw: `${text};`
|
|
101
|
+
});
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
const prop = text.slice(0, colonIdx).trim();
|
|
105
|
+
const value = text.slice(colonIdx + 1).trim();
|
|
106
|
+
if (!prop || !value) {
|
|
107
|
+
entries.push({
|
|
108
|
+
kind: "block",
|
|
109
|
+
raw: `${text};`
|
|
110
|
+
});
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
entries.push({
|
|
114
|
+
kind: "decl",
|
|
115
|
+
prop,
|
|
116
|
+
value,
|
|
117
|
+
raw: `${prop}: ${value};`
|
|
118
|
+
});
|
|
119
|
+
};
|
|
120
|
+
for (let i = 0; i < len; i++) {
|
|
121
|
+
const code = css.charCodeAt(i);
|
|
122
|
+
if (quote !== 0) {
|
|
123
|
+
if (code === 92) i++;
|
|
124
|
+
else if (code === quote) quote = 0;
|
|
125
|
+
continue;
|
|
126
|
+
}
|
|
127
|
+
if (code === 34 || code === 39) {
|
|
128
|
+
quote = code;
|
|
129
|
+
continue;
|
|
130
|
+
}
|
|
131
|
+
if (code === 40) {
|
|
132
|
+
parenDepth++;
|
|
133
|
+
continue;
|
|
134
|
+
}
|
|
135
|
+
if (code === 41) {
|
|
136
|
+
if (parenDepth > 0) parenDepth--;
|
|
137
|
+
continue;
|
|
138
|
+
}
|
|
139
|
+
if (parenDepth > 0) continue;
|
|
140
|
+
if (code === 123) {
|
|
141
|
+
depth++;
|
|
142
|
+
continue;
|
|
143
|
+
}
|
|
144
|
+
if (code === 125) {
|
|
145
|
+
depth--;
|
|
146
|
+
if (depth === 0) {
|
|
147
|
+
const raw = css.slice(segmentStart, i + 1).trim();
|
|
148
|
+
if (raw) entries.push({
|
|
149
|
+
kind: "block",
|
|
150
|
+
raw
|
|
151
|
+
});
|
|
152
|
+
segmentStart = i + 1;
|
|
153
|
+
}
|
|
154
|
+
continue;
|
|
155
|
+
}
|
|
156
|
+
if (depth === 0 && code === 59) {
|
|
157
|
+
pushSegment(css.slice(segmentStart, i));
|
|
158
|
+
segmentStart = i + 1;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
if (segmentStart < len) {
|
|
162
|
+
const trailing = css.slice(segmentStart).trim();
|
|
163
|
+
if (trailing) if (depth > 0) entries.push({
|
|
164
|
+
kind: "block",
|
|
165
|
+
raw: trailing
|
|
166
|
+
});
|
|
167
|
+
else pushSegment(trailing);
|
|
168
|
+
}
|
|
169
|
+
return entries;
|
|
170
|
+
};
|
|
171
|
+
/**
|
|
172
|
+
* Apply the mobile-first cascade diff. The first entry passes through
|
|
173
|
+
* unchanged; subsequent entries are pruned to the delta vs. the running
|
|
174
|
+
* cascade (declarations by prop, blocks by exact text match).
|
|
175
|
+
*/
|
|
176
|
+
const optimizeBreakpointDeltas = (cssStrings) => {
|
|
177
|
+
if (cssStrings.length <= 1) return cssStrings;
|
|
178
|
+
const cascadeDecl = /* @__PURE__ */ new Map();
|
|
179
|
+
const cascadeBlocks = /* @__PURE__ */ new Set();
|
|
180
|
+
const out = new Array(cssStrings.length);
|
|
181
|
+
for (let i = 0; i < cssStrings.length; i++) {
|
|
182
|
+
const css = cssStrings[i];
|
|
183
|
+
if (!css) {
|
|
184
|
+
out[i] = "";
|
|
185
|
+
continue;
|
|
186
|
+
}
|
|
187
|
+
const entries = parse(css);
|
|
188
|
+
const kept = [];
|
|
189
|
+
for (const e of entries) if (e.kind === "decl") {
|
|
190
|
+
if (cascadeDecl.get(e.prop) !== e.value) {
|
|
191
|
+
kept.push(e.raw);
|
|
192
|
+
cascadeDecl.set(e.prop, e.value);
|
|
193
|
+
}
|
|
194
|
+
} else if (!cascadeBlocks.has(e.raw)) {
|
|
195
|
+
kept.push(e.raw);
|
|
196
|
+
cascadeBlocks.add(e.raw);
|
|
197
|
+
}
|
|
198
|
+
out[i] = kept.join(" ");
|
|
199
|
+
}
|
|
200
|
+
return out;
|
|
201
|
+
};
|
|
202
|
+
|
|
82
203
|
//#endregion
|
|
83
204
|
//#region src/responsive/optimizeTheme.ts
|
|
84
205
|
const shallowEqual = (a, b) => {
|
|
@@ -145,6 +266,23 @@ const transformTheme = ({ theme, breakpoints }) => {
|
|
|
145
266
|
//#endregion
|
|
146
267
|
//#region src/responsive/makeItResponsive.ts
|
|
147
268
|
/**
|
|
269
|
+
* Coerce a styles-callback result to a CSS string for delta optimization.
|
|
270
|
+
* Returns null when the engine's result type can't be stringified cleanly
|
|
271
|
+
* (e.g. Emotion / styled-components objects whose default toString() yields
|
|
272
|
+
* "[object Object]") — caller falls back to the unoptimized path.
|
|
273
|
+
*
|
|
274
|
+
* Styler's CSSResult provides toString() that resolves with empty props,
|
|
275
|
+
* so any function interpolation that needs render-time props must come from
|
|
276
|
+
* the styles-callback closure (theme is destructured at call time, not
|
|
277
|
+
* resolved later). Verified across the project's styles callbacks.
|
|
278
|
+
*/
|
|
279
|
+
const stringifyResult = (result) => {
|
|
280
|
+
if (result == null) return "";
|
|
281
|
+
if (typeof result === "string") return result;
|
|
282
|
+
const text = String(result);
|
|
283
|
+
return text.includes("[object ") ? null : text;
|
|
284
|
+
};
|
|
285
|
+
/**
|
|
148
286
|
* Core responsive engine used by every styled component in the system.
|
|
149
287
|
*
|
|
150
288
|
* Returns a styled-components interpolation function that:
|
|
@@ -190,6 +328,19 @@ const makeItResponsive = ({ theme: customTheme, key = "", css, styles, normalize
|
|
|
190
328
|
optimized: optimizedTheme
|
|
191
329
|
});
|
|
192
330
|
}
|
|
331
|
+
const renderedTexts = sortedBreakpoints.map((item) => {
|
|
332
|
+
const breakpointTheme = optimizedTheme[item];
|
|
333
|
+
if (!breakpointTheme || !media) return "";
|
|
334
|
+
return stringifyResult(renderStyles(breakpointTheme));
|
|
335
|
+
});
|
|
336
|
+
if (renderedTexts.every((t) => t !== null)) {
|
|
337
|
+
const deltas = optimizeBreakpointDeltas(renderedTexts);
|
|
338
|
+
return sortedBreakpoints.map((item, i) => {
|
|
339
|
+
const css = deltas[i];
|
|
340
|
+
if (!css || !media) return "";
|
|
341
|
+
return media[item]`${css}`;
|
|
342
|
+
});
|
|
343
|
+
}
|
|
193
344
|
return sortedBreakpoints.map((item) => {
|
|
194
345
|
const breakpointTheme = optimizedTheme[item];
|
|
195
346
|
if (!breakpointTheme || !media) return "";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vitus-labs/unistyle",
|
|
3
|
-
"version": "2.0.0
|
|
3
|
+
"version": "2.0.0",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"author": "Vit Bokisch <vit@bokisch.cz>",
|
|
6
6
|
"maintainers": [
|
|
@@ -51,7 +51,7 @@
|
|
|
51
51
|
"node": ">= 18"
|
|
52
52
|
},
|
|
53
53
|
"peerDependencies": {
|
|
54
|
-
"@vitus-labs/core": "2.0.0
|
|
54
|
+
"@vitus-labs/core": "2.0.0",
|
|
55
55
|
"react": ">= 19",
|
|
56
56
|
"react-native": ">= 0.76"
|
|
57
57
|
},
|