UNPKG

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