├── .github └── workflows │ ├── gh-pages.yml │ └── release.yml ├── .gitignore ├── README.md ├── example ├── calendar.css ├── index.html ├── main.jsx └── webpack.config.js ├── package-lock.json ├── package.json ├── src └── index.tsx └── tsconfig.json /.github/workflows/gh-pages.yml: -------------------------------------------------------------------------------- 1 | name: Build gh-pages 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | - fix-builds 8 | 9 | jobs: 10 | build: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@master 14 | - name: Setup nodejs 15 | uses: actions/setup-node@v1 16 | with: 17 | node-version: 18.x 18 | - name: Npm install and build 19 | run: | 20 | npm ci 21 | npm run build 22 | env: 23 | CI: true 24 | - name: Deploy action for GitHub Pages 25 | uses: peaceiris/actions-gh-pages@v2.4.0 26 | env: 27 | ACTIONS_DEPLOY_KEY: ${{ secrets.ACTIONS_DEPLOY_KEY }} 28 | PUBLISH_BRANCH: gh-pages 29 | PUBLISH_DIR: ./example 30 | with: 31 | emptyCommits: false 32 | keepFiles: false -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Publish Package to npmjs 2 | 3 | on: 4 | release: 5 | types: [created] 6 | 7 | jobs: 8 | build: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: actions/checkout@v2 12 | - name: Setup nodejs 13 | uses: actions/setup-node@v1 14 | with: 15 | node-version: 18.x 16 | registry-url: 'https://registry.npmjs.org' 17 | - name: Npm install and build 18 | run: | 19 | npm ci 20 | npm run build 21 | env: 22 | CI: true 23 | - run: npm publish 24 | env: 25 | NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} 26 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /lib 2 | /example/dist 3 | /node_modules -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | react-github-contribution-calendar 2 | ==== 3 | 4 | [![npm version](https://badge.fury.io/js/react-github-contribution-calendar.svg)](https://badge.fury.io/js/react-github-contribution-calendar) 5 | 6 | A responsive react component for GitHub-like heatmap calendar 7 | 8 | ## Demo 9 | 10 | [Demo and documents](http://haripo.github.io/react-github-contribution-calendar/) 11 | 12 | ## Usage 13 | 14 | ``` javascript 15 | // main.jsx 16 | import React from 'react'; 17 | import ReactDOM from 'react-dom'; 18 | import Calendar from 'react-github-contribution-calendar'; 19 | 20 | var values = { 21 | '2016-06-23': 1, 22 | '2016-06-26': 2, 23 | '2016-06-27': 3, 24 | '2016-06-28': 4, 25 | '2016-06-29': 4 26 | } 27 | var until = '2016-06-30'; 28 | 29 | var elem = document.getElementById('app'); 30 | ReactDOM.render(, elem); 31 | ``` 32 | 33 | ## Install 34 | 35 | ``` npm i react-github-contribution-calendar --save ``` 36 | 37 | ## Licence 38 | 39 | MIT 40 | 41 | ## Author 42 | 43 | [haripo](https://github.com/haripo) 44 | -------------------------------------------------------------------------------- /example/calendar.css: -------------------------------------------------------------------------------- 1 | @import url(https://fonts.googleapis.com/css?family=Lato:400,900); 2 | 3 | * { 4 | margin: 0; 5 | padding: 0; 6 | } 7 | 8 | body { 9 | font-family: 'Lato', helvetica, sans-serif; 10 | } 11 | 12 | header { 13 | color: #F6F6F6; 14 | background-color: #1E6823; 15 | text-align: center; 16 | 17 | width: 100%; 18 | box-sizing: border-box; 19 | padding: 100px 0; 20 | } 21 | 22 | section { 23 | max-width: 800px; 24 | margin: 0 auto; 25 | } 26 | 27 | footer { 28 | color: #F6F6F6; 29 | background-color: #1E6823; 30 | text-align: center; 31 | 32 | width: 100%; 33 | box-sizing: border-box; 34 | padding: 30px 0; 35 | } 36 | 37 | footer a { 38 | color: #F6F6F6; 39 | } 40 | 41 | footer a:hover { 42 | color: #F6F6F6; 43 | } 44 | 45 | h1 { 46 | font-weight: 900; 47 | } 48 | 49 | h2 { 50 | text-align: center; 51 | color: #1E6823; 52 | text-transform: uppercase; 53 | line-height: 100px; 54 | } 55 | 56 | /* header */ 57 | 58 | .header-sample { 59 | display: inline-block; 60 | margin: 50px 0; 61 | padding: 10px; 62 | background-color: #F6F6F6; 63 | border-radius: 10px 10px 0 10px; 64 | 65 | width: 500px; 66 | min-width: 10px; 67 | max-width: 706px; 68 | } 69 | 70 | @media screen and (max-width: 800px) { 71 | .header-sample { 72 | max-width: 300px; 73 | } 74 | } 75 | 76 | .is-resizable { 77 | resize: horizontal; 78 | overflow: auto; 79 | } 80 | 81 | .draghere { 82 | color: #1E6823; 83 | text-align: right; 84 | font-size: 12px; 85 | } 86 | 87 | /* article */ 88 | .example { 89 | display: flex; 90 | justify-content: center; 91 | } 92 | 93 | .example > .code { 94 | width: 400px; 95 | margin-right: 20px; 96 | } 97 | 98 | .example > .render { 99 | width: 400px; 100 | } 101 | -------------------------------------------------------------------------------- /example/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | react-github-contribution-calendar 11 | 12 | 13 |
14 |

react-github-contribution-calendar

15 |

16 | A React component for GitHub like calendar 17 |

18 |
19 |
20 |
21 | It's responsive! drag here! ↘ 22 |
23 |
24 |
25 |
26 |

getting started

27 |

Install

28 |

29 |

 30 |           
 31 |   $ npm install react-github-contribution-calendar
 32 |           
 33 |         
34 |

35 |

Load styles

36 |

37 |

 38 |           
 39 |   <link rel="stylesheet" href="node_modules/react-github-contribution-calendar/default.css" type="text/css" />
 40 |           
 41 |         
42 |

43 |

Render

44 |

45 |

 46 |           
 47 |   import React from 'react';
 48 |   import ReactDOM from 'react-dom';
 49 |   import Calendar from 'react-github-contribution-calendar';
 50 | 
 51 |   var values = {
 52 |     '2016-06-23': 1,
 53 |     '2016-06-26': 2,
 54 |     '2016-06-27': 3,
 55 |     '2016-06-28': 4,
 56 |     '2016-06-29': 4
 57 |   }
 58 |   var until = '2016-06-30';
 59 | 
 60 |   var elem = document.getElementById('app');
 61 |   ReactDOM.render(<Calendar values={values} until={until} />, elem);
 62 |           
 63 |         
64 |

65 |
66 |
67 |

customizing

68 |

Change panel colors

69 |
70 |
71 |
 72 |             
 73 |   var values = {}
 74 |   var until = '2016-06-30';
 75 |   var values = {
 76 |     '2016-06-23': 1,
 77 |     '2016-06-26': 2,
 78 |     '2016-06-27': 3,
 79 |     '2016-06-28': 4,
 80 |     '2016-06-29': 4
 81 |   };
 82 |   var panelColors = [
 83 |     '#EEEEEE',
 84 |     '#F78A23',
 85 |     '#F87D09',
 86 |     '#AC5808',
 87 |     '#7B3F06'
 88 |   ];
 89 |             
 90 |             
 91 |   <Calendar
 92 |     values={values} until={until}
 93 |     weekNames={weekNames} monthNames={monthNames}/>
 94 |             
 95 |           
96 |
97 |
98 |
99 |
100 |
101 | 102 |

Change label texts

103 |
104 |
105 |
106 |             
107 |   var values = {}
108 |   var until = '2016-12-30';
109 |   var weekNames = ['s', 'm', 't', 'w', 't', 'f', 's'];
110 |   var monthNames = [
111 |     '1', '2', '3', '4', '5', '6',
112 |     '7', '8', '9', '10', '11', '12'
113 |   ];
114 |             
115 |             
116 |   <Calendar
117 |     values={values} until={until}
118 |     weekNames={weekNames} monthNames={monthNames}/>
119 |             
120 |           
121 |
122 |
123 |
124 |
125 |
126 |

Change styles by overwriting SVG attributes

127 |
128 |
129 |
130 |             
131 |   var values = {}
132 |   var until = '2016-12-30';
133 |   var panelAttributes = { 'rx': 6, 'ry': 6 };
134 |   var weekLabelAttributes = {
135 |     'rotate': 20
136 |   };
137 |   var monthLabelAttributes = {
138 |     'style': {
139 |       'text-decoration': 'underline',
140 |       'font-size': 10,
141 |       'alignment-baseline': 'central',
142 |       'fill': '#AAA'
143 |     }
144 |   };
145 |             
146 |             
147 |   <Calendar
148 |     values={values}
149 |     until={until}
150 |     panelAttributes={panelAttributes}
151 |     weekLabelAttributes={weekLabelAttributes}
152 |     monthLabelAttributes={monthLabelAttributes}
153 |   />
154 |             
155 |           
156 |
157 |
158 |
159 |
160 |
161 |
162 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | -------------------------------------------------------------------------------- /example/main.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import Calendar from './../lib/index.js'; 4 | /* import Calendar from 'react-github-contribution-calendar'; */ 5 | 6 | (() => { 7 | var values = {}; 8 | var pad = (v) => (v < 10 ? '0' + v : v) 9 | for (var i = 1; i <= 12; i++) { 10 | for (var j = 1; j <= 31; j++) { 11 | values['2016-' + pad(i) + '-' + pad(j)] = Math.floor(Math.random() * 5); 12 | } 13 | } 14 | var until = '2016-12-31'; 15 | var panelColors = ['#EEEEEE', '#D6E685', '#8CC665', '#44A340', '#1E6823']; 16 | 17 | var elem = document.getElementById('app'); 18 | ReactDOM.render(, 19 | elem); 20 | })(); 21 | 22 | 23 | (() => { 24 | var values = {} 25 | var until = '2016-06-30'; 26 | var values = { 27 | '2016-06-23': 1, 28 | '2016-06-26': 2, 29 | '2016-06-27': 3, 30 | '2016-06-28': 4, 31 | '2016-06-29': 4 32 | }; 33 | var panelColors = ['#EEEEEE', '#F78A23', '#F87D09', '#AC5808', '#7B3F06']; 34 | 35 | var elem = document.getElementById('example1'); 36 | ReactDOM.render(, elem); 38 | })(); 39 | 40 | (() => { 41 | var values = {} 42 | var until = '2016-12-30'; 43 | var weekNames = ['s', 'm', 't', 'w', 't', 'f', 's']; 44 | var monthNames = [ 45 | '1', '2', '3', '4', '5', '6', 46 | '7', '8', '9', '10', '11', '12' 47 | ]; 48 | 49 | var elem = document.getElementById('example2'); 50 | ReactDOM.render(, elem); 52 | })(); 53 | 54 | (() => { 55 | var values = {} 56 | var until = '2016-12-30'; 57 | var panelAttributes = { 'rx': 6, 'ry': 6 }; 58 | var weekLabelAttributes = { 59 | 'rotate': 20 60 | }; 61 | var monthLabelAttributes = { 62 | 'style': { 63 | 'text-decoration': 'underline', 64 | 'font-size': 10, 65 | 'alignment-baseline': 'central', 66 | 'fill': '#AAA' 67 | } 68 | }; 69 | 70 | var elem = document.getElementById('example3'); 71 | ReactDOM.render(, elem); 78 | })(); 79 | -------------------------------------------------------------------------------- /example/webpack.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | mode: 'production', 3 | entry: './main.jsx', 4 | output: { 5 | filename: './bundle.js' 6 | }, 7 | module: { 8 | rules: [ 9 | { 10 | test: /\.jsx$/, 11 | use: [ 12 | { 13 | loader: 'babel-loader', 14 | options: { 15 | presets: ['@babel/preset-env', '@babel/preset-react'] 16 | } 17 | } 18 | ] 19 | } 20 | ] 21 | }, 22 | resolve: { 23 | modules: ['../node_modules'], 24 | extensions: ['.js', '.jsx'] 25 | } 26 | }; 27 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-github-contribution-calendar", 3 | "version": "2.2.0", 4 | "description": "React component for github-like calendar", 5 | "main": "./lib/index.js", 6 | "scripts": { 7 | "build": "npm run lib:build && npm run example:build", 8 | "lib:build": "tsc -p .", 9 | "example:build": "cd example && webpack", 10 | "example:watch": "cd example && webpack -w" 11 | }, 12 | "keywords": [ 13 | "react", 14 | "github", 15 | "calendar" 16 | ], 17 | "author": "haripo", 18 | "license": "MIT", 19 | "files": [ 20 | "lib" 21 | ], 22 | "devDependencies": { 23 | "@babel/core": "^7.20.2", 24 | "@babel/preset-env": "^7.20.2", 25 | "@babel/preset-react": "^7.18.6", 26 | "@types/react": "^18.0.25", 27 | "@types/react-measure": "^2.0.8", 28 | "babel-loader": "^9.1.0", 29 | "react": "^18.2.0", 30 | "react-dom": "^18.2.0", 31 | "typescript": "^4.8.4", 32 | "webpack": "^5.75.0", 33 | "webpack-cli": "^4.10.0" 34 | }, 35 | "dependencies": { 36 | "dayjs": "^1.11.6", 37 | "react-measure": "^2.5.2" 38 | }, 39 | "peerDependencies": { 40 | "react": "^16 || ^17 || ^18" 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/index.tsx: -------------------------------------------------------------------------------- 1 | import dayjs from 'dayjs'; 2 | import React, { ReactElement } from 'react'; 3 | import Measure, { BoundingRect } from 'react-measure'; 4 | 5 | interface Props { 6 | weekNames?: string[] 7 | monthNames?: string[] 8 | panelColors?: string[] 9 | values: { [date: string]: number } 10 | until: string 11 | dateFormat?: string 12 | weekLabelAttributes: any | undefined 13 | monthLabelAttributes: any | undefined 14 | panelAttributes: any | undefined 15 | } 16 | 17 | interface State { 18 | columns: number 19 | maxWidth: number 20 | } 21 | 22 | export default class GitHubCalendar extends React.Component { 23 | monthLabelHeight: number; 24 | weekLabelWidth: number; 25 | panelSize: number; 26 | panelMargin: number; 27 | 28 | constructor(props: any) { 29 | super(props); 30 | 31 | this.monthLabelHeight = 15; 32 | this.weekLabelWidth = 15; 33 | this.panelSize = 11; 34 | this.panelMargin = 2; 35 | 36 | this.state = { 37 | columns: 53, 38 | maxWidth: 53 39 | } 40 | } 41 | 42 | getPanelPosition(row: number, col: number) { 43 | const bounds = this.panelSize + this.panelMargin; 44 | return { 45 | x: this.weekLabelWidth + bounds * row, 46 | y: this.monthLabelHeight + bounds * col 47 | }; 48 | } 49 | 50 | makeCalendarData(history: { [k: string]: number }, lastDay: string, columns: number) { 51 | const d = dayjs(lastDay, { format: this.props.dateFormat }); 52 | const lastWeekend = d.endOf('week'); 53 | const endDate = d.endOf('day'); 54 | 55 | var result: ({ value: number, month: number } | null)[][] = []; 56 | for (var i = 0; i < columns; i++) { 57 | result[i] = []; 58 | for (var j = 0; j < 7; j++) { 59 | var date = lastWeekend.subtract((columns - i - 1) * 7 + (6 - j), 'day'); 60 | if (date <= endDate) { 61 | result[i][j] = { 62 | value: history[date.format(this.props.dateFormat)] || 0, 63 | month: date.month() 64 | }; 65 | } else { 66 | result[i][j] = null; 67 | } 68 | } 69 | } 70 | 71 | return result; 72 | } 73 | 74 | render() { 75 | const columns = this.state.columns; 76 | const values = this.props.values; 77 | const until = this.props.until; 78 | 79 | // TODO: More sophisticated typing 80 | if (this.props.panelColors == undefined || this.props.weekNames == undefined || this.props.monthNames == undefined) { 81 | return; 82 | } 83 | 84 | var contributions = this.makeCalendarData(values, until, columns); 85 | var innerDom: ReactElement[] = []; 86 | 87 | // panels 88 | for (var i = 0; i < columns; i++) { 89 | for (var j = 0; j < 7; j++) { 90 | var contribution = contributions[i][j]; 91 | if (contribution === null) continue; 92 | const pos = this.getPanelPosition(i, j); 93 | const numOfColors = this.props.panelColors.length 94 | const color = 95 | contribution.value >= numOfColors 96 | ? this.props.panelColors[numOfColors - 1] 97 | : this.props.panelColors[contribution.value]; 98 | const dom = ( 99 | 108 | ); 109 | innerDom.push(dom); 110 | } 111 | } 112 | 113 | // week texts 114 | for (var i = 0; i < this.props.weekNames.length; i++) { 115 | const textBasePos = this.getPanelPosition(0, i); 116 | const dom = ( 117 | 129 | { this.props.weekNames[i] } 130 | 131 | ); 132 | innerDom.push(dom); 133 | } 134 | 135 | // month texts 136 | var prevMonth = -1; 137 | for (var i = 0; i < columns; i++) { 138 | const c = contributions[i][0]; 139 | if (c === null) continue; 140 | if (columns > 1 && i == 0 && c.month != contributions[i + 1][0]?.month) { 141 | // skip first month name to avoid text overlap 142 | continue; 143 | } 144 | if (c.month != prevMonth) { 145 | var textBasePos = this.getPanelPosition(i, 0); 146 | innerDom.push( 158 | { this.props.monthNames[c.month] } 159 | 160 | ); 161 | } 162 | prevMonth = c.month; 163 | } 164 | 165 | return ( 166 | this.updateSize(rect.bounds) }> 167 | { ({ measureRef }: any) => ( 168 |
169 | 175 | { innerDom } 176 | 177 |
178 | ) } 179 |
180 | ); 181 | } 182 | 183 | updateSize(size?: BoundingRect) { 184 | if (!size) return; 185 | 186 | const visibleWeeks = Math.floor((size.width - this.weekLabelWidth) / 13); 187 | this.setState({ 188 | columns: Math.min(visibleWeeks, this.state.maxWidth) 189 | }); 190 | } 191 | }; 192 | 193 | // @ts-ignore 194 | GitHubCalendar.defaultProps = { 195 | weekNames: ['', 'M', '', 'W', '', 'F', ''], 196 | monthNames: [ 197 | 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 198 | 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec' 199 | ], 200 | panelColors: ['#EEE', '#DDD', '#AAA', '#444'], 201 | dateFormat: 'YYYY-MM-DD' 202 | }; 203 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | /* Basic Options */ 4 | // "incremental": true, /* Enable incremental compilation */ 5 | "target": "es2015", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019' or 'ESNEXT'. */ 6 | "module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */ 7 | // "lib": [], /* Specify library files to be included in the compilation. */ 8 | // "allowJs": true, /* Allow javascript files to be compiled. */ 9 | // "checkJs": true, /* Report errors in .js files. */ 10 | "jsx": "react", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */ 11 | "declaration": true, /* Generates corresponding '.d.ts' file. */ 12 | // "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */ 13 | // "sourceMap": true, /* Generates corresponding '.map' file. */ 14 | // "outFile": "./", /* Concatenate and emit output to single file. */ 15 | "outDir": "./lib", /* Redirect output structure to the directory. */ 16 | // "rootDir": "./", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */ 17 | // "composite": true, /* Enable project compilation */ 18 | // "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */ 19 | // "removeComments": true, /* Do not emit comments to output. */ 20 | // "noEmit": true, /* Do not emit outputs. */ 21 | // "importHelpers": true, /* Import emit helpers from 'tslib'. */ 22 | // "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */ 23 | // "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */ 24 | 25 | /* Strict Type-Checking Options */ 26 | "strict": true, /* Enable all strict type-checking options. */ 27 | // "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */ 28 | "strictNullChecks": true, /* Enable strict null checks. */ 29 | "strictFunctionTypes": false, /* Enable strict checking of function types. */ 30 | "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */ 31 | "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */ 32 | "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */ 33 | // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */ 34 | 35 | /* Additional Checks */ 36 | "noUnusedLocals": true, /* Report errors on unused locals. */ 37 | "noUnusedParameters": true, /* Report errors on unused parameters. */ 38 | "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ 39 | "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ 40 | 41 | /* Module Resolution Options */ 42 | "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */ 43 | // "baseUrl": "./", /* Base directory to resolve non-absolute module names. */ 44 | // "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */ 45 | // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */ 46 | // "typeRoots": [], /* List of folders to include type definitions from. */ 47 | // "types": [], /* Type declaration files to be included in compilation. */ 48 | // "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */ 49 | "esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */ 50 | // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */ 51 | // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ 52 | 53 | /* Source Map Options */ 54 | // "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */ 55 | // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ 56 | // "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */ 57 | // "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */ 58 | 59 | /* Experimental Options */ 60 | // "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */ 61 | // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */ 62 | } 63 | } 64 | --------------------------------------------------------------------------------