diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 2816a36f4fa5..0652827a898c 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -14,6 +14,6 @@ Describe the fix you have made as succinctly as possible. I hereby swear that: - [ ] I opened a hydrogen project and it loaded -- [ ] I could navigate to various routes in Preview mode +- [ ] I could navigate to various routes in Play mode Fixes #[ticket_number] (<< pls delete this line if it's not relevant) diff --git a/.github/workflows/pull-requests.yml b/.github/workflows/pull-requests.yml index df6622984508..92d538ee67b5 100644 --- a/.github/workflows/pull-requests.yml +++ b/.github/workflows/pull-requests.yml @@ -255,7 +255,7 @@ jobs: issue-number: ${{ github.event.pull_request.number }} edit-mode: replace body: | - ## [Try me](https://${{ secrets.STAGING_SERVER }}/p/?accessLevel=public&branch_name=${{ steps.extract_branch.outputs.branch }}) + ## [Try me](https://${{ secrets.STAGING_SERVER }}/p/?accessLevel=public&clone=concrete-utopia/hydrogen-editions-24&branch_name=${{ steps.extract_branch.outputs.branch }}) performance-test: name: Run Performance Tests diff --git a/ThirdPartyNotices.txt b/ThirdPartyNotices.txt new file mode 100644 index 000000000000..81a8cc0bda74 --- /dev/null +++ b/ThirdPartyNotices.txt @@ -0,0 +1,17465 @@ +THIRD-PARTY SOFTWARE NOTICES AND INFORMATION + +This repository incorporates material as listed below or described in the code. + +======================================================================== + +@ampproject/remapping 2.2.1 +-- +Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + 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 + + http://www.apache.org/licenses/LICENSE-2.0 + + 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. + +======================================================================== + +@babel/code-frame 7.23.5 +-- +MIT License + +Copyright (c) 2014-present Sebastian McKenzie and other contributors + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +======================================================================== + +@babel/compat-data 7.23.5 +-- +MIT License + +Copyright (c) 2014-present Sebastian McKenzie and other contributors + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +======================================================================== + +@babel/core 7.23.6 +-- +MIT License + +Copyright (c) 2014-present Sebastian McKenzie and other contributors + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +======================================================================== + +@babel/generator 7.23.6 +-- +MIT License + +Copyright (c) 2014-present Sebastian McKenzie and other contributors + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +======================================================================== + +@babel/helper-annotate-as-pure 7.22.5 +-- +MIT License + +Copyright (c) 2014-present Sebastian McKenzie and other contributors + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +======================================================================== + +@babel/helper-compilation-targets 7.23.6 +-- +MIT License + +Copyright (c) 2014-present Sebastian McKenzie and other contributors + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +======================================================================== + +@babel/helper-create-class-features-plugin 7.23.6 +-- +MIT License + +Copyright (c) 2014-present Sebastian McKenzie and other contributors + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +======================================================================== + +@babel/helper-environment-visitor 7.22.20 +-- +MIT License + +Copyright (c) 2014-present Sebastian McKenzie and other contributors + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +======================================================================== + +@babel/helper-function-name 7.23.0 +-- +MIT License + +Copyright (c) 2014-present Sebastian McKenzie and other contributors + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +======================================================================== + +@babel/helper-hoist-variables 7.22.5 +-- +MIT License + +Copyright (c) 2014-present Sebastian McKenzie and other contributors + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +======================================================================== + +@babel/helper-member-expression-to-functions 7.23.0 +-- +MIT License + +Copyright (c) 2014-present Sebastian McKenzie and other contributors + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +======================================================================== + +@babel/helper-module-imports 7.22.15 +-- +MIT License + +Copyright (c) 2014-present Sebastian McKenzie and other contributors + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +======================================================================== + +@babel/helper-module-transforms 7.23.3 +-- +MIT License + +Copyright (c) 2014-present Sebastian McKenzie and other contributors + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +======================================================================== + +@babel/helper-optimise-call-expression 7.22.5 +-- +MIT License + +Copyright (c) 2014-present Sebastian McKenzie and other contributors + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +======================================================================== + +@babel/helper-plugin-utils 7.22.5 +-- +MIT License + +Copyright (c) 2014-present Sebastian McKenzie and other contributors + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +======================================================================== + +@babel/helper-replace-supers 7.22.20 +-- +MIT License + +Copyright (c) 2014-present Sebastian McKenzie and other contributors + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +======================================================================== + +@babel/helper-simple-access 7.22.5 +-- +MIT License + +Copyright (c) 2014-present Sebastian McKenzie and other contributors + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +======================================================================== + +@babel/helper-skip-transparent-expression-wrappers 7.22.5 +-- +MIT License + +Copyright (c) 2014-present Sebastian McKenzie and other contributors + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +======================================================================== + +@babel/helper-split-export-declaration 7.22.6 +-- +MIT License + +Copyright (c) 2014-present Sebastian McKenzie and other contributors + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +======================================================================== + +@babel/helper-string-parser 7.23.4 +-- +MIT License + +Copyright (c) 2014-present Sebastian McKenzie and other contributors + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +======================================================================== + +@babel/helper-validator-identifier 7.22.20 +-- +MIT License + +Copyright (c) 2014-present Sebastian McKenzie and other contributors + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +======================================================================== + +@babel/helper-validator-option 7.23.5 +-- +MIT License + +Copyright (c) 2014-present Sebastian McKenzie and other contributors + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +======================================================================== + +@babel/helpers 7.23.6 +-- +MIT License + +Copyright (c) 2014-present Sebastian McKenzie and other contributors + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +======================================================================== + +@babel/highlight 7.23.4 +-- +MIT License + +Copyright (c) 2014-present Sebastian McKenzie and other contributors + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +======================================================================== + +@babel/parser 7.24.5 +-- +Copyright (C) 2012-2014 by various contributors (see AUTHORS) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +======================================================================== + +@babel/plugin-proposal-class-properties 7.16.7 +-- +MIT License + +Copyright (c) 2014-present Sebastian McKenzie and other contributors + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +======================================================================== + +@babel/plugin-proposal-export-namespace-from 7.14.5 +-- +MIT License + +Copyright (c) 2014-present Sebastian McKenzie and other contributors + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +======================================================================== + +@babel/plugin-syntax-export-namespace-from 7.8.3 +-- +MIT License + +Copyright (c) 2014-present Sebastian McKenzie and other contributors + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +======================================================================== + +@babel/plugin-transform-modules-commonjs 7.15.4 +-- +MIT License + +Copyright (c) 2014-present Sebastian McKenzie and other contributors + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +======================================================================== + +@babel/runtime 7.15.4 +-- +MIT License + +Copyright (c) 2014-present Sebastian McKenzie and other contributors + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +======================================================================== + +@babel/runtime-corejs3 7.15.4 +-- +MIT License + +Copyright (c) 2014-present Sebastian McKenzie and other contributors + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +======================================================================== + +@babel/standalone 7.15.7 +-- +MIT License + +Copyright (c) 2014-present Sebastian McKenzie and other contributors + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +======================================================================== + +@babel/template 7.22.15 +-- +MIT License + +Copyright (c) 2014-present Sebastian McKenzie and other contributors + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +======================================================================== + +@babel/traverse 7.23.6 +-- +MIT License + +Copyright (c) 2014-present Sebastian McKenzie and other contributors + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +======================================================================== + +@babel/types 7.23.6 +-- +MIT License + +Copyright (c) 2014-present Sebastian McKenzie and other contributors + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +======================================================================== + +@emotion/cache 10.0.29 +-- +MIT License + +Copyright (c) Emotion team and other contributors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +======================================================================== + +@emotion/core 10.1.1 +-- +MIT License + +Copyright (c) Emotion team and other contributors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +======================================================================== + +@emotion/css 10.0.27 +-- +MIT License + +Copyright (c) Emotion team and other contributors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +======================================================================== + +@emotion/hash 0.8.0 +-- +MIT License + +Copyright (c) Emotion team and other contributors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +======================================================================== + +@emotion/is-prop-valid 1.1.0 +-- +MIT License + +Copyright (c) Emotion team and other contributors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +======================================================================== + +@emotion/memoize 0.7.4 +-- +MIT License + +Copyright (c) Emotion team and other contributors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +======================================================================== + +@emotion/react 11.1.2 +-- +MIT License + +Copyright (c) Emotion team and other contributors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +======================================================================== + +@emotion/serialize 0.11.16 +-- +MIT License + +Copyright (c) Emotion team and other contributors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +======================================================================== + +@emotion/sheet 0.9.4 +-- +MIT License + +Copyright (c) Emotion team and other contributors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +======================================================================== + +@emotion/styled 11.0.0 +-- +MIT License + +Copyright (c) Emotion team and other contributors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +======================================================================== + +@emotion/stylis 0.8.5 +-- +MIT License + +Copyright (c) Emotion team and other contributors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +======================================================================== + +@emotion/unitless 0.7.5 +-- +MIT License + +Copyright (c) Emotion team and other contributors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +======================================================================== + +@emotion/utils 0.11.3 +-- +MIT License + +Copyright (c) Emotion team and other contributors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +======================================================================== + +@emotion/weak-memoize 0.2.5 +-- +MIT License + +Copyright (c) Emotion team and other contributors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +======================================================================== + +@floating-ui/core 1.5.0 +-- +MIT License + +Copyright (c) 2021-present Floating UI contributors + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +======================================================================== + +@floating-ui/dom 1.5.3 +-- +MIT License + +Copyright (c) 2021-present Floating UI contributors + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +======================================================================== + +@floating-ui/react-dom 2.0.4 +-- +MIT License + +Copyright (c) 2021-present Floating UI contributors + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +======================================================================== + +@floating-ui/utils 0.1.4 +-- +MIT License + +Copyright (c) 2021-present Floating UI contributors + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +======================================================================== + +@jridgewell/gen-mapping 0.3.3 +-- +Copyright 2022 Justin Ridgewell + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +======================================================================== + +@jridgewell/resolve-uri 3.1.0 +-- +Copyright 2019 Justin Ridgewell + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +======================================================================== + +@jridgewell/set-array 1.1.2 +-- +Copyright 2022 Justin Ridgewell + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +======================================================================== + +@jridgewell/sourcemap-codec 1.4.15 +-- +The MIT License + +Copyright (c) 2015 Rich Harris + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +======================================================================== + +@jridgewell/trace-mapping 0.3.18 +-- +Copyright 2022 Justin Ridgewell + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +======================================================================== + +@juggle/resize-observer 3.4.0 +-- +Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2019 JUGGLE LTD + + 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 + + http://www.apache.org/licenses/LICENSE-2.0 + + 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. + +======================================================================== + +@liveblocks/core 1.10.0 +-- +Licensed under Apache-2.0 (https://spdx.org/licenses/Apache-2.0.html) + +======================================================================== + +@liveblocks/react 1.10.0 +-- +Licensed under Apache-2.0 (https://spdx.org/licenses/Apache-2.0.html) + +======================================================================== + +@liveblocks/react-comments 1.10.0 +-- +Licensed under Apache-2.0 (https://spdx.org/licenses/Apache-2.0.html) + +======================================================================== + +@liveblocks/yjs 1.10.0 +-- +Licensed under Apache-2.0 (https://spdx.org/licenses/Apache-2.0.html) + +======================================================================== + +@popperjs/core 2.4.4 +-- +The MIT License (MIT) + +Copyright (c) 2019 Federico Zivolo + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +======================================================================== + +@radix-ui/number 1.1.0 +-- +Licensed under MIT (https://spdx.org/licenses/MIT.html) + +======================================================================== + +@radix-ui/primitive 1.0.1 +-- +Licensed under MIT (https://spdx.org/licenses/MIT.html) + +======================================================================== + +@radix-ui/react-arrow 1.1.0 +-- +Licensed under MIT (https://spdx.org/licenses/MIT.html) + +======================================================================== + +@radix-ui/react-collection 1.1.0 +-- +Licensed under MIT (https://spdx.org/licenses/MIT.html) + +======================================================================== + +@radix-ui/react-compose-refs 1.0.1 +-- +Licensed under MIT (https://spdx.org/licenses/MIT.html) + +======================================================================== + +@radix-ui/react-context 1.0.1 +-- +Licensed under MIT (https://spdx.org/licenses/MIT.html) + +======================================================================== + +@radix-ui/react-direction 1.1.0 +-- +Licensed under MIT (https://spdx.org/licenses/MIT.html) + +======================================================================== + +@radix-ui/react-dismissable-layer 1.0.5 +-- +Licensed under MIT (https://spdx.org/licenses/MIT.html) + +======================================================================== + +@radix-ui/react-dropdown-menu 2.1.1 +-- +Licensed under MIT (https://spdx.org/licenses/MIT.html) + +======================================================================== + +@radix-ui/react-focus-guards 1.0.1 +-- +Licensed under MIT (https://spdx.org/licenses/MIT.html) + +======================================================================== + +@radix-ui/react-focus-scope 1.0.4 +-- +Licensed under MIT (https://spdx.org/licenses/MIT.html) + +======================================================================== + +@radix-ui/react-id 1.0.1 +-- +Licensed under MIT (https://spdx.org/licenses/MIT.html) + +======================================================================== + +@radix-ui/react-menu 2.1.1 +-- +Licensed under MIT (https://spdx.org/licenses/MIT.html) + +======================================================================== + +@radix-ui/react-popover 1.0.7 +-- +Licensed under MIT (https://spdx.org/licenses/MIT.html) + +======================================================================== + +@radix-ui/react-popper 1.1.3 +-- +Licensed under MIT (https://spdx.org/licenses/MIT.html) + +======================================================================== + +@radix-ui/react-portal 1.0.4 +-- +Licensed under MIT (https://spdx.org/licenses/MIT.html) + +======================================================================== + +@radix-ui/react-presence 1.0.1 +-- +Licensed under MIT (https://spdx.org/licenses/MIT.html) + +======================================================================== + +@radix-ui/react-primitive 1.0.3 +-- +Licensed under MIT (https://spdx.org/licenses/MIT.html) + +======================================================================== + +@radix-ui/react-roving-focus 1.1.0 +-- +Licensed under MIT (https://spdx.org/licenses/MIT.html) + +======================================================================== + +@radix-ui/react-select 2.1.1 +-- +Licensed under MIT (https://spdx.org/licenses/MIT.html) + +======================================================================== + +@radix-ui/react-slot 1.0.2 +-- +Licensed under MIT (https://spdx.org/licenses/MIT.html) + +======================================================================== + +@radix-ui/react-toggle 1.0.3 +-- +Licensed under MIT (https://spdx.org/licenses/MIT.html) + +======================================================================== + +@radix-ui/react-tooltip 1.0.7 +-- +Licensed under MIT (https://spdx.org/licenses/MIT.html) + +======================================================================== + +@radix-ui/react-use-callback-ref 1.0.1 +-- +Licensed under MIT (https://spdx.org/licenses/MIT.html) + +======================================================================== + +@radix-ui/react-use-controllable-state 1.0.1 +-- +Licensed under MIT (https://spdx.org/licenses/MIT.html) + +======================================================================== + +@radix-ui/react-use-escape-keydown 1.0.3 +-- +Licensed under MIT (https://spdx.org/licenses/MIT.html) + +======================================================================== + +@radix-ui/react-use-layout-effect 1.0.1 +-- +Licensed under MIT (https://spdx.org/licenses/MIT.html) + +======================================================================== + +@radix-ui/react-use-previous 1.1.0 +-- +Licensed under MIT (https://spdx.org/licenses/MIT.html) + +======================================================================== + +@radix-ui/react-use-size 1.0.1 +-- +Licensed under MIT (https://spdx.org/licenses/MIT.html) + +======================================================================== + +@radix-ui/react-visually-hidden 1.0.3 +-- +Licensed under MIT (https://spdx.org/licenses/MIT.html) + +======================================================================== + +@react-spring/animated 9.0.0-rc.3 +-- +MIT License + +Copyright (c) 2018-present Paul Henschel, Alec Larson + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +======================================================================== + +@react-spring/core 9.0.0-rc.3 +-- +MIT License + +Copyright (c) 2018-present Paul Henschel, Alec Larson + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +======================================================================== + +@react-spring/shared 9.0.0-rc.3 +-- +MIT License + +Copyright (c) 2018-present Paul Henschel, Alec Larson + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +======================================================================== + +@react-spring/web 9.0.0-rc.3 +-- +MIT License + +Copyright (c) 2018-present Paul Henschel, Alec Larson + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +======================================================================== + +@remix-run/react 2.0.1 +-- +MIT License + +Copyright (c) Remix Software Inc. 2020-2021 +Copyright (c) Shopify Inc. 2022-2023 + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +======================================================================== + +@remix-run/router 1.13.0 +-- +MIT License + +Copyright (c) React Training LLC 2015-2019 +Copyright (c) Remix Software Inc. 2020-2021 +Copyright (c) Shopify Inc. 2022-2023 + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +======================================================================== + +@remix-run/server-runtime 2.3.1 +-- +MIT License + +Copyright (c) Remix Software Inc. 2020-2021 +Copyright (c) Shopify Inc. 2022-2023 + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +======================================================================== + +@root/encoding 1.0.1 +-- +Mozilla Public License Version 2.0 + + 1. Definitions + +1.1. "Contributor" means each individual or legal entity that creates, contributes +to the creation of, or owns Covered Software. + +1.2. "Contributor Version" means the combination of the Contributions of others +(if any) used by a Contributor and that particular Contributor's Contribution. + + 1.3. "Contribution" means Covered Software of a particular Contributor. + +1.4. "Covered Software" means Source Code Form to which the initial Contributor +has attached the notice in Exhibit A, the Executable Form of such Source Code +Form, and Modifications of such Source Code Form, in each case including portions +thereof. + + 1.5. "Incompatible With Secondary Licenses" means + +(a) that the initial Contributor has attached the notice described in Exhibit +B to the Covered Software; or + +(b) that the Covered Software was made available under the terms of version +1.1 or earlier of the License, but not also under the terms of a Secondary +License. + +1.6. "Executable Form" means any form of the work other than Source Code Form. + +1.7. "Larger Work" means a work that combines Covered Software with other +material, in a separate file or files, that is not Covered Software. + + 1.8. "License" means this document. + +1.9. "Licensable" means having the right to grant, to the maximum extent possible, +whether at the time of the initial grant or subsequently, any and all of the +rights conveyed by this License. + + 1.10. "Modifications" means any of the following: + +(a) any file in Source Code Form that results from an addition to, deletion +from, or modification of the contents of Covered Software; or + +(b) any new file in Source Code Form that contains any Covered Software. + +1.11. "Patent Claims" of a Contributor means any patent claim(s), including +without limitation, method, process, and apparatus claims, in any patent Licensable +by such Contributor that would be infringed, but for the grant of the License, +by the making, using, selling, offering for sale, having made, import, or +transfer of either its Contributions or its Contributor Version. + +1.12. "Secondary License" means either the GNU General Public License, Version +2.0, the GNU Lesser General Public License, Version 2.1, the GNU Affero General +Public License, Version 3.0, or any later versions of those licenses. + +1.13. "Source Code Form" means the form of the work preferred for making modifications. + +1.14. "You" (or "Your") means an individual or a legal entity exercising rights +under this License. For legal entities, "You" includes any entity that controls, +is controlled by, or is under common control with You. For purposes of this +definition, "control" means (a) the power, direct or indirect, to cause the +direction or management of such entity, whether by contract or otherwise, +or (b) ownership of more than fifty percent (50%) of the outstanding shares +or beneficial ownership of such entity. + + 2. License Grants and Conditions + + 2.1. Grants + +Each Contributor hereby grants You a world-wide, royalty-free, non-exclusive +license: + +(a) under intellectual property rights (other than patent or trademark) Licensable +by such Contributor to use, reproduce, make available, modify, display, perform, +distribute, and otherwise exploit its Contributions, either on an unmodified +basis, with Modifications, or as part of a Larger Work; and + +(b) under Patent Claims of such Contributor to make, use, sell, offer for +sale, have made, import, and otherwise transfer either its Contributions or +its Contributor Version. + + 2.2. Effective Date + +The licenses granted in Section 2.1 with respect to any Contribution become +effective for each Contribution on the date the Contributor first distributes +such Contribution. + + 2.3. Limitations on Grant Scope + +The licenses granted in this Section 2 are the only rights granted under this +License. No additional rights or licenses will be implied from the distribution +or licensing of Covered Software under this License. Notwithstanding Section +2.1(b) above, no patent license is granted by a Contributor: + +(a) for any code that a Contributor has removed from Covered Software; or + +(b) for infringements caused by: (i) Your and any other third party's modifications +of Covered Software, or (ii) the combination of its Contributions with other +software (except as part of its Contributor Version); or + +(c) under Patent Claims infringed by Covered Software in the absence of its +Contributions. + +This License does not grant any rights in the trademarks, service marks, or +logos of any Contributor (except as may be necessary to comply with the notice +requirements in Section 3.4). + + 2.4. Subsequent Licenses + +No Contributor makes additional grants as a result of Your choice to distribute +the Covered Software under a subsequent version of this License (see Section +10.2) or under the terms of a Secondary License (if permitted under the terms +of Section 3.3). + + 2.5. Representation + +Each Contributor represents that the Contributor believes its Contributions +are its original creation(s) or it has sufficient rights to grant the rights +to its Contributions conveyed by this License. + + 2.6. Fair Use + +This License is not intended to limit any rights You have under applicable +copyright doctrines of fair use, fair dealing, or other equivalents. + + 2.7. Conditions + +Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted in +Section 2.1. + + 3. Responsibilities + + 3.1. Distribution of Source Form + +All distribution of Covered Software in Source Code Form, including any Modifications +that You create or to which You contribute, must be under the terms of this +License. You must inform recipients that the Source Code Form of the Covered +Software is governed by the terms of this License, and how they can obtain +a copy of this License. You may not attempt to alter or restrict the recipients' +rights in the Source Code Form. + + 3.2. Distribution of Executable Form + + If You distribute Covered Software in Executable Form then: + +(a) such Covered Software must also be made available in Source Code Form, +as described in Section 3.1, and You must inform recipients of the Executable +Form how they can obtain a copy of such Source Code Form by reasonable means +in a timely manner, at a charge no more than the cost of distribution to the +recipient; and + +(b) You may distribute such Executable Form under the terms of this License, +or sublicense it under different terms, provided that the license for the +Executable Form does not attempt to limit or alter the recipients' rights +in the Source Code Form under this License. + + 3.3. Distribution of a Larger Work + +You may create and distribute a Larger Work under terms of Your choice, provided +that You also comply with the requirements of this License for the Covered +Software. If the Larger Work is a combination of Covered Software with a work +governed by one or more Secondary Licenses, and the Covered Software is not +Incompatible With Secondary Licenses, this License permits You to additionally +distribute such Covered Software under the terms of such Secondary License(s), +so that the recipient of the Larger Work may, at their option, further distribute +the Covered Software under the terms of either this License or such Secondary +License(s). + + 3.4. Notices + +You may not remove or alter the substance of any license notices (including +copyright notices, patent notices, disclaimers of warranty, or limitations +of liability) contained within the Source Code Form of the Covered Software, +except that You may alter any license notices to the extent required to remedy +known factual inaccuracies. + + 3.5. Application of Additional Terms + +You may choose to offer, and to charge a fee for, warranty, support, indemnity +or liability obligations to one or more recipients of Covered Software. However, +You may do so only on Your own behalf, and not on behalf of any Contributor. +You must make it absolutely clear that any such warranty, support, indemnity, +or liability obligation is offered by You alone, and You hereby agree to indemnify +every Contributor for any liability incurred by such Contributor as a result +of warranty, support, indemnity or liability terms You offer. You may include +additional disclaimers of warranty and limitations of liability specific to +any jurisdiction. + + 4. Inability to Comply Due to Statute or Regulation + +If it is impossible for You to comply with any of the terms of this License +with respect to some or all of the Covered Software due to statute, judicial +order, or regulation then You must: (a) comply with the terms of this License +to the maximum extent possible; and (b) describe the limitations and the code +they affect. Such description must be placed in a text file included with +all distributions of the Covered Software under this License. Except to the +extent prohibited by statute or regulation, such description must be sufficiently +detailed for a recipient of ordinary skill to be able to understand it. + + 5. Termination + +5.1. The rights granted under this License will terminate automatically if +You fail to comply with any of its terms. However, if You become compliant, +then the rights granted under this License from a particular Contributor are +reinstated (a) provisionally, unless and until such Contributor explicitly +and finally terminates Your grants, and (b) on an ongoing basis, if such Contributor +fails to notify You of the non-compliance by some reasonable means prior to +60 days after You have come back into compliance. Moreover, Your grants from +a particular Contributor are reinstated on an ongoing basis if such Contributor +notifies You of the non-compliance by some reasonable means, this is the first +time You have received notice of non-compliance with this License from such +Contributor, and You become compliant prior to 30 days after Your receipt +of the notice. + +5.2. If You initiate litigation against any entity by asserting a patent infringement +claim (excluding declaratory judgment actions, counter-claims, and cross-claims) +alleging that a Contributor Version directly or indirectly infringes any patent, +then the rights granted to You by any and all Contributors for the Covered +Software under Section 2.1 of this License shall terminate. + +5.3. In the event of termination under Sections 5.1 or 5.2 above, all end +user license agreements (excluding distributors and resellers) which have +been validly granted by You or Your distributors under this License prior +to termination shall survive termination. + + 6. Disclaimer of Warranty + +Covered Software is provided under this License on an "as is" basis, without +warranty of any kind, either expressed, implied, or statutory, including, +without limitation, warranties that the Covered Software is free of defects, +merchantable, fit for a particular purpose or non-infringing. The entire risk +as to the quality and performance of the Covered Software is with You. Should +any Covered Software prove defective in any respect, You (not any Contributor) +assume the cost of any necessary servicing, repair, or correction. This disclaimer +of warranty constitutes an essential part of this License. No use of any Covered +Software is authorized under this License except under this disclaimer. + + 7. Limitation of Liability + +Under no circumstances and under no legal theory, whether tort (including +negligence), contract, or otherwise, shall any Contributor, or anyone who +distributes Covered Software as permitted above, be liable to You for any +direct, indirect, special, incidental, or consequential damages of any character +including, without limitation, damages for lost profits, loss of goodwill, +work stoppage, computer failure or malfunction, or any and all other commercial +damages or losses, even if such party shall have been informed of the possibility +of such damages. This limitation of liability shall not apply to liability +for death or personal injury resulting from such party's negligence to the +extent applicable law prohibits such limitation. Some jurisdictions do not +allow the exclusion or limitation of incidental or consequential damages, +so this exclusion and limitation may not apply to You. + + 8. Litigation + +Any litigation relating to this License may be brought only in the courts +of a jurisdiction where the defendant maintains its principal place of business +and such litigation shall be governed by laws of that jurisdiction, without +reference to its conflict-of-law provisions. Nothing in this Section shall +prevent a party's ability to bring cross-claims or counter-claims. + + 9. Miscellaneous + +This License represents the complete agreement concerning the subject matter +hereof. If any provision of this License is held to be unenforceable, such +provision shall be reformed only to the extent necessary to make it enforceable. +Any law or regulation which provides that the language of a contract shall +be construed against the drafter shall not be used to construe this License +against a Contributor. + + 10. Versions of the License + + 10.1. New Versions + +Mozilla Foundation is the license steward. Except as provided in Section 10.3, +no one other than the license steward has the right to modify or publish new +versions of this License. Each version will be given a distinguishing version +number. + + 10.2. Effect of New Versions + +You may distribute the Covered Software under the terms of the version of +the License under which You originally received the Covered Software, or under +the terms of any subsequent version published by the license steward. + + 10.3. Modified Versions + +If you create software not governed by this License, and you want to create +a new license for such software, you may create and use a modified version +of this License if you rename the license and remove any references to the +name of the license steward (except to note that such modified license differs +from this License). + +10.4. Distributing Source Code Form that is Incompatible With Secondary Licenses + +If You choose to distribute Source Code Form that is Incompatible With Secondary +Licenses under the terms of this version of the License, the notice described +in Exhibit B of this License must be attached. Exhibit A - Source Code Form +License Notice + +This Source Code Form is subject to the terms of the Mozilla Public License, +v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain +one at http://mozilla.org/MPL/2.0/. + +If it is not possible or desirable to put the notice in a particular file, +then You may include the notice in a location (such as a LICENSE file in a +relevant directory) where a recipient would be likely to look for such a notice. + +You may add additional accurate notices of copyright ownership. + +Exhibit B - "Incompatible With Secondary Licenses" Notice + +This Source Code Form is "Incompatible With Secondary Licenses", as defined +by the Mozilla Public License, v. 2.0. + +======================================================================== + +@seznam/compose-react-refs 1.0.6 +-- +Copyright (c) 2019, Seznam.cz, a.s. + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +======================================================================== + +@shopify/hydrogen 2024.4.1 +-- +Copyright 2023-present, Shopify Inc. + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +======================================================================== + +@shopify/hydrogen-react 2024.4.1 +-- +Licensed under MIT (https://spdx.org/licenses/MIT.html) + +======================================================================== + +@stitches/react 1.2.8 +-- +Licensed under MIT (https://spdx.org/licenses/MIT.html) + +======================================================================== + +@tippyjs/react 4.1.0 +-- +MIT License + +Copyright (c) 2018 atomiks + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +======================================================================== + +@twind/core 1.1.3 +-- +MIT License + +Copyright (c) 2022 [these people](https://github.com/tw-in-js/twind/graphs/contributors) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +======================================================================== + +@twind/preset-autoprefix 1.0.7 +-- +MIT License + +Copyright (c) 2022 [these people](https://github.com/tw-in-js/twind/graphs/contributors) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +======================================================================== + +@twind/preset-tailwind 1.1.4 +-- +MIT License + +Copyright (c) 2022 [these people](https://github.com/tw-in-js/twind/graphs/contributors) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +======================================================================== + +@use-it/interval 0.1.3 +-- +MIT License + +Copyright (c) 2019-present Donavon West + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +======================================================================== + +@vercel/stega 0.1.0 +-- +# Mozilla Public License Version 2.0 + +1. Definitions + +--- + +1.1. "Contributor" +means each individual or legal entity that creates, contributes to +the creation of, or owns Covered Software. + +1.2. "Contributor Version" +means the combination of the Contributions of others (if any) used +by a Contributor and that particular Contributor's Contribution. + +1.3. "Contribution" +means Covered Software of a particular Contributor. + +1.4. "Covered Software" +means Source Code Form to which the initial Contributor has attached +the notice in Exhibit A, the Executable Form of such Source Code +Form, and Modifications of such Source Code Form, in each case +including portions thereof. + +1.5. "Incompatible With Secondary Licenses" +means + + (a) that the initial Contributor has attached the notice described + in Exhibit B to the Covered Software; or + + (b) that the Covered Software was made available under the terms of + version 1.1 or earlier of the License, but not also under the + terms of a Secondary License. + +1.6. "Executable Form" +means any form of the work other than Source Code Form. + +1.7. "Larger Work" +means a work that combines Covered Software with other material, in +a separate file or files, that is not Covered Software. + +1.8. "License" +means this document. + +1.9. "Licensable" +means having the right to grant, to the maximum extent possible, +whether at the time of the initial grant or subsequently, any and +all of the rights conveyed by this License. + +1.10. "Modifications" +means any of the following: + + (a) any file in Source Code Form that results from an addition to, + deletion from, or modification of the contents of Covered + Software; or + + (b) any new file in Source Code Form that contains any Covered + Software. + +1.11. "Patent Claims" of a Contributor +means any patent claim(s), including without limitation, method, +process, and apparatus claims, in any patent Licensable by such +Contributor that would be infringed, but for the grant of the +License, by the making, using, selling, offering for sale, having +made, import, or transfer of either its Contributions or its +Contributor Version. + +1.12. "Secondary License" +means either the GNU General Public License, Version 2.0, the GNU +Lesser General Public License, Version 2.1, the GNU Affero General +Public License, Version 3.0, or any later versions of those +licenses. + +1.13. "Source Code Form" +means the form of the work preferred for making modifications. + +1.14. "You" (or "Your") +means an individual or a legal entity exercising rights under this +License. For legal entities, "You" includes any entity that +controls, is controlled by, or is under common control with You. For +purposes of this definition, "control" means (a) the power, direct +or indirect, to cause the direction or management of such entity, +whether by contract or otherwise, or (b) ownership of more than +fifty percent (50%) of the outstanding shares or beneficial +ownership of such entity. + +2. License Grants and Conditions + +--- + +2.1. Grants + +Each Contributor hereby grants You a world-wide, royalty-free, +non-exclusive license: + +(a) under intellectual property rights (other than patent or trademark) +Licensable by such Contributor to use, reproduce, make available, +modify, display, perform, distribute, and otherwise exploit its +Contributions, either on an unmodified basis, with Modifications, or +as part of a Larger Work; and + +(b) under Patent Claims of such Contributor to make, use, sell, offer +for sale, have made, import, and otherwise transfer either its +Contributions or its Contributor Version. + +2.2. Effective Date + +The licenses granted in Section 2.1 with respect to any Contribution +become effective for each Contribution on the date the Contributor first +distributes such Contribution. + +2.3. Limitations on Grant Scope + +The licenses granted in this Section 2 are the only rights granted under +this License. No additional rights or licenses will be implied from the +distribution or licensing of Covered Software under this License. +Notwithstanding Section 2.1(b) above, no patent license is granted by a +Contributor: + +(a) for any code that a Contributor has removed from Covered Software; +or + +(b) for infringements caused by: (i) Your and any other third party's +modifications of Covered Software, or (ii) the combination of its +Contributions with other software (except as part of its Contributor +Version); or + +(c) under Patent Claims infringed by Covered Software in the absence of +its Contributions. + +This License does not grant any rights in the trademarks, service marks, +or logos of any Contributor (except as may be necessary to comply with +the notice requirements in Section 3.4). + +2.4. Subsequent Licenses + +No Contributor makes additional grants as a result of Your choice to +distribute the Covered Software under a subsequent version of this +License (see Section 10.2) or under the terms of a Secondary License (if +permitted under the terms of Section 3.3). + +2.5. Representation + +Each Contributor represents that the Contributor believes its +Contributions are its original creation(s) or it has sufficient rights +to grant the rights to its Contributions conveyed by this License. + +2.6. Fair Use + +This License is not intended to limit any rights You have under +applicable copyright doctrines of fair use, fair dealing, or other +equivalents. + +2.7. Conditions + +Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted +in Section 2.1. + +3. Responsibilities + +--- + +3.1. Distribution of Source Form + +All distribution of Covered Software in Source Code Form, including any +Modifications that You create or to which You contribute, must be under +the terms of this License. You must inform recipients that the Source +Code Form of the Covered Software is governed by the terms of this +License, and how they can obtain a copy of this License. You may not +attempt to alter or restrict the recipients' rights in the Source Code +Form. + +3.2. Distribution of Executable Form + +If You distribute Covered Software in Executable Form then: + +(a) such Covered Software must also be made available in Source Code +Form, as described in Section 3.1, and You must inform recipients of +the Executable Form how they can obtain a copy of such Source Code +Form by reasonable means in a timely manner, at a charge no more +than the cost of distribution to the recipient; and + +(b) You may distribute such Executable Form under the terms of this +License, or sublicense it under different terms, provided that the +license for the Executable Form does not attempt to limit or alter +the recipients' rights in the Source Code Form under this License. + +3.3. Distribution of a Larger Work + +You may create and distribute a Larger Work under terms of Your choice, +provided that You also comply with the requirements of this License for +the Covered Software. If the Larger Work is a combination of Covered +Software with a work governed by one or more Secondary Licenses, and the +Covered Software is not Incompatible With Secondary Licenses, this +License permits You to additionally distribute such Covered Software +under the terms of such Secondary License(s), so that the recipient of +the Larger Work may, at their option, further distribute the Covered +Software under the terms of either this License or such Secondary +License(s). + +3.4. Notices + +You may not remove or alter the substance of any license notices +(including copyright notices, patent notices, disclaimers of warranty, +or limitations of liability) contained within the Source Code Form of +the Covered Software, except that You may alter any license notices to +the extent required to remedy known factual inaccuracies. + +3.5. Application of Additional Terms + +You may choose to offer, and to charge a fee for, warranty, support, +indemnity or liability obligations to one or more recipients of Covered +Software. However, You may do so only on Your own behalf, and not on +behalf of any Contributor. You must make it absolutely clear that any +such warranty, support, indemnity, or liability obligation is offered by +You alone, and You hereby agree to indemnify every Contributor for any +liability incurred by such Contributor as a result of warranty, support, +indemnity or liability terms You offer. You may include additional +disclaimers of warranty and limitations of liability specific to any +jurisdiction. + +4. Inability to Comply Due to Statute or Regulation + +--- + +If it is impossible for You to comply with any of the terms of this +License with respect to some or all of the Covered Software due to +statute, judicial order, or regulation then You must: (a) comply with +the terms of this License to the maximum extent possible; and (b) +describe the limitations and the code they affect. Such description must +be placed in a text file included with all distributions of the Covered +Software under this License. Except to the extent prohibited by statute +or regulation, such description must be sufficiently detailed for a +recipient of ordinary skill to be able to understand it. + +5. Termination + +--- + +5.1. The rights granted under this License will terminate automatically +if You fail to comply with any of its terms. However, if You become +compliant, then the rights granted under this License from a particular +Contributor are reinstated (a) provisionally, unless and until such +Contributor explicitly and finally terminates Your grants, and (b) on an +ongoing basis, if such Contributor fails to notify You of the +non-compliance by some reasonable means prior to 60 days after You have +come back into compliance. Moreover, Your grants from a particular +Contributor are reinstated on an ongoing basis if such Contributor +notifies You of the non-compliance by some reasonable means, this is the +first time You have received notice of non-compliance with this License +from such Contributor, and You become compliant prior to 30 days after +Your receipt of the notice. + +5.2. If You initiate litigation against any entity by asserting a patent +infringement claim (excluding declaratory judgment actions, +counter-claims, and cross-claims) alleging that a Contributor Version +directly or indirectly infringes any patent, then the rights granted to +You by any and all Contributors for the Covered Software under Section +2.1 of this License shall terminate. + +5.3. In the event of termination under Sections 5.1 or 5.2 above, all +end user license agreements (excluding distributors and resellers) which +have been validly granted by You or Your distributors under this License +prior to termination shall survive termination. + +--- + +- * +- 6. Disclaimer of Warranty \* +- ------------------------- \* +- * +- Covered Software is provided under this License on an "as is" \* +- basis, without warranty of any kind, either expressed, implied, or \* +- statutory, including, without limitation, warranties that the \* +- Covered Software is free of defects, merchantable, fit for a \* +- particular purpose or non-infringing. The entire risk as to the \* +- quality and performance of the Covered Software is with You. \* +- Should any Covered Software prove defective in any respect, You \* +- (not any Contributor) assume the cost of any necessary servicing, \* +- repair, or correction. This disclaimer of warranty constitutes an \* +- essential part of this License. No use of any Covered Software is \* +- authorized under this License except under this disclaimer. \* +- * + +--- + +--- + +- * +- 7. Limitation of Liability \* +- -------------------------- \* +- * +- Under no circumstances and under no legal theory, whether tort \* +- (including negligence), contract, or otherwise, shall any \* +- Contributor, or anyone who distributes Covered Software as \* +- permitted above, be liable to You for any direct, indirect, \* +- special, incidental, or consequential damages of any character \* +- including, without limitation, damages for lost profits, loss of \* +- goodwill, work stoppage, computer failure or malfunction, or any \* +- and all other commercial damages or losses, even if such party \* +- shall have been informed of the possibility of such damages. This \* +- limitation of liability shall not apply to liability for death or \* +- personal injury resulting from such party's negligence to the \* +- extent applicable law prohibits such limitation. Some \* +- jurisdictions do not allow the exclusion or limitation of \* +- incidental or consequential damages, so this exclusion and \* +- limitation may not apply to You. \* +- * + +--- + +8. Litigation + +--- + +Any litigation relating to this License may be brought only in the +courts of a jurisdiction where the defendant maintains its principal +place of business and such litigation shall be governed by laws of that +jurisdiction, without reference to its conflict-of-law provisions. +Nothing in this Section shall prevent a party's ability to bring +cross-claims or counter-claims. + +9. Miscellaneous + +--- + +This License represents the complete agreement concerning the subject +matter hereof. If any provision of this License is held to be +unenforceable, such provision shall be reformed only to the extent +necessary to make it enforceable. Any law or regulation which provides +that the language of a contract shall be construed against the drafter +shall not be used to construe this License against a Contributor. + +10. Versions of the License + +--- + +10.1. New Versions + +Mozilla Foundation is the license steward. Except as provided in Section +10.3, no one other than the license steward has the right to modify or +publish new versions of this License. Each version will be given a +distinguishing version number. + +10.2. Effect of New Versions + +You may distribute the Covered Software under the terms of the version +of the License under which You originally received the Covered Software, +or under the terms of any subsequent version published by the license +steward. + +10.3. Modified Versions + +If you create software not governed by this License, and you want to +create a new license for such software, you may create and use a +modified version of this License if you rename the license and remove +any references to the name of the license steward (except to note that +such modified license differs from this License). + +10.4. Distributing Source Code Form that is Incompatible With Secondary +Licenses + +If You choose to distribute Source Code Form that is Incompatible With +Secondary Licenses under the terms of this version of the License, the +notice described in Exhibit B of this License must be attached. + +## Exhibit A - Source Code Form License Notice + +This Source Code Form is subject to the terms of the Mozilla Public +License, v. 2.0. If a copy of the MPL was not distributed with this +file, You can obtain one at http://mozilla.org/MPL/2.0/. + +If it is not possible or desirable to put the notice in a particular +file, then You may include the notice in a location (such as a LICENSE +file in a relevant directory) where a recipient would be likely to look +for such a notice. + +You may add additional accurate notices of copyright ownership. + +## Exhibit B - "Incompatible With Secondary Licenses" Notice + +This Source Code Form is "Incompatible With Secondary Licenses", as +defined by the Mozilla Public License, v. 2.0. + +======================================================================== + +@web3-storage/multipart-parser 1.0.0 +-- +Copyright vasco-santos; licensed under (Apache-2.0 AND MIT) (https://spdx.org/licenses/Apache-2.0.html https://spdx.org/licenses/MIT.html) + +======================================================================== + +acorn 7.4.1 +-- +MIT License + +Copyright (C) 2012-2018 by various contributors (see AUTHORS) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +======================================================================== + +acorn-jsx 5.3.2 +-- +Copyright (C) 2012-2017 by Ingvar Stepanyan + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +======================================================================== + +add-dom-event-listener 1.1.0 +-- +The MIT License (MIT) + +Copyright (c) 2014-present yiminghe + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +======================================================================== + +ajv 6.12.6 +-- +The MIT License (MIT) + +Copyright (c) 2015-2017 Evgeny Poberezkin + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +======================================================================== + +anser 2.1.0 +-- +The MIT License (MIT) + +Copyright (c) 2012-21 Ionică Bizău (https://ionicabizau.net) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +======================================================================== + +ansi-regex 5.0.1 +-- +MIT License + +Copyright (c) Sindre Sorhus (sindresorhus.com) + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +======================================================================== + +ansi-styles 3.2.1 +-- +MIT License + +Copyright (c) Sindre Sorhus (sindresorhus.com) + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +======================================================================== + +aria-hidden 1.2.3 +-- +MIT License + +Copyright (c) 2017 Anton Korzunov + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +======================================================================== + +aria-query 4.2.2 +-- +Apache License +Version 2.0, January 2004 +http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + +"License" shall mean the terms and conditions for use, reproduction, +and distribution as defined by Sections 1 through 9 of this document. + +"Licensor" shall mean the copyright owner or entity authorized by +the copyright owner that is granting the License. + +"Legal Entity" shall mean the union of the acting entity and all +other entities that control, are controlled by, or are under common +control with that entity. For the purposes of this definition, +"control" means (i) the power, direct or indirect, to cause the +direction or management of such entity, whether by contract or +otherwise, or (ii) ownership of fifty percent (50%) or more of the +outstanding shares, or (iii) beneficial ownership of such entity. + +"You" (or "Your") shall mean an individual or Legal Entity +exercising permissions granted by this License. + +"Source" form shall mean the preferred form for making modifications, +including but not limited to software source code, documentation +source, and configuration files. + +"Object" form shall mean any form resulting from mechanical +transformation or translation of a Source form, including but +not limited to compiled object code, generated documentation, +and conversions to other media types. + +"Work" shall mean the work of authorship, whether in Source or +Object form, made available under the License, as indicated by a +copyright notice that is included in or attached to the work +(an example is provided in the Appendix below). + +"Derivative Works" shall mean any work, whether in Source or Object +form, that is based on (or derived from) the Work and for which the +editorial revisions, annotations, elaborations, or other modifications +represent, as a whole, an original work of authorship. For the purposes +of this License, Derivative Works shall not include works that remain +separable from, or merely link (or bind by name) to the interfaces of, +the Work and Derivative Works thereof. + +"Contribution" shall mean any work of authorship, including +the original version of the Work and any modifications or additions +to that Work or Derivative Works thereof, that is intentionally +submitted to Licensor for inclusion in the Work by the copyright owner +or by an individual or Legal Entity authorized to submit on behalf of +the copyright owner. For the purposes of this definition, "submitted" +means any form of electronic, verbal, or written communication sent +to the Licensor or its representatives, including but not limited to +communication on electronic mailing lists, source code control systems, +and issue tracking systems that are managed by, or on behalf of, the +Licensor for the purpose of discussing and improving the Work, but +excluding communication that is conspicuously marked or otherwise +designated in writing by the copyright owner as "Not a Contribution." + +"Contributor" shall mean Licensor and any individual or Legal Entity +on behalf of whom a Contribution has been received by Licensor and +subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of +this License, each Contributor hereby grants to You a perpetual, +worldwide, non-exclusive, no-charge, royalty-free, irrevocable +copyright license to reproduce, prepare Derivative Works of, +publicly display, publicly perform, sublicense, and distribute the +Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of +this License, each Contributor hereby grants to You a perpetual, +worldwide, non-exclusive, no-charge, royalty-free, irrevocable +(except as stated in this section) patent license to make, have made, +use, offer to sell, sell, import, and otherwise transfer the Work, +where such license applies only to those patent claims licensable +by such Contributor that are necessarily infringed by their +Contribution(s) alone or by combination of their Contribution(s) +with the Work to which such Contribution(s) was submitted. If You +institute patent litigation against any entity (including a +cross-claim or counterclaim in a lawsuit) alleging that the Work +or a Contribution incorporated within the Work constitutes direct +or contributory patent infringement, then any patent licenses +granted to You under this License for that Work shall terminate +as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the +Work or Derivative Works thereof in any medium, with or without +modifications, and in Source or Object form, provided that You +meet the following conditions: + +(a) You must give any other recipients of the Work or +Derivative Works a copy of this License; and + +(b) You must cause any modified files to carry prominent notices +stating that You changed the files; and + +(c) You must retain, in the Source form of any Derivative Works +that You distribute, all copyright, patent, trademark, and +attribution notices from the Source form of the Work, +excluding those notices that do not pertain to any part of +the Derivative Works; and + +(d) If the Work includes a "NOTICE" text file as part of its +distribution, then any Derivative Works that You distribute must +include a readable copy of the attribution notices contained +within such NOTICE file, excluding those notices that do not +pertain to any part of the Derivative Works, in at least one +of the following places: within a NOTICE text file distributed +as part of the Derivative Works; within the Source form or +documentation, if provided along with the Derivative Works; or, +within a display generated by the Derivative Works, if and +wherever such third-party notices normally appear. The contents +of the NOTICE file are for informational purposes only and +do not modify the License. You may add Your own attribution +notices within Derivative Works that You distribute, alongside +or as an addendum to the NOTICE text from the Work, provided +that such additional attribution notices cannot be construed +as modifying the License. + +You may add Your own copyright statement to Your modifications and +may provide additional or different license terms and conditions +for use, reproduction, or distribution of Your modifications, or +for any such Derivative Works as a whole, provided Your use, +reproduction, and distribution of the Work otherwise complies with +the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, +any Contribution intentionally submitted for inclusion in the Work +by You to the Licensor shall be under the terms and conditions of +this License, without any additional terms or conditions. +Notwithstanding the above, nothing herein shall supersede or modify +the terms of any separate license agreement you may have executed +with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade +names, trademarks, service marks, or product names of the Licensor, +except as required for reasonable and customary use in describing the +origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or +agreed to in writing, Licensor provides the Work (and each +Contributor provides its Contributions) on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +implied, including, without limitation, any warranties or conditions +of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A +PARTICULAR PURPOSE. You are solely responsible for determining the +appropriateness of using or redistributing the Work and assume any +risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, +whether in tort (including negligence), contract, or otherwise, +unless required by applicable law (such as deliberate and grossly +negligent acts) or agreed to in writing, shall any Contributor be +liable to You for damages, including any direct, indirect, special, +incidental, or consequential damages of any character arising as a +result of this License or out of the use or inability to use the +Work (including but not limited to damages for loss of goodwill, +work stoppage, computer failure or malfunction, or any and all +other commercial damages or losses), even if such Contributor +has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing +the Work or Derivative Works thereof, You may choose to offer, +and charge a fee for, acceptance of support, warranty, indemnity, +or other liability obligations and/or rights consistent with this +License. However, in accepting such obligations, You may act only +on Your own behalf and on Your sole responsibility, not on behalf +of any other Contributor, and only if You agree to indemnify, +defend, and hold each Contributor harmless for any liability +incurred by, or claims asserted against, such Contributor by reason +of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + +To apply the Apache License to your work, attach the following +boilerplate notice, with the fields enclosed by brackets "{}" +replaced with your own identifying information. (Don't include +the brackets!) The text should be enclosed in the appropriate +comment syntax for the file format. We also recommend that a +file or class name and description of purpose be included on the +same "printed page" as the copyright notice for easier +identification within third-party archives. + +Copyright 2020 A11yance + +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 + +http://www.apache.org/licenses/LICENSE-2.0 + +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. + +======================================================================== + +array-includes 3.1.4 +-- +The MIT License (MIT) + +Copyright (C) 2015 Jordan Harband + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +======================================================================== + +array.prototype.flatmap 1.2.4 +-- +MIT License + +Copyright (c) 2017 ECMAScript Shims + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +======================================================================== + +assert 1.3.0 +-- +Copyright Joyent, Inc. and other Node contributors. All rights reserved. +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to +deal in the Software without restriction, including without limitation the +rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +sell copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +IN THE SOFTWARE. + +======================================================================== + +axobject-query 2.2.0 +-- +Apache License +Version 2.0, January 2004 +http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + +"License" shall mean the terms and conditions for use, reproduction, +and distribution as defined by Sections 1 through 9 of this document. + +"Licensor" shall mean the copyright owner or entity authorized by +the copyright owner that is granting the License. + +"Legal Entity" shall mean the union of the acting entity and all +other entities that control, are controlled by, or are under common +control with that entity. For the purposes of this definition, +"control" means (i) the power, direct or indirect, to cause the +direction or management of such entity, whether by contract or +otherwise, or (ii) ownership of fifty percent (50%) or more of the +outstanding shares, or (iii) beneficial ownership of such entity. + +"You" (or "Your") shall mean an individual or Legal Entity +exercising permissions granted by this License. + +"Source" form shall mean the preferred form for making modifications, +including but not limited to software source code, documentation +source, and configuration files. + +"Object" form shall mean any form resulting from mechanical +transformation or translation of a Source form, including but +not limited to compiled object code, generated documentation, +and conversions to other media types. + +"Work" shall mean the work of authorship, whether in Source or +Object form, made available under the License, as indicated by a +copyright notice that is included in or attached to the work +(an example is provided in the Appendix below). + +"Derivative Works" shall mean any work, whether in Source or Object +form, that is based on (or derived from) the Work and for which the +editorial revisions, annotations, elaborations, or other modifications +represent, as a whole, an original work of authorship. For the purposes +of this License, Derivative Works shall not include works that remain +separable from, or merely link (or bind by name) to the interfaces of, +the Work and Derivative Works thereof. + +"Contribution" shall mean any work of authorship, including +the original version of the Work and any modifications or additions +to that Work or Derivative Works thereof, that is intentionally +submitted to Licensor for inclusion in the Work by the copyright owner +or by an individual or Legal Entity authorized to submit on behalf of +the copyright owner. For the purposes of this definition, "submitted" +means any form of electronic, verbal, or written communication sent +to the Licensor or its representatives, including but not limited to +communication on electronic mailing lists, source code control systems, +and issue tracking systems that are managed by, or on behalf of, the +Licensor for the purpose of discussing and improving the Work, but +excluding communication that is conspicuously marked or otherwise +designated in writing by the copyright owner as "Not a Contribution." + +"Contributor" shall mean Licensor and any individual or Legal Entity +on behalf of whom a Contribution has been received by Licensor and +subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of +this License, each Contributor hereby grants to You a perpetual, +worldwide, non-exclusive, no-charge, royalty-free, irrevocable +copyright license to reproduce, prepare Derivative Works of, +publicly display, publicly perform, sublicense, and distribute the +Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of +this License, each Contributor hereby grants to You a perpetual, +worldwide, non-exclusive, no-charge, royalty-free, irrevocable +(except as stated in this section) patent license to make, have made, +use, offer to sell, sell, import, and otherwise transfer the Work, +where such license applies only to those patent claims licensable +by such Contributor that are necessarily infringed by their +Contribution(s) alone or by combination of their Contribution(s) +with the Work to which such Contribution(s) was submitted. If You +institute patent litigation against any entity (including a +cross-claim or counterclaim in a lawsuit) alleging that the Work +or a Contribution incorporated within the Work constitutes direct +or contributory patent infringement, then any patent licenses +granted to You under this License for that Work shall terminate +as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the +Work or Derivative Works thereof in any medium, with or without +modifications, and in Source or Object form, provided that You +meet the following conditions: + +(a) You must give any other recipients of the Work or +Derivative Works a copy of this License; and + +(b) You must cause any modified files to carry prominent notices +stating that You changed the files; and + +(c) You must retain, in the Source form of any Derivative Works +that You distribute, all copyright, patent, trademark, and +attribution notices from the Source form of the Work, +excluding those notices that do not pertain to any part of +the Derivative Works; and + +(d) If the Work includes a "NOTICE" text file as part of its +distribution, then any Derivative Works that You distribute must +include a readable copy of the attribution notices contained +within such NOTICE file, excluding those notices that do not +pertain to any part of the Derivative Works, in at least one +of the following places: within a NOTICE text file distributed +as part of the Derivative Works; within the Source form or +documentation, if provided along with the Derivative Works; or, +within a display generated by the Derivative Works, if and +wherever such third-party notices normally appear. The contents +of the NOTICE file are for informational purposes only and +do not modify the License. You may add Your own attribution +notices within Derivative Works that You distribute, alongside +or as an addendum to the NOTICE text from the Work, provided +that such additional attribution notices cannot be construed +as modifying the License. + +You may add Your own copyright statement to Your modifications and +may provide additional or different license terms and conditions +for use, reproduction, or distribution of Your modifications, or +for any such Derivative Works as a whole, provided Your use, +reproduction, and distribution of the Work otherwise complies with +the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, +any Contribution intentionally submitted for inclusion in the Work +by You to the Licensor shall be under the terms and conditions of +this License, without any additional terms or conditions. +Notwithstanding the above, nothing herein shall supersede or modify +the terms of any separate license agreement you may have executed +with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade +names, trademarks, service marks, or product names of the Licensor, +except as required for reasonable and customary use in describing the +origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or +agreed to in writing, Licensor provides the Work (and each +Contributor provides its Contributions) on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +implied, including, without limitation, any warranties or conditions +of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A +PARTICULAR PURPOSE. You are solely responsible for determining the +appropriateness of using or redistributing the Work and assume any +risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, +whether in tort (including negligence), contract, or otherwise, +unless required by applicable law (such as deliberate and grossly +negligent acts) or agreed to in writing, shall any Contributor be +liable to You for damages, including any direct, indirect, special, +incidental, or consequential damages of any character arising as a +result of this License or out of the use or inability to use the +Work (including but not limited to damages for loss of goodwill, +work stoppage, computer failure or malfunction, or any and all +other commercial damages or losses), even if such Contributor +has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing +the Work or Derivative Works thereof, You may choose to offer, +and charge a fee for, acceptance of support, warranty, indemnity, +or other liability obligations and/or rights consistent with this +License. However, in accepting such obligations, You may act only +on Your own behalf and on Your sole responsibility, not on behalf +of any other Contributor, and only if You agree to indemnify, +defend, and hold each Contributor harmless for any liability +incurred by, or claims asserted against, such Contributor by reason +of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + +To apply the Apache License to your work, attach the following +boilerplate notice, with the fields enclosed by brackets "{}" +replaced with your own identifying information. (Don't include +the brackets!) The text should be enclosed in the appropriate +comment syntax for the file format. We also recommend that a +file or class name and description of purpose be included on the +same "printed page" as the copyright notice for easier +identification within third-party archives. + +Copyright 2020 A11yance + +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 + +http://www.apache.org/licenses/LICENSE-2.0 + +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. + +======================================================================== + +babel-eslint 10.0.1 +-- +Copyright (c) 2014-2016 Sebastian McKenzie + +MIT License + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +======================================================================== + +babel-helper-builder-react-jsx 6.26.0 +-- +Licensed under MIT (https://spdx.org/licenses/MIT.html) + +======================================================================== + +babel-plugin-dynamic-import-node 2.3.3 +-- +MIT License + +Copyright (c) 2016 Airbnb + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +======================================================================== + +babel-plugin-syntax-jsx 6.18.0 +-- +Licensed under MIT (https://spdx.org/licenses/MIT.html) + +======================================================================== + +babel-plugin-transform-react-jsx 6.24.1 +-- +Licensed under MIT (https://spdx.org/licenses/MIT.html) + +======================================================================== + +babel-runtime 6.26.0 +-- +Copyright Sebastian McKenzie ; licensed under MIT (https://spdx.org/licenses/MIT.html) + +======================================================================== + +babel-types 6.26.0 +-- +Copyright Sebastian McKenzie ; licensed under MIT (https://spdx.org/licenses/MIT.html) + +======================================================================== + +balanced-match 1.0.2 +-- +(MIT) + +Copyright (c) 2013 Julian Gruber <julian@juliangruber.com> + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +======================================================================== + +base64-js 1.5.1 +-- +The MIT License (MIT) + +Copyright (c) 2014 Jameson Little + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +======================================================================== + +binaryextensions 4.18.0 +-- + + +

License

+ +Unless stated otherwise all works are: + + + +and licensed under: + + + +

MIT License

+ +
+Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+ + + +======================================================================== + +brace-expansion 1.1.11 +-- +MIT License + +Copyright (c) 2013 Julian Gruber + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +======================================================================== + +browserfs 2.0.0 +-- +BrowserFS's license follows: + +==== + +Copyright (c) 2013, 2014, 2015, 2016, 2017 John Vilk and other BrowserFS contributors. + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +==== + +This license applies to all parts of BrowserFS, except for the following items: + +- The unit tests from Node, located in `test/tests/**/node-*.js`, and their + test fixtures, located in `test/fixtures/files/node`. Their license follows: + """ + Copyright Joyent, Inc. and other Node contributors. All rights reserved. + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to + deal in the Software without restriction, including without limitation the + rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + sell copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. + """ + +- The Emscripten file system in src/generic/emscripten_fs.ts is a modified + version of Emscripten's NODEFS. Emscripten's license follows: + """ + Emscripten is available under 2 licenses, the MIT license and the + University of Illinois/NCSA Open Source License. + + Both are permissive open source licenses, with little if any + practical difference between them. + + The reason for offering both is that (1) the MIT license is + well-known, while (2) the University of Illinois/NCSA Open Source + License allows Emscripten's code to be integrated upstream into + LLVM, which uses that license, should the opportunity arise. + + The full text of both licenses follows. + + ============================================================================== + + Copyright (c) 2010-2011 Emscripten authors, see AUTHORS file. + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. + + ============================================================================== + + Copyright (c) 2010-2011 Emscripten authors, see AUTHORS file. + All rights reserved. + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the + "Software"), to deal with the Software without restriction, including + without limitation the rights to use, copy, modify, merge, publish, + distribute, sublicense, and/or sell copies of the Software, and to + permit persons to whom the Software is furnished to do so, subject to + the following conditions: + + Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimers. + + Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following disclaimers + in the documentation and/or other materials provided with the + distribution. + + Neither the names of Mozilla, + nor the names of its contributors may be used to endorse + or promote products derived from this Software without specific prior + written permission. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL THE CONTRIBUTORS OR COPYRIGHT HOLDERS BE LIABLE FOR + ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + SOFTWARE OR THE USE OR OTHER DEALINGS WITH THE SOFTWARE. + """ + +======================================================================== + +browserslist 4.22.2 +-- +The MIT License (MIT) + +Copyright 2014 Andrey Sitnik and other contributors + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +======================================================================== + +buffer 6.0.3 +-- +The MIT License (MIT) + +Copyright (c) Feross Aboukhadijeh, and other contributors. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +======================================================================== + +call-bind 1.0.2 +-- +MIT License + +Copyright (c) 2020 Jordan Harband + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +======================================================================== + +caniuse-lite 1.0.30001570 +-- +Attribution 4.0 International + +======================================================================= + +Creative Commons Corporation ("Creative Commons") is not a law firm and +does not provide legal services or legal advice. Distribution of +Creative Commons public licenses does not create a lawyer-client or +other relationship. Creative Commons makes its licenses and related +information available on an "as-is" basis. Creative Commons gives no +warranties regarding its licenses, any material licensed under their +terms and conditions, or any related information. Creative Commons +disclaims all liability for damages resulting from their use to the +fullest extent possible. + +Using Creative Commons Public Licenses + +Creative Commons public licenses provide a standard set of terms and +conditions that creators and other rights holders may use to share +original works of authorship and other material subject to copyright +and certain other rights specified in the public license below. The +following considerations are for informational purposes only, are not +exhaustive, and do not form part of our licenses. + + Considerations for licensors: Our public licenses are + intended for use by those authorized to give the public + permission to use material in ways otherwise restricted by + copyright and certain other rights. Our licenses are + irrevocable. Licensors should read and understand the terms + and conditions of the license they choose before applying it. + Licensors should also secure all rights necessary before + applying our licenses so that the public can reuse the + material as expected. Licensors should clearly mark any + material not subject to the license. This includes other CC- + licensed material, or material used under an exception or + limitation to copyright. More considerations for licensors: + wiki.creativecommons.org/Considerations_for_licensors + + Considerations for the public: By using one of our public + licenses, a licensor grants the public permission to use the + licensed material under specified terms and conditions. If + the licensor's permission is not necessary for any reason--for + example, because of any applicable exception or limitation to + copyright--then that use is not regulated by the license. Our + licenses grant only permissions under copyright and certain + other rights that a licensor has authority to grant. Use of + the licensed material may still be restricted for other + reasons, including because others have copyright or other + rights in the material. A licensor may make special requests, + such as asking that all changes be marked or described. + Although not required by our licenses, you are encouraged to + respect those requests where reasonable. More_considerations + for the public: + wiki.creativecommons.org/Considerations_for_licensees + +======================================================================= + +Creative Commons Attribution 4.0 International Public License + +By exercising the Licensed Rights (defined below), You accept and agree +to be bound by the terms and conditions of this Creative Commons +Attribution 4.0 International Public License ("Public License"). To the +extent this Public License may be interpreted as a contract, You are +granted the Licensed Rights in consideration of Your acceptance of +these terms and conditions, and the Licensor grants You such rights in +consideration of benefits the Licensor receives from making the +Licensed Material available under these terms and conditions. + + +Section 1 -- Definitions. + + a. Adapted Material means material subject to Copyright and Similar + Rights that is derived from or based upon the Licensed Material + and in which the Licensed Material is translated, altered, + arranged, transformed, or otherwise modified in a manner requiring + permission under the Copyright and Similar Rights held by the + Licensor. For purposes of this Public License, where the Licensed + Material is a musical work, performance, or sound recording, + Adapted Material is always produced where the Licensed Material is + synched in timed relation with a moving image. + + b. Adapter's License means the license You apply to Your Copyright + and Similar Rights in Your contributions to Adapted Material in + accordance with the terms and conditions of this Public License. + + c. Copyright and Similar Rights means copyright and/or similar rights + closely related to copyright including, without limitation, + performance, broadcast, sound recording, and Sui Generis Database + Rights, without regard to how the rights are labeled or + categorized. For purposes of this Public License, the rights + specified in Section 2(b)(1)-(2) are not Copyright and Similar + Rights. + + d. Effective Technological Measures means those measures that, in the + absence of proper authority, may not be circumvented under laws + fulfilling obligations under Article 11 of the WIPO Copyright + Treaty adopted on December 20, 1996, and/or similar international + agreements. + + e. Exceptions and Limitations means fair use, fair dealing, and/or + any other exception or limitation to Copyright and Similar Rights + that applies to Your use of the Licensed Material. + + f. Licensed Material means the artistic or literary work, database, + or other material to which the Licensor applied this Public + License. + + g. Licensed Rights means the rights granted to You subject to the + terms and conditions of this Public License, which are limited to + all Copyright and Similar Rights that apply to Your use of the + Licensed Material and that the Licensor has authority to license. + + h. Licensor means the individual(s) or entity(ies) granting rights + under this Public License. + + i. Share means to provide material to the public by any means or + process that requires permission under the Licensed Rights, such + as reproduction, public display, public performance, distribution, + dissemination, communication, or importation, and to make material + available to the public including in ways that members of the + public may access the material from a place and at a time + individually chosen by them. + + j. Sui Generis Database Rights means rights other than copyright + resulting from Directive 96/9/EC of the European Parliament and of + the Council of 11 March 1996 on the legal protection of databases, + as amended and/or succeeded, as well as other essentially + equivalent rights anywhere in the world. + + k. You means the individual or entity exercising the Licensed Rights + under this Public License. Your has a corresponding meaning. + + +Section 2 -- Scope. + + a. License grant. + + 1. Subject to the terms and conditions of this Public License, + the Licensor hereby grants You a worldwide, royalty-free, + non-sublicensable, non-exclusive, irrevocable license to + exercise the Licensed Rights in the Licensed Material to: + + a. reproduce and Share the Licensed Material, in whole or + in part; and + + b. produce, reproduce, and Share Adapted Material. + + 2. Exceptions and Limitations. For the avoidance of doubt, where + Exceptions and Limitations apply to Your use, this Public + License does not apply, and You do not need to comply with + its terms and conditions. + + 3. Term. The term of this Public License is specified in Section + 6(a). + + 4. Media and formats; technical modifications allowed. The + Licensor authorizes You to exercise the Licensed Rights in + all media and formats whether now known or hereafter created, + and to make technical modifications necessary to do so. The + Licensor waives and/or agrees not to assert any right or + authority to forbid You from making technical modifications + necessary to exercise the Licensed Rights, including + technical modifications necessary to circumvent Effective + Technological Measures. For purposes of this Public License, + simply making modifications authorized by this Section 2(a) + (4) never produces Adapted Material. + + 5. Downstream recipients. + + a. Offer from the Licensor -- Licensed Material. Every + recipient of the Licensed Material automatically + receives an offer from the Licensor to exercise the + Licensed Rights under the terms and conditions of this + Public License. + + b. No downstream restrictions. You may not offer or impose + any additional or different terms or conditions on, or + apply any Effective Technological Measures to, the + Licensed Material if doing so restricts exercise of the + Licensed Rights by any recipient of the Licensed + Material. + + 6. No endorsement. Nothing in this Public License constitutes or + may be construed as permission to assert or imply that You + are, or that Your use of the Licensed Material is, connected + with, or sponsored, endorsed, or granted official status by, + the Licensor or others designated to receive attribution as + provided in Section 3(a)(1)(A)(i). + + b. Other rights. + + 1. Moral rights, such as the right of integrity, are not + licensed under this Public License, nor are publicity, + privacy, and/or other similar personality rights; however, to + the extent possible, the Licensor waives and/or agrees not to + assert any such rights held by the Licensor to the limited + extent necessary to allow You to exercise the Licensed + Rights, but not otherwise. + + 2. Patent and trademark rights are not licensed under this + Public License. + + 3. To the extent possible, the Licensor waives any right to + collect royalties from You for the exercise of the Licensed + Rights, whether directly or through a collecting society + under any voluntary or waivable statutory or compulsory + licensing scheme. In all other cases the Licensor expressly + reserves any right to collect such royalties. + + +Section 3 -- License Conditions. + +Your exercise of the Licensed Rights is expressly made subject to the +following conditions. + + a. Attribution. + + 1. If You Share the Licensed Material (including in modified + form), You must: + + a. retain the following if it is supplied by the Licensor + with the Licensed Material: + + i. identification of the creator(s) of the Licensed + Material and any others designated to receive + attribution, in any reasonable manner requested by + the Licensor (including by pseudonym if + designated); + + ii. a copyright notice; + + iii. a notice that refers to this Public License; + + iv. a notice that refers to the disclaimer of + warranties; + + v. a URI or hyperlink to the Licensed Material to the + extent reasonably practicable; + + b. indicate if You modified the Licensed Material and + retain an indication of any previous modifications; and + + c. indicate the Licensed Material is licensed under this + Public License, and include the text of, or the URI or + hyperlink to, this Public License. + + 2. You may satisfy the conditions in Section 3(a)(1) in any + reasonable manner based on the medium, means, and context in + which You Share the Licensed Material. For example, it may be + reasonable to satisfy the conditions by providing a URI or + hyperlink to a resource that includes the required + information. + + 3. If requested by the Licensor, You must remove any of the + information required by Section 3(a)(1)(A) to the extent + reasonably practicable. + + 4. If You Share Adapted Material You produce, the Adapter's + License You apply must not prevent recipients of the Adapted + Material from complying with this Public License. + + +Section 4 -- Sui Generis Database Rights. + +Where the Licensed Rights include Sui Generis Database Rights that +apply to Your use of the Licensed Material: + + a. for the avoidance of doubt, Section 2(a)(1) grants You the right + to extract, reuse, reproduce, and Share all or a substantial + portion of the contents of the database; + + b. if You include all or a substantial portion of the database + contents in a database in which You have Sui Generis Database + Rights, then the database in which You have Sui Generis Database + Rights (but not its individual contents) is Adapted Material; and + + c. You must comply with the conditions in Section 3(a) if You Share + all or a substantial portion of the contents of the database. + +For the avoidance of doubt, this Section 4 supplements and does not +replace Your obligations under this Public License where the Licensed +Rights include other Copyright and Similar Rights. + + +Section 5 -- Disclaimer of Warranties and Limitation of Liability. + + a. UNLESS OTHERWISE SEPARATELY UNDERTAKEN BY THE LICENSOR, TO THE + EXTENT POSSIBLE, THE LICENSOR OFFERS THE LICENSED MATERIAL AS-IS + AND AS-AVAILABLE, AND MAKES NO REPRESENTATIONS OR WARRANTIES OF + ANY KIND CONCERNING THE LICENSED MATERIAL, WHETHER EXPRESS, + IMPLIED, STATUTORY, OR OTHER. THIS INCLUDES, WITHOUT LIMITATION, + WARRANTIES OF TITLE, MERCHANTABILITY, FITNESS FOR A PARTICULAR + PURPOSE, NON-INFRINGEMENT, ABSENCE OF LATENT OR OTHER DEFECTS, + ACCURACY, OR THE PRESENCE OR ABSENCE OF ERRORS, WHETHER OR NOT + KNOWN OR DISCOVERABLE. WHERE DISCLAIMERS OF WARRANTIES ARE NOT + ALLOWED IN FULL OR IN PART, THIS DISCLAIMER MAY NOT APPLY TO YOU. + + b. TO THE EXTENT POSSIBLE, IN NO EVENT WILL THE LICENSOR BE LIABLE + TO YOU ON ANY LEGAL THEORY (INCLUDING, WITHOUT LIMITATION, + NEGLIGENCE) OR OTHERWISE FOR ANY DIRECT, SPECIAL, INDIRECT, + INCIDENTAL, CONSEQUENTIAL, PUNITIVE, EXEMPLARY, OR OTHER LOSSES, + COSTS, EXPENSES, OR DAMAGES ARISING OUT OF THIS PUBLIC LICENSE OR + USE OF THE LICENSED MATERIAL, EVEN IF THE LICENSOR HAS BEEN + ADVISED OF THE POSSIBILITY OF SUCH LOSSES, COSTS, EXPENSES, OR + DAMAGES. WHERE A LIMITATION OF LIABILITY IS NOT ALLOWED IN FULL OR + IN PART, THIS LIMITATION MAY NOT APPLY TO YOU. + + c. The disclaimer of warranties and limitation of liability provided + above shall be interpreted in a manner that, to the extent + possible, most closely approximates an absolute disclaimer and + waiver of all liability. + + +Section 6 -- Term and Termination. + + a. This Public License applies for the term of the Copyright and + Similar Rights licensed here. However, if You fail to comply with + this Public License, then Your rights under this Public License + terminate automatically. + + b. Where Your right to use the Licensed Material has terminated under + Section 6(a), it reinstates: + + 1. automatically as of the date the violation is cured, provided + it is cured within 30 days of Your discovery of the + violation; or + + 2. upon express reinstatement by the Licensor. + + For the avoidance of doubt, this Section 6(b) does not affect any + right the Licensor may have to seek remedies for Your violations + of this Public License. + + c. For the avoidance of doubt, the Licensor may also offer the + Licensed Material under separate terms or conditions or stop + distributing the Licensed Material at any time; however, doing so + will not terminate this Public License. + + d. Sections 1, 5, 6, 7, and 8 survive termination of this Public + License. + + +Section 7 -- Other Terms and Conditions. + + a. The Licensor shall not be bound by any additional or different + terms or conditions communicated by You unless expressly agreed. + + b. Any arrangements, understandings, or agreements regarding the + Licensed Material not stated herein are separate from and + independent of the terms and conditions of this Public License. + + +Section 8 -- Interpretation. + + a. For the avoidance of doubt, this Public License does not, and + shall not be interpreted to, reduce, limit, restrict, or impose + conditions on any use of the Licensed Material that could lawfully + be made without permission under this Public License. + + b. To the extent possible, if any provision of this Public License is + deemed unenforceable, it shall be automatically reformed to the + minimum extent necessary to make it enforceable. If the provision + cannot be reformed, it shall be severed from this Public License + without affecting the enforceability of the remaining terms and + conditions. + + c. No term or condition of this Public License will be waived and no + failure to comply consented to unless expressly agreed to by the + Licensor. + + d. Nothing in this Public License constitutes or may be interpreted + as a limitation upon, or waiver of, any privileges and immunities + that apply to the Licensor or You, including from the legal + processes of any jurisdiction or authority. + + +======================================================================= + +Creative Commons is not a party to its public +licenses. Notwithstanding, Creative Commons may elect to apply one of +its public licenses to material it publishes and in those instances +will be considered the “Licensor.” The text of the Creative Commons +public licenses is dedicated to the public domain under the CC0 Public +Domain Dedication. Except for the limited purpose of indicating that +material is shared under a Creative Commons public license or as +otherwise permitted by the Creative Commons policies published at +creativecommons.org/policies, Creative Commons does not authorize the +use of the trademark "Creative Commons" or any other trademark or logo +of Creative Commons without its prior written consent including, +without limitation, in connection with any unauthorized modifications +to any of its public licenses or any other arrangements, +understandings, or agreements concerning use of licensed material. For +the avoidance of doubt, this paragraph does not form part of the +public licenses. + +Creative Commons may be contacted at creativecommons.org. + +======================================================================== + +chalk 2.4.2 +-- +MIT License + +Copyright (c) Sindre Sorhus (sindresorhus.com) + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +======================================================================== + +chroma-js 2.1.0 +-- +###* + * @license + * + * chroma.js - JavaScript library for color conversions + * + * Copyright (c) 2011-2017, Gregor Aisch + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * 3. The name Gregor Aisch may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL GREGOR AISCH OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, + * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * +### + +======================================================================== + +classnames 2.2.6 +-- +The MIT License (MIT) + +Copyright (c) 2017 Jed Watson + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +======================================================================== + +clipboard-polyfill 2.4.6 +-- +# License + +The MIT License (MIT) + +Copyright (c) 2014 Lucas Garron + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +======================================================================== + +clsx 1.2.1 +-- +MIT License + +Copyright (c) Luke Edwards (lukeed.com) + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +======================================================================== + +color-convert 1.9.3 +-- +Copyright (c) 2011-2016 Heather Arthur + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +======================================================================== + +color-name 1.1.3 +-- +The MIT License (MIT) +Copyright (c) 2015 Dmitry Ivanov + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +======================================================================== + +component-classes 1.2.6 +-- +Licensed under MIT (https://spdx.org/licenses/MIT.html) + +======================================================================== + +compute-scroll-into-view 1.0.17 +-- +MIT License + +Copyright (c) 2018 Cody Olsen + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +======================================================================== + +concat-map 0.0.1 +-- +This software is released under the MIT license: + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +======================================================================== + +content-security-policy-builder 2.1.1 +-- +The MIT License (MIT) + +Copyright (c) 2015-2022 Evan Hahn + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +======================================================================== + +convert-source-map 2.0.0 +-- +Copyright 2013 Thorsten Lorenz. +All rights reserved. + +Permission is hereby granted, free of charge, to any person +obtaining a copy of this software and associated documentation +files (the "Software"), to deal in the Software without +restriction, including without limitation the rights to use, +copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. + +======================================================================== + +cookie 0.5.0 +-- +(The MIT License) + +Copyright (c) 2012-2014 Roman Shtylman +Copyright (c) 2015 Douglas Christopher Wilson + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +'Software'), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +======================================================================== + +core-js 2.6.12 +-- +Copyright (c) 2014-2020 Denis Pushkarev + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +======================================================================== + +core-js-pure 3.18.1 +-- +Copyright (c) 2014-2021 Denis Pushkarev + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +======================================================================== + +create-react-class 15.6.3 +-- +MIT License + +Copyright (c) 2013-present, Facebook, Inc. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +======================================================================== + +css-animation 1.6.1 +-- +The MIT License (MIT) + +Copyright (c) 2014-present yiminghe + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +======================================================================== + +css-loader 0.28.4 +-- +Copyright JS Foundation and other contributors + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +'Software'), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +======================================================================== + +css-tree 2.3.1 +-- +Copyright (C) 2016-2022 by Roman Dvornov + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +======================================================================== + +damerau-levenshtein 1.0.7 +-- +BSD 2-Clause License + +Copyright (c) 2018, Tadeusz Łazurski +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +======================================================================== + +debug 4.3.4 +-- +(The MIT License) + +Copyright (c) 2014-2017 TJ Holowaychuk +Copyright (c) 2018-2021 Josh Junon + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software +and associated documentation files (the 'Software'), to deal in the Software without restriction, +including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial +portions of the Software. + +THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT +LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +======================================================================== + +deepmerge 4.2.2 +-- +The MIT License (MIT) + +Copyright (c) 2012 James Halliday, Josh Duff, and other contributors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +======================================================================== + +define-properties 1.2.0 +-- +The MIT License (MIT) + +Copyright (C) 2015 Jordan Harband + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +======================================================================== + +direction 1.0.4 +-- +(The MIT License) + +Copyright (c) 2014 Titus Wormer + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +'Software'), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +======================================================================== + +dnd-core 16.0.1 +-- +The MIT License (MIT) + +Copyright (c) 2015 Dan Abramov + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +======================================================================== + +doctrine 3.0.0 +-- +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +======================================================================== + +dom-align 1.12.4 +-- +The MIT License (MIT) + +Copyright (c) 2014-present yiminghe + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +======================================================================== + +dom-serializer 2.0.0 +-- +License + +(The MIT License) + +Copyright (c) 2014 The cheeriojs contributors + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the 'Software'), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +======================================================================== + +domelementtype 2.3.0 +-- +Copyright (c) Felix Böhm +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + +Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + +Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + +THIS IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS, +EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +======================================================================== + +domhandler 5.0.3 +-- +Copyright (c) Felix Böhm +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + +Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + +Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + +THIS IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS, +EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +======================================================================== + +domutils 3.1.0 +-- +Copyright (c) Felix Böhm +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + +Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + +Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + +THIS IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS, +EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +======================================================================== + +electron-to-chromium 1.4.613 +-- +Copyright 2018 Kilian Valkhof + +Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +======================================================================== + +emoji-regex 9.2.2 +-- +Copyright Mathias Bynens + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +======================================================================== + +entities 4.5.0 +-- +Copyright (c) Felix Böhm +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + +Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + +Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + +THIS IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS, +EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +======================================================================== + +es-abstract 1.21.3 +-- +The MIT License (MIT) + +Copyright (C) 2015 Jordan Harband + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +======================================================================== + +es-to-primitive 1.2.1 +-- +The MIT License (MIT) + +Copyright (c) 2015 Jordan Harband + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +======================================================================== + +escape-string-regexp 4.0.0 +-- +MIT License + +Copyright (c) Sindre Sorhus (https://sindresorhus.com) + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +======================================================================== + +eslint 7.32.0 +-- +Copyright JS Foundation and other contributors, https://js.foundation + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +======================================================================== + +eslint-module-utils 2.8.0 +-- +The MIT License (MIT) + +Copyright (c) 2015 Ben Mosher + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +======================================================================== + +eslint-plugin-import 2.25.3 +-- +The MIT License (MIT) + +Copyright (c) 2015 Ben Mosher + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +======================================================================== + +eslint-plugin-jsx-a11y 6.5.1 +-- +The MIT License (MIT) +Copyright (c) 2016 Ethan Cohen + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +======================================================================== + +eslint-plugin-react 7.23.2 +-- +The MIT License (MIT) + +Copyright (c) 2014 Yannick Croissant + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +======================================================================== + +eslint-plugin-react-hooks 4.2.0 +-- +MIT License + +Copyright (c) Facebook, Inc. and its affiliates. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +======================================================================== + +eslint-scope 5.1.1 +-- +Copyright JS Foundation and other contributors, https://js.foundation +Copyright (C) 2012-2013 Yusuke Suzuki (twitter: @Constellation) and other contributors. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +======================================================================== + +eslint-utils 1.4.3 +-- +MIT License + +Copyright (c) 2018 Toru Nagashima + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +======================================================================== + +eslint-visitor-keys 1.3.0 +-- +Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright contributors + + 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 + + http://www.apache.org/licenses/LICENSE-2.0 + + 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. + +======================================================================== + +espree 6.2.1 +-- +Espree +Copyright JS Foundation and other contributors, https://js.foundation + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +======================================================================== + +esquery 1.4.0 +-- +Copyright (c) 2013, Joel Feenstra +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the ESQuery nor the names of its contributors may + be used to endorse or promote products derived from this software without + specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL JOEL FEENSTRA BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +======================================================================== + +esrecurse 4.3.0 +-- +Licensed under BSD-2-Clause (https://spdx.org/licenses/BSD-2-Clause.html) + +======================================================================== + +estraverse 5.3.0 +-- +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +======================================================================== + +esutils 2.0.3 +-- +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +======================================================================== + +events 1.1.1 +-- +MIT + +Copyright Joyent, Inc. and other Node contributors. + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to permit +persons to whom the Software is furnished to do so, subject to the +following conditions: + +The above copyright notice and this permission notice shall be included +in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +USE OR OTHER DEALINGS IN THE SOFTWARE. + +======================================================================== + +fast-deep-equal 3.1.3 +-- +MIT License + +Copyright (c) 2017 Evgeny Poberezkin + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +======================================================================== + +fast-equals 3.0.3 +-- +MIT License + +Copyright (c) 2017 Tony Quetano + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +======================================================================== + +fast-json-stable-stringify 2.1.0 +-- +This software is released under the MIT license: + +Copyright (c) 2017 Evgeny Poberezkin +Copyright (c) 2013 James Halliday + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +======================================================================== + +fast-memoize 2.5.2 +-- +The MIT License (MIT) + +Copyright (c) 2016 Caio Gondim + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +======================================================================== + +fbjs 0.8.17 +-- +MIT License + +Copyright (c) 2013-present, Facebook, Inc. + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +======================================================================== + +fluids 0.1.10 +-- +Copyright Alec Larson; licensed under MIT (https://spdx.org/licenses/MIT.html) + +======================================================================== + +framer-motion 11.2.13 +-- +The MIT License (MIT) + +Copyright (c) 2018 Framer B.V. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +======================================================================== + +friendly-words 1.1.10 +-- +MIT License + +Copyright (c) 2018 Glitch + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +======================================================================== + +function-bind 1.1.1 +-- +Copyright (c) 2013 Raynos. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +======================================================================== + +functional-red-black-tree 1.0.1 +-- +The MIT License (MIT) + +Copyright (c) 2013 Mikola Lysenko + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +======================================================================== + +functions-have-names 1.2.3 +-- +MIT License + +Copyright (c) 2019 Jordan Harband + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +======================================================================== + +gensync 1.0.0-beta.2 +-- +Copyright 2018 Logan Smyth + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +======================================================================== + +get-intrinsic 1.2.1 +-- +MIT License + +Copyright (c) 2020 Jordan Harband + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +======================================================================== + +get-nonce 1.0.1 +-- +MIT License + +Copyright (c) 2020 Anton Korzunov + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +======================================================================== + +glob-to-regexp 0.4.1 +-- +Copyright Nick Fitzgerald ; licensed under BSD-2-Clause (https://spdx.org/licenses/BSD-2-Clause.html) + +======================================================================== + +globals 11.12.0 +-- +MIT License + +Copyright (c) Sindre Sorhus (sindresorhus.com) + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +======================================================================== + +gopd 1.0.1 +-- +MIT License + +Copyright (c) 2022 Jordan Harband + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +======================================================================== + +has 1.0.3 +-- +Copyright (c) 2013 Thiago de Arruda + +Permission is hereby granted, free of charge, to any person +obtaining a copy of this software and associated documentation +files (the "Software"), to deal in the Software without +restriction, including without limitation the rights to use, +copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. + +======================================================================== + +has-property-descriptors 1.0.0 +-- +MIT License + +Copyright (c) 2022 Inspect JS + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +======================================================================== + +has-proto 1.0.1 +-- +MIT License + +Copyright (c) 2022 Inspect JS + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +======================================================================== + +has-symbols 1.0.3 +-- +MIT License + +Copyright (c) 2016 Jordan Harband + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +======================================================================== + +has-tostringtag 1.0.0 +-- +MIT License + +Copyright (c) 2021 Inspect JS + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +======================================================================== + +he 1.2.0 +-- +Copyright Mathias Bynens + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +======================================================================== + +hoist-non-react-statics 3.3.2 +-- +Software License Agreement (BSD License) +======================================== + +Copyright (c) 2015, Yahoo! Inc. All rights reserved. +---------------------------------------------------- + +Redistribution and use of this software in source and binary forms, with or +without modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + * Neither the name of Yahoo! Inc. nor the names of YUI's contributors may be + used to endorse or promote products derived from this software without + specific prior written permission of Yahoo! Inc. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +======================================================================== + +hosted-git-info 4.0.2 +-- +Copyright (c) 2015, Rebecca Turner + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH +REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND +FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, +INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM +LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR +OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +PERFORMANCE OF THIS SOFTWARE. + +======================================================================== + +html-dom-parser 0.3.0 +-- +MIT License + +Copyright (c) 2016 Menglin "Mark" Xu + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +======================================================================== + +html-entities 1.2.1 +-- +Copyright (c) 2013 Dulin Marat + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +======================================================================== + +html-react-parser 0.13.0 +-- +The MIT License + +Copyright (c) 2016 Menglin "Mark" Xu + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +======================================================================== + +htmlparser2 8.0.2 +-- +Copyright 2010, 2011, Chris Winberry . All rights reserved. +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to +deal in the Software without restriction, including without limitation the +rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +sell copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +IN THE SOFTWARE. + +======================================================================== + +ieee754 1.2.1 +-- +Copyright 2008 Fair Oaks Labs, Inc. + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +======================================================================== + +ignore 4.0.6 +-- +Copyright (c) 2013 Kael Zhang , contributors +http://kael.me/ + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +======================================================================== + +immediate 3.0.6 +-- +Copyright (c) 2012 Barnesandnoble.com, llc, Donavon West, Domenic Denicola, Brian Cavalier + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +======================================================================== + +immer 9.0.21 +-- +MIT License + +Copyright (c) 2017 Michel Weststrate + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +======================================================================== + +immutability-helper 3.1.1 +-- +MIT License + +Copyright (c) 2017 Moshe Kolodny + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +======================================================================== + +inherits 2.0.4 +-- +The ISC License + +Copyright (c) Isaac Z. Schlueter + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH +REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND +FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, +INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM +LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR +OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +PERFORMANCE OF THIS SOFTWARE. + +======================================================================== + +inline-style-parser 0.1.1 +-- +Licensed under MIT (https://spdx.org/licenses/MIT.html) + +======================================================================== + +internal-slot 1.0.5 +-- +MIT License + +Copyright (c) 2019 Jordan Harband + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +======================================================================== + +is-callable 1.2.7 +-- +The MIT License (MIT) + +Copyright (c) 2015 Jordan Harband + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +======================================================================== + +is-core-module 2.12.1 +-- +The MIT License (MIT) + +Copyright (c) 2014 Dave Justice + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +======================================================================== + +is-date-object 1.0.5 +-- +The MIT License (MIT) + +Copyright (c) 2015 Jordan Harband + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +======================================================================== + +is-hotkey 0.1.8 +-- +The MIT License + +Copyright © 2017, [Ian Storm Taylor](https://ianstormtaylor.com) + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +======================================================================== + +is-plain-object 5.0.0 +-- +The MIT License (MIT) + +Copyright (c) 2014-2017, Jon Schlinkert. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +======================================================================== + +is-regex 1.1.4 +-- +The MIT License (MIT) + +Copyright (c) 2014 Jordan Harband + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +======================================================================== + +is-string 1.0.7 +-- +The MIT License (MIT) + +Copyright (c) 2015 Jordan Harband + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +======================================================================== + +is-symbol 1.0.4 +-- +The MIT License (MIT) + +Copyright (c) 2015 Jordan Harband + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +======================================================================== + +isobject 3.0.1 +-- +The MIT License (MIT) + +Copyright (c) 2014-2017, Jon Schlinkert. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +======================================================================== + +istextorbinary 5.11.0 +-- + + +

License

+ +Unless stated otherwise all works are: + + + +and licensed under: + + + +

MIT License

+ +
+Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice (including the next paragraph) shall be included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+ + + +======================================================================== + +jotai 2.4.3 +-- +MIT License + +Copyright (c) 2020-2023 Poimandres + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +======================================================================== + +jotai-devtools 0.6.2 +-- +MIT License + +Copyright (c) 2023 Jotai Labs + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +======================================================================== + +js-base64 3.7.5 +-- +Copyright (c) 2014, Dan Kogai +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +* Neither the name of {{{project}}} nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +======================================================================== + +js-tokens 4.0.0 +-- +The MIT License (MIT) + +Copyright (c) 2014, 2015, 2016, 2017, 2018 Simon Lydell + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +======================================================================== + +jsesc 2.5.2 +-- +Copyright Mathias Bynens + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +======================================================================== + +json-schema-traverse 0.4.1 +-- +MIT License + +Copyright (c) 2017 Evgeny Poberezkin + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +======================================================================== + +json5 0.5.1 +-- +MIT License + +Copyright (c) 2012-2016 Aseem Kishore, and [others](https://github.com/aseemk/json5/contributors). + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +======================================================================== + +jsx-ast-utils 3.2.1 +-- +The MIT License (MIT) +Copyright (c) 2016 Ethan Cohen + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +======================================================================== + +jszip 3.5.0 +-- +JSZip is dual licensed. You may use it under the MIT license *or* the GPLv3 +license. + +The MIT License +=============== + +Copyright (c) 2009-2016 Stuart Knightley, David Duponchel, Franz Buchinger, António Afonso + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + + +GPL version 3 +============= + + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + +======================================================================== + +language-tags 1.0.5 +-- +Copyright Matthew Caruana Galizia ; licensed under MIT (https://spdx.org/licenses/MIT.html) + +======================================================================== + +levn 0.3.0 +-- +Copyright (c) George Zahariev + +Permission is hereby granted, free of charge, to any person +obtaining a copy of this software and associated documentation +files (the "Software"), to deal in the Software without +restriction, including without limitation the rights to use, +copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. + +======================================================================== + +lib0 0.2.87 +-- +The MIT License (MIT) + +Copyright (c) 2019 Kevin Jahns . + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +======================================================================== + +lie 3.3.0 +-- +#Copyright (c) 2014-2018 Calvin Metcalf, Jordan Harband + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +**THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.** + +======================================================================== + +localforage 1.7.3 +-- +Copyright 2014 Mozilla + +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 + +http://www.apache.org/licenses/LICENSE-2.0 + +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. + +======================================================================== + +lodash 4.17.21 +-- +Copyright OpenJS Foundation and other contributors + +Based on Underscore.js, copyright Jeremy Ashkenas, +DocumentCloud and Investigative Reporters & Editors + +This software consists of voluntary contributions made by many +individuals. For exact contribution history, see the revision history +available at https://github.com/lodash/lodash + +The following license applies to all parts of this software except as +documented below: + +==== + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +==== + +Copyright and related rights for sample code are waived via CC0. Sample +code is defined as all source code displayed within the prose of the +documentation. + +CC0: http://creativecommons.org/publicdomain/zero/1.0/ + +==== + +Files located in the node_modules and vendor directories are externally +maintained libraries used by this software which have their own +licenses; we recommend you read them, as their terms may differ from the +terms above. + +======================================================================== + +lodash.clamp 4.0.3 +-- +Copyright jQuery Foundation and other contributors + +Based on Underscore.js, copyright Jeremy Ashkenas, +DocumentCloud and Investigative Reporters & Editors + +This software consists of voluntary contributions made by many +individuals. For exact contribution history, see the revision history +available at https://github.com/lodash/lodash + +The following license applies to all parts of this software except as +documented below: + +==== + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +==== + +Copyright and related rights for sample code are waived via CC0. Sample +code is defined as all source code displayed within the prose of the +documentation. + +CC0: http://creativecommons.org/publicdomain/zero/1.0/ + +==== + +Files located in the node_modules and vendor directories are externally +maintained libraries used by this software which have their own +licenses; we recommend you read them, as their terms may differ from the +terms above. + +======================================================================== + +lodash.debounce 4.0.8 +-- +Copyright jQuery Foundation and other contributors + +Based on Underscore.js, copyright Jeremy Ashkenas, +DocumentCloud and Investigative Reporters & Editors + +This software consists of voluntary contributions made by many +individuals. For exact contribution history, see the revision history +available at https://github.com/lodash/lodash + +The following license applies to all parts of this software except as +documented below: + +==== + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +==== + +Copyright and related rights for sample code are waived via CC0. Sample +code is defined as all source code displayed within the prose of the +documentation. + +CC0: http://creativecommons.org/publicdomain/zero/1.0/ + +==== + +Files located in the node_modules and vendor directories are externally +maintained libraries used by this software which have their own +licenses; we recommend you read them, as their terms may differ from the +terms above. + +======================================================================== + +lodash.findlastindex 4.6.0 +-- +Copyright jQuery Foundation and other contributors + +Based on Underscore.js, copyright Jeremy Ashkenas, +DocumentCloud and Investigative Reporters & Editors + +This software consists of voluntary contributions made by many +individuals. For exact contribution history, see the revision history +available at https://github.com/lodash/lodash + +The following license applies to all parts of this software except as +documented below: + +==== + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +==== + +Copyright and related rights for sample code are waived via CC0. Sample +code is defined as all source code displayed within the prose of the +documentation. + +CC0: http://creativecommons.org/publicdomain/zero/1.0/ + +==== + +Files located in the node_modules and vendor directories are externally +maintained libraries used by this software which have their own +licenses; we recommend you read them, as their terms may differ from the +terms above. + +======================================================================== + +lodash.throttle 4.1.1 +-- +Copyright jQuery Foundation and other contributors + +Based on Underscore.js, copyright Jeremy Ashkenas, +DocumentCloud and Investigative Reporters & Editors + +This software consists of voluntary contributions made by many +individuals. For exact contribution history, see the revision history +available at https://github.com/lodash/lodash + +The following license applies to all parts of this software except as +documented below: + +==== + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +==== + +Copyright and related rights for sample code are waived via CC0. Sample +code is defined as all source code displayed within the prose of the +documentation. + +CC0: http://creativecommons.org/publicdomain/zero/1.0/ + +==== + +Files located in the node_modules and vendor directories are externally +maintained libraries used by this software which have their own +licenses; we recommend you read them, as their terms may differ from the +terms above. + +======================================================================== + +lru-cache 7.10.1 +-- +The ISC License + +Copyright (c) 2010-2022 Isaac Z. Schlueter and Contributors + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR +IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +======================================================================== + +memoize-one 5.2.1 +-- +MIT License + +Copyright (c) 2019 Alexander Reardon + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +======================================================================== + +micro-memoize 4.0.14 +-- +MIT License + +Copyright (c) 2018 Tony Quetano + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +======================================================================== + +mime-db 1.40.0 +-- +The MIT License (MIT) + +Copyright (c) 2014 Jonathan Ong me@jongleberry.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +======================================================================== + +mime-types 2.1.24 +-- +(The MIT License) + +Copyright (c) 2014 Jonathan Ong +Copyright (c) 2015 Douglas Christopher Wilson + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +'Software'), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +======================================================================== + +minimatch 3.0.4 +-- +The ISC License + +Copyright (c) Isaac Z. Schlueter and Contributors + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR +IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +======================================================================== + +moize 6.1.4 +-- +MIT License + +Copyright (c) 2016 Tony Quetano + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +======================================================================== + +ms 2.1.2 +-- +The MIT License (MIT) + +Copyright (c) 2016 Zeit, Inc. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +======================================================================== + +murmurhash3js 3.0.1 +-- +The MIT License (MIT) + +Copyright (c) 2012-2015 Karan Lyons, Sascha Droste + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +======================================================================== + +nanoid 3.3.6 +-- +The MIT License (MIT) + +Copyright 2017 Andrey Sitnik + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +======================================================================== + +natural-compare 1.4.0 +-- +Copyright Lauri Rooden (https://github.com/litejs/natural-compare-lite); licensed under MIT (https://spdx.org/licenses/MIT.html) + +======================================================================== + +node-diff3 3.1.2 +-- +### The MIT License (MIT) + +diff function extracted from Project Synchrotron. + +For more detail please visit: +- https://leastfixedpoint.com/tonyg/kcbbs/projects/synchrotron.html +- https://github.com/tonyg/synchrotron + +Copyright (c) 2006, 2008 Tony Garnock-Jones <tonyg@lshift.net>
+Copyright (c) 2006, 2008 LShift Ltd. <query@lshift.net>
+Copyright (c) 2019 Bryan Housel <bhousel@gmail.com>
+ +Permission is hereby granted, free of charge, to any person +obtaining a copy of this software and associated documentation files +(the "Software"), to deal in the Software without restriction, +including without limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of the Software, +and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS +BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +======================================================================== + +node-html-parser 1.2.20 +-- +Copyright 2019 Tao Qiufeng + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +======================================================================== + +npm-package-arg 8.1.5 +-- +The ISC License + +Copyright (c) npm, Inc. + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR +IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +======================================================================== + +object-assign 4.1.1 +-- +The MIT License (MIT) + +Copyright (c) Sindre Sorhus (sindresorhus.com) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +======================================================================== + +object-inspect 1.12.3 +-- +MIT License + +Copyright (c) 2013 James Halliday + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +======================================================================== + +object-keys 1.1.1 +-- +The MIT License (MIT) + +Copyright (C) 2013 Jordan Harband + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +======================================================================== + +object-path 0.11.4 +-- +The MIT License (MIT) + +Copyright (c) 2015 Mario Casciaro + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +======================================================================== + +object-path-immutable 3.0.0 +-- +The MIT License (MIT) + +Copyright (c) 2015 Mario Casciaro + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +======================================================================== + +object.assign 4.1.4 +-- +The MIT License (MIT) + +Copyright (c) 2014 Jordan Harband + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +======================================================================== + +object.entries 1.1.4 +-- +The MIT License (MIT) + +Copyright (c) 2015 Jordan Harband + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +======================================================================== + +object.fromentries 2.0.4 +-- +MIT License + +Copyright (c) 2018 Jordan Harband + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +======================================================================== + +object.values 1.1.4 +-- +The MIT License (MIT) + +Copyright (c) 2015 Jordan Harband + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +======================================================================== + +pako 1.0.11 +-- +(The MIT License) + +Copyright (C) 2014-2017 by Vitaly Puzrin and Andrei Tuputcyn + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +======================================================================== + +parse-srcset 1.0.2 +-- +The MIT License (MIT) + +Copyright (c) 2014 Alex Bell + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +======================================================================== + +path 0.12.7 +-- +Copyright Joyent, Inc. and other Node contributors. All rights reserved. +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to +deal in the Software without restriction, including without limitation the +rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +sell copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +IN THE SOFTWARE. + +======================================================================== + +path-browserify 1.0.1 +-- +MIT License + +Copyright (c) 2013 James Halliday + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +======================================================================== + +path-parse 1.0.7 +-- +The MIT License (MIT) + +Copyright (c) 2015 Javier Blanco + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +======================================================================== + +picocolors 1.0.0 +-- +ISC License + +Copyright (c) 2021 Alexey Raspopov, Kostiantyn Denysov, Anton Verinov + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +======================================================================== + +platform-detect 3.0.0 +-- +MIT License + +Copyright (c) 2018 Mike Kovařík, Mutiny.cz + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +======================================================================== + +postcss 8.4.27 +-- +The MIT License (MIT) + +Copyright 2013 Andrey Sitnik + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +======================================================================== + +prelude-ls 1.1.2 +-- +Copyright (c) George Zahariev + +Permission is hereby granted, free of charge, to any person +obtaining a copy of this software and associated documentation +files (the "Software"), to deal in the Software without +restriction, including without limitation the rights to use, +copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. + +======================================================================== + +prettier 2.8.8 +-- +# Prettier license + +Prettier is released under the MIT license: + +Copyright © James Long and contributors + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +## Licenses of bundled dependencies + +The published Prettier artifact additionally contains code with the following licenses: +MIT, ISC, BSD-2-Clause, BSD-3-Clause, Apache-2.0, 0BSD + +## Bundled dependencies + +### @angular/compiler@v12.2.16 + +License: MIT +By: angular +Repository: + +---------------------------------------- + +### @babel/code-frame@v7.18.6 + +License: MIT +By: The Babel Team +Repository: + +> MIT License +> +> Copyright (c) 2014-present Sebastian McKenzie and other contributors +> +> Permission is hereby granted, free of charge, to any person obtaining +> a copy of this software and associated documentation files (the +> "Software"), to deal in the Software without restriction, including +> without limitation the rights to use, copy, modify, merge, publish, +> distribute, sublicense, and/or sell copies of the Software, and to +> permit persons to whom the Software is furnished to do so, subject to +> the following conditions: +> +> The above copyright notice and this permission notice shall be +> included in all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +> EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +> MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +> NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +> LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +> OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +> WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +---------------------------------------- + +### @babel/helper-validator-identifier@v7.19.1 + +License: MIT +By: The Babel Team +Repository: + +> MIT License +> +> Copyright (c) 2014-present Sebastian McKenzie and other contributors +> +> Permission is hereby granted, free of charge, to any person obtaining +> a copy of this software and associated documentation files (the +> "Software"), to deal in the Software without restriction, including +> without limitation the rights to use, copy, modify, merge, publish, +> distribute, sublicense, and/or sell copies of the Software, and to +> permit persons to whom the Software is furnished to do so, subject to +> the following conditions: +> +> The above copyright notice and this permission notice shall be +> included in all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +> EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +> MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +> NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +> LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +> OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +> WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +---------------------------------------- + +### @babel/highlight@v7.18.6 + +License: MIT +By: The Babel Team +Repository: + +> MIT License +> +> Copyright (c) 2014-present Sebastian McKenzie and other contributors +> +> Permission is hereby granted, free of charge, to any person obtaining +> a copy of this software and associated documentation files (the +> "Software"), to deal in the Software without restriction, including +> without limitation the rights to use, copy, modify, merge, publish, +> distribute, sublicense, and/or sell copies of the Software, and to +> permit persons to whom the Software is furnished to do so, subject to +> the following conditions: +> +> The above copyright notice and this permission notice shall be +> included in all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +> EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +> MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +> NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +> LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +> OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +> WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +---------------------------------------- + +### @babel/parser@v7.21.3 + +License: MIT +By: The Babel Team +Repository: + +> Copyright (C) 2012-2014 by various contributors (see AUTHORS) +> +> Permission is hereby granted, free of charge, to any person obtaining a copy +> of this software and associated documentation files (the "Software"), to deal +> in the Software without restriction, including without limitation the rights +> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +> copies of the Software, and to permit persons to whom the Software is +> furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in +> all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +> THE SOFTWARE. + +---------------------------------------- + +### @glimmer/env@v0.1.7 + +License: MIT + +> Copyright (c) 2017 Martin Muñoz and contributors. +> +> Permission is hereby granted, free of charge, to any person obtaining a copy of +> this software and associated documentation files (the "Software"), to deal in +> the Software without restriction, including without limitation the rights to +> use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +> of the Software, and to permit persons to whom the Software is furnished to do +> so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in all +> copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +> SOFTWARE. + +---------------------------------------- + +### @glimmer/syntax@v0.84.2 + +License: MIT + +> Copyright (c) 2015 Tilde, Inc. +> +> Permission is hereby granted, free of charge, to any person obtaining a copy of +> this software and associated documentation files (the "Software"), to deal in +> the Software without restriction, including without limitation the rights to +> use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +> of the Software, and to permit persons to whom the Software is furnished to do +> so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in all +> copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +> SOFTWARE. + +---------------------------------------- + +### @glimmer/util@v0.84.2 + +License: MIT + +> Copyright (c) 2015 Tilde, Inc. +> +> Permission is hereby granted, free of charge, to any person obtaining a copy of +> this software and associated documentation files (the "Software"), to deal in +> the Software without restriction, including without limitation the rights to +> use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +> of the Software, and to permit persons to whom the Software is furnished to do +> so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in all +> copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +> SOFTWARE. + +---------------------------------------- + +### @handlebars/parser@v2.0.0 + +License: ISC +Repository: + +---------------------------------------- + +### @iarna/toml@v2.2.5 + +License: ISC +By: Rebecca Turner +Repository: + +> Copyright (c) 2016, Rebecca Turner +> +> Permission to use, copy, modify, and/or distribute this software for any +> purpose with or without fee is hereby granted, provided that the above +> copyright notice and this permission notice appear in all copies. +> +> THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +> WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +> MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +> ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +> WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +> ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +> OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +---------------------------------------- + +### @nodelib/fs.scandir@v2.1.5 + +License: MIT + +> The MIT License (MIT) +> +> Copyright (c) Denis Malinochkin +> +> Permission is hereby granted, free of charge, to any person obtaining a copy +> of this software and associated documentation files (the "Software"), to deal +> in the Software without restriction, including without limitation the rights +> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +> copies of the Software, and to permit persons to whom the Software is +> furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in all +> copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +> SOFTWARE. + +---------------------------------------- + +### @nodelib/fs.stat@v2.0.5 + +License: MIT + +> The MIT License (MIT) +> +> Copyright (c) Denis Malinochkin +> +> Permission is hereby granted, free of charge, to any person obtaining a copy +> of this software and associated documentation files (the "Software"), to deal +> in the Software without restriction, including without limitation the rights +> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +> copies of the Software, and to permit persons to whom the Software is +> furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in all +> copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +> SOFTWARE. + +---------------------------------------- + +### @nodelib/fs.walk@v1.2.8 + +License: MIT + +> The MIT License (MIT) +> +> Copyright (c) Denis Malinochkin +> +> Permission is hereby granted, free of charge, to any person obtaining a copy +> of this software and associated documentation files (the "Software"), to deal +> in the Software without restriction, including without limitation the rights +> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +> copies of the Software, and to permit persons to whom the Software is +> furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in all +> copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +> SOFTWARE. + +---------------------------------------- + +### @typescript-eslint/types@v5.55.0 + +License: MIT +Repository: + +> MIT License +> +> Copyright (c) 2019 typescript-eslint and other contributors +> +> Permission is hereby granted, free of charge, to any person obtaining a copy +> of this software and associated documentation files (the "Software"), to deal +> in the Software without restriction, including without limitation the rights +> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +> copies of the Software, and to permit persons to whom the Software is +> furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in all +> copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +> SOFTWARE. + +---------------------------------------- + +### @typescript-eslint/typescript-estree@v5.55.0 + +License: BSD-2-Clause +Repository: + +> TypeScript ESTree +> +> Originally extracted from: +> +> TypeScript ESLint Parser +> Copyright JS Foundation and other contributors, https://js.foundation +> +> Redistribution and use in source and binary forms, with or without +> modification, are permitted provided that the following conditions are met: +> +> - Redistributions of source code must retain the above copyright +> notice, this list of conditions and the following disclaimer. +> - Redistributions in binary form must reproduce the above copyright +> notice, this list of conditions and the following disclaimer in the +> documentation and/or other materials provided with the distribution. +> +> THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +> AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +> IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +> ARE DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY +> DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +> (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +> LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +> ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +> (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +> THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +---------------------------------------- + +### @typescript-eslint/visitor-keys@v5.55.0 + +License: MIT +Repository: + +> MIT License +> +> Copyright (c) 2019 typescript-eslint and other contributors +> +> Permission is hereby granted, free of charge, to any person obtaining a copy +> of this software and associated documentation files (the "Software"), to deal +> in the Software without restriction, including without limitation the rights +> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +> copies of the Software, and to permit persons to whom the Software is +> furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in all +> copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +> SOFTWARE. + +---------------------------------------- + +### acorn@v8.8.1 + +License: MIT +Repository: + +> MIT License +> +> Copyright (C) 2012-2022 by various contributors (see AUTHORS) +> +> Permission is hereby granted, free of charge, to any person obtaining a copy +> of this software and associated documentation files (the "Software"), to deal +> in the Software without restriction, including without limitation the rights +> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +> copies of the Software, and to permit persons to whom the Software is +> furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in +> all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +> THE SOFTWARE. + +---------------------------------------- + +### acorn-jsx@v5.3.2 + +License: MIT +Repository: + +> Copyright (C) 2012-2017 by Ingvar Stepanyan +> +> Permission is hereby granted, free of charge, to any person obtaining a copy +> of this software and associated documentation files (the "Software"), to deal +> in the Software without restriction, including without limitation the rights +> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +> copies of the Software, and to permit persons to whom the Software is +> furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in +> all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +> THE SOFTWARE. + +---------------------------------------- + +### aggregate-error@v3.1.0 + +License: MIT +By: Sindre Sorhus + +> MIT License +> +> Copyright (c) Sindre Sorhus (sindresorhus.com) +> +> Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +---------------------------------------- + +### angular-estree-parser@v2.5.1 + +License: MIT +By: Ika + +> MIT License +> +> Copyright (c) Ika (https://github.com/ikatyang) +> +> Permission is hereby granted, free of charge, to any person obtaining a copy +> of this software and associated documentation files (the "Software"), to deal +> in the Software without restriction, including without limitation the rights +> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +> copies of the Software, and to permit persons to whom the Software is +> furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in all +> copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +> SOFTWARE. + +---------------------------------------- + +### angular-html-parser@v1.8.0 + +License: MIT +By: Ika + +> MIT License +> +> Copyright (c) Ika (https://github.com/ikatyang) +> +> Permission is hereby granted, free of charge, to any person obtaining a copy +> of this software and associated documentation files (the "Software"), to deal +> in the Software without restriction, including without limitation the rights +> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +> copies of the Software, and to permit persons to whom the Software is +> furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in all +> copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +> SOFTWARE. + +---------------------------------------- + +### ansi-regex@v6.0.1 + +License: MIT +By: Sindre Sorhus + +> MIT License +> +> Copyright (c) Sindre Sorhus (https://sindresorhus.com) +> +> Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +---------------------------------------- + +### ansi-styles@v3.2.1 + +License: MIT +By: Sindre Sorhus + +> MIT License +> +> Copyright (c) Sindre Sorhus (sindresorhus.com) +> +> Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +---------------------------------------- + +### array-union@v2.1.0 + +License: MIT +By: Sindre Sorhus + +> MIT License +> +> Copyright (c) Sindre Sorhus (sindresorhus.com) +> +> Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +---------------------------------------- + +### bail@v1.0.5 + +License: MIT +By: Titus Wormer + +> (The MIT License) +> +> Copyright (c) 2015 Titus Wormer +> +> Permission is hereby granted, free of charge, to any person obtaining +> a copy of this software and associated documentation files (the +> 'Software'), to deal in the Software without restriction, including +> without limitation the rights to use, copy, modify, merge, publish, +> distribute, sublicense, and/or sell copies of the Software, and to +> permit persons to whom the Software is furnished to do so, subject to +> the following conditions: +> +> The above copyright notice and this permission notice shall be +> included in all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, +> EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +> MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +> IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +> CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +> TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +> SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +---------------------------------------- + +### balanced-match@v1.0.2 + +License: MIT +By: Julian Gruber +Repository: + +> (MIT) +> +> Copyright (c) 2013 Julian Gruber <julian@juliangruber.com> +> +> Permission is hereby granted, free of charge, to any person obtaining a copy of +> this software and associated documentation files (the "Software"), to deal in +> the Software without restriction, including without limitation the rights to +> use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +> of the Software, and to permit persons to whom the Software is furnished to do +> so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in all +> copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +> SOFTWARE. + +---------------------------------------- + +### brace-expansion@v1.1.11 + +License: MIT +By: Julian Gruber +Repository: + +> MIT License +> +> Copyright (c) 2013 Julian Gruber +> +> Permission is hereby granted, free of charge, to any person obtaining a copy +> of this software and associated documentation files (the "Software"), to deal +> in the Software without restriction, including without limitation the rights +> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +> copies of the Software, and to permit persons to whom the Software is +> furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in all +> copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +> SOFTWARE. + +---------------------------------------- + +### braces@v3.0.2 + +License: MIT +By: Jon Schlinkert + +> The MIT License (MIT) +> +> Copyright (c) 2014-2018, Jon Schlinkert. +> +> Permission is hereby granted, free of charge, to any person obtaining a copy +> of this software and associated documentation files (the "Software"), to deal +> in the Software without restriction, including without limitation the rights +> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +> copies of the Software, and to permit persons to whom the Software is +> furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in +> all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +> THE SOFTWARE. + +---------------------------------------- + +### camelcase@v6.3.0 + +License: MIT +By: Sindre Sorhus + +> MIT License +> +> Copyright (c) Sindre Sorhus (https://sindresorhus.com) +> +> Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +---------------------------------------- + +### ccount@v1.1.0 + +License: MIT +By: Titus Wormer + +> (The MIT License) +> +> Copyright (c) 2015 Titus Wormer +> +> Permission is hereby granted, free of charge, to any person obtaining +> a copy of this software and associated documentation files (the +> 'Software'), to deal in the Software without restriction, including +> without limitation the rights to use, copy, modify, merge, publish, +> distribute, sublicense, and/or sell copies of the Software, and to +> permit persons to whom the Software is furnished to do so, subject to +> the following conditions: +> +> The above copyright notice and this permission notice shall be +> included in all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, +> EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +> MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +> IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +> CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +> TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +> SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +---------------------------------------- + +### chalk@v2.4.2 + +License: MIT + +> MIT License +> +> Copyright (c) Sindre Sorhus (sindresorhus.com) +> +> Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +---------------------------------------- + +### chalk@v5.0.1 + +License: MIT + +> MIT License +> +> Copyright (c) Sindre Sorhus (https://sindresorhus.com) +> +> Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +---------------------------------------- + +### character-entities@v1.2.4 + +License: MIT +By: Titus Wormer + +> (The MIT License) +> +> Copyright (c) 2015 Titus Wormer +> +> Permission is hereby granted, free of charge, to any person obtaining +> a copy of this software and associated documentation files (the +> 'Software'), to deal in the Software without restriction, including +> without limitation the rights to use, copy, modify, merge, publish, +> distribute, sublicense, and/or sell copies of the Software, and to +> permit persons to whom the Software is furnished to do so, subject to +> the following conditions: +> +> The above copyright notice and this permission notice shall be +> included in all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, +> EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +> MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +> IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +> CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +> TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +> SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +---------------------------------------- + +### character-entities-legacy@v1.1.4 + +License: MIT +By: Titus Wormer + +> (The MIT License) +> +> Copyright (c) 2015 Titus Wormer +> +> Permission is hereby granted, free of charge, to any person obtaining +> a copy of this software and associated documentation files (the +> 'Software'), to deal in the Software without restriction, including +> without limitation the rights to use, copy, modify, merge, publish, +> distribute, sublicense, and/or sell copies of the Software, and to +> permit persons to whom the Software is furnished to do so, subject to +> the following conditions: +> +> The above copyright notice and this permission notice shall be +> included in all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, +> EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +> MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +> IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +> CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +> TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +> SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +---------------------------------------- + +### character-reference-invalid@v1.1.4 + +License: MIT +By: Titus Wormer + +> (The MIT License) +> +> Copyright (c) 2015 Titus Wormer +> +> Permission is hereby granted, free of charge, to any person obtaining +> a copy of this software and associated documentation files (the +> 'Software'), to deal in the Software without restriction, including +> without limitation the rights to use, copy, modify, merge, publish, +> distribute, sublicense, and/or sell copies of the Software, and to +> permit persons to whom the Software is furnished to do so, subject to +> the following conditions: +> +> The above copyright notice and this permission notice shall be +> included in all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, +> EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +> MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +> IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +> CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +> TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +> SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +---------------------------------------- + +### ci-info@v3.3.0 + +License: MIT +By: Thomas Watson Steen +Repository: + +> The MIT License (MIT) +> +> Copyright (c) 2016-2021 Thomas Watson Steen +> +> Permission is hereby granted, free of charge, to any person obtaining a copy +> of this software and associated documentation files (the "Software"), to deal +> in the Software without restriction, including without limitation the rights +> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +> copies of the Software, and to permit persons to whom the Software is +> furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in all +> copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +> SOFTWARE. + +---------------------------------------- + +### clean-stack@v2.2.0 + +License: MIT +By: Sindre Sorhus + +> MIT License +> +> Copyright (c) Sindre Sorhus (sindresorhus.com) +> +> Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +---------------------------------------- + +### clone@v1.0.4 + +License: MIT +By: Paul Vorbach +Repository: + +> Copyright © 2011-2015 Paul Vorbach +> +> Permission is hereby granted, free of charge, to any person obtaining a copy of +> this software and associated documentation files (the “Software”), to deal in +> the Software without restriction, including without limitation the rights to +> use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +> the Software, and to permit persons to whom the Software is furnished to do so, +> subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in all +> copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +> FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +> COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +> IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, OUT OF OR IN CONNECTION WITH THE +> SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +---------------------------------------- + +### collapse-white-space@v1.0.6 + +License: MIT +By: Titus Wormer + +> (The MIT License) +> +> Copyright (c) 2015 Titus Wormer +> +> Permission is hereby granted, free of charge, to any person obtaining +> a copy of this software and associated documentation files (the +> 'Software'), to deal in the Software without restriction, including +> without limitation the rights to use, copy, modify, merge, publish, +> distribute, sublicense, and/or sell copies of the Software, and to +> permit persons to whom the Software is furnished to do so, subject to +> the following conditions: +> +> The above copyright notice and this permission notice shall be +> included in all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, +> EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +> MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +> IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +> CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +> TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +> SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +---------------------------------------- + +### color-convert@v1.9.3 + +License: MIT +By: Heather Arthur + +> Copyright (c) 2011-2016 Heather Arthur +> +> Permission is hereby granted, free of charge, to any person obtaining +> a copy of this software and associated documentation files (the +> "Software"), to deal in the Software without restriction, including +> without limitation the rights to use, copy, modify, merge, publish, +> distribute, sublicense, and/or sell copies of the Software, and to +> permit persons to whom the Software is furnished to do so, subject to +> the following conditions: +> +> The above copyright notice and this permission notice shall be +> included in all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +> EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +> MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +> NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +> LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +> OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +> WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +---------------------------------------- + +### color-name@v1.1.3 + +License: MIT +By: DY +Repository: + +> The MIT License (MIT) +> Copyright (c) 2015 Dmitry Ivanov +> +> Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +---------------------------------------- + +### commondir@v1.0.1 + +License: MIT +By: James Halliday +Repository: + +> The MIT License +> +> Copyright (c) 2013 James Halliday (mail@substack.net) +> +> Permission is hereby granted, free of charge, +> to any person obtaining a copy of this software and +> associated documentation files (the "Software"), to +> deal in the Software without restriction, including +> without limitation the rights to use, copy, modify, +> merge, publish, distribute, sublicense, and/or sell +> copies of the Software, and to permit persons to whom +> the Software is furnished to do so, +> subject to the following conditions: +> +> The above copyright notice and this permission notice +> shall be included in all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +> EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +> OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +> IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR +> ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +> TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +> SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +---------------------------------------- + +### concat-map@v0.0.1 + +License: MIT +By: James Halliday +Repository: + +> This software is released under the MIT license: +> +> Permission is hereby granted, free of charge, to any person obtaining a copy of +> this software and associated documentation files (the "Software"), to deal in +> the Software without restriction, including without limitation the rights to +> use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +> the Software, and to permit persons to whom the Software is furnished to do so, +> subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in all +> copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +> FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +> COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +> IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +> CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +---------------------------------------- + +### cosmiconfig@v7.0.1 + +License: MIT +By: David Clark +Repository: + +> The MIT License (MIT) +> +> Copyright (c) 2015 David Clark +> +> Permission is hereby granted, free of charge, to any person obtaining a copy +> of this software and associated documentation files (the "Software"), to deal +> in the Software without restriction, including without limitation the rights +> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +> copies of the Software, and to permit persons to whom the Software is +> furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in all +> copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +> SOFTWARE. + +---------------------------------------- + +### cross-spawn@v7.0.3 + +License: MIT +By: André Cruz +Repository: + +> The MIT License (MIT) +> +> Copyright (c) 2018 Made With MOXY Lda +> +> Permission is hereby granted, free of charge, to any person obtaining a copy +> of this software and associated documentation files (the "Software"), to deal +> in the Software without restriction, including without limitation the rights +> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +> copies of the Software, and to permit persons to whom the Software is +> furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in +> all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +> THE SOFTWARE. + +---------------------------------------- + +### crypto-random-string@v4.0.0 + +License: MIT +By: Sindre Sorhus + +> MIT License +> +> Copyright (c) Sindre Sorhus (https://sindresorhus.com) +> +> Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +---------------------------------------- + +### css-units-list@v1.1.0 + +License: MIT +By: fisker Cheung + +> MIT License +> +> Copyright (c) fisker Cheung (https://www.fiskercheung.com/) +> +> Permission is hereby granted, free of charge, to any person obtaining a copy +> of this software and associated documentation files (the "Software"), to deal +> in the Software without restriction, including without limitation the rights +> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +> copies of the Software, and to permit persons to whom the Software is +> furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in all +> copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +> SOFTWARE. + +---------------------------------------- + +### dashify@v2.0.0 + +License: MIT +By: Jon Schlinkert + +> The MIT License (MIT) +> +> Copyright (c) 2015-present, Jon Schlinkert. +> +> Permission is hereby granted, free of charge, to any person obtaining a copy +> of this software and associated documentation files (the "Software"), to deal +> in the Software without restriction, including without limitation the rights +> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +> copies of the Software, and to permit persons to whom the Software is +> furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in +> all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +> THE SOFTWARE. + +---------------------------------------- + +### defaults@v1.0.4 + +License: MIT +By: Elijah Insua +Repository: + +> The MIT License (MIT) +> +> Copyright (c) 2022 Sindre Sorhus +> Copyright (c) 2015 Elijah Insua +> +> Permission is hereby granted, free of charge, to any person obtaining a copy +> of this software and associated documentation files (the "Software"), to deal +> in the Software without restriction, including without limitation the rights +> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +> copies of the Software, and to permit persons to whom the Software is +> furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in +> all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +> THE SOFTWARE. + +---------------------------------------- + +### del@v6.1.1 + +License: MIT +By: Sindre Sorhus + +> MIT License +> +> Copyright (c) Sindre Sorhus (https://sindresorhus.com) +> +> Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +---------------------------------------- + +### detect-newline@v3.1.0 + +License: MIT +By: Sindre Sorhus + +> MIT License +> +> Copyright (c) Sindre Sorhus (sindresorhus.com) +> +> Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +---------------------------------------- + +### diff@v5.0.0 + +License: BSD-3-Clause +Repository: + +> Software License Agreement (BSD License) +> +> Copyright (c) 2009-2015, Kevin Decker +> +> All rights reserved. +> +> Redistribution and use of this software in source and binary forms, with or without modification, +> are permitted provided that the following conditions are met: +> +> * Redistributions of source code must retain the above +> copyright notice, this list of conditions and the +> following disclaimer. +> +> * Redistributions in binary form must reproduce the above +> copyright notice, this list of conditions and the +> following disclaimer in the documentation and/or other +> materials provided with the distribution. +> +> * Neither the name of Kevin Decker nor the names of its +> contributors may be used to endorse or promote products +> derived from this software without specific prior +> written permission. +> +> THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR +> IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND +> FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +> CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +> DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +> DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER +> IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT +> OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +---------------------------------------- + +### dir-glob@v3.0.1 + +License: MIT +By: Kevin Mårtensson + +> MIT License +> +> Copyright (c) Kevin Mårtensson (github.com/kevva) +> +> Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +---------------------------------------- + +### editorconfig@v0.15.3 + +License: MIT +By: EditorConfig Team +Repository: + +> Copyright © 2012 EditorConfig Team +> +> Permission is hereby granted, free of charge, to any person obtaining a copy +> of this software and associated documentation files (the “Software”), to deal +> in the Software without restriction, including without limitation the rights +> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +> copies of the Software, and to permit persons to whom the Software is +> furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in +> all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +> THE SOFTWARE. + +---------------------------------------- + +### editorconfig-to-prettier@v1.0.0 + +License: ISC +By: Joseph Frazier +Repository: + +---------------------------------------- + +### emoji-regex@v9.2.2 + +License: MIT +By: Mathias Bynens +Repository: + +> Copyright Mathias Bynens +> +> Permission is hereby granted, free of charge, to any person obtaining +> a copy of this software and associated documentation files (the +> "Software"), to deal in the Software without restriction, including +> without limitation the rights to use, copy, modify, merge, publish, +> distribute, sublicense, and/or sell copies of the Software, and to +> permit persons to whom the Software is furnished to do so, subject to +> the following conditions: +> +> The above copyright notice and this permission notice shall be +> included in all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +> EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +> MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +> NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +> LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +> OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +> WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +---------------------------------------- + +### error-ex@v1.3.2 + +License: MIT + +> The MIT License (MIT) +> +> Copyright (c) 2015 JD Ballard +> +> Permission is hereby granted, free of charge, to any person obtaining a copy +> of this software and associated documentation files (the "Software"), to deal +> in the Software without restriction, including without limitation the rights +> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +> copies of the Software, and to permit persons to whom the Software is +> furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in +> all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +> THE SOFTWARE. + +---------------------------------------- + +### escape-string-regexp@v1.0.5 + +License: MIT +By: Sindre Sorhus + +> The MIT License (MIT) +> +> Copyright (c) Sindre Sorhus (sindresorhus.com) +> +> Permission is hereby granted, free of charge, to any person obtaining a copy +> of this software and associated documentation files (the "Software"), to deal +> in the Software without restriction, including without limitation the rights +> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +> copies of the Software, and to permit persons to whom the Software is +> furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in +> all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +> THE SOFTWARE. + +---------------------------------------- + +### escape-string-regexp@v5.0.0 + +License: MIT +By: Sindre Sorhus + +> MIT License +> +> Copyright (c) Sindre Sorhus (https://sindresorhus.com) +> +> Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +---------------------------------------- + +### eslint-visitor-keys@v3.3.0 + +License: Apache-2.0 +By: Toru Nagashima + +> Apache License +> Version 2.0, January 2004 +> http://www.apache.org/licenses/ +> +> TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION +> +> 1. Definitions. +> +> "License" shall mean the terms and conditions for use, reproduction, +> and distribution as defined by Sections 1 through 9 of this document. +> +> "Licensor" shall mean the copyright owner or entity authorized by +> the copyright owner that is granting the License. +> +> "Legal Entity" shall mean the union of the acting entity and all +> other entities that control, are controlled by, or are under common +> control with that entity. For the purposes of this definition, +> "control" means (i) the power, direct or indirect, to cause the +> direction or management of such entity, whether by contract or +> otherwise, or (ii) ownership of fifty percent (50%) or more of the +> outstanding shares, or (iii) beneficial ownership of such entity. +> +> "You" (or "Your") shall mean an individual or Legal Entity +> exercising permissions granted by this License. +> +> "Source" form shall mean the preferred form for making modifications, +> including but not limited to software source code, documentation +> source, and configuration files. +> +> "Object" form shall mean any form resulting from mechanical +> transformation or translation of a Source form, including but +> not limited to compiled object code, generated documentation, +> and conversions to other media types. +> +> "Work" shall mean the work of authorship, whether in Source or +> Object form, made available under the License, as indicated by a +> copyright notice that is included in or attached to the work +> (an example is provided in the Appendix below). +> +> "Derivative Works" shall mean any work, whether in Source or Object +> form, that is based on (or derived from) the Work and for which the +> editorial revisions, annotations, elaborations, or other modifications +> represent, as a whole, an original work of authorship. For the purposes +> of this License, Derivative Works shall not include works that remain +> separable from, or merely link (or bind by name) to the interfaces of, +> the Work and Derivative Works thereof. +> +> "Contribution" shall mean any work of authorship, including +> the original version of the Work and any modifications or additions +> to that Work or Derivative Works thereof, that is intentionally +> submitted to Licensor for inclusion in the Work by the copyright owner +> or by an individual or Legal Entity authorized to submit on behalf of +> the copyright owner. For the purposes of this definition, "submitted" +> means any form of electronic, verbal, or written communication sent +> to the Licensor or its representatives, including but not limited to +> communication on electronic mailing lists, source code control systems, +> and issue tracking systems that are managed by, or on behalf of, the +> Licensor for the purpose of discussing and improving the Work, but +> excluding communication that is conspicuously marked or otherwise +> designated in writing by the copyright owner as "Not a Contribution." +> +> "Contributor" shall mean Licensor and any individual or Legal Entity +> on behalf of whom a Contribution has been received by Licensor and +> subsequently incorporated within the Work. +> +> 2. Grant of Copyright License. Subject to the terms and conditions of +> this License, each Contributor hereby grants to You a perpetual, +> worldwide, non-exclusive, no-charge, royalty-free, irrevocable +> copyright license to reproduce, prepare Derivative Works of, +> publicly display, publicly perform, sublicense, and distribute the +> Work and such Derivative Works in Source or Object form. +> +> 3. Grant of Patent License. Subject to the terms and conditions of +> this License, each Contributor hereby grants to You a perpetual, +> worldwide, non-exclusive, no-charge, royalty-free, irrevocable +> (except as stated in this section) patent license to make, have made, +> use, offer to sell, sell, import, and otherwise transfer the Work, +> where such license applies only to those patent claims licensable +> by such Contributor that are necessarily infringed by their +> Contribution(s) alone or by combination of their Contribution(s) +> with the Work to which such Contribution(s) was submitted. If You +> institute patent litigation against any entity (including a +> cross-claim or counterclaim in a lawsuit) alleging that the Work +> or a Contribution incorporated within the Work constitutes direct +> or contributory patent infringement, then any patent licenses +> granted to You under this License for that Work shall terminate +> as of the date such litigation is filed. +> +> 4. Redistribution. You may reproduce and distribute copies of the +> Work or Derivative Works thereof in any medium, with or without +> modifications, and in Source or Object form, provided that You +> meet the following conditions: +> +> (a) You must give any other recipients of the Work or +> Derivative Works a copy of this License; and +> +> (b) You must cause any modified files to carry prominent notices +> stating that You changed the files; and +> +> (c) You must retain, in the Source form of any Derivative Works +> that You distribute, all copyright, patent, trademark, and +> attribution notices from the Source form of the Work, +> excluding those notices that do not pertain to any part of +> the Derivative Works; and +> +> (d) If the Work includes a "NOTICE" text file as part of its +> distribution, then any Derivative Works that You distribute must +> include a readable copy of the attribution notices contained +> within such NOTICE file, excluding those notices that do not +> pertain to any part of the Derivative Works, in at least one +> of the following places: within a NOTICE text file distributed +> as part of the Derivative Works; within the Source form or +> documentation, if provided along with the Derivative Works; or, +> within a display generated by the Derivative Works, if and +> wherever such third-party notices normally appear. The contents +> of the NOTICE file are for informational purposes only and +> do not modify the License. You may add Your own attribution +> notices within Derivative Works that You distribute, alongside +> or as an addendum to the NOTICE text from the Work, provided +> that such additional attribution notices cannot be construed +> as modifying the License. +> +> You may add Your own copyright statement to Your modifications and +> may provide additional or different license terms and conditions +> for use, reproduction, or distribution of Your modifications, or +> for any such Derivative Works as a whole, provided Your use, +> reproduction, and distribution of the Work otherwise complies with +> the conditions stated in this License. +> +> 5. Submission of Contributions. Unless You explicitly state otherwise, +> any Contribution intentionally submitted for inclusion in the Work +> by You to the Licensor shall be under the terms and conditions of +> this License, without any additional terms or conditions. +> Notwithstanding the above, nothing herein shall supersede or modify +> the terms of any separate license agreement you may have executed +> with Licensor regarding such Contributions. +> +> 6. Trademarks. This License does not grant permission to use the trade +> names, trademarks, service marks, or product names of the Licensor, +> except as required for reasonable and customary use in describing the +> origin of the Work and reproducing the content of the NOTICE file. +> +> 7. Disclaimer of Warranty. Unless required by applicable law or +> agreed to in writing, Licensor provides the Work (and each +> Contributor provides its Contributions) on an "AS IS" BASIS, +> WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +> implied, including, without limitation, any warranties or conditions +> of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A +> PARTICULAR PURPOSE. You are solely responsible for determining the +> appropriateness of using or redistributing the Work and assume any +> risks associated with Your exercise of permissions under this License. +> +> 8. Limitation of Liability. In no event and under no legal theory, +> whether in tort (including negligence), contract, or otherwise, +> unless required by applicable law (such as deliberate and grossly +> negligent acts) or agreed to in writing, shall any Contributor be +> liable to You for damages, including any direct, indirect, special, +> incidental, or consequential damages of any character arising as a +> result of this License or out of the use or inability to use the +> Work (including but not limited to damages for loss of goodwill, +> work stoppage, computer failure or malfunction, or any and all +> other commercial damages or losses), even if such Contributor +> has been advised of the possibility of such damages. +> +> 9. Accepting Warranty or Additional Liability. While redistributing +> the Work or Derivative Works thereof, You may choose to offer, +> and charge a fee for, acceptance of support, warranty, indemnity, +> or other liability obligations and/or rights consistent with this +> License. However, in accepting such obligations, You may act only +> on Your own behalf and on Your sole responsibility, not on behalf +> of any other Contributor, and only if You agree to indemnify, +> defend, and hold each Contributor harmless for any liability +> incurred by, or claims asserted against, such Contributor by reason +> of your accepting any such warranty or additional liability. +> +> END OF TERMS AND CONDITIONS +> +> APPENDIX: How to apply the Apache License to your work. +> +> To apply the Apache License to your work, attach the following +> boilerplate notice, with the fields enclosed by brackets "{}" +> replaced with your own identifying information. (Don't include +> the brackets!) The text should be enclosed in the appropriate +> comment syntax for the file format. We also recommend that a +> file or class name and description of purpose be included on the +> same "printed page" as the copyright notice for easier +> identification within third-party archives. +> +> Copyright contributors +> +> 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 +> +> http://www.apache.org/licenses/LICENSE-2.0 +> +> 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. + +---------------------------------------- + +### espree@v9.4.1 + +License: BSD-2-Clause +By: Nicholas C. Zakas + +> BSD 2-Clause License +> +> Copyright (c) Open JS Foundation +> All rights reserved. +> +> Redistribution and use in source and binary forms, with or without +> modification, are permitted provided that the following conditions are met: +> +> 1. Redistributions of source code must retain the above copyright notice, this +> list of conditions and the following disclaimer. +> +> 2. Redistributions in binary form must reproduce the above copyright notice, +> this list of conditions and the following disclaimer in the documentation +> and/or other materials provided with the distribution. +> +> THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +> AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +> IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +> DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +> FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +> DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +> SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +> CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +> OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +> OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +---------------------------------------- + +### esutils@v2.0.3 + +License: BSD-2-Clause +Repository: + +> Redistribution and use in source and binary forms, with or without +> modification, are permitted provided that the following conditions are met: +> +> * Redistributions of source code must retain the above copyright +> notice, this list of conditions and the following disclaimer. +> * Redistributions in binary form must reproduce the above copyright +> notice, this list of conditions and the following disclaimer in the +> documentation and/or other materials provided with the distribution. +> +> THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +> AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +> IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +> ARE DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY +> DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +> (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +> LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +> ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +> (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +> THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +---------------------------------------- + +### execa@v6.1.0 + +License: MIT +By: Sindre Sorhus + +> MIT License +> +> Copyright (c) Sindre Sorhus (https://sindresorhus.com) +> +> Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +---------------------------------------- + +### extend@v3.0.2 + +License: MIT +By: Stefan Thomas +Repository: + +> The MIT License (MIT) +> +> Copyright (c) 2014 Stefan Thomas +> +> Permission is hereby granted, free of charge, to any person obtaining +> a copy of this software and associated documentation files (the +> "Software"), to deal in the Software without restriction, including +> without limitation the rights to use, copy, modify, merge, publish, +> distribute, sublicense, and/or sell copies of the Software, and to +> permit persons to whom the Software is furnished to do so, subject to +> the following conditions: +> +> The above copyright notice and this permission notice shall be +> included in all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +> EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +> MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +> NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +> LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +> OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +> WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +---------------------------------------- + +### fast-glob@v3.2.12 + +License: MIT +By: Denis Malinochkin + +> The MIT License (MIT) +> +> Copyright (c) Denis Malinochkin +> +> Permission is hereby granted, free of charge, to any person obtaining a copy +> of this software and associated documentation files (the "Software"), to deal +> in the Software without restriction, including without limitation the rights +> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +> copies of the Software, and to permit persons to whom the Software is +> furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in all +> copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +> SOFTWARE. + +---------------------------------------- + +### fast-json-stable-stringify@v2.1.0 + +License: MIT +By: James Halliday +Repository: + +> This software is released under the MIT license: +> +> Copyright (c) 2017 Evgeny Poberezkin +> Copyright (c) 2013 James Halliday +> +> Permission is hereby granted, free of charge, to any person obtaining a copy of +> this software and associated documentation files (the "Software"), to deal in +> the Software without restriction, including without limitation the rights to +> use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +> the Software, and to permit persons to whom the Software is furnished to do so, +> subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in all +> copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +> FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +> COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +> IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +> CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +---------------------------------------- + +### fastq@v1.14.0 + +License: ISC +By: Matteo Collina +Repository: + +> Copyright (c) 2015-2020, Matteo Collina +> +> Permission to use, copy, modify, and/or distribute this software for any +> purpose with or without fee is hereby granted, provided that the above +> copyright notice and this permission notice appear in all copies. +> +> THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +> WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +> MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +> ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +> WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +> ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +> OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +---------------------------------------- + +### file-entry-cache@v6.0.1 + +License: MIT +By: Roy Riojas + +> The MIT License (MIT) +> +> Copyright (c) 2015 Roy Riojas +> +> Permission is hereby granted, free of charge, to any person obtaining a copy +> of this software and associated documentation files (the "Software"), to deal +> in the Software without restriction, including without limitation the rights +> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +> copies of the Software, and to permit persons to whom the Software is +> furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in all +> copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +> SOFTWARE. + +---------------------------------------- + +### fill-range@v7.0.1 + +License: MIT +By: Jon Schlinkert + +> The MIT License (MIT) +> +> Copyright (c) 2014-present, Jon Schlinkert. +> +> Permission is hereby granted, free of charge, to any person obtaining a copy +> of this software and associated documentation files (the "Software"), to deal +> in the Software without restriction, including without limitation the rights +> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +> copies of the Software, and to permit persons to whom the Software is +> furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in +> all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +> THE SOFTWARE. + +---------------------------------------- + +### find-cache-dir@v3.3.2 + +License: MIT + +> MIT License +> +> Copyright (c) Sindre Sorhus (https://sindresorhus.com) +> +> Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +---------------------------------------- + +### find-parent-dir@v0.3.1 + +License: MIT +By: Thorsten Lorenz +Repository: + +> Copyright 2013 Thorsten Lorenz. +> All rights reserved. +> +> Permission is hereby granted, free of charge, to any person +> obtaining a copy of this software and associated documentation +> files (the "Software"), to deal in the Software without +> restriction, including without limitation the rights to use, +> copy, modify, merge, publish, distribute, sublicense, and/or sell +> copies of the Software, and to permit persons to whom the +> Software is furnished to do so, subject to the following +> conditions: +> +> The above copyright notice and this permission notice shall be +> included in all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +> EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +> OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +> NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +> HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +> WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +> FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +> OTHER DEALINGS IN THE SOFTWARE. + +---------------------------------------- + +### find-up@v4.1.0 + +License: MIT +By: Sindre Sorhus + +> MIT License +> +> Copyright (c) Sindre Sorhus (sindresorhus.com) +> +> Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +---------------------------------------- + +### flat-cache@v3.0.4 + +License: MIT +By: Roy Riojas + +> The MIT License (MIT) +> +> Copyright (c) 2015 Roy Riojas +> +> Permission is hereby granted, free of charge, to any person obtaining a copy +> of this software and associated documentation files (the "Software"), to deal +> in the Software without restriction, including without limitation the rights +> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +> copies of the Software, and to permit persons to whom the Software is +> furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in all +> copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +> SOFTWARE. + +---------------------------------------- + +### flatted@v3.2.7 + +License: ISC +By: Andrea Giammarchi +Repository: + +> ISC License +> +> Copyright (c) 2018-2020, Andrea Giammarchi, @WebReflection +> +> Permission to use, copy, modify, and/or distribute this software for any +> purpose with or without fee is hereby granted, provided that the above +> copyright notice and this permission notice appear in all copies. +> +> THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH +> REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY +> AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, +> INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM +> LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE +> OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +> PERFORMANCE OF THIS SOFTWARE. + +---------------------------------------- + +### flatten@v1.0.3 + +License: MIT +By: Joshua Holbrook +Repository: + +> The MIT License (MIT) +> +> Copyright (c) 2016 Joshua Holbrook +> +> Permission is hereby granted, free of charge, to any person obtaining a copy +> of this software and associated documentation files (the "Software"), to deal +> in the Software without restriction, including without limitation the rights +> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +> copies of the Software, and to permit persons to whom the Software is +> furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in +> all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +> THE SOFTWARE. + +---------------------------------------- + +### flow-parser@v0.180.0 + +License: MIT +By: Flow Team +Repository: + +---------------------------------------- + +### fs.realpath@v1.0.0 + +License: ISC +By: Isaac Z. Schlueter +Repository: + +> The ISC License +> +> Copyright (c) Isaac Z. Schlueter and Contributors +> +> Permission to use, copy, modify, and/or distribute this software for any +> purpose with or without fee is hereby granted, provided that the above +> copyright notice and this permission notice appear in all copies. +> +> THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +> WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +> MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +> ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +> WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +> ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR +> IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +> +> ---- +> +> This library bundles a version of the `fs.realpath` and `fs.realpathSync` +> methods from Node.js v0.10 under the terms of the Node.js MIT license. +> +> Node's license follows, also included at the header of `old.js` which contains +> the licensed code: +> +> Copyright Joyent, Inc. and other Node contributors. +> +> Permission is hereby granted, free of charge, to any person obtaining a +> copy of this software and associated documentation files (the "Software"), +> to deal in the Software without restriction, including without limitation +> the rights to use, copy, modify, merge, publish, distribute, sublicense, +> and/or sell copies of the Software, and to permit persons to whom the +> Software is furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in +> all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +> FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +> DEALINGS IN THE SOFTWARE. + +---------------------------------------- + +### function-bind@v1.1.1 + +License: MIT +By: Raynos + +> Copyright (c) 2013 Raynos. +> +> Permission is hereby granted, free of charge, to any person obtaining a copy +> of this software and associated documentation files (the "Software"), to deal +> in the Software without restriction, including without limitation the rights +> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +> copies of the Software, and to permit persons to whom the Software is +> furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in +> all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +> THE SOFTWARE. + +---------------------------------------- + +### get-stdin@v8.0.0 + +License: MIT +By: Sindre Sorhus + +> MIT License +> +> Copyright (c) Sindre Sorhus (https://sindresorhus.com) +> +> Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +---------------------------------------- + +### get-stream@v6.0.1 + +License: MIT +By: Sindre Sorhus + +> MIT License +> +> Copyright (c) Sindre Sorhus (https://sindresorhus.com) +> +> Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +---------------------------------------- + +### glob@v7.2.3 + +License: ISC +By: Isaac Z. Schlueter +Repository: + +> The ISC License +> +> Copyright (c) Isaac Z. Schlueter and Contributors +> +> Permission to use, copy, modify, and/or distribute this software for any +> purpose with or without fee is hereby granted, provided that the above +> copyright notice and this permission notice appear in all copies. +> +> THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +> WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +> MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +> ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +> WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +> ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR +> IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +> +> ## Glob Logo +> +> Glob's logo created by Tanya Brassie , licensed +> under a Creative Commons Attribution-ShareAlike 4.0 International License +> https://creativecommons.org/licenses/by-sa/4.0/ + +---------------------------------------- + +### glob-parent@v5.1.2 + +License: ISC +By: Gulp Team + +> The ISC License +> +> Copyright (c) 2015, 2019 Elan Shanker +> +> Permission to use, copy, modify, and/or distribute this software for any +> purpose with or without fee is hereby granted, provided that the above +> copyright notice and this permission notice appear in all copies. +> +> THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +> WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +> MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +> ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +> WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +> ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR +> IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +---------------------------------------- + +### globby@v11.1.0 + +License: MIT +By: Sindre Sorhus + +> MIT License +> +> Copyright (c) Sindre Sorhus (sindresorhus.com) +> +> Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +---------------------------------------- + +### graceful-fs@v4.2.10 + +License: ISC +Repository: + +> The ISC License +> +> Copyright (c) 2011-2022 Isaac Z. Schlueter, Ben Noordhuis, and Contributors +> +> Permission to use, copy, modify, and/or distribute this software for any +> purpose with or without fee is hereby granted, provided that the above +> copyright notice and this permission notice appear in all copies. +> +> THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +> WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +> MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +> ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +> WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +> ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR +> IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +---------------------------------------- + +### graphql@v15.6.1 + +License: MIT +Repository: + +> MIT License +> +> Copyright (c) GraphQL Contributors +> +> Permission is hereby granted, free of charge, to any person obtaining a copy +> of this software and associated documentation files (the "Software"), to deal +> in the Software without restriction, including without limitation the rights +> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +> copies of the Software, and to permit persons to whom the Software is +> furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in all +> copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +> SOFTWARE. + +---------------------------------------- + +### has@v1.0.3 + +License: MIT +By: Thiago de Arruda +Repository: + +> Copyright (c) 2013 Thiago de Arruda +> +> Permission is hereby granted, free of charge, to any person +> obtaining a copy of this software and associated documentation +> files (the "Software"), to deal in the Software without +> restriction, including without limitation the rights to use, +> copy, modify, merge, publish, distribute, sublicense, and/or sell +> copies of the Software, and to permit persons to whom the +> Software is furnished to do so, subject to the following +> conditions: +> +> The above copyright notice and this permission notice shall be +> included in all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +> EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +> OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +> NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +> HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +> WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +> FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +> OTHER DEALINGS IN THE SOFTWARE. + +---------------------------------------- + +### has-flag@v3.0.0 + +License: MIT +By: Sindre Sorhus + +> MIT License +> +> Copyright (c) Sindre Sorhus (sindresorhus.com) +> +> Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +---------------------------------------- + +### html-element-attributes@v3.1.0 + +License: MIT +By: Titus Wormer + +> (The MIT License) +> +> Copyright (c) 2016 Titus Wormer +> +> Permission is hereby granted, free of charge, to any person obtaining +> a copy of this software and associated documentation files (the +> 'Software'), to deal in the Software without restriction, including +> without limitation the rights to use, copy, modify, merge, publish, +> distribute, sublicense, and/or sell copies of the Software, and to +> permit persons to whom the Software is furnished to do so, subject to +> the following conditions: +> +> The above copyright notice and this permission notice shall be +> included in all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, +> EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +> MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +> IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +> CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +> TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +> SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +---------------------------------------- + +### html-tag-names@v2.0.1 + +License: MIT +By: Titus Wormer + +> (The MIT License) +> +> Copyright (c) 2016 Titus Wormer +> +> Permission is hereby granted, free of charge, to any person obtaining +> a copy of this software and associated documentation files (the +> 'Software'), to deal in the Software without restriction, including +> without limitation the rights to use, copy, modify, merge, publish, +> distribute, sublicense, and/or sell copies of the Software, and to +> permit persons to whom the Software is furnished to do so, subject to +> the following conditions: +> +> The above copyright notice and this permission notice shall be +> included in all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, +> EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +> MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +> IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +> CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +> TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +> SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +---------------------------------------- + +### human-signals@v3.0.1 + +License: Apache-2.0 +By: ehmicky + +> Apache License +> Version 2.0, January 2004 +> http://www.apache.org/licenses/ +> +> TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION +> +> 1. Definitions. +> +> "License" shall mean the terms and conditions for use, reproduction, +> and distribution as defined by Sections 1 through 9 of this document. +> +> "Licensor" shall mean the copyright owner or entity authorized by +> the copyright owner that is granting the License. +> +> "Legal Entity" shall mean the union of the acting entity and all +> other entities that control, are controlled by, or are under common +> control with that entity. For the purposes of this definition, +> "control" means (i) the power, direct or indirect, to cause the +> direction or management of such entity, whether by contract or +> otherwise, or (ii) ownership of fifty percent (50%) or more of the +> outstanding shares, or (iii) beneficial ownership of such entity. +> +> "You" (or "Your") shall mean an individual or Legal Entity +> exercising permissions granted by this License. +> +> "Source" form shall mean the preferred form for making modifications, +> including but not limited to software source code, documentation +> source, and configuration files. +> +> "Object" form shall mean any form resulting from mechanical +> transformation or translation of a Source form, including but +> not limited to compiled object code, generated documentation, +> and conversions to other media types. +> +> "Work" shall mean the work of authorship, whether in Source or +> Object form, made available under the License, as indicated by a +> copyright notice that is included in or attached to the work +> (an example is provided in the Appendix below). +> +> "Derivative Works" shall mean any work, whether in Source or Object +> form, that is based on (or derived from) the Work and for which the +> editorial revisions, annotations, elaborations, or other modifications +> represent, as a whole, an original work of authorship. For the purposes +> of this License, Derivative Works shall not include works that remain +> separable from, or merely link (or bind by name) to the interfaces of, +> the Work and Derivative Works thereof. +> +> "Contribution" shall mean any work of authorship, including +> the original version of the Work and any modifications or additions +> to that Work or Derivative Works thereof, that is intentionally +> submitted to Licensor for inclusion in the Work by the copyright owner +> or by an individual or Legal Entity authorized to submit on behalf of +> the copyright owner. For the purposes of this definition, "submitted" +> means any form of electronic, verbal, or written communication sent +> to the Licensor or its representatives, including but not limited to +> communication on electronic mailing lists, source code control systems, +> and issue tracking systems that are managed by, or on behalf of, the +> Licensor for the purpose of discussing and improving the Work, but +> excluding communication that is conspicuously marked or otherwise +> designated in writing by the copyright owner as "Not a Contribution." +> +> "Contributor" shall mean Licensor and any individual or Legal Entity +> on behalf of whom a Contribution has been received by Licensor and +> subsequently incorporated within the Work. +> +> 2. Grant of Copyright License. Subject to the terms and conditions of +> this License, each Contributor hereby grants to You a perpetual, +> worldwide, non-exclusive, no-charge, royalty-free, irrevocable +> copyright license to reproduce, prepare Derivative Works of, +> publicly display, publicly perform, sublicense, and distribute the +> Work and such Derivative Works in Source or Object form. +> +> 3. Grant of Patent License. Subject to the terms and conditions of +> this License, each Contributor hereby grants to You a perpetual, +> worldwide, non-exclusive, no-charge, royalty-free, irrevocable +> (except as stated in this section) patent license to make, have made, +> use, offer to sell, sell, import, and otherwise transfer the Work, +> where such license applies only to those patent claims licensable +> by such Contributor that are necessarily infringed by their +> Contribution(s) alone or by combination of their Contribution(s) +> with the Work to which such Contribution(s) was submitted. If You +> institute patent litigation against any entity (including a +> cross-claim or counterclaim in a lawsuit) alleging that the Work +> or a Contribution incorporated within the Work constitutes direct +> or contributory patent infringement, then any patent licenses +> granted to You under this License for that Work shall terminate +> as of the date such litigation is filed. +> +> 4. Redistribution. You may reproduce and distribute copies of the +> Work or Derivative Works thereof in any medium, with or without +> modifications, and in Source or Object form, provided that You +> meet the following conditions: +> +> (a) You must give any other recipients of the Work or +> Derivative Works a copy of this License; and +> +> (b) You must cause any modified files to carry prominent notices +> stating that You changed the files; and +> +> (c) You must retain, in the Source form of any Derivative Works +> that You distribute, all copyright, patent, trademark, and +> attribution notices from the Source form of the Work, +> excluding those notices that do not pertain to any part of +> the Derivative Works; and +> +> (d) If the Work includes a "NOTICE" text file as part of its +> distribution, then any Derivative Works that You distribute must +> include a readable copy of the attribution notices contained +> within such NOTICE file, excluding those notices that do not +> pertain to any part of the Derivative Works, in at least one +> of the following places: within a NOTICE text file distributed +> as part of the Derivative Works; within the Source form or +> documentation, if provided along with the Derivative Works; or, +> within a display generated by the Derivative Works, if and +> wherever such third-party notices normally appear. The contents +> of the NOTICE file are for informational purposes only and +> do not modify the License. You may add Your own attribution +> notices within Derivative Works that You distribute, alongside +> or as an addendum to the NOTICE text from the Work, provided +> that such additional attribution notices cannot be construed +> as modifying the License. +> +> You may add Your own copyright statement to Your modifications and +> may provide additional or different license terms and conditions +> for use, reproduction, or distribution of Your modifications, or +> for any such Derivative Works as a whole, provided Your use, +> reproduction, and distribution of the Work otherwise complies with +> the conditions stated in this License. +> +> 5. Submission of Contributions. Unless You explicitly state otherwise, +> any Contribution intentionally submitted for inclusion in the Work +> by You to the Licensor shall be under the terms and conditions of +> this License, without any additional terms or conditions. +> Notwithstanding the above, nothing herein shall supersede or modify +> the terms of any separate license agreement you may have executed +> with Licensor regarding such Contributions. +> +> 6. Trademarks. This License does not grant permission to use the trade +> names, trademarks, service marks, or product names of the Licensor, +> except as required for reasonable and customary use in describing the +> origin of the Work and reproducing the content of the NOTICE file. +> +> 7. Disclaimer of Warranty. Unless required by applicable law or +> agreed to in writing, Licensor provides the Work (and each +> Contributor provides its Contributions) on an "AS IS" BASIS, +> WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +> implied, including, without limitation, any warranties or conditions +> of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A +> PARTICULAR PURPOSE. You are solely responsible for determining the +> appropriateness of using or redistributing the Work and assume any +> risks associated with Your exercise of permissions under this License. +> +> 8. Limitation of Liability. In no event and under no legal theory, +> whether in tort (including negligence), contract, or otherwise, +> unless required by applicable law (such as deliberate and grossly +> negligent acts) or agreed to in writing, shall any Contributor be +> liable to You for damages, including any direct, indirect, special, +> incidental, or consequential damages of any character arising as a +> result of this License or out of the use or inability to use the +> Work (including but not limited to damages for loss of goodwill, +> work stoppage, computer failure or malfunction, or any and all +> other commercial damages or losses), even if such Contributor +> has been advised of the possibility of such damages. +> +> 9. Accepting Warranty or Additional Liability. While redistributing +> the Work or Derivative Works thereof, You may choose to offer, +> and charge a fee for, acceptance of support, warranty, indemnity, +> or other liability obligations and/or rights consistent with this +> License. However, in accepting such obligations, You may act only +> on Your own behalf and on Your sole responsibility, not on behalf +> of any other Contributor, and only if You agree to indemnify, +> defend, and hold each Contributor harmless for any liability +> incurred by, or claims asserted against, such Contributor by reason +> of your accepting any such warranty or additional liability. +> +> END OF TERMS AND CONDITIONS +> +> APPENDIX: How to apply the Apache License to your work. +> +> To apply the Apache License to your work, attach the following +> boilerplate notice, with the fields enclosed by brackets "[]" +> replaced with your own identifying information. (Don't include +> the brackets!) The text should be enclosed in the appropriate +> comment syntax for the file format. We also recommend that a +> file or class name and description of purpose be included on the +> same "printed page" as the copyright notice for easier +> identification within third-party archives. +> +> Copyright 2021 ehmicky +> +> 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 +> +> http://www.apache.org/licenses/LICENSE-2.0 +> +> 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. + +---------------------------------------- + +### ignore@v5.2.0 + +License: MIT +By: kael +Repository: + +> Copyright (c) 2013 Kael Zhang , contributors +> http://kael.me/ +> +> Permission is hereby granted, free of charge, to any person obtaining +> a copy of this software and associated documentation files (the +> "Software"), to deal in the Software without restriction, including +> without limitation the rights to use, copy, modify, merge, publish, +> distribute, sublicense, and/or sell copies of the Software, and to +> permit persons to whom the Software is furnished to do so, subject to +> the following conditions: +> +> The above copyright notice and this permission notice shall be +> included in all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +> EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +> MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +> NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +> LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +> OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +> WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +---------------------------------------- + +### ignore@v5.2.4 + +License: MIT +By: kael +Repository: + +> Copyright (c) 2013 Kael Zhang , contributors +> http://kael.me/ +> +> Permission is hereby granted, free of charge, to any person obtaining +> a copy of this software and associated documentation files (the +> "Software"), to deal in the Software without restriction, including +> without limitation the rights to use, copy, modify, merge, publish, +> distribute, sublicense, and/or sell copies of the Software, and to +> permit persons to whom the Software is furnished to do so, subject to +> the following conditions: +> +> The above copyright notice and this permission notice shall be +> included in all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +> EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +> MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +> NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +> LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +> OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +> WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +---------------------------------------- + +### import-fresh@v3.3.0 + +License: MIT +By: Sindre Sorhus + +> MIT License +> +> Copyright (c) Sindre Sorhus (https://sindresorhus.com) +> +> Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +---------------------------------------- + +### indent-string@v4.0.0 + +License: MIT +By: Sindre Sorhus + +> MIT License +> +> Copyright (c) Sindre Sorhus (sindresorhus.com) +> +> Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +---------------------------------------- + +### indexes-of@v1.0.1 + +License: MIT +By: Dominic Tarr +Repository: + +> Copyright (c) 2013 Dominic Tarr +> +> Permission is hereby granted, free of charge, +> to any person obtaining a copy of this software and +> associated documentation files (the "Software"), to +> deal in the Software without restriction, including +> without limitation the rights to use, copy, modify, +> merge, publish, distribute, sublicense, and/or sell +> copies of the Software, and to permit persons to whom +> the Software is furnished to do so, +> subject to the following conditions: +> +> The above copyright notice and this permission notice +> shall be included in all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +> EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +> OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +> IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR +> ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +> TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +> SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +---------------------------------------- + +### inflight@v1.0.6 + +License: ISC +By: Isaac Z. Schlueter +Repository: + +> The ISC License +> +> Copyright (c) Isaac Z. Schlueter +> +> Permission to use, copy, modify, and/or distribute this software for any +> purpose with or without fee is hereby granted, provided that the above +> copyright notice and this permission notice appear in all copies. +> +> THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +> WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +> MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +> ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +> WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +> ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR +> IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +---------------------------------------- + +### inherits@v2.0.4 + +License: ISC + +> The ISC License +> +> Copyright (c) Isaac Z. Schlueter +> +> Permission to use, copy, modify, and/or distribute this software for any +> purpose with or without fee is hereby granted, provided that the above +> copyright notice and this permission notice appear in all copies. +> +> THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH +> REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND +> FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, +> INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM +> LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR +> OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +> PERFORMANCE OF THIS SOFTWARE. + +---------------------------------------- + +### is-alphabetical@v1.0.4 + +License: MIT +By: Titus Wormer + +> (The MIT License) +> +> Copyright (c) 2016 Titus Wormer +> +> Permission is hereby granted, free of charge, to any person obtaining +> a copy of this software and associated documentation files (the +> 'Software'), to deal in the Software without restriction, including +> without limitation the rights to use, copy, modify, merge, publish, +> distribute, sublicense, and/or sell copies of the Software, and to +> permit persons to whom the Software is furnished to do so, subject to +> the following conditions: +> +> The above copyright notice and this permission notice shall be +> included in all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, +> EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +> MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +> IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +> CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +> TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +> SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +---------------------------------------- + +### is-alphanumerical@v1.0.4 + +License: MIT +By: Titus Wormer + +> (The MIT License) +> +> Copyright (c) 2016 Titus Wormer +> +> Permission is hereby granted, free of charge, to any person obtaining +> a copy of this software and associated documentation files (the +> 'Software'), to deal in the Software without restriction, including +> without limitation the rights to use, copy, modify, merge, publish, +> distribute, sublicense, and/or sell copies of the Software, and to +> permit persons to whom the Software is furnished to do so, subject to +> the following conditions: +> +> The above copyright notice and this permission notice shall be +> included in all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, +> EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +> MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +> IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +> CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +> TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +> SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +---------------------------------------- + +### is-arrayish@v0.2.1 + +License: MIT +By: Qix +Repository: + +> The MIT License (MIT) +> +> Copyright (c) 2015 JD Ballard +> +> Permission is hereby granted, free of charge, to any person obtaining a copy +> of this software and associated documentation files (the "Software"), to deal +> in the Software without restriction, including without limitation the rights +> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +> copies of the Software, and to permit persons to whom the Software is +> furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in +> all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +> THE SOFTWARE. + +---------------------------------------- + +### is-buffer@v2.0.5 + +License: MIT +By: Feross Aboukhadijeh +Repository: + +> The MIT License (MIT) +> +> Copyright (c) Feross Aboukhadijeh +> +> Permission is hereby granted, free of charge, to any person obtaining a copy +> of this software and associated documentation files (the "Software"), to deal +> in the Software without restriction, including without limitation the rights +> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +> copies of the Software, and to permit persons to whom the Software is +> furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in +> all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +> THE SOFTWARE. + +---------------------------------------- + +### is-core-module@v2.11.0 + +License: MIT +By: Jordan Harband +Repository: + +> The MIT License (MIT) +> +> Copyright (c) 2014 Dave Justice +> +> Permission is hereby granted, free of charge, to any person obtaining a copy of +> this software and associated documentation files (the "Software"), to deal in +> the Software without restriction, including without limitation the rights to +> use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +> the Software, and to permit persons to whom the Software is furnished to do so, +> subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in all +> copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +> FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +> COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +> IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +> CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +---------------------------------------- + +### is-decimal@v1.0.4 + +License: MIT +By: Titus Wormer + +> (The MIT License) +> +> Copyright (c) 2016 Titus Wormer +> +> Permission is hereby granted, free of charge, to any person obtaining +> a copy of this software and associated documentation files (the +> 'Software'), to deal in the Software without restriction, including +> without limitation the rights to use, copy, modify, merge, publish, +> distribute, sublicense, and/or sell copies of the Software, and to +> permit persons to whom the Software is furnished to do so, subject to +> the following conditions: +> +> The above copyright notice and this permission notice shall be +> included in all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, +> EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +> MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +> IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +> CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +> TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +> SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +---------------------------------------- + +### is-extglob@v2.1.1 + +License: MIT +By: Jon Schlinkert + +> The MIT License (MIT) +> +> Copyright (c) 2014-2016, Jon Schlinkert +> +> Permission is hereby granted, free of charge, to any person obtaining a copy +> of this software and associated documentation files (the "Software"), to deal +> in the Software without restriction, including without limitation the rights +> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +> copies of the Software, and to permit persons to whom the Software is +> furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in +> all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +> THE SOFTWARE. + +---------------------------------------- + +### is-fullwidth-code-point@v4.0.0 + +License: MIT +By: Sindre Sorhus + +> MIT License +> +> Copyright (c) Sindre Sorhus (https://sindresorhus.com) +> +> Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +---------------------------------------- + +### is-glob@v4.0.3 + +License: MIT +By: Jon Schlinkert + +> The MIT License (MIT) +> +> Copyright (c) 2014-2017, Jon Schlinkert. +> +> Permission is hereby granted, free of charge, to any person obtaining a copy +> of this software and associated documentation files (the "Software"), to deal +> in the Software without restriction, including without limitation the rights +> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +> copies of the Software, and to permit persons to whom the Software is +> furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in +> all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +> THE SOFTWARE. + +---------------------------------------- + +### is-hexadecimal@v1.0.4 + +License: MIT +By: Titus Wormer + +> (The MIT License) +> +> Copyright (c) 2016 Titus Wormer +> +> Permission is hereby granted, free of charge, to any person obtaining +> a copy of this software and associated documentation files (the +> 'Software'), to deal in the Software without restriction, including +> without limitation the rights to use, copy, modify, merge, publish, +> distribute, sublicense, and/or sell copies of the Software, and to +> permit persons to whom the Software is furnished to do so, subject to +> the following conditions: +> +> The above copyright notice and this permission notice shall be +> included in all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, +> EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +> MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +> IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +> CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +> TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +> SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +---------------------------------------- + +### is-number@v7.0.0 + +License: MIT +By: Jon Schlinkert + +> The MIT License (MIT) +> +> Copyright (c) 2014-present, Jon Schlinkert. +> +> Permission is hereby granted, free of charge, to any person obtaining a copy +> of this software and associated documentation files (the "Software"), to deal +> in the Software without restriction, including without limitation the rights +> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +> copies of the Software, and to permit persons to whom the Software is +> furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in +> all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +> THE SOFTWARE. + +---------------------------------------- + +### is-path-cwd@v2.2.0 + +License: MIT +By: Sindre Sorhus + +> MIT License +> +> Copyright (c) Sindre Sorhus (sindresorhus.com) +> +> Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +---------------------------------------- + +### is-path-inside@v3.0.3 + +License: MIT +By: Sindre Sorhus + +> MIT License +> +> Copyright (c) Sindre Sorhus (sindresorhus.com) +> +> Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +---------------------------------------- + +### is-plain-obj@v2.1.0 + +License: MIT +By: Sindre Sorhus + +> MIT License +> +> Copyright (c) Sindre Sorhus (sindresorhus.com) +> +> Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +---------------------------------------- + +### is-stream@v3.0.0 + +License: MIT +By: Sindre Sorhus + +> MIT License +> +> Copyright (c) Sindre Sorhus (https://sindresorhus.com) +> +> Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +---------------------------------------- + +### is-whitespace-character@v1.0.4 + +License: MIT +By: Titus Wormer + +> (The MIT License) +> +> Copyright (c) 2016 Titus Wormer +> +> Permission is hereby granted, free of charge, to any person obtaining +> a copy of this software and associated documentation files (the +> 'Software'), to deal in the Software without restriction, including +> without limitation the rights to use, copy, modify, merge, publish, +> distribute, sublicense, and/or sell copies of the Software, and to +> permit persons to whom the Software is furnished to do so, subject to +> the following conditions: +> +> The above copyright notice and this permission notice shall be +> included in all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, +> EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +> MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +> IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +> CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +> TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +> SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +---------------------------------------- + +### is-word-character@v1.0.4 + +License: MIT +By: Titus Wormer + +> (The MIT License) +> +> Copyright (c) 2016 Titus Wormer +> +> Permission is hereby granted, free of charge, to any person obtaining +> a copy of this software and associated documentation files (the +> 'Software'), to deal in the Software without restriction, including +> without limitation the rights to use, copy, modify, merge, publish, +> distribute, sublicense, and/or sell copies of the Software, and to +> permit persons to whom the Software is furnished to do so, subject to +> the following conditions: +> +> The above copyright notice and this permission notice shall be +> included in all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, +> EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +> MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +> IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +> CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +> TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +> SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +---------------------------------------- + +### isexe@v2.0.0 + +License: ISC +By: Isaac Z. Schlueter +Repository: + +> The ISC License +> +> Copyright (c) Isaac Z. Schlueter and Contributors +> +> Permission to use, copy, modify, and/or distribute this software for any +> purpose with or without fee is hereby granted, provided that the above +> copyright notice and this permission notice appear in all copies. +> +> THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +> WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +> MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +> ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +> WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +> ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR +> IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +---------------------------------------- + +### jest-docblock@v28.1.1 + +License: MIT +Repository: + +> MIT License +> +> Copyright (c) Facebook, Inc. and its affiliates. +> +> Permission is hereby granted, free of charge, to any person obtaining a copy +> of this software and associated documentation files (the "Software"), to deal +> in the Software without restriction, including without limitation the rights +> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +> copies of the Software, and to permit persons to whom the Software is +> furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in all +> copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +> SOFTWARE. + +---------------------------------------- + +### js-tokens@v4.0.0 + +License: MIT +By: Simon Lydell + +> The MIT License (MIT) +> +> Copyright (c) 2014, 2015, 2016, 2017, 2018 Simon Lydell +> +> Permission is hereby granted, free of charge, to any person obtaining a copy +> of this software and associated documentation files (the "Software"), to deal +> in the Software without restriction, including without limitation the rights +> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +> copies of the Software, and to permit persons to whom the Software is +> furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in +> all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +> THE SOFTWARE. + +---------------------------------------- + +### json-parse-even-better-errors@v2.3.1 + +License: MIT +By: Kat Marchán + +> Copyright 2017 Kat Marchán +> Copyright npm, Inc. +> +> Permission is hereby granted, free of charge, to any person obtaining a +> copy of this software and associated documentation files (the "Software"), +> to deal in the Software without restriction, including without limitation +> the rights to use, copy, modify, merge, publish, distribute, sublicense, +> and/or sell copies of the Software, and to permit persons to whom the +> Software is furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in +> all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +> FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +> DEALINGS IN THE SOFTWARE. +> +> --- +> +> This library is a fork of 'better-json-errors' by Kat Marchán, extended and +> distributed under the terms of the MIT license above. + +---------------------------------------- + +### json5@v2.2.2 + +License: MIT +By: Aseem Kishore +Repository: + +> MIT License +> +> Copyright (c) 2012-2018 Aseem Kishore, and [others]. +> +> Permission is hereby granted, free of charge, to any person obtaining a copy +> of this software and associated documentation files (the "Software"), to deal +> in the Software without restriction, including without limitation the rights +> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +> copies of the Software, and to permit persons to whom the Software is +> furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in all +> copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +> SOFTWARE. +> +> [others]: https://github.com/json5/json5/contributors + +---------------------------------------- + +### leven@v2.1.0 + +License: MIT +By: Sindre Sorhus + +> The MIT License (MIT) +> +> Copyright (c) Sindre Sorhus (sindresorhus.com) +> +> Permission is hereby granted, free of charge, to any person obtaining a copy +> of this software and associated documentation files (the "Software"), to deal +> in the Software without restriction, including without limitation the rights +> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +> copies of the Software, and to permit persons to whom the Software is +> furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in +> all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +> THE SOFTWARE. + +---------------------------------------- + +### leven@v4.0.0 + +License: MIT +By: Sindre Sorhus + +> MIT License +> +> Copyright (c) Sindre Sorhus (https://sindresorhus.com) +> +> Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +---------------------------------------- + +### lines-and-columns@v1.2.4 + +License: MIT +By: Brian Donovan +Repository: + +> The MIT License (MIT) +> +> Copyright (c) 2015 Brian Donovan +> +> Permission is hereby granted, free of charge, to any person obtaining a copy +> of this software and associated documentation files (the "Software"), to deal +> in the Software without restriction, including without limitation the rights +> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +> copies of the Software, and to permit persons to whom the Software is +> furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in +> all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +> THE SOFTWARE. + +---------------------------------------- + +### lines-and-columns@v2.0.3 + +License: MIT +By: Brian Donovan +Repository: + +> The MIT License (MIT) +> +> Copyright (c) 2015 Brian Donovan +> +> Permission is hereby granted, free of charge, to any person obtaining a copy +> of this software and associated documentation files (the "Software"), to deal +> in the Software without restriction, including without limitation the rights +> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +> copies of the Software, and to permit persons to whom the Software is +> furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in +> all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +> THE SOFTWARE. + +---------------------------------------- + +### linguist-languages@v7.21.0 + +License: MIT +By: Ika + +> MIT License +> +> Copyright (c) Ika (https://github.com/ikatyang) +> +> Permission is hereby granted, free of charge, to any person obtaining a copy +> of this software and associated documentation files (the "Software"), to deal +> in the Software without restriction, including without limitation the rights +> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +> copies of the Software, and to permit persons to whom the Software is +> furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in all +> copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +> SOFTWARE. + +---------------------------------------- + +### locate-path@v5.0.0 + +License: MIT +By: Sindre Sorhus + +> MIT License +> +> Copyright (c) Sindre Sorhus (sindresorhus.com) +> +> Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +---------------------------------------- + +### lru-cache@v4.1.5 + +License: ISC +By: Isaac Z. Schlueter + +> The ISC License +> +> Copyright (c) Isaac Z. Schlueter and Contributors +> +> Permission to use, copy, modify, and/or distribute this software for any +> purpose with or without fee is hereby granted, provided that the above +> copyright notice and this permission notice appear in all copies. +> +> THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +> WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +> MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +> ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +> WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +> ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR +> IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +---------------------------------------- + +### lru-cache@v6.0.0 + +License: ISC +By: Isaac Z. Schlueter + +> The ISC License +> +> Copyright (c) Isaac Z. Schlueter and Contributors +> +> Permission to use, copy, modify, and/or distribute this software for any +> purpose with or without fee is hereby granted, provided that the above +> copyright notice and this permission notice appear in all copies. +> +> THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +> WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +> MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +> ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +> WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +> ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR +> IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +---------------------------------------- + +### make-dir@v3.1.0 + +License: MIT +By: Sindre Sorhus + +> MIT License +> +> Copyright (c) Sindre Sorhus (sindresorhus.com) +> +> Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +---------------------------------------- + +### map-age-cleaner@v0.1.3 + +License: MIT +By: Sam Verschueren + +> MIT License +> +> Copyright (c) Sam Verschueren (github.com/SamVerschueren) +> +> Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +---------------------------------------- + +### markdown-escapes@v1.0.4 + +License: MIT +By: Titus Wormer + +> (The MIT License) +> +> Copyright (c) 2016 Titus Wormer +> +> Permission is hereby granted, free of charge, to any person obtaining +> a copy of this software and associated documentation files (the +> 'Software'), to deal in the Software without restriction, including +> without limitation the rights to use, copy, modify, merge, publish, +> distribute, sublicense, and/or sell copies of the Software, and to +> permit persons to whom the Software is furnished to do so, subject to +> the following conditions: +> +> The above copyright notice and this permission notice shall be +> included in all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, +> EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +> MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +> IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +> CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +> TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +> SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +---------------------------------------- + +### mem@v9.0.2 + +License: MIT +By: Sindre Sorhus + +> MIT License +> +> Copyright (c) Sindre Sorhus (https://sindresorhus.com) +> +> Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +---------------------------------------- + +### merge-stream@v2.0.0 + +License: MIT +By: Stephen Sugden + +> The MIT License (MIT) +> +> Copyright (c) Stephen Sugden (stephensugden.com) +> +> Permission is hereby granted, free of charge, to any person obtaining a copy +> of this software and associated documentation files (the "Software"), to deal +> in the Software without restriction, including without limitation the rights +> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +> copies of the Software, and to permit persons to whom the Software is +> furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in +> all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +> THE SOFTWARE. + +---------------------------------------- + +### merge2@v1.4.1 + +License: MIT +Repository: + +> The MIT License (MIT) +> +> Copyright (c) 2014-2020 Teambition +> +> Permission is hereby granted, free of charge, to any person obtaining a copy +> of this software and associated documentation files (the "Software"), to deal +> in the Software without restriction, including without limitation the rights +> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +> copies of the Software, and to permit persons to whom the Software is +> furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in all +> copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +> SOFTWARE. + +---------------------------------------- + +### meriyah@v4.2.1 + +License: ISC +By: Kenny F. +Repository: + +> ISC License +> +> Copyright (c) 2019 and later, KFlash and others. +> +> Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. +> +> THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +---------------------------------------- + +### micromatch@v4.0.5 + +License: MIT +By: Jon Schlinkert + +> The MIT License (MIT) +> +> Copyright (c) 2014-present, Jon Schlinkert. +> +> Permission is hereby granted, free of charge, to any person obtaining a copy +> of this software and associated documentation files (the "Software"), to deal +> in the Software without restriction, including without limitation the rights +> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +> copies of the Software, and to permit persons to whom the Software is +> furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in +> all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +> THE SOFTWARE. + +---------------------------------------- + +### mimic-fn@v4.0.0 + +License: MIT +By: Sindre Sorhus + +> MIT License +> +> Copyright (c) Sindre Sorhus (https://sindresorhus.com) +> +> Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +---------------------------------------- + +### minimatch@v3.1.2 + +License: ISC +By: Isaac Z. Schlueter +Repository: + +> The ISC License +> +> Copyright (c) Isaac Z. Schlueter and Contributors +> +> Permission to use, copy, modify, and/or distribute this software for any +> purpose with or without fee is hereby granted, provided that the above +> copyright notice and this permission notice appear in all copies. +> +> THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +> WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +> MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +> ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +> WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +> ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR +> IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +---------------------------------------- + +### minimist@v1.2.6 + +License: MIT +By: James Halliday +Repository: + +> This software is released under the MIT license: +> +> Permission is hereby granted, free of charge, to any person obtaining a copy of +> this software and associated documentation files (the "Software"), to deal in +> the Software without restriction, including without limitation the rights to +> use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +> the Software, and to permit persons to whom the Software is furnished to do so, +> subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in all +> copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +> FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +> COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +> IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +> CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +---------------------------------------- + +### n-readlines@v1.0.1 + +License: MIT +By: Yoan Arnaudov +Repository: + +> The MIT License (MIT) +> +> Copyright (c) 2013 Liucw +> +> Permission is hereby granted, free of charge, to any person obtaining a copy of +> this software and associated documentation files (the "Software"), to deal in +> the Software without restriction, including without limitation the rights to +> use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +> the Software, and to permit persons to whom the Software is furnished to do so, +> subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in all +> copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +> FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +> COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +> IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +> CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +---------------------------------------- + +### npm-run-path@v5.1.0 + +License: MIT +By: Sindre Sorhus + +> MIT License +> +> Copyright (c) Sindre Sorhus (https://sindresorhus.com) +> +> Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +---------------------------------------- + +### once@v1.4.0 + +License: ISC +By: Isaac Z. Schlueter +Repository: + +> The ISC License +> +> Copyright (c) Isaac Z. Schlueter and Contributors +> +> Permission to use, copy, modify, and/or distribute this software for any +> purpose with or without fee is hereby granted, provided that the above +> copyright notice and this permission notice appear in all copies. +> +> THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +> WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +> MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +> ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +> WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +> ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR +> IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +---------------------------------------- + +### onetime@v6.0.0 + +License: MIT +By: Sindre Sorhus + +> MIT License +> +> Copyright (c) Sindre Sorhus (https://sindresorhus.com) +> +> Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +---------------------------------------- + +### outdent@v0.8.0 + +License: MIT +By: Andrew Bradley +Repository: + +> The MIT License (MIT) +> +> Copyright (c) 2016 Andrew Bradley +> +> Permission is hereby granted, free of charge, to any person obtaining a copy +> of this software and associated documentation files (the "Software"), to deal +> in the Software without restriction, including without limitation the rights +> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +> copies of the Software, and to permit persons to whom the Software is +> furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in all +> copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +> SOFTWARE. + +---------------------------------------- + +### p-defer@v1.0.0 + +License: MIT +By: Sindre Sorhus + +> The MIT License (MIT) +> +> Copyright (c) Sindre Sorhus (sindresorhus.com) +> +> Permission is hereby granted, free of charge, to any person obtaining a copy +> of this software and associated documentation files (the "Software"), to deal +> in the Software without restriction, including without limitation the rights +> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +> copies of the Software, and to permit persons to whom the Software is +> furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in +> all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +> THE SOFTWARE. + +---------------------------------------- + +### p-limit@v2.3.0 + +License: MIT +By: Sindre Sorhus + +> MIT License +> +> Copyright (c) Sindre Sorhus (sindresorhus.com) +> +> Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +---------------------------------------- + +### p-locate@v4.1.0 + +License: MIT +By: Sindre Sorhus + +> MIT License +> +> Copyright (c) Sindre Sorhus (sindresorhus.com) +> +> Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +---------------------------------------- + +### p-map@v4.0.0 + +License: MIT +By: Sindre Sorhus + +> MIT License +> +> Copyright (c) Sindre Sorhus (https://sindresorhus.com) +> +> Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +---------------------------------------- + +### p-try@v2.2.0 + +License: MIT +By: Sindre Sorhus + +> MIT License +> +> Copyright (c) Sindre Sorhus (sindresorhus.com) +> +> Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +---------------------------------------- + +### parse-entities@v2.0.0 + +License: MIT +By: Titus Wormer + +> (The MIT License) +> +> Copyright (c) 2015 Titus Wormer +> +> Permission is hereby granted, free of charge, to any person obtaining +> a copy of this software and associated documentation files (the +> 'Software'), to deal in the Software without restriction, including +> without limitation the rights to use, copy, modify, merge, publish, +> distribute, sublicense, and/or sell copies of the Software, and to +> permit persons to whom the Software is furnished to do so, subject to +> the following conditions: +> +> The above copyright notice and this permission notice shall be +> included in all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, +> EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +> MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +> IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +> CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +> TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +> SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +---------------------------------------- + +### parse-json@v5.2.0 + +License: MIT +By: Sindre Sorhus + +> MIT License +> +> Copyright (c) Sindre Sorhus (https://sindresorhus.com) +> +> Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +---------------------------------------- + +### parse-srcset@v1.0.2 + +License: MIT +By: Alex Bell +Repository: + +> The MIT License (MIT) +> +> Copyright (c) 2014 Alex Bell +> +> Permission is hereby granted, free of charge, to any person obtaining a copy +> of this software and associated documentation files (the "Software"), to deal +> in the Software without restriction, including without limitation the rights +> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +> copies of the Software, and to permit persons to whom the Software is +> furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in all +> copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +> SOFTWARE. + +---------------------------------------- + +### path-exists@v4.0.0 + +License: MIT +By: Sindre Sorhus + +> MIT License +> +> Copyright (c) Sindre Sorhus (sindresorhus.com) +> +> Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +---------------------------------------- + +### path-is-absolute@v1.0.1 + +License: MIT +By: Sindre Sorhus + +> The MIT License (MIT) +> +> Copyright (c) Sindre Sorhus (sindresorhus.com) +> +> Permission is hereby granted, free of charge, to any person obtaining a copy +> of this software and associated documentation files (the "Software"), to deal +> in the Software without restriction, including without limitation the rights +> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +> copies of the Software, and to permit persons to whom the Software is +> furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in +> all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +> THE SOFTWARE. + +---------------------------------------- + +### path-key@v3.1.1 + +License: MIT +By: Sindre Sorhus + +> MIT License +> +> Copyright (c) Sindre Sorhus (sindresorhus.com) +> +> Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +---------------------------------------- + +### path-parse@v1.0.7 + +License: MIT +By: Javier Blanco +Repository: + +> The MIT License (MIT) +> +> Copyright (c) 2015 Javier Blanco +> +> Permission is hereby granted, free of charge, to any person obtaining a copy +> of this software and associated documentation files (the "Software"), to deal +> in the Software without restriction, including without limitation the rights +> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +> copies of the Software, and to permit persons to whom the Software is +> furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in all +> copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +> SOFTWARE. + +---------------------------------------- + +### path-type@v4.0.0 + +License: MIT +By: Sindre Sorhus + +> MIT License +> +> Copyright (c) Sindre Sorhus (sindresorhus.com) +> +> Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +---------------------------------------- + +### picocolors@v0.2.1 + +License: ISC +By: Alexey Raspopov + +> ISC License +> +> Copyright (c) 2021 Alexey Raspopov, Kostiantyn Denysov, Anton Verinov +> +> Permission to use, copy, modify, and/or distribute this software for any +> purpose with or without fee is hereby granted, provided that the above +> copyright notice and this permission notice appear in all copies. +> +> THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +> WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +> MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +> ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +> WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +> ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +> OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +---------------------------------------- + +### picomatch@v2.3.1 + +License: MIT +By: Jon Schlinkert + +> The MIT License (MIT) +> +> Copyright (c) 2017-present, Jon Schlinkert. +> +> Permission is hereby granted, free of charge, to any person obtaining a copy +> of this software and associated documentation files (the "Software"), to deal +> in the Software without restriction, including without limitation the rights +> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +> copies of the Software, and to permit persons to whom the Software is +> furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in +> all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +> THE SOFTWARE. + +---------------------------------------- + +### pkg-dir@v4.2.0 + +License: MIT +By: Sindre Sorhus + +> MIT License +> +> Copyright (c) Sindre Sorhus (sindresorhus.com) +> +> Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +---------------------------------------- + +### please-upgrade-node@v3.2.0 + +License: MIT +By: typicode +Repository: + +> MIT License +> +> Copyright (c) 2017 +> +> Permission is hereby granted, free of charge, to any person obtaining a copy +> of this software and associated documentation files (the "Software"), to deal +> in the Software without restriction, including without limitation the rights +> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +> copies of the Software, and to permit persons to whom the Software is +> furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in all +> copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +> SOFTWARE. + +---------------------------------------- + +### postcss@v7.0.39 + +License: MIT +By: Andrey Sitnik + +> The MIT License (MIT) +> +> Copyright 2013 Andrey Sitnik +> +> Permission is hereby granted, free of charge, to any person obtaining a copy of +> this software and associated documentation files (the "Software"), to deal in +> the Software without restriction, including without limitation the rights to +> use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +> the Software, and to permit persons to whom the Software is furnished to do so, +> subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in all +> copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +> FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +> COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +> IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +> CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +---------------------------------------- + +### postcss-less@v3.1.4 + +License: MIT +By: Denys Kniazevych + +> The MIT License (MIT) +> +> Copyright (c) 2013 Andrey Sitnik +> Copyright (c) 2016 Denys Kniazevych +> Copyright (c) 2016 Pat Sissons +> Copyright (c) 2017 Andrew Powell +> +> Permission is hereby granted, free of charge, to any person obtaining a copy +> of this software and associated documentation files (the "Software"), to deal +> in the Software without restriction, including without limitation the rights +> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +> copies of the Software, and to permit persons to whom the Software is +> furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in all +> copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +> SOFTWARE. + +---------------------------------------- + +### postcss-media-query-parser@v0.2.3 + +License: MIT +By: dryoma +Repository: + +---------------------------------------- + +### postcss-scss@v2.1.1 + +License: MIT +By: Andrey Sitnik + +> The MIT License (MIT) +> +> Copyright 2013 Andrey Sitnik +> +> Permission is hereby granted, free of charge, to any person obtaining a copy of +> this software and associated documentation files (the "Software"), to deal in +> the Software without restriction, including without limitation the rights to +> use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +> the Software, and to permit persons to whom the Software is furnished to do so, +> subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in all +> copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +> FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +> COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +> IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +> CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +---------------------------------------- + +### postcss-selector-parser@v2.2.3 + +License: MIT +By: Ben Briggs + +> Copyright (c) Ben Briggs (http://beneb.info) +> +> Permission is hereby granted, free of charge, to any person +> obtaining a copy of this software and associated documentation +> files (the "Software"), to deal in the Software without +> restriction, including without limitation the rights to use, +> copy, modify, merge, publish, distribute, sublicense, and/or sell +> copies of the Software, and to permit persons to whom the +> Software is furnished to do so, subject to the following +> conditions: +> +> The above copyright notice and this permission notice shall be +> included in all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +> EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +> OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +> NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +> HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +> WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +> FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +> OTHER DEALINGS IN THE SOFTWARE. + +---------------------------------------- + +### postcss-values-parser@v2.0.1 + +License: MIT +By: Andrew Powell (shellscape) + +> Copyright (c) Andrew Powell +> +> Permission is hereby granted, free of charge, to any person +> obtaining a copy of this software and associated documentation +> files (the "Software"), to deal in the Software without +> restriction, including without limitation the rights to use, +> copy, modify, merge, publish, distribute, sublicense, and/or sell +> copies of the Software, and to permit persons to whom the +> Software is furnished to do so, subject to the following +> conditions: +> +> The above copyright notice and this permission notice shall be +> included in all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +> EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +> OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +> NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +> HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +> WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +> FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +> OTHER DEALINGS IN THE SOFTWARE. + +---------------------------------------- + +### pseudomap@v1.0.2 + +License: ISC +By: Isaac Z. Schlueter +Repository: + +> The ISC License +> +> Copyright (c) Isaac Z. Schlueter and Contributors +> +> Permission to use, copy, modify, and/or distribute this software for any +> purpose with or without fee is hereby granted, provided that the above +> copyright notice and this permission notice appear in all copies. +> +> THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +> WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +> MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +> ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +> WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +> ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR +> IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +---------------------------------------- + +### queue-microtask@v1.2.3 + +License: MIT +By: Feross Aboukhadijeh +Repository: + +> The MIT License (MIT) +> +> Copyright (c) Feross Aboukhadijeh +> +> Permission is hereby granted, free of charge, to any person obtaining a copy of +> this software and associated documentation files (the "Software"), to deal in +> the Software without restriction, including without limitation the rights to +> use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +> the Software, and to permit persons to whom the Software is furnished to do so, +> subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in all +> copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +> FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +> COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +> IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +> CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +---------------------------------------- + +### remark-footnotes@v2.0.0 + +License: MIT +By: Titus Wormer + +> (The MIT License) +> +> Copyright (c) 2020 Titus Wormer +> +> Permission is hereby granted, free of charge, to any person obtaining +> a copy of this software and associated documentation files (the +> 'Software'), to deal in the Software without restriction, including +> without limitation the rights to use, copy, modify, merge, publish, +> distribute, sublicense, and/or sell copies of the Software, and to +> permit persons to whom the Software is furnished to do so, subject to +> the following conditions: +> +> The above copyright notice and this permission notice shall be +> included in all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, +> EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +> MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +> IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +> CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +> TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +> SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +---------------------------------------- + +### remark-math@v3.0.1 + +License: MIT +By: Junyoung Choi + +---------------------------------------- + +### remark-parse@v8.0.3 + +License: MIT +By: Titus Wormer + +---------------------------------------- + +### repeat-string@v1.6.1 + +License: MIT +By: Jon Schlinkert + +> The MIT License (MIT) +> +> Copyright (c) 2014-2016, Jon Schlinkert. +> +> Permission is hereby granted, free of charge, to any person obtaining a copy +> of this software and associated documentation files (the "Software"), to deal +> in the Software without restriction, including without limitation the rights +> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +> copies of the Software, and to permit persons to whom the Software is +> furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in +> all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +> THE SOFTWARE. + +---------------------------------------- + +### resolve@v1.22.1 + +License: MIT +By: James Halliday +Repository: + +> MIT License +> +> Copyright (c) 2012 James Halliday +> +> Permission is hereby granted, free of charge, to any person obtaining a copy +> of this software and associated documentation files (the "Software"), to deal +> in the Software without restriction, including without limitation the rights +> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +> copies of the Software, and to permit persons to whom the Software is +> furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in all +> copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +> SOFTWARE. + +---------------------------------------- + +### resolve-from@v4.0.0 + +License: MIT +By: Sindre Sorhus + +> MIT License +> +> Copyright (c) Sindre Sorhus (sindresorhus.com) +> +> Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +---------------------------------------- + +### reusify@v1.0.4 + +License: MIT +By: Matteo Collina +Repository: + +> The MIT License (MIT) +> +> Copyright (c) 2015 Matteo Collina +> +> Permission is hereby granted, free of charge, to any person obtaining a copy +> of this software and associated documentation files (the "Software"), to deal +> in the Software without restriction, including without limitation the rights +> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +> copies of the Software, and to permit persons to whom the Software is +> furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in all +> copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +> SOFTWARE. + +---------------------------------------- + +### rimraf@v3.0.2 + +License: ISC +By: Isaac Z. Schlueter + +> The ISC License +> +> Copyright (c) Isaac Z. Schlueter and Contributors +> +> Permission to use, copy, modify, and/or distribute this software for any +> purpose with or without fee is hereby granted, provided that the above +> copyright notice and this permission notice appear in all copies. +> +> THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +> WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +> MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +> ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +> WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +> ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR +> IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +---------------------------------------- + +### rollup-plugin-node-polyfills@v0.2.1 + +License: MIT +Repository: + +> The MIT License (MIT) +> +> Copyright (c) 2019 these people +> +> Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +---------------------------------------- + +### run-parallel@v1.2.0 + +License: MIT +By: Feross Aboukhadijeh +Repository: + +> The MIT License (MIT) +> +> Copyright (c) Feross Aboukhadijeh +> +> Permission is hereby granted, free of charge, to any person obtaining a copy of +> this software and associated documentation files (the "Software"), to deal in +> the Software without restriction, including without limitation the rights to +> use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +> the Software, and to permit persons to whom the Software is furnished to do so, +> subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in all +> copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +> FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +> COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +> IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +> CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +---------------------------------------- + +### sdbm@v2.0.0 + +License: MIT +By: Sindre Sorhus + +> MIT License +> +> Copyright (c) Sindre Sorhus (https://sindresorhus.com) +> +> Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +---------------------------------------- + +### semver@v6.3.0 + +License: ISC + +> The ISC License +> +> Copyright (c) Isaac Z. Schlueter and Contributors +> +> Permission to use, copy, modify, and/or distribute this software for any +> purpose with or without fee is hereby granted, provided that the above +> copyright notice and this permission notice appear in all copies. +> +> THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +> WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +> MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +> ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +> WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +> ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR +> IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +---------------------------------------- + +### semver@v7.3.7 + +License: ISC +By: GitHub Inc. +Repository: + +> The ISC License +> +> Copyright (c) Isaac Z. Schlueter and Contributors +> +> Permission to use, copy, modify, and/or distribute this software for any +> purpose with or without fee is hereby granted, provided that the above +> copyright notice and this permission notice appear in all copies. +> +> THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +> WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +> MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +> ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +> WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +> ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR +> IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +---------------------------------------- + +### semver@v7.3.8 + +License: ISC +By: GitHub Inc. +Repository: + +> The ISC License +> +> Copyright (c) Isaac Z. Schlueter and Contributors +> +> Permission to use, copy, modify, and/or distribute this software for any +> purpose with or without fee is hereby granted, provided that the above +> copyright notice and this permission notice appear in all copies. +> +> THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +> WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +> MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +> ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +> WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +> ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR +> IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +---------------------------------------- + +### semver-compare@v1.0.0 + +License: MIT +By: James Halliday +Repository: + +> This software is released under the MIT license: +> +> Permission is hereby granted, free of charge, to any person obtaining a copy of +> this software and associated documentation files (the "Software"), to deal in +> the Software without restriction, including without limitation the rights to +> use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +> the Software, and to permit persons to whom the Software is furnished to do so, +> subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in all +> copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +> FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +> COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +> IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +> CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +---------------------------------------- + +### shebang-command@v2.0.0 + +License: MIT +By: Kevin Mårtensson + +> MIT License +> +> Copyright (c) Kevin Mårtensson (github.com/kevva) +> +> Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +---------------------------------------- + +### shebang-regex@v3.0.0 + +License: MIT +By: Sindre Sorhus + +> MIT License +> +> Copyright (c) Sindre Sorhus (sindresorhus.com) +> +> Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +---------------------------------------- + +### sigmund@v1.0.1 + +License: ISC +By: Isaac Z. Schlueter +Repository: + +> The ISC License +> +> Copyright (c) Isaac Z. Schlueter and Contributors +> +> Permission to use, copy, modify, and/or distribute this software for any +> purpose with or without fee is hereby granted, provided that the above +> copyright notice and this permission notice appear in all copies. +> +> THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +> WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +> MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +> ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +> WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +> ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR +> IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +---------------------------------------- + +### signal-exit@v3.0.7 + +License: ISC +By: Ben Coe +Repository: + +> The ISC License +> +> Copyright (c) 2015, Contributors +> +> Permission to use, copy, modify, and/or distribute this software +> for any purpose with or without fee is hereby granted, provided +> that the above copyright notice and this permission notice +> appear in all copies. +> +> THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +> WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES +> OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE +> LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES +> OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, +> WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, +> ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +---------------------------------------- + +### simple-html-tokenizer@v0.5.11 + +License: MIT +Repository: + +> Copyright (c) 2014 Yehuda Katz and contributors +> +> Permission is hereby granted, free of charge, to any person obtaining a copy of +> this software and associated documentation files (the "Software"), to deal in +> the Software without restriction, including without limitation the rights to +> use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +> of the Software, and to permit persons to whom the Software is furnished to do +> so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in all +> copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +> SOFTWARE. + +---------------------------------------- + +### slash@v3.0.0 + +License: MIT +By: Sindre Sorhus + +> MIT License +> +> Copyright (c) Sindre Sorhus (sindresorhus.com) +> +> Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +---------------------------------------- + +### state-toggle@v1.0.3 + +License: MIT +By: Titus Wormer + +> (The MIT License) +> +> Copyright (c) 2016 Titus Wormer +> +> Permission is hereby granted, free of charge, to any person obtaining +> a copy of this software and associated documentation files (the +> 'Software'), to deal in the Software without restriction, including +> without limitation the rights to use, copy, modify, merge, publish, +> distribute, sublicense, and/or sell copies of the Software, and to +> permit persons to whom the Software is furnished to do so, subject to +> the following conditions: +> +> The above copyright notice and this permission notice shall be +> included in all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, +> EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +> MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +> IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +> CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +> TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +> SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +---------------------------------------- + +### string-width@v5.0.1 + +License: MIT +By: Sindre Sorhus + +> MIT License +> +> Copyright (c) Sindre Sorhus (https://sindresorhus.com) +> +> Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +---------------------------------------- + +### strip-ansi@v7.0.1 + +License: MIT +By: Sindre Sorhus + +> MIT License +> +> Copyright (c) Sindre Sorhus (https://sindresorhus.com) +> +> Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +---------------------------------------- + +### strip-final-newline@v3.0.0 + +License: MIT +By: Sindre Sorhus + +> MIT License +> +> Copyright (c) Sindre Sorhus (https://sindresorhus.com) +> +> Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +---------------------------------------- + +### supports-color@v5.5.0 + +License: MIT +By: Sindre Sorhus + +> MIT License +> +> Copyright (c) Sindre Sorhus (sindresorhus.com) +> +> Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +---------------------------------------- + +### temp-dir@v2.0.0 + +License: MIT +By: Sindre Sorhus + +> MIT License +> +> Copyright (c) Sindre Sorhus (sindresorhus.com) +> +> Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +---------------------------------------- + +### tempy@v2.0.0 + +License: MIT +By: Sindre Sorhus + +> MIT License +> +> Copyright (c) Sindre Sorhus (https://sindresorhus.com) +> +> Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +---------------------------------------- + +### to-regex-range@v5.0.1 + +License: MIT +By: Jon Schlinkert + +> The MIT License (MIT) +> +> Copyright (c) 2015-present, Jon Schlinkert. +> +> Permission is hereby granted, free of charge, to any person obtaining a copy +> of this software and associated documentation files (the "Software"), to deal +> in the Software without restriction, including without limitation the rights +> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +> copies of the Software, and to permit persons to whom the Software is +> furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in +> all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +> THE SOFTWARE. + +---------------------------------------- + +### trim@v0.0.1 + +By: TJ Holowaychuk + +---------------------------------------- + +### trim-trailing-lines@v1.1.4 + +License: MIT +By: Titus Wormer + +> (The MIT License) +> +> Copyright (c) 2015 Titus Wormer +> +> Permission is hereby granted, free of charge, to any person obtaining +> a copy of this software and associated documentation files (the +> 'Software'), to deal in the Software without restriction, including +> without limitation the rights to use, copy, modify, merge, publish, +> distribute, sublicense, and/or sell copies of the Software, and to +> permit persons to whom the Software is furnished to do so, subject to +> the following conditions: +> +> The above copyright notice and this permission notice shall be +> included in all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, +> EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +> MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +> IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +> CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +> TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +> SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +---------------------------------------- + +### trough@v1.0.5 + +License: MIT +By: Titus Wormer + +> (The MIT License) +> +> Copyright (c) 2016 Titus Wormer +> +> Permission is hereby granted, free of charge, to any person obtaining a copy +> of this software and associated documentation files (the "Software"), to deal +> in the Software without restriction, including without limitation the rights +> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +> copies of the Software, and to permit persons to whom the Software is +> furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in +> all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +> THE SOFTWARE. + +---------------------------------------- + +### tslib@v1.14.1 + +License: 0BSD +By: Microsoft Corp. +Repository: + +> Copyright (c) Microsoft Corporation. +> +> Permission to use, copy, modify, and/or distribute this software for any +> purpose with or without fee is hereby granted. +> +> THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH +> REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY +> AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, +> INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM +> LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR +> OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +> PERFORMANCE OF THIS SOFTWARE. + +---------------------------------------- + +### tsutils@v3.21.0 + +License: MIT +By: Klaus Meinhardt +Repository: + +> The MIT License (MIT) +> +> Copyright (c) 2017 Klaus Meinhardt +> +> Permission is hereby granted, free of charge, to any person obtaining a copy +> of this software and associated documentation files (the "Software"), to deal +> in the Software without restriction, including without limitation the rights +> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +> copies of the Software, and to permit persons to whom the Software is +> furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in all +> copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +> SOFTWARE. + +---------------------------------------- + +### typescript@v5.0.2 + +License: Apache-2.0 +By: Microsoft Corp. +Repository: + +> Apache License +> +> Version 2.0, January 2004 +> +> http://www.apache.org/licenses/ +> +> TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION +> +> 1. Definitions. +> +> "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. +> +> "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. +> +> "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. +> +> "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. +> +> "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. +> +> "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. +> +> "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). +> +> "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. +> +> "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." +> +> "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. +> +> 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. +> +> 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. +> +> 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: +> +> You must give any other recipients of the Work or Derivative Works a copy of this License; and +> +> You must cause any modified files to carry prominent notices stating that You changed the files; and +> +> You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and +> +> If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. +> +> 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. +> +> 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. +> +> 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. +> +> 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. +> +> 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. +> +> END OF TERMS AND CONDITIONS + +---------------------------------------- + +### unherit@v1.1.3 + +License: MIT +By: Titus Wormer + +> (The MIT License) +> +> Copyright (c) 2015 Titus Wormer +> +> Permission is hereby granted, free of charge, to any person obtaining a copy +> of this software and associated documentation files (the "Software"), to deal +> in the Software without restriction, including without limitation the rights +> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +> copies of the Software, and to permit persons to whom the Software is +> furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in +> all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +> THE SOFTWARE. + +---------------------------------------- + +### unified@v9.2.1 + +License: MIT +By: Titus Wormer + +> (The MIT License) +> +> Copyright (c) 2015 Titus Wormer +> +> Permission is hereby granted, free of charge, to any person obtaining a copy +> of this software and associated documentation files (the "Software"), to deal +> in the Software without restriction, including without limitation the rights +> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +> copies of the Software, and to permit persons to whom the Software is +> furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in +> all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +> THE SOFTWARE. + +---------------------------------------- + +### uniq@v1.0.1 + +License: MIT +By: Mikola Lysenko +Repository: + +> The MIT License (MIT) +> +> Copyright (c) 2013 Mikola Lysenko +> +> Permission is hereby granted, free of charge, to any person obtaining a copy +> of this software and associated documentation files (the "Software"), to deal +> in the Software without restriction, including without limitation the rights +> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +> copies of the Software, and to permit persons to whom the Software is +> furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in +> all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +> THE SOFTWARE. + +---------------------------------------- + +### unique-string@v3.0.0 + +License: MIT +By: Sindre Sorhus + +> MIT License +> +> Copyright (c) Sindre Sorhus (https://sindresorhus.com) +> +> Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +---------------------------------------- + +### unist-util-is@v4.1.0 + +License: MIT +By: Titus Wormer + +> (The MIT license) +> +> Copyright (c) 2015 Titus Wormer +> +> Permission is hereby granted, free of charge, to any person obtaining +> a copy of this software and associated documentation files (the +> 'Software'), to deal in the Software without restriction, including +> without limitation the rights to use, copy, modify, merge, publish, +> distribute, sublicense, and/or sell copies of the Software, and to +> permit persons to whom the Software is furnished to do so, subject to +> the following conditions: +> +> The above copyright notice and this permission notice shall be +> included in all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, +> EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +> MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +> IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +> CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +> TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +> SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +---------------------------------------- + +### unist-util-remove-position@v2.0.1 + +License: MIT +By: Titus Wormer + +> (The MIT License) +> +> Copyright (c) 2016 Titus Wormer +> +> Permission is hereby granted, free of charge, to any person obtaining +> a copy of this software and associated documentation files (the +> 'Software'), to deal in the Software without restriction, including +> without limitation the rights to use, copy, modify, merge, publish, +> distribute, sublicense, and/or sell copies of the Software, and to +> permit persons to whom the Software is furnished to do so, subject to +> the following conditions: +> +> The above copyright notice and this permission notice shall be +> included in all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, +> EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +> MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +> IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +> CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +> TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +> SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +---------------------------------------- + +### unist-util-stringify-position@v2.0.3 + +License: MIT +By: Titus Wormer + +> (The MIT License) +> +> Copyright (c) 2016 Titus Wormer +> +> Permission is hereby granted, free of charge, to any person obtaining +> a copy of this software and associated documentation files (the +> 'Software'), to deal in the Software without restriction, including +> without limitation the rights to use, copy, modify, merge, publish, +> distribute, sublicense, and/or sell copies of the Software, and to +> permit persons to whom the Software is furnished to do so, subject to +> the following conditions: +> +> The above copyright notice and this permission notice shall be +> included in all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, +> EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +> MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +> IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +> CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +> TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +> SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +---------------------------------------- + +### unist-util-visit@v2.0.3 + +License: MIT +By: Titus Wormer + +> (The MIT License) +> +> Copyright (c) 2015 Titus Wormer +> +> Permission is hereby granted, free of charge, to any person obtaining +> a copy of this software and associated documentation files (the +> 'Software'), to deal in the Software without restriction, including +> without limitation the rights to use, copy, modify, merge, publish, +> distribute, sublicense, and/or sell copies of the Software, and to +> permit persons to whom the Software is furnished to do so, subject to +> the following conditions: +> +> The above copyright notice and this permission notice shall be +> included in all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, +> EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +> MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +> IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +> CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +> TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +> SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +---------------------------------------- + +### unist-util-visit-parents@v3.1.1 + +License: MIT +By: Titus Wormer + +> (The MIT License) +> +> Copyright (c) 2016 Titus Wormer +> +> Permission is hereby granted, free of charge, to any person obtaining +> a copy of this software and associated documentation files (the +> 'Software'), to deal in the Software without restriction, including +> without limitation the rights to use, copy, modify, merge, publish, +> distribute, sublicense, and/or sell copies of the Software, and to +> permit persons to whom the Software is furnished to do so, subject to +> the following conditions: +> +> The above copyright notice and this permission notice shall be +> included in all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, +> EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +> MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +> IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +> CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +> TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +> SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +---------------------------------------- + +### vfile@v4.2.1 + +License: MIT +By: Titus Wormer + +> (The MIT License) +> +> Copyright (c) 2015 Titus Wormer +> +> Permission is hereby granted, free of charge, to any person obtaining a copy +> of this software and associated documentation files (the "Software"), to deal +> in the Software without restriction, including without limitation the rights +> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +> copies of the Software, and to permit persons to whom the Software is +> furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in +> all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +> THE SOFTWARE. + +---------------------------------------- + +### vfile-location@v3.2.0 + +License: MIT +By: Titus Wormer + +> (The MIT License) +> +> Copyright (c) 2016 Titus Wormer +> +> Permission is hereby granted, free of charge, to any person obtaining +> a copy of this software and associated documentation files (the +> 'Software'), to deal in the Software without restriction, including +> without limitation the rights to use, copy, modify, merge, publish, +> distribute, sublicense, and/or sell copies of the Software, and to +> permit persons to whom the Software is furnished to do so, subject to +> the following conditions: +> +> The above copyright notice and this permission notice shall be +> included in all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, +> EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +> MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +> IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +> CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +> TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +> SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +---------------------------------------- + +### vfile-message@v2.0.4 + +License: MIT +By: Titus Wormer + +> (The MIT License) +> +> Copyright (c) 2017 Titus Wormer +> +> Permission is hereby granted, free of charge, to any person obtaining +> a copy of this software and associated documentation files (the +> 'Software'), to deal in the Software without restriction, including +> without limitation the rights to use, copy, modify, merge, publish, +> distribute, sublicense, and/or sell copies of the Software, and to +> permit persons to whom the Software is furnished to do so, subject to +> the following conditions: +> +> The above copyright notice and this permission notice shall be +> included in all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, +> EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +> MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +> IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +> CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +> TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +> SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +---------------------------------------- + +### vnopts@v1.0.2 + +License: MIT +By: Ika + +> MIT License +> +> Copyright (c) Ika (https://github.com/ikatyang) +> +> Permission is hereby granted, free of charge, to any person obtaining a copy +> of this software and associated documentation files (the "Software"), to deal +> in the Software without restriction, including without limitation the rights +> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +> copies of the Software, and to permit persons to whom the Software is +> furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in all +> copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +> SOFTWARE. + +---------------------------------------- + +### wcwidth@v1.0.1 + +License: MIT +By: Tim Oxley +Repository: + +> wcwidth.js: JavaScript Portng of Markus Kuhn's wcwidth() Implementation +> ======================================================================= +> +> Copyright (C) 2012 by Jun Woong. +> +> This package is a JavaScript porting of `wcwidth()` implementation +> [by Markus Kuhn](http://www.cl.cam.ac.uk/~mgk25/ucs/wcwidth.c). +> +> Permission is hereby granted, free of charge, to any person obtaining a copy of +> this software and associated documentation files (the "Software"), to deal in +> the Software without restriction, including without limitation the rights to +> use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +> of the Software, and to permit persons to whom the Software is furnished to do +> so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in all +> copies or substantial portions of the Software. +> +> +> THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, +> INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND +> FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR +> OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +> EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +> PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR +> BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER +> IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +> ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +> POSSIBILITY OF SUCH DAMAGE. + +---------------------------------------- + +### which@v2.0.2 + +License: ISC +By: Isaac Z. Schlueter +Repository: + +> The ISC License +> +> Copyright (c) Isaac Z. Schlueter and Contributors +> +> Permission to use, copy, modify, and/or distribute this software for any +> purpose with or without fee is hereby granted, provided that the above +> copyright notice and this permission notice appear in all copies. +> +> THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +> WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +> MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +> ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +> WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +> ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR +> IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +---------------------------------------- + +### wrappy@v1.0.2 + +License: ISC +By: Isaac Z. Schlueter +Repository: + +> The ISC License +> +> Copyright (c) Isaac Z. Schlueter and Contributors +> +> Permission to use, copy, modify, and/or distribute this software for any +> purpose with or without fee is hereby granted, provided that the above +> copyright notice and this permission notice appear in all copies. +> +> THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +> WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +> MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +> ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +> WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +> ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR +> IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +---------------------------------------- + +### xtend@v4.0.2 + +License: MIT +By: Raynos + +> The MIT License (MIT) +> Copyright (c) 2012-2014 Raynos. +> +> Permission is hereby granted, free of charge, to any person obtaining a copy +> of this software and associated documentation files (the "Software"), to deal +> in the Software without restriction, including without limitation the rights +> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +> copies of the Software, and to permit persons to whom the Software is +> furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in +> all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +> THE SOFTWARE. + +---------------------------------------- + +### yallist@v2.1.2 + +License: ISC +By: Isaac Z. Schlueter +Repository: + +> The ISC License +> +> Copyright (c) Isaac Z. Schlueter and Contributors +> +> Permission to use, copy, modify, and/or distribute this software for any +> purpose with or without fee is hereby granted, provided that the above +> copyright notice and this permission notice appear in all copies. +> +> THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +> WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +> MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +> ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +> WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +> ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR +> IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +---------------------------------------- + +### yallist@v4.0.0 + +License: ISC +By: Isaac Z. Schlueter +Repository: + +> The ISC License +> +> Copyright (c) Isaac Z. Schlueter and Contributors +> +> Permission to use, copy, modify, and/or distribute this software for any +> purpose with or without fee is hereby granted, provided that the above +> copyright notice and this permission notice appear in all copies. +> +> THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +> WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +> MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +> ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +> WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +> ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR +> IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +---------------------------------------- + +### yaml@v1.10.2 + +License: ISC +By: Eemeli Aro + +> Copyright 2018 Eemeli Aro +> +> Permission to use, copy, modify, and/or distribute this software for any purpose +> with or without fee is hereby granted, provided that the above copyright notice +> and this permission notice appear in all copies. +> +> THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH +> REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND +> FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, +> INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS +> OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER +> TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF +> THIS SOFTWARE. + +---------------------------------------- + +### yaml-unist-parser@v1.3.1 + +License: MIT +By: Ika + +> MIT License +> +> Copyright (c) Ika (https://github.com/ikatyang) +> +> Permission is hereby granted, free of charge, to any person obtaining a copy +> of this software and associated documentation files (the "Software"), to deal +> in the Software without restriction, including without limitation the rights +> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +> copies of the Software, and to permit persons to whom the Software is +> furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in all +> copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +> SOFTWARE. + +======================================================================== + +prop-types 15.8.1 +-- +MIT License + +Copyright (c) 2013-present, Facebook, Inc. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +======================================================================== + +pubsub-js 1.9.3 +-- +The MIT License (MIT) +===================== + +Copyright © `2010` [Morgan Roderick](https://roderick.dk), [http://roderick.dk/](https://roderick.dk) <[morgan@roderick.dk](mailto:morgan@roderick.dk)> + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +======================================================================== + +punycode 1.3.2 +-- +Copyright Mathias Bynens + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +======================================================================== + +querystring 0.2.0 +-- +Copyright 2012 Irakli Gozalishvili. All rights reserved. +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to +deal in the Software without restriction, including without limitation the +rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +sell copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +IN THE SOFTWARE. + +======================================================================== + +rc-align 2.4.5 +-- +The MIT License (MIT) + +Copyright (c) 2014-present yiminghe + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +======================================================================== + +rc-animate 2.11.1 +-- +The MIT License (MIT) + +Copyright (c) 2014-present yiminghe + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +======================================================================== + +rc-slider 8.6.0 +-- +The MIT License (MIT) +Copyright (c) 2015-present Alipay.com, https://www.alipay.com/ + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +======================================================================== + +rc-tooltip 3.7.3 +-- +The MIT License (MIT) +Copyright (c) 2015-present Alipay.com, https://www.alipay.com/ + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +======================================================================== + +rc-trigger 2.6.5 +-- +The MIT License (MIT) +Copyright (c) 2015-present Alipay.com, https://www.alipay.com/ + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +======================================================================== + +rc-util 4.21.1 +-- +The MIT License (MIT) + +Copyright (c) 2014-present yiminghe +Copyright (c) 2015-present Alipay.com, https://www.alipay.com/ + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +======================================================================== + +re-reselect 4.0.1 +-- +Copyright (c) Andrea Carraro + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +======================================================================== + +re-resizable 6.9.1 +-- +The MIT License (MIT) + +Copyright (c) 2018 @bokuweb + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +======================================================================== + +react 18.1.0 +-- +MIT License + +Copyright (c) Facebook, Inc. and its affiliates. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +======================================================================== + +react-contexify 6.0.0 +-- +MIT License + +Copyright (c) 2020 Fadi Khadra + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +======================================================================== + +react-dnd 16.0.0 +-- +The MIT License (MIT) + +Copyright (c) 2016 Dan Abramov + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +======================================================================== + +react-dnd-html5-backend 16.0.0 +-- +The MIT License (MIT) + +Copyright (c) 2015 Dan Abramov + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +======================================================================== + +react-dom 18.1.0 +-- +MIT License + +Copyright (c) Facebook, Inc. and its affiliates. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +======================================================================== + +react-fast-compare 3.2.0 +-- +MIT License + +Copyright (c) 2018 Formidable Labs +Copyright (c) 2017 Evgeny Poberezkin + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +======================================================================== + +react-helmet 6.1.0 +-- +Copyright (c) 2015 NFL + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +======================================================================== + +react-highlight-words 0.17.0 +-- +The MIT License (MIT) + +Copyright (c) 2015 Treasure Data + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +======================================================================== + +react-hot-loader 4.12.12 +-- +MIT License + +Copyright (c) 2016 Dan Abramov + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +======================================================================== + +react-input-autosize 2.2.2 +-- +The MIT License (MIT) + +Copyright (c) 2018 Jed Watson + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +======================================================================== + +react-is 16.13.1 +-- +MIT License + +Copyright (c) Facebook, Inc. and its affiliates. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +======================================================================== + +react-layout-effect 1.0.5 +-- +MIT License + +Copyright (c) Alec Larson + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +======================================================================== + +react-lifecycles-compat 3.0.4 +-- +MIT License + +Copyright (c) 2013-present, Facebook, Inc. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +======================================================================== + +react-onclickoutside 6.9.0 +-- +Licensed under MIT (https://spdx.org/licenses/MIT.html) + +======================================================================== + +react-popper 2.2.3 +-- +The MIT License (MIT) + +Copyright (c) 2018 React Popper authors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +======================================================================== + +react-property 1.0.1 +-- +Licensed under MIT (https://spdx.org/licenses/MIT.html) + +======================================================================== + +react-remove-scroll 2.5.5 +-- +MIT License + +Copyright (c) 2017 Anton Korzunov + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +======================================================================== + +react-remove-scroll-bar 2.3.4 +-- +Copyright Anton Korzunov ; licensed under MIT (https://spdx.org/licenses/MIT.html) + +======================================================================== + +react-router 6.16.0 +-- +MIT License + +Copyright (c) React Training LLC 2015-2019 +Copyright (c) Remix Software Inc. 2020-2021 +Copyright (c) Shopify Inc. 2022-2023 + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +======================================================================== + +react-router-dom 6.16.0 +-- +MIT License + +Copyright (c) React Training LLC 2015-2019 +Copyright (c) Remix Software Inc. 2020-2021 +Copyright (c) Shopify Inc. 2022-2023 + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +======================================================================== + +react-select 3.1.0 +-- +Copyright Jed Watson; licensed under MIT (https://spdx.org/licenses/MIT.html) + +======================================================================== + +react-side-effect 2.1.1 +-- +The MIT License (MIT) + +Copyright (c) 2015 Dan Abramov + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +======================================================================== + +react-style-singleton 2.2.1 +-- +Copyright Anton Korzunov (thekashey@gmail.com); licensed under MIT (https://spdx.org/licenses/MIT.html) + +======================================================================== + +react-timeago 7.1.0 +-- +The MIT License (MIT) + +Copyright (c) 2014 Naman Goel + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +======================================================================== + +react-use-gesture 6.0.2 +-- +Copyright (c) 2018-present Paul Henschel + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +======================================================================== + +react-virtualized-auto-sizer 1.0.2 +-- +Copyright Brian Vaughn (https://github.com/bvaughn/); licensed under MIT (https://spdx.org/licenses/MIT.html) + +======================================================================== + +react-virtuoso 4.6.2 +-- +MIT License + +Copyright (c) 2020 Petyo Ivanov + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +======================================================================== + +react-vtree 2.0.0 +-- +MIT License + +Copyright (c) 2018-present Vladimir Rindevich + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +======================================================================== + +react-window 1.8.7 +-- +The MIT License (MIT) + +Copyright (c) 2018 Brian Vaughn + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +======================================================================== + +readable-stream 3.6.0 +-- +Node.js is licensed for use as follows: + +""" +Copyright Node.js contributors. All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to +deal in the Software without restriction, including without limitation the +rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +sell copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +IN THE SOFTWARE. +""" + +This license applies to parts of Node.js originating from the +https://github.com/joyent/node repository: + +""" +Copyright Joyent, Inc. and other Node contributors. All rights reserved. +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to +deal in the Software without restriction, including without limitation the +rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +sell copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +IN THE SOFTWARE. +""" + +======================================================================== + +redux 4.2.0 +-- +The MIT License (MIT) + +Copyright (c) 2015-present Dan Abramov + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +======================================================================== + +regexp.prototype.flags 1.5.0 +-- +The MIT License (MIT) + +Copyright (C) 2014 Jordan Harband + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +======================================================================== + +regexpp 2.0.1 +-- +MIT License + +Copyright (c) 2018 Toru Nagashima + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +======================================================================== + +reselect 4.1.7 +-- +The MIT License (MIT) + +Copyright (c) 2015-2018 Reselect Contributors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +======================================================================== + +resize-observer-polyfill 1.5.0 +-- +The MIT License (MIT) + +Copyright (c) 2016 Denis Rul + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +======================================================================== + +resolve 2.0.0-next.3 +-- +MIT License + +Copyright (c) 2012 James Halliday + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +======================================================================== + +safe-buffer 5.2.1 +-- +The MIT License (MIT) + +Copyright (c) Feross Aboukhadijeh + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +======================================================================== + +safe-regex-test 1.0.0 +-- +MIT License + +Copyright (c) 2022 Jordan Harband + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +======================================================================== + +sanitize-html 2.11.0 +-- +Copyright (c) 2013, 2014, 2015 P'unk Avenue LLC + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +======================================================================== + +scheduler 0.22.0 +-- +MIT License + +Copyright (c) Facebook, Inc. and its affiliates. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +======================================================================== + +scroll-into-view-if-needed 2.2.28 +-- +MIT License + +Copyright (c) 2017 Stian Didriksen + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +======================================================================== + +semver 7.3.5 +-- +The ISC License + +Copyright (c) Isaac Z. Schlueter and Contributors + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR +IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +======================================================================== + +set-cookie-parser 2.6.0 +-- +The MIT License (MIT) + +Copyright (c) 2015 Nathan Friedly (http://nfriedly.com/) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +======================================================================== + +set-immediate-shim 1.0.1 +-- +Copyright Sindre Sorhus; licensed under MIT (https://spdx.org/licenses/MIT.html) + +======================================================================== + +settle-promise 1.0.0 +-- +Copyright Joe Haddad ; licensed under MIT (https://spdx.org/licenses/MIT.html) + +======================================================================== + +sha.js 2.4.11 +-- +Copyright (c) 2013-2018 sha.js contributors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + +Copyright (c) 1998 - 2009, Paul Johnston & Contributors +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +Redistributions of source code must retain the above copyright notice, this +list of conditions and the following disclaimer. + +Redistributions in binary form must reproduce the above copyright notice, this +list of conditions and the following disclaimer in the documentation and/or +other materials provided with the distribution. + +Neither the name of the author nor the names of its contributors may be used to +endorse or promote products derived from this software without specific prior +written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +======================================================================== + +shallowequal 1.1.0 +-- +MIT License + +Copyright (c) 2017 Alberto Leal (github.com/dashed) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +======================================================================== + +side-channel 1.0.4 +-- +MIT License + +Copyright (c) 2019 Jordan Harband + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +======================================================================== + +slate 0.94.1 +-- +Licensed under MIT (https://spdx.org/licenses/MIT.html) + +======================================================================== + +slate-history 0.93.0 +-- +Licensed under MIT (https://spdx.org/licenses/MIT.html) + +======================================================================== + +slate-react 0.98.4 +-- +Licensed under MIT (https://spdx.org/licenses/MIT.html) + +======================================================================== + +source-map 0.5.6 +-- +Copyright (c) 2009-2011, Mozilla Foundation and contributors +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +* Neither the names of the Mozilla Foundation nor the names of project + contributors may be used to endorse or promote products derived from this + software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +======================================================================== + +source-map-js 1.0.2 +-- +Copyright (c) 2009-2011, Mozilla Foundation and contributors +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +* Neither the names of the Mozilla Foundation nor the names of project + contributors may be used to endorse or promote products derived from this + software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +======================================================================== + +stream-browserify 3.0.0 +-- +MIT License + +Copyright (c) James Halliday + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +======================================================================== + +string-hash 1.1.3 +-- +Copyright The Dark Sky Company; licensed under CC0-1.0 (https://spdx.org/licenses/CC0-1.0.html) + +======================================================================== + +string.prototype.matchall 4.0.5 +-- +The MIT License (MIT) + +Copyright (c) 2015 Jordan Harband + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +======================================================================== + +string.prototype.trim 1.2.7 +-- +The MIT License (MIT) + +Copyright (c) 2015 Jordan Harband + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +======================================================================== + +string_decoder 1.3.0 +-- +Node.js is licensed for use as follows: + +""" +Copyright Node.js contributors. All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to +deal in the Software without restriction, including without limitation the +rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +sell copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +IN THE SOFTWARE. +""" + +This license applies to parts of Node.js originating from the +https://github.com/joyent/node repository: + +""" +Copyright Joyent, Inc. and other Node contributors. All rights reserved. +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to +deal in the Software without restriction, including without limitation the +rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +sell copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +IN THE SOFTWARE. +""" + +======================================================================== + +strip-ansi 6.0.0 +-- +MIT License + +Copyright (c) Sindre Sorhus (sindresorhus.com) + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +======================================================================== + +style-loader 0.18.2 +-- +Copyright JS Foundation and other contributors + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +'Software'), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +======================================================================== + +style-to-object 0.3.0 +-- +The MIT License (MIT) + +Copyright (c) 2017 Menglin "Mark" Xu + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +======================================================================== + +style-vendorizer 2.2.3 +-- +The MIT License (MIT) + +Copyright (c) 2020-2022 Kristóf Poduszló + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +======================================================================== + +stylis 4.0.10 +-- +MIT License + +Copyright (c) 2016-present Sultan Tarimo + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +======================================================================== + +supports-color 5.5.0 +-- +MIT License + +Copyright (c) Sindre Sorhus (sindresorhus.com) + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +======================================================================== + +textextensions 5.14.0 +-- + + +

License

+ +Unless stated otherwise all works are: + + + +and licensed under: + + + +

MIT License

+ +
+Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+ + + +======================================================================== + +tippy.js 6.2.6 +-- +MIT License + +Copyright (c) 2017-present atomiks + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +======================================================================== + +to-fast-properties 2.0.0 +-- +MIT License + +Copyright (c) 2014 Petka Antonov + 2015 Sindre Sorhus + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +======================================================================== + +tslib 2.6.2 +-- +Copyright (c) Microsoft Corporation. + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH +REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY +AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, +INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM +LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR +OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +PERFORMANCE OF THIS SOFTWARE. + +======================================================================== + +type-check 0.3.2 +-- +Copyright (c) George Zahariev + +Permission is hereby granted, free of charge, to any person +obtaining a copy of this software and associated documentation +files (the "Software"), to deal in the Software without +restriction, including without limitation the rights to use, +copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. + +======================================================================== + +typescript 4.6.4 +-- +Apache License + +Version 2.0, January 2004 + +http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + +"License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. + +"Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. + +"Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. + +"You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. + +"Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. + +"Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. + +"Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). + +"Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. + +"Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." + +"Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: + +You must give any other recipients of the Work or Derivative Works a copy of this License; and + +You must cause any modified files to carry prominent notices stating that You changed the files; and + +You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and + +If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +======================================================================== + +uri-js 4.4.1 +-- +Copyright 2011 Gary Court. All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY GARY COURT "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GARY COURT OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +The views and conclusions contained in the software and documentation are those of the authors and should not be interpreted as representing official policies, either expressed or implied, of Gary Court. + +======================================================================== + +url 0.11.0 +-- +The MIT License (MIT) + +Copyright Joyent, Inc. and other Node contributors. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +======================================================================== + +url-join 4.0.1 +-- +MIT License + +Copyright (c) 2015 José F. Romaniello + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +======================================================================== + +use-callback-ref 1.3.0 +-- +MIT License + +Copyright (c) 2017 Anton Korzunov + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +======================================================================== + +use-context-selector 1.3.9 +-- +The MIT License (MIT) + +Copyright (c) 2019-2021 Daishi Kato + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +======================================================================== + +use-memo-one 1.1.2 +-- +MIT License + +Copyright (c) 2019 Alexander Reardon + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +======================================================================== + +use-sidecar 1.1.2 +-- +MIT License + +Copyright (c) 2017 Anton Korzunov + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +======================================================================== + +use-sync-external-store 1.2.0 +-- +MIT License + +Copyright (c) Facebook, Inc. and its affiliates. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +======================================================================== + +util 0.10.4 +-- +Copyright Joyent, Inc. and other Node contributors. All rights reserved. +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to +deal in the Software without restriction, including without limitation the +rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +sell copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +IN THE SOFTWARE. + +======================================================================== + +util-deprecate 1.0.2 +-- +(The MIT License) + +Copyright (c) 2014 Nathan Rajlich + +Permission is hereby granted, free of charge, to any person +obtaining a copy of this software and associated documentation +files (the "Software"), to deal in the Software without +restriction, including without limitation the rights to use, +copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. + +======================================================================== + +uuid 8.3.2 +-- +The MIT License (MIT) + +Copyright (c) 2010-2020 Robert Kieffer and other contributors + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +======================================================================== + +validate-npm-package-name 3.0.0 +-- +Copyright (c) 2015, npm, Inc + + +Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +======================================================================== + +warning 3.0.0 +-- +BSD License + +For React software + +Copyright (c) 2013-2015, Facebook, Inc. +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + + * Neither the name Facebook nor the names of its contributors may be used to + endorse or promote products derived from this software without specific + prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +======================================================================== + +worktop 0.7.3 +-- +MIT License + +Copyright (c) Luke Edwards (lukeed.com) + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +======================================================================== + +xstate 4.23.1 +-- +The MIT License (MIT) + +Copyright (c) 2015 David Khourshid + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +======================================================================== + +yallist 4.0.0 +-- +The ISC License + +Copyright (c) Isaac Z. Schlueter and Contributors + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR +IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +======================================================================== + +yjs 13.6.8 +-- +The MIT License (MIT) + +Copyright (c) 2014 + - Kevin Jahns . + - Chair of Computer Science 5 (Databases & Information Systems), RWTH Aachen University, Germany + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +======================================================================== + +zustand 4.1.5 +-- +MIT License + +Copyright (c) 2019 Paul Henschel + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +======================================================================== + +microsoft/vscode 1.91.1 +-- +MIT License + +Copyright (c) 2015 - present Microsoft Corporation + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/editor/jest.config.js b/editor/jest.config.js index bc651b6d6b29..0a99d49a3e88 100644 --- a/editor/jest.config.js +++ b/editor/jest.config.js @@ -112,6 +112,8 @@ module.exports = { 'react-dnd-html5-backend': '/test/jest/__mocks__/react-dnd-html5-backend.js', '^react$': '/node_modules/react/index.js', '^react-dom$': '/node_modules/react-dom/index.js', + '^@mhsdesign/jit-browser-tailwindcss$': + '/src/utils/__mocks__/jit-browser-tailwindcss.js', '\\.(jpg|ico|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$': '/test/jest/__mocks__/styleMock.js', }, diff --git a/editor/package.json b/editor/package.json index 91b7e6b6f694..147dd196ddb6 100644 --- a/editor/package.json +++ b/editor/package.json @@ -152,6 +152,7 @@ "@liveblocks/react": "1.10.0", "@liveblocks/react-comments": "1.10.0", "@liveblocks/yjs": "1.10.0", + "@mhsdesign/jit-browser-tailwindcss": "0.4.1", "@popperjs/core": "2.4.4", "@radix-ui/react-dropdown-menu": "2.1.1", "@radix-ui/react-select": "2.1.1", @@ -164,15 +165,13 @@ "@svgr/plugin-jsx": "5.5.0", "@testing-library/user-event": "14.5.2", "@tippyjs/react": "4.1.0", - "@twind/core": "1.1.3", - "@twind/preset-autoprefix": "1.0.7", - "@twind/preset-tailwind": "1.1.4", "@types/fontfaceobserver": "0.0.6", "@types/lodash.findlastindex": "4.6.7", "@types/react-syntax-highlighter": "11.0.4", "@types/w3c-css-typed-object-model-level-1": "20180410.0.5", "@use-it/interval": "0.1.3", "@vercel/stega": "0.1.0", + "@xengine/tailwindcss-class-parser": "1.1.19", "ajv": "6.4.0", "anser": "2.1.0", "antd": "4.3.5", @@ -185,12 +184,13 @@ "classnames": "2.2.6", "clipboard-polyfill": "2.4.6", "create-react-class": "15.6.3", - "css-tree": "2.3.1", + "css-tree": "3.0.1", "eslint-config-utopia": "link:../eslint-config-utopia", "eslint-plugin-import": "2.25.3", "eslint-plugin-jsx-a11y": "6.5.1", "eslint-plugin-react": "7.23.2", "eslint-plugin-react-hooks": "4.2.0", + "eslint-plugin-react-refresh": "0.4.12", "eslint4b": "6.6.0", "fast-deep-equal": "2.0.1", "fontfaceobserver": "2.1.0", @@ -309,7 +309,7 @@ "@types/classnames": "2.2.4", "@types/codemirror": "0.0.40", "@types/create-react-class": "15.6.2", - "@types/css-tree": "2.3.4", + "@types/css-tree": "2.3.9", "@types/diff": "4.0.2", "@types/enzyme": "3.1.9", "@types/eslint": "7.2.2", @@ -371,7 +371,7 @@ "clean-webpack-plugin": "4.0.0", "concurrently": "3.5.1", "css-loader": "0.28.4", - "csstype": "3.0.3", + "csstype": "3.1.3", "dependency-cruiser": "13.1.5", "diff": "5.0.0", "enzyme": "3.3.0", @@ -426,6 +426,7 @@ "source-map-loader": "0.2.3", "string-replace-loader": "2.2.0", "style-loader": "0.18.2", + "tailwindcss": "^3.4.13", "tar": "6.0.5", "terser-webpack-plugin": "5.3.9", "three": "0.140.2", diff --git a/editor/pnpm-lock.yaml b/editor/pnpm-lock.yaml index a624524d7631..b6009018b6a1 100644 --- a/editor/pnpm-lock.yaml +++ b/editor/pnpm-lock.yaml @@ -65,6 +65,7 @@ specifiers: '@liveblocks/react': 1.10.0 '@liveblocks/react-comments': 1.10.0 '@liveblocks/yjs': 1.10.0 + '@mhsdesign/jit-browser-tailwindcss': 0.4.1 '@originjs/vite-plugin-commonjs': 1.0.3 '@peculiar/webcrypto': 1.4.3 '@popperjs/core': 2.4.4 @@ -84,16 +85,13 @@ specifiers: '@testing-library/react': 14.0.0 '@testing-library/user-event': 14.5.2 '@tippyjs/react': 4.1.0 - '@twind/core': 1.1.3 - '@twind/preset-autoprefix': 1.0.7 - '@twind/preset-tailwind': 1.1.4 '@types/babel__traverse': 7.18.3 '@types/chai': 3.5.1 '@types/chroma-js': 2.0.0 '@types/classnames': 2.2.4 '@types/codemirror': 0.0.40 '@types/create-react-class': 15.6.2 - '@types/css-tree': 2.3.4 + '@types/css-tree': 2.3.9 '@types/diff': 4.0.2 '@types/enzyme': 3.1.9 '@types/eslint': 7.2.2 @@ -152,6 +150,7 @@ specifiers: '@vercel/stega': 0.1.0 '@vitejs/plugin-react': 4.0.4 '@welldone-software/why-did-you-render': 5.0.0-rc.1 + '@xengine/tailwindcss-class-parser': 1.1.19 ajv: 6.4.0 anser: 2.1.0 antd: 4.3.5 @@ -173,8 +172,8 @@ specifiers: concurrently: 3.5.1 create-react-class: 15.6.3 css-loader: 0.28.4 - css-tree: 2.3.1 - csstype: 3.0.3 + css-tree: 3.0.1 + csstype: 3.1.3 dependency-cruiser: 13.1.5 diff: 5.0.0 enzyme: 3.3.0 @@ -188,6 +187,7 @@ specifiers: eslint-plugin-prettier: 3.1.2 eslint-plugin-react: 7.23.2 eslint-plugin-react-hooks: 4.2.0 + eslint-plugin-react-refresh: 0.4.12 eslint4b: 6.6.0 expect: 26.1.0 fast-check: 1.15.0 @@ -320,6 +320,7 @@ specifiers: string-replace-loader: 2.2.0 strip-ansi: 6.0.0 style-loader: 0.18.2 + tailwindcss: ^3.4.13 tar: 6.0.5 terser-webpack-plugin: 5.3.9 three: 0.140.2 @@ -370,6 +371,7 @@ dependencies: '@liveblocks/react': 1.10.0_react@18.1.0 '@liveblocks/react-comments': 1.10.0_wgqxz3kjpz4ixw4iz4mbg2tk4i '@liveblocks/yjs': 1.10.0_yjs@13.6.8 + '@mhsdesign/jit-browser-tailwindcss': 0.4.1 '@popperjs/core': 2.4.4 '@radix-ui/react-dropdown-menu': 2.1.1_wgqxz3kjpz4ixw4iz4mbg2tk4i '@radix-ui/react-select': 2.1.1_wgqxz3kjpz4ixw4iz4mbg2tk4i @@ -382,15 +384,13 @@ dependencies: '@svgr/plugin-jsx': 5.5.0 '@testing-library/user-event': 14.5.2 '@tippyjs/react': 4.1.0_ef5jwxihqo6n7gxfmzogljlgcm - '@twind/core': 1.1.3_typescript@5.5.4 - '@twind/preset-autoprefix': 1.0.7_gmrgfak3invo7trrvnu65or3fu - '@twind/preset-tailwind': 1.1.4_gmrgfak3invo7trrvnu65or3fu '@types/fontfaceobserver': 0.0.6 '@types/lodash.findlastindex': 4.6.7 '@types/react-syntax-highlighter': 11.0.4 '@types/w3c-css-typed-object-model-level-1': 20180410.0.5 '@use-it/interval': 0.1.3_react@18.1.0 '@vercel/stega': 0.1.0 + '@xengine/tailwindcss-class-parser': 1.1.19_tailwindcss@3.4.13 ajv: 6.4.0 anser: 2.1.0 antd: 4.3.5_ef5jwxihqo6n7gxfmzogljlgcm @@ -403,12 +403,13 @@ dependencies: classnames: 2.2.6 clipboard-polyfill: 2.4.6 create-react-class: 15.6.3 - css-tree: 2.3.1 + css-tree: 3.0.1 eslint-config-utopia: link:../eslint-config-utopia eslint-plugin-import: 2.25.3_xoh4mdbbxtgv4pgsofz5xwre24 eslint-plugin-jsx-a11y: 6.5.1_5wlsrnjcorawie777qour2ptie_eslint@7.32.0 eslint-plugin-react: 7.23.2_eslint@7.32.0 eslint-plugin-react-hooks: 4.2.0_eslint@7.32.0 + eslint-plugin-react-refresh: 0.4.12_eslint@7.32.0 eslint4b: 6.6.0 fast-deep-equal: 2.0.1 fontfaceobserver: 2.1.0 @@ -530,7 +531,7 @@ devDependencies: '@types/classnames': 2.2.4 '@types/codemirror': 0.0.40 '@types/create-react-class': 15.6.2 - '@types/css-tree': 2.3.4 + '@types/css-tree': 2.3.9 '@types/diff': 4.0.2 '@types/enzyme': 3.1.9 '@types/eslint': 7.2.2 @@ -592,7 +593,7 @@ devDependencies: clean-webpack-plugin: 4.0.0_webpack@5.88.2 concurrently: 3.5.1 css-loader: 0.28.4 - csstype: 3.0.3 + csstype: 3.1.3 dependency-cruiser: 13.1.5 diff: 5.0.0 enzyme: 3.3.0 @@ -647,6 +648,7 @@ devDependencies: source-map-loader: 0.2.3 string-replace-loader: 2.2.0_webpack@5.88.2 style-loader: 0.18.2 + tailwindcss: 3.4.13 tar: 6.0.5 terser-webpack-plugin: 5.3.9_webpack@5.88.2 three: 0.140.2 @@ -670,6 +672,10 @@ packages: resolution: {integrity: sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA==} engines: {node: '>=0.10.0'} + /@alloc/quick-lru/5.2.0: + resolution: {integrity: sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==} + engines: {node: '>=10'} + /@alloc/types/1.3.0: resolution: {integrity: sha512-mH7LiFiq9g6rX2tvt1LtwsclfG5hnsmtIfkZiauAGrm1AwXhoRS0sF2WrN9JGN7eV5vFXqNaB0eXZ3IvMsVi9g==} dev: false @@ -706,7 +712,7 @@ packages: '@ant-design/icons-svg': 4.2.1 '@babel/runtime': 7.20.13 classnames: 2.2.6 - rc-util: 5.38.1_ef5jwxihqo6n7gxfmzogljlgcm + rc-util: 5.43.0_ef5jwxihqo6n7gxfmzogljlgcm react: 18.1.0_47cciibm4ysmleigs33s763fqu react-dom: 18.1.0_abari7w75zfr6mrhvamxwmfpxm_react@18.1.0 dev: false @@ -772,7 +778,7 @@ packages: slash: 2.0.0 optionalDependencies: '@nicolo-ribaudo/chokidar-2': 2.1.8-no-fsevents.3 - chokidar: 3.5.3 + chokidar: 3.6.0 dev: true /@babel/code-frame/7.12.11: @@ -2346,7 +2352,7 @@ packages: '@emotion/memoize': 0.7.5 '@emotion/unitless': 0.7.5 '@emotion/utils': 1.0.0 - csstype: 3.0.3 + csstype: 3.1.3 dev: false /@emotion/sheet/0.9.4: @@ -2716,7 +2722,6 @@ packages: strip-ansi-cjs: /strip-ansi/6.0.1 wrap-ansi: 8.1.0 wrap-ansi-cjs: /wrap-ansi/7.0.0 - dev: true /@istanbuljs/load-nyc-config/1.1.0: resolution: {integrity: sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==} @@ -3166,6 +3171,23 @@ packages: react: 18.1.0_47cciibm4ysmleigs33s763fqu dev: false + /@mhsdesign/jit-browser-tailwindcss/0.4.1: + resolution: {integrity: sha512-n08EkCXPP6XADdXtZ+zLRyF5qkEsoC37FrC7pAceREQRCT+SB9oI7qXMS2hNPwTXdBwUdR3d2TSk9R7imMyMRQ==} + dependencies: + color-name: 1.1.4 + didyoumean: 1.2.2 + dlv: 1.1.3 + postcss: 8.4.27 + postcss-js: 4.0.1_postcss@8.4.27 + postcss-nested: 5.0.6_postcss@8.4.27 + postcss-selector-parser: 6.1.2 + postcss-value-parser: 4.2.0 + quick-lru: 5.1.1 + tailwindcss: 3.4.13 + transitivePeerDependencies: + - ts-node + dev: false + /@nicolo-ribaudo/chokidar-2/2.1.8-no-fsevents.3: resolution: {integrity: sha512-s88O1aVtXftvp5bCPB7WnmXc5IwOZZ7YPuwNPt+GtOOXpPvad1LfbmjYv+qII7zP6RU2QGnqve27dnLycEnyEQ==} requiresBuild: true @@ -3226,7 +3248,6 @@ packages: resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} engines: {node: '>=14'} requiresBuild: true - dev: true optional: true /@polka/url/1.0.0-next.20: @@ -4633,7 +4654,7 @@ packages: dependencies: '@types/estree': 0.0.39 estree-walker: 1.0.1 - picomatch: 2.3.0 + picomatch: 2.3.1 dev: true /@rollup/pluginutils/4.2.1: @@ -4918,48 +4939,6 @@ packages: resolution: {integrity: sha512-C5Mc6rdnsaJDjO3UpGW/CQTHtCKaYlScZTly4JIu97Jxo/odCiH0ITnDXSJPTOrEKk/ycSZ0AOgTmkDtkOsvIA==} dev: false - /@twind/core/1.1.3_typescript@5.5.4: - resolution: {integrity: sha512-/B/aNFerMb2IeyjSJy3SJxqVxhrT77gBDknLMiZqXIRr4vNJqiuhx7KqUSRzDCwUmyGuogkamz+aOLzN6MeSLw==} - engines: {node: '>=14.15.0'} - peerDependencies: - typescript: ^4.8.4 - peerDependenciesMeta: - typescript: - optional: true - dependencies: - csstype: 3.1.2 - typescript: 5.5.4 - dev: false - - /@twind/preset-autoprefix/1.0.7_gmrgfak3invo7trrvnu65or3fu: - resolution: {integrity: sha512-3wmHO0pG/CVxYBNZUV0tWcL7CP0wD5KpyWAQE/KOalWmOVBj+nH6j3v6Y3I3pRuMFaG5DC78qbYbhA1O11uG3w==} - engines: {node: '>=14.15.0'} - peerDependencies: - '@twind/core': ^1.1.0 - typescript: ^4.8.4 - peerDependenciesMeta: - typescript: - optional: true - dependencies: - '@twind/core': 1.1.3_typescript@5.5.4 - style-vendorizer: 2.2.3 - typescript: 5.5.4 - dev: false - - /@twind/preset-tailwind/1.1.4_gmrgfak3invo7trrvnu65or3fu: - resolution: {integrity: sha512-zv85wrP/DW4AxgWrLfH7kyGn/KJF3K04FMLVl2AjoxZGYdCaoZDkL8ma3hzaKQ+WGgBFRubuB/Ku2Rtv/wjzVw==} - engines: {node: '>=14.15.0'} - peerDependencies: - '@twind/core': ^1.1.0 - typescript: ^4.8.4 - peerDependenciesMeta: - typescript: - optional: true - dependencies: - '@twind/core': 1.1.3_typescript@5.5.4 - typescript: 5.5.4 - dev: false - /@types/aria-query/5.0.1: resolution: {integrity: sha512-XTIieEY+gvJ39ChLcB4If5zHtPxt3Syj5rgZR+e1ctpmK8NjPf0zFqsz4JpLJT0xla9GFDKjy8Cpu331nrmE1Q==} dev: true @@ -5070,8 +5049,8 @@ packages: '@types/react': 18.0.9 dev: true - /@types/css-tree/2.3.4: - resolution: {integrity: sha512-wdxxe7zEpOXfy5C3FmwinAIc/6p6du/wOKMGZf07JHuHHRIvLtLq8h66zi3Yn7PCyswxbp3Ujx9h+vSuMvfN/w==} + /@types/css-tree/2.3.9: + resolution: {integrity: sha512-g1FE6xkPDP4tsccmTd6jIugjKZdxIDqAf9h2pc+4LsGgYbOyfa9phNjBHYbm6FtwIlNfT1NBx3f2zSeqO7aRAw==} dev: true /@types/diff/4.0.2: @@ -5493,7 +5472,7 @@ packages: dependencies: '@types/prop-types': 15.5.6 '@types/scheduler': 0.16.1 - csstype: 3.1.2 + csstype: 3.1.3 dev: false /@types/react/18.0.9: @@ -5501,7 +5480,7 @@ packages: dependencies: '@types/prop-types': 15.5.6 '@types/scheduler': 0.16.1 - csstype: 3.0.9 + csstype: 3.1.3 /@types/resolve/1.17.1: resolution: {integrity: sha512-yy7HuzQhj0dhGpD8RLXSZWEkLsV9ibvxvi6EiJ3bkqLAO1RGo0WbkWQiwpRlSFymTJRz0d3k5LM3kkx8ArDbLw==} @@ -6023,6 +6002,15 @@ packages: react: 18.1.0_47cciibm4ysmleigs33s763fqu dev: true + /@xengine/tailwindcss-class-parser/1.1.19_tailwindcss@3.4.13: + resolution: {integrity: sha512-1zOP0agVG+phPKWOJl5B/3mQB5+WhntynWExW6cYTgv6KCaJ6QM22m5wTPrQ0CNFRguEGUHyWgFY83PWkJ4Yrw==} + peerDependencies: + tailwindcss: '*' + dependencies: + colord: 2.9.3 + tailwindcss: 3.4.13 + dev: false + /@xstate/fsm/2.1.0: resolution: {integrity: sha512-oJlc0iD0qZvAM7If/KlyJyqUt7wVI8ocpsnlWzAPl97evguPbd+oJbRM9R4A1vYJffYH96+Bx44nLDE6qS8jQg==} dev: false @@ -6342,7 +6330,6 @@ packages: /ansi-regex/6.0.1: resolution: {integrity: sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==} engines: {node: '>=12'} - dev: true /ansi-styles/1.1.0: resolution: {integrity: sha512-f2PKUkN5QngiSemowa6Mrk9MPCdtFiOSmibjZ+j1qhLGHHYsqZwmBMRF3IRMVXo8sybDqx2fJl2d/8OphBoWkA==} @@ -6374,7 +6361,6 @@ packages: /ansi-styles/6.2.1: resolution: {integrity: sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==} engines: {node: '>=12'} - dev: true /antd/4.3.5_ef5jwxihqo6n7gxfmzogljlgcm: resolution: {integrity: sha512-C1qILCKh+G7q06wtm4uqmyvqzAzAaDKKzuTO0BO+rtUbSATjzbUg2Bp5byDEw0ThUGTWfJEQ0J7HZHh0FgiaJA==} @@ -6445,8 +6431,7 @@ packages: dev: true /any-promise/1.3.0: - resolution: {integrity: sha1-q8av7tzqUugJzcA3au0845Y10X8=} - dev: true + resolution: {integrity: sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==} /anymatch/3.1.2: resolution: {integrity: sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==} @@ -6454,7 +6439,9 @@ packages: dependencies: normalize-path: 3.0.0 picomatch: 2.3.1 - dev: true + + /arg/5.0.2: + resolution: {integrity: sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==} /argparse/1.0.10: resolution: {integrity: sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==} @@ -6958,14 +6945,13 @@ packages: dev: false optional: true - /bare-fs/2.1.5: - resolution: {integrity: sha512-5t0nlecX+N2uJqdxe9d18A98cp2u9BETelbjKpiVgQqzzmVNFYWEAjQHqS+2Khgto1vcwhik9cXucaj5ve2WWA==} + /bare-fs/2.3.5: + resolution: {integrity: sha512-SlE9eTxifPDJrT6YgemQ1WGFleevzwY+XAP1Xqgl56HtcrisC2CHCZ2tq6dBpcH2TnNxwUEUGhweo+lrQtYuiw==} requiresBuild: true dependencies: bare-events: 2.2.0 - bare-os: 2.2.0 - bare-path: 2.1.0 - streamx: 2.15.5 + bare-path: 2.1.3 + bare-stream: 2.3.2 dev: false optional: true @@ -6974,13 +6960,20 @@ packages: dev: false optional: true - /bare-path/2.1.0: - resolution: {integrity: sha512-DIIg7ts8bdRKwJRJrUMy/PICEaQZaPGZ26lsSx9MJSwIhSrcdHn7/C8W+XmnG/rKi6BaRcz+JO00CjZteybDtw==} + /bare-path/2.1.3: + resolution: {integrity: sha512-lh/eITfU8hrj9Ru5quUp0Io1kJWIk1bTjzo7JH1P5dWmQ2EL4hFUlfI8FonAhSlgIfhn63p84CDY/x+PisgcXA==} dependencies: bare-os: 2.2.0 dev: false optional: true + /bare-stream/2.3.2: + resolution: {integrity: sha512-EFZHSIBkDgSHIwj2l2QZfP4U5OcD4xFAOwhSb/vlr9PIqyGJGvB/nfClJbcnh3EY4jtPE4zsb5ztae96bVF79A==} + dependencies: + streamx: 2.20.2 + dev: false + optional: true + /base/0.11.2: resolution: {integrity: sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==} engines: {node: '>=0.10.0'} @@ -7047,7 +7040,6 @@ packages: /binary-extensions/2.2.0: resolution: {integrity: sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==} engines: {node: '>=8'} - dev: true /binaryextensions/4.18.0: resolution: {integrity: sha512-PQu3Kyv9dM4FnwB7XGj1+HucW+ShvJzJqjuw1JkKVs1mWdwOKVcRjOi+pV9X52A0tNvrPCsPkbFFQb+wE1EAXw==} @@ -7137,7 +7129,6 @@ packages: resolution: {integrity: sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==} dependencies: balanced-match: 1.0.2 - dev: true /braces/2.3.2: resolution: {integrity: sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==} @@ -7484,6 +7475,10 @@ packages: tslib: 2.6.2 dev: true + /camelcase-css/2.0.1: + resolution: {integrity: sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==} + engines: {node: '>= 6'} + /camelcase/5.3.1: resolution: {integrity: sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==} engines: {node: '>=6'} @@ -7662,7 +7657,23 @@ packages: readdirp: 3.6.0 optionalDependencies: fsevents: 2.3.3 + + /chokidar/3.6.0: + resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==} + engines: {node: '>= 8.10.0'} + requiresBuild: true + dependencies: + anymatch: 3.1.2 + braces: 3.0.2 + glob-parent: 5.1.2 + is-binary-path: 2.1.0 + is-glob: 4.0.3 + normalize-path: 3.0.0 + readdirp: 3.6.0 + optionalDependencies: + fsevents: 2.3.3 dev: true + optional: true /chownr/2.0.0: resolution: {integrity: sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==} @@ -7915,6 +7926,10 @@ packages: color-string: 1.9.1 dev: false + /colord/2.9.3: + resolution: {integrity: sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw==} + dev: false + /colorette/2.0.20: resolution: {integrity: sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==} dev: true @@ -7973,7 +7988,6 @@ packages: /commander/4.1.1: resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==} engines: {node: '>= 6'} - dev: true /commander/6.2.1: resolution: {integrity: sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA==} @@ -8380,11 +8394,11 @@ packages: fastparse: 1.1.2 dev: true - /css-tree/2.3.1: - resolution: {integrity: sha512-6Fv1DV/TYw//QF5IzQdqsNDjx/wc8TrMBZsqjL9eW01tWb7R7k/mq+/VXfJCl7SoD5emsJop9cOByJZfs8hYIw==} + /css-tree/3.0.1: + resolution: {integrity: sha512-8Fxxv+tGhORlshCdCwnNJytvlvq46sOLSYEx2ZIGurahWvMucSRnyjPA3AmrMq4VPRYbHVpWj5VkiVasrM2H4Q==} engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0} dependencies: - mdn-data: 2.0.30 + mdn-data: 2.12.1 source-map-js: 1.0.2 dev: false @@ -8402,7 +8416,6 @@ packages: resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==} engines: {node: '>=4'} hasBin: true - dev: true /cssnano/3.10.0: resolution: {integrity: sha1-Tzj2zqK5sX+gFJDyPx3GjqZcHDg=} @@ -8473,16 +8486,13 @@ packages: resolution: {integrity: sha512-RSU6Hyeg14am3Ah4VZEmeX8H7kLwEEirXe6aU2IPfKNvhXwTflK5HQRDNI0ypQXoqmm+QPyG2IaPuQE5zMwSIQ==} dev: false - /csstype/3.0.3: - resolution: {integrity: sha512-jPl+wbWPOWJ7SXsWyqGRk3lGecbar0Cb0OvZF/r/ZU011R4YqiRehgkQ9p4eQfo9DSDLqLL3wHwfxeJiuIsNag==} - /csstype/3.0.9: resolution: {integrity: sha512-rpw6JPxK6Rfg1zLOYCSwle2GFOOsnjmDYDaBwEcwoOg4qlsIVCN789VkBZDJAGi4T07gI4YSutR43t9Zz4Lzuw==} - - /csstype/3.1.2: - resolution: {integrity: sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ==} dev: false + /csstype/3.1.3: + resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==} + /custom-event/1.0.1: resolution: {integrity: sha512-GAj5FOq0Hd+RsCGVJxZuKaIDXDf3h6GQoNEjFgbLLI/trgtavwUbSnZ5pVfg27DVCaWjIohryS0JFwIJyT2cMg==} dev: true @@ -8855,6 +8865,9 @@ packages: resolution: {integrity: sha512-uJaamHkagcZtHPqCIHZxnFrXlunQXgBOsZSUOWwFw31QJCAbyTBoHMW75YOTur5ZNx8pIeAKgf6GWIgaqqiLhA==} dev: true + /didyoumean/1.2.2: + resolution: {integrity: sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==} + /diff-match-patch/1.0.5: resolution: {integrity: sha512-IayShXAgj/QMXgB0IWmKx+rOPuGMhqm5w6jvFxmVenXKIzRqTAAsbBPT3kWQeGANj3jGgvcvv4yK6SxqYmikgw==} dev: false @@ -8907,6 +8920,9 @@ packages: resolution: {integrity: sha1-44Mx8IRLukm5qctxx3FYWqsbxlo=} dev: true + /dlv/1.1.3: + resolution: {integrity: sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==} + /dnd-core/16.0.1: resolution: {integrity: sha512-HK294sl7tbw6F6IeuK16YSBUoorvHpY8RHO+9yFfaJyCDVb6n7PRcezrOEOa2SBCqiYpemh5Jx20ZcjKdFAVng==} dependencies: @@ -8956,7 +8972,7 @@ packages: resolution: {integrity: sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==} dependencies: '@babel/runtime': 7.20.13 - csstype: 3.1.2 + csstype: 3.1.3 dev: false /dom-serialize/2.2.1: @@ -9081,7 +9097,6 @@ packages: /eastasianwidth/0.2.0: resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} - dev: true /editions/4.2.0: resolution: {integrity: sha512-pmUkYpyeF6LY1tXXPaVr902upCeSAisp7vtY5udlSSj31emhRTNKBxEJk30sD6CJHbYSPAh+B3GDR4aJvXi/FQ==} @@ -9844,6 +9859,14 @@ packages: eslint: 7.32.0 dev: false + /eslint-plugin-react-refresh/0.4.12_eslint@7.32.0: + resolution: {integrity: sha512-9neVjoGv20FwYtCP6CB1dzR1vr57ZDNOXst21wd2xJ/cTlM2xLq0GWVlSNTdMn/4BtP6cHYBMCSp1wFBJ9jBsg==} + peerDependencies: + eslint: '>=7' + dependencies: + eslint: 7.32.0 + dev: false + /eslint-plugin-react/7.23.2_eslint@7.32.0: resolution: {integrity: sha512-AfjgFQB+nYszudkxRkTFu0UR1zEQig0ArVMPloKhxwlwkzaw/fBiH0QWcBBhZONlXqQC51+nfqFrkn4EzHcGBw==} engines: {node: '>=4'} @@ -10099,7 +10122,7 @@ packages: is-stream: 1.1.0 npm-run-path: 2.0.2 p-finally: 1.0.0 - signal-exit: 3.0.5 + signal-exit: 3.0.7 strip-eof: 1.0.0 dev: true @@ -10580,7 +10603,6 @@ packages: dependencies: cross-spawn: 7.0.3 signal-exit: 4.1.0 - dev: true /fork-ts-checker-async-overlay-webpack-plugin/0.4.0_sjzsq4ndqjvzfirlvtns5daumi: resolution: {integrity: sha512-PdmEymfvFsPsZ3o0b72rtcIOvl3QAUiVXIdV4uzuV/+AH0zjHYy9Mo8qN5wJSLYYzkHnDte+5rGf4mKhuONqJA==} @@ -10890,6 +10912,12 @@ packages: dependencies: is-glob: 4.0.3 + /glob-parent/6.0.2: + resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} + engines: {node: '>=10.13.0'} + dependencies: + is-glob: 4.0.3 + /glob-to-regexp/0.4.1: resolution: {integrity: sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==} @@ -10900,13 +10928,25 @@ packages: dependencies: foreground-child: 3.1.1 jackspeak: 2.3.6 - minimatch: 9.0.3 - minipass: 7.0.4 - path-scurry: 1.10.1 + minimatch: 9.0.5 + minipass: 7.1.2 + path-scurry: 1.11.1 dev: true + /glob/10.4.5: + resolution: {integrity: sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==} + hasBin: true + dependencies: + foreground-child: 3.1.1 + jackspeak: 3.4.3 + minimatch: 9.0.5 + minipass: 7.1.2 + package-json-from-dist: 1.0.0 + path-scurry: 1.11.1 + /glob/3.1.21: resolution: {integrity: sha512-ANhy2V2+tFpRajE3wN4DhkNQ08KDr0Ir1qL12/cUe5+a7STEK8jkW4onUYuY8/06qAFuT5je7mjAqzx0eKI2tQ==} + deprecated: Glob versions prior to v9 are no longer supported dependencies: graceful-fs: 1.2.3 inherits: 1.0.2 @@ -11094,7 +11134,7 @@ packages: source-map: 0.6.1 wordwrap: 1.0.0 optionalDependencies: - uglify-js: 3.17.4 + uglify-js: 3.19.3 dev: true /has-ansi/0.1.0: @@ -11785,7 +11825,6 @@ packages: engines: {node: '>=8'} dependencies: binary-extensions: 2.2.0 - dev: true /is-boolean-object/1.1.2: resolution: {integrity: sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==} @@ -12329,6 +12368,13 @@ packages: '@pkgjs/parseargs': 0.11.0 dev: true + /jackspeak/3.4.3: + resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==} + dependencies: + '@isaacs/cliui': 8.0.2 + optionalDependencies: + '@pkgjs/parseargs': 0.11.0 + /jake/10.8.2: resolution: {integrity: sha512-eLpKyrfG3mzvGE2Du8VoPbeSkRry093+tyNjdYaBbJS9v17knImYGNXQCUV0gLxQtF82m3E8iRb/wdSQZLoq7A==} hasBin: true @@ -12962,6 +13008,10 @@ packages: - utf-8-validate dev: true + /jiti/1.21.6: + resolution: {integrity: sha512-2yTgeWTWzMWkHu6Jp9NKgePDaYHbntiwvYuuJLbbN9vl7DC9DvXKOB2BC3ZZ92D3cvV/aflH0osDfwpHepQ53w==} + hasBin: true + /jotai-devtools/0.6.2_hgjqizhc26gc665cnflpub44vy: resolution: {integrity: sha512-iHKYt8V2T2Gh2DtGRpvpv2daVoFoHRJXqk5/LHnhFkJy9rMQuIGo4XgVu/v1ZMSvMzwDXdU3hDOQkfQWlDErUQ==} engines: {node: '>=14.0.0'} @@ -13462,6 +13512,14 @@ packages: immediate: 3.0.6 dev: false + /lilconfig/2.1.0: + resolution: {integrity: sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==} + engines: {node: '>=10'} + + /lilconfig/3.1.2: + resolution: {integrity: sha512-eop+wDAvpItUys0FWkHIKeC9ybYrTGbU41U5K7+bttZZeohvnY7M9dZ5kB21GNWiFT2q1OoPTvncPCgSOVO5ow==} + engines: {node: '>=14'} + /lines-and-columns/1.1.6: resolution: {integrity: sha512-8ZmlJFVK9iCmtLz19HpSsR8HaAMWBT284VMNednLwlIMDP2hJDCIhUp0IZ2xUcZ+Ob6BM0VvCSJwzASDM45NLQ==} @@ -13793,7 +13851,6 @@ packages: /lru-cache/10.2.0: resolution: {integrity: sha512-2bIM8x+VAf6JT4bKAljS1qUWgMsqZRPGJS6FSahIMPVvctcNhyVp7AJu7quxOW9jwkryBReKZY5tY5JYv2n/7Q==} engines: {node: 14 || >=16.14} - dev: true /lru-cache/2.7.3: resolution: {integrity: sha512-WpibWJ60c3AgAz8a2iYErDrcT2C7OmKnsWhIcHOjkUHFjkXncJhtLxNSqUmxRxRunpb5I8Vprd7aNSd2NtksJQ==} @@ -13877,8 +13934,8 @@ packages: safe-buffer: 5.2.1 dev: true - /mdn-data/2.0.30: - resolution: {integrity: sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA==} + /mdn-data/2.12.1: + resolution: {integrity: sha512-rsfnCbOHjqrhWxwt5/wtSLzpoKTzW7OXdT5lLOIH1OTYhWu9rRJveGq0sKvDZODABH7RX+uoR+DYcpFnq4Tf6Q==} dev: false /media-typer/0.3.0: @@ -14077,12 +14134,11 @@ packages: dependencies: brace-expansion: 1.1.11 - /minimatch/9.0.3: - resolution: {integrity: sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==} + /minimatch/9.0.5: + resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==} engines: {node: '>=16 || 14 >=14.17'} dependencies: brace-expansion: 2.0.1 - dev: true /minimist/1.2.0: resolution: {integrity: sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=} @@ -14102,10 +14158,9 @@ packages: yallist: 4.0.0 dev: true - /minipass/7.0.4: - resolution: {integrity: sha512-jYofLM5Dam9279rdkWzqHozUo4ybjdZmCsDHePy5V/PbBcVMiSZR97gmAy45aqi8CK1lG2ECd356FU86avfwUQ==} + /minipass/7.1.2: + resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==} engines: {node: '>=16 || 14 >=14.17'} - dev: true /minizlib/2.1.2: resolution: {integrity: sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==} @@ -14230,6 +14285,13 @@ packages: engines: {node: '>=0.10.0'} dev: false + /mz/2.7.0: + resolution: {integrity: sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==} + dependencies: + any-promise: 1.3.0 + object-assign: 4.1.1 + thenify-all: 1.6.0 + /nanoid/3.1.20: resolution: {integrity: sha512-a1cQNyczgKbLX9jwbS/+d7W8fX/RfgYR7lVWwWOGIPNgK2m0MWvrGF6/m4kk6U3QcFMnZf3RIhL0v2Jgh/0Uxw==} engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} @@ -14402,7 +14464,6 @@ packages: /normalize-path/3.0.0: resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} engines: {node: '>=0.10.0'} - dev: true /normalize-range/0.1.2: resolution: {integrity: sha1-LRDAa9/TEuqXd2laTShDlFa3WUI=} @@ -14508,6 +14569,10 @@ packages: kind-of: 3.2.2 dev: true + /object-hash/3.0.0: + resolution: {integrity: sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==} + engines: {node: '>= 6'} + /object-inspect/1.11.0: resolution: {integrity: sha512-jp7ikS6Sd3GxQfZJPyH3cjcbJF6GZPClgdV+EFygjFLQ5FmW/dRUnTd9PQ9k0JhoNDabWFbpF1yCdSWCC6gexg==} dev: true @@ -14818,6 +14883,9 @@ packages: netmask: 2.0.2 dev: false + /package-json-from-dist/1.0.0: + resolution: {integrity: sha512-dATvCeZN/8wQsGywez1mzHtTlP22H8OEfPrVMLNr4/eGa+ijtLn/6M5f0dY8UKNrC2O9UCU6SSoG3qRKnt7STw==} + /pako/0.2.9: resolution: {integrity: sha1-8/dSL073gjSNqBYbrZ7P1Rv4OnU=} dev: true @@ -14962,6 +15030,7 @@ packages: /path-parse/1.0.6: resolution: {integrity: sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==} + dev: false /path-parse/1.0.7: resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} @@ -14971,13 +15040,12 @@ packages: engines: {node: '>= 0.8.0'} dev: true - /path-scurry/1.10.1: - resolution: {integrity: sha512-MkhCqzzBEpPvxxQ71Md0b1Kk51W01lrYvlMzSUaIzNsODdd7mqhiimSZlr+VegAz5Z6Vzt9Xg2ttE//XBhH3EQ==} - engines: {node: '>=16 || 14 >=14.17'} + /path-scurry/1.11.1: + resolution: {integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==} + engines: {node: '>=16 || 14 >=14.18'} dependencies: lru-cache: 10.2.0 - minipass: 7.0.4 - dev: true + minipass: 7.1.2 /path-to-regexp/0.1.7: resolution: {integrity: sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==} @@ -15031,11 +15099,6 @@ packages: /picocolors/1.0.0: resolution: {integrity: sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==} - /picomatch/2.3.0: - resolution: {integrity: sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw==} - engines: {node: '>=8.6'} - dev: true - /picomatch/2.3.1: resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} engines: {node: '>=8.6'} @@ -15043,7 +15106,6 @@ packages: /pify/2.3.0: resolution: {integrity: sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==} engines: {node: '>=0.10.0'} - dev: true /pify/3.0.0: resolution: {integrity: sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=} @@ -15070,7 +15132,6 @@ packages: /pirates/4.0.6: resolution: {integrity: sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==} engines: {node: '>= 6'} - dev: true /pkg-dir/4.2.0: resolution: {integrity: sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==} @@ -15165,6 +15226,42 @@ packages: postcss: 5.2.18 dev: true + /postcss-import/15.1.0_postcss@8.4.27: + resolution: {integrity: sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==} + engines: {node: '>=14.0.0'} + peerDependencies: + postcss: ^8.0.0 + dependencies: + postcss: 8.4.27 + postcss-value-parser: 4.2.0 + read-cache: 1.0.0 + resolve: 1.22.2 + + /postcss-js/4.0.1_postcss@8.4.27: + resolution: {integrity: sha512-dDLF8pEO191hJMtlHFPRa8xsizHaM82MLfNkUHdUtVEV3tgTp5oj+8qbEqYM57SLfc74KSbw//4SeJma2LRVIw==} + engines: {node: ^12 || ^14 || >= 16} + peerDependencies: + postcss: ^8.4.21 + dependencies: + camelcase-css: 2.0.1 + postcss: 8.4.27 + + /postcss-load-config/4.0.2_postcss@8.4.27: + resolution: {integrity: sha512-bSVhyJGL00wMVoPUzAVAnbEoWyqRxkjv64tUl427SKnPrENtq6hJwUojroMz2VB+Q1edmi4IfrAPpami5VVgMQ==} + engines: {node: '>= 14'} + peerDependencies: + postcss: '>=8.0.9' + ts-node: '>=9.0.0' + peerDependenciesMeta: + postcss: + optional: true + ts-node: + optional: true + dependencies: + lilconfig: 3.1.2 + postcss: 8.4.27 + yaml: 2.5.1 + /postcss-merge-idents/2.1.7: resolution: {integrity: sha1-TFUwMTwI4dWzu/PSu8dH4njuonA=} dependencies: @@ -15253,6 +15350,25 @@ packages: postcss: 6.0.23 dev: true + /postcss-nested/5.0.6_postcss@8.4.27: + resolution: {integrity: sha512-rKqm2Fk0KbA8Vt3AdGN0FB9OBOMDVajMG6ZCf/GoHgdxUJ4sBFp0A/uMIRm+MJUdo33YXEtjqIz8u7DAp8B7DA==} + engines: {node: '>=12.0'} + peerDependencies: + postcss: ^8.2.14 + dependencies: + postcss: 8.4.27 + postcss-selector-parser: 6.1.2 + dev: false + + /postcss-nested/6.2.0_postcss@8.4.27: + resolution: {integrity: sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==} + engines: {node: '>=12.0'} + peerDependencies: + postcss: ^8.2.14 + dependencies: + postcss: 8.4.27 + postcss-selector-parser: 6.1.2 + /postcss-normalize-charset/1.1.1: resolution: {integrity: sha1-757nEhLX/nWceO0WL2HtYrXLk/E=} dependencies: @@ -15297,13 +15413,20 @@ packages: dev: true /postcss-selector-parser/2.2.3: - resolution: {integrity: sha1-+UN3iGBsPJrO4W/+jYsWKX8nu5A=} + resolution: {integrity: sha512-3pqyakeGhrO0BQ5+/tGTfvi5IAUAhHRayGK8WFSu06aEv2BmHoXw/Mhb+w7VY5HERIuC+QoUI7wgrCcq2hqCVA==} dependencies: flatten: 1.0.3 indexes-of: 1.0.1 uniq: 1.0.1 dev: true + /postcss-selector-parser/6.1.2: + resolution: {integrity: sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==} + engines: {node: '>=4'} + dependencies: + cssesc: 3.0.0 + util-deprecate: 1.0.2 + /postcss-svgo/2.1.6: resolution: {integrity: sha1-tt8YqmE7Zm4TPwittSGcJoSsEI0=} dependencies: @@ -15325,6 +15448,9 @@ packages: resolution: {integrity: sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==} dev: true + /postcss-value-parser/4.2.0: + resolution: {integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==} + /postcss-zindex/2.2.0: resolution: {integrity: sha1-0hCd3AVbka9n/EyzsCWUZjnSryI=} dependencies: @@ -15688,7 +15814,6 @@ packages: /quick-lru/5.1.1: resolution: {integrity: sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==} engines: {node: '>=10'} - dev: true /raf/3.4.1: resolution: {integrity: sha512-Sq4CW4QhwOHE8ucn6J34MqtZCeWFP2aQSmrlroYgqAV1PjStIhJXxYuTgUIfkEk7zTLjmIjLmU5q+fbD1NnOJA==} @@ -15763,7 +15888,7 @@ packages: '@babel/runtime': 7.20.13 classnames: 2.2.6 dom-align: 1.12.4 - rc-util: 5.38.1_ef5jwxihqo6n7gxfmzogljlgcm + rc-util: 5.43.0_ef5jwxihqo6n7gxfmzogljlgcm react: 18.1.0_47cciibm4ysmleigs33s763fqu react-dom: 18.1.0_abari7w75zfr6mrhvamxwmfpxm_react@18.1.0 resize-observer-polyfill: 1.5.1 @@ -15805,7 +15930,7 @@ packages: dependencies: array-tree-filter: 2.1.0 rc-trigger: 4.3.5_ef5jwxihqo6n7gxfmzogljlgcm - rc-util: 5.38.1_ef5jwxihqo6n7gxfmzogljlgcm + rc-util: 5.43.0_ef5jwxihqo6n7gxfmzogljlgcm warning: 4.0.3 transitivePeerDependencies: - react @@ -15825,7 +15950,7 @@ packages: '@ant-design/css-animation': 1.7.3 classnames: 2.2.6 rc-animate: 3.1.1_ef5jwxihqo6n7gxfmzogljlgcm - rc-util: 5.38.1_ef5jwxihqo6n7gxfmzogljlgcm + rc-util: 5.43.0_ef5jwxihqo6n7gxfmzogljlgcm shallowequal: 1.1.0 transitivePeerDependencies: - react @@ -15837,7 +15962,7 @@ packages: dependencies: babel-runtime: 6.26.0 rc-animate: 3.1.1_ef5jwxihqo6n7gxfmzogljlgcm - rc-util: 5.38.1_ef5jwxihqo6n7gxfmzogljlgcm + rc-util: 5.43.0_ef5jwxihqo6n7gxfmzogljlgcm transitivePeerDependencies: - react - react-dom @@ -15850,7 +15975,7 @@ packages: dependencies: '@babel/runtime': 7.20.13 classnames: 2.2.6 - rc-util: 5.38.1_ef5jwxihqo6n7gxfmzogljlgcm + rc-util: 5.43.0_ef5jwxihqo6n7gxfmzogljlgcm react: 18.1.0_47cciibm4ysmleigs33s763fqu transitivePeerDependencies: - react-dom @@ -15877,7 +16002,7 @@ packages: dependencies: '@babel/runtime': 7.20.13 async-validator: 3.5.2 - rc-util: 5.38.1_ef5jwxihqo6n7gxfmzogljlgcm + rc-util: 5.43.0_ef5jwxihqo6n7gxfmzogljlgcm react: 18.1.0_47cciibm4ysmleigs33s763fqu transitivePeerDependencies: - react-dom @@ -15887,7 +16012,7 @@ packages: resolution: {integrity: sha512-4GgnJCjllAVNsZ9fPA+3LnoIgwUqM8QAWpyoKiTkPDN1UWapXYsPiKJCXOhnmiR0X8xpEoYHiobUaiquMliWiQ==} dependencies: classnames: 2.2.6 - rc-util: 5.38.1_ef5jwxihqo6n7gxfmzogljlgcm + rc-util: 5.43.0_ef5jwxihqo6n7gxfmzogljlgcm transitivePeerDependencies: - react - react-dom @@ -15902,7 +16027,7 @@ packages: classnames: 2.2.6 rc-menu: 8.3.1_ef5jwxihqo6n7gxfmzogljlgcm rc-trigger: 4.3.5_ef5jwxihqo6n7gxfmzogljlgcm - rc-util: 5.38.1_ef5jwxihqo6n7gxfmzogljlgcm + rc-util: 5.43.0_ef5jwxihqo6n7gxfmzogljlgcm react: 18.1.0_47cciibm4ysmleigs33s763fqu transitivePeerDependencies: - react-dom @@ -15916,7 +16041,7 @@ packages: mini-store: 3.0.6_ef5jwxihqo6n7gxfmzogljlgcm rc-animate: 3.1.1_ef5jwxihqo6n7gxfmzogljlgcm rc-trigger: 4.3.5_ef5jwxihqo6n7gxfmzogljlgcm - rc-util: 5.38.1_ef5jwxihqo6n7gxfmzogljlgcm + rc-util: 5.43.0_ef5jwxihqo6n7gxfmzogljlgcm resize-observer-polyfill: 1.5.1 shallowequal: 1.1.0 transitivePeerDependencies: @@ -15933,7 +16058,7 @@ packages: '@babel/runtime': 7.20.13 classnames: 2.2.6 raf: 3.4.1 - rc-util: 5.38.1_ef5jwxihqo6n7gxfmzogljlgcm + rc-util: 5.43.0_ef5jwxihqo6n7gxfmzogljlgcm react: 18.1.0_47cciibm4ysmleigs33s763fqu react-dom: 18.1.0_abari7w75zfr6mrhvamxwmfpxm_react@18.1.0 dev: false @@ -15946,7 +16071,7 @@ packages: dependencies: '@babel/runtime': 7.20.13 classnames: 2.2.6 - rc-util: 5.38.1_ef5jwxihqo6n7gxfmzogljlgcm + rc-util: 5.43.0_ef5jwxihqo6n7gxfmzogljlgcm react: 18.1.0_47cciibm4ysmleigs33s763fqu react-dom: 18.1.0_abari7w75zfr6mrhvamxwmfpxm_react@18.1.0 dev: false @@ -15961,7 +16086,7 @@ packages: '@babel/runtime': 7.20.13 classnames: 2.2.6 rc-animate: 3.1.1_ef5jwxihqo6n7gxfmzogljlgcm - rc-util: 5.38.1_ef5jwxihqo6n7gxfmzogljlgcm + rc-util: 5.43.0_ef5jwxihqo6n7gxfmzogljlgcm react: 18.1.0_47cciibm4ysmleigs33s763fqu react-dom: 18.1.0_abari7w75zfr6mrhvamxwmfpxm_react@18.1.0 dev: false @@ -15990,7 +16115,7 @@ packages: classnames: 2.2.6 moment: 2.29.1 rc-trigger: 4.3.5_ef5jwxihqo6n7gxfmzogljlgcm - rc-util: 5.38.1_ef5jwxihqo6n7gxfmzogljlgcm + rc-util: 5.43.0_ef5jwxihqo6n7gxfmzogljlgcm react: 18.1.0_47cciibm4ysmleigs33s763fqu react-dom: 18.1.0_abari7w75zfr6mrhvamxwmfpxm_react@18.1.0 shallowequal: 1.1.0 @@ -16008,7 +16133,7 @@ packages: dependencies: '@babel/runtime': 7.20.13 classnames: 2.2.6 - rc-util: 5.38.1_ef5jwxihqo6n7gxfmzogljlgcm + rc-util: 5.43.0_ef5jwxihqo6n7gxfmzogljlgcm transitivePeerDependencies: - react - react-dom @@ -16022,7 +16147,7 @@ packages: dependencies: '@babel/runtime': 7.20.13 classnames: 2.2.6 - rc-util: 5.38.1_ef5jwxihqo6n7gxfmzogljlgcm + rc-util: 5.43.0_ef5jwxihqo6n7gxfmzogljlgcm react: 18.1.0_47cciibm4ysmleigs33s763fqu react-dom: 18.1.0_abari7w75zfr6mrhvamxwmfpxm_react@18.1.0 resize-observer-polyfill: 1.5.1 @@ -16036,7 +16161,7 @@ packages: dependencies: '@babel/runtime': 7.20.13 classnames: 2.2.6 - rc-util: 5.38.1_ef5jwxihqo6n7gxfmzogljlgcm + rc-util: 5.43.0_ef5jwxihqo6n7gxfmzogljlgcm react: 18.1.0_47cciibm4ysmleigs33s763fqu react-dom: 18.1.0_abari7w75zfr6mrhvamxwmfpxm_react@18.1.0 resize-observer-polyfill: 1.5.1 @@ -16053,7 +16178,7 @@ packages: classnames: 2.2.6 rc-motion: 1.1.2_ef5jwxihqo6n7gxfmzogljlgcm rc-trigger: 4.3.5_ef5jwxihqo6n7gxfmzogljlgcm - rc-util: 5.38.1_ef5jwxihqo6n7gxfmzogljlgcm + rc-util: 5.43.0_ef5jwxihqo6n7gxfmzogljlgcm rc-virtual-list: 1.1.6_ef5jwxihqo6n7gxfmzogljlgcm react: 18.1.0_47cciibm4ysmleigs33s763fqu react-dom: 18.1.0_abari7w75zfr6mrhvamxwmfpxm_react@18.1.0 @@ -16070,7 +16195,7 @@ packages: '@babel/runtime': 7.20.13 classnames: 2.2.6 rc-tooltip: 4.2.3_ef5jwxihqo6n7gxfmzogljlgcm - rc-util: 5.38.1_ef5jwxihqo6n7gxfmzogljlgcm + rc-util: 5.43.0_ef5jwxihqo6n7gxfmzogljlgcm react: 18.1.0_47cciibm4ysmleigs33s763fqu react-dom: 18.1.0_abari7w75zfr6mrhvamxwmfpxm_react@18.1.0 shallowequal: 1.1.0 @@ -16085,7 +16210,7 @@ packages: dependencies: '@babel/runtime': 7.20.13 classnames: 2.2.6 - rc-util: 5.38.1_ef5jwxihqo6n7gxfmzogljlgcm + rc-util: 5.43.0_ef5jwxihqo6n7gxfmzogljlgcm react: 18.1.0_47cciibm4ysmleigs33s763fqu react-dom: 18.1.0_abari7w75zfr6mrhvamxwmfpxm_react@18.1.0 dev: false @@ -16098,7 +16223,7 @@ packages: dependencies: '@babel/runtime': 7.20.13 classnames: 2.2.6 - rc-util: 5.38.1_ef5jwxihqo6n7gxfmzogljlgcm + rc-util: 5.43.0_ef5jwxihqo6n7gxfmzogljlgcm react: 18.1.0_47cciibm4ysmleigs33s763fqu react-dom: 18.1.0_abari7w75zfr6mrhvamxwmfpxm_react@18.1.0 dev: false @@ -16131,7 +16256,7 @@ packages: rc-menu: 8.3.1_ef5jwxihqo6n7gxfmzogljlgcm rc-resize-observer: 0.2.6_ef5jwxihqo6n7gxfmzogljlgcm rc-trigger: 4.3.5_ef5jwxihqo6n7gxfmzogljlgcm - rc-util: 5.38.1_ef5jwxihqo6n7gxfmzogljlgcm + rc-util: 5.43.0_ef5jwxihqo6n7gxfmzogljlgcm transitivePeerDependencies: - react - react-dom @@ -16168,7 +16293,7 @@ packages: classnames: 2.2.6 rc-select: 11.0.13_ef5jwxihqo6n7gxfmzogljlgcm rc-tree: 3.11.0_ef5jwxihqo6n7gxfmzogljlgcm - rc-util: 5.38.1_ef5jwxihqo6n7gxfmzogljlgcm + rc-util: 5.43.0_ef5jwxihqo6n7gxfmzogljlgcm react: 18.1.0_47cciibm4ysmleigs33s763fqu react-dom: 18.1.0_abari7w75zfr6mrhvamxwmfpxm_react@18.1.0 dev: false @@ -16183,7 +16308,7 @@ packages: '@babel/runtime': 7.20.13 classnames: 2.2.6 rc-motion: 2.4.4_ef5jwxihqo6n7gxfmzogljlgcm - rc-util: 5.38.1_ef5jwxihqo6n7gxfmzogljlgcm + rc-util: 5.43.0_ef5jwxihqo6n7gxfmzogljlgcm rc-virtual-list: 3.4.1_ef5jwxihqo6n7gxfmzogljlgcm react: 18.1.0_47cciibm4ysmleigs33s763fqu react-dom: 18.1.0_abari7w75zfr6mrhvamxwmfpxm_react@18.1.0 @@ -16199,7 +16324,7 @@ packages: '@babel/runtime': 7.20.13 classnames: 2.2.6 rc-animate: 3.1.1_ef5jwxihqo6n7gxfmzogljlgcm - rc-util: 5.38.1_ef5jwxihqo6n7gxfmzogljlgcm + rc-util: 5.43.0_ef5jwxihqo6n7gxfmzogljlgcm rc-virtual-list: 1.1.6_ef5jwxihqo6n7gxfmzogljlgcm react: 18.1.0_47cciibm4ysmleigs33s763fqu react-dom: 18.1.0_abari7w75zfr6mrhvamxwmfpxm_react@18.1.0 @@ -16229,7 +16354,7 @@ packages: raf: 3.4.1 rc-align: 4.0.15_ef5jwxihqo6n7gxfmzogljlgcm rc-animate: 3.1.1_ef5jwxihqo6n7gxfmzogljlgcm - rc-util: 5.38.1_ef5jwxihqo6n7gxfmzogljlgcm + rc-util: 5.43.0_ef5jwxihqo6n7gxfmzogljlgcm transitivePeerDependencies: - react - react-dom @@ -16264,8 +16389,8 @@ packages: shallowequal: 1.1.0 dev: false - /rc-util/5.38.1_ef5jwxihqo6n7gxfmzogljlgcm: - resolution: {integrity: sha512-e4ZMs7q9XqwTuhIK7zBIVFltUtMSjphuPPQXHoHlzRzNdOwUxDejo0Zls5HYaJfRKNURcsS/ceKVULlhjBrxng==} + /rc-util/5.43.0_ef5jwxihqo6n7gxfmzogljlgcm: + resolution: {integrity: sha512-AzC7KKOXFqAdIBqdGWepL9Xn7cm3vnAmjlHqUnoQaTMZYhM4VlXGLkkHHxj/BZ7Td0+SOPKB4RGPboBVKT9htw==} peerDependencies: react: '>=16.9.0' react-dom: '>=16.9.0' @@ -16273,7 +16398,7 @@ packages: '@babel/runtime': 7.20.13 react: 18.1.0_47cciibm4ysmleigs33s763fqu react-dom: 18.1.0_abari7w75zfr6mrhvamxwmfpxm_react@18.1.0 - react-is: 18.2.0 + react-is: 18.3.1 dev: false /rc-virtual-list/1.1.6_ef5jwxihqo6n7gxfmzogljlgcm: @@ -16285,7 +16410,7 @@ packages: dependencies: classnames: 2.2.6 raf: 3.4.1 - rc-util: 5.38.1_ef5jwxihqo6n7gxfmzogljlgcm + rc-util: 5.43.0_ef5jwxihqo6n7gxfmzogljlgcm react: 18.1.0_47cciibm4ysmleigs33s763fqu react-dom: 18.1.0_abari7w75zfr6mrhvamxwmfpxm_react@18.1.0 dev: false @@ -16299,7 +16424,7 @@ packages: dependencies: classnames: 2.2.6 rc-resize-observer: 1.0.1_ef5jwxihqo6n7gxfmzogljlgcm - rc-util: 5.38.1_ef5jwxihqo6n7gxfmzogljlgcm + rc-util: 5.43.0_ef5jwxihqo6n7gxfmzogljlgcm react: 18.1.0_47cciibm4ysmleigs33s763fqu react-dom: 18.1.0_abari7w75zfr6mrhvamxwmfpxm_react@18.1.0 dev: false @@ -16331,7 +16456,7 @@ packages: '@types/lodash': 4.14.198 base16: 1.0.0 color: 3.2.1 - csstype: 3.1.2 + csstype: 3.1.3 lodash.curry: 4.1.1 dev: false @@ -16477,8 +16602,8 @@ packages: resolution: {integrity: sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==} dev: true - /react-is/18.2.0: - resolution: {integrity: sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==} + /react-is/18.3.1: + resolution: {integrity: sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==} dev: false /react-json-tree/0.18.0_7cpxmzzodpxnolj5zcc5cr63ji: @@ -16891,6 +17016,11 @@ packages: loose-envify: 1.4.0 patched: true + /read-cache/1.0.0: + resolution: {integrity: sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==} + dependencies: + pify: 2.3.0 + /read-only-stream/2.0.0: resolution: {integrity: sha1-JyT9aoET1zdkrCiNQ4YnDB2/F/A=} dependencies: @@ -16939,7 +17069,6 @@ packages: engines: {node: '>=8.10.0'} dependencies: picomatch: 2.3.1 - dev: true /rechoir/0.8.0: resolution: {integrity: sha512-/vxpCXddiX8NGfGO/mTafwjq4aFa/71pvamip0++IQk3zG8cbCj0fifNPrjjF1XMXUne91jL9OoxmdykoEtifQ==} @@ -17155,7 +17284,7 @@ packages: resolution: {integrity: sha512-wENBPt4ySzg4ybFQW2TT1zMQucPK95HSh/nq2CFTZVOGut2+pQvSsgtda4d26YrYcr067wjbmzOG8byDPBX63A==} dependencies: is-core-module: 2.12.1 - path-parse: 1.0.6 + path-parse: 1.0.7 /resolve/1.22.2: resolution: {integrity: sha512-Sb+mjNHOULsBv818T40qSPeRiuWLyaGMa5ewydRLFimneixmVy2zdivRl+AF6jaYPC8ERxGDmFSiqui6SfPd+g==} @@ -17678,10 +17807,6 @@ packages: resolution: {integrity: sha1-P/IfGYytIXX587eBhT/ZTQ0ZtZA=} dev: true - /signal-exit/3.0.5: - resolution: {integrity: sha512-KWcOiKeQj6ZyXx7zq4YxSMgHRlod4czeBQZrPb8OKcohcqAXShm7E20kEMle9WBt26hFcAf0qLOcp5zmY7kOqQ==} - dev: true - /signal-exit/3.0.7: resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==} dev: true @@ -17689,7 +17814,6 @@ packages: /signal-exit/4.1.0: resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} engines: {node: '>=14'} - dev: true /simple-concat/1.0.1: resolution: {integrity: sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==} @@ -18120,6 +18244,17 @@ packages: queue-tick: 1.0.1 dev: false + /streamx/2.20.2: + resolution: {integrity: sha512-aDGDLU+j9tJcUdPGOaHmVF1u/hhI+CsGkT02V3OKlHDV7IukOI+nTWAGkiZEKCO35rWN1wIr4tS7YFr1f4qSvA==} + dependencies: + fast-fifo: 1.3.2 + queue-tick: 1.0.1 + text-decoder: 1.2.1 + optionalDependencies: + bare-events: 2.2.0 + dev: false + optional: true + /strict-uri-encode/1.1.0: resolution: {integrity: sha1-J5siXfHVgrH1TmWt3UNS4Y+qBxM=} engines: {node: '>=0.10.0'} @@ -18188,7 +18323,6 @@ packages: eastasianwidth: 0.2.0 emoji-regex: 9.2.2 strip-ansi: 7.1.0 - dev: true /string.prototype.matchall/4.0.5: resolution: {integrity: sha512-Z5ZaXO0svs0M2xd/6By3qpeKpLKd9mO4v4q3oMEQrk8Ck4xOD5d5XeBOOjGrmVZZ/AHB1S0CgG4N5r1G9N3E2Q==} @@ -18302,7 +18436,6 @@ packages: engines: {node: '>=12'} dependencies: ansi-regex: 6.0.1 - dev: true /strip-bom/2.0.0: resolution: {integrity: sha512-kwrX1y7czp1E69n2ajbG65mIo9dqvJ+8aBQXOGVxqwvNbsXdFM6Lq37dLAY3mknUwru8CfcCbfOLL/gMo+fi3g==} @@ -18353,10 +18486,6 @@ packages: inline-style-parser: 0.1.1 dev: false - /style-vendorizer/2.2.3: - resolution: {integrity: sha512-/VDRsWvQAgspVy9eATN3z6itKTuyg+jW1q6UoTCQCFRqPDw8bi3E1hXIKnGw5LvXS2AQPuJ7Af4auTLYeBOLEg==} - dev: false - /stylis/4.0.10: resolution: {integrity: sha512-m3k+dk7QeJw660eIKRRn3xPF6uuvHs/FFzjX3HQ5ove0qYsiygoAhwn5a3IYKaZPo5LrYD0rfVmtv1gNY1uYwg==} dev: false @@ -18367,6 +18496,19 @@ packages: minimist: 1.2.8 dev: true + /sucrase/3.35.0: + resolution: {integrity: sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA==} + engines: {node: '>=16 || 14 >=14.17'} + hasBin: true + dependencies: + '@jridgewell/gen-mapping': 0.3.3 + commander: 4.1.1 + glob: 10.4.5 + lines-and-columns: 1.1.6 + mz: 2.7.0 + pirates: 4.0.6 + ts-interface-checker: 0.1.13 + /superstruct/0.8.4: resolution: {integrity: sha512-48Ors8IVWZm/tMr8r0Si6+mJiB7mkD7jqvIzktjJ4+EnP5tBp0qOpiM1J8sCUorKx+TXWrfb3i1UcjdD1YK/wA==} dependencies: @@ -18482,6 +18624,36 @@ packages: string-width: 4.2.3 strip-ansi: 6.0.1 + /tailwindcss/3.4.13: + resolution: {integrity: sha512-KqjHOJKogOUt5Bs752ykCeiwvi0fKVkr5oqsFNt/8px/tA8scFPIlkygsf6jXrfCqGHz7VflA6+yytWuM+XhFw==} + engines: {node: '>=14.0.0'} + hasBin: true + dependencies: + '@alloc/quick-lru': 5.2.0 + arg: 5.0.2 + chokidar: 3.5.3 + didyoumean: 1.2.2 + dlv: 1.1.3 + fast-glob: 3.3.1 + glob-parent: 6.0.2 + is-glob: 4.0.3 + jiti: 1.21.6 + lilconfig: 2.1.0 + micromatch: 4.0.5 + normalize-path: 3.0.0 + object-hash: 3.0.0 + picocolors: 1.0.0 + postcss: 8.4.27 + postcss-import: 15.1.0_postcss@8.4.27 + postcss-js: 4.0.1_postcss@8.4.27 + postcss-load-config: 4.0.2_postcss@8.4.27 + postcss-nested: 6.2.0_postcss@8.4.27 + postcss-selector-parser: 6.1.2 + resolve: 1.22.2 + sucrase: 3.35.0 + transitivePeerDependencies: + - ts-node + /tapable/1.1.3: resolution: {integrity: sha512-4WK/bYZmj8xLr+HUCODHGF1ZFzsYffasLUgEiMBY4fgtltdO6B4WJtlSbPaDTLpYTcGVwM2qLnFTICEcNxs3kA==} engines: {node: '>=6'} @@ -18498,8 +18670,8 @@ packages: pump: 3.0.0 tar-stream: 3.1.6 optionalDependencies: - bare-fs: 2.1.5 - bare-path: 2.1.0 + bare-fs: 2.3.5 + bare-path: 2.1.3 dev: false /tar-stream/3.1.6: @@ -18583,6 +18755,11 @@ packages: minimatch: 3.0.4 dev: true + /text-decoder/1.2.1: + resolution: {integrity: sha512-x9v3H/lTKIJKQQe7RPQkLfKAnc9lUTkWDypIQgTzPJAq+5/GCDHonmshfvlsNSj58yyshbIJJDLmU15qNERrXQ==} + dev: false + optional: true + /text-table/0.2.0: resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==} @@ -18591,6 +18768,17 @@ packages: engines: {node: '>=0.8'} dev: false + /thenify-all/1.6.0: + resolution: {integrity: sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==} + engines: {node: '>=0.8'} + dependencies: + thenify: 3.3.1 + + /thenify/3.3.1: + resolution: {integrity: sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==} + dependencies: + any-promise: 1.3.0 + /three/0.139.2: resolution: {integrity: sha512-gV7q7QY8rogu7HLFZR9cWnOQAUedUhu2WXAnpr2kdXZP9YDKsG/0ychwQvWkZN5PlNw9mv5MoCTin6zNTXoONg==} dev: false @@ -18782,6 +18970,9 @@ packages: dependencies: typescript: 5.5.4 + /ts-interface-checker/0.1.13: + resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==} + /ts-loader/5.3.3_typescript@5.5.4: resolution: {integrity: sha512-KwF1SplmOJepnoZ4eRIloH/zXL195F51skt7reEsS6jvDqzgc/YSbz9b8E07GxIUwLXdcD4ssrJu6v8CwaTafA==} engines: {node: '>=6.11.5'} @@ -19004,8 +19195,8 @@ packages: /ua-parser-js/0.7.33: resolution: {integrity: sha512-s8ax/CeZdK9R/56Sui0WM6y9OFREJarMRHqLB2EwkovemBxNQ+Bqu8GAsUnVcXKgphb++ghr/B2BZx4mahujPw==} - /uglify-js/3.17.4: - resolution: {integrity: sha512-T9q82TJI9e/C1TAxYvfb16xO120tMVFZrGA3f9/P4424DNu6ypK103y0GPFVa17yotwSyZW5iYXgjYHkGrJW/g==} + /uglify-js/3.19.3: + resolution: {integrity: sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ==} engines: {node: '>=0.8.0'} hasBin: true requiresBuild: true @@ -19839,7 +20030,6 @@ packages: ansi-styles: 6.2.1 string-width: 5.1.2 strip-ansi: 7.1.0 - dev: true /wrappy/1.0.2: resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} @@ -19978,6 +20168,11 @@ packages: resolution: {integrity: sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==} engines: {node: '>= 6'} + /yaml/2.5.1: + resolution: {integrity: sha512-bLQOjaX/ADgQ20isPJRvF0iRUHIxVhYvr53Of7wGcWlO2jvtUlH5m87DsmulFVxRpNLOnI4tB6p/oh8D7kpn9Q==} + engines: {node: '>= 14'} + hasBin: true + /yargs-parser/20.2.4: resolution: {integrity: sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA==} engines: {node: '>=10'} diff --git a/editor/resources/editor/icons/light/inspector/align-center-black-16x16@2x.png b/editor/resources/editor/icons/light/inspector/align-center-black-16x16@2x.png new file mode 100644 index 000000000000..93f2170de29e Binary files /dev/null and b/editor/resources/editor/icons/light/inspector/align-center-black-16x16@2x.png differ diff --git a/editor/resources/editor/icons/light/inspector/align-center-blue-16x16@2x.png b/editor/resources/editor/icons/light/inspector/align-center-blue-16x16@2x.png new file mode 100644 index 000000000000..1bcf7fa8881b Binary files /dev/null and b/editor/resources/editor/icons/light/inspector/align-center-blue-16x16@2x.png differ diff --git a/editor/resources/editor/icons/light/inspector/align-center-darkgray-16x16@2x.png b/editor/resources/editor/icons/light/inspector/align-center-darkgray-16x16@2x.png new file mode 100644 index 000000000000..b398eeccaa7d Binary files /dev/null and b/editor/resources/editor/icons/light/inspector/align-center-darkgray-16x16@2x.png differ diff --git a/editor/resources/editor/icons/light/inspector/align-center-gray-16x16@2x.png b/editor/resources/editor/icons/light/inspector/align-center-gray-16x16@2x.png new file mode 100644 index 000000000000..b65c95474241 Binary files /dev/null and b/editor/resources/editor/icons/light/inspector/align-center-gray-16x16@2x.png differ diff --git a/editor/resources/editor/icons/light/inspector/align-center-lightblue-16x16@2x.png b/editor/resources/editor/icons/light/inspector/align-center-lightblue-16x16@2x.png new file mode 100644 index 000000000000..aa7bf5077359 Binary files /dev/null and b/editor/resources/editor/icons/light/inspector/align-center-lightblue-16x16@2x.png differ diff --git a/editor/resources/editor/icons/light/inspector/align-center-lightgray-16x16@2x.png b/editor/resources/editor/icons/light/inspector/align-center-lightgray-16x16@2x.png new file mode 100644 index 000000000000..c82864bf183e Binary files /dev/null and b/editor/resources/editor/icons/light/inspector/align-center-lightgray-16x16@2x.png differ diff --git a/editor/resources/editor/icons/light/inspector/align-center-white-16x16@2x.png b/editor/resources/editor/icons/light/inspector/align-center-white-16x16@2x.png new file mode 100644 index 000000000000..6da6b2deff5a Binary files /dev/null and b/editor/resources/editor/icons/light/inspector/align-center-white-16x16@2x.png differ diff --git a/editor/resources/editor/icons/light/inspector/align-end-black-16x16@2x.png b/editor/resources/editor/icons/light/inspector/align-end-black-16x16@2x.png new file mode 100644 index 000000000000..20a0e5313c70 Binary files /dev/null and b/editor/resources/editor/icons/light/inspector/align-end-black-16x16@2x.png differ diff --git a/editor/resources/editor/icons/light/inspector/align-end-blue-16x16@2x.png b/editor/resources/editor/icons/light/inspector/align-end-blue-16x16@2x.png new file mode 100644 index 000000000000..42abb635ecd1 Binary files /dev/null and b/editor/resources/editor/icons/light/inspector/align-end-blue-16x16@2x.png differ diff --git a/editor/resources/editor/icons/light/inspector/align-end-darkgray-16x16@2x.png b/editor/resources/editor/icons/light/inspector/align-end-darkgray-16x16@2x.png new file mode 100644 index 000000000000..f4285f1ebc37 Binary files /dev/null and b/editor/resources/editor/icons/light/inspector/align-end-darkgray-16x16@2x.png differ diff --git a/editor/resources/editor/icons/light/inspector/align-end-gray-16x16@2x.png b/editor/resources/editor/icons/light/inspector/align-end-gray-16x16@2x.png new file mode 100644 index 000000000000..0b2f29bbb090 Binary files /dev/null and b/editor/resources/editor/icons/light/inspector/align-end-gray-16x16@2x.png differ diff --git a/editor/resources/editor/icons/light/inspector/align-end-lightblue-16x16@2x.png b/editor/resources/editor/icons/light/inspector/align-end-lightblue-16x16@2x.png new file mode 100644 index 000000000000..44372410e880 Binary files /dev/null and b/editor/resources/editor/icons/light/inspector/align-end-lightblue-16x16@2x.png differ diff --git a/editor/resources/editor/icons/light/inspector/align-end-lightgray-16x16@2x.png b/editor/resources/editor/icons/light/inspector/align-end-lightgray-16x16@2x.png new file mode 100644 index 000000000000..d65bbc55a9ac Binary files /dev/null and b/editor/resources/editor/icons/light/inspector/align-end-lightgray-16x16@2x.png differ diff --git a/editor/resources/editor/icons/light/inspector/align-end-white-16x16@2x.png b/editor/resources/editor/icons/light/inspector/align-end-white-16x16@2x.png new file mode 100644 index 000000000000..79c3ae8993d6 Binary files /dev/null and b/editor/resources/editor/icons/light/inspector/align-end-white-16x16@2x.png differ diff --git a/editor/resources/editor/icons/light/inspector/align-start-black-16x16@2x.png b/editor/resources/editor/icons/light/inspector/align-start-black-16x16@2x.png new file mode 100644 index 000000000000..960a86d1bad1 Binary files /dev/null and b/editor/resources/editor/icons/light/inspector/align-start-black-16x16@2x.png differ diff --git a/editor/resources/editor/icons/light/inspector/align-start-blue-16x16@2x.png b/editor/resources/editor/icons/light/inspector/align-start-blue-16x16@2x.png new file mode 100644 index 000000000000..d38daf6327d7 Binary files /dev/null and b/editor/resources/editor/icons/light/inspector/align-start-blue-16x16@2x.png differ diff --git a/editor/resources/editor/icons/light/inspector/align-start-darkgray-16x16@2x.png b/editor/resources/editor/icons/light/inspector/align-start-darkgray-16x16@2x.png new file mode 100644 index 000000000000..c5bd477faab5 Binary files /dev/null and b/editor/resources/editor/icons/light/inspector/align-start-darkgray-16x16@2x.png differ diff --git a/editor/resources/editor/icons/light/inspector/align-start-gray-16x16@2x.png b/editor/resources/editor/icons/light/inspector/align-start-gray-16x16@2x.png new file mode 100644 index 000000000000..43d57b22c49d Binary files /dev/null and b/editor/resources/editor/icons/light/inspector/align-start-gray-16x16@2x.png differ diff --git a/editor/resources/editor/icons/light/inspector/align-start-lightblue-16x16@2x.png b/editor/resources/editor/icons/light/inspector/align-start-lightblue-16x16@2x.png new file mode 100644 index 000000000000..126b17b52b43 Binary files /dev/null and b/editor/resources/editor/icons/light/inspector/align-start-lightblue-16x16@2x.png differ diff --git a/editor/resources/editor/icons/light/inspector/align-start-lightgray-16x16@2x.png b/editor/resources/editor/icons/light/inspector/align-start-lightgray-16x16@2x.png new file mode 100644 index 000000000000..4590d2307705 Binary files /dev/null and b/editor/resources/editor/icons/light/inspector/align-start-lightgray-16x16@2x.png differ diff --git a/editor/resources/editor/icons/light/inspector/align-start-white-16x16@2x.png b/editor/resources/editor/icons/light/inspector/align-start-white-16x16@2x.png new file mode 100644 index 000000000000..ad454f10a5f7 Binary files /dev/null and b/editor/resources/editor/icons/light/inspector/align-start-white-16x16@2x.png differ diff --git a/editor/resources/editor/icons/light/inspector/alignSelf-center-black-16x16@2x.png b/editor/resources/editor/icons/light/inspector/alignSelf-center-black-16x16@2x.png new file mode 100644 index 000000000000..999a530d99eb Binary files /dev/null and b/editor/resources/editor/icons/light/inspector/alignSelf-center-black-16x16@2x.png differ diff --git a/editor/resources/editor/icons/light/inspector/alignSelf-center-blue-16x16@2x.png b/editor/resources/editor/icons/light/inspector/alignSelf-center-blue-16x16@2x.png new file mode 100644 index 000000000000..e9e4b2669f0a Binary files /dev/null and b/editor/resources/editor/icons/light/inspector/alignSelf-center-blue-16x16@2x.png differ diff --git a/editor/resources/editor/icons/light/inspector/alignSelf-center-darkgray-16x16@2x.png b/editor/resources/editor/icons/light/inspector/alignSelf-center-darkgray-16x16@2x.png new file mode 100644 index 000000000000..0b6664c068f8 Binary files /dev/null and b/editor/resources/editor/icons/light/inspector/alignSelf-center-darkgray-16x16@2x.png differ diff --git a/editor/resources/editor/icons/light/inspector/alignSelf-center-gray-16x16@2x.png b/editor/resources/editor/icons/light/inspector/alignSelf-center-gray-16x16@2x.png new file mode 100644 index 000000000000..c28e4aef26fa Binary files /dev/null and b/editor/resources/editor/icons/light/inspector/alignSelf-center-gray-16x16@2x.png differ diff --git a/editor/resources/editor/icons/light/inspector/alignSelf-center-lightblue-16x16@2x.png b/editor/resources/editor/icons/light/inspector/alignSelf-center-lightblue-16x16@2x.png new file mode 100644 index 000000000000..a1ace9c02fd6 Binary files /dev/null and b/editor/resources/editor/icons/light/inspector/alignSelf-center-lightblue-16x16@2x.png differ diff --git a/editor/resources/editor/icons/light/inspector/alignSelf-center-lightgray-16x16@2x.png b/editor/resources/editor/icons/light/inspector/alignSelf-center-lightgray-16x16@2x.png new file mode 100644 index 000000000000..2fa005e86d61 Binary files /dev/null and b/editor/resources/editor/icons/light/inspector/alignSelf-center-lightgray-16x16@2x.png differ diff --git a/editor/resources/editor/icons/light/inspector/alignSelf-center-white-16x16@2x.png b/editor/resources/editor/icons/light/inspector/alignSelf-center-white-16x16@2x.png new file mode 100644 index 000000000000..4680a1423a25 Binary files /dev/null and b/editor/resources/editor/icons/light/inspector/alignSelf-center-white-16x16@2x.png differ diff --git a/editor/resources/editor/icons/light/inspector/alignSelf-end-black-16x16@2x.png b/editor/resources/editor/icons/light/inspector/alignSelf-end-black-16x16@2x.png new file mode 100644 index 000000000000..95d3dc87718e Binary files /dev/null and b/editor/resources/editor/icons/light/inspector/alignSelf-end-black-16x16@2x.png differ diff --git a/editor/resources/editor/icons/light/inspector/alignSelf-end-blue-16x16@2x.png b/editor/resources/editor/icons/light/inspector/alignSelf-end-blue-16x16@2x.png new file mode 100644 index 000000000000..9f48c837088e Binary files /dev/null and b/editor/resources/editor/icons/light/inspector/alignSelf-end-blue-16x16@2x.png differ diff --git a/editor/resources/editor/icons/light/inspector/alignSelf-end-darkgray-16x16@2x.png b/editor/resources/editor/icons/light/inspector/alignSelf-end-darkgray-16x16@2x.png new file mode 100644 index 000000000000..bc87fbb0246b Binary files /dev/null and b/editor/resources/editor/icons/light/inspector/alignSelf-end-darkgray-16x16@2x.png differ diff --git a/editor/resources/editor/icons/light/inspector/alignSelf-end-gray-16x16@2x.png b/editor/resources/editor/icons/light/inspector/alignSelf-end-gray-16x16@2x.png new file mode 100644 index 000000000000..94b4bc51b761 Binary files /dev/null and b/editor/resources/editor/icons/light/inspector/alignSelf-end-gray-16x16@2x.png differ diff --git a/editor/resources/editor/icons/light/inspector/alignSelf-end-lightblue-16x16@2x.png b/editor/resources/editor/icons/light/inspector/alignSelf-end-lightblue-16x16@2x.png new file mode 100644 index 000000000000..ebdf99131910 Binary files /dev/null and b/editor/resources/editor/icons/light/inspector/alignSelf-end-lightblue-16x16@2x.png differ diff --git a/editor/resources/editor/icons/light/inspector/alignSelf-end-lightgray-16x16@2x.png b/editor/resources/editor/icons/light/inspector/alignSelf-end-lightgray-16x16@2x.png new file mode 100644 index 000000000000..521136787fd9 Binary files /dev/null and b/editor/resources/editor/icons/light/inspector/alignSelf-end-lightgray-16x16@2x.png differ diff --git a/editor/resources/editor/icons/light/inspector/alignSelf-end-white-16x16@2x.png b/editor/resources/editor/icons/light/inspector/alignSelf-end-white-16x16@2x.png new file mode 100644 index 000000000000..7f675ead8ef4 Binary files /dev/null and b/editor/resources/editor/icons/light/inspector/alignSelf-end-white-16x16@2x.png differ diff --git a/editor/resources/editor/icons/light/inspector/alignSelf-start-black-16x16@2x.png b/editor/resources/editor/icons/light/inspector/alignSelf-start-black-16x16@2x.png new file mode 100644 index 000000000000..2f3fbea477df Binary files /dev/null and b/editor/resources/editor/icons/light/inspector/alignSelf-start-black-16x16@2x.png differ diff --git a/editor/resources/editor/icons/light/inspector/alignSelf-start-blue-16x16@2x.png b/editor/resources/editor/icons/light/inspector/alignSelf-start-blue-16x16@2x.png new file mode 100644 index 000000000000..3b0d12638ed1 Binary files /dev/null and b/editor/resources/editor/icons/light/inspector/alignSelf-start-blue-16x16@2x.png differ diff --git a/editor/resources/editor/icons/light/inspector/alignSelf-start-darkgray-16x16@2x.png b/editor/resources/editor/icons/light/inspector/alignSelf-start-darkgray-16x16@2x.png new file mode 100644 index 000000000000..52125f4dce6c Binary files /dev/null and b/editor/resources/editor/icons/light/inspector/alignSelf-start-darkgray-16x16@2x.png differ diff --git a/editor/resources/editor/icons/light/inspector/alignSelf-start-gray-16x16@2x.png b/editor/resources/editor/icons/light/inspector/alignSelf-start-gray-16x16@2x.png new file mode 100644 index 000000000000..cc25074cc04f Binary files /dev/null and b/editor/resources/editor/icons/light/inspector/alignSelf-start-gray-16x16@2x.png differ diff --git a/editor/resources/editor/icons/light/inspector/alignSelf-start-lightblue-16x16@2x.png b/editor/resources/editor/icons/light/inspector/alignSelf-start-lightblue-16x16@2x.png new file mode 100644 index 000000000000..8c56c96ef502 Binary files /dev/null and b/editor/resources/editor/icons/light/inspector/alignSelf-start-lightblue-16x16@2x.png differ diff --git a/editor/resources/editor/icons/light/inspector/alignSelf-start-lightgray-16x16@2x.png b/editor/resources/editor/icons/light/inspector/alignSelf-start-lightgray-16x16@2x.png new file mode 100644 index 000000000000..d3bc0bcfefa4 Binary files /dev/null and b/editor/resources/editor/icons/light/inspector/alignSelf-start-lightgray-16x16@2x.png differ diff --git a/editor/resources/editor/icons/light/inspector/alignSelf-start-white-16x16@2x.png b/editor/resources/editor/icons/light/inspector/alignSelf-start-white-16x16@2x.png new file mode 100644 index 000000000000..43ddb4087f04 Binary files /dev/null and b/editor/resources/editor/icons/light/inspector/alignSelf-start-white-16x16@2x.png differ diff --git a/editor/resources/editor/icons/light/inspector/distribute-horizontal-black-16x16@2x.png b/editor/resources/editor/icons/light/inspector/distribute-horizontal-black-16x16@2x.png new file mode 100644 index 000000000000..fb95baab2754 Binary files /dev/null and b/editor/resources/editor/icons/light/inspector/distribute-horizontal-black-16x16@2x.png differ diff --git a/editor/resources/editor/icons/light/inspector/distribute-horizontal-blue-16x16@2x.png b/editor/resources/editor/icons/light/inspector/distribute-horizontal-blue-16x16@2x.png new file mode 100644 index 000000000000..fa22295ff2d9 Binary files /dev/null and b/editor/resources/editor/icons/light/inspector/distribute-horizontal-blue-16x16@2x.png differ diff --git a/editor/resources/editor/icons/light/inspector/distribute-horizontal-darkgray-16x16@2x.png b/editor/resources/editor/icons/light/inspector/distribute-horizontal-darkgray-16x16@2x.png new file mode 100644 index 000000000000..1c0431b18098 Binary files /dev/null and b/editor/resources/editor/icons/light/inspector/distribute-horizontal-darkgray-16x16@2x.png differ diff --git a/editor/resources/editor/icons/light/inspector/distribute-horizontal-gray-16x16@2x.png b/editor/resources/editor/icons/light/inspector/distribute-horizontal-gray-16x16@2x.png new file mode 100644 index 000000000000..e7531010a7ed Binary files /dev/null and b/editor/resources/editor/icons/light/inspector/distribute-horizontal-gray-16x16@2x.png differ diff --git a/editor/resources/editor/icons/light/inspector/distribute-horizontal-lightblue-16x16@2x.png b/editor/resources/editor/icons/light/inspector/distribute-horizontal-lightblue-16x16@2x.png new file mode 100644 index 000000000000..0fe8a80f9315 Binary files /dev/null and b/editor/resources/editor/icons/light/inspector/distribute-horizontal-lightblue-16x16@2x.png differ diff --git a/editor/resources/editor/icons/light/inspector/distribute-horizontal-lightgray-16x16@2x.png b/editor/resources/editor/icons/light/inspector/distribute-horizontal-lightgray-16x16@2x.png new file mode 100644 index 000000000000..d53c4bab49ea Binary files /dev/null and b/editor/resources/editor/icons/light/inspector/distribute-horizontal-lightgray-16x16@2x.png differ diff --git a/editor/resources/editor/icons/light/inspector/distribute-horizontal-white-16x16@2x.png b/editor/resources/editor/icons/light/inspector/distribute-horizontal-white-16x16@2x.png new file mode 100644 index 000000000000..b5a8b35fc257 Binary files /dev/null and b/editor/resources/editor/icons/light/inspector/distribute-horizontal-white-16x16@2x.png differ diff --git a/editor/resources/editor/icons/light/inspector/distribute-vertical-black-16x16@2x.png b/editor/resources/editor/icons/light/inspector/distribute-vertical-black-16x16@2x.png new file mode 100644 index 000000000000..f4bebd133b87 Binary files /dev/null and b/editor/resources/editor/icons/light/inspector/distribute-vertical-black-16x16@2x.png differ diff --git a/editor/resources/editor/icons/light/inspector/distribute-vertical-blue-16x16@2x.png b/editor/resources/editor/icons/light/inspector/distribute-vertical-blue-16x16@2x.png new file mode 100644 index 000000000000..5227d874e854 Binary files /dev/null and b/editor/resources/editor/icons/light/inspector/distribute-vertical-blue-16x16@2x.png differ diff --git a/editor/resources/editor/icons/light/inspector/distribute-vertical-darkgray-16x16@2x.png b/editor/resources/editor/icons/light/inspector/distribute-vertical-darkgray-16x16@2x.png new file mode 100644 index 000000000000..23201445ddfd Binary files /dev/null and b/editor/resources/editor/icons/light/inspector/distribute-vertical-darkgray-16x16@2x.png differ diff --git a/editor/resources/editor/icons/light/inspector/distribute-vertical-gray-16x16@2x.png b/editor/resources/editor/icons/light/inspector/distribute-vertical-gray-16x16@2x.png new file mode 100644 index 000000000000..b888a4b176e7 Binary files /dev/null and b/editor/resources/editor/icons/light/inspector/distribute-vertical-gray-16x16@2x.png differ diff --git a/editor/resources/editor/icons/light/inspector/distribute-vertical-lightblue-16x16@2x.png b/editor/resources/editor/icons/light/inspector/distribute-vertical-lightblue-16x16@2x.png new file mode 100644 index 000000000000..0fcffa786438 Binary files /dev/null and b/editor/resources/editor/icons/light/inspector/distribute-vertical-lightblue-16x16@2x.png differ diff --git a/editor/resources/editor/icons/light/inspector/distribute-vertical-lightgray-16x16@2x.png b/editor/resources/editor/icons/light/inspector/distribute-vertical-lightgray-16x16@2x.png new file mode 100644 index 000000000000..a14dac6e0895 Binary files /dev/null and b/editor/resources/editor/icons/light/inspector/distribute-vertical-lightgray-16x16@2x.png differ diff --git a/editor/resources/editor/icons/light/inspector/distribute-vertical-white-16x16@2x.png b/editor/resources/editor/icons/light/inspector/distribute-vertical-white-16x16@2x.png new file mode 100644 index 000000000000..ac38e63e7484 Binary files /dev/null and b/editor/resources/editor/icons/light/inspector/distribute-vertical-white-16x16@2x.png differ diff --git a/editor/resources/editor/icons/light/inspector/justify-center-black-16x16@2x.png b/editor/resources/editor/icons/light/inspector/justify-center-black-16x16@2x.png new file mode 100644 index 000000000000..da0d7558fae2 Binary files /dev/null and b/editor/resources/editor/icons/light/inspector/justify-center-black-16x16@2x.png differ diff --git a/editor/resources/editor/icons/light/inspector/justify-center-blue-16x16@2x.png b/editor/resources/editor/icons/light/inspector/justify-center-blue-16x16@2x.png new file mode 100644 index 000000000000..4efe18981c87 Binary files /dev/null and b/editor/resources/editor/icons/light/inspector/justify-center-blue-16x16@2x.png differ diff --git a/editor/resources/editor/icons/light/inspector/justify-center-darkgray-16x16@2x.png b/editor/resources/editor/icons/light/inspector/justify-center-darkgray-16x16@2x.png new file mode 100644 index 000000000000..0ad9e2a3f03e Binary files /dev/null and b/editor/resources/editor/icons/light/inspector/justify-center-darkgray-16x16@2x.png differ diff --git a/editor/resources/editor/icons/light/inspector/justify-center-gray-16x16@2x.png b/editor/resources/editor/icons/light/inspector/justify-center-gray-16x16@2x.png new file mode 100644 index 000000000000..fcb1ba165a41 Binary files /dev/null and b/editor/resources/editor/icons/light/inspector/justify-center-gray-16x16@2x.png differ diff --git a/editor/resources/editor/icons/light/inspector/justify-center-lightblue-16x16@2x.png b/editor/resources/editor/icons/light/inspector/justify-center-lightblue-16x16@2x.png new file mode 100644 index 000000000000..dd851c79a36e Binary files /dev/null and b/editor/resources/editor/icons/light/inspector/justify-center-lightblue-16x16@2x.png differ diff --git a/editor/resources/editor/icons/light/inspector/justify-center-lightgray-16x16@2x.png b/editor/resources/editor/icons/light/inspector/justify-center-lightgray-16x16@2x.png new file mode 100644 index 000000000000..1b8a9dc091e8 Binary files /dev/null and b/editor/resources/editor/icons/light/inspector/justify-center-lightgray-16x16@2x.png differ diff --git a/editor/resources/editor/icons/light/inspector/justify-center-white-16x16@2x.png b/editor/resources/editor/icons/light/inspector/justify-center-white-16x16@2x.png new file mode 100644 index 000000000000..d8acc3f050c1 Binary files /dev/null and b/editor/resources/editor/icons/light/inspector/justify-center-white-16x16@2x.png differ diff --git a/editor/resources/editor/icons/light/inspector/justify-end-black-16x16@2x.png b/editor/resources/editor/icons/light/inspector/justify-end-black-16x16@2x.png new file mode 100644 index 000000000000..66b91617ff39 Binary files /dev/null and b/editor/resources/editor/icons/light/inspector/justify-end-black-16x16@2x.png differ diff --git a/editor/resources/editor/icons/light/inspector/justify-end-blue-16x16@2x.png b/editor/resources/editor/icons/light/inspector/justify-end-blue-16x16@2x.png new file mode 100644 index 000000000000..587fbe287d86 Binary files /dev/null and b/editor/resources/editor/icons/light/inspector/justify-end-blue-16x16@2x.png differ diff --git a/editor/resources/editor/icons/light/inspector/justify-end-darkgray-16x16@2x.png b/editor/resources/editor/icons/light/inspector/justify-end-darkgray-16x16@2x.png new file mode 100644 index 000000000000..32094c00018b Binary files /dev/null and b/editor/resources/editor/icons/light/inspector/justify-end-darkgray-16x16@2x.png differ diff --git a/editor/resources/editor/icons/light/inspector/justify-end-gray-16x16@2x.png b/editor/resources/editor/icons/light/inspector/justify-end-gray-16x16@2x.png new file mode 100644 index 000000000000..c4b114f7ba03 Binary files /dev/null and b/editor/resources/editor/icons/light/inspector/justify-end-gray-16x16@2x.png differ diff --git a/editor/resources/editor/icons/light/inspector/justify-end-lightblue-16x16@2x.png b/editor/resources/editor/icons/light/inspector/justify-end-lightblue-16x16@2x.png new file mode 100644 index 000000000000..42ec86ee68ae Binary files /dev/null and b/editor/resources/editor/icons/light/inspector/justify-end-lightblue-16x16@2x.png differ diff --git a/editor/resources/editor/icons/light/inspector/justify-end-lightgray-16x16@2x.png b/editor/resources/editor/icons/light/inspector/justify-end-lightgray-16x16@2x.png new file mode 100644 index 000000000000..7b9f5fad1a9b Binary files /dev/null and b/editor/resources/editor/icons/light/inspector/justify-end-lightgray-16x16@2x.png differ diff --git a/editor/resources/editor/icons/light/inspector/justify-end-white-16x16@2x.png b/editor/resources/editor/icons/light/inspector/justify-end-white-16x16@2x.png new file mode 100644 index 000000000000..4fcd7ab13bdb Binary files /dev/null and b/editor/resources/editor/icons/light/inspector/justify-end-white-16x16@2x.png differ diff --git a/editor/resources/editor/icons/light/inspector/justify-start-black-16x16@2x.png b/editor/resources/editor/icons/light/inspector/justify-start-black-16x16@2x.png new file mode 100644 index 000000000000..3f8de1d98230 Binary files /dev/null and b/editor/resources/editor/icons/light/inspector/justify-start-black-16x16@2x.png differ diff --git a/editor/resources/editor/icons/light/inspector/justify-start-blue-16x16@2x.png b/editor/resources/editor/icons/light/inspector/justify-start-blue-16x16@2x.png new file mode 100644 index 000000000000..27dc3fd271a8 Binary files /dev/null and b/editor/resources/editor/icons/light/inspector/justify-start-blue-16x16@2x.png differ diff --git a/editor/resources/editor/icons/light/inspector/justify-start-darkgray-16x16@2x.png b/editor/resources/editor/icons/light/inspector/justify-start-darkgray-16x16@2x.png new file mode 100644 index 000000000000..e967e31bae2a Binary files /dev/null and b/editor/resources/editor/icons/light/inspector/justify-start-darkgray-16x16@2x.png differ diff --git a/editor/resources/editor/icons/light/inspector/justify-start-gray-16x16@2x.png b/editor/resources/editor/icons/light/inspector/justify-start-gray-16x16@2x.png new file mode 100644 index 000000000000..1889700e7d47 Binary files /dev/null and b/editor/resources/editor/icons/light/inspector/justify-start-gray-16x16@2x.png differ diff --git a/editor/resources/editor/icons/light/inspector/justify-start-lightblue-16x16@2x.png b/editor/resources/editor/icons/light/inspector/justify-start-lightblue-16x16@2x.png new file mode 100644 index 000000000000..0a96b9545138 Binary files /dev/null and b/editor/resources/editor/icons/light/inspector/justify-start-lightblue-16x16@2x.png differ diff --git a/editor/resources/editor/icons/light/inspector/justify-start-lightgray-16x16@2x.png b/editor/resources/editor/icons/light/inspector/justify-start-lightgray-16x16@2x.png new file mode 100644 index 000000000000..a90100c0fe42 Binary files /dev/null and b/editor/resources/editor/icons/light/inspector/justify-start-lightgray-16x16@2x.png differ diff --git a/editor/resources/editor/icons/light/inspector/justify-start-white-16x16@2x.png b/editor/resources/editor/icons/light/inspector/justify-start-white-16x16@2x.png new file mode 100644 index 000000000000..a9bfd13e0ea7 Binary files /dev/null and b/editor/resources/editor/icons/light/inspector/justify-start-white-16x16@2x.png differ diff --git a/editor/resources/editor/icons/light/inspector/justifySelf-center-black-16x16@2x.png b/editor/resources/editor/icons/light/inspector/justifySelf-center-black-16x16@2x.png new file mode 100644 index 000000000000..2aad798edbf3 Binary files /dev/null and b/editor/resources/editor/icons/light/inspector/justifySelf-center-black-16x16@2x.png differ diff --git a/editor/resources/editor/icons/light/inspector/justifySelf-center-blue-16x16@2x.png b/editor/resources/editor/icons/light/inspector/justifySelf-center-blue-16x16@2x.png new file mode 100644 index 000000000000..ea35b38f92ad Binary files /dev/null and b/editor/resources/editor/icons/light/inspector/justifySelf-center-blue-16x16@2x.png differ diff --git a/editor/resources/editor/icons/light/inspector/justifySelf-center-darkgray-16x16@2x.png b/editor/resources/editor/icons/light/inspector/justifySelf-center-darkgray-16x16@2x.png new file mode 100644 index 000000000000..2c9b99a4cfdf Binary files /dev/null and b/editor/resources/editor/icons/light/inspector/justifySelf-center-darkgray-16x16@2x.png differ diff --git a/editor/resources/editor/icons/light/inspector/justifySelf-center-gray-16x16@2x.png b/editor/resources/editor/icons/light/inspector/justifySelf-center-gray-16x16@2x.png new file mode 100644 index 000000000000..ae498bc69a9e Binary files /dev/null and b/editor/resources/editor/icons/light/inspector/justifySelf-center-gray-16x16@2x.png differ diff --git a/editor/resources/editor/icons/light/inspector/justifySelf-center-lightblue-16x16@2x.png b/editor/resources/editor/icons/light/inspector/justifySelf-center-lightblue-16x16@2x.png new file mode 100644 index 000000000000..d811646a521d Binary files /dev/null and b/editor/resources/editor/icons/light/inspector/justifySelf-center-lightblue-16x16@2x.png differ diff --git a/editor/resources/editor/icons/light/inspector/justifySelf-center-lightgray-16x16@2x.png b/editor/resources/editor/icons/light/inspector/justifySelf-center-lightgray-16x16@2x.png new file mode 100644 index 000000000000..7425952dc67d Binary files /dev/null and b/editor/resources/editor/icons/light/inspector/justifySelf-center-lightgray-16x16@2x.png differ diff --git a/editor/resources/editor/icons/light/inspector/justifySelf-center-white-16x16@2x.png b/editor/resources/editor/icons/light/inspector/justifySelf-center-white-16x16@2x.png new file mode 100644 index 000000000000..8aa5312073a1 Binary files /dev/null and b/editor/resources/editor/icons/light/inspector/justifySelf-center-white-16x16@2x.png differ diff --git a/editor/resources/editor/icons/light/inspector/justifySelf-end-black-16x16@2x.png b/editor/resources/editor/icons/light/inspector/justifySelf-end-black-16x16@2x.png new file mode 100644 index 000000000000..fa52a02737f4 Binary files /dev/null and b/editor/resources/editor/icons/light/inspector/justifySelf-end-black-16x16@2x.png differ diff --git a/editor/resources/editor/icons/light/inspector/justifySelf-end-blue-16x16@2x.png b/editor/resources/editor/icons/light/inspector/justifySelf-end-blue-16x16@2x.png new file mode 100644 index 000000000000..ebea0168202c Binary files /dev/null and b/editor/resources/editor/icons/light/inspector/justifySelf-end-blue-16x16@2x.png differ diff --git a/editor/resources/editor/icons/light/inspector/justifySelf-end-darkgray-16x16@2x.png b/editor/resources/editor/icons/light/inspector/justifySelf-end-darkgray-16x16@2x.png new file mode 100644 index 000000000000..32790cb578a2 Binary files /dev/null and b/editor/resources/editor/icons/light/inspector/justifySelf-end-darkgray-16x16@2x.png differ diff --git a/editor/resources/editor/icons/light/inspector/justifySelf-end-gray-16x16@2x.png b/editor/resources/editor/icons/light/inspector/justifySelf-end-gray-16x16@2x.png new file mode 100644 index 000000000000..f5c08ddcbb88 Binary files /dev/null and b/editor/resources/editor/icons/light/inspector/justifySelf-end-gray-16x16@2x.png differ diff --git a/editor/resources/editor/icons/light/inspector/justifySelf-end-lightblue-16x16@2x.png b/editor/resources/editor/icons/light/inspector/justifySelf-end-lightblue-16x16@2x.png new file mode 100644 index 000000000000..49b0712fd1c8 Binary files /dev/null and b/editor/resources/editor/icons/light/inspector/justifySelf-end-lightblue-16x16@2x.png differ diff --git a/editor/resources/editor/icons/light/inspector/justifySelf-end-lightgray-16x16@2x.png b/editor/resources/editor/icons/light/inspector/justifySelf-end-lightgray-16x16@2x.png new file mode 100644 index 000000000000..b9c2911c2a85 Binary files /dev/null and b/editor/resources/editor/icons/light/inspector/justifySelf-end-lightgray-16x16@2x.png differ diff --git a/editor/resources/editor/icons/light/inspector/justifySelf-end-white-16x16@2x.png b/editor/resources/editor/icons/light/inspector/justifySelf-end-white-16x16@2x.png new file mode 100644 index 000000000000..4f8ab867c30c Binary files /dev/null and b/editor/resources/editor/icons/light/inspector/justifySelf-end-white-16x16@2x.png differ diff --git a/editor/resources/editor/icons/light/inspector/justifySelf-start-black-16x16@2x.png b/editor/resources/editor/icons/light/inspector/justifySelf-start-black-16x16@2x.png new file mode 100644 index 000000000000..c6b9f0c8d3c4 Binary files /dev/null and b/editor/resources/editor/icons/light/inspector/justifySelf-start-black-16x16@2x.png differ diff --git a/editor/resources/editor/icons/light/inspector/justifySelf-start-blue-16x16@2x.png b/editor/resources/editor/icons/light/inspector/justifySelf-start-blue-16x16@2x.png new file mode 100644 index 000000000000..b54200cc390f Binary files /dev/null and b/editor/resources/editor/icons/light/inspector/justifySelf-start-blue-16x16@2x.png differ diff --git a/editor/resources/editor/icons/light/inspector/justifySelf-start-darkgray-16x16@2x.png b/editor/resources/editor/icons/light/inspector/justifySelf-start-darkgray-16x16@2x.png new file mode 100644 index 000000000000..8b3b2453dc09 Binary files /dev/null and b/editor/resources/editor/icons/light/inspector/justifySelf-start-darkgray-16x16@2x.png differ diff --git a/editor/resources/editor/icons/light/inspector/justifySelf-start-gray-16x16@2x.png b/editor/resources/editor/icons/light/inspector/justifySelf-start-gray-16x16@2x.png new file mode 100644 index 000000000000..2168246c04cd Binary files /dev/null and b/editor/resources/editor/icons/light/inspector/justifySelf-start-gray-16x16@2x.png differ diff --git a/editor/resources/editor/icons/light/inspector/justifySelf-start-lightblue-16x16@2x.png b/editor/resources/editor/icons/light/inspector/justifySelf-start-lightblue-16x16@2x.png new file mode 100644 index 000000000000..5e99e15d07f9 Binary files /dev/null and b/editor/resources/editor/icons/light/inspector/justifySelf-start-lightblue-16x16@2x.png differ diff --git a/editor/resources/editor/icons/light/inspector/justifySelf-start-lightgray-16x16@2x.png b/editor/resources/editor/icons/light/inspector/justifySelf-start-lightgray-16x16@2x.png new file mode 100644 index 000000000000..cead8128da03 Binary files /dev/null and b/editor/resources/editor/icons/light/inspector/justifySelf-start-lightgray-16x16@2x.png differ diff --git a/editor/resources/editor/icons/light/inspector/justifySelf-start-white-16x16@2x.png b/editor/resources/editor/icons/light/inspector/justifySelf-start-white-16x16@2x.png new file mode 100644 index 000000000000..554d560ce6cc Binary files /dev/null and b/editor/resources/editor/icons/light/inspector/justifySelf-start-white-16x16@2x.png differ diff --git a/editor/resources/editor/icons/light/inspector/tidy-horizontal-black-16x16@2x.png b/editor/resources/editor/icons/light/inspector/tidy-horizontal-black-16x16@2x.png new file mode 100644 index 000000000000..7411ad73bb42 Binary files /dev/null and b/editor/resources/editor/icons/light/inspector/tidy-horizontal-black-16x16@2x.png differ diff --git a/editor/resources/editor/icons/light/inspector/tidy-horizontal-blue-16x16@2x.png b/editor/resources/editor/icons/light/inspector/tidy-horizontal-blue-16x16@2x.png new file mode 100644 index 000000000000..824528ab3753 Binary files /dev/null and b/editor/resources/editor/icons/light/inspector/tidy-horizontal-blue-16x16@2x.png differ diff --git a/editor/resources/editor/icons/light/inspector/tidy-horizontal-darkgray-16x16@2x.png b/editor/resources/editor/icons/light/inspector/tidy-horizontal-darkgray-16x16@2x.png new file mode 100644 index 000000000000..34eaa35d53d6 Binary files /dev/null and b/editor/resources/editor/icons/light/inspector/tidy-horizontal-darkgray-16x16@2x.png differ diff --git a/editor/resources/editor/icons/light/inspector/tidy-horizontal-gray-16x16@2x.png b/editor/resources/editor/icons/light/inspector/tidy-horizontal-gray-16x16@2x.png new file mode 100644 index 000000000000..93502238ae71 Binary files /dev/null and b/editor/resources/editor/icons/light/inspector/tidy-horizontal-gray-16x16@2x.png differ diff --git a/editor/resources/editor/icons/light/inspector/tidy-horizontal-lightblue-16x16@2x.png b/editor/resources/editor/icons/light/inspector/tidy-horizontal-lightblue-16x16@2x.png new file mode 100644 index 000000000000..0af639ef0be7 Binary files /dev/null and b/editor/resources/editor/icons/light/inspector/tidy-horizontal-lightblue-16x16@2x.png differ diff --git a/editor/resources/editor/icons/light/inspector/tidy-horizontal-lightgray-16x16@2x.png b/editor/resources/editor/icons/light/inspector/tidy-horizontal-lightgray-16x16@2x.png new file mode 100644 index 000000000000..870c06cc17ea Binary files /dev/null and b/editor/resources/editor/icons/light/inspector/tidy-horizontal-lightgray-16x16@2x.png differ diff --git a/editor/resources/editor/icons/light/inspector/tidy-horizontal-white-16x16@2x.png b/editor/resources/editor/icons/light/inspector/tidy-horizontal-white-16x16@2x.png new file mode 100644 index 000000000000..d22acbb431e3 Binary files /dev/null and b/editor/resources/editor/icons/light/inspector/tidy-horizontal-white-16x16@2x.png differ diff --git a/editor/resources/editor/icons/light/inspector/tidy-vertical-black-16x16@2x.png b/editor/resources/editor/icons/light/inspector/tidy-vertical-black-16x16@2x.png new file mode 100644 index 000000000000..7625d1e09619 Binary files /dev/null and b/editor/resources/editor/icons/light/inspector/tidy-vertical-black-16x16@2x.png differ diff --git a/editor/resources/editor/icons/light/inspector/tidy-vertical-blue-16x16@2x.png b/editor/resources/editor/icons/light/inspector/tidy-vertical-blue-16x16@2x.png new file mode 100644 index 000000000000..d6a7f4106b3b Binary files /dev/null and b/editor/resources/editor/icons/light/inspector/tidy-vertical-blue-16x16@2x.png differ diff --git a/editor/resources/editor/icons/light/inspector/tidy-vertical-darkgray-16x16@2x.png b/editor/resources/editor/icons/light/inspector/tidy-vertical-darkgray-16x16@2x.png new file mode 100644 index 000000000000..013431baeeb1 Binary files /dev/null and b/editor/resources/editor/icons/light/inspector/tidy-vertical-darkgray-16x16@2x.png differ diff --git a/editor/resources/editor/icons/light/inspector/tidy-vertical-gray-16x16@2x.png b/editor/resources/editor/icons/light/inspector/tidy-vertical-gray-16x16@2x.png new file mode 100644 index 000000000000..229e421c3f8d Binary files /dev/null and b/editor/resources/editor/icons/light/inspector/tidy-vertical-gray-16x16@2x.png differ diff --git a/editor/resources/editor/icons/light/inspector/tidy-vertical-lightblue-16x16@2x.png b/editor/resources/editor/icons/light/inspector/tidy-vertical-lightblue-16x16@2x.png new file mode 100644 index 000000000000..5bacf82e5f27 Binary files /dev/null and b/editor/resources/editor/icons/light/inspector/tidy-vertical-lightblue-16x16@2x.png differ diff --git a/editor/resources/editor/icons/light/inspector/tidy-vertical-lightgray-16x16@2x.png b/editor/resources/editor/icons/light/inspector/tidy-vertical-lightgray-16x16@2x.png new file mode 100644 index 000000000000..a8a1ad7f9ed2 Binary files /dev/null and b/editor/resources/editor/icons/light/inspector/tidy-vertical-lightgray-16x16@2x.png differ diff --git a/editor/resources/editor/icons/light/inspector/tidy-vertical-white-16x16@2x.png b/editor/resources/editor/icons/light/inspector/tidy-vertical-white-16x16@2x.png new file mode 100644 index 000000000000..664a3170f82b Binary files /dev/null and b/editor/resources/editor/icons/light/inspector/tidy-vertical-white-16x16@2x.png differ diff --git a/editor/resources/editor/icons/light/navigator-element/warningtriangle-lightorange-16x16@2x.png b/editor/resources/editor/icons/light/navigator-element/warningtriangle-lightorange-16x16@2x.png new file mode 100644 index 000000000000..1e1d7841b947 Binary files /dev/null and b/editor/resources/editor/icons/light/navigator-element/warningtriangle-lightorange-16x16@2x.png differ diff --git a/editor/resources/editor/icons/light/navigator-element/warningtriangle-orange-16x16@2x.png b/editor/resources/editor/icons/light/navigator-element/warningtriangle-orange-16x16@2x.png new file mode 100644 index 000000000000..48809192df54 Binary files /dev/null and b/editor/resources/editor/icons/light/navigator-element/warningtriangle-orange-16x16@2x.png differ diff --git a/editor/resources/editor/icons/light/semantic/checkmark-green-16x16@2x.png b/editor/resources/editor/icons/light/semantic/checkmark-green-16x16@2x.png new file mode 100644 index 000000000000..b2714b4914c0 Binary files /dev/null and b/editor/resources/editor/icons/light/semantic/checkmark-green-16x16@2x.png differ diff --git a/editor/resources/editor/icons/light/semantic/checkmark-lightgreen-16x16@2x.png b/editor/resources/editor/icons/light/semantic/checkmark-lightgreen-16x16@2x.png new file mode 100644 index 000000000000..df239c446525 Binary files /dev/null and b/editor/resources/editor/icons/light/semantic/checkmark-lightgreen-16x16@2x.png differ diff --git a/editor/resources/editor/icons/light/semantic/cross-lightred-12x12@2x.png b/editor/resources/editor/icons/light/semantic/cross-lightred-12x12@2x.png new file mode 100644 index 000000000000..dacc9d4278b0 Binary files /dev/null and b/editor/resources/editor/icons/light/semantic/cross-lightred-12x12@2x.png differ diff --git a/editor/resources/editor/icons/light/semantic/cross-red-16x16@2x.png b/editor/resources/editor/icons/light/semantic/cross-red-16x16@2x.png new file mode 100644 index 000000000000..f5cb79e4c30e Binary files /dev/null and b/editor/resources/editor/icons/light/semantic/cross-red-16x16@2x.png differ diff --git a/editor/src/benchmarks.benchmark.ts b/editor/src/benchmarks.benchmark.ts index 4cb01c8292cf..57192a78c629 100644 --- a/editor/src/benchmarks.benchmark.ts +++ b/editor/src/benchmarks.benchmark.ts @@ -5,9 +5,9 @@ import { benchmarkOptics } from './core/shared/optics.benchmark' import { benchmarkPropertyPathFunction } from './core/shared/property-path.benchmark' import { benchmarkGetUniqueUids } from './core/model/get-unique-ids.benchmark' -// await benchmarkBuildTree() +await benchmarkBuildTree() // await benchmarkElementPathFunction() // await benchmarkPropertyPathFunction() // await benchmarkAttributes() // await benchmarkOptics() -await benchmarkGetUniqueUids() +// await benchmarkGetUniqueUids() diff --git a/editor/src/components/assets.spec.ts b/editor/src/components/assets.spec.ts index a1927e9f873f..1a71f481a02c 100644 --- a/editor/src/components/assets.spec.ts +++ b/editor/src/components/assets.spec.ts @@ -1,16 +1,25 @@ import * as FastCheck from 'fast-check' import fastDeepEquals from 'fast-deep-equal' import type { ProjectContents } from '../core/shared/project-file-types' -import { directory, isDirectory } from '../core/shared/project-file-types' +import { + directory, + isDirectory, + RevisionsState, + textFile, + textFileContents, + unparsed, +} from '../core/shared/project-file-types' import { codeFile } from '../core/shared/project-file-types' import type { ProjectContentTreeRoot } from './assets' import { contentsToTree, + getProjectDependencies, projectContentDirectory, projectContentFile, treeToContents, } from './assets' import { projectContentsArbitrary } from './assets.test-utils' +import { simpleDefaultProject } from '../sample-projects/sample-project-utils' function checkContentsToTree(contents: ProjectContents): boolean { const result = treeToContents(contentsToTree(contents)) @@ -51,3 +60,37 @@ describe('contentsToTree', () => { expect(contentsToTree(contents)).toEqual(expectedResult) }) }) + +describe('getProjectDependencies', () => { + it('should merge the dependencies from package.json and package-lock.json', () => { + const project = simpleDefaultProject({ + additionalFiles: { + '/package-lock.json': textFile( + textFileContents( + JSON.stringify( + { + dependencies: { + react: { + version: '1.0.0', + }, + }, + }, + null, + 2, + ), + unparsed, + RevisionsState.CodeAhead, + ), + null, + null, + 0, + ), + }, + }) + const dependencies = getProjectDependencies(project.projectContents) + // from package.json + expect(dependencies?.['react-dom']).toEqual('16.13.1') + // from package-lock.json + expect(dependencies?.react).toEqual('1.0.0') + }) +}) diff --git a/editor/src/components/assets.ts b/editor/src/components/assets.ts index 39e02fe1bf8f..43328b2e050c 100644 --- a/editor/src/components/assets.ts +++ b/editor/src/components/assets.ts @@ -19,7 +19,6 @@ import { dropLeadingSlash } from './filebrowser/filepath-utils' import { assertNever, fastForEach } from '../core/shared/utils' import { mapValues, propOrNull } from '../core/shared/object-utils' import { emptySet } from '../core/shared/set-utils' -import { sha1 } from 'sha.js' import type { GithubFileChanges, TreeConflicts } from '../core/shared/github/helpers' import type { FileChecksumsWithFile } from './editor/store/editor-state' import { memoize, valueDependentCache } from '../core/shared/memoize' @@ -34,6 +33,8 @@ import type { } from 'utopia-shared/src/types/assets' import { filtered, fromField, fromTypeGuard } from '../core/shared/optics/optic-creators' import { anyBy, toArrayOf } from '../core/shared/optics/optic-utilities' +import { gitBlobChecksumFromBuffer } from '../core/shared/file-utils' + export type { AssetFileWithFileName, ProjectContentTreeRoot, @@ -60,18 +61,6 @@ export function getAllProjectAssetFiles( return allProjectAssets } -function getSHA1ChecksumInner(contents: string | Buffer): string { - return new sha1().update(contents).digest('hex') -} - -// Memoized because it can be called for the same piece of code more than once before the -// checksum gets cached. For example in the canvas strategies and the regular dispatch flow, which don't share -// those cached checksum objects. -export const getSHA1Checksum = memoize(getSHA1ChecksumInner, { - maxSize: 10, - matchesArg: (first, second) => first === second, -}) - export function gitBlobChecksumFromBase64(base64: string): string { return gitBlobChecksumFromBuffer(Buffer.from(base64, 'base64')) } @@ -80,16 +69,6 @@ export function gitBlobChecksumFromText(text: string): string { return gitBlobChecksumFromBuffer(Buffer.from(text, 'utf8')) } -export function gitBlobChecksumFromBuffer(buffer: Buffer): string { - // This function returns the same SHA1 checksum string that git would return for the same contents. - // Given the contents in the buffer variable, the final checksum is calculated by hashing - // a string built as "". The prefix looks like "blob ". - // Ref: https://git-scm.com/book/en/v2/Git-Internals-Git-Objects - const prefix = Buffer.from(`blob ${buffer.byteLength}\0`) - const wrapped = Buffer.concat([prefix, buffer]) - return getSHA1Checksum(wrapped) -} - export function checkFilesHaveSameContent(first: ProjectFile, second: ProjectFile): boolean { switch (first.type) { case 'DIRECTORY': @@ -782,3 +761,36 @@ export function ensureDirectoriesExist(projectContents: ProjectContents): Projec return result } } + +function getFileAsJson(projectContents: ProjectContentTreeRoot, fileName: string): T | null { + const file = getProjectFileByFilePath(projectContents, fileName) + if (file != null && isTextFile(file)) { + return JSON.parse(file.fileContents.code) as T + } + return null +} + +type PackageJson = { utopia?: Record; dependencies?: Record } +export function getPackageJson(projectContents: ProjectContentTreeRoot): PackageJson | null { + return getFileAsJson(projectContents, '/package.json') +} + +type PackageLockJson = { dependencies?: Record } +export function getPackageLockJson( + projectContents: ProjectContentTreeRoot, +): PackageLockJson | null { + return getFileAsJson(projectContents, '/package-lock.json') +} + +export function getProjectDependencies( + projectContents: ProjectContentTreeRoot, +): Record | null { + const packageJsonDependencies = getPackageJson(projectContents)?.dependencies ?? {} + const packageLockJsonDependencies = getPackageLockJson(projectContents)?.dependencies ?? {} + for (const packageName of Object.keys(packageJsonDependencies)) { + if (packageLockJsonDependencies[packageName]?.version != null) { + packageJsonDependencies[packageName] = packageLockJsonDependencies[packageName].version + } + } + return packageJsonDependencies +} diff --git a/editor/src/components/canvas/__snapshots__/ui-jsx-canvas-bugs.spec.tsx.snap b/editor/src/components/canvas/__snapshots__/ui-jsx-canvas-bugs.spec.tsx.snap index ea9b9c982796..20a071cae079 100644 --- a/editor/src/components/canvas/__snapshots__/ui-jsx-canvas-bugs.spec.tsx.snap +++ b/editor/src/components/canvas/__snapshots__/ui-jsx-canvas-bugs.spec.tsx.snap @@ -14,6 +14,7 @@ exports[`UiJsxCanvas #747 - DOM object constructor cannot be called as a functio data-path=\\"sb/scene\\" style=\\" overflow: hidden; + container: scene / inline-size; position: absolute; background-color: var(--utopitheme-emphasizedBackground); box-shadow: 0px 1px 2px 0px var(--utopitheme-shadow90), @@ -158,8 +159,16 @@ Object { "label": null, "nonRoundedGlobalFrame": null, "specialSizeMeasurements": Object { + "alignContent": null, "alignItems": null, + "alignSelf": null, "borderRadius": null, + "borderWidths": Object { + "bottom": 0, + "left": 0, + "right": 0, + "top": 0, + }, "clientHeight": 0, "clientWidth": 0, "closestOffsetParentPath": Object { @@ -207,6 +216,7 @@ Object { "gap": null, "globalContentBoxForChildren": null, "globalFrameWithTextContent": null, + "gridCellGlobalFrames": null, "hasPositionOffset": false, "hasTransform": false, "htmlElementName": "div", @@ -218,7 +228,9 @@ Object { }, "immediateParentProvidesLayout": true, "justifyContent": null, + "justifySelf": null, "layoutSystemForChildren": null, + "layoutSystemForChildrenInherited": false, "margin": Object {}, "naturalHeight": null, "naturalWidth": null, @@ -227,8 +239,24 @@ Object { "y": 0, }, "padding": Object {}, + "parentContainerGridProperties": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, + "parentContainerGridPropertiesFromProps": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, "parentFlexDirection": null, "parentFlexGap": 0, + "parentGridCellGlobalFrames": null, + "parentGridFrame": null, "parentHugsOnMainAxis": false, "parentJustifyContent": null, "parentLayoutSystem": "flow", @@ -304,8 +332,16 @@ Object { "label": null, "nonRoundedGlobalFrame": null, "specialSizeMeasurements": Object { + "alignContent": null, "alignItems": null, + "alignSelf": null, "borderRadius": null, + "borderWidths": Object { + "bottom": 0, + "left": 0, + "right": 0, + "top": 0, + }, "clientHeight": 0, "clientWidth": 0, "closestOffsetParentPath": Object { @@ -353,6 +389,7 @@ Object { "gap": null, "globalContentBoxForChildren": null, "globalFrameWithTextContent": null, + "gridCellGlobalFrames": null, "hasPositionOffset": false, "hasTransform": false, "htmlElementName": "div", @@ -364,7 +401,9 @@ Object { }, "immediateParentProvidesLayout": true, "justifyContent": null, + "justifySelf": null, "layoutSystemForChildren": null, + "layoutSystemForChildrenInherited": false, "margin": Object {}, "naturalHeight": null, "naturalWidth": null, @@ -373,8 +412,24 @@ Object { "y": 0, }, "padding": Object {}, + "parentContainerGridProperties": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, + "parentContainerGridPropertiesFromProps": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, "parentFlexDirection": null, "parentFlexGap": 0, + "parentGridCellGlobalFrames": null, + "parentGridFrame": null, "parentHugsOnMainAxis": false, "parentJustifyContent": null, "parentLayoutSystem": "flow", diff --git a/editor/src/components/canvas/__snapshots__/ui-jsx-canvas.spec.tsx.snap b/editor/src/components/canvas/__snapshots__/ui-jsx-canvas.spec.tsx.snap index 301e9913f6e9..21d4cc888da1 100644 --- a/editor/src/components/canvas/__snapshots__/ui-jsx-canvas.spec.tsx.snap +++ b/editor/src/components/canvas/__snapshots__/ui-jsx-canvas.spec.tsx.snap @@ -14,6 +14,7 @@ exports[`UiJsxCanvas render Label carried through for generated elements 1`] = ` data-path=\\"utopia-storyboard-uid/scene-aaa\\" style=\\" overflow: hidden; + container: scene / inline-size; position: absolute; background-color: var(--utopitheme-emphasizedBackground); box-shadow: 0px 1px 2px 0px var(--utopitheme-shadow90), @@ -199,8 +200,16 @@ Object { "label": null, "nonRoundedGlobalFrame": null, "specialSizeMeasurements": Object { + "alignContent": null, "alignItems": null, + "alignSelf": null, "borderRadius": null, + "borderWidths": Object { + "bottom": 0, + "left": 0, + "right": 0, + "top": 0, + }, "clientHeight": 0, "clientWidth": 0, "closestOffsetParentPath": Object { @@ -248,6 +257,7 @@ Object { "gap": null, "globalContentBoxForChildren": null, "globalFrameWithTextContent": null, + "gridCellGlobalFrames": null, "hasPositionOffset": false, "hasTransform": false, "htmlElementName": "div", @@ -259,7 +269,9 @@ Object { }, "immediateParentProvidesLayout": true, "justifyContent": null, + "justifySelf": null, "layoutSystemForChildren": null, + "layoutSystemForChildrenInherited": false, "margin": Object {}, "naturalHeight": null, "naturalWidth": null, @@ -268,8 +280,24 @@ Object { "y": 0, }, "padding": Object {}, + "parentContainerGridProperties": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, + "parentContainerGridPropertiesFromProps": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, "parentFlexDirection": null, "parentFlexGap": 0, + "parentGridCellGlobalFrames": null, + "parentGridFrame": null, "parentHugsOnMainAxis": false, "parentJustifyContent": null, "parentLayoutSystem": "flow", @@ -368,8 +396,16 @@ Object { "label": null, "nonRoundedGlobalFrame": null, "specialSizeMeasurements": Object { + "alignContent": null, "alignItems": null, + "alignSelf": null, "borderRadius": null, + "borderWidths": Object { + "bottom": 0, + "left": 0, + "right": 0, + "top": 0, + }, "clientHeight": 0, "clientWidth": 0, "closestOffsetParentPath": Object { @@ -417,6 +453,7 @@ Object { "gap": null, "globalContentBoxForChildren": null, "globalFrameWithTextContent": null, + "gridCellGlobalFrames": null, "hasPositionOffset": false, "hasTransform": false, "htmlElementName": "div", @@ -428,7 +465,9 @@ Object { }, "immediateParentProvidesLayout": true, "justifyContent": null, + "justifySelf": null, "layoutSystemForChildren": null, + "layoutSystemForChildrenInherited": false, "margin": Object {}, "naturalHeight": null, "naturalWidth": null, @@ -437,8 +476,24 @@ Object { "y": 0, }, "padding": Object {}, + "parentContainerGridProperties": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, + "parentContainerGridPropertiesFromProps": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, "parentFlexDirection": null, "parentFlexGap": 0, + "parentGridCellGlobalFrames": null, + "parentGridFrame": null, "parentHugsOnMainAxis": false, "parentJustifyContent": null, "parentLayoutSystem": "flow", @@ -832,8 +887,16 @@ Object { "label": null, "nonRoundedGlobalFrame": null, "specialSizeMeasurements": Object { + "alignContent": null, "alignItems": null, + "alignSelf": null, "borderRadius": null, + "borderWidths": Object { + "bottom": 0, + "left": 0, + "right": 0, + "top": 0, + }, "clientHeight": 0, "clientWidth": 0, "closestOffsetParentPath": Object { @@ -881,6 +944,7 @@ Object { "gap": null, "globalContentBoxForChildren": null, "globalFrameWithTextContent": null, + "gridCellGlobalFrames": null, "hasPositionOffset": false, "hasTransform": false, "htmlElementName": "div", @@ -892,7 +956,9 @@ Object { }, "immediateParentProvidesLayout": true, "justifyContent": null, + "justifySelf": null, "layoutSystemForChildren": null, + "layoutSystemForChildrenInherited": false, "margin": Object {}, "naturalHeight": null, "naturalWidth": null, @@ -901,8 +967,24 @@ Object { "y": 0, }, "padding": Object {}, + "parentContainerGridProperties": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, + "parentContainerGridPropertiesFromProps": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, "parentFlexDirection": null, "parentFlexGap": 0, + "parentGridCellGlobalFrames": null, + "parentGridFrame": null, "parentHugsOnMainAxis": false, "parentJustifyContent": null, "parentLayoutSystem": "flow", @@ -1145,8 +1227,16 @@ Object { "label": null, "nonRoundedGlobalFrame": null, "specialSizeMeasurements": Object { + "alignContent": null, "alignItems": null, + "alignSelf": null, "borderRadius": null, + "borderWidths": Object { + "bottom": 0, + "left": 0, + "right": 0, + "top": 0, + }, "clientHeight": 0, "clientWidth": 0, "closestOffsetParentPath": Object { @@ -1194,6 +1284,7 @@ Object { "gap": null, "globalContentBoxForChildren": null, "globalFrameWithTextContent": null, + "gridCellGlobalFrames": null, "hasPositionOffset": false, "hasTransform": false, "htmlElementName": "div", @@ -1205,7 +1296,9 @@ Object { }, "immediateParentProvidesLayout": true, "justifyContent": null, + "justifySelf": null, "layoutSystemForChildren": null, + "layoutSystemForChildrenInherited": false, "margin": Object {}, "naturalHeight": null, "naturalWidth": null, @@ -1214,8 +1307,24 @@ Object { "y": 0, }, "padding": Object {}, + "parentContainerGridProperties": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, + "parentContainerGridPropertiesFromProps": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, "parentFlexDirection": null, "parentFlexGap": 0, + "parentGridCellGlobalFrames": null, + "parentGridFrame": null, "parentHugsOnMainAxis": false, "parentJustifyContent": null, "parentLayoutSystem": "flow", @@ -1313,8 +1422,16 @@ Object { "label": null, "nonRoundedGlobalFrame": null, "specialSizeMeasurements": Object { + "alignContent": null, "alignItems": null, + "alignSelf": null, "borderRadius": null, + "borderWidths": Object { + "bottom": 0, + "left": 0, + "right": 0, + "top": 0, + }, "clientHeight": 0, "clientWidth": 0, "closestOffsetParentPath": Object { @@ -1362,6 +1479,7 @@ Object { "gap": null, "globalContentBoxForChildren": null, "globalFrameWithTextContent": null, + "gridCellGlobalFrames": null, "hasPositionOffset": false, "hasTransform": false, "htmlElementName": "div", @@ -1373,7 +1491,9 @@ Object { }, "immediateParentProvidesLayout": true, "justifyContent": null, + "justifySelf": null, "layoutSystemForChildren": null, + "layoutSystemForChildrenInherited": false, "margin": Object {}, "naturalHeight": null, "naturalWidth": null, @@ -1382,8 +1502,24 @@ Object { "y": 0, }, "padding": Object {}, + "parentContainerGridProperties": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, + "parentContainerGridPropertiesFromProps": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, "parentFlexDirection": null, "parentFlexGap": 0, + "parentGridCellGlobalFrames": null, + "parentGridFrame": null, "parentHugsOnMainAxis": false, "parentJustifyContent": null, "parentLayoutSystem": "flow", @@ -1481,8 +1617,16 @@ Object { "label": null, "nonRoundedGlobalFrame": null, "specialSizeMeasurements": Object { + "alignContent": null, "alignItems": null, + "alignSelf": null, "borderRadius": null, + "borderWidths": Object { + "bottom": 0, + "left": 0, + "right": 0, + "top": 0, + }, "clientHeight": 0, "clientWidth": 0, "closestOffsetParentPath": Object { @@ -1530,6 +1674,7 @@ Object { "gap": null, "globalContentBoxForChildren": null, "globalFrameWithTextContent": null, + "gridCellGlobalFrames": null, "hasPositionOffset": false, "hasTransform": false, "htmlElementName": "div", @@ -1541,7 +1686,9 @@ Object { }, "immediateParentProvidesLayout": true, "justifyContent": null, + "justifySelf": null, "layoutSystemForChildren": null, + "layoutSystemForChildrenInherited": false, "margin": Object {}, "naturalHeight": null, "naturalWidth": null, @@ -1550,8 +1697,24 @@ Object { "y": 0, }, "padding": Object {}, + "parentContainerGridProperties": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, + "parentContainerGridPropertiesFromProps": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, "parentFlexDirection": null, "parentFlexGap": 0, + "parentGridCellGlobalFrames": null, + "parentGridFrame": null, "parentHugsOnMainAxis": false, "parentJustifyContent": null, "parentLayoutSystem": "flow", @@ -1649,8 +1812,16 @@ Object { "label": null, "nonRoundedGlobalFrame": null, "specialSizeMeasurements": Object { + "alignContent": null, "alignItems": null, + "alignSelf": null, "borderRadius": null, + "borderWidths": Object { + "bottom": 0, + "left": 0, + "right": 0, + "top": 0, + }, "clientHeight": 0, "clientWidth": 0, "closestOffsetParentPath": Object { @@ -1698,6 +1869,7 @@ Object { "gap": null, "globalContentBoxForChildren": null, "globalFrameWithTextContent": null, + "gridCellGlobalFrames": null, "hasPositionOffset": false, "hasTransform": false, "htmlElementName": "div", @@ -1709,7 +1881,9 @@ Object { }, "immediateParentProvidesLayout": true, "justifyContent": null, + "justifySelf": null, "layoutSystemForChildren": null, + "layoutSystemForChildrenInherited": false, "margin": Object {}, "naturalHeight": null, "naturalWidth": null, @@ -1718,8 +1892,24 @@ Object { "y": 0, }, "padding": Object {}, + "parentContainerGridProperties": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, + "parentContainerGridPropertiesFromProps": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, "parentFlexDirection": null, "parentFlexGap": 0, + "parentGridCellGlobalFrames": null, + "parentGridFrame": null, "parentHugsOnMainAxis": false, "parentJustifyContent": null, "parentLayoutSystem": "flow", @@ -1752,6 +1942,7 @@ exports[`UiJsxCanvas render Label carried through for normal elements 1`] = ` data-path=\\"utopia-storyboard-uid/scene-aaa\\" style=\\" overflow: hidden; + container: scene / inline-size; position: absolute; background-color: var(--utopitheme-emphasizedBackground); box-shadow: 0px 1px 2px 0px var(--utopitheme-shadow90), @@ -1922,177 +2113,16 @@ Object { "label": null, "nonRoundedGlobalFrame": null, "specialSizeMeasurements": Object { + "alignContent": null, "alignItems": null, + "alignSelf": null, "borderRadius": null, - "clientHeight": 0, - "clientWidth": 0, - "closestOffsetParentPath": Object { - "parts": Array [], - "type": "elementpath", + "borderWidths": Object { + "bottom": 0, + "left": 0, + "right": 0, + "top": 0, }, - "columnGap": null, - "computedHugProperty": Object { - "height": null, - "width": null, - }, - "containerGridProperties": Object { - "gridAutoColumns": null, - "gridAutoFlow": null, - "gridAutoRows": null, - "gridTemplateColumns": null, - "gridTemplateRows": null, - }, - "containerGridPropertiesFromProps": Object { - "gridAutoColumns": null, - "gridAutoFlow": null, - "gridAutoRows": null, - "gridTemplateColumns": null, - "gridTemplateRows": null, - }, - "coordinateSystemBounds": null, - "display": "initial", - "elementGridProperties": Object { - "gridColumnEnd": null, - "gridColumnStart": null, - "gridRowEnd": null, - "gridRowStart": null, - }, - "elementGridPropertiesFromProps": Object { - "gridColumnEnd": null, - "gridColumnStart": null, - "gridRowEnd": null, - "gridRowStart": null, - }, - "flexDirection": null, - "float": "none", - "fontSize": null, - "fontStyle": null, - "fontWeight": null, - "gap": null, - "globalContentBoxForChildren": null, - "globalFrameWithTextContent": null, - "hasPositionOffset": false, - "hasTransform": false, - "htmlElementName": "div", - "immediateParentBounds": Object { - "height": 0, - "width": 0, - "x": 0, - "y": 0, - }, - "immediateParentProvidesLayout": true, - "justifyContent": null, - "layoutSystemForChildren": null, - "margin": Object {}, - "naturalHeight": null, - "naturalWidth": null, - "offset": Object { - "x": 0, - "y": 0, - }, - "padding": Object {}, - "parentFlexDirection": null, - "parentFlexGap": 0, - "parentHugsOnMainAxis": false, - "parentJustifyContent": null, - "parentLayoutSystem": "flow", - "parentPadding": Object {}, - "parentTextDirection": "ltr", - "position": null, - "providesBoundsForAbsoluteChildren": false, - "renderedChildrenCount": 0, - "rowGap": null, - "textBounds": null, - "textDecorationLine": null, - "usesParentBounds": false, - }, - "textContent": null, - }, - "utopia-storyboard-uid/scene-aaa/app-entity": Object { - "assignedToProp": null, - "attributeMetadata": Object {}, - "componentInstance": true, - "computedStyle": Object {}, - "conditionValue": "not-a-conditional", - "earlyReturn": null, - "element": Object { - "type": "RIGHT", - "value": Object { - "children": Array [], - "name": Object { - "baseVariable": "App", - "propertyPath": Object { - "propertyElements": Array [], - }, - }, - "props": Array [ - Object { - "comments": Object { - "leadingComments": Array [], - "trailingComments": Array [], - }, - "key": "data-uid", - "type": "JSX_ATTRIBUTES_ENTRY", - "value": Object { - "comments": Object { - "leadingComments": Array [], - "trailingComments": Array [], - }, - "type": "ATTRIBUTE_VALUE", - "uid": "", - "value": "app-entity", - }, - }, - Object { - "comments": Object { - "leadingComments": Array [], - "trailingComments": Array [], - }, - "key": "style", - "type": "JSX_ATTRIBUTES_ENTRY", - "value": Object { - "comments": Object { - "leadingComments": Array [], - "trailingComments": Array [], - }, - "type": "ATTRIBUTE_VALUE", - "uid": "", - "value": Object { - "bottom": 0, - "left": 0, - "position": "absolute", - "right": 0, - "top": 0, - }, - }, - }, - ], - "type": "JSX_ELEMENT", - "uid": "", - }, - }, - "elementPath": Object { - "parts": Array [ - Array [ - "utopia-storyboard-uid", - "scene-aaa", - "app-entity", - ], - ], - "type": "elementpath", - }, - "globalFrame": null, - "importInfo": Object { - "filePath": "test.js", - "type": "SAME_FILE_ORIGIN", - "variableName": "App", - }, - "isEmotionOrStyledComponent": false, - "label": null, - "nonRoundedGlobalFrame": null, - "specialSizeMeasurements": Object { - "alignItems": null, - "borderRadius": null, "clientHeight": 0, "clientWidth": 0, "closestOffsetParentPath": Object { @@ -2140,6 +2170,7 @@ Object { "gap": null, "globalContentBoxForChildren": null, "globalFrameWithTextContent": null, + "gridCellGlobalFrames": null, "hasPositionOffset": false, "hasTransform": false, "htmlElementName": "div", @@ -2151,7 +2182,9 @@ Object { }, "immediateParentProvidesLayout": true, "justifyContent": null, + "justifySelf": null, "layoutSystemForChildren": null, + "layoutSystemForChildrenInherited": false, "margin": Object {}, "naturalHeight": null, "naturalWidth": null, @@ -2160,8 +2193,220 @@ Object { "y": 0, }, "padding": Object {}, + "parentContainerGridProperties": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, + "parentContainerGridPropertiesFromProps": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, "parentFlexDirection": null, "parentFlexGap": 0, + "parentGridCellGlobalFrames": null, + "parentGridFrame": null, + "parentHugsOnMainAxis": false, + "parentJustifyContent": null, + "parentLayoutSystem": "flow", + "parentPadding": Object {}, + "parentTextDirection": "ltr", + "position": null, + "providesBoundsForAbsoluteChildren": false, + "renderedChildrenCount": 0, + "rowGap": null, + "textBounds": null, + "textDecorationLine": null, + "usesParentBounds": false, + }, + "textContent": null, + }, + "utopia-storyboard-uid/scene-aaa/app-entity": Object { + "assignedToProp": null, + "attributeMetadata": Object {}, + "componentInstance": true, + "computedStyle": Object {}, + "conditionValue": "not-a-conditional", + "earlyReturn": null, + "element": Object { + "type": "RIGHT", + "value": Object { + "children": Array [], + "name": Object { + "baseVariable": "App", + "propertyPath": Object { + "propertyElements": Array [], + }, + }, + "props": Array [ + Object { + "comments": Object { + "leadingComments": Array [], + "trailingComments": Array [], + }, + "key": "data-uid", + "type": "JSX_ATTRIBUTES_ENTRY", + "value": Object { + "comments": Object { + "leadingComments": Array [], + "trailingComments": Array [], + }, + "type": "ATTRIBUTE_VALUE", + "uid": "", + "value": "app-entity", + }, + }, + Object { + "comments": Object { + "leadingComments": Array [], + "trailingComments": Array [], + }, + "key": "style", + "type": "JSX_ATTRIBUTES_ENTRY", + "value": Object { + "comments": Object { + "leadingComments": Array [], + "trailingComments": Array [], + }, + "type": "ATTRIBUTE_VALUE", + "uid": "", + "value": Object { + "bottom": 0, + "left": 0, + "position": "absolute", + "right": 0, + "top": 0, + }, + }, + }, + ], + "type": "JSX_ELEMENT", + "uid": "", + }, + }, + "elementPath": Object { + "parts": Array [ + Array [ + "utopia-storyboard-uid", + "scene-aaa", + "app-entity", + ], + ], + "type": "elementpath", + }, + "globalFrame": null, + "importInfo": Object { + "filePath": "test.js", + "type": "SAME_FILE_ORIGIN", + "variableName": "App", + }, + "isEmotionOrStyledComponent": false, + "label": null, + "nonRoundedGlobalFrame": null, + "specialSizeMeasurements": Object { + "alignContent": null, + "alignItems": null, + "alignSelf": null, + "borderRadius": null, + "borderWidths": Object { + "bottom": 0, + "left": 0, + "right": 0, + "top": 0, + }, + "clientHeight": 0, + "clientWidth": 0, + "closestOffsetParentPath": Object { + "parts": Array [], + "type": "elementpath", + }, + "columnGap": null, + "computedHugProperty": Object { + "height": null, + "width": null, + }, + "containerGridProperties": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, + "containerGridPropertiesFromProps": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, + "coordinateSystemBounds": null, + "display": "initial", + "elementGridProperties": Object { + "gridColumnEnd": null, + "gridColumnStart": null, + "gridRowEnd": null, + "gridRowStart": null, + }, + "elementGridPropertiesFromProps": Object { + "gridColumnEnd": null, + "gridColumnStart": null, + "gridRowEnd": null, + "gridRowStart": null, + }, + "flexDirection": null, + "float": "none", + "fontSize": null, + "fontStyle": null, + "fontWeight": null, + "gap": null, + "globalContentBoxForChildren": null, + "globalFrameWithTextContent": null, + "gridCellGlobalFrames": null, + "hasPositionOffset": false, + "hasTransform": false, + "htmlElementName": "div", + "immediateParentBounds": Object { + "height": 0, + "width": 0, + "x": 0, + "y": 0, + }, + "immediateParentProvidesLayout": true, + "justifyContent": null, + "justifySelf": null, + "layoutSystemForChildren": null, + "layoutSystemForChildrenInherited": false, + "margin": Object {}, + "naturalHeight": null, + "naturalWidth": null, + "offset": Object { + "x": 0, + "y": 0, + }, + "padding": Object {}, + "parentContainerGridProperties": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, + "parentContainerGridPropertiesFromProps": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, + "parentFlexDirection": null, + "parentFlexGap": 0, + "parentGridCellGlobalFrames": null, + "parentGridFrame": null, "parentHugsOnMainAxis": false, "parentJustifyContent": null, "parentLayoutSystem": "flow", @@ -2366,8 +2611,16 @@ Object { "label": null, "nonRoundedGlobalFrame": null, "specialSizeMeasurements": Object { + "alignContent": null, "alignItems": null, + "alignSelf": null, "borderRadius": null, + "borderWidths": Object { + "bottom": 0, + "left": 0, + "right": 0, + "top": 0, + }, "clientHeight": 0, "clientWidth": 0, "closestOffsetParentPath": Object { @@ -2415,6 +2668,7 @@ Object { "gap": null, "globalContentBoxForChildren": null, "globalFrameWithTextContent": null, + "gridCellGlobalFrames": null, "hasPositionOffset": false, "hasTransform": false, "htmlElementName": "div", @@ -2426,7 +2680,9 @@ Object { }, "immediateParentProvidesLayout": true, "justifyContent": null, + "justifySelf": null, "layoutSystemForChildren": null, + "layoutSystemForChildrenInherited": false, "margin": Object {}, "naturalHeight": null, "naturalWidth": null, @@ -2435,8 +2691,24 @@ Object { "y": 0, }, "padding": Object {}, + "parentContainerGridProperties": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, + "parentContainerGridPropertiesFromProps": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, "parentFlexDirection": null, "parentFlexGap": 0, + "parentGridCellGlobalFrames": null, + "parentGridFrame": null, "parentHugsOnMainAxis": false, "parentJustifyContent": null, "parentLayoutSystem": "flow", @@ -2469,6 +2741,7 @@ exports[`UiJsxCanvas render Renders input tag without errors 1`] = ` data-path=\\"utopia-storyboard-uid/scene-aaa\\" style=\\" overflow: hidden; + container: scene / inline-size; position: absolute; background-color: var(--utopitheme-emphasizedBackground); box-shadow: 0px 1px 2px 0px var(--utopitheme-shadow90), @@ -2638,8 +2911,16 @@ Object { "label": null, "nonRoundedGlobalFrame": null, "specialSizeMeasurements": Object { + "alignContent": null, "alignItems": null, + "alignSelf": null, "borderRadius": null, + "borderWidths": Object { + "bottom": 0, + "left": 0, + "right": 0, + "top": 0, + }, "clientHeight": 0, "clientWidth": 0, "closestOffsetParentPath": Object { @@ -2687,6 +2968,7 @@ Object { "gap": null, "globalContentBoxForChildren": null, "globalFrameWithTextContent": null, + "gridCellGlobalFrames": null, "hasPositionOffset": false, "hasTransform": false, "htmlElementName": "div", @@ -2698,7 +2980,9 @@ Object { }, "immediateParentProvidesLayout": true, "justifyContent": null, + "justifySelf": null, "layoutSystemForChildren": null, + "layoutSystemForChildrenInherited": false, "margin": Object {}, "naturalHeight": null, "naturalWidth": null, @@ -2707,8 +2991,24 @@ Object { "y": 0, }, "padding": Object {}, + "parentContainerGridProperties": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, + "parentContainerGridPropertiesFromProps": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, "parentFlexDirection": null, "parentFlexGap": 0, + "parentGridCellGlobalFrames": null, + "parentGridFrame": null, "parentHugsOnMainAxis": false, "parentJustifyContent": null, "parentLayoutSystem": "flow", @@ -2807,8 +3107,16 @@ Object { "label": null, "nonRoundedGlobalFrame": null, "specialSizeMeasurements": Object { + "alignContent": null, "alignItems": null, + "alignSelf": null, "borderRadius": null, + "borderWidths": Object { + "bottom": 0, + "left": 0, + "right": 0, + "top": 0, + }, "clientHeight": 0, "clientWidth": 0, "closestOffsetParentPath": Object { @@ -2856,6 +3164,7 @@ Object { "gap": null, "globalContentBoxForChildren": null, "globalFrameWithTextContent": null, + "gridCellGlobalFrames": null, "hasPositionOffset": false, "hasTransform": false, "htmlElementName": "div", @@ -2867,7 +3176,9 @@ Object { }, "immediateParentProvidesLayout": true, "justifyContent": null, + "justifySelf": null, "layoutSystemForChildren": null, + "layoutSystemForChildrenInherited": false, "margin": Object {}, "naturalHeight": null, "naturalWidth": null, @@ -2876,8 +3187,24 @@ Object { "y": 0, }, "padding": Object {}, + "parentContainerGridProperties": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, + "parentContainerGridPropertiesFromProps": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, "parentFlexDirection": null, "parentFlexGap": 0, + "parentGridCellGlobalFrames": null, + "parentGridFrame": null, "parentHugsOnMainAxis": false, "parentJustifyContent": null, "parentLayoutSystem": "flow", @@ -2956,8 +3283,16 @@ Object { "label": null, "nonRoundedGlobalFrame": null, "specialSizeMeasurements": Object { + "alignContent": null, "alignItems": null, + "alignSelf": null, "borderRadius": null, + "borderWidths": Object { + "bottom": 0, + "left": 0, + "right": 0, + "top": 0, + }, "clientHeight": 0, "clientWidth": 0, "closestOffsetParentPath": Object { @@ -3005,6 +3340,7 @@ Object { "gap": null, "globalContentBoxForChildren": null, "globalFrameWithTextContent": null, + "gridCellGlobalFrames": null, "hasPositionOffset": false, "hasTransform": false, "htmlElementName": "div", @@ -3016,7 +3352,9 @@ Object { }, "immediateParentProvidesLayout": true, "justifyContent": null, + "justifySelf": null, "layoutSystemForChildren": null, + "layoutSystemForChildrenInherited": false, "margin": Object {}, "naturalHeight": null, "naturalWidth": null, @@ -3025,8 +3363,24 @@ Object { "y": 0, }, "padding": Object {}, + "parentContainerGridProperties": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, + "parentContainerGridPropertiesFromProps": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, "parentFlexDirection": null, "parentFlexGap": 0, + "parentGridCellGlobalFrames": null, + "parentGridFrame": null, "parentHugsOnMainAxis": false, "parentJustifyContent": null, "parentLayoutSystem": "flow", @@ -3059,6 +3413,7 @@ exports[`UiJsxCanvas render arbitrary jsx block inside an element inside an arbi data-path=\\"utopia-storyboard-uid/scene-aaa\\" style=\\" overflow: hidden; + container: scene / inline-size; position: absolute; background-color: var(--utopitheme-emphasizedBackground); box-shadow: 0px 1px 2px 0px var(--utopitheme-shadow90), @@ -3246,8 +3601,16 @@ Object { "label": null, "nonRoundedGlobalFrame": null, "specialSizeMeasurements": Object { + "alignContent": null, "alignItems": null, + "alignSelf": null, "borderRadius": null, + "borderWidths": Object { + "bottom": 0, + "left": 0, + "right": 0, + "top": 0, + }, "clientHeight": 0, "clientWidth": 0, "closestOffsetParentPath": Object { @@ -3295,6 +3658,7 @@ Object { "gap": null, "globalContentBoxForChildren": null, "globalFrameWithTextContent": null, + "gridCellGlobalFrames": null, "hasPositionOffset": false, "hasTransform": false, "htmlElementName": "div", @@ -3306,7 +3670,9 @@ Object { }, "immediateParentProvidesLayout": true, "justifyContent": null, + "justifySelf": null, "layoutSystemForChildren": null, + "layoutSystemForChildrenInherited": false, "margin": Object {}, "naturalHeight": null, "naturalWidth": null, @@ -3315,8 +3681,24 @@ Object { "y": 0, }, "padding": Object {}, + "parentContainerGridProperties": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, + "parentContainerGridPropertiesFromProps": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, "parentFlexDirection": null, "parentFlexGap": 0, + "parentGridCellGlobalFrames": null, + "parentGridFrame": null, "parentHugsOnMainAxis": false, "parentJustifyContent": null, "parentLayoutSystem": "flow", @@ -3415,8 +3797,16 @@ Object { "label": null, "nonRoundedGlobalFrame": null, "specialSizeMeasurements": Object { + "alignContent": null, "alignItems": null, + "alignSelf": null, "borderRadius": null, + "borderWidths": Object { + "bottom": 0, + "left": 0, + "right": 0, + "top": 0, + }, "clientHeight": 0, "clientWidth": 0, "closestOffsetParentPath": Object { @@ -3464,6 +3854,7 @@ Object { "gap": null, "globalContentBoxForChildren": null, "globalFrameWithTextContent": null, + "gridCellGlobalFrames": null, "hasPositionOffset": false, "hasTransform": false, "htmlElementName": "div", @@ -3475,7 +3866,9 @@ Object { }, "immediateParentProvidesLayout": true, "justifyContent": null, + "justifySelf": null, "layoutSystemForChildren": null, + "layoutSystemForChildrenInherited": false, "margin": Object {}, "naturalHeight": null, "naturalWidth": null, @@ -3484,8 +3877,24 @@ Object { "y": 0, }, "padding": Object {}, + "parentContainerGridProperties": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, + "parentContainerGridPropertiesFromProps": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, "parentFlexDirection": null, "parentFlexGap": 0, + "parentGridCellGlobalFrames": null, + "parentGridFrame": null, "parentHugsOnMainAxis": false, "parentJustifyContent": null, "parentLayoutSystem": "flow", @@ -3791,8 +4200,16 @@ Object { "label": null, "nonRoundedGlobalFrame": null, "specialSizeMeasurements": Object { + "alignContent": null, "alignItems": null, + "alignSelf": null, "borderRadius": null, + "borderWidths": Object { + "bottom": 0, + "left": 0, + "right": 0, + "top": 0, + }, "clientHeight": 0, "clientWidth": 0, "closestOffsetParentPath": Object { @@ -3840,6 +4257,7 @@ Object { "gap": null, "globalContentBoxForChildren": null, "globalFrameWithTextContent": null, + "gridCellGlobalFrames": null, "hasPositionOffset": false, "hasTransform": false, "htmlElementName": "div", @@ -3851,7 +4269,9 @@ Object { }, "immediateParentProvidesLayout": true, "justifyContent": null, + "justifySelf": null, "layoutSystemForChildren": null, + "layoutSystemForChildrenInherited": false, "margin": Object {}, "naturalHeight": null, "naturalWidth": null, @@ -3860,8 +4280,24 @@ Object { "y": 0, }, "padding": Object {}, + "parentContainerGridProperties": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, + "parentContainerGridPropertiesFromProps": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, "parentFlexDirection": null, "parentFlexGap": 0, + "parentGridCellGlobalFrames": null, + "parentGridFrame": null, "parentHugsOnMainAxis": false, "parentJustifyContent": null, "parentLayoutSystem": "flow", @@ -4133,8 +4569,16 @@ Object { "label": null, "nonRoundedGlobalFrame": null, "specialSizeMeasurements": Object { + "alignContent": null, "alignItems": null, + "alignSelf": null, "borderRadius": null, + "borderWidths": Object { + "bottom": 0, + "left": 0, + "right": 0, + "top": 0, + }, "clientHeight": 0, "clientWidth": 0, "closestOffsetParentPath": Object { @@ -4182,6 +4626,7 @@ Object { "gap": null, "globalContentBoxForChildren": null, "globalFrameWithTextContent": null, + "gridCellGlobalFrames": null, "hasPositionOffset": false, "hasTransform": false, "htmlElementName": "div", @@ -4193,7 +4638,9 @@ Object { }, "immediateParentProvidesLayout": true, "justifyContent": null, + "justifySelf": null, "layoutSystemForChildren": null, + "layoutSystemForChildrenInherited": false, "margin": Object {}, "naturalHeight": null, "naturalWidth": null, @@ -4202,8 +4649,24 @@ Object { "y": 0, }, "padding": Object {}, + "parentContainerGridProperties": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, + "parentContainerGridPropertiesFromProps": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, "parentFlexDirection": null, "parentFlexGap": 0, + "parentGridCellGlobalFrames": null, + "parentGridFrame": null, "parentHugsOnMainAxis": false, "parentJustifyContent": null, "parentLayoutSystem": "flow", @@ -4330,8 +4793,16 @@ Object { "label": null, "nonRoundedGlobalFrame": null, "specialSizeMeasurements": Object { + "alignContent": null, "alignItems": null, + "alignSelf": null, "borderRadius": null, + "borderWidths": Object { + "bottom": 0, + "left": 0, + "right": 0, + "top": 0, + }, "clientHeight": 0, "clientWidth": 0, "closestOffsetParentPath": Object { @@ -4379,6 +4850,7 @@ Object { "gap": null, "globalContentBoxForChildren": null, "globalFrameWithTextContent": null, + "gridCellGlobalFrames": null, "hasPositionOffset": false, "hasTransform": false, "htmlElementName": "div", @@ -4390,7 +4862,9 @@ Object { }, "immediateParentProvidesLayout": true, "justifyContent": null, + "justifySelf": null, "layoutSystemForChildren": null, + "layoutSystemForChildrenInherited": false, "margin": Object {}, "naturalHeight": null, "naturalWidth": null, @@ -4399,8 +4873,24 @@ Object { "y": 0, }, "padding": Object {}, + "parentContainerGridProperties": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, + "parentContainerGridPropertiesFromProps": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, "parentFlexDirection": null, "parentFlexGap": 0, + "parentGridCellGlobalFrames": null, + "parentGridFrame": null, "parentHugsOnMainAxis": false, "parentJustifyContent": null, "parentLayoutSystem": "flow", @@ -4493,8 +4983,16 @@ Object { "label": null, "nonRoundedGlobalFrame": null, "specialSizeMeasurements": Object { + "alignContent": null, "alignItems": null, + "alignSelf": null, "borderRadius": null, + "borderWidths": Object { + "bottom": 0, + "left": 0, + "right": 0, + "top": 0, + }, "clientHeight": 0, "clientWidth": 0, "closestOffsetParentPath": Object { @@ -4542,6 +5040,7 @@ Object { "gap": null, "globalContentBoxForChildren": null, "globalFrameWithTextContent": null, + "gridCellGlobalFrames": null, "hasPositionOffset": false, "hasTransform": false, "htmlElementName": "div", @@ -4553,7 +5052,9 @@ Object { }, "immediateParentProvidesLayout": true, "justifyContent": null, + "justifySelf": null, "layoutSystemForChildren": null, + "layoutSystemForChildrenInherited": false, "margin": Object {}, "naturalHeight": null, "naturalWidth": null, @@ -4562,8 +5063,24 @@ Object { "y": 0, }, "padding": Object {}, + "parentContainerGridProperties": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, + "parentContainerGridPropertiesFromProps": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, "parentFlexDirection": null, "parentFlexGap": 0, + "parentGridCellGlobalFrames": null, + "parentGridFrame": null, "parentHugsOnMainAxis": false, "parentJustifyContent": null, "parentLayoutSystem": "flow", @@ -4690,8 +5207,16 @@ Object { "label": null, "nonRoundedGlobalFrame": null, "specialSizeMeasurements": Object { + "alignContent": null, "alignItems": null, + "alignSelf": null, "borderRadius": null, + "borderWidths": Object { + "bottom": 0, + "left": 0, + "right": 0, + "top": 0, + }, "clientHeight": 0, "clientWidth": 0, "closestOffsetParentPath": Object { @@ -4739,6 +5264,7 @@ Object { "gap": null, "globalContentBoxForChildren": null, "globalFrameWithTextContent": null, + "gridCellGlobalFrames": null, "hasPositionOffset": false, "hasTransform": false, "htmlElementName": "div", @@ -4750,7 +5276,9 @@ Object { }, "immediateParentProvidesLayout": true, "justifyContent": null, + "justifySelf": null, "layoutSystemForChildren": null, + "layoutSystemForChildrenInherited": false, "margin": Object {}, "naturalHeight": null, "naturalWidth": null, @@ -4759,8 +5287,24 @@ Object { "y": 0, }, "padding": Object {}, + "parentContainerGridProperties": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, + "parentContainerGridPropertiesFromProps": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, "parentFlexDirection": null, "parentFlexGap": 0, + "parentGridCellGlobalFrames": null, + "parentGridFrame": null, "parentHugsOnMainAxis": false, "parentJustifyContent": null, "parentLayoutSystem": "flow", @@ -4853,8 +5397,16 @@ Object { "label": null, "nonRoundedGlobalFrame": null, "specialSizeMeasurements": Object { + "alignContent": null, "alignItems": null, + "alignSelf": null, "borderRadius": null, + "borderWidths": Object { + "bottom": 0, + "left": 0, + "right": 0, + "top": 0, + }, "clientHeight": 0, "clientWidth": 0, "closestOffsetParentPath": Object { @@ -4902,6 +5454,7 @@ Object { "gap": null, "globalContentBoxForChildren": null, "globalFrameWithTextContent": null, + "gridCellGlobalFrames": null, "hasPositionOffset": false, "hasTransform": false, "htmlElementName": "div", @@ -4913,7 +5466,9 @@ Object { }, "immediateParentProvidesLayout": true, "justifyContent": null, + "justifySelf": null, "layoutSystemForChildren": null, + "layoutSystemForChildrenInherited": false, "margin": Object {}, "naturalHeight": null, "naturalWidth": null, @@ -4922,8 +5477,24 @@ Object { "y": 0, }, "padding": Object {}, + "parentContainerGridProperties": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, + "parentContainerGridPropertiesFromProps": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, "parentFlexDirection": null, "parentFlexGap": 0, + "parentGridCellGlobalFrames": null, + "parentGridFrame": null, "parentHugsOnMainAxis": false, "parentJustifyContent": null, "parentLayoutSystem": "flow", @@ -5050,8 +5621,16 @@ Object { "label": null, "nonRoundedGlobalFrame": null, "specialSizeMeasurements": Object { + "alignContent": null, "alignItems": null, + "alignSelf": null, "borderRadius": null, + "borderWidths": Object { + "bottom": 0, + "left": 0, + "right": 0, + "top": 0, + }, "clientHeight": 0, "clientWidth": 0, "closestOffsetParentPath": Object { @@ -5099,6 +5678,7 @@ Object { "gap": null, "globalContentBoxForChildren": null, "globalFrameWithTextContent": null, + "gridCellGlobalFrames": null, "hasPositionOffset": false, "hasTransform": false, "htmlElementName": "div", @@ -5110,7 +5690,9 @@ Object { }, "immediateParentProvidesLayout": true, "justifyContent": null, + "justifySelf": null, "layoutSystemForChildren": null, + "layoutSystemForChildrenInherited": false, "margin": Object {}, "naturalHeight": null, "naturalWidth": null, @@ -5119,8 +5701,24 @@ Object { "y": 0, }, "padding": Object {}, + "parentContainerGridProperties": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, + "parentContainerGridPropertiesFromProps": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, "parentFlexDirection": null, "parentFlexGap": 0, + "parentGridCellGlobalFrames": null, + "parentGridFrame": null, "parentHugsOnMainAxis": false, "parentJustifyContent": null, "parentLayoutSystem": "flow", @@ -5213,8 +5811,16 @@ Object { "label": null, "nonRoundedGlobalFrame": null, "specialSizeMeasurements": Object { + "alignContent": null, "alignItems": null, + "alignSelf": null, "borderRadius": null, + "borderWidths": Object { + "bottom": 0, + "left": 0, + "right": 0, + "top": 0, + }, "clientHeight": 0, "clientWidth": 0, "closestOffsetParentPath": Object { @@ -5262,6 +5868,7 @@ Object { "gap": null, "globalContentBoxForChildren": null, "globalFrameWithTextContent": null, + "gridCellGlobalFrames": null, "hasPositionOffset": false, "hasTransform": false, "htmlElementName": "div", @@ -5273,7 +5880,9 @@ Object { }, "immediateParentProvidesLayout": true, "justifyContent": null, + "justifySelf": null, "layoutSystemForChildren": null, + "layoutSystemForChildrenInherited": false, "margin": Object {}, "naturalHeight": null, "naturalWidth": null, @@ -5282,8 +5891,24 @@ Object { "y": 0, }, "padding": Object {}, + "parentContainerGridProperties": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, + "parentContainerGridPropertiesFromProps": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, "parentFlexDirection": null, "parentFlexGap": 0, + "parentGridCellGlobalFrames": null, + "parentGridFrame": null, "parentHugsOnMainAxis": false, "parentJustifyContent": null, "parentLayoutSystem": "flow", @@ -5316,6 +5941,7 @@ exports[`UiJsxCanvas render arbitrary jsx block inside an element inside an arbi data-path=\\"utopia-storyboard-uid/scene-aaa\\" style=\\" overflow: hidden; + container: scene / inline-size; position: absolute; background-color: var(--utopitheme-emphasizedBackground); box-shadow: 0px 1px 2px 0px var(--utopitheme-shadow90), @@ -5554,8 +6180,16 @@ Object { "label": null, "nonRoundedGlobalFrame": null, "specialSizeMeasurements": Object { + "alignContent": null, "alignItems": null, + "alignSelf": null, "borderRadius": null, + "borderWidths": Object { + "bottom": 0, + "left": 0, + "right": 0, + "top": 0, + }, "clientHeight": 0, "clientWidth": 0, "closestOffsetParentPath": Object { @@ -5603,6 +6237,7 @@ Object { "gap": null, "globalContentBoxForChildren": null, "globalFrameWithTextContent": null, + "gridCellGlobalFrames": null, "hasPositionOffset": false, "hasTransform": false, "htmlElementName": "div", @@ -5614,7 +6249,9 @@ Object { }, "immediateParentProvidesLayout": true, "justifyContent": null, + "justifySelf": null, "layoutSystemForChildren": null, + "layoutSystemForChildrenInherited": false, "margin": Object {}, "naturalHeight": null, "naturalWidth": null, @@ -5623,8 +6260,24 @@ Object { "y": 0, }, "padding": Object {}, + "parentContainerGridProperties": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, + "parentContainerGridPropertiesFromProps": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, "parentFlexDirection": null, "parentFlexGap": 0, + "parentGridCellGlobalFrames": null, + "parentGridFrame": null, "parentHugsOnMainAxis": false, "parentJustifyContent": null, "parentLayoutSystem": "flow", @@ -5723,8 +6376,16 @@ Object { "label": null, "nonRoundedGlobalFrame": null, "specialSizeMeasurements": Object { + "alignContent": null, "alignItems": null, + "alignSelf": null, "borderRadius": null, + "borderWidths": Object { + "bottom": 0, + "left": 0, + "right": 0, + "top": 0, + }, "clientHeight": 0, "clientWidth": 0, "closestOffsetParentPath": Object { @@ -5772,6 +6433,7 @@ Object { "gap": null, "globalContentBoxForChildren": null, "globalFrameWithTextContent": null, + "gridCellGlobalFrames": null, "hasPositionOffset": false, "hasTransform": false, "htmlElementName": "div", @@ -5783,7 +6445,9 @@ Object { }, "immediateParentProvidesLayout": true, "justifyContent": null, + "justifySelf": null, "layoutSystemForChildren": null, + "layoutSystemForChildrenInherited": false, "margin": Object {}, "naturalHeight": null, "naturalWidth": null, @@ -5792,8 +6456,24 @@ Object { "y": 0, }, "padding": Object {}, + "parentContainerGridProperties": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, + "parentContainerGridPropertiesFromProps": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, "parentFlexDirection": null, "parentFlexGap": 0, + "parentGridCellGlobalFrames": null, + "parentGridFrame": null, "parentHugsOnMainAxis": false, "parentJustifyContent": null, "parentLayoutSystem": "flow", @@ -6320,8 +7000,16 @@ Object { "label": null, "nonRoundedGlobalFrame": null, "specialSizeMeasurements": Object { + "alignContent": null, "alignItems": null, + "alignSelf": null, "borderRadius": null, + "borderWidths": Object { + "bottom": 0, + "left": 0, + "right": 0, + "top": 0, + }, "clientHeight": 0, "clientWidth": 0, "closestOffsetParentPath": Object { @@ -6369,6 +7057,7 @@ Object { "gap": null, "globalContentBoxForChildren": null, "globalFrameWithTextContent": null, + "gridCellGlobalFrames": null, "hasPositionOffset": false, "hasTransform": false, "htmlElementName": "div", @@ -6380,7 +7069,9 @@ Object { }, "immediateParentProvidesLayout": true, "justifyContent": null, + "justifySelf": null, "layoutSystemForChildren": null, + "layoutSystemForChildrenInherited": false, "margin": Object {}, "naturalHeight": null, "naturalWidth": null, @@ -6389,8 +7080,24 @@ Object { "y": 0, }, "padding": Object {}, + "parentContainerGridProperties": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, + "parentContainerGridPropertiesFromProps": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, "parentFlexDirection": null, "parentFlexGap": 0, + "parentGridCellGlobalFrames": null, + "parentGridFrame": null, "parentHugsOnMainAxis": false, "parentJustifyContent": null, "parentLayoutSystem": "flow", @@ -6883,8 +7590,16 @@ Object { "label": null, "nonRoundedGlobalFrame": null, "specialSizeMeasurements": Object { + "alignContent": null, "alignItems": null, + "alignSelf": null, "borderRadius": null, + "borderWidths": Object { + "bottom": 0, + "left": 0, + "right": 0, + "top": 0, + }, "clientHeight": 0, "clientWidth": 0, "closestOffsetParentPath": Object { @@ -6932,6 +7647,7 @@ Object { "gap": null, "globalContentBoxForChildren": null, "globalFrameWithTextContent": null, + "gridCellGlobalFrames": null, "hasPositionOffset": false, "hasTransform": false, "htmlElementName": "div", @@ -6943,7 +7659,9 @@ Object { }, "immediateParentProvidesLayout": true, "justifyContent": null, + "justifySelf": null, "layoutSystemForChildren": null, + "layoutSystemForChildrenInherited": false, "margin": Object {}, "naturalHeight": null, "naturalWidth": null, @@ -6952,8 +7670,24 @@ Object { "y": 0, }, "padding": Object {}, + "parentContainerGridProperties": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, + "parentContainerGridPropertiesFromProps": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, "parentFlexDirection": null, "parentFlexGap": 0, + "parentGridCellGlobalFrames": null, + "parentGridFrame": null, "parentHugsOnMainAxis": false, "parentJustifyContent": null, "parentLayoutSystem": "flow", @@ -7286,8 +8020,16 @@ Object { "label": null, "nonRoundedGlobalFrame": null, "specialSizeMeasurements": Object { + "alignContent": null, "alignItems": null, + "alignSelf": null, "borderRadius": null, + "borderWidths": Object { + "bottom": 0, + "left": 0, + "right": 0, + "top": 0, + }, "clientHeight": 0, "clientWidth": 0, "closestOffsetParentPath": Object { @@ -7335,6 +8077,7 @@ Object { "gap": null, "globalContentBoxForChildren": null, "globalFrameWithTextContent": null, + "gridCellGlobalFrames": null, "hasPositionOffset": false, "hasTransform": false, "htmlElementName": "div", @@ -7346,7 +8089,9 @@ Object { }, "immediateParentProvidesLayout": true, "justifyContent": null, + "justifySelf": null, "layoutSystemForChildren": null, + "layoutSystemForChildrenInherited": false, "margin": Object {}, "naturalHeight": null, "naturalWidth": null, @@ -7355,8 +8100,24 @@ Object { "y": 0, }, "padding": Object {}, + "parentContainerGridProperties": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, + "parentContainerGridPropertiesFromProps": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, "parentFlexDirection": null, "parentFlexGap": 0, + "parentGridCellGlobalFrames": null, + "parentGridFrame": null, "parentHugsOnMainAxis": false, "parentJustifyContent": null, "parentLayoutSystem": "flow", @@ -7655,8 +8416,16 @@ Object { "label": null, "nonRoundedGlobalFrame": null, "specialSizeMeasurements": Object { + "alignContent": null, "alignItems": null, + "alignSelf": null, "borderRadius": null, + "borderWidths": Object { + "bottom": 0, + "left": 0, + "right": 0, + "top": 0, + }, "clientHeight": 0, "clientWidth": 0, "closestOffsetParentPath": Object { @@ -7704,6 +8473,7 @@ Object { "gap": null, "globalContentBoxForChildren": null, "globalFrameWithTextContent": null, + "gridCellGlobalFrames": null, "hasPositionOffset": false, "hasTransform": false, "htmlElementName": "div", @@ -7715,7 +8485,9 @@ Object { }, "immediateParentProvidesLayout": true, "justifyContent": null, + "justifySelf": null, "layoutSystemForChildren": null, + "layoutSystemForChildrenInherited": false, "margin": Object {}, "naturalHeight": null, "naturalWidth": null, @@ -7724,8 +8496,24 @@ Object { "y": 0, }, "padding": Object {}, + "parentContainerGridProperties": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, + "parentContainerGridPropertiesFromProps": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, "parentFlexDirection": null, "parentFlexGap": 0, + "parentGridCellGlobalFrames": null, + "parentGridFrame": null, "parentHugsOnMainAxis": false, "parentJustifyContent": null, "parentLayoutSystem": "flow", @@ -7872,8 +8660,16 @@ Object { "label": null, "nonRoundedGlobalFrame": null, "specialSizeMeasurements": Object { + "alignContent": null, "alignItems": null, + "alignSelf": null, "borderRadius": null, + "borderWidths": Object { + "bottom": 0, + "left": 0, + "right": 0, + "top": 0, + }, "clientHeight": 0, "clientWidth": 0, "closestOffsetParentPath": Object { @@ -7921,6 +8717,7 @@ Object { "gap": null, "globalContentBoxForChildren": null, "globalFrameWithTextContent": null, + "gridCellGlobalFrames": null, "hasPositionOffset": false, "hasTransform": false, "htmlElementName": "div", @@ -7932,7 +8729,9 @@ Object { }, "immediateParentProvidesLayout": true, "justifyContent": null, + "justifySelf": null, "layoutSystemForChildren": null, + "layoutSystemForChildrenInherited": false, "margin": Object {}, "naturalHeight": null, "naturalWidth": null, @@ -7941,8 +8740,24 @@ Object { "y": 0, }, "padding": Object {}, + "parentContainerGridProperties": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, + "parentContainerGridPropertiesFromProps": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, "parentFlexDirection": null, "parentFlexGap": 0, + "parentGridCellGlobalFrames": null, + "parentGridFrame": null, "parentHugsOnMainAxis": false, "parentJustifyContent": null, "parentLayoutSystem": "flow", @@ -8055,8 +8870,16 @@ Object { "label": null, "nonRoundedGlobalFrame": null, "specialSizeMeasurements": Object { + "alignContent": null, "alignItems": null, + "alignSelf": null, "borderRadius": null, + "borderWidths": Object { + "bottom": 0, + "left": 0, + "right": 0, + "top": 0, + }, "clientHeight": 0, "clientWidth": 0, "closestOffsetParentPath": Object { @@ -8104,6 +8927,7 @@ Object { "gap": null, "globalContentBoxForChildren": null, "globalFrameWithTextContent": null, + "gridCellGlobalFrames": null, "hasPositionOffset": false, "hasTransform": false, "htmlElementName": "div", @@ -8115,7 +8939,9 @@ Object { }, "immediateParentProvidesLayout": true, "justifyContent": null, + "justifySelf": null, "layoutSystemForChildren": null, + "layoutSystemForChildrenInherited": false, "margin": Object {}, "naturalHeight": null, "naturalWidth": null, @@ -8124,8 +8950,24 @@ Object { "y": 0, }, "padding": Object {}, + "parentContainerGridProperties": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, + "parentContainerGridPropertiesFromProps": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, "parentFlexDirection": null, "parentFlexGap": 0, + "parentGridCellGlobalFrames": null, + "parentGridFrame": null, "parentHugsOnMainAxis": false, "parentJustifyContent": null, "parentLayoutSystem": "flow", @@ -8272,8 +9114,16 @@ Object { "label": null, "nonRoundedGlobalFrame": null, "specialSizeMeasurements": Object { + "alignContent": null, "alignItems": null, + "alignSelf": null, "borderRadius": null, + "borderWidths": Object { + "bottom": 0, + "left": 0, + "right": 0, + "top": 0, + }, "clientHeight": 0, "clientWidth": 0, "closestOffsetParentPath": Object { @@ -8321,6 +9171,7 @@ Object { "gap": null, "globalContentBoxForChildren": null, "globalFrameWithTextContent": null, + "gridCellGlobalFrames": null, "hasPositionOffset": false, "hasTransform": false, "htmlElementName": "div", @@ -8332,7 +9183,9 @@ Object { }, "immediateParentProvidesLayout": true, "justifyContent": null, + "justifySelf": null, "layoutSystemForChildren": null, + "layoutSystemForChildrenInherited": false, "margin": Object {}, "naturalHeight": null, "naturalWidth": null, @@ -8341,8 +9194,24 @@ Object { "y": 0, }, "padding": Object {}, + "parentContainerGridProperties": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, + "parentContainerGridPropertiesFromProps": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, "parentFlexDirection": null, "parentFlexGap": 0, + "parentGridCellGlobalFrames": null, + "parentGridFrame": null, "parentHugsOnMainAxis": false, "parentJustifyContent": null, "parentLayoutSystem": "flow", @@ -8455,8 +9324,16 @@ Object { "label": null, "nonRoundedGlobalFrame": null, "specialSizeMeasurements": Object { + "alignContent": null, "alignItems": null, + "alignSelf": null, "borderRadius": null, + "borderWidths": Object { + "bottom": 0, + "left": 0, + "right": 0, + "top": 0, + }, "clientHeight": 0, "clientWidth": 0, "closestOffsetParentPath": Object { @@ -8504,6 +9381,7 @@ Object { "gap": null, "globalContentBoxForChildren": null, "globalFrameWithTextContent": null, + "gridCellGlobalFrames": null, "hasPositionOffset": false, "hasTransform": false, "htmlElementName": "div", @@ -8515,7 +9393,9 @@ Object { }, "immediateParentProvidesLayout": true, "justifyContent": null, + "justifySelf": null, "layoutSystemForChildren": null, + "layoutSystemForChildrenInherited": false, "margin": Object {}, "naturalHeight": null, "naturalWidth": null, @@ -8524,8 +9404,24 @@ Object { "y": 0, }, "padding": Object {}, + "parentContainerGridProperties": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, + "parentContainerGridPropertiesFromProps": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, "parentFlexDirection": null, "parentFlexGap": 0, + "parentGridCellGlobalFrames": null, + "parentGridFrame": null, "parentHugsOnMainAxis": false, "parentJustifyContent": null, "parentLayoutSystem": "flow", @@ -8672,8 +9568,16 @@ Object { "label": null, "nonRoundedGlobalFrame": null, "specialSizeMeasurements": Object { + "alignContent": null, "alignItems": null, + "alignSelf": null, "borderRadius": null, + "borderWidths": Object { + "bottom": 0, + "left": 0, + "right": 0, + "top": 0, + }, "clientHeight": 0, "clientWidth": 0, "closestOffsetParentPath": Object { @@ -8721,6 +9625,7 @@ Object { "gap": null, "globalContentBoxForChildren": null, "globalFrameWithTextContent": null, + "gridCellGlobalFrames": null, "hasPositionOffset": false, "hasTransform": false, "htmlElementName": "div", @@ -8732,7 +9637,9 @@ Object { }, "immediateParentProvidesLayout": true, "justifyContent": null, + "justifySelf": null, "layoutSystemForChildren": null, + "layoutSystemForChildrenInherited": false, "margin": Object {}, "naturalHeight": null, "naturalWidth": null, @@ -8741,8 +9648,24 @@ Object { "y": 0, }, "padding": Object {}, + "parentContainerGridProperties": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, + "parentContainerGridPropertiesFromProps": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, "parentFlexDirection": null, "parentFlexGap": 0, + "parentGridCellGlobalFrames": null, + "parentGridFrame": null, "parentHugsOnMainAxis": false, "parentJustifyContent": null, "parentLayoutSystem": "flow", @@ -8855,8 +9778,16 @@ Object { "label": null, "nonRoundedGlobalFrame": null, "specialSizeMeasurements": Object { + "alignContent": null, "alignItems": null, + "alignSelf": null, "borderRadius": null, + "borderWidths": Object { + "bottom": 0, + "left": 0, + "right": 0, + "top": 0, + }, "clientHeight": 0, "clientWidth": 0, "closestOffsetParentPath": Object { @@ -8904,6 +9835,7 @@ Object { "gap": null, "globalContentBoxForChildren": null, "globalFrameWithTextContent": null, + "gridCellGlobalFrames": null, "hasPositionOffset": false, "hasTransform": false, "htmlElementName": "div", @@ -8915,7 +9847,9 @@ Object { }, "immediateParentProvidesLayout": true, "justifyContent": null, + "justifySelf": null, "layoutSystemForChildren": null, + "layoutSystemForChildrenInherited": false, "margin": Object {}, "naturalHeight": null, "naturalWidth": null, @@ -8924,8 +9858,24 @@ Object { "y": 0, }, "padding": Object {}, + "parentContainerGridProperties": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, + "parentContainerGridPropertiesFromProps": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, "parentFlexDirection": null, "parentFlexGap": 0, + "parentGridCellGlobalFrames": null, + "parentGridFrame": null, "parentHugsOnMainAxis": false, "parentJustifyContent": null, "parentLayoutSystem": "flow", @@ -9258,8 +10208,16 @@ Object { "label": null, "nonRoundedGlobalFrame": null, "specialSizeMeasurements": Object { + "alignContent": null, "alignItems": null, + "alignSelf": null, "borderRadius": null, + "borderWidths": Object { + "bottom": 0, + "left": 0, + "right": 0, + "top": 0, + }, "clientHeight": 0, "clientWidth": 0, "closestOffsetParentPath": Object { @@ -9307,6 +10265,7 @@ Object { "gap": null, "globalContentBoxForChildren": null, "globalFrameWithTextContent": null, + "gridCellGlobalFrames": null, "hasPositionOffset": false, "hasTransform": false, "htmlElementName": "div", @@ -9318,7 +10277,9 @@ Object { }, "immediateParentProvidesLayout": true, "justifyContent": null, + "justifySelf": null, "layoutSystemForChildren": null, + "layoutSystemForChildrenInherited": false, "margin": Object {}, "naturalHeight": null, "naturalWidth": null, @@ -9327,8 +10288,24 @@ Object { "y": 0, }, "padding": Object {}, + "parentContainerGridProperties": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, + "parentContainerGridPropertiesFromProps": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, "parentFlexDirection": null, "parentFlexGap": 0, + "parentGridCellGlobalFrames": null, + "parentGridFrame": null, "parentHugsOnMainAxis": false, "parentJustifyContent": null, "parentLayoutSystem": "flow", @@ -9627,8 +10604,16 @@ Object { "label": null, "nonRoundedGlobalFrame": null, "specialSizeMeasurements": Object { + "alignContent": null, "alignItems": null, + "alignSelf": null, "borderRadius": null, + "borderWidths": Object { + "bottom": 0, + "left": 0, + "right": 0, + "top": 0, + }, "clientHeight": 0, "clientWidth": 0, "closestOffsetParentPath": Object { @@ -9676,6 +10661,7 @@ Object { "gap": null, "globalContentBoxForChildren": null, "globalFrameWithTextContent": null, + "gridCellGlobalFrames": null, "hasPositionOffset": false, "hasTransform": false, "htmlElementName": "div", @@ -9687,7 +10673,9 @@ Object { }, "immediateParentProvidesLayout": true, "justifyContent": null, + "justifySelf": null, "layoutSystemForChildren": null, + "layoutSystemForChildrenInherited": false, "margin": Object {}, "naturalHeight": null, "naturalWidth": null, @@ -9696,8 +10684,24 @@ Object { "y": 0, }, "padding": Object {}, + "parentContainerGridProperties": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, + "parentContainerGridPropertiesFromProps": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, "parentFlexDirection": null, "parentFlexGap": 0, + "parentGridCellGlobalFrames": null, + "parentGridFrame": null, "parentHugsOnMainAxis": false, "parentJustifyContent": null, "parentLayoutSystem": "flow", @@ -9844,8 +10848,16 @@ Object { "label": null, "nonRoundedGlobalFrame": null, "specialSizeMeasurements": Object { + "alignContent": null, "alignItems": null, + "alignSelf": null, "borderRadius": null, + "borderWidths": Object { + "bottom": 0, + "left": 0, + "right": 0, + "top": 0, + }, "clientHeight": 0, "clientWidth": 0, "closestOffsetParentPath": Object { @@ -9893,6 +10905,7 @@ Object { "gap": null, "globalContentBoxForChildren": null, "globalFrameWithTextContent": null, + "gridCellGlobalFrames": null, "hasPositionOffset": false, "hasTransform": false, "htmlElementName": "div", @@ -9904,7 +10917,9 @@ Object { }, "immediateParentProvidesLayout": true, "justifyContent": null, + "justifySelf": null, "layoutSystemForChildren": null, + "layoutSystemForChildrenInherited": false, "margin": Object {}, "naturalHeight": null, "naturalWidth": null, @@ -9913,8 +10928,24 @@ Object { "y": 0, }, "padding": Object {}, + "parentContainerGridProperties": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, + "parentContainerGridPropertiesFromProps": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, "parentFlexDirection": null, "parentFlexGap": 0, + "parentGridCellGlobalFrames": null, + "parentGridFrame": null, "parentHugsOnMainAxis": false, "parentJustifyContent": null, "parentLayoutSystem": "flow", @@ -10027,8 +11058,16 @@ Object { "label": null, "nonRoundedGlobalFrame": null, "specialSizeMeasurements": Object { + "alignContent": null, "alignItems": null, + "alignSelf": null, "borderRadius": null, + "borderWidths": Object { + "bottom": 0, + "left": 0, + "right": 0, + "top": 0, + }, "clientHeight": 0, "clientWidth": 0, "closestOffsetParentPath": Object { @@ -10076,6 +11115,7 @@ Object { "gap": null, "globalContentBoxForChildren": null, "globalFrameWithTextContent": null, + "gridCellGlobalFrames": null, "hasPositionOffset": false, "hasTransform": false, "htmlElementName": "div", @@ -10087,7 +11127,9 @@ Object { }, "immediateParentProvidesLayout": true, "justifyContent": null, + "justifySelf": null, "layoutSystemForChildren": null, + "layoutSystemForChildrenInherited": false, "margin": Object {}, "naturalHeight": null, "naturalWidth": null, @@ -10096,8 +11138,24 @@ Object { "y": 0, }, "padding": Object {}, + "parentContainerGridProperties": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, + "parentContainerGridPropertiesFromProps": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, "parentFlexDirection": null, "parentFlexGap": 0, + "parentGridCellGlobalFrames": null, + "parentGridFrame": null, "parentHugsOnMainAxis": false, "parentJustifyContent": null, "parentLayoutSystem": "flow", @@ -10244,8 +11302,16 @@ Object { "label": null, "nonRoundedGlobalFrame": null, "specialSizeMeasurements": Object { + "alignContent": null, "alignItems": null, + "alignSelf": null, "borderRadius": null, + "borderWidths": Object { + "bottom": 0, + "left": 0, + "right": 0, + "top": 0, + }, "clientHeight": 0, "clientWidth": 0, "closestOffsetParentPath": Object { @@ -10293,6 +11359,7 @@ Object { "gap": null, "globalContentBoxForChildren": null, "globalFrameWithTextContent": null, + "gridCellGlobalFrames": null, "hasPositionOffset": false, "hasTransform": false, "htmlElementName": "div", @@ -10304,7 +11371,9 @@ Object { }, "immediateParentProvidesLayout": true, "justifyContent": null, + "justifySelf": null, "layoutSystemForChildren": null, + "layoutSystemForChildrenInherited": false, "margin": Object {}, "naturalHeight": null, "naturalWidth": null, @@ -10313,8 +11382,24 @@ Object { "y": 0, }, "padding": Object {}, + "parentContainerGridProperties": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, + "parentContainerGridPropertiesFromProps": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, "parentFlexDirection": null, "parentFlexGap": 0, + "parentGridCellGlobalFrames": null, + "parentGridFrame": null, "parentHugsOnMainAxis": false, "parentJustifyContent": null, "parentLayoutSystem": "flow", @@ -10427,8 +11512,16 @@ Object { "label": null, "nonRoundedGlobalFrame": null, "specialSizeMeasurements": Object { + "alignContent": null, "alignItems": null, + "alignSelf": null, "borderRadius": null, + "borderWidths": Object { + "bottom": 0, + "left": 0, + "right": 0, + "top": 0, + }, "clientHeight": 0, "clientWidth": 0, "closestOffsetParentPath": Object { @@ -10476,6 +11569,7 @@ Object { "gap": null, "globalContentBoxForChildren": null, "globalFrameWithTextContent": null, + "gridCellGlobalFrames": null, "hasPositionOffset": false, "hasTransform": false, "htmlElementName": "div", @@ -10487,7 +11581,9 @@ Object { }, "immediateParentProvidesLayout": true, "justifyContent": null, + "justifySelf": null, "layoutSystemForChildren": null, + "layoutSystemForChildrenInherited": false, "margin": Object {}, "naturalHeight": null, "naturalWidth": null, @@ -10496,8 +11592,24 @@ Object { "y": 0, }, "padding": Object {}, + "parentContainerGridProperties": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, + "parentContainerGridPropertiesFromProps": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, "parentFlexDirection": null, "parentFlexGap": 0, + "parentGridCellGlobalFrames": null, + "parentGridFrame": null, "parentHugsOnMainAxis": false, "parentJustifyContent": null, "parentLayoutSystem": "flow", @@ -10644,8 +11756,16 @@ Object { "label": null, "nonRoundedGlobalFrame": null, "specialSizeMeasurements": Object { + "alignContent": null, "alignItems": null, + "alignSelf": null, "borderRadius": null, + "borderWidths": Object { + "bottom": 0, + "left": 0, + "right": 0, + "top": 0, + }, "clientHeight": 0, "clientWidth": 0, "closestOffsetParentPath": Object { @@ -10693,6 +11813,7 @@ Object { "gap": null, "globalContentBoxForChildren": null, "globalFrameWithTextContent": null, + "gridCellGlobalFrames": null, "hasPositionOffset": false, "hasTransform": false, "htmlElementName": "div", @@ -10704,7 +11825,9 @@ Object { }, "immediateParentProvidesLayout": true, "justifyContent": null, + "justifySelf": null, "layoutSystemForChildren": null, + "layoutSystemForChildrenInherited": false, "margin": Object {}, "naturalHeight": null, "naturalWidth": null, @@ -10713,8 +11836,24 @@ Object { "y": 0, }, "padding": Object {}, + "parentContainerGridProperties": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, + "parentContainerGridPropertiesFromProps": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, "parentFlexDirection": null, "parentFlexGap": 0, + "parentGridCellGlobalFrames": null, + "parentGridFrame": null, "parentHugsOnMainAxis": false, "parentJustifyContent": null, "parentLayoutSystem": "flow", @@ -10827,8 +11966,16 @@ Object { "label": null, "nonRoundedGlobalFrame": null, "specialSizeMeasurements": Object { + "alignContent": null, "alignItems": null, + "alignSelf": null, "borderRadius": null, + "borderWidths": Object { + "bottom": 0, + "left": 0, + "right": 0, + "top": 0, + }, "clientHeight": 0, "clientWidth": 0, "closestOffsetParentPath": Object { @@ -10876,6 +12023,7 @@ Object { "gap": null, "globalContentBoxForChildren": null, "globalFrameWithTextContent": null, + "gridCellGlobalFrames": null, "hasPositionOffset": false, "hasTransform": false, "htmlElementName": "div", @@ -10887,7 +12035,9 @@ Object { }, "immediateParentProvidesLayout": true, "justifyContent": null, + "justifySelf": null, "layoutSystemForChildren": null, + "layoutSystemForChildrenInherited": false, "margin": Object {}, "naturalHeight": null, "naturalWidth": null, @@ -10896,8 +12046,24 @@ Object { "y": 0, }, "padding": Object {}, + "parentContainerGridProperties": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, + "parentContainerGridPropertiesFromProps": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, "parentFlexDirection": null, "parentFlexGap": 0, + "parentGridCellGlobalFrames": null, + "parentGridFrame": null, "parentHugsOnMainAxis": false, "parentJustifyContent": null, "parentLayoutSystem": "flow", @@ -11230,8 +12396,16 @@ Object { "label": null, "nonRoundedGlobalFrame": null, "specialSizeMeasurements": Object { + "alignContent": null, "alignItems": null, + "alignSelf": null, "borderRadius": null, + "borderWidths": Object { + "bottom": 0, + "left": 0, + "right": 0, + "top": 0, + }, "clientHeight": 0, "clientWidth": 0, "closestOffsetParentPath": Object { @@ -11279,6 +12453,7 @@ Object { "gap": null, "globalContentBoxForChildren": null, "globalFrameWithTextContent": null, + "gridCellGlobalFrames": null, "hasPositionOffset": false, "hasTransform": false, "htmlElementName": "div", @@ -11290,7 +12465,9 @@ Object { }, "immediateParentProvidesLayout": true, "justifyContent": null, + "justifySelf": null, "layoutSystemForChildren": null, + "layoutSystemForChildrenInherited": false, "margin": Object {}, "naturalHeight": null, "naturalWidth": null, @@ -11299,8 +12476,24 @@ Object { "y": 0, }, "padding": Object {}, + "parentContainerGridProperties": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, + "parentContainerGridPropertiesFromProps": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, "parentFlexDirection": null, "parentFlexGap": 0, + "parentGridCellGlobalFrames": null, + "parentGridFrame": null, "parentHugsOnMainAxis": false, "parentJustifyContent": null, "parentLayoutSystem": "flow", @@ -11599,8 +12792,16 @@ Object { "label": null, "nonRoundedGlobalFrame": null, "specialSizeMeasurements": Object { + "alignContent": null, "alignItems": null, + "alignSelf": null, "borderRadius": null, + "borderWidths": Object { + "bottom": 0, + "left": 0, + "right": 0, + "top": 0, + }, "clientHeight": 0, "clientWidth": 0, "closestOffsetParentPath": Object { @@ -11648,6 +12849,7 @@ Object { "gap": null, "globalContentBoxForChildren": null, "globalFrameWithTextContent": null, + "gridCellGlobalFrames": null, "hasPositionOffset": false, "hasTransform": false, "htmlElementName": "div", @@ -11659,7 +12861,9 @@ Object { }, "immediateParentProvidesLayout": true, "justifyContent": null, + "justifySelf": null, "layoutSystemForChildren": null, + "layoutSystemForChildrenInherited": false, "margin": Object {}, "naturalHeight": null, "naturalWidth": null, @@ -11668,8 +12872,24 @@ Object { "y": 0, }, "padding": Object {}, + "parentContainerGridProperties": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, + "parentContainerGridPropertiesFromProps": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, "parentFlexDirection": null, "parentFlexGap": 0, + "parentGridCellGlobalFrames": null, + "parentGridFrame": null, "parentHugsOnMainAxis": false, "parentJustifyContent": null, "parentLayoutSystem": "flow", @@ -11816,8 +13036,16 @@ Object { "label": null, "nonRoundedGlobalFrame": null, "specialSizeMeasurements": Object { + "alignContent": null, "alignItems": null, + "alignSelf": null, "borderRadius": null, + "borderWidths": Object { + "bottom": 0, + "left": 0, + "right": 0, + "top": 0, + }, "clientHeight": 0, "clientWidth": 0, "closestOffsetParentPath": Object { @@ -11865,6 +13093,7 @@ Object { "gap": null, "globalContentBoxForChildren": null, "globalFrameWithTextContent": null, + "gridCellGlobalFrames": null, "hasPositionOffset": false, "hasTransform": false, "htmlElementName": "div", @@ -11876,7 +13105,9 @@ Object { }, "immediateParentProvidesLayout": true, "justifyContent": null, + "justifySelf": null, "layoutSystemForChildren": null, + "layoutSystemForChildrenInherited": false, "margin": Object {}, "naturalHeight": null, "naturalWidth": null, @@ -11885,8 +13116,24 @@ Object { "y": 0, }, "padding": Object {}, + "parentContainerGridProperties": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, + "parentContainerGridPropertiesFromProps": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, "parentFlexDirection": null, "parentFlexGap": 0, + "parentGridCellGlobalFrames": null, + "parentGridFrame": null, "parentHugsOnMainAxis": false, "parentJustifyContent": null, "parentLayoutSystem": "flow", @@ -11999,8 +13246,16 @@ Object { "label": null, "nonRoundedGlobalFrame": null, "specialSizeMeasurements": Object { + "alignContent": null, "alignItems": null, + "alignSelf": null, "borderRadius": null, + "borderWidths": Object { + "bottom": 0, + "left": 0, + "right": 0, + "top": 0, + }, "clientHeight": 0, "clientWidth": 0, "closestOffsetParentPath": Object { @@ -12048,6 +13303,7 @@ Object { "gap": null, "globalContentBoxForChildren": null, "globalFrameWithTextContent": null, + "gridCellGlobalFrames": null, "hasPositionOffset": false, "hasTransform": false, "htmlElementName": "div", @@ -12059,7 +13315,9 @@ Object { }, "immediateParentProvidesLayout": true, "justifyContent": null, + "justifySelf": null, "layoutSystemForChildren": null, + "layoutSystemForChildrenInherited": false, "margin": Object {}, "naturalHeight": null, "naturalWidth": null, @@ -12068,8 +13326,24 @@ Object { "y": 0, }, "padding": Object {}, + "parentContainerGridProperties": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, + "parentContainerGridPropertiesFromProps": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, "parentFlexDirection": null, "parentFlexGap": 0, + "parentGridCellGlobalFrames": null, + "parentGridFrame": null, "parentHugsOnMainAxis": false, "parentJustifyContent": null, "parentLayoutSystem": "flow", @@ -12216,8 +13490,16 @@ Object { "label": null, "nonRoundedGlobalFrame": null, "specialSizeMeasurements": Object { + "alignContent": null, "alignItems": null, + "alignSelf": null, "borderRadius": null, + "borderWidths": Object { + "bottom": 0, + "left": 0, + "right": 0, + "top": 0, + }, "clientHeight": 0, "clientWidth": 0, "closestOffsetParentPath": Object { @@ -12265,6 +13547,7 @@ Object { "gap": null, "globalContentBoxForChildren": null, "globalFrameWithTextContent": null, + "gridCellGlobalFrames": null, "hasPositionOffset": false, "hasTransform": false, "htmlElementName": "div", @@ -12276,7 +13559,9 @@ Object { }, "immediateParentProvidesLayout": true, "justifyContent": null, + "justifySelf": null, "layoutSystemForChildren": null, + "layoutSystemForChildrenInherited": false, "margin": Object {}, "naturalHeight": null, "naturalWidth": null, @@ -12285,8 +13570,24 @@ Object { "y": 0, }, "padding": Object {}, + "parentContainerGridProperties": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, + "parentContainerGridPropertiesFromProps": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, "parentFlexDirection": null, "parentFlexGap": 0, + "parentGridCellGlobalFrames": null, + "parentGridFrame": null, "parentHugsOnMainAxis": false, "parentJustifyContent": null, "parentLayoutSystem": "flow", @@ -12399,8 +13700,16 @@ Object { "label": null, "nonRoundedGlobalFrame": null, "specialSizeMeasurements": Object { + "alignContent": null, "alignItems": null, + "alignSelf": null, "borderRadius": null, + "borderWidths": Object { + "bottom": 0, + "left": 0, + "right": 0, + "top": 0, + }, "clientHeight": 0, "clientWidth": 0, "closestOffsetParentPath": Object { @@ -12448,6 +13757,7 @@ Object { "gap": null, "globalContentBoxForChildren": null, "globalFrameWithTextContent": null, + "gridCellGlobalFrames": null, "hasPositionOffset": false, "hasTransform": false, "htmlElementName": "div", @@ -12459,7 +13769,9 @@ Object { }, "immediateParentProvidesLayout": true, "justifyContent": null, + "justifySelf": null, "layoutSystemForChildren": null, + "layoutSystemForChildrenInherited": false, "margin": Object {}, "naturalHeight": null, "naturalWidth": null, @@ -12468,8 +13780,24 @@ Object { "y": 0, }, "padding": Object {}, + "parentContainerGridProperties": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, + "parentContainerGridPropertiesFromProps": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, "parentFlexDirection": null, "parentFlexGap": 0, + "parentGridCellGlobalFrames": null, + "parentGridFrame": null, "parentHugsOnMainAxis": false, "parentJustifyContent": null, "parentLayoutSystem": "flow", @@ -12616,8 +13944,16 @@ Object { "label": null, "nonRoundedGlobalFrame": null, "specialSizeMeasurements": Object { + "alignContent": null, "alignItems": null, + "alignSelf": null, "borderRadius": null, + "borderWidths": Object { + "bottom": 0, + "left": 0, + "right": 0, + "top": 0, + }, "clientHeight": 0, "clientWidth": 0, "closestOffsetParentPath": Object { @@ -12665,6 +14001,7 @@ Object { "gap": null, "globalContentBoxForChildren": null, "globalFrameWithTextContent": null, + "gridCellGlobalFrames": null, "hasPositionOffset": false, "hasTransform": false, "htmlElementName": "div", @@ -12676,7 +14013,9 @@ Object { }, "immediateParentProvidesLayout": true, "justifyContent": null, + "justifySelf": null, "layoutSystemForChildren": null, + "layoutSystemForChildrenInherited": false, "margin": Object {}, "naturalHeight": null, "naturalWidth": null, @@ -12685,8 +14024,24 @@ Object { "y": 0, }, "padding": Object {}, + "parentContainerGridProperties": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, + "parentContainerGridPropertiesFromProps": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, "parentFlexDirection": null, "parentFlexGap": 0, + "parentGridCellGlobalFrames": null, + "parentGridFrame": null, "parentHugsOnMainAxis": false, "parentJustifyContent": null, "parentLayoutSystem": "flow", @@ -12799,8 +14154,16 @@ Object { "label": null, "nonRoundedGlobalFrame": null, "specialSizeMeasurements": Object { + "alignContent": null, "alignItems": null, + "alignSelf": null, "borderRadius": null, + "borderWidths": Object { + "bottom": 0, + "left": 0, + "right": 0, + "top": 0, + }, "clientHeight": 0, "clientWidth": 0, "closestOffsetParentPath": Object { @@ -12848,6 +14211,7 @@ Object { "gap": null, "globalContentBoxForChildren": null, "globalFrameWithTextContent": null, + "gridCellGlobalFrames": null, "hasPositionOffset": false, "hasTransform": false, "htmlElementName": "div", @@ -12859,7 +14223,9 @@ Object { }, "immediateParentProvidesLayout": true, "justifyContent": null, + "justifySelf": null, "layoutSystemForChildren": null, + "layoutSystemForChildrenInherited": false, "margin": Object {}, "naturalHeight": null, "naturalWidth": null, @@ -12868,8 +14234,24 @@ Object { "y": 0, }, "padding": Object {}, + "parentContainerGridProperties": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, + "parentContainerGridPropertiesFromProps": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, "parentFlexDirection": null, "parentFlexGap": 0, + "parentGridCellGlobalFrames": null, + "parentGridFrame": null, "parentHugsOnMainAxis": false, "parentJustifyContent": null, "parentLayoutSystem": "flow", @@ -12902,6 +14284,7 @@ exports[`UiJsxCanvas render class component is available from arbitrary block in data-path=\\"utopia-storyboard-uid/scene-aaa\\" style=\\" overflow: hidden; + container: scene / inline-size; position: absolute; background-color: var(--utopitheme-emphasizedBackground); box-shadow: 0px 1px 2px 0px var(--utopitheme-shadow90), @@ -13083,8 +14466,16 @@ Object { "label": null, "nonRoundedGlobalFrame": null, "specialSizeMeasurements": Object { + "alignContent": null, "alignItems": null, + "alignSelf": null, "borderRadius": null, + "borderWidths": Object { + "bottom": 0, + "left": 0, + "right": 0, + "top": 0, + }, "clientHeight": 0, "clientWidth": 0, "closestOffsetParentPath": Object { @@ -13132,6 +14523,7 @@ Object { "gap": null, "globalContentBoxForChildren": null, "globalFrameWithTextContent": null, + "gridCellGlobalFrames": null, "hasPositionOffset": false, "hasTransform": false, "htmlElementName": "div", @@ -13143,7 +14535,9 @@ Object { }, "immediateParentProvidesLayout": true, "justifyContent": null, + "justifySelf": null, "layoutSystemForChildren": null, + "layoutSystemForChildrenInherited": false, "margin": Object {}, "naturalHeight": null, "naturalWidth": null, @@ -13152,8 +14546,24 @@ Object { "y": 0, }, "padding": Object {}, + "parentContainerGridProperties": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, + "parentContainerGridPropertiesFromProps": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, "parentFlexDirection": null, "parentFlexGap": 0, + "parentGridCellGlobalFrames": null, + "parentGridFrame": null, "parentHugsOnMainAxis": false, "parentJustifyContent": null, "parentLayoutSystem": "flow", @@ -13252,8 +14662,16 @@ Object { "label": null, "nonRoundedGlobalFrame": null, "specialSizeMeasurements": Object { + "alignContent": null, "alignItems": null, + "alignSelf": null, "borderRadius": null, + "borderWidths": Object { + "bottom": 0, + "left": 0, + "right": 0, + "top": 0, + }, "clientHeight": 0, "clientWidth": 0, "closestOffsetParentPath": Object { @@ -13301,6 +14719,7 @@ Object { "gap": null, "globalContentBoxForChildren": null, "globalFrameWithTextContent": null, + "gridCellGlobalFrames": null, "hasPositionOffset": false, "hasTransform": false, "htmlElementName": "div", @@ -13312,7 +14731,9 @@ Object { }, "immediateParentProvidesLayout": true, "justifyContent": null, + "justifySelf": null, "layoutSystemForChildren": null, + "layoutSystemForChildrenInherited": false, "margin": Object {}, "naturalHeight": null, "naturalWidth": null, @@ -13321,8 +14742,24 @@ Object { "y": 0, }, "padding": Object {}, + "parentContainerGridProperties": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, + "parentContainerGridPropertiesFromProps": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, "parentFlexDirection": null, "parentFlexGap": 0, + "parentGridCellGlobalFrames": null, + "parentGridFrame": null, "parentHugsOnMainAxis": false, "parentJustifyContent": null, "parentLayoutSystem": "flow", @@ -13462,8 +14899,16 @@ Object { "label": null, "nonRoundedGlobalFrame": null, "specialSizeMeasurements": Object { + "alignContent": null, "alignItems": null, + "alignSelf": null, "borderRadius": null, + "borderWidths": Object { + "bottom": 0, + "left": 0, + "right": 0, + "top": 0, + }, "clientHeight": 0, "clientWidth": 0, "closestOffsetParentPath": Object { @@ -13511,6 +14956,7 @@ Object { "gap": null, "globalContentBoxForChildren": null, "globalFrameWithTextContent": null, + "gridCellGlobalFrames": null, "hasPositionOffset": false, "hasTransform": false, "htmlElementName": "div", @@ -13522,7 +14968,9 @@ Object { }, "immediateParentProvidesLayout": true, "justifyContent": null, + "justifySelf": null, "layoutSystemForChildren": null, + "layoutSystemForChildrenInherited": false, "margin": Object {}, "naturalHeight": null, "naturalWidth": null, @@ -13531,8 +14979,24 @@ Object { "y": 0, }, "padding": Object {}, + "parentContainerGridProperties": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, + "parentContainerGridPropertiesFromProps": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, "parentFlexDirection": null, "parentFlexGap": 0, + "parentGridCellGlobalFrames": null, + "parentGridFrame": null, "parentHugsOnMainAxis": false, "parentJustifyContent": null, "parentLayoutSystem": "flow", @@ -13612,8 +15076,16 @@ Object { "label": null, "nonRoundedGlobalFrame": null, "specialSizeMeasurements": Object { + "alignContent": null, "alignItems": null, + "alignSelf": null, "borderRadius": null, + "borderWidths": Object { + "bottom": 0, + "left": 0, + "right": 0, + "top": 0, + }, "clientHeight": 0, "clientWidth": 0, "closestOffsetParentPath": Object { @@ -13661,6 +15133,7 @@ Object { "gap": null, "globalContentBoxForChildren": null, "globalFrameWithTextContent": null, + "gridCellGlobalFrames": null, "hasPositionOffset": false, "hasTransform": false, "htmlElementName": "div", @@ -13672,7 +15145,9 @@ Object { }, "immediateParentProvidesLayout": true, "justifyContent": null, + "justifySelf": null, "layoutSystemForChildren": null, + "layoutSystemForChildrenInherited": false, "margin": Object {}, "naturalHeight": null, "naturalWidth": null, @@ -13681,8 +15156,24 @@ Object { "y": 0, }, "padding": Object {}, + "parentContainerGridProperties": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, + "parentContainerGridPropertiesFromProps": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, "parentFlexDirection": null, "parentFlexGap": 0, + "parentGridCellGlobalFrames": null, + "parentGridFrame": null, "parentHugsOnMainAxis": false, "parentJustifyContent": null, "parentLayoutSystem": "flow", @@ -13762,8 +15253,16 @@ Object { "label": null, "nonRoundedGlobalFrame": null, "specialSizeMeasurements": Object { + "alignContent": null, "alignItems": null, + "alignSelf": null, "borderRadius": null, + "borderWidths": Object { + "bottom": 0, + "left": 0, + "right": 0, + "top": 0, + }, "clientHeight": 0, "clientWidth": 0, "closestOffsetParentPath": Object { @@ -13811,6 +15310,7 @@ Object { "gap": null, "globalContentBoxForChildren": null, "globalFrameWithTextContent": null, + "gridCellGlobalFrames": null, "hasPositionOffset": false, "hasTransform": false, "htmlElementName": "div", @@ -13822,7 +15322,9 @@ Object { }, "immediateParentProvidesLayout": true, "justifyContent": null, + "justifySelf": null, "layoutSystemForChildren": null, + "layoutSystemForChildrenInherited": false, "margin": Object {}, "naturalHeight": null, "naturalWidth": null, @@ -13831,8 +15333,24 @@ Object { "y": 0, }, "padding": Object {}, + "parentContainerGridProperties": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, + "parentContainerGridPropertiesFromProps": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, "parentFlexDirection": null, "parentFlexGap": 0, + "parentGridCellGlobalFrames": null, + "parentGridFrame": null, "parentHugsOnMainAxis": false, "parentJustifyContent": null, "parentLayoutSystem": "flow", @@ -13865,6 +15383,7 @@ exports[`UiJsxCanvas render console logging does not do anything bizarre 1`] = ` data-path=\\"utopia-storyboard-uid/scene-aaa\\" style=\\" overflow: hidden; + container: scene / inline-size; position: absolute; background-color: var(--utopitheme-emphasizedBackground); box-shadow: 0px 1px 2px 0px var(--utopitheme-shadow90), @@ -14034,8 +15553,16 @@ Object { "label": null, "nonRoundedGlobalFrame": null, "specialSizeMeasurements": Object { + "alignContent": null, "alignItems": null, + "alignSelf": null, "borderRadius": null, + "borderWidths": Object { + "bottom": 0, + "left": 0, + "right": 0, + "top": 0, + }, "clientHeight": 0, "clientWidth": 0, "closestOffsetParentPath": Object { @@ -14083,6 +15610,7 @@ Object { "gap": null, "globalContentBoxForChildren": null, "globalFrameWithTextContent": null, + "gridCellGlobalFrames": null, "hasPositionOffset": false, "hasTransform": false, "htmlElementName": "div", @@ -14094,7 +15622,9 @@ Object { }, "immediateParentProvidesLayout": true, "justifyContent": null, + "justifySelf": null, "layoutSystemForChildren": null, + "layoutSystemForChildrenInherited": false, "margin": Object {}, "naturalHeight": null, "naturalWidth": null, @@ -14103,8 +15633,24 @@ Object { "y": 0, }, "padding": Object {}, + "parentContainerGridProperties": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, + "parentContainerGridPropertiesFromProps": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, "parentFlexDirection": null, "parentFlexGap": 0, + "parentGridCellGlobalFrames": null, + "parentGridFrame": null, "parentHugsOnMainAxis": false, "parentJustifyContent": null, "parentLayoutSystem": "flow", @@ -14203,8 +15749,16 @@ Object { "label": null, "nonRoundedGlobalFrame": null, "specialSizeMeasurements": Object { + "alignContent": null, "alignItems": null, + "alignSelf": null, "borderRadius": null, + "borderWidths": Object { + "bottom": 0, + "left": 0, + "right": 0, + "top": 0, + }, "clientHeight": 0, "clientWidth": 0, "closestOffsetParentPath": Object { @@ -14252,6 +15806,7 @@ Object { "gap": null, "globalContentBoxForChildren": null, "globalFrameWithTextContent": null, + "gridCellGlobalFrames": null, "hasPositionOffset": false, "hasTransform": false, "htmlElementName": "div", @@ -14263,7 +15818,9 @@ Object { }, "immediateParentProvidesLayout": true, "justifyContent": null, + "justifySelf": null, "layoutSystemForChildren": null, + "layoutSystemForChildrenInherited": false, "margin": Object {}, "naturalHeight": null, "naturalWidth": null, @@ -14272,8 +15829,24 @@ Object { "y": 0, }, "padding": Object {}, + "parentContainerGridProperties": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, + "parentContainerGridPropertiesFromProps": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, "parentFlexDirection": null, "parentFlexGap": 0, + "parentGridCellGlobalFrames": null, + "parentGridFrame": null, "parentHugsOnMainAxis": false, "parentJustifyContent": null, "parentLayoutSystem": "flow", @@ -14465,8 +16038,16 @@ Object { "label": null, "nonRoundedGlobalFrame": null, "specialSizeMeasurements": Object { + "alignContent": null, "alignItems": null, + "alignSelf": null, "borderRadius": null, + "borderWidths": Object { + "bottom": 0, + "left": 0, + "right": 0, + "top": 0, + }, "clientHeight": 0, "clientWidth": 0, "closestOffsetParentPath": Object { @@ -14514,6 +16095,7 @@ Object { "gap": null, "globalContentBoxForChildren": null, "globalFrameWithTextContent": null, + "gridCellGlobalFrames": null, "hasPositionOffset": false, "hasTransform": false, "htmlElementName": "div", @@ -14525,7 +16107,9 @@ Object { }, "immediateParentProvidesLayout": true, "justifyContent": null, + "justifySelf": null, "layoutSystemForChildren": null, + "layoutSystemForChildrenInherited": false, "margin": Object {}, "naturalHeight": null, "naturalWidth": null, @@ -14534,8 +16118,24 @@ Object { "y": 0, }, "padding": Object {}, + "parentContainerGridProperties": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, + "parentContainerGridPropertiesFromProps": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, "parentFlexDirection": null, "parentFlexGap": 0, + "parentGridCellGlobalFrames": null, + "parentGridFrame": null, "parentHugsOnMainAxis": false, "parentJustifyContent": null, "parentLayoutSystem": "flow", @@ -14568,6 +16168,7 @@ exports[`UiJsxCanvas render does not crash if the metadata scenes are not the ap data-path=\\"utopia-storyboard-uid/scene-aaa\\" style=\\" overflow: hidden; + container: scene / inline-size; position: absolute; background-color: var(--utopitheme-emphasizedBackground); box-shadow: 0px 1px 2px 0px var(--utopitheme-shadow90), @@ -14755,8 +16356,16 @@ Object { "label": null, "nonRoundedGlobalFrame": null, "specialSizeMeasurements": Object { + "alignContent": null, "alignItems": null, + "alignSelf": null, "borderRadius": null, + "borderWidths": Object { + "bottom": 0, + "left": 0, + "right": 0, + "top": 0, + }, "clientHeight": 0, "clientWidth": 0, "closestOffsetParentPath": Object { @@ -14804,6 +16413,7 @@ Object { "gap": null, "globalContentBoxForChildren": null, "globalFrameWithTextContent": null, + "gridCellGlobalFrames": null, "hasPositionOffset": false, "hasTransform": false, "htmlElementName": "div", @@ -14815,7 +16425,9 @@ Object { }, "immediateParentProvidesLayout": true, "justifyContent": null, + "justifySelf": null, "layoutSystemForChildren": null, + "layoutSystemForChildrenInherited": false, "margin": Object {}, "naturalHeight": null, "naturalWidth": null, @@ -14824,8 +16436,24 @@ Object { "y": 0, }, "padding": Object {}, + "parentContainerGridProperties": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, + "parentContainerGridPropertiesFromProps": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, "parentFlexDirection": null, "parentFlexGap": 0, + "parentGridCellGlobalFrames": null, + "parentGridFrame": null, "parentHugsOnMainAxis": false, "parentJustifyContent": null, "parentLayoutSystem": "flow", @@ -14924,8 +16552,16 @@ Object { "label": null, "nonRoundedGlobalFrame": null, "specialSizeMeasurements": Object { + "alignContent": null, "alignItems": null, + "alignSelf": null, "borderRadius": null, + "borderWidths": Object { + "bottom": 0, + "left": 0, + "right": 0, + "top": 0, + }, "clientHeight": 0, "clientWidth": 0, "closestOffsetParentPath": Object { @@ -14973,6 +16609,7 @@ Object { "gap": null, "globalContentBoxForChildren": null, "globalFrameWithTextContent": null, + "gridCellGlobalFrames": null, "hasPositionOffset": false, "hasTransform": false, "htmlElementName": "div", @@ -14984,7 +16621,9 @@ Object { }, "immediateParentProvidesLayout": true, "justifyContent": null, + "justifySelf": null, "layoutSystemForChildren": null, + "layoutSystemForChildrenInherited": false, "margin": Object {}, "naturalHeight": null, "naturalWidth": null, @@ -14993,8 +16632,24 @@ Object { "y": 0, }, "padding": Object {}, + "parentContainerGridProperties": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, + "parentContainerGridPropertiesFromProps": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, "parentFlexDirection": null, "parentFlexGap": 0, + "parentGridCellGlobalFrames": null, + "parentGridFrame": null, "parentHugsOnMainAxis": false, "parentJustifyContent": null, "parentLayoutSystem": "flow", @@ -15317,8 +16972,16 @@ Object { "label": null, "nonRoundedGlobalFrame": null, "specialSizeMeasurements": Object { + "alignContent": null, "alignItems": null, + "alignSelf": null, "borderRadius": null, + "borderWidths": Object { + "bottom": 0, + "left": 0, + "right": 0, + "top": 0, + }, "clientHeight": 0, "clientWidth": 0, "closestOffsetParentPath": Object { @@ -15366,6 +17029,7 @@ Object { "gap": null, "globalContentBoxForChildren": null, "globalFrameWithTextContent": null, + "gridCellGlobalFrames": null, "hasPositionOffset": false, "hasTransform": false, "htmlElementName": "div", @@ -15377,7 +17041,9 @@ Object { }, "immediateParentProvidesLayout": true, "justifyContent": null, + "justifySelf": null, "layoutSystemForChildren": null, + "layoutSystemForChildrenInherited": false, "margin": Object {}, "naturalHeight": null, "naturalWidth": null, @@ -15386,8 +17052,24 @@ Object { "y": 0, }, "padding": Object {}, + "parentContainerGridProperties": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, + "parentContainerGridPropertiesFromProps": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, "parentFlexDirection": null, "parentFlexGap": 0, + "parentGridCellGlobalFrames": null, + "parentGridFrame": null, "parentHugsOnMainAxis": false, "parentJustifyContent": null, "parentLayoutSystem": "flow", @@ -15675,8 +17357,16 @@ Object { "label": null, "nonRoundedGlobalFrame": null, "specialSizeMeasurements": Object { + "alignContent": null, "alignItems": null, + "alignSelf": null, "borderRadius": null, + "borderWidths": Object { + "bottom": 0, + "left": 0, + "right": 0, + "top": 0, + }, "clientHeight": 0, "clientWidth": 0, "closestOffsetParentPath": Object { @@ -15724,6 +17414,7 @@ Object { "gap": null, "globalContentBoxForChildren": null, "globalFrameWithTextContent": null, + "gridCellGlobalFrames": null, "hasPositionOffset": false, "hasTransform": false, "htmlElementName": "div", @@ -15735,7 +17426,9 @@ Object { }, "immediateParentProvidesLayout": true, "justifyContent": null, + "justifySelf": null, "layoutSystemForChildren": null, + "layoutSystemForChildrenInherited": false, "margin": Object {}, "naturalHeight": null, "naturalWidth": null, @@ -15744,8 +17437,24 @@ Object { "y": 0, }, "padding": Object {}, + "parentContainerGridProperties": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, + "parentContainerGridPropertiesFromProps": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, "parentFlexDirection": null, "parentFlexGap": 0, + "parentGridCellGlobalFrames": null, + "parentGridFrame": null, "parentHugsOnMainAxis": false, "parentJustifyContent": null, "parentLayoutSystem": "flow", @@ -15892,8 +17601,16 @@ Object { "label": null, "nonRoundedGlobalFrame": null, "specialSizeMeasurements": Object { + "alignContent": null, "alignItems": null, + "alignSelf": null, "borderRadius": null, + "borderWidths": Object { + "bottom": 0, + "left": 0, + "right": 0, + "top": 0, + }, "clientHeight": 0, "clientWidth": 0, "closestOffsetParentPath": Object { @@ -15941,6 +17658,7 @@ Object { "gap": null, "globalContentBoxForChildren": null, "globalFrameWithTextContent": null, + "gridCellGlobalFrames": null, "hasPositionOffset": false, "hasTransform": false, "htmlElementName": "div", @@ -15952,7 +17670,9 @@ Object { }, "immediateParentProvidesLayout": true, "justifyContent": null, + "justifySelf": null, "layoutSystemForChildren": null, + "layoutSystemForChildrenInherited": false, "margin": Object {}, "naturalHeight": null, "naturalWidth": null, @@ -15961,8 +17681,24 @@ Object { "y": 0, }, "padding": Object {}, + "parentContainerGridProperties": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, + "parentContainerGridPropertiesFromProps": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, "parentFlexDirection": null, "parentFlexGap": 0, + "parentGridCellGlobalFrames": null, + "parentGridFrame": null, "parentHugsOnMainAxis": false, "parentJustifyContent": null, "parentLayoutSystem": "flow", @@ -16109,8 +17845,16 @@ Object { "label": null, "nonRoundedGlobalFrame": null, "specialSizeMeasurements": Object { + "alignContent": null, "alignItems": null, + "alignSelf": null, "borderRadius": null, + "borderWidths": Object { + "bottom": 0, + "left": 0, + "right": 0, + "top": 0, + }, "clientHeight": 0, "clientWidth": 0, "closestOffsetParentPath": Object { @@ -16158,6 +17902,7 @@ Object { "gap": null, "globalContentBoxForChildren": null, "globalFrameWithTextContent": null, + "gridCellGlobalFrames": null, "hasPositionOffset": false, "hasTransform": false, "htmlElementName": "div", @@ -16169,7 +17914,9 @@ Object { }, "immediateParentProvidesLayout": true, "justifyContent": null, + "justifySelf": null, "layoutSystemForChildren": null, + "layoutSystemForChildrenInherited": false, "margin": Object {}, "naturalHeight": null, "naturalWidth": null, @@ -16178,8 +17925,24 @@ Object { "y": 0, }, "padding": Object {}, + "parentContainerGridProperties": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, + "parentContainerGridPropertiesFromProps": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, "parentFlexDirection": null, "parentFlexGap": 0, + "parentGridCellGlobalFrames": null, + "parentGridFrame": null, "parentHugsOnMainAxis": false, "parentJustifyContent": null, "parentLayoutSystem": "flow", @@ -16326,8 +18089,16 @@ Object { "label": null, "nonRoundedGlobalFrame": null, "specialSizeMeasurements": Object { + "alignContent": null, "alignItems": null, + "alignSelf": null, "borderRadius": null, + "borderWidths": Object { + "bottom": 0, + "left": 0, + "right": 0, + "top": 0, + }, "clientHeight": 0, "clientWidth": 0, "closestOffsetParentPath": Object { @@ -16375,6 +18146,7 @@ Object { "gap": null, "globalContentBoxForChildren": null, "globalFrameWithTextContent": null, + "gridCellGlobalFrames": null, "hasPositionOffset": false, "hasTransform": false, "htmlElementName": "div", @@ -16386,7 +18158,9 @@ Object { }, "immediateParentProvidesLayout": true, "justifyContent": null, + "justifySelf": null, "layoutSystemForChildren": null, + "layoutSystemForChildrenInherited": false, "margin": Object {}, "naturalHeight": null, "naturalWidth": null, @@ -16395,8 +18169,24 @@ Object { "y": 0, }, "padding": Object {}, + "parentContainerGridProperties": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, + "parentContainerGridPropertiesFromProps": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, "parentFlexDirection": null, "parentFlexGap": 0, + "parentGridCellGlobalFrames": null, + "parentGridFrame": null, "parentHugsOnMainAxis": false, "parentJustifyContent": null, "parentLayoutSystem": "flow", @@ -16429,6 +18219,7 @@ exports[`UiJsxCanvas render does not crash if the metadata scenes are undefined data-path=\\"utopia-storyboard-uid/scene-aaa\\" style=\\" overflow: hidden; + container: scene / inline-size; position: absolute; background-color: var(--utopitheme-emphasizedBackground); box-shadow: 0px 1px 2px 0px var(--utopitheme-shadow90), @@ -16616,8 +18407,16 @@ Object { "label": null, "nonRoundedGlobalFrame": null, "specialSizeMeasurements": Object { + "alignContent": null, "alignItems": null, + "alignSelf": null, "borderRadius": null, + "borderWidths": Object { + "bottom": 0, + "left": 0, + "right": 0, + "top": 0, + }, "clientHeight": 0, "clientWidth": 0, "closestOffsetParentPath": Object { @@ -16665,6 +18464,7 @@ Object { "gap": null, "globalContentBoxForChildren": null, "globalFrameWithTextContent": null, + "gridCellGlobalFrames": null, "hasPositionOffset": false, "hasTransform": false, "htmlElementName": "div", @@ -16676,7 +18476,9 @@ Object { }, "immediateParentProvidesLayout": true, "justifyContent": null, + "justifySelf": null, "layoutSystemForChildren": null, + "layoutSystemForChildrenInherited": false, "margin": Object {}, "naturalHeight": null, "naturalWidth": null, @@ -16685,8 +18487,24 @@ Object { "y": 0, }, "padding": Object {}, + "parentContainerGridProperties": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, + "parentContainerGridPropertiesFromProps": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, "parentFlexDirection": null, "parentFlexGap": 0, + "parentGridCellGlobalFrames": null, + "parentGridFrame": null, "parentHugsOnMainAxis": false, "parentJustifyContent": null, "parentLayoutSystem": "flow", @@ -16785,8 +18603,16 @@ Object { "label": null, "nonRoundedGlobalFrame": null, "specialSizeMeasurements": Object { + "alignContent": null, "alignItems": null, + "alignSelf": null, "borderRadius": null, + "borderWidths": Object { + "bottom": 0, + "left": 0, + "right": 0, + "top": 0, + }, "clientHeight": 0, "clientWidth": 0, "closestOffsetParentPath": Object { @@ -16834,6 +18660,7 @@ Object { "gap": null, "globalContentBoxForChildren": null, "globalFrameWithTextContent": null, + "gridCellGlobalFrames": null, "hasPositionOffset": false, "hasTransform": false, "htmlElementName": "div", @@ -16845,7 +18672,9 @@ Object { }, "immediateParentProvidesLayout": true, "justifyContent": null, + "justifySelf": null, "layoutSystemForChildren": null, + "layoutSystemForChildrenInherited": false, "margin": Object {}, "naturalHeight": null, "naturalWidth": null, @@ -16854,8 +18683,24 @@ Object { "y": 0, }, "padding": Object {}, + "parentContainerGridProperties": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, + "parentContainerGridPropertiesFromProps": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, "parentFlexDirection": null, "parentFlexGap": 0, + "parentGridCellGlobalFrames": null, + "parentGridFrame": null, "parentHugsOnMainAxis": false, "parentJustifyContent": null, "parentLayoutSystem": "flow", @@ -17178,8 +19023,16 @@ Object { "label": null, "nonRoundedGlobalFrame": null, "specialSizeMeasurements": Object { + "alignContent": null, "alignItems": null, + "alignSelf": null, "borderRadius": null, + "borderWidths": Object { + "bottom": 0, + "left": 0, + "right": 0, + "top": 0, + }, "clientHeight": 0, "clientWidth": 0, "closestOffsetParentPath": Object { @@ -17227,6 +19080,7 @@ Object { "gap": null, "globalContentBoxForChildren": null, "globalFrameWithTextContent": null, + "gridCellGlobalFrames": null, "hasPositionOffset": false, "hasTransform": false, "htmlElementName": "div", @@ -17238,7 +19092,9 @@ Object { }, "immediateParentProvidesLayout": true, "justifyContent": null, + "justifySelf": null, "layoutSystemForChildren": null, + "layoutSystemForChildrenInherited": false, "margin": Object {}, "naturalHeight": null, "naturalWidth": null, @@ -17247,8 +19103,24 @@ Object { "y": 0, }, "padding": Object {}, + "parentContainerGridProperties": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, + "parentContainerGridPropertiesFromProps": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, "parentFlexDirection": null, "parentFlexGap": 0, + "parentGridCellGlobalFrames": null, + "parentGridFrame": null, "parentHugsOnMainAxis": false, "parentJustifyContent": null, "parentLayoutSystem": "flow", @@ -17536,8 +19408,16 @@ Object { "label": null, "nonRoundedGlobalFrame": null, "specialSizeMeasurements": Object { + "alignContent": null, "alignItems": null, + "alignSelf": null, "borderRadius": null, + "borderWidths": Object { + "bottom": 0, + "left": 0, + "right": 0, + "top": 0, + }, "clientHeight": 0, "clientWidth": 0, "closestOffsetParentPath": Object { @@ -17585,6 +19465,7 @@ Object { "gap": null, "globalContentBoxForChildren": null, "globalFrameWithTextContent": null, + "gridCellGlobalFrames": null, "hasPositionOffset": false, "hasTransform": false, "htmlElementName": "div", @@ -17596,7 +19477,9 @@ Object { }, "immediateParentProvidesLayout": true, "justifyContent": null, + "justifySelf": null, "layoutSystemForChildren": null, + "layoutSystemForChildrenInherited": false, "margin": Object {}, "naturalHeight": null, "naturalWidth": null, @@ -17605,8 +19488,24 @@ Object { "y": 0, }, "padding": Object {}, + "parentContainerGridProperties": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, + "parentContainerGridPropertiesFromProps": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, "parentFlexDirection": null, "parentFlexGap": 0, + "parentGridCellGlobalFrames": null, + "parentGridFrame": null, "parentHugsOnMainAxis": false, "parentJustifyContent": null, "parentLayoutSystem": "flow", @@ -17753,8 +19652,16 @@ Object { "label": null, "nonRoundedGlobalFrame": null, "specialSizeMeasurements": Object { + "alignContent": null, "alignItems": null, + "alignSelf": null, "borderRadius": null, + "borderWidths": Object { + "bottom": 0, + "left": 0, + "right": 0, + "top": 0, + }, "clientHeight": 0, "clientWidth": 0, "closestOffsetParentPath": Object { @@ -17802,6 +19709,7 @@ Object { "gap": null, "globalContentBoxForChildren": null, "globalFrameWithTextContent": null, + "gridCellGlobalFrames": null, "hasPositionOffset": false, "hasTransform": false, "htmlElementName": "div", @@ -17813,7 +19721,9 @@ Object { }, "immediateParentProvidesLayout": true, "justifyContent": null, + "justifySelf": null, "layoutSystemForChildren": null, + "layoutSystemForChildrenInherited": false, "margin": Object {}, "naturalHeight": null, "naturalWidth": null, @@ -17822,8 +19732,24 @@ Object { "y": 0, }, "padding": Object {}, + "parentContainerGridProperties": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, + "parentContainerGridPropertiesFromProps": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, "parentFlexDirection": null, "parentFlexGap": 0, + "parentGridCellGlobalFrames": null, + "parentGridFrame": null, "parentHugsOnMainAxis": false, "parentJustifyContent": null, "parentLayoutSystem": "flow", @@ -17970,8 +19896,16 @@ Object { "label": null, "nonRoundedGlobalFrame": null, "specialSizeMeasurements": Object { + "alignContent": null, "alignItems": null, + "alignSelf": null, "borderRadius": null, + "borderWidths": Object { + "bottom": 0, + "left": 0, + "right": 0, + "top": 0, + }, "clientHeight": 0, "clientWidth": 0, "closestOffsetParentPath": Object { @@ -18019,6 +19953,7 @@ Object { "gap": null, "globalContentBoxForChildren": null, "globalFrameWithTextContent": null, + "gridCellGlobalFrames": null, "hasPositionOffset": false, "hasTransform": false, "htmlElementName": "div", @@ -18030,7 +19965,9 @@ Object { }, "immediateParentProvidesLayout": true, "justifyContent": null, + "justifySelf": null, "layoutSystemForChildren": null, + "layoutSystemForChildrenInherited": false, "margin": Object {}, "naturalHeight": null, "naturalWidth": null, @@ -18039,8 +19976,24 @@ Object { "y": 0, }, "padding": Object {}, + "parentContainerGridProperties": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, + "parentContainerGridPropertiesFromProps": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, "parentFlexDirection": null, "parentFlexGap": 0, + "parentGridCellGlobalFrames": null, + "parentGridFrame": null, "parentHugsOnMainAxis": false, "parentJustifyContent": null, "parentLayoutSystem": "flow", @@ -18187,8 +20140,16 @@ Object { "label": null, "nonRoundedGlobalFrame": null, "specialSizeMeasurements": Object { + "alignContent": null, "alignItems": null, + "alignSelf": null, "borderRadius": null, + "borderWidths": Object { + "bottom": 0, + "left": 0, + "right": 0, + "top": 0, + }, "clientHeight": 0, "clientWidth": 0, "closestOffsetParentPath": Object { @@ -18236,6 +20197,7 @@ Object { "gap": null, "globalContentBoxForChildren": null, "globalFrameWithTextContent": null, + "gridCellGlobalFrames": null, "hasPositionOffset": false, "hasTransform": false, "htmlElementName": "div", @@ -18247,7 +20209,9 @@ Object { }, "immediateParentProvidesLayout": true, "justifyContent": null, + "justifySelf": null, "layoutSystemForChildren": null, + "layoutSystemForChildrenInherited": false, "margin": Object {}, "naturalHeight": null, "naturalWidth": null, @@ -18256,8 +20220,24 @@ Object { "y": 0, }, "padding": Object {}, + "parentContainerGridProperties": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, + "parentContainerGridPropertiesFromProps": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, "parentFlexDirection": null, "parentFlexGap": 0, + "parentGridCellGlobalFrames": null, + "parentGridFrame": null, "parentHugsOnMainAxis": false, "parentJustifyContent": null, "parentLayoutSystem": "flow", @@ -18290,6 +20270,7 @@ exports[`UiJsxCanvas render function component is available from arbitrary block data-path=\\"utopia-storyboard-uid/scene-aaa\\" style=\\" overflow: hidden; + container: scene / inline-size; position: absolute; background-color: var(--utopitheme-emphasizedBackground); box-shadow: 0px 1px 2px 0px var(--utopitheme-shadow90), @@ -18471,8 +20452,16 @@ Object { "label": null, "nonRoundedGlobalFrame": null, "specialSizeMeasurements": Object { + "alignContent": null, "alignItems": null, + "alignSelf": null, "borderRadius": null, + "borderWidths": Object { + "bottom": 0, + "left": 0, + "right": 0, + "top": 0, + }, "clientHeight": 0, "clientWidth": 0, "closestOffsetParentPath": Object { @@ -18520,6 +20509,7 @@ Object { "gap": null, "globalContentBoxForChildren": null, "globalFrameWithTextContent": null, + "gridCellGlobalFrames": null, "hasPositionOffset": false, "hasTransform": false, "htmlElementName": "div", @@ -18531,7 +20521,9 @@ Object { }, "immediateParentProvidesLayout": true, "justifyContent": null, + "justifySelf": null, "layoutSystemForChildren": null, + "layoutSystemForChildrenInherited": false, "margin": Object {}, "naturalHeight": null, "naturalWidth": null, @@ -18540,8 +20532,24 @@ Object { "y": 0, }, "padding": Object {}, + "parentContainerGridProperties": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, + "parentContainerGridPropertiesFromProps": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, "parentFlexDirection": null, "parentFlexGap": 0, + "parentGridCellGlobalFrames": null, + "parentGridFrame": null, "parentHugsOnMainAxis": false, "parentJustifyContent": null, "parentLayoutSystem": "flow", @@ -18640,8 +20648,16 @@ Object { "label": null, "nonRoundedGlobalFrame": null, "specialSizeMeasurements": Object { + "alignContent": null, "alignItems": null, + "alignSelf": null, "borderRadius": null, + "borderWidths": Object { + "bottom": 0, + "left": 0, + "right": 0, + "top": 0, + }, "clientHeight": 0, "clientWidth": 0, "closestOffsetParentPath": Object { @@ -18689,6 +20705,7 @@ Object { "gap": null, "globalContentBoxForChildren": null, "globalFrameWithTextContent": null, + "gridCellGlobalFrames": null, "hasPositionOffset": false, "hasTransform": false, "htmlElementName": "div", @@ -18700,7 +20717,9 @@ Object { }, "immediateParentProvidesLayout": true, "justifyContent": null, + "justifySelf": null, "layoutSystemForChildren": null, + "layoutSystemForChildrenInherited": false, "margin": Object {}, "naturalHeight": null, "naturalWidth": null, @@ -18709,8 +20728,24 @@ Object { "y": 0, }, "padding": Object {}, + "parentContainerGridProperties": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, + "parentContainerGridPropertiesFromProps": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, "parentFlexDirection": null, "parentFlexGap": 0, + "parentGridCellGlobalFrames": null, + "parentGridFrame": null, "parentHugsOnMainAxis": false, "parentJustifyContent": null, "parentLayoutSystem": "flow", @@ -18850,8 +20885,16 @@ Object { "label": null, "nonRoundedGlobalFrame": null, "specialSizeMeasurements": Object { + "alignContent": null, "alignItems": null, + "alignSelf": null, "borderRadius": null, + "borderWidths": Object { + "bottom": 0, + "left": 0, + "right": 0, + "top": 0, + }, "clientHeight": 0, "clientWidth": 0, "closestOffsetParentPath": Object { @@ -18899,6 +20942,7 @@ Object { "gap": null, "globalContentBoxForChildren": null, "globalFrameWithTextContent": null, + "gridCellGlobalFrames": null, "hasPositionOffset": false, "hasTransform": false, "htmlElementName": "div", @@ -18910,7 +20954,9 @@ Object { }, "immediateParentProvidesLayout": true, "justifyContent": null, + "justifySelf": null, "layoutSystemForChildren": null, + "layoutSystemForChildrenInherited": false, "margin": Object {}, "naturalHeight": null, "naturalWidth": null, @@ -18919,8 +20965,24 @@ Object { "y": 0, }, "padding": Object {}, + "parentContainerGridProperties": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, + "parentContainerGridPropertiesFromProps": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, "parentFlexDirection": null, "parentFlexGap": 0, + "parentGridCellGlobalFrames": null, + "parentGridFrame": null, "parentHugsOnMainAxis": false, "parentJustifyContent": null, "parentLayoutSystem": "flow", @@ -19000,8 +21062,16 @@ Object { "label": null, "nonRoundedGlobalFrame": null, "specialSizeMeasurements": Object { + "alignContent": null, "alignItems": null, + "alignSelf": null, "borderRadius": null, + "borderWidths": Object { + "bottom": 0, + "left": 0, + "right": 0, + "top": 0, + }, "clientHeight": 0, "clientWidth": 0, "closestOffsetParentPath": Object { @@ -19049,6 +21119,7 @@ Object { "gap": null, "globalContentBoxForChildren": null, "globalFrameWithTextContent": null, + "gridCellGlobalFrames": null, "hasPositionOffset": false, "hasTransform": false, "htmlElementName": "div", @@ -19060,7 +21131,9 @@ Object { }, "immediateParentProvidesLayout": true, "justifyContent": null, + "justifySelf": null, "layoutSystemForChildren": null, + "layoutSystemForChildrenInherited": false, "margin": Object {}, "naturalHeight": null, "naturalWidth": null, @@ -19069,8 +21142,24 @@ Object { "y": 0, }, "padding": Object {}, + "parentContainerGridProperties": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, + "parentContainerGridPropertiesFromProps": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, "parentFlexDirection": null, "parentFlexGap": 0, + "parentGridCellGlobalFrames": null, + "parentGridFrame": null, "parentHugsOnMainAxis": false, "parentJustifyContent": null, "parentLayoutSystem": "flow", @@ -19150,8 +21239,16 @@ Object { "label": null, "nonRoundedGlobalFrame": null, "specialSizeMeasurements": Object { + "alignContent": null, "alignItems": null, + "alignSelf": null, "borderRadius": null, + "borderWidths": Object { + "bottom": 0, + "left": 0, + "right": 0, + "top": 0, + }, "clientHeight": 0, "clientWidth": 0, "closestOffsetParentPath": Object { @@ -19199,6 +21296,7 @@ Object { "gap": null, "globalContentBoxForChildren": null, "globalFrameWithTextContent": null, + "gridCellGlobalFrames": null, "hasPositionOffset": false, "hasTransform": false, "htmlElementName": "div", @@ -19210,7 +21308,9 @@ Object { }, "immediateParentProvidesLayout": true, "justifyContent": null, + "justifySelf": null, "layoutSystemForChildren": null, + "layoutSystemForChildrenInherited": false, "margin": Object {}, "naturalHeight": null, "naturalWidth": null, @@ -19219,8 +21319,24 @@ Object { "y": 0, }, "padding": Object {}, + "parentContainerGridProperties": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, + "parentContainerGridPropertiesFromProps": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, "parentFlexDirection": null, "parentFlexGap": 0, + "parentGridCellGlobalFrames": null, + "parentGridFrame": null, "parentHugsOnMainAxis": false, "parentJustifyContent": null, "parentLayoutSystem": "flow", @@ -19253,6 +21369,7 @@ exports[`UiJsxCanvas render function component works inside a map 1`] = ` data-path=\\"utopia-storyboard-uid/scene-aaa\\" style=\\" overflow: hidden; + container: scene / inline-size; position: absolute; background-color: var(--utopitheme-emphasizedBackground); box-shadow: 0px 1px 2px 0px var(--utopitheme-shadow90), @@ -19434,8 +21551,16 @@ Object { "label": null, "nonRoundedGlobalFrame": null, "specialSizeMeasurements": Object { + "alignContent": null, "alignItems": null, + "alignSelf": null, "borderRadius": null, + "borderWidths": Object { + "bottom": 0, + "left": 0, + "right": 0, + "top": 0, + }, "clientHeight": 0, "clientWidth": 0, "closestOffsetParentPath": Object { @@ -19483,6 +21608,7 @@ Object { "gap": null, "globalContentBoxForChildren": null, "globalFrameWithTextContent": null, + "gridCellGlobalFrames": null, "hasPositionOffset": false, "hasTransform": false, "htmlElementName": "div", @@ -19494,7 +21620,9 @@ Object { }, "immediateParentProvidesLayout": true, "justifyContent": null, + "justifySelf": null, "layoutSystemForChildren": null, + "layoutSystemForChildrenInherited": false, "margin": Object {}, "naturalHeight": null, "naturalWidth": null, @@ -19503,8 +21631,24 @@ Object { "y": 0, }, "padding": Object {}, + "parentContainerGridProperties": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, + "parentContainerGridPropertiesFromProps": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, "parentFlexDirection": null, "parentFlexGap": 0, + "parentGridCellGlobalFrames": null, + "parentGridFrame": null, "parentHugsOnMainAxis": false, "parentJustifyContent": null, "parentLayoutSystem": "flow", @@ -19603,8 +21747,16 @@ Object { "label": null, "nonRoundedGlobalFrame": null, "specialSizeMeasurements": Object { + "alignContent": null, "alignItems": null, + "alignSelf": null, "borderRadius": null, + "borderWidths": Object { + "bottom": 0, + "left": 0, + "right": 0, + "top": 0, + }, "clientHeight": 0, "clientWidth": 0, "closestOffsetParentPath": Object { @@ -19652,6 +21804,7 @@ Object { "gap": null, "globalContentBoxForChildren": null, "globalFrameWithTextContent": null, + "gridCellGlobalFrames": null, "hasPositionOffset": false, "hasTransform": false, "htmlElementName": "div", @@ -19663,7 +21816,9 @@ Object { }, "immediateParentProvidesLayout": true, "justifyContent": null, + "justifySelf": null, "layoutSystemForChildren": null, + "layoutSystemForChildrenInherited": false, "margin": Object {}, "naturalHeight": null, "naturalWidth": null, @@ -19672,8 +21827,24 @@ Object { "y": 0, }, "padding": Object {}, + "parentContainerGridProperties": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, + "parentContainerGridPropertiesFromProps": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, "parentFlexDirection": null, "parentFlexGap": 0, + "parentGridCellGlobalFrames": null, + "parentGridFrame": null, "parentHugsOnMainAxis": false, "parentJustifyContent": null, "parentLayoutSystem": "flow", @@ -19918,8 +22089,16 @@ Object { "label": null, "nonRoundedGlobalFrame": null, "specialSizeMeasurements": Object { + "alignContent": null, "alignItems": null, + "alignSelf": null, "borderRadius": null, + "borderWidths": Object { + "bottom": 0, + "left": 0, + "right": 0, + "top": 0, + }, "clientHeight": 0, "clientWidth": 0, "closestOffsetParentPath": Object { @@ -19967,6 +22146,7 @@ Object { "gap": null, "globalContentBoxForChildren": null, "globalFrameWithTextContent": null, + "gridCellGlobalFrames": null, "hasPositionOffset": false, "hasTransform": false, "htmlElementName": "div", @@ -19978,7 +22158,9 @@ Object { }, "immediateParentProvidesLayout": true, "justifyContent": null, + "justifySelf": null, "layoutSystemForChildren": null, + "layoutSystemForChildrenInherited": false, "margin": Object {}, "naturalHeight": null, "naturalWidth": null, @@ -19987,8 +22169,24 @@ Object { "y": 0, }, "padding": Object {}, + "parentContainerGridProperties": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, + "parentContainerGridPropertiesFromProps": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, "parentFlexDirection": null, "parentFlexGap": 0, + "parentGridCellGlobalFrames": null, + "parentGridFrame": null, "parentHugsOnMainAxis": false, "parentJustifyContent": null, "parentLayoutSystem": "flow", @@ -20199,8 +22397,16 @@ Object { "label": null, "nonRoundedGlobalFrame": null, "specialSizeMeasurements": Object { + "alignContent": null, "alignItems": null, + "alignSelf": null, "borderRadius": null, + "borderWidths": Object { + "bottom": 0, + "left": 0, + "right": 0, + "top": 0, + }, "clientHeight": 0, "clientWidth": 0, "closestOffsetParentPath": Object { @@ -20248,6 +22454,7 @@ Object { "gap": null, "globalContentBoxForChildren": null, "globalFrameWithTextContent": null, + "gridCellGlobalFrames": null, "hasPositionOffset": false, "hasTransform": false, "htmlElementName": "div", @@ -20259,7 +22466,9 @@ Object { }, "immediateParentProvidesLayout": true, "justifyContent": null, + "justifySelf": null, "layoutSystemForChildren": null, + "layoutSystemForChildrenInherited": false, "margin": Object {}, "naturalHeight": null, "naturalWidth": null, @@ -20268,8 +22477,24 @@ Object { "y": 0, }, "padding": Object {}, + "parentContainerGridProperties": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, + "parentContainerGridPropertiesFromProps": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, "parentFlexDirection": null, "parentFlexGap": 0, + "parentGridCellGlobalFrames": null, + "parentGridFrame": null, "parentHugsOnMainAxis": false, "parentJustifyContent": null, "parentLayoutSystem": "flow", @@ -20350,8 +22575,16 @@ Object { "label": null, "nonRoundedGlobalFrame": null, "specialSizeMeasurements": Object { + "alignContent": null, "alignItems": null, + "alignSelf": null, "borderRadius": null, + "borderWidths": Object { + "bottom": 0, + "left": 0, + "right": 0, + "top": 0, + }, "clientHeight": 0, "clientWidth": 0, "closestOffsetParentPath": Object { @@ -20399,6 +22632,7 @@ Object { "gap": null, "globalContentBoxForChildren": null, "globalFrameWithTextContent": null, + "gridCellGlobalFrames": null, "hasPositionOffset": false, "hasTransform": false, "htmlElementName": "div", @@ -20410,7 +22644,9 @@ Object { }, "immediateParentProvidesLayout": true, "justifyContent": null, + "justifySelf": null, "layoutSystemForChildren": null, + "layoutSystemForChildrenInherited": false, "margin": Object {}, "naturalHeight": null, "naturalWidth": null, @@ -20419,8 +22655,24 @@ Object { "y": 0, }, "padding": Object {}, + "parentContainerGridProperties": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, + "parentContainerGridPropertiesFromProps": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, "parentFlexDirection": null, "parentFlexGap": 0, + "parentGridCellGlobalFrames": null, + "parentGridFrame": null, "parentHugsOnMainAxis": false, "parentJustifyContent": null, "parentLayoutSystem": "flow", @@ -20501,8 +22753,16 @@ Object { "label": null, "nonRoundedGlobalFrame": null, "specialSizeMeasurements": Object { + "alignContent": null, "alignItems": null, + "alignSelf": null, "borderRadius": null, + "borderWidths": Object { + "bottom": 0, + "left": 0, + "right": 0, + "top": 0, + }, "clientHeight": 0, "clientWidth": 0, "closestOffsetParentPath": Object { @@ -20550,6 +22810,7 @@ Object { "gap": null, "globalContentBoxForChildren": null, "globalFrameWithTextContent": null, + "gridCellGlobalFrames": null, "hasPositionOffset": false, "hasTransform": false, "htmlElementName": "div", @@ -20561,7 +22822,9 @@ Object { }, "immediateParentProvidesLayout": true, "justifyContent": null, + "justifySelf": null, "layoutSystemForChildren": null, + "layoutSystemForChildrenInherited": false, "margin": Object {}, "naturalHeight": null, "naturalWidth": null, @@ -20570,8 +22833,24 @@ Object { "y": 0, }, "padding": Object {}, + "parentContainerGridProperties": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, + "parentContainerGridPropertiesFromProps": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, "parentFlexDirection": null, "parentFlexGap": 0, + "parentGridCellGlobalFrames": null, + "parentGridFrame": null, "parentHugsOnMainAxis": false, "parentJustifyContent": null, "parentLayoutSystem": "flow", @@ -20604,6 +22883,7 @@ exports[`UiJsxCanvas render handles a component that destructures its props obje data-path=\\"utopia-storyboard-uid/scene-aaa\\" style=\\" overflow: hidden; + container: scene / inline-size; position: absolute; background-color: var(--utopitheme-emphasizedBackground); box-shadow: 0px 1px 2px 0px var(--utopitheme-shadow90), @@ -20801,8 +23081,16 @@ Object { "label": null, "nonRoundedGlobalFrame": null, "specialSizeMeasurements": Object { + "alignContent": null, "alignItems": null, + "alignSelf": null, "borderRadius": null, + "borderWidths": Object { + "bottom": 0, + "left": 0, + "right": 0, + "top": 0, + }, "clientHeight": 0, "clientWidth": 0, "closestOffsetParentPath": Object { @@ -20850,6 +23138,7 @@ Object { "gap": null, "globalContentBoxForChildren": null, "globalFrameWithTextContent": null, + "gridCellGlobalFrames": null, "hasPositionOffset": false, "hasTransform": false, "htmlElementName": "div", @@ -20861,7 +23150,9 @@ Object { }, "immediateParentProvidesLayout": true, "justifyContent": null, + "justifySelf": null, "layoutSystemForChildren": null, + "layoutSystemForChildrenInherited": false, "margin": Object {}, "naturalHeight": null, "naturalWidth": null, @@ -20870,8 +23161,24 @@ Object { "y": 0, }, "padding": Object {}, + "parentContainerGridProperties": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, + "parentContainerGridPropertiesFromProps": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, "parentFlexDirection": null, "parentFlexGap": 0, + "parentGridCellGlobalFrames": null, + "parentGridFrame": null, "parentHugsOnMainAxis": false, "parentJustifyContent": null, "parentLayoutSystem": "flow", @@ -20985,8 +23292,16 @@ Object { "label": null, "nonRoundedGlobalFrame": null, "specialSizeMeasurements": Object { + "alignContent": null, "alignItems": null, + "alignSelf": null, "borderRadius": null, + "borderWidths": Object { + "bottom": 0, + "left": 0, + "right": 0, + "top": 0, + }, "clientHeight": 0, "clientWidth": 0, "closestOffsetParentPath": Object { @@ -21034,6 +23349,7 @@ Object { "gap": null, "globalContentBoxForChildren": null, "globalFrameWithTextContent": null, + "gridCellGlobalFrames": null, "hasPositionOffset": false, "hasTransform": false, "htmlElementName": "div", @@ -21045,7 +23361,9 @@ Object { }, "immediateParentProvidesLayout": true, "justifyContent": null, + "justifySelf": null, "layoutSystemForChildren": null, + "layoutSystemForChildrenInherited": false, "margin": Object {}, "naturalHeight": null, "naturalWidth": null, @@ -21054,8 +23372,24 @@ Object { "y": 0, }, "padding": Object {}, + "parentContainerGridProperties": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, + "parentContainerGridPropertiesFromProps": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, "parentFlexDirection": null, "parentFlexGap": 0, + "parentGridCellGlobalFrames": null, + "parentGridFrame": null, "parentHugsOnMainAxis": false, "parentJustifyContent": null, "parentLayoutSystem": "flow", @@ -21448,8 +23782,16 @@ Object { "label": null, "nonRoundedGlobalFrame": null, "specialSizeMeasurements": Object { + "alignContent": null, "alignItems": null, + "alignSelf": null, "borderRadius": null, + "borderWidths": Object { + "bottom": 0, + "left": 0, + "right": 0, + "top": 0, + }, "clientHeight": 0, "clientWidth": 0, "closestOffsetParentPath": Object { @@ -21497,6 +23839,7 @@ Object { "gap": null, "globalContentBoxForChildren": null, "globalFrameWithTextContent": null, + "gridCellGlobalFrames": null, "hasPositionOffset": false, "hasTransform": false, "htmlElementName": "div", @@ -21508,7 +23851,9 @@ Object { }, "immediateParentProvidesLayout": true, "justifyContent": null, + "justifySelf": null, "layoutSystemForChildren": null, + "layoutSystemForChildrenInherited": false, "margin": Object {}, "naturalHeight": null, "naturalWidth": null, @@ -21517,8 +23862,24 @@ Object { "y": 0, }, "padding": Object {}, + "parentContainerGridProperties": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, + "parentContainerGridPropertiesFromProps": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, "parentFlexDirection": null, "parentFlexGap": 0, + "parentGridCellGlobalFrames": null, + "parentGridFrame": null, "parentHugsOnMainAxis": false, "parentJustifyContent": null, "parentLayoutSystem": "flow", @@ -21730,8 +24091,16 @@ Object { "label": null, "nonRoundedGlobalFrame": null, "specialSizeMeasurements": Object { + "alignContent": null, "alignItems": null, + "alignSelf": null, "borderRadius": null, + "borderWidths": Object { + "bottom": 0, + "left": 0, + "right": 0, + "top": 0, + }, "clientHeight": 0, "clientWidth": 0, "closestOffsetParentPath": Object { @@ -21779,6 +24148,7 @@ Object { "gap": null, "globalContentBoxForChildren": null, "globalFrameWithTextContent": null, + "gridCellGlobalFrames": null, "hasPositionOffset": false, "hasTransform": false, "htmlElementName": "div", @@ -21790,7 +24160,9 @@ Object { }, "immediateParentProvidesLayout": true, "justifyContent": null, + "justifySelf": null, "layoutSystemForChildren": null, + "layoutSystemForChildrenInherited": false, "margin": Object {}, "naturalHeight": null, "naturalWidth": null, @@ -21799,8 +24171,24 @@ Object { "y": 0, }, "padding": Object {}, + "parentContainerGridProperties": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, + "parentContainerGridPropertiesFromProps": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, "parentFlexDirection": null, "parentFlexGap": 0, + "parentGridCellGlobalFrames": null, + "parentGridFrame": null, "parentHugsOnMainAxis": false, "parentJustifyContent": null, "parentLayoutSystem": "flow", @@ -21833,6 +24221,7 @@ exports[`UiJsxCanvas render handles a component that renames its props object 1` data-path=\\"utopia-storyboard-uid/scene-aaa\\" style=\\" overflow: hidden; + container: scene / inline-size; position: absolute; background-color: var(--utopitheme-emphasizedBackground); box-shadow: 0px 1px 2px 0px var(--utopitheme-shadow90), @@ -22029,8 +24418,16 @@ Object { "label": null, "nonRoundedGlobalFrame": null, "specialSizeMeasurements": Object { + "alignContent": null, "alignItems": null, + "alignSelf": null, "borderRadius": null, + "borderWidths": Object { + "bottom": 0, + "left": 0, + "right": 0, + "top": 0, + }, "clientHeight": 0, "clientWidth": 0, "closestOffsetParentPath": Object { @@ -22078,6 +24475,7 @@ Object { "gap": null, "globalContentBoxForChildren": null, "globalFrameWithTextContent": null, + "gridCellGlobalFrames": null, "hasPositionOffset": false, "hasTransform": false, "htmlElementName": "div", @@ -22089,7 +24487,9 @@ Object { }, "immediateParentProvidesLayout": true, "justifyContent": null, + "justifySelf": null, "layoutSystemForChildren": null, + "layoutSystemForChildrenInherited": false, "margin": Object {}, "naturalHeight": null, "naturalWidth": null, @@ -22098,8 +24498,24 @@ Object { "y": 0, }, "padding": Object {}, + "parentContainerGridProperties": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, + "parentContainerGridPropertiesFromProps": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, "parentFlexDirection": null, "parentFlexGap": 0, + "parentGridCellGlobalFrames": null, + "parentGridFrame": null, "parentHugsOnMainAxis": false, "parentJustifyContent": null, "parentLayoutSystem": "flow", @@ -22213,8 +24629,16 @@ Object { "label": null, "nonRoundedGlobalFrame": null, "specialSizeMeasurements": Object { + "alignContent": null, "alignItems": null, + "alignSelf": null, "borderRadius": null, + "borderWidths": Object { + "bottom": 0, + "left": 0, + "right": 0, + "top": 0, + }, "clientHeight": 0, "clientWidth": 0, "closestOffsetParentPath": Object { @@ -22262,6 +24686,7 @@ Object { "gap": null, "globalContentBoxForChildren": null, "globalFrameWithTextContent": null, + "gridCellGlobalFrames": null, "hasPositionOffset": false, "hasTransform": false, "htmlElementName": "div", @@ -22273,7 +24698,9 @@ Object { }, "immediateParentProvidesLayout": true, "justifyContent": null, + "justifySelf": null, "layoutSystemForChildren": null, + "layoutSystemForChildrenInherited": false, "margin": Object {}, "naturalHeight": null, "naturalWidth": null, @@ -22282,8 +24709,24 @@ Object { "y": 0, }, "padding": Object {}, + "parentContainerGridProperties": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, + "parentContainerGridPropertiesFromProps": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, "parentFlexDirection": null, "parentFlexGap": 0, + "parentGridCellGlobalFrames": null, + "parentGridFrame": null, "parentHugsOnMainAxis": false, "parentJustifyContent": null, "parentLayoutSystem": "flow", @@ -22676,8 +25119,16 @@ Object { "label": null, "nonRoundedGlobalFrame": null, "specialSizeMeasurements": Object { + "alignContent": null, "alignItems": null, + "alignSelf": null, "borderRadius": null, + "borderWidths": Object { + "bottom": 0, + "left": 0, + "right": 0, + "top": 0, + }, "clientHeight": 0, "clientWidth": 0, "closestOffsetParentPath": Object { @@ -22725,6 +25176,7 @@ Object { "gap": null, "globalContentBoxForChildren": null, "globalFrameWithTextContent": null, + "gridCellGlobalFrames": null, "hasPositionOffset": false, "hasTransform": false, "htmlElementName": "div", @@ -22736,7 +25188,9 @@ Object { }, "immediateParentProvidesLayout": true, "justifyContent": null, + "justifySelf": null, "layoutSystemForChildren": null, + "layoutSystemForChildrenInherited": false, "margin": Object {}, "naturalHeight": null, "naturalWidth": null, @@ -22745,8 +25199,24 @@ Object { "y": 0, }, "padding": Object {}, + "parentContainerGridProperties": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, + "parentContainerGridPropertiesFromProps": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, "parentFlexDirection": null, "parentFlexGap": 0, + "parentGridCellGlobalFrames": null, + "parentGridFrame": null, "parentHugsOnMainAxis": false, "parentJustifyContent": null, "parentLayoutSystem": "flow", @@ -22958,8 +25428,16 @@ Object { "label": null, "nonRoundedGlobalFrame": null, "specialSizeMeasurements": Object { + "alignContent": null, "alignItems": null, + "alignSelf": null, "borderRadius": null, + "borderWidths": Object { + "bottom": 0, + "left": 0, + "right": 0, + "top": 0, + }, "clientHeight": 0, "clientWidth": 0, "closestOffsetParentPath": Object { @@ -23007,6 +25485,7 @@ Object { "gap": null, "globalContentBoxForChildren": null, "globalFrameWithTextContent": null, + "gridCellGlobalFrames": null, "hasPositionOffset": false, "hasTransform": false, "htmlElementName": "div", @@ -23018,7 +25497,9 @@ Object { }, "immediateParentProvidesLayout": true, "justifyContent": null, + "justifySelf": null, "layoutSystemForChildren": null, + "layoutSystemForChildrenInherited": false, "margin": Object {}, "naturalHeight": null, "naturalWidth": null, @@ -23027,8 +25508,24 @@ Object { "y": 0, }, "padding": Object {}, + "parentContainerGridProperties": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, + "parentContainerGridPropertiesFromProps": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, "parentFlexDirection": null, "parentFlexGap": 0, + "parentGridCellGlobalFrames": null, + "parentGridFrame": null, "parentHugsOnMainAxis": false, "parentJustifyContent": null, "parentLayoutSystem": "flow", @@ -23061,6 +25558,7 @@ exports[`UiJsxCanvas render handles a component with a props object written by s data-path=\\"utopia-storyboard-uid/scene-aaa\\" style=\\" overflow: hidden; + container: scene / inline-size; position: absolute; background-color: var(--utopitheme-emphasizedBackground); box-shadow: 0px 1px 2px 0px var(--utopitheme-shadow90), @@ -23258,8 +25756,16 @@ Object { "label": null, "nonRoundedGlobalFrame": null, "specialSizeMeasurements": Object { + "alignContent": null, "alignItems": null, + "alignSelf": null, "borderRadius": null, + "borderWidths": Object { + "bottom": 0, + "left": 0, + "right": 0, + "top": 0, + }, "clientHeight": 0, "clientWidth": 0, "closestOffsetParentPath": Object { @@ -23307,6 +25813,7 @@ Object { "gap": null, "globalContentBoxForChildren": null, "globalFrameWithTextContent": null, + "gridCellGlobalFrames": null, "hasPositionOffset": false, "hasTransform": false, "htmlElementName": "div", @@ -23318,7 +25825,9 @@ Object { }, "immediateParentProvidesLayout": true, "justifyContent": null, + "justifySelf": null, "layoutSystemForChildren": null, + "layoutSystemForChildrenInherited": false, "margin": Object {}, "naturalHeight": null, "naturalWidth": null, @@ -23327,8 +25836,24 @@ Object { "y": 0, }, "padding": Object {}, + "parentContainerGridProperties": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, + "parentContainerGridPropertiesFromProps": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, "parentFlexDirection": null, "parentFlexGap": 0, + "parentGridCellGlobalFrames": null, + "parentGridFrame": null, "parentHugsOnMainAxis": false, "parentJustifyContent": null, "parentLayoutSystem": "flow", @@ -23442,8 +25967,16 @@ Object { "label": null, "nonRoundedGlobalFrame": null, "specialSizeMeasurements": Object { + "alignContent": null, "alignItems": null, + "alignSelf": null, "borderRadius": null, + "borderWidths": Object { + "bottom": 0, + "left": 0, + "right": 0, + "top": 0, + }, "clientHeight": 0, "clientWidth": 0, "closestOffsetParentPath": Object { @@ -23491,6 +26024,7 @@ Object { "gap": null, "globalContentBoxForChildren": null, "globalFrameWithTextContent": null, + "gridCellGlobalFrames": null, "hasPositionOffset": false, "hasTransform": false, "htmlElementName": "div", @@ -23502,7 +26036,9 @@ Object { }, "immediateParentProvidesLayout": true, "justifyContent": null, + "justifySelf": null, "layoutSystemForChildren": null, + "layoutSystemForChildrenInherited": false, "margin": Object {}, "naturalHeight": null, "naturalWidth": null, @@ -23511,8 +26047,24 @@ Object { "y": 0, }, "padding": Object {}, + "parentContainerGridProperties": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, + "parentContainerGridPropertiesFromProps": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, "parentFlexDirection": null, "parentFlexGap": 0, + "parentGridCellGlobalFrames": null, + "parentGridFrame": null, "parentHugsOnMainAxis": false, "parentJustifyContent": null, "parentLayoutSystem": "flow", @@ -23956,8 +26508,16 @@ Object { "label": null, "nonRoundedGlobalFrame": null, "specialSizeMeasurements": Object { + "alignContent": null, "alignItems": null, + "alignSelf": null, "borderRadius": null, + "borderWidths": Object { + "bottom": 0, + "left": 0, + "right": 0, + "top": 0, + }, "clientHeight": 0, "clientWidth": 0, "closestOffsetParentPath": Object { @@ -24005,6 +26565,7 @@ Object { "gap": null, "globalContentBoxForChildren": null, "globalFrameWithTextContent": null, + "gridCellGlobalFrames": null, "hasPositionOffset": false, "hasTransform": false, "htmlElementName": "div", @@ -24016,7 +26577,9 @@ Object { }, "immediateParentProvidesLayout": true, "justifyContent": null, + "justifySelf": null, "layoutSystemForChildren": null, + "layoutSystemForChildrenInherited": false, "margin": Object {}, "naturalHeight": null, "naturalWidth": null, @@ -24025,8 +26588,24 @@ Object { "y": 0, }, "padding": Object {}, + "parentContainerGridProperties": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, + "parentContainerGridPropertiesFromProps": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, "parentFlexDirection": null, "parentFlexGap": 0, + "parentGridCellGlobalFrames": null, + "parentGridFrame": null, "parentHugsOnMainAxis": false, "parentJustifyContent": null, "parentLayoutSystem": "flow", @@ -24289,8 +26868,16 @@ Object { "label": null, "nonRoundedGlobalFrame": null, "specialSizeMeasurements": Object { + "alignContent": null, "alignItems": null, + "alignSelf": null, "borderRadius": null, + "borderWidths": Object { + "bottom": 0, + "left": 0, + "right": 0, + "top": 0, + }, "clientHeight": 0, "clientWidth": 0, "closestOffsetParentPath": Object { @@ -24338,6 +26925,7 @@ Object { "gap": null, "globalContentBoxForChildren": null, "globalFrameWithTextContent": null, + "gridCellGlobalFrames": null, "hasPositionOffset": false, "hasTransform": false, "htmlElementName": "div", @@ -24349,7 +26937,9 @@ Object { }, "immediateParentProvidesLayout": true, "justifyContent": null, + "justifySelf": null, "layoutSystemForChildren": null, + "layoutSystemForChildrenInherited": false, "margin": Object {}, "naturalHeight": null, "naturalWidth": null, @@ -24358,8 +26948,24 @@ Object { "y": 0, }, "padding": Object {}, + "parentContainerGridProperties": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, + "parentContainerGridPropertiesFromProps": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, "parentFlexDirection": null, "parentFlexGap": 0, + "parentGridCellGlobalFrames": null, + "parentGridFrame": null, "parentHugsOnMainAxis": false, "parentJustifyContent": null, "parentLayoutSystem": "flow", @@ -24392,6 +26998,7 @@ exports[`UiJsxCanvas render handles a component with a props object written by s data-path=\\"utopia-storyboard-uid/scene-aaa\\" style=\\" overflow: hidden; + container: scene / inline-size; position: absolute; background-color: var(--utopitheme-emphasizedBackground); box-shadow: 0px 1px 2px 0px var(--utopitheme-shadow90), @@ -24589,8 +27196,16 @@ Object { "label": null, "nonRoundedGlobalFrame": null, "specialSizeMeasurements": Object { + "alignContent": null, "alignItems": null, + "alignSelf": null, "borderRadius": null, + "borderWidths": Object { + "bottom": 0, + "left": 0, + "right": 0, + "top": 0, + }, "clientHeight": 0, "clientWidth": 0, "closestOffsetParentPath": Object { @@ -24638,6 +27253,7 @@ Object { "gap": null, "globalContentBoxForChildren": null, "globalFrameWithTextContent": null, + "gridCellGlobalFrames": null, "hasPositionOffset": false, "hasTransform": false, "htmlElementName": "div", @@ -24649,7 +27265,9 @@ Object { }, "immediateParentProvidesLayout": true, "justifyContent": null, + "justifySelf": null, "layoutSystemForChildren": null, + "layoutSystemForChildrenInherited": false, "margin": Object {}, "naturalHeight": null, "naturalWidth": null, @@ -24658,8 +27276,24 @@ Object { "y": 0, }, "padding": Object {}, + "parentContainerGridProperties": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, + "parentContainerGridPropertiesFromProps": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, "parentFlexDirection": null, "parentFlexGap": 0, + "parentGridCellGlobalFrames": null, + "parentGridFrame": null, "parentHugsOnMainAxis": false, "parentJustifyContent": null, "parentLayoutSystem": "flow", @@ -24773,8 +27407,16 @@ Object { "label": null, "nonRoundedGlobalFrame": null, "specialSizeMeasurements": Object { + "alignContent": null, "alignItems": null, + "alignSelf": null, "borderRadius": null, + "borderWidths": Object { + "bottom": 0, + "left": 0, + "right": 0, + "top": 0, + }, "clientHeight": 0, "clientWidth": 0, "closestOffsetParentPath": Object { @@ -24822,6 +27464,7 @@ Object { "gap": null, "globalContentBoxForChildren": null, "globalFrameWithTextContent": null, + "gridCellGlobalFrames": null, "hasPositionOffset": false, "hasTransform": false, "htmlElementName": "div", @@ -24833,7 +27476,9 @@ Object { }, "immediateParentProvidesLayout": true, "justifyContent": null, + "justifySelf": null, "layoutSystemForChildren": null, + "layoutSystemForChildrenInherited": false, "margin": Object {}, "naturalHeight": null, "naturalWidth": null, @@ -24842,8 +27487,24 @@ Object { "y": 0, }, "padding": Object {}, + "parentContainerGridProperties": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, + "parentContainerGridPropertiesFromProps": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, "parentFlexDirection": null, "parentFlexGap": 0, + "parentGridCellGlobalFrames": null, + "parentGridFrame": null, "parentHugsOnMainAxis": false, "parentJustifyContent": null, "parentLayoutSystem": "flow", @@ -25287,8 +27948,16 @@ Object { "label": null, "nonRoundedGlobalFrame": null, "specialSizeMeasurements": Object { + "alignContent": null, "alignItems": null, + "alignSelf": null, "borderRadius": null, + "borderWidths": Object { + "bottom": 0, + "left": 0, + "right": 0, + "top": 0, + }, "clientHeight": 0, "clientWidth": 0, "closestOffsetParentPath": Object { @@ -25336,6 +28005,7 @@ Object { "gap": null, "globalContentBoxForChildren": null, "globalFrameWithTextContent": null, + "gridCellGlobalFrames": null, "hasPositionOffset": false, "hasTransform": false, "htmlElementName": "div", @@ -25347,7 +28017,9 @@ Object { }, "immediateParentProvidesLayout": true, "justifyContent": null, + "justifySelf": null, "layoutSystemForChildren": null, + "layoutSystemForChildrenInherited": false, "margin": Object {}, "naturalHeight": null, "naturalWidth": null, @@ -25356,8 +28028,24 @@ Object { "y": 0, }, "padding": Object {}, + "parentContainerGridProperties": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, + "parentContainerGridPropertiesFromProps": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, "parentFlexDirection": null, "parentFlexGap": 0, + "parentGridCellGlobalFrames": null, + "parentGridFrame": null, "parentHugsOnMainAxis": false, "parentJustifyContent": null, "parentLayoutSystem": "flow", @@ -25620,8 +28308,16 @@ Object { "label": null, "nonRoundedGlobalFrame": null, "specialSizeMeasurements": Object { + "alignContent": null, "alignItems": null, + "alignSelf": null, "borderRadius": null, + "borderWidths": Object { + "bottom": 0, + "left": 0, + "right": 0, + "top": 0, + }, "clientHeight": 0, "clientWidth": 0, "closestOffsetParentPath": Object { @@ -25669,6 +28365,7 @@ Object { "gap": null, "globalContentBoxForChildren": null, "globalFrameWithTextContent": null, + "gridCellGlobalFrames": null, "hasPositionOffset": false, "hasTransform": false, "htmlElementName": "div", @@ -25680,7 +28377,9 @@ Object { }, "immediateParentProvidesLayout": true, "justifyContent": null, + "justifySelf": null, "layoutSystemForChildren": null, + "layoutSystemForChildrenInherited": false, "margin": Object {}, "naturalHeight": null, "naturalWidth": null, @@ -25689,8 +28388,24 @@ Object { "y": 0, }, "padding": Object {}, + "parentContainerGridProperties": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, + "parentContainerGridPropertiesFromProps": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, "parentFlexDirection": null, "parentFlexGap": 0, + "parentGridCellGlobalFrames": null, + "parentGridFrame": null, "parentHugsOnMainAxis": false, "parentJustifyContent": null, "parentLayoutSystem": "flow", @@ -25723,6 +28438,7 @@ exports[`UiJsxCanvas render handles chaining dependencies into the appropriate o data-path=\\"utopia-storyboard-uid/scene-aaa\\" style=\\" overflow: hidden; + container: scene / inline-size; position: absolute; background-color: var(--utopitheme-emphasizedBackground); box-shadow: 0px 1px 2px 0px var(--utopitheme-shadow90), @@ -25891,8 +28607,16 @@ Object { "label": null, "nonRoundedGlobalFrame": null, "specialSizeMeasurements": Object { + "alignContent": null, "alignItems": null, + "alignSelf": null, "borderRadius": null, + "borderWidths": Object { + "bottom": 0, + "left": 0, + "right": 0, + "top": 0, + }, "clientHeight": 0, "clientWidth": 0, "closestOffsetParentPath": Object { @@ -25940,6 +28664,7 @@ Object { "gap": null, "globalContentBoxForChildren": null, "globalFrameWithTextContent": null, + "gridCellGlobalFrames": null, "hasPositionOffset": false, "hasTransform": false, "htmlElementName": "div", @@ -25951,7 +28676,9 @@ Object { }, "immediateParentProvidesLayout": true, "justifyContent": null, + "justifySelf": null, "layoutSystemForChildren": null, + "layoutSystemForChildrenInherited": false, "margin": Object {}, "naturalHeight": null, "naturalWidth": null, @@ -25960,8 +28687,24 @@ Object { "y": 0, }, "padding": Object {}, + "parentContainerGridProperties": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, + "parentContainerGridPropertiesFromProps": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, "parentFlexDirection": null, "parentFlexGap": 0, + "parentGridCellGlobalFrames": null, + "parentGridFrame": null, "parentHugsOnMainAxis": false, "parentJustifyContent": null, "parentLayoutSystem": "flow", @@ -26060,8 +28803,16 @@ Object { "label": null, "nonRoundedGlobalFrame": null, "specialSizeMeasurements": Object { + "alignContent": null, "alignItems": null, + "alignSelf": null, "borderRadius": null, + "borderWidths": Object { + "bottom": 0, + "left": 0, + "right": 0, + "top": 0, + }, "clientHeight": 0, "clientWidth": 0, "closestOffsetParentPath": Object { @@ -26109,6 +28860,7 @@ Object { "gap": null, "globalContentBoxForChildren": null, "globalFrameWithTextContent": null, + "gridCellGlobalFrames": null, "hasPositionOffset": false, "hasTransform": false, "htmlElementName": "div", @@ -26120,7 +28872,9 @@ Object { }, "immediateParentProvidesLayout": true, "justifyContent": null, + "justifySelf": null, "layoutSystemForChildren": null, + "layoutSystemForChildrenInherited": false, "margin": Object {}, "naturalHeight": null, "naturalWidth": null, @@ -26129,8 +28883,24 @@ Object { "y": 0, }, "padding": Object {}, + "parentContainerGridProperties": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, + "parentContainerGridPropertiesFromProps": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, "parentFlexDirection": null, "parentFlexGap": 0, + "parentGridCellGlobalFrames": null, + "parentGridFrame": null, "parentHugsOnMainAxis": false, "parentJustifyContent": null, "parentLayoutSystem": "flow", @@ -26209,8 +28979,16 @@ Object { "label": null, "nonRoundedGlobalFrame": null, "specialSizeMeasurements": Object { + "alignContent": null, "alignItems": null, + "alignSelf": null, "borderRadius": null, + "borderWidths": Object { + "bottom": 0, + "left": 0, + "right": 0, + "top": 0, + }, "clientHeight": 0, "clientWidth": 0, "closestOffsetParentPath": Object { @@ -26258,6 +29036,7 @@ Object { "gap": null, "globalContentBoxForChildren": null, "globalFrameWithTextContent": null, + "gridCellGlobalFrames": null, "hasPositionOffset": false, "hasTransform": false, "htmlElementName": "div", @@ -26269,7 +29048,9 @@ Object { }, "immediateParentProvidesLayout": true, "justifyContent": null, + "justifySelf": null, "layoutSystemForChildren": null, + "layoutSystemForChildrenInherited": false, "margin": Object {}, "naturalHeight": null, "naturalWidth": null, @@ -26278,8 +29059,24 @@ Object { "y": 0, }, "padding": Object {}, + "parentContainerGridProperties": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, + "parentContainerGridPropertiesFromProps": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, "parentFlexDirection": null, "parentFlexGap": 0, + "parentGridCellGlobalFrames": null, + "parentGridFrame": null, "parentHugsOnMainAxis": false, "parentJustifyContent": null, "parentLayoutSystem": "flow", @@ -26312,6 +29109,7 @@ exports[`UiJsxCanvas render handles fragments in an arbitrary block 1`] = ` data-path=\\"utopia-storyboard-uid/scene-aaa\\" style=\\" overflow: hidden; + container: scene / inline-size; position: absolute; background-color: var(--utopitheme-emphasizedBackground); box-shadow: 0px 1px 2px 0px var(--utopitheme-shadow90), @@ -26603,8 +29401,16 @@ Object { "label": null, "nonRoundedGlobalFrame": null, "specialSizeMeasurements": Object { + "alignContent": null, "alignItems": null, + "alignSelf": null, "borderRadius": null, + "borderWidths": Object { + "bottom": 0, + "left": 0, + "right": 0, + "top": 0, + }, "clientHeight": 0, "clientWidth": 0, "closestOffsetParentPath": Object { @@ -26652,6 +29458,7 @@ Object { "gap": null, "globalContentBoxForChildren": null, "globalFrameWithTextContent": null, + "gridCellGlobalFrames": null, "hasPositionOffset": false, "hasTransform": false, "htmlElementName": "div", @@ -26663,7 +29470,9 @@ Object { }, "immediateParentProvidesLayout": true, "justifyContent": null, + "justifySelf": null, "layoutSystemForChildren": null, + "layoutSystemForChildrenInherited": false, "margin": Object {}, "naturalHeight": null, "naturalWidth": null, @@ -26672,8 +29481,24 @@ Object { "y": 0, }, "padding": Object {}, + "parentContainerGridProperties": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, + "parentContainerGridPropertiesFromProps": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, "parentFlexDirection": null, "parentFlexGap": 0, + "parentGridCellGlobalFrames": null, + "parentGridFrame": null, "parentHugsOnMainAxis": false, "parentJustifyContent": null, "parentLayoutSystem": "flow", @@ -26772,8 +29597,16 @@ Object { "label": null, "nonRoundedGlobalFrame": null, "specialSizeMeasurements": Object { + "alignContent": null, "alignItems": null, + "alignSelf": null, "borderRadius": null, + "borderWidths": Object { + "bottom": 0, + "left": 0, + "right": 0, + "top": 0, + }, "clientHeight": 0, "clientWidth": 0, "closestOffsetParentPath": Object { @@ -26821,6 +29654,7 @@ Object { "gap": null, "globalContentBoxForChildren": null, "globalFrameWithTextContent": null, + "gridCellGlobalFrames": null, "hasPositionOffset": false, "hasTransform": false, "htmlElementName": "div", @@ -26832,7 +29666,9 @@ Object { }, "immediateParentProvidesLayout": true, "justifyContent": null, + "justifySelf": null, "layoutSystemForChildren": null, + "layoutSystemForChildrenInherited": false, "margin": Object {}, "naturalHeight": null, "naturalWidth": null, @@ -26841,8 +29677,24 @@ Object { "y": 0, }, "padding": Object {}, + "parentContainerGridProperties": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, + "parentContainerGridPropertiesFromProps": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, "parentFlexDirection": null, "parentFlexGap": 0, + "parentGridCellGlobalFrames": null, + "parentGridFrame": null, "parentHugsOnMainAxis": false, "parentJustifyContent": null, "parentLayoutSystem": "flow", @@ -28699,8 +31551,16 @@ export var storyboard = (props) => { "label": null, "nonRoundedGlobalFrame": null, "specialSizeMeasurements": Object { + "alignContent": null, "alignItems": null, + "alignSelf": null, "borderRadius": null, + "borderWidths": Object { + "bottom": 0, + "left": 0, + "right": 0, + "top": 0, + }, "clientHeight": 0, "clientWidth": 0, "closestOffsetParentPath": Object { @@ -28748,6 +31608,7 @@ export var storyboard = (props) => { "gap": null, "globalContentBoxForChildren": null, "globalFrameWithTextContent": null, + "gridCellGlobalFrames": null, "hasPositionOffset": false, "hasTransform": false, "htmlElementName": "div", @@ -28759,7 +31620,9 @@ export var storyboard = (props) => { }, "immediateParentProvidesLayout": true, "justifyContent": null, + "justifySelf": null, "layoutSystemForChildren": null, + "layoutSystemForChildrenInherited": false, "margin": Object {}, "naturalHeight": null, "naturalWidth": null, @@ -28768,8 +31631,24 @@ export var storyboard = (props) => { "y": 0, }, "padding": Object {}, + "parentContainerGridProperties": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, + "parentContainerGridPropertiesFromProps": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, "parentFlexDirection": null, "parentFlexGap": 0, + "parentGridCellGlobalFrames": null, + "parentGridFrame": null, "parentHugsOnMainAxis": false, "parentJustifyContent": null, "parentLayoutSystem": "flow", @@ -30172,8 +33051,16 @@ export var storyboard = (props) => { "label": null, "nonRoundedGlobalFrame": null, "specialSizeMeasurements": Object { + "alignContent": null, "alignItems": null, + "alignSelf": null, "borderRadius": null, + "borderWidths": Object { + "bottom": 0, + "left": 0, + "right": 0, + "top": 0, + }, "clientHeight": 0, "clientWidth": 0, "closestOffsetParentPath": Object { @@ -30221,6 +33108,7 @@ export var storyboard = (props) => { "gap": null, "globalContentBoxForChildren": null, "globalFrameWithTextContent": null, + "gridCellGlobalFrames": null, "hasPositionOffset": false, "hasTransform": false, "htmlElementName": "div", @@ -30232,7 +33120,9 @@ export var storyboard = (props) => { }, "immediateParentProvidesLayout": true, "justifyContent": null, + "justifySelf": null, "layoutSystemForChildren": null, + "layoutSystemForChildrenInherited": false, "margin": Object {}, "naturalHeight": null, "naturalWidth": null, @@ -30241,8 +33131,24 @@ export var storyboard = (props) => { "y": 0, }, "padding": Object {}, + "parentContainerGridProperties": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, + "parentContainerGridPropertiesFromProps": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, "parentFlexDirection": null, "parentFlexGap": 0, + "parentGridCellGlobalFrames": null, + "parentGridFrame": null, "parentHugsOnMainAxis": false, "parentJustifyContent": null, "parentLayoutSystem": "flow", @@ -31611,8 +34517,16 @@ export var storyboard = (props) => { "label": null, "nonRoundedGlobalFrame": null, "specialSizeMeasurements": Object { + "alignContent": null, "alignItems": null, + "alignSelf": null, "borderRadius": null, + "borderWidths": Object { + "bottom": 0, + "left": 0, + "right": 0, + "top": 0, + }, "clientHeight": 0, "clientWidth": 0, "closestOffsetParentPath": Object { @@ -31660,6 +34574,7 @@ export var storyboard = (props) => { "gap": null, "globalContentBoxForChildren": null, "globalFrameWithTextContent": null, + "gridCellGlobalFrames": null, "hasPositionOffset": false, "hasTransform": false, "htmlElementName": "div", @@ -31671,7 +34586,9 @@ export var storyboard = (props) => { }, "immediateParentProvidesLayout": true, "justifyContent": null, + "justifySelf": null, "layoutSystemForChildren": null, + "layoutSystemForChildrenInherited": false, "margin": Object {}, "naturalHeight": null, "naturalWidth": null, @@ -31680,8 +34597,24 @@ export var storyboard = (props) => { "y": 0, }, "padding": Object {}, + "parentContainerGridProperties": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, + "parentContainerGridPropertiesFromProps": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, "parentFlexDirection": null, "parentFlexGap": 0, + "parentGridCellGlobalFrames": null, + "parentGridFrame": null, "parentHugsOnMainAxis": false, "parentJustifyContent": null, "parentLayoutSystem": "flow", @@ -32232,8 +35165,16 @@ export var storyboard = (props) => { "label": null, "nonRoundedGlobalFrame": null, "specialSizeMeasurements": Object { + "alignContent": null, "alignItems": null, + "alignSelf": null, "borderRadius": null, + "borderWidths": Object { + "bottom": 0, + "left": 0, + "right": 0, + "top": 0, + }, "clientHeight": 0, "clientWidth": 0, "closestOffsetParentPath": Object { @@ -32281,6 +35222,7 @@ export var storyboard = (props) => { "gap": null, "globalContentBoxForChildren": null, "globalFrameWithTextContent": null, + "gridCellGlobalFrames": null, "hasPositionOffset": false, "hasTransform": false, "htmlElementName": "div", @@ -32292,7 +35234,9 @@ export var storyboard = (props) => { }, "immediateParentProvidesLayout": true, "justifyContent": null, + "justifySelf": null, "layoutSystemForChildren": null, + "layoutSystemForChildrenInherited": false, "margin": Object {}, "naturalHeight": null, "naturalWidth": null, @@ -32301,8 +35245,24 @@ export var storyboard = (props) => { "y": 0, }, "padding": Object {}, + "parentContainerGridProperties": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, + "parentContainerGridPropertiesFromProps": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, "parentFlexDirection": null, "parentFlexGap": 0, + "parentGridCellGlobalFrames": null, + "parentGridFrame": null, "parentHugsOnMainAxis": false, "parentJustifyContent": null, "parentLayoutSystem": "flow", @@ -32581,8 +35541,16 @@ export var storyboard = (props) => { "label": null, "nonRoundedGlobalFrame": null, "specialSizeMeasurements": Object { + "alignContent": null, "alignItems": null, + "alignSelf": null, "borderRadius": null, + "borderWidths": Object { + "bottom": 0, + "left": 0, + "right": 0, + "top": 0, + }, "clientHeight": 0, "clientWidth": 0, "closestOffsetParentPath": Object { @@ -32630,6 +35598,7 @@ export var storyboard = (props) => { "gap": null, "globalContentBoxForChildren": null, "globalFrameWithTextContent": null, + "gridCellGlobalFrames": null, "hasPositionOffset": false, "hasTransform": false, "htmlElementName": "div", @@ -32641,7 +35610,9 @@ export var storyboard = (props) => { }, "immediateParentProvidesLayout": true, "justifyContent": null, + "justifySelf": null, "layoutSystemForChildren": null, + "layoutSystemForChildrenInherited": false, "margin": Object {}, "naturalHeight": null, "naturalWidth": null, @@ -32650,8 +35621,24 @@ export var storyboard = (props) => { "y": 0, }, "padding": Object {}, + "parentContainerGridProperties": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, + "parentContainerGridPropertiesFromProps": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, "parentFlexDirection": null, "parentFlexGap": 0, + "parentGridCellGlobalFrames": null, + "parentGridFrame": null, "parentHugsOnMainAxis": false, "parentJustifyContent": null, "parentLayoutSystem": "flow", @@ -33202,8 +36189,16 @@ export var storyboard = (props) => { "label": null, "nonRoundedGlobalFrame": null, "specialSizeMeasurements": Object { + "alignContent": null, "alignItems": null, + "alignSelf": null, "borderRadius": null, + "borderWidths": Object { + "bottom": 0, + "left": 0, + "right": 0, + "top": 0, + }, "clientHeight": 0, "clientWidth": 0, "closestOffsetParentPath": Object { @@ -33251,6 +36246,7 @@ export var storyboard = (props) => { "gap": null, "globalContentBoxForChildren": null, "globalFrameWithTextContent": null, + "gridCellGlobalFrames": null, "hasPositionOffset": false, "hasTransform": false, "htmlElementName": "div", @@ -33262,7 +36258,9 @@ export var storyboard = (props) => { }, "immediateParentProvidesLayout": true, "justifyContent": null, + "justifySelf": null, "layoutSystemForChildren": null, + "layoutSystemForChildrenInherited": false, "margin": Object {}, "naturalHeight": null, "naturalWidth": null, @@ -33271,8 +36269,24 @@ export var storyboard = (props) => { "y": 0, }, "padding": Object {}, + "parentContainerGridProperties": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, + "parentContainerGridPropertiesFromProps": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, "parentFlexDirection": null, "parentFlexGap": 0, + "parentGridCellGlobalFrames": null, + "parentGridFrame": null, "parentHugsOnMainAxis": false, "parentJustifyContent": null, "parentLayoutSystem": "flow", @@ -33551,8 +36565,16 @@ export var storyboard = (props) => { "label": null, "nonRoundedGlobalFrame": null, "specialSizeMeasurements": Object { + "alignContent": null, "alignItems": null, + "alignSelf": null, "borderRadius": null, + "borderWidths": Object { + "bottom": 0, + "left": 0, + "right": 0, + "top": 0, + }, "clientHeight": 0, "clientWidth": 0, "closestOffsetParentPath": Object { @@ -33600,6 +36622,7 @@ export var storyboard = (props) => { "gap": null, "globalContentBoxForChildren": null, "globalFrameWithTextContent": null, + "gridCellGlobalFrames": null, "hasPositionOffset": false, "hasTransform": false, "htmlElementName": "div", @@ -33611,7 +36634,9 @@ export var storyboard = (props) => { }, "immediateParentProvidesLayout": true, "justifyContent": null, + "justifySelf": null, "layoutSystemForChildren": null, + "layoutSystemForChildrenInherited": false, "margin": Object {}, "naturalHeight": null, "naturalWidth": null, @@ -33620,8 +36645,24 @@ export var storyboard = (props) => { "y": 0, }, "padding": Object {}, + "parentContainerGridProperties": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, + "parentContainerGridPropertiesFromProps": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, "parentFlexDirection": null, "parentFlexGap": 0, + "parentGridCellGlobalFrames": null, + "parentGridFrame": null, "parentHugsOnMainAxis": false, "parentJustifyContent": null, "parentLayoutSystem": "flow", @@ -34172,8 +37213,16 @@ export var storyboard = (props) => { "label": null, "nonRoundedGlobalFrame": null, "specialSizeMeasurements": Object { + "alignContent": null, "alignItems": null, + "alignSelf": null, "borderRadius": null, + "borderWidths": Object { + "bottom": 0, + "left": 0, + "right": 0, + "top": 0, + }, "clientHeight": 0, "clientWidth": 0, "closestOffsetParentPath": Object { @@ -34221,6 +37270,7 @@ export var storyboard = (props) => { "gap": null, "globalContentBoxForChildren": null, "globalFrameWithTextContent": null, + "gridCellGlobalFrames": null, "hasPositionOffset": false, "hasTransform": false, "htmlElementName": "div", @@ -34232,7 +37282,9 @@ export var storyboard = (props) => { }, "immediateParentProvidesLayout": true, "justifyContent": null, + "justifySelf": null, "layoutSystemForChildren": null, + "layoutSystemForChildrenInherited": false, "margin": Object {}, "naturalHeight": null, "naturalWidth": null, @@ -34241,8 +37293,24 @@ export var storyboard = (props) => { "y": 0, }, "padding": Object {}, + "parentContainerGridProperties": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, + "parentContainerGridPropertiesFromProps": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, "parentFlexDirection": null, "parentFlexGap": 0, + "parentGridCellGlobalFrames": null, + "parentGridFrame": null, "parentHugsOnMainAxis": false, "parentJustifyContent": null, "parentLayoutSystem": "flow", @@ -34521,8 +37589,16 @@ export var storyboard = (props) => { "label": null, "nonRoundedGlobalFrame": null, "specialSizeMeasurements": Object { + "alignContent": null, "alignItems": null, + "alignSelf": null, "borderRadius": null, + "borderWidths": Object { + "bottom": 0, + "left": 0, + "right": 0, + "top": 0, + }, "clientHeight": 0, "clientWidth": 0, "closestOffsetParentPath": Object { @@ -34570,6 +37646,7 @@ export var storyboard = (props) => { "gap": null, "globalContentBoxForChildren": null, "globalFrameWithTextContent": null, + "gridCellGlobalFrames": null, "hasPositionOffset": false, "hasTransform": false, "htmlElementName": "div", @@ -34581,7 +37658,9 @@ export var storyboard = (props) => { }, "immediateParentProvidesLayout": true, "justifyContent": null, + "justifySelf": null, "layoutSystemForChildren": null, + "layoutSystemForChildrenInherited": false, "margin": Object {}, "naturalHeight": null, "naturalWidth": null, @@ -34590,8 +37669,24 @@ export var storyboard = (props) => { "y": 0, }, "padding": Object {}, + "parentContainerGridProperties": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, + "parentContainerGridPropertiesFromProps": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, "parentFlexDirection": null, "parentFlexGap": 0, + "parentGridCellGlobalFrames": null, + "parentGridFrame": null, "parentHugsOnMainAxis": false, "parentJustifyContent": null, "parentLayoutSystem": "flow", @@ -35180,8 +38275,16 @@ export var storyboard = (props) => { "label": null, "nonRoundedGlobalFrame": null, "specialSizeMeasurements": Object { + "alignContent": null, "alignItems": null, + "alignSelf": null, "borderRadius": null, + "borderWidths": Object { + "bottom": 0, + "left": 0, + "right": 0, + "top": 0, + }, "clientHeight": 0, "clientWidth": 0, "closestOffsetParentPath": Object { @@ -35229,6 +38332,7 @@ export var storyboard = (props) => { "gap": null, "globalContentBoxForChildren": null, "globalFrameWithTextContent": null, + "gridCellGlobalFrames": null, "hasPositionOffset": false, "hasTransform": false, "htmlElementName": "div", @@ -35240,7 +38344,9 @@ export var storyboard = (props) => { }, "immediateParentProvidesLayout": true, "justifyContent": null, + "justifySelf": null, "layoutSystemForChildren": null, + "layoutSystemForChildrenInherited": false, "margin": Object {}, "naturalHeight": null, "naturalWidth": null, @@ -35249,8 +38355,24 @@ export var storyboard = (props) => { "y": 0, }, "padding": Object {}, + "parentContainerGridProperties": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, + "parentContainerGridPropertiesFromProps": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, "parentFlexDirection": null, "parentFlexGap": 0, + "parentGridCellGlobalFrames": null, + "parentGridFrame": null, "parentHugsOnMainAxis": false, "parentJustifyContent": null, "parentLayoutSystem": "flow", @@ -35571,8 +38693,16 @@ export var storyboard = (props) => { "label": null, "nonRoundedGlobalFrame": null, "specialSizeMeasurements": Object { + "alignContent": null, "alignItems": null, + "alignSelf": null, "borderRadius": null, + "borderWidths": Object { + "bottom": 0, + "left": 0, + "right": 0, + "top": 0, + }, "clientHeight": 0, "clientWidth": 0, "closestOffsetParentPath": Object { @@ -35620,6 +38750,7 @@ export var storyboard = (props) => { "gap": null, "globalContentBoxForChildren": null, "globalFrameWithTextContent": null, + "gridCellGlobalFrames": null, "hasPositionOffset": false, "hasTransform": false, "htmlElementName": "div", @@ -35631,7 +38762,9 @@ export var storyboard = (props) => { }, "immediateParentProvidesLayout": true, "justifyContent": null, + "justifySelf": null, "layoutSystemForChildren": null, + "layoutSystemForChildrenInherited": false, "margin": Object {}, "naturalHeight": null, "naturalWidth": null, @@ -35640,8 +38773,24 @@ export var storyboard = (props) => { "y": 0, }, "padding": Object {}, + "parentContainerGridProperties": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, + "parentContainerGridPropertiesFromProps": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, "parentFlexDirection": null, "parentFlexGap": 0, + "parentGridCellGlobalFrames": null, + "parentGridFrame": null, "parentHugsOnMainAxis": false, "parentJustifyContent": null, "parentLayoutSystem": "flow", @@ -36230,8 +39379,16 @@ export var storyboard = (props) => { "label": null, "nonRoundedGlobalFrame": null, "specialSizeMeasurements": Object { + "alignContent": null, "alignItems": null, + "alignSelf": null, "borderRadius": null, + "borderWidths": Object { + "bottom": 0, + "left": 0, + "right": 0, + "top": 0, + }, "clientHeight": 0, "clientWidth": 0, "closestOffsetParentPath": Object { @@ -36279,6 +39436,7 @@ export var storyboard = (props) => { "gap": null, "globalContentBoxForChildren": null, "globalFrameWithTextContent": null, + "gridCellGlobalFrames": null, "hasPositionOffset": false, "hasTransform": false, "htmlElementName": "div", @@ -36290,7 +39448,9 @@ export var storyboard = (props) => { }, "immediateParentProvidesLayout": true, "justifyContent": null, + "justifySelf": null, "layoutSystemForChildren": null, + "layoutSystemForChildrenInherited": false, "margin": Object {}, "naturalHeight": null, "naturalWidth": null, @@ -36299,8 +39459,24 @@ export var storyboard = (props) => { "y": 0, }, "padding": Object {}, + "parentContainerGridProperties": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, + "parentContainerGridPropertiesFromProps": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, "parentFlexDirection": null, "parentFlexGap": 0, + "parentGridCellGlobalFrames": null, + "parentGridFrame": null, "parentHugsOnMainAxis": false, "parentJustifyContent": null, "parentLayoutSystem": "flow", @@ -36621,8 +39797,16 @@ export var storyboard = (props) => { "label": null, "nonRoundedGlobalFrame": null, "specialSizeMeasurements": Object { + "alignContent": null, "alignItems": null, + "alignSelf": null, "borderRadius": null, + "borderWidths": Object { + "bottom": 0, + "left": 0, + "right": 0, + "top": 0, + }, "clientHeight": 0, "clientWidth": 0, "closestOffsetParentPath": Object { @@ -36670,6 +39854,7 @@ export var storyboard = (props) => { "gap": null, "globalContentBoxForChildren": null, "globalFrameWithTextContent": null, + "gridCellGlobalFrames": null, "hasPositionOffset": false, "hasTransform": false, "htmlElementName": "div", @@ -36681,7 +39866,9 @@ export var storyboard = (props) => { }, "immediateParentProvidesLayout": true, "justifyContent": null, + "justifySelf": null, "layoutSystemForChildren": null, + "layoutSystemForChildrenInherited": false, "margin": Object {}, "naturalHeight": null, "naturalWidth": null, @@ -36690,8 +39877,24 @@ export var storyboard = (props) => { "y": 0, }, "padding": Object {}, + "parentContainerGridProperties": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, + "parentContainerGridPropertiesFromProps": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, "parentFlexDirection": null, "parentFlexGap": 0, + "parentGridCellGlobalFrames": null, + "parentGridFrame": null, "parentHugsOnMainAxis": false, "parentJustifyContent": null, "parentLayoutSystem": "flow", @@ -37280,8 +40483,16 @@ export var storyboard = (props) => { "label": null, "nonRoundedGlobalFrame": null, "specialSizeMeasurements": Object { + "alignContent": null, "alignItems": null, + "alignSelf": null, "borderRadius": null, + "borderWidths": Object { + "bottom": 0, + "left": 0, + "right": 0, + "top": 0, + }, "clientHeight": 0, "clientWidth": 0, "closestOffsetParentPath": Object { @@ -37329,6 +40540,7 @@ export var storyboard = (props) => { "gap": null, "globalContentBoxForChildren": null, "globalFrameWithTextContent": null, + "gridCellGlobalFrames": null, "hasPositionOffset": false, "hasTransform": false, "htmlElementName": "div", @@ -37340,7 +40552,9 @@ export var storyboard = (props) => { }, "immediateParentProvidesLayout": true, "justifyContent": null, + "justifySelf": null, "layoutSystemForChildren": null, + "layoutSystemForChildrenInherited": false, "margin": Object {}, "naturalHeight": null, "naturalWidth": null, @@ -37349,8 +40563,24 @@ export var storyboard = (props) => { "y": 0, }, "padding": Object {}, + "parentContainerGridProperties": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, + "parentContainerGridPropertiesFromProps": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, "parentFlexDirection": null, "parentFlexGap": 0, + "parentGridCellGlobalFrames": null, + "parentGridFrame": null, "parentHugsOnMainAxis": false, "parentJustifyContent": null, "parentLayoutSystem": "flow", @@ -37671,8 +40901,16 @@ export var storyboard = (props) => { "label": null, "nonRoundedGlobalFrame": null, "specialSizeMeasurements": Object { + "alignContent": null, "alignItems": null, + "alignSelf": null, "borderRadius": null, + "borderWidths": Object { + "bottom": 0, + "left": 0, + "right": 0, + "top": 0, + }, "clientHeight": 0, "clientWidth": 0, "closestOffsetParentPath": Object { @@ -37720,6 +40958,7 @@ export var storyboard = (props) => { "gap": null, "globalContentBoxForChildren": null, "globalFrameWithTextContent": null, + "gridCellGlobalFrames": null, "hasPositionOffset": false, "hasTransform": false, "htmlElementName": "div", @@ -37731,7 +40970,9 @@ export var storyboard = (props) => { }, "immediateParentProvidesLayout": true, "justifyContent": null, + "justifySelf": null, "layoutSystemForChildren": null, + "layoutSystemForChildrenInherited": false, "margin": Object {}, "naturalHeight": null, "naturalWidth": null, @@ -37740,8 +40981,24 @@ export var storyboard = (props) => { "y": 0, }, "padding": Object {}, + "parentContainerGridProperties": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, + "parentContainerGridPropertiesFromProps": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, "parentFlexDirection": null, "parentFlexGap": 0, + "parentGridCellGlobalFrames": null, + "parentGridFrame": null, "parentHugsOnMainAxis": false, "parentJustifyContent": null, "parentLayoutSystem": "flow", @@ -37784,6 +41041,7 @@ exports[`UiJsxCanvas render props can be accessed inside the arbitrary js block data-path=\\"utopia-storyboard-uid/scene-aaa\\" style=\\" overflow: hidden; + container: scene / inline-size; position: absolute; background-color: var(--utopitheme-emphasizedBackground); box-shadow: 0px 1px 2px 0px var(--utopitheme-shadow90), @@ -37965,8 +41223,16 @@ Object { "label": null, "nonRoundedGlobalFrame": null, "specialSizeMeasurements": Object { + "alignContent": null, "alignItems": null, + "alignSelf": null, "borderRadius": null, + "borderWidths": Object { + "bottom": 0, + "left": 0, + "right": 0, + "top": 0, + }, "clientHeight": 0, "clientWidth": 0, "closestOffsetParentPath": Object { @@ -38014,6 +41280,7 @@ Object { "gap": null, "globalContentBoxForChildren": null, "globalFrameWithTextContent": null, + "gridCellGlobalFrames": null, "hasPositionOffset": false, "hasTransform": false, "htmlElementName": "div", @@ -38025,7 +41292,9 @@ Object { }, "immediateParentProvidesLayout": true, "justifyContent": null, + "justifySelf": null, "layoutSystemForChildren": null, + "layoutSystemForChildrenInherited": false, "margin": Object {}, "naturalHeight": null, "naturalWidth": null, @@ -38034,8 +41303,24 @@ Object { "y": 0, }, "padding": Object {}, + "parentContainerGridProperties": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, + "parentContainerGridPropertiesFromProps": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, "parentFlexDirection": null, "parentFlexGap": 0, + "parentGridCellGlobalFrames": null, + "parentGridFrame": null, "parentHugsOnMainAxis": false, "parentJustifyContent": null, "parentLayoutSystem": "flow", @@ -38134,8 +41419,16 @@ Object { "label": null, "nonRoundedGlobalFrame": null, "specialSizeMeasurements": Object { + "alignContent": null, "alignItems": null, + "alignSelf": null, "borderRadius": null, + "borderWidths": Object { + "bottom": 0, + "left": 0, + "right": 0, + "top": 0, + }, "clientHeight": 0, "clientWidth": 0, "closestOffsetParentPath": Object { @@ -38183,6 +41476,7 @@ Object { "gap": null, "globalContentBoxForChildren": null, "globalFrameWithTextContent": null, + "gridCellGlobalFrames": null, "hasPositionOffset": false, "hasTransform": false, "htmlElementName": "div", @@ -38194,7 +41488,9 @@ Object { }, "immediateParentProvidesLayout": true, "justifyContent": null, + "justifySelf": null, "layoutSystemForChildren": null, + "layoutSystemForChildrenInherited": false, "margin": Object {}, "naturalHeight": null, "naturalWidth": null, @@ -38203,8 +41499,24 @@ Object { "y": 0, }, "padding": Object {}, + "parentContainerGridProperties": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, + "parentContainerGridPropertiesFromProps": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, "parentFlexDirection": null, "parentFlexGap": 0, + "parentGridCellGlobalFrames": null, + "parentGridFrame": null, "parentHugsOnMainAxis": false, "parentJustifyContent": null, "parentLayoutSystem": "flow", @@ -38378,8 +41690,16 @@ Object { "label": null, "nonRoundedGlobalFrame": null, "specialSizeMeasurements": Object { + "alignContent": null, "alignItems": null, + "alignSelf": null, "borderRadius": null, + "borderWidths": Object { + "bottom": 0, + "left": 0, + "right": 0, + "top": 0, + }, "clientHeight": 0, "clientWidth": 0, "closestOffsetParentPath": Object { @@ -38427,6 +41747,7 @@ Object { "gap": null, "globalContentBoxForChildren": null, "globalFrameWithTextContent": null, + "gridCellGlobalFrames": null, "hasPositionOffset": false, "hasTransform": false, "htmlElementName": "div", @@ -38438,7 +41759,9 @@ Object { }, "immediateParentProvidesLayout": true, "justifyContent": null, + "justifySelf": null, "layoutSystemForChildren": null, + "layoutSystemForChildrenInherited": false, "margin": Object {}, "naturalHeight": null, "naturalWidth": null, @@ -38447,8 +41770,24 @@ Object { "y": 0, }, "padding": Object {}, + "parentContainerGridProperties": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, + "parentContainerGridPropertiesFromProps": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, "parentFlexDirection": null, "parentFlexGap": 0, + "parentGridCellGlobalFrames": null, + "parentGridFrame": null, "parentHugsOnMainAxis": false, "parentJustifyContent": null, "parentLayoutSystem": "flow", @@ -38545,8 +41884,16 @@ Object { "label": null, "nonRoundedGlobalFrame": null, "specialSizeMeasurements": Object { + "alignContent": null, "alignItems": null, + "alignSelf": null, "borderRadius": null, + "borderWidths": Object { + "bottom": 0, + "left": 0, + "right": 0, + "top": 0, + }, "clientHeight": 0, "clientWidth": 0, "closestOffsetParentPath": Object { @@ -38594,6 +41941,7 @@ Object { "gap": null, "globalContentBoxForChildren": null, "globalFrameWithTextContent": null, + "gridCellGlobalFrames": null, "hasPositionOffset": false, "hasTransform": false, "htmlElementName": "div", @@ -38605,7 +41953,9 @@ Object { }, "immediateParentProvidesLayout": true, "justifyContent": null, + "justifySelf": null, "layoutSystemForChildren": null, + "layoutSystemForChildrenInherited": false, "margin": Object {}, "naturalHeight": null, "naturalWidth": null, @@ -38614,8 +41964,24 @@ Object { "y": 0, }, "padding": Object {}, + "parentContainerGridProperties": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, + "parentContainerGridPropertiesFromProps": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, "parentFlexDirection": null, "parentFlexGap": 0, + "parentGridCellGlobalFrames": null, + "parentGridFrame": null, "parentHugsOnMainAxis": false, "parentJustifyContent": null, "parentLayoutSystem": "flow", @@ -38712,8 +42078,16 @@ Object { "label": null, "nonRoundedGlobalFrame": null, "specialSizeMeasurements": Object { + "alignContent": null, "alignItems": null, + "alignSelf": null, "borderRadius": null, + "borderWidths": Object { + "bottom": 0, + "left": 0, + "right": 0, + "top": 0, + }, "clientHeight": 0, "clientWidth": 0, "closestOffsetParentPath": Object { @@ -38761,6 +42135,7 @@ Object { "gap": null, "globalContentBoxForChildren": null, "globalFrameWithTextContent": null, + "gridCellGlobalFrames": null, "hasPositionOffset": false, "hasTransform": false, "htmlElementName": "div", @@ -38772,7 +42147,9 @@ Object { }, "immediateParentProvidesLayout": true, "justifyContent": null, + "justifySelf": null, "layoutSystemForChildren": null, + "layoutSystemForChildrenInherited": false, "margin": Object {}, "naturalHeight": null, "naturalWidth": null, @@ -38781,8 +42158,24 @@ Object { "y": 0, }, "padding": Object {}, + "parentContainerGridProperties": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, + "parentContainerGridPropertiesFromProps": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, "parentFlexDirection": null, "parentFlexGap": 0, + "parentGridCellGlobalFrames": null, + "parentGridFrame": null, "parentHugsOnMainAxis": false, "parentJustifyContent": null, "parentLayoutSystem": "flow", @@ -38815,6 +42208,7 @@ exports[`UiJsxCanvas render refs are handled and triggered correctly in a class data-path=\\"utopia-storyboard-uid/scene-aaa\\" style=\\" overflow: hidden; + container: scene / inline-size; position: absolute; background-color: var(--utopitheme-emphasizedBackground); box-shadow: 0px 1px 2px 0px var(--utopitheme-shadow90), @@ -38985,8 +42379,16 @@ Object { "label": null, "nonRoundedGlobalFrame": null, "specialSizeMeasurements": Object { + "alignContent": null, "alignItems": null, + "alignSelf": null, "borderRadius": null, + "borderWidths": Object { + "bottom": 0, + "left": 0, + "right": 0, + "top": 0, + }, "clientHeight": 0, "clientWidth": 0, "closestOffsetParentPath": Object { @@ -39034,6 +42436,7 @@ Object { "gap": null, "globalContentBoxForChildren": null, "globalFrameWithTextContent": null, + "gridCellGlobalFrames": null, "hasPositionOffset": false, "hasTransform": false, "htmlElementName": "div", @@ -39045,7 +42448,9 @@ Object { }, "immediateParentProvidesLayout": true, "justifyContent": null, + "justifySelf": null, "layoutSystemForChildren": null, + "layoutSystemForChildrenInherited": false, "margin": Object {}, "naturalHeight": null, "naturalWidth": null, @@ -39054,8 +42459,24 @@ Object { "y": 0, }, "padding": Object {}, + "parentContainerGridProperties": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, + "parentContainerGridPropertiesFromProps": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, "parentFlexDirection": null, "parentFlexGap": 0, + "parentGridCellGlobalFrames": null, + "parentGridFrame": null, "parentHugsOnMainAxis": false, "parentJustifyContent": null, "parentLayoutSystem": "flow", @@ -39154,8 +42575,16 @@ Object { "label": null, "nonRoundedGlobalFrame": null, "specialSizeMeasurements": Object { + "alignContent": null, "alignItems": null, + "alignSelf": null, "borderRadius": null, + "borderWidths": Object { + "bottom": 0, + "left": 0, + "right": 0, + "top": 0, + }, "clientHeight": 0, "clientWidth": 0, "closestOffsetParentPath": Object { @@ -39203,6 +42632,7 @@ Object { "gap": null, "globalContentBoxForChildren": null, "globalFrameWithTextContent": null, + "gridCellGlobalFrames": null, "hasPositionOffset": false, "hasTransform": false, "htmlElementName": "div", @@ -39214,7 +42644,9 @@ Object { }, "immediateParentProvidesLayout": true, "justifyContent": null, + "justifySelf": null, "layoutSystemForChildren": null, + "layoutSystemForChildrenInherited": false, "margin": Object {}, "naturalHeight": null, "naturalWidth": null, @@ -39223,8 +42655,24 @@ Object { "y": 0, }, "padding": Object {}, + "parentContainerGridProperties": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, + "parentContainerGridPropertiesFromProps": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, "parentFlexDirection": null, "parentFlexGap": 0, + "parentGridCellGlobalFrames": null, + "parentGridFrame": null, "parentHugsOnMainAxis": false, "parentJustifyContent": null, "parentLayoutSystem": "flow", @@ -39370,8 +42818,16 @@ Object { "label": null, "nonRoundedGlobalFrame": null, "specialSizeMeasurements": Object { + "alignContent": null, "alignItems": null, + "alignSelf": null, "borderRadius": null, + "borderWidths": Object { + "bottom": 0, + "left": 0, + "right": 0, + "top": 0, + }, "clientHeight": 0, "clientWidth": 0, "closestOffsetParentPath": Object { @@ -39419,6 +42875,7 @@ Object { "gap": null, "globalContentBoxForChildren": null, "globalFrameWithTextContent": null, + "gridCellGlobalFrames": null, "hasPositionOffset": false, "hasTransform": false, "htmlElementName": "div", @@ -39430,7 +42887,9 @@ Object { }, "immediateParentProvidesLayout": true, "justifyContent": null, + "justifySelf": null, "layoutSystemForChildren": null, + "layoutSystemForChildrenInherited": false, "margin": Object {}, "naturalHeight": null, "naturalWidth": null, @@ -39439,8 +42898,24 @@ Object { "y": 0, }, "padding": Object {}, + "parentContainerGridProperties": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, + "parentContainerGridPropertiesFromProps": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, "parentFlexDirection": null, "parentFlexGap": 0, + "parentGridCellGlobalFrames": null, + "parentGridFrame": null, "parentHugsOnMainAxis": false, "parentJustifyContent": null, "parentLayoutSystem": "flow", @@ -39473,6 +42948,7 @@ exports[`UiJsxCanvas render refs are handled and triggered correctly in a functi data-path=\\"utopia-storyboard-uid/scene-aaa\\" style=\\" overflow: hidden; + container: scene / inline-size; position: absolute; background-color: var(--utopitheme-emphasizedBackground); box-shadow: 0px 1px 2px 0px var(--utopitheme-shadow90), @@ -39642,8 +43118,16 @@ Object { "label": null, "nonRoundedGlobalFrame": null, "specialSizeMeasurements": Object { + "alignContent": null, "alignItems": null, + "alignSelf": null, "borderRadius": null, + "borderWidths": Object { + "bottom": 0, + "left": 0, + "right": 0, + "top": 0, + }, "clientHeight": 0, "clientWidth": 0, "closestOffsetParentPath": Object { @@ -39691,6 +43175,7 @@ Object { "gap": null, "globalContentBoxForChildren": null, "globalFrameWithTextContent": null, + "gridCellGlobalFrames": null, "hasPositionOffset": false, "hasTransform": false, "htmlElementName": "div", @@ -39702,7 +43187,9 @@ Object { }, "immediateParentProvidesLayout": true, "justifyContent": null, + "justifySelf": null, "layoutSystemForChildren": null, + "layoutSystemForChildrenInherited": false, "margin": Object {}, "naturalHeight": null, "naturalWidth": null, @@ -39711,8 +43198,24 @@ Object { "y": 0, }, "padding": Object {}, + "parentContainerGridProperties": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, + "parentContainerGridPropertiesFromProps": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, "parentFlexDirection": null, "parentFlexGap": 0, + "parentGridCellGlobalFrames": null, + "parentGridFrame": null, "parentHugsOnMainAxis": false, "parentJustifyContent": null, "parentLayoutSystem": "flow", @@ -39811,8 +43314,16 @@ Object { "label": null, "nonRoundedGlobalFrame": null, "specialSizeMeasurements": Object { + "alignContent": null, "alignItems": null, + "alignSelf": null, "borderRadius": null, + "borderWidths": Object { + "bottom": 0, + "left": 0, + "right": 0, + "top": 0, + }, "clientHeight": 0, "clientWidth": 0, "closestOffsetParentPath": Object { @@ -39860,6 +43371,7 @@ Object { "gap": null, "globalContentBoxForChildren": null, "globalFrameWithTextContent": null, + "gridCellGlobalFrames": null, "hasPositionOffset": false, "hasTransform": false, "htmlElementName": "div", @@ -39871,7 +43383,9 @@ Object { }, "immediateParentProvidesLayout": true, "justifyContent": null, + "justifySelf": null, "layoutSystemForChildren": null, + "layoutSystemForChildrenInherited": false, "margin": Object {}, "naturalHeight": null, "naturalWidth": null, @@ -39880,8 +43394,24 @@ Object { "y": 0, }, "padding": Object {}, + "parentContainerGridProperties": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, + "parentContainerGridPropertiesFromProps": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, "parentFlexDirection": null, "parentFlexGap": 0, + "parentGridCellGlobalFrames": null, + "parentGridFrame": null, "parentHugsOnMainAxis": false, "parentJustifyContent": null, "parentLayoutSystem": "flow", @@ -40131,8 +43661,16 @@ Object { "label": null, "nonRoundedGlobalFrame": null, "specialSizeMeasurements": Object { + "alignContent": null, "alignItems": null, + "alignSelf": null, "borderRadius": null, + "borderWidths": Object { + "bottom": 0, + "left": 0, + "right": 0, + "top": 0, + }, "clientHeight": 0, "clientWidth": 0, "closestOffsetParentPath": Object { @@ -40180,6 +43718,7 @@ Object { "gap": null, "globalContentBoxForChildren": null, "globalFrameWithTextContent": null, + "gridCellGlobalFrames": null, "hasPositionOffset": false, "hasTransform": false, "htmlElementName": "div", @@ -40191,7 +43730,9 @@ Object { }, "immediateParentProvidesLayout": true, "justifyContent": null, + "justifySelf": null, "layoutSystemForChildren": null, + "layoutSystemForChildrenInherited": false, "margin": Object {}, "naturalHeight": null, "naturalWidth": null, @@ -40200,8 +43741,24 @@ Object { "y": 0, }, "padding": Object {}, + "parentContainerGridProperties": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, + "parentContainerGridPropertiesFromProps": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, "parentFlexDirection": null, "parentFlexGap": 0, + "parentGridCellGlobalFrames": null, + "parentGridFrame": null, "parentHugsOnMainAxis": false, "parentJustifyContent": null, "parentLayoutSystem": "flow", @@ -40234,6 +43791,7 @@ exports[`UiJsxCanvas render renders a 1st party component with uids correctly, u data-path=\\"utopia-storyboard-uid/scene-aaa\\" style=\\" overflow: hidden; + container: scene / inline-size; position: absolute; background-color: var(--utopitheme-emphasizedBackground); box-shadow: 0px 1px 2px 0px var(--utopitheme-shadow90), @@ -40449,8 +44007,16 @@ Object { "label": null, "nonRoundedGlobalFrame": null, "specialSizeMeasurements": Object { + "alignContent": null, "alignItems": null, + "alignSelf": null, "borderRadius": null, + "borderWidths": Object { + "bottom": 0, + "left": 0, + "right": 0, + "top": 0, + }, "clientHeight": 0, "clientWidth": 0, "closestOffsetParentPath": Object { @@ -40498,6 +44064,7 @@ Object { "gap": null, "globalContentBoxForChildren": null, "globalFrameWithTextContent": null, + "gridCellGlobalFrames": null, "hasPositionOffset": false, "hasTransform": false, "htmlElementName": "div", @@ -40509,7 +44076,9 @@ Object { }, "immediateParentProvidesLayout": true, "justifyContent": null, + "justifySelf": null, "layoutSystemForChildren": null, + "layoutSystemForChildrenInherited": false, "margin": Object {}, "naturalHeight": null, "naturalWidth": null, @@ -40518,8 +44087,24 @@ Object { "y": 0, }, "padding": Object {}, + "parentContainerGridProperties": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, + "parentContainerGridPropertiesFromProps": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, "parentFlexDirection": null, "parentFlexGap": 0, + "parentGridCellGlobalFrames": null, + "parentGridFrame": null, "parentHugsOnMainAxis": false, "parentJustifyContent": null, "parentLayoutSystem": "flow", @@ -40618,8 +44203,16 @@ Object { "label": null, "nonRoundedGlobalFrame": null, "specialSizeMeasurements": Object { + "alignContent": null, "alignItems": null, + "alignSelf": null, "borderRadius": null, + "borderWidths": Object { + "bottom": 0, + "left": 0, + "right": 0, + "top": 0, + }, "clientHeight": 0, "clientWidth": 0, "closestOffsetParentPath": Object { @@ -40667,6 +44260,7 @@ Object { "gap": null, "globalContentBoxForChildren": null, "globalFrameWithTextContent": null, + "gridCellGlobalFrames": null, "hasPositionOffset": false, "hasTransform": false, "htmlElementName": "div", @@ -40678,7 +44272,9 @@ Object { }, "immediateParentProvidesLayout": true, "justifyContent": null, + "justifySelf": null, "layoutSystemForChildren": null, + "layoutSystemForChildrenInherited": false, "margin": Object {}, "naturalHeight": null, "naturalWidth": null, @@ -40687,8 +44283,24 @@ Object { "y": 0, }, "padding": Object {}, + "parentContainerGridProperties": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, + "parentContainerGridPropertiesFromProps": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, "parentFlexDirection": null, "parentFlexGap": 0, + "parentGridCellGlobalFrames": null, + "parentGridFrame": null, "parentHugsOnMainAxis": false, "parentJustifyContent": null, "parentLayoutSystem": "flow", @@ -40895,8 +44507,16 @@ Object { "label": null, "nonRoundedGlobalFrame": null, "specialSizeMeasurements": Object { + "alignContent": null, "alignItems": null, + "alignSelf": null, "borderRadius": null, + "borderWidths": Object { + "bottom": 0, + "left": 0, + "right": 0, + "top": 0, + }, "clientHeight": 0, "clientWidth": 0, "closestOffsetParentPath": Object { @@ -40944,6 +44564,7 @@ Object { "gap": null, "globalContentBoxForChildren": null, "globalFrameWithTextContent": null, + "gridCellGlobalFrames": null, "hasPositionOffset": false, "hasTransform": false, "htmlElementName": "div", @@ -40955,7 +44576,9 @@ Object { }, "immediateParentProvidesLayout": true, "justifyContent": null, + "justifySelf": null, "layoutSystemForChildren": null, + "layoutSystemForChildrenInherited": false, "margin": Object {}, "naturalHeight": null, "naturalWidth": null, @@ -40964,8 +44587,24 @@ Object { "y": 0, }, "padding": Object {}, + "parentContainerGridProperties": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, + "parentContainerGridPropertiesFromProps": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, "parentFlexDirection": null, "parentFlexGap": 0, + "parentGridCellGlobalFrames": null, + "parentGridFrame": null, "parentHugsOnMainAxis": false, "parentJustifyContent": null, "parentLayoutSystem": "flow", @@ -41121,8 +44760,16 @@ Object { "label": null, "nonRoundedGlobalFrame": null, "specialSizeMeasurements": Object { + "alignContent": null, "alignItems": null, + "alignSelf": null, "borderRadius": null, + "borderWidths": Object { + "bottom": 0, + "left": 0, + "right": 0, + "top": 0, + }, "clientHeight": 0, "clientWidth": 0, "closestOffsetParentPath": Object { @@ -41170,6 +44817,7 @@ Object { "gap": null, "globalContentBoxForChildren": null, "globalFrameWithTextContent": null, + "gridCellGlobalFrames": null, "hasPositionOffset": false, "hasTransform": false, "htmlElementName": "div", @@ -41181,7 +44829,9 @@ Object { }, "immediateParentProvidesLayout": true, "justifyContent": null, + "justifySelf": null, "layoutSystemForChildren": null, + "layoutSystemForChildrenInherited": false, "margin": Object {}, "naturalHeight": null, "naturalWidth": null, @@ -41190,8 +44840,24 @@ Object { "y": 0, }, "padding": Object {}, + "parentContainerGridProperties": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, + "parentContainerGridPropertiesFromProps": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, "parentFlexDirection": null, "parentFlexGap": 0, + "parentGridCellGlobalFrames": null, + "parentGridFrame": null, "parentHugsOnMainAxis": false, "parentJustifyContent": null, "parentLayoutSystem": "flow", @@ -41296,8 +44962,16 @@ Object { "label": null, "nonRoundedGlobalFrame": null, "specialSizeMeasurements": Object { + "alignContent": null, "alignItems": null, + "alignSelf": null, "borderRadius": null, + "borderWidths": Object { + "bottom": 0, + "left": 0, + "right": 0, + "top": 0, + }, "clientHeight": 0, "clientWidth": 0, "closestOffsetParentPath": Object { @@ -41345,6 +45019,7 @@ Object { "gap": null, "globalContentBoxForChildren": null, "globalFrameWithTextContent": null, + "gridCellGlobalFrames": null, "hasPositionOffset": false, "hasTransform": false, "htmlElementName": "div", @@ -41356,7 +45031,9 @@ Object { }, "immediateParentProvidesLayout": true, "justifyContent": null, + "justifySelf": null, "layoutSystemForChildren": null, + "layoutSystemForChildrenInherited": false, "margin": Object {}, "naturalHeight": null, "naturalWidth": null, @@ -41365,8 +45042,24 @@ Object { "y": 0, }, "padding": Object {}, + "parentContainerGridProperties": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, + "parentContainerGridPropertiesFromProps": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, "parentFlexDirection": null, "parentFlexGap": 0, + "parentGridCellGlobalFrames": null, + "parentGridFrame": null, "parentHugsOnMainAxis": false, "parentJustifyContent": null, "parentLayoutSystem": "flow", @@ -41399,6 +45092,7 @@ exports[`UiJsxCanvas render renders a canvas defined by a utopia storyboard comp data-path=\\"utopia-storyboard-uid/scene-aaa\\" style=\\" overflow: hidden; + container: scene / inline-size; position: absolute; background-color: var(--utopitheme-emphasizedBackground); box-shadow: 0px 1px 2px 0px var(--utopitheme-shadow90), @@ -41596,8 +45290,16 @@ Object { "label": null, "nonRoundedGlobalFrame": null, "specialSizeMeasurements": Object { + "alignContent": null, "alignItems": null, + "alignSelf": null, "borderRadius": null, + "borderWidths": Object { + "bottom": 0, + "left": 0, + "right": 0, + "top": 0, + }, "clientHeight": 0, "clientWidth": 0, "closestOffsetParentPath": Object { @@ -41645,6 +45347,7 @@ Object { "gap": null, "globalContentBoxForChildren": null, "globalFrameWithTextContent": null, + "gridCellGlobalFrames": null, "hasPositionOffset": false, "hasTransform": false, "htmlElementName": "div", @@ -41656,7 +45359,9 @@ Object { }, "immediateParentProvidesLayout": true, "justifyContent": null, + "justifySelf": null, "layoutSystemForChildren": null, + "layoutSystemForChildrenInherited": false, "margin": Object {}, "naturalHeight": null, "naturalWidth": null, @@ -41665,8 +45370,24 @@ Object { "y": 0, }, "padding": Object {}, + "parentContainerGridProperties": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, + "parentContainerGridPropertiesFromProps": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, "parentFlexDirection": null, "parentFlexGap": 0, + "parentGridCellGlobalFrames": null, + "parentGridFrame": null, "parentHugsOnMainAxis": false, "parentJustifyContent": null, "parentLayoutSystem": "flow", @@ -41780,8 +45501,16 @@ Object { "label": null, "nonRoundedGlobalFrame": null, "specialSizeMeasurements": Object { + "alignContent": null, "alignItems": null, + "alignSelf": null, "borderRadius": null, + "borderWidths": Object { + "bottom": 0, + "left": 0, + "right": 0, + "top": 0, + }, "clientHeight": 0, "clientWidth": 0, "closestOffsetParentPath": Object { @@ -41829,6 +45558,7 @@ Object { "gap": null, "globalContentBoxForChildren": null, "globalFrameWithTextContent": null, + "gridCellGlobalFrames": null, "hasPositionOffset": false, "hasTransform": false, "htmlElementName": "div", @@ -41840,7 +45570,9 @@ Object { }, "immediateParentProvidesLayout": true, "justifyContent": null, + "justifySelf": null, "layoutSystemForChildren": null, + "layoutSystemForChildrenInherited": false, "margin": Object {}, "naturalHeight": null, "naturalWidth": null, @@ -41849,8 +45581,24 @@ Object { "y": 0, }, "padding": Object {}, + "parentContainerGridProperties": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, + "parentContainerGridPropertiesFromProps": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, "parentFlexDirection": null, "parentFlexGap": 0, + "parentGridCellGlobalFrames": null, + "parentGridFrame": null, "parentHugsOnMainAxis": false, "parentJustifyContent": null, "parentLayoutSystem": "flow", @@ -42134,8 +45882,16 @@ Object { "label": null, "nonRoundedGlobalFrame": null, "specialSizeMeasurements": Object { + "alignContent": null, "alignItems": null, + "alignSelf": null, "borderRadius": null, + "borderWidths": Object { + "bottom": 0, + "left": 0, + "right": 0, + "top": 0, + }, "clientHeight": 0, "clientWidth": 0, "closestOffsetParentPath": Object { @@ -42183,6 +45939,7 @@ Object { "gap": null, "globalContentBoxForChildren": null, "globalFrameWithTextContent": null, + "gridCellGlobalFrames": null, "hasPositionOffset": false, "hasTransform": false, "htmlElementName": "div", @@ -42194,7 +45951,9 @@ Object { }, "immediateParentProvidesLayout": true, "justifyContent": null, + "justifySelf": null, "layoutSystemForChildren": null, + "layoutSystemForChildrenInherited": false, "margin": Object {}, "naturalHeight": null, "naturalWidth": null, @@ -42203,8 +45962,24 @@ Object { "y": 0, }, "padding": Object {}, + "parentContainerGridProperties": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, + "parentContainerGridPropertiesFromProps": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, "parentFlexDirection": null, "parentFlexGap": 0, + "parentGridCellGlobalFrames": null, + "parentGridFrame": null, "parentHugsOnMainAxis": false, "parentJustifyContent": null, "parentLayoutSystem": "flow", @@ -42310,8 +46085,16 @@ Object { "label": null, "nonRoundedGlobalFrame": null, "specialSizeMeasurements": Object { + "alignContent": null, "alignItems": null, + "alignSelf": null, "borderRadius": null, + "borderWidths": Object { + "bottom": 0, + "left": 0, + "right": 0, + "top": 0, + }, "clientHeight": 0, "clientWidth": 0, "closestOffsetParentPath": Object { @@ -42359,6 +46142,7 @@ Object { "gap": null, "globalContentBoxForChildren": null, "globalFrameWithTextContent": null, + "gridCellGlobalFrames": null, "hasPositionOffset": false, "hasTransform": false, "htmlElementName": "div", @@ -42370,7 +46154,9 @@ Object { }, "immediateParentProvidesLayout": true, "justifyContent": null, + "justifySelf": null, "layoutSystemForChildren": null, + "layoutSystemForChildrenInherited": false, "margin": Object {}, "naturalHeight": null, "naturalWidth": null, @@ -42379,8 +46165,24 @@ Object { "y": 0, }, "padding": Object {}, + "parentContainerGridProperties": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, + "parentContainerGridPropertiesFromProps": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, "parentFlexDirection": null, "parentFlexGap": 0, + "parentGridCellGlobalFrames": null, + "parentGridFrame": null, "parentHugsOnMainAxis": false, "parentJustifyContent": null, "parentLayoutSystem": "flow", @@ -42413,6 +46215,7 @@ exports[`UiJsxCanvas render renders a canvas reliant on another file that uses m data-path=\\"utopia-storyboard-uid/scene-aaa\\" style=\\" overflow: hidden; + container: scene / inline-size; position: absolute; background-color: var(--utopitheme-emphasizedBackground); box-shadow: 0px 1px 2px 0px var(--utopitheme-shadow90), @@ -42763,8 +46566,16 @@ Object { "label": null, "nonRoundedGlobalFrame": null, "specialSizeMeasurements": Object { + "alignContent": null, "alignItems": null, + "alignSelf": null, "borderRadius": null, + "borderWidths": Object { + "bottom": 0, + "left": 0, + "right": 0, + "top": 0, + }, "clientHeight": 0, "clientWidth": 0, "closestOffsetParentPath": Object { @@ -42812,6 +46623,7 @@ Object { "gap": null, "globalContentBoxForChildren": null, "globalFrameWithTextContent": null, + "gridCellGlobalFrames": null, "hasPositionOffset": false, "hasTransform": false, "htmlElementName": "div", @@ -42823,7 +46635,9 @@ Object { }, "immediateParentProvidesLayout": true, "justifyContent": null, + "justifySelf": null, "layoutSystemForChildren": null, + "layoutSystemForChildrenInherited": false, "margin": Object {}, "naturalHeight": null, "naturalWidth": null, @@ -42832,8 +46646,24 @@ Object { "y": 0, }, "padding": Object {}, + "parentContainerGridProperties": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, + "parentContainerGridPropertiesFromProps": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, "parentFlexDirection": null, "parentFlexGap": 0, + "parentGridCellGlobalFrames": null, + "parentGridFrame": null, "parentHugsOnMainAxis": false, "parentJustifyContent": null, "parentLayoutSystem": "flow", @@ -42947,8 +46777,16 @@ Object { "label": null, "nonRoundedGlobalFrame": null, "specialSizeMeasurements": Object { + "alignContent": null, "alignItems": null, + "alignSelf": null, "borderRadius": null, + "borderWidths": Object { + "bottom": 0, + "left": 0, + "right": 0, + "top": 0, + }, "clientHeight": 0, "clientWidth": 0, "closestOffsetParentPath": Object { @@ -42996,6 +46834,7 @@ Object { "gap": null, "globalContentBoxForChildren": null, "globalFrameWithTextContent": null, + "gridCellGlobalFrames": null, "hasPositionOffset": false, "hasTransform": false, "htmlElementName": "div", @@ -43007,7 +46846,9 @@ Object { }, "immediateParentProvidesLayout": true, "justifyContent": null, + "justifySelf": null, "layoutSystemForChildren": null, + "layoutSystemForChildrenInherited": false, "margin": Object {}, "naturalHeight": null, "naturalWidth": null, @@ -43016,8 +46857,24 @@ Object { "y": 0, }, "padding": Object {}, + "parentContainerGridProperties": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, + "parentContainerGridPropertiesFromProps": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, "parentFlexDirection": null, "parentFlexGap": 0, + "parentGridCellGlobalFrames": null, + "parentGridFrame": null, "parentHugsOnMainAxis": false, "parentJustifyContent": null, "parentLayoutSystem": "flow", @@ -43303,8 +47160,16 @@ Object { "label": null, "nonRoundedGlobalFrame": null, "specialSizeMeasurements": Object { + "alignContent": null, "alignItems": null, + "alignSelf": null, "borderRadius": null, + "borderWidths": Object { + "bottom": 0, + "left": 0, + "right": 0, + "top": 0, + }, "clientHeight": 0, "clientWidth": 0, "closestOffsetParentPath": Object { @@ -43352,6 +47217,7 @@ Object { "gap": null, "globalContentBoxForChildren": null, "globalFrameWithTextContent": null, + "gridCellGlobalFrames": null, "hasPositionOffset": false, "hasTransform": false, "htmlElementName": "div", @@ -43363,7 +47229,9 @@ Object { }, "immediateParentProvidesLayout": true, "justifyContent": null, + "justifySelf": null, "layoutSystemForChildren": null, + "layoutSystemForChildrenInherited": false, "margin": Object {}, "naturalHeight": null, "naturalWidth": null, @@ -43372,8 +47240,24 @@ Object { "y": 0, }, "padding": Object {}, + "parentContainerGridProperties": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, + "parentContainerGridPropertiesFromProps": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, "parentFlexDirection": null, "parentFlexGap": 0, + "parentGridCellGlobalFrames": null, + "parentGridFrame": null, "parentHugsOnMainAxis": false, "parentJustifyContent": null, "parentLayoutSystem": "flow", @@ -43479,8 +47363,16 @@ Object { "label": null, "nonRoundedGlobalFrame": null, "specialSizeMeasurements": Object { + "alignContent": null, "alignItems": null, + "alignSelf": null, "borderRadius": null, + "borderWidths": Object { + "bottom": 0, + "left": 0, + "right": 0, + "top": 0, + }, "clientHeight": 0, "clientWidth": 0, "closestOffsetParentPath": Object { @@ -43528,6 +47420,7 @@ Object { "gap": null, "globalContentBoxForChildren": null, "globalFrameWithTextContent": null, + "gridCellGlobalFrames": null, "hasPositionOffset": false, "hasTransform": false, "htmlElementName": "div", @@ -43539,7 +47432,9 @@ Object { }, "immediateParentProvidesLayout": true, "justifyContent": null, + "justifySelf": null, "layoutSystemForChildren": null, + "layoutSystemForChildrenInherited": false, "margin": Object {}, "naturalHeight": null, "naturalWidth": null, @@ -43548,8 +47443,24 @@ Object { "y": 0, }, "padding": Object {}, + "parentContainerGridProperties": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, + "parentContainerGridPropertiesFromProps": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, "parentFlexDirection": null, "parentFlexGap": 0, + "parentGridCellGlobalFrames": null, + "parentGridFrame": null, "parentHugsOnMainAxis": false, "parentJustifyContent": null, "parentLayoutSystem": "flow", @@ -43582,6 +47493,7 @@ exports[`UiJsxCanvas render renders a canvas testing a multitude of export style data-path=\\"utopia-storyboard-uid/scene-aaa\\" style=\\" overflow: hidden; + container: scene / inline-size; position: absolute; background-color: var(--utopitheme-emphasizedBackground); box-shadow: 0px 1px 2px 0px var(--utopitheme-shadow90), @@ -43863,8 +47775,16 @@ Object { "label": null, "nonRoundedGlobalFrame": null, "specialSizeMeasurements": Object { + "alignContent": null, "alignItems": null, + "alignSelf": null, "borderRadius": null, + "borderWidths": Object { + "bottom": 0, + "left": 0, + "right": 0, + "top": 0, + }, "clientHeight": 0, "clientWidth": 0, "closestOffsetParentPath": Object { @@ -43912,6 +47832,7 @@ Object { "gap": null, "globalContentBoxForChildren": null, "globalFrameWithTextContent": null, + "gridCellGlobalFrames": null, "hasPositionOffset": false, "hasTransform": false, "htmlElementName": "div", @@ -43923,7 +47844,9 @@ Object { }, "immediateParentProvidesLayout": true, "justifyContent": null, + "justifySelf": null, "layoutSystemForChildren": null, + "layoutSystemForChildrenInherited": false, "margin": Object {}, "naturalHeight": null, "naturalWidth": null, @@ -43932,8 +47855,24 @@ Object { "y": 0, }, "padding": Object {}, + "parentContainerGridProperties": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, + "parentContainerGridPropertiesFromProps": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, "parentFlexDirection": null, "parentFlexGap": 0, + "parentGridCellGlobalFrames": null, + "parentGridFrame": null, "parentHugsOnMainAxis": false, "parentJustifyContent": null, "parentLayoutSystem": "flow", @@ -44031,8 +47970,16 @@ Object { "label": null, "nonRoundedGlobalFrame": null, "specialSizeMeasurements": Object { + "alignContent": null, "alignItems": null, + "alignSelf": null, "borderRadius": null, + "borderWidths": Object { + "bottom": 0, + "left": 0, + "right": 0, + "top": 0, + }, "clientHeight": 0, "clientWidth": 0, "closestOffsetParentPath": Object { @@ -44080,6 +48027,7 @@ Object { "gap": null, "globalContentBoxForChildren": null, "globalFrameWithTextContent": null, + "gridCellGlobalFrames": null, "hasPositionOffset": false, "hasTransform": false, "htmlElementName": "div", @@ -44091,7 +48039,9 @@ Object { }, "immediateParentProvidesLayout": true, "justifyContent": null, + "justifySelf": null, "layoutSystemForChildren": null, + "layoutSystemForChildrenInherited": false, "margin": Object {}, "naturalHeight": null, "naturalWidth": null, @@ -44100,8 +48050,24 @@ Object { "y": 0, }, "padding": Object {}, + "parentContainerGridProperties": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, + "parentContainerGridPropertiesFromProps": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, "parentFlexDirection": null, "parentFlexGap": 0, + "parentGridCellGlobalFrames": null, + "parentGridFrame": null, "parentHugsOnMainAxis": false, "parentJustifyContent": null, "parentLayoutSystem": "flow", @@ -44824,8 +48790,16 @@ export var App = (props) => { "label": null, "nonRoundedGlobalFrame": null, "specialSizeMeasurements": Object { + "alignContent": null, "alignItems": null, + "alignSelf": null, "borderRadius": null, + "borderWidths": Object { + "bottom": 0, + "left": 0, + "right": 0, + "top": 0, + }, "clientHeight": 0, "clientWidth": 0, "closestOffsetParentPath": Object { @@ -44873,6 +48847,7 @@ export var App = (props) => { "gap": null, "globalContentBoxForChildren": null, "globalFrameWithTextContent": null, + "gridCellGlobalFrames": null, "hasPositionOffset": false, "hasTransform": false, "htmlElementName": "div", @@ -44884,7 +48859,9 @@ export var App = (props) => { }, "immediateParentProvidesLayout": true, "justifyContent": null, + "justifySelf": null, "layoutSystemForChildren": null, + "layoutSystemForChildrenInherited": false, "margin": Object {}, "naturalHeight": null, "naturalWidth": null, @@ -44893,8 +48870,24 @@ export var App = (props) => { "y": 0, }, "padding": Object {}, + "parentContainerGridProperties": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, + "parentContainerGridPropertiesFromProps": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, "parentFlexDirection": null, "parentFlexGap": 0, + "parentGridCellGlobalFrames": null, + "parentGridFrame": null, "parentHugsOnMainAxis": false, "parentJustifyContent": null, "parentLayoutSystem": "flow", @@ -44975,8 +48968,16 @@ export var App = (props) => { "label": null, "nonRoundedGlobalFrame": null, "specialSizeMeasurements": Object { + "alignContent": null, "alignItems": null, + "alignSelf": null, "borderRadius": null, + "borderWidths": Object { + "bottom": 0, + "left": 0, + "right": 0, + "top": 0, + }, "clientHeight": 0, "clientWidth": 0, "closestOffsetParentPath": Object { @@ -45024,6 +49025,7 @@ export var App = (props) => { "gap": null, "globalContentBoxForChildren": null, "globalFrameWithTextContent": null, + "gridCellGlobalFrames": null, "hasPositionOffset": false, "hasTransform": false, "htmlElementName": "div", @@ -45035,7 +49037,9 @@ export var App = (props) => { }, "immediateParentProvidesLayout": true, "justifyContent": null, + "justifySelf": null, "layoutSystemForChildren": null, + "layoutSystemForChildrenInherited": false, "margin": Object {}, "naturalHeight": null, "naturalWidth": null, @@ -45044,8 +49048,24 @@ export var App = (props) => { "y": 0, }, "padding": Object {}, + "parentContainerGridProperties": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, + "parentContainerGridPropertiesFromProps": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, "parentFlexDirection": null, "parentFlexGap": 0, + "parentGridCellGlobalFrames": null, + "parentGridFrame": null, "parentHugsOnMainAxis": false, "parentJustifyContent": null, "parentLayoutSystem": "flow", @@ -45126,8 +49146,16 @@ export var App = (props) => { "label": null, "nonRoundedGlobalFrame": null, "specialSizeMeasurements": Object { + "alignContent": null, "alignItems": null, + "alignSelf": null, "borderRadius": null, + "borderWidths": Object { + "bottom": 0, + "left": 0, + "right": 0, + "top": 0, + }, "clientHeight": 0, "clientWidth": 0, "closestOffsetParentPath": Object { @@ -45175,6 +49203,7 @@ export var App = (props) => { "gap": null, "globalContentBoxForChildren": null, "globalFrameWithTextContent": null, + "gridCellGlobalFrames": null, "hasPositionOffset": false, "hasTransform": false, "htmlElementName": "div", @@ -45186,7 +49215,9 @@ export var App = (props) => { }, "immediateParentProvidesLayout": true, "justifyContent": null, + "justifySelf": null, "layoutSystemForChildren": null, + "layoutSystemForChildrenInherited": false, "margin": Object {}, "naturalHeight": null, "naturalWidth": null, @@ -45195,8 +49226,24 @@ export var App = (props) => { "y": 0, }, "padding": Object {}, + "parentContainerGridProperties": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, + "parentContainerGridPropertiesFromProps": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, "parentFlexDirection": null, "parentFlexGap": 0, + "parentGridCellGlobalFrames": null, + "parentGridFrame": null, "parentHugsOnMainAxis": false, "parentJustifyContent": null, "parentLayoutSystem": "flow", @@ -45277,8 +49324,16 @@ export var App = (props) => { "label": null, "nonRoundedGlobalFrame": null, "specialSizeMeasurements": Object { + "alignContent": null, "alignItems": null, + "alignSelf": null, "borderRadius": null, + "borderWidths": Object { + "bottom": 0, + "left": 0, + "right": 0, + "top": 0, + }, "clientHeight": 0, "clientWidth": 0, "closestOffsetParentPath": Object { @@ -45326,6 +49381,7 @@ export var App = (props) => { "gap": null, "globalContentBoxForChildren": null, "globalFrameWithTextContent": null, + "gridCellGlobalFrames": null, "hasPositionOffset": false, "hasTransform": false, "htmlElementName": "div", @@ -45337,7 +49393,9 @@ export var App = (props) => { }, "immediateParentProvidesLayout": true, "justifyContent": null, + "justifySelf": null, "layoutSystemForChildren": null, + "layoutSystemForChildrenInherited": false, "margin": Object {}, "naturalHeight": null, "naturalWidth": null, @@ -45346,8 +49404,24 @@ export var App = (props) => { "y": 0, }, "padding": Object {}, + "parentContainerGridProperties": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, + "parentContainerGridPropertiesFromProps": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, "parentFlexDirection": null, "parentFlexGap": 0, + "parentGridCellGlobalFrames": null, + "parentGridFrame": null, "parentHugsOnMainAxis": false, "parentJustifyContent": null, "parentLayoutSystem": "flow", @@ -45430,8 +49504,16 @@ export var App = (props) => { "label": null, "nonRoundedGlobalFrame": null, "specialSizeMeasurements": Object { + "alignContent": null, "alignItems": null, + "alignSelf": null, "borderRadius": null, + "borderWidths": Object { + "bottom": 0, + "left": 0, + "right": 0, + "top": 0, + }, "clientHeight": 0, "clientWidth": 0, "closestOffsetParentPath": Object { @@ -45479,6 +49561,7 @@ export var App = (props) => { "gap": null, "globalContentBoxForChildren": null, "globalFrameWithTextContent": null, + "gridCellGlobalFrames": null, "hasPositionOffset": false, "hasTransform": false, "htmlElementName": "div", @@ -45490,7 +49573,9 @@ export var App = (props) => { }, "immediateParentProvidesLayout": true, "justifyContent": null, + "justifySelf": null, "layoutSystemForChildren": null, + "layoutSystemForChildrenInherited": false, "margin": Object {}, "naturalHeight": null, "naturalWidth": null, @@ -45499,8 +49584,24 @@ export var App = (props) => { "y": 0, }, "padding": Object {}, + "parentContainerGridProperties": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, + "parentContainerGridPropertiesFromProps": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, "parentFlexDirection": null, "parentFlexGap": 0, + "parentGridCellGlobalFrames": null, + "parentGridFrame": null, "parentHugsOnMainAxis": false, "parentJustifyContent": null, "parentLayoutSystem": "flow", @@ -45581,8 +49682,16 @@ export var App = (props) => { "label": null, "nonRoundedGlobalFrame": null, "specialSizeMeasurements": Object { + "alignContent": null, "alignItems": null, + "alignSelf": null, "borderRadius": null, + "borderWidths": Object { + "bottom": 0, + "left": 0, + "right": 0, + "top": 0, + }, "clientHeight": 0, "clientWidth": 0, "closestOffsetParentPath": Object { @@ -45630,6 +49739,7 @@ export var App = (props) => { "gap": null, "globalContentBoxForChildren": null, "globalFrameWithTextContent": null, + "gridCellGlobalFrames": null, "hasPositionOffset": false, "hasTransform": false, "htmlElementName": "div", @@ -45641,7 +49751,9 @@ export var App = (props) => { }, "immediateParentProvidesLayout": true, "justifyContent": null, + "justifySelf": null, "layoutSystemForChildren": null, + "layoutSystemForChildrenInherited": false, "margin": Object {}, "naturalHeight": null, "naturalWidth": null, @@ -45650,8 +49762,24 @@ export var App = (props) => { "y": 0, }, "padding": Object {}, + "parentContainerGridProperties": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, + "parentContainerGridPropertiesFromProps": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, "parentFlexDirection": null, "parentFlexGap": 0, + "parentGridCellGlobalFrames": null, + "parentGridFrame": null, "parentHugsOnMainAxis": false, "parentJustifyContent": null, "parentLayoutSystem": "flow", @@ -45732,8 +49860,16 @@ export var App = (props) => { "label": null, "nonRoundedGlobalFrame": null, "specialSizeMeasurements": Object { + "alignContent": null, "alignItems": null, + "alignSelf": null, "borderRadius": null, + "borderWidths": Object { + "bottom": 0, + "left": 0, + "right": 0, + "top": 0, + }, "clientHeight": 0, "clientWidth": 0, "closestOffsetParentPath": Object { @@ -45781,6 +49917,7 @@ export var App = (props) => { "gap": null, "globalContentBoxForChildren": null, "globalFrameWithTextContent": null, + "gridCellGlobalFrames": null, "hasPositionOffset": false, "hasTransform": false, "htmlElementName": "div", @@ -45792,7 +49929,9 @@ export var App = (props) => { }, "immediateParentProvidesLayout": true, "justifyContent": null, + "justifySelf": null, "layoutSystemForChildren": null, + "layoutSystemForChildrenInherited": false, "margin": Object {}, "naturalHeight": null, "naturalWidth": null, @@ -45801,8 +49940,24 @@ export var App = (props) => { "y": 0, }, "padding": Object {}, + "parentContainerGridProperties": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, + "parentContainerGridPropertiesFromProps": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, "parentFlexDirection": null, "parentFlexGap": 0, + "parentGridCellGlobalFrames": null, + "parentGridFrame": null, "parentHugsOnMainAxis": false, "parentJustifyContent": null, "parentLayoutSystem": "flow", @@ -45883,8 +50038,16 @@ export var App = (props) => { "label": null, "nonRoundedGlobalFrame": null, "specialSizeMeasurements": Object { + "alignContent": null, "alignItems": null, + "alignSelf": null, "borderRadius": null, + "borderWidths": Object { + "bottom": 0, + "left": 0, + "right": 0, + "top": 0, + }, "clientHeight": 0, "clientWidth": 0, "closestOffsetParentPath": Object { @@ -45932,6 +50095,7 @@ export var App = (props) => { "gap": null, "globalContentBoxForChildren": null, "globalFrameWithTextContent": null, + "gridCellGlobalFrames": null, "hasPositionOffset": false, "hasTransform": false, "htmlElementName": "div", @@ -45943,7 +50107,9 @@ export var App = (props) => { }, "immediateParentProvidesLayout": true, "justifyContent": null, + "justifySelf": null, "layoutSystemForChildren": null, + "layoutSystemForChildrenInherited": false, "margin": Object {}, "naturalHeight": null, "naturalWidth": null, @@ -45952,8 +50118,24 @@ export var App = (props) => { "y": 0, }, "padding": Object {}, + "parentContainerGridProperties": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, + "parentContainerGridPropertiesFromProps": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, "parentFlexDirection": null, "parentFlexGap": 0, + "parentGridCellGlobalFrames": null, + "parentGridFrame": null, "parentHugsOnMainAxis": false, "parentJustifyContent": null, "parentLayoutSystem": "flow", @@ -46102,8 +50284,16 @@ export var App = (props) => { "label": null, "nonRoundedGlobalFrame": null, "specialSizeMeasurements": Object { + "alignContent": null, "alignItems": null, + "alignSelf": null, "borderRadius": null, + "borderWidths": Object { + "bottom": 0, + "left": 0, + "right": 0, + "top": 0, + }, "clientHeight": 0, "clientWidth": 0, "closestOffsetParentPath": Object { @@ -46151,6 +50341,7 @@ export var App = (props) => { "gap": null, "globalContentBoxForChildren": null, "globalFrameWithTextContent": null, + "gridCellGlobalFrames": null, "hasPositionOffset": false, "hasTransform": false, "htmlElementName": "div", @@ -46162,7 +50353,9 @@ export var App = (props) => { }, "immediateParentProvidesLayout": true, "justifyContent": null, + "justifySelf": null, "layoutSystemForChildren": null, + "layoutSystemForChildrenInherited": false, "margin": Object {}, "naturalHeight": null, "naturalWidth": null, @@ -46171,8 +50364,24 @@ export var App = (props) => { "y": 0, }, "padding": Object {}, + "parentContainerGridProperties": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, + "parentContainerGridPropertiesFromProps": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, "parentFlexDirection": null, "parentFlexGap": 0, + "parentGridCellGlobalFrames": null, + "parentGridFrame": null, "parentHugsOnMainAxis": false, "parentJustifyContent": null, "parentLayoutSystem": "flow", @@ -46282,8 +50491,16 @@ export var App = (props) => { "label": null, "nonRoundedGlobalFrame": null, "specialSizeMeasurements": Object { + "alignContent": null, "alignItems": null, + "alignSelf": null, "borderRadius": null, + "borderWidths": Object { + "bottom": 0, + "left": 0, + "right": 0, + "top": 0, + }, "clientHeight": 0, "clientWidth": 0, "closestOffsetParentPath": Object { @@ -46331,6 +50548,7 @@ export var App = (props) => { "gap": null, "globalContentBoxForChildren": null, "globalFrameWithTextContent": null, + "gridCellGlobalFrames": null, "hasPositionOffset": false, "hasTransform": false, "htmlElementName": "div", @@ -46342,7 +50560,9 @@ export var App = (props) => { }, "immediateParentProvidesLayout": true, "justifyContent": null, + "justifySelf": null, "layoutSystemForChildren": null, + "layoutSystemForChildrenInherited": false, "margin": Object {}, "naturalHeight": null, "naturalWidth": null, @@ -46351,8 +50571,24 @@ export var App = (props) => { "y": 0, }, "padding": Object {}, + "parentContainerGridProperties": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, + "parentContainerGridPropertiesFromProps": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, "parentFlexDirection": null, "parentFlexGap": 0, + "parentGridCellGlobalFrames": null, + "parentGridFrame": null, "parentHugsOnMainAxis": false, "parentJustifyContent": null, "parentLayoutSystem": "flow", @@ -46435,8 +50671,16 @@ export var App = (props) => { "label": null, "nonRoundedGlobalFrame": null, "specialSizeMeasurements": Object { + "alignContent": null, "alignItems": null, + "alignSelf": null, "borderRadius": null, + "borderWidths": Object { + "bottom": 0, + "left": 0, + "right": 0, + "top": 0, + }, "clientHeight": 0, "clientWidth": 0, "closestOffsetParentPath": Object { @@ -46484,6 +50728,7 @@ export var App = (props) => { "gap": null, "globalContentBoxForChildren": null, "globalFrameWithTextContent": null, + "gridCellGlobalFrames": null, "hasPositionOffset": false, "hasTransform": false, "htmlElementName": "div", @@ -46495,7 +50740,9 @@ export var App = (props) => { }, "immediateParentProvidesLayout": true, "justifyContent": null, + "justifySelf": null, "layoutSystemForChildren": null, + "layoutSystemForChildrenInherited": false, "margin": Object {}, "naturalHeight": null, "naturalWidth": null, @@ -46504,8 +50751,24 @@ export var App = (props) => { "y": 0, }, "padding": Object {}, + "parentContainerGridProperties": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, + "parentContainerGridPropertiesFromProps": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, "parentFlexDirection": null, "parentFlexGap": 0, + "parentGridCellGlobalFrames": null, + "parentGridFrame": null, "parentHugsOnMainAxis": false, "parentJustifyContent": null, "parentLayoutSystem": "flow", @@ -46586,8 +50849,16 @@ export var App = (props) => { "label": null, "nonRoundedGlobalFrame": null, "specialSizeMeasurements": Object { + "alignContent": null, "alignItems": null, + "alignSelf": null, "borderRadius": null, + "borderWidths": Object { + "bottom": 0, + "left": 0, + "right": 0, + "top": 0, + }, "clientHeight": 0, "clientWidth": 0, "closestOffsetParentPath": Object { @@ -46635,6 +50906,7 @@ export var App = (props) => { "gap": null, "globalContentBoxForChildren": null, "globalFrameWithTextContent": null, + "gridCellGlobalFrames": null, "hasPositionOffset": false, "hasTransform": false, "htmlElementName": "div", @@ -46646,7 +50918,9 @@ export var App = (props) => { }, "immediateParentProvidesLayout": true, "justifyContent": null, + "justifySelf": null, "layoutSystemForChildren": null, + "layoutSystemForChildrenInherited": false, "margin": Object {}, "naturalHeight": null, "naturalWidth": null, @@ -46655,8 +50929,24 @@ export var App = (props) => { "y": 0, }, "padding": Object {}, + "parentContainerGridProperties": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, + "parentContainerGridPropertiesFromProps": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, "parentFlexDirection": null, "parentFlexGap": 0, + "parentGridCellGlobalFrames": null, + "parentGridFrame": null, "parentHugsOnMainAxis": false, "parentJustifyContent": null, "parentLayoutSystem": "flow", @@ -46737,8 +51027,16 @@ export var App = (props) => { "label": null, "nonRoundedGlobalFrame": null, "specialSizeMeasurements": Object { + "alignContent": null, "alignItems": null, + "alignSelf": null, "borderRadius": null, + "borderWidths": Object { + "bottom": 0, + "left": 0, + "right": 0, + "top": 0, + }, "clientHeight": 0, "clientWidth": 0, "closestOffsetParentPath": Object { @@ -46786,6 +51084,7 @@ export var App = (props) => { "gap": null, "globalContentBoxForChildren": null, "globalFrameWithTextContent": null, + "gridCellGlobalFrames": null, "hasPositionOffset": false, "hasTransform": false, "htmlElementName": "div", @@ -46797,7 +51096,9 @@ export var App = (props) => { }, "immediateParentProvidesLayout": true, "justifyContent": null, + "justifySelf": null, "layoutSystemForChildren": null, + "layoutSystemForChildrenInherited": false, "margin": Object {}, "naturalHeight": null, "naturalWidth": null, @@ -46806,8 +51107,24 @@ export var App = (props) => { "y": 0, }, "padding": Object {}, + "parentContainerGridProperties": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, + "parentContainerGridPropertiesFromProps": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, "parentFlexDirection": null, "parentFlexGap": 0, + "parentGridCellGlobalFrames": null, + "parentGridFrame": null, "parentHugsOnMainAxis": false, "parentJustifyContent": null, "parentLayoutSystem": "flow", @@ -46888,8 +51205,16 @@ export var App = (props) => { "label": null, "nonRoundedGlobalFrame": null, "specialSizeMeasurements": Object { + "alignContent": null, "alignItems": null, + "alignSelf": null, "borderRadius": null, + "borderWidths": Object { + "bottom": 0, + "left": 0, + "right": 0, + "top": 0, + }, "clientHeight": 0, "clientWidth": 0, "closestOffsetParentPath": Object { @@ -46937,6 +51262,7 @@ export var App = (props) => { "gap": null, "globalContentBoxForChildren": null, "globalFrameWithTextContent": null, + "gridCellGlobalFrames": null, "hasPositionOffset": false, "hasTransform": false, "htmlElementName": "div", @@ -46948,7 +51274,9 @@ export var App = (props) => { }, "immediateParentProvidesLayout": true, "justifyContent": null, + "justifySelf": null, "layoutSystemForChildren": null, + "layoutSystemForChildrenInherited": false, "margin": Object {}, "naturalHeight": null, "naturalWidth": null, @@ -46957,8 +51285,24 @@ export var App = (props) => { "y": 0, }, "padding": Object {}, + "parentContainerGridProperties": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, + "parentContainerGridPropertiesFromProps": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, "parentFlexDirection": null, "parentFlexGap": 0, + "parentGridCellGlobalFrames": null, + "parentGridFrame": null, "parentHugsOnMainAxis": false, "parentJustifyContent": null, "parentLayoutSystem": "flow", @@ -47039,8 +51383,16 @@ export var App = (props) => { "label": null, "nonRoundedGlobalFrame": null, "specialSizeMeasurements": Object { + "alignContent": null, "alignItems": null, + "alignSelf": null, "borderRadius": null, + "borderWidths": Object { + "bottom": 0, + "left": 0, + "right": 0, + "top": 0, + }, "clientHeight": 0, "clientWidth": 0, "closestOffsetParentPath": Object { @@ -47088,6 +51440,7 @@ export var App = (props) => { "gap": null, "globalContentBoxForChildren": null, "globalFrameWithTextContent": null, + "gridCellGlobalFrames": null, "hasPositionOffset": false, "hasTransform": false, "htmlElementName": "div", @@ -47099,7 +51452,9 @@ export var App = (props) => { }, "immediateParentProvidesLayout": true, "justifyContent": null, + "justifySelf": null, "layoutSystemForChildren": null, + "layoutSystemForChildrenInherited": false, "margin": Object {}, "naturalHeight": null, "naturalWidth": null, @@ -47108,8 +51463,24 @@ export var App = (props) => { "y": 0, }, "padding": Object {}, + "parentContainerGridProperties": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, + "parentContainerGridPropertiesFromProps": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, "parentFlexDirection": null, "parentFlexGap": 0, + "parentGridCellGlobalFrames": null, + "parentGridFrame": null, "parentHugsOnMainAxis": false, "parentJustifyContent": null, "parentLayoutSystem": "flow", @@ -47190,8 +51561,16 @@ export var App = (props) => { "label": null, "nonRoundedGlobalFrame": null, "specialSizeMeasurements": Object { + "alignContent": null, "alignItems": null, + "alignSelf": null, "borderRadius": null, + "borderWidths": Object { + "bottom": 0, + "left": 0, + "right": 0, + "top": 0, + }, "clientHeight": 0, "clientWidth": 0, "closestOffsetParentPath": Object { @@ -47239,6 +51618,7 @@ export var App = (props) => { "gap": null, "globalContentBoxForChildren": null, "globalFrameWithTextContent": null, + "gridCellGlobalFrames": null, "hasPositionOffset": false, "hasTransform": false, "htmlElementName": "div", @@ -47250,7 +51630,9 @@ export var App = (props) => { }, "immediateParentProvidesLayout": true, "justifyContent": null, + "justifySelf": null, "layoutSystemForChildren": null, + "layoutSystemForChildrenInherited": false, "margin": Object {}, "naturalHeight": null, "naturalWidth": null, @@ -47259,8 +51641,24 @@ export var App = (props) => { "y": 0, }, "padding": Object {}, + "parentContainerGridProperties": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, + "parentContainerGridPropertiesFromProps": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, "parentFlexDirection": null, "parentFlexGap": 0, + "parentGridCellGlobalFrames": null, + "parentGridFrame": null, "parentHugsOnMainAxis": false, "parentJustifyContent": null, "parentLayoutSystem": "flow", @@ -47341,8 +51739,16 @@ export var App = (props) => { "label": null, "nonRoundedGlobalFrame": null, "specialSizeMeasurements": Object { + "alignContent": null, "alignItems": null, + "alignSelf": null, "borderRadius": null, + "borderWidths": Object { + "bottom": 0, + "left": 0, + "right": 0, + "top": 0, + }, "clientHeight": 0, "clientWidth": 0, "closestOffsetParentPath": Object { @@ -47390,6 +51796,7 @@ export var App = (props) => { "gap": null, "globalContentBoxForChildren": null, "globalFrameWithTextContent": null, + "gridCellGlobalFrames": null, "hasPositionOffset": false, "hasTransform": false, "htmlElementName": "div", @@ -47401,7 +51808,9 @@ export var App = (props) => { }, "immediateParentProvidesLayout": true, "justifyContent": null, + "justifySelf": null, "layoutSystemForChildren": null, + "layoutSystemForChildrenInherited": false, "margin": Object {}, "naturalHeight": null, "naturalWidth": null, @@ -47410,159 +51819,24 @@ export var App = (props) => { "y": 0, }, "padding": Object {}, - "parentFlexDirection": null, - "parentFlexGap": 0, - "parentHugsOnMainAxis": false, - "parentJustifyContent": null, - "parentLayoutSystem": "flow", - "parentPadding": Object {}, - "parentTextDirection": "ltr", - "position": null, - "providesBoundsForAbsoluteChildren": false, - "renderedChildrenCount": 0, - "rowGap": null, - "textBounds": null, - "textDecorationLine": null, - "usesParentBounds": false, - }, - "textContent": null, - }, - "utopia-storyboard-uid/scene-aaa/app-entity:74b/aeb": Object { - "assignedToProp": null, - "attributeMetadata": Object {}, - "componentInstance": true, - "computedStyle": Object {}, - "conditionValue": "not-a-conditional", - "earlyReturn": null, - "element": Object { - "type": "RIGHT", - "value": Object { - "children": Array [], - "name": Object { - "baseVariable": "OriginallyAssigned2", - "propertyPath": Object { - "propertyElements": Array [], - }, - }, - "props": Array [ - Object { - "comments": Object { - "leadingComments": Array [], - "trailingComments": Array [], - }, - "key": "data-uid", - "type": "JSX_ATTRIBUTES_ENTRY", - "value": Object { - "comments": Object { - "leadingComments": Array [], - "trailingComments": Array [], - }, - "type": "ATTRIBUTE_VALUE", - "uid": "", - "value": "aeb", - }, - }, - ], - "type": "JSX_ELEMENT", - "uid": "", - }, - }, - "elementPath": Object { - "parts": Array [ - Array [ - "utopia-storyboard-uid", - "scene-aaa", - "app-entity", - ], - Array [ - "74b", - "aeb", - ], - ], - "type": "elementpath", - }, - "globalFrame": null, - "importInfo": Object { - "exportedName": "OriginallyAssigned1", - "filePath": "/reexportspecificnamed", - "type": "IMPORTED_ORIGIN", - "variableName": "OriginallyAssigned2", - }, - "isEmotionOrStyledComponent": false, - "label": null, - "nonRoundedGlobalFrame": null, - "specialSizeMeasurements": Object { - "alignItems": null, - "borderRadius": null, - "clientHeight": 0, - "clientWidth": 0, - "closestOffsetParentPath": Object { - "parts": Array [], - "type": "elementpath", - }, - "columnGap": null, - "computedHugProperty": Object { - "height": null, - "width": null, - }, - "containerGridProperties": Object { + "parentContainerGridProperties": Object { "gridAutoColumns": null, "gridAutoFlow": null, "gridAutoRows": null, "gridTemplateColumns": null, "gridTemplateRows": null, }, - "containerGridPropertiesFromProps": Object { + "parentContainerGridPropertiesFromProps": Object { "gridAutoColumns": null, "gridAutoFlow": null, "gridAutoRows": null, "gridTemplateColumns": null, "gridTemplateRows": null, }, - "coordinateSystemBounds": null, - "display": "initial", - "elementGridProperties": Object { - "gridColumnEnd": null, - "gridColumnStart": null, - "gridRowEnd": null, - "gridRowStart": null, - }, - "elementGridPropertiesFromProps": Object { - "gridColumnEnd": null, - "gridColumnStart": null, - "gridRowEnd": null, - "gridRowStart": null, - }, - "flexDirection": null, - "float": "none", - "fontSize": null, - "fontStyle": null, - "fontWeight": null, - "gap": null, - "globalContentBoxForChildren": null, - "globalFrameWithTextContent": null, - "hasPositionOffset": false, - "hasTransform": false, - "htmlElementName": "div", - "immediateParentBounds": Object { - "height": 0, - "width": 0, - "x": 0, - "y": 0, - }, - "immediateParentProvidesLayout": true, - "justifyContent": null, - "layoutSystemForChildren": null, - "margin": Object {}, - "naturalHeight": null, - "naturalWidth": null, - "offset": Object { - "x": 0, - "y": 0, - }, - "padding": Object {}, "parentFlexDirection": null, "parentFlexGap": 0, + "parentGridCellGlobalFrames": null, + "parentGridFrame": null, "parentHugsOnMainAxis": false, "parentJustifyContent": null, "parentLayoutSystem": "flow", @@ -47578,7 +51852,7 @@ export var App = (props) => { }, "textContent": null, }, - "utopia-storyboard-uid/scene-aaa/app-entity:74b/bb2": Object { + "utopia-storyboard-uid/scene-aaa/app-entity:74b/aeb": Object { "assignedToProp": null, "attributeMetadata": Object {}, "componentInstance": true, @@ -47590,7 +51864,7 @@ export var App = (props) => { "value": Object { "children": Array [], "name": Object { - "baseVariable": "FirstInStructure", + "baseVariable": "OriginallyAssigned2", "propertyPath": Object { "propertyElements": Array [], }, @@ -47610,7 +51884,7 @@ export var App = (props) => { }, "type": "ATTRIBUTE_VALUE", "uid": "", - "value": "bb2", + "value": "aeb", }, }, ], @@ -47627,24 +51901,32 @@ export var App = (props) => { ], Array [ "74b", - "bb2", + "aeb", ], ], "type": "elementpath", }, "globalFrame": null, "importInfo": Object { - "exportedName": "FirstInStructure", - "filePath": "/destructuredassignment", + "exportedName": "OriginallyAssigned1", + "filePath": "/reexportspecificnamed", "type": "IMPORTED_ORIGIN", - "variableName": "FirstInStructure", + "variableName": "OriginallyAssigned2", }, "isEmotionOrStyledComponent": false, "label": null, "nonRoundedGlobalFrame": null, "specialSizeMeasurements": Object { + "alignContent": null, "alignItems": null, + "alignSelf": null, "borderRadius": null, + "borderWidths": Object { + "bottom": 0, + "left": 0, + "right": 0, + "top": 0, + }, "clientHeight": 0, "clientWidth": 0, "closestOffsetParentPath": Object { @@ -47692,6 +51974,7 @@ export var App = (props) => { "gap": null, "globalContentBoxForChildren": null, "globalFrameWithTextContent": null, + "gridCellGlobalFrames": null, "hasPositionOffset": false, "hasTransform": false, "htmlElementName": "div", @@ -47703,7 +51986,9 @@ export var App = (props) => { }, "immediateParentProvidesLayout": true, "justifyContent": null, + "justifySelf": null, "layoutSystemForChildren": null, + "layoutSystemForChildrenInherited": false, "margin": Object {}, "naturalHeight": null, "naturalWidth": null, @@ -47712,8 +51997,202 @@ export var App = (props) => { "y": 0, }, "padding": Object {}, + "parentContainerGridProperties": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, + "parentContainerGridPropertiesFromProps": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, "parentFlexDirection": null, "parentFlexGap": 0, + "parentGridCellGlobalFrames": null, + "parentGridFrame": null, + "parentHugsOnMainAxis": false, + "parentJustifyContent": null, + "parentLayoutSystem": "flow", + "parentPadding": Object {}, + "parentTextDirection": "ltr", + "position": null, + "providesBoundsForAbsoluteChildren": false, + "renderedChildrenCount": 0, + "rowGap": null, + "textBounds": null, + "textDecorationLine": null, + "usesParentBounds": false, + }, + "textContent": null, + }, + "utopia-storyboard-uid/scene-aaa/app-entity:74b/bb2": Object { + "assignedToProp": null, + "attributeMetadata": Object {}, + "componentInstance": true, + "computedStyle": Object {}, + "conditionValue": "not-a-conditional", + "earlyReturn": null, + "element": Object { + "type": "RIGHT", + "value": Object { + "children": Array [], + "name": Object { + "baseVariable": "FirstInStructure", + "propertyPath": Object { + "propertyElements": Array [], + }, + }, + "props": Array [ + Object { + "comments": Object { + "leadingComments": Array [], + "trailingComments": Array [], + }, + "key": "data-uid", + "type": "JSX_ATTRIBUTES_ENTRY", + "value": Object { + "comments": Object { + "leadingComments": Array [], + "trailingComments": Array [], + }, + "type": "ATTRIBUTE_VALUE", + "uid": "", + "value": "bb2", + }, + }, + ], + "type": "JSX_ELEMENT", + "uid": "", + }, + }, + "elementPath": Object { + "parts": Array [ + Array [ + "utopia-storyboard-uid", + "scene-aaa", + "app-entity", + ], + Array [ + "74b", + "bb2", + ], + ], + "type": "elementpath", + }, + "globalFrame": null, + "importInfo": Object { + "exportedName": "FirstInStructure", + "filePath": "/destructuredassignment", + "type": "IMPORTED_ORIGIN", + "variableName": "FirstInStructure", + }, + "isEmotionOrStyledComponent": false, + "label": null, + "nonRoundedGlobalFrame": null, + "specialSizeMeasurements": Object { + "alignContent": null, + "alignItems": null, + "alignSelf": null, + "borderRadius": null, + "borderWidths": Object { + "bottom": 0, + "left": 0, + "right": 0, + "top": 0, + }, + "clientHeight": 0, + "clientWidth": 0, + "closestOffsetParentPath": Object { + "parts": Array [], + "type": "elementpath", + }, + "columnGap": null, + "computedHugProperty": Object { + "height": null, + "width": null, + }, + "containerGridProperties": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, + "containerGridPropertiesFromProps": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, + "coordinateSystemBounds": null, + "display": "initial", + "elementGridProperties": Object { + "gridColumnEnd": null, + "gridColumnStart": null, + "gridRowEnd": null, + "gridRowStart": null, + }, + "elementGridPropertiesFromProps": Object { + "gridColumnEnd": null, + "gridColumnStart": null, + "gridRowEnd": null, + "gridRowStart": null, + }, + "flexDirection": null, + "float": "none", + "fontSize": null, + "fontStyle": null, + "fontWeight": null, + "gap": null, + "globalContentBoxForChildren": null, + "globalFrameWithTextContent": null, + "gridCellGlobalFrames": null, + "hasPositionOffset": false, + "hasTransform": false, + "htmlElementName": "div", + "immediateParentBounds": Object { + "height": 0, + "width": 0, + "x": 0, + "y": 0, + }, + "immediateParentProvidesLayout": true, + "justifyContent": null, + "justifySelf": null, + "layoutSystemForChildren": null, + "layoutSystemForChildrenInherited": false, + "margin": Object {}, + "naturalHeight": null, + "naturalWidth": null, + "offset": Object { + "x": 0, + "y": 0, + }, + "padding": Object {}, + "parentContainerGridProperties": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, + "parentContainerGridPropertiesFromProps": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, + "parentFlexDirection": null, + "parentFlexGap": 0, + "parentGridCellGlobalFrames": null, + "parentGridFrame": null, "parentHugsOnMainAxis": false, "parentJustifyContent": null, "parentLayoutSystem": "flow", @@ -47794,8 +52273,16 @@ export var App = (props) => { "label": null, "nonRoundedGlobalFrame": null, "specialSizeMeasurements": Object { + "alignContent": null, "alignItems": null, + "alignSelf": null, "borderRadius": null, + "borderWidths": Object { + "bottom": 0, + "left": 0, + "right": 0, + "top": 0, + }, "clientHeight": 0, "clientWidth": 0, "closestOffsetParentPath": Object { @@ -47843,6 +52330,7 @@ export var App = (props) => { "gap": null, "globalContentBoxForChildren": null, "globalFrameWithTextContent": null, + "gridCellGlobalFrames": null, "hasPositionOffset": false, "hasTransform": false, "htmlElementName": "div", @@ -47854,7 +52342,9 @@ export var App = (props) => { }, "immediateParentProvidesLayout": true, "justifyContent": null, + "justifySelf": null, "layoutSystemForChildren": null, + "layoutSystemForChildrenInherited": false, "margin": Object {}, "naturalHeight": null, "naturalWidth": null, @@ -47863,8 +52353,24 @@ export var App = (props) => { "y": 0, }, "padding": Object {}, + "parentContainerGridProperties": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, + "parentContainerGridPropertiesFromProps": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, "parentFlexDirection": null, "parentFlexGap": 0, + "parentGridCellGlobalFrames": null, + "parentGridFrame": null, "parentHugsOnMainAxis": false, "parentJustifyContent": null, "parentLayoutSystem": "flow", @@ -47945,8 +52451,16 @@ export var App = (props) => { "label": null, "nonRoundedGlobalFrame": null, "specialSizeMeasurements": Object { + "alignContent": null, "alignItems": null, + "alignSelf": null, "borderRadius": null, + "borderWidths": Object { + "bottom": 0, + "left": 0, + "right": 0, + "top": 0, + }, "clientHeight": 0, "clientWidth": 0, "closestOffsetParentPath": Object { @@ -47994,6 +52508,7 @@ export var App = (props) => { "gap": null, "globalContentBoxForChildren": null, "globalFrameWithTextContent": null, + "gridCellGlobalFrames": null, "hasPositionOffset": false, "hasTransform": false, "htmlElementName": "div", @@ -48005,7 +52520,9 @@ export var App = (props) => { }, "immediateParentProvidesLayout": true, "justifyContent": null, + "justifySelf": null, "layoutSystemForChildren": null, + "layoutSystemForChildrenInherited": false, "margin": Object {}, "naturalHeight": null, "naturalWidth": null, @@ -48014,8 +52531,24 @@ export var App = (props) => { "y": 0, }, "padding": Object {}, + "parentContainerGridProperties": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, + "parentContainerGridPropertiesFromProps": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, "parentFlexDirection": null, "parentFlexGap": 0, + "parentGridCellGlobalFrames": null, + "parentGridFrame": null, "parentHugsOnMainAxis": false, "parentJustifyContent": null, "parentLayoutSystem": "flow", @@ -48048,6 +52581,7 @@ exports[`UiJsxCanvas render renders a component used in an arbitrary block corre data-path=\\"utopia-storyboard-uid/scene-aaa\\" style=\\" overflow: hidden; + container: scene / inline-size; position: absolute; background-color: var(--utopitheme-emphasizedBackground); box-shadow: 0px 1px 2px 0px var(--utopitheme-shadow90), @@ -48235,8 +52769,16 @@ Object { "label": null, "nonRoundedGlobalFrame": null, "specialSizeMeasurements": Object { + "alignContent": null, "alignItems": null, + "alignSelf": null, "borderRadius": null, + "borderWidths": Object { + "bottom": 0, + "left": 0, + "right": 0, + "top": 0, + }, "clientHeight": 0, "clientWidth": 0, "closestOffsetParentPath": Object { @@ -48284,6 +52826,7 @@ Object { "gap": null, "globalContentBoxForChildren": null, "globalFrameWithTextContent": null, + "gridCellGlobalFrames": null, "hasPositionOffset": false, "hasTransform": false, "htmlElementName": "div", @@ -48295,7 +52838,9 @@ Object { }, "immediateParentProvidesLayout": true, "justifyContent": null, + "justifySelf": null, "layoutSystemForChildren": null, + "layoutSystemForChildrenInherited": false, "margin": Object {}, "naturalHeight": null, "naturalWidth": null, @@ -48304,8 +52849,24 @@ Object { "y": 0, }, "padding": Object {}, + "parentContainerGridProperties": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, + "parentContainerGridPropertiesFromProps": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, "parentFlexDirection": null, "parentFlexGap": 0, + "parentGridCellGlobalFrames": null, + "parentGridFrame": null, "parentHugsOnMainAxis": false, "parentJustifyContent": null, "parentLayoutSystem": "flow", @@ -48404,8 +52965,16 @@ Object { "label": null, "nonRoundedGlobalFrame": null, "specialSizeMeasurements": Object { + "alignContent": null, "alignItems": null, + "alignSelf": null, "borderRadius": null, + "borderWidths": Object { + "bottom": 0, + "left": 0, + "right": 0, + "top": 0, + }, "clientHeight": 0, "clientWidth": 0, "closestOffsetParentPath": Object { @@ -48453,6 +53022,7 @@ Object { "gap": null, "globalContentBoxForChildren": null, "globalFrameWithTextContent": null, + "gridCellGlobalFrames": null, "hasPositionOffset": false, "hasTransform": false, "htmlElementName": "div", @@ -48464,7 +53034,9 @@ Object { }, "immediateParentProvidesLayout": true, "justifyContent": null, + "justifySelf": null, "layoutSystemForChildren": null, + "layoutSystemForChildrenInherited": false, "margin": Object {}, "naturalHeight": null, "naturalWidth": null, @@ -48473,8 +53045,24 @@ Object { "y": 0, }, "padding": Object {}, + "parentContainerGridProperties": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, + "parentContainerGridPropertiesFromProps": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, "parentFlexDirection": null, "parentFlexGap": 0, + "parentGridCellGlobalFrames": null, + "parentGridFrame": null, "parentHugsOnMainAxis": false, "parentJustifyContent": null, "parentLayoutSystem": "flow", @@ -48797,8 +53385,16 @@ Object { "label": null, "nonRoundedGlobalFrame": null, "specialSizeMeasurements": Object { + "alignContent": null, "alignItems": null, + "alignSelf": null, "borderRadius": null, + "borderWidths": Object { + "bottom": 0, + "left": 0, + "right": 0, + "top": 0, + }, "clientHeight": 0, "clientWidth": 0, "closestOffsetParentPath": Object { @@ -48846,6 +53442,7 @@ Object { "gap": null, "globalContentBoxForChildren": null, "globalFrameWithTextContent": null, + "gridCellGlobalFrames": null, "hasPositionOffset": false, "hasTransform": false, "htmlElementName": "div", @@ -48857,7 +53454,9 @@ Object { }, "immediateParentProvidesLayout": true, "justifyContent": null, + "justifySelf": null, "layoutSystemForChildren": null, + "layoutSystemForChildrenInherited": false, "margin": Object {}, "naturalHeight": null, "naturalWidth": null, @@ -48866,8 +53465,24 @@ Object { "y": 0, }, "padding": Object {}, + "parentContainerGridProperties": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, + "parentContainerGridPropertiesFromProps": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, "parentFlexDirection": null, "parentFlexGap": 0, + "parentGridCellGlobalFrames": null, + "parentGridFrame": null, "parentHugsOnMainAxis": false, "parentJustifyContent": null, "parentLayoutSystem": "flow", @@ -49155,8 +53770,16 @@ Object { "label": null, "nonRoundedGlobalFrame": null, "specialSizeMeasurements": Object { + "alignContent": null, "alignItems": null, + "alignSelf": null, "borderRadius": null, + "borderWidths": Object { + "bottom": 0, + "left": 0, + "right": 0, + "top": 0, + }, "clientHeight": 0, "clientWidth": 0, "closestOffsetParentPath": Object { @@ -49204,6 +53827,7 @@ Object { "gap": null, "globalContentBoxForChildren": null, "globalFrameWithTextContent": null, + "gridCellGlobalFrames": null, "hasPositionOffset": false, "hasTransform": false, "htmlElementName": "div", @@ -49215,7 +53839,9 @@ Object { }, "immediateParentProvidesLayout": true, "justifyContent": null, + "justifySelf": null, "layoutSystemForChildren": null, + "layoutSystemForChildrenInherited": false, "margin": Object {}, "naturalHeight": null, "naturalWidth": null, @@ -49224,8 +53850,24 @@ Object { "y": 0, }, "padding": Object {}, + "parentContainerGridProperties": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, + "parentContainerGridPropertiesFromProps": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, "parentFlexDirection": null, "parentFlexGap": 0, + "parentGridCellGlobalFrames": null, + "parentGridFrame": null, "parentHugsOnMainAxis": false, "parentJustifyContent": null, "parentLayoutSystem": "flow", @@ -49372,8 +54014,16 @@ Object { "label": null, "nonRoundedGlobalFrame": null, "specialSizeMeasurements": Object { + "alignContent": null, "alignItems": null, + "alignSelf": null, "borderRadius": null, + "borderWidths": Object { + "bottom": 0, + "left": 0, + "right": 0, + "top": 0, + }, "clientHeight": 0, "clientWidth": 0, "closestOffsetParentPath": Object { @@ -49421,6 +54071,7 @@ Object { "gap": null, "globalContentBoxForChildren": null, "globalFrameWithTextContent": null, + "gridCellGlobalFrames": null, "hasPositionOffset": false, "hasTransform": false, "htmlElementName": "div", @@ -49432,7 +54083,9 @@ Object { }, "immediateParentProvidesLayout": true, "justifyContent": null, + "justifySelf": null, "layoutSystemForChildren": null, + "layoutSystemForChildrenInherited": false, "margin": Object {}, "naturalHeight": null, "naturalWidth": null, @@ -49441,8 +54094,24 @@ Object { "y": 0, }, "padding": Object {}, + "parentContainerGridProperties": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, + "parentContainerGridPropertiesFromProps": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, "parentFlexDirection": null, "parentFlexGap": 0, + "parentGridCellGlobalFrames": null, + "parentGridFrame": null, "parentHugsOnMainAxis": false, "parentJustifyContent": null, "parentLayoutSystem": "flow", @@ -49589,8 +54258,16 @@ Object { "label": null, "nonRoundedGlobalFrame": null, "specialSizeMeasurements": Object { + "alignContent": null, "alignItems": null, + "alignSelf": null, "borderRadius": null, + "borderWidths": Object { + "bottom": 0, + "left": 0, + "right": 0, + "top": 0, + }, "clientHeight": 0, "clientWidth": 0, "closestOffsetParentPath": Object { @@ -49638,6 +54315,7 @@ Object { "gap": null, "globalContentBoxForChildren": null, "globalFrameWithTextContent": null, + "gridCellGlobalFrames": null, "hasPositionOffset": false, "hasTransform": false, "htmlElementName": "div", @@ -49649,7 +54327,9 @@ Object { }, "immediateParentProvidesLayout": true, "justifyContent": null, + "justifySelf": null, "layoutSystemForChildren": null, + "layoutSystemForChildrenInherited": false, "margin": Object {}, "naturalHeight": null, "naturalWidth": null, @@ -49658,8 +54338,24 @@ Object { "y": 0, }, "padding": Object {}, + "parentContainerGridProperties": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, + "parentContainerGridPropertiesFromProps": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, "parentFlexDirection": null, "parentFlexGap": 0, + "parentGridCellGlobalFrames": null, + "parentGridFrame": null, "parentHugsOnMainAxis": false, "parentJustifyContent": null, "parentLayoutSystem": "flow", @@ -49806,8 +54502,16 @@ Object { "label": null, "nonRoundedGlobalFrame": null, "specialSizeMeasurements": Object { + "alignContent": null, "alignItems": null, + "alignSelf": null, "borderRadius": null, + "borderWidths": Object { + "bottom": 0, + "left": 0, + "right": 0, + "top": 0, + }, "clientHeight": 0, "clientWidth": 0, "closestOffsetParentPath": Object { @@ -49855,6 +54559,7 @@ Object { "gap": null, "globalContentBoxForChildren": null, "globalFrameWithTextContent": null, + "gridCellGlobalFrames": null, "hasPositionOffset": false, "hasTransform": false, "htmlElementName": "div", @@ -49866,7 +54571,9 @@ Object { }, "immediateParentProvidesLayout": true, "justifyContent": null, + "justifySelf": null, "layoutSystemForChildren": null, + "layoutSystemForChildrenInherited": false, "margin": Object {}, "naturalHeight": null, "naturalWidth": null, @@ -49875,8 +54582,24 @@ Object { "y": 0, }, "padding": Object {}, + "parentContainerGridProperties": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, + "parentContainerGridPropertiesFromProps": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, "parentFlexDirection": null, "parentFlexGap": 0, + "parentGridCellGlobalFrames": null, + "parentGridFrame": null, "parentHugsOnMainAxis": false, "parentJustifyContent": null, "parentLayoutSystem": "flow", @@ -49909,6 +54632,7 @@ exports[`UiJsxCanvas render renders a component used in an arbitrary block corre data-path=\\"utopia-storyboard-uid/scene-aaa\\" style=\\" overflow: hidden; + container: scene / inline-size; position: absolute; background-color: var(--utopitheme-emphasizedBackground); box-shadow: 0px 1px 2px 0px var(--utopitheme-shadow90), @@ -50096,8 +54820,16 @@ Object { "label": null, "nonRoundedGlobalFrame": null, "specialSizeMeasurements": Object { + "alignContent": null, "alignItems": null, + "alignSelf": null, "borderRadius": null, + "borderWidths": Object { + "bottom": 0, + "left": 0, + "right": 0, + "top": 0, + }, "clientHeight": 0, "clientWidth": 0, "closestOffsetParentPath": Object { @@ -50145,6 +54877,7 @@ Object { "gap": null, "globalContentBoxForChildren": null, "globalFrameWithTextContent": null, + "gridCellGlobalFrames": null, "hasPositionOffset": false, "hasTransform": false, "htmlElementName": "div", @@ -50156,7 +54889,9 @@ Object { }, "immediateParentProvidesLayout": true, "justifyContent": null, + "justifySelf": null, "layoutSystemForChildren": null, + "layoutSystemForChildrenInherited": false, "margin": Object {}, "naturalHeight": null, "naturalWidth": null, @@ -50165,8 +54900,24 @@ Object { "y": 0, }, "padding": Object {}, + "parentContainerGridProperties": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, + "parentContainerGridPropertiesFromProps": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, "parentFlexDirection": null, "parentFlexGap": 0, + "parentGridCellGlobalFrames": null, + "parentGridFrame": null, "parentHugsOnMainAxis": false, "parentJustifyContent": null, "parentLayoutSystem": "flow", @@ -50265,8 +55016,16 @@ Object { "label": null, "nonRoundedGlobalFrame": null, "specialSizeMeasurements": Object { + "alignContent": null, "alignItems": null, + "alignSelf": null, "borderRadius": null, + "borderWidths": Object { + "bottom": 0, + "left": 0, + "right": 0, + "top": 0, + }, "clientHeight": 0, "clientWidth": 0, "closestOffsetParentPath": Object { @@ -50314,6 +55073,7 @@ Object { "gap": null, "globalContentBoxForChildren": null, "globalFrameWithTextContent": null, + "gridCellGlobalFrames": null, "hasPositionOffset": false, "hasTransform": false, "htmlElementName": "div", @@ -50325,7 +55085,9 @@ Object { }, "immediateParentProvidesLayout": true, "justifyContent": null, + "justifySelf": null, "layoutSystemForChildren": null, + "layoutSystemForChildrenInherited": false, "margin": Object {}, "naturalHeight": null, "naturalWidth": null, @@ -50334,8 +55096,24 @@ Object { "y": 0, }, "padding": Object {}, + "parentContainerGridProperties": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, + "parentContainerGridPropertiesFromProps": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, "parentFlexDirection": null, "parentFlexGap": 0, + "parentGridCellGlobalFrames": null, + "parentGridFrame": null, "parentHugsOnMainAxis": false, "parentJustifyContent": null, "parentLayoutSystem": "flow", @@ -50657,8 +55435,16 @@ Object { "label": null, "nonRoundedGlobalFrame": null, "specialSizeMeasurements": Object { + "alignContent": null, "alignItems": null, + "alignSelf": null, "borderRadius": null, + "borderWidths": Object { + "bottom": 0, + "left": 0, + "right": 0, + "top": 0, + }, "clientHeight": 0, "clientWidth": 0, "closestOffsetParentPath": Object { @@ -50706,6 +55492,7 @@ Object { "gap": null, "globalContentBoxForChildren": null, "globalFrameWithTextContent": null, + "gridCellGlobalFrames": null, "hasPositionOffset": false, "hasTransform": false, "htmlElementName": "div", @@ -50717,7 +55504,9 @@ Object { }, "immediateParentProvidesLayout": true, "justifyContent": null, + "justifySelf": null, "layoutSystemForChildren": null, + "layoutSystemForChildrenInherited": false, "margin": Object {}, "naturalHeight": null, "naturalWidth": null, @@ -50726,8 +55515,24 @@ Object { "y": 0, }, "padding": Object {}, + "parentContainerGridProperties": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, + "parentContainerGridPropertiesFromProps": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, "parentFlexDirection": null, "parentFlexGap": 0, + "parentGridCellGlobalFrames": null, + "parentGridFrame": null, "parentHugsOnMainAxis": false, "parentJustifyContent": null, "parentLayoutSystem": "flow", @@ -51014,8 +55819,16 @@ Object { "label": null, "nonRoundedGlobalFrame": null, "specialSizeMeasurements": Object { + "alignContent": null, "alignItems": null, + "alignSelf": null, "borderRadius": null, + "borderWidths": Object { + "bottom": 0, + "left": 0, + "right": 0, + "top": 0, + }, "clientHeight": 0, "clientWidth": 0, "closestOffsetParentPath": Object { @@ -51063,6 +55876,7 @@ Object { "gap": null, "globalContentBoxForChildren": null, "globalFrameWithTextContent": null, + "gridCellGlobalFrames": null, "hasPositionOffset": false, "hasTransform": false, "htmlElementName": "div", @@ -51074,7 +55888,9 @@ Object { }, "immediateParentProvidesLayout": true, "justifyContent": null, + "justifySelf": null, "layoutSystemForChildren": null, + "layoutSystemForChildrenInherited": false, "margin": Object {}, "naturalHeight": null, "naturalWidth": null, @@ -51083,8 +55899,24 @@ Object { "y": 0, }, "padding": Object {}, + "parentContainerGridProperties": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, + "parentContainerGridPropertiesFromProps": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, "parentFlexDirection": null, "parentFlexGap": 0, + "parentGridCellGlobalFrames": null, + "parentGridFrame": null, "parentHugsOnMainAxis": false, "parentJustifyContent": null, "parentLayoutSystem": "flow", @@ -51231,8 +56063,16 @@ Object { "label": null, "nonRoundedGlobalFrame": null, "specialSizeMeasurements": Object { + "alignContent": null, "alignItems": null, + "alignSelf": null, "borderRadius": null, + "borderWidths": Object { + "bottom": 0, + "left": 0, + "right": 0, + "top": 0, + }, "clientHeight": 0, "clientWidth": 0, "closestOffsetParentPath": Object { @@ -51280,6 +56120,7 @@ Object { "gap": null, "globalContentBoxForChildren": null, "globalFrameWithTextContent": null, + "gridCellGlobalFrames": null, "hasPositionOffset": false, "hasTransform": false, "htmlElementName": "div", @@ -51291,7 +56132,9 @@ Object { }, "immediateParentProvidesLayout": true, "justifyContent": null, + "justifySelf": null, "layoutSystemForChildren": null, + "layoutSystemForChildrenInherited": false, "margin": Object {}, "naturalHeight": null, "naturalWidth": null, @@ -51300,8 +56143,24 @@ Object { "y": 0, }, "padding": Object {}, + "parentContainerGridProperties": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, + "parentContainerGridPropertiesFromProps": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, "parentFlexDirection": null, "parentFlexGap": 0, + "parentGridCellGlobalFrames": null, + "parentGridFrame": null, "parentHugsOnMainAxis": false, "parentJustifyContent": null, "parentLayoutSystem": "flow", @@ -51448,8 +56307,16 @@ Object { "label": null, "nonRoundedGlobalFrame": null, "specialSizeMeasurements": Object { + "alignContent": null, "alignItems": null, + "alignSelf": null, "borderRadius": null, + "borderWidths": Object { + "bottom": 0, + "left": 0, + "right": 0, + "top": 0, + }, "clientHeight": 0, "clientWidth": 0, "closestOffsetParentPath": Object { @@ -51497,6 +56364,7 @@ Object { "gap": null, "globalContentBoxForChildren": null, "globalFrameWithTextContent": null, + "gridCellGlobalFrames": null, "hasPositionOffset": false, "hasTransform": false, "htmlElementName": "div", @@ -51508,7 +56376,9 @@ Object { }, "immediateParentProvidesLayout": true, "justifyContent": null, + "justifySelf": null, "layoutSystemForChildren": null, + "layoutSystemForChildrenInherited": false, "margin": Object {}, "naturalHeight": null, "naturalWidth": null, @@ -51517,8 +56387,24 @@ Object { "y": 0, }, "padding": Object {}, + "parentContainerGridProperties": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, + "parentContainerGridPropertiesFromProps": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, "parentFlexDirection": null, "parentFlexGap": 0, + "parentGridCellGlobalFrames": null, + "parentGridFrame": null, "parentHugsOnMainAxis": false, "parentJustifyContent": null, "parentLayoutSystem": "flow", @@ -51665,8 +56551,16 @@ Object { "label": null, "nonRoundedGlobalFrame": null, "specialSizeMeasurements": Object { + "alignContent": null, "alignItems": null, + "alignSelf": null, "borderRadius": null, + "borderWidths": Object { + "bottom": 0, + "left": 0, + "right": 0, + "top": 0, + }, "clientHeight": 0, "clientWidth": 0, "closestOffsetParentPath": Object { @@ -51714,6 +56608,7 @@ Object { "gap": null, "globalContentBoxForChildren": null, "globalFrameWithTextContent": null, + "gridCellGlobalFrames": null, "hasPositionOffset": false, "hasTransform": false, "htmlElementName": "div", @@ -51725,7 +56620,9 @@ Object { }, "immediateParentProvidesLayout": true, "justifyContent": null, + "justifySelf": null, "layoutSystemForChildren": null, + "layoutSystemForChildrenInherited": false, "margin": Object {}, "naturalHeight": null, "naturalWidth": null, @@ -51734,8 +56631,24 @@ Object { "y": 0, }, "padding": Object {}, + "parentContainerGridProperties": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, + "parentContainerGridPropertiesFromProps": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, "parentFlexDirection": null, "parentFlexGap": 0, + "parentGridCellGlobalFrames": null, + "parentGridFrame": null, "parentHugsOnMainAxis": false, "parentJustifyContent": null, "parentLayoutSystem": "flow", @@ -51768,6 +56681,7 @@ exports[`UiJsxCanvas render renders a component used in an arbitrary block with data-path=\\"utopia-storyboard-uid/scene-aaa\\" style=\\" overflow: hidden; + container: scene / inline-size; position: absolute; background-color: var(--utopitheme-emphasizedBackground); box-shadow: 0px 1px 2px 0px var(--utopitheme-shadow90), @@ -51955,8 +56869,16 @@ Object { "label": null, "nonRoundedGlobalFrame": null, "specialSizeMeasurements": Object { + "alignContent": null, "alignItems": null, + "alignSelf": null, "borderRadius": null, + "borderWidths": Object { + "bottom": 0, + "left": 0, + "right": 0, + "top": 0, + }, "clientHeight": 0, "clientWidth": 0, "closestOffsetParentPath": Object { @@ -52004,6 +56926,7 @@ Object { "gap": null, "globalContentBoxForChildren": null, "globalFrameWithTextContent": null, + "gridCellGlobalFrames": null, "hasPositionOffset": false, "hasTransform": false, "htmlElementName": "div", @@ -52015,7 +56938,9 @@ Object { }, "immediateParentProvidesLayout": true, "justifyContent": null, + "justifySelf": null, "layoutSystemForChildren": null, + "layoutSystemForChildrenInherited": false, "margin": Object {}, "naturalHeight": null, "naturalWidth": null, @@ -52024,8 +56949,24 @@ Object { "y": 0, }, "padding": Object {}, + "parentContainerGridProperties": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, + "parentContainerGridPropertiesFromProps": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, "parentFlexDirection": null, "parentFlexGap": 0, + "parentGridCellGlobalFrames": null, + "parentGridFrame": null, "parentHugsOnMainAxis": false, "parentJustifyContent": null, "parentLayoutSystem": "flow", @@ -52124,8 +57065,16 @@ Object { "label": null, "nonRoundedGlobalFrame": null, "specialSizeMeasurements": Object { + "alignContent": null, "alignItems": null, + "alignSelf": null, "borderRadius": null, + "borderWidths": Object { + "bottom": 0, + "left": 0, + "right": 0, + "top": 0, + }, "clientHeight": 0, "clientWidth": 0, "closestOffsetParentPath": Object { @@ -52173,6 +57122,7 @@ Object { "gap": null, "globalContentBoxForChildren": null, "globalFrameWithTextContent": null, + "gridCellGlobalFrames": null, "hasPositionOffset": false, "hasTransform": false, "htmlElementName": "div", @@ -52184,7 +57134,9 @@ Object { }, "immediateParentProvidesLayout": true, "justifyContent": null, + "justifySelf": null, "layoutSystemForChildren": null, + "layoutSystemForChildrenInherited": false, "margin": Object {}, "naturalHeight": null, "naturalWidth": null, @@ -52193,8 +57145,24 @@ Object { "y": 0, }, "padding": Object {}, + "parentContainerGridProperties": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, + "parentContainerGridPropertiesFromProps": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, "parentFlexDirection": null, "parentFlexGap": 0, + "parentGridCellGlobalFrames": null, + "parentGridFrame": null, "parentHugsOnMainAxis": false, "parentJustifyContent": null, "parentLayoutSystem": "flow", @@ -52560,8 +57528,16 @@ Object { "label": null, "nonRoundedGlobalFrame": null, "specialSizeMeasurements": Object { + "alignContent": null, "alignItems": null, + "alignSelf": null, "borderRadius": null, + "borderWidths": Object { + "bottom": 0, + "left": 0, + "right": 0, + "top": 0, + }, "clientHeight": 0, "clientWidth": 0, "closestOffsetParentPath": Object { @@ -52609,6 +57585,7 @@ Object { "gap": null, "globalContentBoxForChildren": null, "globalFrameWithTextContent": null, + "gridCellGlobalFrames": null, "hasPositionOffset": false, "hasTransform": false, "htmlElementName": "div", @@ -52620,7 +57597,9 @@ Object { }, "immediateParentProvidesLayout": true, "justifyContent": null, + "justifySelf": null, "layoutSystemForChildren": null, + "layoutSystemForChildrenInherited": false, "margin": Object {}, "naturalHeight": null, "naturalWidth": null, @@ -52629,8 +57608,24 @@ Object { "y": 0, }, "padding": Object {}, + "parentContainerGridProperties": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, + "parentContainerGridPropertiesFromProps": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, "parentFlexDirection": null, "parentFlexGap": 0, + "parentGridCellGlobalFrames": null, + "parentGridFrame": null, "parentHugsOnMainAxis": false, "parentJustifyContent": null, "parentLayoutSystem": "flow", @@ -52961,8 +57956,16 @@ Object { "label": null, "nonRoundedGlobalFrame": null, "specialSizeMeasurements": Object { + "alignContent": null, "alignItems": null, + "alignSelf": null, "borderRadius": null, + "borderWidths": Object { + "bottom": 0, + "left": 0, + "right": 0, + "top": 0, + }, "clientHeight": 0, "clientWidth": 0, "closestOffsetParentPath": Object { @@ -53010,6 +58013,7 @@ Object { "gap": null, "globalContentBoxForChildren": null, "globalFrameWithTextContent": null, + "gridCellGlobalFrames": null, "hasPositionOffset": false, "hasTransform": false, "htmlElementName": "div", @@ -53021,7 +58025,9 @@ Object { }, "immediateParentProvidesLayout": true, "justifyContent": null, + "justifySelf": null, "layoutSystemForChildren": null, + "layoutSystemForChildrenInherited": false, "margin": Object {}, "naturalHeight": null, "naturalWidth": null, @@ -53030,8 +58036,24 @@ Object { "y": 0, }, "padding": Object {}, + "parentContainerGridProperties": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, + "parentContainerGridPropertiesFromProps": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, "parentFlexDirection": null, "parentFlexGap": 0, + "parentGridCellGlobalFrames": null, + "parentGridFrame": null, "parentHugsOnMainAxis": false, "parentJustifyContent": null, "parentLayoutSystem": "flow", @@ -53178,8 +58200,16 @@ Object { "label": null, "nonRoundedGlobalFrame": null, "specialSizeMeasurements": Object { + "alignContent": null, "alignItems": null, + "alignSelf": null, "borderRadius": null, + "borderWidths": Object { + "bottom": 0, + "left": 0, + "right": 0, + "top": 0, + }, "clientHeight": 0, "clientWidth": 0, "closestOffsetParentPath": Object { @@ -53227,6 +58257,7 @@ Object { "gap": null, "globalContentBoxForChildren": null, "globalFrameWithTextContent": null, + "gridCellGlobalFrames": null, "hasPositionOffset": false, "hasTransform": false, "htmlElementName": "div", @@ -53238,7 +58269,9 @@ Object { }, "immediateParentProvidesLayout": true, "justifyContent": null, + "justifySelf": null, "layoutSystemForChildren": null, + "layoutSystemForChildrenInherited": false, "margin": Object {}, "naturalHeight": null, "naturalWidth": null, @@ -53247,8 +58280,24 @@ Object { "y": 0, }, "padding": Object {}, + "parentContainerGridProperties": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, + "parentContainerGridPropertiesFromProps": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, "parentFlexDirection": null, "parentFlexGap": 0, + "parentGridCellGlobalFrames": null, + "parentGridFrame": null, "parentHugsOnMainAxis": false, "parentJustifyContent": null, "parentLayoutSystem": "flow", @@ -53395,8 +58444,16 @@ Object { "label": null, "nonRoundedGlobalFrame": null, "specialSizeMeasurements": Object { + "alignContent": null, "alignItems": null, + "alignSelf": null, "borderRadius": null, + "borderWidths": Object { + "bottom": 0, + "left": 0, + "right": 0, + "top": 0, + }, "clientHeight": 0, "clientWidth": 0, "closestOffsetParentPath": Object { @@ -53444,6 +58501,7 @@ Object { "gap": null, "globalContentBoxForChildren": null, "globalFrameWithTextContent": null, + "gridCellGlobalFrames": null, "hasPositionOffset": false, "hasTransform": false, "htmlElementName": "div", @@ -53455,7 +58513,9 @@ Object { }, "immediateParentProvidesLayout": true, "justifyContent": null, + "justifySelf": null, "layoutSystemForChildren": null, + "layoutSystemForChildrenInherited": false, "margin": Object {}, "naturalHeight": null, "naturalWidth": null, @@ -53464,8 +58524,24 @@ Object { "y": 0, }, "padding": Object {}, + "parentContainerGridProperties": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, + "parentContainerGridPropertiesFromProps": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, "parentFlexDirection": null, "parentFlexGap": 0, + "parentGridCellGlobalFrames": null, + "parentGridFrame": null, "parentHugsOnMainAxis": false, "parentJustifyContent": null, "parentLayoutSystem": "flow", @@ -53612,8 +58688,16 @@ Object { "label": null, "nonRoundedGlobalFrame": null, "specialSizeMeasurements": Object { + "alignContent": null, "alignItems": null, + "alignSelf": null, "borderRadius": null, + "borderWidths": Object { + "bottom": 0, + "left": 0, + "right": 0, + "top": 0, + }, "clientHeight": 0, "clientWidth": 0, "closestOffsetParentPath": Object { @@ -53661,6 +58745,7 @@ Object { "gap": null, "globalContentBoxForChildren": null, "globalFrameWithTextContent": null, + "gridCellGlobalFrames": null, "hasPositionOffset": false, "hasTransform": false, "htmlElementName": "div", @@ -53672,7 +58757,9 @@ Object { }, "immediateParentProvidesLayout": true, "justifyContent": null, + "justifySelf": null, "layoutSystemForChildren": null, + "layoutSystemForChildrenInherited": false, "margin": Object {}, "naturalHeight": null, "naturalWidth": null, @@ -53681,8 +58768,24 @@ Object { "y": 0, }, "padding": Object {}, + "parentContainerGridProperties": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, + "parentContainerGridPropertiesFromProps": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, "parentFlexDirection": null, "parentFlexGap": 0, + "parentGridCellGlobalFrames": null, + "parentGridFrame": null, "parentHugsOnMainAxis": false, "parentJustifyContent": null, "parentLayoutSystem": "flow", @@ -53715,6 +58818,7 @@ exports[`UiJsxCanvas render renders a component using the spread operator 1`] = data-path=\\"utopia-storyboard-uid/scene-aaa\\" style=\\" overflow: hidden; + container: scene / inline-size; position: absolute; background-color: var(--utopitheme-emphasizedBackground); box-shadow: 0px 1px 2px 0px var(--utopitheme-shadow90), @@ -53908,8 +59012,16 @@ Object { "label": null, "nonRoundedGlobalFrame": null, "specialSizeMeasurements": Object { + "alignContent": null, "alignItems": null, + "alignSelf": null, "borderRadius": null, + "borderWidths": Object { + "bottom": 0, + "left": 0, + "right": 0, + "top": 0, + }, "clientHeight": 0, "clientWidth": 0, "closestOffsetParentPath": Object { @@ -53957,6 +59069,7 @@ Object { "gap": null, "globalContentBoxForChildren": null, "globalFrameWithTextContent": null, + "gridCellGlobalFrames": null, "hasPositionOffset": false, "hasTransform": false, "htmlElementName": "div", @@ -53968,7 +59081,9 @@ Object { }, "immediateParentProvidesLayout": true, "justifyContent": null, + "justifySelf": null, "layoutSystemForChildren": null, + "layoutSystemForChildrenInherited": false, "margin": Object {}, "naturalHeight": null, "naturalWidth": null, @@ -53977,8 +59092,24 @@ Object { "y": 0, }, "padding": Object {}, + "parentContainerGridProperties": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, + "parentContainerGridPropertiesFromProps": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, "parentFlexDirection": null, "parentFlexGap": 0, + "parentGridCellGlobalFrames": null, + "parentGridFrame": null, "parentHugsOnMainAxis": false, "parentJustifyContent": null, "parentLayoutSystem": "flow", @@ -54092,8 +59223,16 @@ Object { "label": null, "nonRoundedGlobalFrame": null, "specialSizeMeasurements": Object { + "alignContent": null, "alignItems": null, + "alignSelf": null, "borderRadius": null, + "borderWidths": Object { + "bottom": 0, + "left": 0, + "right": 0, + "top": 0, + }, "clientHeight": 0, "clientWidth": 0, "closestOffsetParentPath": Object { @@ -54141,6 +59280,7 @@ Object { "gap": null, "globalContentBoxForChildren": null, "globalFrameWithTextContent": null, + "gridCellGlobalFrames": null, "hasPositionOffset": false, "hasTransform": false, "htmlElementName": "div", @@ -54152,7 +59292,9 @@ Object { }, "immediateParentProvidesLayout": true, "justifyContent": null, + "justifySelf": null, "layoutSystemForChildren": null, + "layoutSystemForChildrenInherited": false, "margin": Object {}, "naturalHeight": null, "naturalWidth": null, @@ -54161,8 +59303,24 @@ Object { "y": 0, }, "padding": Object {}, + "parentContainerGridProperties": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, + "parentContainerGridPropertiesFromProps": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, "parentFlexDirection": null, "parentFlexGap": 0, + "parentGridCellGlobalFrames": null, + "parentGridFrame": null, "parentHugsOnMainAxis": false, "parentJustifyContent": null, "parentLayoutSystem": "flow", @@ -54355,8 +59513,16 @@ Object { "label": null, "nonRoundedGlobalFrame": null, "specialSizeMeasurements": Object { + "alignContent": null, "alignItems": null, + "alignSelf": null, "borderRadius": null, + "borderWidths": Object { + "bottom": 0, + "left": 0, + "right": 0, + "top": 0, + }, "clientHeight": 0, "clientWidth": 0, "closestOffsetParentPath": Object { @@ -54404,6 +59570,7 @@ Object { "gap": null, "globalContentBoxForChildren": null, "globalFrameWithTextContent": null, + "gridCellGlobalFrames": null, "hasPositionOffset": false, "hasTransform": false, "htmlElementName": "div", @@ -54415,7 +59582,9 @@ Object { }, "immediateParentProvidesLayout": true, "justifyContent": null, + "justifySelf": null, "layoutSystemForChildren": null, + "layoutSystemForChildrenInherited": false, "margin": Object {}, "naturalHeight": null, "naturalWidth": null, @@ -54424,8 +59593,24 @@ Object { "y": 0, }, "padding": Object {}, + "parentContainerGridProperties": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, + "parentContainerGridPropertiesFromProps": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, "parentFlexDirection": null, "parentFlexGap": 0, + "parentGridCellGlobalFrames": null, + "parentGridFrame": null, "parentHugsOnMainAxis": false, "parentJustifyContent": null, "parentLayoutSystem": "flow", @@ -54531,8 +59716,16 @@ Object { "label": null, "nonRoundedGlobalFrame": null, "specialSizeMeasurements": Object { + "alignContent": null, "alignItems": null, + "alignSelf": null, "borderRadius": null, + "borderWidths": Object { + "bottom": 0, + "left": 0, + "right": 0, + "top": 0, + }, "clientHeight": 0, "clientWidth": 0, "closestOffsetParentPath": Object { @@ -54580,6 +59773,7 @@ Object { "gap": null, "globalContentBoxForChildren": null, "globalFrameWithTextContent": null, + "gridCellGlobalFrames": null, "hasPositionOffset": false, "hasTransform": false, "htmlElementName": "div", @@ -54591,7 +59785,9 @@ Object { }, "immediateParentProvidesLayout": true, "justifyContent": null, + "justifySelf": null, "layoutSystemForChildren": null, + "layoutSystemForChildrenInherited": false, "margin": Object {}, "naturalHeight": null, "naturalWidth": null, @@ -54600,8 +59796,24 @@ Object { "y": 0, }, "padding": Object {}, + "parentContainerGridProperties": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, + "parentContainerGridPropertiesFromProps": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, "parentFlexDirection": null, "parentFlexGap": 0, + "parentGridCellGlobalFrames": null, + "parentGridFrame": null, "parentHugsOnMainAxis": false, "parentJustifyContent": null, "parentLayoutSystem": "flow", @@ -54634,6 +59846,7 @@ exports[`UiJsxCanvas render renders a component with a fragment at the root 1`] data-path=\\"utopia-storyboard-uid/scene-aaa\\" style=\\" overflow: hidden; + container: scene / inline-size; position: absolute; background-color: var(--utopitheme-emphasizedBackground); box-shadow: 0px 1px 2px 0px var(--utopitheme-shadow90), @@ -54805,8 +60018,16 @@ Object { "label": null, "nonRoundedGlobalFrame": null, "specialSizeMeasurements": Object { + "alignContent": null, "alignItems": null, + "alignSelf": null, "borderRadius": null, + "borderWidths": Object { + "bottom": 0, + "left": 0, + "right": 0, + "top": 0, + }, "clientHeight": 0, "clientWidth": 0, "closestOffsetParentPath": Object { @@ -54854,6 +60075,7 @@ Object { "gap": null, "globalContentBoxForChildren": null, "globalFrameWithTextContent": null, + "gridCellGlobalFrames": null, "hasPositionOffset": false, "hasTransform": false, "htmlElementName": "div", @@ -54865,7 +60087,9 @@ Object { }, "immediateParentProvidesLayout": true, "justifyContent": null, + "justifySelf": null, "layoutSystemForChildren": null, + "layoutSystemForChildrenInherited": false, "margin": Object {}, "naturalHeight": null, "naturalWidth": null, @@ -54874,8 +60098,24 @@ Object { "y": 0, }, "padding": Object {}, + "parentContainerGridProperties": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, + "parentContainerGridPropertiesFromProps": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, "parentFlexDirection": null, "parentFlexGap": 0, + "parentGridCellGlobalFrames": null, + "parentGridFrame": null, "parentHugsOnMainAxis": false, "parentJustifyContent": null, "parentLayoutSystem": "flow", @@ -54951,8 +60191,16 @@ Object { "label": null, "nonRoundedGlobalFrame": null, "specialSizeMeasurements": Object { + "alignContent": null, "alignItems": null, + "alignSelf": null, "borderRadius": null, + "borderWidths": Object { + "bottom": 0, + "left": 0, + "right": 0, + "top": 0, + }, "clientHeight": 0, "clientWidth": 0, "closestOffsetParentPath": Object { @@ -55000,6 +60248,7 @@ Object { "gap": null, "globalContentBoxForChildren": null, "globalFrameWithTextContent": null, + "gridCellGlobalFrames": null, "hasPositionOffset": false, "hasTransform": false, "htmlElementName": "div", @@ -55011,7 +60260,9 @@ Object { }, "immediateParentProvidesLayout": true, "justifyContent": null, + "justifySelf": null, "layoutSystemForChildren": null, + "layoutSystemForChildrenInherited": false, "margin": Object {}, "naturalHeight": null, "naturalWidth": null, @@ -55020,8 +60271,24 @@ Object { "y": 0, }, "padding": Object {}, + "parentContainerGridProperties": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, + "parentContainerGridPropertiesFromProps": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, "parentFlexDirection": null, "parentFlexGap": 0, + "parentGridCellGlobalFrames": null, + "parentGridFrame": null, "parentHugsOnMainAxis": false, "parentJustifyContent": null, "parentLayoutSystem": "flow", @@ -55145,8 +60412,16 @@ Object { "label": null, "nonRoundedGlobalFrame": null, "specialSizeMeasurements": Object { + "alignContent": null, "alignItems": null, + "alignSelf": null, "borderRadius": null, + "borderWidths": Object { + "bottom": 0, + "left": 0, + "right": 0, + "top": 0, + }, "clientHeight": 0, "clientWidth": 0, "closestOffsetParentPath": Object { @@ -55194,6 +60469,7 @@ Object { "gap": null, "globalContentBoxForChildren": null, "globalFrameWithTextContent": null, + "gridCellGlobalFrames": null, "hasPositionOffset": false, "hasTransform": false, "htmlElementName": "div", @@ -55205,7 +60481,9 @@ Object { }, "immediateParentProvidesLayout": true, "justifyContent": null, + "justifySelf": null, "layoutSystemForChildren": null, + "layoutSystemForChildrenInherited": false, "margin": Object {}, "naturalHeight": null, "naturalWidth": null, @@ -55214,8 +60492,24 @@ Object { "y": 0, }, "padding": Object {}, + "parentContainerGridProperties": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, + "parentContainerGridPropertiesFromProps": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, "parentFlexDirection": null, "parentFlexGap": 0, + "parentGridCellGlobalFrames": null, + "parentGridFrame": null, "parentHugsOnMainAxis": false, "parentJustifyContent": null, "parentLayoutSystem": "flow", @@ -55301,8 +60595,16 @@ Object { "label": null, "nonRoundedGlobalFrame": null, "specialSizeMeasurements": Object { + "alignContent": null, "alignItems": null, + "alignSelf": null, "borderRadius": null, + "borderWidths": Object { + "bottom": 0, + "left": 0, + "right": 0, + "top": 0, + }, "clientHeight": 0, "clientWidth": 0, "closestOffsetParentPath": Object { @@ -55350,6 +60652,7 @@ Object { "gap": null, "globalContentBoxForChildren": null, "globalFrameWithTextContent": null, + "gridCellGlobalFrames": null, "hasPositionOffset": false, "hasTransform": false, "htmlElementName": "div", @@ -55361,7 +60664,9 @@ Object { }, "immediateParentProvidesLayout": true, "justifyContent": null, + "justifySelf": null, "layoutSystemForChildren": null, + "layoutSystemForChildrenInherited": false, "margin": Object {}, "naturalHeight": null, "naturalWidth": null, @@ -55370,8 +60675,24 @@ Object { "y": 0, }, "padding": Object {}, + "parentContainerGridProperties": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, + "parentContainerGridPropertiesFromProps": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, "parentFlexDirection": null, "parentFlexGap": 0, + "parentGridCellGlobalFrames": null, + "parentGridFrame": null, "parentHugsOnMainAxis": false, "parentJustifyContent": null, "parentLayoutSystem": "flow", @@ -55457,8 +60778,16 @@ Object { "label": null, "nonRoundedGlobalFrame": null, "specialSizeMeasurements": Object { + "alignContent": null, "alignItems": null, + "alignSelf": null, "borderRadius": null, + "borderWidths": Object { + "bottom": 0, + "left": 0, + "right": 0, + "top": 0, + }, "clientHeight": 0, "clientWidth": 0, "closestOffsetParentPath": Object { @@ -55506,6 +60835,7 @@ Object { "gap": null, "globalContentBoxForChildren": null, "globalFrameWithTextContent": null, + "gridCellGlobalFrames": null, "hasPositionOffset": false, "hasTransform": false, "htmlElementName": "div", @@ -55517,7 +60847,9 @@ Object { }, "immediateParentProvidesLayout": true, "justifyContent": null, + "justifySelf": null, "layoutSystemForChildren": null, + "layoutSystemForChildrenInherited": false, "margin": Object {}, "naturalHeight": null, "naturalWidth": null, @@ -55526,8 +60858,24 @@ Object { "y": 0, }, "padding": Object {}, + "parentContainerGridProperties": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, + "parentContainerGridPropertiesFromProps": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, "parentFlexDirection": null, "parentFlexGap": 0, + "parentGridCellGlobalFrames": null, + "parentGridFrame": null, "parentHugsOnMainAxis": false, "parentJustifyContent": null, "parentLayoutSystem": "flow", @@ -55560,6 +60908,7 @@ exports[`UiJsxCanvas render renders correctly when a component is passed in via data-path=\\"eee/fff\\" style=\\" overflow: hidden; + container: scene / inline-size; position: absolute; background-color: var(--utopitheme-emphasizedBackground); box-shadow: 0px 1px 2px 0px var(--utopitheme-shadow90), @@ -55718,8 +61067,16 @@ Object { "label": null, "nonRoundedGlobalFrame": null, "specialSizeMeasurements": Object { + "alignContent": null, "alignItems": null, + "alignSelf": null, "borderRadius": null, + "borderWidths": Object { + "bottom": 0, + "left": 0, + "right": 0, + "top": 0, + }, "clientHeight": 0, "clientWidth": 0, "closestOffsetParentPath": Object { @@ -55767,6 +61124,7 @@ Object { "gap": null, "globalContentBoxForChildren": null, "globalFrameWithTextContent": null, + "gridCellGlobalFrames": null, "hasPositionOffset": false, "hasTransform": false, "htmlElementName": "div", @@ -55778,7 +61136,9 @@ Object { }, "immediateParentProvidesLayout": true, "justifyContent": null, + "justifySelf": null, "layoutSystemForChildren": null, + "layoutSystemForChildrenInherited": false, "margin": Object {}, "naturalHeight": null, "naturalWidth": null, @@ -55787,8 +61147,24 @@ Object { "y": 0, }, "padding": Object {}, + "parentContainerGridProperties": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, + "parentContainerGridPropertiesFromProps": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, "parentFlexDirection": null, "parentFlexGap": 0, + "parentGridCellGlobalFrames": null, + "parentGridFrame": null, "parentHugsOnMainAxis": false, "parentJustifyContent": null, "parentLayoutSystem": "flow", @@ -55864,8 +61240,16 @@ Object { "label": null, "nonRoundedGlobalFrame": null, "specialSizeMeasurements": Object { + "alignContent": null, "alignItems": null, + "alignSelf": null, "borderRadius": null, + "borderWidths": Object { + "bottom": 0, + "left": 0, + "right": 0, + "top": 0, + }, "clientHeight": 0, "clientWidth": 0, "closestOffsetParentPath": Object { @@ -55913,6 +61297,7 @@ Object { "gap": null, "globalContentBoxForChildren": null, "globalFrameWithTextContent": null, + "gridCellGlobalFrames": null, "hasPositionOffset": false, "hasTransform": false, "htmlElementName": "div", @@ -55924,7 +61309,9 @@ Object { }, "immediateParentProvidesLayout": true, "justifyContent": null, + "justifySelf": null, "layoutSystemForChildren": null, + "layoutSystemForChildrenInherited": false, "margin": Object {}, "naturalHeight": null, "naturalWidth": null, @@ -55933,8 +61320,24 @@ Object { "y": 0, }, "padding": Object {}, + "parentContainerGridProperties": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, + "parentContainerGridPropertiesFromProps": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, "parentFlexDirection": null, "parentFlexGap": 0, + "parentGridCellGlobalFrames": null, + "parentGridFrame": null, "parentHugsOnMainAxis": false, "parentJustifyContent": null, "parentLayoutSystem": "flow", @@ -56116,8 +61519,16 @@ Object { "label": null, "nonRoundedGlobalFrame": null, "specialSizeMeasurements": Object { + "alignContent": null, "alignItems": null, + "alignSelf": null, "borderRadius": null, + "borderWidths": Object { + "bottom": 0, + "left": 0, + "right": 0, + "top": 0, + }, "clientHeight": 0, "clientWidth": 0, "closestOffsetParentPath": Object { @@ -56165,6 +61576,7 @@ Object { "gap": null, "globalContentBoxForChildren": null, "globalFrameWithTextContent": null, + "gridCellGlobalFrames": null, "hasPositionOffset": false, "hasTransform": false, "htmlElementName": "div", @@ -56176,7 +61588,9 @@ Object { }, "immediateParentProvidesLayout": true, "justifyContent": null, + "justifySelf": null, "layoutSystemForChildren": null, + "layoutSystemForChildrenInherited": false, "margin": Object {}, "naturalHeight": null, "naturalWidth": null, @@ -56185,8 +61599,24 @@ Object { "y": 0, }, "padding": Object {}, + "parentContainerGridProperties": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, + "parentContainerGridPropertiesFromProps": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, "parentFlexDirection": null, "parentFlexGap": 0, + "parentGridCellGlobalFrames": null, + "parentGridFrame": null, "parentHugsOnMainAxis": false, "parentJustifyContent": null, "parentLayoutSystem": "flow", @@ -56310,8 +61740,16 @@ Object { "label": null, "nonRoundedGlobalFrame": null, "specialSizeMeasurements": Object { + "alignContent": null, "alignItems": null, + "alignSelf": null, "borderRadius": null, + "borderWidths": Object { + "bottom": 0, + "left": 0, + "right": 0, + "top": 0, + }, "clientHeight": 0, "clientWidth": 0, "closestOffsetParentPath": Object { @@ -56359,6 +61797,7 @@ Object { "gap": null, "globalContentBoxForChildren": null, "globalFrameWithTextContent": null, + "gridCellGlobalFrames": null, "hasPositionOffset": false, "hasTransform": false, "htmlElementName": "div", @@ -56370,7 +61809,9 @@ Object { }, "immediateParentProvidesLayout": true, "justifyContent": null, + "justifySelf": null, "layoutSystemForChildren": null, + "layoutSystemForChildrenInherited": false, "margin": Object {}, "naturalHeight": null, "naturalWidth": null, @@ -56379,8 +61820,24 @@ Object { "y": 0, }, "padding": Object {}, + "parentContainerGridProperties": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, + "parentContainerGridPropertiesFromProps": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, "parentFlexDirection": null, "parentFlexGap": 0, + "parentGridCellGlobalFrames": null, + "parentGridFrame": null, "parentHugsOnMainAxis": false, "parentJustifyContent": null, "parentLayoutSystem": "flow", @@ -56467,8 +61924,16 @@ Object { "label": null, "nonRoundedGlobalFrame": null, "specialSizeMeasurements": Object { + "alignContent": null, "alignItems": null, + "alignSelf": null, "borderRadius": null, + "borderWidths": Object { + "bottom": 0, + "left": 0, + "right": 0, + "top": 0, + }, "clientHeight": 0, "clientWidth": 0, "closestOffsetParentPath": Object { @@ -56516,6 +61981,7 @@ Object { "gap": null, "globalContentBoxForChildren": null, "globalFrameWithTextContent": null, + "gridCellGlobalFrames": null, "hasPositionOffset": false, "hasTransform": false, "htmlElementName": "div", @@ -56527,7 +61993,9 @@ Object { }, "immediateParentProvidesLayout": true, "justifyContent": null, + "justifySelf": null, "layoutSystemForChildren": null, + "layoutSystemForChildrenInherited": false, "margin": Object {}, "naturalHeight": null, "naturalWidth": null, @@ -56536,8 +62004,24 @@ Object { "y": 0, }, "padding": Object {}, + "parentContainerGridProperties": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, + "parentContainerGridPropertiesFromProps": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, "parentFlexDirection": null, "parentFlexGap": 0, + "parentGridCellGlobalFrames": null, + "parentGridFrame": null, "parentHugsOnMainAxis": false, "parentJustifyContent": null, "parentLayoutSystem": "flow", @@ -56570,6 +62054,7 @@ exports[`UiJsxCanvas render renders correctly with a context 1`] = ` data-path=\\"ccc/ddd\\" style=\\" overflow: hidden; + container: scene / inline-size; position: absolute; background-color: var(--utopitheme-emphasizedBackground); box-shadow: 0px 1px 2px 0px var(--utopitheme-shadow90), @@ -56716,8 +62201,16 @@ Object { "label": null, "nonRoundedGlobalFrame": null, "specialSizeMeasurements": Object { + "alignContent": null, "alignItems": null, + "alignSelf": null, "borderRadius": null, + "borderWidths": Object { + "bottom": 0, + "left": 0, + "right": 0, + "top": 0, + }, "clientHeight": 0, "clientWidth": 0, "closestOffsetParentPath": Object { @@ -56765,6 +62258,7 @@ Object { "gap": null, "globalContentBoxForChildren": null, "globalFrameWithTextContent": null, + "gridCellGlobalFrames": null, "hasPositionOffset": false, "hasTransform": false, "htmlElementName": "div", @@ -56776,7 +62270,9 @@ Object { }, "immediateParentProvidesLayout": true, "justifyContent": null, + "justifySelf": null, "layoutSystemForChildren": null, + "layoutSystemForChildrenInherited": false, "margin": Object {}, "naturalHeight": null, "naturalWidth": null, @@ -56785,8 +62281,24 @@ Object { "y": 0, }, "padding": Object {}, + "parentContainerGridProperties": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, + "parentContainerGridPropertiesFromProps": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, "parentFlexDirection": null, "parentFlexGap": 0, + "parentGridCellGlobalFrames": null, + "parentGridFrame": null, "parentHugsOnMainAxis": false, "parentJustifyContent": null, "parentLayoutSystem": "flow", @@ -56862,8 +62374,16 @@ Object { "label": null, "nonRoundedGlobalFrame": null, "specialSizeMeasurements": Object { + "alignContent": null, "alignItems": null, + "alignSelf": null, "borderRadius": null, + "borderWidths": Object { + "bottom": 0, + "left": 0, + "right": 0, + "top": 0, + }, "clientHeight": 0, "clientWidth": 0, "closestOffsetParentPath": Object { @@ -56911,6 +62431,7 @@ Object { "gap": null, "globalContentBoxForChildren": null, "globalFrameWithTextContent": null, + "gridCellGlobalFrames": null, "hasPositionOffset": false, "hasTransform": false, "htmlElementName": "div", @@ -56922,7 +62443,9 @@ Object { }, "immediateParentProvidesLayout": true, "justifyContent": null, + "justifySelf": null, "layoutSystemForChildren": null, + "layoutSystemForChildrenInherited": false, "margin": Object {}, "naturalHeight": null, "naturalWidth": null, @@ -56931,8 +62454,24 @@ Object { "y": 0, }, "padding": Object {}, + "parentContainerGridProperties": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, + "parentContainerGridPropertiesFromProps": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, "parentFlexDirection": null, "parentFlexGap": 0, + "parentGridCellGlobalFrames": null, + "parentGridFrame": null, "parentHugsOnMainAxis": false, "parentJustifyContent": null, "parentLayoutSystem": "flow", @@ -57124,8 +62663,16 @@ export var storyboard = ( "label": null, "nonRoundedGlobalFrame": null, "specialSizeMeasurements": Object { + "alignContent": null, "alignItems": null, + "alignSelf": null, "borderRadius": null, + "borderWidths": Object { + "bottom": 0, + "left": 0, + "right": 0, + "top": 0, + }, "clientHeight": 0, "clientWidth": 0, "closestOffsetParentPath": Object { @@ -57173,6 +62720,7 @@ export var storyboard = ( "gap": null, "globalContentBoxForChildren": null, "globalFrameWithTextContent": null, + "gridCellGlobalFrames": null, "hasPositionOffset": false, "hasTransform": false, "htmlElementName": "div", @@ -57184,7 +62732,9 @@ export var storyboard = ( }, "immediateParentProvidesLayout": true, "justifyContent": null, + "justifySelf": null, "layoutSystemForChildren": null, + "layoutSystemForChildrenInherited": false, "margin": Object {}, "naturalHeight": null, "naturalWidth": null, @@ -57193,8 +62743,24 @@ export var storyboard = ( "y": 0, }, "padding": Object {}, + "parentContainerGridProperties": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, + "parentContainerGridPropertiesFromProps": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, "parentFlexDirection": null, "parentFlexGap": 0, + "parentGridCellGlobalFrames": null, + "parentGridFrame": null, "parentHugsOnMainAxis": false, "parentJustifyContent": null, "parentLayoutSystem": "flow", @@ -57295,8 +62861,16 @@ export var storyboard = ( "label": null, "nonRoundedGlobalFrame": null, "specialSizeMeasurements": Object { + "alignContent": null, "alignItems": null, + "alignSelf": null, "borderRadius": null, + "borderWidths": Object { + "bottom": 0, + "left": 0, + "right": 0, + "top": 0, + }, "clientHeight": 0, "clientWidth": 0, "closestOffsetParentPath": Object { @@ -57344,6 +62918,7 @@ export var storyboard = ( "gap": null, "globalContentBoxForChildren": null, "globalFrameWithTextContent": null, + "gridCellGlobalFrames": null, "hasPositionOffset": false, "hasTransform": false, "htmlElementName": "div", @@ -57355,7 +62930,9 @@ export var storyboard = ( }, "immediateParentProvidesLayout": true, "justifyContent": null, + "justifySelf": null, "layoutSystemForChildren": null, + "layoutSystemForChildrenInherited": false, "margin": Object {}, "naturalHeight": null, "naturalWidth": null, @@ -57364,8 +62941,24 @@ export var storyboard = ( "y": 0, }, "padding": Object {}, + "parentContainerGridProperties": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, + "parentContainerGridPropertiesFromProps": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, "parentFlexDirection": null, "parentFlexGap": 0, + "parentGridCellGlobalFrames": null, + "parentGridFrame": null, "parentHugsOnMainAxis": false, "parentJustifyContent": null, "parentLayoutSystem": "flow", @@ -57398,6 +62991,7 @@ exports[`UiJsxCanvas render renders correctly with a nested context in another c data-path=\\"ccc/ddd\\" style=\\" overflow: hidden; + container: scene / inline-size; position: absolute; background-color: var(--utopitheme-emphasizedBackground); box-shadow: 0px 1px 2px 0px var(--utopitheme-shadow90), @@ -57544,8 +63138,16 @@ Object { "label": null, "nonRoundedGlobalFrame": null, "specialSizeMeasurements": Object { + "alignContent": null, "alignItems": null, + "alignSelf": null, "borderRadius": null, + "borderWidths": Object { + "bottom": 0, + "left": 0, + "right": 0, + "top": 0, + }, "clientHeight": 0, "clientWidth": 0, "closestOffsetParentPath": Object { @@ -57593,6 +63195,7 @@ Object { "gap": null, "globalContentBoxForChildren": null, "globalFrameWithTextContent": null, + "gridCellGlobalFrames": null, "hasPositionOffset": false, "hasTransform": false, "htmlElementName": "div", @@ -57604,7 +63207,9 @@ Object { }, "immediateParentProvidesLayout": true, "justifyContent": null, + "justifySelf": null, "layoutSystemForChildren": null, + "layoutSystemForChildrenInherited": false, "margin": Object {}, "naturalHeight": null, "naturalWidth": null, @@ -57613,8 +63218,24 @@ Object { "y": 0, }, "padding": Object {}, + "parentContainerGridProperties": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, + "parentContainerGridPropertiesFromProps": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, "parentFlexDirection": null, "parentFlexGap": 0, + "parentGridCellGlobalFrames": null, + "parentGridFrame": null, "parentHugsOnMainAxis": false, "parentJustifyContent": null, "parentLayoutSystem": "flow", @@ -57690,8 +63311,16 @@ Object { "label": null, "nonRoundedGlobalFrame": null, "specialSizeMeasurements": Object { + "alignContent": null, "alignItems": null, + "alignSelf": null, "borderRadius": null, + "borderWidths": Object { + "bottom": 0, + "left": 0, + "right": 0, + "top": 0, + }, "clientHeight": 0, "clientWidth": 0, "closestOffsetParentPath": Object { @@ -57739,6 +63368,7 @@ Object { "gap": null, "globalContentBoxForChildren": null, "globalFrameWithTextContent": null, + "gridCellGlobalFrames": null, "hasPositionOffset": false, "hasTransform": false, "htmlElementName": "div", @@ -57750,7 +63380,9 @@ Object { }, "immediateParentProvidesLayout": true, "justifyContent": null, + "justifySelf": null, "layoutSystemForChildren": null, + "layoutSystemForChildrenInherited": false, "margin": Object {}, "naturalHeight": null, "naturalWidth": null, @@ -57759,8 +63391,24 @@ Object { "y": 0, }, "padding": Object {}, + "parentContainerGridProperties": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, + "parentContainerGridPropertiesFromProps": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, "parentFlexDirection": null, "parentFlexGap": 0, + "parentGridCellGlobalFrames": null, + "parentGridFrame": null, "parentHugsOnMainAxis": false, "parentJustifyContent": null, "parentLayoutSystem": "flow", @@ -57839,8 +63487,16 @@ Object { "label": null, "nonRoundedGlobalFrame": null, "specialSizeMeasurements": Object { + "alignContent": null, "alignItems": null, + "alignSelf": null, "borderRadius": null, + "borderWidths": Object { + "bottom": 0, + "left": 0, + "right": 0, + "top": 0, + }, "clientHeight": 0, "clientWidth": 0, "closestOffsetParentPath": Object { @@ -57888,6 +63544,7 @@ Object { "gap": null, "globalContentBoxForChildren": null, "globalFrameWithTextContent": null, + "gridCellGlobalFrames": null, "hasPositionOffset": false, "hasTransform": false, "htmlElementName": "div", @@ -57899,7 +63556,9 @@ Object { }, "immediateParentProvidesLayout": true, "justifyContent": null, + "justifySelf": null, "layoutSystemForChildren": null, + "layoutSystemForChildrenInherited": false, "margin": Object {}, "naturalHeight": null, "naturalWidth": null, @@ -57908,8 +63567,24 @@ Object { "y": 0, }, "padding": Object {}, + "parentContainerGridProperties": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, + "parentContainerGridPropertiesFromProps": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, "parentFlexDirection": null, "parentFlexGap": 0, + "parentGridCellGlobalFrames": null, + "parentGridFrame": null, "parentHugsOnMainAxis": false, "parentJustifyContent": null, "parentLayoutSystem": "flow", @@ -57942,6 +63617,7 @@ exports[`UiJsxCanvas render renders fine with a valid registerModule call 1`] = data-path=\\"utopia-storyboard-uid/scene-aaa\\" style=\\" overflow: hidden; + container: scene / inline-size; position: absolute; background-color: var(--utopitheme-emphasizedBackground); box-shadow: 0px 1px 2px 0px var(--utopitheme-shadow90), @@ -58096,8 +63772,16 @@ Object { "label": null, "nonRoundedGlobalFrame": null, "specialSizeMeasurements": Object { + "alignContent": null, "alignItems": null, + "alignSelf": null, "borderRadius": null, + "borderWidths": Object { + "bottom": 0, + "left": 0, + "right": 0, + "top": 0, + }, "clientHeight": 0, "clientWidth": 0, "closestOffsetParentPath": Object { @@ -58145,6 +63829,7 @@ Object { "gap": null, "globalContentBoxForChildren": null, "globalFrameWithTextContent": null, + "gridCellGlobalFrames": null, "hasPositionOffset": false, "hasTransform": false, "htmlElementName": "div", @@ -58156,7 +63841,9 @@ Object { }, "immediateParentProvidesLayout": true, "justifyContent": null, + "justifySelf": null, "layoutSystemForChildren": null, + "layoutSystemForChildrenInherited": false, "margin": Object {}, "naturalHeight": null, "naturalWidth": null, @@ -58165,8 +63852,24 @@ Object { "y": 0, }, "padding": Object {}, + "parentContainerGridProperties": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, + "parentContainerGridPropertiesFromProps": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, "parentFlexDirection": null, "parentFlexGap": 0, + "parentGridCellGlobalFrames": null, + "parentGridFrame": null, "parentHugsOnMainAxis": false, "parentJustifyContent": null, "parentLayoutSystem": "flow", @@ -58242,8 +63945,16 @@ Object { "label": null, "nonRoundedGlobalFrame": null, "specialSizeMeasurements": Object { + "alignContent": null, "alignItems": null, + "alignSelf": null, "borderRadius": null, + "borderWidths": Object { + "bottom": 0, + "left": 0, + "right": 0, + "top": 0, + }, "clientHeight": 0, "clientWidth": 0, "closestOffsetParentPath": Object { @@ -58291,6 +64002,7 @@ Object { "gap": null, "globalContentBoxForChildren": null, "globalFrameWithTextContent": null, + "gridCellGlobalFrames": null, "hasPositionOffset": false, "hasTransform": false, "htmlElementName": "div", @@ -58302,7 +64014,9 @@ Object { }, "immediateParentProvidesLayout": true, "justifyContent": null, + "justifySelf": null, "layoutSystemForChildren": null, + "layoutSystemForChildrenInherited": false, "margin": Object {}, "naturalHeight": null, "naturalWidth": null, @@ -58311,8 +64025,24 @@ Object { "y": 0, }, "padding": Object {}, + "parentContainerGridProperties": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, + "parentContainerGridPropertiesFromProps": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, "parentFlexDirection": null, "parentFlexGap": 0, + "parentGridCellGlobalFrames": null, + "parentGridFrame": null, "parentHugsOnMainAxis": false, "parentJustifyContent": null, "parentLayoutSystem": "flow", @@ -58469,8 +64199,16 @@ Object { "label": null, "nonRoundedGlobalFrame": null, "specialSizeMeasurements": Object { + "alignContent": null, "alignItems": null, + "alignSelf": null, "borderRadius": null, + "borderWidths": Object { + "bottom": 0, + "left": 0, + "right": 0, + "top": 0, + }, "clientHeight": 0, "clientWidth": 0, "closestOffsetParentPath": Object { @@ -58518,6 +64256,7 @@ Object { "gap": null, "globalContentBoxForChildren": null, "globalFrameWithTextContent": null, + "gridCellGlobalFrames": null, "hasPositionOffset": false, "hasTransform": false, "htmlElementName": "div", @@ -58529,7 +64268,9 @@ Object { }, "immediateParentProvidesLayout": true, "justifyContent": null, + "justifySelf": null, "layoutSystemForChildren": null, + "layoutSystemForChildrenInherited": false, "margin": Object {}, "naturalHeight": null, "naturalWidth": null, @@ -58538,8 +64279,24 @@ Object { "y": 0, }, "padding": Object {}, + "parentContainerGridProperties": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, + "parentContainerGridPropertiesFromProps": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, "parentFlexDirection": null, "parentFlexGap": 0, + "parentGridCellGlobalFrames": null, + "parentGridFrame": null, "parentHugsOnMainAxis": false, "parentJustifyContent": null, "parentLayoutSystem": "flow", @@ -58645,8 +64402,16 @@ Object { "label": null, "nonRoundedGlobalFrame": null, "specialSizeMeasurements": Object { + "alignContent": null, "alignItems": null, + "alignSelf": null, "borderRadius": null, + "borderWidths": Object { + "bottom": 0, + "left": 0, + "right": 0, + "top": 0, + }, "clientHeight": 0, "clientWidth": 0, "closestOffsetParentPath": Object { @@ -58694,6 +64459,7 @@ Object { "gap": null, "globalContentBoxForChildren": null, "globalFrameWithTextContent": null, + "gridCellGlobalFrames": null, "hasPositionOffset": false, "hasTransform": false, "htmlElementName": "div", @@ -58705,7 +64471,9 @@ Object { }, "immediateParentProvidesLayout": true, "justifyContent": null, + "justifySelf": null, "layoutSystemForChildren": null, + "layoutSystemForChildrenInherited": false, "margin": Object {}, "naturalHeight": null, "naturalWidth": null, @@ -58714,8 +64482,24 @@ Object { "y": 0, }, "padding": Object {}, + "parentContainerGridProperties": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, + "parentContainerGridPropertiesFromProps": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, "parentFlexDirection": null, "parentFlexGap": 0, + "parentGridCellGlobalFrames": null, + "parentGridFrame": null, "parentHugsOnMainAxis": false, "parentJustifyContent": null, "parentLayoutSystem": "flow", @@ -58748,6 +64532,7 @@ exports[`UiJsxCanvas render renders fine with two circularly referencing arbitra data-path=\\"utopia-storyboard-uid/scene\\" style=\\" overflow: hidden; + container: scene / inline-size; position: absolute; background-color: var(--utopitheme-emphasizedBackground); box-shadow: 0px 1px 2px 0px var(--utopitheme-shadow90), @@ -58896,8 +64681,16 @@ Object { "label": null, "nonRoundedGlobalFrame": null, "specialSizeMeasurements": Object { + "alignContent": null, "alignItems": null, + "alignSelf": null, "borderRadius": null, + "borderWidths": Object { + "bottom": 0, + "left": 0, + "right": 0, + "top": 0, + }, "clientHeight": 0, "clientWidth": 0, "closestOffsetParentPath": Object { @@ -58945,6 +64738,7 @@ Object { "gap": null, "globalContentBoxForChildren": null, "globalFrameWithTextContent": null, + "gridCellGlobalFrames": null, "hasPositionOffset": false, "hasTransform": false, "htmlElementName": "div", @@ -58956,7 +64750,9 @@ Object { }, "immediateParentProvidesLayout": true, "justifyContent": null, + "justifySelf": null, "layoutSystemForChildren": null, + "layoutSystemForChildrenInherited": false, "margin": Object {}, "naturalHeight": null, "naturalWidth": null, @@ -58965,8 +64761,24 @@ Object { "y": 0, }, "padding": Object {}, + "parentContainerGridProperties": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, + "parentContainerGridPropertiesFromProps": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, "parentFlexDirection": null, "parentFlexGap": 0, + "parentGridCellGlobalFrames": null, + "parentGridFrame": null, "parentHugsOnMainAxis": false, "parentJustifyContent": null, "parentLayoutSystem": "flow", @@ -59042,8 +64854,16 @@ Object { "label": null, "nonRoundedGlobalFrame": null, "specialSizeMeasurements": Object { + "alignContent": null, "alignItems": null, + "alignSelf": null, "borderRadius": null, + "borderWidths": Object { + "bottom": 0, + "left": 0, + "right": 0, + "top": 0, + }, "clientHeight": 0, "clientWidth": 0, "closestOffsetParentPath": Object { @@ -59091,6 +64911,7 @@ Object { "gap": null, "globalContentBoxForChildren": null, "globalFrameWithTextContent": null, + "gridCellGlobalFrames": null, "hasPositionOffset": false, "hasTransform": false, "htmlElementName": "div", @@ -59102,7 +64923,9 @@ Object { }, "immediateParentProvidesLayout": true, "justifyContent": null, + "justifySelf": null, "layoutSystemForChildren": null, + "layoutSystemForChildrenInherited": false, "margin": Object {}, "naturalHeight": null, "naturalWidth": null, @@ -59111,8 +64934,24 @@ Object { "y": 0, }, "padding": Object {}, + "parentContainerGridProperties": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, + "parentContainerGridPropertiesFromProps": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, "parentFlexDirection": null, "parentFlexGap": 0, + "parentGridCellGlobalFrames": null, + "parentGridFrame": null, "parentHugsOnMainAxis": false, "parentJustifyContent": null, "parentLayoutSystem": "flow", @@ -59366,8 +65205,16 @@ export var storyboard = ( "label": null, "nonRoundedGlobalFrame": null, "specialSizeMeasurements": Object { + "alignContent": null, "alignItems": null, + "alignSelf": null, "borderRadius": null, + "borderWidths": Object { + "bottom": 0, + "left": 0, + "right": 0, + "top": 0, + }, "clientHeight": 0, "clientWidth": 0, "closestOffsetParentPath": Object { @@ -59415,6 +65262,7 @@ export var storyboard = ( "gap": null, "globalContentBoxForChildren": null, "globalFrameWithTextContent": null, + "gridCellGlobalFrames": null, "hasPositionOffset": false, "hasTransform": false, "htmlElementName": "div", @@ -59426,7 +65274,9 @@ export var storyboard = ( }, "immediateParentProvidesLayout": true, "justifyContent": null, + "justifySelf": null, "layoutSystemForChildren": null, + "layoutSystemForChildrenInherited": false, "margin": Object {}, "naturalHeight": null, "naturalWidth": null, @@ -59435,8 +65285,24 @@ export var storyboard = ( "y": 0, }, "padding": Object {}, + "parentContainerGridProperties": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, + "parentContainerGridPropertiesFromProps": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, "parentFlexDirection": null, "parentFlexGap": 0, + "parentGridCellGlobalFrames": null, + "parentGridFrame": null, "parentHugsOnMainAxis": false, "parentJustifyContent": null, "parentLayoutSystem": "flow", @@ -59556,8 +65422,16 @@ export var storyboard = ( "label": null, "nonRoundedGlobalFrame": null, "specialSizeMeasurements": Object { + "alignContent": null, "alignItems": null, + "alignSelf": null, "borderRadius": null, + "borderWidths": Object { + "bottom": 0, + "left": 0, + "right": 0, + "top": 0, + }, "clientHeight": 0, "clientWidth": 0, "closestOffsetParentPath": Object { @@ -59605,6 +65479,7 @@ export var storyboard = ( "gap": null, "globalContentBoxForChildren": null, "globalFrameWithTextContent": null, + "gridCellGlobalFrames": null, "hasPositionOffset": false, "hasTransform": false, "htmlElementName": "div", @@ -59616,7 +65491,9 @@ export var storyboard = ( }, "immediateParentProvidesLayout": true, "justifyContent": null, + "justifySelf": null, "layoutSystemForChildren": null, + "layoutSystemForChildrenInherited": false, "margin": Object {}, "naturalHeight": null, "naturalWidth": null, @@ -59625,8 +65502,24 @@ export var storyboard = ( "y": 0, }, "padding": Object {}, + "parentContainerGridProperties": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, + "parentContainerGridPropertiesFromProps": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, "parentFlexDirection": null, "parentFlexGap": 0, + "parentGridCellGlobalFrames": null, + "parentGridFrame": null, "parentHugsOnMainAxis": false, "parentJustifyContent": null, "parentLayoutSystem": "flow", @@ -59746,8 +65639,16 @@ export var storyboard = ( "label": null, "nonRoundedGlobalFrame": null, "specialSizeMeasurements": Object { + "alignContent": null, "alignItems": null, + "alignSelf": null, "borderRadius": null, + "borderWidths": Object { + "bottom": 0, + "left": 0, + "right": 0, + "top": 0, + }, "clientHeight": 0, "clientWidth": 0, "closestOffsetParentPath": Object { @@ -59795,6 +65696,7 @@ export var storyboard = ( "gap": null, "globalContentBoxForChildren": null, "globalFrameWithTextContent": null, + "gridCellGlobalFrames": null, "hasPositionOffset": false, "hasTransform": false, "htmlElementName": "div", @@ -59806,7 +65708,9 @@ export var storyboard = ( }, "immediateParentProvidesLayout": true, "justifyContent": null, + "justifySelf": null, "layoutSystemForChildren": null, + "layoutSystemForChildrenInherited": false, "margin": Object {}, "naturalHeight": null, "naturalWidth": null, @@ -59815,8 +65719,24 @@ export var storyboard = ( "y": 0, }, "padding": Object {}, + "parentContainerGridProperties": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, + "parentContainerGridPropertiesFromProps": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, "parentFlexDirection": null, "parentFlexGap": 0, + "parentGridCellGlobalFrames": null, + "parentGridFrame": null, "parentHugsOnMainAxis": false, "parentJustifyContent": null, "parentLayoutSystem": "flow", @@ -59849,6 +65769,7 @@ exports[`UiJsxCanvas render renders fine with two components that reference each data-path=\\"utopia-storyboard-uid/scene\\" style=\\" overflow: hidden; + container: scene / inline-size; position: absolute; background-color: var(--utopitheme-emphasizedBackground); box-shadow: 0px 1px 2px 0px var(--utopitheme-shadow90), @@ -59996,8 +65917,16 @@ Object { "label": null, "nonRoundedGlobalFrame": null, "specialSizeMeasurements": Object { + "alignContent": null, "alignItems": null, + "alignSelf": null, "borderRadius": null, + "borderWidths": Object { + "bottom": 0, + "left": 0, + "right": 0, + "top": 0, + }, "clientHeight": 0, "clientWidth": 0, "closestOffsetParentPath": Object { @@ -60045,6 +65974,7 @@ Object { "gap": null, "globalContentBoxForChildren": null, "globalFrameWithTextContent": null, + "gridCellGlobalFrames": null, "hasPositionOffset": false, "hasTransform": false, "htmlElementName": "div", @@ -60056,7 +65986,9 @@ Object { }, "immediateParentProvidesLayout": true, "justifyContent": null, + "justifySelf": null, "layoutSystemForChildren": null, + "layoutSystemForChildrenInherited": false, "margin": Object {}, "naturalHeight": null, "naturalWidth": null, @@ -60065,8 +65997,24 @@ Object { "y": 0, }, "padding": Object {}, + "parentContainerGridProperties": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, + "parentContainerGridPropertiesFromProps": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, "parentFlexDirection": null, "parentFlexGap": 0, + "parentGridCellGlobalFrames": null, + "parentGridFrame": null, "parentHugsOnMainAxis": false, "parentJustifyContent": null, "parentLayoutSystem": "flow", @@ -60142,8 +66090,16 @@ Object { "label": null, "nonRoundedGlobalFrame": null, "specialSizeMeasurements": Object { + "alignContent": null, "alignItems": null, + "alignSelf": null, "borderRadius": null, + "borderWidths": Object { + "bottom": 0, + "left": 0, + "right": 0, + "top": 0, + }, "clientHeight": 0, "clientWidth": 0, "closestOffsetParentPath": Object { @@ -60191,6 +66147,7 @@ Object { "gap": null, "globalContentBoxForChildren": null, "globalFrameWithTextContent": null, + "gridCellGlobalFrames": null, "hasPositionOffset": false, "hasTransform": false, "htmlElementName": "div", @@ -60202,7 +66159,9 @@ Object { }, "immediateParentProvidesLayout": true, "justifyContent": null, + "justifySelf": null, "layoutSystemForChildren": null, + "layoutSystemForChildrenInherited": false, "margin": Object {}, "naturalHeight": null, "naturalWidth": null, @@ -60211,8 +66170,24 @@ Object { "y": 0, }, "padding": Object {}, + "parentContainerGridProperties": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, + "parentContainerGridPropertiesFromProps": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, "parentFlexDirection": null, "parentFlexGap": 0, + "parentGridCellGlobalFrames": null, + "parentGridFrame": null, "parentHugsOnMainAxis": false, "parentJustifyContent": null, "parentLayoutSystem": "flow", @@ -60308,8 +66283,16 @@ Object { "label": null, "nonRoundedGlobalFrame": null, "specialSizeMeasurements": Object { + "alignContent": null, "alignItems": null, + "alignSelf": null, "borderRadius": null, + "borderWidths": Object { + "bottom": 0, + "left": 0, + "right": 0, + "top": 0, + }, "clientHeight": 0, "clientWidth": 0, "closestOffsetParentPath": Object { @@ -60357,6 +66340,7 @@ Object { "gap": null, "globalContentBoxForChildren": null, "globalFrameWithTextContent": null, + "gridCellGlobalFrames": null, "hasPositionOffset": false, "hasTransform": false, "htmlElementName": "div", @@ -60368,7 +66352,9 @@ Object { }, "immediateParentProvidesLayout": true, "justifyContent": null, + "justifySelf": null, "layoutSystemForChildren": null, + "layoutSystemForChildrenInherited": false, "margin": Object {}, "naturalHeight": null, "naturalWidth": null, @@ -60377,8 +66363,24 @@ Object { "y": 0, }, "padding": Object {}, + "parentContainerGridProperties": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, + "parentContainerGridPropertiesFromProps": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, "parentFlexDirection": null, "parentFlexGap": 0, + "parentGridCellGlobalFrames": null, + "parentGridFrame": null, "parentHugsOnMainAxis": false, "parentJustifyContent": null, "parentLayoutSystem": "flow", @@ -60411,6 +66413,7 @@ exports[`UiJsxCanvas render renders fragments correctly 1`] = ` data-path=\\"eee/fff\\" style=\\" overflow: hidden; + container: scene / inline-size; position: absolute; background-color: var(--utopitheme-emphasizedBackground); box-shadow: 0px 1px 2px 0px var(--utopitheme-shadow90), @@ -60603,8 +66606,16 @@ Object { "label": null, "nonRoundedGlobalFrame": null, "specialSizeMeasurements": Object { + "alignContent": null, "alignItems": null, + "alignSelf": null, "borderRadius": null, + "borderWidths": Object { + "bottom": 0, + "left": 0, + "right": 0, + "top": 0, + }, "clientHeight": 0, "clientWidth": 0, "closestOffsetParentPath": Object { @@ -60652,6 +66663,7 @@ Object { "gap": null, "globalContentBoxForChildren": null, "globalFrameWithTextContent": null, + "gridCellGlobalFrames": null, "hasPositionOffset": false, "hasTransform": false, "htmlElementName": "div", @@ -60663,7 +66675,9 @@ Object { }, "immediateParentProvidesLayout": true, "justifyContent": null, + "justifySelf": null, "layoutSystemForChildren": null, + "layoutSystemForChildrenInherited": false, "margin": Object {}, "naturalHeight": null, "naturalWidth": null, @@ -60672,8 +66686,24 @@ Object { "y": 0, }, "padding": Object {}, + "parentContainerGridProperties": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, + "parentContainerGridPropertiesFromProps": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, "parentFlexDirection": null, "parentFlexGap": 0, + "parentGridCellGlobalFrames": null, + "parentGridFrame": null, "parentHugsOnMainAxis": false, "parentJustifyContent": null, "parentLayoutSystem": "flow", @@ -60772,8 +66802,16 @@ Object { "label": null, "nonRoundedGlobalFrame": null, "specialSizeMeasurements": Object { + "alignContent": null, "alignItems": null, + "alignSelf": null, "borderRadius": null, + "borderWidths": Object { + "bottom": 0, + "left": 0, + "right": 0, + "top": 0, + }, "clientHeight": 0, "clientWidth": 0, "closestOffsetParentPath": Object { @@ -60821,6 +66859,7 @@ Object { "gap": null, "globalContentBoxForChildren": null, "globalFrameWithTextContent": null, + "gridCellGlobalFrames": null, "hasPositionOffset": false, "hasTransform": false, "htmlElementName": "div", @@ -60832,7 +66871,9 @@ Object { }, "immediateParentProvidesLayout": true, "justifyContent": null, + "justifySelf": null, "layoutSystemForChildren": null, + "layoutSystemForChildrenInherited": false, "margin": Object {}, "naturalHeight": null, "naturalWidth": null, @@ -60841,8 +66882,24 @@ Object { "y": 0, }, "padding": Object {}, + "parentContainerGridProperties": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, + "parentContainerGridPropertiesFromProps": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, "parentFlexDirection": null, "parentFlexGap": 0, + "parentGridCellGlobalFrames": null, + "parentGridFrame": null, "parentHugsOnMainAxis": false, "parentJustifyContent": null, "parentLayoutSystem": "flow", @@ -61278,8 +67335,16 @@ export var storyboard = ( "label": null, "nonRoundedGlobalFrame": null, "specialSizeMeasurements": Object { + "alignContent": null, "alignItems": null, + "alignSelf": null, "borderRadius": null, + "borderWidths": Object { + "bottom": 0, + "left": 0, + "right": 0, + "top": 0, + }, "clientHeight": 0, "clientWidth": 0, "closestOffsetParentPath": Object { @@ -61327,6 +67392,7 @@ export var storyboard = ( "gap": null, "globalContentBoxForChildren": null, "globalFrameWithTextContent": null, + "gridCellGlobalFrames": null, "hasPositionOffset": false, "hasTransform": false, "htmlElementName": "div", @@ -61338,7 +67404,9 @@ export var storyboard = ( }, "immediateParentProvidesLayout": true, "justifyContent": null, + "justifySelf": null, "layoutSystemForChildren": null, + "layoutSystemForChildrenInherited": false, "margin": Object {}, "naturalHeight": null, "naturalWidth": null, @@ -61347,8 +67415,24 @@ export var storyboard = ( "y": 0, }, "padding": Object {}, + "parentContainerGridProperties": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, + "parentContainerGridPropertiesFromProps": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, "parentFlexDirection": null, "parentFlexGap": 0, + "parentGridCellGlobalFrames": null, + "parentGridFrame": null, "parentHugsOnMainAxis": false, "parentJustifyContent": null, "parentLayoutSystem": "flow", @@ -61434,8 +67518,16 @@ export var storyboard = ( "label": null, "nonRoundedGlobalFrame": null, "specialSizeMeasurements": Object { + "alignContent": null, "alignItems": null, + "alignSelf": null, "borderRadius": null, + "borderWidths": Object { + "bottom": 0, + "left": 0, + "right": 0, + "top": 0, + }, "clientHeight": 0, "clientWidth": 0, "closestOffsetParentPath": Object { @@ -61483,6 +67575,7 @@ export var storyboard = ( "gap": null, "globalContentBoxForChildren": null, "globalFrameWithTextContent": null, + "gridCellGlobalFrames": null, "hasPositionOffset": false, "hasTransform": false, "htmlElementName": "div", @@ -61494,7 +67587,9 @@ export var storyboard = ( }, "immediateParentProvidesLayout": true, "justifyContent": null, + "justifySelf": null, "layoutSystemForChildren": null, + "layoutSystemForChildrenInherited": false, "margin": Object {}, "naturalHeight": null, "naturalWidth": null, @@ -61503,8 +67598,24 @@ export var storyboard = ( "y": 0, }, "padding": Object {}, + "parentContainerGridProperties": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, + "parentContainerGridPropertiesFromProps": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, "parentFlexDirection": null, "parentFlexGap": 0, + "parentGridCellGlobalFrames": null, + "parentGridFrame": null, "parentHugsOnMainAxis": false, "parentJustifyContent": null, "parentLayoutSystem": "flow", @@ -61704,8 +67815,16 @@ export var storyboard = ( "label": null, "nonRoundedGlobalFrame": null, "specialSizeMeasurements": Object { + "alignContent": null, "alignItems": null, + "alignSelf": null, "borderRadius": null, + "borderWidths": Object { + "bottom": 0, + "left": 0, + "right": 0, + "top": 0, + }, "clientHeight": 0, "clientWidth": 0, "closestOffsetParentPath": Object { @@ -61753,6 +67872,7 @@ export var storyboard = ( "gap": null, "globalContentBoxForChildren": null, "globalFrameWithTextContent": null, + "gridCellGlobalFrames": null, "hasPositionOffset": false, "hasTransform": false, "htmlElementName": "div", @@ -61764,7 +67884,9 @@ export var storyboard = ( }, "immediateParentProvidesLayout": true, "justifyContent": null, + "justifySelf": null, "layoutSystemForChildren": null, + "layoutSystemForChildrenInherited": false, "margin": Object {}, "naturalHeight": null, "naturalWidth": null, @@ -61773,8 +67895,24 @@ export var storyboard = ( "y": 0, }, "padding": Object {}, + "parentContainerGridProperties": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, + "parentContainerGridPropertiesFromProps": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, "parentFlexDirection": null, "parentFlexGap": 0, + "parentGridCellGlobalFrames": null, + "parentGridFrame": null, "parentHugsOnMainAxis": false, "parentJustifyContent": null, "parentLayoutSystem": "flow", @@ -61972,8 +68110,16 @@ export var storyboard = ( "label": null, "nonRoundedGlobalFrame": null, "specialSizeMeasurements": Object { + "alignContent": null, "alignItems": null, + "alignSelf": null, "borderRadius": null, + "borderWidths": Object { + "bottom": 0, + "left": 0, + "right": 0, + "top": 0, + }, "clientHeight": 0, "clientWidth": 0, "closestOffsetParentPath": Object { @@ -62021,6 +68167,7 @@ export var storyboard = ( "gap": null, "globalContentBoxForChildren": null, "globalFrameWithTextContent": null, + "gridCellGlobalFrames": null, "hasPositionOffset": false, "hasTransform": false, "htmlElementName": "div", @@ -62032,7 +68179,9 @@ export var storyboard = ( }, "immediateParentProvidesLayout": true, "justifyContent": null, + "justifySelf": null, "layoutSystemForChildren": null, + "layoutSystemForChildrenInherited": false, "margin": Object {}, "naturalHeight": null, "naturalWidth": null, @@ -62041,8 +68190,24 @@ export var storyboard = ( "y": 0, }, "padding": Object {}, + "parentContainerGridProperties": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, + "parentContainerGridPropertiesFromProps": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, "parentFlexDirection": null, "parentFlexGap": 0, + "parentGridCellGlobalFrames": null, + "parentGridFrame": null, "parentHugsOnMainAxis": false, "parentJustifyContent": null, "parentLayoutSystem": "flow", @@ -62164,8 +68329,16 @@ export var storyboard = ( "label": null, "nonRoundedGlobalFrame": null, "specialSizeMeasurements": Object { + "alignContent": null, "alignItems": null, + "alignSelf": null, "borderRadius": null, + "borderWidths": Object { + "bottom": 0, + "left": 0, + "right": 0, + "top": 0, + }, "clientHeight": 0, "clientWidth": 0, "closestOffsetParentPath": Object { @@ -62213,6 +68386,7 @@ export var storyboard = ( "gap": null, "globalContentBoxForChildren": null, "globalFrameWithTextContent": null, + "gridCellGlobalFrames": null, "hasPositionOffset": false, "hasTransform": false, "htmlElementName": "div", @@ -62224,7 +68398,9 @@ export var storyboard = ( }, "immediateParentProvidesLayout": true, "justifyContent": null, + "justifySelf": null, "layoutSystemForChildren": null, + "layoutSystemForChildrenInherited": false, "margin": Object {}, "naturalHeight": null, "naturalWidth": null, @@ -62233,8 +68409,24 @@ export var storyboard = ( "y": 0, }, "padding": Object {}, + "parentContainerGridProperties": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, + "parentContainerGridPropertiesFromProps": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, "parentFlexDirection": null, "parentFlexGap": 0, + "parentGridCellGlobalFrames": null, + "parentGridFrame": null, "parentHugsOnMainAxis": false, "parentJustifyContent": null, "parentLayoutSystem": "flow", @@ -62354,8 +68546,16 @@ export var storyboard = ( "label": null, "nonRoundedGlobalFrame": null, "specialSizeMeasurements": Object { + "alignContent": null, "alignItems": null, + "alignSelf": null, "borderRadius": null, + "borderWidths": Object { + "bottom": 0, + "left": 0, + "right": 0, + "top": 0, + }, "clientHeight": 0, "clientWidth": 0, "closestOffsetParentPath": Object { @@ -62403,6 +68603,7 @@ export var storyboard = ( "gap": null, "globalContentBoxForChildren": null, "globalFrameWithTextContent": null, + "gridCellGlobalFrames": null, "hasPositionOffset": false, "hasTransform": false, "htmlElementName": "div", @@ -62414,7 +68615,9 @@ export var storyboard = ( }, "immediateParentProvidesLayout": true, "justifyContent": null, + "justifySelf": null, "layoutSystemForChildren": null, + "layoutSystemForChildrenInherited": false, "margin": Object {}, "naturalHeight": null, "naturalWidth": null, @@ -62423,8 +68626,24 @@ export var storyboard = ( "y": 0, }, "padding": Object {}, + "parentContainerGridProperties": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, + "parentContainerGridPropertiesFromProps": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, "parentFlexDirection": null, "parentFlexGap": 0, + "parentGridCellGlobalFrames": null, + "parentGridFrame": null, "parentHugsOnMainAxis": false, "parentJustifyContent": null, "parentLayoutSystem": "flow", @@ -62457,6 +68676,7 @@ exports[`UiJsxCanvas render renders img tag 1`] = ` data-path=\\"utopia-storyboard-uid/scene-aaa\\" style=\\" overflow: hidden; + container: scene / inline-size; position: absolute; background-color: var(--utopitheme-emphasizedBackground); box-shadow: 0px 1px 2px 0px var(--utopitheme-shadow90), @@ -62631,8 +68851,16 @@ Object { "label": null, "nonRoundedGlobalFrame": null, "specialSizeMeasurements": Object { + "alignContent": null, "alignItems": null, + "alignSelf": null, "borderRadius": null, + "borderWidths": Object { + "bottom": 0, + "left": 0, + "right": 0, + "top": 0, + }, "clientHeight": 0, "clientWidth": 0, "closestOffsetParentPath": Object { @@ -62680,6 +68908,7 @@ Object { "gap": null, "globalContentBoxForChildren": null, "globalFrameWithTextContent": null, + "gridCellGlobalFrames": null, "hasPositionOffset": false, "hasTransform": false, "htmlElementName": "div", @@ -62691,7 +68920,9 @@ Object { }, "immediateParentProvidesLayout": true, "justifyContent": null, + "justifySelf": null, "layoutSystemForChildren": null, + "layoutSystemForChildrenInherited": false, "margin": Object {}, "naturalHeight": null, "naturalWidth": null, @@ -62700,8 +68931,24 @@ Object { "y": 0, }, "padding": Object {}, + "parentContainerGridProperties": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, + "parentContainerGridPropertiesFromProps": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, "parentFlexDirection": null, "parentFlexGap": 0, + "parentGridCellGlobalFrames": null, + "parentGridFrame": null, "parentHugsOnMainAxis": false, "parentJustifyContent": null, "parentLayoutSystem": "flow", @@ -62800,8 +69047,16 @@ Object { "label": null, "nonRoundedGlobalFrame": null, "specialSizeMeasurements": Object { + "alignContent": null, "alignItems": null, + "alignSelf": null, "borderRadius": null, + "borderWidths": Object { + "bottom": 0, + "left": 0, + "right": 0, + "top": 0, + }, "clientHeight": 0, "clientWidth": 0, "closestOffsetParentPath": Object { @@ -62849,6 +69104,7 @@ Object { "gap": null, "globalContentBoxForChildren": null, "globalFrameWithTextContent": null, + "gridCellGlobalFrames": null, "hasPositionOffset": false, "hasTransform": false, "htmlElementName": "div", @@ -62860,7 +69116,9 @@ Object { }, "immediateParentProvidesLayout": true, "justifyContent": null, + "justifySelf": null, "layoutSystemForChildren": null, + "layoutSystemForChildrenInherited": false, "margin": Object {}, "naturalHeight": null, "naturalWidth": null, @@ -62869,8 +69127,24 @@ Object { "y": 0, }, "padding": Object {}, + "parentContainerGridProperties": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, + "parentContainerGridPropertiesFromProps": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, "parentFlexDirection": null, "parentFlexGap": 0, + "parentGridCellGlobalFrames": null, + "parentGridFrame": null, "parentHugsOnMainAxis": false, "parentJustifyContent": null, "parentLayoutSystem": "flow", @@ -62998,8 +69272,16 @@ Object { "label": null, "nonRoundedGlobalFrame": null, "specialSizeMeasurements": Object { + "alignContent": null, "alignItems": null, + "alignSelf": null, "borderRadius": null, + "borderWidths": Object { + "bottom": 0, + "left": 0, + "right": 0, + "top": 0, + }, "clientHeight": 0, "clientWidth": 0, "closestOffsetParentPath": Object { @@ -63047,6 +69329,7 @@ Object { "gap": null, "globalContentBoxForChildren": null, "globalFrameWithTextContent": null, + "gridCellGlobalFrames": null, "hasPositionOffset": false, "hasTransform": false, "htmlElementName": "div", @@ -63058,7 +69341,9 @@ Object { }, "immediateParentProvidesLayout": true, "justifyContent": null, + "justifySelf": null, "layoutSystemForChildren": null, + "layoutSystemForChildrenInherited": false, "margin": Object {}, "naturalHeight": null, "naturalWidth": null, @@ -63067,8 +69352,24 @@ Object { "y": 0, }, "padding": Object {}, + "parentContainerGridProperties": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, + "parentContainerGridPropertiesFromProps": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, "parentFlexDirection": null, "parentFlexGap": 0, + "parentGridCellGlobalFrames": null, + "parentGridFrame": null, "parentHugsOnMainAxis": false, "parentJustifyContent": null, "parentLayoutSystem": "flow", @@ -63165,8 +69466,16 @@ Object { "label": null, "nonRoundedGlobalFrame": null, "specialSizeMeasurements": Object { + "alignContent": null, "alignItems": null, + "alignSelf": null, "borderRadius": null, + "borderWidths": Object { + "bottom": 0, + "left": 0, + "right": 0, + "top": 0, + }, "clientHeight": 0, "clientWidth": 0, "closestOffsetParentPath": Object { @@ -63214,6 +69523,7 @@ Object { "gap": null, "globalContentBoxForChildren": null, "globalFrameWithTextContent": null, + "gridCellGlobalFrames": null, "hasPositionOffset": false, "hasTransform": false, "htmlElementName": "div", @@ -63225,7 +69535,9 @@ Object { }, "immediateParentProvidesLayout": true, "justifyContent": null, + "justifySelf": null, "layoutSystemForChildren": null, + "layoutSystemForChildrenInherited": false, "margin": Object {}, "naturalHeight": null, "naturalWidth": null, @@ -63234,8 +69546,24 @@ Object { "y": 0, }, "padding": Object {}, + "parentContainerGridProperties": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, + "parentContainerGridPropertiesFromProps": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, "parentFlexDirection": null, "parentFlexGap": 0, + "parentGridCellGlobalFrames": null, + "parentGridFrame": null, "parentHugsOnMainAxis": false, "parentJustifyContent": null, "parentLayoutSystem": "flow", @@ -63269,6 +69597,7 @@ exports[`UiJsxCanvas render respects a jsx pragma 1`] = ` data-factory-function-works=\\"true\\" style=\\" overflow: hidden; + container: scene / inline-size; position: absolute; background-color: var(--utopitheme-emphasizedBackground); box-shadow: 0px 1px 2px 0px var(--utopitheme-shadow90), @@ -63440,8 +69769,16 @@ Object { "label": null, "nonRoundedGlobalFrame": null, "specialSizeMeasurements": Object { + "alignContent": null, "alignItems": null, + "alignSelf": null, "borderRadius": null, + "borderWidths": Object { + "bottom": 0, + "left": 0, + "right": 0, + "top": 0, + }, "clientHeight": 0, "clientWidth": 0, "closestOffsetParentPath": Object { @@ -63489,6 +69826,7 @@ Object { "gap": null, "globalContentBoxForChildren": null, "globalFrameWithTextContent": null, + "gridCellGlobalFrames": null, "hasPositionOffset": false, "hasTransform": false, "htmlElementName": "div", @@ -63500,7 +69838,9 @@ Object { }, "immediateParentProvidesLayout": true, "justifyContent": null, + "justifySelf": null, "layoutSystemForChildren": null, + "layoutSystemForChildrenInherited": false, "margin": Object {}, "naturalHeight": null, "naturalWidth": null, @@ -63509,8 +69849,24 @@ Object { "y": 0, }, "padding": Object {}, + "parentContainerGridProperties": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, + "parentContainerGridPropertiesFromProps": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, "parentFlexDirection": null, "parentFlexGap": 0, + "parentGridCellGlobalFrames": null, + "parentGridFrame": null, "parentHugsOnMainAxis": false, "parentJustifyContent": null, "parentLayoutSystem": "flow", @@ -63609,8 +69965,16 @@ Object { "label": null, "nonRoundedGlobalFrame": null, "specialSizeMeasurements": Object { + "alignContent": null, "alignItems": null, + "alignSelf": null, "borderRadius": null, + "borderWidths": Object { + "bottom": 0, + "left": 0, + "right": 0, + "top": 0, + }, "clientHeight": 0, "clientWidth": 0, "closestOffsetParentPath": Object { @@ -63658,6 +70022,7 @@ Object { "gap": null, "globalContentBoxForChildren": null, "globalFrameWithTextContent": null, + "gridCellGlobalFrames": null, "hasPositionOffset": false, "hasTransform": false, "htmlElementName": "div", @@ -63669,7 +70034,9 @@ Object { }, "immediateParentProvidesLayout": true, "justifyContent": null, + "justifySelf": null, "layoutSystemForChildren": null, + "layoutSystemForChildrenInherited": false, "margin": Object {}, "naturalHeight": null, "naturalWidth": null, @@ -63678,8 +70045,24 @@ Object { "y": 0, }, "padding": Object {}, + "parentContainerGridProperties": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, + "parentContainerGridPropertiesFromProps": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, "parentFlexDirection": null, "parentFlexGap": 0, + "parentGridCellGlobalFrames": null, + "parentGridFrame": null, "parentHugsOnMainAxis": false, "parentJustifyContent": null, "parentLayoutSystem": "flow", @@ -63758,8 +70141,16 @@ Object { "label": null, "nonRoundedGlobalFrame": null, "specialSizeMeasurements": Object { + "alignContent": null, "alignItems": null, + "alignSelf": null, "borderRadius": null, + "borderWidths": Object { + "bottom": 0, + "left": 0, + "right": 0, + "top": 0, + }, "clientHeight": 0, "clientWidth": 0, "closestOffsetParentPath": Object { @@ -63807,6 +70198,7 @@ Object { "gap": null, "globalContentBoxForChildren": null, "globalFrameWithTextContent": null, + "gridCellGlobalFrames": null, "hasPositionOffset": false, "hasTransform": false, "htmlElementName": "div", @@ -63818,7 +70210,9 @@ Object { }, "immediateParentProvidesLayout": true, "justifyContent": null, + "justifySelf": null, "layoutSystemForChildren": null, + "layoutSystemForChildrenInherited": false, "margin": Object {}, "naturalHeight": null, "naturalWidth": null, @@ -63827,8 +70221,24 @@ Object { "y": 0, }, "padding": Object {}, + "parentContainerGridProperties": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, + "parentContainerGridPropertiesFromProps": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, "parentFlexDirection": null, "parentFlexGap": 0, + "parentGridCellGlobalFrames": null, + "parentGridFrame": null, "parentHugsOnMainAxis": false, "parentJustifyContent": null, "parentLayoutSystem": "flow", @@ -63861,6 +70271,7 @@ exports[`UiJsxCanvas render supports passing down the scope to children of compo data-path=\\"utopia-storyboard-uid/scene-aaa\\" style=\\" overflow: hidden; + container: scene / inline-size; position: absolute; background-color: var(--utopitheme-emphasizedBackground); box-shadow: 0px 1px 2px 0px var(--utopitheme-shadow90), @@ -64063,8 +70474,16 @@ Object { "label": null, "nonRoundedGlobalFrame": null, "specialSizeMeasurements": Object { + "alignContent": null, "alignItems": null, + "alignSelf": null, "borderRadius": null, + "borderWidths": Object { + "bottom": 0, + "left": 0, + "right": 0, + "top": 0, + }, "clientHeight": 0, "clientWidth": 0, "closestOffsetParentPath": Object { @@ -64112,6 +70531,7 @@ Object { "gap": null, "globalContentBoxForChildren": null, "globalFrameWithTextContent": null, + "gridCellGlobalFrames": null, "hasPositionOffset": false, "hasTransform": false, "htmlElementName": "div", @@ -64123,7 +70543,9 @@ Object { }, "immediateParentProvidesLayout": true, "justifyContent": null, + "justifySelf": null, "layoutSystemForChildren": null, + "layoutSystemForChildrenInherited": false, "margin": Object {}, "naturalHeight": null, "naturalWidth": null, @@ -64132,8 +70554,24 @@ Object { "y": 0, }, "padding": Object {}, + "parentContainerGridProperties": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, + "parentContainerGridPropertiesFromProps": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, "parentFlexDirection": null, "parentFlexGap": 0, + "parentGridCellGlobalFrames": null, + "parentGridFrame": null, "parentHugsOnMainAxis": false, "parentJustifyContent": null, "parentLayoutSystem": "flow", @@ -64232,8 +70670,16 @@ Object { "label": null, "nonRoundedGlobalFrame": null, "specialSizeMeasurements": Object { + "alignContent": null, "alignItems": null, + "alignSelf": null, "borderRadius": null, + "borderWidths": Object { + "bottom": 0, + "left": 0, + "right": 0, + "top": 0, + }, "clientHeight": 0, "clientWidth": 0, "closestOffsetParentPath": Object { @@ -64281,6 +70727,7 @@ Object { "gap": null, "globalContentBoxForChildren": null, "globalFrameWithTextContent": null, + "gridCellGlobalFrames": null, "hasPositionOffset": false, "hasTransform": false, "htmlElementName": "div", @@ -64292,7 +70739,9 @@ Object { }, "immediateParentProvidesLayout": true, "justifyContent": null, + "justifySelf": null, "layoutSystemForChildren": null, + "layoutSystemForChildrenInherited": false, "margin": Object {}, "naturalHeight": null, "naturalWidth": null, @@ -64301,8 +70750,24 @@ Object { "y": 0, }, "padding": Object {}, + "parentContainerGridProperties": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, + "parentContainerGridPropertiesFromProps": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, "parentFlexDirection": null, "parentFlexGap": 0, + "parentGridCellGlobalFrames": null, + "parentGridFrame": null, "parentHugsOnMainAxis": false, "parentJustifyContent": null, "parentLayoutSystem": "flow", @@ -64650,8 +71115,16 @@ Object { "label": null, "nonRoundedGlobalFrame": null, "specialSizeMeasurements": Object { + "alignContent": null, "alignItems": null, + "alignSelf": null, "borderRadius": null, + "borderWidths": Object { + "bottom": 0, + "left": 0, + "right": 0, + "top": 0, + }, "clientHeight": 0, "clientWidth": 0, "closestOffsetParentPath": Object { @@ -64699,6 +71172,7 @@ Object { "gap": null, "globalContentBoxForChildren": null, "globalFrameWithTextContent": null, + "gridCellGlobalFrames": null, "hasPositionOffset": false, "hasTransform": false, "htmlElementName": "div", @@ -64710,7 +71184,9 @@ Object { }, "immediateParentProvidesLayout": true, "justifyContent": null, + "justifySelf": null, "layoutSystemForChildren": null, + "layoutSystemForChildrenInherited": false, "margin": Object {}, "naturalHeight": null, "naturalWidth": null, @@ -64719,8 +71195,24 @@ Object { "y": 0, }, "padding": Object {}, + "parentContainerGridProperties": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, + "parentContainerGridPropertiesFromProps": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, "parentFlexDirection": null, "parentFlexGap": 0, + "parentGridCellGlobalFrames": null, + "parentGridFrame": null, "parentHugsOnMainAxis": false, "parentJustifyContent": null, "parentLayoutSystem": "flow", @@ -65033,8 +71525,16 @@ Object { "label": null, "nonRoundedGlobalFrame": null, "specialSizeMeasurements": Object { + "alignContent": null, "alignItems": null, + "alignSelf": null, "borderRadius": null, + "borderWidths": Object { + "bottom": 0, + "left": 0, + "right": 0, + "top": 0, + }, "clientHeight": 0, "clientWidth": 0, "closestOffsetParentPath": Object { @@ -65082,6 +71582,7 @@ Object { "gap": null, "globalContentBoxForChildren": null, "globalFrameWithTextContent": null, + "gridCellGlobalFrames": null, "hasPositionOffset": false, "hasTransform": false, "htmlElementName": "div", @@ -65093,7 +71594,9 @@ Object { }, "immediateParentProvidesLayout": true, "justifyContent": null, + "justifySelf": null, "layoutSystemForChildren": null, + "layoutSystemForChildrenInherited": false, "margin": Object {}, "naturalHeight": null, "naturalWidth": null, @@ -65102,8 +71605,24 @@ Object { "y": 0, }, "padding": Object {}, + "parentContainerGridProperties": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, + "parentContainerGridPropertiesFromProps": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, "parentFlexDirection": null, "parentFlexGap": 0, + "parentGridCellGlobalFrames": null, + "parentGridFrame": null, "parentHugsOnMainAxis": false, "parentJustifyContent": null, "parentLayoutSystem": "flow", @@ -65264,8 +71783,16 @@ Object { "label": null, "nonRoundedGlobalFrame": null, "specialSizeMeasurements": Object { + "alignContent": null, "alignItems": null, + "alignSelf": null, "borderRadius": null, + "borderWidths": Object { + "bottom": 0, + "left": 0, + "right": 0, + "top": 0, + }, "clientHeight": 0, "clientWidth": 0, "closestOffsetParentPath": Object { @@ -65313,6 +71840,7 @@ Object { "gap": null, "globalContentBoxForChildren": null, "globalFrameWithTextContent": null, + "gridCellGlobalFrames": null, "hasPositionOffset": false, "hasTransform": false, "htmlElementName": "div", @@ -65324,7 +71852,9 @@ Object { }, "immediateParentProvidesLayout": true, "justifyContent": null, + "justifySelf": null, "layoutSystemForChildren": null, + "layoutSystemForChildrenInherited": false, "margin": Object {}, "naturalHeight": null, "naturalWidth": null, @@ -65333,8 +71863,24 @@ Object { "y": 0, }, "padding": Object {}, + "parentContainerGridProperties": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, + "parentContainerGridPropertiesFromProps": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, "parentFlexDirection": null, "parentFlexGap": 0, + "parentGridCellGlobalFrames": null, + "parentGridFrame": null, "parentHugsOnMainAxis": false, "parentJustifyContent": null, "parentLayoutSystem": "flow", @@ -65465,8 +72011,16 @@ Object { "label": null, "nonRoundedGlobalFrame": null, "specialSizeMeasurements": Object { + "alignContent": null, "alignItems": null, + "alignSelf": null, "borderRadius": null, + "borderWidths": Object { + "bottom": 0, + "left": 0, + "right": 0, + "top": 0, + }, "clientHeight": 0, "clientWidth": 0, "closestOffsetParentPath": Object { @@ -65514,6 +72068,7 @@ Object { "gap": null, "globalContentBoxForChildren": null, "globalFrameWithTextContent": null, + "gridCellGlobalFrames": null, "hasPositionOffset": false, "hasTransform": false, "htmlElementName": "div", @@ -65525,7 +72080,9 @@ Object { }, "immediateParentProvidesLayout": true, "justifyContent": null, + "justifySelf": null, "layoutSystemForChildren": null, + "layoutSystemForChildrenInherited": false, "margin": Object {}, "naturalHeight": null, "naturalWidth": null, @@ -65534,8 +72091,24 @@ Object { "y": 0, }, "padding": Object {}, + "parentContainerGridProperties": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, + "parentContainerGridPropertiesFromProps": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, "parentFlexDirection": null, "parentFlexGap": 0, + "parentGridCellGlobalFrames": null, + "parentGridFrame": null, "parentHugsOnMainAxis": false, "parentJustifyContent": null, "parentLayoutSystem": "flow", @@ -65632,8 +72205,16 @@ Object { "label": null, "nonRoundedGlobalFrame": null, "specialSizeMeasurements": Object { + "alignContent": null, "alignItems": null, + "alignSelf": null, "borderRadius": null, + "borderWidths": Object { + "bottom": 0, + "left": 0, + "right": 0, + "top": 0, + }, "clientHeight": 0, "clientWidth": 0, "closestOffsetParentPath": Object { @@ -65681,6 +72262,7 @@ Object { "gap": null, "globalContentBoxForChildren": null, "globalFrameWithTextContent": null, + "gridCellGlobalFrames": null, "hasPositionOffset": false, "hasTransform": false, "htmlElementName": "div", @@ -65692,7 +72274,9 @@ Object { }, "immediateParentProvidesLayout": true, "justifyContent": null, + "justifySelf": null, "layoutSystemForChildren": null, + "layoutSystemForChildrenInherited": false, "margin": Object {}, "naturalHeight": null, "naturalWidth": null, @@ -65701,8 +72285,24 @@ Object { "y": 0, }, "padding": Object {}, + "parentContainerGridProperties": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, + "parentContainerGridPropertiesFromProps": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, "parentFlexDirection": null, "parentFlexGap": 0, + "parentGridCellGlobalFrames": null, + "parentGridFrame": null, "parentHugsOnMainAxis": false, "parentJustifyContent": null, "parentLayoutSystem": "flow", @@ -65863,8 +72463,16 @@ Object { "label": null, "nonRoundedGlobalFrame": null, "specialSizeMeasurements": Object { + "alignContent": null, "alignItems": null, + "alignSelf": null, "borderRadius": null, + "borderWidths": Object { + "bottom": 0, + "left": 0, + "right": 0, + "top": 0, + }, "clientHeight": 0, "clientWidth": 0, "closestOffsetParentPath": Object { @@ -65912,6 +72520,7 @@ Object { "gap": null, "globalContentBoxForChildren": null, "globalFrameWithTextContent": null, + "gridCellGlobalFrames": null, "hasPositionOffset": false, "hasTransform": false, "htmlElementName": "div", @@ -65923,7 +72532,9 @@ Object { }, "immediateParentProvidesLayout": true, "justifyContent": null, + "justifySelf": null, "layoutSystemForChildren": null, + "layoutSystemForChildrenInherited": false, "margin": Object {}, "naturalHeight": null, "naturalWidth": null, @@ -65932,8 +72543,24 @@ Object { "y": 0, }, "padding": Object {}, + "parentContainerGridProperties": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, + "parentContainerGridPropertiesFromProps": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, "parentFlexDirection": null, "parentFlexGap": 0, + "parentGridCellGlobalFrames": null, + "parentGridFrame": null, "parentHugsOnMainAxis": false, "parentJustifyContent": null, "parentLayoutSystem": "flow", @@ -66064,8 +72691,16 @@ Object { "label": null, "nonRoundedGlobalFrame": null, "specialSizeMeasurements": Object { + "alignContent": null, "alignItems": null, + "alignSelf": null, "borderRadius": null, + "borderWidths": Object { + "bottom": 0, + "left": 0, + "right": 0, + "top": 0, + }, "clientHeight": 0, "clientWidth": 0, "closestOffsetParentPath": Object { @@ -66113,6 +72748,7 @@ Object { "gap": null, "globalContentBoxForChildren": null, "globalFrameWithTextContent": null, + "gridCellGlobalFrames": null, "hasPositionOffset": false, "hasTransform": false, "htmlElementName": "div", @@ -66124,7 +72760,9 @@ Object { }, "immediateParentProvidesLayout": true, "justifyContent": null, + "justifySelf": null, "layoutSystemForChildren": null, + "layoutSystemForChildrenInherited": false, "margin": Object {}, "naturalHeight": null, "naturalWidth": null, @@ -66133,8 +72771,24 @@ Object { "y": 0, }, "padding": Object {}, + "parentContainerGridProperties": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, + "parentContainerGridPropertiesFromProps": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, "parentFlexDirection": null, "parentFlexGap": 0, + "parentGridCellGlobalFrames": null, + "parentGridFrame": null, "parentHugsOnMainAxis": false, "parentJustifyContent": null, "parentLayoutSystem": "flow", @@ -66231,8 +72885,16 @@ Object { "label": null, "nonRoundedGlobalFrame": null, "specialSizeMeasurements": Object { + "alignContent": null, "alignItems": null, + "alignSelf": null, "borderRadius": null, + "borderWidths": Object { + "bottom": 0, + "left": 0, + "right": 0, + "top": 0, + }, "clientHeight": 0, "clientWidth": 0, "closestOffsetParentPath": Object { @@ -66280,6 +72942,7 @@ Object { "gap": null, "globalContentBoxForChildren": null, "globalFrameWithTextContent": null, + "gridCellGlobalFrames": null, "hasPositionOffset": false, "hasTransform": false, "htmlElementName": "div", @@ -66291,7 +72954,9 @@ Object { }, "immediateParentProvidesLayout": true, "justifyContent": null, + "justifySelf": null, "layoutSystemForChildren": null, + "layoutSystemForChildrenInherited": false, "margin": Object {}, "naturalHeight": null, "naturalWidth": null, @@ -66300,8 +72965,24 @@ Object { "y": 0, }, "padding": Object {}, + "parentContainerGridProperties": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, + "parentContainerGridPropertiesFromProps": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, "parentFlexDirection": null, "parentFlexGap": 0, + "parentGridCellGlobalFrames": null, + "parentGridFrame": null, "parentHugsOnMainAxis": false, "parentJustifyContent": null, "parentLayoutSystem": "flow", @@ -66462,8 +73143,16 @@ Object { "label": null, "nonRoundedGlobalFrame": null, "specialSizeMeasurements": Object { + "alignContent": null, "alignItems": null, + "alignSelf": null, "borderRadius": null, + "borderWidths": Object { + "bottom": 0, + "left": 0, + "right": 0, + "top": 0, + }, "clientHeight": 0, "clientWidth": 0, "closestOffsetParentPath": Object { @@ -66511,6 +73200,7 @@ Object { "gap": null, "globalContentBoxForChildren": null, "globalFrameWithTextContent": null, + "gridCellGlobalFrames": null, "hasPositionOffset": false, "hasTransform": false, "htmlElementName": "div", @@ -66522,7 +73212,9 @@ Object { }, "immediateParentProvidesLayout": true, "justifyContent": null, + "justifySelf": null, "layoutSystemForChildren": null, + "layoutSystemForChildrenInherited": false, "margin": Object {}, "naturalHeight": null, "naturalWidth": null, @@ -66531,8 +73223,24 @@ Object { "y": 0, }, "padding": Object {}, + "parentContainerGridProperties": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, + "parentContainerGridPropertiesFromProps": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, "parentFlexDirection": null, "parentFlexGap": 0, + "parentGridCellGlobalFrames": null, + "parentGridFrame": null, "parentHugsOnMainAxis": false, "parentJustifyContent": null, "parentLayoutSystem": "flow", @@ -66663,8 +73371,16 @@ Object { "label": null, "nonRoundedGlobalFrame": null, "specialSizeMeasurements": Object { + "alignContent": null, "alignItems": null, + "alignSelf": null, "borderRadius": null, + "borderWidths": Object { + "bottom": 0, + "left": 0, + "right": 0, + "top": 0, + }, "clientHeight": 0, "clientWidth": 0, "closestOffsetParentPath": Object { @@ -66712,6 +73428,7 @@ Object { "gap": null, "globalContentBoxForChildren": null, "globalFrameWithTextContent": null, + "gridCellGlobalFrames": null, "hasPositionOffset": false, "hasTransform": false, "htmlElementName": "div", @@ -66723,7 +73440,9 @@ Object { }, "immediateParentProvidesLayout": true, "justifyContent": null, + "justifySelf": null, "layoutSystemForChildren": null, + "layoutSystemForChildrenInherited": false, "margin": Object {}, "naturalHeight": null, "naturalWidth": null, @@ -66732,8 +73451,24 @@ Object { "y": 0, }, "padding": Object {}, + "parentContainerGridProperties": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, + "parentContainerGridPropertiesFromProps": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, "parentFlexDirection": null, "parentFlexGap": 0, + "parentGridCellGlobalFrames": null, + "parentGridFrame": null, "parentHugsOnMainAxis": false, "parentJustifyContent": null, "parentLayoutSystem": "flow", @@ -66830,8 +73565,16 @@ Object { "label": null, "nonRoundedGlobalFrame": null, "specialSizeMeasurements": Object { + "alignContent": null, "alignItems": null, + "alignSelf": null, "borderRadius": null, + "borderWidths": Object { + "bottom": 0, + "left": 0, + "right": 0, + "top": 0, + }, "clientHeight": 0, "clientWidth": 0, "closestOffsetParentPath": Object { @@ -66879,6 +73622,7 @@ Object { "gap": null, "globalContentBoxForChildren": null, "globalFrameWithTextContent": null, + "gridCellGlobalFrames": null, "hasPositionOffset": false, "hasTransform": false, "htmlElementName": "div", @@ -66890,7 +73634,9 @@ Object { }, "immediateParentProvidesLayout": true, "justifyContent": null, + "justifySelf": null, "layoutSystemForChildren": null, + "layoutSystemForChildrenInherited": false, "margin": Object {}, "naturalHeight": null, "naturalWidth": null, @@ -66899,8 +73645,24 @@ Object { "y": 0, }, "padding": Object {}, + "parentContainerGridProperties": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, + "parentContainerGridPropertiesFromProps": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, "parentFlexDirection": null, "parentFlexGap": 0, + "parentGridCellGlobalFrames": null, + "parentGridFrame": null, "parentHugsOnMainAxis": false, "parentJustifyContent": null, "parentLayoutSystem": "flow", @@ -66933,6 +73695,7 @@ exports[`UiJsxCanvas render the canvas supports emotion CSS prop 1`] = ` data-path=\\"utopia-storyboard-uid/scene-aaa\\" style=\\" overflow: hidden; + container: scene / inline-size; position: absolute; background-color: var(--utopitheme-emphasizedBackground); box-shadow: 0px 1px 2px 0px var(--utopitheme-shadow90), @@ -67104,8 +73867,16 @@ Object { "label": null, "nonRoundedGlobalFrame": null, "specialSizeMeasurements": Object { + "alignContent": null, "alignItems": null, + "alignSelf": null, "borderRadius": null, + "borderWidths": Object { + "bottom": 0, + "left": 0, + "right": 0, + "top": 0, + }, "clientHeight": 0, "clientWidth": 0, "closestOffsetParentPath": Object { @@ -67153,6 +73924,7 @@ Object { "gap": null, "globalContentBoxForChildren": null, "globalFrameWithTextContent": null, + "gridCellGlobalFrames": null, "hasPositionOffset": false, "hasTransform": false, "htmlElementName": "div", @@ -67164,7 +73936,9 @@ Object { }, "immediateParentProvidesLayout": true, "justifyContent": null, + "justifySelf": null, "layoutSystemForChildren": null, + "layoutSystemForChildrenInherited": false, "margin": Object {}, "naturalHeight": null, "naturalWidth": null, @@ -67173,8 +73947,24 @@ Object { "y": 0, }, "padding": Object {}, + "parentContainerGridProperties": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, + "parentContainerGridPropertiesFromProps": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, "parentFlexDirection": null, "parentFlexGap": 0, + "parentGridCellGlobalFrames": null, + "parentGridFrame": null, "parentHugsOnMainAxis": false, "parentJustifyContent": null, "parentLayoutSystem": "flow", @@ -67273,8 +74063,16 @@ Object { "label": null, "nonRoundedGlobalFrame": null, "specialSizeMeasurements": Object { + "alignContent": null, "alignItems": null, + "alignSelf": null, "borderRadius": null, + "borderWidths": Object { + "bottom": 0, + "left": 0, + "right": 0, + "top": 0, + }, "clientHeight": 0, "clientWidth": 0, "closestOffsetParentPath": Object { @@ -67322,6 +74120,7 @@ Object { "gap": null, "globalContentBoxForChildren": null, "globalFrameWithTextContent": null, + "gridCellGlobalFrames": null, "hasPositionOffset": false, "hasTransform": false, "htmlElementName": "div", @@ -67333,7 +74132,9 @@ Object { }, "immediateParentProvidesLayout": true, "justifyContent": null, + "justifySelf": null, "layoutSystemForChildren": null, + "layoutSystemForChildrenInherited": false, "margin": Object {}, "naturalHeight": null, "naturalWidth": null, @@ -67342,8 +74143,24 @@ Object { "y": 0, }, "padding": Object {}, + "parentContainerGridProperties": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, + "parentContainerGridPropertiesFromProps": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, "parentFlexDirection": null, "parentFlexGap": 0, + "parentGridCellGlobalFrames": null, + "parentGridFrame": null, "parentHugsOnMainAxis": false, "parentJustifyContent": null, "parentLayoutSystem": "flow", @@ -67422,8 +74239,16 @@ Object { "label": null, "nonRoundedGlobalFrame": null, "specialSizeMeasurements": Object { + "alignContent": null, "alignItems": null, + "alignSelf": null, "borderRadius": null, + "borderWidths": Object { + "bottom": 0, + "left": 0, + "right": 0, + "top": 0, + }, "clientHeight": 0, "clientWidth": 0, "closestOffsetParentPath": Object { @@ -67471,6 +74296,7 @@ Object { "gap": null, "globalContentBoxForChildren": null, "globalFrameWithTextContent": null, + "gridCellGlobalFrames": null, "hasPositionOffset": false, "hasTransform": false, "htmlElementName": "div", @@ -67482,7 +74308,9 @@ Object { }, "immediateParentProvidesLayout": true, "justifyContent": null, + "justifySelf": null, "layoutSystemForChildren": null, + "layoutSystemForChildrenInherited": false, "margin": Object {}, "naturalHeight": null, "naturalWidth": null, @@ -67491,8 +74319,24 @@ Object { "y": 0, }, "padding": Object {}, + "parentContainerGridProperties": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, + "parentContainerGridPropertiesFromProps": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, "parentFlexDirection": null, "parentFlexGap": 0, + "parentGridCellGlobalFrames": null, + "parentGridFrame": null, "parentHugsOnMainAxis": false, "parentJustifyContent": null, "parentLayoutSystem": "flow", @@ -67525,6 +74369,7 @@ exports[`UiJsxCanvas render the spy wrapper is compatible with React.cloneElemen data-path=\\"utopia-storyboard-uid/scene-aaa\\" style=\\" overflow: hidden; + container: scene / inline-size; position: absolute; background-color: var(--utopitheme-emphasizedBackground); box-shadow: 0px 1px 2px 0px var(--utopitheme-shadow90), @@ -67706,8 +74551,16 @@ Object { "label": null, "nonRoundedGlobalFrame": null, "specialSizeMeasurements": Object { + "alignContent": null, "alignItems": null, + "alignSelf": null, "borderRadius": null, + "borderWidths": Object { + "bottom": 0, + "left": 0, + "right": 0, + "top": 0, + }, "clientHeight": 0, "clientWidth": 0, "closestOffsetParentPath": Object { @@ -67755,6 +74608,7 @@ Object { "gap": null, "globalContentBoxForChildren": null, "globalFrameWithTextContent": null, + "gridCellGlobalFrames": null, "hasPositionOffset": false, "hasTransform": false, "htmlElementName": "div", @@ -67766,7 +74620,9 @@ Object { }, "immediateParentProvidesLayout": true, "justifyContent": null, + "justifySelf": null, "layoutSystemForChildren": null, + "layoutSystemForChildrenInherited": false, "margin": Object {}, "naturalHeight": null, "naturalWidth": null, @@ -67775,8 +74631,24 @@ Object { "y": 0, }, "padding": Object {}, + "parentContainerGridProperties": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, + "parentContainerGridPropertiesFromProps": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, "parentFlexDirection": null, "parentFlexGap": 0, + "parentGridCellGlobalFrames": null, + "parentGridFrame": null, "parentHugsOnMainAxis": false, "parentJustifyContent": null, "parentLayoutSystem": "flow", @@ -67875,8 +74747,16 @@ Object { "label": null, "nonRoundedGlobalFrame": null, "specialSizeMeasurements": Object { + "alignContent": null, "alignItems": null, + "alignSelf": null, "borderRadius": null, + "borderWidths": Object { + "bottom": 0, + "left": 0, + "right": 0, + "top": 0, + }, "clientHeight": 0, "clientWidth": 0, "closestOffsetParentPath": Object { @@ -67924,6 +74804,7 @@ Object { "gap": null, "globalContentBoxForChildren": null, "globalFrameWithTextContent": null, + "gridCellGlobalFrames": null, "hasPositionOffset": false, "hasTransform": false, "htmlElementName": "div", @@ -67935,7 +74816,9 @@ Object { }, "immediateParentProvidesLayout": true, "justifyContent": null, + "justifySelf": null, "layoutSystemForChildren": null, + "layoutSystemForChildrenInherited": false, "margin": Object {}, "naturalHeight": null, "naturalWidth": null, @@ -67944,8 +74827,24 @@ Object { "y": 0, }, "padding": Object {}, + "parentContainerGridProperties": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, + "parentContainerGridPropertiesFromProps": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, "parentFlexDirection": null, "parentFlexGap": 0, + "parentGridCellGlobalFrames": null, + "parentGridFrame": null, "parentHugsOnMainAxis": false, "parentJustifyContent": null, "parentLayoutSystem": "flow", @@ -68092,8 +74991,16 @@ Object { "label": null, "nonRoundedGlobalFrame": null, "specialSizeMeasurements": Object { + "alignContent": null, "alignItems": null, + "alignSelf": null, "borderRadius": null, + "borderWidths": Object { + "bottom": 0, + "left": 0, + "right": 0, + "top": 0, + }, "clientHeight": 0, "clientWidth": 0, "closestOffsetParentPath": Object { @@ -68141,6 +75048,7 @@ Object { "gap": null, "globalContentBoxForChildren": null, "globalFrameWithTextContent": null, + "gridCellGlobalFrames": null, "hasPositionOffset": false, "hasTransform": false, "htmlElementName": "div", @@ -68152,7 +75060,9 @@ Object { }, "immediateParentProvidesLayout": true, "justifyContent": null, + "justifySelf": null, "layoutSystemForChildren": null, + "layoutSystemForChildrenInherited": false, "margin": Object {}, "naturalHeight": null, "naturalWidth": null, @@ -68161,8 +75071,24 @@ Object { "y": 0, }, "padding": Object {}, + "parentContainerGridProperties": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, + "parentContainerGridPropertiesFromProps": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, "parentFlexDirection": null, "parentFlexGap": 0, + "parentGridCellGlobalFrames": null, + "parentGridFrame": null, "parentHugsOnMainAxis": false, "parentJustifyContent": null, "parentLayoutSystem": "flow", @@ -68279,8 +75205,16 @@ Object { "label": null, "nonRoundedGlobalFrame": null, "specialSizeMeasurements": Object { + "alignContent": null, "alignItems": null, + "alignSelf": null, "borderRadius": null, + "borderWidths": Object { + "bottom": 0, + "left": 0, + "right": 0, + "top": 0, + }, "clientHeight": 0, "clientWidth": 0, "closestOffsetParentPath": Object { @@ -68328,6 +75262,7 @@ Object { "gap": null, "globalContentBoxForChildren": null, "globalFrameWithTextContent": null, + "gridCellGlobalFrames": null, "hasPositionOffset": false, "hasTransform": false, "htmlElementName": "div", @@ -68339,7 +75274,9 @@ Object { }, "immediateParentProvidesLayout": true, "justifyContent": null, + "justifySelf": null, "layoutSystemForChildren": null, + "layoutSystemForChildrenInherited": false, "margin": Object {}, "naturalHeight": null, "naturalWidth": null, @@ -68348,8 +75285,24 @@ Object { "y": 0, }, "padding": Object {}, + "parentContainerGridProperties": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, + "parentContainerGridPropertiesFromProps": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, "parentFlexDirection": null, "parentFlexGap": 0, + "parentGridCellGlobalFrames": null, + "parentGridFrame": null, "parentHugsOnMainAxis": false, "parentJustifyContent": null, "parentLayoutSystem": "flow", @@ -68436,8 +75389,16 @@ Object { "label": null, "nonRoundedGlobalFrame": null, "specialSizeMeasurements": Object { + "alignContent": null, "alignItems": null, + "alignSelf": null, "borderRadius": null, + "borderWidths": Object { + "bottom": 0, + "left": 0, + "right": 0, + "top": 0, + }, "clientHeight": 0, "clientWidth": 0, "closestOffsetParentPath": Object { @@ -68485,6 +75446,7 @@ Object { "gap": null, "globalContentBoxForChildren": null, "globalFrameWithTextContent": null, + "gridCellGlobalFrames": null, "hasPositionOffset": false, "hasTransform": false, "htmlElementName": "div", @@ -68496,7 +75458,9 @@ Object { }, "immediateParentProvidesLayout": true, "justifyContent": null, + "justifySelf": null, "layoutSystemForChildren": null, + "layoutSystemForChildrenInherited": false, "margin": Object {}, "naturalHeight": null, "naturalWidth": null, @@ -68505,8 +75469,24 @@ Object { "y": 0, }, "padding": Object {}, + "parentContainerGridProperties": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, + "parentContainerGridPropertiesFromProps": Object { + "gridAutoColumns": null, + "gridAutoFlow": null, + "gridAutoRows": null, + "gridTemplateColumns": null, + "gridTemplateRows": null, + }, "parentFlexDirection": null, "parentFlexGap": 0, + "parentGridCellGlobalFrames": null, + "parentGridFrame": null, "parentHugsOnMainAxis": false, "parentJustifyContent": null, "parentLayoutSystem": "flow", diff --git a/editor/src/components/canvas/canvas-component-entry.tsx b/editor/src/components/canvas/canvas-component-entry.tsx index ba919a3f5bbb..52e1365dea3f 100644 --- a/editor/src/components/canvas/canvas-component-entry.tsx +++ b/editor/src/components/canvas/canvas-component-entry.tsx @@ -25,7 +25,11 @@ import type { } from './ui-jsx-canvas' import { DomWalkerInvalidatePathsCtxAtom, UiJsxCanvas, pickUiJsxCanvasProps } from './ui-jsx-canvas' -interface CanvasComponentEntryProps {} +export const CanvasContainerOuterId = 'canvas-container-outer' + +interface CanvasComponentEntryProps { + shouldRenderCanvas: boolean +} export const CanvasComponentEntry = React.memo((props: CanvasComponentEntryProps) => { const canvasStore = React.useContext(CanvasStateContext) @@ -85,7 +89,7 @@ const CanvasComponentEntryInner = React.memo((props: CanvasComponentEntryProps) <> {when(canvasProps == null, )}
- {canvasProps == null ? null : ( + {props.shouldRenderCanvas && canvasProps != null ? ( - )} + ) : null}
) diff --git a/editor/src/components/canvas/canvas-floating-toolbars.tsx b/editor/src/components/canvas/canvas-floating-toolbars.tsx index 87e1e7041f26..938290b89427 100644 --- a/editor/src/components/canvas/canvas-floating-toolbars.tsx +++ b/editor/src/components/canvas/canvas-floating-toolbars.tsx @@ -6,6 +6,7 @@ import { ErrorOverlayComponent } from './canvas-error-overlay' import { SafeModeErrorOverlay } from './canvas-wrapper-component' import { CanvasStrategyPicker } from './controls/select-mode/canvas-strategy-picker' import { TestMenu } from '../titlebar/test-menu' +import { ToastRenderer } from '../editor/editor-component' export const CanvasFloatingToolbars = React.memo((props: { style: React.CSSProperties }) => { const safeMode = useEditorState( @@ -43,6 +44,9 @@ export const CanvasFloatingToolbars = React.memo((props: { style: React.CSSPrope + + + {/* The error overlays are deliberately the last here so they hide other canvas UI, except the test menu */} {safeMode ? : } diff --git a/editor/src/components/canvas/canvas-loading-screen.tsx b/editor/src/components/canvas/canvas-loading-screen.tsx index c9778254c3a1..854dec875c93 100644 --- a/editor/src/components/canvas/canvas-loading-screen.tsx +++ b/editor/src/components/canvas/canvas-loading-screen.tsx @@ -1,9 +1,46 @@ import React from 'react' import { Global, css } from '@emotion/react' import { useColorTheme } from '../../uuiui' +import { useEditorState } from '../editor/store/store-hook' +import { Substores } from '../editor/store/store-hook' +import { getTotalImportStatusAndResult } from '../../core/shared/import/import-operation-service' +import type { TotalImportResult } from '../../core/shared/import/import-operation-types' export const CanvasLoadingScreen = React.memo(() => { const colorTheme = useColorTheme() + const importState = useEditorState( + Substores.github, + (store) => store.editor.importState, + 'CanvasLoadingScreen importState', + ) + const importWizardOpen = useEditorState( + Substores.restOfEditor, + (store) => store.editor.importWizardOpen, + 'CanvasLoadingScreen importWizardOpen', + ) + + const totalImportResult: TotalImportResult = React.useMemo( + () => getTotalImportStatusAndResult(importState), + [importState], + ) + + const importingStoppedStyleOverride = React.useMemo( + () => + // if the importing was stopped, we want to pause the shimmer animation + (importWizardOpen && totalImportResult.importStatus.status === 'done') || + totalImportResult.importStatus.status === 'paused' + ? { + background: colorTheme.codeEditorShimmerPrimary.value, + animation: 'none', + } + : {}, + [ + importWizardOpen, + totalImportResult.importStatus.status, + colorTheme.codeEditorShimmerPrimary.value, + ], + ) + return ( { background-size: 1468px 104px; position: relative; } + + .no-shimmer { + animation: none; + background: ${colorTheme.codeEditorShimmerPrimary.value}; + } `} />
{ top: 0, width: '100vw', height: '100vh', + ...importingStoppedStyleOverride, }} >
diff --git a/editor/src/components/canvas/canvas-strategies/canvas-strategies.spec.browser2.tsx b/editor/src/components/canvas/canvas-strategies/canvas-strategies.spec.browser2.tsx index 3239dcd9cee1..c3dad1aac9a2 100644 --- a/editor/src/components/canvas/canvas-strategies/canvas-strategies.spec.browser2.tsx +++ b/editor/src/components/canvas/canvas-strategies/canvas-strategies.spec.browser2.tsx @@ -176,6 +176,7 @@ describe('Strategy Fitness', () => { const canvasStrategy = findCanvasStrategy( RegisteredCanvasStrategies, pickCanvasStateFromEditorState( + renderResult.getEditorState().editor.selectedViews, renderResult.getEditorState().editor, renderResult.getEditorState().builtInDependencies, ), @@ -226,6 +227,7 @@ describe('Strategy Fitness', () => { const canvasStrategy = findCanvasStrategy( RegisteredCanvasStrategies, pickCanvasStateFromEditorState( + renderResult.getEditorState().editor.selectedViews, renderResult.getEditorState().editor, renderResult.getEditorState().builtInDependencies, ), @@ -313,6 +315,7 @@ describe('Strategy Fitness', () => { const canvasStrategy = findCanvasStrategy( RegisteredCanvasStrategies, pickCanvasStateFromEditorState( + renderResult.getEditorState().editor.selectedViews, renderResult.getEditorState().editor, renderResult.getEditorState().builtInDependencies, ), @@ -363,6 +366,7 @@ describe('Strategy Fitness', () => { const canvasStrategy = findCanvasStrategy( RegisteredCanvasStrategies, pickCanvasStateFromEditorState( + renderResult.getEditorState().editor.selectedViews, renderResult.getEditorState().editor, renderResult.getEditorState().builtInDependencies, ), diff --git a/editor/src/components/canvas/canvas-strategies/canvas-strategies.tsx b/editor/src/components/canvas/canvas-strategies/canvas-strategies.tsx index 02751f5b3c75..1f6367b241eb 100644 --- a/editor/src/components/canvas/canvas-strategies/canvas-strategies.tsx +++ b/editor/src/components/canvas/canvas-strategies/canvas-strategies.tsx @@ -1,6 +1,5 @@ import React from 'react' -import { createSelector } from 'reselect' -import { addAllUniquelyBy, mapDropNulls, sortBy } from '../../../core/shared/array-utils' +import { mapDropNulls, pushUniquelyBy, sortBy } from '../../../core/shared/array-utils' import type { ElementInstanceMetadataMap } from '../../../core/shared/element-template' import { arrayEqualsByReference, assertNever } from '../../../core/shared/utils' import type { @@ -8,6 +7,7 @@ import type { EditorState, EditorStatePatch, EditorStorePatched, + GridIdentifier, } from '../../editor/store/editor-state' import { Substores, useEditorState, useSelectorWithCallback } from '../../editor/store/store-hook' import type { @@ -19,6 +19,7 @@ import type { StrategyApplicationResult, InteractionLifecycle, CustomStrategyState, + WhenToShowControl, } from './canvas-strategy-types' import { ControlDelay, @@ -67,11 +68,11 @@ import type { InsertionSubject, InsertionSubjectWrapper } from '../../editor/edi import { generateUidWithExistingComponents } from '../../../core/model/element-template-utils' import { retargetStrategyToChildrenOfFragmentLikeElements } from './strategies/fragment-like-helpers' import { MetadataUtils } from '../../../core/model/element-metadata-utils' -import { gridRearrangeMoveStrategy } from './strategies/grid-rearrange-move-strategy' +import { gridChangeElementLocationStrategy } from './strategies/grid-change-element-location-strategy' import { resizeGridStrategy } from './strategies/resize-grid-strategy' -import { rearrangeGridSwapStrategy } from './strategies/rearrange-grid-swap-strategy' import { gridResizeElementStrategy } from './strategies/grid-resize-element-strategy' -import { gridRearrangeMoveDuplicateStrategy } from './strategies/grid-rearrange-move-duplicate-strategy' +import { gridResizeElementRulerStrategy } from './strategies/grid-resize-element-ruler-strategy' +import { gridChangeElementLocationDuplicateStrategy } from './strategies/grid-change-element-location-duplicate-strategy' import { setGridGapStrategy } from './strategies/set-grid-gap-strategy' import type { CanvasCommand } from '../commands/commands' import { foldAndApplyCommandsInner } from '../commands/commands' @@ -80,7 +81,16 @@ import { wrapInContainerCommand } from '../commands/wrap-in-container-command' import type { ElementPath } from 'utopia-shared/src/types' import { reparentSubjectsForInteractionTarget } from './strategies/reparent-helpers/reparent-strategy-helpers' import { getReparentTargetUnified } from './strategies/reparent-helpers/reparent-strategy-parent-lookup' -import { gridRearrangeResizeKeyboardStrategy } from './strategies/grid-rearrange-keyboard-strategy' +import { gridChangeElementLocationResizeKeyboardStrategy } from './strategies/grid-change-element-location-keyboard-strategy' +import createCachedSelector from 're-reselect' +import { getActivePlugin, patchRemovedProperties } from '../plugins/style-plugins' +import { + controlsForGridPlaceholders, + GridControls, + isGridControlsProps, +} from '../controls/grid-controls-for-strategies' +import { gridReorderStrategy } from './strategies/grid-reorder-strategy' +import { gridMoveAbsoluteStrategy } from './strategies/grid-move-absolute' export type CanvasStrategyFactory = ( canvasState: InteractionCanvasState, @@ -108,10 +118,11 @@ const moveOrReorderStrategies: MetaCanvasStrategy = ( convertToAbsoluteAndMoveStrategy, convertToAbsoluteAndMoveAndSetParentFixedStrategy, reorderSliderStategy, - gridRearrangeMoveStrategy, - rearrangeGridSwapStrategy, - gridRearrangeMoveDuplicateStrategy, - gridRearrangeResizeKeyboardStrategy, + gridChangeElementLocationStrategy, + gridChangeElementLocationDuplicateStrategy, + gridReorderStrategy, + gridChangeElementLocationResizeKeyboardStrategy, + gridMoveAbsoluteStrategy, ], ) } @@ -131,6 +142,7 @@ const resizeStrategies: MetaCanvasStrategy = ( basicResizeStrategy, resizeGridStrategy, gridResizeElementStrategy, + gridResizeElementRulerStrategy, ], ) } @@ -197,12 +209,14 @@ export const RegisteredCanvasStrategies: Array = [ ] export function pickCanvasStateFromEditorState( + localSelectedViews: Array, editorState: EditorState, builtInDependencies: BuiltInDependencies, ): InteractionCanvasState { + const activePlugin = getActivePlugin(editorState) return { builtInDependencies: builtInDependencies, - interactionTarget: getInteractionTargetFromEditorState(editorState), + interactionTarget: getInteractionTargetFromEditorState(editorState, localSelectedViews), projectContents: editorState.projectContents, nodeModules: editorState.nodeModules.files, openFile: editorState.canvas.openFile?.filename, @@ -212,18 +226,25 @@ export function pickCanvasStateFromEditorState( startingElementPathTree: editorState.elementPathTree, startingAllElementProps: editorState.allElementProps, propertyControlsInfo: editorState.propertyControlsInfo, + styleInfoReader: activePlugin.styleInfoFactory({ + projectContents: editorState.projectContents, + jsxMetadata: editorState.jsxMetadata, + }), } } export function pickCanvasStateFromEditorStateWithMetadata( + localSelectedViews: Array, editorState: EditorState, builtInDependencies: BuiltInDependencies, metadata: ElementInstanceMetadataMap, allElementProps?: AllElementProps, ): InteractionCanvasState { + const activePlugin = getActivePlugin(editorState) + return { builtInDependencies: builtInDependencies, - interactionTarget: getInteractionTargetFromEditorState(editorState), + interactionTarget: getInteractionTargetFromEditorState(editorState, localSelectedViews), projectContents: editorState.projectContents, nodeModules: editorState.nodeModules.files, openFile: editorState.canvas.openFile?.filename, @@ -233,10 +254,17 @@ export function pickCanvasStateFromEditorStateWithMetadata( startingElementPathTree: editorState.elementPathTree, // IMPORTANT! This isn't based on the passed in metadata startingAllElementProps: allElementProps ?? editorState.allElementProps, propertyControlsInfo: editorState.propertyControlsInfo, + styleInfoReader: activePlugin.styleInfoFactory({ + projectContents: editorState.projectContents, + jsxMetadata: editorState.jsxMetadata, + }), } } -function getInteractionTargetFromEditorState(editor: EditorState): InteractionTarget { +function getInteractionTargetFromEditorState( + editor: EditorState, + localSelectedViews: Array, +): InteractionTarget { switch (editor.mode.type) { case 'insert': return insertionSubjects(editor.mode.subjects) @@ -245,7 +273,7 @@ function getInteractionTargetFromEditorState(editor: EditorState): InteractionTa case 'textEdit': case 'comment': case 'follow': - return targetPaths(editor.selectedViews) + return targetPaths(localSelectedViews) default: assertNever(editor.mode) } @@ -288,17 +316,21 @@ export function getApplicableStrategies( return strategies.flatMap((s) => s(canvasState, interactionSession, customStrategyState)) } -const getApplicableStrategiesSelector = createSelector( - (store: EditorStorePatched) => +const getApplicableStrategiesSelector = createCachedSelector( + (store: EditorStorePatched, _) => optionalMap( (sas) => sas.map((s) => s.strategy), store.strategyState.sortedApplicableStrategies, ), - (store: EditorStorePatched): InteractionCanvasState => { - return pickCanvasStateFromEditorState(store.editor, store.builtInDependencies) + (store: EditorStorePatched, localSelectedViews: Array): InteractionCanvasState => { + return pickCanvasStateFromEditorState( + localSelectedViews, + store.editor, + store.builtInDependencies, + ) }, - (store: EditorStorePatched) => store.editor.canvas.interactionSession, - (store: EditorStorePatched) => store.strategyState.customStrategyState, + (store: EditorStorePatched, _) => store.editor.canvas.interactionSession, + (store: EditorStorePatched, _) => store.strategyState.customStrategyState, ( applicableStrategiesFromStrategyState: Array | null, canvasState: InteractionCanvasState, @@ -316,12 +348,12 @@ const getApplicableStrategiesSelector = createSelector( ) } }, -) +)((_, localSelectedViews: Array) => localSelectedViews.map(EP.toString).join(',')) -function useGetApplicableStrategies(): Array { +function useGetApplicableStrategies(localSelectedViews: Array): Array { return useEditorState( Substores.fullStore, - getApplicableStrategiesSelector, + (store) => getApplicableStrategiesSelector(store, localSelectedViews), 'useGetApplicableStrategies', arrayEqualsByReference, ) @@ -465,6 +497,29 @@ export function applyCanvasStrategy( return strategy.apply(strategyLifecycle) } +export function applyElementsToRerenderFromStrategyResultAndPatchRemovedProps( + editorState: EditorState, + strategyResult: StrategyApplicationResult, +): EditorState { + return applyElementsToRerenderFromStrategyResult( + patchRemovedProperties(editorState), + strategyResult, + ) +} + +export function applyElementsToRerenderFromStrategyResult( + editorState: EditorState, + strategyResult: StrategyApplicationResult, +): EditorState { + return { + ...editorState, + canvas: { + ...editorState.canvas, + elementsToRerender: strategyResult.elementsToRerender, + }, + } +} + export function useDelayedEditorState( selector: StateSelector, selectorName: string, @@ -611,8 +666,52 @@ function controlPriorityToNumber(prio: ControlWithProps['priority']): numbe } } -export function useGetApplicableStrategyControls(): Array> { - const applicableStrategies = useGetApplicableStrategies() +export function combineApplicableControls( + strategyControls: Array>, +): Array> { + // Separate out the instances of `GridControls`. + let result: Array> = [] + let gridControlsInstances: Array> = [] + for (const control of strategyControls) { + if (control.control === GridControls) { + gridControlsInstances.push(control) + } else { + result.push(control) + } + } + + // Sift the instances of `GridControls`, storing their targets by when they should be shown. + let gridControlsTargets: Map> = new Map() + for (const control of gridControlsInstances) { + if (isGridControlsProps(control.props)) { + let possibleTargets = gridControlsTargets.get(control.show) + if (possibleTargets == null) { + gridControlsTargets.set(control.show, control.props.targets) + } else { + possibleTargets.push(...control.props.targets) + } + } + } + + // Create new instances of `GridControls` with the combined targets. + for (const [show, targets] of gridControlsTargets) { + result.push(controlsForGridPlaceholders(targets, show, `-${show}`)) + } + + // Return the newly created controls with the combined entries. + return result +} + +const controlEquals = (l: ControlWithProps, r: ControlWithProps) => { + return l.control === r.control && l.key === r.key +} + +export function useGetApplicableStrategyControls(localSelectedViews: Array): { + bottomStrategyControls: Array> + middleStrategyControls: Array> + topStrategyControls: Array> +} { + const applicableStrategies = useGetApplicableStrategies(localSelectedViews) const currentStrategy = useDelayedCurrentStrategy() const currentlyInProgress = useEditorState( Substores.canvas, @@ -622,30 +721,47 @@ export function useGetApplicableStrategyControls(): Array { - let applicableControls: Array> = [] + let strategyControls: Array> = [] let isResizable: boolean = false // Add the controls for currently applicable strategies. for (const strategy of applicableStrategies) { if (isResizableStrategy(strategy)) { isResizable = true } - const strategyControls = getApplicableControls(currentStrategy, strategy) - applicableControls = addAllUniquelyBy( - applicableControls, - strategyControls, - (l, r) => l.control === r.control && l.key === r.key, - ) + strategyControls.push(...getApplicableControls(currentStrategy, strategy)) + } + const combinedControls = combineApplicableControls(strategyControls) + const bottomStrategyControls: Array> = [] + const middleStrategyControls: Array> = [] + const topStrategyControls: Array> = [] + + // uniquely add the strategyControls to the bottom, middle, and top arrays + for (const control of combinedControls) { + switch (control.priority) { + case 'bottom': + pushUniquelyBy(bottomStrategyControls, control, controlEquals) + break + case undefined: + pushUniquelyBy(middleStrategyControls, control, controlEquals) + break + case 'top': + pushUniquelyBy(topStrategyControls, control, controlEquals) + break + default: + assertNever(control.priority) + } } + // Special case controls. if (!isResizable && !currentlyInProgress) { - applicableControls.push(notResizableControls) + middleStrategyControls.push(notResizableControls) } - applicableControls = applicableControls.sort( - (a, b) => controlPriorityToNumber(a.priority) - controlPriorityToNumber(b.priority), - ) - - return applicableControls + return { + bottomStrategyControls: bottomStrategyControls, + middleStrategyControls: middleStrategyControls, + topStrategyControls: topStrategyControls, + } }, [applicableStrategies, currentStrategy, currentlyInProgress]) } diff --git a/editor/src/components/canvas/canvas-strategies/canvas-strategy-types.ts b/editor/src/components/canvas/canvas-strategies/canvas-strategy-types.ts index e1809f458ea4..e27c7bdd404b 100644 --- a/editor/src/components/canvas/canvas-strategies/canvas-strategy-types.ts +++ b/editor/src/components/canvas/canvas-strategies/canvas-strategy-types.ts @@ -1,7 +1,10 @@ import React from 'react' import type { BuiltInDependencies } from '../../../core/es-modules/package-manager/built-in-dependencies-list' import type { ElementPathTrees } from '../../../core/shared/element-path-tree' -import type { ElementInstanceMetadataMap } from '../../../core/shared/element-template' +import type { + ElementInstanceMetadata, + ElementInstanceMetadataMap, +} from '../../../core/shared/element-template' import type { CanvasVector } from '../../../core/shared/math-utils' import type { ElementPath, NodeModules } from '../../../core/shared/project-file-types' import type { ProjectContentTreeRoot } from '../../assets' @@ -10,9 +13,8 @@ import type { InsertionSubject } from '../../editor/editor-modes' import type { AllElementProps } from '../../editor/store/editor-state' import type { CanvasCommand } from '../commands/commands' import type { ActiveFrameAction } from '../commands/set-active-frames-command' -import type { GridCellCoordinates } from '../controls/grid-controls' import type { StrategyApplicationStatus } from './interaction-state' -import type { TargetGridCellData } from './strategies/grid-helpers' +import type { StyleInfo } from '../canvas-types' // TODO: fill this in, maybe make it an ADT for different strategies export interface CustomStrategyState { @@ -26,10 +28,7 @@ export interface CustomStrategyState { } export type GridCustomStrategyState = { - targetCellData: TargetGridCellData | null - draggingFromCell: GridCellCoordinates | null - originalRootCell: GridCellCoordinates | null - currentRootCell: GridCellCoordinates | null + metadataCacheForGrids: { [gridPath: string]: ElementInstanceMetadata } } export type CustomStrategyStatePatch = Partial @@ -43,33 +42,34 @@ export function defaultCustomStrategyState(): CustomStrategyState { elementsToRerender: [], action: null, grid: { - targetCellData: null, - draggingFromCell: null, - originalRootCell: null, - currentRootCell: null, + metadataCacheForGrids: {}, }, } } export interface StrategyApplicationResult { commands: Array + elementsToRerender: ElementPath[] customStatePatch: CustomStrategyStatePatch status: StrategyApplicationStatus } export const emptyStrategyApplicationResult: StrategyApplicationResult = { commands: [], + elementsToRerender: [], customStatePatch: {}, status: 'success', } export function strategyApplicationResult( commands: Array, + elementsToRerender: ElementPath[], customStatePatch: CustomStrategyStatePatch = {}, status: StrategyApplicationStatus = 'success', ): StrategyApplicationResult { return { commands: commands, + elementsToRerender: elementsToRerender, customStatePatch: customStatePatch, status: status, } @@ -85,8 +85,12 @@ export interface ControlForStrategy

{ control: React.FC

} +export function controlForStrategy

(control: React.FC

): ControlForStrategy

{ + return { type: 'ControlForStrategy', control: control } +} + export function controlForStrategyMemoized

(control: React.FC

): ControlForStrategy

{ - return { type: 'ControlForStrategy', control: React.memo(control) } + return controlForStrategy(React.memo(control)) } export type WhenToShowControl = @@ -106,6 +110,14 @@ export function controlWithProps

(value: ControlWithProps

): ControlWithProp return value } +export type StyleInfoReader = (elementPath: ElementPath) => StyleInfo | null + +export type StyleInfoContext = { + projectContents: ProjectContentTreeRoot + jsxMetadata: ElementInstanceMetadataMap +} +export type StyleInfoFactory = (context: StyleInfoContext) => StyleInfoReader + export interface InteractionCanvasState { interactionTarget: InteractionTarget projectContents: ProjectContentTreeRoot @@ -118,6 +130,7 @@ export interface InteractionCanvasState { startingElementPathTree: ElementPathTrees startingAllElementProps: AllElementProps propertyControlsInfo: PropertyControlsInfo + styleInfoReader: StyleInfoReader } export type InteractionTarget = TargetPaths | InsertionSubjects diff --git a/editor/src/components/canvas/canvas-strategies/interaction-state.ts b/editor/src/components/canvas/canvas-strategies/interaction-state.ts index 8be4107e03b3..90d223c210b6 100644 --- a/editor/src/components/canvas/canvas-strategies/interaction-state.ts +++ b/editor/src/components/canvas/canvas-strategies/interaction-state.ts @@ -646,13 +646,13 @@ export function reorderSlider(): ReorderSlider { export interface GridCellHandle { type: 'GRID_CELL_HANDLE' - id: string + path: ElementPath } -export function gridCellHandle(params: { id: string }): GridCellHandle { +export function gridCellHandle(params: { path: ElementPath }): GridCellHandle { return { type: 'GRID_CELL_HANDLE', - id: params.id, + path: params.path, } } @@ -689,6 +689,20 @@ export function gridResizeHandle(id: string, edge: GridResizeEdge): GridResizeHa } } +export interface GridResizeRulerHandle { + type: 'GRID_RESIZE_RULER_HANDLE' + id: string + edge: GridResizeEdge +} + +export function gridResizeRulerHandle(id: string, edge: GridResizeEdge): GridResizeRulerHandle { + return { + type: 'GRID_RESIZE_RULER_HANDLE', + id: id, + edge: edge, + } +} + export type CanvasControlType = | BoundingArea | ResizeHandle @@ -701,6 +715,7 @@ export type CanvasControlType = | GridCellHandle | GridAxisHandle | GridResizeHandle + | GridResizeRulerHandle export function isDragToPan( interaction: InteractionSession | null, diff --git a/editor/src/components/canvas/canvas-strategies/strategies/absolute-duplicate-strategy.spec.tsx b/editor/src/components/canvas/canvas-strategies/strategies/absolute-duplicate-strategy.spec.tsx index 39f4ab07acaa..8db594de244a 100644 --- a/editor/src/components/canvas/canvas-strategies/strategies/absolute-duplicate-strategy.spec.tsx +++ b/editor/src/components/canvas/canvas-strategies/strategies/absolute-duplicate-strategy.spec.tsx @@ -74,6 +74,7 @@ function dragByPixelsIsApplicable( return ( absoluteDuplicateStrategy( pickCanvasStateFromEditorStateWithMetadata( + editorState.selectedViews, editorState, createBuiltInDependenciesList(null), metadata, diff --git a/editor/src/components/canvas/canvas-strategies/strategies/absolute-duplicate-strategy.tsx b/editor/src/components/canvas/canvas-strategies/strategies/absolute-duplicate-strategy.tsx index 2dff81adbae2..9fae9c8996d2 100644 --- a/editor/src/components/canvas/canvas-strategies/strategies/absolute-duplicate-strategy.tsx +++ b/editor/src/components/canvas/canvas-strategies/strategies/absolute-duplicate-strategy.tsx @@ -12,7 +12,7 @@ import type { CanvasCommand } from '../../commands/commands' import { foldAndApplyCommandsInner } from '../../commands/commands' import { duplicateElement } from '../../commands/duplicate-element-command' import { setCursorCommand } from '../../commands/set-cursor-command' -import { setElementsToRerenderCommand } from '../../commands/set-elements-to-rerender-command' + import { updateFunctionCommand } from '../../commands/update-function-command' import { updateSelectedViews } from '../../commands/update-selected-views-command' import { ImmediateParentBounds } from '../../controls/parent-bounds' @@ -127,7 +127,6 @@ export function absoluteDuplicateStrategy( [ ...duplicateCommands, ...maybeAddContainLayoutCommand(commonParentPath), - setElementsToRerenderCommand([commonParentPath, ...selectedElements, ...newPaths]), updateSelectedViews('always', selectedElements), updateFunctionCommand('always', (editorState, commandLifecycle) => runMoveStrategy( @@ -140,13 +139,14 @@ export function absoluteDuplicateStrategy( ), setCursorCommand(CSSCursor.Duplicate), ], + [commonParentPath, ...selectedElements, ...newPaths], { duplicatedElementNewUids: duplicatedElementNewUids, }, ) } else { // Fallback for when the checks above are not satisfied. - return strategyApplicationResult([setCursorCommand(CSSCursor.Duplicate)]) + return strategyApplicationResult([setCursorCommand(CSSCursor.Duplicate)], []) } }, } diff --git a/editor/src/components/canvas/canvas-strategies/strategies/absolute-reparent-strategy-canvas.spec.tsx b/editor/src/components/canvas/canvas-strategies/strategies/absolute-reparent-strategy-canvas.spec.tsx index e438034e8e9c..1e10e820b95b 100644 --- a/editor/src/components/canvas/canvas-strategies/strategies/absolute-reparent-strategy-canvas.spec.tsx +++ b/editor/src/components/canvas/canvas-strategies/strategies/absolute-reparent-strategy-canvas.spec.tsx @@ -133,6 +133,7 @@ function reparentElement( } const canvasState = pickCanvasStateFromEditorStateWithMetadata( + editorState.selectedViews, editorState, createBuiltInDependenciesList(null), startingMetadata, @@ -151,15 +152,7 @@ function reparentElement( expect(strategyResult.customStatePatch).toEqual({}) expect(strategyResult.status).toEqual('success') - // Check if there are set SetElementsToRerenderCommands with the new parent path - expect( - strategyResult.commands.find( - (c) => - c.type === 'SET_ELEMENTS_TO_RERENDER_COMMAND' && - c.value !== 'rerender-all-elements' && - c.value.every((p) => EP.pathsEqual(EP.parentPath(p), newParent)), - ), - ).not.toBeNull() + expect(strategyResult.elementsToRerender.map(EP.parentPath)).toEqual([newParent]) const finalEditor = foldAndApplyCommands( editorState, diff --git a/editor/src/components/canvas/canvas-strategies/strategies/absolute-reparent-strategy.spec.tsx b/editor/src/components/canvas/canvas-strategies/strategies/absolute-reparent-strategy.spec.tsx index 464de829ae5b..ed2b95df96ac 100644 --- a/editor/src/components/canvas/canvas-strategies/strategies/absolute-reparent-strategy.spec.tsx +++ b/editor/src/components/canvas/canvas-strategies/strategies/absolute-reparent-strategy.spec.tsx @@ -202,6 +202,7 @@ function reparentElement( } const canvasState = pickCanvasStateFromEditorStateWithMetadata( + editorState.selectedViews, editorState, createBuiltInDependenciesList(null), startingMetadata, @@ -222,15 +223,7 @@ function reparentElement( expect(strategyResult.customStatePatch).toEqual({}) expect(strategyResult.status).toEqual('success') - // Check if there are set SetElementsToRerenderCommands with the new parent path - expect( - strategyResult.commands.find( - (c) => - c.type === 'SET_ELEMENTS_TO_RERENDER_COMMAND' && - c.value !== 'rerender-all-elements' && - c.value.every((p) => EP.pathsEqual(EP.parentPath(p), newParent)), - ), - ).not.toBeNull() + expect(strategyResult.elementsToRerender.map(EP.parentPath)).toEqual([newParent]) return foldAndApplyCommands( editorState, diff --git a/editor/src/components/canvas/canvas-strategies/strategies/absolute-reparent-strategy.tsx b/editor/src/components/canvas/canvas-strategies/strategies/absolute-reparent-strategy.tsx index 557f1c8724cf..736f34d19fcf 100644 --- a/editor/src/components/canvas/canvas-strategies/strategies/absolute-reparent-strategy.tsx +++ b/editor/src/components/canvas/canvas-strategies/strategies/absolute-reparent-strategy.tsx @@ -3,9 +3,13 @@ import { MetadataUtils } from '../../../../core/model/element-metadata-utils' import { mapDropNulls } from '../../../../core/shared/array-utils' import * as EP from '../../../../core/shared/element-path' import type { ElementPathTrees } from '../../../../core/shared/element-path-tree' -import type { ElementInstanceMetadataMap } from '../../../../core/shared/element-template' +import { + metadataHasPositionAbsoluteOrNull, + type ElementInstanceMetadataMap, +} from '../../../../core/shared/element-template' import type { ElementPath, NodeModules } from '../../../../core/shared/project-file-types' import * as PP from '../../../../core/shared/property-path' +import { assertNever } from '../../../../core/shared/utils' import type { IndexPosition } from '../../../../utils/utils' import type { ProjectContentTreeRoot } from '../../../assets' import type { AllElementProps } from '../../../editor/store/editor-state' @@ -13,7 +17,7 @@ import type { InsertionPath } from '../../../editor/store/insertion-path' import { CSSCursor } from '../../canvas-types' import type { CanvasCommand } from '../../commands/commands' import { setCursorCommand } from '../../commands/set-cursor-command' -import { setElementsToRerenderCommand } from '../../commands/set-elements-to-rerender-command' + import { setProperty } from '../../commands/set-property-command' import { updateSelectedViews } from '../../commands/update-selected-views-command' import { ParentBounds } from '../../controls/parent-bounds' @@ -37,6 +41,7 @@ import type { InteractionSession, UpdatedPathMap } from '../interaction-state' import { absoluteMoveStrategy } from './absolute-move-strategy' import { honoursPropsPosition, shouldKeepMovingDraggedGroupChildren } from './absolute-utils' import { replaceFragmentLikePathsWithTheirChildrenRecursive } from './fragment-like-helpers' +import type { ShouldAddContainLayout } from './reparent-helpers/reparent-helpers' import { ifAllowedToReparent, isAllowedToReparent } from './reparent-helpers/reparent-helpers' import type { ForcePins } from './reparent-helpers/reparent-property-changes' import { getAbsoluteReparentPropertyChanges } from './reparent-helpers/reparent-property-changes' @@ -74,10 +79,18 @@ export function baseAbsoluteReparentStrategy( element, ) - return ( - elementMetadata?.specialSizeMeasurements.position === 'absolute' && - honoursPropsPosition(canvasState, element) - ) + const honoursPosition = honoursPropsPosition(canvasState, element) + + switch (honoursPosition) { + case 'does-not-honour': + return false + case 'absolute-position-and-honours-numeric-props': + return metadataHasPositionAbsoluteOrNull(elementMetadata) + case 'honours-numeric-props-only': + return elementMetadata?.specialSizeMeasurements.position === 'absolute' + default: + assertNever(honoursPosition) + } }) if (!isApplicable) { return null @@ -163,7 +176,6 @@ export function applyAbsoluteReparent( const allowedToReparent = selectedElements.every((selectedElement) => { return isAllowedToReparent( - canvasState.projectContents, canvasState.startingMetadata, selectedElement, newParent.intendedParentPath, @@ -184,6 +196,12 @@ export function applyAbsoluteReparent( projectContents, nodeModules, 'force-pins', + shouldAddContainLayout( + canvasState.startingMetadata, + canvasState.startingAllElementProps, + canvasState.startingElementPathTree, + newParent.intendedParentPath, + ), ), selectedElements, ) @@ -217,7 +235,6 @@ export function applyAbsoluteReparent( ...moveCommands, ...commands.flatMap((c) => c.commands), updateSelectedViews('always', newPaths), - setElementsToRerenderCommand(elementsToRerender), ...maybeAddContainLayout( canvasState.startingMetadata, canvasState.startingAllElementProps, @@ -226,17 +243,18 @@ export function applyAbsoluteReparent( ), setCursorCommand(CSSCursor.Reparent), ], + elementsToRerender, { elementsToRerender, }, ) } else { - const moveCommands = + return ( absoluteMoveStrategy(canvasState, interactionSession, { ...defaultCustomStrategyState(), action: 'reparent', - })?.strategy.apply(strategyLifecycle).commands ?? [] - return strategyApplicationResult(moveCommands) + })?.strategy.apply(strategyLifecycle) ?? emptyStrategyApplicationResult + ) } }, ) @@ -254,6 +272,7 @@ export function createAbsoluteReparentAndOffsetCommands( projectContents: ProjectContentTreeRoot, nodeModules: NodeModules, forcePins: ForcePins, + willContainLayoutBeAdded: ShouldAddContainLayout, ) { const reparentResult = getReparentOutcome( metadata, @@ -271,12 +290,13 @@ export function createAbsoluteReparentAndOffsetCommands( if (reparentResult == null) { return null } else { - const offsetCommands = replaceFragmentLikePathsWithTheirChildrenRecursive( + const replacedPaths = replaceFragmentLikePathsWithTheirChildrenRecursive( metadata, elementProps, pathTree, [target], - ).flatMap((p) => { + ) + const offsetCommands = replacedPaths.flatMap((p) => { return getAbsoluteReparentPropertyChanges( p, newParent.intendedParentPath, @@ -284,6 +304,7 @@ export function createAbsoluteReparentAndOffsetCommands( metadata, projectContents, forcePins, + willContainLayoutBeAdded, ) }) @@ -296,12 +317,12 @@ export function createAbsoluteReparentAndOffsetCommands( } } -function maybeAddContainLayout( +export function shouldAddContainLayout( metadata: ElementInstanceMetadataMap, allElementProps: AllElementProps, pathTrees: ElementPathTrees, path: ElementPath, -): CanvasCommand[] { +): ShouldAddContainLayout { const closestNonFragmentParent = MetadataUtils.getClosestNonFragmentParent( metadata, allElementProps, @@ -310,14 +331,23 @@ function maybeAddContainLayout( ) if (EP.isStoryboardPath(closestNonFragmentParent)) { - return [] + return 'dont-add-contain-layout' } const parentProvidesBoundsForAbsoluteChildren = MetadataUtils.findElementByElementPath(metadata, closestNonFragmentParent) ?.specialSizeMeasurements.providesBoundsForAbsoluteChildren === true - return parentProvidesBoundsForAbsoluteChildren - ? [] - : [setProperty('always', path, PP.create('style', 'contain'), 'layout')] + return parentProvidesBoundsForAbsoluteChildren ? 'dont-add-contain-layout' : 'add-contain-layout' +} + +function maybeAddContainLayout( + metadata: ElementInstanceMetadataMap, + allElementProps: AllElementProps, + pathTrees: ElementPathTrees, + path: ElementPath, +): CanvasCommand[] { + return shouldAddContainLayout(metadata, allElementProps, pathTrees, path) === 'add-contain-layout' + ? [setProperty('always', path, PP.create('style', 'contain'), 'layout')] + : [] } diff --git a/editor/src/components/canvas/canvas-strategies/strategies/absolute-resize-bounding-box-strategy.spec.browser2.tsx b/editor/src/components/canvas/canvas-strategies/strategies/absolute-resize-bounding-box-strategy.spec.browser2.tsx index 60d5a5802bde..be9f3546588b 100644 --- a/editor/src/components/canvas/canvas-strategies/strategies/absolute-resize-bounding-box-strategy.spec.browser2.tsx +++ b/editor/src/components/canvas/canvas-strategies/strategies/absolute-resize-bounding-box-strategy.spec.browser2.tsx @@ -55,7 +55,6 @@ import { CanvasControlsContainerID } from '../../controls/new-canvas-controls' import type { CSSProperties } from 'react' import { MaxContent } from '../../../inspector/inspector-common' import { - SizeLabelTestId, ResizePointTestId, AbsoluteResizeControlTestId, } from '../../controls/select-mode/absolute-resize-control' @@ -78,6 +77,7 @@ import { } from '../../controls/bounding-box-hooks' import { act } from '@testing-library/react' import { ComponentsHonouringPropsStylesProject } from './common-projects.test-utils' +import { SizeLabelTestId } from '../../controls/select-mode/size-label' // no mouseup here! it starts the interaction and resizes with drag delta async function startDragUsingActions( @@ -678,7 +678,7 @@ describe('Absolute Resize Strategy', () => { EP.appendNewElementPath(TestScenePath, ['root', 'sizeless']), ]) const sizeLabel = await editor.renderedDOM.findByTestId(SizeLabelTestId) - expect(sizeLabel.textContent).toEqual('(Children) 188 x 161') + expect(sizeLabel.textContent).toEqual('(Children) 188 × 161') }) it('resizes component instances that honour the size properties', async () => { const renderResult = await renderTestEditorWithCode( diff --git a/editor/src/components/canvas/canvas-strategies/strategies/absolute-resize-bounding-box-strategy.spec.tsx b/editor/src/components/canvas/canvas-strategies/strategies/absolute-resize-bounding-box-strategy.spec.tsx index dc38cee0ac73..c159ff9fa5c5 100644 --- a/editor/src/components/canvas/canvas-strategies/strategies/absolute-resize-bounding-box-strategy.spec.tsx +++ b/editor/src/components/canvas/canvas-strategies/strategies/absolute-resize-bounding-box-strategy.spec.tsx @@ -52,6 +52,7 @@ function multiselectResizeElements( const strategyResult = absoluteResizeBoundingBoxStrategy( pickCanvasStateFromEditorStateWithMetadata( + initialEditor.selectedViews, initialEditor, createBuiltInDependenciesList(null), metadata, diff --git a/editor/src/components/canvas/canvas-strategies/strategies/absolute-resize-bounding-box-strategy.tsx b/editor/src/components/canvas/canvas-strategies/strategies/absolute-resize-bounding-box-strategy.tsx index 9ce4abe252c4..1aabffbe33ee 100644 --- a/editor/src/components/canvas/canvas-strategies/strategies/absolute-resize-bounding-box-strategy.tsx +++ b/editor/src/components/canvas/canvas-strategies/strategies/absolute-resize-bounding-box-strategy.tsx @@ -29,13 +29,14 @@ import { pushIntendedBoundsAndUpdateGroups } from '../../commands/push-intended- import { queueTrueUpElement } from '../../commands/queue-true-up-command' import { activeFrameTargetRect, setActiveFrames } from '../../commands/set-active-frames-command' import { setCursorCommand } from '../../commands/set-cursor-command' -import { setElementsToRerenderCommand } from '../../commands/set-elements-to-rerender-command' + import { setSnappingGuidelines } from '../../commands/set-snapping-guidelines-command' import { updateHighlightedViews } from '../../commands/update-highlighted-views-command' import { gatherParentAndSiblingTargets } from '../../controls/guideline-helpers' import { ImmediateParentBounds } from '../../controls/parent-bounds' import { ImmediateParentOutlines } from '../../controls/parent-outlines' import { AbsoluteResizeControl } from '../../controls/select-mode/absolute-resize-control' +import { StrategySizeLabel } from '../../controls/select-mode/size-label' import { ZeroSizeResizeControlWrapper } from '../../controls/zero-sized-element-controls' import { getDescriptiveStrategyLabelWithRetargetedPaths, @@ -98,7 +99,7 @@ export function absoluteResizeBoundingBoxStrategy( } if ( - retargetedTargets.some((path) => MetadataUtils.isGridCell(canvasState.startingMetadata, path)) + retargetedTargets.some((path) => MetadataUtils.isGridItem(canvasState.startingMetadata, path)) ) { return null } @@ -120,6 +121,14 @@ export function absoluteResizeBoundingBoxStrategy( props: { targets: originalTargets, pathsWereReplaced: pathsWereReplaced }, key: 'absolute-resize-control', show: 'visible-except-when-other-strategy-is-active', + priority: 'top', + }), + controlWithProps({ + control: StrategySizeLabel, + props: { targets: originalTargets, pathsWereReplaced: pathsWereReplaced }, + key: 'size-label', + show: 'visible-except-when-other-strategy-is-active', + priority: 'top', }), controlWithProps({ control: ZeroSizeResizeControlWrapper, @@ -141,7 +150,7 @@ export function absoluteResizeBoundingBoxStrategy( }), ], fitness: onlyFitWhenDraggingThisControl(interactionSession, 'RESIZE_HANDLE', 1), - apply: () => { + apply: (lifecycle) => { if ( interactionSession != null && interactionSession.interactionData.type === 'DRAG' && @@ -289,19 +298,24 @@ export function absoluteResizeBoundingBoxStrategy( ] }) - return strategyApplicationResult([ - ...commandsForSelectedElements, - setSnappingGuidelines('mid-interaction', guidelinesWithSnappingVector), - updateHighlightedViews('mid-interaction', []), - setCursorCommand(pickCursorFromEdgePosition(edgePosition)), - setElementsToRerenderCommand(retargetedTargets), - ]) + return strategyApplicationResult( + [ + ...commandsForSelectedElements, + setSnappingGuidelines('mid-interaction', guidelinesWithSnappingVector), + updateHighlightedViews('mid-interaction', []), + setCursorCommand(pickCursorFromEdgePosition(edgePosition)), + ], + retargetedTargets, + ) } } else { - return strategyApplicationResult([ - setCursorCommand(pickCursorFromEdgePosition(edgePosition)), - updateHighlightedViews('mid-interaction', []), - ]) + return strategyApplicationResult( + [ + setCursorCommand(pickCursorFromEdgePosition(edgePosition)), + updateHighlightedViews('mid-interaction', []), + ], + [], + ) } } // Fallback for when the checks above are not satisfied. @@ -476,7 +490,7 @@ function getConstrainedSizes( constraints.right || constraints.width - const localFrame = MetadataUtils.getLocalFrame(element.elementPath, jsxMetadata) + const localFrame = MetadataUtils.getLocalFrame(element.elementPath, jsxMetadata, null) if ( isConstrained && localFrame != null && diff --git a/editor/src/components/canvas/canvas-strategies/strategies/ancestor-metastrategy.tsx b/editor/src/components/canvas/canvas-strategies/strategies/ancestor-metastrategy.tsx index bc4b655e30ea..ff5db3fc3121 100644 --- a/editor/src/components/canvas/canvas-strategies/strategies/ancestor-metastrategy.tsx +++ b/editor/src/components/canvas/canvas-strategies/strategies/ancestor-metastrategy.tsx @@ -9,20 +9,21 @@ import { CSSCursor } from '../../canvas-types' import type { CanvasCommand } from '../../commands/commands' import { highlightElementsCommand } from '../../commands/highlight-element-command' import { setCursorCommand } from '../../commands/set-cursor-command' -import { appendElementsToRerenderCommand } from '../../commands/set-elements-to-rerender-command' import { wildcardPatch } from '../../commands/wildcard-patch-command' import { onlyFitWhenThisControlIsActive, type MetaCanvasStrategy } from '../canvas-strategies' import type { CanvasStrategy, InteractionCanvasState, InteractionLifecycle, + StrategyApplicationResult, } from '../canvas-strategy-types' import { getTargetPathsFromInteractionTarget, targetPaths } from '../canvas-strategy-types' import { DoNothingStrategyID } from './drag-to-move-metastrategy' -import { - retargetStrategyToChildrenOfFragmentLikeElements, - treatElementAsFragmentLike, -} from './fragment-like-helpers' +import { retargetStrategyToChildrenOfFragmentLikeElements } from './fragment-like-helpers' +import type { Optic } from '../../../../core/shared/optics/optics' +import { filtered, fromField } from '../../../../core/shared/optics/optic-creators' +import { modify } from '../../../../core/shared/optics/optic-utilities' +import type { ElementPath } from '../../../../core/shared/project-file-types' const ANCESTOR_INCOMPATIBLE_STRATEGIES = [DoNothingStrategyID] @@ -125,11 +126,7 @@ export function ancestorMetaStrategy( return nextAncestorResult.map((s) => ({ ...s, fitness: fitness(s), - apply: appendCommandsToApplyResult( - s.apply, - [appendElementsToRerenderCommand([target])], - [], - ), + apply: appendElementsToRerenderToApplyResult(s.apply, [target]), })) } else { // Otherwise we should stop at this ancestor and return the strategies for this ancestor @@ -150,22 +147,21 @@ export function ancestorMetaStrategy( id: `${s.id}_ANCESTOR_${level}`, name: applyLevelSuffix(s.name, level), fitness: fitness(s), - apply: appendCommandsToApplyResult( - s.apply, - [ - appendElementsToRerenderCommand([target]), - highlightElementsCommand([parentPath]), - setCursorCommand(CSSCursor.MovingMagic), - ], - [ - wildcardPatch('mid-interaction', { - canvas: { - controls: { - dragToMoveIndicatorFlags: { ancestor: { $set: true } }, + apply: appendElementsToRerenderToApplyResult( + appendCommandsToApplyResult( + s.apply, + [highlightElementsCommand([parentPath]), setCursorCommand(CSSCursor.MovingMagic)], + [ + wildcardPatch('mid-interaction', { + canvas: { + controls: { + dragToMoveIndicatorFlags: { ancestor: { $set: true } }, + }, }, - }, - }), - ], + }), + ], + ), + [target], ), } }), @@ -175,6 +171,7 @@ export function ancestorMetaStrategy( } type ApplyFn = CanvasStrategy['apply'] + export function appendCommandsToApplyResult( applyFn: ApplyFn, commandsToAppendToExtendResult: Array, @@ -204,6 +201,27 @@ export function appendCommandsToApplyResult( } } +const strategyResultElementsToRerenderOptic: Optic = + filtered((result) => result.status === 'success').compose( + fromField('elementsToRerender'), + ) + +export function appendElementsToRerenderToApplyResult( + applyFn: ApplyFn, + additionalElementsToRerender: ElementPath[], +): ApplyFn { + return (strategyLifecycle: InteractionLifecycle) => { + const result = applyFn(strategyLifecycle) + return modify( + strategyResultElementsToRerenderOptic, + (toRerender) => { + return [...toRerender, ...additionalElementsToRerender] + }, + result, + ) + } +} + function applyLevelSuffix(name: string, level: number): string { // FIXME What should we use for the label here? const newSuffix = `(Up ${level})` diff --git a/editor/src/components/canvas/canvas-strategies/strategies/basic-resize-strategy.tsx b/editor/src/components/canvas/canvas-strategies/strategies/basic-resize-strategy.tsx index 6a0246a9980f..976888db8e84 100644 --- a/editor/src/components/canvas/canvas-strategies/strategies/basic-resize-strategy.tsx +++ b/editor/src/components/canvas/canvas-strategies/strategies/basic-resize-strategy.tsx @@ -1,8 +1,8 @@ -import { styleStringInArray } from '../../../../utils/common-constants' import { getLayoutProperty } from '../../../../core/layout/getLayoutProperty' import type { PropsOrJSXAttributes } from '../../../../core/model/element-metadata-utils' import { MetadataUtils } from '../../../../core/model/element-metadata-utils' import { foldEither, isLeft, right } from '../../../../core/shared/either' +import * as EP from '../../../../core/shared/element-path' import type { ElementInstanceMetadata } from '../../../../core/shared/element-template' import { isJSXElement } from '../../../../core/shared/element-template' import type { CanvasPoint, CanvasRectangle } from '../../../../core/shared/math-utils' @@ -11,12 +11,17 @@ import { isInfinityRectangle, offsetPoint, } from '../../../../core/shared/math-utils' +import * as PP from '../../../../core/shared/property-path' +import { styleStringInArray } from '../../../../utils/common-constants' +import { gridItemIdentifier, trueUpGroupElementChanged } from '../../../editor/store/editor-state' import { stylePropPathMappingFn } from '../../../inspector/common/property-path-hooks' +import { isFillOrStretchModeApplied } from '../../../inspector/inspector-common' import type { EdgePosition } from '../../canvas-types' import { oppositeEdgePosition } from '../../canvas-types' import { isEdgePositionACorner, isEdgePositionAHorizontalEdge, + isEdgePositionAVerticalEdge, pickPointOnRect, } from '../../canvas-utils' import type { LengthPropertyToAdjust } from '../../commands/adjust-css-length-command' @@ -24,12 +29,21 @@ import { adjustCssLengthProperties, lengthPropertyToAdjust, } from '../../commands/adjust-css-length-command' +import type { CanvasCommand } from '../../commands/commands' +import { deleteProperties } from '../../commands/delete-properties-command' +import { pushIntendedBoundsAndUpdateGroups } from '../../commands/push-intended-bounds-and-update-groups-command' +import { queueTrueUpElement } from '../../commands/queue-true-up-command' import { setCursorCommand } from '../../commands/set-cursor-command' -import { setElementsToRerenderCommand } from '../../commands/set-elements-to-rerender-command' + import { updateHighlightedViews } from '../../commands/update-highlighted-views-command' +import { + controlsForGridPlaceholders, + controlsForGridRulers, +} from '../../controls/grid-controls-for-strategies' import { ImmediateParentBounds } from '../../controls/parent-bounds' import { ImmediateParentOutlines } from '../../controls/parent-outlines' import { AbsoluteResizeControl } from '../../controls/select-mode/absolute-resize-control' +import { StrategySizeLabel } from '../../controls/select-mode/size-label' import { ZeroSizeResizeControlWrapper } from '../../controls/zero-sized-element-controls' import type { CanvasStrategy, @@ -44,16 +58,13 @@ import { } from '../canvas-strategy-types' import type { InteractionSession } from '../interaction-state' import { honoursPropsSize } from './absolute-utils' +import { treatElementAsGroupLike } from './group-helpers' import { getLockedAspectRatio, isAnySelectedElementAspectRatioLocked, pickCursorFromEdgePosition, resizeBoundingBox, } from './resize-helpers' -import { pushIntendedBoundsAndUpdateGroups } from '../../commands/push-intended-bounds-and-update-groups-command' -import { queueTrueUpElement } from '../../commands/queue-true-up-command' -import { treatElementAsGroupLike } from './group-helpers' -import { trueUpGroupElementChanged } from '../../../editor/store/editor-state' export const BASIC_RESIZE_STRATEGY_ID = 'BASIC_RESIZE' @@ -76,7 +87,8 @@ export function basicResizeStrategy( const elementDimensionsProps = metadata != null ? getElementDimensions(metadata) : null const elementParentBounds = metadata?.specialSizeMeasurements.immediateParentBounds ?? null - if (MetadataUtils.isGridCell(canvasState.startingMetadata, selectedElement)) { + const isGridCell = MetadataUtils.isGridItem(canvasState.startingMetadata, selectedElement) + if (isGridCell && isFillOrStretchModeApplied(canvasState.startingMetadata, selectedElement)) { return null } @@ -94,6 +106,14 @@ export function basicResizeStrategy( props: { targets: selectedElements, pathsWereReplaced: false }, key: 'absolute-resize-control', show: 'always-visible', + priority: 'top', + }), + controlWithProps({ + control: StrategySizeLabel, + props: { targets: selectedElements, pathsWereReplaced: false }, + key: 'size-label', + show: 'visible-except-when-other-strategy-is-active', + priority: 'top', }), controlWithProps({ control: ZeroSizeResizeControlWrapper, @@ -113,6 +133,12 @@ export function basicResizeStrategy( key: 'parent-bounds-control', show: 'visible-only-while-active', }), + ...(isGridCell + ? [ + controlsForGridPlaceholders(gridItemIdentifier(selectedElement)), + controlsForGridRulers(gridItemIdentifier(selectedElement)), + ] + : []), ], fitness: interactionSession != null && @@ -205,11 +231,16 @@ export function basicResizeStrategy( elementParentBounds?.height, ) - return strategyApplicationResult([ + const gridsToRerender = selectedElements + .filter((element) => MetadataUtils.isGridItem(canvasState.startingMetadata, element)) + .map(EP.parentPath) + + const elementsToRerender = [...selectedElements, ...gridsToRerender] + + let commands: CanvasCommand[] = [ adjustCssLengthProperties('always', selectedElement, null, resizeProperties), updateHighlightedViews('mid-interaction', []), setCursorCommand(pickCursorFromEdgePosition(edgePosition)), - setElementsToRerenderCommand(selectedElements), pushIntendedBoundsAndUpdateGroups( [{ target: selectedElement, frame: resizedBounds }], 'starting-metadata', @@ -217,12 +248,28 @@ export function basicResizeStrategy( ...groupChildren.map((c) => queueTrueUpElement([trueUpGroupElementChanged(c.elementPath)]), ), - ]) + ] + + if (isEdgePositionAHorizontalEdge(edgePosition) && !isGridCell) { + commands.push( + deleteProperties('always', selectedElement, [PP.create('style', 'justifySelf')]), + ) + } + if (isEdgePositionAVerticalEdge(edgePosition) && !isGridCell) { + commands.push( + deleteProperties('always', selectedElement, [PP.create('style', 'alignSelf')]), + ) + } + + return strategyApplicationResult(commands, elementsToRerender) } else { - return strategyApplicationResult([ - updateHighlightedViews('mid-interaction', []), - setCursorCommand(pickCursorFromEdgePosition(edgePosition)), - ]) + return strategyApplicationResult( + [ + updateHighlightedViews('mid-interaction', []), + setCursorCommand(pickCursorFromEdgePosition(edgePosition)), + ], + [], + ) } } // Fallback for when the checks above are not satisfied. diff --git a/editor/src/components/canvas/canvas-strategies/strategies/convert-to-absolute-and-move-strategy.spec.tsx b/editor/src/components/canvas/canvas-strategies/strategies/convert-to-absolute-and-move-strategy.spec.tsx index 4fac80bc13c9..1d5682e256bd 100644 --- a/editor/src/components/canvas/canvas-strategies/strategies/convert-to-absolute-and-move-strategy.spec.tsx +++ b/editor/src/components/canvas/canvas-strategies/strategies/convert-to-absolute-and-move-strategy.spec.tsx @@ -175,6 +175,7 @@ function dragByPixels( const strategyResultCommands = convertToAbsoluteAndMoveStrategy( pickCanvasStateFromEditorStateWithMetadata( + editorState.selectedViews, editorState, createBuiltInDependenciesList(null), metadata, diff --git a/editor/src/components/canvas/canvas-strategies/strategies/convert-to-absolute-and-move-strategy.tsx b/editor/src/components/canvas/canvas-strategies/strategies/convert-to-absolute-and-move-strategy.tsx index ff61a2be348c..4f448960f964 100644 --- a/editor/src/components/canvas/canvas-strategies/strategies/convert-to-absolute-and-move-strategy.tsx +++ b/editor/src/components/canvas/canvas-strategies/strategies/convert-to-absolute-and-move-strategy.tsx @@ -44,7 +44,6 @@ import { getFullFrame } from '../../../frame' import { stylePropPathMappingFn } from '../../../inspector/common/property-path-hooks' import type { CanvasFrameAndTarget } from '../../canvas-types' import type { CanvasCommand } from '../../commands/commands' -import { convertToAbsolute } from '../../commands/convert-to-absolute-command' import type { SetCssLengthProperty } from '../../commands/set-css-length-command' import { setCssLengthProperty, @@ -181,7 +180,7 @@ function convertToAbsoluteAndMoveStrategyFactory(setHuggingParentToFixed: SetHug originalTargets, ).filter( // don't show the siblings control for grid cells - (sibling) => !MetadataUtils.isGridCell(canvasState.startingMetadata, sibling.elementPath), + (sibling) => !MetadataUtils.isGridItem(canvasState.startingMetadata, sibling.elementPath), ) const showSiblingsControl = autoLayoutSiblingsExceptGridCells.length > 1 @@ -278,10 +277,10 @@ function convertToAbsoluteAndMoveStrategyFactory(setHuggingParentToFixed: SetHug }, }) - return strategyApplicationResult([ - ...absoluteMoveApplyResult.commands, - strategyIndicatorCommand, - ]) + return strategyApplicationResult( + [...absoluteMoveApplyResult.commands, strategyIndicatorCommand], + absoluteMoveApplyResult.elementsToRerender, + ) } // Fallback for when the checks above are not satisfied. return emptyStrategyApplicationResult diff --git a/editor/src/components/canvas/canvas-strategies/strategies/drag-to-insert-metastrategy.tsx b/editor/src/components/canvas/canvas-strategies/strategies/drag-to-insert-metastrategy.tsx index 04da545d72f5..fe2a11266c2a 100644 --- a/editor/src/components/canvas/canvas-strategies/strategies/drag-to-insert-metastrategy.tsx +++ b/editor/src/components/canvas/canvas-strategies/strategies/drag-to-insert-metastrategy.tsx @@ -216,6 +216,7 @@ function dragToInsertStrategyFactory( if (insertionSubjects.length === 0) { return strategyApplicationResult( [setCursorCommand(CSSCursor.NotPermitted)], + [], {}, 'failure', ) @@ -280,6 +281,7 @@ function dragToInsertStrategyFactory( reparentCommand, ...optionalWrappingCommand, ], + [targetParent.intendedParentPath], { strategyGeneratedUidsCache: { [insertionSubjects[0].uid]: maybeWrapperWithUid?.uid, @@ -389,6 +391,7 @@ function runTargetStrategiesForFreshlyInsertedElement( // because that element is inserted to the storyboard before reparenting to the correct location, // so its index amongst its starting siblings isn't relevant. const canvasState = pickCanvasStateFromEditorStateWithMetadata( + editorState.selectedViews, editorState, builtInDependencies, patchedMetadata, diff --git a/editor/src/components/canvas/canvas-strategies/strategies/drag-to-move-metastrategy.tsx b/editor/src/components/canvas/canvas-strategies/strategies/drag-to-move-metastrategy.tsx index 45dfa9f1d595..c70f692fd2e8 100644 --- a/editor/src/components/canvas/canvas-strategies/strategies/drag-to-move-metastrategy.tsx +++ b/editor/src/components/canvas/canvas-strategies/strategies/drag-to-move-metastrategy.tsx @@ -58,7 +58,8 @@ export const dragToMoveMetaStrategy: MetaCanvasStrategy = ( selectedElements.length === 0 || selectedElements.some(EP.isRootElementOfInstance) || interactionSession == null || - interactionSession.activeControl.type !== 'BOUNDING_AREA' || + (interactionSession.activeControl.type !== 'BOUNDING_AREA' && + interactionSession.activeControl.type !== 'GRID_CELL_HANDLE') || interactionSession.interactionData.type !== 'DRAG' || interactionSession.interactionData.modifiers.alt ) { @@ -153,23 +154,26 @@ export function doNothingStrategy(canvasState: InteractionCanvasState): CanvasSt ], fitness: DoNothingFitness, apply: () => { - return strategyApplicationResult([ - wildcardPatch('mid-interaction', { - canvas: { - controls: { - dragToMoveIndicatorFlags: { - $set: { - showIndicator: true, - dragType: 'none', - reparent: 'none', - ancestor: false, + return strategyApplicationResult( + [ + wildcardPatch('mid-interaction', { + canvas: { + controls: { + dragToMoveIndicatorFlags: { + $set: { + showIndicator: true, + dragType: 'none', + reparent: 'none', + ancestor: false, + }, }, }, }, - }, - }), - setCursorCommand(CSSCursor.NotPermitted), - ]) + }), + setCursorCommand(CSSCursor.NotPermitted), + ], + [], + ) }, } } diff --git a/editor/src/components/canvas/canvas-strategies/strategies/draw-to-insert-metastrategy.tsx b/editor/src/components/canvas/canvas-strategies/strategies/draw-to-insert-metastrategy.tsx index 3ce8cfb1f4f6..2897296e9680 100644 --- a/editor/src/components/canvas/canvas-strategies/strategies/draw-to-insert-metastrategy.tsx +++ b/editor/src/components/canvas/canvas-strategies/strategies/draw-to-insert-metastrategy.tsx @@ -303,6 +303,7 @@ export function drawToInsertStrategyFactory( resizeCommand, ...optionalWrappingCommand, ], + [targetParent.intendedParentPath], { strategyGeneratedUidsCache: { [insertionSubject.uid]: maybeWrapperWithUid?.uid, @@ -348,6 +349,7 @@ export function drawToInsertStrategyFactory( return strategyApplicationResult( [insertionCommand.command, reparentCommand, ...optionalWrappingCommand], + [targetParent.intendedParentPath], { strategyGeneratedUidsCache: { [insertionSubject.uid]: maybeWrapperWithUid?.uid, @@ -359,11 +361,13 @@ export function drawToInsertStrategyFactory( // drag is null, the cursor is not moved yet, but the mousedown already happened return strategyApplicationResult( getHighlightAndReorderIndicatorCommands(targetParent.intendedParentPath, targetIndex), + [], ) } } else if (interactionSession.interactionData.type === 'HOVER') { return strategyApplicationResult( getHighlightAndReorderIndicatorCommands(targetParent.intendedParentPath, targetIndex), + [], ) } } @@ -580,7 +584,11 @@ function runTargetStrategiesForFreshlyInsertedElementToReparent( strategyLifecycle: InteractionLifecycle, startingMetadata: ElementInstanceMetadataMap, ): Array { - const canvasState = pickCanvasStateFromEditorState(editorState, builtInDependencies) + const canvasState = pickCanvasStateFromEditorState( + editorState.selectedViews, + editorState, + builtInDependencies, + ) const rootPath = getRootPath(startingMetadata) if (rootPath == null) { @@ -669,6 +677,7 @@ function runTargetStrategiesForFreshlyInsertedElementToResize( // when actually applying the strategies. If we ever need to pick a resize strategy based on the target // element's index, we will need to update the elementPathTree with the new element and pass it in here. const canvasState = pickCanvasStateFromEditorStateWithMetadata( + editorState.selectedViews, editorState, builtInDependencies, patchedMetadata, diff --git a/editor/src/components/canvas/canvas-strategies/strategies/draw-to-insert-strategy.spec.browser2.tsx b/editor/src/components/canvas/canvas-strategies/strategies/draw-to-insert-strategy.spec.browser2.tsx index 09b3c6d1914b..07f3ec449d2c 100644 --- a/editor/src/components/canvas/canvas-strategies/strategies/draw-to-insert-strategy.spec.browser2.tsx +++ b/editor/src/components/canvas/canvas-strategies/strategies/draw-to-insert-strategy.spec.browser2.tsx @@ -16,9 +16,15 @@ import { import { RightMenuTab } from '../../../editor/store/editor-state' import { FOR_TESTS_setNextGeneratedUid } from '../../../../core/model/element-template-utils.test-utils' import type { WindowPoint } from '../../../../core/shared/math-utils' -import { windowPoint } from '../../../../core/shared/math-utils' +import { + nullIfInfinity, + rectangleContainsRectangle, + windowPoint, +} from '../../../../core/shared/math-utils' import { selectComponentsForTest } from '../../../../utils/utils.test-utils' import CanvasActions from '../../canvas-actions' +import { MetadataUtils } from '../../../../core/model/element-metadata-utils' +import { forceNotNull } from '../../../../core/shared/optional-utils' function slightlyOffsetWindowPointBecauseVeryWeirdIssue(point: { x: number; y: number }) { // FIXME when running in headless chrome, the result of getBoundingClientRect will be slightly @@ -201,6 +207,109 @@ export var storyboard = ( ) +` + + const projectWithGridContent = `import * as React from 'react' +import { Scene, Storyboard } from 'utopia-api' + +export var storyboard = ( + + +

+
+
+
+
+
+
+
+
+
+
+ + +) ` it('can click to insert into a grid', async () => { @@ -237,9 +346,9 @@ export var storyboard = ( gridColumn: '2', gridRow: '1', height: '100px', - left: '83px', - position: 'absolute', - top: '100px', + left: '', + position: '', + top: '', width: '100px', }) }) @@ -280,9 +389,9 @@ export var storyboard = ( gridColumn: '1', gridRow: '1', height: '100px', - left: '100px', - position: 'absolute', - top: '25px', + left: '', + position: '', + top: '', width: '100px', }) }) @@ -324,9 +433,9 @@ export var storyboard = ( gridColumn: '2', gridRow: '1', height: '60px', - left: '83px', - position: 'absolute', - top: '150px', + left: '', + position: '', + top: '', width: '40px', }) }) @@ -371,11 +480,72 @@ export var storyboard = ( gridColumn: '1', gridRow: '1', height: '30px', - left: '125px', - position: 'absolute', - top: '75px', + left: '', + position: '', + top: '', width: '20px', }) }) + + it('can draw to insert into an element in a grid', async () => { + const editor = await renderTestEditorWithCode( + projectWithGridContent, + 'await-first-dom-report', + ) + + await selectComponentsForTest(editor, [EP.fromString('sb/scene/grid/grid-item-8')]) + + await pressKey('d') + ensureInInsertMode(editor) + + const gridItem = editor.renderedDOM.getByTestId('grid-item-8') + const gridBB = gridItem.getBoundingClientRect() + + const target: WindowPoint = windowPoint({ + x: gridBB.x + 5, + y: gridBB.y + 5, + }) + + const canvasControlsLayer = editor.renderedDOM.getByTestId(CanvasControlsContainerID) + + await mouseMoveToPoint(canvasControlsLayer, target) + await mouseDragFromPointToPoint(canvasControlsLayer, target, { + x: target.x + 40, + y: target.y + 60, + }) + await editor.getDispatchFollowUpActionsFinished() + + const child = gridItem.firstChild + if (child == null) { + throw new Error('Draw to insert should be able to insert an element') + } + + const { position, top, left, width, height } = (child as HTMLElement).style + + expect({ position, top, left, width, height }).toEqual({ + position: 'absolute', + left: '6px', + top: '6px', + width: '40px', + height: '60px', + }) + + const gridItem8Metadata = + editor.getEditorState().editor.jsxMetadata['sb/scene/grid/grid-item-8'] + const gridItem8GlobalFrame = forceNotNull( + 'Item 8 should have a global frame.', + nullIfInfinity(gridItem8Metadata.globalFrame), + ) + const gridItem8Children = MetadataUtils.getChildrenUnordered( + editor.getEditorState().editor.jsxMetadata, + EP.fromString('sb/scene/grid/grid-item-8'), + ) + const gridItem8Child = forceNotNull('Could not find child.', gridItem8Children.at(0)) + const gridItem8ChildGlobalFrame = forceNotNull( + 'Child of item 8 should have a global frame.', + nullIfInfinity(gridItem8Child.globalFrame), + ) + expect(rectangleContainsRectangle(gridItem8GlobalFrame, gridItem8ChildGlobalFrame)).toBe(true) + }) }) }) diff --git a/editor/src/components/canvas/canvas-strategies/strategies/draw-to-insert-text-strategy.tsx b/editor/src/components/canvas/canvas-strategies/strategies/draw-to-insert-text-strategy.tsx index 5ab27f69b3d5..0a02ebe94b00 100644 --- a/editor/src/components/canvas/canvas-strategies/strategies/draw-to-insert-text-strategy.tsx +++ b/editor/src/components/canvas/canvas-strategies/strategies/draw-to-insert-text-strategy.tsx @@ -87,7 +87,7 @@ export const drawToInsertTextMetaStrategy: MetaCanvasStrategy = ( ['hasOnlyTextChildren', 'supportsChildren'], ) if (applicableReparentFactories.length < 1) { - return strategyApplicationResult([]) + return strategyApplicationResult([], []) } const factory = applicableReparentFactories[0] @@ -107,24 +107,27 @@ export const drawToInsertTextMetaStrategy: MetaCanvasStrategy = ( const isRoot = targetParentPathParts === 1 const isClick = s === 'end-interaction' && interactionSession.interactionData.drag == null if (!isRoot && textEditableAndHasText && isClick) { - return strategyApplicationResult([ - updateSelectedViews('on-complete', [targetParent.intendedParentPath]), - setCursorCommand(CSSCursor.Select), - wildcardPatch('on-complete', { - mode: { - $set: EditorModes.textEditMode( - targetParent.intendedParentPath, - canvasPointToWindowPoint( - pointOnCanvas, - canvasState.scale, - canvasState.canvasOffset, + return strategyApplicationResult( + [ + updateSelectedViews('on-complete', [targetParent.intendedParentPath]), + setCursorCommand(CSSCursor.Select), + wildcardPatch('on-complete', { + mode: { + $set: EditorModes.textEditMode( + targetParent.intendedParentPath, + canvasPointToWindowPoint( + pointOnCanvas, + canvasState.scale, + canvasState.canvasOffset, + ), + 'existing', + 'no-text-selection', ), - 'existing', - 'no-text-selection', - ), - }, - }), - ]) + }, + }), + ], + [targetParent.intendedParentPath], + ) } const strategy = drawToInsertStrategyFactory( @@ -138,7 +141,7 @@ export const drawToInsertTextMetaStrategy: MetaCanvasStrategy = ( factory.targetIndex, ) if (strategy == null) { - return strategyApplicationResult([]) + return strategyApplicationResult([], []) } const targetElement = EP.appendToPath(targetParent.intendedParentPath, insertionSubject.uid) diff --git a/editor/src/components/canvas/canvas-strategies/strategies/flex-reorder-strategy.tsx b/editor/src/components/canvas/canvas-strategies/strategies/flex-reorder-strategy.tsx index 71c2ee5e0533..74cdcf6be833 100644 --- a/editor/src/components/canvas/canvas-strategies/strategies/flex-reorder-strategy.tsx +++ b/editor/src/components/canvas/canvas-strategies/strategies/flex-reorder-strategy.tsx @@ -96,7 +96,6 @@ export function flexReorderStrategy( return interactionSession == null ? emptyStrategyApplicationResult : applyReorderCommon( - originalTargets, retargetedTargets, canvasState, interactionSession, diff --git a/editor/src/components/canvas/canvas-strategies/strategies/flex-reparent-to-absolute-strategy.tsx b/editor/src/components/canvas/canvas-strategies/strategies/flex-reparent-to-absolute-strategy.tsx index b36ceb998322..e94bdc502612 100644 --- a/editor/src/components/canvas/canvas-strategies/strategies/flex-reparent-to-absolute-strategy.tsx +++ b/editor/src/components/canvas/canvas-strategies/strategies/flex-reparent-to-absolute-strategy.tsx @@ -1,6 +1,7 @@ import { canvasPoint } from '../../../../core/shared/math-utils' import type { EditorStatePatch } from '../../../editor/store/editor-state' import { foldAndApplyCommandsInner } from '../../commands/commands' + import { updateFunctionCommand } from '../../commands/update-function-command' import { ParentBounds } from '../../controls/parent-bounds' import { ParentOutlines } from '../../controls/parent-outlines' @@ -108,6 +109,7 @@ export function baseFlexReparentToAbsoluteStrategy( 'always', (editorState, commandLifecycle): Array => { const updatedCanvasState = pickCanvasStateFromEditorState( + editorState.selectedViews, editorState, canvasState.builtInDependencies, ) @@ -132,6 +134,7 @@ export function baseFlexReparentToAbsoluteStrategy( }, ), ], + [newParent.intendedParentPath, ...filteredSelectedElements], { duplicatedElementNewUids: placeholderCommands.duplicatedElementNewUids, }, diff --git a/editor/src/components/canvas/canvas-strategies/strategies/flex-resize-basic-strategy.tsx b/editor/src/components/canvas/canvas-strategies/strategies/flex-resize-basic-strategy.tsx index 3d8a48bebaa5..047481d64619 100644 --- a/editor/src/components/canvas/canvas-strategies/strategies/flex-resize-basic-strategy.tsx +++ b/editor/src/components/canvas/canvas-strategies/strategies/flex-resize-basic-strategy.tsx @@ -8,7 +8,7 @@ import { lengthPropertyToAdjust, } from '../../commands/adjust-css-length-command' import { setCursorCommand } from '../../commands/set-cursor-command' -import { setElementsToRerenderCommand } from '../../commands/set-elements-to-rerender-command' + import { updateHighlightedViews } from '../../commands/update-highlighted-views-command' import { ImmediateParentBounds } from '../../controls/parent-bounds' import { ImmediateParentOutlines } from '../../controls/parent-outlines' @@ -38,6 +38,7 @@ import { pushIntendedBoundsAndUpdateGroups } from '../../commands/push-intended- import { queueTrueUpElement } from '../../commands/queue-true-up-command' import { treatElementAsGroupLike } from './group-helpers' import { trueUpGroupElementChanged } from '../../../editor/store/editor-state' +import { StrategySizeLabel } from '../../controls/select-mode/size-label' export function flexResizeBasicStrategy( canvasState: InteractionCanvasState, @@ -89,7 +90,7 @@ export function flexResizeBasicStrategy( elementParentBounds != null && (elementParentBounds.width !== 0 || elementParentBounds.height !== 0) - if (MetadataUtils.isGridCell(canvasState.startingMetadata, selectedElements[0])) { + if (MetadataUtils.isGridItem(canvasState.startingMetadata, selectedElements[0])) { return null } @@ -107,6 +108,14 @@ export function flexResizeBasicStrategy( props: { targets: selectedElements, pathsWereReplaced: false }, key: 'absolute-resize-control', show: 'always-visible', + priority: 'top', + }), + controlWithProps({ + control: StrategySizeLabel, + props: { targets: selectedElements, pathsWereReplaced: false }, + key: 'size-label', + show: 'visible-except-when-other-strategy-is-active', + priority: 'top', }), controlWithProps({ control: ZeroSizeResizeControlWrapper, @@ -220,24 +229,29 @@ export function flexResizeBasicStrategy( elementParentBounds?.height, ) - return strategyApplicationResult([ - adjustCssLengthProperties('always', selectedElement, null, resizeProperties), - updateHighlightedViews('mid-interaction', []), - setCursorCommand(pickCursorFromEdgePosition(edgePosition)), - setElementsToRerenderCommand(selectedElements), - pushIntendedBoundsAndUpdateGroups( - [{ target: selectedElement, frame: resizedBounds }], - 'starting-metadata', - ), - ...groupChildren.map((c) => - queueTrueUpElement([trueUpGroupElementChanged(c.elementPath)]), - ), - ]) + return strategyApplicationResult( + [ + adjustCssLengthProperties('always', selectedElement, null, resizeProperties), + updateHighlightedViews('mid-interaction', []), + setCursorCommand(pickCursorFromEdgePosition(edgePosition)), + pushIntendedBoundsAndUpdateGroups( + [{ target: selectedElement, frame: resizedBounds }], + 'starting-metadata', + ), + ...groupChildren.map((c) => + queueTrueUpElement([trueUpGroupElementChanged(c.elementPath)]), + ), + ], + selectedElements, + ) } else { - return strategyApplicationResult([ - updateHighlightedViews('mid-interaction', []), - setCursorCommand(pickCursorFromEdgePosition(edgePosition)), - ]) + return strategyApplicationResult( + [ + updateHighlightedViews('mid-interaction', []), + setCursorCommand(pickCursorFromEdgePosition(edgePosition)), + ], + [], + ) } } // Fallback for when the checks above are not satisfied. diff --git a/editor/src/components/canvas/canvas-strategies/strategies/flex-resize-strategy.tsx b/editor/src/components/canvas/canvas-strategies/strategies/flex-resize-strategy.tsx index 1cff33d7db1f..2238128d65d1 100644 --- a/editor/src/components/canvas/canvas-strategies/strategies/flex-resize-strategy.tsx +++ b/editor/src/components/canvas/canvas-strategies/strategies/flex-resize-strategy.tsx @@ -24,7 +24,7 @@ import { lengthPropertyToAdjust, } from '../../commands/adjust-css-length-command' import { setCursorCommand } from '../../commands/set-cursor-command' -import { setElementsToRerenderCommand } from '../../commands/set-elements-to-rerender-command' + import { updateHighlightedViews } from '../../commands/update-highlighted-views-command' import { ImmediateParentBounds } from '../../controls/parent-bounds' import { ImmediateParentOutlines } from '../../controls/parent-outlines' @@ -67,6 +67,7 @@ import { treatElementAsGroupLike } from './group-helpers' import { queueTrueUpElement } from '../../commands/queue-true-up-command' import { pushIntendedBoundsAndUpdateGroups } from '../../commands/push-intended-bounds-and-update-groups-command' import { trueUpGroupElementChanged } from '../../../editor/store/editor-state' +import { StrategySizeLabel } from '../../controls/select-mode/size-label' export const FLEX_RESIZE_STRATEGY_ID = 'FLEX_RESIZE' @@ -109,7 +110,7 @@ export function flexResizeStrategy( elementParentBounds.width !== 0 && elementParentBounds.height !== 0 - if (MetadataUtils.isGridCell(canvasState.startingMetadata, selectedElements[0])) { + if (MetadataUtils.isGridItem(canvasState.startingMetadata, selectedElements[0])) { return null } @@ -127,6 +128,14 @@ export function flexResizeStrategy( props: { targets: selectedElements, pathsWereReplaced: false }, key: 'absolute-resize-control', show: 'always-visible', + priority: 'top', + }), + controlWithProps({ + control: StrategySizeLabel, + props: { targets: selectedElements, pathsWereReplaced: false }, + key: 'size-label', + show: 'visible-except-when-other-strategy-is-active', + priority: 'top', }), controlWithProps({ control: ZeroSizeResizeControlWrapper, @@ -353,24 +362,29 @@ export function flexResizeStrategy( 'center-based', ) - return strategyApplicationResult([ - ...resizeCommands, - updateHighlightedViews('mid-interaction', []), - setCursorCommand(pickCursorFromEdgePosition(edgePosition)), - setElementsToRerenderCommand(selectedElements), - pushIntendedBoundsAndUpdateGroups( - [{ target: selectedElement, frame: newFrame }], - 'starting-metadata', - ), - ...groupChildren.map((c) => - queueTrueUpElement([trueUpGroupElementChanged(c.elementPath)]), - ), - ]) + return strategyApplicationResult( + [ + ...resizeCommands, + updateHighlightedViews('mid-interaction', []), + setCursorCommand(pickCursorFromEdgePosition(edgePosition)), + pushIntendedBoundsAndUpdateGroups( + [{ target: selectedElement, frame: newFrame }], + 'starting-metadata', + ), + ...groupChildren.map((c) => + queueTrueUpElement([trueUpGroupElementChanged(c.elementPath)]), + ), + ], + selectedElements, + ) } else { - return strategyApplicationResult([ - updateHighlightedViews('mid-interaction', []), - setCursorCommand(pickCursorFromEdgePosition(edgePosition)), - ]) + return strategyApplicationResult( + [ + updateHighlightedViews('mid-interaction', []), + setCursorCommand(pickCursorFromEdgePosition(edgePosition)), + ], + [], + ) } } // Fallback for when the checks above are not satisfied. diff --git a/editor/src/components/canvas/canvas-strategies/strategies/flow-reorder-strategy.tsx b/editor/src/components/canvas/canvas-strategies/strategies/flow-reorder-strategy.tsx index d87eff82e39b..97de575b346f 100644 --- a/editor/src/components/canvas/canvas-strategies/strategies/flow-reorder-strategy.tsx +++ b/editor/src/components/canvas/canvas-strategies/strategies/flow-reorder-strategy.tsx @@ -93,7 +93,6 @@ export function flowReorderStrategy( return interactionSession == null ? emptyStrategyApplicationResult : applyReorderCommon( - originalTargets, retargetedTargets, canvasState, interactionSession, diff --git a/editor/src/components/canvas/canvas-strategies/strategies/flow-reparent-to-flow-strategy.spec.browser2.tsx b/editor/src/components/canvas/canvas-strategies/strategies/flow-reparent-to-flow-strategy.spec.browser2.tsx index e8435f7a8cdf..faf67b6292b1 100644 --- a/editor/src/components/canvas/canvas-strategies/strategies/flow-reparent-to-flow-strategy.spec.browser2.tsx +++ b/editor/src/components/canvas/canvas-strategies/strategies/flow-reparent-to-flow-strategy.spec.browser2.tsx @@ -259,6 +259,263 @@ describe('Flow Reparent To Flow Strategy', () => { `), ) }) + + it('reparents flow component without style prop support to flow parent', async () => { + const renderResult = await renderTestEditorWithCode( + `import * as React from 'react' +import { Scene, Storyboard, View, Group } from 'utopia-api' + +export var App = (props) => { + return ( +
+
+
+
+
+
+ +
+
+
+ ) +} + +export var storyboard = (props) => { + return ( + + + + + + ) +} + +function CompNoStyle(props) { + return ( +
+ ) +} +`, + 'await-first-dom-report', + ) + + const targetFlowParent = await renderResult.renderedDOM.findByTestId('flowparent1') + const targetFlowParentRect = targetFlowParent.getBoundingClientRect() + const targetFlowParentEnd = { + x: targetFlowParentRect.x + targetFlowParentRect.width / 2, + y: targetFlowParentRect.y + targetFlowParentRect.height - 15, + } + const flowChildToReparent = await renderResult.renderedDOM.findByTestId('compnostyle') + const flowChildToReparentRect = flowChildToReparent.getBoundingClientRect() + const flowChildToReparentCenter = { + x: flowChildToReparentRect.x + flowChildToReparentRect.width / 2, + y: flowChildToReparentRect.y + flowChildToReparentRect.height / 2, + } + + await renderResult.getDispatchFollowUpActionsFinished() + const dragDelta = windowPoint({ + x: targetFlowParentEnd.x - flowChildToReparentCenter.x + 5, + y: targetFlowParentEnd.y - flowChildToReparentCenter.y, + }) + + await dragElement(renderResult, 'compnostyle', dragDelta, cmdModifier) + + await renderResult.getDispatchFollowUpActionsFinished() + expect(getPrintedUiJsCode(renderResult.getEditorState())).toEqual( + formatTestProjectCode(`import * as React from 'react' +import { Scene, Storyboard, View, Group } from 'utopia-api' + +export var App = (props) => { + return ( +
+
+
+
+ +
+
+
+
+
+ ) +} + +export var storyboard = (props) => { + return ( + + + + + + ) +} + +function CompNoStyle(props) { + return ( +
+ ) +}`), + ) + }) + it('reparents flow element to flow parent in row layout', async () => { const renderResult = await renderTestEditorWithCode( makeTestProjectCodeWithSnippet(` diff --git a/editor/src/components/canvas/canvas-strategies/strategies/forced-absolute-reparent-strategies.spec.browser2.tsx b/editor/src/components/canvas/canvas-strategies/strategies/forced-absolute-reparent-strategies.spec.browser2.tsx index 7c9ec0953a84..a9712b9857f4 100644 --- a/editor/src/components/canvas/canvas-strategies/strategies/forced-absolute-reparent-strategies.spec.browser2.tsx +++ b/editor/src/components/canvas/canvas-strategies/strategies/forced-absolute-reparent-strategies.spec.browser2.tsx @@ -281,7 +281,7 @@ describe('Fallback Absolute Reparent Strategies', () => {
{
{ @@ -79,43 +107,38 @@ export const gridRearrangeMoveDuplicateStrategy: CanvasStrategyFactory = ( } const targetElement = EP.appendToPath(EP.parentPath(selectedElement), newUid) + const { parentGridCellGlobalFrames, parentContainerGridProperties } = + selectedElementMetadata.specialSizeMeasurements + if (parentGridCellGlobalFrames == null) { + return emptyStrategyApplicationResult + } - const { - commands: moveCommands, - targetCell: targetGridCellData, - draggingFromCell, - originalRootCell, - targetRootCell, - } = runGridRearrangeMove( - targetElement, - selectedElement, + const moveCommands = runGridChangeElementLocation( canvasState.startingMetadata, interactionSession.interactionData, - canvasState.scale, - canvasState.canvasOffset, - customState.grid, - true, + selectedElementMetadata, + parentGridCellGlobalFrames, + parentContainerGridProperties, + null, + ) + + const { midInteractionCommands, onCompleteCommands } = gridMoveStrategiesExtraCommands( + EP.parentPath(selectedElement), // TODO: don't use EP.parentPath + initialTemplates, ) - if (moveCommands.length === 0) { - return emptyStrategyApplicationResult - } return strategyApplicationResult( [ duplicateElement('always', selectedElement, newUid), ...moveCommands, - setElementsToRerenderCommand([...selectedElements, targetElement]), + ...midInteractionCommands, + ...onCompleteCommands, updateSelectedViews('always', [targetElement]), updateHighlightedViews('always', [targetElement]), setCursorCommand(CSSCursor.Duplicate), ], + [...selectedElements, targetElement], { - grid: { - targetCellData: targetGridCellData, - draggingFromCell: draggingFromCell, - originalRootCell: originalRootCell, - currentRootCell: targetRootCell, - }, duplicatedElementNewUids: duplicatedElementNewUids, }, ) diff --git a/editor/src/components/canvas/canvas-strategies/strategies/grid-rearrange-keyboard-strategy.ts b/editor/src/components/canvas/canvas-strategies/strategies/grid-change-element-location-keyboard-strategy.ts similarity index 81% rename from editor/src/components/canvas/canvas-strategies/strategies/grid-rearrange-keyboard-strategy.ts rename to editor/src/components/canvas/canvas-strategies/strategies/grid-change-element-location-keyboard-strategy.ts index a8254b819c27..be83591b9b74 100644 --- a/editor/src/components/canvas/canvas-strategies/strategies/grid-rearrange-keyboard-strategy.ts +++ b/editor/src/components/canvas/canvas-strategies/strategies/grid-change-element-location-keyboard-strategy.ts @@ -2,25 +2,25 @@ import { MetadataUtils } from '../../../../core/model/element-metadata-utils' import * as EP from '../../../../core/shared/element-path' import type { GridPositionValue } from '../../../../core/shared/element-template' import { gridPositionValue } from '../../../../core/shared/element-template' -import { GridControls, GridControlsKey } from '../../controls/grid-controls' -import type { - CanvasStrategy, - CustomStrategyState, - InteractionCanvasState, -} from '../canvas-strategy-types' +import { + controlsForGridPlaceholders, + controlsForGridRulers, +} from '../../controls/grid-controls-for-strategies' +import type { CanvasStrategy, InteractionCanvasState } from '../canvas-strategy-types' import { emptyStrategyApplicationResult, getTargetPathsFromInteractionTarget, strategyApplicationResult, } from '../canvas-strategy-types' import type { InteractionSession } from '../interaction-state' -import { getGridCellBoundsFromCanvas, setGridPropsCommands } from './grid-helpers' +import { getCommandsForGridItemPlacement } from './grid-helpers' +import { getGridChildCellCoordBoundsFromCanvas } from './grid-cell-bounds' import { accumulatePresses } from './shared-keyboard-strategy-helpers' +import { gridItemIdentifier } from '../../../editor/store/editor-state' -export function gridRearrangeResizeKeyboardStrategy( +export function gridChangeElementLocationResizeKeyboardStrategy( canvasState: InteractionCanvasState, interactionSession: InteractionSession | null, - customState: CustomStrategyState, ): CanvasStrategy | null { if ( interactionSession?.activeControl.type !== 'KEYBOARD_CATCHER_CONTROL' || @@ -36,7 +36,7 @@ export function gridRearrangeResizeKeyboardStrategy( const target = selectedElements[0] - if (!MetadataUtils.isGridCell(canvasState.startingMetadata, target)) { + if (!MetadataUtils.isGridItem(canvasState.startingMetadata, target)) { return null } @@ -45,19 +45,13 @@ export function gridRearrangeResizeKeyboardStrategy( return null } - const parentGridPath = EP.parentPath(target) - - const grid = MetadataUtils.findElementByElementPath(canvasState.startingMetadata, parentGridPath) - if (grid == null) { - return null - } - const gridTemplate = grid.specialSizeMeasurements.containerGridProperties + const gridTemplate = cell.specialSizeMeasurements.parentContainerGridProperties + const gridCellGlobalFrames = cell.specialSizeMeasurements.parentGridCellGlobalFrames - const initialCellBounds = getGridCellBoundsFromCanvas( - cell, - canvasState.scale, - canvasState.canvasOffset, - ) + const initialCellBounds = + gridCellGlobalFrames != null + ? getGridChildCellCoordBoundsFromCanvas(cell, gridCellGlobalFrames) + : null if (initialCellBounds == null) { return null } @@ -67,10 +61,10 @@ export function gridRearrangeResizeKeyboardStrategy( interactionSession.interactionData.keyStates.length - 1, )?.modifiers.shift ?? false - const label = resizing ? 'Grid resize' : 'Grid rearrange' + const label = resizing ? 'Grid resize' : 'Change Location' return { - id: 'GRID_KEYBOARD_REARRANGE_RESIZE', + id: 'GRID_KEYBOARD_CHANGE_ELEMENT_LOCATION_RESIZE', name: label, descriptiveLabel: label, icon: { @@ -78,13 +72,8 @@ export function gridRearrangeResizeKeyboardStrategy( type: 'reorder-large', }, controlsToRender: [ - { - control: GridControls, - props: { targets: [parentGridPath] }, - key: GridControlsKey(parentGridPath), - show: 'always-visible', - priority: 'bottom', - }, + controlsForGridPlaceholders(gridItemIdentifier(target)), + controlsForGridRulers(gridItemIdentifier(target)), ], fitness: fitness(interactionSession), apply: () => { @@ -138,12 +127,13 @@ export function gridRearrangeResizeKeyboardStrategy( } return strategyApplicationResult( - setGridPropsCommands(target, gridTemplate, { + getCommandsForGridItemPlacement(target, gridTemplate, { gridColumnStart, gridColumnEnd, gridRowStart, gridRowEnd, }), + [EP.parentPath(target)], ) }, } diff --git a/editor/src/components/canvas/canvas-strategies/strategies/grid-change-element-location-strategy.ts b/editor/src/components/canvas/canvas-strategies/strategies/grid-change-element-location-strategy.ts new file mode 100644 index 000000000000..03d6a8fcb7a7 --- /dev/null +++ b/editor/src/components/canvas/canvas-strategies/strategies/grid-change-element-location-strategy.ts @@ -0,0 +1,382 @@ +import type { ElementPath } from 'utopia-shared/src/types' +import { MetadataUtils } from '../../../../core/model/element-metadata-utils' +import * as EP from '../../../../core/shared/element-path' +import type { + ElementInstanceMetadata, + ElementInstanceMetadataMap, + GridContainerProperties, + GridElementProperties, + GridPositionOrSpan, +} from '../../../../core/shared/element-template' +import { gridPositionValue, isGridSpan } from '../../../../core/shared/element-template' +import { absolute } from '../../../../utils/utils' +import { gridItemIdentifier } from '../../../editor/store/editor-state' +import { cssKeyword } from '../../../inspector/common/css-utils' +import { getTargetGridCellData } from '../../../inspector/grid-helpers' +import type { CanvasCommand } from '../../commands/commands' +import { reorderElement } from '../../commands/reorder-element-command' +import { showGridControls } from '../../commands/show-grid-controls-command' +import { + controlsForGridPlaceholders, + controlsForGridRulers, +} from '../../controls/grid-controls-for-strategies' +import type { CanvasStrategyFactory } from '../canvas-strategies' +import { onlyFitWhenDraggingThisControl } from '../canvas-strategies' +import type { InteractionCanvasState } from '../canvas-strategy-types' +import { + emptyStrategyApplicationResult, + getTargetPathsFromInteractionTarget, + strategyApplicationResult, +} from '../canvas-strategy-types' +import type { DragInteractionData, InteractionSession } from '../interaction-state' +import type { GridCellGlobalFrames, SortableGridElementProperties } from './grid-helpers' +import { + getOriginalElementGridConfiguration, + getParentGridTemplatesFromChildMeasurements, + gridMoveStrategiesExtraCommands, + isAutoGridPin, + getCommandsForGridItemPlacement, + sortElementsByGridPosition, +} from './grid-helpers' +import type { GridCellCoordinates } from './grid-cell-bounds' + +export const gridChangeElementLocationStrategy: CanvasStrategyFactory = ( + canvasState: InteractionCanvasState, + interactionSession: InteractionSession | null, +) => { + const selectedElements = getTargetPathsFromInteractionTarget(canvasState.interactionTarget) + if ( + selectedElements.length !== 1 || + interactionSession == null || + interactionSession.interactionData.type !== 'DRAG' || + interactionSession.interactionData.drag == null || + interactionSession.activeControl.type !== 'GRID_CELL_HANDLE' || + interactionSession.interactionData.modifiers.alt + ) { + return null + } + + const selectedElement = selectedElements[0] + if (!MetadataUtils.isGridItem(canvasState.startingMetadata, selectedElement)) { + return null + } + + const selectedElementMetadata = MetadataUtils.findElementByElementPath( + canvasState.startingMetadata, + selectedElement, + ) + if ( + selectedElementMetadata == null || + !MetadataUtils.targetRegisteredStyleControlsOrHonoursStyleProps( + canvasState.projectContents, + selectedElementMetadata, + canvasState.propertyControlsInfo, + 'layout', + ['gridRow', 'gridColumn', 'gridRowStart', 'gridColumnStart', 'gridRowEnd', 'gridColumnEnd'], + 'every', + ) + ) { + return null + } + + const initialTemplates = getParentGridTemplatesFromChildMeasurements( + selectedElementMetadata.specialSizeMeasurements, + ) + if (initialTemplates == null) { + return null + } + + if (MetadataUtils.isPositionAbsolute(selectedElementMetadata)) { + return null + } + + return { + id: 'grid-change-element-location-strategy', + name: 'Change Location', + descriptiveLabel: 'Change Location', + icon: { + category: 'tools', + type: 'pointer', + }, + controlsToRender: [ + controlsForGridPlaceholders(gridItemIdentifier(selectedElement), 'visible-only-while-active'), + controlsForGridRulers(gridItemIdentifier(selectedElement), 'visible-only-while-active'), + ], + fitness: onlyFitWhenDraggingThisControl(interactionSession, 'GRID_CELL_HANDLE', 2), + apply: () => { + if ( + interactionSession == null || + interactionSession.interactionData.type !== 'DRAG' || + interactionSession.interactionData.drag == null || + interactionSession.activeControl.type !== 'GRID_CELL_HANDLE' + ) { + return emptyStrategyApplicationResult + } + + const { commands, elementsToRerender } = getCommandsAndPatchForGridChangeElementLocation( + canvasState, + interactionSession.interactionData, + selectedElement, + ) + if (commands.length === 0) { + return emptyStrategyApplicationResult + } + + const { midInteractionCommands, onCompleteCommands } = gridMoveStrategiesExtraCommands( + EP.parentPath(selectedElement), // TODO: don't use EP.parentPath + initialTemplates, + ) + + return strategyApplicationResult( + [...midInteractionCommands, ...onCompleteCommands, ...commands], + elementsToRerender, + ) + }, + } +} + +function getCommandsAndPatchForGridChangeElementLocation( + canvasState: InteractionCanvasState, + interactionData: DragInteractionData, + selectedElement: ElementPath, +): { + commands: CanvasCommand[] + elementsToRerender: ElementPath[] +} { + if (interactionData.drag == null) { + return { commands: [], elementsToRerender: [] } + } + + const selectedElementMetadata = MetadataUtils.findElementByElementPath( + canvasState.startingMetadata, + selectedElement, + ) + if (selectedElementMetadata == null) { + return { commands: [], elementsToRerender: [] } + } + + const { parentGridCellGlobalFrames, parentContainerGridProperties } = + selectedElementMetadata.specialSizeMeasurements + if (parentGridCellGlobalFrames == null) { + return { commands: [], elementsToRerender: [] } + } + + const commands = runGridChangeElementLocation( + canvasState.startingMetadata, + interactionData, + selectedElementMetadata, + parentGridCellGlobalFrames, + parentContainerGridProperties, + null, + ) + + return { + commands: commands, + elementsToRerender: [EP.parentPath(selectedElement), selectedElement], + } +} + +export interface NewGridElementProps { + gridElementProperties: GridElementProperties + targetCellCoords: GridCellCoordinates + targetRootCell: GridCellCoordinates +} + +export function getNewGridElementProps( + interactionData: DragInteractionData, + selectedElementMetadata: ElementInstanceMetadata, + gridCellGlobalFrames: GridCellGlobalFrames, + newPathAfterReparent: ElementPath | null, +): NewGridElementProps | null { + if (interactionData.drag == null) { + return null + } + + const isReparent = newPathAfterReparent != null + + const gridConfig = isReparent + ? { + originalCellBounds: { width: 1, height: 1 }, // when reparenting, we just put it in a single cell + mouseCellPosInOriginalElement: { row: 0, column: 0 }, + } + : getOriginalElementGridConfiguration( + gridCellGlobalFrames, + interactionData, + selectedElementMetadata, + ) + if (gridConfig == null) { + return null + } + const { mouseCellPosInOriginalElement, originalCellBounds } = gridConfig + + const targetGridCellData = getTargetGridCellData( + interactionData, + gridCellGlobalFrames, + mouseCellPosInOriginalElement, + ) + if (targetGridCellData == null) { + return null + } + const { targetCellCoords, targetRootCell } = targetGridCellData + + const elementGridProperties = + selectedElementMetadata.specialSizeMeasurements.elementGridProperties + + function getUpdatedPins( + start: GridPositionOrSpan | null, + end: GridPositionOrSpan | null, + axis: 'row' | 'column', + dimension: number, + ): { + start: GridPositionOrSpan + end: GridPositionOrSpan + } { + const isSpanning = isGridSpan(start) || isGridSpan(end) + if (isSpanning) { + if (isGridSpan(start)) { + const isEndGridSpanArea = isGridSpan(end) || start.type === 'SPAN_AREA' + if (!isEndGridSpanArea) { + return { + start: start, + end: gridPositionValue(start.value + targetRootCell[axis]), + } + } + } else if (isGridSpan(end)) { + return { + start: gridPositionValue(targetRootCell[axis]), + end: end, + } + } + } else { + const shouldSetEndPosition = end != null && !isAutoGridPin(end) + if (shouldSetEndPosition) { + return { + start: gridPositionValue(targetRootCell[axis]), + end: + dimension === 1 + ? cssKeyword('auto') + : gridPositionValue(targetRootCell[axis] + dimension), + } + } + return { + start: gridPositionValue(targetRootCell[axis]), + end: cssKeyword('auto'), + } + } + + return { + start: cssKeyword('auto'), + end: cssKeyword('auto'), + } + } + + const columnBounds = getUpdatedPins( + elementGridProperties.gridColumnStart, + elementGridProperties.gridColumnEnd, + 'column', + originalCellBounds.width, + ) + + const rowBounds = getUpdatedPins( + elementGridProperties.gridRowStart, + elementGridProperties.gridRowEnd, + 'row', + originalCellBounds.height, + ) + + const gridProps: GridElementProperties = { + gridColumnStart: columnBounds.start, + gridColumnEnd: columnBounds.end, + gridRowStart: rowBounds.start, + gridRowEnd: rowBounds.end, + } + + return { + gridElementProperties: gridProps, + targetCellCoords: targetCellCoords, + targetRootCell: targetRootCell, + } +} + +export function runGridChangeElementLocation( + jsxMetadata: ElementInstanceMetadataMap, + interactionData: DragInteractionData, + selectedElementMetadata: ElementInstanceMetadata, + gridCellGlobalFrames: GridCellGlobalFrames, + gridTemplate: GridContainerProperties, + newPathAfterReparent: ElementPath | null, +): CanvasCommand[] { + const newGridElementProps = getNewGridElementProps( + interactionData, + selectedElementMetadata, + gridCellGlobalFrames, + newPathAfterReparent, + ) + if (newGridElementProps == null) { + return [] + } + const { gridElementProperties, targetCellCoords, targetRootCell } = newGridElementProps + + const isReparent = newPathAfterReparent != null + const pathForCommands = isReparent ? newPathAfterReparent : selectedElementMetadata.elementPath // when reparenting, we want to use the new path for commands + + const gridCellMoveCommands = getCommandsForGridItemPlacement( + pathForCommands, + gridTemplate, + gridElementProperties, + ) + + // The siblings of the grid element being moved + const siblings = MetadataUtils.getSiblingsUnordered( + jsxMetadata, + newPathAfterReparent ?? selectedElementMetadata.elementPath, + ) + .filter((s) => !EP.pathsEqual(s.elementPath, selectedElementMetadata.elementPath)) + .map( + (s, index): SortableGridElementProperties => ({ + ...s.specialSizeMeasurements.elementGridProperties, + index: index, + path: s.elementPath, + }), + ) + + // Sort the siblings and the cell under mouse ascending based on their grid coordinates, so that + // the indexes grow left-right, top-bottom. + const templateColumnsCount = + gridTemplate.gridTemplateColumns?.type === 'DIMENSIONS' + ? gridTemplate.gridTemplateColumns.dimensions.length + : 1 + const cellsSortedByPosition = siblings + .concat({ + ...{ + gridColumnStart: gridPositionValue(targetCellCoords.column), + gridColumnEnd: gridPositionValue(targetCellCoords.column), + gridRowStart: gridPositionValue(targetCellCoords.row), + gridRowEnd: gridPositionValue(targetCellCoords.row), + }, + path: selectedElementMetadata.elementPath, + index: siblings.length + 1, + }) + .sort(sortElementsByGridPosition(templateColumnsCount)) + + const indexInSortedCellsForChangeLocation = cellsSortedByPosition.findIndex((s) => + EP.pathsEqual(selectedElementMetadata.elementPath, s.path), + ) + + const updateGridControlsCommand = showGridControls( + 'mid-interaction', + gridItemIdentifier(selectedElementMetadata.elementPath), + targetCellCoords, + targetRootCell, + ) + + return [ + ...gridCellMoveCommands, + reorderElement( + 'always', + pathForCommands, + absolute(Math.max(indexInSortedCellsForChangeLocation, 0)), + ), + updateGridControlsCommand, + ] +} diff --git a/editor/src/components/canvas/canvas-strategies/strategies/grid-draw-to-insert-strategy.tsx b/editor/src/components/canvas/canvas-strategies/strategies/grid-draw-to-insert-strategy.tsx index c485498776aa..530ad70803d7 100644 --- a/editor/src/components/canvas/canvas-strategies/strategies/grid-draw-to-insert-strategy.tsx +++ b/editor/src/components/canvas/canvas-strategies/strategies/grid-draw-to-insert-strategy.tsx @@ -1,30 +1,26 @@ import type { ElementPath } from 'utopia-shared/src/types' import { MetadataUtils } from '../../../../core/model/element-metadata-utils' +import { stripNulls } from '../../../../core/shared/array-utils' import * as EP from '../../../../core/shared/element-path' -import type { - CanvasPoint, - CanvasRectangle, - Size, - WindowRectangle, -} from '../../../../core/shared/math-utils' +import type { CanvasPoint, CanvasRectangle, Size } from '../../../../core/shared/math-utils' import { canvasPoint, canvasRectangle, - canvasVector, - offsetPoint, + pointDifference, roundRectangleToNearestWhole, - scaleVector, size, - windowPoint, } from '../../../../core/shared/math-utils' +import * as PP from '../../../../core/shared/property-path' import { assertNever } from '../../../../core/shared/utils' import { EditorModes, type InsertionSubject } from '../../../editor/editor-modes' import { childInsertionPath } from '../../../editor/store/insertion-path' +import { deleteProperties } from '../../commands/delete-properties-command' import type { InsertElementInsertionSubject } from '../../commands/insert-element-insertion-subject' import { insertElementInsertionSubject } from '../../commands/insert-element-insertion-subject' +import { showGridControls } from '../../commands/show-grid-controls-command' import { updateHighlightedViews } from '../../commands/update-highlighted-views-command' import { wildcardPatch } from '../../commands/wildcard-patch-command' -import { GridControls } from '../../controls/grid-controls' +import { controlsForGridPlaceholders } from '../../controls/grid-controls-for-strategies' import { canvasPointToWindowPoint } from '../../dom-lookup' import { getWrapperWithGeneratedUid, @@ -47,11 +43,12 @@ import { getStyleAttributesForFrameInAbsolutePosition, updateInsertionSubjectWithAttributes, } from './draw-to-insert-metastrategy' -import { getTargetCell, setGridPropsCommands } from './grid-helpers' +import { getCommandsForGridItemPlacement } from './grid-helpers' import { newReparentSubjects } from './reparent-helpers/reparent-strategy-helpers' import { getReparentTargetUnified } from './reparent-helpers/reparent-strategy-parent-lookup' -import { stripNulls } from '../../../../core/shared/array-utils' -import { showGridControls } from '../../commands/show-grid-controls-command' +import { getGridCellUnderMouseFromMetadata } from './grid-cell-bounds' +import { nukeAllAbsolutePositioningPropsCommands } from '../../../inspector/inspector-common' +import { gridContainerIdentifier } from '../../../editor/store/editor-state' export const gridDrawToInsertText: CanvasStrategyFactory = ( canvasState: InteractionCanvasState, @@ -153,22 +150,13 @@ const gridDrawToInsertStrategyInner = category: 'tools', type: 'pointer', }, - controlsToRender: [ - { - control: GridControls, - props: { targets: [targetParent] }, - key: `draw-into-grid-strategy-controls`, - show: 'always-visible', - priority: 'bottom', - }, - ], + controlsToRender: [controlsForGridPlaceholders(gridContainerIdentifier(targetParent))], fitness: 5, apply: (strategyLifecycle) => { - const newTargetCell = getGridCellUnderCursor( - interactionData, - canvasState, - customStrategyState, - ) + const canvasPointToUse = + interactionData.type === 'DRAG' ? interactionData.dragStart : interactionData.point + + const newTargetCell = getGridCellUnderMouseFromMetadata(parent, canvasPointToUse) if (strategyLifecycle === 'mid-interaction' && interactionData.type === 'HOVER') { return strategyApplicationResult( @@ -176,19 +164,15 @@ const gridDrawToInsertStrategyInner = wildcardPatch('mid-interaction', { selectedViews: { $set: [] }, }), - showGridControls('mid-interaction', targetParent), + showGridControls( + 'mid-interaction', + gridContainerIdentifier(targetParent), + newTargetCell?.gridCellCoordinates ?? null, + null, + ), updateHighlightedViews('mid-interaction', [targetParent]), ], - { - ...customStrategyState, - grid: { - ...customStrategyState.grid, - // this is added here during the hover interaction so that - // `GridControls` can render the hover highlight based on the - // coordinates in `targetCellData` - targetCellData: newTargetCell ?? customStrategyState.grid.targetCellData, - }, - }, + [], ) } @@ -196,9 +180,7 @@ const gridDrawToInsertStrategyInner = return emptyStrategyApplicationResult } - const { gridCellCoordinates, cellWindowRectangle } = newTargetCell - - const offset = getOffsetFromGridCell(interactionData, canvasState, cellWindowRectangle) + const { gridCellCoordinates, cellCanvasRectangle } = newTargetCell const defaultSize = interactionData.type === 'DRAG' && @@ -210,7 +192,7 @@ const gridDrawToInsertStrategyInner = const insertionCommand = getInsertionCommand( targetParent, insertionSubject, - getFrameForInsertion(interactionData, defaultSize, offset), + getFrameForInsertion(interactionData, defaultSize, cellCanvasRectangle), ) const gridTemplate = parent.specialSizeMeasurements.containerGridProperties @@ -229,11 +211,15 @@ const gridDrawToInsertStrategyInner = return strategyApplicationResult( [ insertionCommand, - ...setGridPropsCommands(insertedElementPath, gridTemplate, { + ...nukeAllAbsolutePositioningPropsCommands(insertedElementPath), // do not use absolute positioning in grid cells + ...getCommandsForGridItemPlacement(insertedElementPath, gridTemplate, { gridRowStart: { numericalPosition: gridCellCoordinates.row }, gridColumnStart: { numericalPosition: gridCellCoordinates.column }, gridRowEnd: { numericalPosition: gridCellCoordinates.row + 1 }, gridColumnEnd: { numericalPosition: gridCellCoordinates.column + 1 }, + // TODO! this is currently going to assign the element to the cell the interaction started in, + // however it would be good to instead assign the element to _all_ cells overlapping with the final + // inserted frame. }), ...wrappingCommands, ...stripNulls([ @@ -255,6 +241,7 @@ const gridDrawToInsertStrategyInner = : null, ]), ], + [targetParent], { strategyGeneratedUidsCache: { [insertionSubject.uid]: maybeWrapperWithUid?.uid, @@ -268,17 +255,22 @@ const gridDrawToInsertStrategyInner = function getFrameForInsertion( interactionData: DragInteractionData | HoverInteractionData, defaultSize: Size, - offset: CanvasPoint, + cellOrigin: CanvasPoint, ): CanvasRectangle { if (interactionData.type === 'DRAG') { - const origin = interactionData.drag ?? { x: defaultSize.width / 2, y: defaultSize.height / 2 } - - const { x, y } = { x: offset.x - origin.x, y: offset.y - origin.y } + const dragStart = interactionData.dragStart + const mouseAt = { + x: interactionData.dragStart.x + (interactionData.drag?.x ?? 0), + y: interactionData.dragStart.y + (interactionData.drag?.y ?? 0), + } + const width = Math.abs(interactionData.drag?.x ?? defaultSize.width) + const height = Math.abs(interactionData.drag?.y ?? defaultSize.height) - const { width, height } = - interactionData.drag == null - ? defaultSize - : { width: interactionData.drag.x, height: interactionData.drag.y } + const origin = canvasPoint({ + x: Math.min(dragStart.x, mouseAt.x), + y: Math.min(dragStart.y, mouseAt.y), + }) + const { x, y } = pointDifference(cellOrigin, origin) return roundRectangleToNearestWhole(canvasRectangle({ x, y, width, height })) } @@ -315,51 +307,3 @@ function getInsertionCommand( return insertElementInsertionSubject('always', updatedInsertionSubject, insertionPath) } - -function getGridCellUnderCursor( - interactionData: DragInteractionData | HoverInteractionData, - canvasState: InteractionCanvasState, - customStrategyState: CustomStrategyState, -) { - const windowPointToUse = - interactionData.type === 'DRAG' ? interactionData.dragStart : interactionData.point - - const mouseWindowPoint = canvasPointToWindowPoint( - windowPointToUse, - canvasState.scale, - canvasState.canvasOffset, - ) - - return getTargetCell( - customStrategyState.grid.targetCellData?.gridCellCoordinates ?? null, - false, - mouseWindowPoint, - ) -} - -function getOffsetFromGridCell( - interactionData: DragInteractionData | HoverInteractionData, - canvasState: InteractionCanvasState, - cellWindowRectangle: WindowRectangle, -) { - const windowPointToUse = - interactionData.type === 'DRAG' - ? offsetPoint(interactionData.dragStart, interactionData.drag ?? canvasVector({ x: 0, y: 0 })) - : interactionData.point - - const mouseWindowPoint = canvasPointToWindowPoint( - windowPointToUse, - canvasState.scale, - canvasState.canvasOffset, - ) - - const { x, y } = scaleVector( - windowPoint({ - x: mouseWindowPoint.x - cellWindowRectangle.x, - y: mouseWindowPoint.y - cellWindowRectangle.y, - }), - 1 / canvasState.scale, - ) - - return canvasPoint({ x, y }) -} diff --git a/editor/src/components/canvas/canvas-strategies/strategies/grid-element-change-location-strategy.spec.browser2.tsx b/editor/src/components/canvas/canvas-strategies/strategies/grid-element-change-location-strategy.spec.browser2.tsx new file mode 100644 index 000000000000..04c6a3939405 --- /dev/null +++ b/editor/src/components/canvas/canvas-strategies/strategies/grid-element-change-location-strategy.spec.browser2.tsx @@ -0,0 +1,1486 @@ +import * as EP from '../../../../core/shared/element-path' +import { offsetPoint, windowPoint } from '../../../../core/shared/math-utils' +import { selectComponentsForTest } from '../../../../utils/utils.test-utils' +import CanvasActions from '../../canvas-actions' +import { GridCellTestId } from '../../controls/grid-controls-for-strategies' +import { mouseDownAtPoint, mouseMoveToPoint, mouseUpAtPoint } from '../../event-helpers.test-utils' +import { renderTestEditorWithCode } from '../../ui-jsx.test-utils' +import { runGridMoveTest } from './grid.test-utils' + +describe('grid element change location strategy', () => { + it('can change the location of elements on a grid', async () => { + const editor = await renderTestEditorWithCode(ProjectCode, 'await-first-dom-report') + + const testId = 'aaa' + const { gridRowStart, gridRowEnd, gridColumnStart, gridColumnEnd } = await runGridMoveTest( + editor, + { + scale: 1, + gridPath: 'sb/scene/grid', + testId: testId, + }, + ) + expect({ gridRowStart, gridRowEnd, gridColumnStart, gridColumnEnd }).toEqual({ + gridColumnEnd: '7', + gridColumnStart: '3', + gridRowEnd: '4', + gridRowStart: '2', + }) + }) + + it('does not alter the grid template', async () => { + const editor = await renderTestEditorWithCode( + ProjectCodeFractionalTemplate, + 'await-first-dom-report', + ) + + const testId = 'aaa' + const { gridRowStart, gridRowEnd, gridColumnStart, gridColumnEnd } = await runGridMoveTest( + editor, + { + scale: 1, + gridPath: 'sb/scene/grid', + testId: testId, + }, + ) + + expect({ gridRowStart, gridRowEnd, gridColumnStart, gridColumnEnd }).toEqual({ + gridColumnEnd: '7', + gridColumnStart: '3', + gridRowEnd: '4', + gridRowStart: '2', + }) + + const grid = await editor.renderedDOM.findByTestId('grid') + expect(grid.style.gridTemplateColumns).toEqual('1fr 1fr 2fr 2fr 1fr 1fr') + expect(grid.style.gridTemplateRows).toEqual('1fr 1fr 2fr 1fr') + }) + + describe('component items', () => { + it('can change the location of components on a grid when component takes style prop', async () => { + const editor = await renderTestEditorWithCode( + makeProjectCodeWithItemComponent(`export function Item(props) { + return ( +
+ ) +}`), + 'await-first-dom-report', + ) + const testId = 'aaa' + const { gridRowStart, gridRowEnd, gridColumnStart, gridColumnEnd } = await runGridMoveTest( + editor, + { + scale: 1, + gridPath: 'sb/scene/grid', + testId: testId, + }, + ) + expect({ gridRowStart, gridRowEnd, gridColumnStart, gridColumnEnd }).toEqual({ + gridColumnEnd: '7', + gridColumnStart: '3', + gridRowEnd: '4', + gridRowStart: '2', + }) + }) + + it('can not change the location of components on a grid when component doesnt take style prop', async () => { + const editor = await renderTestEditorWithCode( + makeProjectCodeWithItemComponent(`export function Item(props) { + return ( +
+ ) +}`), + 'await-first-dom-report', + ) + + const testId = 'aaa' + await runGridMoveTest( + editor, + { + scale: 1, + gridPath: 'sb/scene/grid', + testId: testId, + }, + (ed) => { + const strategies = ed.getEditorState().strategyState.sortedApplicableStrategies + const changeLocationStrategy = strategies?.find( + (s) => s.strategy.id === 'grid-change-element-location-strategy', + ) + expect(changeLocationStrategy).toBeUndefined() + const reorderStrategy = strategies?.find( + (s) => s.strategy.id === 'reorder-grid-move-strategy', + ) + expect(reorderStrategy).not.toBeUndefined() + }, + ) + }) + }) + + describe('grid component', () => { + it('can change the location of elements in a grid component', async () => { + const editor = await renderTestEditorWithCode( + ProjectCodeGridComponent, + 'await-first-dom-report', + ) + + const testId = 'aaa' + const { gridRowStart, gridRowEnd, gridColumnStart, gridColumnEnd } = await runGridMoveTest( + editor, + { + scale: 1, + gridPath: 'sb/scene/grid/', + testId: testId, + }, + ) + expect({ gridRowStart, gridRowEnd, gridColumnStart, gridColumnEnd }).toEqual({ + gridColumnEnd: '7', + gridColumnStart: '3', + gridRowEnd: '4', + gridRowStart: '2', + }) + }) + }) + + it('can not change location of a multicell element out of the grid', async () => { + const editor = await renderTestEditorWithCode(ProjectCode, 'await-first-dom-report') + + const testId = 'aaa' + // moving a multi-cell element to the left, but it is already on the left of the grid + const { gridRowStart, gridRowEnd, gridColumnStart, gridColumnEnd } = await runGridMoveTest( + editor, + { + scale: 1, + gridPath: 'sb/scene/grid', + testId: testId, + targetCell: { row: 2, column: 1 }, + draggedCell: { row: 2, column: 2 }, + }, + ) + expect({ gridRowStart, gridRowEnd, gridColumnStart, gridColumnEnd }).toEqual({ + gridColumnEnd: '5', + gridColumnStart: '1', + gridRowEnd: '3', + gridRowStart: '1', + }) + }) + + it('can change the location of element with no explicit grid props set', async () => { + const editor = await renderTestEditorWithCode(ProjectCode, 'await-first-dom-report') + + const testId = 'bbb' + const { gridRowStart, gridRowEnd, gridColumnStart, gridColumnEnd } = await runGridMoveTest( + editor, + { + scale: 1, + gridPath: 'sb/scene/grid', + testId: testId, + tab: true, + }, + ) + + expect({ gridRowStart, gridRowEnd, gridColumnStart, gridColumnEnd }).toEqual({ + gridColumnEnd: 'auto', + gridColumnStart: '3', + gridRowEnd: 'auto', + gridRowStart: '2', + }) + }) + + it('can change the location of a multicell element (shorthand)', async () => { + const editor = await renderTestEditorWithCode( + ProjectCodeReorderwithMultiCellChildShorthand, + 'await-first-dom-report', + ) + + const testId = 'orange' + const { gridRowStart, gridRowEnd, gridColumnStart, gridColumnEnd } = await runGridMoveTest( + editor, + { + scale: 1, + gridPath: 'sb/scene/grid', + testId: testId, + targetCell: { row: 3, column: 1 }, + }, + ) + + expect({ gridRowStart, gridRowEnd, gridColumnStart, gridColumnEnd }).toEqual({ + gridColumnStart: '1', + gridColumnEnd: '3', + gridRowStart: '3', + gridRowEnd: 'auto', + }) + }) + + it('can change the location of elements on a grid (zoom out)', async () => { + const editor = await renderTestEditorWithCode(ProjectCode, 'await-first-dom-report') + + const testId = 'aaa' + const { gridRowStart, gridRowEnd, gridColumnStart, gridColumnEnd } = await runGridMoveTest( + editor, + { + scale: 0.5, + gridPath: 'sb/scene/grid', + testId: testId, + }, + ) + expect({ gridRowStart, gridRowEnd, gridColumnStart, gridColumnEnd }).toEqual({ + gridColumnEnd: '7', + gridColumnStart: '3', + gridRowEnd: '4', + gridRowStart: '2', + }) + }) + + it('can change the location of elements on a grid (zoom in)', async () => { + const editor = await renderTestEditorWithCode(ProjectCode, 'await-first-dom-report') + + const testId = 'aaa' + const { gridRowStart, gridRowEnd, gridColumnStart, gridColumnEnd } = await runGridMoveTest( + editor, + { + scale: 2, + gridPath: 'sb/scene/grid', + testId: testId, + }, + ) + expect({ gridRowStart, gridRowEnd, gridColumnStart, gridColumnEnd }).toEqual({ + gridColumnEnd: '7', + gridColumnStart: '3', + gridRowEnd: '4', + gridRowStart: '2', + }) + }) + + it('can change the location of an element with span (from start, implicit end)', async () => { + const editor = await renderTestEditorWithCode( + ProjectCodeWithSpanningItems, + 'await-first-dom-report', + ) + + const testId = 'pink' + const { gridRowStart, gridRowEnd, gridColumnStart, gridColumnEnd } = await runGridMoveTest( + editor, + { + scale: 1, + gridPath: 'sb/scene/grid', + testId: testId, + }, + ) + + expect({ gridRowStart, gridRowEnd, gridColumnStart, gridColumnEnd }).toEqual({ + gridColumnEnd: '5', + gridColumnStart: 'span 2', + gridRowEnd: 'auto', + gridRowStart: '2', + }) + }) + + it('can change the location of an element with span (from start, explicit end)', async () => { + const editor = await renderTestEditorWithCode( + ProjectCodeWithSpanningItems, + 'await-first-dom-report', + ) + + const testId = 'cyan' + const { gridRowStart, gridRowEnd, gridColumnStart, gridColumnEnd } = await runGridMoveTest( + editor, + { + scale: 1, + gridPath: 'sb/scene/grid', + testId: testId, + }, + ) + + expect({ gridRowStart, gridRowEnd, gridColumnStart, gridColumnEnd }).toEqual({ + gridColumnEnd: '5', + gridColumnStart: 'span 2', + gridRowEnd: 'auto', + gridRowStart: '2', + }) + }) + + it('can change the location of an element with span (from end, explicit start)', async () => { + const editor = await renderTestEditorWithCode( + ProjectCodeWithSpanningItems, + 'await-first-dom-report', + ) + + const testId = 'orange' + const { gridRowStart, gridRowEnd, gridColumnStart, gridColumnEnd } = await runGridMoveTest( + editor, + { + scale: 1, + gridPath: 'sb/scene/grid', + testId: testId, + targetCell: { row: 3, column: 2 }, + }, + ) + + expect({ gridRowStart, gridRowEnd, gridColumnStart, gridColumnEnd }).toEqual({ + gridColumnEnd: 'span 3', + gridColumnStart: '2', + gridRowEnd: 'auto', + gridRowStart: '3', + }) + }) + + it('can change the location of an element with span (longhand start)', async () => { + const editor = await renderTestEditorWithCode( + ProjectCodeWithSpanningItems, + 'await-first-dom-report', + ) + + const testId = 'blue' + const { gridRowStart, gridRowEnd, gridColumnStart, gridColumnEnd } = await runGridMoveTest( + editor, + { + scale: 1, + gridPath: 'sb/scene/grid', + testId: testId, + }, + ) + + expect({ gridRowStart, gridRowEnd, gridColumnStart, gridColumnEnd }).toEqual({ + gridColumnEnd: '5', + gridColumnStart: 'span 2', + gridRowEnd: 'auto', + gridRowStart: '2', + }) + }) + + it('can change the location of an element with span (longhand end)', async () => { + const editor = await renderTestEditorWithCode( + ProjectCodeWithSpanningItems, + 'await-first-dom-report', + ) + + const testId = 'purple' + const { gridRowStart, gridRowEnd, gridColumnStart, gridColumnEnd } = await runGridMoveTest( + editor, + { + scale: 1, + gridPath: 'sb/scene/grid', + testId: testId, + }, + ) + + expect({ gridRowStart, gridRowEnd, gridColumnStart, gridColumnEnd }).toEqual({ + gridColumnEnd: 'span 2', + gridColumnStart: '3', + gridRowEnd: 'auto', + gridRowStart: '2', + }) + }) + + describe('grids within grids', () => { + it('can move a grid child that is a grid itself', async () => { + const editor = await renderTestEditorWithCode( + `import * as React from 'react' +import { Storyboard } from 'utopia-api' + +export var storyboard = ( + +
+
+
+
+
+
+
+ +) +`, + 'await-first-dom-report', + ) + + const testId = 'grid-inside-grid' + await runGridMoveTest(editor, { + scale: 1, + gridPath: 'sb/grid', + testId: testId, + }) + + const { gridRowStart, gridRowEnd, gridColumnStart, gridColumnEnd } = + editor.renderedDOM.getByTestId(testId).style + + expect({ gridRowStart, gridRowEnd, gridColumnStart, gridColumnEnd }).toEqual({ + gridColumnEnd: 'auto', + gridColumnStart: '3', + gridRowEnd: 'auto', + gridRowStart: '2', + }) + }) + }) + + describe('absolute move within grid', () => { + describe('when the cell has positioning', () => { + const ProjectCode = `import { Scene, Storyboard } from 'utopia-api' +export var storyboard = ( + + +
+
+
+ + +) +` + + const ProjectCodeWithBRPins = `import { Scene, Storyboard } from 'utopia-api' +export var storyboard = ( + + +
+
+
+ + +) +` + + it('can move absolute element inside a grid cell maintaining right and bottom pins', async () => { + const editor = await renderTestEditorWithCode( + ProjectCodeWithBRPins, + 'await-first-dom-report', + ) + + const child = editor.renderedDOM.getByTestId('child') + + { + const { bottom, right, gridColumn, gridRow } = child.style + expect({ bottom, right, gridColumn, gridRow }).toEqual({ + gridColumn: '1', + gridRow: '1', + right: '12px', + bottom: '16px', + }) + } + + await selectComponentsForTest(editor, [EP.fromString('sb/scene/grid/child')]) + + const childBounds = child.getBoundingClientRect() + const childCenter = windowPoint({ + x: Math.floor(childBounds.left + childBounds.width / 2), + y: Math.floor(childBounds.top + childBounds.height / 2), + }) + + const endPoint = offsetPoint(childCenter, windowPoint({ x: 20, y: 20 })) + + const dragTarget = editor.renderedDOM.getByTestId( + GridCellTestId(EP.fromString('sb/scene/grid/child')), + ) + await mouseDownAtPoint(dragTarget, childCenter) + await mouseMoveToPoint(dragTarget, endPoint) + await mouseUpAtPoint(dragTarget, endPoint) + + const { bottom, right, gridColumn, gridRow } = child.style + expect({ bottom, right, gridColumn, gridRow }).toEqual({ + gridColumn: '1', + gridRow: '1', + right: '-8px', + bottom: '-4px', + }) + }) + it('can move absolute element inside a grid cell', async () => { + const editor = await renderTestEditorWithCode(ProjectCode, 'await-first-dom-report') + + const child = editor.renderedDOM.getByTestId('child') + + { + const { top, left, gridColumn, gridRow } = child.style + expect({ top, left, gridColumn, gridRow }).toEqual({ + gridColumn: '1', + gridRow: '1', + left: '12px', + top: '16px', + }) + } + + await selectComponentsForTest(editor, [EP.fromString('sb/scene/grid/child')]) + + const childBounds = child.getBoundingClientRect() + const childCenter = windowPoint({ + x: Math.floor(childBounds.left + childBounds.width / 2), + y: Math.floor(childBounds.top + childBounds.height / 2), + }) + + const endPoint = offsetPoint(childCenter, windowPoint({ x: 20, y: 20 })) + + const dragTarget = editor.renderedDOM.getByTestId( + GridCellTestId(EP.fromString('sb/scene/grid/child')), + ) + await mouseDownAtPoint(dragTarget, childCenter) + await mouseMoveToPoint(dragTarget, endPoint) + await mouseUpAtPoint(dragTarget, endPoint) + + const { top, left, gridColumn, gridRow } = child.style + expect({ top, left, gridColumn, gridRow }).toEqual({ + gridColumn: '1', + gridRow: '1', + left: '32px', + top: '36px', + }) + }) + + it('can move absolute element inside a grid cell, zoomed in', async () => { + const editor = await renderTestEditorWithCode(ProjectCode, 'await-first-dom-report') + + await editor.dispatch([CanvasActions.zoom(2)], true) + + const child = editor.renderedDOM.getByTestId('child') + + { + const { top, left, gridColumn, gridRow } = child.style + expect({ top, left, gridColumn, gridRow }).toEqual({ + gridColumn: '1', + gridRow: '1', + left: '12px', + top: '16px', + }) + } + + await selectComponentsForTest(editor, [EP.fromString('sb/scene/grid/child')]) + + const childBounds = child.getBoundingClientRect() + const childCenter = windowPoint({ + x: Math.floor(childBounds.left + childBounds.width / 2), + y: Math.floor(childBounds.top + childBounds.height / 2), + }) + + const dragTarget = editor.renderedDOM.getByTestId( + GridCellTestId(EP.fromString('sb/scene/grid/child')), + ) + const endPoint = offsetPoint(childCenter, windowPoint({ x: 240, y: 240 })) + await mouseDownAtPoint(dragTarget, childCenter) + await mouseMoveToPoint(dragTarget, endPoint) + await mouseUpAtPoint(dragTarget, endPoint) + + { + const { top, left, gridColumn, gridRow } = child.style + expect({ top, left, gridColumn, gridRow }).toEqual({ + gridColumn: '1', + gridRow: '1', + left: '132px', + top: '136px', + }) + } + }) + + it('can move absolute element among grid cells (within containing block)', async () => { + const editor = await renderTestEditorWithCode(ProjectCode, 'await-first-dom-report') + + const child = editor.renderedDOM.getByTestId('child') + + { + const { top, left, gridColumn, gridRow } = child.style + expect({ top, left, gridColumn, gridRow }).toEqual({ + gridColumn: '1', + gridRow: '1', + left: '12px', + top: '16px', + }) + } + + await selectComponentsForTest(editor, [EP.fromString('sb/scene/grid/child')]) + + const childBounds = child.getBoundingClientRect() + const childCenter = windowPoint({ + x: Math.floor(childBounds.left + childBounds.width / 2), + y: Math.floor(childBounds.top + childBounds.height / 2), + }) + + const endPoint = offsetPoint(childCenter, windowPoint({ x: 240, y: 240 })) + const dragTarget = editor.renderedDOM.getByTestId( + GridCellTestId(EP.fromString('sb/scene/grid/child')), + ) + + await mouseDownAtPoint(dragTarget, childCenter) + await mouseMoveToPoint(dragTarget, endPoint) + await mouseUpAtPoint(dragTarget, endPoint) + + { + const { top, left, gridColumn, gridRow } = child.style + expect({ top, left, gridColumn, gridRow }).toEqual({ + gridColumn: '1', + gridRow: '1', + left: '252px', + top: '256px', + }) + } + }) + + it('can move absolute element among grid cells', async () => { + const editor = await renderTestEditorWithCode(ProjectCode, 'await-first-dom-report') + + const child = editor.renderedDOM.getByTestId('child') + + { + const { top, left, gridColumn, gridRow } = child.style + expect({ top, left, gridColumn, gridRow }).toEqual({ + gridColumn: '1', + gridRow: '1', + left: '12px', + top: '16px', + }) + } + + await selectComponentsForTest(editor, [EP.fromString('sb/scene/grid/child')]) + + const childBounds = child.getBoundingClientRect() + const childCenter = windowPoint({ + x: Math.floor(childBounds.left + childBounds.width / 2), + y: Math.floor(childBounds.top + childBounds.height / 2), + }) + + const endPoint = offsetPoint(childCenter, windowPoint({ x: 300, y: 300 })) + const dragTarget = editor.renderedDOM.getByTestId( + GridCellTestId(EP.fromString('sb/scene/grid/child')), + ) + + await mouseDownAtPoint(dragTarget, childCenter) + await mouseMoveToPoint(dragTarget, endPoint) + await mouseUpAtPoint(dragTarget, endPoint) + + { + const { top, left, gridColumn, gridRow } = child.style + expect({ top, left, gridColumn, gridRow }).toEqual({ + gridColumn: '2', + gridRow: '2', + left: '81.5px', + top: '92px', + }) + } + }) + + it("can move element spanning multiple cell by a cell that isn't the root cell", async () => { + const editor = await renderTestEditorWithCode( + `import { Scene, Storyboard } from 'utopia-api' +export var storyboard = ( + + +
+
+
+ + +) +`, + 'await-first-dom-report', + ) + + await selectComponentsForTest(editor, [EP.fromString('sb/scene/grid/child')]) + + const child = editor.renderedDOM.getByTestId('child') + const childBounds = child.getBoundingClientRect() + const startPoint = windowPoint({ + x: Math.floor(childBounds.left + childBounds.width - 3), + y: Math.floor(childBounds.top + childBounds.height - 3), + }) + + const endPoint = offsetPoint(startPoint, windowPoint({ x: -100, y: -100 })) + const dragTarget = editor.renderedDOM.getByTestId( + GridCellTestId(EP.fromString('sb/scene/grid/child')), + ) + + await mouseDownAtPoint(dragTarget, startPoint) + await mouseMoveToPoint(dragTarget, endPoint) + await mouseUpAtPoint(dragTarget, endPoint) + + { + const { top, left, gridColumnStart, gridColumnEnd, gridRowStart, gridRowEnd } = + child.style + expect({ top, left, gridColumnStart, gridColumnEnd, gridRowStart, gridRowEnd }).toEqual({ + gridColumnStart: '1', + gridColumnEnd: 'auto', + gridRowStart: '1', + gridRowEnd: 'auto', + left: '59px', + top: '60px', + }) + } + }) + }) + describe('when the cell has no positioning', () => { + const ProjectCode = `import { Scene, Storyboard } from 'utopia-api' +export var storyboard = ( + + +
+
+
+ + +) +` + it('performs an absolute move relative to the grid', async () => { + const editor = await renderTestEditorWithCode(ProjectCode, 'await-first-dom-report') + + const child = editor.renderedDOM.getByTestId('child') + + { + const { top, left, gridColumn, gridRow } = child.style + expect({ top, left, gridColumn, gridRow }).toEqual({ + gridColumn: '', + gridRow: '', + left: '12px', + top: '16px', + }) + } + + await selectComponentsForTest(editor, [EP.fromString('sb/scene/grid/child')]) + + const childBounds = child.getBoundingClientRect() + const childCenter = windowPoint({ + x: Math.floor(childBounds.left + childBounds.width / 2), + y: Math.floor(childBounds.top + childBounds.height / 2), + }) + + const dragTarget = editor.renderedDOM.getByTestId( + GridCellTestId(EP.fromString('sb/scene/grid/child')), + ) + const endPoint = offsetPoint(childCenter, windowPoint({ x: 280, y: 120 })) + + await mouseDownAtPoint(dragTarget, childCenter) + await mouseMoveToPoint(dragTarget, endPoint) + await mouseUpAtPoint(dragTarget, endPoint) + + { + const { top, left, gridColumn, gridRow } = child.style + expect({ top, left, gridColumn, gridRow }).toEqual({ + gridColumn: '', + gridRow: '', + left: '292px', + top: '136px', + }) + } + }) + }) + }) +}) + +const ProjectCode = `import * as React from 'react' +import { Scene, Storyboard, Placeholder } from 'utopia-api' + +export var storyboard = ( + + +
+
+
+ + +
+ + +) +` + +const ProjectCodeFractionalTemplate = `import * as React from 'react' +import { Scene, Storyboard, Placeholder } from 'utopia-api' + +export var storyboard = ( + + +
+
+
+ + +
+ + +) +` + +const makeProjectCodeWithItemComponent = ( + itemComponentCode: string, +) => `import * as React from 'react' +import { Scene, Storyboard, Placeholder } from 'utopia-api' + +export var storyboard = ( + + +
+ + + + +
+
+
+) + +${itemComponentCode} +` + +const ProjectCodeGridComponent = `import * as React from 'react' +import { Scene, Storyboard, Placeholder } from 'utopia-api' + +export var storyboard = ( + + + +
+
+ + + + + +) + +export function Grid(props) { + return ( +
+
+ {props.children} +
+
+
+ ) +} +` + +const ProjectCodeReorderwithMultiCellChildShorthand = `import * as React from 'react' +import { Scene, Storyboard } from 'utopia-api' + +export var storyboard = ( + + +
+
+
+
+
+
+ + +) +` + +const ProjectCodeWithSpanningItems = `import * as React from 'react' +import { Scene, Storyboard } from 'utopia-api' + +export var storyboard = ( + + +
+
+
+
+
+
+
+ + +) +` diff --git a/editor/src/components/canvas/canvas-strategies/strategies/grid-helpers.ts b/editor/src/components/canvas/canvas-strategies/strategies/grid-helpers.ts index f1d8f4a573e5..b722d1e84c82 100644 --- a/editor/src/components/canvas/canvas-strategies/strategies/grid-helpers.ts +++ b/editor/src/components/canvas/canvas-strategies/strategies/grid-helpers.ts @@ -1,250 +1,112 @@ import type { ElementPath } from 'utopia-shared/src/types' import { MetadataUtils } from '../../../../core/model/element-metadata-utils' -import type { ElementInstanceMetadataMap } from '../../../../core/shared/element-template' +import { mapDropNulls } from '../../../../core/shared/array-utils' +import * as EP from '../../../../core/shared/element-path' +import type { + ElementInstanceMetadataMap, + GridAutoOrTemplateBase, + GridPositionOrSpan, + GridPositionValue, + GridSpan, + SpecialSizeMeasurements, +} from '../../../../core/shared/element-template' import { - gridPositionValue, + isGridSpan, + stringifyGridSpan, type ElementInstanceMetadata, type GridContainerProperties, type GridElementProperties, - type GridPosition, } from '../../../../core/shared/element-template' -import type { CanvasVector, WindowRectangle } from '../../../../core/shared/math-utils' import { - canvasPoint, - isInfinityRectangle, - offsetPoint, - rectContainsPoint, - scaleVector, - windowPoint, - windowRectangle, - windowVector, - type WindowPoint, + localRectangle, + zeroRectangle, + zeroRectIfNullOrInfinity, + type CanvasRectangle, + type LocalRectangle, } from '../../../../core/shared/math-utils' import * as PP from '../../../../core/shared/property-path' +import { assertNever } from '../../../../core/shared/utils' +import type { GridDimension } from '../../../inspector/common/css-utils' +import { + cssKeyword, + gridCSSRepeat, + isCSSKeyword, + isGridCSSRepeat, + isStaticGridRepeat, + printGridAutoOrTemplateBase, +} from '../../../inspector/common/css-utils' import type { CanvasCommand } from '../../commands/commands' -import { setProperty } from '../../commands/set-property-command' -import { canvasPointToWindowPoint } from '../../dom-lookup' -import type { DragInteractionData } from '../interaction-state' -import type { GridCustomStrategyState, InteractionCanvasState } from '../canvas-strategy-types' -import type { GridCellCoordinates } from '../../controls/grid-controls' -import { gridCellCoordinates } from '../../controls/grid-controls' -import * as EP from '../../../../core/shared/element-path' import { deleteProperties } from '../../commands/delete-properties-command' -import { cssNumber, isCSSKeyword } from '../../../inspector/common/css-utils' -import { setCssLengthProperty } from '../../commands/set-css-length-command' - -export function getGridCellUnderMouse(mousePoint: WindowPoint) { - return getGridCellAtPoint(mousePoint, false) -} - -function getGridCellUnderMouseRecursive(mousePoint: WindowPoint) { - return getGridCellAtPoint(mousePoint, true) -} - -const gridCellTargetIdPrefix = 'grid-cell-target-' - -export function gridCellTargetId( - gridElementPath: ElementPath, - row: number, - column: number, -): string { - return gridCellTargetIdPrefix + `${EP.toString(gridElementPath)}-${row}-${column}` -} - -function isGridCellTargetId(id: string): boolean { - return id.startsWith(gridCellTargetIdPrefix) -} - -export function getGridCellAtPoint( - point: WindowPoint, - duplicating: boolean, -): { id: string; coordinates: GridCellCoordinates; cellWindowRectangle: WindowRectangle } | null { - function maybeRecursivelyFindCellAtPoint( - elements: Element[], - ): { element: Element; cellWindowRectangle: WindowRectangle } | null { - // If this used during duplication, the canvas controls will be in the way and we need to traverse the children too. - for (const element of elements) { - if (isGridCellTargetId(element.id)) { - const domRect = element.getBoundingClientRect() - const windowRect = windowRectangle(domRect) - if (rectContainsPoint(windowRect, point)) { - return { element: element, cellWindowRectangle: windowRect } - } - } - - if (duplicating) { - const child = maybeRecursivelyFindCellAtPoint(Array.from(element.children)) - if (child != null) { - return child - } - } - } - - return null - } - - const cellUnderMouse = maybeRecursivelyFindCellAtPoint( - document.elementsFromPoint(point.x, point.y), - ) - if (cellUnderMouse == null) { +import type { PropertyToUpdate } from '../../commands/set-property-command' +import { + propertyToDelete, + propertyToSet, + setProperty, + updateBulkProperties, +} from '../../commands/set-property-command' +import type { DragInteractionData } from '../interaction-state' +import type { GridCellCoordinates } from './grid-cell-bounds' +import { + getClosestGridCellToPoint, + getGridChildCellCoordBoundsFromCanvas, + gridCellCoordinates, +} from './grid-cell-bounds' +import type { GridIdentifier } from '../../../editor/store/editor-state' + +export function gridPositionToValue( + p: GridPositionOrSpan | null | undefined, + spanOffset: GridPositionOrSpan | null, +): string | number | null { + if (p == null) { return null } - const { element, cellWindowRectangle } = cellUnderMouse - const row = element.getAttribute('data-grid-row') - const column = element.getAttribute('data-grid-column') - - return { - id: element.id, - cellWindowRectangle: cellWindowRectangle, - coordinates: gridCellCoordinates( - row == null ? 0 : parseInt(row), - column == null ? 0 : parseInt(column), - ), - } -} - -export function runGridRearrangeMove( - targetElement: ElementPath, - selectedElement: ElementPath, - jsxMetadata: ElementInstanceMetadataMap, - interactionData: DragInteractionData, - canvasScale: number, - canvasOffset: CanvasVector, - customState: GridCustomStrategyState, - duplicating: boolean, -): { - commands: CanvasCommand[] - targetCell: TargetGridCellData | null - draggingFromCell: GridCellCoordinates | null - originalRootCell: GridCellCoordinates | null - targetRootCell: GridCellCoordinates | null -} { - if (interactionData.drag == null) { - return { - commands: [], - targetCell: null, - originalRootCell: null, - draggingFromCell: null, - targetRootCell: null, - } - } - - const mouseWindowPoint = canvasPointToWindowPoint( - offsetPoint(interactionData.dragStart, interactionData.drag), - canvasScale, - canvasOffset, - ) - - const targetCellData = - getTargetCell( - customState.targetCellData?.gridCellCoordinates ?? null, - duplicating, - mouseWindowPoint, - ) ?? customState.targetCellData - - if (targetCellData == null) { - return { - commands: [], - targetCell: null, - draggingFromCell: null, - originalRootCell: null, - targetRootCell: null, - } - } - - const targetCellUnderMouse = targetCellData?.gridCellCoordinates ?? null + const offset = isGridSpan(spanOffset) && spanOffset.type === 'SPAN_NUMERIC' ? spanOffset.value : 0 - const absoluteMoveCommands = - targetCellData == null - ? [] - : gridChildAbsoluteMoveCommands( - MetadataUtils.findElementByElementPath(jsxMetadata, targetElement), - targetCellData.cellWindowRectangle, - interactionData, - { scale: canvasScale, canvasOffset: canvasOffset }, - ) - - const originalElementMetadata = MetadataUtils.findElementByElementPath( - jsxMetadata, - selectedElement, - ) - if (originalElementMetadata == null) { - return { - commands: [], - targetCell: null, - originalRootCell: null, - draggingFromCell: null, - targetRootCell: null, + if (isGridSpan(p)) { + switch (p.type) { + case 'SPAN_AREA': + return null // # TODO fill this in once we support grid areas + case 'SPAN_NUMERIC': + return p.value + offset } } - const containerMetadata = MetadataUtils.findElementByElementPath( - jsxMetadata, - EP.parentPath(selectedElement), - ) - if (containerMetadata == null) { - return { - commands: [], - targetCell: null, - originalRootCell: null, - draggingFromCell: null, - targetRootCell: null, - } + if (isCSSKeyword(p)) { + return p.value } - const gridTemplate = containerMetadata.specialSizeMeasurements.containerGridProperties - - const cellGridProperties = getElementGridProperties(originalElementMetadata, targetCellUnderMouse) - - // calculate the difference between the cell the mouse started the interaction from, and the "root" - // cell of the element, meaning the top-left-most cell the element occupies. - const draggingFromCell = customState.draggingFromCell ?? targetCellUnderMouse - const rootCell = - customState.originalRootCell ?? - gridCellCoordinates(cellGridProperties.row, cellGridProperties.column) - const coordsDiff = getCellCoordsDelta(draggingFromCell, rootCell) - - // get the new adjusted row - const row = getCoordBounds(targetCellUnderMouse, 'row', cellGridProperties.width, coordsDiff.row) - // get the new adjusted column - const column = getCoordBounds( - targetCellUnderMouse, - 'column', - cellGridProperties.height, - coordsDiff.column, - ) - - const targetRootCell = gridCellCoordinates(row.start, column.start) - - const gridCellMoveCommands = setGridPropsCommands(targetElement, gridTemplate, { - gridColumnStart: gridPositionValue(column.start), - gridColumnEnd: gridPositionValue(column.end), - gridRowEnd: gridPositionValue(row.end), - gridRowStart: gridPositionValue(row.start), - }) + return p.numericalPosition +} - return { - commands: [...gridCellMoveCommands, ...absoluteMoveCommands], - targetCell: targetCellData ?? customState.targetCellData, - originalRootCell: rootCell, - draggingFromCell: draggingFromCell, - targetRootCell: targetRootCell, - } +export function isAutoGridPin(v: GridPositionOrSpan | null): boolean { + return isCSSKeyword(v) && v.value === 'auto' } -export function gridPositionToValue(p: GridPosition | null | undefined): string | number | null { - if (p == null) { - return null +export function printPin( + gridTemplate: GridContainerProperties, + pin: GridPositionOrSpan, + axis: 'row' | 'column', +): string | number { + if (isGridSpan(pin)) { + return stringifyGridSpan(pin) } - if (isCSSKeyword(p)) { - return p.value + if (isCSSKeyword(pin)) { + return pin.value } - - return p.numericalPosition + const tracks = + axis === 'column' ? gridTemplate.gridTemplateColumns : gridTemplate.gridTemplateRows + const maybeLineName = + tracks?.type === 'DIMENSIONS' + ? tracks.dimensions.find((_, index) => index + 1 === pin.numericalPosition)?.lineName + : null + if (maybeLineName != null) { + return maybeLineName + } + return pin.numericalPosition ?? 'auto' } -export function setGridPropsCommands( +export function getCommandsForGridItemPlacement( elementPath: ElementPath, gridTemplate: GridContainerProperties, gridProps: Partial, @@ -260,99 +122,97 @@ export function setGridPropsCommands( PP.create('style', 'gridRowEnd'), ]), ] - const columnStart = gridPositionToValue(gridProps.gridColumnStart) - const columnEnd = gridPositionToValue(gridProps.gridColumnEnd) - const rowStart = gridPositionToValue(gridProps.gridRowStart) - const rowEnd = gridPositionToValue(gridProps.gridRowEnd) - - const areaColumnStart = asMaybeNamedAreaOrValue(gridTemplate, 'column', columnStart) - const areaColumnEnd = asMaybeNamedAreaOrValue(gridTemplate, 'column', columnEnd) - const areaRowStart = asMaybeNamedAreaOrValue(gridTemplate, 'row', rowStart) - const areaRowEnd = asMaybeNamedAreaOrValue(gridTemplate, 'row', rowEnd) - - if (columnStart != null && columnStart === columnEnd) { - commands.push( - setProperty('always', elementPath, PP.create('style', 'gridColumn'), areaColumnStart), - ) - } else if ( - columnStart != null && - typeof columnStart === 'number' && - columnEnd != null && - typeof columnEnd === 'number' && - columnStart === columnEnd - 1 - ) { - commands.push( - setProperty('always', elementPath, PP.create('style', 'gridColumn'), areaColumnStart), - ) - } else { - if (columnStart != null) { - commands.push( - setProperty('always', elementPath, PP.create('style', 'gridColumnStart'), areaColumnStart), - ) - } - if (columnEnd != null) { - commands.push( - setProperty('always', elementPath, PP.create('style', 'gridColumnEnd'), areaColumnEnd), - ) + + function serializeAxis( + startPosition: GridPositionOrSpan, + endPosition: GridPositionOrSpan, + axis: 'row' | 'column', + ): { + property: + | 'gridColumn' + | 'gridColumnStart' + | 'gridColumnEnd' + | 'gridRow' + | 'gridRowStart' + | 'gridRowEnd' + value: string | number + } { + const startValue = printPin(gridTemplate, startPosition, axis) + const endValue = printPin(gridTemplate, endPosition, axis) + + if (isAutoGridPin(startPosition) && !isAutoGridPin(endPosition)) { + return { + property: axis === 'column' ? 'gridColumnEnd' : 'gridRowEnd', + value: endValue, + } } - } - if (rowStart != null && rowStart === rowEnd) { - commands.push(setProperty('always', elementPath, PP.create('style', 'gridRow'), areaRowStart)) - } else if ( - rowStart != null && - typeof rowStart === 'number' && - rowEnd != null && - typeof rowEnd === 'number' && - rowStart === rowEnd - 1 - ) { - commands.push(setProperty('always', elementPath, PP.create('style', 'gridRow'), areaRowStart)) - } else { - if (rowStart != null) { - commands.push( - setProperty('always', elementPath, PP.create('style', 'gridRowStart'), areaRowStart), - ) + function shouldReturnSingleValue(): boolean { + const isAutoPin = isAutoGridPin(endPosition) + if (isAutoPin) { + return true + } + + const printedValuedEqual = startValue === endValue + if (printedValuedEqual) { + return true + } + + const positionsAreNumeric = + isGridPositionNumericValue(endPosition) && isGridPositionNumericValue(startPosition) + if (positionsAreNumeric) { + const startNumericPosition = startPosition.numericalPosition ?? 0 + const endNumericPosition = endPosition.numericalPosition ?? 0 + const positionsDeltaAtMostOne = + endNumericPosition >= startNumericPosition && + endNumericPosition - startNumericPosition <= 1 + if (positionsDeltaAtMostOne) { + return true + } + } + + return false } - if (rowEnd != null) { - commands.push( - setProperty('always', elementPath, PP.create('style', 'gridRowEnd'), areaRowEnd), - ) + if (shouldReturnSingleValue()) { + return { + property: axis === 'column' ? 'gridColumn' : 'gridRow', + value: startValue, + } + } + + return { + property: axis === 'column' ? 'gridColumn' : 'gridRow', + value: `${startValue} / ${endValue}`, } } + const gridColumn = serializeAxis( + gridProps.gridColumnStart ?? cssKeyword('auto'), + gridProps.gridColumnEnd ?? cssKeyword('auto'), + 'column', + ) + const gridColumnProp = PP.create('style', gridColumn.property) + commands.push(setProperty('always', elementPath, gridColumnProp, gridColumn.value)) + + const gridRow = serializeAxis( + gridProps.gridRowStart ?? cssKeyword('auto'), + gridProps.gridRowEnd ?? cssKeyword('auto'), + 'row', + ) + const gridRowProp = PP.create('style', gridRow.property) + commands.push(setProperty('always', elementPath, gridRowProp, gridRow.value)) + return commands } export interface TargetGridCellData { gridCellCoordinates: GridCellCoordinates - cellWindowRectangle: WindowRectangle -} - -export function getTargetCell( - previousTargetCell: GridCellCoordinates | null, - duplicating: boolean, - mouseWindowPoint: WindowPoint, -): TargetGridCellData | null { - let cell = previousTargetCell ?? null - const cellUnderMouse = duplicating - ? getGridCellUnderMouseRecursive(mouseWindowPoint) - : getGridCellUnderMouse(mouseWindowPoint) - if (cellUnderMouse == null) { - return null - } - cell = cellUnderMouse.coordinates - if (cell.row < 1 || cell.column < 1) { - return null - } - return { - gridCellCoordinates: cell, - cellWindowRectangle: cellUnderMouse.cellWindowRectangle, - } + cellCanvasRectangle: CanvasRectangle } -function getElementGridProperties( +function getGridChildCellCoordBoundsFromProps( element: ElementInstanceMetadata, - cellUnderMouse: { row: number; column: number }, + fallback: { row: number; column: number; width?: number; height?: number }, ): { row: number width: number @@ -360,17 +220,18 @@ function getElementGridProperties( height: number } { // get the grid fixtures (start and end for column and row) from the element metadata - function getGridProperty(field: keyof GridElementProperties, fallback: number) { + function getGridProperty(field: keyof GridElementProperties, innerFallback: number) { const propValue = element.specialSizeMeasurements.elementGridProperties[field] - if (propValue == null || isCSSKeyword(propValue)) { - return fallback + if (propValue == null || isCSSKeyword(propValue) || isGridSpan(propValue)) { + return innerFallback } - return propValue.numericalPosition ?? fallback + return propValue.numericalPosition ?? innerFallback } - const column = getGridProperty('gridColumnStart', cellUnderMouse.column) - const height = getGridProperty('gridColumnEnd', cellUnderMouse.column + 1) - column - const row = getGridProperty('gridRowStart', cellUnderMouse.row) - const width = getGridProperty('gridRowEnd', cellUnderMouse.row + 1) - row + + const column = getGridProperty('gridColumnStart', fallback.column) + const width = getGridProperty('gridColumnEnd', fallback.column + (fallback.width ?? 1)) - column + const row = getGridProperty('gridRowStart', fallback.row) + const height = getGridProperty('gridRowEnd', fallback.row + (fallback.height ?? 1)) - row return { row, @@ -390,164 +251,665 @@ function getCellCoordsDelta( return gridCellCoordinates(rowDiff, columnDiff) } -function getCoordBounds( - cell: GridCellCoordinates, - coord: 'column' | 'row', - size: number, // width or height - adjustOffset: number, // adjustment based on the difference between the initial dragging cell and the root cell -): { start: number; end: number } { - // the start is the first cell's coord the element will occupy - const start = Math.max(1, cell[coord] - adjustOffset) - // the end is the last cell's coord the element will occupy - const end = Math.max(1, start + size) - return { start, end } +export type SortableGridElementProperties = GridElementProperties & { + path: ElementPath + index: number } -function asMaybeNamedAreaOrValue( - grid: GridContainerProperties, - axis: 'row' | 'column', - value: number | string | null, -): string | number { - if (value == null) { - return 1 - } else if (typeof value === 'number') { - const template = axis === 'row' ? grid.gridTemplateRows : grid.gridTemplateColumns - if (template?.type === 'DIMENSIONS') { - const maybeAreaStart = template.dimensions.at(value - 1) - if (maybeAreaStart != null && maybeAreaStart.areaName != null) { - return maybeAreaStart.areaName +export function sortElementsByGridPosition(gridTemplateColumns: number) { + return function (a: SortableGridElementProperties, b: SortableGridElementProperties): number { + function getPosition(index: number, e: GridElementProperties) { + if ( + e.gridColumnStart == null || + isCSSKeyword(e.gridColumnStart) || + e.gridRowStart == null || + isCSSKeyword(e.gridRowStart) + ) { + return index } + + function maybeNumericalValue(dim: GridSpan | GridPositionValue) { + return isGridSpan(dim) + ? dim.type === 'SPAN_NUMERIC' + ? dim.value + : null + : dim.numericalPosition + } + + const start = maybeNumericalValue(e.gridRowStart) + const end = maybeNumericalValue(e.gridColumnStart) + + const row = start ?? 1 + const column = end ?? 1 + + return (row - 1) * gridTemplateColumns + column - 1 } - return value === 0 ? 1 : value + + return getPosition(a.index, a) - getPosition(b.index, b) } - return value } -function gridChildAbsoluteMoveCommands( - targetMetadata: ElementInstanceMetadata | null, - targetCellWindowRect: WindowRectangle, - dragInteractionData: DragInteractionData, - canvasContext: Pick, -): CanvasCommand[] { - if ( - targetMetadata == null || - targetMetadata.globalFrame == null || - isInfinityRectangle(targetMetadata.globalFrame) || - !MetadataUtils.isPositionAbsolute(targetMetadata) - ) { - return [] +function isGridPositionNumericValue(p: GridPositionOrSpan | null): p is GridPositionValue { + return p != null && !isGridSpan(p) && !(isCSSKeyword(p) && p.value === 'auto') +} + +export function getGridPositionIndex(props: { + row: number + column: number + gridTemplateColumns: number +}): number { + return (props.row - 1) * props.gridTemplateColumns + props.column - 1 +} + +export type GridCellGlobalFrames = Array> + +export function getGlobalFrameOfGridCellFromMetadata( + grid: ElementInstanceMetadata, + coords: GridCellCoordinates, +): CanvasRectangle | null { + const gridCellGlobalFrames = grid.specialSizeMeasurements.gridCellGlobalFrames + if (gridCellGlobalFrames == null) { + return null } - const targetFrameWindow = canvasPointToWindowPoint( - canvasPoint({ - x: targetMetadata.globalFrame.x, - y: targetMetadata.globalFrame.y, - }), - canvasContext.scale, - canvasContext.canvasOffset, - ) + return getGlobalFrameOfGridCell(gridCellGlobalFrames, coords) +} - const dragStartWindow = canvasPointToWindowPoint( - canvasPoint({ - x: dragInteractionData.originalDragStart.x, - y: dragInteractionData.originalDragStart.y, - }), - canvasContext.scale, - canvasContext.canvasOffset, - ) +export function getGlobalFrameOfGridCell( + gridCellGlobalFrames: GridCellGlobalFrames, + coords: GridCellCoordinates, +): CanvasRectangle | null { + return gridCellGlobalFrames[coords.row - 1]?.[coords.column - 1] ?? null +} + +type DimensionIndexes = { + originalIndex: number // the index of this element in the original values + repeatedIndex: number // the index of this element, if it's generated via a repeat, inside the repeated values array definition +} + +export type ExpandedGridDimension = GridDimension & { + indexes: DimensionIndexes +} + +function expandedGridDimension( + dim: GridDimension, + originalIndex: number, + repeatedIndex: number = 0, +): ExpandedGridDimension { + return { + ...dim, + indexes: { + originalIndex: originalIndex, + repeatedIndex: repeatedIndex, + }, + } +} + +export function expandGridDimensions(template: GridDimension[]): ExpandedGridDimension[] { + // Expanded representation of the original values, where repeated elements are serialized. + // Each element also contains the indexes information to be used later on to build the resized + // template string. + return template.reduce((acc, cur, index) => { + if (isStaticGridRepeat(cur)) { + const repeatGroup = cur.value.map((dim, repeatedIndex) => + expandedGridDimension(dim, index, repeatedIndex), + ) + let expanded: ExpandedGridDimension[] = [] + for (let i = 0; i < cur.times; i++) { + expanded.push(...repeatGroup) + } + return [...acc, ...expanded] + } else { + return [...acc, expandedGridDimension(cur, index)] + } + }, [] as ExpandedGridDimension[]) +} + +function alterGridTemplateDimensions(params: { + originalValues: GridDimension[] + target: ExpandedGridDimension + patch: AlterGridTemplateDimensionPatch +}): GridDimension[] { + return mapDropNulls((dim, index) => { + if (index !== params.target.indexes.originalIndex) { + return dim + } else if (isGridCSSRepeat(dim)) { + const repeatedIndex = params.target.indexes.repeatedIndex ?? 0 + const before = dim.value.slice(0, repeatedIndex) + const after = dim.value.slice(repeatedIndex + 1) + switch (params.patch.type) { + case 'REMOVE': + if (before.length + after.length === 0) { + return null + } + return gridCSSRepeat(dim.times, [...before, ...after], dim.lineName) + case 'REPLACE': + return gridCSSRepeat( + dim.times, + [...before, params.patch.newValue, ...after], + dim.lineName, + ) + default: + assertNever(params.patch) + } + } else { + switch (params.patch.type) { + case 'REPLACE': + return params.patch.newValue + case 'REMOVE': + return null + default: + assertNever(params.patch) + } + } + }, params.originalValues) +} + +export type ReplaceGridDimensionPatch = { + type: 'REPLACE' + newValue: GridDimension +} - const offsetInTarget = windowPoint({ - x: dragStartWindow.x - targetFrameWindow.x, - y: dragStartWindow.y - targetFrameWindow.y, +export type RemoveGridDimensionPatch = { + type: 'REMOVE' +} + +export type AlterGridTemplateDimensionPatch = ReplaceGridDimensionPatch | RemoveGridDimensionPatch + +export function replaceGridTemplateDimensionAtIndex( + template: GridDimension[], + expanded: ExpandedGridDimension[], + index: number, + newValue: GridDimension, +): GridDimension[] { + return alterGridTemplateDimensions({ + originalValues: template, + target: expanded[index], + patch: { + type: 'REPLACE', + newValue: newValue, + }, }) +} - const dragWindowOffset = canvasPointToWindowPoint( - offsetPoint( - dragInteractionData.originalDragStart, - dragInteractionData.drag ?? canvasPoint({ x: 0, y: 0 }), - ), - canvasContext.scale, - canvasContext.canvasOffset, +export function removeGridTemplateDimensionAtIndex( + template: GridDimension[], + expanded: ExpandedGridDimension[], + index: number, +): GridDimension[] { + return alterGridTemplateDimensions({ + originalValues: template, + target: expanded[index], + patch: { + type: 'REMOVE', + }, + }) +} + +// Return an array of related indexes to a given index inside a grid's template dimensions. +export function getGridRelatedIndexes(params: { + template: GridDimension[] + index: number +}): number[] { + let relatedIndexes: number[][][] = [] // This looks scary but it's not! It's just a list of indexes, containing a list of the indexes *per group element*. + // For example, 1fr repeat(3, 10px 20px) 1fr, will be represented as: + /** + * [ + * [ [0] ] + * [ [1, 3] [2, 4] ] + * [ [5] ] + * ] + */ + let elementCount = 0 // basically the expanded index + for (const dim of params.template) { + if (isStaticGridRepeat(dim)) { + let groupIndexes: number[][] = [] + // for each value push the related indexes as many times as the repeats counter + for (let valueIndex = 0; valueIndex < dim.value.length; valueIndex++) { + let repeatedValueIndexes: number[] = [] + for (let repeatIndex = 0; repeatIndex < dim.times; repeatIndex++) { + repeatedValueIndexes.push(elementCount + valueIndex + repeatIndex * dim.value.length) + } + groupIndexes.push(repeatedValueIndexes) + } + relatedIndexes.push(groupIndexes) + elementCount += dim.value.length * dim.times // advance the counter as many times as the repeated values *combined* + } else { + relatedIndexes.push([[elementCount]]) + elementCount++ + } + } + + // Now, expand the indexes calculated above so they "flatten out" to match the generated values + let expandedRelatedIndexes: number[][] = [] + params.template.forEach((dim, dimIndex) => { + if (isStaticGridRepeat(dim)) { + for (let repeatIndex = 0; repeatIndex < dim.times * dim.value.length; repeatIndex++) { + const indexes = relatedIndexes[dimIndex][repeatIndex % dim.value.length] + expandedRelatedIndexes.push(indexes) + } + } else { + expandedRelatedIndexes.push(relatedIndexes[dimIndex][0]) + } + }) + + return expandedRelatedIndexes[params.index] ?? [] +} + +export function getOriginalElementGridConfiguration( + gridCellGlobalFrames: GridCellGlobalFrames, + interactionData: DragInteractionData, + originalElement: ElementInstanceMetadata, +) { + const draggingFromCellCoords = getClosestGridCellToPoint( + gridCellGlobalFrames, + interactionData.dragStart, + 'exclusive', + )?.gridCellCoordinates + if (draggingFromCellCoords == null) { + return null + } + + // measured cell coord bounds on the canvas, this is the default when the cell position is not explicitly set + const originalElementCellCoordsOnCanvas = getGridChildCellCoordBoundsFromCanvas( + originalElement, + gridCellGlobalFrames, ) - const offset = scaleVector( - windowVector({ - x: dragWindowOffset.x - targetCellWindowRect.x - offsetInTarget.x, - y: dragWindowOffset.y - targetCellWindowRect.y - offsetInTarget.y, - }), - 1 / canvasContext.scale, + // get the bounds from the props, or the canvas, or just default to the cell of the starting mouse position + const originalCellBounds = getGridChildCellCoordBoundsFromProps( + originalElement, + originalElementCellCoordsOnCanvas ?? draggingFromCellCoords, ) - return [ - deleteProperties('always', targetMetadata.elementPath, [ - PP.create('style', 'top'), - PP.create('style', 'left'), - PP.create('style', 'right'), - PP.create('style', 'bottom'), - ]), - setCssLengthProperty( - 'always', - targetMetadata.elementPath, - PP.create('style', 'top'), - { type: 'EXPLICIT_CSS_NUMBER', value: cssNumber(offset.y, null) }, - null, - ), - setCssLengthProperty( - 'always', - targetMetadata.elementPath, - PP.create('style', 'left'), - { type: 'EXPLICIT_CSS_NUMBER', value: cssNumber(offset.x, null) }, - null, - ), - ] + // the cell position of the mouse relative to the original element (we have to keep this offset while dragging) + const mouseCellPosInOriginalElement = getCellCoordsDelta( + draggingFromCellCoords, + originalCellBounds, + ) + + return { + originalCellBounds, + mouseCellPosInOriginalElement, + } } -const GRID_BOUNDS_TOLERANCE = 5 // px +export function findOriginalGrid( + metadata: ElementInstanceMetadataMap, + path: ElementPath, +): ElementPath | null { + const elementMetadata = MetadataUtils.findElementByElementPath(metadata, path) + if (elementMetadata == null) { + return null + } -export function getGridCellBoundsFromCanvas( - cell: ElementInstanceMetadata, - canvasScale: number, - canvasOffset: CanvasVector, -) { - const cellFrame = cell.globalFrame - if (cellFrame == null || isInfinityRectangle(cellFrame)) { + if (!MetadataUtils.isGridLayoutedContainer(elementMetadata)) { return null } - const canvasFrameWidth = cellFrame.width * canvasScale - const canvasFrameHeight = cellFrame.height * canvasScale + if (!elementMetadata.specialSizeMeasurements.layoutSystemForChildrenInherited) { + return path + } - const cellOriginPoint = offsetPoint( - canvasPointToWindowPoint(cellFrame, canvasScale, canvasOffset), - windowPoint({ x: GRID_BOUNDS_TOLERANCE, y: GRID_BOUNDS_TOLERANCE }), - ) - const cellOrigin = getGridCellAtPoint(cellOriginPoint, true) - if (cellOrigin == null) { + const parentPath = EP.parentPath(path) + if (parentPath == null) { return null } - const cellEndPoint = offsetPoint( - cellOriginPoint, - windowPoint({ - x: canvasFrameWidth - GRID_BOUNDS_TOLERANCE, - y: canvasFrameHeight - GRID_BOUNDS_TOLERANCE, - }), + return findOriginalGrid(metadata, parentPath) +} + +// Returns whether the given dimensions are made of just one item, being a CSS keyword with value "auto". +export function isJustAutoGridDimension(dimensions: GridDimension[]): boolean { + return ( + dimensions.length === 1 && + dimensions[0].type === 'KEYWORD' && + dimensions[0].value.value === 'auto' ) - const cellEnd = getGridCellAtPoint(cellEndPoint, true) - if (cellEnd == null) { - return null +} + +type GridElementPinState = 'not-pinned' | 'auto-pinned' | 'pinned' + +export function getGridElementPinState( + elementGridPropertiesFromProps: GridElementProperties | null, +): GridElementPinState { + if ( + elementGridPropertiesFromProps?.gridColumnEnd == null && + elementGridPropertiesFromProps?.gridColumnStart == null && + elementGridPropertiesFromProps?.gridRowEnd == null && + elementGridPropertiesFromProps?.gridRowStart == null + ) { + return 'not-pinned' + } + if ( + isGridPositionNumericValue(elementGridPropertiesFromProps?.gridColumnEnd) || + isGridSpan(elementGridPropertiesFromProps?.gridColumnEnd) || + isGridPositionNumericValue(elementGridPropertiesFromProps?.gridColumnStart) || + isGridSpan(elementGridPropertiesFromProps?.gridColumnStart) || + isGridPositionNumericValue(elementGridPropertiesFromProps?.gridRowEnd) || + isGridSpan(elementGridPropertiesFromProps?.gridRowEnd) || + isGridPositionNumericValue(elementGridPropertiesFromProps?.gridRowStart) || + isGridSpan(elementGridPropertiesFromProps?.gridRowStart) + ) { + return 'pinned' + } + return 'auto-pinned' +} + +export function isFlowGridChild(child: ElementInstanceMetadata) { + return getGridElementPinState(child.specialSizeMeasurements.elementGridProperties) !== 'pinned' +} + +function restoreGridTemplateFromProps(params: { + columns: GridAutoOrTemplateBase + rows: GridAutoOrTemplateBase +}): PropertyToUpdate[] { + let properties: PropertyToUpdate[] = [] + const newCols = printGridAutoOrTemplateBase(params.columns) + const newRows = printGridAutoOrTemplateBase(params.rows) + if (newCols === '') { + properties.push(propertyToDelete(PP.create('style', 'gridTemplateColumns'))) + } else { + properties.push(propertyToSet(PP.create('style', 'gridTemplateColumns'), newCols)) + } + if (newRows === '') { + properties.push(propertyToDelete(PP.create('style', 'gridTemplateRows'))) + } else { + properties.push(propertyToSet(PP.create('style', 'gridTemplateRows'), newRows)) + } + return properties +} + +type GridInitialTemplates = { + calculated: { + columns: GridAutoOrTemplateBase + rows: GridAutoOrTemplateBase } + fromProps: { + columns: GridAutoOrTemplateBase + rows: GridAutoOrTemplateBase + } +} - const cellOriginCoords = cellOrigin.coordinates - const cellEndCoords = cellEnd.coordinates +export function getParentGridTemplatesFromChildMeasurements( + specialSizeMeasurements: SpecialSizeMeasurements, +): GridInitialTemplates | null { + const parentTemplateCalculated = specialSizeMeasurements.parentContainerGridProperties + const parentTemplateFromProps = specialSizeMeasurements.parentContainerGridPropertiesFromProps - const cellWidth = cellEndCoords.column - cellOriginCoords.column + 1 - const cellHeight = cellEndCoords.row - cellOriginCoords.row + 1 + const templateColsCalculated = parentTemplateCalculated.gridTemplateColumns + if (templateColsCalculated == null) { + return null + } + const templateRowsCalculated = parentTemplateCalculated.gridTemplateRows + if (templateRowsCalculated == null) { + return null + } + + const templateColsFromProps = parentTemplateFromProps.gridTemplateColumns + if (templateColsFromProps == null) { + return null + } + const templateRowsFromProps = parentTemplateFromProps.gridTemplateRows + if (templateRowsFromProps == null) { + return null + } return { - column: cellOriginCoords.column, - row: cellOriginCoords.row, - width: cellWidth, - height: cellHeight, + calculated: { + columns: templateColsCalculated, + rows: templateRowsCalculated, + }, + fromProps: { + columns: templateColsFromProps, + rows: templateRowsFromProps, + }, + } +} + +export function gridMoveStrategiesExtraCommands( + parentGridPath: ElementPath, + initialTemplates: GridInitialTemplates, +) { + const midInteractionCommands = [ + // during the interaction, freeze the template with the calculated values… + updateBulkProperties('mid-interaction', parentGridPath, [ + propertyToSet( + PP.create('style', 'gridTemplateColumns'), + printGridAutoOrTemplateBase(initialTemplates.calculated.columns), + ), + propertyToSet( + PP.create('style', 'gridTemplateRows'), + printGridAutoOrTemplateBase(initialTemplates.calculated.rows), + ), + ]), + ] + + const onCompleteCommands = [ + // …eventually, restore the grid template on complete. + updateBulkProperties( + 'on-complete', + parentGridPath, + restoreGridTemplateFromProps(initialTemplates.fromProps), + ), + ] + + return { midInteractionCommands, onCompleteCommands } +} + +export function getGridIdentifierContainerOrComponentPath(identifier: GridIdentifier): ElementPath { + switch (identifier.type) { + case 'GRID_CONTAINER': + return identifier.container + case 'GRID_ITEM': + return EP.parentPath(identifier.item) + default: + assertNever(identifier) + } +} + +export function gridIdentifiersSimilar(a: GridIdentifier, b: GridIdentifier): boolean { + switch (a.type) { + case 'GRID_CONTAINER': + return b.type === 'GRID_CONTAINER' + ? EP.pathsEqual(a.container, b.container) + : EP.isParentOf(a.container, b.item) + case 'GRID_ITEM': + return b.type === 'GRID_ITEM' + ? EP.pathsEqual(a.item, b.item) + : EP.isParentOf(b.container, a.item) + default: + assertNever(a) + } +} + +export function gridIdentifierToString(identifier: GridIdentifier): string { + switch (identifier.type) { + case 'GRID_CONTAINER': + return `${identifier.type}-${EP.toString(identifier.container)}` + case 'GRID_ITEM': + return `${identifier.type}-${EP.toString(identifier.item)}` + default: + assertNever(identifier) + } +} + +function printPinAsString( + gridTemplate: GridContainerProperties, + pin: GridPositionOrSpan, + axis: 'row' | 'column', +): string { + const printPinResult = printPin(gridTemplate, pin, axis) + if (typeof printPinResult === 'number') { + return `${printPinResult}` + } else { + return printPinResult + } +} + +const TemporaryGridID = 'temporary-grid' + +export function getGridRelativeContainingBlock( + gridMetadata: ElementInstanceMetadata, + gridElements: ElementInstanceMetadata[], + targetElement: ElementPath, + options?: { + forcePositionRelative?: boolean + }, +): LocalRectangle { + const gridProperties = gridMetadata.specialSizeMeasurements.containerGridProperties + + // Create containing fragment. + const fragment = document.createDocumentFragment() + + // Create offset container that potentially provides the layout positioning. + const offsetContainer = document.createElement('div') + offsetContainer.id = TemporaryGridID + offsetContainer.style.position = 'absolute' + offsetContainer.style.left = '0' + offsetContainer.style.top = '0' + fragment.appendChild(offsetContainer) + + // Create a grid element with the appropriate properties. + const gridElement = document.createElement('div') + gridElement.style.display = 'grid' + gridElement.style.position = gridMetadata.specialSizeMeasurements.position ?? 'initial' + const gridGlobalFrame = zeroRectIfNullOrInfinity(gridMetadata.globalFrame) + gridElement.style.left = `${gridGlobalFrame.x}px` + gridElement.style.top = `${gridGlobalFrame.y}px` + gridElement.style.width = `${gridGlobalFrame.width}px` + gridElement.style.height = `${gridGlobalFrame.height}px` + + // Gap needs to be set only if the other two are not present or we'll have rendering issues + // due to how measurements are calculated. + const hasRowGap = gridMetadata.specialSizeMeasurements.rowGap != null + if (hasRowGap) { + const rowGap = gridMetadata.specialSizeMeasurements.rowGap + gridElement.style.rowGap = `${rowGap}px` + } + const hasColumnGap = gridMetadata.specialSizeMeasurements.columnGap != null + if (hasColumnGap) { + const columnGap = gridMetadata.specialSizeMeasurements.columnGap + gridElement.style.columnGap = `${columnGap}px` + } + if (!hasColumnGap && !hasRowGap) { + const gap = gridMetadata.specialSizeMeasurements.gap + gridElement.style.gap = gap == null ? 'initial' : `${gap}px` + } + + // Include the padding. + const gridPadding = gridMetadata.specialSizeMeasurements.padding + gridElement.style.paddingLeft = gridPadding.left == null ? 'initial' : `${gridPadding.left}px` + gridElement.style.paddingTop = gridPadding.top == null ? 'initial' : `${gridPadding.top}px` + gridElement.style.paddingRight = gridPadding.right == null ? 'initial' : `${gridPadding.right}px` + gridElement.style.paddingBottom = + gridPadding.bottom == null ? 'initial' : `${gridPadding.bottom}px` + + // Keep the grid hidden from view so that it doesn't flash visibly in the editor. + gridElement.style.visibility = 'hidden' + + if (gridProperties.gridTemplateColumns != null) { + gridElement.style.gridTemplateColumns = printGridAutoOrTemplateBase( + gridProperties.gridTemplateColumns, + ) + } + if (gridProperties.gridTemplateRows != null) { + gridElement.style.gridTemplateRows = printGridAutoOrTemplateBase( + gridProperties.gridTemplateRows, + ) + } + if (gridProperties.gridAutoColumns != null) { + gridElement.style.gridAutoColumns = printGridAutoOrTemplateBase(gridProperties.gridAutoColumns) + } + if (gridProperties.gridAutoRows != null) { + gridElement.style.gridAutoRows = printGridAutoOrTemplateBase(gridProperties.gridAutoRows) + } + if (gridProperties.gridAutoFlow != null) { + gridElement.style.gridAutoFlow = gridProperties.gridAutoFlow + } + offsetContainer.appendChild(gridElement) + + function addItemToGrid(measurements: SpecialSizeMeasurements) { + // Create a child of the grid element with the appropriate properties. + const gridChildElement = document.createElement('div') + + const { elementGridProperties, position } = measurements + + if (elementGridProperties.gridColumnStart != null) { + gridChildElement.style.gridColumnStart = printPinAsString( + gridProperties, + elementGridProperties.gridColumnStart, + 'column', + ) + } + if (elementGridProperties.gridColumnEnd != null) { + gridChildElement.style.gridColumnEnd = printPinAsString( + gridProperties, + elementGridProperties.gridColumnEnd, + 'column', + ) + } + if (elementGridProperties.gridRowStart != null) { + gridChildElement.style.gridRowStart = printPinAsString( + gridProperties, + elementGridProperties.gridRowStart, + 'row', + ) + } + if (elementGridProperties.gridRowEnd != null) { + gridChildElement.style.gridRowEnd = printPinAsString( + gridProperties, + elementGridProperties.gridRowEnd, + 'row', + ) + } + gridChildElement.style.position = options?.forcePositionRelative + ? 'relative' + : position ?? 'initial' + // Fill out the entire space available. + gridChildElement.style.top = '0' + gridChildElement.style.left = '0' + gridChildElement.style.bottom = '0' + gridChildElement.style.right = '0' + + return gridChildElement + } + + // add every element into the grid, so flow elements will be correctly positioned + const createdElements = gridElements.map((element) => + addItemToGrid(element.specialSizeMeasurements), + ) + createdElements.forEach((elem) => gridElement.appendChild(elem)) + + // get the index of the generated target element + const targetIndex = gridElements.findIndex((element) => + EP.pathsEqual(element.elementPath, targetElement), + ) + if (targetIndex < 0 || targetIndex >= createdElements.length) { + // this should never happen + return localRectangle(zeroRectangle) + } + // grab the generated element via its index + const gridChildElement = createdElements[targetIndex] + + // Get the result and cleanup the temporary elements. + try { + document.body.appendChild(fragment) + const gridProvidesBounds = + gridMetadata.specialSizeMeasurements.providesBoundsForAbsoluteChildren + const boundingRect = gridChildElement.getBoundingClientRect() + // If the grid provides the bounds, then we need to remove it's position, otherwise + // we need to include its position in the offset container. + return localRectangle({ + x: boundingRect.left - (gridProvidesBounds ? gridGlobalFrame.x : 0), + y: boundingRect.top - (gridProvidesBounds ? gridGlobalFrame.y : 0), + width: boundingRect.width, + height: boundingRect.height, + }) + } finally { + const gridElementFromDocument = document.getElementById(TemporaryGridID) + if (gridElementFromDocument != null) { + gridElementFromDocument.remove() + } } } diff --git a/editor/src/components/canvas/canvas-strategies/strategies/grid-move-absolute.spec.browser2.tsx b/editor/src/components/canvas/canvas-strategies/strategies/grid-move-absolute.spec.browser2.tsx new file mode 100644 index 000000000000..f4de8c072645 --- /dev/null +++ b/editor/src/components/canvas/canvas-strategies/strategies/grid-move-absolute.spec.browser2.tsx @@ -0,0 +1,180 @@ +import * as EP from '../../../../core/shared/element-path' +import type { WindowPoint } from '../../../../core/shared/math-utils' +import { offsetPoint, windowPoint } from '../../../../core/shared/math-utils' +import type { Modifiers } from '../../../../utils/modifiers' +import { cmdModifier, emptyModifiers } from '../../../../utils/modifiers' +import { CanvasControlsContainerID } from '../../controls/new-canvas-controls' +import type { Point } from '../../event-helpers.test-utils' +import { + mouseClickAtPoint, + mouseDownAtPoint, + mouseMoveToPoint, + mouseUpAtPoint, +} from '../../event-helpers.test-utils' +import type { EditorRenderResult } from '../../ui-jsx.test-utils' +import { getPrintedUiJsCode, renderTestEditorWithCode } from '../../ui-jsx.test-utils' + +async function dragElement( + renderResult: EditorRenderResult, + targetTestId: string, + targetControlTestId: string, + dragDelta: WindowPoint, + modifiers: Modifiers, +): Promise { + const targetElement = renderResult.renderedDOM.getByTestId(targetTestId) + const targetElementBounds = targetElement.getBoundingClientRect() + const canvasControlsLayer = renderResult.renderedDOM.getByTestId(CanvasControlsContainerID) + + const startPoint = windowPoint({ x: targetElementBounds.x + 5, y: targetElementBounds.y + 5 }) + const endPoint = offsetPoint(startPoint, dragDelta) + + await mouseClickAtPoint(canvasControlsLayer, startPoint, { modifiers: cmdModifier }) + + const targetControl = renderResult.renderedDOM.getByTestId(targetControlTestId) + + await mouseDownAtPoint(targetControl, startPoint, { modifiers: modifiers }) + + const delta: Point = { + x: endPoint.x - startPoint.x, + y: endPoint.y - startPoint.y, + } + await mouseMoveToPoint( + targetControl, + { + x: endPoint.x, + y: endPoint.y, + }, + { + modifiers: modifiers, + eventOptions: { + movementX: delta.x, + movementY: delta.y, + buttons: 1, + }, + }, + ) + + await mouseUpAtPoint(canvasControlsLayer, endPoint, { + modifiers: modifiers, + }) +} + +describe('grid move absolute strategy', () => { + it('move an absolute element in a non-absolute grid', async () => { + const editor = await renderTestEditorWithCode( + `import * as React from 'react' +import { Storyboard } from 'utopia-api' + +export var storyboard = ( + +
+
+
+
+
+ +)`, + 'await-first-dom-report', + ) + + await dragElement( + editor, + 'dragme', + 'grid-cell-sb/container/grid/dragme', + windowPoint({ x: -200, y: -200 }), + emptyModifiers, + ) + + expect(getPrintedUiJsCode(editor.getEditorState())).toEqual( + `import * as React from 'react' +import { Storyboard } from 'utopia-api' + +export var storyboard = ( + +
+
+
+
+
+ +) +`, + ) + + expect(1).toEqual(1) + }) +}) diff --git a/editor/src/components/canvas/canvas-strategies/strategies/grid-move-absolute.ts b/editor/src/components/canvas/canvas-strategies/strategies/grid-move-absolute.ts new file mode 100644 index 000000000000..aa206cd67988 --- /dev/null +++ b/editor/src/components/canvas/canvas-strategies/strategies/grid-move-absolute.ts @@ -0,0 +1,395 @@ +import type { ElementPath } from 'utopia-shared/src/types' +import { MetadataUtils } from '../../../../core/model/element-metadata-utils' +import * as EP from '../../../../core/shared/element-path' +import type { + JSXElementChild, + SpecialSizeMeasurements, +} from '../../../../core/shared/element-template' +import { + isJSXElement, + type ElementInstanceMetadata, + type ElementInstanceMetadataMap, + type GridContainerProperties, +} from '../../../../core/shared/element-template' +import type { CanvasRectangle } from '../../../../core/shared/math-utils' +import { + canvasRectangle, + canvasRectangleToLocalRectangle, + isNotNullFiniteRectangle, + nullIfInfinity, + offsetRect, + rectangleContainsRectangleInclusive, + zeroRectIfNullOrInfinity, +} from '../../../../core/shared/math-utils' +import type { CanvasCommand } from '../../commands/commands' +import { showGridControls } from '../../commands/show-grid-controls-command' +import { + controlsForGridPlaceholders, + controlsForGridRulers, +} from '../../controls/grid-controls-for-strategies' +import type { CanvasStrategyFactory } from '../canvas-strategies' +import { onlyFitWhenDraggingThisControl } from '../canvas-strategies' +import type { InteractionCanvasState } from '../canvas-strategy-types' +import { + emptyStrategyApplicationResult, + getTargetPathsFromInteractionTarget, + strategyApplicationResult, +} from '../canvas-strategy-types' +import type { DragInteractionData, InteractionSession } from '../interaction-state' +import type { GridCellGlobalFrames } from './grid-helpers' +import { + findOriginalGrid, + getGridRelativeContainingBlock, + getOriginalElementGridConfiguration, + getParentGridTemplatesFromChildMeasurements, + gridMoveStrategiesExtraCommands, +} from './grid-helpers' +import type { NewGridElementProps } from './grid-change-element-location-strategy' +import { + getNewGridElementProps, + runGridChangeElementLocation, +} from './grid-change-element-location-strategy' +import { getTargetGridCellData } from '../../../inspector/grid-helpers' +import { gridItemIdentifier } from '../../../editor/store/editor-state' +import { getMoveCommandsForDrag } from './shared-move-strategies-helpers' +import { toFirst } from '../../../../core/shared/optics/optic-utilities' +import { eitherRight, fromTypeGuard } from '../../../../core/shared/optics/optic-creators' +import { defaultEither } from '../../../../core/shared/either' + +export const gridMoveAbsoluteStrategy: CanvasStrategyFactory = ( + canvasState: InteractionCanvasState, + interactionSession: InteractionSession | null, +) => { + const selectedElements = getTargetPathsFromInteractionTarget(canvasState.interactionTarget) + if ( + selectedElements.length !== 1 || + interactionSession == null || + interactionSession.interactionData.type !== 'DRAG' || + interactionSession.interactionData.drag == null || + interactionSession.activeControl.type !== 'GRID_CELL_HANDLE' || + interactionSession.interactionData.modifiers.alt + ) { + return null + } + + const selectedElement = selectedElements[0] + if (!MetadataUtils.isGridItem(canvasState.startingMetadata, selectedElement)) { + return null + } + + const selectedElementMetadata = MetadataUtils.findElementByElementPath( + canvasState.startingMetadata, + selectedElement, + ) + if (selectedElementMetadata == null) { + return null + } + const initialTemplates = getParentGridTemplatesFromChildMeasurements( + selectedElementMetadata.specialSizeMeasurements, + ) + if (initialTemplates == null) { + return null + } + + if (!MetadataUtils.isPositionAbsolute(selectedElementMetadata)) { + return null + } + + return { + id: 'absolute-grid-move-strategy', + name: 'Grid move (Abs)', + descriptiveLabel: 'Grid move (Abs)', + icon: { + category: 'tools', + type: 'pointer', + }, + controlsToRender: [ + controlsForGridPlaceholders(gridItemIdentifier(selectedElement), 'visible-only-while-active'), + controlsForGridRulers(gridItemIdentifier(selectedElement), 'visible-only-while-active'), + ], + fitness: onlyFitWhenDraggingThisControl(interactionSession, 'GRID_CELL_HANDLE', 2), + apply: () => { + if ( + interactionSession == null || + interactionSession.interactionData.type !== 'DRAG' || + interactionSession.interactionData.drag == null || + interactionSession.activeControl.type !== 'GRID_CELL_HANDLE' + ) { + return emptyStrategyApplicationResult + } + + const { commands, elementsToRerender } = getCommandsAndPatchForGridAbsoluteMove( + canvasState, + interactionSession.interactionData, + selectedElement, + ) + if (commands.length === 0) { + return emptyStrategyApplicationResult + } + + const { midInteractionCommands, onCompleteCommands } = gridMoveStrategiesExtraCommands( + EP.parentPath(selectedElement), // TODO: don't use EP.parentPath + initialTemplates, + ) + return strategyApplicationResult( + [...midInteractionCommands, ...onCompleteCommands, ...commands], + elementsToRerender, + ) + }, + } +} + +function getCommandsAndPatchForGridAbsoluteMove( + canvasState: InteractionCanvasState, + interactionData: DragInteractionData, + selectedElement: ElementPath, +): { + commands: CanvasCommand[] + elementsToRerender: ElementPath[] +} { + if (interactionData.drag == null) { + return { commands: [], elementsToRerender: [] } + } + + const selectedElementMetadata = MetadataUtils.findElementByElementPath( + canvasState.startingMetadata, + selectedElement, + ) + if (selectedElementMetadata == null) { + return { commands: [], elementsToRerender: [] } + } + + const { parentGridCellGlobalFrames, parentContainerGridProperties } = + selectedElementMetadata.specialSizeMeasurements + if (parentGridCellGlobalFrames == null) { + return { commands: [], elementsToRerender: [] } + } + + const commands = runGridMoveAbsolute( + canvasState.startingMetadata, + interactionData, + selectedElementMetadata, + parentGridCellGlobalFrames, + parentContainerGridProperties, + ) + + return { + commands: commands, + elementsToRerender: [EP.parentPath(selectedElement), selectedElement], + } +} + +export function getNewGridElementPropsCheckingOriginalGrid( + originalGridMetadata: ElementInstanceMetadata, + interactionData: DragInteractionData, + selectedElementMetadata: ElementInstanceMetadata, + gridCellGlobalFrames: GridCellGlobalFrames, + coordinateSystemBounds: CanvasRectangle, + newPathAfterReparent: ElementPath | null, +): NewGridElementProps | null { + if (interactionData.drag == null) { + return null + } + + // Identify the containing block position and size. + const originalContainingBlockRectangle = getGridRelativeContainingBlock( + originalGridMetadata, + [selectedElementMetadata], + selectedElementMetadata.elementPath, + ) + + // Capture the original position of the grid child. + const originalCanvasFrame = selectedElementMetadata.globalFrame + if (!isNotNullFiniteRectangle(originalCanvasFrame)) { + return null + } + + // Identify if the new position of the grid child is wholly inside the containing block's + // global frame. + const containingBlockGlobalFrame = offsetRect( + canvasRectangle(originalContainingBlockRectangle), + coordinateSystemBounds, + ) + const gridChildNewGlobalFrame = offsetRect(originalCanvasFrame, interactionData.drag) + const insideOriginalContainingBlock = rectangleContainsRectangleInclusive( + containingBlockGlobalFrame, + gridChildNewGlobalFrame, + ) + + // If the element is inside the containing block, + // then don't attempt to move it. + if (insideOriginalContainingBlock) { + return null + } + + return getNewGridElementProps( + interactionData, + selectedElementMetadata, + gridCellGlobalFrames, + newPathAfterReparent, + ) +} + +function runGridMoveAbsolute( + jsxMetadata: ElementInstanceMetadataMap, + interactionData: DragInteractionData, + selectedElementMetadata: ElementInstanceMetadata, + gridCellGlobalFrames: GridCellGlobalFrames, + gridTemplate: GridContainerProperties, +): CanvasCommand[] { + if (interactionData.drag == null) { + return [] + } + + const gridConfig = getOriginalElementGridConfiguration( + gridCellGlobalFrames, + interactionData, + selectedElementMetadata, + ) + if (gridConfig == null) { + return [] + } + const { mouseCellPosInOriginalElement } = gridConfig + + const targetGridCellData = getTargetGridCellData( + interactionData, + gridCellGlobalFrames, + mouseCellPosInOriginalElement, + ) + if (targetGridCellData == null) { + return [] + } + const { targetCellCoords, targetRootCell } = targetGridCellData + const element = defaultEither( + null, + toFirst( + eitherRight().compose(fromTypeGuard(isJSXElement)), + selectedElementMetadata.element, + ), + ) + if (element == null) { + return [] + } + + const globalFrame = nullIfInfinity( + MetadataUtils.getFrameInCanvasCoords(selectedElementMetadata.elementPath, jsxMetadata), + ) + + if (globalFrame == null) { + return [] + } + + const localFrame = nullIfInfinity( + MetadataUtils.getLocalFrame( + selectedElementMetadata.elementPath, + jsxMetadata, + EP.parentPath(selectedElementMetadata.elementPath), + ), + ) + + // if moving an absolutely-positioned child which does not have pinning + // props, do not set them at all. + if (MetadataUtils.hasNoGridItemPositioning(selectedElementMetadata.specialSizeMeasurements)) { + return [ + showGridControls( + 'mid-interaction', + gridItemIdentifier(selectedElementMetadata.elementPath), + targetCellCoords, + targetRootCell, + ), + ...getMoveCommandsForDrag( + zeroRectIfNullOrInfinity( + selectedElementMetadata.specialSizeMeasurements.immediateParentBounds, + ), + element, + selectedElementMetadata.elementPath, + selectedElementMetadata.elementPath, + interactionData.drag, + globalFrame, + localFrame, + null, + false, + ).commands, + ] + } + + // Get the metadata of the original grid. + const originalGridPath = findOriginalGrid( + jsxMetadata, + EP.parentPath(selectedElementMetadata.elementPath), + ) + if (originalGridPath == null) { + return [] + } + const originalGrid = MetadataUtils.findElementByElementPath(jsxMetadata, originalGridPath) + if (originalGrid == null) { + return [] + } + + const coordinateSystemBounds = + selectedElementMetadata.specialSizeMeasurements.immediateParentBounds + if (coordinateSystemBounds == null) { + return [] + } + + // The element may be moving to a different grid position, which is then used + // to calculate the potentially new containing block. + const newGridElementProps = getNewGridElementPropsCheckingOriginalGrid( + originalGrid, + interactionData, + selectedElementMetadata, + gridCellGlobalFrames, + coordinateSystemBounds, + null, + ) + + // Get the containing block of the grid child. + const patchedSpecialSizeMeasurements: SpecialSizeMeasurements = { + ...selectedElementMetadata.specialSizeMeasurements, + elementGridProperties: + newGridElementProps?.gridElementProperties ?? + selectedElementMetadata.specialSizeMeasurements.elementGridProperties, + } + const containingBlockRectangle = getGridRelativeContainingBlock( + originalGrid, + [ + { + ...selectedElementMetadata, + specialSizeMeasurements: patchedSpecialSizeMeasurements, + }, + ], + selectedElementMetadata.elementPath, + ) + + // Get the appropriately shifted and typed local frame value to use. + const containingRect = offsetRect( + canvasRectangle(containingBlockRectangle), + coordinateSystemBounds, + ) + const adjustedLocalFrame = canvasRectangleToLocalRectangle(globalFrame, containingRect) + + // otherwise, return a change location + absolute adjustment + return [ + ...(newGridElementProps == null + ? [] + : runGridChangeElementLocation( + jsxMetadata, + interactionData, + selectedElementMetadata, + gridCellGlobalFrames, + gridTemplate, + null, + )), + ...getMoveCommandsForDrag( + containingRect, + element, + selectedElementMetadata.elementPath, + selectedElementMetadata.elementPath, + interactionData.drag, + globalFrame, + adjustedLocalFrame, + null, + false, + ).commands, + ] +} diff --git a/editor/src/components/canvas/canvas-strategies/strategies/grid-rearrange-move-strategy.spec.browser2.tsx b/editor/src/components/canvas/canvas-strategies/strategies/grid-rearrange-move-strategy.spec.browser2.tsx deleted file mode 100644 index 6326d5d74cb2..000000000000 --- a/editor/src/components/canvas/canvas-strategies/strategies/grid-rearrange-move-strategy.spec.browser2.tsx +++ /dev/null @@ -1,489 +0,0 @@ -import * as EP from '../../../../core/shared/element-path' -import { - getRectCenter, - localRectangle, - offsetPoint, - windowPoint, -} from '../../../../core/shared/math-utils' -import { selectComponentsForTest, wait } from '../../../../utils/utils.test-utils' -import CanvasActions from '../../canvas-actions' -import { GridCellTestId } from '../../controls/grid-controls' -import { mouseDragFromPointToPoint } from '../../event-helpers.test-utils' -import type { EditorRenderResult } from '../../ui-jsx.test-utils' -import { renderTestEditorWithCode } from '../../ui-jsx.test-utils' -import { gridCellTargetId } from './grid-helpers' - -describe('grid rearrange move strategy', () => { - it('can rearrange elements on a grid', async () => { - const editor = await renderTestEditorWithCode(ProjectCode, 'await-first-dom-report') - - const testId = 'aaa' - const { gridRowStart, gridRowEnd, gridColumnStart, gridColumnEnd } = await runMoveTest(editor, { - scale: 1, - pathString: `sb/scene/grid/${testId}`, - testId: testId, - }) - expect({ gridRowStart, gridRowEnd, gridColumnStart, gridColumnEnd }).toEqual({ - gridColumnEnd: '7', - gridColumnStart: '3', - gridRowEnd: '4', - gridRowStart: '2', - }) - }) - - it('can rearrange element with no explicit grid props set', async () => { - const editor = await renderTestEditorWithCode(ProjectCode, 'await-first-dom-report') - - const testId = 'bbb' - const { gridRowStart, gridRowEnd, gridColumnStart, gridColumnEnd } = await runMoveTest(editor, { - scale: 1, - pathString: `sb/scene/grid/${testId}`, - testId: testId, - }) - expect({ gridRowStart, gridRowEnd, gridColumnStart, gridColumnEnd }).toEqual({ - gridColumnEnd: 'auto', - gridColumnStart: '3', - gridRowEnd: 'auto', - gridRowStart: '2', - }) - }) - - it('can rearrange elements on a grid (zoom out)', async () => { - const editor = await renderTestEditorWithCode(ProjectCode, 'await-first-dom-report') - - const testId = 'aaa' - const { gridRowStart, gridRowEnd, gridColumnStart, gridColumnEnd } = await runMoveTest(editor, { - scale: 0.5, - pathString: `sb/scene/grid/${testId}`, - testId: testId, - }) - expect({ gridRowStart, gridRowEnd, gridColumnStart, gridColumnEnd }).toEqual({ - gridColumnEnd: '7', - gridColumnStart: '3', - gridRowEnd: '4', - gridRowStart: '2', - }) - }) - - it('can rearrange elements on a grid (zoom in)', async () => { - const editor = await renderTestEditorWithCode(ProjectCode, 'await-first-dom-report') - - const testId = 'aaa' - const { gridRowStart, gridRowEnd, gridColumnStart, gridColumnEnd } = await runMoveTest(editor, { - scale: 2, - pathString: `sb/scene/grid/${testId}`, - testId: testId, - }) - expect({ gridRowStart, gridRowEnd, gridColumnStart, gridColumnEnd }).toEqual({ - gridColumnEnd: '7', - gridColumnStart: '3', - gridRowEnd: '4', - gridRowStart: '2', - }) - }) - - describe('grids within grids', () => { - it('can move a grid child that is a grid itself', async () => { - const editor = await renderTestEditorWithCode( - `import * as React from 'react' -import { Storyboard } from 'utopia-api' - -export var storyboard = ( - -
-
-
-
-
-
-
- -) -`, - 'await-first-dom-report', - ) - - const testId = 'grid-inside-grid' - const elementPathToDrag = EP.fromString(`sb/grid/${testId}`) - - await selectComponentsForTest(editor, [elementPathToDrag]) - - const sourceGridCell = editor.renderedDOM.getByTestId(GridCellTestId(elementPathToDrag)) - const targetGridCell = editor.renderedDOM.getByTestId( - gridCellTargetId(EP.fromString('sb/grid'), 2, 3), - ) - - const sourceRect = sourceGridCell.getBoundingClientRect() - const targetRect = targetGridCell.getBoundingClientRect() - - await mouseDragFromPointToPoint( - sourceGridCell, - { - x: sourceRect.x + 10, - y: sourceRect.y + 10, - }, - getRectCenter( - localRectangle({ - x: targetRect.x, - y: targetRect.y, - width: targetRect.width, - height: targetRect.height, - }), - ), - ) - - const { gridRowStart, gridRowEnd, gridColumnStart, gridColumnEnd } = - editor.renderedDOM.getByTestId(testId).style - - expect({ gridRowStart, gridRowEnd, gridColumnStart, gridColumnEnd }).toEqual({ - gridColumnEnd: 'auto', - gridColumnStart: '3', - gridRowEnd: 'auto', - gridRowStart: '2', - }) - }) - }) - - describe('absolute move within grid', () => { - const ProjectCode = `import { Scene, Storyboard } from 'utopia-api' -export var storyboard = ( - - -
-
-
- - -) -` - it('can move absolute element inside a grid cell', async () => { - const editor = await renderTestEditorWithCode(ProjectCode, 'await-first-dom-report') - - const child = editor.renderedDOM.getByTestId('child') - - { - const { top, left, gridColumn, gridRow } = child.style - expect({ top, left, gridColumn, gridRow }).toEqual({ - gridColumn: '1', - gridRow: '1', - left: '12px', - top: '16px', - }) - } - - await selectComponentsForTest(editor, [EP.fromString('sb/scene/grid/child')]) - - const childBounds = child.getBoundingClientRect() - const childCenter = windowPoint({ - x: Math.floor(childBounds.left + childBounds.width / 2), - y: Math.floor(childBounds.top + childBounds.height / 2), - }) - - await mouseDragFromPointToPoint( - editor.renderedDOM.getByTestId(GridCellTestId(EP.fromString('sb/scene/grid/child'))), - childCenter, - offsetPoint(childCenter, windowPoint({ x: 20, y: 20 })), - ) - - { - const { top, left, gridColumn, gridRow } = child.style - expect({ top, left, gridColumn, gridRow }).toEqual({ - gridColumn: '1', - gridRow: '1', - left: '36px', - top: '40px', - }) - } - }) - - it('can move absolute element inside a grid cell, zoomed in', async () => { - const editor = await renderTestEditorWithCode(ProjectCode, 'await-first-dom-report') - - await editor.dispatch([CanvasActions.zoom(2)], true) - - const child = editor.renderedDOM.getByTestId('child') - - { - const { top, left, gridColumn, gridRow } = child.style - expect({ top, left, gridColumn, gridRow }).toEqual({ - gridColumn: '1', - gridRow: '1', - left: '12px', - top: '16px', - }) - } - - await selectComponentsForTest(editor, [EP.fromString('sb/scene/grid/child')]) - - const childBounds = child.getBoundingClientRect() - const childCenter = windowPoint({ - x: Math.floor(childBounds.left + childBounds.width / 2), - y: Math.floor(childBounds.top + childBounds.height / 2), - }) - - await mouseDragFromPointToPoint( - editor.renderedDOM.getByTestId(GridCellTestId(EP.fromString('sb/scene/grid/child'))), - childCenter, - offsetPoint(childCenter, windowPoint({ x: 240, y: 240 })), - ) - - { - const { top, left, gridColumn, gridRow } = child.style - expect({ top, left, gridColumn, gridRow }).toEqual({ - gridColumn: '1', - gridRow: '1', - left: '136.5px', - top: '140.5px', - }) - } - }) - - it('can move absolute element among grid cells', async () => { - const editor = await renderTestEditorWithCode(ProjectCode, 'await-first-dom-report') - - const child = editor.renderedDOM.getByTestId('child') - - { - const { top, left, gridColumn, gridRow } = child.style - expect({ top, left, gridColumn, gridRow }).toEqual({ - gridColumn: '1', - gridRow: '1', - left: '12px', - top: '16px', - }) - } - - await selectComponentsForTest(editor, [EP.fromString('sb/scene/grid/child')]) - - const childBounds = child.getBoundingClientRect() - const childCenter = windowPoint({ - x: Math.floor(childBounds.left + childBounds.width / 2), - y: Math.floor(childBounds.top + childBounds.height / 2), - }) - - await mouseDragFromPointToPoint( - editor.renderedDOM.getByTestId(GridCellTestId(EP.fromString('sb/scene/grid/child'))), - childCenter, - offsetPoint(childCenter, windowPoint({ x: 240, y: 240 })), - ) - - { - const { top, left, gridColumn, gridRow } = child.style - expect({ top, left, gridColumn, gridRow }).toEqual({ - gridColumn: '2', - gridRow: '2', - left: '25.5px', - top: '36px', - }) - } - }) - }) -}) - -async function runMoveTest( - editor: EditorRenderResult, - props: { scale: number; pathString: string; testId: string }, -) { - const elementPathToDrag = EP.fromString(props.pathString) - - await selectComponentsForTest(editor, [elementPathToDrag]) - - await editor.dispatch([CanvasActions.zoom(props.scale)], true) - - const sourceGridCell = editor.renderedDOM.getByTestId(GridCellTestId(elementPathToDrag)) - const targetGridCell = editor.renderedDOM.getByTestId( - gridCellTargetId(EP.fromString('sb/scene/grid'), 2, 3), - ) - - const sourceRect = sourceGridCell.getBoundingClientRect() - const targetRect = targetGridCell.getBoundingClientRect() - - await mouseDragFromPointToPoint( - sourceGridCell, - { - x: sourceRect.x + 10, - y: sourceRect.y + 10, - }, - getRectCenter( - localRectangle({ - x: targetRect.x, - y: targetRect.y, - width: targetRect.width, - height: targetRect.height, - }), - ), - ) - - return editor.renderedDOM.getByTestId(props.testId).style -} - -const ProjectCode = `import * as React from 'react' -import { Scene, Storyboard, Placeholder } from 'utopia-api' - -export var storyboard = ( - - -
-
-
- - -
- - -) -` diff --git a/editor/src/components/canvas/canvas-strategies/strategies/grid-rearrange-move-strategy.ts b/editor/src/components/canvas/canvas-strategies/strategies/grid-rearrange-move-strategy.ts deleted file mode 100644 index 193d7b4566ba..000000000000 --- a/editor/src/components/canvas/canvas-strategies/strategies/grid-rearrange-move-strategy.ts +++ /dev/null @@ -1,422 +0,0 @@ -import type { ElementPath } from 'utopia-shared/src/types' -import { MetadataUtils } from '../../../../core/model/element-metadata-utils' -import * as EP from '../../../../core/shared/element-path' -import { GridControls, GridControlsKey } from '../../controls/grid-controls' -import type { - ElementInstanceMetadataMap, - GridAutoOrTemplateBase, -} from '../../../../core/shared/element-template' -import * as PP from '../../../../core/shared/property-path' -import { printGridAutoOrTemplateBase } from '../../../inspector/common/css-utils' -import type { PropertyToUpdate } from '../../commands/set-property-command' -import { - propertyToDelete, - propertyToSet, - updateBulkProperties, -} from '../../commands/set-property-command' -import type { CanvasStrategyFactory } from '../canvas-strategies' -import { onlyFitWhenDraggingThisControl } from '../canvas-strategies' -import type { - ControlWithProps, - CustomStrategyState, - CustomStrategyStatePatch, - InteractionCanvasState, - InteractionLifecycle, -} from '../canvas-strategy-types' -import { - getTargetPathsFromInteractionTarget, - emptyStrategyApplicationResult, - strategyApplicationResult, -} from '../canvas-strategy-types' -import type { DragInteractionData, InteractionSession } from '../interaction-state' -import { runGridRearrangeMove } from './grid-helpers' -import type { CanvasRectangle } from '../../../../core/shared/math-utils' -import { isInfinityRectangle, offsetPoint } from '../../../../core/shared/math-utils' -import { findReparentStrategies } from './reparent-helpers/reparent-strategy-helpers' -import { applyAbsoluteReparent, controlsForAbsoluteReparent } from './absolute-reparent-strategy' -import type { CanvasCommand } from '../../commands/commands' -import { applyStaticReparent, controlsForStaticReparent } from './reparent-as-static-strategy' -import type { FindReparentStrategyResult } from './reparent-helpers/reparent-strategy-parent-lookup' -import { applyGridReparent, controlsForGridReparent } from './grid-reparent-strategy' -import { assertNever } from '../../../../core/shared/utils' - -export const gridRearrangeMoveStrategy: CanvasStrategyFactory = ( - canvasState: InteractionCanvasState, - interactionSession: InteractionSession | null, - customState: CustomStrategyState, -) => { - const selectedElements = getTargetPathsFromInteractionTarget(canvasState.interactionTarget) - if ( - selectedElements.length === 0 || - interactionSession == null || - interactionSession.interactionData.type !== 'DRAG' || - interactionSession.interactionData.drag == null || - interactionSession.activeControl.type !== 'GRID_CELL_HANDLE' || - interactionSession.interactionData.modifiers.alt - ) { - return null - } - - const selectedElement = selectedElements[0] - if (!MetadataUtils.isGridCell(canvasState.startingMetadata, selectedElement)) { - return null - } - - const parentGridPath = EP.parentPath(selectedElement) - const gridFrame = MetadataUtils.findElementByElementPath( - canvasState.startingMetadata, - parentGridPath, - )?.globalFrame - if (gridFrame == null || isInfinityRectangle(gridFrame)) { - return null - } - - const initialTemplates = getGridTemplates(canvasState.startingMetadata, parentGridPath) - if (initialTemplates == null) { - return null - } - - const strategyToApply = getStrategyToApply( - canvasState, - interactionSession.interactionData, - parentGridPath, - ) - if (strategyToApply == null) { - return null - } - - return { - id: 'rearrange-grid-move-strategy', - name: strategyToApply.name, - descriptiveLabel: strategyToApply.name, - icon: { - category: 'tools', - type: 'pointer', - }, - controlsToRender: strategyToApply.controlsToRender, - fitness: onlyFitWhenDraggingThisControl(interactionSession, 'GRID_CELL_HANDLE', 2), - apply: (strategyLifecycle) => { - if ( - interactionSession == null || - interactionSession.interactionData.type !== 'DRAG' || - interactionSession.interactionData.drag == null || - interactionSession.activeControl.type !== 'GRID_CELL_HANDLE' - ) { - return emptyStrategyApplicationResult - } - - const midInteractionCommands = [ - // during the interaction, freeze the template with the calculated values… - updateBulkProperties('mid-interaction', parentGridPath, [ - propertyToSet( - PP.create('style', 'gridTemplateColumns'), - printGridAutoOrTemplateBase(initialTemplates.calculated.columns), - ), - propertyToSet( - PP.create('style', 'gridTemplateRows'), - printGridAutoOrTemplateBase(initialTemplates.calculated.rows), - ), - ]), - ] - - const onCompleteCommands = [ - // …eventually, restore the grid template on complete. - updateBulkProperties( - 'on-complete', - parentGridPath, - restoreGridTemplateFromProps(initialTemplates.fromProps), - ), - ] - - const { commands, patch } = - strategyToApply.type === 'GRID_REARRANGE' - ? getCommandsAndPatchForGridRearrange( - canvasState, - interactionSession.interactionData, - customState, - selectedElement, - ) - : getCommandsAndPatchForReparent( - strategyToApply.strategy, - canvasState, - interactionSession.interactionData, - interactionSession, - customState, - selectedElement, - strategyLifecycle, - gridFrame, - ) - - if (commands.length === 0) { - return emptyStrategyApplicationResult - } - - return strategyApplicationResult( - [...midInteractionCommands, ...onCompleteCommands, ...commands], - patch, - ) - }, - } -} - -function getCommandsAndPatchForGridRearrange( - canvasState: InteractionCanvasState, - interactionData: DragInteractionData, - customState: CustomStrategyState, - selectedElement: ElementPath, -): { commands: CanvasCommand[]; patch: CustomStrategyStatePatch } { - if (interactionData.drag == null) { - return { commands: [], patch: {} } - } - - const { - commands, - targetCell: targetGridCell, - draggingFromCell, - originalRootCell, - targetRootCell, - } = runGridRearrangeMove( - selectedElement, - selectedElement, - canvasState.startingMetadata, - interactionData, - canvasState.scale, - canvasState.canvasOffset, - customState.grid, - false, - ) - - return { - commands: commands, - patch: { - grid: { - targetCellData: targetGridCell, - draggingFromCell: draggingFromCell, - originalRootCell: originalRootCell, - currentRootCell: targetRootCell, - }, - }, - } -} - -function getCommandsAndPatchForReparent( - strategy: FindReparentStrategyResult, - canvasState: InteractionCanvasState, - interactionData: DragInteractionData, - interactionSession: InteractionSession, - customState: CustomStrategyState, - targetElement: ElementPath, - strategyLifecycle: InteractionLifecycle, - gridFrame: CanvasRectangle, -): { commands: CanvasCommand[]; patch: CustomStrategyStatePatch } { - if (interactionData.drag == null) { - return { commands: [], patch: {} } - } - - function applyReparent() { - switch (strategy.strategy) { - case 'REPARENT_AS_ABSOLUTE': - return applyAbsoluteReparent( - canvasState, - interactionSession, - customState, - strategy.target, - [targetElement], - )(strategyLifecycle) - case 'REPARENT_AS_STATIC': - return applyStaticReparent(canvasState, interactionSession, customState, strategy.target) - case 'REPARENT_INTO_GRID': - return applyGridReparent( - canvasState, - interactionData, - customState, - strategy.target, - [targetElement], - gridFrame, - )() - default: - assertNever(strategy.strategy) - } - } - const result = applyReparent() - - let commands: CanvasCommand[] = [] - - const frame = MetadataUtils.getFrameOrZeroRect(targetElement, canvasState.startingMetadata) - - if (strategy.strategy === 'REPARENT_AS_ABSOLUTE') { - // for absolute reparents, set positioning and size props - commands.push( - updateBulkProperties('always', targetElement, [ - propertyToSet(PP.create('style', 'position'), 'absolute'), - propertyToSet(PP.create('style', 'top'), frame.y + interactionData.drag.y), - propertyToSet(PP.create('style', 'left'), frame.x + interactionData.drag.x), - propertyToSet(PP.create('style', 'width'), frame.width), - propertyToSet(PP.create('style', 'height'), frame.height), - ]), - ) - } else if (strategy.strategy === 'REPARENT_AS_STATIC') { - // for static reparents, set size props - commands.push( - updateBulkProperties('always', targetElement, [ - propertyToSet(PP.create('style', 'width'), frame.width), - propertyToSet(PP.create('style', 'height'), frame.height), - ]), - ) - } - - // for absolute, static, or non-same-grid reparents, remove cell placement props - if ( - strategy.strategy !== 'REPARENT_INTO_GRID' || - !EP.pathsEqual(strategy.target.newParent.intendedParentPath, EP.parentPath(targetElement)) - ) { - commands.push( - updateBulkProperties('on-complete', targetElement, [ - propertyToDelete(PP.create('style', 'gridRow')), - propertyToDelete(PP.create('style', 'gridColumn')), - ]), - ) - } - - commands.push(...result.commands) - - return { - commands: commands, - patch: result.customStatePatch, - } -} - -function restoreGridTemplateFromProps(params: { - columns: GridAutoOrTemplateBase - rows: GridAutoOrTemplateBase -}): PropertyToUpdate[] { - let properties: PropertyToUpdate[] = [] - const newCols = printGridAutoOrTemplateBase(params.columns) - const newRows = printGridAutoOrTemplateBase(params.rows) - if (newCols === '') { - properties.push(propertyToDelete(PP.create('style', 'gridTemplateColumns'))) - } else { - properties.push(propertyToSet(PP.create('style', 'gridTemplateColumns'), newCols)) - } - if (newRows === '') { - properties.push(propertyToDelete(PP.create('style', 'gridTemplateRows'))) - } else { - properties.push(propertyToSet(PP.create('style', 'gridTemplateRows'), newRows)) - } - return properties -} - -function getGridTemplates(jsxMetadata: ElementInstanceMetadataMap, gridPath: ElementPath) { - const grid = MetadataUtils.findElementByElementPath(jsxMetadata, gridPath) - if (grid == null) { - return null - } - - const templateFromProps = grid.specialSizeMeasurements.containerGridPropertiesFromProps - const templateRowsFromProps = templateFromProps.gridTemplateRows - if (templateRowsFromProps == null) { - return null - } - const templateColsFromProps = templateFromProps.gridTemplateColumns - if (templateColsFromProps == null) { - return null - } - - const templateCalculated = grid.specialSizeMeasurements.containerGridProperties - const templateRowsCalculated = templateCalculated.gridTemplateRows - if (templateRowsCalculated == null) { - return null - } - const templateColsCalculated = templateCalculated.gridTemplateColumns - if (templateColsCalculated == null) { - return null - } - - return { - calculated: { - columns: templateColsCalculated, - rows: templateRowsCalculated, - }, - fromProps: { - columns: templateColsFromProps, - rows: templateRowsFromProps, - }, - } -} - -type StrategyToApply = - | { - type: 'GRID_REARRANGE' - controlsToRender: ControlWithProps[] - name: string - } - | { - type: 'REPARENT' - controlsToRender: ControlWithProps[] - name: string - strategy: FindReparentStrategyResult - } - -function getStrategyToApply( - canvasState: InteractionCanvasState, - interactionData: DragInteractionData, - parentGridPath: ElementPath, -): StrategyToApply | null { - if (interactionData.drag == null) { - return null - } - - const shouldReparent = interactionData.modifiers.cmd - if (shouldReparent) { - const pointOnCanvas = offsetPoint(interactionData.originalDragStart, interactionData.drag) - const reparentStrategies = findReparentStrategies( - canvasState, - true, - pointOnCanvas, - 'allow-smaller-parent', - ) - - const strategy = reparentStrategies[0] - if (strategy != null) { - switch (strategy.strategy) { - case 'REPARENT_AS_ABSOLUTE': - return { - type: 'REPARENT', - name: 'Reparent (Abs)', - controlsToRender: controlsForAbsoluteReparent(strategy.target), - strategy: strategy, - } - case 'REPARENT_AS_STATIC': - return { - type: 'REPARENT', - name: 'Reparent (Flex)', - controlsToRender: controlsForStaticReparent(strategy.target), - strategy: strategy, - } - case 'REPARENT_INTO_GRID': - return { - type: 'REPARENT', - name: 'Reparent (Grid)', - controlsToRender: controlsForGridReparent(strategy.target), - strategy: strategy, - } - default: - assertNever(strategy.strategy) - } - } - } - - return { - type: 'GRID_REARRANGE', - name: 'Rearrange Grid (Move)', - controlsToRender: [ - { - control: GridControls, - props: { targets: [parentGridPath] }, - key: GridControlsKey(parentGridPath), - show: 'always-visible', - priority: 'bottom', - }, - ], - } -} diff --git a/editor/src/components/canvas/canvas-strategies/strategies/grid-reorder-strategy.spec.browser2.tsx b/editor/src/components/canvas/canvas-strategies/strategies/grid-reorder-strategy.spec.browser2.tsx new file mode 100644 index 000000000000..30699b6b2d84 --- /dev/null +++ b/editor/src/components/canvas/canvas-strategies/strategies/grid-reorder-strategy.spec.browser2.tsx @@ -0,0 +1,699 @@ +import { MetadataUtils } from '../../../../core/model/element-metadata-utils' +import { isLeft } from '../../../../core/shared/either' +import { isJSXElement } from '../../../../core/shared/element-template' +import type { EditorRenderResult } from '../../ui-jsx.test-utils' +import { renderTestEditorWithCode } from '../../ui-jsx.test-utils' +import type { GridCellCoordinates } from './grid-cell-bounds' +import { runGridMoveTest } from './grid.test-utils' +import * as EP from '../../../../core/shared/element-path' +import { selectComponentsForTest } from '../../../../utils/utils.test-utils' + +describe('grid reorder', () => { + it('reorders an element without setting positioning (inside contiguous area)', async () => { + const editor = await renderTestEditorWithCode(ProjectCodeReorder, 'await-first-dom-report') + const { gridRowStart, gridRowEnd, gridColumnStart, gridColumnEnd, cells } = + await runReorderTest(editor, 'sb/scene/grid', 'orange', { row: 1, column: 3 }) + + expect({ gridRowStart, gridRowEnd, gridColumnStart, gridColumnEnd }).toEqual({ + gridColumnEnd: '', + gridColumnStart: '', + gridRowEnd: '', + gridRowStart: '', + }) + + expect(cells).toEqual(['pink', 'cyan', 'orange', 'blue']) + }) + it('reorders an element in a grid component without setting positioning (inside contiguous area)', async () => { + const editor = await renderTestEditorWithCode( + ProjectCodeReorderGridComponent, + 'await-first-dom-report', + ) + const { gridRowStart, gridRowEnd, gridColumnStart, gridColumnEnd, cells } = + await runReorderTest(editor, 'sb/scene/grid', 'orange', { row: 1, column: 3 }) + + expect({ gridRowStart, gridRowEnd, gridColumnStart, gridColumnEnd }).toEqual({ + gridColumnEnd: '', + gridColumnStart: '', + gridRowEnd: '', + gridRowStart: '', + }) + + expect(cells).toEqual(['pink', 'cyan', 'orange', 'blue']) + }) + it('reorders a component (which does not take style props) inside contiguous area', async () => { + const editor = await renderTestEditorWithCode( + ProjectCodeReorderWithComponentItem, + 'await-first-dom-report', + ) + const { gridRowStart, gridRowEnd, gridColumnStart, gridColumnEnd, cells } = + await runReorderTest(editor, 'sb/scene/grid', 'orange', { row: 1, column: 3 }) + + expect({ gridRowStart, gridRowEnd, gridColumnStart, gridColumnEnd }).toEqual({ + gridColumnEnd: '', + gridColumnStart: '', + gridRowEnd: '', + gridRowStart: '', + }) + + expect(cells).toEqual(['pink', 'cyan', 'orange', 'blue']) + }) + it('reorders an element without setting positioning (edge of contiguous area)', async () => { + const editor = await renderTestEditorWithCode(ProjectCodeReorder, 'await-first-dom-report') + + const { gridRowStart, gridRowEnd, gridColumnStart, gridColumnEnd, cells } = + await runReorderTest(editor, 'sb/scene/grid', 'orange', { row: 2, column: 1 }) + + expect({ gridRowStart, gridRowEnd, gridColumnStart, gridColumnEnd }).toEqual({ + gridColumnEnd: '', + gridColumnStart: '', + gridRowEnd: '', + gridRowStart: '', + }) + + expect(cells).toEqual(['pink', 'cyan', 'blue', 'orange']) + }) + it('reorders an element setting positioning when outside of contiguous area', async () => { + const editor = await renderTestEditorWithCode(ProjectCodeReorder, 'await-first-dom-report') + + const { gridRowStart, gridRowEnd, gridColumnStart, gridColumnEnd, cells } = + await runReorderTest(editor, 'sb/scene/grid', 'orange', { row: 2, column: 2 }, { tab: true }) + + expect({ gridRowStart, gridRowEnd, gridColumnStart, gridColumnEnd }).toEqual({ + gridColumnEnd: 'auto', + gridColumnStart: '2', + gridRowEnd: 'auto', + gridRowStart: '2', + }) + + expect(cells).toEqual(['pink', 'cyan', 'blue', 'orange']) + }) + it('reorders an element setting positioning also relative to other fixed elements', async () => { + const editor = await renderTestEditorWithCode(ProjectCodeReorder, 'await-first-dom-report') + + const first = await runReorderTest( + editor, + 'sb/scene/grid', + 'orange', + { row: 2, column: 2 }, + { tab: true }, + ) + + expect({ + gridRowStart: first.gridRowStart, + gridRowEnd: first.gridRowEnd, + gridColumnStart: first.gridColumnStart, + gridColumnEnd: first.gridColumnEnd, + }).toEqual({ + gridColumnEnd: 'auto', + gridColumnStart: '2', + gridRowEnd: 'auto', + gridRowStart: '2', + }) + + expect(first.cells).toEqual(['pink', 'cyan', 'blue', 'orange']) + + await selectComponentsForTest(editor, []) + + const second = await runReorderTest( + editor, + 'sb/scene/grid', + 'pink', + { row: 2, column: 3 }, + { tab: true }, + ) + + expect({ + gridRowStart: second.gridRowStart, + gridRowEnd: second.gridRowEnd, + gridColumnStart: second.gridColumnStart, + gridColumnEnd: second.gridColumnEnd, + }).toEqual({ + gridColumnEnd: 'auto', + gridColumnStart: '3', + gridRowEnd: 'auto', + gridRowStart: '2', + }) + + expect(second.cells).toEqual(['cyan', 'blue', 'orange', 'pink']) + }) + + it('reorders and removes positioning when moving back to contiguous', async () => { + const editor = await renderTestEditorWithCode(ProjectCodeReorder, 'await-first-dom-report') + + const first = await runReorderTest( + editor, + 'sb/scene/grid', + 'orange', + { row: 2, column: 2 }, + { tab: true }, + ) + expect({ + gridRowStart: first.gridRowStart, + gridRowEnd: first.gridRowEnd, + gridColumnStart: first.gridColumnStart, + gridColumnEnd: first.gridColumnEnd, + }).toEqual({ + gridColumnEnd: 'auto', + gridColumnStart: '2', + gridRowEnd: 'auto', + gridRowStart: '2', + }) + + expect(first.cells).toEqual(['pink', 'cyan', 'blue', 'orange']) + + const second = await runReorderTest( + editor, + 'sb/scene/grid', + 'orange', + { row: 1, column: 1 }, + { tab: true }, + ) + + expect({ + gridRowStart: second.gridRowStart, + gridRowEnd: second.gridRowEnd, + gridColumnStart: second.gridColumnStart, + gridColumnEnd: second.gridColumnEnd, + }).toEqual({ + gridColumnEnd: '', + gridColumnStart: '', + gridRowEnd: '', + gridRowStart: '', + }) + + expect(second.cells).toEqual(['orange', 'pink', 'cyan', 'blue']) + }) + + it('reorders when element occupies multiple cells', async () => { + const editor = await renderTestEditorWithCode( + ProjectCodeReorderWithMultiCellChild, + 'await-first-dom-report', + ) + + const result = await runReorderTest( + editor, + 'sb/scene/grid', + 'orange', + { row: 1, column: 1 }, + { tab: true }, + ) + + expect({ + gridRowStart: result.gridRowStart, + gridRowEnd: result.gridRowEnd, + gridColumnStart: result.gridColumnStart, + gridColumnEnd: result.gridColumnEnd, + }).toEqual({ + gridColumnEnd: 'auto', + gridColumnStart: 'span 2', + gridRowEnd: '', + gridRowStart: '', + }) + + expect(result.cells).toEqual(['orange', 'pink', 'cyan', 'blue']) + }) + + it('reordering a spanning element keeps its size', async () => { + const editor = await renderTestEditorWithCode( + ProjectCodeReorderWithSpanningChild, + 'await-first-dom-report', + ) + + const result = await runReorderTest( + editor, + 'sb/scene/grid', + 'orange', + { row: 3, column: 2 }, + { tab: true }, + ) + + expect({ + gridRowStart: result.gridRowStart, + gridRowEnd: result.gridRowEnd, + gridColumnStart: result.gridColumnStart, + gridColumnEnd: result.gridColumnEnd, + }).toEqual({ + gridColumnEnd: 'auto', + gridColumnStart: 'span 3', + gridRowEnd: '', + gridRowStart: '', + }) + + expect(result.cells).toEqual(['pink', 'cyan', 'blue', 'orange']) + }) +}) + +async function runReorderTest( + editor: EditorRenderResult, + gridPath: string, + testId: string, + targetCell: GridCellCoordinates, + options?: { tab?: boolean }, +) { + const { gridRowStart, gridRowEnd, gridColumnStart, gridColumnEnd } = await runGridMoveTest( + editor, + { + scale: 1, + gridPath: gridPath, + testId: testId, + targetCell: targetCell, + tab: options?.tab, + }, + ) + + const grid = editor.getEditorState().editor.jsxMetadata[gridPath] + if (isLeft(grid.element) || !isJSXElement(grid.element.value)) { + throw new Error('expected jsx element') + } + + const cells = MetadataUtils.getChildrenOrdered( + editor.getEditorState().editor.jsxMetadata, + editor.getEditorState().editor.elementPathTree, + EP.fromString(gridPath), + ) + + return { + gridRowStart: gridRowStart, + gridRowEnd: gridRowEnd, + gridColumnStart: gridColumnStart, + gridColumnEnd: gridColumnEnd, + cells: cells.map((c) => EP.toUid(c.elementPath)), + } +} + +const ProjectCodeReorder = `import * as React from 'react' +import { Scene, Storyboard } from 'utopia-api' + +export var storyboard = ( + + +
+
+
+
+
+
+ + +) +` + +const ProjectCodeReorderGridComponent = `import * as React from 'react' +import { Scene, Storyboard } from 'utopia-api' + +export var storyboard = ( + + + +
+
+
+
+ + + +) + +export function Grid(props) { + return ( +
+
+ {props.children} +
+
+
+ ) +} +` + +const ProjectCodeReorderWithComponentItem = `import * as React from 'react' +import { Scene, Storyboard } from 'utopia-api' + +export var storyboard = ( + + +
+ + + + +
+
+
+) + +export function Item(props) { + return ( +
+ ) +} +` + +const ProjectCodeReorderWithMultiCellChild = `import * as React from 'react' +import { Scene, Storyboard } from 'utopia-api' + +export var storyboard = ( + + +
+
+
+
+
+
+ + +) +` + +const ProjectCodeReorderWithSpanningChild = `import * as React from 'react' +import { Scene, Storyboard } from 'utopia-api' + +export var storyboard = ( + + +
+
+
+
+
+
+ + +) +` diff --git a/editor/src/components/canvas/canvas-strategies/strategies/grid-reorder-strategy.ts b/editor/src/components/canvas/canvas-strategies/strategies/grid-reorder-strategy.ts new file mode 100644 index 000000000000..52e38352c41f --- /dev/null +++ b/editor/src/components/canvas/canvas-strategies/strategies/grid-reorder-strategy.ts @@ -0,0 +1,264 @@ +import type { ElementPath } from 'utopia-shared/src/types' +import { MetadataUtils } from '../../../../core/model/element-metadata-utils' +import * as EP from '../../../../core/shared/element-path' +import { + type ElementInstanceMetadata, + type ElementInstanceMetadataMap, + type GridContainerProperties, +} from '../../../../core/shared/element-template' +import * as PP from '../../../../core/shared/property-path' +import { absolute } from '../../../../utils/utils' +import type { CanvasCommand } from '../../commands/commands' +import { reorderElement } from '../../commands/reorder-element-command' +import { showGridControls } from '../../commands/show-grid-controls-command' +import { + controlsForGridPlaceholders, + controlsForGridRulers, +} from '../../controls/grid-controls-for-strategies' +import type { CanvasStrategyFactory } from '../canvas-strategies' +import { onlyFitWhenDraggingThisControl } from '../canvas-strategies' +import type { InteractionCanvasState } from '../canvas-strategy-types' +import { + emptyStrategyApplicationResult, + getTargetPathsFromInteractionTarget, + strategyApplicationResult, +} from '../canvas-strategy-types' +import type { DragInteractionData, InteractionSession } from '../interaction-state' +import type { GridCellGlobalFrames } from './grid-helpers' +import { + getGridElementPinState, + getGridPositionIndex, + getOriginalElementGridConfiguration, + getParentGridTemplatesFromChildMeasurements, + gridMoveStrategiesExtraCommands, + isFlowGridChild, +} from './grid-helpers' +import { getTargetGridCellData } from '../../../inspector/grid-helpers' +import { gridItemIdentifier } from '../../../editor/store/editor-state' +import type { PropertyToUpdate } from '../../commands/set-property-command' +import { + propertyToDelete, + propertyToSet, + updateBulkProperties, +} from '../../commands/set-property-command' + +export const gridReorderStrategy: CanvasStrategyFactory = ( + canvasState: InteractionCanvasState, + interactionSession: InteractionSession | null, +) => { + const selectedElements = getTargetPathsFromInteractionTarget(canvasState.interactionTarget) + if ( + selectedElements.length !== 1 || + interactionSession == null || + interactionSession.interactionData.type !== 'DRAG' || + interactionSession.interactionData.drag == null || + interactionSession.activeControl.type !== 'GRID_CELL_HANDLE' || + interactionSession.interactionData.modifiers.alt || + interactionSession.interactionData.modifiers.cmd // disable reorder when reparenting, for now (TODO) + ) { + return null + } + + const selectedElement = selectedElements[0] + if (!MetadataUtils.isGridItem(canvasState.startingMetadata, selectedElement)) { + return null + } + + const selectedElementMetadata = MetadataUtils.findElementByElementPath( + canvasState.startingMetadata, + selectedElement, + ) + if (selectedElementMetadata == null) { + return null + } + if (MetadataUtils.isPositionAbsolute(selectedElementMetadata)) { + return null + } + + const initialTemplates = getParentGridTemplatesFromChildMeasurements( + selectedElementMetadata.specialSizeMeasurements, + ) + if (initialTemplates == null) { + return null + } + + const elementGridPropertiesFromProps = + selectedElementMetadata.specialSizeMeasurements.elementGridPropertiesFromProps + + const pinnedState = getGridElementPinState(elementGridPropertiesFromProps ?? null) + const fitnessModifier = pinnedState !== 'pinned' ? 1 : -1 + + return { + id: 'reorder-grid-move-strategy', + name: 'Grid Reorder', + descriptiveLabel: 'Grid Reorder', + icon: { + category: 'tools', + type: 'pointer', + }, + controlsToRender: [ + controlsForGridPlaceholders(gridItemIdentifier(selectedElement), 'visible-only-while-active'), + controlsForGridRulers(gridItemIdentifier(selectedElement), 'visible-only-while-active'), + ], + fitness: onlyFitWhenDraggingThisControl( + interactionSession, + 'GRID_CELL_HANDLE', + 2 + fitnessModifier, + ), + apply: () => { + if ( + interactionSession == null || + interactionSession.interactionData.type !== 'DRAG' || + interactionSession.interactionData.drag == null || + interactionSession.activeControl.type !== 'GRID_CELL_HANDLE' + ) { + return emptyStrategyApplicationResult + } + + const { commands, elementsToRerender } = getCommandsAndPatchForGridReorder( + canvasState, + interactionSession.interactionData, + selectedElement, + ) + if (commands.length === 0) { + return emptyStrategyApplicationResult + } + + const { midInteractionCommands, onCompleteCommands } = gridMoveStrategiesExtraCommands( + EP.parentPath(selectedElement), // TODO: don't use EP.parentPath + initialTemplates, + ) + + return strategyApplicationResult( + [...midInteractionCommands, ...onCompleteCommands, ...commands], + elementsToRerender, + ) + }, + } +} + +function getCommandsAndPatchForGridReorder( + canvasState: InteractionCanvasState, + interactionData: DragInteractionData, + selectedElement: ElementPath, +): { + commands: CanvasCommand[] + elementsToRerender: ElementPath[] +} { + if (interactionData.drag == null) { + return { commands: [], elementsToRerender: [] } + } + + const selectedElementMetadata = MetadataUtils.findElementByElementPath( + canvasState.startingMetadata, + selectedElement, + ) + if (selectedElementMetadata == null) { + return { commands: [], elementsToRerender: [] } + } + + const { parentGridCellGlobalFrames, parentContainerGridProperties } = + selectedElementMetadata.specialSizeMeasurements + if (parentGridCellGlobalFrames == null) { + return { commands: [], elementsToRerender: [] } + } + + const commands = runGridReorder( + canvasState.startingMetadata, + interactionData, + selectedElementMetadata, + parentGridCellGlobalFrames, + parentContainerGridProperties, + ) + + return { + commands: commands, + elementsToRerender: [EP.parentPath(selectedElement), selectedElement], + } +} + +function runGridReorder( + jsxMetadata: ElementInstanceMetadataMap, + interactionData: DragInteractionData, + selectedElementMetadata: ElementInstanceMetadata, + gridCellGlobalFrames: GridCellGlobalFrames, + gridTemplate: GridContainerProperties, +): CanvasCommand[] { + if (interactionData.drag == null) { + return [] + } + + const mouseCellPosInOriginalElement = getOriginalElementGridConfiguration( + gridCellGlobalFrames, + interactionData, + selectedElementMetadata, + )?.mouseCellPosInOriginalElement + if (mouseCellPosInOriginalElement == null) { + return [] + } + + const targetGridCellData = getTargetGridCellData( + interactionData, + gridCellGlobalFrames, + mouseCellPosInOriginalElement, + ) + if (targetGridCellData == null) { + return [] + } + const { targetCellCoords, targetRootCell } = targetGridCellData + + const gridTemplateColumns = + gridTemplate.gridTemplateColumns?.type === 'DIMENSIONS' + ? gridTemplate.gridTemplateColumns.dimensions.length + : 1 + + const gridChildren = MetadataUtils.getSiblingsUnordered( + jsxMetadata, + selectedElementMetadata.elementPath, + ) + const gridFlowChildrenCount = gridChildren.filter(isFlowGridChild).length + + // The "pure" index in the grid children for the cell under mouse + const possiblyReorderIndex = getGridPositionIndex({ + row: targetCellCoords.row, + column: targetCellCoords.column, + gridTemplateColumns: gridTemplateColumns, + }) + + const canReorderToIndex = possiblyReorderIndex < gridFlowChildrenCount + + const updateGridControlsCommand = showGridControls( + 'mid-interaction', + gridItemIdentifier(selectedElementMetadata.elementPath), + targetCellCoords, + canReorderToIndex ? targetRootCell : null, + ) + + const gridConfig = getOriginalElementGridConfiguration( + gridCellGlobalFrames, + interactionData, + selectedElementMetadata, + ) + + const width = gridConfig?.originalCellBounds.width ?? 1 + const height = gridConfig?.originalCellBounds.height ?? 1 + + const propsToUpdate: PropertyToUpdate[] = [ + propertyToDelete(PP.create('style', 'gridColumnStart')), + propertyToDelete(PP.create('style', 'gridColumnEnd')), + propertyToDelete(PP.create('style', 'gridRowStart')), + propertyToDelete(PP.create('style', 'gridRowEnd')), + ...(width > 1 + ? [propertyToSet(PP.create('style', 'gridColumn'), `span ${width}`)] + : [propertyToDelete(PP.create('style', 'gridColumn'))]), + ...(height > 1 + ? [propertyToSet(PP.create('style', 'gridRow'), `span ${height}`)] + : [propertyToDelete(PP.create('style', 'gridRow'))]), + ] + + return [ + reorderElement('always', selectedElementMetadata.elementPath, absolute(possiblyReorderIndex)), + updateBulkProperties('always', selectedElementMetadata.elementPath, propsToUpdate), + updateGridControlsCommand, + ] +} diff --git a/editor/src/components/canvas/canvas-strategies/strategies/grid-reparent-strategies.spec.browser2.tsx b/editor/src/components/canvas/canvas-strategies/strategies/grid-reparent-strategies.spec.browser2.tsx index 1456ab0423f2..b1e296f1aec2 100644 --- a/editor/src/components/canvas/canvas-strategies/strategies/grid-reparent-strategies.spec.browser2.tsx +++ b/editor/src/components/canvas/canvas-strategies/strategies/grid-reparent-strategies.spec.browser2.tsx @@ -4,14 +4,17 @@ import type { WindowPoint } from '../../../../core/shared/math-utils' import { offsetPoint, windowPoint } from '../../../../core/shared/math-utils' import type { Modifiers } from '../../../../utils/modifiers' import { cmdModifier } from '../../../../utils/modifiers' -import { selectComponentsForTest } from '../../../../utils/utils.test-utils' -import { GridCellTestId } from '../../controls/grid-controls' +import { selectComponentsForTest, wait } from '../../../../utils/utils.test-utils' +import { GridCellTestId } from '../../controls/grid-controls-for-strategies' import { CanvasControlsContainerID } from '../../controls/new-canvas-controls' import type { Point } from '../../event-helpers.test-utils' import { mouseClickAtPoint, + mouseDownAtPoint, mouseDragFromPointToPoint, + mouseMoveToPoint, mouseUpAtPoint, + pressKey, } from '../../event-helpers.test-utils' import type { EditorRenderResult } from '../../ui-jsx.test-utils' import { @@ -20,12 +23,18 @@ import { renderTestEditorWithCode, } from '../../ui-jsx.test-utils' +const testConfigs = [ + { label: 'element', projectFunction: makeTestProjectCode }, + { label: 'component', projectFunction: makeTestProjectCodeWithComponent }, +] + describe('grid reparent strategies', () => { - describe('reparent into a grid', () => { - it('from the storyboard', async () => { - const editor = await renderTestEditorWithCode( - makeTestProjectCode({ - extraCode: ` + testConfigs.forEach((config) => { + describe(`reparent into a grid ${config.label}`, () => { + it('from the storyboard', async () => { + const editor = await renderTestEditorWithCode( + config.projectFunction({ + extraCode: `
{ data-testid='dragme' /> `, - }), - 'await-first-dom-report', - ) + }), + 'await-first-dom-report', + ) - await selectComponentsForTest(editor, [EP.fromString('sb/dragme')]) + await selectComponentsForTest(editor, [EP.fromString('sb/dragme')]) - await dragElement(editor, 'dragme', windowPoint({ x: -200, y: -200 }), cmdModifier) + await dragElement(editor, 'dragme', windowPoint({ x: -200, y: -200 }), cmdModifier) - expect(getPrintedUiJsCode(editor.getEditorState())).toEqual( - formatTestProjectCode( - makeTestProjectCode({ - insideGrid: ` + expect(getPrintedUiJsCode(editor.getEditorState())).toEqual( + formatTestProjectCode( + config.projectFunction({ + insideGrid: `
{ data-testid='dragme' /> `, - }), - ), - ) - }) - it('from a flex container', async () => { - const editor = await renderTestEditorWithCode( - makeTestProjectCode({ - extraCode: ` + }), + ), + ) + }) + it('from a flex container', async () => { + const editor = await renderTestEditorWithCode( + config.projectFunction({ + extraCode: `
{ />
`, - }), - 'await-first-dom-report', - ) + }), + 'await-first-dom-report', + ) - await selectComponentsForTest(editor, [EP.fromString('sb/flex/dragme')]) + await selectComponentsForTest(editor, [EP.fromString('sb/flex/dragme')]) - await dragElement(editor, 'dragme', windowPoint({ x: -200, y: -200 }), cmdModifier) + await dragElement(editor, 'dragme', windowPoint({ x: -200, y: -200 }), cmdModifier) - expect(getPrintedUiJsCode(editor.getEditorState())).toEqual( - formatTestProjectCode( - makeTestProjectCode({ - extraCode: ` + expect(getPrintedUiJsCode(editor.getEditorState())).toEqual( + formatTestProjectCode( + config.projectFunction({ + extraCode: `
{ />
`, - insideGrid: ` + insideGrid: `
{ data-testid='dragme' /> `, - }), - ), - ) - }) - it('from a flow element', async () => { - const editor = await renderTestEditorWithCode( - makeTestProjectCode({ - extraCode: ` + }), + ), + ) + }) + it('from a flow element', async () => { + const editor = await renderTestEditorWithCode( + config.projectFunction({ + extraCode: `
{ />
`, - }), - 'await-first-dom-report', - ) + }), + 'await-first-dom-report', + ) - await selectComponentsForTest(editor, [EP.fromString('sb/flow/dragme')]) + await selectComponentsForTest(editor, [EP.fromString('sb/flow/dragme')]) - await dragElement(editor, 'dragme', windowPoint({ x: -200, y: -200 }), cmdModifier) + await dragElement(editor, 'dragme', windowPoint({ x: -200, y: -200 }), cmdModifier) - expect(getPrintedUiJsCode(editor.getEditorState())).toEqual( - formatTestProjectCode( - makeTestProjectCode({ - extraCode: ` + expect(getPrintedUiJsCode(editor.getEditorState())).toEqual( + formatTestProjectCode( + config.projectFunction({ + extraCode: `
{ />
`, - insideGrid: ` + insideGrid: `
{ data-testid='dragme' /> `, - }), - ), - ) + }), + ), + ) + }) }) - }) - describe('reparent out of a grid', () => { - it('into the storyboard', async () => { - const editor = await renderTestEditorWithCode( - makeTestProjectCode({ - insideGrid: ` + describe(`reparent out of a grid ${config.label}`, () => { + it('into the storyboard', async () => { + const editor = await renderTestEditorWithCode( + config.projectFunction({ + insideGrid: `
{ data-testid='dragme' /> `, - }), - 'await-first-dom-report', - ) + }), + 'await-first-dom-report', + ) - await selectComponentsForTest(editor, [EP.fromString('sb/grid/dragme')]) + await selectComponentsForTest(editor, [EP.fromString('sb/grid/dragme')]) - await dragOut(editor, 'grid', EP.fromString('sb/grid/dragme'), { x: 2000, y: 1000 }) + await dragOut(editor, EP.fromString('sb/grid/dragme'), { x: 2000, y: 1000 }) - expect(getPrintedUiJsCode(editor.getEditorState())).toEqual( - formatTestProjectCode( - makeTestProjectCode({ - extraCode: ` + expect(getPrintedUiJsCode(editor.getEditorState())).toEqual( + formatTestProjectCode( + config.projectFunction({ + extraCode: `
`, - }), - ), - ) - }) - it('into a flex container', async () => { - const editor = await renderTestEditorWithCode( - makeTestProjectCode({ - insideGrid: ` + }), + ), + ) + }) + it('into a flex container', async () => { + const editor = await renderTestEditorWithCode( + config.projectFunction({ + insideGrid: `
{ data-testid='dragme' /> `, - extraCode: ` + extraCode: `
{ height: 86, }} data-uid='bar' + data-testid='bar' />
{ />
`, - }), - 'await-first-dom-report', - ) + }), + 'await-first-dom-report', + ) - await selectComponentsForTest(editor, [EP.fromString('sb/grid/dragme')]) + await selectComponentsForTest(editor, [EP.fromString('sb/grid/dragme')]) - await dragOut(editor, 'grid', EP.fromString('sb/grid/dragme'), { x: 2600, y: 1600 }) + const barElement = editor.renderedDOM.getByTestId('bar') + const barRect = barElement.getBoundingClientRect() + const endPoint = { + x: barRect.x - 10, + y: barRect.y + barRect.height / 2, + } - expect(getPrintedUiJsCode(editor.getEditorState())).toEqual( - formatTestProjectCode( - makeTestProjectCode({ - extraCode: ` + await dragOut(editor, EP.fromString('sb/grid/dragme'), endPoint) + + expect(getPrintedUiJsCode(editor.getEditorState())).toEqual( + formatTestProjectCode( + config.projectFunction({ + extraCode: `
{ height: 86, }} data-uid='bar' + data-testid='bar' />
{ />
`, - }), - ), - ) - }) - it('into a flow element', async () => { - const editor = await renderTestEditorWithCode( - makeTestProjectCode({ - insideGrid: ` + }), + ), + ) + }) + it('into a flow element with flow strategy selected', async () => { + const editor = await renderTestEditorWithCode( + config.projectFunction({ + insideGrid: `
{ data-testid='dragme' /> `, - extraCode: ` + extraCode: `
{ height: 86, }} data-uid='foo' + data-testid='foo' />
{ />
`, - }), - 'await-first-dom-report', - ) + }), + 'await-first-dom-report', + ) - await selectComponentsForTest(editor, [EP.fromString('sb/grid/dragme')]) + await selectComponentsForTest(editor, [EP.fromString('sb/grid/dragme')]) - await dragOut(editor, 'grid', EP.fromString('sb/grid/dragme'), { x: 2200, y: 1800 }) + const fooElement = editor.renderedDOM.getByTestId('foo') + const fooRect = fooElement.getBoundingClientRect() + const endPoint = { + x: fooRect.x + fooRect.width / 2, + y: fooRect.y + fooRect.height / 2, + } - expect(getPrintedUiJsCode(editor.getEditorState())).toEqual( - formatTestProjectCode( - makeTestProjectCode({ - extraCode: ` + await dragOut(editor, EP.fromString('sb/grid/dragme'), endPoint, async () => { + await pressKey('3', { modifiers: cmdModifier }) // this should select the Reparent (Flow) strategy + }) + + expect(getPrintedUiJsCode(editor.getEditorState())).toEqual( + formatTestProjectCode( + config.projectFunction({ + extraCode: `
{ height: 86, }} data-uid='foo' + data-testid='foo' >
{ />
`, - }), - ), - ) - }) - it('into a grid container', async () => { - const editor = await renderTestEditorWithCode( - makeTestProjectCode({ - insideGrid: ` + }), + ), + ) + }) + it('into a grid container', async () => { + const editor = await renderTestEditorWithCode( + config.projectFunction({ + insideGrid: `
{ data-testid='dragme' /> `, - extraCode: ` + extraCode: `
{ gridColumn: 1, }} data-uid='foo' + data-testid='foo' />
{ gridColumn: 3, }} data-uid='bar' + data-testid='bar' />
{ />
`, - }), - 'await-first-dom-report', - ) - - await selectComponentsForTest(editor, [EP.fromString('sb/grid/dragme')]) + }), + 'await-first-dom-report', + ) - await dragOut(editor, 'grid', EP.fromString('sb/grid/dragme'), { x: 2200, y: 2500 }) + await selectComponentsForTest(editor, [EP.fromString('sb/grid/dragme')]) - expect(getPrintedUiJsCode(editor.getEditorState())).toEqual( - formatTestProjectCode( - makeTestProjectCode({ - extraCode: ` + await dragOutToAnotherGrid( + editor, + 'another-grid', + { + x: 10, + y: 180, + }, + EP.fromString('sb/grid/dragme'), + ) + expect(getPrintedUiJsCode(editor.getEditorState())).toEqual( + formatTestProjectCode( + config.projectFunction({ + extraCode: ` +
+
+
+
+
+
+ `, + }), + ), + ) + }) + it('into a grid container with reorder (no explicit gridRow/gridColumn props', async () => { + const editor = await renderTestEditorWithCode( + config.projectFunction({ + insideGrid: `
-
-
-
-
-
+ data-uid='dragme' + data-testid='dragme' + /> `, + extraCode: ` +
+
+
+
+ `, }), - ), - ) - }) - it('when the cell has no explicit size', async () => { - const editor = await renderTestEditorWithCode( - makeTestProjectCode({ - insideGrid: ` + 'await-first-dom-report', + ) + + await selectComponentsForTest(editor, [EP.fromString('sb/grid/dragme')]) + + await dragOutToAnotherGrid( + editor, + 'another-grid', + { + x: 300, + y: 20, + }, + EP.fromString('sb/grid/dragme'), + ) + + expect(getPrintedUiJsCode(editor.getEditorState())).toEqual( + formatTestProjectCode( + config.projectFunction({ + extraCode: ` +
+
+
+
+
+ `, + }), + ), + ) + }) + it('when the cell has no explicit size', async () => { + const editor = await renderTestEditorWithCode( + config.projectFunction({ + insideGrid: `
{ data-testid='dragme' /> `, - }), - 'await-first-dom-report', - ) + }), + 'await-first-dom-report', + ) - await selectComponentsForTest(editor, [EP.fromString('sb/grid/dragme')]) + await selectComponentsForTest(editor, [EP.fromString('sb/grid/dragme')]) - await dragOut(editor, 'grid', EP.fromString('sb/grid/dragme'), { x: 2000, y: 1000 }) + await dragOut(editor, EP.fromString('sb/grid/dragme'), { x: 2000, y: 1000 }) - expect(getPrintedUiJsCode(editor.getEditorState())).toEqual( - formatTestProjectCode( - makeTestProjectCode({ - extraCode: ` + expect(getPrintedUiJsCode(editor.getEditorState())).toEqual( + formatTestProjectCode( + config.projectFunction({ + extraCode: `
`, - }), - ), - ) + }), + ), + ) + }) }) }) }) @@ -773,6 +938,51 @@ export var storyboard = ( ` } +function makeTestProjectCodeWithComponent(params: { extraCode?: string; insideGrid?: string }) { + const insideGrid = params.insideGrid?.trim() + const extraCode = params.extraCode?.trim() + return ` +import * as React from 'react' +import { Storyboard } from 'utopia-api' + +export var storyboard = ( + + ' : `>${insideGrid}`} + ${extraCode ?? ''} + +) + +export function Grid(props) { + return ( +
+ {props.children} +
+ ) +} +` +} + async function dragElement( renderResult: EditorRenderResult, targetTestId: string, @@ -794,19 +1004,91 @@ async function dragElement( async function dragOut( renderResult: EditorRenderResult, - gridTestId: string, cell: ElementPath, endPoint: Point, -) { - const grid = renderResult.renderedDOM.getByTestId(gridTestId) - const gridRect = grid.getBoundingClientRect() - + midDragCallback?: () => Promise, +): Promise { const sourceGridCell = renderResult.renderedDOM.getByTestId(GridCellTestId(cell)) const sourceRect = sourceGridCell.getBoundingClientRect() - await mouseClickAtPoint(sourceGridCell, sourceRect, { modifiers: cmdModifier }) - await mouseDragFromPointToPoint(sourceGridCell, sourceRect, endPoint, { + await mouseClickAtPoint( + sourceGridCell, + { x: sourceRect.x + 5, y: sourceRect.y + 5 }, + { modifiers: cmdModifier }, + ) + + await mouseDownAtPoint( + sourceGridCell, + { x: sourceRect.x + 5, y: sourceRect.y + 5 }, + { + modifiers: cmdModifier, + }, + ) + + const delta: Point = { + x: endPoint.x - sourceRect.x + 5, + y: endPoint.y - sourceRect.y + 5, + } + await mouseMoveToPoint(sourceGridCell, endPoint, { + eventOptions: { + movementX: delta.x, + movementY: delta.y, + buttons: 1, + }, + modifiers: cmdModifier, + }) + if (midDragCallback != null) { + await midDragCallback() + } + await mouseUpAtPoint(renderResult.renderedDOM.getByTestId(CanvasControlsContainerID), endPoint, { modifiers: cmdModifier, }) - await mouseUpAtPoint(grid, gridRect, { modifiers: cmdModifier }) +} + +async function dragOutToAnotherGrid( + renderResult: EditorRenderResult, + anotherGridTestId: string, + offsetInAnotherGrid: Point, + cell: ElementPath, +) { + const sourceGridCell = renderResult.renderedDOM.getByTestId(GridCellTestId(cell)) + const sourceRect = sourceGridCell.getBoundingClientRect() + + const canvasControlsLayer = renderResult.renderedDOM.getByTestId(CanvasControlsContainerID) + const anotherGrid = renderResult.renderedDOM.getByTestId(anotherGridTestId) + const anotherGridRect = anotherGrid.getBoundingClientRect() + + // selecting the cell + await mouseClickAtPoint( + sourceGridCell, + { x: sourceRect.x + 5, y: sourceRect.y + 5 }, + { modifiers: cmdModifier }, + ) + + // starting the drag + await mouseDownAtPoint( + sourceGridCell, + { x: sourceRect.x + 5, y: sourceRect.y + 5 }, + { modifiers: cmdModifier }, + ) + + // first move over target grid hovers the grid controls, so the dom sampler can run + await mouseMoveToPoint( + canvasControlsLayer, + { x: anotherGridRect.x + offsetInAnotherGrid.x, y: anotherGridRect.y + offsetInAnotherGrid.y }, + { modifiers: cmdModifier }, + ) + + // second move runs the strategy for the first time with cell metadata + await mouseMoveToPoint( + canvasControlsLayer, + { x: anotherGridRect.x + offsetInAnotherGrid.x, y: anotherGridRect.y + offsetInAnotherGrid.y }, + { modifiers: cmdModifier }, + ) + + await mouseUpAtPoint( + canvasControlsLayer, + { x: anotherGridRect.x + offsetInAnotherGrid.x, y: anotherGridRect.y + offsetInAnotherGrid.y }, + { modifiers: cmdModifier }, + ) } diff --git a/editor/src/components/canvas/canvas-strategies/strategies/grid-reparent-strategy.tsx b/editor/src/components/canvas/canvas-strategies/strategies/grid-reparent-strategy.tsx index 4c9a08b22bf3..4606575caac4 100644 --- a/editor/src/components/canvas/canvas-strategies/strategies/grid-reparent-strategy.tsx +++ b/editor/src/components/canvas/canvas-strategies/strategies/grid-reparent-strategy.tsx @@ -1,12 +1,21 @@ +import type { NodeModules, ProjectContentTreeRoot } from 'utopia-shared/src/types' +import type { BuiltInDependencies } from '../../../../core/es-modules/package-manager/built-in-dependencies-list' import { MetadataUtils } from '../../../../core/model/element-metadata-utils' import { mapDropNulls } from '../../../../core/shared/array-utils' import * as EP from '../../../../core/shared/element-path' -import * as PP from '../../../../core/shared/property-path' +import type { ElementPathTrees } from '../../../../core/shared/element-path-tree' +import { type ElementInstanceMetadataMap } from '../../../../core/shared/element-template' +import type { CanvasRectangle } from '../../../../core/shared/math-utils' +import { isInfinityRectangle } from '../../../../core/shared/math-utils' import type { ElementPath } from '../../../../core/shared/project-file-types' +import * as PP from '../../../../core/shared/property-path' +import { gridContainerIdentifier, type AllElementProps } from '../../../editor/store/editor-state' +import type { InsertionPath } from '../../../editor/store/insertion-path' import { CSSCursor } from '../../canvas-types' import { setCursorCommand } from '../../commands/set-cursor-command' import { propertyToSet, updateBulkProperties } from '../../commands/set-property-command' import { updateSelectedViews } from '../../commands/update-selected-views-command' +import { controlsForGridPlaceholders } from '../../controls/grid-controls-for-strategies' import { ParentBounds } from '../../controls/parent-bounds' import { ParentOutlines } from '../../controls/parent-outlines' import { ZeroSizedElementControls } from '../../controls/zero-sized-element-controls' @@ -15,7 +24,6 @@ import type { CanvasStrategy, ControlWithProps, CustomStrategyState, - GridCustomStrategyState, InteractionCanvasState, } from '../canvas-strategy-types' import { @@ -27,28 +35,12 @@ import { import type { DragInteractionData, InteractionSession, UpdatedPathMap } from '../interaction-state' import { honoursPropsPosition, shouldKeepMovingDraggedGroupChildren } from './absolute-utils' import { replaceFragmentLikePathsWithTheirChildrenRecursive } from './fragment-like-helpers' +import { runGridChangeElementLocation } from './grid-change-element-location-strategy' import { ifAllowedToReparent, isAllowedToReparent } from './reparent-helpers/reparent-helpers' +import { removeAbsolutePositioningProps } from './reparent-helpers/reparent-property-changes' import type { ReparentTarget } from './reparent-helpers/reparent-strategy-helpers' import { getReparentOutcome, pathToReparent } from './reparent-utils' import { flattenSelection } from './shared-move-strategies-helpers' -import type { CanvasRectangle, CanvasVector } from '../../../../core/shared/math-utils' -import { canvasVector, isInfinityRectangle, offsetPoint } from '../../../../core/shared/math-utils' -import { showGridControls } from '../../commands/show-grid-controls-command' -import type { GridCellCoordinates } from '../../controls/grid-controls' -import { GridControls } from '../../controls/grid-controls' -import { - gridPositionValue, - type ElementInstanceMetadataMap, -} from '../../../../core/shared/element-template' -import type { ElementPathTrees } from '../../../../core/shared/element-path-tree' -import type { AllElementProps } from '../../../editor/store/editor-state' -import type { BuiltInDependencies } from '../../../../core/es-modules/package-manager/built-in-dependencies-list' -import type { NodeModules, ProjectContentTreeRoot } from 'utopia-shared/src/types' -import type { InsertionPath } from '../../../editor/store/insertion-path' -import { removeAbsolutePositioningProps } from './reparent-helpers/reparent-property-changes' -import { canvasPointToWindowPoint } from '../../dom-lookup' -import type { TargetGridCellData } from './grid-helpers' -import { getTargetCell, setGridPropsCommands } from './grid-helpers' export function gridReparentStrategy( reparentTarget: ReparentTarget, @@ -108,6 +100,7 @@ export function gridReparentStrategy( apply: applyGridReparent( canvasState, dragInteractionData, + interactionSession, customStrategyState, reparentTarget, filteredSelectedElements, @@ -137,47 +130,23 @@ export function controlsForGridReparent(reparentTarget: ReparentTarget): Control key: 'zero-size-control', show: 'visible-only-while-active', }), - { - control: GridControls, - props: { targets: [reparentTarget.newParent.intendedParentPath] }, - key: `draw-into-grid-strategy-controls`, - show: 'always-visible', - priority: 'bottom', - }, + controlsForGridPlaceholders( + gridContainerIdentifier(reparentTarget.newParent.intendedParentPath), + ), ] } -function getTargetGridCellUnderCursor( - interactionData: DragInteractionData, - canvasScale: number, - canvasOffset: CanvasVector, - customState: GridCustomStrategyState, -): TargetGridCellData | null { - const mouseWindowPoint = canvasPointToWindowPoint( - offsetPoint(interactionData.dragStart, interactionData.drag ?? canvasVector({ x: 0, y: 0 })), - canvasScale, - canvasOffset, - ) - - const targetCellUnderMouse = getTargetCell( - customState.targetCellData?.gridCellCoordinates ?? null, - false, - mouseWindowPoint, - ) - - return targetCellUnderMouse -} - export function applyGridReparent( canvasState: InteractionCanvasState, interactionData: DragInteractionData, + interactionSession: InteractionSession, customStrategyState: CustomStrategyState, reparentTarget: ReparentTarget, selectedElements: ElementPath[], gridFrame: CanvasRectangle, ) { return () => { - if (interactionData.drag == null) { + if (interactionData.drag == null || selectedElements.length === 0) { return emptyStrategyApplicationResult } @@ -189,13 +158,12 @@ export function applyGridReparent( selectedElements, newParent.intendedParentPath, () => { - if (interactionData.drag == null) { + if (interactionData.drag == null || selectedElements.length === 0) { return emptyStrategyApplicationResult } const allowedToReparent = selectedElements.every((selectedElement) => { return isAllowedToReparent( - canvasState.projectContents, canvasState.startingMetadata, selectedElement, newParent.intendedParentPath, @@ -206,17 +174,6 @@ export function applyGridReparent( return emptyStrategyApplicationResult } - const targetCellData = - getTargetGridCellUnderCursor( - interactionData, - canvasState.scale, - canvasState.canvasOffset, - customStrategyState.grid, - ) ?? customStrategyState.grid.targetCellData - - if (targetCellData == null) { - return emptyStrategyApplicationResult - } const outcomes = mapDropNulls( (selectedElement) => gridReparentCommands( @@ -228,7 +185,7 @@ export function applyGridReparent( nodeModules, selectedElement, newParent, - targetCellData.gridCellCoordinates, + interactionData, ), selectedElements, ) @@ -255,6 +212,7 @@ export function applyGridReparent( ...newPaths, ...newPaths.map(EP.parentPath), ...selectedElements.map(EP.parentPath), + newParent.intendedParentPath, ]) return strategyApplicationResult( @@ -263,14 +221,10 @@ export function applyGridReparent( gridContainerCommands, updateSelectedViews('always', newPaths), setCursorCommand(CSSCursor.Reparent), - showGridControls('mid-interaction', reparentTarget.newParent.intendedParentPath), ], + elementsToRerender, { elementsToRerender: elementsToRerender, - grid: { - ...customStrategyState.grid, - targetCellData: targetCellData, - }, }, ) }, @@ -278,35 +232,6 @@ export function applyGridReparent( } } -function getGridPositioningCommands( - jsxMetadata: ElementInstanceMetadataMap, - hoveredCoordinates: GridCellCoordinates, - { - parentPath, - target, - }: { - parentPath: ElementPath - target: ElementPath - }, -) { - const containerMetadata = MetadataUtils.findElementByElementPath(jsxMetadata, parentPath) - if (containerMetadata == null) { - return null - } - const { column, row } = hoveredCoordinates - - const gridTemplate = containerMetadata.specialSizeMeasurements.containerGridProperties - - const gridPropsCommands = setGridPropsCommands(target, gridTemplate, { - gridColumnStart: gridPositionValue(column), - gridColumnEnd: gridPositionValue(column), - gridRowEnd: gridPositionValue(row), - gridRowStart: gridPositionValue(row), - }) - - return gridPropsCommands -} - function gridReparentCommands( jsxMetadata: ElementInstanceMetadataMap, tree: ElementPathTrees, @@ -316,7 +241,7 @@ function gridReparentCommands( nodeModules: NodeModules, target: ElementPath, newParent: InsertionPath, - hoveredCoordinates: GridCellCoordinates, + interactionData: DragInteractionData, ) { const reparentResult = getReparentOutcome( jsxMetadata, @@ -335,21 +260,44 @@ function gridReparentCommands( return null } - const gridPropsCommands = getGridPositioningCommands(jsxMetadata, hoveredCoordinates, { - parentPath: newParent.intendedParentPath, - target: target, - }) + const { commands: reparentCommands, newPath } = reparentResult + + const targetParent = MetadataUtils.findElementByElementPath( + jsxMetadata, + newParent.intendedParentPath, + ) + if (targetParent == null) { + return null + } + + const gridCellGlobalFrames = targetParent?.specialSizeMeasurements.gridCellGlobalFrames ?? null + if (gridCellGlobalFrames == null) { + return null + } + + const gridTemplate = targetParent?.specialSizeMeasurements.containerGridProperties ?? null + if (gridTemplate == null) { + return null + } - if (gridPropsCommands == null) { + const targetElement = MetadataUtils.findElementByElementPath(jsxMetadata, target) + if (targetElement == null) { return null } - const { commands: reparentCommands, newPath } = reparentResult + const gridPropsCommands = runGridChangeElementLocation( + jsxMetadata, + interactionData, + targetElement, + gridCellGlobalFrames, + gridTemplate, + newPath, + ) const removeAbsolutePositioningPropsCommands = removeAbsolutePositioningProps('always', newPath) return { - commands: [...gridPropsCommands, ...reparentCommands, removeAbsolutePositioningPropsCommands], + commands: [...reparentCommands, ...gridPropsCommands, removeAbsolutePositioningPropsCommands], newPath: newPath, oldPath: target, } diff --git a/editor/src/components/canvas/canvas-strategies/strategies/grid-resize-element-ruler-strategy.spec.browser2.tsx b/editor/src/components/canvas/canvas-strategies/strategies/grid-resize-element-ruler-strategy.spec.browser2.tsx new file mode 100644 index 000000000000..153e382509cd --- /dev/null +++ b/editor/src/components/canvas/canvas-strategies/strategies/grid-resize-element-ruler-strategy.spec.browser2.tsx @@ -0,0 +1,414 @@ +import * as EP from '../../../../core/shared/element-path' +import { + selectComponentsForTest, + setFeatureForBrowserTestsUseInDescribeBlockOnly, +} from '../../../../utils/utils.test-utils' +import { + RulerMarkerColumnEndTestId, + RulerMarkerColumnStartTestId, + RulerMarkerRowEndTestId, + RulerMarkerRowStartTestId, +} from '../../controls/grid-controls' +import { mouseDownAtPoint, mouseMoveToPoint, mouseUpAtPoint } from '../../event-helpers.test-utils' +import { renderTestEditorWithCode } from '../../ui-jsx.test-utils' + +describe('grid resize element ruler strategy', () => { + setFeatureForBrowserTestsUseInDescribeBlockOnly('Grid Ruler Markers', true) + + it('can resize a pinned element horizontally', async () => { + const renderResult = await renderTestEditorWithCode(ProjectCode, 'await-first-dom-report') + + await selectComponentsForTest(renderResult, [EP.fromString('sb/grid/pinned')]) + + const control = await renderResult.renderedDOM.findByTestId(RulerMarkerColumnEndTestId) + const controlRect = control.getBoundingClientRect() + const startPoint = { x: controlRect.x + 5, y: controlRect.y + 5 } + const endPoint = { x: controlRect.x + 200, y: controlRect.y + 5 } + await mouseDownAtPoint(control, startPoint) + await mouseMoveToPoint(control, endPoint) + await mouseUpAtPoint(control, endPoint) + + const element = await renderResult.renderedDOM.findByTestId('pinned') + expect(element.style.gridColumn).toBe('2 / 4') + expect(element.style.gridRow).toBe('3') + }) + + it('can resize a pinned element vertically', async () => { + const renderResult = await renderTestEditorWithCode(ProjectCode, 'await-first-dom-report') + + await selectComponentsForTest(renderResult, [EP.fromString('sb/grid/pinned')]) + + const control = await renderResult.renderedDOM.findByTestId(RulerMarkerRowEndTestId) + const controlRect = control.getBoundingClientRect() + const startPoint = { x: controlRect.x + 5, y: controlRect.y + 5 } + const endPoint = { x: controlRect.x + 5, y: controlRect.y + 100 } + await mouseDownAtPoint(control, startPoint) + await mouseMoveToPoint(control, endPoint) + await mouseUpAtPoint(control, endPoint) + + const element = await renderResult.renderedDOM.findByTestId('pinned') + expect(element.style.gridColumn).toBe('2') + expect(element.style.gridRow).toBe('3 / 5') + }) + + it('can resize a flow element horizontally', async () => { + const renderResult = await renderTestEditorWithCode(ProjectCode, 'await-first-dom-report') + + await selectComponentsForTest(renderResult, [EP.fromString('sb/grid/flow1')]) + + const control = await renderResult.renderedDOM.findByTestId(RulerMarkerColumnEndTestId) + const controlRect = control.getBoundingClientRect() + const startPoint = { x: controlRect.x + 5, y: controlRect.y + 5 } + const endPoint = { x: controlRect.x + 200, y: controlRect.y + 5 } + await mouseDownAtPoint(control, startPoint) + await mouseMoveToPoint(control, endPoint) + await mouseUpAtPoint(control, endPoint) + + const element = await renderResult.renderedDOM.findByTestId('flow1') + expect(element.style.gridColumn).toBe('span 2') + expect(element.style.gridRow).toBe('auto') + }) + + it('can resize a flow element vertically', async () => { + const renderResult = await renderTestEditorWithCode(ProjectCode, 'await-first-dom-report') + + await selectComponentsForTest(renderResult, [EP.fromString('sb/grid/flow1')]) + + const control = await renderResult.renderedDOM.findByTestId(RulerMarkerRowEndTestId) + const controlRect = control.getBoundingClientRect() + const startPoint = { x: controlRect.x + 5, y: controlRect.y + 5 } + const endPoint = { x: controlRect.x + 5, y: controlRect.y + 100 } + await mouseDownAtPoint(control, startPoint) + await mouseMoveToPoint(control, endPoint) + await mouseUpAtPoint(control, endPoint) + + const element = await renderResult.renderedDOM.findByTestId('flow1') + expect(element.style.gridColumn).toBe('auto') + expect(element.style.gridRow).toBe('span 2') + }) + + it('can resize a flow element into a pinned element', async () => { + const renderResult = await renderTestEditorWithCode(ProjectCode, 'await-first-dom-report') + + await selectComponentsForTest(renderResult, [EP.fromString('sb/grid/flow1')]) + + { + const control = await renderResult.renderedDOM.findByTestId(RulerMarkerRowEndTestId) + const controlRect = control.getBoundingClientRect() + const startPoint = { x: controlRect.x + 5, y: controlRect.y + 5 } + const endPoint = { x: controlRect.x + 5, y: controlRect.y + 100 } + await mouseDownAtPoint(control, startPoint) + await mouseMoveToPoint(control, endPoint) + await mouseUpAtPoint(control, endPoint) + + const element = await renderResult.renderedDOM.findByTestId('flow1') + expect(element.style.gridColumn).toBe('auto') + expect(element.style.gridRow).toBe('span 2') + } + + { + const control = await renderResult.renderedDOM.findByTestId(RulerMarkerRowStartTestId) + const controlRect = control.getBoundingClientRect() + const startPoint = { x: controlRect.x + 5, y: controlRect.y + 5 } + const endPoint = { x: controlRect.x + 5, y: controlRect.y + 100 } + await mouseDownAtPoint(control, startPoint) + await mouseMoveToPoint(control, endPoint) + await mouseUpAtPoint(control, endPoint) + + const element = await renderResult.renderedDOM.findByTestId('flow1') + expect(element.style.gridColumn).toBe('auto') + expect(element.style.gridRowEnd).toBe('3') + } + }) + + it('can resize a non-stretching pinned element', async () => { + const renderResult = await renderTestEditorWithCode(ProjectCode, 'await-first-dom-report') + + await selectComponentsForTest(renderResult, [EP.fromString('sb/grid/pinned-fixed')]) + + const control = await renderResult.renderedDOM.findByTestId(RulerMarkerColumnEndTestId) + const controlRect = control.getBoundingClientRect() + const startPoint = { x: controlRect.x + 5, y: controlRect.y + 5 } + const endPoint = { x: controlRect.x + 200, y: controlRect.y + 5 } + await mouseDownAtPoint(control, startPoint) + await mouseMoveToPoint(control, endPoint) + await mouseUpAtPoint(control, endPoint) + + const element = await renderResult.renderedDOM.findByTestId('pinned-fixed') + expect(element.style.gridColumn).toBe('1 / 3') + expect(element.style.gridRow).toBe('2') + }) + + it('can resize a non-stretching flow element', async () => { + const renderResult = await renderTestEditorWithCode(ProjectCode, 'await-first-dom-report') + + await selectComponentsForTest(renderResult, [EP.fromString('sb/grid/flow-fixed')]) + + const control = await renderResult.renderedDOM.findByTestId(RulerMarkerColumnEndTestId) + const controlRect = control.getBoundingClientRect() + const startPoint = { x: controlRect.x + 5, y: controlRect.y + 5 } + const endPoint = { x: controlRect.x + 200, y: controlRect.y + 5 } + await mouseDownAtPoint(control, startPoint) + await mouseMoveToPoint(control, endPoint) + await mouseUpAtPoint(control, endPoint) + + const element = await renderResult.renderedDOM.findByTestId('flow-fixed') + expect(element.style.gridColumn).toBe('span 2') + expect(element.style.gridRow).toBe('auto') + }) + + describe('spans', () => { + it('can resize a flow spanning element (start, from the right)', async () => { + const renderResult = await renderTestEditorWithCode( + ProjectCodeWithSpans, + 'await-first-dom-report', + ) + + await selectComponentsForTest(renderResult, [EP.fromString('sb/grid/flow1')]) + + const control = await renderResult.renderedDOM.findByTestId(RulerMarkerColumnEndTestId) + const controlRect = control.getBoundingClientRect() + const startPoint = { x: controlRect.x + 5, y: controlRect.y + 5 } + const endPoint = { x: controlRect.x + 200, y: controlRect.y + 5 } + await mouseDownAtPoint(control, startPoint) + await mouseMoveToPoint(control, endPoint) + await mouseUpAtPoint(control, endPoint) + + const element = await renderResult.renderedDOM.findByTestId('flow1') + expect(element.style.gridColumn).toBe('span 3') + expect(element.style.gridRow).toBe('auto') + }) + + it('can resize a flow spanning element (start, from the left)', async () => { + const renderResult = await renderTestEditorWithCode( + ProjectCodeWithSpans, + 'await-first-dom-report', + ) + + await selectComponentsForTest(renderResult, [EP.fromString('sb/grid/flow1')]) + + const control = await renderResult.renderedDOM.findByTestId(RulerMarkerColumnStartTestId) + const controlRect = control.getBoundingClientRect() + const startPoint = { x: controlRect.x + 5, y: controlRect.y + 5 } + const endPoint = { x: controlRect.x + 200, y: controlRect.y + 5 } + await mouseDownAtPoint(control, startPoint) + await mouseMoveToPoint(control, endPoint) + await mouseUpAtPoint(control, endPoint) + + const element = await renderResult.renderedDOM.findByTestId('flow1') + expect(element.style.gridColumn).toBe('') + expect(element.style.gridColumnStart).toBe('') + expect(element.style.gridColumnEnd).toBe('3') + expect(element.style.gridRow).toBe('auto') + }) + + it('can resize a flow spanning element (end)', async () => { + const renderResult = await renderTestEditorWithCode( + ProjectCodeWithSpans, + 'await-first-dom-report', + ) + + await selectComponentsForTest(renderResult, [EP.fromString('sb/grid/flow2')]) + + // expand to the right + { + const control = await renderResult.renderedDOM.findByTestId(RulerMarkerColumnEndTestId) + const controlRect = control.getBoundingClientRect() + const startPoint = { x: controlRect.x + 5, y: controlRect.y + 5 } + const endPoint = { x: controlRect.x + 200, y: controlRect.y + 5 } + await mouseDownAtPoint(control, startPoint) + await mouseMoveToPoint(control, endPoint) + await mouseUpAtPoint(control, endPoint) + + const element = await renderResult.renderedDOM.findByTestId('flow2') + expect(element.style.gridColumn).toBe('2 / span 3') + expect(element.style.gridRow).toBe('auto') + } + + // shrink from the left + { + const control = await renderResult.renderedDOM.findByTestId(RulerMarkerColumnStartTestId) + const controlRect = control.getBoundingClientRect() + const startPoint = { x: controlRect.x + 5, y: controlRect.y + 5 } + const endPoint = { x: controlRect.x + 200, y: controlRect.y + 5 } + await mouseDownAtPoint(control, startPoint) + await mouseMoveToPoint(control, endPoint) + await mouseUpAtPoint(control, endPoint) + + const element = await renderResult.renderedDOM.findByTestId('flow2') + expect(element.style.gridColumn).toBe('3 / span 2') + expect(element.style.gridRow).toBe('auto') + } + + // expand back from the left + { + const control = await renderResult.renderedDOM.findByTestId(RulerMarkerColumnStartTestId) + const controlRect = control.getBoundingClientRect() + const startPoint = { x: controlRect.x + 5, y: controlRect.y + 5 } + const endPoint = { x: controlRect.x - 400, y: controlRect.y + 5 } + await mouseDownAtPoint(control, startPoint) + await mouseMoveToPoint(control, endPoint) + await mouseUpAtPoint(control, endPoint) + + const element = await renderResult.renderedDOM.findByTestId('flow2') + expect(element.style.gridColumn).toBe('1 / span 4') + expect(element.style.gridRow).toBe('auto') + } + }) + + it('can resize a pinned spanning element', async () => { + const renderResult = await renderTestEditorWithCode( + ProjectCodeWithSpans, + 'await-first-dom-report', + ) + + await selectComponentsForTest(renderResult, [EP.fromString('sb/grid/pinned')]) + + const control = await renderResult.renderedDOM.findByTestId(RulerMarkerColumnEndTestId) + const controlRect = control.getBoundingClientRect() + const startPoint = { x: controlRect.x + 5, y: controlRect.y + 5 } + const endPoint = { x: controlRect.x + 200, y: controlRect.y + 5 } + await mouseDownAtPoint(control, startPoint) + await mouseMoveToPoint(control, endPoint) + await mouseUpAtPoint(control, endPoint) + + const element = await renderResult.renderedDOM.findByTestId('pinned') + expect(element.style.gridColumn).toBe('2 / span 3') + expect(element.style.gridRow).toBe('3') + }) + }) +}) + +const ProjectCode = ` +import * as React from 'react' +import { Storyboard } from 'utopia-api' + +export var storyboard = ( + +
+
+
+
+
+
+
+ +) +` + +const ProjectCodeWithSpans = ` +import * as React from 'react' +import { Storyboard } from 'utopia-api' + +export var storyboard = ( + +
+
+
+
+
+ +) +` diff --git a/editor/src/components/canvas/canvas-strategies/strategies/grid-resize-element-ruler-strategy.ts b/editor/src/components/canvas/canvas-strategies/strategies/grid-resize-element-ruler-strategy.ts new file mode 100644 index 000000000000..d8eeee81929c --- /dev/null +++ b/editor/src/components/canvas/canvas-strategies/strategies/grid-resize-element-ruler-strategy.ts @@ -0,0 +1,340 @@ +import { MetadataUtils } from '../../../../core/model/element-metadata-utils' +import { mapDropNulls } from '../../../../core/shared/array-utils' +import * as EP from '../../../../core/shared/element-path' +import type { + GridElementProperties, + GridPositionOrSpan, +} from '../../../../core/shared/element-template' +import { + gridPositionValue, + isGridPositionValue, + isGridSpan, +} from '../../../../core/shared/element-template' +import type { CanvasPoint, CanvasRectangle, CanvasVector } from '../../../../core/shared/math-utils' +import { canvasRectangle, isInfinityRectangle } from '../../../../core/shared/math-utils' +import { assertNever } from '../../../../core/shared/utils' +import { gridContainerIdentifier, gridItemIdentifier } from '../../../editor/store/editor-state' +import { isCSSKeyword } from '../../../inspector/common/css-utils' +import { + controlsForGridPlaceholders, + controlsForGridRulers, + GridResizeControls, +} from '../../controls/grid-controls-for-strategies' +import type { CanvasStrategyFactory } from '../canvas-strategies' +import { onlyFitWhenDraggingThisControl } from '../canvas-strategies' +import type { InteractionCanvasState } from '../canvas-strategy-types' +import { + getTargetPathsFromInteractionTarget, + emptyStrategyApplicationResult, + strategyApplicationResult, +} from '../canvas-strategy-types' +import type { GridResizeEdge, InteractionSession } from '../interaction-state' +import type { GridCellCoordBounds } from './grid-cell-bounds' +import { getGridChildCellCoordBoundsFromCanvas } from './grid-cell-bounds' +import { getCommandsForGridItemPlacement } from './grid-helpers' +import { normalizeGridElementPositionAfterResize } from './grid-resize-element-strategy' + +export const gridResizeElementRulerStrategy: CanvasStrategyFactory = ( + canvasState: InteractionCanvasState, + interactionSession: InteractionSession | null, +) => { + const selectedElements = getTargetPathsFromInteractionTarget(canvasState.interactionTarget) + if (selectedElements.length !== 1) { + return null + } + + const selectedElement = selectedElements[0] + const selectedElementMetadata = MetadataUtils.findElementByElementPath( + canvasState.startingMetadata, + selectedElement, + ) + if (selectedElementMetadata == null) { + return null + } + const isElementInsideGrid = MetadataUtils.isGridItem( + canvasState.startingMetadata, + selectedElement, + ) + if (!isElementInsideGrid) { + return null + } + + const selectedElementBounds = MetadataUtils.getFrameInCanvasCoords( + selectedElement, + canvasState.startingMetadata, + ) + if (selectedElementBounds == null || isInfinityRectangle(selectedElementBounds)) { + return null + } + + return { + id: 'GRID-CELL-RESIZE-RULER-STRATEGY', + name: 'Resize Grid Cell (ruler)', + descriptiveLabel: 'Resize Grid Cell (ruler)', + icon: { + category: 'tools', + type: 'pointer', + }, + controlsToRender: [ + { + control: GridResizeControls, + props: { target: gridContainerIdentifier(selectedElement) }, + key: `grid-resize-controls-${EP.toString(selectedElement)}`, + show: 'always-visible', + }, + controlsForGridPlaceholders(gridItemIdentifier(selectedElement)), + controlsForGridRulers(gridItemIdentifier(selectedElement)), + ], + fitness: onlyFitWhenDraggingThisControl(interactionSession, 'GRID_RESIZE_RULER_HANDLE', 1), + apply: () => { + if ( + interactionSession == null || + interactionSession.interactionData.type !== 'DRAG' || + interactionSession.interactionData.drag == null || + interactionSession.activeControl.type !== 'GRID_RESIZE_RULER_HANDLE' + ) { + return emptyStrategyApplicationResult + } + + const allCellBounds = + selectedElementMetadata.specialSizeMeasurements.parentGridCellGlobalFrames + + if (allCellBounds == null) { + return emptyStrategyApplicationResult + } + + const bounds = getGridChildCellCoordBoundsFromCanvas(selectedElementMetadata, allCellBounds) + if (bounds == null) { + return emptyStrategyApplicationResult + } + + if (allCellBounds.length < 1) { + return emptyStrategyApplicationResult + } + const columns = allCellBounds[0] + const columnsWithAddition = + // count one extra column so we correctly find the eastmost bound + columns.concat( + canvasRectangle({ + x: columns[columns.length - 1].x + columns[columns.length - 1].width, + y: columns[columns.length - 1].y, + width: 0, + height: 0, + }), + ) + const closestHorizontal = getClosestCellAlongAxis( + columnsWithAddition, + 'x', + interactionSession.interactionData.drag, + interactionSession.interactionData.dragStart, + ) + + const rows = mapDropNulls((r) => { + if (r.length < 1) { + return null + } + return r[0] + }, allCellBounds) + if (rows.length < 1) { + return emptyStrategyApplicationResult + } + const rowsWithAddition = + // count one extra row so we correctly find the southmost bound + rows.concat( + canvasRectangle({ + x: rows[rows.length - 1].x, + y: rows[rows.length - 1].y + rows[rows.length - 1].height, + width: 0, + height: 0, + }), + ) + const closestVertical = getClosestCellAlongAxis( + rowsWithAddition, + 'y', + interactionSession.interactionData.drag, + interactionSession.interactionData.dragStart, + ) + + const elementGridPropertiesFromProps = + selectedElementMetadata.specialSizeMeasurements.elementGridPropertiesFromProps + + const resizedProps = getResizedElementProperties( + interactionSession.activeControl.edge, + selectedElementMetadata.specialSizeMeasurements.elementGridProperties, + bounds, + closestHorizontal, + closestVertical, + ) + + const columnCount = getCellsCount( + resizedProps.gridColumnStart, + resizedProps.gridColumnEnd, + bounds.column, + bounds.width, + ) + const rowCount = getCellsCount( + resizedProps.gridRowStart, + resizedProps.gridRowEnd, + bounds.row, + bounds.height, + ) + + const normalizedGridProps: GridElementProperties = { + gridColumnStart: normalizeGridElementPositionAfterResize( + elementGridPropertiesFromProps.gridColumnStart, + resizedProps.gridColumnStart, + columnCount ?? bounds.width, + 'start', + elementGridPropertiesFromProps.gridColumnEnd, + resizedProps.gridColumnEnd, + interactionSession.activeControl.edge, + ), + gridColumnEnd: normalizeGridElementPositionAfterResize( + elementGridPropertiesFromProps.gridColumnEnd, + resizedProps.gridColumnEnd, + columnCount ?? bounds.width, + 'end', + elementGridPropertiesFromProps.gridColumnStart, + resizedProps.gridColumnStart, + interactionSession.activeControl.edge, + ), + gridRowStart: normalizeGridElementPositionAfterResize( + elementGridPropertiesFromProps.gridRowStart, + resizedProps.gridRowStart, + rowCount ?? bounds.height, + 'start', + elementGridPropertiesFromProps.gridRowEnd, + resizedProps.gridRowEnd, + interactionSession.activeControl.edge, + ), + gridRowEnd: normalizeGridElementPositionAfterResize( + elementGridPropertiesFromProps.gridRowEnd, + resizedProps.gridRowEnd, + rowCount ?? bounds.height, + 'end', + elementGridPropertiesFromProps.gridRowStart, + resizedProps.gridRowStart, + interactionSession.activeControl.edge, + ), + } + + const gridTemplate = + selectedElementMetadata.specialSizeMeasurements.parentContainerGridProperties + + return strategyApplicationResult( + getCommandsForGridItemPlacement(selectedElement, gridTemplate, normalizedGridProps), + [EP.parentPath(selectedElement), selectedElement], + ) + }, + } +} + +function getClosestCellAlongAxis( + cells: CanvasRectangle[], + axis: 'x' | 'y', + drag: CanvasVector, + dragStart: CanvasPoint, +): number | null { + let closest: number | null = null + let minDist = Infinity + for (let i = 0; i < cells.length; i++) { + const cell = cells[i] + const position = drag[axis] + dragStart[axis] + const dist = Math.abs(cell[axis] - position) + if (dist < minDist) { + minDist = dist + closest = i + 1 // plus one because it's a grid pin, so it's 1-indexed + } + } + return closest +} + +function getResizedElementProperties( + edge: GridResizeEdge, + initial: GridElementProperties, + bounds: GridCellCoordBounds, + closestHorizontal: number | null, + closestVertical: number | null, +): GridElementProperties { + function maybeAuto( + position: GridPositionOrSpan | null, + valueIfAuto: number, + ): GridPositionOrSpan | null { + if (isCSSKeyword(position) && position.value === 'auto') { + return gridPositionValue(valueIfAuto) + } + return position + } + + switch (edge) { + case 'column-end': + return { + ...initial, + gridColumnEnd: + closestHorizontal != null + ? gridPositionValue(Math.max(bounds.column + 1, closestHorizontal)) + : initial.gridColumnEnd, + gridColumnStart: maybeAuto(initial.gridColumnStart, bounds.column), + } + case 'column-start': + return { + ...initial, + gridColumnStart: + closestHorizontal != null + ? gridPositionValue(Math.min(bounds.column + bounds.width - 1, closestHorizontal)) + : initial.gridColumnStart, + gridColumnEnd: maybeAuto(initial.gridColumnEnd, bounds.column + bounds.width), + } + case 'row-end': + return { + ...initial, + gridRowEnd: + closestVertical != null + ? gridPositionValue(Math.max(bounds.row + 1, closestVertical)) + : initial.gridRowEnd, + gridRowStart: maybeAuto(initial.gridRowStart, bounds.row), + } + case 'row-start': + return { + ...initial, + gridRowStart: + closestVertical != null + ? gridPositionValue(Math.min(bounds.row + bounds.height - 1, closestVertical)) + : initial.gridRowStart, + gridRowEnd: maybeAuto(initial.gridRowEnd, bounds.row + bounds.height), + } + default: + assertNever(edge) + } +} + +function getCellsCount( + start: GridPositionOrSpan | null, + end: GridPositionOrSpan | null, + originalStart: number, + originalSize: number, +): number | null { + // start is a number + if (isGridPositionValue(start) && start.numericalPosition != null) { + // end is also a number, return the difference + if (isGridPositionValue(end) && end.numericalPosition != null) { + return end.numericalPosition - start.numericalPosition + } + + // end is a discrete span, recalculate the size as a shrink or an expansion of the original size + if (isGridSpan(end) && end.type === 'SPAN_NUMERIC') { + return originalSize + (originalStart - start.numericalPosition) + } + } + + // start is a discrete span + if (isGridSpan(start) && start.type === 'SPAN_NUMERIC') { + // end is a number, return the max between the span size and the start position + if (isGridPositionValue(end) && end.numericalPosition != null) { + return Math.max(end.numericalPosition, start.value) - 1 + } + } + + // nothing specific found, will be handled with a separate fallback + return null +} diff --git a/editor/src/components/canvas/canvas-strategies/strategies/grid-resize-element-strategy.spec.browser2.tsx b/editor/src/components/canvas/canvas-strategies/strategies/grid-resize-element-strategy.spec.browser2.tsx index 269d2c956972..3231cf6bb4b3 100644 --- a/editor/src/components/canvas/canvas-strategies/strategies/grid-resize-element-strategy.spec.browser2.tsx +++ b/editor/src/components/canvas/canvas-strategies/strategies/grid-resize-element-strategy.spec.browser2.tsx @@ -7,15 +7,21 @@ import type { } from 'utopia-shared/src/types' import { MetadataUtils } from '../../../../core/model/element-metadata-utils' import * as EP from '../../../../core/shared/element-path' -import { getRectCenter, localRectangle } from '../../../../core/shared/math-utils' +import { + getRectCenter, + localPoint, + type LocalPoint, + localRectangle, + offsetPoint, +} from '../../../../core/shared/math-utils' import { selectComponentsForTest } from '../../../../utils/utils.test-utils' -import { GridResizeEdgeTestId } from '../../controls/grid-controls' -import { mouseDragFromPointToPoint } from '../../event-helpers.test-utils' +import { mouseDownAtPoint, mouseDragFromPointToPoint } from '../../event-helpers.test-utils' import type { EditorRenderResult } from '../../ui-jsx.test-utils' import { renderTestEditorWithCode } from '../../ui-jsx.test-utils' import type { GridResizeEdge } from '../interaction-state' -import { gridCellTargetId } from './grid-helpers' -import { wait } from '../../../../core/model/performance-scripts' +import { gridCellTargetId } from './grid-cell-bounds' +import { ResizePointTestId } from '../../controls/select-mode/absolute-resize-control' +import { gridEdgeToEdgePosition } from '../../controls/grid-controls-for-strategies' async function runCellResizeTest( editor: EditorRenderResult, @@ -25,7 +31,12 @@ async function runCellResizeTest( ) { await selectComponentsForTest(editor, [elementPathToDrag]) - const resizeControl = editor.renderedDOM.getByTestId(GridResizeEdgeTestId(edge)) + const resizeControl = editor.renderedDOM.getByTestId( + ResizePointTestId(gridEdgeToEdgePosition(edge)), + ) + + const resizeControlBox = resizeControl.getBoundingClientRect() + await mouseDownAtPoint(resizeControl, { x: resizeControlBox.x + 5, y: resizeControlBox.y + 5 }) const targetGridCell = editor.renderedDOM.getByTestId(dragToCellTestId) await mouseDragFromPointToPoint( @@ -42,6 +53,39 @@ async function runCellResizeTest( height: targetGridCell.getBoundingClientRect().height, }), ), + { + moveBeforeMouseDown: true, + }, + ) +} + +async function runCellResizeTestWithDragVector( + editor: EditorRenderResult, + edge: GridResizeEdge, + dragVector: LocalPoint, + elementPathToDrag: ElementPath = EP.fromString('sb/scene/grid/ddd'), +) { + await selectComponentsForTest(editor, [elementPathToDrag]) + + const resizeControl = editor.renderedDOM.getByTestId( + ResizePointTestId(gridEdgeToEdgePosition(edge)), + ) + + const resizeControlCenter = getRectCenter( + localRectangle({ + x: resizeControl.getBoundingClientRect().x, + y: resizeControl.getBoundingClientRect().y, + width: resizeControl.getBoundingClientRect().width, + height: resizeControl.getBoundingClientRect().height, + }), + ) + await mouseDragFromPointToPoint( + resizeControl, + resizeControlCenter, + offsetPoint(resizeControlCenter, dragVector), + { + moveBeforeMouseDown: true, + }, ) } @@ -84,6 +128,50 @@ describe('grid resize element strategy', () => { gridRowStart: '2', }) }) + + it('can enlarge element in grid component', async () => { + const editor = await renderTestEditorWithCode( + ProjectCodeGridComponent, + 'await-first-dom-report', + ) + + await runCellResizeTest( + editor, + 'column-end', + gridCellTargetId(EP.fromString('sb/scene/grid'), 2, 10), + ) + + const { gridRowStart, gridRowEnd, gridColumnStart, gridColumnEnd } = + editor.renderedDOM.getByTestId('grid-child').style + expect({ gridRowStart, gridRowEnd, gridColumnStart, gridColumnEnd }).toEqual({ + gridColumnEnd: '11', + gridColumnStart: '7', + gridRowStart: '2', + gridRowEnd: 'auto', + }) + }) + + it('can shrink element in grid component', async () => { + const editor = await renderTestEditorWithCode( + ProjectCodeGridComponent, + 'await-first-dom-report', + ) + + await runCellResizeTest( + editor, + 'column-end', + gridCellTargetId(EP.fromString('sb/scene/grid'), 2, 8), + ) + + const { gridRowStart, gridRowEnd, gridColumnStart, gridColumnEnd } = + editor.renderedDOM.getByTestId('grid-child').style + expect({ gridRowStart, gridRowEnd, gridColumnStart, gridColumnEnd }).toEqual({ + gridColumnEnd: '9', + gridColumnStart: '7', + gridRowEnd: 'auto', + gridRowStart: '2', + }) + }) }) describe('column-start', () => { @@ -124,6 +212,50 @@ describe('grid resize element strategy', () => { gridRowStart: '2', }) }) + + it('can enlarge element in grid component', async () => { + const editor = await renderTestEditorWithCode( + ProjectCodeGridComponent, + 'await-first-dom-report', + ) + + await runCellResizeTest( + editor, + 'column-start', + gridCellTargetId(EP.fromString('sb/scene/grid'), 2, 6), + ) + + const { gridRowStart, gridRowEnd, gridColumnStart, gridColumnEnd } = + editor.renderedDOM.getByTestId('grid-child').style + expect({ gridRowStart, gridRowEnd, gridColumnStart, gridColumnEnd }).toEqual({ + gridColumnEnd: '10', + gridColumnStart: '6', + gridRowEnd: 'auto', + gridRowStart: '2', + }) + }) + + it('can shrink element in grid component', async () => { + const editor = await renderTestEditorWithCode( + ProjectCodeGridComponent, + 'await-first-dom-report', + ) + + await runCellResizeTest( + editor, + 'column-start', + gridCellTargetId(EP.fromString('sb/scene/grid'), 2, 8), + ) + + const { gridRowStart, gridRowEnd, gridColumnStart, gridColumnEnd } = + editor.renderedDOM.getByTestId('grid-child').style + expect({ gridRowStart, gridRowEnd, gridColumnStart, gridColumnEnd }).toEqual({ + gridColumnEnd: '10', + gridColumnStart: '8', + gridRowEnd: 'auto', + gridRowStart: '2', + }) + }) }) describe('row-end', () => { @@ -162,6 +294,45 @@ describe('grid resize element strategy', () => { }) } }) + + it('can resize element in grid component', async () => { + const editor = await renderTestEditorWithCode( + ProjectCodeGridComponent, + 'await-first-dom-report', + ) + + await runCellResizeTest( + editor, + 'row-end', + gridCellTargetId(EP.fromString('sb/scene/grid'), 3, 6), + ) + { + const { gridRowStart, gridRowEnd, gridColumnStart, gridColumnEnd } = + editor.renderedDOM.getByTestId('grid-child').style + expect({ gridRowStart, gridRowEnd, gridColumnStart, gridColumnEnd }).toEqual({ + gridColumnEnd: '10', + gridColumnStart: '7', + gridRowEnd: '4', + gridRowStart: '2', + }) + } + + await runCellResizeTest( + editor, + 'row-end', + gridCellTargetId(EP.fromString('sb/scene/grid'), 2, 8), + ) + { + const { gridRowStart, gridRowEnd, gridColumnStart, gridColumnEnd } = + editor.renderedDOM.getByTestId('grid-child').style + expect({ gridRowStart, gridRowEnd, gridColumnStart, gridColumnEnd }).toEqual({ + gridColumnEnd: '10', + gridColumnStart: '7', + gridRowEnd: 'auto', + gridRowStart: '2', + }) + } + }) }) describe('row-start', () => { @@ -202,6 +373,132 @@ describe('grid resize element strategy', () => { }) } }) + + it('can resize element in grid component', async () => { + const editor = await renderTestEditorWithCode( + ProjectCodeGridComponent, + 'await-first-dom-report', + ) + + await runCellResizeTest( + editor, + 'row-start', + gridCellTargetId(EP.fromString('sb/scene/grid'), 1, 6), + ) + + { + const { gridRowStart, gridRowEnd, gridColumnStart, gridColumnEnd } = + editor.renderedDOM.getByTestId('grid-child').style + expect({ gridRowStart, gridRowEnd, gridColumnStart, gridColumnEnd }).toEqual({ + gridColumnEnd: '10', + gridColumnStart: '7', + gridRowEnd: '3', + gridRowStart: '1', + }) + } + + { + await runCellResizeTest( + editor, + 'row-start', + gridCellTargetId(EP.fromString('sb/scene/grid'), 2, 8), + ) + + const { gridRowStart, gridRowEnd, gridColumnStart, gridColumnEnd } = + editor.renderedDOM.getByTestId('grid-child').style + expect({ gridRowStart, gridRowEnd, gridColumnStart, gridColumnEnd }).toEqual({ + gridColumnEnd: '10', + gridColumnStart: '7', + gridRowEnd: 'auto', + gridRowStart: '2', + }) + } + }) + }) + + it('can resize element with mouse move outside of grid cells', async () => { + const editor = await renderTestEditorWithCode(ProjectCode, 'await-first-dom-report') + await runCellResizeTest( + editor, + 'column-end', + gridCellTargetId(EP.fromString('sb/scene/grid'), 1, 8), + ) + + { + const { gridRowStart, gridRowEnd, gridColumnStart, gridColumnEnd } = + editor.renderedDOM.getByTestId('grid-child').style + expect({ gridRowStart, gridRowEnd, gridColumnStart, gridColumnEnd }).toEqual({ + gridColumnEnd: '9', + gridColumnStart: '7', + gridRowEnd: 'auto', + gridRowStart: '2', + }) + } + + { + // moving a 2 cell wide element in the middle, over the gap between 2 cells + await runCellResizeTestWithDragVector( + editor, + 'row-start', + localPoint({ + x: 0, + y: -50, + }), + ) + + const { gridRowStart, gridRowEnd, gridColumnStart, gridColumnEnd } = + editor.renderedDOM.getByTestId('grid-child').style + expect({ gridRowStart, gridRowEnd, gridColumnStart, gridColumnEnd }).toEqual({ + gridColumnEnd: '9', + gridColumnStart: '7', + gridRowEnd: '3', + gridRowStart: '1', + }) + } + }) + + it('can resize element with mouse move outside of grid cells in grid component', async () => { + const editor = await renderTestEditorWithCode( + ProjectCodeGridComponent, + 'await-first-dom-report', + ) + await runCellResizeTest( + editor, + 'column-end', + gridCellTargetId(EP.fromString('sb/scene/grid'), 1, 8), + ) + + { + const { gridRowStart, gridRowEnd, gridColumnStart, gridColumnEnd } = + editor.renderedDOM.getByTestId('grid-child').style + expect({ gridRowStart, gridRowEnd, gridColumnStart, gridColumnEnd }).toEqual({ + gridColumnEnd: '9', + gridColumnStart: '7', + gridRowEnd: 'auto', + gridRowStart: '2', + }) + } + + { + // moving a 2 cell wide element in the middle, over the gap between 2 cells + await runCellResizeTestWithDragVector( + editor, + 'row-start', + localPoint({ + x: 0, + y: -50, + }), + ) + + const { gridRowStart, gridRowEnd, gridColumnStart, gridColumnEnd } = + editor.renderedDOM.getByTestId('grid-child').style + expect({ gridRowStart, gridRowEnd, gridColumnStart, gridColumnEnd }).toEqual({ + gridColumnEnd: '9', + gridColumnStart: '7', + gridRowEnd: '3', + gridRowStart: '1', + }) + } }) it('removes the grid-area prop on resize', async () => { @@ -231,8 +528,9 @@ describe('grid resize element strategy', () => { expect(styleProp).toEqual([ ['minHeight', 0], ['backgroundColor', '#db48f6'], - ['gridColumnStart', 7], - ['gridColumnEnd', 11], + ['width', '100%'], + ['height', '100%'], + ['gridColumn', '7 / 11'], ['gridRow', 2], ]) }) @@ -272,6 +570,8 @@ export var storyboard = ( gridTemplateRows: '1fr', gridColumn: 1, gridRow: 2, + width: '100%', + height: '100%', }} >
{ + const editor = await renderTestEditorWithCode(ProjectCode, 'await-first-dom-report') -export var storyboard = ( - - -
{ + const editor = await renderTestEditorWithCode( + ProjectCodeGridComponent, + 'await-first-dom-report', + ) + + await runCellResizeTest( + editor, + 'column-end', + gridCellTargetId(EP.fromString('sb/scene/grid'), 2, 10), + EP.fromString('sb/scene/grid/eee'), + ) + + const { gridRowStart, gridRowEnd, gridColumnStart, gridColumnEnd } = + editor.renderedDOM.getByTestId('grid-child-stretch').style + expect({ gridRowStart, gridRowEnd, gridColumnStart, gridColumnEnd }).toEqual({ + gridColumnEnd: '11', + gridColumnStart: '4', + gridRowStart: '4', + gridRowEnd: 'auto', + }) + }) + + describe('spans', () => { + it('respects column start spans', async () => { + const editor = await renderTestEditorWithCode( + makeProjectCodeWithCustomPlacement({ gridColumn: 'span 2', gridRow: '2' }), + 'await-first-dom-report', + ) + + // enlarge to the right + { + await runCellResizeTest( + editor, + 'column-end', + gridCellTargetId(EP.fromString('sb/grid'), 2, 3), + EP.fromString('sb/grid/cell'), + ) + + const { gridRowStart, gridRowEnd, gridColumnStart, gridColumnEnd } = + editor.renderedDOM.getByTestId('cell').style + expect({ gridRowStart, gridRowEnd, gridColumnStart, gridColumnEnd }).toEqual({ + gridColumnEnd: 'auto', + gridColumnStart: 'span 3', + gridRowEnd: 'auto', + gridRowStart: '2', + }) + } + + // shrink from the left + { + await runCellResizeTest( + editor, + 'column-start', + gridCellTargetId(EP.fromString('sb/grid'), 2, 2), + EP.fromString('sb/grid/cell'), + ) + + const { gridRowStart, gridRowEnd, gridColumnStart, gridColumnEnd } = + editor.renderedDOM.getByTestId('cell').style + expect({ gridRowStart, gridRowEnd, gridColumnStart, gridColumnEnd }).toEqual({ + gridColumnEnd: '4', + gridColumnStart: 'span 2', + gridRowEnd: 'auto', + gridRowStart: '2', + }) + } + + // enlarge back from the left + { + await runCellResizeTest( + editor, + 'column-start', + gridCellTargetId(EP.fromString('sb/grid'), 2, 1), + EP.fromString('sb/grid/cell'), + ) + + const { gridRowStart, gridRowEnd, gridColumnStart, gridColumnEnd } = + editor.renderedDOM.getByTestId('cell').style + expect({ gridRowStart, gridRowEnd, gridColumnStart, gridColumnEnd }).toEqual({ + gridColumnEnd: 'auto', + gridColumnStart: 'span 3', + gridRowEnd: 'auto', + gridRowStart: '2', + }) + } + }) + it('respects column end spans', async () => { + const editor = await renderTestEditorWithCode( + makeProjectCodeWithCustomPlacement({ gridColumn: '2 / span 2', gridRow: '2' }), + 'await-first-dom-report', + ) + + // enlarge to the right + { + await runCellResizeTest( + editor, + 'column-end', + gridCellTargetId(EP.fromString('sb/grid'), 2, 4), + EP.fromString('sb/grid/cell'), + ) + + const { gridRowStart, gridRowEnd, gridColumnStart, gridColumnEnd } = + editor.renderedDOM.getByTestId('cell').style + expect({ gridRowStart, gridRowEnd, gridColumnStart, gridColumnEnd }).toEqual({ + gridColumnEnd: 'span 3', + gridColumnStart: '2', + gridRowEnd: 'auto', + gridRowStart: '2', + }) + } + }) + it('respects row start spans', async () => { + const editor = await renderTestEditorWithCode( + makeProjectCodeWithCustomPlacement({ gridColumn: '2', gridRow: 'span 2' }), + 'await-first-dom-report', + ) + + // enlarge to the bottom + { + await runCellResizeTest( + editor, + 'row-end', + gridCellTargetId(EP.fromString('sb/grid'), 3, 2), + EP.fromString('sb/grid/cell'), + ) + + const { gridRowStart, gridRowEnd, gridColumnStart, gridColumnEnd } = + editor.renderedDOM.getByTestId('cell').style + expect({ gridRowStart, gridRowEnd, gridColumnStart, gridColumnEnd }).toEqual({ + gridColumnEnd: 'auto', + gridColumnStart: '2', + gridRowEnd: 'auto', + gridRowStart: 'span 3', + }) + } + + // shrink from the top + { + await runCellResizeTest( + editor, + 'row-start', + gridCellTargetId(EP.fromString('sb/grid'), 2, 2), + EP.fromString('sb/grid/cell'), + ) + + const { gridRowStart, gridRowEnd, gridColumnStart, gridColumnEnd } = + editor.renderedDOM.getByTestId('cell').style + expect({ gridRowStart, gridRowEnd, gridColumnStart, gridColumnEnd }).toEqual({ + gridColumnEnd: 'auto', + gridColumnStart: '2', + gridRowEnd: '4', + gridRowStart: 'span 2', + }) + } + + // enlarge back from the top + { + await runCellResizeTest( + editor, + 'row-start', + gridCellTargetId(EP.fromString('sb/grid'), 1, 2), + EP.fromString('sb/grid/cell'), + ) + + const { gridRowStart, gridRowEnd, gridColumnStart, gridColumnEnd } = + editor.renderedDOM.getByTestId('cell').style + expect({ gridRowStart, gridRowEnd, gridColumnStart, gridColumnEnd }).toEqual({ + gridColumnEnd: 'auto', + gridColumnStart: '2', + gridRowEnd: 'auto', + gridRowStart: 'span 3', + }) + } + }) + it('respects row end spans', async () => { + const editor = await renderTestEditorWithCode( + makeProjectCodeWithCustomPlacement({ gridColumn: '2', gridRow: '2 / span 2' }), + 'await-first-dom-report', + ) + + // enlarge to the bottom + { + await runCellResizeTest( + editor, + 'row-end', + gridCellTargetId(EP.fromString('sb/grid'), 4, 2), + EP.fromString('sb/grid/cell'), + ) + + const { gridRowStart, gridRowEnd, gridColumnStart, gridColumnEnd } = + editor.renderedDOM.getByTestId('cell').style + expect({ gridRowStart, gridRowEnd, gridColumnStart, gridColumnEnd }).toEqual({ + gridColumnEnd: 'auto', + gridColumnStart: '2', + gridRowEnd: 'span 3', + gridRowStart: '2', + }) + } + }) + it('uses spans for flow elements', async () => { + const editor = await renderTestEditorWithCode( + makeProjectCodeWithCustomPlacement({ gridColumn: 'auto', gridRow: 'auto' }), + 'await-first-dom-report', + ) + + // enlarge to the right, spans in flow + { + await runCellResizeTest( + editor, + 'column-end', + gridCellTargetId(EP.fromString('sb/grid'), 1, 3), + EP.fromString('sb/grid/cell'), + ) + + const { gridRowStart, gridRowEnd, gridColumnStart, gridColumnEnd } = + editor.renderedDOM.getByTestId('cell').style + expect({ gridRowStart, gridRowEnd, gridColumnStart, gridColumnEnd }).toEqual({ + gridColumnEnd: 'auto', + gridColumnStart: 'span 3', + gridRowEnd: 'auto', + gridRowStart: 'auto', + }) + } + + // enlarge to the bottom, still spans in flow + { + await runCellResizeTest( + editor, + 'row-end', + gridCellTargetId(EP.fromString('sb/grid'), 4, 3), + EP.fromString('sb/grid/cell'), + ) + + const { gridRowStart, gridRowEnd, gridColumnStart, gridColumnEnd } = + editor.renderedDOM.getByTestId('cell').style + expect({ gridRowStart, gridRowEnd, gridColumnStart, gridColumnEnd }).toEqual({ + gridColumnEnd: 'auto', + gridColumnStart: 'span 3', + gridRowEnd: 'auto', + gridRowStart: 'span 4', + }) + } + + // shrink from the right, still spans in flow + { + await runCellResizeTest( + editor, + 'column-end', + gridCellTargetId(EP.fromString('sb/grid'), 4, 2), + EP.fromString('sb/grid/cell'), + ) + + const { gridRowStart, gridRowEnd, gridColumnStart, gridColumnEnd } = + editor.renderedDOM.getByTestId('cell').style + expect({ gridRowStart, gridRowEnd, gridColumnStart, gridColumnEnd }).toEqual({ + gridColumnEnd: 'auto', + gridColumnStart: 'span 2', + gridRowEnd: 'auto', + gridRowStart: 'span 4', + }) + } + + // shrink from the bottom, still spans in flow + { + await runCellResizeTest( + editor, + 'row-end', + gridCellTargetId(EP.fromString('sb/grid'), 3, 2), + EP.fromString('sb/grid/cell'), + ) + + const { gridRowStart, gridRowEnd, gridColumnStart, gridColumnEnd } = + editor.renderedDOM.getByTestId('cell').style + expect({ gridRowStart, gridRowEnd, gridColumnStart, gridColumnEnd }).toEqual({ + gridColumnEnd: 'auto', + gridColumnStart: 'span 2', + gridRowEnd: 'auto', + gridRowStart: 'span 3', + }) + } + + // shrink from the top, it spans but now is pinned + { + await runCellResizeTest( + editor, + 'row-start', + gridCellTargetId(EP.fromString('sb/grid'), 2, 2), + EP.fromString('sb/grid/cell'), + ) + + const { gridRowStart, gridRowEnd, gridColumnStart, gridColumnEnd } = + editor.renderedDOM.getByTestId('cell').style + expect({ gridRowStart, gridRowEnd, gridColumnStart, gridColumnEnd }).toEqual({ + gridColumnEnd: 'auto', + gridColumnStart: 'span 2', + gridRowEnd: '4', + gridRowStart: 'span 2', + }) + } + }) + }) +}) + +const ProjectCode = `import * as React from 'react' +import { Scene, Storyboard, Placeholder } from 'utopia-api' + +export var storyboard = ( + + +
+
) ` +const ProjectCodeGridComponent = `import * as React from 'react' +import { Scene, Storyboard, Placeholder } from 'utopia-api' + +export var storyboard = ( + + + +
+ + +
+
+ + + +) + +export function Grid(props) { + return ( +
+
+ {props.children} +
+
+
+ ) +} +` + const ProjectCodeWithGridArea = `import * as React from 'react' import { Scene, Storyboard, Placeholder } from 'utopia-api' @@ -452,6 +1213,8 @@ export var storyboard = ( minHeight: 0, gridArea: '2 / 7 / 3 / 10', backgroundColor: '#db48f6', + width: '100%', + height: '100%', }} data-uid='ddd' data-testid='grid-child' @@ -465,3 +1228,42 @@ export var storyboard = ( function unsafeCast(a: unknown): T { return a as T } + +function makeProjectCodeWithCustomPlacement(params: { + gridColumn: string + gridRow: string +}): string { + return `import * as React from 'react' +import { Storyboard } from 'utopia-api' + +export var storyboard = ( + +
+
+
+ +) +` +} diff --git a/editor/src/components/canvas/canvas-strategies/strategies/grid-resize-element-strategy.ts b/editor/src/components/canvas/canvas-strategies/strategies/grid-resize-element-strategy.ts index a855235f8f39..1c9129be903a 100644 --- a/editor/src/components/canvas/canvas-strategies/strategies/grid-resize-element-strategy.ts +++ b/editor/src/components/canvas/canvas-strategies/strategies/grid-resize-element-strategy.ts @@ -1,11 +1,29 @@ import { MetadataUtils } from '../../../../core/model/element-metadata-utils' import * as EP from '../../../../core/shared/element-path' -import type { GridElementProperties, GridPosition } from '../../../../core/shared/element-template' -import { offsetPoint } from '../../../../core/shared/math-utils' -import { assertNever } from '../../../../core/shared/utils' -import { isCSSKeyword } from '../../../inspector/common/css-utils' -import { GridControls, GridControlsKey, GridResizeControls } from '../../controls/grid-controls' -import { canvasPointToWindowPoint } from '../../dom-lookup' +import type { + GridElementProperties, + GridPositionOrSpan, +} from '../../../../core/shared/element-template' +import { + gridSpanNumeric, + isGridPositionValue, + isGridSpan, +} from '../../../../core/shared/element-template' +import { + type CanvasRectangle, + isInfinityRectangle, + rectangleIntersection, +} from '../../../../core/shared/math-utils' +import { gridContainerIdentifier, gridItemIdentifier } from '../../../editor/store/editor-state' +import { cssKeyword } from '../../../inspector/common/css-utils' +import { isFillOrStretchModeAppliedOnAnySide } from '../../../inspector/inspector-common' +import { CSSCursor } from '../../canvas-types' +import { setCursorCommand } from '../../commands/set-cursor-command' +import { + controlsForGridRulers, + gridEdgeToEdgePosition, + GridResizeControls, +} from '../../controls/grid-controls-for-strategies' import type { CanvasStrategyFactory } from '../canvas-strategies' import { onlyFitWhenDraggingThisControl } from '../canvas-strategies' import type { InteractionCanvasState } from '../canvas-strategy-types' @@ -14,14 +32,13 @@ import { emptyStrategyApplicationResult, strategyApplicationResult, } from '../canvas-strategy-types' -import type { InteractionSession } from '../interaction-state' -import type { TargetGridCellData } from './grid-helpers' -import { getGridCellUnderMouse, setGridPropsCommands } from './grid-helpers' +import type { GridResizeEdge, InteractionSession } from '../interaction-state' +import { getCommandsForGridItemPlacement, isAutoGridPin } from './grid-helpers' +import { resizeBoundingBoxFromSide } from './resize-helpers' export const gridResizeElementStrategy: CanvasStrategyFactory = ( canvasState: InteractionCanvasState, interactionSession: InteractionSession | null, - customState, ) => { const selectedElements = getTargetPathsFromInteractionTarget(canvasState.interactionTarget) if (selectedElements.length !== 1) { @@ -29,7 +46,14 @@ export const gridResizeElementStrategy: CanvasStrategyFactory = ( } const selectedElement = selectedElements[0] - const isElementInsideGrid = MetadataUtils.isGridCell( + const selectedElementMetadata = MetadataUtils.findElementByElementPath( + canvasState.startingMetadata, + selectedElement, + ) + if (selectedElementMetadata == null) { + return null + } + const isElementInsideGrid = MetadataUtils.isGridItem( canvasState.startingMetadata, selectedElement, ) @@ -37,7 +61,17 @@ export const gridResizeElementStrategy: CanvasStrategyFactory = ( return null } - const parentGridPath = EP.parentPath(selectedElement) + const selectedElementBounds = MetadataUtils.getFrameInCanvasCoords( + selectedElement, + canvasState.startingMetadata, + ) + if (selectedElementBounds == null || isInfinityRectangle(selectedElementBounds)) { + return null + } + + if (!isFillOrStretchModeAppliedOnAnySide(canvasState.startingMetadata, selectedElement)) { + return null + } return { id: 'GRID-CELL-RESIZE-STRATEGY', @@ -50,17 +84,11 @@ export const gridResizeElementStrategy: CanvasStrategyFactory = ( controlsToRender: [ { control: GridResizeControls, - props: { target: selectedElement }, + props: { target: gridContainerIdentifier(selectedElement) }, key: `grid-resize-controls-${EP.toString(selectedElement)}`, show: 'always-visible', }, - { - control: GridControls, - props: { targets: [parentGridPath] }, - key: GridControlsKey(parentGridPath), - show: 'always-visible', - priority: 'bottom', - }, + controlsForGridRulers(gridItemIdentifier(selectedElement)), ], fitness: onlyFitWhenDraggingThisControl(interactionSession, 'GRID_RESIZE_HANDLE', 1), apply: () => { @@ -73,122 +101,182 @@ export const gridResizeElementStrategy: CanvasStrategyFactory = ( return emptyStrategyApplicationResult } - const mouseWindowPoint = canvasPointToWindowPoint( - offsetPoint( - interactionSession.interactionData.dragStart, - interactionSession.interactionData.drag, - ), - canvasState.scale, - canvasState.canvasOffset, - ) - - let targetCell: TargetGridCellData | null = customState.grid.targetCellData - const cellUnderMouse = getGridCellUnderMouse(mouseWindowPoint) - if (cellUnderMouse != null) { - const { cellWindowRectangle, coordinates: gridCellCoordinates } = cellUnderMouse - targetCell = { cellWindowRectangle, gridCellCoordinates } - } + const allCellBounds = + selectedElementMetadata.specialSizeMeasurements.parentGridCellGlobalFrames - if (targetCell == null) { + if (allCellBounds == null) { return emptyStrategyApplicationResult } - const container = MetadataUtils.findElementByElementPath( - canvasState.startingMetadata, - EP.parentPath(selectedElement), + const resizeBoundingBox = resizeBoundingBoxFromSide( + selectedElementBounds, + interactionSession.interactionData.drag, + gridEdgeToEdgePosition(interactionSession.activeControl.edge), + 'non-center-based', + null, ) - if (container == null) { + + const gridPropsNumeric = getNewGridPropsFromResizeBox(resizeBoundingBox, allCellBounds) + + if (gridPropsNumeric == null) { return emptyStrategyApplicationResult } - const gridTemplate = container.specialSizeMeasurements.containerGridProperties - - let gridProps: GridElementProperties = MetadataUtils.findElementByElementPath( - canvasState.startingMetadata, - selectedElement, - )?.specialSizeMeasurements.elementGridProperties ?? { - gridColumnEnd: { numericalPosition: 0 }, - gridColumnStart: { numericalPosition: 0 }, - gridRowEnd: { numericalPosition: 0 }, - gridRowStart: { numericalPosition: 0 }, - } - switch (interactionSession.activeControl.edge) { - case 'column-start': - gridProps = { - ...gridProps, - gridColumnStart: { numericalPosition: targetCell.gridCellCoordinates.column }, - } - break - case 'column-end': - gridProps = { - ...gridProps, - gridColumnEnd: { numericalPosition: targetCell.gridCellCoordinates.column + 1 }, - } - break - case 'row-end': - gridProps = { - ...gridProps, - gridRowEnd: { numericalPosition: targetCell.gridCellCoordinates.row + 1 }, - } - break - case 'row-start': - gridProps = { - ...gridProps, - gridRowStart: { numericalPosition: targetCell.gridCellCoordinates.row }, - } - break - default: - assertNever(interactionSession.activeControl.edge) + const gridTemplate = + selectedElementMetadata.specialSizeMeasurements.parentContainerGridProperties + + const elementGridPropertiesFromProps = + selectedElementMetadata.specialSizeMeasurements.elementGridPropertiesFromProps + + const columnCount = + gridPropsNumeric.gridColumnEnd.numericalPosition - + gridPropsNumeric.gridColumnStart.numericalPosition + const rowCount = + gridPropsNumeric.gridRowEnd.numericalPosition - + gridPropsNumeric.gridRowStart.numericalPosition + + const gridProps: GridElementProperties = { + gridColumnStart: normalizeGridElementPositionAfterResize( + elementGridPropertiesFromProps.gridColumnStart, + gridPropsNumeric.gridColumnStart, + columnCount, + 'start', + elementGridPropertiesFromProps.gridColumnEnd, + gridPropsNumeric.gridColumnEnd, + interactionSession.activeControl.edge, + ), + gridColumnEnd: normalizeGridElementPositionAfterResize( + elementGridPropertiesFromProps.gridColumnEnd, + gridPropsNumeric.gridColumnEnd, + columnCount, + 'end', + elementGridPropertiesFromProps.gridColumnStart, + gridPropsNumeric.gridColumnStart, + interactionSession.activeControl.edge, + ), + gridRowStart: normalizeGridElementPositionAfterResize( + elementGridPropertiesFromProps.gridRowStart, + gridPropsNumeric.gridRowStart, + rowCount, + 'start', + elementGridPropertiesFromProps.gridRowEnd, + gridPropsNumeric.gridRowEnd, + interactionSession.activeControl.edge, + ), + gridRowEnd: normalizeGridElementPositionAfterResize( + elementGridPropertiesFromProps.gridRowEnd, + gridPropsNumeric.gridRowEnd, + rowCount, + 'end', + elementGridPropertiesFromProps.gridRowStart, + gridPropsNumeric.gridRowStart, + interactionSession.activeControl.edge, + ), } + const cursor = + interactionSession.activeControl.edge === 'column-start' || + interactionSession.activeControl.edge === 'column-end' + ? CSSCursor.ColResize + : CSSCursor.RowResize + return strategyApplicationResult( - setGridPropsCommands(selectedElement, gridTemplate, gridPropsWithDragOver(gridProps)), - { - grid: { ...customState.grid, targetCellData: targetCell }, - }, + [ + ...getCommandsForGridItemPlacement(selectedElement, gridTemplate, gridProps), + setCursorCommand(cursor), + ], + [(EP.parentPath(selectedElement), selectedElement)], ) }, } } -function orderedGridPositions({ - start, - end, -}: { - start: GridPosition | null - end: GridPosition | null -}): { - start: GridPosition | null - end: GridPosition | null -} { +function getNewGridPropsFromResizeBox( + resizeBoundingBox: CanvasRectangle, + allCellBounds: CanvasRectangle[][], +) { + let newRowStart = Infinity + let newRowEnd = -Infinity + let newColumnStart = Infinity + let newColumnEnd = -Infinity + + // those cells should be occupied by the element which has an intersection with the resize box + for (let rowIdx = 0; rowIdx < allCellBounds.length; rowIdx++) { + for (let colIdx = 0; colIdx < allCellBounds[rowIdx].length; colIdx++) { + if (rectangleIntersection(resizeBoundingBox, allCellBounds[rowIdx][colIdx]) != null) { + newRowStart = Math.min(newRowStart, rowIdx + 1) + newColumnStart = Math.min(newColumnStart, colIdx + 1) + newRowEnd = Math.max(newRowEnd, rowIdx + 2) + newColumnEnd = Math.max(newColumnEnd, colIdx + 2) + } + } + } + if ( - start == null || - isCSSKeyword(start) || - start.numericalPosition == null || - end == null || - isCSSKeyword(end) || - end.numericalPosition == null + !isFinite(newRowStart) || + !isFinite(newColumnStart) || + !isFinite(newRowEnd) || + !isFinite(newColumnEnd) ) { - return { start, end } + return null } - return start.numericalPosition < end.numericalPosition - ? { start, end } - : { - start: { numericalPosition: end.numericalPosition - 1 }, - end: { numericalPosition: start.numericalPosition + 1 }, - } + return { + gridRowStart: { numericalPosition: newRowStart }, + gridRowEnd: { numericalPosition: newRowEnd }, + gridColumnStart: { numericalPosition: newColumnStart }, + gridColumnEnd: { numericalPosition: newColumnEnd }, + } } -function gridPropsWithDragOver(props: GridElementProperties): GridElementProperties { - const { start: gridColumnStart, end: gridColumnEnd } = orderedGridPositions({ - start: props.gridColumnStart, - end: props.gridColumnEnd, - }) - const { start: gridRowStart, end: gridRowEnd } = orderedGridPositions({ - start: props.gridRowStart, - end: props.gridRowEnd, - }) - - return { gridRowStart, gridRowEnd, gridColumnStart, gridColumnEnd } +/* + After a resize happens and we know the numerical grid positioning of the new bounds, + return a normalized version of the new position so that it respects any spans that + may have been there before the resize, and/or default it to 'auto' when it would become redundant. + If the positions match a flow configuration, give priority to span notation. +*/ +export function normalizeGridElementPositionAfterResize( + position: GridPositionOrSpan | null, + resizedPosition: GridPositionOrSpan | null, + size: number, // the number of cols/rows the cell occupies + bound: 'start' | 'end', + counterpart: GridPositionOrSpan | null, + counterpartResizedPosition: GridPositionOrSpan | null, + edge: GridResizeEdge, +): GridPositionOrSpan | null { + function isFlowResizeOnBound( + wantedBound: 'start' | 'end', + flowStart: GridPositionOrSpan | null, + flowEnd: GridPositionOrSpan | null, + ): boolean { + return ( + (edge === 'column-end' || edge === 'row-end') && + bound === wantedBound && + (isGridSpan(flowStart) || isAutoGridPin(flowStart) || flowStart == null) && + (isAutoGridPin(flowEnd) || flowEnd == null) + ) + } + + const isFlowStart = isFlowResizeOnBound('start', position, counterpart) + if (isFlowStart || isGridSpan(position)) { + if (size === 1) { + return cssKeyword('auto') + } + return gridSpanNumeric(size) + } + + const isFlowEnd = isFlowResizeOnBound('end', counterpart, position) + if (isFlowEnd) { + return cssKeyword('auto') + } + + if ( + isGridSpan(counterpart) && + isGridPositionValue(counterpartResizedPosition) && + counterpartResizedPosition.numericalPosition === 1 && + bound === 'end' + ) { + return cssKeyword('auto') + } + return resizedPosition } diff --git a/editor/src/components/canvas/canvas-strategies/strategies/grid.test-utils.ts b/editor/src/components/canvas/canvas-strategies/strategies/grid.test-utils.ts new file mode 100644 index 000000000000..2250a04742af --- /dev/null +++ b/editor/src/components/canvas/canvas-strategies/strategies/grid.test-utils.ts @@ -0,0 +1,105 @@ +import { getRectCenter, localRectangle } from '../../../../core/shared/math-utils' +import { selectComponentsForTest } from '../../../../utils/utils.test-utils' +import CanvasActions from '../../canvas-actions' +import { GridCellTestId } from '../../controls/grid-controls-for-strategies' +import { + mouseDownAtPoint, + mouseMoveToPoint, + keyDown, + mouseUpAtPoint, +} from '../../event-helpers.test-utils' +import type { EditorRenderResult } from '../../ui-jsx.test-utils' +import type { GridCellCoordinates } from './grid-cell-bounds' +import { gridCellTargetId } from './grid-cell-bounds' +import * as EP from '../../../../core/shared/element-path' +import type { ElementPath } from 'utopia-shared/src/types' +import { CanvasControlsContainerID } from '../../controls/new-canvas-controls' + +export async function runGridMoveTest( + editor: EditorRenderResult, + props: { + scale: number + gridPath: string + testId: string + targetCell?: GridCellCoordinates + draggedCell?: GridCellCoordinates + tab?: boolean + }, + midDragCallback?: (editor: EditorRenderResult) => void, +) { + const elementPathToDrag = EP.appendToPath(EP.fromString(props.gridPath), props.testId) + + await selectComponentsForTest(editor, [elementPathToDrag]) + + if (props.scale !== 1) { + await editor.dispatch([CanvasActions.zoom(props.scale)], true) + } + + // trigger the grid controls so we know where to find the cells + const { sourceGridCell, dragFrom, dragTo } = await getSafeGridMoveTestEndpoints( + editor, + elementPathToDrag, + props.gridPath, + { + draggedCell: props.draggedCell, + targetCell: props.targetCell, + }, + ) + + await mouseDownAtPoint(sourceGridCell, dragFrom) + await mouseMoveToPoint(sourceGridCell, dragTo) + if (props.tab) { + await keyDown('Tab') + } + if (midDragCallback != null) { + midDragCallback(editor) + } + + await mouseUpAtPoint(editor.renderedDOM.getByTestId(CanvasControlsContainerID), dragTo) + + return editor.renderedDOM.getByTestId(props.testId).style +} + +export async function getSafeGridMoveTestEndpoints( + editor: EditorRenderResult, + elementPathToDrag: ElementPath, + gridPath: string, + props?: { + draggedCell?: GridCellCoordinates + targetCell?: GridCellCoordinates + }, +) { + const gridCellTestId = GridCellTestId(elementPathToDrag) + const gridCellBaseElement = editor.renderedDOM.getByTestId(gridCellTestId) + const gridCellBaseElementBox = gridCellBaseElement.getBoundingClientRect() + const testPoint = getRectCenter(localRectangle(gridCellBaseElementBox)) + + await mouseDownAtPoint(gridCellBaseElement, testPoint) + + const sourceGridCell = editor.renderedDOM.getByTestId( + props?.draggedCell == null + ? gridCellTestId + : gridCellTargetId( + EP.fromString(gridPath), + props?.draggedCell.row, + props?.draggedCell.column, + ), + ) + + const targetGridCell = editor.renderedDOM.getByTestId( + gridCellTargetId( + EP.fromString(gridPath), + props?.targetCell?.row ?? 2, + props?.targetCell?.column ?? 3, + ), + ) + const sourceRect = sourceGridCell.getBoundingClientRect() + const dragFrom = { x: sourceRect.x + 5, y: sourceRect.y + 5 } + + const targetRect = targetGridCell.getBoundingClientRect() + const dragTo = { x: targetRect.x + 5, y: targetRect.y + 5 } + + await mouseUpAtPoint(editor.renderedDOM.getByTestId(CanvasControlsContainerID), testPoint) + + return { sourceGridCell, dragFrom, dragTo } +} diff --git a/editor/src/components/canvas/canvas-strategies/strategies/group-helpers.ts b/editor/src/components/canvas/canvas-strategies/strategies/group-helpers.ts index 724aa52032d5..5045588186f0 100644 --- a/editor/src/components/canvas/canvas-strategies/strategies/group-helpers.ts +++ b/editor/src/components/canvas/canvas-strategies/strategies/group-helpers.ts @@ -5,7 +5,7 @@ import { trueUpChildrenOfGroupChanged, } from '../../../../components/editor/store/editor-state' import { getLayoutProperty } from '../../../../core/layout/getLayoutProperty' -import type { StyleLayoutProp } from '../../../../core/layout/layout-helpers-new' +import type { LayoutPinnedProp, StyleLayoutProp } from '../../../../core/layout/layout-helpers-new' import type { PropsOrJSXAttributes } from '../../../../core/model/element-metadata-utils' import { MetadataUtils, @@ -551,7 +551,7 @@ export function isEmptyGroup(metadata: ElementInstanceMetadataMap, path: Element ) } -function fixLengthCommand(path: ElementPath, prop: StyleLayoutProp, size: number): CanvasCommand { +function fixLengthCommand(path: ElementPath, prop: LayoutPinnedProp, size: number): CanvasCommand { return setCssLengthProperty( 'always', path, diff --git a/editor/src/components/canvas/canvas-strategies/strategies/keyboard-absolute-move-strategy.ts b/editor/src/components/canvas/canvas-strategies/strategies/keyboard-absolute-move-strategy.ts index 41c3f2f5eeca..2e15719b0104 100644 --- a/editor/src/components/canvas/canvas-strategies/strategies/keyboard-absolute-move-strategy.ts +++ b/editor/src/components/canvas/canvas-strategies/strategies/keyboard-absolute-move-strategy.ts @@ -19,7 +19,7 @@ import { getInteractionMoveCommandsForSelectedElement, getMultiselectBounds, } from './shared-move-strategies-helpers' -import { setElementsToRerenderCommand } from '../../commands/set-elements-to-rerender-command' + import { setSnappingGuidelines } from '../../commands/set-snapping-guidelines-command' import type { CanvasCommand } from '../../commands/commands' import { @@ -120,8 +120,7 @@ export function keyboardAbsoluteMoveStrategy( commands.push(setSnappingGuidelines('mid-interaction', guidelines)) commands.push(pushIntendedBoundsAndUpdateGroups(intendedBounds, 'starting-metadata')) - commands.push(setElementsToRerenderCommand(selectedElements)) - return strategyApplicationResult(commands) + return strategyApplicationResult(commands, selectedElements) } else { return emptyStrategyApplicationResult } diff --git a/editor/src/components/canvas/canvas-strategies/strategies/keyboard-absolute-resize-strategy.tsx b/editor/src/components/canvas/canvas-strategies/strategies/keyboard-absolute-resize-strategy.tsx index 785a8cd041f1..08b263222543 100644 --- a/editor/src/components/canvas/canvas-strategies/strategies/keyboard-absolute-resize-strategy.tsx +++ b/editor/src/components/canvas/canvas-strategies/strategies/keyboard-absolute-resize-strategy.tsx @@ -12,7 +12,7 @@ import type { CanvasFrameAndTarget, EdgePosition } from '../../canvas-types' import { EdgePositionBottom, EdgePositionRight } from '../../canvas-types' import type { CanvasCommand } from '../../commands/commands' import { pushIntendedBoundsAndUpdateGroups } from '../../commands/push-intended-bounds-and-update-groups-command' -import { setElementsToRerenderCommand } from '../../commands/set-elements-to-rerender-command' + import { setSnappingGuidelines } from '../../commands/set-snapping-guidelines-command' import { AbsoluteResizeControl } from '../../controls/select-mode/absolute-resize-control' import type { CanvasStrategy, InteractionCanvasState } from '../canvas-strategy-types' @@ -44,6 +44,7 @@ import type { ElementInstanceMetadataMap } from '../../../../core/shared/element import type { AllElementProps } from '../../../editor/store/editor-state' import { getDescriptiveStrategyLabelWithRetargetedPaths } from '../canvas-strategies' import { MetadataUtils } from '../../../../core/model/element-metadata-utils' +import { StrategySizeLabel } from '../../controls/select-mode/size-label' interface VectorAndEdge { movement: CanvasVector @@ -145,7 +146,7 @@ export function keyboardAbsoluteResizeStrategy( return null } - if (MetadataUtils.isGridCell(canvasState.startingMetadata, selectedElements[0])) { + if (MetadataUtils.isGridItem(canvasState.startingMetadata, selectedElements[0])) { return null } @@ -166,6 +167,14 @@ export function keyboardAbsoluteResizeStrategy( props: { targets: selectedElements, pathsWereReplaced: pathsWereReplaced }, key: 'absolute-resize-control', show: 'visible-except-when-other-strategy-is-active', + priority: 'top', + }), + controlWithProps({ + control: StrategySizeLabel, + props: { targets: selectedElements, pathsWereReplaced: pathsWereReplaced }, + key: 'size-label', + show: 'visible-except-when-other-strategy-is-active', + priority: 'top', }), ], fitness: getFitness(interactionSession), @@ -225,8 +234,7 @@ export function keyboardAbsoluteResizeStrategy( const guidelines = getKeyboardStrategyGuidelines(snapTargets, interactionSession, newFrame) commands.push(setSnappingGuidelines('mid-interaction', guidelines)) commands.push(pushIntendedBoundsAndUpdateGroups(intendedBounds, 'starting-metadata')) - commands.push(setElementsToRerenderCommand(selectedElements)) - return strategyApplicationResult(commands) + return strategyApplicationResult(commands, selectedElements) } else { return emptyStrategyApplicationResult } diff --git a/editor/src/components/canvas/canvas-strategies/strategies/keyboard-interaction.test-utils.tsx b/editor/src/components/canvas/canvas-strategies/strategies/keyboard-interaction.test-utils.tsx index 70478e0390d7..457a84ac9304 100644 --- a/editor/src/components/canvas/canvas-strategies/strategies/keyboard-interaction.test-utils.tsx +++ b/editor/src/components/canvas/canvas-strategies/strategies/keyboard-interaction.test-utils.tsx @@ -80,6 +80,7 @@ export function pressKeys( const strategy = strategyFactoryFunction( pickCanvasStateFromEditorStateWithMetadata( + editorState.selectedViews, editorState, createBuiltInDependenciesList(null), metadata, diff --git a/editor/src/components/canvas/canvas-strategies/strategies/keyboard-reorder-strategy.ts b/editor/src/components/canvas/canvas-strategies/strategies/keyboard-reorder-strategy.ts index eaa0abf15914..8a0cdcdc73dc 100644 --- a/editor/src/components/canvas/canvas-strategies/strategies/keyboard-reorder-strategy.ts +++ b/editor/src/components/canvas/canvas-strategies/strategies/keyboard-reorder-strategy.ts @@ -5,7 +5,7 @@ import { shallowEqual } from '../../../../core/shared/equality-utils' import { emptyModifiers, Modifier } from '../../../../utils/modifiers' import { absolute } from '../../../../utils/utils' import { reorderElement } from '../../commands/reorder-element-command' -import { setElementsToRerenderCommand } from '../../commands/set-elements-to-rerender-command' + import { updateHighlightedViews } from '../../commands/update-highlighted-views-command' import type { CanvasStrategy, @@ -90,10 +90,8 @@ export function keyboardReorderStrategy( if (newIndex === unpatchedIndex) { return strategyApplicationResult( - [ - updateHighlightedViews('mid-interaction', []), - setElementsToRerenderCommand(siblingsAndParent), - ], + [updateHighlightedViews('mid-interaction', [])], + siblingsAndParent, { lastReorderIdx: newIndex, }, @@ -102,9 +100,9 @@ export function keyboardReorderStrategy( return strategyApplicationResult( [ reorderElement('always', target, absolute(newIndex)), - setElementsToRerenderCommand(siblingsAndParent), updateHighlightedViews('mid-interaction', []), ], + siblingsAndParent, { lastReorderIdx: newIndex, }, diff --git a/editor/src/components/canvas/canvas-strategies/strategies/keyboard-set-font-size-strategy.tsx b/editor/src/components/canvas/canvas-strategies/strategies/keyboard-set-font-size-strategy.tsx index 6bf48e00b559..ba77e1cb87cb 100644 --- a/editor/src/components/canvas/canvas-strategies/strategies/keyboard-set-font-size-strategy.tsx +++ b/editor/src/components/canvas/canvas-strategies/strategies/keyboard-set-font-size-strategy.tsx @@ -79,7 +79,7 @@ export function keyboardSetFontSizeStrategy( ), ) - return strategyApplicationResult(commands) + return strategyApplicationResult(commands, validTargets) }, } } diff --git a/editor/src/components/canvas/canvas-strategies/strategies/keyboard-set-font-weight-strategy.tsx b/editor/src/components/canvas/canvas-strategies/strategies/keyboard-set-font-weight-strategy.tsx index faab0cfca6d9..3d6a7cee0843 100644 --- a/editor/src/components/canvas/canvas-strategies/strategies/keyboard-set-font-weight-strategy.tsx +++ b/editor/src/components/canvas/canvas-strategies/strategies/keyboard-set-font-weight-strategy.tsx @@ -87,7 +87,7 @@ export function keyboardSetFontWeightStrategy( ), ) - return strategyApplicationResult(commands) + return strategyApplicationResult(commands, validTargets) }, } } diff --git a/editor/src/components/canvas/canvas-strategies/strategies/keyboard-set-opacity-strategy.tsx b/editor/src/components/canvas/canvas-strategies/strategies/keyboard-set-opacity-strategy.tsx index f3b9c1350ad2..10a12118f16b 100644 --- a/editor/src/components/canvas/canvas-strategies/strategies/keyboard-set-opacity-strategy.tsx +++ b/editor/src/components/canvas/canvas-strategies/strategies/keyboard-set-opacity-strategy.tsx @@ -57,7 +57,7 @@ export function keyboardSetOpacityStrategy( setProperty('always', path, PP.create('style', 'opacity'), inputValue), ) - return strategyApplicationResult(commands) + return strategyApplicationResult(commands, selectedElements) }, } } diff --git a/editor/src/components/canvas/canvas-strategies/strategies/rearrange-grid-swap-strategy.spec.browser2.tsx b/editor/src/components/canvas/canvas-strategies/strategies/rearrange-grid-swap-strategy.spec.browser2.tsx deleted file mode 100644 index ed0edd56ee70..000000000000 --- a/editor/src/components/canvas/canvas-strategies/strategies/rearrange-grid-swap-strategy.spec.browser2.tsx +++ /dev/null @@ -1,206 +0,0 @@ -import { getPrintedUiJsCode, renderTestEditorWithCode } from '../../ui-jsx.test-utils' -import * as EP from '../../../../core/shared/element-path' -import { selectComponents } from '../../../../components/editor/actions/meta-actions' -import { CanvasControlsContainerID } from '../../controls/new-canvas-controls' -import { - mouseDownAtPoint, - mouseMoveToPoint, - mouseUpAtPoint, - pressKey, -} from '../../event-helpers.test-utils' -import { canvasPoint } from '../../../../core/shared/math-utils' -import { GridCellTestId } from '../../controls/grid-controls' - -const testProject = ` -import * as React from 'react' -import { Storyboard } from 'utopia-api' - -export var storyboard = ( - -
-
-
-
-
-
-
-
-
-
-
- -) -` - -describe('swap an element', () => { - it('swap out an element for another from a different column and row', async () => { - const renderResult = await renderTestEditorWithCode(testProject, 'await-first-dom-report') - const draggedItem = EP.fromString(`sb/grid/row-1-column-2`) - await renderResult.dispatch(selectComponents([draggedItem], false), true) - await renderResult.getDispatchFollowUpActionsFinished() - const canvasControlsLayer = renderResult.renderedDOM.getByTestId(CanvasControlsContainerID) - const draggedItemElement = renderResult.renderedDOM.getByTestId( - GridCellTestId(EP.fromString(`sb/grid/row-1-column-2`)), - ) - const draggedItemRect = draggedItemElement.getBoundingClientRect() - const startPoint = canvasPoint({ - x: draggedItemRect.x + draggedItemRect.width / 2, - y: draggedItemRect.y + draggedItemRect.height / 2, - }) - const swapTargetElement = renderResult.renderedDOM.getByTestId( - GridCellTestId(EP.fromString(`sb/grid/row-2-column-1`)), - ) - const swapTargetRect = swapTargetElement.getBoundingClientRect() - const endPoint = canvasPoint({ - x: swapTargetRect.x + swapTargetRect.width / 2, - y: swapTargetRect.y + swapTargetRect.height / 2, - }) - await mouseMoveToPoint(draggedItemElement, startPoint) - await mouseDownAtPoint(draggedItemElement, startPoint) - await mouseMoveToPoint(canvasControlsLayer, endPoint) - await pressKey('Tab') - await renderResult.getDispatchFollowUpActionsFinished() - expect( - renderResult.getEditorState().editor.canvas.interactionSession?.userPreferredStrategy, - ).toEqual('rearrange-grid-swap-strategy') - await mouseUpAtPoint(canvasControlsLayer, endPoint) - await renderResult.getDispatchFollowUpActionsFinished() - - expect(getPrintedUiJsCode(renderResult.getEditorState())) - .toEqual(`import * as React from 'react' -import { Storyboard } from 'utopia-api' - -export var storyboard = ( - -
-
-
-
-
-
-
-
-
-
-
- -) -`) - }) -}) diff --git a/editor/src/components/canvas/canvas-strategies/strategies/rearrange-grid-swap-strategy.ts b/editor/src/components/canvas/canvas-strategies/strategies/rearrange-grid-swap-strategy.ts deleted file mode 100644 index 85688d7c2441..000000000000 --- a/editor/src/components/canvas/canvas-strategies/strategies/rearrange-grid-swap-strategy.ts +++ /dev/null @@ -1,201 +0,0 @@ -import { MetadataUtils } from '../../../../core/model/element-metadata-utils' -import * as EP from '../../../../core/shared/element-path' -import type { - ElementInstanceMetadata, - GridContainerProperties, -} from '../../../../core/shared/element-template' -import { - isFiniteRectangle, - offsetPoint, - rectContainsPointInclusive, -} from '../../../../core/shared/math-utils' -import type { ElementPath } from '../../../../core/shared/project-file-types' -import { create } from '../../../../core/shared/property-path' -import type { CanvasCommand } from '../../commands/commands' -import { deleteProperties } from '../../commands/delete-properties-command' -import { rearrangeChildren } from '../../commands/rearrange-children-command' -import { GridControls, GridControlsKey } from '../../controls/grid-controls' -import { recurseIntoChildrenOfMapOrFragment } from '../../gap-utils' -import type { CanvasStrategyFactory } from '../canvas-strategies' -import { onlyFitWhenDraggingThisControl } from '../canvas-strategies' -import type { InteractionCanvasState } from '../canvas-strategy-types' -import { - getTargetPathsFromInteractionTarget, - emptyStrategyApplicationResult, - strategyApplicationResult, -} from '../canvas-strategy-types' -import type { InteractionSession } from '../interaction-state' -import { setGridPropsCommands } from './grid-helpers' - -export const rearrangeGridSwapStrategy: CanvasStrategyFactory = ( - canvasState: InteractionCanvasState, - interactionSession: InteractionSession | null, -) => { - const selectedElements = getTargetPathsFromInteractionTarget(canvasState.interactionTarget) - if ( - selectedElements.length !== 1 || - interactionSession == null || - interactionSession.interactionData.type !== 'DRAG' || - interactionSession.interactionData.drag == null || - interactionSession.activeControl.type !== 'GRID_CELL_HANDLE' || - interactionSession.interactionData.modifiers.alt || - interactionSession.interactionData.modifiers.cmd - ) { - return null - } - - const selectedElement = selectedElements[0] - - if (!MetadataUtils.isGridCell(canvasState.startingMetadata, selectedElement)) { - return null - } - - const children = recurseIntoChildrenOfMapOrFragment( - canvasState.startingMetadata, - canvasState.startingAllElementProps, - canvasState.startingElementPathTree, - EP.parentPath(selectedElement), - ) - - const parentGridPath = EP.parentPath(selectedElement) - - return { - id: 'rearrange-grid-swap-strategy', - name: 'Rearrange Grid (Swap)', - descriptiveLabel: 'Rearrange Grid (Swap)', - icon: { - category: 'tools', - type: 'pointer', - }, - controlsToRender: [ - { - control: GridControls, - props: { targets: [parentGridPath] }, - key: GridControlsKey(parentGridPath), - show: 'always-visible', - priority: 'bottom', - }, - ], - fitness: onlyFitWhenDraggingThisControl(interactionSession, 'GRID_CELL_HANDLE', 1), - apply: () => { - if ( - interactionSession == null || - interactionSession.interactionData.type !== 'DRAG' || - interactionSession.interactionData.drag == null || - interactionSession.activeControl.type !== 'GRID_CELL_HANDLE' - ) { - return emptyStrategyApplicationResult - } - - const pointOnCanvas = offsetPoint( - interactionSession.interactionData.dragStart, - interactionSession.interactionData.drag, - ) - - const pointerOverChild = children.find( - (c) => - c.globalFrame != null && - isFiniteRectangle(c.globalFrame) && - rectContainsPointInclusive(c.globalFrame, pointOnCanvas), - ) - - const container = MetadataUtils.findElementByElementPath( - canvasState.startingMetadata, - EP.parentPath(selectedElement), - ) - if (container == null) { - return emptyStrategyApplicationResult - } - const gridTemplate = container.specialSizeMeasurements.containerGridProperties - - let commands: CanvasCommand[] = [] - - if ( - pointerOverChild != null && - EP.toUid(pointerOverChild.elementPath) !== interactionSession.activeControl.id - ) { - commands.push( - ...swapChildrenCommands({ - grabbedElementUid: interactionSession.activeControl.id, - swapToElementUid: EP.toUid(pointerOverChild.elementPath), - children: children, - parentPath: EP.parentPath(selectedElement), - gridTemplate: gridTemplate, - }), - ) - } - - if (commands == null) { - return emptyStrategyApplicationResult - } - - return strategyApplicationResult(commands) - }, - } -} - -const GridPositioningProps: Array = [ - 'gridColumn', - 'gridRow', - 'gridColumnStart', - 'gridColumnEnd', - 'gridRowStart', - 'gridRowEnd', -] - -function swapChildrenCommands({ - grabbedElementUid, - swapToElementUid, - children, - parentPath, - gridTemplate, -}: { - grabbedElementUid: string - swapToElementUid: string - children: ElementInstanceMetadata[] - parentPath: ElementPath - gridTemplate: GridContainerProperties -}): CanvasCommand[] { - const grabbedElement = children.find((c) => EP.toUid(c.elementPath) === grabbedElementUid) - const swapToElement = children.find((c) => EP.toUid(c.elementPath) === swapToElementUid) - - if (grabbedElement == null || swapToElement == null) { - return [] - } - - const rearrangedChildren = children - .map((c) => { - if (EP.pathsEqual(c.elementPath, grabbedElement.elementPath)) { - return swapToElement.elementPath - } - if (EP.pathsEqual(c.elementPath, swapToElement.elementPath)) { - return grabbedElement.elementPath - } - return c.elementPath - }) - .map((path) => EP.dynamicPathToStaticPath(path)) - - return [ - rearrangeChildren('always', parentPath, rearrangedChildren), - deleteProperties( - 'always', - swapToElement.elementPath, - GridPositioningProps.map((p) => create('style', p)), - ), - deleteProperties( - 'always', - grabbedElement.elementPath, - GridPositioningProps.map((p) => create('style', p)), - ), - ...setGridPropsCommands( - grabbedElement.elementPath, - gridTemplate, - swapToElement.specialSizeMeasurements.elementGridProperties, - ), - ...setGridPropsCommands( - swapToElement.elementPath, - gridTemplate, - grabbedElement.specialSizeMeasurements.elementGridProperties, - ), - ] -} diff --git a/editor/src/components/canvas/canvas-strategies/strategies/reorder-slider-strategy.tsx b/editor/src/components/canvas/canvas-strategies/strategies/reorder-slider-strategy.tsx index 73ed8070f7f4..6ed0746c318c 100644 --- a/editor/src/components/canvas/canvas-strategies/strategies/reorder-slider-strategy.tsx +++ b/editor/src/components/canvas/canvas-strategies/strategies/reorder-slider-strategy.tsx @@ -4,7 +4,7 @@ import { absolute } from '../../../../utils/utils' import { CSSCursor } from '../../canvas-types' import { reorderElement } from '../../commands/reorder-element-command' import { setCursorCommand } from '../../commands/set-cursor-command' -import { setElementsToRerenderCommand } from '../../commands/set-elements-to-rerender-command' + import { updateHighlightedViews } from '../../commands/update-highlighted-views-command' import { ReorderSliderControl } from '../../controls/reorder-slider-control' import type { CanvasStrategy, InteractionCanvasState } from '../canvas-strategy-types' @@ -83,6 +83,7 @@ export function reorderSliderStategy( if (!isReorderAllowed(siblingsOfTarget)) { return strategyApplicationResult( [setCursorCommand(CSSCursor.NotPermitted)], + [], {}, 'failure', ) @@ -103,17 +104,17 @@ export function reorderSliderStategy( return strategyApplicationResult( [ reorderElement('always', target, absolute(newIndex)), - setElementsToRerenderCommand(siblingsAndParent), updateHighlightedViews('mid-interaction', []), setCursorCommand(CSSCursor.ResizeEW), ], + siblingsAndParent, { lastReorderIdx: newIndex, }, ) } else { // Fallback for when the checks above are not satisfied. - return strategyApplicationResult([setCursorCommand(CSSCursor.ResizeEW)]) + return strategyApplicationResult([setCursorCommand(CSSCursor.ResizeEW)], []) } } else { return emptyStrategyApplicationResult diff --git a/editor/src/components/canvas/canvas-strategies/strategies/reorder-utils.ts b/editor/src/components/canvas/canvas-strategies/strategies/reorder-utils.ts index 094a48b0656c..7031a7ad78f6 100644 --- a/editor/src/components/canvas/canvas-strategies/strategies/reorder-utils.ts +++ b/editor/src/components/canvas/canvas-strategies/strategies/reorder-utils.ts @@ -13,7 +13,7 @@ import { absolute } from '../../../../utils/utils' import { CSSCursor } from '../../canvas-types' import { reorderElement } from '../../commands/reorder-element-command' import { setCursorCommand } from '../../commands/set-cursor-command' -import { setElementsToRerenderCommand } from '../../commands/set-elements-to-rerender-command' + import { updateHighlightedViews } from '../../commands/update-highlighted-views-command' import type { CustomStrategyState, @@ -36,7 +36,6 @@ function isRootOfGeneratedElement(target: ElementPath): boolean { } export function applyReorderCommon( - originalTargets: Array, targets: Array, canvasState: InteractionCanvasState, interactionSession: InteractionSession, @@ -61,7 +60,12 @@ export function applyReorderCommon( const siblingsAndParent = siblings.concat(EP.parentPath(target)) if (!isReorderAllowed(siblings)) { - return strategyApplicationResult([setCursorCommand(CSSCursor.NotPermitted)], {}, 'failure') + return strategyApplicationResult( + [setCursorCommand(CSSCursor.NotPermitted)], + [], + {}, + 'failure', + ) } const pointOnCanvas = offsetPoint( @@ -101,9 +105,9 @@ export function applyReorderCommon( { action: 'reorder', target: activeFrameTargetRect(targetFrame), source: sourceFrame }, ]), updateHighlightedViews('mid-interaction', []), - setElementsToRerenderCommand(siblingsAndParent), setCursorCommand(CSSCursor.Move), ], + siblingsAndParent, { lastReorderIdx: newResultOrLastIndex, }, @@ -115,10 +119,10 @@ export function applyReorderCommon( { action: 'reorder', target: activeFrameTargetRect(targetFrame), source: sourceFrame }, ]), reorderElement('always', target, absolute(newResultOrLastIndex)), - setElementsToRerenderCommand(siblingsAndParent), updateHighlightedViews('mid-interaction', []), setCursorCommand(CSSCursor.Move), ], + siblingsAndParent, { lastReorderIdx: newResultOrLastIndex, }, @@ -126,7 +130,7 @@ export function applyReorderCommon( } } else { // Fallback for when the checks above are not satisfied. - return strategyApplicationResult([setCursorCommand(CSSCursor.Move)]) + return strategyApplicationResult([setCursorCommand(CSSCursor.Move)], []) } } diff --git a/editor/src/components/canvas/canvas-strategies/strategies/reparent-as-static-strategy.tsx b/editor/src/components/canvas/canvas-strategies/strategies/reparent-as-static-strategy.tsx index a932268764d4..7eb3bb7f5d2c 100644 --- a/editor/src/components/canvas/canvas-strategies/strategies/reparent-as-static-strategy.tsx +++ b/editor/src/components/canvas/canvas-strategies/strategies/reparent-as-static-strategy.tsx @@ -14,7 +14,6 @@ import type { CanvasCommand } from '../../commands/commands' import { reorderElement } from '../../commands/reorder-element-command' import { activeFrameTargetRect, setActiveFrames } from '../../commands/set-active-frames-command' import { setCursorCommand } from '../../commands/set-cursor-command' -import { setElementsToRerenderCommand } from '../../commands/set-elements-to-rerender-command' import { showReorderIndicator } from '../../commands/show-reorder-indicator-command' import { updateHighlightedViews } from '../../commands/update-highlighted-views-command' import { updateSelectedViews } from '../../commands/update-selected-views-command' @@ -229,7 +228,7 @@ export function applyStaticReparent( const commandsAfterReorder = [ ...propertyChangeCommands, - setElementsToRerenderCommand(elementsToRerender), + updateHighlightedViews('mid-interaction', []), setCursorCommand(CSSCursor.Move), ] @@ -348,6 +347,7 @@ export function applyStaticReparent( return { commands: [...midInteractionCommands, ...interactionFinishCommands], + elementsToRerender: elementsToRerender, customStatePatch: { duplicatedElementNewUids, elementsToRerender }, status: 'success', } diff --git a/editor/src/components/canvas/canvas-strategies/strategies/reparent-helpers/reparent-helpers.ts b/editor/src/components/canvas/canvas-strategies/strategies/reparent-helpers/reparent-helpers.ts index a8df61f98d0b..331f23695d47 100644 --- a/editor/src/components/canvas/canvas-strategies/strategies/reparent-helpers/reparent-helpers.ts +++ b/editor/src/components/canvas/canvas-strategies/strategies/reparent-helpers/reparent-helpers.ts @@ -75,8 +75,9 @@ import type { ReparentTargetForPaste } from '../reparent-utils' import { cleanSteganoTextData } from '../../../../../core/shared/stegano-text' import { assertNever } from '../../../../../core/shared/utils' +export type ShouldAddContainLayout = 'add-contain-layout' | 'dont-add-contain-layout' + export function isAllowedToReparent( - projectContents: ProjectContentTreeRoot, startingMetadata: ElementInstanceMetadataMap, elementToReparent: ElementPath, targetParentPath: ElementPath, @@ -112,15 +113,12 @@ export function isAllowedToReparent( return foldEither( (_) => true, - (elementFromMetadata) => - !elementReferencesElsewhere(elementFromMetadata) && - MetadataUtils.targetHonoursPropsPosition(projectContents, metadata) !== 'does-not-honour', + (elementFromMetadata) => !elementReferencesElsewhere(elementFromMetadata), metadata.element, ) } export function isAllowedToNavigatorReparent( - projectContents: ProjectContentTreeRoot, startingMetadata: ElementInstanceMetadataMap, target: ElementPath, ): boolean { @@ -135,14 +133,8 @@ export function isAllowedToNavigatorReparent( return maybeBranchConditionalCase(parentPath, conditional, target) != null } return false - } else { - return foldEither( - (_) => true, - (elementFromMetadata) => - MetadataUtils.targetHonoursPropsPosition(projectContents, metadata) !== 'does-not-honour', - metadata.element, - ) } + return true } } @@ -239,17 +231,12 @@ export function ifAllowedToReparent( ifAllowed: () => StrategyApplicationResult, ): StrategyApplicationResult { const allowed = elementsToReparent.every((elementToReparent) => { - return isAllowedToReparent( - canvasState.projectContents, - startingMetadata, - elementToReparent, - targetParentPath, - ) + return isAllowedToReparent(startingMetadata, elementToReparent, targetParentPath) }) if (allowed) { return ifAllowed() } else { - return strategyApplicationResult([setCursorCommand(CSSCursor.NotPermitted)], {}, 'failure') + return strategyApplicationResult([setCursorCommand(CSSCursor.NotPermitted)], [], {}, 'failure') } } @@ -531,7 +518,7 @@ export function absolutePositionForReparent( } const localFrame = zeroRectIfNullOrInfinity( - MetadataUtils.getLocalFrame(targetParent, metadata.currentMetadata) ?? null, + MetadataUtils.getLocalFrame(targetParent, metadata.currentMetadata, null) ?? null, ) // offset the element with the target parent's offset, since the target parent doesn't diff --git a/editor/src/components/canvas/canvas-strategies/strategies/reparent-helpers/reparent-property-changes.ts b/editor/src/components/canvas/canvas-strategies/strategies/reparent-helpers/reparent-property-changes.ts index 86a6f01ccc2d..f8053cd0a8f6 100644 --- a/editor/src/components/canvas/canvas-strategies/strategies/reparent-helpers/reparent-property-changes.ts +++ b/editor/src/components/canvas/canvas-strategies/strategies/reparent-helpers/reparent-property-changes.ts @@ -67,12 +67,19 @@ import { treatElementAsFragmentLike, } from '../fragment-like-helpers' import type { OldPathToNewPathMapping } from '../../post-action-options/post-action-paste' +import type { ShouldAddContainLayout } from './reparent-helpers' const propertiesToRemove: Array = [ PP.create('style', 'left'), PP.create('style', 'top'), PP.create('style', 'right'), PP.create('style', 'bottom'), + PP.create('style', 'gridRow'), + PP.create('style', 'gridColumn'), + PP.create('style', 'gridRowStart'), + PP.create('style', 'gridRowEnd'), + PP.create('style', 'gridColumnStart'), + PP.create('style', 'gridColumnEnd'), ] export type ForcePins = 'force-pins' | 'do-not-force-pins' @@ -84,6 +91,7 @@ export function getAbsoluteReparentPropertyChanges( newParentStartingMetadata: ElementInstanceMetadataMap, projectContents: ProjectContentTreeRoot, forcePins: ForcePins, + willContainLayoutBeAdded: ShouldAddContainLayout, ): Array { const element: JSXElement | null = getJSXElementFromProjectContents(target, projectContents) @@ -102,7 +110,10 @@ export function getAbsoluteReparentPropertyChanges( const currentParentContentBox = MetadataUtils.getGlobalContentBoxForChildren(originalParentInstance) - const newParentContentBox = MetadataUtils.getGlobalContentBoxForChildren(newParentInstance) + const newParentContentBox = + willContainLayoutBeAdded === 'add-contain-layout' + ? nullIfInfinity(newParentInstance.globalFrame) + : MetadataUtils.getGlobalContentBoxForChildren(newParentInstance) if (currentParentContentBox == null || newParentContentBox == null) { return [] @@ -343,6 +354,7 @@ export function getReparentPropertyChanges( currentMetadata, projectContents, 'force-pins', + 'dont-add-contain-layout', ) const strategyCommands = runReparentPropertyStrategies([ diff --git a/editor/src/components/canvas/canvas-strategies/strategies/reparent-helpers/reparent-property-strategies.ts b/editor/src/components/canvas/canvas-strategies/strategies/reparent-helpers/reparent-property-strategies.ts index 39c06c04f062..526cb7d73a83 100644 --- a/editor/src/components/canvas/canvas-strategies/strategies/reparent-helpers/reparent-property-strategies.ts +++ b/editor/src/components/canvas/canvas-strategies/strategies/reparent-helpers/reparent-property-strategies.ts @@ -313,6 +313,7 @@ export const convertFragmentLikeChildrenToVisualSize = metadata.currentMetadata, projectContents, 'force-pins', + 'dont-add-contain-layout', ) } else { const directions = singleAxisAutoLayoutContainerDirections( diff --git a/editor/src/components/canvas/canvas-strategies/strategies/reparent-metastrategy.tsx b/editor/src/components/canvas/canvas-strategies/strategies/reparent-metastrategy.tsx index a5d79e59521d..41478bff23f0 100644 --- a/editor/src/components/canvas/canvas-strategies/strategies/reparent-metastrategy.tsx +++ b/editor/src/components/canvas/canvas-strategies/strategies/reparent-metastrategy.tsx @@ -274,7 +274,8 @@ export const reparentMetaStrategy: MetaCanvasStrategy = ( if ( reparentSubjects.length === 0 || interactionSession == null || - interactionSession.activeControl.type !== 'BOUNDING_AREA' || + (interactionSession.activeControl.type !== 'BOUNDING_AREA' && + interactionSession.activeControl.type !== 'GRID_CELL_HANDLE') || interactionSession.interactionData.type !== 'DRAG' || interactionSession.interactionData.drag == null || interactionSession.interactionData.modifiers.alt diff --git a/editor/src/components/canvas/canvas-strategies/strategies/reparent-utils.ts b/editor/src/components/canvas/canvas-strategies/strategies/reparent-utils.ts index 0b148574d5e8..8616aa1d39c3 100644 --- a/editor/src/components/canvas/canvas-strategies/strategies/reparent-utils.ts +++ b/editor/src/components/canvas/canvas-strategies/strategies/reparent-utils.ts @@ -538,6 +538,36 @@ function pasteNextToSameSizedElement( return null } +function pasteIntoNextGridCell( + copyData: ParsedCopyData, + selectedViews: NonEmptyArray, + metadata: ElementInstanceMetadataMap, +): ReparentTargetForPaste | null { + if (selectedViews.length === 0) { + return null + } + const selectedView = lastOfNonEmptyArray(selectedViews) + + // if the copied elements are grid cells and the target is a grid cell, paste next to it + + const copyDataElementsAreGridCells = copyData.elementPaste.every(({ originalElementPath }) => + MetadataUtils.isGridItem(copyData.originalContextMetadata, originalElementPath), + ) + if (!copyDataElementsAreGridCells) { + return null + } + + const targetIsGridChild = MetadataUtils.isGridItem(metadata, selectedView) + if (!targetIsGridChild) { + return null + } + + return { + type: 'parent', + parentPath: childInsertionPath(EP.parentPath(selectedView)), + } +} + function canInsertIntoTarget( projectContents: ProjectContentTreeRoot, metadata: ElementInstanceMetadataMap, @@ -762,6 +792,11 @@ export function getTargetParentForPaste( return right(pasteNextToSameSizedElementResult) } + const paseIntoNextGridCellResult = pasteIntoNextGridCell(copyData, selectedViews, metadata) + if (paseIntoNextGridCellResult != null) { + return right(paseIntoNextGridCellResult) + } + const pasteIntoParentOrGrandparentResult = pasteIntoParentOrGrandparent( copyData.elementPaste.map((e) => e.element), projectContents, diff --git a/editor/src/components/canvas/canvas-strategies/strategies/resize-grid-strategy.spec.browser2.tsx b/editor/src/components/canvas/canvas-strategies/strategies/resize-grid-strategy.spec.browser2.tsx index 67593bcedcee..2669d33be3a7 100644 --- a/editor/src/components/canvas/canvas-strategies/strategies/resize-grid-strategy.spec.browser2.tsx +++ b/editor/src/components/canvas/canvas-strategies/strategies/resize-grid-strategy.spec.browser2.tsx @@ -5,7 +5,12 @@ import { CanvasControlsContainerID } from '../../controls/new-canvas-controls' import { mouseDownAtPoint, mouseMoveToPoint, mouseUpAtPoint } from '../../event-helpers.test-utils' import { canvasPoint } from '../../../../core/shared/math-utils' -const makeTestProject = (columns: string, rows: string) => ` +const makeTestProject = (params: { + columns: string | null + rows: string | null + shorthand: string | null + height?: number | string +}) => ` import * as React from 'react' import { Storyboard } from 'utopia-api' @@ -21,10 +26,10 @@ export var storyboard = ( display: 'grid', gap: 10, width: 600, - height: 600, - gridTemplateColumns: '${columns}', - gridTemplateRows: '${rows}', - height: 'max-content', + height: ${params.height ?? "'max-content'"}, + ${params.columns != null ? `gridTemplateColumns: '${params.columns}',` : ''} + ${params.rows != null ? `gridTemplateRows: '${params.rows}',` : ''} + ${params.shorthand != null ? `gridTemplate: '${params.shorthand}',` : ''} }} >
` +import * as React from 'react' +import { Storyboard } from 'utopia-api' + +export var storyboard = ( + +
+ {[1,2,3].map(n => { + return
+ })} +
+ +) +` + describe('resize a grid', () => { it('update a fractionally sized column', async () => { const renderResult = await renderTestEditorWithCode( - makeTestProject('2.4fr 1fr 1fr', '99px 109px 90px'), + makeTestProject({ columns: '2.4fr 1fr 1fr', rows: '99px 109px 90px', shorthand: null }), 'await-first-dom-report', ) - const target = EP.fromString(`sb/grid/row-1-column-2`) + const target = EP.fromString(`sb/grid`) await renderResult.dispatch(selectComponents([target], false), true) await renderResult.getDispatchFollowUpActionsFinished() const canvasControlsLayer = renderResult.renderedDOM.getByTestId(CanvasControlsContainerID) - const resizeControl = renderResult.renderedDOM.getByTestId(`grid-column-handle-1`) + const resizeControl = renderResult.renderedDOM.getByTestId(`grid-track-size-column-handle-1`) const resizeControlRect = resizeControl.getBoundingClientRect() const startPoint = canvasPoint({ x: resizeControlRect.x + resizeControlRect.width / 2, y: resizeControlRect.y + resizeControlRect.height / 2, }) const endPoint = canvasPoint({ - x: startPoint.x + 20, + x: startPoint.x + 100, y: startPoint.y, }) await mouseMoveToPoint(resizeControl, startPoint) @@ -137,10 +176,9 @@ export var storyboard = ( display: 'grid', gap: 10, width: 600, - height: 600, - gridTemplateColumns: '2.4fr 2.5fr 1fr', - gridTemplateRows: '99px 109px 90px', height: 'max-content', + gridTemplateColumns: '2.4fr 1.8fr 1fr', + gridTemplateRows: '99px 109px 90px', }} >
{ + const renderResult = await renderTestEditorWithCode( + makeTestProjectForAGeneratedGrid('2.4fr 1fr 1fr', '99px 109px 90px'), + 'await-first-dom-report', + ) + const target = EP.fromString(`sb/grid`) + await renderResult.dispatch(selectComponents([target], false), true) + await renderResult.getDispatchFollowUpActionsFinished() + const canvasControlsLayer = renderResult.renderedDOM.getByTestId(CanvasControlsContainerID) + const resizeControl = renderResult.renderedDOM.getByTestId(`grid-track-size-column-handle-1`) + const resizeControlRect = resizeControl.getBoundingClientRect() + const startPoint = canvasPoint({ + x: resizeControlRect.x + resizeControlRect.width / 2, + y: resizeControlRect.y + resizeControlRect.height / 2, + }) + const endPoint = canvasPoint({ + x: startPoint.x + 100, + y: startPoint.y, + }) + await mouseMoveToPoint(resizeControl, startPoint) + await mouseDownAtPoint(resizeControl, startPoint) + await mouseMoveToPoint(canvasControlsLayer, endPoint) + await mouseUpAtPoint(canvasControlsLayer, endPoint) + await renderResult.getDispatchFollowUpActionsFinished() + + expect(getPrintedUiJsCode(renderResult.getEditorState())) + .toEqual(`import * as React from 'react' +import { Storyboard } from 'utopia-api' + +export var storyboard = ( + +
+ {[1, 2, 3].map((n) => { + return ( +
+ ) + })} +
+ +) +`) + }) + it('update a pixel sized row', async () => { const renderResult = await renderTestEditorWithCode( - makeTestProject('2.4fr 1fr 1fr', '99px 109px 90px'), + makeTestProject({ columns: '2.4fr 1fr 1fr', rows: '99px 109px 90px', shorthand: null }), 'await-first-dom-report', ) - const target = EP.fromString(`sb/grid/row-1-column-2`) + const target = EP.fromString(`sb/grid`) await renderResult.dispatch(selectComponents([target], false), true) await renderResult.getDispatchFollowUpActionsFinished() const canvasControlsLayer = renderResult.renderedDOM.getByTestId(CanvasControlsContainerID) - const resizeControl = renderResult.renderedDOM.getByTestId(`grid-row-handle-1`) + const resizeControl = renderResult.renderedDOM.getByTestId(`grid-track-size-row-handle-1`) const resizeControlRect = resizeControl.getBoundingClientRect() const startPoint = canvasPoint({ x: resizeControlRect.x + resizeControlRect.width / 2, @@ -253,10 +352,9 @@ export var storyboard = ( display: 'grid', gap: 10, width: 600, - height: 600, - gridTemplateColumns: '2.4fr 1fr 1fr', - gridTemplateRows: '99px 129px 90px 0px', height: 'max-content', + gridTemplateColumns: '2.4fr 1fr 1fr', + gridTemplateRows: '99px 129px 90px', }} >
{ const renderResult = await renderTestEditorWithCode( - makeTestProject('repeat(3, 1fr)', '99px 109px 90px'), + makeTestProject({ columns: 'repeat(3, 1fr)', rows: '99px 109px 90px', shorthand: null }), 'await-first-dom-report', ) - const target = EP.fromString(`sb/grid/row-1-column-2`) + const target = EP.fromString(`sb/grid`) await renderResult.dispatch(selectComponents([target], false), true) await renderResult.getDispatchFollowUpActionsFinished() const canvasControlsLayer = renderResult.renderedDOM.getByTestId(CanvasControlsContainerID) - const resizeControl = renderResult.renderedDOM.getByTestId(`grid-column-handle-1`) + const resizeControl = renderResult.renderedDOM.getByTestId(`grid-track-size-column-handle-1`) const resizeControlRect = resizeControl.getBoundingClientRect() const startPoint = canvasPoint({ x: resizeControlRect.x + resizeControlRect.width / 2, y: resizeControlRect.y + resizeControlRect.height / 2, }) const endPoint = canvasPoint({ - x: startPoint.x + 20, + x: startPoint.x + 100, y: startPoint.y, }) await mouseMoveToPoint(resizeControl, startPoint) @@ -369,10 +467,129 @@ export var storyboard = ( display: 'grid', gap: 10, width: 600, - height: 600, - gridTemplateColumns: '1fr 2fr 1fr', + height: 'max-content', + gridTemplateColumns: 'repeat(3, 1.5fr)', gridTemplateRows: '99px 109px 90px', + }} + > +
+
+
+
+
+
+
+
+
+
+ +) +`) + }) + + describe('shorthand handling', () => { + it('removes the shorthand and adds the longhand for both the other axii', async () => { + const renderResult = await renderTestEditorWithCode( + makeTestProject({ + shorthand: '99px 109px 90px / 2.4fr 1fr 1fr', + columns: null, + rows: null, + }), + 'await-first-dom-report', + ) + const target = EP.fromString(`sb/grid`) + await renderResult.dispatch(selectComponents([target], false), true) + await renderResult.getDispatchFollowUpActionsFinished() + const canvasControlsLayer = renderResult.renderedDOM.getByTestId(CanvasControlsContainerID) + const resizeControl = renderResult.renderedDOM.getByTestId(`grid-track-size-column-handle-1`) + const resizeControlRect = resizeControl.getBoundingClientRect() + const startPoint = canvasPoint({ + x: resizeControlRect.x + resizeControlRect.width / 2, + y: resizeControlRect.y + resizeControlRect.height / 2, + }) + const endPoint = canvasPoint({ + x: startPoint.x + 100, + y: startPoint.y, + }) + await mouseMoveToPoint(resizeControl, startPoint) + await mouseDownAtPoint(resizeControl, startPoint) + await mouseMoveToPoint(canvasControlsLayer, endPoint) + await mouseUpAtPoint(canvasControlsLayer, endPoint) + await renderResult.getDispatchFollowUpActionsFinished() + + expect(getPrintedUiJsCode(renderResult.getEditorState())) + .toEqual(`import * as React from 'react' +import { Storyboard } from 'utopia-api' + +export var storyboard = ( + +
) `) + }) + it("removes the shorthand without adding the longhand if it's auto", async () => { + const renderResult = await renderTestEditorWithCode( + makeTestProject({ + shorthand: 'auto / 2.4fr 1fr 1fr', + columns: null, + rows: null, + height: 600, + }), + 'await-first-dom-report', + ) + const target = EP.fromString(`sb/grid`) + await renderResult.dispatch(selectComponents([target], false), true) + await renderResult.getDispatchFollowUpActionsFinished() + const canvasControlsLayer = renderResult.renderedDOM.getByTestId(CanvasControlsContainerID) + const resizeControl = renderResult.renderedDOM.getByTestId(`grid-track-size-column-handle-1`) + const resizeControlRect = resizeControl.getBoundingClientRect() + const startPoint = canvasPoint({ + x: resizeControlRect.x + resizeControlRect.width / 2, + y: resizeControlRect.y + resizeControlRect.height / 2, + }) + const endPoint = canvasPoint({ + x: startPoint.x + 100, + y: startPoint.y, + }) + await mouseMoveToPoint(resizeControl, startPoint) + await mouseDownAtPoint(resizeControl, startPoint) + await mouseMoveToPoint(canvasControlsLayer, endPoint) + await mouseUpAtPoint(canvasControlsLayer, endPoint) + await renderResult.getDispatchFollowUpActionsFinished() + + expect(getPrintedUiJsCode(renderResult.getEditorState())) + .toEqual(`import * as React from 'react' +import { Storyboard } from 'utopia-api' + +export var storyboard = ( + +
+
+
+
+
+
+
+
+
+
+
+ +) +`) + }) + it("does not add the other longhand if the shorthand is not defined and the other longhand it's auto", async () => { + const renderResult = await renderTestEditorWithCode( + makeTestProject({ + shorthand: 'auto / 2.4fr 1fr 1fr', + columns: null, + rows: null, + height: 600, + }), + 'await-first-dom-report', + ) + const target = EP.fromString(`sb/grid`) + await renderResult.dispatch(selectComponents([target], false), true) + await renderResult.getDispatchFollowUpActionsFinished() + const canvasControlsLayer = renderResult.renderedDOM.getByTestId(CanvasControlsContainerID) + const resizeControl = renderResult.renderedDOM.getByTestId(`grid-track-size-column-handle-1`) + const resizeControlRect = resizeControl.getBoundingClientRect() + const startPoint = canvasPoint({ + x: resizeControlRect.x + resizeControlRect.width / 2, + y: resizeControlRect.y + resizeControlRect.height / 2, + }) + const endPoint = canvasPoint({ + x: startPoint.x + 100, + y: startPoint.y, + }) + await mouseMoveToPoint(resizeControl, startPoint) + await mouseDownAtPoint(resizeControl, startPoint) + await mouseMoveToPoint(canvasControlsLayer, endPoint) + await mouseUpAtPoint(canvasControlsLayer, endPoint) + await renderResult.getDispatchFollowUpActionsFinished() + + expect(getPrintedUiJsCode(renderResult.getEditorState())) + .toEqual(`import * as React from 'react' +import { Storyboard } from 'utopia-api' + +export var storyboard = ( + +
+
+
+
+
+
+
+
+
+
+
+ +) +`) + }) }) }) diff --git a/editor/src/components/canvas/canvas-strategies/strategies/resize-grid-strategy.ts b/editor/src/components/canvas/canvas-strategies/strategies/resize-grid-strategy.ts index 619fe370b4ce..fd441a32dea0 100644 --- a/editor/src/components/canvas/canvas-strategies/strategies/resize-grid-strategy.ts +++ b/editor/src/components/canvas/canvas-strategies/strategies/resize-grid-strategy.ts @@ -7,12 +7,16 @@ import { import { MetadataUtils } from '../../../../core/model/element-metadata-utils' import * as EP from '../../../../core/shared/element-path' import * as PP from '../../../../core/shared/property-path' -import { setProperty } from '../../commands/set-property-command' +import type { PropertyToUpdate } from '../../commands/set-property-command' import { - GridControls, - GridControlsKey, + propertyToDelete, + propertyToSet, + updateBulkProperties, +} from '../../commands/set-property-command' +import { + controlsForGridPlaceholders, GridRowColumnResizingControls, -} from '../../controls/grid-controls' +} from '../../controls/grid-controls-for-strategies' import type { CanvasStrategyFactory } from '../canvas-strategies' import { onlyFitWhenDraggingThisControl } from '../canvas-strategies' import type { InteractionCanvasState } from '../canvas-strategy-types' @@ -24,15 +28,28 @@ import { import type { InteractionSession } from '../interaction-state' import type { GridDimension } from '../../../../components/inspector/common/css-utils' import { + cssNumber, + gridCSSNumber, + isDynamicGridRepeat, isGridCSSNumber, - printArrayGridDimension, + printArrayGridDimensions, } from '../../../../components/inspector/common/css-utils' -import { modify, toFirst } from '../../../../core/shared/optics/optic-utilities' -import { setElementsToRerenderCommand } from '../../commands/set-elements-to-rerender-command' -import type { Either } from '../../../../core/shared/either' -import { foldEither, isRight } from '../../../../core/shared/either' +import { toFirst } from '../../../../core/shared/optics/optic-utilities' +import { isLeft } from '../../../../core/shared/either' import { roundToNearestWhole } from '../../../../core/shared/math-utils' import type { GridAutoOrTemplateBase } from '../../../../core/shared/element-template' +import { + expandGridDimensions, + findOriginalGrid, + isJustAutoGridDimension, + replaceGridTemplateDimensionAtIndex, +} from './grid-helpers' +import { setCursorCommand } from '../../commands/set-cursor-command' +import { CSSCursor } from '../../canvas-types' +import type { CanvasCommand } from '../../commands/commands' +import type { Axis } from '../../gap-utils' +import { getComponentDescriptorForTarget } from '../../../../core/property-controls/property-controls-utils' +import { gridContainerIdentifier } from '../../../editor/store/editor-state' export const resizeGridStrategy: CanvasStrategyFactory = ( canvasState: InteractionCanvasState, @@ -44,8 +61,24 @@ export const resizeGridStrategy: CanvasStrategyFactory = ( } const selectedElement = selectedElements[0] + const selectedElementMetadata = MetadataUtils.findElementByElementPath( + canvasState.startingMetadata, + selectedElement, + ) + if (selectedElementMetadata == null) { + return null + } - const isGridCell = MetadataUtils.isGridCell(canvasState.startingMetadata, selectedElement) + const supportsStyleProp = MetadataUtils.targetRegisteredStyleControlsOrHonoursStyleProps( + canvasState.projectContents, + selectedElementMetadata, + canvasState.propertyControlsInfo, + 'layout-system', + ['gridTemplateColumns', 'gridTemplateRows'], + 'some', + ) + + const isGridCell = MetadataUtils.isGridItem(canvasState.startingMetadata, selectedElement) const isGrid = MetadataUtils.isGridLayoutedContainer( MetadataUtils.findElementByElementPath(canvasState.startingMetadata, selectedElement), ) @@ -55,7 +88,12 @@ export const resizeGridStrategy: CanvasStrategyFactory = ( return null } - const gridPath = isGrid ? selectedElement : EP.parentPath(selectedElement) + const gridPath = isGrid + ? selectedElement + : findOriginalGrid(canvasState.startingMetadata, EP.parentPath(selectedElement)) // TODO don't use EP.parentPath + if (gridPath == null) { + return null + } return { id: 'resize-grid-strategy', @@ -68,20 +106,15 @@ export const resizeGridStrategy: CanvasStrategyFactory = ( controlsToRender: [ { control: GridRowColumnResizingControls, - props: { target: gridPath }, + props: { target: gridContainerIdentifier(gridPath) }, key: `grid-row-col-resize-controls-${EP.toString(gridPath)}`, show: 'always-visible', - priority: 'top', - }, - { - control: GridControls, - props: { targets: [gridPath] }, - key: GridControlsKey(gridPath), - show: 'always-visible', - priority: 'bottom', }, + controlsForGridPlaceholders(gridContainerIdentifier(gridPath)), ], - fitness: onlyFitWhenDraggingThisControl(interactionSession, 'GRID_AXIS_HANDLE', 1), + fitness: supportsStyleProp + ? onlyFitWhenDraggingThisControl(interactionSession, 'GRID_AXIS_HANDLE', 1) + : 0, apply: () => { if ( interactionSession == null || @@ -110,6 +143,12 @@ export const resizeGridStrategy: CanvasStrategyFactory = ( ? gridSpecialSizeMeasurements.containerGridProperties.gridTemplateColumns : gridSpecialSizeMeasurements.containerGridProperties.gridTemplateRows + const otherAxis: Axis = control.axis === 'column' ? 'row' : 'column' + const otherAxisValues = + otherAxis === 'column' + ? gridSpecialSizeMeasurements.containerGridPropertiesFromProps.gridTemplateColumns + : gridSpecialSizeMeasurements.containerGridPropertiesFromProps.gridTemplateRows + if ( calculatedValues == null || calculatedValues.type !== 'DIMENSIONS' || @@ -119,11 +158,16 @@ export const resizeGridStrategy: CanvasStrategyFactory = ( return emptyStrategyApplicationResult } + if (!canResizeGridTemplate(originalValues)) { + return strategyApplicationResult([setCursorCommand(CSSCursor.NotPermitted)], []) + } + + const expandedOriginalValues = expandGridDimensions(originalValues.dimensions) const mergedValues: GridAutoOrTemplateBase = { type: calculatedValues.type, dimensions: calculatedValues.dimensions.map((dim, index) => { - if (index < originalValues.dimensions.length) { - return originalValues.dimensions[index] + if (index < expandedOriginalValues.length) { + return expandedOriginalValues[index] } return dim }), @@ -140,38 +184,70 @@ export const resizeGridStrategy: CanvasStrategyFactory = ( .compose(fromField('value')) const calculatedValue = toFirst(valueOptic, calculatedValues.dimensions) + if (isLeft(calculatedValue)) { + return emptyStrategyApplicationResult + } const mergedValue = toFirst(valueOptic, mergedValues.dimensions) + if (isLeft(mergedValue)) { + return emptyStrategyApplicationResult + } const mergedUnit = toFirst(unitOptic, mergedValues.dimensions) - const isFractional = isRight(mergedUnit) && mergedUnit.value === 'fr' + if (isLeft(mergedUnit)) { + return emptyStrategyApplicationResult + } + + const isFractional = mergedUnit.value === 'fr' const precision = modifiers.cmd ? 'coarse' : 'precise' + const lineName = mergedValues.dimensions[control.columnOrRow]?.lineName ?? null - const newSetting = modify( - valueOptic, - (current) => - newResizedValue( - current, - getNewDragValue(dragAmount, isFractional, calculatedValue, mergedValue), - precision, - isFractional, - ), - mergedValues.dimensions, - ) - const propertyValueAsString = printArrayGridDimension(newSetting) - - const commands = [ - setProperty( - 'always', - gridPath, - PP.create( - 'style', - control.axis === 'column' ? 'gridTemplateColumns' : 'gridTemplateRows', - ), - propertyValueAsString, + const newValue = Math.max( + 0, + newResizedValue( + mergedValue.value, + getNewDragValue(dragAmount, isFractional, calculatedValue.value, mergedValue.value), + precision, + isFractional, ), - setElementsToRerenderCommand([gridPath]), + ) + + const newDimensions = replaceGridTemplateDimensionAtIndex( + originalValues.dimensions, + expandedOriginalValues, + control.columnOrRow, + gridCSSNumber(cssNumber(newValue, mergedUnit.value), lineName), + ) + + const propertyValueAsString = printArrayGridDimensions(newDimensions) + + const propertyAxis = PP.create( + 'style', + control.axis === 'column' ? 'gridTemplateColumns' : 'gridTemplateRows', + ) + let propertiesToUpdate: PropertyToUpdate[] = [ + propertyToSet(propertyAxis, propertyValueAsString), + propertyToDelete(PP.create('style', 'gridTemplate')), // delete the shorthand in favor of the longhands ] - return strategyApplicationResult(commands) + if ( + otherAxisValues?.type === 'DIMENSIONS' && + otherAxisValues.dimensions.length > 0 && + !isJustAutoGridDimension(otherAxisValues.dimensions) + ) { + // if the other axis has dimensions, serialize them in their longhand + const propertyOtherAxis = PP.create( + 'style', + otherAxis === 'column' ? 'gridTemplateColumns' : 'gridTemplateRows', + ) + const otherAxisValueAsString = printArrayGridDimensions(otherAxisValues.dimensions) + propertiesToUpdate.push(propertyToSet(propertyOtherAxis, otherAxisValueAsString)) + } + + let commands: CanvasCommand[] = [ + updateBulkProperties('always', gridPath, propertiesToUpdate), + setCursorCommand(control.axis === 'column' ? CSSCursor.ColResize : CSSCursor.RowResize), + ] + + return strategyApplicationResult(commands, [gridPath]) }, } } @@ -179,27 +255,24 @@ export const resizeGridStrategy: CanvasStrategyFactory = ( function getNewDragValue( dragAmount: number, isFractional: boolean, - possibleCalculatedValue: Either, - mergedValue: Either, -) { + calculatedValue: number, + mergedValue: number, +): number { if (!isFractional) { return dragAmount } - if (!isRight(possibleCalculatedValue)) { + if (calculatedValue === 0) { return 0 } - const mergedFractionalValue = foldEither( - () => 0, - (r) => r, - mergedValue, - ) - const calculatedValue = possibleCalculatedValue.value - const perPointOne = - mergedFractionalValue == 0 ? 10 : (calculatedValue / mergedFractionalValue) * 0.1 - const newValue = roundToNearestWhole((dragAmount / perPointOne) * 10) / 10 - return newValue + // for fr units, adjust the value to proportionally to .1 + let proportionalResize = calculatedValue * 0.1 + if (mergedValue !== 0) { + proportionalResize /= mergedValue + } + + return roundToNearestWhole(dragAmount / proportionalResize) * 0.1 } function newResizedValue( @@ -219,3 +292,7 @@ function newResizedValue( return Math.round(newValue / 10) * 10 } } + +export function canResizeGridTemplate(template: GridAutoOrTemplateBase): boolean { + return template.type === 'DIMENSIONS' && !template.dimensions.some(isDynamicGridRepeat) +} diff --git a/editor/src/components/canvas/canvas-strategies/strategies/set-border-radius-strategy.spec.browser2.tsx b/editor/src/components/canvas/canvas-strategies/strategies/set-border-radius-strategy.spec.browser2.tsx index ac49bea9ee31..8e98bcb3f3b2 100644 --- a/editor/src/components/canvas/canvas-strategies/strategies/set-border-radius-strategy.spec.browser2.tsx +++ b/editor/src/components/canvas/canvas-strategies/strategies/set-border-radius-strategy.spec.browser2.tsx @@ -2,9 +2,17 @@ import { fromString } from '../../../../core/shared/element-path' import type { CanvasVector, Size, WindowPoint } from '../../../../core/shared/math-utils' import { canvasVector, size, windowPoint } from '../../../../core/shared/math-utils' import { assertNever } from '../../../../core/shared/utils' +import { TailwindConfigPath } from '../../../../core/tailwind/tailwind-config' +import { createModifiedProject } from '../../../../sample-projects/sample-project-utils.test-utils' import type { Modifiers } from '../../../../utils/modifiers' import { cmdModifier, emptyModifiers } from '../../../../utils/modifiers' +import { + selectComponentsForTest, + setFeatureForBrowserTestsUseInDescribeBlockOnly, + wait, +} from '../../../../utils/utils.test-utils' import { selectComponents, setFocusedElement } from '../../../editor/actions/action-creators' +import { StoryboardFilePath } from '../../../editor/store/editor-state' import type { BorderRadiusCorner } from '../../border-radius-control-utils' import { BorderRadiusCorners } from '../../border-radius-control-utils' import type { EdgePosition } from '../../canvas-types' @@ -23,6 +31,7 @@ import { renderTestEditorWithCode, makeTestProjectCodeWithSnippet, getPrintedUiJsCode, + renderTestEditorWithModel, } from '../../ui-jsx.test-utils' describe('set border radius strategy', () => { @@ -424,6 +433,333 @@ describe('set border radius strategy', () => { }) }) }) + + describe('Tailwind', () => { + setFeatureForBrowserTestsUseInDescribeBlockOnly('Tailwind', true) + + const TailwindProject = (classes: string) => + createModifiedProject({ + [StoryboardFilePath]: ` + import React from 'react' + import { Scene, Storyboard } from 'utopia-api' + export var storyboard = ( + + +
+ + + ) + + `, + [TailwindConfigPath]: ` + const TailwindConfig = { } + export default TailwindConfig + `, + 'app.css': ` + @tailwind base; + @tailwind components; + @tailwind utilities;`, + }) + + it('border radius controls show up for elements that have tailwind border radius set', async () => { + const editor = await renderTestEditorWithModel( + TailwindProject('rounded-2xl'), + 'await-first-dom-report', + ) + await selectComponentsForTest(editor, [fromString('sb/scene/mydiv')]) + + const borderRadiusControls = BorderRadiusCorners.flatMap((corner) => + editor.renderedDOM.queryAllByTestId(CircularHandleTestId(corner)), + ) + + expect(borderRadiusControls.length).toEqual(4) + }) + + it('border radius controls show up for elements that dont have tailwind border radius set', async () => { + const editor = await renderTestEditorWithModel(TailwindProject(''), 'await-first-dom-report') + await selectComponentsForTest(editor, [fromString('sb/scene/mydiv')]) + + const borderRadiusControls = BorderRadiusCorners.flatMap((corner) => + editor.renderedDOM.queryAllByTestId(CircularHandleTestId(corner)), + ) + + expect(borderRadiusControls.length).toEqual(4) + }) + + describe('adjust border radius via handles', () => { + it('top left', async () => { + const editor = await renderTestEditorWithModel( + TailwindProject('rounded-[10px]'), + 'await-first-dom-report', + ) + await doDragTest(editor, 'tl', 10, emptyModifiers) + await editor.getDispatchFollowUpActionsFinished() + const div = editor.renderedDOM.getByTestId('mydiv') + expect(div.className).toEqual( + 'top-28 left-28 w-28 h-28 bg-black absolute rounded-[22px] overflow-hidden', + ) + }) + + it('top right', async () => { + const editor = await renderTestEditorWithModel( + TailwindProject('rounded-[10px]'), + 'await-first-dom-report', + ) + await doDragTest(editor, 'tr', 10, emptyModifiers) + await editor.getDispatchFollowUpActionsFinished() + const div = editor.renderedDOM.getByTestId('mydiv') + expect(div.className).toEqual( + 'top-28 left-28 w-28 h-28 bg-black absolute rounded-[22px] overflow-hidden', + ) + }) + + it('bottom left', async () => { + const editor = await renderTestEditorWithModel( + TailwindProject('rounded-[10px]'), + 'await-first-dom-report', + ) + await doDragTest(editor, 'tl', 10, emptyModifiers) + await editor.getDispatchFollowUpActionsFinished() + const div = editor.renderedDOM.getByTestId('mydiv') + expect(div.className).toEqual( + 'top-28 left-28 w-28 h-28 bg-black absolute rounded-[22px] overflow-hidden', + ) + }) + + it('bottom right', async () => { + const editor = await renderTestEditorWithModel( + TailwindProject('rounded-[10px]'), + 'await-first-dom-report', + ) + await doDragTest(editor, 'tl', 10, emptyModifiers) + await editor.getDispatchFollowUpActionsFinished() + const div = editor.renderedDOM.getByTestId('mydiv') + expect(div.className).toEqual( + 'top-28 left-28 w-28 h-28 bg-black absolute rounded-[22px] overflow-hidden', + ) + }) + }) + describe('adjust border radius via handles with non-arbitrary tailwind classes', () => { + it('top left', async () => { + const editor = await renderTestEditorWithModel( + TailwindProject('rounded-2xl'), + 'await-first-dom-report', + ) + await doDragTest(editor, 'tl', 10, emptyModifiers) + await editor.getDispatchFollowUpActionsFinished() + const div = editor.renderedDOM.getByTestId('mydiv') + expect(div.className).toEqual( + 'top-28 left-28 w-28 h-28 bg-black absolute rounded-[2rem] overflow-hidden', + ) + }) + + it('top right', async () => { + const editor = await renderTestEditorWithModel( + TailwindProject('rounded-2xl'), + 'await-first-dom-report', + ) + await doDragTest(editor, 'tr', 10, emptyModifiers) + await editor.getDispatchFollowUpActionsFinished() + const div = editor.renderedDOM.getByTestId('mydiv') + expect(div.className).toEqual( + 'top-28 left-28 w-28 h-28 bg-black absolute rounded-[2rem] overflow-hidden', + ) + }) + + it('bottom left', async () => { + const editor = await renderTestEditorWithModel( + TailwindProject('rounded-2xl'), + 'await-first-dom-report', + ) + await doDragTest(editor, 'tl', 10, emptyModifiers) + await editor.getDispatchFollowUpActionsFinished() + const div = editor.renderedDOM.getByTestId('mydiv') + expect(div.className).toEqual( + 'top-28 left-28 w-28 h-28 bg-black absolute rounded-[2rem] overflow-hidden', + ) + }) + + it('bottom right', async () => { + const editor = await renderTestEditorWithModel( + TailwindProject('rounded-2xl'), + 'await-first-dom-report', + ) + await doDragTest(editor, 'tl', 10, emptyModifiers) + await editor.getDispatchFollowUpActionsFinished() + const div = editor.renderedDOM.getByTestId('mydiv') + expect(div.className).toEqual( + 'top-28 left-28 w-28 h-28 bg-black absolute rounded-[2rem] overflow-hidden', + ) + }) + }) + describe('adjust border radius via handles, individually', () => { + it('top left', async () => { + const editor = await renderTestEditorWithModel( + TailwindProject('rounded-[10px]'), + 'await-first-dom-report', + ) + await doDragTest(editor, 'tl', 10, cmdModifier) + await editor.getDispatchFollowUpActionsFinished() + const div = editor.renderedDOM.getByTestId('mydiv') + expect(div.className).toEqual( + 'top-28 left-28 w-28 h-28 bg-black absolute rounded-tl-[22px] rounded-tr-[10px] rounded-br-[10px] rounded-bl-[10px] overflow-hidden', + ) + }) + + it('top right', async () => { + const editor = await renderTestEditorWithModel( + TailwindProject('rounded-[10px]'), + 'await-first-dom-report', + ) + await doDragTest(editor, 'tr', 10, cmdModifier) + await editor.getDispatchFollowUpActionsFinished() + const div = editor.renderedDOM.getByTestId('mydiv') + expect(div.className).toEqual( + 'top-28 left-28 w-28 h-28 bg-black absolute rounded-tl-[10px] rounded-tr-[22px] rounded-br-[10px] rounded-bl-[10px] overflow-hidden', + ) + }) + + it('bottom left', async () => { + const editor = await renderTestEditorWithModel( + TailwindProject('rounded-[10px]'), + 'await-first-dom-report', + ) + await doDragTest(editor, 'bl', 10, cmdModifier) + await editor.getDispatchFollowUpActionsFinished() + const div = editor.renderedDOM.getByTestId('mydiv') + expect(div.className).toEqual( + 'top-28 left-28 w-28 h-28 bg-black absolute rounded-tl-[10px] rounded-tr-[10px] rounded-br-[10px] rounded-bl-[22px] overflow-hidden', + ) + }) + + it('bottom right', async () => { + const editor = await renderTestEditorWithModel( + TailwindProject('rounded-[10px]'), + 'await-first-dom-report', + ) + await doDragTest(editor, 'br', 10, cmdModifier) + await editor.getDispatchFollowUpActionsFinished() + const div = editor.renderedDOM.getByTestId('mydiv') + expect(div.className).toEqual( + 'top-28 left-28 w-28 h-28 bg-black absolute rounded-tl-[10px] rounded-tr-[10px] rounded-br-[22px] rounded-bl-[10px] overflow-hidden', + ) + }) + }) + describe('adjust border radius via handles, individually, with non-arbitrary tailwind classes', () => { + it('top left', async () => { + const editor = await renderTestEditorWithModel( + TailwindProject('rounded-2xl'), + 'await-first-dom-report', + ) + await doDragTest(editor, 'tl', 10, cmdModifier) + await editor.getDispatchFollowUpActionsFinished() + const div = editor.renderedDOM.getByTestId('mydiv') + expect(div.className).toEqual( + 'top-28 left-28 w-28 h-28 bg-black absolute rounded-tl-[2rem] rounded-tr-2xl rounded-br-2xl rounded-bl-2xl overflow-hidden', + ) + }) + + it('top right', async () => { + const editor = await renderTestEditorWithModel( + TailwindProject('rounded-2xl'), + 'await-first-dom-report', + ) + await doDragTest(editor, 'tr', 10, cmdModifier) + await editor.getDispatchFollowUpActionsFinished() + const div = editor.renderedDOM.getByTestId('mydiv') + expect(div.className).toEqual( + 'top-28 left-28 w-28 h-28 bg-black absolute rounded-tl-2xl rounded-tr-[2rem] rounded-br-2xl rounded-bl-2xl overflow-hidden', + ) + }) + + it('bottom left', async () => { + const editor = await renderTestEditorWithModel( + TailwindProject('rounded-2xl'), + 'await-first-dom-report', + ) + await doDragTest(editor, 'bl', 10, cmdModifier) + await editor.getDispatchFollowUpActionsFinished() + const div = editor.renderedDOM.getByTestId('mydiv') + expect(div.className).toEqual( + 'top-28 left-28 w-28 h-28 bg-black absolute rounded-tl-2xl rounded-tr-2xl rounded-br-2xl rounded-bl-[2rem] overflow-hidden', + ) + }) + + it('bottom right', async () => { + const editor = await renderTestEditorWithModel( + TailwindProject('rounded-2xl'), + 'await-first-dom-report', + ) + await doDragTest(editor, 'br', 10, cmdModifier) + await editor.getDispatchFollowUpActionsFinished() + const div = editor.renderedDOM.getByTestId('mydiv') + expect(div.className).toEqual( + 'top-28 left-28 w-28 h-28 bg-black absolute rounded-tl-2xl rounded-tr-2xl rounded-br-[2rem] rounded-bl-2xl overflow-hidden', + ) + }) + }) + + describe('Overflow property handling', () => { + it('does not overwrite existing overflow property', async () => { + const editor = await renderTestEditorWithModel( + TailwindProject('rounded-[10px] overflow-visible'), + 'await-first-dom-report', + ) + await doDragTest(editor, 'tl', 10, cmdModifier) + await editor.getDispatchFollowUpActionsFinished() + const div = editor.renderedDOM.getByTestId('mydiv') + expect(div.className).toEqual( + 'top-28 left-28 w-28 h-28 bg-black absolute overflow-visible rounded-tl-[22px] rounded-tr-[10px] rounded-br-[10px] rounded-bl-[10px]', + ) + }) + + it('shows toast message', async () => { + const editor = await renderTestEditorWithModel( + TailwindProject('rounded-[10px]'), + 'await-first-dom-report', + ) + await doDragTest(editor, 'tl', 10, cmdModifier) + expect(editor.getEditorState().editor.toasts).toHaveLength(1) + expect(editor.getEditorState().editor.toasts[0]).toEqual({ + id: 'property-added', + level: 'NOTICE', + message: 'Element now hides overflowing content', + persistent: false, + }) + }) + }) + + it('can handle 4-value syntax', async () => { + const editor = await renderTestEditorWithModel( + TailwindProject( + 'rounded-tl-[14px] rounded-tr-[15px] rounded-br-[16px] rounded-bl-[17px] overflow-visible', + ), + 'await-first-dom-report', + ) + + await doDragTest(editor, 'tl', 10, emptyModifiers) + await editor.getDispatchFollowUpActionsFinished() + const div = editor.renderedDOM.getByTestId('mydiv') + expect(div.className).toEqual( + 'top-28 left-28 w-28 h-28 bg-black absolute rounded-tl-[24px] rounded-tr-[15px] rounded-br-[16px] rounded-bl-[17px] overflow-visible', + ) + }) + }) }) function codeForDragTest(borderRadius: string): string { diff --git a/editor/src/components/canvas/canvas-strategies/strategies/set-border-radius-strategy.tsx b/editor/src/components/canvas/canvas-strategies/strategies/set-border-radius-strategy.tsx index f53fd038bc83..9d9e18ff297a 100644 --- a/editor/src/components/canvas/canvas-strategies/strategies/set-border-radius-strategy.tsx +++ b/editor/src/components/canvas/canvas-strategies/strategies/set-border-radius-strategy.tsx @@ -1,18 +1,13 @@ import { styleStringInArray } from '../../../../utils/common-constants' import type { Sides } from 'utopia-api/core' -import { getLayoutProperty } from '../../../../core/layout/getLayoutProperty' import { MetadataUtils } from '../../../../core/model/element-metadata-utils' -import { defaultEither, foldEither, right } from '../../../../core/shared/either' -import type { - ElementInstanceMetadata, - JSXAttributes, -} from '../../../../core/shared/element-template' +import { foldEither } from '../../../../core/shared/either' +import type { ElementInstanceMetadata } from '../../../../core/shared/element-template' import { isIntrinsicElement, isJSXElement, jsxElementName, jsxElementNameEquals, - modifiableAttributeIsAttributeNotFound, } from '../../../../core/shared/element-template' import type { CanvasPoint, CanvasVector, Size } from '../../../../core/shared/math-utils' import { @@ -32,7 +27,7 @@ import type { CSSNumber, ParsedCSSPropertiesKeys, } from '../../../inspector/common/css-utils' -import { cssNumber, ParsedCSSProperties, printCSSNumber } from '../../../inspector/common/css-utils' +import { cssNumber, printCSSNumber } from '../../../inspector/common/css-utils' import { stylePropPathMappingFn } from '../../../inspector/common/property-path-hooks' import type { BorderRadiusAdjustMode, @@ -43,10 +38,10 @@ import { BorderRadiusControlMinimumForDisplay, maxBorderRadius, } from '../../border-radius-control-utils' -import { CSSCursor } from '../../canvas-types' +import { CSSCursor, maybePropertyValue, type StyleInfo } from '../../canvas-types' import type { CanvasCommand } from '../../commands/commands' import { setCursorCommand } from '../../commands/set-cursor-command' -import { setElementsToRerenderCommand } from '../../commands/set-elements-to-rerender-command' + import { setProperty } from '../../commands/set-property-command' import { BorderRadiusControl } from '../../controls/select-mode/border-radius-control' import type { CSSNumberWithRenderedValue } from '../../controls/select-mode/controls-common' @@ -58,7 +53,6 @@ import { measurementBasedOnOtherMeasurement, precisionFromModifiers, shouldShowControls, - unitlessCSSNumberWithRenderedValue, } from '../../controls/select-mode/controls-common' import type { CanvasStrategyFactory } from '../canvas-strategies' import { onlyFitWhenDraggingThisControl } from '../canvas-strategies' @@ -70,11 +64,6 @@ import { } from '../canvas-strategy-types' import type { InteractionSession } from '../interaction-state' import { deleteProperties } from '../../commands/delete-properties-command' -import { allElemsEqual } from '../../../../core/shared/array-utils' -import * as PP from '../../../../core/shared/property-path' -import { withUnderlyingTarget } from '../../../editor/store/editor-state' -import type { ProjectContentTreeRoot } from '../../../assets' -import { getModifiableJSXAttributeAtPath } from '../../../../core/shared/jsx-attribute-utils' import { showToastCommand } from '../../commands/show-toast-command' import { activeFrameTargetPath, setActiveFrames } from '../../commands/set-active-frames-command' @@ -112,7 +101,10 @@ export const setBorderRadiusStrategy: CanvasStrategyFactory = ( return null } - const borderRadius = borderRadiusFromElement(element) + const borderRadius = borderRadiusFromElement( + element, + canvasState.styleInfoReader(selectedElement), + ) if (borderRadius == null) { return null } @@ -155,21 +147,26 @@ export const setBorderRadiusStrategy: CanvasStrategyFactory = ( }), ], apply: () => - strategyApplicationResult([ - setCursorCommand(CSSCursor.Radius), - ...commands(selectedElement), - ...getAddOverflowHiddenCommands(selectedElement, canvasState.projectContents), - setElementsToRerenderCommand(selectedElements), - setActiveFrames( - selectedElements.map((path) => ({ - action: 'set-radius', - target: activeFrameTargetPath(path), - source: zeroRectIfNullOrInfinity( - MetadataUtils.getFrameInCanvasCoords(path, canvasState.startingMetadata), - ), - })), - ), - ]), + strategyApplicationResult( + [ + setCursorCommand(CSSCursor.Radius), + ...commands(selectedElement), + ...getAddOverflowHiddenCommands( + selectedElement, + canvasState.styleInfoReader(selectedElement), + ), + setActiveFrames( + selectedElements.map((path) => ({ + action: 'set-radius', + target: activeFrameTargetPath(path), + source: zeroRectIfNullOrInfinity( + MetadataUtils.getFrameInCanvasCoords(path, canvasState.startingMetadata), + ), + })), + ), + ], + selectedElements, + ), } } @@ -220,6 +217,7 @@ interface BorderRadiusData { export function borderRadiusFromElement( element: ElementInstanceMetadata, + styleInfo: StyleInfo | null, ): BorderRadiusData | null { return foldEither( () => null, @@ -230,7 +228,7 @@ export function borderRadiusFromElement( return null } - const fromProps = borderRadiusFromProps(jsxElement.props) + const fromStyleInfo = borderRadiusFromStyleInfo(styleInfo) const measurementsNonZero = AllSides.some((c) => { const measurement = renderedValueSides[c] if (measurement == null) { @@ -248,7 +246,7 @@ export function borderRadiusFromElement( if ( !( elementIsIntrinsicElementOrScene || - shouldShowControls(fromProps != null, measurementsNonZero) + shouldShowControls(fromStyleInfo != null, measurementsNonZero) ) ) { return null @@ -256,7 +254,7 @@ export function borderRadiusFromElement( const borderRadius = optionalMap( (radius) => measurementFromBorderRadius(renderedValueSides, radius), - fromProps, + fromStyleInfo, ) const defaultBorderRadiusSides = borderRadiusSidesFromValue( @@ -279,7 +277,7 @@ export function borderRadiusFromElement( const borderRadiusMinMax = { min: 0, max: borderRadiusUpperLimit } return { - mode: fromProps?.type === 'sides' ? 'individual' : 'all', + mode: fromStyleInfo?.type === 'sides' ? 'individual' : 'all', borderRadius: mapBorderRadiusSides( (n) => adjustBorderRadius(borderRadiusMinMax, n), borderRadius ?? defaultBorderRadiusSides, @@ -298,36 +296,20 @@ interface BorderRadiusFromProps { sides: BorderRadiusSides } -function borderRadiusFromProps(props: JSXAttributes): BorderRadiusFromProps | null { - const wrappedProps = right(props) +function borderRadiusFromStyleInfo(styleInfo: StyleInfo | null): BorderRadiusFromProps | null { + const borderRadius = optionalMap(maybePropertyValue, styleInfo?.borderRadius) - const borderRadius = getLayoutProperty('borderRadius', wrappedProps, styleStringInArray) - const simpleBorderRadius = foldEither( - () => null, - (radius) => { - if (radius == null) { - return null - } else { - return foldEither(borderRadiusSidesFromValue, (value) => value, radius) - } - }, + const simpleBorderRadius = optionalMap( + (radius) => foldEither(borderRadiusSidesFromValue, (value) => value, radius), borderRadius, ) - const borderTopLeftRadius = defaultEither( - null, - getLayoutProperty('borderTopLeftRadius', wrappedProps, styleStringInArray), - ) - const borderTopRightRadius = defaultEither( - null, - getLayoutProperty('borderTopRightRadius', wrappedProps, styleStringInArray), - ) - const borderBottomLeftRadius = defaultEither( - null, - getLayoutProperty('borderBottomLeftRadius', wrappedProps, styleStringInArray), - ) - const borderBottomRightRadius = defaultEither( - null, - getLayoutProperty('borderBottomRightRadius', wrappedProps, styleStringInArray), + + const borderTopLeftRadius = optionalMap(maybePropertyValue, styleInfo?.borderTopLeftRadius) + const borderTopRightRadius = optionalMap(maybePropertyValue, styleInfo?.borderTopRightRadius) + const borderBottomLeftRadius = optionalMap(maybePropertyValue, styleInfo?.borderBottomLeftRadius) + const borderBottomRightRadius = optionalMap( + maybePropertyValue, + styleInfo?.borderBottomRightRadius, ) if ( @@ -604,27 +586,15 @@ const setShorthandStylePropertyCommand = function getAddOverflowHiddenCommands( target: ElementPath, - projectContents: ProjectContentTreeRoot, + styleInfo: StyleInfo | null, ): Array { - const overflowProp = PP.create('style', 'overflow') - - const propertyExists = withUnderlyingTarget(target, projectContents, false, (_, element) => { - if (isJSXElement(element)) { - return foldEither( - () => false, - (value) => !modifiableAttributeIsAttributeNotFound(value), - getModifiableJSXAttributeAtPath(element.props, overflowProp), - ) - } else { - return false - } - }) - + const propertyExists = styleInfo?.overflow != null && styleInfo.overflow.type !== 'not-found' if (propertyExists) { return [] } + return [ showToastCommand('Element now hides overflowing content', 'NOTICE', 'property-added'), - setProperty('always', target, overflowProp, 'hidden'), + setProperty('always', target, StyleProp('overflow'), 'hidden'), ] } diff --git a/editor/src/components/canvas/canvas-strategies/strategies/set-flex-gap-strategy.spec.browser2.tsx b/editor/src/components/canvas/canvas-strategies/strategies/set-flex-gap-strategy.spec.browser2.tsx index 6a205486f4aa..bd91e06ef3d4 100644 --- a/editor/src/components/canvas/canvas-strategies/strategies/set-flex-gap-strategy.spec.browser2.tsx +++ b/editor/src/components/canvas/canvas-strategies/strategies/set-flex-gap-strategy.spec.browser2.tsx @@ -13,6 +13,7 @@ import { getPrintedUiJsCode, makeTestProjectCodeWithSnippet, renderTestEditorWithCode, + renderTestEditorWithModel, } from '../../ui-jsx.test-utils' import { shiftModifier } from '../../../../utils/modifiers' import { FlexGapTearThreshold } from './set-flex-gap-strategy' @@ -20,8 +21,14 @@ import type { CanvasPoint } from '../../../../core/shared/math-utils' import { canvasPoint } from '../../../../core/shared/math-utils' import { checkFlexGapHandlesPositionedCorrectly } from '../../controls/select-mode/flex-gap-control.test-utils' import { BakedInStoryboardUID } from '../../../../core/model/scene-utils' -import { selectComponentsForTest, wait } from '../../../../utils/utils.test-utils' +import { + selectComponentsForTest, + setFeatureForBrowserTestsUseInDescribeBlockOnly, +} from '../../../../utils/utils.test-utils' import * as EP from '../../../../core/shared/element-path' +import { createModifiedProject } from '../../../../sample-projects/sample-project-utils.test-utils' +import { StoryboardFilePath } from '../../../editor/store/editor-state' +import { TailwindConfigPath } from '../../../../core/tailwind/tailwind-config' const DivTestId = 'mydiv' @@ -714,6 +721,66 @@ export var storyboard = ( expect(getPrintedUiJsCode(editor.getEditorState())).toEqual(code(20)) }) }) + + describe('tailwind', () => { + setFeatureForBrowserTestsUseInDescribeBlockOnly('Tailwind', true) + const Project = createModifiedProject({ + [StoryboardFilePath]: ` + import React from 'react' + import { Scene, Storyboard } from 'utopia-api' + export var storyboard = ( + + +
+
+
+
+ + + ) + + `, + [TailwindConfigPath]: ` + const TailwindConfig = { } + export default TailwindConfig + `, + 'app.css': ` + @tailwind base; + @tailwind components; + @tailwind utilities;`, + }) + + it('can set tailwind gap', async () => { + const editor = await renderTestEditorWithModel(Project, 'await-first-dom-report') + await selectComponentsForTest(editor, [EP.fromString('sb/scene/div')]) + await doGapResize(editor, canvasPoint({ x: 10, y: 0 })) + const div = editor.renderedDOM.getByTestId(DivTestId) + expect(div.className).toEqual('top-10 left-10 absolute flex flex-row gap-16') + }) + + it('can remove tailwind gap by dragging past threshold', async () => { + const editor = await renderTestEditorWithModel(Project, 'await-first-dom-report') + await selectComponentsForTest(editor, [EP.fromString('sb/scene/div')]) + await doGapResize(editor, canvasPoint({ x: -100, y: 0 })) + const div = editor.renderedDOM.getByTestId(DivTestId) + expect(div.className).toEqual('top-10 left-10 absolute flex flex-row') + }) + }) }) interface GapTestCodeParams { diff --git a/editor/src/components/canvas/canvas-strategies/strategies/set-flex-gap-strategy.tsx b/editor/src/components/canvas/canvas-strategies/strategies/set-flex-gap-strategy.tsx index 8a1cb6aa2dfb..b7740ceeb0e8 100644 --- a/editor/src/components/canvas/canvas-strategies/strategies/set-flex-gap-strategy.tsx +++ b/editor/src/components/canvas/canvas-strategies/strategies/set-flex-gap-strategy.tsx @@ -14,7 +14,7 @@ import { printCSSNumber } from '../../../inspector/common/css-utils' import { stylePropPathMappingFn } from '../../../inspector/common/property-path-hooks' import { deleteProperties } from '../../commands/delete-properties-command' import { setCursorCommand } from '../../commands/set-cursor-command' -import { setElementsToRerenderCommand } from '../../commands/set-elements-to-rerender-command' + import { setProperty } from '../../commands/set-property-command' import { fallbackEmptyValue, @@ -95,7 +95,11 @@ export const setFlexGapStrategy: CanvasStrategyFactory = ( return null } - const flexGap = maybeFlexGapData(canvasState.startingMetadata, selectedElement) + const flexGap = maybeFlexGapData( + canvasState.styleInfoReader(selectedElement), + MetadataUtils.findElementByElementPath(canvasState.startingMetadata, selectedElement), + ) + if (flexGap == null) { return null } @@ -143,7 +147,7 @@ export const setFlexGapStrategy: CanvasStrategyFactory = ( control: FloatingIndicator, props: { ...props, - color: colorTheme.brandNeonPink.value, + color: colorTheme.brandNeonOrange.value, }, key: 'padding-value-indicator-control', show: 'visible-except-when-other-strategy-is-active', @@ -172,30 +176,33 @@ export const setFlexGapStrategy: CanvasStrategyFactory = ( } if (shouldTearOffGap) { - return strategyApplicationResult([ - deleteProperties('always', selectedElement, [StyleGapProp]), - ]) + return strategyApplicationResult( + [deleteProperties('always', selectedElement, [StyleGapProp])], + selectedElements, + ) } - return strategyApplicationResult([ - setProperty( - 'always', - selectedElement, - StyleGapProp, - printCSSNumber(fallbackEmptyValue(updatedFlexGapMeasurement), null), - ), - setCursorCommand(cursorFromFlexDirection(flexGap.direction)), - setElementsToRerenderCommand([...selectedElements, ...children.map((c) => c.elementPath)]), - setActiveFrames([ - { - action: 'set-gap', - target: activeFrameTargetPath(selectedElement), - source: zeroRectIfNullOrInfinity( - MetadataUtils.getFrameInCanvasCoords(selectedElement, canvasState.startingMetadata), - ), - }, - ]), - ]) + return strategyApplicationResult( + [ + setProperty( + 'always', + selectedElement, + StyleGapProp, + printCSSNumber(fallbackEmptyValue(updatedFlexGapMeasurement), null), + ), + setCursorCommand(cursorFromFlexDirection(flexGap.direction)), + setActiveFrames([ + { + action: 'set-gap', + target: activeFrameTargetPath(selectedElement), + source: zeroRectIfNullOrInfinity( + MetadataUtils.getFrameInCanvasCoords(selectedElement, canvasState.startingMetadata), + ), + }, + ]), + ], + selectedElements, + ) }, } } diff --git a/editor/src/components/canvas/canvas-strategies/strategies/set-grid-gap-strategy.tsx b/editor/src/components/canvas/canvas-strategies/strategies/set-grid-gap-strategy.tsx index 52dafc7bb7dd..36b1a71e6ba6 100644 --- a/editor/src/components/canvas/canvas-strategies/strategies/set-grid-gap-strategy.tsx +++ b/editor/src/components/canvas/canvas-strategies/strategies/set-grid-gap-strategy.tsx @@ -12,7 +12,7 @@ import { printCSSNumber } from '../../../inspector/common/css-utils' import { stylePropPathMappingFn } from '../../../inspector/common/property-path-hooks' import { deleteProperties } from '../../commands/delete-properties-command' import { setCursorCommand } from '../../commands/set-cursor-command' -import { setElementsToRerenderCommand } from '../../commands/set-elements-to-rerender-command' + import { setProperty } from '../../commands/set-property-command' import { fallbackEmptyValue, @@ -30,7 +30,7 @@ import { } from '../../gap-utils' import type { CanvasStrategyFactory } from '../canvas-strategies' import { onlyFitWhenDraggingThisControl } from '../canvas-strategies' -import type { InteractionCanvasState } from '../canvas-strategy-types' +import type { ControlWithProps, InteractionCanvasState } from '../canvas-strategy-types' import { controlWithProps, emptyStrategyApplicationResult, @@ -40,7 +40,12 @@ import { import type { InteractionSession } from '../interaction-state' import { colorTheme } from '../../../../uuiui' import { activeFrameTargetPath, setActiveFrames } from '../../commands/set-active-frames-command' +import type { GridGapControlProps } from '../../controls/select-mode/grid-gap-control-component' import { GridGapControl } from '../../controls/select-mode/grid-gap-control' +import type { GridControlsProps } from '../../controls/grid-controls-for-strategies' +import { controlsForGridPlaceholders } from '../../controls/grid-controls-for-strategies' +import { getComponentDescriptorForTarget } from '../../../../core/property-controls/property-controls-utils' +import { gridContainerIdentifier } from '../../../editor/store/editor-state' const SetGridGapStrategyId = 'SET_GRID_GAP_STRATEGY' @@ -60,22 +65,31 @@ export const setGridGapStrategy: CanvasStrategyFactory = ( } const selectedElement = selectedElements[0] + const selectedElementMetadata = MetadataUtils.findElementByElementPath( + canvasState.startingMetadata, + selectedElement, + ) if ( - !MetadataUtils.isGridLayoutedContainer( - MetadataUtils.findElementByElementPath(canvasState.startingMetadata, selectedElement), - ) + selectedElementMetadata == null || + !MetadataUtils.isGridLayoutedContainer(selectedElementMetadata) ) { return null } - const children = recurseIntoChildrenOfMapOrFragment( - canvasState.startingMetadata, - canvasState.startingAllElementProps, - canvasState.startingElementPathTree, - selectedElement, + const supportsStyleProp = MetadataUtils.targetRegisteredStyleControlsOrHonoursStyleProps( + canvasState.projectContents, + selectedElementMetadata, + canvasState.propertyControlsInfo, + 'layout-system', + ['gap', 'rowGap', 'columnGap'], + 'some', ) + if (!supportsStyleProp) { + return null + } const gridGap = maybeGridGapData(canvasState.startingMetadata, selectedElement) + if (gridGap == null) { return null } @@ -101,7 +115,7 @@ export const setGridGapStrategy: CanvasStrategyFactory = ( column: offsetMeasurementByDelta(gridGap.column, dragDelta.x, adjustPrecision), } - const resizeControl = controlWithProps({ + const gridGapControl = controlWithProps({ control: GridGapControl, props: { selectedElement: selectedElement, @@ -116,21 +130,31 @@ export const setGridGapStrategy: CanvasStrategyFactory = ( const maybeIndicatorProps = gridGapValueIndicatorProps(interactionSession, gridGap) - const controlsToRender = optionalMap( - (props) => [ - resizeControl, + const controlsToRender: Array< + | ControlWithProps + | ControlWithProps + | ControlWithProps + > = [gridGapControl] + + // show indicator if needed + if (maybeIndicatorProps != null) { + controlsToRender.push( controlWithProps({ control: FloatingIndicator, props: { - ...props, - color: colorTheme.brandNeonPink.value, + ...maybeIndicatorProps, + color: colorTheme.brandNeonOrange.value, }, key: 'padding-value-indicator-control', show: 'visible-except-when-other-strategy-is-active', }), - ], - maybeIndicatorProps, - ) ?? [resizeControl] + ) + } + + // when the drag is ongoing, keep showing the grid cells + if (isDragOngoing(interactionSession)) { + controlsToRender.push(controlsForGridPlaceholders(gridContainerIdentifier(selectedElement))) + } return { id: SetGridGapStrategyId, @@ -151,37 +175,48 @@ export const setGridGapStrategy: CanvasStrategyFactory = ( return emptyStrategyApplicationResult } + const shouldSetGapByAxis = gridGap.row.value != null || gridGap.column.value != null + const axis = interactionSession.activeControl.axis const shouldTearOffGapByAxis = axis === 'row' ? shouldTearOffGap.y : shouldTearOffGap.x - const axisStyleProp = axis === 'row' ? StyleRowGapProp : StyleColumnGapProp + + const axisStyleProp = shouldSetGapByAxis + ? axis === 'row' + ? StyleRowGapProp + : StyleColumnGapProp + : StyleGapProp + const gridGapMeasurement = axis === 'row' ? updatedGridGapMeasurement.row : updatedGridGapMeasurement.column if (shouldTearOffGapByAxis) { - return strategyApplicationResult([ - deleteProperties('always', selectedElement, [axisStyleProp]), - ]) + return strategyApplicationResult( + [deleteProperties('always', selectedElement, [axisStyleProp])], + selectedElements, + ) } - return strategyApplicationResult([ - setProperty( - 'always', - selectedElement, - axisStyleProp, - printCSSNumber(fallbackEmptyValue(gridGapMeasurement), null), - ), - setCursorCommand(cursorFromAxis(axis)), - setElementsToRerenderCommand([...selectedElements, ...children.map((c) => c.elementPath)]), - setActiveFrames([ - { - action: 'set-gap', - target: activeFrameTargetPath(selectedElement), - source: zeroRectIfNullOrInfinity( - MetadataUtils.getFrameInCanvasCoords(selectedElement, canvasState.startingMetadata), - ), - }, - ]), - ]) + return strategyApplicationResult( + [ + setProperty( + 'always', + selectedElement, + axisStyleProp, + printCSSNumber(fallbackEmptyValue(gridGapMeasurement), null), + ), + setCursorCommand(cursorFromAxis(axis)), + setActiveFrames([ + { + action: 'set-gap', + target: activeFrameTargetPath(selectedElement), + source: zeroRectIfNullOrInfinity( + MetadataUtils.getFrameInCanvasCoords(selectedElement, canvasState.startingMetadata), + ), + }, + ]), + ], + selectedElements, + ) }, } } diff --git a/editor/src/components/canvas/canvas-strategies/strategies/set-padding-strategy.spec.browser2.tsx b/editor/src/components/canvas/canvas-strategies/strategies/set-padding-strategy.spec.browser2.tsx index 286cbe95f566..d5d791ff98f6 100644 --- a/editor/src/components/canvas/canvas-strategies/strategies/set-padding-strategy.spec.browser2.tsx +++ b/editor/src/components/canvas/canvas-strategies/strategies/set-padding-strategy.spec.browser2.tsx @@ -1,7 +1,16 @@ +import * as EP from '../../../../core/shared/element-path' import { assertNever } from '../../../../core/shared/utils' +import { TailwindConfigPath } from '../../../../core/tailwind/tailwind-config' +import { createModifiedProject } from '../../../../sample-projects/sample-project-utils.test-utils' import type { Modifiers } from '../../../../utils/modifiers' -import { cmdModifier, shiftModifier } from '../../../../utils/modifiers' -import { expectSingleUndo2Saves, wait } from '../../../../utils/utils.test-utils' +import { cmdModifier } from '../../../../utils/modifiers' +import { + expectSingleUndo2Saves, + selectComponentsForTest, + setFeatureForBrowserTestsUseInDescribeBlockOnly, + wait, +} from '../../../../utils/utils.test-utils' +import { StoryboardFilePath } from '../../../editor/store/editor-state' import { cssNumber } from '../../../inspector/common/css-utils' import type { EdgePiece } from '../../canvas-types' import { isHorizontalEdgePiece } from '../../canvas-types' @@ -39,6 +48,7 @@ import { getPrintedUiJsCode, makeTestProjectCodeWithSnippet, renderTestEditorWithCode, + renderTestEditorWithModel, } from '../../ui-jsx.test-utils' import { PaddingTearThreshold, SetPaddingStrategyName } from './set-padding-strategy' @@ -745,6 +755,88 @@ describe('Padding resize strategy', () => { }) }) }) + + describe('Tailwind', () => { + setFeatureForBrowserTestsUseInDescribeBlockOnly('Tailwind', true) + + const TailwindProject = (classes: string) => + createModifiedProject({ + [StoryboardFilePath]: ` + import React from 'react' + import { Scene, Storyboard } from 'utopia-api' + export var storyboard = ( + + +
+
+
+
+ + + ) + + `, + [TailwindConfigPath]: ` + const TailwindConfig = { } + export default TailwindConfig + `, + 'app.css': ` + @tailwind base; + @tailwind components; + @tailwind utilities;`, + }) + + it('can set tailwind padding', async () => { + const editor = await renderTestEditorWithModel( + TailwindProject('p-12'), + 'await-first-dom-report', + ) + await selectComponentsForTest(editor, [EP.fromString('sb/scene/mydiv')]) + await testPaddingResizeForEdge(editor, 50, 'top', 'precise') + await editor.getDispatchFollowUpActionsFinished() + const div = editor.renderedDOM.getByTestId('mydiv') + expect(div.className).toEqual('top-10 left-10 absolute flex flex-row p-[6rem_3rem_3rem_3rem]') + }) + + it('can remove tailwind padding', async () => { + const editor = await renderTestEditorWithModel( + TailwindProject('p-4'), + 'await-first-dom-report', + ) + await selectComponentsForTest(editor, [EP.fromString('sb/scene/mydiv')]) + await testPaddingResizeForEdge(editor, -150, 'top', 'precise') + await editor.getDispatchFollowUpActionsFinished() + const div = editor.renderedDOM.getByTestId('mydiv') + expect(div.className).toEqual('top-10 left-10 absolute flex flex-row pb-4 pl-4 pr-4') + }) + + it('can set tailwind padding longhand', async () => { + const editor = await renderTestEditorWithModel( + TailwindProject('pt-12'), + 'await-first-dom-report', + ) + await selectComponentsForTest(editor, [EP.fromString('sb/scene/mydiv')]) + await testPaddingResizeForEdge(editor, 50, 'top', 'precise') + await editor.getDispatchFollowUpActionsFinished() + const div = editor.renderedDOM.getByTestId('mydiv') + expect(div.className).toEqual('top-10 left-10 absolute flex flex-row pt-24') + }) + }) }) async function testAdjustIndividualPaddingValue(edge: EdgePiece, precision: AdjustPrecision) { diff --git a/editor/src/components/canvas/canvas-strategies/strategies/set-padding-strategy.tsx b/editor/src/components/canvas/canvas-strategies/strategies/set-padding-strategy.tsx index 7210fb3da37e..a613e847cb17 100644 --- a/editor/src/components/canvas/canvas-strategies/strategies/set-padding-strategy.tsx +++ b/editor/src/components/canvas/canvas-strategies/strategies/set-padding-strategy.tsx @@ -14,7 +14,7 @@ import type { EdgePiece } from '../../canvas-types' import { CSSCursor, isHorizontalEdgePiece, oppositeEdgePiece } from '../../canvas-types' import { deleteProperties } from '../../commands/delete-properties-command' import { setCursorCommand } from '../../commands/set-cursor-command' -import { setElementsToRerenderCommand } from '../../commands/set-elements-to-rerender-command' + import { setProperty } from '../../commands/set-property-command' import { updateHighlightedViews } from '../../commands/update-highlighted-views-command' import { isZeroSizedElement } from '../../controls/outline-utils' @@ -32,11 +32,11 @@ import { paddingPropForEdge, paddingToPaddingString, printCssNumberWithDefaultUnit, - simplePaddingFromMetadata, + simplePaddingFromStyleInfo, } from '../../padding-utils' import type { CanvasStrategyFactory } from '../canvas-strategies' import { onlyFitWhenDraggingThisControl } from '../canvas-strategies' -import type { InteractionCanvasState } from '../canvas-strategy-types' +import type { InteractionCanvasState, StyleInfoReader } from '../canvas-strategy-types' import { controlWithProps, emptyStrategyApplicationResult, @@ -72,10 +72,6 @@ import { elementHasOnlyTextChildren } from '../../canvas-utils' import type { Modifiers } from '../../../../utils/modifiers' import type { Axis } from '../../../inspector/inspector-common' import { detectFillHugFixedState, isHuggingFixedHugFill } from '../../../inspector/inspector-common' -import { - AdjustCssLengthProperties, - adjustCssLengthProperties, -} from '../../commands/adjust-css-length-command' import type { ElementPathTrees } from '../../../../core/shared/element-path-tree' import { activeFrameTargetPath, setActiveFrames } from '../../commands/set-active-frames-command' @@ -118,6 +114,7 @@ export const setPaddingStrategy: CanvasStrategyFactory = (canvasState, interacti canvasState.startingMetadata, canvasState.startingElementPathTree, selectedElements[0], + canvasState.styleInfoReader, ) ) { return null @@ -134,6 +131,7 @@ export const setPaddingStrategy: CanvasStrategyFactory = (canvasState, interacti props: { targets: selectedElements }, key: 'padding-resize-control', show: 'visible-except-when-other-strategy-is-active', + priority: 'bottom', }) const controlsToRender = optionalMap( @@ -184,7 +182,11 @@ export const setPaddingStrategy: CanvasStrategyFactory = (canvasState, interacti const edgePiece = interactionSession.activeControl.edgePiece const drag = interactionSession.interactionData.drag ?? canvasVector({ x: 0, y: 0 }) - const padding = simplePaddingFromMetadata(canvasState.startingMetadata, selectedElement) + const padding = simplePaddingFromStyleInfo( + canvasState.startingMetadata, + selectedElement, + canvasState.styleInfoReader(selectedElement), + ) const paddingPropInteractedWith = paddingPropForEdge(edgePiece) const currentPadding = padding[paddingPropInteractedWith]?.renderedValuePx ?? 0 const rawDelta = deltaFromEdge(drag, edgePiece) @@ -207,7 +209,6 @@ export const setPaddingStrategy: CanvasStrategyFactory = (canvasState, interacti const basicCommands: CanvasCommand[] = [ updateHighlightedViews('mid-interaction', []), setCursorCommand(pickCursorFromEdge(edgePiece)), - setElementsToRerenderCommand(selectedElements), ] const nonZeroPropsToAdd = IndividualPaddingProps.flatMap( @@ -246,11 +247,70 @@ export const setPaddingStrategy: CanvasStrategyFactory = (canvasState, interacti // "tearing off" padding if (newPaddingEdge.renderedValuePx < PaddingTearThreshold) { - return strategyApplicationResult([ + return strategyApplicationResult( + [ + ...basicCommands, + deleteProperties('always', selectedElement, [ + StylePaddingProp, + stylePropPathMappingFn(paddingPropInteractedWith, styleStringInArray), + ]), + ...nonZeroPropsToAdd.map(([p, value]) => + setProperty( + 'always', + selectedElement, + stylePropPathMappingFn(p, styleStringInArray), + value, + ), + ), + setActiveFrames( + selectedElements.map((path) => ({ + action: 'set-padding', + target: activeFrameTargetPath(path), + source: zeroRectIfNullOrInfinity( + MetadataUtils.getFrameInCanvasCoords(path, canvasState.startingMetadata), + ), + })), + ), + ], + selectedElements, + ) + } + + const allPaddingPropsDefined = maybeFullPadding(newPaddingMaxed) + + // all 4 sides present - can be represented via the padding shorthand property + if (allPaddingPropsDefined != null) { + const paddingString = paddingToPaddingString(allPaddingPropsDefined) + return strategyApplicationResult( + [ + ...basicCommands, + ...IndividualPaddingProps.map((p) => + deleteProperties('always', selectedElement, [ + stylePropPathMappingFn(p, styleStringInArray), + ]), + ), + setProperty('always', selectedElement, StylePaddingProp, paddingString), + setActiveFrames( + selectedElements.map((path) => ({ + action: 'set-padding', + target: activeFrameTargetPath(path), + source: zeroRectIfNullOrInfinity( + MetadataUtils.getFrameInCanvasCoords(path, canvasState.startingMetadata), + ), + })), + ), + ], + selectedElements, + ) + } + + // only some sides are present - longhand properties have to be used + return strategyApplicationResult( + [ ...basicCommands, deleteProperties('always', selectedElement, [ StylePaddingProp, - stylePropPathMappingFn(paddingPropInteractedWith, styleStringInArray), + ...IndividualPaddingProps.map((p) => stylePropPathMappingFn(p, styleStringInArray)), ]), ...nonZeroPropsToAdd.map(([p, value]) => setProperty( @@ -269,59 +329,9 @@ export const setPaddingStrategy: CanvasStrategyFactory = (canvasState, interacti ), })), ), - ]) - } - - const allPaddingPropsDefined = maybeFullPadding(newPaddingMaxed) - - // all 4 sides present - can be represented via the padding shorthand property - if (allPaddingPropsDefined != null) { - const paddingString = paddingToPaddingString(allPaddingPropsDefined) - return strategyApplicationResult([ - ...basicCommands, - ...IndividualPaddingProps.map((p) => - deleteProperties('always', selectedElement, [ - stylePropPathMappingFn(p, styleStringInArray), - ]), - ), - setProperty('always', selectedElement, StylePaddingProp, paddingString), - setActiveFrames( - selectedElements.map((path) => ({ - action: 'set-padding', - target: activeFrameTargetPath(path), - source: zeroRectIfNullOrInfinity( - MetadataUtils.getFrameInCanvasCoords(path, canvasState.startingMetadata), - ), - })), - ), - ]) - } - - // only some sides are present - longhand properties have to be used - return strategyApplicationResult([ - ...basicCommands, - deleteProperties('always', selectedElement, [ - StylePaddingProp, - ...IndividualPaddingProps.map((p) => stylePropPathMappingFn(p, styleStringInArray)), - ]), - ...nonZeroPropsToAdd.map(([p, value]) => - setProperty( - 'always', - selectedElement, - stylePropPathMappingFn(p, styleStringInArray), - value, - ), - ), - setActiveFrames( - selectedElements.map((path) => ({ - action: 'set-padding', - target: activeFrameTargetPath(path), - source: zeroRectIfNullOrInfinity( - MetadataUtils.getFrameInCanvasCoords(path, canvasState.startingMetadata), - ), - })), - ), - ]) + ], + selectedElements, + ) }, } } @@ -343,6 +353,7 @@ function supportsPaddingControls( metadata: ElementInstanceMetadataMap, pathTrees: ElementPathTrees, path: ElementPath, + styleInfoReader: StyleInfoReader, ): boolean { const element = MetadataUtils.findElementByElementPath(metadata, path) if (element == null) { @@ -357,7 +368,7 @@ function supportsPaddingControls( return false } - const padding = simplePaddingFromMetadata(metadata, path) + const padding = simplePaddingFromStyleInfo(metadata, path, styleInfoReader(path)) const { top, right, bottom, left } = element.specialSizeMeasurements.padding const elementHasNonzeroPaddingFromMeasurements = [top, right, bottom, left].some( (s) => s != null && s > 0, @@ -422,9 +433,10 @@ function paddingValueIndicatorProps( const edgePiece = interactionSession.activeControl.edgePiece - const padding = simplePaddingFromMetadata( + const padding = simplePaddingFromStyleInfo( canvasState.startingMetadata, filteredSelectedElements[0], + canvasState.styleInfoReader(filteredSelectedElements[0]), ) const currentPadding = padding[paddingPropForEdge(edgePiece)] ?? unitlessCSSNumberWithRenderedValue(0) @@ -550,7 +562,11 @@ function calculateAdjustDelta( const edgePiece = interactionSession.activeControl.edgePiece const drag = interactionSession.interactionData.drag ?? canvasVector({ x: 0, y: 0 }) - const padding = simplePaddingFromMetadata(canvasState.startingMetadata, selectedElement) + const padding = simplePaddingFromStyleInfo( + canvasState.startingMetadata, + selectedElement, + canvasState.styleInfoReader(selectedElement), + ) const paddingPropInteractedWith = paddingPropForEdge(edgePiece) const currentPadding = padding[paddingPropForEdge(edgePiece)]?.renderedValuePx ?? 0 const rawDelta = deltaFromEdge(drag, edgePiece) diff --git a/editor/src/components/canvas/canvas-strategies/strategies/shared-absolute-resize-strategy-helpers.ts b/editor/src/components/canvas/canvas-strategies/strategies/shared-absolute-resize-strategy-helpers.ts index bc658645f1f5..a70ebc28d65c 100644 --- a/editor/src/components/canvas/canvas-strategies/strategies/shared-absolute-resize-strategy-helpers.ts +++ b/editor/src/components/canvas/canvas-strategies/strategies/shared-absolute-resize-strategy-helpers.ts @@ -40,11 +40,6 @@ import { EdgePositionBottom, } from '../../canvas-types' import { pickPointOnRect, snapPoint } from '../../canvas-utils' -import type { AdjustCssLengthProperties } from '../../commands/adjust-css-length-command' -import { - adjustCssLengthProperties, - lengthPropertyToAdjust, -} from '../../commands/adjust-css-length-command' import { pointGuidelineToBoundsEdge } from '../../controls/guideline-helpers' import type { AbsolutePin } from './resize-helpers' import { ensureAtLeastTwoPinsForEdgePosition, resizeBoundingBox } from './resize-helpers' @@ -57,7 +52,7 @@ import { MetadataUtils } from '../../../../core/model/element-metadata-utils' import { addOrMergeIntendedBounds } from './shared-keyboard-strategy-helpers' import type { InspectorStrategy } from '../../../../components/inspector/inspector-strategies/inspector-strategy' import { pushIntendedBoundsAndUpdateGroups } from '../../commands/push-intended-bounds-and-update-groups-command' -import { setElementsToRerenderCommand } from '../../commands/set-elements-to-rerender-command' + import { withUnderlyingTarget } from '../../../../components/editor/store/editor-state' import type { SetCssLengthProperty } from '../../commands/set-css-length-command' import { @@ -416,7 +411,7 @@ export function resizeInspectorStrategy( commands.push( pushIntendedBoundsAndUpdateGroups(changeBoundsResult.intendedBounds, 'live-metadata'), ) - commands.push(setElementsToRerenderCommand(selectedElements)) + return commands }, } @@ -460,7 +455,7 @@ export function directResizeInspectorStrategy( pushIntendedBoundsAndUpdateGroups(changeBoundsResult.intendedBounds, 'live-metadata'), ) } - commands.push(setElementsToRerenderCommand(selectedElements)) + return commands }, } diff --git a/editor/src/components/canvas/canvas-strategies/strategies/shared-move-strategies-helpers.ts b/editor/src/components/canvas/canvas-strategies/strategies/shared-move-strategies-helpers.ts index 5397ee1f84a9..26595f82095b 100644 --- a/editor/src/components/canvas/canvas-strategies/strategies/shared-move-strategies-helpers.ts +++ b/editor/src/components/canvas/canvas-strategies/strategies/shared-move-strategies-helpers.ts @@ -31,6 +31,7 @@ import { } from '../../../../core/shared/math-utils' import type { ElementPath } from '../../../../core/shared/project-file-types' +import type { AllElementProps } from '../../../editor/store/editor-state' import { getJSXElementFromProjectContents } from '../../../editor/store/editor-state' import { stylePropPathMappingFn } from '../../../inspector/common/property-path-hooks' import { determineConstrainedDragAxis } from '../../canvas-controls-frame' @@ -44,7 +45,7 @@ import { import type { CanvasCommand } from '../../commands/commands' import { pushIntendedBoundsAndUpdateGroups } from '../../commands/push-intended-bounds-and-update-groups-command' import { setCursorCommand } from '../../commands/set-cursor-command' -import { setElementsToRerenderCommand } from '../../commands/set-elements-to-rerender-command' + import { setSnappingGuidelines } from '../../commands/set-snapping-guidelines-command' import { updateHighlightedViews } from '../../commands/update-highlighted-views-command' import { @@ -78,6 +79,7 @@ import { } from '../../commands/set-css-length-command' import type { ActiveFrame, ActiveFrameAction } from '../../commands/set-active-frames-command' import { activeFrameTargetRect, setActiveFrames } from '../../commands/set-active-frames-command' +import type { ElementPathTrees } from '../../../../core/shared/element-path-tree' export interface MoveCommandsOptions { ignoreLocalFrame?: boolean @@ -152,17 +154,20 @@ export function applyMoveCommon( if (cmdKeyPressed) { const commandsForSelectedElements = getMoveCommands(drag) - return strategyApplicationResult([ - ...commandsForSelectedElements.commands, - pushIntendedBoundsAndUpdateGroups( - commandsForSelectedElements.intendedBounds, - 'starting-metadata', - ), - updateHighlightedViews('mid-interaction', []), - setElementsToRerenderCommand(targets), - setCursorCommand(CSSCursor.Select), - setActiveFrames(getActiveFrames(commandsForSelectedElements.intendedBounds)), - ]) + return strategyApplicationResult( + [ + ...commandsForSelectedElements.commands, + pushIntendedBoundsAndUpdateGroups( + commandsForSelectedElements.intendedBounds, + 'starting-metadata', + ), + updateHighlightedViews('mid-interaction', []), + + setCursorCommand(CSSCursor.Select), + setActiveFrames(getActiveFrames(commandsForSelectedElements.intendedBounds)), + ], + targets, + ) } else { const constrainedDragAxis = shiftKeyPressed && drag != null ? determineConstrainedDragAxis(drag) : null @@ -189,18 +194,21 @@ export function applyMoveCommon( canvasState.scale, ) const commandsForSelectedElements = getMoveCommands(snappedDragVector) - return strategyApplicationResult([ - ...commandsForSelectedElements.commands, - updateHighlightedViews('mid-interaction', []), - setSnappingGuidelines('mid-interaction', guidelinesWithSnappingVector), - pushIntendedBoundsAndUpdateGroups( - commandsForSelectedElements.intendedBounds, - 'starting-metadata', - ), - setElementsToRerenderCommand([...targets, ...targetsForSnapping]), - setCursorCommand(CSSCursor.Select), - setActiveFrames(getActiveFrames(commandsForSelectedElements.intendedBounds)), - ]) + return strategyApplicationResult( + [ + ...commandsForSelectedElements.commands, + updateHighlightedViews('mid-interaction', []), + setSnappingGuidelines('mid-interaction', guidelinesWithSnappingVector), + pushIntendedBoundsAndUpdateGroups( + commandsForSelectedElements.intendedBounds, + 'starting-metadata', + ), + + setCursorCommand(CSSCursor.Select), + setActiveFrames(getActiveFrames(commandsForSelectedElements.intendedBounds)), + ], + [...targets, ...targetsForSnapping], + ) } } else { // Fallback for when the checks above are not satisfied. @@ -218,18 +226,22 @@ function getAppropriateLocalFrame( : MetadataUtils.getLocalFrameFromSpecialSizeMeasurements(selectedElement, startingMetadata) } +interface CommandsAndIntendedBounds { + commands: Array + intendedBounds: Array +} + export function getDirectMoveCommandsForSelectedElement( projectContents: ProjectContentTreeRoot, startingMetadata: ElementInstanceMetadataMap, + startingAllElementProps: AllElementProps, + startingElementPathTree: ElementPathTrees, selectedElement: ElementPath, mappedPath: ElementPath, leftOrTop: 'left' | 'top', newPixelValue: number, options?: MoveCommandsOptions, -): { - commands: Array - intendedBounds: Array -} { +): CommandsAndIntendedBounds { const localFrame = getAppropriateLocalFrame(options, selectedElement, startingMetadata) const drag = canvasVector({ @@ -240,23 +252,63 @@ export function getDirectMoveCommandsForSelectedElement( return getMoveCommandsForSelectedElement( projectContents, startingMetadata, + startingAllElementProps, + startingElementPathTree, + selectedElement, + mappedPath, + drag, + ) +} + +export function getMoveCommandsForDrag( + elementParentBounds: CanvasRectangle | null, + element: JSXElement, + selectedElement: ElementPath, + mappedPath: ElementPath, + drag: CanvasVector, + globalFrame: CanvasRectangle | null, + localFrame: LocalRectangle | null, + parentFlexDirection: FlexDirection | null, + ignoreLocalFrame: boolean, +): CommandsAndIntendedBounds { + if (ignoreLocalFrame) { + return createMoveCommandsForElementPositionRelative( + element, + selectedElement, + mappedPath, + drag, + globalFrame, + elementParentBounds, + parentFlexDirection, + ) + } + + if (globalFrame == null || localFrame == null) { + return { commands: [], intendedBounds: [] } + } + + return createMoveCommandsForElementCreatingMissingPins( + element, selectedElement, mappedPath, drag, + globalFrame, + localFrame, + elementParentBounds, + parentFlexDirection, ) } export function getMoveCommandsForSelectedElement( projectContents: ProjectContentTreeRoot, startingMetadata: ElementInstanceMetadataMap, + startingAllElementProps: AllElementProps, + startingElementPathTree: ElementPathTrees, selectedElement: ElementPath, mappedPath: ElementPath, drag: CanvasVector, options?: MoveCommandsOptions, -): { - commands: Array - intendedBounds: Array -} { +): CommandsAndIntendedBounds { const element: JSXElement | null = getJSXElementFromProjectContents( selectedElement, projectContents, @@ -267,44 +319,49 @@ export function getMoveCommandsForSelectedElement( selectedElement, ) - const elementParentBounds = - elementMetadata?.specialSizeMeasurements.coordinateSystemBounds ?? null - - const globalFrame = nullIfInfinity( - MetadataUtils.getFrameInCanvasCoords(selectedElement, startingMetadata), + const closestNonFragmentParent = MetadataUtils.getClosestNonFragmentParent( + startingMetadata, + startingAllElementProps, + startingElementPathTree, + EP.parentPath(mappedPath), ) + const closestNonFragmentParentMetadata = MetadataUtils.findElementByElementPath( + startingMetadata, + closestNonFragmentParent, + ) + const providesBoundsForAbsoluteChildren = + closestNonFragmentParentMetadata?.specialSizeMeasurements.providesBoundsForAbsoluteChildren ?? + false - const localFrame = nullIfInfinity(MetadataUtils.getLocalFrame(selectedElement, startingMetadata)) + const elementParentBounds = + elementMetadata?.specialSizeMeasurements.coordinateSystemBounds ?? + (providesBoundsForAbsoluteChildren + ? nullIfInfinity(closestNonFragmentParentMetadata?.globalFrame) + : null) ?? + null if (element == null) { return { commands: [], intendedBounds: [] } } - if (options?.ignoreLocalFrame === true) { - return createMoveCommandsForElementPositionRelative( - element, - selectedElement, - mappedPath, - drag, - globalFrame, - elementParentBounds, - elementMetadata?.specialSizeMeasurements.parentFlexDirection ?? null, - ) - } + const globalFrame = nullIfInfinity( + MetadataUtils.getFrameInCanvasCoords(selectedElement, startingMetadata), + ) - if (globalFrame == null || localFrame == null) { - return { commands: [], intendedBounds: [] } - } + const localFrame = nullIfInfinity( + MetadataUtils.getLocalFrame(selectedElement, startingMetadata, EP.parentPath(mappedPath)), + ) - return createMoveCommandsForElementCreatingMissingPins( + return getMoveCommandsForDrag( + elementParentBounds, element, selectedElement, mappedPath, drag, globalFrame, localFrame, - elementParentBounds, elementMetadata?.specialSizeMeasurements.parentFlexDirection ?? null, + options?.ignoreLocalFrame ?? false, ) } @@ -324,6 +381,8 @@ export function getInteractionMoveCommandsForSelectedElement( return getMoveCommandsForSelectedElement( canvasState.projectContents, canvasState.startingMetadata, + canvasState.startingAllElementProps, + canvasState.startingElementPathTree, selectedElement, mappedPath, drag, @@ -333,6 +392,8 @@ export function getInteractionMoveCommandsForSelectedElement( export function moveInspectorStrategy( metadata: ElementInstanceMetadataMap, + allElementProps: AllElementProps, + elementPathTree: ElementPathTrees, selectedElementPaths: ElementPath[], projectContents: ProjectContentTreeRoot, movement: CanvasVector, @@ -346,6 +407,8 @@ export function moveInspectorStrategy( const moveCommandsResult = getMoveCommandsForSelectedElement( projectContents, metadata, + allElementProps, + elementPathTree, selectedPath, selectedPath, movement, @@ -354,7 +417,7 @@ export function moveInspectorStrategy( intendedBounds.push(...moveCommandsResult.intendedBounds) } commands.push(pushIntendedBoundsAndUpdateGroups(intendedBounds, 'live-metadata')) - commands.push(setElementsToRerenderCommand(selectedElementPaths)) + return commands }, } @@ -362,6 +425,8 @@ export function moveInspectorStrategy( export function directMoveInspectorStrategy( metadata: ElementInstanceMetadataMap, + allElementProps: AllElementProps, + elementPathTree: ElementPathTrees, selectedElementPaths: ElementPath[], projectContents: ProjectContentTreeRoot, leftOrTop: 'left' | 'top', @@ -376,6 +441,8 @@ export function directMoveInspectorStrategy( const moveCommandsResult = getDirectMoveCommandsForSelectedElement( projectContents, metadata, + allElementProps, + elementPathTree, selectedPath, selectedPath, leftOrTop, @@ -385,7 +452,7 @@ export function directMoveInspectorStrategy( intendedBounds.push(...moveCommandsResult.intendedBounds) } commands.push(pushIntendedBoundsAndUpdateGroups(intendedBounds, 'live-metadata')) - commands.push(setElementsToRerenderCommand(selectedElementPaths)) + return commands }, } diff --git a/editor/src/components/canvas/canvas-types.ts b/editor/src/components/canvas/canvas-types.ts index 92033f498e91..787c04380f36 100644 --- a/editor/src/components/canvas/canvas-types.ts +++ b/editor/src/components/canvas/canvas-types.ts @@ -1,9 +1,8 @@ import type { ReactElement } from 'react' -import { ElementInstanceMetadataMap } from '../../core/shared/element-template' +import type { JSExpression, PartOfJSXAttributeValue } from '../../core/shared/element-template' import type { PropertyPath, ElementPath } from '../../core/shared/project-file-types' import type { KeysPressed } from '../../utils/keyboard' import type { Modifiers } from '../../utils/modifiers' -import { keepDeepReferenceEqualityIfPossible } from '../../utils/react-performance' import type { CanvasPoint, CanvasRectangle, @@ -15,19 +14,26 @@ import type { import type { EditorPanel } from '../common/actions/index' import type { Mode } from '../editor/editor-modes' import type { EditorState } from '../editor/store/editor-state' -import { OriginalCanvasAndLocalFrame } from '../editor/store/editor-state' -import { isFeatureEnabled } from '../../utils/feature-switches' -import { assertNever, xor } from '../../core/shared/utils' +import { assertNever } from '../../core/shared/utils' import type { LayoutTargetableProp } from '../../core/layout/layout-helpers-new' import type { DragInteractionData, InteractionSessionWithoutMetadata, } from './canvas-strategies/interaction-state' -import { InteractionSession } from './canvas-strategies/interaction-state' import type { CanvasStrategyId } from './canvas-strategies/canvas-strategy-types' import type { MouseButtonsPressed } from '../../utils/mouse' +import type { FlexWrap } from 'utopia-api/core' +import type { + CSSBorderRadius, + CSSNumber, + CSSOverflow, + CSSPadding, + FlexDirection, +} from '../inspector/common/css-utils' +import type { ScreenSize } from './responsive-types' export const CanvasContainerID = 'canvas-container' +export const SceneContainerName = 'scene' // TODO: this should not be an enum but a const object export enum CSSCursor { @@ -533,3 +539,126 @@ export const EdgePositionBottomRight: EdgePosition = { x: 1, y: 1 } export const EdgePositionTopRight: EdgePosition = { x: 1, y: 0 } export type SelectionLocked = 'locked' | 'locked-hierarchy' | 'selectable' + +export type PropertyTag = { type: 'hover' } | { type: 'breakpoint'; name: string } + +interface CSSStylePropertyNotFound { + type: 'not-found' +} + +interface CSSStylePropertyNotParsable { + type: 'not-parsable' + originalValue: JSExpression | PartOfJSXAttributeValue +} + +interface ParsedCSSStyleProperty { + type: 'property' + tags: PropertyTag[] + propertyValue: JSExpression | PartOfJSXAttributeValue + value: T +} + +type StyleHoverModifier = { type: 'hover' } +export type StyleMediaSizeModifier = { + type: 'media-size' + size: ScreenSize +} +export type StyleModifier = StyleHoverModifier | StyleMediaSizeModifier + +export type CSSStyleProperty = + | CSSStylePropertyNotFound + | CSSStylePropertyNotParsable + | ParsedCSSStyleProperty + +export function cssStylePropertyNotFound(): CSSStylePropertyNotFound { + return { type: 'not-found' } +} + +export function cssStylePropertyNotParsable( + originalValue: JSExpression | PartOfJSXAttributeValue, +): CSSStylePropertyNotParsable { + return { type: 'not-parsable', originalValue: originalValue } +} + +export function cssStyleProperty( + value: T, + propertyValue: JSExpression | PartOfJSXAttributeValue, +): ParsedCSSStyleProperty { + return { type: 'property', tags: [], value: value, propertyValue: propertyValue } +} + +export function maybePropertyValue(property: CSSStyleProperty): T | null { + if (property.type === 'property') { + return property.value + } + return null +} + +export type FlexGapInfo = CSSStyleProperty +export type FlexDirectionInfo = CSSStyleProperty +export type LeftInfo = CSSStyleProperty +export type RightInfo = CSSStyleProperty +export type TopInfo = CSSStyleProperty +export type BottomInfo = CSSStyleProperty +export type WidthInfo = CSSStyleProperty +export type HeightInfo = CSSStyleProperty +export type FlexBasisInfo = CSSStyleProperty +export type PaddingInfo = CSSStyleProperty +export type PaddingSideInfo = CSSStyleProperty +export type BorderRadiusInfo = CSSStyleProperty +export type BorderRadiusCornerInfo = CSSStyleProperty +export type ZIndexInfo = CSSStyleProperty +export type FlexWrapInfo = CSSStyleProperty +export type OverflowInfo = CSSStyleProperty + +export interface StyleInfo { + gap: FlexGapInfo | null + flexDirection: FlexDirectionInfo | null + left: LeftInfo | null + right: RightInfo | null + top: TopInfo | null + bottom: BottomInfo | null + width: WidthInfo | null + height: HeightInfo | null + flexBasis: FlexBasisInfo | null + padding: PaddingInfo | null + paddingTop: PaddingSideInfo | null + paddingRight: PaddingSideInfo | null + paddingBottom: PaddingSideInfo | null + paddingLeft: PaddingSideInfo | null + borderRadius: BorderRadiusInfo | null + borderTopLeftRadius: BorderRadiusCornerInfo | null + borderTopRightRadius: BorderRadiusCornerInfo | null + borderBottomRightRadius: BorderRadiusCornerInfo | null + borderBottomLeftRadius: BorderRadiusCornerInfo | null + zIndex: ZIndexInfo | null + flexWrap: FlexWrapInfo | null + overflow: OverflowInfo | null +} + +const emptyStyleInfo: StyleInfo = { + gap: null, + flexDirection: null, + left: null, + right: null, + top: null, + bottom: null, + width: null, + height: null, + flexBasis: null, + padding: null, + paddingTop: null, + paddingRight: null, + paddingBottom: null, + paddingLeft: null, + borderRadius: null, + borderTopLeftRadius: null, + borderTopRightRadius: null, + borderBottomRightRadius: null, + borderBottomLeftRadius: null, + zIndex: null, + flexWrap: null, + overflow: null, +} + +export const isStyleInfoKey = (key: string): key is keyof StyleInfo => key in emptyStyleInfo diff --git a/editor/src/components/canvas/canvas-utils.ts b/editor/src/components/canvas/canvas-utils.ts index 0b384a89ae13..2bfd96433fdc 100644 --- a/editor/src/components/canvas/canvas-utils.ts +++ b/editor/src/components/canvas/canvas-utils.ts @@ -63,7 +63,12 @@ import type { HighlightBoundsForUids, ExportsDetail, } from '../../core/shared/project-file-types' -import { isParseSuccess, isTextFile } from '../../core/shared/project-file-types' +import { + importsEquals, + isExportDefault, + isParseSuccess, + isTextFile, +} from '../../core/shared/project-file-types' import { applyUtopiaJSXComponentsChanges, getDefaultExportedTopLevelElement, @@ -140,7 +145,11 @@ import { getStoryboardUID } from '../../core/model/scene-utils' import { optionalMap } from '../../core/shared/optional-utils' import { assertNever, fastForEach } from '../../core/shared/utils' import type { ProjectContentTreeRoot } from '../assets' -import { getProjectFileByFilePath } from '../assets' +import { + getProjectFileByFilePath, + isProjectContentDirectory, + isProjectContentFile, +} from '../assets' import type { CSSNumber } from '../inspector/common/css-utils' import { parseCSSLengthPercent, printCSSNumber } from '../inspector/common/css-utils' import { getTopLevelName, importedFromWhere } from '../editor/import-utils' @@ -168,6 +177,7 @@ import { getComponentDescriptorForTarget } from '../../core/property-controls/pr import type { PropertyControlsInfo } from '../custom-code/code-file' import { mapDropNulls } from '../../core/shared/array-utils' import { isFeatureEnabled } from '../../utils/feature-switches' +import { addAll } from '../../core/shared/set-utils' function dragDeltaScaleForProp(prop: LayoutTargetableProp): number { switch (prop) { @@ -346,6 +356,7 @@ export function updateFramesOfScenesAndComponents( const localFrame = MetadataUtils.getLocalFrame( frameAndTarget.target, workingEditorState.jsxMetadata, + null, ) const valueFromDOM = getObservableValueForLayoutProp( elementMetadata, @@ -429,7 +440,7 @@ export function updateFramesOfScenesAndComponents( frameAndTarget.frame, ) const currentLocalFrame = nullIfInfinity( - MetadataUtils.getLocalFrame(target, workingEditorState.jsxMetadata), + MetadataUtils.getLocalFrame(target, workingEditorState.jsxMetadata, null), ) const currentFullFrame = optionalMap(Frame.getFullFrame, currentLocalFrame) const fullFrame = Frame.getFullFrame(newLocalFrame) @@ -925,7 +936,7 @@ export function collectGuidelines( } const instance = MetadataUtils.findElementByElementPath(metadata, selectedView) - const localFrame = MetadataUtils.getLocalFrame(selectedView, metadata) + const localFrame = MetadataUtils.getLocalFrame(selectedView, metadata, null) if ( instance != null && MetadataUtils.isImg(instance) && @@ -1729,9 +1740,9 @@ export function getValidElementPaths( const { topLevelElements: resolvedTopLevelElements, exportsDetail } = getParseSuccessForFilePath(resolvedFilePath, projectContents) // Handle default exports as they may actually be named. - if (originTopLevelName == null) { + if (originTopLevelName == null || originTopLevelName === 'default') { for (const exportDetail of exportsDetail) { - if (exportDetail.type === 'EXPORT_DEFAULT_FUNCTION_OR_CLASS') { + if (isExportDefault(exportDetail)) { originTopLevelName = exportDetail.name } } @@ -1782,7 +1793,10 @@ function getValidElementPathsFromElement( ? EP.appendNewElementPath(parentPath, uid) : EP.appendToPath(parentPath, uid) : parentPath - let paths = includeElementInPath ? [path] : [] + let paths: Set = new Set() + if (includeElementInPath) { + paths.add(path) + } if (isJSXElementLike(element)) { const isRemixScene = isRemixSceneElement(element, filePath, projectContents) if (isRemixScene) { @@ -1800,8 +1814,9 @@ function getValidElementPathsFromElement( return } - paths.push( - ...getValidElementPathsFromElement( + addAll( + paths, + getValidElementPathsFromElement( focusedElementPath, topLevelElement, parentPathInner, @@ -1834,7 +1849,7 @@ function getValidElementPathsFromElement( } } - return paths + return Array.from(paths) } } @@ -1866,7 +1881,7 @@ function getValidElementPathsFromElement( resolve, getRemixValidPathsGenerationContext, ) - paths.push(...result) + addAll(paths, result) } matchingAutofocusedPathParts.forEach((autofocusedPathPart) => { const result = getValidElementPaths( @@ -1879,13 +1894,14 @@ function getValidElementPathsFromElement( resolve, getRemixValidPathsGenerationContext, ) - paths.push(...result) + addAll(paths, result) }) // finally, add children elements fastForEach(element.children, (c) => - paths.push( - ...getValidElementPathsFromElement( + addAll( + paths, + getValidElementPathsFromElement( focusedElementPath, c, path, @@ -1907,8 +1923,9 @@ function getValidElementPathsFromElement( if (p.type === 'JSX_ATTRIBUTES_ENTRY') { const prop = p.value if (prop.type === 'JSX_ELEMENT') { - paths.push( - ...getValidElementPathsFromElement( + addAll( + paths, + getValidElementPathsFromElement( focusedElementPath, prop, path, @@ -1928,17 +1945,18 @@ function getValidElementPathsFromElement( }) } - return paths + return Array.from(paths) } else if ( (isJSXTextBlock(element) && isFeatureEnabled('Condensed Navigator Entries')) || isJSIdentifier(element) || isJSPropertyAccess(element) || isJSElementAccess(element) ) { - return paths + return Array.from(paths) } else if (isJSXMapExpression(element)) { - paths.push( - ...getValidElementPathsFromElement( + addAll( + paths, + getValidElementPathsFromElement( focusedElementPath, element.valueToMap, path, @@ -1952,7 +1970,10 @@ function getValidElementPathsFromElement( resolve, getRemixValidPathsGenerationContext, ), - ...getValidElementPathsFromElement( + ) + addAll( + paths, + getValidElementPathsFromElement( focusedElementPath, element.mapFunction, path, @@ -1967,7 +1988,7 @@ function getValidElementPathsFromElement( getRemixValidPathsGenerationContext, ), ) - return paths + return Array.from(paths) } else if (isJSExpressionOtherJavaScript(element)) { // FIXME: From investigation of https://github.com/concrete-utopia/utopia/issues/1137 // The paths this will generate will only be correct if the elements from `elementsWithin` @@ -1988,8 +2009,9 @@ function getValidElementPathsFromElement( // We explicitly prevent auto-focusing generated elements here, because to support it would // require using the elementPathTree to determine how many children of a scene were actually // generated, creating a chicken and egg situation. - paths.push( - ...getValidElementPathsFromElement( + addAll( + paths, + getValidElementPathsFromElement( focusedElementPath, e, path, @@ -2005,11 +2027,12 @@ function getValidElementPathsFromElement( ), ), ) - return paths + return Array.from(paths) } else if (isJSXConditionalExpression(element)) { fastForEach([element.whenTrue, element.whenFalse], (e) => { - paths.push( - ...getValidElementPathsFromElement( + addAll( + paths, + getValidElementPathsFromElement( focusedElementPath, e, path, @@ -2025,7 +2048,7 @@ function getValidElementPathsFromElement( ), ) }) - return paths + return Array.from(paths) } else { return [] } @@ -2167,3 +2190,73 @@ export function canvasPanelOffsets(): { right: inspector?.clientWidth ?? 0, } } + +export function projectContentsSameForRefreshRequire( + oldProjectContents: ProjectContentTreeRoot, + newProjectContents: ProjectContentTreeRoot, +): boolean { + if (oldProjectContents === newProjectContents) { + // Identical references, so the imports are the same. + return true + } else { + for (const [filename, oldProjectTree] of Object.entries(oldProjectContents)) { + const newProjectTree = newProjectContents[filename] + // No need to check these further if they have the same reference. + if (oldProjectTree === newProjectTree) { + continue + } + // If the file can't be found in the other tree, the imports are not the same. + if (newProjectTree == null) { + return false + } + if (isProjectContentFile(oldProjectTree) && isProjectContentFile(newProjectTree)) { + // Both entries are files. + const oldContent = oldProjectTree.content + const newContent = newProjectTree.content + if (isTextFile(oldContent) || isTextFile(newContent)) { + if (isTextFile(oldContent) && isTextFile(newContent)) { + const oldParsed = oldContent.fileContents.parsed + const newParsed = newContent.fileContents.parsed + if (isParseSuccess(oldParsed) || isParseSuccess(newParsed)) { + if (isParseSuccess(oldParsed) && isParseSuccess(newParsed)) { + if ( + !importsEquals(oldParsed.imports, newParsed.imports) || + oldParsed.combinedTopLevelArbitraryBlock !== + newParsed.combinedTopLevelArbitraryBlock || + oldParsed.exportsDetail !== newParsed.exportsDetail + ) { + // For the same file the imports, combined top + // level arbitrary block or exports have changed. + return false + } + } else { + // One of the files is a parse success but the other is not. + return false + } + } + } else { + // One of the files is a text file but the other is not. + return false + } + } + } else if ( + isProjectContentDirectory(oldProjectTree) && + isProjectContentDirectory(newProjectTree) + ) { + // Both entries are directories. + if ( + !projectContentsSameForRefreshRequire(oldProjectTree.children, newProjectTree.children) + ) { + // The imports of the subdirectories differ. + return false + } + } else { + // One of the entries is a file and the other is a directory. + return false + } + } + } + + // If nothing differs, return true. + return true +} diff --git a/editor/src/components/canvas/canvas-wrapper-component.tsx b/editor/src/components/canvas/canvas-wrapper-component.tsx index 099a094a4747..d3782584d585 100644 --- a/editor/src/components/canvas/canvas-wrapper-component.tsx +++ b/editor/src/components/canvas/canvas-wrapper-component.tsx @@ -85,17 +85,16 @@ export const CanvasWrapperComponent = React.memo(() => { // ^ prevents Monaco from pushing the inspector out }} > - {fatalErrors.length === 0 && !safeMode ? ( - - ) : null} + = [] let unfurledComponents: Array = [] - fastForEach(Object.values(componentTree.children), (childTree) => { + forEachElementPathTreeChild(componentTree, (childTree) => { if (EP.isRootElementOfInstance(childTree.path)) { unfurledComponents.push(childTree) } else { diff --git a/editor/src/components/canvas/commands/add-contain-layout-if-needed-command.ts b/editor/src/components/canvas/commands/add-contain-layout-if-needed-command.ts index 19364723ad6d..f9d67d83cb80 100644 --- a/editor/src/components/canvas/commands/add-contain-layout-if-needed-command.ts +++ b/editor/src/components/canvas/commands/add-contain-layout-if-needed-command.ts @@ -2,11 +2,10 @@ import { MetadataUtils } from '../../../core/model/element-metadata-utils' import * as EP from '../../../core/shared/element-path' import { emptyComments, jsExpressionValue } from '../../../core/shared/element-template' import type { ElementPath } from '../../../core/shared/project-file-types' -import { PropertyPath } from '../../../core/shared/project-file-types' import * as PP from '../../../core/shared/property-path' import type { EditorState } from '../../editor/store/editor-state' -import { applyValuesAtPath } from './adjust-number-command' import type { BaseCommand, CommandFunction, WhenToRun } from './commands' +import { applyValuesAtPath } from './utils/property-utils' export interface AddContainLayoutIfNeeded extends BaseCommand { type: 'ADD_CONTAIN_LAYOUT_IF_NEEDED' diff --git a/editor/src/components/canvas/commands/adjust-css-length-command.ts b/editor/src/components/canvas/commands/adjust-css-length-command.ts index 90daf3c850e6..df4b81ecd292 100644 --- a/editor/src/components/canvas/commands/adjust-css-length-command.ts +++ b/editor/src/components/canvas/commands/adjust-css-length-command.ts @@ -1,41 +1,34 @@ import { MetadataUtils } from '../../../core/model/element-metadata-utils' -import type { Either } from '../../../core/shared/either' -import { foldEither, isLeft, isRight, left, mapEither } from '../../../core/shared/either' import * as EP from '../../../core/shared/element-path' -import type { JSXAttributes, JSXElement } from '../../../core/shared/element-template' -import { - emptyComments, - isJSXElement, - jsExpressionValue, -} from '../../../core/shared/element-template' -import type { ValueAtPath } from '../../../core/shared/jsx-attributes' -import { setJSXValuesAtPaths, unsetJSXValuesAtPaths } from '../../../core/shared/jsx-attributes' -import { - getModifiableJSXAttributeAtPath, - jsxSimpleAttributeToValue, -} from '../../../core/shared/jsx-attribute-utils' import { roundTo, roundToNearestWhole } from '../../../core/shared/math-utils' import type { ElementPath, PropertyPath } from '../../../core/shared/project-file-types' import * as PP from '../../../core/shared/property-path' import type { EditorState } from '../../editor/store/editor-state' -import { modifyUnderlyingForOpenFile } from '../../editor/store/editor-state' import type { CSSNumber, FlexDirection } from '../../inspector/common/css-utils' -import { parseCSSPercent, parseCSSPx, printCSSNumber } from '../../inspector/common/css-utils' -import type { BaseCommand, CommandFunction, WhenToRun } from './commands' -import { deleteValuesAtPath } from './delete-properties-command' -import { patchParseSuccessAtElementPath } from './patch-utils' +import { printCSSNumber } from '../../inspector/common/css-utils' +import type { BaseCommand, CommandFunctionResult, WhenToRun } from './commands' +import { mapDropNulls } from '../../../core/shared/array-utils' +import { getCSSNumberFromStyleInfo, maybeCssPropertyFromInlineStyle } from './utils/property-utils' +import type { StyleUpdate } from '../plugins/style-plugins' +import { getActivePlugin, runStyleUpdateForStrategy } from '../plugins/style-plugins' +import type { InteractionLifecycle } from '../canvas-strategies/canvas-strategy-types' +import type { StyleInfo } from '../canvas-types' export type CreateIfNotExistant = 'create-if-not-existing' | 'do-not-create-if-doesnt-exist' +export type LengthProperty = 'left' | 'right' | 'bottom' | 'top' | 'width' | 'height' | 'flexBasis' + +type LengthPropertyPath = PropertyPath<['style', LengthProperty]> + export interface LengthPropertyToAdjust { - property: PropertyPath + property: LengthPropertyPath valuePx: number parentDimensionPx: number | undefined createIfNonExistant: CreateIfNotExistant } export function lengthPropertyToAdjust( - property: PropertyPath, + property: LengthPropertyPath, valuePx: number, parentDimensionPx: number | undefined, createIfNonExistant: CreateIfNotExistant, @@ -70,237 +63,152 @@ export function adjustCssLengthProperties( } } -interface UpdatedPropsAndCommandDescription { - updatedProps: JSXAttributes - commandDescription: string -} - -export const runAdjustCssLengthProperties: CommandFunction = ( +export const runAdjustCssLengthProperties = ( editorState: EditorState, command: AdjustCssLengthProperties, -) => { - let commandDescriptions: Array = [] - const updatedEditorState: EditorState = modifyUnderlyingForOpenFile( - command.target, + interactionLifecycle: InteractionLifecycle, +): CommandFunctionResult => { + const withConflictingPropertiesRemoved = deleteConflictingPropsForWidthHeight( + interactionLifecycle, editorState, - (element) => { - if (isJSXElement(element)) { - return command.properties.reduce((workingElement, property) => { - // Remove any conflicting properties... - const attributesWithConflictingPropsDeleted = - deleteConflictingPropsForWidthHeightFromAttributes( - workingElement.props, - property.property, - command.parentFlexDirection, - ) - // ...If we were unable to remove those properties, then bail out as we could break something. - if (isLeft(attributesWithConflictingPropsDeleted)) { - commandDescriptions.push(attributesWithConflictingPropsDeleted.value) - return workingElement - } - - // Get the current value of the property... - const currentValue = getModifiableJSXAttributeAtPath( - attributesWithConflictingPropsDeleted.value, - property.property, - ) - // ...If the value is not writeable then escape out. - if (isLeft(currentValue)) { - commandDescriptions.push( - `Adjust Css Length Prop: ${EP.toUid(command.target)}/${PP.toString( - property.property, - )} not applied as value is not writeable.`, - ) - return workingElement - } - - // ...Determine some other facts about the current value. - const currentModifiableValue = currentValue.value - const simpleValueResult = jsxSimpleAttributeToValue(currentModifiableValue) - const valueProbablyExpression = isLeft(simpleValueResult) - const targetPropertyNonExistant: boolean = - currentModifiableValue.type === 'ATTRIBUTE_NOT_FOUND' - - // ...If the current value does not exist and we shouldn't create it if it doesn't exist - // then exit early from handling this property. - if ( - targetPropertyNonExistant && - property.createIfNonExistant === 'do-not-create-if-doesnt-exist' - ) { - commandDescriptions.push( - `Adjust Css Length Prop: ${EP.toUid(command.target)}/${PP.toString( - property.property, - )} not applied as the property does not exist.`, - ) - return workingElement - } - - // ...If the value is an expression then we can't update it. - if (valueProbablyExpression) { - // TODO add option to override expressions!!! - commandDescriptions.push( - `Adjust Css Length Prop: ${EP.toUid(command.target)}/${PP.toString( - property.property, - )} not applied as the property is an expression we did not want to override.`, - ) - return workingElement - } - - // Commonly used function for handling the updates. - function handleUpdateResult( - result: Either, - ): JSXElement { - return foldEither( - (error) => { - commandDescriptions.push(error) - return workingElement - }, - (updatedProps) => { - commandDescriptions.push(updatedProps.commandDescription) - return { - ...workingElement, - props: updatedProps.updatedProps, - } - }, - result, - ) - } - - // Parse the current value as a pixel value... - const parsePxResult = parseCSSPx(simpleValueResult.value) // TODO make type contain px - // ...If the value can be parsed as a pixel value then update it. - if (isRight(parsePxResult)) { - return handleUpdateResult( - updatePixelValueByPixel( - attributesWithConflictingPropsDeleted.value, - command.target, - property.property, - parsePxResult.value, - property.valuePx, - ), - ) - } - - // Parse the current value as a percentage value... - const parsePercentResult = parseCSSPercent(simpleValueResult.value) // TODO make type contain % - // ...If the value can be parsed as a percentage value then update it. - if (isRight(parsePercentResult)) { - return handleUpdateResult( - updatePercentageValueByPixel( - attributesWithConflictingPropsDeleted.value, - command.target, - property.property, - property.parentDimensionPx, - parsePercentResult.value, - property.valuePx, - ), - ) - } - - // Otherwise if it is permitted to create it if it doesn't exist, then do so. - if (property.createIfNonExistant === 'create-if-not-existing') { - return handleUpdateResult( - setPixelValue( - attributesWithConflictingPropsDeleted.value, - command.target, - property.property, - property.valuePx, - ), - ) - } - - // Updating the props fallback. - commandDescriptions.push( - `Adjust Css Length Prop: ${EP.toUid(command.target)}/${PP.toString( - property.property, - )} not applied as the property is in a CSS unit we do not support. (${ - simpleValueResult.value - })`, - ) - return workingElement - }, element) - } - - // Final fallback. - return element - }, + command.target, + command.properties.map((p) => p.property), + command.parentFlexDirection, ) - if (commandDescriptions.length === 0) { - // Cater for no updates at all happened. + const styleInfoReader = getActivePlugin(withConflictingPropertiesRemoved).styleInfoFactory({ + projectContents: withConflictingPropertiesRemoved.projectContents, + jsxMetadata: withConflictingPropertiesRemoved.jsxMetadata, + }) + + const styleInfo = styleInfoReader(command.target) + if (styleInfo == null) { return { editorStatePatches: [], - commandDescription: `No JSXElement was found at path ${EP.toString(command.target)}.`, + commandDescription: `Adjust CSS Length Properties: Element at ${EP.toString( + command.target, + )} not found`, } - } else { - if (updatedEditorState === editorState) { - // As the `EditorState` never changed, return an empty patch. - return { - editorStatePatches: [], - commandDescription: commandDescriptions.join('\n'), - } - } else { - // Build the patch for the changes. - const editorStatePatch = patchParseSuccessAtElementPath( + } + + let commandDescriptions: Array = [] + + const propsToUpdate: StyleUpdate[] = mapDropNulls((propertyUpdate) => { + const property = propertyUpdate.property.propertyElements[1] // the safety of propertyElements[1] is guaranteed by the LengthPropertyPath type + + const currentValue = getCSSNumberFromStyleInfo(styleInfo, property) + if (currentValue.type === 'not-css-number') { + commandDescriptions.push( + `Adjust Css Length Prop: ${EP.toUid(command.target)}/${PP.toString( + propertyUpdate.property, + )} not applied as value is not writeable.`, + ) + return null + } + + if ( + currentValue.type === 'not-found' && + propertyUpdate.createIfNonExistant === 'do-not-create-if-doesnt-exist' + ) { + commandDescriptions.push( + `Adjust Css Length Prop: ${EP.toUid(command.target)}/${PP.toString( + propertyUpdate.property, + )} not applied as the property does not exist.`, + ) + return null + } + + if ( + currentValue.type === 'css-number' && + (currentValue.number.unit == null || currentValue.number.unit === 'px') + ) { + const { commandDescription, styleUpdate } = updatePixelValueByPixel( command.target, - updatedEditorState, - (success) => { - return { - topLevelElements: { - $set: success.topLevelElements, - }, - imports: { - $set: success.imports, - }, - } - }, + property, + currentValue.number, + propertyUpdate.valuePx, ) - return { - editorStatePatches: [editorStatePatch], - commandDescription: commandDescriptions.join('\n'), - } + commandDescriptions.push(commandDescription) + return styleUpdate + } + + if (currentValue.type === 'css-number' && currentValue.number.unit === '%') { + const { commandDescription, styleUpdate } = updatePercentageValueByPixel( + command.target, + property, + propertyUpdate.parentDimensionPx, + currentValue.number, + propertyUpdate.valuePx, + ) + commandDescriptions.push(commandDescription) + return styleUpdate ?? null } + + if ( + currentValue.type === 'not-found' && + propertyUpdate.createIfNonExistant === 'create-if-not-existing' + ) { + const { commandDescription, styleUpdate } = setPixelValue( + command.target, + property, + propertyUpdate.valuePx, + ) + commandDescriptions.push(commandDescription) + return styleUpdate + } + + commandDescriptions.push( + `Adjust Css Length Prop: ${EP.toUid(command.target)}/${PP.toString( + propertyUpdate.property, + )} not applied as the property is in a CSS unit we do not support.`, + ) + return null + }, command.properties) + + if (propsToUpdate.length === 0) { + return { editorStatePatches: [], commandDescription: 'No props to update' } + } + + const { editorStatePatches } = runStyleUpdateForStrategy( + interactionLifecycle, + withConflictingPropertiesRemoved, + command.target, + propsToUpdate, + ) + + return { + editorStatePatches: editorStatePatches, + commandDescription: commandDescriptions.join('\n'), } } +type TargetProperty = keyof StyleInfo + function setPixelValue( - properties: JSXAttributes, targetElement: ElementPath, - targetProperty: PropertyPath, + targetProperty: TargetProperty, value: number, -): Either { +): { commandDescription: string; styleUpdate: StyleUpdate } { const newValueCssNumber: CSSNumber = { value: value, unit: null, } const newValue = printCSSNumber(newValueCssNumber, null) - const propsToUpdate: Array = [ - { - path: targetProperty, - value: jsExpressionValue(newValue, emptyComments), - }, - ] - - const updatePropsResult = setJSXValuesAtPaths(properties, propsToUpdate) - - return mapEither((updatedProps) => { - return { - updatedProps: updatedProps, - commandDescription: `Set css Length Prop: ${EP.toUid(targetElement)}/${PP.toString( - targetProperty, - )} to ${value}.`, - } - }, updatePropsResult) + return { + styleUpdate: { type: 'set', property: targetProperty, value: newValue }, + commandDescription: `Set css Length Prop: ${EP.toUid( + targetElement, + )}/${targetProperty} to ${value}.`, + } } function updatePixelValueByPixel( - properties: JSXAttributes, targetElement: ElementPath, - targetProperty: PropertyPath, + targetProperty: TargetProperty, currentValue: CSSNumber, byValue: number, -): Either { +): { commandDescription: string; styleUpdate: StyleUpdate } { if (currentValue.unit != null && currentValue.unit !== 'px') { throw new Error('updatePixelValueByPixel called with a non-pixel cssnumber') } @@ -311,48 +219,37 @@ function updatePixelValueByPixel( } const newValue = printCSSNumber(newValueCssNumber, null) - const propsToUpdate: Array = [ - { - path: targetProperty, - value: jsExpressionValue(newValue, emptyComments), - }, - ] - const updatePropsResult = setJSXValuesAtPaths(properties, propsToUpdate) - - return mapEither((updatedProps) => { - return { - updatedProps: updatedProps, - commandDescription: `Adjust Css Length Prop: ${EP.toUid(targetElement)}/${PP.toString( - targetProperty, - )} by ${byValue}`, - } - }, updatePropsResult) + return { + styleUpdate: { type: 'set', property: targetProperty, value: newValue }, + commandDescription: `Adjust Css Length Prop: ${EP.toUid( + targetElement, + )}/${targetProperty} by ${byValue}`, + } } function updatePercentageValueByPixel( - properties: JSXAttributes, targetElement: ElementPath, - targetProperty: PropertyPath, + targetProperty: TargetProperty, parentDimensionPx: number | undefined, currentValue: CSSNumber, // TODO restrict to percentage numbers byValue: number, -): Either { +): { commandDescription: string; styleUpdate?: StyleUpdate } { if (currentValue.unit == null || currentValue.unit !== '%') { throw new Error('updatePercentageValueByPixel called with a non-percentage cssnumber') } if (parentDimensionPx == null) { - return left( - `Adjust Css Length Prop: ${EP.toUid(targetElement)}/${PP.toString( - targetProperty, - )} not applied because the parent dimensions are unknown for some reason.`, - ) + return { + commandDescription: `Adjust Css Length Prop: ${EP.toUid( + targetElement, + )}/${targetProperty} not applied because the parent dimensions are unknown for some reason.`, + } } if (parentDimensionPx === 0) { - return left( - `Adjust Css Length Prop: ${EP.toUid(targetElement)}/${PP.toString( - targetProperty, - )} not applied because the parent dimension is 0.`, - ) + return { + commandDescription: `Adjust Css Length Prop: ${EP.toUid( + targetElement, + )}/${targetProperty} not applied because the parent dimension is 0.`, + } } const currentValuePercent = currentValue.value const offsetInPercent = (byValue / parentDimensionPx) * 100 @@ -362,23 +259,12 @@ function updatePercentageValueByPixel( } const newValue = printCSSNumber(newValueCssNumber, null) - const propsToUpdate: Array = [ - { - path: targetProperty, - value: jsExpressionValue(newValue, emptyComments), - }, - ] - - const updatePropsResult = setJSXValuesAtPaths(properties, propsToUpdate) - - return mapEither((updatedProps) => { - return { - updatedProps: updatedProps, - commandDescription: `Adjust Css Length Prop: ${EP.toUid(targetElement)}/${PP.toString( - targetProperty, - )} by ${byValue}`, - } - }, updatePropsResult) + return { + styleUpdate: { type: 'set', property: targetProperty, value: newValue }, + commandDescription: `Adjust Css Length Prop: ${EP.toUid( + targetElement, + )}/${targetProperty} by ${byValue}`, + } } const FlexSizeProperties: Array = [ @@ -391,7 +277,7 @@ const FlexSizeProperties: Array = [ function getConflictingPropertiesToDelete( parentFlexDirection: FlexDirection | null, propertyPath: PropertyPath, -): Array { +): Array { let propertiesToDelete: Array = [] const parentFlexDimension = @@ -413,41 +299,27 @@ function getConflictingPropertiesToDelete( } break } - return propertiesToDelete -} - -export function deleteConflictingPropsForWidthHeightFromAttributes( - attributes: JSXAttributes, - propertyPath: PropertyPath, - parentFlexDirection: FlexDirection | null, -): Either { - const propertiesToDelete: Array = getConflictingPropertiesToDelete( - parentFlexDirection, - propertyPath, - ) - return unsetJSXValuesAtPaths(attributes, propertiesToDelete) + return mapDropNulls(maybeCssPropertyFromInlineStyle, propertiesToDelete) } export function deleteConflictingPropsForWidthHeight( + interactionLifecycle: InteractionLifecycle, editorState: EditorState, - target: ElementPath, - propertyPath: PropertyPath, + elementPath: ElementPath, + propertyPaths: PropertyPath[], parentFlexDirection: FlexDirection | null, ): EditorState { - const propertiesToDelete: Array = getConflictingPropertiesToDelete( - parentFlexDirection, - propertyPath, - ) - - if (propertiesToDelete.length === 0) { - return editorState - } else { - const { editorStateWithChanges: editorStateWithPropsDeleted } = deleteValuesAtPath( - editorState, - target, - propertiesToDelete, - ) + return propertyPaths.reduce((editor, propertyPath) => { + const propertiesToDelete = getConflictingPropertiesToDelete(parentFlexDirection, propertyPath) + if (propertiesToDelete.length === 0) { + return editor + } - return editorStateWithPropsDeleted - } + return runStyleUpdateForStrategy( + interactionLifecycle, + editor, + elementPath, + propertiesToDelete.map((p) => ({ type: 'delete', property: p })), + ).editorStateWithChanges + }, editorState) } diff --git a/editor/src/components/canvas/commands/adjust-number-command.spec.tsx b/editor/src/components/canvas/commands/adjust-number-command.spec.tsx deleted file mode 100644 index 41ac2e58d9f5..000000000000 --- a/editor/src/components/canvas/commands/adjust-number-command.spec.tsx +++ /dev/null @@ -1,143 +0,0 @@ -import update from 'immutability-helper' -import { styleStringInArray } from '../../../utils/common-constants' -import { createBuiltInDependenciesList } from '../../../core/es-modules/package-manager/built-in-dependencies-list' -import * as EP from '../../../core/shared/element-path' -import { getNumberPropertyFromProps } from '../../../core/shared/jsx-attributes' -import { complexDefaultProjectPreParsed } from '../../../sample-projects/sample-project-utils.test-utils' -import { withUnderlyingTargetFromEditorState } from '../../editor/store/editor-state' -import { stylePropPathMappingFn } from '../../inspector/common/property-path-hooks' -import { - DefaultStartingFeatureSwitches, - makeTestProjectCodeWithSnippet, - renderTestEditorWithCode, - renderTestEditorWithModel, -} from '../ui-jsx.test-utils' -import { adjustNumberProperty, runAdjustNumberProperty } from './adjust-number-command' -import { updateEditorStateWithPatches } from './commands' -import { isJSXElement } from '../../../core/shared/element-template' - -describe('adjustNumberProperty', () => { - it('works for left style prop', async () => { - const renderResult = await renderTestEditorWithModel( - complexDefaultProjectPreParsed(), - 'dont-await-first-dom-report', - DefaultStartingFeatureSwitches, - createBuiltInDependenciesList(null), - ) - - const cardInstancePath = EP.elementPath([ - ['storyboard-entity', 'scene-1-entity', 'app-entity'], - ['app-outer-div', 'card-instance'], - ]) - - const originalEditorState = renderResult.getEditorState().editor - - const originalLeftStyleProp = withUnderlyingTargetFromEditorState( - cardInstancePath, - originalEditorState, - null, - (success, element, underlyingTarget, underlyingFilePath) => { - if (isJSXElement(element)) { - return getNumberPropertyFromProps( - element.props, - stylePropPathMappingFn('left', styleStringInArray), - ) - } else { - return null - } - }, - )! - - const delta = 10 - - const adjustNumberPropertyCommand = adjustNumberProperty( - 'always', - cardInstancePath, - stylePropPathMappingFn('left', styleStringInArray), - delta, - true, - ) - - const result = runAdjustNumberProperty( - renderResult.getEditorState().editor, - adjustNumberPropertyCommand, - ) - - const patchedEditor = updateEditorStateWithPatches( - renderResult.getEditorState().editor, - result.editorStatePatches, - ) - - const updatedLeftStyleProp = withUnderlyingTargetFromEditorState( - cardInstancePath, - patchedEditor, - null, - (success, element, underlyingTarget, underlyingFilePath) => { - if (isJSXElement(element)) { - return getNumberPropertyFromProps( - element.props, - stylePropPathMappingFn('left', styleStringInArray), - ) - } else { - return null - } - }, - ) - - expect(updatedLeftStyleProp).toEqual(originalLeftStyleProp + delta) - }) - it('works for missing left style prop', async () => { - const renderResult = await renderTestEditorWithCode( - makeTestProjectCodeWithSnippet(` - - `), - 'dont-await-first-dom-report', - ) - - const elementPath = EP.elementPath([ - ['scene-aaa', 'app-entity'], - ['parent', 'child'], - ]) - - const delta = 10 - - // left prop is missing, it should be just set to the delta value - const adjustNumberPropertyCommand = adjustNumberProperty( - 'always', - elementPath, - stylePropPathMappingFn('left', styleStringInArray), - delta, - true, - ) - - const result = runAdjustNumberProperty( - renderResult.getEditorState().editor, - adjustNumberPropertyCommand, - ) - - const patchedEditor = updateEditorStateWithPatches( - renderResult.getEditorState().editor, - result.editorStatePatches, - ) - const updatedLeftStyleProp = withUnderlyingTargetFromEditorState( - elementPath, - patchedEditor, - null, - (success, element, underlyingTarget, underlyingFilePath) => { - if (isJSXElement(element)) { - return getNumberPropertyFromProps( - element.props, - stylePropPathMappingFn('left', styleStringInArray), - ) - } else { - return null - } - }, - ) - - expect(updatedLeftStyleProp).toEqual(delta) - }) -}) diff --git a/editor/src/components/canvas/commands/adjust-number-command.ts b/editor/src/components/canvas/commands/adjust-number-command.ts deleted file mode 100644 index 6750b8a0b328..000000000000 --- a/editor/src/components/canvas/commands/adjust-number-command.ts +++ /dev/null @@ -1,211 +0,0 @@ -import { foldEither } from '../../../core/shared/either' -import * as EP from '../../../core/shared/element-path' -import type { JSXElement } from '../../../core/shared/element-template' -import { - emptyComments, - isJSXElement, - jsExpressionValue, -} from '../../../core/shared/element-template' -import type { ValueAtPath } from '../../../core/shared/jsx-attributes' -import { - getNumberPropertyFromProps, - setJSXValuesAtPaths, -} from '../../../core/shared/jsx-attributes' -import type { ElementPath, PropertyPath } from '../../../core/shared/project-file-types' -import * as PP from '../../../core/shared/property-path' -import type { EditorState, EditorStatePatch } from '../../editor/store/editor-state' -import { - modifyUnderlyingElementForOpenFile, - withUnderlyingTargetFromEditorState, -} from '../../editor/store/editor-state' -import type { BaseCommand, CommandFunction, WhenToRun } from './commands' -import { patchParseSuccessAtElementPath } from './patch-utils' - -export interface AdjustNumberProperty extends BaseCommand { - type: 'ADJUST_NUMBER_PROPERTY' - target: ElementPath - property: PropertyPath - value: number | AdjustNumberInequalityCondition - createIfNonExistant: boolean -} - -export function adjustNumberProperty( - whenToRun: WhenToRun, - target: ElementPath, - property: PropertyPath, - value: number | AdjustNumberInequalityCondition, - createIfNonExistant: boolean, -): AdjustNumberProperty { - return { - type: 'ADJUST_NUMBER_PROPERTY', - whenToRun: whenToRun, - target: target, - property: property, - value: value, - createIfNonExistant: createIfNonExistant, - } -} - -export type AdjustNumberCondition = 'less-than' | 'greater-than' - -export interface AdjustNumberInequalityCondition { - property: PropertyPath - condition: AdjustNumberCondition -} - -export function adjustNumberInequalityCondition( - property: PropertyPath, - condition: AdjustNumberCondition, -): AdjustNumberInequalityCondition { - return { - property: property, - condition: condition, - } -} - -export const runAdjustNumberProperty: CommandFunction = ( - editorState: EditorState, - command: AdjustNumberProperty, -) => { - // Handle updating the existing value, treating a value that can't be parsed - // as zero. - let newValue: number = 0 - - // Identify the current value, whatever that may be. - let targetPropertyNonExistant: boolean = false - let inequalityValue: number | null = null - const currentValue = withUnderlyingTargetFromEditorState( - command.target, - editorState, - null, - (success, element, underlyingTarget, underlyingFilePath) => { - if (isJSXElement(element)) { - // Check for the inequality adjustment target while we're here. - if (typeof command.value !== 'number') { - inequalityValue = getNumberPropertyFromProps(element.props, command.value.property) - } - - // Try the property we're updating. - const fromProperty = getNumberPropertyFromProps(element.props, command.property) - if (fromProperty == null) { - targetPropertyNonExistant = true - } else { - return fromProperty - } - } - return null - }, - ) - - if (targetPropertyNonExistant && !command.createIfNonExistant) { - return { - editorStatePatches: [], - commandDescription: `Adjust Number Prop: ${EP.toUid(command.target)}/${PP.toString( - command.property, - )} not applied as the property does not exist.`, - } - } else { - if (typeof command.value === 'number') { - if (currentValue != null) { - newValue += currentValue - } - - // Change the value. - newValue += command.value - } else { - if (currentValue != null && inequalityValue != null) { - switch (command.value.condition) { - case 'less-than': - if (inequalityValue <= currentValue) { - return { - editorStatePatches: [], - commandDescription: `Adjust Number Prop: ${EP.toUid(command.target)}/${PP.toString( - command.property, - )} not applied as value is large enough already.`, - } - } else { - newValue = inequalityValue - } - break - case 'greater-than': - if (inequalityValue >= currentValue) { - return { - editorStatePatches: [], - commandDescription: `Adjust Number Prop: ${EP.toUid(command.target)}/${PP.toString( - command.property, - )} not applied as value is small enough already.`, - } - } else { - newValue = inequalityValue - } - break - default: - const _exhaustiveCheck: never = command.value.condition - throw new Error(`Unhandled command condition of ${JSON.stringify(command.value)}`) - } - } - } - - const propsToUpdate: Array = [ - { - path: command.property, - value: jsExpressionValue(newValue, emptyComments), - }, - ] - - // Apply the update to the properties. - const { editorStatePatch: propertyUpdatePatch } = applyValuesAtPath( - editorState, - command.target, - propsToUpdate, - ) - - return { - editorStatePatches: [propertyUpdatePatch], - commandDescription: `Adjust Number Prop: ${EP.toUid(command.target)}/${PP.toString( - command.property, - )} by ${command.value}`, - } - } -} - -export function applyValuesAtPath( - editorState: EditorState, - target: ElementPath, - jsxValuesAndPathsToSet: ValueAtPath[], -): { editorStateWithChanges: EditorState; editorStatePatch: EditorStatePatch } { - const workingEditorState = modifyUnderlyingElementForOpenFile( - target, - editorState, - (element: JSXElement) => { - return foldEither( - () => { - return element - }, - (updatedProps) => { - return { - ...element, - props: updatedProps, - } - }, - setJSXValuesAtPaths(element.props, jsxValuesAndPathsToSet), - ) - }, - ) - - const editorStatePatch = patchParseSuccessAtElementPath(target, workingEditorState, (success) => { - return { - topLevelElements: { - $set: success.topLevelElements, - }, - imports: { - $set: success.imports, - }, - } - }) - - return { - editorStateWithChanges: workingEditorState, - editorStatePatch: editorStatePatch, - } -} diff --git a/editor/src/components/canvas/commands/commands.ts b/editor/src/components/canvas/commands/commands.ts index 2e5241d5c0b3..7ce5e9b1e6ae 100644 --- a/editor/src/components/canvas/commands/commands.ts +++ b/editor/src/components/canvas/commands/commands.ts @@ -6,8 +6,6 @@ import type { EditorState, EditorStatePatch } from '../../editor/store/editor-st import type { CommandDescription } from '../canvas-strategies/interaction-state' import type { AdjustCssLengthProperties } from './adjust-css-length-command' import { runAdjustCssLengthProperties } from './adjust-css-length-command' -import type { AdjustNumberProperty } from './adjust-number-command' -import { runAdjustNumberProperty } from './adjust-number-command' import type { ConvertToAbsolute } from './convert-to-absolute-command' import { runConvertToAbsolute } from './convert-to-absolute-command' import type { ReorderElement } from './reorder-element-command' @@ -31,14 +29,6 @@ import type { ShowOutlineHighlight } from './show-outline-highlight-command' import { runShowOutlineHighlight } from './show-outline-highlight-command' import type { SetCursorCommand } from './set-cursor-command' import { runSetCursor } from './set-cursor-command' -import type { - AppendElementsToRerenderCommand, - SetElementsToRerenderCommand, -} from './set-elements-to-rerender-command' -import { - runAppendElementsToRerender, - runSetElementsToRerender, -} from './set-elements-to-rerender-command' import type { DuplicateElement } from './duplicate-element-command' import { runDuplicateElement } from './duplicate-element-command' import type { UpdateFunctionCommand } from './update-function-command' @@ -55,8 +45,6 @@ import type { InsertElementInsertionSubject } from './insert-element-insertion-s import { runInsertElementInsertionSubject } from './insert-element-insertion-subject' import type { AddElement } from './add-element-command' import { runAddElement } from './add-element-command' -import type { UpdatePropIfExists } from './update-prop-if-exists-command' -import { runUpdatePropIfExists } from './update-prop-if-exists-command' import type { HighlightElementsCommand } from './highlight-element-command' import { runHighlightElementsCommand } from './highlight-element-command' import type { InteractionLifecycle } from '../canvas-strategies/canvas-strategy-types' @@ -108,7 +96,6 @@ export type CanvasCommand = | WildcardPatch | UpdateFunctionCommand | StrategySwitched - | AdjustNumberProperty | AdjustCssLengthProperties | ReparentElement | DuplicateElement @@ -121,13 +108,10 @@ export type CanvasCommand = | ShowOutlineHighlight | ShowReorderIndicator | SetCursorCommand - | SetElementsToRerenderCommand - | AppendElementsToRerenderCommand | PushIntendedBoundsAndUpdateGroups | PushIntendedBoundsAndUpdateHuggingElements | DeleteProperties | SetProperty - | UpdatePropIfExists | AddImportsToFile | AddToReparentedToPaths | InsertElementInsertionSubject @@ -158,10 +142,8 @@ export function runCanvasCommand( return runUpdateFunctionCommand(editorState, command, commandLifecycle) case 'STRATEGY_SWITCHED': return runStrategySwitchedCommand(command) - case 'ADJUST_NUMBER_PROPERTY': - return runAdjustNumberProperty(editorState, command) case 'ADJUST_CSS_LENGTH_PROPERTY': - return runAdjustCssLengthProperties(editorState, command) + return runAdjustCssLengthProperties(editorState, command, commandLifecycle) case 'REPARENT_ELEMENT': return runReparentElement(editorState, command) case 'DUPLICATE_ELEMENT': @@ -175,7 +157,7 @@ export function runCanvasCommand( case 'CONVERT_TO_ABSOLUTE': return runConvertToAbsolute(editorState, command) case 'SET_CSS_LENGTH_PROPERTY': - return runSetCssLengthProperty(editorState, command) + return runSetCssLengthProperty(editorState, command, commandLifecycle) case 'REORDER_ELEMENT': return runReorderElement(editorState, command) case 'SHOW_OUTLINE_HIGHLIGHT': @@ -184,23 +166,16 @@ export function runCanvasCommand( return runShowReorderIndicator(editorState, command) case 'SET_CURSOR_COMMAND': return runSetCursor(editorState, command) - case 'SET_ELEMENTS_TO_RERENDER_COMMAND': - return runSetElementsToRerender(editorState, command) - case 'APPEND_ELEMENTS_TO_RERENDER_COMMAND': - return runAppendElementsToRerender(editorState, command) case 'PUSH_INTENDED_BOUNDS_AND_UPDATE_GROUPS': return runPushIntendedBoundsAndUpdateGroups(editorState, command, commandLifecycle) case 'PUSH_INTENDED_BOUNDS_AND_UPDATE_HUGGING_ELEMENTS': return runPushIntendedBoundsAndUpdateHuggingElements(editorState, command) - case 'DELETE_PROPERTIES': - return runDeleteProperties(editorState, command) + return runDeleteProperties(editorState, command, commandLifecycle) case 'SET_PROPERTY': - return runSetProperty(editorState, command) + return runSetProperty(editorState, command, commandLifecycle) case 'UPDATE_BULK_PROPERTIES': return runBulkUpdateProperties(editorState, command) - case 'UPDATE_PROP_IF_EXISTS': - return runUpdatePropIfExists(editorState, command) case 'ADD_IMPORTS_TO_FILE': return runAddImportsToFile(editorState, command) case 'ADD_TO_REPARENTED_TO_PATHS': diff --git a/editor/src/components/canvas/commands/convert-css-percent-to-px-command.ts b/editor/src/components/canvas/commands/convert-css-percent-to-px-command.ts index 990dbc2ac63c..8b77cf92a033 100644 --- a/editor/src/components/canvas/commands/convert-css-percent-to-px-command.ts +++ b/editor/src/components/canvas/commands/convert-css-percent-to-px-command.ts @@ -16,8 +16,8 @@ import type { EditorState } from '../../editor/store/editor-state' import { withUnderlyingTargetFromEditorState } from '../../editor/store/editor-state' import type { CSSNumber } from '../../inspector/common/css-utils' import { parseCSSPercent, printCSSNumber } from '../../inspector/common/css-utils' -import { applyValuesAtPath } from './adjust-number-command' import type { BaseCommand, CommandFunction, WhenToRun } from './commands' +import { applyValuesAtPath } from './utils/property-utils' export interface ConvertCssPercentToPx extends BaseCommand { type: 'CONVERT_CSS_PERCENT_TO_PX' diff --git a/editor/src/components/canvas/commands/convert-to-absolute-command.ts b/editor/src/components/canvas/commands/convert-to-absolute-command.ts index 7ecb5b1726ab..2effb0eef891 100644 --- a/editor/src/components/canvas/commands/convert-to-absolute-command.ts +++ b/editor/src/components/canvas/commands/convert-to-absolute-command.ts @@ -7,8 +7,8 @@ import type { ValueAtPath } from '../../../core/shared/jsx-attributes' import type { ElementPath } from '../../../core/shared/project-file-types' import type { EditorState, EditorStatePatch } from '../../editor/store/editor-state' import { stylePropPathMappingFn } from '../../inspector/common/property-path-hooks' -import { applyValuesAtPath } from './adjust-number-command' import type { BaseCommand, CommandFunction, WhenToRun } from './commands' +import { applyValuesAtPath } from './utils/property-utils' export interface ConvertToAbsolute extends BaseCommand { type: 'CONVERT_TO_ABSOLUTE' diff --git a/editor/src/components/canvas/commands/delete-properties-command.ts b/editor/src/components/canvas/commands/delete-properties-command.ts index f1d8501385b3..c7cf7652d341 100644 --- a/editor/src/components/canvas/commands/delete-properties-command.ts +++ b/editor/src/components/canvas/commands/delete-properties-command.ts @@ -1,20 +1,18 @@ -import type { JSXElement } from '../../../core/shared/element-template' -import type { EditorState, EditorStatePatch } from '../../../components/editor/store/editor-state' -import { modifyUnderlyingElementForOpenFile } from '../../../components/editor/store/editor-state' -import { foldEither } from '../../../core/shared/either' -import { unsetJSXValuesAtPaths } from '../../../core/shared/jsx-attributes' +import type { EditorState } from '../../../components/editor/store/editor-state' import type { ElementPath, PropertyPath } from '../../../core/shared/project-file-types' -import type { BaseCommand, CommandFunction, WhenToRun } from './commands' +import type { BaseCommand, WhenToRun } from './commands' import * as EP from '../../../core/shared/element-path' import * as PP from '../../../core/shared/property-path' -import { patchParseSuccessAtElementPath } from './patch-utils' +import { deleteValuesAtPath, maybeCssPropertyFromInlineStyle } from './utils/property-utils' +import type { InteractionLifecycle } from '../canvas-strategies/canvas-strategy-types' +import { runStyleUpdateForStrategy } from '../plugins/style-plugins' +import * as SU from '../plugins/style-plugins' export interface DeleteProperties extends BaseCommand { type: 'DELETE_PROPERTIES' element: ElementPath properties: Array } - export function deleteProperties( whenToRun: WhenToRun, element: ElementPath, @@ -28,61 +26,42 @@ export function deleteProperties( } } -export const runDeleteProperties: CommandFunction = ( +export const runDeleteProperties = ( editorState: EditorState, command: DeleteProperties, + interactionLifecycle: InteractionLifecycle, ) => { + const propsToDelete = command.properties.reduce( + (acc: { styleProps: string[]; rest: PropertyPath[] }, p) => { + const prop = maybeCssPropertyFromInlineStyle(p) + if (prop == null) { + acc.rest.push(p) + } else { + acc.styleProps.push(prop) + } + return acc + }, + { styleProps: [], rest: [] }, + ) + // Apply the update to the properties. - const { editorStatePatch: propertyUpdatePatch } = deleteValuesAtPath( + const { editorStateWithChanges } = deleteValuesAtPath( editorState, command.element, - command.properties, + propsToDelete.rest, + ) + + const { editorStatePatches } = runStyleUpdateForStrategy( + interactionLifecycle, + editorStateWithChanges, + command.element, + propsToDelete.styleProps.map(SU.deleteCSSProp), ) return { - editorStatePatches: [propertyUpdatePatch], + editorStatePatches: editorStatePatches, commandDescription: `Delete Properties ${command.properties .map(PP.toString) .join(',')} on ${EP.toUid(command.element)}`, } } - -export function deleteValuesAtPath( - editorState: EditorState, - target: ElementPath, - properties: Array, -): { editorStateWithChanges: EditorState; editorStatePatch: EditorStatePatch } { - const workingEditorState = modifyUnderlyingElementForOpenFile( - target, - editorState, - (element: JSXElement) => { - return foldEither( - () => { - return element - }, - (updatedProps) => { - return { - ...element, - props: updatedProps, - } - }, - unsetJSXValuesAtPaths(element.props, properties), - ) - }, - ) - - const editorStatePatch = patchParseSuccessAtElementPath(target, workingEditorState, (success) => { - return { - topLevelElements: { - $set: success.topLevelElements, - }, - imports: { - $set: success.imports, - }, - } - }) - return { - editorStateWithChanges: workingEditorState, - editorStatePatch: editorStatePatch, - } -} diff --git a/editor/src/components/canvas/commands/push-intended-bounds-and-update-hugging-elements-command.ts b/editor/src/components/canvas/commands/push-intended-bounds-and-update-hugging-elements-command.ts index 7b82f99e77c4..d4cbf9f313af 100644 --- a/editor/src/components/canvas/commands/push-intended-bounds-and-update-hugging-elements-command.ts +++ b/editor/src/components/canvas/commands/push-intended-bounds-and-update-hugging-elements-command.ts @@ -1,3 +1,5 @@ +import type { CanvasRectangle } from '../../../core/shared/math-utils' +import { isFiniteRectangle, sizesEqual } from '../../../core/shared/math-utils' import { MetadataUtils } from '../../../core/model/element-metadata-utils' import * as EP from '../../../core/shared/element-path' import type { ElementInstanceMetadataMap } from '../../../core/shared/element-template' @@ -14,16 +16,23 @@ import type { CanvasFrameAndTarget } from '../canvas-types' import type { BaseCommand, CanvasCommand, CommandFunctionResult } from './commands' import { foldAndApplyCommandsSimple } from './commands' import { setCssLengthProperty, setExplicitCssValue } from './set-css-length-command' -import { setProperty } from './set-property-command' import { showToastCommand } from './show-toast-command' +import { isZeroSizedElement } from '../controls/outline-utils' +import type { HuggingElementContentsStatus } from '../hugging-utils' +import { getHuggingElementContentsStatus } from '../hugging-utils' + +export interface IntendedBoundsAndChildrenState extends CanvasFrameAndTarget { + elementFrame: CanvasRectangle + huggingElementContentsStatus: HuggingElementContentsStatus +} export interface PushIntendedBoundsAndUpdateHuggingElements extends BaseCommand { type: 'PUSH_INTENDED_BOUNDS_AND_UPDATE_HUGGING_ELEMENTS' - value: Array + value: Array } export function pushIntendedBoundsAndUpdateHuggingElements( - value: Array, + value: Array, ): PushIntendedBoundsAndUpdateHuggingElements { return { type: 'PUSH_INTENDED_BOUNDS_AND_UPDATE_HUGGING_ELEMENTS', @@ -60,23 +69,6 @@ export const runPushIntendedBoundsAndUpdateHuggingElements = ( } } -function getHuggingElementContentsStatus( - jsxMetadata: ElementInstanceMetadataMap, - path: ElementPath, -): 'empty' | 'contains-only-absolute' | 'contains-some-absolute' | 'non-empty' { - const children = MetadataUtils.getChildrenUnordered(jsxMetadata, path) - const absoluteChildren = children.filter(MetadataUtils.isPositionAbsolute).length - if (children.length === 0) { - return 'empty' - } else if (absoluteChildren === children.length) { - return 'contains-only-absolute' - } else if (absoluteChildren > 0) { - return 'contains-some-absolute' - } else { - return 'non-empty' - } -} - function applyUpdateResizeHuggingElementsCommands( editor: EditorState, command: PushIntendedBoundsAndUpdateHuggingElements, @@ -89,17 +81,30 @@ function applyUpdateResizeHuggingElementsCommands( editor.jsxMetadata, frameAndTarget.target, ) + + // If the element isn't hugging its parent in either direction, skip this case. if ( metadata == null || !(isHuggingParent(metadata, 'width') || isHuggingParent(metadata, 'height')) ) { continue } + + // If the element still has non-absolute children, skip this case. const status = getHuggingElementContentsStatus(editor.jsxMetadata, frameAndTarget.target) if (status === 'non-empty') { continue } + // If the element is now empty, but used to contain only absolute children, and is zero-sized, skip this case. + if ( + status === 'empty' && + frameAndTarget.huggingElementContentsStatus === 'contains-only-absolute' && + isZeroSizedElement(frameAndTarget.elementFrame) + ) { + continue + } + function setCSSDimension( flexDirection: FlexDirection | null, prop: 'left' | 'top' | 'width' | 'height', @@ -128,39 +133,8 @@ function applyUpdateResizeHuggingElementsCommands( 'height', frameAndTarget.frame.height, ), + showToastCommand('Added fixed width and height', 'NOTICE', 'added-width-height'), ) - - const parentPath = EP.parentPath(frameAndTarget.target) - const parentElement = MetadataUtils.findElementByElementPath(editor.jsxMetadata, parentPath) - const parentIsHugging = - parentElement != null && - (isHuggingParent(parentElement, 'width') || isHuggingParent(parentElement, 'height')) - const shouldSetAbsolutePosition = - EP.isStoryboardPath(parentPath) || parentElement == null || parentIsHugging - if (shouldSetAbsolutePosition) { - commands.push( - setProperty('always', frameAndTarget.target, PP.create('style', 'position'), 'absolute'), - setCSSDimension( - metadata.specialSizeMeasurements.flexDirection, - 'left', - frameAndTarget.frame.x, - ), - setCSSDimension( - metadata.specialSizeMeasurements.flexDirection, - 'top', - frameAndTarget.frame.y, - ), - showToastCommand( - 'Converted to fixed size and absolute position', - 'NOTICE', - 'convert-to-fixed-size', - ), - ) - } else { - commands.push( - showToastCommand('Added fixed width and height', 'NOTICE', 'added-width-height'), - ) - } } } return { diff --git a/editor/src/components/canvas/commands/push-intended-bounds-and-update-targets-command.spec.browser2.tsx b/editor/src/components/canvas/commands/push-intended-bounds-and-update-targets-command.spec.browser2.tsx index 5a42a32d7d88..15a9f36a92f1 100644 --- a/editor/src/components/canvas/commands/push-intended-bounds-and-update-targets-command.spec.browser2.tsx +++ b/editor/src/components/canvas/commands/push-intended-bounds-and-update-targets-command.spec.browser2.tsx @@ -19,7 +19,7 @@ function makeScenePath(trailingPath: string): ElementPath { describe('push intended bounds', () => { describe('hugging elements', () => { describe('flex containers', () => { - xit('keeps the container dimensions when becoming empty (flex) (delete, single element)', async () => { + it('keeps the container dimensions when becoming empty (flex) (delete, single element)', async () => { const renderResult = await renderTestEditorWithCode( formatTestProjectCode( makeTestProjectCodeWithSnippet(` @@ -74,7 +74,7 @@ describe('push intended bounds', () => { ), ) }) - xit('keeps the container dimensions when becoming empty (flex) (delete, multiple elements)', async () => { + it('keeps the container dimensions when becoming empty (flex) (delete, multiple elements)', async () => { const renderResult = await renderTestEditorWithCode( formatTestProjectCode( makeTestProjectCodeWithSnippet(` @@ -177,7 +177,7 @@ describe('push intended bounds', () => { ), ) }) - xit('keeps the container dimensions when becoming empty (zero-sized)', async () => { + it('keeps the container dimensions when becoming empty (zero-sized)', async () => { const renderResult = await renderTestEditorWithCode( formatTestProjectCode(` import * as React from 'react' @@ -205,13 +205,13 @@ describe('push intended bounds', () => { export var storyboard = ( -
+
) `), ) }) - xit('keeps the container dimensions when becoming empty (inside a flex container)', async () => { + it('keeps the container dimensions when becoming empty (inside a flex container)', async () => { const renderResult = await renderTestEditorWithCode( formatTestProjectCode(` import * as React from 'react' @@ -251,6 +251,155 @@ describe('push intended bounds', () => { `), ) }) + it('keeps the container dimensions when becoming empty (inside a grid container)', async () => { + const renderResult = await renderTestEditorWithCode( + formatTestProjectCode(` + import * as React from 'react' + import { Storyboard, Group } from 'utopia-api' + + export var storyboard = ( + +
+
+
+ delete me pls +
+
+
+
+ ) + `), + 'await-first-dom-report', + ) + + await selectComponentsForTest(renderResult, [EP.fromString(`sb/grid/container/delete-me`)]) + + await renderResult.dispatch([deleteSelected()], true) + + expect(getPrintedUiJsCode(renderResult.getEditorState())).toEqual( + formatTestProjectCode(` + import * as React from 'react' + import { Storyboard, Group } from 'utopia-api' + + export var storyboard = ( + +
+
+
+ + ) + `), + ) + }) + }) + it('keeps the container dimensions with a fractionally defined grid becomes empty', async () => { + const renderResult = await renderTestEditorWithCode( + formatTestProjectCode(` + import * as React from 'react' + import { Storyboard, Group } from 'utopia-api' + + export var storyboard = ( + +
+
+ delete me pls +
+
+
+ ) + `), + 'await-first-dom-report', + ) + + await selectComponentsForTest(renderResult, [EP.fromString(`sb/grid/delete-me`)]) + + await renderResult.dispatch([deleteSelected()], true) + + expect(getPrintedUiJsCode(renderResult.getEditorState())).toEqual( + formatTestProjectCode(` + import * as React from 'react' + import { Storyboard, Group } from 'utopia-api' + + export var storyboard = ( + +
+ + ) + `), + ) + }) + + it('keeps the container dimensions with a grid that has auto repeat becomes empty', async () => { + const renderResult = await renderTestEditorWithCode( + formatTestProjectCode(` + import * as React from 'react' + import { Storyboard, Group } from 'utopia-api' + + export var storyboard = ( + +
+
+ delete me pls +
+
+
+ ) + `), + 'await-first-dom-report', + ) + + await selectComponentsForTest(renderResult, [EP.fromString(`sb/grid/delete-me`)]) + + await renderResult.dispatch([deleteSelected()], true) + + expect(getPrintedUiJsCode(renderResult.getEditorState())).toEqual( + formatTestProjectCode(` + import * as React from 'react' + import { Storyboard, Group } from 'utopia-api' + + export var storyboard = ( + +
+ + ) + `), + ) + }) + it('keeps the container dimensions with a grid that has all sorts of options becomes empty', async () => { + const renderResult = await renderTestEditorWithCode( + formatTestProjectCode(` + import * as React from 'react' + import { Storyboard, Group } from 'utopia-api' + + export var storyboard = ( + +
+
+ delete me pls +
+
+
+ ) + `), + 'await-first-dom-report', + ) + + await selectComponentsForTest(renderResult, [EP.fromString(`sb/grid/delete-me`)]) + + await renderResult.dispatch([deleteSelected()], true) + + expect(getPrintedUiJsCode(renderResult.getEditorState())).toEqual( + formatTestProjectCode(` + import * as React from 'react' + import { Storyboard, Group } from 'utopia-api' + + export var storyboard = ( + +
+ + ) + `), + ) }) }) }) diff --git a/editor/src/components/canvas/commands/set-css-length-command.spec.tsx b/editor/src/components/canvas/commands/set-css-length-command.spec.tsx index cb624278ce0c..0960f89062ed 100644 --- a/editor/src/components/canvas/commands/set-css-length-command.spec.tsx +++ b/editor/src/components/canvas/commands/set-css-length-command.spec.tsx @@ -51,6 +51,7 @@ describe('setCssLengthProperty', () => { const result = runSetCssLengthProperty( renderResult.getEditorState().editor, setCSSPropertyCommand, + 'end-interaction', ) const patchedEditor = updateEditorStateWithPatches( @@ -227,7 +228,7 @@ function runCommandUpdateEditor( store: EditorStorePatched, command: SetCssLengthProperty, ): EditorState { - const result = runSetCssLengthProperty(store.editor, command) + const result = runSetCssLengthProperty(store.editor, command, 'end-interaction') return updateEditorStateWithPatches(store.editor, result.editorStatePatches) } diff --git a/editor/src/components/canvas/commands/set-css-length-command.ts b/editor/src/components/canvas/commands/set-css-length-command.ts index cde200d644a5..cc2a0998f8a2 100644 --- a/editor/src/components/canvas/commands/set-css-length-command.ts +++ b/editor/src/components/canvas/commands/set-css-length-command.ts @@ -1,33 +1,26 @@ import { notice } from '../../../components/common/notice' -import { isLeft, isRight, left } from '../../../core/shared/either' import * as EP from '../../../core/shared/element-path' -import { - emptyComments, - isJSXElement, - jsExpressionValue, -} from '../../../core/shared/element-template' -import type { GetModifiableAttributeResult, ValueAtPath } from '../../../core/shared/jsx-attributes' -import { - getModifiableJSXAttributeAtPath, - jsxSimpleAttributeToValue, -} from '../../../core/shared/jsx-attribute-utils' import { roundTo } from '../../../core/shared/math-utils' import type { ElementPath, PropertyPath } from '../../../core/shared/project-file-types' import * as PP from '../../../core/shared/property-path' -import type { EditorState, EditorStatePatch } from '../../editor/store/editor-state' -import { withUnderlyingTargetFromEditorState } from '../../editor/store/editor-state' +import type { EditorState } from '../../editor/store/editor-state' import type { CSSKeyword, CSSNumber, FlexDirection } from '../../inspector/common/css-utils' import { cssPixelLength, - parseCSSPercent, printCSSNumber, printCSSNumberOrKeyword, } from '../../inspector/common/css-utils' -import type { CreateIfNotExistant } from './adjust-css-length-command' -import { deleteConflictingPropsForWidthHeight } from './adjust-css-length-command' -import { applyValuesAtPath } from './adjust-number-command' -import type { BaseCommand, CommandFunction, WhenToRun } from './commands' +import type { LengthProperty } from './adjust-css-length-command' +import { + deleteConflictingPropsForWidthHeight, + type CreateIfNotExistant, +} from './adjust-css-length-command' +import type { BaseCommand, CommandFunctionResult, WhenToRun } from './commands' import { addToastPatch } from './show-toast-command' +import { getCSSNumberFromStyleInfo } from './utils/property-utils' +import type { InteractionLifecycle } from '../canvas-strategies/canvas-strategy-types' +import type { StyleUpdate } from '../plugins/style-plugins' +import { getActivePlugin, runStyleUpdateForStrategy } from '../plugins/style-plugins' type CssNumberOrKeepOriginalUnit = | { type: 'EXPLICIT_CSS_NUMBER'; value: CSSNumber | CSSKeyword } @@ -44,10 +37,13 @@ export function setValueKeepingOriginalUnit( return { type: 'KEEP_ORIGINAL_UNIT', valuePx: valuePx, parentDimensionPx: parentDimensionPx } } +type CSSLengthProperty = LengthProperty | 'zIndex' +type CSSLengthPropertyPath = PropertyPath<['style', CSSLengthProperty]> + export interface SetCssLengthProperty extends BaseCommand { type: 'SET_CSS_LENGTH_PROPERTY' target: ElementPath - property: PropertyPath + property: CSSLengthPropertyPath value: CssNumberOrKeepOriginalUnit parentFlexDirection: FlexDirection | null createIfNonExistant: CreateIfNotExistant @@ -57,7 +53,7 @@ export interface SetCssLengthProperty extends BaseCommand { export function setCssLengthProperty( whenToRun: WhenToRun, target: ElementPath, - property: PropertyPath, + property: CSSLengthPropertyPath, value: CssNumberOrKeepOriginalUnit, parentFlexDirection: FlexDirection | null, createIfNonExistant: CreateIfNotExistant = 'create-if-not-existing', // TODO remove the default value and set it explicitly everywhere @@ -75,45 +71,37 @@ export function setCssLengthProperty( } } -export const runSetCssLengthProperty: CommandFunction = ( +export const runSetCssLengthProperty = ( editorState: EditorState, command: SetCssLengthProperty, -) => { + interactionLifecycle: InteractionLifecycle, +): CommandFunctionResult => { // in case of width or height change, delete min, max and flex props const editorStateWithPropsDeleted = deleteConflictingPropsForWidthHeight( + interactionLifecycle, editorState, command.target, - command.property, + [command.property], command.parentFlexDirection, ) - // Identify the current value, whatever that may be. - const currentValue: GetModifiableAttributeResult = withUnderlyingTargetFromEditorState( - command.target, - editorState, - left(`no target element was found at path ${EP.toString(command.target)}`), - (_, element) => { - if (isJSXElement(element)) { - return getModifiableJSXAttributeAtPath(element.props, command.property) - } else { - return left(`No JSXElement was found at path ${EP.toString(command.target)}`) - } - }, - ) - if (isLeft(currentValue)) { + const styleInfo = getActivePlugin(editorStateWithPropsDeleted).styleInfoFactory({ + projectContents: editorStateWithPropsDeleted.projectContents, + jsxMetadata: editorStateWithPropsDeleted.jsxMetadata, + })(command.target) + + if (styleInfo == null) { return { editorStatePatches: [], - commandDescription: `Set Css Length Prop: ${EP.toUid(command.target)}/${PP.toString( - command.property, - )} not applied as value is not writeable.`, + commandDescription: `Set Css Length Prop: Element not found at ${EP.toUid(command.target)}`, } } - const currentModifiableValue = currentValue.value - const simpleValueResult = jsxSimpleAttributeToValue(currentModifiableValue) - const targetPropertyNonExistant: boolean = currentModifiableValue.type === 'ATTRIBUTE_NOT_FOUND' + const property = command.property.propertyElements[1] // the safety of propertyElements[1] is guaranteed by the LengthPropertyPath type + + const currentValue = getCSSNumberFromStyleInfo(styleInfo, property) if ( - targetPropertyNonExistant && + currentValue.type === 'not-found' && command.createIfNonExistant === 'do-not-create-if-doesnt-exist' ) { return { @@ -124,18 +112,18 @@ export const runSetCssLengthProperty: CommandFunction = ( } } - let propsToUpdate: Array = [] + let propsToUpdate: Array = [] let percentageValueWasReplaced: boolean = false - const javascriptExpressionValueWasReplaced: boolean = isLeft(simpleValueResult) // Left for jsxSimpleAttributeToValue means "not simple" which means a javascript expression like `5 + props.hello` + const javascriptExpressionValueWasReplaced = currentValue.type === 'not-css-number' - const parsePercentResult = parseCSSPercent(simpleValueResult.value) if ( - isRight(parsePercentResult) && + currentValue.type === 'css-number' && + currentValue.number.unit === '%' && command.value.type === 'KEEP_ORIGINAL_UNIT' && command.value.parentDimensionPx != null ) { - const currentValuePercent = parsePercentResult.value + const currentValuePercent = currentValue.number const valueInPercent = roundTo( (command.value.valuePx / command.value.parentDimensionPx) * 100, 2, @@ -147,8 +135,9 @@ export const runSetCssLengthProperty: CommandFunction = ( const newValue = printCSSNumber(newValueCssNumber, null) propsToUpdate.push({ - path: command.property, - value: jsExpressionValue(newValue, emptyComments), + type: 'set', + property: property, + value: newValue, }) } else { const newCssValue = @@ -158,7 +147,8 @@ export const runSetCssLengthProperty: CommandFunction = ( if ( command.whenReplacingPercentageValues === 'warn-about-replacement' && - isRight(parsePercentResult) + currentValue.type === 'css-number' && + currentValue.number.unit === '%' ) { percentageValueWasReplaced = true } @@ -166,13 +156,15 @@ export const runSetCssLengthProperty: CommandFunction = ( const printedValue = printCSSNumberOrKeyword(newCssValue, 'px') propsToUpdate.push({ - path: command.property, - value: jsExpressionValue(printedValue, emptyComments), + type: 'set', + property: property, + value: printedValue, }) } // Apply the update to the properties. - const { editorStatePatch: propertyUpdatePatch } = applyValuesAtPath( + const { editorStatePatches } = runStyleUpdateForStrategy( + interactionLifecycle, editorStateWithPropsDeleted, command.target, propsToUpdate, @@ -180,7 +172,6 @@ export const runSetCssLengthProperty: CommandFunction = ( // Always include the property update patch, but potentially also include a warning // that a percentage based property was replaced with a pixel based one. - let editorStatePatches: Array = [propertyUpdatePatch] if (percentageValueWasReplaced) { editorStatePatches.push( addToastPatch( diff --git a/editor/src/components/canvas/commands/set-elements-to-rerender-command.ts b/editor/src/components/canvas/commands/set-elements-to-rerender-command.ts deleted file mode 100644 index 1b6c52fa3368..000000000000 --- a/editor/src/components/canvas/commands/set-elements-to-rerender-command.ts +++ /dev/null @@ -1,85 +0,0 @@ -import * as EP from '../../../core/shared/element-path' -import type { - EditorState, - EditorStatePatch, - ElementsToRerender, -} from '../../editor/store/editor-state' -import type { BaseCommand, CommandFunction } from './commands' - -export interface SetElementsToRerenderCommand extends BaseCommand { - type: 'SET_ELEMENTS_TO_RERENDER_COMMAND' - value: ElementsToRerender -} - -export function setElementsToRerenderCommand( - value: ElementsToRerender, -): SetElementsToRerenderCommand { - const uniqueValues = value === 'rerender-all-elements' ? value : EP.uniqueElementPaths(value) - return { - type: 'SET_ELEMENTS_TO_RERENDER_COMMAND', - whenToRun: 'mid-interaction', - value: uniqueValues, - } -} - -export const runSetElementsToRerender: CommandFunction = ( - e: EditorState, - command: SetElementsToRerenderCommand, -) => { - const editorStatePatch: EditorStatePatch = { - canvas: { - elementsToRerender: { $set: command.value }, - }, - } - return { - editorStatePatches: [editorStatePatch], - commandDescription: `Set Elements To Rerender: [${ - typeof command.value === 'string' ? command.value : command.value.map(EP.toString).join(', ') - }]`, - } -} - -// NOTE: You probably want to use the command above! Since we don't explicitly clear the current -// value of canvas.elementsToRerender, and the default value is 'render-all-elements', if you use -// this command in the wrong place you can very easily end up re-rendering everything -export interface AppendElementsToRerenderCommand extends BaseCommand { - type: 'APPEND_ELEMENTS_TO_RERENDER_COMMAND' - value: ElementsToRerender -} - -export function appendElementsToRerenderCommand( - value: ElementsToRerender, -): AppendElementsToRerenderCommand { - return { - type: 'APPEND_ELEMENTS_TO_RERENDER_COMMAND', - whenToRun: 'mid-interaction', - value: value, - } -} - -function mergeElementsToRerender(l: ElementsToRerender, r: ElementsToRerender): ElementsToRerender { - if (l === 'rerender-all-elements' || r === 'rerender-all-elements') { - return 'rerender-all-elements' - } else { - return [...l, ...r] - } -} - -export const runAppendElementsToRerender: CommandFunction = ( - e: EditorState, - command: AppendElementsToRerenderCommand, -) => { - const editorStatePatch: EditorStatePatch = { - canvas: { - elementsToRerender: { - $apply: (current: ElementsToRerender) => mergeElementsToRerender(current, command.value), - }, - }, - } - return { - editorStatePatches: [editorStatePatch], - commandDescription: `Append Elements To Rerender: [${ - typeof command.value === 'string' ? command.value : command.value.map(EP.toString).join(', ') - }]`, - } -} diff --git a/editor/src/components/canvas/commands/set-property-command.ts b/editor/src/components/canvas/commands/set-property-command.ts index a7b06f27de13..8add2e7c0f94 100644 --- a/editor/src/components/canvas/commands/set-property-command.ts +++ b/editor/src/components/canvas/commands/set-property-command.ts @@ -8,9 +8,15 @@ import type { } from '../../../core/shared/project-file-types' import * as PP from '../../../core/shared/property-path' import type { EditorState } from '../../editor/store/editor-state' -import { applyValuesAtPath } from './adjust-number-command' -import type { BaseCommand, CommandFunction, WhenToRun } from './commands' -import { deleteValuesAtPath } from './delete-properties-command' +import type { InteractionLifecycle } from '../canvas-strategies/canvas-strategy-types' +import { runStyleUpdateForStrategy } from '../plugins/style-plugins' +import * as SU from '../plugins/style-plugins' +import type { BaseCommand, CommandFunction, CommandFunctionResult, WhenToRun } from './commands' +import { + applyValuesAtPath, + deleteValuesAtPath, + maybeCssPropertyFromInlineStyle, +} from './utils/property-utils' type PositionProp = 'left' | 'top' | 'right' | 'bottom' | 'width' | 'height' @@ -20,12 +26,9 @@ export interface SetProperty extends BaseCommand { property: PropertyPath value: string | number } - export type PropertyToUpdate = PropertyToSet | PropertyToDelete - type PropertyToSet = { type: 'SET'; path: PropertyPath; value: string | number } type PropertyToDelete = { type: 'DELETE'; path: PropertyPath } - export function propertyToSet(path: PropertyPath, value: string | number): PropertyToUpdate { return { type: 'SET', @@ -33,20 +36,17 @@ export function propertyToSet(path: PropertyPath, value: string | number): Prope value: value, } } - export function propertyToDelete(path: PropertyPath): PropertyToUpdate { return { type: 'DELETE', path: path, } } - export interface UpdateBulkProperties extends BaseCommand { type: 'UPDATE_BULK_PROPERTIES' element: ElementPath properties: PropertyToUpdate[] } - export function setProperty( whenToRun: WhenToRun, element: ElementPath, @@ -61,7 +61,6 @@ export function setProperty( value: value, } } - export function updateBulkProperties( whenToRun: WhenToRun, element: ElementPath, @@ -74,7 +73,6 @@ export function updateBulkProperties( properties: properties, } } - export function setPropertyOmitNullProp( whenToRun: WhenToRun, element: ElementPath, @@ -88,24 +86,40 @@ export function setPropertyOmitNullProp( } } -export const runSetProperty: CommandFunction = ( +function getCommandDescription(command: SetProperty) { + return `Set Property ${PP.toString(command.property)}=${JSON.stringify( + command.property, + null, + 2, + )} on ${EP.toUid(command.element)}` +} + +export const runSetProperty = ( editorState: EditorState, command: SetProperty, -) => { - // Apply the update to the properties. - const { editorStatePatch: propertyUpdatePatch } = applyValuesAtPath( + interactionLifecycle: InteractionLifecycle, +): CommandFunctionResult => { + const prop = maybeCssPropertyFromInlineStyle(command.property) + if (prop == null) { + const { editorStatePatch } = applyValuesAtPath(editorState, command.element, [ + { path: command.property, value: jsExpressionValue(command.value, emptyComments) }, + ]) + return { + editorStatePatches: [editorStatePatch], + commandDescription: getCommandDescription(command), + } + } + + const { editorStatePatches } = runStyleUpdateForStrategy( + interactionLifecycle, editorState, command.element, - [{ path: command.property, value: jsExpressionValue(command.value, emptyComments) }], + [SU.updateCSSProp(prop, command.value)], ) return { - editorStatePatches: [propertyUpdatePatch], - commandDescription: `Set Property ${PP.toString(command.property)}=${JSON.stringify( - command.property, - null, - 2, - )} on ${EP.toUid(command.element)}`, + editorStatePatches: editorStatePatches, + commandDescription: getCommandDescription(command), } } diff --git a/editor/src/components/canvas/commands/show-grid-controls-command.ts b/editor/src/components/canvas/commands/show-grid-controls-command.ts index 8d92c0855510..4c5caed06cad 100644 --- a/editor/src/components/canvas/commands/show-grid-controls-command.ts +++ b/editor/src/components/canvas/commands/show-grid-controls-command.ts @@ -1,20 +1,26 @@ -import type { ElementPath } from 'utopia-shared/src/types' import type { BaseCommand, CommandFunction, WhenToRun } from './commands' -import type { EditorState, EditorStatePatch } from '../../editor/store/editor-state' +import type { EditorState, EditorStatePatch, GridIdentifier } from '../../editor/store/editor-state' +import type { GridCellCoordinates } from '../canvas-strategies/strategies/grid-cell-bounds' export interface ShowGridControlsCommand extends BaseCommand { type: 'SHOW_GRID_CONTROLS' - target: ElementPath + target: GridIdentifier + targetCell: GridCellCoordinates | null + rootCell: GridCellCoordinates | null } export function showGridControls( whenToRun: WhenToRun, - target: ElementPath, + target: GridIdentifier, + targetCell: GridCellCoordinates | null, + rootCell: GridCellCoordinates | null, ): ShowGridControlsCommand { return { type: 'SHOW_GRID_CONTROLS', whenToRun: whenToRun, target: target, + targetCell: targetCell, + rootCell: rootCell, } } @@ -25,7 +31,13 @@ export const runShowGridControlsCommand: CommandFunction = ( - editorState: EditorState, - command: UpdatePropIfExists, -) => { - // check if the prop exists - const propertyExists = withUnderlyingTargetFromEditorState( - command.element, - editorState, - false, - (_, element) => { - if (isJSXElement(element)) { - return foldEither( - () => false, - (value) => !modifiableAttributeIsAttributeNotFound(value), - getModifiableJSXAttributeAtPath(element.props, command.property), - ) - } else { - return false - } - }, - ) - - if (propertyExists) { - // Apply the update to the properties. - const { editorStatePatch: propertyUpdatePatch } = applyValuesAtPath( - editorState, - command.element, - [{ path: command.property, value: jsExpressionValue(command.value, emptyComments) }], - ) - - return { - editorStatePatches: [propertyUpdatePatch], - commandDescription: `Update Prop if Exists ${PP.toString(command.property)}=${JSON.stringify( - command.property, - null, - 2, - )} on ${EP.toUid(command.element)}`, - } - } else { - // no op return to prevent updating a nonexistant prop. if you want to set a prop regardless whether it exists or not, use the setPropertyCommand - return { - editorStatePatches: [], - commandDescription: `Update Prop if Exists did not find existing prop for ${PP.toString( - command.property, - )}`, - } - } -} diff --git a/editor/src/components/canvas/commands/utils/property-utils.ts b/editor/src/components/canvas/commands/utils/property-utils.ts new file mode 100644 index 000000000000..123bba748aae --- /dev/null +++ b/editor/src/components/canvas/commands/utils/property-utils.ts @@ -0,0 +1,124 @@ +import type { ElementPath, JSXElement, PropertyPath } from 'utopia-shared/src/types' +import { foldEither } from '../../../../core/shared/either' +import type { ValueAtPath } from '../../../../core/shared/jsx-attributes' +import { setJSXValuesAtPaths, unsetJSXValuesAtPaths } from '../../../../core/shared/jsx-attributes' +import type { EditorState, EditorStatePatch } from '../../../editor/store/editor-state' +import { modifyUnderlyingElementForOpenFile } from '../../../editor/store/editor-state' +import { patchParseSuccessAtElementPath } from '../patch-utils' +import type { CSSNumber } from '../../../inspector/common/css-utils' +import { isCSSNumber } from '../../../inspector/common/css-utils' +import type { StyleInfo } from '../../canvas-types' + +export interface EditorStateWithPatch { + editorStateWithChanges: EditorState + editorStatePatch: EditorStatePatch +} + +export function applyValuesAtPath( + editorState: EditorState, + target: ElementPath, + jsxValuesAndPathsToSet: ValueAtPath[], +): EditorStateWithPatch { + const workingEditorState = modifyUnderlyingElementForOpenFile( + target, + editorState, + (element: JSXElement) => { + return foldEither( + () => { + return element + }, + (updatedProps) => { + return { + ...element, + props: updatedProps, + } + }, + setJSXValuesAtPaths(element.props, jsxValuesAndPathsToSet), + ) + }, + ) + + const editorStatePatch = patchParseSuccessAtElementPath(target, workingEditorState, (success) => { + return { + topLevelElements: { + $set: success.topLevelElements, + }, + imports: { + $set: success.imports, + }, + } + }) + + return { + editorStateWithChanges: workingEditorState, + editorStatePatch: editorStatePatch, + } +} + +export function deleteValuesAtPath( + editorState: EditorState, + target: ElementPath, + properties: Array, +): EditorStateWithPatch { + const workingEditorState = modifyUnderlyingElementForOpenFile( + target, + editorState, + (element: JSXElement) => { + return foldEither( + () => { + return element + }, + (updatedProps) => { + return { + ...element, + props: updatedProps, + } + }, + unsetJSXValuesAtPaths(element.props, properties), + ) + }, + ) + + const editorStatePatch = patchParseSuccessAtElementPath(target, workingEditorState, (success) => { + return { + topLevelElements: { + $set: success.topLevelElements, + }, + imports: { + $set: success.imports, + }, + } + }) + return { + editorStateWithChanges: workingEditorState, + editorStatePatch: editorStatePatch, + } +} + +export function maybeCssPropertyFromInlineStyle(property: PropertyPath): string | null { + const [maybeStyle, prop] = property.propertyElements + if (maybeStyle !== 'style' || prop == null || typeof prop !== 'string') { + return null + } + return prop +} + +export type GetCSSNumberFromStyleInfoResult = + | { type: 'not-found' } + | { type: 'not-css-number' } + | { type: 'css-number'; number: CSSNumber } + +export function getCSSNumberFromStyleInfo( + styleInfo: StyleInfo, + property: keyof StyleInfo, +): GetCSSNumberFromStyleInfoResult { + const prop = styleInfo[property] + if (prop == null || prop.type === 'not-found') { + return { type: 'not-found' } + } + + if (prop.type === 'not-parsable' || !isCSSNumber(prop.value)) { + return { type: 'not-css-number' } + } + return { type: 'css-number', number: prop.value } +} diff --git a/editor/src/components/canvas/controls/elements-outside-visible-area-hooks.tsx b/editor/src/components/canvas/controls/elements-outside-visible-area-hooks.tsx index b256b4cef2e6..4908e3fc23c9 100644 --- a/editor/src/components/canvas/controls/elements-outside-visible-area-hooks.tsx +++ b/editor/src/components/canvas/controls/elements-outside-visible-area-hooks.tsx @@ -1,26 +1,16 @@ import React from 'react' import { MetadataUtils } from '../../../core/model/element-metadata-utils' import { mapDropNulls } from '../../../core/shared/array-utils' -import * as EP from '../../../core/shared/element-path' -import type { CanvasRectangle, WindowPoint, WindowRectangle } from '../../../core/shared/math-utils' +import type { CanvasRectangle, WindowPoint } from '../../../core/shared/math-utils' import { boundingRectangleArray, getRectCenter, - isFiniteRectangle, rectangleIntersection, windowRectangle, } from '../../../core/shared/math-utils' -import type { ElementPath } from '../../../core/shared/project-file-types' -import { Substores, useEditorState, useRefEditorState } from '../../editor/store/store-hook' +import { Substores, useEditorState } from '../../editor/store/store-hook' import { canvasPointToWindowPoint } from '../dom-lookup' -const topBarHeight = 40 // px - -type ElementOutsideVisibleArea = { - path: ElementPath - rect: WindowRectangle -} - export type ElementOutsideVisibleAreaIndicator = { position: WindowPoint } @@ -28,16 +18,6 @@ export type ElementOutsideVisibleAreaIndicator = { export function useElementsOutsideVisibleArea(): ElementOutsideVisibleAreaIndicator | null { const canvasBounds = document.getElementById('canvas-root')?.getBoundingClientRect() - const selectedViews = useEditorState( - Substores.selectedViews, - (store) => store.editor.selectedViews, - 'useElementsOutsideVisibleArea selectedViews', - ) - - const storeRef = useRefEditorState((store) => ({ - jsxMetadata: store.editor.jsxMetadata, - })) - const canvasScale = useEditorState( Substores.canvasOffset, (store) => store.editor.canvas.scale, @@ -49,21 +29,6 @@ export function useElementsOutsideVisibleArea(): ElementOutsideVisibleAreaIndica 'useElementsOutsideVisibleArea canvasOffset', ) - const framesByPathString = React.useMemo(() => { - const frames: { [key: string]: CanvasRectangle } = {} - for (const path of selectedViews) { - const metadata = MetadataUtils.findElementByElementPath(storeRef.current.jsxMetadata, path) - if ( - metadata != null && - metadata.globalFrame != null && - isFiniteRectangle(metadata.globalFrame) - ) { - frames[EP.toString(path)] = metadata.globalFrame - } - } - return frames - }, [storeRef, selectedViews]) - const scaledCanvasArea = React.useMemo(() => { if (canvasBounds == null) { return null @@ -71,54 +36,56 @@ export function useElementsOutsideVisibleArea(): ElementOutsideVisibleAreaIndica const scaleRatio = canvasScale > 1 ? canvasScale : 1 return windowRectangle({ x: canvasBounds.x * scaleRatio, - y: (canvasBounds.y - topBarHeight) * scaleRatio, + y: canvasBounds.y * scaleRatio, width: canvasBounds.width, height: canvasBounds.height, }) }, [canvasBounds, canvasScale]) - const elementsOutsideVisibleArea = React.useMemo(() => { - return mapDropNulls((path: ElementPath): ElementOutsideVisibleArea | null => { - if (scaledCanvasArea == null) { - return null - } - const frame = framesByPathString[EP.toString(path)] - if (frame == null) { + const selectedElementsFrames: CanvasRectangle[] = useEditorState( + Substores.metadata, + (store) => { + return mapDropNulls( + (view) => MetadataUtils.getFrameOrZeroRectInCanvasCoords(view, store.editor.jsxMetadata), + store.editor.selectedViews, + ) + }, + 'useElementsOutsideVisibleArea selectedElementsFrames', + ) + + return React.useMemo(() => { + if (scaledCanvasArea == null) { + return null + } + + const framesOutsideVisibleArea = mapDropNulls((frame) => { + if (frame.width === 0 || frame.height === 0) { return null } const topLeftPoint = canvasPointToWindowPoint(frame, canvasScale, canvasOffset) - const elementRect = windowRectangle({ + const elementWindowRect = windowRectangle({ x: topLeftPoint.x, - y: topLeftPoint.y - topBarHeight, + y: topLeftPoint.y, width: frame.width * canvasScale, height: frame.height * canvasScale, }) - - const isOutsideVisibleArea = rectangleIntersection(scaledCanvasArea, elementRect) == null - if (!isOutsideVisibleArea) { + if (rectangleIntersection(scaledCanvasArea, elementWindowRect) != null) { return null } - return { - path: path, - rect: elementRect, - } - }, selectedViews) - }, [selectedViews, canvasOffset, canvasScale, scaledCanvasArea, framesByPathString]) + return elementWindowRect + }, selectedElementsFrames) - return React.useMemo((): ElementOutsideVisibleAreaIndicator | null => { - if (elementsOutsideVisibleArea.length === 0) { - return null - } - const windowRect = boundingRectangleArray(elementsOutsideVisibleArea.map((a) => a.rect)) + const windowRect = boundingRectangleArray(framesOutsideVisibleArea) if (windowRect == null) { return null } + return { position: getRectCenter(windowRect), } - }, [elementsOutsideVisibleArea]) + }, [selectedElementsFrames, canvasOffset, canvasScale, scaledCanvasArea]) } export function getIndicatorAngleToTarget(from: WindowPoint, to: WindowPoint): number { diff --git a/editor/src/components/canvas/controls/grid-controls-for-strategies.tsx b/editor/src/components/canvas/controls/grid-controls-for-strategies.tsx new file mode 100644 index 000000000000..b91113d0677d --- /dev/null +++ b/editor/src/components/canvas/controls/grid-controls-for-strategies.tsx @@ -0,0 +1,249 @@ +/** @jsxRuntime classic */ +/** @jsx jsx */ +import type { ElementPath } from 'utopia-shared/src/types' +import { printGridAutoOrTemplateBase } from '../../inspector/common/css-utils' +import { MetadataUtils } from '../../../core/model/element-metadata-utils' +import { mapDropNulls } from '../../../core/shared/array-utils' +import * as EP from '../../../core/shared/element-path' +import { type GridAutoOrTemplateBase } from '../../../core/shared/element-template' +import { assertNever } from '../../../core/shared/utils' +import { Substores, useEditorState } from '../../editor/store/store-hook' +import type { + ControlWithProps, + WhenToShowControl, +} from '../canvas-strategies/canvas-strategy-types' +import { controlForStrategyMemoized } from '../canvas-strategies/canvas-strategy-types' +import type { GridResizeEdge } from '../canvas-strategies/interaction-state' +import type { EdgePosition } from '../canvas-types' +import { + EdgePositionBottom, + EdgePositionLeft, + EdgePositionRight, + EdgePositionTop, +} from '../canvas-types' +import { + GridControlsComponent, + GridResizeControlsComponent, + GridRowColumnResizingControlsComponent, + GridRulersControlsComponent, +} from './grid-controls' +import { isEdgePositionOnSide } from '../canvas-utils' +import { useMonitorChangesToElements } from '../../../components/editor/store/store-monitor' +import { useKeepReferenceEqualityIfPossible } from '../../../utils/react-performance' +import { + gridContainerIdentifier, + gridItemIdentifier, + pathOfGridFromGridIdentifier, + type GridIdentifier, +} from '../../editor/store/editor-state' +import { findOriginalGrid } from '../canvas-strategies/strategies/grid-helpers' +import * as React from 'react' +import type { GridData } from './grid-measurements' +import { getGridMeasurementHelperData, useObserversToWatch } from './grid-measurements' + +export const GridMeasurementHelperMap = { current: new WeakMap() } + +export const GridCellTestId = (elementPath: ElementPath) => `grid-cell-${EP.toString(elementPath)}` + +export function getNullableAutoOrTemplateBaseString( + template: GridAutoOrTemplateBase | null, +): string | undefined { + if (template == null) { + return undefined + } else { + return printGridAutoOrTemplateBase(template) + } +} + +export function useGridData(gridIdentifiers: GridIdentifier[]): GridData[] { + const scale = useEditorState( + Substores.canvas, + (store) => store.editor.canvas.scale, + 'useGridData scale', + ) + + const elementPaths = React.useMemo(() => { + return gridIdentifiers.map(pathOfGridFromGridIdentifier) + }, [gridIdentifiers]) + + useMonitorChangesToElements(elementPaths) + + useObserversToWatch(elementPaths) + + const grids = useEditorState( + Substores.metadata, + (store) => { + return mapDropNulls((view) => { + switch (view.type) { + case 'GRID_CONTAINER': { + const originalGridPath = findOriginalGrid(store.editor.jsxMetadata, view.container) + if (originalGridPath == null) { + return null + } + const element = MetadataUtils.findElementByElementPath( + store.editor.jsxMetadata, + originalGridPath, + ) + + const targetGridContainer = MetadataUtils.isGridLayoutedContainer(element) + ? element + : null + if (targetGridContainer == null) { + return null + } + + const helperData = getGridMeasurementHelperData(originalGridPath, scale, 'element') + if (helperData == null) { + return null + } + + const gridData: GridData = { + ...helperData, + identifier: gridContainerIdentifier(originalGridPath), + } + return gridData + } + case 'GRID_ITEM': + const item = MetadataUtils.isGridItem(store.editor.jsxMetadata, view.item) + ? MetadataUtils.findElementByElementPath(store.editor.jsxMetadata, view.item) + : null + if (item == null) { + return null + } + + const helperData = getGridMeasurementHelperData(view.item, scale, 'parent') + if (helperData == null) { + return null + } + + const gridData: GridData = { + ...helperData, + identifier: gridItemIdentifier(view.item), + } + return gridData + default: + assertNever(view) + } + }, gridIdentifiers) + }, + 'useGridData', + ) + + return useKeepReferenceEqualityIfPossible(grids) +} + +interface GridRowColumnResizingControlsProps { + target: GridIdentifier +} + +export const GridRowColumnResizingControls = + controlForStrategyMemoized( + GridRowColumnResizingControlsComponent, + ) + +export const GridControlsKey = (gridPath: ElementPath) => `grid-controls-${EP.toString(gridPath)}` +export const GridControlKey = (gridPath: ElementPath) => `grid-control-${EP.toString(gridPath)}` + +export const GridMeasurementHelpersKey = (gridPath: ElementPath) => + `grid-measurement-helpers-${EP.toString(gridPath)}` +export const GridMeasurementHelperKey = (gridPath: ElementPath) => + `grid-measurement-helper-${EP.toString(gridPath)}` +export const GridElementContainingBlockKey = (gridPath: ElementPath) => + `grid-measurement-containing-block-${EP.toString(gridPath)}` +export const GridElementChildContainingBlockKey = (gridPath: ElementPath) => + `${GridElementContainingBlockKey(gridPath)}-child` + +export interface GridControlProps { + grid: GridData +} + +export interface GridControlsProps { + type: 'GRID_CONTROLS_PROPS' + targets: GridIdentifier[] +} + +export function isGridControlsProps(props: unknown): props is GridControlsProps { + return (props as GridControlsProps).type === 'GRID_CONTROLS_PROPS' +} + +export const GridControls = controlForStrategyMemoized(GridControlsComponent) + +export interface GridRulersControlsProps { + type: 'GRID_RULERS_CONTROLS_PROPS' + targets: GridIdentifier[] +} + +export const GridRulersControls = controlForStrategyMemoized( + GridRulersControlsComponent, +) + +interface GridResizeControlProps { + target: GridIdentifier +} + +export const GridResizeControls = controlForStrategyMemoized( + GridResizeControlsComponent, +) + +export function gridEdgeToEdgePosition(edge: GridResizeEdge): EdgePosition { + switch (edge) { + case 'column-end': + return EdgePositionRight + case 'column-start': + return EdgePositionLeft + case 'row-end': + return EdgePositionBottom + case 'row-start': + return EdgePositionTop + default: + assertNever(edge) + } +} + +export function edgePositionToGridResizeEdge(position: EdgePosition): GridResizeEdge | null { + if (!isEdgePositionOnSide(position)) { + return null + } else if (position.x === 0) { + return 'column-start' + } else if (position.x === 1) { + return 'column-end' + } else if (position.y === 0) { + return 'row-start' + } else if (position.y === 1) { + return 'row-end' + } else { + return null + } +} + +export function controlsForGridPlaceholders( + gridPath: GridIdentifier | Array, + whenToShow: WhenToShowControl = 'always-visible', + suffix: string | null = null, +): ControlWithProps { + return { + control: GridControls, + props: { + type: 'GRID_CONTROLS_PROPS', + targets: Array.isArray(gridPath) ? gridPath : [gridPath], + }, + key: `GridControls${suffix == null ? '' : suffix}`, + show: whenToShow, + } +} + +export function controlsForGridRulers( + gridPath: GridIdentifier | Array, + whenToShow: WhenToShowControl = 'always-visible', + suffix: string | null = null, +): ControlWithProps { + return { + control: GridRulersControls, + props: { + type: 'GRID_RULERS_CONTROLS_PROPS', + targets: Array.isArray(gridPath) ? gridPath : [gridPath], + }, + key: `GridRulersControls${suffix == null ? '' : suffix}`, + show: whenToShow, + } +} diff --git a/editor/src/components/canvas/controls/grid-controls-helpers.ts b/editor/src/components/canvas/controls/grid-controls-helpers.ts new file mode 100644 index 000000000000..c24340b47410 --- /dev/null +++ b/editor/src/components/canvas/controls/grid-controls-helpers.ts @@ -0,0 +1,31 @@ +import type { CSSProperties } from 'react' +import type { GridMeasurementHelperData } from './grid-measurements' + +export function getGridHelperStyleMatchingTargetGrid( + grid: GridMeasurementHelperData, +): CSSProperties { + let style: CSSProperties = { + ...grid.computedStyling, + position: 'absolute', + top: grid.frame.y, + left: grid.frame.x, + width: grid.frame.width, + height: grid.frame.height, + display: 'grid', + pointerEvents: 'none', + borderTop: `${grid.border?.top}px solid transparent`, + borderLeft: `${grid.border?.left}px solid transparent`, + borderBottom: `${grid.border?.bottom}px solid transparent`, + borderRight: `${grid.border?.right}px solid transparent`, + } + + // Gap needs to be set only if the other two are not present or we'll have rendering issues + // due to how measurements are calculated. + if (grid.rowGap != null && grid.columnGap != null) { + style.rowGap = grid.rowGap + style.columnGap = grid.columnGap + delete style['gap'] + } + + return style +} diff --git a/editor/src/components/canvas/controls/grid-controls-ruler-markers.tsx b/editor/src/components/canvas/controls/grid-controls-ruler-markers.tsx new file mode 100644 index 000000000000..f8023464a468 --- /dev/null +++ b/editor/src/components/canvas/controls/grid-controls-ruler-markers.tsx @@ -0,0 +1,161 @@ +import React from 'react' +import { colorTheme, type UtopiColor } from '../../../uuiui' +import { RulerMarkerIconSize } from './grid-controls' + +export type RulerMarkerType = 'span-start' | 'span-end' | 'auto' | 'auto-short' | 'pinned' + +interface MarkerSVGProps { + scale: number +} + +function MarkerSVG({ scale, children }: React.PropsWithChildren) { + return ( + + {children} + + ) +} + +function upFacingTriangle(fillColor: UtopiColor, scale: number): React.ReactNode { + return ( + + + + ) +} + +function rightFacingTriangle(fillColor: UtopiColor, scale: number): React.ReactNode { + return ( + + + + ) +} + +function downFacingTriangle(fillColor: UtopiColor, scale: number): React.ReactNode { + return ( + + + + ) +} + +function leftFacingTriangle(fillColor: UtopiColor, scale: number): React.ReactNode { + return ( + + + + ) +} + +function regularVerticalPipe(fillColor: UtopiColor, scale: number): React.ReactNode { + return ( + + + + ) +} + +function regularHorizontalPipe(fillColor: UtopiColor, scale: number): React.ReactNode { + return ( + + + + ) +} + +function shortVerticalPipe(fillColor: UtopiColor, scale: number): React.ReactNode { + return ( + + + + ) +} + +function shortHorizontalPipe(fillColor: UtopiColor, scale: number): React.ReactNode { + return ( + + + + ) +} + +type ColorToReactNode = (fillColor: UtopiColor, scale: number) => React.ReactNode + +export const rulerMarkerIcons: { + [key in RulerMarkerType]: { column: ColorToReactNode; row: ColorToReactNode } +} = { + 'span-start': { + column: rightFacingTriangle, + row: downFacingTriangle, + }, + 'span-end': { + column: leftFacingTriangle, + row: upFacingTriangle, + }, + auto: { + column: regularVerticalPipe, + row: regularHorizontalPipe, + }, + 'auto-short': { + column: shortVerticalPipe, + row: shortHorizontalPipe, + }, + pinned: { + column: downFacingTriangle, + row: rightFacingTriangle, + }, +} diff --git a/editor/src/components/canvas/controls/grid-controls.spec.browser2.tsx b/editor/src/components/canvas/controls/grid-controls.spec.browser2.tsx new file mode 100644 index 000000000000..ab6344931042 --- /dev/null +++ b/editor/src/components/canvas/controls/grid-controls.spec.browser2.tsx @@ -0,0 +1,739 @@ +import type { CSSProperties } from 'react' +import { renderTestEditorWithCode } from '../ui-jsx.test-utils' +import type { SimpleRectangle } from '../../..//core/shared/math-utils' +import { selectComponentsForTest } from '../../../utils/utils.test-utils' +import * as EP from '../../../core/shared/element-path' + +interface TestGridOutlinesResult { + top?: SimpleRectangle + left?: SimpleRectangle + bottom?: SimpleRectangle + right?: SimpleRectangle +} + +async function testGridOutlines( + targetPath: string, + attributes: CSSProperties, +): Promise { + const fullAttributes: CSSProperties = { backgroundColor: 'pink', ...attributes } + let fullAttributesText: string = '{\n' + for (const [key, value] of Object.entries(fullAttributes)) { + fullAttributesText += `${key}: ${JSON.stringify(value)},\n` + } + fullAttributesText += '}' + + const projectCode = `import * as React from 'react' +import { Scene, Storyboard } from 'utopia-api' + +export var storyboard = ( + + +
+
+
+
+
+
+
+
+ + +) +` + const editor = await renderTestEditorWithCode(projectCode, 'await-first-dom-report') + await selectComponentsForTest(editor, [EP.fromString(targetPath)]) + + const topPinLine = editor.renderedDOM.queryByTestId('pin-line-top') + const leftPinLine = editor.renderedDOM.queryByTestId('pin-line-left') + const bottomPinLine = editor.renderedDOM.queryByTestId('pin-line-bottom') + const rightPinLine = editor.renderedDOM.queryByTestId('pin-line-right') + + function toBoundingRect(element: HTMLElement): SimpleRectangle { + const domRect = element.getBoundingClientRect() + return { + x: domRect.x, + y: domRect.y, + width: domRect.width, + height: domRect.height, + } + } + + let result: TestGridOutlinesResult = {} + if (topPinLine != null) { + result.top = toBoundingRect(topPinLine) + } + if (leftPinLine != null) { + result.left = toBoundingRect(leftPinLine) + } + if (bottomPinLine != null) { + result.bottom = toBoundingRect(bottomPinLine) + } + if (rightPinLine != null) { + result.right = toBoundingRect(rightPinLine) + } + return result +} + +describe('Grid Pin Outlines', () => { + it('pinned top and left, grid rows and columns fully specified and not absolutely positioned', async () => { + const result = await testGridOutlines('storyboard/scene/grid/grid-child', { + top: 5, + left: 12, + gridRowStart: 2, + gridColumnStart: 3, + gridRowEnd: 2, + gridColumnEnd: 3, + width: 50, + height: 50, + }) + expect(result).toEqual({}) + }) + it('pinned top and left, grid rows and columns fully specified and absolutely positioned', async () => { + const result = await testGridOutlines('storyboard/scene/grid/grid-child', { + position: 'absolute', + top: 5, + left: 12, + gridRowStart: 2, + gridColumnStart: 3, + gridRowEnd: 2, + gridColumnEnd: 3, + width: 50, + height: 50, + }) + expect(result).toEqual({ + left: { x: 688.5, y: 315.5, width: 12, height: 1 }, + top: { x: 725.5, y: 285.5, width: 1, height: 5 }, + }) + }) + it('pinned top and right, grid rows and columns fully specified and not absolutely positioned', async () => { + const result = await testGridOutlines('storyboard/scene/grid/grid-child', { + top: 5, + right: 12, + gridRowStart: 2, + gridColumnStart: 3, + gridRowEnd: 2, + gridColumnEnd: 3, + width: 50, + height: 50, + }) + expect(result).toEqual({}) + }) + it('pinned top and right, grid rows and columns fully specified and absolutely positioned', async () => { + const result = await testGridOutlines('storyboard/scene/grid/grid-child', { + position: 'absolute', + top: 5, + right: 12, + gridRowStart: 2, + gridColumnStart: 3, + gridRowEnd: 2, + gridColumnEnd: 3, + width: 50, + height: 50, + }) + expect(result).toEqual({ + right: { x: 777.5, y: 315.5, width: 12, height: 1 }, + top: { x: 751.5, y: 285.5, width: 1, height: 5 }, + }) + }) + it('pinned bottom and left, grid rows and columns fully specified and not absolutely positioned', async () => { + const result = await testGridOutlines('storyboard/scene/grid/grid-child', { + bottom: 5, + left: 12, + gridRowStart: 2, + gridColumnStart: 3, + gridRowEnd: 2, + gridColumnEnd: 3, + width: 50, + height: 50, + }) + expect(result).toEqual({}) + }) + it('pinned bottom and left, grid rows and columns fully specified and absolutely positioned', async () => { + const result = await testGridOutlines('storyboard/scene/grid/grid-child', { + position: 'absolute', + bottom: 5, + left: 12, + gridRowStart: 2, + gridColumnStart: 3, + gridRowEnd: 2, + gridColumnEnd: 3, + width: 50, + height: 50, + }) + expect(result).toEqual({ + left: { x: 688.5, y: 355.5, width: 12, height: 1 }, + bottom: { x: 725.5, y: 381.5, width: 1, height: 5 }, + }) + }) + it('pinned bottom and right, grid rows and columns fully specified and not absolutely positioned', async () => { + const result = await testGridOutlines('storyboard/scene/grid/grid-child', { + bottom: 5, + right: 12, + gridRowStart: 2, + gridColumnStart: 3, + gridRowEnd: 2, + gridColumnEnd: 3, + width: 50, + height: 50, + }) + expect(result).toEqual({}) + }) + it('pinned bottom and right, grid rows and columns fully specified and absolutely positioned', async () => { + const result = await testGridOutlines('storyboard/scene/grid/grid-child', { + position: 'absolute', + bottom: 5, + right: 12, + gridRowStart: 2, + gridColumnStart: 3, + gridRowEnd: 2, + gridColumnEnd: 3, + width: 50, + height: 50, + }) + expect(result).toEqual({ + right: { x: 777.5, y: 355.5, width: 12, height: 1 }, + bottom: { x: 751.5, y: 381.5, width: 1, height: 5 }, + }) + }) + + it('pinned top and left, grid column end set to auto and not absolutely positioned', async () => { + const result = await testGridOutlines('storyboard/scene/grid/grid-child', { + top: 5, + left: 12, + gridRowStart: 2, + gridColumnStart: 3, + gridRowEnd: 2, + gridColumnEnd: 'auto', + width: 50, + height: 50, + }) + expect(result).toEqual({}) + }) + it('pinned top and left, grid column end set to auto and absolutely positioned', async () => { + const result = await testGridOutlines('storyboard/scene/grid/grid-child', { + position: 'absolute', + top: 5, + left: 12, + gridRowStart: 2, + gridColumnStart: 3, + gridRowEnd: 2, + gridColumnEnd: 'auto', + width: 50, + height: 50, + }) + expect(result).toEqual({ + left: { x: 688.5, y: 315.5, width: 12, height: 1 }, + top: { x: 725.5, y: 285.5, width: 1, height: 5 }, + }) + }) + it('pinned top and right, grid column end set to auto and not absolutely positioned', async () => { + const result = await testGridOutlines('storyboard/scene/grid/grid-child', { + top: 5, + right: 12, + gridRowStart: 2, + gridColumnStart: 3, + gridRowEnd: 2, + gridColumnEnd: 'auto', + width: 50, + height: 50, + }) + expect(result).toEqual({}) + }) + it('pinned top and right, grid column end set to auto and absolutely positioned', async () => { + const result = await testGridOutlines('storyboard/scene/grid/grid-child', { + position: 'absolute', + top: 5, + right: 12, + gridRowStart: 2, + gridColumnStart: 3, + gridRowEnd: 2, + gridColumnEnd: 'auto', + width: 50, + height: 50, + }) + expect(result).toEqual({ + right: { x: 807.5, y: 315.5, width: 12, height: 1 }, + top: { x: 781.5, y: 285.5, width: 1, height: 5 }, + }) + }) + it('pinned bottom and left, grid column end set to auto and not absolutely positioned', async () => { + const result = await testGridOutlines('storyboard/scene/grid/grid-child', { + bottom: 5, + left: 12, + gridRowStart: 2, + gridColumnStart: 3, + gridRowEnd: 2, + gridColumnEnd: 'auto', + width: 50, + height: 50, + }) + expect(result).toEqual({}) + }) + it('pinned bottom and left, grid column end set to auto and absolutely positioned', async () => { + const result = await testGridOutlines('storyboard/scene/grid/grid-child', { + position: 'absolute', + bottom: 5, + left: 12, + gridRowStart: 2, + gridColumnStart: 3, + gridRowEnd: 2, + gridColumnEnd: 'auto', + width: 50, + height: 50, + }) + expect(result).toEqual({ + left: { x: 688.5, y: 355.5, width: 12, height: 1 }, + bottom: { x: 725.5, y: 381.5, width: 1, height: 5 }, + }) + }) + it('pinned bottom and right, grid column end set to auto and not absolutely positioned', async () => { + const result = await testGridOutlines('storyboard/scene/grid/grid-child', { + bottom: 5, + right: 12, + gridRowStart: 2, + gridColumnStart: 3, + gridRowEnd: 2, + gridColumnEnd: 'auto', + width: 50, + height: 50, + }) + expect(result).toEqual({}) + }) + it('pinned bottom and right, grid column end set to auto and absolutely positioned', async () => { + const result = await testGridOutlines('storyboard/scene/grid/grid-child', { + position: 'absolute', + bottom: 5, + right: 12, + gridRowStart: 2, + gridColumnStart: 3, + gridRowEnd: 2, + gridColumnEnd: 'auto', + width: 50, + height: 50, + }) + expect(result).toEqual({ + right: { x: 807.5, y: 355.5, width: 12, height: 1 }, + bottom: { x: 781.5, y: 381.5, width: 1, height: 5 }, + }) + }) + + it('pinned top and left, grid rows and columns fully specified and not absolutely positioned in non-absolute grid', async () => { + const result = await testGridOutlines('storyboard/scene/grid-2-outer/grid-2/grid-child-2', { + top: 5, + left: 12, + gridRowStart: 2, + gridColumnStart: 3, + gridRowEnd: 2, + gridColumnEnd: 3, + width: 50, + height: 50, + }) + expect(result).toEqual({}) + }) + it('pinned top and left, grid rows and columns fully specified and absolutely positioned in non-absolute grid', async () => { + const result = await testGridOutlines('storyboard/scene/grid-2-outer/grid-2/grid-child-2', { + position: 'absolute', + top: 5, + left: 12, + gridRowStart: 2, + gridColumnStart: 3, + gridRowEnd: 2, + gridColumnEnd: 3, + width: 50, + height: 50, + }) + expect(result).toEqual({ + left: { x: 1418.5, y: 190.5, width: 12, height: 1 }, + top: { x: 1455.5, y: 160.5, width: 1, height: 5 }, + }) + }) + it('pinned top and right, grid rows and columns fully specified and not absolutely positioned in non-absolute grid', async () => { + const result = await testGridOutlines('storyboard/scene/grid-2-outer/grid-2/grid-child-2', { + top: 5, + right: 12, + gridRowStart: 2, + gridColumnStart: 3, + gridRowEnd: 2, + gridColumnEnd: 3, + width: 50, + height: 50, + }) + expect(result).toEqual({}) + }) + it('pinned top and right, grid rows and columns fully specified and absolutely positioned in non-absolute grid', async () => { + const result = await testGridOutlines('storyboard/scene/grid-2-outer/grid-2/grid-child-2', { + position: 'absolute', + top: 5, + right: 12, + gridRowStart: 2, + gridColumnStart: 3, + gridRowEnd: 2, + gridColumnEnd: 3, + width: 50, + height: 50, + }) + expect(result).toEqual({ + right: { x: 1806.5, y: 190.5, width: 12, height: 1 }, + top: { x: 1781.5, y: 160.5, width: 1, height: 5 }, + }) + }) + it('pinned bottom and left, grid rows and columns fully specified and not absolutely positioned in non-absolute grid', async () => { + const result = await testGridOutlines('storyboard/scene/grid-2-outer/grid-2/grid-child-2', { + bottom: 5, + left: 12, + gridRowStart: 2, + gridColumnStart: 3, + gridRowEnd: 2, + gridColumnEnd: 3, + width: 50, + height: 50, + }) + expect(result).toEqual({}) + }) + it('pinned bottom and left, grid rows and columns fully specified and absolutely positioned in non-absolute grid', async () => { + const result = await testGridOutlines('storyboard/scene/grid-2-outer/grid-2/grid-child-2', { + position: 'absolute', + bottom: 5, + left: 12, + gridRowStart: 2, + gridColumnStart: 3, + gridRowEnd: 2, + gridColumnEnd: 3, + width: 50, + height: 50, + }) + expect(result).toEqual({ + left: { x: 1418.5, y: 530.5, width: 12, height: 1 }, + bottom: { x: 1455.5, y: 555.5, width: 1, height: 5 }, + }) + }) + it('pinned bottom and right, grid rows and columns fully specified and not absolutely positioned in non-absolute grid', async () => { + const result = await testGridOutlines('storyboard/scene/grid-2-outer/grid-2/grid-child-2', { + bottom: 5, + right: 12, + gridRowStart: 2, + gridColumnStart: 3, + gridRowEnd: 2, + gridColumnEnd: 3, + width: 50, + height: 50, + }) + expect(result).toEqual({}) + }) + it('pinned bottom and right, grid rows and columns fully specified and absolutely positioned in non-absolute grid', async () => { + const result = await testGridOutlines('storyboard/scene/grid-2-outer/grid-2/grid-child-2', { + position: 'absolute', + bottom: 5, + right: 12, + gridRowStart: 2, + gridColumnStart: 3, + gridRowEnd: 2, + gridColumnEnd: 3, + width: 50, + height: 50, + }) + expect(result).toEqual({ + right: { x: 1806.5, y: 530.5, width: 12, height: 1 }, + bottom: { x: 1781.5, y: 555.5, width: 1, height: 5 }, + }) + }) + it('pinned top and left, grid column end set to auto and not absolutely positioned in non-absolute grid', async () => { + const result = await testGridOutlines('storyboard/scene/grid-2-outer/grid-2/grid-child-2', { + top: 5, + left: 12, + gridRowStart: 2, + gridColumnStart: 3, + gridRowEnd: 2, + gridColumnEnd: 'auto', + width: 50, + height: 50, + }) + expect(result).toEqual({}) + }) + it('pinned top and left, grid column end set to auto and absolutely positioned in non-absolute grid', async () => { + const result = await testGridOutlines('storyboard/scene/grid-2-outer/grid-2/grid-child-2', { + position: 'absolute', + top: 5, + left: 12, + gridRowStart: 2, + gridColumnStart: 3, + gridRowEnd: 2, + gridColumnEnd: 'auto', + width: 50, + height: 50, + }) + expect(result).toEqual({ + left: { x: 1418.5, y: 190.5, width: 12, height: 1 }, + top: { x: 1455.5, y: 160.5, width: 1, height: 5 }, + }) + }) + it('pinned top and right, grid column end set to auto and not absolutely positioned in non-absolute grid', async () => { + const result = await testGridOutlines('storyboard/scene/grid-2-outer/grid-2/grid-child-2', { + top: 5, + right: 12, + gridRowStart: 2, + gridColumnStart: 3, + gridRowEnd: 2, + gridColumnEnd: 'auto', + width: 50, + height: 50, + }) + expect(result).toEqual({}) + }) + it('pinned top and right, grid column end set to auto and absolutely positioned in non-absolute grid', async () => { + const result = await testGridOutlines('storyboard/scene/grid-2-outer/grid-2/grid-child-2', { + position: 'absolute', + top: 5, + right: 12, + gridRowStart: 2, + gridColumnStart: 3, + gridRowEnd: 2, + gridColumnEnd: 'auto', + width: 50, + height: 50, + }) + expect(result).toEqual({ + right: { x: 1806.5, y: 190.5, width: 12, height: 1 }, + top: { x: 1781.5, y: 160.5, width: 1, height: 5 }, + }) + }) + it('pinned bottom and left, grid column end set to auto and not absolutely positioned in non-absolute grid', async () => { + const result = await testGridOutlines('storyboard/scene/grid-2-outer/grid-2/grid-child-2', { + bottom: 5, + left: 12, + gridRowStart: 2, + gridColumnStart: 3, + gridRowEnd: 2, + gridColumnEnd: 'auto', + width: 50, + height: 50, + }) + expect(result).toEqual({}) + }) + it('pinned bottom and left, grid column end set to auto and absolutely positioned in non-absolute grid', async () => { + const result = await testGridOutlines('storyboard/scene/grid-2-outer/grid-2/grid-child-2', { + position: 'absolute', + bottom: 5, + left: 12, + gridRowStart: 2, + gridColumnStart: 3, + gridRowEnd: 2, + gridColumnEnd: 'auto', + width: 50, + height: 50, + }) + expect(result).toEqual({ + left: { x: 1418.5, y: 530.5, width: 12, height: 1 }, + bottom: { x: 1455.5, y: 555.5, width: 1, height: 5 }, + }) + }) + it('pinned bottom and right, grid column end set to auto and not absolutely positioned in non-absolute grid', async () => { + const result = await testGridOutlines('storyboard/scene/grid-2-outer/grid-2/grid-child-2', { + bottom: 5, + right: 12, + gridRowStart: 2, + gridColumnStart: 3, + gridRowEnd: 2, + gridColumnEnd: 'auto', + width: 50, + height: 50, + }) + expect(result).toEqual({}) + }) + it('pinned bottom and right, grid column end set to auto and absolutely positioned in non-absolute grid', async () => { + const result = await testGridOutlines('storyboard/scene/grid-2-outer/grid-2/grid-child-2', { + position: 'absolute', + bottom: 5, + right: 12, + gridRowStart: 2, + gridColumnStart: 3, + gridRowEnd: 2, + gridColumnEnd: 'auto', + width: 50, + height: 50, + }) + expect(result).toEqual({ + right: { x: 1806.5, y: 530.5, width: 12, height: 1 }, + bottom: { x: 1781.5, y: 555.5, width: 1, height: 5 }, + }) + }) + + it('pinned top and left, grid rows and columns fully specified and not absolutely positioned, with a row start span', async () => { + const result = await testGridOutlines('storyboard/scene/grid/grid-child', { + top: 5, + left: 12, + gridRowStart: 'span 2', + gridColumnStart: 3, + gridRowEnd: 2, + gridColumnEnd: 3, + width: 50, + height: 50, + }) + expect(result).toEqual({}) + }) + it('pinned top and left, grid rows and columns fully specified and absolutely positioned, with a row start span', async () => { + const result = await testGridOutlines('storyboard/scene/grid/grid-child', { + position: 'absolute', + top: 5, + left: 12, + gridRowStart: 'span 2', + gridColumnStart: 3, + gridRowEnd: 2, + gridColumnEnd: 3, + width: 50, + height: 50, + }) + expect(result).toEqual({ + left: { x: 688.5, y: 190.5, width: 12, height: 1 }, + top: { x: 725.5, y: 160.5, width: 1, height: 5 }, + }) + }) + it('pinned bottom and right, grid rows and columns fully specified and not absolutely positioned, with a row start span', async () => { + const result = await testGridOutlines('storyboard/scene/grid/grid-child', { + bottom: 5, + right: 12, + gridRowStart: 'span 2', + gridColumnStart: 3, + gridRowEnd: 2, + gridColumnEnd: 3, + width: 50, + height: 50, + }) + expect(result).toEqual({}) + }) + it('pinned bottom and right, grid rows and columns fully specified and absolutely positioned, with a row start span', async () => { + const result = await testGridOutlines('storyboard/scene/grid/grid-child', { + position: 'absolute', + bottom: 5, + right: 12, + gridRowStart: 'span 2', + gridColumnStart: 3, + gridRowEnd: 2, + gridColumnEnd: 3, + width: 50, + height: 50, + }) + expect(result).toEqual({ + right: { x: 777.5, y: 240.5, width: 12, height: 1 }, + bottom: { x: 751.5, y: 266.5, width: 1, height: 5 }, + }) + }) + it('pinned top and left, grid rows and columns fully specified and not absolutely positioned, with a row end span', async () => { + const result = await testGridOutlines('storyboard/scene/grid/grid-child', { + top: 5, + left: 12, + gridRowStart: 2, + gridColumnStart: 3, + gridRowEnd: 'span 2', + gridColumnEnd: 3, + width: 50, + height: 50, + }) + expect(result).toEqual({}) + }) + it('pinned top and left, grid rows and columns fully specified and absolutely positioned, with a row end span', async () => { + const result = await testGridOutlines('storyboard/scene/grid/grid-child', { + position: 'absolute', + top: 5, + left: 12, + gridRowStart: 2, + gridColumnStart: 3, + gridRowEnd: 'span 2', + gridColumnEnd: 3, + width: 50, + height: 50, + }) + expect(result).toEqual({ + left: { x: 688.5, y: 315.5, width: 12, height: 1 }, + top: { x: 725.5, y: 285.5, width: 1, height: 5 }, + }) + }) + it('pinned bottom and right, grid rows and columns fully specified and not absolutely positioned, with a row end span', async () => { + const result = await testGridOutlines('storyboard/scene/grid/grid-child', { + bottom: 5, + right: 12, + gridRowStart: 2, + gridColumnStart: 3, + gridRowEnd: 'span 2', + gridColumnEnd: 3, + width: 50, + height: 50, + }) + expect(result).toEqual({}) + }) + it('pinned bottom and right, grid rows and columns fully specified and absolutely positioned, with a row end span', async () => { + const result = await testGridOutlines('storyboard/scene/grid/grid-child', { + position: 'absolute', + bottom: 5, + right: 12, + gridRowStart: 2, + gridColumnStart: 3, + gridRowEnd: 'span 2', + gridColumnEnd: 3, + width: 50, + height: 50, + }) + expect(result).toEqual({ + right: { x: 777.5, y: 470.5, width: 12, height: 1 }, + bottom: { x: 751.5, y: 496.5, width: 1, height: 5 }, + }) + }) +}) diff --git a/editor/src/components/canvas/controls/grid-controls.tsx b/editor/src/components/canvas/controls/grid-controls.tsx index 2a9fbc4a3aa6..4a2475f88264 100644 --- a/editor/src/components/canvas/controls/grid-controls.tsx +++ b/editor/src/components/canvas/controls/grid-controls.tsx @@ -1,34 +1,43 @@ /** @jsxRuntime classic */ /** @jsx jsx */ +/** @jsxFrag React.Fragment */ import { jsx } from '@emotion/react' +import { v4 as UUID } from 'uuid' import type { AnimationControls } from 'framer-motion' import { motion, useAnimationControls } from 'framer-motion' import type { CSSProperties } from 'react' import React from 'react' +import type { Sides } from 'utopia-api/core' import type { ElementPath } from 'utopia-shared/src/types' -import type { GridDimension } from '../../../components/inspector/common/css-utils' -import { - isCSSKeyword, - printGridAutoOrTemplateBase, - printGridCSSNumber, -} from '../../../components/inspector/common/css-utils' +import type { PropsOrJSXAttributes } from '../../../core/model/element-metadata-utils' import { MetadataUtils } from '../../../core/model/element-metadata-utils' -import { mapDropNulls, stripNulls } from '../../../core/shared/array-utils' -import { defaultEither } from '../../../core/shared/either' +import { mapDropNulls, range, stripNulls, uniqBy } from '../../../core/shared/array-utils' +import { defaultEither, eitherToMaybe } from '../../../core/shared/either' import * as EP from '../../../core/shared/element-path' +import type { + ElementInstanceMetadataMap, + GridAutoOrTemplateDimensions, + GridContainerProperties, + GridPositionOrSpan, +} from '../../../core/shared/element-template' import { isGridAutoOrTemplateDimensions, + isGridPositionValue, + isGridSpan, type GridAutoOrTemplateBase, } from '../../../core/shared/element-template' -import type { CanvasPoint, CanvasRectangle } from '../../../core/shared/math-utils' +import type { CanvasPoint, CanvasRectangle, LocalRectangle } from '../../../core/shared/math-utils' import { + boundingRectangleArray, canvasPoint, + clamp, isFiniteRectangle, isInfinityRectangle, + nullIfInfinity, + offsetPoint, pointsEqual, scaleRect, windowPoint, - zeroRectangle, zeroRectIfNullOrInfinity, } from '../../../core/shared/math-utils' import { @@ -39,81 +48,97 @@ import { } from '../../../core/shared/optics/optic-creators' import { toFirst } from '../../../core/shared/optics/optic-utilities' import type { Optic } from '../../../core/shared/optics/optics' -import { assertNever } from '../../../core/shared/utils' +import { optionalMap } from '../../../core/shared/optional-utils' +import { assertNever, NO_OP } from '../../../core/shared/utils' import { Modifier } from '../../../utils/modifiers' import { when } from '../../../utils/react-conditionals' +import type { UtopiColor } from '../../../uuiui' +import { useColorTheme, UtopiaStyles } from '../../../uuiui' import { useDispatch } from '../../editor/store/dispatch-context' -import { Substores, useEditorState, useRefEditorState } from '../../editor/store/store-hook' -import { useRollYourOwnFeatures } from '../../navigator/left-pane/roll-your-own-pane' +import { + EditorStateContext, + HelperControlsStateContext, + Substores, + useEditorState, + useRefEditorState, +} from '../../editor/store/store-hook' +import type { GridDimension, GridDiscreteDimension } from '../../inspector/common/css-utils' +import { + isCSSKeyword, + isDynamicGridRepeat, + isGridCSSRepeat, + printCSSNumberWithDefaultUnit, + printGridCSSNumber, +} from '../../inspector/common/css-utils' import CanvasActions from '../canvas-actions' -import { controlForStrategyMemoized } from '../canvas-strategies/canvas-strategy-types' -import type { - GridResizeEdge, - GridResizeEdgeProperties, -} from '../canvas-strategies/interaction-state' +import type { GridResizeEdge } from '../canvas-strategies/interaction-state' import { - GridResizeEdges, createInteractionViaMouse, gridAxisHandle, gridCellHandle, - gridResizeEdgeProperties, gridResizeHandle, + gridResizeRulerHandle, } from '../canvas-strategies/interaction-state' -import { windowToCanvasCoordinates } from '../dom-lookup' -import { CanvasOffsetWrapper } from './canvas-offset-wrapper' -import { useColorTheme, UtopiaStyles } from '../../../uuiui' -import { gridCellTargetId } from '../canvas-strategies/strategies/grid-helpers' +import type { GridCellCoordinates } from '../canvas-strategies/strategies/grid-cell-bounds' +import { gridCellTargetId } from '../canvas-strategies/strategies/grid-cell-bounds' +import type { GridCellGlobalFrames } from '../canvas-strategies/strategies/grid-helpers' +import { + getGlobalFrameOfGridCellFromMetadata, + getGridRelatedIndexes, + getGridElementPinState, + printPin, + getGridIdentifierContainerOrComponentPath, + gridIdentifierToString, + gridIdentifiersSimilar, + findOriginalGrid, + isAutoGridPin, +} from '../canvas-strategies/strategies/grid-helpers' +import { canResizeGridTemplate } from '../canvas-strategies/strategies/resize-grid-strategy' import { resizeBoundingBoxFromSide } from '../canvas-strategies/strategies/resize-helpers' import type { EdgePosition } from '../canvas-types' -import { - CSSCursor, - EdgePositionBottom, - EdgePositionLeft, - EdgePositionRight, - EdgePositionTop, -} from '../canvas-types' -import { useCanvasAnimation } from '../ui-jsx-canvas-renderer/animation-context' -import { CanvasLabel } from './select-mode/controls-common' -import { optionalMap } from '../../../core/shared/optional-utils' -import type { Sides } from 'utopia-api/core' +import { CSSCursor } from '../canvas-types' +import { windowToCanvasCoordinates } from '../dom-lookup' import type { Axis } from '../gap-utils' +import { useCanvasAnimation } from '../ui-jsx-canvas-renderer/animation-context' +import { CanvasOffsetWrapper } from './canvas-offset-wrapper' +import type { GridControlsProps } from './grid-controls-for-strategies' +import { + edgePositionToGridResizeEdge, + GridCellTestId, + GridControlKey, + gridEdgeToEdgePosition, + GridElementChildContainingBlockKey, + GridElementContainingBlockKey, + GridMeasurementHelperKey, + GridMeasurementHelperMap, + GridMeasurementHelpersKey, + useGridData, +} from './grid-controls-for-strategies' import { useMaybeHighlightElement } from './select-mode/select-mode-hooks' +import { useResizeEdges } from './select-mode/use-resize-edges' +import { getGridHelperStyleMatchingTargetGrid } from './grid-controls-helpers' +import { isFillOrStretchModeAppliedOnSpecificSide } from '../../inspector/inspector-common' +import type { PinOutlineProps } from './position-outline' +import { PinOutline, usePropsOrJSXAttributes } from './position-outline' +import { getLayoutProperty } from '../../../core/layout/getLayoutProperty' +import { styleStringInArray } from '../../../utils/common-constants' +import { gridContainerIdentifier, type GridIdentifier } from '../../editor/store/editor-state' +import type { RulerMarkerType } from './grid-controls-ruler-markers' +import { rulerMarkerIcons } from './grid-controls-ruler-markers' +import type { GridData, GridMeasurementHelperData } from './grid-measurements' +import { + calculateGridCellRectangle, + getGridElementMeasurementHelperData, + useGridElementMeasurementHelperData, + useGridMeasurementHelperData, +} from './grid-measurements' +import type { Property } from 'csstype' +import { isFeatureEnabled } from '../../../utils/feature-switches' +import type { ThemeObject } from '../../../uuiui/styles/theme/theme-helpers' +import { atom, useAtom } from 'jotai' const CELL_ANIMATION_DURATION = 0.15 // seconds -export const GridCellTestId = (elementPath: ElementPath) => `grid-cell-${EP.toString(elementPath)}` - -export type GridCellCoordinates = { row: number; column: number } - -export function gridCellCoordinates(row: number, column: number): GridCellCoordinates { - return { row: row, column: column } -} - -function getCellsCount(template: GridAutoOrTemplateBase | null): number { - if (template == null) { - return 0 - } - - switch (template.type) { - case 'DIMENSIONS': - return template.dimensions.length - case 'FALLBACK': - return 0 - default: - assertNever(template) - } -} - -export function getNullableAutoOrTemplateBaseString( - template: GridAutoOrTemplateBase | null, -): string | undefined { - if (template == null) { - return undefined - } else { - return printGridAutoOrTemplateBase(template) - } -} - function getFromPropsOptic(index: number): Optic { return notNull() .compose(fromTypeGuard(isGridAutoOrTemplateDimensions)) @@ -128,26 +153,32 @@ function gridCSSNumberToLabel(gridCSSNumber: GridDimension): string { function getLabelForAxis( fromDOM: GridDimension, index: number, - fromProps: GridAutoOrTemplateBase | null, + fromProps: GridAutoOrTemplateDimensions | null, ): string { const fromPropsAtIndex = toFirst(getFromPropsOptic(index), fromProps) return gridCSSNumberToLabel(defaultEither(fromDOM, fromPropsAtIndex)) } -const SHADOW_SNAP_ANIMATION = 'shadow-snap' -const GRID_RESIZE_HANDLE_CONTAINER_SIZE = 30 // px const GRID_RESIZE_HANDLE_SIZE = 15 // px -export interface GridResizingControlProps { +const forceShowGridPlaceholdersAtom = atom('not-visible') + +interface GridResizingControlProps { dimension: GridDimension dimensionIndex: number axis: Axis containingFrame: CanvasRectangle - fromPropsAxisValues: GridAutoOrTemplateBase | null - padding: number | null + fromPropsAxisValues: GridAutoOrTemplateDimensions | null + padding: number + resizing: 'resize-target' | 'resize-generated' | 'not-resizing' + setResizingIndex: (v: number | null) => void + resizeLocked: boolean + stripedAreaLength: number | null } -export const GridResizingControl = React.memo((props: GridResizingControlProps) => { +const GridTrackSizeLabel = React.memo((props: GridResizingControlProps) => { + const { setResizingIndex } = props + const canvasOffset = useEditorState( Substores.canvasOffset, (store) => store.editor.canvas.roundedCanvasOffset, @@ -161,12 +192,24 @@ export const GridResizingControl = React.memo((props: GridResizingControlProps) const dispatch = useDispatch() const colorTheme = useColorTheme() - const [resizing, setResizing] = React.useState(false) + const canResize = React.useMemo(() => { + return ( + props.fromPropsAxisValues?.type === 'DIMENSIONS' && + props.fromPropsAxisValues.dimensions.length > 0 + ) + }, [props.fromPropsAxisValues]) const mouseDownHandler = React.useCallback( (event: React.MouseEvent): void => { + event.stopPropagation() + event.preventDefault() + + if (!canResize) { + return + } + function mouseUpHandler() { - setResizing(false) + setResizingIndex(null) window.removeEventListener('mouseup', mouseUpHandler) } window.addEventListener('mouseup', mouseUpHandler) @@ -176,7 +219,7 @@ export const GridResizingControl = React.memo((props: GridResizingControlProps) canvasOffset, windowPoint({ x: event.nativeEvent.x, y: event.nativeEvent.y }), ) - setResizing(true) + setResizingIndex(props.dimensionIndex) dispatch([ CanvasActions.createInteractionSession( @@ -188,10 +231,8 @@ export const GridResizingControl = React.memo((props: GridResizingControlProps) ), ), ]) - event.stopPropagation() - event.preventDefault() }, - [canvasOffset, dispatch, props.axis, props.dimensionIndex, scale], + [canvasOffset, dispatch, props.axis, props.dimensionIndex, scale, setResizingIndex, canResize], ) const { maybeClearHighlightsOnHoverEnd } = useMaybeHighlightElement() @@ -204,14 +245,22 @@ export const GridResizingControl = React.memo((props: GridResizingControlProps) [maybeClearHighlightsOnHoverEnd], ) - const labelId = `grid-${props.axis}-handle-${props.dimensionIndex}` + const labelId = `grid-track-size-${props.axis}-handle-${props.dimensionIndex}` const containerId = `${labelId}-container` - const shadowSize = React.useMemo(() => { - return props.axis === 'column' - ? props.containingFrame.height + GRID_RESIZE_HANDLE_CONTAINER_SIZE - : props.containingFrame.width + GRID_RESIZE_HANDLE_CONTAINER_SIZE - }, [props.containingFrame, props.axis]) + const cssProp = React.useMemo( + () => ({ + backgroundColor: + props.resizing === 'resize-target' + ? colorTheme.primary.value + : colorTheme.primarySubdued.value, + '&:hover': { + zIndex: 1, + backgroundColor: colorTheme.primary.value, + }, + }), + [props.resizing, colorTheme], + ) return (
- {props.axis === 'row' ? '↕' : '↔'} - {when( - props.dimension.areaName != null, - {props.dimension.areaName}, - )} + {getLabelForAxis(props.dimension, props.dimensionIndex, props.fromPropsAxisValues)}
+
+ ) +}) +GridTrackSizeLabel.displayName = 'GridTrackSizeLabel' + +const GridResizingStripedIndicator = React.memo((props: GridResizingControlProps) => { + const scale = useEditorState( + Substores.canvas, + (store) => store.editor.canvas.scale, + 'GridResizingControl scale', + ) + const colorTheme = useColorTheme() + + const labelId = `grid-resizing-${props.axis}-handle-${props.dimensionIndex}` + const containerId = `${labelId}-container` + + return ( +
{when( - resizing, + props.resizing !== 'not-resizing',
- + {when( + props.dimension.lineName != null, +
+ {props.dimension.lineName} +
, + )}
, )}
) }) -GridResizingControl.displayName = 'GridResizingControl' +GridResizingStripedIndicator.displayName = 'GridResizingStripedIndicator' -export interface GridResizingProps { +interface GridResizingProps { + targetGrid: GridMeasurementHelperData axisValues: GridAutoOrTemplateBase | null fromPropsAxisValues: GridAutoOrTemplateBase | null + stripedAreaLength: number | null containingFrame: CanvasRectangle axis: Axis gap: number | null padding: Sides | null + justifyContent: string | undefined + alignContent: string | undefined } -export const GridResizing = React.memo((props: GridResizingProps) => { - const canvasScale = useEditorState( - Substores.canvasOffset, - (store) => store.editor.canvas.scale, - 'GridResizing canvasScale', - ) +const GridResizing = React.memo((props: GridResizingProps) => { + const fromProps = React.useMemo((): GridAutoOrTemplateDimensions | null => { + if (props.fromPropsAxisValues?.type !== 'DIMENSIONS') { + return null + } + if (!canResizeGridTemplate(props.fromPropsAxisValues)) { + return null + } + return { + type: 'DIMENSIONS', + dimensions: props.fromPropsAxisValues.dimensions.reduce( + (acc, cur): GridDiscreteDimension[] => { + if (isGridCSSRepeat(cur)) { + if (isDynamicGridRepeat(cur)) { + return acc + } + let expanded: GridDiscreteDimension[] = [] + for (let i = 0; i < cur.times; i++) { + expanded.push(...cur.value.filter((v) => v.type !== 'REPEAT')) + } + return acc.concat(...expanded) + } else { + return acc.concat(cur) + } + }, + [] as GridDiscreteDimension[], + ), + } + }, [props.fromPropsAxisValues]) + + const resizeLocked = React.useMemo(() => { + return fromProps == null || !canResizeGridTemplate(fromProps) + }, [fromProps]) + + const [resizingIndex, setResizingIndex] = React.useState(null) + + // These are the indexes of the elements that will resize too alongside the one at the index of + // `resizingIndex`. + const coresizingIndexes: number[] = React.useMemo(() => { + if (props.fromPropsAxisValues?.type !== 'DIMENSIONS' || resizingIndex == null) { + return [] + } + return getGridRelatedIndexes({ + template: props.fromPropsAxisValues.dimensions, + index: resizingIndex, + }) + }, [props.fromPropsAxisValues, resizingIndex]) + if (props.axisValues == null) { return null } switch (props.axisValues.type) { case 'DIMENSIONS': - const size = GRID_RESIZE_HANDLE_CONTAINER_SIZE / canvasScale + const dimensions = props.axisValues.dimensions + + const helperGridBaseStyle: React.CSSProperties = getGridHelperStyleMatchingTargetGrid( + props.targetGrid, + ) + + const helperGridStyle: React.CSSProperties = { + ...helperGridBaseStyle, + gridTemplateRows: props.axis === 'row' ? helperGridBaseStyle.gridTemplateRows : '1fr', + gridTemplateColumns: + props.axis === 'column' ? helperGridBaseStyle.gridTemplateColumns : '1fr', + } + return ( -
printGridCSSNumber(dim)).join(' ') - : undefined, - gridTemplateRows: - props.axis === 'row' - ? props.axisValues.dimensions.map((dim) => printGridCSSNumber(dim)).join(' ') - : undefined, - gap: props.gap ?? 0, - paddingLeft: - props.axis === 'column' && props.padding != null - ? `${props.padding.left}px` - : undefined, - paddingTop: - props.axis === 'row' && props.padding != null ? `${props.padding.top}px` : undefined, - }} - > - {props.axisValues.dimensions.flatMap((dimension, dimensionIndex) => { - return ( - - ) - })} -
+ +
+ {dimensions.flatMap((dimension, dimensionIndex) => { + return ( + + ) + })} +
+
+ {dimensions.flatMap((dimension, dimensionIndex) => { + return ( + + ) + })} +
+
) case 'FALLBACK': return null @@ -370,139 +529,157 @@ export const GridResizing = React.memo((props: GridResizingProps) => { }) GridResizing.displayName = 'GridResizing' -export type GridData = { - elementPath: ElementPath - frame: CanvasRectangle - gridTemplateColumns: GridAutoOrTemplateBase | null - gridTemplateRows: GridAutoOrTemplateBase | null - gridTemplateColumnsFromProps: GridAutoOrTemplateBase | null - gridTemplateRowsFromProps: GridAutoOrTemplateBase | null - gap: number | null - rowGap: number | null - columnGap: number | null - padding: Sides - rows: number - columns: number - cells: number +interface GridRowColumnResizingControlsProps { + target: GridIdentifier } -export function useGridData(elementPaths: ElementPath[]): GridData[] { - const grids = useEditorState( - Substores.metadata, - (store) => { - return mapDropNulls((view) => { - const element = MetadataUtils.findElementByElementPath(store.editor.jsxMetadata, view) - - const targetGridContainer = MetadataUtils.isGridLayoutedContainer(element) ? element : null - if ( - targetGridContainer == null || - targetGridContainer.globalFrame == null || - !isFiniteRectangle(targetGridContainer.globalFrame) - ) { - return null - } +export const GridRowColumnResizingControlsComponent = ({ + target, +}: GridRowColumnResizingControlsProps) => { + const grids = useGridData([target]) - const gap = targetGridContainer.specialSizeMeasurements.gap - const rowGap = targetGridContainer.specialSizeMeasurements.rowGap - const columnGap = targetGridContainer.specialSizeMeasurements.columnGap - const padding = targetGridContainer.specialSizeMeasurements.padding - - const gridTemplateColumns = - targetGridContainer.specialSizeMeasurements.containerGridProperties.gridTemplateColumns - const gridTemplateRows = - targetGridContainer.specialSizeMeasurements.containerGridProperties.gridTemplateRows - const gridTemplateColumnsFromProps = - targetGridContainer.specialSizeMeasurements.containerGridPropertiesFromProps - .gridTemplateColumns - const gridTemplateRowsFromProps = - targetGridContainer.specialSizeMeasurements.containerGridPropertiesFromProps - .gridTemplateRows - - const columns = getCellsCount( - targetGridContainer.specialSizeMeasurements.containerGridProperties.gridTemplateColumns, - ) - const rows = getCellsCount( - targetGridContainer.specialSizeMeasurements.containerGridProperties.gridTemplateRows, - ) + function getStripedAreaLength( + template: GridAutoOrTemplateBase | null, + gap: number, + ): number | null { + if (template?.type !== 'DIMENSIONS') { + return null + } + const fromDimensions = template.dimensions.reduce((acc, curr, index) => { + if (curr.type === 'NUMBER') { + return acc + curr.value.value + (index > 0 ? gap : 0) + } + return acc + }, 0) + if (fromDimensions <= 0) { + return null + } + return fromDimensions + } - return { - elementPath: targetGridContainer.elementPath, - frame: targetGridContainer.globalFrame, - gridTemplateColumns: gridTemplateColumns, - gridTemplateRows: gridTemplateRows, - gridTemplateColumnsFromProps: gridTemplateColumnsFromProps, - gridTemplateRowsFromProps: gridTemplateRowsFromProps, - gap: gap, - rowGap: rowGap, - columnGap: columnGap, - padding: padding, - rows: rows, - columns: columns, - cells: rows * columns, - } - }, elementPaths) - }, - 'useGridData', + const scale = useEditorState( + Substores.canvas, + (store) => store.editor.canvas.scale, + 'GridRowColumnResizingControls scale', ) - return grids -} + const selectedGridItems = useSelectedGridItems() -interface GridRowColumnResizingControlsProps { - target: ElementPath -} + const gridsWithVisibleResizeControls = React.useMemo(() => { + return grids.filter((grid) => { + if ( + grid.gridTemplateColumns?.type !== 'DIMENSIONS' || + grid.gridTemplateRows?.type !== 'DIMENSIONS' + ) { + return false + } -export const GridRowColumnResizingControls = - controlForStrategyMemoized(({ target }) => { - const grids = useGridData([target]) + // returns whether the rendered dimensions are too crowded, as in there are two cols/rows that are closer than the handle sizes + function tooCrowded(dimensions: GridDimension[]): boolean { + const visualSizes = dimensions.map( + (dim) => (dim.type === 'NUMBER' ? dim.value.value : 0) * scale, + ) + return visualSizes.some((dim, index) => { + if (index < visualSizes.length - 1) { + const next = visualSizes[index + 1] + if (dim + next < GRID_RESIZE_HANDLE_SIZE * 2) { + return true + } + } + return false + }) + } - return ( - - {grids.flatMap((grid) => { - return ( - - ) - })} - {grids.flatMap((grid) => { - return ( - - ) - })} - - ) - }) + return ( + !tooCrowded(grid.gridTemplateColumns.dimensions) && + !tooCrowded(grid.gridTemplateRows.dimensions) + ) + }) + }, [scale, grids]) + + return ( + + {gridsWithVisibleResizeControls.flatMap((grid) => { + if (selectedGridItems.length > 0) { + return null + } + return ( + + ) + })} + {gridsWithVisibleResizeControls.flatMap((grid) => { + if (selectedGridItems.length > 0) { + return null + } + return ( + + ) + })} + + ) +} -export const GridControlsKey = (gridPath: ElementPath) => `grid-controls-${EP.toString(gridPath)}` +type GridControlVisibility = 'all' | 'not-visible' -export interface GridControlsProps { - targets: ElementPath[] +interface GridControlProps { + grid: GridData + controlsVisible: GridControlVisibility } -export const GridControls = controlForStrategyMemoized(({ targets }) => { +const GridControl = React.memo(({ grid, controlsVisible }) => { const dispatch = useDispatch() const controls = useAnimationControls() const colorTheme = useColorTheme() - const features = useRollYourOwnFeatures() - const canvasOffsetRef = useRefEditorState((store) => store.editor.canvas.roundedCanvasOffset) - const scaleRef = useRefEditorState((store) => store.editor.canvas.scale) - const metadataRef = useRefEditorState((store) => store.editor.jsxMetadata) + const editorMetadata = useEditorState( + Substores.metadata, + (store) => store.editor.jsxMetadata, + 'GridControl editorMetadata', + ) + + const interactionLatestMetadata = useEditorState( + Substores.canvas, + (store) => + store.editor.canvas.interactionSession?.interactionData.type === 'DRAG' + ? store.editor.canvas.interactionSession.latestMetadata + : null, + 'GridControl interactionLatestMetadata', + ) + + const jsxMetadata = React.useMemo( + () => interactionLatestMetadata ?? editorMetadata, + [interactionLatestMetadata, editorMetadata], + ) const activelyDraggingOrResizingCell = useEditorState( Substores.canvas, @@ -512,157 +689,71 @@ export const GridControls = controlForStrategyMemoized(({ tar store.editor.canvas.interactionSession?.interactionData.type === 'DRAG' && store.editor.canvas.interactionSession?.interactionData.modifiers.cmd !== true && store.editor.canvas.interactionSession?.interactionData.drag != null - ? store.editor.canvas.interactionSession.activeControl.id + ? EP.toUid(store.editor.canvas.interactionSession.activeControl.path) : null, - 'GridControls activelyDraggingOrResizingCell', - ) - - const { hoveringStart, mouseCanvasPosition } = useMouseMove(activelyDraggingOrResizingCell) - - const targetRootCell = useEditorState( - Substores.restOfStore, - (store) => store.strategyState.customStrategyState.grid.currentRootCell, - 'GridControls targetRootCell', + 'GridControl activelyDraggingOrResizingCell', ) const currentHoveredCell = useEditorState( - Substores.restOfStore, - (store) => - store.strategyState.customStrategyState.grid.targetCellData?.gridCellCoordinates ?? null, - 'GridControls currentHoveredCell', + Substores.canvas, + (store) => store.editor.canvas.controls.gridControlData?.targetCell ?? null, + 'GridControl currentHoveredCell', ) - const dragging = useEditorState( + const currentHoveredGrid = useEditorState( Substores.canvas, + (store) => store.editor.canvas.controls.gridControlData?.grid ?? null, + 'GridControl currentHoveredGrid', + ) + + const targetsAreCellsWithPositioning = useEditorState( + Substores.metadata, (store) => - store.editor.canvas.interactionSession != null && - store.editor.canvas.interactionSession.activeControl.type === 'GRID_CELL_HANDLE' - ? store.editor.canvas.interactionSession.activeControl.id - : null, - 'GridControls dragging', + store.editor.selectedViews.every((elementPath) => + MetadataUtils.isGridItemWithPositioning(store.editor.jsxMetadata, elementPath), + ), + 'GridControl targetsAreCellsWithPositioning', ) - const interactionData = useEditorState( - Substores.canvas, + const anyTargetAbsolute = useEditorState( + Substores.metadata, (store) => - store.editor.canvas.interactionSession?.interactionData.type === 'DRAG' - ? store.editor.canvas.interactionSession.interactionData - : null, - 'GridControls interactionData', + store.editor.selectedViews.some((elementPath) => + MetadataUtils.isPositionAbsolute( + MetadataUtils.findElementByElementPath(store.editor.jsxMetadata, elementPath), + ), + ), + 'GridControl anyTargetAbsolute', ) - const interactionLatestMetadata = useEditorState( + const scale = useEditorState( Substores.canvas, - (store) => - store.editor.canvas.interactionSession?.interactionData.type === 'DRAG' - ? store.editor.canvas.interactionSession.latestMetadata - : null, - 'GridControls interactionLatestMetadata', + (store) => store.editor.canvas.scale, + 'GridControl scale', ) - const editorMetadata = useEditorState( + const anyTargetNotPinned = useEditorState( Substores.metadata, - (store) => store.editor.jsxMetadata, - 'GridControls editorMetadata', + (store) => + store.editor.selectedViews.some((elementPath) => { + return ( + getGridElementPinState( + getGridElementMeasurementHelperData(elementPath, scale)?.gridElementProperties ?? null, + ) !== 'pinned' + ) + }), + 'GridControl anyTargetNotPinned', ) - const jsxMetadata = React.useMemo( - () => interactionLatestMetadata ?? editorMetadata, - [interactionLatestMetadata, editorMetadata], - ) - - const hoveredGrids = useEditorState( - Substores.canvas, - (store) => stripNulls([store.editor.canvas.controls.gridControls]), - 'FlexReparentTargetIndicator lines', - ) - - const grids = useGridData([...targets, ...hoveredGrids]) - - const cells = React.useMemo(() => { - return grids.flatMap((grid) => { - const children = MetadataUtils.getChildrenUnordered(jsxMetadata, grid.elementPath) - return mapDropNulls((cell, index) => { - if (cell == null || cell.globalFrame == null || !isFiniteRectangle(cell.globalFrame)) { - return null - } - const countedRow = Math.floor(index / grid.columns) + 1 - const countedColumn = Math.floor(index % grid.columns) + 1 - - const columnFromProps = cell.specialSizeMeasurements.elementGridProperties.gridColumnStart - const rowFromProps = cell.specialSizeMeasurements.elementGridProperties.gridRowStart - return { - elementPath: cell.elementPath, - globalFrame: cell.globalFrame, - borderRadius: cell.specialSizeMeasurements.borderRadius, - column: - columnFromProps == null - ? countedColumn - : isCSSKeyword(columnFromProps) - ? countedColumn - : columnFromProps.numericalPosition ?? countedColumn, - row: - rowFromProps == null - ? countedRow - : isCSSKeyword(rowFromProps) - ? countedRow - : rowFromProps.numericalPosition ?? countedRow, - index: index, - } - }, children) - }) - }, [grids, jsxMetadata]) - - const shadow = React.useMemo(() => { - return cells.find((cell) => EP.toUid(cell.elementPath) === dragging) ?? null - }, [cells, dragging]) - - const [initialShadowFrame, setInitialShadowFrame] = React.useState( - shadow?.globalFrame ?? null, - ) - - const anyTargetAbsolute = useEditorState( - Substores.metadata, - (store) => - store.editor.selectedViews.some((elementPath) => - MetadataUtils.isPositionAbsolute( - MetadataUtils.findElementByElementPath(store.editor.jsxMetadata, elementPath), - ), - ), - 'GridControls anyTargetAbsolute', - ) - - const gridPath = optionalMap(EP.parentPath, shadow?.elementPath) - - const gridFrame = React.useMemo(() => { - if (gridPath == null) { - return zeroRectangle - } - const maybeGridFrame = MetadataUtils.findElementByElementPath( - metadataRef.current, - gridPath, - )?.globalFrame - if (maybeGridFrame == null || !isFiniteRectangle(maybeGridFrame)) { - return zeroRectangle - } - return maybeGridFrame - }, [gridPath, metadataRef]) - - useSnapAnimation({ - disabled: anyTargetAbsolute, - targetRootCell: targetRootCell, - controls: controls, - shadowFrame: initialShadowFrame, - gridPath: gridPath, - }) + const canvasOffsetRef = useRefEditorState((store) => store.editor.canvas.roundedCanvasOffset) const startInteractionWithUid = React.useCallback( - (params: { uid: string; row: number; column: number; frame: CanvasRectangle }) => + (params: { path: ElementPath; row: number; column: number; frame: CanvasRectangle }) => (event: React.MouseEvent) => { setInitialShadowFrame(params.frame) const start = windowToCanvasCoordinates( - scaleRef.current, + scale, canvasOffsetRef.current, windowPoint({ x: event.nativeEvent.x, y: event.nativeEvent.y }), ) @@ -672,15 +763,93 @@ export const GridControls = controlForStrategyMemoized(({ tar createInteractionViaMouse( start.canvasPositionRounded, Modifier.modifiersForEvent(event), - gridCellHandle({ id: params.uid }), + gridCellHandle({ path: params.path }), 'zero-drag-not-permitted', ), ), ]) }, - [canvasOffsetRef, dispatch, scaleRef], + [canvasOffsetRef, dispatch, scale], + ) + + const cells = React.useMemo(() => { + const children = (() => { + switch (grid.identifier.type) { + case 'GRID_CONTAINER': + return MetadataUtils.getChildrenUnordered(jsxMetadata, grid.identifier.container) + case 'GRID_ITEM': + return MetadataUtils.getSiblingsUnordered(jsxMetadata, grid.identifier.item) + default: + assertNever(grid.identifier) + } + })() + return mapDropNulls((cell, index) => { + if (cell == null || cell.globalFrame == null || !isFiniteRectangle(cell.globalFrame)) { + return null + } + const countedRow = Math.floor(index / grid.columns) + 1 + const countedColumn = Math.floor(index % grid.columns) + 1 + + const gridElementData = getGridElementMeasurementHelperData(cell.elementPath, scale) + if (gridElementData == null) { + return null + } + const columnFromProps = gridElementData.computedStyling.gridColumnStart + const rowFromProps = gridElementData.computedStyling.gridRowStart + + function getAxisValue( + value: Property.GridColumnStart | Property.GridRowStart | undefined, + counted: number, + ): number { + if (value == null || typeof value !== 'number') { + return counted + } + return value + } + return { + elementPath: cell.elementPath, + globalFrame: gridElementData.frame, + borderRadius: gridElementData.computedStyling.borderRadius, + column: getAxisValue(columnFromProps, countedColumn), + row: getAxisValue(rowFromProps, countedRow), + index: index, + } + }, children) + }, [grid.columns, grid.identifier, jsxMetadata, scale]) + + const dragging = useEditorState( + Substores.canvas, + (store) => + store.editor.canvas.interactionSession != null && + store.editor.canvas.interactionSession.activeControl.type === 'GRID_CELL_HANDLE' + ? store.editor.canvas.interactionSession.activeControl.path + : null, + 'GridControl dragging', + ) + + const shadow = React.useMemo(() => { + return ( + cells.find( + (cell) => EP.toUid(cell.elementPath) === (dragging == null ? null : EP.toUid(dragging)), + ) ?? null + ) + }, [cells, dragging]) + + const [initialShadowFrame, setInitialShadowFrame] = React.useState( + shadow?.globalFrame ?? null, + ) + + const interactionData = useEditorState( + Substores.canvas, + (store) => + store.editor.canvas.interactionSession?.interactionData.type === 'DRAG' + ? store.editor.canvas.interactionSession.interactionData + : null, + 'GridControl interactionData', ) + const { hoveringStart } = useMouseMove(activelyDraggingOrResizingCell) + // NOTE: this stuff is meant to be temporary, until we settle on the set of interaction pieces we like. // After that, we should get rid of this. const shadowPosition = React.useMemo(() => { @@ -698,21 +867,13 @@ export const GridControls = controlForStrategyMemoized(({ tar } const getCoord = (axis: 'x' | 'y', dimension: 'width' | 'height') => { - if (features.Grid.dragVerbatim) { - return initialShadowFrame[axis] + drag[axis] - } else if (features.Grid.dragMagnetic) { - return shadow.globalFrame[axis] + (mouseCanvasPosition[axis] - hoveringStart.point[axis]) - } else if (features.Grid.dragRatio) { - return ( - shadow.globalFrame[axis] + - drag[axis] - - (shadow.globalFrame[axis] - dragStart[axis]) - - shadow.globalFrame[dimension] * - ((dragStart[axis] - initialShadowFrame[axis]) / initialShadowFrame[dimension]) - ) - } else { - return undefined - } + return ( + shadow.globalFrame[axis] + + drag[axis] - + (shadow.globalFrame[axis] - dragStart[axis]) - + shadow.globalFrame[dimension] * + ((dragStart[axis] - initialShadowFrame[axis]) / initialShadowFrame[dimension]) + ) } // make sure the shadow is displayed only inside the grid container bounds @@ -723,649 +884,2150 @@ export const GridControls = controlForStrategyMemoized(({ tar return { x: wrapCoord( getCoord('x', 'width') ?? 0, - gridFrame.x, - gridFrame.x + gridFrame.width, + grid.frame.x, + grid.frame.x + grid.frame.width, shadow.globalFrame.width, ), y: wrapCoord( getCoord('y', 'height') ?? 0, - gridFrame.y, - gridFrame.y + gridFrame.height, + grid.frame.y, + grid.frame.y + grid.frame.height, shadow.globalFrame.height, ), } }, [ - features, - initialShadowFrame, interactionData, - shadow, + initialShadowFrame, hoveringStart, - mouseCanvasPosition, - gridFrame, + shadow, + grid.frame.x, + grid.frame.width, + grid.frame.y, + grid.frame.height, ]) - if (grids.length === 0) { - return null + const gridPath = optionalMap(EP.parentPath, shadow?.elementPath) + + const targetRootCell = useEditorState( + Substores.canvas, + (store) => store.editor.canvas.controls.gridControlData?.rootCell ?? null, + 'GridControl targetRootCell', + ) + + useCellAnimation({ + disabled: anyTargetAbsolute || anyTargetNotPinned, + targetRootCell: targetRootCell, + controls: controls, + shadowFrame: initialShadowFrame, + gridPath: gridPath, + }) + + const placeholders = controlsVisible !== 'not-visible' ? range(0, grid.cells) : [] + const baseStyle = getGridHelperStyleMatchingTargetGrid(grid) + const style = { + ...baseStyle, + backgroundColor: + activelyDraggingOrResizingCell == null || controlsVisible === 'not-visible' + ? 'transparent' + : colorTheme.primary10.value, + outline: `1px solid ${ + activelyDraggingOrResizingCell == null || controlsVisible === 'not-visible' + ? 'transparent' + : colorTheme.primary.value + }`, } + const targetRootCellIsValidTarget = useEditorState( + Substores.canvas, + (store) => store.editor.canvas.controls.gridControlData?.rootCell != null, + 'GridControl targetRootCellIsValidTarget', + ) + + const dontShowActiveCellHighlight = + (!targetsAreCellsWithPositioning && anyTargetAbsolute) || + (anyTargetNotPinned && !targetRootCellIsValidTarget) + + const gridContainerOrComponentPath = getGridIdentifierContainerOrComponentPath(grid.identifier) return ( - - {/* grid lines */} - {grids.map((grid) => { - const placeholders = Array.from(Array(grid.cells).keys()) - let style: CSSProperties = { - position: 'absolute', - top: grid.frame.y - 1, // account for border! - left: grid.frame.x - 1, // account for border! - width: grid.frame.width, - height: grid.frame.height, - display: 'grid', - gridTemplateColumns: getNullableAutoOrTemplateBaseString(grid.gridTemplateColumns), - gridTemplateRows: getNullableAutoOrTemplateBaseString(grid.gridTemplateRows), - backgroundColor: - activelyDraggingOrResizingCell != null - ? features.Grid.activeGridBackground - : 'transparent', - border: `1px solid ${ - activelyDraggingOrResizingCell != null ? colorTheme.primary.value : 'transparent' - }`, - pointerEvents: 'none', - padding: - grid.padding == null - ? 0 - : `${grid.padding.top}px ${grid.padding.right}px ${grid.padding.bottom}px ${grid.padding.left}px`, - } - - // Gap needs to be set only if the other two are not present or we'll have rendering issues - // due to how measurements are calculated. - if (grid.rowGap != null && grid.columnGap != null) { - style.rowGap = grid.rowGap - style.columnGap = grid.columnGap - } else { - if (grid.gap != null) { - style.gap = grid.gap - } - if (grid.rowGap != null) { - style.rowGap = grid.rowGap - } - if (grid.columnGap != null) { - style.columnGap = grid.columnGap - } - } + {/* grid lines */} +
+ {placeholders.map((cell) => { + const countedRow = Math.floor(cell / grid.columns) + 1 + const countedColumn = Math.floor(cell % grid.columns) + 1 + const id = gridCellTargetId(gridContainerOrComponentPath, countedRow, countedColumn) + const borderID = `${id}-border` + + const isActiveGrid = + (dragging != null && EP.isParentOf(gridContainerOrComponentPath, dragging)) || + (currentHoveredGrid != null && + gridIdentifiersSimilar(grid.identifier, currentHoveredGrid)) + const isActiveCell = + isActiveGrid && + countedColumn === currentHoveredCell?.column && + countedRow === currentHoveredCell?.row + + const activePositioningTarget = isActiveCell && !dontShowActiveCellHighlight // TODO: move the logic into runGridChangeElementLocation and do not set targetCell prop in these cases + + const borderColor = activePositioningTarget + ? colorTheme.gridControlsPink.value + : colorTheme.grey65.value return (
- {placeholders.map((cell) => { - const countedRow = Math.floor(cell / grid.columns) + 1 - const countedColumn = Math.floor(cell % grid.columns) + 1 - const id = gridCellTargetId(grid.elementPath, countedRow, countedColumn) - const dotgridColor = - activelyDraggingOrResizingCell != null - ? features.Grid.dotgridColor - : 'transparent' - - const borderColor = - countedColumn === currentHoveredCell?.column && - countedRow === currentHoveredCell?.row - ? colorTheme.brandNeonPink.value - : features.Grid.inactiveGridColor - - return ( -
= grid.rows || (grid.rowGap != null && grid.rowGap > 0) - ? gridPlaceholderBorder(borderColor) - : undefined, - borderRight: - countedColumn >= grid.columns || - (grid.columnGap != null && grid.columnGap > 0) - ? gridPlaceholderBorder(borderColor) - : undefined, - position: 'relative', - pointerEvents: 'initial', - }} - data-grid-row={countedRow} - data-grid-column={countedColumn} - > - {when( - features.Grid.dotgrid, - -
-
-
-
-
-
-
- , - )} -
- ) - })} -
- ) - })} - {/* cell targets */} - {cells.map((cell) => { - return ( -
+ data-grid-row={countedRow} + data-grid-column={countedColumn} + > +
+
) })} - {/* shadow */} - {features.Grid.shadow && - !anyTargetAbsolute && - shadow != null && - initialShadowFrame != null && - interactionData?.dragStart != null && - interactionData?.drag != null && - hoveringStart != null ? ( - + {/* cell targets */} + {cells.map((cell) => { + return ( +
- ) : null} - + ) + })} + {/* shadow */} + {!anyTargetAbsolute && + shadow != null && + initialShadowFrame != null && + interactionData?.dragStart != null && + interactionData?.drag != null && + hoveringStart != null && + controlsVisible === 'all' ? ( + + ) : null} ) }) +GridControl.displayName = 'GridControl' -function useSnapAnimation(params: { - disabled: boolean - gridPath: ElementPath | null - shadowFrame: CanvasRectangle | null - targetRootCell: GridCellCoordinates | null - controls: AnimationControls -}) { - const { gridPath, targetRootCell, controls, shadowFrame, disabled } = params - const features = useRollYourOwnFeatures() - - const [lastTargetRootCellId, setLastTargetRootCellId] = React.useState(targetRootCell) - const [lastSnapPoint, setLastSnapPoint] = React.useState(shadowFrame) - - const selectedViews = useEditorState( - Substores.selectedViews, - (store) => store.editor.selectedViews, - 'useSnapAnimation selectedViews', +export const GridMeasurementHelpers = React.memo(() => { + const metadata = useEditorState( + Substores.metadata, + (store) => store.editor.jsxMetadata, + 'GridMeasurementHelpers metadata', ) - const animate = useCanvasAnimation(selectedViews) - - const canvasScale = useEditorState( - Substores.canvasOffset, - (store) => store.editor.canvas.scale, - 'useSnapAnimation canvasScale', - ) + const { grids, gridItems } = useAllGrids(metadata) - const canvasOffset = useEditorState( - Substores.canvasOffset, - (store) => store.editor.canvas.roundedCanvasOffset, - 'useSnapAnimation canvasOffset', + return ( + + {grids.map((grid) => ( + + ))} + {gridItems.map((gridItem) => ( + + ))} + ) +}) +GridMeasurementHelpers.displayName = 'GridMeasurementHelpers' - const moveFromPoint = React.useMemo(() => { - return lastSnapPoint ?? shadowFrame - }, [lastSnapPoint, shadowFrame]) - - const snapPoint = React.useMemo(() => { - if (gridPath == null || targetRootCell == null) { - return null - } +export interface GridMeasurementHelperProps { + elementPath: ElementPath + source: 'parent' | 'element' +} - const element = document.getElementById( - gridCellTargetId(gridPath, targetRootCell.row, targetRootCell.column), - ) - if (element == null) { +export const GridMeasurementHelper = React.memo( + ({ elementPath, source }) => { + const gridData = useGridMeasurementHelperData(elementPath, source) + if (gridData == null) { return null } - const rect = element.getBoundingClientRect() - const point = windowPoint({ x: rect.x, y: rect.y }) - - return windowToCanvasCoordinates(canvasScale, canvasOffset, point).canvasPositionRounded - }, [canvasScale, canvasOffset, gridPath, targetRootCell]) + const placeholders = range(0, gridData.cells) - React.useEffect(() => { - if (disabled) { - return + const style: CSSProperties = { + ...getGridHelperStyleMatchingTargetGrid(gridData), + opacity: 1, } - if (targetRootCell != null && snapPoint != null && moveFromPoint != null) { - const snapPointsDiffer = lastSnapPoint == null || !pointsEqual(snapPoint, lastSnapPoint) - const hasMovedToANewCell = lastTargetRootCellId != null - const shouldAnimate = snapPointsDiffer && hasMovedToANewCell - if (shouldAnimate) { - void animate( - { - scale: [0.97, 1.02, 1], // a very subtle boop - x: [moveFromPoint.x - snapPoint.x, 0], - y: [moveFromPoint.y - snapPoint.y, 0], - }, - { duration: CELL_ANIMATION_DURATION }, - ) + const uid = UUID() + GridMeasurementHelperMap.current.set(gridData.element, uid) - if (features.Grid.animateShadowSnap) { - void controls.start(SHADOW_SNAP_ANIMATION) - } - } - } - setLastSnapPoint(snapPoint) - setLastTargetRootCellId(targetRootCell) - }, [ - targetRootCell, - controls, - features.Grid.animateShadowSnap, - lastSnapPoint, - snapPoint, - animate, - moveFromPoint, - lastTargetRootCellId, - disabled, - ]) -} + return ( +
+ {placeholders.map((cell) => { + const countedRow = Math.floor(cell / gridData.columns) + 1 + const countedColumn = Math.floor(cell % gridData.columns) + 1 + const id = `${GridMeasurementHelperKey(elementPath)}-${countedRow}-${countedColumn}` + return ( +
+ ) + })} +
+ ) + }, +) +GridMeasurementHelper.displayName = 'GridMeasurementHelper' -function useMouseMove(activelyDraggingOrResizingCell: string | null) { - const [hoveringStart, setHoveringStart] = React.useState<{ - point: CanvasPoint - } | null>(null) - const [mouseCanvasPosition, setMouseCanvasPosition] = React.useState( - canvasPoint({ x: 0, y: 0 }), +export const GridControlsComponent = ({ targets }: GridControlsProps) => { + const ancestorPaths = React.useMemo(() => { + return targets.flatMap((target) => + EP.getAncestors(getGridIdentifierContainerOrComponentPath(target)), + ) + }, [targets]) + const ancestorGrids: Array = useEditorState( + Substores.metadata, + (store) => { + return ancestorPaths + .filter((ancestorPath) => { + const ancestorMetadata = MetadataUtils.findElementByElementPath( + store.editor.jsxMetadata, + ancestorPath, + ) + return MetadataUtils.isGridLayoutedContainer(ancestorMetadata) + }) + .map((p) => gridContainerIdentifier(p)) + }, + 'GridControlsComponent ancestorGrids', ) - const canvasScale = useEditorState( - Substores.canvasOffset, - (store) => store.editor.canvas.scale, - 'useHoveringCell canvasScale', + const targetRootCell = useEditorState( + Substores.canvas, + (store) => store.editor.canvas.controls.gridControlData?.rootCell ?? null, + 'GridControlsComponent targetRootCell', ) - const canvasOffset = useEditorState( - Substores.canvasOffset, - (store) => store.editor.canvas.roundedCanvasOffset, - 'useHoveringCell canvasOffset', + const hoveredGrids = useEditorState( + Substores.canvas, + (store) => stripNulls([store.editor.canvas.controls.gridControlData?.grid]), + 'GridControlsComponent hoveredGrids', ) - React.useEffect(() => { - function handleMouseMove(e: MouseEvent) { - if (activelyDraggingOrResizingCell == null) { - setHoveringStart(null) - return - } + const gridsWithVisibleControls: Array = [...targets, ...hoveredGrids] + + const selectedGridItems = useSelectedGridItems() + const isGridItemInteractionActive = useIsGridItemInteractionActive() + + // Uniqify the grid paths, and then sort them by depth. + // With the lowest depth grid at the end so that it renders on top and catches the events + // before those above it in the hierarchy. + const grids = useGridData( + uniqBy([...gridsWithVisibleControls, ...ancestorGrids], gridIdentifiersSimilar).sort((a, b) => { + const aDepth = + a.type === 'GRID_CONTAINER' ? EP.fullDepth(a.container) : EP.fullDepth(a.item) - 1 + const bDepth = + b.type === 'GRID_CONTAINER' ? EP.fullDepth(b.container) : EP.fullDepth(b.item) - 1 + return aDepth - bDepth + }), + ) - const newMouseCanvasPosition = windowToCanvasCoordinates( + const isGridItemSelectedWithoutInteraction = + selectedGridItems.length > 0 && !isGridItemInteractionActive + + const [forceShowGridPlaceholders] = useAtom(forceShowGridPlaceholdersAtom) + + if (grids.length === 0) { + return null + } + + return ( +
+ + {grids.map((grid) => { + const gridContainerOrComponentPath = getGridIdentifierContainerOrComponentPath( + grid.identifier, + ) + const shouldHaveVisibleControls = gridsWithVisibleControls.some((g) => { + if (isGridItemSelectedWithoutInteraction) { + return false + } + + const visibleControlPath = getGridIdentifierContainerOrComponentPath(g) + return EP.pathsEqual(gridContainerOrComponentPath, visibleControlPath) + }) + + return ( + + ) + })} + + +
+ ) +} + +export const GridRulersControlsComponent = React.memo(() => { + const selectedGridItems = useSelectedGridItems() + + if (!isFeatureEnabled('Grid Ruler Markers')) { + return null + } + return ( +
+ + {selectedGridItems.map((path) => { + return + })} + +
+ ) +}) +GridRulersControlsComponent.displayName = 'GridRulersControlsComponent' + +const MIN_INDICATORS_DISTANCE = 32 // px + +const AbsoluteDistanceIndicators = React.memo( + (props: { targetRootCell: GridCellCoordinates | null }) => { + const colorTheme = useColorTheme() + + const gridMetadata = useEditorState( + Substores.metadata, + (store) => { + if (store.editor.selectedViews.length !== 1) { + return null + } + + return MetadataUtils.findElementByElementPath( + store.editor.jsxMetadata, + store.editor.selectedViews[0], + ) + }, + 'AbsoluteDistanceIndicators cellFrame', + ) + + const cellFrame = !MetadataUtils.isPositionAbsolute(gridMetadata) + ? null + : nullIfInfinity(gridMetadata?.globalFrame) + + const targetCellBoundingBox = React.useMemo(() => { + if (gridMetadata == null || props.targetRootCell == null) { + return null + } + return getGlobalFrameOfGridCellFromMetadata(gridMetadata, props.targetRootCell) + }, [props.targetRootCell, gridMetadata]) + + const distanceTop = + targetCellBoundingBox == null || cellFrame == null ? 0 : cellFrame.y - targetCellBoundingBox.y + + const distanceLeft = + targetCellBoundingBox == null || cellFrame == null ? 0 : cellFrame.x - targetCellBoundingBox.x + + const positioning = React.useMemo(() => { + if (cellFrame == null || targetCellBoundingBox == null) { + return null + } + + function position( + wantedCoord: 'x' | 'y', + cell: CanvasRectangle, + root: CanvasRectangle, + dominantDistance: number, + otherDistance: number, + ): { left: number; top: number } { + const otherCoord = wantedCoord === 'x' ? 'y' : 'x' + const dimension = wantedCoord === 'x' ? 'width' : 'height' + const dominant = + cell[wantedCoord] < root[wantedCoord] || + dominantDistance < MIN_INDICATORS_DISTANCE || + otherDistance < 0 + ? root[wantedCoord] + root[dimension] / 2 + : Math.max(root[wantedCoord], cell[wantedCoord]) + const other = otherDistance < 0 ? cell[otherCoord] : root[otherCoord] + if (wantedCoord === 'x') { + return { + left: dominant, + top: other, + } + } else { + return { + left: other, + top: dominant, + } + } + } + + function compensationNegative( + wantedCoord: 'x' | 'y', + cell: CanvasRectangle, + root: CanvasRectangle, + dist: number, + ): { width: number; height: number; left: number; top: number } | null { + const otherCoord = wantedCoord === 'x' ? 'y' : 'x' + const dimension = wantedCoord === 'x' ? 'width' : 'height' + + const shouldCompensate = + dist < 0 && cell[wantedCoord] > root[wantedCoord] + root[dimension] / 2 + if (!shouldCompensate) { + return null + } + + const size = Math.abs(root[wantedCoord] + root[dimension] / 2 - cell[wantedCoord]) + const dominant = root[wantedCoord] + root[dimension] / 2 + const other = cell[otherCoord] + + return wantedCoord === 'x' + ? { + width: size, + height: 1, + top: other, + left: dominant, + } + : { + width: 1, + height: size, + top: dominant, + left: other, + } + } + + function compensationPositive( + wantedCoord: 'x' | 'y', + cell: CanvasRectangle, + root: CanvasRectangle, + dist: number, + ): { width: number; height: number; left: number; top: number } | null { + const otherCoord = wantedCoord === 'x' ? 'y' : 'x' + const dimension = wantedCoord === 'x' ? 'width' : 'height' + + const shouldCompensate = dist > 0 && cell[wantedCoord] > root[wantedCoord] + root[dimension] + if (!shouldCompensate) { + return null + } + + const size = Math.abs(root[wantedCoord] + root[dimension] / 2 - cell[wantedCoord]) + const other = root[otherCoord] + const dominant = root[wantedCoord] + root[dimension] / 2 + + return wantedCoord === 'x' + ? { + width: size, + height: 1, + top: other, + left: dominant, + } + : { + height: size, + width: 1, + left: other, + top: dominant, + } + } + + const topIndicator = { + ...position('x', cellFrame, targetCellBoundingBox, distanceLeft, distanceTop), + compensateNegative: compensationNegative( + 'x', + cellFrame, + targetCellBoundingBox, + distanceTop, + ), + compensatePositive: compensationPositive( + 'x', + cellFrame, + targetCellBoundingBox, + distanceTop, + ), + } + + const leftIndicator = { + ...position('y', cellFrame, targetCellBoundingBox, distanceLeft, distanceLeft), + compensateNegative: compensationNegative( + 'y', + cellFrame, + targetCellBoundingBox, + distanceLeft, + ), + compensatePositive: compensationPositive( + 'y', + cellFrame, + targetCellBoundingBox, + distanceLeft, + ), + } + + return { topIndicator, leftIndicator } + }, [cellFrame, targetCellBoundingBox, distanceLeft, distanceTop]) + + if (targetCellBoundingBox == null || cellFrame == null || positioning == null) { + return null + } + + const backgroundColor = colorTheme.primary.value + const dashedBorder = `1px dashed ${backgroundColor}` + + return ( + + {/* top distance */} + +
+ + {distanceTop} + +
+ {/* compensate */} + {positioning.topIndicator.compensateNegative != null ? ( +
+ ) : null} + {positioning.topIndicator.compensatePositive != null ? ( +
+ ) : null} + + + {/* left distance */} + +
+ + {distanceLeft} + +
+ {/* compensate */} + {positioning.leftIndicator.compensateNegative != null ? ( +
+ ) : null} + {positioning.leftIndicator.compensatePositive != null ? ( +
+ ) : null} + + + ) + }, +) + +function useCellAnimation(params: { + disabled: boolean + gridPath: ElementPath | null + shadowFrame: CanvasRectangle | null + targetRootCell: GridCellCoordinates | null + controls: AnimationControls +}) { + const { gridPath, targetRootCell, controls, shadowFrame, disabled } = params + + const [lastTargetRootCellId, setLastTargetRootCellId] = React.useState(targetRootCell) + const [lastSnapPoint, setLastSnapPoint] = React.useState(shadowFrame) + + const selectedViews = useEditorState( + Substores.selectedViews, + (store) => store.editor.selectedViews, + 'useCellAnimation selectedViews', + ) + + const animate = useCanvasAnimation(selectedViews) + + const gridMetadata = useEditorState( + Substores.metadata, + (store) => MetadataUtils.findElementByElementPath(store.editor.jsxMetadata, gridPath), + 'useCellAnimation gridMetadata', + ) + + const moveFromPoint = React.useMemo(() => { + return lastSnapPoint ?? shadowFrame + }, [lastSnapPoint, shadowFrame]) + + const snapPoint = React.useMemo(() => { + if (gridMetadata == null || targetRootCell == null) { + return null + } + + return getGlobalFrameOfGridCellFromMetadata(gridMetadata, targetRootCell) + }, [gridMetadata, targetRootCell]) + + React.useEffect(() => { + if (disabled) { + return + } + + if (targetRootCell != null && snapPoint != null && moveFromPoint != null) { + const snapPointsDiffer = lastSnapPoint == null || !pointsEqual(snapPoint, lastSnapPoint) + const hasMovedToANewCell = lastTargetRootCellId != null + const shouldAnimate = snapPointsDiffer && hasMovedToANewCell + if (shouldAnimate) { + void animate( + { + scale: [0.97, 1.02, 1], // a very subtle boop + x: [moveFromPoint.x - snapPoint.x, 0], + y: [moveFromPoint.y - snapPoint.y, 0], + }, + { + duration: CELL_ANIMATION_DURATION, + type: 'tween', + ease: 'easeInOut', + }, + ) + } + } + setLastSnapPoint(snapPoint) + setLastTargetRootCellId(targetRootCell) + }, [ + targetRootCell, + controls, + lastSnapPoint, + snapPoint, + animate, + moveFromPoint, + lastTargetRootCellId, + disabled, + ]) +} + +function useMouseMove(activelyDraggingOrResizingCell: string | null) { + const [hoveringStart, setHoveringStart] = React.useState<{ + point: CanvasPoint + } | null>(null) + const [mouseCanvasPosition, setMouseCanvasPosition] = React.useState( + canvasPoint({ x: 0, y: 0 }), + ) + + const canvasScale = useEditorState( + Substores.canvasOffset, + (store) => store.editor.canvas.scale, + 'useHoveringCell canvasScale', + ) + + const canvasOffset = useEditorState( + Substores.canvasOffset, + (store) => store.editor.canvas.roundedCanvasOffset, + 'useHoveringCell canvasOffset', + ) + + React.useEffect(() => { + function handleMouseMove(e: MouseEvent) { + if (activelyDraggingOrResizingCell == null) { + setHoveringStart(null) + return + } + + const newMouseCanvasPosition = windowToCanvasCoordinates( + canvasScale, + canvasOffset, + windowPoint({ x: e.clientX, y: e.clientY }), + ).canvasPositionRaw + setMouseCanvasPosition(newMouseCanvasPosition) + + setHoveringStart((start) => { + if (start == null) { + return { + point: canvasPoint(newMouseCanvasPosition), + } + } + return start + }) + } + window.addEventListener('mousemove', handleMouseMove) + return function () { + window.removeEventListener('mousemove', handleMouseMove) + } + }, [activelyDraggingOrResizingCell, canvasOffset, canvasScale]) + + return { hoveringStart, mouseCanvasPosition } +} + +interface GridResizeControlProps { + target: GridIdentifier +} + +export const GridResizeControlsComponent = ({ target }: GridResizeControlProps) => { + const gridTarget = getGridIdentifierContainerOrComponentPath(target) + + const element = useEditorState( + Substores.metadata, + (store) => MetadataUtils.findElementByElementPath(store.editor.jsxMetadata, gridTarget), + 'GridResizeControls element', + ) + + const dispatch = useDispatch() + const canvasOffsetRef = useRefEditorState((store) => store.editor.canvas.roundedCanvasOffset) + const scale = useEditorState( + Substores.canvas, + (store) => store.editor.canvas.scale, + 'GridResizeControls scale', + ) + + const resizeControlRef = useRefEditorState((store) => + store.editor.canvas.interactionSession?.activeControl.type !== 'GRID_RESIZE_HANDLE' + ? null + : store.editor.canvas.interactionSession.activeControl, + ) + + const dragRef = useRefEditorState((store) => + store.editor.canvas.interactionSession?.interactionData.type !== 'DRAG' + ? null + : store.editor.canvas.interactionSession?.interactionData.drag, + ) + + const [startingBounds, setStartingBounds] = React.useState(null) + const [bounds, setBounds] = React.useState(null) + const onMouseMove = React.useCallback(() => { + if (resizeControlRef.current == null || dragRef.current == null) { + return + } + + if (startingBounds == null) { + return + } + setBounds( + resizeBoundingBoxFromSide( + startingBounds, + dragRef.current, + gridEdgeToEdgePosition(resizeControlRef.current.edge), + 'non-center-based', + null, + ), + ) + }, [dragRef, resizeControlRef, startingBounds]) + + const isResizing = bounds != null + + const onMouseUp = React.useCallback(() => { + setBounds(null) + setStartingBounds(null) + }, []) + + React.useEffect(() => { + window.addEventListener('mousemove', onMouseMove) + window.addEventListener('mouseup', onMouseUp) + return () => { + window.removeEventListener('mousemove', onMouseMove) + window.removeEventListener('mouseup', onMouseUp) + } + }, [onMouseMove, onMouseUp]) + + const startResizeInteraction = React.useCallback( + (uid: string, edge: GridResizeEdge) => (event: React.MouseEvent) => { + event.stopPropagation() + const frame = zeroRectIfNullOrInfinity(element?.globalFrame ?? null) + setBounds(frame) + setStartingBounds(frame) + const start = windowToCanvasCoordinates( + scale, + canvasOffsetRef.current, + windowPoint({ x: event.nativeEvent.x, y: event.nativeEvent.y }), + ) + dispatch([ + CanvasActions.createInteractionSession( + createInteractionViaMouse( + start.canvasPositionRounded, + Modifier.modifiersForEvent(event), + gridResizeHandle(uid, edge), + 'zero-drag-not-permitted', + ), + ), + ]) + }, + [canvasOffsetRef, dispatch, element?.globalFrame, scale], + ) + + const canShowHandles = React.useMemo(() => { + if (isResizing) { + return true + } + if (element?.globalFrame == null || isInfinityRectangle(element.globalFrame)) { + return false + } + const scaledFrame = scaleRect(element.globalFrame, scale) + return scaledFrame.width * scale > 30 && scaledFrame.height > 30 + }, [element, scale, isResizing]) + + const onEdgeMouseDown = React.useCallback( + (position: EdgePosition) => (e: React.MouseEvent) => { + if (element == null) { + return + } + + const edge = edgePositionToGridResizeEdge(position) + if (edge == null) { + return + } + + startResizeInteraction(EP.toUid(element.elementPath), edge)(e) + }, + [element, startResizeInteraction], + ) + + const resizeEdges = useResizeEdges([gridTarget], { + onEdgeDoubleClick: () => NO_OP, + onEdgeMouseMove: NO_OP, + onEdgeMouseDown: onEdgeMouseDown, + cursors: { + top: CSSCursor.RowResize, + bottom: CSSCursor.RowResize, + left: CSSCursor.ColResize, + right: CSSCursor.ColResize, + }, + }) + + const resizeDirection = useEditorState( + Substores.metadata, + (store) => { + if (element == null) { + return { horizontal: false, vertical: false } + } + return { + horizontal: isFillOrStretchModeAppliedOnSpecificSide( + store.editor.jsxMetadata, + element.elementPath, + 'horizontal', + ), + vertical: isFillOrStretchModeAppliedOnSpecificSide( + store.editor.jsxMetadata, + element.elementPath, + 'vertical', + ), + } + }, + 'GridResizeControlsComponent resizeDirection', + ) + + if ( + element == null || + element.globalFrame == null || + isInfinityRectangle(element.globalFrame) || + !canShowHandles + ) { + return null + } + + return ( + +
+
+ {when( + resizeDirection.vertical, + + {resizeEdges.top} + {resizeEdges.bottom} + , + )} + {when( + resizeDirection.horizontal, + + {resizeEdges.left} + {resizeEdges.right} + , + )} +
+
+
+ ) +} + +function collectGridPinOutlines( + attributes: PropsOrJSXAttributes, + frame: LocalRectangle, + scale: number, +): PinOutlineProps[] { + const pinLeft = eitherToMaybe(getLayoutProperty('left', attributes, styleStringInArray)) + const pinTop = eitherToMaybe(getLayoutProperty('top', attributes, styleStringInArray)) + const pinRight = eitherToMaybe(getLayoutProperty('right', attributes, styleStringInArray)) + const pinBottom = eitherToMaybe(getLayoutProperty('bottom', attributes, styleStringInArray)) + let pins: PinOutlineProps[] = [] + if (pinLeft != null) { + let startY: string | number | undefined = undefined + let endY: string | number | undefined = undefined + if (pinTop != null) { + startY = `calc(${printCSSNumberWithDefaultUnit(pinTop, 'px')} + ${frame.height / 2}px)` + } else if (pinBottom != null) { + endY = `calc(${printCSSNumberWithDefaultUnit(pinBottom, 'px')} + ${frame.height / 2}px)` + } else { + startY = frame.height / 2 + } + pins.push({ + name: 'left', + isHorizontalLine: true, + size: printCSSNumberWithDefaultUnit(pinLeft, 'px'), + startX: 0, + startY: startY, + endY: endY, + scale: scale, + }) + } + if (pinTop != null) { + let startX: string | number | undefined = undefined + let endX: string | number | undefined = undefined + if (pinLeft != null) { + startX = `calc(${printCSSNumberWithDefaultUnit(pinLeft, 'px')} + ${frame.width / 2}px)` + } else if (pinRight != null) { + endX = `calc(${printCSSNumberWithDefaultUnit(pinRight, 'px')} + ${frame.width / 2}px)` + } else { + startX = frame.width / 2 + } + pins.push({ + name: 'top', + isHorizontalLine: false, + size: printCSSNumberWithDefaultUnit(pinTop, 'px'), + startX: startX, + endX: endX, + startY: 0, + scale: scale, + }) + } + if (pinRight != null) { + let startY: string | number | undefined = undefined + let endY: string | number | undefined = undefined + if (pinTop != null) { + startY = `calc(${printCSSNumberWithDefaultUnit(pinTop, 'px')} + ${frame.height / 2}px)` + } else if (pinBottom != null) { + endY = `calc(${printCSSNumberWithDefaultUnit(pinBottom, 'px')} + ${frame.height / 2}px)` + } else { + endY = frame.height / 2 + } + pins.push({ + name: 'right', + isHorizontalLine: true, + size: printCSSNumberWithDefaultUnit(pinRight, 'px'), + startY: startY, + endX: 0, + endY: endY, + scale: scale, + }) + } + if (pinBottom != null) { + let startX: string | number | undefined = undefined + let endX: string | number | undefined = undefined + if (pinLeft != null) { + startX = `calc(${printCSSNumberWithDefaultUnit(pinLeft, 'px')} + ${frame.width / 2}px)` + } else if (pinRight != null) { + endX = `calc(${printCSSNumberWithDefaultUnit(pinRight, 'px')} + ${frame.width / 2}px)` + } else { + endX = frame.width / 2 + } + pins.push({ + name: 'bottom', + isHorizontalLine: false, + size: printCSSNumberWithDefaultUnit(pinBottom, 'px'), + startX: startX, + endX: endX, + endY: 0, + scale: scale, + }) + } + return pins +} + +export interface GridElementContainingBlockProps { + gridPath: ElementPath + gridChild: ElementPath +} + +const GridElementContainingBlock = React.memo((props) => { + const gridData = useGridMeasurementHelperData(props.gridPath, 'element') + const gridChildData = useGridElementMeasurementHelperData(props.gridChild) + const scale = useEditorState( + Substores.canvas, + (store) => store.editor.canvas.scale, + 'GridElementContainingBlock scale', + ) + const position = useEditorState( + Substores.metadata, + (store) => { + const childMetadata = MetadataUtils.findElementByElementPath( + store.editor.jsxMetadata, + props.gridChild, + ) + return childMetadata?.specialSizeMeasurements.position + }, + 'GridElementContainingBlock position', + ) + const gridChildStyle: CSSProperties = React.useMemo(() => { + if (gridChildData == null) { + return {} + } + return { + gridColumnStart: gridChildData.computedStyling.gridColumnStart, + gridColumnEnd: gridChildData.computedStyling.gridColumnEnd, + gridRowStart: gridChildData.computedStyling.gridRowStart, + gridRowEnd: gridChildData.computedStyling.gridRowEnd, + position: gridChildData.computedStyling.position, + } + }, [gridChildData]) + const gridChildFrame = useEditorState( + Substores.metadata, + (store) => { + return MetadataUtils.getLocalFrame(props.gridChild, store.editor.jsxMetadata, null) + }, + 'GridElementContainingBlock gridChildFrame', + ) + const attributes = usePropsOrJSXAttributes(props.gridChild) + + if (gridChildFrame == null || isInfinityRectangle(gridChildFrame)) { + return null + } + + if (gridData == null) { + return null + } + + if (position !== 'absolute') { + return null + } + + const style: CSSProperties = { + ...getGridHelperStyleMatchingTargetGrid(gridData), + opacity: 1, + } + + const pins: Array = collectGridPinOutlines(attributes, gridChildFrame, scale) + + return ( +
+
+ {pins.map((pin) => ( + + ))} +
+
+ ) +}) +GridElementContainingBlock.displayName = 'GridElementContainingBlock' + +export interface GridElementContainingBlocksProps {} + +export const GridElementContainingBlocks = React.memo((props) => { + const selectedGridChildElements = useEditorState( + Substores.metadata, + (store) => { + return store.editor.selectedViews.filter((selectedView) => { + return MetadataUtils.isGridItemWithLayoutProvidingGridParent( + store.editor.jsxMetadata, + selectedView, + ) + }) + }, + 'GridElementContainingBlocks selectedViews', + ) + + return ( + + {selectedGridChildElements.map((selectedGridChildElement) => { + return ( + + ) + })} + + ) +}) + +function gridEdgeToCSSCursor(edge: GridResizeEdge): CSSCursor { + switch (edge) { + case 'column-end': + case 'column-start': + return CSSCursor.ColResize + case 'row-end': + case 'row-start': + return CSSCursor.RowResize + default: + assertNever(edge) + } +} + +function gridKeyFromPath(path: ElementPath): string { + return `grid-${EP.toString(path)}` +} + +const borderWidth = 1 +function gridPlaceholderBorder(color: string, scale: number): string { + return `${borderWidth / scale}px solid ${color}` +} + +const borderExtension = 0.5 +function gridPlaceholderTopOrLeftPosition(scale: number): string { + return `${-borderExtension / scale}px` +} + +function gridPlaceholderWidthOrHeight(scale: number): string { + return `calc(100% + ${(borderExtension * 2) / scale}px)` +} + +function useAllGrids(metadata: ElementInstanceMetadataMap) { + return React.useMemo(() => { + const gridPaths = MetadataUtils.getAllGrids(metadata) + const gridItemPaths = MetadataUtils.getAllGridItems(metadata) + const gridItemPathsWithoutGridPaths = gridItemPaths.filter( + (path) => !gridPaths.some((gridPath) => EP.isParentOf(gridPath, path)), + ) + return { + grids: gridPaths, + gridItems: gridItemPathsWithoutGridPaths, + } + }, [metadata]) +} + +function useIsGridItemInteractionActive() { + return useEditorState( + Substores.canvas, + (store) => { + if (store.editor.canvas.interactionSession == null) { + return false + } + return ( + // movement + store.editor.canvas.interactionSession.activeControl.type === 'GRID_CELL_HANDLE' || + // resize (cell) + store.editor.canvas.interactionSession.activeControl.type === 'GRID_RESIZE_HANDLE' || + // resize (abs) + store.editor.canvas.interactionSession.activeControl.type === 'RESIZE_HANDLE' + ) + }, + 'useIsGridItemInteractionActive isItemInteractionActive', + ) +} + +function useSelectedGridItems(): ElementPath[] { + const selectedViewsRef = useRefEditorState((store) => store.editor.selectedViews) + const jsxMetadataRef = useRefEditorState((store) => store.editor.jsxMetadata) + return selectedViewsRef.current.filter((selected) => + MetadataUtils.isGridItem(jsxMetadataRef.current, selected), + ) +} + +export const RulerMarkerIconSize = 11 // px + +type RulerMarkerData = { + parentGrid: GridContainerProperties + cellRect: CanvasRectangle + gridRect: CanvasRectangle + otherColumnMarkers: Array + otherRowMarkers: Array + columnStart: RulerMarkerPositionData + columnEnd: RulerMarkerPositionData + rowStart: RulerMarkerPositionData + rowEnd: RulerMarkerPositionData +} + +type RulerMarkerPositionData = { + markerType: 'selected' | 'target' | 'other' + rowOrColumn: 'row' | 'column' + top: number + left: number + position: GridPositionOrSpan | null + bound: 'start' | 'end' +} + +interface RulerMarkersProps { + path: ElementPath +} + +export const RulerMarkerColumnStartTestId = 'ruler-marker-column-start' +export const RulerMarkerColumnEndTestId = 'ruler-marker-column-end' +export const RulerMarkerRowStartTestId = 'ruler-marker-row-start' +export const RulerMarkerRowEndTestId = 'ruler-marker-row-end' + +const RulerMarkers = React.memo((props: RulerMarkersProps) => { + const dispatch = useDispatch() + + const [frozenMarkers, setFrozenMarkers] = React.useState(null) + const [showExtraMarkers, setShowExtraMarkers] = React.useState<'row' | 'column' | null>(null) + + const gridRect: CanvasRectangle | null = useEditorState( + Substores.metadata, + (store) => { + const originalGrid = findOriginalGrid(store.editor.jsxMetadata, EP.parentPath(props.path)) + if (originalGrid == null) { + return null + } + + return MetadataUtils.getFrameOrZeroRectInCanvasCoords(originalGrid, store.editor.jsxMetadata) + }, + 'RulerMarkers gridRect', + ) + + const parentGridCellGlobalFrames = useEditorState( + Substores.metadata, + (store) => { + const elementMetadata = MetadataUtils.findElementByElementPath( + store.editor.jsxMetadata, + props.path, + ) + if (elementMetadata == null) { + return null + } + + return elementMetadata.specialSizeMeasurements.parentGridCellGlobalFrames + }, + 'RulerMarkers parentGridCellGlobalFrames', + ) + + const rulerMarkerData: RulerMarkerData | null = useEditorState( + Substores.metadata, + (store) => { + if (gridRect == null || parentGridCellGlobalFrames == null) { + return null + } + + const elementMetadata = MetadataUtils.findElementByElementPath( + store.editor.jsxMetadata, + props.path, + ) + if (elementMetadata == null) { + return null + } + + const elementGridProperties = elementMetadata.specialSizeMeasurements.elementGridProperties + if (elementGridProperties == null) { + return null + } + + const parentGrid = elementMetadata.specialSizeMeasurements.parentContainerGridProperties + const cellRect = calculateGridCellRectangle( + store.editor.jsxMetadata, + store.editor.elementPathTree, + props.path, + ) + if (cellRect == null) { + return null + } + + let otherColumnMarkers: Array = [] + let otherRowMarkers: Array = [] + + function addOtherMarker( + rowOrColumn: RulerMarkerPositionData['rowOrColumn'], + bound: RulerMarkerPositionData['bound'], + rulerLeft: number, + rulerTop: number, + ): void { + const otherMarker: RulerMarkerPositionData = { + markerType: 'other', + rowOrColumn: rowOrColumn, + top: rulerTop, + left: rulerLeft, + position: null, + bound: bound, + } + const addTo = rowOrColumn === 'row' ? otherRowMarkers : otherColumnMarkers + addTo.push(otherMarker) + } + + // Add the additional markers for columns. + const lastColumnIndex = parentGridCellGlobalFrames[0].length - 1 + for (let columnIndex = 0; columnIndex <= lastColumnIndex; columnIndex++) { + const cell = parentGridCellGlobalFrames[0][columnIndex] + if (cellRect.x !== cell.x) { + addOtherMarker('column', 'start', cell.x, gridRect.y) + } + if (cellRect.x + cellRect.width !== cell.x + cell.width) { + addOtherMarker('column', 'end', cell.x + cell.width, gridRect.y) + } + } + + // Add the additional markers for rows. + const lastRowIndex = parentGridCellGlobalFrames.length - 1 + for (let rowIndex = 0; rowIndex <= lastRowIndex; rowIndex++) { + const cell = parentGridCellGlobalFrames[rowIndex][0] + if (cellRect.y !== cell.y) { + addOtherMarker('row', 'start', gridRect.x, cell.y) + } + if (cellRect.y + cellRect.height !== cell.y + cell.height) { + addOtherMarker('row', 'end', gridRect.x, cell.y + cell.height) + } + } + + const columnStart: RulerMarkerPositionData = { + markerType: 'selected', + rowOrColumn: 'column', + top: gridRect.y, + left: cellRect.x, + position: elementGridProperties.gridColumnStart, + bound: 'start', + } + const columnEnd: RulerMarkerPositionData = { + markerType: 'selected', + rowOrColumn: 'column', + top: gridRect.y, + left: cellRect.x + cellRect.width, + position: elementGridProperties.gridColumnEnd, + bound: 'end', + } + const rowStart: RulerMarkerPositionData = { + markerType: 'selected', + rowOrColumn: 'row', + top: cellRect.y, + left: gridRect.x, + position: elementGridProperties.gridRowStart, + bound: 'start', + } + const rowEnd: RulerMarkerPositionData = { + markerType: 'selected', + rowOrColumn: 'row', + top: cellRect.y + cellRect.height, + left: gridRect.x, + position: elementGridProperties.gridRowEnd, + bound: 'end', + } + + const data: RulerMarkerData = { + parentGrid: parentGrid, + cellRect: cellRect, + gridRect: gridRect, + otherColumnMarkers: otherColumnMarkers, + otherRowMarkers: otherRowMarkers, + columnStart: columnStart, + columnEnd: columnEnd, + rowStart: rowStart, + rowEnd: rowEnd, + } + return data + }, + 'RulerMarkers markers', + ) + + const canvasScale = useEditorState( + Substores.canvasOffset, + (store) => store.editor.canvas.scale, + 'RulerMarkers canvasScale', + ) + + const dragRef = useRefEditorState((store) => + store.editor.canvas.interactionSession?.activeControl.type === 'GRID_RESIZE_RULER_HANDLE' && + store.editor.canvas.interactionSession?.interactionData.type === 'DRAG' && + store.editor.canvas.interactionSession?.interactionData.drag != null + ? offsetPoint( + store.editor.canvas.interactionSession?.interactionData.dragStart, + store.editor.canvas.interactionSession?.interactionData.drag, + ) + : null, + ) + + const resizeControlRef = useRefEditorState((store) => + store.editor.canvas.interactionSession?.activeControl.type !== 'GRID_RESIZE_RULER_HANDLE' + ? null + : store.editor.canvas.interactionSession.activeControl, + ) + + const canvasOffsetRef = useRefEditorState((store) => store.editor.canvas.roundedCanvasOffset) + + const startResizeInteraction = React.useCallback( + (uid: string, edge: GridResizeEdge) => (e: React.MouseEvent) => { + e.stopPropagation() + + const start = windowToCanvasCoordinates( canvasScale, - canvasOffset, - windowPoint({ x: e.clientX, y: e.clientY }), - ).canvasPositionRaw - setMouseCanvasPosition(newMouseCanvasPosition) + canvasOffsetRef.current, + windowPoint({ x: e.nativeEvent.x, y: e.nativeEvent.y }), + ) + dispatch([ + CanvasActions.createInteractionSession( + createInteractionViaMouse( + start.canvasPositionRounded, + Modifier.modifiersForEvent(e), + gridResizeRulerHandle(uid, edge), + 'zero-drag-not-permitted', + ), + ), + ]) + }, + [canvasOffsetRef, dispatch, canvasScale], + ) - setHoveringStart((start) => { - if (start == null) { - return { - point: canvasPoint(newMouseCanvasPosition), - } - } - return start - }) - } - window.addEventListener('mousemove', handleMouseMove) - return function () { - window.removeEventListener('mousemove', handleMouseMove) - } - }, [activelyDraggingOrResizingCell, canvasOffset, canvasScale]) + const [, setForceShowGridPlaceholders] = useAtom(forceShowGridPlaceholdersAtom) - return { hoveringStart, mouseCanvasPosition } -} + const markerMouseUp = React.useCallback( + (event: MouseEvent) => { + event.preventDefault() + event.stopPropagation() + setShowExtraMarkers(null) + setFrozenMarkers(null) + setForceShowGridPlaceholders('not-visible') -export const GridResizeEdgeTestId = (edge: GridResizeEdge) => `grid-resize-edge-${edge}` + window.removeEventListener('mouseup', markerMouseUp) + }, + [setForceShowGridPlaceholders], + ) -interface GridResizeControlProps { - target: ElementPath -} + const rowMarkerMouseDown = React.useCallback( + (edge: GridResizeEdge) => (event: React.MouseEvent) => { + event.preventDefault() + event.stopPropagation() -export const GridResizeControls = controlForStrategyMemoized( - ({ target }) => { - const colorTheme = useColorTheme() + setShowExtraMarkers('row') + setForceShowGridPlaceholders('all') + setFrozenMarkers(rulerMarkerData) + startResizeInteraction(EP.toUid(props.path), edge)(event) - const element = useEditorState( - Substores.metadata, - (store) => MetadataUtils.findElementByElementPath(store.editor.jsxMetadata, target), - 'GridResizeShadow element', - ) + window.addEventListener('mouseup', markerMouseUp) + }, + [markerMouseUp, props, startResizeInteraction, rulerMarkerData, setForceShowGridPlaceholders], + ) - const dispatch = useDispatch() - const canvasOffsetRef = useRefEditorState((store) => store.editor.canvas.roundedCanvasOffset) - const scale = useEditorState( - Substores.canvas, - (store) => store.editor.canvas.scale, - 'GridResizingControl scale', - ) + const columnMarkerMouseDown = React.useCallback( + (edge: GridResizeEdge) => (event: React.MouseEvent) => { + event.preventDefault() + event.stopPropagation() - const resizeControlRef = useRefEditorState((store) => - store.editor.canvas.interactionSession?.activeControl.type !== 'GRID_RESIZE_HANDLE' - ? null - : store.editor.canvas.interactionSession.activeControl, - ) + setShowExtraMarkers('column') + setForceShowGridPlaceholders('all') + setFrozenMarkers(rulerMarkerData) + startResizeInteraction(EP.toUid(props.path), edge)(event) - const dragRef = useRefEditorState((store) => - store.editor.canvas.interactionSession?.interactionData.type !== 'DRAG' - ? null - : store.editor.canvas.interactionSession?.interactionData.drag, - ) + window.addEventListener('mouseup', markerMouseUp) + }, + [markerMouseUp, props, startResizeInteraction, rulerMarkerData, setForceShowGridPlaceholders], + ) - const [startingBounds, setStartingBounds] = React.useState(null) - const [bounds, setBounds] = React.useState(null) - const onMouseMove = React.useCallback(() => { - if (resizeControlRef.current == null || dragRef.current == null) { - return - } + if (rulerMarkerData == null || gridRect == null) { + return null + } - if (startingBounds == null) { - return - } + const frozenOrRegularMarkerData = frozenMarkers ?? rulerMarkerData - setBounds( - resizeBoundingBoxFromSide( - startingBounds, - dragRef.current, - gridEdgeToEdgePosition(resizeControlRef.current.edge), - 'non-center-based', - null, - ), - ) - }, [dragRef, resizeControlRef, startingBounds]) + return ( + + + {/* Other markers for unselected tracks */} + {frozenOrRegularMarkerData.otherColumnMarkers.map((marker, index) => { + return ( + + ) + })} + {/* Selected item markers */} + + + + + + {/* Other markers for unselected tracks */} + {frozenOrRegularMarkerData.otherRowMarkers.map((marker, index) => { + return ( + + ) + })} + {/* Selected item markers */} + + + + + {/* Offset lines */} + + + + + + {/* Cell outline */} + + + {/* Snap line during resize */} + + + {/* Offset line during resize, following the mouse */} + + + ) +}) +RulerMarkers.displayName = 'RulerMarkers' + +const ResizeOffsetLine = React.memo( + (props: { + edge: GridResizeEdge | null + drag: CanvasPoint | null + container: CanvasRectangle + }) => { + const colorTheme = useColorTheme() - const isResizing = bounds != null + if (props.edge == null || props.drag == null) { + return null + } + const isColumn = props.edge === 'column-start' || props.edge === 'column-end' - const [resizingEdge, setResizingEdge] = React.useState(null) + const top = isColumn + ? props.container.y + : clamp(props.container.y, props.container.y + props.container.height, props.drag.y) + const left = !isColumn + ? props.container.x + : clamp(props.container.x, props.container.x + props.container.width, props.drag.x) - const onMouseUp = React.useCallback(() => { - setBounds(null) - setStartingBounds(null) - setResizingEdge(null) - }, []) + return ( +
+ ) + }, +) +ResizeOffsetLine.displayName = 'ResizeOffsetLine' + +const SnapLine = React.memo( + (props: { + gridTemplate: GridContainerProperties + edge: GridResizeEdge | null + container: CanvasRectangle + target: CanvasRectangle + markers: RulerMarkerData + frozenMarkers: RulerMarkerData | null + }) => { + const colorTheme = useColorTheme() - React.useEffect(() => { - window.addEventListener('mousemove', onMouseMove) - window.addEventListener('mouseup', onMouseUp) - return () => { - window.removeEventListener('mousemove', onMouseMove) - window.removeEventListener('mouseup', onMouseUp) + const targetMarker = React.useMemo(() => { + if (props.edge == null) { + return null } - }, [onMouseMove, onMouseUp]) - - const startResizeInteraction = React.useCallback( - (uid: string, edge: GridResizeEdge) => (event: React.MouseEvent) => { - event.stopPropagation() - const frame = zeroRectIfNullOrInfinity(element?.globalFrame ?? null) - setResizingEdge(edge) - setBounds(frame) - setStartingBounds(frame) - const start = windowToCanvasCoordinates( - scale, - canvasOffsetRef.current, - windowPoint({ x: event.nativeEvent.x, y: event.nativeEvent.y }), - ) - dispatch([ - CanvasActions.createInteractionSession( - createInteractionViaMouse( - start.canvasPositionRounded, - Modifier.modifiersForEvent(event), - gridResizeHandle(uid, edge), - 'zero-drag-not-permitted', - ), - ), - ]) - }, - [canvasOffsetRef, dispatch, element?.globalFrame, scale], - ) + switch (props.edge) { + case 'column-end': + return props.markers.columnEnd + case 'column-start': + return props.markers.columnStart + case 'row-end': + return props.markers.rowEnd + case 'row-start': + return props.markers.rowStart + default: + assertNever(props.edge) + } + }, [props.edge, props.markers]) - const canShowHandles = React.useMemo(() => { - if (isResizing) { - return true + const targetFrozenMarker = React.useMemo(() => { + if (props.edge == null || props.frozenMarkers == null) { + return null } - if (element?.globalFrame == null || isInfinityRectangle(element.globalFrame)) { - return false + switch (props.edge) { + case 'column-end': + return props.frozenMarkers.columnEnd + case 'column-start': + return props.frozenMarkers.columnStart + case 'row-end': + return props.frozenMarkers.rowEnd + case 'row-start': + return props.frozenMarkers.rowStart + default: + assertNever(props.edge) } - const scaledFrame = scaleRect(element.globalFrame, scale) - return scaledFrame.width * scale > 30 && scaledFrame.height > 30 - }, [element, scale, isResizing]) + }, [props.edge, props.frozenMarkers]) + const axis = props.edge === 'column-end' || props.edge === 'column-start' ? 'column' : 'row' + + const canvasScale = useEditorState( + Substores.canvasOffset, + (store) => store.editor.canvas.scale, + 'SnapLine canvasScale', + ) if ( - element == null || - element.globalFrame == null || - isInfinityRectangle(element.globalFrame) || - !canShowHandles + props.edge == null || + targetMarker == null || + targetMarker.position == null || + targetFrozenMarker == null || + targetFrozenMarker.position == null ) { return null } + const showLabel = !gridPositionOrSpanEquals(targetMarker.position, targetFrozenMarker.position) + + const labelWidth = 50 + const labelHeight = 20 + + const isColumn = props.edge === 'column-start' || props.edge === 'column-end' + + const top = isColumn + ? props.container.y + : props.target.y + (props.edge === 'row-end' ? props.target.height : 0) + const left = !isColumn + ? props.container.x + : props.target.x + (props.edge === 'column-end' ? props.target.width : 0) + return ( - -
+
+ {when( + showLabel,
- {GridResizeEdges.map((edge) => { - const properties = gridResizeEdgeProperties(edge) - const visible = !isResizing || resizingEdge === edge - return ( -
-
-
- ) - })} -
-
- + + {printPin(props.gridTemplate, targetMarker.position, axis)} + +
, + )} +
) }, ) +SnapLine.displayName = 'SnapLine' -const GRID_RESIZE_HANDLE_SIZES = { - long: 24, - short: 4, +interface GridRulerProps { + axis: 'row' | 'column' + gridRect: CanvasRectangle + cellFrames: GridCellGlobalFrames | null + rulerVisible: 'visible' | 'not-visible' } -function gridEdgeToEdgePosition(edge: GridResizeEdge): EdgePosition { - switch (edge) { - case 'column-end': - return EdgePositionRight - case 'column-start': - return EdgePositionLeft - case 'row-end': - return EdgePositionBottom - case 'row-start': - return EdgePositionTop - default: - assertNever(edge) +const GridRuler = React.memo>( + ({ axis, gridRect, rulerVisible, cellFrames, children }) => { + const colorTheme = useColorTheme() + const scale = useEditorState( + Substores.canvas, + (store) => store.editor.canvas.scale, + 'GridRuler scale', + ) + + // Make sure the ruler extends to cover all the cells and not just the grid dimensions. + const cellMaxRect = React.useMemo(() => { + return boundingRectangleArray([gridRect, ...(cellFrames?.flat() ?? [])]) ?? gridRect + }, [cellFrames, gridRect]) + + const columnLeft = gridRect.x + const rowLeft = gridRect.x - RulerMarkerIconSize / scale + const left = axis === 'row' ? rowLeft : columnLeft + + const columnTop = gridRect.y - RulerMarkerIconSize / scale + const rowTop = gridRect.y + const top = axis === 'row' ? rowTop : columnTop + + const width = axis === 'row' ? RulerMarkerIconSize / scale : cellMaxRect.width + const height = axis === 'row' ? cellMaxRect.height : RulerMarkerIconSize / scale + + return ( +
+ {children} +
+ ) + }, +) +GridRuler.displayName = 'GridRuler' + +const RulerMarkerIndicator = React.memo( + (props: { + gridRect: CanvasRectangle + parentGrid: GridContainerProperties + marker: RulerMarkerPositionData + axis: 'row' | 'column' + testID: string + visible: 'visible' | 'not-visible' + onMouseDown?: (event: React.MouseEvent) => void + }) => { + const colorTheme = useColorTheme() + + const canvasScale = useEditorState( + Substores.canvasOffset, + (store) => store.editor.canvas.scale, + 'RulerMarkerIndicator canvasScale', + ) + + const markerType = getRulerMarkerType(props.marker) + const markerColor = getRulerMarkerColor(colorTheme, props.marker) + const markerIcon = rulerMarkerIcons[markerType][props.axis](markerColor, canvasScale) + + const scaledTop = + props.axis === 'row' + ? props.marker.top - props.gridRect.y - RulerMarkerIconSize / canvasScale / 2 + 0.5 + : 0 + + const scaledLeft = + props.axis === 'column' + ? props.marker.left - props.gridRect.x - RulerMarkerIconSize / canvasScale / 2 + 0.5 + : 0 + + const labelText = React.useMemo(() => { + if (props.marker.position == null) { + return null + } + return printPin(props.parentGrid, props.marker.position, props.axis) + }, [props.marker, props.parentGrid, props.axis]) + + const labelClass = 'ruler-marker-label' + + const edge: GridResizeEdge = `${props.axis}-${props.marker.bound}` + + const resizeControlRef = useRefEditorState((store) => + store.editor.canvas.interactionSession?.activeControl.type !== 'GRID_RESIZE_RULER_HANDLE' + ? null + : store.editor.canvas.interactionSession.activeControl, + ) + + return ( +
.${labelClass}`]: { + visibility: resizeControlRef.current?.edge === edge ? 'visible' : 'hidden', + }, + ':hover': { + [`> .${labelClass}`]: { + visibility: 'visible', + }, + }, + }} + > + {markerIcon} + {when( + labelText != null, +
+ {labelText} +
, + )} +
+ ) + }, +) +RulerMarkerIndicator.displayName = 'RulerMarkerIndicator' + +function getRulerMarkerType(marker: RulerMarkerPositionData): RulerMarkerType { + const isAuto = isAutoGridPin(marker.position) + const isSpanStart = marker.bound === 'start' && isGridSpan(marker.position) + const isSpanEnd = marker.bound === 'end' && isGridSpan(marker.position) + + if (marker.markerType === 'other' || marker.markerType === 'target') { + return 'auto-short' + } else if (isSpanStart) { + return 'span-start' + } else if (isSpanEnd) { + return 'span-end' + } else if (isAuto) { + return 'auto' + } else { + return 'pinned' } } -function gridEdgeToCSSCursor(edge: GridResizeEdge): CSSCursor { - switch (edge) { - case 'column-end': - case 'column-start': - return CSSCursor.ColResize - case 'row-end': - case 'row-start': - return CSSCursor.RowResize +function getRulerMarkerColor(colorTheme: ThemeObject, marker: RulerMarkerPositionData): UtopiColor { + switch (marker.markerType) { + case 'selected': + return colorTheme.primary + case 'target': + return colorTheme.brandPurple + case 'other': + return colorTheme.grey65 default: - assertNever(edge) + assertNever(marker.markerType) } } -function gridEdgeToWidthHeight(props: GridResizeEdgeProperties, scale: number): CSSProperties { - return { - width: props.isColumn ? (GRID_RESIZE_HANDLE_SIZES.short * 4) / scale : '100%', - height: props.isRow ? (GRID_RESIZE_HANDLE_SIZES.short * 4) / scale : '100%', - top: props.isStart ? 0 : undefined, - left: props.isStart ? 0 : undefined, - right: props.isEnd ? 0 : undefined, - bottom: props.isEnd ? 0 : undefined, - } +export const GridHelperControls = () => { + const helperControlsStore = React.useContext(HelperControlsStateContext) + return ( + + + + + ) } +GridHelperControls.displayName = 'GridHelperControls' -function gridKeyFromPath(path: ElementPath): string { - return `grid-${EP.toString(path)}` -} +const GridCellOffsetLine = React.memo( + (props: { top: number; left: number; size: number; orientation: 'vertical' | 'horizontal' }) => { + const colorTheme = useColorTheme() -export function getGridPlaceholderDomElement(elementPath: ElementPath): HTMLElement | null { - return document.getElementById(gridKeyFromPath(elementPath)) -} + return ( +
+ ) + }, +) +GridCellOffsetLine.displayName = 'GridCellOffsetLine' -const gridPlaceholderBorder = (color: string) => `2px solid ${color}` +const GridCellOutline = React.memo( + (props: { top: number; left: number; width: number; height: number }) => { + const colorTheme = useColorTheme() + + return ( +
+ ) + }, +) +GridCellOutline.displayName = 'GridCellOutline' + +function gridPositionOrSpanEquals(a: GridPositionOrSpan, b: GridPositionOrSpan): boolean { + if (isGridSpan(a)) { + if (!isGridSpan(b)) { + return false + } + return a.value === b.value + } + if (isCSSKeyword(a)) { + if (!isCSSKeyword(b)) { + return false + } + return a.value === b.value + } + if (!isGridPositionValue(b)) { + return false + } + return a.numericalPosition === b.numericalPosition +} diff --git a/editor/src/components/canvas/controls/grid-measurements.tsx b/editor/src/components/canvas/controls/grid-measurements.tsx new file mode 100644 index 000000000000..ce2418a87189 --- /dev/null +++ b/editor/src/components/canvas/controls/grid-measurements.tsx @@ -0,0 +1,387 @@ +import type { Property } from 'csstype' +import type { CSSProperties } from 'react' +import React from 'react' +import type { Sides } from 'utopia-api/core' +import { sides } from 'utopia-api/core' +import type { ElementPath } from 'utopia-shared/src/types' +import { MetadataUtils } from '../../../core/model/element-metadata-utils' +import { domRectToScaledCanvasRectangle, getRoundingFn } from '../../../core/shared/dom-utils' +import { applicative4Either, defaultEither, isRight, mapEither } from '../../../core/shared/either' +import * as EP from '../../../core/shared/element-path' +import type { + BorderWidths, + ElementInstanceMetadataMap, + GridAutoOrTemplateBase, + GridElementProperties, +} from '../../../core/shared/element-template' +import { + canvasRectangle, + offsetRect, + zeroCanvasRect, + type CanvasRectangle, +} from '../../../core/shared/math-utils' +import { assertNever } from '../../../core/shared/utils' +import { useKeepReferenceEqualityIfPossible } from '../../../utils/react-performance' +import Utils from '../../../utils/utils' +import type { GridIdentifier } from '../../editor/store/editor-state' +import { Substores, useEditorState } from '../../editor/store/store-hook' +import { useMonitorChangesToElements } from '../../editor/store/store-monitor' +import { isStaticGridRepeat, parseCSSLength } from '../../inspector/common/css-utils' +import { + findOriginalGrid, + getGridRelativeContainingBlock, +} from '../canvas-strategies/strategies/grid-helpers' +import { CanvasContainerID } from '../canvas-types' +import { getFromElement } from '../direct-dom-lookups' +import { + applicativeSidesPxTransform, + getGridContainerProperties, + getGridElementProperties, +} from '../dom-walker' +import { addChangeCallback, removeChangeCallback } from '../observers' +import type { ElementPathTrees } from '../../../core/shared/element-path-tree' + +export type GridElementMeasurementHelperData = { + frame: CanvasRectangle + computedStyling: CSSProperties + gridElementProperties: GridElementProperties +} + +export type ElementOrParent = 'parent' | 'element' + +export type GridMeasurementHelperData = { + frame: CanvasRectangle + rows: number + columns: number + cells: number + computedStyling: CSSProperties + gridTemplateColumns: GridAutoOrTemplateBase | null + gridTemplateRows: GridAutoOrTemplateBase | null + gridTemplateColumnsFromProps: GridAutoOrTemplateBase | null + gridTemplateRowsFromProps: GridAutoOrTemplateBase | null + border: BorderWidths + gap: number | null + rowGap: number | null + columnGap: number | null + padding: Sides + element: HTMLElement +} + +export type GridData = GridMeasurementHelperData & { + identifier: GridIdentifier +} + +function getPositionFromComputedStyling(position: string): Property.Position | undefined { + switch (position) { + case '-moz-initial': + case 'inherit': + case 'initial': + case 'revert': + case 'unset': + case '-webkit-sticky': + case 'absolute': + case 'fixed': + case 'relative': + case 'static': + case 'sticky': + return position + default: + return undefined + } +} + +function getGridElementStylingSubset(styling: CSSStyleDeclaration): CSSProperties { + // Fields chosen to not overlap with any others, so as to not make React complain. + return { + position: getPositionFromComputedStyling(styling.position), + gridColumnStart: styling.gridColumnStart, + gridColumnEnd: styling.gridColumnEnd, + gridRowStart: styling.gridRowStart, + gridRowEnd: styling.gridRowEnd, + borderRadius: styling.borderRadius, + } +} + +function getGlobalFrame( + canvasRootContainer: HTMLElement, + targetElement: HTMLElement, + scale: number, +): CanvasRectangle { + const boundingRectangle = targetElement.getBoundingClientRect() + const elementRect = domRectToScaledCanvasRectangle( + boundingRectangle, + 1 / scale, + getRoundingFn('nearest-half'), + ) + const parentRect = domRectToScaledCanvasRectangle( + canvasRootContainer.getBoundingClientRect(), + 1 / scale, + getRoundingFn('nearest-half'), + ) + return Utils.offsetRect(elementRect, Utils.negate(parentRect)) +} + +export function gridElementMeasurementHelperDataFromElement( + scale: number, +): (element: HTMLElement) => GridElementMeasurementHelperData | undefined { + return (element) => { + const canvasRootContainer = document.getElementById(CanvasContainerID) + if (canvasRootContainer == null) { + return undefined + } + + const computedStyle = getComputedStyle(element) + const computedStyling: CSSProperties = getGridElementStylingSubset(computedStyle) + + const frame = getGlobalFrame(canvasRootContainer, element, scale) + + const parentElementStyle = + element.parentElement == null ? null : getComputedStyle(element.parentElement) + const gridContainerProperties = getGridContainerProperties(parentElementStyle) + const gridElementProperties = getGridElementProperties(gridContainerProperties, element.style) + + return { + frame: frame, + computedStyling: computedStyling, + gridElementProperties: gridElementProperties, + } + } +} + +export function getGridElementMeasurementHelperData( + elementPath: ElementPath, + scale: number, +): GridElementMeasurementHelperData | undefined { + return getFromElement(elementPath, gridElementMeasurementHelperDataFromElement(scale), 'element') +} + +export function getGridMeasurementHelperData( + elementPath: ElementPath, + scale: number, + source: ElementOrParent, +): GridMeasurementHelperData | undefined { + return getFromElement(elementPath, gridMeasurementHelperDataFromElement(scale), source) +} + +function getGridStylingSubset(styling: CSSStyleDeclaration): CSSProperties { + // Fields chosen to not overlap with any others, so as to not make React complain. + return { + gridAutoFlow: styling.gridAutoFlow, + gridAutoColumns: styling.gridAutoColumns, + gridAutoRows: styling.gridAutoRows, + gridTemplateColumns: styling.gridTemplateColumns, + gridTemplateRows: styling.gridTemplateRows, + gridColumn: styling.gridColumn, + gridRow: styling.gridRow, + gap: styling.gap, + rowGap: styling.rowGap, + columnGap: styling.columnGap, + justifyContent: styling.justifyContent, + alignContent: styling.alignContent, + padding: styling.padding, + paddingTop: styling.paddingTop, + paddingLeft: styling.paddingLeft, + paddingBottom: styling.paddingBottom, + paddingRight: styling.paddingRight, + borderTop: styling.borderTopWidth, + borderLeft: styling.borderLeftWidth, + borderBottom: styling.borderBottomWidth, + borderRight: styling.borderRightWidth, + } +} + +function getCellsCount(template: GridAutoOrTemplateBase | null): number { + if (template == null) { + return 0 + } + + switch (template.type) { + case 'DIMENSIONS': + return template.dimensions.reduce((acc, cur) => { + return acc + (isStaticGridRepeat(cur) ? cur.times : 1) + }, 0) + case 'FALLBACK': + return 0 + default: + assertNever(template) + } +} + +export function gridMeasurementHelperDataFromElement( + scale: number, +): (element: HTMLElement) => GridMeasurementHelperData | undefined { + return (element) => { + const canvasRootContainer = document.getElementById(CanvasContainerID) + if (canvasRootContainer == null) { + return undefined + } + + const computedStyle = getComputedStyle(element) + + const computedStyling: CSSProperties = getGridStylingSubset(computedStyle) + + const containerGridProperties = getGridContainerProperties(computedStyle) + const containerGridPropertiesFromProps = getGridContainerProperties(element.style) + + const columns = getCellsCount(containerGridProperties.gridTemplateColumns) + const rows = getCellsCount(containerGridProperties.gridTemplateRows) + const borderTopWidth = parseCSSLength(computedStyle.borderTopWidth) + const borderRightWidth = parseCSSLength(computedStyle.borderRightWidth) + const borderBottomWidth = parseCSSLength(computedStyle.borderBottomWidth) + const borderLeftWidth = parseCSSLength(computedStyle.borderLeftWidth) + const border: BorderWidths = { + top: isRight(borderTopWidth) ? borderTopWidth.value.value : 0, + right: isRight(borderRightWidth) ? borderRightWidth.value.value : 0, + bottom: isRight(borderBottomWidth) ? borderBottomWidth.value.value : 0, + left: isRight(borderLeftWidth) ? borderLeftWidth.value.value : 0, + } + const padding = defaultEither( + sides(undefined, undefined, undefined, undefined), + applicative4Either( + applicativeSidesPxTransform, + parseCSSLength(computedStyle.paddingTop), + parseCSSLength(computedStyle.paddingRight), + parseCSSLength(computedStyle.paddingBottom), + parseCSSLength(computedStyle.paddingLeft), + ), + ) + const gap = defaultEither( + null, + mapEither((n) => n.value, parseCSSLength(computedStyle.gap)), + ) + + const rowGap = defaultEither( + null, + mapEither((n) => n.value, parseCSSLength(computedStyle.rowGap)), + ) + + const columnGap = defaultEither( + null, + mapEither((n) => n.value, parseCSSLength(computedStyle.columnGap)), + ) + + const frame = getGlobalFrame(canvasRootContainer, element, scale) + + return { + frame: frame, + gridTemplateColumns: containerGridProperties.gridTemplateColumns, + gridTemplateRows: containerGridProperties.gridTemplateRows, + gridTemplateColumnsFromProps: containerGridPropertiesFromProps.gridTemplateColumns, + gridTemplateRowsFromProps: containerGridPropertiesFromProps.gridTemplateRows, + border: border, + padding: padding, + gap: gap, + rowGap: rowGap, + columnGap: columnGap, + rows: rows, + columns: columns, + cells: columns * rows, + computedStyling: computedStyling, + element: element, + } + } +} + +export function useObserversToWatch(elementPathOrPaths: Array | ElementPath): number { + // Used to trigger extra renders. + const [counter, setCounter] = React.useState(0) + const bumpCounter = React.useCallback(() => { + setCounter((value) => value + 1) + }, []) + + // Need to use the mount count for the callback trigger. + const mountCount = useEditorState( + Substores.canvas, + (store) => store.editor.canvas.mountCount, + 'useObserversToWatch mountCount', + ) + + React.useEffect(() => { + // Add the change callback(s) for the element path or paths. + if (Array.isArray(elementPathOrPaths)) { + for (const elementPath of elementPathOrPaths) { + addChangeCallback(mountCount, elementPath, bumpCounter) + } + } else { + addChangeCallback(mountCount, elementPathOrPaths, bumpCounter) + } + + return function cleanup() { + if (Array.isArray(elementPathOrPaths)) { + for (const elementPath of elementPathOrPaths) { + removeChangeCallback(elementPath, bumpCounter) + } + } else { + removeChangeCallback(elementPathOrPaths, bumpCounter) + } + } + }, [mountCount, elementPathOrPaths, bumpCounter]) + + return counter +} + +export function useGridMeasurementHelperData( + elementPath: ElementPath, + source: ElementOrParent, +): GridMeasurementHelperData | undefined { + const scale = useEditorState( + Substores.canvas, + (store) => store.editor.canvas.scale, + 'useGridMeasurementHelperData scale', + ) + + useMonitorChangesToElements([elementPath]) + + useObserversToWatch(elementPath) + + return useKeepReferenceEqualityIfPossible( + getGridMeasurementHelperData(elementPath, scale, source), + ) +} + +export function useGridElementMeasurementHelperData( + elementPath: ElementPath, +): GridElementMeasurementHelperData | undefined { + const scale = useEditorState( + Substores.canvas, + (store) => store.editor.canvas.scale, + 'useGridMeasurementHelperData scale', + ) + + useMonitorChangesToElements([elementPath]) + + useObserversToWatch(elementPath) + + return useKeepReferenceEqualityIfPossible(getGridElementMeasurementHelperData(elementPath, scale)) +} + +export function calculateGridCellRectangle( + jsxMetadata: ElementInstanceMetadataMap, + pathTree: ElementPathTrees, + path: ElementPath, +): CanvasRectangle | null { + const cellItemMetadata = MetadataUtils.findElementByElementPath(jsxMetadata, path) + if (cellItemMetadata == null) { + return null + } + + const originalGrid = findOriginalGrid(jsxMetadata, EP.parentPath(cellItemMetadata.elementPath)) + if (originalGrid == null) { + return null + } + + const gridMetadata = MetadataUtils.findElementByElementPath(jsxMetadata, originalGrid) + if (gridMetadata == null) { + return null + } + + const coordinateSystemBounds = + cellItemMetadata.specialSizeMeasurements.immediateParentBounds ?? zeroCanvasRect + + const siblings = MetadataUtils.getSiblingsOrdered(jsxMetadata, pathTree, path) + + const calculatedCellBounds = getGridRelativeContainingBlock(gridMetadata, siblings, path, { + forcePositionRelative: true, + }) + return offsetRect(canvasRectangle(calculatedCellBounds), coordinateSystemBounds) +} diff --git a/editor/src/components/canvas/controls/new-canvas-controls.tsx b/editor/src/components/canvas/controls/new-canvas-controls.tsx index 53aa788e9c00..35c4d54ee87b 100644 --- a/editor/src/components/canvas/controls/new-canvas-controls.tsx +++ b/editor/src/components/canvas/controls/new-canvas-controls.tsx @@ -24,6 +24,7 @@ import { ElementContextMenu } from '../../element-context-menu' import type { Mode } from '../../editor/editor-modes' import { isCommentMode, + isInsertMode, isLiveMode, isSelectMode, isSelectModeWithArea, @@ -36,6 +37,7 @@ import type { ResolveFn } from '../../custom-code/code-file' import { useColorTheme } from '../../../uuiui' import { isDragInteractionActive, + isGridDragInteractionActive, pickSelectionEnabled, useMaybeHighlightElement, useSelectAndHover, @@ -71,10 +73,14 @@ import { useSelectionArea } from './selection-area-hooks' import { RemixSceneLabelControl } from './select-mode/remix-scene-label' import { NO_OP } from '../../../core/shared/utils' import { useIsMyProject } from '../../editor/store/collaborative-editing' -import { useStatus } from '../../../../liveblocks.config' import { MultiplayerWrapper } from '../../../utils/multiplayer-wrapper' import { MultiplayerPresence } from '../multiplayer-presence' -import { isFeatureEnabled } from '../../../utils/feature-switches' +import { + GridElementContainingBlocks, + GridHelperControls, + GridMeasurementHelpers, +} from './grid-controls' +import { SizeLabel, StrategySizeLabel } from './select-mode/size-label' export const CanvasControlsContainerID = 'new-canvas-controls-container' @@ -170,7 +176,7 @@ export const NewCanvasControls = React.memo((props: NewCanvasControlsProps) => { canDrop: () => true, } - const [_, drop] = useDrop(dropSpec) + const [, drop] = useDrop(dropSpec) const forwardedRef = React.useCallback( (node: ConnectableElement) => { @@ -275,9 +281,27 @@ interface NewCanvasControlsInnerProps { } const NewCanvasControlsInner = (props: NewCanvasControlsInnerProps) => { + const { + localSelectedViews, + localHighlightedViews, + setLocalSelectedViews, + setLocalHighlightedViews, + } = props + const dispatch = useDispatch() const colorTheme = useColorTheme() - const strategyControls = useGetApplicableStrategyControls() + const { bottomStrategyControls, middleStrategyControls, topStrategyControls } = + useGetApplicableStrategyControls(localSelectedViews) + const sizeLabelInStrategyControls = + topStrategyControls.some((control) => { + return control.control === StrategySizeLabel + }) || + middleStrategyControls.some((control) => { + return control.control === StrategySizeLabel + }) || + bottomStrategyControls.some((control) => { + return control.control === StrategySizeLabel + }) const [inspectorHoveredControls] = useAtom(InspectorHoveredCanvasControls) const [inspectorFocusedControls] = useAtom(InspectorFocusedCanvasControls) @@ -295,6 +319,7 @@ const NewCanvasControlsInner = (props: NewCanvasControlsInnerProps) => { keysPressed, componentMetadata, dragInteractionActive, + gridDragInteractionActive, selectionEnabled, textEditor, editorMode, @@ -313,6 +338,7 @@ const NewCanvasControlsInner = (props: NewCanvasControlsInnerProps) => { keysPressed: store.editor.keysPressed, componentMetadata: getMetadata(store.editor), dragInteractionActive: isDragInteractionActive(store.editor), + gridDragInteractionActive: isGridDragInteractionActive(store.editor), selectionEnabled: pickSelectionEnabled(store.editor.canvas, store.editor.keysPressed), editorMode: store.editor.mode, textEditor: store.editor.canvas.textEditor, @@ -330,12 +356,6 @@ const NewCanvasControlsInner = (props: NewCanvasControlsInnerProps) => { 'NewCanvasControlsInner', ) - const { - localSelectedViews, - localHighlightedViews, - setLocalSelectedViews, - setLocalHighlightedViews, - } = props const cmdKeyPressed = keysPressed['cmd'] ?? false const contextMenuEnabled = !isLiveMode(editorMode) @@ -519,6 +539,11 @@ const NewCanvasControlsInner = (props: NewCanvasControlsInnerProps) => { const resizeStatus = getResizeStatus() + const showStrategyControls = + isMyProject && + isSelectOrInsertMode(editorMode) && + !EP.multiplePathsAllWithTheSameUID(localSelectedViews) + return ( <>
{ {when( resizeStatus !== 'disabled', <> + {when( + showStrategyControls, + <> + {bottomStrategyControls.map((c) => ( + + ))} + , + )} {renderHighlightControls()} {unless(dragInteractionActive, )} - + {unless( + gridDragInteractionActive, + , + )} {when( @@ -588,10 +628,20 @@ const NewCanvasControlsInner = (props: NewCanvasControlsInnerProps) => { {renderTextEditableControls()} {when(isSelectMode(editorMode), )} {when( - isSelectOrInsertMode(editorMode) && - !EP.multiplePathsAllWithTheSameUID(localSelectedViews), + showStrategyControls, <> - {strategyControls.map((c) => ( + {when( + isSelectMode(editorMode) || isInsertMode(editorMode), + , + )} + {middleStrategyControls.map((c) => ( + + ))} + {topStrategyControls.map((c) => ( { , )} {when(isSelectMode(editorMode), )} + {unless( + sizeLabelInStrategyControls, + , + )} , )} @@ -667,5 +721,4 @@ const SelectionAreaRectangle = React.memo( ) }, ) - SelectionAreaRectangle.displayName = 'SelectionAreaRectangle' diff --git a/editor/src/components/canvas/controls/padding-controls.tsx b/editor/src/components/canvas/controls/padding-controls.tsx deleted file mode 100644 index 9ef3c9ad16a2..000000000000 --- a/editor/src/components/canvas/controls/padding-controls.tsx +++ /dev/null @@ -1,127 +0,0 @@ -import React from 'react' -import type { Sides } from 'utopia-api/core' -import type { CanvasRectangle, CanvasPoint } from '../../../core/shared/math-utils' -import { useColorTheme } from '../../../uuiui' - -interface PaddingControlsProps { - padding: Partial | null - frame: CanvasRectangle - canvasOffset: CanvasPoint - scale: number -} - -export const PaddingControls = React.memo((props: PaddingControlsProps) => { - const colorTheme = useColorTheme() - if (props.padding == null) { - return null - } else { - const leftElement = - props.padding.left != null && props.padding.left !== 0 ? ( -
- {props.padding.left} -
- ) : null - const topElement = - props.padding.top != null && props.padding.top !== 0 ? ( -
- {props.padding.top} -
- ) : null - const rightElement = - props.padding.right != null && props.padding.right !== 0 ? ( -
- {props.padding.right} -
- ) : null - const bottomElement = - props.padding.bottom != null && props.padding.bottom !== 0 ? ( -
- {props.padding.bottom} -
- ) : null - const innerDiv = - leftElement == null && - topElement == null && - rightElement == null && - bottomElement == null ? null : ( -
- ) - return <>{[leftElement, topElement, rightElement, bottomElement, innerDiv]} - } -}) diff --git a/editor/src/components/canvas/controls/position-outline.tsx b/editor/src/components/canvas/controls/position-outline.tsx index 8de8bb92a96c..16c42fb4332b 100644 --- a/editor/src/components/canvas/controls/position-outline.tsx +++ b/editor/src/components/canvas/controls/position-outline.tsx @@ -31,8 +31,12 @@ export const PinLines = React.memo(() => { return mapDropNulls((path) => { const element = MetadataUtils.findElementByElementPath(store.editor.jsxMetadata, path) const isAbsolute = MetadataUtils.isPositionAbsolute(element) + const isGridItem = MetadataUtils.isGridItemWithLayoutProvidingGridParent( + store.editor.jsxMetadata, + path, + ) const frame = element?.globalFrame - if (isAbsolute && frame != null && isFiniteRectangle(frame)) { + if (isAbsolute && !isGridItem && frame != null && isFiniteRectangle(frame)) { return { path: path, frame: frame, @@ -78,26 +82,26 @@ interface PositionOutlineProps { export const PositionOutline = React.memo((props: PositionOutlineProps) => { const containingFrame = useContainingFrameForElement(props.path) const attributes = usePropsOrJSXAttributes(props.path) - if (containingFrame != null) { - let pins: PinOutlineProps[] = collectPinOutlines( - attributes, - props.frame, - containingFrame, - props.scale, - ) - return ( - - {pins.map((pin) => ( - - ))} - - ) - } else { + if (containingFrame == null) { return null } + + const pins: PinOutlineProps[] = collectPinOutlines( + attributes, + props.frame, + containingFrame, + props.scale, + ) + return ( + + {pins.map((pin) => ( + + ))} + + ) }) -const usePropsOrJSXAttributes = (path: ElementPath): PropsOrJSXAttributes => { +export const usePropsOrJSXAttributes = (path: ElementPath): PropsOrJSXAttributes => { return useEditorState( Substores.metadata, (store) => { @@ -128,7 +132,7 @@ const useContainingFrameForElement = (path: ElementPath): CanvasRectangle | null ) } -const collectPinOutlines = ( +export const collectPinOutlines = ( attributes: PropsOrJSXAttributes, frame: CanvasRectangle, containingFrame: CanvasRectangle, @@ -182,31 +186,66 @@ const collectPinOutlines = ( return pins } -interface PinOutlineProps { +export interface PinOutlineProps { name: string isHorizontalLine: boolean - startX: number - startY: number - size: number + startX?: number | string + endX?: number | string + startY?: number | string + endY?: number | string + size: number | string scale: number } -const PinOutline = React.memo((props: PinOutlineProps): JSX.Element => { +const PinOutlineUnscaledSize = 1 + +export const PinOutline = React.memo((props: PinOutlineProps): JSX.Element => { const colorTheme = useColorTheme() - const width = props.isHorizontalLine ? props.size : 0 - const height = props.isHorizontalLine ? 0 : props.size + function numberOrStringToSize(value: number | string): string { + return typeof value === 'number' ? `${value}px` : value + } + const width = numberOrStringToSize(props.isHorizontalLine ? props.size : 0) + const height = numberOrStringToSize(props.isHorizontalLine ? 0 : props.size) const borderTop = props.isHorizontalLine - ? `${1 / props.scale}px dashed ${colorTheme.primary.value}` + ? `${PinOutlineUnscaledSize / props.scale}px dashed ${colorTheme.primary.value}` : 'none' const borderLeft = props.isHorizontalLine ? 'none' - : `${1 / props.scale}px dashed ${colorTheme.primary.value}` + : `${PinOutlineUnscaledSize / props.scale}px dashed ${colorTheme.primary.value}` + + function lineStart(startValue?: number | string): string | undefined { + if (startValue == null) { + return undefined + } else { + return `calc(${numberOrStringToSize(startValue)} - ${ + PinOutlineUnscaledSize / 2 / props.scale + }px)` + } + } + + function lineEnd(endValue?: number | string): string | undefined { + if (endValue == null) { + return undefined + } else { + return `calc(${numberOrStringToSize(endValue)} - ${ + PinOutlineUnscaledSize / 2 / props.scale + }px)` + } + } + + const lineLeft = lineStart(props.startX) + const lineTop = lineStart(props.startY) + const lineRight = lineEnd(props.endX) + const lineBottom = lineEnd(props.endY) + return (
): string => `${targets.map(EP.toString).sort()}-absolute-resize-control` @@ -51,20 +41,16 @@ interface AbsoluteResizeControlProps { pathsWereReplaced: boolean } -export const SizeLabelID = 'SizeLabel' -export const SizeLabelTestId = 'SizeLabelTestId' - -function shouldUseSmallElementResizeControl(size: number, scale: number): boolean { - return size <= SmallElementSize / scale -} - export const AbsoluteResizeControl = controlForStrategyMemoized( ({ targets, pathsWereReplaced }: AbsoluteResizeControlProps) => { - const scale = useEditorState( - Substores.canvasOffset, - (store) => store.editor.canvas.scale, - 'AbsoluteResizeControl scale', - ) + const dispatch = useDispatch() + + const canvasOffsetRef = useRefEditorState((store) => store.editor.canvas.roundedCanvasOffset) + const metadataRef = useRefEditorState((store) => store.editor.jsxMetadata) + const selectedElementsRef = useRefEditorState((store) => store.editor.selectedViews) + const elementPathTreeRef = useRefEditorState((store) => store.editor.elementPathTree) + + const { maybeClearHighlightsOnHoverEnd } = useMaybeHighlightElement() const controlRef = useBoundingBox(targets, (ref, safeGappedBoundingBox, realBoundingBox) => { if (isZeroSizedElement(realBoundingBox)) { @@ -78,54 +64,6 @@ export const AbsoluteResizeControl = controlForStrategyMemoized( } }) - const leftRef = useBoundingBox(targets, (ref, boundingBox) => { - const isSmallElement = shouldUseSmallElementResizeControl(boundingBox.width, scale) - const lineSize = ResizeMouseAreaSize / scale - const width = isSmallElement ? lineSize / 2 : lineSize - const offsetLeft = `${-lineSize / 2}px` - const offsetTop = `0px` - - ref.current.style.width = `${width}px` - ref.current.style.transform = `translate(${offsetLeft}, ${offsetTop})` - ref.current.style.height = boundingBox.height + 'px' - }) - const topRef = useBoundingBox(targets, (ref, boundingBox) => { - const isSmallElement = shouldUseSmallElementResizeControl(boundingBox.height, scale) - const lineSize = ResizeMouseAreaSize / scale - const height = isSmallElement ? lineSize / 2 : lineSize - const offsetLeft = `0px` - const offsetTop = `${-lineSize / 2}px` - - ref.current.style.width = boundingBox.width + 'px' - ref.current.style.height = height + 'px' - ref.current.style.transform = `translate(${offsetLeft}, ${offsetTop})` - }) - const rightRef = useBoundingBox(targets, (ref, boundingBox) => { - const isSmallElement = shouldUseSmallElementResizeControl(boundingBox.width, scale) - const lineSize = ResizeMouseAreaSize / scale - const width = isSmallElement ? lineSize / 2 : lineSize - const offsetLeft = isSmallElement ? `0px` : `${-lineSize / 2}px` - const offsetTop = `0px` - - ref.current.style.transform = `translate(${offsetLeft}, ${offsetTop})` - ref.current.style.left = boundingBox.width + 'px' - ref.current.style.width = width + 'px' - ref.current.style.height = boundingBox.height + 'px' - }) - - const bottomRef = useBoundingBox(targets, (ref, boundingBox) => { - const isSmallElement = shouldUseSmallElementResizeControl(boundingBox.height, scale) - const lineSize = ResizeMouseAreaSize / scale - const height = isSmallElement ? lineSize / 2 : lineSize - const offsetLeft = `0px` - const offsetTop = isSmallElement ? `0px` : `${-lineSize / 2}px` - - ref.current.style.transform = `translate(${offsetLeft}, ${offsetTop})` - ref.current.style.top = boundingBox.height + 'px' - ref.current.style.width = boundingBox.width + 'px' - ref.current.style.height = height + 'px' - }) - const topLeftRef = useBoundingBox(targets, NO_OP) const topRightRef = useBoundingBox(targets, (ref, boundingBox) => { ref.current.style.left = boundingBox.width + 'px' @@ -138,12 +76,79 @@ export const AbsoluteResizeControl = controlForStrategyMemoized( ref.current.style.top = boundingBox.height + 'px' }) - const resizeRef = useBoundingBox(targets, (ref, boundingBox) => { - ref.current.style.top = boundingBox.height + 'px' - ref.current.style.left = 0 + 'px' - ref.current.style.width = boundingBox.width + 'px' + const scale = useEditorState( + Substores.canvasOffset, + (store) => store.editor.canvas.scale, + 'AbsoluteResizeControl scale', + ) + const onEdgeMouseDown = React.useCallback( + (position: EdgePosition) => (event: React.MouseEvent) => { + startResizeInteraction(event, dispatch, position, canvasOffsetRef.current, scale) + }, + [dispatch, canvasOffsetRef, scale], + ) + + const onEdgeMouseMove = React.useCallback( + (event: React.MouseEvent) => { + maybeClearHighlightsOnHoverEnd() + event.stopPropagation() + }, + [maybeClearHighlightsOnHoverEnd], + ) + + const onEdgeDoubleClick = React.useCallback( + (direction: 'horizontal' | 'vertical') => () => { + executeFirstApplicableStrategy( + dispatch, + setPropHugStrategies( + metadataRef.current, + selectedElementsRef.current, + elementPathTreeRef.current, + invert(direction), + ), + ) + }, + [dispatch, metadataRef, elementPathTreeRef, selectedElementsRef], + ) + + const resizeEdges = useResizeEdges(targets, { + onEdgeMouseDown, + onEdgeMouseMove, + onEdgeDoubleClick, }) + const canResize = useEditorState( + Substores.metadata, + (store) => { + const metadata = store.editor.jsxMetadata + + let horizontally = true + let vertically = true + let diagonally = true + + for (const element of selectedElementsRef.current) { + if (MetadataUtils.isGridItem(metadata, element)) { + if (isFillOrStretchModeAppliedOnAnySide(metadata, element)) { + diagonally = false + } + if (isFillOrStretchModeAppliedOnSpecificSide(metadata, element, 'horizontal')) { + horizontally = false + } + if (isFillOrStretchModeAppliedOnSpecificSide(metadata, element, 'vertical')) { + vertically = false + } + } + } + + return { + horizontally: horizontally, + vertically: vertically, + diagonally: diagonally, + } + }, + 'AbsoluteResizeControl canResize', + ) + return (
- - - - - - - - - + {when( + canResize.vertically, + + {resizeEdges.top} + {resizeEdges.bottom} + , + )} + {when( + canResize.horizontally, + + {resizeEdges.left} + {resizeEdges.right} + , + )} + {when( + canResize.diagonally, + + + + + + , + )}
) @@ -315,317 +322,6 @@ const ResizePoint = React.memo( ) ResizePoint.displayName = 'ResizePoint' -interface ResizeEdgeProps { - cursor: CSSCursor - direction: 'horizontal' | 'vertical' - position: EdgePosition -} - -const ResizeMouseAreaSize = 10 -const ResizeEdge = React.memo( - React.forwardRef((props, ref) => { - const scale = useEditorState( - Substores.canvasOffset, - (store) => store.editor.canvas.scale, - 'ResizeEdge scale', - ) - const dispatch = useDispatch() - const canvasOffsetRef = useRefEditorState((store) => store.editor.canvas.roundedCanvasOffset) - const metadataRef = useRefEditorState((store) => store.editor.jsxMetadata) - const selectedElementsRef = useRefEditorState((store) => store.editor.selectedViews) - const elementPathTreeRef = useRefEditorState((store) => store.editor.elementPathTree) - - const { maybeClearHighlightsOnHoverEnd } = useMaybeHighlightElement() - - const onEdgeMouseDown = React.useCallback( - (event: React.MouseEvent) => { - startResizeInteraction(event, dispatch, props.position, canvasOffsetRef.current, scale) - }, - [dispatch, props.position, canvasOffsetRef, scale], - ) - - const onMouseMove = React.useCallback( - (event: React.MouseEvent) => { - maybeClearHighlightsOnHoverEnd() - event.stopPropagation() - }, - [maybeClearHighlightsOnHoverEnd], - ) - - const onEdgeDblClick = React.useCallback(() => { - executeFirstApplicableStrategy( - dispatch, - setPropHugStrategies( - metadataRef.current, - selectedElementsRef.current, - elementPathTreeRef.current, - invert(props.direction), - ), - ) - }, [dispatch, metadataRef, props.direction, elementPathTreeRef, selectedElementsRef]) - - return ( -
- ) - }), -) - -const sizeLabel = (state: FixedHugFill['type'], actualSize: number): string => { - switch (state) { - case 'fill': - return 'Fill' - case 'hug': - case 'hug-group': - return 'Hug' - case 'squeeze': - return 'Squeeze' - case 'collapsed': - return 'Collapsed' - case 'fixed': - case 'scaled': - case 'detected': - case 'computed': - return `${actualSize}` - default: - assertNever(state) - } -} - -export type SizeLabelSize = { - type: 'SIZE_LABEL_WITH_DIMENSIONS' - h: string - v: string -} - -function sizeLabelWithDimensions(h: string, v: string): SizeLabelSize { - return { - type: 'SIZE_LABEL_WITH_DIMENSIONS', - h: h, - v: v, - } -} - -export type SizeLabelGroup = { - type: 'SIZE_LABEL_GROUP' -} - -function sizeLabelGroup(): SizeLabelGroup { - return { - type: 'SIZE_LABEL_GROUP', - } -} - -export type SizeLabelChildren = { - type: 'SIZE_LABEL_CHILDREN' - h: string - v: string -} - -function sizeLabelChildren(h: string, v: string): SizeLabelChildren { - return { - type: 'SIZE_LABEL_CHILDREN', - h: h, - v: v, - } -} - -export type SizeLabelContents = SizeLabelSize | SizeLabelGroup | SizeLabelChildren - -function sizeLabelContents( - metadata: ElementInstanceMetadataMap, - selectedElements: Array, - boundingBox: CanvasRectangle | null, - pathsWereReplaced: boolean, -): SizeLabelContents | null { - if (selectedElements.length === 0) { - return null - } - - if (selectedElements.length === 1) { - if (treatElementAsGroupLike(metadata, selectedElements[0])) { - return sizeLabelGroup() - } - - const globalFrame = MetadataUtils.findElementByElementPath( - metadata, - selectedElements[0], - )?.globalFrame - if (globalFrame == null || isInfinityRectangle(globalFrame)) { - return null - } - - const horizontal = - detectFillHugFixedState('horizontal', metadata, selectedElements[0]).fixedHugFill?.type ?? - 'fixed' - const vertical = - detectFillHugFixedState('vertical', metadata, selectedElements[0]).fixedHugFill?.type ?? - 'fixed' - - if (pathsWereReplaced) { - return sizeLabelChildren( - sizeLabel(horizontal, globalFrame.width), - sizeLabel(vertical, globalFrame.height), - ) - } - - return sizeLabelWithDimensions( - sizeLabel(horizontal, globalFrame.width), - sizeLabel(vertical, globalFrame.height), - ) - } - - if (boundingBox != null) { - return sizeLabelWithDimensions(`${boundingBox.width}`, `${boundingBox.height}`) - } - - return null -} - -interface SizeLabelProps { - targets: Array - pathsWereReplaced: boolean -} - -const FontSize = 11 -const PaddingV = 0 -const PaddingH = 2 -const ExplicitHeightHacked = 20 -const BorderRadius = 2 -const SizeLabelMarginTop = 8 - -function getLabelText(label: SizeLabelContents | null): string | null { - if (label == null) { - return null - } - switch (label.type) { - case 'SIZE_LABEL_GROUP': - return 'Group' - case 'SIZE_LABEL_WITH_DIMENSIONS': - return `${label.h} x ${label.v}` - case 'SIZE_LABEL_CHILDREN': - return `(Children) ${label.h} x ${label.v}` - default: - assertNever(label) - } -} - -const SizeLabel = React.memo( - React.forwardRef(({ targets, pathsWereReplaced }, ref) => { - const scale = useEditorState( - Substores.canvas, - (store) => store.editor.canvas.scale, - 'Resizelabel scale', - ) - const colorTheme = useColorTheme() - const metadata = useEditorState( - Substores.metadata, - (store) => getMetadata(store.editor), - 'ResizeLabel metadata', - ) - - const boundingBox = boundingRectangleArray( - targets.map((t) => nullIfInfinity(MetadataUtils.getFrameInCanvasCoords(t, metadata))), - ) - - const label = sizeLabelContents(metadata, targets, boundingBox, pathsWereReplaced) - const labelText = getLabelText(label) - - const [dimmed, setDimmed] = React.useState(false) - - const editorRef = useRefEditorState((store) => ({ - scale: store.editor.canvas.scale, - offset: store.editor.canvas.roundedCanvasOffset, - jsxMetadata: store.editor.jsxMetadata, - elementPathTree: store.editor.elementPathTree, - allElementProps: store.editor.allElementProps, - hiddenInstances: store.editor.hiddenInstances, - })) - - const onMouseEnter = React.useCallback(() => { - const distanceBetweenBoxAndLabel = 10 // px - const labelRect = document.getElementById(SizeLabelID)?.getBoundingClientRect() - if (boundingBox != null && labelRect != null) { - const area = canvasRectangle({ - x: boundingBox.x + (boundingBox.width - labelRect.width) / 2, - y: - boundingBox.y + - boundingBox.height + - distanceBetweenBoxAndLabel / editorRef.current.scale, - width: labelRect.width, - height: labelRect.height, - }) - const elementsUnderLabel = getAllTargetsUnderAreaAABB( - editorRef.current.jsxMetadata, - [], - editorRef.current.hiddenInstances, - 'no-filter', - area, - editorRef.current.elementPathTree, - editorRef.current.allElementProps, - true, - ) - setDimmed(elementsUnderLabel.length > 0) - } - }, [editorRef, boundingBox]) - - const onMouseLeave = React.useCallback(() => { - setDimmed(false) - }, []) - - return ( -
- {when( - labelText != null, -
- {labelText} -
, - )} -
- ) - }), -) -SizeLabel.displayName = 'SizeLabel' - function startResizeInteraction( event: React.MouseEvent, dispatch: EditorDispatch, diff --git a/editor/src/components/canvas/controls/select-mode/border-radius-control.tsx b/editor/src/components/canvas/controls/select-mode/border-radius-control.tsx index f1b733c9811e..7f01a640b7e8 100644 --- a/editor/src/components/canvas/controls/select-mode/border-radius-control.tsx +++ b/editor/src/components/canvas/controls/select-mode/border-radius-control.tsx @@ -1,6 +1,5 @@ import createCachedSelector from 're-reselect' import React from 'react' -import { createSelector } from 'reselect' import { MetadataUtils } from '../../../../core/model/element-metadata-utils' import * as EP from '../../../../core/shared/element-path' import type { CanvasVector, Size } from '../../../../core/shared/math-utils' @@ -15,6 +14,7 @@ import { Substores, useEditorState, useRefEditorState } from '../../../editor/st import type { CanvasSubstate, MetadataSubstate, + StyleInfoSubstate, } from '../../../editor/store/store-hook-substore-types' import { printCSSNumber } from '../../../inspector/common/css-utils' import { metadataSelector } from '../../../inspector/inpector-selectors' @@ -24,7 +24,6 @@ import { BorderRadiusCorners, BorderRadiusHandleHitArea, BorderRadiusHandleSize, - BorderRadiusSides, handlePosition, } from '../../border-radius-control-utils' import CanvasActions from '../../canvas-actions' @@ -41,6 +40,8 @@ import { CanvasOffsetWrapper } from '../canvas-offset-wrapper' import { isZeroSizedElement } from '../outline-utils' import type { CSSNumberWithRenderedValue } from './controls-common' import { CanvasLabel, fallbackEmptyValue } from './controls-common' +import { getActivePlugin } from '../../plugins/style-plugins' +import type { EditorStorePatched } from '../../../editor/store/editor-state' export const CircularHandleTestId = (corner: BorderRadiusCorner): string => `circular-handle-${corner}` @@ -59,13 +60,18 @@ const isDraggingSelector = (store: CanvasSubstate): boolean => { const borderRadiusSelector = createCachedSelector( metadataSelector, + (store: StyleInfoSubstate) => + getActivePlugin(store.editor).styleInfoFactory({ + projectContents: store.editor.projectContents, + jsxMetadata: store.editor.jsxMetadata, + }), (_: MetadataSubstate, x: ElementPath) => x, - (metadata, selectedElement) => { + (metadata, styleInfoReader, selectedElement) => { const element = MetadataUtils.findElementByElementPath(metadata, selectedElement) if (element == null) { return null } - return borderRadiusFromElement(element) + return borderRadiusFromElement(element, styleInfoReader(selectedElement)) }, )((_, x) => EP.toString(x)) @@ -117,7 +123,7 @@ export const BorderRadiusControl = controlForStrategyMemoized borderRadiusSelector(store, selectedElement), 'BorderRadiusControl borderRadius', ) diff --git a/editor/src/components/canvas/controls/select-mode/canvas-strategy-picker.spec.browser2.tsx b/editor/src/components/canvas/controls/select-mode/canvas-strategy-picker.spec.browser2.tsx index 1982d17b690b..feb540d8191e 100644 --- a/editor/src/components/canvas/controls/select-mode/canvas-strategy-picker.spec.browser2.tsx +++ b/editor/src/components/canvas/controls/select-mode/canvas-strategy-picker.spec.browser2.tsx @@ -32,7 +32,7 @@ const BestStrategy: CanvasStrategy = { icon: { category: 'tools', type: 'pointer' }, controlsToRender: [], fitness: 10, - apply: () => strategyApplicationResult([]), + apply: () => strategyApplicationResult([], []), } const AverageStrategy: CanvasStrategy = { @@ -42,7 +42,7 @@ const AverageStrategy: CanvasStrategy = { icon: { category: 'tools', type: 'pointer' }, controlsToRender: [], fitness: 5, - apply: () => strategyApplicationResult([]), + apply: () => strategyApplicationResult([], []), } const WorstStrategy: CanvasStrategy = { @@ -52,7 +52,7 @@ const WorstStrategy: CanvasStrategy = { icon: { category: 'tools', type: 'pointer' }, controlsToRender: [], fitness: 1, - apply: () => strategyApplicationResult([]), + apply: () => strategyApplicationResult([], []), } const UnfitStrategy: CanvasStrategy = { @@ -62,7 +62,7 @@ const UnfitStrategy: CanvasStrategy = { icon: { category: 'tools', type: 'pointer' }, controlsToRender: [], fitness: 0, - apply: () => strategyApplicationResult([]), + apply: () => strategyApplicationResult([], []), } // Deliberately not in sorted order diff --git a/editor/src/components/canvas/controls/select-mode/controls-common.tsx b/editor/src/components/canvas/controls/select-mode/controls-common.tsx index d625dcc0fb06..befbf9c125c0 100644 --- a/editor/src/components/canvas/controls/select-mode/controls-common.tsx +++ b/editor/src/components/canvas/controls/select-mode/controls-common.tsx @@ -104,7 +104,7 @@ interface CanvasLabelProps { scale: number color: string textColor: string - value: string | number + value: string | number | null onMouseDown?: React.MouseEventHandler testId?: string } @@ -168,17 +168,26 @@ export function useHoverWithDelay( ): [React.MouseEventHandler, React.MouseEventHandler] { const fadeInTimeout = React.useRef(null) - const onHoverEnd = () => { - if (fadeInTimeout.current != null) { - clearTimeout(fadeInTimeout.current) - } - fadeInTimeout.current = null - update(false) - } + const onHoverEnd = React.useCallback( + (e: React.MouseEvent) => { + if (fadeInTimeout.current != null) { + clearTimeout(fadeInTimeout.current) + } + fadeInTimeout.current = null + update(false) + }, + [update], + ) - const onHoverStart = () => { - fadeInTimeout.current = setTimeout(() => update(true), delay) - } + const onHoverStart = React.useCallback( + (e: React.MouseEvent) => { + if (fadeInTimeout.current != null) { + clearTimeout(fadeInTimeout.current) + } + fadeInTimeout.current = setTimeout(() => update(true), delay) + }, + [update, delay], + ) return [onHoverStart, onHoverEnd] } diff --git a/editor/src/components/canvas/controls/select-mode/flex-gap-control.test-utils.tsx b/editor/src/components/canvas/controls/select-mode/flex-gap-control.test-utils.tsx index a17811afdd54..a5d6243d21ae 100644 --- a/editor/src/components/canvas/controls/select-mode/flex-gap-control.test-utils.tsx +++ b/editor/src/components/canvas/controls/select-mode/flex-gap-control.test-utils.tsx @@ -38,6 +38,7 @@ export async function checkFlexGapHandlesPositionedCorrectly( const localFrame = MetadataUtils.getLocalFrame( selectedElement.elementPath, editorState.jsxMetadata, + null, ) const selectedElementFrame = zeroRectIfNullOrInfinity(localFrame) // If this is a flex element and it has a gap specified. diff --git a/editor/src/components/canvas/controls/select-mode/flex-gap-control.tsx b/editor/src/components/canvas/controls/select-mode/flex-gap-control.tsx index ee073c9b828a..6f0121923126 100644 --- a/editor/src/components/canvas/controls/select-mode/flex-gap-control.tsx +++ b/editor/src/components/canvas/controls/select-mode/flex-gap-control.tsx @@ -22,10 +22,11 @@ import CanvasActions from '../../canvas-actions' import { controlForStrategyMemoized } from '../../canvas-strategies/canvas-strategy-types' import { createInteractionViaMouse, flexGapHandle } from '../../canvas-strategies/interaction-state' import { windowToCanvasCoordinates } from '../../dom-lookup' +import type { FlexGapData } from '../../gap-utils' import { cursorFromFlexDirection, - maybeFlexGapData, gapControlBoundsFromMetadata, + maybeFlexGapData, recurseIntoChildrenOfMapOrFragment, } from '../../gap-utils' import { CanvasOffsetWrapper } from '../canvas-offset-wrapper' @@ -46,6 +47,7 @@ import { reverseJustifyContent, } from '../../../../core/model/flex-utils' import { optionalMap } from '../../../../core/shared/optional-utils' +import { getActivePlugin } from '../../plugins/style-plugins' interface FlexGapControlProps { selectedElement: ElementPath @@ -130,21 +132,39 @@ export const FlexGapControl = controlForStrategyMemoized((p elementPathTrees, selectedElement, ) - const flexGap = maybeFlexGapData(metadata, selectedElement) - if (flexGap == null) { - return null - } - const flexGapValue = updatedGapValue ?? flexGap.value + const flexGapFromEditor = useEditorState( + Substores.fullStore, + (store) => + maybeFlexGapData( + getActivePlugin(store.editor).styleInfoFactory({ + projectContents: store.editor.projectContents, + jsxMetadata: store.editor.jsxMetadata, + })(selectedElement), + MetadataUtils.findElementByElementPath(store.editor.jsxMetadata, selectedElement), + ), + 'FlexGapControl flexGapFromEditor', + ) - const controlBounds = gapControlBoundsFromMetadata( - metadata, - selectedElement, - children.map((c) => c.elementPath), - flexGapValue.renderedValuePx, - flexGap.direction, + const flexGap: FlexGapData | null = optionalMap( + (gap) => ({ + direction: gap.direction, + value: updatedGapValue ?? gap.value, + }), + flexGapFromEditor, ) + const controlBounds = + flexGapFromEditor == null || flexGap == null + ? null + : gapControlBoundsFromMetadata( + metadata, + selectedElement, + children.map((c) => c.elementPath), + flexGap.value.renderedValuePx, + flexGapFromEditor.direction, + ) + const contentArea = React.useMemo((): Size => { function valueForDimension( directions: FlexDirection[], @@ -157,12 +177,12 @@ export const FlexGapControl = controlForStrategyMemoized((p const bounds = boundingRectangleArray( mapDropNulls((c) => { - const localFrame = MetadataUtils.getLocalFrame(c.elementPath, metadata) + const localFrame = MetadataUtils.getLocalFrame(c.elementPath, metadata, null) return localFrame != null && isFiniteRectangle(localFrame) ? localFrame : null }, children), ) - if (bounds == null) { + if (bounds == null || flexGap == null) { return zeroSize } else { return { @@ -170,17 +190,17 @@ export const FlexGapControl = controlForStrategyMemoized((p ['column', 'column-reverse'], flexGap.direction, bounds.width, - flexGapValue.renderedValuePx, + flexGap.value.renderedValuePx, ), height: valueForDimension( ['row', 'row-reverse'], flexGap.direction, bounds.height, - flexGapValue.renderedValuePx, + flexGap.value.renderedValuePx, ), } } - }, [children, flexGap.direction, flexGapValue.renderedValuePx, metadata]) + }, [children, flexGap, metadata]) const justifyContent = React.useMemo(() => { return ( @@ -196,12 +216,16 @@ export const FlexGapControl = controlForStrategyMemoized((p ) }, [metadata, selectedElement]) + if (flexGap == null || controlBounds == null) { + return null + } + return (
{controlBounds.map(({ bounds, path: p }) => { const path = EP.toString(p) - const valueToShow = fallbackEmptyValue(flexGapValue) + const valueToShow = fallbackEmptyValue(flexGap.value) return ( ((props) => { , )} @@ -391,7 +415,7 @@ const GapControlSegment = React.memo((props) => {
@@ -402,7 +426,7 @@ const GapControlSegment = React.memo((props) => { function handleDimensions(flexDirection: FlexDirection, scale: number): Size { if (flexDirection === 'row' || flexDirection === 'row-reverse') { - return size(3 / scale, 12 / scale) + return size(4 / scale, 12 / scale) } if (flexDirection === 'column' || flexDirection === 'column-reverse') { return size(12 / scale, 4 / scale) diff --git a/editor/src/components/canvas/controls/select-mode/grid-gap-control-component.tsx b/editor/src/components/canvas/controls/select-mode/grid-gap-control-component.tsx new file mode 100644 index 000000000000..7d78d0c8f46f --- /dev/null +++ b/editor/src/components/canvas/controls/select-mode/grid-gap-control-component.tsx @@ -0,0 +1,483 @@ +import type { CSSProperties } from 'react' +import React from 'react' +import { createArrayWithLength, interleaveArray } from '../../../../core/shared/array-utils' +import type { GridAutoOrTemplateBase } from '../../../../core/shared/element-template' +import type { Size } from '../../../../core/shared/math-utils' +import { size } from '../../../../core/shared/math-utils' +import type { ElementPath } from '../../../../core/shared/project-file-types' +import { assertNever } from '../../../../core/shared/utils' +import { when } from '../../../../utils/react-conditionals' +import type { UtopiColor } from '../../../../uuiui' +import { useColorTheme, UtopiaStyles } from '../../../../uuiui' +import { CSSCursor } from '../../../../uuiui-deps' +import { useDispatch } from '../../../editor/store/dispatch-context' +import { Substores, useEditorState, useRefEditorState } from '../../../editor/store/store-hook' +import { + cssNumber, + printCSSNumber, + stringifyGridDimension, +} from '../../../inspector/common/css-utils' +import type { Axis } from '../../gap-utils' +import { maybeGridGapData } from '../../gap-utils' +import { CanvasOffsetWrapper } from '../canvas-offset-wrapper' +import { getNullableAutoOrTemplateBaseString, useGridData } from '../grid-controls-for-strategies' +import { getGridHelperStyleMatchingTargetGrid } from '../grid-controls-helpers' +import type { CSSNumberWithRenderedValue } from './controls-common' +import { CanvasLabel, PillHandle, useHoverWithDelay } from './controls-common' +import { startGapControlInteraction } from './grid-gap-control-helpers' +import type { AlignContent, FlexJustifyContent } from '../../../inspector/inspector-common' +import { gridContainerIdentifier } from '../../../editor/store/editor-state' +import type { GridData } from '../grid-measurements' + +export interface GridGapControlProps { + selectedElement: ElementPath + updatedGapValueRow: CSSNumberWithRenderedValue | null + updatedGapValueColumn: CSSNumberWithRenderedValue | null +} + +export const GridGapControlTestId = 'grid-gap-control' +export const GridGapControlHandleTestId = 'grid-gap-control-handle' +// background delay when hovering the gap +export const GridGapBackgroundHoverDelay = 1500 +// background delay when hovering the handle itself +const GapHandleBackgroundHoverDelay = 750 +// px threshold for showing the gap handles even without hovering the gap itself +// (for narrow gaps) +const GapHandleGapWidthThreshold = 10 + +const DefaultGapControlSizeConstants: GapControlSizeConstants = { + borderWidth: 1, + paddingIndicatorOffset: 10, + hitAreaPadding: 5, +} + +export const GridGapControlComponent = React.memo((props) => { + const { selectedElement } = props + + const dispatch = useDispatch() + const scale = useEditorState( + Substores.canvas, + (store) => store.editor.canvas.scale, + 'GridGapControlComponent scale', + ) + + const elementHovered = + useEditorState( + Substores.highlightedHoveredViews, + (store) => store.editor.hoveredViews.includes(selectedElement), + 'GridGapControlComponent elementHovered', + ) ?? false + + const grid = useGridData([gridContainerIdentifier(selectedElement)]).at(0) + + const activeDraggingAxis = useEditorState( + Substores.canvas, + (store) => + store.editor.canvas.interactionSession?.activeControl.type === 'GRID_GAP_HANDLE' + ? store.editor.canvas.interactionSession?.activeControl.axis + : null, + 'GridGapControl isDragging', + ) + + const canvasOffset = useRefEditorState((store) => store.editor.canvas.roundedCanvasOffset) + + const axisMouseDownHandler = React.useCallback( + (e: React.MouseEvent, axis: Axis) => { + startGapControlInteraction(e, dispatch, canvasOffset.current, scale, axis) + }, + [canvasOffset, dispatch, scale], + ) + const rowMouseDownHandler = React.useCallback( + (e: React.MouseEvent) => axisMouseDownHandler(e, 'row'), + [axisMouseDownHandler], + ) + + const columnMouseDownHandler = React.useCallback( + (e: React.MouseEvent) => axisMouseDownHandler(e, 'column'), + [axisMouseDownHandler], + ) + + const [hoveredAxis, setHoveredAxis] = React.useState<'row' | 'column'>('row') + const onMouseOverRow = React.useCallback(() => setHoveredAxis('row'), []) + const onMouseOverColumn = React.useCallback(() => setHoveredAxis('column'), []) + + const gridGap = useEditorState( + Substores.metadata, + (store) => maybeGridGapData(store.editor.jsxMetadata, selectedElement), + 'GridGapControlComponent gridGap', + ) + + if (grid == null || gridGap == null) { + return null + } + + return ( + + + + + ) +}) + +export const GridPaddingOutlineForDimension = (props: { + grid: GridData + dimension: 'rows' | 'columns' + onMouseDown: (e: React.MouseEvent) => void + beingDragged: boolean + onMouseOver: () => void + zIndexPriority: boolean + gridGap: CSSNumberWithRenderedValue + elementHovered: boolean + draggedOutlineColor?: UtopiColor +}) => { + const { + grid, + gridGap, + dimension, + onMouseDown, + beingDragged, + onMouseOver, + zIndexPriority, + elementHovered, + draggedOutlineColor, + } = props + + let style: CSSProperties = { + ...getGridHelperStyleMatchingTargetGrid(grid), + zIndex: zIndexPriority ? 1 : undefined, + gap: undefined, + rowGap: undefined, + columnGap: undefined, + gridTemplateRows: + dimension === 'rows' + ? tweakTrackListByInsertingGap(grid.gridTemplateRows, grid.rowGap ?? grid.gap) + : '1fr', + gridTemplateColumns: + dimension === 'columns' + ? tweakTrackListByInsertingGap(grid.gridTemplateColumns, grid.columnGap ?? grid.gap) + : '1fr', + overflow: 'hidden', + } + + const length = 2 * (dimension === 'rows' ? grid.rows : grid.columns) - 1 + + return ( +
+ {createArrayWithLength(length, (index) => { + const hide = index === 0 || index === length - 1 || index % 2 === 0 + return ( + + ) + })} +
+ ) +} + +const GridRowOrColumnHighlight = (props: { + gapId: string + onMouseDown: React.MouseEventHandler + hide: boolean + axis: 'row' | 'column' + template: string | undefined + numberOfHandles: number + gap: number | null + gapValue: CSSNumberWithRenderedValue | null + beingDragged: boolean + onMouseOver: () => void + elementHovered: boolean + gridJustifyContent: string | undefined + gridAlignContent: string | undefined + draggedOutlineColor?: UtopiColor +}) => { + const { + gapId, + onMouseDown, + hide, + axis, + template, + gap, + gapValue, + numberOfHandles, + beingDragged, + onMouseOver, + elementHovered, + gridJustifyContent, + gridAlignContent, + draggedOutlineColor, + } = props + + const colorTheme = useColorTheme() + const canvasScale = useEditorState( + Substores.canvas, + (store) => store.editor.canvas.scale, + 'GridRowHighlight canvasScale', + ) + + const lineWidth = 1 / canvasScale + + const outlineColor = beingDragged + ? (draggedOutlineColor ?? colorTheme.brandNeonOrange).value + : 'transparent' + + const [backgroundShown, setBackgroundShown] = React.useState(false) + + const [gapIsHovered, setGapIsHovered] = React.useState(false) + const [handleIsHovered, setHandleIsHovered] = React.useState(null) + const [hoverStart, hoverEnd] = useHoverWithDelay(GridGapBackgroundHoverDelay, setBackgroundShown) + + const onGapHover = React.useCallback( + (e: React.MouseEvent) => { + onMouseOver() + setGapIsHovered(true) + }, + [onMouseOver], + ) + + const onHandleHover = React.useCallback( + (e: React.MouseEvent, index: number) => { + hoverStart(e) + setHandleIsHovered(index) + }, + [hoverStart], + ) + + const onGapHoverEnd = React.useCallback( + (e: React.MouseEvent) => { + hoverEnd(e) + setGapIsHovered(false) + setHandleIsHovered(null) + }, + [hoverEnd], + ) + + const shouldShowBackground = !beingDragged && backgroundShown + + if (gapValue == null) { + return null + } + + return ( +
+ {createArrayWithLength(numberOfHandles, (i) => ( + + ))} +
+ ) +} + +function tweakTrackListByInsertingGap( + trackList: GridAutoOrTemplateBase | null, + gap: number | null, +): string | undefined { + if (trackList == null) { + return undefined + } + + if (trackList.type === 'FALLBACK') { + throw new Error('Cannot insert gap into fallback') + } + + const gapTrack = gap == null ? `0px` : `${gap}px` + + return interleaveArray(trackList.dimensions.map(stringifyGridDimension), gapTrack).join(' ') +} + +interface GapControlSizeConstants { + paddingIndicatorOffset: number + hitAreaPadding: number + borderWidth: number +} + +const gapControlSizeConstants = ( + constants: GapControlSizeConstants, + scale: number, +): GapControlSizeConstants => ({ + borderWidth: constants.borderWidth / scale, + paddingIndicatorOffset: constants.paddingIndicatorOffset / scale, + hitAreaPadding: constants.hitAreaPadding / scale, +}) + +type GridGapHandleProps = { + gapId: string + index: number + scale: number + gapValue: CSSNumberWithRenderedValue + axis: Axis + onMouseDown: React.MouseEventHandler + isDragging: boolean + onHandleHoverStartInner: (e: React.MouseEvent, index: number) => void + indicatorShown: number | null + elementHovered: boolean + gapIsHovered: boolean + backgroundShown: boolean +} +export function GridGapHandle({ + gapId, + index, + scale, + gapValue, + axis, + onMouseDown, + onHandleHoverStartInner, + isDragging, + indicatorShown, + elementHovered, + gapIsHovered, + backgroundShown, +}: GridGapHandleProps) { + const { width, height } = handleDimensions(axis, scale) + const { hitAreaPadding, paddingIndicatorOffset, borderWidth } = gapControlSizeConstants( + DefaultGapControlSizeConstants, + scale, + ) + const colorTheme = useColorTheme() + const shouldShowIndicator = !isDragging && indicatorShown === index + let shouldShowHandle = !isDragging && gapIsHovered + // show the handle also if the gap is too narrow to hover + if (!gapIsHovered && !backgroundShown) { + shouldShowHandle = elementHovered && gapValue.renderedValuePx <= GapHandleGapWidthThreshold + } + const handleOpacity = gapIsHovered ? 1 : 0.3 + + const onHandleHoverStart = React.useCallback( + (e: React.MouseEvent) => { + onHandleHoverStartInner(e, index) + }, + [onHandleHoverStartInner, index], + ) + + return ( +
+
+ {when( + shouldShowIndicator, + , + )} +
+ +
+ ) +} + +function handleDimensions(axis: Axis, scale: number): Size { + if (axis === 'row') { + return size(12 / scale, 4 / scale) + } + if (axis === 'column') { + return size(4 / scale, 12 / scale) + } + assertNever(axis) +} diff --git a/editor/src/components/canvas/controls/select-mode/grid-gap-control-helpers.tsx b/editor/src/components/canvas/controls/select-mode/grid-gap-control-helpers.tsx new file mode 100644 index 000000000000..296b24d5090e --- /dev/null +++ b/editor/src/components/canvas/controls/select-mode/grid-gap-control-helpers.tsx @@ -0,0 +1,35 @@ +import type { CanvasVector } from '../../../../core/shared/math-utils' +import { windowPoint } from '../../../../core/shared/math-utils' +import { Modifier } from '../../../../utils/modifiers' +import type { EditorDispatch } from '../../../editor/action-types' +import CanvasActions from '../../canvas-actions' +import { createInteractionViaMouse, gridGapHandle } from '../../canvas-strategies/interaction-state' +import { windowToCanvasCoordinates } from '../../dom-lookup' +import type { Axis } from '../../gap-utils' + +export function startGapControlInteraction( + event: React.MouseEvent, + dispatch: EditorDispatch, + canvasOffset: CanvasVector, + scale: number, + axis: Axis, +) { + if (event.buttons === 1 && event.button !== 2) { + event.stopPropagation() + const canvasPositions = windowToCanvasCoordinates( + scale, + canvasOffset, + windowPoint({ x: event.nativeEvent.x, y: event.nativeEvent.y }), + ) + dispatch([ + CanvasActions.createInteractionSession( + createInteractionViaMouse( + canvasPositions.canvasPositionRaw, + Modifier.modifiersForEvent(event), + gridGapHandle(axis), + 'zero-drag-not-permitted', + ), + ), + ]) + } +} diff --git a/editor/src/components/canvas/controls/select-mode/grid-gap-control.tsx b/editor/src/components/canvas/controls/select-mode/grid-gap-control.tsx index e0601c4436db..6cb1b9031ceb 100644 --- a/editor/src/components/canvas/controls/select-mode/grid-gap-control.tsx +++ b/editor/src/components/canvas/controls/select-mode/grid-gap-control.tsx @@ -1,510 +1,4 @@ -import React, { useState } from 'react' -import type { CanvasRectangle, CanvasVector, Size } from '../../../../core/shared/math-utils' -import { size, windowPoint } from '../../../../core/shared/math-utils' -import type { ElementPath } from '../../../../core/shared/project-file-types' -import { assertNever } from '../../../../core/shared/utils' -import { Modifier } from '../../../../utils/modifiers' -import { when } from '../../../../utils/react-conditionals' -import { useColorTheme, UtopiaStyles } from '../../../../uuiui' -import type { EditorDispatch } from '../../../editor/action-types' -import { useDispatch } from '../../../editor/store/dispatch-context' -import { Substores, useEditorState, useRefEditorState } from '../../../editor/store/store-hook' -import type { CSSNumber } from '../../../inspector/common/css-utils' -import { printCSSNumber } from '../../../inspector/common/css-utils' -import CanvasActions from '../../canvas-actions' import { controlForStrategyMemoized } from '../../canvas-strategies/canvas-strategy-types' -import { createInteractionViaMouse, gridGapHandle } from '../../canvas-strategies/interaction-state' -import { windowToCanvasCoordinates } from '../../dom-lookup' -import type { Axis } from '../../gap-utils' -import { maybeGridGapData, gridGapControlBoundsFromMetadata } from '../../gap-utils' -import { CanvasOffsetWrapper } from '../canvas-offset-wrapper' -import type { CSSNumberWithRenderedValue } from './controls-common' -import { CanvasLabel, fallbackEmptyValue, PillHandle, useHoverWithDelay } from './controls-common' -import { CSSCursor } from '../../../../uuiui-deps' -import { useBoundingBox } from '../bounding-box-hooks' -import { isZeroSizedElement } from '../outline-utils' -import { createArrayWithLength } from '../../../../core/shared/array-utils' -import { useGridData } from '../grid-controls' +import { GridGapControlComponent } from './grid-gap-control-component' -interface GridGapControlProps { - selectedElement: ElementPath - updatedGapValueRow: CSSNumberWithRenderedValue | null - updatedGapValueColumn: CSSNumberWithRenderedValue | null -} - -export const GridGapControlTestId = 'grid-gap-control' -export const GridGapControlHandleTestId = 'grid-gap-control-handle' -// background delay when hovering the gap -const GridGapBackgroundHoverDelay = 1500 -// background delay when hovering the handle itself -const GapHandleBackgroundHoverDelay = 750 -// px threshold for showing the gap handles even without hovering the gap itself -// (for narrow gaps) -const GapHandleGapWidthThreshold = 10 - -export const GridGapControl = controlForStrategyMemoized((props) => { - const { selectedElement, updatedGapValueRow, updatedGapValueColumn } = props - const colorTheme = useColorTheme() - const accentColor = colorTheme.gapControlsBg.value - - const hoveredViews = useEditorState( - Substores.highlightedHoveredViews, - (store) => store.editor.hoveredViews, - 'GridGapControl hoveredViews', - ) - - const [elementHovered, setElementHovered] = useState(false) - const [rowAxisHandleHovererd, setRowAxisHandleHovered] = useState(false) - const [columnAxisHandleHovererd, setColumnAxisHandleHovered] = useState(false) - - const [rowBackgroundShown, setRowBackgroundShown] = React.useState(false) - const [columnBackgroundShown, setColumnBackgroundShown] = React.useState(false) - - const [rowControlHoverStart, rowControlHoverEnd] = useHoverWithDelay( - GridGapBackgroundHoverDelay, - setRowBackgroundShown, - ) - const [columnControlHoverStart, columnControlHoverEnd] = useHoverWithDelay( - GridGapBackgroundHoverDelay, - setColumnBackgroundShown, - ) - - const [rowAxisHandleHoverStart, rowAxisHandleHoverEnd] = useHoverWithDelay( - GapHandleBackgroundHoverDelay, - setRowAxisHandleHovered, - ) - - const [columnAxisHandleHoverStart, columnAxisHandleHoverEnd] = useHoverWithDelay( - GapHandleBackgroundHoverDelay, - setColumnAxisHandleHovered, - ) - - const timeoutRef = React.useRef(null) - React.useEffect(() => { - const timeoutHandle = timeoutRef.current - if (timeoutHandle != null) { - clearTimeout(timeoutHandle) - } - - if (hoveredViews.includes(selectedElement)) { - timeoutRef.current = setTimeout(() => setElementHovered(true), 200) - } else { - setElementHovered(false) - } - }, [hoveredViews, selectedElement]) - - const dispatch = useDispatch() - const scale = useEditorState( - Substores.canvas, - (store) => store.editor.canvas.scale, - 'GridGapControl scale', - ) - const metadata = useEditorState( - Substores.metadata, - (store) => store.editor.jsxMetadata, - 'GridGapControl metadata', - ) - - const isDragging = useEditorState( - Substores.canvas, - (store) => store.editor.canvas.interactionSession?.activeControl.type === 'GRID_GAP_HANDLE', - 'GridGapControl isDragging', - ) - - const canvasOffset = useRefEditorState((store) => store.editor.canvas.roundedCanvasOffset) - - const axisMouseDownHandler = React.useCallback( - (e: React.MouseEvent, axis: Axis) => { - startInteraction(e, dispatch, canvasOffset.current, scale, axis) - }, - [canvasOffset, dispatch, scale], - ) - const rowMouseDownHandler = React.useCallback( - (e: React.MouseEvent) => axisMouseDownHandler(e, 'row'), - [axisMouseDownHandler], - ) - - const columnMouseDownHandler = React.useCallback( - (e: React.MouseEvent) => axisMouseDownHandler(e, 'column'), - [axisMouseDownHandler], - ) - - const gridGap = maybeGridGapData(metadata, selectedElement) - if (gridGap == null) { - return null - } - - const controlRef = useBoundingBox( - [selectedElement], - (ref, safeGappedBoundingBox, realBoundingBox) => { - if (isZeroSizedElement(realBoundingBox)) { - ref.current.style.display = 'none' - } else { - ref.current.style.display = 'block' - ref.current.style.left = safeGappedBoundingBox.x + 'px' - ref.current.style.top = safeGappedBoundingBox.y + 'px' - ref.current.style.width = safeGappedBoundingBox.width + 'px' - ref.current.style.height = safeGappedBoundingBox.height + 'px' - } - }, - ) - - const gridGapRow = updatedGapValueRow ?? gridGap.row - const gridGapColumn = updatedGapValueColumn ?? gridGap.column - - const gridRowColumnInfo = useGridData([selectedElement]) - - const controlBounds = gridGapControlBoundsFromMetadata( - selectedElement, - gridRowColumnInfo[0], - { - row: fallbackEmptyValue(gridGapRow), - column: fallbackEmptyValue(gridGapColumn), - }, - scale, - ) - - return ( - -
- {controlBounds.gaps.map(({ gap, bounds, axis, gapId }) => { - const gapControlProps = { - mouseDownHandler: axisMouseDownHandler, - gapId: gapId, - bounds: bounds, - accentColor: accentColor, - scale: scale, - isDragging: isDragging, - axis: axis, - gapValue: gap, - internalGrid: { - gridTemplateRows: controlBounds.gridTemplateRows, - gridTemplateColumns: controlBounds.gridTemplateColumns, - gap: axis === 'row' ? controlBounds.gapValues.column : controlBounds.gapValues.row, - }, - elementHovered: elementHovered, - handles: axis === 'row' ? controlBounds.columns : controlBounds.rows, - } - if (axis === 'row') { - return ( - - ) - } - return ( - - ) - })} -
-
- ) -}) - -interface GapControlSizeConstants { - dragBorderWidth: number - paddingIndicatorOffset: number - hitAreaPadding: number - borderWidth: number -} - -const DefaultGapControlSizeConstants: GapControlSizeConstants = { - dragBorderWidth: 1, - borderWidth: 1, - paddingIndicatorOffset: 10, - hitAreaPadding: 5, -} - -const gapControlSizeConstants = ( - constants: GapControlSizeConstants, - scale: number, -): GapControlSizeConstants => ({ - dragBorderWidth: constants.dragBorderWidth / scale, - borderWidth: constants.borderWidth / scale, - paddingIndicatorOffset: constants.paddingIndicatorOffset / scale, - hitAreaPadding: constants.hitAreaPadding / scale, -}) - -interface GridGapControlSegmentProps { - onMouseDown: React.MouseEventHandler - gapHoverStart: React.MouseEventHandler - gapHoverEnd: React.MouseEventHandler - onHandleHoverStart: React.MouseEventHandler - onHandleHoverEnd: React.MouseEventHandler - bounds: CanvasRectangle - axis: Axis - gapValue: CSSNumber - elementHovered: boolean - gapId: string - accentColor: string - scale: number - isDragging: boolean - backgroundShown: boolean - handles: number - internalGrid: { - gridTemplateRows: string - gridTemplateColumns: string - gap: CSSNumber - } -} - -const GapControlSegment = React.memo((props) => { - const { - gapHoverStart, - gapHoverEnd, - onHandleHoverStart, - onHandleHoverEnd, - bounds, - isDragging, - accentColor: accentColor, - scale, - gapId, - backgroundShown, - axis, - handles, - internalGrid, - } = props - - const [indicatorShown, setIndicatorShown] = React.useState(null) - const [gapIsHovered, setGapIsHovered] = React.useState(false) - - const { dragBorderWidth } = gapControlSizeConstants(DefaultGapControlSizeConstants, scale) - - const onHandleHoverStartInner = React.useCallback( - (e: React.MouseEvent, indicatorIndex: number) => { - setIndicatorShown(indicatorIndex) - onHandleHoverStart(e) - }, - [onHandleHoverStart], - ) - - const onGapHover = React.useCallback( - (e: React.MouseEvent) => { - setGapIsHovered(true) - gapHoverStart(e) - }, - [gapHoverStart], - ) - - const onHandleHoverEndInner = React.useCallback( - (e: React.MouseEvent) => { - setGapIsHovered(false) - gapHoverEnd(e) - setIndicatorShown(null) - onHandleHoverEnd(e) - }, - [onHandleHoverEnd, gapHoverEnd], - ) - - const shouldShowBackground = !isDragging && backgroundShown - - // Invert the direction for the handle. - const segmentFlexDirection = axis === 'row' ? 'column' : 'row' - - return ( -
-
- {createArrayWithLength(handles, (i) => ( - - ))} -
-
- ) -}) - -type GridGapHandleProps = { - gapId: string - index: number - scale: number - gapValue: CSSNumber - axis: Axis - onMouseDown: React.MouseEventHandler - isDragging: boolean - onHandleHoverStartInner: (e: React.MouseEvent, index: number) => void - indicatorShown: number | null - elementHovered: boolean - gapIsHovered: boolean - backgroundShown: boolean -} -function GridGapHandle({ - gapId, - index, - scale, - gapValue, - axis, - onMouseDown, - onHandleHoverStartInner, - isDragging, - indicatorShown, - elementHovered, - gapIsHovered, - backgroundShown, -}: GridGapHandleProps) { - const { width, height } = handleDimensions(axis, scale) - const { hitAreaPadding, paddingIndicatorOffset, borderWidth } = gapControlSizeConstants( - DefaultGapControlSizeConstants, - scale, - ) - const colorTheme = useColorTheme() - const shouldShowIndicator = !isDragging && indicatorShown === index - let shouldShowHandle = !isDragging && gapIsHovered - // show the handle also if the gap is too narrow to hover - if (!gapIsHovered && !backgroundShown) { - shouldShowHandle = elementHovered && gapValue.value <= GapHandleGapWidthThreshold - } - const handleOpacity = gapIsHovered ? 1 : 0.3 - - const onHandleHoverStart = React.useCallback( - (e: React.MouseEvent) => { - onHandleHoverStartInner(e, index) - }, - [onHandleHoverStartInner, index], - ) - - const rowGapStyles = - axis === 'row' - ? ({ - left: '50%', - top: '50%', - transform: 'translate(-50%, -50%)', - position: 'absolute', - gridArea: `1/${index + 1}/2/${index + 2}`, - } as const) - : {} - return ( -
-
- {when( - shouldShowIndicator, - , - )} -
- -
- ) -} - -function handleDimensions(axis: Axis, scale: number): Size { - if (axis === 'row') { - return size(12 / scale, 4 / scale) - } - if (axis === 'column') { - return size(3 / scale, 12 / scale) - } - assertNever(axis) -} - -function startInteraction( - event: React.MouseEvent, - dispatch: EditorDispatch, - canvasOffset: CanvasVector, - scale: number, - axis: Axis, -) { - if (event.buttons === 1 && event.button !== 2) { - event.stopPropagation() - const canvasPositions = windowToCanvasCoordinates( - scale, - canvasOffset, - windowPoint({ x: event.nativeEvent.x, y: event.nativeEvent.y }), - ) - dispatch([ - CanvasActions.createInteractionSession( - createInteractionViaMouse( - canvasPositions.canvasPositionRaw, - Modifier.modifiersForEvent(event), - gridGapHandle(axis), - 'zero-drag-not-permitted', - ), - ), - ]) - } -} +export const GridGapControl = controlForStrategyMemoized(GridGapControlComponent) diff --git a/editor/src/components/canvas/controls/select-mode/padding-resize-control.tsx b/editor/src/components/canvas/controls/select-mode/padding-resize-control.tsx index 0951eb5597ac..81827667dec2 100644 --- a/editor/src/components/canvas/controls/select-mode/padding-resize-control.tsx +++ b/editor/src/components/canvas/controls/select-mode/padding-resize-control.tsx @@ -34,7 +34,7 @@ import { paddingAdjustMode, paddingFromSpecialSizeMeasurements, PaddingIndictorOffset, - simplePaddingFromMetadata, + simplePaddingFromStyleInfo, } from '../../padding-utils' import { useBoundingBox } from '../bounding-box-hooks' import { CanvasOffsetWrapper } from '../canvas-offset-wrapper' @@ -43,6 +43,7 @@ import type { CSSNumberWithRenderedValue } from './controls-common' import { CanvasLabel, fallbackEmptyValue, PillHandle, useHoverWithDelay } from './controls-common' import { MetadataUtils } from '../../../../core/model/element-metadata-utils' import { mapDropNulls } from '../../../../core/shared/array-utils' +import { getActivePlugin } from '../../plugins/style-plugins' export const paddingControlTestId = (edge: EdgePiece): string => `padding-control-${edge}` export const paddingControlHandleTestId = (edge: EdgePiece): string => @@ -358,12 +359,23 @@ export const PaddingResizeControl = controlForStrategyMemoized((props: PaddingCo } }, [hoveredViews, selectedElements]) + const styleInfoReaderRef = useRefEditorState((store) => + getActivePlugin(store.editor).styleInfoFactory({ + projectContents: store.editor.projectContents, + jsxMetadata: store.editor.jsxMetadata, + }), + ) + const currentPadding = React.useMemo(() => { return combinePaddings( paddingFromSpecialSizeMeasurements(elementMetadata, selectedElements[0]), - simplePaddingFromMetadata(elementMetadata, selectedElements[0]), + simplePaddingFromStyleInfo( + elementMetadata, + selectedElements[0], + styleInfoReaderRef.current(selectedElements[0]), + ), ) - }, [elementMetadata, selectedElements]) + }, [elementMetadata, selectedElements, styleInfoReaderRef]) const shownByParent = selectedElementHovered || anyControlHovered diff --git a/editor/src/components/canvas/controls/select-mode/resize-edge.tsx b/editor/src/components/canvas/controls/select-mode/resize-edge.tsx new file mode 100644 index 000000000000..6e913ffe61f2 --- /dev/null +++ b/editor/src/components/canvas/controls/select-mode/resize-edge.tsx @@ -0,0 +1,33 @@ +import React from 'react' +import type { CSSCursor, EdgePosition } from '../../canvas-types' +import { ResizePointTestId } from './absolute-resize-control' + +interface ResizeEdgeProps { + cursor: CSSCursor + direction: 'horizontal' | 'vertical' + position: EdgePosition + onMouseDown: (e: React.MouseEvent) => void + onMouseMove: (e: React.MouseEvent) => void + onDoubleClick: (e: React.MouseEvent) => void +} + +export const ResizeEdge = React.memo( + React.forwardRef((props, ref) => { + return ( +
+ ) + }), +) +ResizeEdge.displayName = 'ResizeEdge' diff --git a/editor/src/components/canvas/controls/select-mode/scene-label.tsx b/editor/src/components/canvas/controls/select-mode/scene-label.tsx index fc5db3cd09f0..36e9227f4157 100644 --- a/editor/src/components/canvas/controls/select-mode/scene-label.tsx +++ b/editor/src/components/canvas/controls/select-mode/scene-label.tsx @@ -18,7 +18,7 @@ import { boundingArea, createInteractionViaMouse } from '../../canvas-strategies import { windowToCanvasCoordinates } from '../../dom-lookup' import { CanvasOffsetWrapper } from '../canvas-offset-wrapper' import { isCommentMode, isSelectModeWithArea } from '../../../editor/editor-modes' -import { getSubTree } from '../../../../core/shared/element-path-tree' +import { getElementPathTreeChildren, getSubTree } from '../../../../core/shared/element-path-tree' interface SceneLabelControlProps { maybeHighlightOnHover: (target: ElementPath) => void @@ -65,7 +65,7 @@ const SceneLabel = React.memo((props) => { if (subTree == null) { return false } else { - return subTree.children.length === 1 + return getElementPathTreeChildren(subTree).length === 1 } }, 'SceneLabel sceneHasSingleChild', diff --git a/editor/src/components/canvas/controls/select-mode/select-mode-hooks.tsx b/editor/src/components/canvas/controls/select-mode/select-mode-hooks.tsx index 2566bd81d97b..6ee632ce1d42 100644 --- a/editor/src/components/canvas/controls/select-mode/select-mode-hooks.tsx +++ b/editor/src/components/canvas/controls/select-mode/select-mode-hooks.tsx @@ -1,21 +1,14 @@ import React from 'react' +import ReactDOM from 'react-dom' import { MetadataUtils } from '../../../../core/model/element-metadata-utils' -import { mapArrayToDictionary, mapDropNulls, uniqBy } from '../../../../core/shared/array-utils' +import { uniqBy } from '../../../../core/shared/array-utils' import type { ElementInstanceMetadataMap } from '../../../../core/shared/element-template' import type { WindowPoint } from '../../../../core/shared/math-utils' -import { - boundingRectangleArray, - CanvasPoint, - distance, - isInfinityRectangle, - point, - windowPoint, -} from '../../../../core/shared/math-utils' +import { point, windowPoint } from '../../../../core/shared/math-utils' import type { ElementPath } from '../../../../core/shared/project-file-types' import * as EP from '../../../../core/shared/element-path' -import { NO_OP, assertNever, fastForEach } from '../../../../core/shared/utils' +import { assertNever } from '../../../../core/shared/utils' import type { KeysPressed } from '../../../../utils/keyboard' -import Keyboard, { isDigit } from '../../../../utils/keyboard' import Utils from '../../../../utils/utils' import { clearHighlightedViews, @@ -25,6 +18,7 @@ import { selectComponents, setHoveredView, clearHoveredViews, + runDOMWalker, } from '../../../editor/actions/action-creators' import { cancelInsertModeActions } from '../../../editor/actions/meta-actions' import { @@ -49,14 +43,12 @@ import { import { Modifier } from '../../../../utils/modifiers' import { pathsEqual } from '../../../../core/shared/element-path' import type { EditorAction } from '../../../../components/editor/action-types' -import { EditorDispatch } from '../../../../components/editor/action-types' -import { EditorModes, isInsertMode, isSelectModeWithArea } from '../../../editor/editor-modes' +import { isInsertMode, isSelectModeWithArea } from '../../../editor/editor-modes' import { scheduleTextEditForNextFrame, useTextEditModeSelectAndHover, } from '../text-edit-mode/text-edit-mode-hooks' import { useDispatch } from '../../../editor/store/dispatch-context' -import { isFeatureEnabled } from '../../../../utils/feature-switches' import { useSetAtom } from 'jotai' import type { CanvasControlWithProps } from '../../../inspector/common/inspector-atoms' import { @@ -68,11 +60,22 @@ import { getAllLockedElementPaths } from '../../../../core/shared/element-lockin import { treatElementAsGroupLike } from '../../canvas-strategies/strategies/group-helpers' import { useCommentModeSelectAndHover } from '../comment-mode/comment-mode-hooks' import { useFollowModeSelectAndHover } from '../follow-mode/follow-mode-hooks' +import { handleGlobalMouseUp } from '../../../../templates/global-handlers' +import { wait } from '../../../../core/model/performance-scripts' +import { IS_TEST_ENVIRONMENT } from '../../../../common/env-vars' +import { isFeatureEnabled } from '../../../../utils/feature-switches' export function isDragInteractionActive(editorState: EditorState): boolean { return editorState.canvas.interactionSession?.interactionData.type === 'DRAG' } +export function isGridDragInteractionActive(editorState: EditorState): boolean { + return ( + editorState.canvas.interactionSession?.interactionData.type === 'DRAG' && + editorState.canvas.interactionSession.activeControl.type === 'GRID_CELL_HANDLE' + ) +} + export function pickSelectionEnabled( canvas: EditorState['canvas'], keysPressed: KeysPressed, @@ -643,7 +646,7 @@ function useSelectOrLiveModeSelectAndHover( [innerOnMouseMove, editorStoreRef], ) const mouseHandler = React.useCallback( - (event: React.MouseEvent) => { + async (event: React.MouseEvent) => { const isLeftClick = event.button === 0 const isRightClick = event.type === 'contextmenu' && event.detail === 0 const isCanvasPanIntention = @@ -665,10 +668,14 @@ function useSelectOrLiveModeSelectAndHover( (!hadInteractionSessionThatWasCancelled || !draggedOverThreshold.current) && (activeControl == null || activeControl.type === 'BOUNDING_AREA') + let editorActions: Array = [] + if (event.type === 'mousedown') { didWeHandleMouseDown.current = true } if (event.type === 'mouseup') { + const handleMouseUpActions = handleGlobalMouseUp(event.nativeEvent) + editorActions.push(...handleMouseUpActions) // Clear the interaction session tracking flag interactionSessionHappened.current = false // didWeHandleMouseDown is used to avoid selecting when closing text editing @@ -676,6 +683,7 @@ function useSelectOrLiveModeSelectAndHover( draggedOverThreshold.current = false if (!mouseUpSelectionAllowed) { + dispatch(editorActions) // We should skip this mouseup return } @@ -687,6 +695,7 @@ function useSelectOrLiveModeSelectAndHover( !active || !(isLeftClick || isRightClick) ) { + dispatch(editorActions) // Skip all of this handling if 'space' is pressed or a mousemove happened in an interaction, or the hook is not active return } @@ -707,7 +716,6 @@ function useSelectOrLiveModeSelectAndHover( const isMultiselect = event.shiftKey const isDeselect = foundTarget == null && !isMultiselect - let editorActions: Array = [] if (foundTarget != null || isDeselect) { if ( @@ -716,7 +724,7 @@ function useSelectOrLiveModeSelectAndHover( foundTarget != null && draggingAllowed && // grid has its own drag handling - !MetadataUtils.isGridCell( + !MetadataUtils.isGridItem( editorStoreRef.current.editor.jsxMetadata, foundTarget.elementPath, ) @@ -776,8 +784,10 @@ function useSelectOrLiveModeSelectAndHover( } if (!foundTargetIsSelected) { - // first we only set the selected views for the canvas controls - setSelectedViewsForCanvasControlsOnly(updatedSelection) + // first we only set the selected views for the canvas controls. however this will be clumped together with the dispatch, unless we wait asynchronously before we dispatch + ReactDOM.flushSync(() => { + setSelectedViewsForCanvasControlsOnly(updatedSelection) + }) // In either case cancel insert mode. if (isInsertMode(editorStoreRef.current.editor.mode)) { @@ -792,7 +802,19 @@ function useSelectOrLiveModeSelectAndHover( } } } - dispatch(editorActions) + + if (event.detail === 1 && isFeatureEnabled('Canvas Fast Selection Hack')) { + // If event.detail is 1 that means this is a first click, where it is safe to delay dispatching actions + // to allow the localSelectedViews to be updated. + // For subsequent clicks, we want to dispatch immediately to avoid out of sync event handlers queueing up + + dispatch(editorActions, 'canvas-fast-selection-hack') // first we dispatch only to update the editor state, but not run the expensive parts + await new Promise((resolve) => requestAnimationFrame(resolve)) // the first requestAnimationFrame fires in the same animation frame we are in, so we need to wait one more + await new Promise((resolve) => requestAnimationFrame(resolve)) // the second requestAnimationFrame is fired in the next actual animation frame, at which point it is safe to run the expensive parts + dispatch([runDOMWalker(null)], 'resume-canvas-fast-selection-hack') // then we dispatch to run the expensive parts + } else { + dispatch(editorActions) + } }, [ dispatch, diff --git a/editor/src/components/canvas/controls/select-mode/select-mode.spec.browser2.tsx b/editor/src/components/canvas/controls/select-mode/select-mode.spec.browser2.tsx index 572d9dbfb79b..aba628750c29 100644 --- a/editor/src/components/canvas/controls/select-mode/select-mode.spec.browser2.tsx +++ b/editor/src/components/canvas/controls/select-mode/select-mode.spec.browser2.tsx @@ -325,7 +325,7 @@ describe('Select Mode Clicking', () => { checkSelectedPaths(renderResult, [desiredPath]) }) - it('Single click and then double click to select Button on a Card Scene Root', async () => { + xit('Single click and then double click to select Button on a Card Scene Root', async () => { // prettier-ignore const desiredPaths = createConsecutivePaths( 'sb' + // Skipped as it's the storyboard @@ -416,7 +416,7 @@ describe('Select Mode Clicking', () => { checkFocusedPath(renderResult, desiredPaths[2]) checkSelectedPaths(renderResult, [desiredPaths[2]]) }) - it('Single click and five double clicks will focus a generated Card and select the Button inside', async function (this: Mocha.Context) { + xit('Single click and five double clicks will focus a generated Card and select the Button inside', async function (this: Mocha.Context) { this.timeout(TimeoutForThisFile) // prettier-ignore const desiredPaths = createConsecutivePaths( @@ -703,7 +703,7 @@ describe('Select Mode Double Clicking With Fragments', () => { checkSelectedPaths(renderResult, [desiredPath]) }) - it('Single click and then double click to select Button on a Card Scene Root', async () => { + xit('Single click and then double click to select Button on a Card Scene Root', async () => { FOR_TESTS_setNextGeneratedUids([ 'cardlistfragment', 'manuallistfragment', @@ -750,7 +750,7 @@ describe('Select Mode Double Clicking With Fragments', () => { checkSelectedPaths(renderResult, [desiredPaths[1]]) }) - it('Single click and three double clicks will focus a generated Card', async () => { + xit('Single click and three double clicks will focus a generated Card', async () => { // prettier-ignore const desiredPaths = createConsecutivePaths( 'sb' + // Skipped as it's the storyboard diff --git a/editor/src/components/canvas/controls/select-mode/size-label.tsx b/editor/src/components/canvas/controls/select-mode/size-label.tsx new file mode 100644 index 000000000000..2a773d059fa6 --- /dev/null +++ b/editor/src/components/canvas/controls/select-mode/size-label.tsx @@ -0,0 +1,358 @@ +import React from 'react' +import { MetadataUtils } from '../../../../core/model/element-metadata-utils' +import type { ElementInstanceMetadata } from '../../../../core/shared/element-template' +import { + isJSXElement, + type ElementInstanceMetadataMap, +} from '../../../../core/shared/element-template' +import type { CanvasRectangle } from '../../../../core/shared/math-utils' +import { + boundingRectangleArray, + canvasRectangle, + isInfinityRectangle, + nullIfInfinity, +} from '../../../../core/shared/math-utils' +import type { ElementPath } from '../../../../core/shared/project-file-types' +import { assertNever } from '../../../../core/shared/utils' +import { when } from '../../../../utils/react-conditionals' +import { useColorTheme } from '../../../../uuiui' +import { getMetadata } from '../../../editor/store/editor-state' +import { Substores, useEditorState, useRefEditorState } from '../../../editor/store/store-hook' +import type { DetectedFillHugFixedState, FixedHugFill } from '../../../inspector/inspector-common' +import { detectFillHugFixedState } from '../../../inspector/inspector-common' +import { getAllTargetsUnderAreaAABB } from '../../dom-lookup' +import { treatElementAsGroupLike } from '../../canvas-strategies/strategies/group-helpers' +import { defaultEither } from '../../../../core/shared/either' +import type { CSSNumber } from '../../../inspector/common/css-utils' +import { isCSSNumber, printCSSNumber } from '../../../inspector/common/css-utils' +import { toFirst } from '../../../../core/shared/optics/optic-utilities' +import { + eitherRight, + fromField, + fromTypeGuard, + notNull, +} from '../../../../core/shared/optics/optic-creators' +import { useBoundingBox } from '../bounding-box-hooks' +import { controlForStrategy } from '../../canvas-strategies/canvas-strategy-types' +import { CanvasOffsetWrapper } from '../canvas-offset-wrapper' +import { isZeroSizedElement } from '../outline-utils' + +export const SizeLabelID = 'size-label' +export const SizeLabelTestId = 'size-label' + +const sizeLabel = (state: FixedHugFill['type'] | CSSNumber, actualSize: number): string => { + if (isCSSNumber(state)) { + const printed = printCSSNumber(state, 'px') + return typeof printed === 'number' ? `${printed}` : printed + } else { + switch (state) { + case 'fill': + return 'Fill' + case 'hug': + case 'hug-group': + return 'Hug' + case 'squeeze': + return 'Squeeze' + case 'collapsed': + return 'Collapsed' + case 'fixed': + case 'scaled': + case 'detected': + case 'computed': + return `${actualSize}` + case 'stretch': + return 'Stretch' + default: + assertNever(state) + } + } +} + +export type SizeLabelSize = { + type: 'SIZE_LABEL_WITH_DIMENSIONS' + h: string + v: string +} + +function sizeLabelWithDimensions(h: string, v: string): SizeLabelSize { + return { + type: 'SIZE_LABEL_WITH_DIMENSIONS', + h: h, + v: v, + } +} + +export type SizeLabelGroup = { + type: 'SIZE_LABEL_GROUP' +} + +function sizeLabelGroup(): SizeLabelGroup { + return { + type: 'SIZE_LABEL_GROUP', + } +} + +export type SizeLabelChildren = { + type: 'SIZE_LABEL_CHILDREN' + h: string + v: string +} + +function sizeLabelChildren(h: string, v: string): SizeLabelChildren { + return { + type: 'SIZE_LABEL_CHILDREN', + h: h, + v: v, + } +} + +export type SizeLabelContents = SizeLabelSize | SizeLabelGroup | SizeLabelChildren + +function detectedStateToOutputValue( + detectedState: DetectedFillHugFixedState | null, +): FixedHugFill['type'] | CSSNumber | null { + if (detectedState == null || detectedState.fixedHugFill == null) { + return null + } + switch (detectedState.fixedHugFill.type) { + case 'hug': + case 'squeeze': + case 'collapsed': + case 'stretch': + return detectedState.fixedHugFill.type + case 'hug-group': + return 'hug' + case 'fixed': + case 'fill': + case 'computed': + case 'detected': + case 'scaled': + return detectedState.fixedHugFill.value + default: + assertNever(detectedState.fixedHugFill) + } +} + +function sizeLabelContents( + metadata: ElementInstanceMetadataMap, + selectedElements: Array, + boundingBox: CanvasRectangle | null, + pathsWereReplaced: boolean, +): SizeLabelContents | null { + const selectedElement = selectedElements.at(0) + if (selectedElement == null || selectedElements.length === 0) { + return null + } + + if (selectedElements.length === 1) { + if (treatElementAsGroupLike(metadata, selectedElement)) { + return sizeLabelGroup() + } + + const elementMetadata = MetadataUtils.findElementByElementPath(metadata, selectedElement) + if (elementMetadata == null) { + return null + } + const element = defaultEither( + null, + toFirst( + notNull() + .compose(fromField('element')) + .compose(eitherRight()) + .compose(fromTypeGuard(isJSXElement)), + elementMetadata, + ), + ) + if (element == null) { + return null + } + + const globalFrame = elementMetadata.globalFrame + if (globalFrame == null || isInfinityRectangle(globalFrame)) { + return null + } + + const horizontalState = detectFillHugFixedState('horizontal', metadata, selectedElement) + const verticalState = detectFillHugFixedState('vertical', metadata, selectedElement) + + const horizontal = detectedStateToOutputValue(horizontalState) ?? 'fixed' + const vertical = detectedStateToOutputValue(verticalState) ?? 'fixed' + + if (pathsWereReplaced) { + return sizeLabelChildren( + sizeLabel(horizontal, globalFrame.width), + sizeLabel(vertical, globalFrame.height), + ) + } + + return sizeLabelWithDimensions( + sizeLabel(horizontal, globalFrame.width), + sizeLabel(vertical, globalFrame.height), + ) + } + + if (boundingBox != null) { + return sizeLabelWithDimensions(`${boundingBox.width}`, `${boundingBox.height}`) + } + + return null +} + +interface SizeLabelProps { + targets: Array + pathsWereReplaced: boolean +} + +const FontSize = 11 +const PaddingV = 0 +const PaddingH = 2 +const ExplicitHeightHacked = 20 +const BorderRadius = 2 +const SizeLabelMarginTop = 8 + +function getLabelText(label: SizeLabelContents | null): string | null { + if (label == null) { + return null + } + switch (label.type) { + case 'SIZE_LABEL_GROUP': + return 'Group' + case 'SIZE_LABEL_WITH_DIMENSIONS': + return `${label.h} × ${label.v}` + case 'SIZE_LABEL_CHILDREN': + return `(Children) ${label.h} × ${label.v}` + default: + assertNever(label) + } +} + +export const SizeLabel = React.memo(({ targets, pathsWereReplaced }) => { + const resizeRef = useBoundingBox(targets, (ref, boundingBox) => { + ref.current.style.top = boundingBox.height + 'px' + ref.current.style.left = 0 + 'px' + ref.current.style.width = boundingBox.width + 'px' + }) + + const controlRef = useBoundingBox(targets, (ref, safeGappedBoundingBox, realBoundingBox) => { + if (isZeroSizedElement(realBoundingBox)) { + ref.current.style.display = 'none' + } else { + ref.current.style.display = 'block' + ref.current.style.left = safeGappedBoundingBox.x + 'px' + ref.current.style.top = safeGappedBoundingBox.y + 'px' + ref.current.style.width = safeGappedBoundingBox.width + 'px' + ref.current.style.height = safeGappedBoundingBox.height + 'px' + } + }) + + const scale = useEditorState( + Substores.canvas, + (store) => store.editor.canvas.scale, + 'Resizelabel scale', + ) + const colorTheme = useColorTheme() + const metadata = useEditorState( + Substores.metadata, + (store) => getMetadata(store.editor), + 'ResizeLabel metadata', + ) + + const boundingBox = boundingRectangleArray( + targets.map((t) => nullIfInfinity(MetadataUtils.getFrameInCanvasCoords(t, metadata))), + ) + + const label = sizeLabelContents(metadata, targets, boundingBox, pathsWereReplaced) + const labelText = getLabelText(label) + + const [dimmed, setDimmed] = React.useState(false) + + const editorRef = useRefEditorState((store) => ({ + scale: store.editor.canvas.scale, + offset: store.editor.canvas.roundedCanvasOffset, + jsxMetadata: store.editor.jsxMetadata, + elementPathTree: store.editor.elementPathTree, + allElementProps: store.editor.allElementProps, + hiddenInstances: store.editor.hiddenInstances, + })) + + const onMouseEnter = React.useCallback(() => { + const distanceBetweenBoxAndLabel = 10 // px + const labelRect = document.getElementById(SizeLabelID)?.getBoundingClientRect() + if (boundingBox != null && labelRect != null) { + const area = canvasRectangle({ + x: boundingBox.x + (boundingBox.width - labelRect.width) / 2, + y: + boundingBox.y + boundingBox.height + distanceBetweenBoxAndLabel / editorRef.current.scale, + width: labelRect.width, + height: labelRect.height, + }) + const elementsUnderLabel = getAllTargetsUnderAreaAABB( + editorRef.current.jsxMetadata, + [], + editorRef.current.hiddenInstances, + 'no-filter', + area, + editorRef.current.elementPathTree, + editorRef.current.allElementProps, + true, + ) + setDimmed(elementsUnderLabel.length > 0) + } + }, [editorRef, boundingBox]) + + const onMouseLeave = React.useCallback(() => { + setDimmed(false) + }, []) + + return ( + +
+
+ {when( + labelText != null, +
+ {labelText} +
, + )} +
+
+
+ ) +}) + +export const StrategySizeLabel = controlForStrategy(SizeLabel) diff --git a/editor/src/components/canvas/controls/select-mode/subdued-flex-gap-controls.tsx b/editor/src/components/canvas/controls/select-mode/subdued-flex-gap-controls.tsx index ed5037d454df..4e0091951593 100644 --- a/editor/src/components/canvas/controls/select-mode/subdued-flex-gap-controls.tsx +++ b/editor/src/components/canvas/controls/select-mode/subdued-flex-gap-controls.tsx @@ -3,7 +3,7 @@ import { useColorTheme } from '../../../../uuiui' import { Substores, useEditorState, useRefEditorState } from '../../../editor/store/store-hook' import { useBoundingBox } from '../bounding-box-hooks' import { CanvasOffsetWrapper } from '../canvas-offset-wrapper' -import type { PathWithBounds } from '../../gap-utils' +import type { FlexGapData, PathWithBounds } from '../../gap-utils' import { gapControlBoundsFromMetadata, maybeFlexGapData, @@ -11,6 +11,8 @@ import { } from '../../gap-utils' import type { ElementPath } from 'utopia-shared/src/types' import type { ElementInstanceMetadata } from '../../../../core/shared/element-template' +import { getActivePlugin } from '../../plugins/style-plugins' +import { MetadataUtils } from '../../../../core/model/element-metadata-utils' export interface SubduedFlexGapControlProps { hoveredOrFocused: 'hovered' | 'focused' @@ -34,7 +36,20 @@ export const SubduedFlexGapControl = React.memo((pro elementPathTrees.current, targets[0], ) - const flexGap = maybeFlexGapData(metadata.current, selectedElement) + + const flexGap = useEditorState( + Substores.fullStore, + (store) => + maybeFlexGapData( + getActivePlugin(store.editor).styleInfoFactory({ + projectContents: store.editor.projectContents, + jsxMetadata: store.editor.jsxMetadata, + })(selectedElement), + MetadataUtils.findElementByElementPath(store.editor.jsxMetadata, selectedElement), + ), + 'FlexGapControl flexGap', + ) + if (flexGap == null) { return null } @@ -54,7 +69,7 @@ export const SubduedFlexGapControl = React.memo((pro {controlBounds.map((controlBound, i) => ( ((pro }) function FlexGapControl({ - targets, + flexGap, selectedElement, hoveredOrFocused, controlBound, flexChildren, }: { - targets: Array selectedElement: ElementPath hoveredOrFocused: 'hovered' | 'focused' controlBound: PathWithBounds flexChildren: Array + flexGap: FlexGapData }) { const metadata = useRefEditorState((store) => store.editor.jsxMetadata) const sideRef = useBoundingBox([controlBound.path], (ref) => { - const flexGap = maybeFlexGapData(metadata.current, selectedElement) - if (flexGap == null) { - return - } - const flexGapValue = flexGap.value const controlBounds = gapControlBoundsFromMetadata( diff --git a/editor/src/components/canvas/controls/select-mode/subdued-grid-gap-controls.tsx b/editor/src/components/canvas/controls/select-mode/subdued-grid-gap-controls.tsx index 61f436cda21b..a0d08fe9d7f0 100644 --- a/editor/src/components/canvas/controls/select-mode/subdued-grid-gap-controls.tsx +++ b/editor/src/components/canvas/controls/select-mode/subdued-grid-gap-controls.tsx @@ -1,15 +1,15 @@ import React from 'react' import { useColorTheme } from '../../../../uuiui' -import { Substores, useEditorState, useRefEditorState } from '../../../editor/store/store-hook' -import { useBoundingBox } from '../bounding-box-hooks' +import { Substores, useEditorState } from '../../../editor/store/store-hook' import { CanvasOffsetWrapper } from '../canvas-offset-wrapper' import type { Axis } from '../../gap-utils' -import { gridGapControlBoundsFromMetadata, maybeGridGapData } from '../../gap-utils' -import type { ElementPath } from 'utopia-shared/src/types' -import { useGridData } from '../grid-controls' -import { fallbackEmptyValue } from './controls-common' -import type { CanvasRectangle } from '../../../../core/shared/math-utils' -import type { CSSNumber } from '../../../../components/inspector/common/css-utils' +import { useGridData } from '../grid-controls-for-strategies' +import { unitlessCSSNumberWithRenderedValue } from './controls-common' +import { NO_OP } from '../../../../core/shared/utils' +import * as EP from '../../../../core/shared/element-path' +import { GridPaddingOutlineForDimension } from './grid-gap-control-component' +import { gridContainerIdentifier } from '../../../editor/store/editor-state' +import { getGridIdentifierContainerOrComponentPath } from '../../canvas-strategies/strategies/grid-helpers' export interface SubduedGridGapControlProps { hoveredOrFocused: 'hovered' | 'focused' @@ -18,138 +18,52 @@ export interface SubduedGridGapControlProps { export const SubduedGridGapControl = React.memo((props) => { const { hoveredOrFocused, axis } = props + const colorTheme = useColorTheme() const targets = useEditorState( Substores.selectedViews, - (store) => store.editor.selectedViews, + (store) => store.editor.selectedViews.map(gridContainerIdentifier), 'SubduedGridGapControl selectedViews', ) - const scale = useEditorState( - Substores.canvas, - (store) => store.editor.canvas.scale, - 'GridGapControl scale', - ) - const metadata = useRefEditorState((store) => store.editor.jsxMetadata) - const selectedElement = targets.at(0) const gridRowColumnInfo = useGridData(targets) - const selectedGrid = gridRowColumnInfo.at(0) - - const filteredGaps = React.useMemo(() => { - if (selectedElement == null || selectedGrid == null) { - return [] - } - const gridGap = maybeGridGapData(metadata.current, selectedElement) - if (gridGap == null) { - return [] - } - - const gridGapRow = gridGap.row - const gridGapColumn = gridGap.column - const controlBounds = gridGapControlBoundsFromMetadata( - selectedElement, - selectedGrid, - { - row: fallbackEmptyValue(gridGapRow), - column: fallbackEmptyValue(gridGapColumn), - }, - scale, - ) - return controlBounds.gaps.filter((gap) => gap.axis === axis || axis === 'both') - }, [axis, metadata, scale, selectedElement, selectedGrid]) - - if (filteredGaps.length === 0 || selectedElement == null) { + if (gridRowColumnInfo.length === 0) { return null } - return ( - <> - {filteredGaps.map((gap) => ( - - ))} - - ) -}) - -function GridGapControl({ - targets, - selectedElement, - hoveredOrFocused, - gap, -}: { - targets: Array - selectedElement: ElementPath - hoveredOrFocused: 'hovered' | 'focused' - gap: { - bounds: CanvasRectangle - gapId: string - gap: CSSNumber - axis: Axis - } -}) { - const metadata = useRefEditorState((store) => store.editor.jsxMetadata) - const scale = useEditorState( - Substores.canvas, - (store) => store.editor.canvas.scale, - 'GridGapControl scale', - ) - const gridRowColumnInfo = useGridData([selectedElement]) - - const sideRef = useBoundingBox([selectedElement], (ref, parentBoundingBox) => { - const gridGap = maybeGridGapData(metadata.current, selectedElement) - const selectedGrid = gridRowColumnInfo.at(0) - if (gridGap == null || selectedGrid == null) { - return - } - - const controlBounds = gridGapControlBoundsFromMetadata( - selectedElement, - selectedGrid, - { - row: fallbackEmptyValue(gridGap.row), - column: fallbackEmptyValue(gridGap.column), - }, - scale, - ) - - const bound = controlBounds.gaps.find((updatedGap) => updatedGap.gapId === gap.gapId) - if (bound == null) { - return - } - - ref.current.style.display = 'block' - ref.current.style.left = `${bound.bounds.x + parentBoundingBox.x}px` - ref.current.style.top = `${bound.bounds.y + parentBoundingBox.y}px` - ref.current.style.height = numberToPxValue(bound.bounds.height) - ref.current.style.width = numberToPxValue(bound.bounds.width) - }) - - const color = useColorTheme().brandNeonPink.value - - const solidOrDashed = hoveredOrFocused === 'focused' ? 'solid' : 'dashed' - return ( -
+ {gridRowColumnInfo.map((gridData) => { + const gridContainerOrComponent = getGridIdentifierContainerOrComponentPath( + gridData.identifier, + ) + return ( + + + + + ) + })} ) -} - -export function getSubduedGridGaplTestID(hoveredOrFocused: 'hovered' | 'focused'): string { - return `SubduedGridGapControl-${hoveredOrFocused}` -} - -const numberToPxValue = (n: number) => n + 'px' +}) diff --git a/editor/src/components/canvas/controls/select-mode/subdued-padding-control.tsx b/editor/src/components/canvas/controls/select-mode/subdued-padding-control.tsx index d46615b6ad39..3fb67c29a496 100644 --- a/editor/src/components/canvas/controls/select-mode/subdued-padding-control.tsx +++ b/editor/src/components/canvas/controls/select-mode/subdued-padding-control.tsx @@ -2,9 +2,10 @@ import React from 'react' import { useColorTheme } from '../../../../uuiui' import { Substores, useEditorState, useRefEditorState } from '../../../editor/store/store-hook' import type { EdgePiece } from '../../canvas-types' -import { paddingPropForEdge, simplePaddingFromMetadata } from '../../padding-utils' +import { paddingPropForEdge, simplePaddingFromStyleInfo } from '../../padding-utils' import { useBoundingBox } from '../bounding-box-hooks' import { CanvasOffsetWrapper } from '../canvas-offset-wrapper' +import { getActivePlugin } from '../../plugins/style-plugins' export interface SubduedPaddingControlProps { side: EdgePiece @@ -25,9 +26,20 @@ export const SubduedPaddingControl = React.memo((pro const isVerticalPadding = !isHorizontalPadding const paddingKey = paddingPropForEdge(side) + const styleInfoReaderRef = useRefEditorState((store) => + getActivePlugin(store.editor).styleInfoFactory({ + projectContents: store.editor.projectContents, + jsxMetadata: store.editor.jsxMetadata, + }), + ) + // TODO Multiselect const sideRef = useBoundingBox(targets, (ref, boundingBox) => { - const padding = simplePaddingFromMetadata(elementMetadata.current, targets[0]) + const padding = simplePaddingFromStyleInfo( + elementMetadata.current, + targets[0], + styleInfoReaderRef.current(targets[0]), + ) const paddingValue = padding[paddingKey]?.renderedValuePx ?? 0 const { x, y, width, height } = boundingBox diff --git a/editor/src/components/canvas/controls/select-mode/use-resize-edges.tsx b/editor/src/components/canvas/controls/select-mode/use-resize-edges.tsx new file mode 100644 index 000000000000..34831303a362 --- /dev/null +++ b/editor/src/components/canvas/controls/select-mode/use-resize-edges.tsx @@ -0,0 +1,133 @@ +import React from 'react' +import type { ElementPath } from '../../../../core/shared/project-file-types' +import { Substores, useEditorState } from '../../../editor/store/store-hook' +import type { EdgePosition } from '../../canvas-types' +import { CSSCursor } from '../../canvas-types' +import { SmallElementSize, useBoundingBox } from '../bounding-box-hooks' +import { ResizeEdge } from './resize-edge' + +const RESIZE_MOUSE_AREA_SIZE = 10 + +export function useResizeEdges( + targets: ElementPath[], + params: { + onEdgeMouseDown: (position: EdgePosition) => (e: React.MouseEvent) => void + onEdgeMouseMove: (e: React.MouseEvent) => void + onEdgeDoubleClick: ( + direction: 'horizontal' | 'vertical', + ) => (e: React.MouseEvent) => void + cursors?: { + top?: CSSCursor + left?: CSSCursor + bottom?: CSSCursor + right?: CSSCursor + } + }, +) { + const scale = useEditorState( + Substores.canvasOffset, + (store) => store.editor.canvas.scale, + 'useResizeEdges scale', + ) + + const topRef = useBoundingBox(targets, (ref, boundingBox) => { + const isSmallElement = shouldUseSmallElementResizeControl(boundingBox.height, scale) + const lineSize = RESIZE_MOUSE_AREA_SIZE / scale + const height = isSmallElement ? lineSize / 2 : lineSize + const offsetLeft = `0px` + const offsetTop = `${-lineSize / 2}px` + + ref.current.style.width = boundingBox.width + 'px' + ref.current.style.height = height + 'px' + ref.current.style.transform = `translate(${offsetLeft}, ${offsetTop})` + }) + + const leftRef = useBoundingBox(targets, (ref, boundingBox) => { + const isSmallElement = shouldUseSmallElementResizeControl(boundingBox.width, scale) + const lineSize = RESIZE_MOUSE_AREA_SIZE / scale + const width = isSmallElement ? lineSize / 2 : lineSize + const offsetLeft = `${-lineSize / 2}px` + const offsetTop = `0px` + + ref.current.style.width = `${width}px` + ref.current.style.transform = `translate(${offsetLeft}, ${offsetTop})` + ref.current.style.height = boundingBox.height + 'px' + }) + + const bottomRef = useBoundingBox(targets, (ref, boundingBox) => { + const isSmallElement = shouldUseSmallElementResizeControl(boundingBox.height, scale) + const lineSize = RESIZE_MOUSE_AREA_SIZE / scale + const height = isSmallElement ? lineSize / 2 : lineSize + const offsetLeft = `0px` + const offsetTop = isSmallElement ? `0px` : `${-lineSize / 2}px` + + ref.current.style.transform = `translate(${offsetLeft}, ${offsetTop})` + ref.current.style.top = boundingBox.height + 'px' + ref.current.style.width = boundingBox.width + 'px' + ref.current.style.height = height + 'px' + }) + + const rightRef = useBoundingBox(targets, (ref, boundingBox) => { + const isSmallElement = shouldUseSmallElementResizeControl(boundingBox.width, scale) + const lineSize = RESIZE_MOUSE_AREA_SIZE / scale + const width = isSmallElement ? lineSize / 2 : lineSize + const offsetLeft = isSmallElement ? `0px` : `${-lineSize / 2}px` + const offsetTop = `0px` + + ref.current.style.transform = `translate(${offsetLeft}, ${offsetTop})` + ref.current.style.left = boundingBox.width + 'px' + ref.current.style.width = width + 'px' + ref.current.style.height = boundingBox.height + 'px' + }) + + return { + top: ( + + ), + left: ( + + ), + bottom: ( + + ), + right: ( + + ), + } +} + +function shouldUseSmallElementResizeControl(size: number, scale: number): boolean { + return size <= SmallElementSize / scale +} diff --git a/editor/src/components/canvas/design-panel-root.tsx b/editor/src/components/canvas/design-panel-root.tsx index cfd85994fbef..d9f877e33fc3 100644 --- a/editor/src/components/canvas/design-panel-root.tsx +++ b/editor/src/components/canvas/design-panel-root.tsx @@ -35,8 +35,6 @@ import { EditorModes, isCommentMode } from '../editor/editor-modes' import { useAllowedToEditProject } from '../editor/store/collaborative-editing' import { useCanComment } from '../../core/commenting/comment-hooks' import { ElementsOutsideVisibleAreaIndicator } from '../editor/elements-outside-visible-area-indicator' -import { isFeatureEnabled } from '../../utils/feature-switches' -import { RollYourOwnFeaturesPane } from '../navigator/left-pane/roll-your-own-pane' import { AnimationContext } from './ui-jsx-canvas-renderer/animation-context' function isCodeEditorEnabled(): boolean { @@ -136,7 +134,7 @@ export const RightPane = React.memo((props) => { ) const dispatch = useDispatch() - const onClickTab = React.useCallback( + const onMouseDownTab = React.useCallback( (menuTab: RightMenuTab) => { const actions: Array = [EditorActions.setRightMenuTab(menuTab)] if (isCommentMode(editorModeRef.current) && menuTab !== RightMenuTab.Comments) { @@ -147,119 +145,127 @@ export const RightPane = React.memo((props) => { [dispatch, editorModeRef], ) - const onClickInsertTab = React.useCallback(() => { - onClickTab(RightMenuTab.Insert) - }, [onClickTab]) + const onMouseDownInsertTab = React.useCallback(() => { + onMouseDownTab(RightMenuTab.Insert) + }, [onMouseDownTab]) - const onClickCommentsTab = React.useCallback(() => { - onClickTab(RightMenuTab.Comments) - }, [onClickTab]) + const onMouseDownCommentsTab = React.useCallback(() => { + onMouseDownTab(RightMenuTab.Comments) + }, [onMouseDownTab]) - const onClickInspectorTab = React.useCallback(() => { - onClickTab(RightMenuTab.Inspector) - }, [onClickTab]) + const onMouseDownInspectorTab = React.useCallback(() => { + onMouseDownTab(RightMenuTab.Inspector) + }, [onMouseDownTab]) - const onClickSettingsTab = React.useCallback(() => { - onClickTab(RightMenuTab.Settings) - }, [onClickTab]) - - const onClickRollYourOwnTab = React.useCallback(() => { - onClickTab(RightMenuTab.RollYourOwn) - }, [onClickTab]) + const onMouseDownSettingsTab = React.useCallback(() => { + onMouseDownTab(RightMenuTab.Settings) + }, [onMouseDownTab]) const canComment = useCanComment() const allowedToEdit = useAllowedToEditProject() + const designPanelRef = React.useRef(null) + if (!isRightMenuExpanded) { return null } + const panelWidth = designPanelRef.current?.offsetWidth ?? 0 + const panelHeight = designPanelRef.current?.offsetHeight ?? 0 + return ( - - - + - - {when( - allowedToEdit, - <> - {when( - IS_TEST_ENVIRONMENT, - , - )} - , - )} - {when( - canComment, + + , - )} - - {when( - isFeatureEnabled('Roll Your Own'), + label={'Inspector'} + selected={selectedTab === RightMenuTab.Inspector} + onMouseDown={onMouseDownInspectorTab} + /> + {when( + allowedToEdit, + <> + {when( + IS_TEST_ENVIRONMENT, + , + )} + , + )} + {when( + canComment, + , + )} , - )} - - - {when(selectedTab === RightMenuTab.Inspector, )} - {when(selectedTab === RightMenuTab.Settings, )} - {when(selectedTab === RightMenuTab.Comments, )} - {when(selectedTab === RightMenuTab.RollYourOwn, )} - - - + label={'Settings'} + selected={selectedTab === RightMenuTab.Settings} + onMouseDown={onMouseDownSettingsTab} + /> + + + {when(selectedTab === RightMenuTab.Inspector, )} + {when(selectedTab === RightMenuTab.Settings, )} + {when(selectedTab === RightMenuTab.Comments, )} + + + + ) }) +// a context provider for the design panel measurements +export const DesignPanelContext = React.createContext<{ + panelWidth: number + panelHeight: number +}>({ + panelWidth: 0, + panelHeight: 0, +}) + +export const useDesignPanelContext = () => { + return React.useContext(DesignPanelContext) +} + interface CodeEditorPaneProps { panelData: StoredPanel small: boolean diff --git a/editor/src/components/canvas/direct-dom-lookups.ts b/editor/src/components/canvas/direct-dom-lookups.ts new file mode 100644 index 000000000000..4e4a883d7ad2 --- /dev/null +++ b/editor/src/components/canvas/direct-dom-lookups.ts @@ -0,0 +1,44 @@ +import { UTOPIA_PATH_KEY } from '../../core/model/utopia-constants' +import { CanvasContainerID } from './canvas-types' +import { getDeepestPathOnDomElement } from '../../core/shared/uid-utils' +import * as EP from '../../core/shared/element-path' +import type { ElementPath } from 'utopia-shared/src/types' +import { assertNever } from '../../core/shared/utils' +import type { ElementOrParent } from './controls/grid-measurements' + +export type FromElement = (element: HTMLElement) => T + +export function getFromElement( + path: ElementPath, + fromElement: FromElement, + elementOrParent: ElementOrParent, +): T | undefined { + const pathString = EP.toString(path) + const elements = document.querySelectorAll( + `#${CanvasContainerID} [${UTOPIA_PATH_KEY}^="${pathString}"]`, + ) + for (const element of Array.from(elements)) { + const pathFromElement = getDeepestPathOnDomElement(element) + if ( + (EP.pathsEqual(path, pathFromElement) && + pathFromElement != null && + !EP.isRootElementOfInstance(pathFromElement)) || + EP.isRootElementOf(pathFromElement, path) + ) { + const realElement = (() => { + switch (elementOrParent) { + case 'element': + return element + case 'parent': + return element.parentElement + default: + assertNever(elementOrParent) + } + })() + if (realElement instanceof HTMLElement) { + return fromElement(realElement) + } + } + } + return undefined +} diff --git a/editor/src/components/canvas/dom-lookup.ts b/editor/src/components/canvas/dom-lookup.ts index 7ed972ace357..94047c78525b 100644 --- a/editor/src/components/canvas/dom-lookup.ts +++ b/editor/src/components/canvas/dom-lookup.ts @@ -6,6 +6,7 @@ import type { CanvasRectangle, CanvasVector, WindowPoint, + WindowRectangle, } from '../../core/shared/math-utils' import { boundingRectangleArray, @@ -14,8 +15,10 @@ import { negate, offsetPoint, roundPointToNearestHalf, + scaleRect, scaleVector, windowPoint, + windowRectangle, } from '../../core/shared/math-utils' import type { ElementPath } from '../../core/shared/project-file-types' import * as EP from '../../core/shared/element-path' @@ -636,3 +639,18 @@ export function canvasPointToWindowPoint( throw new Error('calling screenToElementCoordinates() before being mounted') } } + +export function canvasRectangleToWindowRectangle( + canvasRect: CanvasRectangle, + canvasScale: number, + canvasOffset: CanvasVector, +): WindowRectangle { + const location = canvasPointToWindowPoint(canvasRect, canvasScale, canvasOffset) + const scaledRect = scaleRect(canvasRect, canvasScale) + return windowRectangle({ + x: location.x, + y: location.y, + width: scaledRect.width, + height: scaledRect.height, + }) +} diff --git a/editor/src/components/canvas/dom-sampler.spec.browser2.tsx b/editor/src/components/canvas/dom-sampler.spec.browser2.tsx index f1ca07c54a40..43ff3cfbcdfb 100644 --- a/editor/src/components/canvas/dom-sampler.spec.browser2.tsx +++ b/editor/src/components/canvas/dom-sampler.spec.browser2.tsx @@ -5,6 +5,7 @@ import { BakedInStoryboardVariableName } from '../../core/model/scene-utils' import * as EP from '../../core/shared/element-path' import { objectMap } from '../../core/shared/object-utils' import { optionalMap } from '../../core/shared/optional-utils' +import { selectComponentsForTest } from '../../utils/utils.test-utils' import { runDOMWalker, setFocusedElement } from '../editor/actions/action-creators' import { navigatorEntryToKey } from '../editor/store/editor-state' import { getNavigatorTargetsFromEditorState } from '../navigator/navigator-utils' @@ -82,7 +83,7 @@ export var Playground = ({ style }) => { 'await-first-dom-report', ) - await editor.dispatch([runDOMWalker()], true) + await editor.dispatch([runDOMWalker(null)], true) matchInlineSnapshotBrowser( Object.keys(editor.getEditorState().editor.jsxMetadata), @@ -296,16 +297,14 @@ export var storyboard = ( navigatorEntryToKey, ), ).toEqual([ - 'regular-sb/1e7', 'regular-sb/sc', 'regular-sb/sc/app', 'regular-sb/sc/app:app-root', 'regular-sb/sc/app:app-root/card', 'regular-sb/sc/app:app-root/card:card-root', - 'regular-sb/sc/app:app-root/card:card-root/30d', 'regular-sb/sc/app:app-root/card:card-root/card-span', + 'regular-sb/sc/app:app-root/card:card-root/30d', 'regular-sb/sc/app:app-root/card/card-child', - 'regular-sb/sc/app:app-root/children-code-block', 'regular-sb/sc/app:app-root/frag', 'regular-sb/sc/app:app-root/frag/frag-child', 'regular-sb/sc/app:app-root/frag/cond-1', @@ -319,7 +318,9 @@ export var storyboard = ( 'synthetic-sb/sc/app:app-root/frag/cond-1/cond-1-true/cond-2/d84-attribute', 'conditional-clause-sb/sc/app:app-root/frag/cond-1-false-case', 'synthetic-sb/sc/app:app-root/frag/cond-1/019-attribute', + 'regular-sb/sc/app:app-root/children-code-block', 'regular-sb/sc/app/app-child', + 'regular-sb/1e7', ]) }) @@ -457,3 +458,279 @@ function makeTestProjectCodeWithStoryboard(codeForComponents: string): string { return formatTestProjectCode(code) } + +describe('Grid dom sampler tests', () => { + it('gridCellGlobalFrames calculated correctly', async () => { + const editor = await renderTestEditorWithCode( + `import * as React from 'react' +import { Scene, Storyboard } from 'utopia-api' + +export var storyboard = ( + + +
+
+
+
+
+
+
+
+
+ + +) +`, + 'await-first-dom-report', + ) + + await selectComponentsForTest(editor, [EP.fromString('storyboard/scene/grid')]) + await editor.dispatch([runDOMWalker(null)], true) + + // non-grids don't have cell measurements: + expect( + editor.getEditorState().editor.jsxMetadata['storyboard/scene/grid/child'] + .specialSizeMeasurements.gridCellGlobalFrames, + ).toBeNull() + + // grids have cell measurements: + matchInlineSnapshotBrowser( + editor.getEditorState().editor.jsxMetadata['storyboard/scene/grid'].specialSizeMeasurements + .gridCellGlobalFrames, + `Array [ + Array [ + Object { + \"height\": 50, + \"width\": 150, + \"x\": 268, + \"y\": 174, + }, + Object { + \"height\": 50, + \"width\": 80, + \"x\": 438, + \"y\": 174, + }, + Object { + \"height\": 50, + \"width\": 121.5, + \"x\": 538, + \"y\": 174, + }, + Object { + \"height\": 50, + \"width\": 121.5, + \"x\": 679.5, + \"y\": 174, + }, + ], + Array [ + Object { + \"height\": 87.5, + \"width\": 150, + \"x\": 268, + \"y\": 244, + }, + Object { + \"height\": 87.5, + \"width\": 80, + \"x\": 438, + \"y\": 244, + }, + Object { + \"height\": 87.5, + \"width\": 121.5, + \"x\": 538, + \"y\": 244, + }, + Object { + \"height\": 87.5, + \"width\": 121.5, + \"x\": 679.5, + \"y\": 244, + }, + ], + Array [ + Object { + \"height\": 87.5, + \"width\": 150, + \"x\": 268, + \"y\": 351.5, + }, + Object { + \"height\": 87.5, + \"width\": 80, + \"x\": 438, + \"y\": 351.5, + }, + Object { + \"height\": 87.5, + \"width\": 121.5, + \"x\": 538, + \"y\": 351.5, + }, + Object { + \"height\": 87.5, + \"width\": 121.5, + \"x\": 679.5, + \"y\": 351.5, + }, + ], + Array [ + Object { + \"height\": 87.5, + \"width\": 150, + \"x\": 268, + \"y\": 458.5, + }, + Object { + \"height\": 87.5, + \"width\": 80, + \"x\": 438, + \"y\": 458.5, + }, + Object { + \"height\": 87.5, + \"width\": 121.5, + \"x\": 538, + \"y\": 458.5, + }, + Object { + \"height\": 87.5, + \"width\": 121.5, + \"x\": 679.5, + \"y\": 458.5, + }, + ], + Array [ + Object { + \"height\": 87.5, + \"width\": 150, + \"x\": 268, + \"y\": 566, + }, + Object { + \"height\": 87.5, + \"width\": 80, + \"x\": 438, + \"y\": 566, + }, + Object { + \"height\": 87.5, + \"width\": 121.5, + \"x\": 538, + \"y\": 566, + }, + Object { + \"height\": 87.5, + \"width\": 121.5, + \"x\": 679.5, + \"y\": 566, + }, + ], +]`, + ) + }) +}) diff --git a/editor/src/components/canvas/dom-sampler.tsx b/editor/src/components/canvas/dom-sampler.tsx index 7aefefa59b25..0c14be8cd74f 100644 --- a/editor/src/components/canvas/dom-sampler.tsx +++ b/editor/src/components/canvas/dom-sampler.tsx @@ -42,8 +42,11 @@ import { getAttributesComingFromStyleSheets, } from './dom-walker' import type { UiJsxCanvasContextData } from './ui-jsx-canvas' +import type { LimitExecutionCountReset } from '../../core/shared/execution-count' +import { limitExecutionCount } from '../../core/shared/execution-count' +import { IS_TEST_ENVIRONMENT } from '../../common/env-vars' -export function runDomSampler(options: { +export function runDomSamplerUnchecked(options: { elementsToFocusOn: ElementsToRerender domWalkerAdditionalElementsToFocusOn: Array scale: number @@ -67,6 +70,15 @@ export function runDomSampler(options: { } const validPaths = getValidPathsFromCanvasContainer(canvasRootContainer) + // Only perform this validation while in a test environment. + if (IS_TEST_ENVIRONMENT) { + const uniqueValidPaths = new Set(validPaths.map(EP.toString)) + if (uniqueValidPaths.size !== validPaths.length) { + throw new Error( + `Duplicate paths in validPaths: ${JSON.stringify(validPaths.map(EP.toString), null, 2)}`, + ) + } + } const spyPaths = Object.keys(options.spyCollector.current.spyValues.metadata) if (spyPaths.length === 0 && validPaths.length > 0) { @@ -116,6 +128,25 @@ export function runDomSampler(options: { return result } +let domSamplerExecutionLimitResets: Array = [] + +export function resetDomSamplerExecutionCounts() { + for (const reset of domSamplerExecutionLimitResets) { + reset() + } +} + +const wrappedRunDomSamplerRegular = limitExecutionCount( + { maximumExecutionCount: 1, addToResetArray: domSamplerExecutionLimitResets }, + runDomSamplerUnchecked, +) +const wrappedRunDomSamplerGroups = limitExecutionCount( + { maximumExecutionCount: 1, addToResetArray: domSamplerExecutionLimitResets }, + runDomSamplerUnchecked, +) +export const runDomSamplerRegular = wrappedRunDomSamplerRegular.wrappedFunction +export const runDomSamplerGroups = wrappedRunDomSamplerGroups.wrappedFunction + function collectMetadataForPaths({ canvasRootContainer, pathsToCollect, @@ -156,6 +187,8 @@ function collectMetadataForPaths({ ) }) + let domMetadataCollected: { [key: string]: DomElementMetadata | null } = {} + if (checkExistingMetadata === 'check-existing') { // delete all metadata which should be collected now and those which are not in the dom anymore Object.keys(metadataToUpdate_MUTATE).forEach((p) => { @@ -175,19 +208,23 @@ function collectMetadataForPaths({ if (domMetadata == null) { delete metadataToUpdate_MUTATE[p] } + domMetadataCollected[p] = domMetadata }) } dynamicPathsToCollect.forEach((pathString) => { const path = EP.fromString(pathString) - const domMetadata = collectMetadataForElementPath( - path, - validPaths, - selectedViews, - scale, - containerRect, - elementCanvasRectangleCache, - ) + const domMetadata = + pathString in domMetadataCollected + ? domMetadataCollected[pathString] + : collectMetadataForElementPath( + path, + validPaths, + selectedViews, + scale, + containerRect, + elementCanvasRectangleCache, + ) const spyMetadata = spyCollector.current.spyValues.metadata[EP.toString(path)] if (spyMetadata == null) { diff --git a/editor/src/components/canvas/dom-walker.spec.browser2.tsx b/editor/src/components/canvas/dom-walker.spec.browser2.tsx index 43d4423bc2d5..5e24dfa510cd 100644 --- a/editor/src/components/canvas/dom-walker.spec.browser2.tsx +++ b/editor/src/components/canvas/dom-walker.spec.browser2.tsx @@ -33,9 +33,27 @@ import { mouseClickAtPoint, mouseDownAtPoint, mouseMoveToPoint } from './event-h import { EditorModes } from '../editor/editor-modes' import { MetadataUtils } from '../../core/model/element-metadata-utils' import { optionalMap } from '../../core/shared/optional-utils' +import type { SinonFakeTimers } from 'sinon' +import sinon from 'sinon' disableStoredStateforTests() +function configureSetupTeardown(): { clock: { current: SinonFakeTimers } } { + let clock: { current: SinonFakeTimers } = { current: null as any } // it will be non-null thanks to beforeEach + beforeEach(function () { + // TODO there is something wrong with sinon fake timers here that remotely break other tests that come after these. If your new browser tests are broken, this may be the reason. + clock.current = sinon.useFakeTimers({ + // the timers will tick so the editor is not totally broken, but we can fast-forward time at will + // WARNING: the Sinon fake timers will advance in 20ms increments + shouldAdvanceTime: true, + }) + }) + afterEach(function () { + clock.current?.restore() + }) + return { clock: clock } +} + describe('DOM Walker', () => { it('Test Project metadata contains entry for all elements', async () => { const renderResult = await renderTestEditorWithCode(TestProject, 'await-first-dom-report') @@ -413,6 +431,7 @@ describe('Capturing closest offset parent', () => { }) describe('Observing runtime changes', () => { + const { clock } = configureSetupTeardown() const changingProjectCode = ` import * as React from 'react' import { Scene, Storyboard } from 'utopia-api' @@ -502,8 +521,6 @@ describe('Observing runtime changes', () => { ) expect(metadataBefore).toBeNull() - const recordedActionsBefore = [...renderResult.getRecordedActions()] - const buttonToClick = renderResult.renderedDOM.getByTestId('click-me') const buttonToClickBounds = buttonToClick.getBoundingClientRect() const clickPoint = { @@ -512,14 +529,9 @@ describe('Observing runtime changes', () => { } await mouseClickAtPoint(buttonToClick, clickPoint) - await wait(20) + clock.current.tick(100) await renderResult.getDispatchFollowUpActionsFinished() - - const recordedActionsAfter = [...renderResult.getRecordedActions()] - const recordedActionsDuring = recordedActionsAfter.slice(recordedActionsBefore.length) - - const runDomWalkerActions = recordedActionsDuring.filter((a) => a.action === 'RUN_DOM_WALKER') - expect(runDomWalkerActions.length).toEqual(2) // Both the resize observer and mutation observer will fire + clock.current.tick(100) // Check there is metadata for the target at the end const metadataAfter = MetadataUtils.findElementByElementPath( diff --git a/editor/src/components/canvas/dom-walker.ts b/editor/src/components/canvas/dom-walker.ts index 7a9a55e604f8..19a10c35f826 100644 --- a/editor/src/components/canvas/dom-walker.ts +++ b/editor/src/components/canvas/dom-walker.ts @@ -1,24 +1,26 @@ import React from 'react' import { sides } from 'utopia-api/core' -import * as ResizeObserverSyntheticDefault from 'resize-observer-polyfill' import * as EP from '../../core/shared/element-path' import type { DetectedLayoutSystem, - ComputedStyle, SpecialSizeMeasurements, - StyleAttributeMetadata, ElementInstanceMetadataMap, GridContainerProperties, GridElementProperties, DomElementMetadata, + GridAutoOrTemplateBase, + BorderWidths, + GridPositionOrSpan, } from '../../core/shared/element-template' import { - elementInstanceMetadata, specialSizeMeasurements, gridContainerProperties, gridElementProperties, gridAutoOrTemplateFallback, domElementMetadata, + gridAutoOrTemplateDimensions, + isGridSpan, + isGridAutoOrTemplateDimensions, } from '../../core/shared/element-template' import type { ElementPath } from '../../core/shared/project-file-types' import type { ElementCanvasRectangleCache } from '../../core/shared/dom-utils' @@ -47,7 +49,6 @@ import type { CSSNumber, CSSPosition } from '../inspector/common/css-utils' import { parseCSSLength, positionValues, - computedStyleKeys, parseDirection, parseFlexDirection, parseCSSPx, @@ -56,28 +57,32 @@ import { parseGridAutoOrTemplateBase, parseGridAutoFlow, isCSSKeyword, + isDynamicGridRepeat, } from '../inspector/common/css-utils' -import { camelCaseToDashed } from '../../core/shared/string-utils' import type { UtopiaStoreAPI } from '../editor/store/store-hook' -import { UTOPIA_SCENE_ID_KEY } from '../../core/model/utopia-constants' -import { CanvasContainerID } from './canvas-types' +import { UTOPIA_SCENE_ID_KEY, UTOPIA_UID_KEY } from '../../core/model/utopia-constants' import { emptySet } from '../../core/shared/set-utils' -import type { PathWithString } from '../../core/shared/uid-utils' import { getDeepestPathOnDomElement, getPathStringsOnDomElement } from '../../core/shared/uid-utils' import { forceNotNull } from '../../core/shared/optional-utils' import { fastForEach } from '../../core/shared/utils' import type { EditorState, EditorStorePatched } from '../editor/store/editor-state' import { shallowEqual } from '../../core/shared/equality-utils' -import { pick } from '../../core/shared/object-utils' -import { getFlexAlignment, getFlexJustifyContent, MaxContent } from '../inspector/inspector-common' +import { + getAlignContent, + getFlexAlignment, + getFlexJustifyContent, + getSelfAlignment, + MaxContent, +} from '../inspector/inspector-common' import type { EditorDispatch } from '../editor/action-types' import { runDOMWalker } from '../editor/actions/action-creators' - -export const ResizeObserver = - window.ResizeObserver ?? ResizeObserverSyntheticDefault.default ?? ResizeObserverSyntheticDefault +import { CanvasContainerOuterId } from './canvas-component-entry' +import { ElementsToRerenderGLOBAL } from './ui-jsx-canvas' +import type { GridCellGlobalFrames } from './canvas-strategies/strategies/grid-helpers' +import { GridMeasurementHelperMap } from './controls/grid-controls-for-strategies' +import { ObserversAvailable, ResizeObserver } from './observers' const MutationObserverConfig = { attributes: true, childList: true, subtree: true } -const ObserversAvailable = window.MutationObserver != null && ResizeObserver != null function elementLayoutSystem(computedStyle: CSSStyleDeclaration | null): DetectedLayoutSystem { if (computedStyle == null) { @@ -142,7 +147,12 @@ function isElementAContainingBlockForAbsolute(computedStyle: CSSStyleDeclaration return false } -const applicativeSidesPxTransform = (t: CSSNumber, r: CSSNumber, b: CSSNumber, l: CSSNumber) => +export const applicativeSidesPxTransform = ( + t: CSSNumber, + r: CSSNumber, + b: CSSNumber, + l: CSSNumber, +) => sides( t.unit === 'px' ? t.value : undefined, r.unit === 'px' ? r.value : undefined, @@ -258,6 +268,7 @@ export interface DomWalkerMutableStateData { initComplete: boolean mutationObserver: MutationObserver resizeObserver: ResizeObserver + gridControlObserver: MutationObserver } export function createDomWalkerMutableState( @@ -270,12 +281,13 @@ export function createDomWalkerMutableState( initComplete: true, mutationObserver: null as any, resizeObserver: null as any, + gridControlObserver: null as any, } const observers = initDomWalkerObservers(mutableData, editorStoreApi, dispatch) mutableData.mutationObserver = observers.mutationObserver mutableData.resizeObserver = observers.resizeObserver - + mutableData.gridControlObserver = observers.gridControlObserver return mutableData } @@ -290,31 +302,51 @@ function useDomWalkerMutableStateContext() { export function resubscribeObservers(domWalkerMutableState: { mutationObserver: MutationObserver resizeObserver: ResizeObserver + gridControlObserver: MutationObserver }) { - const canvasRootContainer = document.getElementById(CanvasContainerID) + const canvasRootContainer = document.getElementById(CanvasContainerOuterId) + const gridControls = document.getElementById('grid-controls') + if ( ObserversAvailable && canvasRootContainer != null && domWalkerMutableState.resizeObserver != null && domWalkerMutableState.mutationObserver != null ) { - document.querySelectorAll(`#${CanvasContainerID} *`).forEach((elem) => { + document.querySelectorAll(`#${CanvasContainerOuterId} [${UTOPIA_UID_KEY}]`).forEach((elem) => { domWalkerMutableState.resizeObserver.observe(elem) }) domWalkerMutableState.mutationObserver.observe(canvasRootContainer, MutationObserverConfig) + if (gridControls != null) { + domWalkerMutableState.gridControlObserver.observe(gridControls, MutationObserverConfig) + } } } -function selectCanvasInteractionHappening(store: EditorStorePatched): boolean { +function isCanvasInteractionHappening(store: EditorStorePatched): boolean { const interactionSessionActive = store.editor.canvas.interactionSession != null - return interactionSessionActive + return interactionSessionActive || ElementsToRerenderGLOBAL.current !== 'rerender-all-elements' } export function initDomWalkerObservers( domWalkerMutableState: DomWalkerMutableStateData, editorStore: UtopiaStoreAPI, dispatch: EditorDispatch, -): { resizeObserver: ResizeObserver; mutationObserver: MutationObserver } { +): { + resizeObserver: ResizeObserver + mutationObserver: MutationObserver + gridControlObserver: MutationObserver +} { + let domWalkerTimeoutID: number | null = null + function queueUpDomWalker(restrictToElements: Array | null): void { + if (domWalkerTimeoutID == null) { + domWalkerTimeoutID = window.setTimeout(() => { + dispatch([runDOMWalker(restrictToElements)]) + domWalkerTimeoutID = null + }) + } + } + // Warning: I modified this code so it runs in all modes, not just in live mode. We still don't trigger // the DOM walker during canvas interactions, so the performance impact doesn't seem that bad. But it is // necessary, because after remix navigation, and after dynamic changes coming from loaders sometimes the @@ -328,7 +360,7 @@ export function initDomWalkerObservers( // adequately assess the performance impact of doing so, and ideally find a way to only do so when the observed // change was not triggered by a user interaction const resizeObserver = new ResizeObserver((entries: ResizeObserverEntry[]) => { - const canvasInteractionHappening = selectCanvasInteractionHappening(editorStore.getState()) + const canvasInteractionHappening = isCanvasInteractionHappening(editorStore.getState()) const selectedViews = editorStore.getState().editor.selectedViews if (canvasInteractionHappening) { // Warning this only adds the selected views instead of the observed element @@ -345,13 +377,13 @@ export function initDomWalkerObservers( } } if (shouldRunDOMWalker) { - dispatch([runDOMWalker()]) + queueUpDomWalker(null) } } }) const mutationObserver = new window.MutationObserver((mutations: MutationRecord[]) => { - const canvasInteractionHappening = selectCanvasInteractionHappening(editorStore.getState()) + const canvasInteractionHappening = isCanvasInteractionHappening(editorStore.getState()) const selectedViews = editorStore.getState().editor.selectedViews if (canvasInteractionHappening) { @@ -377,12 +409,29 @@ export function initDomWalkerObservers( } } if (shouldRunDOMWalker) { - dispatch([runDOMWalker()]) + queueUpDomWalker(null) } } }) - return { resizeObserver, mutationObserver } + const gridControlObserver = new window.MutationObserver((mutations: MutationRecord[]) => { + let shouldRunDOMWalkerOnPath = null + mutations.forEach((mutation) => { + if (mutation.target instanceof HTMLElement) { + for (const child of mutation.target.children) { + const gridPath = child.getAttribute('data-grid-path') + if (gridPath != null) { + shouldRunDOMWalkerOnPath = EP.fromString(gridPath) + } + } + } + }) + if (shouldRunDOMWalkerOnPath != null) { + queueUpDomWalker([shouldRunDOMWalkerOnPath]) + } + }) + + return { resizeObserver, mutationObserver, gridControlObserver } } export function invalidateDomWalkerIfNecessary( @@ -510,8 +559,12 @@ export function collectDomElementMetadataForElement( ) } -function getGridContainerProperties( +export function getGridContainerProperties( elementStyle: CSSStyleDeclaration | null, + options?: { + dynamicCols: boolean + dynamicRows: boolean + }, ): GridContainerProperties { if (elementStyle == null) { return { @@ -522,14 +575,23 @@ function getGridContainerProperties( gridAutoFlow: null, } } - const gridTemplateColumns = defaultEither( - gridAutoOrTemplateFallback(elementStyle.gridTemplateColumns), - parseGridAutoOrTemplateBase(elementStyle.gridTemplateColumns), + + const gridTemplateColumns = trimDynamicEmptyDimensions( + defaultEither( + gridAutoOrTemplateFallback(elementStyle.gridTemplateColumns), + parseGridAutoOrTemplateBase(elementStyle.gridTemplateColumns), + ), + options?.dynamicCols === true, ) - const gridTemplateRows = defaultEither( - gridAutoOrTemplateFallback(elementStyle.gridTemplateRows), - parseGridAutoOrTemplateBase(elementStyle.gridTemplateRows), + + const gridTemplateRows = trimDynamicEmptyDimensions( + defaultEither( + gridAutoOrTemplateFallback(elementStyle.gridTemplateRows), + parseGridAutoOrTemplateBase(elementStyle.gridTemplateRows), + ), + options?.dynamicRows === true, ) + const gridAutoColumns = defaultEither( gridAutoOrTemplateFallback(elementStyle.gridAutoColumns), parseGridAutoOrTemplateBase(elementStyle.gridAutoColumns), @@ -547,66 +609,72 @@ function getGridContainerProperties( ) } -function getGridElementProperties( +function trimDynamicEmptyDimensions( + template: GridAutoOrTemplateBase, + isDynamic: boolean, +): GridAutoOrTemplateBase { + if (!isDynamic) { + return template + } + if (template.type !== 'DIMENSIONS') { + return template + } + + const lastNonEmptyColumn = template.dimensions.findLastIndex( + (d) => d.type === 'KEYWORD' || (d.type === 'NUMBER' && d.value.value !== 0), + ) + return gridAutoOrTemplateDimensions(template.dimensions.slice(0, lastNonEmptyColumn + 1)) +} + +export function getGridElementProperties( container: GridContainerProperties, elementStyle: CSSStyleDeclaration, ): GridElementProperties { + function getPlacementPin( + value: GridPositionOrSpan | null, + axis: 'row' | 'column', + pin: 'start' | 'end', + style: string, + ) { + if (isGridSpan(value) || value != null) { + return value + } + return defaultEither(null, parseGridPosition(container, axis, pin, value ?? null, style)) + } + const gridColumn = defaultEither( null, parseGridRange(container, 'column', elementStyle.gridColumn), ) - - const gridColumnStart = - gridColumn?.start ?? - defaultEither( - null, - parseGridPosition( - container, - 'column', - 'start', - gridColumn?.start ?? null, - elementStyle.gridColumnStart, - ), - ) ?? - null - const gridColumnEnd = - gridColumn?.end ?? - defaultEither( - null, - parseGridPosition( - container, - 'column', - 'end', - gridColumn?.end ?? null, - elementStyle.gridColumnEnd, - ), - ) ?? - null + const gridColumnStart = getPlacementPin( + gridColumn?.start ?? null, + 'column', + 'start', + elementStyle.gridColumnStart, + ) + const gridColumnEnd = getPlacementPin( + gridColumn?.end ?? null, + 'column', + 'end', + elementStyle.gridColumnEnd, + ) const adjustedColumnEnd = - isCSSKeyword(gridColumnEnd) && gridColumn?.end != null ? gridColumn.end : gridColumnEnd + isGridSpan(gridColumn?.end) || (isCSSKeyword(gridColumnEnd) && gridColumn?.end != null) + ? gridColumn.end + : gridColumnEnd const gridRow = defaultEither(null, parseGridRange(container, 'row', elementStyle.gridRow)) - const gridRowStart = - gridRow?.start ?? - defaultEither( - null, - parseGridPosition( - container, - 'row', - 'start', - gridRow?.start ?? null, - elementStyle.gridRowStart, - ), - ) ?? - null - const gridRowEnd = - gridRow?.end ?? - defaultEither( - null, - parseGridPosition(container, 'row', 'end', gridRow?.end ?? null, elementStyle.gridRowEnd), - ) ?? - null - const adjustedRowEnd = isCSSKeyword(gridRowEnd) && gridRow?.end != null ? gridRow.end : gridRowEnd + const gridRowStart = getPlacementPin( + gridRow?.start ?? null, + 'row', + 'start', + elementStyle.gridRowStart, + ) + const gridRowEnd = getPlacementPin(gridRow?.end ?? null, 'row', 'end', elementStyle.gridRowEnd) + const adjustedRowEnd = + isGridSpan(gridRow?.end) || (isCSSKeyword(gridRowEnd) && gridRow?.end != null) + ? gridRow.end + : gridRowEnd const result = gridElementProperties( gridColumnStart, @@ -624,9 +692,9 @@ function getSpecialMeasurements( containerRectLazy: CanvasPoint | (() => CanvasPoint), elementCanvasRectangleCache: ElementCanvasRectangleCache, ): SpecialSizeMeasurements { - const elementStyle = window.getComputedStyle(element) - const layoutSystemForChildren = elementLayoutSystem(elementStyle) - const position = getPosition(elementStyle) + const computedStyle = window.getComputedStyle(element) + const layoutSystemForChildren = elementLayoutSystem(computedStyle) + const position = getPosition(computedStyle) const offset = { x: roundToNearestHalf(element.offsetLeft), @@ -661,7 +729,7 @@ function getSpecialMeasurements( element.parentElement == null ? null : window.getComputedStyle(element.parentElement) const isParentNonStatic = isElementNonStatic(parentElementStyle) - const providesBoundsForAbsoluteChildren = isElementAContainingBlockForAbsolute(elementStyle) + const providesBoundsForAbsoluteChildren = isElementAContainingBlockForAbsolute(computedStyle) const parentLayoutSystem = elementLayoutSystem(parentElementStyle) const parentProvidesLayout = element.parentElement === element.offsetParent @@ -676,26 +744,29 @@ function getSpecialMeasurements( element.parentElement != null && element.parentElement.style[sizeMainAxis] === MaxContent - const flexDirection = eitherToMaybe(parseFlexDirection(elementStyle.flexDirection, null)) + const flexDirection = eitherToMaybe(parseFlexDirection(computedStyle.flexDirection, null)) const parentTextDirection = eitherToMaybe(parseDirection(parentElementStyle?.direction, null)) - const justifyContent = getFlexJustifyContent(elementStyle.justifyContent) - const alignItems = getFlexAlignment(elementStyle.alignItems) + const justifyContent = getFlexJustifyContent(computedStyle.justifyContent) + const alignContent = getAlignContent(computedStyle.alignContent) + const alignItems = getFlexAlignment(computedStyle.alignItems) + const alignSelf = getSelfAlignment(computedStyle.alignSelf) + const justifySelf = getSelfAlignment(computedStyle.justifySelf) const margin = applicative4Either( applicativeSidesPxTransform, - parseCSSLength(elementStyle.marginTop), - parseCSSLength(elementStyle.marginRight), - parseCSSLength(elementStyle.marginBottom), - parseCSSLength(elementStyle.marginLeft), + parseCSSLength(computedStyle.marginTop), + parseCSSLength(computedStyle.marginRight), + parseCSSLength(computedStyle.marginBottom), + parseCSSLength(computedStyle.marginLeft), ) const padding = applicative4Either( applicativeSidesPxTransform, - parseCSSLength(elementStyle.paddingTop), - parseCSSLength(elementStyle.paddingRight), - parseCSSLength(elementStyle.paddingBottom), - parseCSSLength(elementStyle.paddingLeft), + parseCSSLength(computedStyle.paddingTop), + parseCSSLength(computedStyle.paddingRight), + parseCSSLength(computedStyle.paddingBottom), + parseCSSLength(computedStyle.paddingLeft), ) const parentPadding = applicative4Either( @@ -718,11 +789,11 @@ function getSpecialMeasurements( const childrenCount = element.childElementCount - const borderTopWidth = parseCSSLength(elementStyle.borderTopWidth) - const borderRightWidth = parseCSSLength(elementStyle.borderRightWidth) - const borderBottomWidth = parseCSSLength(elementStyle.borderBottomWidth) - const borderLeftWidth = parseCSSLength(elementStyle.borderLeftWidth) - const border = { + const borderTopWidth = parseCSSLength(computedStyle.borderTopWidth) + const borderRightWidth = parseCSSLength(computedStyle.borderRightWidth) + const borderBottomWidth = parseCSSLength(computedStyle.borderBottomWidth) + const borderLeftWidth = parseCSSLength(computedStyle.borderLeftWidth) + const border: BorderWidths = { top: isRight(borderTopWidth) ? borderTopWidth.value.value : 0, right: isRight(borderRightWidth) ? borderRightWidth.value.value : 0, bottom: isRight(borderBottomWidth) ? borderBottomWidth.value.value : 0, @@ -763,25 +834,25 @@ function getSpecialMeasurements( } const hasPositionOffset = - !positionValueIsDefault(elementStyle.top) || - !positionValueIsDefault(elementStyle.right) || - !positionValueIsDefault(elementStyle.bottom) || - !positionValueIsDefault(elementStyle.left) - const hasTransform = elementStyle.transform !== 'none' + !positionValueIsDefault(computedStyle.top) || + !positionValueIsDefault(computedStyle.right) || + !positionValueIsDefault(computedStyle.bottom) || + !positionValueIsDefault(computedStyle.left) + const hasTransform = computedStyle.transform !== 'none' const gap = defaultEither( null, - mapEither((n) => n.value, parseCSSLength(elementStyle.gap)), + mapEither((n) => n.value, parseCSSLength(computedStyle.gap)), ) const rowGap = defaultEither( null, - mapEither((n) => n.value, parseCSSLength(elementStyle.rowGap)), + mapEither((n) => n.value, parseCSSLength(computedStyle.rowGap)), ) const columnGap = defaultEither( null, - mapEither((n) => n.value, parseCSSLength(elementStyle.columnGap)), + mapEither((n) => n.value, parseCSSLength(computedStyle.columnGap)), ) const flexGapValue = parseCSSLength(parentElementStyle?.gap) @@ -791,17 +862,17 @@ function getSpecialMeasurements( null, applicative4Either( applicativeSidesPxTransform, - parseCSSLength(elementStyle.borderTopLeftRadius), - parseCSSLength(elementStyle.borderTopRightRadius), - parseCSSLength(elementStyle.borderBottomLeftRadius), - parseCSSLength(elementStyle.borderBottomRightRadius), + parseCSSLength(computedStyle.borderTopLeftRadius), + parseCSSLength(computedStyle.borderTopRightRadius), + parseCSSLength(computedStyle.borderBottomLeftRadius), + parseCSSLength(computedStyle.borderBottomRightRadius), ), ) - const fontSize = elementStyle.fontSize - const fontWeight = elementStyle.fontWeight - const fontStyle = elementStyle.fontStyle - const textDecorationLine = elementStyle.textDecorationLine + const fontSize = computedStyle.fontSize + const fontWeight = computedStyle.fontWeight + const fontStyle = computedStyle.fontStyle + const textDecorationLine = computedStyle.textDecorationLine const textBounds = elementContainsOnlyText(element) ? stretchRect( @@ -814,15 +885,15 @@ function getSpecialMeasurements( ), { w: - maybeValueFromComputedStyle(elementStyle.paddingLeft) + - maybeValueFromComputedStyle(elementStyle.paddingRight) + - maybeValueFromComputedStyle(elementStyle.marginLeft) + - maybeValueFromComputedStyle(elementStyle.marginRight), + maybeValueFromComputedStyle(computedStyle.paddingLeft) + + maybeValueFromComputedStyle(computedStyle.paddingRight) + + maybeValueFromComputedStyle(computedStyle.marginLeft) + + maybeValueFromComputedStyle(computedStyle.marginRight), h: - maybeValueFromComputedStyle(elementStyle.paddingTop) + - maybeValueFromComputedStyle(elementStyle.paddingBottom) + - maybeValueFromComputedStyle(elementStyle.marginTop) + - maybeValueFromComputedStyle(elementStyle.marginBottom), + maybeValueFromComputedStyle(computedStyle.paddingTop) + + maybeValueFromComputedStyle(computedStyle.paddingBottom) + + maybeValueFromComputedStyle(computedStyle.marginTop) + + maybeValueFromComputedStyle(computedStyle.marginBottom), }, ) : null @@ -834,18 +905,60 @@ function getSpecialMeasurements( globalFrame, ) - const parentContainerGridProperties = getGridContainerProperties(parentElementStyle) + const paddingValue = isRight(padding) + ? padding.value + : sides(undefined, undefined, undefined, undefined) + + const gridCellGlobalFrames = + layoutSystemForChildren === 'grid' + ? measureGlobalFramesOfGridCells( + element, + scale, + containerRectLazy, + elementCanvasRectangleCache, + ) + : null + + const parentGridCellGlobalFrames = + element.parentElement != null && elementLayoutSystem(parentElementStyle) === 'grid' + ? measureGlobalFramesOfGridCells( + element.parentElement, + scale, + containerRectLazy, + elementCanvasRectangleCache, + ) + : null + + const parentGridFrame = + element.parentElement != null && elementLayoutSystem(parentElementStyle) === 'grid' + ? globalFrameForElement( + element.parentElement, + scale, + containerRectLazy, + 'without-text-content', + 'nearest-half', + elementCanvasRectangleCache, + ) + : null - const containerGridProperties = getGridContainerProperties(elementStyle) - const containerElementProperties = getGridElementProperties( - parentContainerGridProperties, - elementStyle, - ) const containerGridPropertiesFromProps = getGridContainerProperties(element.style) + const parentContainerGridPropertiesFromProps = getGridContainerProperties( + element.parentElement?.style ?? parentElementStyle, + ) + const containerGridProperties = getGridContainerProperties(computedStyle, { + dynamicCols: isDynamicGridTemplate(containerGridPropertiesFromProps.gridTemplateColumns), + dynamicRows: isDynamicGridTemplate(containerGridPropertiesFromProps.gridTemplateRows), + }) + + const parentContainerGridProperties = getGridContainerProperties(parentElementStyle) const containerElementPropertiesFromProps = getGridElementProperties( parentContainerGridProperties, element.style, ) + const containerElementProperties = getGridElementProperties( + parentContainerGridProperties, + computedStyle, + ) return specialSizeMeasurements( offset, @@ -857,11 +970,12 @@ function getSpecialMeasurements( isParentNonStatic, parentLayoutSystem, layoutSystemForChildren, + false, // layoutSystemForChildrenInherited providesBoundsForAbsoluteChildren, - elementStyle.display, + computedStyle.display, position, isRight(margin) ? margin.value : sides(undefined, undefined, undefined, undefined), - isRight(padding) ? padding.value : sides(undefined, undefined, undefined, undefined), + paddingValue, naturalWidth, naturalHeight, clientWidth, @@ -876,11 +990,12 @@ function getSpecialMeasurements( gap, flexDirection, justifyContent, + alignContent, alignItems, element.localName, childrenCount, globalContentBoxForChildren, - elementStyle.float, + computedStyle.float, hasPositionOffset, parentTextDirection, hasTransform, @@ -892,14 +1007,26 @@ function getSpecialMeasurements( textBounds, computedHugProperty, containerGridProperties, + parentContainerGridProperties, containerElementProperties, containerGridPropertiesFromProps, + parentContainerGridPropertiesFromProps, containerElementPropertiesFromProps, rowGap, columnGap, + gridCellGlobalFrames, + parentGridCellGlobalFrames, + justifySelf, + alignSelf, + border, + parentGridFrame, ) } +export function isDynamicGridTemplate(template: GridAutoOrTemplateBase | null) { + return template?.type === 'DIMENSIONS' && template.dimensions.some((d) => isDynamicGridRepeat(d)) +} + function elementContainsOnlyText(element: HTMLElement): boolean { if (element.childNodes.length === 0) { return false @@ -953,3 +1080,49 @@ function getClosestOffsetParent(element: HTMLElement): Element | null { } return null } + +function measureGlobalFramesOfGridCells( + element: HTMLElement, + scale: number, + containerRectLazy: CanvasPoint | (() => CanvasPoint), + elementCanvasRectangleCache: ElementCanvasRectangleCache, +): GridCellGlobalFrames | null { + let gridCellGlobalFrames: GridCellGlobalFrames | null = null + + const gridMeasurementHelperId = GridMeasurementHelperMap.current.get(element) + + const gridControlElement = + gridMeasurementHelperId != null ? document.getElementById(gridMeasurementHelperId) : null + + if (gridControlElement != null) { + gridCellGlobalFrames = [] + for (const cell of gridControlElement.children) { + if (!(cell instanceof HTMLElement)) { + continue + } + const rowIndexAttr = cell.getAttribute('data-grid-row') + const columnIndexAttr = cell.getAttribute('data-grid-column') + if (rowIndexAttr == null || columnIndexAttr == null) { + continue + } + const rowIndex = parseInt(rowIndexAttr) + const columnIndex = parseInt(columnIndexAttr) + if (!isFinite(rowIndex) || !isFinite(columnIndex)) { + continue + } + const row = gridCellGlobalFrames[rowIndex - 1] + if (row == null) { + gridCellGlobalFrames[rowIndex - 1] = [] + } + gridCellGlobalFrames[rowIndex - 1][columnIndex - 1] = globalFrameForElement( + cell, + scale, + containerRectLazy, + 'without-text-content', + 'nearest-half', + elementCanvasRectangleCache, + ) + } + } + return gridCellGlobalFrames +} diff --git a/editor/src/components/canvas/editor-dispatch-flow.tsx b/editor/src/components/canvas/editor-dispatch-flow.tsx index f74e035eaa3c..fbfdf7987849 100644 --- a/editor/src/components/canvas/editor-dispatch-flow.tsx +++ b/editor/src/components/canvas/editor-dispatch-flow.tsx @@ -1,9 +1,10 @@ +import type { ElementPath } from 'utopia-shared/src/types' import type { EditorDispatch } from '../editor/action-types' import { updateMetadataInEditorState } from '../editor/actions/action-creators' import type { DispatchResult } from '../editor/store/dispatch' import { editorDispatchActionRunner } from '../editor/store/dispatch' import type { EditorStoreFull } from '../editor/store/editor-state' -import { runDomSampler } from './dom-sampler' +import { runDomSamplerRegular } from './dom-sampler' import { resubscribeObservers } from './dom-walker' import { ElementsToRerenderGLOBAL, type UiJsxCanvasContextData } from './ui-jsx-canvas' @@ -29,11 +30,20 @@ export function runDomSamplerAndSaveResults( domWalkerMutableState: { mutationObserver: MutationObserver resizeObserver: ResizeObserver + gridControlObserver: MutationObserver }, spyCollector: UiJsxCanvasContextData, + restrictToElements: + | 'run-full' + | { + restrictToElements: Array + }, ) { - const metadataResult = runDomSampler({ - elementsToFocusOn: ElementsToRerenderGLOBAL.current, + const metadataResult = runDomSamplerRegular({ + elementsToFocusOn: + restrictToElements === 'run-full' + ? ElementsToRerenderGLOBAL.current + : restrictToElements.restrictToElements, domWalkerAdditionalElementsToFocusOn: storedState.patchedEditor.canvas.domWalkerAdditionalElementsToUpdate, scale: storedState.patchedEditor.canvas.scale, diff --git a/editor/src/components/canvas/event-helpers.test-utils.tsx b/editor/src/components/canvas/event-helpers.test-utils.tsx index 11f37d4f63fb..1f5e1c50e768 100644 --- a/editor/src/components/canvas/event-helpers.test-utils.tsx +++ b/editor/src/components/canvas/event-helpers.test-utils.tsx @@ -205,6 +205,7 @@ export async function mouseDragFromPointToPoint( moveBeforeMouseDown?: boolean skipMouseUp?: boolean realMouseDown?: boolean + tab?: boolean } = {}, ): Promise { const { buttons, ...mouseUpOptions } = options.eventOptions ?? {} @@ -271,6 +272,10 @@ export async function mouseDragFromPointToPoint( ) } + if (options.tab) { + await keyDown('Tab') + } + if (options.midDragCallback != null) { await options.midDragCallback() } diff --git a/editor/src/components/canvas/gap-utils.ts b/editor/src/components/canvas/gap-utils.ts index 99a70eeae5c4..b0c227034138 100644 --- a/editor/src/components/canvas/gap-utils.ts +++ b/editor/src/components/canvas/gap-utils.ts @@ -16,22 +16,23 @@ import type { CanvasRectangle, CanvasVector, Size } from '../../core/shared/math import { canvasRectangle, isInfinityRectangle } from '../../core/shared/math-utils' import type { ElementPath } from '../../core/shared/project-file-types' import { assertNever } from '../../core/shared/utils' -import { CSSCursor } from './canvas-types' +import type { StyleInfo } from './canvas-types' +import { CSSCursor, maybePropertyValue } from './canvas-types' import type { CSSNumberWithRenderedValue } from './controls/select-mode/controls-common' import type { CSSNumber, FlexDirection } from '../inspector/common/css-utils' import type { Sides } from 'utopia-api/core' import { sides } from 'utopia-api/core' import { styleStringInArray } from '../../utils/common-constants' -import { getSubTree, type ElementPathTrees } from '../../core/shared/element-path-tree' +import { + getElementPathTreeChildren, + getSubTree, + type ElementPathTrees, +} from '../../core/shared/element-path-tree' import { isReversedFlexDirection } from '../../core/model/flex-utils' import * as EP from '../../core/shared/element-path' import { treatElementAsFragmentLike } from './canvas-strategies/strategies/fragment-like-helpers' import type { AllElementProps } from '../editor/store/editor-state' -import type { GridData } from './controls/grid-controls' -import { - getGridPlaceholderDomElement, - getNullableAutoOrTemplateBaseString, -} from './controls/grid-controls' +import { optionalMap } from '../../core/shared/optional-utils' export interface PathWithBounds { bounds: CanvasRectangle @@ -174,122 +175,6 @@ export function gapControlBoundsFromMetadata( ) } -export function gridGapControlBoundsFromMetadata( - parentPath: ElementPath, - gridRowColumnInfo: GridData, - gapValues: { row: CSSNumber; column: CSSNumber }, - scale: number, -): { - gaps: Array<{ - bounds: CanvasRectangle - gapId: string - gap: CSSNumber - axis: Axis - }> - rows: number - columns: number - cellBounds: CanvasRectangle - gapValues: { row: CSSNumber; column: CSSNumber } - gridTemplateRows: string - gridTemplateColumns: string -} { - const emptyResult = { - rows: 0, - columns: 0, - gaps: [], - cellBounds: canvasRectangle({ x: 0, y: 0, width: 0, height: 0 }), - gapValues: gapValues, - gridTemplateRows: '1fr', - gridTemplateColumns: '1fr', - } - const parentGrid = getGridPlaceholderDomElement(parentPath) - if (parentGrid == null) { - return emptyResult - } - const parentGridBounds = parentGrid?.getBoundingClientRect() - const gridRows = gridRowColumnInfo.rows - const gridColumns = gridRowColumnInfo.columns - const gridTemplateRows = getNullableAutoOrTemplateBaseString(gridRowColumnInfo.gridTemplateRows) - const gridTemplateColumns = getNullableAutoOrTemplateBaseString( - gridRowColumnInfo.gridTemplateColumns, - ) - const childrenArray = Array.from(parentGrid?.children ?? []) - if (childrenArray.length !== gridRows * gridColumns) { - return emptyResult - } - const cell = matrixGetter(childrenArray, gridColumns) - // the actual rectangle that surrounds the cell placeholders - const cellBounds = canvasRectangle({ - x: cell(0, 0).getBoundingClientRect().x - parentGridBounds.x, - y: cell(0, 0).getBoundingClientRect().y - parentGridBounds.y, - width: - cell(0, gridColumns - 1).getBoundingClientRect().right - cell(0, 0).getBoundingClientRect().x, - height: - cell(gridRows - 1, 0).getBoundingClientRect().bottom - cell(0, 0).getBoundingClientRect().y, - }) - - // row gaps array - const rowGaps = createArrayWithLength(gridRows - 1, (i) => { - // cell i represents the gap between child [i * gridColumns] and child [(i+1) * gridColumns] - const firstChildBounds = cell(i, 0).getBoundingClientRect() - const secondChildBounds = cell(i + 1, 0).getBoundingClientRect() - return { - gapId: `${EP.toString(parentPath)}-row-gap-${i}`, - bounds: adjustToScale( - canvasRectangle({ - x: cellBounds.x, - y: firstChildBounds.bottom - parentGridBounds.y, - width: cellBounds.width, - height: secondChildBounds.top - firstChildBounds.bottom, - }), - scale, - ), - gap: gapValues.row, - axis: 'row' as Axis, - } - }) - - // column gaps array - const columnGaps = createArrayWithLength(gridColumns - 1, (i) => { - // cell i represents the gap between child [i] and child [i + 1] - const firstChildBounds = cell(0, i).getBoundingClientRect() - const secondChildBounds = cell(0, i + 1).getBoundingClientRect() - return { - gapId: `${EP.toString(parentPath)}-column-gap-${i}`, - bounds: adjustToScale( - canvasRectangle({ - x: firstChildBounds.right - parentGridBounds.x, - y: cellBounds.y, - width: secondChildBounds.left - firstChildBounds.right, - height: cellBounds.height, - }), - scale, - ), - gap: gapValues.column, - axis: 'column' as Axis, - } - }) - - return { - gaps: rowGaps.concat(columnGaps), - rows: gridRows, - columns: gridColumns, - gridTemplateRows: gridTemplateRows ?? '', - gridTemplateColumns: gridTemplateColumns ?? '', - cellBounds: cellBounds, - gapValues: gapValues, - } -} - -function adjustToScale(rectangle: CanvasRectangle, scale: number): CanvasRectangle { - return canvasRectangle({ - x: rectangle.x / scale, - y: rectangle.y / scale, - width: rectangle.width / scale, - height: rectangle.height / scale, - }) -} - export interface GridGapData { row: CSSNumberWithRenderedValue column: CSSNumberWithRenderedValue @@ -334,15 +219,15 @@ export interface FlexGapData { } export function maybeFlexGapData( - metadata: ElementInstanceMetadataMap, - elementPath: ElementPath, + info: StyleInfo | null, + element: ElementInstanceMetadata | null, ): FlexGapData | null { - const element = MetadataUtils.findElementByElementPath(metadata, elementPath) if ( element == null || element.specialSizeMeasurements.display !== 'flex' || isLeft(element.element) || - !isJSXElement(element.element.value) + !isJSXElement(element.element.value) || + info == null ) { return null } @@ -352,18 +237,14 @@ export function maybeFlexGapData( } const gap = element.specialSizeMeasurements.gap ?? 0 - - const gapFromProps: CSSNumber | undefined = defaultEither( - undefined, - getLayoutProperty('gap', right(element.element.value.props), styleStringInArray), - ) + const gapFromReader = optionalMap(maybePropertyValue, info.gap) const flexDirection = element.specialSizeMeasurements.flexDirection ?? 'row' return { value: { renderedValuePx: gap, - value: gapFromProps ?? null, + value: gapFromReader, }, direction: flexDirection, } @@ -380,7 +261,7 @@ export function recurseIntoChildrenOfMapOrFragment( return [] } - return subTree.children.flatMap((element) => { + return getElementPathTreeChildren(subTree).flatMap((element) => { const elementPath = element.path if (EP.isRootElementOfInstance(elementPath)) { return [] diff --git a/editor/src/components/canvas/hugging-utils.ts b/editor/src/components/canvas/hugging-utils.ts new file mode 100644 index 000000000000..c6f552f74976 --- /dev/null +++ b/editor/src/components/canvas/hugging-utils.ts @@ -0,0 +1,26 @@ +import { MetadataUtils } from '../../core/model/element-metadata-utils' +import type { ElementInstanceMetadataMap } from '../../core/shared/element-template' +import type { ElementPath } from '../../core/shared/project-file-types' + +export type HuggingElementContentsStatus = + | 'empty' + | 'contains-only-absolute' + | 'contains-some-absolute' + | 'non-empty' + +export function getHuggingElementContentsStatus( + jsxMetadata: ElementInstanceMetadataMap, + path: ElementPath, +): HuggingElementContentsStatus { + const children = MetadataUtils.getChildrenUnordered(jsxMetadata, path) + const absoluteChildren = children.filter(MetadataUtils.isPositionAbsolute).length + if (children.length === 0) { + return 'empty' + } else if (absoluteChildren === children.length) { + return 'contains-only-absolute' + } else if (absoluteChildren > 0) { + return 'contains-some-absolute' + } else { + return 'non-empty' + } +} diff --git a/editor/src/components/canvas/observers.ts b/editor/src/components/canvas/observers.ts new file mode 100644 index 000000000000..20c15dadf17c --- /dev/null +++ b/editor/src/components/canvas/observers.ts @@ -0,0 +1,117 @@ +import * as ResizeObserverSyntheticDefault from 'resize-observer-polyfill' +import type { ElementPath } from 'utopia-shared/src/types' +import { getDeepestPathOnDomElement } from '../../core/shared/uid-utils' +import * as EP from '../../core/shared/element-path' +import { assertNever } from '../../core/shared/utils' +import { CanvasContainerID } from './canvas-types' + +export const ResizeObserver = + window.ResizeObserver ?? ResizeObserverSyntheticDefault.default ?? ResizeObserverSyntheticDefault + +export const ObserversAvailable = window.MutationObserver != null && ResizeObserver != null + +export type ChangeCallback = (change: 'updated' | 'deleted') => void + +export interface TargetAndCallback { + target: ElementPath + callback: ChangeCallback +} + +let targetsAndCallbacks: Array = [] +let currentMutationObserver: MutationObserver | null = null +let observersSetup = false +let priorMountCount: number = -1 + +function cleanupObservers(): void { + if (currentMutationObserver != null) { + currentMutationObserver.disconnect() + currentMutationObserver = null + } + observersSetup = false +} + +function setupObservers(mountCount: number): void { + // If the observers can't be used bail out right from the start. + if (!ObserversAvailable) { + return + } + + // Skip setting up the observers if they have already been setup and the mount count hasn't changed. + // Where the mount count changing implies the entire canvas will have been unmounted and recreated. + if (observersSetup && mountCount === priorMountCount) { + return + } + + // Disconnect the old observer if it exists. + cleanupObservers() + + // Find the canvas container to observe. + const canvasContainer = document.getElementById(CanvasContainerID) + if (canvasContainer == null) { + return + } + + const mutationObserver = new MutationObserver((mutations) => { + for (const mutation of mutations) { + switch (mutation.type) { + case 'childList': + case 'attributes': + function maybeTriggerCallback(targetNode: Node, change: 'updated' | 'deleted'): void { + if (targetNode instanceof Element) { + // Find the path of this particular element. + const pathOnElement = getDeepestPathOnDomElement(targetNode) + if (pathOnElement != null) { + for (const targetAndCallback of targetsAndCallbacks) { + if (EP.pathsEqual(targetAndCallback.target, pathOnElement)) { + targetAndCallback.callback(change) + } + } + } + } + } + + // Handle updates to the target first, then deletions from the children followed by + // additions to the children. + maybeTriggerCallback(mutation.target, 'updated') + mutation.removedNodes.forEach((removedNode) => { + maybeTriggerCallback(removedNode, 'deleted') + }) + mutation.addedNodes.forEach((addedNode) => { + maybeTriggerCallback(addedNode, 'updated') + }) + break + case 'characterData': + break + default: + assertNever(mutation.type) + } + } + }) + + // Connect up the mutation observer. + mutationObserver.observe(canvasContainer, { + childList: true, + subtree: true, + attributes: true, + }) + + // Record our current state of affairs. + currentMutationObserver = mutationObserver + priorMountCount = mountCount + observersSetup = true +} + +export function addChangeCallback( + mountCount: number, + target: ElementPath, + callback: ChangeCallback, +): void { + setupObservers(mountCount) + targetsAndCallbacks.push({ target: target, callback: callback }) +} + +export function removeChangeCallback(target: ElementPath, callback: ChangeCallback): void { + targetsAndCallbacks = targetsAndCallbacks.filter( + (item) => item.target !== target || item.callback !== callback, + ) +} diff --git a/editor/src/components/canvas/padding-utils.tsx b/editor/src/components/canvas/padding-utils.tsx index 7f2247aeeaed..117e0f35973f 100644 --- a/editor/src/components/canvas/padding-utils.tsx +++ b/editor/src/components/canvas/padding-utils.tsx @@ -1,9 +1,6 @@ import { styleStringInArray } from '../../utils/common-constants' -import { getLayoutProperty } from '../../core/layout/getLayoutProperty' import { MetadataUtils } from '../../core/model/element-metadata-utils' -import { defaultEither, isLeft, right } from '../../core/shared/either' import type { ElementInstanceMetadataMap } from '../../core/shared/element-template' -import { isJSXElement } from '../../core/shared/element-template' import type { CanvasVector, Size } from '../../core/shared/math-utils' import { numberIsZero, roundTo, zeroRectIfNullOrInfinity } from '../../core/shared/math-utils' import { optionalMap } from '../../core/shared/optional-utils' @@ -11,7 +8,7 @@ import type { ElementPath } from '../../core/shared/project-file-types' import { assertNever } from '../../core/shared/utils' import type { CSSNumber, CSSNumberUnit, CSSPadding } from '../inspector/common/css-utils' import { printCSSNumber } from '../inspector/common/css-utils' -import type { EdgePiece } from './canvas-types' +import { maybePropertyValue, type EdgePiece, type StyleInfo } from './canvas-types' import type { AdjustPrecision, CSSNumberWithRenderedValue, @@ -58,12 +55,11 @@ export function paddingFromSpecialSizeMeasurements( return paddingMappedMeasurements } -export function simplePaddingFromMetadata( +export function simplePaddingFromStyleInfo( metadata: ElementInstanceMetadataMap, elementPath: ElementPath, + styleInfo: StyleInfo | null, ): CSSPaddingMappedValues { - const element = MetadataUtils.findElementByElementPath(metadata, elementPath) - const defaults: CSSPaddingMappedValues = { paddingTop: undefined, paddingRight: undefined, @@ -71,39 +67,15 @@ export function simplePaddingFromMetadata( paddingLeft: undefined, } - if (element == null || isLeft(element.element) || !isJSXElement(element.element.value)) { - return { - paddingTop: undefined, - paddingRight: undefined, - paddingBottom: undefined, - paddingLeft: undefined, - } - } - const paddingNumbers = paddingFromSpecialSizeMeasurements(metadata, elementPath) - const padding: CSSPadding | undefined = defaultEither( - undefined, - getLayoutProperty('padding', right(element.element.value.props), styleStringInArray), - ) + const padding = optionalMap(maybePropertyValue, styleInfo?.padding) - const paddingLonghands: CSSPaddingMappedValues = { - paddingTop: defaultEither( - undefined, - getLayoutProperty('paddingTop', right(element.element.value.props), styleStringInArray), - ), - paddingBottom: defaultEither( - undefined, - getLayoutProperty('paddingBottom', right(element.element.value.props), styleStringInArray), - ), - paddingLeft: defaultEither( - undefined, - getLayoutProperty('paddingLeft', right(element.element.value.props), styleStringInArray), - ), - paddingRight: defaultEither( - undefined, - getLayoutProperty('paddingRight', right(element.element.value.props), styleStringInArray), - ), + const paddingLonghands: CSSPaddingMappedValues = { + paddingTop: optionalMap(maybePropertyValue, styleInfo?.paddingTop), + paddingBottom: optionalMap(maybePropertyValue, styleInfo?.paddingBottom), + paddingLeft: optionalMap(maybePropertyValue, styleInfo?.paddingLeft), + paddingRight: optionalMap(maybePropertyValue, styleInfo?.paddingRight), } const make = (prop: CSSPaddingKey): CSSNumberWithRenderedValue | undefined => { diff --git a/editor/src/components/canvas/plugins/inline-style-plugin.spec.ts b/editor/src/components/canvas/plugins/inline-style-plugin.spec.ts new file mode 100644 index 000000000000..e0e8029bce6f --- /dev/null +++ b/editor/src/components/canvas/plugins/inline-style-plugin.spec.ts @@ -0,0 +1,117 @@ +import * as EP from '../../../core/shared/element-path' +import { cssNumber } from '../../inspector/common/css-utils' +import { cssStylePropertyNotFound } from '../canvas-types' +import type { EditorRenderResult } from '../ui-jsx.test-utils' +import { renderTestEditorWithCode } from '../ui-jsx.test-utils' +import { InlineStylePlugin } from './inline-style-plugin' + +describe('inline style plugin', () => { + it('can parse style info from element', async () => { + const editor = await renderTestEditorWithCode( + ` +import React from 'react' +import { Scene, Storyboard } from 'utopia-api' +export var storyboard = ( + + +
+ + +) + +`, + 'await-first-dom-report', + ) + + const styleInfo = getStyleInfoFromInlineStyle(editor) + + expect(styleInfo).not.toBeNull() + const { flexDirection, gap } = styleInfo! + expect(flexDirection).toMatchObject({ + type: 'property', + tags: [], + value: 'column', + propertyValue: { value: 'column' }, + }) + expect(gap).toMatchObject({ + type: 'property', + tags: [], + value: cssNumber(2, 'rem'), + propertyValue: { value: '2rem' }, + }) + }) + + it('can parse style info with missing/unparsable props', async () => { + const editor = await renderTestEditorWithCode( + ` + import React from 'react' + import { Scene, Storyboard } from 'utopia-api' + + const gap = { small: '1rem' } + + export var storyboard = ( + + +
+ + + ) + + `, + 'await-first-dom-report', + ) + + const styleInfo = getStyleInfoFromInlineStyle(editor) + + expect(styleInfo).not.toBeNull() + const { flexDirection, gap } = styleInfo! + expect(flexDirection).toEqual(cssStylePropertyNotFound()) + expect(gap).toMatchObject({ + type: 'not-parsable', + originalValue: { + type: 'JS_PROPERTY_ACCESS', + onValue: { type: 'JS_IDENTIFIER', name: 'gap' }, + property: 'small', + }, + }) + }) +}) + +function getStyleInfoFromInlineStyle(editor: EditorRenderResult) { + const { jsxMetadata, projectContents, elementPathTree } = editor.getEditorState().editor + + const styleInfoReader = InlineStylePlugin.styleInfoFactory({ + projectContents: projectContents, + jsxMetadata: jsxMetadata, + }) + const styleInfo = styleInfoReader(EP.fromString('sb/scene/div')) + return styleInfo +} diff --git a/editor/src/components/canvas/plugins/inline-style-plugin.ts b/editor/src/components/canvas/plugins/inline-style-plugin.ts new file mode 100644 index 000000000000..d8c71ec114dc --- /dev/null +++ b/editor/src/components/canvas/plugins/inline-style-plugin.ts @@ -0,0 +1,147 @@ +import type { JSXAttributes, PropertyPath } from 'utopia-shared/src/types' +import * as Either from '../../../core/shared/either' +import { + getJSXAttributesAtPath, + jsxSimpleAttributeToValue, +} from '../../../core/shared/jsx-attribute-utils' +import type { ModifiableAttribute } from '../../../core/shared/jsx-attributes' +import { getJSXElementFromProjectContents } from '../../editor/store/editor-state' +import { cssParsers, type ParsedCSSProperties } from '../../inspector/common/css-utils' +import { stylePropPathMappingFn } from '../../inspector/common/property-path-hooks' +import type { CSSStyleProperty, StyleInfo } from '../canvas-types' +import { + cssStyleProperty, + cssStylePropertyNotParsable, + cssStylePropertyNotFound, +} from '../canvas-types' +import { mapDropNulls } from '../../../core/shared/array-utils' +import { emptyComments, jsExpressionValue } from '../../../core/shared/element-template' +import * as PP from '../../../core/shared/property-path' +import { applyValuesAtPath, deleteValuesAtPath } from '../commands/utils/property-utils' +import type { StylePlugin } from './style-plugins' + +function getPropValue(attributes: JSXAttributes, path: PropertyPath): ModifiableAttribute { + const result = getJSXAttributesAtPath(attributes, path) + if (result.remainingPath != null) { + return { type: 'ATTRIBUTE_NOT_FOUND' } + } + return result.attribute +} + +function getPropertyFromInstance

( + prop: P, + attributes: JSXAttributes, +): CSSStyleProperty> | null { + const attribute = getPropValue(attributes, stylePropPathMappingFn(prop, ['style'])) + if (attribute.type === 'ATTRIBUTE_NOT_FOUND') { + return cssStylePropertyNotFound() + } + const simpleValue = jsxSimpleAttributeToValue(attribute) + if (Either.isLeft(simpleValue)) { + return cssStylePropertyNotParsable(attribute) + } + const parser = cssParsers[prop] as (value: unknown) => Either.Either + const parsed = parser(simpleValue.value) + if (Either.isLeft(parsed) || parsed.value == null) { + return cssStylePropertyNotParsable(attribute) + } + return cssStyleProperty(parsed.value, attribute) +} + +export const InlineStylePlugin: StylePlugin = { + name: 'Inline Style', + readStyleFromElementProps: ( + attributes: JSXAttributes, + prop: T, + ): CSSStyleProperty> | null => { + return getPropertyFromInstance(prop, attributes) + }, + styleInfoFactory: + ({ projectContents }) => + (elementPath) => { + const element = getJSXElementFromProjectContents(elementPath, projectContents) + if (element == null) { + return null + } + + const gap = getPropertyFromInstance('gap', element.props) + const flexDirection = getPropertyFromInstance('flexDirection', element.props) + const left = getPropertyFromInstance('left', element.props) + const right = getPropertyFromInstance('right', element.props) + const top = getPropertyFromInstance('top', element.props) + const bottom = getPropertyFromInstance('bottom', element.props) + const width = getPropertyFromInstance('width', element.props) + const height = getPropertyFromInstance('height', element.props) + const flexBasis = getPropertyFromInstance('flexBasis', element.props) + const padding = getPropertyFromInstance('padding', element.props) + const paddingTop = getPropertyFromInstance('paddingTop', element.props) + const paddingBottom = getPropertyFromInstance('paddingBottom', element.props) + const paddingLeft = getPropertyFromInstance('paddingLeft', element.props) + const paddingRight = getPropertyFromInstance('paddingRight', element.props) + const borderRadius = getPropertyFromInstance('borderRadius', element.props) + const borderTopLeftRadius = getPropertyFromInstance('borderTopLeftRadius', element.props) + const borderTopRightRadius = getPropertyFromInstance('borderTopRightRadius', element.props) + const borderBottomRightRadius = getPropertyFromInstance( + 'borderBottomRightRadius', + element.props, + ) + const borderBottomLeftRadius = getPropertyFromInstance( + 'borderBottomLeftRadius', + element.props, + ) + const zIndex = getPropertyFromInstance('zIndex', element.props) + const flexWrap = getPropertyFromInstance('flexWrap', element.props) + + const overflow = getPropertyFromInstance('overflow', element.props) + + return { + gap: gap, + flexDirection: flexDirection, + left: left, + right: right, + top: top, + bottom: bottom, + width: width, + height: height, + flexBasis: flexBasis, + padding: padding, + paddingTop: paddingTop, + paddingBottom: paddingBottom, + paddingLeft: paddingLeft, + paddingRight: paddingRight, + borderRadius: borderRadius, + borderTopLeftRadius: borderTopLeftRadius, + borderTopRightRadius: borderTopRightRadius, + borderBottomRightRadius: borderBottomRightRadius, + borderBottomLeftRadius: borderBottomLeftRadius, + zIndex: zIndex, + flexWrap: flexWrap, + overflow: overflow, + } + }, + updateStyles: (editorState, elementPath, updates) => { + const propsToDelete = mapDropNulls( + (update) => (update.type === 'delete' ? PP.create('style', update.property) : null), + updates, + ) + + const propsToSet = mapDropNulls( + (update) => + update.type === 'set' + ? { + path: PP.create('style', update.property), + value: jsExpressionValue(update.value, emptyComments), + } + : null, + updates, + ) + + const { editorStateWithChanges: withValuesDeleted } = deleteValuesAtPath( + editorState, + elementPath, + propsToDelete, + ) + + return applyValuesAtPath(withValuesDeleted, elementPath, propsToSet) + }, +} diff --git a/editor/src/components/canvas/plugins/style-plugins.ts b/editor/src/components/canvas/plugins/style-plugins.ts new file mode 100644 index 000000000000..79ef09308456 --- /dev/null +++ b/editor/src/components/canvas/plugins/style-plugins.ts @@ -0,0 +1,306 @@ +import type { ElementPath, JSXAttributes } from 'utopia-shared/src/types' +import type { EditorState, EditorStatePatch } from '../../editor/store/editor-state' +import type { + InteractionLifecycle, + StyleInfoFactory, +} from '../canvas-strategies/canvas-strategy-types' +import { InlineStylePlugin } from './inline-style-plugin' +import { TailwindPlugin } from './tailwind-style-plugin' +import { + getTailwindConfigCached, + isTailwindEnabled, +} from '../../../core/tailwind/tailwind-compilation' +import { isFeatureEnabled } from '../../../utils/feature-switches' +import { assertNever } from '../../../core/shared/utils' +import * as EP from '../../../core/shared/element-path' +import type { ValueAtPath } from '../../../core/shared/jsx-attributes' +import type { EditorStateWithPatch } from '../commands/utils/property-utils' +import { applyValuesAtPath } from '../commands/utils/property-utils' +import * as PP from '../../../core/shared/property-path' +import { emptyComments, jsExpressionValue } from '../../../core/shared/element-template' +import type { CSSStyleProperty } from '../canvas-types' +import { isStyleInfoKey, type StyleInfo } from '../canvas-types' +import type { StyleInfoSubEditorState } from '../../editor/store/store-hook-substore-types' +import type { ParsedCSSProperties } from '../../inspector/common/css-utils' + +export interface UpdateCSSProp { + type: 'set' + property: string + value: string | number +} + +interface DeleteCSSProp { + type: 'delete' + property: string +} + +export function updateCSSProp(property: string, value: string | number): UpdateCSSProp { + return { + type: 'set', + property: property, + value: value, + } +} + +export function deleteCSSProp(property: string): DeleteCSSProp { + return { + type: 'delete', + property: property, + } +} + +export type StyleUpdate = UpdateCSSProp | DeleteCSSProp + +export type SceneSize = { type: 'no-scene' } | { type: 'scene'; width: number } +export function noSceneSize(): SceneSize { + return { type: 'no-scene' } +} +export function sceneSize(width: number | undefined | null): SceneSize { + if (width == null) { + return noSceneSize() + } + return { type: 'scene', width: width } +} +export type StylePluginContext = { + sceneSize: SceneSize +} + +export interface StylePlugin { + name: string + styleInfoFactory: StyleInfoFactory + readStyleFromElementProps: ( + attributes: JSXAttributes, + prop: T, + context: StylePluginContext, + ) => CSSStyleProperty> | null + updateStyles: ( + editorState: EditorState, + elementPath: ElementPath, + updates: StyleUpdate[], + ) => EditorStateWithPatch +} + +export function getActivePlugin(editorState: StyleInfoSubEditorState): StylePlugin { + if (isFeatureEnabled('Tailwind') && isTailwindEnabled()) { + return TailwindPlugin(getTailwindConfigCached(editorState)) + } + return InlineStylePlugin +} + +export interface StylePropsUpdatedDuringInteraction { + propertiesUpdated: string[] + propertiesDeleted: string[] +} + +export interface UpdatedProperties { + [elementPathString: string]: StylePropsUpdatedDuringInteraction +} + +export function resetUpdatedProperties(editorState: EditorState): EditorState { + return { ...editorState, propertiesUpdatedDuringInteraction: {} } +} + +function getPropertiesUpdatedDuringInteraction(editorState: EditorState) { + return editorState.propertiesUpdatedDuringInteraction +} + +function ensureElementPathInUpdatedPropertiesGlobal( + elementPath: ElementPath, + updatedProperties: UpdatedProperties, +) { + const updatedPropertiesToExtend = { ...updatedProperties } + const elementPathString = EP.toString(elementPath) + if (!(elementPathString in updatedPropertiesToExtend)) { + updatedPropertiesToExtend[elementPathString] = { + propertiesDeleted: [], + propertiesUpdated: [], + } + } + + return updatedPropertiesToExtend +} + +interface EditorStateWithPatches { + editorStateWithChanges: EditorState + editorStatePatches: EditorStatePatch[] +} + +function runStyleUpdateMidInteraction( + editorState: EditorState, + elementPath: ElementPath, + updates: StyleUpdate[], +): EditorStateWithPatches { + const updatedProperties = ensureElementPathInUpdatedPropertiesGlobal( + elementPath, + getPropertiesUpdatedDuringInteraction(editorState), + ) + for (const update of updates) { + switch (update.type) { + case 'delete': + updatedProperties[EP.toString(elementPath)].propertiesDeleted.push(update.property) + break + case 'set': + updatedProperties[EP.toString(elementPath)].propertiesUpdated.push(update.property) + break + default: + assertNever(update) + } + } + const { editorStatePatch, editorStateWithChanges } = InlineStylePlugin.updateStyles( + editorState, + elementPath, + updates, + ) + return { + editorStateWithChanges: editorStateWithChanges, + editorStatePatches: [ + editorStatePatch, + { propertiesUpdatedDuringInteraction: { $set: updatedProperties } }, + ], + } +} + +const makeZeroProp = (cssProp: string, zeroValue: string = '0px'): ValueAtPath => { + return { + path: PP.create('style', cssProp), + value: jsExpressionValue(zeroValue, emptyComments), + } +} + +interface PropPatcher { + matches: (prop: string) => boolean + patch: ( + prop: keyof StyleInfo, + styleInfo: StyleInfo | null, + updatedProperties: StylePropsUpdatedDuringInteraction, + ) => ValueAtPath[] +} + +const genericPropPatcher = + (zeroValue: string) => + ( + prop: keyof StyleInfo, + styleInfo: StyleInfo | null, + updatedProperties: StylePropsUpdatedDuringInteraction, + ) => { + const propIsSetOnElement = styleInfo?.[prop] != null + const propIsSetFromCommand = updatedProperties.propertiesUpdated.includes(prop) + if (!propIsSetOnElement || propIsSetFromCommand) { + return [] + } + return [makeZeroProp(prop, zeroValue)] + } + +const PaddingLonghands = ['paddingTop', 'paddingBottom', 'paddingLeft', 'paddingRight'] + +const BorderRadiusLonghands = [ + 'borderTopLeftRadius', + 'borderTopRightRadius', + 'borderBottomLeftRadius', + 'borderBottomRightRadius', +] + +const patchers: PropPatcher[] = [ + { matches: (p) => p === 'gap', patch: genericPropPatcher('0px') }, + { + matches: (p) => p === 'padding', + patch: (_, styleInfo, updatedProperties) => { + const propIsSetOnElement = styleInfo?.padding != null + + if (!propIsSetOnElement) { + return [] + } + + return PaddingLonghands.filter((p) => !updatedProperties.propertiesUpdated.includes(p)).map( + (p) => makeZeroProp(p), + ) + }, + }, + { + matches: (p) => p === 'borderRadius', + patch: (_, styleInfo, updatedProperties) => { + const propIsSetOnElement = styleInfo?.padding != null + + if (!propIsSetOnElement) { + return [] + } + + return BorderRadiusLonghands.filter( + (p) => !updatedProperties.propertiesUpdated.includes(p), + ).map((p) => makeZeroProp(p)) + }, + }, + { matches: (p) => PaddingLonghands.includes(p), patch: genericPropPatcher('0px') }, + { + matches: (p) => BorderRadiusLonghands.includes(p), + patch: genericPropPatcher('0px'), + }, +] + +function getPropertiesToZero( + styleInfo: StyleInfo | null, + updatedProperties: StylePropsUpdatedDuringInteraction, +): ValueAtPath[] { + return updatedProperties.propertiesDeleted.flatMap((prop): ValueAtPath[] => { + if (!isStyleInfoKey(prop)) { + console.error("Trying to zero prop that's not a handled by StyleInfo:", prop) + return [] + } + + const match = patchers.find((p) => p.matches(prop)) + if (match == null) { + return [] + } + return match.patch(prop, styleInfo, updatedProperties) + }) +} + +export function patchRemovedProperties(editorState: EditorState): EditorState { + const activePlugin = getActivePlugin(editorState) + if (activePlugin.name === InlineStylePlugin.name) { + return editorState + } + + const styleInfoReader = activePlugin.styleInfoFactory({ + projectContents: editorState.projectContents, + jsxMetadata: editorState.jsxMetadata, + }) + + const propertiesUpdatedDuringInteraction = getPropertiesUpdatedDuringInteraction(editorState) + + return Object.entries(propertiesUpdatedDuringInteraction).reduce( + (acc, [elementPathString, updatedProperties]) => { + const elementPath = EP.fromString(elementPathString) + const styleInfo = styleInfoReader(elementPath) + const propsToZero = getPropertiesToZero(styleInfo, updatedProperties) + if (propsToZero.length === 0) { + return acc + } else { + return applyValuesAtPath(acc, elementPath, propsToZero).editorStateWithChanges + } + }, + editorState, + ) +} + +export function runStyleUpdateForStrategy( + commandLifecycle: InteractionLifecycle, + editorState: EditorState, + elementPath: ElementPath, + updates: StyleUpdate[], +): EditorStateWithPatches { + switch (commandLifecycle) { + case 'mid-interaction': + return runStyleUpdateMidInteraction(editorState, elementPath, updates) + case 'end-interaction': + const { editorStatePatch, editorStateWithChanges } = getActivePlugin( + editorState, + ).updateStyles(editorState, elementPath, updates) + return { + editorStateWithChanges: editorStateWithChanges, + editorStatePatches: [editorStatePatch], + } + default: + assertNever(commandLifecycle) + } +} diff --git a/editor/src/components/canvas/plugins/tailwind-style-plugin-utils/update-class-list.ts b/editor/src/components/canvas/plugins/tailwind-style-plugin-utils/update-class-list.ts new file mode 100644 index 000000000000..b8896c93aa64 --- /dev/null +++ b/editor/src/components/canvas/plugins/tailwind-style-plugin-utils/update-class-list.ts @@ -0,0 +1,72 @@ +import type { ElementPath } from 'utopia-shared/src/types' +import { emptyComments } from 'utopia-shared/src/types' +import { mapDropNulls } from '../../../../core/shared/array-utils' +import { jsExpressionValue } from '../../../../core/shared/element-template' +import type { PropertiesToUpdate } from '../../../../core/tailwind/tailwind-class-list-utils' +import { + getParsedClassList, + removeClasses, + updateExistingClasses, + addNewClasses, + getClassListFromParsedClassList, +} from '../../../../core/tailwind/tailwind-class-list-utils' +import { getClassNameAttribute } from '../../../../core/tailwind/tailwind-options' +import type { EditorState } from '../../../editor/store/editor-state' +import { getElementFromProjectContents } from '../../../editor/store/editor-state' +import type { EditorStateWithPatch } from '../../commands/utils/property-utils' +import { applyValuesAtPath } from '../../commands/utils/property-utils' +import * as PP from '../../../../core/shared/property-path' +import type { Config } from 'tailwindcss/types/config' + +export type ClassListUpdate = + | { type: 'add'; property: string; value: string } + | { type: 'remove'; property: string } + +export const add = ({ property, value }: { property: string; value: string }): ClassListUpdate => ({ + type: 'add', + property: property, + value: value, +}) +export const remove = (property: string): ClassListUpdate => ({ + type: 'remove', + property: property, +}) + +export const runUpdateClassList = ( + editorState: EditorState, + element: ElementPath, + classNameUpdates: ClassListUpdate[], + config: Config | null, +): EditorStateWithPatch => { + const currentClassNameAttribute = + getClassNameAttribute(getElementFromProjectContents(element, editorState.projectContents)) + ?.value ?? '' + + const parsedClassList = getParsedClassList(currentClassNameAttribute, config) + + const propertiesToRemove = mapDropNulls( + (update) => (update.type !== 'remove' ? null : update.property), + classNameUpdates, + ) + + const propertiesToUpdate: PropertiesToUpdate = classNameUpdates.reduce( + (acc: { [property: string]: string }, val) => + val.type === 'remove' ? acc : { ...acc, [val.property]: val.value }, + {}, + ) + + const updatedClassList = [ + removeClasses(propertiesToRemove), + updateExistingClasses(propertiesToUpdate), + addNewClasses(propertiesToUpdate), + ].reduce((classList, fn) => fn(classList), parsedClassList) + + const newClassList = getClassListFromParsedClassList(updatedClassList, config) + + return applyValuesAtPath(editorState, element, [ + { + path: PP.create('className'), + value: jsExpressionValue(newClassList, emptyComments), + }, + ]) +} diff --git a/editor/src/components/canvas/plugins/tailwind-style-plugin.spec.ts b/editor/src/components/canvas/plugins/tailwind-style-plugin.spec.ts new file mode 100644 index 000000000000..cf1e604a3833 --- /dev/null +++ b/editor/src/components/canvas/plugins/tailwind-style-plugin.spec.ts @@ -0,0 +1,100 @@ +import type { JSXAttributes } from 'utopia-shared/src/types' +import * as EP from '../../../core/shared/element-path' +import { + getJSXElementFromProjectContents, + StoryboardFilePath, +} from '../../editor/store/editor-state' +import { renderTestEditorWithModel } from '../ui-jsx.test-utils' +import { TailwindPlugin } from './tailwind-style-plugin' +import { createModifiedProject } from '../../../sample-projects/sample-project-utils.test-utils' +import { TailwindConfigPath } from '../../../core/tailwind/tailwind-config' +import { getTailwindConfigCached } from '../../../core/tailwind/tailwind-compilation' + +const Project = createModifiedProject({ + [StoryboardFilePath]: ` +import React from 'react' +import { Scene, Storyboard } from 'utopia-api' +export var storyboard = ( + + +

+ + +) + +`, + [TailwindConfigPath]: ` + const TailwindConfig = { + content: [], + theme: { extend: { gap: { enormous: '222px' } } } + } + export default TailwindConfig +`, +}) + +describe('tailwind style plugin', () => { + it('can set Tailwind class', async () => { + const editor = await renderTestEditorWithModel(Project, 'await-first-dom-report') + const target = EP.fromString('sb/scene/div') + const updatedEditor = TailwindPlugin(null).updateStyles( + editor.getEditorState().editor, + target, + [ + { type: 'set', property: 'gap', value: '222px' }, + { type: 'set', property: 'display', value: 'flex' }, + { type: 'set', property: 'flexDirection', value: 'column' }, + ], + ) + const normalizedElement = getJSXElementFromProjectContents( + target, + updatedEditor.editorStateWithChanges.projectContents, + )! + expect(formatJSXAttributes(normalizedElement.props)).toEqual({ + className: 'flex flex-col gap-[222px]', + 'data-uid': 'div', + }) + }) + + it('can set Tailwind class, with custom values in the Tailwind config', async () => { + const editor = await renderTestEditorWithModel(Project, 'await-first-dom-report') + const target = EP.fromString('sb/scene/div') + const updatedEditor = TailwindPlugin( + getTailwindConfigCached(editor.getEditorState().editor), + ).updateStyles(editor.getEditorState().editor, target, [ + { type: 'set', property: 'gap', value: '222px' }, + { type: 'set', property: 'display', value: 'flex' }, + { type: 'set', property: 'flexDirection', value: 'column' }, + ]) + const normalizedElement = getJSXElementFromProjectContents( + target, + updatedEditor.editorStateWithChanges.projectContents, + )! + expect(formatJSXAttributes(normalizedElement.props)).toEqual({ + className: 'flex flex-col gap-enormous', + 'data-uid': 'div', + }) + }) +}) + +function formatJSXAttributes(attributes: JSXAttributes) { + return attributes.reduce((acc: Record, attribute) => { + if (attribute.type === 'JSX_ATTRIBUTES_SPREAD' || attribute.value.type !== 'ATTRIBUTE_VALUE') { + return acc + } + return { ...acc, [attribute.key]: attribute.value.value } + }, {}) +} diff --git a/editor/src/components/canvas/plugins/tailwind-style-plugin.ts b/editor/src/components/canvas/plugins/tailwind-style-plugin.ts new file mode 100644 index 000000000000..e9fadf9ef689 --- /dev/null +++ b/editor/src/components/canvas/plugins/tailwind-style-plugin.ts @@ -0,0 +1,224 @@ +import * as TailwindClassParser from '@xengine/tailwindcss-class-parser' +import { defaultEither, flatMapEither, isLeft } from '../../../core/shared/either' +import { getClassNameAttribute } from '../../../core/tailwind/tailwind-options' +import { getElementFromProjectContents } from '../../editor/store/editor-state' +import type { ParsedCSSProperties } from '../../inspector/common/css-utils' +import { cssParsers } from '../../inspector/common/css-utils' +import { mapDropNulls } from '../../../core/shared/array-utils' +import type { StylePlugin, StylePluginContext } from './style-plugins' +import type { Config } from 'tailwindcss/types/config' +import type { StyleInfo } from '../canvas-types' +import { cssStyleProperty, type CSSStyleProperty } from '../canvas-types' +import * as UCL from './tailwind-style-plugin-utils/update-class-list' +import { assertNever } from '../../../core/shared/utils' +import { + jsxSimpleAttributeToValue, + getModifiableJSXAttributeAtPath, +} from '../../../core/shared/jsx-attribute-utils' +import { emptyComments, type JSXAttributes } from 'utopia-shared/src/types' +import * as PP from '../../../core/shared/property-path' +import { jsExpressionValue } from '../../../core/shared/element-template' +import { getContainingSceneSize } from '../responsive-utils' + +const parseTailwindPropertyFactory = + (config: Config | null, context: StylePluginContext) => + ( + value: string | number | undefined, + prop: T, + ): CSSStyleProperty> | null => { + const parsed = cssParsers[prop](value, null) + if (isLeft(parsed) || parsed.value == null) { + return null + } + return cssStyleProperty(parsed.value, jsExpressionValue(value, emptyComments)) + } + +const TailwindPropertyMapping: Record = { + left: 'positionLeft', + right: 'positionRight', + top: 'positionTop', + bottom: 'positionBottom', + + width: 'width', + height: 'height', + + padding: 'padding', + paddingTop: 'paddingTop', + paddingRight: 'paddingRight', + paddingBottom: 'paddingBottom', + paddingLeft: 'paddingLeft', + + borderRadius: 'borderRadius', + borderTopLeftRadius: 'borderTopLeftRadius', + borderTopRightRadius: 'borderTopRightRadius', + borderBottomRightRadius: 'borderBottomRightRadius', + borderBottomLeftRadius: 'borderBottomLeftRadius', + + justifyContent: 'justifyContent', + alignItems: 'alignItems', + flex: 'flex', + flexDirection: 'flexDirection', + flexGrow: 'flexGrow', + flexShrink: 'flexShrink', + flexBasis: 'flexBasis', + flexWrap: 'flexWrap', + gap: 'gap', + + overflow: 'overflow', + + zIndex: 'zIndex', +} + +function isSupportedTailwindProperty(prop: unknown): prop is keyof typeof TailwindPropertyMapping { + return typeof prop === 'string' && prop in TailwindPropertyMapping +} + +function stringifyPropertyValue(value: string | number): string { + switch (typeof value) { + case 'number': + return `${value}px` + case 'string': + return value + default: + assertNever(value) + } +} + +function getTailwindClassMapping(classes: string[], config: Config | null): Record { + const mapping: Record = {} + classes.forEach((className) => { + const parsed = TailwindClassParser.parse(className, config ?? undefined) + if (parsed.kind === 'error' || !isSupportedTailwindProperty(parsed.property)) { + return + } + mapping[parsed.property] = parsed.value + }) + return mapping +} + +const underscoresToSpaces = (s: string | undefined) => s?.replace(/[-_]/g, ' ') + +export const TailwindPlugin = (config: Config | null): StylePlugin => ({ + name: 'Tailwind', + readStyleFromElementProps:

( + attributes: JSXAttributes, + prop: P, + context: StylePluginContext, + ): CSSStyleProperty> | null => { + const classNameAttribute = defaultEither( + null, + flatMapEither( + (attr) => jsxSimpleAttributeToValue(attr), + getModifiableJSXAttributeAtPath(attributes, PP.create('className')), + ), + ) + + if (typeof classNameAttribute !== 'string') { + return null + } + + const mapping = getTailwindClassMapping(classNameAttribute.split(' '), config) + const parseTailwindProperty = parseTailwindPropertyFactory(config, context) + return parseTailwindProperty(mapping[TailwindPropertyMapping[prop]], prop) + }, + styleInfoFactory: + ({ projectContents, jsxMetadata }) => + (elementPath) => { + const classList = getClassNameAttribute( + getElementFromProjectContents(elementPath, projectContents), + )?.value + + if (classList == null || typeof classList !== 'string') { + return null + } + + const mapping = getTailwindClassMapping(classList.split(' '), config) + const parseTailwindProperty = parseTailwindPropertyFactory(config, { + sceneSize: getContainingSceneSize(elementPath, jsxMetadata), + }) + + return { + gap: parseTailwindProperty(mapping[TailwindPropertyMapping.gap], 'gap'), + flexDirection: parseTailwindProperty( + mapping[TailwindPropertyMapping.flexDirection], + 'flexDirection', + ), + left: parseTailwindProperty(mapping[TailwindPropertyMapping.left], 'left'), + right: parseTailwindProperty(mapping[TailwindPropertyMapping.right], 'right'), + top: parseTailwindProperty(mapping[TailwindPropertyMapping.top], 'top'), + bottom: parseTailwindProperty(mapping[TailwindPropertyMapping.bottom], 'bottom'), + width: parseTailwindProperty(mapping[TailwindPropertyMapping.width], 'width'), + height: parseTailwindProperty(mapping[TailwindPropertyMapping.height], 'height'), + flexBasis: parseTailwindProperty(mapping[TailwindPropertyMapping.flexBasis], 'flexBasis'), + padding: parseTailwindProperty( + underscoresToSpaces(mapping[TailwindPropertyMapping.padding]), + 'padding', + ), + paddingTop: parseTailwindProperty( + mapping[TailwindPropertyMapping.paddingTop], + 'paddingTop', + ), + paddingRight: parseTailwindProperty( + mapping[TailwindPropertyMapping.paddingRight], + 'paddingRight', + ), + paddingBottom: parseTailwindProperty( + mapping[TailwindPropertyMapping.paddingBottom], + 'paddingBottom', + ), + paddingLeft: parseTailwindProperty( + mapping[TailwindPropertyMapping.paddingLeft], + 'paddingLeft', + ), + borderRadius: parseTailwindProperty( + mapping[TailwindPropertyMapping.borderRadius], + 'borderRadius', + ), + borderTopLeftRadius: parseTailwindProperty( + mapping[TailwindPropertyMapping.borderTopLeftRadius], + 'borderTopLeftRadius', + ), + borderTopRightRadius: parseTailwindProperty( + mapping[TailwindPropertyMapping.borderTopRightRadius], + 'borderTopRightRadius', + ), + borderBottomRightRadius: parseTailwindProperty( + mapping[TailwindPropertyMapping.borderBottomRightRadius], + 'borderBottomRightRadius', + ), + borderBottomLeftRadius: parseTailwindProperty( + mapping[TailwindPropertyMapping.borderBottomLeftRadius], + 'borderBottomLeftRadius', + ), + zIndex: parseTailwindProperty(mapping[TailwindPropertyMapping.zIndex], 'zIndex'), + flexWrap: parseTailwindProperty(mapping[TailwindPropertyMapping.flexWrap], 'flexWrap'), + overflow: parseTailwindProperty(mapping[TailwindPropertyMapping.overflow], 'overflow'), + } + }, + updateStyles: (editorState, elementPath, updates) => { + const propsToDelete = mapDropNulls( + (update) => + update.type !== 'delete' || TailwindPropertyMapping[update.property] == null // TODO: make this type-safe + ? null + : UCL.remove(TailwindPropertyMapping[update.property]), + updates, + ) + + const propsToSet = mapDropNulls( + (update) => + update.type !== 'set' || TailwindPropertyMapping[update.property] == null // TODO: make this type-safe + ? null + : UCL.add({ + property: TailwindPropertyMapping[update.property], + value: stringifyPropertyValue(update.value), + }), + updates, + ) + return UCL.runUpdateClassList( + editorState, + elementPath, + [...propsToDelete, ...propsToSet], + config, + ) + }, +}) diff --git a/editor/src/components/canvas/remix/remix-error-handling.test-utils.tsx b/editor/src/components/canvas/remix/remix-error-handling.test-utils.tsx index 492b757571c9..e1d229403149 100644 --- a/editor/src/components/canvas/remix/remix-error-handling.test-utils.tsx +++ b/editor/src/components/canvas/remix/remix-error-handling.test-utils.tsx @@ -25,7 +25,7 @@ export async function renderRemixProject(project: PersistentModel) { notLoggedIn, false, ) - await renderResult.dispatch([runDOMWalker()], true) + await renderResult.dispatch([runDOMWalker(null)], true) return renderResult } diff --git a/editor/src/components/canvas/remix/remix-navigator.spec.browser2.tsx b/editor/src/components/canvas/remix/remix-navigator.spec.browser2.tsx index 89082a268898..37567000c4bf 100644 --- a/editor/src/components/canvas/remix/remix-navigator.spec.browser2.tsx +++ b/editor/src/components/canvas/remix/remix-navigator.spec.browser2.tsx @@ -23,7 +23,7 @@ const RootTextContent = 'This is root!' async function renderRemixProject(project: PersistentModel) { const renderResult = await renderTestEditorWithModel(project, 'await-first-dom-report') - await renderResult.dispatch([runDOMWalker()], true) + await renderResult.dispatch([runDOMWalker(null)], true) return renderResult } diff --git a/editor/src/components/canvas/remix/remix-rendering.spec.browser2.tsx b/editor/src/components/canvas/remix/remix-rendering.spec.browser2.tsx index 197fe32db18f..a514372eb581 100644 --- a/editor/src/components/canvas/remix/remix-rendering.spec.browser2.tsx +++ b/editor/src/components/canvas/remix/remix-rendering.spec.browser2.tsx @@ -31,6 +31,7 @@ import { MockClipboardHandlers, firePasteEvent, mouseClickAtPoint, + mouseDoubleClickAtPoint, mouseDownAtPoint, mouseDragFromPointWithDelta, pressKey, @@ -81,7 +82,7 @@ async function renderRemixProject( undefined, strategiesToUse, ) - await renderResult.dispatch([runDOMWalker()], true) + await renderResult.dispatch([runDOMWalker(null)], true) return renderResult } @@ -812,6 +813,109 @@ describe('Remix content', () => { expectRemixSceneToBeRendered(renderResult), ) }) + + it('Can focus component in remix', async () => { + const project = createModifiedProject({ + [StoryboardFilePath]: `import * as React from 'react' + import { RemixScene, Storyboard } from 'utopia-api' + + export var storyboard = ( + + + + ) + `, + ['/app/root.js']: `import React from 'react' + import { Outlet } from '@remix-run/react' + + export default function Root() { + return ( +

+ ${RootTextContent} + +
+ ) + } + `, + '/app/routes/_index.js': `import React from 'react' + import { Hero } from './index/Hero.jsx' + import { Column } from './index/column.jsx' + + export default function Index() { + return ( + + + + + ) + } + `, + '/app/routes/index/Hero.jsx': `import React from 'react' + + export function Hero() { + return ( +
+

This is the hero section

+
+ ) + } + `, + '/app/routes/index/column.jsx': `import React from 'react' + export function Column({ children }) { + return
{children}
+ } + `, + }) + const renderResult = await renderRemixProject(project) + + expect( + getNavigatorTargetsFromEditorState(renderResult.getEditorState().editor).navigatorTargets.map( + navigatorEntryToKey, + ), + ).toEqual([ + 'regular-storyboard/remix-scene', + 'regular-storyboard/remix-scene:rootdiv', + 'regular-storyboard/remix-scene:rootdiv/outlet', + 'regular-storyboard/remix-scene:rootdiv/outlet:index-column', + 'regular-storyboard/remix-scene:rootdiv/outlet:index-column/index-hero', + ]) + + const heroH1 = renderResult.renderedDOM.getByTestId('index-hero-h1') + const heroH1Bounds = heroH1.getBoundingClientRect() + const heroH1Center = windowPoint({ + x: heroH1Bounds.x + heroH1Bounds.width / 2, + y: heroH1Bounds.y + heroH1Bounds.height / 2, + }) + + await mouseDoubleClickAtPoint( + renderResult.renderedDOM.getByTestId(CanvasControlsContainerID), + heroH1Center, + ) + + expect( + getNavigatorTargetsFromEditorState(renderResult.getEditorState().editor).navigatorTargets.map( + navigatorEntryToKey, + ), + ).toEqual([ + 'regular-storyboard/remix-scene', + 'regular-storyboard/remix-scene:rootdiv', + 'regular-storyboard/remix-scene:rootdiv/outlet', + 'regular-storyboard/remix-scene:rootdiv/outlet:index-column', + 'regular-storyboard/remix-scene:rootdiv/outlet:index-column/index-hero', + 'regular-storyboard/remix-scene:rootdiv/outlet:index-column/index-hero:index-container-div', + 'regular-storyboard/remix-scene:rootdiv/outlet:index-column/index-hero:index-container-div/index-hero-h1', + ]) + }) }) describe('Remix getLoadContext', () => { @@ -1765,7 +1869,7 @@ export default function Index() { it('flex reorder elements inside Remix', async () => { const renderResult = await renderRemixProject(remixProjectForEditingTests) - await renderResult.dispatch([runDOMWalker()], true) + await renderResult.dispatch([runDOMWalker(null)], true) expect( getNavigatorTargetsFromEditorState(renderResult.getEditorState().editor).navigatorTargets.map( @@ -1871,7 +1975,7 @@ export default function Index() { it('draw to insert into Remix', async () => { const renderResult = await renderRemixProject(remixProjectForEditingTests) - await renderResult.dispatch([runDOMWalker()], true) + await renderResult.dispatch([runDOMWalker(null)], true) expect( getNavigatorTargetsFromEditorState(renderResult.getEditorState().editor).navigatorTargets.map( diff --git a/editor/src/components/canvas/responsive-types.ts b/editor/src/components/canvas/responsive-types.ts new file mode 100644 index 000000000000..c1e025a6f8fc --- /dev/null +++ b/editor/src/components/canvas/responsive-types.ts @@ -0,0 +1,39 @@ +import type { Identifier, Dimension } from 'css-tree' +import type { CSSNumber } from '../inspector/common/css-utils' +// @media (min-width: 100px) and (max-width: 200em) => { min: { value: 100, unit: 'px' }, max: { value: 200, unit: 'em' } } +export type ScreenSize = { + min?: CSSNumber + max?: CSSNumber +} + +export interface MediaQuery { + type: 'MediaQuery' + loc: null + modifier: null + mediaType: null + condition?: { + type: 'Condition' + loc: null + kind: 'media' + children: Array + } +} + +export interface FeatureRange { + type: 'FeatureRange' + loc: null + kind: 'media' + left?: Dimension | Identifier + leftComparison: '<' | '>' + middle: Dimension | Identifier + rightComparison: '<' | '>' + right?: Dimension | Identifier +} + +export interface Feature { + type: 'Feature' + loc: null + kind: 'media' + name: 'min-width' | 'max-width' + value?: Dimension +} diff --git a/editor/src/components/canvas/responsive-utils.spec.ts b/editor/src/components/canvas/responsive-utils.spec.ts new file mode 100644 index 000000000000..dcbcc0c434c1 --- /dev/null +++ b/editor/src/components/canvas/responsive-utils.spec.ts @@ -0,0 +1,176 @@ +import * as csstree from 'css-tree' +import { mediaQueryToScreenSize, selectValueByBreakpoint } from './responsive-utils' +import type { ScreenSize, MediaQuery } from './responsive-types' +import { extractScreenSizeFromCss } from './responsive-utils' +import type { StyleModifier } from './canvas-types' + +describe('extractScreenSizeFromCss', () => { + it('extracts screen size from simple media query', () => { + const css = '@media (min-width: 100px) and (max-width: 500px)' + const result = extractScreenSizeFromCss(css) + expect(result).toEqual({ + min: { value: 100, unit: 'px' }, + max: { value: 500, unit: 'px' }, + }) + }) + + it('returns null for invalid media query', () => { + const css = 'not-a-media-query' + const result = extractScreenSizeFromCss(css) + expect(result).toBeNull() + }) + + it('uses cache for repeated calls with same CSS', () => { + const css = '@media (min-width: 100px)' + + // First call + const result1 = extractScreenSizeFromCss(css) + // Second call - should return same object reference + const result2 = extractScreenSizeFromCss(css) + + expect(result1).toBe(result2) // Use toBe for reference equality + expect(result1).toEqual({ + min: { value: 100, unit: 'px' }, + }) + }) + + it('handles different CSS strings independently in cache', () => { + const css1 = '@media (min-width: 100px)' + const css2 = '@media (max-width: 500px)' + + // First string + const result1a = extractScreenSizeFromCss(css1) + const result1b = extractScreenSizeFromCss(css1) + expect(result1a).toBe(result1b) + expect(result1a).toEqual({ + min: { value: 100, unit: 'px' }, + }) + + // Second string + const result2a = extractScreenSizeFromCss(css2) + const result2b = extractScreenSizeFromCss(css2) + expect(result2a).toBe(result2b) + expect(result2a).toEqual({ + max: { value: 500, unit: 'px' }, + }) + + // Different strings should have different references + expect(result1a).not.toBe(result2a) + }) +}) + +describe('selectValueByBreakpoint', () => { + const variants: { value: string; modifiers?: StyleModifier[] }[] = [ + { + value: 'Desktop Value', + modifiers: [{ type: 'media-size', size: { min: { value: 200, unit: 'px' } } }], + }, + { + value: 'Tablet Value', + modifiers: [{ type: 'media-size', size: { min: { value: 100, unit: 'px' } } }], + }, + { + value: 'Extra Large Value', + modifiers: [{ type: 'media-size', size: { min: { value: 20, unit: 'em' } } }], + }, + { + value: 'Ranged Value', + modifiers: [ + { + type: 'media-size', + size: { min: { value: 80, unit: 'px' }, max: { value: 90, unit: 'px' } }, + }, + ], + }, + { + value: 'Mobile Value', + modifiers: [{ type: 'media-size', size: { min: { value: 60, unit: 'px' } } }], + }, + { value: 'Default Value' }, + ] + const tests: { title: string; screenSize: number; expected: string }[] = [ + { title: 'selects the correct value', screenSize: 150, expected: 'Tablet Value' }, + { title: 'select the closest value', screenSize: 250, expected: 'Desktop Value' }, + { title: 'converts em to px', screenSize: 350, expected: 'Extra Large Value' }, + { + title: 'selects the default value if no breakpoint is matched', + screenSize: 50, + expected: 'Default Value', + }, + { + title: 'selects the ranged value if the screen size is within the range', + screenSize: 85, + expected: 'Ranged Value', + }, + { + title: 'selects the mobile value if the screen size is outside the ranged values', + screenSize: 95, + expected: 'Mobile Value', + }, + ] as const + + tests.forEach((test) => { + it(`${test.title}`, () => { + expect(selectValueByBreakpoint(variants, test.screenSize)?.value).toEqual(test.expected) + }) + }) + + it('selects null if no matching breakpoint and no default value', () => { + const largeVariants: { value: string; modifiers?: StyleModifier[] }[] = [ + { + value: 'Desktop Value', + modifiers: [{ type: 'media-size', size: { min: { value: 200, unit: 'px' } } }], + }, + { + value: 'Tablet Value', + modifiers: [{ type: 'media-size', size: { min: { value: 100, unit: 'px' } } }], + }, + ] + expect(selectValueByBreakpoint(largeVariants, 50)).toBeNull() + }) + it('selects default value if no media modifiers', () => { + const noMediaVariants: { value: string; modifiers?: StyleModifier[] }[] = [ + { + value: 'Hover Value', + modifiers: [{ type: 'hover' }], + }, + { value: 'Default Value' }, + ] + expect(selectValueByBreakpoint(noMediaVariants, 50)?.value).toEqual('Default Value') + }) +}) + +describe('mediaQueryToScreenSize', () => { + it('converts simple screen size queries', () => { + const testCases: { input: string; expected: ScreenSize }[] = [ + { + input: '@media (100px 100px)', + expected: { min: { value: 100, unit: 'px' } }, + }, + ] + testCases.forEach((testCase) => { + csstree.walk(csstree.parse(testCase.input), (node) => { + if (node.type === 'MediaQuery') { + const result = mediaQueryToScreenSize(node as unknown as MediaQuery) + expect(result).toEqual(testCase.expected) + } + }) + }) + }) +}) diff --git a/editor/src/components/canvas/responsive-utils.ts b/editor/src/components/canvas/responsive-utils.ts new file mode 100644 index 000000000000..9d828e6399d5 --- /dev/null +++ b/editor/src/components/canvas/responsive-utils.ts @@ -0,0 +1,239 @@ +import type { Feature, FeatureRange, MediaQuery, ScreenSize } from './responsive-types' +import * as csstree from 'css-tree' +import type { StyleMediaSizeModifier, StyleModifier } from './canvas-types' +import { type CSSNumber, type CSSNumberUnit, cssNumber } from '../inspector/common/css-utils' +import { memoize } from '../../core/shared/memoize' +import type { ElementPath } from '../../core/shared/project-file-types' +import type { ElementInstanceMetadataMap } from '../../core/shared/element-template' +import { MetadataUtils } from '../../core/model/element-metadata-utils' +import { noSceneSize, sceneSize, type SceneSize } from './plugins/style-plugins' +import type { EditorState } from '../editor/store/editor-state' + +export function getContainingSceneSize( + selectedElement: ElementPath, + jsxMetadata: ElementInstanceMetadataMap, +): SceneSize { + if (selectedElement == null) { + return noSceneSize() + } + const containingScene = MetadataUtils.getParentSceneMetadata(jsxMetadata, selectedElement) + return sceneSize(containingScene?.specialSizeMeasurements?.clientWidth) +} + +export function getContainingSceneSizeFromEditorState(editor: EditorState): SceneSize { + if (editor == null) { + return noSceneSize() + } + // we're taking the first selected element because we're assuming elements are in the same scene + // TODO: support multiple selected elements that are in different scenes? + const selectedElement = editor.selectedViews.at(0) + if (selectedElement == null) { + return noSceneSize() + } + return getContainingSceneSize(selectedElement, editor.jsxMetadata) +} + +/** + * Extracts the screen size from a CSS string, for example: + * `@media (min-width: 100px)` -> { min: {value: 100, unit: 'px'} } + * `@media (20px < width < 50em)` -> { min: {value: 20, unit: 'px'}, max: {value: 50, unit: 'em'} } + */ +export const extractScreenSizeFromCss = memoize((css: string): ScreenSize | null => { + const mediaQuery = parseMediaQueryFromCss(css) + return mediaQuery == null ? null : mediaQueryToScreenSize(mediaQuery) +}) + +function extractFromFeatureRange(featureRange: FeatureRange): { + leftValue: CSSNumber | null + rightValue: CSSNumber | null + leftComparison: '<' | '>' | null + rightComparison: '<' | '>' | null +} | null { + // (100px < width < 500px) OR (500px > width > 100px) OR (100px > width) OR (500px < width) + if (featureRange?.middle?.type === 'Identifier' && featureRange.middle.name === 'width') { + const leftValue = + featureRange.left?.type === 'Dimension' + ? cssNumber(Number(featureRange.left.value), featureRange.left.unit as CSSNumberUnit) + : null + + const rightValue = + featureRange.right?.type === 'Dimension' + ? cssNumber(Number(featureRange.right.value), featureRange.right.unit as CSSNumberUnit) + : null + + return { + leftValue: leftValue, + rightValue: rightValue, + leftComparison: featureRange.leftComparison, + rightComparison: featureRange.rightComparison, + } + } + // (width > 100px) OR (width < 500px) + if (featureRange?.left?.type === 'Identifier' && featureRange.left.name === 'width') { + const rightValue = + featureRange.middle?.type === 'Dimension' + ? cssNumber(Number(featureRange.middle.value), featureRange.middle.unit as CSSNumberUnit) + : null + // this is not a mistake, since we normalize the "width" to be in the middle + const rightComparison = featureRange.leftComparison + + return { + leftValue: null, + leftComparison: null, + rightValue: rightValue, + rightComparison: rightComparison, + } + } + return null +} + +export function mediaQueryToScreenSize(mediaQuery: MediaQuery): ScreenSize { + const result: ScreenSize = {} + + if (mediaQuery.condition?.type === 'Condition') { + // 1. Handle FeatureRange case + const featureRanges = mediaQuery.condition.children.filter( + (child): child is FeatureRange => child.type === 'FeatureRange', + ) as Array + + featureRanges.forEach((featureRange) => { + const rangeData = extractFromFeatureRange(featureRange) + if (rangeData == null) { + return + } + const { leftValue, rightValue, leftComparison, rightComparison } = rangeData + if (leftValue != null) { + if (leftComparison === '<') { + result.min = leftValue + } else { + result.max = leftValue + } + } + if (rightValue != null) { + if (rightComparison === '<') { + result.max = rightValue + } else { + result.min = rightValue + } + } + }) + + // 2. Handle Feature case (min-width/max-width) + const features = mediaQuery.condition.children.filter( + (child): child is Feature => child.type === 'Feature', + ) + features.forEach((feature) => { + if (feature.value?.type === 'Dimension') { + if (feature.name === 'min-width') { + result.min = cssNumber(Number(feature.value.value), feature.value.unit as CSSNumberUnit) + } else if (feature.name === 'max-width') { + result.max = cssNumber(Number(feature.value.value), feature.value.unit as CSSNumberUnit) + } + } + }) + } + + return result +} + +function parseMediaQueryFromCss(css: string): MediaQuery | null { + let result: MediaQuery | null = null + csstree.walk(csstree.parse(css), (node) => { + if (node.type === 'MediaQuery') { + result = node as unknown as MediaQuery + } + }) + return result +} + +function getMediaModifier( + modifiers: StyleModifier[] | undefined | null, +): StyleMediaSizeModifier | null { + return (modifiers ?? []).filter( + (modifier): modifier is StyleMediaSizeModifier => modifier.type === 'media-size', + )[0] +} + +export function selectValueByBreakpoint( + parsedVariants: T[], + sceneWidthInPx?: number, +): T | null { + const relevantModifiers = parsedVariants.filter((variant) => { + // 1. filter out variants that don't have media modifiers, but keep variants with no modifiers at all + if (variant.modifiers == null || variant.modifiers.length === 0) { + return true + } + const mediaModifier = getMediaModifier(variant.modifiers) + if (mediaModifier == null) { + // this means it only has other modifiers + return false + } + + if (sceneWidthInPx == null) { + // filter out variants that require a scene width + return false + } + + // 2. check that it has at least one media modifier that satisfies the current scene width + const maxSizeInPx = cssNumberAsPx(mediaModifier.size.max) + const minSizeInPx = cssNumberAsPx(mediaModifier.size.min) + + // if it has only max + if (maxSizeInPx != null && minSizeInPx == null && sceneWidthInPx <= maxSizeInPx) { + return true + } + + // if it has only min + if (maxSizeInPx == null && minSizeInPx != null && sceneWidthInPx >= minSizeInPx) { + return true + } + + // if it has both max and min + if ( + maxSizeInPx != null && + minSizeInPx != null && + sceneWidthInPx >= minSizeInPx && + sceneWidthInPx <= maxSizeInPx + ) { + return true + } + return false + }) + let chosen: T | null = null + for (const variant of relevantModifiers) { + const chosenMediaModifier = getMediaModifier(chosen?.modifiers) + const variantMediaModifier = getMediaModifier(variant.modifiers) + if (variantMediaModifier == null) { + if (chosenMediaModifier == null) { + // if we have nothing chosen then we'll take the base value + chosen = variant + } + continue + } + if (chosenMediaModifier == null) { + chosen = variant + continue + } + // find the closest media modifier + const minSizeInPx = cssNumberAsPx(variantMediaModifier.size.min) + const chosenMinSizeInPx = cssNumberAsPx(chosenMediaModifier.size.min) + if (minSizeInPx != null && (chosenMinSizeInPx == null || minSizeInPx > chosenMinSizeInPx)) { + chosen = variant + } + const maxSizeInPx = cssNumberAsPx(variantMediaModifier.size.max) + const chosenMaxSizeInPx = cssNumberAsPx(chosenMediaModifier.size.max) + if (maxSizeInPx != null && (chosenMaxSizeInPx == null || maxSizeInPx < chosenMaxSizeInPx)) { + chosen = variant + } + } + if (chosen == null) { + return null + } + return chosen +} + +// TODO: get this value from the Scene +const EM_TO_PX_RATIO = 16 +export function cssNumberAsPx(value: CSSNumber | null | undefined): number | null { + return value == null ? null : value.unit === 'em' ? value.value * EM_TO_PX_RATIO : value.value +} diff --git a/editor/src/components/canvas/stored-layout.ts b/editor/src/components/canvas/stored-layout.ts index 595716a5efd2..bb2426034158 100644 --- a/editor/src/components/canvas/stored-layout.ts +++ b/editor/src/components/canvas/stored-layout.ts @@ -24,12 +24,7 @@ export const GridPanelsNumberOfRows = 12 export type Menu = 'inspector' | 'navigator' export type Pane = 'code-editor' -export const allMenusAndPanels: Array = [ - 'navigator', - 'code-editor', - 'inspector', - // 'preview', // Does this exist? -] +export const allMenusAndPanels: Array = ['navigator', 'code-editor', 'inspector'] export interface GridPanelData { panel: StoredPanel diff --git a/editor/src/components/canvas/ui-jsx-canvas-bugs.spec.tsx b/editor/src/components/canvas/ui-jsx-canvas-bugs.spec.tsx index 92e358376fe7..689cf7420104 100644 --- a/editor/src/components/canvas/ui-jsx-canvas-bugs.spec.tsx +++ b/editor/src/components/canvas/ui-jsx-canvas-bugs.spec.tsx @@ -82,6 +82,7 @@ export var storyboard = ( data-path=\\"sb/scene\\" style=\\" overflow: hidden; + container: scene / inline-size; position: absolute; background-color: var(--utopitheme-emphasizedBackground); box-shadow: 0px 1px 2px 0px var(--utopitheme-shadow90), @@ -132,7 +133,7 @@ export var storyboard = ( ) `, { - 'app.js': ` + '/app.js': ` import React from 'react' export default function App(props) { return
@@ -148,7 +149,7 @@ export default function App(props) { id=\\"canvas-container\\" data-testid=\\"canvas-container\\" style=\\"position: absolute\\" - data-utopia-valid-paths=\\"sb sb/scene sb/scene/app\\" + data-utopia-valid-paths=\\"sb sb/scene sb/scene/app sb/scene/app:app-outer-div sb/scene/app:app-outer-div/inner-div\\" data-utopia-root-element-path=\\"sb\\" >
-
-
hello
+
+
+ hello +
+
+
+
+
+ " + `) + }) + + it('Handles importing default exports declared separately', () => { + const result = testCanvasRenderInlineMultifile( + null, + ` +import React from 'react' +import Utopia, { + Scene, + Storyboard, +} from 'utopia-api' +import {default as Appy} from '/app' + +export var storyboard = ( + + + + + +) +`, + { + '/app.js': ` +import React from 'react' +function App(props) { + return
+
hello
+
+} +export default App`, + }, + ) + + expect(result).toMatchInlineSnapshot(` + "
+
+
+
+
+ hello +
@@ -240,7 +330,7 @@ export var storyboard = ( ) `, { - 'app.js': ` + '/app.js': ` import React from 'react' export default function App(props) { return
@@ -256,7 +346,7 @@ export default function App(props) { id=\\"canvas-container\\" data-testid=\\"canvas-container\\" style=\\"position: absolute\\" - data-utopia-valid-paths=\\"storyboard-entity storyboard-entity/scene-1-entity storyboard-entity/scene-1-entity/app-entity storyboard-entity/scene-2-entity storyboard-entity/scene-2-entity/same-file-app-entity storyboard-entity/scene-2-entity/same-file-app-entity:same-file-app-div\\" + data-utopia-valid-paths=\\"storyboard-entity storyboard-entity/scene-1-entity storyboard-entity/scene-1-entity/app-entity storyboard-entity/scene-1-entity/app-entity:app-outer-div storyboard-entity/scene-1-entity/app-entity:app-outer-div/inner-div storyboard-entity/scene-2-entity storyboard-entity/scene-2-entity/same-file-app-entity storyboard-entity/scene-2-entity/same-file-app-entity:same-file-app-div\\" data-utopia-root-element-path=\\"storyboard-entity\\" >
-
hello
+
+ hello +
{ } `, { - 'app.js': ` + '/app.js': ` import { throwError } from './card' export function throwErrorFromCard() { diff --git a/editor/src/components/canvas/ui-jsx-canvas-renderer/animation-context.ts b/editor/src/components/canvas/ui-jsx-canvas-renderer/animation-context.ts index df61da3f9837..7e0aed35c661 100644 --- a/editor/src/components/canvas/ui-jsx-canvas-renderer/animation-context.ts +++ b/editor/src/components/canvas/ui-jsx-canvas-renderer/animation-context.ts @@ -46,21 +46,24 @@ export function useCanvasAnimation(paths: ElementPath[]) { ) const selector = React.useMemo(() => { + if (uids.length === 0) { + return null + } return uids.map((uid) => `[data-uid='${uid}']`).join(',') }, [uids]) const elements = React.useMemo( - () => (selector === '' ? [] : document.querySelectorAll(selector)), + () => (selector == null || selector === '' ? [] : document.querySelectorAll(selector)), [selector], ) return React.useCallback( (keyframes: DOMKeyframesDefinition, options?: DynamicAnimationOptions) => { - if (ctx.animate == null || elements.length === 0) { + if (ctx.animate == null || elements.length === 0 || selector == null) { return } - void ctx.animate(elements, keyframes, options) + void ctx.animate(selector, keyframes, options) }, - [ctx, elements], + [ctx, elements, selector], ) } diff --git a/editor/src/components/canvas/ui-jsx-canvas-renderer/remix-scene-component.tsx b/editor/src/components/canvas/ui-jsx-canvas-renderer/remix-scene-component.tsx index 972216bfb53f..83057fafdc6d 100644 --- a/editor/src/components/canvas/ui-jsx-canvas-renderer/remix-scene-component.tsx +++ b/editor/src/components/canvas/ui-jsx-canvas-renderer/remix-scene-component.tsx @@ -8,7 +8,7 @@ import { UTOPIA_PATH_KEY } from '../../../core/model/utopia-constants' import * as EP from '../../../core/shared/element-path' import { useSetAtom } from 'jotai' import type { AppLoadContext } from '@remix-run/server-runtime' - +import { SceneContainerName } from '../canvas-types' export const REMIX_SCENE_TESTID = 'remix-scene' export interface RemixSceneProps { @@ -24,6 +24,7 @@ export const RemixSceneComponent = React.memo((props: React.PropsWithChildren { await switchToLiveMode(renderResult) + function checkClicky(expectedContentText: string): void { + const clicky = renderResult.renderedDOM.getByTestId('clicky') + expect(clicky.innerText).toEqual(expectedContentText) + } + // Ensure we can find the original text - expect(renderResult.renderedDOM.queryByText('Clicked 0 times')).not.toBeNull() + checkClicky('Clicked 0 times') await clickButton(renderResult) // Ensure it has been updated - expect(renderResult.renderedDOM.queryByText('Clicked 1 times')).not.toBeNull() + checkClicky('Clicked 1 times') // Update the top level arbitrary JS block await updateCode( @@ -231,7 +236,7 @@ describe('Re-mounting is avoided when', () => { ) // Check that it has updated without resetting the state - expect(renderResult.renderedDOM.queryByText('Clicked: 1 times')).not.toBeNull() + checkClicky('Clicked: 1 times') // Update the component itself await updateCode( @@ -241,7 +246,7 @@ describe('Re-mounting is avoided when', () => { ) // Check again that it has updated without resetting the state - expect(renderResult.renderedDOM.queryByText('Clicked: 1 times!')).not.toBeNull() + checkClicky('Clicked: 1 times!') }) it('arbitrary JS or a component is edited in a remix project', async () => { diff --git a/editor/src/components/canvas/ui-jsx-canvas-renderer/ui-jsx-canvas-component-renderer.tsx b/editor/src/components/canvas/ui-jsx-canvas-renderer/ui-jsx-canvas-component-renderer.tsx index 55954b926d6a..ce6cefe6446a 100644 --- a/editor/src/components/canvas/ui-jsx-canvas-renderer/ui-jsx-canvas-component-renderer.tsx +++ b/editor/src/components/canvas/ui-jsx-canvas-renderer/ui-jsx-canvas-component-renderer.tsx @@ -66,6 +66,25 @@ function tryToGetInstancePath( } } +type EmptyBuildResult = { + type: 'EMPTY_BUILD_RESULT' +} + +type RenderedBuildResult = { + type: 'RENDERED_BUILD_RESULT' + result: React.ReactElement | null +} + +type BuildResult = EmptyBuildResult | RenderedBuildResult + +function emptyBuildResult(): EmptyBuildResult { + return { type: 'EMPTY_BUILD_RESULT' } +} + +function renderedBuildResult(result: React.ReactElement | null): RenderedBuildResult { + return { type: 'RENDERED_BUILD_RESULT', result: result } +} + export function createComponentRendererComponent(params: { topLevelElementName: string | null filePath: string @@ -247,7 +266,7 @@ export function createComponentRendererComponent(params: { filePathMappings: rerenderUtopiaContext.filePathMappings, } - const buildResult = React.useRef(null) + const buildResult = React.useRef(emptyBuildResult()) let earlyReturn: EarlyReturn | null = null if (utopiaJsxComponent.arbitraryJSBlock != null) { @@ -299,11 +318,11 @@ export function createComponentRendererComponent(params: { break case 'EARLY_RETURN_VOID': earlyReturn = arbitraryBlockResult - buildResult.current = undefined as any + buildResult.current = renderedBuildResult(undefined as any) break case 'EARLY_RETURN_RESULT': earlyReturn = arbitraryBlockResult - buildResult.current = arbitraryBlockResult.result as any + buildResult.current = renderedBuildResult(arbitraryBlockResult.result as any) break default: assertNever(arbitraryBlockResult) @@ -350,10 +369,19 @@ export function createComponentRendererComponent(params: { null, ) } - } else if (shouldUpdate() || buildResult.current === null) { - buildResult.current = buildComponentRenderResult(utopiaJsxComponent.rootElement) + } else if (shouldUpdate() || buildResult.current.type === 'EMPTY_BUILD_RESULT') { + buildResult.current = renderedBuildResult( + buildComponentRenderResult(utopiaJsxComponent.rootElement), + ) + } + switch (buildResult.current.type) { + case 'EMPTY_BUILD_RESULT': + return null + case 'RENDERED_BUILD_RESULT': + return buildResult.current.result + default: + assertNever(buildResult.current) } - return buildResult.current } Component.displayName = `ComponentRenderer(${params.topLevelElementName})` Component.topLevelElementName = params.topLevelElementName diff --git a/editor/src/components/canvas/ui-jsx-canvas.spec.tsx b/editor/src/components/canvas/ui-jsx-canvas.spec.tsx index 5f4835769c7d..d26b9a678fe6 100644 --- a/editor/src/components/canvas/ui-jsx-canvas.spec.tsx +++ b/editor/src/components/canvas/ui-jsx-canvas.spec.tsx @@ -1451,6 +1451,7 @@ export var ${BakedInStoryboardVariableName} = (props) => { data-path=\\"utopia-storyboard-uid/scene-aaa\\" style=\\" overflow: hidden; + container: scene / inline-size; position: absolute; background-color: var(--utopitheme-emphasizedBackground); box-shadow: 0px 1px 2px 0px var(--utopitheme-shadow90), @@ -1560,6 +1561,7 @@ export var ${BakedInStoryboardVariableName} = (props) => { data-path=\\"utopia-storyboard-uid/scene-aaa\\" style=\\" overflow: hidden; + container: scene / inline-size; position: absolute; background-color: var(--utopitheme-emphasizedBackground); box-shadow: 0px 1px 2px 0px var(--utopitheme-shadow90), @@ -2062,6 +2064,7 @@ describe('UiJsxCanvas render multifile projects', () => { data-path=\\"utopia-storyboard-uid/scene-aaa\\" style=\\" overflow: hidden; + container: scene / inline-size; position: absolute; background-color: var(--utopitheme-emphasizedBackground); box-shadow: 0px 1px 2px 0px var(--utopitheme-shadow90), @@ -2157,6 +2160,7 @@ describe('UiJsxCanvas render multifile projects', () => { data-path=\\"utopia-storyboard-uid/scene-aaa\\" style=\\" overflow: hidden; + container: scene / inline-size; position: absolute; background-color: var(--utopitheme-emphasizedBackground); box-shadow: 0px 1px 2px 0px var(--utopitheme-shadow90), @@ -2254,6 +2258,7 @@ describe('UiJsxCanvas render multifile projects', () => { data-path=\\"utopia-storyboard-uid/scene-aaa\\" style=\\" overflow: hidden; + container: scene / inline-size; position: absolute; background-color: var(--utopitheme-emphasizedBackground); box-shadow: 0px 1px 2px 0px var(--utopitheme-shadow90), @@ -2336,6 +2341,7 @@ describe('UiJsxCanvas render multifile projects', () => { data-path=\\"utopia-storyboard-uid/scene-aaa\\" style=\\" overflow: hidden; + container: scene / inline-size; position: absolute; background-color: var(--utopitheme-emphasizedBackground); box-shadow: 0px 1px 2px 0px var(--utopitheme-shadow90), @@ -2431,6 +2437,7 @@ describe('UiJsxCanvas render multifile projects', () => { data-path=\\"utopia-storyboard-uid/scene-aaa\\" style=\\" overflow: hidden; + container: scene / inline-size; position: absolute; background-color: var(--utopitheme-emphasizedBackground); box-shadow: 0px 1px 2px 0px var(--utopitheme-shadow90), diff --git a/editor/src/components/canvas/ui-jsx-canvas.test-utils.tsx b/editor/src/components/canvas/ui-jsx-canvas.test-utils.tsx index 450653a9d639..dc7dc9c7cd2f 100644 --- a/editor/src/components/canvas/ui-jsx-canvas.test-utils.tsx +++ b/editor/src/components/canvas/ui-jsx-canvas.test-utils.tsx @@ -60,8 +60,13 @@ export interface PartialCanvasProps { } export const dumbResolveFn = (filenames: Array): CurriedResolveFn => { - return (_: ProjectContentTreeRoot) => (importOrigin: string, toImport: string) => - resolveTestFiles(filenames, importOrigin, toImport) + return (_: ProjectContentTreeRoot) => (importOrigin: string, toImport: string) => { + const result = resolveTestFiles(filenames, importOrigin, toImport) + if (!isRight(result)) { + console.error(result.value) + } + return result + } } function resolveTestFiles( @@ -72,7 +77,8 @@ function resolveTestFiles( let normalizedName = normalizeName(importOrigin, toImport) // Partly restoring what `normalizeName` strips away. if (toImport.startsWith('.')) { - normalizedName = path.normalize(`${importOrigin}/${normalizedName}`) + const parsedOrigin = path.parse(importOrigin) + normalizedName = path.normalize(`${parsedOrigin.dir}/${normalizedName}`) } else if (toImport.startsWith('/')) { normalizedName = `/${normalizedName}` } @@ -92,7 +98,9 @@ function resolveTestFiles( case UiFilePath: return right(UiFilePath) default: - return left(`Test error, the dumbResolveFn did not know about this file: ${toImport}`) + return left( + `Test error, the dumbResolveFn did not know about this file: ${toImport}, got ${normalizedName}`, + ) } } diff --git a/editor/src/components/canvas/ui-jsx-canvas.tsx b/editor/src/components/canvas/ui-jsx-canvas.tsx index 19413a4462d6..5912a6c1ca57 100644 --- a/editor/src/components/canvas/ui-jsx-canvas.tsx +++ b/editor/src/components/canvas/ui-jsx-canvas.tsx @@ -75,9 +75,12 @@ import { } from './ui-jsx-canvas-renderer/ui-jsx-canvas-execution-scope' import { applyUIDMonkeyPatch } from '../../utils/canvas-react-utils' import type { RemixValidPathsGenerationContext } from './canvas-utils' -import { getParseSuccessForFilePath, getValidElementPaths } from './canvas-utils' +import { + projectContentsSameForRefreshRequire, + getParseSuccessForFilePath, + getValidElementPaths, +} from './canvas-utils' import { arrayEqualsByValue, fastForEach, NO_OP } from '../../core/shared/utils' -import { useTwind } from '../../core/tailwind/tailwind' import { AlwaysFalse, atomWithPubSub, @@ -98,6 +101,7 @@ import { IS_TEST_ENVIRONMENT } from '../../common/env-vars' import { listenForReactRouterErrors } from '../../core/shared/runtime-report-logs' import { getFilePathMappings } from '../../core/model/project-file-utils' import { useInvalidatedCanvasRemount } from './canvas-component-entry' +import { useTailwindCompilation } from '../../core/tailwind/tailwind-compilation' applyUIDMonkeyPatch() @@ -360,20 +364,13 @@ export const UiJsxCanvas = React.memo((props) useClearSpyMetadataOnRemount(props.invalidatedCanvasData, isRemounted, metadataContext) - const elementsToRerenderRef = React.useRef(ElementsToRerenderGLOBAL.current) - const shouldRerenderRef = React.useRef(false) - shouldRerenderRef.current = - ElementsToRerenderGLOBAL.current === 'rerender-all-elements' || - elementsToRerenderRef.current === 'rerender-all-elements' || // TODO this means the first drag frame will still be slow, figure out a nicer way to immediately switch to true. probably this should live in a dedicated a function - !arrayEqualsByValue( - ElementsToRerenderGLOBAL.current, - elementsToRerenderRef.current, - EP.pathsEqual, - ) // once we get here, we know that both `ElementsToRerenderGLOBAL.current` and `elementsToRerenderRef.current` are arrays - elementsToRerenderRef.current = ElementsToRerenderGLOBAL.current - const maybeOldProjectContents = React.useRef(projectContents) - if (shouldRerenderRef.current) { + + const projectContentsSimilarEnough = projectContentsSameForRefreshRequire( + maybeOldProjectContents.current, + projectContents, + ) + if (!projectContentsSimilarEnough) { maybeOldProjectContents.current = projectContents } @@ -498,7 +495,7 @@ export const UiJsxCanvas = React.memo((props) const executionScope = scope - useTwind(projectContentsForRequireFn, customRequire, '#canvas-container') + useTailwindCompilation() const topLevelElementsMap = useKeepReferenceEqualityIfPossible(new Map(topLevelJsxComponents)) @@ -833,7 +830,7 @@ function useGetStoryboardRoot( getRemixValidPathsGenerationContext, ) const storyboardRootElementPath = useKeepReferenceEqualityIfPossible( - validPaths[0] ?? EP.emptyElementPath, + validPaths.at(0) ?? EP.emptyElementPath, ) const rootValidPathsArray = validPaths.map(EP.makeLastPartOfPathStatic) diff --git a/editor/src/components/canvas/ui-jsx.test-utils.tsx b/editor/src/components/canvas/ui-jsx.test-utils.tsx index a95396aafcc8..a08f5fed99f1 100644 --- a/editor/src/components/canvas/ui-jsx.test-utils.tsx +++ b/editor/src/components/canvas/ui-jsx.test-utils.tsx @@ -59,7 +59,7 @@ import { FakeWatchdogWorker, } from '../../core/workers/test-workers' import { UtopiaTsWorkersImplementation } from '../../core/workers/workers' -import { EditorRoot } from '../../templates/editor' +import { collectElementsToRerender, EditorRoot } from '../../templates/editor' import Utils from '../../utils/utils' import { getNamedPath } from '../../utils/react-helpers' import type { @@ -156,7 +156,11 @@ import { import { uniqBy } from '../../core/shared/array-utils' import { InitialOnlineState } from '../editor/online-status' import { RadixComponentsPortalId } from '../../uuiui/radix-components' -import { runDomSampler } from './dom-sampler' +import { + resetDomSamplerExecutionCounts, + runDomSamplerGroups, + runDomSamplerRegular, +} from './dom-sampler' import { ElementInstanceMetadataKeepDeepEquality, ElementInstanceMetadataMapKeepDeepEquality, @@ -306,6 +310,8 @@ export function optOutFromCheckFileTimestamps() { }) } +let prevDomWalkerMutableState: DomWalkerMutableStateData | null = null + export async function renderTestEditorWithModel( rawModel: PersistentModel, awaitFirstDomReport: 'await-first-dom-report' | 'dont-await-first-dom-report', @@ -352,6 +358,7 @@ export async function renderTestEditorWithModel( waitForDispatchEntireUpdate = false, innerStrategiesToUse: Array = strategiesToUse, ) => { + resetDomSamplerExecutionCounts() recordedActions.push(...actions) const originalEditorState = workingEditorState const result = editorDispatchActionRunner( @@ -421,6 +428,9 @@ export async function renderTestEditorWithModel( flushSync(() => { canvasStoreHook.setState(patchedStoreFromFullStore(workingEditorState, 'canvas-store')) + helperControlsStoreHook.setState( + patchedStoreFromFullStore(workingEditorState, 'helper-controls-store'), + ) }) // run dom SAMPLER @@ -428,8 +438,9 @@ export async function renderTestEditorWithModel( { resubscribeObservers(domWalkerMutableState) - const metadataResult = runDomSampler({ - elementsToFocusOn: workingEditorState.patchedEditor.canvas.elementsToRerender, + const elementsToFocusOn = collectElementsToRerender(workingEditorState, actions) + const metadataResult = runDomSamplerRegular({ + elementsToFocusOn: elementsToFocusOn, domWalkerAdditionalElementsToFocusOn: workingEditorState.patchedEditor.canvas.domWalkerAdditionalElementsToUpdate, scale: workingEditorState.patchedEditor.canvas.scale, @@ -488,8 +499,6 @@ export async function renderTestEditorWithModel( // re-render the canvas { - // TODO run fixElementsToRerender and set ElementsToRerenderGLOBAL - flushSync(() => { canvasStoreHook.setState(patchedStoreFromFullStore(workingEditorState, 'canvas-store')) }) @@ -500,8 +509,14 @@ export async function renderTestEditorWithModel( { resubscribeObservers(domWalkerMutableState) - const metadataResult = runDomSampler({ - elementsToFocusOn: workingEditorState.patchedEditor.canvas.elementsToRerender, + // TODO: The real dispatch updates ElementsToRerenderGLOBAL.current, + // while the fake one doesn't. Ideally this behaviour would be the + // same, but solving this was out of scope for the PR that introduced + // collectElementsToRerender + // (https://github.com/concrete-utopia/utopia/pull/6465) + const elementsToFocusOn = collectElementsToRerender(workingEditorState, actions) + const metadataResult = runDomSamplerGroups({ + elementsToFocusOn: elementsToFocusOn, domWalkerAdditionalElementsToFocusOn: workingEditorState.patchedEditor.canvas.domWalkerAdditionalElementsToUpdate, scale: workingEditorState.patchedEditor.canvas.scale, @@ -557,7 +572,7 @@ export async function renderTestEditorWithModel( } const workers = new UtopiaTsWorkersImplementation( - new FakeParserPrinterWorker(), + [new FakeParserPrinterWorker()], new FakeLinterWorker(), new FakeWatchdogWorker(), ) @@ -600,7 +615,14 @@ export async function renderTestEditorWithModel( patchedStoreFromFullStore(initialEditorStore, 'canvas-store'), ) + const helperControlsStoreHook: UtopiaStoreAPI = createStoresAndState( + patchedStoreFromFullStore(initialEditorStore, 'helper-controls-store'), + ) + + prevDomWalkerMutableState?.resizeObserver.disconnect() + prevDomWalkerMutableState?.mutationObserver.disconnect() const domWalkerMutableState = createDomWalkerMutableState(canvasStoreHook, asyncTestDispatch) + prevDomWalkerMutableState = domWalkerMutableState const lowPriorityStoreHook: UtopiaStoreAPI = createStoresAndState( patchedStoreFromFullStore(initialEditorStore, 'low-priority-store'), @@ -658,6 +680,7 @@ label { dispatch={asyncTestDispatch as EditorDispatch} mainStore={storeHook} canvasStore={canvasStoreHook} + helperControlsStore={helperControlsStoreHook} spyCollector={spyCollector} lowPriorityStore={lowPriorityStoreHook} domWalkerMutableState={domWalkerMutableState} @@ -1027,7 +1050,7 @@ export function createBuiltinDependenciesWithTestWorkers( extraBuiltinDependencies: BuiltInDependencies, ): BuiltInDependencies { const workers = new UtopiaTsWorkersImplementation( - new FakeParserPrinterWorker(), + [new FakeParserPrinterWorker()], new FakeLinterWorker(), new FakeWatchdogWorker(), ) diff --git a/editor/src/components/common/actions/index.ts b/editor/src/components/common/actions/index.ts index b58160a0e349..9272ebc26903 100644 --- a/editor/src/components/common/actions/index.ts +++ b/editor/src/components/common/actions/index.ts @@ -1,5 +1,3 @@ -export type PreviewPanel = 'preview' - export type LeftMenuPanel = | 'filebrowser' | 'dependencylist' @@ -22,10 +20,9 @@ export type EditorPanel = | CenterPanel | CodeEditorPanel | InspectorPanel - | PreviewPanel | NavigatorPanel -export type EditorPane = 'leftmenu' | 'center' | 'inspector' | 'preview' | 'rightmenu' +export type EditorPane = 'leftmenu' | 'center' | 'inspector' | 'rightmenu' export function paneForPanel(panel: EditorPanel | null): EditorPane | null { switch (panel) { @@ -55,8 +52,6 @@ export function paneForPanel(panel: EditorPanel | null): EditorPane | null { return 'rightmenu' case 'codeEditor': return 'center' - case 'preview': - return 'preview' default: const _exhaustiveCheck: never = panel throw new Error(`Unhandled panel ${panel}`) diff --git a/editor/src/components/common/shared-strategies/convert-to-flex-strategy.ts b/editor/src/components/common/shared-strategies/convert-to-flex-strategy.ts index 812fd1f10dc7..f1963d1ee0c4 100644 --- a/editor/src/components/common/shared-strategies/convert-to-flex-strategy.ts +++ b/editor/src/components/common/shared-strategies/convert-to-flex-strategy.ts @@ -174,7 +174,7 @@ function convertThreeElementGroupRow( ).map((element) => { return { ...element, - localFrame: MetadataUtils.getLocalFrame(element.elementPath, metadata), + localFrame: MetadataUtils.getLocalFrame(element.elementPath, metadata, null), } }) if (childrenMetadata.length === 3) { diff --git a/editor/src/components/common/shared-strategies/convert-to-grid-strategy.ts b/editor/src/components/common/shared-strategies/convert-to-grid-strategy.ts index 9c99dddfe74c..8299cbd6cb21 100644 --- a/editor/src/components/common/shared-strategies/convert-to-grid-strategy.ts +++ b/editor/src/components/common/shared-strategies/convert-to-grid-strategy.ts @@ -47,7 +47,10 @@ function guessLayoutInfoAlongAxis( } } -function guessMatchingGridSetup(children: Array): { +function guessMatchingGridSetup( + children: Array, + isFlexContainer: boolean, +): { gap: number numberOfColumns: number numberOfRows: number @@ -65,10 +68,12 @@ function guessMatchingGridSetup(children: Array): { (a, b) => b.frame.y - (a.frame.y + a.frame.height), ) + const minRowsOrCols = isFlexContainer ? 1 : 2 + return { gap: (horizontalData.averageGap + verticalData.averageGap) / 2, - numberOfColumns: Math.max(1, horizontalData.nChildren), - numberOfRows: Math.max(1, verticalData.nChildren), + numberOfColumns: Math.max(minRowsOrCols, horizontalData.nChildren), + numberOfRows: Math.max(minRowsOrCols, verticalData.nChildren), } } @@ -90,9 +95,16 @@ export function convertLayoutToGridCommands( frame: MetadataUtils.getFrameOrZeroRectInCanvasCoords(child, metadata), })) - const { gap, numberOfColumns, numberOfRows } = guessMatchingGridSetup(childFrames) + const isFlexContainer = MetadataUtils.isFlexLayoutedContainer( + MetadataUtils.findElementByElementPath(metadata, elementPath), + ) + + const { gap, numberOfColumns, numberOfRows } = guessMatchingGridSetup( + childFrames, + isFlexContainer, + ) - return [ + let commands = [ ...prunePropsCommands(flexContainerProps, elementPath), ...prunePropsCommands(gridContainerProps, elementPath), ...childrenPaths.flatMap((child) => [ @@ -114,5 +126,11 @@ export function convertLayoutToGridCommands( Array(numberOfRows).fill('1fr').join(' '), ), ] + + if (!isFlexContainer) { + commands.push(setProperty('always', elementPath, PP.create('style', 'gap'), 10)) + } + + return commands }) } diff --git a/editor/src/components/context-menu-wrapper.tsx b/editor/src/components/context-menu-wrapper.tsx index 42af87678d63..98e2b054ce04 100644 --- a/editor/src/components/context-menu-wrapper.tsx +++ b/editor/src/components/context-menu-wrapper.tsx @@ -155,8 +155,18 @@ export const ContextMenu = ({ dispatch, getData, id, items }: ContextMenuPro [getData, dispatch, isDisabled, isHidden], ) + const onMouseDown = React.useCallback((event: React.MouseEvent) => { + event.stopPropagation() + }, []) + return ( - + {splitItems.map((item, index) => { if (item?.type === 'submenu') { return ( diff --git a/editor/src/components/custom-code/code-file.ts b/editor/src/components/custom-code/code-file.ts index db71fa3fa70c..22fda15045d8 100644 --- a/editor/src/components/custom-code/code-file.ts +++ b/editor/src/components/custom-code/code-file.ts @@ -154,7 +154,7 @@ export interface ShownInspectorSpec { sections: Styling[] } -export type TypedInpsectorSpec = { type: 'hidden' } | ShownInspectorSpec +export type TypedInspectorSpec = { type: 'hidden' } | ShownInspectorSpec export interface ComponentDescriptor { properties: PropertyControls @@ -163,7 +163,7 @@ export interface ComponentDescriptor { variants: ComponentInfo[] source: ComponentDescriptorSource focus: Focus - inspector: TypedInpsectorSpec + inspector: TypedInspectorSpec emphasis: Emphasis icon: Icon label: string | null @@ -187,7 +187,7 @@ export function componentDescriptor( preferredChildComponents: Array, source: ComponentDescriptorSource, focus: Focus, - inspector: TypedInpsectorSpec, + inspector: TypedInspectorSpec, emphasis: Emphasis, icon: Icon, label: string | null, diff --git a/editor/src/components/editor/action-types.ts b/editor/src/components/editor/action-types.ts index 54ac5bcb998c..18165510ac49 100644 --- a/editor/src/components/editor/action-types.ts +++ b/editor/src/components/editor/action-types.ts @@ -86,6 +86,13 @@ import type { Bounds } from 'utopia-vscode-common' import type { Optic } from '../../core/shared/optics/optics' import { makeOptic } from '../../core/shared/optics/optics' import type { ElementPathTrees } from '../../core/shared/element-path-tree' +import { assertNever } from '../../core/shared/utils' +import type { + ImportOperation, + ImportOperationAction, + ImportStatus, +} from '../../core/shared/import/import-operation-types' +import type { ProjectRequirements } from '../../core/shared/import/project-health-check/utopia-requirements-types' export { isLoggedIn, loggedInUser, notLoggedIn } from '../../common/user' export type { LoginState, UserDetails } from '../../common/user' @@ -294,7 +301,7 @@ export type SetZIndex = { export type TransientActions = { action: 'TRANSIENT_ACTIONS' transientActions: Array - elementsToRerender: Array | null + elementsToRerender: Array } // This is a wrapper action which changes the undo behavior for the included actions. @@ -601,11 +608,6 @@ export interface SetProjectDescription { description: string } -export interface UpdatePreviewConnected { - action: 'UPDATE_PREVIEW_CONNECTED' - connected: boolean -} - export interface AlignSelectedViews { action: 'ALIGN_SELECTED_VIEWS' alignment: Alignment @@ -627,10 +629,6 @@ export interface SetCursorOverlay { cursor: CSSCursor | null } -export interface SendPreviewModel { - action: 'SEND_PREVIEW_MODEL' -} - export interface UpdateFilePath { action: 'UPDATE_FILE_PATH' oldPath: string @@ -796,6 +794,7 @@ export interface UpdateMetadataInEditorState { export interface RunDOMWalker { action: 'RUN_DOM_WALKER' + restrictToElements: Array | null } export interface TrueUpElements { @@ -1004,6 +1003,27 @@ export interface UpdateGithubOperations { type: GithubOperationType } +export interface UpdateImportOperations { + action: 'UPDATE_IMPORT_OPERATIONS' + operations: ImportOperation[] + type: ImportOperationAction +} + +export interface UpdateImportStatus { + action: 'UPDATE_IMPORT_STATUS' + importStatus: ImportStatus +} + +export interface UpdateProjectRequirements { + action: 'UPDATE_PROJECT_REQUIREMENTS' + requirements: Partial +} + +export interface SetImportWizardOpen { + action: 'SET_IMPORT_WIZARD_OPEN' + open: boolean +} + export interface SetRefreshingDependencies { action: 'SET_REFRESHING_DEPENDENCIES' value: boolean @@ -1084,11 +1104,6 @@ export interface RunEscapeHatch { setHuggingParentToFixed: SetHuggingParentToFixed } -export interface SetElementsToRerender { - action: 'SET_ELEMENTS_TO_RERENDER' - value: ElementsToRerender -} - export type ToggleSelectionLock = { action: 'TOGGLE_SELECTION_LOCK' targets: Array @@ -1277,13 +1292,11 @@ export type EditorAction = | OpenCodeEditor | SetProjectName | SetProjectDescription - | UpdatePreviewConnected | AlignSelectedViews | DistributeSelectedViews | SetCursorOverlay | DuplicateSpecificElements | UpdateDuplicationState - | SendPreviewModel | UpdateFilePath | UpdateRemixRoute | OpenCodeEditorFile @@ -1358,11 +1371,14 @@ export type EditorAction = | SetResizeOptionsTargetOptions | ForceParseFile | RunEscapeHatch - | SetElementsToRerender | ToggleSelectionLock | UpdateAgainstGithub | SetImageDragSessionState | UpdateGithubOperations + | UpdateImportOperations + | UpdateImportStatus + | UpdateProjectRequirements + | SetImportWizardOpen | UpdateBranchContents | SetRefreshingDependencies | ApplyCommandsAction @@ -1445,6 +1461,9 @@ export type DispatchPriority = | 'topmenu' | 'contextmenu' | 'noone' + | 'canvas-fast-selection-hack' + | 'resume-canvas-fast-selection-hack' + export type EditorDispatch = ( actions: ReadonlyArray, priority?: DispatchPriority, @@ -1472,3 +1491,22 @@ export const usingDispatch = ( export type Alignment = 'left' | 'hcenter' | 'right' | 'top' | 'vcenter' | 'bottom' export type Distribution = 'horizontal' | 'vertical' + +export function isAlignment( + alignmentOrDistribution: Alignment | Distribution, +): alignmentOrDistribution is Alignment { + switch (alignmentOrDistribution) { + case 'bottom': + case 'hcenter': + case 'left': + case 'right': + case 'top': + case 'vcenter': + return true + case 'horizontal': + case 'vertical': + return false + default: + assertNever(alignmentOrDistribution) + } +} diff --git a/editor/src/components/editor/actions/action-creators.ts b/editor/src/components/editor/actions/action-creators.ts index fa9ad247a85b..a8c9383b9f1a 100644 --- a/editor/src/components/editor/actions/action-creators.ts +++ b/editor/src/components/editor/actions/action-creators.ts @@ -92,7 +92,6 @@ import type { SaveImageSwitchMode, SelectAllSiblings, SelectComponents, - SendPreviewModel, SetAspectRatioLock, SetCanvasFrames, SetCodeEditorBuildErrors, @@ -144,7 +143,6 @@ import type { UpdateKeysPressed, UpdateNodeModulesContents, UpdatePackageJson, - UpdatePreviewConnected, UpdatePropertyControlsInfo, CloseDesignerFile, SetFocusedElement, @@ -239,6 +237,10 @@ import type { ToggleDataCanCondense, UpdateMetadataInEditorState, SetErrorBoundaryHandling, + SetImportWizardOpen, + UpdateImportOperations, + UpdateProjectRequirements, + UpdateImportStatus, } from '../action-types' import type { InsertionSubjectWrapper, Mode } from '../editor-modes' import { EditorModes, insertionSubject } from '../editor-modes' @@ -270,6 +272,12 @@ import type { Collaborator } from '../../../core/shared/multiplayer' import type { PageTemplate } from '../../canvas/remix/remix-utils' import type { Bounds } from 'utopia-vscode-common' import type { ElementPathTrees } from '../../../core/shared/element-path-tree' +import type { + ImportOperation, + ImportOperationAction, + ImportStatus, +} from '../../../core/shared/import/import-operation-types' +import type { ProjectRequirements } from '../../../core/shared/import/project-health-check/utopia-requirements-types' export function clearSelection(): EditorAction { return { @@ -377,7 +385,7 @@ export function toggleDataCanCondense(targets: Array): ToggleDataCa export function transientActions( actions: Array, - elementsToRerender: Array | null = null, + elementsToRerender: Array, ): TransientActions { return { action: 'TRANSIENT_ACTIONS', @@ -974,13 +982,6 @@ export function setProjectDescription(projectDescription: string): SetProjectDes } } -export function updatePreviewConnected(connected: boolean): UpdatePreviewConnected { - return { - action: 'UPDATE_PREVIEW_CONNECTED', - connected: connected, - } -} - export function alignSelectedViews(alignment: Alignment): AlignSelectedViews { return { action: 'ALIGN_SELECTED_VIEWS', @@ -1006,12 +1007,6 @@ export function showContextMenu( } } -export function sendPreviewModel(): SendPreviewModel { - return { - action: 'SEND_PREVIEW_MODEL', - } -} - export function updateFilePath(oldPath: string, newPath: string): UpdateFilePath { return { action: 'UPDATE_FILE_PATH', @@ -1261,9 +1256,10 @@ export function updateMetadataInEditorState( } } -export function runDOMWalker(): RunDOMWalker { +export function runDOMWalker(restrictToElements: Array | null): RunDOMWalker { return { action: 'RUN_DOM_WALKER', + restrictToElements: restrictToElements, } } @@ -1605,6 +1601,40 @@ export function resetCanvas(): ResetCanvas { } } +export function updateImportOperations( + operations: ImportOperation[], + type: ImportOperationAction, +): UpdateImportOperations { + return { + action: 'UPDATE_IMPORT_OPERATIONS', + operations: operations, + type: type, + } +} + +export function updateImportStatus(importStatus: ImportStatus): UpdateImportStatus { + return { + action: 'UPDATE_IMPORT_STATUS', + importStatus: importStatus, + } +} + +export function updateProjectRequirements( + requirements: Partial, +): UpdateProjectRequirements { + return { + action: 'UPDATE_PROJECT_REQUIREMENTS', + requirements: requirements, + } +} + +export function setImportWizardOpen(open: boolean): SetImportWizardOpen { + return { + action: 'SET_IMPORT_WIZARD_OPEN', + open: open, + } +} + export function setFilebrowserDropTarget(target: string | null): SetFilebrowserDropTarget { return { action: 'SET_FILEBROWSER_DROPTARGET', diff --git a/editor/src/components/editor/actions/action-utils.ts b/editor/src/components/editor/actions/action-utils.ts index 6b4e6b7c33ec..ae7d431c9904 100644 --- a/editor/src/components/editor/actions/action-utils.ts +++ b/editor/src/components/editor/actions/action-utils.ts @@ -62,8 +62,6 @@ export function isTransientAction(action: EditorAction): boolean { case 'SET_PROJECT_ID': case 'SET_CODE_EDITOR_VISIBILITY': case 'OPEN_CODE_EDITOR': - case 'UPDATE_PREVIEW_CONNECTED': - case 'SEND_PREVIEW_MODEL': case 'CLOSE_DESIGNER_FILE': case 'UPDATE_CODE_RESULT_CACHE': case 'SET_CODE_EDITOR_BUILD_ERRORS': @@ -121,7 +119,6 @@ export function isTransientAction(action: EditorAction): boolean { case 'UPDATE_INTERACTION_SESSION': case 'UPDATE_DRAG_INTERACTION_DATA': case 'SET_USERS_PREFERRED_STRATEGY': - case 'SET_ELEMENTS_TO_RERENDER': case 'TOGGLE_SELECTION_LOCK': case 'UPDATE_GITHUB_OPERATIONS': case 'SET_REFRESHING_DEPENDENCIES': @@ -139,6 +136,10 @@ export function isTransientAction(action: EditorAction): boolean { case 'RESET_ONLINE_STATE': case 'INCREASE_ONLINE_STATE_FAILURE_COUNT': case 'SET_ERROR_BOUNDARY_HANDLING': + case 'SET_IMPORT_WIZARD_OPEN': + case 'UPDATE_IMPORT_OPERATIONS': + case 'UPDATE_IMPORT_STATUS': + case 'UPDATE_PROJECT_REQUIREMENTS': return true case 'TRUE_UP_ELEMENTS': diff --git a/editor/src/components/editor/actions/actions.spec.browser2.tsx b/editor/src/components/editor/actions/actions.spec.browser2.tsx index 4ea691d27750..c378be6fb6ac 100644 --- a/editor/src/components/editor/actions/actions.spec.browser2.tsx +++ b/editor/src/components/editor/actions/actions.spec.browser2.tsx @@ -421,68 +421,68 @@ describe('actions', () => { `, wantSelection: [makeTargetPath('view/group/child1')], }, - // { - // name: 'delete group child selects next sibling (multiple selection)', - // input: ` - // - // - //
- //
- //
- //
- // - //
- //
- //
- // - // `, - // targets: [makeTargetPath('view/group/child3'), makeTargetPath('view/foo/bar')], - // wantCode: ` - // - // - //
- //
- //
- // - //
- // - // `, - // wantSelection: [makeTargetPath('view/group/child1'), makeTargetPath('view/foo')], - // }, - // { - // name: 'delete last group child deletes the group', - // input: ` - // - // - //
- // - // - // `, - // targets: [makeTargetPath('view/group/child1')], - // wantCode: ` - // - // `, - // wantSelection: [makeTargetPath('view')], - // }, - // { - // name: 'recursively delete empty parents when groups or fragments', - // input: ` - //
- // - // - // - //
- // - // - // - //
- // `, - // targets: [makeTargetPath(`root/g1/g2/f1/child`)], - // wantCode: ` - //
- // `, - // wantSelection: [makeTargetPath(`root`)], - // }, + { + name: 'delete group child selects next sibling (multiple selection)', + input: ` + + +
+
+
+
+ +
+
+
+ + `, + targets: [makeTargetPath('view/group/child3'), makeTargetPath('view/foo/bar')], + wantCode: ` + + +
+
+
+ +
+ + `, + wantSelection: [makeTargetPath('view/group/child1'), makeTargetPath('view/foo')], + }, + { + name: 'delete last group child deletes the group', + input: ` + + +
+ + + `, + targets: [makeTargetPath('view/group/child1')], + wantCode: ` + + `, + wantSelection: [makeTargetPath('view')], + }, + { + name: 'recursively delete empty parents when groups or fragments', + input: ` +
+ + + +
+ + + +
+ `, + targets: [makeTargetPath(`root/g1/g2/f1/child`)], + wantCode: ` +
+ `, + wantSelection: [makeTargetPath(`root`)], + }, { name: 'recursively delete empty parents when groups or fragments and stops', input: ` @@ -507,26 +507,26 @@ describe('actions', () => { `, wantSelection: [makeTargetPath(`root/g1/stop-here`)], }, - // { - // name: 'recursively delete empty parents when groups or fragments with multiselect', - // input: ` - //
- // - //
- // - // - //
- // - // - // - //
- // `, - // targets: [makeTargetPath(`root/g1/g2/f1/child`), makeTargetPath(`root/g1/delete-me`)], - // wantCode: ` - //
- // `, - // wantSelection: [makeTargetPath(`root`)], - // }, + { + name: 'recursively delete empty parents when groups or fragments with multiselect', + input: ` +
+ +
+ + +
+ + + +
+ `, + targets: [makeTargetPath(`root/g1/g2/f1/child`), makeTargetPath(`root/g1/delete-me`)], + wantCode: ` +
+ `, + wantSelection: [makeTargetPath(`root`)], + }, ] tests.forEach((tt, idx) => { it(`(${idx + 1}) ${tt.name}`, async () => { diff --git a/editor/src/components/editor/actions/actions.spec.tsx b/editor/src/components/editor/actions/actions.spec.tsx index d30e9ff2d7c2..037c806da975 100644 --- a/editor/src/components/editor/actions/actions.spec.tsx +++ b/editor/src/components/editor/actions/actions.spec.tsx @@ -1133,6 +1133,7 @@ describe('UPDATE_FROM_WORKER', () => { updateToCheck, startingEditorState, defaultUserState, + NO_OP, ) // Check that the model hasn't changed, because of the stale revised time. @@ -1180,6 +1181,7 @@ describe('UPDATE_FROM_WORKER', () => { updateToCheck, startingEditorState, defaultUserState, + NO_OP, ) // Get the same values that we started with but from the updated editor state. diff --git a/editor/src/components/editor/actions/actions.tsx b/editor/src/components/editor/actions/actions.tsx index d2fe43d86b6f..1f910fb5b9b0 100644 --- a/editor/src/components/editor/actions/actions.tsx +++ b/editor/src/components/editor/actions/actions.tsx @@ -4,8 +4,7 @@ import localforage from 'localforage' import { imagePathURL } from '../../../common/server' import { roundAttributeLayoutValues } from '../../../core/layout/layout-utils' import { - findElementAtPath, - findJSXElementAtPath, + getSimpleAttributeAtPath, getZIndexOrderedViewsWithoutDirectChildren, MetadataUtils, } from '../../../core/model/element-metadata-utils' @@ -102,6 +101,7 @@ import type { LocalRectangle, Size, CanvasVector, + MaybeInfinityCanvasRectangle, } from '../../../core/shared/math-utils' import { canvasRectangle, @@ -155,9 +155,14 @@ import { import * as PP from '../../../core/shared/property-path' import { assertNever, fastForEach, getProjectLockedKey, identity } from '../../../core/shared/utils' import { emptyImports, mergeImports } from '../../../core/workers/common/project-file-utils' -import type { UtopiaTsWorkers } from '../../../core/workers/common/worker-types' +import { + createParseAndPrintOptions, + createParseFile, + createPrintAndReparseFile, + type UtopiaTsWorkers, +} from '../../../core/workers/common/worker-types' import type { IndexPosition } from '../../../utils/utils' -import Utils, { absolute } from '../../../utils/utils' +import Utils from '../../../utils/utils' import type { ProjectContentTreeRoot } from '../../assets' import { isProjectContentFile, @@ -239,7 +244,6 @@ import type { ScrollToElement, SelectAllSiblings, SelectComponents, - SendPreviewModel, SetAspectRatioLock, SetCanvasFrames, SetCodeEditorBuildErrors, @@ -247,7 +251,6 @@ import type { SetCodeEditorVisibility, SetCurrentTheme, SetCursorOverlay, - SetElementsToRerender, SetFilebrowserDropTarget, SetFilebrowserRenamingTarget, SetFocusedElement, @@ -304,7 +307,6 @@ import type { UpdateMouseButtonsPressed, UpdateNodeModulesContents, UpdatePackageJson, - UpdatePreviewConnected, UpdateProjectContents, UpdatePropertyControlsInfo, WrapInElement, @@ -349,8 +351,12 @@ import type { ToggleDataCanCondense, UpdateMetadataInEditorState, SetErrorBoundaryHandling, + SetImportWizardOpen, + UpdateImportOperations, + UpdateProjectRequirements, + UpdateImportStatus, } from '../action-types' -import { isLoggedIn } from '../action-types' +import { isAlignment, isLoggedIn } from '../action-types' import type { Mode } from '../editor-modes' import { isCommentMode, isFollowMode, isTextEditMode } from '../editor-modes' import { EditorModes, isLiveMode, isSelectMode } from '../editor-modes' @@ -392,7 +398,6 @@ import { trueUpChildrenOfGroupChanged, trueUpHuggingElement, trueUpGroupElementChanged, - getPackageJsonFromProjectContents, modifyUnderlyingTargetJSXElement, getAllComponentDescriptorErrors, updatePackageJsonInEditorState, @@ -430,12 +435,11 @@ import { import { loadStoredState } from '../stored-state' import { applyMigrations } from './migrations/migrations' -import { boundsInFile, defaultConfig } from 'utopia-vscode-common' +import { defaultConfig } from 'utopia-vscode-common' import { reorderElement } from '../../../components/canvas/commands/reorder-element-command' import type { BuiltInDependencies } from '../../../core/es-modules/package-manager/built-in-dependencies-list' import { fetchNodeModules } from '../../../core/es-modules/package-manager/fetch-packages' import { resolveModule } from '../../../core/es-modules/package-manager/module-resolution' -import { addStoryboardFileToProject } from '../../../core/model/storyboard-utils' import { UTOPIA_UID_KEY } from '../../../core/model/utopia-constants' import { mapDropNulls, uniqBy } from '../../../core/shared/array-utils' import type { TreeConflicts } from '../../../core/shared/github/helpers' @@ -485,7 +489,6 @@ import { } from '../../canvas/canvas-strategies/strategies/shared-move-strategies-helpers' import type { CanvasCommand } from '../../canvas/commands/commands' import { foldAndApplyCommandsSimple } from '../../canvas/commands/commands' -import { setElementsToRerenderCommand } from '../../canvas/commands/set-elements-to-rerender-command' import type { UiJsxCanvasContextData } from '../../canvas/ui-jsx-canvas' import { notice } from '../../common/notice' import type { ShortcutConfiguration } from '../shortcut-definitions' @@ -496,7 +499,6 @@ import { clearImageFileBlob, enableInsertModeForJSXElement, finishCheckpointTimer, - insertAsChildTarget, insertJSXElement, openCodeEditorFile, replaceMappedElement, @@ -518,6 +520,7 @@ import { import { addToastToState, includeToast, removeToastFromState } from './toast-helpers' import { AspectRatioLockedProp } from '../../aspect-ratio' import { + getDependenciesStatus, refreshDependencies, removeModulesFromNodeModules, } from '../../../core/shared/dependencies' @@ -525,7 +528,6 @@ import { styleStringInArray } from '../../../utils/common-constants' import { collapseTextElements } from '../../../components/text-editor/text-handling' import { LayoutPropertyList, StyleProperties } from '../../inspector/common/css-utils' import { - getFromPropOrFlagComment, isUtopiaPropOrCommentFlag, makeUtopiaFlagComment, removePropOrFlagComment, @@ -620,10 +622,28 @@ import { import type { FixUIDsState } from '../../../core/workers/parser-printer/uid-fix' import { fixTopLevelElementsUIDs } from '../../../core/workers/parser-printer/uid-fix' import { nextSelectedTab } from '../../navigator/left-pane/left-pane-utils' -import { getDefaultedRemixRootDir, getRemixRootDir } from '../store/remix-derived-data' +import { getDefaultedRemixRootDir } from '../store/remix-derived-data' import { isReplaceKeepChildrenAndStyleTarget } from '../../navigator/navigator-item/component-picker-context-menu' import { canCondenseJSXElementChild } from '../../../utils/can-condense' import { getNavigatorTargetsFromEditorState } from '../../navigator/navigator-utils' +import { getParseCacheOptions } from '../../../core/shared/parse-cache-utils' +import { styleP } from '../../inspector/inspector-common' +import { + getUpdateOperationResult, + notifyOperationFinished, + notifyOperationStarted, + notifyImportStatusToDiscord, +} from '../../../core/shared/import/import-operation-service' +import { updateRequirements } from '../../../core/shared/import/project-health-check/utopia-requirements-service' +import { + applyValuesAtPath, + deleteValuesAtPath, + maybeCssPropertyFromInlineStyle, +} from '../../canvas/commands/utils/property-utils' +import type { HuggingElementContentsStatus } from '../../../components/canvas/hugging-utils' +import { getHuggingElementContentsStatus } from '../../../components/canvas/hugging-utils' +import { createStoryboardFileIfNecessary } from '../../../core/shared/import/project-health-check/requirements/requirement-storyboard' +import { setProperty } from '../../canvas/commands/set-property-command' export const MIN_CODE_PANE_REOPEN_WIDTH = 100 @@ -998,10 +1018,6 @@ export function restoreEditorState( formulaBarMode: desiredEditor.topmenu.formulaBarMode, formulaBarFocusCounter: currentEditor.topmenu.formulaBarFocusCounter, }, - preview: { - visible: currentEditor.preview.visible, - connected: currentEditor.preview.connected, - }, home: { visible: currentEditor.home.visible, }, @@ -1031,6 +1047,9 @@ export function restoreEditorState( githubSettings: currentEditor.githubSettings, imageDragSessionState: currentEditor.imageDragSessionState, githubOperations: currentEditor.githubOperations, + importState: currentEditor.importState, + projectRequirements: currentEditor.projectRequirements, + importWizardOpen: currentEditor.importWizardOpen, branchOriginContents: currentEditor.branchOriginContents, githubData: currentEditor.githubData, refreshingDependencies: currentEditor.refreshingDependencies, @@ -1043,6 +1062,7 @@ export function restoreEditorState( collaborators: currentEditor.collaborators, sharingDialogOpen: currentEditor.sharingDialogOpen, editorRemixConfig: currentEditor.editorRemixConfig, + propertiesUpdatedDuringInteraction: {}, } } @@ -1080,6 +1100,7 @@ function deleteElements( console.error(`Attempted to delete element(s) with no UI file open.`) return editor } else { + const targetStaticUIDs = targets.map(EP.toStaticUid) const updatedEditor = targets.reduce((working, targetPath) => { const underlyingTarget = normalisePathToUnderlyingTarget(working.projectContents, targetPath) @@ -1130,7 +1151,7 @@ function deleteElements( if (metadata == null || isLeft(metadata.element)) { return null } - const frame = MetadataUtils.getLocalFrame(path, editor.jsxMetadata) + const frame = MetadataUtils.getLocalFrame(path, editor.jsxMetadata, null) if (frame == null || !isFiniteRectangle(frame)) { return null } @@ -1139,6 +1160,8 @@ function deleteElements( ? right(metadata.element.value.props) : null + const children = MetadataUtils.getChildrenUnordered(editor.jsxMetadata, path) + const childrenFrame = boundingRectangleArray( mapDropNulls((child) => { @@ -1147,7 +1170,7 @@ function deleteElements( return null } return childFrame - }, MetadataUtils.getChildrenUnordered(editor.jsxMetadata, path)), + }, children), ) ?? canvasRectangle(zeroRectangle) const hasHorizontalPosition = @@ -1167,7 +1190,14 @@ function deleteElements( height: main.height !== 0 ? main.height : backup.height, }) } - return trueUpHuggingElement(path, combineFrames(canvasRectangle(frame), childrenFrame)) + const huggingElementContentsStatus: HuggingElementContentsStatus = + getHuggingElementContentsStatus(editor.jsxMetadata, path) + return trueUpHuggingElement( + path, + canvasRectangle(frame), + combineFrames(canvasRectangle(frame), childrenFrame), + huggingElementContentsStatus, + ) }, uniqBy(targets.map(EP.parentPath), EP.pathsEqual)) trueUps.push(...trueUpHuggingElements) } @@ -1583,66 +1613,6 @@ function updateCodeEditorVisibility(editor: EditorModel, codePaneVisible: boolea } } -function createStoryboardFileIfRemixProject( - projectContents: ProjectContentTreeRoot, -): ProjectContentTreeRoot | null { - const packageJsonContents = defaultEither( - null, - getPackageJsonFromProjectContents(projectContents), - ) - if (packageJsonContents == null) { - return null - } - const remixNotIncluded = packageJsonContents['dependencies']?.['@remix-run/react'] == null - if (remixNotIncluded) { - return null - } - - const updatedProjectContents = addFileToProjectContents( - projectContents, - StoryboardFilePath, - codeFile(DefaultStoryboardWithRemix, null, 1), - ) - return updatedProjectContents -} - -function createStoryboardFileIfMainComponentPresent( - projectContents: ProjectContentTreeRoot, -): ProjectContentTreeRoot | null { - return addStoryboardFileToProject(projectContents) -} - -function createStoryboardFileWithPlaceholderContents( - projectContents: ProjectContentTreeRoot, - createPlaceholder: 'create-placeholder' | 'skip-creating-placeholder', -): ProjectContentTreeRoot { - if (createPlaceholder === 'skip-creating-placeholder') { - return projectContents - } - const updatedProjectContents = addFileToProjectContents( - projectContents, - StoryboardFilePath, - codeFile(DefaultStoryboardContents, null, 1), - ) - return updatedProjectContents -} - -export function createStoryboardFileIfNecessary( - projectContents: ProjectContentTreeRoot, - createPlaceholder: 'create-placeholder' | 'skip-creating-placeholder', -): ProjectContentTreeRoot { - const storyboardFile = getProjectFileByFilePath(projectContents, StoryboardFilePath) - if (storyboardFile != null) { - return projectContents - } - - return ( - createStoryboardFileIfRemixProject(projectContents) ?? - createStoryboardFileIfMainComponentPresent(projectContents) ?? - createStoryboardFileWithPlaceholderContents(projectContents, createPlaceholder) - ) -} - // JS Editor Actions: export const UPDATE_FNS = { NEW: ( @@ -1767,50 +1737,41 @@ export const UPDATE_FNS = { }, UNSET_PROPERTY: (action: UnsetProperty, editor: EditorModel): EditorModel => { // TODO also queue group true up, just like for SET_PROP - let unsetPropFailedMessage: string | null = null - const updatedEditor = modifyUnderlyingElementForOpenFile( - action.element, - editor, - (element) => { - const updatedProps = unsetJSXValueAtPath(element.props, action.property) - return foldEither( - (failureMessage) => { - unsetPropFailedMessage = failureMessage - return element - }, - (updatedAttributes) => ({ - ...element, - props: updatedAttributes, - }), - updatedProps, - ) - }, - (success) => success, - ) - if (unsetPropFailedMessage != null) { - const toastAction = showToast(notice(unsetPropFailedMessage, 'ERROR')) - return UPDATE_FNS.ADD_TOAST(toastAction, editor) - } else { - return updatedEditor - } + // TODO this used to fire a toast if the prop couldn't be removed + return foldAndApplyCommandsSimple(editor, [ + deleteProperties('always', action.element, [action.property]), + ]) }, SET_PROP: (action: SetProp, editor: EditorModel): EditorModel => { let setPropFailedMessage: string | null = null let newSelectedViews: Array = editor.selectedViews + const prop = maybeCssPropertyFromInlineStyle(action.propertyPath) + const valueForStyleProp = + action.value.type === 'ATTRIBUTE_VALUE' && + (typeof action.value.value === 'number' || typeof action.value.value === 'string') + ? action.value.value + : null + + const editorWithPropSet = + prop == null || valueForStyleProp == null + ? applyValuesAtPath(editor, action.target, [ + { path: action.propertyPath, value: action.value }, + ]).editorStateWithChanges + : foldAndApplyCommandsSimple(editor, [ + setProperty('always', action.target, PP.create('style', prop), valueForStyleProp), + ]) + + if (isJSXElement(action.value)) { + newSelectedViews = [EP.appendToPath(action.target, action.value.uid)] + } let updatedEditor = modifyUnderlyingTargetElement( action.target, - editor, + editorWithPropSet, (element) => { if (!isJSXElement(element)) { return element } - const updatedProps = setJSXValueAtPath(element.props, action.propertyPath, action.value) - // when this is a render prop we should select it - if (isJSXElement(action.value)) { - newSelectedViews = [EP.appendToPath(action.target, action.value.uid)] - } if ( - isRight(updatedProps) && PP.contains( [ PP.create('style', 'top'), @@ -1823,8 +1784,9 @@ export const UPDATE_FNS = { action.propertyPath, ) ) { + // TODO: refactor this to read from the plugins const maybeInvalidGroupState = groupStateFromJSXElement( - { ...element, props: updatedProps.value }, + element, action.target, editor.jsxMetadata, editor.elementPathTree, @@ -1843,18 +1805,11 @@ export const UPDATE_FNS = { return element } } - return foldEither( - (failureMessage) => { - setPropFailedMessage = failureMessage - return element - }, - (updatedAttributes) => ({ - ...element, - // we round style.left/top/right/bottom/width/height pins for the modified element - props: roundAttributeLayoutValues(styleStringInArray, updatedAttributes), - }), - updatedProps, - ) + return { + ...element, + // TODO: refactor this to use commands + props: roundAttributeLayoutValues(styleStringInArray, element.props), + } }, (success, _, underlyingFilePath) => { const updatedImports = mergeImports( @@ -2230,6 +2185,46 @@ export const UPDATE_FNS = { githubOperations: operations, } }, + UPDATE_IMPORT_OPERATIONS: (action: UpdateImportOperations, editor: EditorModel): EditorModel => { + const resultImportOperations = getUpdateOperationResult( + editor.importState.importOperations, + action.operations, + action.type, + ) + return { + ...editor, + importState: { ...editor.importState, importOperations: resultImportOperations }, + } + }, + UPDATE_IMPORT_STATUS: (action: UpdateImportStatus, editor: EditorModel): EditorModel => { + const newImportState = { + ...editor.importState, + importStatus: action.importStatus, + } + // side effect ☢️ + notifyImportStatusToDiscord(newImportState, editor.projectName) + return { + ...editor, + importState: newImportState, + } + }, + UPDATE_PROJECT_REQUIREMENTS: ( + action: UpdateProjectRequirements, + editor: EditorModel, + dispatch: EditorDispatch, + ): EditorModel => { + const result = updateRequirements(dispatch, editor.projectRequirements, action.requirements) + return { + ...editor, + projectRequirements: result, + } + }, + SET_IMPORT_WIZARD_OPEN: (action: SetImportWizardOpen, editor: EditorModel): EditorModel => { + return { + ...editor, + importWizardOpen: action.open, + } + }, SET_REFRESHING_DEPENDENCIES: ( action: SetRefreshingDependencies, editor: EditorModel, @@ -2964,14 +2959,6 @@ export const UPDATE_FNS = { visible: action.visible, }, } - case 'preview': - return { - ...editor, - preview: { - ...editor.preview, - visible: action.visible, - }, - } case 'codeEditor': return { ...editor, @@ -3067,14 +3054,6 @@ export const UPDATE_FNS = { visible: !editor.inspector.visible, }, } - case 'preview': - return { - ...editor, - preview: { - ...editor.preview, - visible: !editor.preview.visible, - }, - } case 'projectsettings': return { ...editor, @@ -3333,7 +3312,7 @@ export const UPDATE_FNS = { }, RESET_PINS: (action: ResetPins, editor: EditorModel): EditorModel => { const target = action.target - const frame = MetadataUtils.getLocalFrame(target, editor.jsxMetadata) + const frame = MetadataUtils.getLocalFrame(target, editor.jsxMetadata, null) if (frame == null || isInfinityRectangle(frame)) { return editor @@ -3361,7 +3340,7 @@ export const UPDATE_FNS = { } }, UPDATE_FRAME_DIMENSIONS: (action: UpdateFrameDimensions, editor: EditorModel): EditorModel => { - const initialFrame = MetadataUtils.getLocalFrame(action.element, editor.jsxMetadata) + const initialFrame = MetadataUtils.getLocalFrame(action.element, editor.jsxMetadata, null) if (initialFrame == null || isInfinityRectangle(initialFrame)) { return editor @@ -3741,12 +3720,6 @@ export const UPDATE_FNS = { projectDescription: action.description, } }, - - UPDATE_PREVIEW_CONNECTED: (action: UpdatePreviewConnected, editor: EditorModel): EditorModel => { - return produce(editor, (editorState) => { - editorState.preview.connected = action.connected - }) - }, ALIGN_SELECTED_VIEWS: (action: AlignSelectedViews, editor: EditorModel): EditorModel => { return alignOrDistributeSelectedViews(editor, action.alignment) }, @@ -3761,9 +3734,6 @@ export const UPDATE_FNS = { openMenu(action.menuName, action.event) return editor }, - SEND_PREVIEW_MODEL: (action: SendPreviewModel, editor: EditorModel): EditorModel => { - return editor - }, UPDATE_FILE_PATH: ( action: UpdateFilePath, editor: EditorModel, @@ -3973,6 +3943,7 @@ export const UPDATE_FNS = { action: UpdateFromWorker, editor: EditorModel, userState: UserState, + dispatch: EditorDispatch, ): EditorModel => { let workingProjectContents: ProjectContentTreeRoot = editor.projectContents let anyParsedUpdates: boolean = false @@ -4008,13 +3979,12 @@ export const UPDATE_FNS = { } return { ...editor, - projectContents: createStoryboardFileIfNecessary( - workingProjectContents, - // If we are in the process of cloning a Github repository, do not create placeholder Storyboard + projectContents: + // If we are in the process of cloning a Github repository, do not create storyboard + // it will be created in the requirements check phase userState.githubState.gitRepoToLoad != null - ? 'skip-creating-placeholder' - : 'create-placeholder', - ), + ? workingProjectContents + : createStoryboardFileIfNecessary(workingProjectContents), canvas: { ...editor.canvas, canvasContentInvalidateCount: anyParsedUpdates @@ -4136,13 +4106,13 @@ export const UPDATE_FNS = { getAllUniqueUidsFromMapping(getUidMappings(editor.projectContents).filePathToUids), ) const parsedResult = getParseFileResult( - newFileName, - getFilePathMappings(editor.projectContents), - templateFile.fileContents.code, - null, - 1, - existingUIDs, - isSteganographyEnabled(), + createParseFile(newFileName, templateFile.fileContents.code, null, 1), + createParseAndPrintOptions( + getFilePathMappings(editor.projectContents), + existingUIDs, + isSteganographyEnabled(), + getParseCacheOptions(), + ), ) // 3. write the new text file @@ -4834,7 +4804,11 @@ export const UPDATE_FNS = { propertyControlsInfo: action.propertyControlsInfo, } }, - UPDATE_TEXT: (action: UpdateText, editorStore: EditorStoreUnpatched): EditorStoreUnpatched => { + UPDATE_TEXT: ( + action: UpdateText, + editorStore: EditorStoreUnpatched, + dispatch: EditorDispatch, + ): EditorStoreUnpatched => { const { textProp } = action // This flag is useful when editing conditional expressions: // if the edited element is a js expression AND the content is still between curly brackets after editing, @@ -5003,13 +4977,18 @@ export const UPDATE_FNS = { const workerUpdates = filesToUpdateResult.filesToUpdate.flatMap((fileToUpdate) => { if (fileToUpdate.type === 'printandreparsefile') { const printParsedContent = getPrintAndReparseCodeResult( - fileToUpdate.filename, - filePathMappings, - fileToUpdate.parseSuccess, - fileToUpdate.stripUIDs, - fileToUpdate.versionNumber, - filesToUpdateResult.existingUIDs, - isSteganographyEnabled(), + createPrintAndReparseFile( + fileToUpdate.filename, + fileToUpdate.parseSuccess, + fileToUpdate.stripUIDs, + fileToUpdate.versionNumber, + ), + createParseAndPrintOptions( + filePathMappings, + filesToUpdateResult.existingUIDs, + isSteganographyEnabled(), + getParseCacheOptions(), + ), ) const updateAction = workerCodeAndParsedUpdate( printParsedContent.filename, @@ -5027,6 +5006,7 @@ export const UPDATE_FNS = { updateFromWorker(workerUpdates), withFileChanges.unpatchedEditor, withFileChanges.userState, + dispatch, ) return { ...withFileChanges, @@ -5537,6 +5517,7 @@ export const UPDATE_FNS = { const localFrame = MetadataUtils.getLocalFrame( action.insertionPath.intendedParentPath, editor.jsxMetadata, + null, ) if (group != null && localFrame != null) { switch (element.type) { @@ -5641,7 +5622,7 @@ export const UPDATE_FNS = { requestedNpmDependency('tailwindcss', tailwindVersion.version), requestedNpmDependency('postcss', postcssVersion.version), ] - void fetchNodeModules(updatedNpmDeps, builtInDependencies).then( + void fetchNodeModules(dispatch, updatedNpmDeps, builtInDependencies).then( (fetchNodeModulesResult) => { const loadedPackagesStatus = createLoadedPackageStatusMapFromDependencies( updatedNpmDeps, @@ -5765,7 +5746,11 @@ export const UPDATE_FNS = { editor: EditorModel, builtInDependencies: BuiltInDependencies, ): EditorModel => { - const canvasState = pickCanvasStateFromEditorState(editor, builtInDependencies) + const canvasState = pickCanvasStateFromEditorState( + editor.selectedViews, + editor, + builtInDependencies, + ) if (areAllSelectedElementsNonAbsolute(action.targets, editor.jsxMetadata)) { const commands = getEscapeHatchCommands( action.targets, @@ -5779,9 +5764,6 @@ export const UPDATE_FNS = { return editor } }, - SET_ELEMENTS_TO_RERENDER: (action: SetElementsToRerender, editor: EditorModel): EditorModel => { - return foldAndApplyCommandsSimple(editor, [setElementsToRerenderCommand(action.value)]) - }, TOGGLE_SELECTION_LOCK: (action: ToggleSelectionLock, editor: EditorModel): EditorModel => { const targets = action.targets return targets.reduce((working, target) => { @@ -6180,59 +6162,32 @@ export function alignOrDistributeSelectedViews( .map(trueUpGroupElementChanged), ] - if (selectedViews.length > 0) { - // this array of canvasFrames excludes the non-layoutables. it means in a multiselect, they will not be considered - const canvasFrames: Array<{ - target: ElementPath - frame: CanvasRectangle - }> = Utils.stripNulls( - selectedViews.map((target) => { - const instanceGlobalFrame = MetadataUtils.getFrameInCanvasCoords(target, editor.jsxMetadata) - if (instanceGlobalFrame == null || isInfinityRectangle(instanceGlobalFrame)) { - return null - } else { - return { - target: target, - frame: instanceGlobalFrame, - } - } - }), - ) + let workingEditorState = { ...editor } - if (canvasFrames.length > 0) { - const parentPath = EP.parentPath(selectedViews[0]) - const sourceIsParent = selectedViews.length === 1 && parentPath != null - let source: CanvasRectangle - if (sourceIsParent) { - const parentFrame = MetadataUtils.getFrameInCanvasCoords(parentPath, editor.jsxMetadata) + const targetAlignment = isAlignment(alignmentOrDistribution) + if (targetAlignment) { + workingEditorState = alignFlexOrGridChildren( + workingEditorState, + selectedViews.filter((v) => + MetadataUtils.isFlexOrGridChild(workingEditorState.jsxMetadata, v), + ), + alignmentOrDistribution, + ) + } - // if the parent frame is null, that means we probably ran into some error state, - // as it means the child's globalFrame should also be null, so we shouldn't be in this branch - const maybeSource = Utils.forceNotNull( - `found no parent global frame for ${EP.toComponentId(parentPath!)}`, - parentFrame, - ) + workingEditorState = alignFlowChildren( + workingEditorState, + selectedViews.filter( + (v) => + !targetAlignment || !MetadataUtils.isFlexOrGridChild(workingEditorState.jsxMetadata, v), + ), + alignmentOrDistribution, + ) - // If the parent frame is infinite, fall back to using the selected element's frame - source = isInfinityRectangle(maybeSource) ? canvasFrames[0].frame : maybeSource - } else { - source = Utils.boundingRectangleArray(Utils.pluck(canvasFrames, 'frame'))! // I know this can't be null because we checked the canvasFrames array is non-empty - } - const updatedCanvasFrames = alignOrDistributeCanvasRects( - editor.jsxMetadata, - canvasFrames, - source, - alignmentOrDistribution, - sourceIsParent, - ) - return { - ...setCanvasFramesInnerNew(editor, updatedCanvasFrames, null), - trueUpElementsAfterDomWalkerRuns: groupTrueUps, - } - } + return { + ...workingEditorState, + trueUpElementsAfterDomWalkerRuns: groupTrueUps, } - - return editor } function alignOrDistributeCanvasRects( @@ -6342,8 +6297,7 @@ function alignOrDistributeCanvasRects( break } default: - const _exhaustiveCheck: never = alignmentOrDistribution - throw new Error('Something went really wrong.') + assertNever(alignmentOrDistribution) } return results @@ -6368,7 +6322,10 @@ export async function load( // this action is now async! const migratedModel = applyMigrations(model) const npmDependencies = dependenciesWithEditorRequirements(migratedModel.projectContents) + // side effect ☢️ + notifyOperationStarted(dispatch, { type: 'refreshDependencies' }) const fetchNodeModulesResult = await fetchNodeModules( + dispatch, npmDependencies, builtInDependencies, retryFetchNodeModules, @@ -6381,6 +6338,13 @@ export async function load( fetchNodeModulesResult.dependenciesNotFound, ) + // side effect ☢️ + notifyOperationFinished( + dispatch, + { type: 'refreshDependencies' }, + getDependenciesStatus(packageResult), + ) + const codeResultCache: CodeResultCache = generateCodeResultCache( // TODO is this sufficient here? migratedModel.projectContents, @@ -6414,10 +6378,6 @@ export async function load( ) } -export function isSendPreviewModel(action: any): action is SendPreviewModel { - return action != null && (action as SendPreviewModel).action === 'SEND_PREVIEW_MODEL' -} - function saveFileInProjectContents( projectContents: ProjectContentTreeRoot, filePath: string, @@ -6430,61 +6390,6 @@ function saveFileInProjectContents( } } -const DefaultStoryboardWithRemix = `import * as React from 'react' -import { Storyboard, RemixScene } from 'utopia-api' - -export var storyboard = ( - - - -) -` - -const DefaultStoryboardContents = `import * as React from 'react' -import { Scene, Storyboard } from 'utopia-api' - -export var storyboard = ( - - - - Open the insert menu or press the + button in the - toolbar to insert components - - - - ) -` - function addTextFile( editor: EditorState, parentPath: string, @@ -6607,3 +6512,154 @@ function removeErrorMessagesForFile(editor: EditorState, filename: string): Edit ), ) } + +function alignFlexOrGridChildren(editor: EditorState, views: ElementPath[], alignment: Alignment) { + let workingEditorState = { ...editor } + for (const view of views) { + // When updating alongside the given alignment, also update the opposite one so that it makes sense: + // For example, if alignment is 'alignSelf', delete the 'justifySelf' if currently set to stretch and, if so, + // set the explicit height of the element (and vice versa for 'justifySelf'). + function updateOpposite( + editorState: EditorState, + frame: MaybeInfinityCanvasRectangle | null, + target: 'alignSelf' | 'justifySelf', + dimension: 'width' | 'height', + ) { + let working = { ...editorState } + + working = deleteValuesAtPath(working, view, [styleP(target)]).editorStateWithChanges + + working = applyValuesAtPath(working, view, [ + { + path: styleP(dimension), + value: jsExpressionValue(zeroRectIfNullOrInfinity(frame)[dimension], emptyComments), + }, + ]).editorStateWithChanges + + return working + } + + function apply(editorState: EditorState, prop: 'alignSelf' | 'justifySelf', value: string) { + const element = MetadataUtils.findElementByElementPath(editor.jsxMetadata, view) + if (element == null || isLeft(element.element) || !isJSXElement(element.element.value)) { + return workingEditorState + } + + let working = editorState + + working = applyValuesAtPath(working, view, [ + { path: PP.create('style', prop), value: jsExpressionValue(value, emptyComments) }, + ]).editorStateWithChanges + + const alignSelfStretch = + getSimpleAttributeAtPath(right(element.element.value.props), styleP('alignSelf')).value === + 'stretch' + const justifySelfStretch = + getSimpleAttributeAtPath(right(element.element.value.props), styleP('justifySelf')) + .value === 'stretch' + + if (prop === 'alignSelf' && justifySelfStretch) { + working = updateOpposite(working, element.globalFrame, 'justifySelf', 'height') + } else if (prop === 'justifySelf' && alignSelfStretch) { + working = updateOpposite(working, element.globalFrame, 'alignSelf', 'width') + } + return working + } + + const { align, justify } = MetadataUtils.getRelativeAlignJustify( + workingEditorState.jsxMetadata, + view, + ) + + switch (alignment) { + case 'bottom': + workingEditorState = apply(workingEditorState, align, 'end') + break + case 'top': + workingEditorState = apply(workingEditorState, align, 'start') + break + case 'vcenter': + workingEditorState = apply(workingEditorState, align, 'center') + break + case 'hcenter': + workingEditorState = apply(workingEditorState, justify, 'center') + break + case 'left': + workingEditorState = apply(workingEditorState, justify, 'start') + break + case 'right': + workingEditorState = apply(workingEditorState, justify, 'end') + break + default: + assertNever(alignment) + } + } + + return workingEditorState +} + +function alignFlowChildren( + editor: EditorState, + views: ElementPath[], + alignmentOrDistribution: Alignment | Distribution, +) { + let workingEditorState = { ...editor } + + if (views.length > 0) { + // this array of canvasFrames excludes the non-layoutables. it means in a multiselect, they will not be considered + const canvasFrames: Array<{ + target: ElementPath + frame: CanvasRectangle + }> = Utils.stripNulls( + views.map((target) => { + const instanceGlobalFrame = MetadataUtils.getFrameInCanvasCoords( + target, + workingEditorState.jsxMetadata, + ) + if (instanceGlobalFrame == null || isInfinityRectangle(instanceGlobalFrame)) { + return null + } else { + return { + target: target, + frame: instanceGlobalFrame, + } + } + }), + ) + + if (canvasFrames.length > 0) { + const parentPath = EP.parentPath(views[0]) + const sourceIsParent = views.length === 1 && parentPath != null + let source: CanvasRectangle + if (sourceIsParent) { + const parentFrame = MetadataUtils.getFrameInCanvasCoords( + parentPath, + workingEditorState.jsxMetadata, + ) + + // if the parent frame is null, that means we probably ran into some error state, + // as it means the child's globalFrame should also be null, so we shouldn't be in this branch + const maybeSource = Utils.forceNotNull( + `found no parent global frame for ${EP.toComponentId(parentPath!)}`, + parentFrame, + ) + + // If the parent frame is infinite, fall back to using the selected element's frame + source = isInfinityRectangle(maybeSource) ? canvasFrames[0].frame : maybeSource + } else { + source = Utils.boundingRectangleArray(Utils.pluck(canvasFrames, 'frame'))! // I know this can't be null because we checked the canvasFrames array is non-empty + } + const updatedCanvasFrames = alignOrDistributeCanvasRects( + workingEditorState.jsxMetadata, + canvasFrames, + source, + alignmentOrDistribution, + sourceIsParent, + ) + workingEditorState = { + ...setCanvasFramesInnerNew(workingEditorState, updatedCanvasFrames, null), + } + } + } + return workingEditorState +} diff --git a/editor/src/components/editor/canvas-toolbar-states.tsx b/editor/src/components/editor/canvas-toolbar-states.tsx index 091326327fbe..ed410bd250f9 100644 --- a/editor/src/components/editor/canvas-toolbar-states.tsx +++ b/editor/src/components/editor/canvas-toolbar-states.tsx @@ -8,6 +8,9 @@ import type { Optic } from '../../core/shared/optics/optics' import { fromField, fromTypeGuard } from '../../core/shared/optics/optic-creators' import { anyBy, set } from '../../core/shared/optics/optic-utilities' import type { EditorAction } from './action-types' +import { MetadataUtils } from '../../core/model/element-metadata-utils' +import { getJSXAttributesAtPath } from '../../core/shared/jsx-attribute-utils' +import { create } from '../../core/shared/property-path' // This is the data structure that governs the Canvas Toolbar's submenus and active buttons type ToolbarMode = @@ -21,6 +24,7 @@ type ToolbarMode = imageInsertionActive: boolean buttonInsertionActive: boolean conditionalInsertionActive: boolean + gridInsertionActive: boolean insertSidebarOpen: boolean } } @@ -110,6 +114,28 @@ export function useToolbarMode(): ToolbarMode { const insertionTargetConditional = editorMode.type === 'insert' && editorMode.subjects.some((subject) => subject.insertionSubjectWrapper === 'conditional') + const insertionTargetGrid = + editorMode.type === 'insert' && + editorMode.subjects.some((subject) => { + if (subject.element.name.baseVariable !== 'div') { + return false + } + + const style = subject.element.props.find( + (p) => p.type === 'JSX_ATTRIBUTES_ENTRY' && p.key === 'style', + ) + if (style == null) { + return false + } + + const display = getJSXAttributesAtPath(subject.element.props, create('style', 'display')) + return ( + style.type === 'JSX_ATTRIBUTES_ENTRY' && + style.value.type === 'ATTRIBUTE_VALUE' && + display.attribute.type === 'PART_OF_ATTRIBUTE_VALUE' && + display.attribute.value === 'grid' + ) + }) return { primary: 'insert', @@ -119,6 +145,7 @@ export function useToolbarMode(): ToolbarMode { imageInsertionActive: insertionTargetImage, buttonInsertionActive: insertionTargetButton, conditionalInsertionActive: insertionTargetConditional, + gridInsertionActive: insertionTargetGrid, insertSidebarOpen: rightMenuTab === RightMenuTab.Insert, }, } diff --git a/editor/src/components/editor/canvas-toolbar.tsx b/editor/src/components/editor/canvas-toolbar.tsx index d7c42dcc9670..c19eb1cbee9f 100644 --- a/editor/src/components/editor/canvas-toolbar.tsx +++ b/editor/src/components/editor/canvas-toolbar.tsx @@ -28,6 +28,7 @@ import { useEnterDrawToInsertForButton, useEnterDrawToInsertForConditional, useEnterDrawToInsertForDiv, + useEnterDrawToInsertForGrid, useEnterDrawToInsertForImage, useEnterTextEditMode, } from './insert-callbacks' @@ -72,12 +73,14 @@ import { keyToString, shortcutDetailsWithDefaults, } from './shortcut-definitions' +import { LowPriorityStoreProvider } from './store/store-context-providers' export const InsertMenuButtonTestId = 'insert-menu-button' export const InsertOrEditTextButtonTestId = 'insert-or-edit-text-button' export const PlayModeButtonTestId = 'canvas-toolbar-play-mode' export const CommentModeButtonTestId = (status: string) => `canvas-toolbar-comment-mode-${status}` export const InsertConditionalButtonTestId = 'insert-mode-conditional' +export const InsertGridButtonTestId = 'insert-mode-grid' export const CanvasToolbarId = 'canvas-toolbar' export const CanvasToolbarSearchPortalId = 'canvas-toolbar-search-portal' @@ -219,6 +222,7 @@ export const CanvasToolbar = React.memo(() => { const insertTextCallback = useEnterTextEditMode() const insertButtonCallback = useEnterDrawToInsertForButton() const insertConditionalCallback = useEnterDrawToInsertForConditional() + const insertGridCallback = useEnterDrawToInsertForGrid() // Back to select mode, close the "floating" menu and turn off the forced insert mode. const dispatchSwitchToSelectModeCloseMenus = React.useCallback(() => { @@ -530,7 +534,7 @@ export const CanvasToolbar = React.memo(() => { {/* Insert Mode */} {canvasToolbarMode.primary === 'insert' ? wrapInSubmenu( - + { + + + @@ -579,7 +600,9 @@ export const CanvasToolbar = React.memo(() => { ) : null} {/* Live Mode */} - {showRemixNavBar ? wrapInSubmenu() : null} + + {showRemixNavBar ? wrapInSubmenu() : null} + ) }) diff --git a/editor/src/components/editor/conditionals.spec.browser2.tsx b/editor/src/components/editor/conditionals.spec.browser2.tsx index 735968b12e6b..3b48b06eb47f 100644 --- a/editor/src/components/editor/conditionals.spec.browser2.tsx +++ b/editor/src/components/editor/conditionals.spec.browser2.tsx @@ -264,7 +264,7 @@ describe('conditionals', () => { expect(EP.isParentOf(conditionalPath, selectedViews[0])).toBe(true) expect(EP.toUid(selectedViews[0])).not.toBe('ccc') }) - xit('keeps the selection on the null branch (multiple targets)', async () => { + it('keeps the selection on the null branch (multiple targets)', async () => { const startSnippet = `
{ @@ -336,7 +336,7 @@ describe('conditionals', () => { null ) } -
+
`), ) diff --git a/editor/src/components/editor/defaults.ts b/editor/src/components/editor/defaults.ts index 222c4723733d..d3584072378b 100644 --- a/editor/src/components/editor/defaults.ts +++ b/editor/src/components/editor/defaults.ts @@ -12,7 +12,7 @@ import { simpleAttribute, } from '../../core/shared/element-template' import type { NormalisedFrame } from 'utopia-api/core' -import { defaultImageAttributes } from '../shared/project-components' +import { defaultImageAttributes, insertableGridStyle } from '../shared/project-components' export function defaultSceneElement( uid: string, @@ -98,11 +98,12 @@ export function defaultRectangleElementStyle(): JSExpression { export function defaultRectangleElement(uid: string): JSXElement { return jsxElement( - jsxElementName('Rectangle', []), + jsxElementName('div', []), uid, jsxAttributesFromMap({ style: defaultRectangleElementStyle(), 'data-uid': jsExpressionValue(uid, emptyComments), + 'data-label': jsExpressionValue('Rectangle', emptyComments), }), [], ) @@ -171,6 +172,18 @@ export function defaultButtonElement(uid: string): JSXElement { ) } +export function defaultGridElement(uid: string): JSXElement { + return jsxElement( + jsxElementName('div', []), + uid, + jsxAttributesFromMap({ + 'data-uid': jsExpressionValue(uid, emptyComments), + style: jsExpressionValue(insertableGridStyle(), emptyComments), + }), + [], + ) +} + export function defaultFlexRowOrColStyle(): JSExpression { return jsExpressionValue( { diff --git a/editor/src/components/editor/editor-component.tsx b/editor/src/components/editor/editor-component.tsx index c87d105ed4d7..a712edca6ee0 100644 --- a/editor/src/components/editor/editor-component.tsx +++ b/editor/src/components/editor/editor-component.tsx @@ -39,7 +39,6 @@ import { ConfirmDeleteDialog } from '../filebrowser/confirm-delete-dialog' import { ConfirmOverwriteDialog } from '../filebrowser/confirm-overwrite-dialog' import { ConfirmRevertDialog } from '../filebrowser/confirm-revert-dialog' import { ConfirmRevertAllDialog } from '../filebrowser/confirm-revert-all-dialog' -import { PreviewColumn } from '../preview/preview-pane' import * as EditorActions from './actions/action-creators' import { FatalIndexedDBErrorComponent } from './fatal-indexeddb-error-component' import { editorIsTarget, handleKeyDown, handleKeyUp } from './global-shortcuts' @@ -97,6 +96,7 @@ import { navigatorTargetsSelector, navigatorTargetsSelectorNavigatorTargets, } from '../navigator/navigator-utils' +import { ImportWizard } from './import-wizard/import-wizard' const liveModeToastId = 'play-mode-toast' @@ -377,12 +377,6 @@ export const EditorComponentInner = React.memo((props: EditorProps) => { (store) => store.editor.id, 'EditorComponentInner projectId', ) - const previewVisible = useEditorState( - Substores.restOfEditor, - (store) => store.editor.preview.visible, - 'EditorComponentInner previewVisible', - ) - const yDoc = useEditorState( Substores.restOfStore, (store) => store.collaborativeEditingSupport.session?.mergeDoc, @@ -417,11 +411,6 @@ export const EditorComponentInner = React.memo((props: EditorProps) => { } }, [projectName, projectId, forking]) - const onClosePreview = React.useCallback( - () => dispatch([EditorActions.setPanelVisibility('preview', false)]), - [dispatch], - ) - const startDragInsertion = React.useCallback( (event: React.DragEvent) => { const draggedTypes = event.nativeEvent?.dataTransfer?.types @@ -524,37 +513,6 @@ export const EditorComponentInner = React.memo((props: EditorProps) => { {/* insert more columns here */} - - {previewVisible ? ( - - - } - onClose={onClosePreview} - /> - - - - ) : null} @@ -563,6 +521,7 @@ export const EditorComponentInner = React.memo((props: EditorProps) => { + {portalTarget != null ? ReactDOM.createPortal(, portalTarget) @@ -686,6 +645,7 @@ export const ToastRenderer = React.memo(() => { return ( { 'LockedOverlay refreshingDependencies', ) + const importWizardOpen = useEditorState( + Substores.restOfEditor, + (store) => store.editor.importWizardOpen, + 'LockedOverlay importWizardOpen', + ) + const forking = useEditorState( Substores.restOfEditor, (store) => store.editor.forking, @@ -742,8 +708,8 @@ const LockedOverlay = React.memo(() => { ` const locked = React.useMemo(() => { - return editorLocked || refreshingDependencies || forking - }, [editorLocked, refreshingDependencies, forking]) + return (editorLocked || refreshingDependencies || forking) && !importWizardOpen + }, [editorLocked, refreshingDependencies, forking, importWizardOpen]) const dialogContent = React.useMemo((): string | null => { if (refreshingDependencies) { diff --git a/editor/src/components/editor/global-shortcuts.tsx b/editor/src/components/editor/global-shortcuts.tsx index 2c6c4e21bbe1..d752adab41a5 100644 --- a/editor/src/components/editor/global-shortcuts.tsx +++ b/editor/src/components/editor/global-shortcuts.tsx @@ -653,9 +653,7 @@ export function handleKeyDown( EditorActions.enableInsertModeForJSXElement( defaultRectangleElement(newUID), newUID, - { - 'utopia-api': importDetails(null, [importAlias('Rectangle')], null), - }, + {}, null, ), modifiers, @@ -963,6 +961,7 @@ export function handleKeyDown( editor.allElementProps, editor.elementPathTree, editor.selectedViews, + { scale: editor.canvas.scale, offset: editor.canvas.realCanvasOffset }, ) if (commands.length === 0) { diff --git a/editor/src/components/editor/import-wizard/components.tsx b/editor/src/components/editor/import-wizard/components.tsx new file mode 100644 index 000000000000..d23b51c3b26d --- /dev/null +++ b/editor/src/components/editor/import-wizard/components.tsx @@ -0,0 +1,248 @@ +/** @jsxRuntime classic */ +/** @jsx jsx */ +import { jsx } from '@emotion/react' +import React from 'react' +import type { + ImportFetchDependency, + ImportOperation, +} from '../../../core/shared/import/import-operation-types' +import { ImportOperationResult } from '../../../core/shared/import/import-operation-types' +import { Icn, Icons, useColorTheme } from '../../../uuiui' +import { GithubSpinner } from '../../../components/navigator/left-pane/github-pane/github-spinner' +import { getImportOperationTextAsJsx } from './import-wizard-helpers' + +export function OperationLine({ operation }: { operation: ImportOperation }) { + const operationRunningStatus = React.useMemo(() => { + if (operation.timeDone != null) { + return 'done' + } + return operation.timeStarted == null ? 'waiting' : 'running' + }, [operation.timeStarted, operation.timeDone]) + const colorTheme = useColorTheme() + const textColor = operationRunningStatus === 'waiting' ? 'gray' : colorTheme.fg0.value + + const [childrenShown, serChildrenShown] = React.useState(false) + const shouldShowChildren = React.useMemo( + () => + childrenShown || + operation.timeDone == null || + operation.result != ImportOperationResult.Success, + [childrenShown, operation.timeDone, operation.result], + ) + const hasChildren = React.useMemo( + () => operation.children != null && operation.children.length > 0, + [operation.children], + ) + const toggleShowChildren = React.useCallback(() => { + if (hasChildren) { + serChildrenShown((shown) => !shown) + } + }, [hasChildren]) + + return ( + + + +
{getImportOperationTextAsJsx(operation)}
+
+ +
+ {hasChildren ? ( +
+ {shouldShowChildren ? : } +
+ ) : null} +
+ {shouldShowChildren && hasChildren ? ( +
+ +
+ ) : null} +
+ ) +} + +function OperationChildrenList({ operation }: { operation: ImportOperation }) { + if (operation.children == null || operation.children.length === 0) { + return null + } + // this is a special case where we don't list all of the children + // but we collapse the successful ones to a single line + if (operation.type === 'refreshDependencies') { + return ( + + ) + } + // otherwise, we list all of the children + return ( + + {operation.children?.map((childOperation) => ( + + ))} + + ) +} + +const dependenciesSuccessFn = (op: ImportFetchDependency) => + op.result === ImportOperationResult.Success +const dependenciesSuccessTextFn = (successCount: number) => + `${successCount} dependencies fetched successfully` + +function AggregatedChildrenStatus({ + childOperations, + successFn, + successTextFn, +}: { + childOperations: T[] + successFn: (operation: T) => boolean + successTextFn: (successCount: number) => string +}) { + const colorTheme = useColorTheme() + const doneDependencies = childOperations.filter(successFn) + const restOfDependencies = childOperations.filter((op) => !successFn(op)) + return ( + + {doneDependencies.length > 0 ? ( + + + +
{successTextFn(doneDependencies.length)}
+
+
+ ) : null} + {restOfDependencies.map((operation) => ( + + ))} +
+ ) +} + +function OperationIcon({ + runningStatus, + result, +}: { + runningStatus: 'waiting' | 'running' | 'done' + result?: ImportOperationResult +}) { + if (runningStatus === 'running') { + return + } else if (runningStatus === 'done' && result === 'success') { + return + } else if (runningStatus === 'done' && result === 'warn') { + return + } else if (runningStatus === 'waiting') { + return + } else { + return + } +} + +function TimeFromInSeconds({ + operation, + runningStatus, +}: { + operation: ImportOperation + runningStatus: 'waiting' | 'running' | 'done' +}) { + const colorTheme = useColorTheme() + const [currentTime, setCurrentTime] = React.useState(Date.now()) + React.useEffect(() => { + const interval = setInterval(() => { + setCurrentTime(Date.now()) + }, 1000) + return () => clearInterval(interval) + }, []) + const operationTime = React.useMemo(() => { + if (operation.timeStarted == null) { + return 0 + } + if (operation.timeDone == null) { + return currentTime - operation.timeStarted + } + return operation.timeDone - operation.timeStarted + }, [operation.timeStarted, operation.timeDone, currentTime]) + const timeInSeconds = + operation.timeDone != null + ? (operationTime / 1000).toFixed(2) + : Math.max(Math.floor(operationTime / 1000), 0) + return operation.timeStarted == null ? null : ( +
+ {timeInSeconds}s +
+ ) +} + +function OperationLineWrapper({ + children, + className, + onClick, +}: { + children: React.ReactNode + className: string + onClick?: () => void +}) { + return ( +
&': { + paddingLeft: 26, + }, + '.import-wizard-operation-children .operation-done [data-short-time=true]': { + visibility: 'hidden', + }, + }} + onClick={onClick} + > + {children} +
+ ) +} + +function OperationLineContent({ + children, + textColor, +}: { + children: React.ReactNode + textColor: string +}) { + return ( +
+ {children} +
+ ) +} diff --git a/editor/src/components/editor/import-wizard/import-wizard-helpers.tsx b/editor/src/components/editor/import-wizard/import-wizard-helpers.tsx new file mode 100644 index 000000000000..226649100353 --- /dev/null +++ b/editor/src/components/editor/import-wizard/import-wizard-helpers.tsx @@ -0,0 +1,49 @@ +import * as React from 'react' +import { + ImportOperationResult, + type ImportOperation, +} from '../../../core/shared/import/import-operation-types' +import { assertNever } from '../../../core/shared/utils' + +export function getImportOperationText(operation: ImportOperation): string { + if (operation.text != null) { + return operation.text + } + switch (operation.type) { + case 'loadBranch': + const action = + operation.result == ImportOperationResult.Error || + operation.result == ImportOperationResult.CriticalError + ? 'Error Fetching' + : 'Fetching' + if (operation.branchName != null) { + return `${action} branch **${operation.githubRepo?.owner}/${operation.githubRepo?.repository}@${operation.branchName}**` + } else { + return `${action} repository **${operation.githubRepo?.owner}/${operation.githubRepo?.repository}**` + } + case 'fetchDependency': + return `Fetching ${operation.dependencyName}@${operation.dependencyVersion}` + case 'parseFiles': + return 'Parsing files' + case 'refreshDependencies': + return 'Fetching dependencies' + case 'checkRequirementsPreParse': + return 'Validating code' + case 'checkRequirementsPostParse': + return 'Checking Utopia requirements' + case 'checkRequirementAndFixPreParse': + return operation.text + case 'checkRequirementAndFixPostParse': + return operation.text + default: + assertNever(operation) + } +} + +export function getImportOperationTextAsJsx(operation: ImportOperation): React.ReactNode { + const text = getImportOperationText(operation) + const nodes = text.split('**').map((part, index) => { + return index % 2 == 0 ? part : {part} + }) + return {nodes} +} diff --git a/editor/src/components/editor/import-wizard/import-wizard.tsx b/editor/src/components/editor/import-wizard/import-wizard.tsx new file mode 100644 index 000000000000..8c5c54b76c86 --- /dev/null +++ b/editor/src/components/editor/import-wizard/import-wizard.tsx @@ -0,0 +1,324 @@ +/** @jsxRuntime classic */ +/** @jsx jsx */ +import React from 'react' +import { jsx } from '@emotion/react' +import { getProjectID } from '../../../common/env-vars' +import { Button, FlexRow, useColorTheme, UtopiaStyles } from '../../../uuiui' +import { useEditorState, Substores } from '../store/store-hook' +import { unless, when } from '../../../utils/react-conditionals' +import { + getTotalImportStatusAndResult, + hideImportWizard, + notifyImportStatusToDiscord, + updateProjectImportStatus, +} from '../../../core/shared/import/import-operation-service' +import { OperationLine } from './components' +import type { TotalImportResult } from '../../../core/shared/import/import-operation-types' +import { ImportOperationResult } from '../../../core/shared/import/import-operation-types' +import { assertNever } from '../../../core/shared/utils' +import { useDispatch } from '../store/dispatch-context' +import { + setImportWizardOpen, + setLeftMenuTab, + updateGithubSettings, +} from '../actions/action-creators' +import { emptyGithubSettings, LeftMenuTab } from '../store/editor-state' + +export const ImportWizard = React.memo(() => { + const colorTheme = useColorTheme() + const projectId = getProjectID() + + const importWizardOpen: boolean = useEditorState( + Substores.restOfEditor, + (store) => store.editor.importWizardOpen, + 'ImportWizard importWizardOpen', + ) + + const importState = useEditorState( + Substores.github, + (store) => store.editor.importState, + 'ImportWizard importState', + ) + + const operations = importState.importOperations + + const stopPropagation = React.useCallback((e: React.MouseEvent) => { + e.stopPropagation() + }, []) + + if (projectId == null) { + return null + } + + return ( +
+ {when( + importWizardOpen, +
+ +
+ +
+ {operations.map((operation) => ( + + ))} +
+
+ +
+
, + )} +
+ ) +}) +ImportWizard.displayName = 'ImportWizard' + +function ActionButtons() { + const importState = useEditorState( + Substores.github, + (store) => store.editor.importState, + 'ImportWizard importState', + ) + + const projectName = useEditorState( + Substores.restOfEditor, + (store) => store.editor.projectName, + 'ImportWizard projectName', + ) + + const importResult: TotalImportResult = React.useMemo( + () => getTotalImportStatusAndResult(importState), + [importState], + ) + const colorTheme = useColorTheme() + const dispatch = useDispatch() + const hideWizard = React.useCallback(() => { + hideImportWizard(dispatch) + }, [dispatch]) + const continueAnyway = React.useCallback(() => { + if (importResult.importStatus.status === 'done') { + hideWizard() + } + if (importResult.importStatus.status === 'paused') { + updateProjectImportStatus(dispatch, { + status: 'in-progress', + }) + importResult.importStatus.onResume() + } + }, [dispatch, hideWizard, importResult.importStatus]) + const importADifferentProject = React.useCallback(() => { + if (importResult.importStatus.status !== 'done') { + // force a notification to discord that the import was exited in the middle + notifyImportStatusToDiscord(importState, projectName, true) + } + dispatch( + [ + setImportWizardOpen(false), + setLeftMenuTab(LeftMenuTab.Github), + updateGithubSettings(emptyGithubSettings()), + ], + 'everyone', + ) + }, [dispatch, importResult.importStatus.status, importState, projectName]) + const buttonStyle = { + backgroundColor: colorTheme.buttonBackground.value, + padding: 20, + fontSize: 14, + cursor: 'pointer', + } + React.useEffect(() => { + if ( + importResult.importStatus.status == 'done' && + importResult.result == ImportOperationResult.Success + ) { + hideWizard() + } + }, [importResult, hideWizard]) + if ( + importResult.importStatus.status === 'in-progress' || + importResult.importStatus.status === 'not-started' + ) { + return null + } + switch (importResult.result) { + case ImportOperationResult.Success: + return ( + + + + ) + case ImportOperationResult.Warn: + return ( + + + + ) + case ImportOperationResult.CriticalError: + return ( + + + + ) + case ImportOperationResult.Error: + return ( + + {when( + importResult.importStatus.status !== 'done', + , + )} + {importResult.importStatus.status === 'done' ? ( + + ) : ( + + )} + + ) + default: + assertNever(importResult.result) + } +} + +function Header() { + const importState = useEditorState( + Substores.github, + (store) => store.editor.importState, + 'ImportWizard importState', + ) + const totalImportResult: TotalImportResult = React.useMemo( + () => getTotalImportStatusAndResult(importState), + [importState], + ) + const colorTheme = useColorTheme() + const importResult = totalImportResult.result + const importStatus = totalImportResult.importStatus.status + const textColor = React.useMemo(() => { + if (importStatus !== 'done' && importStatus !== 'paused') { + return colorTheme.fg0.value + } + switch (importResult) { + case ImportOperationResult.Success: + return colorTheme.green.value + case ImportOperationResult.Warn: + return colorTheme.warningOrange.value + case ImportOperationResult.Error: + return colorTheme.error.value + case ImportOperationResult.CriticalError: + return colorTheme.error.value + case null: + return colorTheme.fg0.value + default: + assertNever(importResult) + } + }, [ + colorTheme.error.value, + colorTheme.fg0.value, + colorTheme.green.value, + colorTheme.warningOrange.value, + importStatus, + importResult, + ]) + + const getStatusText = () => { + if (importStatus !== 'done' && importStatus !== 'paused') { + return 'Cloning Project' + } + + switch (importResult) { + case ImportOperationResult.Success: + return 'Project Imported Successfully' + case ImportOperationResult.Warn: + return 'Project Imported With Warnings' + case ImportOperationResult.CriticalError: + return 'Error Importing Project' + case ImportOperationResult.Error: + return importStatus !== 'done' + ? 'Error While Importing Project' + : 'Project Imported With Errors' + case null: + return 'Cloning Project' + default: + assertNever(importResult) + } + } + + return
{getStatusText()}
+} diff --git a/editor/src/components/editor/insert-callbacks.ts b/editor/src/components/editor/insert-callbacks.ts index bed02ea43072..1aefc7255cd0 100644 --- a/editor/src/components/editor/insert-callbacks.ts +++ b/editor/src/components/editor/insert-callbacks.ts @@ -23,6 +23,7 @@ import { enableInsertModeForJSXElement, showToast } from './actions/action-creat import { defaultButtonElement, defaultDivElement, + defaultGridElement, defaultImgElement, defaultSpanElement, } from './defaults' @@ -106,6 +107,10 @@ export function useEnterDrawToInsertForButton(): (event: React.MouseEvent) => void { + return useEnterDrawToInsertForElement(defaultGridElement) +} + export function useEnterDrawToInsertForConditional(): (event: React.MouseEvent) => void { const conditionalInsertCallback = useEnterDrawToInsertForElement(defaultDivElement) diff --git a/editor/src/components/editor/insertmenu.spec.browser2.tsx b/editor/src/components/editor/insertmenu.spec.browser2.tsx index 3dc761f4d0c3..7e70223e118f 100644 --- a/editor/src/components/editor/insertmenu.spec.browser2.tsx +++ b/editor/src/components/editor/insertmenu.spec.browser2.tsx @@ -26,7 +26,7 @@ function getInsertItems() { return screen.queryAllByTestId(/^component-picker-item-/gi) } -const allInsertItemsCount = 23 +const allInsertItemsCount = 24 function openInsertMenu(renderResult: EditorRenderResult) { return renderResult.dispatch( diff --git a/editor/src/components/editor/loading-screen.tsx b/editor/src/components/editor/loading-screen.tsx new file mode 100644 index 000000000000..c5197ec5c768 --- /dev/null +++ b/editor/src/components/editor/loading-screen.tsx @@ -0,0 +1,159 @@ +import React from 'react' +import { Substores, useEditorState } from './store/store-hook' +import { getImportOperationTextAsJsx } from './import-wizard/import-wizard-helpers' +import { getTotalImportStatusAndResult } from '../../core/shared/import/import-operation-service' +import type { TotalImportResult } from '../../core/shared/import/import-operation-types' +import type { Theme } from '../../uuiui' +import { getCurrentTheme } from './store/editor-state' +import ReactDOM from 'react-dom' + +export function LoadingEditorComponent() { + const currentTheme: Theme = useEditorState( + Substores.theme, + (store) => getCurrentTheme(store.userState), + 'currentTheme', + ) + + const importState = useEditorState( + Substores.restOfEditor, + (store) => store.editor.importState, + 'LoadingEditorComponent importState', + ) + + const githubRepo = useEditorState( + Substores.userState, + (store) => store.userState.githubState.gitRepoToLoad, + 'LoadingEditorComponent githubRepoToLoad', + ) + + const totalImportResult: TotalImportResult = React.useMemo( + () => getTotalImportStatusAndResult(importState), + [importState], + ) + + const projectId = useEditorState( + Substores.restOfEditor, + (store) => store.editor.id, + 'LoadingEditorComponent projectId', + ) + + const cleared = React.useRef(false) + + const currentOperationToShow: { + text: React.ReactNode + id: string + timeDone: number | null | undefined + timeStarted: number | null | undefined + } | null = React.useMemo(() => { + if (totalImportResult.importStatus.status == 'not-started') { + if (projectId == null) { + return { + text: 'Loading Editor...', + id: 'loading-editor', + timeDone: null, + timeStarted: null, + } + } else { + return { + text: `Parsing files`, + id: 'parseFiles', + timeDone: null, + timeStarted: null, + } + } + } + for (const op of importState.importOperations) { + if (op?.children?.length == 0 || op.type == 'refreshDependencies') { + if (op.timeStarted != null && op.timeDone == null) { + return { + text: getImportOperationTextAsJsx(op), + id: op.id ?? op.type, + timeDone: op.timeDone, + timeStarted: op.timeStarted, + } + } + } + if (op.type !== 'refreshDependencies') { + for (const child of op.children ?? []) { + if (child.timeStarted != null && child.timeDone == null) { + return { + text: getImportOperationTextAsJsx(child), + id: child.id ?? child.type, + timeDone: child.timeDone, + timeStarted: child.timeStarted, + } + } + } + } + } + return { + text: 'Loading Editor...', + id: 'loading-editor', + timeDone: null, + timeStarted: null, + } + }, [totalImportResult, importState.importOperations, projectId]) + + const shouldBeCleared = React.useMemo(() => { + return ( + cleared.current || + (totalImportResult.importStatus.status == 'done' && + (githubRepo == null || totalImportResult.result == 'criticalError')) || + totalImportResult.importStatus.status == 'paused' + ) + }, [totalImportResult, githubRepo]) + + React.useEffect(() => { + if (shouldBeCleared) { + const loadingScreenWrapper = document.getElementById('loading-screen-wrapper') + if (loadingScreenWrapper != null) { + loadingScreenWrapper.remove() + } + } + }, [shouldBeCleared]) + + const portal = React.useRef(document.getElementById('loading-screen-progress-bar-portal')).current + const hasMounted = React.useRef(false) + if (portal == null) { + return null + } + + if (shouldBeCleared) { + cleared.current = true + return null + } + + if (!hasMounted.current) { + portal.innerHTML = '' + hasMounted.current = true + } + + return ReactDOM.createPortal( + +
+
+
+
+
    + {currentOperationToShow != null ? ( +
  • + {currentOperationToShow.text} +
  • + ) : null} +
+
+
, + portal, + ) +} diff --git a/editor/src/components/editor/one-shot-insertion-strategies/insert-as-absolute-strategy.tsx b/editor/src/components/editor/one-shot-insertion-strategies/insert-as-absolute-strategy.tsx index db58c7c4a590..4358eeae2143 100644 --- a/editor/src/components/editor/one-shot-insertion-strategies/insert-as-absolute-strategy.tsx +++ b/editor/src/components/editor/one-shot-insertion-strategies/insert-as-absolute-strategy.tsx @@ -72,6 +72,7 @@ export const insertAsAbsoluteStrategy = ( metadata, state.projectContents, 'force-pins', + 'dont-add-contain-layout', ), setProperty('always', result.newPath, PP.create('style', 'position'), 'absolute'), ], diff --git a/editor/src/components/editor/one-shot-unwrap-strategies/reparent-to-unwrap-as-absolute-strategy.tsx b/editor/src/components/editor/one-shot-unwrap-strategies/reparent-to-unwrap-as-absolute-strategy.tsx index f6fa75be2874..1734b52a4b1d 100644 --- a/editor/src/components/editor/one-shot-unwrap-strategies/reparent-to-unwrap-as-absolute-strategy.tsx +++ b/editor/src/components/editor/one-shot-unwrap-strategies/reparent-to-unwrap-as-absolute-strategy.tsx @@ -48,6 +48,7 @@ export const reparentToUnwrapAsAbsoluteStrategy = ( projectContents, nodeModules, 'do-not-force-pins', + 'dont-add-contain-layout', ) if (result == null) { diff --git a/editor/src/components/editor/preview-report-handler.ts b/editor/src/components/editor/preview-report-handler.ts deleted file mode 100644 index f7bfd86fa05b..000000000000 --- a/editor/src/components/editor/preview-report-handler.ts +++ /dev/null @@ -1,31 +0,0 @@ -import type { EditorDispatch } from './action-types' -import { updatePreviewConnected } from './actions/action-creators' - -export const InternalPreviewTimeout = 500 - -let lastReportFromPreviewTS = 0 -let timeout = 0 -let intervalId: number - -export function previewIsAlive(newTimeout: number) { - timeout = newTimeout - lastReportFromPreviewTS = Date.now() -} - -export function handlePreviewDisconnected() { - lastReportFromPreviewTS = 0 -} - -export function startPreviewConnectedMonitoring(dispatch: EditorDispatch) { - let previousPreviewConnected: boolean - - window.clearInterval(intervalId) - intervalId = window.setInterval(() => { - const stillConnected = - lastReportFromPreviewTS > 0 && Date.now() - lastReportFromPreviewTS < timeout - if (stillConnected !== previousPreviewConnected) { - dispatch([updatePreviewConnected(stillConnected)], 'everyone') - } - previousPreviewConnected = stillConnected - }, 200) -} diff --git a/editor/src/components/editor/remix-navigation-bar.spec.browser2.tsx b/editor/src/components/editor/remix-navigation-bar.spec.browser2.tsx index f14856bb17c8..116b3e904569 100644 --- a/editor/src/components/editor/remix-navigation-bar.spec.browser2.tsx +++ b/editor/src/components/editor/remix-navigation-bar.spec.browser2.tsx @@ -13,7 +13,7 @@ import { getFeaturedRoutesFromPackageJSON } from '../../printer-parsers/html/ext async function renderRemixProject(project: PersistentModel): Promise { const renderResult = await renderTestEditorWithModel(project, 'await-first-dom-report') - await renderResult.dispatch([runDOMWalker()], true) + await renderResult.dispatch([runDOMWalker(null)], true) return renderResult } diff --git a/editor/src/components/editor/remix-navigation-bar.tsx b/editor/src/components/editor/remix-navigation-bar.tsx index 4b03f1e33e39..e83e2a763956 100644 --- a/editor/src/components/editor/remix-navigation-bar.tsx +++ b/editor/src/components/editor/remix-navigation-bar.tsx @@ -137,13 +137,11 @@ export const RemixNavigationBar = React.memo(() => { { padding: '2px 2px 2px 6px', fontSize: 11, minWidth: 150, + flexGrow: 1, height: 20, alignItems: 'center', + justifyContent: 'space-between', }} > { canvasRectangle({ x: 186, y: 34, width: 94, height: 55 }), ) }) + + describe('inside grid', () => { + it('can place element spanning a single cell into the grid and take it out', async () => { + const editor = await renderTestEditorWithCode( + `import { Scene, Storyboard } from 'utopia-api' + +export var storyboard = ( + + +
+
+
+ + +) +`, + 'await-first-dom-report', + ) + + await selectComponentsForTest(editor, [EP.fromString('sb/scene/grid/child')]) + await pressKey('x') // convert it to absolute + + { + const { position, gridRow, gridColumn, width, height } = + editor.renderedDOM.getByTestId('child').style + expect({ position, gridRow, gridColumn, width, height }).toEqual({ + gridColumn: '', + gridRow: '', + height: '91px', + position: 'absolute', + width: '104px', + }) + } + + await pressKey('x') // convert it back to flow with size kept (but no gridColumn/Row or other grid specific prop is set yet) + + { + const { position, gridRow, gridColumn, width, height, top, left } = + editor.renderedDOM.getByTestId('child').style + expect({ position, gridRow, gridColumn, width, height, top, left }).toEqual({ + gridColumn: '', + gridRow: '', + height: '91px', + left: '', + position: '', + top: '', + width: '104px', + }) + } + }) + }) }) describe('jump to parent', () => { diff --git a/editor/src/components/editor/store/dispatch-strategies.spec.tsx b/editor/src/components/editor/store/dispatch-strategies.spec.tsx index 79b1e71e1a0b..46afee9d3b88 100644 --- a/editor/src/components/editor/store/dispatch-strategies.spec.tsx +++ b/editor/src/components/editor/store/dispatch-strategies.spec.tsx @@ -105,7 +105,7 @@ function createEditorStore( }, }, workers: new UtopiaTsWorkersImplementation( - new FakeParserPrinterWorker(), + [new FakeParserPrinterWorker()], new FakeLinterWorker(), new FakeWatchdogWorker(), ), @@ -140,7 +140,7 @@ describe('interactionCancel', () => { 'zero-drag-not-permitted', ), ) - const actualResult = interactionCancel(editorStore, dispatchResultFromEditorStore(editorStore)) + const actualResult = interactionCancel(dispatchResultFromEditorStore(editorStore)) expect(actualResult.newStrategyState.commandDescriptions).toHaveLength(0) expect(actualResult.newStrategyState.currentStrategyCommands).toHaveLength(0) expect(actualResult.newStrategyState.currentStrategy).toBeNull() @@ -157,9 +157,10 @@ const testStrategy: MetaCanvasStrategy = ( controlsToRender: [], fitness: 10, apply: function (): StrategyApplicationResult { - return strategyApplicationResult([ - wildcardPatch('always', { canvas: { scale: { $set: 100 } } }), - ]) + return strategyApplicationResult( + [wildcardPatch('always', { canvas: { scale: { $set: 100 } } })], + [], + ) }, descriptiveLabel: 'A Test Strategy', icon: { @@ -198,10 +199,7 @@ describe('interactionStart', () => { "elementsToRerender": Array [], "escapeHatchActivated": false, "grid": Object { - "currentRootCell": null, - "draggingFromCell": null, - "originalRootCell": null, - "targetCellData": null, + "metadataCacheForGrids": Object {}, }, "lastReorderIdx": null, "strategyGeneratedUidsCache": Object {}, @@ -266,10 +264,7 @@ describe('interactionStart', () => { "elementsToRerender": Array [], "escapeHatchActivated": false, "grid": Object { - "currentRootCell": null, - "draggingFromCell": null, - "originalRootCell": null, - "targetCellData": null, + "metadataCacheForGrids": Object {}, }, "lastReorderIdx": null, "strategyGeneratedUidsCache": Object {}, @@ -354,10 +349,7 @@ describe('interactionUpdate', () => { "elementsToRerender": Array [], "escapeHatchActivated": false, "grid": Object { - "currentRootCell": null, - "draggingFromCell": null, - "originalRootCell": null, - "targetCellData": null, + "metadataCacheForGrids": Object {}, }, "lastReorderIdx": null, "strategyGeneratedUidsCache": Object {}, @@ -442,10 +434,7 @@ describe('interactionUpdate', () => { "elementsToRerender": Array [], "escapeHatchActivated": false, "grid": Object { - "currentRootCell": null, - "draggingFromCell": null, - "originalRootCell": null, - "targetCellData": null, + "metadataCacheForGrids": Object {}, }, "lastReorderIdx": null, "strategyGeneratedUidsCache": Object {}, @@ -524,10 +513,7 @@ describe('interactionHardReset', () => { "elementsToRerender": Array [], "escapeHatchActivated": false, "grid": Object { - "currentRootCell": null, - "draggingFromCell": null, - "originalRootCell": null, - "targetCellData": null, + "metadataCacheForGrids": Object {}, }, "lastReorderIdx": null, "strategyGeneratedUidsCache": Object {}, @@ -614,10 +600,7 @@ describe('interactionHardReset', () => { "elementsToRerender": Array [], "escapeHatchActivated": false, "grid": Object { - "currentRootCell": null, - "draggingFromCell": null, - "originalRootCell": null, - "targetCellData": null, + "metadataCacheForGrids": Object {}, }, "lastReorderIdx": null, "strategyGeneratedUidsCache": Object {}, @@ -710,10 +693,7 @@ describe('interactionUpdate with user changed strategy', () => { "elementsToRerender": Array [], "escapeHatchActivated": false, "grid": Object { - "currentRootCell": null, - "draggingFromCell": null, - "originalRootCell": null, - "targetCellData": null, + "metadataCacheForGrids": Object {}, }, "lastReorderIdx": null, "strategyGeneratedUidsCache": Object {}, @@ -801,10 +781,7 @@ describe('interactionUpdate with user changed strategy', () => { "elementsToRerender": Array [], "escapeHatchActivated": false, "grid": Object { - "currentRootCell": null, - "draggingFromCell": null, - "originalRootCell": null, - "targetCellData": null, + "metadataCacheForGrids": Object {}, }, "lastReorderIdx": null, "strategyGeneratedUidsCache": Object {}, @@ -984,7 +961,7 @@ describe('only update metadata on SAVE_DOM_REPORT', () => { }, apply: function (): StrategyApplicationResult { if (interactionSession == null) { - return strategyApplicationResult([]) + return strategyApplicationResult([], []) } expect(canvasState.startingMetadata).not.toBe(interactionSession.latestMetadata) expect(canvasState.startingAllElementProps).not.toBe( @@ -1011,7 +988,7 @@ describe('only update metadata on SAVE_DOM_REPORT', () => { .backgroundColor, ).toBeDefined() testStrategyRan = true - return strategyApplicationResult([]) + return strategyApplicationResult([], []) }, }, ], diff --git a/editor/src/components/editor/store/dispatch-strategies.tsx b/editor/src/components/editor/store/dispatch-strategies.tsx index 682477d3084e..a9e3861a4cc0 100644 --- a/editor/src/components/editor/store/dispatch-strategies.tsx +++ b/editor/src/components/editor/store/dispatch-strategies.tsx @@ -5,6 +5,8 @@ import type { } from '../../canvas/canvas-strategies/canvas-strategies' import { applyCanvasStrategy, + applyElementsToRerenderFromStrategyResult, + applyElementsToRerenderFromStrategyResultAndPatchRemovedProps, findCanvasStrategy, interactionInProgress, pickCanvasStateFromEditorState, @@ -47,15 +49,15 @@ import type { CustomStrategyState, CustomStrategyStatePatch, InteractionCanvasState, + StrategyApplicationResult, } from '../../canvas/canvas-strategies/canvas-strategy-types' import { strategyApplicationResult } from '../../canvas/canvas-strategies/canvas-strategy-types' -import { isFeatureEnabled } from '../../../utils/feature-switches' -import { PERFORMANCE_MARKS_ALLOWED } from '../../../common/env-vars' import { last } from '../../../core/shared/array-utils' import type { BuiltInDependencies } from '../../../core/es-modules/package-manager/built-in-dependencies-list' import { isInsertMode } from '../editor-modes' import { patchedCreateRemixDerivedDataMemo } from './remix-derived-data' import { allowedToEditProject } from './collaborative-editing' +import { canMeasurePerformance } from '../../../core/performance/performance-utils' interface HandleStrategiesResult { unpatchedEditorState: EditorState @@ -77,6 +79,7 @@ export function interactionFinished( newEditorState.elementPathTree, ) const canvasState: InteractionCanvasState = pickCanvasStateFromEditorState( + newEditorState.selectedViews, newEditorState, result.builtInDependencies, ) @@ -91,7 +94,7 @@ export function interactionFinished( result.strategyState.currentStrategy, ) - const strategyResult = + const strategyResult: StrategyApplicationResult = strategy != null ? applyCanvasStrategy( strategy.strategy, @@ -100,10 +103,9 @@ export function interactionFinished( result.strategyState.customStrategyState, 'end-interaction', ) - : { - commands: [], - } - const commandResult = foldAndApplyCommands( + : strategyApplicationResult([], []) + + const { editorState } = foldAndApplyCommands( newEditorState, storedState.patchedEditor, [], @@ -111,13 +113,16 @@ export function interactionFinished( 'end-interaction', ) - const finalEditor: EditorState = { - ...commandResult.editorState, - // TODO instead of clearing the metadata, we should save the latest valid metadata here to save a dom-walker run - jsxMetadata: {}, - domMetadata: {}, - spyMetadata: {}, - } + const finalEditor: EditorState = applyElementsToRerenderFromStrategyResult( + { + ...editorState, + // TODO instead of clearing the metadata, we should save the latest valid metadata here to save a dom-walker run + jsxMetadata: {}, + domMetadata: {}, + spyMetadata: {}, + }, + strategyResult, + ) return { unpatchedEditorState: finalEditor, @@ -153,6 +158,7 @@ export function interactionHardReset( startingMetadata: storedState.unpatchedEditor.jsxMetadata, } const canvasState: InteractionCanvasState = pickCanvasStateFromEditorState( + newEditorState.selectedViews, newEditorState, result.builtInDependencies, ) @@ -217,7 +223,10 @@ export function interactionHardReset( return { unpatchedEditorState: newEditorState, - patchedEditorState: commandResult.editorState, + patchedEditorState: applyElementsToRerenderFromStrategyResultAndPatchRemovedProps( + commandResult.editorState, + strategyResult, + ), newStrategyState: newStrategyState, } } else { @@ -238,6 +247,7 @@ export function interactionUpdate( ): HandleStrategiesResult { const newEditorState = result.unpatchedEditor const canvasState: InteractionCanvasState = pickCanvasStateFromEditorState( + newEditorState.selectedViews, newEditorState, result.builtInDependencies, ) @@ -320,6 +330,7 @@ export function interactionStart( newEditorState.elementPathTree, ) const canvasState: InteractionCanvasState = pickCanvasStateFromEditorState( + newEditorState.selectedViews, newEditorState, result.builtInDependencies, ) @@ -380,7 +391,10 @@ export function interactionStart( return { unpatchedEditorState: newEditorState, - patchedEditorState: commandResult.editorState, + patchedEditorState: applyElementsToRerenderFromStrategyResultAndPatchRemovedProps( + commandResult.editorState, + strategyResult, + ), newStrategyState: newStrategyState, } } else { @@ -393,22 +407,16 @@ export function interactionStart( } } -export function interactionCancel( - storedState: EditorStoreFull, - result: EditorStoreUnpatched, -): HandleStrategiesResult { - const interactionWasInProgress = interactionInProgress( - storedState.unpatchedEditor.canvas.interactionSession, - ) +export function interactionCancel(result: EditorStoreUnpatched): HandleStrategiesResult { const updatedEditorState: EditorState = { ...result.unpatchedEditor, canvas: { ...result.unpatchedEditor.canvas, interactionSession: null, }, - jsxMetadata: interactionWasInProgress ? {} : result.unpatchedEditor.jsxMetadata, - domMetadata: interactionWasInProgress ? {} : result.unpatchedEditor.domMetadata, - spyMetadata: interactionWasInProgress ? {} : result.unpatchedEditor.spyMetadata, + jsxMetadata: result.unpatchedEditor.jsxMetadata, + domMetadata: result.unpatchedEditor.domMetadata, + spyMetadata: result.unpatchedEditor.spyMetadata, } return { @@ -428,6 +436,7 @@ function handleUserChangedStrategy( sortedApplicableStrategies: Array, ): HandleStrategiesResult { const canvasState: InteractionCanvasState = pickCanvasStateFromEditorState( + newEditorState.selectedViews, newEditorState, builtInDependencies, ) @@ -483,7 +492,10 @@ function handleUserChangedStrategy( return { unpatchedEditorState: newEditorState, - patchedEditorState: commandResult.editorState, + patchedEditorState: applyElementsToRerenderFromStrategyResultAndPatchRemovedProps( + commandResult.editorState, + strategyResult, + ), newStrategyState: newStrategyState, } } else { @@ -505,6 +517,7 @@ function handleAccumulatingKeypresses( sortedApplicableStrategies: Array, ): HandleStrategiesResult { const canvasState: InteractionCanvasState = pickCanvasStateFromEditorState( + newEditorState.selectedViews, newEditorState, builtInDependencies, ) @@ -537,7 +550,7 @@ function handleAccumulatingKeypresses( strategyState.customStrategyState, 'mid-interaction', ) - : strategyApplicationResult([]) + : strategyApplicationResult([], []) const commandResult = foldAndApplyCommands( updatedEditorState, storedEditorState, @@ -565,7 +578,10 @@ function handleAccumulatingKeypresses( return { unpatchedEditorState: updatedEditorState, - patchedEditorState: commandResult.editorState, + patchedEditorState: applyElementsToRerenderFromStrategyResultAndPatchRemovedProps( + commandResult.editorState, + strategyResult, + ), newStrategyState: newStrategyState, } } @@ -587,6 +603,7 @@ function handleUpdate( sortedApplicableStrategies: Array, ): HandleStrategiesResult { const canvasState: InteractionCanvasState = pickCanvasStateFromEditorState( + newEditorState.selectedViews, newEditorState, builtInDependencies, ) @@ -601,7 +618,7 @@ function handleUpdate( strategyState.customStrategyState, 'mid-interaction', ) - : strategyApplicationResult([]) + : strategyApplicationResult([], []) const commandResult = foldAndApplyCommands( newEditorState, storedEditorState, @@ -628,7 +645,10 @@ function handleUpdate( } return { unpatchedEditorState: newEditorState, - patchedEditorState: commandResult.editorState, + patchedEditorState: applyElementsToRerenderFromStrategyResultAndPatchRemovedProps( + commandResult.editorState, + strategyResult, + ), newStrategyState: newStrategyState, } } else { @@ -647,10 +667,7 @@ export function handleStrategies( result: EditorStoreUnpatched, oldDerivedState: DerivedState, ): HandleStrategiesResult & { patchedDerivedState: DerivedState } { - const MeasureDispatchTime = - (isFeatureEnabled('Debug – Performance Marks (Fast)') || - isFeatureEnabled('Debug – Performance Marks (Slow)')) && - PERFORMANCE_MARKS_ALLOWED + const MeasureDispatchTime = canMeasurePerformance() if (MeasureDispatchTime) { window.performance.mark('strategies_begin') @@ -711,7 +728,7 @@ export function handleStrategies( return { unpatchedEditorState: unpatchedEditorState, patchedEditorState: patchedEditorWithMetadata, - patchedDerivedState, + patchedDerivedState: patchedDerivedState, newStrategyState: newStrategyState, } } @@ -800,7 +817,7 @@ function handleStrategiesInner( } } else { if (cancelInteraction) { - return interactionCancel(storedState, result) + return interactionCancel(result) } else if (makeChangesPermanent) { return interactionFinished(strategies, storedState, result) } else { diff --git a/editor/src/components/editor/store/dispatch.tsx b/editor/src/components/editor/store/dispatch.tsx index 5c7295c5ed17..44a7a54adf44 100644 --- a/editor/src/components/editor/store/dispatch.tsx +++ b/editor/src/components/editor/store/dispatch.tsx @@ -1,4 +1,3 @@ -import { PERFORMANCE_MARKS_ALLOWED } from '../../../common/env-vars' import type { UtopiaTsWorkers } from '../../../core/workers/common/worker-types' import { getParseResult } from '../../../core/workers/common/worker-types' import { runLocalCanvasAction } from '../../../templates/editor-canvas' @@ -6,7 +5,6 @@ import { runLocalNavigatorAction } from '../../../templates/editor-navigator' import { optionalDeepFreeze } from '../../../utils/deep-freeze' import type { CanvasAction } from '../../canvas/canvas-types' import type { LocalNavigatorAction } from '../../navigator/actions' -import { PreviewIframeId, projectContentsUpdateMessage } from '../../preview/preview-pane' import type { EditorAction, EditorDispatch, UpdateMetadataInEditorState } from '../action-types' import { isLoggedIn } from '../action-types' import { @@ -29,6 +27,7 @@ import type { } from './editor-state' import { deriveState, + getJSXElementFromProjectContents, persistentModelFromEditorModel, storedEditorStateFromEditorState, } from './editor-state' @@ -42,11 +41,11 @@ import { runResetOnlineState, runUpdateProjectServerState, } from './editor-update' -import { isBrowserEnvironment } from '../../../core/shared/utils' +import { assertNever, isBrowserEnvironment } from '../../../core/shared/utils' import type { UiJsxCanvasContextData } from '../../canvas/ui-jsx-canvas' import type { ProjectContentTreeRoot } from '../../assets' import { treeToContents } from '../../assets' -import { isSendPreviewModel, restoreDerivedState, UPDATE_FNS } from '../actions/actions' +import { restoreDerivedState, UPDATE_FNS } from '../actions/actions' import { getTransitiveReverseDependencies } from '../../../core/shared/project-contents-dependencies' import { reduxDevtoolsSendActions, @@ -60,7 +59,6 @@ import { getProjectChanges, sendVSCodeChanges, } from './vscode-changes' -import { isFeatureEnabled } from '../../../utils/feature-switches' import { handleStrategies, updatePostActionState } from './dispatch-strategies' import type { MetaCanvasStrategy } from '../../canvas/canvas-strategies/canvas-strategies' @@ -78,18 +76,25 @@ import { maybeClearPseudoInsertMode } from '../canvas-toolbar-states' import { isSteganographyEnabled } from '../../../core/shared/stegano-text' import { updateCollaborativeProjectContents } from './collaborative-editing' import { ensureSceneIdsExist } from '../../../core/model/scene-id-utils' -import type { ModuleEvaluator } from '../../../core/property-controls/property-controls-local' +import { isComponentDescriptorFile } from '../../../core/property-controls/property-controls-local' +import { getFilePathMappings } from '../../../core/model/project-file-utils' import { - createModuleEvaluator, - isComponentDescriptorFile, -} from '../../../core/property-controls/property-controls-local' + getParserChunkCount, + getParserWorkerCount, + isConcurrencyLoggingEnabled, +} from '../../../core/workers/common/concurrency-utils' import { - hasReactRouterErrorBeenLogged, - setReactRouterErrorHasBeenLogged, -} from '../../../core/shared/runtime-report-logs' -import type { PropertyControlsInfo } from '../../custom-code/code-file' -import { getFilePathMappings } from '../../../core/model/project-file-utils' -import type { ElementInstanceMetadataMap } from '../../../core/shared/element-template' + canMeasurePerformance, + startPerformanceMeasure, +} from '../../../core/performance/performance-utils' +import { getParseCacheOptions } from '../../../core/shared/parse-cache-utils' +import { resetUpdatedProperties } from '../../canvas/plugins/style-plugins' +import { + notifyOperationFinished, + notifyOperationStarted, +} from '../../../core/shared/import/import-operation-service' +import { ImportOperationResult } from '../../../core/shared/import/import-operation-types' +import { updateImportStatus } from '../actions/action-creators' type DispatchResultFields = { nothingChanged: boolean @@ -183,7 +188,7 @@ function processAction( } if (action.action === 'UPDATE_TEXT') { - working = UPDATE_FNS.UPDATE_TEXT(action, working) + working = UPDATE_FNS.UPDATE_TEXT(action, working, dispatchEvent) } if (action.action === 'TRUNCATE_HISTORY') { @@ -309,26 +314,6 @@ function processActions( }, working) } -export function updateEmbeddedPreview( - modelId: string | null, - projectContents: ProjectContentTreeRoot, -): void { - const embeddedPreviewElement = document.getElementById(PreviewIframeId) - if (embeddedPreviewElement != null) { - const embeddedPreviewIframe = embeddedPreviewElement as any as HTMLIFrameElement - const contentWindow = embeddedPreviewIframe.contentWindow - if (contentWindow != null) { - try { - contentWindow.postMessage(projectContentsUpdateMessage(projectContents), '*') - } catch (exception) { - // Don't nuke the editor if there's an exception posting the message. - // This can happen if a value can't be cloned when posted. - console.error('Error updating preview.', exception) - } - } - } -} - function maybeRequestModelUpdate( projectContents: ProjectContentTreeRoot, workers: UtopiaTsWorkers, @@ -347,14 +332,29 @@ function maybeRequestModelUpdate( // Should anything need to be sent across, do so here. if (filesToUpdate.length > 0) { + const { endMeasure } = startPerformanceMeasure('file-parse', { uniqueId: true }) + notifyOperationStarted(dispatch, { type: 'parseFiles' }) const parseFinished = getParseResult( workers, filesToUpdate, getFilePathMappings(projectContents), existingUIDs, isSteganographyEnabled(), + getParserChunkCount(), + getParseCacheOptions(), ) .then((parseResult) => { + notifyOperationFinished(dispatch, { type: 'parseFiles' }, ImportOperationResult.Success) + const duration = endMeasure() + if (isConcurrencyLoggingEnabled() && filesToUpdate.length > 1) { + console.info( + `parse finished for ${ + filesToUpdate.length + } files, using ${getParserChunkCount()} chunks and ${getParserWorkerCount()} workers, in ${duration.toFixed( + 2, + )}ms`, + ) + } const updates = parseResult.map((fileResult) => { return parseResultToWorkerUpdates(fileResult) }) @@ -372,12 +372,19 @@ function maybeRequestModelUpdate( ) } - dispatch([EditorActions.mergeWithPrevUndo(actionsToDispatch)]) + dispatch([ + EditorActions.mergeWithPrevUndo(actionsToDispatch), + updateImportStatus({ status: 'done' }), + ]) return true }) .catch((e) => { console.error('error during parse', e) - dispatch([EditorActions.clearParseOrPrintInFlight()]) + notifyOperationFinished(dispatch, { type: 'parseFiles' }, ImportOperationResult.Error) + dispatch([ + EditorActions.clearParseOrPrintInFlight(), + updateImportStatus({ status: 'done' }), + ]) return true }) return { @@ -445,7 +452,7 @@ function reducerToSplitToActionGroups( [[]], ) const wrappedTransientActionGroups = transientActionGroups.map((actionGroup) => [ - EditorActions.transientActions(actionGroup), + EditorActions.transientActions(actionGroup, currentAction.elementsToRerender), ]) return [...actionGroups, ...wrappedTransientActionGroups] } else if (i > 0 && actions[i - 1].action === 'CLEAR_INTERACTION_SESSION') { @@ -525,14 +532,13 @@ export function editorDispatchClosingOut( }) const anyWorkerUpdates = checkAnyWorkerUpdates(dispatchedActions) const anyUndoOrRedo = dispatchedActions.some(isUndoOrRedo) - const anySendPreviewModel = dispatchedActions.some(isSendPreviewModel) // The FINISH_CHECKPOINT_TIMER action effectively overrides the case where nothing changed, // as it's likely that action on it's own didn't change anything, but the actions that paired with // START_CHECKPOINT_TIMER likely did. const transientOrNoChange = (allTransient || result.nothingChanged) && !anyFinishCheckpointTimer - const unpatchedEditorState = result.unpatchedEditor + const unpatchedEditorState = resetUnpatchedEditorTransientFields(result.unpatchedEditor) const patchedEditorState = result.patchedEditor const newStrategyState = result.strategyState const patchedDerivedState = result.patchedDerived @@ -723,13 +729,6 @@ export function editorDispatchClosingOut( } } - const shouldUpdatePreview = - anySendPreviewModel || - frozenEditorState.projectContents !== storedState.unpatchedEditor.projectContents - if (shouldUpdatePreview) { - updateEmbeddedPreview(frozenEditorState.id, frozenEditorState.projectContents) - } - if (frozenEditorState.id != null && frozenEditorState.id != storedState.unpatchedEditor.id) { storedState.workers.initWatchdogWorker(frozenEditorState.id) } @@ -837,10 +836,7 @@ function editorDispatchInner( ): DispatchResult { // console.log('DISPATCH', simpleStringifyActions(dispatchedActions), dispatchedActions) - const MeasureDispatchTime = - (isFeatureEnabled('Debug – Performance Marks (Fast)') || - isFeatureEnabled('Debug – Performance Marks (Slow)')) && - PERFORMANCE_MARKS_ALLOWED + const MeasureDispatchTime = canMeasurePerformance() if (MeasureDispatchTime) { window.performance.mark('dispatch_begin') @@ -1014,13 +1010,19 @@ function editorDispatchInner( ) } + const storedStateForStrategies: EditorStoreFull = { + ...storedState, + patchedEditor: resetUpdatedProperties(storedState.patchedEditor), + unpatchedEditor: resetUpdatedProperties(storedState.unpatchedEditor), + } + const { unpatchedEditorState, patchedEditorState, newStrategyState, patchedDerivedState } = handleStrategies( strategiesToUse, dispatchedActions, - storedState, + storedStateForStrategies, result, - storedState.patchedDerived, + storedStateForStrategies.patchedDerived, ) return { @@ -1072,3 +1074,14 @@ function filterEditorForFiles(editor: EditorState) { }, } } + +function resetUnpatchedEditorTransientFields(editor: EditorState): EditorState { + // reset all parts of the state which is meant to be "empty" or "default", and mostly expect the non-default values to come from patchedEditor + return { + ...editor, + canvas: { + ...editor.canvas, + elementsToRerender: 'rerender-all-elements', + }, + } +} diff --git a/editor/src/components/editor/store/editor-dispatch-performance-logging.tsx b/editor/src/components/editor/store/editor-dispatch-performance-logging.tsx index 6f1290045faf..457bf3b37c58 100644 --- a/editor/src/components/editor/store/editor-dispatch-performance-logging.tsx +++ b/editor/src/components/editor/store/editor-dispatch-performance-logging.tsx @@ -1,14 +1,11 @@ -import { PERFORMANCE_MARKS_ALLOWED } from '../../../common/env-vars' +import { canMeasurePerformance } from '../../../core/performance/performance-utils' import { isFeatureEnabled } from '../../../utils/feature-switches' import type { EditorAction } from '../action-types' import { simpleStringifyActions } from '../actions/action-utils' export function createPerformanceMeasure() { const MeasureSelectorsEnabled = isFeatureEnabled('Debug – Measure Selectors') - const PerformanceMarks = - (isFeatureEnabled('Debug – Performance Marks (Slow)') || - isFeatureEnabled('Debug – Performance Marks (Fast)')) && - PERFORMANCE_MARKS_ALLOWED + const PerformanceMarks = canMeasurePerformance() let stringifiedActions = '' diff --git a/editor/src/components/editor/store/editor-state.ts b/editor/src/components/editor/store/editor-state.ts index b874154f03d2..4f186b5cf9ba 100644 --- a/editor/src/components/editor/store/editor-state.ts +++ b/editor/src/components/editor/store/editor-state.ts @@ -31,7 +31,6 @@ import type { } from '../../../core/shared/element-template' import { emptyJsxMetadata, - getElementsByUIDFromTopLevelElements, isJSExpression, isJSXConditionalExpression, isJSXElement, @@ -61,14 +60,12 @@ import type { NodeModules, ParseSuccess, ProjectFile, - PropertyPath, StaticElementPath, TextFile, } from '../../../core/shared/project-file-types' import { RevisionsState, codeFile, - foldParsedTextFile, isParseFailure, isParseSuccess, isParsedTextFile, @@ -125,7 +122,6 @@ import type { Notice } from '../../common/notice' import type { ShortcutConfiguration } from '../shortcut-definitions' import { DerivedStateKeepDeepEquality, - ElementInstanceMetadataMapKeepDeepEquality, InvalidOverrideNavigatorEntryKeepDeepEquality, RenderPropNavigatorEntryKeepDeepEquality, RenderPropValueNavigatorEntryKeepDeepEquality, @@ -192,6 +188,18 @@ import type { Collaborator } from '../../../core/shared/multiplayer' import type { OnlineState } from '../online-status' import type { NavigatorRow } from '../../navigator/navigator-row' import type { FancyError } from '../../../core/shared/code-exec-utils' +import type { GridCellCoordinates } from '../../canvas/canvas-strategies/strategies/grid-cell-bounds' +import type { HuggingElementContentsStatus } from '../../../components/canvas/hugging-utils' +import { + emptyImportState, + type ImportState, +} from '../../../core/shared/import/import-operation-types' +import { + emptyProjectRequirements, + type ProjectRequirements, +} from '../../../core/shared/import/project-health-check/utopia-requirements-types' +import type { UpdatedProperties } from '../../canvas/plugins/style-plugins' +import { isFeatureEnabled } from '../../../utils/feature-switches' const ObjectPathImmutable: any = OPI @@ -214,7 +222,6 @@ export enum RightMenuTab { Inspector = 'inspector', Settings = 'settings', Comments = 'comments', - RollYourOwn = 'roll-your-own', } // TODO: this should just contain an NpmDependency and a status @@ -357,6 +364,8 @@ export function githubOperationLocksEditor(op: GithubOperation): boolean { case 'loadRepositories': case 'listPullRequestsForBranch': return false + case 'loadBranch': + return !isFeatureEnabled('Import Wizard') default: return true } @@ -485,7 +494,7 @@ export type EditorStoreFull = EditorStoreShared & { patchedDerived: DerivedState } -type StoreName = 'editor-store' | 'canvas-store' | 'low-priority-store' +type StoreName = 'editor-store' | 'canvas-store' | 'helper-controls-store' | 'low-priority-store' export type EditorStorePatched = EditorStoreShared & { storeName: StoreName @@ -813,6 +822,49 @@ export interface DragToMoveIndicatorFlags { ancestor: boolean } +export type GridIdentifier = GridContainerIdentifier | GridItemIdentifier + +export interface GridContainerIdentifier { + type: 'GRID_CONTAINER' + container: ElementPath +} + +export function gridContainerIdentifier(path: ElementPath): GridContainerIdentifier { + return { + type: 'GRID_CONTAINER', + container: path, + } +} + +export interface GridItemIdentifier { + type: 'GRID_ITEM' + item: ElementPath +} + +export function gridItemIdentifier(path: ElementPath): GridItemIdentifier { + return { + type: 'GRID_ITEM', + item: path, + } +} + +export function pathOfGridFromGridIdentifier(identifier: GridIdentifier): ElementPath { + switch (identifier.type) { + case 'GRID_ITEM': + return EP.parentPath(identifier.item) + case 'GRID_CONTAINER': + return identifier.container + default: + assertNever(identifier) + } +} + +export interface GridControlData { + grid: GridIdentifier + targetCell: GridCellCoordinates | null // the cell under the mouse + rootCell: GridCellCoordinates | null // the top-left cell of the target child +} + export interface EditorStateCanvasControls { // this is where we can put props for the strategy controls snappingGuidelines: Array @@ -823,7 +875,7 @@ export interface EditorStateCanvasControls { reparentedToPaths: Array dragToMoveIndicatorFlags: DragToMoveIndicatorFlags parentOutlineHighlight: ElementPath | null - gridControls: ElementPath | null + gridControlData: GridControlData | null } export function editorStateCanvasControls( @@ -835,7 +887,7 @@ export function editorStateCanvasControls( reparentedToPaths: Array, dragToMoveIndicatorFlagsValue: DragToMoveIndicatorFlags, parentOutlineHighlight: ElementPath | null, - gridControls: ElementPath | null, + gridControlData: GridControlData | null, ): EditorStateCanvasControls { return { snappingGuidelines: snappingGuidelines, @@ -846,7 +898,7 @@ export function editorStateCanvasControls( reparentedToPaths: reparentedToPaths, dragToMoveIndicatorFlags: dragToMoveIndicatorFlagsValue, parentOutlineHighlight: parentOutlineHighlight, - gridControls: gridControls, + gridControlData: gridControlData, } } @@ -1078,18 +1130,6 @@ export function editorStateTopMenu( } } -export interface EditorStatePreview { - visible: boolean - connected: boolean -} - -export function editorStatePreview(visible: boolean, connected: boolean): EditorStatePreview { - return { - visible: visible, - connected: connected, - } -} - export interface EditorStateHome { visible: boolean } @@ -1365,17 +1405,23 @@ export function trueUpChildrenOfGroupChanged( export interface TrueUpHuggingElement { type: 'TRUE_UP_HUGGING_ELEMENT' target: ElementPath + elementFrame: CanvasRectangle frame: CanvasRectangle + huggingElementContentsStatus: HuggingElementContentsStatus } export function trueUpHuggingElement( target: ElementPath, + elementFrame: CanvasRectangle, frame: CanvasRectangle, + huggingElementContentsStatus: HuggingElementContentsStatus, ): TrueUpHuggingElement { return { type: 'TRUE_UP_HUGGING_ELEMENT', target: target, + elementFrame: elementFrame, frame: frame, + huggingElementContentsStatus: huggingElementContentsStatus, } } @@ -1435,7 +1481,6 @@ export interface EditorState { projectSettings: EditorStateProjectSettings navigator: NavigatorState topmenu: EditorStateTopMenu - preview: EditorStatePreview home: EditorStateHome lastUsedFont: FontSettings | null modal: ModalDialog | null @@ -1463,6 +1508,9 @@ export interface EditorState { githubSettings: ProjectGithubSettings imageDragSessionState: ImageDragSessionState githubOperations: Array + importState: ImportState + projectRequirements: ProjectRequirements + importWizardOpen: boolean githubData: GithubData refreshingDependencies: boolean colorSwatches: Array @@ -1474,6 +1522,7 @@ export interface EditorState { collaborators: Collaborator[] sharingDialogOpen: boolean editorRemixConfig: EditorRemixConfig + propertiesUpdatedDuringInteraction: UpdatedProperties } export function editorState( @@ -1519,7 +1568,6 @@ export function editorState( projectSettings: EditorStateProjectSettings, editorStateNavigator: NavigatorState, topmenu: EditorStateTopMenu, - preview: EditorStatePreview, home: EditorStateHome, lastUsedFont: FontSettings | null, modal: ModalDialog | null, @@ -1547,6 +1595,9 @@ export function editorState( githubSettings: ProjectGithubSettings, imageDragSessionState: ImageDragSessionState, githubOperations: Array, + importState: ImportState, + importWizardOpen: boolean, + projectRequirements: ProjectRequirements, branchOriginContents: ProjectContentTreeRoot | null, githubData: GithubData, refreshingDependencies: boolean, @@ -1559,6 +1610,7 @@ export function editorState( collaborators: Collaborator[], sharingDialogOpen: boolean, remixConfig: EditorRemixConfig, + propertiesUpdatedDuringInteraction: UpdatedProperties, ): EditorState { return { id: id, @@ -1604,7 +1656,6 @@ export function editorState( projectSettings: projectSettings, navigator: editorStateNavigator, topmenu: topmenu, - preview: preview, home: home, lastUsedFont: lastUsedFont, modal: modal, @@ -1632,6 +1683,9 @@ export function editorState( githubSettings: githubSettings, imageDragSessionState: imageDragSessionState, githubOperations: githubOperations, + importState: importState, + importWizardOpen: importWizardOpen, + projectRequirements: projectRequirements, githubData: githubData, refreshingDependencies: refreshingDependencies, colorSwatches: colorSwatches, @@ -1643,6 +1697,7 @@ export function editorState( collaborators: collaborators, sharingDialogOpen: sharingDialogOpen, editorRemixConfig: remixConfig, + propertiesUpdatedDuringInteraction: propertiesUpdatedDuringInteraction, } } @@ -2640,7 +2695,7 @@ export function createEditorState(dispatch: EditorDispatch): EditorState { reparentedToPaths: [], dragToMoveIndicatorFlags: emptyDragToMoveIndicatorFlags, parentOutlineHighlight: null, - gridControls: null, + gridControlData: null, }, }, inspector: { @@ -2676,10 +2731,6 @@ export function createEditorState(dispatch: EditorDispatch): EditorState { formulaBarMode: 'content', formulaBarFocusCounter: 0, }, - preview: { - visible: false, - connected: false, - }, home: { visible: false, }, @@ -2713,6 +2764,9 @@ export function createEditorState(dispatch: EditorDispatch): EditorState { githubSettings: emptyGithubSettings(), imageDragSessionState: notDragging(), githubOperations: [], + importState: emptyImportState(), + importWizardOpen: false, + projectRequirements: emptyProjectRequirements(), branchOriginContents: null, githubData: emptyGithubData(), refreshingDependencies: false, @@ -2730,6 +2784,7 @@ export function createEditorState(dispatch: EditorDispatch): EditorState { editorRemixConfig: { errorBoundaryHandling: 'ignore-error-boundaries', }, + propertiesUpdatedDuringInteraction: {}, } } @@ -3019,7 +3074,7 @@ export function editorModelFromPersistentModel( reparentedToPaths: [], dragToMoveIndicatorFlags: emptyDragToMoveIndicatorFlags, parentOutlineHighlight: null, - gridControls: null, + gridControlData: null, }, }, inspector: { @@ -3038,10 +3093,6 @@ export function editorModelFromPersistentModel( formulaBarMode: 'content', formulaBarFocusCounter: 0, }, - preview: { - visible: false, - connected: false, - }, home: { visible: false, }, @@ -3084,6 +3135,9 @@ export function editorModelFromPersistentModel( githubSettings: persistentModel.githubSettings, imageDragSessionState: notDragging(), githubOperations: [], + importState: emptyImportState(), + importWizardOpen: false, + projectRequirements: emptyProjectRequirements(), refreshingDependencies: false, branchOriginContents: null, githubData: emptyGithubData(), @@ -3101,6 +3155,7 @@ export function editorModelFromPersistentModel( editorRemixConfig: { errorBoundaryHandling: 'ignore-error-boundaries', }, + propertiesUpdatedDuringInteraction: {}, } return editor } @@ -3278,14 +3333,17 @@ export function updatePackageJsonInEditorState( // There is a package.json file, we should update it. updatedPackageJsonFile = codeFile( transformPackageJson(packageJsonFile.fileContents.code), - RevisionsState.CodeAhead, + null, packageJsonFile.versionNumber + 1, + RevisionsState.CodeAheadButPleaseTellVSCodeAboutIt, ) } else { // There is something else called package.json, we should bulldoze over it. updatedPackageJsonFile = codeFile( transformPackageJson(JSON.stringify(DefaultPackageJson)), null, + 0, + RevisionsState.CodeAheadButPleaseTellVSCodeAboutIt, ) } } diff --git a/editor/src/components/editor/store/editor-update.spec.tsx b/editor/src/components/editor/store/editor-update.spec.tsx index 62d35e4ec115..fe59fde9219a 100644 --- a/editor/src/components/editor/store/editor-update.spec.tsx +++ b/editor/src/components/editor/store/editor-update.spec.tsx @@ -308,38 +308,6 @@ describe('action TOGGLE_PANE', () => { ) chaiExpect(updatedEditor2.inspector.visible).to.not.equal(updatedEditor.inspector.visible) }) - - it('can toggle preview visibility', () => { - const { editor, derivedState, dispatch } = createEditorStates() - const action = togglePanel('preview') - const updatedEditor = runLocalEditorAction( - editor, - derivedState, - defaultUserState, - workers, - action, - History.init(editor, derivedState), - dispatch, - emptyUiJsxCanvasContextData(), - builtInDependencies, - emptyCollaborativeEditingSupport(), - emptyProjectServerState(), - ) - const updatedEditor2 = runLocalEditorAction( - updatedEditor, - derivedState, - defaultUserState, - workers, - action, - History.init(editor, derivedState), - dispatch, - emptyUiJsxCanvasContextData(), - builtInDependencies, - emptyCollaborativeEditingSupport(), - emptyProjectServerState(), - ) - chaiExpect(updatedEditor2.preview.visible).to.not.equal(updatedEditor.preview.visible) - }) }) describe('action DUPLICATE_SPECIFIC_ELEMENTS', () => { @@ -1030,7 +998,7 @@ describe('updating package.json', () => { "parsed": Object { "type": "UNPARSED", }, - "revisionsState": "CODE_AHEAD", + "revisionsState": "CODE_AHEAD_BUT_PLEASE_TELL_VSCODE_ABOUT_IT", } `) } diff --git a/editor/src/components/editor/store/editor-update.tsx b/editor/src/components/editor/store/editor-update.tsx index 6b6cff5c9c4e..6a636b69c0a1 100644 --- a/editor/src/components/editor/store/editor-update.tsx +++ b/editor/src/components/editor/store/editor-update.tsx @@ -220,6 +220,14 @@ export function runSimpleLocalEditorAction( return UPDATE_FNS.SET_REFRESHING_DEPENDENCIES(action, state) case 'UPDATE_GITHUB_OPERATIONS': return UPDATE_FNS.UPDATE_GITHUB_OPERATIONS(action, state) + case 'UPDATE_IMPORT_OPERATIONS': + return UPDATE_FNS.UPDATE_IMPORT_OPERATIONS(action, state) + case 'UPDATE_IMPORT_STATUS': + return UPDATE_FNS.UPDATE_IMPORT_STATUS(action, state) + case 'SET_IMPORT_WIZARD_OPEN': + return UPDATE_FNS.SET_IMPORT_WIZARD_OPEN(action, state) + case 'UPDATE_PROJECT_REQUIREMENTS': + return UPDATE_FNS.UPDATE_PROJECT_REQUIREMENTS(action, state, dispatch) case 'REMOVE_TOAST': return UPDATE_FNS.REMOVE_TOAST(action, state) case 'SET_HIGHLIGHTED_VIEWS': @@ -262,14 +270,10 @@ export function runSimpleLocalEditorAction( return UPDATE_FNS.SET_PROJECT_NAME(action, state) case 'SET_PROJECT_DESCRIPTION': return UPDATE_FNS.SET_PROJECT_DESCRIPTION(action, state) - case 'UPDATE_PREVIEW_CONNECTED': - return UPDATE_FNS.UPDATE_PREVIEW_CONNECTED(action, state) case 'SHOW_CONTEXT_MENU': return UPDATE_FNS.SHOW_CONTEXT_MENU(action, state) case 'DUPLICATE_SPECIFIC_ELEMENTS': return UPDATE_FNS.DUPLICATE_SPECIFIC_ELEMENTS(action, state, dispatch) - case 'SEND_PREVIEW_MODEL': - return UPDATE_FNS.SEND_PREVIEW_MODEL(action, state) case 'UPDATE_FILE_PATH': return UPDATE_FNS.UPDATE_FILE_PATH(action, state, userState) case 'UPDATE_REMIX_ROUTE': @@ -291,7 +295,7 @@ export function runSimpleLocalEditorAction( case 'REMOVE_FILE_CONFLICT': return UPDATE_FNS.REMOVE_FILE_CONFLICT(action, state) case 'UPDATE_FROM_WORKER': - return UPDATE_FNS.UPDATE_FROM_WORKER(action, state, userState) + return UPDATE_FNS.UPDATE_FROM_WORKER(action, state, userState, dispatch) case 'UPDATE_FROM_CODE_EDITOR': return UPDATE_FNS.UPDATE_FROM_CODE_EDITOR( action, diff --git a/editor/src/components/editor/store/store-deep-equality-instances-3.spec.ts b/editor/src/components/editor/store/store-deep-equality-instances-3.spec.ts index 02cbcf84fcec..b996d1160c6d 100644 --- a/editor/src/components/editor/store/store-deep-equality-instances-3.spec.ts +++ b/editor/src/components/editor/store/store-deep-equality-instances-3.spec.ts @@ -233,6 +233,7 @@ describe('SpecialSizeMeasurementsKeepDeepEquality', () => { usesParentBounds: false, parentLayoutSystem: 'flex', layoutSystemForChildren: 'flex', + layoutSystemForChildrenInherited: false, providesBoundsForAbsoluteChildren: true, display: 'flex', position: 'absolute', @@ -265,6 +266,7 @@ describe('SpecialSizeMeasurementsKeepDeepEquality', () => { gap: 11, flexDirection: 'column', justifyContent: 'center', + alignContent: null, alignItems: 'auto', htmlElementName: 'div', renderedChildrenCount: 10, @@ -300,6 +302,13 @@ describe('SpecialSizeMeasurementsKeepDeepEquality', () => { gridAutoRows: null, gridAutoFlow: null, }, + parentContainerGridProperties: { + gridTemplateColumns: null, + gridTemplateRows: null, + gridAutoColumns: null, + gridAutoRows: null, + gridAutoFlow: null, + }, elementGridProperties: { gridColumnStart: null, gridColumnEnd: null, @@ -313,6 +322,13 @@ describe('SpecialSizeMeasurementsKeepDeepEquality', () => { gridAutoRows: null, gridAutoFlow: null, }, + parentContainerGridPropertiesFromProps: { + gridTemplateColumns: null, + gridTemplateRows: null, + gridAutoColumns: null, + gridAutoRows: null, + gridAutoFlow: null, + }, elementGridPropertiesFromProps: { gridColumnStart: null, gridColumnEnd: null, @@ -321,6 +337,17 @@ describe('SpecialSizeMeasurementsKeepDeepEquality', () => { }, rowGap: null, columnGap: null, + gridCellGlobalFrames: null, + parentGridCellGlobalFrames: null, + justifySelf: null, + alignSelf: null, + borderWidths: { + top: 0, + left: 0, + bottom: 0, + right: 0, + }, + parentGridFrame: null, } const newDifferentValue: SpecialSizeMeasurements = { @@ -351,6 +378,7 @@ describe('SpecialSizeMeasurementsKeepDeepEquality', () => { usesParentBounds: false, parentLayoutSystem: 'flex', layoutSystemForChildren: 'flex', + layoutSystemForChildrenInherited: false, providesBoundsForAbsoluteChildren: true, display: 'flex', position: 'absolute', @@ -383,6 +411,7 @@ describe('SpecialSizeMeasurementsKeepDeepEquality', () => { gap: 11, flexDirection: 'column', justifyContent: 'center', + alignContent: null, alignItems: 'auto', htmlElementName: 'div', renderedChildrenCount: 10, @@ -418,6 +447,13 @@ describe('SpecialSizeMeasurementsKeepDeepEquality', () => { gridAutoRows: null, gridAutoFlow: null, }, + parentContainerGridProperties: { + gridTemplateColumns: null, + gridTemplateRows: null, + gridAutoColumns: null, + gridAutoRows: null, + gridAutoFlow: null, + }, elementGridProperties: { gridColumnStart: null, gridColumnEnd: null, @@ -431,6 +467,13 @@ describe('SpecialSizeMeasurementsKeepDeepEquality', () => { gridAutoRows: null, gridAutoFlow: null, }, + parentContainerGridPropertiesFromProps: { + gridTemplateColumns: null, + gridTemplateRows: null, + gridAutoColumns: null, + gridAutoRows: null, + gridAutoFlow: null, + }, elementGridPropertiesFromProps: { gridColumnStart: null, gridColumnEnd: null, @@ -439,6 +482,17 @@ describe('SpecialSizeMeasurementsKeepDeepEquality', () => { }, rowGap: null, columnGap: null, + gridCellGlobalFrames: null, + parentGridCellGlobalFrames: null, + justifySelf: null, + alignSelf: null, + borderWidths: { + top: 0, + left: 0, + bottom: 0, + right: 0, + }, + parentGridFrame: null, } it('same reference returns the same reference', () => { @@ -524,6 +578,7 @@ describe('ElementInstanceMetadataKeepDeepEquality', () => { usesParentBounds: false, parentLayoutSystem: 'flex', layoutSystemForChildren: 'flex', + layoutSystemForChildrenInherited: false, providesBoundsForAbsoluteChildren: true, display: 'flex', position: 'absolute', @@ -556,6 +611,7 @@ describe('ElementInstanceMetadataKeepDeepEquality', () => { gap: 11, flexDirection: 'column', justifyContent: 'center', + alignContent: null, alignItems: 'auto', htmlElementName: 'div', renderedChildrenCount: 10, @@ -591,6 +647,13 @@ describe('ElementInstanceMetadataKeepDeepEquality', () => { gridAutoRows: null, gridAutoFlow: null, }, + parentContainerGridProperties: { + gridTemplateColumns: null, + gridTemplateRows: null, + gridAutoColumns: null, + gridAutoRows: null, + gridAutoFlow: null, + }, elementGridProperties: { gridColumnStart: null, gridColumnEnd: null, @@ -604,6 +667,13 @@ describe('ElementInstanceMetadataKeepDeepEquality', () => { gridAutoRows: null, gridAutoFlow: null, }, + parentContainerGridPropertiesFromProps: { + gridTemplateColumns: null, + gridTemplateRows: null, + gridAutoColumns: null, + gridAutoRows: null, + gridAutoFlow: null, + }, elementGridPropertiesFromProps: { gridColumnStart: null, gridColumnEnd: null, @@ -612,6 +682,17 @@ describe('ElementInstanceMetadataKeepDeepEquality', () => { }, rowGap: null, columnGap: null, + gridCellGlobalFrames: null, + parentGridCellGlobalFrames: null, + justifySelf: null, + alignSelf: null, + borderWidths: { + top: 0, + left: 0, + bottom: 0, + right: 0, + }, + parentGridFrame: null, }, computedStyle: { a: 'a', @@ -674,6 +755,7 @@ describe('ElementInstanceMetadataKeepDeepEquality', () => { usesParentBounds: false, parentLayoutSystem: 'flex', layoutSystemForChildren: 'flex', + layoutSystemForChildrenInherited: false, providesBoundsForAbsoluteChildren: true, display: 'flex', position: 'absolute', @@ -706,6 +788,7 @@ describe('ElementInstanceMetadataKeepDeepEquality', () => { gap: 11, flexDirection: 'column', justifyContent: 'center', + alignContent: null, alignItems: 'auto', htmlElementName: 'div', renderedChildrenCount: 10, @@ -741,6 +824,13 @@ describe('ElementInstanceMetadataKeepDeepEquality', () => { gridAutoRows: null, gridAutoFlow: null, }, + parentContainerGridProperties: { + gridTemplateColumns: null, + gridTemplateRows: null, + gridAutoColumns: null, + gridAutoRows: null, + gridAutoFlow: null, + }, elementGridProperties: { gridColumnStart: null, gridColumnEnd: null, @@ -754,6 +844,13 @@ describe('ElementInstanceMetadataKeepDeepEquality', () => { gridAutoRows: null, gridAutoFlow: null, }, + parentContainerGridPropertiesFromProps: { + gridTemplateColumns: null, + gridTemplateRows: null, + gridAutoColumns: null, + gridAutoRows: null, + gridAutoFlow: null, + }, elementGridPropertiesFromProps: { gridColumnStart: null, gridColumnEnd: null, @@ -762,6 +859,17 @@ describe('ElementInstanceMetadataKeepDeepEquality', () => { }, rowGap: null, columnGap: null, + gridCellGlobalFrames: null, + parentGridCellGlobalFrames: null, + justifySelf: null, + alignSelf: null, + borderWidths: { + top: 0, + left: 0, + bottom: 0, + right: 0, + }, + parentGridFrame: null, }, computedStyle: { a: 'a', @@ -849,6 +957,7 @@ describe('ElementInstanceMetadataMapKeepDeepEquality', () => { usesParentBounds: false, parentLayoutSystem: 'flex', layoutSystemForChildren: 'flex', + layoutSystemForChildrenInherited: false, providesBoundsForAbsoluteChildren: true, display: 'flex', position: 'absolute', @@ -881,6 +990,7 @@ describe('ElementInstanceMetadataMapKeepDeepEquality', () => { gap: 11, flexDirection: 'column', justifyContent: 'center', + alignContent: null, alignItems: 'auto', htmlElementName: 'div', renderedChildrenCount: 10, @@ -916,6 +1026,13 @@ describe('ElementInstanceMetadataMapKeepDeepEquality', () => { gridAutoRows: null, gridAutoFlow: null, }, + parentContainerGridProperties: { + gridTemplateColumns: null, + gridTemplateRows: null, + gridAutoColumns: null, + gridAutoRows: null, + gridAutoFlow: null, + }, elementGridProperties: { gridColumnStart: null, gridColumnEnd: null, @@ -929,6 +1046,13 @@ describe('ElementInstanceMetadataMapKeepDeepEquality', () => { gridAutoRows: null, gridAutoFlow: null, }, + parentContainerGridPropertiesFromProps: { + gridTemplateColumns: null, + gridTemplateRows: null, + gridAutoColumns: null, + gridAutoRows: null, + gridAutoFlow: null, + }, elementGridPropertiesFromProps: { gridColumnStart: null, gridColumnEnd: null, @@ -937,6 +1061,17 @@ describe('ElementInstanceMetadataMapKeepDeepEquality', () => { }, rowGap: null, columnGap: null, + gridCellGlobalFrames: null, + parentGridCellGlobalFrames: null, + justifySelf: null, + alignSelf: null, + borderWidths: { + top: 0, + left: 0, + bottom: 0, + right: 0, + }, + parentGridFrame: null, }, computedStyle: { a: 'a', @@ -1002,6 +1137,7 @@ describe('ElementInstanceMetadataMapKeepDeepEquality', () => { parentLayoutSystem: 'flex', parentFlexGap: 0, layoutSystemForChildren: 'flex', + layoutSystemForChildrenInherited: false, providesBoundsForAbsoluteChildren: true, display: 'flex', position: 'absolute', @@ -1033,6 +1169,7 @@ describe('ElementInstanceMetadataMapKeepDeepEquality', () => { gap: 11, flexDirection: 'column', justifyContent: 'center', + alignContent: null, alignItems: 'auto', htmlElementName: 'div', renderedChildrenCount: 10, @@ -1068,6 +1205,13 @@ describe('ElementInstanceMetadataMapKeepDeepEquality', () => { gridAutoRows: null, gridAutoFlow: null, }, + parentContainerGridProperties: { + gridTemplateColumns: null, + gridTemplateRows: null, + gridAutoColumns: null, + gridAutoRows: null, + gridAutoFlow: null, + }, elementGridProperties: { gridColumnStart: null, gridColumnEnd: null, @@ -1081,6 +1225,13 @@ describe('ElementInstanceMetadataMapKeepDeepEquality', () => { gridAutoRows: null, gridAutoFlow: null, }, + parentContainerGridPropertiesFromProps: { + gridTemplateColumns: null, + gridTemplateRows: null, + gridAutoColumns: null, + gridAutoRows: null, + gridAutoFlow: null, + }, elementGridPropertiesFromProps: { gridColumnStart: null, gridColumnEnd: null, @@ -1089,6 +1240,17 @@ describe('ElementInstanceMetadataMapKeepDeepEquality', () => { }, rowGap: null, columnGap: null, + gridCellGlobalFrames: null, + parentGridCellGlobalFrames: null, + justifySelf: null, + alignSelf: null, + borderWidths: { + top: 0, + left: 0, + bottom: 0, + right: 0, + }, + parentGridFrame: null, }, computedStyle: { a: 'a', @@ -1153,6 +1315,7 @@ describe('ElementInstanceMetadataMapKeepDeepEquality', () => { usesParentBounds: false, parentLayoutSystem: 'flex', layoutSystemForChildren: 'flex', + layoutSystemForChildrenInherited: false, providesBoundsForAbsoluteChildren: true, display: 'flex', position: 'absolute', @@ -1185,6 +1348,7 @@ describe('ElementInstanceMetadataMapKeepDeepEquality', () => { gap: 11, flexDirection: 'column', justifyContent: 'center', + alignContent: null, alignItems: 'auto', htmlElementName: 'div', renderedChildrenCount: 10, @@ -1220,6 +1384,13 @@ describe('ElementInstanceMetadataMapKeepDeepEquality', () => { gridAutoRows: null, gridAutoFlow: null, }, + parentContainerGridProperties: { + gridTemplateColumns: null, + gridTemplateRows: null, + gridAutoColumns: null, + gridAutoRows: null, + gridAutoFlow: null, + }, elementGridProperties: { gridColumnStart: null, gridColumnEnd: null, @@ -1233,6 +1404,13 @@ describe('ElementInstanceMetadataMapKeepDeepEquality', () => { gridAutoRows: null, gridAutoFlow: null, }, + parentContainerGridPropertiesFromProps: { + gridTemplateColumns: null, + gridTemplateRows: null, + gridAutoColumns: null, + gridAutoRows: null, + gridAutoFlow: null, + }, elementGridPropertiesFromProps: { gridColumnStart: null, gridColumnEnd: null, @@ -1241,6 +1419,17 @@ describe('ElementInstanceMetadataMapKeepDeepEquality', () => { }, rowGap: null, columnGap: null, + gridCellGlobalFrames: null, + parentGridCellGlobalFrames: null, + justifySelf: null, + alignSelf: null, + borderWidths: { + top: 0, + left: 0, + bottom: 0, + right: 0, + }, + parentGridFrame: null, }, computedStyle: { a: 'a', diff --git a/editor/src/components/editor/store/store-deep-equality-instances.ts b/editor/src/components/editor/store/store-deep-equality-instances.ts index 6f3b13d23626..d1f16d90eed1 100644 --- a/editor/src/components/editor/store/store-deep-equality-instances.ts +++ b/editor/src/components/editor/store/store-deep-equality-instances.ts @@ -151,6 +151,11 @@ import type { GridAutoOrTemplateBase, GridAutoOrTemplateDimensions, GridAutoOrTemplateFallback, + BorderWidths, + GridSpan, + GridSpanArea, + GridSpanNumeric, + GridPositionOrSpan, } from '../../../core/shared/element-template' import { elementInstanceMetadata, @@ -222,6 +227,7 @@ import { gridPositionValue, gridAutoOrTemplateFallback, gridAutoOrTemplateDimensions, + isGridSpan, } from '../../../core/shared/element-template' import type { CanvasRectangle, @@ -265,7 +271,6 @@ import { combine9EqualityCalls, unionDeepEquality, combine11EqualityCalls, - combine16EqualityCalls, combine15EqualityCalls, } from '../../../utils/deep-equality' import { @@ -314,7 +319,6 @@ import type { EditorStateGoogleFontsResources, EditorStateProjectSettings, EditorStateTopMenu, - EditorStatePreview, EditorStateHome, FileDeleteModal, ModalDialog, @@ -363,6 +367,10 @@ import type { RenderedAt, EditorRemixConfig, ErrorBoundaryHandling, + GridControlData, + GridIdentifier, + GridContainerIdentifier, + GridItemIdentifier, } from './editor-state' import { trueUpGroupElementChanged, @@ -375,6 +383,8 @@ import { newGithubData, renderedAtPropertyPath, renderedAtChildNode, + gridContainerIdentifier, + gridItemIdentifier, } from './editor-state' import { editorStateNodeModules, @@ -399,7 +409,6 @@ import { editorStateGoogleFontsResources, editorStateProjectSettings, editorStateTopMenu, - editorStatePreview, editorStateHome, fileDeleteModal, fileOverwriteModal, @@ -452,6 +461,7 @@ import type { GridResizeHandle, GridResizeEdge, GridGapHandle, + GridResizeRulerHandle, } from '../../canvas/canvas-strategies/interaction-state' import { boundingArea, @@ -464,6 +474,7 @@ import { gridAxisHandle, gridResizeHandle, gridGapHandle, + gridResizeRulerHandle, } from '../../canvas/canvas-strategies/interaction-state' import type { Modifiers } from '../../../utils/modifiers' import type { @@ -493,7 +504,7 @@ import type { CurriedUtopiaRequireFn, PropertyControlsInfo, ComponentDescriptorFromDescriptorFile, - TypedInpsectorSpec, + TypedInspectorSpec, ShownInspectorSpec, StyleSectionState, } from '../../custom-code/code-file' @@ -567,15 +578,17 @@ import type { GridCSSNumber, GridDimension, GridAutoFlow, + GridCSSRepeat, + GridCSSMinmax, } from '../../inspector/common/css-utils' import { cssNumber, fontSettings, gridCSSKeyword, + gridCSSMinmax, gridCSSNumber, + gridCSSRepeat, isCSSKeyword, - isGridCSSKeyword, - isGridCSSNumber, } from '../../inspector/common/css-utils' import type { ElementPaste, ProjectListing } from '../action-types' import { projectListing } from '../action-types' @@ -642,6 +655,24 @@ import type { ComponentDescriptorPropertiesBounds, } from '../../../core/property-controls/component-descriptor-parser' import type { Axis } from '../../../components/canvas/gap-utils' +import type { GridCellCoordinates } from '../../canvas/canvas-strategies/strategies/grid-cell-bounds' +import { + newImportState, + type ImportOperation, + type ImportState, +} from '../../../core/shared/import/import-operation-types' +import type { + ProjectRequirements, + RequirementResolution, +} from '../../../core/shared/import/project-health-check/utopia-requirements-types' +import { + newProjectRequirements, + requirementResolution, +} from '../../../core/shared/import/project-health-check/utopia-requirements-types' +import type { + StylePropsUpdatedDuringInteraction, + UpdatedProperties, +} from '../../canvas/plugins/style-plugins' export function ElementPropertyPathKeepDeepEquality(): KeepDeepEqualityCall { return combine2EqualityCalls( @@ -1976,7 +2007,7 @@ export const GridCSSNumberKeepDeepEquality: KeepDeepEqualityCall combine2EqualityCalls( (p) => p.value, CSSNumberKeepDeepEquality, - (p) => p.areaName, + (p) => p.lineName, NullableStringKeepDeepEquality, gridCSSNumber, ) @@ -1985,7 +2016,7 @@ export const GridCSSKeywordKeepDeepEquality: KeepDeepEqualityCall p.value, createCallWithTripleEquals(), - (p) => p.areaName, + (p) => p.lineName, NullableStringKeepDeepEquality, gridCSSKeyword, ) @@ -1993,15 +2024,81 @@ export const GridCSSKeywordKeepDeepEquality: KeepDeepEqualityCall = combine1EqualityCall( (dimension) => dimension, - unionDeepEquality( - GridCSSNumberKeepDeepEquality, - GridCSSKeywordKeepDeepEquality, - isGridCSSNumber, - isGridCSSKeyword, - ), + (oldValue: GridDimension, newValue: GridDimension): KeepDeepEqualityResult => { + switch (oldValue.type) { + case 'KEYWORD': + if (newValue.type === oldValue.type) { + return GridCSSKeywordKeepDeepEquality(oldValue, newValue) + } + break + case 'NUMBER': + if (newValue.type === oldValue.type) { + return GridCSSNumberKeepDeepEquality(oldValue, newValue) + } + break + case 'REPEAT': + if (newValue.type === oldValue.type) { + return GridCSSRepeatKeepDeepEquality(oldValue, newValue) + } + break + case 'MINMAX': + if (newValue.type === oldValue.type) { + return GridCSSMinmaxKeepDeepEquality(oldValue, newValue) + } + break + default: + assertNever(oldValue) + } + return keepDeepEqualityResult(newValue, false) + }, (dimension) => dimension, ) +export const GridCSSRepeatKeepDeepEquality: KeepDeepEqualityCall = + combine3EqualityCalls( + (p) => p.times, + createCallWithTripleEquals(), + (p) => p.value, + arrayDeepEquality(GridDimensionKeepDeepEquality), + (p) => p.lineName, + NullableStringKeepDeepEquality, + gridCSSRepeat, + ) + +const GridCSSNumberOrKeywordKeepDeepEquality: KeepDeepEqualityCall = + combine1EqualityCall( + (dim) => dim, + (oldValue, newValue) => { + switch (oldValue.type) { + case 'KEYWORD': + if (newValue.type === oldValue.type) { + return GridCSSKeywordKeepDeepEquality(oldValue, newValue) + } + break + case 'NUMBER': + if (newValue.type === oldValue.type) { + return GridCSSNumberKeepDeepEquality(oldValue, newValue) + } + break + default: + assertNever(oldValue) + } + return keepDeepEqualityResult(newValue, false) + }, + (dim) => dim, + ) + +export const GridCSSMinmaxKeepDeepEquality: KeepDeepEqualityCall = + combine3EqualityCalls( + (p) => p.min, + GridCSSNumberOrKeywordKeepDeepEquality, + (p) => p.max, + GridCSSNumberOrKeywordKeepDeepEquality, + (p) => p.lineName, + NullableStringKeepDeepEquality, + gridCSSMinmax, + ) + export const GridAutoOrTemplateDimensionsKeepDeepEquality: KeepDeepEqualityCall = combine1EqualityCall( (value) => value.dimensions, @@ -2084,20 +2181,84 @@ export const GridPositionKeepDeepEquality: KeepDeepEqualityCall = } } +export const GridSpanAreaKeepDeepEquality: KeepDeepEqualityCall = + combine1EqualityCall( + (p) => p.value, + StringKeepDeepEquality, + (value) => ({ type: 'SPAN_AREA', value: value }), + ) + +export const GridSpanNumericKeepDeepEquality: KeepDeepEqualityCall = + combine1EqualityCall( + (p) => p.value, + NumberKeepDeepEquality, + (value) => ({ type: 'SPAN_NUMERIC', value: value }), + ) + +export const GridSpanKeepDeepEquality: KeepDeepEqualityCall = (oldValue, newValue) => { + switch (oldValue.type) { + case 'SPAN_AREA': + if (newValue.type === oldValue.type) { + return GridSpanAreaKeepDeepEquality(oldValue, newValue) + } + break + case 'SPAN_NUMERIC': + if (newValue.type === oldValue.type) { + return GridSpanNumericKeepDeepEquality(oldValue, newValue) + } + break + default: + assertNever(oldValue) + } + return keepDeepEqualityResult(newValue, false) +} + +export const GridPositionOrSpanKeepDeepEquality: KeepDeepEqualityCall = ( + oldValue, + newValue, +) => { + if (isGridSpan(oldValue)) { + if (isGridSpan(newValue)) { + return GridSpanKeepDeepEquality(oldValue, newValue) + } else { + return keepDeepEqualityResult(newValue, false) + } + } else { + if (isGridSpan(newValue)) { + return keepDeepEqualityResult(newValue, false) + } + return GridPositionKeepDeepEquality(oldValue, newValue) + } +} + export function GridElementPropertiesKeepDeepEquality(): KeepDeepEqualityCall { return combine4EqualityCalls( (properties) => properties.gridColumnStart, - nullableDeepEquality(GridPositionKeepDeepEquality), + nullableDeepEquality(GridPositionOrSpanKeepDeepEquality), (properties) => properties.gridColumnEnd, - nullableDeepEquality(GridPositionKeepDeepEquality), + nullableDeepEquality(GridPositionOrSpanKeepDeepEquality), (properties) => properties.gridRowStart, - nullableDeepEquality(GridPositionKeepDeepEquality), + nullableDeepEquality(GridPositionOrSpanKeepDeepEquality), (properties) => properties.gridRowEnd, - nullableDeepEquality(GridPositionKeepDeepEquality), + nullableDeepEquality(GridPositionOrSpanKeepDeepEquality), gridElementProperties, ) } +export function BorderWidthsKeepDeepEquality(): KeepDeepEqualityCall { + return combine4EqualityCalls( + (p) => p.bottom, + NumberKeepDeepEquality, + (p) => p.left, + NumberKeepDeepEquality, + (p) => p.right, + NumberKeepDeepEquality, + (p) => p.top, + NumberKeepDeepEquality, + (bottom, left, right, top) => ({ bottom, left, right, top }), + ) +} + export function SpecialSizeMeasurementsKeepDeepEquality(): KeepDeepEqualityCall { return (oldSize, newSize) => { const offsetResult = LocalPointKeepDeepEquality(oldSize.offset, newSize.offset) @@ -2124,6 +2285,8 @@ export function SpecialSizeMeasurementsKeepDeepEquality(): KeepDeepEqualityCall< oldSize.layoutSystemForChildren, newSize.layoutSystemForChildren, ).areEqual + const layoutSystemForChildrenInheritedResult = + oldSize.layoutSystemForChildrenInherited === newSize.layoutSystemForChildrenInherited const providesBoundsForAbsoluteChildrenResult = oldSize.providesBoundsForAbsoluteChildren === newSize.providesBoundsForAbsoluteChildren const positionResult = oldSize.position === newSize.position @@ -2178,6 +2341,10 @@ export function SpecialSizeMeasurementsKeepDeepEquality(): KeepDeepEqualityCall< oldSize.containerGridProperties, newSize.containerGridProperties, ).areEqual + const parentGridContainerPropertiesEqual = GridContainerPropertiesKeepDeepEquality()( + oldSize.parentContainerGridProperties, + newSize.parentContainerGridProperties, + ).areEqual const gridElementPropertiesEqual = GridElementPropertiesKeepDeepEquality()( oldSize.elementGridProperties, newSize.elementGridProperties, @@ -2187,6 +2354,10 @@ export function SpecialSizeMeasurementsKeepDeepEquality(): KeepDeepEqualityCall< oldSize.containerGridPropertiesFromProps, newSize.containerGridPropertiesFromProps, ).areEqual + const parentGridContainerPropertiesFromPropsEqual = GridContainerPropertiesKeepDeepEquality()( + oldSize.parentContainerGridPropertiesFromProps, + newSize.parentContainerGridPropertiesFromProps, + ).areEqual const gridElementPropertiesFromPropsEqual = GridElementPropertiesKeepDeepEquality()( oldSize.elementGridPropertiesFromProps, newSize.elementGridPropertiesFromProps, @@ -2198,6 +2369,21 @@ export function SpecialSizeMeasurementsKeepDeepEquality(): KeepDeepEqualityCall< newSize.columnGap, ).areEqual + const justifySelfEquals = oldSize.justifySelf === newSize.justifySelf + const alignSelfEquals = oldSize.alignSelf === newSize.alignSelf + + const gridCellGlobalFramesEqual = nullableDeepEquality( + arrayDeepEquality(arrayDeepEquality(CanvasRectangleKeepDeepEquality)), + )(oldSize.gridCellGlobalFrames, newSize.gridCellGlobalFrames).areEqual + const parentGridCellGlobalFramesEqual = nullableDeepEquality( + arrayDeepEquality(arrayDeepEquality(CanvasRectangleKeepDeepEquality)), + )(oldSize.gridCellGlobalFrames, newSize.gridCellGlobalFrames).areEqual + + const borderWidthsEqual = BorderWidthsKeepDeepEquality()( + oldSize.borderWidths, + newSize.borderWidths, + ).areEqual + const areEqual = offsetResult.areEqual && coordinateSystemBoundsResult.areEqual && @@ -2208,6 +2394,7 @@ export function SpecialSizeMeasurementsKeepDeepEquality(): KeepDeepEqualityCall< usesParentBoundsResult && parentLayoutSystemResult && layoutSystemForChildrenResult && + layoutSystemForChildrenInheritedResult && providesBoundsForAbsoluteChildrenResult && positionResult && marginResult.areEqual && @@ -2241,11 +2428,18 @@ export function SpecialSizeMeasurementsKeepDeepEquality(): KeepDeepEqualityCall< textBoundsEqual && computedHugPropertyEqual && gridContainerPropertiesEqual && + parentGridContainerPropertiesEqual && gridElementPropertiesEqual && gridContainerPropertiesFromPropsEqual && + parentGridContainerPropertiesFromPropsEqual && gridElementPropertiesFromPropsEqual && rowGapEquals && - columnGapEquals + columnGapEquals && + justifySelfEquals && + alignSelfEquals && + gridCellGlobalFramesEqual && + parentGridCellGlobalFramesEqual && + borderWidthsEqual if (areEqual) { return keepDeepEqualityResult(oldSize, true) } else { @@ -2259,6 +2453,7 @@ export function SpecialSizeMeasurementsKeepDeepEquality(): KeepDeepEqualityCall< newSize.usesParentBounds, newSize.parentLayoutSystem, newSize.layoutSystemForChildren, + newSize.layoutSystemForChildrenInherited, newSize.providesBoundsForAbsoluteChildren, newSize.display, newSize.position, @@ -2276,6 +2471,7 @@ export function SpecialSizeMeasurementsKeepDeepEquality(): KeepDeepEqualityCall< newSize.gap, newSize.flexDirection, newSize.justifyContent, + newSize.alignContent, newSize.alignItems, newSize.htmlElementName, newSize.renderedChildrenCount, @@ -2292,11 +2488,19 @@ export function SpecialSizeMeasurementsKeepDeepEquality(): KeepDeepEqualityCall< newSize.textBounds, newSize.computedHugProperty, newSize.containerGridProperties, + newSize.parentContainerGridProperties, newSize.elementGridProperties, newSize.containerGridPropertiesFromProps, + newSize.parentContainerGridPropertiesFromProps, newSize.elementGridPropertiesFromProps, newSize.rowGap, newSize.columnGap, + newSize.gridCellGlobalFrames, + newSize.parentGridCellGlobalFrames, + newSize.justifySelf, + newSize.alignSelf, + newSize.borderWidths, + newSize.parentGridFrame, ) return keepDeepEqualityResult(sizeMeasurements, false) } @@ -2644,6 +2848,66 @@ export const DragToMoveIndicatorFlagsKeepDeepEquality: KeepDeepEqualityCall = + combine2EqualityCalls( + (data) => data.row, + createCallWithTripleEquals(), + (data) => data.column, + createCallWithTripleEquals(), + (row, column) => ({ row, column }), + ) + +export const GridContainerIdentifierKeepDeepEquality: KeepDeepEqualityCall = + combine1EqualityCall( + (identifier) => identifier.container, + ElementPathKeepDeepEquality, + gridContainerIdentifier, + ) + +export const GridItemIdentifierKeepDeepEquality: KeepDeepEqualityCall = + combine1EqualityCall( + (identifier) => identifier.item, + ElementPathKeepDeepEquality, + gridItemIdentifier, + ) + +export const GridIdentifierKeepDeepEquality: KeepDeepEqualityCall = ( + oldValue, + newValue, +) => { + switch (oldValue.type) { + case 'GRID_CONTAINER': + if (newValue.type === oldValue.type) { + return GridContainerIdentifierKeepDeepEquality(oldValue, newValue) + } + break + case 'GRID_ITEM': + if (newValue.type === oldValue.type) { + return GridItemIdentifierKeepDeepEquality(oldValue, newValue) + } + break + default: + const _exhaustiveCheck: never = oldValue + throw new Error(`Unhandled type ${JSON.stringify(oldValue)}`) + } + return keepDeepEqualityResult(newValue, false) +} + +const GridControlDataKeepDeepEquality: KeepDeepEqualityCall = + combine3EqualityCalls( + (data) => data.grid, + GridIdentifierKeepDeepEquality, + (data) => data.targetCell, + nullableDeepEquality(GridCellCoordinatesKeepDeepEquality), + (data) => data.rootCell, + nullableDeepEquality(GridCellCoordinatesKeepDeepEquality), + (grid, targetCell, rootCell) => ({ + grid, + targetCell, + rootCell, + }), + ) + export const EditorStateCanvasControlsKeepDeepEquality: KeepDeepEqualityCall = combine9EqualityCalls( (controls) => controls.snappingGuidelines, @@ -2662,8 +2926,8 @@ export const EditorStateCanvasControlsKeepDeepEquality: KeepDeepEqualityCall controls.parentOutlineHighlight, nullableDeepEquality(ElementPathKeepDeepEquality), - (controls) => controls.gridControls, - nullableDeepEquality(ElementPathKeepDeepEquality), + (controls) => controls.gridControlData, + nullableDeepEquality(GridControlDataKeepDeepEquality), editorStateCanvasControls, ) @@ -2895,9 +3159,9 @@ export const BorderRadiusResizeHandleKeepDeepEquality: KeepDeepEqualityCall< export const GridCellHandleKeepDeepEquality: KeepDeepEqualityCall = combine1EqualityCall( - (handle) => handle.id, - createCallWithTripleEquals(), - (id) => gridCellHandle({ id }), + (handle) => handle.path, + ElementPathKeepDeepEquality, + (path) => gridCellHandle({ path }), ) export const GridAxisHandleKeepDeepEquality: KeepDeepEqualityCall = @@ -2918,6 +3182,15 @@ export const GridResizeHandleKeepDeepEquality: KeepDeepEqualityCall = + combine2EqualityCalls( + (handle) => handle.id, + createCallWithTripleEquals(), + (handle) => handle.edge, + createCallWithTripleEquals(), + gridResizeRulerHandle, + ) + export const GridGapHandleKeepDeepEquality: KeepDeepEqualityCall = combine1EqualityCall((handle) => handle.axis, createCallWithTripleEquals(), gridGapHandle) @@ -2981,9 +3254,13 @@ export const CanvasControlTypeKeepDeepEquality: KeepDeepEqualityCall { - return combine3EqualityCalls( + return combine4EqualityCalls( (pathTree) => pathTree.path, ElementPathKeepDeepEquality, (pathTree) => pathTree.pathString, createCallWithTripleEquals(), - (pathTree) => pathTree.children, + (pathTree) => pathTree.innerChildren, + arrayDeepEquality(ElementPathTreeKeepDeepEquality), + (pathTree) => pathTree.propsChildren, arrayDeepEquality(ElementPathTreeKeepDeepEquality), elementPathTree, )(oldValue, newValue) @@ -3798,7 +4077,7 @@ export function ComponentDescriptorSourceKeepDeepEquality(): KeepDeepEqualityCal } } -const InspectorSpecKeepDeepEquality: KeepDeepEqualityCall = ( +const InspectorSpecKeepDeepEquality: KeepDeepEqualityCall = ( oldValue, newValue, ) => { @@ -4322,15 +4601,6 @@ export const EditorStateTopMenuKeepDeepEquality: KeepDeepEqualityCall = - combine2EqualityCalls( - (preview) => preview.visible, - BooleanKeepDeepEquality, - (preview) => preview.connected, - BooleanKeepDeepEquality, - editorStatePreview, - ) - export const EditorStateHomeKeepDeepEquality: KeepDeepEqualityCall = combine1EqualityCall((preview) => preview.visible, BooleanKeepDeepEquality, editorStateHome) @@ -4662,6 +4932,47 @@ export const ProjectGithubSettingsKeepDeepEquality: KeepDeepEqualityCall = + combine3EqualityCalls( + (resolution) => resolution.status, + createCallWithTripleEquals(), + (resolution) => resolution.value, + createCallWithTripleEquals(), + (resolution) => resolution.resolution, + createCallWithTripleEquals(), + requirementResolution, + ) + +export const ProjectRequirementsKeepDeepEquality: KeepDeepEqualityCall = + combine5EqualityCalls( + (requirements) => requirements.storyboard, + ProjectRequirementResolutionKeepDeepEquality, + (requirements) => requirements.packageJsonEntries, + ProjectRequirementResolutionKeepDeepEquality, + (requirements) => requirements.language, + ProjectRequirementResolutionKeepDeepEquality, + (requirements) => requirements.reactVersion, + ProjectRequirementResolutionKeepDeepEquality, + (requirements) => requirements.serverPackages, + ProjectRequirementResolutionKeepDeepEquality, + newProjectRequirements, + ) + +export const ImportOperationKeepDeepEquality: KeepDeepEqualityCall = ( + oldValue, + newValue, +) => { + if (oldValue.type !== newValue.type) { + return keepDeepEqualityResult(newValue, false) + } else if (oldValue.id !== newValue.id) { + return keepDeepEqualityResult(newValue, false) + } + return keepDeepEqualityResult(oldValue, true) +} + +export const ImportOperationsKeepDeepEquality: KeepDeepEqualityCall> = + arrayDeepEquality(ImportOperationKeepDeepEquality) + export const GithubFileChangesKeepDeepEquality: KeepDeepEqualityCall = combine3EqualityCalls( (settings) => settings.modified, @@ -4735,6 +5046,14 @@ export const GithubOperationKeepDeepEquality: KeepDeepEqualityCall> = arrayDeepEquality(GithubOperationKeepDeepEquality) +export const ImportStateKeepDeepEquality: KeepDeepEqualityCall = combine2EqualityCalls( + (importState) => importState.importStatus, + createCallWithTripleEquals(), + (importState) => importState.importOperations, + arrayDeepEquality(ImportOperationKeepDeepEquality), + newImportState, +) + export const ColorSwatchDeepEquality: KeepDeepEqualityCall = combine2EqualityCalls( (c) => c.id, StringKeepDeepEquality, @@ -4922,12 +5241,17 @@ export const TrueUpChildrenOfGroupChangedKeepDeepEquality: KeepDeepEqualityCall< ) export const TrueUpHuggingElementKeepDeepEquality: KeepDeepEqualityCall = - combine2EqualityCalls( + combine4EqualityCalls( (value) => value.target, ElementPathKeepDeepEquality, + (value) => value.elementFrame, + CanvasRectangleKeepDeepEquality, (value) => value.frame, CanvasRectangleKeepDeepEquality, - (target, frame) => trueUpHuggingElement(target, frame), + (value) => value.huggingElementContentsStatus, + createCallWithTripleEquals(), + (target, elementFrame, frame, huggingElementContentsStatus) => + trueUpHuggingElement(target, elementFrame, frame, huggingElementContentsStatus), ) export const TrueUpTargetKeepDeepEquality: KeepDeepEqualityCall = ( @@ -5012,6 +5336,21 @@ export const RemixConfigKeepDeepEquality: KeepDeepEqualityCall = + combine2EqualityCalls( + (data) => data.propertiesDeleted, + arrayDeepEquality(StringKeepDeepEquality), + (data) => data.propertiesUpdated, + arrayDeepEquality(StringKeepDeepEquality), + (propertiesDeleted, propertiesUpdated) => ({ + propertiesDeleted, + propertiesUpdated, + }), + ) + +export const PropertiesUpdatedDuringInteractionKeepDeepEquality: KeepDeepEqualityCall = + objectDeepEquality(StylePropsUpdatedDuringInteractionKeepDeepEquality) + export const EditorStateKeepDeepEquality: KeepDeepEqualityCall = ( oldValue, newValue, @@ -5166,7 +5505,6 @@ export const EditorStateKeepDeepEquality: KeepDeepEqualityCall = ( ) const navigatorResults = NavigatorStateKeepDeepEquality(oldValue.navigator, newValue.navigator) const topmenuResults = EditorStateTopMenuKeepDeepEquality(oldValue.topmenu, newValue.topmenu) - const previewResults = EditorStatePreviewKeepDeepEquality(oldValue.preview, newValue.preview) const homeResults = EditorStateHomeKeepDeepEquality(oldValue.home, newValue.home) const lastUsedFontResults = nullableDeepEquality(FontSettingsKeepDeepEquality)( oldValue.lastUsedFont, @@ -5266,6 +5604,18 @@ export const EditorStateKeepDeepEquality: KeepDeepEqualityCall = ( newValue.githubOperations, ) + const importStateResults = ImportStateKeepDeepEquality(oldValue.importState, newValue.importState) + + const importWizardOpenResults = BooleanKeepDeepEquality( + oldValue.importWizardOpen, + newValue.importWizardOpen, + ) + + const projectRequirementsResults = ProjectRequirementsKeepDeepEquality( + oldValue.projectRequirements, + newValue.projectRequirements, + ) + const branchContentsResults = nullableDeepEquality(ProjectContentTreeRootKeepDeepEquality())( oldValue.branchOriginContents, newValue.branchOriginContents, @@ -5318,6 +5668,12 @@ export const EditorStateKeepDeepEquality: KeepDeepEqualityCall = ( newValue.editorRemixConfig, ) + const propertiesUpdatedDuringInteractionResults = + PropertiesUpdatedDuringInteractionKeepDeepEquality( + oldValue.propertiesUpdatedDuringInteraction, + newValue.propertiesUpdatedDuringInteraction, + ) + const areEqual = idResult.areEqual && forkedFromProjectIdResult.areEqual && @@ -5361,7 +5717,6 @@ export const EditorStateKeepDeepEquality: KeepDeepEqualityCall = ( projectSettingsResults.areEqual && navigatorResults.areEqual && topmenuResults.areEqual && - previewResults.areEqual && homeResults.areEqual && lastUsedFontResults.areEqual && modalResults.areEqual && @@ -5388,6 +5743,9 @@ export const EditorStateKeepDeepEquality: KeepDeepEqualityCall = ( githubSettingsResults.areEqual && imageDragSessionStateEqual.areEqual && githubOperationsResults.areEqual && + importStateResults.areEqual && + importWizardOpenResults.areEqual && + projectRequirementsResults.areEqual && branchContentsResults.areEqual && githubDataResults.areEqual && refreshingDependenciesResults.areEqual && @@ -5399,7 +5757,8 @@ export const EditorStateKeepDeepEquality: KeepDeepEqualityCall = ( forkingResults.areEqual && collaboratorsResults.areEqual && sharingDialogOpenResults.areEqual && - remixConfigResults.areEqual + remixConfigResults.areEqual && + propertiesUpdatedDuringInteractionResults.areEqual if (areEqual) { return keepDeepEqualityResult(oldValue, true) @@ -5447,7 +5806,6 @@ export const EditorStateKeepDeepEquality: KeepDeepEqualityCall = ( projectSettingsResults.value, navigatorResults.value, topmenuResults.value, - previewResults.value, homeResults.value, lastUsedFontResults.value, modalResults.value, @@ -5475,6 +5833,9 @@ export const EditorStateKeepDeepEquality: KeepDeepEqualityCall = ( githubSettingsResults.value, imageDragSessionStateEqual.value, githubOperationsResults.value, + importStateResults.value, + importWizardOpenResults.value, + projectRequirementsResults.value, branchContentsResults.value, githubDataResults.value, refreshingDependenciesResults.value, @@ -5487,6 +5848,7 @@ export const EditorStateKeepDeepEquality: KeepDeepEqualityCall = ( collaboratorsResults.value, sharingDialogOpenResults.value, remixConfigResults.value, + propertiesUpdatedDuringInteractionResults.value, ) return keepDeepEqualityResult(newEditorState, false) diff --git a/editor/src/components/editor/store/store-hook-substore-helpers.ts b/editor/src/components/editor/store/store-hook-substore-helpers.ts index 7e41cf6cc104..a0fb1af4feb6 100644 --- a/editor/src/components/editor/store/store-hook-substore-helpers.ts +++ b/editor/src/components/editor/store/store-hook-substore-helpers.ts @@ -1,4 +1,6 @@ -import type { EditorState } from './editor-state' +import { type EditorState } from './editor-state' +import { emptyProjectRequirements } from '../../../core/shared/import/project-health-check/utopia-requirements-types' +import { emptyImportState } from '../../../core/shared/import/import-operation-types' export const EmptyEditorStateForKeysOnly: EditorState = { id: null, @@ -88,7 +90,7 @@ export const EmptyEditorStateForKeysOnly: EditorState = { reparentedToPaths: [], dragToMoveIndicatorFlags: null as any, parentOutlineHighlight: null, - gridControls: null, + gridControlData: null, }, }, inspector: { @@ -124,10 +126,6 @@ export const EmptyEditorStateForKeysOnly: EditorState = { formulaBarMode: 'content', formulaBarFocusCounter: 0, }, - preview: { - visible: false, - connected: false, - }, home: { visible: false, }, @@ -161,6 +159,9 @@ export const EmptyEditorStateForKeysOnly: EditorState = { githubSettings: null as any, imageDragSessionState: null as any, githubOperations: [], + importState: emptyImportState(), + importWizardOpen: false, + projectRequirements: emptyProjectRequirements(), branchOriginContents: null, githubData: null as any, refreshingDependencies: false, @@ -178,4 +179,5 @@ export const EmptyEditorStateForKeysOnly: EditorState = { editorRemixConfig: { errorBoundaryHandling: 'ignore-error-boundaries', }, + propertiesUpdatedDuringInteraction: {}, } diff --git a/editor/src/components/editor/store/store-hook-substore-types.ts b/editor/src/components/editor/store/store-hook-substore-types.ts index c6a0fd468326..eea6a848a3f9 100644 --- a/editor/src/components/editor/store/store-hook-substore-types.ts +++ b/editor/src/components/editor/store/store-hook-substore-types.ts @@ -158,6 +158,18 @@ const propertyControlsInfoSubstate = { } as const export type PropertyControlsInfoSubstate = typeof propertyControlsInfoSubstate +// StyleInfoSubstate +export const styleInfoSubstateKeys = [ + ...metadataSubstateKeys, + ...projectContentsKeys, + 'codeResultCache', +] as const +const emptyStyleInfoSubstate = { + editor: pick(styleInfoSubstateKeys, EmptyEditorStateForKeysOnly), +} as const +export type StyleInfoSubstate = typeof emptyStyleInfoSubstate +export type StyleInfoSubEditorState = StyleInfoSubstate['editor'] + export type MetadataAndPropertyControlsInfoSubstate = MetadataSubstate & PropertyControlsInfoSubstate @@ -174,7 +186,13 @@ export interface ThemeSubstate { } // GithubSubstate -export const githubSubstateKeys = ['githubSettings', 'githubOperations', 'githubData'] as const +export const githubSubstateKeys = [ + 'githubSettings', + 'githubOperations', + 'githubData', + 'importState', + 'projectRequirements', +] as const export const emptyGithubSubstate = { editor: pick(githubSubstateKeys, EmptyEditorStateForKeysOnly), } as const diff --git a/editor/src/components/editor/store/store-hook.ts b/editor/src/components/editor/store/store-hook.ts index 9ff6287b8a82..172dec350cca 100644 --- a/editor/src/components/editor/store/store-hook.ts +++ b/editor/src/components/editor/store/store-hook.ts @@ -40,6 +40,7 @@ import type { RestOfEditorState, SelectedViewsSubstate, StoreKey, + StyleInfoSubstate, Substates, ThemeSubstate, UserStateSubstate, @@ -58,6 +59,7 @@ import { restOfEditorStateKeys, restOfStoreKeys, selectedViewsSubstateKeys, + styleInfoSubstateKeys, variablesInScopeSubstateKeys, } from './store-hook-substore-types' import { Getter } from '../hook-utils' @@ -80,6 +82,8 @@ export const EditorStateContext = React.createContext(nul EditorStateContext.displayName = 'EditorStateContext' export const CanvasStateContext = React.createContext(null) CanvasStateContext.displayName = 'CanvasStateContext' +export const HelperControlsStateContext = React.createContext(null) +HelperControlsStateContext.displayName = 'HelperControlsStateContext' export const LowPriorityStateContext = React.createContext(null) LowPriorityStateContext.displayName = 'LowPriorityStateContext' @@ -355,6 +359,9 @@ export const Substores = { b.editor, ) }, + styleInfo: (a: StyleInfoSubstate, b: StyleInfoSubstate) => { + return keysEquality(styleInfoSubstateKeys, a.editor, b.editor) + }, } as const export const SubstateEqualityFns: { diff --git a/editor/src/components/editor/store/store-monitor.ts b/editor/src/components/editor/store/store-monitor.ts new file mode 100644 index 000000000000..9d6b39284443 --- /dev/null +++ b/editor/src/components/editor/store/store-monitor.ts @@ -0,0 +1,33 @@ +import type { ElementPath } from 'utopia-shared/src/types' +import { Substores, useEditorState } from './store-hook' +import { mapDropNulls } from '../../../core/shared/array-utils' +import { withUnderlyingTarget } from './editor-state' + +export function useMonitorChangesToElements(elementPaths: Array): void { + useEditorState( + Substores.projectContents, + (store) => { + return mapDropNulls((elementPath) => { + return withUnderlyingTarget( + elementPath, + store.editor.projectContents, + null, + (_, element) => { + return element + }, + ) + }, elementPaths) + }, + 'useMonitorChangesToElements', + ) +} + +export function useMonitorChangesToEditor(): void { + useEditorState( + Substores.fullStore, + (store) => { + return { editor: store.editor, derived: store.derived } + }, + 'useMonitorChangesToEditor', + ) +} diff --git a/editor/src/components/editor/store/worker-update-race.spec.tsx b/editor/src/components/editor/store/worker-update-race.spec.tsx index f07d6bc520bd..bd3a636d7210 100644 --- a/editor/src/components/editor/store/worker-update-race.spec.tsx +++ b/editor/src/components/editor/store/worker-update-race.spec.tsx @@ -9,6 +9,8 @@ import { getProjectFileByFilePath } from '../../assets' import { renderTestEditorWithModel } from '../../canvas/ui-jsx.test-utils' import { updateFile } from '../actions/action-creators' import { StoryboardFilePath } from './editor-state' +import type { ParseCacheOptions } from '../../../core/shared/parse-cache-utils' +import type { FilePathMappings } from '../../../core/model/project-file-utils' // We have to prefix all of these with "mock" otherwise Jest won't allow us to use them below const mockDefer = defer @@ -22,13 +24,24 @@ jest.mock('../../../core/workers/common/worker-types', () => ({ async getParseResult( workers: any, files: Array, + filePathMappings: FilePathMappings, alreadyExistingUIDs: Set, applySteganography: SteganographyMode, + parserChunkCount: number, + parsingCacheOptions: ParseCacheOptions, ): Promise> { mockParseStartedCount++ const result = await jest .requireActual('../../../core/workers/common/worker-types') - .getParseResult(workers, files, alreadyExistingUIDs, applySteganography) + .getParseResult( + workers, + files, + filePathMappings, + alreadyExistingUIDs, + applySteganography, + parserChunkCount, + parsingCacheOptions, + ) mockLock2.resolve() await mockLock1 mockLock1 = mockDefer() diff --git a/editor/src/components/github/github-repository-clone-flow.tsx b/editor/src/components/github/github-repository-clone-flow.tsx index fbd56a47ef4d..9ea1ca795b86 100644 --- a/editor/src/components/github/github-repository-clone-flow.tsx +++ b/editor/src/components/github/github-repository-clone-flow.tsx @@ -26,6 +26,11 @@ import { CloneParamKey } from '../editor/persistence/persistence-backend' import { getPublicRepositoryEntryOrNull } from '../../core/shared/github/operations/load-repositories' import { OperationContext } from '../../core/shared/github/operations/github-operation-context' import { notice } from '../common/notice' +import { isFeatureEnabled } from '../../utils/feature-switches' +import { + notifyOperationCriticalError, + startImportProcess, +} from '../../core/shared/import/import-operation-service' export const LoadActionsDispatched = 'loadActionDispatched' @@ -123,7 +128,16 @@ async function cloneGithubRepo( repo: githubRepo.repository, }) if (repositoryEntry == null) { - dispatch([showToast(notice('Cannot find repository', 'ERROR'))]) + if (isFeatureEnabled('Import Wizard')) { + startImportProcess(dispatch) + notifyOperationCriticalError(dispatch, { + type: 'loadBranch', + branchName: githubRepo.branch ?? undefined, + githubRepo: githubRepo, + }) + } else { + dispatch([showToast(notice('Cannot find repository', 'ERROR'))]) + } return } const githubBranch = githubRepo.branch ?? repositoryEntry.defaultBranch diff --git a/editor/src/components/inspector/add-remove-layout-system-control.tsx b/editor/src/components/inspector/add-remove-layout-system-control.tsx index 26499eb80cef..3da4b06c2f8f 100644 --- a/editor/src/components/inspector/add-remove-layout-system-control.tsx +++ b/editor/src/components/inspector/add-remove-layout-system-control.tsx @@ -2,7 +2,14 @@ /** @jsx jsx */ import React from 'react' import { jsx } from '@emotion/react' -import { Icons, FlexRow, SquareButton, InspectorSectionHeader, useColorTheme } from '../../uuiui' +import { + Icons, + FlexRow, + SquareButton, + InspectorSectionHeader, + useColorTheme, + Button, +} from '../../uuiui' import { Substores, useEditorState, useRefEditorState } from '../editor/store/store-hook' import { metadataSelector, selectedViewsSelector } from './inpector-selectors' import { @@ -15,10 +22,17 @@ import { executeFirstApplicableStrategy } from './inspector-strategies/inspector import { useDispatch } from '../editor/store/dispatch-context' import { NO_OP, assertNever } from '../../core/shared/utils' import type { DropdownMenuItem } from '../../uuiui/radix-components' -import { DropdownMenu, regularDropdownMenuItem } from '../../uuiui/radix-components' +import { + DropdownMenu, + regularDropdownMenuItem, + separatorDropdownMenuItem, +} from '../../uuiui/radix-components' import { stripNulls } from '../../core/shared/array-utils' import { layoutSystemSelector } from './flex-section' -import { optionalMap } from '../../core/shared/optional-utils' +import { AdvancedGridModal } from './controls/advanced-grid-modal' +import { when } from '../../utils/react-conditionals' +import { useDesignPanelContext } from '../canvas/design-panel-root' +import { useGridAdvancedPropertiesCount } from './use-grid-advanced-properties' export const AddRemoveLayoutSystemControlTestId = (): string => 'AddRemoveLayoutSystemControlTestId' export const AddFlexLayoutOptionId = 'add-flex-layout' @@ -27,7 +41,19 @@ export const RemoveLayoutSystemOptionId = 'remove-layout-system' interface AddRemoveLayoutSystemControlProps {} +// adjusting the modal position to have some space from the left edge of the design panel +const X_OFFSET = 20 +const Y_OFFSET = 20 + export const AddRemoveLayoutSystemControl = React.memo(() => { + const [popupOpen, setPopupOpen] = React.useState(false) + const closePopup = React.useCallback(() => { + setPopupOpen(false) + }, []) + const openPopup = React.useCallback(() => { + setPopupOpen(true) + }, []) + const layoutSystem = useEditorState( Substores.metadata, layoutSystemSelector, @@ -87,41 +113,81 @@ export const AddRemoveLayoutSystemControl = React.memo ( - - {layoutSystem == null ? : } + const addLayoutSystemOpenerButton = React.useCallback(() => { + return ( + + {layoutSystem == null ? ( + + ) : ( + + )} - ), - [layoutSystem], - ) + ) + }, [layoutSystem, popupOpen]) - const addLayoutSystemMenuDropdownItems = React.useMemo( - (): DropdownMenuItem[] => - stripNulls([ - regularDropdownMenuItem({ - id: AddFlexLayoutOptionId, - label: 'Convert to Flex', - onSelect: addFlexLayoutSystem, - }), - regularDropdownMenuItem({ - id: AddGridLayoutOptionId, - label: 'Convert to Grid', - onSelect: addGridLayoutSystem, - }), - optionalMap( - () => - regularDropdownMenuItem({ - id: RemoveLayoutSystemOptionId, - label: 'Remove layout system', - onSelect: removeLayoutSystem, - danger: true, - }), - layoutSystem, - ), - ]), - [addFlexLayoutSystem, addGridLayoutSystem, layoutSystem, removeLayoutSystem], - ) + const numberOfGridPropsSet = useGridAdvancedPropertiesCount() + + const addLayoutSystemMenuDropdownItems = React.useMemo((): DropdownMenuItem[] => { + const gridItems: DropdownMenuItem[] = [ + regularDropdownMenuItem({ + id: 'more-settings', + label: 'Advanced', + onSelect: openPopup, + badge: numberOfGridPropsSet > 0 ? `+${numberOfGridPropsSet}` : null, + }), + regularDropdownMenuItem({ + id: AddFlexLayoutOptionId, + label: 'Convert to Flex', + onSelect: addFlexLayoutSystem, + }), + separatorDropdownMenuItem('dropdown-separator'), + regularDropdownMenuItem({ + id: RemoveLayoutSystemOptionId, + label: 'Remove Grid', + onSelect: removeLayoutSystem, + danger: true, + }), + ] + + const flexItems: DropdownMenuItem[] = [ + regularDropdownMenuItem({ + id: AddGridLayoutOptionId, + label: 'Convert to Grid', + onSelect: addGridLayoutSystem, + }), + separatorDropdownMenuItem('dropdown-separator'), + regularDropdownMenuItem({ + id: RemoveLayoutSystemOptionId, + label: 'Remove Flex', + onSelect: removeLayoutSystem, + danger: true, + }), + ] + + const noLayoutItems: DropdownMenuItem[] = [ + regularDropdownMenuItem({ + id: AddFlexLayoutOptionId, + label: 'Flex', + onSelect: addFlexLayoutSystem, + }), + regularDropdownMenuItem({ + id: AddGridLayoutOptionId, + label: 'Grid', + onSelect: addGridLayoutSystem, + }), + ] + + return stripNulls( + layoutSystem === 'grid' ? gridItems : layoutSystem === 'flex' ? flexItems : noLayoutItems, + ) + }, [ + addFlexLayoutSystem, + addGridLayoutSystem, + layoutSystem, + removeLayoutSystem, + openPopup, + numberOfGridPropsSet, + ]) const label = () => { switch (layoutSystem) { @@ -136,6 +202,15 @@ export const AddRemoveLayoutSystemControl = React.memo { + const x = -1 * (panelWidth + X_OFFSET) + const y = -1 * Y_OFFSET + return { x: x, y: y } + }, [panelWidth]) + + const [menuOpen, setMenuOpen] = React.useState(false) + return ( {label()} + {when( + popupOpen, + , + )} -
+ + {when( + numberOfGridPropsSet > 0 && !menuOpen && !popupOpen, + , + )} -
+
) }) diff --git a/editor/src/components/inspector/alignment-buttons.spec.browser2.tsx b/editor/src/components/inspector/alignment-buttons.spec.browser2.tsx new file mode 100644 index 000000000000..1158a6aad77b --- /dev/null +++ b/editor/src/components/inspector/alignment-buttons.spec.browser2.tsx @@ -0,0 +1,290 @@ +import { MetadataUtils } from '../../core/model/element-metadata-utils' +import * as EP from '../../core/shared/element-path' +import { renderTestEditorWithCode } from '../canvas/ui-jsx.test-utils' +import { isAlignmentGroupDisabled } from './use-disable-alignment' + +describe('alignment buttons', () => { + describe('isAlignmentGroupDisabled', () => { + it('enables buttons for grid cells', async () => { + const renderResult = await renderTestEditorWithCode(projectCode, 'await-first-dom-report') + const metadata = renderResult.getEditorState().editor.jsxMetadata + + expect( + isAlignmentGroupDisabled( + MetadataUtils.findElementByElementPath(metadata, EP.fromString('sb/grid/grid-child')), + MetadataUtils.findElementByElementPath(metadata, EP.fromString('sb/grid')), + 'horizontal', + [], + ), + ).toBe(false) + }) + it('enables buttons for flex only in the opposite direction', async () => { + const renderResult = await renderTestEditorWithCode(projectCode, 'await-first-dom-report') + const metadata = renderResult.getEditorState().editor.jsxMetadata + + // flex-direction: row + { + expect( + isAlignmentGroupDisabled( + MetadataUtils.findElementByElementPath( + metadata, + EP.fromString('sb/flex-horiz/flex-horiz-child'), + ), + MetadataUtils.findElementByElementPath(metadata, EP.fromString('sb/flex-horiz')), + 'vertical', + [], + ), + ).toBe(false) + expect( + isAlignmentGroupDisabled( + MetadataUtils.findElementByElementPath( + metadata, + EP.fromString('sb/flex-horiz/flex-horiz-child'), + ), + MetadataUtils.findElementByElementPath(metadata, EP.fromString('sb/flex-horiz')), + 'horizontal', + [], + ), + ).toBe(true) + } + + // flex-direction: column + { + expect( + isAlignmentGroupDisabled( + MetadataUtils.findElementByElementPath( + metadata, + EP.fromString('sb/flex-vert/flex-vert-child'), + ), + MetadataUtils.findElementByElementPath(metadata, EP.fromString('sb/flex-vert')), + 'horizontal', + [], + ), + ).toBe(false) + expect( + isAlignmentGroupDisabled( + MetadataUtils.findElementByElementPath( + metadata, + EP.fromString('sb/flex-vert/flex-vert-child'), + ), + MetadataUtils.findElementByElementPath(metadata, EP.fromString('sb/flex-vert')), + 'vertical', + [], + ), + ).toBe(true) + } + }) + it('enables buttons for absolute only if not storyboard children', async () => { + const renderResult = await renderTestEditorWithCode(projectCode, 'await-first-dom-report') + const metadata = renderResult.getEditorState().editor.jsxMetadata + + expect( + isAlignmentGroupDisabled( + MetadataUtils.findElementByElementPath( + metadata, + EP.fromString('sb/flow/flow-child-absolute'), + ), + MetadataUtils.findElementByElementPath(metadata, EP.fromString('sb/flow')), + 'horizontal', + [], + ), + ).toBe(false) + + expect( + isAlignmentGroupDisabled( + MetadataUtils.findElementByElementPath(metadata, EP.fromString('sb/flow')), + MetadataUtils.findElementByElementPath(metadata, EP.fromString('sb')), + 'horizontal', + [], + ), + ).toBe(true) + + expect( + isAlignmentGroupDisabled( + MetadataUtils.findElementByElementPath(metadata, EP.fromString('sb/flow')), + MetadataUtils.findElementByElementPath(metadata, EP.fromString('sb')), + 'horizontal', + [EP.fromString('sb/flow'), EP.fromString('sb/grid')], + ), + ).toBe(false) + }) + it('disables buttons for flow', async () => { + const renderResult = await renderTestEditorWithCode(projectCode, 'await-first-dom-report') + const metadata = renderResult.getEditorState().editor.jsxMetadata + + expect( + isAlignmentGroupDisabled( + MetadataUtils.findElementByElementPath( + metadata, + EP.fromString('sb/flow/flow-child-relative'), + ), + MetadataUtils.findElementByElementPath(metadata, EP.fromString('sb/flow')), + 'horizontal', + [], + ), + ).toBe(true) + }) + }) +}) + +const projectCode = `import * as React from 'react' +import { Storyboard } from 'utopia-api' + +export var storyboard = ( + +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ +) +` diff --git a/editor/src/components/inspector/alignment-buttons.tsx b/editor/src/components/inspector/alignment-buttons.tsx new file mode 100644 index 000000000000..450654e57fd0 --- /dev/null +++ b/editor/src/components/inspector/alignment-buttons.tsx @@ -0,0 +1,404 @@ +/** @jsxRuntime classic */ +/** @jsx jsx */ +import { jsx } from '@emotion/react' +import React from 'react' +import { MetadataUtils } from '../../core/model/element-metadata-utils' +import { mapDropNulls } from '../../core/shared/array-utils' +import * as EP from '../../core/shared/element-path' +import { zeroRectangle, zeroRectIfNullOrInfinity } from '../../core/shared/math-utils' +import * as PP from '../../core/shared/property-path' +import { assertNever } from '../../core/shared/utils' +import { Button, Icn, Icons } from '../../uuiui' +import type { DropdownMenuItem } from '../../uuiui/radix-components' +import { + DropdownMenu, + regularDropdownMenuItem, + separatorDropdownMenuItem, +} from '../../uuiui/radix-components' +import type { Alignment, Distribution, EditorAction } from '../editor/action-types' +import { + alignSelectedViews, + distributeSelectedViews, + unsetProperty, +} from '../editor/actions/action-creators' +import { useDispatch } from '../editor/store/dispatch-context' +import { Substores, useEditorState, useRefEditorState } from '../editor/store/store-hook' +import { getControlStyles } from './common/control-styles' +import { OptionChainControl } from './controls/option-chain-control' +import { UIGridRow } from './widgets/ui-grid-row' +import { useDisableAlignment } from './use-disable-alignment' + +type ActiveAlignments = { [key in Alignment]: boolean } + +export const AlignmentButtons = React.memo(() => { + const dispatch = useDispatch() + + const selectedViews = useRefEditorState((store) => store.editor.selectedViews) + + const disableDistribute = selectedViews.current.length < 3 + + const { activeAlignments, allSelectedViewsAreFlexOrGridChildren } = useActiveAlignments() + const hasActiveAlignments = React.useMemo(() => { + return ( + activeAlignments.bottom || + activeAlignments.left || + activeAlignments.right || + activeAlignments.vcenter || + activeAlignments.hcenter || + activeAlignments.top + ) + }, [activeAlignments]) + + const getUnsetAlignmentsActions = useGetUnsetAlignmentsActions(activeAlignments) + + const unsetAllAlignments = React.useCallback(() => { + const actions = getUnsetAlignmentsActions().map((a) => a.action) + dispatch(actions) + }, [dispatch, getUnsetAlignmentsActions]) + + const distributeSelected = React.useCallback( + (distribution: Distribution) => { + dispatch([distributeSelectedViews(distribution)], 'everyone') + }, + [dispatch], + ) + + const distributeHorizontal = React.useCallback( + () => distributeSelected('horizontal'), + [distributeSelected], + ) + + const distributeVertical = React.useCallback( + () => distributeSelected('vertical'), + [distributeSelected], + ) + + const dropdownOpener = React.useCallback(() => { + return ( + + ) + }, []) + + const alignSelected = React.useCallback( + (alignment: Alignment) => { + if (activeAlignments[alignment]) { + const unsetAlignment = getUnsetAlignmentsActions() + .filter((a) => a.alignments.includes(alignment)) + .map((a) => a.action) + dispatch(unsetAlignment) + } else { + dispatch([alignSelectedViews(alignment)], 'everyone') + } + }, + [dispatch, activeAlignments, getUnsetAlignmentsActions], + ) + + const dropdownItems = React.useMemo(() => { + let items: DropdownMenuItem[] = [] + + if (hasActiveAlignments) { + items.push( + regularDropdownMenuItem({ + id: 'unset', + label: 'Unset', + onSelect: unsetAllAlignments, + }), + separatorDropdownMenuItem('sep'), + ) + } + + items.push( + regularDropdownMenuItem({ + id: 'distribute-horizontal', + label: 'Distribute Horizontal', + onSelect: distributeHorizontal, + disabled: disableDistribute, + icon: ( + + ), + }), + regularDropdownMenuItem({ + id: 'distribute-vertical', + label: 'Distribute Vertical', + onSelect: distributeVertical, + disabled: disableDistribute, + icon: ( + + ), + }), + ) + + return items + }, [ + disableDistribute, + distributeHorizontal, + distributeVertical, + hasActiveAlignments, + unsetAllAlignments, + ]) + + const chainValueJustify = React.useMemo(() => { + return activeAlignments.left + ? 'left' + : activeAlignments.hcenter + ? 'hcenter' + : activeAlignments.right + ? 'right' + : null + }, [activeAlignments]) + + const chainValueAlign = React.useMemo(() => { + return activeAlignments.top + ? 'top' + : activeAlignments.vcenter + ? 'vcenter' + : activeAlignments.bottom + ? 'bottom' + : null + }, [activeAlignments]) + + const disableJustify = useDisableAlignment(selectedViews.current, 'horizontal') + const disableAlign = useDisableAlignment(selectedViews.current, 'vertical') + + const justifyIconPrefix = allSelectedViewsAreFlexOrGridChildren ? 'justifySelf' : 'justify' + const alignIconPrefix = allSelectedViewsAreFlexOrGridChildren ? 'alignSelf' : 'align' + + return ( + + + + + + + ) +}) +AlignmentButtons.displayName = 'AlignmentButtons' + +function useActiveAlignments(): { + activeAlignments: ActiveAlignments + allSelectedViewsAreFlexOrGridChildren: boolean +} { + const selectedViews = useRefEditorState((store) => store.editor.selectedViews) + + const jsxMetadata = useEditorState( + Substores.metadata, + (store) => store.editor.jsxMetadata, + 'useActiveAlignments jsxMetadata', + ) + + const allSelectedViewsAreFlexOrGridChildren = React.useMemo( + () => selectedViews.current.every((path) => MetadataUtils.isFlexOrGridChild(jsxMetadata, path)), + [selectedViews, jsxMetadata], + ) + + const isActive = React.useCallback( + (alignment: Alignment) => { + if (selectedViews.current.length === 0) { + return false + } + + // Only flex/grid children can have active alignments, because they would mean nothing for flow elements. + if (!allSelectedViewsAreFlexOrGridChildren) { + return false + } + + return selectedViews.current.every((view) => { + const isFlexOrGridChild = MetadataUtils.isFlexOrGridChild(jsxMetadata, view) + const { align, justify } = MetadataUtils.getRelativeAlignJustify(jsxMetadata, view) + + const meta = MetadataUtils.findElementByElementPath(jsxMetadata, view) + if (meta == null) { + return false + } + + const parent = MetadataUtils.findElementByElementPath(jsxMetadata, EP.parentPath(view)) + const parentFrame = + parent != null ? zeroRectIfNullOrInfinity(parent.globalFrame) : zeroRectangle + + switch (alignment) { + case 'top': + return isFlexOrGridChild + ? meta.specialSizeMeasurements[align] === 'flex-start' || + meta.specialSizeMeasurements[align] === 'start' + : meta.specialSizeMeasurements.offset.y === 0 + case 'bottom': + return isFlexOrGridChild + ? meta.specialSizeMeasurements[align] === 'flex-end' || + meta.specialSizeMeasurements[align] === 'end' + : parent != null && + meta.specialSizeMeasurements.offset.y === + parentFrame.height - meta.specialSizeMeasurements.clientHeight + case 'vcenter': + return isFlexOrGridChild + ? meta.specialSizeMeasurements[align] === 'center' + : parent != null && + meta.specialSizeMeasurements.offset.y === + Math.ceil( + parentFrame.height / 2 - meta.specialSizeMeasurements.clientHeight / 2, + ) + case 'left': + return isFlexOrGridChild + ? meta.specialSizeMeasurements[justify] === 'flex-start' || + meta.specialSizeMeasurements[justify] === 'start' + : meta.specialSizeMeasurements.offset.x === 0 + case 'right': + return isFlexOrGridChild + ? meta.specialSizeMeasurements[justify] === 'flex-end' || + meta.specialSizeMeasurements[justify] === 'end' + : parent != null && + meta.specialSizeMeasurements.offset.x === + parentFrame.width - meta.specialSizeMeasurements.clientWidth + case 'hcenter': + return isFlexOrGridChild + ? meta.specialSizeMeasurements[justify] === 'center' + : parent != null && + meta.specialSizeMeasurements.offset.x === + Math.ceil(parentFrame.width / 2 - meta.specialSizeMeasurements.clientWidth / 2) + default: + assertNever(alignment) + return false + } + }) + }, + [jsxMetadata, selectedViews, allSelectedViewsAreFlexOrGridChildren], + ) + + const activeAlignments = React.useMemo(() => { + return { + left: isActive('left'), + hcenter: isActive('hcenter'), + right: isActive('right'), + top: isActive('top'), + vcenter: isActive('vcenter'), + bottom: isActive('bottom'), + } + }, [isActive]) + + return { + activeAlignments: activeAlignments, + allSelectedViewsAreFlexOrGridChildren: allSelectedViewsAreFlexOrGridChildren, + } +} + +type UnsetAlignment = { + alignments: Alignment[] + action: EditorAction +} + +function useGetUnsetAlignmentsActions(activeAlignments: ActiveAlignments) { + const selectedViews = useRefEditorState((store) => store.editor.selectedViews) + + const jsxMetadata = useEditorState( + Substores.metadata, + (store) => store.editor.jsxMetadata, + 'useGetUnsetAlignmentsActions jsxMetadata', + ) + + return React.useCallback((): UnsetAlignment[] => { + const activeAlignmentsList = Object.entries(activeAlignments).reduce((acc, [key, set]) => { + if (set) { + return acc.concat(key as Alignment) + } + return acc + }, [] as Alignment[]) + + return mapDropNulls((path) => { + let actions: UnsetAlignment[] = [] + + const isFlexOrGridChild = MetadataUtils.isFlexOrGridChild(jsxMetadata, path) + const { align, justify } = MetadataUtils.getRelativeAlignJustify(jsxMetadata, path) + + if (activeAlignments.left || activeAlignments.hcenter || activeAlignments.right) { + const alignments: Alignment[] = activeAlignmentsList.filter( + (alignment) => alignment === 'left' || alignment === 'hcenter' || alignment === 'right', + ) + actions.push({ + action: unsetProperty(path, PP.create('style', isFlexOrGridChild ? justify : 'left')), + alignments: alignments, + }) + } + + if (activeAlignments.top || activeAlignments.vcenter || activeAlignments.bottom) { + const alignments: Alignment[] = activeAlignmentsList.filter( + (alignment) => alignment === 'top' || alignment === 'vcenter' || alignment === 'bottom', + ) + actions.push({ + action: unsetProperty(path, PP.create('style', isFlexOrGridChild ? align : 'top')), + alignments: alignments, + }) + } + + return actions + }, selectedViews.current).flat() + }, [activeAlignments, selectedViews, jsxMetadata]) +} diff --git a/editor/src/components/inspector/common/css-tree-utils.ts b/editor/src/components/inspector/common/css-tree-utils.ts index 46f7ea72ce25..4fd6d4de0ef3 100644 --- a/editor/src/components/inspector/common/css-tree-utils.ts +++ b/editor/src/components/inspector/common/css-tree-utils.ts @@ -16,53 +16,3 @@ export function cssTreeNodeValue(children: csstree.List): csstr children: children, } } - -export function expandCssTreeNodeValue(node: csstree.CssNode): csstree.Value { - return cssTreeNodeValue(expandNode(node)) -} - -function expandNode(node: csstree.CssNode): csstree.List { - if (node.type === 'Function' && node.name === 'repeat') { - // specific expansion for repeat functions - return expandRepeatFunction(node) - } else if (node.type === 'Value') { - // recursively expand children of Value nodes - const children = node.children.toArray() - const expanded = children.flatMap((child) => expandNode(child).toArray()) - return cssTreeNodeList(expanded) - } else { - // fallback to just the verbatim node - return cssTreeNodeList([node]) - } -} - -function expandRepeatFunction(fnNode: csstree.FunctionNode): csstree.List { - // The node should have 3+ children, because it should be [Number + Operator (,) + Value(s)] - // TODO this should be extended to support non-numeric, keyword repeaters - const children = fnNode.children.toArray() - if (children.length < 3) { - // just return the original children if the format is not supported - return fnNode.children - } - - // 1. parse the repeat number - const repeatNumber = children[0] - if (repeatNumber.type !== 'Number') { - return fnNode.children - } - const times = parseInt(repeatNumber.value) - - // 2. grab ALL the values to repeat (rightside of the comma), wrap them in a new Value node, - // and expand them so they support nested repeats - const valuesToRepeat = children.slice(2) - const nodeToRepeat = cssTreeNodeValue(cssTreeNodeList(valuesToRepeat)) - const expandedValues = expandNode(nodeToRepeat) - - // 3. append the expanded values N times, where N is the number of repeats parsed earlier - let result = new csstree.List() - for (let i = 0; i < times; i++) { - expandedValues.forEach((v) => result.appendData(v)) - } - - return result -} diff --git a/editor/src/components/inspector/common/css-utils.spec.ts b/editor/src/components/inspector/common/css-utils.spec.ts index 139eb037d072..b27f1c8d8303 100644 --- a/editor/src/components/inspector/common/css-utils.spec.ts +++ b/editor/src/components/inspector/common/css-utils.spec.ts @@ -1,6 +1,7 @@ import { clearModifiableAttributeUniqueIDs } from '../../../core/shared/jsx-attributes' import type { Either } from '../../../core/shared/either' -import { isLeft, isRight, right } from '../../../core/shared/either' +import { isLeft, isRight, left, right } from '../../../core/shared/either' +import type { GridContainerProperties } from '../../../core/shared/element-template' import { emptyComments, jsExpressionFunctionCall, @@ -10,6 +11,12 @@ import { jsxTestElement, clearExpressionUniqueIDs, clearJSXElementChildUniqueIDs, + gridContainerProperties, + gridAutoOrTemplateDimensions, + gridPositionValue, + gridRange, + gridSpanNumeric, + gridSpanArea, } from '../../../core/shared/element-template' import * as PP from '../../../core/shared/property-path' import type { @@ -19,6 +26,7 @@ import type { CSSColor, CSSTextShadows, CSSTransforms, + GridDimension, } from './css-utils' import { cssAngle, @@ -31,7 +39,6 @@ import { cssNumber, cssPixelLength, cssPixelLengthZero, - CSSSolidColor, cssTransformRotate, cssTransformRotateX, cssTransformRotateY, @@ -48,13 +55,15 @@ import { cssTransformTranslateY, cssTransformTranslateZ, cssUnitlessLength, - CSSUnknownArrayItem, defaultBGSize, defaultCSSGradientStops, defaultCSSRadialGradientSize, defaultCSSRadialOrConicGradientCenter, disabledFunctionName, - expandRepeatFunctions, + gridCSSKeyword, + gridCSSMinmax, + gridCSSNumber, + gridCSSRepeat, parseBackgroundColor, parseBackgroundImage, parseBorderRadius, @@ -63,16 +72,18 @@ import { parseConicGradient, parseCSSURLFunction, parsedCurlyBrace, + parseGridRange, parseLinearGradient, parseRadialGradient, parseTextShadow, parseTransform, printBackgroundImage, printBackgroundSize, + printGridDimensionCSS, RegExpLibrary, + stringifyGridDimension, toggleSimple, toggleStylePropPath, - tokenizeGridTemplate, } from './css-utils' describe('toggleStyleProp', () => { @@ -1810,56 +1821,317 @@ describe('printBackgroundSize', () => { }) }) -describe('tokenizeGridTemplate', () => { - it('tokenizes the grid template strings (no units)', async () => { - expect(tokenizeGridTemplate('123 456 78 9')).toEqual(['123', '456', '78', '9']) +describe('stringifyGridDimension', () => { + it('keyword', async () => { + expect(stringifyGridDimension(gridCSSKeyword(cssKeyword('auto'), null))).toBe('auto') + expect(stringifyGridDimension(gridCSSKeyword(cssKeyword('auto'), 'the-area'))).toBe('auto') }) - it('tokenizes the grid template strings (with units)', async () => { - expect(tokenizeGridTemplate('123 456px 78 9rem')).toEqual(['123', '456px', '78', '9rem']) + + it('number', async () => { + expect(stringifyGridDimension(gridCSSNumber(cssNumber(123), null))).toBe('123') + expect(stringifyGridDimension(gridCSSNumber(cssNumber(123, 'px'), null))).toBe('123px') + expect(stringifyGridDimension(gridCSSNumber(cssNumber(123), 'the-area'))).toBe('123') }) - it('tokenizes the grid template strings (with some area names)', async () => { - expect(tokenizeGridTemplate('[foo] 123 456px 78 9rem')).toEqual([ - '[foo] 123', - '456px', - '78', - '9rem', - ]) - expect(tokenizeGridTemplate('123 [foo]456px 78 [bar] 9rem')).toEqual([ - '123', - '[foo] 456px', - '78', - '[bar] 9rem', - ]) + + it('repeat', async () => { + expect( + stringifyGridDimension( + gridCSSRepeat( + 3, + [ + gridCSSKeyword(cssKeyword('auto'), null), + gridCSSKeyword(cssKeyword('min-content'), null), + gridCSSNumber(cssNumber(123, 'px'), null), + ], + null, + ), + ), + ).toBe(`repeat(3, auto min-content 123px)`) + + expect( + stringifyGridDimension( + gridCSSRepeat( + 3, + [ + gridCSSKeyword(cssKeyword('auto'), 'foo'), + gridCSSKeyword(cssKeyword('min-content'), 'bar'), + gridCSSNumber(cssNumber(123, 'px'), null), + ], + 'the-area', + ), + ), + ).toBe(`repeat(3, [foo] auto [bar] min-content 123px)`) + + expect( + stringifyGridDimension( + gridCSSRepeat( + cssKeyword('auto-fit'), + [ + gridCSSMinmax( + gridCSSNumber(cssNumber(400, 'px'), null), + gridCSSNumber(cssNumber(1, 'fr'), null), + null, + ), + ], + null, + ), + ), + ).toBe(`repeat(auto-fit, minmax(400px, 1fr))`) }) - it('tokenizes the grid template strings (with all area names)', async () => { - expect(tokenizeGridTemplate('[foo] 123 [bar]456px [baz] 78 [QUX]9rem')).toEqual([ - '[foo] 123', - '[bar] 456px', - '[baz] 78', - '[QUX] 9rem', - ]) + + it('minmax', async () => { + expect( + stringifyGridDimension( + gridCSSMinmax( + gridCSSKeyword(cssKeyword('auto'), null), + gridCSSKeyword(cssKeyword('min-content'), null), + null, + ), + ), + ).toBe('minmax(auto, min-content)') + + expect( + stringifyGridDimension( + gridCSSMinmax( + gridCSSKeyword(cssKeyword('auto'), null), + gridCSSKeyword(cssKeyword('min-content'), null), + 'the-area', + ), + ), + ).toBe('minmax(auto, min-content)') }) }) -describe('expandRepeatFunctions', () => { - it('expands repeat', async () => { - expect(expandRepeatFunctions('repeat(4, 1fr)')).toEqual('1fr 1fr 1fr 1fr') +describe('printGridDimensionCSS', () => { + it('keyword', async () => { + expect(printGridDimensionCSS(gridCSSKeyword(cssKeyword('auto'), null))).toBe('auto') + expect(printGridDimensionCSS(gridCSSKeyword(cssKeyword('auto'), 'the-area'))).toBe( + '[the-area] auto', + ) }) - it('expands repeat with multiple units', () => { - expect(expandRepeatFunctions('repeat(2, 1fr 2fr 3fr)')).toEqual('1fr 2fr 3fr 1fr 2fr 3fr') + + it('number', async () => { + expect(printGridDimensionCSS(gridCSSNumber(cssNumber(123), null))).toBe('123') + expect(printGridDimensionCSS(gridCSSNumber(cssNumber(123, 'px'), null))).toBe('123px') + expect(printGridDimensionCSS(gridCSSNumber(cssNumber(123), 'the-area'))).toBe('[the-area] 123') }) - it('expands repeat with spacing', () => { - expect(expandRepeatFunctions('repeat( 4 , 1fr )')).toEqual('1fr 1fr 1fr 1fr') + + it('repeat', async () => { + expect( + printGridDimensionCSS( + gridCSSRepeat( + 3, + [ + gridCSSKeyword(cssKeyword('auto'), null), + gridCSSKeyword(cssKeyword('min-content'), null), + gridCSSNumber(cssNumber(123, 'px'), null), + ], + null, + ), + ), + ).toBe(`repeat(3, auto min-content 123px)`) + + expect( + printGridDimensionCSS( + gridCSSRepeat( + 3, + [ + gridCSSKeyword(cssKeyword('auto'), 'foo'), + gridCSSKeyword(cssKeyword('min-content'), 'bar'), + gridCSSNumber(cssNumber(123, 'px'), null), + ], + 'the-area', + ), + ), + ).toBe(`[the-area] repeat(3, [foo] auto [bar] min-content 123px)`) + + expect( + printGridDimensionCSS( + gridCSSRepeat( + cssKeyword('auto-fit'), + [ + gridCSSMinmax( + gridCSSNumber(cssNumber(400, 'px'), null), + gridCSSNumber(cssNumber(1, 'fr'), null), + null, + ), + ], + null, + ), + ), + ).toBe(`repeat(auto-fit, minmax(400px, 1fr))`) }) - it('expands repeat with decimals', () => { - expect(expandRepeatFunctions('repeat(4, 1.5fr)')).toEqual('1.5fr 1.5fr 1.5fr 1.5fr') + + it('minmax', async () => { + expect( + printGridDimensionCSS( + gridCSSMinmax( + gridCSSKeyword(cssKeyword('auto'), null), + gridCSSKeyword(cssKeyword('min-content'), null), + null, + ), + ), + ).toBe('minmax(auto, min-content)') + + expect( + printGridDimensionCSS( + gridCSSMinmax( + gridCSSKeyword(cssKeyword('auto'), null), + gridCSSKeyword(cssKeyword('min-content'), null), + 'the-area', + ), + ), + ).toBe('[the-area] minmax(auto, min-content)') + + expect( + printGridDimensionCSS( + gridCSSMinmax( + gridCSSKeyword(cssKeyword('auto'), 'foo'), + gridCSSKeyword(cssKeyword('min-content'), 'bar'), + 'the-area', + ), + ), + ).toBe('[the-area] minmax(auto, min-content)') }) - it('expands nested', () => { - expect(expandRepeatFunctions('repeat(2, repeat(3, 1fr))')).toEqual('1fr 1fr 1fr 1fr 1fr 1fr') +}) - // Note: crazytown, I'm not even sure this is valid CSS *but still* - expect(expandRepeatFunctions('repeat(2, repeat(2, 1fr repeat(3, 2fr 4em)))')).toEqual( - '1fr 2fr 4em 2fr 4em 2fr 4em 1fr 2fr 4em 2fr 4em 2fr 4em 1fr 2fr 4em 2fr 4em 2fr 4em 1fr 2fr 4em 2fr 4em 2fr 4em', +function testGridContainerProperties( + cols: GridDimension[], + rows: GridDimension[], +): GridContainerProperties { + return gridContainerProperties( + gridAutoOrTemplateDimensions(cols), + gridAutoOrTemplateDimensions(rows), + gridAutoOrTemplateDimensions([]), + gridAutoOrTemplateDimensions([]), + null, + ) +} + +describe('parseGridRange', () => { + it('can parse a numerical unit', async () => { + const got = parseGridRange(testGridContainerProperties([], []), 'row', '3') + expect(got).toEqual(right(gridRange(gridPositionValue(3), null))) + }) + it('can parse a numerical range', async () => { + const got = parseGridRange(testGridContainerProperties([], []), 'row', '3 / 4') + expect(got).toEqual(right(gridRange(gridPositionValue(3), gridPositionValue(4)))) + }) + it('can parse a line', async () => { + const got = parseGridRange( + testGridContainerProperties( + [], + [ + gridCSSNumber(cssNumber(1, 'fr'), 'foo'), + gridCSSNumber(cssNumber(1, 'fr'), 'bar'), + gridCSSNumber(cssNumber(1, 'fr'), 'baz'), + ], + ), + 'row', + 'bar', + ) + expect(got).toEqual(right(gridRange(gridPositionValue(2), null))) + }) + it('errors if the line is not found in the template', async () => { + const got = parseGridRange( + testGridContainerProperties( + [], + [ + gridCSSNumber(cssNumber(1, 'fr'), 'foo'), + gridCSSNumber(cssNumber(1, 'fr'), 'bar'), + gridCSSNumber(cssNumber(1, 'fr'), 'baz'), + ], + ), + 'row', + 'WRONG', + ) + expect(got).toEqual(left('missing grid item start')) + }) + it('can parse a line range', async () => { + const got = parseGridRange( + testGridContainerProperties( + [], + [ + gridCSSNumber(cssNumber(1, 'fr'), 'foo'), + gridCSSNumber(cssNumber(1, 'fr'), 'bar'), + gridCSSNumber(cssNumber(1, 'fr'), 'baz'), + ], + ), + 'row', + 'bar / baz', + ) + expect(got).toEqual(right(gridRange(gridPositionValue(2), gridPositionValue(3)))) + }) + it('can parse a line / unit mixed range', async () => { + const got = parseGridRange( + testGridContainerProperties( + [], + [ + gridCSSNumber(cssNumber(1, 'fr'), 'foo'), + gridCSSNumber(cssNumber(1, 'fr'), 'bar'), + gridCSSNumber(cssNumber(1, 'fr'), 'baz'), + ], + ), + 'row', + 'bar / 3', + ) + expect(got).toEqual(right(gridRange(gridPositionValue(2), gridPositionValue(3)))) + }) + it('can parse a numerical span', async () => { + const got = parseGridRange(testGridContainerProperties([], []), 'row', 'span 2') + expect(got).toEqual(right(gridRange(gridSpanNumeric(2), null))) + }) + it('can parse a numerical span (flipped)', async () => { + const got = parseGridRange(testGridContainerProperties([], []), 'row', '2 span') + expect(got).toEqual(right(gridRange(gridSpanNumeric(2), null))) + }) + it('can parse an area span', async () => { + const got = parseGridRange(testGridContainerProperties([], []), 'row', 'span some-area') + expect(got).toEqual(right(gridRange(gridSpanArea('some-area'), null))) + }) + it('can parse an area span (flipped)', async () => { + const got = parseGridRange(testGridContainerProperties([], []), 'row', 'some-area span') + expect(got).toEqual(right(gridRange(gridSpanArea('some-area'), null))) + }) + it('can parse a span numerical range', async () => { + const got = parseGridRange(testGridContainerProperties([], []), 'row', 'span 2 / span 3') + expect(got).toEqual(right(gridRange(gridSpanNumeric(2), gridSpanNumeric(3)))) + }) + it('can parse an area span range', async () => { + const got = parseGridRange( + testGridContainerProperties([], []), + 'row', + 'span some-area / span some-other-area', + ) + expect(got).toEqual( + right(gridRange(gridSpanArea('some-area'), gridSpanArea('some-other-area'))), + ) + }) + it('can parse a mixed span range', async () => { + const got = parseGridRange( + testGridContainerProperties([], []), + 'row', + 'span some-area / span 3', + ) + expect(got).toEqual(right(gridRange(gridSpanArea('some-area'), gridSpanNumeric(3)))) + }) + it('can parse a mixed span and numerical range', async () => { + const got = parseGridRange(testGridContainerProperties([], []), 'row', 'span some-area / 3') + expect(got).toEqual(right(gridRange(gridSpanArea('some-area'), gridPositionValue(3)))) + }) + it('can parse a mixed span and line range', async () => { + const got = parseGridRange( + testGridContainerProperties( + [], + [ + gridCSSNumber(cssNumber(1, 'fr'), 'foo'), + gridCSSNumber(cssNumber(1, 'fr'), 'bar'), + gridCSSNumber(cssNumber(1, 'fr'), 'baz'), + ], + ), + 'row', + 'span some-area / bar', ) + expect(got).toEqual(right(gridRange(gridSpanArea('some-area'), gridPositionValue(2)))) }) }) diff --git a/editor/src/components/inspector/common/css-utils.ts b/editor/src/components/inspector/common/css-utils.ts index 405a1e1a24a9..6147a5ce6441 100644 --- a/editor/src/components/inspector/common/css-utils.ts +++ b/editor/src/components/inspector/common/css-utils.ts @@ -14,14 +14,12 @@ import type { LayoutPropertyTypes, StyleLayoutProp } from '../../../core/layout/ import { findLastIndex } from '../../../core/shared/array-utils' import type { Either, Right as EitherRight } from '../../../core/shared/either' import { - applicative2Either, bimapEither, eitherToMaybe, flatMapEither, isLeft, isRight, left, - leftMapEither, mapEither, right, traverseEither, @@ -35,6 +33,7 @@ import type { GridRange, GridAutoOrTemplateBase, GridContainerProperties, + GridSpan, } from '../../../core/shared/element-template' import { emptyComments, @@ -46,7 +45,8 @@ import { jsExpressionValue, gridPositionValue, gridRange, - gridAutoOrTemplateDimensions, + gridSpanArea, + gridSpanNumeric, } from '../../../core/shared/element-template' import type { ModifiableAttribute } from '../../../core/shared/jsx-attributes' import { @@ -83,12 +83,9 @@ import { } from '../../../printer-parsers/css/css-parser-margin' import { parseFlex, printFlexAsAttributeValue } from '../../../printer-parsers/css/css-parser-flex' import { memoize } from '../../../core/shared/memoize' -import { parseCSSArray } from '../../../printer-parsers/css/css-parser-utils' -import type { ParseError } from '../../../utils/value-parser-utils' -import { descriptionParseError } from '../../../utils/value-parser-utils' import * as csstree from 'css-tree' -import { expandCssTreeNodeValue, parseCssTreeNodeValue } from './css-tree-utils' import type { IcnProps } from '../../../uuiui' +import { cssNumberEqual } from '../../canvas/controls/select-mode/controls-common' var combineRegExp = function (regexpList: Array, flags?: string) { let source: string = '' @@ -579,6 +576,10 @@ const CSSNumberUnits: Array = [ '%', ] +export function isFR(unit: CSSNumberUnit): unit is 'fr' { + return unit === 'fr' +} + export interface CSSNumber { value: number unit: CSSNumberUnit | null @@ -588,7 +589,7 @@ export type GridCSSNumberUnit = LengthUnit | ResolutionUnit | PercentUnit | 'fr' const GridCSSNumberUnits: Array = [...LengthUnits, ...ResolutionUnits, '%', 'fr'] type BaseGridDimension = { - areaName: string | null + lineName: string | null } export type GridCSSNumber = BaseGridDimension & { @@ -601,18 +602,141 @@ export type GridCSSKeyword = BaseGridDimension & { value: CSSKeyword } +export function gridDimensionsAreEqual(a: GridDimension, b: GridDimension): boolean { + switch (a.type) { + case 'KEYWORD': + if (a.type !== b.type) { + return false + } + return a.value.type === b.value.type && a.value.value === b.value.value + case 'NUMBER': + if (a.type !== b.type) { + return false + } + return cssNumberEqual(a.value, b.value) + case 'MINMAX': + if (a.type !== b.type) { + return false + } + return gridDimensionsAreEqual(a.min, b.min) && gridDimensionsAreEqual(a.max, b.max) + case 'REPEAT': + if (a.type !== b.type) { + return false + } + return ( + a.times === b.times && + a.value.length === b.value.length && + a.value.every((value, index) => gridDimensionsAreEqual(value, b.value[index])) + ) + default: + assertNever(a) + } +} + +type BaseGridCSSRepeat = { + type: 'REPEAT' + value: Array + lineName: string | null +} + +function baseGridCSSRepeat( + value: Array, + lineName: string | null, +): BaseGridCSSRepeat { + return { + type: 'REPEAT', + value: value, + lineName: lineName, + } +} + +type GridCSSRepeatStatic = BaseGridCSSRepeat & { + times: number +} + +function gridCSSRepeatStatic( + times: number, + value: Array, + lineName: string | null, +): GridCSSRepeatStatic { + return { + ...baseGridCSSRepeat(value, lineName), + times: times, + } +} + +type GridCSSRepeatDynamic = BaseGridCSSRepeat & { + times: CSSKeyword<'auto-fill' | 'auto-fit'> +} + +function gridCSSRepeatDynamic( + times: CSSKeyword<'auto-fill' | 'auto-fit'>, + value: Array, + lineName: string | null, +): GridCSSRepeatDynamic { + return { + ...baseGridCSSRepeat(value, lineName), + times: times, + } +} + +export type GridCSSRepeat = GridCSSRepeatStatic | GridCSSRepeatDynamic + +type GridCSSRepeatTimes = GridCSSRepeat['times'] + +export function isStaticGridRepeat(dim: GridDimension): dim is GridCSSRepeatStatic { + return isGridCSSRepeat(dim) && !isCSSKeyword(dim.times) +} + +export function isDynamicGridRepeat(dim: GridDimension): dim is GridCSSRepeatDynamic { + return isGridCSSRepeat(dim) && isCSSKeyword(dim.times) +} + +export type GridCSSMinmax = BaseGridDimension & { + type: 'MINMAX' + min: GridCSSNumber | GridCSSKeyword + max: GridCSSNumber | GridCSSKeyword +} + +export function parseGridCSSMinmaxOrRepeat(input: string): GridCSSMinmax | GridCSSRepeat | null { + const parsed = csstree.parse(input, { context: 'value' }) + if (parsed.type === 'Value') { + const parsedDimensions = parseGridChildren(parsed.children) + if ( + isRight(parsedDimensions) && + parsedDimensions.value.length === 1 && + (isGridCSSMinmax(parsedDimensions.value[0]) || isGridCSSRepeat(parsedDimensions.value[0])) + ) { + return parsedDimensions.value[0] + } + } + return null +} + export function isGridCSSKeyword(dim: GridDimension): dim is GridCSSKeyword { return dim.type === 'KEYWORD' } export function gridCSSKeyword( value: CSSKeyword, - areaName: string | null, + lineName: string | null, ): GridCSSKeyword { return { type: 'KEYWORD', value: value, - areaName: areaName, + lineName: lineName, + } +} + +export function gridCSSRepeat( + times: GridCSSRepeatTimes, + value: GridDimension[], + lineName: string | null, +): GridCSSRepeat { + if (typeof times === 'number') { + return gridCSSRepeatStatic(times, value, lineName) + } else { + return gridCSSRepeatDynamic(times, value, lineName) } } @@ -620,21 +744,54 @@ export function isGridCSSNumber(dim: GridDimension): dim is GridCSSNumber { return dim.type === 'NUMBER' } -export function gridCSSNumber(value: CSSNumber, areaName: string | null): GridCSSNumber { +export function isGridCSSRepeat(dim: GridDimension): dim is GridCSSRepeat { + return dim.type === 'REPEAT' +} + +export function isGridCSSMinmax(dim: GridDimension): dim is GridCSSMinmax { + return dim.type === 'MINMAX' +} + +export function gridCSSMinmax( + min: GridCSSNumber | GridCSSKeyword, + max: GridCSSNumber | GridCSSKeyword, + lineName: string | null, +): GridCSSMinmax { + return { + type: 'MINMAX', + min: min, + max: max, + lineName: lineName, + } +} + +export function gridCSSNumber(value: CSSNumber, lineName: string | null): GridCSSNumber { return { type: 'NUMBER', value: value, - areaName: areaName, + lineName: lineName, } } -export type GridDimension = GridCSSNumber | GridCSSKeyword +export type GridDiscreteDimension = GridCSSNumber | GridCSSKeyword | GridCSSMinmax +export type GridDimension = GridDiscreteDimension | GridCSSRepeat export function printGridCSSNumber(dim: GridDimension): string { - if (isGridCSSNumber(dim)) { - return `${dim.value.value}${dim.value.unit ?? ''}` + switch (dim.type) { + case 'KEYWORD': + return dim.value.value + case 'NUMBER': + return `${dim.value.value}${dim.value.unit ?? ''}` + case 'REPEAT': + if (dim.value.length === 0) { + return '' + } + return dim.value.map(printGridCSSNumber).join(' ') + case 'MINMAX': + return `minmax(${printGridCSSNumber(dim.min)}, ${printGridCSSNumber(dim.max)})` + default: + assertNever(dim) } - return dim.value.value } export function cssNumber(value: number, unit: CSSNumberUnit | null = null): CSSNumber { @@ -797,23 +954,42 @@ export function printCSSNumber( } } -export function printArrayGridDimension(array: Array): string { - return array - .map((dimension) => { - if (isGridCSSKeyword(dimension)) { - return dimension.value.value - } - const printed = printCSSNumber(dimension.value, null) - const areaName = dimension.areaName != null ? `[${dimension.areaName}] ` : '' - return `${areaName}${printed}` - }) - .join(' ') +export function printGridDimensionCSS(dimension: GridDimension): string { + const lineName = dimension.lineName != null ? `[${dimension.lineName}] ` : '' + return lineName + stringifyGridDimension(dimension) +} + +export function stringifyGridDimension(dimension: GridDimension): string { + switch (dimension.type) { + case 'KEYWORD': { + return dimension.value.value + } + case 'NUMBER': { + return `${printCSSNumber(dimension.value, null)}` + } + case 'REPEAT': { + const times = isCSSKeyword(dimension.times) ? dimension.times.value : dimension.times + const values = dimension.value.map(printGridDimensionCSS).join(' ') + return `repeat(${times}, ${values})` + } + case 'MINMAX': { + const min = stringifyGridDimension(dimension.min) + const max = stringifyGridDimension(dimension.max) + return `minmax(${min}, ${max})` + } + default: + assertNever(dimension) + } +} + +export function printArrayGridDimensions(array: Array): string { + return array.map(printGridDimensionCSS).join(' ') } export function printGridAutoOrTemplateBase(input: GridAutoOrTemplateBase): string { switch (input.type) { case 'DIMENSIONS': - return printArrayGridDimension(input.dimensions) + return printArrayGridDimensions(input.dimensions) case 'FALLBACK': return input.value default: @@ -897,7 +1073,6 @@ const validGridDimensionKeywords = [ 'subgrid', 'auto-fit', 'auto-fill', - // NOTE: function keywords are omitted as they are treated separately ] as const export type ValidGridDimensionKeyword = (typeof validGridDimensionKeywords)[number] @@ -914,20 +1089,20 @@ export function parseToCSSGridDimension(input: unknown): Either { return { ...value, - areaName: areaName, - } + lineName: value.type === 'REPEAT' ? null : lineName, + } as GridDimension }, parseCSSGrid(inputToParse)) } @@ -959,7 +1134,7 @@ export function parseGridPosition( const referenceTemplate = axis === 'row' ? container.gridTemplateRows : container.gridTemplateColumns if (referenceTemplate?.type === 'DIMENSIONS') { - const maybeArea = referenceTemplate.dimensions.findIndex((dim) => dim.areaName === input) + const maybeArea = referenceTemplate.dimensions.findIndex((dim) => dim.lineName === input) if (maybeArea >= 0) { let value = gridPositionValue(maybeArea + 1) if ( @@ -1025,89 +1200,151 @@ export function parseGridRange( axis: 'row' | 'column', input: unknown, ): Either { - if (typeof input === 'string') { - if (input.includes('/')) { - const splitInput = input.split('/') - const startParsed = parseGridPosition(container, axis, 'start', null, splitInput[0]) - const endParsed = parseGridPosition(container, axis, 'end', null, splitInput[1]) - return applicative2Either(gridRange, startParsed, endParsed) - } else { - const startParsed = parseGridPosition(container, axis, 'start', null, input) - return mapEither((start) => { - const end = - !isCSSKeyword(start) && start.numericalPosition != null - ? gridPositionValue(start.numericalPosition + 1) - : null - return gridRange(start, end) - }, startParsed) - } - } else { - return left('Not a valid grid range.') + if (typeof input !== 'string') { + return left('invalid grid item') } -} - -export function expandRepeatFunctions(str: string): string { - const node = parseCssTreeNodeValue(str) - const expanded = expandCssTreeNodeValue(node) - return csstree.generate(expanded) -} -const reGridAreaNameBrackets = /^\[.+\]$/ + const parsed = csstree.parse(input, { context: 'value' }) + if (parsed.type !== 'Value') { + return left('invalid grid item value') + } -function normalizeGridTemplate(template: string): string { - type normalizeFn = (s: string) => string + const children = parsed.children.toArray() + const slashIndex = children.findIndex((c) => c.type === 'Operator' && c.value === '/') - const normalizePasses: normalizeFn[] = [ - // 1. expand repeat functions - expandRepeatFunctions, - // 2. normalize area names spacing - (s) => s.replace(/\]/g, '] ').replace(/\[/g, ' ['), - ] + const isRange = slashIndex >= 0 + const start = isRange ? children.slice(0, slashIndex) : children + const end = isRange ? children.slice(slashIndex + 1) : [] - return normalizePasses.reduce((working, normalize) => normalize(working), template).trim() -} + if (start.length === 0) { + return left('invalid grid item start') + } -export function tokenizeGridTemplate(template: string): string[] { - let tokens: string[] = [] - let parts = normalizeGridTemplate(template).split(/\s+/) - while (parts.length > 0) { - const part = parts.shift()?.trim() - if (part == null) { - break - } - if (part.match(reGridAreaNameBrackets) != null && parts.length > 0) { - const withAreaName = `${part} ${parts.shift()}` - tokens.push(withAreaName) - } else { - tokens.push(part) - } + const maybeStart = maybeParseGridSpan(start) ?? maybeParseGridLine(start, axis, container) + if (maybeStart == null) { + return left('missing grid item start') } - return tokens + + const maybeEnd = maybeParseGridSpan(end) ?? maybeParseGridLine(end, axis, container) + + return right(gridRange(maybeStart, maybeEnd)) } export function parseGridAutoOrTemplateBase( input: unknown, ): Either { - function numberOrKeywordParse(inputToParse: unknown): Either { - const result = parseToCSSGridDimension(inputToParse) - return leftMapEither(descriptionParseError, result) - } if (typeof input === 'string') { - const parsedCSSArray = parseCSSArray([numberOrKeywordParse])(tokenizeGridTemplate(input)) - return bimapEither( - (error) => { - if (error.type === 'DESCRIPTION_PARSE_ERROR') { - return error.description + const parsed = csstree.parse(input, { context: 'value' }) + if (parsed.type === 'Value') { + const dimensions = parseGridChildren(parsed.children) + if (isRight(dimensions)) { + return right({ type: 'DIMENSIONS', dimensions: dimensions.value }) + } + + console.warn(`Invalid grid template, falling back: ${dimensions.value}.`) + return right({ type: 'FALLBACK', value: input }) + } + } + return left('Invalid grid template input.') +} + +export function parseGridChildren( + children: csstree.List, +): Either { + let nextLineName: string | null = null + + function getLineName() { + const currentLineName = nextLineName != null ? `${nextLineName}` : null + nextLineName = null + return currentLineName + } + + let dimensions: GridDimension[] = [] + for (const child of children) { + switch (child.type) { + case 'Dimension': { + const parsedDimension = parseCSSNumber(`${child.value}${child.unit}`, 'AnyValid') + if (isRight(parsedDimension)) { + dimensions.push(gridCSSNumber(parsedDimension.value, getLineName())) } else { - return error.toString() + return left('Invalid grid CSS dimension.') } - }, - gridAutoOrTemplateDimensions, - parsedCSSArray, - ) - } else { - return left('Unknown input.') + break + } + case 'Identifier': { + if (isValidGridDimensionKeyword(child.name)) { + dimensions.push(gridCSSKeyword(cssKeyword(child.name), getLineName())) + } else { + return left('Invalid grid CSS keyword.') + } + break + } + case 'Function': { + const functionName = child.name.toLowerCase() + switch (functionName) { + case 'repeat': { + const [firstChild, ...otherChildren] = child.children.toArray() + const times = parseRepeatTimes(firstChild) + if (times == null) { + return left('Invalid grid CSS repeat times.') + } + + const lineName = getLineName() + + const values = new csstree.List().fromArray( + otherChildren.filter( + (c) => + c.type === 'Dimension' || + c.type === 'Identifier' || + c.type === 'Brackets' || + c.type === 'Function', + ), + ) + const parsedDimensions = parseGridChildren(values) + if (isRight(parsedDimensions)) { + dimensions.push(gridCSSRepeat(times, parsedDimensions.value, lineName)) + } else { + return left('Invalid grid CSS repeat values.') + } + break + } + case 'minmax': { + const values = new csstree.List().fromArray( + child.children + .toArray() + .filter((c) => c.type === 'Dimension' || c.type === 'Identifier'), + ) + const parsedDimensions = parseGridChildren(values) + if (isRight(parsedDimensions)) { + const min = parsedDimensions.value[0] + const max = parsedDimensions.value[1] + if ( + min == null || + !(min.type === 'NUMBER' || min.type === 'KEYWORD') || + max == null || + !(max.type === 'NUMBER' || max.type === 'KEYWORD') + ) { + return left('Invalid minmax arguments.') + } + dimensions.push(gridCSSMinmax(min, max, getLineName())) + } + break + } + default: + console.warn(`unknown css grid function ${functionName}`) + } + break + } + case 'Brackets': { + // The next child will get this line name + nextLineName = child.children.toArray().find((c) => c.type === 'Identifier')?.name ?? null + break + } + default: + return left(`invalid grid child type ${child.type}`) + } } + return right(dimensions) } export function parseDisplay(input: unknown): Either { @@ -2246,7 +2483,7 @@ function printTransformOrigin(transformOrigin: CSSTransformOrigin): JSExpression ) } -type CSSOverflow = boolean +export type CSSOverflow = boolean function parseOverflow(overflow: unknown): Either { if (typeof overflow === 'string') { @@ -4398,6 +4635,11 @@ const flexAlignmentsParser: Parser = isOneOfTheseParser([ FlexAlignment.Center, FlexAlignment.FlexEnd, FlexAlignment.Stretch, + FlexAlignment.Baseline, + FlexAlignment.FirstBaseline, + FlexAlignment.LastBaseline, + FlexAlignment.SafeCenter, + FlexAlignment.UnsafeCenter, ]) const flexJustifyContentParser: Parser = isOneOfTheseParser([ @@ -4407,6 +4649,10 @@ const flexJustifyContentParser: Parser = isOneOfTheseParser( FlexJustifyContent.SpaceAround, FlexJustifyContent.SpaceBetween, FlexJustifyContent.SpaceEvenly, + FlexJustifyContent.Stretch, + FlexJustifyContent.Normal, + FlexJustifyContent.SafeCenter, + FlexJustifyContent.UnsafeCenter, ]) export type CSSPosition = '-webkit-sticky' | 'absolute' | 'fixed' | 'relative' | 'static' | 'sticky' @@ -4492,7 +4738,8 @@ export interface ParsedCSSProperties { flexWrap: FlexWrap flexDirection: FlexDirection alignItems: FlexAlignment - alignContent: FlexAlignment + justifyItems: FlexAlignment + alignContent: FlexJustifyContent justifyContent: FlexJustifyContent alignSelf: FlexAlignment position: CSSPosition @@ -4602,7 +4849,8 @@ export const cssEmptyValues: ParsedCSSProperties = { flexWrap: FlexWrap.NoWrap, flexDirection: 'row', alignItems: FlexAlignment.FlexStart, - alignContent: FlexAlignment.FlexStart, + justifyItems: FlexAlignment.FlexStart, + alignContent: FlexJustifyContent.FlexStart, justifyContent: FlexJustifyContent.FlexStart, padding: { paddingTop: { @@ -4771,7 +5019,8 @@ export const cssParsers: CSSParsers = { flexWrap: flexWrapParser, flexDirection: parseFlexDirection, alignItems: flexAlignmentsParser, - alignContent: flexAlignmentsParser, + justifyItems: flexAlignmentsParser, + alignContent: flexJustifyContentParser, justifyContent: flexJustifyContentParser, padding: parsePadding, paddingTop: parseCSSLengthPercent, @@ -4848,6 +5097,7 @@ const cssPrinters: CSSPrinters = { flexWrap: jsxAttributeValueWithNoComments, flexDirection: jsxAttributeValueWithNoComments, alignItems: jsxAttributeValueWithNoComments, + justifyItems: jsxAttributeValueWithNoComments, alignContent: jsxAttributeValueWithNoComments, justifyContent: jsxAttributeValueWithNoComments, padding: printPaddingAsAttributeValue, @@ -5251,7 +5501,10 @@ export const computedStyleKeys: Array = Object.keys({ ...layoutEmptyValuesNew, }) -type Parser = (simpleValue: unknown, rawValue: ModifiableAttribute | null) => Either +export type Parser = ( + simpleValue: unknown, + rawValue: ModifiableAttribute | null, +) => Either type ParseFunction = ( prop: K, @@ -5526,7 +5779,8 @@ export const trivialDefaultValues: ParsedPropertiesWithNonTrivial = { flexWrap: FlexWrap.NoWrap, flexDirection: 'row', alignItems: FlexAlignment.FlexStart, - alignContent: FlexAlignment.FlexStart, + justifyItems: FlexAlignment.FlexStart, + alignContent: FlexJustifyContent.FlexStart, justifyContent: FlexJustifyContent.FlexStart, padding: nontrivial, paddingTop: { @@ -5648,3 +5902,81 @@ export function toggleShadowEnabled(oldValue: CSSBoxShadow): CSSBoxShadow { newValue.enabled = !newValue.enabled return newValue } + +function parseRepeatTimes(firstChild: csstree.CssNode) { + switch (firstChild.type) { + case 'Number': + return parseInt(firstChild.value) ?? '0' + case 'Identifier': + return firstChild.name === 'auto-fill' || firstChild.name === 'auto-fit' + ? cssKeyword(firstChild.name) + : null + default: + return null + } +} + +export function maybeParseGridSpan(nodes: csstree.CssNode[]): GridSpan | null { + if (nodes.length !== 2) { + return null + } + + const spanIndex = nodes.findIndex((node) => node.type === 'Identifier' && node.name === 'span') + if (spanIndex < 0) { + return null + } + + const valueNodes = nodes.slice(0, spanIndex).concat(nodes.slice(spanIndex + 1)) + + const numericValue: csstree.NumberNode | null = + valueNodes.find((node) => node.type === 'Number') ?? null + const areaValue: csstree.Identifier | null = + valueNodes.find((node) => node.type === 'Identifier') ?? null + + if (numericValue != null) { + return gridSpanNumeric(parseInt(numericValue.value)) + } else if (areaValue != null) { + return gridSpanArea(areaValue.name) + } else { + return null + } +} + +export function maybeParseGridLine( + nodes: csstree.CssNode[], + axis: 'row' | 'column', + container: GridContainerProperties, +): GridPosition | null { + if (nodes.length !== 1) { + return null + } + + const firstNode = nodes[0] + switch (firstNode.type) { + case 'Number': + return gridPositionValue(parseInt(firstNode.value)) + case 'Identifier': + // the identifier can either be a keyword… + if (isValidGridDimensionKeyword(firstNode.name)) { + return cssKeyword(firstNode.name) + } + + // …or a line name, in which case look it up in the template and return its index + const targetTracks = + axis === 'column' ? container.gridTemplateColumns : container.gridTemplateRows + // TODO important! this behavior is currently incorrect, as this needs to find the _first_ lineName (in case of repeats) + // or otherwise relative (even backwards). + const maybeLineFromName = + targetTracks?.type === 'DIMENSIONS' + ? targetTracks.dimensions.findIndex((dim) => dim.lineName === firstNode.name) + : null + if (maybeLineFromName == null || maybeLineFromName < 0) { + // line name not found + return null + } + + return gridPositionValue(maybeLineFromName + 1) // tracks are 1-indexed + default: + return null + } +} diff --git a/editor/src/components/inspector/common/inspector-end-to-end-tests.spec.browser2.tsx b/editor/src/components/inspector/common/inspector-end-to-end-tests.spec.browser2.tsx index a01a706829f8..3b14d2a59c07 100644 --- a/editor/src/components/inspector/common/inspector-end-to-end-tests.spec.browser2.tsx +++ b/editor/src/components/inspector/common/inspector-end-to-end-tests.spec.browser2.tsx @@ -70,6 +70,8 @@ import { invalidGroupStateToString } from '../../canvas/canvas-strategies/strate import selectEvent from 'react-select-event' import { MixedPlaceholder } from '../../../uuiui/inputs/base-input' import { cmdModifier } from '../../../utils/modifiers' +import type { SinonFakeTimers } from 'sinon' +import sinon from 'sinon' async function getControl( controlTestId: string, @@ -164,7 +166,25 @@ async function pressKeyTimes( }) } +function configureSinonSetupTeardown(): { clock: { current: SinonFakeTimers } } { + let clock: { current: SinonFakeTimers } = { current: null as any } // it will be non-null thanks to beforeEach + beforeEach(function () { + // TODO there is something wrong with sinon fake timers here that remotely break other tests that come after these. If your new browser tests are broken, this may be the reason. + clock.current = sinon.useFakeTimers({ + // the timers will tick so the editor is not totally broken, but we can fast-forward time at will + // WARNING: the Sinon fake timers will advance in 20ms increments + shouldAdvanceTime: true, + }) + }) + afterEach(function () { + clock.current?.restore() + }) + return { clock: clock } +} + describe('inspector tests with real metadata', () => { + const { clock } = configureSinonSetupTeardown() + it('padding controls', async () => { const renderResult = await renderTestEditorWithCode( makeTestProjectCodeWithSnippet(` @@ -1460,12 +1480,12 @@ describe('inspector tests with real metadata', () => { await act(async () => { await screen.findByTestId('section-header-Advanced') - fireEvent.click(screen.getByTestId('section-header-Advanced')) + fireEvent.mouseDown(screen.getByTestId('section-header-Advanced')) }) await act(async () => { await screen.findByTestId('target-selector-style') - fireEvent.click(screen.getByTestId('target-selector')) + fireEvent.mouseDown(screen.getByTestId('target-selector')) }) await act(async () => { await screen.findByTestId('target-list-item-css') @@ -1567,12 +1587,12 @@ describe('inspector tests with real metadata', () => { await act(async () => { await screen.findByTestId('section-header-Advanced') - fireEvent.click(screen.getByTestId('section-header-Advanced')) + fireEvent.mouseDown(screen.getByTestId('section-header-Advanced')) }) await act(async () => { await screen.findByTestId('target-selector-style') - fireEvent.click(screen.getByTestId('target-selector')) + fireEvent.mouseDown(screen.getByTestId('target-selector')) }) await act(async () => { await screen.findByTestId('target-list-item-css') @@ -1732,6 +1752,7 @@ describe('inspector tests with real metadata', () => { expect(widthControl.value).toBe('0') await pressKeyTimes(widthControl, renderResult, ['ArrowUp']) + clock.current.tick(700) expect(widthControl.value).toBe('1') await setControlValue('frame-width-number-input', '100', renderResult.renderedDOM) diff --git a/editor/src/components/inspector/common/longhand-shorthand-hooks.ts b/editor/src/components/inspector/common/longhand-shorthand-hooks.ts index de417fa225a2..1bf8322004e9 100644 --- a/editor/src/components/inspector/common/longhand-shorthand-hooks.ts +++ b/editor/src/components/inspector/common/longhand-shorthand-hooks.ts @@ -190,7 +190,11 @@ export function useInspectorInfoLonghandShorthand< setProp_UNSAFE(selectedView, shorthandPropertyPath, printedValue), ] }, selectedViewsRef.current) - dispatch(transient ? [transientActions(actionsToDispatch)] : actionsToDispatch) + dispatch( + transient + ? [transientActions(actionsToDispatch, selectedViewsRef.current)] + : actionsToDispatch, + ) } else { // we either have a dominant longhand key, or we need to append a new one const propertyPath = pathMappingFn(longhand, inspectorTargetPath) @@ -203,7 +207,11 @@ export function useInspectorInfoLonghandShorthand< setProp_UNSAFE(selectedView, propertyPath, printedValue), ] }, selectedViewsRef.current) - dispatch(transient ? [transientActions(actionsToDispatch)] : actionsToDispatch) + dispatch( + transient + ? [transientActions(actionsToDispatch, selectedViewsRef.current)] + : actionsToDispatch, + ) } } diff --git a/editor/src/components/inspector/common/property-path-hooks.ts b/editor/src/components/inspector/common/property-path-hooks.ts index cd7d469b963f..c0ef77d62597 100644 --- a/editor/src/components/inspector/common/property-path-hooks.ts +++ b/editor/src/components/inspector/common/property-path-hooks.ts @@ -1,5 +1,4 @@ import * as PP from '../../../core/shared/property-path' -import * as EP from '../../../core/shared/element-path' import deepEqual from 'fast-deep-equal' import * as ObjectPath from 'object-path' @@ -48,7 +47,7 @@ import type { StyleLayoutProp } from '../../../core/layout/layout-helpers-new' import { isHTMLComponent, isUtopiaAPIComponent } from '../../../core/model/project-file-utils' import { stripNulls } from '../../../core/shared/array-utils' import type { Either } from '../../../core/shared/either' -import { eitherToMaybe, flatMapEither, isRight, left } from '../../../core/shared/either' +import { eitherToMaybe, flatMapEither, isRight, left, right } from '../../../core/shared/either' import type { JSXAttributes, ComputedStyle, @@ -92,6 +91,11 @@ import type { EditorAction } from '../../editor/action-types' import { useDispatch } from '../../editor/store/dispatch-context' import { eitherRight, fromTypeGuard } from '../../../core/shared/optics/optic-creators' import { modify } from '../../../core/shared/optics/optic-utilities' +import { getActivePlugin } from '../../canvas/plugins/style-plugins' +import { isStyleInfoKey, type StyleInfo } from '../../canvas/canvas-types' +import { assertNever } from '../../../core/shared/utils' +import { maybeCssPropertyFromInlineStyle } from '../../canvas/commands/utils/property-utils' +import { getContainingSceneSizeFromEditorState } from '../../canvas/responsive-utils' export interface InspectorPropsContextData { selectedViews: Array @@ -744,10 +748,40 @@ const getModifiableAttributeResultToExpressionOptic = eitherRight< ModifiableAttribute >().compose(fromTypeGuard(isRegularJSXAttribute)) +function maybeStyleInfoKeyFromPropertyPath(propertyPath: PropertyPath): keyof StyleInfo | null { + const maybeCSSProp = maybeCssPropertyFromInlineStyle(propertyPath) + if (maybeCSSProp == null || !isStyleInfoKey(maybeCSSProp)) { + return null + } + return maybeCSSProp +} + export function useGetMultiselectedProps

( pathMappingFn: PathMappingFn

, propKeys: P[], ): MultiselectAtProps

{ + const styleInfoReaderRef = useRefEditorState( + (store) => + (props: JSXAttributes, prop: keyof StyleInfo): GetModifiableAttributeResult => { + const elementStyle = getActivePlugin(store.editor).readStyleFromElementProps(props, prop, { + sceneSize: getContainingSceneSizeFromEditorState(store.editor), + }) + if (elementStyle == null) { + return right({ type: 'ATTRIBUTE_NOT_FOUND' }) + } + switch (elementStyle.type) { + case 'not-found': + return right({ type: 'ATTRIBUTE_NOT_FOUND' }) + case 'not-parsable': + return right(elementStyle.originalValue) + case 'property': + return right(elementStyle.propertyValue) + default: + assertNever(elementStyle) + } + }, + ) + return useKeepReferenceEqualityIfPossible( useContextSelector( InspectorPropsContext, @@ -755,10 +789,13 @@ export function useGetMultiselectedProps

( const keyFn = (propKey: P) => propKey const mapFn = (propKey: P) => { return contextData.editedMultiSelectedProps.map((props) => { - const result = getModifiableJSXAttributeAtPath( - props, - pathMappingFn(propKey, contextData.targetPath), - ) + const targetPath = pathMappingFn(propKey, contextData.targetPath) + const maybeStyleInfoKey = maybeStyleInfoKeyFromPropertyPath(targetPath) + const result = + maybeStyleInfoKey != null + ? styleInfoReaderRef.current(props, maybeStyleInfoKey) + : getModifiableJSXAttributeAtPath(props, targetPath) + // This wipes the uid from any `JSExpression` values we may have retrieved, // as that can cause the deep equality check to fail for different elements // with the same value for a given property. diff --git a/editor/src/components/inspector/controls/advanced-grid-modal.tsx b/editor/src/components/inspector/controls/advanced-grid-modal.tsx new file mode 100644 index 000000000000..f42b93c157c9 --- /dev/null +++ b/editor/src/components/inspector/controls/advanced-grid-modal.tsx @@ -0,0 +1,347 @@ +import React from 'react' +import { InspectorModal } from '../widgets/inspector-modal' +import { colorTheme, FlexColumn, H1, SquareButton, UtopiaStyles } from '../../../uuiui' +import type { GridRowVariant } from '../widgets/ui-grid-row' +import { UIGridRow } from '../widgets/ui-grid-row' +import type { OptionChainOption } from './option-chain-control' +import { OptionChainControl } from './option-chain-control' +import { PrettyLabel } from '../sections/layout-section/flex-container-subsection/flex-container-controls' +import type { InspectorInfo } from '../common/property-path-hooks' +import { useInspectorLayoutInfo } from '../common/property-path-hooks' +import { + RadixSelect, + regularRadixSelectOption, + separatorRadixSelectOption, +} from '../../../uuiui/radix-components' +import { optionalMap } from '../../../core/shared/optional-utils' +import { AllFlexAlignments, AllFlexJustifyContents, FlexAlignment } from 'utopia-api/core' +import { FlexJustifyContent } from 'utopia-api/core' +import { GridAutoColsOrRowsControlInner } from '../grid-auto-cols-or-rows-control' +import { Substores, useEditorState, useRefEditorState } from '../../editor/store/store-hook' +import { MetadataUtils } from '../../../core/model/element-metadata-utils' +import { selectedViewsSelector } from '../inpector-selectors' + +export interface AdvancedGridModalProps { + id: string + testId: string + popupOpen: boolean + closePopup: () => void + modalOffset: { + x: number + y: number + } +} + +export const AdvancedGridModal = React.memo((props: AdvancedGridModalProps) => { + const modalOffset = props.modalOffset ?? { x: 0, y: 0 } + const [dropdownOpen, setDropdownOpen] = React.useState({ + justifyContent: false, + alignContent: false, + }) + const toggleJustifyContentDropdown = React.useCallback((state: boolean) => { + setDropdownOpen((prev) => ({ ...prev, justifyContent: state })) + }, []) + const toggleAlignContentDropdown = React.useCallback((state: boolean) => { + setDropdownOpen((prev) => ({ ...prev, alignContent: state })) + }, []) + const closePopup = React.useCallback(() => { + if (!dropdownOpen.justifyContent && !dropdownOpen.alignContent) { + props.closePopup() + } + }, [dropdownOpen, props]) + + /** + * If you edit/add/remove props here, remember to update the same values in useGridAdvancedPropertiesCount + * to keep the counter badge up to date displayed in the advanced menu up to date. + */ + const alignItemsLayoutInfo = useInspectorLayoutInfo('alignItems') + const justifyItemsLayoutInfo = useInspectorLayoutInfo('justifyItems') + const alignContentLayoutInfo = useInspectorLayoutInfo('alignContent') + const justifyContentLayoutInfo = useInspectorLayoutInfo('justifyContent') + + const currentJustifyContentValue = React.useMemo( + () => getLayoutInfoValue(justifyContentLayoutInfo), + [justifyContentLayoutInfo], + ) + + const currentAlignContentValue = React.useMemo( + () => getLayoutInfoValue(alignContentLayoutInfo), + [alignContentLayoutInfo], + ) + + const currentJustifyItemsValue = React.useMemo( + () => getValueOrUnset(justifyItemsLayoutInfo), + [justifyItemsLayoutInfo], + ) + + const currentAlignItemsValue = React.useMemo( + () => getValueOrUnset(alignItemsLayoutInfo), + [alignItemsLayoutInfo], + ) + + const justifyOptions = [ + unsetSelectOption, + separatorRadixSelectOption(), + ...[ + FlexJustifyContent.FlexStart, + FlexJustifyContent.Center, + FlexJustifyContent.FlexEnd, + FlexJustifyContent.SpaceAround, + FlexJustifyContent.SpaceBetween, + FlexJustifyContent.SpaceEvenly, + FlexJustifyContent.Stretch, + ].map(selectOption), + ] + + const alignOptions = [ + unsetSelectOption, + separatorRadixSelectOption(), + ...[ + FlexAlignment.Auto, + FlexAlignment.FlexStart, + FlexAlignment.Center, + FlexAlignment.FlexEnd, + FlexAlignment.Stretch, + ].map(selectOption), + ] + + const onSubmitJustifyContent = React.useCallback( + (value: string) => { + if (value === 'unset') { + justifyContentLayoutInfo.onUnsetValues() + } else { + justifyContentLayoutInfo.onSubmitValue(value as FlexJustifyContent) + } + }, + [justifyContentLayoutInfo], + ) + + const onSubmitAlignContent = React.useCallback( + (value: string) => { + if (value === 'unset') { + alignContentLayoutInfo.onUnsetValues() + } else { + alignContentLayoutInfo.onSubmitValue(value as FlexJustifyContent) + } + }, + [alignContentLayoutInfo], + ) + + const selectedViewsRef = useRefEditorState(selectedViewsSelector) + const grid = useEditorState( + Substores.metadata, + (store) => { + if (selectedViewsRef.current.length !== 1) { + return null + } + return MetadataUtils.findElementByElementPath( + store.editor.jsxMetadata, + selectedViewsRef.current[0], + ) + }, + 'AdvancedGridModal grid', + ) + + if (grid == null) { + return null + } + + const advancedGridModal = ( + + + +

+ Grid Settings +

+ + + × + + + + Items + + + + Justify + + + + Align + + + + Entire Grid + + + Justify + + + + Align + + + + Template + + + + + + + + + + ) + + return ( +
+ {props.popupOpen ? advancedGridModal : null} +
+ ) +}) + +AdvancedGridModal.displayName = 'AdvancedGridModal' + +const itemsOptions = (alignOrJustify: 'align' | 'justify') => + [ + { + value: 'flex-start', + tooltip: PrettyLabel[alignOrJustify === 'justify' ? 'left' : 'top'], + icon: { + category: `inspector-element`, + type: `${alignOrJustify}Items-start`, + color: 'secondary', + width: 16, + height: 16, + }, + }, + { + value: 'center', + tooltip: 'Center', + icon: { + category: `inspector-element`, + type: `${alignOrJustify}Items-center`, + color: 'secondary', + width: 16, + height: 16, + }, + }, + { + value: 'flex-end', + tooltip: PrettyLabel[alignOrJustify === 'justify' ? 'right' : 'bottom'], + icon: { + category: `inspector-element`, + type: `${alignOrJustify}Items-end`, + color: 'secondary', + width: 16, + height: 16, + }, + }, + { + value: 'stretch', + tooltip: 'Stretch', + icon: { + category: `inspector-element`, + type: `${alignOrJustify}Items-stretch`, + color: 'secondary', + width: 16, + height: 16, + }, + }, + ] as Array> + +const justifyItemsOptions = itemsOptions('justify') +const alignItemsOptions = itemsOptions('align') +const rowVariant: GridRowVariant = '|--67px--|<--------1fr-------->' + +function selectOption(value: FlexJustifyContent | FlexAlignment) { + return regularRadixSelectOption({ + label: value.replace('-', ' '), + value: value, + placeholder: false, + }) +} + +const unsetSelectOption = regularRadixSelectOption({ + label: 'unset', + value: 'unset', + placeholder: true, +}) + +const isUnset = (control: InspectorInfo): boolean => + control.controlStatus === 'detected' || control.controlStatus === 'unset' + +const getLayoutInfoValue = (control: InspectorInfo) => + isUnset(control) ? unsetSelectOption : optionalMap(selectOption, control.value) ?? undefined + +const getValueOrUnset = (control: InspectorInfo): string => + isUnset(control) ? 'unset' : control.value diff --git a/editor/src/components/inspector/controls/color-picker-utils.ts b/editor/src/components/inspector/controls/color-picker-utils.ts new file mode 100644 index 000000000000..2be87ffa1bd8 --- /dev/null +++ b/editor/src/components/inspector/controls/color-picker-utils.ts @@ -0,0 +1,18 @@ +let colorPickerElement: Node | null = null + +export function setColorPickerElement(node: Node | null): void { + colorPickerElement = node +} + +export function clearColorPickerElement(): void { + colorPickerElement = null +} + +export function isInsideColorPicker(eventTarget: EventTarget | null): boolean { + return ( + eventTarget != null && + colorPickerElement != null && + eventTarget instanceof Node && + colorPickerElement.contains(eventTarget) + ) +} diff --git a/editor/src/components/inspector/controls/color-picker.tsx b/editor/src/components/inspector/controls/color-picker.tsx index cc86b918cce6..e08380c30df4 100644 --- a/editor/src/components/inspector/controls/color-picker.tsx +++ b/editor/src/components/inspector/controls/color-picker.tsx @@ -28,6 +28,8 @@ import { } from '../../../uuiui' import { pickColorWithEyeDropper } from '../../canvas/canvas-utils' import { ColorPickerSwatches } from './color-picker-swatches' +import { didWeHandleMouseMoveForThisFrame, mouseMoveHandled } from '../../../components/mouse-move' +import { clearColorPickerElement, setColorPickerElement } from './color-picker-utils' const checkerboardBackground = UtopiaStyles.backgrounds.checkerboardBackground @@ -196,17 +198,18 @@ export class ColorPickerInner extends React.Component< ColorPickerInnerProps, ColorPickerInnerState > { - private RefFirstControl = React.createRef() - private fullWidth = colorPickerWidth private fullHeight = MetadataEditorModalPreviewHeight private paddedWidth = this.fullWidth - inspectorEdgePadding * 2 private SVControlRef = React.createRef() + private SVControlIndicatorRef = React.createRef() private SVOrigin: WindowPoint = { x: 0, y: 0 } as WindowPoint private HueControlRef = React.createRef() + private HueControlIndicatorRef = React.createRef() private HueOriginLeft: number = 0 private AlphaControlRef = React.createRef() + private AlphaControlIndicatorRef = React.createRef() private AlphaOriginLeft: number = 0 constructor(props: ColorPickerInnerProps) { @@ -232,13 +235,12 @@ export class ColorPickerInner extends React.Component< componentDidMount() { setTimeout(() => { // wrapping in a setTimeout so we don't dispatch from inside React lifecycle - if (this.RefFirstControl.current != null) { - this.RefFirstControl.current.focus() - } + this.setIndicators() }, 0) } componentWillUnmount() { + clearColorPickerElement() document.removeEventListener('mousemove', this.onSVMouseMove) document.removeEventListener('mouseup', this.onSVMouseUp) document.removeEventListener('mousemove', this.onHueSliderMouseMove) @@ -255,10 +257,12 @@ export class ColorPickerInner extends React.Component< return Chroma(h * 360, s, v, 'hsv') .alpha(a) .hex('auto') - .toUpperCase() } - static getDerivedStateFromProps(props: ColorPickerInnerProps, state: ColorPickerInnerState) { + static getDerivedStateFromProps( + props: ColorPickerInnerProps, + state: ColorPickerInnerState, + ): Partial | null { const chroma = cssColorToChromaColorOrDefault(props.value) if (state.dirty) { return { @@ -267,19 +271,24 @@ export class ColorPickerInner extends React.Component< } } else { const controlStateHexa = ColorPickerInner.getHexaColorFromControlPositionState(state) - const newPropsHexa = chroma.hex('auto').toUpperCase() + const newPropsHexa = chroma.hex('auto') if (controlStateHexa === newPropsHexa) { return null } else { const newCalculatedState = deriveStateFromNewColor(chroma, state.normalisedHuePosition) - return { ...newCalculatedState, _propsHexa: chroma.hex('auto').toUpperCase() } + return newCalculatedState } } } - componentDidUpdate(prevProps: ColorPickerInnerProps) { - if (this.RefFirstControl.current != null && prevProps.id !== this.props.id) { - this.RefFirstControl.current.focus() + componentDidUpdate(prevProps: ColorPickerInnerProps, prevState: ColorPickerInnerState) { + if ( + this.state.normalisedSaturationPosition !== prevState.normalisedSaturationPosition || + this.state.normalisedValuePosition !== prevState.normalisedValuePosition || + this.state.normalisedHuePosition !== prevState.normalisedHuePosition || + this.state.normalisedAlphaPosition !== prevState.normalisedAlphaPosition + ) { + this.setIndicators() } } @@ -366,7 +375,10 @@ export class ColorPickerInner extends React.Component< onSVMouseMove = (e: MouseEvent) => { e.stopPropagation() - this.setSVFromClientPosition(e.clientX, e.clientY, true) + if (!didWeHandleMouseMoveForThisFrame) { + mouseMoveHandled() + this.setSVFromClientPosition(e.clientX, e.clientY, true) + } } hexFromHue = (h: number, s: number, v: number, a: number): string => { @@ -427,7 +439,10 @@ export class ColorPickerInner extends React.Component< onHueSliderMouseMove = (e: MouseEvent) => { e.stopPropagation() - this.setHueFromClientX(e.clientX, true) + if (!didWeHandleMouseMoveForThisFrame) { + mouseMoveHandled() + this.setHueFromClientX(e.clientX, true) + } } onHueSliderMouseUp = (e: MouseEvent) => { @@ -468,7 +483,10 @@ export class ColorPickerInner extends React.Component< onAlphaSliderMouseMove = (e: MouseEvent) => { e.stopPropagation() - this.setAlphaFromClientX(e.clientX, true) + if (!didWeHandleMouseMoveForThisFrame) { + mouseMoveHandled() + this.setAlphaFromClientX(e.clientX, true) + } } onAlphaSliderMouseUp = (e: MouseEvent) => { @@ -532,6 +550,25 @@ export class ColorPickerInner extends React.Component< this.setNewHex(newValue) } + setIndicators = () => { + if (this.SVControlIndicatorRef.current != null) { + this.SVControlIndicatorRef.current.style.left = `${ + this.state.normalisedSaturationPosition * 100 + }%` + this.SVControlIndicatorRef.current.style.top = `${ + (1 - this.state.normalisedValuePosition) * 100 + }%` + } + if (this.HueControlIndicatorRef.current != null) { + this.HueControlIndicatorRef.current.style.left = `${this.state.normalisedHuePosition * 100}%` + } + if (this.AlphaControlIndicatorRef.current != null) { + this.AlphaControlIndicatorRef.current.style.left = `${ + this.state.normalisedAlphaPosition * 100 + }%` + } + } + render() { const h = this.state.normalisedHuePosition const s = this.state.normalisedSaturationPosition @@ -553,7 +590,12 @@ export class ColorPickerInner extends React.Component< )` return ( -
+
{ + setColorPickerElement(node) + }} + >
@@ -610,6 +651,7 @@ export class ColorPickerInner extends React.Component<
= ( transient?: boolean, ) => void +export type OnSubmitValueOrUnknownOrEmptyMaybeTransient = ( + value: UnknownOrEmptyInput, + transient: boolean, +) => void + export interface DEPRECATEDControlProps { id: string testId: string @@ -32,6 +37,7 @@ export interface DEPRECATEDControlProps { readOnly?: boolean selected?: boolean options?: ReadonlyArray | ReadonlyArray> + onUnsetValues?: () => void DEPRECATED_controlOptions?: | DEPRECATEDGenericControlOptions | DEPRECATEDOptionControlOptions diff --git a/editor/src/components/inspector/controls/option-chain-control.tsx b/editor/src/components/inspector/controls/option-chain-control.tsx index b65cb235a863..05081a2a53a4 100644 --- a/editor/src/components/inspector/controls/option-chain-control.tsx +++ b/editor/src/components/inspector/controls/option-chain-control.tsx @@ -16,6 +16,7 @@ export interface OptionChainOption { label?: string tooltip?: string forceCallOnSubmitValue?: boolean // Call the onSubmitValue again even when the control is already on that value + disabled?: boolean } export function getOptionControlTestId(testIdPrefix: string, postfix: string): string { @@ -89,12 +90,18 @@ export const OptionChainControl: React.FunctionComponent< icon: option.icon, iconComponent: option.iconComponent, labelInner: option.label, + disabled: option.disabled, }} value={props.value === option.value} // eslint-disable-next-line react/jsx-no-bind - onSubmitValue={(value: boolean) => { - if (value || option.forceCallOnSubmitValue) { + onSubmitValue={(isChecked: boolean) => { + if (option.disabled === true) { + return + } + if (isChecked || option.forceCallOnSubmitValue) { props.onSubmitValue(option.value) + } else { + props.onUnsetValues?.() } }} /> diff --git a/editor/src/components/inspector/controls/option-control.tsx b/editor/src/components/inspector/controls/option-control.tsx index 24423c448ef2..f01c89636fce 100644 --- a/editor/src/components/inspector/controls/option-control.tsx +++ b/editor/src/components/inspector/controls/option-control.tsx @@ -15,6 +15,7 @@ export interface DEPRECATEDOptionControlOptions extends DEPRECATEDGenericControl tooltip?: string width?: number height?: number + disabled?: boolean } export const OptionControl: React.FunctionComponent< @@ -101,15 +102,15 @@ export const OptionControl: React.FunctionComponent< isChecked && props.controlStatus === 'overridden' ? colorTheme.brandNeonPink.value : colorTheme.fg1.value, - filter: props.controlStatus == 'disabled' ? 'grayscale(0)' : undefined, - opacity: props.controlStatus == 'disabled' ? undefined : isChecked ? 1 : 0.4, + filter: props.controlStatus === 'disabled' ? 'grayscale(0)' : undefined, + opacity: props.controlStatus === 'disabled' ? undefined : isChecked ? 1 : 0.7, // If part of a option chain control: '.option-chain-control-container &': { boxShadow: 'none !important', borderRadius: 2, - opacity: isChecked ? 1 : 0.4, + opacity: controlOptions.disabled ? 0.2 : isChecked ? 1 : 0.7, '&:hover': { - opacity: props.controlStatus == 'disabled' ? undefined : 1, + opacity: controlOptions.disabled ? undefined : 1, color: isChecked && props.controlStatus === 'overridden' ? colorTheme.brandNeonPink.value @@ -117,10 +118,11 @@ export const OptionControl: React.FunctionComponent< }, }, '&:hover': { - opacity: props.controlStatus == 'disabled' ? undefined : 1, + opacity: + controlOptions.disabled || props.controlStatus === 'disabled' ? undefined : 1, }, '.control-option-icon-component': { - opacity: 0.4, + opacity: 0.7, }, '&:hover .control-option-icon-component': { opacity: 1, diff --git a/editor/src/components/inspector/fill-hug-fixed-control.tsx b/editor/src/components/inspector/fill-hug-fixed-control.tsx index e97e7e2e5e5b..08dd8f40050e 100644 --- a/editor/src/components/inspector/fill-hug-fixed-control.tsx +++ b/editor/src/components/inspector/fill-hug-fixed-control.tsx @@ -80,6 +80,7 @@ export const CollapsedLabel = 'Collapsed' as const export const HugGroupContentsLabel = 'Hug' as const export const ComputedLabel = 'Computed' as const export const DetectedLabel = 'Detected' as const +export const StretchLabel = 'Stretch' as const export function selectOptionLabel(mode: FixedHugFillMode): string { switch (mode) { @@ -101,6 +102,8 @@ export function selectOptionLabel(mode: FixedHugFillMode): string { return ComputedLabel case 'detected': return DetectedLabel + case 'stretch': + return StretchLabel default: assertNever(mode) } @@ -126,6 +129,8 @@ export function selectOptionIconType( return `fixed-${dimension}` case 'detected': return `fixed-${dimension}` + case 'stretch': + return `fill-${dimension}` default: assertNever(mode) } @@ -396,6 +401,7 @@ function strategyForChangingFillFixedHugType( ): Array { switch (mode) { case 'fill': + case 'stretch': return setPropFillStrategies(metadata, selectedElements, axis, 'default', otherAxisSetToFill) case 'hug': case 'squeeze': @@ -427,6 +433,7 @@ function pickFixedValue(value: FixedHugFill): CSSNumber | undefined { case 'fill': case 'hug-group': return value.value + case 'stretch': case 'hug': case 'squeeze': case 'collapsed': diff --git a/editor/src/components/inspector/flex-direction-control.spec.browser2.tsx b/editor/src/components/inspector/flex-direction-control.spec.browser2.tsx index d9ad55ec3bca..5ea4e771d533 100644 --- a/editor/src/components/inspector/flex-direction-control.spec.browser2.tsx +++ b/editor/src/components/inspector/flex-direction-control.spec.browser2.tsx @@ -3,14 +3,16 @@ import { expectSingleUndo2Saves, hoverControlWithCheck, selectComponentsForTest, + setFeatureForBrowserTestsUseInDescribeBlockOnly, } from '../../utils/utils.test-utils' import { CanvasControlsContainerID } from '../canvas/controls/new-canvas-controls' import { getSubduedPaddingControlTestID } from '../canvas/controls/select-mode/subdued-padding-control' import { mouseClickAtPoint } from '../canvas/event-helpers.test-utils' import type { EditorRenderResult } from '../canvas/ui-jsx.test-utils' -import { renderTestEditorWithCode } from '../canvas/ui-jsx.test-utils' +import { renderTestEditorWithCode, renderTestEditorWithModel } from '../canvas/ui-jsx.test-utils' import type { FlexDirection } from './common/css-utils' import { FlexDirectionControlTestId, FlexDirectionToggleTestId } from './flex-direction-control' +import { TailwindProject } from './sections/flex-section.test-utils' describe('set flex direction', () => { it('set flex direction to row from not set', async () => { @@ -111,6 +113,62 @@ describe('set flex direction', () => { expect(controls.length).toEqual(4) }) }) + + describe('Tailwind', () => { + setFeatureForBrowserTestsUseInDescribeBlockOnly('Tailwind', true) + + it('set flex direction to column from not set', async () => { + const editor = await renderTestEditorWithModel( + TailwindProject('flex'), + 'await-first-dom-report', + ) + await selectDiv(editor) + await expectSingleUndo2Saves(editor, () => clickOn(editor, 'column')) + const div = editor.renderedDOM.getByTestId('mydiv') + expect(div.className).toEqual( + 'top-10 left-10 w-64 h-64 bg-slate-100 absolute flex flex-col', // flex-col is set by the control + ) + }) + + it('set flex direction to row-reverse from not set', async () => { + const editor = await renderTestEditorWithModel( + TailwindProject('flex'), + 'await-first-dom-report', + ) + await selectDiv(editor) + await expectSingleUndo2Saves(editor, () => clickOn(editor, 'row-reverse')) + const div = editor.renderedDOM.getByTestId('mydiv') + expect(div.className).toEqual( + 'top-10 left-10 w-64 h-64 bg-slate-100 absolute flex flex-row-reverse', // flex-row-reverse is set by the control + ) + }) + + it('set flex direction to column-reverse from not set', async () => { + const editor = await renderTestEditorWithModel( + TailwindProject('flex'), + 'await-first-dom-report', + ) + await selectDiv(editor) + await expectSingleUndo2Saves(editor, () => clickOn(editor, 'column-reverse')) + const div = editor.renderedDOM.getByTestId('mydiv') + expect(div.className).toEqual( + 'top-10 left-10 w-64 h-64 bg-slate-100 absolute flex flex-col-reverse', // flex-row-reverse is set by the control + ) + }) + + it('set flex direction to row from column', async () => { + const editor = await renderTestEditorWithModel( + TailwindProject('flex flex-col'), + 'await-first-dom-report', + ) + await selectDiv(editor) + await expectSingleUndo2Saves(editor, () => clickOn(editor, 'row')) + const div = editor.renderedDOM.getByTestId('mydiv') + expect(div.className).toEqual( + 'top-10 left-10 w-64 h-64 bg-slate-100 absolute flex flex-row', // flex-row is set by the control + ) + }) + }) }) async function selectDiv(editor: EditorRenderResult): Promise { diff --git a/editor/src/components/inspector/flex-section.spec.browser2.tsx b/editor/src/components/inspector/flex-section.spec.browser2.tsx index 61e646a27eca..e7c1749b892c 100644 --- a/editor/src/components/inspector/flex-section.spec.browser2.tsx +++ b/editor/src/components/inspector/flex-section.spec.browser2.tsx @@ -2,6 +2,7 @@ import { selectComponentsForTest } from '../../utils/utils.test-utils' import { renderTestEditorWithCode } from '../canvas/ui-jsx.test-utils' import * as EP from '../../core/shared/element-path' import { act, fireEvent, screen } from '@testing-library/react' +import { GridAutoColsOrRowsControlTestId } from './grid-auto-cols-or-rows-control' describe('flex section', () => { describe('grid dimensions', () => { @@ -11,7 +12,7 @@ describe('flex section', () => { const control = await screen.findByTestId('grid-dimension-column-0') await typeIntoField(control, '200px') const grid = await renderResult.renderedDOM.findByTestId('grid') - expect(grid.style.gridTemplateColumns).toEqual('[area1] 200px 1fr 1fr 1fr 1fr') + expect(grid.style.gridTemplateColumns).toEqual('[line1] 200px 1fr 1fr 1fr 1fr') }) it('can type a number without unit for dimension', async () => { const renderResult = await renderTestEditorWithCode(gridProject, 'await-first-dom-report') @@ -19,7 +20,7 @@ describe('flex section', () => { const control = await screen.findByTestId('grid-dimension-column-0') await typeIntoField(control, '2') const grid = await renderResult.renderedDOM.findByTestId('grid') - expect(grid.style.gridTemplateColumns).toEqual('[area1] 2fr 1fr 1fr 1fr 1fr') + expect(grid.style.gridTemplateColumns).toEqual('[line1] 2fr 1fr 1fr 1fr 1fr') }) it('can type a fractional number for dimension', async () => { const renderResult = await renderTestEditorWithCode(gridProject, 'await-first-dom-report') @@ -27,7 +28,7 @@ describe('flex section', () => { const control = await screen.findByTestId('grid-dimension-column-0') await typeIntoField(control, '2fr') const grid = await renderResult.renderedDOM.findByTestId('grid') - expect(grid.style.gridTemplateColumns).toEqual('[area1] 2fr 1fr 1fr 1fr 1fr') + expect(grid.style.gridTemplateColumns).toEqual('[line1] 2fr 1fr 1fr 1fr 1fr') }) it('can type a keyword for dimension', async () => { const renderResult = await renderTestEditorWithCode(gridProject, 'await-first-dom-report') @@ -35,7 +36,7 @@ describe('flex section', () => { const control = await screen.findByTestId('grid-dimension-column-0') await typeIntoField(control, 'min-content') const grid = await renderResult.renderedDOM.findByTestId('grid') - expect(grid.style.gridTemplateColumns).toEqual('[area1] min-content 1fr 1fr 1fr 1fr') + expect(grid.style.gridTemplateColumns).toEqual('[line1] min-content 1fr 1fr 1fr 1fr') }) it('ignores a typed invalid keyword for dimension', async () => { const renderResult = await renderTestEditorWithCode(gridProject, 'await-first-dom-report') @@ -43,7 +44,7 @@ describe('flex section', () => { const control = await screen.findByTestId('grid-dimension-column-0') await typeIntoField(control, 'not-a-keyword') const grid = await renderResult.renderedDOM.findByTestId('grid') - expect(grid.style.gridTemplateColumns).toEqual('[area1] 1fr 1fr 1fr 1fr 1fr') + expect(grid.style.gridTemplateColumns).toEqual('[line1] 1fr 1fr 1fr 1fr 1fr') }) it('defaults to auto if empty', async () => { const renderResult = await renderTestEditorWithCode(gridProject, 'await-first-dom-report') @@ -51,18 +52,72 @@ describe('flex section', () => { const control = await screen.findByTestId('grid-dimension-column-0') await typeIntoField(control, null) const grid = await renderResult.renderedDOM.findByTestId('grid') - expect(grid.style.gridTemplateColumns).toEqual('[area1] auto 1fr 1fr 1fr 1fr') + expect(grid.style.gridTemplateColumns).toEqual('[line1] auto 1fr 1fr 1fr 1fr') }) - it('uses auto for defaults when empty', async () => { + it('updates a repeat expression', async () => { + const renderResult = await renderTestEditorWithCode( + gridProjectWithRepeat, + 'await-first-dom-report', + ) + await selectComponentsForTest(renderResult, [EP.fromString('sb/grid')]) + + const grid = await renderResult.renderedDOM.findByTestId('grid') + const input: HTMLInputElement = await screen.findByTestId('grid-dimension-column-1') + await typeIntoField(input, 'repeat(2, 0.5fr 42px)') + expect(grid.style.gridTemplateColumns).toEqual('[line1] 1fr repeat(2, 0.5fr 42px) 2fr') + expect(input.value).toBe('repeat(2, 0.5fr 42px)') + }) + it('does not show line names in the input', async () => { + const renderResult = await renderTestEditorWithCode(gridProject, 'await-first-dom-report') + await selectComponentsForTest(renderResult, [EP.fromString('sb/grid')]) + const control: HTMLInputElement = await screen.findByTestId('grid-dimension-column-0') + const grid = await renderResult.renderedDOM.findByTestId('grid') + expect(grid.style.gridTemplateColumns).toEqual('[line1] 1fr 1fr 1fr 1fr 1fr') + expect(control.value).toBe('1fr') + }) + }) + describe('auto cols/rows', () => { + it('can set a number', async () => { const renderResult = await renderTestEditorWithCode( gridProjectWithoutTemplate, 'await-first-dom-report', ) await selectComponentsForTest(renderResult, [EP.fromString('sb/grid')]) - const control = await screen.findByTestId('grid-dimension-column-0') - await typeIntoField(control, '100') + const control: HTMLInputElement = await screen.findByTestId( + GridAutoColsOrRowsControlTestId('column'), + ) + await typeIntoField(control, '50px') + const grid = await renderResult.renderedDOM.findByTestId('grid') + expect(grid.style.gridAutoColumns).toEqual('50px') + expect(control.value).toBe('50px') + }) + it('can set a keyword', async () => { + const renderResult = await renderTestEditorWithCode( + gridProjectWithoutTemplate, + 'await-first-dom-report', + ) + await selectComponentsForTest(renderResult, [EP.fromString('sb/grid')]) + const control: HTMLInputElement = await screen.findByTestId( + GridAutoColsOrRowsControlTestId('column'), + ) + await typeIntoField(control, 'min-content') const grid = await renderResult.renderedDOM.findByTestId('grid') - expect(grid.style.gridTemplateColumns).toEqual('100px auto auto') + expect(grid.style.gridAutoColumns).toEqual('min-content') + expect(control.value).toBe('min-content') + }) + it('can set an expression', async () => { + const renderResult = await renderTestEditorWithCode( + gridProjectWithoutTemplate, + 'await-first-dom-report', + ) + await selectComponentsForTest(renderResult, [EP.fromString('sb/grid')]) + const control: HTMLInputElement = await screen.findByTestId( + GridAutoColsOrRowsControlTestId('column'), + ) + await typeIntoField(control, 'minmax(50px, 1fr)') + const grid = await renderResult.renderedDOM.findByTestId('grid') + expect(grid.style.gridAutoColumns).toEqual('minmax(50px, 1fr)') + expect(control.value).toBe('minmax(50px, 1fr)') }) }) }) @@ -87,7 +142,7 @@ export var storyboard = ( style={{ display: 'grid', gridTemplateRows: '80px 1fr 1fr', - gridTemplateColumns: '[area1] 1fr 1fr 1fr 1fr 1fr', + gridTemplateColumns: '[line1] 1fr 1fr 1fr 1fr 1fr', gridGap: 10, height: 322, width: 364, @@ -111,7 +166,7 @@ export var storyboard = ( width: 41, height: 23, border: '1px solid #000', - gridColumn: 'area1', + gridColumn: 'line1', gridRow: 1, backgroundColor: '#09f', }} @@ -168,7 +223,66 @@ export var storyboard = ( width: 41, height: 23, border: '1px solid #000', - gridColumn: 'area1', + gridColumn: 'line1', + gridRow: 1, + backgroundColor: '#09f', + }} + /> +
+ a test +
+
+ +) +` + +const gridProjectWithRepeat = ` +import * as React from 'react' +import { Storyboard } from 'utopia-api' + +export var storyboard = ( + +
+
+
{ - it('empty values', async () => { - expect( - mergeGridTemplateValues({ - calculated: [], - fromProps: [], - autoValues: [], - }), - ).toEqual([]) - }) - - it('only calculated', async () => { - expect( - mergeGridTemplateValues({ - calculated: [gridCSSNumber(cssNumber(1), null)], - fromProps: [], - autoValues: [], - }), - ).toEqual([gridCSSKeyword(cssKeyword('auto'), null)]) - }) - - it('competing calculated and from props', async () => { - expect( - mergeGridTemplateValues({ - calculated: [gridCSSNumber(cssNumber(1), null)], - fromProps: [gridCSSNumber(cssNumber(2), null)], - autoValues: [], - }), - ).toEqual([gridCSSNumber(cssNumber(2), null)]) - }) - - it('multiple elements, empty from props', async () => { - expect( - mergeGridTemplateValues({ - calculated: [gridCSSNumber(cssNumber(1), null), gridCSSNumber(cssNumber(2), null)], - fromProps: [gridCSSNumber(cssNumber(2), null)], - autoValues: [], - }), - ).toEqual([gridCSSNumber(cssNumber(2), null), gridCSSNumber(cssNumber(2), null)]) - }) - - it('multiple elements, competing', async () => { - expect( - mergeGridTemplateValues({ - calculated: [gridCSSNumber(cssNumber(1), null), gridCSSNumber(cssNumber(2), null)], - fromProps: [gridCSSNumber(cssNumber(2), null), gridCSSKeyword(cssKeyword('auto'), null)], - autoValues: [], - }), - ).toEqual([gridCSSNumber(cssNumber(2), null), gridCSSKeyword(cssKeyword('auto'), null)]) - }) - - it('one auto value, auto calculated', async () => { - expect( - mergeGridTemplateValues({ - calculated: [gridCSSKeyword(cssKeyword('auto'), null)], - fromProps: [], - autoValues: [gridCSSNumber(cssNumber(42), null)], - }), - ).toEqual([gridCSSNumber(cssNumber(42), null)]) - }) - - it('one auto value, multiple auto calculated', async () => { - expect( - mergeGridTemplateValues({ - calculated: [ - gridCSSKeyword(cssKeyword('auto'), null), - gridCSSKeyword(cssKeyword('auto'), null), - gridCSSKeyword(cssKeyword('auto'), null), - ], - fromProps: [], - autoValues: [gridCSSNumber(cssNumber(42), null)], - }), - ).toEqual([ - gridCSSNumber(cssNumber(42), null), - gridCSSNumber(cssNumber(42), null), - gridCSSNumber(cssNumber(42), null), - ]) - }) - - it('multiple auto values, multiple auto calculated', async () => { - expect( - mergeGridTemplateValues({ - calculated: [ - gridCSSKeyword(cssKeyword('auto'), null), - gridCSSKeyword(cssKeyword('auto'), null), - gridCSSKeyword(cssKeyword('auto'), null), - gridCSSKeyword(cssKeyword('auto'), null), - gridCSSKeyword(cssKeyword('auto'), null), - ], - fromProps: [], - autoValues: [ - gridCSSNumber(cssNumber(1), null), - gridCSSNumber(cssNumber(2), null), - gridCSSNumber(cssNumber(3), null), - ], - }), - ).toEqual([ - gridCSSNumber(cssNumber(1), null), - gridCSSNumber(cssNumber(2), null), - gridCSSNumber(cssNumber(3), null), - gridCSSNumber(cssNumber(1), null), - gridCSSNumber(cssNumber(2), null), - ]) - }) - - it('one auto value, multiple auto calculated, but values from props', async () => { - expect( - mergeGridTemplateValues({ - calculated: [ - gridCSSKeyword(cssKeyword('auto'), null), - gridCSSKeyword(cssKeyword('auto'), null), - gridCSSKeyword(cssKeyword('auto'), null), - gridCSSKeyword(cssKeyword('auto'), null), - gridCSSKeyword(cssKeyword('auto'), null), - ], - fromProps: [gridCSSNumber(cssNumber(42), null)], - autoValues: [gridCSSNumber(cssNumber(1), null)], - }), - ).toEqual([ - gridCSSNumber(cssNumber(42), null), - gridCSSNumber(cssNumber(1), null), - gridCSSNumber(cssNumber(1), null), - gridCSSNumber(cssNumber(1), null), - gridCSSNumber(cssNumber(1), null), - ]) - }) - - it('multiple auto values, multiple auto calculated, but values from props', async () => { - expect( - mergeGridTemplateValues({ - calculated: [ - gridCSSKeyword(cssKeyword('auto'), null), - gridCSSKeyword(cssKeyword('auto'), null), - gridCSSKeyword(cssKeyword('auto'), null), - gridCSSKeyword(cssKeyword('auto'), null), - gridCSSKeyword(cssKeyword('auto'), null), - ], - fromProps: [ - gridCSSNumber(cssNumber(42), null), - gridCSSKeyword(cssKeyword('auto'), null), - gridCSSNumber(cssNumber(23), null), - ], - autoValues: [gridCSSNumber(cssNumber(1), null), gridCSSNumber(cssNumber(2), null)], - }), - ).toEqual([ - gridCSSNumber(cssNumber(42), null), - gridCSSNumber(cssNumber(2), null), - gridCSSNumber(cssNumber(23), null), - gridCSSNumber(cssNumber(2), null), - gridCSSNumber(cssNumber(1), null), - ]) - }) -}) diff --git a/editor/src/components/inspector/flex-section.tsx b/editor/src/components/inspector/flex-section.tsx index f158e65eebe1..5e3a768f4237 100644 --- a/editor/src/components/inspector/flex-section.tsx +++ b/editor/src/components/inspector/flex-section.tsx @@ -30,6 +30,7 @@ import { SquareButton, Subdued, Tooltip, + UtopiaTheme, } from '../../uuiui' import type { CSSKeyword, @@ -47,10 +48,7 @@ import { gridCSSNumber, isCSSKeyword, isCSSNumber, - isEmptyInputValue, - isGridCSSKeyword, - isGridCSSNumber, - isValidGridDimensionKeyword, + printArrayGridDimensions, type GridDimension, } from './common/css-utils' import { applyCommandsAction, transientActions } from '../editor/actions/action-creators' @@ -63,16 +61,19 @@ import { } from '../canvas/commands/set-property-command' import * as PP from '../../core/shared/property-path' import type { - GridAutoOrTemplateBase, GridContainerProperties, - GridPosition, + GridPositionOrSpan, } from '../../core/shared/element-template' import { gridPositionValue, + isGridSpan, type ElementInstanceMetadata, type GridElementProperties, } from '../../core/shared/element-template' -import { setGridPropsCommands } from '../canvas/canvas-strategies/strategies/grid-helpers' +import { + isJustAutoGridDimension, + getCommandsForGridItemPlacement, +} from '../canvas/canvas-strategies/strategies/grid-helpers' import { type CanvasCommand } from '../canvas/commands/commands' import type { DropdownMenuItem } from '../../uuiui/radix-components' import { @@ -83,7 +84,6 @@ import { separatorRadixSelectOption, } from '../../uuiui/radix-components' import { useInspectorLayoutInfo, useInspectorStyleInfo } from './common/property-path-hooks' -import { NumberOrKeywordControl } from '../../uuiui/inputs/number-or-keyword-control' import { optionalMap } from '../../core/shared/optional-utils' import { cssNumberEqual } from '../canvas/controls/select-mode/controls-common' import type { EditorAction } from '../editor/action-types' @@ -95,8 +95,13 @@ import { useSetHoveredControlsHandlers, } from '../canvas/controls/select-mode/select-mode-hooks' import type { Axis } from '../canvas/gap-utils' - -const axisDropdownMenuButton = 'axisDropdownMenuButton' +import { GridExpressionInput } from '../../uuiui/inputs/grid-expression-input' +import { + gridDimensionDropdownKeywords, + parseGridDimensionInput, + useGridExpressionInputFocused, +} from './grid-helpers' +import { GridAutoColsOrRowsControl } from './grid-auto-cols-or-rows-control' function getLayoutSystem( layoutSystem: DetectedLayoutSystem | null | undefined, @@ -151,38 +156,6 @@ export const FlexSection = React.memo(() => { 'FlexSection grid', ) - const columns = React.useMemo(() => { - const autoCols: GridDimension[] = - grid?.specialSizeMeasurements.containerGridProperties.gridAutoColumns?.type === 'DIMENSIONS' - ? grid.specialSizeMeasurements.containerGridProperties.gridAutoColumns.dimensions - : [] - return mergeGridTemplateValues({ - autoValues: autoCols, - ...getGridTemplateAxisValues({ - calculated: - grid?.specialSizeMeasurements.containerGridProperties.gridTemplateColumns ?? null, - fromProps: - grid?.specialSizeMeasurements.containerGridPropertiesFromProps.gridTemplateColumns ?? - null, - }), - }) - }, [grid]) - - const rows = React.useMemo(() => { - const autoRows: GridDimension[] = - grid?.specialSizeMeasurements.containerGridProperties.gridAutoRows?.type === 'DIMENSIONS' - ? grid.specialSizeMeasurements.containerGridProperties.gridAutoRows.dimensions - : [] - return mergeGridTemplateValues({ - autoValues: autoRows, - ...getGridTemplateAxisValues({ - calculated: grid?.specialSizeMeasurements.containerGridProperties.gridTemplateRows ?? null, - fromProps: - grid?.specialSizeMeasurements.containerGridPropertiesFromProps.gridTemplateRows ?? null, - }), - }) - }, [grid]) - return (
@@ -196,13 +169,8 @@ export const FlexSection = React.memo(() => {
- - + +
) : null} , @@ -232,21 +200,13 @@ export const FlexSection = React.memo(() => { ) }) -const gridDimensionDropdownKeywords = [ - { label: 'Auto', value: cssKeyword('auto') }, - { label: 'Min-Content', value: cssKeyword('min-content') }, - { label: 'Max-Content', value: cssKeyword('max-content') }, -] - const TemplateDimensionControl = React.memo( ({ grid, - values, axis, title, }: { grid: ElementInstanceMetadata - values: GridDimension[] axis: 'column' | 'row' title: string }) => { @@ -254,53 +214,106 @@ const TemplateDimensionControl = React.memo( const metadataRef = useRefEditorState((store) => store.editor.jsxMetadata) - const onUpdate = React.useCallback( + const values = React.useMemo((): GridDimension[] => { + // TODO: handle gridAutoRows/Cols too + switch (axis) { + case 'row': { + const { gridTemplateRows } = grid.specialSizeMeasurements.containerGridPropertiesFromProps + + return gridTemplateRows?.type === 'DIMENSIONS' ? gridTemplateRows.dimensions : [] + } + case 'column': { + const { gridTemplateColumns } = + grid.specialSizeMeasurements.containerGridPropertiesFromProps + + return gridTemplateColumns?.type === 'DIMENSIONS' ? gridTemplateColumns.dimensions : [] + } + default: + assertNever(axis) + } + }, [grid, axis]) + + const template = React.useMemo(() => { + const fromProps = + axis === 'column' + ? grid.specialSizeMeasurements.containerGridPropertiesFromProps.gridTemplateColumns + : grid.specialSizeMeasurements.containerGridPropertiesFromProps.gridTemplateRows + if (fromProps?.type === 'DIMENSIONS' && fromProps.dimensions.length === 0) { + return { type: 'DIMENSIONS', dimensions: values } + } + return fromProps + }, [grid, axis, values]) + + const autoTemplate = React.useMemo(() => { + return axis === 'column' + ? grid.specialSizeMeasurements.containerGridPropertiesFromProps.gridAutoColumns + : grid.specialSizeMeasurements.containerGridPropertiesFromProps.gridAutoRows + }, [grid, axis]) + + const onUpdateDimension = React.useCallback( + (index: number) => (newValue: GridDimension) => { + if (template?.type !== 'DIMENSIONS') { + return + } + const left = template.dimensions.slice(0, index) + const right = template.dimensions.slice(index + 1) + + const newDimensions = [...left, newValue, ...right] + + dispatch([ + applyCommandsAction([ + setProperty( + 'always', + grid.elementPath, + PP.create('style', axis === 'column' ? 'gridTemplateColumns' : 'gridTemplateRows'), + printArrayGridDimensions(newDimensions), + ), + ]), + ]) + }, + [template, dispatch, axis, grid], + ) + + const onUpdateNumberOrKeyword = React.useCallback( (index: number) => (value: UnknownOrEmptyInput>) => { - const newValues = [...values] - const gridValueAtIndex = values[index] - if (isCSSNumber(value)) { - const maybeUnit = isGridCSSNumber(gridValueAtIndex) ? gridValueAtIndex.value.unit : null - newValues[index] = gridCSSNumber( - cssNumber(value.value, value.unit ?? maybeUnit), - gridValueAtIndex.areaName, - ) - } else if (isCSSKeyword(value)) { - newValues[index] = gridCSSKeyword(value, gridValueAtIndex.areaName) - } else if (isEmptyInputValue(value)) { - newValues[index] = gridCSSKeyword(cssKeyword('auto'), gridValueAtIndex.areaName) + function getNewValue() { + const gridValueAtIndex = values[index] + return parseGridDimensionInput(value, gridValueAtIndex ?? null) + } + const newValue = getNewValue() + if (newValue == null) { + return } - dispatch([ - applyCommandsAction([ - setProperty( - 'always', - grid.elementPath, - PP.create('style', axis === 'column' ? 'gridTemplateColumns' : 'gridTemplateRows'), - gridNumbersToTemplateString(newValues), - ), - ]), - ]) + onUpdateDimension(index)(newValue) }, - [grid, values, dispatch, axis], + [values, onUpdateDimension], ) const onRemove = React.useCallback( (index: number) => () => { - const newValues = values.filter((_, idx) => idx !== index) + if (template?.type !== 'DIMENSIONS') { + return + } + + const left = template.dimensions.slice(0, index) + const right = template.dimensions.slice(index + 1) + + const newValues = [...left, ...right] let commands: CanvasCommand[] = [ setProperty( 'always', grid.elementPath, PP.create('style', axis === 'column' ? 'gridTemplateColumns' : 'gridTemplateRows'), - gridNumbersToTemplateString(newValues), + printArrayGridDimensions(newValues), ), ] // adjust the position of the elements if they need to be moved const adjustedGridTemplate = removeTemplateValueAtIndex( - grid.specialSizeMeasurements.containerGridProperties, + grid.specialSizeMeasurements.containerGridPropertiesFromProps, axis, index, ) @@ -310,19 +323,20 @@ const TemplateDimensionControl = React.memo( const children = MetadataUtils.getChildrenUnordered(metadataRef.current, grid.elementPath) for (const child of children) { let updated: Partial = { - ...child.specialSizeMeasurements.elementGridProperties, + ...child.specialSizeMeasurements.elementGridPropertiesFromProps, } - function needsAdjusting(pos: GridPosition | null, bound: number) { + function needsAdjusting(pos: GridPositionOrSpan | null, bound: number) { return pos != null && !isCSSKeyword(pos) && + !isGridSpan(pos) && // TODO support grid spans pos.numericalPosition != null && pos.numericalPosition >= bound ? pos.numericalPosition : null } - const position = child.specialSizeMeasurements.elementGridProperties + const position = child.specialSizeMeasurements.elementGridPropertiesFromProps if (axis === 'column') { const adjustColumnStart = needsAdjusting(position.gridColumnStart, gridIndex) const adjustColumnEnd = needsAdjusting(position.gridColumnEnd, gridIndex + 1) @@ -343,87 +357,96 @@ const TemplateDimensionControl = React.memo( } } - commands.push(...setGridPropsCommands(child.elementPath, adjustedGridTemplate, updated)) + commands.push( + ...getCommandsForGridItemPlacement(child.elementPath, adjustedGridTemplate, updated), + ) } dispatch([applyCommandsAction(commands)]) }, - [grid, values, dispatch, axis, metadataRef], + [grid, dispatch, axis, metadataRef, template], ) - const onAdd = React.useCallback(() => { - const newValues = values.concat(gridCSSNumber(cssNumber(1, 'fr'), null)) + const onAppend = React.useCallback(() => { + if (template?.type !== 'DIMENSIONS') { + return + } + + const newValues = [...template.dimensions, gridCSSNumber(cssNumber(1, 'fr'), null)] + dispatch([ applyCommandsAction([ setProperty( 'always', grid.elementPath, PP.create('style', axis === 'column' ? 'gridTemplateColumns' : 'gridTemplateRows'), - gridNumbersToTemplateString(newValues), + printArrayGridDimensions(newValues), ), ]), ]) - }, [dispatch, grid, axis, values]) + }, [dispatch, grid, axis, template]) const onRename = React.useCallback( (index: number) => () => { - const container = grid.specialSizeMeasurements.containerGridProperties + if (template?.type !== 'DIMENSIONS') { + return + } + const container = grid.specialSizeMeasurements.containerGridPropertiesFromProps const dimensions = axis === 'column' ? container.gridTemplateColumns : container.gridTemplateRows if (dimensions?.type !== 'DIMENSIONS') { return } - const currentAreaName = dimensions.dimensions[index]?.areaName ?? undefined + const currentLineName = dimensions.dimensions[index]?.lineName ?? undefined - const rawNewAreaName = window.prompt('Area name:', currentAreaName)?.trim() - if (rawNewAreaName == null) { + const rawNewLineName = window.prompt('Line name:', currentLineName)?.trim() + if (rawNewLineName == null) { return } - const newAreaName: string | null = - rawNewAreaName.length === 0 ? null : sanitizeAreaName(rawNewAreaName) + const newLineName: string | null = + rawNewLineName.length === 0 ? null : sanitizeLineName(rawNewLineName) - const newValues = values.map((value, idx) => { - if (idx !== index) { - return value - } - return { - ...value, - areaName: newAreaName, - } - }) + const left = template.dimensions.slice(0, index) + const right = template.dimensions.slice(index + 1) + + const newValues = [ + ...left, + { ...values[index], lineName: newLineName } as GridDimension, + ...right, + ] let commands: CanvasCommand[] = [ setProperty( 'always', grid.elementPath, PP.create('style', axis === 'column' ? 'gridTemplateColumns' : 'gridTemplateRows'), - gridNumbersToTemplateString(newValues), + printArrayGridDimensions(newValues), ), ] - // replace the area name in the template and update the grid children so they - // reference the new area name, if they used to reference the previous one - const adjustedGridTemplate = renameAreaInTemplateAtIndex( + // replace the line name in the template and update the grid children so they + // reference the new line name, if they used to reference the previous one + const adjustedGridTemplate = renameLineInTemplateAtIndex( container, axis, index, - newAreaName, + newLineName, ) const children = MetadataUtils.getChildrenUnordered(metadataRef.current, grid.elementPath) for (const child of children) { commands.push( - ...setGridPropsCommands( + ...getCommandsForGridItemPlacement( child.elementPath, adjustedGridTemplate, - child.specialSizeMeasurements.elementGridProperties, + child.specialSizeMeasurements.elementGridPropertiesFromProps, ), ) } dispatch([applyCommandsAction(commands)]) }, - [grid, axis, values, dispatch, metadataRef], + [grid, axis, values, dispatch, metadataRef, template], ) const dropdownMenuItems = React.useCallback( @@ -459,6 +482,18 @@ const TemplateDimensionControl = React.memo( [], ) + const dimensionsWithGeneratedIndexes = useGeneratedIndexesFromGridDimensions(values) + + const showAutoColsOrRows = React.useMemo(() => { + return ( + template?.type !== 'DIMENSIONS' || + template.dimensions.length === 0 || + (autoTemplate?.type === 'DIMENSIONS' && + autoTemplate.dimensions.length > 0 && + !isJustAutoGridDimension(autoTemplate.dimensions)) + ) + }, [template, autoTemplate]) + return (
{title}
- +
- {values.map((value, index) => ( + {dimensionsWithGeneratedIndexes.map((value, index) => ( ))} + {when( + showAutoColsOrRows, + , + )}
) }, @@ -493,66 +539,131 @@ TemplateDimensionControl.displayName = 'TemplateDimensionControl' function AxisDimensionControl({ value, index, + generatedIndexFrom: indexFrom, + generatedIndexTo: indexTo, items, axis, - onUpdate, + onUpdateNumberOrKeyword, + onUpdateDimension, opener, }: { value: GridDimension index: number + generatedIndexFrom: number + generatedIndexTo: number items: DropdownMenuItem[] axis: 'column' | 'row' - onUpdate: ( + onUpdateNumberOrKeyword: ( index: number, ) => (value: UnknownOrEmptyInput>) => void + onUpdateDimension: (index: number) => (value: GridDimension) => void opener: (isOpen: boolean) => React.ReactElement }) { const testId = `grid-dimension-${axis}-${index}` - const [isOpen, setIsOpen] = React.useState(false) - const onOpenChange = React.useCallback((isDropdownOpen: boolean) => { - setIsOpen(isDropdownOpen) + const [isDotsMenuOpen, setDotsMenuOpen] = React.useState(false) + const [isTitleMenuOpen, setTitleMenuOpen] = React.useState(false) + + const onOpenChangeDotsMenu = React.useCallback((isDropdownOpen: boolean) => { + setDotsMenuOpen(isDropdownOpen) + }, []) + + const onOpenChangeTitleMenu = React.useCallback(() => { + setTitleMenuOpen(false) + }, []) + + const isDynamic = React.useMemo(() => { + return indexFrom !== indexTo + }, [indexFrom, indexTo]) + + const dynamicIndexTitle = React.useMemo(() => { + return `${indexFrom} → ${indexTo}` + }, [indexFrom, indexTo]) + + const title = React.useMemo(() => { + if (isDynamic) { + return value.lineName ?? dynamicIndexTitle + } + return value.lineName ?? indexFrom + }, [value, indexFrom, isDynamic, dynamicIndexTitle]) + + const gridExpressionInputFocused = useGridExpressionInputFocused() + + const [isHovered, setIsHovered] = React.useState(false) + const onMouseEnter = React.useCallback(() => { + setIsHovered(true) + }, []) + const onMouseLeave = React.useCallback(() => { + setIsHovered(false) }, []) + + const onContextMenuTitle = React.useCallback((e: React.MouseEvent) => { + e.preventDefault() + e.stopPropagation() + setTitleMenuOpen(true) + }, []) + + const invisibleOpener = React.useCallback(() => null, []) + return (
.${axisDropdownMenuButton}`]: { - visibility: isOpen ? 'visible' : 'hidden', - }, - ':hover': { - [`& > .${axisDropdownMenuButton}`]: { - visibility: 'visible', - }, - }, - }} + onMouseEnter={onMouseEnter} + onMouseLeave={onMouseLeave} > -
+
- {value.areaName ?? index + 1} + {title} + - + {when( + (isHovered && !gridExpressionInputFocused.focused) || isDotsMenuOpen, + + + , + )}
- - -
) } @@ -589,14 +700,19 @@ function removeTemplateValueAtIndex( } } -function renameAreaInTemplateAtIndex( +function renameLineInTemplateAtIndex( original: GridContainerProperties, axis: 'column' | 'row', index: number, - newAreaName: string | null, -) { - function renameDimension(dimension: GridDimension, idx: number) { - return idx === index ? { ...dimension, areaName: newAreaName } : dimension + newLineName: string | null, +): GridContainerProperties { + function renameDimension(dimension: GridDimension, idx: number): GridDimension { + return idx === index + ? ({ + ...dimension, + lineName: dimension.type === 'REPEAT' ? null : newLineName, + } as GridDimension) + : dimension } const gridTemplateRows = @@ -622,40 +738,10 @@ function renameAreaInTemplateAtIndex( } } -function gridNumbersToTemplateString(values: GridDimension[]) { - return values - .map((v) => { - function getValue(): string { - if (isGridCSSKeyword(v)) { - return v.value.value - } - return `${v.value.value}${v.value.unit != null ? `${v.value.unit}` : 'px'}` - } - const areaName = v.areaName != null ? `[${v.areaName}] ` : '' - const value = getValue() - return `${areaName}${value}` - }) - .join(' ') -} - -function getGridTemplateAxisValues(template: { - calculated: GridAutoOrTemplateBase | null - fromProps: GridAutoOrTemplateBase | null -}): { calculated: GridDimension[]; fromProps: GridDimension[] } { - const { calculated, fromProps } = template - if (fromProps?.type !== 'DIMENSIONS' && calculated?.type !== 'DIMENSIONS') { - return { calculated: [], fromProps: [] } - } - - const calculatedDimensions = calculated?.type === 'DIMENSIONS' ? calculated.dimensions : [] - const fromPropsDimensions = fromProps?.type === 'DIMENSIONS' ? fromProps.dimensions : [] - return { calculated: calculatedDimensions, fromProps: fromPropsDimensions } -} - const reAlphanumericDashUnderscore = /[^0-9a-z\-_]+/gi -function sanitizeAreaName(areaName: string): string { - return areaName.replace(reAlphanumericDashUnderscore, '-') +function sanitizeLineName(lineName: string): string { + return lineName.replace(reAlphanumericDashUnderscore, '-') } function serializeValue(v: CSSNumber) { @@ -819,7 +905,7 @@ const GapRowColumnControl = React.memo(() => { } const transientWrapper = (actions: EditorAction[]) => - transient ? [transientActions(actions)] : actions + transient ? [transientActions(actions, [grid.elementPath])] : actions dispatch( transientWrapper([ @@ -872,6 +958,8 @@ const GapRowColumnControl = React.memo(() => { { { (isOpen && currentValue !== 'auto' ? 'unset' : 'auto'), + value: 'auto', placeholder: true, }) @@ -997,7 +1089,7 @@ const AutoFlowControl = React.memo(() => { selectededViewsRef.current.map((path) => applyCommandsAction([ updateBulkProperties('always', path, [ - value === 'unset' + value === 'auto' ? propertyToDelete(PP.create('style', 'gridAutoFlow')) : propertyToSet(PP.create('style', 'gridAutoFlow'), value), ]), @@ -1022,35 +1114,38 @@ const AutoFlowControl = React.memo(() => { }) AutoFlowControl.displayName = 'AutoFlowControl' -export function mergeGridTemplateValues({ - calculated, - fromProps, - autoValues, -}: { - calculated: GridDimension[] - fromProps: GridDimension[] - autoValues: GridDimension[] -}): GridDimension[] { - function getExplicitValue(dimension: GridDimension, index: number): GridDimension { - if (fromProps.length === 0) { - return gridCSSKeyword(cssKeyword('auto'), dimension.areaName) - } else if (fromProps[index] == null) { - return dimension - } else { - return fromProps[index] - } - } - - return calculated.map((c, index) => { - const explicitValue = getExplicitValue(c, index) - - const autoValueIndex = index % autoValues.length // wrap around - const autoValue = autoValues.at(autoValueIndex) - - if (isGridCSSKeyword(explicitValue) && explicitValue.value.value === 'auto') { - return autoValue ?? explicitValue - } +interface GridDimensionWithGeneratedIndexes { + generatedIndexFrom: number + generatedIndexTo: number + dimension: GridDimension +} - return explicitValue - }) +function useGeneratedIndexesFromGridDimensions( + dimensions: Array, +): Array { + return React.useMemo(() => { + let nextIndexFrom = 1 + let result: Array = [] + + dimensions.forEach((dim) => { + if (dim.type !== 'REPEAT') { + result.push({ + generatedIndexFrom: nextIndexFrom, + generatedIndexTo: nextIndexFrom, + dimension: dim, + }) + nextIndexFrom += 1 + } else { + // TODO: handle auto-fill and auto-fit in value.times + const shift = !isCSSKeyword(dim.times) ? dim.times * dim.value.length : dim.value.length + result.push({ + generatedIndexFrom: nextIndexFrom, + generatedIndexTo: nextIndexFrom + shift - 1, + dimension: dim, + }) + nextIndexFrom += shift + } + }) + return result + }, [dimensions]) } diff --git a/editor/src/components/inspector/grid-auto-cols-or-rows-control.tsx b/editor/src/components/inspector/grid-auto-cols-or-rows-control.tsx new file mode 100644 index 000000000000..6a60037d8866 --- /dev/null +++ b/editor/src/components/inspector/grid-auto-cols-or-rows-control.tsx @@ -0,0 +1,115 @@ +import React from 'react' +import { useDispatch } from '../editor/store/dispatch-context' +import { UtopiaTheme } from '../../uuiui' +import type { + CSSKeyword, + CSSNumber, + UnknownOrEmptyInput, + ValidGridDimensionKeyword, +} from './common/css-utils' +import { + cssKeyword, + gridCSSKeyword, + printArrayGridDimensions, + type GridDimension, +} from './common/css-utils' +import { applyCommandsAction } from '../editor/actions/action-creators' +import { setProperty } from '../canvas/commands/set-property-command' +import * as PP from '../../core/shared/property-path' +import { type ElementInstanceMetadata } from '../../core/shared/element-template' +import { GridExpressionInput } from '../../uuiui/inputs/grid-expression-input' +import { + gridDimensionDropdownKeywords, + parseGridDimensionInput, + useGridExpressionInputFocused, +} from './grid-helpers' + +export const GridAutoColsOrRowsControl = React.memo( + (props: { axis: 'column' | 'row'; grid: ElementInstanceMetadata; label: string }) => { + const autoColsOrRowsValueFocused = useGridExpressionInputFocused() + + return ( +
+ +
+ ) + }, +) +GridAutoColsOrRowsControl.displayName = 'GridAutoColsOrRowsControl' + +export const GridAutoColsOrRowsControlInner = React.memo( + (props: { axis: 'column' | 'row'; grid: ElementInstanceMetadata; label: string }) => { + const value = React.useMemo(() => { + const template = props.grid.specialSizeMeasurements.containerGridPropertiesFromProps + const data = props.axis === 'column' ? template.gridAutoColumns : template.gridAutoRows + if (data?.type !== 'DIMENSIONS') { + return null + } + return data.dimensions[0] + }, [props.grid, props.axis]) + + const dispatch = useDispatch() + + const onUpdateDimension = React.useCallback( + (newDimension: GridDimension) => { + dispatch([ + applyCommandsAction([ + setProperty( + 'always', + props.grid.elementPath, + PP.create('style', props.axis === 'column' ? 'gridAutoColumns' : 'gridAutoRows'), + printArrayGridDimensions([newDimension]), + ), + ]), + ]) + }, + [props.grid, props.axis, dispatch], + ) + + const onUpdateNumberOrKeyword = React.useCallback( + (newValue: UnknownOrEmptyInput>) => { + const parsed = parseGridDimensionInput(newValue, null) + if (parsed == null) { + return + } + onUpdateDimension(parsed) + }, + [onUpdateDimension], + ) + + const autoColsOrRowsValueFocused = useGridExpressionInputFocused() + + return ( + +
{props.label}
+ +
+ ) + }, +) +GridAutoColsOrRowsControlInner.displayName = 'GridAutoColsOrRowsControlInner' + +export function GridAutoColsOrRowsControlTestId(axis: 'column' | 'row'): string { + return `grid-template-auto-${axis}` +} diff --git a/editor/src/components/inspector/grid-helpers.ts b/editor/src/components/inspector/grid-helpers.ts new file mode 100644 index 000000000000..da8f9b4a67c0 --- /dev/null +++ b/editor/src/components/inspector/grid-helpers.ts @@ -0,0 +1,100 @@ +import React from 'react' +import type { + CSSKeyword, + CSSNumber, + UnknownOrEmptyInput, + ValidGridDimensionKeyword, +} from './common/css-utils' +import { + cssKeyword, + cssNumber, + gridCSSKeyword, + gridCSSNumber, + isCSSKeyword, + isCSSNumber, + isEmptyInputValue, + isGridCSSNumber, + type GridDimension, +} from './common/css-utils' +import type { DragInteractionData } from '../canvas/canvas-strategies/interaction-state' +import { offsetPoint } from '../../core/shared/math-utils' +import type { GridCellCoordinates } from '../canvas/canvas-strategies/strategies/grid-cell-bounds' +import { + getClosestGridCellToPoint, + gridCellCoordinates, +} from '../canvas/canvas-strategies/strategies/grid-cell-bounds' +import type { GridCellGlobalFrames } from '../canvas/canvas-strategies/strategies/grid-helpers' + +export const useGridExpressionInputFocused = () => { + const [focused, setFocused] = React.useState(false) + const onFocus = React.useCallback(() => setFocused(true), []) + const onBlur = React.useCallback(() => setFocused(false), []) + return { focused, onFocus, onBlur } +} + +export function parseGridDimensionInput( + value: UnknownOrEmptyInput>, + currentValue: GridDimension | null, +) { + if (isCSSNumber(value)) { + const maybeUnit = + currentValue != null && isGridCSSNumber(currentValue) ? currentValue.value.unit : null + return gridCSSNumber( + cssNumber(value.value, value.unit ?? maybeUnit), + currentValue?.lineName ?? null, + ) + } else if (isCSSKeyword(value)) { + return gridCSSKeyword(value, currentValue?.lineName ?? null) + } else if (isEmptyInputValue(value)) { + return gridCSSKeyword(cssKeyword('auto'), currentValue?.lineName ?? null) + } else { + return null + } +} + +export const gridDimensionDropdownKeywords = [ + { label: 'Auto', value: cssKeyword('auto') }, + { label: 'Min-Content', value: cssKeyword('min-content') }, + { label: 'Max-Content', value: cssKeyword('max-content') }, +] + +export const GridPositioningProps: Array = [ + 'gridColumn', + 'gridRow', + 'gridColumnStart', + 'gridColumnEnd', + 'gridRowStart', + 'gridRowEnd', +] + +export function getTargetGridCellData( + interactionData: DragInteractionData, + gridCellGlobalFrames: GridCellGlobalFrames, + mouseCellPosInOriginalElement: GridCellCoordinates, +): { + targetCellCoords: GridCellCoordinates + targetRootCell: GridCellCoordinates +} | null { + if (interactionData.drag == null) { + return null + } + const mousePos = offsetPoint(interactionData.dragStart, interactionData.drag) + const targetCellData = getClosestGridCellToPoint(gridCellGlobalFrames, mousePos, 'exclusive') + if (targetCellData == null) { + return null + } + const targetCellCoords = targetCellData?.gridCellCoordinates + if (targetCellCoords == null) { + return null + } + + const row = Math.max(targetCellCoords.row - mouseCellPosInOriginalElement.row, 1) + const column = Math.max(targetCellCoords.column - mouseCellPosInOriginalElement.column, 1) + + const targetRootCell = gridCellCoordinates(row, column) + + return { + targetCellCoords, + targetRootCell, + } +} diff --git a/editor/src/components/inspector/inspector-common.ts b/editor/src/components/inspector/inspector-common.ts index 0f48cbb3ff53..c232156d0a66 100644 --- a/editor/src/components/inspector/inspector-common.ts +++ b/editor/src/components/inspector/inspector-common.ts @@ -13,7 +13,6 @@ import type { ElementInstanceMetadata, ElementInstanceMetadataMap, JSXAttributes, - JSXElement, } from '../../core/shared/element-template' import { isJSXElement, @@ -47,7 +46,7 @@ import { setPropHugAbsoluteStrategies, } from './inspector-strategies/inspector-strategies' import { commandsForFirstApplicableStrategy } from './inspector-strategies/inspector-strategy' -import type { Size } from '../../core/shared/math-utils' +import type { CanvasVector, Size } from '../../core/shared/math-utils' import { isFiniteRectangle, isInfinityRectangle, @@ -100,7 +99,77 @@ export function getFlexJustifyContent(value: string | null): FlexJustifyContent } } +export type AlignContent = + | 'normal' + | 'start' + | 'center' + | 'end' + | 'flex-start' + | 'flex-end' + | 'baseline' + | 'first baseline' + | 'last baseline' + | 'space-between' + | 'space-around' + | 'space-evenly' + | 'stretch' + | 'safe center' + | 'unsafe center' + | 'inherit' + | 'initial' + | 'revert' + | 'revert-layer' + | 'unset' + +export function getAlignContent(value: string | null): AlignContent | null { + switch (value) { + case 'normal': + return 'normal' + case 'start': + return 'start' + case 'center': + return 'center' + case 'end': + return 'end' + case 'flex-start': + return 'flex-start' + case 'flex-end': + return 'flex-end' + case 'baseline': + return 'baseline' + case 'first baseline': + return 'first baseline' + case 'last baseline': + return 'last baseline' + case 'space-between': + return 'space-between' + case 'space-around': + return 'space-around' + case 'space-evenly': + return 'space-evenly' + case 'stretch': + return 'stretch' + case 'safe center': + return 'safe center' + case 'unsafe center': + return 'unsafe center' + case 'inherit': + return 'inherit' + case 'initial': + return 'initial' + case 'revert': + return 'revert' + case 'revert-layer': + return 'revert-layer' + case 'unset': + return 'unset' + default: + return null + } +} + export type FlexAlignment = StartCenterEnd | 'auto' | 'stretch' +export type SelfAlignment = FlexAlignment | 'end' | 'start' export function getFlexAlignment(value: string | null): FlexAlignment | null { switch (value) { @@ -119,6 +188,17 @@ export function getFlexAlignment(value: string | null): FlexAlignment | null { } } +export function getSelfAlignment(value: string | null): SelfAlignment | null { + switch (value) { + case 'end': + return 'end' + case 'start': + return 'start' + default: + return getFlexAlignment(value) + } +} + function stringToFlexDirection(str: string | null): FlexDirection | null { switch (str) { case 'row': @@ -612,6 +692,19 @@ export const nukeAllAbsolutePositioningPropsCommands = ( ] } +export const nukeGridCellPositioningPropsCommands = (path: ElementPath): Array => { + return [ + deleteProperties('always', path, [ + PP.create('style', 'gridColumn'), + PP.create('style', 'gridColumnStart'), + PP.create('style', 'gridColumnEnd'), + PP.create('style', 'gridRow'), + PP.create('style', 'gridRowStart'), + PP.create('style', 'gridRowEnd'), + ]), + ] +} + export type FixedHugFill = | { type: 'fixed'; value: CSSNumber } | { type: 'fill'; value: CSSNumber } @@ -622,6 +715,7 @@ export type FixedHugFill = | { type: 'computed'; value: CSSNumber } | { type: 'detected'; value: CSSNumber } | { type: 'scaled'; value: CSSNumber } + | { type: 'stretch' } export type FixedHugFillMode = FixedHugFill['type'] @@ -631,16 +725,54 @@ export function isHuggingFixedHugFill(fixedHugFillMode: FixedHugFillMode | null ) } +export interface DetectedFillHugFixedState { + fixedHugFill: FixedHugFill | null + controlStatus: ControlStatus +} + export function detectFillHugFixedState( axis: Axis, metadata: ElementInstanceMetadataMap, elementPath: ElementPath, -): { fixedHugFill: FixedHugFill | null; controlStatus: ControlStatus } { +): DetectedFillHugFixedState { const element = MetadataUtils.findElementByElementPath(metadata, elementPath) if (element == null || isLeft(element.element) || !isJSXElement(element.element.value)) { return { fixedHugFill: null, controlStatus: 'off' } } + const width = foldEither( + () => null, + (value) => defaultEither(null, parseCSSNumber(value, 'Unitless')), + getSimpleAttributeAtPath(right(element.element.value.props), PP.create('style', 'width')), + ) + const height = foldEither( + () => null, + (value) => defaultEither(null, parseCSSNumber(value, 'Unitless')), + getSimpleAttributeAtPath(right(element.element.value.props), PP.create('style', 'height')), + ) + + if (MetadataUtils.isGridItem(metadata, elementPath)) { + const isStretchingExplicitly = + (element.specialSizeMeasurements.alignSelf === 'stretch' && + axis === 'horizontal' && + width == null) || + (element.specialSizeMeasurements.justifySelf === 'stretch' && + axis === 'vertical' && + height == null) + + const isStretchingImplicitly = + width == null && + height == null && + (element.specialSizeMeasurements.alignSelf == null || + element.specialSizeMeasurements.alignSelf === 'auto') && + (element.specialSizeMeasurements.justifySelf == null || + element.specialSizeMeasurements.justifySelf === 'auto') + + if (isStretchingExplicitly || isStretchingImplicitly) { + return { fixedHugFill: { type: 'stretch' }, controlStatus: 'detected' } + } + } + const flexGrowLonghand = foldEither( () => null, (value) => defaultEither(null, parseCSSNumber(value, 'Unitless')), @@ -784,6 +916,37 @@ export function isFixedHugFillModeApplied( ) } +export function isFillOrStretchModeApplied( + metadata: ElementInstanceMetadataMap, + element: ElementPath, +): boolean { + return ( + isFixedHugFillModeApplied(metadata, element, 'fill') || + isFixedHugFillModeApplied(metadata, element, 'stretch') + ) +} + +export function isFillOrStretchModeAppliedOnAnySide( + metadata: ElementInstanceMetadataMap, + element: ElementPath, +): boolean { + return ( + isFixedHugFillModeAppliedOnAnySide(metadata, element, 'fill') || + isFixedHugFillModeAppliedOnAnySide(metadata, element, 'stretch') + ) +} + +export function isFillOrStretchModeAppliedOnSpecificSide( + metadata: ElementInstanceMetadataMap, + element: ElementPath, + side: 'horizontal' | 'vertical', +): boolean { + return ( + detectFillHugFixedState(side, metadata, element).fixedHugFill?.type === 'fill' || + detectFillHugFixedState(side, metadata, element).fixedHugFill?.type === 'stretch' + ) +} + export function isFixedHugFillModeAppliedOnAnySide( metadata: ElementInstanceMetadataMap, element: ElementPath, @@ -1046,7 +1209,11 @@ export function getFixedFillHugOptionsForElement( (!isGroup && basicHugContentsApplicableForContainer(metadata, pathTrees, selectedView)) ? 'hug' : null, - fillContainerApplicable(metadata, selectedView) ? 'fill' : null, + fillContainerApplicable(metadata, selectedView) + ? MetadataUtils.isGridItem(metadata, selectedView) + ? 'stretch' + : 'fill' + : null, ]), ) } @@ -1083,7 +1250,7 @@ export function setParentToFixedIfHugCommands( return [] } - const globalFrame = MetadataUtils.getLocalFrame(parentPath, metadata) + const globalFrame = MetadataUtils.getLocalFrame(parentPath, metadata, null) if (globalFrame == null || isInfinityRectangle(globalFrame)) { return [] } @@ -1173,6 +1340,20 @@ export function removeExtraPinsWhenSettingSize( ) } +export function removeAlignJustifySelf( + axis: Axis, + elementMetadata: ElementInstanceMetadata | null, +): Array { + if (elementMetadata == null) { + return [] + } + return [ + deleteProperties('always', elementMetadata.elementPath, [ + styleP(axis === 'horizontal' ? 'alignSelf' : 'justifySelf'), + ]), + ] +} + export function isFixedHugFillEqual( a: { fixedHugFill: FixedHugFill | null; controlStatus: ControlStatus }, b: { fixedHugFill: FixedHugFill | null; controlStatus: ControlStatus }, @@ -1205,9 +1386,10 @@ export function isFixedHugFillEqual( a.fixedHugFill.value.value === b.fixedHugFill.value.value && a.fixedHugFill.value.unit === b.fixedHugFill.value.unit ) + case 'stretch': + return a.fixedHugFill.type === b.fixedHugFill.type default: - const _exhaustiveCheck: never = a.fixedHugFill - throw new Error(`Unknown type in FixedHugFill ${JSON.stringify(a.fixedHugFill)}`) + assertNever(a.fixedHugFill) } } @@ -1216,6 +1398,7 @@ export function toggleAbsolutePositioningCommands( allElementProps: AllElementProps, elementPathTree: ElementPathTrees, selectedViews: Array, + canvasContext: { scale: number; offset: CanvasVector }, ): Array { const commands = selectedViews.flatMap((elementPath) => { const maybeGroupConversionCommands = groupConversionCommands( @@ -1280,7 +1463,7 @@ export function getConvertIndividualElementToAbsoluteCommandsFromMetadata( jsxMetadata: ElementInstanceMetadataMap, elementPathTree: ElementPathTrees, ): Array { - const localFrame = MetadataUtils.getLocalFrame(target, jsxMetadata) + const localFrame = MetadataUtils.getLocalFrame(target, jsxMetadata, null) if (localFrame == null || isInfinityRectangle(localFrame)) { return [] } @@ -1311,6 +1494,7 @@ export function getConvertIndividualElementToAbsoluteCommands( // First round the frame so that we don't end up with half pixel values const roundedFrame = roundRectangleToNearestWhole(frame) return [ + ...nukeGridCellPositioningPropsCommands(target), ...sizeToDimensionsFromFrame(jsxMetadata, elementPathTree, target, roundedFrame), ...addPositionAbsoluteTopLeft(target, roundedFrame, parentFlexDirection), ] diff --git a/editor/src/components/inspector/inspector-strategies/fill-container-basic-strategy.ts b/editor/src/components/inspector/inspector-strategies/fill-container-basic-strategy.ts index 230669cf7a09..3970554122c3 100644 --- a/editor/src/components/inspector/inspector-strategies/fill-container-basic-strategy.ts +++ b/editor/src/components/inspector/inspector-strategies/fill-container-basic-strategy.ts @@ -1,7 +1,12 @@ import * as PP from '../../../core/shared/property-path' -import { MetadataUtils } from '../../../core/model/element-metadata-utils' +import { getSimpleAttributeAtPath, MetadataUtils } from '../../../core/model/element-metadata-utils' import { clamp } from '../../../core/shared/math-utils' -import { setProperty } from '../../canvas/commands/set-property-command' +import { + propertyToDelete, + propertyToSet, + setProperty, + updateBulkProperties, +} from '../../canvas/commands/set-property-command' import type { FlexDirection } from '../common/css-utils' import { cssNumber, printCSSNumber } from '../common/css-utils' import type { Axis } from '../inspector-common' @@ -14,6 +19,8 @@ import { nukeSizingPropsForAxisCommand, nullOrNonEmpty, setParentToFixedIfHugCommands, + removeAlignJustifySelf, + styleP, } from '../inspector-common' import type { InspectorStrategy } from './inspector-strategy' import { @@ -24,8 +31,13 @@ import { groupErrorToastCommand, maybeInvalidGroupState, } from '../../canvas/canvas-strategies/strategies/group-helpers' -import type { ElementInstanceMetadataMap } from '../../../core/shared/element-template' +import { + isJSXElement, + type ElementInstanceMetadataMap, +} from '../../../core/shared/element-template' import type { ElementPath } from '../../../core/shared/project-file-types' +import { foldEither, defaultEither, right, isLeft } from '../../../core/shared/either' +import { parseString } from '../../../utils/value-parser-utils' export const fillContainerStrategyFlow = ( metadata: ElementInstanceMetadataMap, @@ -102,6 +114,8 @@ export const fillContainerStrategyFlexParent = ( } const commands = elements.flatMap((path) => { + const elementMetadata = MetadataUtils.findElementByElementPath(metadata, path) + const flexDirection = overrides.forceFlexDirectionForParent ?? detectParentFlexDirection(metadata, path) ?? 'row' @@ -113,6 +127,7 @@ export const fillContainerStrategyFlexParent = ( value === 'default' ? cssNumber(100, '%') : cssNumber(clamp(0, 100, value), '%') return [ ...setParentToFixedIfHugCommands(axis, metadata, path), + ...removeAlignJustifySelf(axis, elementMetadata), setCssLengthProperty( 'always', path, @@ -128,6 +143,7 @@ export const fillContainerStrategyFlexParent = ( return [ ...nukeAllAbsolutePositioningPropsCommands(path), + ...removeAlignJustifySelf(axis, elementMetadata), ...setParentToFixedIfHugCommands(axis, metadata, path), nukeSizingPropsForAxisCommand(axis, path), setProperty( @@ -142,3 +158,59 @@ export const fillContainerStrategyFlexParent = ( return nullOrNonEmpty(commands) }, }) + +export const fillContainerStrategyGridParent = ( + metadata: ElementInstanceMetadataMap, + elementPaths: ElementPath[], + axis: Axis, +): InspectorStrategy => ({ + name: 'Set to Fill Container, in grid layout', + strategy: () => { + const elements = elementPaths.filter( + (path) => fillContainerApplicable(metadata, path) && MetadataUtils.isGridItem(metadata, path), + ) + + if (elements.length === 0) { + return null + } + + const commands = elements.flatMap((path) => { + const element = MetadataUtils.findElementByElementPath(metadata, path) + if (element == null || isLeft(element.element) || !isJSXElement(element.element.value)) { + return [] + } + + const alignSelf = foldEither( + () => null, + (value) => defaultEither(null, parseString(value)), + getSimpleAttributeAtPath(right(element.element.value.props), styleP('alignSelf')), + ) + + const justifySelf = foldEither( + () => null, + (value) => defaultEither(null, parseString(value)), + getSimpleAttributeAtPath(right(element.element.value.props), styleP('justifySelf')), + ) + + let updates = [ + propertyToSet(styleP(axis === 'horizontal' ? 'alignSelf' : 'justifySelf'), 'stretch'), + ] + + // delete the opposite side value (justify <> align) if not set to stretch + if (axis === 'vertical' && alignSelf !== 'stretch') { + updates.push(propertyToDelete(styleP('alignSelf'))) + } else if (axis === 'horizontal' && justifySelf !== 'stretch') { + updates.push(propertyToDelete(styleP('justifySelf'))) + } + + return [ + ...nukeAllAbsolutePositioningPropsCommands(path), + ...setParentToFixedIfHugCommands(axis, metadata, path), + nukeSizingPropsForAxisCommand(axis, path), + updateBulkProperties('always', path, updates), + ] + }) + + return nullOrNonEmpty(commands) + }, +}) diff --git a/editor/src/components/inspector/inspector-strategies/fixed-size-basic-strategy.ts b/editor/src/components/inspector/inspector-strategies/fixed-size-basic-strategy.ts index 1c62f3d07fcf..504650a00db9 100644 --- a/editor/src/components/inspector/inspector-strategies/fixed-size-basic-strategy.ts +++ b/editor/src/components/inspector/inspector-strategies/fixed-size-basic-strategy.ts @@ -7,7 +7,11 @@ import { } from '../../canvas/commands/set-css-length-command' import type { CSSNumber } from '../common/css-utils' import type { Axis } from '../inspector-common' -import { removeExtraPinsWhenSettingSize, widthHeightFromAxis } from '../inspector-common' +import { + removeAlignJustifySelf, + removeExtraPinsWhenSettingSize, + widthHeightFromAxis, +} from '../inspector-common' import type { InspectorStrategy } from './inspector-strategy' import { queueTrueUpElement } from '../../canvas/commands/queue-true-up-command' import { @@ -48,6 +52,7 @@ export const fixedSizeBasicStrategy = ( return [ ...removeExtraPinsWhenSettingSize(axis, elementMetadata), + ...removeAlignJustifySelf(axis, elementMetadata), setCssLengthProperty( whenToRun, path, diff --git a/editor/src/components/inspector/inspector-strategies/inspector-strategies.ts b/editor/src/components/inspector/inspector-strategies/inspector-strategies.ts index 3e87820adcfb..a066a3494321 100644 --- a/editor/src/components/inspector/inspector-strategies/inspector-strategies.ts +++ b/editor/src/components/inspector/inspector-strategies/inspector-strategies.ts @@ -26,6 +26,7 @@ import { import { fillContainerStrategyFlexParent, fillContainerStrategyFlow, + fillContainerStrategyGridParent, } from './fill-container-basic-strategy' import { setSpacingModePacked, setSpacingModeSpaceBetween } from './spacing-mode-strategies' import { convertLayoutToFlexCommands } from '../../common/shared-strategies/convert-to-flex-strategy' @@ -226,6 +227,7 @@ export const setPropFillStrategies = ( value: 'default' | number, otherAxisSetToFill: boolean, ): Array => [ + fillContainerStrategyGridParent(metadata, elementPaths, axis), fillContainerStrategyFlexParent(metadata, elementPaths, axis, value), fillContainerStrategyFlow(metadata, elementPaths, axis, value, otherAxisSetToFill), ] diff --git a/editor/src/components/inspector/inspector-strategies/inspector-strategy.ts b/editor/src/components/inspector/inspector-strategies/inspector-strategy.ts index cdf7d85255cd..38af9cf837a9 100644 --- a/editor/src/components/inspector/inspector-strategies/inspector-strategy.ts +++ b/editor/src/components/inspector/inspector-strategies/inspector-strategy.ts @@ -1,6 +1,7 @@ import type { CanvasCommand } from '../../canvas/commands/commands' import type { EditorDispatch } from '../../editor/action-types' -import { applyCommandsAction } from '../../editor/actions/action-creators' +import { applyCommandsAction, transientActions } from '../../editor/actions/action-creators' +import type { ElementsToRerender } from '../../editor/store/editor-state' interface CustomInspectorStrategyResultBase { commands: Array @@ -47,11 +48,26 @@ export function commandsForFirstApplicableStrategy( export function executeFirstApplicableStrategy( dispatch: EditorDispatch, + strategies: InspectorStrategy[], +): void { + return executeFirstApplicableStrategyForContinuousInteraction( + dispatch, + strategies, + 'rerender-all-elements', + ) +} +export function executeFirstApplicableStrategyForContinuousInteraction( + dispatch: EditorDispatch, strategies: InspectorStrategy[], + elementsToRerenderTransient: ElementsToRerender, ): void { const commands = commandsForFirstApplicableStrategy(strategies) if (commands != null) { - dispatch([applyCommandsAction(commands)]) + if (elementsToRerenderTransient !== 'rerender-all-elements') { + dispatch([transientActions([applyCommandsAction(commands)], elementsToRerenderTransient)]) + } else { + dispatch([applyCommandsAction(commands)]) + } } } diff --git a/editor/src/components/inspector/inspector.tsx b/editor/src/components/inspector/inspector.tsx index e98d2a548c41..9ac261b7a0d9 100644 --- a/editor/src/components/inspector/inspector.tsx +++ b/editor/src/components/inspector/inspector.tsx @@ -14,15 +14,9 @@ import * as PP from '../../core/shared/property-path' import * as EP from '../../core/shared/element-path' import Utils from '../../utils/utils' import { setFocus } from '../common/actions' -import type { Alignment, Distribution, EditorAction } from '../editor/action-types' +import type { EditorAction } from '../editor/action-types' import * as EditorActions from '../editor/actions/action-creators' -import { - alignSelectedViews, - distributeSelectedViews, - setProp_UNSAFE, - transientActions, - unsetProperty, -} from '../editor/actions/action-creators' +import { setProp_UNSAFE, transientActions, unsetProperty } from '../editor/actions/action-creators' import type { ElementsToRerender } from '../editor/store/editor-state' import { @@ -51,11 +45,9 @@ import { useKeepReferenceEqualityIfPossible, useKeepShallowReferenceEquality, } from '../../utils/react-performance' -import { Icn, useColorTheme, UtopiaTheme, FlexRow, Button, SquareButton } from '../../uuiui' import { getElementsToTarget } from './common/inspector-utils' import type { ElementPath, PropertyPath } from '../../core/shared/project-file-types' import { unless, when } from '../../utils/react-conditionals' -import { isTwindEnabled } from '../../core/tailwind/tailwind' import { isKeyboardAbsoluteStrategy, isKeyboardReorderStrategy, @@ -95,8 +87,8 @@ import { replaceFirstChildAndDeleteSiblings, } from '../editor/element-children' import { InspectorSectionHeader } from './section-header' -import { GridPlacementSubsection } from './sections/style-section/container-subsection/grid-cell-subsection' import { ContainerSubsection } from './sections/style-section/container-subsection/container-subsection' +import { isTailwindEnabled } from '../../core/tailwind/tailwind-compilation' export interface ElementPathElement { name?: string @@ -112,128 +104,6 @@ export interface InspectorProps extends TargetSelectorSectionProps { selectedViews: Array } -interface AlignDistributeButtonProps { - onMouseUp: () => void - toolTip: string - iconType: string - disabled: boolean -} - -const AlignDistributeButton = React.memo( - (props: AlignDistributeButtonProps) => { - return ( - - - - ) - }, -) -AlignDistributeButton.displayName = 'AlignDistributeButton' - -const AlignmentButtons = React.memo((props: { numberOfTargets: number }) => { - const colorTheme = useColorTheme() - const dispatch = useDispatch() - const alignSelected = React.useCallback( - (alignment: Alignment) => { - dispatch([alignSelectedViews(alignment)], 'everyone') - }, - [dispatch], - ) - - const distributeSelected = React.useCallback( - (distribution: Distribution) => { - dispatch([distributeSelectedViews(distribution)], 'everyone') - }, - [dispatch], - ) - const disableAlign = props.numberOfTargets === 0 - const disableDistribute = props.numberOfTargets < 3 - const multipleTargets = props.numberOfTargets > 1 - - const alignLeft = React.useCallback(() => alignSelected('left'), [alignSelected]) - const alignHCenter = React.useCallback(() => alignSelected('hcenter'), [alignSelected]) - const alignRight = React.useCallback(() => alignSelected('right'), [alignSelected]) - const alignTop = React.useCallback(() => alignSelected('top'), [alignSelected]) - const alignVCenter = React.useCallback(() => alignSelected('vcenter'), [alignSelected]) - const alignBottom = React.useCallback(() => alignSelected('bottom'), [alignSelected]) - const distributeHorizontal = React.useCallback( - () => distributeSelected('horizontal'), - [distributeSelected], - ) - const distributeVertical = React.useCallback( - () => distributeSelected('vertical'), - [distributeSelected], - ) - - return ( - - - - - - - - - - - ) -}) -AlignmentButtons.displayName = 'AlignmentButtons' - function buildNonDefaultPositionPaths(propertyTarget: Array): Array { return [ stylePropPathMappingFn('right', propertyTarget), @@ -341,7 +211,9 @@ export const Inspector = React.memo((props: InspectorProps) => { const onFocus = React.useCallback( (event: React.FocusEvent) => { if (focusedPanel !== 'inspector') { - dispatch([setFocus('inspector')], 'inspector') + queueMicrotask(() => { + dispatch([setFocus('inspector')], 'inspector') + }) } }, [dispatch, focusedPanel], @@ -413,8 +285,8 @@ export const Inspector = React.memo((props: InspectorProps) => { const shouldShowStyleSectionContents = styleSectionOpen && !shouldHideInspectorSections const shouldShowAdvancedSectionContents = advancedSectionOpen && !shouldHideInspectorSections - const shouldShowAlignmentButtons = !isCodeElement && inspectorPreferences.includes('layout') - const shouldShowClassNameSubsection = isTwindEnabled() && inspectorPreferences.includes('visual') + const shouldShowClassNameSubsection = + isTailwindEnabled() && inspectorPreferences.includes('visual') const shouldShowTargetSelectorSection = canEdit && inspectorPreferences.includes('visual') const shouldShowFlexSection = multiselectedContract === 'frame' && @@ -423,14 +295,6 @@ export const Inspector = React.memo((props: InspectorProps) => { const shouldShowSimplifiedLayoutSection = inspectorPreferences.includes('layout') - const shouldShowGridCellSection = useEditorState( - Substores.metadata, - (store) => - store.editor.selectedViews.length === 1 && - MetadataUtils.isGridCell(store.editor.jsxMetadata, store.editor.selectedViews[0]), - 'Inspector shouldShowGridCellSection', - ) - const shouldShowContainerSection = selectedViews.length > 0 && inspectorPreferences.includes('layout') @@ -473,14 +337,7 @@ export const Inspector = React.memo((props: InspectorProps) => { paddingBottom: 50, }} > - {rootElementIsSelected ? ( - - ) : ( - when( - shouldShowAlignmentButtons, - , - ) - )} + {when(rootElementIsSelected, )} {anyComponents || multiselectedContract === 'fragment' ? ( ) : null} @@ -507,7 +364,6 @@ export const Inspector = React.memo((props: InspectorProps) => { , )} - {when(shouldShowGridCellSection, )} {when(shouldShowFlexSection, )} {when( multiselectedContract === 'frame' || multiselectedContract === 'wrapper-div', diff --git a/editor/src/components/inspector/nine-block-control.spec.browser2.tsx b/editor/src/components/inspector/nine-block-control.spec.browser2.tsx index 121a4fe81528..82849f91d88a 100644 --- a/editor/src/components/inspector/nine-block-control.spec.browser2.tsx +++ b/editor/src/components/inspector/nine-block-control.spec.browser2.tsx @@ -3,14 +3,20 @@ import { expectSingleUndo2Saves, hoverControlWithCheck, selectComponentsForTest, + setFeatureForBrowserTestsUseInDescribeBlockOnly, } from '../../utils/utils.test-utils' import { CanvasControlsContainerID } from '../canvas/controls/new-canvas-controls' import { getSubduedPaddingControlTestID } from '../canvas/controls/select-mode/subdued-padding-control' import { mouseClickAtPoint } from '../canvas/event-helpers.test-utils' import type { EditorRenderResult } from '../canvas/ui-jsx.test-utils' -import { renderTestEditorWithCode } from '../canvas/ui-jsx.test-utils' +import { renderTestEditorWithCode, renderTestEditorWithModel } from '../canvas/ui-jsx.test-utils' import type { StartCenterEnd } from './inspector-common' import { NineBlockControlTestId, NineBlockSectors, NineBlockTestId } from './nine-block-controls' +import { + AlignItemsClassMapping, + JustifyContentClassMapping, + TailwindProject, +} from './sections/flex-section.test-utils' describe('Nine-block control', () => { describe('in flex row', () => { @@ -49,6 +55,27 @@ describe('Nine-block control', () => { expect(controls.length).toEqual(4) }) }) + + describe('Tailwind', () => { + setFeatureForBrowserTestsUseInDescribeBlockOnly('Tailwind', true) + + for (const [justifyContent, alignItems] of NineBlockSectors) { + it(`set ${justifyContent} and ${alignItems} via the nine-block control`, async () => { + const editor = await renderTestEditorWithModel( + TailwindProject('flex flex-row'), + 'await-first-dom-report', + ) + + const div = await doTest(editor, alignItems, justifyContent) + + expect(getComputedStyle(div).justifyContent).toEqual(justifyContent) + expect(getComputedStyle(div).alignItems).toEqual(alignItems) + expect(div.className).toEqual( + `top-10 left-10 w-64 h-64 bg-slate-100 absolute flex flex-row ${AlignItemsClassMapping[alignItems]} ${JustifyContentClassMapping[justifyContent]}`, + ) + }) + } + }) }) async function doTest( @@ -60,8 +87,8 @@ async function doTest( const div = editor.renderedDOM.getByTestId('mydiv') const divBounds = div.getBoundingClientRect() const divCorner = { - x: divBounds.x + 50, - y: divBounds.y + 40, + x: divBounds.x + 5, + y: divBounds.y + 4, } await mouseClickAtPoint(canvasControlsLayer, divCorner) diff --git a/editor/src/components/inspector/resize-to-fit-control.tsx b/editor/src/components/inspector/resize-to-fit-control.tsx index 7e5598f4aa9a..84f8d46a3e0c 100644 --- a/editor/src/components/inspector/resize-to-fit-control.tsx +++ b/editor/src/components/inspector/resize-to-fit-control.tsx @@ -40,6 +40,7 @@ function checkGroupSuitability( // Do not let a group be the target of a resize to fit operation. return !treatElementAsGroupLike(metadata, target) case 'fill': + case 'stretch': // Neither a group or the child of a group should be eligible for a resize to fill operation. return !( treatElementAsGroupLike(metadata, target) || treatElementAsGroupLike(metadata, parentPath) diff --git a/editor/src/components/inspector/section-header.tsx b/editor/src/components/inspector/section-header.tsx index 1887359b8cd5..77719edac898 100644 --- a/editor/src/components/inspector/section-header.tsx +++ b/editor/src/components/inspector/section-header.tsx @@ -19,7 +19,7 @@ export function InspectorSectionHeader({ padding: 8, cursor: 'pointer', }} - onClick={toggle} + onMouseDown={toggle} data-testid={`section-header-${title}`} >
{ return (
Component )} - + { propPath: PropertyPath @@ -492,9 +494,26 @@ const NumberWithSliderControl = React.memo( }, ) => { const { propName, propMetadata, controlDescription } = props + const { onTransientSubmitValue, onSubmitValue } = propMetadata const controlId = `${propName}-slider-property-control` - const value = getValueOrUndefinedFromPropMetadata(propMetadata) ?? 0 + const valueComingFromProps = getValueOrUndefinedFromPropMetadata(propMetadata) ?? 0 + + const [valueToShow, setValueToShow] = usePropControlledStateV2(valueComingFromProps) + + const onChangeValue = React.useCallback( + (value: any, transient: boolean = false) => { + ReactDOM.flushSync(() => { + setValueToShow(value) + }) + if (transient) { + onTransientSubmitValue(value) + } else { + onSubmitValue(value, transient) + } + }, + [setValueToShow, onTransientSubmitValue, onSubmitValue], + ) return ( |--45px--|'}> @@ -502,10 +521,10 @@ const NumberWithSliderControl = React.memo( key={`${controlId}-slider`} id={`${controlId}-slider`} testId={`${controlId}-slider`} - value={value} - onTransientSubmitValue={propMetadata.onTransientSubmitValue} - onForcedSubmitValue={propMetadata.onSubmitValue} - onSubmitValue={propMetadata.onSubmitValue} + value={valueToShow} + onTransientSubmitValue={onChangeValue} + onForcedSubmitValue={onChangeValue} + onSubmitValue={onChangeValue} controlStatus={propMetadata.controlStatus} controlStyles={propMetadata.controlStyles} DEPRECATED_controlOptions={props.controlOptions} @@ -514,7 +533,7 @@ const NumberWithSliderControl = React.memo( id={`${controlId}-number`} testId={`${controlId}-number`} key={`${controlId}-number`} - value={value} + value={valueToShow} onTransientSubmitValue={propMetadata.onTransientSubmitValue} onForcedSubmitValue={propMetadata.onSubmitValue} onSubmitValue={propMetadata.onSubmitValue} diff --git a/editor/src/components/inspector/sections/flex-section.test-utils.ts b/editor/src/components/inspector/sections/flex-section.test-utils.ts new file mode 100644 index 000000000000..8cd3a3cbe6f1 --- /dev/null +++ b/editor/src/components/inspector/sections/flex-section.test-utils.ts @@ -0,0 +1,57 @@ +import { TailwindConfigPath } from '../../../core/tailwind/tailwind-config' +import { createModifiedProject } from '../../../sample-projects/sample-project-utils.test-utils' +import { StoryboardFilePath } from '../../editor/store/editor-state' + +export const JustifyContentClassMapping = { + 'flex-start': 'justify-start', + center: 'justify-center', + 'flex-end': 'justify-end', +} as const + +export const AlignItemsClassMapping = { + 'flex-start': 'items-start', + center: 'items-center', + 'flex-end': 'items-end', +} as const + +export const TailwindProject = (classes: string) => + createModifiedProject({ + [StoryboardFilePath]: ` + import React from 'react' + import { Scene, Storyboard } from 'utopia-api' + export var storyboard = ( + + +
+
+
+
+ + + ) + + `, + [TailwindConfigPath]: ` + const TailwindConfig = { } + export default TailwindConfig + `, + 'app.css': ` + @tailwind base; + @tailwind components; + @tailwind utilities;`, + }) diff --git a/editor/src/components/inspector/sections/header-section/target-selector.tsx b/editor/src/components/inspector/sections/header-section/target-selector.tsx index 84a1e19dcc29..93115475a56b 100644 --- a/editor/src/components/inspector/sections/header-section/target-selector.tsx +++ b/editor/src/components/inspector/sections/header-section/target-selector.tsx @@ -408,10 +408,10 @@ const TargetListHeader = React.memo((props: TargetListHeaderProps) => { Target - + - + { +export interface FlexFieldControlProps { value: T controlStatus: ControlStatus controlStyles: ControlStyles @@ -79,86 +71,6 @@ interface FlexFieldControlProps { onUnset: () => void } -interface FlexDirectionControlProps extends FlexFieldControlProps { - flexWrap: FlexWrap -} - -export const FlexDirectionControl = React.memo((props: FlexDirectionControlProps) => { - return ( - - - - ) -}) - -interface FlexAlignItemsControlProps extends FlexFieldControlProps { - alignDirection: uglyLabel - alignItemsFlexStart: uglyLabel - alignItemsFlexEnd: uglyLabel -} - -export const FlexAlignItemsControl = React.memo((props: FlexAlignItemsControlProps) => { - const targetPath = useContextSelector(InspectorPropsContext, (contextData) => { - return contextData.targetPath - }) - const alignItemsProp = React.useMemo(() => { - return [stylePropPathMappingFn('alignItems', targetPath)] - }, [targetPath]) - return ( - - - Align -
- -
-
-
- ) -}) - interface FlexWrapControlProps extends FlexFieldControlProps {} const FlexWrapOptions: OptionsType = [ @@ -216,44 +128,6 @@ export const FlexWrapControl = React.memo((props: FlexWrapControlProps) => { ) }) -interface FlexJustifyContentControlProps extends FlexFieldControlProps { - flexDirection: FlexDirection - justifyFlexStart: uglyLabel - justifyFlexEnd: uglyLabel -} - -export const FlexJustifyContentControl = React.memo((props: FlexJustifyContentControlProps) => { - return ( - - - - ) -}) - const flexGapControlsForHoverAndFocused: { hovered: Array> focused: Array> @@ -328,6 +202,8 @@ export const FlexGapControl = React.memo(() => { testId='flex.container.gap' key='flex.container.gap' value={value} + minimum={0} + clampOnSubmitValue={true} onSubmitValue={wrappedOnSubmitValue} onTransientSubmitValue={wrappedOnTransientSubmitValue} onForcedSubmitValue={wrappedOnSubmitValue} @@ -348,69 +224,6 @@ export const FlexGapControl = React.memo(() => { ) }) -const justifyContentOptions = ( - alignDirection: FlexDirection, - justifyFlexStart: uglyLabel, - justifyFlexEnd: uglyLabel, -) => - [ - { - value: 'flex-start', - tooltip: PrettyLabel[justifyFlexStart], - icon: { - category: `layout/flex`, - type: `justifyContent-${alignDirection}-${justifyFlexStart}`, - color: 'secondary', - width: 16, - height: 16, - }, - }, - { - value: 'center', - tooltip: 'Center', - icon: { - category: `layout/flex`, - type: `justifyContent-${alignDirection}-center`, - color: 'secondary', - width: 16, - height: 16, - }, - }, - { - value: 'flex-end', - tooltip: PrettyLabel[justifyFlexEnd], - icon: { - category: `layout/flex`, - type: `justifyContent-${alignDirection}-${justifyFlexEnd}`, - color: 'secondary', - width: 16, - height: 16, - }, - }, - { - value: 'space-between', - tooltip: 'Space Between', - icon: { - category: `layout/flex`, - type: `justifyContent-${alignDirection}-spaceBetween`, - color: 'secondary', - width: 16, - height: 16, - }, - }, - { - value: 'space-around', - tooltip: 'Space Around', - icon: { - category: `layout/flex`, - type: `justifyContent-${alignDirection}-spaceAround`, - color: 'secondary', - width: 16, - height: 16, - }, - }, - ] as Array> - export const alignItemsOptions = ( alignDirection: string, alignItemsFlexStart: uglyLabel, diff --git a/editor/src/components/inspector/sections/layout-section/layout-system-subsection/split-chained-number-input.tsx b/editor/src/components/inspector/sections/layout-section/layout-system-subsection/split-chained-number-input.tsx index 19d4859c27f8..a17f0dfbe444 100644 --- a/editor/src/components/inspector/sections/layout-section/layout-system-subsection/split-chained-number-input.tsx +++ b/editor/src/components/inspector/sections/layout-section/layout-system-subsection/split-chained-number-input.tsx @@ -443,7 +443,7 @@ export const longhandShorthandEventHandler = ( } if (isTransient) { - dispatch([transientActions(actions)]) + dispatch([transientActions(actions, selectedViewsRef.current)]) } else { dispatch(actions) } diff --git a/editor/src/components/inspector/sections/layout-section/list-source-cartouche.tsx b/editor/src/components/inspector/sections/layout-section/list-source-cartouche.tsx index 831a8b20f88d..4771e65af340 100644 --- a/editor/src/components/inspector/sections/layout-section/list-source-cartouche.tsx +++ b/editor/src/components/inspector/sections/layout-section/list-source-cartouche.tsx @@ -100,11 +100,14 @@ const MapListSourceCartoucheInner = React.memo( const { popupIsOpen, DataPickerComponent, setReferenceElement, openPopup } = useDataPickerButton(variableNamesInScope, onPickMappedElement, pathToMappedExpression, target) - const onClick = React.useCallback(() => { - if (openOn === 'single-click') { - openPopup() - } - }, [openOn, openPopup]) + const onClick = React.useCallback( + (e: React.MouseEvent) => { + if (openOn === 'single-click') { + openPopup() + } + }, + [openOn, openPopup], + ) const onDoubleClick = React.useCallback(() => { if (openOn === 'double-click') { diff --git a/editor/src/components/inspector/sections/layout-section/self-layout-subsection/frame-updating-layout-section.spec.browser2.tsx b/editor/src/components/inspector/sections/layout-section/self-layout-subsection/frame-updating-layout-section.spec.browser2.tsx index 3321e3df30c6..4ae0d9813c11 100644 --- a/editor/src/components/inspector/sections/layout-section/self-layout-subsection/frame-updating-layout-section.spec.browser2.tsx +++ b/editor/src/components/inspector/sections/layout-section/self-layout-subsection/frame-updating-layout-section.spec.browser2.tsx @@ -142,6 +142,7 @@ describe('Frame updating layout section', () => { const actualLocalFrame = MetadataUtils.getLocalFrame( metadataForElement.elementPath, metadataMap, + null, ) expect(actualLocalFrame).toEqual(expectedFrame) } diff --git a/editor/src/components/inspector/sections/layout-section/self-layout-subsection/frame-updating-layout-section.tsx b/editor/src/components/inspector/sections/layout-section/self-layout-subsection/frame-updating-layout-section.tsx index 33c5d446267d..d5187fa60c8e 100644 --- a/editor/src/components/inspector/sections/layout-section/self-layout-subsection/frame-updating-layout-section.tsx +++ b/editor/src/components/inspector/sections/layout-section/self-layout-subsection/frame-updating-layout-section.tsx @@ -36,7 +36,12 @@ import { import { InspectorContextMenuWrapper } from '../../../../context-menu-wrapper' import { useDispatch } from '../../../../editor/store/dispatch-context' import { Substores, useEditorState, useRefEditorState } from '../../../../editor/store/store-hook' -import { metadataSelector, selectedViewsSelector } from '../../../../inspector/inpector-selectors' +import { + allElementPropsSelector, + metadataSelector, + pathTreesSelector, + selectedViewsSelector, +} from '../../../../inspector/inpector-selectors' import { unsetPropertyMenuItem } from '../../../common/context-menu-items' import { cssNumber, @@ -46,7 +51,10 @@ import { type UnknownOrEmptyInput, } from '../../../common/css-utils' import { useInspectorLayoutInfo } from '../../../common/property-path-hooks' -import { executeFirstApplicableStrategy } from '../../../inspector-strategies/inspector-strategy' +import { + executeFirstApplicableStrategy, + executeFirstApplicableStrategyForContinuousInteraction, +} from '../../../inspector-strategies/inspector-strategy' import { UIGridRow } from '../../../widgets/ui-grid-row' import * as EP from '../../../../../core/shared/element-path' @@ -90,8 +98,8 @@ interface LTWHPixelValues { export const FrameUpdatingLayoutSection = React.memo(() => { const dispatch = useDispatch() const metadataRef = useRefEditorState(metadataSelector) - const allElementsPropsRef = useRefEditorState((store) => store.editor.allElementProps) - const pathTreesRef = useRefEditorState((store) => store.editor.elementPathTree) + const allElementPropsRef = useRefEditorState(allElementPropsSelector) + const elementPathTreesRef = useRefEditorState(pathTreesSelector) const selectedViewsRef = useRefEditorState(selectedViewsSelector) const projectContentsRef = useRefEditorState((store) => store.editor.projectContents) const originalGlobalFrame: CanvasRectangle = useEditorState( @@ -181,6 +189,7 @@ export const FrameUpdatingLayoutSection = React.memo(() => { const maybeInfinityLocalFrame = MetadataUtils.getLocalFrame( selectedView, store.editor.jsxMetadata, + null, ) if (maybeInfinityLocalFrame == null || isInfinityRectangle(maybeInfinityLocalFrame)) { result.left.push(0) @@ -201,32 +210,45 @@ export const FrameUpdatingLayoutSection = React.memo(() => { ) const updateFrame = React.useCallback( - (frameUpdate: FrameUpdate) => { + (frameUpdate: FrameUpdate, transient: boolean) => { + const elementsToRerenderTransient = transient + ? selectedViewsRef.current + : 'rerender-all-elements' switch (frameUpdate.type) { case 'DELTA_FRAME_UPDATE': if ( frameUpdate.edgePosition === EdgePositionTop || frameUpdate.edgePosition === EdgePositionLeft ) { - executeFirstApplicableStrategy(dispatch, [ - moveInspectorStrategy( - metadataRef.current, - selectedViewsRef.current, - projectContentsRef.current, - frameUpdate.edgeMovement, - ), - ]) + executeFirstApplicableStrategyForContinuousInteraction( + dispatch, + [ + moveInspectorStrategy( + metadataRef.current, + allElementPropsRef.current, + elementPathTreesRef.current, + selectedViewsRef.current, + projectContentsRef.current, + frameUpdate.edgeMovement, + ), + ], + elementsToRerenderTransient, + ) } else { - executeFirstApplicableStrategy(dispatch, [ - resizeInspectorStrategy( - metadataRef.current, - selectedViewsRef.current, - projectContentsRef.current, - originalGlobalFrame, - frameUpdate.edgePosition, - frameUpdate.edgeMovement, - ), - ]) + executeFirstApplicableStrategyForContinuousInteraction( + dispatch, + [ + resizeInspectorStrategy( + metadataRef.current, + selectedViewsRef.current, + projectContentsRef.current, + originalGlobalFrame, + frameUpdate.edgePosition, + frameUpdate.edgeMovement, + ), + ], + elementsToRerenderTransient, + ) } break case 'DIRECT_FRAME_UPDATE': @@ -235,52 +257,78 @@ export const FrameUpdatingLayoutSection = React.memo(() => { frameUpdate.edgePosition === EdgePositionLeft ) { const leftOrTop = frameUpdate.edgePosition === EdgePositionLeft ? 'left' : 'top' - executeFirstApplicableStrategy(dispatch, [ - directMoveInspectorStrategy( - metadataRef.current, - selectedViewsRef.current, - projectContentsRef.current, - leftOrTop, - frameUpdate.edgeValue, - ), - ]) + executeFirstApplicableStrategyForContinuousInteraction( + dispatch, + [ + directMoveInspectorStrategy( + metadataRef.current, + allElementPropsRef.current, + elementPathTreesRef.current, + selectedViewsRef.current, + projectContentsRef.current, + leftOrTop, + frameUpdate.edgeValue, + ), + ], + elementsToRerenderTransient, + ) } else { const widthOrHeight = frameUpdate.edgePosition === EdgePositionRight ? 'width' : 'height' - executeFirstApplicableStrategy(dispatch, [ - directResizeInspectorStrategy( - metadataRef.current, - selectedViewsRef.current, - projectContentsRef.current, - widthOrHeight, - frameUpdate.edgeValue, - ), - ]) + executeFirstApplicableStrategyForContinuousInteraction( + dispatch, + [ + directResizeInspectorStrategy( + metadataRef.current, + selectedViewsRef.current, + projectContentsRef.current, + widthOrHeight, + frameUpdate.edgeValue, + ), + ], + elementsToRerenderTransient, + ) } break default: assertNever(frameUpdate) } }, - [dispatch, metadataRef, originalGlobalFrame, projectContentsRef, selectedViewsRef], + [ + allElementPropsRef, + dispatch, + elementPathTreesRef, + metadataRef, + originalGlobalFrame, + projectContentsRef, + selectedViewsRef, + ], ) + const disableXYControls = React.useMemo(() => { + return selectedViewsRef.current.some((view) => + MetadataUtils.isFlexOrGridChild(metadataRef.current, view), + ) + }, [selectedViewsRef, metadataRef]) + return ( <> @@ -341,8 +389,9 @@ interface LayoutPinPropertyControlProps { label: string property: TLWH currentValues: Array - updateFrame: (frameUpdate: FrameUpdate) => void + updateFrame: (frameUpdate: FrameUpdate, transient: boolean) => void invalid?: boolean + disabled?: boolean } function getSingleCommonValue(currentValues: Array): number | null { @@ -378,7 +427,7 @@ const FrameUpdatingLayoutControl = React.memo((props: LayoutPinPropertyControlPr currentValuesRef.current = currentValues const onSubmitValue = React.useCallback( - (newValue: UnknownOrEmptyInput) => { + (newValue: UnknownOrEmptyInput, transient: boolean = false) => { const calculatedSingleCommonValue = getSingleCommonValue(currentValuesRef.current) if (isUnknownInputValue(newValue)) { // Ignore right now. @@ -390,14 +439,14 @@ const FrameUpdatingLayoutControl = React.memo((props: LayoutPinPropertyControlPr if (newValue.unit == null || newValue.unit === 'px') { const edgePosition = getTLWHEdgePosition(property) if (calculatedSingleCommonValue == null) { - updateFrame(directFrameUpdate(edgePosition, newValue.value)) + updateFrame(directFrameUpdate(edgePosition, newValue.value), transient) } else { const movement = getMovementFromValues( property, calculatedSingleCommonValue, newValue.value, ) - updateFrame(deltaFrameUpdate(edgePosition, movement)) + updateFrame(deltaFrameUpdate(edgePosition, movement), transient) } } else { console.error('Attempting to use a value with a unit, which is invalid.') @@ -429,6 +478,7 @@ const FrameUpdatingLayoutControl = React.memo((props: LayoutPinPropertyControlPr defaultUnitToHide={'px'} stepSize={1} innerLabel={props.label} + style={{ opacity: props.disabled ? 0.5 : 1 }} /> ) diff --git a/editor/src/components/inspector/sections/layout-section/self-layout-subsection/simplified-layout-subsection.tsx b/editor/src/components/inspector/sections/layout-section/self-layout-subsection/simplified-layout-subsection.tsx index 0c76a94ea7af..f634e05e943c 100644 --- a/editor/src/components/inspector/sections/layout-section/self-layout-subsection/simplified-layout-subsection.tsx +++ b/editor/src/components/inspector/sections/layout-section/self-layout-subsection/simplified-layout-subsection.tsx @@ -1,12 +1,6 @@ import React from 'react' import { when } from '../../../../../utils/react-conditionals' -import { - FlexColumn, - FlexRow, - InspectorSubsectionHeader, - Tooltip, - UtopiaTheme, -} from '../../../../../uuiui' +import { FlexColumn, FlexRow, Tooltip, UtopiaTheme } from '../../../../../uuiui' import { Link } from '../../../../../uuiui/link' import { useConvertWrapperToFrame } from '../../../../canvas/canvas-strategies/strategies/group-conversion-helpers' import { Substores, useEditorState } from '../../../../editor/store/store-hook' @@ -20,6 +14,10 @@ import { UIGridRow } from '../../../widgets/ui-grid-row' import { RadiusRow } from '../../style-section/container-subsection/radius-row' import { ClipContentControl } from './clip-content-control' import { FrameUpdatingLayoutSection } from './frame-updating-layout-section' +import { MetadataUtils } from '../../../../../core/model/element-metadata-utils' +import { getInspectorPreferencesForTargets } from '../../../../../core/property-controls/property-controls-utils' +import { AlignmentButtons } from '../../../alignment-buttons' +import { GridPlacementSubsection } from '../../style-section/container-subsection/grid-cell-subsection' export const SimplifiedLayoutSubsection = React.memo(() => { const selectedElementContract = useEditorState( @@ -33,6 +31,40 @@ export const SimplifiedLayoutSubsection = React.memo(() => { const showWrapperSectionWarning = selectedElementContract === 'wrapper-div' + const isCodeElement = useEditorState( + Substores.metadata, + (store) => + store.editor.selectedViews.length > 0 && + store.editor.selectedViews.every( + (path) => + MetadataUtils.isConditional(path, store.editor.jsxMetadata) || + MetadataUtils.isExpressionOtherJavascript(path, store.editor.jsxMetadata) || + MetadataUtils.isJSXMapExpression(path, store.editor.jsxMetadata), + ), + 'SimplifiedLayoutSubsection isCodeElement', + ) + + const inspectorPreferences = useEditorState( + Substores.propertyControlsInfo, + (store) => + getInspectorPreferencesForTargets( + store.editor.selectedViews, + store.editor.propertyControlsInfo, + store.editor.projectContents, + ), + 'SimplifiedLayoutSubsection inspectorPreferences', + ) + + const shouldShowAlignmentButtons = !isCodeElement && inspectorPreferences.includes('layout') + + const shouldShowGridCellSection = useEditorState( + Substores.metadata, + (store) => + store.editor.selectedViews.length === 1 && + MetadataUtils.isGridItem(store.editor.jsxMetadata, store.editor.selectedViews[0]), + 'Inspector shouldShowGridCellSection', + ) + return ( { {when( showLayoutSection, <> + {when(shouldShowAlignmentButtons, )} @@ -66,6 +99,7 @@ export const SimplifiedLayoutSubsection = React.memo(() => { , )} + {when(shouldShowGridCellSection, )} ) }) diff --git a/editor/src/components/inspector/sections/style-section/background-subsection/solid-background-layer.tsx b/editor/src/components/inspector/sections/style-section/background-subsection/solid-background-layer.tsx index 2eb227918cdb..d5f9381117f5 100644 --- a/editor/src/components/inspector/sections/style-section/background-subsection/solid-background-layer.tsx +++ b/editor/src/components/inspector/sections/style-section/background-subsection/solid-background-layer.tsx @@ -117,9 +117,9 @@ export const SolidBackgroundLayer = React.memo((props onMouseDown={stopPropagation} /> ((props setOpenPopup={props.setOpenPopup} /> { return } - const commands = setGridPropsCommands( + const commands = getCommandsForGridItemPlacement( cell.elementPath, gridTemplate, cell.specialSizeMeasurements.elementGridProperties, @@ -100,6 +105,18 @@ export const GridPlacementSubsection = React.memo(() => { dispatch([applyCommandsAction(commands)]) }, [dispatch, cell, gridTemplate]) + const removePlacementProps = React.useCallback(() => { + if (cell == null) { + return + } + + dispatch([ + applyCommandsAction([ + deleteProperties('always', cell.elementPath, GridPositioningProps.map(styleP)), + ]), + ]) + }, [dispatch, cell]) + if (cell == null || gridTemplate == null) { return null } @@ -122,6 +139,12 @@ export const GridPlacementSubsection = React.memo(() => {
, )} + {unless( + isAllDefaults, + + + , + )} {unless( isAllDefaults, @@ -190,12 +213,12 @@ const DimensionsControls = React.memo( ? gridPositionValue(e.value) : cssKeyword(e.value) - const maybeAreaValue = maybeValueFromAreaName( + const maybeLineValue = maybeValueFromLineName( value, dimension === 'gridColumnStart' || dimension === 'width' ? columnLabels : rowLabels, ) - if (maybeAreaValue != null) { - value = maybeAreaValue + if (maybeLineValue != null) { + value = maybeLineValue } let newValues = { @@ -226,6 +249,7 @@ const DimensionsControls = React.memo( case 'width': if ( !isCSSKeyword(newValues.gridColumnStart) && + !isGridSpan(newValues.gridColumnStart) && // TODO support grid spans newValues.gridColumnStart?.numericalPosition != null && isCSSNumber(e) ) { @@ -237,6 +261,7 @@ const DimensionsControls = React.memo( case 'height': if ( !isCSSKeyword(newValues.gridRowStart) && + !isGridSpan(newValues.gridRowStart) && // TODO support grid spans newValues.gridRowStart?.numericalPosition != null && isCSSNumber(e) ) { @@ -246,7 +271,11 @@ const DimensionsControls = React.memo( } break } - const commands = setGridPropsCommands(cell.elementPath, gridTemplate, newValues) + const commands = getCommandsForGridItemPlacement( + cell.elementPath, + gridTemplate, + newValues, + ) dispatch([applyCommandsAction(commands)]) }, @@ -256,6 +285,7 @@ const DimensionsControls = React.memo( const columnStartValue = React.useMemo(() => { return getValueWithAlias( cell.specialSizeMeasurements.elementGridProperties.gridColumnStart, + cell.specialSizeMeasurements.elementGridProperties.gridColumnEnd, columnLabels, ) }, [cell, columnLabels]) @@ -263,6 +293,7 @@ const DimensionsControls = React.memo( const rowStartValue = React.useMemo(() => { return getValueWithAlias( cell.specialSizeMeasurements.elementGridProperties.gridRowStart, + cell.specialSizeMeasurements.elementGridProperties.gridRowEnd, rowLabels, ) }, [cell, rowLabels]) @@ -277,8 +308,8 @@ const DimensionsControls = React.memo( onSubmitValue={onSubmitPosition('gridColumnStart')} value={columnStartValue.value} valueAlias={columnStartValue.alias} - keywords={keywordsForPosition(columnLabels.map((l) => l.areaName))} - keywordTypeCheck={isValidGridPositionKeyword(columnLabels.map((l) => l.areaName))} + keywords={keywordsForPosition(columnLabels.map((l) => l.lineName))} + keywordTypeCheck={isValidGridPositionKeyword(columnLabels.map((l) => l.lineName))} labelInner={{ category: 'inspector-element', type: 'gridColumn', @@ -290,8 +321,8 @@ const DimensionsControls = React.memo( onSubmitValue={onSubmitPosition('gridRowStart')} value={rowStartValue.value} valueAlias={rowStartValue.alias} - keywords={keywordsForPosition(rowLabels.map((l) => l.areaName))} - keywordTypeCheck={isValidGridPositionKeyword(rowLabels.map((l) => l.areaName))} + keywords={keywordsForPosition(rowLabels.map((l) => l.lineName))} + keywordTypeCheck={isValidGridPositionKeyword(rowLabels.map((l) => l.lineName))} labelInner={{ category: 'inspector-element', type: 'gridRow', @@ -354,21 +385,25 @@ const BoundariesControls = React.memo( ? gridPositionValue(e.value) : cssKeyword(e.value) - const maybeAreaValue = maybeValueFromAreaName( + const maybeLineValue = maybeValueFromLineName( value, dimension === 'gridColumnStart' || dimension === 'gridColumnEnd' ? columnLabels : rowLabels, ) - if (maybeAreaValue != null) { - value = maybeAreaValue + if (maybeLineValue != null) { + value = maybeLineValue } const newValues = { ...cell.specialSizeMeasurements.elementGridProperties, [dimension]: value, } - const commands = setGridPropsCommands(cell.elementPath, gridTemplate, newValues) + const commands = getCommandsForGridItemPlacement( + cell.elementPath, + gridTemplate, + newValues, + ) dispatch([applyCommandsAction(commands)]) }, @@ -378,6 +413,7 @@ const BoundariesControls = React.memo( const columnStartValue = React.useMemo(() => { return getValueWithAlias( cell.specialSizeMeasurements.elementGridProperties.gridColumnStart, + cell.specialSizeMeasurements.elementGridProperties.gridColumnEnd, columnLabels, ) }, [cell, columnLabels]) @@ -385,6 +421,7 @@ const BoundariesControls = React.memo( const columnEndValue = React.useMemo(() => { return getValueWithAlias( cell.specialSizeMeasurements.elementGridProperties.gridColumnEnd, + null, columnLabels, ) }, [cell, columnLabels]) @@ -392,6 +429,7 @@ const BoundariesControls = React.memo( const rowStartValue = React.useMemo(() => { return getValueWithAlias( cell.specialSizeMeasurements.elementGridProperties.gridRowStart, + cell.specialSizeMeasurements.elementGridProperties.gridRowEnd, rowLabels, ) }, [cell, rowLabels]) @@ -399,6 +437,7 @@ const BoundariesControls = React.memo( const rowEndValue = React.useMemo(() => { return getValueWithAlias( cell.specialSizeMeasurements.elementGridProperties.gridRowEnd, + null, rowLabels, ) }, [cell, rowLabels]) @@ -418,8 +457,8 @@ const BoundariesControls = React.memo( type: 'gridColumn-start', color: 'on-highlight-secondary', }} - keywords={keywordsForPosition(columnLabels.map((l) => l.areaName))} - keywordTypeCheck={isValidGridPositionKeyword(columnLabels.map((l) => l.areaName))} + keywords={keywordsForPosition(columnLabels.map((l) => l.lineName))} + keywordTypeCheck={isValidGridPositionKeyword(columnLabels.map((l) => l.lineName))} /> l.areaName))} - keywordTypeCheck={isValidGridPositionKeyword(rowLabels.map((l) => l.areaName))} + keywords={keywordsForPosition(rowLabels.map((l) => l.lineName))} + keywordTypeCheck={isValidGridPositionKeyword(rowLabels.map((l) => l.lineName))} /> @@ -449,8 +488,8 @@ const BoundariesControls = React.memo( type: 'gridColumn-end', color: 'on-highlight-secondary', }} - keywords={keywordsForPosition(columnLabels.map((l) => l.areaName))} - keywordTypeCheck={isValidGridPositionKeyword(columnLabels.map((l) => l.areaName))} + keywords={keywordsForPosition(columnLabels.map((l) => l.lineName))} + keywordTypeCheck={isValidGridPositionKeyword(columnLabels.map((l) => l.lineName))} /> l.areaName))} - keywordTypeCheck={isValidGridPositionKeyword(rowLabels.map((l) => l.areaName))} + keywords={keywordsForPosition(rowLabels.map((l) => l.lineName))} + keywordTypeCheck={isValidGridPositionKeyword(rowLabels.map((l) => l.lineName))} /> @@ -473,8 +512,14 @@ const BoundariesControls = React.memo( ) BoundariesControls.displayName = 'BoundariesControls' -function getValue(pos: GridPosition | null): CSSNumber | null { - if (pos == null || isCSSKeyword(pos) || pos.numericalPosition == null) { +function getValue(pos: GridPositionOrSpan | null, maybeOffset: number = 0): CSSNumber | null { + if (pos == null || isCSSKeyword(pos)) { + return null + } + if (isGridSpan(pos)) { + return pos.type === 'SPAN_AREA' ? null : cssNumber(pos.value + maybeOffset) + } + if (pos.numericalPosition == null) { return null } return cssNumber(pos.numericalPosition) @@ -482,11 +527,14 @@ function getValue(pos: GridPosition | null): CSSNumber | null { function getWidthOrHeight(props: GridElementProperties, dimension: 'width' | 'height') { const start = getValue(dimension === 'width' ? props.gridColumnStart : props.gridRowStart) - const end = getValue(dimension === 'width' ? props.gridColumnEnd : props.gridRowEnd) + const end = getValue( + dimension === 'width' ? props.gridColumnEnd : props.gridRowEnd, + start?.value ?? 0, + ) if (start == null || end == null) { - return 1 + return Math.max(1, start?.value ?? 0, end?.value ?? 0) } - return end.value - start.value + return Math.max(1, end.value - start.value) } function getLabelsFromTemplate(gridTemplate: GridContainerProperties) { @@ -496,10 +544,10 @@ function getLabelsFromTemplate(gridTemplate: GridContainerProperties) { return [] } return mapDropNulls((d, index) => { - if (d.areaName == null) { + if (d.lineName == null) { return null } - return { areaName: d.areaName, position: index + 1 } + return { lineName: d.lineName, position: index + 1 } }, template.dimensions) } const columnLabels = getAxisLabels('gridTemplateColumns') @@ -523,28 +571,41 @@ function keywordsForPosition(labels: string[]) { return items } -function maybeValueFromAreaName( +function maybeValueFromLineName( value: GridPosition, - labels: { areaName: string; position: number }[], + labels: { lineName: string; position: number }[], ): GridPositionValue | null { if (!isCSSKeyword(value)) { return null } - const areaMatch = labels.find((l) => l.areaName === value.value) - if (areaMatch != null) { - return gridPositionValue(areaMatch.position) + const lineMatch = labels.find((l) => l.lineName === value.value) + if (lineMatch != null) { + return gridPositionValue(lineMatch.position) } return null } function getValueWithAlias( - position: GridPosition | null, - labels: { areaName: string; position: number }[], -) { - const value = getValue(position) ?? cssKeyword('auto') + start: GridPositionOrSpan | null, + end: GridPositionOrSpan | null, + labels: { lineName: string; position: number }[], +): { + value: CSSNumber | CSSKeyword + alias?: string +} { + if (isGridSpan(start)) { + if (isGridSpan(end)) { + return start.type === 'SPAN_NUMERIC' + ? { value: cssNumber(start.value) } + : { value: cssKeyword('auto') } + } else { + return { value: cssKeyword('auto') } + } + } + const value = getValue(start) ?? cssKeyword('auto') if (isCSSKeyword(value)) { return { value: value } } - const areaName = labels.find((l) => l.position === value.value) - return { value: value, alias: areaName?.areaName } + const lineName = labels.find((l) => l.position === value.value) + return { value: value, alias: lineName?.lineName } } diff --git a/editor/src/components/inspector/spaced-packed-control.spec.browser2.tsx b/editor/src/components/inspector/spaced-packed-control.spec.browser2.tsx index 5ffcca13b170..4e9974cd4e6d 100644 --- a/editor/src/components/inspector/spaced-packed-control.spec.browser2.tsx +++ b/editor/src/components/inspector/spaced-packed-control.spec.browser2.tsx @@ -3,11 +3,13 @@ import { expectSingleUndo2Saves, hoverControlWithCheck, selectComponentsForTest, + setFeatureForBrowserTestsUseInDescribeBlockOnly, } from '../../utils/utils.test-utils' import { getSubduedPaddingControlTestID } from '../canvas/controls/select-mode/subdued-padding-control' import { mouseClickAtPoint } from '../canvas/event-helpers.test-utils' import type { EditorRenderResult } from '../canvas/ui-jsx.test-utils' -import { renderTestEditorWithCode } from '../canvas/ui-jsx.test-utils' +import { renderTestEditorWithCode, renderTestEditorWithModel } from '../canvas/ui-jsx.test-utils' +import { TailwindProject } from './sections/flex-section.test-utils' import { PackedLabelCopy, SpacedLabelCopy, @@ -46,6 +48,35 @@ describe('spaced - packed control', () => { expect(div.style.justifyContent).toEqual('flex-start') }) + describe('Tailwind', () => { + setFeatureForBrowserTestsUseInDescribeBlockOnly('Tailwind', true) + it('set element to spaced layout', async () => { + const editor = await renderTestEditorWithModel( + TailwindProject('flex flex-row'), + 'await-first-dom-report', + ) + await selectComponentsForTest(editor, [EP.fromString('sb/scene/mydiv')]) + await expectSingleUndo2Saves(editor, () => clickButton(editor, SpacedLabelCopy)) + const div = editor.renderedDOM.getByTestId('mydiv') + expect(div.className).toEqual( + 'top-10 left-10 w-64 h-64 bg-slate-100 absolute flex flex-row justify-between', + ) + }) + it('set element to packed layout', async () => { + const editor = await renderTestEditorWithModel( + TailwindProject('flex flex-row'), + 'await-first-dom-report', + ) + await selectComponentsForTest(editor, [EP.fromString('sb/scene/mydiv')]) + await expectSingleUndo2Saves(editor, () => clickButton(editor, SpacedLabelCopy)) + await expectSingleUndo2Saves(editor, () => clickButton(editor, PackedLabelCopy)) + const div = editor.renderedDOM.getByTestId('mydiv') + expect(div.className).toEqual( + 'top-10 left-10 w-64 h-64 bg-slate-100 absolute flex flex-row justify-start', + ) + }) + }) + it('when spaced/packed control is hovered, padding hihglights are shown', async () => { const editor = await renderTestEditorWithCode(projectWithPadding, 'await-first-dom-report') await selectComponentsForTest(editor, [EP.fromString('sb/div')]) diff --git a/editor/src/components/inspector/three-bar-control.spec.browser2.tsx b/editor/src/components/inspector/three-bar-control.spec.browser2.tsx index 285d7f9e2a52..cf79060ee8ab 100644 --- a/editor/src/components/inspector/three-bar-control.spec.browser2.tsx +++ b/editor/src/components/inspector/three-bar-control.spec.browser2.tsx @@ -1,10 +1,16 @@ +import type { ElementPath } from 'utopia-shared/src/types' import * as EP from '../../core/shared/element-path' -import { expectSingleUndo2Saves, selectComponentsForTest } from '../../utils/utils.test-utils' +import { + expectSingleUndo2Saves, + selectComponentsForTest, + setFeatureForBrowserTestsUseInDescribeBlockOnly, +} from '../../utils/utils.test-utils' import { mouseClickAtPoint } from '../canvas/event-helpers.test-utils' import type { EditorRenderResult } from '../canvas/ui-jsx.test-utils' -import { renderTestEditorWithCode } from '../canvas/ui-jsx.test-utils' +import { renderTestEditorWithCode, renderTestEditorWithModel } from '../canvas/ui-jsx.test-utils' import type { FlexDirection } from './common/css-utils' import type { FlexAlignment } from './inspector-common' +import { AlignItemsClassMapping, TailwindProject } from './sections/flex-section.test-utils' import { ThreeBarControlTestId } from './three-bar-control' const StoryboardId = 'sb' @@ -13,56 +19,122 @@ const ParentId = 'p' describe('three bar control', () => { describe('set align-items in flex-direcion: column', () => { + const ElementToSelect = EP.fromString(`${StoryboardId}/${SceneId}/${ParentId}`) + it('align-items: start', async () => { const editor = await renderTestEditorWithCode(project('row'), 'await-first-dom-report') const desiredAlignItems: FlexAlignment = 'flex-start' - const div = await doTest(editor, ThreeBarControlTestId(desiredAlignItems)) + const div = await doTest( + editor, + ThreeBarControlTestId(desiredAlignItems), + ElementToSelect, + ParentId, + ) expect(div.style.alignItems).toEqual(desiredAlignItems) }) it('align-items: center', async () => { const editor = await renderTestEditorWithCode(project('row'), 'await-first-dom-report') const desiredAlignItems: FlexAlignment = 'center' - const div = await doTest(editor, ThreeBarControlTestId(desiredAlignItems)) + const div = await doTest( + editor, + ThreeBarControlTestId(desiredAlignItems), + ElementToSelect, + ParentId, + ) expect(div.style.alignItems).toEqual(desiredAlignItems) }) it('align-items: end', async () => { const editor = await renderTestEditorWithCode(project('row'), 'await-first-dom-report') const desiredAlignItems: FlexAlignment = 'flex-end' - const div = await doTest(editor, ThreeBarControlTestId(desiredAlignItems)) + const div = await doTest( + editor, + ThreeBarControlTestId(desiredAlignItems), + ElementToSelect, + ParentId, + ) expect(div.style.alignItems).toEqual(desiredAlignItems) }) }) describe('set align-items in flex-direcion: row', () => { + const ElementToSelect = EP.fromString(`${StoryboardId}/${SceneId}/${ParentId}`) + it('align-items: start', async () => { const editor = await renderTestEditorWithCode(project('column'), 'await-first-dom-report') const desiredAlignItems: FlexAlignment = 'flex-start' - const div = await doTest(editor, ThreeBarControlTestId(desiredAlignItems)) + const div = await doTest( + editor, + ThreeBarControlTestId(desiredAlignItems), + EP.fromString(`${StoryboardId}/${SceneId}/${ParentId}`), + ParentId, + ) expect(div.style.alignItems).toEqual(desiredAlignItems) }) it('align-items: center', async () => { const editor = await renderTestEditorWithCode(project('column'), 'await-first-dom-report') const desiredAlignItems: FlexAlignment = 'center' - const div = await doTest(editor, ThreeBarControlTestId(desiredAlignItems)) + const div = await doTest( + editor, + ThreeBarControlTestId(desiredAlignItems), + ElementToSelect, + ParentId, + ) expect(div.style.alignItems).toEqual(desiredAlignItems) }) it('align-items: end', async () => { const editor = await renderTestEditorWithCode(project('column'), 'await-first-dom-report') const desiredAlignItems: FlexAlignment = 'flex-end' - const div = await doTest(editor, ThreeBarControlTestId(desiredAlignItems)) + const div = await doTest( + editor, + ThreeBarControlTestId(desiredAlignItems), + ElementToSelect, + ParentId, + ) expect(div.style.alignItems).toEqual(desiredAlignItems) }) }) + + describe('Tailwind', () => { + setFeatureForBrowserTestsUseInDescribeBlockOnly('Tailwind', true) + + const ElementToSelect = EP.fromString('sb/scene/mydiv') + + const AlignItems = ['flex-start', 'center', 'flex-end'] as const + + for (const alignItems of AlignItems) { + it(`align-items: ${alignItems}`, async () => { + const editor = await renderTestEditorWithModel( + TailwindProject('flex flex-row justify-between'), + 'await-first-dom-report', + ) + const div = await doTest( + editor, + ThreeBarControlTestId(alignItems), + ElementToSelect, + 'mydiv', + ) + expect(getComputedStyle(div).alignItems).toEqual(alignItems) + expect(div.className).toEqual( + `top-10 left-10 w-64 h-64 bg-slate-100 absolute flex flex-row justify-between ${AlignItemsClassMapping[alignItems]}`, + ) + }) + } + }) }) -async function doTest(editor: EditorRenderResult, controlId: string): Promise { - await selectComponentsForTest(editor, [EP.fromString(`${StoryboardId}/${SceneId}/${ParentId}`)]) +async function doTest( + editor: EditorRenderResult, + controlId: string, + elementPath: ElementPath, + testId: string, +): Promise { + await selectComponentsForTest(editor, [elementPath]) - const div = editor.renderedDOM.getByTestId(ParentId) + const div = editor.renderedDOM.getByTestId(testId) const control = editor.renderedDOM.getByTestId(controlId) const controlBounds = control.getBoundingClientRect() await expectSingleUndo2Saves(editor, async () => { diff --git a/editor/src/components/inspector/use-disable-alignment.tsx b/editor/src/components/inspector/use-disable-alignment.tsx new file mode 100644 index 000000000000..839ced711303 --- /dev/null +++ b/editor/src/components/inspector/use-disable-alignment.tsx @@ -0,0 +1,74 @@ +import React from 'react' +import type { ElementPath } from 'utopia-shared/src/types' +import { MetadataUtils } from '../../core/model/element-metadata-utils' +import type { ElementInstanceMetadata } from '../../core/shared/element-template' +import { useEditorState, Substores } from '../editor/store/store-hook' +import * as EP from '../../core/shared/element-path' + +export function useDisableAlignment( + selectedViews: ElementPath[], + orientation: 'horizontal' | 'vertical', +) { + const selectedElements = useEditorState( + Substores.metadata, + (store) => + selectedViews.map((path) => { + return { + element: MetadataUtils.findElementByElementPath(store.editor.jsxMetadata, path), + parent: MetadataUtils.findElementByElementPath( + store.editor.jsxMetadata, + EP.parentPath(path), + ), + } + }), + 'useDisableAlignment selectedElements', + ) + + return React.useMemo(() => { + if (selectedViews.length === 0) { + return true + } + + return selectedElements.some(({ element, parent }) => { + return isAlignmentGroupDisabled(element, parent, orientation, selectedViews) + }) + }, [selectedViews, selectedElements, orientation]) +} + +export function isAlignmentGroupDisabled( + element: ElementInstanceMetadata | null, + parent: ElementInstanceMetadata | null, + orientation: 'horizontal' | 'vertical', + selection: ElementPath[], +): boolean { + if (element == null || parent == null) { + return true + } + + // grid cells have all alignments available + const isGridCell = MetadataUtils.isGridLayoutedContainer(parent) + if (isGridCell) { + return false + } + + // flex children have alignment enabled on the opposite orientation to their parent's flex direction + const isFlexChild = MetadataUtils.isFlexLayoutedContainer(parent) + if (isFlexChild) { + const flexDirection = MetadataUtils.getFlexDirection(parent) + return flexDirection === 'column' || flexDirection === 'column-reverse' + ? orientation === 'vertical' + : orientation === 'horizontal' + } + + // absolute elements have all alignments available, unless they are storyboard children or all + // selected elements are storyboard children + if ( + MetadataUtils.isPositionAbsolute(element) && + (!EP.isStoryboardChild(element.elementPath) || + (selection.length > 1 && selection.every((other) => EP.isStoryboardChild(other)))) + ) { + return false + } + + return true +} diff --git a/editor/src/components/inspector/use-grid-advanced-properties.tsx b/editor/src/components/inspector/use-grid-advanced-properties.tsx new file mode 100644 index 000000000000..1f00af867df8 --- /dev/null +++ b/editor/src/components/inspector/use-grid-advanced-properties.tsx @@ -0,0 +1,41 @@ +import React from 'react' +import { countSetProperties, MetadataUtils } from '../../core/model/element-metadata-utils' +import { Substores, useEditorState } from '../editor/store/store-hook' +import { isLeft, right } from '../../core/shared/either' +import { isJSXElement } from '../../core/shared/element-template' +import { styleP } from './inspector-common' + +/** + * These should match the props managed by the Advanced menu of the Grid inspector section. + */ +const advancedGridProps = [ + styleP('justifyContent'), + styleP('alignContent'), + styleP('justifyItems'), + styleP('alignItems'), +] + +export function useGridAdvancedPropertiesCount(): number { + const grid = useEditorState( + Substores.metadata, + (store) => { + if (store.editor.selectedViews.length !== 1) { + return null + } + const element = MetadataUtils.findElementByElementPath( + store.editor.jsxMetadata, + store.editor.selectedViews[0], + ) + return MetadataUtils.isGridLayoutedContainer(element) ? element : null + }, + 'useGridAdvancedProperties grids', + ) + + return React.useMemo(() => { + if (grid == null || isLeft(grid.element) || !isJSXElement(grid.element.value)) { + return 0 + } + + return countSetProperties(advancedGridProps, right(grid.element.value.props)) + }, [grid]) +} diff --git a/editor/src/components/inspector/widgets/inspector-modal.tsx b/editor/src/components/inspector/widgets/inspector-modal.tsx index f018628df9ed..a050a22bdad6 100644 --- a/editor/src/components/inspector/widgets/inspector-modal.tsx +++ b/editor/src/components/inspector/widgets/inspector-modal.tsx @@ -3,7 +3,6 @@ import * as ReactDOM from 'react-dom' import { useHandleCloseOnESCOrEnter } from '../common/inspector-utils' import { EditorID, PortalTargetID } from '../../../core/shared/utils' import { OnClickOutsideHOC } from '../../../uuiui' -import { ResizeObserver } from '../../canvas/dom-walker' export type InspectorModalProps = { offsetX: number diff --git a/editor/src/components/navigator/dependency-list.tsx b/editor/src/components/navigator/dependency-list.tsx index b17107b3c26a..0460ec7a355c 100644 --- a/editor/src/components/navigator/dependency-list.tsx +++ b/editor/src/components/navigator/dependency-list.tsx @@ -194,22 +194,24 @@ class DependencyListInner extends React.PureComponent { - if (fetchNodeModulesResult.dependenciesWithError.length > 0) { - this.packagesUpdateFailed( - `Failed to download the following dependencies: ${JSON.stringify( - fetchNodeModulesResult.dependenciesWithError.map((d) => d.name), - )}`, - fetchNodeModulesResult.dependenciesWithError[0]?.name, - ) - } - this.setState({ dependencyLoadingStatus: 'not-loading' }) - this.props.editorDispatch([ - EditorActions.updateNodeModulesContents(fetchNodeModulesResult.nodeModules), - ]) - }, - ) + void fetchNodeModules( + this.props.editorDispatch, + npmDependencies, + this.props.builtInDependencies, + ).then((fetchNodeModulesResult) => { + if (fetchNodeModulesResult.dependenciesWithError.length > 0) { + this.packagesUpdateFailed( + `Failed to download the following dependencies: ${JSON.stringify( + fetchNodeModulesResult.dependenciesWithError.map((d) => d.name), + )}`, + fetchNodeModulesResult.dependenciesWithError[0]?.name, + ) + } + this.setState({ dependencyLoadingStatus: 'not-loading' }) + this.props.editorDispatch([ + EditorActions.updateNodeModulesContents(fetchNodeModulesResult.nodeModules), + ]) + }) this.setState({ dependencyLoadingStatus: 'removing' }) } @@ -358,6 +360,7 @@ class DependencyListInner extends React.PureComponent((props) => { const dispatch = useDispatch() - const onClickTab = React.useCallback( + const onMouseDownTab = React.useCallback( (menuTab: LeftMenuTab) => { let actions: Array = [] actions.push(setLeftMenuTab(menuTab)) @@ -57,21 +57,21 @@ export const LeftPaneComponent = React.memo((props) => { [dispatch], ) - const onClickPagesTab = React.useCallback(() => { - onClickTab(LeftMenuTab.Pages) - }, [onClickTab]) + const onMouseDownPagesTab = React.useCallback(() => { + onMouseDownTab(LeftMenuTab.Pages) + }, [onMouseDownTab]) - const onClickProjectTab = React.useCallback(() => { - onClickTab(LeftMenuTab.Project) - }, [onClickTab]) + const onMouseDownProjectTab = React.useCallback(() => { + onMouseDownTab(LeftMenuTab.Project) + }, [onMouseDownTab]) - const onClickNavigatorTab = React.useCallback(() => { - onClickTab(LeftMenuTab.Navigator) - }, [onClickTab]) + const onMouseDownNavigatorTab = React.useCallback(() => { + onMouseDownTab(LeftMenuTab.Navigator) + }, [onMouseDownTab]) - const onClickGithubTab = React.useCallback(() => { - onClickTab(LeftMenuTab.Github) - }, [onClickTab]) + const onMouseDownGithubTab = React.useCallback(() => { + onMouseDownTab(LeftMenuTab.Github) + }, [onMouseDownTab]) const isMyProject = useIsMyProject() @@ -126,23 +126,23 @@ export const LeftPaneComponent = React.memo((props) => { , )} , )} diff --git a/editor/src/components/navigator/left-pane/roll-your-own-pane.tsx b/editor/src/components/navigator/left-pane/roll-your-own-pane.tsx deleted file mode 100644 index f574bff73575..000000000000 --- a/editor/src/components/navigator/left-pane/roll-your-own-pane.tsx +++ /dev/null @@ -1,189 +0,0 @@ -import React from 'react' -import { Button, colorTheme, FlexColumn, FlexRow, Section } from '../../../uuiui' -import { when } from '../../../utils/react-conditionals' -import { atom, useAtom, useSetAtom } from 'jotai' -import { UIGridRow } from '../../inspector/widgets/ui-grid-row' -import { atomWithStorage } from 'jotai/utils' -import { IS_TEST_ENVIRONMENT } from '../../../common/env-vars' -import { Ellipsis } from './github-pane/github-file-changes-list' - -const sections = ['Grid'] as const -type Section = (typeof sections)[number] - -type GridFeatures = { - dragVerbatim: boolean - dragMagnetic: boolean - dragRatio: boolean - animateShadowSnap: boolean - dotgrid: boolean - shadow: boolean - activeGridColor: string - dotgridColor: string - inactiveGridColor: string - shadowOpacity: number - activeGridBackground: string -} - -type RollYourOwnFeaturesTypes = { - Grid: GridFeatures -} - -type RollYourOwnFeatures = { - [K in Section]: RollYourOwnFeaturesTypes[K] -} - -const defaultRollYourOwnFeatures: RollYourOwnFeatures = { - Grid: { - dragVerbatim: false, - dragMagnetic: false, - dragRatio: true, - animateShadowSnap: false, - dotgrid: true, - shadow: true, - activeGridColor: '#0099ff77', - activeGridBackground: colorTheme.primary10.value, - dotgridColor: '#0099ffaa', - inactiveGridColor: '#00000033', - shadowOpacity: 0.1, - }, -} - -const ROLL_YOUR_OWN_FEATURES_KEY: string = 'roll-your-own-features' - -const rollYourOwnFeaturesAtom = IS_TEST_ENVIRONMENT - ? atom(defaultRollYourOwnFeatures) - : atomWithStorage(ROLL_YOUR_OWN_FEATURES_KEY, defaultRollYourOwnFeatures) - -export function useRollYourOwnFeatures() { - const [features] = useAtom(rollYourOwnFeaturesAtom) - const merged: RollYourOwnFeatures = { - Grid: { - ...defaultRollYourOwnFeatures.Grid, - ...features.Grid, - }, - } - return merged -} - -export const RollYourOwnFeaturesPane = React.memo(() => { - const [currentSection, setCurrentSection] = React.useState
(null) - - const onChangeSection = React.useCallback((e: React.ChangeEvent) => { - const maybeSectionValue = e.target.value as Section - if (sections.includes(maybeSectionValue)) { - setCurrentSection(maybeSectionValue) - } else { - setCurrentSection(null) - } - }, []) - - return ( - -
- - Roll Your Own - - - - - - {when(currentSection === 'Grid', )} -
-
- ) -}) -RollYourOwnFeaturesPane.displayName = 'RollYourOwnFeaturesPane' - -function getNewFeatureValueOrNull(currentValue: any, e: React.ChangeEvent) { - switch (typeof currentValue) { - case 'boolean': - return e.target.checked - case 'string': - return e.target.value - case 'number': - return parseFloat(e.target.value) - default: - return null - } -} - -const GridSection = React.memo(() => { - const features = useRollYourOwnFeatures() - const setFeatures = useSetAtom(rollYourOwnFeaturesAtom) - - const onChange = React.useCallback( - (feat: keyof GridFeatures) => (e: React.ChangeEvent) => { - const newValue = getNewFeatureValueOrNull(features.Grid[feat], e) - if (newValue != null) { - setFeatures({ - ...features, - Grid: { - ...features.Grid, - [feat]: newValue, - }, - }) - } - }, - [features, setFeatures], - ) - - const resetDefaults = React.useCallback(() => { - setFeatures(defaultRollYourOwnFeatures) - }, [setFeatures]) - - return ( - - - - - {Object.keys(defaultRollYourOwnFeatures.Grid).map((key) => { - const feat = key as keyof GridFeatures - const value = features.Grid[feat] ?? defaultRollYourOwnFeatures.Grid[feat] - return ( - - {feat} - {typeof value === 'boolean' ? ( - - ) : typeof value === 'string' ? ( - - ) : typeof value === 'number' ? ( - - ) : null} - - ) - })} - - ) -}) -GridSection.displayName = 'GridSection' diff --git a/editor/src/components/navigator/left-pane/settings-pane.spec.browser2.tsx b/editor/src/components/navigator/left-pane/settings-pane.spec.browser2.tsx index a9c311417bfb..ff48ed3d9619 100644 --- a/editor/src/components/navigator/left-pane/settings-pane.spec.browser2.tsx +++ b/editor/src/components/navigator/left-pane/settings-pane.spec.browser2.tsx @@ -7,7 +7,7 @@ import { StoryboardFilePath } from '../../editor/store/editor-state' async function renderRemixProject(project: PersistentModel) { const renderResult = await renderTestEditorWithModel(project, 'await-first-dom-report') - await renderResult.dispatch([runDOMWalker()], true) + await renderResult.dispatch([runDOMWalker(null)], true) return renderResult } @@ -55,6 +55,7 @@ describe('Settings Pane', () => { it('Project name is editable if the user can edit project', async () => { const renderResult = await renderRemixProject(project) await renderResult.dispatch([updateProjectServerState({ isMyProject: 'yes' })], true) + await renderResult.getDispatchFollowUpActionsFinished() const settingsTabButton = await renderResult.renderedDOM.findByText('Settings') @@ -71,6 +72,7 @@ describe('Settings Pane', () => { it('Project name is not editable if the user can not edit project', async () => { const renderResult = await renderRemixProject(project) await renderResult.dispatch([updateProjectServerState({ isMyProject: 'no' })], true) + await renderResult.getDispatchFollowUpActionsFinished() const settingsTabButton = await renderResult.renderedDOM.findByText('Settings') diff --git a/editor/src/components/navigator/left-pane/settings-pane.tsx b/editor/src/components/navigator/left-pane/settings-pane.tsx index 0b2b1d9f6352..76741a0993c3 100644 --- a/editor/src/components/navigator/left-pane/settings-pane.tsx +++ b/editor/src/components/navigator/left-pane/settings-pane.tsx @@ -27,7 +27,7 @@ import type { FeatureName } from '../../../utils/feature-switches' import { toggleFeatureEnabled, isFeatureEnabled, - AllFeatureNames, + getFeaturesToDisplay, } from '../../../utils/feature-switches' import json5 from 'json5' import { load } from '../../../components/editor/actions/actions' @@ -59,14 +59,23 @@ const themeOptions = [ const defaultTheme = themeOptions[0] export const FeatureSwitchesSection = React.memo(() => { - if (AllFeatureNames.length > 0) { + const [changeCount, setChangeCount] = React.useState(0) + // this replaces the 'forceRender' in the original implementation + const onFeatureChange = React.useCallback(() => setChangeCount(changeCount + 1), [changeCount]) + // eslint-disable-next-line react-hooks/exhaustive-deps + const featuresToDisplay = React.useMemo(() => getFeaturesToDisplay(), [changeCount]) + if (featuresToDisplay.length > 0) { return (

Experimental Toggle Features

- {AllFeatureNames.map((name) => ( - + {featuresToDisplay.map((name) => ( + ))}
) @@ -75,15 +84,14 @@ export const FeatureSwitchesSection = React.memo(() => { } }) -const FeatureSwitchRow = React.memo((props: { name: FeatureName }) => { +const FeatureSwitchRow = React.memo((props: { name: FeatureName; onFeatureChange: () => void }) => { const name = props.name + const onFeatureChange = props.onFeatureChange const id = `toggle-${name}` - const [changeCount, setChangeCount] = React.useState(0) - const forceRender = React.useCallback(() => setChangeCount(changeCount + 1), [changeCount]) const onChange = React.useCallback(() => { toggleFeatureEnabled(name) - forceRender() - }, [forceRender, name]) + onFeatureChange() + }, [name, onFeatureChange]) return ( diff --git a/editor/src/components/navigator/navigator-item/navigator-condensed-entry.tsx b/editor/src/components/navigator/navigator-item/navigator-condensed-entry.tsx index 60f9e0afce4e..6f208b183679 100644 --- a/editor/src/components/navigator/navigator-item/navigator-condensed-entry.tsx +++ b/editor/src/components/navigator/navigator-item/navigator-condensed-entry.tsx @@ -20,7 +20,7 @@ import { LayoutIcon } from './layout-icon' import { DataReferenceCartoucheControl } from '../../inspector/sections/component-section/data-reference-cartouche' import { NavigatorRowClickableWrapper, - useGetNavigatorClickActions, + useGetNavigatorMouseDownActions, } from './navigator-item-clickable-wrapper' import type { ThemeObject } from '../../../uuiui/styles/theme/theme-helpers' import { useNavigatorSelectionBoundsForEntry } from './use-navigator-selection-bounds-for-entry' @@ -341,18 +341,18 @@ const CondensedEntryItemContent = React.memo( ]) }, [props.entry, dispatch, highlightedViews]) - const getClickActions = useGetNavigatorClickActions( + const getMouseDownActions = useGetNavigatorMouseDownActions( props.entry.elementPath, props.selected, condensedNavigatorRow([props.entry], 'leaf', props.indentation), ) - const onClick = React.useCallback( + const onMouseDown = React.useCallback( (e: React.MouseEvent) => { e.stopPropagation() - dispatch(getClickActions(e)) + dispatch(getMouseDownActions(e)) }, - [dispatch, getClickActions], + [dispatch, getMouseDownActions], ) return ( @@ -374,7 +374,7 @@ const CondensedEntryItemContent = React.memo( }} onMouseOver={onMouseOver} onMouseOut={onMouseOut} - onClick={onClick} + onMouseDown={onMouseDown} >
EP.pathsEqual(targetPath, view)) }, [selectedViews, targetPath]) - const getActions = useGetNavigatorClickActions(targetPath, selected, props.row) + const getMouseDownActions = useGetNavigatorMouseDownActions(targetPath, selected, props.row) - const onClick = React.useCallback( + const onMouseDown = React.useCallback( (e: React.MouseEvent) => { - e.stopPropagation() - e.preventDefault() - - const actions = getActions(e) + const actions = getMouseDownActions(e) dispatch(actions) }, - [dispatch, getActions], + [dispatch, getMouseDownActions], ) return ( -
+
{props.children}
) @@ -68,11 +65,11 @@ export const NavigatorRowClickableWrapper = React.memo( ) NavigatorRowClickableWrapper.displayName = 'NavigatorRowClickableWrapper' -export function useGetNavigatorClickActions( +export function useGetNavigatorMouseDownActions( targetPath: ElementPath, selected: boolean, row: NavigatorRow, -) { +): (event: React.MouseEvent) => Array { const navigatorTargets = useRefEditorState(navigatorTargetsSelector) const selectedViews = useRefEditorState((store) => store.editor.selectedViews) const collapsedViews = useRefEditorState((store) => store.editor.navigator.collapsedViews) diff --git a/editor/src/components/navigator/navigator-item/navigator-item-components.tsx b/editor/src/components/navigator/navigator-item/navigator-item-components.tsx index 3fe002f4ea84..1686b791019d 100644 --- a/editor/src/components/navigator/navigator-item/navigator-item-components.tsx +++ b/editor/src/components/navigator/navigator-item/navigator-item-components.tsx @@ -180,7 +180,7 @@ interface VisiblityIndicatorProps { visibilityEnabled: boolean selected: boolean iconColor: IcnProps['color'] - onClick: () => void + onMouseDown: () => void } export const VisibilityIndicator: React.FunctionComponent< @@ -190,7 +190,7 @@ export const VisibilityIndicator: React.FunctionComponent< return ( - ))} - - ) - - function addInBranchNames(url: string): string { - if (url === '') { - return url - } else { - const parsedURL = new URL(url) - setBranchNameFromURL(parsedURL.searchParams) - return parsedURL.toString() - } - } - let floatingPreviewURL = - this.props.id == null - ? '' - : shareURLForProject(FLOATING_PREVIEW_BASE_URL, this.props.id, this.props.projectName) - floatingPreviewURL = addInBranchNames(floatingPreviewURL) - let iframePreviewURL = - this.props.id == null - ? '' - : `${floatingPreviewURL}?embedded=true&refreshCount=${this.state.refreshCount}` - iframePreviewURL = addInBranchNames(iframePreviewURL) - let popoutPreviewURL = - this.props.id == null - ? '' - : shareURLForProject(BASE_URL, this.props.id, this.props.projectName) - popoutPreviewURL = addInBranchNames(popoutPreviewURL) - - const iFrame = ( -