tailwindcss 3.2.0 → 3.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.
@@ -328,6 +328,26 @@ export async function createProcessor(args, cliConfigPath) {
328
328
 
329
329
  return readInput()
330
330
  .then((css) => processor.process(css, { ...postcssOptions, from: input, to: output }))
331
+ .then((result) => {
332
+ if (!state.watcher) {
333
+ return result
334
+ }
335
+
336
+ env.DEBUG && console.time('Recording PostCSS dependencies')
337
+ for (let message of result.messages) {
338
+ if (message.type === 'dependency') {
339
+ state.contextDependencies.add(message.file)
340
+ }
341
+ }
342
+ env.DEBUG && console.timeEnd('Recording PostCSS dependencies')
343
+
344
+ // TODO: This needs to be in a different spot
345
+ env.DEBUG && console.time('Watch new files')
346
+ state.watcher.refreshWatchedFiles()
347
+ env.DEBUG && console.timeEnd('Watch new files')
348
+
349
+ return result
350
+ })
331
351
  .then((result) => {
332
352
  if (!output) {
333
353
  process.stdout.write(result.css)
@@ -8,6 +8,14 @@ import path from 'path'
8
8
 
9
9
  import { readFileWithRetries } from './utils.js'
10
10
 
11
+ /**
12
+ * The core idea of this watcher is:
13
+ * 1. Whenever a file is added, changed, or renamed we queue a rebuild
14
+ * 2. Perform as few rebuilds as possible by batching them together
15
+ * 3. Coalesce events that happen in quick succession to avoid unnecessary rebuilds
16
+ * 4. Ensure another rebuild happens _if_ changed while a rebuild is in progress
17
+ */
18
+
11
19
  /**
12
20
  *
13
21
  * @param {*} args
@@ -42,27 +50,93 @@ export function createWatcher(args, { state, rebuild }) {
42
50
  : false,
43
51
  })
44
52
 
53
+ // A queue of rebuilds, file reads, etc… to run
45
54
  let chain = Promise.resolve()
46
- let pendingRebuilds = new Set()
55
+
56
+ /**
57
+ * A list of files that have been changed since the last rebuild
58
+ *
59
+ * @type {{file: string, content: () => Promise<string>, extension: string}[]}
60
+ */
47
61
  let changedContent = []
48
62
 
63
+ /**
64
+ * A list of files for which a rebuild has already been queued.
65
+ * This is used to prevent duplicate rebuilds when multiple events are fired for the same file.
66
+ * The rebuilt file is cleared from this list when it's associated rebuild has _started_
67
+ * This is because if the file is changed during a rebuild it won't trigger a new rebuild which it should
68
+ **/
69
+ let pendingRebuilds = new Set()
70
+
71
+ let _timer
72
+ let _reject
73
+
74
+ /**
75
+ * Rebuilds the changed files and resolves when the rebuild is
76
+ * complete regardless of whether it was successful or not
77
+ */
78
+ async function rebuildAndContinue() {
79
+ let changes = changedContent.splice(0)
80
+
81
+ // There are no changes to rebuild so we can just do nothing
82
+ if (changes.length === 0) {
83
+ return Promise.resolve()
84
+ }
85
+
86
+ // Clear all pending rebuilds for the about-to-be-built files
87
+ changes.forEach((change) => pendingRebuilds.delete(change.file))
88
+
89
+ // Resolve the promise even when the rebuild fails
90
+ return rebuild(changes).then(
91
+ () => {},
92
+ () => {}
93
+ )
94
+ }
95
+
49
96
  /**
50
97
  *
51
98
  * @param {*} file
52
99
  * @param {(() => Promise<string>) | null} content
100
+ * @returns {Promise<void>}
53
101
  */
54
102
  function recordChangedFile(file, content = null) {
55
103
  file = path.resolve(file)
56
104
 
57
- content = content ?? (async () => await fs.promises.readFile(file, 'utf8'))
105
+ // Applications like Vim/Neovim fire both rename and change events in succession for atomic writes
106
+ // In that case rebuild has already been queued by rename, so can be skipped in change
107
+ if (pendingRebuilds.has(file)) {
108
+ return Promise.resolve()
109
+ }
110
+
111
+ // Mark that a rebuild of this file is going to happen
112
+ // It MUST happen synchronously before the rebuild is queued for this to be effective
113
+ pendingRebuilds.add(file)
58
114
 
59
115
  changedContent.push({
60
116
  file,
61
- content,
117
+ content: content ?? (() => fs.promises.readFile(file, 'utf8')),
62
118
  extension: path.extname(file).slice(1),
63
119
  })
64
120
 
65
- chain = chain.then(() => rebuild(changedContent))
121
+ if (_timer) {
122
+ clearTimeout(_timer)
123
+ _reject()
124
+ }
125
+
126
+ // If a rebuild is already in progress we don't want to start another one until the 10ms timer has expired
127
+ chain = chain.then(
128
+ () =>
129
+ new Promise((resolve, reject) => {
130
+ _timer = setTimeout(resolve, 10)
131
+ _reject = reject
132
+ })
133
+ )
134
+
135
+ // Resolves once this file has been rebuilt (or the rebuild for this file has failed)
136
+ // This queues as many rebuilds as there are changed files
137
+ // But those rebuilds happen after some delay
138
+ // And will immediately resolve if there are no changes
139
+ chain = chain.then(rebuildAndContinue, rebuildAndContinue)
66
140
 
67
141
  return chain
68
142
  }
@@ -107,18 +181,34 @@ export function createWatcher(args, { state, rebuild }) {
107
181
  return
108
182
  }
109
183
 
184
+ // We'll go ahead and add the file to the pending rebuilds list here
185
+ // It'll be removed when the rebuild starts unless the read fails
186
+ // which will be taken care of as well
110
187
  pendingRebuilds.add(filePath)
111
188
 
112
- chain = chain.then(async () => {
113
- let content
114
-
189
+ async function enqueue() {
115
190
  try {
116
- content = await readFileWithRetries(path.resolve(filePath))
117
- } finally {
118
- pendingRebuilds.delete(filePath)
191
+ // We need to read the file as early as possible outside of the chain
192
+ // because it may be gone by the time we get to it. doing the read
193
+ // immediately increases the chance that the file is still there
194
+ let content = await readFileWithRetries(path.resolve(filePath))
195
+
196
+ if (content === undefined) {
197
+ return
198
+ }
199
+
200
+ // This will push the rebuild onto the chain
201
+ // @ts-ignore: TypeScript isn't picking up that content is a string here
202
+ await recordChangedFile(filePath, () => content)
203
+ } catch {
204
+ // If reading the file fails, it's was probably a deleted temporary file
205
+ // So we can ignore it and no rebuild is needed
119
206
  }
207
+ }
120
208
 
121
- return recordChangedFile(filePath, () => content)
209
+ enqueue().then(() => {
210
+ // If the file read fails we still need to make sure the file isn't stuck in the pending rebuilds list
211
+ pendingRebuilds.delete(filePath)
122
212
  })
123
213
  })
124
214
 
@@ -22,6 +22,7 @@
22
22
  2. Prevent adjustments of font size after orientation changes in iOS.
23
23
  3. Use a more readable tab size.
24
24
  4. Use the user's configured `sans` font-family by default.
25
+ 5. Use the user's configured `sans` font-feature-settings by default.
25
26
  */
26
27
 
27
28
  html {
@@ -30,6 +31,7 @@ html {
30
31
  -moz-tab-size: 4; /* 3 */
31
32
  tab-size: 4; /* 3 */
32
33
  font-family: theme('fontFamily.sans', ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"); /* 4 */
34
+ font-feature-settings: theme('fontFamily.sans[1].fontFeatureSettings', normal); /* 5 */
33
35
  }
34
36
 
35
37
  /*
@@ -94,17 +94,18 @@ function parseFilePath(filePath, ignore) {
94
94
  * @returns {ContentPath}
95
95
  */
96
96
  function resolveGlobPattern(contentPath) {
97
- contentPath.pattern = contentPath.glob
98
- ? `${contentPath.base}/${contentPath.glob}`
99
- : contentPath.base
100
-
101
- contentPath.pattern = contentPath.ignore ? `!${contentPath.pattern}` : contentPath.pattern
102
-
103
97
  // This is required for Windows support to properly pick up Glob paths.
104
98
  // Afaik, this technically shouldn't be needed but there's probably
105
99
  // some internal, direct path matching with a normalized path in
106
100
  // a package which can't handle mixed directory separators
107
- contentPath.pattern = normalizePath(contentPath.pattern)
101
+ let base = normalizePath(contentPath.base)
102
+
103
+ // If the user's file path contains any special characters (like parens) for instance fast-glob
104
+ // is like "OOOH SHINY" and treats them as such. So we have to escape the base path to fix this
105
+ base = fastGlob.escapePath(base)
106
+
107
+ contentPath.pattern = contentPath.glob ? `${base}/${contentPath.glob}` : base
108
+ contentPath.pattern = contentPath.ignore ? `!${contentPath.pattern}` : contentPath.pattern
108
109
 
109
110
  return contentPath
110
111
  }
@@ -29,7 +29,7 @@ function* buildRegExps(context) {
29
29
 
30
30
  let utility = regex.any([
31
31
  // Arbitrary properties
32
- /\[[^\s:'"`]+:[^\s\]]+\]/,
32
+ /\[[^\s:'"`]+:[^\s]+\]/,
33
33
 
34
34
  // Utilities
35
35
  regex.pattern([
@@ -346,22 +346,42 @@ function processApply(root, context, localCache) {
346
346
  })
347
347
  })
348
348
 
349
- // Sort tag names before class names
349
+ // Sort tag names before class names (but only sort each group (separated by a combinator)
350
+ // separately and not in total)
350
351
  // This happens when replacing `.bar` in `.foo.bar` with a tag like `section`
351
- for (const sel of replaced) {
352
- sel.sort((a, b) => {
353
- if (a.type === 'tag' && b.type === 'class') {
354
- return -1
355
- } else if (a.type === 'class' && b.type === 'tag') {
356
- return 1
357
- } else if (a.type === 'class' && b.type === 'pseudo') {
358
- return -1
359
- } else if (a.type === 'pseudo' && b.type === 'class') {
360
- return 1
352
+ for (let sel of replaced) {
353
+ let groups = [[]]
354
+ for (let node of sel.nodes) {
355
+ if (node.type === 'combinator') {
356
+ groups.push(node)
357
+ groups.push([])
358
+ } else {
359
+ let last = groups[groups.length - 1]
360
+ last.push(node)
361
361
  }
362
+ }
362
363
 
363
- return sel.index(a) - sel.index(b)
364
- })
364
+ sel.nodes = []
365
+
366
+ for (let group of groups) {
367
+ if (Array.isArray(group)) {
368
+ group.sort((a, b) => {
369
+ if (a.type === 'tag' && b.type === 'class') {
370
+ return -1
371
+ } else if (a.type === 'class' && b.type === 'tag') {
372
+ return 1
373
+ } else if (a.type === 'class' && b.type === 'pseudo') {
374
+ return -1
375
+ } else if (a.type === 'pseudo' && b.type === 'class') {
376
+ return 1
377
+ }
378
+
379
+ return 0
380
+ })
381
+ }
382
+
383
+ sel.nodes = sel.nodes.concat(group)
384
+ }
365
385
  }
366
386
 
367
387
  sel.replaceWith(...replaced)
@@ -382,7 +402,7 @@ function processApply(root, context, localCache) {
382
402
 
383
403
  if (apply.parent.type === 'atrule') {
384
404
  if (apply.parent.name === 'screen') {
385
- const screenType = apply.parent.params
405
+ let screenType = apply.parent.params
386
406
 
387
407
  throw apply.error(
388
408
  `@apply is not supported within nested at-rules like @screen. We suggest you write this as @apply ${applyCandidates
@@ -414,7 +434,7 @@ function processApply(root, context, localCache) {
414
434
  }
415
435
  }
416
436
 
417
- for (const [parent, [candidates, atApplySource]] of perParentApplies) {
437
+ for (let [parent, [candidates, atApplySource]] of perParentApplies) {
418
438
  let siblings = []
419
439
 
420
440
  for (let [applyCandidate, important, rules] of candidates) {
@@ -3,7 +3,7 @@ import selectorParser from 'postcss-selector-parser'
3
3
  import parseObjectStyles from '../util/parseObjectStyles'
4
4
  import isPlainObject from '../util/isPlainObject'
5
5
  import prefixSelector from '../util/prefixSelector'
6
- import { updateAllClasses, getMatchingTypes } from '../util/pluginUtils'
6
+ import { updateAllClasses, filterSelectorsForClass, getMatchingTypes } from '../util/pluginUtils'
7
7
  import log from '../util/log'
8
8
  import * as sharedState from './sharedState'
9
9
  import { formatVariantSelector, finalizeSelector } from '../util/formatVariantSelector'
@@ -116,12 +116,15 @@ function applyImportant(matches, classCandidate) {
116
116
  for (let [meta, rule] of matches) {
117
117
  let container = postcss.root({ nodes: [rule.clone()] })
118
118
  container.walkRules((r) => {
119
- r.selector = updateAllClasses(r.selector, (className) => {
120
- if (className === classCandidate) {
121
- return `!${className}`
119
+ r.selector = updateAllClasses(
120
+ filterSelectorsForClass(r.selector, classCandidate),
121
+ (className) => {
122
+ if (className === classCandidate) {
123
+ return `!${className}`
124
+ }
125
+ return className
122
126
  }
123
- return className
124
- })
127
+ )
125
128
  r.walkDecls((d) => (d.important = true))
126
129
  })
127
130
  result.push([{ ...meta, important: true }, container.nodes[0]])
@@ -210,7 +213,7 @@ function applyVariant(variant, matches, context) {
210
213
  let container = postcss.root({ nodes: [rule.clone()] })
211
214
 
212
215
  for (let [variantSort, variantFunction, containerFromArray] of variantFunctionTuples) {
213
- let clone = containerFromArray ?? container.clone()
216
+ let clone = (containerFromArray ?? container).clone()
214
217
  let collectedFormats = []
215
218
 
216
219
  function prepareBackup() {
@@ -528,7 +528,7 @@ function buildPluginApi(tailwindConfig, context, { variantList, variantMap, offs
528
528
  variantFunctions = [].concat(variantFunctions).map((variantFunction) => {
529
529
  if (typeof variantFunction !== 'string') {
530
530
  // Safelist public API functions
531
- return (api) => {
531
+ return (api = {}) => {
532
532
  let { args, modifySelectors, container, separator, wrap, format } = api
533
533
  let result = variantFunction(
534
534
  Object.assign(
@@ -581,11 +581,13 @@ function buildPluginApi(tailwindConfig, context, { variantList, variantMap, offs
581
581
 
582
582
  api.addVariant(
583
583
  isSpecial ? `${variant}${key}` : `${variant}-${key}`,
584
- ({ args, container }) =>
585
- variantFn(
584
+ ({ args, container }) => {
585
+ return variantFn(
586
586
  value,
587
- modifiersEnabled ? { modifier: args.modifier, container } : { container }
588
- ),
587
+ modifiersEnabled ? { modifier: args?.modifier, container } : { container }
588
+ )
589
+ },
590
+
589
591
  {
590
592
  ...options,
591
593
  value,
@@ -601,13 +603,17 @@ function buildPluginApi(tailwindConfig, context, { variantList, variantMap, offs
601
603
  api.addVariant(
602
604
  variant,
603
605
  ({ args, container }) => {
604
- if (args.value === sharedState.NONE && !hasDefault) {
606
+ if (args?.value === sharedState.NONE && !hasDefault) {
605
607
  return null
606
608
  }
607
609
 
608
610
  return variantFn(
609
- args.value === sharedState.NONE ? options.values.DEFAULT : args.value,
610
- modifiersEnabled ? { modifier: args.modifier, container } : { container }
611
+ args?.value === sharedState.NONE
612
+ ? options.values.DEFAULT
613
+ : // Falling back to args if it is a string, otherwise '' for older intellisense
614
+ // (JetBrains) plugins.
615
+ args?.value ?? (typeof args === 'string' ? args : ''),
616
+ modifiersEnabled ? { modifier: args?.modifier, container } : { container }
611
617
  )
612
618
  },
613
619
  {
@@ -37,6 +37,23 @@ export function updateAllClasses(selectors, updateClass) {
37
37
  return result
38
38
  }
39
39
 
40
+ export function filterSelectorsForClass(selectors, classCandidate) {
41
+ let parser = selectorParser((selectors) => {
42
+ selectors.each((sel) => {
43
+ const containsClass = sel.nodes.some(
44
+ (node) => node.type === 'class' && node.value === classCandidate
45
+ )
46
+ if (!containsClass) {
47
+ sel.remove()
48
+ }
49
+ })
50
+ })
51
+
52
+ let result = parser.processSync(selectors)
53
+
54
+ return result
55
+ }
56
+
40
57
  function resolveArbitraryValue(modifier, validate) {
41
58
  if (!isArbitraryValue(modifier)) {
42
59
  return undefined
@@ -233,14 +250,20 @@ export function coerceValue(types, modifier, options, tailwindConfig) {
233
250
  export function* getMatchingTypes(types, rawModifier, options, tailwindConfig) {
234
251
  let modifiersEnabled = flagEnabled(tailwindConfig, 'generalizedModifiers')
235
252
 
253
+ let [modifier, utilityModifier] = splitUtilityModifier(rawModifier)
254
+
236
255
  let canUseUtilityModifier =
237
256
  modifiersEnabled &&
238
257
  options.modifiers != null &&
239
- (options.modifiers === 'any' || typeof options.modifiers === 'object')
240
-
241
- let [modifier, utilityModifier] = canUseUtilityModifier
242
- ? splitUtilityModifier(rawModifier)
243
- : [rawModifier, undefined]
258
+ (options.modifiers === 'any' ||
259
+ (typeof options.modifiers === 'object' &&
260
+ ((utilityModifier && isArbitraryValue(utilityModifier)) ||
261
+ utilityModifier in options.modifiers)))
262
+
263
+ if (!canUseUtilityModifier) {
264
+ modifier = rawModifier
265
+ utilityModifier = undefined
266
+ }
244
267
 
245
268
  if (utilityModifier !== undefined && modifier === '') {
246
269
  modifier = 'DEFAULT'
@@ -16,10 +16,6 @@ function isFunction(input) {
16
16
  return typeof input === 'function'
17
17
  }
18
18
 
19
- function isObject(input) {
20
- return typeof input === 'object' && input !== null
21
- }
22
-
23
19
  function mergeWith(target, ...sources) {
24
20
  let customizer = sources.pop()
25
21
 
@@ -28,7 +24,7 @@ function mergeWith(target, ...sources) {
28
24
  let merged = customizer(target[k], source[k])
29
25
 
30
26
  if (merged === undefined) {
31
- if (isObject(target[k]) && isObject(source[k])) {
27
+ if (isPlainObject(target[k]) && isPlainObject(source[k])) {
32
28
  target[k] = mergeWith({}, target[k], source[k], customizer)
33
29
  } else {
34
30
  target[k] = source[k]
@@ -103,12 +99,12 @@ function mergeThemes(themes) {
103
99
 
104
100
  function mergeExtensionCustomizer(merged, value) {
105
101
  // When we have an array of objects, we do want to merge it
106
- if (Array.isArray(merged) && isObject(merged[0])) {
102
+ if (Array.isArray(merged) && isPlainObject(merged[0])) {
107
103
  return merged.concat(value)
108
104
  }
109
105
 
110
106
  // When the incoming value is an array, and the existing config is an object, prepend the existing object
111
- if (Array.isArray(value) && isObject(value[0]) && isObject(merged)) {
107
+ if (Array.isArray(value) && isPlainObject(value[0]) && isPlainObject(merged)) {
112
108
  return [merged, ...value]
113
109
  }
114
110
 
@@ -11,6 +11,7 @@ module.exports = {
11
11
  xl: '1280px',
12
12
  '2xl': '1536px',
13
13
  },
14
+ supports: {},
14
15
  colors: ({ colors }) => ({
15
16
  inherit: colors.inherit,
16
17
  current: colors.current,
package/types/config.d.ts CHANGED
@@ -84,6 +84,7 @@ type ScreensConfig = string[] | KeyValuePair<string, string | Screen | Screen[]>
84
84
  interface ThemeConfig {
85
85
  // Responsiveness
86
86
  screens: ResolvableTo<ScreensConfig>
87
+ supports: ResolvableTo<Record<string, string>>
87
88
 
88
89
  // Reusable base configs
89
90
  colors: ResolvableTo<RecursiveKeyValuePair>
package/peers/.DS_Store DELETED
Binary file
package/peers/.svgo.yml DELETED
@@ -1,75 +0,0 @@
1
- # replace default config
2
-
3
- # multipass: true
4
- # full: true
5
-
6
- plugins:
7
-
8
- # - name
9
- #
10
- # or:
11
- # - name: false
12
- # - name: true
13
- #
14
- # or:
15
- # - name:
16
- # param1: 1
17
- # param2: 2
18
-
19
- - removeDoctype
20
- - removeXMLProcInst
21
- - removeComments
22
- - removeMetadata
23
- - removeXMLNS
24
- - removeEditorsNSData
25
- - cleanupAttrs
26
- - inlineStyles
27
- - minifyStyles
28
- - convertStyleToAttrs
29
- - cleanupIDs
30
- - prefixIds
31
- - removeRasterImages
32
- - removeUselessDefs
33
- - cleanupNumericValues
34
- - cleanupListOfValues
35
- - convertColors
36
- - removeUnknownsAndDefaults
37
- - removeNonInheritableGroupAttrs
38
- - removeUselessStrokeAndFill
39
- - removeViewBox
40
- - cleanupEnableBackground
41
- - removeHiddenElems
42
- - removeEmptyText
43
- - convertShapeToPath
44
- - convertEllipseToCircle
45
- - moveElemsAttrsToGroup
46
- - moveGroupAttrsToElems
47
- - collapseGroups
48
- - convertPathData
49
- - convertTransform
50
- - removeEmptyAttrs
51
- - removeEmptyContainers
52
- - mergePaths
53
- - removeUnusedNS
54
- - sortAttrs
55
- - sortDefsChildren
56
- - removeTitle
57
- - removeDesc
58
- - removeDimensions
59
- - removeAttrs
60
- - removeAttributesBySelector
61
- - removeElementsByAttr
62
- - addClassesToSVGElement
63
- - removeStyleElement
64
- - removeScriptElement
65
- - addAttributesToSVGElement
66
- - removeOffCanvasPaths
67
- - reusePaths
68
-
69
- # configure the indent (default 4 spaces) used by `--pretty` here:
70
- #
71
- # @see https://github.com/svg/svgo/blob/master/lib/svgo/js2svg.js#L6 for more config options
72
- #
73
- # js2svg:
74
- # pretty: true
75
- # indent: ' '