views-morph
Version:
Views language morpher
248 lines (228 loc) • 7.76 kB
JavaScript
import { isStyle, STYLE } from './prop-is-style.js'
import DidYouMeanMatcher from './did-you-mean.js'
import isNumber from './prop-is-number.js'
const dymBlockMatcher = new DidYouMeanMatcher(
'CaptureEmail|CaptureFile|CaptureNumber|CapturePhone|CaptureSecure|CaptureText|CaptureTextArea|G|Horizontal|Image|List|Svg|SvgCircle|SvgEllipse|SvgDefs|SvgGroup|SvgLinearGradient|SvgRadialGradient|SvgLine|SvgPath|SvgPolygon|SvgPolyline|SvgRect|SvgSymbol|SvgText|SvgUse|SvgStop|Text|Vertical'.split(
'|'
)
)
const dymPropMatcher = new DidYouMeanMatcher([
...STYLE,
'defaultValue',
'fill',
'stroke',
'from',
'viewBox',
'stroke',
'strokeWidth',
'strokeLinecap',
'strokeMiterlimit',
'at',
'd',
'x',
'x1',
'y1',
'y',
'x2',
'y2',
'strokeLinejoin',
'onBlur',
'onChange',
'onClick',
'onDrag',
'onDragEnd',
'onDragOver',
'onDragStart',
'onFocus',
'onMouseDown',
'onMouseMove',
'onMouseOver',
'onMouseUp',
'onWheel',
'onWhen',
'ref',
'tabIndex',
'text',
'value',
'when',
])
export const didYouMeanBlock = block => dymBlockMatcher.get(block)
export const didYouMeanProp = prop => dymPropMatcher.get(prop)
const BASIC = /^(CaptureEmail|CaptureFile|CaptureNumber|CapturePhone|CaptureSecure|CaptureText|CaptureTextArea|Horizontal|Image|List|Svg|SvgCircle|SvgEllipse|SvgDefs|SvgGroup|SvgLinearGradient|SvgRadialGradient|SvgLine|SvgPath|SvgPolygon|SvgPolyline|SvgRect|SvgSymbol|SvgText|SvgUse|SvgStop|Text|Vertical)$/i
const BLOCK = /^([A-Z][a-zA-Z0-9]*)(\s+([A-Z][a-zA-Z0-9]*))?$/
const BOOL = /^(false|true)$/i
const CAPTURE = /^(CaptureEmail|CaptureFile|CaptureNumber|CapturePhone|CaptureSecure|CaptureText|CaptureTextArea)$/i
const COMMENT = /^#(.+)$/
const FLOAT = /^-?[0-9]+\.[0-9]+$/
const FONTABLE = /^(CaptureEmail|CaptureNumber|CapturePhone|CaptureSecure|CaptureText|CaptureTextArea|Text)$/
const INT = /^-?[0-9]+$/
const NOT_GROUP = /^(Image|Text|Proxy|SvgCircle|SvgEllipse|SvgLine|SvgPath|SvgPolygon|SvgPolyline|SvgRect|SvgText|SvgStop)$/i
const PROP = /^([a-z][a-zA-Z0-9]*)(\s+(.+))?$/
const UNSUPPORTED_SHORTHAND = {
border: ['borderWidth', 'borderStyle', 'borderColor'],
borderBottom: ['borderBottomWidth', 'borderBottomStyle', 'borderBottomColor'],
borderTop: ['borderTopWidth', 'borderTopStyle', 'borderTopColor'],
borderRight: ['borderRightWidth', 'borderRightStyle', 'borderRightColor'],
borderLeft: ['borderLeftWidth', 'borderLeftStyle', 'borderLeftColor'],
borderRadius: [
'borderTopLeftRadius',
'borderTopRightRadius',
'borderBottomLeftRadius',
'borderBottomRightRadius',
],
boxShadow: [
'shadowOffsetX',
'shadowOffsetY',
'shadowRadius',
'shadowSpread',
'shadowColor',
],
margin: ['marginTop', 'marginRight', 'marginBottom', 'marginLeft'],
padding: ['paddingTop', 'paddingRight', 'paddingBottom', 'paddingLeft'],
textShadow: ['shadowOffsetX', 'shadowOffsetY', 'shadowRadius', 'shadowColor'],
outline: ['outlineWidth', 'outlineStyle', 'outlineColor'],
overflow: ['overflowX', 'overflowY'],
}
const TRUE = /^true$/i
const USER_COMMENT = /^##(.*)$/
// TODO slot can't start with a number
const SLOT = /^<((!)?([a-zA-Z0-9]+))?(\s+(.+))?$/
export const is = (thing, line) => thing.test(line)
export const isBasic = line => is(BASIC, line)
export const isBlock = line => is(BLOCK, line)
export const isBool = line => is(BOOL, line)
export const isCapture = line => is(CAPTURE, line)
export const isComment = line => is(COMMENT, line)
export const isEmptyText = line => line === ''
export const isEnd = line => line === ''
export const isFloat = line => is(FLOAT, line)
export const isFontable = line => is(FONTABLE, line)
export const isGroup = line => !is(NOT_GROUP, line) && !isCapture(line)
export const isList = line => line === 'List'
export const isInt = line => is(INT, line)
export const isProp = line => is(PROP, line)
export const isSlot = line => is(SLOT, line)
export const isUnsupportedShorthand = name => name in UNSUPPORTED_SHORTHAND
export { isStyle }
export const isTrue = line => is(TRUE, line)
export const isUserComment = line => is(USER_COMMENT, line)
const get = (regex, line) => line.match(regex)
export const getBlock = line => {
// eslint-disable-next-line
const [_, is, _1, block] = get(BLOCK, line)
return {
block: block || is,
is: block ? is : null,
}
}
export const getComment = line => {
try {
return get(COMMENT, line).slice(1)
} catch (err) {
return ''
}
}
export const getProp = line => {
// eslint-disable-next-line
let [_, name, _1, value = ''] = get(PROP, line)
const prop = { name, isSlot: false, value }
if (is(SLOT, value)) {
const [
// eslint-disable-next-line
_2,
slotIsNot = false,
slotName = '',
// eslint-disable-next-line
_3,
// eslint-disable-next-line
defaultValue = '',
] = getSlot(value)
prop.isSlot = true
prop.slotIsNot = slotIsNot === '!'
prop.slotName = slotName
prop.value = defaultValue || value
}
return prop
}
export const getSlot = line => get(SLOT, line).slice(1)
export const getUnsupportedShorthandExpanded = (name, value) => {
const props = UNSUPPORTED_SHORTHAND[name]
if (name === 'borderRadius') {
const theValue = value.replace('px', '')
return [
`${props[0]} ${theValue}`,
`${props[1]} ${theValue}`,
`${props[2]} ${theValue}`,
`${props[3]} ${theValue}`,
]
} else if (name.startsWith('border') || name === 'outline') {
const [width, style, ...color] = value.split(' ')
return [
`${props[0]} ${width.replace('px', '')}`,
`${props[1]} ${style}`,
`${props[2]} ${color.join(' ')}`,
]
} else if (name === 'boxShadow') {
const [offsetX, offsetY, blurRadius, spreadRadius, ...color] = value.split(
' '
)
return [
`${props[0]} ${offsetX.replace('px', '')}`,
`${props[1]} ${offsetY.replace('px', '')}`,
`${props[2]} ${blurRadius.replace('px', '')}`,
`${props[3]} ${spreadRadius.replace('px', '')}`,
`${props[4]} ${color.join(' ')}`,
]
} else if (name === 'textShadow') {
const [offsetX, offsetY, blurRadius, ...color] = value.split(' ')
return [
`${props[0]} ${offsetX.replace('px', '')}`,
`${props[1]} ${offsetY.replace('px', '')}`,
`${props[2]} ${blurRadius.replace('px', '')}`,
`${props[3]} ${color.join(' ')}`,
]
} else if (name === 'overflow') {
return [`${props[0]} ${value}`, `${props[1]} ${value}`]
} else if (name === 'padding' || name === 'margin') {
let [top, right, bottom, left] = value.split(' ')
top = top.replace('px', '')
right = right ? right.replace('px', '') : top
bottom = bottom ? bottom.replace('px', '') : top
left = left ? left.replace('px', '') : right || top
return [
`${props[0]} ${top}`,
`${props[1]} ${right}`,
`${props[2]} ${bottom}`,
`${props[3]} ${left}`,
]
}
}
export const getValue = value => {
if (isFloat(value)) {
return parseFloat(value, 10)
} else if (isInt(value)) {
return parseInt(value, 10)
} else if (isEmptyText(value)) {
return ''
} else if (isBool(value)) {
return isTrue(value)
} else {
return value
}
}
const SYSTEM_SCOPES = [
'active',
// TODO disabled should be a prop instead I think
'disabled',
'focus',
'hover',
'placeholder',
'print',
// TODO do we want to do media queries here?
]
export const isSystemScope = name => SYSTEM_SCOPES.includes(name)
const isActionable = name => name !== 'onWhen' && /^on[A-Z]/.test(name)
export const getPropType = (block, name, defaultValue) =>
block.isList && name === 'from'
? 'array'
: isActionable(name) ? 'function' : isNumber[name] ? 'number' : 'string'