UNPKG

178 kBJavaScriptView Raw
1#!/usr/bin/env node
2require('source-map-support').install();
3'use strict';
4
5function _interopDefault (ex) { return (ex && (typeof ex === 'object') && 'default' in ex) ? ex['default'] : ex; }
6
7require('core-js/modules/es.promise');
8var fs = require('fs');
9var chalk = _interopDefault(require('chalk'));
10var deleteEmpty = _interopDefault(require('delete-empty'));
11var glob = _interopDefault(require('fast-glob'));
12var path = _interopDefault(require('path'));
13var minimist = _interopDefault(require('minimist'));
14var updateNotifier = _interopDefault(require('update-notifier'));
15var list = _interopDefault(require('google-fonts-complete/api-response.json'));
16var sort = _interopDefault(require('bubblesort'));
17require('core-js/modules/es.array.flat');
18require('core-js/modules/es.array.unscopables.flat');
19require('core-js/modules/es.object.from-entries');
20var mm = _interopDefault(require('micromatch'));
21var fsExtra = _interopDefault(require('fs-extra'));
22var flatten = _interopDefault(require('flatten'));
23var toCamelCase = _interopDefault(require('to-camel-case'));
24var toSlugCase = _interopDefault(require('to-slug-case'));
25var uniq = _interopDefault(require('array-uniq'));
26var Levenshtein = _interopDefault(require('levenshtein'));
27var sortBy = _interopDefault(require('lodash.sortby'));
28var chokidar = _interopDefault(require('chokidar'));
29
30async function clean(src, verbose) {
31 let morphed = await glob(['**/*.view.js', `Fonts/*.js`, 'useFlow.js', 'useIsMedia.js', 'useIsBefore.js', 'useTools.js'], {
32 bashNative: ['linux'],
33 cwd: src,
34 ignore: ['*node_modules*']
35 });
36 await Promise.all(morphed.map(f => {
37 verbose && console.log(`x ${f}`);
38 return fs.promises.unlink(path.join(src, f));
39 }));
40 let deleted = await deleteEmpty(src);
41
42 if (verbose) {
43 deleted.forEach(d => console.log(`x ${d}`));
44 }
45}
46
47var name = "@viewstools/morph";
48var version = "23.1.2";
49var description = "Views language morpher";
50var main = "bin.js";
51var bin = {
52 "views-morph": "./bin.js"
53};
54var scripts = {
55 prepare: "cross-env NODE_ENV=production rollup --config rollup.config.js && chmod +x bin.js",
56 test: "jest",
57 "debug:issue": "npx nodemon -w bin.js -- bin.js issue/src --as react-dom --watch",
58 watch: "rollup --config rollup.config.js --watch"
59};
60var repository = {
61 type: "git",
62 url: "git+https://github.com/viewstools/morph.git"
63};
64var author = "Darío Javier Cravero <dario@uxtemple.com>";
65var license = "BSD-3-Clause";
66var bugs = {
67 url: "https://github.com/viewstools/morph/issues"
68};
69var homepage = "https://github.com/viewstools/morph#readme";
70var devDependencies = {
71 "@babel/core": "^7.3.4",
72 "@babel/preset-env": "^7.3.4",
73 "babel-eslint": "^10.0.1",
74 "babel-jest": "^24.3.1",
75 "cross-env": "^5.2.0",
76 eslint: "^6.1.0",
77 "eslint-plugin-import": "^2.3.0",
78 husky: "^3.0.2",
79 jest: "^24.3.1",
80 "lint-staged": "^9.2.1",
81 nodemon: "^2.0.2",
82 rollup: "^1.6.0",
83 "rollup-plugin-babel": "^4.3.2",
84 "rollup-plugin-json": "^4.0.0"
85};
86var dependencies = {
87 "array-uniq": "^2.0.0",
88 bubblesort: "^1.0.1",
89 chalk: "^2.3.0",
90 chokidar: "^3.0.2",
91 color: "^3.0.0",
92 "core-js": "3",
93 "delete-empty": "^3.0.0",
94 "fast-glob": "^3.0.4",
95 flatten: "^1.0.2",
96 "fs-extra": "^8.0.1",
97 "google-fonts-complete": "^1.1.1",
98 "has-yarn": "^2.1.0",
99 "i18n-locales": "^0.0.2",
100 "latest-version": "^5.1.0",
101 levenshtein: "^1.0.5",
102 "lodash.sortby": "^4.7.0",
103 micromatch: "^4.0.2",
104 minimist: "^1.2.0",
105 mz: "^2.6.0",
106 "react-query": "^0.3.24",
107 "read-pkg-up": "^6.0.0",
108 "source-map-support": "^0.5.12",
109 "to-camel-case": "^1.0.0",
110 "to-pascal-case": "^1.0.0",
111 "to-slug-case": "^1.0.0",
112 "update-notifier": "^3.0.1",
113 uuid: "^3.3.2"
114};
115var husky = {
116 hooks: {
117 "pre-commit": "lint-staged"
118 }
119};
120var jest = {
121 testEnvironment: "node"
122};
123var pkg = {
124 name: name,
125 version: version,
126 description: description,
127 main: main,
128 bin: bin,
129 "private": false,
130 scripts: scripts,
131 repository: repository,
132 author: author,
133 license: license,
134 bugs: bugs,
135 homepage: homepage,
136 devDependencies: devDependencies,
137 dependencies: dependencies,
138 husky: husky,
139 "lint-staged": {
140 "*.js": [
141 "prettier --trailing-comma es5 --single-quote --no-semi --write",
142 "git add"
143 ]
144},
145 jest: jest
146};
147
148function addToMapSet(map, key, value) {
149 if (!map.has(key)) {
150 map.set(key, new Set());
151 }
152
153 map.get(key).add(value);
154}
155
156let getWeights = variants => variants.filter(v => !v.includes('italic')).map(v => v === 'regular' ? 400 : v);
157
158let all = list.map(font => ({
159 category: font.category,
160 value: font.family,
161 weights: getWeights(font.variants)
162}));
163let byName = {};
164all.forEach(f => byName[f.value] = f);
165let fontFamily = all.map(f => f.value);
166let isGoogleFont = family => !!byName[family];
167let maybeAddFallbackFont = f => {
168 let font = byName[f];
169 return font && font.category ? `${f}, ${font.category}` : f;
170};
171
172var morphFontAsReactDom = ((font, sources) => {
173 let body;
174
175 if (isGoogleFont(font.family)) {
176 body = `injectGlobal("@import url('https://fonts.googleapis.com/css?family=${font.family.replace(/\s/g, '+')}:${font.weight}${font.style === 'italic' ? 'i' : ''}');body{}")`;
177 } else {
178 body = `${sources.map(src => `import ${src.type} from '${src.file}'`).join('\n')}
179
180injectGlobal(\`@font-face {
181 font-family: '${font.family}';
182 font-style: ${font.style};
183 font-weight: ${font.weight};
184 src: ${sources.map(src => `url(\${${src.type}}) format('${src.type}')`).join(', ')};
185}\`)`;
186 }
187
188 return `import { injectGlobal } from 'emotion'\n${body}`;
189});
190
191var morphFontAsReactNative = ((font, sources) => {
192 // TODO implement
193 return `export default "${font.id}"`; // let body = `export default {\n`
194 // fonts.forEach(font => {
195 // body += `${sources
196 // .map(src => `'${font.id}': require('./${src.file}'),\n`)
197 // .join(',')}`
198 // })
199 // return `${body}}`
200});
201
202function relativise(from, to) {
203 let r = path.relative(from, to).replace(/\\/g, '/');
204 return r.substr(r.startsWith('../..') ? 3 : 1);
205}
206
207async function ensureDir(dir) {
208 try {
209 await fs.promises.mkdir(dir, {
210 recursive: true
211 });
212 } catch (error) {}
213}
214
215let morphFont = {
216 'react-dom': morphFontAsReactDom,
217 'react-native': morphFontAsReactNative,
218 'react-pdf': morphFontAsReactNative
219};
220async function ensureFontsDirectory(src) {
221 await ensureDir(path.join(src, 'DesignSystem', 'Fonts'));
222}
223let getFontId = file => path.basename(file, path.extname(file));
224let fontsOrder = ['eot', 'woff2', 'woff', 'ttf', 'svg', 'otf'];
225
226let sortFonts = fonts => {
227 return new Set(sort([...fonts], (a, b) => fontsOrder.indexOf(b.type) - fontsOrder.indexOf(a.type)));
228};
229
230function processCustomFonts({
231 customFonts,
232 filesFontCustom
233}) {
234 for (let file of filesFontCustom) {
235 addToMapSet(customFonts, getFontId(file), file);
236 }
237
238 for (let [id, fonts] of customFonts) {
239 customFonts.set(id, sortFonts(fonts));
240 }
241}
242function morphAllFonts({
243 as,
244 customFonts,
245 filesView,
246 src,
247 viewsToFiles
248}) {
249 let fontsDirectory = path.join(src, 'DesignSystem', 'Fonts');
250 let fontsInUse = new Set();
251
252 let mapCustomFont = file => ({
253 type: FONT_TYPES[path.extname(file)],
254 file: file.replace(fontsDirectory, '.')
255 });
256
257 for (let file of filesView) {
258 let view = viewsToFiles.get(file);
259 if (view.custom) continue;
260 view.parsed.fonts.forEach(font => {
261 fontsInUse.add(font.id);
262 });
263 }
264
265 return [...fontsInUse].map(font => {
266 let [family, weight, style = 'normal'] = font.split('-');
267 let customFontSources = [];
268
269 if (customFonts.has(font)) {
270 customFontSources = [...customFonts.get(font)].map(mapCustomFont);
271 }
272
273 return {
274 file: path.join(src, 'DesignSystem', 'Fonts', `${font}.js`),
275 content: morphFont[as]({
276 id: font,
277 family,
278 style,
279 weight
280 }, customFontSources)
281 };
282 });
283} // let removeFont = file => {
284// let id = getFontId(file)
285// instance.customFonts = instance.customFonts.filter(font => font.id !== id)
286// }
287
288let FONT_TYPES = {
289 '.otf': 'opentype',
290 '.eot': 'eot',
291 '.svg': 'svg',
292 '.ttf': 'truetype',
293 '.woff': 'woff',
294 '.woff2': 'woff2'
295};
296let makeGetFontImport = src => (font, view) => `import "${relativise(view.file, path.join(src, 'DesignSystem', 'Fonts', `${font}.js`))}"`; // let isFont = f => Object.keys(FONT_TYPES).includes(path.extname(f))
297
298let PATTERNS = {
299 filesView: {
300 match: ['**/view.blocks']
301 },
302 filesViewLogic: {
303 match: ['**/logic.js']
304 },
305 filesViewCustom: {
306 match: ['**/react.js']
307 },
308 filesFontCustom: {
309 match: ['**/DesignSystem/Fonts/*.eot', '**/DesignSystem/Fonts/*.otf', '**/DesignSystem/Fonts/*.ttf', '**/DesignSystem/Fonts/*.svg', '**/DesignSystem/Fonts/*.woff', '**/DesignSystem/Fonts/*.woff2']
310 }
311}; // @ts-ignore
312
313let isViewFile = async file => mm.isMatch(file, PATTERNS.filesView.match);
314let isViewLogicFile = async file => // @ts-ignore
315mm.isMatch(file, PATTERNS.filesViewLogic.match);
316let isViewCustomFile = async file => // @ts-ignore
317mm.isMatch(file, PATTERNS.filesViewCustom.match);
318let isFontCustomFile = async file => // @ts-ignore
319mm.isMatch(file, PATTERNS.filesFontCustom.match);
320let MATCH = Object.values(PATTERNS).map(item => item.match).flat();
321async function getMatchesPerPattern(files) {
322 // @ts-ignore
323 return Object.fromEntries((await Promise.all(Object.entries(PATTERNS).map(async ([key, {
324 filter,
325 match,
326 ignore
327 }]) => {
328 let matches = mm(files, match, {
329 ignore
330 });
331
332 if (typeof filter === 'function') {
333 matches = await filter(matches);
334 }
335
336 return [key, new Set(matches)];
337 }))));
338}
339
340async function getFiles(src) {
341 let files = await glob(MATCH, {
342 absolute: true,
343 cwd: src,
344 ignore: ['**/node_modules/**', '**/view.js']
345 });
346 return await getMatchesPerPattern(files);
347}
348
349async function ensureFile({
350 file,
351 content
352}) {
353 await ensureDir(path.dirname(file));
354 return {
355 file,
356 content // content: prettier.format(content, {
357 // parser: 'babel',
358 // singleQuote: true,
359 // trailingComma: 'es5',
360 // }),
361
362 };
363}
364
365let DATA = `// This file is automatically generated by Views and will be overwritten
366// when the morpher runs. If you want to contribute to how it's generated, eg,
367// improving the algorithms inside, etc, see this:
368// https://github.com/viewstools/morph/blob/master/ensure-data.js
369import * as fromValidate from './validate.js'
370import * as fromFormat from './format.js'
371// import get from 'dlv';
372import get from 'lodash/get'
373import produce from 'immer'
374// import set from 'dset';
375import set from 'lodash/set'
376import React, {
377 useContext,
378 useEffect,
379 useMemo,
380 useReducer,
381 useRef,
382} from 'react'
383
384let SET = 'data/SET'
385let SET_FN = 'data/SET_FN'
386let RESET = 'data/RESET'
387let FORCE_REQUIRED = 'data/FORCE_REQUIRED'
388let reducer = produce((draft, action) => {
389 switch (action.type) {
390 case SET: {
391 set(draft, action.path, action.value)
392 break
393 }
394
395 case SET_FN: {
396 action.fn(draft, set, get)
397 break
398 }
399
400 case RESET: {
401 return action.value
402 }
403
404 case FORCE_REQUIRED: {
405 draft._forceRequired = true
406 break
407 }
408
409 default: {
410 throw new Error(
411 \`Unknown action type "\${action.type}" in useData reducer.\`
412 )
413 }
414 }
415})
416
417let DataContexts = {
418 default: React.createContext([]),
419}
420export function DataProvider(props) {
421 if (!props.context) {
422 throw new Error(
423 \`You're missing the context value in DataProvider. Eg: <DataProvider context="namespace" ...\`
424 )
425 }
426 if (!(props.context in DataContexts)) {
427 DataContexts[props.context] = React.createContext([])
428 }
429 let Context = DataContexts[props.context]
430
431 let [state, dispatch] = useReducer(reducer, props.value)
432 let isSubmitting = useRef(false)
433 let shouldCallOnChange = useRef(false)
434
435 useEffect(() => {
436 if (isSubmitting.current) return
437
438 shouldCallOnChange.current = false
439 dispatch({ type: RESET, value: props.value })
440 }, [props.value]) // eslint-disable-line
441 // ignore dispatch
442
443 let value = useMemo(() => {
444 async function onSubmit(args) {
445 if (isSubmitting.current) return
446 isSubmitting.current = true
447
448 try {
449 let res = await props.onSubmit(state, args)
450 isSubmitting.current = false
451
452 if (!res) return
453 } catch (error) {
454 isSubmitting.current = false
455 }
456
457 dispatch({ type: FORCE_REQUIRED })
458 }
459
460 return [state, dispatch, onSubmit]
461 }, [state, props.onSubmit]) // eslint-disable-line
462 // the linter says we need props when props.onSubmit is already there
463
464 // keep track of props.onChange outside of the following effect to
465 // prevent loops. Making the function useCallback didn't work
466 let onChange = useRef(props.onChange)
467 useEffect(() => {
468 onChange.current = props.onChange
469 }, [props.onChange])
470
471 useEffect(() => {
472 if (!shouldCallOnChange.current) {
473 shouldCallOnChange.current = true
474 return
475 }
476
477 onChange.current(state, fn => dispatch({ type: SET_FN, fn }))
478 }, [state])
479
480 return <Context.Provider value={value}>{props.children}</Context.Provider>
481}
482DataProvider.defaultProps = {
483 context: 'default',
484 onChange: () => {},
485 onSubmit: () => {},
486}
487
488export function useData({
489 path = null,
490 context = 'default',
491 formatIn = null,
492 formatOut = null,
493 validate = null,
494 validateRequired = false,
495} = {}) {
496 if (process.env.NODE_ENV === 'development') {
497 if (!(context in DataContexts)) {
498 throw new Error(
499 \`"\${context}" isn't a valid Data context. Check that you have <DataProvider context="\${context}" value={data}> in the component that defines the context for this story.\`
500 )
501 }
502
503 if (formatIn && !(formatIn in fromFormat)) {
504 throw new Error(
505 \`"\${formatIn}" function doesn't exist or is not exported in Data/format.js\`
506 )
507 }
508
509 if (formatOut && !(formatOut in fromFormat)) {
510 throw new Error(
511 \`"\${formatOut}" function doesn't exist or is not exported in Data/format.js\`
512 )
513 }
514
515 if (validate && !(validate in fromValidate)) {
516 throw new Error(
517 \`"\${validate}" function doesn't exist or is not exported in Data/validators.js\`
518 )
519 }
520 }
521
522 let contextValue = useContext(DataContexts[context])
523 let touched = useRef(false)
524
525 return useMemo(() => {
526 let [data, dispatch, onSubmit] = contextValue
527
528 if (!data) {
529 if (process.env.NODE_ENV === 'development') {
530 console.error(
531 'Check that you have <DataProvider value={data}> in the component that defines the data for this story.',
532 {
533 path,
534 formatIn,
535 formatOut,
536 validate,
537 validateRequired,
538 data,
539 }
540 )
541 }
542 return {}
543 }
544
545 let rawValue = path ? get(data, path) : data
546 let value = rawValue
547 if (path && formatIn) {
548 value = fromFormat[formatIn](rawValue, data)
549 }
550
551 let isValidInitial = true
552 if (validate) {
553 isValidInitial = fromValidate[validate](rawValue, value, data)
554 }
555 let isValid = touched.current || (validateRequired && data._forceRequired)? isValidInitial : true
556
557 function onChange(value, changePath = path) {
558 touched.current = !!value
559
560 if (typeof value === 'function') {
561 dispatch({ type: SET_FN, fn: value })
562 } else if (!changePath) {
563 dispatch({ type: RESET, value })
564 } else {
565 dispatch({
566 type: SET,
567 path: changePath,
568 value: formatOut ? fromFormat[formatOut](value, data) : value,
569 })
570 }
571 }
572
573 return {
574 onChange,
575 onSubmit,
576 value,
577 isValid,
578 isValidInitial,
579 isInvalid: !isValid,
580 isInvalidInitial: !isValidInitial,
581 }
582 }, [contextValue, path, formatIn, formatOut, validateRequired, validate])
583}
584`;
585function ensureData({
586 src
587}) {
588 return ensureFile({
589 file: path.join(src, 'Data', 'ViewsData.js'),
590 content: DATA
591 });
592}
593
594function getViewRelativeToView({
595 id,
596 view,
597 viewsById,
598 viewsToFiles
599}) {
600 let importCandidates = viewsById.get(id);
601
602 if (!importCandidates) {
603 // TODO add better error message
604 console.log('No import candidates for ', id, 'from', view.file);
605 importCandidates = new Set();
606 }
607
608 let importViewFile = [...importCandidates][0];
609
610 if (importCandidates.size > 1) {
611 let pathToView = path.dirname(view.file);
612 let maybeFileViewInside = path.join(pathToView, id, 'view.blocks').replace(/\\/g, '/');
613 let maybeFileViewCustomInside = path.join(pathToView, id, 'react.js').replace(/\\/g, '/');
614
615 if (importCandidates.has(maybeFileViewInside)) {
616 importViewFile = maybeFileViewInside;
617 } else if (importCandidates.has(maybeFileViewCustomInside)) {
618 importViewFile = maybeFileViewCustomInside;
619 }
620 }
621
622 return viewsToFiles.get(importViewFile);
623}
624
625function ensureFirstStoryIsOn(flow, key, stories) {
626 if (!stories.has(key)) return;
627 let story = flow.get(key);
628
629 if (story && story.stories.size > 0) {
630 let index = 0;
631
632 for (let id of story.stories) {
633 if (index === 0 || !story.isSeparate) {
634 stories.add(id);
635 }
636
637 index++;
638 ensureFirstStoryIsOn(flow, id, stories);
639 }
640 }
641}
642
643function makeFlow({
644 tools,
645 viewsById,
646 viewsToFiles
647}) {
648 let flowMap = new Map();
649 let flowMapStr = [];
650
651 for (let view of viewsToFiles.values()) {
652 if (!view || view.custom || !view.parsed.view.isStory) continue;
653 let states = [];
654
655 for (let id of view.parsed.view.views) {
656 let viewInView = getViewRelativeToView({
657 id,
658 view,
659 viewsById,
660 viewsToFiles
661 });
662
663 if (viewInView && !viewInView.custom && viewInView.parsed.view.isStory) {
664 states.push(viewInView.parsed.view.viewPath); // `${pathToViewId}/${id}`)
665 }
666 }
667
668 let isSeparate = view.parsed.view.flow === 'separate';
669 let parent = view.parsed.view.viewPathParent;
670 flowMapStr.push(`['${view.parsed.view.viewPath}', { isSeparate: ${isSeparate}, parent: '${parent === '/' ? '' : parent}',
671 stories: new Set(${states.length > 0 ? JSON.stringify(states) : ''}) }]`);
672 flowMap.set(view.parsed.view.viewPath, {
673 parent,
674 isSeparate,
675 stories: new Set(states)
676 });
677 }
678
679 let topStory = '/App';
680 let initialState = new Set([topStory]);
681 ensureFirstStoryIsOn(flowMap, topStory, initialState);
682 return `// This file is automatically generated by Views and will be overwritten
683// when the morpher runs. If you want to contribute to how it's generated, eg,
684// improving the algorithms inside, etc, see this:
685// https://github.com/viewstools/morph/blob/master/ensure-flow.js
686
687import React, { useCallback, useContext, useEffect, useReducer } from 'react'
688${tools ? "import ViewsTools from './ViewsTools.js'" : ''}
689
690export let flow = new Map([
691${flowMapStr.join(',\n')}
692])
693
694let TOP_STORY = "${topStory}"
695
696function ensureFirstStoryIsOn(key, stories) {
697 if (!stories.has(key)) return
698
699 let story = flow.get(key)
700 if (story.stories.size > 0) {
701 let index = 0
702 let canAdd = intersection(stories, story.stories).size === 0
703 for (let id of story.stories) {
704 if ((canAdd && index === 0) || !story.isSeparate) {
705 stories.add(id)
706 }
707 index++
708 ensureFirstStoryIsOn(id, stories)
709 }
710 }
711}
712
713function ensureParents(key, stories) {
714 let story = flow.get(key)
715 if (!story) {
716 console.error(\`View "\${key}" is missing its parent\`)
717 return
718 }
719 if (!story.parent) {
720 return
721 }
722
723 stories.add(story.parent)
724 ensureParents(story.parent, stories)
725}
726
727function getAllChildrenOf(key, children) {
728 if (!flow.has(key)) return
729
730 let story = flow.get(key)
731 for (let id of story.stories) {
732 children.add(id)
733 getAllChildrenOf(id, children)
734 }
735}
736
737
738let intersection = (a, b) => new Set([...a].filter(ai => b.has(ai)))
739let difference = (a, b) => new Set([...a].filter(ai => !b.has(ai)))
740
741function getNextFlow(key, state) {
742 if (state.has(key)) return state
743
744 let next = new Set([key])
745
746 ensureFirstStoryIsOn(key, next)
747 ensureParents(key, next)
748
749 let diffIn = difference(next, state)
750 let diffOut = new Set()
751
752 difference(state, next).forEach(id => {
753 let story = flow.get(id)
754 if (!story) {
755 console.debug({ type: 'views/flow/missing-story', id })
756 diffOut.add(id)
757 return
758 }
759
760 if (state.has(story.parent)) {
761 let parent = flow.get(story.parent)
762 if (intersection(parent.stories, diffIn).size > 0) {
763 diffOut.add(id)
764 let children = new Set()
765 getAllChildrenOf(id, children)
766 children.forEach(cid => diffOut.add(cid))
767 }
768 }
769 })
770
771 let nextState = new Set([...difference(state, diffOut), ...diffIn])
772 ensureFirstStoryIsOn(TOP_STORY, nextState)
773 return new Set([...nextState].sort())
774}
775
776let MAX_ACTIONS = 10000
777let SYNC = 'flow/SYNC'
778let SET = 'flow/SET'
779
780let Context = React.createContext([{ actions: [], flow: new Set() }, () => {}])
781export let useFlowState = () => useContext(Context)[0]
782export let useFlow = () => useFlowState().flow
783export let useSetFlowTo = () => {
784 let [, dispatch] = useContext(Context)
785 return useCallback(id => dispatch({ type: SET, id }), []) // eslint-disable-line
786 // ignore dispatch
787}
788
789function getNextActions(state, id) {
790 return [id, ...state.actions].slice(0, MAX_ACTIONS)
791}
792
793function reducer(state, action) {
794 switch (action.type) {
795 ${tools ? `case SYNC: {
796 return {
797 flow: new Set(action.flow),
798 actions: getNextActions(state, action.id)
799 }
800 }` : ''}
801
802 case SET: {
803 if (process.env.NODE_ENV === 'development') {
804 console.debug({ type: 'views/flow/set', id: action.id })
805
806 if (!flow.has(action.id)) {
807 console.debug({ type: 'views/flow/invalid-story', id: action.id, availableStories: flow })
808 throw new Error(
809 \`Story "$\{action.id}" doesn't exist. See the valid stories logged above this error.\`
810 )
811 }
812 }
813
814 if (state.actions[0] === action.id) {
815 if (process.env.NODE_ENV === 'development') {
816 console.debug({ type: 'views/flow/already-set-as-last-action-ignoring', id: action.id, actions: state.actions })
817 }
818 return state
819 }
820
821 return {
822 flow: getNextFlow(action.id, state.flow),
823 actions: getNextActions(state, action.id)
824 }
825 }
826
827 default: {
828 throw new Error(\`Unknown action "\${action.type}" in Flow\`)
829 }
830 }
831}
832
833export function ViewsFlow(props) {
834 let context = useReducer(reducer, { actions: [], flow: props.initialState })
835 let [state] = context
836
837 useEffect(() => {
838 if (typeof props.onChange === 'function') {
839 props.onChange(state)
840 }
841 }, [state]) // eslint-disable-line
842 // ignore props.onChange
843
844 return (
845 <Context.Provider value={context}>
846 ${tools ? '<ViewsTools flow={context}>{props.children}</ViewsTools>' : '{props.children}'}
847 </Context.Provider>
848 )
849}
850
851ViewsFlow.defaultProps = {
852 initialState: new Set(${JSON.stringify([...initialState], null, ' ')})
853}
854
855export function normalizePath(viewPath, relativePath) {
856 let url = new URL(\`file://\${viewPath}/../\${relativePath}\`)
857 return url.pathname
858}`;
859}
860
861function ensureFlow({
862 src,
863 tools,
864 viewsById,
865 viewsToFiles
866}) {
867 return ensureFile({
868 file: path.join(src, 'Logic', 'ViewsFlow.js'),
869 content: makeFlow({
870 tools,
871 viewsById,
872 viewsToFiles
873 })
874 });
875}
876
877let USE_IS_BEFORE = `// This file is automatically generated by Views and will be overwritten
878// when the morpher runs. If you want to contribute to how it's generated, eg,
879// improving the algorithms inside, etc, see this:
880// https://github.com/viewstools/morph/blob/master/ensure-is-before.js
881import { useEffect, useState } from 'react'
882
883export default function useIsBefore() {
884 let [isBefore, setIsBefore] = useState(true)
885
886 useEffect(function() {
887 let cancel = false
888 requestAnimationFrame(function() {
889 if (cancel) return
890 setIsBefore(false)
891 })
892 return () => cancel = true
893 }, [])
894
895 return isBefore
896}`;
897function ensureIsBefore({
898 src
899}) {
900 return ensureFile({
901 file: path.join(src, 'Logic', 'useIsBefore.js'),
902 content: USE_IS_BEFORE
903 });
904}
905
906let USE_IS_HOVERED = `// This file is automatically generated by Views and will be overwritten
907// when the morpher runs. If you want to contribute to how it's generated, eg,
908// improving the algorithms inside, etc, see this:
909// https://github.com/viewstools/morph/blob/master/ensure-is-hovered.js
910import { useEffect, useMemo, useState } from 'react'
911
912// TODO replace with something more performant like:
913// https://github.com/therealparmesh/use-hovering/blob/master/src/index.js
914// or useTooltip from Reach UI
915// https://github.com/reach/reach-ui/blob/master/packages/tooltip/src/index.tsx
916export default function useIsHovered({ isDisabled, isSelected, onMouseEnter, onMouseLeave }) {
917 let [isHovered, setIsHovered] = useState(false)
918
919 let isHoveredBind = useMemo(() => {
920 return {
921 onMouseEnter: event => {
922 setIsHovered(true)
923
924 if (typeof onMouseEnter === 'function') {
925 onMouseEnter(event)
926 }
927 },
928 onMouseLeave: event => {
929 setIsHovered(false)
930
931 if (typeof onMouseLeave === 'function') {
932 onMouseLeave(event)
933 }
934 },
935 }
936 }, [onMouseEnter, onMouseLeave])
937
938 useEffect(() => {
939 if (!isDisabled) return
940
941 setIsHovered(false)
942 }, [isDisabled])
943
944 return [isHovered, isHovered && isSelected, isHoveredBind]
945}`;
946function ensureIsHovered({
947 src
948}) {
949 return ensureFile({
950 file: path.join(src, 'Logic', 'useIsHovered.js'),
951 content: USE_IS_HOVERED
952 });
953}
954
955let makeUseIsMedia = media => `// This file is automatically generated by Views and will be overwritten
956// when the morpher runs. If you want to contribute to how it's generated, eg,
957// improving the algorithms inside, etc, see this:
958// https://github.com/viewstools/morph/blob/master/ensure-is-media.js
959
960import { useMedia } from 'use-media';
961
962let useIsMedia = () => ({
963 ${media.map(({
964 name,
965 minWidth,
966 maxWidth
967}) => {
968 let ret = [`"${name}": useMedia({ minWidth: ${minWidth}`];
969
970 if (maxWidth) {
971 ret.push(`, maxWidth: ${maxWidth}`);
972 }
973
974 ret.push('})');
975 return ret.join('');
976}).join(',')}
977})
978export default useIsMedia`;
979
980async function getMediaConfig(src) {
981 let media = {
982 mobile: {
983 width: 414
984 },
985 tablet: {
986 width: 1024
987 },
988 laptop: {
989 width: 1280
990 }
991 };
992
993 try {
994 media = JSON.parse((await fs.promises.readFile(path.resolve(path.join(src, '..', 'app.viewstools')), 'utf8'))).media;
995 delete media.base;
996 } catch (error) {}
997
998 return Object.entries(media).sort((a, b) => a[1].width - b[1].width).map(([name, item], index, list) => ({
999 name,
1000 minWidth: index === 0 ? 0 : list[index - 1][1].width + 1,
1001 maxWidth: index === 0 ? item.width : index === list.length - 1 ? null : item.width
1002 }));
1003}
1004
1005async function ensureIsMedia({
1006 src
1007}) {
1008 return ensureFile({
1009 file: path.join(src, 'Logic', 'useIsMedia.js'),
1010 content: makeUseIsMedia((await getMediaConfig(src)))
1011 });
1012}
1013
1014let TOOLS_FILE = `import { useEffect } from 'react'
1015
1016export default function ViewsTools(props) {
1017 useEffect(() => {
1018 console.log(\`
1019
1020
1021
1022 😱😱😱😱😱😱😱😱😱😱😱
1023
1024
1025
1026 🚨 You're missing out!!!
1027
1028 🚀 Views Tools can help you find product market fit before you run out of money.
1029
1030 ✨ Find out how 👉 https://views.tools
1031
1032
1033
1034
1035 \`)
1036 }, [])
1037
1038 return props.children
1039}`;
1040async function ensureTools({
1041 src
1042}) {
1043 let file = path.join(src, 'Logic', 'ViewsTools.js');
1044 if ((await fsExtra.exists(file)) && process.env.REACT_APP_VIEWS_TOOLS) return null;
1045 return ensureFile({
1046 file,
1047 content: TOOLS_FILE
1048 });
1049}
1050
1051let FILE_USE_IS_BEFORE = path.join('Logic', 'useIsBefore.js');
1052let FILE_USE_IS_HOVERED = path.join('Logic', 'useIsHovered.js');
1053let FILE_USE_IS_MEDIA = path.join('Logic', 'useIsMedia.js');
1054let FILE_USE_DATA = path.join('Data', 'ViewsData.js');
1055let FILE_USE_FLOW = path.join('Logic', 'ViewsFlow.js');
1056function makeGetSystemImport(src) {
1057 return function getSystemImport(id, file) {
1058 switch (id) {
1059 case 'Column':
1060 // Column is imported from react-virtualized
1061 break;
1062
1063 case 'ViewsUseIsMedia':
1064 return `import useIsMedia from '${relativise(file, path.join(src, FILE_USE_IS_MEDIA))}'`;
1065
1066 case 'ViewsUseIsBefore':
1067 return `import useIsBefore from '${relativise(file, path.join(src, FILE_USE_IS_BEFORE))}'`;
1068
1069 case 'ViewsUseIsHovered':
1070 return `import useIsHovered from '${relativise(file, path.join(src, FILE_USE_IS_HOVERED))}'`;
1071
1072 case 'ViewsUseData':
1073 return `import * as fromData from '${relativise(file, path.join(src, FILE_USE_DATA))}'`;
1074
1075 case 'ViewsUseFlow':
1076 return `import * as fromFlow from '${relativise(file, path.join(src, FILE_USE_FLOW))}'`;
1077
1078 default:
1079 return false;
1080 }
1081 };
1082}
1083
1084let enter = (node, parent, state) => {
1085 if (node.isFragment) return;
1086 let value;
1087
1088 if (parent) {
1089 value = `"${state.name}.${node.testId}"`;
1090 } else {
1091 value = `{\`\${props['${state.testIdKey}'] || '${node.testId}'}\`}`;
1092 }
1093
1094 state.render.push(` ${state.testIdKey}=${value}`);
1095};
1096
1097let enter$1 = (node, parent, state) => {
1098 if (node.isFragment || !process.env.REACT_APP_VIEWS_TOOLS) return;
1099
1100 if (state.viewPath) {
1101 state.render.push(` ${state.viewPathKey}="${state.viewPath}"`);
1102 } else {
1103 state.render.push(` ${state.viewPathKey}={props["${state.viewPathKey}"]}`);
1104 }
1105};
1106
1107let IS_INT = /^([0-9]+)(.*)$/;
1108let IS_FLOAT = /^([0-9]+\.[0-9]+)(.*)$/;
1109let MULTIPLE_BY_FONT_SIZE = 'x font size';
1110let PIXEL = 'px';
1111let PERCENTAGE = '%';
1112let EM = 'em';
1113let REM = 'rem';
1114let VW = 'vw';
1115let VH = 'vh';
1116let DEGREES = 'deg';
1117let GRADIANS = 'grad';
1118let RADIANS = 'rad';
1119let TURN = 'turn';
1120
1121let getUnit = node => {
1122 let units = getUnits(node.name);
1123 if (typeof node.value === 'number') return units[0] || '';
1124 let match = node.value.match(IS_INT.test(node.value) ? IS_INT : IS_FLOAT);
1125 return match && units.find(u => u === match[2]) || units[0] || '';
1126};
1127let LENGTH = [PIXEL, PERCENTAGE, EM, REM, VW, VH];
1128let PERCENTAGE_FIRST_LENGTH = [PERCENTAGE, PIXEL, EM, REM, VW, VH];
1129let MULTIPLE_BY_FONT_SIZE_LENGTH = [MULTIPLE_BY_FONT_SIZE, PIXEL, PERCENTAGE, EM, REM, VW, VH];
1130let ANGLE = [DEGREES, GRADIANS, RADIANS, TURN];
1131let UNITLESS = [];
1132let UNITS = {
1133 blurRadius: LENGTH,
1134 borderBottomLeftRadius: LENGTH,
1135 borderBottomRightRadius: LENGTH,
1136 borderBottomWidth: LENGTH,
1137 borderLeftWidth: LENGTH,
1138 borderRadius: LENGTH,
1139 borderRightWidth: LENGTH,
1140 borderTopLeftRadius: LENGTH,
1141 borderTopRightRadius: LENGTH,
1142 borderTopWidth: LENGTH,
1143 borderWidth: LENGTH,
1144 bottom: LENGTH,
1145 flexBasis: PERCENTAGE_FIRST_LENGTH,
1146 flexGrow: UNITLESS,
1147 flexShrink: UNITLESS,
1148 fontSize: LENGTH,
1149 height: LENGTH,
1150 left: LENGTH,
1151 letterSpacing: UNITLESS,
1152 lineHeight: MULTIPLE_BY_FONT_SIZE_LENGTH,
1153 margin: LENGTH,
1154 marginBottom: LENGTH,
1155 marginLeft: LENGTH,
1156 marginRight: LENGTH,
1157 marginTop: LENGTH,
1158 maxHeight: LENGTH,
1159 maxWidth: LENGTH,
1160 minHeight: LENGTH,
1161 minWidth: LENGTH,
1162 offsetX: LENGTH,
1163 offsetY: LENGTH,
1164 opacity: UNITLESS,
1165 outline: LENGTH,
1166 padding: LENGTH,
1167 paddingBottom: LENGTH,
1168 paddingLeft: LENGTH,
1169 paddingRight: LENGTH,
1170 paddingTop: LENGTH,
1171 perspective: LENGTH,
1172 right: LENGTH,
1173 rotate: ANGLE,
1174 rotateX: ANGLE,
1175 rotateY: ANGLE,
1176 scale: UNITLESS,
1177 scaleX: UNITLESS,
1178 scaleY: UNITLESS,
1179 shadowOffsetY: LENGTH,
1180 shadowOffsetX: LENGTH,
1181 shadowBlur: LENGTH,
1182 shadowSpread: LENGTH,
1183 skew: ANGLE,
1184 spreadRadius: LENGTH,
1185 top: LENGTH,
1186 translateX: LENGTH,
1187 translateY: LENGTH,
1188 width: LENGTH,
1189 wordSpacing: LENGTH,
1190 zIndex: UNITLESS
1191};
1192let UNITS_KEYS = Object.keys(UNITS);
1193
1194let getUnits = key => {
1195 let found = UNITS_KEYS.find(ukey => key.startsWith(ukey));
1196 return UNITS[found] || UNITLESS;
1197};
1198
1199var wrap = (s => `{${s}}`);
1200
1201var safe = ((value, node) => typeof value === 'string' && !isSlot(value, node) ? JSON.stringify(value) : wrap(value));
1202
1203let checkParentStem = (node, styleKey) => {
1204 if (styleKey !== 'isHovered' || styleKey !== 'isDisabled' || styleKey !== 'isSelected' || styleKey !== 'isSelectedHovered' || !node.parent) return false;
1205 let matchingParentStem = node.parent.scopes.some(scope => scope.value === styleKey);
1206 return matchingParentStem && (node.parent.is || node.parent.name);
1207};
1208let INTERPOLATION = /\${(.+)}/;
1209let isInterpolation = str => INTERPOLATION.test(str);
1210let deinterpolate = str => {
1211 let match = str.match(INTERPOLATION);
1212 return match ? match[1] : str;
1213};
1214let getObjectAsString = obj => Array.isArray(obj) ? `[${obj.map(getObjectAsString)}]` : wrap(Object.keys(obj).map(k => {
1215 let v = typeof obj[k] === 'object' && hasKeys(obj[k]) ? getObjectAsString(obj[k]) : obj[k];
1216 return `${JSON.stringify(k)}: ${v}`;
1217}).join(','));
1218let getProp = (node, key, scope = 'base') => {
1219 let finder = typeof key === 'string' ? p => p.name === key : p => key.test(p.name);
1220
1221 if (scope !== 'base') {
1222 let nodeScope = node.scopes.find(item => item.value === scope);
1223 let prop = nodeScope && nodeScope.properties.find(finder);
1224
1225 if (prop) {
1226 return prop;
1227 }
1228 }
1229
1230 return node.properties && node.properties.find(finder);
1231};
1232let getPropValueOrDefault = (node, key, defaultValue) => {
1233 return hasProp(node, key) ? getProp(node, key).value : defaultValue;
1234};
1235
1236let getScopedProps = (propNode, blockNode) => {
1237 let scopes = blockNode.scopes.filter(scope => !scope.isSystem).map(scope => {
1238 let prop = scope.properties.find(prop => prop.name === propNode.name);
1239 return prop && {
1240 prop,
1241 when: scope.value,
1242 scope
1243 };
1244 }).filter(Boolean).reverse();
1245 if (isEmpty(scopes)) return false;
1246 return scopes;
1247};
1248
1249let getScopedConditionPropValue = node => {
1250 let value = null;
1251
1252 if (node.tags.slot) {
1253 value = node.value;
1254 } else if (typeof node.value === 'string') {
1255 value = safe(node.value);
1256 } else if (typeof node.value === 'boolean' && node.name === 'shadowInset') {
1257 value = node.value ? "'inset'" : "''";
1258 } else {
1259 let unit = getUnit(node);
1260 value = unit ? `"${node.value}${unit}"` : node.value;
1261 }
1262
1263 return value;
1264};
1265
1266let CHILD_VALUES = /!?props\.(isSelected|isHovered|isFocused|isSelectedHovered)/;
1267let DATA_VALUES = /!?props\.(isInvalid|isInvalidInitial|isValid|isValidInitial|value)/;
1268let IS_HOVERED_OR_SELECTED_HOVER = /!?props\.(isHovered|isSelectedHovered)/;
1269let IS_FLOW = /!?props.flow$/;
1270let getScopedCondition = (propNode, blockNode, state) => {
1271 let scopedProps = getScopedProps(propNode, blockNode);
1272 if (!scopedProps) return false;
1273 let conditional = getScopedConditionPropValue(propNode);
1274 scopedProps.forEach(scope => {
1275 let when = scope.when;
1276
1277 if (state.data && DATA_VALUES.test(when)) {
1278 when = when.replace('props', 'data');
1279 } else if (hasCustomBlockParent(blockNode) && CHILD_VALUES.test(when)) {
1280 when = when.replace('props.', 'childProps.');
1281 } else if (IS_FLOW.test(when)) {
1282 let flowPath = getFlowPath(scope.scope, blockNode, state);
1283 when = when.replace('props.flow', `flow.has('${flowPath}')`);
1284 } else if ((blockNode.action || !!getActionableParent(blockNode)) && IS_HOVERED_OR_SELECTED_HOVER.test(when)) {
1285 when = when.replace('props.', '');
1286 }
1287
1288 conditional = `${when} ? ${getScopedConditionPropValue(scope.prop)} : ${conditional}`;
1289 });
1290 let lastScope = scopedProps[scopedProps.length - 1];
1291
1292 if (!lastScope.prop.animation || lastScope.prop.animation.curve !== 'spring') {
1293 lastScope.prop.conditional = conditional;
1294 }
1295
1296 return conditional;
1297};
1298let getScopedImageCondition = (scopes, scopedNames, defaultName) => {
1299 let conditional = defaultName;
1300 scopes.forEach((scope, index) => {
1301 conditional = `${scope.when} ? ${scopedNames[index]} : ` + conditional;
1302 });
1303 return conditional;
1304};
1305let styleStems = ['isHovered', 'isFocused', 'isPlaceholder', 'isDisabled', 'isSelected', 'isSelectedHovered'];
1306let getStyleType = node => styleStems.find(tag => isTag(node, tag)) || 'base';
1307let hasKeys = obj => Object.keys(obj).length > 0;
1308let hasKeysInChildren = obj => Object.keys(obj).some(k => hasKeys(obj[k]));
1309let hasProp = (node, key, match) => {
1310 let prop = getProp(node, key);
1311 if (!prop) return false;
1312 return typeof match === 'function' ? match(prop.value) : true;
1313};
1314let isSlot = (maybeNode1, maybeNode2) => {
1315 let node = maybeNode2 || maybeNode1;
1316 return typeof node === 'string' ? /(data|isHovered|childProps|props|isBefore|isMedia\.)/.test(node) : isTag(node, 'slot');
1317};
1318let isStyle = node => isTag(node, 'style');
1319let isTag = (node, tag) => node && node.tags[tag];
1320let getActionableParent = node => {
1321 if (!node.parent) return false;
1322 if (node.parent.action) return node.parent;
1323 return getActionableParent(node.parent);
1324};
1325let hasCustomBlockParent = node => {
1326 if (!node.parent) return !node.isBasic;
1327 if (!node.parent.isBasic) return true;
1328 return hasCustomBlockParent(node.parent);
1329};
1330let getAllowedStyleKeys = node => {
1331 if (node.isCapture) {
1332 return ['base', 'isFocused', 'isHovered', 'isDisabled', 'isPlaceholder'];
1333 } else if (node.action || isTable(node) || getActionableParent(node)) {
1334 return ['base', 'isFocused', 'isHovered', 'isDisabled', 'isSelected', 'isSelectedHovered'];
1335 }
1336
1337 return ['base', 'isFocused'];
1338};
1339let isList = node => node && node.type === 'Block' && node.name === 'List';
1340let isCell = node => node.properties.some(prop => prop.name === 'isCell');
1341let isHeader = node => node.properties.some(prop => prop.name === 'isHeader');
1342let isColumn = node => node && node.type === 'Block' && node.name === 'Column';
1343let isTable = node => node && node.type === 'Block' && node.name === 'Table';
1344let isEmpty = list => list.length === 0;
1345let isValidImgSrc = (node, parent) => node.name === 'source' && parent.name === 'Image' && parent.isBasic;
1346let pushImageToState = (state, scopedNames, paths) => scopedNames.forEach(name => {
1347 let path = paths[scopedNames.findIndex(item => item === name)];
1348
1349 if (!state.images.includes(path)) {
1350 state.images.push({
1351 name,
1352 file: path
1353 });
1354 }
1355});
1356let getScopes = (node, parent) => {
1357 let scopedProps = getScopedProps(node, parent);
1358 if (!scopedProps) return false;
1359 let paths = scopedProps.map(scope => scope.prop.value);
1360 let scopedNames = paths.map(path => toCamelCase(path));
1361 return {
1362 scopedProps,
1363 paths,
1364 scopedNames
1365 };
1366};
1367let isSvg = node => /^Svg/.test(node.name) && node.isBasic;
1368let hasCustomScopes = (propNode, blockNode) => blockNode.scopes.some(scope => !scope.isSystem && scope.properties.some(prop => prop.name === propNode.name)); // let isRotate = name =>
1369// name === 'rotate' || name === 'rotateX' || name === 'rotateY'
1370
1371let getStandardAnimatedString = (node, prop, isNative) => {
1372 let value = `animated${node.id}${prop.animationIndexOnBlock > 0 ? prop.animationIndexOnBlock : ''}.${prop.name}`; // let unit = getUnit(prop)
1373 // if (unit) {
1374 // value = `\`\${${value}}${unit}\``
1375 // }
1376
1377 return `${isNative ? prop.name : `"--${prop.name}"`}: ${value}`;
1378};
1379
1380let getTransformString = (node, transform, isNative) => {
1381 let transformStr = `transform: [`;
1382 transform.props.forEach((prop, i) => {
1383 transformStr += `{${getAnimatedString(node, prop, isNative)}},`;
1384 });
1385 return `${transformStr}]`;
1386};
1387let getAnimatedStyles = (node, isNative) => {
1388 let props = isNative ? getAllAnimatedProps(node, true) : getSpringProps(node);
1389 return props.map(prop => getAnimatedString(node, prop, isNative)).join(', ');
1390};
1391
1392let getPropValue = (prop, interpolateValue = true) => {
1393 let unit = getUnit(prop);
1394
1395 if (unit) {
1396 let value = interpolateValue ? `\`\${${prop.value}}${unit}\`` : `"${prop.value}${unit}"`;
1397 return `typeof ${prop.value} === 'number' ? ${value} : ${prop.value}`;
1398 } else {
1399 return prop.value;
1400 }
1401};
1402
1403let getDynamicStyles = node => {
1404 return flatten([node.properties.filter(prop => prop.tags.style && prop.tags.slot && !getScopedProps(prop, node)).map(prop => `'--${prop.name}': ${getPropValue(prop)}`), node.scopes.map(scope => scope.properties.filter(prop => prop.tags.style).map(prop => {
1405 let value = null;
1406
1407 if (prop.conditional) {
1408 value = prop.conditional; // } else if (prop.tags.slot && prop.scope === 'isHovered') {
1409 // value = getPropValue(prop)
1410 }
1411
1412 return value && `'--${prop.name}': ${value}`;
1413 }))]).filter(Boolean);
1414};
1415
1416let getAnimatedString = (node, prop, isNative) => prop.name === 'transform' ? getTransformString(node, prop, isNative) : getStandardAnimatedString(node, prop, isNative);
1417
1418let getNonAnimatedDynamicStyles = node => {
1419 let animatedProps = getAllAnimatedProps(node, true).map(prop => prop.name);
1420 let animatedTransforms = animatedProps.includes('transform') ? getAllAnimatedProps(node, true).find(prop => prop.name === 'transform').props.map(prop => prop.name) : [];
1421 return Object.keys(node.style.dynamic.base).filter(key => !animatedProps.includes(key) && !animatedTransforms.includes(key)).reduce((obj, key) => {
1422 obj[key] = node.style.dynamic.base[key];
1423 return obj;
1424 }, {});
1425};
1426let getAllAnimatedProps = (node, isNative) => {
1427 let props = flatten(node.scopes.map(scope => scope.properties.filter(prop => prop.animation)));
1428 return checkForTransforms(props) && isNative ? combineTransforms(props) : props;
1429};
1430
1431let combineTransforms = props => {
1432 // TODO: handle transforms on different scopes
1433 let transform = {
1434 name: 'transform',
1435 props: []
1436 };
1437 props.forEach((prop, i) => {
1438 if (TRANSFORM_WHITELIST[prop.name]) {
1439 prop.isTransform = true;
1440 transform.props.push(prop);
1441 }
1442 });
1443 props.push(transform);
1444 return props.filter(prop => !prop.isTransform);
1445};
1446
1447let checkForTransforms = props => props.some(prop => TRANSFORM_WHITELIST[prop.name]);
1448
1449let getTimingProps = node => flatten(node.scopes.map(scope => scope.properties.filter(prop => prop.animation && prop.animation.curve !== 'spring')));
1450
1451let getSpringProps = node => flatten(node.scopes.map(scope => scope.properties.filter(prop => prop.animation && prop.animation.curve === 'spring'))); // https://github.com/facebook/react-native/blob/26684cf3adf4094eb6c405d345a75bf8c7c0bf88/Libraries/Animated/src/NativeAnimatedHelper.js
1452let TRANSFORM_WHITELIST = {
1453 translateX: true,
1454 translateY: true,
1455 scale: true,
1456 scaleX: true,
1457 scaleY: true,
1458 rotate: true,
1459 rotateX: true,
1460 rotateY: true,
1461 perspective: true
1462};
1463let createId = (node, state, addClassName = true) => {
1464 let id = node.is || node.name; // count repeatead ones
1465
1466 if (state.usedBlockNames[id]) {
1467 id = `${id}${state.usedBlockNames[id]++}`;
1468 } else {
1469 state.usedBlockNames[id] = 1;
1470 }
1471
1472 node.styleName = id;
1473
1474 if (addClassName && node.className) {
1475 node.className.push(`\${styles.${id}}`);
1476 }
1477
1478 return id;
1479};
1480let CONTENT_CONTAINER_STYLE_PROPS = ['paddingTop', 'paddingBottom', 'paddingLeft', 'paddingRight', 'flexDirection', 'justifyContent', 'alignItems'];
1481
1482let isContentContainerStyleProp = prop => CONTENT_CONTAINER_STYLE_PROPS.includes(prop);
1483
1484let hasContentContainerStyleProp = styleProps => Object.keys(styleProps).some(isContentContainerStyleProp);
1485let getContentContainerStyleProps = styleProps => Object.keys(styleProps).filter(isContentContainerStyleProp).reduce((obj, key) => {
1486 obj[key] = styleProps[key];
1487 return obj;
1488}, {});
1489let removeContentContainerStyleProps = styleProps => Object.keys(styleProps).filter(key => !isContentContainerStyleProp(key)).reduce((obj, key) => {
1490 obj[key] = styleProps[key];
1491 return obj;
1492}, {});
1493let hasRowStyles = node => node.properties.some(prop => prop.name.match(/^row/) && prop.name !== 'rowHeight');
1494let MAYBE_HYPHENATED_STYLE_PROPS = ['alignContent', 'alignItems', 'alignSelf', 'backgroundBlendMode', 'backgroundClip', 'backgroudOrigin', 'backgroundRepeat', 'boxSizing', 'clear', 'cursor', 'flexBasis', 'flexDirection', 'flexFlow', 'flexWrap', 'float', 'fontFamily', 'fontStretch', 'justifyContent', 'objectFit', 'overflowWrap', 'textAlign', 'textDecorationLine', 'textTransform', 'whiteSpace', 'wordBreak'];
1495let maybeMakeHyphenated = ({
1496 name,
1497 value
1498}) => MAYBE_HYPHENATED_STYLE_PROPS.includes(name) && /^[a-zA-Z]+$/.test(value) ? toSlugCase(value) : value;
1499let isStory = (node, state) => !node.isBasic && state.isStory(node.name) && state.flow === 'separate';
1500function getFlowPath(node, parent, state) {
1501 // TODO warn if action is used but it isn't in actions (on parser)
1502 // TODO warn that there's setFlowTo without an id (on parser)
1503 let setFlowTo = node.defaultValue;
1504
1505 if (!setFlowTo.startsWith('/')) {
1506 setFlowTo = path.normalize(path.join(state.viewPath, setFlowTo));
1507 }
1508
1509 return setFlowTo;
1510}
1511
1512let typesMap = {
1513 email: 'email',
1514 text: 'text',
1515 number: 'number',
1516 phone: 'tel',
1517 secure: 'password',
1518 file: 'file'
1519};
1520let enter$2 = (node, parent, state) => {
1521 if (!node.isCapture || node.name === 'CaptureTextArea') return;
1522 let type = getProp(node, 'type');
1523
1524 if (isSlot(type)) {
1525 state.render.push(` type=${safe(type.value)}`); // fix for iOS Safari to show the numpad on
1526 // http://danielfriesen.name/blog/2013/09/19/input-type-number-and-ios-numeric-keypad/
1527
1528 let maybeNumber = (name, valueWhenTrue) => `${name}={${type.value} === 'number' || ${type.value} === 'phone'? "${valueWhenTrue}" : undefined}`;
1529
1530 state.render.push(maybeNumber('inputMode', 'numeric'));
1531 state.render.push(maybeNumber('pattern', '[0-9]*'));
1532 } else {
1533 state.render.push(` type=${safe(typesMap[type.value])}`); // fix for iOS Safari to show the numpad on
1534 // http://danielfriesen.name/blog/2013/09/19/input-type-number-and-ios-numeric-keypad/
1535
1536 if (type.value === 'number' || type.value === 'phone') {
1537 state.render.push(` inputMode="numeric" pattern="[0-9]*"`);
1538 }
1539 }
1540};
1541
1542function enter$3(node, parent, state) {
1543 let value = state.getValueForProperty(node, parent, state);
1544
1545 if (value) {
1546 Object.keys(value).forEach(k => {
1547 if (k === '...') {
1548 return state.render.push(` {...${value[k]}}`);
1549 } else {
1550 return state.render.push(` ${k}=${value[k]}`);
1551 }
1552 });
1553 }
1554}
1555
1556function enter$4(node, parent, state) {
1557 if (!isColumn(node)) return;
1558 let dataKey = getProp(node, 'key');
1559 let width = getWidth(node, parent);
1560 state.render.push(` dataKey="${dataKey.value}" width={${width}}`); // let label = getLabel(node)
1561 // if (label) {
1562 // state.render.push(`label="${label}"`)
1563 // }
1564
1565 let header = node.children.find(isHeader);
1566
1567 if (header && !header.isBasic) {
1568 state.render.push(` headerRenderer={headerProps => <${header.name} {...headerProps} `);
1569 header.properties.forEach(propNode => {
1570 if (propNode.name === 'isHeader') return;
1571 enter$3(propNode, header, state);
1572 });
1573 state.render.push(` />}`);
1574 }
1575
1576 let cell = node.children.find(isCell);
1577
1578 if (cell && !cell.isBasic) {
1579 state.render.push(` cellRenderer={cellProps => <${cell.name} {...cellProps} `);
1580 cell.properties.forEach(propNode => {
1581 if (propNode.name === 'isCell') return;
1582 enter$3(propNode, cell, state);
1583 });
1584 state.render.push(` />}`);
1585 }
1586}
1587
1588let getWidth = (node, parent) => {
1589 let width = getProp(node, 'width');
1590
1591 if (width && width.value !== 'auto') {
1592 return width.value;
1593 }
1594
1595 let columns = parent.children.filter(child => child.name === 'Column');
1596 let columnsWithFixedWidth = columns.map(node => {
1597 let width = getProp(node, 'width');
1598 return width && typeof width.value === 'number' && width.value;
1599 }).filter(Boolean);
1600 let columnsWidthSum = columnsWithFixedWidth.reduce((res, value) => res + value, 0);
1601 return columnsWidthSum ? `(width - ${columnsWidthSum}) / ${columns.length - columnsWithFixedWidth.length}` : `width / ${columns.length}`;
1602}; // let getLabel = node => {
1603// let header = node.children.find(node => getProp(node, 'isHeader'))
1604// if (!header) return
1605// if (header.name === 'Text') {
1606// // removing the text node, because the column handles the label
1607// node.children.splice(node.children.indexOf(header), 1)
1608// } else {
1609// node.externalHeader = header
1610// }
1611// return getProp(header, 'text').value
1612// }
1613
1614function leave(node, parent, state) {
1615 if (node.explicitChildren) {
1616 state.render.push('>');
1617 state.render.push(node.explicitChildren);
1618 }
1619}
1620
1621let enter$5 = (node, parent, state) => {
1622 if (node.goTo) {
1623 let goTo = getProp(node, 'goTo');
1624 state.render.push(` href=${safe(goTo.value)} rel='noopener noreferrer' target='_blank'`);
1625 }
1626};
1627
1628function enter$6(node, parent, state) {
1629 if (!node.isDefiningChildrenExplicitly && node.isGroup && node.children.length > 0 && !node.nameFinal.includes('FlatList')) {
1630 state.render.push('>');
1631
1632 if (!node.isBasic) {
1633 state.render.push('{childProps => (');
1634
1635 if (node.children.length > 1) {
1636 state.render.push('<React.Fragment>');
1637 }
1638 }
1639 }
1640}
1641function leave$1(node, parent, state) {
1642 if (!node.isDefiningChildrenExplicitly && node.isGroup && node.children.length > 0 && !node.nameFinal.includes('FlatList') && !node.isBasic) {
1643 if (node.children.length > 1) {
1644 state.render.push('</React.Fragment>');
1645 }
1646
1647 state.render.push(')}');
1648 }
1649}
1650
1651function enter$7(node, parent, state) {
1652 if (isList(parent)) {
1653 state.render.push(` index={index}`);
1654 let pass = getProp(parent, 'pass');
1655
1656 if (pass) {
1657 state.render.push(` ${pass.value}={${pass.value}}`);
1658 } else {
1659 state.render.push(' {...item}');
1660 }
1661
1662 if (!parent.nameFinal.includes('FlatList')) {
1663 let key = getProp(node, 'key');
1664 key = key ? key.value : 'index';
1665 state.render.push(` key={${key}}`);
1666 }
1667 }
1668}
1669
1670function enter$8(node, parent, state) {
1671 if (isList(node)) {
1672 let from = getProp(node, 'from');
1673 if (!from) return;
1674 let pass = getProp(node, 'pass');
1675 let itemName = pass ? pass.value : 'item';
1676
1677 if (node.nameFinal.includes('FlatList')) {
1678 let key = getProp(node.children[0], 'key');
1679 key = key ? key.value : 'index';
1680 state.render.push(`data={${from.value}} keyExtractor={(item, index) => ${key}} renderItem={({ ${itemName}, index }) =>`);
1681 } else {
1682 state.render.push(`{Array.isArray(${from.value}) && ${from.value}.map((${itemName}, index) => `);
1683 }
1684 }
1685}
1686function leave$2(node, parent, state) {
1687 if (isList(node)) {
1688 state.render.push(node.nameFinal.includes('FlatList') ? '}' : ')}');
1689 }
1690}
1691
1692let enter$9 = node => node.skip;
1693
1694function leave$3(node, parent, state) {
1695 if (node.isDefiningChildrenExplicitly) return;
1696
1697 if ((!parent && node.isGroup || node.explicitChildren || node.isGroup && node.children.length > 0) && !node.nameFinal.includes('FlatList')) {
1698 if (!parent && node.isGroup) {
1699 if (node.children.length === 0) {
1700 state.render.push('>');
1701 }
1702
1703 if (!state.hasAlreadyDefinedChildren) {
1704 state.hasAlreadyDefinedChildren = true;
1705 state.render.push(`{props.children}`);
1706 }
1707 }
1708
1709 state.render.push(`</${node.nameFinal}>`);
1710 } else {
1711 state.render.push('/>');
1712 }
1713}
1714
1715var handleTable = ((node, parent, state) => {
1716 if (isTable(node)) {
1717 let from = getProp(node, 'from'); // skip the table if we don't have a from prop
1718
1719 if (!from) return true;
1720 state.isTable = true;
1721 state.render.push(`<AutoSizer>{({ width, height }) => (`);
1722 }
1723
1724 return isCell(node) || isHeader(node);
1725});
1726
1727var getBlockName = ((node, parent, state) => {
1728 let name = node.name;
1729
1730 switch (node.name) {
1731 case 'Capture':
1732 name = 'input';
1733 break;
1734
1735 case 'CaptureTextArea':
1736 name = 'textarea';
1737 break;
1738
1739 case 'Horizontal':
1740 case 'Vertical':
1741 name = getGroupBlockName(node, parent);
1742 break;
1743
1744 case 'Image':
1745 name = 'img';
1746 break;
1747
1748 case 'Text':
1749 return 'span';
1750
1751 case 'List':
1752 name = 'div';
1753 break;
1754
1755 case 'Svg':
1756 name = 'svg';
1757 break;
1758
1759 case 'SvgGroup':
1760 name = 'g';
1761 break;
1762
1763 case 'SvgCircle':
1764 case 'SvgEllipse':
1765 case 'SvgLinearGradient':
1766 case 'SvgRadialGradient':
1767 case 'SvgLine':
1768 case 'SvgText':
1769 case 'SvgPath':
1770 case 'SvgPolygon':
1771 case 'SvgPolyline':
1772 case 'SvgRect':
1773 case 'SvgSymbol':
1774 case 'SvgUse':
1775 case 'SvgDefs':
1776 case 'SvgStop':
1777 name = toCamelCase(node.name.replace('Svg', ''));
1778 break;
1779
1780 case 'Block':
1781 case 'View':
1782 name = 'React.Fragment';
1783 break;
1784 }
1785
1786 if (node.hasSpringAnimation) {
1787 return `animated.${name}`;
1788 }
1789
1790 return name;
1791});
1792
1793let getGroupBlockName = (node, parent, state) => {
1794 let name = 'div';
1795
1796 if (node.isFragment) {
1797 name = 'React.Fragment';
1798 } else if (hasProp(node, 'isModal')) {
1799 name = 'ViewsModalOverlay';
1800 } else if (parent && hasProp(parent, 'isModal')) {
1801 name = 'ViewsModalOverlayContent';
1802 } else if (hasProp(node, 'goTo')) {
1803 name = 'a';
1804 node.goTo = true;
1805 node.action = getProp(node, 'goTo').value;
1806 } else if (hasProp(node, 'onClick')) {
1807 let propNode = getProp(node, 'onClick');
1808 let prevParent = parent;
1809 let canBeButton = true;
1810
1811 while (prevParent && canBeButton) {
1812 if (prevParent.type === 'Block') {
1813 canBeButton = !hasProp(prevParent, 'onClick');
1814 }
1815
1816 prevParent = prevParent.parent;
1817 }
1818
1819 if (canBeButton) {
1820 if (!hasProp(node, 'onClickUseDiv')) {
1821 name = 'button';
1822 }
1823
1824 node.action = propNode.value;
1825 }
1826 } else if (hasProp(node, 'overflowY', v => v === 'auto' || v === 'scroll')) {
1827 name = 'div';
1828 } else if (hasProp(node, 'onSubmit')) {
1829 name = 'form';
1830 }
1831
1832 return name;
1833};
1834
1835function enter$a(node, parent, state) {
1836 // if (parent && !parent.isBasic && !node.isBasic) return true
1837 if (node.isFragment && node.children.length === 0) return true;
1838
1839 if (node.isChildren) {
1840 state.hasAlreadyDefinedChildren = true;
1841 state.render.push(`{typeof props.children === 'function'? props.children({ isSelected: props.isSelected`);
1842 let useIsHovered = !!getActionableParent(node);
1843
1844 if (useIsHovered) {
1845 state.useIsHovered = true;
1846 state.render.push(', isHovered, isSelectedHovered');
1847 }
1848
1849 state.render.push(`}) : null}`);
1850 return true;
1851 }
1852
1853 if (node.isFragment && node.name === 'View') {
1854 state.flow = getPropValueOrDefault(node, 'is', false);
1855 state.flowDefaultState = null;
1856 }
1857
1858 if (isStory(node, state)) {
1859 state.use('ViewsUseFlow');
1860 }
1861
1862 let name = getBlockName(node, parent);
1863 if (name === null) return true;
1864 state.use(name, node.isLazy); // TODO remove the use of those because they're just the name and keep one
1865
1866 node.nameFinal = name;
1867 node.nameTag = name;
1868 if (handleTable(node, parent, state)) return true;
1869
1870 if (!node.isDefiningChildrenExplicitly) {
1871 state.render.push(`<${node.nameFinal}`);
1872 }
1873}
1874
1875let IS_MEDIA = /(!?props\.isMedia)(.+)/;
1876let DATA_VALUES$1 = /!?props\.(isInvalid|isInvalidInitial|isValid|isValidInitial|value)/;
1877let CHILD_VALUES$1 = /!?props\.(isSelected|isHovered|isFocused)/;
1878let IS_HOVERED = /!?props\.(isHovered|isSelectedHovered)/;
1879let IS_FLOW$1 = /!?props\.flow$/;
1880function enter$b(node, parent, state) {
1881 if (node.isFragment && node.children.length === 0) return; // onWhen lets you show/hide blocks depending on props
1882
1883 let onWhen = getProp(node, 'onWhen');
1884
1885 if (onWhen) {
1886 node.onWhen = true;
1887 if (parent && !isList(parent)) state.render.push('{');
1888 let value = onWhen.value;
1889
1890 if (state.data && DATA_VALUES$1.test(value)) {
1891 value = value.replace('props', 'data');
1892 } else if (IS_MEDIA.test(value)) {
1893 let [, variable, media] = value.match(IS_MEDIA);
1894 value = `${variable.replace('props.', '')}.${media.toLowerCase()}`;
1895 } else if (hasCustomBlockParent(node) && CHILD_VALUES$1.test(value)) {
1896 value = value.replace('props.', 'childProps.');
1897 } else if (IS_FLOW$1.test(value)) {
1898 let flowPath = getFlowPath(onWhen, node, state);
1899 value = value.replace('props.flow', `flow.has('${flowPath}')`);
1900 } else if (IS_HOVERED.test(value)) {
1901 value = value.replace('props.', '');
1902 }
1903
1904 state.render.push(`${value} ? `);
1905 } else if (isStory(node, state)) {
1906 node.onWhen = true;
1907 state.render.push(`{flow.has("${state.viewPath}/${node.name}") ? `);
1908 }
1909}
1910function leave$4(node, parent, state) {
1911 if (node.onWhen) {
1912 state.render.push(` : null`);
1913 if (parent && !isList(parent)) state.render.push('}');
1914 }
1915}
1916
1917let enter$c = (node, parent, state) => {
1918 node.className = node.isBasic ? [node.name === 'Text' ? 'views-text' : node.isCapture ? 'views-capture' : 'views-block'] : [];
1919 let className = getProp(node, 'className');
1920
1921 if (className) {
1922 node.className.push(className.tags.slot ? `\${${className.value}}` : className.value);
1923 }
1924};
1925let leave$5 = (node, parent, state) => {
1926 if (node.isFragment) return;
1927
1928 if (node.className.length === 1) {
1929 let className = node.className[0];
1930 let shouldWrap = true;
1931
1932 if (isInterpolation(className)) {
1933 className = deinterpolate(className);
1934 } else if (!isSlot(className)) {
1935 className = `"${className}"`;
1936 shouldWrap = false;
1937 }
1938
1939 state.render.push(` className=${shouldWrap ? wrap(className) : className}`);
1940 } else if (node.className.length > 1) {
1941 let className = `\`${node.className.join(' ')}\``;
1942 state.render.push(` className=${wrap(className)}`);
1943 }
1944};
1945
1946let enter$d = (node, parent, state) => {
1947 // TODO caption
1948 if (node.name === 'Image') {
1949 state.render.push(' alt={""}');
1950 }
1951};
1952
1953function enter$e(node, parent, state) {
1954 node.style = {
1955 dynamic: {
1956 base: {},
1957 isHovered: {},
1958 isFocused: {},
1959 isDisabled: {},
1960 isPlaceholder: {},
1961 isSelected: {},
1962 isSelectedHovered: {}
1963 },
1964 static: {
1965 base: {},
1966 isHovered: {},
1967 isFocused: {},
1968 isDisabled: {},
1969 isPlaceholder: {},
1970 isSelected: {},
1971 isSelectedHovered: {}
1972 }
1973 };
1974 if (node.isFragment) return; // TODO use this directly in styles without having to go through this
1975
1976 node.scopes.filter(scope => scope.isSystem).forEach(scope => {
1977 scope.properties.forEach(propNode => {
1978 if (propNode.name === 'when') return;
1979 let {
1980 _isProp,
1981 ...styleForProperty
1982 } = state.getStyleForProperty(propNode, node, state, isSlot(propNode));
1983
1984 if (_isProp) {
1985 Object.keys(styleForProperty).forEach(k => state.render.push(` ${k}=${safe(styleForProperty[k], node)}`));
1986 } else {
1987 let hasMatchingParent = parent && node.isDynamic ? checkParentStem(node, scope.value) : false;
1988 let target = isSlot(propNode) || hasMatchingParent ? node.style.dynamic : node.style.static;
1989 Object.assign(target[scope.value], styleForProperty);
1990 }
1991 });
1992 }); // ensure flex-direction in Horizontals
1993
1994 if (node.isGroup && node.name === 'Horizontal') {
1995 node.style.static.base.flexDirection = 'row';
1996 }
1997}
1998
1999function leave$6(node, parent, state) {
2000 if (node.isFragment) return;
2001 let allowedStyleKeys = getAllowedStyleKeys(node);
2002 let scopedUnderParent = !node.isCapture && !node.action && getActionableParent(node);
2003
2004 if (scopedUnderParent) {
2005 scopedUnderParent = scopedUnderParent.styleName;
2006 }
2007
2008 if (node.hasSpringAnimation) {
2009 state.isAnimated = true;
2010 state.hasSpringAnimation = true;
2011 state.animations[node.id] = node.animations;
2012 state.scopes = node.scopes;
2013 }
2014
2015 let id = null;
2016
2017 if (isTable(node) && hasRowStyles(node)) {
2018 id = createId(node, state, false);
2019 getTableRowCss({
2020 node,
2021 state,
2022 id,
2023 scopedUnderParent
2024 });
2025 }
2026
2027 let css = [getStaticCss({
2028 node,
2029 scopedUnderParent,
2030 state,
2031 allowedStyleKeys
2032 }), node.isAnimated && getAnimatedCss(node), getDynamicCss({
2033 node,
2034 scopedUnderParent,
2035 state,
2036 allowedStyleKeys
2037 })].filter(Boolean);
2038
2039 if (css.length > 0) {
2040 if (id === null) {
2041 id = createId(node, state);
2042 } else if (isTable(node)) {
2043 if (node.className) {
2044 node.className.push(`\${styles.${id}}`);
2045 }
2046 }
2047
2048 state.styles[id] = `css({label: '${id}',\n ${css.join(',\n ')}})`;
2049 state.stylesOrder.push(id);
2050 }
2051}
2052
2053let composeStyles = (node, styles, scopedUnderParent) => {
2054 let allowedStyleKeys = getAllowedStyleKeys(node);
2055
2056 if (hasKeysInChildren(styles.dynamic)) {
2057 let cssStatic = Object.keys(styles.static).filter(key => allowedStyleKeys.includes(key) && hasKeys(styles.static[key])).map(key => asCss(asStaticCss(styles.static[key], Object.keys(styles.dynamic[key])), key, scopedUnderParent).join('\n'));
2058 cssStatic = cssStatic.join(',\n');
2059 let cssDynamic = Object.keys(styles.dynamic).filter(key => allowedStyleKeys.includes(key) && hasKeys(styles.dynamic[key])).map(key => asCss(asDynamicCss(styles.dynamic[key]), key, scopedUnderParent).join('\n')).join(',\n');
2060 return {
2061 cssDynamic,
2062 cssStatic
2063 };
2064 }
2065
2066 let cssStatic = Object.keys(styles.static).filter(key => allowedStyleKeys.includes(key) && hasKeys(styles.static[key])).map(key => asCss(asStaticCss(styles.static[key]), key, scopedUnderParent).join('\n')).join(',\n');
2067 return {
2068 cssStatic
2069 };
2070};
2071
2072let getStaticCss = ({
2073 node,
2074 scopedUnderParent,
2075 state,
2076 allowedStyleKeys
2077}) => {
2078 let style = node.style.static;
2079 if (!hasKeysInChildren(style)) return false;
2080 state.cssStatic = true;
2081 let hasDynamicCss = hasKeysInChildren(node.style.dynamic);
2082 return Object.keys(style).filter(key => allowedStyleKeys.includes(key) && hasKeys(style[key])).map(key => asCss(asStaticCss(style[key], hasDynamicCss ? Object.keys(node.style.dynamic[key]) : []), key, scopedUnderParent).join('\n')).join(',\n');
2083};
2084
2085let getDynamicCss = ({
2086 node,
2087 scopedUnderParent,
2088 state,
2089 allowedStyleKeys
2090}) => {
2091 let style = node.style.dynamic;
2092 if (!hasKeysInChildren(style)) return false;
2093 state.cssDynamic = true;
2094 node.styleName = node.nameFinal;
2095
2096 if (node.hasSpringAnimation) {
2097 state.render.push(` style={{${getAnimatedStyles(node, state.isReactNative)},${getDynamicStyles(node)}}}`);
2098 } else {
2099 let inlineDynamicStyles = getDynamicStyles(node);
2100
2101 if (inlineDynamicStyles) {
2102 state.render.push(` style={{${inlineDynamicStyles}}}`);
2103 }
2104 }
2105
2106 return Object.keys(style).filter(key => allowedStyleKeys.includes(key) && hasKeys(style[key])).map(key => asCss(asDynamicCss(style[key]), key, scopedUnderParent).join('\n')).join(',\n');
2107};
2108
2109let getAnimatedCss = node => {
2110 if (node.hasTimingAnimation) {
2111 let transition = uniq(getTimingProps(node).map(makeTransition)).join(', ');
2112 return `\ntransition: '${transition}',\nwillChange: '${getUniqueNames(node)}'`;
2113 }
2114
2115 return `\nwillChange: '${getUniqueNames(node)}'`;
2116};
2117
2118let getUniqueNames = node => {
2119 let names = [...new Set(getAllAnimatedProps(node, false).map(prop => prop.name))];
2120 return uniq(names.map(name => ensurePropName(name))).join(', ');
2121};
2122
2123let ensurePropName = name => {
2124 switch (name) {
2125 case 'rotate':
2126 case 'rotateX':
2127 case 'rotateY':
2128 case 'scale':
2129 case 'scaleX':
2130 case 'scaleY':
2131 case 'translateX':
2132 case 'translateY':
2133 return 'transform';
2134
2135 case 'shadowColor':
2136 case 'shadowBlur':
2137 case 'shadowOffsetX':
2138 case 'shadowOffsetY':
2139 case 'shadowSpread':
2140 case 'shadowInset':
2141 return 'box-shadow';
2142
2143 default:
2144 return toSlugCase(name);
2145 }
2146};
2147
2148let makeTransition = ({
2149 name,
2150 animation
2151}) => [ensurePropName(name), `${animation.duration}ms`, toSlugCase(animation.curve), animation.delay && `${animation.delay}ms`].filter(Boolean).join(' ');
2152
2153let asDynamicCss = styles => Object.keys(styles).map(prop => `${prop}: ${styles[prop]}`);
2154
2155let safe$1 = str => typeof str === 'string' ? `"${str.replace(/"/g, "'")}"` : str;
2156
2157let asStaticCss = (styles, dynamicStyles = []) => Object.keys(styles).filter(prop => !dynamicStyles.includes(prop)).map(prop => `${prop}: ${safe$1(styles[prop])}`);
2158
2159let systemScopeToCssKey = {
2160 isDisabled: 'disabled',
2161 // isHovered: 'hover:enabled',
2162 isFocused: 'focus:enabled'
2163};
2164
2165let ensureSystemScopeCssKey = key => systemScopeToCssKey[key] || key;
2166
2167let asCss = (styles, key, scopedUnderParent) => {
2168 let css = [];
2169
2170 if (key !== 'base') {
2171 if (scopedUnderParent) {
2172 let parent = `.\${styles.${scopedUnderParent}}`;
2173 let theKey = ensureSystemScopeCssKey(key);
2174 css.push(`[\`${parent}:${theKey} &\`]: {`);
2175 } else if (key === 'isDisabled' || // key === 'isHovered' ||
2176 key === 'isFocused') {
2177 css.push(`"&:${ensureSystemScopeCssKey(key)}": {`);
2178 } else if (key === 'isPlaceholder') {
2179 css.push(`"&::placeholder": {`);
2180 }
2181 }
2182
2183 css.push(styles.join(',\n'));
2184 if (key !== 'base') css.push(`}`);
2185 return css;
2186};
2187
2188let getTableRowCss = ({
2189 node,
2190 state,
2191 id,
2192 scopedUnderParent
2193}) => {
2194 let normalStyles = {};
2195 let alternateStyles = {};
2196 Object.entries(node.style).forEach(([type, typeScopes]) => {
2197 if (!(type in alternateStyles)) {
2198 alternateStyles[type] = {};
2199 }
2200
2201 if (!(type in normalStyles)) {
2202 normalStyles[type] = {};
2203 }
2204
2205 Object.entries(typeScopes).forEach(([scope, scopeStyles]) => {
2206 if (!(scope in alternateStyles[type])) {
2207 alternateStyles[type][scope] = {};
2208 }
2209
2210 if (!(scope in normalStyles[type])) {
2211 normalStyles[type][scope] = {};
2212 }
2213
2214 Object.entries(scopeStyles).forEach(([key, value]) => {
2215 switch (key) {
2216 case 'rowColor':
2217 normalStyles[type][scope]['color'] = value;
2218 delete node.style[type][scope][key];
2219 break;
2220
2221 case 'rowBackgroundColor':
2222 normalStyles[type][scope]['backgroundColor'] = value;
2223 delete node.style[type][scope][key];
2224 break;
2225
2226 case 'rowColorAlternate':
2227 alternateStyles[type][scope]['color'] = value;
2228 delete node.style[type][scope][key];
2229 break;
2230
2231 case 'rowBackgroundColorAlternate':
2232 alternateStyles[type][scope]['backgroundColor'] = value;
2233 delete node.style[type][scope][key];
2234 break;
2235 }
2236 });
2237 });
2238 });
2239 let {
2240 cssDynamic: normalDynamic,
2241 cssStatic: normalStatic
2242 } = composeStyles(node, normalStyles, scopedUnderParent);
2243 let {
2244 cssDynamic: alternateDynamic,
2245 cssStatic: alternateStatic
2246 } = composeStyles(node, alternateStyles, scopedUnderParent);
2247 let normalCss = `${normalStatic ? `${normalStatic}` : ''} ${normalDynamic ? `, ${normalDynamic}` : ''}`;
2248 let alternateCss = `${alternateStatic ? `${alternateStatic}` : ''} ${alternateDynamic ? `, ${alternateDynamic}` : ''}`;
2249 node.hasDynamicRowStyles = !!(normalDynamic || alternateDynamic);
2250 state.render.push(` rowClassName={styles.${id}Row}`);
2251 state.styles[`${id}Row`] = `css({ display: 'flex'
2252 ${normalCss ? `, ${normalCss}` : ''}
2253 ${alternateCss ? `, "&:nth-child(even)": {${alternateCss}}` : ''}
2254 })`;
2255 state.stylesOrder.push(`${id}Row`);
2256};
2257
2258let enter$f = (node, parent, state) => {
2259 if (node.children && node.children.some(child => child.format)) {
2260 state.isFormatted = true;
2261 state.formats = [];
2262 node.children.filter(child => child.format).forEach(child => state.formats.push(child.format));
2263 } else if (node.format && !parent) {
2264 state.isFormatted = true;
2265 state.formats = [];
2266 state.formats.push(node.format);
2267 }
2268};
2269
2270function enter$g(node, parent, state) {
2271 if (!node.action || 'userSelect' in node.style.static.base) return;
2272 node.style.static.base.userSelect = 'none';
2273}
2274
2275function enter$h(node, parent, state) {
2276 if (node.name === 'ref') {
2277 if (!parent.isBasic) {
2278 state.hasRefs = true;
2279 } // TODO swap for React.forwardRef
2280
2281
2282 state.render.push(` ${parent.isDynamic ? 'innerRef' : 'ref'}=${wrap(node.value)}`);
2283 return true;
2284 }
2285}
2286
2287let PROPS_THAT_IMPLY_CODE_FOR_OTHERS = {
2288 fontFamily: 'fontWeight',
2289 shadowOffsetX: 'shadowOffsetY',
2290 shadowOffsetY: 'shadowOffsetX'
2291};
2292function enter$i(node, parent, state) {
2293 if (!isStyle(node) || !parent.isBasic || isSvg(parent) && state.isReactNative || parent.name === 'SvgGroup') return;
2294 let code = isSlot(node);
2295
2296 if (!code && state.isReactNative) {
2297 let otherProp = PROPS_THAT_IMPLY_CODE_FOR_OTHERS[node.name];
2298
2299 if (otherProp) {
2300 code = isSlot(getProp(parent, otherProp));
2301 }
2302 }
2303
2304 let {
2305 _isProp,
2306 _isScoped,
2307 ...styleForProperty
2308 } = state.getStyleForProperty(node, parent, state, code);
2309
2310 if (_isProp) {
2311 Object.keys(styleForProperty).forEach(k => state.render.push(` ${k}=${safe(styleForProperty[k], node)}`));
2312 } else {
2313 let hasMatchingParent = parent && node.isDynamic ? checkParentStem(node, getStyleType(node)) : false;
2314 let target = code || _isScoped || hasMatchingParent ? parent.style.dynamic : parent.style.static;
2315 Object.assign(target[getStyleType(node)], styleForProperty);
2316 }
2317
2318 return true;
2319}
2320
2321let HAS_RESTRICTED_CHARACTERS = /[{}<>/]/;
2322function enter$j(node, parent, state) {
2323 if (node.name === 'text' && parent.name === 'Text') {
2324 if (state.data && node.value === 'props.value') {
2325 parent.explicitChildren = '{data.value}';
2326 } else if (hasCustomScopes(node, parent)) {
2327 parent.explicitChildren = wrap(getScopedCondition(node, parent, state));
2328 } else if (isSlot(node)) {
2329 parent.explicitChildren = wrap(node.value);
2330 } else if (HAS_RESTRICTED_CHARACTERS.test(node.value)) {
2331 parent.explicitChildren = `{"${node.value}"}`;
2332 } else {
2333 parent.explicitChildren = node.value;
2334 }
2335
2336 return true;
2337 }
2338}
2339
2340function enter$k(node, parent, state) {
2341 if (node.isBasic || !state.viewPath) return; // this is something we may use when going into dynamic paths through data
2342 // state.render.push(`viewPath={\`\${props.viewPath}/${state.name}\`}`)
2343
2344 state.render.push(`viewPath="${state.viewPath}/${node.name}"`);
2345}
2346
2347let blacklist = ['classname', 'format', 'goto', 'isfragment', 'ismodal', 'onclickid', 'onclickselected', 'onclickselectedtype', 'onclickusediv', 'perspective', 'rotatez', 'scalez', 'teleportto', 'transformoriginz', 'translatez', 'type'];
2348var isValidPropertyForBlock = ((node, parent, state) => !blacklist.includes(node.name.toLowerCase()));
2349
2350function enter$l(node, parent, state) {
2351 if (node.isFragment) return false;
2352 enter$e(node, parent, state);
2353 enter$c(node);
2354 enter$d(node, parent, state);
2355 enter$f(node, parent, state);
2356 enter$k(node, parent, state);
2357 node.properties.forEach(propNode => {
2358 if (propNode.name === 'lazy' || propNode.name === 'at' || propNode.name === 'when' || propNode.name === 'onWhen' || propNode.tags.unsupportedShorthand || !isValidPropertyForBlock(propNode) && node.isBasic || propNode.name === 'from' && (node.name === 'List' || node.name === 'Table') || propNode.name === 'key' && parent.isList || propNode.name === 'pass' && node.isList || propNode.name === 'width' && isColumn(node)) return;
2359 !enter$h(propNode, node, state) && !enter$i(propNode, node, state) && !enter$j(propNode, node, state) && enter$3(propNode, node, state);
2360 });
2361 enter$g(node);
2362 leave$6(node, parent, state);
2363 leave$5(node, parent, state);
2364}
2365
2366let enter$m = (node, parent, state) => {
2367 let blockName = node.is || node.name;
2368
2369 if (typeof state.testIds[blockName] === 'number') {
2370 state.testIds[blockName]++;
2371 blockName = `${blockName}:${state.testIds[blockName]}`;
2372 } else {
2373 state.testIds[blockName] = 0;
2374 }
2375
2376 node.testId = blockName;
2377};
2378
2379function enter$n(node, parent, state) {
2380 if (!isTable(node)) return;
2381 state.render.push(` width={width} height={height} rowCount={Array.isArray(props.from) ? props.from.length : 0} rowGetter={({ index }) => props.from[index]}`);
2382}
2383function leave$7(node, parent, state) {
2384 if (!isTable(node)) return;
2385 state.render.push(`)}</AutoSizer>`);
2386}
2387
2388let enter$o = [enter$9, enter$m, enter$b, enter$a, enter$n, enter$4, enter$2, enter$5, enter$7, enter, enter$1, enter$l, enter$6, enter$8];
2389let leave$8 = [leave$2, leave, leave$1, leave$3, leave$7, leave$4];
2390
2391var visitor = /*#__PURE__*/Object.freeze({
2392 __proto__: null,
2393 enter: enter$o,
2394 leave: leave$8
2395});
2396
2397function getStyleForProperty(node, parent, state, code) {
2398 let scopedVar = setScopedVar(node, parent, state);
2399
2400 if (scopedVar) {
2401 switch (node.name) {
2402 case 'rotate':
2403 case 'rotateX':
2404 case 'rotateY':
2405 case 'scale':
2406 case 'scaleX':
2407 case 'scaleY':
2408 case 'translateX':
2409 case 'translateY':
2410 return {
2411 _isScoped: true,
2412 transform: `'${getTransform(node, parent)}'`
2413 };
2414
2415 case 'shadowColor':
2416 case 'shadowBlur':
2417 case 'shadowOffsetX':
2418 case 'shadowOffsetY':
2419 case 'shadowSpread':
2420 case 'shadowInset':
2421 {
2422 let shadow = getShadow(node, parent, state);
2423 let key = Object.keys(shadow)[0];
2424 return {
2425 _isScoped: true,
2426 [key]: `'${shadow[key]}'`
2427 };
2428 }
2429
2430 default:
2431 return {
2432 _isScoped: true,
2433 [node.name]: scopedVar
2434 };
2435 }
2436 }
2437
2438 switch (node.name) {
2439 case 'appRegion':
2440 return {
2441 WebkitAppRegion: maybeAsVar(node, code)
2442 };
2443
2444 case 'backgroundImage':
2445 return {
2446 backgroundImage: code ? `\`url(\${${asVar(node)}})\`` : `url("${node.value}")`
2447 };
2448
2449 case 'fontFamily':
2450 return {
2451 fontFamily: code ? asVar(node) : maybeAddFallbackFont(node.value)
2452 };
2453
2454 case 'shadowColor':
2455 case 'shadowBlur':
2456 case 'shadowOffsetX':
2457 case 'shadowOffsetY':
2458 case 'shadowSpread':
2459 case 'shadowInset':
2460 return getShadow(node, parent, state);
2461
2462 case 'rotate':
2463 case 'rotateX':
2464 case 'rotateY':
2465 case 'scale':
2466 case 'scaleX':
2467 case 'scaleY':
2468 case 'translateX':
2469 case 'translateY':
2470 return {
2471 transform: getTransform(node, parent)
2472 };
2473
2474 case 'transformOriginX':
2475 case 'transformOriginY':
2476 return {
2477 transformOrigin: getTransformOrigin(node, parent)
2478 };
2479
2480 case 'zIndex':
2481 return {
2482 zIndex: code ? asVar(node) : parseInt(node.value, 10)
2483 };
2484
2485 default:
2486 return {
2487 [node.name]: maybeAsVar(node, code)
2488 };
2489 }
2490}
2491
2492let maybeAsVar = (prop, code) => code ? asVar(prop) : maybeMakeHyphenated(prop);
2493
2494let asVar = prop => `'var(--${prop.name})'`;
2495
2496let setScopedVar = (prop, block, state) => {
2497 if (prop.scope === 'isHovered' || prop.scope === 'isFocused' || prop.scope === 'isDisabled') return false;
2498 let scopedCondition = getScopedCondition(prop, block, state);
2499 return scopedCondition && asVar(prop);
2500};
2501
2502let getPropValue$1 = (prop, block, state, unit = '') => {
2503 if (!prop) return false;
2504 let scopedVar = setScopedVar(prop, block, state);
2505 if (scopedVar) return scopedVar;
2506
2507 if (prop.tags.slot) {
2508 return `var(--${prop.name})`;
2509 }
2510
2511 return typeof prop.value === 'number' ? `${prop.value}${unit}` : prop.value;
2512};
2513
2514let getShadow = (node, parent, state) => {
2515 let isText = parent.name === 'Text';
2516 let shadowColor = getProp(parent, 'shadowColor', node.scope);
2517 let shadowBlur = getProp(parent, 'shadowBlur', node.scope);
2518 let shadowOffsetX = getProp(parent, 'shadowOffsetX', node.scope);
2519 let shadowOffsetY = getProp(parent, 'shadowOffsetY', node.scope);
2520 let shadowSpread = getProp(parent, 'shadowSpread', node.scope);
2521 let shadowInset = getProp(parent, 'shadowInset', node.scope);
2522 let shadowInsetValue = getPropValue$1(shadowInset, parent, state);
2523 let value = [!isText && (typeof shadowInsetValue === 'string' && /var\(/.test(shadowInsetValue) ? shadowInsetValue : shadowInsetValue && 'inset'), getPropValue$1(shadowOffsetX, parent, state, 'px'), getPropValue$1(shadowOffsetY, parent, state, 'px'), getPropValue$1(shadowBlur, parent, state, 'px'), !isText && getPropValue$1(shadowSpread, parent, state, 'px'), getPropValue$1(shadowColor, parent, state)].filter(Boolean).join(' ').replace(/'/g, '');
2524
2525 if (isSlot(shadowColor) || isSlot(shadowBlur) || isSlot(shadowOffsetY) || isSlot(shadowOffsetX) || isSlot(shadowSpread) || isSlot(shadowInset)) {
2526 value = `\`${value}\``;
2527 }
2528
2529 return {
2530 [isText ? 'textShadow' : 'boxShadow']: value
2531 };
2532};
2533
2534let getTransformValue = (prop, parent, unit) => {
2535 if (!prop) return false;
2536 return `${prop.name}(${getPropValue$1(prop, parent, unit)})`;
2537};
2538
2539let getTransform = (node, parent) => {
2540 let rotate = getProp(parent, 'rotate', node.scope);
2541 let rotateX = getProp(parent, 'rotateX', node.scope);
2542 let rotateY = getProp(parent, 'rotateY', node.scope);
2543 let scale = getProp(parent, 'scale', node.scope);
2544 let scaleX = getProp(parent, 'scaleX', node.scope);
2545 let scaleY = getProp(parent, 'scaleY', node.scope);
2546 let translateX = getProp(parent, 'translateX', node.scope);
2547 let translateY = getProp(parent, 'translateY', node.scope);
2548 let value = [getTransformValue(rotate, parent, 'deg'), getTransformValue(rotateX, parent, 'deg'), getTransformValue(rotateY, parent, 'deg'), getTransformValue(scale, parent, ''), getTransformValue(scaleX, parent, ''), getTransformValue(scaleY, parent, ''), getTransformValue(translateX, parent, 'px'), getTransformValue(translateY, parent, 'px')].filter(Boolean).join(' ');
2549
2550 if (isSlot(rotate) || isSlot(rotateX) || isSlot(rotateY) || isSlot(scale) || isSlot(scaleX) || isSlot(scaleY) || isSlot(translateX) || isSlot(translateY)) {
2551 value = `\`${value}\``;
2552 } // TODO FIXME this is a hack to remove strings because my head is fried
2553 // and yeah it does what we need for now :)
2554
2555
2556 return value.replace(/'/g, '');
2557};
2558
2559let getTransformOrigin = (node, parent) => {
2560 let transformOriginX = getProp(parent, 'transformOriginX', node.scope);
2561 let transformOriginY = getProp(parent, 'transformOriginY', node.scope);
2562 let value = [getPropValue$1(transformOriginX, parent, Number.isInteger(transformOriginX.value) ? 'px' : ''), getPropValue$1(transformOriginY, parent, Number.isInteger(transformOriginY.value) ? 'px' : '')].filter(Boolean).join(' ');
2563
2564 if (isSlot(transformOriginX) || isSlot(transformOriginY)) {
2565 value = `\`${value}\``;
2566 }
2567
2568 return value;
2569};
2570
2571var getStyles = (({
2572 styles,
2573 stylesOrder
2574}) => {
2575 if (!hasKeys(styles)) return '';
2576 let res = [`let styles = {}`];
2577 stylesOrder.forEach(id => {
2578 res.push(`styles.${id} = ${styles[id]}`);
2579 });
2580 return res.join('\n');
2581});
2582
2583let isUrl = str => /^https?:\/\//.test(str);
2584
2585function getImageSource(node, parent, state) {
2586 let scopes = getScopes(node, parent);
2587
2588 if (scopes && (isUrl(node.value) || node.tags.slot)) {
2589 return wrap(getScopedCondition(node, parent, state));
2590 } else if (isUrl(node.value) || node.tags.slot) {
2591 if (node.defaultValue && !isUrl(node.defaultValue)) {
2592 state.slots.forEach(item => {
2593 if (item.defaultValue === node.defaultValue) {
2594 item.type = 'import';
2595 let name = toCamelCase(item.defaultValue);
2596
2597 if (!state.images.includes(item.defaultValue)) {
2598 state.images.push({
2599 name,
2600 file: item.defaultValue
2601 });
2602 }
2603
2604 item.defaultValue = name;
2605 }
2606 });
2607 }
2608
2609 return safe(node.value);
2610 } else {
2611 if (scopes) {
2612 pushImageToState(state, scopes.scopedNames, scopes.paths);
2613 }
2614
2615 let name = toCamelCase(node.value);
2616
2617 if (!state.images.includes(node.value)) {
2618 state.images.push({
2619 name,
2620 file: node.value
2621 });
2622 }
2623
2624 return scopes ? wrap(getScopedImageCondition(scopes.scopedProps, scopes.scopedNames, name)) : `{${name}}`;
2625 }
2626}
2627
2628let CHILD_VALUES$2 = /props\.(isSelected|isHovered|isFocused|isSelectedHovered)/;
2629let ON_IS_SELECTED = /(onClick|onPress|goTo)/;
2630function getValueForProperty(node, parent, state) {
2631 if (state.data && (node.value === '!props.value' || node.value === 'props.value' || node.value === 'props.onSubmit' || node.value === 'props.onChange' || node.value === 'props.isInvalid' || node.value === 'props.isInvalidInitial' || node.value === 'props.isValid' || node.value === 'props.isValidInitial' || node.value === 'props.onSubmit')) {
2632 return {
2633 [node.name]: `{${node.value.replace('props.', 'data.')}}`
2634 };
2635 } else if (hasCustomBlockParent(parent) && CHILD_VALUES$2.test(node.value)) {
2636 return {
2637 [node.name]: `{${node.value.replace('props.', 'childProps.')}}`
2638 };
2639 } else if (isValidImgSrc(node, parent)) {
2640 return {
2641 src: getImageSource(node, parent, state)
2642 };
2643 } else if (parent.isBasic && node.name === 'isDisabled') {
2644 return {
2645 disabled: safe(node.value, node)
2646 };
2647 } else if (getScopedCondition(node, parent, state)) {
2648 return {
2649 [node.name]: safe(getScopedCondition(node, parent, state))
2650 };
2651 } else if (/^on[A-Z]/.test(node.name) && node.slotName === 'setFlowTo') {
2652 let flowPath = getFlowPath(node, parent, state);
2653 state.use('ViewsUseFlow');
2654 state.setFlowTo = true;
2655 let ret = {
2656 [node.name]: `{() => setFlowTo('${flowPath}')}`
2657 };
2658
2659 if (!parent.isBasic && ON_IS_SELECTED.test(node.name)) {
2660 state.useFlow = true;
2661 ret[node.name.replace(ON_IS_SELECTED, 'isSelected')] = `{flow.has('${flowPath}')}`;
2662 }
2663
2664 if (parent.isBasic && ON_IS_SELECTED.test(node.name)) {
2665 state.useIsHovered = true;
2666 ret['...'] = `${node.name.replace(ON_IS_SELECTED, 'isHovered')}Bind`;
2667 }
2668
2669 return ret;
2670 } else if (parent.isBasic && ON_IS_SELECTED.test(node.name)) {
2671 state.useIsHovered = true;
2672 return {
2673 '...': `${node.name.replace(ON_IS_SELECTED, 'isHovered')}Bind`,
2674 [node.name]: safe(node.value, node)
2675 };
2676 } else if (CHILD_VALUES$2.test(node.value) && !!getActionableParent(parent) && !parent.isBasic) {
2677 // TODO support more than one hover/selected in the same block - let's wait
2678 // for a use case
2679 if (/isHovered/.test(node.value)) {
2680 return {
2681 [node.name]: safe(node.value.replace('props.', ''), node)
2682 };
2683 } else if (/isSelected/.test(node.value)) {
2684 let actionableParent = getActionableParent(parent);
2685 let actionableProp = getProp(actionableParent, 'onClick') || getProp(actionableParent, 'goTo');
2686
2687 if (!actionableProp.defaultValue) {
2688 return {
2689 [node.name]: safe(node.value, node)
2690 };
2691 }
2692
2693 let flowPath = getFlowPath(actionableProp, actionableParent, state);
2694 state.use('ViewsUseFlow');
2695 state.useFlow = true;
2696 return {
2697 [node.name]: `{flow.has('${flowPath}')}`
2698 };
2699 } else {
2700 return {
2701 [node.name]: safe(node.value, node)
2702 };
2703 }
2704 } else {
2705 return {
2706 [node.name]: safe(node.value, node)
2707 };
2708 }
2709}
2710
2711function makeGetImport({
2712 imports,
2713 getSystemImport,
2714 view,
2715 viewsById,
2716 viewsToFiles
2717}) {
2718 return function getImport(id, isLazy) {
2719 if (imports[id]) return imports[id];
2720 let externalImport = getSystemImport(id, view.file);
2721 if (externalImport) return externalImport;
2722
2723 if (!viewsById.has(id)) {
2724 throw new Error(`Import "${id}" doesn't exist. It's being imported from ${view.id}.`);
2725 }
2726
2727 let importView = getViewRelativeToView({
2728 id,
2729 view,
2730 viewsById,
2731 viewsToFiles
2732 });
2733
2734 if (!importView) {
2735 throw new Error(`Import "${id}" doesn't exist. It's being imported from ${view.id}.`);
2736 }
2737
2738 let importFile = importView.file;
2739
2740 if (importView.logic) {
2741 importFile = importView.logic;
2742 } else if (!importView.custom) {
2743 importFile = path.join(path.dirname(importFile), 'view.js');
2744 }
2745
2746 let importPath = relativise(view.file, importFile);
2747 return isLazy ? `let ${id} = React.lazy(() => import('${importPath}'))` : `import ${id} from '${importPath}'`;
2748 };
2749}
2750
2751var restrictedNames = ['Block', 'Capture', 'CaptureTextArea', 'Children', 'Column', 'Horizontal', 'Image', 'List', 'ViewsModalOverlay', 'ViewsModalOverlayContent', 'Spring', 'Svg', 'SvgCircle', 'SvgDefs', 'SvgEllipse', 'SvgGroup', 'SvgLine', 'SvgLinearGradient', 'SvgPath', 'SvgPolygon', 'SvgPolyline', 'SvgRadialGradient', 'SvgRect', 'SvgStop', 'SvgSymbol', 'SvgText', 'SvgUse', 'Table', 'Text', 'Timing', 'Vertical', 'View'];
2752
2753var getBody = (({
2754 state,
2755 name
2756}) => {
2757 let render = state.render.join('\n');
2758 let animated = [];
2759
2760 if (state.isAnimated) {
2761 Object.keys(state.animations).forEach(blockId => {
2762 Object.values(state.animations[blockId]).forEach(item => {
2763 let {
2764 curve,
2765 ...configValues
2766 } = item.animation.properties;
2767 if (!state.isReactNative && curve !== 'spring') return;
2768 let spring = ['{', '"config": {'];
2769 Object.entries(configValues).forEach(([k, v]) => {
2770 spring.push(`${k}: ${JSON.stringify(v)},`);
2771 });
2772
2773 if (curve !== 'spring' && curve !== 'linear') {
2774 spring.push(`"easing": Easing.${curve.replace('ease', 'easeCubic')},`);
2775 }
2776
2777 spring.push('},');
2778 let toValue = [];
2779 let fromValue = [];
2780 Object.values(item.props).forEach(prop => {
2781 prop.scopes.reverse();
2782 let value = prop.scopes.reduce((current, scope) => `props.${scope.name}? ${JSON.stringify(scope.value)} : ${current}`, JSON.stringify(prop.value));
2783 toValue.push(`${JSON.stringify(prop.name)}: ${value}`);
2784 fromValue.push(`${JSON.stringify(prop.name)}: ${JSON.stringify(prop.scopes[0].value)}`);
2785 });
2786 spring.push(`"from": {${fromValue.join(',')}},`);
2787 spring.push(`"to": {${toValue.join(',')}},`);
2788 spring.push('}');
2789 animated.push(`let animated${blockId}${item.index > 0 ? item.index : ''} = useSpring(${spring.join('\n')})`);
2790 });
2791 });
2792 }
2793
2794 let flow = [];
2795
2796 if (state.useFlow || state.flow === 'separate' && state.uses.includes('ViewsUseFlow')) {
2797 flow.push(`let flow = fromFlow.useFlow()`);
2798 }
2799
2800 if (state.setFlowTo) {
2801 flow.push(`let setFlowTo = fromFlow.useSetFlowTo()`);
2802 }
2803
2804 let data = [];
2805
2806 if (state.data) {
2807 switch (state.data.type) {
2808 case 'show':
2809 case 'capture':
2810 {
2811 data.push(`let data = fromData.useData({ path: '${state.data.path}', `);
2812 maybeDataContext(state.data, data);
2813 maybeDataFormat(state.dataFormat, data);
2814 maybeDataValidate(state.dataValidate, data);
2815 data.push('})');
2816 break;
2817 }
2818 }
2819 }
2820
2821 if (state.hasRefs) {
2822 return `export default class ${name} extends React.Component {
2823 render() {
2824 let { props } = this
2825 return (${render})
2826 }
2827}`;
2828 } else {
2829 let ret = render ? `(${render})` : null;
2830 return `export default function ${name}(props) {
2831 ${state.useIsBefore ? 'let isBefore = useIsBefore()' : ''}
2832 ${state.useIsHovered ? 'let [isHovered, isSelectedHovered, isHoveredBind] = useIsHovered(props)' : ''}
2833 ${state.useIsMedia ? 'let isMedia = useIsMedia()' : ''}
2834 ${animated.join('\n')}
2835 ${flow.join('\n')}
2836 ${data.join('\n')}
2837
2838 return ${ret}
2839}`;
2840 }
2841});
2842
2843function maybeDataContext(dataDefinition, data) {
2844 if (dataDefinition.context === null) return;
2845 data.push(`context: '${dataDefinition.context}',`);
2846}
2847
2848function maybeDataFormat(format, data) {
2849 if (!format) return;
2850
2851 if (format.formatIn) {
2852 data.push(`formatIn: '${format.formatIn}',`);
2853 }
2854
2855 if (format.formatOut) {
2856 data.push(`formatOut: '${format.formatOut}',`);
2857 }
2858}
2859
2860function maybeDataValidate(validate, data) {
2861 if (!validate || validate.type !== 'js') return;
2862 data.push(`validate: '${validate.value}',`);
2863
2864 if (validate.required) {
2865 data.push('validateRequired: true,');
2866 }
2867}
2868
2869let stringify = slot => {
2870 if (slot.type === 'array') {
2871 return JSON.stringify([]);
2872 } else if (/^[0-9]+$/.test(slot.defaultValue) || slot.type === 'import') {
2873 return slot.defaultValue;
2874 } else {
2875 return JSON.stringify(slot.defaultValue);
2876 }
2877};
2878
2879var getDefaultProps = (({
2880 state,
2881 name
2882}) => {
2883 let slots = state.slots.filter(slot => slot.defaultValue !== false).map(slot => `${slot.name}: ${stringify(slot)}`);
2884 return slots.length === 0 ? '' : `${name}.defaultProps = {${slots.join(',\n')}}`;
2885});
2886
2887var SVG = ['Svg', 'SvgCircle', 'SvgEllipse', 'SvgDefs', 'SvgGroup', 'SvgLinearGradient', 'SvgRadialGradient', 'SvgLine', 'SvgPath', 'SvgPolygon', 'SvgPolyline', 'SvgRect', 'SvgSymbol', 'SvgText', 'SvgUse', 'SvgDefs', 'SvgStop'];
2888
2889let NATIVE = ['Animated', 'Image', 'KeyboardAvoidingView', 'FlatList', 'ScrollView', 'StyleSheet', 'Text', 'TextInput', 'TouchableWithoutFeedback', 'TouchableHighlight', 'View'];
2890
2891let sortAlphabetically = (a, b) => {
2892 return a === b ? 0 : a < b ? -1 : 1;
2893};
2894
2895let importsFirst = (a, b) => {
2896 let aIsImport = a.startsWith('import');
2897 let bIsImport = b.startsWith('import');
2898 if (aIsImport && bIsImport || !aIsImport && !bIsImport) return sortAlphabetically(a, b);
2899 if (aIsImport) return -1;
2900 if (bIsImport) return 1;
2901};
2902
2903var getDependencies = ((state, getImport) => {
2904 let usesNative = [];
2905 let usesSvg = [];
2906
2907 let useNative = d => {
2908 if (!usesNative.includes(d)) {
2909 usesNative.push(d);
2910 }
2911 };
2912
2913 let useSvg = d => {
2914 if (!usesSvg.includes(d)) {
2915 usesSvg.push(d);
2916 }
2917 };
2918
2919 let dependencies = [`import React from 'react'`];
2920 state.uses.sort().forEach(d => {
2921 if (state.isReactNative && NATIVE.includes(d)) {
2922 useNative(d);
2923 } else if (state.isReactNative && SVG.includes(d)) {
2924 useSvg(d === 'Svg' ? d : d === 'SvgGroup' ? `G as SvgGroup` : `${d.replace('Svg', '')} as ${d}`);
2925 } else if (d === 'Table') ; else if (/^[A-Z]/.test(d)) {
2926 let importStatement = getImport(d, state.lazy[d]);
2927 dependencies.push(importStatement);
2928
2929 if (/logic\.js'/.test(importStatement)) {
2930 dependencies.push(`${d}.displayName = '${d}Logic'`);
2931 }
2932 }
2933 });
2934
2935 if (state.isReactNative) ; else {
2936 state.fonts.forEach(usedFont => dependencies.push(state.getFontImport(usedFont.id)));
2937 } // TODO we probably want to check that the file exists and do something if it
2938 // doesn't, like warn the user at least?
2939
2940
2941 state.images.forEach(img => dependencies.push(`import ${img.name} from "${img.file}"`));
2942
2943 if (state.cssDynamic || state.cssStatic) {
2944 dependencies.push('import { css } from "emotion"');
2945 state.dependencies.add('emotion');
2946 }
2947
2948 if (state.isAnimated) {
2949 let animations = ['animated', (state.hasSpringAnimation || state.hasTimingAnimation && state.isReactNative) && 'useSpring'].filter(Boolean);
2950
2951 if (animations.length > 0) {
2952 dependencies.push(`import { ${animations.join(', ')} } from "react-spring"`);
2953 state.dependencies.add('react-spring');
2954
2955 if (state.hasTimingAnimation && state.isReactNative) {
2956 dependencies.push(`import * as Easing from 'd3-ease'`);
2957 state.dependencies.add('d3-ease');
2958 }
2959 }
2960 }
2961
2962 if (state.isReactNative) {
2963 for (let component of state.animated) {
2964 dependencies.push(`let Animated${component} = animated(${component})`);
2965 }
2966 }
2967
2968 if (state.isTable) {
2969 dependencies.push(`import { AutoSizer, Column, Table } from "@viewstools/tables/${state.isReactNative ? 'native' : 'dom'}"`);
2970 state.dependencies.add('@viewstools/tables');
2971 }
2972
2973 if (usesSvg.length > 0) {
2974 let svg = usesSvg.filter(m => m !== 'Svg').join(', ');
2975 dependencies.push(`import Svg, { ${svg} } from 'react-native-svg'`);
2976 }
2977
2978 if (usesNative.length > 0) {
2979 dependencies.push(`import { ${usesNative.join(', ')} } from '${state.reactNativeLibraryImport}'`);
2980 }
2981
2982 if (state.useIsBefore) {
2983 dependencies.push(getImport('ViewsUseIsBefore'));
2984 }
2985
2986 if (state.useIsHovered) {
2987 dependencies.push(getImport('ViewsUseIsHovered'));
2988 }
2989
2990 if (state.useIsMedia) {
2991 dependencies.push(getImport('ViewsUseIsMedia'));
2992 }
2993
2994 return dependencies.filter(Boolean).sort(importsFirst).join('\n');
2995});
2996
2997var getFlatList = (({
2998 uses
2999}) => {
3000 return uses.includes('Animated') && uses.includes('FlatList') ? `let AnimatedFlatList = Animated.createAnimatedComponent(FlatList)` : '';
3001});
3002
3003var toComponent = (({
3004 getImport,
3005 getStyles,
3006 name,
3007 state
3008}) => {
3009 // TODO Emojis should be wrapped in <span>, have role="img", and have an accessible description
3010 // with aria-label or aria-labelledby jsx-a11y/accessible-emoji
3011 return `/* eslint-disable jsx-a11y/accessible-emoji, no-unused-vars, no-dupe-keys, react/jsx-no-duplicate-props */
3012// This file is auto-generated. Edit ${name}.view to change it. More info: https://github.com/viewstools/docs/blob/master/UseViews/README.md#viewjs-is-auto-generated-and-shouldnt-be-edited
3013${getDependencies(state, getImport)}
3014
3015${getStyles(state, name)}
3016${getFlatList(state)}
3017
3018${getBody({
3019 state,
3020 name
3021 })}
3022${getDefaultProps({
3023 state,
3024 name
3025 })}`;
3026});
3027
3028function walk(ast, {
3029 enter,
3030 leave
3031}, state) {
3032 visit(ast, null, enter, leave, state);
3033}
3034
3035function visit(node, parent, enter, leave, state) {
3036 if (!node) return;
3037
3038 if (enter) {
3039 if (enter.some(fn => fn(node, parent, state))) return;
3040 }
3041
3042 if (Array.isArray(node.children)) {
3043 node.children.forEach(child => {
3044 child.parent = node;
3045 visit(child, node, enter, leave, state);
3046 });
3047 }
3048
3049 if (leave) {
3050 leave.forEach(fn => fn(node, parent, state));
3051 }
3052}
3053
3054let imports = {
3055 ViewsModalOverlay: "import { DialogOverlay as ViewsModalOverlay, DialogContent as ViewsModalOverlayContent } from '@reach/dialog'"
3056};
3057var reactDom = (({
3058 getFontImport,
3059 getSystemImport,
3060 tools,
3061 view,
3062 viewsById,
3063 viewsToFiles
3064}) => {
3065 let name = view.id;
3066 let finalName = restrictedNames.includes(name) ? `${name}1` : name;
3067
3068 if (name !== finalName) {
3069 console.warn(`// "${name}" is a Views reserved name.
3070 We've renamed it to "${finalName}", so your view should work but this isn't ideal.
3071 To fix this, change its file name to something else.`);
3072 }
3073
3074 let state = {
3075 animated: new Set(),
3076 animations: {},
3077 cssDynamic: false,
3078 cssStatic: false,
3079 data: view.parsed.view.data,
3080 dataFormat: view.parsed.view.dataFormat,
3081 dataValidate: view.parsed.view.dataValidate,
3082 dependencies: new Set(),
3083 flow: null,
3084 setFlowTo: false,
3085 getFontImport: font => getFontImport(font, view),
3086 getStyleForProperty,
3087 getValueForProperty,
3088 hasRefs: false,
3089 images: [],
3090 isDynamic: false,
3091 isReactNative: false,
3092 isStory: id => {
3093 let viewInView = getViewRelativeToView({
3094 id,
3095 view,
3096 viewsById,
3097 viewsToFiles
3098 });
3099 return viewInView && !viewInView.custom && viewInView.parsed.view.isStory;
3100 },
3101 lazy: {},
3102 name: finalName,
3103 viewPath: view.parsed.view.viewPath,
3104 viewPathParent: view.parsed.view.viewPathParent,
3105 render: [],
3106 styles: {},
3107 stylesOrder: [],
3108 usedBlockNames: {
3109 [finalName]: 1,
3110 AutoSizer: 1,
3111 Column: 1,
3112 Table: 1
3113 },
3114 uses: [],
3115 testIdKey: 'data-testid',
3116 viewPathKey: 'data-view-path',
3117 testIds: {},
3118 tools,
3119
3120 use(block, isLazy = false) {
3121 if (isLazy) {
3122 state.lazy[block] = true;
3123 }
3124
3125 if (state.uses.includes(block) || /props/.test(block) || /^Animated/.test(block) || 'React.Fragment' === block || 'ViewsModalOverlayContent' === block || block === finalName) return;
3126 state.uses.push(block);
3127 },
3128
3129 useIsBefore: view.parsed.view.useIsBefore,
3130 useIsMedia: view.parsed.view.useIsMedia
3131 }; // TIP: use the following code to trace generated code
3132 // let _push = state.render.push.bind(state.render)
3133 // state.render.push = item => {
3134 // _push(item)
3135 // if (item.includes("Some <isHovered")) {
3136 // console.trace()
3137 // }
3138 // }
3139
3140 if (state.data) {
3141 state.use('ViewsUseData');
3142 }
3143
3144 if (name !== finalName) {
3145 console.warn(`// ${name} is a Views reserved name. To fix this, change its file name to something else.`);
3146 }
3147
3148 state.fonts = view.parsed.fonts;
3149 state.slots = view.parsed.slots;
3150 walk(view.parsed.view, visitor, state);
3151 return {
3152 code: toComponent({
3153 getImport: makeGetImport({
3154 imports,
3155 getSystemImport,
3156 view,
3157 viewsById,
3158 viewsToFiles
3159 }),
3160 getStyles,
3161 name: finalName,
3162 state
3163 }),
3164 dependencies: state.dependencies,
3165 flow: state.flow,
3166 flowDefaultState: state.flowDefaultState // TODO flow supported states
3167 // fonts: view.parsed.fonts,
3168 // slots: view.parsed.slots,
3169
3170 };
3171});
3172
3173let BORDER_RADIUS = ['borderRadius', 'borderTopLeftRadius', 'borderTopRightRadius', 'borderBottomLeftRadius', 'borderBottomRightRadius'];
3174let enter$p = (node, parent, state) => {
3175 if (node.backgroundImage) {
3176 let source = getObjectAsString({
3177 uri: node.backgroundImage
3178 });
3179 let resizeMode = getProp(node, 'backgroundSize');
3180 resizeMode = safe(resizeMode ? resizeMode.value.value : 'cover');
3181 state.render.push(` resizeMode=${resizeMode} source={${source}}`); // hack until https://github.com/facebook/react-native/issues/8885
3182 // is fixed
3183
3184 BORDER_RADIUS.forEach(prop => {
3185 if (hasProp(node, prop)) {
3186 let propNode = getProp(node, prop);
3187 state.render.push(` ${prop}={${propNode.value.value}}`);
3188 }
3189 });
3190 }
3191};
3192
3193let keyboardType = {
3194 email: 'email-address',
3195 number: 'numeric',
3196 phone: 'phone-pad'
3197};
3198let enter$q = (node, parent, state) => {
3199 if (!node.isCapture) return;
3200
3201 if (node.name === 'CaptureTextArea') {
3202 state.render.push(` multiline={true}`);
3203 } else {
3204 let type = getProp(node, 'type'); // TODO warn on parser
3205 // TODO support file upload in RN
3206
3207 if (type.value === 'file') {
3208 type.value = 'text';
3209 }
3210
3211 if (type.value === 'secure') {
3212 state.render.push(` secureTextEntry`);
3213 } else {
3214 if (isSlot(type)) {
3215 let dynamicKeyboardType = `
3216 ${type.value} === 'email'?
3217 '${keyboardType.email}' :
3218 ${type.value} === 'number'?
3219 '${keyboardType.number}' :
3220 ${type.value} === 'phone'?
3221 '${keyboardType.phone}' : 'default'
3222 `;
3223 state.render.push(` keyboardType={${dynamicKeyboardType}}`);
3224 } else if (keyboardType[type.value]) {
3225 state.render.push(` keyboardType='${keyboardType[type.value]}'`);
3226 }
3227 }
3228 }
3229
3230 let autoCorrect = getProp(node, 'autoCorrect');
3231
3232 if (!autoCorrect) {
3233 state.render.push(` autoCorrect={false}`);
3234 }
3235
3236 let underlineColorAndroid = getProp(node, 'underlineColorAndroid');
3237
3238 if (!underlineColorAndroid) {
3239 state.render.push(` underlineColorAndroid="transparent"`);
3240 }
3241
3242 let textAlignVertical = getProp(node, 'textAlignVertical');
3243
3244 if (!textAlignVertical) {
3245 state.render.push(` textAlignVertical="top"`);
3246 }
3247};
3248
3249var getBlockName$1 = ((node, parent, state) => {
3250 switch (node.name) {
3251 case 'Capture':
3252 case 'CaptureTextArea':
3253 return 'TextInput';
3254
3255 case 'Horizontal':
3256 case 'Vertical':
3257 return getGroupBlockName$1(node, state);
3258
3259 case 'List':
3260 return getListBlockName(node, state);
3261
3262 case 'Text':
3263 if (node.isAnimated) {
3264 state.animated.add('Text');
3265 return 'AnimatedText';
3266 } else {
3267 return 'Text';
3268 }
3269
3270 case 'Block':
3271 case 'View':
3272 return 'React.Fragment';
3273
3274 default:
3275 return node.name;
3276 }
3277});
3278
3279let getGroupBlockName$1 = (node, state) => {
3280 let name = 'View';
3281
3282 if (node.isFragment) {
3283 name = 'React.Fragment';
3284 } else if (hasProp(node, 'goTo')) {
3285 node.goTo = true;
3286 let propNode = getProp(node, 'goTo');
3287 node.action = propNode.value;
3288 } else if (hasProp(node, 'onClick')) {
3289 let propNode = getProp(node, 'onClick');
3290 node.action = propNode.value;
3291 }
3292
3293 if (hasProp(node, 'backgroundImage')) {
3294 let propNode = getProp(node, 'backgroundImage');
3295 node.backgroundImage = isSlot(propNode) ? propNode.value : JSON.stringify(propNode.value);
3296 name = 'Image';
3297 } else if (hasProp(node, 'overflowY', v => v === 'auto' || v === 'scroll')) {
3298 name = 'ScrollView';
3299 }
3300
3301 if (node.isAnimated) {
3302 state.animated.add(name);
3303 name = `Animated${name}`;
3304 }
3305
3306 return name;
3307};
3308
3309let getListBlockName = (node, state) => {
3310 let base = hasProp(node, /^overflow/, v => v === 'auto' || v === 'scroll') ? 'FlatList' : 'View';
3311
3312 if (node.isAnimated) {
3313 state.animated.add(base);
3314 return `Animated${base}`;
3315 } else {
3316 return base;
3317 }
3318};
3319
3320function enter$r(node, parent, state) {
3321 // if (parent && !parent.isBasic && !node.isBasic) return true
3322 if (node.isFragment && node.children.length === 0) return true;
3323
3324 if (node.isChildren) {
3325 state.hasAlreadyDefinedChildren = true;
3326 state.render.push(`{typeof props.children === 'function'? props.children({ isSelected: props.isSelected`);
3327 let useIsHovered = !!getActionableParent(node);
3328
3329 if (useIsHovered) {
3330 state.useIsHovered = true;
3331 state.render.push(', isHovered, isSelectedHovered');
3332 }
3333
3334 state.render.push(`}) : null}`);
3335 return true;
3336 }
3337
3338 if (node.isFragment && node.name === 'View') {
3339 state.flow = getPropValueOrDefault(node, 'is', false);
3340 state.flowDefaultState = null;
3341 }
3342
3343 if (isStory(node, state)) {
3344 state.use('ViewsUseFlow');
3345 }
3346
3347 let name = getBlockName$1(node, parent, state);
3348 if (name === null) return true;
3349
3350 if (name === 'Animated.FlatList') {
3351 state.use('FlatList');
3352 name = 'AnimatedFlatList';
3353 }
3354
3355 state.use(node.isBasic ? name.replace(/^Animated/, '') : name, node.isLazy);
3356 node.nameFinal = name;
3357 if (handleTable(node, parent, state)) return true;
3358 state.render.push(`<${name}`);
3359}
3360
3361let leave$9 = (node, parent, state) => {
3362 if (node.isFragment) return;
3363 let dynamicStyles = getNonAnimatedDynamicStyles(node);
3364 let baseStyle = null;
3365 let animatedStyle = null;
3366 let dynamicStyle = null;
3367 let containerStyle = null;
3368
3369 if (node.ensureBackgroundColor && (!('backgroundColor' in node.style.static.base) || !('backgroundColor' in node.style.dynamic.base))) {
3370 node.style.static.base.backgroundColor = 'transparent';
3371 }
3372
3373 if (hasKeys(node.style.static.base)) {
3374 let id = createId(node, state);
3375
3376 if (node.nameFinal.includes('FlatList') && hasContentContainerStyleProp(node.style.static.base)) {
3377 state.styles[`${id}ContentContainer`] = getContentContainerStyleProps(node.style.static.base);
3378 node.style.static.base = removeContentContainerStyleProps(node.style.static.base);
3379 containerStyle = `styles.${id}ContentContainer`;
3380 }
3381
3382 if (hasKeys(node.style.static.base)) {
3383 state.styles[id] = node.style.static.base;
3384 baseStyle = `styles.${id}`;
3385 }
3386 }
3387
3388 if (node.isAnimated) {
3389 animatedStyle = getAnimatedStyles(node, state.isReactNative);
3390 state.isAnimated = true;
3391 state.animations[node.id] = node.animations;
3392
3393 if (node.hasSpringAnimation) {
3394 state.hasSpringAnimation = true;
3395 }
3396
3397 if (node.hasTimingAnimation) {
3398 state.hasTimingAnimation = true;
3399 }
3400
3401 state.scopes = node.scopes;
3402 }
3403
3404 if (hasKeys(dynamicStyles)) {
3405 if (node.nameFinal.includes('FlatList') && hasContentContainerStyleProp(dynamicStyles)) {
3406 let dynamicContainerStyle = getObjectAsString(getContentContainerStyleProps(dynamicStyles));
3407 dynamicStyles = removeContentContainerStyleProps(dynamicStyles);
3408 containerStyle = containerStyle ? `[${containerStyle},${dynamicContainerStyle}]` : dynamicContainerStyle;
3409 }
3410
3411 if (hasKeys(dynamicStyles)) {
3412 dynamicStyle = getObjectAsString(dynamicStyles);
3413 dynamicStyle = dynamicStyle.substr(1, dynamicStyle.length - 2);
3414 }
3415 }
3416
3417 if (baseStyle || animatedStyle || dynamicStyle) {
3418 let style = baseStyle;
3419
3420 if (animatedStyle) {
3421 // TODO once https://github.com/drcmda/react-spring/issues/337 gets
3422 // fixed, come back to using the array notation
3423 style = `{...${baseStyle},${animatedStyle},${dynamicStyle || ''}}`;
3424 } else if (dynamicStyle) {
3425 style = `[${baseStyle},{${dynamicStyle}}]`;
3426 }
3427
3428 state.render.push(` style={${style}}`);
3429 }
3430
3431 if (containerStyle) {
3432 state.render.push(` contentContainerStyle={${containerStyle}}`);
3433 }
3434};
3435
3436let blacklist$1 = ['appregion', 'backgroundimage', 'backgroundsize', 'classname', 'clippath', 'cursor', 'fontweight', 'format', 'goto', 'isdisabled', 'isfragment', 'onclickusediv', 'onsubmit', 'overflow', 'overflowx', 'overflowy', 'pagebreakinside', 'perspective', 'rotatez', 'scalez', 'shadowspread', 'texttransform', 'transformorigin', 'transformoriginz', 'transition', 'translatez', 'type', 'userselect'];
3437var isValidPropertyForBlock$1 = ((node, parent, state) => !blacklist$1.includes(node.name.toLowerCase()));
3438
3439function enter$s(node, parent, state) {
3440 if (node.isFragment) return false;
3441 enter$e(node, parent, state);
3442 enter$f(node, parent, state);
3443 enter$k(node, parent, state);
3444 node.properties.forEach(propNode => {
3445 if (propNode.name === 'lazy' || propNode.name === 'at' || propNode.name === 'when' || propNode.name === 'onWhen' || propNode.tags.unsupportedShorthand || !isValidPropertyForBlock$1(propNode) && node.isBasic || propNode.name === 'from' && (node.name === 'List' || node.name === 'Table') || propNode.name === 'key' && parent.isList || propNode.name === 'pass' && node.isList || propNode.name === 'opacity' && state.isSvg || propNode.name === 'width' && isColumn(node)) return;
3446 !enter$h(propNode, node, state) && !enter$i(propNode, node, state) && !enter$j(propNode, node, state) && enter$3(propNode, node, state);
3447 });
3448 leave$9(node, parent, state);
3449}
3450
3451let SVG_BLOCKS_WITH_OPACITY = ['SvgPath', 'SvgCircle', 'SvgEllipse', 'SvgPolygon', 'SvgPolyline', 'SvgRect', 'SvgText'];
3452function enter$t(node, parent, state) {
3453 if (node.name === 'Svg' && !parent) {
3454 state.isSvg = true;
3455 state.svgOpacity = getProp(node, 'opacity');
3456 } else if (state.isSvg && state.svgOpacity && SVG_BLOCKS_WITH_OPACITY.includes(node.name)) {
3457 if (hasProp(node, 'fill')) {
3458 node.properties.push({ ...state.svgOpacity,
3459 name: 'fillOpacity'
3460 });
3461 }
3462
3463 if (hasProp(node, 'stroke')) {
3464 node.properties.push({ ...state.svgOpacity,
3465 name: 'strokeOpacity'
3466 });
3467 }
3468 }
3469}
3470
3471let enter$u = (node, parent, state) => {
3472 let name = getBlockName$1(node, parent, state);
3473
3474 if (name === 'Text' && parent && (parent.backgroundImage || parent.ensureBackgroundColor)) {
3475 node.ensureBackgroundColor = true;
3476 }
3477
3478 if (node.action) {
3479 let block = 'TouchableWithoutFeedback';
3480 let isDisabled = getProp(node, 'isDisabled');
3481 let onClick = getProp(node, 'onClick');
3482 let onPress = wrap(onClick.value);
3483
3484 if (/^on[A-Z]/.test(onClick.name) && onClick.slotName === 'setFlowTo') {
3485 // TODO warn if action is used but it isn't in actions (on parser)
3486 // TODO warn that there's setFlowTo without an id (on parser)
3487 let setFlowTo = getFlowPath(onClick, parent, state);
3488 state.use('ViewsUseFlow');
3489 state.setFlowTo = true;
3490 onPress = `{() => setFlowTo('${setFlowTo}')}`;
3491 }
3492
3493 let key = getProp(node, 'key');
3494 state.use(block);
3495 state.render.push(`<${block}
3496 activeOpacity={0.7}
3497 onPress=${onPress}
3498 ${isDisabled ? `disabled=${wrap(isDisabled.value)}` : ''}
3499 underlayColor='transparent'
3500 ${node.isInList ? `key={${key ? key.value : 'index'}}` : ''}>`);
3501 node.wrapEnd = `</${block}>`;
3502 } else if (node.goTo) ;
3503};
3504let leave$a = (node, parent, state) => {
3505 if (node.wrapEnd) {
3506 state.render.push(node.wrapEnd);
3507 }
3508};
3509
3510let enter$v = [enter$9, enter$m, enter$b, enter$u, enter$r, enter$n, enter$4, enter$t, enter$q, enter$p, enter$7, enter, enter$s, enter$6, enter$8];
3511let leave$b = [leave$2, leave, leave$1, leave$3, leave$7, leave$a, leave$4];
3512
3513var visitor$1 = /*#__PURE__*/Object.freeze({
3514 __proto__: null,
3515 enter: enter$v,
3516 leave: leave$b
3517});
3518
3519function getStyleForProperty$1(node, parent, state, code) {
3520 let scopedCondition = getScopedCondition(node, parent, state);
3521
3522 if (scopedCondition) {
3523 switch (node.name) {
3524 case 'rotate':
3525 case 'rotateX':
3526 case 'rotateY':
3527 case 'scale':
3528 case 'scaleX':
3529 case 'scaleY':
3530 case 'translateX':
3531 case 'translateY':
3532 return {
3533 _isScoped: true,
3534 transform: getTransform$1(node, parent, state)
3535 };
3536
3537 default:
3538 return {
3539 _isScoped: true,
3540 [node.name]: getScopedCondition(node, parent, state)
3541 };
3542 }
3543 }
3544
3545 switch (node.name) {
3546 case 'borderTopLeftRadius':
3547 case 'borderTopRightRadius':
3548 case 'borderBottomLeftRadius':
3549 case 'borderBottomRightRadius':
3550 return {
3551 [parent.name === 'Image' ? 'borderRadius' : node.name]: node.value
3552 };
3553
3554 case 'borderTopStyle':
3555 case 'borderBottomStyle':
3556 case 'borderLeftStyle':
3557 case 'borderRightStyle':
3558 return {
3559 borderStyle: node.value
3560 };
3561
3562 case 'shadowColor':
3563 return decorateShadow({
3564 shadowColor: node.value
3565 });
3566
3567 case 'shadowBlur':
3568 return decorateShadow({
3569 shadowRadius: node.value
3570 });
3571
3572 case 'shadowOffsetX':
3573 case 'shadowOffsetY':
3574 return getShadowOffset(node, parent);
3575
3576 case 'fontWeight':
3577 case 'fontFamily':
3578 return {
3579 fontFamily: getFontFamily(node, parent)
3580 };
3581
3582 case 'rotate':
3583 case 'rotateX':
3584 case 'rotateY':
3585 case 'scale':
3586 case 'scaleX':
3587 case 'scaleY':
3588 case 'translateX':
3589 case 'translateY':
3590 return {
3591 transform: getTransform$1(node, parent, state)
3592 };
3593
3594 case 'zIndex':
3595 return {
3596 zIndex: code ? node.value : parseInt(node.value, 10)
3597 };
3598
3599 case 'color':
3600 // TODO handle this but differently as we don't have the placeholder tag anymore
3601 if (/Capture/.test(parent.name) && isTag(node, 'placeholder')) {
3602 return {
3603 _isProp: true,
3604 placeholderTextColor: node.value
3605 };
3606 } // Just returning the node.value in cases where if statement is not true
3607 // Otherwise it was falling through to the next case.
3608
3609
3610 return {
3611 color: node.value
3612 };
3613
3614 case 'lineHeight':
3615 return {
3616 lineHeight: getLineHeight(node, parent)
3617 };
3618
3619 default:
3620 return {
3621 [node.name]: maybeMakeHyphenated(node)
3622 };
3623 }
3624}
3625
3626let getFontFamily = (node, parent, state) => {
3627 let fontWeight = getProp(parent, 'fontWeight'); // let key = node.key.value
3628
3629 let fontFamily = node.value.replace(/\s/g, '');
3630
3631 if (fontWeight && (node.tags.slot || fontWeight.tags.slot)) {
3632 return `\`${node.tags.slot ? '${props.fontFamily}' : fontFamily}-${fontWeight.tags.slot ? '${props.fontWeight}' : fontWeight.value}\``;
3633 }
3634
3635 return fontWeight ? `${fontFamily}-${fontWeight.value}` : fontFamily;
3636};
3637
3638let getLineHeight = (node, parent, state) => {
3639 let fontSize = getProp(parent, 'fontSize'); // using a default font size of 16 if none specified
3640
3641 let fontSizeValue = fontSize ? fontSize.value : 16;
3642 return node.value * fontSizeValue;
3643};
3644
3645let getShadowOffset = (node, parent, state) => {
3646 let shadowOffsetX = getProp(parent, 'shadowOffsetX');
3647 let shadowOffsetY = getProp(parent, 'shadowOffsetY');
3648 return decorateShadow({
3649 // iOS,
3650 shadowOffset: {
3651 width: shadowOffsetX ? shadowOffsetX.value : 0,
3652 height: shadowOffsetY ? shadowOffsetY.value : 0
3653 }
3654 });
3655};
3656
3657let decorateShadow = obj => {
3658 obj.elevation = 1; // for Android
3659
3660 obj.shadowOpacity = 1;
3661 return obj;
3662};
3663
3664let getPropValue$2 = (prop, block, state, unit = '') => {
3665 if (!prop) return false;
3666 let scopedCondition = getScopedCondition(prop, block, state);
3667
3668 if (scopedCondition) {
3669 return unit ? `\`\${${scopedCondition}}${unit}\`` : scopedCondition;
3670 }
3671
3672 if (prop.tags.slot) {
3673 return `\${${prop.value}}${unit}`;
3674 }
3675
3676 return typeof prop.value === 'number' && unit ? `${prop.value}${unit}` : prop.value;
3677};
3678
3679let getTransformValue$1 = (prop, parent, state, unit) => prop && {
3680 [prop.name]: getPropValue$2(prop, parent, state, unit)
3681};
3682
3683let getTransform$1 = (node, parent, state) => {
3684 let rotate = getProp(parent, 'rotate');
3685 let rotateX = getProp(parent, 'rotateX');
3686 let rotateY = getProp(parent, 'rotateY');
3687 let scale = getProp(parent, 'scale');
3688 let scaleX = getProp(parent, 'scaleX');
3689 let scaleY = getProp(parent, 'scaleY');
3690 let translateX = getProp(parent, 'translateX');
3691 let translateY = getProp(parent, 'translateY');
3692 return [getTransformValue$1(rotate, parent, state, 'deg'), getTransformValue$1(rotateX, parent, state, 'deg'), getTransformValue$1(rotateY, parent, state, 'deg'), getTransformValue$1(scale, parent, state), getTransformValue$1(scaleX, parent, state), getTransformValue$1(scaleY, parent, state), getTransformValue$1(translateX, parent, state), getTransformValue$1(translateY, parent, state)].filter(Boolean);
3693};
3694
3695var getStyles$1 = (({
3696 styles
3697}) => hasKeys(styles) ? `let styles = StyleSheet.create(${JSON.stringify(styles)})` : '');
3698
3699let isUrl$1 = str => /^https?:\/\//.test(str);
3700
3701function getImageSource$1(node, parent, state) {
3702 let scopes = getScopes(node, parent);
3703
3704 if (scopes && (isUrl$1(node.value) || node.tags.slot)) {
3705 return `{{ uri: ${getScopedCondition(node, parent, state)} }}`;
3706 } else if (isUrl$1(node.value) || node.tags.slot) {
3707 if (node.defaultValue && !isUrl$1(node.defaultValue)) {
3708 state.slots.forEach(item => {
3709 if (item.defaultValue === node.defaultValue) {
3710 item.type = 'import';
3711 let name = toCamelCase(item.defaultValue);
3712
3713 if (!state.images.includes(item.defaultValue)) {
3714 state.images.push({
3715 name,
3716 file: item.defaultValue
3717 });
3718 }
3719
3720 item.defaultValue = name;
3721 }
3722 });
3723 }
3724
3725 return `{{ uri: ${node.tags.slot ? node.value : safe(node.value)} }}`;
3726 } else {
3727 if (scopes) {
3728 pushImageToState(state, scopes.scopedNames, scopes.paths);
3729 }
3730
3731 let name = toCamelCase(node.value);
3732
3733 if (!state.images.includes(node.value)) {
3734 state.images.push({
3735 name,
3736 file: node.value
3737 });
3738 }
3739
3740 return scopes ? wrap(getScopedImageCondition(scopes.scopedProps, scopes.scopedNames, name)) : `{${name}}`;
3741 }
3742}
3743
3744let CHILD_VALUES$3 = /props\.(isSelected|isHovered|isFocused|isSelectedHovered)/;
3745let ON_IS_SELECTED$1 = /(onClick|onPress|goTo)/;
3746function getValueForProperty$1(node, parent, state) {
3747 if (state.data && (node.value === '!props.value' || node.value === 'props.value' || node.value === 'props.onSubmit' || node.value === 'props.onChange' || node.value === 'props.isInvalid' || node.value === 'props.isInvalidInitial' || node.value === 'props.isValid' || node.value === 'props.isValidInitial' || node.value === 'props.onSubmit')) {
3748 return {
3749 [node.name]: `{${node.value.replace('props.', 'data.')}}`
3750 };
3751 } else if (hasCustomBlockParent(parent) && CHILD_VALUES$3.test(node.value)) {
3752 return {
3753 [node.name]: `{${node.value.replace('props.', 'childProps.')}}`
3754 };
3755 } else if (isValidImgSrc(node, parent)) {
3756 return !parent.isSvg && {
3757 source: getImageSource$1(node, parent, state)
3758 };
3759 } else if (getScopedCondition(node, parent, state)) {
3760 return {
3761 [node.name]: safe(getScopedCondition(node, parent, state))
3762 };
3763 } else if (/^on[A-Z]/.test(node.name) && node.slotName === 'setFlowTo') {
3764 let flowPath = getFlowPath(node, parent, state);
3765 state.use('ViewsUseFlow');
3766 state.setFlowTo = true;
3767 let ret = {
3768 [node.name]: `{() => setFlowTo('${flowPath}')}`
3769 };
3770
3771 if (!parent.isBasic && ON_IS_SELECTED$1.test(node.name)) {
3772 state.useFlow = true;
3773 ret[node.name.replace(ON_IS_SELECTED$1, 'isSelected')] = `{flow.has('${flowPath}')}`;
3774 }
3775
3776 if (parent.isBasic && ON_IS_SELECTED$1.test(node.name)) {
3777 state.useIsHovered = true;
3778 ret['...'] = `${node.name.replace(ON_IS_SELECTED$1, 'isHovered')}Bind`;
3779 }
3780
3781 return ret;
3782 } else if (parent.isBasic && ON_IS_SELECTED$1.test(node.name)) {
3783 state.useIsHovered = true;
3784 return {
3785 '...': `${node.name.replace(ON_IS_SELECTED$1, 'isHovered')}Bind`,
3786 [node.name]: safe(node.value, node)
3787 };
3788 } else if (CHILD_VALUES$3.test(node.value) && !!getActionableParent(parent) && !parent.isBasic) {
3789 // TODO support more than one hover/selected in the same block - let's wait
3790 // for a use case
3791 if (/isHovered/.test(node.value)) {
3792 return {
3793 [node.name]: safe(node.value.replace('props.', ''), node)
3794 };
3795 } else if (/isSelected/.test(node.value)) {
3796 try {
3797 let actionableParent = getActionableParent(parent);
3798 let flowPath = getFlowPath(getProp(actionableParent, 'onClick'), actionableParent, state);
3799 state.use('ViewsUseFlow');
3800 state.useFlow = true;
3801 return {
3802 [node.name]: `{flow.has('${flowPath}')}`
3803 };
3804 } catch (error) {
3805 return {
3806 [node.name]: safe(node.value, node)
3807 };
3808 }
3809 } else {
3810 return {
3811 [node.name]: safe(node.value, node)
3812 };
3813 }
3814 } else {
3815 return {
3816 [node.name]: safe(node.value, node)
3817 };
3818 }
3819}
3820
3821var maybeUsesStyleSheet = (state => {
3822 if (Object.keys(state.styles).length > 0) {
3823 state.uses.push('StyleSheet');
3824 }
3825});
3826
3827let imports$1 = {
3828 DismissKeyboard: `import dismissKeyboard from 'dismissKeyboard'`
3829};
3830var reactNative = (({
3831 getFontImport,
3832 getSystemImport,
3833 reactNativeLibraryImport = 'react-native',
3834 tools,
3835 view,
3836 viewsById,
3837 viewsToFiles
3838}) => {
3839 let name = view.id;
3840 let finalName = restrictedNames.includes(name) ? `${name}1` : name;
3841
3842 if (name !== finalName) {
3843 console.warn(`// "${name}" is a Views reserved name.
3844 We've renamed it to "${finalName}", so your view should work but this isn't ideal.
3845 To fix this, change its file name to something else.`);
3846 }
3847
3848 let state = {
3849 animations: {},
3850 animated: new Set(),
3851 images: [],
3852 data: view.parsed.view.data,
3853 dataFormat: view.parsed.view.dataFormat,
3854 dataValidate: view.parsed.view.dataValidate,
3855 dependencies: new Set(),
3856 flow: null,
3857 setFlowTo: false,
3858 getFontImport: font => getFontImport(font, view),
3859 getStyleForProperty: getStyleForProperty$1,
3860 getValueForProperty: getValueForProperty$1,
3861 hasRefs: false,
3862 isReactNative: true,
3863 isStory: id => {
3864 let viewInView = getViewRelativeToView({
3865 id,
3866 view,
3867 viewsById,
3868 viewsToFiles
3869 });
3870 return viewInView && !viewInView.custom && viewInView.parsed.view.isStory;
3871 },
3872 lazy: {},
3873 name: finalName,
3874 viewPath: view.parsed.view.viewPath,
3875 viewPathParent: view.parsed.view.viewPathParent,
3876 remap: {},
3877 render: [],
3878 styles: {},
3879 testIdKey: 'testID',
3880 viewPathKey: 'testID',
3881 testIds: {},
3882 tools,
3883 reactNativeLibraryImport,
3884 usedBlockNames: {
3885 [finalName]: 1,
3886 AutoSizer: 1,
3887 Column: 1,
3888 Table: 1
3889 },
3890 uses: [],
3891
3892 use(block, isLazy = false) {
3893 if (isLazy) {
3894 state.lazy[block] = true;
3895 }
3896
3897 if (state.uses.includes(block) || /props/.test(block) || 'React.Fragment' === block || block === finalName) return;
3898 state.uses.push(block);
3899 },
3900
3901 useIsBefore: view.parsed.view.useIsBefore,
3902 useIsMedia: view.parsed.view.useIsMedia
3903 };
3904 state.fonts = view.parsed.fonts;
3905 state.slots = view.parsed.slots;
3906 walk(view.parsed.view, visitor$1, state);
3907 maybeUsesStyleSheet(state);
3908
3909 if (state.data) {
3910 state.use('ViewsUseData');
3911 }
3912
3913 return {
3914 code: toComponent({
3915 getImport: makeGetImport({
3916 imports: imports$1,
3917 getSystemImport,
3918 view,
3919 viewsById,
3920 viewsToFiles
3921 }),
3922 getStyles: getStyles$1,
3923 name: finalName,
3924 state
3925 }),
3926 dependencies: state.dependencies,
3927 flow: state.flow,
3928 flowDefaultState: state.flowDefaultState // fonts: view.parsed.fonts,
3929 // slots: view.parsed.slots,
3930
3931 };
3932});
3933
3934var reactPdf = (options => reactNative({ ...options,
3935 reactNativeLibraryImport: '@react-pdf/renderer'
3936}));
3937
3938var morphers = {
3939 'react-dom': reactDom,
3940 'react-native': reactNative,
3941 'react-pdf': reactPdf
3942};
3943
3944function maybeMorph({
3945 as,
3946 getFontImport,
3947 getSystemImport,
3948 tools,
3949 view,
3950 viewsById,
3951 viewsToFiles,
3952 verbose
3953}) {
3954 let result = null;
3955
3956 try {
3957 result = morphers[as]({
3958 getFontImport,
3959 getSystemImport,
3960 tools,
3961 view,
3962 viewsById,
3963 viewsToFiles
3964 });
3965 view.version++;
3966 verbose && console.log(`${chalk.green('M')} ${view.id}@${view.version}:${chalk.dim(view.file)}`);
3967 return result.code; // return prettier.format(result.code, {
3968 // parser: 'babel',
3969 // singleQuote: true,
3970 // trailingComma: 'es5',
3971 // })
3972 } catch (error) {
3973 console.error(chalk.red('M'), view, error.codeFrame || error);
3974 return `import { useEffect } from 'react'
3975
3976export default function ${view.id}() {
3977 useEffect(() => {
3978 console.error({
3979 type: 'morph',
3980 view: '${view.id}',
3981 file: '${view.file}',
3982 todo: "Report to https://github.com/viewstools/morph/issues/new with .view and .view.js files and what changed when it failed. This will help us improve the morpher. Thanks!",
3983 })
3984 }, [])
3985
3986 return "😳 Can't morph '${view.id}'. See console for more details."
3987}
3988
3989/*
3990>>> CODE
3991${result && result.code.replace(/(\/\*|\*\/)/g, '')}
3992
3993
3994>>> ERROR
3995${error.message}
3996${error.stack}
3997*/`;
3998 }
3999}
4000
4001function morphAllViews({
4002 as,
4003 getFontImport,
4004 getSystemImport,
4005 filesView,
4006 tools,
4007 viewsById,
4008 viewsToFiles
4009}) {
4010 return [...filesView].map(file => viewsToFiles.get(file)).filter(view => !view.custom).map(view => ({
4011 file: path.join(path.dirname(view.file), 'view.js'),
4012 content: maybeMorph({
4013 as,
4014 getFontImport,
4015 getSystemImport,
4016 tools,
4017 verbose: false,
4018 view,
4019 viewsById,
4020 viewsToFiles
4021 })
4022 }));
4023}
4024
4025function maybePrintWarnings(view, verbose) {
4026 if (!verbose || !view.parsed || view.parsed.warnings.length === 0) return;
4027 console.error(chalk.red(view.id), chalk.dim(view.file));
4028 view.parsed.warnings.forEach(warning => {
4029 console.error(` ${chalk.yellow(warning.loc.start.line)}: ${chalk.blue(warning.type)} Line: "${warning.line}"`);
4030 });
4031 console.log('');
4032}
4033
4034let STYLE = ['alignContent', 'alignItems', 'alignSelf', 'appRegion', 'background', 'backgroundColor', 'backgroundImage', 'backgroundPosition', 'backgroundRepeat', 'backgroundSize', 'backdropFilter', 'borderBottomColor', 'borderBottomLeftRadius', 'borderBottomRightRadius', 'borderBottomStyle', 'borderBottomWidth', 'borderColor', 'borderLeftColor', 'borderLeftStyle', 'borderLeftWidth', 'borderRightColor', 'borderRightStyle', 'borderRightWidth', 'borderStyle', 'borderTopColor', 'borderTopLeftRadius', 'borderTopRightRadius', 'borderTopStyle', 'borderTopWidth', 'borderWidth', 'bottom', 'clipPath', 'color', 'cursor', 'display', 'direction', 'fill', 'filter', 'flexGrow', 'flexBasis', 'flexDirection', 'flexFlow', 'flexShrink', 'flexWrap', 'fontFamily', 'fontSize', 'fontStyle', 'fontWeight', 'height', 'hyphens', 'justifyContent', 'left', 'letterSpacing', 'lineHeight', 'marginBottom', 'marginLeft', 'marginRight', 'marginTop', 'maxHeight', 'maxWidth', 'minHeight', 'minWidth', 'opacity', 'outlineColor', 'outlineOffset', 'outlineStyle', 'outlineWidth', 'overflowWrap', 'overflowX', 'overflowY', 'paddingBottom', 'paddingLeft', 'paddingRight', 'paddingTop', 'pointerEvents', 'position', 'resize', 'right', 'shadowColor', 'shadowOffsetX', 'shadowOffsetY', 'shadowBlur', 'shadowSpread', 'shadowInset', 'stroke', 'textAlign', 'textDecoration', 'textOverflow', 'textJustify', 'textTransform', 'top', 'translateX', 'translateY', 'scale', 'scaleX', 'scaleY', 'rotate', 'rotateX', 'rotateY', 'transformOriginX', 'transformOriginY', 'transition', 'unicodeBidi', 'userSelect', 'whiteSpace', 'width', 'wordBreak', 'wordSpacing', 'wordWrap', 'zIndex', // '@fontFace',
4035// '@keyframes',
4036// '@media',
4037// 'background',
4038// 'backgroundAttachment',
4039// 'backgroundBlend-mode',
4040// 'backgroundClip',
4041// 'backgroundOrigin',
4042// 'backgroundPosition',
4043// 'backgroundRepeat',
4044// 'border',
4045// 'borderBottom',
4046// 'borderCollapse',
4047// 'borderImage',
4048// 'borderImageOutset',
4049// 'borderImageRepeat',
4050// 'borderImageSlice',
4051// 'borderImageSource',
4052// 'borderImageWidth',
4053// 'borderLeft',
4054// 'borderRadius',
4055// 'borderRight',
4056// 'borderSpacing',
4057// 'borderTop',
4058// 'boxShadow',
4059// 'boxSizing',
4060// 'captionSide',
4061// 'clear',
4062// 'clip',
4063// 'column-gap',
4064// 'columnCount',
4065// 'columnFill',
4066// 'columnRule',
4067// 'columnRuleColor',
4068// 'columnRuleStyle',
4069// 'columnRuleWidth',
4070// 'columnSpan',
4071// 'columnWidth',
4072// 'columns',
4073// 'content',
4074// 'counterIncrement',
4075// 'counterReset',
4076// 'direction',
4077// 'display',
4078// 'emptyCells',
4079// 'float',
4080// 'font',
4081// 'font-variant',
4082// 'fontSizeAdjust',
4083// 'fontStretch',
4084// 'hangingPunctuation',
4085// 'listStyle',
4086// 'listStyleImage',
4087// 'listStylePosition',
4088// 'listStyleType',
4089// 'margin',
4090// 'nav-up',
4091// 'navDown',
4092// 'navIndex',
4093// 'navLeft',
4094// 'navRight',
4095'order', // 'outline',
4096// 'overflow',
4097// 'padding',
4098// 'pageBreakAfter',
4099// 'pageBreakBefore',
4100// 'pageBreakInside',
4101'perspective', 'perspectiveOrigin', // 'quotes',
4102// 'tabSize',
4103// 'tableLayout',
4104// 'textAlignLast',
4105'textDecorationColor', 'textDecorationLine', 'textDecorationStyle', // 'textIndent',
4106// 'textShadow',
4107// 'transformStyle',
4108// 'transitionDelay',
4109// 'transitionDuration',
4110// 'transitionProperty',
4111// 'transitionTimingFunction',
4112// 'unicodeBidi',
4113// 'verticalAlign',
4114'visibility'];
4115let ROWSTYLE = ['rowBackgroundColor', 'rowBackgroundColorAlternate', 'rowColor', 'rowColorAlternate'];
4116let isStyle$1 = name => STYLE.includes(name);
4117let isRowStyle = name => ROWSTYLE.includes(name);
4118
4119/**
4120 * Creates a fuzzy matcher.
4121 *
4122 * Usage:
4123 *
4124 * ```
4125 * var m = new Matcher({
4126 * values: 'init install update upgrade',
4127 * threshold: 4
4128 * });
4129 *
4130 * m.list('udpate') // [ { value: 'update', distance: 2 }, { value: 'upgrade', distance: 4 } ]
4131 * m.get('udpate') // { value: 'update', distance: 2 }
4132 * ```
4133 *
4134 * You can also initialize with an array or with a string directly:
4135 *
4136 * ```
4137 * new Matcher('init install update upgrade');
4138 * new Matcher(['init', 'install', 'update', 'upgrade']);
4139 * ```
4140 *
4141 * @param options {Object} may contain:
4142 *
4143 * * `threshold` (number, default `2`) — maximum search distance, increase
4144 * for more relaxed search
4145 *
4146 * * `values` (array or string, default `[]`) — an array of words to match from;
4147 * if a string is given, values can be separated with comma or whitespace
4148 *
4149 * * `caseSensitive` (boolean, default `false`) — ignore case when matching
4150 */
4151
4152var Matcher = function (options) {
4153 options = options || {};
4154 if (typeof options == 'string' || Array.isArray(options)) options = {
4155 values: options
4156 };
4157 this.values = [];
4158 this.threshold = options.threshold || 2; // Try to initialize via `options.values`
4159
4160 if (Array.isArray(options.values)) this.values = options.values;else if (typeof options.values == 'string') this.values = options.values.split(/[, ]\s*/g);
4161 this.caseSensitive = options.caseSensitive || false;
4162};
4163/**
4164 * Adds values to the dictionary.
4165 *
4166 * Usage:
4167 *
4168 * ```
4169 * new Matcher().add('update', 'upgrade', 'delete').get('deete')
4170 * ```
4171 *
4172 * @returns {Matcher} this for chaining
4173 */
4174
4175
4176Matcher.prototype.add = function () {
4177 [].push.apply(this.values, arguments);
4178 return this;
4179};
4180/**
4181 * Chainable helper for settings `this.caseSensitive = false`.
4182 *
4183 * @returns {Matcher} this for chaining
4184 */
4185
4186
4187Matcher.prototype.ignoreCase = function () {
4188 this.caseSensitive = false;
4189 return this;
4190};
4191/**
4192 * Chainable helper for settings `this.caseSensitive = true`.
4193 *
4194 * @returns {Matcher} this for chaining
4195 */
4196
4197
4198Matcher.prototype.matchCase = function () {
4199 this.caseSensitive = true;
4200 return this;
4201};
4202/**
4203 * Chainable helper for settings `this.threshold`.
4204 *
4205 * @param num {Number} new threshold
4206 * @returns {Matcher} this for chaining
4207 */
4208
4209
4210Matcher.prototype.setThreshold = function (num) {
4211 this.threshold = num;
4212 return this;
4213};
4214/**
4215 * Calculate distance (how much difference) between two strings
4216 *
4217 * @param word1 {String} a string input
4218 * @param word2 {String} the other string input
4219 * @returns {Number} Levenshtein distance between word1 and word2
4220 */
4221
4222
4223Matcher.prototype.distance = function (word1, word2) {
4224 return new Levenshtein(word1, word2).distance;
4225};
4226/**
4227 * Lists all results from dictionary which are similar to `q`.
4228 *
4229 * ```
4230 * new Matcher({
4231 * values: 'init install update upgrade',
4232 * threshold: 4
4233 * }).list('udpdate');
4234 * // [ { value: 'update', distance: 2 }, { value: 'upgrade', distance: 4 } ]
4235 * ```
4236 *
4237 * The results are sorted by their distance (similarity). The distance of `0` means
4238 * a strict match.
4239 *
4240 * You can increase `threshold` for more relaxed search, or decrease it to shorten the results.
4241 *
4242 * @param q {String} — search string
4243 * @returns {Array} — an array of objects `{ value: String, distance: Number }`
4244 */
4245
4246
4247Matcher.prototype.list = function (q) {
4248 var m = this;
4249 q = q.trim();
4250 if (!m.caseSensitive) q = q.toLowerCase();
4251 var matches = this.values.reduce(function (results, word) {
4252 var d = m.distance(q, m.caseSensitive ? word : word.toLowerCase());
4253 if (d > m.threshold) return results;
4254 return results.concat({
4255 value: word,
4256 distance: d
4257 });
4258 }, []);
4259 return sortBy(matches, 'distance');
4260};
4261/**
4262 * Returns a single closest match (or `null` if no values from the dictionary
4263 * are similar to `q`).
4264 *
4265 * @param q {String} — search string
4266 * @returns {String} — closest match or `null`
4267 */
4268
4269
4270Matcher.prototype.get = function (q) {
4271 var closest = this.list(q)[0];
4272 return closest ? closest.value : null;
4273};
4274
4275var isNumber = {
4276 width: true,
4277 maxWidth: true,
4278 minWidth: true,
4279 height: true,
4280 maxHeight: true,
4281 minHeight: true,
4282 margin: true,
4283 marginBottom: true,
4284 marginLeft: true,
4285 marginRight: true,
4286 marginTop: true,
4287 padding: true,
4288 paddingBottom: true,
4289 paddingLeft: true,
4290 paddingRight: true,
4291 paddingTop: true,
4292 bottom: true,
4293 left: true,
4294 right: true,
4295 top: true,
4296 outline: true,
4297 opacity: true,
4298 zIndex: true,
4299 shadowOffsetY: true,
4300 shadowOffsetX: true,
4301 shadowSpread: true,
4302 shadowRadius: true,
4303 perspective: true,
4304 scale: true,
4305 scaleX: true,
4306 scaleY: true,
4307 translateX: true,
4308 translateY: true,
4309 borderBottomLeftRadius: true,
4310 borderBottomRightRadius: true,
4311 borderBottomWidth: true,
4312 borderLeftWidth: true,
4313 borderRadius: true,
4314 borderRightWidth: true,
4315 borderTopLeftRadius: true,
4316 borderTopRightRadius: true,
4317 borderTopWidth: true,
4318 borderWidth: true,
4319 fontSize: true,
4320 fontWeight: true,
4321 letterSpacing: true,
4322 lineHeight: true,
4323 wordSpacing: true
4324};
4325
4326let dymPropMatcher = new Matcher([...STYLE, 'at', 'className', 'cx', 'cy', 'd', 'data', 'xdata', 'dataFormat', 'defaultValue', 'fill', 'from', 'direction', 'id', 'is', 'key', 'maxLength', 'max', 'min', 'onBlur', 'onChange', 'onClick', 'onClickUseDiv', 'onDrag', 'onDragEnd', 'onDragEnter', 'onDragExit', 'onDragLeave', 'onDragOver', 'onDragStart', 'onDrop', 'onFocus', 'onFocus', 'onMouseDown', 'onMouseEnter', 'onMouseLeave', 'onMouseMove', 'onMouseOver', 'onMouseUp', 'onWheel', 'onWhen', 'order', 'r', 'ref', 'rx', 'ry', 'step', 'stroke', 'stroke', 'strokeLinecap', 'strokeLinejoin', 'strokeMiterlimit', 'strokeWidth', 'tabIndex', 'text', 'type', 'value', 'viewBox', 'when', 'x', 'x1', 'x2', 'y', 'y1', 'y2']);
4327let makeDidYouMeanBlock = views => {
4328 let dymBlockMatcher = new Matcher('Block|Capture|CaptureTextArea|Children|Horizontal|Image|List|Svg|SvgCircle|SvgEllipse|SvgDefs|SvgGroup|SvgLinearGradient|SvgRadialGradient|SvgLine|SvgPath|SvgPolygon|SvgPolyline|SvgRect|SvgSymbol|SvgText|SvgUse|SvgStop|Text|View|Vertical'.split('|').concat(views));
4329 return block => dymBlockMatcher.get(block);
4330};
4331let didYouMeanProp = prop => dymPropMatcher.get(prop);
4332let makeDidYouMeanFontFamily = customFonts => {
4333 let dymFontFamilyMatcher = new Matcher(fontFamily.concat(customFonts));
4334 return family => dymFontFamilyMatcher.get(family);
4335};
4336let ANIMATION = /(.+)(?:\s)(spring|linear|easeOut|easeInOut|easeIn|ease)(?:\s?(.*)?)/;
4337let BASIC = /^(Block|Capture|CaptureTextArea|Children|Column|Horizontal|Image|List|Svg|SvgCircle|SvgEllipse|SvgDefs|SvgGroup|SvgLinearGradient|SvgRadialGradient|SvgLine|SvgPath|SvgPolygon|SvgPolyline|SvgRect|SvgSymbol|SvgText|SvgUse|SvgStop|Table|Text|View|Vertical)$/i;
4338let BLOCK = /^(\s*)([A-Z][a-zA-Z0-9]*)(\s+([A-Z][a-zA-Z0-9]*))?$/;
4339let BOOL = /^(false|true)$/i;
4340let CAPTURE = /^(Capture|CaptureTextArea)$/i;
4341let CAPTURE_TYPES = ['email', 'file', 'number', 'phone', 'secure', 'text'];
4342let COMMENT = /^#(.+)$/;
4343let FLOAT = /^-?[0-9]+\.[0-9]+$/;
4344let FONTABLE = /^(Capture|CaptureTextArea|Text)$/;
4345let INT = /^-?[0-9]+$/;
4346let NOT_GROUP = /^(Image|Text|SvgCircle|SvgEllipse|SvgLine|SvgPath|SvgPolygon|SvgPolyline|SvgRect|SvgText|SvgStop)$/i;
4347let PROP = /^([a-z][a-zA-Z0-9]*)(\s+(.+))?$/;
4348let UNSUPPORTED_SHORTHAND = {
4349 border: ['borderWidth', 'borderStyle', 'borderColor'],
4350 borderBottom: ['borderBottomWidth', 'borderBottomStyle', 'borderBottomColor'],
4351 borderTop: ['borderTopWidth', 'borderTopStyle', 'borderTopColor'],
4352 borderRight: ['borderRightWidth', 'borderRightStyle', 'borderRightColor'],
4353 borderLeft: ['borderLeftWidth', 'borderLeftStyle', 'borderLeftColor'],
4354 borderRadius: ['borderTopLeftRadius', 'borderTopRightRadius', 'borderBottomLeftRadius', 'borderBottomRightRadius'],
4355 boxShadow: ['shadowOffsetX', 'shadowOffsetY', 'shadowBlur', 'shadowSpread', 'shadowColor'],
4356 flex: ['flexGrow', 'flexShrink', 'flexBasis'],
4357 margin: ['marginTop', 'marginRight', 'marginBottom', 'marginLeft'],
4358 padding: ['paddingTop', 'paddingRight', 'paddingBottom', 'paddingLeft'],
4359 textShadow: ['shadowOffsetX', 'shadowOffsetY', 'shadowBlur', 'shadowColor'],
4360 outline: ['outlineWidth', 'outlineStyle', 'outlineColor'],
4361 overflow: ['overflowX', 'overflowY'],
4362 transform: ['rotate', 'rotateX', 'rotateY', 'scale', 'translateX', 'translateY'],
4363 transformOrigin: ['transformOriginX', 'transformOriginY']
4364};
4365let TRUE = /^true$/i;
4366let USER_COMMENT = /^##(.*)$/; // TODO slot can't start with a number
4367
4368let SLOT = /^<((!)?([a-zA-Z0-9]+))?(\s+(.+))?$/;
4369let is = (thing, line) => thing.test(line);
4370let isAnimation = line => is(ANIMATION, line);
4371let isBasic = line => is(BASIC, line);
4372let isBlock = line => is(BLOCK, line);
4373let isBool = line => is(BOOL, line);
4374let isCapture = line => is(CAPTURE, line);
4375let isColumn$1 = line => line === 'Column';
4376let isComment = line => is(COMMENT, line);
4377let isEmptyText = line => line === '';
4378let isFloat = line => is(FLOAT, line);
4379let isFragment = line => line === 'isFragment';
4380let isFontable = line => is(FONTABLE, line);
4381let isGroup = line => !is(NOT_GROUP, line) && !isCapture(line);
4382let isList$1 = line => line === 'List';
4383let isInt = line => is(INT, line);
4384let isProp = line => is(PROP, line);
4385let isTable$1 = line => line === 'Table';
4386let isUnsupportedShorthand = name => name in UNSUPPORTED_SHORTHAND;
4387let isTrue = line => is(TRUE, line);
4388let isUserComment = line => is(USER_COMMENT, line);
4389
4390let get = (regex, line) => line.match(regex);
4391
4392let addDefaults = (animationType, properties) => {
4393 // if (!properties.delay) {
4394 // properties.delay = 0
4395 // }
4396 if (animationType !== 'spring' && !properties.duration) {
4397 properties.duration = 150;
4398 } else if (animationType === 'spring') {
4399 if (!properties.tension) {
4400 properties.tension = 170;
4401 }
4402
4403 if (!properties.friction) {
4404 properties.friction = 26;
4405 }
4406 }
4407
4408 return properties;
4409};
4410
4411let getAnimation = line => {
4412 // eslint-disable-next-line
4413 let [_, defaultValue, animationType, animationValues] = get(ANIMATION, line);
4414 let properties = {
4415 curve: animationType
4416 };
4417
4418 if (animationValues) {
4419 let values = animationValues.split(' ');
4420
4421 for (let i = 0; i < values.length; i = i + 2) {
4422 properties[values[i]] = getValue(values[i + 1]);
4423 }
4424 }
4425
4426 addDefaults(animationType, properties);
4427 return {
4428 id: Object.keys(properties).sort().map(key => `${key}${properties[key]}`).join(''),
4429 defaultValue: getValue(defaultValue),
4430 properties
4431 };
4432};
4433let getBlock = line => {
4434 // eslint-disable-next-line
4435 let [_, indentation, is, _1, block] = get(BLOCK, line);
4436 return {
4437 block: block || is,
4438 is: block ? is : null,
4439 level: Math.floor(indentation.length / 2)
4440 };
4441};
4442let getComment = line => {
4443 try {
4444 return get(COMMENT, line).slice(1);
4445 } catch (err) {
4446 return '';
4447 }
4448};
4449let getData = maybeProp => {
4450 if (!maybeProp) return null;
4451 let match = maybeProp.value.match(/^(show|capture)\s+(.+)$/);
4452 if (!match) return null;
4453 let path = match[2];
4454 let [context = null] = /\./.test(path) ? path.split('.') : [path];
4455 return {
4456 type: match[1],
4457 path,
4458 context
4459 };
4460};
4461let getDataFormat = maybeProp => {
4462 if (!maybeProp || !maybeProp.value) return null;
4463 let [formatIn, formatOut] = maybeProp.value.split(' ');
4464 return {
4465 type: 'js',
4466 formatIn,
4467 formatOut
4468 };
4469};
4470let getDataValidate = maybeProp => {
4471 if (!maybeProp || !maybeProp.value) return null;
4472 let [value, required] = maybeProp.value.split(' ');
4473 return {
4474 type: 'js',
4475 value,
4476 required: required === 'required'
4477 };
4478};
4479let getFormat = line => {
4480 let properties = {};
4481 let values = line.split(' ');
4482 let formatKey = values[0];
4483
4484 if (values.length === 2) {
4485 properties[formatKey] = values[1];
4486 } else {
4487 properties[formatKey] = {};
4488
4489 for (let i = 1; i < values.length; i = i + 2) {
4490 properties[formatKey][values[i]] = getValue(values[i + 1]);
4491 }
4492 }
4493
4494 return properties;
4495};
4496let getProp$1 = line => {
4497 // eslint-disable-next-line
4498 let [_, name, _1, value = ''] = get(PROP, line);
4499 let prop = {
4500 name,
4501 isSlot: false,
4502 value
4503 };
4504
4505 if (is(SLOT, value)) {
4506 let [// eslint-disable-next-line
4507 _2, slotIsNot = false, slotName = '', // eslint-disable-next-line
4508 _3, // eslint-disable-next-line
4509 defaultValue = ''] = getSlot(value);
4510 prop.isSlot = true;
4511 prop.slotIsNot = slotIsNot === '!';
4512 prop.slotName = slotName;
4513 prop.value = defaultValue || value;
4514 }
4515
4516 return prop;
4517};
4518let getSlot = line => get(SLOT, line).slice(1);
4519let getUnsupportedShorthandExpanded = (name, value) => {
4520 let props = UNSUPPORTED_SHORTHAND[name];
4521
4522 if (name === 'borderRadius') {
4523 let theValue = value.replace('px', '');
4524 return [`${props[0]} ${theValue}`, `${props[1]} ${theValue}`, `${props[2]} ${theValue}`, `${props[3]} ${theValue}`];
4525 } else if (name.startsWith('border') || name === 'outline') {
4526 let [width, style, ...color] = value.split(' ');
4527 return [`${props[0]} ${width.replace('px', '')}`, `${props[1]} ${style}`, `${props[2]} ${color.join(' ')}`];
4528 } else if (name === 'boxShadow') {
4529 let [offsetX, offsetY, blurRadius, spreadRadius, ...color] = value.split(' ');
4530 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(' ')}`];
4531 } else if (name === 'textShadow') {
4532 let [offsetX, offsetY, blurRadius, ...color] = value.split(' ');
4533 return [`${props[0]} ${offsetX.replace('px', '')}`, `${props[1]} ${offsetY.replace('px', '')}`, `${props[2]} ${blurRadius.replace('px', '')}`, `${props[3]} ${color.join(' ')}`];
4534 } else if (name === 'overflow') {
4535 return [`${props[0]} ${value}`, `${props[1]} ${value}`];
4536 } else if (name === 'padding' || name === 'margin') {
4537 let [top, right, bottom, left] = value.split(' ');
4538 top = top.replace('px', '');
4539 right = right ? right.replace('px', '') : top;
4540 bottom = bottom ? bottom.replace('px', '') : top;
4541 left = left ? left.replace('px', '') : right || top;
4542 return [`${props[0]} ${top}`, `${props[1]} ${right}`, `${props[2]} ${bottom}`, `${props[3]} ${left}`];
4543 } else if (name === 'flex') {
4544 return [`flexGrow ${value}`, 'flexShrink 1', 'flexBasis 0%'];
4545 } else if (name === 'transform') {
4546 return [`expand the values like: translateX 10`];
4547 } else if (name === 'transformOrigin') {
4548 let [x, y] = value.split(' ');
4549 return [`${props[0]} ${x}`, `${props[1]} ${y || x}`];
4550 }
4551
4552 return [];
4553};
4554let getValue = (value, name) => {
4555 if (isFloat(value)) {
4556 return parseFloat(value, 10);
4557 } else if (isInt(value)) {
4558 return parseInt(value, 10);
4559 } else if (isEmptyText(value)) {
4560 return '';
4561 } else if (isBool(value)) {
4562 return isTrue(value);
4563 } else {
4564 return maybeMakeHyphenated$1(value, name);
4565 }
4566};
4567let SYSTEM_SCOPES = ['isDisabled', 'isFocused', // 'isHovered',
4568'isPlaceholder' // 'isSelected',
4569// TODO do we want to do media queries here?
4570];
4571let isSystemScope = name => SYSTEM_SCOPES.includes(name);
4572
4573let isActionable = name => name !== 'onWhen' && /^on[A-Z]/.test(name);
4574
4575let getPropType = (block, name, defaultValue) => block.isList && name === 'from' ? 'array' : isActionable(name) ? 'function' : isNumber[name] ? 'number' : 'string';
4576let MAYBE_HYPHENATED_STYLE_PROPS$1 = ['alignContent', 'alignItems', 'alignSelf', 'backgroundBlendMode', 'backgroundClip', 'backgroudOrigin', 'backgroundRepeat', 'boxSizing', 'clear', 'cursor', 'flexBasis', 'flexDirection', 'flexFlow', 'flexWrap', 'float', 'fontStretch', 'justifyContent', 'objectFit', 'overflowWrap', 'textAlign', 'textDecorationLine', 'textTransform', 'whiteSpace', 'wordBreak'];
4577let maybeMakeHyphenated$1 = (value, name) => MAYBE_HYPHENATED_STYLE_PROPS$1.includes(name) && /^[a-zA-Z]+$/.test(value) ? toSlugCase(value) : value;
4578function sortScopes(scopes) {
4579 let isHovered = scopes.find(item => item.slotName === 'isHovered');
4580 let isSelected = scopes.find(item => item.slotName === 'isSelected');
4581 let isSelectedHovered = scopes.find(item => item.slotName === 'isSelectedHovered');
4582 return [isSelectedHovered, isSelected, isHovered, ...scopes.filter(item => !/^(isHovered|isSelected|isSelectedHovered)$/.test(item.slotName))].filter(Boolean);
4583}
4584
4585var getLoc = ((line, scolumn, ecolumn) => ({
4586 start: {
4587 line,
4588 column: scolumn
4589 },
4590 end: {
4591 line,
4592 column: ecolumn
4593 }
4594}));
4595
4596let SLOT_PROPS = ['onBlur', 'onChange', 'onClick', 'onDrag', 'onDragEnd', 'onDragEnter', 'onDragExit', 'onDragLeave', 'onDragOver', 'onDragStart', 'onDrop', 'onFocus', 'onFocus', 'onMouseDown', 'onMouseEnter', 'onMouseLeave', 'onMouseMove', 'onMouseOver', 'onMouseUp', 'onWheel', 'onWhen', 'when'];
4597
4598let shouldBeSlot = (prop, block) => SLOT_PROPS.includes(prop) || block.isList && prop === 'from';
4599
4600var getTags = (({
4601 name,
4602 isSlot,
4603 slotIsNot,
4604 value,
4605 block
4606}) => {
4607 let tags = {};
4608 if (isAnimation(value) && name !== 'text') tags.animation = true;
4609 if (isStyle$1(name)) tags.style = true;
4610
4611 if (isRowStyle(name)) {
4612 tags.style = true;
4613 tags.rowStyle = true;
4614 }
4615
4616 if (isUnsupportedShorthand(name) && block.isBasic && !block.name.startsWith('Svg')) {
4617 tags.unsupportedShorthand = true;
4618 }
4619
4620 if (shouldBeSlot(name, block)) tags.shouldBeSlot = true;
4621 if (isSlot) tags.slot = true;
4622 if (slotIsNot) tags.slotIsNot = true;
4623 tags.validSlot = tags.slot || tags.shouldBeSlot && tags.slot || null;
4624 if (isFragment(name)) tags.fragment = true;
4625 return tags;
4626});
4627
4628var parse = (({
4629 convertSlotToProps = true,
4630 customFonts,
4631 enableSystemScopes = true,
4632 file,
4633 id,
4634 skipComments = true,
4635 skipInvalidProps = true,
4636 warnMissingDefaultValue = false,
4637 source,
4638 src,
4639 views
4640}) => {
4641 // convert crlf to lf
4642 let text = source.replace(/\r\n/g, '\n');
4643 let rlines = text.split('\n');
4644 let lines = rlines.map(line => line.trimRight());
4645 let fonts = [];
4646 let isDefiningChildrenExplicitly = false;
4647 let stack = [];
4648 let slots = [];
4649 let useIsBefore = false;
4650 let useIsMedia = false;
4651 let topBlockShouldBe = file.endsWith('.view') ? 'View' : 'Block';
4652 let view = null;
4653 let viewsInView = new Set();
4654 let warnings = [];
4655 let didYouMeanBlock = makeDidYouMeanBlock([...views.keys()]);
4656 let didYouMeanFontFamily = makeDidYouMeanFontFamily([...customFonts.keys()].map(id => id.split('-')[0]));
4657 let blockIds = [];
4658
4659 function getBlockId(node) {
4660 let maybeId = node.is || node.name;
4661
4662 if (!blockIds.includes(maybeId)) {
4663 blockIds.push(maybeId);
4664 return maybeId;
4665 }
4666
4667 let index = 1;
4668
4669 while (blockIds.includes(`${maybeId}${index}`)) {
4670 index++;
4671 }
4672
4673 let id = `${maybeId}${index}`;
4674 blockIds.push(id);
4675 return id;
4676 }
4677
4678 function lookForFonts(block) {
4679 if (block.properties && (isFontable(block.name) || !block.isBasic)) {
4680 let fontFamilyProp = block.properties.find(p => p.name === 'fontFamily');
4681 if (!fontFamilyProp) return;
4682 let family = fontFamilyProp.value;
4683 let fontWeightProp = block.properties.find(p => p.name === 'fontWeight');
4684 let weight = fontWeightProp ? fontWeightProp.value.toString() : '400';
4685 let fontStyleProp = block.properties.find(p => p.name === 'fontStyle');
4686 let style = fontStyleProp ? fontStyleProp.value.toString() : 'normal';
4687
4688 if (!fonts.find(font => font.family === family && font.weight === weight && font.style === style)) {
4689 let font = {
4690 id: `${family}-${weight}${style === 'italic' ? '-italic' : ''}`,
4691 isGoogleFont: isGoogleFont(family),
4692 family,
4693 weight,
4694 style
4695 };
4696
4697 if (font.isGoogleFont || customFonts.has(font.id)) {
4698 fonts.push(font);
4699 } else {
4700 let meant = didYouMeanFontFamily(family);
4701
4702 if (meant && meant !== family) {
4703 warnings.push({
4704 loc: fontFamilyProp.loc,
4705 type: `The font "${family}" is missing. Did you mean "${meant}" instead?\nIf not, download the font files (eg, "${font.id}.woff2", "${font.id}.woff", "${font.id}.ttf", etc) and add the to the "Fonts" folder.`,
4706 line: lines[fontFamilyProp.loc.start.line - 1]
4707 });
4708 } else if (!font.weight.startsWith('props.')) {
4709 warnings.push({
4710 loc: fontFamilyProp.loc,
4711 type: `The font "${family}" is missing.\nDownload the font files (eg, "${font.id}.woff2", "${font.id}.woff", "${font.id}.ttf", etc) and add the to the "Fonts" folder.`,
4712 line: lines[fontFamilyProp.loc.start.line - 1]
4713 });
4714 }
4715 }
4716 }
4717 }
4718 }
4719
4720 function lookForMultiples(block) {
4721 let freq = {};
4722 block.properties.forEach(prop => {
4723 if (!(prop.name in freq)) {
4724 freq[prop.name] = 0;
4725 }
4726
4727 freq[prop.name]++;
4728 });
4729 let multiples = Object.keys(freq).filter(name => freq[name] > 1);
4730
4731 if (multiples.length > 0) {
4732 multiples.forEach(name => warnings.push({
4733 loc: block.loc,
4734 type: `You have declared a value for ${name} ${freq[name]} times. Only the last value will be used. Delete the ones you don't use.`,
4735 line: lines[block.loc.start.line - 1]
4736 }));
4737 }
4738 }
4739
4740 function end(block, endLine) {
4741 block.loc.end = {
4742 line: endLine + 1,
4743 column: Math.max(0, lines[endLine].length - 1)
4744 };
4745
4746 if (!block.properties) {
4747 block.properties = [];
4748 }
4749
4750 if (block.name === 'List' && !block.properties.some(prop => prop.name === 'from')) {
4751 warnings.push({
4752 loc: block.loc,
4753 type: `A List needs "from <" to work`,
4754 line: lines[block.loc.start.line - 1]
4755 });
4756 }
4757
4758 if (stack.length === 0) {
4759 // if we're the last block on the stack, then this is the view!
4760 view = block;
4761 return true;
4762 }
4763
4764 return false;
4765 }
4766
4767 function parseBlock(line, i) {
4768 let {
4769 block: name,
4770 is,
4771 level
4772 } = getBlock(line);
4773 let shouldPushToStack = false;
4774 let isChildren = name === 'Children';
4775
4776 if (isChildren) {
4777 isDefiningChildrenExplicitly = true;
4778 }
4779
4780 let block = {
4781 type: 'Block',
4782 name,
4783 animations: {},
4784 isAnimated: false,
4785 isBasic: isBasic(name),
4786 isCapture: isCapture(name),
4787 isColumn: isColumn$1(name),
4788 isGroup: false,
4789 isChildren,
4790 level,
4791 loc: getLoc(i + 1, 0),
4792 properties: [],
4793 scopes: []
4794 };
4795 let meant = didYouMeanBlock(name);
4796
4797 if (meant) {
4798 if (meant !== name) {
4799 warnings.push({
4800 loc: block.loc,
4801 type: `"${name}" doesn't exist and won't be morphed. Did you mean "${meant}" instead of "${name}"?`,
4802 line
4803 });
4804 block.skip = true;
4805 }
4806 } else {
4807 warnings.push({
4808 loc: block.loc,
4809 type: `"${name}" doesn't exist and won't be morphed.\nCreate the view or rename the block to point to the right view.`,
4810 line
4811 });
4812 block.skip = true;
4813 }
4814
4815 if (id === name) {
4816 warnings.push({
4817 loc: block.loc,
4818 type: `Is this a typo? You can't use the view within itself.\nRename the view or use a different block instead of ${name} here. This won't be morphed to avoid loops.`,
4819 line
4820 });
4821 block.skip = true;
4822 }
4823
4824 if (!block.isBasic) {
4825 viewsInView.add(block.name);
4826 }
4827
4828 if (is) {
4829 block.is = is;
4830 }
4831
4832 block.id = getBlockId(block);
4833 let last = stack[stack.length - 1];
4834
4835 while (last && last.level >= block.level) {
4836 end(stack.pop(), i);
4837 last = stack[stack.length - 1];
4838 }
4839
4840 if (last) {
4841 shouldPushToStack = true;
4842
4843 if (last.isGroup) {
4844 if (last.isList) {
4845 if (block.isBasic) {
4846 warnings.push({
4847 loc: block.loc,
4848 type: `A basic block "${block.name}" can't be inside a List. Use a view you made instead.`,
4849 line,
4850 blocker: true
4851 });
4852 } else if (last.children.length > 0) {
4853 warnings.push({
4854 loc: block.loc,
4855 type: `A List can only have one view inside. "${block.name}" is outside of it. Put 1 empty line before.`,
4856 line
4857 });
4858 } else {
4859 last.children.push(block);
4860 }
4861 } else if (block.isColumn && !last.isTable) {
4862 warnings.push({
4863 loc: block.loc,
4864 type: `Only tables can contain columns. Put this column directly inside a table.`,
4865 line
4866 });
4867 } else {
4868 last.children.push(block);
4869 }
4870 } else {
4871 end(stack.pop(), i); // inside a block that isn't a group
4872
4873 if (last.isBasic) {
4874 warnings.push({
4875 loc: block.loc,
4876 type: `${block.is || block.name} is inside a block ${last.is || last.name} but ${last.name} isn't a container and can't have blocks inside of it.\nIndent it one level less.`,
4877 line
4878 });
4879 } else {
4880 warnings.push({
4881 loc: block.loc,
4882 type: `${block.is || block.name} is inside a view. While that's allowed for now, it may break in the future. Ideally, you'd refactor it into its specific own view.`,
4883 line
4884 });
4885 }
4886 }
4887 } else if (view !== null) {
4888 warnings.push({
4889 loc: block.loc,
4890 type: `${block.is || block.name} is outside of the top block and it won't render. Views relies on indentation to nest child views within ${topBlockShouldBe} blocks\nTo fix it, either:\na) Indent ${block.is || block.name} within the top block and indent all nested views within ${block.is || block.name}, or\nb) remove it.`,
4891 line
4892 });
4893 }
4894
4895 if (isGroup(name)) {
4896 block.isGroup = true;
4897 block.isList = isList$1(name);
4898 block.isTable = isTable$1(name);
4899 block.children = [];
4900 }
4901
4902 if (block.isBasic && (block.name === 'View' || block.name === 'Block')) {
4903 block.isFragment = true;
4904
4905 if (stack.length > 0) {
4906 warnings.push({
4907 type: `A view can only have one ${block.name} block. Maybe you want to split this block into another view?`,
4908 line,
4909 loc: block.loc
4910 });
4911 }
4912 } else if (stack.length === 0) {
4913 warnings.push({
4914 type: `A view must start with a ${topBlockShouldBe} block. ${block.name} isn't valid.\nWrap everything within a ${topBlockShouldBe} block at the top.`,
4915 line,
4916 loc: block.loc
4917 });
4918 }
4919
4920 if (shouldPushToStack || stack.length === 0) {
4921 stack.push(block);
4922 }
4923
4924 parseProps(i, block);
4925 lookForFonts(block);
4926 lookForMultiples(block);
4927 }
4928
4929 function parseProps(i, block) {
4930 let endOfBlockIndex = i;
4931
4932 while (endOfBlockIndex < lines.length - 1 && !isBlock(lines[endOfBlockIndex + 1])) {
4933 endOfBlockIndex++;
4934 }
4935
4936 let properties = [];
4937 let scopes = [];
4938 let scope;
4939 let inScope = false;
4940
4941 for (let j = i; j <= endOfBlockIndex; j++) {
4942 let line = lines[j].trim();
4943 let propNode = null;
4944
4945 if (isProp(line)) {
4946 let {
4947 name,
4948 isSlot,
4949 slotName,
4950 slotIsNot,
4951 value
4952 } = getProp$1(line);
4953 let loc = getLoc(j + 1, line.indexOf(name), line.length - 1);
4954 let tags = getTags({
4955 name,
4956 isSlot,
4957 slotIsNot,
4958 value,
4959 block
4960 });
4961
4962 if (block.isBasic) {
4963 if (tags.unsupportedShorthand) {
4964 warnings.push({
4965 loc,
4966 type: `The shorthand ${name} isn't supported. You need to expand it like:\n${getUnsupportedShorthandExpanded(name, value).join('\n')}`,
4967 line
4968 });
4969 } else {
4970 let meant = didYouMeanProp(name);
4971
4972 if (meant && meant !== name) {
4973 warnings.push({
4974 loc,
4975 type: `Did you mean "${meant}" instead of "${name}"?`,
4976 line
4977 });
4978 }
4979 }
4980
4981 if (tags.fragment && block.isGroup) {
4982 if (isSlot) {
4983 warnings.push({
4984 loc,
4985 type: `The isFragment prop can't be a slot. You need to change it to isFragment true. Treating it as true for now.`,
4986 line
4987 });
4988 }
4989
4990 block.isFragment = true;
4991 }
4992
4993 if (block.type === 'Capture' && !CAPTURE_TYPES.includes(value)) {
4994 if (/textarea/i.test(value)) {
4995 warnings.push({
4996 loc,
4997 type: `Invalid type ${value} for Capture. Perhaps you want to use a CaptureTextArea block instead? Using type text as a default.`,
4998 line
4999 });
5000 } else {
5001 warnings.push({
5002 loc,
5003 type: `Invalid type ${value} for Capture. You can use ${CAPTURE_TYPES.join(', ')}. Using type text as a default.`,
5004 line
5005 });
5006 }
5007
5008 value = 'text';
5009 }
5010 } else {
5011 if (name === 'lazy') {
5012 block.isLazy = true;
5013 }
5014 }
5015
5016 if (name === 'when') {
5017 let isSystem = enableSystemScopes && isSystemScope(slotName);
5018
5019 if (value === '' || value === '<' || value === '<!') {
5020 warnings.push({
5021 loc,
5022 type: 'This when has no condition assigned to it. Add one like: "when <isCondition"',
5023 line
5024 });
5025 if (skipInvalidProps) continue;
5026 } else if (!tags.validSlot) {
5027 warnings.push({
5028 loc,
5029 type: `The slot name "${name}" isn't valid. Fix it like: "when <isCondition" `,
5030 line
5031 });
5032 if (skipInvalidProps) continue;
5033 }
5034
5035 if (isSystem && slotIsNot) {
5036 warnings.push({
5037 loc,
5038 type: `"${slotName}" is a system slot and it can't take a "!" in its value. Replace the line for: "when <${slotName}".`
5039 });
5040 }
5041
5042 tags.scope = value;
5043 inScope = true;
5044 scope = {
5045 isSystem,
5046 defaultValue: getValue(value, name),
5047 value,
5048 name,
5049 slotName,
5050 slotIsNot: isSystem ? false : slotIsNot,
5051 properties: []
5052 };
5053
5054 if (convertSlotToProps) {
5055 scope.value = slotName;
5056
5057 if (slotName === 'isBefore') {
5058 useIsBefore = true;
5059 } else if (/!?isMedia.+/.test(slotName)) {
5060 scope.value = `isMedia.${slotName.replace('isMedia', '').toLowerCase()}`;
5061 useIsMedia = true;
5062 } else if (!isSystem) {
5063 scope.value = `${slotIsNot ? '!' : ''}props.${slotName || name}`;
5064 }
5065 }
5066
5067 scopes.push(scope);
5068 } else if ((tags.slot || tags.shouldBeSlot) && !tags.validSlot) {
5069 if (name === 'from' && block.name === 'List' || name !== 'from') {
5070 warnings.push({
5071 loc,
5072 type: `The value you used in the slot "${name}" is invalid`,
5073 line
5074 });
5075 if (skipInvalidProps) continue;
5076 }
5077 }
5078
5079 if (name === 'onWhen' && properties.length > 0) {
5080 warnings.push({
5081 type: `Put onWhen at the top of the block. It's easier to see it that way!`,
5082 line,
5083 loc
5084 });
5085 }
5086
5087 if (name === 'onWhen' && /!?isMedia.+/.test(slotName)) {
5088 useIsMedia = true;
5089 }
5090
5091 if (name === 'format') {
5092 block.format = getFormat(value);
5093 }
5094
5095 if (value === '' && name !== 'text') {
5096 warnings.push({
5097 loc,
5098 type: `"${name}" has no value. Please give it a value.`,
5099 line
5100 });
5101 if (skipInvalidProps) continue;
5102 }
5103
5104 propNode = {
5105 type: 'Property',
5106 loc,
5107 name,
5108 tags,
5109 value: getValue(value, name)
5110 };
5111
5112 if (tags.animation && scope) {
5113 block.isAnimated = true;
5114 let currentAnimation = getAnimation(value);
5115 propNode.value = currentAnimation.defaultValue;
5116 propNode.animation = currentAnimation.properties;
5117
5118 if (propNode.animation.curve === 'spring') {
5119 block.hasSpringAnimation = true;
5120 } else {
5121 block.hasTimingAnimation = true;
5122 }
5123
5124 if (!block.animations[currentAnimation.id]) {
5125 block.animations[currentAnimation.id] = {
5126 index: Object.keys(block.animations).length,
5127 animation: currentAnimation,
5128 props: {}
5129 };
5130 }
5131
5132 propNode.animationIndexOnBlock = block.animations[currentAnimation.id].index;
5133
5134 if (!block.animations[currentAnimation.id].props[name]) {
5135 let baseValue = null;
5136 let baseProp = properties.find(prop => prop.name === name);
5137
5138 if (baseProp) {
5139 baseValue = baseProp.value;
5140 }
5141
5142 block.animations[currentAnimation.id].props[name] = {
5143 name,
5144 scopes: [],
5145 value: baseValue
5146 };
5147 }
5148
5149 block.animations[currentAnimation.id].props[name].scopes.push({
5150 name: scope.slotName,
5151 value: currentAnimation.defaultValue
5152 });
5153 }
5154
5155 if (scope) {
5156 propNode.scope = scope.slotName;
5157 }
5158
5159 if (tags.slot) {
5160 let needsDefaultValue = !tags.shouldBeSlot && /</.test(propNode.value);
5161
5162 if (typeof propNode.value === 'string') {
5163 propNode.value = propNode.value.replace(/^</, '');
5164 }
5165
5166 propNode.slotName = slotName;
5167
5168 if (name !== 'when' || name === 'when' && !scope.isSystem) {
5169 propNode.defaultValue = propNode.value;
5170
5171 if (convertSlotToProps) {
5172 propNode.value = `${slotIsNot ? '!' : ''}props.${slotName || name}`;
5173 }
5174
5175 if (needsDefaultValue) {
5176 if (name === 'text' && block.name === 'Text') {
5177 propNode.defaultValue = '';
5178 } else {
5179 propNode.defaultValue = false;
5180
5181 if (warnMissingDefaultValue && block.isBasic && (propNode.tags.style || block.name === 'Text' && propNode.name === 'text')) {
5182 warnings.push({
5183 loc,
5184 type: `Add a default value to "${name}" like: "${name} <${slotName} default value"`,
5185 line
5186 });
5187 }
5188 }
5189 }
5190
5191 if (!inScope && !propNode.tags.fragment && !slots.some(vp => vp.name === (slotName || name))) {
5192 slots.push({
5193 name: slotName || name,
5194 type: getPropType(block, name),
5195 defaultValue: tags.shouldBeSlot ? false : propNode.defaultValue
5196 });
5197 }
5198 }
5199 }
5200 } else if (isComment(line) && !skipComments) {
5201 let [value] = getComment(line);
5202 let userComment = isUserComment(line);
5203
5204 if (userComment) {
5205 value = getComment(value);
5206 }
5207
5208 propNode = {
5209 type: 'Property',
5210 loc: getLoc(j + 1, 0, line.length - 1),
5211 value,
5212 tags: {
5213 comment: true,
5214 userComment
5215 }
5216 };
5217 }
5218
5219 if (propNode) {
5220 block.loc.end = propNode.loc.end;
5221
5222 if (inScope) {
5223 if (propNode.name !== 'when' && !propNode.tags.comment && !properties.some(baseProp => baseProp.name === propNode.name)) {
5224 warnings.push({
5225 loc: propNode.loc,
5226 type: `You're missing a base prop for ${propNode.name}. Add it before all whens on the block.`,
5227 line
5228 });
5229 }
5230
5231 scope.properties.push(propNode);
5232 } else {
5233 properties.push(propNode);
5234 }
5235 }
5236 }
5237
5238 block.properties = properties;
5239 block.scopes = sortScopes(scopes);
5240
5241 if (block.name !== 'View' && block.isFragment) {
5242 let invalidProps = properties.filter(prop => prop.name !== 'isFragment' || prop.name !== 'onWhen').map(prop => prop.name);
5243
5244 if (invalidProps.length > 0) {
5245 warnings.push({
5246 type: `A fragment can only have isFragment and onWhen props.\nEvery other prop will be dismissed.\nEither remove ${invalidProps.join(', ')} or remove isFragment.`,
5247 loc: block.loc,
5248 line: rlines[i]
5249 });
5250 }
5251 }
5252 }
5253
5254 lines.forEach((line, i) => {
5255 if (line !== rlines[i].trimRight()) {
5256 warnings.push({
5257 type: `You have some spaces before or after this line. Clean them up.`,
5258 loc: {
5259 start: {
5260 line: i + 1
5261 }
5262 },
5263 line: rlines[i]
5264 });
5265 }
5266
5267 if (isBlock(line)) {
5268 parseBlock(line, i);
5269 }
5270 });
5271
5272 if (stack.length > 0) {
5273 while (!end(stack.pop(), lines.length - 1)) {}
5274 }
5275
5276 if (!view) {
5277 view = {
5278 type: 'Block',
5279 name: id,
5280 animations: {},
5281 isAnimated: false,
5282 isBasic: true,
5283 isCapture: false,
5284 isColumn: false,
5285 isGroup: false,
5286 isChildren: false,
5287 level: 0,
5288 loc: getLoc(1, 0),
5289 properties: [],
5290 scopes: []
5291 };
5292 warnings.push({
5293 loc: view.loc,
5294 type: `The file for ${id} is empty and won't render! Add some blocks to it like:\n${topBlockShouldBe}\n Text\n text content`,
5295 line: ''
5296 });
5297 }
5298
5299 let flowProp = view.properties.find(p => p.name === 'is');
5300
5301 if (flowProp) {
5302 view.isStory = true;
5303 view.flow = flowProp.value;
5304 view.viewPath = path.dirname(file.replace(src.replace(/\\/g, '/'), ''));
5305 view.viewPathParent = path.dirname(view.viewPath);
5306 view.data = getData(view.properties.find(p => p.name === 'data'));
5307 view.dataFormat = getDataFormat(view.properties.find(p => p.name === 'dataFormat'));
5308 view.dataValidate = getDataValidate(view.properties.find(p => p.name === 'dataValidate'));
5309 } else {
5310 view.isStory = false;
5311 }
5312
5313 view.isDefiningChildrenExplicitly = isDefiningChildrenExplicitly;
5314 view.useIsBefore = useIsBefore;
5315 view.useIsMedia = useIsMedia;
5316 view.views = viewsInView;
5317 return {
5318 fonts,
5319 slots,
5320 view,
5321 warnings
5322 };
5323});
5324
5325let getFileDepth = file => file.split(path.sep).length - 1;
5326
5327let sortByFileDepth = (a, b) => {
5328 let depthA = getFileDepth(a);
5329 let depthB = getFileDepth(b);
5330 let depthDelta = depthA - depthB;
5331 if (depthDelta !== 0) return depthDelta;
5332 return a < b ? 1 : a > b ? -1 : 0;
5333};
5334
5335function sortSetsInMap(map) {
5336 for (let [key, value] of map) {
5337 if (value.size <= 1) continue;
5338 map.set(key, new Set([...value].sort(sortByFileDepth)));
5339 }
5340}
5341
5342function parseViews({
5343 customFonts,
5344 filesView,
5345 src,
5346 verbose,
5347 viewsById,
5348 viewsToFiles
5349}) {
5350 for (let file of filesView) {
5351 let view = viewsToFiles.get(file);
5352 if (view.custom) continue;
5353 view.parsed = parse({
5354 customFonts,
5355 file,
5356 id: view.id,
5357 src,
5358 source: view.source,
5359 views: viewsById
5360 });
5361 maybePrintWarnings(view, verbose);
5362 }
5363
5364 sortSetsInMap(viewsById);
5365}
5366
5367let EXTENSIONS = ['view.blocks', 'logic.js', 'react.js'];
5368function getViewIdFromFile(file) {
5369 let extension = EXTENSIONS.find(item => file.endsWith(item));
5370
5371 if (!extension) {
5372 throw new Error(`Can't recognise the extension of "${file}" to get a view's id.`);
5373 }
5374
5375 return path.basename(path.dirname(file));
5376}
5377
5378function processViewCustomFiles({
5379 filesViewCustom,
5380 viewsById,
5381 viewsToFiles
5382}) {
5383 [...filesViewCustom].forEach(file => {
5384 let id = getViewIdFromFile(file);
5385 addToMapSet(viewsById, id, file);
5386 viewsToFiles.set(file, {
5387 custom: true,
5388 file,
5389 id,
5390 logic: false,
5391 source: null,
5392 version: 0
5393 });
5394 });
5395}
5396
5397async function processViewFiles({
5398 filesView,
5399 filesViewLogic,
5400 viewsById,
5401 viewsToFiles
5402}) {
5403 await Promise.all([...filesView].map(async file => {
5404 let id = getViewIdFromFile(file);
5405 addToMapSet(viewsById, id, file);
5406 let view = viewsToFiles.has(file) ? viewsToFiles.get(file) : {};
5407 let logic = path.join(path.dirname(file), 'logic.js');
5408 viewsToFiles.set(file, { ...view,
5409 custom: false,
5410 file,
5411 id,
5412 logic: filesViewLogic.has(logic) && logic,
5413 source: await fs.promises.readFile(file, 'utf8'),
5414 version: view.version ? view.version + 1 : 0
5415 });
5416 }));
5417}
5418
5419function makeMorpher({
5420 as = 'react-dom',
5421 src,
5422 tools = false,
5423 verbose = true
5424}) {
5425 let state = {
5426 as,
5427 src,
5428 tools,
5429 verbose,
5430 customFonts: new Map(),
5431 viewsById: new Map(),
5432 viewsToFiles: new Map()
5433 };
5434
5435 state.processFiles = async function processFiles({
5436 filesFontCustom = new Set(),
5437 filesView = new Set(),
5438 filesViewCustom = new Set(),
5439 filesViewLogic = new Set()
5440 }) {
5441 if (filesFontCustom.size > 0) {
5442 processCustomFonts({
5443 customFonts: state.customFonts,
5444 filesFontCustom
5445 });
5446 } // detect .view files
5447
5448
5449 await processViewFiles({
5450 filesView,
5451 filesViewLogic,
5452 viewsById: state.viewsById,
5453 viewsToFiles: state.viewsToFiles
5454 }); // detect .js files meant to be custom views with "// @view" at the top
5455
5456 processViewCustomFiles({
5457 filesViewCustom,
5458 viewsById: state.viewsById,
5459 viewsToFiles: state.viewsToFiles
5460 }); // TODO optimise
5461 // parse views
5462
5463 parseViews({
5464 customFonts: state.customFonts,
5465 filesView,
5466 src: state.src,
5467 verbose: state.verbose,
5468 viewsById: state.viewsById,
5469 viewsToFiles: state.viewsToFiles
5470 });
5471 let morphedFonts = morphAllFonts({
5472 as: state.as,
5473 customFonts: state.customFonts,
5474 filesView,
5475 src: state.src,
5476 viewsToFiles: state.viewsToFiles
5477 }); // TODO optimise
5478 // morph views
5479
5480 let morphedViews = morphAllViews({
5481 as: state.as,
5482 filesView,
5483 getFontImport: makeGetFontImport(state.src),
5484 getSystemImport: makeGetSystemImport(state.src),
5485 tools: state.tools,
5486 viewsById: state.viewsById,
5487 viewsToFiles: state.viewsToFiles
5488 });
5489 let filesToWrite = [...morphedFonts, ...morphedViews, // TODO optimise, only if they changed, cache, etc
5490 await ensureData(state), await ensureFlow(state), await ensureTools(state), await ensureIsBefore(state), await ensureIsHovered(state), await ensureIsMedia(state)].filter(Boolean);
5491 await Promise.all(filesToWrite.map(({
5492 file,
5493 content
5494 }) => fs.promises.writeFile(file, content, 'utf-8')));
5495 };
5496
5497 return state;
5498}
5499
5500function getPointsOfUse({
5501 view,
5502 viewsToFiles
5503}) {
5504 let filesView = new Set([view.file]);
5505 let filesViewLogic = new Set();
5506 let viewsId = new Set();
5507
5508 if (view.logic) {
5509 filesViewLogic.add(view.logic);
5510 }
5511
5512 for (let viewInView of viewsToFiles.values()) {
5513 if (viewInView.custom) continue;
5514
5515 if (viewInView.parsed.view.views.has(view.id)) {
5516 filesView.add(viewInView.file);
5517
5518 if (viewInView.logic) {
5519 filesViewLogic.add(viewInView.logic);
5520 }
5521
5522 viewsId.add(viewInView.id);
5523 }
5524 }
5525
5526 return {
5527 filesView,
5528 filesViewLogic,
5529 viewsId
5530 };
5531}
5532
5533async function watchFiles({
5534 morpher
5535}) {
5536 let watcher = chokidar.watch(MATCH, {
5537 cwd: morpher.src,
5538 ignored: [path.join('**', 'node_modules', '**'), path.join('**', 'view.js'), path.join('**', 'DesignSystem', 'Fonts', '*.js'), path.join('**', 'Logic', 'ViewsFlow.js'), path.join('**', 'Logic', 'useIsMedia.js'), path.join('**', 'Logic', 'useIsBefore.js'), path.join('**', 'Logic', 'useIsHovered.js'), path.join('**', 'Logic', 'ViewsTools.js'), path.join('**', 'Data', 'ViewsData.js')],
5539 ignoreInitial: true // awaitWriteFinish: true,
5540
5541 });
5542 let skipTimeout = null;
5543 let processEventsPromise = null;
5544 let queue = [];
5545
5546 function onEvent({
5547 file,
5548 op
5549 }) {
5550 queue.push({
5551 file: path.join(morpher.src, file),
5552 op
5553 });
5554 maybeProcess();
5555 }
5556
5557 function maybeProcess() {
5558 // if busy, try again in a bit
5559 clearTimeout(skipTimeout);
5560 skipTimeout = null;
5561
5562 if (processEventsPromise) {
5563 skipTimeout = setTimeout(maybeProcess, 50);
5564 return;
5565 }
5566
5567 processEvents();
5568 }
5569
5570 async function processEvents() {
5571 if (queue.length === 0 || processEventsPromise) {
5572 return;
5573 }
5574
5575 let queueClone = [...queue];
5576 queue = [];
5577 processEventsPromise = new Promise(async resolve => {
5578 try {
5579 await processQueue({
5580 queue: queueClone,
5581 morpher
5582 });
5583 } catch (error) {
5584 console.error(error);
5585 } finally {
5586 resolve();
5587 processEventsPromise = null;
5588 }
5589 });
5590 }
5591
5592 watcher.on('add', file => onEvent({
5593 file,
5594 op: 'add'
5595 }));
5596 watcher.on('change', file => onEvent({
5597 file,
5598 op: 'change'
5599 }));
5600 watcher.on('unlink', file => onEvent({
5601 file,
5602 op: 'unlink'
5603 }));
5604 return watcher;
5605}
5606
5607async function processQueue({
5608 queue,
5609 morpher
5610}) {
5611 let filesToProcess = {
5612 filesView: new Set(),
5613 filesViewLogic: new Set(),
5614 filesViewCustom: new Set(),
5615 filesFontCustom: new Set()
5616 };
5617 await Promise.all([processUnlinked({
5618 files: queue.filter(item => item.op === 'unlink'),
5619 morpher,
5620 filesToProcess
5621 }), processAddedOrChanged({
5622 files: queue.filter(item => item.op !== 'unlink'),
5623 morpher,
5624 filesToProcess
5625 })]);
5626 await morpher.processFiles(filesToProcess);
5627 if (!morpher.verbose) return; // && viewsId.size > 0) {
5628 // Object.entries(filesToProcess).forEach(([key, value]) => {
5629 // console.log(chalk.greenBright(key), [...value])
5630 // })
5631 // console.log(chalk.green('M'), [...viewsId].join(', '))
5632 // }
5633}
5634
5635function processUnlinked({
5636 files,
5637 morpher,
5638 filesToProcess
5639}) {
5640 return Promise.all(files.map(async ({
5641 file
5642 }) => {
5643 morpher.verbose && console.log(chalk.magenta('X'), path.basename(path.dirname(file)), chalk.dim(file.replace(morpher.src, '').replace(/^\//, '')));
5644
5645 if (await isFontCustomFile(file)) {
5646 let id = getFontId(file);
5647 let font = morpher.customFonts.get(id);
5648 font.delete(file);
5649
5650 if (font.size === 0) {
5651 morpher.customFonts.delete(id);
5652 }
5653 } else {
5654 let viewFile = path.join(path.dirname(file), 'view.blocks');
5655 let view = morpher.viewsToFiles.get(viewFile);
5656 if (!view) return;
5657 processPointsOfUse({
5658 view,
5659 morpher,
5660 filesToProcess
5661 });
5662 filesToProcess.filesView.delete(view.file);
5663 morpher.viewsById.delete(view.id);
5664 morpher.viewsToFiles.delete(view.file);
5665
5666 if (await isViewFile(file)) {
5667 filesToProcess.filesView.delete(file);
5668
5669 try {
5670 await fs.promises.unlink(file.replace('view.blocks', 'view.js'));
5671 } catch (error) {}
5672 } else if (await isViewLogicFile(file)) {
5673 filesToProcess.filesView.add(view.file);
5674 filesToProcess.filesViewLogic.delete(file);
5675 } else if (await isViewCustomFile(file)) {
5676 filesToProcess.filesViewCustom.delete(file);
5677 }
5678 }
5679 }));
5680}
5681
5682function processAddedOrChanged({
5683 files,
5684 morpher,
5685 filesToProcess
5686}) {
5687 return Promise.all(files.map(async ({
5688 file,
5689 op
5690 }) => {
5691 morpher.verbose && console.log(chalk.yellow(op.toUpperCase()[0]), path.basename(path.dirname(file)), chalk.dim(file.replace(morpher.src, '').replace(/^\//, '')));
5692
5693 if (await isFontCustomFile(file)) {
5694 filesToProcess.filesFontCustom.add(file);
5695 } else {
5696 if (await isViewFile(file)) {
5697 filesToProcess.filesView.add(file);
5698 let logicFile = path.join(path.dirname(file), 'logic.js');
5699
5700 if (fs.existsSync(logicFile)) {
5701 filesToProcess.filesViewLogic.add(logicFile);
5702 }
5703
5704 let parentFile = path.join(path.dirname(path.dirname(file)), 'view.blocks');
5705
5706 if (fs.existsSync(parentFile)) {
5707 filesToProcess.filesView.add(parentFile);
5708 }
5709 } else if (await isViewLogicFile(file)) {
5710 filesToProcess.filesView.add(path.join(path.dirname(file), 'view.blocks'));
5711 filesToProcess.filesViewLogic.add(file);
5712 } else if (await isViewCustomFile(file)) {
5713 filesToProcess.filesViewCustom.add(file);
5714 }
5715
5716 let viewFile = path.join(path.dirname(file), 'view.blocks');
5717 let view = morpher.viewsToFiles.get(viewFile);
5718 if (!view) return;
5719 processPointsOfUse({
5720 view,
5721 morpher,
5722 filesToProcess
5723 });
5724 } // morpher.verbose &&
5725 // console.log(
5726 // op === 'add'
5727 // ? `${chalk.yellow('A')} ${chalk.green('M')}`
5728 // : chalk.green('M'),
5729 // getViewIdFromFile(file),
5730 // chalk.dim(`-> ${file}`)
5731 // )
5732
5733 }));
5734}
5735
5736function processPointsOfUse({
5737 view,
5738 morpher,
5739 filesToProcess
5740}) {
5741 let {
5742 filesView,
5743 filesViewLogic
5744 } = getPointsOfUse({
5745 view,
5746 viewsToFiles: morpher.viewsToFiles
5747 });
5748
5749 for (let file of filesView) {
5750 filesToProcess.filesView.add(file);
5751 }
5752
5753 for (let file of filesViewLogic) {
5754 filesToProcess.filesViewLogic.add(file);
5755 }
5756}
5757
5758async function watch(options) {
5759 let morpher = makeMorpher(options);
5760 await ensureFontsDirectory(morpher.src);
5761 await morpher.processFiles((await getFiles(morpher.src)));
5762
5763 if (options.verbose) {
5764 if (morpher.customFonts.size > 0) {
5765 console.log(chalk.yellow(`\nCustom fonts detected:`));
5766 console.log([...morpher.customFonts.keys()].sort().join('\n'));
5767 }
5768
5769 let views = [...morpher.viewsToFiles.values()];
5770 console.log(views.map(view => {
5771 let msg = view.id;
5772
5773 if (view.custom) {
5774 msg = `${view.id} ${chalk.dim('(is custom)')}`;
5775 } else if (view.logic) {
5776 msg = `${view.id} ${chalk.dim('(has logic)')}`;
5777 }
5778
5779 return `${chalk.yellow('A')} ${chalk.green('M')} ${msg} ${chalk.dim(path.relative(process.cwd(), view.file))}`;
5780 }).sort().join('\n'));
5781 views.forEach(maybePrintWarnings);
5782 }
5783
5784 if (options.once) return;
5785 let watcher = watchFiles({
5786 morpher
5787 });
5788 process.on('beforeExit', () => {
5789 console.log('Stopping Views morpher file watcher...');
5790 watcher.close();
5791 });
5792}
5793
5794let wait = time => new Promise(resolve => setTimeout(resolve, time));
5795
5796(async function () {
5797 let {
5798 _,
5799 as,
5800 clean: clean$1,
5801 help,
5802 tools,
5803 watch: shouldWatch,
5804 verbose,
5805 version
5806 } = minimist(process.argv.slice(2), {
5807 alias: {
5808 help: 'h'
5809 },
5810 booleans: ['clean', 'help', 'tools', 'watch', 'version'],
5811 default: {
5812 as: 'react-dom',
5813 clean: false,
5814 tools: true,
5815 verbose: true,
5816 version: false,
5817 watch: false
5818 }
5819 });
5820
5821 if (!shouldWatch && tools) {
5822 tools = false;
5823 }
5824
5825 if (help) {
5826 console.log(`
5827 views-morph [directory]
5828 --as target platform
5829 react-dom (default)
5830 react-native
5831 react-pdf
5832 --clean clean the autogenerated .view.js files
5833 --tools use with Views Tools, defauls to true when
5834 --watch is enabled, otherwise defaults to false
5835 --verbose defaults to true
5836 --version print the version
5837 --watch watch a directory and produce .view.js files
5838 `);
5839 process.exit();
5840 }
5841
5842 if (version) {
5843 console.log(`v${pkg.version}`);
5844 process.exit();
5845 }
5846
5847 let input = Array.isArray(_) && _[0];
5848
5849 if (!input || !(await fs.promises.stat(input)).isDirectory()) {
5850 console.error(`You need to specify an input directory to watch. ${input} is a file.`);
5851 process.exit();
5852 }
5853
5854 if (!path.isAbsolute(input)) {
5855 input = path.normalize(path.join(process.cwd(), input));
5856 }
5857
5858 try {
5859 if ((await fs.promises.stat(path.join(input, 'src'))).isDirectory()) {
5860 input = path.join(input, 'src');
5861 }
5862 } catch (error) {}
5863
5864 if (clean$1) {
5865 console.log(`Cleaning up ${input}...`);
5866 await clean(input, verbose);
5867 process.exit();
5868 }
5869
5870 updateNotifier({
5871 pkg
5872 }).notify();
5873
5874 if (verbose) {
5875 console.log(chalk.underline(`Views Tools morpher v${pkg.version}`));
5876 console.log(`\nWill morph files at "${chalk.green(input)}" as "${chalk.green(as)}" ${tools ? 'with Views Tools' : 'without Views Tools'}`);
5877
5878 if (shouldWatch && !tools) {
5879 console.log(chalk.bgRed(' '));
5880 console.log();
5881 console.log(`🚨 You're missing out!!!`);
5882 console.log(chalk.bold('🚀 Views Tools can help you find product market\n fit before you run out of money.'));
5883 console.log();
5884 console.log('✨ Find out how 👉', chalk.bold(chalk.green('https://views.tools')));
5885 console.log();
5886 console.log(chalk.bgRed(' '));
5887 await wait(15000);
5888 }
5889
5890 console.log(chalk.yellow('A'), '= Added');
5891 console.log(chalk.green('M'), `= Morphed`);
5892 console.log(chalk.blue('X'), `= Deleted`);
5893 console.log('\nPress', chalk.blue('ctrl+c'), 'to stop at any time.\n');
5894 }
5895
5896 watch({
5897 as,
5898 once: !shouldWatch,
5899 src: input,
5900 tools,
5901 verbose
5902 });
5903})();
5904//# sourceMappingURL=bin.js.map