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.2";
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\.(isFlow|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 morpher = 'react-dom',
3108 src,
3109 tools,
3110 view,
3111 viewsById,
3112 viewsToFiles
3113}) => {
3114 let name = view.id;
3115 let finalName = restrictedNames.includes(name) ? `${name}1` : name;
3116 let state = {
3117 animated: new Set(),
3118 animations: {},
3119 cssDynamic: false,
3120 cssStatic: false,
3121 data: view.parsed.view.data,
3122 dataFormat: view.parsed.view.dataFormat,
3123 dataValidate: view.parsed.view.dataValidate,
3124 dependencies: new Set(),
3125 flow: null,
3126 setFlowTo: false,
3127 getFontImport: font => getFontImport(font, view),
3128 getStyleForProperty,
3129 getValueForProperty,
3130 hasRefs: false,
3131 images: [],
3132 isDynamic: false,
3133 isReactNative: false,
3134 isStory: id => {
3135 let viewInView = getViewRelativeToView({
3136 id,
3137 view,
3138 viewsById,
3139 viewsToFiles
3140 });
3141 return viewInView && !viewInView.custom && viewInView.parsed.view.isStory;
3142 },
3143 lazy: {},
3144 morpher,
3145 name: finalName,
3146 viewPath: view.parsed.view.viewPath,
3147 viewPathParent: view.parsed.view.viewPathParent,
3148 render: [],
3149 styles: {},
3150 stylesOrder: [],
3151 usedBlockNames: {
3152 [finalName]: 1,
3153 AutoSizer: 1,
3154 Column: 1,
3155 Table: 1
3156 },
3157 uses: [],
3158 testIdKey: 'data-testid',
3159 viewPathKey: 'data-view-path',
3160 testIds: {},
3161 tools,
3162
3163 use(block, isLazy = false) {
3164 if (isLazy) {
3165 state.lazy[block] = true;
3166 }
3167
3168 if (state.uses.includes(block) || /props/.test(block) || /^Animated/.test(block) || 'React.Fragment' === block || 'ViewsModalOverlayContent' === block) return;
3169
3170 if (block === finalName) {
3171 state.name = `${view.id}${state.usedBlockNames[finalName]++}`;
3172 }
3173
3174 state.uses.push(block);
3175 },
3176
3177 useIsBefore: view.parsed.view.useIsBefore,
3178 useIsMedia: view.parsed.view.useIsMedia
3179 }; // TIP: use the following code to trace generated code
3180 // let _push = state.render.push.bind(state.render)
3181 // state.render.push = item => {
3182 // _push(item)
3183 // if (item.includes("Some <isHovered")) {
3184 // console.trace()
3185 // }
3186 // }
3187
3188 if (state.data) {
3189 state.use('ViewsUseData');
3190 }
3191
3192 if (name !== finalName) {
3193 console.warn(`// ${name} is a Views reserved name. To fix this, change its file name to something else.`);
3194 }
3195
3196 state.fonts = view.parsed.fonts;
3197 state.slots = view.parsed.slots;
3198 walk(view.parsed.view, visitor, state);
3199 return {
3200 code: toComponent({
3201 getImport: makeGetImport({
3202 src,
3203 imports,
3204 getSystemImport,
3205 view,
3206 viewsById,
3207 viewsToFiles
3208 }),
3209 getStyles,
3210 name: state.name,
3211 state
3212 }),
3213 dependencies: state.dependencies,
3214 flow: state.flow,
3215 flowDefaultState: state.flowDefaultState // TODO flow supported states
3216 // fonts: view.parsed.fonts,
3217 // slots: view.parsed.slots,
3218
3219 };
3220});
3221
3222let BORDER_RADIUS = ['borderRadius', 'borderTopLeftRadius', 'borderTopRightRadius', 'borderBottomLeftRadius', 'borderBottomRightRadius'];
3223let enter$p = (node, parent, state) => {
3224 if (node.backgroundImage) {
3225 let source = getObjectAsString({
3226 uri: node.backgroundImage
3227 });
3228 let resizeMode = getProp(node, 'backgroundSize');
3229 resizeMode = safe(resizeMode ? resizeMode.value.value : 'cover');
3230 state.render.push(` resizeMode=${resizeMode} source={${source}}`); // hack until https://github.com/facebook/react-native/issues/8885
3231 // is fixed
3232
3233 BORDER_RADIUS.forEach(prop => {
3234 if (hasProp(node, prop)) {
3235 let propNode = getProp(node, prop);
3236 state.render.push(` ${prop}={${propNode.value.value}}`);
3237 }
3238 });
3239 }
3240};
3241
3242let keyboardType = {
3243 email: 'email-address',
3244 number: 'numeric',
3245 phone: 'phone-pad'
3246};
3247let enter$q = (node, parent, state) => {
3248 if (!node.isCapture) return;
3249
3250 if (node.name === 'CaptureTextArea') {
3251 state.render.push(` multiline={true}`);
3252 } else {
3253 let type = getProp(node, 'type'); // TODO warn on parser
3254 // TODO support file upload in RN
3255
3256 if (type.value === 'file') {
3257 type.value = 'text';
3258 }
3259
3260 if (type.value === 'secure') {
3261 state.render.push(` secureTextEntry`);
3262 } else {
3263 if (isSlot(type)) {
3264 let dynamicKeyboardType = `
3265 ${type.value} === 'email'?
3266 '${keyboardType.email}' :
3267 ${type.value} === 'number'?
3268 '${keyboardType.number}' :
3269 ${type.value} === 'phone'?
3270 '${keyboardType.phone}' : 'default'
3271 `;
3272 state.render.push(` keyboardType={${dynamicKeyboardType}}`);
3273 } else if (keyboardType[type.value]) {
3274 state.render.push(` keyboardType='${keyboardType[type.value]}'`);
3275 }
3276 }
3277 }
3278
3279 let autoCorrect = getProp(node, 'autoCorrect');
3280
3281 if (!autoCorrect) {
3282 state.render.push(` autoCorrect={false}`);
3283 }
3284
3285 let underlineColorAndroid = getProp(node, 'underlineColorAndroid');
3286
3287 if (!underlineColorAndroid) {
3288 state.render.push(` underlineColorAndroid="transparent"`);
3289 }
3290
3291 let textAlignVertical = getProp(node, 'textAlignVertical');
3292
3293 if (!textAlignVertical) {
3294 state.render.push(` textAlignVertical="top"`);
3295 }
3296};
3297
3298var getBlockName$1 = ((node, parent, state) => {
3299 switch (node.name) {
3300 case 'Capture':
3301 case 'CaptureTextArea':
3302 return 'TextInput';
3303
3304 case 'Horizontal':
3305 case 'Vertical':
3306 return getGroupBlockName$1(node, state);
3307
3308 case 'List':
3309 return getListBlockName(node, state);
3310
3311 case 'Text':
3312 if (node.isAnimated) {
3313 state.animated.add('Text');
3314 return 'AnimatedText';
3315 } else {
3316 return 'Text';
3317 }
3318
3319 case 'Block':
3320 case 'View':
3321 return 'React.Fragment';
3322
3323 default:
3324 return node.name;
3325 }
3326});
3327
3328let getGroupBlockName$1 = (node, state) => {
3329 let name = 'View';
3330
3331 if (node.isFragment) {
3332 name = 'React.Fragment';
3333 } else if (hasProp(node, 'goTo')) {
3334 node.goTo = true;
3335 let propNode = getProp(node, 'goTo');
3336 node.action = propNode.value;
3337 } else if (hasProp(node, 'onClick')) {
3338 let propNode = getProp(node, 'onClick');
3339 node.action = propNode.value;
3340 }
3341
3342 if (hasProp(node, 'backgroundImage')) {
3343 let propNode = getProp(node, 'backgroundImage');
3344 node.backgroundImage = isSlot(propNode) ? propNode.value : JSON.stringify(propNode.value);
3345 name = 'Image';
3346 } else if (hasProp(node, 'overflowY', v => v === 'auto' || v === 'scroll')) {
3347 name = 'ScrollView';
3348 }
3349
3350 if (node.isAnimated) {
3351 state.animated.add(name);
3352 name = `Animated${name}`;
3353 }
3354
3355 return name;
3356};
3357
3358let getListBlockName = (node, state) => {
3359 let base = hasProp(node, /^overflow/, v => v === 'auto' || v === 'scroll') ? 'FlatList' : 'View';
3360
3361 if (node.isAnimated) {
3362 state.animated.add(base);
3363 return `Animated${base}`;
3364 } else {
3365 return base;
3366 }
3367};
3368
3369function enter$r(node, parent, state) {
3370 // if (parent && !parent.isBasic && !node.isBasic) return true
3371 if (node.isFragment && node.children.length === 0) return true;
3372
3373 if (node.isChildren) {
3374 state.hasAlreadyDefinedChildren = true;
3375 state.render.push(`{typeof props.children === 'function'? props.children({ isSelected: props.isSelected`);
3376 let useIsHovered = !!getActionableParent(node);
3377
3378 if (useIsHovered) {
3379 state.useIsHovered = true;
3380 state.render.push(', isHovered, isSelectedHovered');
3381 }
3382
3383 state.render.push(`}) : props.children}`);
3384 return true;
3385 }
3386
3387 if (node.isFragment && node.name === 'View') {
3388 state.flow = getPropValueOrDefault(node, 'is', false);
3389 state.flowDefaultState = null;
3390 }
3391
3392 if (isStory(node, state)) {
3393 state.use('ViewsUseFlow');
3394 }
3395
3396 let name = getBlockName$1(node, parent, state);
3397 if (name === null) return true;
3398
3399 if (name === 'Animated.FlatList') {
3400 state.use('FlatList');
3401 name = 'AnimatedFlatList';
3402 }
3403
3404 state.use(node.isBasic ? name.replace(/^Animated/, '') : name, node.isLazy);
3405 node.nameFinal = name;
3406 if (handleTable(node, parent, state)) return true;
3407
3408 if (!node.isDefiningChildrenExplicitly) {
3409 state.render.push(`<${name}`);
3410 }
3411}
3412
3413let leave$9 = (node, parent, state) => {
3414 if (node.isFragment) return;
3415 let dynamicStyles = getNonAnimatedDynamicStyles(node);
3416 let baseStyle = null;
3417 let animatedStyle = null;
3418 let dynamicStyle = null;
3419 let containerStyle = null;
3420
3421 if (node.ensureBackgroundColor && (!('backgroundColor' in node.style.static.base) || !('backgroundColor' in node.style.dynamic.base))) {
3422 node.style.static.base.backgroundColor = 'transparent';
3423 }
3424
3425 if (hasKeys(node.style.static.base)) {
3426 let id = createId(node, state);
3427
3428 if (node.nameFinal.includes('FlatList') && hasContentContainerStyleProp(node.style.static.base)) {
3429 state.styles[`${id}ContentContainer`] = getContentContainerStyleProps(node.style.static.base);
3430 node.style.static.base = removeContentContainerStyleProps(node.style.static.base);
3431 containerStyle = `styles.${id}ContentContainer`;
3432 }
3433
3434 if (hasKeys(node.style.static.base)) {
3435 state.styles[id] = node.style.static.base;
3436 baseStyle = `styles.${id}`;
3437 }
3438 }
3439
3440 if (node.isAnimated) {
3441 animatedStyle = getAnimatedStyles(node, state.isReactNative);
3442 state.isAnimated = true;
3443 state.animations[node.id] = node.animations;
3444
3445 if (node.hasSpringAnimation) {
3446 state.hasSpringAnimation = true;
3447 }
3448
3449 if (node.hasTimingAnimation) {
3450 state.hasTimingAnimation = true;
3451 }
3452
3453 state.scopes = node.scopes;
3454 }
3455
3456 if (hasKeys(dynamicStyles)) {
3457 if (node.nameFinal.includes('FlatList') && hasContentContainerStyleProp(dynamicStyles)) {
3458 let dynamicContainerStyle = getObjectAsString(getContentContainerStyleProps(dynamicStyles));
3459 dynamicStyles = removeContentContainerStyleProps(dynamicStyles);
3460 containerStyle = containerStyle ? `[${containerStyle},${dynamicContainerStyle}]` : dynamicContainerStyle;
3461 }
3462
3463 if (hasKeys(dynamicStyles)) {
3464 dynamicStyle = getObjectAsString(dynamicStyles);
3465 dynamicStyle = dynamicStyle.substr(1, dynamicStyle.length - 2);
3466 }
3467 }
3468
3469 if (baseStyle || animatedStyle || dynamicStyle) {
3470 let style = baseStyle;
3471
3472 if (animatedStyle) {
3473 // TODO once https://github.com/drcmda/react-spring/issues/337 gets
3474 // fixed, come back to using the array notation
3475 style = `{...${baseStyle},${animatedStyle},${dynamicStyle || ''}}`;
3476 } else if (dynamicStyle) {
3477 if (state.morpher === 'react-pdf') {
3478 style = `{...${baseStyle}, ${dynamicStyle}}`;
3479 } else {
3480 style = `[${baseStyle},{${dynamicStyle}}]`;
3481 }
3482 }
3483
3484 state.render.push(` style={${style}}`);
3485 }
3486
3487 if (containerStyle) {
3488 state.render.push(` contentContainerStyle={${containerStyle}}`);
3489 }
3490};
3491
3492let 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'];
3493var isValidPropertyForBlock$1 = ((node, parent, state) => !blacklist$1.includes(node.name.toLowerCase()));
3494
3495function enter$s(node, parent, state) {
3496 if (node.isFragment) return false;
3497 enter$e(node, parent, state);
3498 enter$f(node, parent, state);
3499 enter$k(node, parent, state);
3500 node.properties.forEach(propNode => {
3501 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;
3502 !enter$h(propNode, node, state) && !enter$i(propNode, node, state) && !enter$j(propNode, node, state) && enter$3(propNode, node, state);
3503 });
3504 leave$9(node, parent, state);
3505}
3506
3507let SVG_BLOCKS_WITH_OPACITY = ['SvgPath', 'SvgCircle', 'SvgEllipse', 'SvgPolygon', 'SvgPolyline', 'SvgRect', 'SvgText'];
3508function enter$t(node, parent, state) {
3509 if (node.name === 'Svg' && !parent) {
3510 state.isSvg = true;
3511 state.svgOpacity = getProp(node, 'opacity');
3512 } else if (state.isSvg && state.svgOpacity && SVG_BLOCKS_WITH_OPACITY.includes(node.name)) {
3513 if (hasProp(node, 'fill')) {
3514 node.properties.push({ ...state.svgOpacity,
3515 name: 'fillOpacity'
3516 });
3517 }
3518
3519 if (hasProp(node, 'stroke')) {
3520 node.properties.push({ ...state.svgOpacity,
3521 name: 'strokeOpacity'
3522 });
3523 }
3524 }
3525}
3526
3527let enter$u = (node, parent, state) => {
3528 let name = getBlockName$1(node, parent, state);
3529
3530 if (name === 'Text' && parent && (parent.backgroundImage || parent.ensureBackgroundColor)) {
3531 node.ensureBackgroundColor = true;
3532 }
3533
3534 if (node.action) {
3535 let block = 'TouchableWithoutFeedback';
3536 let isDisabled = getProp(node, 'isDisabled');
3537 let onClick = getProp(node, 'onClick');
3538 let onPress = wrap(onClick.value);
3539
3540 if (/^on[A-Z]/.test(onClick.name) && onClick.slotName === 'setFlowTo') {
3541 // TODO warn if action is used but it isn't in actions (on parser)
3542 // TODO warn that there's setFlowTo without an id (on parser)
3543 let setFlowTo = getFlowPath(onClick, parent, state);
3544 state.use('ViewsUseFlow');
3545 state.setFlowTo = true;
3546 onPress = `{() => setFlowTo('${setFlowTo}')}`;
3547 }
3548
3549 let key = getProp(node, 'key');
3550 state.use(block);
3551 state.render.push(`<${block}
3552 activeOpacity={0.7}
3553 onPress=${onPress}
3554 ${isDisabled ? `disabled=${wrap(isDisabled.value)}` : ''}
3555 underlayColor='transparent'
3556 ${node.isInList ? `key={${key ? key.value : 'index'}}` : ''}>`);
3557 node.wrapEnd = `</${block}>`;
3558 } else if (node.goTo) ;
3559};
3560let leave$a = (node, parent, state) => {
3561 if (node.wrapEnd) {
3562 state.render.push(node.wrapEnd);
3563 }
3564};
3565
3566let 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];
3567let leave$b = [leave$2, leave, leave$1, leave$3, leave$7, leave$a, leave$4];
3568
3569var visitor$1 = /*#__PURE__*/Object.freeze({
3570 __proto__: null,
3571 enter: enter$v,
3572 leave: leave$b
3573});
3574
3575function getStyleForProperty$1(node, parent, state, code) {
3576 let scopedCondition = getScopedCondition(node, parent, state);
3577
3578 if (scopedCondition) {
3579 switch (node.name) {
3580 case 'rotate':
3581 case 'rotateX':
3582 case 'rotateY':
3583 case 'scale':
3584 case 'scaleX':
3585 case 'scaleY':
3586 case 'translateX':
3587 case 'translateY':
3588 return {
3589 _isScoped: true,
3590 transform: getTransform$1(node, parent, state)
3591 };
3592
3593 default:
3594 return {
3595 _isScoped: true,
3596 [node.name]: getScopedCondition(node, parent, state)
3597 };
3598 }
3599 }
3600
3601 switch (node.name) {
3602 case 'borderTopLeftRadius':
3603 case 'borderTopRightRadius':
3604 case 'borderBottomLeftRadius':
3605 case 'borderBottomRightRadius':
3606 return {
3607 [parent.name === 'Image' ? 'borderRadius' : node.name]: node.value
3608 };
3609
3610 case 'borderTopStyle':
3611 case 'borderBottomStyle':
3612 case 'borderLeftStyle':
3613 case 'borderRightStyle':
3614 return {
3615 borderStyle: node.value
3616 };
3617
3618 case 'shadowColor':
3619 return decorateShadow({
3620 shadowColor: node.value
3621 });
3622
3623 case 'shadowBlur':
3624 return decorateShadow({
3625 shadowRadius: node.value
3626 });
3627
3628 case 'shadowOffsetX':
3629 case 'shadowOffsetY':
3630 return getShadowOffset(node, parent);
3631
3632 case 'fontWeight':
3633 case 'fontFamily':
3634 return {
3635 fontFamily: getFontFamily(node, parent)
3636 };
3637
3638 case 'rotate':
3639 case 'rotateX':
3640 case 'rotateY':
3641 case 'scale':
3642 case 'scaleX':
3643 case 'scaleY':
3644 case 'translateX':
3645 case 'translateY':
3646 return {
3647 transform: getTransform$1(node, parent, state)
3648 };
3649
3650 case 'zIndex':
3651 return {
3652 zIndex: code ? node.value : parseInt(node.value, 10)
3653 };
3654
3655 case 'color':
3656 // TODO handle this but differently as we don't have the placeholder tag anymore
3657 if (/Capture/.test(parent.name) && isTag(node, 'placeholder')) {
3658 return {
3659 _isProp: true,
3660 placeholderTextColor: node.value
3661 };
3662 } // Just returning the node.value in cases where if statement is not true
3663 // Otherwise it was falling through to the next case.
3664
3665
3666 return {
3667 color: node.value
3668 };
3669
3670 case 'lineHeight':
3671 return {
3672 lineHeight: getLineHeight(node, parent)
3673 };
3674
3675 default:
3676 return {
3677 [node.name]: maybeMakeHyphenated(node)
3678 };
3679 }
3680}
3681
3682let getFontFamily = (node, parent, state) => {
3683 let fontWeight = getProp(parent, 'fontWeight'); // let key = node.key.value
3684
3685 let fontFamily = node.value.replace(/\s/g, '');
3686
3687 if (fontWeight && (node.tags.slot || fontWeight.tags.slot)) {
3688 return `\`${node.tags.slot ? '${props.fontFamily}' : fontFamily}-${fontWeight.tags.slot ? '${props.fontWeight}' : fontWeight.value}\``;
3689 }
3690
3691 return fontWeight ? `${fontFamily}-${fontWeight.value}` : fontFamily;
3692};
3693
3694let getLineHeight = (node, parent, state) => {
3695 let fontSize = getProp(parent, 'fontSize'); // using a default font size of 16 if none specified
3696
3697 let fontSizeValue = fontSize ? fontSize.value : 16;
3698 return node.value * fontSizeValue;
3699};
3700
3701let getShadowOffset = (node, parent, state) => {
3702 let shadowOffsetX = getProp(parent, 'shadowOffsetX');
3703 let shadowOffsetY = getProp(parent, 'shadowOffsetY');
3704 return decorateShadow({
3705 // iOS,
3706 shadowOffset: {
3707 width: shadowOffsetX ? shadowOffsetX.value : 0,
3708 height: shadowOffsetY ? shadowOffsetY.value : 0
3709 }
3710 });
3711};
3712
3713let decorateShadow = obj => {
3714 obj.elevation = 1; // for Android
3715
3716 obj.shadowOpacity = 1;
3717 return obj;
3718};
3719
3720let getPropValue$2 = (prop, block, state, unit = '') => {
3721 if (!prop) return false;
3722 let scopedCondition = getScopedCondition(prop, block, state);
3723
3724 if (scopedCondition) {
3725 return unit ? `\`\${${scopedCondition}}${unit}\`` : scopedCondition;
3726 }
3727
3728 if (prop.tags.slot) {
3729 return `\${${prop.value}}${unit}`;
3730 }
3731
3732 return typeof prop.value === 'number' && unit ? `${prop.value}${unit}` : prop.value;
3733};
3734
3735let getTransformValue$1 = (prop, parent, state, unit) => prop && {
3736 [prop.name]: getPropValue$2(prop, parent, state, unit)
3737};
3738
3739let getTransform$1 = (node, parent, state) => {
3740 let rotate = getProp(parent, 'rotate');
3741 let rotateX = getProp(parent, 'rotateX');
3742 let rotateY = getProp(parent, 'rotateY');
3743 let scale = getProp(parent, 'scale');
3744 let scaleX = getProp(parent, 'scaleX');
3745 let scaleY = getProp(parent, 'scaleY');
3746 let translateX = getProp(parent, 'translateX');
3747 let translateY = getProp(parent, 'translateY');
3748 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);
3749};
3750
3751var getStyles$1 = (({
3752 styles
3753}) => hasKeys(styles) ? `let styles = StyleSheet.create(${JSON.stringify(styles)})` : '');
3754
3755let isUrl$1 = str => /^https?:\/\//.test(str);
3756
3757function getImageSource$1(node, parent, state) {
3758 let scopes = getScopes(node, parent);
3759
3760 if (scopes && (isUrl$1(node.value) || node.tags.slot)) {
3761 return `{{ uri: ${getScopedCondition(node, parent, state)} }}`;
3762 } else if (isUrl$1(node.value) || node.tags.slot) {
3763 if (node.defaultValue && !isUrl$1(node.defaultValue)) {
3764 state.slots.forEach(item => {
3765 if (item.defaultValue === node.defaultValue) {
3766 item.type = 'import';
3767 let name = toCamelCase(item.defaultValue);
3768
3769 if (!state.images.includes(item.defaultValue)) {
3770 state.images.push({
3771 name,
3772 file: item.defaultValue
3773 });
3774 }
3775
3776 item.defaultValue = name;
3777 }
3778 });
3779 }
3780
3781 return `{{ uri: ${node.tags.slot ? node.value : safe(node.value)} }}`;
3782 } else {
3783 if (scopes) {
3784 pushImageToState(state, scopes.scopedNames, scopes.paths);
3785 }
3786
3787 let name = toCamelCase(node.value);
3788
3789 if (!state.images.includes(node.value)) {
3790 state.images.push({
3791 name,
3792 file: node.value
3793 });
3794 }
3795
3796 return scopes ? wrap(getScopedImageCondition(scopes.scopedProps, scopes.scopedNames, name)) : `{${name}}`;
3797 }
3798}
3799
3800let CHILD_VALUES$3 = /props\.(isSelected|isHovered|isFocused|isSelectedHovered)/;
3801let ON_IS_SELECTED$1 = /(onClick|onPress|goTo)/;
3802function getValueForProperty$1(node, parent, state) {
3803 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')) {
3804 return {
3805 [node.name]: `{${node.value.replace('props.', 'data.')}}`
3806 };
3807 } else if (/!?props\.(isFlow|flow)$/.test(node.value)) {
3808 let flowPath = getFlowPath(node, parent, state);
3809 state.use('ViewsUseFlow');
3810 state.useFlow = true;
3811 return {
3812 [node.name]: `{${node.value.replace(/props\.(isFlow|flow)/, `flow.has('${flowPath}')`)}}`
3813 };
3814 } else if (hasCustomBlockParent(parent) && CHILD_VALUES$3.test(node.value)) {
3815 return {
3816 [node.name]: `{${node.value.replace('props.', 'childProps.')}}`
3817 };
3818 } else if (isValidImgSrc(node, parent)) {
3819 return !parent.isSvg && {
3820 source: getImageSource$1(node, parent, state)
3821 };
3822 } else if (getScopedCondition(node, parent, state)) {
3823 return {
3824 [node.name]: safe(getScopedCondition(node, parent, state))
3825 };
3826 } else if (/^on[A-Z]/.test(node.name) && node.slotName === 'setFlowTo') {
3827 let flowPath = getFlowPath(node, parent, state);
3828 state.use('ViewsUseFlow');
3829 state.setFlowTo = true;
3830 let ret = {
3831 [node.name]: `{() => setFlowTo('${flowPath}')}`
3832 };
3833
3834 if (!parent.isBasic && ON_IS_SELECTED$1.test(node.name)) {
3835 state.useFlow = true;
3836 ret[node.name.replace(ON_IS_SELECTED$1, 'isSelected')] = `{flow.has('${flowPath}')}`;
3837 }
3838
3839 if (parent.isBasic && ON_IS_SELECTED$1.test(node.name)) {
3840 state.useIsHovered = true;
3841 ret['...'] = `${node.name.replace(ON_IS_SELECTED$1, 'isHovered')}Bind`;
3842 }
3843
3844 return ret;
3845 } else if (parent.isBasic && ON_IS_SELECTED$1.test(node.name)) {
3846 state.useIsHovered = true;
3847 return {
3848 '...': `${node.name.replace(ON_IS_SELECTED$1, 'isHovered')}Bind`,
3849 [node.name]: safe(node.value, node)
3850 };
3851 } else if (CHILD_VALUES$3.test(node.value) && !!getActionableParent(parent) && !parent.isBasic) {
3852 // TODO support more than one hover/selected in the same block - let's wait
3853 // for a use case
3854 if (/isHovered/.test(node.value)) {
3855 return {
3856 [node.name]: safe(node.value.replace('props.', ''), node)
3857 };
3858 } else if (/isSelected/.test(node.value)) {
3859 try {
3860 let actionableParent = getActionableParent(parent);
3861 let flowPath = getFlowPath(getProp(actionableParent, 'onClick'), actionableParent, state);
3862 state.use('ViewsUseFlow');
3863 state.useFlow = true;
3864 return {
3865 [node.name]: `{flow.has('${flowPath}')}`
3866 };
3867 } catch (error) {
3868 return {
3869 [node.name]: safe(node.value, node)
3870 };
3871 }
3872 } else {
3873 return {
3874 [node.name]: safe(node.value, node)
3875 };
3876 }
3877 } else {
3878 return {
3879 [node.name]: safe(node.value, node)
3880 };
3881 }
3882}
3883
3884var maybeUsesStyleSheet = (state => {
3885 if (Object.keys(state.styles).length > 0) {
3886 state.uses.push('StyleSheet');
3887 }
3888});
3889
3890let imports$1 = {
3891 DismissKeyboard: `import dismissKeyboard from 'dismissKeyboard'`
3892};
3893var reactNative = (({
3894 getFontImport,
3895 getSystemImport,
3896 morpher = 'react-native',
3897 reactNativeLibraryImport = 'react-native',
3898 src,
3899 tools,
3900 view,
3901 viewsById,
3902 viewsToFiles
3903}) => {
3904 let name = view.id;
3905 let finalName = restrictedNames.includes(name) ? `${name}1` : name;
3906 let state = {
3907 animations: {},
3908 animated: new Set(),
3909 images: [],
3910 data: view.parsed.view.data,
3911 dataFormat: view.parsed.view.dataFormat,
3912 dataValidate: view.parsed.view.dataValidate,
3913 dependencies: new Set(),
3914 flow: null,
3915 setFlowTo: false,
3916 getFontImport: font => getFontImport(font, view),
3917 getStyleForProperty: getStyleForProperty$1,
3918 getValueForProperty: getValueForProperty$1,
3919 hasRefs: false,
3920 isReactNative: true,
3921 isStory: id => {
3922 let viewInView = getViewRelativeToView({
3923 id,
3924 view,
3925 viewsById,
3926 viewsToFiles
3927 });
3928 return viewInView && !viewInView.custom && viewInView.parsed.view.isStory;
3929 },
3930 lazy: {},
3931 morpher,
3932 name: finalName,
3933 viewPath: view.parsed.view.viewPath,
3934 viewPathParent: view.parsed.view.viewPathParent,
3935 remap: {},
3936 render: [],
3937 styles: {},
3938 testIdKey: 'testID',
3939 viewPathKey: 'testID',
3940 testIds: {},
3941 tools,
3942 reactNativeLibraryImport,
3943 usedBlockNames: {
3944 [finalName]: 1,
3945 AutoSizer: 1,
3946 Column: 1,
3947 Table: 1
3948 },
3949 uses: [],
3950
3951 use(block, isLazy = false) {
3952 if (isLazy) {
3953 state.lazy[block] = true;
3954 }
3955
3956 if (state.uses.includes(block) || /props/.test(block) || 'React.Fragment' === block) return;
3957
3958 if (block === finalName) {
3959 state.name = `${view.id}${state.usedBlockNames[finalName]++}`;
3960 }
3961
3962 state.uses.push(block);
3963 },
3964
3965 useIsBefore: view.parsed.view.useIsBefore,
3966 useIsMedia: view.parsed.view.useIsMedia
3967 };
3968 state.fonts = view.parsed.fonts;
3969 state.slots = view.parsed.slots;
3970 walk(view.parsed.view, visitor$1, state);
3971 maybeUsesStyleSheet(state);
3972
3973 if (state.data) {
3974 state.use('ViewsUseData');
3975 }
3976
3977 return {
3978 code: toComponent({
3979 getImport: makeGetImport({
3980 imports: imports$1,
3981 getSystemImport,
3982 src,
3983 view,
3984 viewsById,
3985 viewsToFiles
3986 }),
3987 getStyles: getStyles$1,
3988 name: finalName,
3989 state
3990 }),
3991 dependencies: state.dependencies,
3992 flow: state.flow,
3993 flowDefaultState: state.flowDefaultState // fonts: view.parsed.fonts,
3994 // slots: view.parsed.slots,
3995
3996 };
3997});
3998
3999var reactPdf = (options => reactNative({ ...options,
4000 morpher: 'react-pdf',
4001 reactNativeLibraryImport: '@react-pdf/renderer'
4002}));
4003
4004var morphers = {
4005 'react-dom': reactDom,
4006 'react-native': reactNative,
4007 'react-pdf': reactPdf
4008};
4009
4010function maybeMorph({
4011 as,
4012 getFontImport,
4013 getSystemImport,
4014 src,
4015 tools,
4016 view,
4017 viewsById,
4018 viewsToFiles,
4019 verbose
4020}) {
4021 let result = null;
4022
4023 try {
4024 result = morphers[as]({
4025 getFontImport,
4026 getSystemImport,
4027 src,
4028 tools,
4029 view,
4030 viewsById,
4031 viewsToFiles
4032 });
4033 view.version++;
4034 verbose && console.log(`${chalk.green('M')} ${view.id}@${view.version}:${chalk.dim(view.file)}`);
4035 return result.code; // return prettier.format(result.code, {
4036 // parser: 'babel',
4037 // singleQuote: true,
4038 // trailingComma: 'es5',
4039 // })
4040 } catch (error) {
4041 console.error(chalk.red('M'), view, error.codeFrame || error);
4042 return `import { useEffect } from 'react'
4043
4044export default function ${view.id}() {
4045 useEffect(() => {
4046 console.error({
4047 type: 'morph',
4048 view: '${view.id}',
4049 file: '${view.file}',
4050 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!",
4051 })
4052 }, [])
4053
4054 return "😳 Can't morph '${view.id}'. See console for more details."
4055}
4056
4057/*
4058>>> CODE
4059${result && result.code.replace(/(\/\*|\*\/)/g, '')}
4060
4061
4062>>> ERROR
4063${error.message}
4064${error.stack}
4065*/`;
4066 }
4067}
4068
4069function morphAllViews({
4070 as,
4071 getFontImport,
4072 getSystemImport,
4073 filesView,
4074 src,
4075 tools,
4076 viewsById,
4077 viewsToFiles
4078}) {
4079 return [...filesView].map(file => viewsToFiles.get(file)).filter(view => !view.custom).map(view => ({
4080 file: path.join(path.dirname(view.file), 'view.js'),
4081 content: maybeMorph({
4082 as,
4083 getFontImport,
4084 getSystemImport,
4085 src,
4086 tools,
4087 verbose: false,
4088 view,
4089 viewsById,
4090 viewsToFiles
4091 })
4092 }));
4093}
4094
4095function maybePrintWarnings(view, verbose) {
4096 if (!verbose || !view.parsed || view.parsed.warnings.length === 0) return;
4097 console.error(chalk.red(view.id), chalk.dim(view.file));
4098 view.parsed.warnings.forEach(warning => {
4099 console.error(` ${chalk.yellow(warning.loc.start.line)}: ${chalk.blue(warning.type)} Line: "${warning.line}"`);
4100 });
4101 console.log('');
4102}
4103
4104let 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',
4105// '@keyframes',
4106// '@media',
4107// 'background',
4108// 'backgroundAttachment',
4109// 'backgroundBlend-mode',
4110// 'backgroundClip',
4111// 'backgroundOrigin',
4112// 'backgroundPosition',
4113// 'backgroundRepeat',
4114// 'border',
4115// 'borderBottom',
4116// 'borderCollapse',
4117// 'borderImage',
4118// 'borderImageOutset',
4119// 'borderImageRepeat',
4120// 'borderImageSlice',
4121// 'borderImageSource',
4122// 'borderImageWidth',
4123// 'borderLeft',
4124// 'borderRadius',
4125// 'borderRight',
4126// 'borderSpacing',
4127// 'borderTop',
4128// 'boxShadow',
4129// 'boxSizing',
4130// 'captionSide',
4131// 'clear',
4132// 'clip',
4133// 'column-gap',
4134// 'columnCount',
4135// 'columnFill',
4136// 'columnRule',
4137// 'columnRuleColor',
4138// 'columnRuleStyle',
4139// 'columnRuleWidth',
4140// 'columnSpan',
4141// 'columnWidth',
4142// 'columns',
4143// 'content',
4144// 'counterIncrement',
4145// 'counterReset',
4146// 'direction',
4147// 'display',
4148// 'emptyCells',
4149// 'float',
4150// 'font',
4151// 'font-variant',
4152// 'fontSizeAdjust',
4153// 'fontStretch',
4154// 'hangingPunctuation',
4155// 'listStyle',
4156// 'listStyleImage',
4157// 'listStylePosition',
4158// 'listStyleType',
4159// 'margin',
4160// 'nav-up',
4161// 'navDown',
4162// 'navIndex',
4163// 'navLeft',
4164// 'navRight',
4165'order', // 'outline',
4166// 'overflow',
4167// 'padding',
4168// 'pageBreakAfter',
4169// 'pageBreakBefore',
4170// 'pageBreakInside',
4171'perspective', 'perspectiveOrigin', // 'quotes',
4172// 'tabSize',
4173// 'tableLayout',
4174// 'textAlignLast',
4175'textDecorationColor', 'textDecorationLine', 'textDecorationStyle', // 'textIndent',
4176// 'textShadow',
4177// 'transformStyle',
4178// 'transitionDelay',
4179// 'transitionDuration',
4180// 'transitionProperty',
4181// 'transitionTimingFunction',
4182// 'unicodeBidi',
4183// 'verticalAlign',
4184'visibility'];
4185let ROWSTYLE = ['rowBackgroundColor', 'rowBackgroundColorAlternate', 'rowColor', 'rowColorAlternate'];
4186let isStyle$1 = name => STYLE.includes(name);
4187let isRowStyle = name => ROWSTYLE.includes(name);
4188
4189var isNumber = {
4190 width: true,
4191 maxWidth: true,
4192 minWidth: true,
4193 height: true,
4194 maxHeight: true,
4195 minHeight: true,
4196 margin: true,
4197 marginBottom: true,
4198 marginLeft: true,
4199 marginRight: true,
4200 marginTop: true,
4201 padding: true,
4202 paddingBottom: true,
4203 paddingLeft: true,
4204 paddingRight: true,
4205 paddingTop: true,
4206 bottom: true,
4207 left: true,
4208 right: true,
4209 top: true,
4210 outline: true,
4211 opacity: true,
4212 zIndex: true,
4213 shadowOffsetY: true,
4214 shadowOffsetX: true,
4215 shadowSpread: true,
4216 shadowRadius: true,
4217 perspective: true,
4218 scale: true,
4219 scaleX: true,
4220 scaleY: true,
4221 translateX: true,
4222 translateY: true,
4223 borderBottomLeftRadius: true,
4224 borderBottomRightRadius: true,
4225 borderBottomWidth: true,
4226 borderLeftWidth: true,
4227 borderRadius: true,
4228 borderRightWidth: true,
4229 borderTopLeftRadius: true,
4230 borderTopRightRadius: true,
4231 borderTopWidth: true,
4232 borderWidth: true,
4233 fontSize: true,
4234 fontWeight: true,
4235 letterSpacing: true,
4236 lineHeight: true,
4237 wordSpacing: true,
4238 strokeDasharray: true,
4239 strokeDashoffset: true
4240};
4241
4242// export let makeDidYouMeanFontFamily = (customFonts) => {
4243// let dymFontFamilyMatcher = new DidYouMeanMatcher(
4244// googleFontFamilies.concat(customFonts)
4245// )
4246// return (family) => dymFontFamilyMatcher.get(family)
4247// }
4248
4249let ANIMATION = /(.+)(?:\s)(spring|linear|easeOut|easeInOut|easeIn|ease)(?:\s?(.*)?)/;
4250let 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;
4251let BLOCK = /^(\s*)([A-Z][a-zA-Z0-9]*)(\s+([A-Z][a-zA-Z0-9]*))?$/;
4252let BOOL = /^(false|true)$/i;
4253let CAPTURE = /^(Capture|CaptureTextArea)$/i;
4254let CAPTURE_TYPES = ['email', 'file', 'number', 'phone', 'secure', 'text'];
4255let COMMENT = /^#(.+)$/;
4256let FLOAT = /^-?[0-9]+\.[0-9]+$/;
4257let FONTABLE = /^(Capture|CaptureTextArea|Text)$/;
4258let INT = /^-?[0-9]+$/;
4259let NOT_GROUP = /^(Image|Text|SvgCircle|SvgEllipse|SvgLine|SvgPath|SvgPolygon|SvgPolyline|SvgRect|SvgText|SvgStop)$/i;
4260let PROP = /^([a-z][a-zA-Z0-9]*)(\s+(.+))?$/;
4261let UNSUPPORTED_SHORTHAND = {
4262 border: ['borderWidth', 'borderStyle', 'borderColor'],
4263 borderBottom: ['borderBottomWidth', 'borderBottomStyle', 'borderBottomColor'],
4264 borderTop: ['borderTopWidth', 'borderTopStyle', 'borderTopColor'],
4265 borderRight: ['borderRightWidth', 'borderRightStyle', 'borderRightColor'],
4266 borderLeft: ['borderLeftWidth', 'borderLeftStyle', 'borderLeftColor'],
4267 borderRadius: ['borderTopLeftRadius', 'borderTopRightRadius', 'borderBottomLeftRadius', 'borderBottomRightRadius'],
4268 boxShadow: ['shadowOffsetX', 'shadowOffsetY', 'shadowBlur', 'shadowSpread', 'shadowColor'],
4269 flex: ['flexGrow', 'flexShrink', 'flexBasis'],
4270 margin: ['marginTop', 'marginRight', 'marginBottom', 'marginLeft'],
4271 padding: ['paddingTop', 'paddingRight', 'paddingBottom', 'paddingLeft'],
4272 textShadow: ['shadowOffsetX', 'shadowOffsetY', 'shadowBlur', 'shadowColor'],
4273 outline: ['outlineWidth', 'outlineStyle', 'outlineColor'],
4274 overflow: ['overflowX', 'overflowY'],
4275 transform: ['rotate', 'rotateX', 'rotateY', 'scale', 'translateX', 'translateY'],
4276 transformOrigin: ['transformOriginX', 'transformOriginY']
4277};
4278let TRUE = /^true$/i;
4279let USER_COMMENT = /^##(.*)$/; // TODO slot can't start with a number
4280
4281let SLOT = /^<((!)?([a-zA-Z0-9]+))?(\s+(.+))?$/;
4282let is = (thing, line) => thing.test(line); // TODO only parse animations on lines with less than 250 chars because longer
4283// lines are too expensive for that regex - like svg paths
4284
4285let isAnimation = line => line.length < 250 && is(ANIMATION, line);
4286let isBasic = line => is(BASIC, line);
4287let isBlock = line => is(BLOCK, line);
4288let isBool = line => is(BOOL, line);
4289let isCapture = line => is(CAPTURE, line);
4290let isColumn$1 = line => line === 'Column';
4291let isComment = line => is(COMMENT, line);
4292let isEmptyText = line => line === '';
4293let isFloat = line => is(FLOAT, line);
4294let isFragment = line => line === 'isFragment';
4295let isFontable = line => is(FONTABLE, line);
4296let isGroup = line => !is(NOT_GROUP, line) && !isCapture(line);
4297let isList$1 = line => line === 'List';
4298let isInt = line => is(INT, line);
4299let isProp = line => is(PROP, line);
4300let isTable$1 = line => line === 'Table';
4301let isUnsupportedShorthand = name => name in UNSUPPORTED_SHORTHAND;
4302let isTrue = line => is(TRUE, line);
4303let isUserComment = line => is(USER_COMMENT, line);
4304
4305let get = (regex, line) => line.match(regex);
4306
4307let addDefaults = (animationType, properties) => {
4308 // if (!properties.delay) {
4309 // properties.delay = 0
4310 // }
4311 if (animationType !== 'spring' && !properties.duration) {
4312 properties.duration = 150;
4313 } else if (animationType === 'spring') {
4314 if (!properties.tension) {
4315 properties.tension = 170;
4316 }
4317
4318 if (!properties.friction) {
4319 properties.friction = 26;
4320 }
4321 }
4322
4323 return properties;
4324};
4325
4326let getAnimation = line => {
4327 // eslint-disable-next-line
4328 let [_, defaultValue, animationType, animationValues] = get(ANIMATION, line);
4329 let properties = {
4330 curve: animationType
4331 };
4332
4333 if (animationValues) {
4334 let values = animationValues.split(' ');
4335
4336 for (let i = 0; i < values.length; i = i + 2) {
4337 properties[values[i]] = getValue(values[i + 1]);
4338 }
4339 }
4340
4341 addDefaults(animationType, properties);
4342 return {
4343 id: Object.keys(properties).sort().map(key => `${key}${properties[key]}`).join(''),
4344 defaultValue: getValue(defaultValue),
4345 properties
4346 };
4347};
4348let getBlock = line => {
4349 // eslint-disable-next-line
4350 let [_, indentation, is, _1, block] = get(BLOCK, line);
4351 return {
4352 block: block || is,
4353 is: block ? is : null,
4354 level: Math.floor(indentation.length / 2)
4355 };
4356};
4357let getComment = line => {
4358 try {
4359 return get(COMMENT, line).slice(1);
4360 } catch (err) {
4361 return '';
4362 }
4363};
4364let getData = maybeProp => {
4365 if (!maybeProp) return null;
4366 let match = maybeProp.value.match(/^((show|capture)\s+)?(.+)$/);
4367 if (!match) return null;
4368 let path = match[3];
4369 let [context = null] = /\./.test(path) ? path.split('.') : [path];
4370 return {
4371 path,
4372 context
4373 };
4374};
4375let getDataFormat = maybeProp => {
4376 if (!maybeProp || !maybeProp.value) return null;
4377 let [formatIn, formatOut] = maybeProp.value.split(' ');
4378 return {
4379 type: 'js',
4380 formatIn,
4381 formatOut
4382 };
4383};
4384let getDataValidate = maybeProp => {
4385 if (!maybeProp || !maybeProp.value) return null;
4386 let [value, required] = maybeProp.value.split(' ');
4387 return {
4388 type: 'js',
4389 value,
4390 required: required === 'required'
4391 };
4392};
4393let getFormat = line => {
4394 let properties = {};
4395 let values = line.split(' ');
4396 let formatKey = values[0];
4397
4398 if (values.length === 2) {
4399 properties[formatKey] = values[1];
4400 } else {
4401 properties[formatKey] = {};
4402
4403 for (let i = 1; i < values.length; i = i + 2) {
4404 properties[formatKey][values[i]] = getValue(values[i + 1]);
4405 }
4406 }
4407
4408 return properties;
4409};
4410let getProp$1 = line => {
4411 // eslint-disable-next-line
4412 let [_, name, _1, value = ''] = get(PROP, line);
4413 let prop = {
4414 name,
4415 isSlot: false,
4416 value
4417 };
4418
4419 if (is(SLOT, value)) {
4420 let [// eslint-disable-next-line
4421 _2, slotIsNot = false, slotName = '', // eslint-disable-next-line
4422 _3, // eslint-disable-next-line
4423 defaultValue = ''] = getSlot(value);
4424 prop.isSlot = true;
4425 prop.slotIsNot = slotIsNot === '!';
4426 prop.slotName = slotName;
4427 prop.value = defaultValue || value;
4428 }
4429
4430 return prop;
4431};
4432let getSlot = line => get(SLOT, line).slice(1);
4433let getUnsupportedShorthandExpanded = (name, value) => {
4434 let props = UNSUPPORTED_SHORTHAND[name];
4435
4436 if (name === 'borderRadius') {
4437 let theValue = value.replace('px', '');
4438 return [`${props[0]} ${theValue}`, `${props[1]} ${theValue}`, `${props[2]} ${theValue}`, `${props[3]} ${theValue}`];
4439 } else if (name.startsWith('border') || name === 'outline') {
4440 let [width, style, ...color] = value.split(' ');
4441 return [`${props[0]} ${width.replace('px', '')}`, `${props[1]} ${style}`, `${props[2]} ${color.join(' ')}`];
4442 } else if (name === 'boxShadow') {
4443 let [offsetX, offsetY, blurRadius, spreadRadius, ...color] = value.split(' ');
4444 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(' ')}`];
4445 } else if (name === 'textShadow') {
4446 let [offsetX, offsetY, blurRadius, ...color] = value.split(' ');
4447 return [`${props[0]} ${offsetX.replace('px', '')}`, `${props[1]} ${offsetY.replace('px', '')}`, `${props[2]} ${blurRadius.replace('px', '')}`, `${props[3]} ${color.join(' ')}`];
4448 } else if (name === 'overflow') {
4449 return [`${props[0]} ${value}`, `${props[1]} ${value}`];
4450 } else if (name === 'padding' || name === 'margin') {
4451 let [top, right, bottom, left] = value.split(' ');
4452 top = top.replace('px', '');
4453 right = right ? right.replace('px', '') : top;
4454 bottom = bottom ? bottom.replace('px', '') : top;
4455 left = left ? left.replace('px', '') : right || top;
4456 return [`${props[0]} ${top}`, `${props[1]} ${right}`, `${props[2]} ${bottom}`, `${props[3]} ${left}`];
4457 } else if (name === 'flex') {
4458 return [`flexGrow ${value}`, 'flexShrink 1', 'flexBasis 0%'];
4459 } else if (name === 'transform') {
4460 return [`expand the values like: translateX 10`];
4461 } else if (name === 'transformOrigin') {
4462 let [x, y] = value.split(' ');
4463 return [`${props[0]} ${x}`, `${props[1]} ${y || x}`];
4464 }
4465
4466 return [];
4467};
4468let getValue = (value, name) => {
4469 if (isFloat(value)) {
4470 return parseFloat(value, 10);
4471 } else if (isInt(value)) {
4472 return parseInt(value, 10);
4473 } else if (isEmptyText(value)) {
4474 return '';
4475 } else if (isBool(value)) {
4476 return isTrue(value);
4477 } else {
4478 return maybeMakeHyphenated$1(value, name);
4479 }
4480};
4481let SYSTEM_SCOPES = ['isDisabled', 'isFocused', // 'isHovered',
4482'isPlaceholder' // 'isSelected',
4483// TODO do we want to do media queries here?
4484];
4485let isSystemScope = name => SYSTEM_SCOPES.includes(name);
4486
4487let isActionable = name => name !== 'onWhen' && /^on[A-Z]/.test(name);
4488
4489let getPropType = (block, name, defaultValue) => block.isList && name === 'from' ? 'array' : isActionable(name) ? 'function' : isNumber[name] ? 'number' : 'string';
4490let 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'];
4491let maybeMakeHyphenated$1 = (value, name) => MAYBE_HYPHENATED_STYLE_PROPS$1.includes(name) && /^[a-zA-Z]+$/.test(value) ? toSlugCase(value) : value;
4492function sortScopes(scopes) {
4493 let isHovered = scopes.find(item => item.slotName === 'isHovered');
4494 let isSelected = scopes.find(item => item.slotName === 'isSelected');
4495 let isSelectedHovered = scopes.find(item => item.slotName === 'isSelectedHovered');
4496 return [isSelectedHovered, isSelected, isHovered, ...scopes.filter(item => !/^(isHovered|isSelected|isSelectedHovered)$/.test(item.slotName))].filter(Boolean);
4497}
4498
4499var getLoc = ((line, scolumn, ecolumn) => ({
4500 start: {
4501 line,
4502 column: scolumn
4503 },
4504 end: {
4505 line,
4506 column: ecolumn
4507 }
4508}));
4509
4510let SLOT_PROPS = ['onBlur', 'onChange', 'onClick', 'onDrag', 'onDragEnd', 'onDragEnter', 'onDragExit', 'onDragLeave', 'onDragOver', 'onDragStart', 'onDrop', 'onFocus', 'onFocus', 'onMouseDown', 'onMouseEnter', 'onMouseLeave', 'onMouseMove', 'onMouseOver', 'onMouseUp', 'onWheel', 'onWhen', 'when'];
4511
4512let shouldBeSlot = (prop, block) => SLOT_PROPS.includes(prop) || block.isList && prop === 'from';
4513
4514var getTags = (({
4515 name,
4516 isSlot,
4517 slotIsNot,
4518 value,
4519 block
4520}) => {
4521 let tags = {};
4522 if (isAnimation(value) && name !== 'text' && name !== 'animation') tags.animation = true;
4523 if (isStyle$1(name)) tags.style = true;
4524
4525 if (isRowStyle(name)) {
4526 tags.style = true;
4527 tags.rowStyle = true;
4528 }
4529
4530 if (isUnsupportedShorthand(name) && block.isBasic && !block.name.startsWith('Svg')) {
4531 tags.unsupportedShorthand = true;
4532 }
4533
4534 if (shouldBeSlot(name, block)) tags.shouldBeSlot = true;
4535 if (isSlot) tags.slot = true;
4536 if (slotIsNot) tags.slotIsNot = true;
4537 tags.validSlot = tags.slot || tags.shouldBeSlot && tags.slot || null;
4538 if (isFragment(name)) tags.fragment = true;
4539 return tags;
4540});
4541
4542var parse = (({
4543 convertSlotToProps = true,
4544 customFonts,
4545 enableSystemScopes = true,
4546 file,
4547 id,
4548 skipComments = true,
4549 skipInvalidProps = true,
4550 warnMissingDefaultValue = false,
4551 source,
4552 src,
4553 views
4554}) => {
4555 // convert crlf to lf
4556 let text = source.replace(/\r\n/g, '\n');
4557 let rlines = text.split('\n');
4558 let lines = rlines.map(line => line.trimRight());
4559 let ignoreIndex = lines.findIndex(line => /# ignore/.test(line));
4560
4561 if (ignoreIndex !== -1) {
4562 lines = lines.slice(0, ignoreIndex);
4563 }
4564
4565 let fonts = [];
4566 let isDefiningChildrenExplicitly = false;
4567 let stack = [];
4568 let slots = [];
4569 let useIsBefore = false;
4570 let useIsMedia = false;
4571 let topBlockShouldBe = file.endsWith('.view') ? 'View' : 'Block';
4572 let view = null;
4573 let viewsInView = new Set();
4574 let warnings = []; // TODO revisit at a later stage, it was making longer files super slow to
4575 // parse so we're removing it for now
4576 // let didYouMeanBlock = makeDidYouMeanBlock([...views.keys()])
4577 // let didYouMeanFontFamily = makeDidYouMeanFontFamily(
4578 // [...customFonts.keys()].map((id) => id.split('-')[0])
4579 // )
4580
4581 let blockIds = [];
4582
4583 function getBlockId(node) {
4584 let maybeId = node.is || node.name;
4585
4586 if (!blockIds.includes(maybeId)) {
4587 blockIds.push(maybeId);
4588 return maybeId;
4589 }
4590
4591 let index = 1;
4592
4593 while (blockIds.includes(`${maybeId}${index}`)) {
4594 index++;
4595 }
4596
4597 let id = `${maybeId}${index}`;
4598 blockIds.push(id);
4599 return id;
4600 }
4601
4602 function lookForFonts(block) {
4603 if (block.properties && (isFontable(block.name) || !block.isBasic)) {
4604 let fontFamilyProp = block.properties.find(p => p.name === 'fontFamily');
4605 if (!fontFamilyProp) return;
4606 let family = fontFamilyProp.value;
4607 let fontWeightProp = block.properties.find(p => p.name === 'fontWeight');
4608 let weight = fontWeightProp ? fontWeightProp.value.toString() : '400';
4609 let fontStyleProp = block.properties.find(p => p.name === 'fontStyle');
4610 let style = fontStyleProp ? fontStyleProp.value.toString() : 'normal';
4611
4612 if (!fonts.find(font => font.family === family && font.weight === weight && font.style === style)) {
4613 let font = {
4614 id: `${family}-${weight}${style === 'italic' ? '-italic' : ''}`,
4615 isGoogleFont: isGoogleFont(family),
4616 family,
4617 weight,
4618 style
4619 };
4620
4621 if (font.isGoogleFont || customFonts.has(font.id)) {
4622 fonts.push(font); // TODO revisit at a later stage, it was making longer files super slow to
4623 // parse so we're removing it for now
4624 // } else {
4625 // let meant = didYouMeanFontFamily(family)
4626 // if (meant && meant !== family) {
4627 // warnings.push({
4628 // loc: fontFamilyProp.loc,
4629 // 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.`,
4630 // line: lines[fontFamilyProp.loc.start.line - 1],
4631 // })
4632 // } else if (!font.weight.startsWith('props.')) {
4633 // warnings.push({
4634 // loc: fontFamilyProp.loc,
4635 // 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.`,
4636 // line: lines[fontFamilyProp.loc.start.line - 1],
4637 // })
4638 // }
4639 }
4640 }
4641 }
4642 }
4643
4644 function lookForMultiples(block) {
4645 let freq = {};
4646 block.properties.forEach(prop => {
4647 if (!(prop.name in freq)) {
4648 freq[prop.name] = 0;
4649 }
4650
4651 freq[prop.name]++;
4652 });
4653 let multiples = Object.keys(freq).filter(name => freq[name] > 1);
4654
4655 if (multiples.length > 0) {
4656 multiples.forEach(name => warnings.push({
4657 loc: block.loc,
4658 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.`,
4659 line: lines[block.loc.start.line - 1]
4660 }));
4661 }
4662 }
4663
4664 function end(block, endLine) {
4665 block.loc.end = {
4666 line: endLine + 1,
4667 column: Math.max(0, lines[endLine].length - 1)
4668 };
4669
4670 if (!block.properties) {
4671 block.properties = [];
4672 }
4673
4674 if (block.name === 'List' && !block.properties.some(prop => prop.name === 'from')) {
4675 warnings.push({
4676 loc: block.loc,
4677 type: `A List needs "from <" to work`,
4678 line: lines[block.loc.start.line - 1]
4679 });
4680 }
4681
4682 if (stack.length === 0) {
4683 // if we're the last block on the stack, then this is the view!
4684 view = block;
4685 return true;
4686 }
4687
4688 return false;
4689 }
4690
4691 function parseBlock(line, i) {
4692 let {
4693 block: name,
4694 is,
4695 level
4696 } = getBlock(line);
4697 let shouldPushToStack = false;
4698 let isChildren = name === 'Children';
4699
4700 if (isChildren) {
4701 isDefiningChildrenExplicitly = true;
4702 }
4703
4704 let block = {
4705 type: 'Block',
4706 name,
4707 animations: {},
4708 isAnimated: false,
4709 isBasic: isBasic(name),
4710 isCapture: isCapture(name),
4711 isColumn: isColumn$1(name),
4712 isGroup: false,
4713 isChildren,
4714 level,
4715 loc: getLoc(i + 1, 0),
4716 properties: [],
4717 scopes: []
4718 }; // TODO revisit at a later stage, it was making longer files super slow to
4719 // parse so we're removing it for now
4720 // let meant = didYouMeanBlock(name)
4721 // if (meant) {
4722 // if (meant !== name) {
4723 // warnings.push({
4724 // loc: block.loc,
4725 // type: `"${name}" doesn't exist and won't be morphed. Did you mean "${meant}" instead of "${name}"?`,
4726 // line,
4727 // })
4728 // block.skip = true
4729 // }
4730 // } else {
4731 // warnings.push({
4732 // loc: block.loc,
4733 // type: `"${name}" doesn't exist and won't be morphed.\nCreate the view or rename the block to point to the right view.`,
4734 // line,
4735 // })
4736 // block.skip = true
4737 // }
4738
4739 if (!block.isBasic) {
4740 viewsInView.add(block.name);
4741 }
4742
4743 if (is) {
4744 block.is = is;
4745 }
4746
4747 block.id = getBlockId(block);
4748 let last = stack[stack.length - 1];
4749
4750 while (last && last.level >= block.level) {
4751 end(stack.pop(), i);
4752 last = stack[stack.length - 1];
4753 }
4754
4755 if (last) {
4756 shouldPushToStack = true;
4757
4758 if (last.isGroup) {
4759 if (last.isList) {
4760 if (block.isBasic) {
4761 warnings.push({
4762 loc: block.loc,
4763 type: `A basic block "${block.name}" can't be inside a List. Use a view you made instead.`,
4764 line,
4765 blocker: true
4766 });
4767 } else if (last.children.length > 0) {
4768 warnings.push({
4769 loc: block.loc,
4770 type: `A List can only have one view inside. "${block.name}" is outside of it. Put 1 empty line before.`,
4771 line
4772 });
4773 } else {
4774 last.children.push(block);
4775 }
4776 } else if (block.isColumn && !last.isTable) {
4777 warnings.push({
4778 loc: block.loc,
4779 type: `Only tables can contain columns. Put this column directly inside a table.`,
4780 line
4781 });
4782 } else {
4783 last.children.push(block);
4784 }
4785 } else {
4786 end(stack.pop(), i); // inside a block that isn't a group
4787
4788 if (last.isBasic) {
4789 warnings.push({
4790 loc: block.loc,
4791 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.`,
4792 line
4793 });
4794 } else {
4795 warnings.push({
4796 loc: block.loc,
4797 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.`,
4798 line
4799 });
4800 }
4801 }
4802 } else if (view !== null) {
4803 warnings.push({
4804 loc: block.loc,
4805 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.`,
4806 line
4807 });
4808 }
4809
4810 if (isGroup(name)) {
4811 block.isGroup = true;
4812 block.isList = isList$1(name);
4813 block.isTable = isTable$1(name);
4814 block.children = [];
4815 }
4816
4817 if (block.isBasic && (block.name === 'View' || block.name === 'Block')) {
4818 block.isFragment = true;
4819
4820 if (stack.length > 0) {
4821 warnings.push({
4822 type: `A view can only have one ${block.name} block. Maybe you want to split this block into another view?`,
4823 line,
4824 loc: block.loc
4825 });
4826 }
4827 } else if (stack.length === 0) {
4828 warnings.push({
4829 type: `A view must start with a ${topBlockShouldBe} block. ${block.name} isn't valid.\nWrap everything within a ${topBlockShouldBe} block at the top.`,
4830 line,
4831 loc: block.loc
4832 });
4833 }
4834
4835 if (shouldPushToStack || stack.length === 0) {
4836 stack.push(block);
4837 }
4838
4839 parseProps(i, block);
4840 lookForFonts(block);
4841 lookForMultiples(block);
4842 }
4843
4844 function parseProps(i, block) {
4845 let endOfBlockIndex = i;
4846
4847 while (endOfBlockIndex < lines.length - 1 && !isBlock(lines[endOfBlockIndex + 1])) {
4848 endOfBlockIndex++;
4849 }
4850
4851 let properties = [];
4852 let scopes = [];
4853 let scope;
4854 let inScope = false;
4855
4856 for (let j = i; j <= endOfBlockIndex; j++) {
4857 let line = lines[j].trim();
4858 let propNode = null;
4859
4860 if (isProp(line)) {
4861 let {
4862 name,
4863 isSlot,
4864 slotName,
4865 slotIsNot,
4866 value
4867 } = getProp$1(line);
4868 let loc = getLoc(j + 1, line.indexOf(name), line.length - 1);
4869 let tags = getTags({
4870 name,
4871 isSlot,
4872 slotIsNot,
4873 value,
4874 block
4875 });
4876
4877 if (block.isBasic) {
4878 if (tags.unsupportedShorthand) {
4879 warnings.push({
4880 loc,
4881 type: `The shorthand ${name} isn't supported. You need to expand it like:\n${getUnsupportedShorthandExpanded(name, value).join('\n')}`,
4882 line
4883 }); // TODO revisit at a later stage, it was making longer files super slow to
4884 // parse so we're removing it for now
4885 // } else {
4886 // let meant = didYouMeanProp(name)
4887 // if (meant && meant !== name) {
4888 // warnings.push({
4889 // loc,
4890 // type: `Did you mean "${meant}" instead of "${name}"?`,
4891 // line,
4892 // })
4893 // }
4894 }
4895
4896 if (tags.fragment && block.isGroup) {
4897 if (isSlot) {
4898 warnings.push({
4899 loc,
4900 type: `The isFragment prop can't be a slot. You need to change it to isFragment true. Treating it as true for now.`,
4901 line
4902 });
4903 }
4904
4905 block.isFragment = true;
4906 }
4907
4908 if (block.type === 'Capture' && !CAPTURE_TYPES.includes(value)) {
4909 if (/textarea/i.test(value)) {
4910 warnings.push({
4911 loc,
4912 type: `Invalid type ${value} for Capture. Perhaps you want to use a CaptureTextArea block instead? Using type text as a default.`,
4913 line
4914 });
4915 } else {
4916 warnings.push({
4917 loc,
4918 type: `Invalid type ${value} for Capture. You can use ${CAPTURE_TYPES.join(', ')}. Using type text as a default.`,
4919 line
4920 });
4921 }
4922
4923 value = 'text';
4924 }
4925 } else {
4926 if (name === 'lazy') {
4927 block.isLazy = true;
4928 }
4929 }
4930
4931 if (name === 'when') {
4932 let isSystem = enableSystemScopes && isSystemScope(slotName);
4933
4934 if (value === '' || value === '<' || value === '<!') {
4935 warnings.push({
4936 loc,
4937 type: 'This when has no condition assigned to it. Add one like: "when <isCondition"',
4938 line
4939 });
4940 if (skipInvalidProps) continue;
4941 } else if (!tags.validSlot) {
4942 warnings.push({
4943 loc,
4944 type: `The slot name "${name}" isn't valid. Fix it like: "when <isCondition" `,
4945 line
4946 });
4947 if (skipInvalidProps) continue;
4948 }
4949
4950 if (isSystem && slotIsNot) {
4951 warnings.push({
4952 loc,
4953 type: `"${slotName}" is a system slot and it can't take a "!" in its value. Replace the line for: "when <${slotName}".`
4954 });
4955 }
4956
4957 tags.scope = value;
4958 inScope = true;
4959 scope = {
4960 isSystem,
4961 defaultValue: getValue(value, name),
4962 value,
4963 name,
4964 slotName,
4965 slotIsNot: isSystem ? false : slotIsNot,
4966 properties: []
4967 };
4968
4969 if (convertSlotToProps) {
4970 scope.value = slotName;
4971
4972 if (slotName === 'isBefore') {
4973 useIsBefore = true;
4974 } else if (/!?isMedia.+/.test(slotName)) {
4975 scope.value = `isMedia.${slotName.replace('isMedia', '').toLowerCase()}`;
4976 useIsMedia = true;
4977 } else if (!isSystem) {
4978 scope.value = `${slotIsNot ? '!' : ''}props.${slotName || name}`;
4979 }
4980 }
4981
4982 scopes.push(scope);
4983 } else if ((tags.slot || tags.shouldBeSlot) && !tags.validSlot) {
4984 if (name === 'from' && block.name === 'List' || name !== 'from') {
4985 warnings.push({
4986 loc,
4987 type: `The value you used in the slot "${name}" is invalid`,
4988 line
4989 });
4990 if (skipInvalidProps) continue;
4991 }
4992 }
4993
4994 if (name === 'onWhen' && properties.length > 0) {
4995 warnings.push({
4996 type: `Put onWhen at the top of the block. It's easier to see it that way!`,
4997 line,
4998 loc
4999 });
5000 }
5001
5002 if (name === 'onWhen' && /!?isMedia.+/.test(slotName)) {
5003 useIsMedia = true;
5004 }
5005
5006 if (name === 'format') {
5007 block.format = getFormat(value);
5008 }
5009
5010 if (value === '' && name !== 'text') {
5011 warnings.push({
5012 loc,
5013 type: `"${name}" has no value. Please give it a value.`,
5014 line
5015 });
5016 if (skipInvalidProps) continue;
5017 }
5018
5019 propNode = {
5020 type: 'Property',
5021 loc,
5022 name,
5023 tags,
5024 value: getValue(value, name)
5025 };
5026
5027 if (tags.animation && scope) {
5028 block.isAnimated = true;
5029 let currentAnimation = getAnimation(value);
5030 propNode.value = currentAnimation.defaultValue;
5031 propNode.animation = currentAnimation.properties;
5032
5033 if (propNode.animation.curve === 'spring') {
5034 block.hasSpringAnimation = true;
5035 } else {
5036 block.hasTimingAnimation = true;
5037 }
5038
5039 if (!block.animations[currentAnimation.id]) {
5040 block.animations[currentAnimation.id] = {
5041 index: Object.keys(block.animations).length,
5042 animation: currentAnimation,
5043 props: {}
5044 };
5045 }
5046
5047 propNode.animationIndexOnBlock = block.animations[currentAnimation.id].index;
5048
5049 if (!block.animations[currentAnimation.id].props[name]) {
5050 let baseValue = null;
5051 let baseProp = properties.find(prop => prop.name === name);
5052
5053 if (baseProp) {
5054 baseValue = baseProp.value;
5055 }
5056
5057 block.animations[currentAnimation.id].props[name] = {
5058 name,
5059 scopes: [],
5060 value: baseValue
5061 };
5062 }
5063
5064 block.animations[currentAnimation.id].props[name].scopes.push({
5065 name: scope.slotName,
5066 value: currentAnimation.defaultValue
5067 });
5068 }
5069
5070 if (scope) {
5071 propNode.scope = scope.slotName;
5072 }
5073
5074 if (tags.slot) {
5075 let needsDefaultValue = !tags.shouldBeSlot && /</.test(propNode.value);
5076
5077 if (typeof propNode.value === 'string') {
5078 propNode.value = propNode.value.replace(/^</, '');
5079 }
5080
5081 propNode.slotName = slotName; // if (name !== 'when' || (name === 'when' && !scope.isSystem)) {
5082
5083 propNode.defaultValue = propNode.value;
5084
5085 if (convertSlotToProps) {
5086 propNode.value = `${slotIsNot ? '!' : ''}props.${slotName || name}`;
5087 }
5088
5089 if (needsDefaultValue) {
5090 if (name === 'text' && block.name === 'Text') {
5091 propNode.defaultValue = '';
5092 } else {
5093 propNode.defaultValue = false;
5094
5095 if (warnMissingDefaultValue && block.isBasic && (propNode.tags.style || block.name === 'Text' && propNode.name === 'text')) {
5096 warnings.push({
5097 loc,
5098 type: `Add a default value to "${name}" like: "${name} <${slotName} default value"`,
5099 line
5100 });
5101 }
5102 }
5103 }
5104
5105 let existingDefaultValue = slots.find(vp => vp.name === (slotName || name));
5106
5107 if ( // !inScope &&
5108 !propNode.tags.fragment && (!existingDefaultValue || !existingDefaultValue.defaultValue)) {
5109 if (existingDefaultValue) {
5110 existingDefaultValue.name = slotName || name;
5111 existingDefaultValue.type = getPropType(block, name);
5112 existingDefaultValue.defaultValue = tags.shouldBeSlot ? false : propNode.defaultValue;
5113 } else {
5114 slots.push({
5115 name: slotName || name,
5116 type: getPropType(block, name),
5117 defaultValue: tags.shouldBeSlot ? false : propNode.defaultValue
5118 });
5119 }
5120 } // }
5121
5122 }
5123 } else if (isComment(line) && !skipComments) {
5124 let [value] = getComment(line);
5125 let userComment = isUserComment(line);
5126
5127 if (userComment) {
5128 value = getComment(value);
5129 }
5130
5131 propNode = {
5132 type: 'Property',
5133 loc: getLoc(j + 1, 0, line.length - 1),
5134 value,
5135 tags: {
5136 comment: true,
5137 userComment
5138 }
5139 };
5140 }
5141
5142 if (propNode) {
5143 block.loc.end = propNode.loc.end;
5144
5145 if (inScope) {
5146 if (propNode.name !== 'when' && !propNode.tags.comment && !properties.some(baseProp => baseProp.name === propNode.name)) {
5147 warnings.push({
5148 loc: propNode.loc,
5149 type: `You're missing a base prop for ${propNode.name}. Add it before all whens on the block.`,
5150 line
5151 });
5152 }
5153
5154 scope.properties.push(propNode);
5155 } else {
5156 properties.push(propNode);
5157 }
5158 }
5159 }
5160
5161 block.properties = properties;
5162 block.scopes = sortScopes(scopes);
5163
5164 if (block.name !== 'View' && block.isFragment) {
5165 let invalidProps = properties.filter(prop => prop.name !== 'isFragment' || prop.name !== 'onWhen').map(prop => prop.name);
5166
5167 if (invalidProps.length > 0) {
5168 warnings.push({
5169 type: `A fragment can only have isFragment and onWhen props.\nEvery other prop will be dismissed.\nEither remove ${invalidProps.join(', ')} or remove isFragment.`,
5170 loc: block.loc,
5171 line: rlines[i]
5172 });
5173 }
5174 }
5175 }
5176
5177 lines.forEach((line, i) => {
5178 if (line !== rlines[i].trimRight()) {
5179 warnings.push({
5180 type: `You have some spaces before or after this line. Clean them up.`,
5181 loc: {
5182 start: {
5183 line: i + 1
5184 }
5185 },
5186 line: rlines[i]
5187 });
5188 }
5189
5190 if (isBlock(line)) {
5191 parseBlock(line, i);
5192 }
5193 });
5194
5195 if (stack.length > 0) {
5196 while (!end(stack.pop(), lines.length - 1)) {}
5197 }
5198
5199 if (!view) {
5200 view = {
5201 type: 'Block',
5202 name: id,
5203 animations: {},
5204 isAnimated: false,
5205 isBasic: true,
5206 isCapture: false,
5207 isColumn: false,
5208 isGroup: false,
5209 isChildren: false,
5210 level: 0,
5211 loc: getLoc(1, 0),
5212 properties: [],
5213 scopes: []
5214 };
5215 warnings.push({
5216 loc: view.loc,
5217 type: `The file for ${id} is empty and won't render! Add some blocks to it like:\n${topBlockShouldBe}\n Text\n text content`,
5218 line: ''
5219 });
5220 }
5221
5222 let flowProp = view.properties.find(p => p.name === 'is');
5223
5224 if (flowProp) {
5225 view.isStory = true;
5226 view.flow = flowProp.value;
5227 view.viewPath = path.dirname(file.replace(src.replace(/\\/g, '/'), ''));
5228 view.viewPathParent = path.dirname(view.viewPath);
5229 view.data = getData(view.properties.find(p => p.name === 'data'));
5230 view.dataFormat = getDataFormat(view.properties.find(p => p.name === 'dataFormat'));
5231 view.dataValidate = getDataValidate(view.properties.find(p => p.name === 'dataValidate'));
5232 } else {
5233 view.isStory = false;
5234 }
5235
5236 view.isDefiningChildrenExplicitly = isDefiningChildrenExplicitly;
5237 view.useIsBefore = useIsBefore;
5238 view.useIsMedia = useIsMedia;
5239 view.views = viewsInView;
5240 return {
5241 fonts,
5242 slots,
5243 view,
5244 warnings
5245 };
5246});
5247
5248let getFileDepth = file => file.split(path.sep).length - 1;
5249
5250let sortByFileDepth = (a, b) => {
5251 let depthA = getFileDepth(a);
5252 let depthB = getFileDepth(b);
5253 let depthDelta = depthA - depthB;
5254 if (depthDelta !== 0) return depthDelta;
5255 return a < b ? 1 : a > b ? -1 : 0;
5256};
5257
5258function sortSetsInMap(map) {
5259 for (let [key, value] of map) {
5260 if (value.size <= 1) continue;
5261 map.set(key, new Set([...value].sort(sortByFileDepth)));
5262 }
5263}
5264
5265function parseViews({
5266 customFonts,
5267 filesView,
5268 src,
5269 verbose,
5270 viewsById,
5271 viewsToFiles
5272}) {
5273 for (let file of filesView) {
5274 let view = viewsToFiles.get(file);
5275 if (view.custom) continue;
5276 view.parsed = parse({
5277 customFonts,
5278 file,
5279 id: view.id,
5280 src,
5281 source: view.source,
5282 views: viewsById
5283 });
5284 maybePrintWarnings(view, verbose);
5285 }
5286
5287 sortSetsInMap(viewsById);
5288}
5289
5290let EXTENSIONS = ['view.blocks', 'logic.js', 'react.js'];
5291function getViewIdFromFile(file) {
5292 let extension = EXTENSIONS.find(item => file.endsWith(item));
5293
5294 if (!extension) {
5295 throw new Error(`Can't recognise the extension of "${file}" to get a view's id.`);
5296 }
5297
5298 return path.basename(path.dirname(file));
5299}
5300
5301function processViewCustomFiles({
5302 filesViewCustom,
5303 viewsById,
5304 viewsToFiles
5305}) {
5306 [...filesViewCustom].forEach(file => {
5307 let id = getViewIdFromFile(file);
5308 addToMapSet(viewsById, id, file);
5309 viewsToFiles.set(file, {
5310 custom: true,
5311 file,
5312 id,
5313 logic: false,
5314 source: null,
5315 version: 0
5316 });
5317 });
5318}
5319
5320async function processViewFiles({
5321 filesView,
5322 filesViewLogic,
5323 viewsById,
5324 viewsToFiles
5325}) {
5326 await Promise.all([...filesView].map(async file => {
5327 let id = getViewIdFromFile(file);
5328 addToMapSet(viewsById, id, file);
5329 let view = viewsToFiles.has(file) ? viewsToFiles.get(file) : {};
5330 let logic = path.join(path.dirname(file), 'logic.js');
5331 viewsToFiles.set(file, { ...view,
5332 custom: false,
5333 file,
5334 id,
5335 logic: filesViewLogic.has(logic) && logic,
5336 source: await fs.promises.readFile(file, 'utf8'),
5337 version: view.version ? view.version + 1 : 0
5338 });
5339 }));
5340}
5341
5342function makeMorpher({
5343 as = 'react-dom',
5344 src,
5345 tools = false,
5346 verbose = true
5347}) {
5348 let state = {
5349 as,
5350 src,
5351 tools,
5352 verbose,
5353 customFonts: new Map(),
5354 viewsById: new Map(),
5355 viewsToFiles: new Map()
5356 };
5357
5358 state.processFiles = async function processFiles({
5359 filesFontCustom = new Set(),
5360 filesView = new Set(),
5361 filesViewCustom = new Set(),
5362 filesViewLogic = new Set()
5363 }) {
5364 if (filesFontCustom.size > 0) {
5365 processCustomFonts({
5366 customFonts: state.customFonts,
5367 filesFontCustom
5368 });
5369 } // detect .view files
5370
5371
5372 await processViewFiles({
5373 filesView,
5374 filesViewLogic,
5375 viewsById: state.viewsById,
5376 viewsToFiles: state.viewsToFiles
5377 }); // detect .js files meant to be custom views with "// @view" at the top
5378
5379 processViewCustomFiles({
5380 filesViewCustom,
5381 viewsById: state.viewsById,
5382 viewsToFiles: state.viewsToFiles
5383 }); // TODO optimise
5384 // parse views
5385
5386 parseViews({
5387 customFonts: state.customFonts,
5388 filesView,
5389 src: state.src,
5390 verbose: state.verbose,
5391 viewsById: state.viewsById,
5392 viewsToFiles: state.viewsToFiles
5393 });
5394 let morphedFonts = morphAllFonts({
5395 as: state.as,
5396 customFonts: state.customFonts,
5397 filesView,
5398 src: state.src,
5399 viewsToFiles: state.viewsToFiles
5400 }); // TODO optimise
5401 // morph views
5402
5403 let morphedViews = morphAllViews({
5404 as: state.as,
5405 filesView,
5406 getFontImport: makeGetFontImport(state.src),
5407 getSystemImport: makeGetSystemImport(state.src),
5408 src,
5409 tools: state.tools,
5410 viewsById: state.viewsById,
5411 viewsToFiles: state.viewsToFiles
5412 }); // TODO optimise, only if they changed, cache, etc
5413
5414 let viewsFiles = await Promise.all([ensureData(state), ensureFlow(state), ensureTools(state), ensureIsBefore(state), ensureIsHovered(state), ensureIsMedia(state)]);
5415 let filesToWrite = [...morphedFonts, ...morphedViews, ...viewsFiles].filter(Boolean);
5416 await Promise.all(filesToWrite.map(({
5417 file,
5418 content
5419 }) => fs.promises.writeFile(file, content, 'utf-8')));
5420 };
5421
5422 return state;
5423}
5424
5425function getPointsOfUse({
5426 view,
5427 viewsToFiles
5428}) {
5429 let filesView = new Set([view.file]);
5430 let filesViewLogic = new Set();
5431 let viewsId = new Set();
5432
5433 if (view.logic) {
5434 filesViewLogic.add(view.logic);
5435 }
5436
5437 for (let viewInView of viewsToFiles.values()) {
5438 if (viewInView.custom) continue;
5439
5440 if (viewInView.parsed.view.views.has(view.id)) {
5441 filesView.add(viewInView.file);
5442
5443 if (viewInView.logic) {
5444 filesViewLogic.add(viewInView.logic);
5445 }
5446
5447 viewsId.add(viewInView.id);
5448 }
5449 }
5450
5451 return {
5452 filesView,
5453 filesViewLogic,
5454 viewsId
5455 };
5456}
5457
5458async function watchFiles({
5459 morpher
5460}) {
5461 let watcher = chokidar.watch(MATCH, {
5462 cwd: morpher.src,
5463 ignored: ['**/view.js', 'DesignSystem/Fonts/*.js'],
5464 ignoreInitial: true // awaitWriteFinish: true,
5465
5466 });
5467 let processing = false;
5468 let queue = [];
5469 let processEvents = debounce.debounce(async () => {
5470 if (queue.length === 0 || processing) {
5471 return;
5472 }
5473
5474 let queueClone = [...queue];
5475 queue = [];
5476 processing = true;
5477
5478 try {
5479 await processQueue({
5480 queue: queueClone,
5481 morpher
5482 });
5483 } catch (error) {
5484 console.error(error);
5485 process.exit(1);
5486 }
5487
5488 processing = false;
5489
5490 if (queue.length > 0) {
5491 processEvents();
5492 }
5493 }, 10);
5494
5495 function onEvent({
5496 file,
5497 op
5498 }) {
5499 queue.push({
5500 file: path.join(morpher.src, file),
5501 op
5502 });
5503 processEvents();
5504 }
5505
5506 watcher.on('add', file => onEvent({
5507 file,
5508 op: 'add'
5509 }));
5510 watcher.on('change', file => onEvent({
5511 file,
5512 op: 'change'
5513 }));
5514 watcher.on('unlink', file => onEvent({
5515 file,
5516 op: 'unlink'
5517 }));
5518 return watcher;
5519}
5520
5521async function processQueue({
5522 queue,
5523 morpher
5524}) {
5525 let filesToProcess = {
5526 filesView: new Set(),
5527 filesViewLogic: new Set(),
5528 filesViewCustom: new Set(),
5529 filesFontCustom: new Set()
5530 };
5531 processAddedOrChanged({
5532 files: queue.filter(item => item.op !== 'unlink'),
5533 morpher,
5534 filesToProcess
5535 });
5536 await processUnlinked({
5537 files: queue.filter(item => item.op === 'unlink'),
5538 morpher,
5539 filesToProcess
5540 });
5541 return morpher.processFiles(filesToProcess);
5542}
5543
5544async function processUnlinked({
5545 files,
5546 morpher,
5547 filesToProcess
5548}) {
5549 await Promise.all(files.map(async ({
5550 file
5551 }) => {
5552 morpher.verbose && console.log(chalk.magenta('X'), path.basename(path.dirname(file)), chalk.dim(file.replace(morpher.src, '').replace(/^\//, '')));
5553
5554 if (isFontCustomFile(file)) {
5555 let id = getFontId(file);
5556 let font = morpher.customFonts.get(id);
5557 font.delete(file);
5558
5559 if (font.size === 0) {
5560 morpher.customFonts.delete(id);
5561 }
5562 } else {
5563 let viewFile = path.join(path.dirname(file), 'view.blocks');
5564 let view = morpher.viewsToFiles.get(viewFile);
5565 if (!view) return;
5566 processPointsOfUse({
5567 view,
5568 morpher,
5569 filesToProcess
5570 });
5571 filesToProcess.filesView.delete(view.file);
5572 let viewFilesSet = morpher.viewsById.get(view.id);
5573 viewFilesSet.delete(view.file);
5574
5575 if (viewFilesSet.size === 0) {
5576 morpher.viewsById.delete(view.id);
5577 }
5578
5579 morpher.viewsToFiles.delete(view.file);
5580
5581 if (isViewFile(file)) {
5582 filesToProcess.filesView.delete(file);
5583
5584 try {
5585 await fs.promises.unlink(file.replace('view.blocks', 'view.js'));
5586 } catch (error) {}
5587 } else if (isViewLogicFile(file)) {
5588 if (files.every(item => item.file !== view.file)) {
5589 filesToProcess.filesView.add(view.file);
5590 }
5591
5592 filesToProcess.filesViewLogic.delete(file);
5593 } else if (isViewCustomFile(file)) {
5594 filesToProcess.filesViewCustom.delete(file);
5595 }
5596 }
5597 }));
5598}
5599
5600function processAddedOrChanged({
5601 files,
5602 morpher,
5603 filesToProcess
5604}) {
5605 files.forEach(({
5606 file,
5607 op
5608 }) => {
5609 morpher.verbose && console.log(chalk.yellow(op.toUpperCase()[0]), path.basename(path.dirname(file)), chalk.dim(file.replace(morpher.src, '').replace(/^\//, '')));
5610
5611 if (isFontCustomFile(file)) {
5612 filesToProcess.filesFontCustom.add(file);
5613 } else {
5614 if (isViewFile(file)) {
5615 filesToProcess.filesView.add(file);
5616 let logicFile = path.join(path.dirname(file), 'logic.js');
5617
5618 if (fs.existsSync(logicFile)) {
5619 filesToProcess.filesViewLogic.add(logicFile);
5620 }
5621
5622 let parentFile = path.join(path.dirname(path.dirname(file)), 'view.blocks');
5623
5624 if (fs.existsSync(parentFile)) {
5625 filesToProcess.filesView.add(parentFile);
5626 }
5627 } else if (isViewLogicFile(file)) {
5628 filesToProcess.filesView.add(path.join(path.dirname(file), 'view.blocks'));
5629 filesToProcess.filesViewLogic.add(file);
5630 } else if (isViewCustomFile(file)) {
5631 filesToProcess.filesViewCustom.add(file);
5632 }
5633
5634 let viewFile = path.join(path.dirname(file), 'view.blocks');
5635 let view = morpher.viewsToFiles.get(viewFile);
5636 if (!view) return;
5637 processPointsOfUse({
5638 view,
5639 morpher,
5640 filesToProcess
5641 });
5642 }
5643 });
5644}
5645
5646function processPointsOfUse({
5647 view,
5648 morpher,
5649 filesToProcess
5650}) {
5651 let {
5652 filesView,
5653 filesViewLogic
5654 } = getPointsOfUse({
5655 view,
5656 viewsToFiles: morpher.viewsToFiles
5657 });
5658
5659 for (let file of filesView) {
5660 filesToProcess.filesView.add(file);
5661 }
5662
5663 for (let file of filesViewLogic) {
5664 filesToProcess.filesViewLogic.add(file);
5665 }
5666}
5667
5668async function watch(options) {
5669 console.time('startup time');
5670 let morpher = makeMorpher(options);
5671 await ensureFontsDirectory(morpher.src);
5672 await morpher.processFiles(await getFiles(morpher.src));
5673
5674 if (options.verbose) {
5675 if (morpher.customFonts.size > 0) {
5676 console.log(chalk.yellow(`\nCustom fonts detected:`));
5677 console.log([...morpher.customFonts.keys()].sort().join('\n'));
5678 }
5679
5680 let views = [...morpher.viewsToFiles.values()];
5681 console.log(views.map(view => {
5682 let msg = view.id;
5683
5684 if (view.custom) {
5685 msg = `${view.id} ${chalk.dim('(is custom)')}`;
5686 } else if (view.logic) {
5687 msg = `${view.id} ${chalk.dim('(has logic)')}`;
5688 }
5689
5690 return `${chalk.yellow('A')} ${chalk.green('M')} ${msg} ${chalk.dim(path.relative(morpher.src, view.file))}`;
5691 }).sort().join('\n'));
5692 views.forEach(maybePrintWarnings);
5693 }
5694
5695 console.log(chalk.green('Views Morpher is ready'));
5696 console.timeEnd('startup time');
5697 if (options.once) return;
5698 let watcher = watchFiles({
5699 morpher
5700 });
5701 process.on('beforeExit', () => {
5702 console.log('Stopping Views morpher file watcher...');
5703 watcher.close();
5704 });
5705}
5706
5707let wait = time => new Promise(resolve => setTimeout(resolve, time));
5708
5709(async function () {
5710 let {
5711 _,
5712 as,
5713 clean: clean$1,
5714 help,
5715 tools,
5716 watch: shouldWatch,
5717 verbose,
5718 version
5719 } = minimist(process.argv.slice(2), {
5720 alias: {
5721 help: 'h'
5722 },
5723 booleans: ['clean', 'help', 'tools', 'watch', 'version'],
5724 default: {
5725 as: 'react-dom',
5726 clean: false,
5727 tools: true,
5728 verbose: true,
5729 version: false,
5730 watch: false
5731 }
5732 });
5733
5734 if (!shouldWatch && tools) {
5735 tools = false;
5736 }
5737
5738 if (help) {
5739 console.log(`
5740 views-morph [directory]
5741 --as target platform
5742 react-dom (default)
5743 react-native
5744 react-pdf
5745 --clean clean the autogenerated .view.js files
5746 --tools use with Views Tools, defauls to true when
5747 --watch is enabled, otherwise defaults to false
5748 --verbose defaults to true
5749 --version print the version
5750 --watch watch a directory and produce .view.js files
5751 `);
5752 process.exit();
5753 }
5754
5755 if (version) {
5756 console.log(`v${pkg.version}`);
5757 process.exit();
5758 }
5759
5760 let input = Array.isArray(_) && _[0];
5761
5762 if (!input || !(await fs.promises.stat(input)).isDirectory()) {
5763 console.error(`You need to specify an input directory to watch. ${input} is a file.`);
5764 process.exit();
5765 }
5766
5767 if (!path.isAbsolute(input)) {
5768 input = path.normalize(path.join(process.cwd(), input));
5769 }
5770
5771 try {
5772 if ((await fs.promises.stat(path.join(input, 'src'))).isDirectory()) {
5773 input = path.join(input, 'src');
5774 }
5775 } catch (error) {}
5776
5777 if (clean$1) {
5778 console.log(`Cleaning up ${input}...`);
5779 await clean(input, verbose, true);
5780 process.exit();
5781 } else {
5782 await clean(input, verbose);
5783 }
5784
5785 updateNotifier({
5786 pkg
5787 }).notify();
5788
5789 if (verbose) {
5790 console.log(chalk.underline(`Views Tools morpher v${pkg.version}`));
5791 console.log(`\nWill morph files at "${chalk.green(input)}" as "${chalk.green(as)}" ${tools ? 'with Views Tools' : 'without Views Tools'}`);
5792
5793 if (shouldWatch && !tools) {
5794 console.log(chalk.bgRed(' '));
5795 console.log();
5796 console.log(`🚨 You're missing out!!!`);
5797 console.log(chalk.bold('🚀 Views Tools can help you find product market\n fit before you run out of money.'));
5798 console.log();
5799 console.log('✨ Find out how 👉', chalk.bold(chalk.green('https://views.tools')));
5800 console.log();
5801 console.log(chalk.bgRed(' '));
5802 await wait(15000);
5803 }
5804
5805 console.log(chalk.yellow('A'), '= Added');
5806 console.log(chalk.green('M'), `= Morphed`);
5807 console.log(chalk.magenta('X'), `= Deleted`);
5808 console.log('\nPress', chalk.blue('ctrl+c'), 'to stop at any time.\n');
5809 }
5810
5811 watch({
5812 as,
5813 once: !shouldWatch,
5814 src: input,
5815 tools,
5816 verbose
5817 });
5818})();
5819//# sourceMappingURL=bin.js.map