├── .babelrc ├── .prettierrc ├── .travis.yml ├── public ├── braces.png ├── favicon.ico └── index.html ├── .npmignore ├── src ├── utils │ ├── styles.js │ └── parse.js ├── index.js ├── api.js ├── components │ ├── editor-context.js │ ├── misc.js │ ├── highlightable.js │ ├── declarations.js │ ├── ast-node.js │ ├── literals.js │ ├── editable.js │ ├── demo.js │ ├── keymap.js │ ├── collections.js │ └── editor.js ├── types.js ├── navigate │ └── index.js ├── key-mappings.js └── ast.js ├── .flowconfig ├── .gitignore ├── LICENSE ├── package.json ├── README.md ├── COMPARISON.md └── flow-typed └── npm └── styled-components_v2.x.x.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["react-app"] 3 | } -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | parser: flow 2 | singleQuote: true -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "7" -------------------------------------------------------------------------------- /public/braces.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gregoor/syntactor/HEAD/public/braces.png -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gregoor/syntactor/HEAD/public/favicon.ico -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | build/ 2 | public/ 3 | src/ 4 | tests/ 5 | 6 | .babelrc 7 | .flowconfig 8 | yarn.lock -------------------------------------------------------------------------------- /src/utils/styles.js: -------------------------------------------------------------------------------- 1 | import { css } from 'styled-components'; 2 | 3 | export default { 4 | text: css` 5 | font-size: 14px; 6 | font-family: Monaco, monospace; 7 | ` 8 | }; 9 | -------------------------------------------------------------------------------- /.flowconfig: -------------------------------------------------------------------------------- 1 | [ignore] 2 | /node_modules/fbjs/.* 3 | /node_modules/styled-components/.* 4 | /node_modules/promise/.* 5 | /eslint-plugin-jsx-a11y/__mocks__/.* 6 | 7 | [libs] -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See http://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | node_modules 5 | 6 | # testing 7 | coverage 8 | 9 | # production 10 | build 11 | lib 12 | 13 | # misc 14 | .DS_Store 15 | .env 16 | npm-debug.log 17 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | 4 | import { render } from './api'; 5 | import Demo from './components/demo'; 6 | 7 | const demoElement = document.getElementById('insert-syntactor-demo-here'); 8 | if (demoElement) { 9 | ReactDOM.render(, demoElement); 10 | } else { 11 | window.Syntactor = { render: render }; 12 | } 13 | -------------------------------------------------------------------------------- /src/api.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import React from 'react'; 3 | import ReactDOM from 'react-dom'; 4 | 5 | import Editor from './components/editor'; 6 | 7 | export { Editor }; 8 | 9 | export function render(element: Element, props: Editor.propTypes) { 10 | if (typeof element === 'string') element = document.querySelector(element); 11 | return ReactDOM.render(, element); 12 | } 13 | -------------------------------------------------------------------------------- /src/components/editor-context.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import { List, Map } from 'immutable'; 3 | import { createContext, createRef } from 'react'; 4 | import type { EditorContextValue } from '../types'; 5 | 6 | export default (createContext({ 7 | ast: Map(), 8 | lastDirection: 'DOWN', 9 | onSelect: () => List(), 10 | selected: List.of(-1), 11 | selectedRef: createRef() 12 | }): createContext); 13 | -------------------------------------------------------------------------------- /src/components/misc.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import React from 'react'; 3 | import type { ASTNodeProps } from '../types'; 4 | import ASTNode from './ast-node'; 5 | 6 | export const File = ({ path, ...props }: ASTNodeProps) => ( 7 | 8 | ); 9 | 10 | export const Program = ({ path, ...props }: ASTNodeProps) => ( 11 | 12 | ); 13 | -------------------------------------------------------------------------------- /src/components/highlightable.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import styled, { keyframes } from 'styled-components'; 3 | 4 | const blink = keyframes` 5 | from, to { 6 | outline-color: rgba(0, 0, 0, .2); 7 | } 8 | 50% { 9 | outline-color: rgba(0, 0, 0, .5); 10 | } 11 | `; 12 | 13 | export default styled.span` 14 | animation: ${blink} 1s linear infinite; 15 | ${props => props.highlighted && !props.light && 'outline: 1px solid grey'}; 16 | ${props => props.light && 'background: rgba(0, 0, 0, .05)'}; 17 | `; 18 | -------------------------------------------------------------------------------- /src/utils/parse.js: -------------------------------------------------------------------------------- 1 | import { parse as babylonParse } from 'babylon'; 2 | import * as Immutable from 'immutable'; 3 | 4 | const fromJS = js => 5 | typeof js !== 'object' || js === null 6 | ? js 7 | : Array.isArray(js) 8 | ? Immutable.Seq(js) 9 | .map(fromJS) 10 | .toList() 11 | : Immutable.Seq(js) 12 | .map(fromJS) 13 | .toMap(); 14 | 15 | export default function parse(str) { 16 | return fromJS(babylonParse(str)); 17 | } 18 | 19 | export function parseObject(obj) { 20 | return parse('var obj = ' + JSON.stringify(obj)).program.body[0] 21 | .declarations[0].init; 22 | } 23 | -------------------------------------------------------------------------------- /src/types.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import { List, Map } from 'immutable'; 3 | 4 | export type ASTKey = string | number; 5 | export type AST = Map< 6 | string, 7 | AST | List | string | boolean | number 8 | >; 9 | export type ASTPath = List; 10 | 11 | export type VerticalDirection = 'UP' | 'DOWN'; 12 | export type HorizontalDirection = 'LEFT' | 'RIGHT'; 13 | export type Direction = VerticalDirection | HorizontalDirection; 14 | 15 | export type BaseASTNodeProps = { 16 | level: number, 17 | path: ASTPath, 18 | style?: any 19 | }; 20 | 21 | export type ASTNodeProps = BaseASTNodeProps & { node: AST }; 22 | 23 | export type EditorContextValue = { 24 | ast: AST, 25 | lastDirection: Direction, 26 | onSelect: ASTPath => any, 27 | selected: ASTPath, 28 | selectedRef: { current: null | HTMLInputElement } 29 | }; 30 | -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 15 | Syntactor 16 | 17 | 18 |
19 | 20 | 21 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Gregor Weber 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "syntactor", 3 | "version": "2.0.0", 4 | "homepage": "https://gregoor.github.io/syntactor", 5 | "license": "MIT", 6 | "private": false, 7 | "main": "lib/api", 8 | "repository": { 9 | "type": "git", 10 | "url": "https://github.com/Gregoor/syntactor.git" 11 | }, 12 | "dependencies": { 13 | "babel-generator": "^6.26.1", 14 | "babel-types": "^6.26.0", 15 | "babylon": "^6.18.0", 16 | "immutable": "4.0.0-rc.9", 17 | "react": "^16.3.2", 18 | "react-dom": "^16.3.2", 19 | "styled-components": "^3.2.6" 20 | }, 21 | "devDependencies": { 22 | "babel-cli": "^6.26.0", 23 | "babel-preset-react-app": "^3.1.1", 24 | "flow-bin": "^0.74.0", 25 | "gh-pages": "1.1.0", 26 | "react-scripts": "^1.1.4" 27 | }, 28 | "scripts": { 29 | "start": "react-scripts start", 30 | "build": "react-scripts build", 31 | "build-lib": "babel --out-dir lib/ --ignore test.js src/", 32 | "build-prod": "NODE_ENV=production yarn build-lib && yarn build", 33 | "test": "yarn flow && react-scripts test --env=jsdom", 34 | "flow": "flow", 35 | "predeploy": "yarn build", 36 | "deploy": "gh-pages -d build" 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Build Status](https://travis-ci.org/Gregoor/syntactor.svg?branch=master)](https://travis-ci.org/Gregoor/syntactor) 2 | 3 | # Syntactor 4 | An editor with two basic goals: 5 | 1. Only allow valid code to be entered (no syntax errors) 6 | 2. Common code transformations in just as many or less keystrokes, compared to other editors 7 | 8 | For now, it's only a JSON editor. 9 | 10 | ## Usage 11 | In your commandline: 12 | ```bash 13 | yarn add syntactor 14 | #or 15 | npm install syntactor --save 16 | ``` 17 | In your code: 18 | ```javascript 19 | const Syntactor = require('syntactor'); 20 | Syntactor.render(document.querySelector('#container'), { 21 | initiallyShowKeymap: true, 22 | defaultValue: {answer: 42} 23 | }); 24 | ``` 25 | 26 | Or if you're using React (and ES2015): 27 | ```javascript 28 | import {Editor} from 'syntactor'; 29 | 30 | const SomeComponent = () => ( 31 |
32 | 33 |
34 | ); 35 | ``` 36 | 37 | ### Props 38 | These can be either passed as React props or as the 2nd argument into the `Syntactor.render` function. 39 | 40 | Name | Type | Default Value 41 | ---|---|---| 42 | initiallyShowKeymap | boolean | `true` 43 | defaultValue | JSON | `{}` 44 | 45 | ## Contributing/Running the demo page 46 | 47 | First clone this repo, then: 48 | ```bash 49 | yarn 50 | ``` 51 | And start it with 52 | ```bash 53 | yarn start 54 | ``` 55 | -------------------------------------------------------------------------------- /src/components/declarations.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import React, { Fragment } from 'react'; 3 | import type { ASTNodeProps } from '../types'; 4 | import { is, List } from 'immutable'; 5 | import ASTNode from './ast-node'; 6 | import Editable from './editable'; 7 | import EditorContext from './editor-context'; 8 | import Highlightable from './highlightable'; 9 | 10 | export const Identifier = ({ node, path }: ASTNodeProps) => ( 11 | 12 | {({ selected }) => ( 13 | 14 | {node.get('name')} 15 | 16 | )} 17 | 18 | ); 19 | 20 | export const VariableDeclaration = (props: ASTNodeProps) => { 21 | const { node, path } = props; 22 | const kind = (node.get('kind') || '').toString(); 23 | const declarations = (node.get('declarations'): any) || List(); 24 | return ( 25 | 26 | {({ onSelect, selected }) => ( 27 | 28 | onSelect(path)}>{kind}{' '} 29 | {declarations.map((declarator, i) => [ 30 | i > 0 && '\n ' + ' '.repeat(kind.length), 31 | , 32 | i + 1 < declarations.size && ',' 33 | ])}; 34 | 35 | )} 36 | 37 | ); 38 | }; 39 | 40 | export const VariableDeclarator = ({ path, ...props }: ASTNodeProps) => ( 41 | 42 | 43 | {' = '} 44 | 45 | 46 | ); 47 | -------------------------------------------------------------------------------- /COMPARISON.md: -------------------------------------------------------------------------------- 1 | # Comparison 2 | Each scenario consists of a JSON object before and after editing. The "**|**"-symbol signifies the cursor position from which to start editing.\ 3 | The "keys"-Section may contain TEXT which is dependent on the input and equal across editors. Hence it is not counted in the total key score. US keyboard layout is used as a baseline since that's what I and most programmers I know use. Dvorak might shave off a few keypress here and there. 4 | 5 | ## Insert property 6 | ```json 7 | { 8 | "Charlie Kaufman": "Eternal Sunshine of the Spotless Mind|", 9 | "David Fincher": "Zodiac" 10 | } 11 | ``` 12 | ```json 13 | { 14 | "Charlie Kaufman": "Eternal Sunshine of the Spotless Mind", 15 | "Christopher Nolan": "Memento", 16 | "David Fincher": "Zodiac" 17 | } 18 | ``` 19 | | | Keys | n 20 | | --- | --- | --- 21 | | **Syntactor** | `Enter` → TEXT → `Tab` → `s` → TEXT | 3 22 | | **IntelliJ** | `Shift` + `Enter` → `Shift` + `'` → TEXT → `→` → `Shift` + `;` → `Space` → `Shift` + `'` → TEXT → `→` → `,` | 12 23 | | **VIM** | `o` → `Shift` + `'` → TEXT → `Shift` + `'` → `Shift` + `;` → `→` → `Space` → `Shift` + `'` → TEXT → `Shift` + `'` → `,` | 14 24 | 25 | ## Delete Property 26 | ```json 27 | { 28 | "|Charlie Kaufman": "Eternal Sunshine of the Spotless Mind", 29 | "Christopher Nolan": "Memento", 30 | "David Fincher": "Zodiac", 31 | "Michael Bay": "Transformers 7" 32 | } 33 | ``` 34 | ```json 35 | { 36 | "Charlie Kaufman": "Eternal Sunshine of the Spotless Mind", 37 | "Christopher Nolan": "Memento", 38 | "David Fincher": "Zodiac" 39 | } 40 | ``` 41 | | | Keys | n 42 | | --- | --- | --- 43 | | **Syntactor** | `↓` → `↓` → `↓` → `Ctrl` + `d` | 5 44 | | **IntelliJ** | `Page Down` → `↑` → `Ctrl` + `y` → `↑` → `End` → `Backspace` | 7 45 | | **VIM** | `3` → `Enter` → `d` → `d` → `↑` → `End` → `Delete` | 7 46 | -------------------------------------------------------------------------------- /src/components/ast-node.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import * as React from 'react'; 3 | import { is, List, Map } from 'immutable'; 4 | import type { 5 | AST, 6 | ASTNodeProps, 7 | BaseASTNodeProps, 8 | EditorContextValue 9 | } from '../types'; 10 | import EditorContext from './editor-context'; 11 | 12 | type State = { 13 | node: AST 14 | }; 15 | 16 | class ASTNode extends React.Component< 17 | BaseASTNodeProps & EditorContextValue, 18 | State 19 | > { 20 | static getDerivedStateFromProps({ ast, path }) { 21 | return { node: (ast.getIn((path: any)): any) }; 22 | } 23 | 24 | state = { 25 | node: Map() 26 | }; 27 | 28 | shouldComponentUpdate(nextProps: BaseASTNodeProps, nextState: State) { 29 | return ( 30 | !is(nextProps.path, this.props.path) || 31 | !is(nextState.node, this.state.node) 32 | ); 33 | } 34 | 35 | render() { 36 | const { level, path, style } = this.props; 37 | const node = (this.state.node: any); 38 | 39 | if (node instanceof List) { 40 | return node.map((n, i) => [ 41 | , 42 | i + 1 === node.size ? null :
43 | ]); 44 | } 45 | 46 | const ASTNodeImpl = (ASTNodes[node.get('type')]: React.ComponentType< 47 | ASTNodeProps 48 | >); 49 | if (!ASTNodeImpl) { 50 | return console.warn('Unknown type', node.get('type'), node.toJS()); 51 | } 52 | return ; 53 | } 54 | } 55 | 56 | export default (props: BaseASTNodeProps) => ( 57 | 58 | {editorContextProps => } 59 | 60 | ); 61 | 62 | let ASTNodes: any = {}; 63 | 64 | export function injectASTNodeComponents(value: any) { 65 | ASTNodes = value; 66 | } 67 | -------------------------------------------------------------------------------- /src/components/literals.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import { is, List } from 'immutable'; 3 | import React from 'react'; 4 | import type { ASTNodeProps } from '../types'; 5 | import Editable from './editable'; 6 | import EditorContext from './editor-context'; 7 | import Highlightable from './highlightable'; 8 | 9 | const Literal = ({ 10 | children, 11 | path, 12 | selectable, 13 | style, 14 | tabIndex 15 | }: ASTNodeProps & { children: any, selectable?: boolean, tabIndex?: string }) => ( 16 | 17 | {({ onSelect, selected }) => ( 18 | onSelect(path))} 21 | {...{ style, tabIndex }} 22 | > 23 | {children} 24 | 25 | )} 26 | 27 | ); 28 | 29 | export const BooleanLiteral = (props: ASTNodeProps) => ( 30 | 31 | {(props.node.get('value') || false).toString()} 32 | 33 | ); 34 | 35 | export const NumericLiteral = (props: ASTNodeProps) => { 36 | const { node } = props; 37 | return ( 38 | 39 | 40 | {node.get('value')} 41 | 42 | 43 | ); 44 | }; 45 | 46 | export const NullLiteral = (props: ASTNodeProps) => ( 47 | 48 | null 49 | 50 | ); 51 | 52 | export const StringLiteral = (props: ASTNodeProps) => { 53 | const { node, style } = props; 54 | const mergedStyle = { color: '#b58900', display: 'inline-block', ...style }; 55 | return ( 56 | 57 | " 58 | 59 | {node.get('value')} 60 | 61 | " 62 | 63 | ); 64 | }; 65 | 66 | StringLiteral.defaultProps = { 67 | path: new List() 68 | }; 69 | -------------------------------------------------------------------------------- /src/navigate/index.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import { 3 | isArrayExpression, 4 | isFile, 5 | isLiteral, 6 | isObjectExpression, 7 | isObjectProperty, 8 | isProgram, 9 | isVariableDeclarator, 10 | VISITOR_KEYS 11 | } from 'babel-types'; 12 | import { is, List } from 'immutable'; 13 | import type { AST, ASTPath, Direction } from '../types'; 14 | 15 | const NON_NAVIGATABLE_TESTS = [ 16 | isFile, 17 | isProgram, 18 | isObjectProperty, 19 | isVariableDeclarator 20 | ]; 21 | const ENCLOSED_TESTS = [isArrayExpression, isObjectExpression]; 22 | 23 | function traverseFast(node, enter, path = List()) { 24 | if (!node) return; 25 | 26 | const keys = VISITOR_KEYS[node.type]; 27 | if (!keys) return; 28 | 29 | if (NON_NAVIGATABLE_TESTS.every(test => !test(node))) { 30 | enter(path); 31 | } 32 | 33 | for (const key of keys) { 34 | const subNode = node[key]; 35 | const subPath = path.push(key); 36 | 37 | if (Array.isArray(subNode)) { 38 | for (let i = 0; i < subNode.length; i++) { 39 | traverseFast(subNode[i], enter, subPath.push(i)); 40 | } 41 | if (subNode.length && ENCLOSED_TESTS.some(test => test(node))) { 42 | enter(subPath.push('end')); 43 | } 44 | } else { 45 | traverseFast(subNode, enter, subPath); 46 | } 47 | } 48 | } 49 | 50 | function traverseAndCollectPaths(ast) { 51 | const paths = []; 52 | traverseFast(ast, path => { 53 | paths.push(path); 54 | }); 55 | return paths; 56 | } 57 | 58 | function directionAsBools(direction: Direction) { 59 | return { 60 | isDown: direction === 'DOWN', 61 | isLeft: direction === 'LEFT', 62 | isRight: direction === 'RIGHT', 63 | isUp: direction === 'UP' 64 | }; 65 | } 66 | 67 | const astPaths: Map = new Map(); 68 | export default function navigate( 69 | direction: Direction, 70 | ast: AST, 71 | path: ASTPath 72 | ) { 73 | const { isDown, isRight, isUp } = directionAsBools(direction); 74 | let paths: ASTPath[]; 75 | if (astPaths.has(ast)) { 76 | paths = ((astPaths.get(ast): any): ASTPath[]); 77 | } else { 78 | paths = traverseAndCollectPaths(ast.toJS()); 79 | astPaths.set(ast, paths); 80 | } 81 | const index = paths.findIndex(p => is(p, path)); 82 | const nextPath = paths[index + (isDown || isRight ? 1 : -1)]; 83 | return (isUp || isDown) &&( 84 | path.last() === 'value' && 85 | nextPath.last() === 'key' && 86 | (isDown && ['id', 'key'].includes(path.last()) || 87 | isLiteral((ast: any).getIn(path).toJS()) || 88 | (isUp && path.last() === 'key' && nextPath.last() === 'value') || 89 | (isUp && nextPath.last() === 'init'))) 90 | ? paths[index + (isDown ? 2 : -2)] || nextPath || path 91 | : nextPath || path; 92 | } 93 | -------------------------------------------------------------------------------- /src/components/editable.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import { is } from 'immutable'; 3 | import React from 'react'; 4 | import styled from 'styled-components'; 5 | import EditorContext from './editor-context'; 6 | import styles from '../utils/styles'; 7 | import type { ASTPath, EditorContextValue } from '../types'; 8 | 9 | const Input = styled.input` 10 | width: ${props => (props.size ? 'auto' : '1px')}; 11 | border: none; 12 | outline: none; 13 | white-space: normal; 14 | background: transparent; 15 | ${styles.text}; 16 | `; 17 | 18 | function setCaretPosition(el: any, caretPos) { 19 | el.value = el.value; 20 | el.setSelectionRange(caretPos, caretPos); 21 | } 22 | 23 | type EditableProps = { 24 | children: any, 25 | path: ASTPath, 26 | style?: any 27 | }; 28 | 29 | class Editable extends React.PureComponent< 30 | EditableProps & { 31 | lastDirection: $PropertyType, 32 | onSelect: $PropertyType, 33 | selected: $PropertyType, 34 | selectedRef: $PropertyType 35 | } 36 | > { 37 | input: { current: null | HTMLInputElement } = React.createRef(); 38 | 39 | getSelectedInput() { 40 | return this.input.current; 41 | } 42 | 43 | componentDidMount() { 44 | const el = this.getSelectedInput(); 45 | if (!el) return; 46 | el.addEventListener('focusin', this.handleFocus); 47 | this.initializeInput(true); 48 | } 49 | 50 | componentDidUpdate() { 51 | this.initializeInput(false); 52 | } 53 | 54 | componentWillUnmount() { 55 | const el = this.getSelectedInput(); 56 | if (!el) return; 57 | el.removeEventListener('focusin', this.handleFocus); 58 | } 59 | 60 | handleFocus = () => { 61 | const { onSelect, path, selected } = this.props; 62 | if (!is(path, selected)) onSelect(path); 63 | }; 64 | 65 | initializeInput = (justMounted: boolean) => { 66 | const { children, path, selected, selectedRef, lastDirection } = this.props; 67 | const input = this.getSelectedInput(); 68 | if (!input || document.activeElement === input) return; 69 | 70 | if (is(path, selected)) { 71 | selectedRef.current = input; 72 | const childrenLength = children && children.toString().length; 73 | if (lastDirection) 74 | setCaretPosition( 75 | input, 76 | 'LEFT' === lastDirection 77 | ? childrenLength 78 | : // when it just mounted but already has value, it means that that key was just entered 79 | justMounted ? childrenLength : 0 80 | ); 81 | input.focus(); 82 | } else { 83 | input.blur(); 84 | } 85 | }; 86 | 87 | render() { 88 | const { children, style } = this.props; 89 | return ( 90 | 42} 92 | innerRef={this.input} 93 | size={children !== undefined && children.toString().length} 94 | style={style} 95 | type="text" 96 | value={children} 97 | /> 98 | ); 99 | } 100 | } 101 | 102 | export default (props: EditableProps) => ( 103 | 104 | {({ lastDirection, onSelect, selected, selectedRef }) => ( 105 | 109 | )} 110 | 111 | ); 112 | -------------------------------------------------------------------------------- /src/key-mappings.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import { 3 | isArrayExpression, 4 | isBooleanLiteral, 5 | isExpression, 6 | isNullLiteral, 7 | isNumericLiteral, 8 | isObjectExpression, 9 | isStringLiteral, 10 | isVariableDeclaration 11 | } from 'babel-types'; 12 | import type { ASTPath } from './types'; 13 | 14 | export type Modifier = 'ctrl' | 'alt' | 'shift'; 15 | 16 | type TestMapping = (node: any, path: ASTPath) => boolean; 17 | 18 | export type KeyMapping = {| 19 | name?: string, 20 | type?: any, 21 | keys?: string[], 22 | modifiers?: Modifier[] | ((node: any, path: ASTPath) => Modifier[]), 23 | mappings?: KeyMapping[], 24 | test?: TestMapping 25 | |}; 26 | 27 | const SET_BOOLEAN = { 28 | type: 'SET_BOOLEAN', 29 | mappings: [ 30 | { 31 | type: true, 32 | keys: ['t'] 33 | }, 34 | { 35 | type: false, 36 | keys: ['f'] 37 | } 38 | ] 39 | }; 40 | 41 | export default ([ 42 | { 43 | name: 'Modify', 44 | mappings: [ 45 | { 46 | type: 'INSERT', 47 | keys: ['Enter'] 48 | }, 49 | { 50 | type: 'DELETE', 51 | keys: ['d'], 52 | modifiers: ['ctrl'] 53 | }, 54 | { 55 | type: 'MOVE', 56 | modifiers: ['alt'], 57 | mappings: [ 58 | { 59 | type: 'UP', 60 | keys: ['ArrowUp'] 61 | }, 62 | { 63 | type: 'DOWN', 64 | keys: ['ArrowDown'] 65 | } 66 | ] 67 | }, 68 | { 69 | type: 'CHANGE_DECLARATION_KIND', 70 | mappings: ['const', 'let', 'var'].map(kind => ({type: kind, keys: [kind[0]]})), 71 | test: node => isVariableDeclaration(node) 72 | } 73 | ] 74 | }, 75 | { 76 | name: 'Transform', 77 | mappings: [ 78 | { 79 | ...SET_BOOLEAN, 80 | test: node => isBooleanLiteral(node) 81 | }, 82 | { 83 | type: 'ADD_TO_NUMBER', 84 | mappings: [ 85 | { 86 | type: 1, 87 | keys: ['i'] 88 | }, 89 | { 90 | type: 10, 91 | keys: ['i'], 92 | modifiers: ['shift'] 93 | }, 94 | { 95 | type: -1, 96 | keys: ['d'] 97 | }, 98 | { 99 | type: -10, 100 | keys: ['d'], 101 | modifiers: ['shift'] 102 | } 103 | ], 104 | test: node => isNumericLiteral(node) 105 | }, 106 | { 107 | type: 'TO_STRING', 108 | keys: ['s', "'"], 109 | test: node => !isStringLiteral(node) 110 | }, 111 | { 112 | type: 'TO_NUMBER', 113 | keys: ['n'], 114 | test: node => !isNumericLiteral(node) 115 | }, 116 | { 117 | ...SET_BOOLEAN, 118 | test: node => !isBooleanLiteral(node) 119 | }, 120 | { 121 | type: 'TO_ARRAY', 122 | keys: ['a', '['], 123 | test: node => !isArrayExpression(node) 124 | }, 125 | { 126 | type: 'TO_OBJECT', 127 | keys: ['o', String.fromCharCode(123)], 128 | test: node => !isObjectExpression(node) 129 | }, 130 | { 131 | type: 'TO_NULL', 132 | keys: ['-'], 133 | test: node => !isNullLiteral(node) 134 | } 135 | ], 136 | modifiers: node => 137 | isNullLiteral(node) || isBooleanLiteral(node) ? [] : ['alt'], 138 | test: (node, path) => 139 | !['key', 'id'].includes(path.last()) && isExpression(node) 140 | }, 141 | { 142 | name: 'General', 143 | mappings: [ 144 | { 145 | type: 'UNDO', 146 | keys: ['z'], 147 | modifiers: ['ctrl'] 148 | }, 149 | { 150 | type: 'REDO', 151 | keys: ['z'], 152 | modifiers: ['ctrl', 'shift'] 153 | }, 154 | { 155 | type: 'COPY', 156 | keys: ['c'], 157 | modifiers: ['ctrl'] 158 | }, 159 | { 160 | type: 'PASTE', 161 | keys: ['v'], 162 | modifiers: ['ctrl'] 163 | } 164 | ] 165 | } 166 | ]: KeyMapping[]); 167 | -------------------------------------------------------------------------------- /src/components/demo.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import React, { PureComponent } from 'react'; 3 | import styled from 'styled-components'; 4 | import styles from '../utils/styles'; 5 | import Editor from './editor'; 6 | 7 | const links = [ 8 | ['Installation', 'https://github.com/Gregoor/syntactor#usage'], 9 | [ 10 | 'Reasoning', 11 | 'https://medium.com/@grgtwt/code-is-not-just-text-1082981ae27f' 12 | ], 13 | [ 14 | 'Comparison', 15 | 'https://github.com/Gregoor/syntactor/blob/master/COMPARISON.md' 16 | ], 17 | ['Roadmap', 'https://github.com/Gregoor/syntactor/milestones?with_issues=no'], 18 | ['GitHub', 'https://github.com/gregoor/syntactor'], 19 | ['Issues', 'https://github.com/Gregoor/syntactor/issues'] 20 | ]; 21 | 22 | const Head = styled.h1` 23 | ${styles.text} font-size: 2em; 24 | text-align: center; 25 | cursor: pointer; 26 | `; 27 | 28 | const Symbol = styled.span` 29 | color: #b7b7b7; 30 | `; 31 | 32 | const Brace = ({ charCode }) => ( 33 | {String.fromCharCode(charCode)} 34 | ); 35 | 36 | const Card = styled.div` 37 | padding: 20px; 38 | background: white; 39 | box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.14), 0 3px 1px -2px rgba(0, 0, 0, 0.2), 40 | 0 1px 5px 0 rgba(0, 0, 0, 0.12); 41 | `; 42 | 43 | const Nav = styled.div` 44 | margin: 0 -20px 20px; 45 | padding: 0 20px 20px; 46 | border-bottom: 1px solid lightgrey; 47 | display: flex; 48 | flex-direction: row; 49 | justify-content: space-between; 50 | `; 51 | 52 | const example = ` 53 | const i = 23; 54 | const README = { 55 | 'WelcomeTo': 'Syntactor', 56 | 'Version': 1.2, 57 | 'In Development': true, 58 | 'Goals': { 59 | 'Manage syntax and code style': ['No Syntax Errors', 'No Bikeshedding'], 60 | 'Make common code transformations accessible': ['Just as many or less keystrokes, compared to other editors'] 61 | }, 62 | 'Features': ['Dynamic Keymap (->)', 'Move elements/Change types without a hassle', 'Redo + Undo'], 63 | 'Current State': 'Only a JSON-Editor', 64 | 'How-To': ['Arrow Keys to navigate', 'Keymap on the right shows you all possible actions'] 65 | }; 66 | `; 67 | 68 | interface State { 69 | startValue: any; 70 | } 71 | 72 | export default class Demo extends PureComponent<{}, State> { 73 | editor: Editor | null; 74 | 75 | constructor() { 76 | super(); 77 | 78 | let startValue; 79 | try { 80 | startValue = decodeURIComponent( 81 | window.location.search.substr(1).split('=')[1] || example 82 | ); 83 | } catch (e) { 84 | if (!(e instanceof SyntaxError)) { 85 | throw e; 86 | } 87 | startValue = example; 88 | } 89 | 90 | this.state = { startValue }; 91 | } 92 | 93 | updateQueryString = (json: string) => { 94 | window.history.pushState({}, '', '?code=' + encodeURIComponent(json)); 95 | }; 96 | 97 | resetEditor = () => { 98 | this.setState({ startValue: example }, () => { 99 | this.editor && this.editor.reset(); 100 | }); 101 | this.updateQueryString(example); 102 | }; 103 | 104 | render() { 105 | const { startValue } = this.state; 106 | return ( 107 |
108 | 109 | Syntactor 110 | 111 | 112 | 113 | 123 | {startValue !== example && ( 124 |
125 | The following content was written by a user. To reset the editor{' '} 126 | 127 |
128 | )} 129 | (this.editor = el)} 134 | /> 135 |
136 |
137 | ); 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /src/components/keymap.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import { isArrayExpression } from 'babel-types'; 3 | import React, { PureComponent } from 'react'; 4 | import styled from 'styled-components'; 5 | import type { KeyMapping, Modifier } from '../key-mappings'; 6 | import keyMappings from '../key-mappings'; 7 | import type { ASTPath } from '../types'; 8 | 9 | const Section = styled.section` 10 | display: flex; 11 | flex-direction: row; 12 | justify-content: space-between; 13 | `; 14 | 15 | const Left = styled.div` 16 | width: 100%; 17 | ${props => props.indent && 'padding-left: 20px;'}; 18 | `; 19 | 20 | const Right = styled.div` 21 | margin-left: 20px; 22 | width: 100%; 23 | text-align: right; 24 | `; 25 | 26 | const SectionTitle = styled.h2` 27 | color: #6d6d6d; 28 | border-bottom: 1px solid #6d6d6d; 29 | margin: 0; 30 | font-size: 14px; 31 | font-weight: normal; 32 | `; 33 | 34 | const ActionType = styled.span``; 35 | 36 | const List = styled.ul` 37 | padding: 0; 38 | list-style: none; 39 | `; 40 | 41 | type Props = { 42 | isInArray: boolean, 43 | level: number, 44 | parentModifiers: Modifier[], 45 | selected: ASTPath, 46 | selectedNode: any 47 | }; 48 | 49 | class KeyMap extends PureComponent< 50 | { 51 | children: KeyMapping 52 | } & Props 53 | > { 54 | static defaultProps = { level: 0, parentModifiers: [] }; 55 | 56 | render() { 57 | const { 58 | children: { type, name, mappings, keys, modifiers, test }, 59 | isInArray, 60 | level, 61 | parentModifiers, 62 | selected, 63 | selectedNode 64 | } = this.props; 65 | 66 | if (test && !test(selectedNode, selected)) return null; 67 | 68 | const itemType = isInArray ? 'element' : 'property'; 69 | 70 | const allModifiers = parentModifiers.concat( 71 | modifiers 72 | ? Array.isArray(modifiers) 73 | ? modifiers 74 | : modifiers(selectedNode, selected) 75 | : [] 76 | ); 77 | 78 | const combos = 79 | mappings || !keys 80 | ? [] 81 | : keys.map(key => [ 82 | ...allModifiers.map(modifier => modifier.slice(0, 1).toUpperCase() + modifier.slice(1)), 83 | { ArrowDown: '⬇', ArrowUp: '⬆' }[key] || key 84 | ]); 85 | 86 | return ( 87 |
88 | 89 | {name && {name}} 90 | {type !== undefined && ( 91 | 92 | {{ 93 | INSERT: 94 | 'Insert ' + 95 | (isInArray || isArrayExpression(selectedNode) 96 | ? 'element' 97 | : 'property'), 98 | DELETE: 'Delete ' + itemType, 99 | MOVE: 'Move ' + itemType, 100 | UP: 'up', 101 | DOWN: 'down', 102 | 103 | ADD_TO_NUMBER: 'Add', 104 | SET_BOOLEAN: 'Set', 105 | true: true, 106 | false: false, 107 | TO_STRING: 'String', 108 | TO_NUMBER: 'Number', 109 | TO_ARRAY: 'Array', 110 | TO_OBJECT: 'Object', 111 | TO_NULL: null, 112 | 113 | UNDO: 'Undo', 114 | REDO: 'Redo', 115 | COPY: 'Copy', 116 | PASTE: 'Paste' 117 | }[type] || type} 118 | 119 | )} 120 | 121 | {mappings && 122 | mappings.map((mapping, i) => ( 123 |
  • 124 | 129 | {mapping} 130 | 131 |
  • 132 | ))} 133 |
    134 |
    135 | {combos.length > 0 && ( 136 | 137 | {combos.map((combo, i) => ( 138 | 139 | {combo.map((key, i) => ( 140 | 141 | {key} 142 | {i + 1 < combo.length && ' + '} 143 | 144 | ))} 145 | {i + 1 < combos.length && ' / '} 146 | 147 | ))} 148 | 149 | )} 150 |
    151 | ); 152 | } 153 | } 154 | 155 | export default (props: Props) => ( 156 | {{ mappings: keyMappings }} 157 | ); 158 | -------------------------------------------------------------------------------- /src/components/collections.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import React, { PureComponent } from 'react'; 3 | import styled from 'styled-components'; 4 | import type { ASTNodeProps } from '../types'; 5 | import ASTNode from './ast-node'; 6 | import EditorContext from './editor-context'; 7 | import Highlightable from './highlightable'; 8 | import { is } from 'immutable'; 9 | 10 | const IndentContainer = styled.span` 11 | border-left: 1px solid rgba(0, 0, 0, 0.1); 12 | background: white; 13 | `; 14 | 15 | const indent = level => { 16 | const indents = []; 17 | for (let i = 0; i < level; i++) { 18 | indents.push({' '}); 19 | } 20 | return indents; 21 | }; 22 | 23 | const Symbol = styled.span` 24 | color: grey; 25 | `; 26 | 27 | type CollectionExpressionProps = ASTNodeProps & { 28 | children?: any, 29 | closeString: string, 30 | openString: string 31 | }; 32 | 33 | const InnerCollectionExpression = ({ 34 | children, 35 | closeString, 36 | endSelected, 37 | onSelect, 38 | openString, 39 | level, 40 | path, 41 | startSelected 42 | }: CollectionExpressionProps & { 43 | endSelected: boolean, 44 | openString: string, 45 | closeString: string, 46 | onSelect: any, 47 | startSelected: boolean 48 | }) => { 49 | const highlighted = startSelected || endSelected; 50 | return !children || !children.size ? ( 51 | onSelect(path.butLast())} 54 | > 55 | 56 | {openString} 57 | {closeString} 58 | 59 | 60 | ) : ( 61 | 62 | onSelect(path.butLast())} 66 | > 67 | {openString} 68 | 69 | {'\n'} 70 | {React.Children.map(children, (element, i) => ( 71 | 72 | {indent(level)} 73 | {element} 74 | {children && i + 1 < children.size && ,} 75 | {'\n'} 76 | 77 | ))} 78 | {indent(level - 1)} 79 | onSelect(path.push('end'))} 83 | > 84 | {closeString} 85 | 86 | 87 | ); 88 | }; 89 | 90 | const CollectionExpression = (props: CollectionExpressionProps) => { 91 | const { path } = props; 92 | return ( 93 | 94 | {({ onSelect, selected }) => ( 95 | 101 | )} 102 | 103 | ); 104 | }; 105 | 106 | export class ArrayExpression extends PureComponent { 107 | render() { 108 | const { node } = this.props; 109 | const level = this.props.level + 1; 110 | const path = this.props.path.push('elements'); 111 | return ( 112 | 119 | {(node.get('elements'): any).map((node, i) => ( 120 | 121 | 122 | 123 | ))} 124 | 125 | ); 126 | } 127 | } 128 | 129 | export class ObjectExpression extends PureComponent { 130 | render() { 131 | const { node } = this.props; 132 | const level = this.props.level + 1; 133 | const keyStyle = { color: '#d33682' }; 134 | const path = this.props.path.push('properties'); 135 | return ( 136 | 143 | {(node.get('properties'): any).map((node, i) => { 144 | const propertyPath = path.push(i); 145 | return ( 146 | 147 | 152 | :{' '} 153 | 154 | 155 | ); 156 | })} 157 | 158 | ); 159 | } 160 | } 161 | -------------------------------------------------------------------------------- /src/ast.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import { 3 | BINARY_OPERATORS, 4 | LOGICAL_OPERATORS, 5 | UNARY_OPERATORS, 6 | UPDATE_OPERATORS 7 | } from 'babel-types'; 8 | 9 | type ASTNode = {| 10 | fields?: { 11 | [name: string]: {| 12 | type?: string | any[], 13 | values?: any[], 14 | each?: string | string[], 15 | default?: any, 16 | optional?: true 17 | |} 18 | }, 19 | visitor?: string[], 20 | aliases?: string[], 21 | inherits?: string, 22 | deprecatedAlias?: string 23 | |}; 24 | 25 | const functionCommon = { 26 | params: { 27 | type: 'array', 28 | each: 'LVal' 29 | }, 30 | generator: { 31 | type: 'boolean', 32 | default: false 33 | }, 34 | async: { 35 | type: 'boolean', 36 | default: false 37 | } 38 | }; 39 | 40 | const functionTypeAnnotationCommon = { 41 | returnType: { 42 | type: ['TypeAnnotation', 'TSTypeAnnotation', 'Noop'], 43 | optional: true 44 | }, 45 | typeParameters: { 46 | type: ['TypeParameterDeclaration', 'TSTypeParameterDeclaration', 'Noop'], 47 | optional: true 48 | } 49 | }; 50 | 51 | const functionDeclarationCommon = { 52 | ...functionCommon, 53 | declare: { 54 | type: 'boolean', 55 | optional: true 56 | }, 57 | id: { 58 | type: 'Identifier', 59 | optional: true // May be null for `default function` 60 | } 61 | }; 62 | 63 | const patternLikeCommon = { 64 | typeAnnotation: { 65 | // TODO: @babel/plugin-transform-flow-comments puts a Noop here, is there a better way? 66 | type: ['TypeAnnotation', 'TSTypeAnnotation', 'Noop'], 67 | optional: true 68 | }, 69 | decorators: { 70 | type: 'array', 71 | each: 'Decorator' 72 | } 73 | }; 74 | 75 | const nodeTypes: { [key: string]: ASTNode } = { 76 | ArrayExpression: { 77 | fields: { 78 | elements: { 79 | type: 'array', 80 | each: ['null', 'Expression', 'SpreadElement'], 81 | default: [] 82 | } 83 | }, 84 | visitor: ['elements'], 85 | aliases: ['Expression'] 86 | }, 87 | 88 | AssignmentExpression: { 89 | fields: { 90 | operator: { 91 | type: 'string' 92 | }, 93 | left: { 94 | type: 'LVal' 95 | }, 96 | right: { 97 | type: 'Expression' 98 | } 99 | }, 100 | visitor: ['left', 'right'], 101 | aliases: ['Expression'] 102 | }, 103 | 104 | BinaryExpression: { 105 | fields: { 106 | operator: { 107 | type: BINARY_OPERATORS 108 | }, 109 | left: { 110 | type: 'Expression' 111 | }, 112 | right: { 113 | type: 'Expression' 114 | } 115 | }, 116 | visitor: ['left', 'right'], 117 | aliases: ['Binary', 'Expression'] 118 | }, 119 | 120 | Directive: { 121 | visitor: ['value'], 122 | fields: { 123 | value: { 124 | type: 'DirectiveLiteral' 125 | } 126 | } 127 | }, 128 | 129 | DirectiveLiteral: { 130 | fields: { 131 | value: { 132 | type: 'string' 133 | } 134 | } 135 | }, 136 | 137 | BlockStatement: { 138 | visitor: ['directives', 'body'], 139 | fields: { 140 | directives: { 141 | type: 'array', 142 | each: 'Directive', 143 | default: [] 144 | }, 145 | body: { 146 | type: 'array', 147 | each: 'Statement' 148 | } 149 | }, 150 | aliases: ['Scopable', 'BlockParent', 'Block', 'Statement'] 151 | }, 152 | 153 | BreakStatement: { 154 | visitor: ['label'], 155 | fields: { 156 | label: { 157 | type: 'Identifier', 158 | optional: true 159 | } 160 | }, 161 | aliases: ['Statement', 'Terminatorless', 'CompletionStatement'] 162 | }, 163 | 164 | CallExpression: { 165 | visitor: ['callee', 'arguments', 'typeParameters'], 166 | aliases: ['Expression'], 167 | fields: { 168 | callee: { 169 | type: 'Expression' 170 | }, 171 | arguments: { 172 | type: 'array', 173 | each: ['Expression', 'SpreadElement', 'JSXNamespacedName'] 174 | }, 175 | optional: { 176 | type: [true, false], 177 | optional: true 178 | } 179 | } 180 | }, 181 | 182 | CatchClause: { 183 | visitor: ['param', 'body'], 184 | fields: { 185 | param: { 186 | type: 'Identifier', 187 | optional: true 188 | }, 189 | body: { 190 | type: 'BlockStatement' 191 | } 192 | }, 193 | aliases: ['Scopable', 'BlockParent'] 194 | }, 195 | 196 | ConditionalExpression: { 197 | visitor: ['test', 'consequent', 'alternate'], 198 | fields: { 199 | test: { 200 | type: 'Expression' 201 | }, 202 | consequent: { 203 | type: 'Expression' 204 | }, 205 | alternate: { 206 | type: 'Expression' 207 | } 208 | }, 209 | aliases: ['Expression', 'Conditional'] 210 | }, 211 | 212 | ContinueStatement: { 213 | visitor: ['label'], 214 | fields: { 215 | label: { 216 | type: 'Identifier', 217 | optional: true 218 | } 219 | }, 220 | aliases: ['Statement', 'Terminatorless', 'CompletionStatement'] 221 | }, 222 | 223 | DebuggerStatement: { 224 | aliases: ['Statement'] 225 | }, 226 | 227 | DoWhileStatement: { 228 | visitor: ['test', 'body'], 229 | fields: { 230 | test: { 231 | type: 'Expression' 232 | }, 233 | body: { 234 | type: 'Statement' 235 | } 236 | }, 237 | aliases: ['Statement', 'BlockParent', 'Loop', 'While', 'Scopable'] 238 | }, 239 | 240 | EmptyStatement: { 241 | aliases: ['Statement'] 242 | }, 243 | 244 | ExpressionStatement: { 245 | visitor: ['expression'], 246 | fields: { 247 | expression: { 248 | type: 'Expression' 249 | } 250 | }, 251 | aliases: ['Statement', 'ExpressionWrapper'] 252 | }, 253 | 254 | File: { 255 | visitor: ['program'], 256 | fields: { 257 | program: { 258 | type: 'Program' 259 | } 260 | } 261 | }, 262 | 263 | ForInStatement: { 264 | visitor: ['left', 'right', 'body'], 265 | aliases: [ 266 | 'Scopable', 267 | 'Statement', 268 | 'For', 269 | 'BlockParent', 270 | 'Loop', 271 | 'ForXStatement' 272 | ], 273 | fields: { 274 | left: { 275 | type: ['VariableDeclaration', 'LVal'] 276 | }, 277 | right: { 278 | type: 'Expression' 279 | }, 280 | body: { 281 | type: 'Statement' 282 | } 283 | } 284 | }, 285 | 286 | ForStatement: { 287 | visitor: ['init', 'test', 'update', 'body'], 288 | aliases: ['Scopable', 'Statement', 'For', 'BlockParent', 'Loop'], 289 | fields: { 290 | init: { 291 | type: ['VariableDeclaration', 'Expression'], 292 | optional: true 293 | }, 294 | test: { 295 | type: 'Expression', 296 | optional: true 297 | }, 298 | update: { 299 | type: 'Expression', 300 | optional: true 301 | }, 302 | body: { 303 | type: 'Statement' 304 | } 305 | } 306 | }, 307 | 308 | FunctionDeclaration: { 309 | visitor: ['id', 'params', 'body', 'returnType', 'typeParameters'], 310 | fields: { 311 | ...functionDeclarationCommon, 312 | ...functionTypeAnnotationCommon, 313 | body: { 314 | type: 'BlockStatement' 315 | } 316 | }, 317 | aliases: [ 318 | 'Scopable', 319 | 'Function', 320 | 'BlockParent', 321 | 'FunctionParent', 322 | 'Statement', 323 | 'Pureish', 324 | 'Declaration' 325 | ] 326 | }, 327 | 328 | FunctionExpression: { 329 | inherits: 'FunctionDeclaration', 330 | aliases: [ 331 | 'Scopable', 332 | 'Function', 333 | 'BlockParent', 334 | 'FunctionParent', 335 | 'Expression', 336 | 'Pureish' 337 | ], 338 | fields: { 339 | ...functionCommon, 340 | ...functionTypeAnnotationCommon, 341 | id: { 342 | type: 'Identifier', 343 | optional: true 344 | }, 345 | body: { 346 | type: 'BlockStatement' 347 | } 348 | } 349 | }, 350 | 351 | Identifier: { 352 | visitor: ['typeAnnotation'], 353 | aliases: ['Expression', 'PatternLike', 'LVal', 'TSEntityName'], 354 | fields: { 355 | ...patternLikeCommon, 356 | name: { 357 | type: 'string' // check out isValidIdentifier in Babel 358 | }, 359 | optional: { 360 | type: 'boolean', 361 | optional: true 362 | } 363 | } 364 | }, 365 | 366 | IfStatement: { 367 | visitor: ['test', 'consequent', 'alternate'], 368 | aliases: ['Statement', 'Conditional'], 369 | fields: { 370 | test: { 371 | type: 'Expression' 372 | }, 373 | consequent: { 374 | type: 'Statement' 375 | }, 376 | alternate: { 377 | optional: true, 378 | type: 'Statement' 379 | } 380 | } 381 | }, 382 | 383 | LabeledStatement: { 384 | visitor: ['label', 'body'], 385 | aliases: ['Statement'], 386 | fields: { 387 | label: { 388 | type: 'Identifier' 389 | }, 390 | body: { 391 | type: 'Statement' 392 | } 393 | } 394 | }, 395 | 396 | StringLiteral: { 397 | fields: { 398 | value: { 399 | type: 'string' 400 | } 401 | }, 402 | aliases: ['Expression', 'Pureish', 'Literal', 'Immutable'] 403 | }, 404 | 405 | NumericLiteral: { 406 | deprecatedAlias: 'NumberLiteral', 407 | fields: { 408 | value: { 409 | type: 'number' 410 | } 411 | }, 412 | aliases: ['Expression', 'Pureish', 'Literal', 'Immutable'] 413 | }, 414 | 415 | NullLiteral: { 416 | aliases: ['Expression', 'Pureish', 'Literal', 'Immutable'] 417 | }, 418 | 419 | BooleanLiteral: { 420 | fields: { 421 | value: { 422 | type: 'boolean' 423 | } 424 | }, 425 | aliases: ['Expression', 'Pureish', 'Literal', 'Immutable'] 426 | }, 427 | 428 | RegExpLiteral: { 429 | deprecatedAlias: 'RegexLiteral', 430 | aliases: ['Expression', 'Literal'], 431 | fields: { 432 | pattern: { 433 | type: 'string' 434 | }, 435 | flags: { 436 | type: 'string', 437 | default: '' 438 | } 439 | } 440 | }, 441 | 442 | LogicalExpression: { 443 | visitor: ['left', 'right'], 444 | aliases: ['Binary', 'Expression'], 445 | fields: { 446 | operator: { 447 | type: LOGICAL_OPERATORS 448 | }, 449 | left: { 450 | type: 'Expression' 451 | }, 452 | right: { 453 | type: 'Expression' 454 | } 455 | } 456 | }, 457 | 458 | MemberExpression: { 459 | visitor: ['object', 'property'], 460 | aliases: ['Expression', 'LVal'], 461 | fields: { 462 | object: { 463 | type: 'Expression' 464 | }, 465 | property: { 466 | type: ['Expression', 'Identifier'] 467 | }, 468 | computed: { 469 | default: false 470 | }, 471 | optional: { 472 | type: [true, false], 473 | optional: true 474 | } 475 | } 476 | }, 477 | 478 | NewExpression: { 479 | inherits: 'CallExpression' 480 | }, 481 | 482 | Program: { 483 | visitor: ['directives', 'body'], 484 | fields: { 485 | sourceFile: { 486 | type: 'string' 487 | }, 488 | sourceType: { 489 | type: 'enum', 490 | values: ['script', 'module'], 491 | default: 'script' 492 | }, 493 | directives: { 494 | type: 'array', 495 | each: 'Directive', 496 | default: [] 497 | }, 498 | body: { 499 | type: 'array', 500 | each: 'Statement' 501 | } 502 | }, 503 | aliases: ['Scopable', 'BlockParent', 'Block'] 504 | }, 505 | 506 | ObjectExpression: { 507 | visitor: ['properties'], 508 | aliases: ['Expression'], 509 | fields: { 510 | properties: { 511 | type: 'array', 512 | each: ['ObjectMethod', 'ObjectProperty', 'SpreadElement'] 513 | } 514 | } 515 | }, 516 | 517 | ObjectMethod: { 518 | fields: { 519 | ...functionCommon, 520 | ...functionTypeAnnotationCommon, 521 | kind: { 522 | type: 'enum', 523 | values: ['method', 'get', 'set'], 524 | default: 'method' 525 | }, 526 | computed: { 527 | type: 'boolean', 528 | default: false 529 | }, 530 | key: { 531 | type: ['Identifier', 'StringLiteral', 'NumericLiteral', 'Expression'] 532 | }, 533 | decorators: { 534 | type: 'array', 535 | each: 'Decorator' 536 | }, 537 | body: { 538 | type: 'BlockStatement' 539 | } 540 | }, 541 | visitor: [ 542 | 'key', 543 | 'params', 544 | 'body', 545 | 'decorators', 546 | 'returnType', 547 | 'typeParameters' 548 | ], 549 | aliases: [ 550 | 'UserWhitespacable', 551 | 'Function', 552 | 'Scopable', 553 | 'BlockParent', 554 | 'FunctionParent', 555 | 'Method', 556 | 'ObjectMember' 557 | ] 558 | }, 559 | 560 | ObjectProperty: { 561 | fields: { 562 | computed: { 563 | type: 'boolean', 564 | default: false 565 | }, 566 | key: { 567 | type: ['Identifier', 'StringLiteral', 'NumericLiteral', 'Expression'] 568 | }, 569 | value: { 570 | type: ['Expression', 'PatternLike'] 571 | }, 572 | shorthand: { 573 | type: 'boolean', 574 | default: false 575 | }, 576 | decorators: { 577 | type: 'array', 578 | each: 'Decorator', 579 | optional: true 580 | } 581 | }, 582 | visitor: ['key', 'value' /*TODO:'decorators'*/], 583 | aliases: ['UserWhitespacable', 'Property', 'ObjectMember'] 584 | }, 585 | 586 | RestElement: { 587 | visitor: ['argument', 'typeAnnotation'], 588 | aliases: ['LVal', 'PatternLike'], 589 | deprecatedAlias: 'RestProperty', 590 | fields: { 591 | ...patternLikeCommon, 592 | argument: { 593 | type: 'LVal' 594 | } 595 | } 596 | }, 597 | 598 | ReturnStatement: { 599 | visitor: ['argument'], 600 | aliases: ['Statement', 'Terminatorless', 'CompletionStatement'], 601 | fields: { 602 | argument: { 603 | type: 'Expression', 604 | optional: true 605 | } 606 | } 607 | }, 608 | 609 | SequenceExpression: { 610 | visitor: ['expressions'], 611 | fields: { 612 | expressions: { 613 | type: 'array', 614 | each: 'Expression' 615 | } 616 | }, 617 | aliases: ['Expression'] 618 | }, 619 | 620 | SwitchCase: { 621 | visitor: ['test', 'consequent'], 622 | fields: { 623 | test: { 624 | type: 'Expression', 625 | optional: true 626 | }, 627 | consequent: { 628 | type: 'array', 629 | each: 'Statement' 630 | } 631 | } 632 | }, 633 | 634 | SwitchStatement: { 635 | visitor: ['discriminant', 'cases'], 636 | aliases: ['Statement', 'BlockParent', 'Scopable'], 637 | fields: { 638 | discriminant: { 639 | type: 'Expression' 640 | }, 641 | cases: { 642 | type: 'array', 643 | each: 'SwitchCase' 644 | } 645 | } 646 | }, 647 | 648 | ThisExpression: { 649 | aliases: ['Expression'] 650 | }, 651 | 652 | ThrowStatement: { 653 | visitor: ['argument'], 654 | aliases: ['Statement', 'Terminatorless', 'CompletionStatement'], 655 | fields: { 656 | argument: { 657 | type: 'Expression' 658 | } 659 | } 660 | }, 661 | 662 | TryStatement: { 663 | visitor: ['block', 'handler', 'finalizer'], 664 | aliases: ['Statement'], 665 | fields: { 666 | block: { 667 | type: 'BlockStatement' 668 | }, 669 | handler: { 670 | type: 'CatchClause', 671 | optional: true 672 | }, 673 | finalizer: { 674 | optional: true, 675 | type: 'BlockStatement' 676 | } 677 | } 678 | }, 679 | 680 | UnaryExpression: { 681 | fields: { 682 | prefix: { 683 | default: true 684 | }, 685 | argument: { 686 | type: 'Expression' 687 | }, 688 | operator: { 689 | type: UNARY_OPERATORS 690 | } 691 | }, 692 | visitor: ['argument'], 693 | aliases: ['UnaryLike', 'Expression'] 694 | }, 695 | 696 | UpdateExpression: { 697 | fields: { 698 | prefix: { 699 | default: false 700 | }, 701 | argument: { 702 | type: 'Expression' 703 | }, 704 | operator: { 705 | type: UPDATE_OPERATORS 706 | } 707 | }, 708 | visitor: ['argument'], 709 | aliases: ['Expression'] 710 | }, 711 | 712 | VariableDeclaration: { 713 | visitor: ['kind', 'declarations'], 714 | aliases: ['Statement', 'Declaration'], 715 | fields: { 716 | declare: { 717 | type: 'boolean', 718 | optional: true 719 | }, 720 | kind: { 721 | type: 'enum', 722 | values: ['var', 'let', 'const'] 723 | }, 724 | declarations: { 725 | type: 'array', 726 | each: 'VariableDeclarator' 727 | } 728 | } 729 | }, 730 | 731 | VariableDeclarator: { 732 | visitor: ['id', 'init'], 733 | fields: { 734 | id: { 735 | type: 'LVal' 736 | }, 737 | init: { 738 | type: 'Expression', 739 | optional: true 740 | } 741 | } 742 | }, 743 | 744 | WhileStatement: { 745 | visitor: ['test', 'body'], 746 | aliases: ['Statement', 'BlockParent', 'Loop', 'While', 'Scopable'], 747 | fields: { 748 | test: { 749 | type: 'Expression' 750 | }, 751 | body: { 752 | type: ['BlockStatement', 'Statement'] 753 | } 754 | } 755 | }, 756 | 757 | WithStatement: { 758 | visitor: ['object', 'body'], 759 | aliases: ['Statement'], 760 | fields: { 761 | object: { 762 | type: 'Expression' 763 | }, 764 | body: { 765 | type: ['BlockStatement', 'Statement'] 766 | } 767 | } 768 | } 769 | }; 770 | 771 | export default nodeTypes; 772 | -------------------------------------------------------------------------------- /src/components/editor.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import React, { PureComponent } from 'react'; 3 | import ReactDOM from 'react-dom'; 4 | import styled from 'styled-components'; 5 | import generate from 'babel-generator'; 6 | import { 7 | arrayExpression, 8 | booleanLiteral, 9 | isArrayExpression, 10 | isIdentifier, 11 | isNullLiteral, 12 | isNumericLiteral, 13 | isObjectExpression, 14 | isObjectProperty, 15 | isStringLiteral, 16 | nullLiteral, 17 | numericLiteral, 18 | objectExpression, 19 | objectProperty, 20 | stringLiteral 21 | } from 'babel-types'; 22 | import * as Immutable from 'immutable'; 23 | import EditorContext from './editor-context'; 24 | import keyMappings from '../key-mappings'; 25 | import navigate from '../navigate'; 26 | import parse, { parseObject } from '../utils/parse'; 27 | import styles from '../utils/styles'; 28 | import type { 29 | AST, 30 | ASTPath, 31 | Direction, 32 | EditorContextValue, 33 | VerticalDirection 34 | } from '../types'; 35 | import * as collections from './collections'; 36 | import * as declarations from './declarations'; 37 | import Keymap from './keymap'; 38 | import * as literals from './literals'; 39 | import * as misc from './misc'; 40 | import ASTNode, { injectASTNodeComponents } from './ast-node'; 41 | import type { KeyMapping } from '../key-mappings'; 42 | 43 | const { List } = Immutable; 44 | 45 | const MAX_HISTORY_LENGTH = 100; 46 | 47 | function between(number, lower, upper) { 48 | return number >= lower && number <= upper; 49 | } 50 | 51 | function isEditable(node?: AST) { 52 | return isStringLiteral(node) || isNumericLiteral(node) || isIdentifier(node); 53 | } 54 | 55 | injectASTNodeComponents({ 56 | ...collections, 57 | ...declarations, 58 | ...literals, 59 | ...misc 60 | }); 61 | 62 | const Container = styled.div` 63 | position: relative; 64 | display: flex; 65 | flex-direction: row; 66 | white-space: pre; 67 | outline: none; 68 | ${styles.text}; 69 | `; 70 | 71 | const Button = styled.button` 72 | position: absolute; 73 | right: 0; 74 | `; 75 | 76 | const Form = styled.form` 77 | width: 100%; 78 | padding: 1px; 79 | overflow-x: auto; 80 | `; 81 | 82 | declare type Props = { 83 | initiallyShowKeymap: boolean, 84 | defaultValue: {}, 85 | onChange: (json: string) => any 86 | }; 87 | 88 | declare type EditorState = { 89 | +ast: any /*AST*/, 90 | +selected: ASTPath 91 | }; 92 | 93 | const SELECTED_PREFIX = List.of('program', 'body'); 94 | 95 | export default class Editor extends PureComponent< 96 | Props, 97 | { 98 | future: List, 99 | history: List, 100 | showKeymap: boolean 101 | } 102 | > { 103 | static defaultProps = { 104 | initiallShowKeymap: true, 105 | defaultValue: {}, 106 | onChange: () => null 107 | }; 108 | 109 | actions: { [actionType: string]: (actionParam: any) => any }; 110 | 111 | contextValue: EditorContextValue; 112 | 113 | constructor(props: Props) { 114 | super(props); 115 | const initalEditorState = { 116 | ast: parse(props.defaultValue), 117 | selected: SELECTED_PREFIX 118 | }; 119 | this.state = { 120 | future: List(), 121 | history: List([initalEditorState]), 122 | showKeymap: props.initiallyShowKeymap 123 | }; 124 | this.contextValue = { 125 | ...initalEditorState, 126 | lastDirection: 'DOWN', 127 | selectedRef: React.createRef(), 128 | onSelect: this.handleSelect 129 | }; 130 | this.actions = { 131 | UNDO: () => this.undo(), 132 | REDO: () => this.redo(), 133 | 134 | INSERT: () => this.insert(nullLiteral()), 135 | MOVE: direction => this.moveSelected(direction), 136 | DELETE: () => this.deleteSelected(), 137 | 138 | SET_BOOLEAN: value => this.replace(booleanLiteral(value)), 139 | ADD_TO_NUMBER: increment => 140 | this.updateValue(value => (parseFloat(value) + increment).toString()), 141 | CHANGE_DECLARATION_KIND: (kind) => this.changeDeclarationKind(kind), 142 | TO_STRING: () => 143 | this.replace( 144 | stringLiteral((this.getSelectedNode().value || '').toString()) 145 | ), 146 | TO_NUMBER: () => { 147 | const {value} = this.getSelectedNode(); 148 | return this.replace( 149 | numericLiteral(Number(value) || parseFloat(value) || 0) 150 | ); 151 | }, 152 | TO_ARRAY: () => this.replace(arrayExpression([this.getSelectedNode()])), 153 | TO_OBJECT: () => 154 | this.replace( 155 | (Immutable.fromJS( 156 | objectExpression([ 157 | objectProperty(stringLiteral(''), this.getSelectedNode()) 158 | ]) 159 | ): any), 160 | List.of('properties', 0, 'key') 161 | ), 162 | TO_NULL: () => this.replace(nullLiteral()) 163 | }; 164 | } 165 | 166 | componentDidMount() { 167 | document.addEventListener('copy', this.handleCopy); 168 | document.addEventListener('cut', this.handleCut); 169 | document.addEventListener('paste', this.handlePaste); 170 | } 171 | 172 | componentWillUnmount() { 173 | document.removeEventListener('copy', this.handleCopy); 174 | document.removeEventListener('cut', this.handleCut); 175 | document.removeEventListener('paste', this.handlePaste); 176 | } 177 | 178 | toggleShowKeymap = () => 179 | this.setState(({ showKeymap }) => ({ 180 | showKeymap: !showKeymap 181 | })); 182 | 183 | retainFocus = (el: any) => { 184 | if (el && !isEditable(this.getSelectedNode())) { 185 | const div = ReactDOM.findDOMNode(el); 186 | if (div instanceof HTMLElement) { 187 | div.focus(); 188 | } 189 | } 190 | }; 191 | 192 | getCurrentEditorState() { 193 | return ((this.state.history.first(): any): EditorState); 194 | } 195 | 196 | getSelectedNode() { 197 | const { ast, selected } = this.getCurrentEditorState(); 198 | const node = ast.getIn(selected); 199 | return node ? node.toJS() : node; 200 | } 201 | 202 | getClosestCollectionPath(ast: any /*AST*/, selected: ASTPath) { 203 | const selectedNode = ast.getIn(selected).toJS(); 204 | 205 | if (isObjectExpression(selectedNode)) { 206 | return selected.push('properties'); 207 | } else if (isArrayExpression(selectedNode)) { 208 | return selected.push('elements'); 209 | } 210 | 211 | const index = selected.findLastIndex(key => 212 | ['elements', 'properties'].includes(key) 213 | ); 214 | return selected.slice(0, index + 1); 215 | } 216 | 217 | addToHistory(updateFn: (ast: any /*AST*/, selected: ASTPath) => any) { 218 | this.setState(({ history }) => { 219 | let { ast, selected } = history.first() || {}; 220 | 221 | if (selected.last() !== 'end' && !ast.getIn(selected)) { 222 | selected = List(); 223 | } 224 | 225 | const selectedNode = ast.getIn(selected); 226 | if (selectedNode && isNumericLiteral(selectedNode.toJS())) { 227 | ast = ast.updateIn(selected.push('value'), value => 228 | parseFloat(value).toString() 229 | ); 230 | } 231 | 232 | const newState = { ast, selected, ...updateFn(ast, selected) }; 233 | const isASTPristine = Immutable.is(ast, newState.ast); 234 | 235 | if (newState.ast && !isASTPristine) { 236 | this.props.onChange(generate(newState.ast.toJS()).code); 237 | } 238 | this.updateEditorStateContext(newState); 239 | return Immutable.is(selected, newState.selected) && isASTPristine 240 | ? undefined 241 | : { 242 | future: List(), 243 | history: history.unshift(newState).slice(0, MAX_HISTORY_LENGTH) 244 | }; 245 | }); 246 | } 247 | 248 | updateValue(updateFn: any => any) { 249 | this.addToHistory((ast, selected) => ({ 250 | ast: ast.updateIn(selected.push('value'), updateFn) 251 | })); 252 | } 253 | 254 | changeDeclarationKind(kind: string) { 255 | this.addToHistory((ast, selected) => ({ 256 | ast: ast.updateIn(selected.push('kind'), () => kind) 257 | })); 258 | } 259 | 260 | insert = (node: any) => 261 | this.addToHistory((ast, selected) => { 262 | const immutableNode = Immutable.fromJS(node); 263 | const collectionPath = this.getClosestCollectionPath( 264 | ast, 265 | selected.last() === 'end' ? selected.slice(0, -3) : selected 266 | ); 267 | const itemIndex = 268 | selected.last() === 'end' 269 | ? collectionPath.size 270 | : selected.get(collectionPath.size) + 1 || 0; 271 | 272 | const collectionNode = ast.getIn(collectionPath.butLast()).toJS(); 273 | const isArray = isArrayExpression(collectionNode); 274 | const isObject = isObjectExpression(collectionNode); 275 | 276 | if (!isArray && !isObject) return; 277 | 278 | const newAST = ast.updateIn(collectionPath, list => 279 | list.insert( 280 | itemIndex, 281 | isArray 282 | ? immutableNode 283 | : Immutable.fromJS(objectProperty(stringLiteral(''), node)) 284 | ) 285 | ); 286 | let newSelected = collectionPath.push(itemIndex); 287 | if (!isArray) newSelected = newSelected.push('key'); 288 | return { 289 | ast: newAST, 290 | selected: newSelected 291 | }; 292 | }); 293 | 294 | replace(node: any /*AST*/, subSelected: ASTPath = List.of()) { 295 | this.addToHistory((ast, selected) => ({ 296 | ast: ast.updateIn(selected, () => Immutable.fromJS(node)), 297 | selected: selected.concat(subSelected) 298 | })); 299 | } 300 | 301 | changeSelected = ( 302 | changeFn: ( 303 | ast: any /*AST*/, 304 | selected: ASTPath 305 | ) => { direction?: Direction, selected: ASTPath } 306 | ) => { 307 | return this.addToHistory((ast, selected) => { 308 | const { direction, selected: newSelected } = changeFn(ast, selected); 309 | this.contextValue = { ...this.contextValue, lastDirection: direction }; 310 | return { 311 | ast, 312 | selected: newSelected 313 | }; 314 | }); 315 | }; 316 | 317 | deleteSelected() { 318 | return this.addToHistory((ast, selected) => { 319 | const newAST = ast.deleteIn( 320 | selected.slice( 321 | 0, 322 | 1 + selected.findLastIndex(value => typeof value === 'number') 323 | ) 324 | ); 325 | const isASTDelete = 326 | selected.isEmpty() || 327 | (selected.size === 2 && selected.last() === 'end'); 328 | return { 329 | ast: isASTDelete ? Immutable.fromJS(nullLiteral()) : newAST, 330 | selected: 331 | isASTDelete || selected.last() === 'end' 332 | ? List() 333 | : navigate('DOWN', newAST, navigate('UP', ast, selected)) 334 | }; 335 | }); 336 | } 337 | 338 | moveSelected = (direction: VerticalDirection) => 339 | this.addToHistory((ast, selected) => { 340 | const isMoveUp = direction === 'UP'; 341 | 342 | const collectionPath = this.getClosestCollectionPath(ast, selected); 343 | 344 | const itemIndex = 345 | selected.last() === 'end' 346 | ? collectionPath.size 347 | : selected.get(collectionPath.size) || 0; 348 | const itemPath = collectionPath.push(itemIndex); 349 | const item = ast.getIn(itemPath); 350 | const isItemObjectProperty = isObjectProperty(item); 351 | 352 | const newItemIndex = parseInt(itemIndex, 10) + (isMoveUp ? -1 : 1); 353 | const newItemPath = collectionPath.push(newItemIndex); 354 | const targetItem = ast.getIn(newItemPath); 355 | 356 | if ( 357 | isItemObjectProperty && 358 | isObjectProperty(targetItem) && 359 | isObjectExpression(targetItem.value) 360 | ) { 361 | const targetObjectPath = newItemPath.push('value', 'properties'); 362 | const targetIndex = isMoveUp ? ast.getIn(targetObjectPath).size : 0; 363 | return { 364 | ast: ast 365 | .updateIn(targetObjectPath, collection => 366 | collection.insert(targetIndex, item) 367 | ) 368 | .updateIn(collectionPath, collection => 369 | collection.delete(itemIndex) 370 | ), 371 | selected: collectionPath 372 | .push( 373 | newItemIndex + (isMoveUp ? 0 : -1), 374 | 'value', 375 | 'properties', 376 | targetIndex 377 | ) 378 | .concat(selected.slice(collectionPath.size + 1)) 379 | }; 380 | } 381 | 382 | if (newItemIndex < 0 || !targetItem) { 383 | const collectionIndexPath = newItemPath.slice(0, -3); 384 | const parentCollectionPath = this.getClosestCollectionPath( 385 | ast, 386 | collectionIndexPath 387 | ); 388 | 389 | if ( 390 | !isItemObjectProperty || 391 | !isObjectExpression(ast.getIn(parentCollectionPath.butLast())) 392 | ) { 393 | return; 394 | } 395 | 396 | const collectionIndex = 397 | collectionIndexPath.last() === 'end' 398 | ? parentCollectionPath.size 399 | : selected.get(parentCollectionPath.size) || 0; 400 | const newItemIndex = parseInt(collectionIndex, 10) + (isMoveUp ? 0 : 1); 401 | 402 | return { 403 | ast: ast 404 | .updateIn(collectionPath, collection => 405 | collection.delete(itemIndex) 406 | ) 407 | .updateIn(parentCollectionPath, collection => 408 | collection.insert(newItemIndex, item) 409 | ), 410 | selected: parentCollectionPath 411 | .push(newItemIndex) 412 | .concat(selected.slice(itemPath.size)) 413 | }; 414 | } 415 | 416 | return { 417 | ast: ast 418 | .updateIn(itemPath, () => targetItem) 419 | .updateIn(newItemPath, () => item), 420 | selected: selected.update(collectionPath.size, () => newItemIndex) 421 | }; 422 | }); 423 | 424 | undo() { 425 | this.setState(({ future, history }) => { 426 | const newHistory = history.size > 1 ? history.shift() : history; 427 | this.updateEditorStateContext((newHistory.first(): any)); 428 | return { 429 | future: future.unshift((history.first(): any)), 430 | history: newHistory 431 | }; 432 | }); 433 | } 434 | 435 | redo() { 436 | this.setState(({ future, history }) => { 437 | const newHistory = future.isEmpty() 438 | ? history 439 | : history.unshift((future.first(): any)); 440 | this.updateEditorStateContext((newHistory.first(): any)); 441 | return { 442 | future: future.shift(), 443 | history: newHistory 444 | }; 445 | }); 446 | } 447 | 448 | handleCopy = (event: any) => { 449 | if (isEditable(this.getSelectedNode())) { 450 | return; 451 | } 452 | 453 | let { ast, selected } = this.getCurrentEditorState(); 454 | if (selected.last() === 'end') { 455 | selected = selected.slice(0, -2); 456 | } 457 | event.clipboardData.setData( 458 | 'text/plain', 459 | generate(ast.getIn(selected).toJS()).code 460 | ); 461 | event.preventDefault(); 462 | }; 463 | 464 | handleCut = (event: any) => { 465 | if (isEditable(this.getSelectedNode())) { 466 | return; 467 | } 468 | 469 | this.handleCopy(event); 470 | this.deleteSelected(); 471 | }; 472 | 473 | handlePaste = (event: any) => { 474 | if (isEditable(this.getSelectedNode())) { 475 | return; 476 | } 477 | 478 | const clipboardStr = event.clipboardData.getData('text/plain'); 479 | let data; 480 | try { 481 | data = JSON.parse(clipboardStr); 482 | } catch (e) { 483 | console.error(e); 484 | return; 485 | } 486 | event.preventDefault(); 487 | this.insert(parseObject(data)); 488 | }; 489 | 490 | handleKeyDown = (event: any) => { 491 | const { selected } = this.getCurrentEditorState(); 492 | const selectedNode = this.getSelectedNode(); 493 | 494 | const direction = { 495 | ArrowUp: 'UP', 496 | ArrowDown: 'DOWN', 497 | ArrowLeft: 'LEFT', 498 | ArrowRight: 'RIGHT' 499 | }[event.key]; 500 | const selectedInput = this.contextValue.selectedRef.current; 501 | 502 | if ( 503 | !event.altKey && 504 | direction && 505 | (direction === 'UP' || 506 | direction === 'DOWN' || 507 | !isEditable(selectedNode) || 508 | !selectedInput || 509 | !between( 510 | selectedInput.selectionStart + (direction === 'LEFT' ? -1 : 1), 511 | 0, 512 | selectedInput.value.length 513 | )) 514 | ) { 515 | event.preventDefault(); 516 | return this.changeSelected((ast, selected) => ({ 517 | direction, 518 | selected: navigate(direction, ast, selected) 519 | })); 520 | } 521 | 522 | const enteredNumber = parseInt(event.key, 10); 523 | if (isNullLiteral(selectedNode) && !isNaN(enteredNumber)) { 524 | event.preventDefault(); 525 | return this.replace(numericLiteral(enteredNumber)); 526 | } 527 | 528 | function findActionFor(keyMappings: KeyMapping[], event: any) { 529 | for (const { mappings, name, keys, modifiers, test, type } of keyMappings) { 530 | if ( 531 | (modifiers && 532 | (Array.isArray(modifiers) 533 | ? modifiers 534 | : modifiers(selectedNode, selected) 535 | ).some(modifier => !event[modifier + 'Key'])) || 536 | (keys && keys.every(key => key !== event.key)) || 537 | (test && !test(selectedNode, selected)) 538 | ) { 539 | continue; 540 | } 541 | 542 | if (!mappings) return [type]; 543 | const action = findActionFor(mappings, event); 544 | if (!(type || name) || action) return type ? [type, ...action] : action; 545 | } 546 | } 547 | const [actionName, actionParam] = findActionFor(keyMappings, event) || []; 548 | if (actionName) { 549 | event.preventDefault(); 550 | if (!this.actions[actionName]) { 551 | console.error('Missing action', actionName); 552 | return; 553 | } 554 | this.actions[actionName](actionParam); 555 | } 556 | }; 557 | 558 | handleChange = ({ target: { value } }: any) => { 559 | this.addToHistory((ast, selected) => ({ 560 | ast: ast.setIn( 561 | selected.push(selected.last() === 'id' ? 'name' : 'value'), 562 | value 563 | ) 564 | })); 565 | }; 566 | 567 | handleSelect = (selected: ASTPath) => 568 | this.changeSelected(() => ({ selected })); 569 | 570 | updateEditorStateContext = (newEditorState: EditorState) => { 571 | this.contextValue = { 572 | ...this.contextValue, 573 | ...newEditorState 574 | }; 575 | }; 576 | 577 | render() { 578 | const { showKeymap } = this.state; 579 | const { selected } = this.getCurrentEditorState(); 580 | const isInArray = 581 | (selected.last() === 'end' 582 | ? selected.slice(0, -2) 583 | : selected 584 | ).findLast(key => ['elements', 'properties'].includes(key)) === 585 | 'elements'; 586 | return ( 587 | this.retainFocus(el)} 590 | onKeyDown={this.handleKeyDown} 591 | > 592 | {window.location.host.startsWith('localhost') && ( 593 |
    594 | { 595 | (selected 596 | .toJS() 597 | .map((s, i, arr) => [s, i + 1 < arr.length && ' > ']): any) 598 | } 599 |
    600 | )} 601 | 602 | 605 |
    606 | 607 | 608 | 609 |
    610 | {showKeymap && ( 611 | 615 | )} 616 |
    617 | ); 618 | } 619 | 620 | reset = () => { 621 | this.addToHistory(() => ({ 622 | ast: parse(this.props.defaultValue), 623 | selected: List() 624 | })); 625 | }; 626 | } 627 | -------------------------------------------------------------------------------- /flow-typed/npm/styled-components_v2.x.x.js: -------------------------------------------------------------------------------- 1 | // flow-typed signature: 5ea44337a550f4a886f5be48f94547e4 2 | // flow-typed version: 1be5dad600/styled-components_v2.x.x/flow_>=v0.53.x 3 | 4 | // @flow 5 | 6 | type $npm$styledComponents$Interpolation = ((executionContext: C) => string) | string | number; 7 | type $npm$styledComponents$NameGenerator = (hash: number) => string; 8 | 9 | type $npm$styledComponents$TaggedTemplateLiteral = {| (Array, $npm$styledComponents$Interpolation): R |}; 10 | 11 | // ---- FUNCTIONAL COMPONENT DEFINITIONS ---- 12 | type $npm$styledComponents$ReactComponentFunctional = 13 | & { defaultProps: DefaultProps } 14 | & $npm$styledComponents$ReactComponentFunctionalUndefinedDefaultProps 15 | 16 | type $npm$styledComponents$ReactComponentFunctionalUndefinedDefaultProps = 17 | React$StatelessFunctionalComponent 18 | 19 | // ---- CLASS COMPONENT DEFINITIONS ---- 20 | class $npm$styledComponents$ReactComponent extends React$Component { 21 | static defaultProps: DefaultProps 22 | } 23 | type $npm$styledComponents$ReactComponentClass = Class<$npm$styledComponents$ReactComponent> 24 | type $npm$styledComponents$ReactComponentClassUndefinedDefaultProps = Class> 25 | 26 | // ---- COMPONENT FUNCTIONS INPUT (UNION) & OUTPUT (INTERSECTION) ---- 27 | type $npm$styledComponents$ReactComponentUnion = 28 | $npm$styledComponents$ReactComponentUnionWithDefaultProps 29 | 30 | type $npm$styledComponents$ReactComponentUnionWithDefaultProps = 31 | | $npm$styledComponents$ReactComponentFunctional 32 | | $npm$styledComponents$ReactComponentFunctionalUndefinedDefaultProps 33 | | $npm$styledComponents$ReactComponentClass 34 | | $npm$styledComponents$ReactComponentClassUndefinedDefaultProps 35 | 36 | type $npm$styledComponents$ReactComponentIntersection = 37 | & $npm$styledComponents$ReactComponentFunctional 38 | & $npm$styledComponents$ReactComponentClass; 39 | 40 | // ---- WITHCOMPONENT ---- 41 | type $npm$styledComponents$ReactComponentStyledWithComponent = < 42 | Props, DefaultProps, 43 | Input: 44 | | ComponentList 45 | | $npm$styledComponents$ReactComponentStyled 46 | | $npm$styledComponents$ReactComponentUnionWithDefaultProps 47 | >(Input) => $npm$styledComponents$ReactComponentStyled 48 | 49 | // ---- STATIC PROPERTIES ---- 50 | type $npm$styledComponents$ReactComponentStyledStaticProps = {| 51 | attrs: (AdditionalProps) => $npm$styledComponents$ReactComponentStyledTaggedTemplateLiteral, 52 | extend: $npm$styledComponents$ReactComponentStyledTaggedTemplateLiteral, 53 | |} 54 | 55 | type $npm$styledComponents$ReactComponentStyledStaticPropsWithComponent = {| 56 | withComponent: $npm$styledComponents$ReactComponentStyledWithComponent, 57 | attrs: (AdditionalProps) => $npm$styledComponents$ReactComponentStyledTaggedTemplateLiteralWithComponent, 58 | extend: $npm$styledComponents$ReactComponentStyledTaggedTemplateLiteralWithComponent, 59 | |} 60 | 61 | // ---- STYLED FUNCTION ---- 62 | // Error: styled(CustomComponent).withComponent('a') 63 | // Ok: styled('div').withComponent('a') 64 | type $npm$styledComponents$Call = 65 | & (ComponentListKeys => $npm$styledComponents$ReactComponentStyledTaggedTemplateLiteralWithComponent<{}, ComponentListKeys>) 66 | & (($npm$styledComponents$ReactComponentUnion) => $npm$styledComponents$ReactComponentStyledTaggedTemplateLiteral) 67 | 68 | // ---- STYLED COMPONENT ---- 69 | type $npm$styledComponents$ReactComponentStyled = 70 | & $npm$styledComponents$ReactComponentStyledStaticPropsWithComponent 71 | & $npm$styledComponents$ReactComponentIntersection 72 | 73 | // ---- TAGGED TEMPLATE LITERAL ---- 74 | type $npm$styledComponents$ReactComponentStyledTaggedTemplateLiteral = 75 | & $npm$styledComponents$ReactComponentStyledStaticProps 76 | & $npm$styledComponents$TaggedTemplateLiteral<$npm$styledComponents$ReactComponentStyled> 77 | 78 | type $npm$styledComponents$ReactComponentStyledTaggedTemplateLiteralWithComponent = 79 | & $npm$styledComponents$ReactComponentStyledStaticPropsWithComponent 80 | & $npm$styledComponents$TaggedTemplateLiteral<$npm$styledComponents$ReactComponentStyled> 81 | 82 | // ---- WITHTHEME ---- 83 | type $npm$styledComponents$WithThemeReactComponentClass = < 84 | InputProps: { theme: $npm$styledComponents$Theme }, 85 | InputDefaultProps: {}, 86 | OutputProps: $Diff, 87 | OutputDefaultProps: InputDefaultProps & { theme: $npm$styledComponents$Theme }, 88 | >($npm$styledComponents$ReactComponentClass) => $npm$styledComponents$ReactComponentClass 89 | 90 | type $npm$styledComponents$WithThemeReactComponentClassUndefinedDefaultProps = < 91 | InputProps: { theme: $npm$styledComponents$Theme }, 92 | OutputProps: $Diff, 93 | >($npm$styledComponents$ReactComponentClassUndefinedDefaultProps) => $npm$styledComponents$ReactComponentClass 94 | 95 | type $npm$styledComponents$WithThemeReactComponentFunctional = < 96 | InputProps: { theme: $npm$styledComponents$Theme }, 97 | InputDefaultProps: {}, 98 | OutputProps: $Diff, 99 | OutputDefaultProps: InputDefaultProps & { theme: $npm$styledComponents$Theme }, 100 | >($npm$styledComponents$ReactComponentFunctional) => $npm$styledComponents$ReactComponentFunctional 101 | 102 | type $npm$styledComponents$WithThemeReactComponentFunctionalUndefinedDefaultProps = < 103 | InputProps: { theme: $npm$styledComponents$Theme }, 104 | OutputProps: $Diff 105 | >($npm$styledComponents$ReactComponentFunctionalUndefinedDefaultProps) => $npm$styledComponents$ReactComponentFunctional 106 | 107 | type $npm$styledComponents$WithTheme = 108 | & $npm$styledComponents$WithThemeReactComponentClass 109 | & $npm$styledComponents$WithThemeReactComponentClassUndefinedDefaultProps 110 | & $npm$styledComponents$WithThemeReactComponentFunctional 111 | & $npm$styledComponents$WithThemeReactComponentFunctionalUndefinedDefaultProps 112 | 113 | // ---- MISC ---- 114 | type $npm$styledComponents$Theme = {[key: string]: mixed}; 115 | type $npm$styledComponents$ThemeProviderProps = { 116 | theme: $npm$styledComponents$Theme | ((outerTheme: $npm$styledComponents$Theme) => void) 117 | }; 118 | 119 | class Npm$StyledComponents$ThemeProvider extends React$Component<$npm$styledComponents$ThemeProviderProps> {} 120 | 121 | class Npm$StyledComponents$StyleSheetManager extends React$Component<{ sheet: mixed }> {} 122 | 123 | class Npm$StyledComponents$ServerStyleSheet { 124 | instance: StyleSheet 125 | collectStyles: (children: any) => React$Node 126 | getStyleTags: () => string 127 | getStyleElement: () => React$Node 128 | } 129 | 130 | type $npm$styledComponents$StyledComponentsComponentListKeys = 131 | $Subtype<$Keys<$npm$styledComponents$StyledComponentsComponentList>> 132 | 133 | type $npm$styledComponents$StyledComponentsComponentListValue = 134 | $npm$styledComponents$ReactComponentStyledTaggedTemplateLiteralWithComponent<{}, $npm$styledComponents$StyledComponentsComponentListKeys> 135 | 136 | // ---- COMPONENT LIST ---- 137 | type $npm$styledComponents$StyledComponentsComponentList = {| 138 | a: $npm$styledComponents$StyledComponentsComponentListValue, 139 | abbr: $npm$styledComponents$StyledComponentsComponentListValue, 140 | address: $npm$styledComponents$StyledComponentsComponentListValue, 141 | area: $npm$styledComponents$StyledComponentsComponentListValue, 142 | article: $npm$styledComponents$StyledComponentsComponentListValue, 143 | aside: $npm$styledComponents$StyledComponentsComponentListValue, 144 | audio: $npm$styledComponents$StyledComponentsComponentListValue, 145 | b: $npm$styledComponents$StyledComponentsComponentListValue, 146 | base: $npm$styledComponents$StyledComponentsComponentListValue, 147 | bdi: $npm$styledComponents$StyledComponentsComponentListValue, 148 | bdo: $npm$styledComponents$StyledComponentsComponentListValue, 149 | big: $npm$styledComponents$StyledComponentsComponentListValue, 150 | blockquote: $npm$styledComponents$StyledComponentsComponentListValue, 151 | body: $npm$styledComponents$StyledComponentsComponentListValue, 152 | br: $npm$styledComponents$StyledComponentsComponentListValue, 153 | button: $npm$styledComponents$StyledComponentsComponentListValue, 154 | canvas: $npm$styledComponents$StyledComponentsComponentListValue, 155 | caption: $npm$styledComponents$StyledComponentsComponentListValue, 156 | cite: $npm$styledComponents$StyledComponentsComponentListValue, 157 | code: $npm$styledComponents$StyledComponentsComponentListValue, 158 | col: $npm$styledComponents$StyledComponentsComponentListValue, 159 | colgroup: $npm$styledComponents$StyledComponentsComponentListValue, 160 | data: $npm$styledComponents$StyledComponentsComponentListValue, 161 | datalist: $npm$styledComponents$StyledComponentsComponentListValue, 162 | dd: $npm$styledComponents$StyledComponentsComponentListValue, 163 | del: $npm$styledComponents$StyledComponentsComponentListValue, 164 | details: $npm$styledComponents$StyledComponentsComponentListValue, 165 | dfn: $npm$styledComponents$StyledComponentsComponentListValue, 166 | dialog: $npm$styledComponents$StyledComponentsComponentListValue, 167 | div: $npm$styledComponents$StyledComponentsComponentListValue, 168 | dl: $npm$styledComponents$StyledComponentsComponentListValue, 169 | dt: $npm$styledComponents$StyledComponentsComponentListValue, 170 | em: $npm$styledComponents$StyledComponentsComponentListValue, 171 | embed: $npm$styledComponents$StyledComponentsComponentListValue, 172 | fieldset: $npm$styledComponents$StyledComponentsComponentListValue, 173 | figcaption: $npm$styledComponents$StyledComponentsComponentListValue, 174 | figure: $npm$styledComponents$StyledComponentsComponentListValue, 175 | footer: $npm$styledComponents$StyledComponentsComponentListValue, 176 | form: $npm$styledComponents$StyledComponentsComponentListValue, 177 | h1: $npm$styledComponents$StyledComponentsComponentListValue, 178 | h2: $npm$styledComponents$StyledComponentsComponentListValue, 179 | h3: $npm$styledComponents$StyledComponentsComponentListValue, 180 | h4: $npm$styledComponents$StyledComponentsComponentListValue, 181 | h5: $npm$styledComponents$StyledComponentsComponentListValue, 182 | h6: $npm$styledComponents$StyledComponentsComponentListValue, 183 | head: $npm$styledComponents$StyledComponentsComponentListValue, 184 | header: $npm$styledComponents$StyledComponentsComponentListValue, 185 | hgroup: $npm$styledComponents$StyledComponentsComponentListValue, 186 | hr: $npm$styledComponents$StyledComponentsComponentListValue, 187 | html: $npm$styledComponents$StyledComponentsComponentListValue, 188 | i: $npm$styledComponents$StyledComponentsComponentListValue, 189 | iframe: $npm$styledComponents$StyledComponentsComponentListValue, 190 | img: $npm$styledComponents$StyledComponentsComponentListValue, 191 | input: $npm$styledComponents$StyledComponentsComponentListValue, 192 | ins: $npm$styledComponents$StyledComponentsComponentListValue, 193 | kbd: $npm$styledComponents$StyledComponentsComponentListValue, 194 | keygen: $npm$styledComponents$StyledComponentsComponentListValue, 195 | label: $npm$styledComponents$StyledComponentsComponentListValue, 196 | legend: $npm$styledComponents$StyledComponentsComponentListValue, 197 | li: $npm$styledComponents$StyledComponentsComponentListValue, 198 | link: $npm$styledComponents$StyledComponentsComponentListValue, 199 | main: $npm$styledComponents$StyledComponentsComponentListValue, 200 | map: $npm$styledComponents$StyledComponentsComponentListValue, 201 | mark: $npm$styledComponents$StyledComponentsComponentListValue, 202 | menu: $npm$styledComponents$StyledComponentsComponentListValue, 203 | menuitem: $npm$styledComponents$StyledComponentsComponentListValue, 204 | meta: $npm$styledComponents$StyledComponentsComponentListValue, 205 | meter: $npm$styledComponents$StyledComponentsComponentListValue, 206 | nav: $npm$styledComponents$StyledComponentsComponentListValue, 207 | noscript: $npm$styledComponents$StyledComponentsComponentListValue, 208 | object: $npm$styledComponents$StyledComponentsComponentListValue, 209 | ol: $npm$styledComponents$StyledComponentsComponentListValue, 210 | optgroup: $npm$styledComponents$StyledComponentsComponentListValue, 211 | option: $npm$styledComponents$StyledComponentsComponentListValue, 212 | output: $npm$styledComponents$StyledComponentsComponentListValue, 213 | p: $npm$styledComponents$StyledComponentsComponentListValue, 214 | param: $npm$styledComponents$StyledComponentsComponentListValue, 215 | picture: $npm$styledComponents$StyledComponentsComponentListValue, 216 | pre: $npm$styledComponents$StyledComponentsComponentListValue, 217 | progress: $npm$styledComponents$StyledComponentsComponentListValue, 218 | q: $npm$styledComponents$StyledComponentsComponentListValue, 219 | rp: $npm$styledComponents$StyledComponentsComponentListValue, 220 | rt: $npm$styledComponents$StyledComponentsComponentListValue, 221 | ruby: $npm$styledComponents$StyledComponentsComponentListValue, 222 | s: $npm$styledComponents$StyledComponentsComponentListValue, 223 | samp: $npm$styledComponents$StyledComponentsComponentListValue, 224 | script: $npm$styledComponents$StyledComponentsComponentListValue, 225 | section: $npm$styledComponents$StyledComponentsComponentListValue, 226 | select: $npm$styledComponents$StyledComponentsComponentListValue, 227 | small: $npm$styledComponents$StyledComponentsComponentListValue, 228 | source: $npm$styledComponents$StyledComponentsComponentListValue, 229 | span: $npm$styledComponents$StyledComponentsComponentListValue, 230 | strong: $npm$styledComponents$StyledComponentsComponentListValue, 231 | style: $npm$styledComponents$StyledComponentsComponentListValue, 232 | sub: $npm$styledComponents$StyledComponentsComponentListValue, 233 | summary: $npm$styledComponents$StyledComponentsComponentListValue, 234 | sup: $npm$styledComponents$StyledComponentsComponentListValue, 235 | table: $npm$styledComponents$StyledComponentsComponentListValue, 236 | tbody: $npm$styledComponents$StyledComponentsComponentListValue, 237 | td: $npm$styledComponents$StyledComponentsComponentListValue, 238 | textarea: $npm$styledComponents$StyledComponentsComponentListValue, 239 | tfoot: $npm$styledComponents$StyledComponentsComponentListValue, 240 | th: $npm$styledComponents$StyledComponentsComponentListValue, 241 | thead: $npm$styledComponents$StyledComponentsComponentListValue, 242 | time: $npm$styledComponents$StyledComponentsComponentListValue, 243 | title: $npm$styledComponents$StyledComponentsComponentListValue, 244 | tr: $npm$styledComponents$StyledComponentsComponentListValue, 245 | track: $npm$styledComponents$StyledComponentsComponentListValue, 246 | u: $npm$styledComponents$StyledComponentsComponentListValue, 247 | ul: $npm$styledComponents$StyledComponentsComponentListValue, 248 | var: $npm$styledComponents$StyledComponentsComponentListValue, 249 | video: $npm$styledComponents$StyledComponentsComponentListValue, 250 | wbr: $npm$styledComponents$StyledComponentsComponentListValue, 251 | 252 | // SVG 253 | circle: $npm$styledComponents$StyledComponentsComponentListValue, 254 | clipPath: $npm$styledComponents$StyledComponentsComponentListValue, 255 | defs: $npm$styledComponents$StyledComponentsComponentListValue, 256 | ellipse: $npm$styledComponents$StyledComponentsComponentListValue, 257 | g: $npm$styledComponents$StyledComponentsComponentListValue, 258 | image: $npm$styledComponents$StyledComponentsComponentListValue, 259 | line: $npm$styledComponents$StyledComponentsComponentListValue, 260 | linearGradient: $npm$styledComponents$StyledComponentsComponentListValue, 261 | mask: $npm$styledComponents$StyledComponentsComponentListValue, 262 | path: $npm$styledComponents$StyledComponentsComponentListValue, 263 | pattern: $npm$styledComponents$StyledComponentsComponentListValue, 264 | polygon: $npm$styledComponents$StyledComponentsComponentListValue, 265 | polyline: $npm$styledComponents$StyledComponentsComponentListValue, 266 | radialGradient: $npm$styledComponents$StyledComponentsComponentListValue, 267 | rect: $npm$styledComponents$StyledComponentsComponentListValue, 268 | stop: $npm$styledComponents$StyledComponentsComponentListValue, 269 | svg: $npm$styledComponents$StyledComponentsComponentListValue, 270 | text: $npm$styledComponents$StyledComponentsComponentListValue, 271 | tspan: $npm$styledComponents$StyledComponentsComponentListValue, 272 | |} 273 | 274 | declare module 'styled-components' { 275 | declare type Interpolation = $npm$styledComponents$Interpolation; 276 | declare type NameGenerator = $npm$styledComponents$NameGenerator; 277 | declare type Theme = $npm$styledComponents$Theme; 278 | declare type ThemeProviderProps = $npm$styledComponents$ThemeProviderProps; 279 | declare type TaggedTemplateLiteral = $npm$styledComponents$TaggedTemplateLiteral; 280 | declare type ComponentListKeys = $npm$styledComponents$StyledComponentsComponentListKeys; 281 | 282 | declare type ReactComponentFunctional = $npm$styledComponents$ReactComponentFunctional; 283 | declare type ReactComponentFunctionalUndefinedDefaultProps = $npm$styledComponents$ReactComponentFunctionalUndefinedDefaultProps; 284 | declare type ReactComponentClass = $npm$styledComponents$ReactComponentClass; 285 | declare type ReactComponentClassUndefinedDefaultProps = $npm$styledComponents$ReactComponentClassUndefinedDefaultProps; 286 | declare type ReactComponentUnion = $npm$styledComponents$ReactComponentUnion; 287 | declare type ReactComponentIntersection = $npm$styledComponents$ReactComponentIntersection; 288 | declare type ReactComponentStyledStaticProps = $npm$styledComponents$ReactComponentStyledStaticPropsWithComponent; 289 | declare type ReactComponentStyled = $npm$styledComponents$ReactComponentStyled; 290 | declare type ReactComponentStyledTaggedTemplateLiteral = $npm$styledComponents$ReactComponentStyledTaggedTemplateLiteralWithComponent; 291 | 292 | declare module.exports: { 293 | $call: $npm$styledComponents$Call, 294 | 295 | injectGlobal: TaggedTemplateLiteral, 296 | css: TaggedTemplateLiteral>, 297 | keyframes: TaggedTemplateLiteral, 298 | withTheme: $npm$styledComponents$WithTheme, 299 | ServerStyleSheet: typeof Npm$StyledComponents$ServerStyleSheet, 300 | StyleSheetManager: typeof Npm$StyledComponents$StyleSheetManager, 301 | ThemeProvider: typeof Npm$StyledComponents$ThemeProvider, 302 | 303 | ...$npm$styledComponents$StyledComponentsComponentList, 304 | }; 305 | } 306 | 307 | type $npm$styledComponents$StyledComponentsNativeComponentListKeys = 308 | $Subtype<$Keys<$npm$styledComponents$StyledComponentsNativeComponentList>> 309 | 310 | type $npm$styledComponents$StyledComponentsNativeComponentListValue = 311 | $npm$styledComponents$ReactComponentStyledTaggedTemplateLiteralWithComponent<{}, $npm$styledComponents$StyledComponentsNativeComponentListKeys> 312 | 313 | type $npm$styledComponents$StyledComponentsNativeComponentList = {| 314 | ActivityIndicator: $npm$styledComponents$StyledComponentsNativeComponentListValue, 315 | ActivityIndicatorIOS: $npm$styledComponents$StyledComponentsNativeComponentListValue, 316 | ART: $npm$styledComponents$StyledComponentsNativeComponentListValue, 317 | Button: $npm$styledComponents$StyledComponentsNativeComponentListValue, 318 | DatePickerIOS: $npm$styledComponents$StyledComponentsNativeComponentListValue, 319 | DrawerLayoutAndroid: $npm$styledComponents$StyledComponentsNativeComponentListValue, 320 | FlatList: $npm$styledComponents$StyledComponentsNativeComponentListValue, 321 | Image: $npm$styledComponents$StyledComponentsNativeComponentListValue, 322 | ImageEditor: $npm$styledComponents$StyledComponentsNativeComponentListValue, 323 | ImageStore: $npm$styledComponents$StyledComponentsNativeComponentListValue, 324 | KeyboardAvoidingView: $npm$styledComponents$StyledComponentsNativeComponentListValue, 325 | ListView: $npm$styledComponents$StyledComponentsNativeComponentListValue, 326 | MapView: $npm$styledComponents$StyledComponentsNativeComponentListValue, 327 | Modal: $npm$styledComponents$StyledComponentsNativeComponentListValue, 328 | Navigator: $npm$styledComponents$StyledComponentsNativeComponentListValue, 329 | NavigatorIOS: $npm$styledComponents$StyledComponentsNativeComponentListValue, 330 | Picker: $npm$styledComponents$StyledComponentsNativeComponentListValue, 331 | PickerIOS: $npm$styledComponents$StyledComponentsNativeComponentListValue, 332 | ProgressBarAndroid: $npm$styledComponents$StyledComponentsNativeComponentListValue, 333 | ProgressViewIOS: $npm$styledComponents$StyledComponentsNativeComponentListValue, 334 | RecyclerViewBackedScrollView: $npm$styledComponents$StyledComponentsNativeComponentListValue, 335 | RefreshControl: $npm$styledComponents$StyledComponentsNativeComponentListValue, 336 | ScrollView: $npm$styledComponents$StyledComponentsNativeComponentListValue, 337 | SectionList: $npm$styledComponents$StyledComponentsNativeComponentListValue, 338 | SegmentedControlIOS: $npm$styledComponents$StyledComponentsNativeComponentListValue, 339 | Slider: $npm$styledComponents$StyledComponentsNativeComponentListValue, 340 | SliderIOS: $npm$styledComponents$StyledComponentsNativeComponentListValue, 341 | SnapshotViewIOS: $npm$styledComponents$StyledComponentsNativeComponentListValue, 342 | StatusBar: $npm$styledComponents$StyledComponentsNativeComponentListValue, 343 | SwipeableListView: $npm$styledComponents$StyledComponentsNativeComponentListValue, 344 | Switch: $npm$styledComponents$StyledComponentsNativeComponentListValue, 345 | SwitchAndroid: $npm$styledComponents$StyledComponentsNativeComponentListValue, 346 | SwitchIOS: $npm$styledComponents$StyledComponentsNativeComponentListValue, 347 | TabBarIOS: $npm$styledComponents$StyledComponentsNativeComponentListValue, 348 | Text: $npm$styledComponents$StyledComponentsNativeComponentListValue, 349 | TextInput: $npm$styledComponents$StyledComponentsNativeComponentListValue, 350 | ToastAndroid: $npm$styledComponents$StyledComponentsNativeComponentListValue, 351 | ToolbarAndroid: $npm$styledComponents$StyledComponentsNativeComponentListValue, 352 | Touchable: $npm$styledComponents$StyledComponentsNativeComponentListValue, 353 | TouchableHighlight: $npm$styledComponents$StyledComponentsNativeComponentListValue, 354 | TouchableNativeFeedback: $npm$styledComponents$StyledComponentsNativeComponentListValue, 355 | TouchableOpacity: $npm$styledComponents$StyledComponentsNativeComponentListValue, 356 | TouchableWithoutFeedback: $npm$styledComponents$StyledComponentsNativeComponentListValue, 357 | View: $npm$styledComponents$StyledComponentsNativeComponentListValue, 358 | ViewPagerAndroid: $npm$styledComponents$StyledComponentsNativeComponentListValue, 359 | VirtualizedList: $npm$styledComponents$StyledComponentsNativeComponentListValue, 360 | WebView: $npm$styledComponents$StyledComponentsNativeComponentListValue, 361 | |} 362 | 363 | declare module 'styled-components/native' { 364 | declare type Interpolation = $npm$styledComponents$Interpolation; 365 | declare type NameGenerator = $npm$styledComponents$NameGenerator; 366 | declare type Theme = $npm$styledComponents$Theme; 367 | declare type ThemeProviderProps = $npm$styledComponents$ThemeProviderProps; 368 | declare type TaggedTemplateLiteral = $npm$styledComponents$TaggedTemplateLiteral; 369 | declare type NativeComponentListKeys = $npm$styledComponents$StyledComponentsNativeComponentListKeys; 370 | 371 | declare type ReactComponentFunctional = $npm$styledComponents$ReactComponentFunctional; 372 | declare type ReactComponentFunctionalUndefinedDefaultProps = $npm$styledComponents$ReactComponentFunctionalUndefinedDefaultProps; 373 | declare type ReactComponentClass = $npm$styledComponents$ReactComponentClass; 374 | declare type ReactComponentClassUndefinedDefaultProps = $npm$styledComponents$ReactComponentClassUndefinedDefaultProps; 375 | declare type ReactComponentUnion = $npm$styledComponents$ReactComponentUnion; 376 | declare type ReactComponentIntersection = $npm$styledComponents$ReactComponentIntersection; 377 | declare type ReactComponentStyledStaticProps = $npm$styledComponents$ReactComponentStyledStaticPropsWithComponent; 378 | declare type ReactComponentStyled = $npm$styledComponents$ReactComponentStyled; 379 | declare type ReactComponentStyledTaggedTemplateLiteral = $npm$styledComponents$ReactComponentStyledTaggedTemplateLiteralWithComponent; 380 | 381 | declare module.exports: { 382 | $call: $npm$styledComponents$Call, 383 | 384 | css: TaggedTemplateLiteral>, 385 | keyframes: TaggedTemplateLiteral, 386 | withTheme: $npm$styledComponents$WithTheme, 387 | ThemeProvider: typeof Npm$StyledComponents$ThemeProvider, 388 | 389 | ...$npm$styledComponents$StyledComponentsNativeComponentList, 390 | }; 391 | } 392 | --------------------------------------------------------------------------------