├── .babelrc ├── .eslintignore ├── .eslintrc.js ├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── ci └── test ├── contributing.md ├── dist ├── faber.js ├── faber.js.map ├── faber.min.js └── faber.min.js.map ├── es ├── faber.js ├── grid │ ├── helpers │ │ └── repeatResolver.js │ ├── index.js │ └── track-sizing.js ├── index.js └── utils │ ├── constants.js │ └── index.js ├── package.json ├── src ├── faber.js ├── grid │ ├── helpers │ │ └── repeatResolver.js │ ├── index.js │ └── track-sizing.js ├── index.js └── utils │ ├── constants.js │ └── index.js ├── tests ├── index.spec.js └── utils.js ├── visualiser ├── css-faber-compartor.js └── index.html └── webpack.config.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["@babel/preset-env"] 3 | } -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | webpack.config.js 2 | package.json 3 | *.spec.js -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | "env": { 3 | "browser": true, 4 | "es6": true 5 | }, 6 | "root": true, 7 | "extends": "eslint:recommended", 8 | "globals": { 9 | "Atomics": "readonly", 10 | "SharedArrayBuffer": "readonly" 11 | }, 12 | "parserOptions": { 13 | "ecmaVersion": 2018, 14 | "sourceType": "module" 15 | }, 16 | "rules": { 17 | "semi": ["error", "always"], 18 | "indent": ["error", 2] 19 | } 20 | }; -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | 3 | coverage/ 4 | 5 | package-lock.json 6 | 7 | npm-debug.log* 8 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | stable -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # FaberJS [![Build Status](https://travis-ci.org/fusioncharts/faberjs.svg?branch=develop)](https://travis-ci.org/fusioncharts/faberjs) 2 | 3 | FaberJS is an open-source layouting engine currently supporting CSS Grid like declarations. Unlike HTML element which can leverage the power of CSS for Grid layouts, objects like SVG or custom objects cannot do that. Hence this library tries to solve that problem. 4 | For example, we have an object storing drawing information like dimensions and styles and then for laying itself in a parent container, FaberJS can be used. 5 | 6 | ## Introduction to CSS Grid 7 | CSS Grid Layout is one of the most powerful layout system available in CSS. It is a 2-dimensional system, meaning it can handle both columns and rows, unlike flexbox which is largely a 1-dimensional system. You work with Grid Layout by applying CSS rules both to a parent element (which becomes the Grid Container) and to that element's children (which become Grid Items). 8 | 9 | For more details about CSS grids, refer to this awesome guide: 10 | https://css-tricks.com/snippets/css/complete-guide-grid/ 11 | 12 | ## Usage Guide 13 | 14 | ### Installation 15 | 16 | 17 | ```bash 18 | git clone git@github.com:fusioncharts/faberjs.git 19 | npm install 20 | npm start 21 | ``` 22 | 23 | **Define a Grid with template and items** 24 | ```js 25 | const parent = { 26 | style: { 27 | display: 'grid', 28 | height: 400, 29 | width: 500, 30 | gridTemplateColumns: '100 100', 31 | gridTemplateRows: '100 100', 32 | justifyItems: 'center', 33 | alignItems: 'center', 34 | paddingStart: 10, 35 | paddingEnd: 10, 36 | paddingTop: 10, 37 | paddingBottom: 10, 38 | } 39 | }; 40 | const children = [ 41 | { 42 | style: { 43 | width: 100, 44 | height: 100, 45 | gridColumnStart: 1, 46 | gridColumnEnd: 2, 47 | gridRowStart: 1, 48 | gridRowEnd: 2 49 | } 50 | }, 51 | { 52 | style: { 53 | width: 100, 54 | height: 100, 55 | gridColumnStart: 2, 56 | gridColumnEnd: 3, 57 | gridRowStart: 1, 58 | gridRowEnd: 2 59 | } 60 | }, 61 | { 62 | style: { 63 | width: 100, 64 | height: 100, 65 | gridColumnStart: 1, 66 | gridColumnEnd: 2, 67 | gridRowStart: 2, 68 | gridRowEnd: 3 69 | } 70 | }, 71 | { 72 | style: { 73 | width: 100, 74 | height: 100, 75 | gridColumnStart: 2, 76 | gridColumnEnd: 3, 77 | gridRowStart: 2, 78 | gridRowEnd: 3 79 | } 80 | } 81 | ]; 82 | ``` 83 | **Compute layout** 84 | ```js 85 | const layout = computeLayout({ 86 | ...parent, 87 | children 88 | }) 89 | 90 | /* 91 | { 92 | ... 93 | 94 | "children": [ 95 | { 96 | "style": { 97 | "x": 10, 98 | "y": 10, 99 | "x2": 110, 100 | "y2": 110, 101 | "width": 100, 102 | "height": 100 103 | } 104 | }, 105 | { 106 | "style": { 107 | "x": 110, 108 | "y": 10, 109 | "x2": 210, 110 | "y2": 110, 111 | "width": 100, 112 | "height": 100 113 | } 114 | }, 115 | { 116 | "style": { 117 | "x": 10, 118 | "y": 110, 119 | "x2": 110, 120 | "y2": 210, 121 | "width": 100, 122 | "height": 100 123 | } 124 | }, 125 | { 126 | "style": { 127 | "x": 110, 128 | "y": 110, 129 | "x2": 210, 130 | "y2": 210, 131 | "width": 100, 132 | "height": 100 133 | } 134 | } 135 | ] 136 | } 137 | */ 138 | ``` 139 | 140 | ### Structure of input 141 | ```js 142 | { 143 | style: { 144 | height: required, 145 | width: required, 146 | display: grid, 147 | gridTemplateColumns: 'space speparated track sizes', 148 | gridTemplateRows: 'space speparated track sizes' 149 | }, 150 | children: [] // Array of grid items which will be laid out 151 | } 152 | ``` 153 | 154 | ### Structure of output 155 | Each node will receive a layout object containing the following information. 156 | ```js 157 | { 158 | layout: { 159 | x, 160 | y, 161 | x2, 162 | y2, 163 | width, 164 | height 165 | }, 166 | } 167 | ``` 168 | 169 | ### Template with line names 170 | 171 | ``` 172 | gridTemplateColumns: '[col-1] 100 [col-2] 100' 173 | gridTemplateRows: '[row-1] 100 [row-2] 100' 174 | ``` 175 | 176 | ### Alignments 177 | Standard justification and alignment properties are supported, like justify-items, align-items, justify-self, align-self 178 | 179 | 180 | ## Contribution Guide 181 | 182 | Refer the [CONTRIBUTING.md](contributing.md) before starting any contribution. 183 | 184 | ## License 185 | Copyright 2020 FusionCharts, Inc. 186 | 187 | Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at 188 | 189 | http://www.apache.org/licenses/LICENSE-2.0 190 | 191 | Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. -------------------------------------------------------------------------------- /ci/test: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | #----------------------------------------------------------------------------------- 4 | # THIS SCRIPT IS TO PERFORM THE TESTS ON THE BUILD OF FABER 5 | # TEST INCLUDE MAKING LINTING, MAKING BUILD AND TESTING 6 | #----------------------------------------------------------------------------------- 7 | 8 | GREEN='\033[0;32m' 9 | YELLOW='\033[33m' 10 | NC='\033[0m' 11 | 12 | set -e 13 | 14 | # Run lint on the build 15 | echo -e "${YELLOW}"; 16 | echo " _ _ _ _ "; 17 | echo " | | (_)_ _| |_(_)_ _ __ _ "; 18 | echo " | |__| | ' \ _| | ' \/ _\`|"; 19 | echo " |____|_|_||_\__|_|_||_\__, |"; 20 | echo " |___/ "; 21 | echo -e "${NC}"; 22 | 23 | eslint src/; true 24 | 25 | echo -e "${GREEEN} Lint passed ${NC}"; 26 | 27 | echo -e "${YELLOW}"; 28 | echo " ___ _ _ _ "; 29 | echo " / __|___ _ __ _ __(_) (_)_ _ __ _ "; 30 | echo " | (__/ _ \ ' \| '_ \ | | | ' \/ _\` |"; 31 | echo " \___\___/_|_|_| .__/_|_|_|_||_\__, |"; 32 | echo " |_| |___/ "; 33 | echo -e "${NC}"; 34 | 35 | # make minified es modules 36 | npm run minify 37 | 38 | echo -e "${GREEN} Source files minified successfully ${NC}"; 39 | 40 | # generate build files 41 | npm run build 42 | 43 | echo -e "${GREEN} Build files generated successfully ${NC}"; 44 | 45 | echo -e "${YELLOW}"; 46 | echo "_____ _ _ " 47 | echo "|_ _| | | (_) " 48 | echo " | | ___ ___| |_ _ _ __ __ _ " 49 | echo " | |/ _ \/ __| __| | \'_ \ / _\`|" 50 | echo " | | __/\__ \ |_| | | | | (_| |" 51 | echo " \_/\___||___/\__|_|_| |_|\__, | " 52 | echo " __/ | " 53 | echo " |___/ " 54 | echo -e "${NC}"; 55 | 56 | jest 57 | 58 | echo -e "${GREEN} Test passed ${NC}"; -------------------------------------------------------------------------------- /contributing.md: -------------------------------------------------------------------------------- 1 | # How to Contribute 2 | 3 | When contributing to this repository, please first discuss the change you wish to make via issue, 4 | email, or any other method with the owners of this repository before making a change. 5 | 6 | Please note we have a code of conduct, please follow it in all your interactions with the project. 7 | 8 | ## Code of Conduct 9 | 10 | ### Our Pledge 11 | 12 | In the interest of fostering an open and welcoming environment, we as 13 | contributors and maintainers pledge to making participation in our project and 14 | our community a harassment-free experience for everyone, regardless of age, body 15 | size, disability, ethnicity, gender identity and expression, level of experience, 16 | nationality, personal appearance, race, religion, or sexual identity and 17 | orientation. 18 | 19 | ### Our Standards 20 | 21 | Examples of behavior that contributes to creating a positive environment 22 | include: 23 | 24 | * Using welcoming and inclusive language 25 | * Being respectful of differing viewpoints and experiences 26 | * Gracefully accepting constructive criticism 27 | * Focusing on what is best for the community 28 | * Showing empathy towards other community members 29 | 30 | Examples of unacceptable behavior by participants include: 31 | 32 | * The use of sexualized language or imagery and unwelcome sexual attention or 33 | advances 34 | * Trolling, insulting/derogatory comments, and personal or political attacks 35 | * Public or private harassment 36 | * Publishing others' private information, such as a physical or electronic 37 | address, without explicit permission 38 | * Other conduct which could reasonably be considered inappropriate in a 39 | professional setting 40 | 41 | ### Our Responsibilities 42 | 43 | Project maintainers are responsible for clarifying the standards of acceptable 44 | behavior and are expected to take appropriate and fair corrective action in 45 | response to any instances of unacceptable behavior. 46 | 47 | Project maintainers have the right and responsibility to remove, edit, or 48 | reject comments, commits, code, wiki edits, issues, and other contributions 49 | that are not aligned to this Code of Conduct, or to ban temporarily or 50 | permanently any contributor for other behaviors that they deem inappropriate, 51 | threatening, offensive, or harmful. 52 | 53 | ### Scope 54 | 55 | This Code of Conduct applies both within project spaces and in public spaces 56 | when an individual is representing the project or its community. Examples of 57 | representing a project or community include using an official project e-mail 58 | address, posting via an official social media account, or acting as an appointed 59 | representative at an online or offline event. Representation of a project may be 60 | further defined and clarified by project maintainers. 61 | 62 | ### Enforcement 63 | 64 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 65 | reported by contacting the project team at FusionCharts. All 66 | complaints will be reviewed and investigated and will result in a response that 67 | is deemed necessary and appropriate to the circumstances. The project team is 68 | obligated to maintain confidentiality with regard to the reporter of an incident. 69 | Further details of specific enforcement policies may be posted separately. 70 | 71 | Project maintainers who do not follow or enforce the Code of Conduct in good 72 | faith may face temporary or permanent repercussions as determined by other 73 | members of the project's leadership. 74 | 75 | ### Attribution 76 | 77 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 78 | available at [http://contributor-covenant.org/version/1/4][version] 79 | 80 | [homepage]: http://contributor-covenant.org 81 | [version]: http://contributor-covenant.org/version/1/4/ 82 | 83 | ## Bugs 84 | 85 | ### Where to Find Known Issues 86 | We use the github issues for our public bugs. The status of a bug is clearly mentioned in the comments. 87 | Before filing a new bug, please look into the existing issues and make sure that your problem does not 88 | already exist. 89 | 90 | ### Reporting New Issues 91 | While reporting a new issue, please provide a reduced testcase in the form of code-snippets and/or 92 | jsfiddle links 93 | 94 | ## Proposing a change 95 | If you intend to change the public API, or make any non-trivial changes to the implementation, we recommend filing an issue. This lets us reach an agreement on your proposal before you put significant effort into it. 96 | 97 | If you’re only fixing a bug, it’s fine to submit a pull request right away but we still recommend to file an issue detailing what you’re fixing. This is helpful in case we don’t accept that specific fix but want to keep track of the issue. 98 | 99 | ## Your First Pull Request 100 | 101 | Working on your first Pull Request? You can learn how from this free video series: 102 | 103 | > [How to contribute to Open Source Project on Github](https://egghead.io/courses/how-to-contribute-to-an-open-source-project-on-github) 104 | 105 | The [good first issues](https://github.com/fusioncharts/faberjs/issues?utf8=%E2%9C%93&q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22) are a great place to start and get an idea of how things are working. 106 | 107 | If you decide to fix an issue, please be sure to check the comment thread in case somebody is already working on a fix. If nobody is working on it at the moment, please leave a comment stating that you intend to work on it so other people don’t accidentally duplicate your effort. 108 | 109 | If somebody claims an issue but doesn’t follow up for more than two weeks, it’s fine to take it over but you should still leave a comment. 110 | 111 | ## Sending a Pull Request 112 | Project maintainers are constantly monitoring for any new pull requests. Maintainers will review the PRs and either merge it, request for change or close it with an explanation. 113 | 114 | Please make sure the following steps are done before submitting a pull request: 115 | 116 | 1. Fork [this repository](https://github.com/fusioncharts/faberjs) and create a branch from `develop`. 117 | 2. Run `npm install` at the repositoty root. 118 | 3. For a bug fix and/or new feature, adequate tests should be added. 119 | 4. Run `npm run test` to run lint, create minified es modules and create both development and production builds. 120 | 5. Commit and push the changes to your branch. 121 | 6. Send a pull request to the `develop` branch of original repository. 122 | 123 | ## License 124 | By contributing to FaberJS, you agree that your contributions will be licensed under its MIT license. -------------------------------------------------------------------------------- /dist/faber.min.js: -------------------------------------------------------------------------------- 1 | !function(t,e){"object"==typeof exports&&"object"==typeof module?module.exports=e():"function"==typeof define&&define.amd?define([],e):"object"==typeof exports?exports.faber=e():t.faber=e()}(window,(function(){return function(t){var e={};function r(n){if(e[n])return e[n].exports;var i=e[n]={i:n,l:!1,exports:{}};return t[n].call(i.exports,i,i.exports,r),i.l=!0,i.exports}return r.m=t,r.c=e,r.d=function(t,e,n){r.o(t,e)||Object.defineProperty(t,e,{enumerable:!0,get:n})},r.r=function(t){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(t,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(t,"__esModule",{value:!0})},r.t=function(t,e){if(1&e&&(t=r(t)),8&e)return t;if(4&e&&"object"==typeof t&&t&&t.__esModule)return t;var n=Object.create(null);if(r.r(n),Object.defineProperty(n,"default",{enumerable:!0,value:t}),2&e&&"string"!=typeof t)for(var i in t)r.d(n,i,function(e){return t[e]}.bind(null,i));return n},r.n=function(t){var e=t&&t.__esModule?function(){return t.default}:function(){return t};return r.d(e,"a",e),e},r.o=function(t,e){return Object.prototype.hasOwnProperty.call(t,e)},r.p="",r(r.s=0)}([function(t,e,r){"use strict";r.r(e);var n=["string","number","function","boolean","undefined"];function i(t){return(i="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t})(t)}var o=function(t){return t.style&&t.style.display},a=function(){var t,e,r;for(e=0,r=arguments.length;e0&&void 0!==arguments[0]?arguments[0]:[],r=arguments.length>1&&void 0!==arguments[1]?arguments[1]:[],n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:600;return function(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")}(this,t),this.clear(),this.set("tracks",e),this.set("items",r),this.set("containerSize",n),this}var e,r,n;return e=t,(r=[{key:"set",value:function(t,e){switch(this.props[t]=e,t){case"tracks":this._initTrackSize();break;case"items":this._initItems();break;case"containerSize":this.props[t]=isNaN(+e)?0:+e}return this}},{key:"get",value:function(t){return this.props[t]}},{key:"_initTrackSize",value:function(t){var e,r,n,i,o,a,s,c=t||this.props.tracks||[],u=this._config,d=[{}];for(u.frTracks=[],u.intrinsicTracks=[],e=1,r=c.length;e0||n[0].indexOf("fr")>0?(s=1/0,u.frTracks.push(e),i="minmax"):"auto"===n[1]||"auto"===n[0]?(s=1/0,u.intrinsicTracks.push(e),i="minmax"):isNaN(+n[0])||isNaN(+n[1])||(s=Math.max(+n[0],+n[1]),a=Math.min(+n[0],+n[1]),u.intrinsicTracks.push(e),i="minmax")):isNaN(+n)?n.indexOf("fr")>0?(a=0,s=1/0,u.frTracks.push(e),i="flex",o=f(n)):(a=0,s=1/0,i="intrinsic",u.intrinsicTracks.push(e)):(a=s=+n,i="fixed"),d.push(l({},c[e],{type:i,multiplier:o,baseSize:a,growthLimit:s}));return u.sanitizedTracks=d}},{key:"_initItems",value:function(t){var e,r,n,i,o=t||this.props.items||[],a=this._config,s=[],c=0;for(n=0,i=o.length;n1){e=n;break}return this._config.nonSpanningItemStartIndex=e,this._config.sanitizedItems=s}},{key:"_getParentSize",value:function(t){var e=this._config.sanitizedTracks,r=0;return e.filter((function(e){return e.start>=t.start&&e.end<=t.end})).forEach((function(t){return r+=t.baseSize})),r||0}},{key:"resolveTracks",value:function(){return this._placeNonSpanningItems()._placeSpanningItems()._distributeFreeSpace(),this._config.sanitizedTracks}},{key:"_placeNonSpanningItems",value:function(){var t,e,r=this._config,n=r.sanitizedItems,i=r.sanitizedTracks,o=r.nonSpanningItemStartIndex;return n.slice(0,o).forEach((function(r){e=r.start,"fixed"!==(t=i[e]).type&&(t.baseSize=Math.max(t.baseSize,r.size),t.growthLimit=Math.max(t.growthLimit,t.baseSize))})),this}},{key:"_placeSpanningItems",value:function(){var t,e,r,n,i,o,a,s=this._config,l=s.sanitizedItems,c=s.sanitizedTracks,u=s.nonSpanningItemStartIndex,f=s.frTracks,d=l.slice(u),p=[0];if(!d.length)return this;for(o=1,a=c.length;o=0&&(i=!0),"fixed"!==c[o].type&&n++;if(n&&!i)for(r=e/n,o=a.start;ot.multiplier*i})).forEach((function(t){return r+=t.baseSize})),t(o,r,n);o.forEach((function(t){return t.baseSize=t.multiplier*i}))}}(e,o,i)):r.length&&(r.forEach((function(t,e){r[e]=n[t]})),function(t,e,r){var n,i,o,a,s,l,c,u=0;if(t.length){for(n=r-e,(s=t.filter((function(t){return"minmax"===t.type&&t.growthLimit!==1/0}))).sort((function(t,e){return t.growthLimit-t.baseSize-(e.growthLimit-e.baseSize)})),a=s.length;u0&&void 0!==arguments[0]?arguments[0]:{},r=e.style,n=r.gridTemplateRows,i=r.gridTemplateColumns,o=this._config,a=x(e.children),s=a.maxColumn,l=a.maxRow;return this.set("maxTracks",l),t=this._fetchTrackInformation(n),o.mapping.row={nameToLineMap:t.nameToLineMap,lineToNameMap:t.lineToNameMap},o.rowTracks=t.tracks,this.set("maxTracks",s),t=this._fetchTrackInformation(i),o.mapping.col={nameToLineMap:t.nameToLineMap,lineToNameMap:t.lineToNameMap},o.colTracks=t.tracks,this}},{key:"_fetchTrackInformation",value:function(){var t,e,r,n,i,o,a=arguments.length>0&&void 0!==arguments[0]?arguments[0]:"none",s=a.match(v),l=[{}],c={},u={};for(r=s.filter((function(t){return!t||"string"!=typeof t||!t.length||(e=t.length,"["===t[0]&&"]"===t[e-1])})),n=s.filter((function(t){return!(!t||(e=(t+"").toLowerCase().replace(/px|fr/,""),!(w.indexOf(e)>=0||b.test(e))&&isNaN(e)))})).map((function(t){return z(t)})),e=n.length,"none"===a&&(e=this.getProps("maxTracks")),t=0;td);n++)a+=f,l+=f+" ";for(s=n,r=Math.ceil(i/s);r--;)c+=u+" ";return{gridTemplateColumns:l.trim(),gridTemplateRows:c.trim()}}(t,{itemWidth:o,width:i}),t.style.gridTemplateColumns=e.gridTemplateColumns,t.style.gridTemplateRows=e.gridTemplateRows,d.gridLayoutEngine(t)})),this}},{key:"_assignCoordinatesToCells",value:function(t){var e,r,n,i,o,s,l,c,u,f=t||this.props.domTree,d=this._config,p=d.sanitizedItems,h=d.rowTracks,g=d.colTracks,m=f.style,y=m.justifyItems,w=m.alignItems,b=m.paddingStart,v=m.paddingTop,S=[b],T=[v];for(n=1,r=h.length;n0&&void 0!==arguments[0]?arguments[0]:"",e=arguments.length>1?arguments[1]:void 0,r=(t.match(v)||[]).filter((function(t){return t&&!!t.trim()})),n="",i=1;return r.length&&!/repeat\(/.test(t)?r.forEach((function(t){w.indexOf(t)>-1||/[0-9]fr/.test(t)||b.test(t)||!isNaN(t)?(n+=e[i].calculatedStyle.baseSize+" ",i++):n+=t+" "})):e.forEach((function(t){isNaN(t.calculatedStyle.baseSize)||(n+=t.calculatedStyle.baseSize+" ")})),n.trim()},j=function(t,e){var r,n,i,a,s,l,c,u,f,d,p=t.style,h=e.getConfig("rowTracks"),g=e.getConfig("colTracks"),m=e.getConfig("mapping"),y=p.gridTemplateRows,w=p.gridTemplateColumns;for(t.style.gridTemplateRows=_(y,h),t.style.gridTemplateColumns=_(w,g),n=0,a=(t.children||[]).length;n1&&void 0!==arguments[1]?arguments[1]:1,l=t.style;if(t&&t.style){for(t.userGivenStyles||(t.style.width=isNaN(t.style.width)?"auto":t.style.width,t.style.height=isNaN(t.style.height)?"auto":t.style.height,l.paddingStart=a(l.paddingStart,l.padding,0),l.paddingEnd=a(l.paddingEnd,l.padding,0),l.paddingTop=a(l.paddingTop,l.padding,0),l.paddingBottom=a(l.paddingBottom,l.padding,0),t.userGivenStyles={gridTemplateColumns:t.style.gridTemplateColumns,gridTemplateRows:t.style.gridTemplateRows,width:t.style.width,height:t.style.height}),t.unResolvedChildren=[],e=0,r=t.children&&t.children.length;e-1||null===e)return e;if(Array.isArray(e)){var r,o,a=[];for(r=0,o=e.length;r0&&void 0!==arguments[0]?arguments[0]:{},i=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};for(n.layout=i.layout,e=0,r=(n.children||[]).length;e {\n return domTree.style && domTree.style.display;\n },\n cloneObject = (arg) => {\n if ((ATOMIC_DATA_TYPE.indexOf(typeof arg) > -1) || arg === null) {\n return arg;\n }\n\n if (Array.isArray(arg)) {\n let i,\n len,\n arr = [];\n\n for (i = 0, len = arg.length; i < len; i++) {\n arr.push(cloneObject(arg[i]));\n }\n\n return arr;\n } else if (typeof arg === 'object') {\n let cloneObj = {},\n key;\n\n for (key in arg) {\n cloneObj[key] = cloneObject(arg[key]);\n }\n\n return cloneObj;\n }\n },\n attachLayoutInformation = (baseTree = {}, calculatedTree = {}) => {\n let i,\n len;\n\n baseTree.layout = calculatedTree.layout;\n\n for (i = 0, len = (baseTree.children || []).length; i < len; i++) {\n attachLayoutInformation(baseTree.children[i], calculatedTree.children[i]);\n }\n },\n pluckNumber = function () {\n var arg,\n i,\n l;\n\n for (i = 0, l = arguments.length; i < l; i += 1) {\n arg = arguments[i];\n if (!arg && arg !== false && arg !== 0) {\n continue;\n } else if (isNaN(arg = Number(arg))) {\n continue;\n }\n return arg;\n }\n return UNDEF;\n };\n\nexport {\n cloneObject,\n attachLayoutInformation,\n getDisplayProperty,\n pluckNumber\n};\n","const getMultiplierOfFr = size => +size.replace(/fr/, ''),\n /**\n * Helper function to distribute extra space among all the flexible tracks.\n */\n _frSpaceDistributorHelper = (tracks, totalSpaceUsed, containerSize) => {\n let freeSpace,\n spacePerFrTrack,\n eligibleTracks,\n totalFrTrackRatio = 0;\n\n if (!tracks.length) {\n return;\n }\n\n tracks.forEach(track => (totalFrTrackRatio += track.multiplier));\n\n freeSpace = containerSize - totalSpaceUsed;\n spacePerFrTrack = freeSpace / totalFrTrackRatio;\n\n eligibleTracks = tracks.filter(track => track.baseSize <= track.multiplier * spacePerFrTrack);\n\n if (eligibleTracks.length < tracks.length) {\n tracks.filter(track => track.baseSize > track.multiplier * spacePerFrTrack).forEach(track => (totalSpaceUsed += track.baseSize));\n return _frSpaceDistributorHelper(eligibleTracks, totalSpaceUsed, containerSize);\n } else {\n eligibleTracks.forEach(track => (track.baseSize = track.multiplier * spacePerFrTrack));\n }\n },\n /**\n * Helper function to distribute extra space among all the intrinsic tracks.\n */\n _intrinsicSpaceDistributorHelper = (tracks, totalSpaceUsed, containerSize) => {\n let freeSpace,\n spacePerIntrinsicTrack,\n i,\n len,\n frozenTrack = 0,\n minMaxTracks,\n growthLimit,\n baseSize;\n\n if (!tracks.length) {\n return;\n }\n minMaxTracks = tracks.filter(track => track.type === 'minmax' && track.growthLimit !== Infinity);\n freeSpace = containerSize - totalSpaceUsed;\n\n minMaxTracks.sort(function (a, b) {\n let gap1 = a.growthLimit - a.baseSize,\n gap2 = b.growthLimit - b.baseSize;\n\n return gap1 - gap2;\n });\n\n len = minMaxTracks.length;\n while (frozenTrack < len && freeSpace) {\n spacePerIntrinsicTrack = freeSpace / ((minMaxTracks.length - frozenTrack) || 1);\n /**\n * @todo: remove the frozen tracks.\n */\n for (i = 0, len = minMaxTracks.length; i < len; i++) {\n growthLimit = minMaxTracks[i].growthLimit;\n\n baseSize = Math.min(spacePerIntrinsicTrack + minMaxTracks[i].baseSize, growthLimit);\n freeSpace -= (baseSize - minMaxTracks[i].baseSize);\n minMaxTracks[i].baseSize = baseSize;\n\n if (growthLimit === baseSize && !minMaxTracks[i].frozen) {\n minMaxTracks[i].frozen = true;\n frozenTrack++;\n }\n }\n }\n\n tracks = tracks.filter(track => (track.type === 'minmax' && track.growthLimit === Infinity) || track.type !== 'minmax');\n spacePerIntrinsicTrack = freeSpace / tracks.length;\n\n tracks.forEach(track => (track.baseSize += spacePerIntrinsicTrack));\n };\n\n/**\n * TrackResolver implements the standard track solving algorithm of CSS grid.\n * Refer https://www.w3.org/TR/css-grid-1/#algo-track-sizing\n *\n * @class TrackResolver\n */\nclass TrackResolver {\n constructor (tracks = [], items = [], containerSize = 600) {\n this.clear();\n\n this.set('tracks', tracks);\n this.set('items', items);\n this.set('containerSize', containerSize);\n return this;\n }\n\n /**\n * setter method to set props\n *\n * @param {string} key\n * key represents the name by which the value is to be stored in props object.\n * @param {any} info\n * info is the information(can be anything) that has to be stored against the key.\n * @returns {TrackResolver}\n * Reference of the class instance.\n * @memberof TrackResolver\n */\n set (key, info) {\n this.props[key] = info;\n\n switch (key) {\n case 'tracks':\n this._initTrackSize(); break;\n case 'items':\n this._initItems(); break;\n case 'containerSize': \n this.props[key] = isNaN(+info) ? 0 : +info;\n }\n return this;\n }\n\n /**\n * Getter method to fetch the props\n *\n * @param {string} key\n * key of the value which has to be fetched.\n * @returns {any}\n * alue corresponding to the key in props object\n * @memberof TrackResolver\n */\n get (key) {\n return this.props[key];\n }\n\n /**\n * Initializes the tracks. Both rows and columns in grid are tracks in TrackResolver.\n * Each track is assigned a baseSize and growthLimit. BaseSize is the minimum size that a track can take,\n * while growthLimit is the max size.\n *\n * Terminology:\n * FrTracks: Tracks which have a size definition in terms of fr(free space)\n * Intrinsic Tracks: Tracks which have a size definition of auto.\n *\n * @param {Array} _tracks\n * Array containing information about the tracks.\n * @returns {Array}\n * Array of sanitized tracks. A sanitized track consists of the following information\n * {\n * type: minmax | fixed | flex | intrinsic\n * minmax: track has size definition in minmax format\n * fixed: a fixed numeric value is provided as size definition\n * flex: size definition is provided in terms of fr\n * intrinsic: auto size definition\n * multiplier: Prefix of fr(2 in case of 2fr). default 1.\n * baseSize: lower size limit of track.\n * growthLimit: upper size limit of track.\n * }\n * @memberof TrackResolver\n */\n _initTrackSize (_tracks) {\n let tracks = _tracks || this.props.tracks || [],\n config = this._config,\n trackAr = [{}],\n i,\n len,\n size,\n type,\n multiplier,\n baseSize,\n growthLimit;\n\n config.frTracks = [];\n config.intrinsicTracks = [];\n\n for (i = 1, len = tracks.length; i < len; i++) {\n size = tracks[i].size;\n\n multiplier = 1;\n if (Array.isArray(size)) {\n baseSize = +size[0] || 0;\n\n if (size[1].indexOf('fr') > 0 || size[0].indexOf('fr') > 0) {\n growthLimit = Infinity;\n config.frTracks.push(i);\n type = 'minmax';\n } else if (size[1] === 'auto' || size[0] === 'auto') {\n growthLimit = Infinity;\n config.intrinsicTracks.push(i);\n type = 'minmax';\n } else if (!isNaN(+size[0]) && !isNaN(+size[1])) {\n growthLimit = Math.max(+size[0], +size[1]);\n baseSize = Math.min(+size[0], +size[1]);\n config.intrinsicTracks.push(i);\n type = 'minmax';\n }\n } else if (!isNaN(+size)) {\n baseSize = growthLimit = +size;\n type = 'fixed';\n } else if (size.indexOf('fr') > 0) {\n baseSize = 0;\n growthLimit = Infinity;\n config.frTracks.push(i);\n type = 'flex';\n multiplier = getMultiplierOfFr(size);\n } else {\n baseSize = 0;\n growthLimit = Infinity;\n type = 'intrinsic';\n config.intrinsicTracks.push(i);\n }\n\n trackAr.push({\n ...tracks[i],\n type,\n multiplier,\n baseSize,\n growthLimit\n });\n }\n\n return (config.sanitizedTracks = trackAr);\n }\n\n /**\n * The size of grid items are sanitized in this method. In case the items do not have a valid size, they\n * take up size of the tracks\n *\n * @param {Array} _items\n * Array of grid items\n * @returns {Array}\n * Array of items where each item has valid size\n * @memberof TrackResolver\n */\n _initItems (_items) {\n let items = _items || this.props.items || [],\n config = this._config,\n sanitizedItems = [],\n nonSpanningItemStartIndex,\n item,\n validItems = 0,\n i,\n len;\n\n for (i = 0, len = items.length; i < len; i++) {\n if (isNaN(items[i].start) || isNaN(items[i].end)) {\n config.autoFlow.push(items[i]);\n continue;\n }\n sanitizedItems.push({...items[i]});\n\n item = sanitizedItems[validItems];\n validItems++;\n\n item.size = isNaN(item.size) ? this._getParentSize(item) : +item.size;\n }\n\n sanitizedItems.sort(function (a, b) {\n let gap1 = a.end - a.start,\n gap2 = b.end - b.start;\n\n if (gap1 === gap2) {\n return a.start - b.start;\n } else { return gap1 - gap2; }\n });\n\n for (i = 0, nonSpanningItemStartIndex = len = sanitizedItems.length; i < len; i++) {\n if (sanitizedItems[i].end - sanitizedItems[i].start > 1) {\n nonSpanningItemStartIndex = i;\n break;\n }\n }\n\n this._config.nonSpanningItemStartIndex = nonSpanningItemStartIndex;\n\n return (this._config.sanitizedItems = sanitizedItems);\n }\n\n /**\n * If any grid item do not have a valid size, then it takes up the size of the track.\n *\n * @param {Object} item\n * The item which do not have a proper size and will take up the size of the track.\n * @returns {number}\n * size of the track(s) which will be assigned to the grid item.\n * @memberof TrackResolver\n */\n _getParentSize (item) {\n let { sanitizedTracks } = this._config,\n parentTracks,\n widthOfParentTracks = 0;\n\n parentTracks = sanitizedTracks.filter(track => (track.start >= item.start && track.end <= item.end));\n\n parentTracks.forEach(track => (widthOfParentTracks += track.baseSize));\n\n return (widthOfParentTracks || 0);\n }\n\n /**\n * resolveTracks method is called to resolve the tracks.\n *\n * Terminology:\n * Non-spanning items - items which is contained in a single track.\n * Spanning items - items which is spread across multiple tracks.\n *\n * 1. At first all the non-spanning items are placed. The tracks containing non-spanning gets a minimum size.\n * 2. Then the spanning items are placed. If total size of all the tracks over which the spanning items are spread is less than\n * the size of the spanning items, then the extra space required by the item is accomodated equally by the non-fixed tracks.\n * 3. Afer all the items are placed, if any free space remains, they get distributed among the non-fixed tracks.\n *\n * @returns {Array}\n * Array of objects where each object is a track with resolved size.\n * @memberof TrackResolver\n */\n resolveTracks () {\n this._placeNonSpanningItems()\n ._placeSpanningItems()\n ._distributeFreeSpace();\n\n return this._config.sanitizedTracks;\n }\n\n /**\n * Placing a non-spanning item. After placing the item if the containing track has a non-fixed size, it is increased to\n * accomodate the item.\n *\n * @returns {TrackResolver}\n * Reference of the class instance.\n * @memberof TrackResolver\n */\n _placeNonSpanningItems () {\n let { sanitizedItems, sanitizedTracks, nonSpanningItemStartIndex } = this._config,\n nonSpanningItems = sanitizedItems.slice(0, nonSpanningItemStartIndex),\n track,\n trackIndex;\n\n nonSpanningItems.forEach(item => {\n trackIndex = item.start;\n track = sanitizedTracks[trackIndex];\n\n if (track.type !== 'fixed') {\n track.baseSize = Math.max(track.baseSize, item.size);\n track.growthLimit = Math.max(track.growthLimit, track.baseSize);\n }\n });\n\n return this;\n }\n\n /**\n * Place the non-spanning items. If the total size of all tracks on which the item is spread is less than\n * the size of the item, then the extra size required is accomodated by equally increasing the size of\n * all the non-fixed containing tracks.\n *\n * @returns {TrackResolver}\n * Reference of the class instance.\n * @memberof TrackResolver\n */\n _placeSpanningItems () {\n let { sanitizedItems, sanitizedTracks, nonSpanningItemStartIndex, frTracks } = this._config,\n spanningItems = sanitizedItems.slice(nonSpanningItemStartIndex),\n trackSizedp = [0],\n sizeConsumed,\n sizeLeft,\n sizePerTrack,\n availableTracks,\n hasFrTrack,\n i,\n len;\n\n if (!spanningItems.length) return this;\n\n for (i = 1, len = sanitizedTracks.length; i < len; i++) {\n trackSizedp[i] = trackSizedp[i - 1] + (sanitizedTracks[i].baseSize || 0);\n }\n\n spanningItems.forEach(item => {\n sizeConsumed = trackSizedp[item.end - 1] - trackSizedp[item.start - 1];\n sizeLeft = Math.max(0, item.size - sizeConsumed);\n\n if (!sizeLeft) return;\n\n for (i = item.start, hasFrTrack = false, availableTracks = 0; i < item.end; i++) {\n if (frTracks.indexOf(i) >= 0) {\n hasFrTrack = true;\n }\n if (sanitizedTracks[i].type !== 'fixed') {\n availableTracks++;\n }\n }\n\n if (!availableTracks || hasFrTrack) return;\n\n sizePerTrack = sizeLeft / availableTracks;\n for (i = item.start; i < item.end; i++) {\n if (sanitizedTracks[i].type !== 'fixed') {\n sanitizedTracks[i].baseSize += sizePerTrack;\n }\n }\n });\n return this;\n }\n\n /**\n * After all the items are placed and if any free space remains, it is distributed among the tracks.\n * Distribution strategy depends on the track configurations.\n * If there are tracks with flexible size\n * definition(fr), then all the free space is allocated to those tracks.\n * If there are no tracks with flexible size definiton, then the free space is distributed\n * evenly among the intrinsic tracks.\n * If all the tracks are fixed(ie, have fixed size), then the free space is not distributed.\n *\n * @returns {TrackResolver}\n * Reference of the class instance.\n * @memberof TrackResolver\n */\n _distributeFreeSpace () {\n let { frTracks, intrinsicTracks, sanitizedTracks } = this._config,\n { containerSize } = this.props,\n totalSpaceUsed = 0;\n\n sanitizedTracks.forEach(track => (totalSpaceUsed += (track.baseSize || 0)));\n\n if (totalSpaceUsed < containerSize) {\n if (frTracks.length) {\n frTracks.forEach((trackId, index) => { frTracks[index] = sanitizedTracks[trackId]; });\n frTracks.forEach(track => (totalSpaceUsed -= track.baseSize));\n _frSpaceDistributorHelper(frTracks, totalSpaceUsed, containerSize);\n } else if (intrinsicTracks.length) {\n intrinsicTracks.forEach((trackId, index) => { intrinsicTracks[index] = sanitizedTracks[trackId]; });\n _intrinsicSpaceDistributorHelper(intrinsicTracks, totalSpaceUsed, containerSize);\n }\n }\n return this;\n }\n\n /**\n * clears the props and configuration of TrackResolver. This method is called before using\n * TrackResolver with different set of input.\n *\n * @returns {TrackResolver}\n * Reference of the class instance.\n * @memberof TrackResolver\n */\n clear () {\n this.props = {};\n this._config = {\n frTracks: [],\n intrinsicTracks: [],\n autoFlow: []\n };\n\n return this;\n }\n}\n\nexport default TrackResolver;\n","import { getDisplayProperty, pluckNumber } from '../utils';\nimport TrackResolver from './track-sizing';\nimport { CENTER, END, STRETCH } from '../utils/constants';\nimport { repeatResolver } from './helpers/repeatResolver';\n\nconst validSizes = ['auto', 'none'],\n minmaxRegex = /minmax/,\n // repeatFunctionRegex = /repeat\\(/g,\n // templateSplitRegex = /\\s(\\[.*\\])*(\\(.*\\))*/g,\n templateSplitRegex = /(?:[^\\s[\\]()]+|\\[[^[\\]]*\\]|\\([^()]*\\))+/g,\n getUCFirstString = str => (str.charAt(0).toUpperCase() + str.slice(1)),\n validNestedGrid = tree => {\n let { gridTemplateColumns, gridTemplateRows } = tree.style || {};\n\n if (/repeat\\(/g.test(gridTemplateColumns) || /repeat\\(/g.test(gridTemplateRows)) {\n return false;\n }\n return true;\n },\n parseRepeatFunction = repeatStr => {\n return repeatStr.split(/\\(|\\)/g)[1].split(',').map(arg => arg && arg.trim());\n },\n getCleanSize = size => {\n size = size.trim();\n if (size === 'auto') return size;\n if (!isNaN(+size)) return +size;\n\n if (minmaxRegex.test(size)) {\n let sizeAr = size.split(/\\(|\\)/g)[1].split(',');\n\n return [\n sizeAr[0].trim(),\n sizeAr[1].trim()\n ];\n }\n\n return size;\n },\n getItemSize = (items, dimension) => {\n let filteredItems,\n templateCol,\n parsedDim = getUCFirstString(dimension),\n size,\n trackDir = dimension === 'width' ? 'col' : 'row';\n\n filteredItems = items.map(item => {\n templateCol = item.style['gridTemplate' + getUCFirstString(trackDir === 'col' ? 'columns' : 'rows')];\n if (getDisplayProperty(item) === 'grid' && /repeat\\(/g.test(templateCol)) {\n size = parseRepeatFunction(templateCol)[1];\n } else {\n size = item.style['min' + parsedDim + 'Contribution'] || item.style[dimension] || 'auto';\n }\n\n return {\n start: item[trackDir + 'Start'],\n end: item[trackDir + 'End'],\n size\n };\n });\n return filteredItems;\n },\n updateMatrix = (grid, start, end) => {\n let i,\n j;\n\n for (i = start.x; i < end.x; i++) {\n for (j = start.y; j < end.y; j++) {\n grid[i][j] = true;\n }\n }\n },\n /**\n * Converts gridColumn and gridRow attribute values into numeric grid lines.\n * This function is added to extend support for gridColumn and gridRow properties\n *\n * @param {object} itemStyle\n * itemStyle holds the user given style attributes.\n * @param {object} mapping\n * mapping hold the references from grid line names to grid line number\n * @returns {object} resolvedItemStyle\n * returns resolvedItemStyle which contains numeric grid lines\n */\n resolveItemStyle = (itemStyle, mapping) => {\n let {gridRowStart, gridRowEnd, gridColumnStart, gridColumnEnd} = itemStyle;\n if(itemStyle.gridColumn){\n [gridColumnStart, gridColumnEnd] = itemStyle.gridColumn.split(\"/\").map(line => line.trim());\n gridColumnStart = mapping ? mapping.col.nameToLineMap[gridColumnStart] : 1;\n if(/span\\s+\\d+/g.test(gridColumnEnd)){\n gridColumnEnd = gridColumnStart + +gridColumnEnd.match(/span\\s+(\\d+)/)[1];\n }\n gridColumnEnd = mapping ? mapping.col.nameToLineMap[gridColumnEnd] : 1;\n }\n if(itemStyle.gridRow){\n [gridRowStart, gridRowEnd] = itemStyle.gridRow.split(\"/\").map(line => line.trim());\n gridRowStart = mapping ? mapping.row.nameToLineMap[gridRowStart] : 1;\n if(/span\\s\\d+/g.test(gridRowEnd)){\n gridRowEnd = gridRowStart + +gridRowEnd.match(/span\\s(\\d+)/)[1];\n }\n gridRowEnd = mapping ? mapping.row.nameToLineMap[gridRowEnd] : 1;\n }\n return {\n gridRowStart,\n gridRowEnd,\n gridColumnStart,\n gridColumnEnd\n };\n },\n /**\n * Extracts maximum number of tracklines required when gridTemplateRows / gridTemplateColumns value is 'none' or not given\n *\n * @param {Array} items\n * items holds the list of grid container children.\n * @returns {object} \n * returns maximum number of track lines required\n */ \n getMaxRowColumn = items => {\n let maxRow = 1, maxColumn = 1, itemStyle;\n items.forEach((item) => {\n itemStyle = resolveItemStyle(item.style);\n maxColumn = Math.max(isNaN(+itemStyle.gridColumnStart) ? 0 : +itemStyle.gridColumnStart, maxColumn, isNaN(+itemStyle.gridColumnEnd - 1) ? 0 : +itemStyle.gridColumnEnd - 1);\n maxRow = Math.max(isNaN(+itemStyle.gridRowStart) ? 0 : +itemStyle.gridRowStart, maxRow, isNaN(+itemStyle.gridRowEnd - 1) ? 0 : +itemStyle.gridRowEnd - 1);\n });\n return {\n maxRow,\n maxColumn\n };\n };\nclass Grid {\n /**\n * Creates an instance of Grid. Initializes the props and _config object.\n * @memberof Grid\n */\n constructor () {\n this.setup();\n }\n\n /**\n * Initializes _config, props objects. Also initializes and stores a new instance of TrackResolver.\n *\n * @returns {Grid}\n * Reference of the class instance.\n * @memberof Grid\n */\n setup () {\n this._tsa = new TrackResolver();\n this.props = {};\n this._config = {\n mapping: {}\n };\n\n return this;\n }\n\n /**\n * Setter method to set props.\n *\n * @param {string} key\n * key represents the name by which the value is to be stored in props object.\n * @param {any} value\n * value is the information(can be anything) that has to be stored against the key.\n * @returns {Grid}\n * Reference of the class instance.\n * @memberof Grid\n */\n set (key, value) {\n this.props[key] = value;\n\n return this;\n }\n\n /**\n * Getter method to fetch props.\n *\n * @param {string} key\n * key of the value which has to be fetched.\n * @returns {any}\n * value corresponding to the key in props object\n * @memberof Grid\n */\n getProps (key) {\n return this.props[key];\n }\n\n /**\n * Getter method to fetch config.\n *\n * @param {string} key\n * key of the value which has to be fetched.\n * @returns {any}\n * alue corresponding to the key in _config object\n * @memberof Grid\n */\n getConfig (key) {\n return this._config[key];\n }\n\n /**\n * compute method is called to calculate the layout. This is the driver API.\n * 1. Tracks(rows and columns) are sanitized. Sanitization of tracks consists of going through the child nodes to get an overall estimate\n * regarding the number of tracks that are required.\n * 2. Items(child nodes) are sanitized. Any item without any proper gridStart and gridEnd values gets sanitized here.\n * 3. Track solving algrithm is run for both columns and rows to calculate the size each track will get.\n * 4. Once tracks are resolved and all tracks have their size, all the grid items are assigned their width, height, x and y(when applicable)\n *\n * @param {Object} _domTree\n * Full node tree consisting of grid container and grid items.\n * @memberof Grid\n */\n compute (_domTree) {\n let domTree = _domTree || this.props.domTree;\n\n this._sanitizeTracks(domTree)\n ._sanitizeItems(domTree)\n ._inflateTracks()\n ._assignCoordinatesToCells(domTree);\n }\n\n /**\n * Rows and columns are refered as tracks in css-grid terminology.\n * Track sanitization is required to account for any changes in the number of tracks by considering the grid items.\n * Items are iterated to check if all the times can be accomodated within the user-defined grid cells. If not, tracks will\n * be increased.\n *\n * @param {Object} [_domTree={}]\n * Full node tree consisting of grid container and grid items.\n * @returns {Grid}\n * Reference of the class instance.\n * @memberof Grid\n */\n _sanitizeTracks (_domTree = {}) {\n let style = _domTree.style,\n { gridTemplateRows, gridTemplateColumns } = style,\n config = this._config,\n trackInfo,\n { maxColumn, maxRow } = getMaxRowColumn(_domTree.children);\n\n this.set('maxTracks', maxRow);\n\n trackInfo = this._fetchTrackInformation(gridTemplateRows);\n config.mapping.row = {\n nameToLineMap: trackInfo.nameToLineMap,\n lineToNameMap: trackInfo.lineToNameMap\n };\n config.rowTracks = trackInfo.tracks;\n\n this.set('maxTracks', maxColumn);\n trackInfo = this._fetchTrackInformation(gridTemplateColumns);\n config.mapping.col = {\n nameToLineMap: trackInfo.nameToLineMap,\n lineToNameMap: trackInfo.lineToNameMap\n };\n config.colTracks = trackInfo.tracks;\n\n return this;\n }\n\n /**\n * Any track is bounded by two lines, which are called grid lines. A grid line can have multiple names.\n * To make calculations more easier, a map is maintained between line names and line numbers.\n *\n * @param {string} [tracks='none']\n * gridTemplateRows or gridTemplateColumns(user provided values)\n * @returns {Object}\n * tracks: Array of tracks where track has it's start, end and size(provided by user) specified\n * nameToLineMap: Object where key is the name and the value is the line number\n * lineToNameMap: Object where key is the number and the value is the name\n * @memberof Grid\n */\n _fetchTrackInformation (tracks = 'none') {\n let i,\n len,\n splittedTrackInfo = tracks.match(templateSplitRegex),\n nameList,\n sizeList,\n sanitizedTracks = [{}],\n startLineNames,\n endLineNames,\n nameToLineMap = {},\n lineToNameMap = {};\n\n nameList = splittedTrackInfo.filter(track => {\n if (track && typeof track === 'string' && track.length) {\n len = track.length;\n if (track[0] === '[' && track[len - 1] === ']') {\n return true;\n }\n return false;\n }\n return true;\n });\n\n sizeList = splittedTrackInfo.filter(size => {\n if (!size) return false;\n\n len = (size + '').toLowerCase().replace(/px|fr/, '');\n if (validSizes.indexOf(len) >= 0 || minmaxRegex.test(len) || !isNaN(len)) {\n return true;\n }\n return false;\n }).map(size => getCleanSize(size));\n\n len = sizeList.length;\n if (tracks === 'none') {\n len = this.getProps('maxTracks');\n }\n\n for (i = 0; i < len; i++) {\n startLineNames = (nameList[i] && nameList[i].replace(/\\[|\\]/g, '').split(' ').filter(name => name.length).map(name => name.trim())) || [i + 1 + ''];\n endLineNames = (nameList[i + 1] && nameList[i + 1].replace(/\\[|\\]/g, '').split(' ').filter(name => name.length).map(name => name.trim())) || [i + 2 + ''];\n\n sanitizedTracks.push({\n start: i + 1,\n end: i + 2,\n size: sizeList[i] || 'auto'\n });\n\n // A line can have multiple names but a name can only be assigned to a single line\n lineToNameMap[i + 1] = startLineNames;\n lineToNameMap[i + 2] = endLineNames;\n startLineNames.forEach(name => (nameToLineMap[name] = i + 1));\n endLineNames.forEach(name => (nameToLineMap[name] = i + 2));\n nameToLineMap[i + 1] = i + 1;\n nameToLineMap[i + 2] = i + 2;\n }\n\n return {\n tracks: sanitizedTracks,\n nameToLineMap,\n lineToNameMap\n };\n }\n\n /**\n * Sanitization of grid items. The gridRowStart and gridColumnStart values are replaced by the line numbers. Also,\n * if any item do not have any gridRowStart and/or gridColumnEnd values mentioned, they are placed accordingly in\n * empty cells in rowwise or columnwise manner, based on the value of gridAutoFlow.\n *\n * @param {Object} _domTree\n * Full node tree consisting of grid container and grid items.\n * @returns {Grid}\n * Reference of the class instance.\n * @memberof Grid\n */\n _sanitizeItems (_domTree) {\n let domTree = (_domTree || this.props.domTree),\n items = domTree.children || [],\n mapping = this._config.mapping,\n gridAutoFlow = domTree.style.gridAutoFlow || 'row',\n rowNum = Object.keys(mapping.row.lineToNameMap).length,\n colNum = Object.keys(mapping.col.lineToNameMap).length,\n sanitizedItems = [],\n autoFlowItems = [],\n itemStyle,\n gridMatrix = [[]],\n freeCells = [],\n cell,\n item,\n extraRows,\n i,\n j,\n len;\n\n for (i = 1; i <= rowNum; i++) {\n gridMatrix.push([]);\n }\n for (i = 0, len = items.length; i < len; i++) {\n itemStyle = resolveItemStyle(items[i].style, mapping);\n\n sanitizedItems.push({\n ...items[i],\n rowStart: mapping.row.nameToLineMap[itemStyle.gridRowStart],\n rowEnd: mapping.row.nameToLineMap[itemStyle.gridRowEnd],\n colStart: mapping.col.nameToLineMap[itemStyle.gridColumnStart],\n colEnd: mapping.col.nameToLineMap[itemStyle.gridColumnEnd]\n });\n item = sanitizedItems[i];\n updateMatrix(gridMatrix, {x: item.rowStart, y: item.colStart}, {x: item.rowEnd, y: item.colEnd});\n }\n\n autoFlowItems = sanitizedItems.filter(sanitizedItem => (!sanitizedItem.colStart || !sanitizedItem.rowStart));\n\n /**\n * @todo: Scope to improve code here.\n */\n if (autoFlowItems) {\n if (gridAutoFlow === 'row') {\n for (i = 1; i < rowNum; i++) {\n for (j = 1; j < colNum; j++) {\n if (!gridMatrix[i][j]) {\n freeCells.push({row: i, col: j});\n }\n }\n }\n\n while (autoFlowItems.length && freeCells.length) {\n item = autoFlowItems.shift();\n cell = freeCells.shift();\n\n item.rowStart = cell.row;\n item.colStart = cell.col;\n item.rowEnd = cell.row + 1;\n item.colEnd = cell.col + 1;\n }\n\n extraRows = Math.ceil(autoFlowItems.length / colNum);\n if (extraRows) {\n while (extraRows--) {\n domTree.style.gridTemplateRows += 'auto ';\n mapping.row.nameToLineMap[rowNum + 1] = rowNum + 1;\n mapping.row.nameToLineMap[rowNum + 2] = rowNum + 2;\n rowNum++;\n gridMatrix.push([]);\n }\n domTree.style.gridTemplateRows = domTree.style.gridTemplateRows.trim();\n\n freeCells = [];\n for (i = 1; i <= rowNum; i++) {\n for (j = 1; j <= colNum; j++) {\n if (!gridMatrix[i][j]) {\n freeCells.push({row: i, col: j});\n }\n }\n }\n while (autoFlowItems.length) {\n item = autoFlowItems.shift();\n cell = freeCells.shift();\n\n item.rowStart = cell.row;\n item.colStart = cell.col;\n item.rowEnd = cell.row + 1;\n item.colEnd = cell.col + 1;\n }\n }\n }\n }\n\n this._config.sanitizedItems = sanitizedItems;\n return this;\n }\n\n /**\n * Track solving algorithm is used to calculate the size of each track. First the column tracks are resolved, then the\n * row tracks. For track solving algorithm to run, it is important to resolve all the nested grids. Solving the nested\n * grids allows to consider their min-content contribution while solving tracks of parent grid.\n *\n * An exception arises if a nested grid has repeat in either of the gridTemplateColumns or gridTemplateRows property.\n * In that case, the nested grid is solved once the column tracks of the parent grid is solved.\n *\n * @returns {Grid}\n * Reference of the class instance.\n * @memberof Grid\n */\n _inflateTracks () {\n let { sanitizedItems, colTracks, rowTracks } = this._config,\n sizedTracks,\n minHeightContribution = 0,\n minWidthContribution = 0,\n { domTree } = this.props,\n { paddingStart, paddingEnd, paddingTop, paddingBottom, width, height } = domTree.style || {},\n tsa = new TrackResolver();\n\n if (!isNaN(+width)) {\n width -= (paddingStart + paddingEnd);\n }\n sizedTracks = tsa.clear()\n .set('tracks', colTracks)\n .set('items', getItemSize(sanitizedItems, 'width'))\n .set('containerSize', width || 'auto')\n .resolveTracks();\n\n colTracks.forEach((track, index) => {\n track.calculatedStyle = sizedTracks[index];\n minWidthContribution += sizedTracks[index].baseSize || 0;\n });\n\n this._solveUnresolvedChildren();\n\n if (!isNaN(+height)) {\n height -= (paddingTop + paddingBottom);\n }\n sizedTracks = tsa.clear()\n .set('tracks', rowTracks)\n .set('items', getItemSize(sanitizedItems, 'height'))\n .set('containerSize', height || 'auto')\n .resolveTracks();\n\n rowTracks.forEach((track, index) => {\n track.calculatedStyle = sizedTracks[index];\n minHeightContribution += sizedTracks[index].baseSize || 0;\n });\n\n domTree.style.minHeightContribution = minHeightContribution;\n domTree.style.minWidthContribution = minWidthContribution;\n return this;\n }\n\n /**\n * The grid items which are also grid containers(nested grids) and has repeat() configuration in either of\n * gridTenplateColumns or gridTemplateRows attribute are solved after the column tracks of the parents are solved.\n *\n * @param {Object} _domTree\n * Full node tree consisting of grid container and grid items.\n * @returns {Grid}\n * Reference of the class instance.\n * @memberof Grid\n */\n _solveUnresolvedChildren (_domTree) {\n let domTree = _domTree || this.props.domTree,\n childrenWithRepeatConfiguration = (domTree.unResolvedChildren || []).filter(child => /repeat\\(/g.test(child.style.gridTemplateColumns)\n || /repeat\\(/g.test(child.style.gridTemplateRows)),\n { colTracks, mapping } = this._config,\n parentReference = this.getProps('parent'),\n colTrackDp = [0],\n resolvedTracks,\n i,\n len,\n trackWidth,\n parentInfo,\n parsedWidthOfItem,\n colStart,\n colEnd;\n\n if (!childrenWithRepeatConfiguration.length) {\n return this;\n }\n\n for (i = 1, len = colTracks.length; i < len; i++) {\n colTrackDp[i] = colTrackDp[i - 1] + colTracks[i].calculatedStyle.baseSize;\n }\n\n childrenWithRepeatConfiguration.forEach(child => {\n // if (repeatFunctionRegex.test(child.style.gridTemplateColumns)) {\n parsedWidthOfItem = parseRepeatFunction(child.style.gridTemplateColumns)[1];\n colStart = mapping.col.nameToLineMap[child.style.gridColumnStart];\n colEnd = mapping.col.nameToLineMap[child.style.gridColumnEnd];\n\n trackWidth = colTrackDp[colEnd - 1] - colTrackDp[colStart - 1];\n parentInfo = {\n itemWidth: parsedWidthOfItem,\n width: trackWidth\n };\n\n resolvedTracks = repeatResolver(child, parentInfo);\n\n child.style.gridTemplateColumns = resolvedTracks.gridTemplateColumns;\n child.style.gridTemplateRows = resolvedTracks.gridTemplateRows;\n\n parentReference.gridLayoutEngine(child);\n // }\n });\n\n return this;\n }\n\n /**\n * After the grid is resolved, the items and the container should receive their dimensions(width, height) and positions(x, y).\n * This values are calculated after considering the justifyItem and alignItem attributes.\n *\n * @param {Object} _domTree\n * @memberof Grid\n */\n _assignCoordinatesToCells (_domTree) {\n let domTree = _domTree || this.props.domTree,\n { sanitizedItems, rowTracks, colTracks } = this._config,\n item,\n len,\n i,\n { justifyItems, alignItems, paddingStart, paddingTop } = domTree.style,\n trackWidth,\n trackHeight,\n width,\n height,\n x,\n y,\n rowTrackdp = [paddingStart],\n colTrackdp = [paddingTop];\n\n for (i = 1, len = rowTracks.length; i < len; i++) {\n rowTrackdp[i] = rowTrackdp[i - 1] + rowTracks[i].calculatedStyle.baseSize;\n }\n\n for (i = 1, len = colTracks.length; i < len; i++) {\n colTrackdp[i] = colTrackdp[i - 1] + colTracks[i].calculatedStyle.baseSize;\n }\n domTree.layout = {\n x: 0,\n y: 0,\n width: isNaN(domTree.style.width) ? colTrackdp[colTrackdp.length - 1] : domTree.style.width,\n height: isNaN(domTree.style.height) ? rowTrackdp[rowTrackdp.length - 1] : domTree.style.height\n };\n (domTree.children || []).forEach((child, index) => {\n item = sanitizedItems[index];\n trackWidth = colTrackdp[item.colEnd - 1] - colTrackdp[item.colStart - 1];\n trackHeight = rowTrackdp[item.rowEnd - 1] - rowTrackdp[item.rowStart - 1];\n\n width = isNaN(+child.style.width) ? trackWidth : +child.style.width;\n height = isNaN(+child.style.height) ? trackHeight : +child.style.height;\n\n switch (justifyItems || child.style.justifySelf) {\n case CENTER:\n x = colTrackdp[item.colStart - 1] + (trackWidth / 2) - (width / 2); break;\n case END:\n x = colTrackdp[item.colEnd - 1] - width; break;\n case STRETCH:\n width = trackWidth;\n x = colTrackdp[item.colStart - 1]; break;\n default:\n x = colTrackdp[item.colStart - 1];\n }\n\n switch (alignItems || child.style.alignSelf) {\n case CENTER:\n y = rowTrackdp[item.rowStart - 1] + (trackHeight / 2) - (height / 2); break;\n case END:\n y = rowTrackdp[item.rowEnd - 1] - height; break;\n case STRETCH:\n height = trackHeight;\n y = rowTrackdp[item.rowStart - 1]; break;\n default:\n y = rowTrackdp[item.rowStart - 1];\n }\n\n x += pluckNumber(item.style.paddingStart, item.style.padding, 0);\n y += pluckNumber(item.style.paddingTop, item.style.padding, 0);\n\n child.layout = {\n x,\n y,\n x2: x + width,\n y2: y + height,\n width,\n height\n };\n });\n\n return this;\n }\n}\n\nconst replaceWithAbsValue = (styleTrack = '', calculatedTrack) => {\n let trackSplitAr = (styleTrack.match(templateSplitRegex) || []).filter(track => track && !!track.trim()),\n trackWithAbsValue = '',\n counter = 1;\n\n if (trackSplitAr.length && !(/repeat\\(/.test(styleTrack))) {\n trackSplitAr.forEach(track => {\n if (validSizes.indexOf(track) > -1 || /[0-9]fr/.test(track) || minmaxRegex.test(track) || !isNaN(track)) {\n trackWithAbsValue += calculatedTrack[counter].calculatedStyle.baseSize + ' ';\n counter++;\n } else {\n trackWithAbsValue += track + ' ';\n }\n });\n } else {\n calculatedTrack.forEach(track => {\n if (isNaN(track.calculatedStyle.baseSize)) return;\n\n trackWithAbsValue += (track.calculatedStyle.baseSize + ' ');\n });\n }\n\n return trackWithAbsValue.trim();\n },\n updateDomTreeWithResolvedValues = (domTree, grid) => {\n let containerStyle = domTree.style,\n rowTracks = grid.getConfig('rowTracks'),\n colTracks = grid.getConfig('colTracks'),\n mapping = grid.getConfig('mapping'),\n { gridTemplateRows, gridTemplateColumns } = containerStyle,\n child,\n i,\n j,\n len,\n rowTrackSum,\n colTrackSum,\n rowStart,\n rowEnd,\n colStart,\n colEnd;\n\n domTree.style.gridTemplateRows = replaceWithAbsValue(gridTemplateRows, rowTracks);\n domTree.style.gridTemplateColumns = replaceWithAbsValue(gridTemplateColumns, colTracks);\n\n for (i = 0, len = (domTree.children || []).length; i < len; i++) {\n child = domTree.children[i];\n if (getDisplayProperty(child)) {\n child.style.gridTemplateColumns = child.userGivenStyles.gridTemplateColumns;\n child.style.gridTemplateRows = child.userGivenStyles.gridTemplateRows;\n if (isNaN(child.userGivenStyles.width)) {\n colStart = child.style.gridColumnStart;\n colEnd = child.style.gridColumnEnd;\n\n colStart = mapping.col.nameToLineMap[colStart];\n colEnd = mapping.col.nameToLineMap[colEnd];\n\n for (j = colStart, colTrackSum = 0; j < colEnd; j++) {\n colTrackSum += colTracks[j].calculatedStyle.baseSize;\n }\n child.style.width = colTrackSum;\n }\n if (isNaN(child.userGivenStyles.height)) {\n rowStart = child.style.gridRowStart;\n rowEnd = child.style.gridRowEnd;\n\n rowStart = mapping.row.nameToLineMap[rowStart];\n rowEnd = mapping.row.nameToLineMap[rowEnd];\n\n for (j = rowStart, rowTrackSum = 0; j < rowEnd; j++) {\n rowTrackSum += rowTracks[j].calculatedStyle.baseSize;\n }\n child.style.height = rowTrackSum;\n }\n }\n }\n\n return domTree;\n };\n\nfunction computeGridLayout (domTree, count = 1) {\n let i,\n len,\n style = domTree.style,\n child,\n grid;\n\n if (!domTree || !domTree.style) {\n return;\n }\n\n if (!domTree.userGivenStyles) {\n domTree.style.width = isNaN(domTree.style.width) ? 'auto' : domTree.style.width;\n domTree.style.height = isNaN(domTree.style.height) ? 'auto' : domTree.style.height;\n\n style.paddingStart = pluckNumber(style.paddingStart, style.padding, 0);\n style.paddingEnd = pluckNumber(style.paddingEnd, style.padding, 0);\n style.paddingTop = pluckNumber(style.paddingTop, style.padding, 0);\n style.paddingBottom = pluckNumber(style.paddingBottom, style.padding, 0);\n\n domTree.userGivenStyles = {\n gridTemplateColumns: domTree.style.gridTemplateColumns,\n gridTemplateRows: domTree.style.gridTemplateRows,\n width: domTree.style.width,\n height: domTree.style.height\n };\n }\n\n domTree.unResolvedChildren = [];\n for (i = 0, len = (domTree.children && domTree.children.length); i < len; i++) {\n child = domTree.children[i];\n if (getDisplayProperty(child)) {\n if (validNestedGrid(child)) {\n this.compute(child);\n } else {\n domTree.unResolvedChildren.push(child);\n }\n }\n }\n\n grid = new Grid();\n grid.set('domTree', domTree)\n .set('parent', this)\n .compute();\n\n if (count < 2) {\n this.gridLayoutEngine(updateDomTreeWithResolvedValues(domTree, grid), 2);\n }\n\n return domTree;\n}\n\nexport {\n computeGridLayout\n};\n","/**\n * Resolve repeat configurations if provided in gridTemplateRows or gridTemplateColumns.\n * Based on the size provided by the parent, this method re-defines the gridTemplateRows and/or\n * gridTemplateColumns attributes of the grid container.\n *\n * @param {Object} domTree\n * Object representing the node. The value of gridTemplateColumns and gridTemplateRows are taken from the style\n * object of node\n * @param {Object} parentInfo\n * Object containing the following properties\n * {\n * itemWidth: width of item\n * width: width of track\n * }\n * @returns {Object}\n * {\n * gridTemplateColumns: resolved gridTemplateColumns\n * gridTemplateRows: resolved gridTemplateRows\n * }\n */\nfunction repeatResolver (domTree, parentInfo) {\n let { children } = domTree,\n rowWidth = 0,\n numOfRows,\n itemInARow = 0,\n // itemWidth,\n repeatStyle = 'auto-fit',\n newGridTemplateColumns = '',\n newGridTemplateRows = '',\n i,\n len,\n height = 0,\n { itemWidth, width } = parentInfo;\n\n width = isNaN(+width) ? 0 : +width;\n\n children.forEach(child => (height = Math.max(height, +child.style.height || 0)));\n // [repeatStyle, itemWidth] = parseRepeatFunction(gridTemplateColumns);\n itemWidth = +itemWidth;\n\n if (repeatStyle === 'auto-fit') {\n rowWidth += itemWidth;\n newGridTemplateColumns += (itemWidth + ' ');\n itemInARow = 1;\n for (i = 1, len = children.length; i < len; i++) {\n if (rowWidth + itemWidth > width) {\n break;\n }\n rowWidth += itemWidth;\n newGridTemplateColumns += (itemWidth + ' ');\n }\n\n itemInARow = i;\n numOfRows = Math.ceil(len / itemInARow);\n\n while (numOfRows--) {\n newGridTemplateRows += height + ' ';\n }\n }\n\n return {\n gridTemplateColumns: newGridTemplateColumns.trim(),\n gridTemplateRows: newGridTemplateRows.trim()\n };\n}\n\nexport {\n repeatResolver\n};\n","import { getDisplayProperty, cloneObject, attachLayoutInformation } from './utils';\nimport { DISPLAY_GRID, DISPLAY_FLEX } from './utils/constants';\nimport { computeGridLayout } from './grid';\n\nclass LayoutEngine {\n constructor () {\n this.gridLayoutEngine = computeGridLayout;\n }\n\n compute (domTree) {\n switch (getDisplayProperty(domTree)) {\n case DISPLAY_GRID: return this.gridLayoutEngine(domTree);\n case DISPLAY_FLEX: return this.gridLayoutEngine(domTree);\n default:\n // Probably throw unsupported error?\n return this.gridLayoutEngine(domTree);\n }\n }\n}\n\n/**\n * Public API used externally to provide input to layout engine\n *\n * @param {Object} domTree Object containing the layout node information\n */\nconst computeLayout = (domTree) => {\n const faber = new LayoutEngine();\n let clonedDomTree = cloneObject(domTree),\n calculatedTree;\n\n clonedDomTree.root = true;\n calculatedTree = faber.compute(clonedDomTree);\n attachLayoutInformation(domTree, calculatedTree);\n\n return domTree;\n};\n\nexport {\n computeLayout\n};\n","import { computeLayout } from './faber';\n\nexport {\n computeLayout\n};\n"],"sourceRoot":""} -------------------------------------------------------------------------------- /es/faber.js: -------------------------------------------------------------------------------- 1 | "use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.computeLayout=void 0;var _utils=require("./utils");var _constants=require("./utils/constants");var _grid=require("./grid");function _classCallCheck(instance,Constructor){if(!(instance instanceof Constructor)){throw new TypeError("Cannot call a class as a function")}}function _defineProperties(target,props){for(var i=0;iwidth){break}rowWidth+=itemWidth;newGridTemplateColumns+=itemWidth+" "}itemInARow=i;numOfRows=Math.ceil(len/itemInARow);while(numOfRows--){newGridTemplateRows+=height+" "}}return{gridTemplateColumns:newGridTemplateColumns.trim(),gridTemplateRows:newGridTemplateRows.trim()}} -------------------------------------------------------------------------------- /es/grid/index.js: -------------------------------------------------------------------------------- 1 | "use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.computeGridLayout=computeGridLayout;var _utils=require("../utils");var _trackSizing=_interopRequireDefault(require("./track-sizing"));var _constants=require("../utils/constants");var _repeatResolver=require("./helpers/repeatResolver");function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{"default":obj}}function ownKeys(object,enumerableOnly){var keys=Object.keys(object);if(Object.getOwnPropertySymbols){var symbols=Object.getOwnPropertySymbols(object);if(enumerableOnly)symbols=symbols.filter(function(sym){return Object.getOwnPropertyDescriptor(object,sym).enumerable});keys.push.apply(keys,symbols)}return keys}function _objectSpread(target){for(var i=1;i0&&arguments[0]!==undefined?arguments[0]:{};var style=_domTree.style,gridTemplateRows=style.gridTemplateRows,gridTemplateColumns=style.gridTemplateColumns,config=this._config,trackInfo,_getMaxRowColumn=getMaxRowColumn(_domTree.children),maxColumn=_getMaxRowColumn.maxColumn,maxRow=_getMaxRowColumn.maxRow;this.set("maxTracks",maxRow);trackInfo=this._fetchTrackInformation(gridTemplateRows);config.mapping.row={nameToLineMap:trackInfo.nameToLineMap,lineToNameMap:trackInfo.lineToNameMap};config.rowTracks=trackInfo.tracks;this.set("maxTracks",maxColumn);trackInfo=this._fetchTrackInformation(gridTemplateColumns);config.mapping.col={nameToLineMap:trackInfo.nameToLineMap,lineToNameMap:trackInfo.lineToNameMap};config.colTracks=trackInfo.tracks;return this}},{key:"_fetchTrackInformation",value:function _fetchTrackInformation(){var tracks=arguments.length>0&&arguments[0]!==undefined?arguments[0]:"none";var i,len,splittedTrackInfo=tracks.match(templateSplitRegex),nameList,sizeList,sanitizedTracks=[{}],startLineNames,endLineNames,nameToLineMap={},lineToNameMap={};nameList=splittedTrackInfo.filter(function(track){if(track&&typeof track==="string"&&track.length){len=track.length;if(track[0]==="["&&track[len-1]==="]"){return true}return false}return true});sizeList=splittedTrackInfo.filter(function(size){if(!size)return false;len=(size+"").toLowerCase().replace(/px|fr/,"");if(validSizes.indexOf(len)>=0||minmaxRegex.test(len)||!isNaN(len)){return true}return false}).map(function(size){return getCleanSize(size)});len=sizeList.length;if(tracks==="none"){len=this.getProps("maxTracks")}for(i=0;i0&&arguments[0]!==undefined?arguments[0]:"";var calculatedTrack=arguments.length>1?arguments[1]:undefined;var trackSplitAr=(styleTrack.match(templateSplitRegex)||[]).filter(function(track){return track&&!!track.trim()}),trackWithAbsValue="",counter=1;if(trackSplitAr.length&&!/repeat\(/.test(styleTrack)){trackSplitAr.forEach(function(track){if(validSizes.indexOf(track)>-1||/[0-9]fr/.test(track)||minmaxRegex.test(track)||!isNaN(track)){trackWithAbsValue+=calculatedTrack[counter].calculatedStyle.baseSize+" ";counter++}else{trackWithAbsValue+=track+" "}})}else{calculatedTrack.forEach(function(track){if(isNaN(track.calculatedStyle.baseSize))return;trackWithAbsValue+=track.calculatedStyle.baseSize+" "})}return trackWithAbsValue.trim()},updateDomTreeWithResolvedValues=function updateDomTreeWithResolvedValues(domTree,grid){var containerStyle=domTree.style,rowTracks=grid.getConfig("rowTracks"),colTracks=grid.getConfig("colTracks"),mapping=grid.getConfig("mapping"),gridTemplateRows=containerStyle.gridTemplateRows,gridTemplateColumns=containerStyle.gridTemplateColumns,child,i,j,len,rowTrackSum,colTrackSum,rowStart,rowEnd,colStart,colEnd;domTree.style.gridTemplateRows=replaceWithAbsValue(gridTemplateRows,rowTracks);domTree.style.gridTemplateColumns=replaceWithAbsValue(gridTemplateColumns,colTracks);for(i=0,len=(domTree.children||[]).length;i1&&arguments[1]!==undefined?arguments[1]:1;var i,len,style=domTree.style,child,grid;if(!domTree||!domTree.style){return}if(!domTree.userGivenStyles){domTree.style.width=isNaN(domTree.style.width)?"auto":domTree.style.width;domTree.style.height=isNaN(domTree.style.height)?"auto":domTree.style.height;style.paddingStart=(0,_utils.pluckNumber)(style.paddingStart,style.padding,0);style.paddingEnd=(0,_utils.pluckNumber)(style.paddingEnd,style.padding,0);style.paddingTop=(0,_utils.pluckNumber)(style.paddingTop,style.padding,0);style.paddingBottom=(0,_utils.pluckNumber)(style.paddingBottom,style.padding,0);domTree.userGivenStyles={gridTemplateColumns:domTree.style.gridTemplateColumns,gridTemplateRows:domTree.style.gridTemplateRows,width:domTree.style.width,height:domTree.style.height}}domTree.unResolvedChildren=[];for(i=0,len=domTree.children&&domTree.children.length;itrack.multiplier*spacePerFrTrack}).forEach(function(track){return totalSpaceUsed+=track.baseSize});return _frSpaceDistributorHelper(eligibleTracks,totalSpaceUsed,containerSize)}else{eligibleTracks.forEach(function(track){return track.baseSize=track.multiplier*spacePerFrTrack})}},_intrinsicSpaceDistributorHelper=function _intrinsicSpaceDistributorHelper(tracks,totalSpaceUsed,containerSize){var freeSpace,spacePerIntrinsicTrack,i,len,frozenTrack=0,minMaxTracks,growthLimit,baseSize;if(!tracks.length){return}minMaxTracks=tracks.filter(function(track){return track.type==="minmax"&&track.growthLimit!==Infinity});freeSpace=containerSize-totalSpaceUsed;minMaxTracks.sort(function(a,b){var gap1=a.growthLimit-a.baseSize,gap2=b.growthLimit-b.baseSize;return gap1-gap2});len=minMaxTracks.length;while(frozenTrack0&&arguments[0]!==undefined?arguments[0]:[];var items=arguments.length>1&&arguments[1]!==undefined?arguments[1]:[];var containerSize=arguments.length>2&&arguments[2]!==undefined?arguments[2]:600;_classCallCheck(this,TrackResolver);this.clear();this.set("tracks",tracks);this.set("items",items);this.set("containerSize",containerSize);return this}_createClass(TrackResolver,[{key:"set",value:function set(key,info){this.props[key]=info;switch(key){case"tracks":this._initTrackSize();break;case"items":this._initItems();break;case"containerSize":this.props[key]=isNaN(+info)?0:+info;}return this}},{key:"get",value:function get(key){return this.props[key]}},{key:"_initTrackSize",value:function _initTrackSize(_tracks){var tracks=_tracks||this.props.tracks||[],config=this._config,trackAr=[{}],i,len,size,type,multiplier,baseSize,growthLimit;config.frTracks=[];config.intrinsicTracks=[];for(i=1,len=tracks.length;i0||size[0].indexOf("fr")>0){growthLimit=Infinity;config.frTracks.push(i);type="minmax"}else if(size[1]==="auto"||size[0]==="auto"){growthLimit=Infinity;config.intrinsicTracks.push(i);type="minmax"}else if(!isNaN(+size[0])&&!isNaN(+size[1])){growthLimit=Math.max(+size[0],+size[1]);baseSize=Math.min(+size[0],+size[1]);config.intrinsicTracks.push(i);type="minmax"}}else if(!isNaN(+size)){baseSize=growthLimit=+size;type="fixed"}else if(size.indexOf("fr")>0){baseSize=0;growthLimit=Infinity;config.frTracks.push(i);type="flex";multiplier=getMultiplierOfFr(size)}else{baseSize=0;growthLimit=Infinity;type="intrinsic";config.intrinsicTracks.push(i)}trackAr.push(_objectSpread({},tracks[i],{type:type,multiplier:multiplier,baseSize:baseSize,growthLimit:growthLimit}))}return config.sanitizedTracks=trackAr}},{key:"_initItems",value:function _initItems(_items){var items=_items||this.props.items||[],config=this._config,sanitizedItems=[],nonSpanningItemStartIndex,item,validItems=0,i,len;for(i=0,len=items.length;i1){nonSpanningItemStartIndex=i;break}}this._config.nonSpanningItemStartIndex=nonSpanningItemStartIndex;return this._config.sanitizedItems=sanitizedItems}},{key:"_getParentSize",value:function _getParentSize(item){var sanitizedTracks=this._config.sanitizedTracks,parentTracks,widthOfParentTracks=0;parentTracks=sanitizedTracks.filter(function(track){return track.start>=item.start&&track.end<=item.end});parentTracks.forEach(function(track){return widthOfParentTracks+=track.baseSize});return widthOfParentTracks||0}},{key:"resolveTracks",value:function resolveTracks(){this._placeNonSpanningItems()._placeSpanningItems()._distributeFreeSpace();return this._config.sanitizedTracks}},{key:"_placeNonSpanningItems",value:function _placeNonSpanningItems(){var _this$_config=this._config,sanitizedItems=_this$_config.sanitizedItems,sanitizedTracks=_this$_config.sanitizedTracks,nonSpanningItemStartIndex=_this$_config.nonSpanningItemStartIndex,nonSpanningItems=sanitizedItems.slice(0,nonSpanningItemStartIndex),track,trackIndex;nonSpanningItems.forEach(function(item){trackIndex=item.start;track=sanitizedTracks[trackIndex];if(track.type!=="fixed"){track.baseSize=Math.max(track.baseSize,item.size);track.growthLimit=Math.max(track.growthLimit,track.baseSize)}});return this}},{key:"_placeSpanningItems",value:function _placeSpanningItems(){var _this$_config2=this._config,sanitizedItems=_this$_config2.sanitizedItems,sanitizedTracks=_this$_config2.sanitizedTracks,nonSpanningItemStartIndex=_this$_config2.nonSpanningItemStartIndex,frTracks=_this$_config2.frTracks,spanningItems=sanitizedItems.slice(nonSpanningItemStartIndex),trackSizedp=[0],sizeConsumed,sizeLeft,sizePerTrack,availableTracks,hasFrTrack,i,len;if(!spanningItems.length)return this;for(i=1,len=sanitizedTracks.length;i=0){hasFrTrack=true}if(sanitizedTracks[i].type!=="fixed"){availableTracks++}}if(!availableTracks||hasFrTrack)return;sizePerTrack=sizeLeft/availableTracks;for(i=item.start;i-1||arg===null){return arg}if(Array.isArray(arg)){var i,len,arr=[];for(i=0,len=arg.length;i0&&arguments[0]!==undefined?arguments[0]:{};var calculatedTree=arguments.length>1&&arguments[1]!==undefined?arguments[1]:{};var i,len;baseTree.layout=calculatedTree.layout;for(i=0,len=(baseTree.children||[]).length;i { 27 | const faber = new LayoutEngine(); 28 | let clonedDomTree = cloneObject(domTree), 29 | calculatedTree; 30 | 31 | clonedDomTree.root = true; 32 | calculatedTree = faber.compute(clonedDomTree); 33 | attachLayoutInformation(domTree, calculatedTree); 34 | 35 | return domTree; 36 | }; 37 | 38 | export { 39 | computeLayout 40 | }; 41 | -------------------------------------------------------------------------------- /src/grid/helpers/repeatResolver.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Resolve repeat configurations if provided in gridTemplateRows or gridTemplateColumns. 3 | * Based on the size provided by the parent, this method re-defines the gridTemplateRows and/or 4 | * gridTemplateColumns attributes of the grid container. 5 | * 6 | * @param {Object} domTree 7 | * Object representing the node. The value of gridTemplateColumns and gridTemplateRows are taken from the style 8 | * object of node 9 | * @param {Object} parentInfo 10 | * Object containing the following properties 11 | * { 12 | * itemWidth: width of item 13 | * width: width of track 14 | * } 15 | * @returns {Object} 16 | * { 17 | * gridTemplateColumns: resolved gridTemplateColumns 18 | * gridTemplateRows: resolved gridTemplateRows 19 | * } 20 | */ 21 | function repeatResolver (domTree, parentInfo) { 22 | let { children } = domTree, 23 | rowWidth = 0, 24 | numOfRows, 25 | itemInARow = 0, 26 | // itemWidth, 27 | repeatStyle = 'auto-fit', 28 | newGridTemplateColumns = '', 29 | newGridTemplateRows = '', 30 | i, 31 | len, 32 | height = 0, 33 | { itemWidth, width } = parentInfo; 34 | 35 | width = isNaN(+width) ? 0 : +width; 36 | 37 | children.forEach(child => (height = Math.max(height, +child.style.height || 0))); 38 | // [repeatStyle, itemWidth] = parseRepeatFunction(gridTemplateColumns); 39 | itemWidth = +itemWidth; 40 | 41 | if (repeatStyle === 'auto-fit') { 42 | rowWidth += itemWidth; 43 | newGridTemplateColumns += (itemWidth + ' '); 44 | itemInARow = 1; 45 | for (i = 1, len = children.length; i < len; i++) { 46 | if (rowWidth + itemWidth > width) { 47 | break; 48 | } 49 | rowWidth += itemWidth; 50 | newGridTemplateColumns += (itemWidth + ' '); 51 | } 52 | 53 | itemInARow = i; 54 | numOfRows = Math.ceil(len / itemInARow); 55 | 56 | while (numOfRows--) { 57 | newGridTemplateRows += height + ' '; 58 | } 59 | } 60 | 61 | return { 62 | gridTemplateColumns: newGridTemplateColumns.trim(), 63 | gridTemplateRows: newGridTemplateRows.trim() 64 | }; 65 | } 66 | 67 | export { 68 | repeatResolver 69 | }; 70 | -------------------------------------------------------------------------------- /src/grid/index.js: -------------------------------------------------------------------------------- 1 | import { getDisplayProperty, pluckNumber } from '../utils'; 2 | import TrackResolver from './track-sizing'; 3 | import { CENTER, END, STRETCH } from '../utils/constants'; 4 | import { repeatResolver } from './helpers/repeatResolver'; 5 | 6 | const validSizes = ['auto', 'none'], 7 | minmaxRegex = /minmax/, 8 | // repeatFunctionRegex = /repeat\(/g, 9 | // templateSplitRegex = /\s(\[.*\])*(\(.*\))*/g, 10 | templateSplitRegex = /(?:[^\s[\]()]+|\[[^[\]]*\]|\([^()]*\))+/g, 11 | getUCFirstString = str => (str.charAt(0).toUpperCase() + str.slice(1)), 12 | validNestedGrid = tree => { 13 | let { gridTemplateColumns, gridTemplateRows } = tree.style || {}; 14 | 15 | if (/repeat\(/g.test(gridTemplateColumns) || /repeat\(/g.test(gridTemplateRows)) { 16 | return false; 17 | } 18 | return true; 19 | }, 20 | parseRepeatFunction = repeatStr => { 21 | return repeatStr.split(/\(|\)/g)[1].split(',').map(arg => arg && arg.trim()); 22 | }, 23 | getCleanSize = size => { 24 | size = size.trim(); 25 | if (size === 'auto') return size; 26 | if (!isNaN(+size)) return +size; 27 | 28 | if (minmaxRegex.test(size)) { 29 | let sizeAr = size.split(/\(|\)/g)[1].split(','); 30 | 31 | return [ 32 | sizeAr[0].trim(), 33 | sizeAr[1].trim() 34 | ]; 35 | } 36 | 37 | return size; 38 | }, 39 | getItemSize = (items, dimension) => { 40 | let filteredItems, 41 | templateCol, 42 | parsedDim = getUCFirstString(dimension), 43 | size, 44 | trackDir = dimension === 'width' ? 'col' : 'row'; 45 | 46 | filteredItems = items.map(item => { 47 | templateCol = item.style['gridTemplate' + getUCFirstString(trackDir === 'col' ? 'columns' : 'rows')]; 48 | if (getDisplayProperty(item) === 'grid' && /repeat\(/g.test(templateCol)) { 49 | size = parseRepeatFunction(templateCol)[1]; 50 | } else { 51 | size = item.style['min' + parsedDim + 'Contribution'] || item.style[dimension] || 'auto'; 52 | } 53 | 54 | return { 55 | start: item[trackDir + 'Start'], 56 | end: item[trackDir + 'End'], 57 | size 58 | }; 59 | }); 60 | return filteredItems; 61 | }, 62 | updateMatrix = (grid, start, end) => { 63 | let i, 64 | j; 65 | 66 | for (i = start.x; i < end.x; i++) { 67 | for (j = start.y; j < end.y; j++) { 68 | grid[i][j] = true; 69 | } 70 | } 71 | }, 72 | /** 73 | * Converts gridColumn and gridRow attribute values into numeric grid lines. 74 | * This function is added to extend support for gridColumn and gridRow properties 75 | * 76 | * @param {object} itemStyle 77 | * itemStyle holds the user given style attributes. 78 | * @param {object} mapping 79 | * mapping hold the references from grid line names to grid line number 80 | * @returns {object} resolvedItemStyle 81 | * returns resolvedItemStyle which contains numeric grid lines 82 | */ 83 | resolveItemStyle = (itemStyle, mapping) => { 84 | let {gridRowStart, gridRowEnd, gridColumnStart, gridColumnEnd} = itemStyle; 85 | if(itemStyle.gridColumn){ 86 | [gridColumnStart, gridColumnEnd] = itemStyle.gridColumn.split("/").map(line => line.trim()); 87 | gridColumnStart = mapping ? mapping.col.nameToLineMap[gridColumnStart] : 1; 88 | if(/span\s+\d+/g.test(gridColumnEnd)){ 89 | gridColumnEnd = gridColumnStart + +gridColumnEnd.match(/span\s+(\d+)/)[1]; 90 | } 91 | gridColumnEnd = mapping ? mapping.col.nameToLineMap[gridColumnEnd] : 1; 92 | } 93 | if(itemStyle.gridRow){ 94 | [gridRowStart, gridRowEnd] = itemStyle.gridRow.split("/").map(line => line.trim()); 95 | gridRowStart = mapping ? mapping.row.nameToLineMap[gridRowStart] : 1; 96 | if(/span\s\d+/g.test(gridRowEnd)){ 97 | gridRowEnd = gridRowStart + +gridRowEnd.match(/span\s(\d+)/)[1]; 98 | } 99 | gridRowEnd = mapping ? mapping.row.nameToLineMap[gridRowEnd] : 1; 100 | } 101 | return { 102 | gridRowStart, 103 | gridRowEnd, 104 | gridColumnStart, 105 | gridColumnEnd 106 | }; 107 | }, 108 | /** 109 | * Extracts maximum number of tracklines required when gridTemplateRows / gridTemplateColumns value is 'none' or not given 110 | * 111 | * @param {Array} items 112 | * items holds the list of grid container children. 113 | * @returns {object} 114 | * returns maximum number of track lines required 115 | */ 116 | getMaxRowColumn = items => { 117 | let maxRow = 1, maxColumn = 1, itemStyle; 118 | items.forEach((item) => { 119 | itemStyle = resolveItemStyle(item.style); 120 | maxColumn = Math.max(isNaN(+itemStyle.gridColumnStart) ? 0 : +itemStyle.gridColumnStart, maxColumn, isNaN(+itemStyle.gridColumnEnd - 1) ? 0 : +itemStyle.gridColumnEnd - 1); 121 | maxRow = Math.max(isNaN(+itemStyle.gridRowStart) ? 0 : +itemStyle.gridRowStart, maxRow, isNaN(+itemStyle.gridRowEnd - 1) ? 0 : +itemStyle.gridRowEnd - 1); 122 | }); 123 | return { 124 | maxRow, 125 | maxColumn 126 | }; 127 | }; 128 | class Grid { 129 | /** 130 | * Creates an instance of Grid. Initializes the props and _config object. 131 | * @memberof Grid 132 | */ 133 | constructor () { 134 | this.setup(); 135 | } 136 | 137 | /** 138 | * Initializes _config, props objects. Also initializes and stores a new instance of TrackResolver. 139 | * 140 | * @returns {Grid} 141 | * Reference of the class instance. 142 | * @memberof Grid 143 | */ 144 | setup () { 145 | this._tsa = new TrackResolver(); 146 | this.props = {}; 147 | this._config = { 148 | mapping: {} 149 | }; 150 | 151 | return this; 152 | } 153 | 154 | /** 155 | * Setter method to set props. 156 | * 157 | * @param {string} key 158 | * key represents the name by which the value is to be stored in props object. 159 | * @param {any} value 160 | * value is the information(can be anything) that has to be stored against the key. 161 | * @returns {Grid} 162 | * Reference of the class instance. 163 | * @memberof Grid 164 | */ 165 | set (key, value) { 166 | this.props[key] = value; 167 | 168 | return this; 169 | } 170 | 171 | /** 172 | * Getter method to fetch props. 173 | * 174 | * @param {string} key 175 | * key of the value which has to be fetched. 176 | * @returns {any} 177 | * value corresponding to the key in props object 178 | * @memberof Grid 179 | */ 180 | getProps (key) { 181 | return this.props[key]; 182 | } 183 | 184 | /** 185 | * Getter method to fetch config. 186 | * 187 | * @param {string} key 188 | * key of the value which has to be fetched. 189 | * @returns {any} 190 | * alue corresponding to the key in _config object 191 | * @memberof Grid 192 | */ 193 | getConfig (key) { 194 | return this._config[key]; 195 | } 196 | 197 | /** 198 | * compute method is called to calculate the layout. This is the driver API. 199 | * 1. Tracks(rows and columns) are sanitized. Sanitization of tracks consists of going through the child nodes to get an overall estimate 200 | * regarding the number of tracks that are required. 201 | * 2. Items(child nodes) are sanitized. Any item without any proper gridStart and gridEnd values gets sanitized here. 202 | * 3. Track solving algrithm is run for both columns and rows to calculate the size each track will get. 203 | * 4. Once tracks are resolved and all tracks have their size, all the grid items are assigned their width, height, x and y(when applicable) 204 | * 205 | * @param {Object} _domTree 206 | * Full node tree consisting of grid container and grid items. 207 | * @memberof Grid 208 | */ 209 | compute (_domTree) { 210 | let domTree = _domTree || this.props.domTree; 211 | 212 | this._sanitizeTracks(domTree) 213 | ._sanitizeItems(domTree) 214 | ._inflateTracks() 215 | ._assignCoordinatesToCells(domTree); 216 | } 217 | 218 | /** 219 | * Rows and columns are refered as tracks in css-grid terminology. 220 | * Track sanitization is required to account for any changes in the number of tracks by considering the grid items. 221 | * Items are iterated to check if all the times can be accomodated within the user-defined grid cells. If not, tracks will 222 | * be increased. 223 | * 224 | * @param {Object} [_domTree={}] 225 | * Full node tree consisting of grid container and grid items. 226 | * @returns {Grid} 227 | * Reference of the class instance. 228 | * @memberof Grid 229 | */ 230 | _sanitizeTracks (_domTree = {}) { 231 | let style = _domTree.style, 232 | { gridTemplateRows, gridTemplateColumns } = style, 233 | config = this._config, 234 | trackInfo, 235 | { maxColumn, maxRow } = getMaxRowColumn(_domTree.children); 236 | 237 | this.set('maxTracks', maxRow); 238 | 239 | trackInfo = this._fetchTrackInformation(gridTemplateRows); 240 | config.mapping.row = { 241 | nameToLineMap: trackInfo.nameToLineMap, 242 | lineToNameMap: trackInfo.lineToNameMap 243 | }; 244 | config.rowTracks = trackInfo.tracks; 245 | 246 | this.set('maxTracks', maxColumn); 247 | trackInfo = this._fetchTrackInformation(gridTemplateColumns); 248 | config.mapping.col = { 249 | nameToLineMap: trackInfo.nameToLineMap, 250 | lineToNameMap: trackInfo.lineToNameMap 251 | }; 252 | config.colTracks = trackInfo.tracks; 253 | 254 | return this; 255 | } 256 | 257 | /** 258 | * Any track is bounded by two lines, which are called grid lines. A grid line can have multiple names. 259 | * To make calculations more easier, a map is maintained between line names and line numbers. 260 | * 261 | * @param {string} [tracks='none'] 262 | * gridTemplateRows or gridTemplateColumns(user provided values) 263 | * @returns {Object} 264 | * tracks: Array of tracks where track has it's start, end and size(provided by user) specified 265 | * nameToLineMap: Object where key is the name and the value is the line number 266 | * lineToNameMap: Object where key is the number and the value is the name 267 | * @memberof Grid 268 | */ 269 | _fetchTrackInformation (tracks = 'none') { 270 | let i, 271 | len, 272 | splittedTrackInfo = tracks.match(templateSplitRegex), 273 | nameList, 274 | sizeList, 275 | sanitizedTracks = [{}], 276 | startLineNames, 277 | endLineNames, 278 | nameToLineMap = {}, 279 | lineToNameMap = {}; 280 | 281 | nameList = splittedTrackInfo.filter(track => { 282 | if (track && typeof track === 'string' && track.length) { 283 | len = track.length; 284 | if (track[0] === '[' && track[len - 1] === ']') { 285 | return true; 286 | } 287 | return false; 288 | } 289 | return true; 290 | }); 291 | 292 | sizeList = splittedTrackInfo.filter(size => { 293 | if (!size) return false; 294 | 295 | len = (size + '').toLowerCase().replace(/px|fr/, ''); 296 | if (validSizes.indexOf(len) >= 0 || minmaxRegex.test(len) || !isNaN(len)) { 297 | return true; 298 | } 299 | return false; 300 | }).map(size => getCleanSize(size)); 301 | 302 | len = sizeList.length; 303 | if (tracks === 'none') { 304 | len = this.getProps('maxTracks'); 305 | } 306 | 307 | for (i = 0; i < len; i++) { 308 | startLineNames = (nameList[i] && nameList[i].replace(/\[|\]/g, '').split(' ').filter(name => name.length).map(name => name.trim())) || [i + 1 + '']; 309 | endLineNames = (nameList[i + 1] && nameList[i + 1].replace(/\[|\]/g, '').split(' ').filter(name => name.length).map(name => name.trim())) || [i + 2 + '']; 310 | 311 | sanitizedTracks.push({ 312 | start: i + 1, 313 | end: i + 2, 314 | size: sizeList[i] || 'auto' 315 | }); 316 | 317 | // A line can have multiple names but a name can only be assigned to a single line 318 | lineToNameMap[i + 1] = startLineNames; 319 | lineToNameMap[i + 2] = endLineNames; 320 | startLineNames.forEach(name => (nameToLineMap[name] = i + 1)); 321 | endLineNames.forEach(name => (nameToLineMap[name] = i + 2)); 322 | nameToLineMap[i + 1] = i + 1; 323 | nameToLineMap[i + 2] = i + 2; 324 | } 325 | 326 | return { 327 | tracks: sanitizedTracks, 328 | nameToLineMap, 329 | lineToNameMap 330 | }; 331 | } 332 | 333 | /** 334 | * Sanitization of grid items. The gridRowStart and gridColumnStart values are replaced by the line numbers. Also, 335 | * if any item do not have any gridRowStart and/or gridColumnEnd values mentioned, they are placed accordingly in 336 | * empty cells in rowwise or columnwise manner, based on the value of gridAutoFlow. 337 | * 338 | * @param {Object} _domTree 339 | * Full node tree consisting of grid container and grid items. 340 | * @returns {Grid} 341 | * Reference of the class instance. 342 | * @memberof Grid 343 | */ 344 | _sanitizeItems (_domTree) { 345 | let domTree = (_domTree || this.props.domTree), 346 | items = domTree.children || [], 347 | mapping = this._config.mapping, 348 | gridAutoFlow = domTree.style.gridAutoFlow || 'row', 349 | rowNum = Object.keys(mapping.row.lineToNameMap).length, 350 | colNum = Object.keys(mapping.col.lineToNameMap).length, 351 | sanitizedItems = [], 352 | autoFlowItems = [], 353 | itemStyle, 354 | gridMatrix = [[]], 355 | freeCells = [], 356 | cell, 357 | item, 358 | extraRows, 359 | i, 360 | j, 361 | len; 362 | 363 | for (i = 1; i <= rowNum; i++) { 364 | gridMatrix.push([]); 365 | } 366 | for (i = 0, len = items.length; i < len; i++) { 367 | itemStyle = resolveItemStyle(items[i].style, mapping); 368 | 369 | sanitizedItems.push({ 370 | ...items[i], 371 | rowStart: mapping.row.nameToLineMap[itemStyle.gridRowStart], 372 | rowEnd: mapping.row.nameToLineMap[itemStyle.gridRowEnd], 373 | colStart: mapping.col.nameToLineMap[itemStyle.gridColumnStart], 374 | colEnd: mapping.col.nameToLineMap[itemStyle.gridColumnEnd] 375 | }); 376 | item = sanitizedItems[i]; 377 | updateMatrix(gridMatrix, {x: item.rowStart, y: item.colStart}, {x: item.rowEnd, y: item.colEnd}); 378 | } 379 | 380 | autoFlowItems = sanitizedItems.filter(sanitizedItem => (!sanitizedItem.colStart || !sanitizedItem.rowStart)); 381 | 382 | /** 383 | * @todo: Scope to improve code here. 384 | */ 385 | if (autoFlowItems) { 386 | if (gridAutoFlow === 'row') { 387 | for (i = 1; i < rowNum; i++) { 388 | for (j = 1; j < colNum; j++) { 389 | if (!gridMatrix[i][j]) { 390 | freeCells.push({row: i, col: j}); 391 | } 392 | } 393 | } 394 | 395 | while (autoFlowItems.length && freeCells.length) { 396 | item = autoFlowItems.shift(); 397 | cell = freeCells.shift(); 398 | 399 | item.rowStart = cell.row; 400 | item.colStart = cell.col; 401 | item.rowEnd = cell.row + 1; 402 | item.colEnd = cell.col + 1; 403 | } 404 | 405 | extraRows = Math.ceil(autoFlowItems.length / colNum); 406 | if (extraRows) { 407 | while (extraRows--) { 408 | domTree.style.gridTemplateRows += 'auto '; 409 | mapping.row.nameToLineMap[rowNum + 1] = rowNum + 1; 410 | mapping.row.nameToLineMap[rowNum + 2] = rowNum + 2; 411 | rowNum++; 412 | gridMatrix.push([]); 413 | } 414 | domTree.style.gridTemplateRows = domTree.style.gridTemplateRows.trim(); 415 | 416 | freeCells = []; 417 | for (i = 1; i <= rowNum; i++) { 418 | for (j = 1; j <= colNum; j++) { 419 | if (!gridMatrix[i][j]) { 420 | freeCells.push({row: i, col: j}); 421 | } 422 | } 423 | } 424 | while (autoFlowItems.length) { 425 | item = autoFlowItems.shift(); 426 | cell = freeCells.shift(); 427 | 428 | item.rowStart = cell.row; 429 | item.colStart = cell.col; 430 | item.rowEnd = cell.row + 1; 431 | item.colEnd = cell.col + 1; 432 | } 433 | } 434 | } 435 | } 436 | 437 | this._config.sanitizedItems = sanitizedItems; 438 | return this; 439 | } 440 | 441 | /** 442 | * Track solving algorithm is used to calculate the size of each track. First the column tracks are resolved, then the 443 | * row tracks. For track solving algorithm to run, it is important to resolve all the nested grids. Solving the nested 444 | * grids allows to consider their min-content contribution while solving tracks of parent grid. 445 | * 446 | * An exception arises if a nested grid has repeat in either of the gridTemplateColumns or gridTemplateRows property. 447 | * In that case, the nested grid is solved once the column tracks of the parent grid is solved. 448 | * 449 | * @returns {Grid} 450 | * Reference of the class instance. 451 | * @memberof Grid 452 | */ 453 | _inflateTracks () { 454 | let { sanitizedItems, colTracks, rowTracks } = this._config, 455 | sizedTracks, 456 | minHeightContribution = 0, 457 | minWidthContribution = 0, 458 | { domTree } = this.props, 459 | { paddingStart, paddingEnd, paddingTop, paddingBottom, width, height } = domTree.style || {}, 460 | tsa = new TrackResolver(); 461 | 462 | if (!isNaN(+width)) { 463 | width -= (paddingStart + paddingEnd); 464 | } 465 | sizedTracks = tsa.clear() 466 | .set('tracks', colTracks) 467 | .set('items', getItemSize(sanitizedItems, 'width')) 468 | .set('containerSize', width || 'auto') 469 | .resolveTracks(); 470 | 471 | colTracks.forEach((track, index) => { 472 | track.calculatedStyle = sizedTracks[index]; 473 | minWidthContribution += sizedTracks[index].baseSize || 0; 474 | }); 475 | 476 | this._solveUnresolvedChildren(); 477 | 478 | if (!isNaN(+height)) { 479 | height -= (paddingTop + paddingBottom); 480 | } 481 | sizedTracks = tsa.clear() 482 | .set('tracks', rowTracks) 483 | .set('items', getItemSize(sanitizedItems, 'height')) 484 | .set('containerSize', height || 'auto') 485 | .resolveTracks(); 486 | 487 | rowTracks.forEach((track, index) => { 488 | track.calculatedStyle = sizedTracks[index]; 489 | minHeightContribution += sizedTracks[index].baseSize || 0; 490 | }); 491 | 492 | domTree.style.minHeightContribution = minHeightContribution; 493 | domTree.style.minWidthContribution = minWidthContribution; 494 | return this; 495 | } 496 | 497 | /** 498 | * The grid items which are also grid containers(nested grids) and has repeat() configuration in either of 499 | * gridTenplateColumns or gridTemplateRows attribute are solved after the column tracks of the parents are solved. 500 | * 501 | * @param {Object} _domTree 502 | * Full node tree consisting of grid container and grid items. 503 | * @returns {Grid} 504 | * Reference of the class instance. 505 | * @memberof Grid 506 | */ 507 | _solveUnresolvedChildren (_domTree) { 508 | let domTree = _domTree || this.props.domTree, 509 | childrenWithRepeatConfiguration = (domTree.unResolvedChildren || []).filter(child => /repeat\(/g.test(child.style.gridTemplateColumns) 510 | || /repeat\(/g.test(child.style.gridTemplateRows)), 511 | { colTracks, mapping } = this._config, 512 | parentReference = this.getProps('parent'), 513 | colTrackDp = [0], 514 | resolvedTracks, 515 | i, 516 | len, 517 | trackWidth, 518 | parentInfo, 519 | parsedWidthOfItem, 520 | colStart, 521 | colEnd; 522 | 523 | if (!childrenWithRepeatConfiguration.length) { 524 | return this; 525 | } 526 | 527 | for (i = 1, len = colTracks.length; i < len; i++) { 528 | colTrackDp[i] = colTrackDp[i - 1] + colTracks[i].calculatedStyle.baseSize; 529 | } 530 | 531 | childrenWithRepeatConfiguration.forEach(child => { 532 | // if (repeatFunctionRegex.test(child.style.gridTemplateColumns)) { 533 | parsedWidthOfItem = parseRepeatFunction(child.style.gridTemplateColumns)[1]; 534 | colStart = mapping.col.nameToLineMap[child.style.gridColumnStart]; 535 | colEnd = mapping.col.nameToLineMap[child.style.gridColumnEnd]; 536 | 537 | trackWidth = colTrackDp[colEnd - 1] - colTrackDp[colStart - 1]; 538 | parentInfo = { 539 | itemWidth: parsedWidthOfItem, 540 | width: trackWidth 541 | }; 542 | 543 | resolvedTracks = repeatResolver(child, parentInfo); 544 | 545 | child.style.gridTemplateColumns = resolvedTracks.gridTemplateColumns; 546 | child.style.gridTemplateRows = resolvedTracks.gridTemplateRows; 547 | 548 | parentReference.gridLayoutEngine(child); 549 | // } 550 | }); 551 | 552 | return this; 553 | } 554 | 555 | /** 556 | * After the grid is resolved, the items and the container should receive their dimensions(width, height) and positions(x, y). 557 | * This values are calculated after considering the justifyItem and alignItem attributes. 558 | * 559 | * @param {Object} _domTree 560 | * @memberof Grid 561 | */ 562 | _assignCoordinatesToCells (_domTree) { 563 | let domTree = _domTree || this.props.domTree, 564 | { sanitizedItems, rowTracks, colTracks } = this._config, 565 | item, 566 | len, 567 | i, 568 | { justifyItems, alignItems, paddingStart, paddingTop } = domTree.style, 569 | trackWidth, 570 | trackHeight, 571 | width, 572 | height, 573 | x, 574 | y, 575 | rowTrackdp = [paddingStart], 576 | colTrackdp = [paddingTop]; 577 | 578 | for (i = 1, len = rowTracks.length; i < len; i++) { 579 | rowTrackdp[i] = rowTrackdp[i - 1] + rowTracks[i].calculatedStyle.baseSize; 580 | } 581 | 582 | for (i = 1, len = colTracks.length; i < len; i++) { 583 | colTrackdp[i] = colTrackdp[i - 1] + colTracks[i].calculatedStyle.baseSize; 584 | } 585 | domTree.layout = { 586 | x: 0, 587 | y: 0, 588 | width: isNaN(domTree.style.width) ? colTrackdp[colTrackdp.length - 1] : domTree.style.width, 589 | height: isNaN(domTree.style.height) ? rowTrackdp[rowTrackdp.length - 1] : domTree.style.height 590 | }; 591 | (domTree.children || []).forEach((child, index) => { 592 | item = sanitizedItems[index]; 593 | trackWidth = colTrackdp[item.colEnd - 1] - colTrackdp[item.colStart - 1]; 594 | trackHeight = rowTrackdp[item.rowEnd - 1] - rowTrackdp[item.rowStart - 1]; 595 | 596 | width = isNaN(+child.style.width) ? trackWidth : +child.style.width; 597 | height = isNaN(+child.style.height) ? trackHeight : +child.style.height; 598 | 599 | switch (justifyItems || child.style.justifySelf) { 600 | case CENTER: 601 | x = colTrackdp[item.colStart - 1] + (trackWidth / 2) - (width / 2); break; 602 | case END: 603 | x = colTrackdp[item.colEnd - 1] - width; break; 604 | case STRETCH: 605 | width = trackWidth; 606 | x = colTrackdp[item.colStart - 1]; break; 607 | default: 608 | x = colTrackdp[item.colStart - 1]; 609 | } 610 | 611 | switch (alignItems || child.style.alignSelf) { 612 | case CENTER: 613 | y = rowTrackdp[item.rowStart - 1] + (trackHeight / 2) - (height / 2); break; 614 | case END: 615 | y = rowTrackdp[item.rowEnd - 1] - height; break; 616 | case STRETCH: 617 | height = trackHeight; 618 | y = rowTrackdp[item.rowStart - 1]; break; 619 | default: 620 | y = rowTrackdp[item.rowStart - 1]; 621 | } 622 | 623 | x += pluckNumber(item.style.paddingStart, item.style.padding, 0); 624 | y += pluckNumber(item.style.paddingTop, item.style.padding, 0); 625 | 626 | child.layout = { 627 | x, 628 | y, 629 | x2: x + width, 630 | y2: y + height, 631 | width, 632 | height 633 | }; 634 | }); 635 | 636 | return this; 637 | } 638 | } 639 | 640 | const replaceWithAbsValue = (styleTrack = '', calculatedTrack) => { 641 | let trackSplitAr = (styleTrack.match(templateSplitRegex) || []).filter(track => track && !!track.trim()), 642 | trackWithAbsValue = '', 643 | counter = 1; 644 | 645 | if (trackSplitAr.length && !(/repeat\(/.test(styleTrack))) { 646 | trackSplitAr.forEach(track => { 647 | if (validSizes.indexOf(track) > -1 || /[0-9]fr/.test(track) || minmaxRegex.test(track) || !isNaN(track)) { 648 | trackWithAbsValue += calculatedTrack[counter].calculatedStyle.baseSize + ' '; 649 | counter++; 650 | } else { 651 | trackWithAbsValue += track + ' '; 652 | } 653 | }); 654 | } else { 655 | calculatedTrack.forEach(track => { 656 | if (isNaN(track.calculatedStyle.baseSize)) return; 657 | 658 | trackWithAbsValue += (track.calculatedStyle.baseSize + ' '); 659 | }); 660 | } 661 | 662 | return trackWithAbsValue.trim(); 663 | }, 664 | updateDomTreeWithResolvedValues = (domTree, grid) => { 665 | let containerStyle = domTree.style, 666 | rowTracks = grid.getConfig('rowTracks'), 667 | colTracks = grid.getConfig('colTracks'), 668 | mapping = grid.getConfig('mapping'), 669 | { gridTemplateRows, gridTemplateColumns } = containerStyle, 670 | child, 671 | i, 672 | j, 673 | len, 674 | rowTrackSum, 675 | colTrackSum, 676 | rowStart, 677 | rowEnd, 678 | colStart, 679 | colEnd; 680 | 681 | domTree.style.gridTemplateRows = replaceWithAbsValue(gridTemplateRows, rowTracks); 682 | domTree.style.gridTemplateColumns = replaceWithAbsValue(gridTemplateColumns, colTracks); 683 | 684 | for (i = 0, len = (domTree.children || []).length; i < len; i++) { 685 | child = domTree.children[i]; 686 | if (getDisplayProperty(child)) { 687 | child.style.gridTemplateColumns = child.userGivenStyles.gridTemplateColumns; 688 | child.style.gridTemplateRows = child.userGivenStyles.gridTemplateRows; 689 | if (isNaN(child.userGivenStyles.width)) { 690 | colStart = child.style.gridColumnStart; 691 | colEnd = child.style.gridColumnEnd; 692 | 693 | colStart = mapping.col.nameToLineMap[colStart]; 694 | colEnd = mapping.col.nameToLineMap[colEnd]; 695 | 696 | for (j = colStart, colTrackSum = 0; j < colEnd; j++) { 697 | colTrackSum += colTracks[j].calculatedStyle.baseSize; 698 | } 699 | child.style.width = colTrackSum; 700 | } 701 | if (isNaN(child.userGivenStyles.height)) { 702 | rowStart = child.style.gridRowStart; 703 | rowEnd = child.style.gridRowEnd; 704 | 705 | rowStart = mapping.row.nameToLineMap[rowStart]; 706 | rowEnd = mapping.row.nameToLineMap[rowEnd]; 707 | 708 | for (j = rowStart, rowTrackSum = 0; j < rowEnd; j++) { 709 | rowTrackSum += rowTracks[j].calculatedStyle.baseSize; 710 | } 711 | child.style.height = rowTrackSum; 712 | } 713 | } 714 | } 715 | 716 | return domTree; 717 | }; 718 | 719 | function computeGridLayout (domTree, count = 1) { 720 | let i, 721 | len, 722 | style = domTree.style, 723 | child, 724 | grid; 725 | 726 | if (!domTree || !domTree.style) { 727 | return; 728 | } 729 | 730 | if (!domTree.userGivenStyles) { 731 | domTree.style.width = isNaN(domTree.style.width) ? 'auto' : domTree.style.width; 732 | domTree.style.height = isNaN(domTree.style.height) ? 'auto' : domTree.style.height; 733 | 734 | style.paddingStart = pluckNumber(style.paddingStart, style.padding, 0); 735 | style.paddingEnd = pluckNumber(style.paddingEnd, style.padding, 0); 736 | style.paddingTop = pluckNumber(style.paddingTop, style.padding, 0); 737 | style.paddingBottom = pluckNumber(style.paddingBottom, style.padding, 0); 738 | 739 | domTree.userGivenStyles = { 740 | gridTemplateColumns: domTree.style.gridTemplateColumns, 741 | gridTemplateRows: domTree.style.gridTemplateRows, 742 | width: domTree.style.width, 743 | height: domTree.style.height 744 | }; 745 | } 746 | 747 | domTree.unResolvedChildren = []; 748 | for (i = 0, len = (domTree.children && domTree.children.length); i < len; i++) { 749 | child = domTree.children[i]; 750 | if (getDisplayProperty(child)) { 751 | if (validNestedGrid(child)) { 752 | this.compute(child); 753 | } else { 754 | domTree.unResolvedChildren.push(child); 755 | } 756 | } 757 | } 758 | 759 | grid = new Grid(); 760 | grid.set('domTree', domTree) 761 | .set('parent', this) 762 | .compute(); 763 | 764 | if (count < 2) { 765 | this.gridLayoutEngine(updateDomTreeWithResolvedValues(domTree, grid), 2); 766 | } 767 | 768 | return domTree; 769 | } 770 | 771 | export { 772 | computeGridLayout 773 | }; 774 | -------------------------------------------------------------------------------- /src/grid/track-sizing.js: -------------------------------------------------------------------------------- 1 | const getMultiplierOfFr = size => +size.replace(/fr/, ''), 2 | /** 3 | * Helper function to distribute extra space among all the flexible tracks. 4 | */ 5 | _frSpaceDistributorHelper = (tracks, totalSpaceUsed, containerSize) => { 6 | let freeSpace, 7 | spacePerFrTrack, 8 | eligibleTracks, 9 | totalFrTrackRatio = 0; 10 | 11 | if (!tracks.length) { 12 | return; 13 | } 14 | 15 | tracks.forEach(track => (totalFrTrackRatio += track.multiplier)); 16 | 17 | freeSpace = containerSize - totalSpaceUsed; 18 | spacePerFrTrack = freeSpace / totalFrTrackRatio; 19 | 20 | eligibleTracks = tracks.filter(track => track.baseSize <= track.multiplier * spacePerFrTrack); 21 | 22 | if (eligibleTracks.length < tracks.length) { 23 | tracks.filter(track => track.baseSize > track.multiplier * spacePerFrTrack).forEach(track => (totalSpaceUsed += track.baseSize)); 24 | return _frSpaceDistributorHelper(eligibleTracks, totalSpaceUsed, containerSize); 25 | } else { 26 | eligibleTracks.forEach(track => (track.baseSize = track.multiplier * spacePerFrTrack)); 27 | } 28 | }, 29 | /** 30 | * Helper function to distribute extra space among all the intrinsic tracks. 31 | */ 32 | _intrinsicSpaceDistributorHelper = (tracks, totalSpaceUsed, containerSize) => { 33 | let freeSpace, 34 | spacePerIntrinsicTrack, 35 | i, 36 | len, 37 | frozenTrack = 0, 38 | minMaxTracks, 39 | growthLimit, 40 | baseSize; 41 | 42 | if (!tracks.length) { 43 | return; 44 | } 45 | minMaxTracks = tracks.filter(track => track.type === 'minmax' && track.growthLimit !== Infinity); 46 | freeSpace = containerSize - totalSpaceUsed; 47 | 48 | minMaxTracks.sort(function (a, b) { 49 | let gap1 = a.growthLimit - a.baseSize, 50 | gap2 = b.growthLimit - b.baseSize; 51 | 52 | return gap1 - gap2; 53 | }); 54 | 55 | len = minMaxTracks.length; 56 | while (frozenTrack < len && freeSpace) { 57 | spacePerIntrinsicTrack = freeSpace / ((minMaxTracks.length - frozenTrack) || 1); 58 | /** 59 | * @todo: remove the frozen tracks. 60 | */ 61 | for (i = 0, len = minMaxTracks.length; i < len; i++) { 62 | growthLimit = minMaxTracks[i].growthLimit; 63 | 64 | baseSize = Math.min(spacePerIntrinsicTrack + minMaxTracks[i].baseSize, growthLimit); 65 | freeSpace -= (baseSize - minMaxTracks[i].baseSize); 66 | minMaxTracks[i].baseSize = baseSize; 67 | 68 | if (growthLimit === baseSize && !minMaxTracks[i].frozen) { 69 | minMaxTracks[i].frozen = true; 70 | frozenTrack++; 71 | } 72 | } 73 | } 74 | 75 | tracks = tracks.filter(track => (track.type === 'minmax' && track.growthLimit === Infinity) || track.type !== 'minmax'); 76 | spacePerIntrinsicTrack = freeSpace / tracks.length; 77 | 78 | tracks.forEach(track => (track.baseSize += spacePerIntrinsicTrack)); 79 | }; 80 | 81 | /** 82 | * TrackResolver implements the standard track solving algorithm of CSS grid. 83 | * Refer https://www.w3.org/TR/css-grid-1/#algo-track-sizing 84 | * 85 | * @class TrackResolver 86 | */ 87 | class TrackResolver { 88 | constructor (tracks = [], items = [], containerSize = 600) { 89 | this.clear(); 90 | 91 | this.set('tracks', tracks); 92 | this.set('items', items); 93 | this.set('containerSize', containerSize); 94 | return this; 95 | } 96 | 97 | /** 98 | * setter method to set props 99 | * 100 | * @param {string} key 101 | * key represents the name by which the value is to be stored in props object. 102 | * @param {any} info 103 | * info is the information(can be anything) that has to be stored against the key. 104 | * @returns {TrackResolver} 105 | * Reference of the class instance. 106 | * @memberof TrackResolver 107 | */ 108 | set (key, info) { 109 | this.props[key] = info; 110 | 111 | switch (key) { 112 | case 'tracks': 113 | this._initTrackSize(); break; 114 | case 'items': 115 | this._initItems(); break; 116 | case 'containerSize': 117 | this.props[key] = isNaN(+info) ? 0 : +info; 118 | } 119 | return this; 120 | } 121 | 122 | /** 123 | * Getter method to fetch the props 124 | * 125 | * @param {string} key 126 | * key of the value which has to be fetched. 127 | * @returns {any} 128 | * alue corresponding to the key in props object 129 | * @memberof TrackResolver 130 | */ 131 | get (key) { 132 | return this.props[key]; 133 | } 134 | 135 | /** 136 | * Initializes the tracks. Both rows and columns in grid are tracks in TrackResolver. 137 | * Each track is assigned a baseSize and growthLimit. BaseSize is the minimum size that a track can take, 138 | * while growthLimit is the max size. 139 | * 140 | * Terminology: 141 | * FrTracks: Tracks which have a size definition in terms of fr(free space) 142 | * Intrinsic Tracks: Tracks which have a size definition of auto. 143 | * 144 | * @param {Array} _tracks 145 | * Array containing information about the tracks. 146 | * @returns {Array} 147 | * Array of sanitized tracks. A sanitized track consists of the following information 148 | * { 149 | * type: minmax | fixed | flex | intrinsic 150 | * minmax: track has size definition in minmax format 151 | * fixed: a fixed numeric value is provided as size definition 152 | * flex: size definition is provided in terms of fr 153 | * intrinsic: auto size definition 154 | * multiplier: Prefix of fr(2 in case of 2fr). default 1. 155 | * baseSize: lower size limit of track. 156 | * growthLimit: upper size limit of track. 157 | * } 158 | * @memberof TrackResolver 159 | */ 160 | _initTrackSize (_tracks) { 161 | let tracks = _tracks || this.props.tracks || [], 162 | config = this._config, 163 | trackAr = [{}], 164 | i, 165 | len, 166 | size, 167 | type, 168 | multiplier, 169 | baseSize, 170 | growthLimit; 171 | 172 | config.frTracks = []; 173 | config.intrinsicTracks = []; 174 | 175 | for (i = 1, len = tracks.length; i < len; i++) { 176 | size = tracks[i].size; 177 | 178 | multiplier = 1; 179 | if (Array.isArray(size)) { 180 | baseSize = +size[0] || 0; 181 | 182 | if (size[1].indexOf('fr') > 0 || size[0].indexOf('fr') > 0) { 183 | growthLimit = Infinity; 184 | config.frTracks.push(i); 185 | type = 'minmax'; 186 | } else if (size[1] === 'auto' || size[0] === 'auto') { 187 | growthLimit = Infinity; 188 | config.intrinsicTracks.push(i); 189 | type = 'minmax'; 190 | } else if (!isNaN(+size[0]) && !isNaN(+size[1])) { 191 | growthLimit = Math.max(+size[0], +size[1]); 192 | baseSize = Math.min(+size[0], +size[1]); 193 | config.intrinsicTracks.push(i); 194 | type = 'minmax'; 195 | } 196 | } else if (!isNaN(+size)) { 197 | baseSize = growthLimit = +size; 198 | type = 'fixed'; 199 | } else if (size.indexOf('fr') > 0) { 200 | baseSize = 0; 201 | growthLimit = Infinity; 202 | config.frTracks.push(i); 203 | type = 'flex'; 204 | multiplier = getMultiplierOfFr(size); 205 | } else { 206 | baseSize = 0; 207 | growthLimit = Infinity; 208 | type = 'intrinsic'; 209 | config.intrinsicTracks.push(i); 210 | } 211 | 212 | trackAr.push({ 213 | ...tracks[i], 214 | type, 215 | multiplier, 216 | baseSize, 217 | growthLimit 218 | }); 219 | } 220 | 221 | return (config.sanitizedTracks = trackAr); 222 | } 223 | 224 | /** 225 | * The size of grid items are sanitized in this method. In case the items do not have a valid size, they 226 | * take up size of the tracks 227 | * 228 | * @param {Array} _items 229 | * Array of grid items 230 | * @returns {Array} 231 | * Array of items where each item has valid size 232 | * @memberof TrackResolver 233 | */ 234 | _initItems (_items) { 235 | let items = _items || this.props.items || [], 236 | config = this._config, 237 | sanitizedItems = [], 238 | nonSpanningItemStartIndex, 239 | item, 240 | validItems = 0, 241 | i, 242 | len; 243 | 244 | for (i = 0, len = items.length; i < len; i++) { 245 | if (isNaN(items[i].start) || isNaN(items[i].end)) { 246 | config.autoFlow.push(items[i]); 247 | continue; 248 | } 249 | sanitizedItems.push({...items[i]}); 250 | 251 | item = sanitizedItems[validItems]; 252 | validItems++; 253 | 254 | item.size = isNaN(item.size) ? this._getParentSize(item) : +item.size; 255 | } 256 | 257 | sanitizedItems.sort(function (a, b) { 258 | let gap1 = a.end - a.start, 259 | gap2 = b.end - b.start; 260 | 261 | if (gap1 === gap2) { 262 | return a.start - b.start; 263 | } else { return gap1 - gap2; } 264 | }); 265 | 266 | for (i = 0, nonSpanningItemStartIndex = len = sanitizedItems.length; i < len; i++) { 267 | if (sanitizedItems[i].end - sanitizedItems[i].start > 1) { 268 | nonSpanningItemStartIndex = i; 269 | break; 270 | } 271 | } 272 | 273 | this._config.nonSpanningItemStartIndex = nonSpanningItemStartIndex; 274 | 275 | return (this._config.sanitizedItems = sanitizedItems); 276 | } 277 | 278 | /** 279 | * If any grid item do not have a valid size, then it takes up the size of the track. 280 | * 281 | * @param {Object} item 282 | * The item which do not have a proper size and will take up the size of the track. 283 | * @returns {number} 284 | * size of the track(s) which will be assigned to the grid item. 285 | * @memberof TrackResolver 286 | */ 287 | _getParentSize (item) { 288 | let { sanitizedTracks } = this._config, 289 | parentTracks, 290 | widthOfParentTracks = 0; 291 | 292 | parentTracks = sanitizedTracks.filter(track => (track.start >= item.start && track.end <= item.end)); 293 | 294 | parentTracks.forEach(track => (widthOfParentTracks += track.baseSize)); 295 | 296 | return (widthOfParentTracks || 0); 297 | } 298 | 299 | /** 300 | * resolveTracks method is called to resolve the tracks. 301 | * 302 | * Terminology: 303 | * Non-spanning items - items which is contained in a single track. 304 | * Spanning items - items which is spread across multiple tracks. 305 | * 306 | * 1. At first all the non-spanning items are placed. The tracks containing non-spanning gets a minimum size. 307 | * 2. Then the spanning items are placed. If total size of all the tracks over which the spanning items are spread is less than 308 | * the size of the spanning items, then the extra space required by the item is accomodated equally by the non-fixed tracks. 309 | * 3. Afer all the items are placed, if any free space remains, they get distributed among the non-fixed tracks. 310 | * 311 | * @returns {Array} 312 | * Array of objects where each object is a track with resolved size. 313 | * @memberof TrackResolver 314 | */ 315 | resolveTracks () { 316 | this._placeNonSpanningItems() 317 | ._placeSpanningItems() 318 | ._distributeFreeSpace(); 319 | 320 | return this._config.sanitizedTracks; 321 | } 322 | 323 | /** 324 | * Placing a non-spanning item. After placing the item if the containing track has a non-fixed size, it is increased to 325 | * accomodate the item. 326 | * 327 | * @returns {TrackResolver} 328 | * Reference of the class instance. 329 | * @memberof TrackResolver 330 | */ 331 | _placeNonSpanningItems () { 332 | let { sanitizedItems, sanitizedTracks, nonSpanningItemStartIndex } = this._config, 333 | nonSpanningItems = sanitizedItems.slice(0, nonSpanningItemStartIndex), 334 | track, 335 | trackIndex; 336 | 337 | nonSpanningItems.forEach(item => { 338 | trackIndex = item.start; 339 | track = sanitizedTracks[trackIndex]; 340 | 341 | if (track.type !== 'fixed') { 342 | track.baseSize = Math.max(track.baseSize, item.size); 343 | track.growthLimit = Math.max(track.growthLimit, track.baseSize); 344 | } 345 | }); 346 | 347 | return this; 348 | } 349 | 350 | /** 351 | * Place the non-spanning items. If the total size of all tracks on which the item is spread is less than 352 | * the size of the item, then the extra size required is accomodated by equally increasing the size of 353 | * all the non-fixed containing tracks. 354 | * 355 | * @returns {TrackResolver} 356 | * Reference of the class instance. 357 | * @memberof TrackResolver 358 | */ 359 | _placeSpanningItems () { 360 | let { sanitizedItems, sanitizedTracks, nonSpanningItemStartIndex, frTracks } = this._config, 361 | spanningItems = sanitizedItems.slice(nonSpanningItemStartIndex), 362 | trackSizedp = [0], 363 | sizeConsumed, 364 | sizeLeft, 365 | sizePerTrack, 366 | availableTracks, 367 | hasFrTrack, 368 | i, 369 | len; 370 | 371 | if (!spanningItems.length) return this; 372 | 373 | for (i = 1, len = sanitizedTracks.length; i < len; i++) { 374 | trackSizedp[i] = trackSizedp[i - 1] + (sanitizedTracks[i].baseSize || 0); 375 | } 376 | 377 | spanningItems.forEach(item => { 378 | sizeConsumed = trackSizedp[item.end - 1] - trackSizedp[item.start - 1]; 379 | sizeLeft = Math.max(0, item.size - sizeConsumed); 380 | 381 | if (!sizeLeft) return; 382 | 383 | for (i = item.start, hasFrTrack = false, availableTracks = 0; i < item.end; i++) { 384 | if (frTracks.indexOf(i) >= 0) { 385 | hasFrTrack = true; 386 | } 387 | if (sanitizedTracks[i].type !== 'fixed') { 388 | availableTracks++; 389 | } 390 | } 391 | 392 | if (!availableTracks || hasFrTrack) return; 393 | 394 | sizePerTrack = sizeLeft / availableTracks; 395 | for (i = item.start; i < item.end; i++) { 396 | if (sanitizedTracks[i].type !== 'fixed') { 397 | sanitizedTracks[i].baseSize += sizePerTrack; 398 | } 399 | } 400 | }); 401 | return this; 402 | } 403 | 404 | /** 405 | * After all the items are placed and if any free space remains, it is distributed among the tracks. 406 | * Distribution strategy depends on the track configurations. 407 | * If there are tracks with flexible size 408 | * definition(fr), then all the free space is allocated to those tracks. 409 | * If there are no tracks with flexible size definiton, then the free space is distributed 410 | * evenly among the intrinsic tracks. 411 | * If all the tracks are fixed(ie, have fixed size), then the free space is not distributed. 412 | * 413 | * @returns {TrackResolver} 414 | * Reference of the class instance. 415 | * @memberof TrackResolver 416 | */ 417 | _distributeFreeSpace () { 418 | let { frTracks, intrinsicTracks, sanitizedTracks } = this._config, 419 | { containerSize } = this.props, 420 | totalSpaceUsed = 0; 421 | 422 | sanitizedTracks.forEach(track => (totalSpaceUsed += (track.baseSize || 0))); 423 | 424 | if (totalSpaceUsed < containerSize) { 425 | if (frTracks.length) { 426 | frTracks.forEach((trackId, index) => { frTracks[index] = sanitizedTracks[trackId]; }); 427 | frTracks.forEach(track => (totalSpaceUsed -= track.baseSize)); 428 | _frSpaceDistributorHelper(frTracks, totalSpaceUsed, containerSize); 429 | } else if (intrinsicTracks.length) { 430 | intrinsicTracks.forEach((trackId, index) => { intrinsicTracks[index] = sanitizedTracks[trackId]; }); 431 | _intrinsicSpaceDistributorHelper(intrinsicTracks, totalSpaceUsed, containerSize); 432 | } 433 | } 434 | return this; 435 | } 436 | 437 | /** 438 | * clears the props and configuration of TrackResolver. This method is called before using 439 | * TrackResolver with different set of input. 440 | * 441 | * @returns {TrackResolver} 442 | * Reference of the class instance. 443 | * @memberof TrackResolver 444 | */ 445 | clear () { 446 | this.props = {}; 447 | this._config = { 448 | frTracks: [], 449 | intrinsicTracks: [], 450 | autoFlow: [] 451 | }; 452 | 453 | return this; 454 | } 455 | } 456 | 457 | export default TrackResolver; 458 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import { computeLayout } from './faber'; 2 | 3 | export { 4 | computeLayout 5 | }; 6 | -------------------------------------------------------------------------------- /src/utils/constants.js: -------------------------------------------------------------------------------- 1 | const DISPLAY_GRID = 'grid', 2 | DISPLAY_FLEX = 'flex', 3 | CENTER = 'center', 4 | START = 'start', 5 | END = 'end', 6 | STRETCH = 'stretch', 7 | ATOMIC_DATA_TYPE = ['string', 'number', 'function', 'boolean', 'undefined']; 8 | 9 | export { 10 | DISPLAY_GRID, 11 | DISPLAY_FLEX, 12 | CENTER, 13 | START, 14 | END, 15 | STRETCH, 16 | ATOMIC_DATA_TYPE 17 | }; 18 | -------------------------------------------------------------------------------- /src/utils/index.js: -------------------------------------------------------------------------------- 1 | import { ATOMIC_DATA_TYPE } from './constants'; 2 | 3 | let UNDEF; 4 | 5 | const getDisplayProperty = (domTree) => { 6 | return domTree.style && domTree.style.display; 7 | }, 8 | cloneObject = (arg) => { 9 | if ((ATOMIC_DATA_TYPE.indexOf(typeof arg) > -1) || arg === null) { 10 | return arg; 11 | } 12 | 13 | if (Array.isArray(arg)) { 14 | let i, 15 | len, 16 | arr = []; 17 | 18 | for (i = 0, len = arg.length; i < len; i++) { 19 | arr.push(cloneObject(arg[i])); 20 | } 21 | 22 | return arr; 23 | } else if (typeof arg === 'object') { 24 | let cloneObj = {}, 25 | key; 26 | 27 | for (key in arg) { 28 | cloneObj[key] = cloneObject(arg[key]); 29 | } 30 | 31 | return cloneObj; 32 | } 33 | }, 34 | attachLayoutInformation = (baseTree = {}, calculatedTree = {}) => { 35 | let i, 36 | len; 37 | 38 | baseTree.layout = calculatedTree.layout; 39 | 40 | for (i = 0, len = (baseTree.children || []).length; i < len; i++) { 41 | attachLayoutInformation(baseTree.children[i], calculatedTree.children[i]); 42 | } 43 | }, 44 | pluckNumber = function () { 45 | var arg, 46 | i, 47 | l; 48 | 49 | for (i = 0, l = arguments.length; i < l; i += 1) { 50 | arg = arguments[i]; 51 | if (!arg && arg !== false && arg !== 0) { 52 | continue; 53 | } else if (isNaN(arg = Number(arg))) { 54 | continue; 55 | } 56 | return arg; 57 | } 58 | return UNDEF; 59 | }; 60 | 61 | export { 62 | cloneObject, 63 | attachLayoutInformation, 64 | getDisplayProperty, 65 | pluckNumber 66 | }; 67 | -------------------------------------------------------------------------------- /tests/index.spec.js: -------------------------------------------------------------------------------- 1 | import { computeLayout } from "../src/faber"; 2 | import { basicTwoCrossTwoNodeWithFourChildren, testUndeclearedTracklines, testTwoCrossTwoNodesFillParent, testMultiNameTrackLines, testGridColumnGridRowProperties, isCloseTo } from "./utils"; 3 | 4 | describe('Grid Sizing test', () => { 5 | it('Grid should return layout with x,x2,y,y2,width and height', () => { 6 | const result = computeLayout(basicTwoCrossTwoNodeWithFourChildren); 7 | 8 | result.children.forEach(c => { 9 | expect(c.hasOwnProperty('layout')).toBe(true); 10 | expect(c.layout.hasOwnProperty('x')).toBe(true); 11 | expect(c.layout.hasOwnProperty('x2')).toBe(true); 12 | expect(c.layout.hasOwnProperty('y')).toBe(true); 13 | expect(c.layout.hasOwnProperty('y2')).toBe(true); 14 | expect(c.layout.hasOwnProperty('width')).toBe(true); 15 | expect(c.layout.hasOwnProperty('height')).toBe(true); 16 | }); 17 | 18 | }); 19 | 20 | it('A 2 X 2 grid with 4 children and 100 100 100 100 tracks', () => { 21 | const result = computeLayout(basicTwoCrossTwoNodeWithFourChildren), 22 | expectedGrid = [{ 23 | x: 0, 24 | x2: 100, 25 | y: 0, 26 | y2: 100 27 | }, 28 | { 29 | x: 100, 30 | x2: 200, 31 | y: 0, 32 | y2: 100 33 | }, 34 | { 35 | x: 0, 36 | x2: 100, 37 | y: 100, 38 | y2: 200 39 | }, 40 | { 41 | x: 100, 42 | x2: 200, 43 | y: 100, 44 | y2: 200 45 | }]; 46 | 47 | result.children.forEach((c, index) => { 48 | expect(c.layout.height).toBe(100); 49 | expect(c.layout.width).toBe(100); 50 | 51 | expect(c.layout.x).toBe(expectedGrid[index].x); 52 | expect(c.layout.x2).toBe(expectedGrid[index].x2); 53 | expect(c.layout.y).toBe(expectedGrid[index].y); 54 | expect(c.layout.y2).toBe(expectedGrid[index].y2); 55 | }); 56 | 57 | }); 58 | 59 | it('A 2X2 grid with 4 children without gridTemplateColumns gridTemplateRows engine should work', () => { 60 | const result = computeLayout(testUndeclearedTracklines); 61 | 62 | result.children.forEach(c => { 63 | expect(c.hasOwnProperty('layout')).toBe(true); 64 | expect(c.layout.hasOwnProperty('x')).toBe(true); 65 | expect(c.layout.hasOwnProperty('x2')).toBe(true); 66 | expect(c.layout.hasOwnProperty('y')).toBe(true); 67 | expect(c.layout.hasOwnProperty('y2')).toBe(true); 68 | expect(c.layout.hasOwnProperty('width')).toBe(true); 69 | expect(c.layout.hasOwnProperty('height')).toBe(true); 70 | }); 71 | }); 72 | 73 | it('A 2X2 grid with 4 children 200 200 200 200 without gridTemplateColumns gridTemplateRows', () => { 74 | const result = computeLayout(testTwoCrossTwoNodesFillParent), 75 | expectedGrid = [{ 76 | x: 0, 77 | x2: 200, 78 | y: 0, 79 | y2: 200 80 | }, 81 | { 82 | x: 200, 83 | x2: 400, 84 | y: 0, 85 | y2: 200 86 | }, 87 | { 88 | x: 0, 89 | x2: 200, 90 | y: 200, 91 | y2: 400 92 | }, 93 | { 94 | x: 200, 95 | x2: 400, 96 | y: 200, 97 | y2: 400 98 | }]; 99 | 100 | result.children.forEach((c, index) => { 101 | expect(c.layout.height).toBe(200); 102 | expect(c.layout.width).toBe(200); 103 | 104 | expect(c.layout.x).toBe(expectedGrid[index].x); 105 | expect(c.layout.x2).toBe(expectedGrid[index].x2); 106 | expect(c.layout.y).toBe(expectedGrid[index].y); 107 | expect(c.layout.y2).toBe(expectedGrid[index].y2); 108 | }); 109 | }); 110 | 111 | it('Grid container containing track line identified with multiple track names', () => { 112 | const result = computeLayout(testMultiNameTrackLines), 113 | expectedGrid = [{ 114 | x: 150, 115 | x2: 250, 116 | y: 150, 117 | y2: 250 118 | }, 119 | { 120 | x: 250, 121 | x2: 350, 122 | y: 50, 123 | y2: 150 124 | }, 125 | { 126 | x: 50, 127 | x2: 150, 128 | y: 250, 129 | y2: 350 130 | }, 131 | { 132 | x: 250, 133 | x2: 350, 134 | y: 250, 135 | y2: 350 136 | }]; 137 | 138 | result.children.forEach((c, index) => { 139 | expect(c.layout.height).toBe(100); 140 | expect(c.layout.width).toBe(100); 141 | 142 | expect(c.layout.x).toBe(expectedGrid[index].x); 143 | expect(c.layout.x2).toBe(expectedGrid[index].x2); 144 | expect(c.layout.y).toBe(expectedGrid[index].y); 145 | expect(c.layout.y2).toBe(expectedGrid[index].y2); 146 | }); 147 | }); 148 | 149 | it('Grid children with grid-column grid-row property', () => { 150 | const result = computeLayout(testGridColumnGridRowProperties), 151 | expectedGrid = [{ 152 | x: 150, 153 | x2: 250, 154 | y: 150, 155 | y2: 250 156 | }, 157 | { 158 | x: 250, 159 | x2: 350, 160 | y: 50, 161 | y2: 150 162 | }, 163 | { 164 | x: 50, 165 | x2: 150, 166 | y: 250, 167 | y2: 350 168 | }, 169 | { 170 | x: 250, 171 | x2: 350, 172 | y: 250, 173 | y2: 350 174 | }]; 175 | 176 | result.children.forEach((c, index) => { 177 | expect(c.layout.height).toBe(100); 178 | expect(c.layout.width).toBe(100); 179 | 180 | expect(c.layout.x).toBe(expectedGrid[index].x); 181 | expect(c.layout.x2).toBe(expectedGrid[index].x2); 182 | expect(c.layout.y).toBe(expectedGrid[index].y); 183 | expect(c.layout.y2).toBe(expectedGrid[index].y2); 184 | }); 185 | }); 186 | }); 187 | 188 | describe('Grid with non-spanning items test', () => { 189 | it('Space should be distributed equally among all auto tracks when none of the items provided any dimension', () => { 190 | let nodeTree = { 191 | style: { 192 | display: 'grid', 193 | width: 700, 194 | height: 500, 195 | gridTemplateColumns: 'auto auto auto', 196 | gridTemplateRows: 'auto auto auto' 197 | }, 198 | children: [ 199 | { 200 | style: { 201 | gridColumnStart: 1, 202 | gridColumnEnd: 2, 203 | gridRowStart: 1, 204 | gridRowEnd: 2, 205 | } 206 | }, 207 | { 208 | style: { 209 | gridColumnStart: 2, 210 | gridColumnEnd: 3, 211 | gridRowStart: 2, 212 | gridRowEnd: 3, 213 | } 214 | }, 215 | { 216 | style: { 217 | gridColumnStart: 1, 218 | gridColumnEnd: 2, 219 | gridRowStart: 1, 220 | gridRowEnd: 2, 221 | } 222 | }, 223 | { 224 | style: { 225 | gridColumnStart: 3, 226 | gridColumnEnd: 4, 227 | gridRowStart: 3, 228 | gridRowEnd: 4, 229 | } 230 | } 231 | ] 232 | }; 233 | 234 | computeLayout(nodeTree); 235 | nodeTree.children.forEach(child => { 236 | expect(isCloseTo(child.layout.width, 233, 0.35)).toBe(true); 237 | expect(isCloseTo(child.layout.height, 167, 0.35)).toBe(true); 238 | }); 239 | }); 240 | 241 | it('Tracks should consider min-content contribution of its item before deciding its size', () => { 242 | let nodeTree = { 243 | style: { 244 | display: 'grid', 245 | width: 700, 246 | height: 500, 247 | gridTemplateColumns: 'auto auto auto', 248 | gridTemplateRows: 'auto auto auto' 249 | }, 250 | children: [ 251 | { 252 | style: { 253 | gridColumnStart: 1, 254 | gridColumnEnd: 2, 255 | gridRowStart: 1, 256 | gridRowEnd: 2, 257 | width: 400 258 | } 259 | }, 260 | { 261 | style: { 262 | gridColumnStart: 2, 263 | gridColumnEnd: 3, 264 | gridRowStart: 1, 265 | gridRowEnd: 2, 266 | width: 100 267 | } 268 | }, 269 | { 270 | style: { 271 | gridColumnStart: 3, 272 | gridColumnEnd: 4, 273 | gridRowStart: 1, 274 | gridRowEnd: 2, 275 | width: 140 276 | } 277 | }, 278 | { 279 | style: { 280 | gridColumnStart: 1, 281 | gridColumnEnd: 2, 282 | gridRowStart: 1, 283 | gridRowEnd: 2 284 | } 285 | }, 286 | { 287 | style: { 288 | gridColumnStart: 2, 289 | gridColumnEnd: 3, 290 | gridRowStart: 1, 291 | gridRowEnd: 2 292 | } 293 | }, 294 | { 295 | style: { 296 | gridColumnStart: 3, 297 | gridColumnEnd: 4, 298 | gridRowStart: 1, 299 | gridRowEnd: 2 300 | } 301 | } 302 | ] 303 | }, 304 | expectedOutput = [400, 100, 140, 420, 120, 160]; 305 | 306 | computeLayout(nodeTree); 307 | nodeTree.children.forEach((child, index) => { 308 | expect(isCloseTo(child.layout.width, expectedOutput[index])).toBe(true); 309 | }); 310 | }); 311 | }); 312 | 313 | describe('Grid autoFlow test', () => { 314 | it('GridMatrix should create proper no of rows and columns and should not throw error', () => { 315 | let nodeTree = { 316 | style: { 317 | display: 'grid', 318 | width: 700, 319 | height: 500 320 | }, 321 | children: [ 322 | { 323 | style: { 324 | gridColumnStart: 1, 325 | gridColumnEnd: 2, 326 | gridRowStart: 1, 327 | gridRowEnd: 2 328 | } 329 | }, 330 | { 331 | style: { 332 | gridColumnStart: 2, 333 | gridColumnEnd: 3, 334 | gridRowStart: 1, 335 | gridRowEnd: 2 336 | } 337 | }, 338 | { 339 | style: { 340 | gridColumnStart: 3, 341 | gridColumnEnd: 4, 342 | gridRowStart: 1, 343 | gridRowEnd: 2 344 | } 345 | }, 346 | { 347 | style: { 348 | gridColumnStart: 4, 349 | gridColumnEnd: 5, 350 | gridRowStart: 1, 351 | gridRowEnd: 2 352 | } 353 | }, 354 | { 355 | style: { 356 | gridColumnStart: 5, 357 | gridColumnEnd: 6, 358 | gridRowStart: 1, 359 | gridRowEnd: 2 360 | } 361 | }, 362 | { 363 | style: { 364 | gridColumnStart: 6, 365 | gridColumnEnd: 7, 366 | gridRowStart: 1, 367 | gridRowEnd: 2 368 | } 369 | } 370 | ] 371 | }; 372 | computeLayout(nodeTree); 373 | }); 374 | }); 375 | -------------------------------------------------------------------------------- /tests/utils.js: -------------------------------------------------------------------------------- 1 | const basicTwoCrossTwoNodeWithFourChildren = { 2 | style: { 3 | display: 'grid', 4 | width: 400, 5 | height: 400, 6 | justifyItems: 'center', 7 | gridTemplateRows: '[one] 100 [two] 100 [three]', 8 | gridTemplateColumns: '[one] 100 [two] 100 [three]', 9 | }, 10 | children: [ 11 | { 12 | style: { 13 | width: 100, 14 | height: '100', 15 | gridRowStart: 'one', 16 | gridRowEnd: 'two', 17 | gridColumnStart: 'one', 18 | gridColumnEnd: 'two' 19 | } 20 | }, 21 | { 22 | style: { 23 | width: 100, 24 | height: '100', 25 | gridRowStart: 'one', 26 | gridRowEnd: 'two', 27 | gridColumnStart: 'two', 28 | gridColumnEnd: '3' 29 | } 30 | }, 31 | { 32 | style: { 33 | width: 100, 34 | height: '100', 35 | gridRowStart: '2', 36 | gridRowEnd: '3', 37 | gridColumnStart: '1', 38 | gridColumnEnd: '2' 39 | } 40 | }, 41 | { 42 | style: { 43 | width: 100, 44 | height: '100', 45 | gridRowStart: 'two', 46 | gridRowEnd: 'three', 47 | gridColumnStart: 'two', 48 | gridColumnEnd: 'three' 49 | } 50 | } 51 | ] 52 | }; 53 | 54 | const testUndeclearedTracklines = { 55 | style: { 56 | display: 'grid', 57 | width: 400, 58 | height: 400, 59 | justifyItems: 'center' 60 | }, 61 | children: [ 62 | { 63 | style: { 64 | width: 100, 65 | height: '100', 66 | gridRowStart: '1', 67 | gridRowEnd: '2', 68 | gridColumnStart: '1', 69 | gridColumnEnd: '2' 70 | } 71 | }, 72 | { 73 | style: { 74 | width: 100, 75 | height: '100', 76 | gridRowStart: 'one', 77 | gridRowEnd: 'two', 78 | gridColumnStart: 'two', 79 | gridColumnEnd: '3' 80 | } 81 | }, 82 | { 83 | style: { 84 | width: 100, 85 | height: '100', 86 | gridRowStart: '2', 87 | gridRowEnd: '3', 88 | gridColumnStart: '1', 89 | gridColumnEnd: '2' 90 | } 91 | }, 92 | { 93 | style: { 94 | width: 100, 95 | height: '100', 96 | gridRowStart: 'two', 97 | gridRowEnd: 'three', 98 | gridColumnStart: 'two', 99 | gridColumnEnd: 'three' 100 | } 101 | } 102 | ] 103 | }; 104 | 105 | const testTwoCrossTwoNodesFillParent = { 106 | style: { 107 | display: 'grid', 108 | width: 400, 109 | height: 400, 110 | justifyItems: 'center' 111 | }, 112 | children: [ 113 | { 114 | style: { 115 | width: 200, 116 | height: '200', 117 | gridRowStart: '1', 118 | gridRowEnd: '2', 119 | gridColumnStart: '1', 120 | gridColumnEnd: '2' 121 | } 122 | }, 123 | { 124 | style: { 125 | width: 200, 126 | height: '200', 127 | gridRowStart: '1', 128 | gridRowEnd: '2', 129 | gridColumnStart: '2', 130 | gridColumnEnd: '3' 131 | } 132 | }, 133 | { 134 | style: { 135 | width: 200, 136 | height: '200', 137 | gridRowStart: '2', 138 | gridRowEnd: '3', 139 | gridColumnStart: '1', 140 | gridColumnEnd: '2' 141 | } 142 | }, 143 | { 144 | style: { 145 | width: 200, 146 | height: '200', 147 | gridRowStart: '2', 148 | gridRowEnd: '3', 149 | gridColumnStart: '2', 150 | gridColumnEnd: '3' 151 | } 152 | } 153 | ] 154 | }; 155 | 156 | const testMultiNameTrackLines = { 157 | style: { 158 | display: 'grid', 159 | width: 400, 160 | height: 400, 161 | justifyItems: 'center', 162 | alignItems: 'center', 163 | gridTemplateRows: '[one] 200px [two] 200px [three three-dash]', 164 | gridTemplateColumns: '[one] 200px [two] 200px [three three-dash]', 165 | templateRowsHTML: ['[one]', '200px', '[two]', '200px', '[three]'], 166 | templateColumnsHTML: ['[one]', '200px', '[two]', '200px', '[three three-dash]'] 167 | }, 168 | children: [ 169 | { 170 | style: { 171 | width: 100, 172 | height: '100', 173 | gridRowStart: '1', 174 | gridRowEnd: 'three', 175 | gridColumnStart: '1', 176 | gridColumnEnd: 'three-dash' 177 | } 178 | }, 179 | { 180 | style: { 181 | width: 100, 182 | height: '100', 183 | gridRowStart: '1', 184 | gridRowEnd: '2', 185 | gridColumnStart: '2', 186 | gridColumnEnd: '3' 187 | } 188 | }, 189 | { 190 | style: { 191 | width: 100, 192 | height: '100', 193 | gridRowStart: '2', 194 | gridRowEnd: '3', 195 | gridColumnStart: '1', 196 | gridColumnEnd: '2' 197 | } 198 | }, 199 | { 200 | style: { 201 | width: 100, 202 | height: '100', 203 | gridRowStart: '2', 204 | gridRowEnd: '3', 205 | gridColumnStart: '2', 206 | gridColumnEnd: '3' 207 | } 208 | } 209 | ] 210 | }; 211 | 212 | const testGridColumnGridRowProperties = { 213 | style: { 214 | display: 'grid', 215 | width: 400, 216 | height: 400, 217 | justifyItems: 'center', 218 | alignItems: 'center', 219 | gridTemplateRows: '[one] 200px [two] 200px [three three-dash]', 220 | gridTemplateColumns: '[one] 200px [two] 200px [three three-dash]', 221 | templateRowsHTML: ['[one]', '200px', '[two]', '200px', '[three three-dash]'], 222 | templateColumnsHTML: ['[one]', '200px', '[two]', '200px', '[three three-dash]'] 223 | }, 224 | children: [ 225 | { 226 | style: { 227 | width: 100, 228 | height: '100', 229 | gridRow: '1 / span 2', 230 | gridColumn: '1 / three-dash' 231 | } 232 | }, 233 | { 234 | style: { 235 | width: 100, 236 | height: '100', 237 | gridRow: '1 / 2', 238 | gridColumnStart: '2', 239 | gridColumnEnd: '3' 240 | } 241 | }, 242 | { 243 | style: { 244 | width: 100, 245 | height: '100', 246 | gridRowStart: '2', 247 | gridRowEnd: '3', 248 | gridColumn: '1 / 2' 249 | } 250 | }, 251 | { 252 | style: { 253 | width: 100, 254 | height: '100', 255 | gridRow: '2 / three', 256 | gridColumn: 'two / 3' 257 | } 258 | } 259 | ] 260 | }; 261 | 262 | const isCloseTo = (arg1, arg2, maxBuffer = 0) => { 263 | if (Math.abs(arg1 - arg2) <= maxBuffer) { 264 | return true; 265 | } 266 | return false; 267 | }; 268 | 269 | export { 270 | basicTwoCrossTwoNodeWithFourChildren, 271 | testUndeclearedTracklines, 272 | testTwoCrossTwoNodesFillParent, 273 | testMultiNameTrackLines, 274 | testGridColumnGridRowProperties, 275 | isCloseTo 276 | }; 277 | -------------------------------------------------------------------------------- /visualiser/css-faber-compartor.js: -------------------------------------------------------------------------------- 1 | const chart = { 2 | style: { 3 | display: 'grid', 4 | width: 400, 5 | height: 400, 6 | justifyItems: 'center', 7 | //gridTemplateRows: '[one] 100 [two] 100 [three]', 8 | //gridTemplateColumns: '[one] 100 [two] 100 [three]', 9 | templateRowsHTML: ['none'], 10 | templateColumnsHTML: ['none'] 11 | }, 12 | children: [ 13 | { 14 | style: { 15 | width: 100, 16 | height: '100', 17 | gridRowStart: '1', 18 | gridRowEnd: '2', 19 | gridColumnStart: '1', 20 | gridColumnEnd: '2' 21 | } 22 | }, 23 | { 24 | style: { 25 | width: 100, 26 | height: '100', 27 | gridRowStart: '1', 28 | gridRowEnd: '2', 29 | gridColumnStart: '2', 30 | gridColumnEnd: '3' 31 | } 32 | }, 33 | { 34 | style: { 35 | width: 100, 36 | height: '100', 37 | gridRowStart: '2', 38 | gridRowEnd: '3', 39 | gridColumnStart: '1', 40 | gridColumnEnd: '2' 41 | } 42 | }, 43 | { 44 | style: { 45 | width: 100, 46 | height: '100', 47 | gridRowStart: '2', 48 | gridRowEnd: '3', 49 | gridColumnStart: '2', 50 | gridColumnEnd: '3' 51 | } 52 | } 53 | ] 54 | }; 55 | 56 | const chart2 = { 57 | style: { 58 | display: 'grid', 59 | width: 700, 60 | height: 400, 61 | justifyItems: 'center', 62 | // gridTemplateRows: ['50', '1fr', '100'], 63 | //gridTemplateRows: '[one] auto [two] auto [three]', 64 | templateRowsHTML: ['50px', '1fr', '100px'], 65 | // gridTemplateColumns: ['50', '1fr', '100'], 66 | //gridTemplateColumns: '[one] auto [two] auto [three] auto [four]', 67 | templateColumnsHTML: ['50px', '1fr', '100px'] 68 | }, 69 | children: [ 70 | { 71 | style: { 72 | width: 100, 73 | height: 'auto', 74 | gridRowStart: 'one', 75 | gridRowEnd: 'two', 76 | gridColumnStart: 'one', 77 | gridColumnEnd: 'two' 78 | } 79 | }, 80 | { 81 | style: { 82 | width: 'auto', 83 | height: 'auto', 84 | display: 'grid', 85 | gridTemplateRows: '50', 86 | templateRowsHTML: ['50px'], 87 | gridTemplateColumns: 'auto auto', 88 | templateColumnsHTML: ['50px', '100px'], 89 | gridRowStart: 1, 90 | gridRowEnd: 2, 91 | gridColumnStart: 2, 92 | gridColumnEnd: 3, 93 | }, 94 | children: [ 95 | { 96 | style: { 97 | width: 'auto', 98 | height: 'auto', 99 | gridRowStart: 1, 100 | gridRowEnd: 2, 101 | gridColumnStart: 1, 102 | gridColumnEnd: 2 103 | } 104 | }, 105 | { 106 | style: { 107 | width: 'auto', 108 | height: 'auto', 109 | gridRowStart: 1, 110 | gridRowEnd: 2, 111 | gridColumnStart: 2, 112 | gridColumnEnd: 3 113 | } 114 | } 115 | ] 116 | }, 117 | { 118 | style: { 119 | width: 'auto', 120 | height: 50, 121 | gridRowStart: 2, 122 | gridRowEnd: 'three', 123 | gridColumnStart: 3, 124 | gridColumnEnd: 4 125 | } 126 | }, 127 | // { 128 | // style: { 129 | // width: 50, 130 | // height: 150, 131 | // gridRowStart: 2, 132 | // gridRowEnd: 3, 133 | // gridColumnStart: 1, 134 | // gridColumnEnd: 2 135 | // } 136 | // }, 137 | // { 138 | // style: { 139 | // width: 150, 140 | // height: 150, 141 | // gridRowStart: 2, 142 | // gridRowEnd: 3, 143 | // gridColumnStart: 2, 144 | // gridColumnEnd: 3 145 | // } 146 | // }, 147 | // { 148 | // style: { 149 | // width: 100, 150 | // height: 150, 151 | // gridRowStart: 2, 152 | // gridRowEnd: 3, 153 | // gridColumnStart: 3, 154 | // gridColumnEnd: 4 155 | // } 156 | // }, 157 | // { 158 | // style: { 159 | // width: 50, 160 | // height: 100, 161 | // gridRowStart: 3, 162 | // gridRowEnd: 4, 163 | // gridColumnStart: 1, 164 | // gridColumnEnd: 2 165 | // } 166 | // }, 167 | // { 168 | // style: { 169 | // width: 150, 170 | // height: 100, 171 | // gridRowStart: 3, 172 | // gridRowEnd: 4, 173 | // gridColumnStart: 2, 174 | // gridColumnEnd: 3 175 | // } 176 | // }, 177 | // { 178 | // style: { 179 | // width: 'auto', 180 | // height: 'auto', 181 | // gridRowStart: 3, 182 | // gridRowEnd: 4, 183 | // gridColumnStart: 3, 184 | // gridColumnEnd: 4 185 | // } 186 | // } 187 | ], 188 | }; 189 | 190 | console.log(faber.computeLayout(chart)); 191 | 192 | function getHTMLCSSInlined () { 193 | const htmlContainerEl = document.getElementsByClassName('html')[0]; 194 | const faberContainerEl = document.getElementsByClassName('faber')[0]; 195 | htmlContainerEl.style.display = 'grid'; 196 | htmlContainerEl.style.gridTemplateColumns = chart.style.templateColumnsHTML.join(" "); 197 | htmlContainerEl.style.gridTemplateRows = chart.style.templateRowsHTML.join(" "); 198 | htmlContainerEl.style.justifyItems = chart.style.justifyItems; 199 | htmlContainerEl.style.alignItems = chart.style.alignItems; 200 | htmlContainerEl.style.width = chart.style.width; 201 | htmlContainerEl.style.height = chart.style.height; 202 | 203 | faberContainerEl.style.width = chart.style.width; 204 | faberContainerEl.style.height = chart.style.height; 205 | faberContainerEl.style.position = 'relative'; 206 | 207 | chart.children.forEach(child => { 208 | const childEl = document.createElement('div'); 209 | childEl.classList.add('bordered'); 210 | childEl.style.width = child.style.width; 211 | childEl.style.height = child.style.height; 212 | childEl.style.position = 'absolute'; 213 | childEl.style.left = child.layout.x; 214 | childEl.style.top = child.layout.y; 215 | faberContainerEl.appendChild(childEl); 216 | }); 217 | 218 | chart.children.forEach(child => { 219 | const childEl = document.createElement('div'); 220 | childEl.classList.add('bordered'); 221 | childEl.style.width = child.style.width; 222 | childEl.style.height = child.style.height; 223 | childEl.style.gridColumnStart = child.style.gridColumnStart; 224 | childEl.style.gridColumnEnd = child.style.gridColumnEnd; 225 | childEl.style.gridRowStart = child.style.gridRowStart; 226 | childEl.style.gridRowEnd = child.style.gridRowEnd; 227 | htmlContainerEl.appendChild(childEl); 228 | }); 229 | } 230 | -------------------------------------------------------------------------------- /visualiser/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 29 | 30 | 31 | 32 |

Do not fear, faber is here!

33 | 34 |
35 |
36 |
37 | 38 |
39 |
40 |
41 | 42 | 43 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const webpack = require('webpack'); 3 | 4 | let libraryName = 'faber', 5 | fileName = 'faber', 6 | mode = process.argv[2].slice(2); 7 | 8 | if (mode === 'prod') { 9 | fileName += '.min.js'; 10 | mode = 'production'; 11 | } else { 12 | fileName += '.js'; 13 | mode = 'development'; 14 | } 15 | 16 | module.exports = { 17 | entry: './src/index.js', 18 | output: { 19 | filename: fileName, 20 | path: path.resolve(__dirname, 'dist'), 21 | library: libraryName, 22 | libraryTarget: 'umd' 23 | }, 24 | devtool: 'source-map', 25 | devServer: { 26 | contentBase: path.resolve(__dirname, ''), 27 | compress: true, 28 | open: true, 29 | hot: true, 30 | port: 9000 31 | }, 32 | module: { 33 | rules: [{ 34 | test: /\.js$/, 35 | loader: 'babel-loader', 36 | query: { 37 | presets: ['@babel/preset-env'] 38 | } 39 | }] 40 | }, 41 | mode: mode 42 | }; --------------------------------------------------------------------------------