@sidekick-coder/zenith-kit 0.3.12 → 0.4.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.
- package/dist/client/index.es.js +5829 -10915
- package/dist/client/src/client/components.d.ts +1 -1
- package/dist/components/index.es.js +12487 -36956
- package/dist/components/src/client/components.d.ts +1 -1
- package/dist/components/styles.css +1 -1
- package/package.json +5 -7
- package/src/client/components.ts +2 -2
- package/src/client/css/styles.css +31 -62
- package/src/client/css/theme.css +56 -0
- package/vite/plugins/tailwindAutoPrefix.js +475 -0
- package/vite/plugins/tailwindAutoPrefix.unit.test.ts +347 -0
- package/vite.common.config.ts +19 -8
- package/vite.components.config.ts +1 -1
- package/dist/client/src/client/client.d.ts +0 -1
- package/dist/components/src/client/client.d.ts +0 -1
- package/src/client/client.ts +0 -0
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
@import "tailwindcss";
|
|
2
2
|
@import "tw-animate-css";
|
|
3
|
+
@import "./theme.css";
|
|
3
4
|
|
|
4
5
|
@custom-variant dark (&:is(.dark *));
|
|
5
6
|
|
|
7
|
+
@layer base {
|
|
8
|
+
|
|
6
9
|
:root {
|
|
7
10
|
--background: oklch(1 0 0);
|
|
8
11
|
--foreground: oklch(0.141 0.005 285.823);
|
|
@@ -74,68 +77,6 @@
|
|
|
74
77
|
--sidebar-ring: oklch(0.442 0.017 285.786);
|
|
75
78
|
}
|
|
76
79
|
|
|
77
|
-
@theme inline {
|
|
78
|
-
--color-background: var(--background);
|
|
79
|
-
--color-foreground: var(--foreground);
|
|
80
|
-
--color-card: var(--card);
|
|
81
|
-
--color-card-foreground: var(--card-foreground);
|
|
82
|
-
--color-popover: var(--popover);
|
|
83
|
-
--color-popover-foreground: var(--popover-foreground);
|
|
84
|
-
--color-primary: var(--primary);
|
|
85
|
-
--color-primary-foreground: var(--primary-foreground);
|
|
86
|
-
--color-secondary: var(--secondary);
|
|
87
|
-
--color-secondary-foreground: var(--secondary-foreground);
|
|
88
|
-
--color-muted: var(--muted);
|
|
89
|
-
--color-muted-foreground: var(--muted-foreground);
|
|
90
|
-
--color-accent: var(--accent);
|
|
91
|
-
--color-accent-foreground: var(--accent-foreground);
|
|
92
|
-
--color-destructive: var(--destructive);
|
|
93
|
-
--color-destructive-foreground: var(--destructive-foreground);
|
|
94
|
-
--color-border: var(--border);
|
|
95
|
-
--color-input: var(--input);
|
|
96
|
-
--color-ring: var(--ring);
|
|
97
|
-
--color-chart-1: var(--chart-1);
|
|
98
|
-
--color-chart-2: var(--chart-2);
|
|
99
|
-
--color-chart-3: var(--chart-3);
|
|
100
|
-
--color-chart-4: var(--chart-4);
|
|
101
|
-
--color-chart-5: var(--chart-5);
|
|
102
|
-
--radius-sm: calc(var(--radius) - 4px);
|
|
103
|
-
--radius-md: calc(var(--radius) - 2px);
|
|
104
|
-
--radius-lg: var(--radius);
|
|
105
|
-
--radius-xl: calc(var(--radius) + 4px);
|
|
106
|
-
--color-sidebar: var(--sidebar);
|
|
107
|
-
--color-sidebar-foreground: var(--sidebar-foreground);
|
|
108
|
-
--color-sidebar-primary: var(--sidebar-primary);
|
|
109
|
-
--color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
|
|
110
|
-
--color-sidebar-accent: var(--sidebar-accent);
|
|
111
|
-
--color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
|
|
112
|
-
--color-sidebar-border: var(--sidebar-border);
|
|
113
|
-
--color-sidebar-ring: var(--sidebar-ring);
|
|
114
|
-
--animate-accordion-down: accordion-down 0.2s ease-out;
|
|
115
|
-
--animate-accordion-up: accordion-up 0.2s ease-out;
|
|
116
|
-
|
|
117
|
-
@keyframes accordion-down {
|
|
118
|
-
from {
|
|
119
|
-
height: 0;
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
to {
|
|
123
|
-
height: var(--reka-accordion-content-height);
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
@keyframes accordion-up {
|
|
128
|
-
from {
|
|
129
|
-
height: var(--reka-accordion-content-height);
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
to {
|
|
133
|
-
height: 0;
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
|
|
139
80
|
* {
|
|
140
81
|
@apply border-border outline-ring/50;
|
|
141
82
|
}
|
|
@@ -154,3 +95,31 @@ iconify-icon {
|
|
|
154
95
|
width: 1em;
|
|
155
96
|
height: 1em;
|
|
156
97
|
}
|
|
98
|
+
|
|
99
|
+
/* scrollbars */
|
|
100
|
+
/* .zenith-scrollbar { */
|
|
101
|
+
/* scrollbar-width: thin; */
|
|
102
|
+
/* scrollbar-color: var(--foreground) var(--background); */
|
|
103
|
+
/* scrollbar-gutter: stable both-edges; */
|
|
104
|
+
/* } */
|
|
105
|
+
|
|
106
|
+
.zenith-scrollbar::-webkit-scrollbar {
|
|
107
|
+
width: 8px;
|
|
108
|
+
height: 8px;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
.zenith-scrollbar::-webkit-scrollbar-track {
|
|
112
|
+
background: var(--background);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
.zenith-scrollbar::-webkit-scrollbar-thumb {
|
|
116
|
+
background-color: var(--foreground);
|
|
117
|
+
border-radius: 4px;
|
|
118
|
+
border: 2px solid var(--background);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
.zenith-scrollbar::-webkit-scrollbar-corner {
|
|
122
|
+
background: var(--background);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
@theme inline {
|
|
2
|
+
--color-background: var(--background);
|
|
3
|
+
--color-foreground: var(--foreground);
|
|
4
|
+
--color-card: var(--card);
|
|
5
|
+
--color-card-foreground: var(--card-foreground);
|
|
6
|
+
--color-popover: var(--popover);
|
|
7
|
+
--color-popover-foreground: var(--popover-foreground);
|
|
8
|
+
--color-primary: var(--primary);
|
|
9
|
+
--color-primary-foreground: var(--primary-foreground);
|
|
10
|
+
--color-secondary: var(--secondary);
|
|
11
|
+
--color-secondary-foreground: var(--secondary-foreground);
|
|
12
|
+
--color-muted: var(--muted);
|
|
13
|
+
--color-muted-foreground: var(--muted-foreground);
|
|
14
|
+
--color-accent: var(--accent);
|
|
15
|
+
--color-accent-foreground: var(--accent-foreground);
|
|
16
|
+
--color-destructive: var(--destructive);
|
|
17
|
+
--color-destructive-foreground: var(--destructive-foreground);
|
|
18
|
+
--color-border: var(--border);
|
|
19
|
+
--color-input: var(--input);
|
|
20
|
+
--color-ring: var(--ring);
|
|
21
|
+
--color-chart-1: var(--chart-1);
|
|
22
|
+
--color-chart-2: var(--chart-2);
|
|
23
|
+
--color-chart-3: var(--chart-3);
|
|
24
|
+
--color-chart-4: var(--chart-4);
|
|
25
|
+
--color-chart-5: var(--chart-5);
|
|
26
|
+
--radius-sm: calc(var(--radius) - 4px);
|
|
27
|
+
--radius-md: calc(var(--radius) - 2px);
|
|
28
|
+
--radius-lg: var(--radius);
|
|
29
|
+
--radius-xl: calc(var(--radius) + 4px);
|
|
30
|
+
--color-sidebar: var(--sidebar);
|
|
31
|
+
--color-sidebar-foreground: var(--sidebar-foreground);
|
|
32
|
+
--color-sidebar-primary: var(--sidebar-primary);
|
|
33
|
+
--color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
|
|
34
|
+
--color-sidebar-accent: var(--sidebar-accent);
|
|
35
|
+
--color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
|
|
36
|
+
--color-sidebar-border: var(--sidebar-border);
|
|
37
|
+
--color-sidebar-ring: var(--sidebar-ring);
|
|
38
|
+
--animate-accordion-down: accordion-down 0.2s ease-out;
|
|
39
|
+
--animate-accordion-up: accordion-up 0.2s ease-out;
|
|
40
|
+
@keyframes accordion-down {
|
|
41
|
+
from {
|
|
42
|
+
height: 0;
|
|
43
|
+
}
|
|
44
|
+
to {
|
|
45
|
+
height: var(--reka-accordion-content-height);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
@keyframes accordion-up {
|
|
49
|
+
from {
|
|
50
|
+
height: var(--reka-accordion-content-height);
|
|
51
|
+
}
|
|
52
|
+
to {
|
|
53
|
+
height: 0;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
@@ -0,0 +1,475 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
import { createFilter } from '@rollup/pluginutils'
|
|
3
|
+
import MagicString from 'magic-string'
|
|
4
|
+
import * as acorn from 'acorn'
|
|
5
|
+
import postcss from 'postcss'
|
|
6
|
+
import selectorParser from 'postcss-selector-parser'
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* @typedef {Object} Options
|
|
10
|
+
* @property {string} prefix - Prefix prepended to every Tailwind class found (Tailwind v4 variant-style prefix, e.g. `tw` -> `tw:flex`, `tw:hover:flex`)
|
|
11
|
+
* @property {string | string[]} [include] - Files to include (default: `**\/*.{vue,html,js,jsx,ts,tsx}`)
|
|
12
|
+
* @property {string | string[]} [exclude] - Files to exclude (default: `node_modules/**`)
|
|
13
|
+
* @property {string[]} [excludeVars] - CSS custom property (variable) names to leave untouched, e.g. `--reka-*`. Supports `*` wildcards. By default every custom property is prefixed.
|
|
14
|
+
* @property {{ ignore?: string[] }} [classes] - `classes.ignore` lists literal class names (e.g. `.dark`) that must never receive the prefix, even when found as a standalone class. Compound/variant classes that merely contain the name (e.g. `dark:bg-input`) are still prefixed as usual.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Matches `class`, `className`, `:class` and `v-bind:class` attributes and
|
|
19
|
+
* captures the quote character used and the raw attribute value.
|
|
20
|
+
*/
|
|
21
|
+
const CLASS_ATTR_REGEX = /(?:^|\s)(?:class|className|:class|v-bind:class)\s*=\s*(?<quote>["'])(?<value>.*?)\k<quote>/gs
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Matches `style`, `:style` and `v-bind:style` attributes and captures the
|
|
25
|
+
* quote character used and the raw attribute value.
|
|
26
|
+
*/
|
|
27
|
+
const STYLE_ATTR_REGEX = /(?:^|\s)(?:style|:style|v-bind:style)\s*=\s*(?<quote>["'])(?<value>.*?)\k<quote>/gs
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Matches a CSS custom property name, e.g. `--sidebar-width`.
|
|
31
|
+
*/
|
|
32
|
+
const CSS_VAR_NAME_REGEX = /--[a-zA-Z0-9_-]+/g
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Binary operators that compare a value for equality; string literals used
|
|
36
|
+
* as an operand of these must never be treated as class names, since they
|
|
37
|
+
* are being compared against a variable (e.g. `side === 'left'`), not
|
|
38
|
+
* rendered as a class.
|
|
39
|
+
*/
|
|
40
|
+
const EQUALITY_OPERATORS = new Set(['===', '!==', '==', '!='])
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Builds a matcher for CSS variable names that should be left untouched,
|
|
44
|
+
* supporting simple `*` wildcards (e.g. `--reka-*`).
|
|
45
|
+
*
|
|
46
|
+
* @param {string[] | undefined} patterns
|
|
47
|
+
*/
|
|
48
|
+
function createVarExcludeMatcher(patterns) {
|
|
49
|
+
if (!patterns || patterns.length === 0) {
|
|
50
|
+
return () => false
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const regexes = patterns.map(pattern => {
|
|
54
|
+
const escaped = pattern.replace(/[.+^${}()|[\]\\]/g, '\\$&').replace(/\*/g, '.*')
|
|
55
|
+
|
|
56
|
+
return new RegExp(`^${escaped}$`)
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
return (/** @type {string} */ name) => regexes.some(regex => regex.test(name))
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Builds the set of literal class names that must never receive the
|
|
64
|
+
* prefix (e.g. `.dark`), normalized to strip the leading `.` so it can be
|
|
65
|
+
* compared directly against a class token/selector class value.
|
|
66
|
+
*
|
|
67
|
+
* @param {string[] | undefined} patterns
|
|
68
|
+
*/
|
|
69
|
+
function createIgnoredClassSet(patterns) {
|
|
70
|
+
return new Set((patterns || []).map(pattern => pattern.replace(/^\./, '')))
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Renames a single CSS custom property name (e.g. `--sidebar-width` ->
|
|
76
|
+
* `--zkit-sidebar-width`), leaving it untouched if it's already prefixed or
|
|
77
|
+
* matches the exclude matcher.
|
|
78
|
+
*
|
|
79
|
+
* @param {string} name
|
|
80
|
+
* @param {string} prefix
|
|
81
|
+
* @param {(name: string) => boolean} isExcluded
|
|
82
|
+
*/
|
|
83
|
+
function renameCssVarName(name, prefix, isExcluded) {
|
|
84
|
+
if (!name.startsWith('--') || name.startsWith(`--${prefix}-`) || isExcluded(name)) {
|
|
85
|
+
return name
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
return `--${prefix}-${name.slice(2)}`
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Renames every CSS custom property reference found inside a chunk of text
|
|
93
|
+
* (e.g. `var(--sidebar-width)`, or the `--sidebar-width` inside Tailwind's
|
|
94
|
+
* `w-(--sidebar-width)` shorthand), leaving anything that isn't a `--name`
|
|
95
|
+
* token untouched.
|
|
96
|
+
*
|
|
97
|
+
* @param {string} text
|
|
98
|
+
* @param {string} prefix
|
|
99
|
+
* @param {(name: string) => boolean} isExcluded
|
|
100
|
+
*/
|
|
101
|
+
function renameCssVarRefs(text, prefix, isExcluded) {
|
|
102
|
+
return text.replace(CSS_VAR_NAME_REGEX, match => renameCssVarName(match, prefix, isExcluded))
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* @param {string} className
|
|
107
|
+
* @param {string} prefix
|
|
108
|
+
* @param {(name: string) => boolean} isExcluded
|
|
109
|
+
* @param {Set<string>} ignoredClasses
|
|
110
|
+
*/
|
|
111
|
+
function prefixClassName(className, prefix, isExcluded, ignoredClasses) {
|
|
112
|
+
if (!className) return className
|
|
113
|
+
|
|
114
|
+
// a class that is exactly one of the ignored literal names (e.g. `dark`)
|
|
115
|
+
// must be left completely untouched: no prefix, no embedded var rename
|
|
116
|
+
if (ignoredClasses.has(className)) {
|
|
117
|
+
return className
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// rename any CSS variable reference embedded in the class itself, e.g.
|
|
121
|
+
// `w-(--sidebar-width)` or `[calc(var(--sidebar-width)*-1)]`, so it stays
|
|
122
|
+
// in sync with the renamed custom properties in the generated CSS
|
|
123
|
+
const withRenamedVars = className.includes('--')
|
|
124
|
+
? renameCssVarRefs(className, prefix, isExcluded)
|
|
125
|
+
: className
|
|
126
|
+
|
|
127
|
+
if (withRenamedVars.startsWith(`${prefix}:`)) {
|
|
128
|
+
return withRenamedVars
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
return `${prefix}:${withRenamedVars}`
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* @param {string} value
|
|
136
|
+
* @param {string} prefix
|
|
137
|
+
* @param {(name: string) => boolean} isExcluded
|
|
138
|
+
* @param {Set<string>} ignoredClasses
|
|
139
|
+
*/
|
|
140
|
+
function prefixClassList(value, prefix, isExcluded, ignoredClasses) {
|
|
141
|
+
return value
|
|
142
|
+
.split(/(\s+)/) // split keeping whitespace so original formatting is preserved
|
|
143
|
+
.map(token => (token.trim() ? prefixClassName(token, prefix, isExcluded, ignoredClasses) : token))
|
|
144
|
+
.join('')
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* @param {any} node
|
|
149
|
+
* @param {any} parent
|
|
150
|
+
*/
|
|
151
|
+
function isComparisonOperand(node, parent) {
|
|
152
|
+
return !!parent
|
|
153
|
+
&& parent.type === 'BinaryExpression'
|
|
154
|
+
&& EQUALITY_OPERATORS.has(parent.operator)
|
|
155
|
+
&& (parent.left === node || parent.right === node)
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Prefixes only the string literals that are actually rendered as class
|
|
160
|
+
* names inside a dynamic `:class`/`v-bind:class` JS expression, leaving
|
|
161
|
+
* string literals used as comparison operands untouched
|
|
162
|
+
* (e.g. `side === 'left' ? 'left-0' : 'right-0'` only prefixes `'left-0'`
|
|
163
|
+
* and `'right-0'`, never the `'left'` being compared against `side`).
|
|
164
|
+
*
|
|
165
|
+
* Falls back to returning the original value untouched if the expression
|
|
166
|
+
* cannot be parsed as JavaScript.
|
|
167
|
+
*
|
|
168
|
+
* @param {string} value
|
|
169
|
+
* @param {string} prefix
|
|
170
|
+
* @param {(name: string) => boolean} isExcluded
|
|
171
|
+
* @param {Set<string>} ignoredClasses
|
|
172
|
+
*/
|
|
173
|
+
function prefixDynamicClassValue(value, prefix, isExcluded, ignoredClasses) {
|
|
174
|
+
const wrapped = `(${value})`
|
|
175
|
+
|
|
176
|
+
let ast
|
|
177
|
+
|
|
178
|
+
try {
|
|
179
|
+
ast = acorn.parseExpressionAt(wrapped, 0, { ecmaVersion: 'latest' })
|
|
180
|
+
} catch {
|
|
181
|
+
return value
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
const s = new MagicString(wrapped)
|
|
185
|
+
let hasChanges = false
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* @param {any} node
|
|
189
|
+
* @param {any} parent
|
|
190
|
+
*/
|
|
191
|
+
function walk(node, parent) {
|
|
192
|
+
if (!node || typeof node !== 'object' || typeof node.type !== 'string') return
|
|
193
|
+
|
|
194
|
+
const isStringLiteral = node.type === 'Literal' && typeof node.value === 'string'
|
|
195
|
+
|
|
196
|
+
if (isStringLiteral && !isComparisonOperand(node, parent)) {
|
|
197
|
+
const quote = node.raw?.[0] === "'" ? "'" : '"'
|
|
198
|
+
const newValue = prefixClassList(node.value, prefix, isExcluded, ignoredClasses)
|
|
199
|
+
|
|
200
|
+
if (newValue !== node.value) {
|
|
201
|
+
s.overwrite(node.start, node.end, `${quote}${newValue}${quote}`)
|
|
202
|
+
hasChanges = true
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// don't descend into a string literal node, there is nothing left to walk
|
|
207
|
+
if (isStringLiteral) return
|
|
208
|
+
|
|
209
|
+
for (const key in node) {
|
|
210
|
+
if (key === 'start' || key === 'end' || key === 'type') continue
|
|
211
|
+
const child = node[key]
|
|
212
|
+
|
|
213
|
+
if (Array.isArray(child)) {
|
|
214
|
+
child.forEach(item => walk(item, node))
|
|
215
|
+
} else {
|
|
216
|
+
walk(child, node)
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
walk(ast, null)
|
|
222
|
+
|
|
223
|
+
if (!hasChanges) return value
|
|
224
|
+
|
|
225
|
+
return s.toString().slice(1, -1)
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* Rewrites every class selector in the given CSS so it targets the prefixed
|
|
230
|
+
* classes emitted by `prefixClassList`, e.g. `.flex` -> `.zkit\:flex` and
|
|
231
|
+
* `.hover\:flex:hover` -> `.zkit\:hover\:flex:hover`.
|
|
232
|
+
*
|
|
233
|
+
* This is required because Tailwind's own class scanner reads the original
|
|
234
|
+
* (unprefixed) source files directly from disk, so it always generates
|
|
235
|
+
* unprefixed utility CSS regardless of what this plugin does to the JS/Vue
|
|
236
|
+
* output. Prefixing the generated CSS selectors keeps them in sync with the
|
|
237
|
+
* classes emitted at runtime.
|
|
238
|
+
*
|
|
239
|
+
* Every CSS custom property (variable) declaration and usage is also
|
|
240
|
+
* prefixed (e.g. `--sidebar-width` -> `--zkit-sidebar-width`), unless it
|
|
241
|
+
* matches `excludeVars`, so the library's variables never collide with a
|
|
242
|
+
* consuming app's own variables of the same name.
|
|
243
|
+
*
|
|
244
|
+
* Classes matching `ignoredClasses` exactly (e.g. `dark`) are left
|
|
245
|
+
* completely untouched, even standalone in a selector such as
|
|
246
|
+
* `:is(.dark *)`; compound/variant classes that merely contain the name
|
|
247
|
+
* (e.g. `dark:bg-input`) are still prefixed as usual.
|
|
248
|
+
*
|
|
249
|
+
* @param {string} css
|
|
250
|
+
* @param {string} prefix
|
|
251
|
+
* @param {(name: string) => boolean} isExcluded
|
|
252
|
+
* @param {Set<string>} ignoredClasses
|
|
253
|
+
*/
|
|
254
|
+
function prefixCss(css, prefix, isExcluded, ignoredClasses) {
|
|
255
|
+
const root = postcss.parse(css)
|
|
256
|
+
|
|
257
|
+
const transform = selectorParser(selectors => {
|
|
258
|
+
selectors.walkClasses(classNode => {
|
|
259
|
+
if (classNode.value.startsWith(`${prefix}:`)) return
|
|
260
|
+
if (ignoredClasses.has(classNode.value)) return
|
|
261
|
+
|
|
262
|
+
classNode.value = `${prefix}:${classNode.value}`
|
|
263
|
+
})
|
|
264
|
+
})
|
|
265
|
+
|
|
266
|
+
root.walkRules(rule => {
|
|
267
|
+
rule.selector = transform.processSync(rule.selector)
|
|
268
|
+
})
|
|
269
|
+
|
|
270
|
+
// rename every remaining `--name` token anywhere in the stylesheet:
|
|
271
|
+
// declaration props/values, `@property --name { ... }` at-rules and any
|
|
272
|
+
// custom property reference embedded in a selector (e.g. the
|
|
273
|
+
// `--sidebar-width` inside `.zkit\:w-\(--sidebar-width\)`)
|
|
274
|
+
return renameCssVarRefs(root.toString(), prefix, isExcluded)
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
/**
|
|
278
|
+
* Renames CSS custom properties referenced inside a `:style`/`v-bind:style`
|
|
279
|
+
* dynamic binding, so they stay in sync with the renamed variables in the
|
|
280
|
+
* generated CSS: object keys that declare a custom property (e.g.
|
|
281
|
+
* `{ '--sidebar-width': width }`) and any `var(--name)` reference inside a
|
|
282
|
+
* string value (e.g. `{ '--normal-bg': 'var(--popover)' }`).
|
|
283
|
+
*
|
|
284
|
+
* Falls back to returning the original value unchanged if it cannot be
|
|
285
|
+
* parsed as JavaScript.
|
|
286
|
+
*
|
|
287
|
+
* @param {string} value
|
|
288
|
+
* @param {string} prefix
|
|
289
|
+
* @param {(name: string) => boolean} isExcluded
|
|
290
|
+
*/
|
|
291
|
+
function prefixDynamicStyleValue(value, prefix, isExcluded) {
|
|
292
|
+
const wrapped = `(${value})`
|
|
293
|
+
|
|
294
|
+
let ast
|
|
295
|
+
|
|
296
|
+
try {
|
|
297
|
+
ast = acorn.parseExpressionAt(wrapped, 0, { ecmaVersion: 'latest' })
|
|
298
|
+
} catch {
|
|
299
|
+
return value
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
const s = new MagicString(wrapped)
|
|
303
|
+
let hasChanges = false
|
|
304
|
+
|
|
305
|
+
/**
|
|
306
|
+
* @param {any} node
|
|
307
|
+
* @param {any} parent
|
|
308
|
+
*/
|
|
309
|
+
function walk(node, parent) {
|
|
310
|
+
if (!node || typeof node !== 'object' || typeof node.type !== 'string') return
|
|
311
|
+
|
|
312
|
+
const isStringLiteral = node.type === 'Literal' && typeof node.value === 'string'
|
|
313
|
+
|
|
314
|
+
if (isStringLiteral && node.value.includes('--')) {
|
|
315
|
+
const quote = node.raw?.[0] === "'" ? "'" : '"'
|
|
316
|
+
|
|
317
|
+
// object keys declaring a custom property (`'--sidebar-width': ...`)
|
|
318
|
+
// are renamed wholesale; any other string is only scanned for
|
|
319
|
+
// `var(--name)`-style references
|
|
320
|
+
const isObjectKey = parent?.type === 'Property' && parent.key === node && !parent.computed
|
|
321
|
+
|
|
322
|
+
const newValue = isObjectKey && node.value.startsWith('--')
|
|
323
|
+
? renameCssVarName(node.value, prefix, isExcluded)
|
|
324
|
+
: renameCssVarRefs(node.value, prefix, isExcluded)
|
|
325
|
+
|
|
326
|
+
if (newValue !== node.value) {
|
|
327
|
+
s.overwrite(node.start, node.end, `${quote}${newValue}${quote}`)
|
|
328
|
+
hasChanges = true
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
if (isStringLiteral) return
|
|
333
|
+
|
|
334
|
+
for (const key in node) {
|
|
335
|
+
if (key === 'start' || key === 'end' || key === 'type') continue
|
|
336
|
+
const child = node[key]
|
|
337
|
+
|
|
338
|
+
if (Array.isArray(child)) {
|
|
339
|
+
child.forEach(item => walk(item, node))
|
|
340
|
+
} else {
|
|
341
|
+
walk(child, node)
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
walk(ast, null)
|
|
347
|
+
|
|
348
|
+
if (!hasChanges) return value
|
|
349
|
+
|
|
350
|
+
return s.toString().slice(1, -1)
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
/**
|
|
354
|
+
* Vite plugin that scans included files for Tailwind CSS classes (`class`,
|
|
355
|
+
* `className`, `:class`, `v-bind:class`) and prepends a fixed prefix to each
|
|
356
|
+
* class found, following Tailwind v4's variant-style prefix syntax
|
|
357
|
+
* (e.g. `flex` -> `tw:flex`, `hover:flex` -> `tw:hover:flex`).
|
|
358
|
+
*
|
|
359
|
+
* @param {Options} options
|
|
360
|
+
* @returns {import('vite').Plugin[]}
|
|
361
|
+
*/
|
|
362
|
+
export default function(options) {
|
|
363
|
+
const prefix = options?.prefix
|
|
364
|
+
|
|
365
|
+
if (!prefix) {
|
|
366
|
+
throw new Error('[tailwindAutoPrefix] "prefix" option is required')
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
const include = options?.include || '**/*.{vue,html,js,jsx,ts,tsx}'
|
|
370
|
+
const exclude = options?.exclude || 'node_modules/**'
|
|
371
|
+
|
|
372
|
+
const filter = createFilter(include, exclude)
|
|
373
|
+
const isExcludedVar = createVarExcludeMatcher(options?.excludeVars)
|
|
374
|
+
const ignoredClasses = createIgnoredClassSet(options?.classes?.ignore)
|
|
375
|
+
|
|
376
|
+
const sourcePlugin = {
|
|
377
|
+
name: 'tailwind-auto-prefix',
|
|
378
|
+
enforce: /** @type {const} */ ('pre'),
|
|
379
|
+
|
|
380
|
+
/**
|
|
381
|
+
* @param {string} code
|
|
382
|
+
* @param {string} id
|
|
383
|
+
*/
|
|
384
|
+
transform(code, id) {
|
|
385
|
+
if (!filter(id)) {
|
|
386
|
+
return null
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
const s = new MagicString(code)
|
|
390
|
+
let hasChanges = false
|
|
391
|
+
|
|
392
|
+
CLASS_ATTR_REGEX.lastIndex = 0
|
|
393
|
+
|
|
394
|
+
let match
|
|
395
|
+
|
|
396
|
+
while ((match = CLASS_ATTR_REGEX.exec(code))) {
|
|
397
|
+
const fullMatch = match[0]
|
|
398
|
+
const { quote, value } = /** @type {{ quote: string, value: string }} */ (match.groups)
|
|
399
|
+
|
|
400
|
+
const quotedValue = `${quote}${value}${quote}`
|
|
401
|
+
const relativeIndex = fullMatch.lastIndexOf(quotedValue)
|
|
402
|
+
const valueStart = match.index + relativeIndex + 1
|
|
403
|
+
const valueEnd = valueStart + value.length
|
|
404
|
+
|
|
405
|
+
const isDynamicBinding = /^\s*(?::class|v-bind:class)\s*=/.test(fullMatch)
|
|
406
|
+
|
|
407
|
+
let newValue
|
|
408
|
+
|
|
409
|
+
if (isDynamicBinding) {
|
|
410
|
+
newValue = prefixDynamicClassValue(value, prefix, isExcludedVar, ignoredClasses)
|
|
411
|
+
} else {
|
|
412
|
+
newValue = prefixClassList(value, prefix, isExcludedVar, ignoredClasses)
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
if (newValue !== value) {
|
|
416
|
+
s.overwrite(valueStart, valueEnd, newValue)
|
|
417
|
+
hasChanges = true
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
STYLE_ATTR_REGEX.lastIndex = 0
|
|
422
|
+
|
|
423
|
+
while ((match = STYLE_ATTR_REGEX.exec(code))) {
|
|
424
|
+
const fullMatch = match[0]
|
|
425
|
+
const { quote, value } = /** @type {{ quote: string, value: string }} */ (match.groups)
|
|
426
|
+
|
|
427
|
+
const quotedValue = `${quote}${value}${quote}`
|
|
428
|
+
const relativeIndex = fullMatch.lastIndexOf(quotedValue)
|
|
429
|
+
const valueStart = match.index + relativeIndex + 1
|
|
430
|
+
const valueEnd = valueStart + value.length
|
|
431
|
+
|
|
432
|
+
const isDynamicBinding = /^\s*(?::style|v-bind:style)\s*=/.test(fullMatch)
|
|
433
|
+
|
|
434
|
+
const newValue = isDynamicBinding
|
|
435
|
+
? prefixDynamicStyleValue(value, prefix, isExcludedVar)
|
|
436
|
+
: renameCssVarRefs(value, prefix, isExcludedVar)
|
|
437
|
+
|
|
438
|
+
if (newValue !== value) {
|
|
439
|
+
s.overwrite(valueStart, valueEnd, newValue)
|
|
440
|
+
hasChanges = true
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
if (!hasChanges) return null
|
|
445
|
+
|
|
446
|
+
return {
|
|
447
|
+
code: s.toString(),
|
|
448
|
+
map: s.generateMap({ hires: true }),
|
|
449
|
+
}
|
|
450
|
+
},
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
const cssPlugin = {
|
|
454
|
+
name: 'tailwind-auto-prefix:css',
|
|
455
|
+
enforce: /** @type {const} */ ('post'),
|
|
456
|
+
|
|
457
|
+
/**
|
|
458
|
+
* @param {*} _options
|
|
459
|
+
* @param {Record<string, import('vite').Rollup.OutputAsset | import('vite').Rollup.OutputChunk>} bundle
|
|
460
|
+
*/
|
|
461
|
+
generateBundle(_options, bundle) {
|
|
462
|
+
for (const fileName of Object.keys(bundle)) {
|
|
463
|
+
const chunk = bundle[fileName]
|
|
464
|
+
|
|
465
|
+
if (chunk.type !== 'asset' || !fileName.endsWith('.css') || typeof chunk.source !== 'string') {
|
|
466
|
+
continue
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
chunk.source = prefixCss(chunk.source, prefix, isExcludedVar, ignoredClasses)
|
|
470
|
+
}
|
|
471
|
+
},
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
return [sourcePlugin, cssPlugin]
|
|
475
|
+
}
|