tailwindcss-patch 9.4.1 → 9.4.3

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 (34) hide show
  1. package/README.md +69 -0
  2. package/dist/{cli-CBVPia5Z.js → cli-BztQHMRp.js} +63 -64
  3. package/dist/{cli-CgBdW1U5.mjs → cli-D0jXMGXf.mjs} +1 -1
  4. package/dist/cli.js +5 -6
  5. package/dist/cli.mjs +2 -2
  6. package/dist/commands/cli-runtime.d.mts +1 -1
  7. package/dist/commands/cli-runtime.d.ts +1 -1
  8. package/dist/commands/cli-runtime.js +6 -6
  9. package/dist/commands/cli-runtime.mjs +2 -2
  10. package/dist/{dist-B1VBpHtd.js → dist-DDcbvOwe.js} +2 -2
  11. package/dist/index.d.mts +105 -5
  12. package/dist/index.d.ts +105 -5
  13. package/dist/index.js +325 -62
  14. package/dist/index.mjs +253 -4
  15. package/dist/{migrate-config-DqknZpUe.mjs → validate-Bug_WYcU.mjs} +193 -93
  16. package/dist/{validate-CaJv2g5K.d.ts → validate-BuqRodYI.d.ts} +65 -16
  17. package/dist/{migrate-config-Dn9OTXpO.js → validate-DbuKewV-.js} +257 -100
  18. package/dist/{validate-Dr7IkGU8.d.mts → validate-oAkURzUC.d.mts} +65 -15
  19. package/package.json +9 -9
  20. package/src/extraction/candidate-extractor.ts +76 -12
  21. package/src/public-api.ts +54 -13
  22. package/src/style-candidates.ts +35 -0
  23. package/src/style-generator.ts +80 -0
  24. package/src/v3/index.ts +11 -0
  25. package/src/v3/style-generator.ts +384 -0
  26. package/src/v4/bare-arbitrary-values.ts +127 -2
  27. package/src/v4/engine.ts +5 -2
  28. package/src/v4/index.ts +20 -4
  29. package/src/v4/node-adapter.ts +1 -1
  30. package/src/v4/source-scan.ts +1 -1
  31. package/src/v4/style-generator.ts +44 -0
  32. package/src/v4/types.ts +23 -0
  33. package/dist/chunk-8l464Juk.js +0 -28
  34. /package/dist/{dist-BjUV1yEM.mjs → dist-CxmNpfyy.mjs} +0 -0
@@ -0,0 +1,384 @@
1
+ import type { Node } from 'postcss'
2
+ import type { Config } from 'tailwindcss'
3
+ import type { TailwindStyleCandidateOptions, TailwindStyleSource } from '../style-candidates'
4
+ import type { TailwindcssRuntimeContext } from '../types'
5
+ import { createRequire } from 'node:module'
6
+ import process from 'node:process'
7
+ import path from 'pathe'
8
+ import postcss from 'postcss'
9
+ import { collectTailwindStyleCandidates } from '../style-candidates'
10
+
11
+ type NodeRequire = ReturnType<typeof createRequire>
12
+
13
+ interface TailwindV3CreateContextModule {
14
+ createContext: (
15
+ tailwindConfig: Config,
16
+ changedContent?: Array<{ content?: string, extension?: string }>,
17
+ root?: ReturnType<typeof postcss.root>,
18
+ ) => TailwindcssRuntimeContext
19
+ }
20
+
21
+ interface TailwindV3GenerateRulesModule {
22
+ generateRules: (
23
+ candidates: Set<string>,
24
+ context: TailwindcssRuntimeContext,
25
+ ) => Array<[unknown, Node]>
26
+ }
27
+
28
+ interface TailwindV3ProcessResult {
29
+ css: string
30
+ messages: Array<Record<string, unknown>>
31
+ }
32
+
33
+ interface TailwindV3CollapseRulesModule {
34
+ default?: (context: TailwindcssRuntimeContext) => (root: postcss.Root, result: TailwindV3ProcessResult) => void
35
+ }
36
+
37
+ interface TailwindV3ProcessTailwindFeaturesModule {
38
+ default?: (
39
+ setupContext: () => (root: postcss.Root) => TailwindcssRuntimeContext,
40
+ ) => (root: postcss.Root, result: TailwindV3ProcessResult) => Promise<TailwindcssRuntimeContext>
41
+ }
42
+
43
+ interface TailwindV3ResolveDefaultsAtRulesModule {
44
+ default?: (context: TailwindcssRuntimeContext) => (root: postcss.Root, result: TailwindV3ProcessResult) => void
45
+ }
46
+
47
+ interface TailwindV3ValidateConfigModule {
48
+ validateConfig: (config: unknown) => Config
49
+ }
50
+
51
+ interface TailwindV3SharedStateModule {
52
+ NOT_ON_DEMAND?: string
53
+ }
54
+
55
+ type ResolveConfig = (config: Config) => Config
56
+
57
+ interface TailwindV3Offsets {
58
+ sort: <T>(rules: Array<[unknown, T]>) => Array<[{
59
+ layer: 'base' | 'components' | 'defaults' | 'utilities' | 'variants'
60
+ }, T]>
61
+ }
62
+
63
+ export interface TailwindV3StyleGenerateOptions extends TailwindStyleCandidateOptions {
64
+ /**
65
+ * Tailwind v3 package name or alias. Defaults to `tailwindcss`.
66
+ */
67
+ packageName?: string
68
+ /**
69
+ * `createRequire` base used to resolve the Tailwind v3 package.
70
+ */
71
+ cwd?: string
72
+ /**
73
+ * Inline Tailwind v3 config. `content` is injected from collected candidates.
74
+ */
75
+ config?: Partial<Config>
76
+ /**
77
+ * Generate all layers by default. Pass a subset to emit only selected layers.
78
+ */
79
+ layers?: TailwindV3StyleLayer[]
80
+ }
81
+
82
+ export type TailwindV3StyleLayer = 'base' | 'components' | 'utilities' | 'variants'
83
+
84
+ export interface TailwindV3RawStyleGenerateOptions extends TailwindStyleCandidateOptions {
85
+ /**
86
+ * Tailwind v3 package name or alias. Defaults to `tailwindcss`.
87
+ */
88
+ packageName?: string
89
+ /**
90
+ * `createRequire` base used to resolve the Tailwind v3 package.
91
+ */
92
+ cwd?: string
93
+ /**
94
+ * Tailwind v3 entry CSS. Defaults to `@tailwind utilities;`.
95
+ */
96
+ css?: string
97
+ /**
98
+ * Inline Tailwind v3 config. Candidate content is injected automatically.
99
+ */
100
+ config?: Partial<Config>
101
+ /**
102
+ * Directly append generated utility rules when the CSS is exactly `@tailwind utilities;`.
103
+ * This mirrors Tailwind v3's internal fast path and keeps output order aligned.
104
+ */
105
+ directUtilitiesOnly?: boolean | 'auto'
106
+ }
107
+
108
+ export interface TailwindV3RawStyleGenerateResult {
109
+ version: 3
110
+ css: string
111
+ tokens: Set<string>
112
+ classSet: Set<string>
113
+ context: TailwindcssRuntimeContext
114
+ dependencies: string[]
115
+ sources: TailwindStyleSource[]
116
+ config: Config
117
+ }
118
+
119
+ export interface TailwindV3StyleGenerateResult {
120
+ version: 3
121
+ css: string
122
+ tokens: Set<string>
123
+ classSet: Set<string>
124
+ sources: TailwindStyleSource[]
125
+ config: Config
126
+ }
127
+
128
+ function createPackageRequire(cwd?: string): NodeRequire {
129
+ return createRequire(path.join(path.resolve(cwd ?? process.cwd()), 'package.json'))
130
+ }
131
+
132
+ function createDefaultTailwindV3Config(tokens: Set<string>): Config {
133
+ return {
134
+ content: [
135
+ {
136
+ raw: [...tokens].join(' '),
137
+ extension: 'html',
138
+ },
139
+ ],
140
+ theme: {
141
+ extend: {},
142
+ },
143
+ plugins: [],
144
+ }
145
+ }
146
+
147
+ function getDefaultExport<T>(module: { default?: T } | T): T {
148
+ if (module && typeof module === 'object' && 'default' in module) {
149
+ return module.default as T
150
+ }
151
+ return module as T
152
+ }
153
+
154
+ function loadTailwindV3Modules(options: Pick<TailwindV3StyleGenerateOptions, 'cwd' | 'packageName'>) {
155
+ const packageName = options.packageName ?? 'tailwindcss'
156
+ const moduleRequire = createPackageRequire(options.cwd)
157
+ const resolveConfig = getDefaultExport<ResolveConfig>(
158
+ moduleRequire(`${packageName}/lib/public/resolve-config`) as { default?: ResolveConfig } | ResolveConfig,
159
+ )
160
+ const contextModule = moduleRequire(`${packageName}/lib/lib/setupContextUtils`) as TailwindV3CreateContextModule
161
+ const generateRulesModule = moduleRequire(`${packageName}/lib/lib/generateRules`) as TailwindV3GenerateRulesModule
162
+ const collapseAdjacentRulesModule = moduleRequire(`${packageName}/lib/lib/collapseAdjacentRules`) as TailwindV3CollapseRulesModule
163
+ const collapseDuplicateDeclarationsModule = moduleRequire(`${packageName}/lib/lib/collapseDuplicateDeclarations`) as TailwindV3CollapseRulesModule
164
+ const processTailwindFeaturesModule = moduleRequire(`${packageName}/lib/processTailwindFeatures`) as TailwindV3ProcessTailwindFeaturesModule
165
+ const resolveDefaultsAtRulesModule = moduleRequire(`${packageName}/lib/lib/resolveDefaultsAtRules`) as TailwindV3ResolveDefaultsAtRulesModule
166
+ const sharedStateModule = moduleRequire(`${packageName}/lib/lib/sharedState`) as TailwindV3SharedStateModule
167
+ const validateConfigModule = moduleRequire(`${packageName}/lib/util/validateConfig.js`) as TailwindV3ValidateConfigModule
168
+ return {
169
+ collapseAdjacentRules: getDefaultExport(collapseAdjacentRulesModule),
170
+ collapseDuplicateDeclarations: getDefaultExport(collapseDuplicateDeclarationsModule),
171
+ createContext: contextModule.createContext,
172
+ generateRules: generateRulesModule.generateRules,
173
+ notOnDemandCandidate: sharedStateModule.NOT_ON_DEMAND ?? '*',
174
+ processTailwindFeatures: getDefaultExport(processTailwindFeaturesModule),
175
+ resolveDefaultsAtRules: getDefaultExport(resolveDefaultsAtRulesModule),
176
+ resolveConfig,
177
+ validateConfig: validateConfigModule.validateConfig,
178
+ }
179
+ }
180
+
181
+ function createRawContentEntries(candidates: Iterable<string>, sources: TailwindStyleSource[]) {
182
+ const entries: Array<{ raw: string, extension: string }> = []
183
+ const candidateContent = [...candidates].join(' ')
184
+ if (candidateContent.length > 0) {
185
+ entries.push({
186
+ raw: candidateContent,
187
+ extension: 'html',
188
+ })
189
+ }
190
+ for (const source of sources) {
191
+ entries.push({
192
+ raw: source.content,
193
+ extension: source.extension ?? 'html',
194
+ })
195
+ }
196
+ return entries
197
+ }
198
+
199
+ function createChangedContentEntries(candidates: Iterable<string>, sources: TailwindStyleSource[]) {
200
+ return createRawContentEntries(candidates, sources).map(entry => ({
201
+ content: entry.raw,
202
+ extension: entry.extension,
203
+ }))
204
+ }
205
+
206
+ function createTailwindConfigWithContent(
207
+ config: Partial<Config> | undefined,
208
+ tokens: Set<string>,
209
+ sources: TailwindStyleSource[],
210
+ ) {
211
+ const userContent = config?.content
212
+ return {
213
+ ...createDefaultTailwindV3Config(tokens),
214
+ ...(config ?? {}),
215
+ content: [
216
+ ...(Array.isArray(userContent) ? userContent : []),
217
+ ...createRawContentEntries(tokens, sources),
218
+ ],
219
+ } as Config
220
+ }
221
+
222
+ function isDirectUtilitiesOnlyCss(css: string) {
223
+ return css.replace(/\s+/g, '') === '@tailwindutilities;'
224
+ }
225
+
226
+ function sortCandidates(candidates: Iterable<string>) {
227
+ return [...candidates].sort((a, z) => {
228
+ if (a === z) {
229
+ return 0
230
+ }
231
+ return a < z ? -1 : 1
232
+ })
233
+ }
234
+
235
+ function appendUtilityRules(
236
+ root: postcss.Root,
237
+ context: TailwindcssRuntimeContext,
238
+ rules: Array<[unknown, Node]>,
239
+ ) {
240
+ const sortedRules = (context.offsets as unknown as TailwindV3Offsets).sort(rules)
241
+ for (const [sort, rule] of sortedRules) {
242
+ const tailwindRaw = rule.raws.tailwind as { parentLayer?: string } | undefined
243
+ if (sort.layer === 'utilities' || (sort.layer === 'variants' && tailwindRaw?.parentLayer === 'utilities')) {
244
+ root.append(rule.clone())
245
+ }
246
+ }
247
+ }
248
+
249
+ function collectClassSet(context: TailwindcssRuntimeContext, notOnDemandCandidate: string) {
250
+ const classSet = new Set<string>()
251
+ for (const candidate of context.classCache.keys()) {
252
+ if (String(candidate) !== String(notOnDemandCandidate)) {
253
+ classSet.add(candidate)
254
+ }
255
+ }
256
+ return classSet
257
+ }
258
+
259
+ function collectDependencyMessages(result: TailwindV3ProcessResult) {
260
+ const dependencies = new Set<string>()
261
+ for (const message of result.messages) {
262
+ const file = message.file
263
+ if (message.type === 'dependency' && typeof file === 'string') {
264
+ dependencies.add(file)
265
+ }
266
+ }
267
+ return [...dependencies]
268
+ }
269
+
270
+ function buildStylesheetNodes(
271
+ rules: Array<[unknown, Node]>,
272
+ context: TailwindcssRuntimeContext,
273
+ layers: TailwindV3StyleLayer[],
274
+ ) {
275
+ const sortedRules = (context.offsets as unknown as TailwindV3Offsets).sort(rules)
276
+ const nodes: Node[] = []
277
+ const layerSet = new Set<TailwindV3StyleLayer>(layers)
278
+
279
+ for (const [sort, rule] of sortedRules) {
280
+ const layer = sort.layer === 'defaults' ? 'base' : sort.layer
281
+ if (layerSet.has(layer)) {
282
+ nodes.push(rule.clone())
283
+ }
284
+ }
285
+ return nodes
286
+ }
287
+
288
+ function createRootFromNodes(nodes: Node[]) {
289
+ const root = postcss.root()
290
+ for (const node of nodes) {
291
+ root.append(node)
292
+ }
293
+ return root
294
+ }
295
+
296
+ export async function generateTailwindV3Style(
297
+ options: TailwindV3StyleGenerateOptions = {},
298
+ ): Promise<TailwindV3StyleGenerateResult> {
299
+ const tokens = await collectTailwindStyleCandidates(options)
300
+ const { createContext, generateRules, resolveConfig } = loadTailwindV3Modules(options)
301
+ const userContent = options.config?.content
302
+ const config = resolveConfig({
303
+ ...createDefaultTailwindV3Config(tokens),
304
+ ...(options.config ?? {}),
305
+ content: [
306
+ ...(Array.isArray(userContent) ? userContent : []),
307
+ {
308
+ raw: [...tokens].join(' '),
309
+ extension: 'html',
310
+ },
311
+ ],
312
+ } as Config)
313
+ const root = postcss.root()
314
+ const context = createContext(config, [], root)
315
+ const rules = generateRules(tokens, context)
316
+ const nodes = buildStylesheetNodes(rules, context, options.layers ?? ['base', 'components', 'utilities', 'variants'])
317
+ const css = createRootFromNodes(nodes).toString()
318
+
319
+ return {
320
+ version: 3,
321
+ css,
322
+ tokens,
323
+ classSet: new Set(context.classCache.keys()),
324
+ sources: options.sources ?? [],
325
+ config,
326
+ }
327
+ }
328
+
329
+ export async function generateTailwindV3RawStyle(
330
+ options: TailwindV3RawStyleGenerateOptions = {},
331
+ ): Promise<TailwindV3RawStyleGenerateResult> {
332
+ const tokens = await collectTailwindStyleCandidates(options)
333
+ const {
334
+ collapseAdjacentRules,
335
+ collapseDuplicateDeclarations,
336
+ createContext,
337
+ generateRules,
338
+ notOnDemandCandidate,
339
+ processTailwindFeatures,
340
+ resolveConfig,
341
+ resolveDefaultsAtRules,
342
+ validateConfig,
343
+ } = loadTailwindV3Modules(options)
344
+ const css = options.css ?? '@tailwind utilities;'
345
+ const config = validateConfig(resolveConfig(createTailwindConfigWithContent(options.config, tokens, options.sources ?? [])))
346
+ const root = postcss.parse(css, {
347
+ from: undefined,
348
+ })
349
+ const result: TailwindV3ProcessResult = {
350
+ css: '',
351
+ messages: [],
352
+ }
353
+ const changedContent = createChangedContentEntries(tokens, options.sources ?? [])
354
+ const shouldUseDirectUtilities = options.directUtilitiesOnly === true
355
+ || (options.directUtilitiesOnly !== false && isDirectUtilitiesOnlyCss(css))
356
+ let context: TailwindcssRuntimeContext
357
+
358
+ if (shouldUseDirectUtilities) {
359
+ context = createContext(config, changedContent, root)
360
+ generateRules(new Set(sortCandidates([notOnDemandCandidate, ...tokens])), context)
361
+ root.removeAll()
362
+ appendUtilityRules(root, context, [...context.ruleCache])
363
+ resolveDefaultsAtRules(context)(root, result)
364
+ collapseAdjacentRules(context)(root, result)
365
+ collapseDuplicateDeclarations(context)(root, result)
366
+ }
367
+ else {
368
+ const setupContext = () => {
369
+ return (currentRoot: postcss.Root) => createContext(config, changedContent, currentRoot)
370
+ }
371
+ context = await processTailwindFeatures(setupContext)(root, result)
372
+ }
373
+
374
+ return {
375
+ version: 3,
376
+ css: root.toString(),
377
+ tokens,
378
+ classSet: collectClassSet(context, notOnDemandCandidate),
379
+ context,
380
+ dependencies: collectDependencyMessages(result),
381
+ sources: options.sources ?? [],
382
+ config,
383
+ }
384
+ }
@@ -10,6 +10,12 @@ export interface BareArbitraryValueResolveResult {
10
10
  canonicalCandidate: string
11
11
  }
12
12
 
13
+ export interface BareArbitraryValueSourceCandidate {
14
+ rawCandidate: string
15
+ start: number
16
+ end: number
17
+ }
18
+
13
19
  const DEFAULT_BARE_ARBITRARY_VALUE_UNITS = [
14
20
  '%',
15
21
  'px',
@@ -41,6 +47,8 @@ const DEFAULT_BARE_ARBITRARY_VALUE_UNITS = [
41
47
  const NUMBER_RE = /^-?(?:\d+|\d*\.\d+)$/
42
48
  const FUNCTION_VALUE_RE = /^[a-z_-][\w-]*\(/i
43
49
  const HEX_ESCAPE_RE = /^[\da-f]$/i
50
+ const ASPECT_RATIO_RE = /^\d+\/\d+$/
51
+ const ESCAPED_WHITESPACE_RE = /\\[nrt]/g
44
52
 
45
53
  function splitVariantPrefix(candidate: string) {
46
54
  let depth = 0
@@ -221,6 +229,10 @@ function normalizeBareArbitraryValueOptions(options: boolean | BareArbitraryValu
221
229
  }
222
230
  }
223
231
 
232
+ export function isBareArbitraryValuesEnabled(options: boolean | BareArbitraryValueOptions | undefined) {
233
+ return normalizeBareArbitraryValueOptions(options) !== undefined
234
+ }
235
+
224
236
  function normalizeEscapedValue(value: string) {
225
237
  let result = ''
226
238
  for (let index = 0; index < value.length; index++) {
@@ -276,13 +288,17 @@ function resolveValueWithUnit(body: string, units: string[]) {
276
288
  }
277
289
  }
278
290
 
279
- function resolveArbitraryValue(body: string, units: string[]) {
291
+ function resolveArbitraryValue(utility: string, body: string, units: string[]) {
280
292
  const value = normalizeEscapedValue(body)
281
293
  const withUnit = resolveValueWithUnit(value, units)
282
294
  if (withUnit) {
283
295
  return withUnit
284
296
  }
285
297
 
298
+ if (utility === 'aspect' && ASPECT_RATIO_RE.test(value)) {
299
+ return value
300
+ }
301
+
286
302
  if (isHexColorValue(value)) {
287
303
  return value
288
304
  }
@@ -292,6 +308,9 @@ function resolveArbitraryValue(body: string, units: string[]) {
292
308
  }
293
309
 
294
310
  if (FUNCTION_VALUE_RE.test(value) && value.endsWith(')') && isBalancedFunctionValue(value)) {
311
+ if (utility === 'text' && /^var\(/i.test(value)) {
312
+ return `color:${value}`
313
+ }
295
314
  return value
296
315
  }
297
316
  }
@@ -339,7 +358,7 @@ function resolveUtilityAndValue(body: string, units: string[]) {
339
358
  continue
340
359
  }
341
360
 
342
- const value = resolveArbitraryValue(rawValue, units)
361
+ const value = resolveArbitraryValue(utility, rawValue, units)
343
362
  if (value) {
344
363
  return {
345
364
  utility,
@@ -380,6 +399,112 @@ export function resolveBareArbitraryValueCandidate(
380
399
  }
381
400
  }
382
401
 
402
+ function isBareArbitrarySourceSplitter(char: string) {
403
+ return /\s/.test(char)
404
+ }
405
+
406
+ function isQuoteBoundary(content: string, start: number, index: number) {
407
+ const tokenPrefix = content.slice(start, index)
408
+ return tokenPrefix.length === 0 || !tokenPrefix.endsWith('-')
409
+ }
410
+
411
+ function trimBareArbitrarySourceToken(token: string, start: number) {
412
+ let nextToken = token
413
+ let nextStart = start
414
+ while (nextToken.length > 0 && /^[<{([]$/.test(nextToken[0]!)) {
415
+ nextToken = nextToken.slice(1)
416
+ nextStart++
417
+ }
418
+ while (nextToken.length > 0 && /^[>\],;]$/.test(nextToken[nextToken.length - 1]!)) {
419
+ nextToken = nextToken.slice(0, -1)
420
+ }
421
+ return {
422
+ token: nextToken,
423
+ start: nextStart,
424
+ }
425
+ }
426
+
427
+ function pushBareArbitrarySourceCandidate(
428
+ result: BareArbitraryValueSourceCandidate[],
429
+ token: string,
430
+ start: number,
431
+ options: boolean | BareArbitraryValueOptions | undefined,
432
+ ) {
433
+ const trimmed = trimBareArbitrarySourceToken(token, start)
434
+ if (!trimmed.token || trimmed.token.includes('=') || trimmed.token.includes('[') || trimmed.token.includes(']')) {
435
+ return
436
+ }
437
+ if (!resolveBareArbitraryValueCandidate(trimmed.token, options)) {
438
+ return
439
+ }
440
+ result.push({
441
+ rawCandidate: trimmed.token,
442
+ start: trimmed.start,
443
+ end: trimmed.start + trimmed.token.length,
444
+ })
445
+ }
446
+
447
+ export function extractBareArbitraryValueSourceCandidatesWithPositions(
448
+ content: string,
449
+ options?: boolean | BareArbitraryValueOptions,
450
+ ): BareArbitraryValueSourceCandidate[] {
451
+ if (!isBareArbitraryValuesEnabled(options)) {
452
+ return []
453
+ }
454
+
455
+ const normalized = content.includes('\\') ? content.replace(ESCAPED_WHITESPACE_RE, ' ') : content
456
+ const result: BareArbitraryValueSourceCandidate[] = []
457
+ let depth = 0
458
+ let quote: string | undefined
459
+ let start = 0
460
+
461
+ for (let index = 0; index < normalized.length; index++) {
462
+ const char = normalized[index]
463
+ if (char === undefined) {
464
+ continue
465
+ }
466
+ if (char === '\\') {
467
+ index++
468
+ continue
469
+ }
470
+
471
+ if (quote) {
472
+ if (char === quote) {
473
+ quote = undefined
474
+ }
475
+ }
476
+ else if ((char === '"' || char === '\'' || char === '`') && !isQuoteBoundary(normalized, start, index)) {
477
+ quote = char
478
+ }
479
+ else if (char === '(' || char === '{' || char === '[') {
480
+ depth++
481
+ }
482
+ else if (char === ')' || char === '}' || char === ']') {
483
+ depth = Math.max(0, depth - 1)
484
+ }
485
+
486
+ if (!isBareArbitrarySourceSplitter(char) && !((char === '"' || char === '\'' || char === '`') && depth === 0 && isQuoteBoundary(normalized, start, index))) {
487
+ continue
488
+ }
489
+
490
+ pushBareArbitrarySourceCandidate(result, normalized.slice(start, index), start, options)
491
+ start = index + 1
492
+ }
493
+
494
+ pushBareArbitrarySourceCandidate(result, normalized.slice(start), start, options)
495
+ return result
496
+ }
497
+
498
+ export function extractBareArbitraryValueSourceCandidates(
499
+ content: string,
500
+ options?: boolean | BareArbitraryValueOptions,
501
+ ) {
502
+ return [...new Set(
503
+ extractBareArbitraryValueSourceCandidatesWithPositions(content, options)
504
+ .map(candidate => candidate.rawCandidate),
505
+ )]
506
+ }
507
+
383
508
  // Based on the CSS.escape algorithm, scoped to class selector escaping.
384
509
  export function escapeCssClassName(value: string) {
385
510
  let result = ''
package/src/v4/engine.ts CHANGED
@@ -37,13 +37,16 @@ async function collectRawCandidates(
37
37
  compiledSources: TailwindV4SourcePattern[] = [],
38
38
  ) {
39
39
  const rawCandidates = new Set<string>()
40
+ const extractOptions = options?.bareArbitraryValues === undefined
41
+ ? undefined
42
+ : { bareArbitraryValues: options.bareArbitraryValues }
40
43
 
41
44
  for (const candidate of options?.candidates ?? []) {
42
45
  rawCandidates.add(candidate)
43
46
  }
44
47
 
45
48
  for (const candidateSource of options?.sources ?? []) {
46
- const candidates = await extractRawCandidatesWithPositions(candidateSource.content, candidateSource.extension)
49
+ const candidates = await extractRawCandidatesWithPositions(candidateSource.content, candidateSource.extension, extractOptions)
47
50
  for (const candidate of candidates) {
48
51
  rawCandidates.add(candidate.rawCandidate)
49
52
  }
@@ -51,7 +54,7 @@ async function collectRawCandidates(
51
54
 
52
55
  const filesystemSources = resolveScanSources(options, source, compiledRoot, compiledSources)
53
56
  if (filesystemSources.length > 0) {
54
- for (const candidate of await extractRawCandidates(filesystemSources)) {
57
+ for (const candidate of await extractRawCandidates(filesystemSources, extractOptions)) {
55
58
  rawCandidates.add(candidate)
56
59
  }
57
60
  }
package/src/v4/index.ts CHANGED
@@ -1,5 +1,14 @@
1
1
  export {
2
+ escapeCssClassName,
3
+ extractBareArbitraryValueSourceCandidates,
4
+ extractBareArbitraryValueSourceCandidatesWithPositions,
5
+ isBareArbitraryValuesEnabled,
6
+ resolveBareArbitraryValueCandidate,
7
+ } from './bare-arbitrary-values'
8
+ export {
9
+ canonicalizeBareArbitraryValueCandidates,
2
10
  extractTailwindV4InlineSourceCandidates,
11
+ replaceBareArbitraryValueSelectors,
3
12
  resolveValidTailwindV4Candidates,
4
13
  } from './candidates'
5
14
  export { createTailwindV4Engine } from './engine'
@@ -8,6 +17,11 @@ export {
8
17
  loadTailwindV4DesignSystem,
9
18
  loadTailwindV4NodeModule,
10
19
  } from './node-adapter'
20
+ export {
21
+ resolveTailwindV4Source,
22
+ resolveTailwindV4SourceFromPatchOptions,
23
+ tailwindV4SourceOptionsFromPatchOptions,
24
+ } from './source'
11
25
  export {
12
26
  createTailwindV4CompiledSourceEntries,
13
27
  createTailwindV4DefaultIgnoreSources,
@@ -30,10 +44,9 @@ export {
30
44
  TAILWIND_V4_IGNORED_FILES,
31
45
  } from './source-scan'
32
46
  export {
33
- resolveTailwindV4Source,
34
- resolveTailwindV4SourceFromPatchOptions,
35
- tailwindV4SourceOptionsFromPatchOptions,
36
- } from './source'
47
+ collectTailwindV4StyleCandidates,
48
+ generateTailwindV4Style,
49
+ } from './style-generator'
37
50
  export type {
38
51
  TailwindV4CandidateSource,
39
52
  TailwindV4CompiledSourceRoot,
@@ -45,4 +58,7 @@ export type {
45
58
  TailwindV4ResolvedSource,
46
59
  TailwindV4SourceOptions,
47
60
  TailwindV4SourcePattern,
61
+ TailwindV4StyleGenerateOptions,
62
+ TailwindV4StyleGenerateResult,
63
+ TailwindV4StyleSource,
48
64
  } from './types'
@@ -1,7 +1,7 @@
1
1
  import type {
2
+ TailwindV4CompiledSourceRoot,
2
3
  TailwindV4DesignSystem,
3
4
  TailwindV4ResolvedSource,
4
- TailwindV4CompiledSourceRoot,
5
5
  TailwindV4SourcePattern,
6
6
  } from './types'
7
7
  import { createRequire } from 'node:module'
@@ -3,8 +3,8 @@ import type { TailwindV4CompiledSourceRoot, TailwindV4SourcePattern } from './ty
3
3
  import { realpathSync } from 'node:fs'
4
4
  import { stat } from 'node:fs/promises'
5
5
  import process from 'node:process'
6
- import path from 'pathe'
7
6
  import micromatch from 'micromatch'
7
+ import path from 'pathe'
8
8
 
9
9
  export const TAILWIND_V4_IGNORED_CONTENT_DIRS = [
10
10
  '.git',
@@ -0,0 +1,44 @@
1
+ import type {
2
+ TailwindV4SourceOptions,
3
+ TailwindV4StyleGenerateOptions,
4
+ TailwindV4StyleGenerateResult,
5
+ } from './types'
6
+ import { collectTailwindStyleCandidates } from '../style-candidates'
7
+ import { createTailwindV4Engine } from './engine'
8
+ import { resolveTailwindV4Source } from './source'
9
+
10
+ function createSourceOptions(options: TailwindV4StyleGenerateOptions): TailwindV4SourceOptions {
11
+ return {
12
+ ...(options.projectRoot === undefined ? {} : { projectRoot: options.projectRoot }),
13
+ ...(options.cwd === undefined ? {} : { cwd: options.cwd }),
14
+ ...(options.base === undefined ? {} : { base: options.base }),
15
+ ...(options.baseFallbacks === undefined ? {} : { baseFallbacks: options.baseFallbacks }),
16
+ ...(options.css === undefined ? {} : { css: options.css }),
17
+ ...(options.cssSources === undefined ? {} : { cssSources: options.cssSources }),
18
+ ...(options.cssEntries === undefined ? {} : { cssEntries: options.cssEntries }),
19
+ ...(options.packageName === undefined ? {} : { packageName: options.packageName }),
20
+ }
21
+ }
22
+
23
+ export async function collectTailwindV4StyleCandidates(
24
+ options: Pick<TailwindV4StyleGenerateOptions, 'bareArbitraryValues' | 'candidates' | 'sources'>,
25
+ ): Promise<Set<string>> {
26
+ return collectTailwindStyleCandidates(options)
27
+ }
28
+
29
+ export async function generateTailwindV4Style(
30
+ options: TailwindV4StyleGenerateOptions = {},
31
+ ): Promise<TailwindV4StyleGenerateResult> {
32
+ const source = options.source ?? await resolveTailwindV4Source(createSourceOptions(options))
33
+ const candidates = await collectTailwindV4StyleCandidates(options)
34
+ const result = await createTailwindV4Engine(source).generate({
35
+ candidates,
36
+ ...(options.bareArbitraryValues === undefined ? {} : { bareArbitraryValues: options.bareArbitraryValues }),
37
+ ...(options.scanSources === undefined ? {} : { scanSources: options.scanSources }),
38
+ })
39
+ return {
40
+ ...result,
41
+ tokens: result.rawCandidates,
42
+ source,
43
+ }
44
+ }