UNPKG

unocss-preset-useful

Version:
832 lines (748 loc) 20.9 kB
import { definePreset, mergeConfigs, toArray as toArray$1 } from "@unocss/core"; import postcss from "postcss"; import postcssJs, { objectify } from "postcss-js"; //#region src/core/extractors.ts const extractors = [{ name: "unocss-preset-useful-extractor-includes-base64", order: 0, extract({ code }) { return [...new Set(code.split(/[\\:]?[\s'"`{}]|;(?!base64)+/g))]; } }]; //#endregion //#region src/core/postprocess/important.ts function createFilter(include, exclude) { const includePattern = toArray$1(include || []); const excludePattern = toArray$1(exclude || []); return (id) => { if (excludePattern.some((p) => id.match(p))) return false; return includePattern.some((p) => id.match(p)); }; } function importantProcess(importantOptions) { const keyFilter = createFilter(importantOptions.includes, importantOptions.excludes); return (util) => { for (const item of util.entries) if (keyFilter(item[0])) { if (item[1] != null && !String(item[1]).includes("!important")) item[1] += " !important"; } else if (item[1] != null && String(item[1]).includes("!important")) item[1] = String(item[1]).replace(/\s*!important/g, ""); }; } //#endregion //#region src/core/postprocess/uncolor.ts const rgbRE = /rgb\(([\d\s]+?)\s*\/\s*([^)]+)\)/; const rgbaRE = /rgba\(([\d\s,]+),\s*([^)]+)\)/; function postprocessWithUnColor(unColor) { return (util) => { util.entries.forEach((i) => { const value = i[1]; if (typeof value === "string") { let match = value.match(rgbaRE); if (match != null) { i[1] = value.replace(rgbaRE, `rgba(var(${unColor}) , $2)`); util.entries.unshift([unColor, match[1]]); } else { match = value.match(rgbRE); if (match != null) { i[1] = value.replace(rgbRE, `rgb(var(${unColor}) / $2)`); util.entries.unshift([unColor, match[1].trim()]); } } } }); }; } //#endregion //#region src/core/postprocess/index.ts function postprocess(options) { const { unColor, important } = options; return [unColor ? postprocessWithUnColor(unColor) : void 0, important ? importantProcess(important) : void 0].filter(Boolean); } //#endregion //#region src/meta.ts const PRESET_NAME = "useful"; const layerMeta = { layer: PRESET_NAME }; //#endregion //#region src/core/preflights/reset.ts const resetCSS = `/* Please read: https://github.com/unocss/unocss/blob/main/packages-presets/reset/tailwind-compat.md */ /* 1. Prevent padding and border from affecting element width. (https://github.com/mozdevs/cssremedy/issues/4) 2. Allow adding a border to an element by just adding a border-width. (https://github.com/tailwindcss/tailwindcss/pull/116) 2. [UnoCSS]: allow to override the default border color with css var \`--un-default-border-color\` */ *, ::before, ::after { box-sizing: border-box; /* 1 */ border-width: 0; /* 2 */ border-style: solid; /* 2 */ border-color: var(--un-default-border-color, #e5e7eb); /* 2 */ } /* 1. Use a consistent sensible line-height in all browsers. 2. Prevent adjustments of font size after orientation changes in iOS. 3. Use a more readable tab size. 4. Use the user's configured \`sans\` font-family by default. 5. Use the user's configured \`sans\` font-feature-settings by default. 6. Use the user's configured \`sans\` font-variation-settings by default. 7. Disable tap highlights on iOS. */ html, :host { line-height: 1.5; /* 1 */ -webkit-text-size-adjust: 100%; /* 2 */ -moz-tab-size: 4; /* 3 */ tab-size: 4; /* 3 */ font-family: ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; /* 4 */ font-feature-settings: normal; /* 5 */ font-variation-settings: normal; /* 6 */ -webkit-tap-highlight-color: transparent; /* 7 */ } /* 1. Remove the margin in all browsers. 2. Inherit line-height from \`html\` so users can set them as a class directly on the \`html\` element. */ body { margin: 0; /* 1 */ line-height: inherit; /* 2 */ } /* 1. Add the correct height in Firefox. 2. Correct the inheritance of border color in Firefox. (https://bugzilla.mozilla.org/show_bug.cgi?id=190655) 3. Ensure horizontal rules are visible by default. */ hr { height: 0; /* 1 */ color: inherit; /* 2 */ border-top-width: 1px; /* 3 */ } /* Add the correct text decoration in Chrome, Edge, and Safari. */ abbr:where([title]) { text-decoration: underline dotted; } /* Remove the default font size and weight for headings. */ h1, h2, h3, h4, h5, h6 { font-size: inherit; font-weight: inherit; } /* Reset links to optimize for opt-in styling instead of opt-out. */ a { color: inherit; text-decoration: inherit; } /* Add the correct font weight in Edge and Safari. */ b, strong { font-weight: bolder; } /* 1. Use the user's configured \`mono\` font-family by default. 2. Use the user's configured \`mono\` font-feature-settings by default. 3. Use the user's configured \`mono\` font-variation-settings by default. 4. Correct the odd \`em\` font sizing in all browsers. */ code, kbd, samp, pre { font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; /* 1 */ font-feature-settings: normal; /* 2 */ font-variation-settings: normal; /* 3 */ font-size: 1em; /* 4 */ } /* Add the correct font size in all browsers. */ small { font-size: 80%; } /* Prevent \`sub\` and \`sup\` elements from affecting the line height in all browsers. */ sub, sup { font-size: 75%; line-height: 0; position: relative; vertical-align: baseline; } sub { bottom: -0.25em; } sup { top: -0.5em; } /* 1. Remove text indentation from table contents in Chrome and Safari. (https://bugs.chromium.org/p/chromium/issues/detail?id=999088, https://bugs.webkit.org/show_bug.cgi?id=201297) 2. Correct table border color inheritance in all Chrome and Safari. (https://bugs.chromium.org/p/chromium/issues/detail?id=935729, https://bugs.webkit.org/show_bug.cgi?id=195016) 3. Remove gaps between table borders by default. */ table { text-indent: 0; /* 1 */ border-color: inherit; /* 2 */ border-collapse: collapse; /* 3 */ } /* 1. Change the font styles in all browsers. 2. Remove the margin in Firefox and Safari. 3. Remove default padding in all browsers. */ button, input, optgroup, select, textarea { font-family: inherit; /* 1 */ font-feature-settings: inherit; /* 1 */ font-variation-settings: inherit; /* 1 */ font-size: 100%; /* 1 */ font-weight: inherit; /* 1 */ line-height: inherit; /* 1 */ color: inherit; /* 1 */ margin: 0; /* 2 */ padding: 0; /* 3 */ } /* Remove the inheritance of text transform in Edge and Firefox. */ button, select { text-transform: none; } /* 1. Correct the inability to style clickable types in iOS and Safari. 2. Remove default button styles. */ button, [type='button'], [type='reset'], [type='submit'] { -webkit-appearance: button; /* 1 */ /* Will affect the button style of most component libraries, so disable it */ /* https://github.com/unocss/unocss/issues/2127 */ /* background-color: transparent; !* 2 *! */ background-image: none; /* 2 */ } /* Use the modern Firefox focus style for all focusable elements. */ :-moz-focusring { outline: auto; } /* Remove the additional \`:invalid\` styles in Firefox. (https://github.com/mozilla/gecko-dev/blob/2f9eacd9d3d995c937b4251a5557d95d494c9be1/layout/style/res/forms.css#L728-L737) */ :-moz-ui-invalid { box-shadow: none; } /* Add the correct vertical alignment in Chrome and Firefox. */ progress { vertical-align: baseline; } /* Correct the cursor style of increment and decrement buttons in Safari. */ ::-webkit-inner-spin-button, ::-webkit-outer-spin-button { height: auto; } /* 1. Correct the odd appearance in Chrome and Safari. 2. Correct the outline style in Safari. */ [type='search'] { -webkit-appearance: textfield; /* 1 */ outline-offset: -2px; /* 2 */ } /* Remove the inner padding in Chrome and Safari on macOS. */ ::-webkit-search-decoration { -webkit-appearance: none; } /* 1. Correct the inability to style clickable types in iOS and Safari. 2. Change font properties to \`inherit\` in Safari. */ ::-webkit-file-upload-button { -webkit-appearance: button; /* 1 */ font: inherit; /* 2 */ } /* Add the correct display in Chrome and Safari. */ summary { display: list-item; } /* Removes the default spacing for appropriate elements. */ blockquote, dl, dd, h1, h2, h3, h4, h5, h6, hr, figure, p, pre { margin: 0; } fieldset { margin: 0; padding: 0; } legend { padding: 0; } ol, ul, menu { list-style: none; margin: 0; padding: 0; } dialog { padding: 0; } /* Prevent resizing textareas horizontally by default. */ textarea { resize: vertical; } /* 1. Reset the default placeholder opacity in Firefox. (https://github.com/tailwindlabs/tailwindcss/issues/3300) 2. Set the default placeholder color to the user's configured gray 400 color. */ input::placeholder, textarea::placeholder { opacity: 1; /* 1 */ color: #9ca3af; /* 2 */ } /* Set the default cursor for buttons. */ button, [role="button"] { cursor: pointer; } /* Make sure disabled buttons don't get the pointer cursor. */ :disabled { cursor: default; } /* 1. Make replaced elements \`display: block\` by default. (https://github.com/mozdevs/cssremedy/issues/14) 2. Add \`vertical-align: middle\` to align replaced elements more sensibly by default. (https://github.com/jensimmons/cssremedy/issues/14#issuecomment-634934210) This can trigger a poorly considered lint error in some tools but is included by design. */ img, svg, video, canvas, audio, iframe, embed, object { display: block; /* 1 */ vertical-align: middle; /* 2 */ } /* Constrain images and videos to the parent width and preserve their intrinsic aspect ratio. (https://github.com/mozdevs/cssremedy/issues/14) */ img, video { max-width: 100%; height: auto; } /* Make elements with the HTML hidden attribute stay hidden by default. */ [hidden]:where(:not([hidden="until-found"])) { display: none; } `; function compressCSS(css) { return css.replace(/\s+/g, " ").replace(/\/\*[\s\S]*?\*\//g, ""); } const resetPreflight = { getCSS: () => compressCSS(resetCSS), layer: layerMeta.layer }; //#endregion //#region src/core/preflights/index.ts function preflights(options) { if (typeof options.preflights === "boolean") return options.preflights ? [resetPreflight].filter(Boolean) : []; return [options.preflights.reset ? resetPreflight : void 0].filter(Boolean); } //#endregion //#region src/core/rules.ts const rules = [[/^([^:]+)::(\S|[^:]+)$/, ([, k, v]) => { return { [`--${k}`]: v }; }]]; normalizeRuleMeta(layerMeta); function normalizeRuleMeta(ruleMeta) { for (const r of rules) r[2] = Object.assign(r[2] || {}, ruleMeta); } //#endregion //#region src/core/shortcuts.ts const _shortcuts = [ ["pr", "relative"], ["pa", "absolute"], ["pf", "fixed"], ["ps", "sticky"], [["pxc", "p-x-c"], "pa left-1/2 -translate-x-1/2"], [["pyc", "p-y-c"], "pa top-1/2 -translate-y-1/2"], [[ "pcc", "pc", "p-c", "p-c-c" ], "pxc pyc"], [["fcc", "f-c"], "flex justify-center items-center"], [["fccc", "f-c-c"], "f-c flex-col"], [[ "fc", "fxc", "f-x-c" ], "flex justify-center"], [[ "fi", "fyc", "f-y-c" ], "flex items-center"], [["fs", "fxs"], "flex justify-start"], ["fsc", "flex justify-start items-center"], ["fse", "flex justify-start items-end"], [["fe", "fxe"], "flex justify-end"], ["fec", "flex justify-end items-center"], ["fb", "flex justify-between"], ["fbc", "flex justify-between items-center"], ["fa", "flex justify-around"], ["fac", "flex justify-around items-center"], ["fw", "flex flex-wrap"], ["fwr", "flex flex-wrap-reverse"], ["trans", "transition-all-350 ease-DEFAULT"] ]; const shortcuts = normalizeShortcut(_shortcuts); normalizeShortcutMeta(layerMeta); function normalizeShortcutMeta(ruleMeta) { for (const r of shortcuts) r[2] = Object.assign(r[2] || {}, ruleMeta); } function normalizeShortcut(shortcut) { return shortcut.flatMap((s) => { if (Array.isArray(s[0])) return s[0].map((i) => [ i, s[1], s[2] ]); return [s]; }); } //#endregion //#region src/core/theme/index.ts function nomarlizeTheme(theme) { return { ...theme }; } //#endregion //#region src/core/variants/active.ts const v_active = { name: "@active", match(matcher) { if (!matcher.startsWith("@active")) return matcher; return { matcher: matcher.slice(8), selector: (s) => `${s}.active` }; } }; //#endregion //#region src/core/variants/index.ts function variants(_options) { return [v_active]; } //#endregion //#region src/utils.ts const animationRegExp = /^([a-z-]+)\s+([0-9.]+m?s?|[*+])?\s?([a-z-]+(?:\([^)]+\))?|[*+])?\s*([a-z0-9-]+|[*+])?$/i; /** * Normalize custom animate usage to UnoCSS animations theme. * * ⚠️ You must strictly follow the following format. ⚠️ * * [ name, duration, timing-function, iteration-count ] * * If you use * as placeholder, it will be ignored. * * [name, duration, *, iteration-count] * * If you use + as placeholder, it will be replaced with empty string. * * [name, duration, +, iteration-count] * * @example * * { animate: ['spin 1s linear infinite'] } * * Will be transformd: * * { * animate: ['spin 1s linear infinite'], * durations: { * spin: '1s', * }, * timingFns: { * spin: 'linear', * }, * counts: { * spin: 'infinite', * }, * } */ function resolveAnimation(extend_animation) { const animation = {}; const keys = [ "durations", "timingFns", "counts" ]; const shortcuts$1 = []; for (const k in extend_animation) { const v = extend_animation[k]; const match = v.match(animationRegExp); if (match != null) { const [, name, duration, timing, count] = match; const values = [ duration, timing, count ]; if (name !== k) shortcuts$1.push([`animate-${k}`, `animate-${name}`]); for (let i = 0; i < keys.length; i++) { const key = keys[i]; const value = values[i]; if (value != null) { if (value === "*") continue; if (animation[key]) animation[key][name] = values[i] === "+" ? "" : values[i]; else animation[key] = { [name]: values[i] === "+" ? "" : values[i] }; } } } } return { animation, shortcuts: shortcuts$1 }; } /** * Deep merge two objects. * @param original Original object * @param patch Patch object * @returns Merged object */ function deepMerge(original, patch) { const o = original; const p = patch; if (Array.isArray(o) && Array.isArray(p)) return [...o, ...p]; if (Array.isArray(o)) return [...o]; const output = { ...o }; if (isObject(o) && isObject(p)) Object.keys(p).forEach((key) => { if (isObject(p[key])) if (!(key in o)) Object.assign(output, { [key]: p[key] }); else output[key] = deepMerge(o[key], p[key]); else Object.assign(output, { [key]: p[key] }); }); return output; } function isObject(val) { return val !== null && typeof val === "object"; } function getKeyframes(css) { const root = postcss.parse(css); const obj = objectify(root); return Object.keys(obj).reduce((acc, key) => { if (key.startsWith("@keyframes")) acc[key] = obj[key]; return acc; }, {}); } function cssObj2StrSync(style) { return Object.keys(style).reduce((str, key) => { return `${str}${key}${stringifyObj(style[key])}`; }, "").replace(/\n/g, ""); } function stringifyObj(obj) { return `{${Object.keys(obj).reduce((str, key) => { return `${str}${camelToHyphen(key)}:${obj[key]};`; }, "")}}`; } async function cssObj2StrAsync(style) { return postcss().process(style, { parser: postcssJs }).then((result) => result.css); } function toArray(val) { return Array.isArray(val) ? val : [val]; } function camelToHyphen(str) { return str.replace(/([a-z])([A-Z])/g, "$1-$2").toLowerCase(); } //#endregion //#region src/resolve.ts const defaultOptions = { theme: {}, postprocess: { important: false }, shortcuts: { default: true }, preflights: { reset: false }, wind4: true, wind3: false, attributify: true, icons: true, webFonts: false, typography: false, tagify: false, remToPx: false, magicss: false, directives: true, variantGroup: true, compileClass: false }; const defaultPresetOptions = { webFonts: { provider: "fontsource" } }; const defaultPostprocessOptions = { important: { excludes: [], includes: [/.*/g] }, unColor: "--un-color" }; async function resolveOptions(options) { const optionsWithDefault = Object.assign({}, defaultOptions, options); if (optionsWithDefault.wind4 && optionsWithDefault.wind3) { console.warn("wind3 and wind4 are mutually exclusive, only one can be enabled"); optionsWithDefault.wind3 = false; } if (optionsWithDefault.postprocess === true) optionsWithDefault.postprocess = defaultPostprocessOptions; else if (typeof optionsWithDefault.postprocess === "object") { if (typeof optionsWithDefault.postprocess.important === "object") optionsWithDefault.postprocess.important = { ...defaultPostprocessOptions.important, ...optionsWithDefault.postprocess.important }; else if (optionsWithDefault.postprocess.important === true) optionsWithDefault.postprocess.important = defaultPostprocessOptions.important; if (optionsWithDefault.postprocess.unColor === true) optionsWithDefault.postprocess.unColor = defaultPostprocessOptions.unColor; } const presets = await resolvePresets(optionsWithDefault); const transformers = await resolveTransformers(optionsWithDefault); const { theme: t_theme, shortcuts: shortcuts$1 } = resolveExtend(optionsWithDefault.theme.extend ?? {}); const _theme = deepMerge(optionsWithDefault.theme, t_theme); return { ...optionsWithDefault, theme: nomarlizeTheme(_theme), meta: { presets, shortcuts: shortcuts$1, transformers } }; } function resolveExtend(extend) { const _shortcuts$1 = []; const { animation, keyframes } = extend; const { animation: resolvedAnimation, shortcuts: shortcuts$1 } = resolveAnimation(animation ?? {}); _shortcuts$1.push(...shortcuts$1); resolvedAnimation.keyframes = {}; for (const key in keyframes) resolvedAnimation.keyframes[key] = `{${cssObj2StrSync(keyframes[key])}}`; return { theme: { animation: resolvedAnimation }, shortcuts: _shortcuts$1 }; } async function resolvePresets(options) { const presets = []; const presetMap = { wind3: import("unocss").then((m) => m.presetWind3), wind4: import("unocss").then((m) => m.presetWind4), attributify: import("unocss").then((m) => m.presetAttributify), icons: import("unocss").then((m) => m.presetIcons), webFonts: import("unocss").then((m) => m.presetWebFonts), typography: import("unocss").then((m) => m.presetTypography), tagify: import("unocss").then((m) => m.presetTagify), remToPx: import("./dist-CncyueeJ.js").then((m) => m.presetRemToPx), magicss: import("unocss-preset-magicss").then((m) => m.presetMagicss) }; for (const [key, preset] of Object.entries(presetMap)) { const option = options[key]; if (option) { const p = await preset; const presetOptions = defaultPresetOptions[key]; if (typeof option === "object") presets.push(p({ ...presetOptions, ...option })); else presets.push(p(presetOptions ?? {})); } } return presets; } async function resolveTransformers(options) { const transformers = []; const transformerMap = { directives: import("unocss").then((m) => m.transformerDirectives), variantGroup: import("unocss").then((m) => m.transformerVariantGroup), compileClass: import("unocss").then((m) => m.transformerCompileClass) }; for (const [key, transformer] of Object.entries(transformerMap)) { const option = options[key]; if (option) { const t = await transformer; transformers.push(t(typeof option === "boolean" ? {} : option)); } } return transformers; } //#endregion //#region src/index.ts const presetUseful = definePreset(async (options) => { const resolvedOptions = await resolveOptions(options ?? {}); const { theme, meta, shortcuts: userShortcuts } = resolvedOptions; const shortcuts$1 = userShortcuts === true || typeof userShortcuts === "object" && userShortcuts.default !== false ? shortcuts : []; return { name: `unocss-preset-${PRESET_NAME}`, layers: { [PRESET_NAME]: 2 }, rules, theme, variants: variants(resolvedOptions), shortcuts: shortcuts$1, extractors, postprocess: postprocess(resolvedOptions), presets: meta.presets, transformers: meta.transformers, preflights: preflights(resolvedOptions), options: resolvedOptions }; }); var src_default = presetUseful; function defineConfig(config) { return config; } function defineUsefulConfig(options = {}, config = {}) { return mergeConfigs([defineConfig({ presets: [presetUseful(options)] }), config]); } //#endregion export { animationRegExp, camelToHyphen, cssObj2StrAsync, cssObj2StrSync, deepMerge, src_default as default, defineConfig, defineUsefulConfig, getKeyframes, isObject, presetUseful, resolveAnimation, stringifyObj, toArray };