UNPKG

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