|
| 1 | +import type { CSSObject, CSSValueInput, Rule, RuleContext } from '@unocss/core' |
| 2 | +import type { Theme } from '@unocss/preset-wind4' |
| 3 | +import { |
| 4 | + colorResolver, |
| 5 | + cornerMap, |
| 6 | + defineProperty, |
| 7 | + h, |
| 8 | + hasParseableColor, |
| 9 | + hyphenate, |
| 10 | + numberResolver, |
| 11 | + positionMap, |
| 12 | + themeTracking, |
| 13 | +} from '../utils' |
| 14 | + |
| 15 | +const linearMap: Record<string, string[]> = { |
| 16 | + t: ['top'], |
| 17 | + b: ['bottom'], |
| 18 | + l: ['left'], |
| 19 | + r: ['right'], |
| 20 | + x: ['left', 'right'], |
| 21 | + y: ['top', 'bottom'], |
| 22 | +} |
| 23 | + |
| 24 | +const maskInitialValue = 'linear-gradient(#fff, #fff)' |
| 25 | + |
| 26 | +const baseMaskImage = { |
| 27 | + 'mask-image': 'var(--un-mask-linear), var(--un-mask-radial), var(--un-mask-conic)', |
| 28 | + 'mask-composite': 'intersect', |
| 29 | +} |
| 30 | + |
| 31 | +function handlePosition([,v = '']: string[]) { |
| 32 | + if (v in cornerMap) { |
| 33 | + const positions = v.split('').flatMap(c => linearMap[c]).join(' ') |
| 34 | + return { 'mask-position': positions } |
| 35 | + } |
| 36 | + const _v = h.bracket.cssvar.global.position(v) |
| 37 | + if (_v !== null) |
| 38 | + return { 'mask-position': _v } |
| 39 | +} |
| 40 | + |
| 41 | +function handleImage([_, gradient = '', direction, val]: string[], ctx: RuleContext<Theme>) { |
| 42 | + const css: CSSObject = { ...baseMaskImage } |
| 43 | + const props: (CSSValueInput | string)[] = [] |
| 44 | + |
| 45 | + props.push(...['linear', 'radial', 'conic'].map(g => defineProperty(`--un-mask-${g}`, { initialValue: maskInitialValue }))) |
| 46 | + |
| 47 | + if (gradient in linearMap) { |
| 48 | + css['--un-mask-linear'] = 'var(--un-mask-left), var(--un-mask-right), var(--un-mask-bottom), var(--un-mask-top)' |
| 49 | + |
| 50 | + for (const dir of linearMap[gradient]) { |
| 51 | + css[`--un-mask-${dir}`] = `linear-gradient(to ${dir}, var(--un-mask-${dir}-from-color) var(--un-mask-${dir}-from-position), var(--un-mask-${dir}-to-color) var(--un-mask-${dir}-to-position))` |
| 52 | + |
| 53 | + if (numberResolver(val) != null) { |
| 54 | + themeTracking('spacing') |
| 55 | + css[`--un-mask-${dir}-${direction}-position`] = `calc(var(--spacing) * ${h.bracket.cssvar.fraction.number(val)})` |
| 56 | + } |
| 57 | + else { |
| 58 | + css[`--un-mask-${dir}-${direction}-position`] = h.bracket.cssvar.fraction.rem(val) |
| 59 | + } |
| 60 | + |
| 61 | + if (hasParseableColor(val, ctx.theme)) { |
| 62 | + const result = colorResolver(`--un-mask-${dir}-${direction}-color`, hyphenate('colors'))([_, val], ctx) |
| 63 | + if (result) { |
| 64 | + const [c, ...p] = result |
| 65 | + Object.assign(css, c) |
| 66 | + props.push(...p) |
| 67 | + } |
| 68 | + } |
| 69 | + |
| 70 | + props.push(...['from', 'to'].flatMap(p => [ |
| 71 | + defineProperty(`--un-mask-${dir}-${p}-position`, { syntax: '<length-percentage>', initialValue: p === 'from' ? '0%' : '100%' }), |
| 72 | + defineProperty(`--un-mask-${dir}-${p}-color`, { syntax: '<color>', initialValue: p === 'from' ? 'black' : 'transparent' }), |
| 73 | + ])) |
| 74 | + } |
| 75 | + |
| 76 | + props.push(...['top', 'right', 'bottom', 'left'].map(d => defineProperty(`--un-mask-${d}`, { initialValue: maskInitialValue }))) |
| 77 | + } |
| 78 | + else { |
| 79 | + if (direction == null) { |
| 80 | + if (gradient === 'radial') { |
| 81 | + css['--un-mask-radial'] = 'radial-gradient(var(--un-mask-radial-stops, var(--un-mask-radial-size)))' |
| 82 | + css['--un-mask-radial-size'] = h.bracket.cssvar.rem(val) |
| 83 | + } |
| 84 | + else { |
| 85 | + css[`--un-mask-${gradient}`] = `${gradient}-gradient(var(--un-mask-${gradient}-stops, var(--un-mask-${gradient}-position)))` |
| 86 | + css[`--un-mask-${gradient}-position`] = numberResolver(val) ? `calc(1deg * ${h.bracket.cssvar.number(val)})` : h.bracket.cssvar.fraction(val) |
| 87 | + } |
| 88 | + } |
| 89 | + else { |
| 90 | + const gradientStopsPrefixMap: Record<string, string> = { |
| 91 | + linear: '', |
| 92 | + radial: 'var(--un-mask-radial-shape) var(--un-mask-radial-size) at ', |
| 93 | + conic: 'from ', |
| 94 | + } |
| 95 | + css[`--un-mask-${gradient}-stops`] = `${gradientStopsPrefixMap[gradient]}var(--un-mask-${gradient}-position), var(--un-mask-${gradient}-from-color) var(--un-mask-${gradient}-from-position), var(--un-mask-${gradient}-to-color) var(--un-mask-${gradient}-to-position)` |
| 96 | + css[`--un-mask-${gradient}`] = `${gradient}-gradient(var(--un-mask-${gradient}-stops))` |
| 97 | + |
| 98 | + if (hasParseableColor(val, ctx.theme)) { |
| 99 | + const result = colorResolver(`--un-mask-${gradient}-${direction}-color`, hyphenate('colors'))([_, val], ctx) |
| 100 | + if (result) { |
| 101 | + const [c, ...p] = result |
| 102 | + Object.assign(css, c) |
| 103 | + props.push(...p) |
| 104 | + } |
| 105 | + } |
| 106 | + else { |
| 107 | + if (numberResolver(val) != null) { |
| 108 | + themeTracking('spacing') |
| 109 | + css[`--un-mask-${gradient}-${direction}-position`] = `calc(var(--spacing) * ${h.bracket.cssvar.fraction.number(val)})` |
| 110 | + } |
| 111 | + else { |
| 112 | + css[`--un-mask-${gradient}-${direction}-position`] = h.bracket.cssvar.fraction.rem(val) |
| 113 | + } |
| 114 | + } |
| 115 | + } |
| 116 | + |
| 117 | + if (gradient === 'radial') { |
| 118 | + props.push(...[ |
| 119 | + defineProperty('--un-mask-radial-shape', { initialValue: 'ellipse' }), |
| 120 | + defineProperty('--un-mask-radial-size', { initialValue: 'farthest-corner' }), |
| 121 | + ]) |
| 122 | + } |
| 123 | + |
| 124 | + props.push(...['from', 'to'].flatMap(p => [ |
| 125 | + defineProperty(`--un-mask-${gradient}-position`, { initialValue: gradient === 'radial' ? 'center' : '0deg' }), |
| 126 | + defineProperty(`--un-mask-${gradient}-${p}-position`, { syntax: '<length-percentage>', initialValue: p === 'from' ? '0%' : '100%' }), |
| 127 | + defineProperty(`--un-mask-${gradient}-${p}-color`, { syntax: '<color>', initialValue: p === 'from' ? 'black' : 'transparent' }), |
| 128 | + ])) |
| 129 | + } |
| 130 | + |
| 131 | + return [css, ...props] |
| 132 | +} |
| 133 | + |
| 134 | +function handleSize([, v = '']: string[]) { |
| 135 | + const _v = h.bracket.cssvar.global.fraction.rem(v) |
| 136 | + if (_v !== null) |
| 137 | + return { 'mask-size': _v } |
| 138 | +} |
| 139 | + |
| 140 | +export const masks: Rule<Theme>[] = [ |
| 141 | + // mask-clip |
| 142 | + ['mask-clip-border', { 'mask-clip': 'border-box' }], |
| 143 | + ['mask-clip-padding', { 'mask-clip': 'padding-box' }], |
| 144 | + ['mask-clip-content', { 'mask-clip': 'content-box' }], |
| 145 | + ['mask-clip-fill', { 'mask-clip': 'fill-box' }], |
| 146 | + ['mask-clip-stroke', { 'mask-clip': 'stroke-box' }], |
| 147 | + ['mask-clip-view', { 'mask-clip': 'view-box' }], |
| 148 | + ['mask-no-clip', { 'mask-clip': 'no-clip' }], |
| 149 | + |
| 150 | + // mask-composite |
| 151 | + ['mask-add', { 'mask-composite': 'add' }], |
| 152 | + ['mask-subtract', { 'mask-composite': 'subtract' }], |
| 153 | + ['mask-intersect', { 'mask-composite': 'intersect' }], |
| 154 | + ['mask-exclude', { 'mask-composite': 'exclude' }], |
| 155 | + |
| 156 | + // mask-image |
| 157 | + [/^mask-(.+)$/, ([,v]) => ({ 'mask-image': h.bracket.cssvar(v) })], |
| 158 | + [/^mask-(linear|radial|conic|[xytblr])-(from|to)()(?:-(.+))?$/, handleImage, { |
| 159 | + autocomplete: [ |
| 160 | + 'mask-(linear|radial|conic)-(from|to)-$colors', |
| 161 | + 'mask-(linear|radial|conic)-(from|to)-<percentage>', |
| 162 | + 'mask-(linear|radial|conic)-(from|to)', |
| 163 | + 'mask-(linear|radial|conic)-<percent>', |
| 164 | + 'mask-(x|y|t|b|l|r)-(from|to)-$colors', |
| 165 | + 'mask-(x|y|t|b|l|r)-(from|to)-<percentage>', |
| 166 | + 'mask-(x|y|t|b|l|r)-(from|to)', |
| 167 | + 'mask-(x|y|t|b|l|r)-<percent>', |
| 168 | + ], |
| 169 | + }], |
| 170 | + [/^mask-(linear|radial|conic)-(from|to)?-?(.*)$/, handleImage], |
| 171 | + [/^mask-([trblxy])-(from|to)-(.*)$/, handleImage], |
| 172 | + ['mask-none', { 'mask-image': 'none' }], |
| 173 | + ['mask-radial-circle', { '--un-mask-radial-shape': 'circle' }], |
| 174 | + ['mask-radial-ellipse', { '--un-mask-radial-shape': 'ellipse' }], |
| 175 | + ['mask-radial-closest-side', { '--un-mask-radial-size': 'closest-side' }], |
| 176 | + ['mask-radial-closest-corner', { '--un-mask-radial-size': 'closest-corner' }], |
| 177 | + ['mask-radial-farthest-side', { '--un-mask-radial-size': 'farthest-side' }], |
| 178 | + ['mask-radial-farthest-corner', { '--un-mask-radial-size': 'farthest-corner' }], |
| 179 | + [/^mask-radial-at-([-\w]{3,})$/, ([, s]) => ({ '--un-mask-radial-position': positionMap[s] }), { |
| 180 | + autocomplete: [`mask-radial-at-(${Object.keys(positionMap).filter(p => p.length > 2).join('|')})`], |
| 181 | + }], |
| 182 | + |
| 183 | + // mask-mode |
| 184 | + ['mask-alpha', { 'mask-mode': 'alpha' }], |
| 185 | + ['mask-luminance', { 'mask-mode': 'luminance' }], |
| 186 | + ['mask-match', { 'mask-mode': 'match-source' }], |
| 187 | + |
| 188 | + // mask-origin |
| 189 | + ['mask-origin-border', { 'mask-origin': 'border-box' }], |
| 190 | + ['mask-origin-padding', { 'mask-origin': 'padding-box' }], |
| 191 | + ['mask-origin-content', { 'mask-origin': 'content-box' }], |
| 192 | + ['mask-origin-fill', { 'mask-origin': 'fill-box' }], |
| 193 | + ['mask-origin-stroke', { 'mask-origin': 'stroke-box' }], |
| 194 | + ['mask-origin-view', { 'mask-origin': 'view-box' }], |
| 195 | + |
| 196 | + // mask-position |
| 197 | + [/^mask-([rltb]{1,2})$/, handlePosition], |
| 198 | + [/^mask-([-\w]{3,})$/, ([, s]) => ({ 'mask-position': positionMap[s] })], |
| 199 | + [/^mask-(?:position-|pos-)(.+)$/, handlePosition], |
| 200 | + |
| 201 | + // mask-repeat |
| 202 | + ['mask-repeat', { 'mask-repeat': 'repeat' }], |
| 203 | + ['mask-no-repeat', { 'mask-repeat': 'no-repeat' }], |
| 204 | + ['mask-repeat-x', { 'mask-repeat': 'repeat-x' }], |
| 205 | + ['mask-repeat-y', { 'mask-repeat': 'repeat-y' }], |
| 206 | + ['mask-repeat-space', { 'mask-repeat': 'space' }], |
| 207 | + ['mask-repeat-round', { 'mask-repeat': 'round' }], |
| 208 | + |
| 209 | + // mask-size |
| 210 | + ['mask-auto', { 'mask-size': 'auto' }], |
| 211 | + ['mask-cover', { 'mask-size': 'cover' }], |
| 212 | + ['mask-contain', { 'mask-size': 'contain' }], |
| 213 | + [/^mask-size-(.+)$/, handleSize], |
| 214 | + |
| 215 | + // mask-type |
| 216 | + ['mask-type-luminance', { 'mask-type': 'luminance' }], |
| 217 | + ['mask-type-alpha', { 'mask-type': 'alpha' }], |
| 218 | +] |
0 commit comments