@tamagui/tailwind 0.0.0-bootstrap.0 → 3.0.0-beta.643.1

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 (77) hide show
  1. package/dist/cjs/candidate.cjs +385 -0
  2. package/dist/cjs/candidate.native.cjs +431 -0
  3. package/dist/cjs/candidate.native.js +433 -0
  4. package/dist/cjs/candidate.native.js.map +1 -0
  5. package/dist/cjs/frontend.cjs +80 -0
  6. package/dist/cjs/frontend.native.cjs +95 -0
  7. package/dist/cjs/frontend.native.js +97 -0
  8. package/dist/cjs/frontend.native.js.map +1 -0
  9. package/dist/cjs/index.cjs +39 -0
  10. package/dist/cjs/index.native.cjs +41 -0
  11. package/dist/cjs/index.native.js +43 -0
  12. package/dist/cjs/index.native.js.map +1 -0
  13. package/dist/cjs/styled.cjs +30 -0
  14. package/dist/cjs/styled.native.cjs +32 -0
  15. package/dist/cjs/styled.native.js +34 -0
  16. package/dist/cjs/styled.native.js.map +1 -0
  17. package/dist/cjs/types.cjs +17 -0
  18. package/dist/cjs/types.native.cjs +19 -0
  19. package/dist/cjs/types.native.js +21 -0
  20. package/dist/cjs/types.native.js.map +1 -0
  21. package/dist/cjs/vite/state.cjs +249 -0
  22. package/dist/cjs/vite/state.native.cjs +331 -0
  23. package/dist/cjs/vite/state.native.js +333 -0
  24. package/dist/cjs/vite/state.native.js.map +1 -0
  25. package/dist/cjs/vite.cjs +148 -0
  26. package/dist/cjs/vite.native.cjs +188 -0
  27. package/dist/cjs/vite.native.js +190 -0
  28. package/dist/cjs/vite.native.js.map +1 -0
  29. package/dist/esm/candidate.mjs +360 -0
  30. package/dist/esm/candidate.mjs.map +1 -0
  31. package/dist/esm/candidate.native.js +404 -0
  32. package/dist/esm/candidate.native.js.map +1 -0
  33. package/dist/esm/frontend.mjs +57 -0
  34. package/dist/esm/frontend.mjs.map +1 -0
  35. package/dist/esm/frontend.native.js +70 -0
  36. package/dist/esm/frontend.native.js.map +1 -0
  37. package/dist/esm/index.mjs +11 -0
  38. package/dist/esm/index.mjs.map +1 -0
  39. package/dist/esm/index.native.js +11 -0
  40. package/dist/esm/index.native.js.map +1 -0
  41. package/dist/esm/styled.mjs +10 -0
  42. package/dist/esm/styled.mjs.map +1 -0
  43. package/dist/esm/styled.native.js +10 -0
  44. package/dist/esm/styled.native.js.map +1 -0
  45. package/dist/esm/types.mjs +0 -0
  46. package/dist/esm/types.native.js +0 -0
  47. package/dist/esm/vite/state.mjs +215 -0
  48. package/dist/esm/vite/state.mjs.map +1 -0
  49. package/dist/esm/vite/state.native.js +295 -0
  50. package/dist/esm/vite/state.native.js.map +1 -0
  51. package/dist/esm/vite.mjs +116 -0
  52. package/dist/esm/vite.mjs.map +1 -0
  53. package/dist/esm/vite.native.js +154 -0
  54. package/dist/esm/vite.native.js.map +1 -0
  55. package/package.json +88 -5
  56. package/src/candidate.ts +527 -0
  57. package/src/frontend.ts +116 -0
  58. package/src/index.tsx +24 -0
  59. package/src/styled.tsx +52 -0
  60. package/src/types.ts +226 -0
  61. package/src/vite/state.ts +274 -0
  62. package/src/vite.ts +204 -0
  63. package/types/candidate.d.ts +23 -0
  64. package/types/candidate.d.ts.map +1 -0
  65. package/types/frontend.d.ts +20 -0
  66. package/types/frontend.d.ts.map +1 -0
  67. package/types/index.d.ts +8 -0
  68. package/types/index.d.ts.map +1 -0
  69. package/types/styled.d.ts +12 -0
  70. package/types/styled.d.ts.map +1 -0
  71. package/types/types.d.ts +158 -0
  72. package/types/types.d.ts.map +1 -0
  73. package/types/vite/state.d.ts +28 -0
  74. package/types/vite/state.d.ts.map +1 -0
  75. package/types/vite.d.ts +20 -0
  76. package/types/vite.d.ts.map +1 -0
  77. package/README.md +0 -1
package/src/styled.tsx ADDED
@@ -0,0 +1,52 @@
1
+ import { createFrontendStyled } from '@tamagui/core/internal-runtime'
2
+ import { tailwindStyleFrontend } from './frontend'
3
+ import type {
4
+ GetTailwindNonStyleProps,
5
+ GetTailwindRef,
6
+ GetTailwindVariantProps,
7
+ TailwindComponent,
8
+ TailwindStyledOptions,
9
+ TailwindVariantDefinitions,
10
+ TailwindVariantProps,
11
+ } from './types'
12
+
13
+ const styledWithFrontend = createFrontendStyled(tailwindStyleFrontend)
14
+
15
+ /**
16
+ * Class-first `styled()`. The base is a class string, variant values are class
17
+ * strings, and the resulting component accepts `className` plus its variants —
18
+ * never Tamagui inline style props.
19
+ *
20
+ * The class base and variant values are parsed once per (component, config) pair
21
+ * by the frontend's `normalizeStaticConfig`, so authoring cost is not per render.
22
+ */
23
+ export function styled<
24
+ Parent extends TailwindComponent<any, any, any>,
25
+ Variants extends TailwindVariantDefinitions = {},
26
+ >(
27
+ Component: Parent,
28
+ baseClassName: string,
29
+ options?: TailwindStyledOptions<Variants>
30
+ ): TailwindComponent<
31
+ GetTailwindRef<Parent>,
32
+ GetTailwindNonStyleProps<Parent>,
33
+ GetTailwindVariantProps<Parent> & TailwindVariantProps<Variants>
34
+ >
35
+ export function styled<
36
+ Parent extends TailwindComponent<any, any, any>,
37
+ Variants extends TailwindVariantDefinitions = {},
38
+ >(
39
+ Component: Parent,
40
+ options?: TailwindStyledOptions<Variants>
41
+ ): TailwindComponent<
42
+ GetTailwindRef<Parent>,
43
+ GetTailwindNonStyleProps<Parent>,
44
+ GetTailwindVariantProps<Parent> & TailwindVariantProps<Variants>
45
+ >
46
+ export function styled(
47
+ Component: any,
48
+ optionsOrBaseClassName?: any,
49
+ maybeOptions?: any
50
+ ): any {
51
+ return styledWithFrontend(Component, optionsOrBaseClassName, maybeOptions)
52
+ }
package/src/types.ts ADDED
@@ -0,0 +1,226 @@
1
+ import type { FrontendStaticConfig } from '@tamagui/core/internal-runtime'
2
+ import type {
3
+ AriaAttributes,
4
+ CSSProperties,
5
+ FunctionComponent,
6
+ HTMLAttributes,
7
+ ReactElement,
8
+ ReactNode,
9
+ Ref as ReactRef,
10
+ } from 'react'
11
+ import type {
12
+ PressableProps,
13
+ StyleProp,
14
+ Text as ReactNativeText,
15
+ TextProps as ReactNativeTextProps,
16
+ TextStyle,
17
+ View as ReactNativeView,
18
+ ViewProps as ReactNativeViewProps,
19
+ ViewStyle,
20
+ } from 'react-native'
21
+
22
+ type WebEventProps = {
23
+ onMouseEnter?: HTMLAttributes<HTMLDivElement>['onMouseEnter']
24
+ onMouseLeave?: HTMLAttributes<HTMLDivElement>['onMouseLeave']
25
+ onMouseDown?: HTMLAttributes<HTMLDivElement>['onMouseDown']
26
+ onMouseUp?: HTMLAttributes<HTMLDivElement>['onMouseUp']
27
+ onMouseMove?: HTMLAttributes<HTMLDivElement>['onMouseMove']
28
+ onMouseOver?: HTMLAttributes<HTMLDivElement>['onMouseOver']
29
+ onMouseOut?: HTMLAttributes<HTMLDivElement>['onMouseOut']
30
+ onFocus?: HTMLAttributes<HTMLDivElement>['onFocus']
31
+ onBlur?: HTMLAttributes<HTMLDivElement>['onBlur']
32
+ onClick?: HTMLAttributes<HTMLDivElement>['onClick']
33
+ onDoubleClick?: HTMLAttributes<HTMLDivElement>['onDoubleClick']
34
+ onContextMenu?: HTMLAttributes<HTMLDivElement>['onContextMenu']
35
+ onWheel?: HTMLAttributes<HTMLDivElement>['onWheel']
36
+ onKeyDown?: HTMLAttributes<HTMLDivElement>['onKeyDown']
37
+ onKeyUp?: HTMLAttributes<HTMLDivElement>['onKeyUp']
38
+ onChange?: HTMLAttributes<HTMLDivElement>['onChange']
39
+ onInput?: HTMLAttributes<HTMLDivElement>['onInput']
40
+ onBeforeInput?: HTMLAttributes<HTMLDivElement>['onBeforeInput']
41
+ onScroll?: HTMLAttributes<HTMLDivElement>['onScroll']
42
+ onCopy?: HTMLAttributes<HTMLDivElement>['onCopy']
43
+ onCut?: HTMLAttributes<HTMLDivElement>['onCut']
44
+ onPaste?: HTMLAttributes<HTMLDivElement>['onPaste']
45
+ onDrag?: HTMLAttributes<HTMLDivElement>['onDrag']
46
+ onDragStart?: HTMLAttributes<HTMLDivElement>['onDragStart']
47
+ onDragEnd?: HTMLAttributes<HTMLDivElement>['onDragEnd']
48
+ onDragEnter?: HTMLAttributes<HTMLDivElement>['onDragEnter']
49
+ onDragLeave?: HTMLAttributes<HTMLDivElement>['onDragLeave']
50
+ onDragOver?: HTMLAttributes<HTMLDivElement>['onDragOver']
51
+ onDrop?: HTMLAttributes<HTMLDivElement>['onDrop']
52
+ onPointerDown?: HTMLAttributes<HTMLDivElement>['onPointerDown']
53
+ onPointerMove?: HTMLAttributes<HTMLDivElement>['onPointerMove']
54
+ onPointerUp?: HTMLAttributes<HTMLDivElement>['onPointerUp']
55
+ onPointerCancel?: HTMLAttributes<HTMLDivElement>['onPointerCancel']
56
+ }
57
+
58
+ type TailwindComponentState = {
59
+ unmounted: boolean | 'should-enter'
60
+ disabled?: boolean
61
+ hover?: boolean
62
+ press?: boolean
63
+ pressIn?: boolean
64
+ focus?: boolean
65
+ focusVisible?: boolean
66
+ focusWithin?: boolean
67
+ }
68
+
69
+ type TailwindCommonProps = AriaAttributes &
70
+ WebEventProps & {
71
+ target?: string
72
+ htmlFor?: string
73
+ asChild?: boolean | 'except-style' | 'except-style-web' | 'web'
74
+ dangerouslySetInnerHTML?: { __html: string }
75
+ children?: ReactNode
76
+ debug?: boolean | 'break' | 'verbose' | 'visualize' | 'profile'
77
+ disabled?: boolean
78
+ themeShallow?: boolean
79
+ id?: string
80
+ render?:
81
+ | keyof HTMLElementTagNameMap
82
+ | (string & {})
83
+ | ReactElement
84
+ | ((
85
+ props: Record<string, any> & { ref?: ReactRef<any> },
86
+ state: TailwindComponentState
87
+ ) => ReactElement)
88
+ theme?: string | null
89
+ group?: string | boolean
90
+ container?: boolean
91
+ untilMeasured?: 'hide' | 'show'
92
+ name?: string
93
+ tabIndex?: string | number
94
+ role?: HTMLAttributes<HTMLDivElement>['role']
95
+ disableOptimization?: boolean
96
+ forceStyle?: 'hover' | 'press' | 'focus' | 'focusVisible' | 'focusWithin'
97
+ disableClassName?: boolean
98
+ hitSlop?:
99
+ | number
100
+ | { top?: number; left?: number; bottom?: number; right?: number }
101
+ | null
102
+ animatedBy?: string | null
103
+ onPress?: PressableProps['onPress']
104
+ onLongPress?: PressableProps['onLongPress']
105
+ onPressIn?: PressableProps['onPress']
106
+ onPressOut?: PressableProps['onPress']
107
+ }
108
+
109
+ type LooseCombinedObjects<A extends object, B extends object> = A | B | (A & B)
110
+
111
+ export type TailwindViewNonStyleProps = Omit<
112
+ ReactNativeViewProps,
113
+ 'children' | 'display' | 'hitSlop' | 'pointerEvents' | 'style' | keyof WebEventProps
114
+ > &
115
+ TailwindCommonProps & {
116
+ style?: StyleProp<LooseCombinedObjects<CSSProperties, ViewStyle>>
117
+ }
118
+
119
+ export type TailwindTextNonStyleProps = Omit<
120
+ ReactNativeTextProps,
121
+ 'children' | 'style' | keyof WebEventProps
122
+ > &
123
+ TailwindCommonProps & {
124
+ style?: StyleProp<LooseCombinedObjects<CSSProperties, TextStyle>>
125
+ }
126
+
127
+ type MeasureOnSuccessCallback = (
128
+ x: number,
129
+ y: number,
130
+ width: number,
131
+ height: number,
132
+ pageX: number,
133
+ pageY: number
134
+ ) => void
135
+
136
+ export interface TailwindElementMethods {
137
+ measure(callback: MeasureOnSuccessCallback): void
138
+ measureInWindow(
139
+ callback: (x: number, y: number, width: number, height: number) => void
140
+ ): void
141
+ measureLayout(
142
+ relativeToNativeNode: ReactNativeView | HTMLElement,
143
+ onSuccess: (left: number, top: number, width: number, height: number) => void,
144
+ onFail?: () => void
145
+ ): void
146
+ focus(): void
147
+ blur(): void
148
+ }
149
+
150
+ export type TailwindViewElement = (HTMLElement & TailwindElementMethods) | ReactNativeView
151
+ export type TailwindTextElement = (HTMLElement & TailwindElementMethods) | ReactNativeText
152
+
153
+ /**
154
+ * The complete styling surface of a `@tamagui/tailwind` component.
155
+ *
156
+ * Everything else a component accepts is ordinary behavior: children, refs,
157
+ * accessibility, events, ids, and the raw platform `style` escape hatch, all
158
+ * inherited from the shared non-style prop types. Tamagui inline style props
159
+ * (`padding`, `bg`, shorthands, state/media clauses, theme style props) are
160
+ * deliberately absent — that authoring syntax belongs to `@tamagui/core`.
161
+ */
162
+ export type TailwindStyleProps = {
163
+ className?: string
164
+ }
165
+
166
+ /** `true`/`false` matchers become a boolean prop; every other matcher is its own literal. */
167
+ type VariantAcceptedValue<Key> = Key extends 'true' | 'false' ? boolean : Key
168
+
169
+ /**
170
+ * Finite variant inference: one optional prop per variant, accepting exactly the
171
+ * matchers that variant declares. It never reaches core's style prop graph.
172
+ */
173
+ export type TailwindVariantProps<Variants> = Variants extends object
174
+ ? {
175
+ [Key in keyof Variants]?: VariantAcceptedValue<keyof Variants[Key]>
176
+ }
177
+ : {}
178
+
179
+ /** Variant values are class strings — the same authoring syntax as `className`. */
180
+ export type TailwindVariantDefinitions = {
181
+ [variantName: string]: { [matcher: string]: string }
182
+ }
183
+
184
+ export type TailwindCompoundVariant<Variants> = TailwindVariantProps<Variants> & {
185
+ style: string
186
+ }
187
+
188
+ export type TailwindStyledOptions<Variants extends TailwindVariantDefinitions> = {
189
+ displayName?: string
190
+ variants?: Variants
191
+ defaultVariants?: TailwindVariantProps<Variants>
192
+ compoundVariants?: readonly TailwindCompoundVariant<Variants>[]
193
+ }
194
+
195
+ export interface TailwindComponent<
196
+ Ref = any,
197
+ NonStyleProps = {},
198
+ VariantProps = {},
199
+ > extends FunctionComponent<
200
+ NonStyleProps & TailwindStyleProps & VariantProps & { ref?: ReactRef<Ref> }
201
+ > {
202
+ staticConfig: FrontendStaticConfig
203
+ /** phantom carrier so `styled()` can recover the parts of a parent component */
204
+ __tailwind?: { ref: Ref; props: NonStyleProps; variants: VariantProps }
205
+ }
206
+
207
+ export type GetTailwindRef<Component> =
208
+ Component extends TailwindComponent<infer Ref, any, any> ? Ref : any
209
+
210
+ export type GetTailwindNonStyleProps<Component> =
211
+ Component extends TailwindComponent<any, infer Props, any> ? Props : {}
212
+
213
+ export type GetTailwindVariantProps<Component> =
214
+ Component extends TailwindComponent<any, any, infer Variants> ? Variants : {}
215
+
216
+ export type TailwindView = TailwindComponent<
217
+ TailwindViewElement,
218
+ TailwindViewNonStyleProps
219
+ >
220
+ export type TailwindText = TailwindComponent<
221
+ TailwindTextElement,
222
+ TailwindTextNonStyleProps
223
+ >
224
+
225
+ export type TailwindViewProps = TailwindViewNonStyleProps & TailwindStyleProps
226
+ export type TailwindTextProps = TailwindTextNonStyleProps & TailwindStyleProps
@@ -0,0 +1,274 @@
1
+ import { compile } from '@tailwindcss/node'
2
+ import { Scanner } from '@tailwindcss/oxide'
3
+ import {
4
+ classifyCandidate,
5
+ createGrammarConfigView,
6
+ type GrammarSourceConfig,
7
+ } from '@tamagui/style-grammar/runtime'
8
+ import { readFile, realpath } from 'node:fs/promises'
9
+ import { createRequire } from 'node:module'
10
+ import path from 'node:path'
11
+
12
+ export const TAILWIND_VERSION = '4.3.0'
13
+ export const TAILWIND_VIRTUAL_ID = 'virtual:tamagui-tailwind.css'
14
+ export const TAILWIND_RESOLVED_ID = `\0${TAILWIND_VIRTUAL_ID}`
15
+
16
+ export function wrapWithTamaguiLayer(css: string): string {
17
+ return `@layer tamagui {\n${css}\n}`
18
+ }
19
+
20
+ export function isTamaguiCoreResetCSS(id: string): boolean {
21
+ const normalizedId = id.split('?', 1)[0].replace(/\\/g, '/')
22
+ return (
23
+ normalizedId.endsWith('/@tamagui/core/reset.css') ||
24
+ normalizedId.endsWith('/code/core/core/reset.css')
25
+ )
26
+ }
27
+
28
+ const TAILWIND_INPUT = `@layer tamagui, theme, utilities;
29
+ @import "tailwindcss/theme.css" layer(theme);
30
+ @import "tailwindcss/utilities.css" layer(utilities);`
31
+
32
+ type TailwindCompiler = Awaited<ReturnType<typeof compile>>
33
+
34
+ export type TailwindScannerState = ReturnType<typeof createTailwindScannerState>
35
+
36
+ export type TailwindWatchEvent = 'create' | 'update' | 'delete'
37
+
38
+ function extensionForFile(id: string): string {
39
+ return path.extname(id.split('?', 1)[0]).slice(1) || 'html'
40
+ }
41
+
42
+ async function assertHostTailwind(root: string): Promise<void> {
43
+ const requireFromRoot = createRequire(path.join(root, 'package.json'))
44
+ let packagePath: string
45
+ try {
46
+ packagePath = requireFromRoot.resolve('tailwindcss/package.json')
47
+ } catch {
48
+ throw new Error(
49
+ `[tamagui] @tamagui/tailwind/vite requires tailwindcss@${TAILWIND_VERSION} in the app. Install it with \`bun add -D tailwindcss@${TAILWIND_VERSION}\` (resolved from ${root}).`
50
+ )
51
+ }
52
+
53
+ const packageJSON = JSON.parse(await readFile(packagePath, 'utf8')) as {
54
+ version?: string
55
+ }
56
+ if (packageJSON.version !== TAILWIND_VERSION) {
57
+ throw new Error(
58
+ `[tamagui] @tamagui/tailwind/vite requires tailwindcss@${TAILWIND_VERSION}, but ${packageJSON.version || 'an unknown version'} was resolved from ${packagePath}. Align the app dependency before starting Vite.`
59
+ )
60
+ }
61
+ }
62
+
63
+ /**
64
+ * The official Tailwind scanner and compiler, owned by `@tamagui/tailwind/vite`.
65
+ *
66
+ * It compiles only the candidates Tamagui's own grammar does not claim: claimed
67
+ * candidates already became Tamagui atoms through the shared renderer, so emitting
68
+ * them again would duplicate the rules and hand the cascade to whichever landed
69
+ * last. There is no style-mode gate here — the state exists because the app
70
+ * imported the Tailwind plugin.
71
+ */
72
+ export function createTailwindScannerState() {
73
+ let root = ''
74
+ let generation = -1
75
+ let enabled = false
76
+ let scanner: Scanner | null = null
77
+ let compiler: TailwindCompiler | null = null
78
+ let grammarConfig = createGrammarConfigView({})
79
+ let candidatesByFile = new Map<string, Set<string>>()
80
+ let compiledCandidates = new Set<string>()
81
+ let addDependency: (file: string) => void = () => {}
82
+ let css = ''
83
+
84
+ function clear() {
85
+ root = ''
86
+ generation = -1
87
+ enabled = false
88
+ scanner = null
89
+ compiler = null
90
+ grammarConfig = createGrammarConfigView({})
91
+ candidatesByFile = new Map()
92
+ compiledCandidates = new Set()
93
+ addDependency = () => {}
94
+ css = ''
95
+ }
96
+
97
+ async function configure(
98
+ nextRoot: string,
99
+ nextGeneration: number,
100
+ config: GrammarSourceConfig | null | undefined,
101
+ onDependency: (file: string) => void,
102
+ onSourceGlob: (glob: string) => void = () => {}
103
+ ): Promise<boolean> {
104
+ // a null config means Tamagui itself is disabled for this build, and without a
105
+ // config there is no grammar view to decide which candidates Tamagui owns
106
+ if (!config) {
107
+ clear()
108
+ generation = nextGeneration
109
+ return false
110
+ }
111
+ if (
112
+ compiler &&
113
+ scanner &&
114
+ enabled &&
115
+ generation === nextGeneration &&
116
+ root === nextRoot
117
+ ) {
118
+ return true
119
+ }
120
+
121
+ await assertHostTailwind(nextRoot)
122
+ root = nextRoot
123
+ generation = nextGeneration
124
+ enabled = true
125
+ grammarConfig = createGrammarConfigView(config)
126
+ addDependency = onDependency
127
+ scanner = new Scanner({
128
+ sources: [
129
+ { base: root, pattern: '**/*', negated: false },
130
+ { base: root, pattern: 'node_modules/**', negated: true },
131
+ { base: root, pattern: '.git/**', negated: true },
132
+ { base: root, pattern: '.vite/**', negated: true },
133
+ { base: root, pattern: 'dist/**', negated: true },
134
+ { base: root, pattern: 'build/**', negated: true },
135
+ { base: root, pattern: 'coverage/**', negated: true },
136
+ ],
137
+ })
138
+ compiler = await createCompiler()
139
+ scanner.scan()
140
+ registerScannerSources(scanner, onDependency, onSourceGlob)
141
+ candidatesByFile = new Map()
142
+ await Promise.all(
143
+ scanner.files.map(async (file) => {
144
+ try {
145
+ const source = await readFile(file, 'utf8')
146
+ candidatesByFile.set(
147
+ await normalizeSourcePath(file),
148
+ candidatesForSource(scanner!, file, source)
149
+ )
150
+ } catch {
151
+ // A source may disappear between oxide's inventory and this read.
152
+ }
153
+ })
154
+ )
155
+ await rebuild()
156
+ return true
157
+ }
158
+
159
+ async function createCompiler() {
160
+ return compile(TAILWIND_INPUT, {
161
+ base: root,
162
+ onDependency: addDependency,
163
+ })
164
+ }
165
+
166
+ async function rebuild(): Promise<boolean> {
167
+ if (!compiler) return false
168
+ const allCandidates = new Set<string>()
169
+ for (const candidates of candidatesByFile.values()) {
170
+ for (const candidate of candidates) allCandidates.add(candidate)
171
+ }
172
+ const passthrough = [...allCandidates]
173
+ .filter(
174
+ (candidate) => classifyCandidate(candidate, grammarConfig).kind === 'passthrough'
175
+ )
176
+ .sort((a, b) => (a < b ? -1 : a > b ? 1 : 0))
177
+ const nextCandidates = new Set(passthrough)
178
+ const removedCandidate = [...compiledCandidates].some(
179
+ (candidate) => !nextCandidates.has(candidate)
180
+ )
181
+ if (removedCandidate) {
182
+ compiler = await createCompiler()
183
+ }
184
+ const nextCSS = compiler.build(passthrough)
185
+ compiledCandidates = nextCandidates
186
+ if (nextCSS === css) return false
187
+ css = nextCSS
188
+ return true
189
+ }
190
+
191
+ async function scanSource(id: string, source: string): Promise<boolean> {
192
+ if (!enabled || !scanner) return false
193
+ candidatesByFile.set(
194
+ await normalizeSourcePath(id),
195
+ candidatesForSource(scanner, id, source)
196
+ )
197
+ return rebuild()
198
+ }
199
+
200
+ async function removeSource(id: string): Promise<boolean> {
201
+ if (!candidatesByFile.delete(await normalizeSourcePath(id))) return false
202
+ return rebuild()
203
+ }
204
+
205
+ return {
206
+ clear,
207
+ configure,
208
+ removeSource,
209
+ scanSource,
210
+ get enabled() {
211
+ return enabled
212
+ },
213
+ get css() {
214
+ return css
215
+ },
216
+ get candidateCount() {
217
+ return compiledCandidates.size
218
+ },
219
+ }
220
+ }
221
+
222
+ export async function updateTailwindForWatchChange(
223
+ state: TailwindScannerState,
224
+ id: string,
225
+ event: TailwindWatchEvent,
226
+ configure: () => Promise<boolean>
227
+ ): Promise<boolean> {
228
+ if (!(await configure())) return false
229
+ if (event === 'delete') {
230
+ return state.removeSource(id)
231
+ }
232
+ try {
233
+ return state.scanSource(id, await readFile(id, 'utf8'))
234
+ } catch {
235
+ return state.removeSource(id)
236
+ }
237
+ }
238
+
239
+ function registerScannerSources(
240
+ scanner: Scanner,
241
+ onDependency: (file: string) => void,
242
+ onSourceGlob: (glob: string) => void
243
+ ) {
244
+ for (const file of scanner.files) {
245
+ onDependency(file)
246
+ }
247
+ for (const { base, pattern } of scanner.globs) {
248
+ onSourceGlob(path.join(base, pattern))
249
+ }
250
+ }
251
+
252
+ async function normalizeSourcePath(id: string): Promise<string> {
253
+ const cleanId = path.resolve(id.split('?', 1)[0])
254
+ try {
255
+ return await realpath(cleanId)
256
+ } catch {
257
+ try {
258
+ return path.join(await realpath(path.dirname(cleanId)), path.basename(cleanId))
259
+ } catch {
260
+ return cleanId
261
+ }
262
+ }
263
+ }
264
+
265
+ function candidatesForSource(scanner: Scanner, id: string, source: string): Set<string> {
266
+ return new Set(
267
+ scanner
268
+ .getCandidatesWithPositions({
269
+ content: source,
270
+ extension: extensionForFile(id),
271
+ })
272
+ .map(({ candidate }) => candidate)
273
+ )
274
+ }