UNPKG

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