vendor plugins

This commit is contained in:
Alex 2024-08-10 03:50:37 -04:00
parent 338fe92f3b
commit 45165c8f23
14 changed files with 3456 additions and 863 deletions

View File

@ -2,7 +2,6 @@
const { extendConfig } = require("@aet/eslint-rules");
module.exports = extendConfig({
plugins: ["react", "unicorn"],
rules: {
"class-methods-use-this": "error",
},

View File

@ -1,6 +1,6 @@
{
"name": "@aet/tailwind",
"version": "1.0.3",
"version": "1.0.7",
"license": "MIT",
"scripts": {
"build": "./scripts/index.ts",
@ -14,17 +14,22 @@
".": "./dist/index.js",
"./package.json": "./package.json",
"./classed": "./dist/classed.mjs",
"./µ": {
"types": "./dist/macro.d.ts"
},
"./plugin/react-aria-components": "./dist/vendor/react-aria-components.js",
"./plugin/animate": "./dist/vendor/animate.js",
"./macro": {
"types": "./dist/macro.d.ts"
}
},
"devDependencies": {
"@aet/eslint-rules": "^1.0.1-beta.10",
"@aet/eslint-rules": "1.0.1-beta.24",
"@types/babel__core": "^7.20.5",
"@types/bun": "^1.1.6",
"@types/dedent": "^0.7.2",
"@types/lodash": "^4.17.7",
"@types/node": "^20.14.12",
"@types/node": "^22.2.0",
"@types/postcss-safe-parser": "^5.0.4",
"@types/react": "^18.3.3",
"@types/stylis": "^4.2.6",
@ -38,27 +43,27 @@
"nolyfill": "^1.0.39",
"postcss-nested": "^6.2.0",
"prettier": "^3.3.3",
"tailwindcss": "^3.4.7",
"tailwindcss": "^3.4.9",
"tslib": "^2.6.3",
"tsup": "^8.2.3",
"tsup": "^8.2.4",
"typescript": "^5.5.4",
"vite": "^5.3.5",
"vitest": "^2.0.4"
"vite": "^5.4.0",
"vitest": "^2.0.5"
},
"peerDependencies": {
"tailwindcss": "^3.4.3"
},
"dependencies": {
"@babel/core": "^7.24.9",
"@babel/core": "^7.25.2",
"@emotion/hash": "^0.9.2",
"esbuild": "^0.23.0",
"json5": "^2.2.3",
"lodash": "^4.17.21",
"postcss": "^8.4.40",
"postcss": "^8.4.41",
"postcss-selector-parser": "^6.1.1",
"stylis": "^4.3.2",
"tiny-invariant": "^1.3.3",
"type-fest": "^4.23.0"
"type-fest": "^4.24.0"
},
"prettier": {
"arrowParens": "avoid",

1702
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@ -30,6 +30,19 @@ await Promise.all([
entry: ["src/index.ts"],
external: ["postcss-selector-parser", "postcss", "stylis"],
}),
...["animate", "aspect-ratio", "forms", "react-aria-components", "typography"].map(
name =>
build({
...tsupConfig,
entry: [`./src/vendor/${name}.ts`],
outDir: "dist/vendor",
external: [
"tailwindcss/plugin",
"tailwindcss/colors",
"tailwindcss/defaultTheme",
],
})
),
Bun.write(
"dist/package.json",
JSON.stringify(

View File

@ -60,4 +60,115 @@ describe("attr", () => {
matchSnapshot(files);
});
it.only("fails", async () => {
const { files } = await compileESBuild({
clsx: "emotion",
expectFiles: 2,
javascript: /* tsx */ `
type Type = "dependencies" | "devDependencies" | "peerDependencies"
const Version = classed("span", tw\`text-[var(--color-fg-muted)]\`)
function TreeItem({
name,
version,
type,
}: {
name: string
version: string
type: Type
}) {
const [open, toggle] = useToggle(false)
const query = useQuery({
...getRegistryPackageInfo(name),
enabled: open,
})
const versions = query.data ? Object.keys(query.data.versions) : undefined
const matchingVersion = versions
? semverMaxSatisfying(versions, version)
: undefined
const currentVersion =
matchingVersion != null ? query.data?.versions[matchingVersion] : undefined
const data = currentVersion
? Object.entries(currentVersion[type] ?? {})
: undefined
const isDeprecated = true
const hasNoDeps = data?.length === 0
const Icon = hasNoDeps ? SmallMinus : open ? ChevronDown : ChevronRight
return (
<li data-loading={query.isLoading}>
<div css="flex items-center gap-1 ps-0">
<span css="leading-3">
<Icon
className={Classes.TREE_NODE_CARET}
css={["min-w-0 p-0", hasNoDeps && "cursor-default"]}
onClick={hasNoDeps ? undefined : toggle}
/>
</span>
<Link href={\`/package/\${name}\`}>{name}</Link>
<span
css={[
"text-[var(--color-fg-muted)]",
isDeprecated && "italic line-through opacity-80",
]}
>
{version}
</span>
</div>
{!open || hasNoDeps ? null : query.error ? (
<div css="mb-2 ml-4 mt-1 max-w-96">
<Callout compact intent={Intent.DANGER} css="rounded-md">
{(query.error as Error).message}
</Callout>
</div>
) : data ? (
<ul className={Classes.LIST} css="ml-0 list-none">
{data.map(([dep, version]) => (
<TreeItem key={dep} name={dep} version={version} type={type} />
))}
</ul>
) : (
<div className={Classes.SKELETON} css="mb-2 ml-5 mt-1 h-4 w-40" />
)}
</li>
)
}
export function DepList({
title,
deps,
type,
}: {
title: string
deps: Record<string, string>
count?: number
type: Type
}) {
const entries = Object.entries(deps)
return (
<div className="wmde-markdown-var">
<H4>
{title} ({entries.length})
</H4>
<ul className={Classes.LIST} css="list-none p-0">
{entries.map(([dep, version]) => (
<TreeItem key={dep} name={dep} version={version} type={type} />
))}
</ul>
</div>
)
}
`,
});
files;
});
});

View File

@ -4,7 +4,7 @@ import hash from "@emotion/hash";
import { isPlainObject } from "lodash";
import invariant from "tiny-invariant";
import { type NodePath, type types as t } from "@babel/core";
import { type SourceLocation, type StyleMapEntry, macroName } from "./shared";
import { type SourceLocation, type StyleMapEntry, macroNames } from "./shared";
import { type ResolveTailwindOptions, getClassName } from "./index";
export type ClassNameCollector = (path: string, entries: StyleMapEntry[]) => void;
@ -143,7 +143,7 @@ export function babelTailwind(
const _ = getUtils(path, state, t);
Object.assign(state, _);
for (const { callee, imported, prefix } of getMacros(t, path, macroName).map(
for (const { callee, imported, prefix } of getMacros(t, path, macroNames).map(
macro => mapMacro(t, macro)
)) {
const type = imported === "tw" ? "css" : imported === "tws" ? "js" : undefined;
@ -431,12 +431,12 @@ function getName(t: BabelTypes, exp: t.Node) {
function getMacros(
t: BabelTypes,
programPath: NodePath<t.Program>,
importSource: string
importSources: string[]
) {
const importDecs = programPath
.get("body")
.filter(x => isNodePath(x, t.isImportDeclaration))
.filter(x => x.node.source.value === importSource);
.filter(x => importSources.includes(x.node.source.value));
const macros = importDecs
.flatMap(x => x.get("specifiers"))
@ -526,7 +526,8 @@ function getSuffix(add: boolean | undefined, entries: StyleMapEntry[]) {
return `?${cacheKey}`;
}
const trim = (value: string) => value.replace(/\s+/g, " ").trim().split(" ");
const trim = (value: string) =>
value.replace(/\s+/g, " ").trim().split(" ").filter(Boolean);
const trimPrefix = (cls: string, prefix = "") => trim(cls).map(value => prefix + value);
const flatMapEntries = <K extends string | number, V, R>(

View File

@ -35,6 +35,10 @@ export const babelPlugin = ({
}
const { code } = transformSync(load(), {
assumptions: {
noDocumentAll: true,
noClassCalls: true,
},
parserOpts: {
createImportExpressions: true,
plugins: [

View File

@ -3,7 +3,7 @@ import postcss from "postcss";
import type { ResolveTailwindOptions } from "./index";
export const { name: pkgName } = [require][0]("../package.json");
export const macroName = `${pkgName}/macro`;
export const macroNames = [`${pkgName}/macro`, `${pkgName}`];
interface LineColumn {
line: number;

190
src/vendor/animate.ts vendored Normal file
View File

@ -0,0 +1,190 @@
// https://github.com/jamiebuilds/tailwindcss-animate/commit/ac0dd3a3c81681b78f1d8ea5e7478044213995e1
import plugin from "tailwindcss/plugin";
import type { PluginAPI } from "tailwindcss/types/config";
function filterDefault<T extends object>(values: T) {
return Object.fromEntries(
Object.entries(values).filter(([key]) => key !== "DEFAULT")
) as Omit<T, "DEFAULT">;
}
export default plugin(
({ addUtilities, matchUtilities, theme }) => {
addUtilities({
"@keyframes enter": theme("keyframes.enter"),
"@keyframes exit": theme("keyframes.exit"),
".animate-in": {
animationName: "enter",
animationDuration: theme("animationDuration.DEFAULT"),
"--tw-enter-opacity": "initial",
"--tw-enter-scale": "initial",
"--tw-enter-rotate": "initial",
"--tw-enter-translate-x": "initial",
"--tw-enter-translate-y": "initial",
},
".animate-out": {
animationName: "exit",
animationDuration: theme("animationDuration.DEFAULT"),
"--tw-exit-opacity": "initial",
"--tw-exit-scale": "initial",
"--tw-exit-rotate": "initial",
"--tw-exit-translate-x": "initial",
"--tw-exit-translate-y": "initial",
},
});
matchUtilities(
{
"fade-in": value => ({ "--tw-enter-opacity": value }),
"fade-out": value => ({ "--tw-exit-opacity": value }),
},
{ values: theme("animationOpacity")! }
);
matchUtilities(
{
"zoom-in": value => ({ "--tw-enter-scale": value }),
"zoom-out": value => ({ "--tw-exit-scale": value }),
},
{ values: theme("animationScale")! }
);
matchUtilities(
{
"spin-in": value => ({ "--tw-enter-rotate": value }),
"spin-out": value => ({ "--tw-exit-rotate": value }),
},
{ values: theme("animationRotate")! }
);
matchUtilities(
{
"slide-in-from-top": value => ({
"--tw-enter-translate-y": `-${value}`,
}),
"slide-in-from-bottom": value => ({
"--tw-enter-translate-y": value,
}),
"slide-in-from-left": value => ({
"--tw-enter-translate-x": `-${value}`,
}),
"slide-in-from-right": value => ({
"--tw-enter-translate-x": value,
}),
"slide-out-to-top": value => ({
"--tw-exit-translate-y": `-${value}`,
}),
"slide-out-to-bottom": value => ({
"--tw-exit-translate-y": value,
}),
"slide-out-to-left": value => ({
"--tw-exit-translate-x": `-${value}`,
}),
"slide-out-to-right": value => ({
"--tw-exit-translate-x": value,
}),
},
{ values: theme("animationTranslate")! }
);
matchUtilities(
{ duration: value => ({ animationDuration: value }) },
{ values: filterDefault(theme("animationDuration")!) }
);
matchUtilities(
{ delay: value => ({ animationDelay: value }) },
{ values: theme("animationDelay")! }
);
matchUtilities(
{ ease: value => ({ animationTimingFunction: value }) },
{ values: filterDefault(theme("animationTimingFunction")!) }
);
addUtilities({
".running": { animationPlayState: "running" },
".paused": { animationPlayState: "paused" },
});
matchUtilities(
{ "fill-mode": value => ({ animationFillMode: value }) },
{ values: theme("animationFillMode")! }
);
matchUtilities(
{ direction: value => ({ animationDirection: value }) },
{ values: theme("animationDirection")! }
);
matchUtilities(
{ repeat: value => ({ animationIterationCount: value }) },
{ values: theme("animationRepeat")! }
);
},
{
theme: {
extend: {
animationDelay: ({ theme }: PluginAPI) => ({
...theme("transitionDelay"),
}),
animationDuration: ({ theme }: PluginAPI) => ({
0: "0ms",
...theme("transitionDuration"),
}),
animationTimingFunction: ({ theme }: PluginAPI) => ({
...theme("transitionTimingFunction"),
}),
animationFillMode: {
none: "none",
forwards: "forwards",
backwards: "backwards",
both: "both",
},
animationDirection: {
normal: "normal",
reverse: "reverse",
alternate: "alternate",
"alternate-reverse": "alternate-reverse",
},
animationOpacity: ({ theme }: PluginAPI) => ({
DEFAULT: 0,
...theme("opacity"),
}),
animationTranslate: ({ theme }: PluginAPI) => ({
DEFAULT: "100%",
...theme("translate"),
}),
animationScale: ({ theme }: PluginAPI) => ({
DEFAULT: 0,
...theme("scale"),
}),
animationRotate: ({ theme }: PluginAPI) => ({
DEFAULT: "30deg",
...theme("rotate"),
}),
animationRepeat: {
0: "0",
1: "1",
infinite: "infinite",
},
keyframes: {
enter: {
from: {
opacity: "var(--tw-enter-opacity, 1)",
transform:
"translate3d(var(--tw-enter-translate-x, 0), var(--tw-enter-translate-y, 0), 0) scale3d(var(--tw-enter-scale, 1), var(--tw-enter-scale, 1), var(--tw-enter-scale, 1)) rotate(var(--tw-enter-rotate, 0))",
},
},
exit: {
to: {
opacity: "var(--tw-exit-opacity, 1)",
transform:
"translate3d(var(--tw-exit-translate-x, 0), var(--tw-exit-translate-y, 0), 0) scale3d(var(--tw-exit-scale, 1), var(--tw-exit-scale, 1), var(--tw-exit-scale, 1)) rotate(var(--tw-exit-rotate, 0))",
},
},
},
},
},
}
);

78
src/vendor/aspect-ratio.ts vendored Normal file
View File

@ -0,0 +1,78 @@
// https://github.com/tailwindlabs/tailwindcss-aspect-ratio/commit/b2a9d02229946f3430c0013198be2affa7a175da
import plugin from "tailwindcss/plugin";
const baseStyles = {
position: "relative",
paddingBottom: `calc(var(--tw-aspect-h) / var(--tw-aspect-w) * 100%)`,
};
const childStyles = {
position: "absolute",
height: "100%",
width: "100%",
top: "0",
right: "0",
bottom: "0",
left: "0",
};
const noneComponent = {
".aspect-none": {
position: "static",
paddingBottom: "0",
},
".aspect-none > *": {
position: "static",
height: "auto",
width: "auto",
top: "auto",
right: "auto",
bottom: "auto",
left: "auto",
},
};
export default plugin(
({ addComponents, matchComponents, theme }) => {
const values = theme("aspectRatio")!;
matchComponents<string>(
{
"aspect-w": value => ({
...baseStyles,
"--tw-aspect-w": value,
"> *": childStyles,
}),
"aspect-h": value => ({ "--tw-aspect-h": value }),
},
{ values }
);
addComponents(noneComponent);
},
{
theme: {
aspectRatio: {
1: "1",
2: "2",
3: "3",
4: "4",
5: "5",
6: "6",
7: "7",
8: "8",
9: "9",
10: "10",
11: "11",
12: "12",
13: "13",
14: "14",
15: "15",
16: "16",
},
},
variants: {
aspectRatio: ["responsive"],
},
}
);

461
src/vendor/forms.ts vendored Normal file
View File

@ -0,0 +1,461 @@
// https://github.com/tailwindlabs/tailwindcss-forms/commit/c9d9da3e010b194a1f0e9c36fbd98c83e4762840
import plugin from "tailwindcss/plugin";
import defaultTheme from "tailwindcss/defaultTheme";
import colors from "tailwindcss/colors";
import type { CSSRuleObject } from "tailwindcss/types/config";
const shorterNames = {
aqua: /#00ffff(ff)?(?!\w)|#0ff(f)?(?!\w)/gi,
azure: /#f0ffff(ff)?(?!\w)/gi,
beige: /#f5f5dc(ff)?(?!\w)/gi,
bisque: /#ffe4c4(ff)?(?!\w)/gi,
black: /#000000(ff)?(?!\w)|#000(f)?(?!\w)/gi,
blue: /#0000ff(ff)?(?!\w)|#00f(f)?(?!\w)/gi,
brown: /#a52a2a(ff)?(?!\w)/gi,
coral: /#ff7f50(ff)?(?!\w)/gi,
cornsilk: /#fff8dc(ff)?(?!\w)/gi,
crimson: /#dc143c(ff)?(?!\w)/gi,
cyan: /#00ffff(ff)?(?!\w)|#0ff(f)?(?!\w)/gi,
darkblue: /#00008b(ff)?(?!\w)/gi,
darkcyan: /#008b8b(ff)?(?!\w)/gi,
darkgrey: /#a9a9a9(ff)?(?!\w)/gi,
darkred: /#8b0000(ff)?(?!\w)/gi,
deeppink: /#ff1493(ff)?(?!\w)/gi,
dimgrey: /#696969(ff)?(?!\w)/gi,
gold: /#ffd700(ff)?(?!\w)/gi,
green: /#008000(ff)?(?!\w)/gi,
grey: /#808080(ff)?(?!\w)/gi,
honeydew: /#f0fff0(ff)?(?!\w)/gi,
hotpink: /#ff69b4(ff)?(?!\w)/gi,
indigo: /#4b0082(ff)?(?!\w)/gi,
ivory: /#fffff0(ff)?(?!\w)/gi,
khaki: /#f0e68c(ff)?(?!\w)/gi,
lavender: /#e6e6fa(ff)?(?!\w)/gi,
lime: /#00ff00(ff)?(?!\w)|#0f0(f)?(?!\w)/gi,
linen: /#faf0e6(ff)?(?!\w)/gi,
maroon: /#800000(ff)?(?!\w)/gi,
moccasin: /#ffe4b5(ff)?(?!\w)/gi,
navy: /#000080(ff)?(?!\w)/gi,
oldlace: /#fdf5e6(ff)?(?!\w)/gi,
olive: /#808000(ff)?(?!\w)/gi,
orange: /#ffa500(ff)?(?!\w)/gi,
orchid: /#da70d6(ff)?(?!\w)/gi,
peru: /#cd853f(ff)?(?!\w)/gi,
pink: /#ffc0cb(ff)?(?!\w)/gi,
plum: /#dda0dd(ff)?(?!\w)/gi,
purple: /#800080(ff)?(?!\w)/gi,
red: /#ff0000(ff)?(?!\w)|#f00(f)?(?!\w)/gi,
salmon: /#fa8072(ff)?(?!\w)/gi,
seagreen: /#2e8b57(ff)?(?!\w)/gi,
seashell: /#fff5ee(ff)?(?!\w)/gi,
sienna: /#a0522d(ff)?(?!\w)/gi,
silver: /#c0c0c0(ff)?(?!\w)/gi,
skyblue: /#87ceeb(ff)?(?!\w)/gi,
snow: /#fffafa(ff)?(?!\w)/gi,
tan: /#d2b48c(ff)?(?!\w)/gi,
teal: /#008080(ff)?(?!\w)/gi,
thistle: /#d8bfd8(ff)?(?!\w)/gi,
tomato: /#ff6347(ff)?(?!\w)/gi,
violet: /#ee82ee(ff)?(?!\w)/gi,
wheat: /#f5deb3(ff)?(?!\w)/gi,
white: /#ffffff(ff)?(?!\w)|#fff(f)?(?!\w)/gi,
};
const REGEX = {
whitespace: /\s+/g,
urlHexPairs: /%[\dA-F]{2}/g,
quotes: /"/g,
};
function collapseWhitespace(str: string) {
return str.trim().replace(REGEX.whitespace, " ");
}
function dataURIPayload(string: string) {
return encodeURIComponent(string).replace(REGEX.urlHexPairs, specialHexEncode);
}
// `#` gets converted to `%23`, so quite a few CSS named colors are shorter than
// their equivalent URL-encoded hex codes.
function colorCodeToShorterNames(string: string) {
for (const [key, value] of Object.entries(shorterNames)) {
if (value.test(string)) {
string = string.replace(value, key);
}
}
return string;
}
function specialHexEncode(match: string) {
// Browsers tolerate these characters, and they're frequent
switch (match) {
case "%20":
return " ";
case "%3D":
return "=";
case "%3A":
return ":";
case "%2F":
return "/";
default:
return match.toLowerCase(); // compresses better
}
}
function svgToDataUri(svgString: string) {
// Strip the Byte-Order Mark if the SVG has one
// eslint-disable-next-line unicorn/number-literal-case
if (svgString.charCodeAt(0) === 0xfeff) {
svgString = svgString.slice(1);
}
const body = colorCodeToShorterNames(collapseWhitespace(svgString)).replace(
REGEX.quotes,
"'"
);
return "data:image/svg+xml," + dataURIPayload(body);
}
const [baseFontSize, { lineHeight: baseLineHeight }] = defaultTheme.fontSize.base;
const { spacing, borderWidth, borderRadius } = defaultTheme;
type Strategy = "base" | "class";
export default plugin.withOptions<{ strategy?: Strategy }>(
options =>
function ({ addBase, addComponents, theme }) {
const strategy =
options?.strategy === undefined ? ["base", "class"] : [options.strategy];
const rules = [
{
base: [
"[type='text']",
"input:where(:not([type]))",
"[type='email']",
"[type='url']",
"[type='password']",
"[type='number']",
"[type='date']",
"[type='datetime-local']",
"[type='month']",
"[type='search']",
"[type='tel']",
"[type='time']",
"[type='week']",
"[multiple]",
"textarea",
"select",
],
class: [".form-input", ".form-textarea", ".form-select", ".form-multiselect"],
styles: {
appearance: "none",
"background-color": "#fff",
"border-color": theme("colors.gray.500", colors.gray[500]),
"border-width": borderWidth["DEFAULT"],
"border-radius": borderRadius.none,
"padding-top": spacing[2],
"padding-right": spacing[3],
"padding-bottom": spacing[2],
"padding-left": spacing[3],
"font-size": baseFontSize,
"line-height": baseLineHeight,
"--tw-shadow": "0 0 #0000",
"&:focus": {
outline: "2px solid transparent",
"outline-offset": "2px",
"--tw-ring-inset": "var(--tw-empty,/*!*/ /*!*/)",
"--tw-ring-offset-width": "0px",
"--tw-ring-offset-color": "#fff",
"--tw-ring-color": theme("colors.blue.600", colors.blue[600]),
"--tw-ring-offset-shadow": `var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color)`,
"--tw-ring-shadow": `var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color)`,
"box-shadow": `var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)`,
"border-color": theme("colors.blue.600", colors.blue[600]),
},
},
},
{
base: ["input::placeholder", "textarea::placeholder"],
class: [".form-input::placeholder", ".form-textarea::placeholder"],
styles: {
color: theme("colors.gray.500", colors.gray[500]),
opacity: "1",
},
},
{
base: ["::-webkit-datetime-edit-fields-wrapper"],
class: [".form-input::-webkit-datetime-edit-fields-wrapper"],
styles: {
padding: "0",
},
},
{
// Unfortunate hack until https://bugs.webkit.org/show_bug.cgi?id=198959 is fixed.
// This sucks because users can't change line-height with a utility on date inputs now.
// Reference: https://github.com/twbs/bootstrap/pull/31993
base: ["::-webkit-date-and-time-value"],
class: [".form-input::-webkit-date-and-time-value"],
styles: {
"min-height": "1.5em",
},
},
{
// In Safari on iOS date and time inputs are centered instead of left-aligned and can't be
// changed with `text-align` utilities on the input by default. Resetting this to `inherit`
// makes them left-aligned by default and makes it possible to override the alignment with
// utility classes without using an arbitrary variant to target the pseudo-elements.
base: ["::-webkit-date-and-time-value"],
class: [".form-input::-webkit-date-and-time-value"],
styles: {
"text-align": "inherit",
},
},
{
// In Safari on macOS date time inputs that are set to `display: block` have unexpected
// extra bottom spacing. This can be corrected by setting the `::-webkit-datetime-edit`
// pseudo-element to `display: inline-flex`, instead of the browser default of
// `display: inline-block`.
base: ["::-webkit-datetime-edit"],
class: [".form-input::-webkit-datetime-edit"],
styles: {
display: "inline-flex",
},
},
{
// In Safari on macOS date time inputs are 4px taller than normal inputs
// This is because there is extra padding on the datetime-edit and datetime-edit-{part}-field pseudo elements
// See https://github.com/tailwindlabs/tailwindcss-forms/issues/95
base: [
"::-webkit-datetime-edit",
"::-webkit-datetime-edit-year-field",
"::-webkit-datetime-edit-month-field",
"::-webkit-datetime-edit-day-field",
"::-webkit-datetime-edit-hour-field",
"::-webkit-datetime-edit-minute-field",
"::-webkit-datetime-edit-second-field",
"::-webkit-datetime-edit-millisecond-field",
"::-webkit-datetime-edit-meridiem-field",
],
class: [
".form-input::-webkit-datetime-edit",
".form-input::-webkit-datetime-edit-year-field",
".form-input::-webkit-datetime-edit-month-field",
".form-input::-webkit-datetime-edit-day-field",
".form-input::-webkit-datetime-edit-hour-field",
".form-input::-webkit-datetime-edit-minute-field",
".form-input::-webkit-datetime-edit-second-field",
".form-input::-webkit-datetime-edit-millisecond-field",
".form-input::-webkit-datetime-edit-meridiem-field",
],
styles: {
"padding-top": 0,
"padding-bottom": 0,
},
},
{
base: ["select"],
class: [".form-select"],
styles: {
"background-image": `url("${svgToDataUri(
`<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 20 20"><path stroke="${theme(
"colors.gray.500",
colors.gray[500]
)}" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M6 8l4 4 4-4"/></svg>`
)}")`,
"background-position": `right ${spacing[2]} center`,
"background-repeat": `no-repeat`,
"background-size": `1.5em 1.5em`,
"padding-right": spacing[10],
"print-color-adjust": `exact`,
},
},
{
base: ["[multiple]", '[size]:where(select:not([size="1"]))'],
class: ['.form-select:where([size]:not([size="1"]))'],
styles: {
"background-image": "initial",
"background-position": "initial",
"background-repeat": "unset",
"background-size": "initial",
"padding-right": spacing[3],
"print-color-adjust": "unset",
},
},
{
base: [`[type='checkbox']`, `[type='radio']`],
class: [".form-checkbox", ".form-radio"],
styles: {
appearance: "none",
padding: "0",
"print-color-adjust": "exact",
display: "inline-block",
"vertical-align": "middle",
"background-origin": "border-box",
"user-select": "none",
"flex-shrink": "0",
height: spacing[4],
width: spacing[4],
color: theme("colors.blue.600", colors.blue[600]),
"background-color": "#fff",
"border-color": theme("colors.gray.500", colors.gray[500]),
"border-width": borderWidth["DEFAULT"],
"--tw-shadow": "0 0 #0000",
},
},
{
base: [`[type='checkbox']`],
class: [".form-checkbox"],
styles: {
"border-radius": borderRadius["none"],
},
},
{
base: [`[type='radio']`],
class: [".form-radio"],
styles: {
"border-radius": "100%",
},
},
{
base: [`[type='checkbox']:focus`, `[type='radio']:focus`],
class: [".form-checkbox:focus", ".form-radio:focus"],
styles: {
outline: "2px solid transparent",
"outline-offset": "2px",
"--tw-ring-inset": "var(--tw-empty,/*!*/ /*!*/)",
"--tw-ring-offset-width": "2px",
"--tw-ring-offset-color": "#fff",
"--tw-ring-color": theme("colors.blue.600", colors.blue[600]),
"--tw-ring-offset-shadow": `var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color)`,
"--tw-ring-shadow": `var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color)`,
"box-shadow": `var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)`,
},
},
{
base: [`[type='checkbox']:checked`, `[type='radio']:checked`],
class: [".form-checkbox:checked", ".form-radio:checked"],
styles: {
"border-color": `transparent`,
"background-color": `currentColor`,
"background-size": `100% 100%`,
"background-position": `center`,
"background-repeat": `no-repeat`,
},
},
{
base: [`[type='checkbox']:checked`],
class: [".form-checkbox:checked"],
styles: {
"background-image": `url("${svgToDataUri(
`<svg viewBox="0 0 16 16" fill="white" xmlns="http://www.w3.org/2000/svg"><path d="M12.207 4.793a1 1 0 010 1.414l-5 5a1 1 0 01-1.414 0l-2-2a1 1 0 011.414-1.414L6.5 9.086l4.293-4.293a1 1 0 011.414 0z"/></svg>`
)}")`,
"@media (forced-colors: active) ": {
appearance: "auto",
},
},
},
{
base: [`[type='radio']:checked`],
class: [".form-radio:checked"],
styles: {
"background-image": `url("${svgToDataUri(
`<svg viewBox="0 0 16 16" fill="white" xmlns="http://www.w3.org/2000/svg"><circle cx="8" cy="8" r="3"/></svg>`
)}")`,
"@media (forced-colors: active) ": {
appearance: "auto",
},
},
},
{
base: [
`[type='checkbox']:checked:hover`,
`[type='checkbox']:checked:focus`,
`[type='radio']:checked:hover`,
`[type='radio']:checked:focus`,
],
class: [
".form-checkbox:checked:hover",
".form-checkbox:checked:focus",
".form-radio:checked:hover",
".form-radio:checked:focus",
],
styles: {
"border-color": "transparent",
"background-color": "currentColor",
},
},
{
base: [`[type='checkbox']:indeterminate`],
class: [".form-checkbox:indeterminate"],
styles: {
"background-image": `url("${svgToDataUri(
`<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 16 16"><path stroke="white" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 8h8"/></svg>`
)}")`,
"border-color": `transparent`,
"background-color": `currentColor`,
"background-size": `100% 100%`,
"background-position": `center`,
"background-repeat": `no-repeat`,
"@media (forced-colors: active) ": {
appearance: "auto",
},
},
},
{
base: [
`[type='checkbox']:indeterminate:hover`,
`[type='checkbox']:indeterminate:focus`,
],
class: [
".form-checkbox:indeterminate:hover",
".form-checkbox:indeterminate:focus",
],
styles: {
"border-color": "transparent",
"background-color": "currentColor",
},
},
{
base: [`[type='file']`],
class: null,
styles: {
background: "unset",
"border-color": "inherit",
"border-width": "0",
"border-radius": "0",
padding: "0",
"font-size": "unset",
"line-height": "inherit",
},
},
{
base: [`[type='file']:focus`],
class: null,
styles: {
outline: [`1px solid ButtonText`, `1px auto -webkit-focus-ring-color`],
},
},
];
const getStrategyRules = (strategy: Strategy): CSSRuleObject[] =>
rules
.map(rule => {
if (rule[strategy] === null) return null;
return { [rule[strategy].join(", ")]: rule.styles } as CSSRuleObject;
})
.filter(Boolean);
if (strategy.includes("base")) {
addBase(getStrategyRules("base"));
}
if (strategy.includes("class")) {
addComponents(getStrategyRules("class"));
}
}
);

197
src/vendor/react-aria-components.ts vendored Normal file
View File

@ -0,0 +1,197 @@
// https://github.com/adobe/react-spectrum/blob/14f324fe890fcedc6e34889d9b04d5d6bfeb8380/packages/tailwindcss-react-aria-components/src/index.js
import plugin from "tailwindcss/plugin";
import type { PluginAPI } from "tailwindcss/types/config";
// Order of these is important because it determines which states win in a conflict.
// We mostly follow Tailwind's defaults, adding our additional states following the categories they define.
// https://github.com/tailwindlabs/tailwindcss/blob/304c2bad6cb5fcb62754a4580b1c8f4c16b946ea/src/corePlugins.js#L83
const attributes = {
boolean: [
// Conditions
"allows-removing",
"allows-sorting",
"allows-dragging",
"has-submenu",
// States
"open",
"entering",
"exiting",
"indeterminate",
["placeholder-shown", "placeholder"],
"current",
"required",
"unavailable",
"invalid",
["read-only", "readonly"],
"outside-month",
"outside-visible-range",
// Content
"empty",
// Interactive states
"focus-within",
["hover", "hovered"],
["focus", "focused"],
"focus-visible",
"pressed",
"selected",
"selection-start",
"selection-end",
"dragging",
"drop-target",
"resizing",
"disabled",
],
enum: {
placement: ["left", "right", "top", "bottom"],
type: ["literal", "year", "month", "day"],
layout: ["grid", "stack"],
orientation: ["horizontal", "vertical"],
"selection-mode": ["single", "multiple"],
"resizable-direction": ["right", "left", "both"],
"sort-direction": ["ascending", "descending"],
},
};
const shortNames: Record<string, string> = {
"selection-mode": "selection",
"resizable-direction": "resizable",
"sort-direction": "sort",
};
// Variants we use that are already defined by Tailwind:
// https://github.com/tailwindlabs/tailwindcss/blob/a2fa6932767ab328515f743d6188c2164ad2a5de/src/corePlugins.js#L84
const nativeVariants = [
"indeterminate",
"required",
"invalid",
"empty",
"focus-visible",
"focus-within",
"disabled",
];
const nativeVariantSelectors = new Map<string, string>([
...nativeVariants.map(variant => [variant, `:${variant}`] as const),
["hovered", ":hover"],
["focused", ":focus"],
["readonly", ":read-only"],
["open", "[open]"],
]);
// Variants where both native and RAC attributes should apply. We don't override these.
const nativeMergeSelectors = new Map([["placeholder", ":placeholder-shown"]]);
type SelectorFn = (wrap: (s: string) => string) => string;
type SelectorValue = string | SelectorFn;
type Selector = string | [string, SelectorValue];
// If no prefix is specified, we want to avoid overriding native variants on non-RAC components, so we only target elements with the data-rac attribute for those variants.
function getSelector(
prefix: string,
attributeName: string,
attributeValue: string | null,
hoverOnlyWhenSupported: boolean
): Selector {
const baseSelector = attributeValue
? `[data-${attributeName}="${attributeValue}"]`
: `[data-${attributeName}]`;
const nativeSelector = nativeVariantSelectors.get(attributeName);
if (prefix === "" && nativeSelector) {
const wrappedNativeSelector = `&:not([data-rac])${nativeSelector}`;
let nativeSelectorGenerator: SelectorValue = wrappedNativeSelector;
if (nativeSelector === ":hover" && hoverOnlyWhenSupported) {
nativeSelectorGenerator = wrap =>
`@media (hover: hover) and (pointer: fine) { ${wrap(wrappedNativeSelector)} }`;
}
return [`&[data-rac]${baseSelector}`, nativeSelectorGenerator];
} else if (prefix === "" && nativeMergeSelectors.has(attributeName)) {
return [`&${baseSelector}`, `&${nativeMergeSelectors.get(attributeName)}`];
} else {
return `&${baseSelector}`;
}
}
const mapSelector = (selector: Selector, fn: (v: SelectorValue) => string) =>
Array.isArray(selector) ? selector.map(fn) : fn(selector);
const wrapSelector = (selector: SelectorValue, wrap: (text: string) => string) =>
typeof selector === "function" ? selector(wrap) : wrap(selector);
const addVariants = (
variantName: string,
selectors: Selector,
addVariant: PluginAPI["addVariant"],
matchVariant: PluginAPI["matchVariant"]
) => {
addVariant(
variantName,
mapSelector(selectors, selector => wrapSelector(selector, s => s))
);
matchVariant(
"group",
(_, { modifier }) =>
modifier
? mapSelector(selectors, selector =>
wrapSelector(selector, s => `:merge(.group\\/${modifier})${s.slice(1)} &`)
)
: mapSelector(selectors, selector =>
wrapSelector(selector, s => `:merge(.group)${s.slice(1)} &`)
),
{ values: { [variantName]: variantName } }
);
matchVariant(
"peer",
(_, { modifier }) =>
modifier
? mapSelector(selectors, selector =>
wrapSelector(selector, s => `:merge(.peer\\/${modifier})${s.slice(1)} ~ &`)
)
: mapSelector(selectors, selector =>
wrapSelector(selector, s => `:merge(.peer)${s.slice(1)} ~ &`)
),
{ values: { [variantName]: variantName } }
);
};
export default plugin.withOptions<{ prefix: string }>(
options =>
({ addVariant, matchVariant, config }) => {
const prefix = options?.prefix ? `${options.prefix}-` : "";
const future = config().future;
const hoverOnlyWhenSupported: boolean =
future === "all" ||
(typeof future === "object" &&
!Array.isArray(future) &&
!!future?.hoverOnlyWhenSupported);
// Enum attributes go first because currently they are all non-interactive states.
for (const [attributeName, value] of Object.entries(attributes.enum)) {
for (const attributeValue of value) {
const name = shortNames[attributeName] || attributeName;
const variantName = `${prefix}${name}-${attributeValue}`;
const selectors = getSelector(
prefix,
attributeName,
attributeValue,
hoverOnlyWhenSupported
);
addVariants(variantName, selectors, addVariant, matchVariant);
}
}
for (const attribute of attributes.boolean) {
let variantName = Array.isArray(attribute) ? attribute[0] : attribute;
variantName = `${prefix}${variantName}`;
const attributeName = Array.isArray(attribute) ? attribute[1] : attribute;
const selectors = getSelector(
prefix,
attributeName,
null,
hoverOnlyWhenSupported
);
addVariants(variantName, selectors, addVariant, matchVariant);
}
}
);

1516
src/vendor/typography.ts vendored Normal file

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
import { dirname, join } from "node:path";
import type * as vite from "vite";
import { type Compile, type StyleMap, macroName, pkgName } from "./shared";
import { type Compile, type StyleMap, macroNames, pkgName } from "./shared";
import type { BuildStyleFile } from "./index";
const ROLLUP_PREFIX = "\0tailwind:";
@ -16,8 +16,8 @@ export const vitePlugin = ({
}): vite.Plugin => ({
name: "tailwind",
configResolved(config) {
(config.optimizeDeps.exclude ?? []).push(macroName, `${pkgName}/base`);
config(config) {
((config.optimizeDeps ?? {}).exclude ?? []).push(...macroNames, `${pkgName}/base`);
},
resolveId(id, importer) {
@ -59,4 +59,4 @@ export const vitePlugin = ({
* `babel-plugin-macros` compatible `isMacrosName` function that works with this plugin.
*/
export const isMacrosName = (v: string) =>
v !== macroName && /[./]macro(\.c?js)?$/.test(v);
!macroNames.includes(v) && /[./]macro(\.c?js)?$/.test(v);