rainbowindex 0.2.0 → 0.2.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.
@@ -0,0 +1,402 @@
1
+ import {
2
+ CSS_ENTRY_CANDIDATES,
3
+ UTILITY_VALUE_SPACES,
4
+ enumerateClassNames
5
+ } from "./chunk-KYDEHYIE.mjs";
6
+ import {
7
+ CLASS_HELPER_NAMES,
8
+ RI_IMPORT_SPECIFIERS,
9
+ STATIC_UTILITIES,
10
+ VARIANT_HELPER_NAMES,
11
+ analyzeProjectCSS,
12
+ buildBreakpointWeights,
13
+ compileUtility,
14
+ createEmptyCompilationResult,
15
+ createThemeSnapshot,
16
+ diagnosticFromWarning,
17
+ expandVariantGroups,
18
+ extractClassCandidates,
19
+ extractClasses,
20
+ extractClassesFromSource,
21
+ findClosest,
22
+ hasRIActivation,
23
+ isSourceFile,
24
+ listVariants,
25
+ parseUtility,
26
+ severityForCode,
27
+ warningCode
28
+ } from "./chunk-W6XIBM4M.mjs";
29
+ import {
30
+ analyzeMerge,
31
+ computeDarkStop,
32
+ defaultTheme,
33
+ formatOklch,
34
+ generateStop,
35
+ linearToSrgb,
36
+ oklabToLinearSrgb,
37
+ oklchToOklab
38
+ } from "./chunk-SOMDX7V6.mjs";
39
+
40
+ // src/engine/inspector.ts
41
+ var RESOLUTION_CACHE_CAP = 1e4;
42
+ var OK = Object.freeze({ ok: true });
43
+ function createClassInspector(theme) {
44
+ const customVariantMap = new Map(theme.customVariants.map((cv) => [cv.name, cv]));
45
+ const breakpointWeights = buildBreakpointWeights(theme.breakpoints);
46
+ const variantMemo = /* @__PURE__ */ new Map();
47
+ const scratch = createEmptyCompilationResult();
48
+ const warnSeen = /* @__PURE__ */ new Set();
49
+ const detail = { reason: null, variant: null, declarations: null };
50
+ const cache = /* @__PURE__ */ new Map();
51
+ let variantList = null;
52
+ let variantNames = null;
53
+ let utilityCorpus = null;
54
+ function variants() {
55
+ if (!variantList) variantList = Object.freeze(listVariants(theme));
56
+ return variantList;
57
+ }
58
+ function variantSuggestionCorpus() {
59
+ if (!variantNames) {
60
+ variantNames = variants().filter((v) => v.kind !== "pattern").map((v) => v.name);
61
+ }
62
+ return variantNames;
63
+ }
64
+ function utilitySuggestionCorpus() {
65
+ if (!utilityCorpus) {
66
+ utilityCorpus = [
67
+ ...STATIC_UTILITIES,
68
+ ...theme.customUtilities.filter((u) => !u.functional).map((u) => u.name)
69
+ ];
70
+ }
71
+ return utilityCorpus;
72
+ }
73
+ function baseName(raw, parsed) {
74
+ let base = raw;
75
+ if (parsed.variants.length > 0) {
76
+ const prefix = `${parsed.variants.join(":")}:`;
77
+ if (base.startsWith(prefix)) base = base.slice(prefix.length);
78
+ }
79
+ if (parsed.important && base.endsWith("!")) base = base.slice(0, -1);
80
+ return base;
81
+ }
82
+ function resolve(className) {
83
+ const cached = cache.get(className);
84
+ if (cached) return cached;
85
+ if (cache.size >= RESOLUTION_CACHE_CAP) {
86
+ cache.clear();
87
+ variantMemo.clear();
88
+ }
89
+ scratch.warnings.length = 0;
90
+ warnSeen.clear();
91
+ const parsed = parseUtility(className);
92
+ const rule = compileUtility(
93
+ parsed,
94
+ theme,
95
+ scratch,
96
+ customVariantMap,
97
+ warnSeen,
98
+ breakpointWeights,
99
+ variantMemo,
100
+ detail
101
+ );
102
+ let entry;
103
+ if (rule) {
104
+ entry = {
105
+ validation: OK,
106
+ explanation: {
107
+ parsed,
108
+ declarations: detail.declarations ? [...detail.declarations] : [],
109
+ selector: rule.selector,
110
+ css: rule.css,
111
+ sortKey: rule.sortKey
112
+ }
113
+ };
114
+ } else if (detail.reason === "unknown-variant" && detail.variant !== null) {
115
+ const suggestion = findClosest(detail.variant, variantSuggestionCorpus());
116
+ entry = {
117
+ validation: {
118
+ ok: false,
119
+ reason: "unknown-variant",
120
+ offender: detail.variant,
121
+ ...suggestion ? { suggestion } : {}
122
+ },
123
+ explanation: null
124
+ };
125
+ } else {
126
+ const base = baseName(className, parsed);
127
+ if (parsed.arbitrary || parsed.arbitraryProperty !== null) {
128
+ entry = {
129
+ validation: { ok: false, reason: "invalid-arbitrary", offender: base },
130
+ explanation: null
131
+ };
132
+ } else {
133
+ const suggestion = findClosest(base, utilitySuggestionCorpus());
134
+ entry = {
135
+ validation: {
136
+ ok: false,
137
+ reason: "unknown-utility",
138
+ offender: base,
139
+ ...suggestion ? { suggestion } : {}
140
+ },
141
+ explanation: null
142
+ };
143
+ }
144
+ }
145
+ cache.set(className, entry);
146
+ return entry;
147
+ }
148
+ return {
149
+ theme,
150
+ validate: (className) => resolve(className).validation,
151
+ explain: (className) => resolve(className).explanation,
152
+ variants
153
+ };
154
+ }
155
+
156
+ // src/theme/swatch.ts
157
+ var CANONICAL_COLOR_STOPS = Object.freeze([
158
+ 50,
159
+ 100,
160
+ 150,
161
+ 200,
162
+ 250,
163
+ 300,
164
+ 350,
165
+ 400,
166
+ 450,
167
+ 500,
168
+ 550,
169
+ 600,
170
+ 650,
171
+ 700,
172
+ 750,
173
+ 800,
174
+ 850,
175
+ 900,
176
+ 950
177
+ ]);
178
+ function channelHex(value) {
179
+ return Math.round(Math.min(1, Math.max(0, value)) * 255).toString(16).padStart(2, "0");
180
+ }
181
+ function oklchToHex(l, c, h) {
182
+ const [labL, labA, labB] = oklchToOklab(l, c, h);
183
+ const [r, g, b] = oklabToLinearSrgb(labL, labA, labB);
184
+ return `#${channelHex(linearToSrgb(r))}${channelHex(linearToSrgb(g))}${channelHex(linearToSrgb(b))}`;
185
+ }
186
+ var OKLCH_TEXT_RE = /^oklch\(\s*([\d.]+%?)\s+([\d.]+)\s+([\d.-]+)\s*(?:\/\s*[\d.]+%?\s*)?\)$/i;
187
+ var HEX_TEXT_RE = /^#(?:[0-9a-f]{3,4}|[0-9a-f]{6}|[0-9a-f]{8})$/i;
188
+ function cssColorToHex(css) {
189
+ const text = css.trim();
190
+ if (HEX_TEXT_RE.test(text)) {
191
+ if (text.length === 4 || text.length === 5) {
192
+ return `#${text[1]}${text[1]}${text[2]}${text[2]}${text[3]}${text[3]}`.toLowerCase();
193
+ }
194
+ return text.slice(0, 7).toLowerCase();
195
+ }
196
+ const match = OKLCH_TEXT_RE.exec(text);
197
+ if (match) {
198
+ const rawL = match[1];
199
+ const l = rawL.endsWith("%") ? Number.parseFloat(rawL) / 100 : Number.parseFloat(rawL);
200
+ const c = Number.parseFloat(match[2]);
201
+ const h = Number.parseFloat(match[3]);
202
+ if (Number.isFinite(l) && Number.isFinite(c) && Number.isFinite(h)) {
203
+ return oklchToHex(l, c, h);
204
+ }
205
+ }
206
+ return null;
207
+ }
208
+ var MAX_ALIAS_HOPS = 8;
209
+ var SEMANTIC_SWATCHES = Object.freeze({
210
+ paper: {
211
+ light: { css: "oklch(1 0 0)", hex: "#ffffff" },
212
+ dark: { css: "oklch(0 0 0)", hex: "#000000" }
213
+ },
214
+ ink: {
215
+ light: { css: "oklch(0 0 0)", hex: "#000000" },
216
+ dark: { css: "oklch(1 0 0)", hex: "#ffffff" }
217
+ },
218
+ white: {
219
+ light: { css: "oklch(1 0 0)", hex: "#ffffff" },
220
+ dark: null
221
+ },
222
+ black: {
223
+ light: { css: "oklch(0 0 0)", hex: "#000000" },
224
+ dark: null
225
+ }
226
+ });
227
+ function swatchFromCss(css) {
228
+ return { css, hex: cssColorToHex(css) };
229
+ }
230
+ function resolveColorSwatch(theme, name, stop = 500) {
231
+ let def = theme.colors[name];
232
+ for (let hop = 0; def && def.type === "alias" && hop < MAX_ALIAS_HOPS; hop++) {
233
+ def = theme.colors[def.source];
234
+ }
235
+ if (!def || def.type === "alias") {
236
+ return SEMANTIC_SWATCHES[name] ?? null;
237
+ }
238
+ switch (def.type) {
239
+ case "generative": {
240
+ const light = generateStop(def, stop);
241
+ const lightSwatch = {
242
+ css: formatOklch(light.l, light.c, light.h),
243
+ hex: oklchToHex(light.l, light.c, light.h)
244
+ };
245
+ if (theme.darkConfig.mode === "off" || def.dark?.strategy === "fixed") {
246
+ return { light: lightSwatch, dark: null };
247
+ }
248
+ const dark = computeDarkStop(def, stop, theme.darkConfig, def.dark);
249
+ return {
250
+ light: lightSwatch,
251
+ dark: {
252
+ css: formatOklch(dark.l, dark.c, dark.h),
253
+ hex: oklchToHex(dark.l, dark.c, dark.h)
254
+ }
255
+ };
256
+ }
257
+ case "explicit":
258
+ return { light: swatchFromCss(def.value), dark: null };
259
+ case "pair":
260
+ return {
261
+ light: swatchFromCss(def.light),
262
+ dark: theme.darkConfig.mode === "off" ? null : swatchFromCss(def.dark)
263
+ };
264
+ case "keyword":
265
+ return null;
266
+ }
267
+ }
268
+ function listThemeTokens(theme) {
269
+ return {
270
+ colors: Object.entries(theme.colors).map(([name, def]) => ({ name, kind: def.type })),
271
+ colorStops: CANONICAL_COLOR_STOPS,
272
+ spacingBase: theme.spacing.base,
273
+ textSizes: Object.entries(theme.text).map(([name, def]) => ({
274
+ name,
275
+ fontSize: def.fontSize,
276
+ lineHeight: def.lineHeight
277
+ })),
278
+ breakpoints: { ...theme.breakpoints },
279
+ rounded: { ...theme.rounded },
280
+ shadows: { ...theme.shadows },
281
+ weights: { ...theme.weights },
282
+ easing: { ...theme.easing },
283
+ blur: { ...theme.blur },
284
+ z: { ...theme.z },
285
+ leading: { ...theme.leading },
286
+ tracking: { ...theme.tracking },
287
+ opacity: { ...theme.opacity },
288
+ duration: { ...theme.duration },
289
+ fonts: theme.fonts.map((slot) => ({ slot: slot.slot, family: slot.family })),
290
+ animations: Object.keys(theme.animations)
291
+ };
292
+ }
293
+
294
+ // src/editor/session.ts
295
+ function createEditorSession(options = {}) {
296
+ let css = options.css ?? "";
297
+ let analysis = null;
298
+ let inspector = null;
299
+ let enumeration = null;
300
+ let tokens = null;
301
+ let snapshot = null;
302
+ const ensureAnalysis = () => {
303
+ analysis ??= analyzeProjectCSS(css);
304
+ return analysis;
305
+ };
306
+ const ensureSnapshot = () => {
307
+ snapshot ??= createThemeSnapshot(ensureAnalysis().theme);
308
+ return snapshot;
309
+ };
310
+ return {
311
+ get css() {
312
+ return css;
313
+ },
314
+ setCss(next) {
315
+ if (next === css) return;
316
+ css = next;
317
+ analysis = null;
318
+ inspector = null;
319
+ enumeration = null;
320
+ tokens = null;
321
+ snapshot = null;
322
+ },
323
+ get theme() {
324
+ return ensureAnalysis().theme;
325
+ },
326
+ get diagnostics() {
327
+ return ensureAnalysis().diagnostics;
328
+ },
329
+ get inspector() {
330
+ inspector ??= createClassInspector(ensureAnalysis().theme);
331
+ return inspector;
332
+ },
333
+ enumerate() {
334
+ enumeration ??= enumerateClassNames(ensureAnalysis().theme);
335
+ return enumeration;
336
+ },
337
+ tokens() {
338
+ tokens ??= listThemeTokens(ensureAnalysis().theme);
339
+ return tokens;
340
+ },
341
+ snapshot: ensureSnapshot,
342
+ analyzeMerge(classes) {
343
+ return analyzeMerge(classes, ensureSnapshot());
344
+ },
345
+ extractCandidates(content, path) {
346
+ return extractClassCandidates({ content, path });
347
+ },
348
+ swatch(name, stop) {
349
+ return resolveColorSwatch(ensureAnalysis().theme, name, stop);
350
+ }
351
+ };
352
+ }
353
+
354
+ // src/entries/editor.ts
355
+ var version = true ? "0.2.2" : "0.0.0-dev";
356
+ var EDITOR_API_VERSION = 1;
357
+ var editorCapabilities = Object.freeze([
358
+ "class-candidates",
359
+ "css-entry-detection",
360
+ "theme-analysis",
361
+ "class-inspection",
362
+ "variant-list",
363
+ "class-enumeration",
364
+ "merge-analysis",
365
+ "structured-diagnostics",
366
+ "color-swatches",
367
+ "editor-session"
368
+ ]);
369
+ export {
370
+ CANONICAL_COLOR_STOPS,
371
+ CLASS_HELPER_NAMES,
372
+ CSS_ENTRY_CANDIDATES,
373
+ EDITOR_API_VERSION,
374
+ RI_IMPORT_SPECIFIERS,
375
+ UTILITY_VALUE_SPACES,
376
+ VARIANT_HELPER_NAMES,
377
+ analyzeMerge,
378
+ analyzeProjectCSS,
379
+ createClassInspector,
380
+ createEditorSession,
381
+ createThemeSnapshot,
382
+ cssColorToHex,
383
+ defaultTheme,
384
+ diagnosticFromWarning,
385
+ editorCapabilities,
386
+ enumerateClassNames,
387
+ expandVariantGroups,
388
+ extractClassCandidates,
389
+ extractClasses,
390
+ extractClassesFromSource,
391
+ findClosest,
392
+ hasRIActivation,
393
+ isSourceFile,
394
+ listThemeTokens,
395
+ listVariants,
396
+ oklchToHex,
397
+ parseUtility,
398
+ resolveColorSwatch,
399
+ severityForCode,
400
+ version,
401
+ warningCode
402
+ };
@@ -165,6 +165,34 @@ declare function ri(...inputs: ClassInput[]): string;
165
165
  * ri('p-2 bg-red-500', 'p-4') // → 'bg-red-500 p-4'
166
166
  */
167
167
  declare function createRi(snapshot?: CompilationSnapshot): (...inputs: ClassInput[]) => string;
168
+ interface MergeDrop {
169
+ index: number;
170
+ className: string;
171
+ /** Ascending indices of the surviving classes that together claimed every
172
+ * CSS property this class sets (px-4 + py-4 jointly dominate p-2). */
173
+ overriddenBy: number[];
174
+ }
175
+ interface MergeAnalysis {
176
+ /** The merged output — identical to ri()'s result for this token list. */
177
+ output: string;
178
+ /** Indices of surviving classes, ascending. */
179
+ kept: number[];
180
+ /** Dropped classes with attribution, ascending by index. */
181
+ dropped: MergeDrop[];
182
+ }
183
+ /**
184
+ * Explain ri()'s conflict resolution for a list of class tokens — which
185
+ * classes the right-most-wins scan drops, and which survivors claimed their
186
+ * properties. Powers "this class is overridden" editor diagnostics.
187
+ *
188
+ * Unlike ri(), the input is pre-tokenized: one class per element, no falsy
189
+ * filtering, no whitespace splitting — exactly the token list an editor
190
+ * extracts from one class attribute. `snapshot` binds custom utilities, text
191
+ * sizes, and color names the same way createRi(snapshot) does (editors build
192
+ * one with createThemeSnapshot()); without it, the module-level state of the
193
+ * most recent compile applies. Uncached — call sites own their memoization.
194
+ */
195
+ declare function analyzeMerge(classes: readonly string[], snapshot?: CompilationSnapshot): MergeAnalysis;
168
196
  /**
169
197
  * Create a fresh compilation context.
170
198
  * Called at the start of each compile() pass.
@@ -194,46 +222,4 @@ declare function registerCustomFontFamilies(ctx: CompilationContext, families: s
194
222
  declare function registerColorNames(ctx: CompilationContext, names: string[]): void;
195
223
  declare function finalizeCompilationContext(ctx: CompilationContext): CompilationSnapshot;
196
224
 
197
- /**
198
- * `safelist()` — declare utility classes that must be emitted regardless of
199
- * whether the consumer's source files reference them directly.
200
- *
201
- * At runtime this is a plain identity-join: pass any number of strings (and
202
- * falsy values, which are filtered) and receive a single space-joined string
203
- * suitable for `className`. The function performs no global registration, has
204
- * no side effects, and is tree-shake-safe.
205
- *
206
- * The build-time meaning comes from the scanner: when the source-file
207
- * extractor encounters a `safelist(...)` call, it extracts every literal
208
- * string argument as a class declaration — so the classes get emitted in the
209
- * final CSS even though the consumer's source never names them literally.
210
- *
211
- * Primary use case is component libraries that ship classNames inside their
212
- * bundled code (e.g. a curated icon set whose strokes are described by
213
- * utility classes). The library wraps its declarations in `safelist(...)`,
214
- * the consumer's setup points the scanner at the library's `dist/`, and the
215
- * classes flow through unchanged. The Vite plugin auto-discovers libraries
216
- * that opt in via a `rainbowindex.safelistSources` field in their
217
- * `package.json`, so consumers typically don't have to add `@source` lines
218
- * by hand.
219
- *
220
- * const ICON_BASE = safelist("stroke-cap-round", "stroke-join-round");
221
- * const SidebarLeft = defineIcon({
222
- * primitives: SIDEBAR,
223
- * className: safelist(ICON_BASE, "-scale-x-100"),
224
- * });
225
- *
226
- * Scanner contract:
227
- * - Only STATIC string literals at the call site are extracted. Values
228
- * passed through variables (`safelist(ICON_BASE, ...)`) won't be re-read
229
- * at the outer call site, but the original `safelist("stroke-cap-round",
230
- * ...)` that produced `ICON_BASE` is itself extracted, so the classes are
231
- * still covered.
232
- * - Template literals with no `${…}` interpolation are extracted; templates
233
- * with interpolation are skipped.
234
- * - Falsy arguments are dropped at runtime so conditional fragments compose
235
- * naturally: `safelist("flex", side === "left" && "flex-row-reverse")`.
236
- */
237
- declare function safelist(...parts: ReadonlyArray<string | false | null | undefined>): string;
238
-
239
- export { type AnimationDefinition as A, type ColorDefinition as C, DEFAULT_TEXT_SIZES as D, type FluidConfig as F, type TextSize as T, type CompilationContext as a, type CompilationSnapshot as b, type Theme as c, createCompilationContext as d, createRi as e, defaultTheme as f, finalizeCompilationContext as g, registerCustomFontFamilies as h, registerCustomTextSizes as i, registerCustomUtility as j, ri as k, type DarkModeConfig as l, type CornerShape as m, registerColorNames as r, safelist as s };
225
+ export { type AnimationDefinition as A, type ColorDefinition as C, DEFAULT_TEXT_SIZES as D, type FluidConfig as F, type MergeAnalysis as M, type TextSize as T, type CompilationContext as a, type CompilationSnapshot as b, type Theme as c, createCompilationContext as d, createRi as e, defaultTheme as f, finalizeCompilationContext as g, registerCustomFontFamilies as h, registerCustomTextSizes as i, registerCustomUtility as j, ri as k, type DarkModeConfig as l, type CornerShape as m, type MergeDrop as n, analyzeMerge as o, registerColorNames as r };