UNPKG

unocss-preset-useful

Version:
422 lines (405 loc) 14.9 kB
import { toArray as toArray$1, definePreset, mergeConfigs } from '@unocss/core'; import postcss from 'postcss'; import postcssJs, { objectify } from 'postcss-js'; import { parseColor } from '@unocss/preset-mini/utils'; const extractors = [ { name: "unocss-preset-useful-extractor-includes-base64", order: 0, extract({ code }) { return [...new Set(code.split(/[\\:]?[\s'"`{}]|;(?!base64)+/g))]; } } ]; 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, ""); } } } }; } 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()]); } } } }); }; } function postprocess(options) { const { unColor, important } = options; return [ unColor ? postprocessWithUnColor(unColor) : void 0, important ? importantProcess(important) : void 0 ].filter(Boolean); } const PRESET_NAME = "useful"; const layerMeta = { layer: PRESET_NAME }; const resetCSS = `*,::before,::after{box-sizing:border-box;border-width:0;border-style:solid;border-color:var(--un-default-border-color,#e5e7eb);}html,:host{line-height:1.5;-webkit-text-size-adjust:100%;-moz-tab-size:4;tab-size:4;font-family:ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";font-feature-settings:normal;font-variation-settings:normal;-webkit-tap-highlight-color:transparent;}body{margin:0;line-height:inherit;}hr{height:0;color:inherit;border-top-width:1px;}abbr:where([title]){text-decoration:underline dotted;}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit;}a{color:inherit;text-decoration:inherit;}b,strong{font-weight:bolder;}code,kbd,samp,pre{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;font-feature-settings:normal;font-variation-settings:normal;font-size:1em;}small{font-size:80%;}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline;}sub{bottom:-0.25em;}sup{top:-0.5em;}table{text-indent:0;border-color:inherit;border-collapse:collapse;}button,input,optgroup,select,textarea{font-family:inherit;font-feature-settings:inherit;font-variation-settings:inherit;font-size:100%;font-weight:inherit;line-height:inherit;color:inherit;margin:0;padding:0;}button,select{text-transform:none;}button,[type='button'],[type='reset'],[type='submit']{-webkit-appearance:button;background-color:transparent;background-image:none;}:-moz-focusring{outline:auto;}:-moz-ui-invalid{box-shadow:none;}progress{vertical-align:baseline;}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto;}[type='search']{-webkit-appearance:textfield;outline-offset:-2px;}::-webkit-search-decoration{-webkit-appearance:none;}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit;}summary{display:list-item;}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;}textarea{resize:vertical;}input::placeholder,textarea::placeholder{opacity:1;color:#9ca3af;}button,[role="button"]{cursor:pointer;}:disabled{cursor:default;}img,svg,video,canvas,audio,iframe,embed,object{display:block;vertical-align:middle;}img,video{max-width:100%;height:auto;}[hidden]{display:none;}`; function compressCSS(css) { return css.replace(/\s+/g, " ").replace(/\/\*[\s\S]*?\*\//g, ""); } const resetPreflight = { getCSS: () => compressCSS(resetCSS), layer: layerMeta.layer }; function preflights(options) { if (typeof options.preflights === "boolean") { return options.preflights ? [ resetPreflight ].filter(Boolean) : []; } return [ options.enableResetStyles || options.preflights.reset ? resetPreflight : void 0 ].filter(Boolean); } const rules = [ [/^([^:]+)::(\S|[^:]+)$/, ([, n, v], { theme }) => { const color = parseColor(v, theme); if (color?.cssColor?.type === "rgb" && color.cssColor.components) { return { [`--${n}`]: `${color.cssColor.components.join(",")}` }; } return { [`--${n}`]: v }; }] ]; normalizeRuleMeta(layerMeta); function normalizeRuleMeta(ruleMeta) { for (const r of rules) r[2] = Object.assign(r[2] || {}, ruleMeta); } const _shortcuts = [ // position ["pr", "relative"], ["pa", "absolute"], ["pf", "fixed"], ["ps", "sticky"], // position layout [["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"], // flex layout [["f-c", "fcc"], "flex justify-center items-center"], [["f-c-c", "fccc"], "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"], // transition ["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]; }); } function nomarlizeTheme(theme) { return { ...theme }; } const v_active = { name: "@active", match(matcher) { if (!matcher.startsWith("@active")) return matcher; return { matcher: matcher.slice(8), selector: (s) => `${s}.active` }; } }; function variants(_options) { return [ v_active ]; } const animationRegExp = /^([a-z-]+)\s+([0-9.]+m?s?|[*+])?\s?([a-z-]+(?:\([^)]+\))?|[*+])?\s*([a-z0-9-]+|[*+])?$/i; function resolveAnimation(extend_animation) { const animation = {}; const keys = ["durations", "timingFns", "counts"]; const shortcuts = []; 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.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 }; } 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(); } const defaultOptions = { theme: {}, important: false, enableDefaultShortcuts: true, /** * @deprecated Use `magicss` option instead */ enableMagicAnimations: false, /** * @deprecated Use `preflights.reset` instead */ enableResetStyles: true, preflights: { reset: true }, // presets uno: { preflight: "on-demand" }, attributify: true, icons: true, webFonts: false, typography: false, tagify: false, remToPx: false, scrollbar: false, magicss: false, // transformers directives: true, variantGroup: true, compileClass: false }; const defaultPresetOptions = { webFonts: { provider: "fontsource" } }; const defaultImportantOptions = { excludes: [], includes: [/.*/g] }; async function resolveOptions(options) { const optionsWithDefault = Object.assign({}, defaultOptions, options); optionsWithDefault.unColor = typeof optionsWithDefault.unColor === "string" ? optionsWithDefault.unColor : optionsWithDefault.unColor ? "--un-color" : false; optionsWithDefault.important = typeof optionsWithDefault.important === "object" ? Object.assign({}, defaultImportantOptions, optionsWithDefault.important) : optionsWithDefault.important === true ? defaultImportantOptions : false; const presets = await resolvePresets(optionsWithDefault); const transformers = await resolveTransformers(optionsWithDefault); const { theme: t_theme, shortcuts } = resolveExtend(optionsWithDefault.theme.extend ?? {}); const _theme = deepMerge(optionsWithDefault.theme, t_theme); return { ...optionsWithDefault, theme: nomarlizeTheme(_theme), meta: { presets, shortcuts, transformers } }; } function resolveExtend(extend) { const _shortcuts = []; const { animation, keyframes } = extend; const { animation: resolvedAnimation, shortcuts } = resolveAnimation(animation ?? {}); _shortcuts.push(...shortcuts); resolvedAnimation.keyframes = {}; for (const key in keyframes) resolvedAnimation.keyframes[key] = `{${cssObj2StrSync(keyframes[key])}}`; return { theme: { animation: resolvedAnimation }, shortcuts: _shortcuts }; } async function resolvePresets(options) { const presets = []; const presetMap = { uno: import('@unocss/preset-uno').then((m) => m.presetUno), attributify: import('@unocss/preset-attributify').then((m) => m.presetAttributify), icons: import('@unocss/preset-icons').then((m) => m.presetIcons), webFonts: import('@unocss/preset-web-fonts').then((m) => m.presetWebFonts), typography: import('@unocss/preset-typography').then((m) => m.presetTypography), tagify: import('@unocss/preset-tagify').then((m) => m.presetTagify), remToPx: import('@unocss/preset-rem-to-px').then((m) => m.default), scrollbar: import('unocss-preset-scrollbar').then((m) => m.presetScrollbar), 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; } const presetUseful = definePreset(async (options) => { const resolvedOptions = await resolveOptions(options ?? {}); const { enableDefaultShortcuts, theme, meta } = resolvedOptions; return { name: `unocss-preset-${PRESET_NAME}`, layers: { [PRESET_NAME]: 2 }, rules, theme, variants: variants(), shortcuts: [...enableDefaultShortcuts ? shortcuts : [], ...meta.shortcuts], extractors, postprocess: postprocess(resolvedOptions), presets: meta.presets, transformers: meta.transformers, preflights: preflights(resolvedOptions), options: resolvedOptions }; }); function defineConfig(config) { return config; } function defineUsefulConfig(options = {}, config = {}) { return mergeConfigs([ defineConfig({ presets: [ presetUseful(options) ] }), config ]); } export { animationRegExp, camelToHyphen, cssObj2StrAsync, cssObj2StrSync, deepMerge, presetUseful as default, defineConfig, defineUsefulConfig, getKeyframes, isObject, presetUseful, resolveAnimation, stringifyObj, toArray };