1 | import safe from './react/safe.js'
|
2 | import wrap from './react/wrap.js'
|
3 | import toCamelCase from 'to-camel-case'
|
4 | import toSlugCase from 'to-slug-case'
|
5 |
|
6 | const safeScope = value =>
|
7 | typeof value === 'string' && !isCode(value) ? JSON.stringify(value) : value
|
8 |
|
9 | export const checkParentStem = (node, styleKey) => {
|
10 | if (styleKey !== 'hover' || styleKey !== 'disabled' || !node.parent)
|
11 | return false
|
12 |
|
13 | const matchingParentStem = node.parent.scopes.some(
|
14 | scope => scope.value === styleKey
|
15 | )
|
16 |
|
17 | return matchingParentStem && (node.parent.is || node.parent.name)
|
18 | }
|
19 |
|
20 | const INTERPOLATION = /\${(.+)}/
|
21 | export const isInterpolation = str => INTERPOLATION.test(str)
|
22 | export const deinterpolate = str => {
|
23 | const match = str.match(INTERPOLATION)
|
24 | return match ? match[1] : str
|
25 | }
|
26 |
|
27 | export const getObjectAsString = obj =>
|
28 | wrap(
|
29 | Object.keys(obj)
|
30 | .map(k => {
|
31 | const v =
|
32 | typeof obj[k] === 'object' && hasKeys(obj[k])
|
33 | ? getObjectAsString(obj[k])
|
34 | : obj[k]
|
35 | return `${JSON.stringify(k)}: ${v}`
|
36 | })
|
37 | .join(',')
|
38 | )
|
39 |
|
40 | export const getPropertiesAsObject = list => {
|
41 | const obj = {}
|
42 |
|
43 | list.forEach(prop => {
|
44 | obj[prop.name] = safeScope(prop.value)
|
45 | })
|
46 |
|
47 | return getObjectAsString(obj)
|
48 | }
|
49 |
|
50 | export const getProp = (node, key) => {
|
51 | const finder =
|
52 | typeof key === 'string' ? p => p.name === key : p => key.test(p.name)
|
53 |
|
54 | return node.properties && node.properties.find(finder)
|
55 | }
|
56 |
|
57 | export const getScope = node => node.value.split('when ')[1]
|
58 |
|
59 | const maybeSafe = node =>
|
60 | node.tags.code
|
61 | ? node.value
|
62 | : typeof node.value === 'string' ? safe(node.value) : node.value
|
63 |
|
64 | const getScopedProps = (propNode, blockNode) => {
|
65 | const scopes = blockNode.scopes
|
66 | .filter(scope => !scope.isSystem)
|
67 | .map(scope => {
|
68 | const prop = scope.properties.find(prop => prop.name === propNode.name)
|
69 | return prop && { prop, when: scope.value }
|
70 | })
|
71 | .filter(Boolean)
|
72 | .reverse()
|
73 |
|
74 | if (isEmpty(scopes)) return false
|
75 |
|
76 | return scopes
|
77 | }
|
78 |
|
79 | export const getScopedCondition = (propNode, blockNode) => {
|
80 | let conditional = maybeSafe(propNode)
|
81 |
|
82 | if (!getScopedProps(propNode, blockNode)) return false
|
83 |
|
84 | getScopedProps(propNode, blockNode).forEach(scope => {
|
85 | conditional = `${scope.when} ? ${maybeSafe(scope.prop)} : ` + conditional
|
86 | })
|
87 |
|
88 | return conditional
|
89 | }
|
90 |
|
91 | export const getScopedImageCondition = (scopes, scopedNames, defaultName) => {
|
92 | let conditional = defaultName
|
93 |
|
94 | scopes.forEach((scope, index) => {
|
95 | conditional = `${scope.when} ? ${scopedNames[index]} : ` + conditional
|
96 | })
|
97 |
|
98 | return conditional
|
99 | }
|
100 |
|
101 | export const getScopedRequireCondition = (scopes, paths, defaultName) => {
|
102 | let conditional = `requireImage('${defaultName}')`
|
103 |
|
104 | scopes.forEach((scope, index) => {
|
105 | conditional =
|
106 | `${scope.when} ? requireImage('${paths[index]}') : ` + conditional
|
107 | })
|
108 |
|
109 | return conditional
|
110 | }
|
111 |
|
112 | const styleStems = ['hover', 'focus', 'placeholder', 'disabled', 'print']
|
113 | export const getStyleType = node =>
|
114 | styleStems.find(tag => isTag(node, tag)) || 'base'
|
115 | export const hasKeys = obj => Object.keys(obj).length > 0
|
116 | export const hasKeysInChildren = obj =>
|
117 | Object.keys(obj).some(k => hasKeys(obj[k]))
|
118 |
|
119 | export const hasProp = (node, key, match) => {
|
120 | const prop = getProp(node, key)
|
121 | if (!prop) return false
|
122 | return typeof match === 'function' ? match(prop.value) : true
|
123 | }
|
124 |
|
125 | export const hasDefaultProp = (node, parent) =>
|
126 | parent.properties.some(prop => prop.nameRaw === node.nameRaw)
|
127 |
|
128 | export const CODE_EXPLICIT = /^{.+}$/
|
129 | export const isCodeExplicit = str => CODE_EXPLICIT.test(str)
|
130 | export const isCode = node =>
|
131 | typeof node === 'string'
|
132 | ? /props|item|index/.test(node) || isCodeExplicit(node)
|
133 | : isTag(node, 'code')
|
134 | export const isStyle = node => isTag(node, 'style')
|
135 | export const isTag = (node, tag) => node.tags[tag]
|
136 |
|
137 | export const getActionableParent = node => {
|
138 | if (!node.parent) return false
|
139 | if (node.parent.action) return node.parent
|
140 | return getActionableParent(node.parent)
|
141 | }
|
142 |
|
143 | export const getAllowedStyleKeys = node => {
|
144 | if (node.isCapture) {
|
145 | return ['base', 'focus', 'hover', 'disabled', 'placeholder', 'print']
|
146 | } else if (node.action || getActionableParent(node)) {
|
147 | return ['base', 'focus', 'hover', 'disabled', 'print']
|
148 | }
|
149 | return ['base', 'focus', 'print']
|
150 | }
|
151 |
|
152 | export const isList = node =>
|
153 | node && node.type === 'Block' && node.name === 'List'
|
154 |
|
155 | export const isEmpty = list => list.length === 0
|
156 |
|
157 | export const isValidImgSrc = (node, parent) =>
|
158 | node.name === 'source' && parent.name === 'Image' && parent.isBasic
|
159 |
|
160 | export const pushImageToState = (state, scopedNames, paths) =>
|
161 | scopedNames.forEach(name => {
|
162 | const path = paths[scopedNames.findIndex(item => item === name)]
|
163 | if (!state.images.includes(path)) {
|
164 | state.images.push({
|
165 | name,
|
166 | file: path,
|
167 | })
|
168 | }
|
169 | })
|
170 |
|
171 | export const getScopes = (node, parent) => {
|
172 | const scopedProps = getScopedProps(node, parent)
|
173 | if (!scopedProps) return false
|
174 | const paths = scopedProps.map(scope => scope.prop.value)
|
175 | const scopedNames = paths.map(path => toCamelCase(path))
|
176 |
|
177 | return { scopedProps, paths, scopedNames }
|
178 | }
|
179 |
|
180 | export const isSvg = node => /^Svg/.test(node.name) && node.isBasic
|
181 |
|
182 | export const getScopeDescription = scope => {
|
183 | const dictionary = {}
|
184 | const re = /(?:^|\W)props.(\w+)(?!\w)/g
|
185 |
|
186 | let match = re.exec(scope)
|
187 | while (match) {
|
188 | dictionary[match[1]] = toSlugCase(match[1])
|
189 | match = re.exec(scope)
|
190 | }
|
191 |
|
192 | for (let key in dictionary) {
|
193 | scope = scope.replace(new RegExp(key, 'g'), dictionary[key])
|
194 | }
|
195 |
|
196 | return toCamelCase(
|
197 | scope
|
198 | .replace(/\|\|/g, '-or-')
|
199 | .replace(/!/g, 'not-')
|
200 | .replace(/&&/g, '-and-')
|
201 | .replace(/props\./g, '')
|
202 | .replace(/\s/g, '')
|
203 | )
|
204 | }
|
205 |
|
206 | export const makeOnClickTracker = (node, state) => {
|
207 | const block = node.testId
|
208 | ? `"${state.name}.${node.testId}"`
|
209 | : `props["${state.testIdKey}"] || "${state.name}"`
|
210 |
|
211 | const track = `context.track({ block: ${block}, action: "click" })`
|
212 |
|
213 | state.isTracking = true
|
214 |
|
215 | return `event => { ${track}; (${node.value})(event); }`
|
216 | }
|