├── .eslintrc.js ├── .github ├── FUNDING.yml └── workflows │ ├── cd.yml │ └── ci.yml ├── .gitignore ├── .npmrc ├── .prettierrc.json ├── .vscode └── settings.json ├── LICENSE ├── README.md ├── lerna.json ├── package.json ├── packages ├── preactement │ ├── .npmrc │ ├── LICENSE │ ├── README.md │ ├── babel.config.js │ ├── jest.config.js │ ├── package.json │ ├── src │ │ ├── define.ts │ │ ├── model.ts │ │ └── parse.ts │ ├── tests │ │ ├── define.spec.tsx │ │ ├── parse.spec.ts │ │ └── setup.ts │ ├── tsconfig.json │ ├── webpack.config.ts │ └── yarn.lock ├── reactement │ ├── .npmrc │ ├── LICENSE │ ├── README.md │ ├── babel.config.js │ ├── jest.config.js │ ├── package.json │ ├── src │ │ ├── define.ts │ │ ├── model.ts │ │ └── parse.ts │ ├── tests │ │ ├── define.spec.tsx │ │ ├── parse.spec.ts │ │ └── setup.ts │ ├── tsconfig.json │ ├── webpack.config.ts │ └── yarn.lock └── shared │ ├── babel.config.js │ ├── jest.config.js │ ├── package.json │ ├── src │ ├── element.ts │ ├── index.ts │ ├── model.ts │ └── parse.ts │ ├── tests │ ├── element.spec.ts │ └── parse.spec.ts │ ├── tsconfig.json │ └── yarn.lock ├── tsconfig.json └── yarn.lock /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | env: { 3 | es2020: true, 4 | node: true, 5 | jest: true, 6 | }, 7 | globals: { 8 | 'jest/globals': true, 9 | }, 10 | extends: ['eslint:recommended', 'prettier'], 11 | parser: '@typescript-eslint/parser', 12 | parserOptions: { 13 | ecmaVersion: 11, 14 | sourceType: 'module', 15 | }, 16 | plugins: ['@typescript-eslint', 'jest'], 17 | rules: { 18 | 'react/display-name': 'off', 19 | 'no-unused-vars': 'off', 20 | 'no-void': 'off', 21 | 'linebreak-style': 'off', 22 | 'padding-line-between-statements': [ 23 | 'error', 24 | { blankLine: 'always', prev: 'const', next: 'function' }, 25 | { blankLine: 'always', prev: 'const', next: 'return' }, 26 | { blankLine: 'always', prev: 'const', next: 'if' }, 27 | { blankLine: 'always', prev: 'const', next: 'try' }, 28 | { blankLine: 'always', prev: 'const', next: 'expression' }, 29 | { blankLine: 'always', prev: '*', next: 'return' }, 30 | ], 31 | }, 32 | overrides: [ 33 | { 34 | files: ['**/*.ts'], 35 | parser: '@typescript-eslint/parser', 36 | rules: { 37 | 'no-undef': 'off', 38 | }, 39 | }, 40 | ], 41 | }; 42 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: [jahilldev] 2 | -------------------------------------------------------------------------------- /.github/workflows/cd.yml: -------------------------------------------------------------------------------- 1 | name: CD 2 | 3 | on: 4 | release: 5 | types: [published] 6 | 7 | jobs: 8 | build-test: 9 | name: Build & Test 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@v2 13 | - uses: actions/setup-node@v1 14 | with: 15 | node-version: '18.x' 16 | registry-url: https://registry.npmjs.org/ 17 | 18 | - name: Cached dependencies 19 | uses: actions/cache@v2 20 | with: 21 | path: ~/.npm 22 | key: npm-${{ hashFiles('yarn.lock') }} 23 | restore-keys: npm- 24 | 25 | - name: Installing dependencies 26 | run: yarn 27 | 28 | - name: Linting codebase 29 | run: yarn lint 30 | 31 | - name: Building packages 32 | run: yarn build 33 | 34 | - name: Running unit tests 35 | run: yarn test 36 | 37 | - name: Cache built packages 38 | uses: actions/cache@v2 39 | id: restore-build 40 | with: 41 | path: ./* 42 | key: ${{ github.sha }} 43 | 44 | publish: 45 | name: Publish 46 | runs-on: ubuntu-latest 47 | needs: build-test 48 | strategy: 49 | matrix: { package: ['preactement', 'reactement'] } 50 | steps: 51 | - uses: actions/checkout@v2 52 | - uses: actions/setup-node@v1 53 | with: 54 | node-version: '18.x' 55 | registry-url: https://registry.npmjs.org/ 56 | 57 | - uses: actions/cache@v2 58 | id: restore-build 59 | with: 60 | path: ./* 61 | key: ${{ github.sha }} 62 | 63 | - name: Check version changes 64 | uses: EndBug/version-check@v1 65 | id: check 66 | with: 67 | file-name: ./packages/${{ matrix.package }}/package.json 68 | file-url: https://cdn.jsdelivr.net/npm/${{ matrix.package }}@latest/package.json 69 | static-checking: localIsNew 70 | 71 | - name: Version update detected 72 | if: steps.check.outputs.changed == 'true' 73 | run: 'echo "Version change found! New version: ${{ steps.check.outputs.version }} (${{ steps.check.outputs.type }})"' 74 | 75 | - name: Publishing package 76 | if: steps.check.outputs.changed == 'true' 77 | run: npm publish 78 | working-directory: ./packages/${{ matrix.package }} 79 | env: 80 | NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} 81 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | pull_request: 5 | branches: 6 | - '**' 7 | push: 8 | branches: 9 | - main 10 | 11 | jobs: 12 | build-test: 13 | name: Build & Test 14 | runs-on: ubuntu-latest 15 | steps: 16 | - uses: actions/checkout@v2 17 | - uses: actions/setup-node@v1 18 | with: 19 | node-version: '18.x' 20 | - name: Cache node modules 21 | uses: actions/cache@v1 22 | env: 23 | cache-name: cache-node-modules 24 | with: 25 | path: ~/.npm 26 | # This uses the same name as the build-action so we can share the caches. 27 | key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/yarn.lock') }} 28 | restore-keys: | 29 | ${{ runner.os }}-build-${{ env.cache-name }}- 30 | ${{ runner.os }}-build- 31 | ${{ runner.os }}- 32 | - run: yarn 33 | - run: yarn lint 34 | - run: yarn build 35 | - run: yarn test 36 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | lerna-debug.log 3 | npm-debug.log 4 | yarn-error.log 5 | package-lock.json 6 | packages/*/dist 7 | packages/*/tests/coverage 8 | .DS_Store -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | registry=https://registry.npmjs.org 2 | -------------------------------------------------------------------------------- /.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "trailingComma": "es5", 3 | "tabWidth": 2, 4 | "semi": true, 5 | "printWidth": 98, 6 | "singleQuote": true, 7 | "eslintIntegration": false, 8 | "tslintIntegration": true 9 | } 10 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "prettier.requireConfig": true, 3 | "eslint.format.enable": true, 4 | "editor.formatOnSave": true, 5 | "editor.semanticHighlighting.enabled": false, 6 | "editor.tabSize": 3, 7 | "editor.codeActionsOnSave": { 8 | "source.fixAll.eslint": true 9 | } 10 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 James Hill 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # component-elements 2 | 3 | These packages provide the ability to use an HTML custom element as the root for your components. In addition, it allows the use of async code resolution if your custom element isn't immediately used, which is a great strategy for reducing code weight. 4 | 5 | # Getting Started 6 | 7 | Depending on your component library, visit the relevant package below and follow setup instructions: 8 | 9 | | Library | Package | 10 | | ------- | ---------------------------------------------------------------------------------------------------- | 11 | | Preact | [preactement](https://github.com/jahilldev/component-elements/tree/main/packages/preactement#readme) | 12 | | React | [reactement](https://github.com/jahilldev/component-elements/tree/main/packages/reactement#readme) | 13 | | Vue | supported natively! | 14 | 15 | # Acknowledgement 16 | 17 | This function takes _heavy_ inspiration from the excellent [preact-custom-element](https://github.com/preactjs/preact-custom-element). That library served as a starting point for this package, and all of the Preact guys deserve a massive dose of gratitude. I had slightly different needs, so decided to build this as part solution, part learning excersize. 18 | -------------------------------------------------------------------------------- /lerna.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.0.0", 3 | "packages": ["packages/*"], 4 | "useWorkspaces": true, 5 | "command": { 6 | "run": { 7 | "npmClient": "yarn" 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "component-elements", 3 | "private": true, 4 | "scripts": { 5 | "install": "lerna exec -- yarn", 6 | "clean": "lerna run clean", 7 | "prebuild": "lerna bootstrap --no-ci", 8 | "build": "run-s clean build:*", 9 | "build:packages": "lerna run build", 10 | "publish": "lerna publish", 11 | "lint": "lerna run lint", 12 | "test": "lerna run test" 13 | }, 14 | "workspaces": { 15 | "packages": [ 16 | "packages/*" 17 | ] 18 | }, 19 | "devDependencies": { 20 | "@babel/core": "^7.20.12", 21 | "@babel/preset-env": "^7.20.2", 22 | "@types/jest": "^29.4.0", 23 | "@types/node": "^18.11.18", 24 | "@typescript-eslint/eslint-plugin": "^5.50.0", 25 | "@typescript-eslint/parser": "^5.50.0", 26 | "babel-jest": "^29.4.1", 27 | "babel-loader": "^9.1.2", 28 | "eslint": "^8.33.0", 29 | "eslint-config-prettier": "^8.6.0", 30 | "eslint-plugin-jest": "^27.2.1", 31 | "jest": "^29.4.1", 32 | "jest-environment-jsdom": "^29.4.1", 33 | "lerna": "^6.4.1", 34 | "npm-run-all": "^4.1.5", 35 | "prettier": "^2.8.3", 36 | "rimraf": "^4.1.2", 37 | "ts-jest": "^29.0.5", 38 | "ts-loader": "^9.4.2", 39 | "ts-node": "^10.9.1", 40 | "typescript": "^4.9.5", 41 | "webpack": "^5.75.0", 42 | "webpack-cli": "^5.0.1", 43 | "webpack-node-externals": "^3.0.0" 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /packages/preactement/.npmrc: -------------------------------------------------------------------------------- 1 | registry=https://registry.npmjs.org 2 | -------------------------------------------------------------------------------- /packages/preactement/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 James Hill 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /packages/preactement/README.md: -------------------------------------------------------------------------------- 1 | # preactement 2 | 3 | Sometimes it's useful to let the DOM render our components when needed. Custom Elements are great at this. They provide various methods that can inform you when an element is "connected" or "disconnected" from the DOM. 4 | 5 | This package (only **2KB** GZipped) provides the ability to use an HTML custom element as the root for your components. In addition, it allows the use of async code resolution if your custom element isn't immediately used, which is a great strategy for reducing code weight. The exported function can also be used for hydration from SSR in Node. 6 | 7 | It's also a great way for you to integrate Preact into other server side frameworks that might render your HTML. 8 | 9 | > This package supports Preact. If you're using React, go to [reactement](https://github.com/jahilldev/component-elements/tree/main/packages/reactement#readme) for more info. 10 | 11 | # Getting Started 12 | 13 | Install with Yarn: 14 | 15 | ```bash 16 | $ yarn add preactement 17 | ``` 18 | 19 | Install with NPM: 20 | 21 | ```bash 22 | $ npm i preactement 23 | ``` 24 | 25 | # Using define() 26 | 27 | `preactement` exports one function, `define()`. This allows us to register a custom element via a provided key, and provide the component we'd like to render within. It can also generate a custom element with props ready for hydration if run on the server. 28 | 29 | The first argument **must be a valid custom element string**, e.g hyphenated. If you do not provide this, a prefix of `component-` will be applied to your element name. 30 | 31 | ## In the browser 32 | 33 | In order to register and render a component, you'll need to call `define()` with your chosen component, e.g: 34 | 35 | ```tsx 36 | import { define } from 'preactement'; 37 | import { HeroBanner } from './heroBanner'; 38 | 39 | /*[...]*/ 40 | 41 | define('hero-banner', () => HeroBanner); 42 | ``` 43 | 44 | This registers `` as a custom element. When that element exists on the page, `preactement` will render our component. 45 | 46 | If the custom element isn't present immediately, e.g it's created dynamically at some point in the future, we can provide an async function that explicitly resolves your component: 47 | 48 | ```tsx 49 | define('hero-banner', () => Promise.resolve(HeroBanner)); 50 | ``` 51 | 52 | This allows us to reduce the overall code in our bundle, and load the required component on demand when needed. 53 | 54 | You can either resolve the component from your async function, as seen above, _or_ `preactement` will try to infer the export key based on the provided tag name. For example: 55 | 56 | ```tsx 57 | import { define } from 'preactement'; 58 | 59 | /*[...]*/ 60 | 61 | define('hero-banner', () => import('./heroBanner')); 62 | ``` 63 | 64 | As the `heroBanner.ts` file is exporting the component as a key, e.g `export { HeroBanner };`, and this matches the tag name in kebab-case, e.g `hero-banner`, the component will be correctly rendered. 65 | 66 | ## On the server (SSR) 67 | 68 | You can also use `define()` to generate a custom element container if you're rendering your page in Node. When wrapping your component, e.g: 69 | 70 | ```ts 71 | define('hero-banner', () => HeroBanner); 72 | ``` 73 | 74 | A functional component is returned that you can include elsewhere in your app. For example: 75 | 76 | ```tsx 77 | import { define } from 'preactement'; 78 | 79 | /*[...]*/ 80 | 81 | const Component = define('hero-banner', () => HeroBanner); 82 | 83 | /*[...]*/ 84 | 85 | function HomePage() { 86 | return ( 87 |
88 | 89 |
90 | ); 91 | } 92 | ``` 93 | 94 | ## Properties 95 | 96 | If you're not running `preactement` on the server, you have several ways of defining props for your component. 97 | 98 | #### 1. Nested block of JSON: 99 | 100 | ```html 101 | 102 | 105 | 106 | ``` 107 | 108 | #### 2. A `props` attribute (this must be an encoded JSON string) 109 | 110 | ```html 111 | 112 | ``` 113 | 114 | #### 3. Custom attributes 115 | 116 | ```html 117 | 118 | ``` 119 | 120 | You'll need to define your custom attributes up front when using `define()`, e.g: 121 | 122 | ```ts 123 | define('hero-banner', () => HeroBanner, { attributes: ['title-text'] }); 124 | ``` 125 | 126 | These will then be merged into your components props in camelCase, so `title-text` will become `titleText`. 127 | 128 | ## HTML 129 | 130 | You can also provide nested HTML to your components `children` property. For example: 131 | 132 | ```html 133 | 134 |

Banner Title

135 |
136 | ``` 137 | 138 | This will correctly convert the `

` into virtual DOM nodes for use in your component, e.g: 139 | 140 | ```tsx 141 | /*[...]*/ 142 | 143 | function HeroBanner({ children }) { 144 | return
{children}
; 145 | } 146 | ``` 147 | 148 | ### Important 149 | 150 | Any HTML provided to the custom element **must be valid**; As we're using the DOM's native parser which is quite lax, any html passed that is not properly sanitised or structured might result in unusual bugs. For example: 151 | 152 | This will result in a Preact error: 153 | 154 | ```jsx 155 |

Hello 162 |

Hello

163 | ``` 164 | 165 | ### Slots 166 | 167 | `preactement` now supports the use of `<* slot="{key}" />` elements, to assign string values or full blocks of HTML to your component props. This is useful if your server defines layout rules that are outside of the scope of your component. For example, given the custom element below: 168 | 169 | ```html 170 | 171 |

Please Login

172 |
173 |

You have successfully logged in, congrats!

174 | Continue 175 |
176 |
177 | ``` 178 | 179 | All elements that have a `slot` attribute will be segmented into your components props, using the provided `slot="{value}"` as the key, e.g: 180 | 181 | ```tsx 182 | function LoginForm({ successMessage }) { 183 | const [isLoggedIn, setLoggedIn] = useState(false); 184 | 185 | return ( 186 | 187 | {isLoggedIn && successMessage} 188 |
setLoggedIn(true)}>{/*[...]*/}
189 |
190 | ); 191 | } 192 | ``` 193 | 194 | It's important to note that **slot keys will be normalised into camelCase**, for example: `slot="my-slot"` will be accessed via `mySlot` in your component's props. It's recommended to use camelCase for slot keys, but this isn't always possible. `preactement` will do it's best to handle all common casing conventions, e.g kebab-case, snake_case and PascalCase. Slot values can be either primitive strings, or full HTML structures, as seen in the example above. 195 | 196 | ## Options 197 | 198 | `define` has a third argument, "options". For example: 199 | 200 | ```javascript 201 | define('hero-banner', () => HeroBanner, { 202 | /*[options]*/ 203 | }); 204 | ``` 205 | 206 | ### attributes 207 | 208 | If you require custom attributes to be passed down to your component, you'll need to specify them in this array. For example: 209 | 210 | ```javascript 211 | define('hero-banner', () => HeroBanner, { attributes: ['banner-title'] }); 212 | ``` 213 | 214 | And the custom element will look like the following: 215 | 216 | ```html 217 | 218 | ``` 219 | 220 | ### formatProps 221 | 222 | This allows you to provide a function to process or format your props prior to the component being rendered. One use case is changing property casings. If the data provided by your server uses Pascal, but your components make use of the standard camelCase, this function will allow you to consolidate them. 223 | 224 | ### wrapComponent 225 | 226 | If you need to wrap your component prior to render with a higher order function, you can provide it here. For example, if you asynchronously resolve your component, but also make use of Redux, you'll need to provide a `wrapComponent` function to apply the Provider HOC etc. It can also be useful for themeing, or other use cases. 227 | 228 | ## Useful things 229 | 230 | By default, all components will be provided with a `parent` prop. This is a reference to the root element that the component has been rendered within. This can be useful when working with Web Components, or you wish to apply changes to the custom element. This will **only be defined when run on the client**. 231 | 232 | ## ES5 Support 233 | 234 | To support ES5 or older browsers, like IE11, you'll need to either transpile `preactement`, or import the ES5 version via `preactement/es5`, while also installing the official Web Component [Custom Element polyfill](https://www.npmjs.com/package/@webcomponents/custom-elements). Once installed, you'll need to import the following at the very top of your entry files: 235 | 236 | ```javascript 237 | import '@webcomponents/custom-elements'; 238 | import '@webcomponents/custom-elements/src/native-shim'; 239 | ``` 240 | 241 | # Acknowledgement 242 | 243 | This function takes _heavy_ inspiration from the excellent [preact-custom-element](https://github.com/preactjs/preact-custom-element). That library served as a starting point for this package, and all of the Preact guys deserve a massive dose of gratitude. I had slightly different needs, so decided to build this as part solution, part learning excersize. 244 | -------------------------------------------------------------------------------- /packages/preactement/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | [ 4 | '@babel/preset-env', 5 | { 6 | targets: { 7 | node: 'current', 8 | }, 9 | }, 10 | ], 11 | ], 12 | }; 13 | -------------------------------------------------------------------------------- /packages/preactement/jest.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('ts-jest').JestConfigWithTsJest} */ 2 | 3 | /* ----------------------------------- 4 | * 5 | * Jest 6 | * 7 | * -------------------------------- */ 8 | 9 | module.exports = { 10 | testEnvironment: 'jsdom', 11 | globals: { __DEV__: true }, 12 | roots: [''], 13 | collectCoverage: true, 14 | collectCoverageFrom: ['/src/**/*.{ts,tsx}'], 15 | coverageDirectory: '/tests/coverage', 16 | coveragePathIgnorePatterns: ['/node_modules/', '(.*).d.ts'], 17 | setupFilesAfterEnv: ['/tests/setup.ts'], 18 | coverageThreshold: { 19 | global: { 20 | statements: 97, 21 | branches: 86, 22 | functions: 100, 23 | lines: 97, 24 | }, 25 | }, 26 | transform: { 27 | '^.+\\js$': 'babel-jest', 28 | '^.+\\.tsx?$': 'ts-jest', 29 | }, 30 | transformIgnorePatterns: ['/node_modules/*'], 31 | }; 32 | -------------------------------------------------------------------------------- /packages/preactement/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "preactement", 3 | "version": "1.9.0", 4 | "author": "James Hill ", 5 | "homepage": "https://github.com/jahilldev/component-elements/tree/main/packages/preactement#readme", 6 | "license": "MIT", 7 | "main": "./dist/define.es5.js", 8 | "types": "./dist/define.d.ts", 9 | "engines": { 10 | "node": ">=10" 11 | }, 12 | "exports": { 13 | ".": "./dist/define.js", 14 | "./es5": { 15 | "type": "./dist/define.d.ts", 16 | "import": "./dist/define.es5.js", 17 | "require": "./dist/define.es5.js" 18 | } 19 | }, 20 | "typesVersions": { 21 | "*": { 22 | "es5": [ 23 | "./dist/define.d.ts" 24 | ] 25 | } 26 | }, 27 | "keywords": [ 28 | "preact", 29 | "custom elements", 30 | "web components", 31 | "virtual dom", 32 | "partial hydration", 33 | "universal", 34 | "isomorphic", 35 | "hydrate", 36 | "component" 37 | ], 38 | "directories": { 39 | "lib": "dist", 40 | "test": "tests" 41 | }, 42 | "files": [ 43 | "dist" 44 | ], 45 | "repository": { 46 | "type": "git", 47 | "url": "git+https://github.com/jahilldev/component-elements.git" 48 | }, 49 | "scripts": { 50 | "start": "run-s clean watch", 51 | "build": "webpack --mode=production", 52 | "watch": "webpack --watch", 53 | "clean": "rimraf ./dist", 54 | "lint": "eslint", 55 | "test": "jest" 56 | }, 57 | "bugs": { 58 | "url": "https://github.com/jahilldev/component-elements/issues" 59 | }, 60 | "peerDependencies": { 61 | "preact": "10.x" 62 | }, 63 | "devDependencies": { 64 | "@component-elements/shared": "1.0.0", 65 | "@types/enzyme": "^3.10.12", 66 | "enzyme": "^3.11.0", 67 | "enzyme-adapter-preact-pure": "^4.1.0", 68 | "preact": "^10.11.3" 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /packages/preactement/src/define.ts: -------------------------------------------------------------------------------- 1 | import { h, render, ComponentFactory, FunctionComponent } from 'preact'; 2 | import { 3 | IProps, 4 | ErrorTypes, 5 | CustomElement, 6 | isPromise, 7 | parseJson, 8 | getElementTag, 9 | getPropKey, 10 | getElementAttributes, 11 | getAsyncComponent, 12 | } from '@component-elements/shared'; 13 | import { parseHtml } from './parse'; 14 | import { IOptions, ComponentFunction } from './model'; 15 | 16 | /* ----------------------------------- 17 | * 18 | * Define 19 | * 20 | * -------------------------------- */ 21 | 22 | function define

( 23 | tagName: string, 24 | child: ComponentFunction

, 25 | options: IOptions = {} 26 | ): FunctionComponent

{ 27 | const { wrapComponent } = options; 28 | const preRender = typeof window === 'undefined'; 29 | const elementTag = getElementTag(tagName); 30 | 31 | if (!preRender) { 32 | customElements.define(elementTag, setupElement(child, options)); 33 | 34 | return; 35 | } 36 | 37 | const content = child(); 38 | 39 | if (isPromise(content)) { 40 | throw new Error(`${ErrorTypes.Promise} : <${tagName}>`); 41 | } 42 | 43 | let component = content; 44 | const attributes: Record = { server: true }; 45 | 46 | if (wrapComponent) { 47 | component = wrapComponent(content); 48 | } 49 | 50 | return (props: P) => 51 | h(elementTag, attributes, [ 52 | h('script', { 53 | type: 'application/json', 54 | dangerouslySetInnerHTML: { __html: JSON.stringify(props) }, 55 | }), 56 | h(component, props), 57 | ]); 58 | } 59 | 60 | /* ----------------------------------- 61 | * 62 | * Setup 63 | * 64 | * -------------------------------- */ 65 | 66 | function setupElement(component: ComponentFunction, options: IOptions = {}): any { 67 | const { attributes = [] } = options; 68 | 69 | if (typeof Reflect !== 'undefined' && Reflect.construct) { 70 | const CustomElement = function () { 71 | const element = Reflect.construct(HTMLElement, [], CustomElement); 72 | 73 | element.__mounted = false; 74 | element.__component = component; 75 | element.__properties = {}; 76 | element.__slots = {}; 77 | element.__children = void 0; 78 | element.__options = options; 79 | 80 | return element; 81 | }; 82 | 83 | CustomElement.observedAttributes = ['props', ...attributes]; 84 | 85 | CustomElement.prototype = Object.create(HTMLElement.prototype); 86 | CustomElement.prototype.constructor = CustomElement; 87 | CustomElement.prototype.connectedCallback = onConnected; 88 | CustomElement.prototype.attributeChangedCallback = onAttributeChange; 89 | CustomElement.prototype.disconnectedCallback = onDisconnected; 90 | 91 | return CustomElement; 92 | } 93 | 94 | return class CustomElement extends HTMLElement { 95 | __mounted = false; 96 | __component = component; 97 | __properties = {}; 98 | __slots = {}; 99 | __children = void 0; 100 | __options = options; 101 | 102 | static observedAttributes = ['props', ...attributes]; 103 | 104 | public connectedCallback() { 105 | onConnected.call(this); 106 | } 107 | 108 | public attributeChangedCallback(...args) { 109 | onAttributeChange.call(this, ...args); 110 | } 111 | 112 | public disconnectedCallback() { 113 | onDisconnected.call(this); 114 | } 115 | }; 116 | } 117 | 118 | /* ----------------------------------- 119 | * 120 | * Connected 121 | * 122 | * -------------------------------- */ 123 | 124 | function onConnected(this: CustomElement) { 125 | const attributes = getElementAttributes.call(this); 126 | const props = this.getAttribute('props'); 127 | const json = this.querySelector('[type="application/json"]'); 128 | const data = parseJson.call(this, props || json?.innerHTML || '{}'); 129 | 130 | json?.remove(); 131 | 132 | let children = this.__children; 133 | 134 | if (!this.__mounted && !this.hasAttribute('server')) { 135 | children = h(parseHtml.call(this), {}); 136 | } 137 | 138 | this.__properties = { ...this.__slots, ...data, ...attributes }; 139 | this.__children = children || []; 140 | 141 | this.removeAttribute('server'); 142 | 143 | const response = this.__component(); 144 | const renderer = (result: ComponentFactory) => finaliseComponent.call(this, result); 145 | 146 | if (isPromise(response)) { 147 | getAsyncComponent(response, this.tagName).then(renderer); 148 | 149 | return; 150 | } 151 | 152 | renderer(response); 153 | } 154 | 155 | /* ----------------------------------- 156 | * 157 | * Attribute 158 | * 159 | * -------------------------------- */ 160 | 161 | function onAttributeChange(this: CustomElement, name: string, original: string, updated: string) { 162 | if (!this.__mounted) { 163 | return; 164 | } 165 | 166 | updated = updated == null ? void 0 : updated; 167 | 168 | let props = this.__properties; 169 | 170 | if (name === 'props') { 171 | props = { ...props, ...parseJson.call(this, updated) }; 172 | } else { 173 | props[getPropKey(name)] = updated; 174 | } 175 | 176 | this.__properties = props; 177 | 178 | render(h(this.__instance, { ...props, parent: this, children: this.__children }), this); 179 | } 180 | 181 | /* ----------------------------------- 182 | * 183 | * Disconnected 184 | * 185 | * -------------------------------- */ 186 | 187 | function onDisconnected(this: CustomElement) { 188 | render(null, this); 189 | } 190 | 191 | /* ----------------------------------- 192 | * 193 | * Finalise 194 | * 195 | * -------------------------------- */ 196 | 197 | function finaliseComponent(this: CustomElement, component: ComponentFactory) { 198 | const { tagName } = this; 199 | const { wrapComponent } = this.__options; 200 | 201 | if (!component) { 202 | console.error(ErrorTypes.Missing, `: <${tagName.toLowerCase()}>`); 203 | 204 | return; 205 | } 206 | 207 | if (wrapComponent) { 208 | component = wrapComponent(component); 209 | } 210 | 211 | this.__instance = component; 212 | this.__mounted = true; 213 | 214 | const props = { 215 | ...this.__properties, 216 | parent: this, 217 | children: this.__children, 218 | }; 219 | 220 | render(h(component, props), this); 221 | } 222 | 223 | /* ----------------------------------- 224 | * 225 | * Export 226 | * 227 | * -------------------------------- */ 228 | 229 | export { define }; 230 | -------------------------------------------------------------------------------- /packages/preactement/src/model.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFactory } from 'preact'; 2 | 3 | /* ----------------------------------- 4 | * 5 | * Types 6 | * 7 | * -------------------------------- */ 8 | 9 | type ComponentFunction

= () => ComponentResult

; 10 | type ComponentResult

= ComponentFactory

| ComponentAsync

; 11 | type ComponentAsync

= 12 | | Promise> 13 | | Promise<{ [index: string]: ComponentFactory

}>; 14 | 15 | /* ----------------------------------- 16 | * 17 | * IOptions 18 | * 19 | * -------------------------------- */ 20 | 21 | interface IOptions { 22 | attributes?: string[]; 23 | formatProps?:

(props: P) => P; 24 | wrapComponent?:

(child: ComponentFactory

) => ComponentFactory

; 25 | } 26 | 27 | /* ----------------------------------- 28 | * 29 | * Export 30 | * 31 | * -------------------------------- */ 32 | 33 | export { IOptions, ComponentFunction, ComponentResult, ComponentAsync }; 34 | -------------------------------------------------------------------------------- /packages/preactement/src/parse.ts: -------------------------------------------------------------------------------- 1 | import { h, ComponentFactory, Fragment } from 'preact'; 2 | import { 3 | CustomElement, 4 | getDocument, 5 | getAttributeObject, 6 | selfClosingTags, 7 | getPropKey, 8 | } from '@component-elements/shared'; 9 | 10 | /* ----------------------------------- 11 | * 12 | * parseHtml 13 | * 14 | * -------------------------------- */ 15 | 16 | function parseHtml(this: CustomElement): ComponentFactory<{}> { 17 | const dom = getDocument(this.innerHTML); 18 | 19 | if (!dom) { 20 | return void 0; 21 | } 22 | 23 | const result = convertToVDom.call(this, dom); 24 | 25 | return () => result; 26 | } 27 | 28 | /* ----------------------------------- 29 | * 30 | * convertToVDom 31 | * 32 | * -------------------------------- */ 33 | 34 | function convertToVDom(this: CustomElement, node: Element) { 35 | if (node.nodeType === 3) { 36 | return node.textContent || ''; 37 | } 38 | 39 | if (node.nodeType !== 1) { 40 | return null; 41 | } 42 | 43 | const nodeName = String(node.nodeName).toLowerCase(); 44 | const childNodes = Array.from(node.childNodes); 45 | 46 | const children = () => childNodes.map((child) => convertToVDom.call(this, child)); 47 | const { slot, ...props } = getAttributeObject(node.attributes); 48 | 49 | if (nodeName === 'script') { 50 | return null; 51 | } 52 | 53 | if (nodeName === 'body') { 54 | return h(Fragment, {}, children()); 55 | } 56 | 57 | if (selfClosingTags.includes(nodeName)) { 58 | return h(nodeName, props); 59 | } 60 | 61 | if (slot) { 62 | this.__slots[getPropKey(slot)] = getSlotChildren(children()); 63 | 64 | return null; 65 | } 66 | 67 | return h(nodeName, props, children()); 68 | } 69 | 70 | /* ----------------------------------- 71 | * 72 | * getSlotChildren 73 | * 74 | * -------------------------------- */ 75 | 76 | function getSlotChildren(children: JSX.Element[]) { 77 | const isString = (item) => typeof item === 'string'; 78 | 79 | if (children.every(isString)) { 80 | return children.join(' '); 81 | } 82 | 83 | return h(Fragment, {}, children); 84 | } 85 | 86 | /* ----------------------------------- 87 | * 88 | * Export 89 | * 90 | * -------------------------------- */ 91 | 92 | export { parseHtml }; 93 | -------------------------------------------------------------------------------- /packages/preactement/tests/define.spec.tsx: -------------------------------------------------------------------------------- 1 | import { h, Fragment, ComponentFactory } from 'preact'; 2 | import { mount } from 'enzyme'; 3 | import { define } from '../src/define'; 4 | 5 | /* ----------------------------------- 6 | * 7 | * Promises 8 | * 9 | * -------------------------------- */ 10 | 11 | function flushPromises() { 12 | return new Promise((resolve) => setTimeout(resolve, 0)); 13 | } 14 | 15 | /* ----------------------------------- 16 | * 17 | * IProps 18 | * 19 | * -------------------------------- */ 20 | 21 | interface IProps { 22 | customTitle?: string; 23 | value: string; 24 | children?: any; 25 | } 26 | 27 | /* ----------------------------------- 28 | * 29 | * Component 30 | * 31 | * -------------------------------- */ 32 | 33 | function Message({ customTitle, value, children }: IProps) { 34 | return ( 35 | 36 | {customTitle &&

{customTitle}

} 37 | {value} 38 | {children} 39 | 40 | ); 41 | } 42 | 43 | /* ----------------------------------- 44 | * 45 | * Define 46 | * 47 | * -------------------------------- */ 48 | 49 | describe('define()', () => { 50 | const { document } = globalThis; 51 | const errorSpy = jest.spyOn(console, 'error').mockImplementation(() => {}); 52 | 53 | describe('when run in the browser', () => { 54 | let root; 55 | 56 | beforeEach(() => { 57 | root = document.createElement('div'); 58 | document.body.appendChild(root); 59 | }); 60 | 61 | afterEach(() => { 62 | document.body.removeChild(root); 63 | }); 64 | 65 | it('validates tag name value with prefix if needed', () => { 66 | const props = { value: 'propsValue' }; 67 | 68 | define('message', () => Message); 69 | 70 | const element = document.createElement('component-message'); 71 | 72 | element.setAttribute('props', JSON.stringify(props)); 73 | 74 | root.appendChild(element); 75 | 76 | expect(root.innerHTML).toContain(`${props.value}`); 77 | }); 78 | 79 | it('renders component correctly when from props attribute', async () => { 80 | const props = { value: 'propsValue' }; 81 | 82 | define('message-one', () => Message); 83 | 84 | const element = document.createElement('message-one'); 85 | 86 | element.setAttribute('props', JSON.stringify(props)); 87 | 88 | root.appendChild(element); 89 | 90 | expect(root.innerHTML).toContain(`${props.value}`); 91 | }); 92 | 93 | it('renders component correctly when from json script block', async () => { 94 | const props = { value: 'jsonValue' }; 95 | const json = ``; 96 | 97 | define('message-two', () => Message); 98 | 99 | const element = document.createElement('message-two'); 100 | 101 | element.innerHTML = json; 102 | 103 | root.appendChild(element); 104 | 105 | expect(root.innerHTML).toContain(`${props.value}`); 106 | }); 107 | 108 | it('sets contained HTML as children prop when not server rendered', async () => { 109 | const props = { value: 'childMarkup' }; 110 | const json = ``; 111 | const html = '

Testing


'; 112 | 113 | define('message-three', () => Message); 114 | 115 | const element = document.createElement('message-three'); 116 | 117 | element.innerHTML = json + html; 118 | 119 | root.appendChild(element); 120 | 121 | expect(root.innerHTML).toContain(`${props.value}${html}`); 122 | }); 123 | 124 | it('does not use contained HTML if server rendered', async () => { 125 | const props = { value: 'serverRender' }; 126 | const json = ``; 127 | const html = '

Server rendered!

'; 128 | 129 | define('message-four', () => Message); 130 | 131 | const element = document.createElement('message-four'); 132 | 133 | element.setAttribute('server', ''); 134 | element.innerHTML = json + html; 135 | 136 | root.appendChild(element); 137 | 138 | expect(root.innerHTML).toContain(`${props.value}`); 139 | }); 140 | 141 | it('renders component asynchronously when provided', async () => { 142 | const props = { value: 'asyncValue' }; 143 | const json = ``; 144 | 145 | define('message-five', () => Promise.resolve(Message)); 146 | 147 | const element = document.createElement('message-five'); 148 | 149 | element.innerHTML = json; 150 | 151 | root.appendChild(element); 152 | 153 | await flushPromises(); 154 | 155 | expect(root.innerHTML).toContain(`${props.value}`); 156 | }); 157 | 158 | it('tries to infer the component if not explicitly returned', async () => { 159 | const props = { value: 'inferValue' }; 160 | const json = ``; 161 | 162 | define('message-six', () => Promise.resolve({ MessageSix: Message })); 163 | 164 | const element = document.createElement('message-six'); 165 | 166 | element.innerHTML = json; 167 | 168 | root.appendChild(element); 169 | 170 | await flushPromises(); 171 | 172 | expect(root.innerHTML).toContain(`${props.value}`); 173 | }); 174 | 175 | it('merges defined attributes in array with component props', () => { 176 | const customTitle = 'customTitle'; 177 | const props = { value: 'attrProps' }; 178 | const json = ``; 179 | 180 | define('message-seven', () => Message, { attributes: ['custom-title'] }); 181 | 182 | const element = document.createElement('message-seven'); 183 | 184 | element.setAttribute('custom-title', customTitle); 185 | element.innerHTML = json; 186 | 187 | root.appendChild(element); 188 | 189 | expect(root.innerHTML).toContain(`

${customTitle}

${props.value}`); 190 | }); 191 | 192 | it('errors if component cannot be found in function', async () => { 193 | define('message-eight', () => Promise.resolve({ Message })); 194 | 195 | const element = document.createElement('message-eight'); 196 | 197 | root.appendChild(element); 198 | 199 | await flushPromises(); 200 | 201 | expect(errorSpy).toBeCalled(); 202 | expect(element.innerHTML).toEqual(''); 203 | }); 204 | 205 | it('updates component props when attributes are changed', () => { 206 | const customTitle = 'customTitle'; 207 | const updatedProp = 'updated!'; 208 | const props = { value: 'attrUpdate' }; 209 | const html = ''; 210 | 211 | define('message-nine', () => Message, { attributes: ['custom-title'] }); 212 | 213 | const element = document.createElement('message-nine'); 214 | 215 | element.setAttribute('custom-title', customTitle); 216 | element.setAttribute('props', JSON.stringify(props)); 217 | 218 | element.innerHTML = html; 219 | 220 | root.appendChild(element); 221 | 222 | expect(root.innerHTML).toContain(`

${customTitle}

${props.value}${html}`); 223 | 224 | element.setAttribute('custom-title', ''); 225 | element.setAttribute('props', JSON.stringify({ ...props, value: updatedProp })); 226 | 227 | expect(root.innerHTML).toContain(`${updatedProp}${html}`); 228 | }); 229 | 230 | it('wraps component in an HOC if provided', () => { 231 | const props = { value: 'wrapComponent' }; 232 | const json = ``; 233 | 234 | const wrapComponent = (child: ComponentFactory) => (props: any) => 235 | h('section', {}, h(child, props)); 236 | 237 | define('message-ten', () => Message, { wrapComponent }); 238 | 239 | const element = document.createElement('message-ten'); 240 | 241 | element.innerHTML = json; 242 | 243 | root.appendChild(element); 244 | 245 | expect(root.innerHTML).toContain(`
${props.value}
`); 246 | }); 247 | 248 | it('correctly passes props through formatProps if provided', () => { 249 | const props = { Value: 'formatProps' }; 250 | const json = ``; 251 | 252 | const formatProps = (props: any) => { 253 | const keys = Object.keys(props); 254 | 255 | return keys.reduce((result, key) => { 256 | result[key.toLowerCase()] = props[key]; 257 | 258 | return result; 259 | }, {}); 260 | }; 261 | 262 | define('message-eleven', () => Message, { formatProps }); 263 | 264 | const element = document.createElement('message-eleven'); 265 | 266 | element.innerHTML = json; 267 | 268 | root.appendChild(element); 269 | 270 | expect(root.innerHTML).toContain(`${props.Value}`); 271 | }); 272 | 273 | it('correctly segments <* slot="{key}" /> elements into props', () => { 274 | const customTitle = 'customTitle'; 275 | const html = `
${customTitle}
`; 276 | 277 | define('message-twelve', () => Message); 278 | 279 | const element = document.createElement('message-twelve'); 280 | 281 | element.innerHTML = html; 282 | 283 | root.appendChild(element); 284 | 285 | expect(root.innerHTML).toContain(`

${customTitle}

`); 286 | }); 287 | 288 | it('correctly caches children when moved in the DOM', () => { 289 | const customTitle = 'customTitle'; 290 | const customText = 'Lorem ipsum dolor'; 291 | const html = `
${customTitle}

${customText}

`; 292 | 293 | define('message-thirteen', () => Message); 294 | 295 | const element = document.createElement('message-thirteen'); 296 | const wrapper = document.createElement('main'); 297 | 298 | element.innerHTML = html; 299 | 300 | root.appendChild(element); 301 | 302 | element.remove(); 303 | 304 | expect(root.innerHTML).toContain(''); 305 | 306 | root.appendChild(wrapper); 307 | wrapper.appendChild(element); 308 | 309 | expect(root.innerHTML).toContain(`

${customTitle}

${customText}

`); 310 | }); 311 | 312 | it('hydrates server rendered markup corretly without re-rendering child elements', () => { 313 | const customTitle = 'customTitle'; 314 | const props = { value: 'attrProps' }; 315 | const json = ``; 316 | const html = `

${customTitle}

${props.value}`; 317 | 318 | define('message-fourteen', () => Message, { attributes: ['custom-title'] }); 319 | 320 | const element = document.createElement('message-fourteen'); 321 | 322 | element.setAttribute('custom-title', customTitle); 323 | element.setAttribute('server', ''); 324 | element.innerHTML = json + html; 325 | const originalH2 = element.querySelector('h2'); 326 | 327 | root.appendChild(element); 328 | 329 | expect(element.innerHTML).toEqual(html); 330 | expect(element.querySelector('h2')).toBe(originalH2); 331 | }); 332 | }); 333 | 334 | describe('when run in the browser (no "Reflect.construct")', () => { 335 | const { document, Reflect } = globalThis; 336 | let root; 337 | 338 | beforeAll(() => { 339 | delete (globalThis as any).Reflect; 340 | }); 341 | 342 | beforeEach(() => { 343 | root = document.createElement('div'); 344 | document.body.appendChild(root); 345 | }); 346 | 347 | afterEach(() => { 348 | document.body.removeChild(root); 349 | }); 350 | 351 | afterAll(() => { 352 | globalThis.Reflect = Reflect; 353 | }); 354 | 355 | it('renders component correctly', () => { 356 | const customTitle = 'customTitle'; 357 | const props = { value: 'attrUpdate' }; 358 | const json = ``; 359 | const html = ''; 360 | 361 | define('message-class', () => Message, { attributes: ['custom-title'] }); 362 | 363 | const element = document.createElement('message-class'); 364 | 365 | element.setAttribute('custom-title', customTitle); 366 | element.innerHTML = json + html; 367 | 368 | root.appendChild(element); 369 | 370 | expect(root.innerHTML).toContain(`

${customTitle}

${props.value}${html}`); 371 | 372 | element.setAttribute('custom-title', ''); 373 | 374 | expect(root.innerHTML).toContain(`${props.value}${html}`); 375 | }); 376 | }); 377 | 378 | describe('when run on the server', () => { 379 | const { window } = globalThis; 380 | 381 | beforeAll(() => { 382 | delete (globalThis as any).window; 383 | }); 384 | 385 | afterAll(() => { 386 | globalThis.window = window; 387 | }); 388 | 389 | it('returns the correct markup', () => { 390 | const props = { value: 'serverValue' }; 391 | const component = define('message-one', () => Message); 392 | 393 | const instance = mount(h(component, props) as any); 394 | 395 | expect(instance.find('message-one').length).toEqual(1); 396 | expect(instance.find('message-one').prop('server')).toEqual(true); 397 | expect(instance.find('em').text()).toEqual(props.value); 398 | }); 399 | 400 | it('throws an error when used with a promise', () => { 401 | expect(() => define('message-two', () => Promise.resolve(Message))).toThrow(); 402 | }); 403 | 404 | it('includes a json script block with props', () => { 405 | const props = { value: 'serverValue' }; 406 | const component = define('message-three', () => Message); 407 | 408 | const instance = mount(h(component, props) as any); 409 | 410 | expect(instance.find('script').text()).toEqual(JSON.stringify(props)); 411 | }); 412 | }); 413 | }); 414 | -------------------------------------------------------------------------------- /packages/preactement/tests/parse.spec.ts: -------------------------------------------------------------------------------- 1 | import { h } from 'preact'; 2 | import { mount } from 'enzyme'; 3 | import { parseHtml } from '../src/parse'; 4 | 5 | /* ----------------------------------- 6 | * 7 | * Variables 8 | * 9 | * -------------------------------- */ 10 | 11 | const testHeading = 'testHeading'; 12 | const testWhitespace = ' '; 13 | const testHtml = `

${testHeading}


Hello

`; 14 | const testScript = ``; 15 | 16 | /* ----------------------------------- 17 | * 18 | * Parse 19 | * 20 | * -------------------------------- */ 21 | 22 | describe('parse', () => { 23 | describe('parseHtml()', () => { 24 | it('should correctly handle misformed html', () => { 25 | const testText = 'testText'; 26 | const result = parseHtml.call({ innerHTML: `

${testText}` }); 27 | const instance = mount(h(result, {}) as any); 28 | 29 | expect(instance.html()).toEqual(`

${testText}

`); 30 | }); 31 | 32 | it('handles text values witin custom element', () => { 33 | const result = parseHtml.call({ innerHTML: testHeading }); 34 | const instance = mount(h(result, {}) as any); 35 | 36 | expect(instance.text()).toEqual(testHeading); 37 | }); 38 | 39 | it('retains whitespace within custom element', () => { 40 | const result = parseHtml.call({ innerHTML: testWhitespace }); 41 | const instance = mount(h(result, {}) as any); 42 | 43 | expect(instance.text()).toEqual(testWhitespace); 44 | expect(instance.html()).toEqual(testWhitespace); 45 | }); 46 | 47 | it('removes script blocks for security', () => { 48 | const result = parseHtml.call({ innerHTML: testScript }); 49 | const instance = mount(h(result, {}) as any); 50 | 51 | expect(instance.text()).toEqual(''); 52 | }); 53 | 54 | it('correctly converts an HTML string into a VDom tree', () => { 55 | const result = parseHtml.call({ innerHTML: testHtml }); 56 | const instance = mount(h(result, {}) as any); 57 | 58 | expect(instance.find('h1').text()).toEqual(testHeading); 59 | }); 60 | 61 | describe('slots', () => { 62 | const testKey = 'testSlot'; 63 | 64 | it('should remove <* slot="{key}"> and apply to props', () => { 65 | const slots = {}; 66 | const slotValue = 'slotValue'; 67 | 68 | const slotHtml = `${slotValue}`; 69 | const headingHtml = `

${testHeading}

`; 70 | const testHtml = `
${headingHtml}${slotHtml}
`; 71 | 72 | const result = parseHtml.call({ innerHTML: testHtml, __slots: slots }); 73 | const instance = mount(h(result, {}) as any); 74 | 75 | expect(instance.html()).toEqual(`
${headingHtml}
`); 76 | expect(slots).toEqual({ [testKey]: slotValue }); 77 | }); 78 | }); 79 | }); 80 | }); 81 | -------------------------------------------------------------------------------- /packages/preactement/tests/setup.ts: -------------------------------------------------------------------------------- 1 | import { configure } from 'enzyme'; 2 | import Adapter from 'enzyme-adapter-preact-pure'; 3 | 4 | /* ----------------------------------- 5 | * 6 | * Setup 7 | * 8 | * -------------------------------- */ 9 | 10 | configure({ adapter: new Adapter() }); 11 | -------------------------------------------------------------------------------- /packages/preactement/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "./dist", 5 | "jsx": "react", 6 | "jsxFactory": "h" 7 | }, 8 | "include": ["./src"] 9 | } 10 | -------------------------------------------------------------------------------- /packages/preactement/webpack.config.ts: -------------------------------------------------------------------------------- 1 | import { Configuration, DefinePlugin } from 'webpack'; 2 | import nodeExternals from 'webpack-node-externals'; 3 | import * as path from 'path'; 4 | 5 | /* ----------------------------------- 6 | * 7 | * Output 8 | * 9 | * -------------------------------- */ 10 | 11 | const outputFiles = [ 12 | { target: 'es5', filename: '[name].es5.js' }, 13 | { target: 'es2016', filename: '[name].js' }, 14 | ]; 15 | 16 | /* ----------------------------------- 17 | * 18 | * Default 19 | * 20 | * -------------------------------- */ 21 | 22 | const defaultConfig = { 23 | entry: { 24 | define: path.join(__dirname, './src/define.ts'), 25 | }, 26 | externals: [ 27 | nodeExternals({ 28 | allowlist: ['@component-elements/shared'], 29 | modulesDir: path.resolve(__dirname, '../../node_modules'), 30 | }), 31 | ], 32 | context: path.join(__dirname, './src'), 33 | output: { 34 | path: path.join(__dirname, './dist'), 35 | filename: '[name].js', 36 | libraryTarget: 'umd', 37 | globalObject: 'this', 38 | chunkFormat: 'commonjs', 39 | }, 40 | resolve: { 41 | extensions: ['.js', '.ts', '.tsx', 'json'], 42 | }, 43 | node: { 44 | __filename: true, 45 | __dirname: true, 46 | }, 47 | stats: { 48 | colors: true, 49 | timings: true, 50 | }, 51 | }; 52 | 53 | /* ----------------------------------- 54 | * 55 | * Config 56 | * 57 | * -------------------------------- */ 58 | 59 | const config = ({ mode }): Configuration[] => 60 | outputFiles.map(({ target, filename, ...config }) => ({ 61 | ...defaultConfig, 62 | mode, 63 | target, 64 | devtool: mode === 'development' ? 'eval-source-map' : void 0, 65 | cache: mode === 'development', 66 | output: { 67 | ...defaultConfig.output, 68 | filename, 69 | }, 70 | module: { 71 | rules: [ 72 | { 73 | test: /\.ts$/, 74 | use: [ 75 | { 76 | loader: 'ts-loader', 77 | options: { 78 | compilerOptions: { 79 | target, 80 | }, 81 | }, 82 | }, 83 | ], 84 | }, 85 | { 86 | test: /\.m?js$/, 87 | use: { 88 | loader: 'babel-loader', 89 | options: { 90 | ...(target === 'es5' && { presets: ['@babel/preset-env'] }), 91 | }, 92 | }, 93 | }, 94 | ], 95 | }, 96 | performance: { 97 | hints: mode === 'development' ? 'warning' : void 0, 98 | }, 99 | plugins: [ 100 | new DefinePlugin({ 101 | __DEV__: mode === 'development', 102 | }), 103 | ], 104 | ...config, 105 | })); 106 | 107 | /* ----------------------------------- 108 | * 109 | * Export 110 | * 111 | * -------------------------------- */ 112 | 113 | module.exports = config; 114 | -------------------------------------------------------------------------------- /packages/preactement/yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | "@types/cheerio@*": 6 | "integrity" "sha512-Kt7Cdjjdi2XWSfrZ53v4Of0wG3ZcmaegFXjMmz9tfNrZSkzzo36G0AL1YqSdcIA78Etjt6E609pt5h1xnQkPUw==" 7 | "resolved" "https://registry.npmjs.org/@types/cheerio/-/cheerio-0.22.31.tgz" 8 | "version" "0.22.31" 9 | dependencies: 10 | "@types/node" "*" 11 | 12 | "@types/enzyme@^3.10.11": 13 | "integrity" "sha512-LEtC7zXsQlbGXWGcnnmOI7rTyP+i1QzQv4Va91RKXDEukLDaNyxu0rXlfMiGEhJwfgTPCTb0R+Pnlj//oM9e/w==" 14 | "resolved" "https://registry.npmjs.org/@types/enzyme/-/enzyme-3.10.11.tgz" 15 | "version" "3.10.11" 16 | dependencies: 17 | "@types/cheerio" "*" 18 | "@types/react" "*" 19 | 20 | "@types/node@*": 21 | "integrity" "sha512-eKj4f/BsN/qcculZiRSujogjvp5O/k4lOW5m35NopjZM/QwLOR075a8pJW5hD+Rtdm2DaCVPENS6KtSQnUD6BA==" 22 | "resolved" "https://registry.npmjs.org/@types/node/-/node-17.0.18.tgz" 23 | "version" "17.0.18" 24 | 25 | "@types/prop-types@*": 26 | "integrity" "sha512-rZ5drC/jWjrArrS8BR6SIr4cWpW09RNTYt9AMZo3Jwwif+iacXAqgVjm0B0Bv/S1jhDXKHqRVNCbACkJ89RAnQ==" 27 | "resolved" "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.4.tgz" 28 | "version" "15.7.4" 29 | 30 | "@types/react@*": 31 | "integrity" "sha512-UVavlfAxDd/AgAacMa60Azl7ygyQNRwC/DsHZmKgNvPmRR5p70AJ5Q9EAmL2NWOJmeV+vVUI4IAP7GZrN8h8Ug==" 32 | "resolved" "https://registry.npmjs.org/@types/react/-/react-17.0.39.tgz" 33 | "version" "17.0.39" 34 | dependencies: 35 | "@types/prop-types" "*" 36 | "@types/scheduler" "*" 37 | "csstype" "^3.0.2" 38 | 39 | "@types/scheduler@*": 40 | "integrity" "sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew==" 41 | "resolved" "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.2.tgz" 42 | "version" "0.16.2" 43 | 44 | "array.prototype.filter@^1.0.0": 45 | "integrity" "sha512-Dk3Ty7N42Odk7PjU/Ci3zT4pLj20YvuVnneG/58ICM6bt4Ij5kZaJTVQ9TSaWaIECX2sFyz4KItkVZqHNnciqw==" 46 | "resolved" "https://registry.npmjs.org/array.prototype.filter/-/array.prototype.filter-1.0.1.tgz" 47 | "version" "1.0.1" 48 | dependencies: 49 | "call-bind" "^1.0.2" 50 | "define-properties" "^1.1.3" 51 | "es-abstract" "^1.19.0" 52 | "es-array-method-boxes-properly" "^1.0.0" 53 | "is-string" "^1.0.7" 54 | 55 | "array.prototype.flat@^1.2.3": 56 | "integrity" "sha512-KaYU+S+ndVqyUnignHftkwc58o3uVU1jzczILJ1tN2YaIZpFIKBiP/x/j97E5MVPsaCloPbqWLB/8qCTVvT2qg==" 57 | "resolved" "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.2.5.tgz" 58 | "version" "1.2.5" 59 | dependencies: 60 | "call-bind" "^1.0.2" 61 | "define-properties" "^1.1.3" 62 | "es-abstract" "^1.19.0" 63 | 64 | "array.prototype.flatmap@^1.2.1": 65 | "integrity" "sha512-08u6rVyi1Lj7oqWbS9nUxliETrtIROT4XGTA4D/LWGten6E3ocm7cy9SIrmNHOL5XVbVuckUp3X6Xyg8/zpvHA==" 66 | "resolved" "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.2.5.tgz" 67 | "version" "1.2.5" 68 | dependencies: 69 | "call-bind" "^1.0.0" 70 | "define-properties" "^1.1.3" 71 | "es-abstract" "^1.19.0" 72 | 73 | "boolbase@^1.0.0": 74 | "integrity" "sha1-aN/1++YMUes3cl6p4+0xDcwed24=" 75 | "resolved" "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz" 76 | "version" "1.0.0" 77 | 78 | "call-bind@^1.0.0", "call-bind@^1.0.2": 79 | "integrity" "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==" 80 | "resolved" "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz" 81 | "version" "1.0.2" 82 | dependencies: 83 | "function-bind" "^1.1.1" 84 | "get-intrinsic" "^1.0.2" 85 | 86 | "cheerio-select@^1.5.0": 87 | "integrity" "sha512-qocaHPv5ypefh6YNxvnbABM07KMxExbtbfuJoIie3iZXX1ERwYmJcIiRrr9H05ucQP1k28dav8rpdDgjQd8drg==" 88 | "resolved" "https://registry.npmjs.org/cheerio-select/-/cheerio-select-1.5.0.tgz" 89 | "version" "1.5.0" 90 | dependencies: 91 | "css-select" "^4.1.3" 92 | "css-what" "^5.0.1" 93 | "domelementtype" "^2.2.0" 94 | "domhandler" "^4.2.0" 95 | "domutils" "^2.7.0" 96 | 97 | "cheerio@^1.0.0-rc.3": 98 | "integrity" "sha512-g0J0q/O6mW8z5zxQ3A8E8J1hUgp4SMOvEoW/x84OwyHKe/Zccz83PVT4y5Crcr530FV6NgmKI1qvGTKVl9XXVw==" 99 | "resolved" "https://registry.npmjs.org/cheerio/-/cheerio-1.0.0-rc.10.tgz" 100 | "version" "1.0.0-rc.10" 101 | dependencies: 102 | "cheerio-select" "^1.5.0" 103 | "dom-serializer" "^1.3.2" 104 | "domhandler" "^4.2.0" 105 | "htmlparser2" "^6.1.0" 106 | "parse5" "^6.0.1" 107 | "parse5-htmlparser2-tree-adapter" "^6.0.1" 108 | "tslib" "^2.2.0" 109 | 110 | "commander@^2.19.0": 111 | "integrity" "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==" 112 | "resolved" "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz" 113 | "version" "2.20.3" 114 | 115 | "css-select@^4.1.3": 116 | "integrity" "sha512-/aUslKhzkTNCQUB2qTX84lVmfia9NyjP3WpDGtj/WxhwBzWBYUV3DgUpurHTme8UTPcPlAD1DJ+b0nN/t50zDQ==" 117 | "resolved" "https://registry.npmjs.org/css-select/-/css-select-4.2.1.tgz" 118 | "version" "4.2.1" 119 | dependencies: 120 | "boolbase" "^1.0.0" 121 | "css-what" "^5.1.0" 122 | "domhandler" "^4.3.0" 123 | "domutils" "^2.8.0" 124 | "nth-check" "^2.0.1" 125 | 126 | "css-what@^5.0.1", "css-what@^5.1.0": 127 | "integrity" "sha512-arSMRWIIFY0hV8pIxZMEfmMI47Wj3R/aWpZDDxWYCPEiOMv6tfOrnpDtgxBYPEQD4V0Y/958+1TdC3iWTFcUPw==" 128 | "resolved" "https://registry.npmjs.org/css-what/-/css-what-5.1.0.tgz" 129 | "version" "5.1.0" 130 | 131 | "csstype@^3.0.2": 132 | "integrity" "sha512-2u44ZG2OcNUO9HDp/Jl8C07x6pU/eTR3ncV91SiK3dhG9TWvRVsCoJw14Ckx5DgWkzGA3waZWO3d7pgqpUI/XA==" 133 | "resolved" "https://registry.npmjs.org/csstype/-/csstype-3.0.10.tgz" 134 | "version" "3.0.10" 135 | 136 | "define-properties@^1.1.3": 137 | "integrity" "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==" 138 | "resolved" "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz" 139 | "version" "1.1.3" 140 | dependencies: 141 | "object-keys" "^1.0.12" 142 | 143 | "discontinuous-range@1.0.0": 144 | "integrity" "sha1-44Mx8IRLukm5qctxx3FYWqsbxlo=" 145 | "resolved" "https://registry.npmjs.org/discontinuous-range/-/discontinuous-range-1.0.0.tgz" 146 | "version" "1.0.0" 147 | 148 | "dom-serializer@^1.0.1", "dom-serializer@^1.3.2": 149 | "integrity" "sha512-5c54Bk5Dw4qAxNOI1pFEizPSjVsx5+bpJKmL2kPn8JhBUq2q09tTCa3mjijun2NfK78NMouDYNMBkOrPZiS+ig==" 150 | "resolved" "https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.3.2.tgz" 151 | "version" "1.3.2" 152 | dependencies: 153 | "domelementtype" "^2.0.1" 154 | "domhandler" "^4.2.0" 155 | "entities" "^2.0.0" 156 | 157 | "domelementtype@^2.0.1", "domelementtype@^2.2.0": 158 | "integrity" "sha512-DtBMo82pv1dFtUmHyr48beiuq792Sxohr+8Hm9zoxklYPfa6n0Z3Byjj2IV7bmr2IyqClnqEQhfgHJJ5QF0R5A==" 159 | "resolved" "https://registry.npmjs.org/domelementtype/-/domelementtype-2.2.0.tgz" 160 | "version" "2.2.0" 161 | 162 | "domhandler@^4.0.0", "domhandler@^4.2.0", "domhandler@^4.3.0": 163 | "integrity" "sha512-fC0aXNQXqKSFTr2wDNZDhsEYjCiYsDWl3D01kwt25hm1YIPyDGHvvi3rw+PLqHAl/m71MaiF7d5zvBr0p5UB2g==" 164 | "resolved" "https://registry.npmjs.org/domhandler/-/domhandler-4.3.0.tgz" 165 | "version" "4.3.0" 166 | dependencies: 167 | "domelementtype" "^2.2.0" 168 | 169 | "domutils@^2.5.2", "domutils@^2.7.0", "domutils@^2.8.0": 170 | "integrity" "sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==" 171 | "resolved" "https://registry.npmjs.org/domutils/-/domutils-2.8.0.tgz" 172 | "version" "2.8.0" 173 | dependencies: 174 | "dom-serializer" "^1.0.1" 175 | "domelementtype" "^2.2.0" 176 | "domhandler" "^4.2.0" 177 | 178 | "entities@^2.0.0": 179 | "integrity" "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==" 180 | "resolved" "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz" 181 | "version" "2.2.0" 182 | 183 | "enzyme-adapter-preact-pure@^3.3.0": 184 | "integrity" "sha512-+FNEZBEXwuDDsA8YOvC9p6gcvxQG5V6QnXKkUVJte/GKMWadOXDR+uw0w+QGwxreA8oMOlK/1+O8F7PzealpKA==" 185 | "resolved" "https://registry.npmjs.org/enzyme-adapter-preact-pure/-/enzyme-adapter-preact-pure-3.3.0.tgz" 186 | "version" "3.3.0" 187 | dependencies: 188 | "array.prototype.flatmap" "^1.2.1" 189 | 190 | "enzyme-shallow-equal@^1.0.1": 191 | "integrity" "sha512-MttIwB8kKxypwHvRynuC3ahyNc+cFbR8mjVIltnmzQ0uKGqmsfO4bfBuLxb0beLNPhjblUEYvEbsg+VSygvF1Q==" 192 | "resolved" "https://registry.npmjs.org/enzyme-shallow-equal/-/enzyme-shallow-equal-1.0.4.tgz" 193 | "version" "1.0.4" 194 | dependencies: 195 | "has" "^1.0.3" 196 | "object-is" "^1.1.2" 197 | 198 | "enzyme@^3.11.0", "enzyme@^3.8.0": 199 | "integrity" "sha512-Dw8/Gs4vRjxY6/6i9wU0V+utmQO9kvh9XLnz3LIudviOnVYDEe2ec+0k+NQoMamn1VrjKgCUOWj5jG/5M5M0Qw==" 200 | "resolved" "https://registry.npmjs.org/enzyme/-/enzyme-3.11.0.tgz" 201 | "version" "3.11.0" 202 | dependencies: 203 | "array.prototype.flat" "^1.2.3" 204 | "cheerio" "^1.0.0-rc.3" 205 | "enzyme-shallow-equal" "^1.0.1" 206 | "function.prototype.name" "^1.1.2" 207 | "has" "^1.0.3" 208 | "html-element-map" "^1.2.0" 209 | "is-boolean-object" "^1.0.1" 210 | "is-callable" "^1.1.5" 211 | "is-number-object" "^1.0.4" 212 | "is-regex" "^1.0.5" 213 | "is-string" "^1.0.5" 214 | "is-subset" "^0.1.1" 215 | "lodash.escape" "^4.0.1" 216 | "lodash.isequal" "^4.5.0" 217 | "object-inspect" "^1.7.0" 218 | "object-is" "^1.0.2" 219 | "object.assign" "^4.1.0" 220 | "object.entries" "^1.1.1" 221 | "object.values" "^1.1.1" 222 | "raf" "^3.4.1" 223 | "rst-selector-parser" "^2.2.3" 224 | "string.prototype.trim" "^1.2.1" 225 | 226 | "es-abstract@^1.19.0", "es-abstract@^1.19.1": 227 | "integrity" "sha512-2vJ6tjA/UfqLm2MPs7jxVybLoB8i1t1Jd9R3kISld20sIxPcTbLuggQOUxeWeAvIUkduv/CfMjuh4WmiXr2v9w==" 228 | "resolved" "https://registry.npmjs.org/es-abstract/-/es-abstract-1.19.1.tgz" 229 | "version" "1.19.1" 230 | dependencies: 231 | "call-bind" "^1.0.2" 232 | "es-to-primitive" "^1.2.1" 233 | "function-bind" "^1.1.1" 234 | "get-intrinsic" "^1.1.1" 235 | "get-symbol-description" "^1.0.0" 236 | "has" "^1.0.3" 237 | "has-symbols" "^1.0.2" 238 | "internal-slot" "^1.0.3" 239 | "is-callable" "^1.2.4" 240 | "is-negative-zero" "^2.0.1" 241 | "is-regex" "^1.1.4" 242 | "is-shared-array-buffer" "^1.0.1" 243 | "is-string" "^1.0.7" 244 | "is-weakref" "^1.0.1" 245 | "object-inspect" "^1.11.0" 246 | "object-keys" "^1.1.1" 247 | "object.assign" "^4.1.2" 248 | "string.prototype.trimend" "^1.0.4" 249 | "string.prototype.trimstart" "^1.0.4" 250 | "unbox-primitive" "^1.0.1" 251 | 252 | "es-array-method-boxes-properly@^1.0.0": 253 | "integrity" "sha512-wd6JXUmyHmt8T5a2xreUwKcGPq6f1f+WwIJkijUqiGcJz1qqnZgP6XIK+QyIWU5lT7imeNxUll48bziG+TSYcA==" 254 | "resolved" "https://registry.npmjs.org/es-array-method-boxes-properly/-/es-array-method-boxes-properly-1.0.0.tgz" 255 | "version" "1.0.0" 256 | 257 | "es-to-primitive@^1.2.1": 258 | "integrity" "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==" 259 | "resolved" "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz" 260 | "version" "1.2.1" 261 | dependencies: 262 | "is-callable" "^1.1.4" 263 | "is-date-object" "^1.0.1" 264 | "is-symbol" "^1.0.2" 265 | 266 | "function-bind@^1.1.1": 267 | "integrity" "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" 268 | "resolved" "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz" 269 | "version" "1.1.1" 270 | 271 | "function.prototype.name@^1.1.2": 272 | "integrity" "sha512-uN7m/BzVKQnCUF/iW8jYea67v++2u7m5UgENbHRtdDVclOUP+FMPlCNdmk0h/ysGyo2tavMJEDqJAkJdRa1vMA==" 273 | "resolved" "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.5.tgz" 274 | "version" "1.1.5" 275 | dependencies: 276 | "call-bind" "^1.0.2" 277 | "define-properties" "^1.1.3" 278 | "es-abstract" "^1.19.0" 279 | "functions-have-names" "^1.2.2" 280 | 281 | "functions-have-names@^1.2.2": 282 | "integrity" "sha512-bLgc3asbWdwPbx2mNk2S49kmJCuQeu0nfmaOgbs8WIyzzkw3r4htszdIi9Q9EMezDPTYuJx2wvjZ/EwgAthpnA==" 283 | "resolved" "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.2.tgz" 284 | "version" "1.2.2" 285 | 286 | "get-intrinsic@^1.0.2", "get-intrinsic@^1.1.0", "get-intrinsic@^1.1.1": 287 | "integrity" "sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q==" 288 | "resolved" "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.1.tgz" 289 | "version" "1.1.1" 290 | dependencies: 291 | "function-bind" "^1.1.1" 292 | "has" "^1.0.3" 293 | "has-symbols" "^1.0.1" 294 | 295 | "get-symbol-description@^1.0.0": 296 | "integrity" "sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==" 297 | "resolved" "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.0.tgz" 298 | "version" "1.0.0" 299 | dependencies: 300 | "call-bind" "^1.0.2" 301 | "get-intrinsic" "^1.1.1" 302 | 303 | "has-bigints@^1.0.1": 304 | "integrity" "sha512-LSBS2LjbNBTf6287JEbEzvJgftkF5qFkmCo9hDRpAzKhUOlJ+hx8dd4USs00SgsUNwc4617J9ki5YtEClM2ffA==" 305 | "resolved" "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.1.tgz" 306 | "version" "1.0.1" 307 | 308 | "has-symbols@^1.0.1", "has-symbols@^1.0.2": 309 | "integrity" "sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw==" 310 | "resolved" "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.2.tgz" 311 | "version" "1.0.2" 312 | 313 | "has-tostringtag@^1.0.0": 314 | "integrity" "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==" 315 | "resolved" "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz" 316 | "version" "1.0.0" 317 | dependencies: 318 | "has-symbols" "^1.0.2" 319 | 320 | "has@^1.0.3": 321 | "integrity" "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==" 322 | "resolved" "https://registry.npmjs.org/has/-/has-1.0.3.tgz" 323 | "version" "1.0.3" 324 | dependencies: 325 | "function-bind" "^1.1.1" 326 | 327 | "html-element-map@^1.2.0": 328 | "integrity" "sha512-6XMlxrAFX4UEEGxctfFnmrFaaZFNf9i5fNuV5wZ3WWQ4FVaNP1aX1LkX9j2mfEx1NpjeE/rL3nmgEn23GdFmrg==" 329 | "resolved" "https://registry.npmjs.org/html-element-map/-/html-element-map-1.3.1.tgz" 330 | "version" "1.3.1" 331 | dependencies: 332 | "array.prototype.filter" "^1.0.0" 333 | "call-bind" "^1.0.2" 334 | 335 | "htmlparser2@^6.1.0": 336 | "integrity" "sha512-gyyPk6rgonLFEDGoeRgQNaEUvdJ4ktTmmUh/h2t7s+M8oPpIPxgNACWa+6ESR57kXstwqPiCut0V8NRpcwgU7A==" 337 | "resolved" "https://registry.npmjs.org/htmlparser2/-/htmlparser2-6.1.0.tgz" 338 | "version" "6.1.0" 339 | dependencies: 340 | "domelementtype" "^2.0.1" 341 | "domhandler" "^4.0.0" 342 | "domutils" "^2.5.2" 343 | "entities" "^2.0.0" 344 | 345 | "internal-slot@^1.0.3": 346 | "integrity" "sha512-O0DB1JC/sPyZl7cIo78n5dR7eUSwwpYPiXRhTzNxZVAMUuB8vlnRFyLxdrVToks6XPLVnFfbzaVd5WLjhgg+vA==" 347 | "resolved" "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.3.tgz" 348 | "version" "1.0.3" 349 | dependencies: 350 | "get-intrinsic" "^1.1.0" 351 | "has" "^1.0.3" 352 | "side-channel" "^1.0.4" 353 | 354 | "is-bigint@^1.0.1": 355 | "integrity" "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==" 356 | "resolved" "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz" 357 | "version" "1.0.4" 358 | dependencies: 359 | "has-bigints" "^1.0.1" 360 | 361 | "is-boolean-object@^1.0.1", "is-boolean-object@^1.1.0": 362 | "integrity" "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==" 363 | "resolved" "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz" 364 | "version" "1.1.2" 365 | dependencies: 366 | "call-bind" "^1.0.2" 367 | "has-tostringtag" "^1.0.0" 368 | 369 | "is-callable@^1.1.4", "is-callable@^1.1.5", "is-callable@^1.2.4": 370 | "integrity" "sha512-nsuwtxZfMX67Oryl9LCQ+upnC0Z0BgpwntpS89m1H/TLF0zNfzfLMV/9Wa/6MZsj0acpEjAO0KF1xT6ZdLl95w==" 371 | "resolved" "https://registry.npmjs.org/is-callable/-/is-callable-1.2.4.tgz" 372 | "version" "1.2.4" 373 | 374 | "is-date-object@^1.0.1": 375 | "integrity" "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==" 376 | "resolved" "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz" 377 | "version" "1.0.5" 378 | dependencies: 379 | "has-tostringtag" "^1.0.0" 380 | 381 | "is-negative-zero@^2.0.1": 382 | "integrity" "sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==" 383 | "resolved" "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.2.tgz" 384 | "version" "2.0.2" 385 | 386 | "is-number-object@^1.0.4": 387 | "integrity" "sha512-bEVOqiRcvo3zO1+G2lVMy+gkkEm9Yh7cDMRusKKu5ZJKPUYSJwICTKZrNKHA2EbSP0Tu0+6B/emsYNHZyn6K8g==" 388 | "resolved" "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.6.tgz" 389 | "version" "1.0.6" 390 | dependencies: 391 | "has-tostringtag" "^1.0.0" 392 | 393 | "is-regex@^1.0.5", "is-regex@^1.1.4": 394 | "integrity" "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==" 395 | "resolved" "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz" 396 | "version" "1.1.4" 397 | dependencies: 398 | "call-bind" "^1.0.2" 399 | "has-tostringtag" "^1.0.0" 400 | 401 | "is-shared-array-buffer@^1.0.1": 402 | "integrity" "sha512-IU0NmyknYZN0rChcKhRO1X8LYz5Isj/Fsqh8NJOSf+N/hCOTwy29F32Ik7a+QszE63IdvmwdTPDd6cZ5pg4cwA==" 403 | "resolved" "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.1.tgz" 404 | "version" "1.0.1" 405 | 406 | "is-string@^1.0.5", "is-string@^1.0.7": 407 | "integrity" "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==" 408 | "resolved" "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz" 409 | "version" "1.0.7" 410 | dependencies: 411 | "has-tostringtag" "^1.0.0" 412 | 413 | "is-subset@^0.1.1": 414 | "integrity" "sha1-ilkRfZMt4d4A8kX83TnOQ/HpOaY=" 415 | "resolved" "https://registry.npmjs.org/is-subset/-/is-subset-0.1.1.tgz" 416 | "version" "0.1.1" 417 | 418 | "is-symbol@^1.0.2", "is-symbol@^1.0.3": 419 | "integrity" "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==" 420 | "resolved" "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz" 421 | "version" "1.0.4" 422 | dependencies: 423 | "has-symbols" "^1.0.2" 424 | 425 | "is-weakref@^1.0.1": 426 | "integrity" "sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==" 427 | "resolved" "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz" 428 | "version" "1.0.2" 429 | dependencies: 430 | "call-bind" "^1.0.2" 431 | 432 | "lodash.escape@^4.0.1": 433 | "integrity" "sha1-yQRGkMIeBClL6qUXcS/e0fqI3pg=" 434 | "resolved" "https://registry.npmjs.org/lodash.escape/-/lodash.escape-4.0.1.tgz" 435 | "version" "4.0.1" 436 | 437 | "lodash.flattendeep@^4.4.0": 438 | "integrity" "sha1-+wMJF/hqMTTlvJvsDWngAT3f7bI=" 439 | "resolved" "https://registry.npmjs.org/lodash.flattendeep/-/lodash.flattendeep-4.4.0.tgz" 440 | "version" "4.4.0" 441 | 442 | "lodash.isequal@^4.5.0": 443 | "integrity" "sha1-QVxEePK8wwEgwizhDtMib30+GOA=" 444 | "resolved" "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz" 445 | "version" "4.5.0" 446 | 447 | "moo@^0.5.0": 448 | "integrity" "sha512-I1mnb5xn4fO80BH9BLcF0yLypy2UKl+Cb01Fu0hJRkJjlCRtxZMWkTdAtDd5ZqCOxtCkhmRwyI57vWT+1iZ67w==" 449 | "resolved" "https://registry.npmjs.org/moo/-/moo-0.5.1.tgz" 450 | "version" "0.5.1" 451 | 452 | "nearley@^2.7.10": 453 | "integrity" "sha512-+Mc8UaAebFzgV+KpI5n7DasuuQCHA89dmwm7JXw3TV43ukfNQ9DnBH3Mdb2g/I4Fdxc26pwimBWvjIw0UAILSQ==" 454 | "resolved" "https://registry.npmjs.org/nearley/-/nearley-2.20.1.tgz" 455 | "version" "2.20.1" 456 | dependencies: 457 | "commander" "^2.19.0" 458 | "moo" "^0.5.0" 459 | "railroad-diagrams" "^1.0.0" 460 | "randexp" "0.4.6" 461 | 462 | "nth-check@^2.0.1": 463 | "integrity" "sha512-it1vE95zF6dTT9lBsYbxvqh0Soy4SPowchj0UBGj/V6cTPnXXtQOPUbhZ6CmGzAD/rW22LQK6E96pcdJXk4A4w==" 464 | "resolved" "https://registry.npmjs.org/nth-check/-/nth-check-2.0.1.tgz" 465 | "version" "2.0.1" 466 | dependencies: 467 | "boolbase" "^1.0.0" 468 | 469 | "object-inspect@^1.11.0", "object-inspect@^1.7.0", "object-inspect@^1.9.0": 470 | "integrity" "sha512-Ho2z80bVIvJloH+YzRmpZVQe87+qASmBUKZDWgx9cu+KDrX2ZDH/3tMy+gXbZETVGs2M8YdxObOh7XAtim9Y0g==" 471 | "resolved" "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.0.tgz" 472 | "version" "1.12.0" 473 | 474 | "object-is@^1.0.2", "object-is@^1.1.2": 475 | "integrity" "sha512-3cyDsyHgtmi7I7DfSSI2LDp6SK2lwvtbg0p0R1e0RvTqF5ceGx+K2dfSjm1bKDMVCFEDAQvy+o8c6a7VujOddw==" 476 | "resolved" "https://registry.npmjs.org/object-is/-/object-is-1.1.5.tgz" 477 | "version" "1.1.5" 478 | dependencies: 479 | "call-bind" "^1.0.2" 480 | "define-properties" "^1.1.3" 481 | 482 | "object-keys@^1.0.12", "object-keys@^1.1.1": 483 | "integrity" "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==" 484 | "resolved" "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz" 485 | "version" "1.1.1" 486 | 487 | "object.assign@^4.1.0", "object.assign@^4.1.2": 488 | "integrity" "sha512-ixT2L5THXsApyiUPYKmW+2EHpXXe5Ii3M+f4e+aJFAHao5amFRW6J0OO6c/LU8Be47utCx2GL89hxGB6XSmKuQ==" 489 | "resolved" "https://registry.npmjs.org/object.assign/-/object.assign-4.1.2.tgz" 490 | "version" "4.1.2" 491 | dependencies: 492 | "call-bind" "^1.0.0" 493 | "define-properties" "^1.1.3" 494 | "has-symbols" "^1.0.1" 495 | "object-keys" "^1.1.1" 496 | 497 | "object.entries@^1.1.1": 498 | "integrity" "sha512-TyxmjUoZggd4OrrU1W66FMDG6CuqJxsFvymeyXI51+vQLN67zYfZseptRge703kKQdo4uccgAKebXFcRCzk4+g==" 499 | "resolved" "https://registry.npmjs.org/object.entries/-/object.entries-1.1.5.tgz" 500 | "version" "1.1.5" 501 | dependencies: 502 | "call-bind" "^1.0.2" 503 | "define-properties" "^1.1.3" 504 | "es-abstract" "^1.19.1" 505 | 506 | "object.values@^1.1.1": 507 | "integrity" "sha512-QUZRW0ilQ3PnPpbNtgdNV1PDbEqLIiSFB3l+EnGtBQ/8SUTLj1PZwtQHABZtLgwpJZTSZhuGLOGk57Drx2IvYg==" 508 | "resolved" "https://registry.npmjs.org/object.values/-/object.values-1.1.5.tgz" 509 | "version" "1.1.5" 510 | dependencies: 511 | "call-bind" "^1.0.2" 512 | "define-properties" "^1.1.3" 513 | "es-abstract" "^1.19.1" 514 | 515 | "parse5-htmlparser2-tree-adapter@^6.0.1": 516 | "integrity" "sha512-qPuWvbLgvDGilKc5BoicRovlT4MtYT6JfJyBOMDsKoiT+GiuP5qyrPCnR9HcPECIJJmZh5jRndyNThnhhb/vlA==" 517 | "resolved" "https://registry.npmjs.org/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-6.0.1.tgz" 518 | "version" "6.0.1" 519 | dependencies: 520 | "parse5" "^6.0.1" 521 | 522 | "parse5@^6.0.1": 523 | "integrity" "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==" 524 | "resolved" "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz" 525 | "version" "6.0.1" 526 | 527 | "performance-now@^2.1.0": 528 | "integrity" "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=" 529 | "resolved" "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz" 530 | "version" "2.1.0" 531 | 532 | "preact@^10.0.0", "preact@^10.6.6": 533 | "integrity" "sha512-dgxpTFV2vs4vizwKohYKkk7g7rmp1wOOcfd4Tz3IB3Wi+ivZzsn/SpeKJhRENSE+n8sUfsAl4S3HiCVT923ABw==" 534 | "resolved" "https://registry.npmjs.org/preact/-/preact-10.6.6.tgz" 535 | "version" "10.6.6" 536 | 537 | "raf@^3.4.1": 538 | "integrity" "sha512-Sq4CW4QhwOHE8ucn6J34MqtZCeWFP2aQSmrlroYgqAV1PjStIhJXxYuTgUIfkEk7zTLjmIjLmU5q+fbD1NnOJA==" 539 | "resolved" "https://registry.npmjs.org/raf/-/raf-3.4.1.tgz" 540 | "version" "3.4.1" 541 | dependencies: 542 | "performance-now" "^2.1.0" 543 | 544 | "railroad-diagrams@^1.0.0": 545 | "integrity" "sha1-635iZ1SN3t+4mcG5Dlc3RVnN234=" 546 | "resolved" "https://registry.npmjs.org/railroad-diagrams/-/railroad-diagrams-1.0.0.tgz" 547 | "version" "1.0.0" 548 | 549 | "randexp@0.4.6": 550 | "integrity" "sha512-80WNmd9DA0tmZrw9qQa62GPPWfuXJknrmVmLcxvq4uZBdYqb1wYoKTmnlGUchvVWe0XiLupYkBoXVOxz3C8DYQ==" 551 | "resolved" "https://registry.npmjs.org/randexp/-/randexp-0.4.6.tgz" 552 | "version" "0.4.6" 553 | dependencies: 554 | "discontinuous-range" "1.0.0" 555 | "ret" "~0.1.10" 556 | 557 | "ret@~0.1.10": 558 | "integrity" "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==" 559 | "resolved" "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz" 560 | "version" "0.1.15" 561 | 562 | "rst-selector-parser@^2.2.3": 563 | "integrity" "sha1-gbIw6i/MYGbInjRy3nlChdmwPZE=" 564 | "resolved" "https://registry.npmjs.org/rst-selector-parser/-/rst-selector-parser-2.2.3.tgz" 565 | "version" "2.2.3" 566 | dependencies: 567 | "lodash.flattendeep" "^4.4.0" 568 | "nearley" "^2.7.10" 569 | 570 | "side-channel@^1.0.4": 571 | "integrity" "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==" 572 | "resolved" "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz" 573 | "version" "1.0.4" 574 | dependencies: 575 | "call-bind" "^1.0.0" 576 | "get-intrinsic" "^1.0.2" 577 | "object-inspect" "^1.9.0" 578 | 579 | "string.prototype.trim@^1.2.1": 580 | "integrity" "sha512-Lnh17webJVsD6ECeovpVN17RlAKjmz4rF9S+8Y45CkMc/ufVpTkU3vZIyIC7sllQ1FCvObZnnCdNs/HXTUOTlg==" 581 | "resolved" "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.5.tgz" 582 | "version" "1.2.5" 583 | dependencies: 584 | "call-bind" "^1.0.2" 585 | "define-properties" "^1.1.3" 586 | "es-abstract" "^1.19.1" 587 | 588 | "string.prototype.trimend@^1.0.4": 589 | "integrity" "sha512-y9xCjw1P23Awk8EvTpcyL2NIr1j7wJ39f+k6lvRnSMz+mz9CGz9NYPelDk42kOz6+ql8xjfK8oYzy3jAP5QU5A==" 590 | "resolved" "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.4.tgz" 591 | "version" "1.0.4" 592 | dependencies: 593 | "call-bind" "^1.0.2" 594 | "define-properties" "^1.1.3" 595 | 596 | "string.prototype.trimstart@^1.0.4": 597 | "integrity" "sha512-jh6e984OBfvxS50tdY2nRZnoC5/mLFKOREQfw8t5yytkoUsJRNxvI/E39qu1sD0OtWI3OC0XgKSmcWwziwYuZw==" 598 | "resolved" "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.4.tgz" 599 | "version" "1.0.4" 600 | dependencies: 601 | "call-bind" "^1.0.2" 602 | "define-properties" "^1.1.3" 603 | 604 | "tslib@^2.2.0": 605 | "integrity" "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==" 606 | "resolved" "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz" 607 | "version" "2.3.1" 608 | 609 | "unbox-primitive@^1.0.1": 610 | "integrity" "sha512-tZU/3NqK3dA5gpE1KtyiJUrEB0lxnGkMFHptJ7q6ewdZ8s12QrODwNbhIJStmJkd1QDXa1NRA8aF2A1zk/Ypyw==" 611 | "resolved" "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.1.tgz" 612 | "version" "1.0.1" 613 | dependencies: 614 | "function-bind" "^1.1.1" 615 | "has-bigints" "^1.0.1" 616 | "has-symbols" "^1.0.2" 617 | "which-boxed-primitive" "^1.0.2" 618 | 619 | "which-boxed-primitive@^1.0.2": 620 | "integrity" "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==" 621 | "resolved" "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz" 622 | "version" "1.0.2" 623 | dependencies: 624 | "is-bigint" "^1.0.1" 625 | "is-boolean-object" "^1.1.0" 626 | "is-number-object" "^1.0.4" 627 | "is-string" "^1.0.5" 628 | "is-symbol" "^1.0.3" 629 | -------------------------------------------------------------------------------- /packages/reactement/.npmrc: -------------------------------------------------------------------------------- 1 | registry=https://registry.npmjs.org 2 | -------------------------------------------------------------------------------- /packages/reactement/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 James Hill 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /packages/reactement/README.md: -------------------------------------------------------------------------------- 1 | # reactement 2 | 3 | Sometimes it's useful to let the DOM render our components when needed. Custom Elements are great at this. They provide various methods that can inform you when an element is "connected" or "disconnected" from the DOM. 4 | 5 | This package (only **2KB** GZipped) provides the ability to use an HTML custom element as the root for your components. In addition, it allows the use of async code resolution if your custom element isn't immediately used, which is a great strategy for reducing code weight. The exported function can also be used for hydration from SSR in Node. 6 | 7 | It's also a great way for you to integrate React into other server side frameworks that might render your HTML. 8 | 9 | > This package supports React. If you're using Preact, go to [preactement](https://github.com/jahilldev/component-elements/tree/main/packages/preactement#readme) for more info. 10 | 11 | # Getting Started 12 | 13 | Install with Yarn: 14 | 15 | ```bash 16 | $ yarn add reactement 17 | ``` 18 | 19 | Install with NPM: 20 | 21 | ```bash 22 | $ npm i reactement 23 | ``` 24 | 25 | # Using define() 26 | 27 | `reactement` exports one function, `define()`. This allows us to register a custom element via a provided key, and provide the component we'd like to render within. It can also generate a custom element with props ready for hydration if run on the server. 28 | 29 | The first argument **must be a valid custom element string**, e.g hyphenated. If you do not provide this, a prefix of `component-` will be applied to your element name. 30 | 31 | ## In the browser 32 | 33 | In order to register and render a component, you'll need to call `define()` with your chosen component, e.g: 34 | 35 | ```tsx 36 | import { define } from 'reactement'; 37 | import { HeroBanner } from './heroBanner'; 38 | 39 | /*[...]*/ 40 | 41 | define('hero-banner', () => HeroBanner); 42 | ``` 43 | 44 | This registers `` as a custom element. When that element exists on the page, `reactement` will render our component. 45 | 46 | If the custom element isn't present immediately, e.g it's created dynamically at some point in the future, we can provide an async function that explicitly resolves your component: 47 | 48 | ```tsx 49 | define('hero-banner', () => Promise.resolve(HeroBanner)); 50 | ``` 51 | 52 | This allows us to reduce the overall code in our bundle, and load the required component on demand when needed. 53 | 54 | You can either resolve the component from your async function, as seen above, _or_ `reactement` will try to infer the export key based on the provided tag name. For example: 55 | 56 | ```tsx 57 | import { define } from 'reactement'; 58 | 59 | /*[...]*/ 60 | 61 | define('hero-banner', () => import('./heroBanner')); 62 | ``` 63 | 64 | As the `heroBanner.ts` file is exporting the component as a key, e.g `export { HeroBanner };`, and this matches the tag name in kebab-case, e.g `hero-banner`, the component will be correctly rendered. 65 | 66 | ## On the server (SSR) 67 | 68 | You can also use `define()` to generate a custom element container if you're rendering your page in Node. When wrapping your component, e.g: 69 | 70 | ```ts 71 | define('hero-banner', () => HeroBanner); 72 | ``` 73 | 74 | A functional component is returned that you can include elsewhere in your app. For example: 75 | 76 | ```tsx 77 | import { define } from 'reactement'; 78 | 79 | /*[...]*/ 80 | 81 | const Component = define('hero-banner', () => HeroBanner); 82 | 83 | /*[...]*/ 84 | 85 | function HomePage() { 86 | return ( 87 |
88 | 89 |
90 | ); 91 | } 92 | ``` 93 | 94 | ## Properties 95 | 96 | If you're not running `reactement` on the server, you have several ways of defining props for your component. 97 | 98 | #### 1. Nested block of JSON: 99 | 100 | ```html 101 | 102 | 105 | 106 | ``` 107 | 108 | #### 2. A `props` attribute (this must be an encoded JSON string) 109 | 110 | ```html 111 | 112 | ``` 113 | 114 | #### 3. Custom attributes 115 | 116 | ```html 117 | 118 | ``` 119 | 120 | You'll need to define your custom attributes up front when using `define()`, e.g: 121 | 122 | ```ts 123 | define('hero-banner', () => HeroBanner, { attributes: ['title-text'] }); 124 | ``` 125 | 126 | These will then be merged into your components props in camelCase, so `title-text` will become `titleText`. 127 | 128 | ## HTML 129 | 130 | You can also provide nested HTML to your components `children` property. For example: 131 | 132 | ```html 133 | 134 |

Banner Title

135 |
136 | ``` 137 | 138 | This will correctly convert the `

` into virtual DOM nodes for use in your component, e.g: 139 | 140 | ```tsx 141 | /*[...]*/ 142 | 143 | function HeroBanner({ children }) { 144 | return
{children}
; 145 | } 146 | ``` 147 | 148 | ### Important 149 | 150 | Any HTML provided to the custom element **must be valid**; As we're using the DOM's native parser which is quite lax, any html passed that is not properly sanitised or structured might result in unusual bugs. For example: 151 | 152 | This will result in a React error: 153 | 154 | ```jsx 155 |

Hello 162 |

Hello

163 | ``` 164 | 165 | ### Slots 166 | 167 | `reactement` now supports the use of `<* slot="{key}" />` elements, to assign string values or full blocks of HTML to your component props. This is useful if your server defines layout rules that are outside of the scope of your component. For example, given the custom element below: 168 | 169 | ```html 170 | 171 |

Please Login

172 |
173 |

You have successfully logged in, congrats!

174 | Continue 175 |
176 |
177 | ``` 178 | 179 | All elements that have a `slot` attribute will be segmented into your components props, using the provided `slot="{value}"` as the key, e.g: 180 | 181 | ```tsx 182 | function LoginForm({ successMessage }) { 183 | const [isLoggedIn, setLoggedIn] = useState(false); 184 | 185 | return ( 186 | 187 | {isLoggedIn && successMessage} 188 |
setLoggedIn(true)}>{/*[...]*/}
189 |
190 | ); 191 | } 192 | ``` 193 | 194 | It's important to note that **slot keys will be normalised into camelCase**, for example: `slot="my-slot"` will be accessed via `mySlot` in your component's props. It's recommended to use camelCase for slot keys, but this isn't always possible. `reactement` will do it's best to handle all common casing conventions, e.g kebab-case, snake_case and PascalCase. Slot values can be either primitive strings, or full HTML structures, as seen in the example above. 195 | 196 | ## Options 197 | 198 | `define` has a third argument, "options". For example: 199 | 200 | ```javascript 201 | define('hero-banner', () => HeroBanner, { 202 | /*[options]*/ 203 | }); 204 | ``` 205 | 206 | ### attributes 207 | 208 | If you require custom attributes to be passed down to your component, you'll need to specify them in this array. For example: 209 | 210 | ```javascript 211 | define('hero-banner', () => HeroBanner, { attributes: ['banner-title'] }); 212 | ``` 213 | 214 | And the custom element will look like the following: 215 | 216 | ```html 217 | 218 | ``` 219 | 220 | ### formatProps 221 | 222 | This allows you to provide a function to process or format your props prior to the component being rendered. One use case is changing property casings. If the data provided by your server uses Pascal, but your components make use of the standard camelCase, this function will allow you to consolidate them. 223 | 224 | ### wrapComponent 225 | 226 | If you need to wrap your component prior to render with a higher order function, you can provide it here. For example, if you asynchronously resolve your component, but also make use of Redux, you'll need to provide a `wrapComponent` function to apply the Provider HOC etc. It can also be useful for themeing, or other use cases. 227 | 228 | ## Useful things 229 | 230 | By default, all components will be provided with a `parent` prop. This is a reference to the root element that the component has been rendered within. This can be useful when working with Web Components, or you wish to apply changes to the custom element. This will **only be defined when run on the client**. 231 | 232 | ## ES5 Support 233 | 234 | To support ES5 or older browsers, like IE11, you'll need to either transpile `reactement`, or import the ES5 version via `reactement/es5`, while also installing the official Web Component [Custom Element polyfill](https://www.npmjs.com/package/@webcomponents/custom-elements). Once installed, you'll need to import the following at the very top of your entry files: 235 | 236 | ```javascript 237 | import '@webcomponents/custom-elements'; 238 | import '@webcomponents/custom-elements/src/native-shim'; 239 | ``` 240 | 241 | # Acknowledgement 242 | 243 | This function takes _heavy_ inspiration from the excellent [preact-custom-element](https://github.com/preactjs/preact-custom-element). That library served as a starting point for this package, and all of the Preact guys deserve a massive dose of gratitude. I had slightly different needs, so decided to build this as part solution, part learning excersize. 244 | -------------------------------------------------------------------------------- /packages/reactement/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | [ 4 | '@babel/preset-env', 5 | { 6 | targets: { 7 | node: 'current', 8 | }, 9 | }, 10 | ], 11 | ], 12 | }; 13 | -------------------------------------------------------------------------------- /packages/reactement/jest.config.js: -------------------------------------------------------------------------------- 1 | /* ----------------------------------- 2 | * 3 | * Jest 4 | * 5 | * -------------------------------- */ 6 | 7 | module.exports = { 8 | testEnvironment: 'jsdom', 9 | globals: { __DEV__: true }, 10 | roots: [''], 11 | collectCoverage: true, 12 | collectCoverageFrom: ['/src/**/*.{ts,tsx}'], 13 | coverageDirectory: '/tests/coverage', 14 | coveragePathIgnorePatterns: ['/node_modules/', '(.*).d.ts'], 15 | setupFilesAfterEnv: ['/tests/setup.ts'], 16 | coverageThreshold: { 17 | global: { 18 | statements: 97, 19 | branches: 86, 20 | functions: 100, 21 | lines: 97, 22 | }, 23 | }, 24 | transform: { 25 | '^.+\\.tsx?$': 'ts-jest', 26 | '^.+\\js$': 'babel-jest', 27 | }, 28 | }; 29 | -------------------------------------------------------------------------------- /packages/reactement/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "reactement", 3 | "version": "1.1.4", 4 | "author": "James Hill ", 5 | "homepage": "https://github.com/jahilldev/component-elements/tree/main/packages/reactement#readme", 6 | "license": "MIT", 7 | "main": "./dist/define.es5.js", 8 | "types": "./dist/define.d.ts", 9 | "engines": { 10 | "node": ">=10" 11 | }, 12 | "exports": { 13 | ".": "./dist/define.js", 14 | "./es5": { 15 | "type": "./dist/define.d.ts", 16 | "import": "./dist/define.es5.js", 17 | "require": "./dist/define.es5.js" 18 | } 19 | }, 20 | "typesVersions": { 21 | "*": { 22 | "es5": [ 23 | "./dist/define.d.ts" 24 | ] 25 | } 26 | }, 27 | "keywords": [ 28 | "react", 29 | "custom elements", 30 | "web components", 31 | "virtual dom", 32 | "partial hydration", 33 | "universal", 34 | "isomorphic", 35 | "hydrate", 36 | "component" 37 | ], 38 | "directories": { 39 | "lib": "dist", 40 | "test": "tests" 41 | }, 42 | "files": [ 43 | "dist" 44 | ], 45 | "repository": { 46 | "type": "git", 47 | "url": "git+https://github.com/jahilldev/component-elements.git" 48 | }, 49 | "scripts": { 50 | "start": "run-s clean watch", 51 | "build": "webpack --mode=production", 52 | "watch": "webpack --watch", 53 | "clean": "rimraf ./dist", 54 | "lint": "eslint", 55 | "test": "jest" 56 | }, 57 | "bugs": { 58 | "url": "https://github.com/jahilldev/component-elements/issues" 59 | }, 60 | "peerDependencies": { 61 | "react": ">=16", 62 | "react-dom": ">=16" 63 | }, 64 | "devDependencies": { 65 | "@component-elements/shared": "1.0.0", 66 | "@types/enzyme": "^3.10.12", 67 | "enzyme": "^3.11.0", 68 | "enzyme-adapter-react-16": "^1.15.7", 69 | "react": "^16.14.0", 70 | "react-dom": "^16.14.0" 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /packages/reactement/src/define.ts: -------------------------------------------------------------------------------- 1 | import React, { createElement, ComponentFactory, FunctionComponent } from 'react'; 2 | import { render } from 'react-dom'; 3 | import { 4 | IProps, 5 | ErrorTypes, 6 | CustomElement, 7 | isPromise, 8 | parseJson, 9 | getElementTag, 10 | getPropKey, 11 | getElementAttributes, 12 | getAsyncComponent, 13 | } from '@component-elements/shared'; 14 | import { parseHtml } from './parse'; 15 | import { IOptions, ComponentFunction } from './model'; 16 | 17 | /* ----------------------------------- 18 | * 19 | * Define 20 | * 21 | * -------------------------------- */ 22 | 23 | function define

( 24 | tagName: string, 25 | child: ComponentFunction

, 26 | options: IOptions = {} 27 | ): FunctionComponent

{ 28 | const { wrapComponent } = options; 29 | const preRender = typeof window === 'undefined'; 30 | const elementTag = getElementTag(tagName); 31 | 32 | if (!preRender) { 33 | customElements.define(elementTag, setupElement(child, options)); 34 | 35 | return; 36 | } 37 | 38 | const content = child(); 39 | 40 | if (isPromise(content)) { 41 | throw new Error(`${ErrorTypes.Promise} : <${tagName}>`); 42 | } 43 | 44 | let component = content; 45 | 46 | if (wrapComponent) { 47 | component = wrapComponent(content); 48 | } 49 | 50 | return (props: P) => 51 | createElement(elementTag, { server: true }, [ 52 | createElement('script', { 53 | type: 'application/json', 54 | dangerouslySetInnerHTML: { __html: JSON.stringify(props) }, 55 | }), 56 | createElement(component, props), 57 | ]); 58 | } 59 | 60 | /* ----------------------------------- 61 | * 62 | * Setup 63 | * 64 | * -------------------------------- */ 65 | 66 | function setupElement(component: ComponentFunction, options: IOptions = {}): any { 67 | const { attributes = [] } = options; 68 | 69 | if (typeof Reflect !== 'undefined' && Reflect.construct) { 70 | const CustomElement = function () { 71 | const element = Reflect.construct(HTMLElement, [], CustomElement); 72 | 73 | element.__mounted = false; 74 | element.__component = component; 75 | element.__properties = {}; 76 | element.__slots = {}; 77 | element.__children = void 0; 78 | element.__options = options; 79 | 80 | return element; 81 | }; 82 | 83 | CustomElement.observedAttributes = ['props', ...attributes]; 84 | 85 | CustomElement.prototype = Object.create(HTMLElement.prototype); 86 | CustomElement.prototype.constructor = CustomElement; 87 | CustomElement.prototype.connectedCallback = onConnected; 88 | CustomElement.prototype.attributeChangedCallback = onAttributeChange; 89 | CustomElement.prototype.disconnectedCallback = onDisconnected; 90 | 91 | return CustomElement; 92 | } 93 | 94 | return class CustomElement extends HTMLElement { 95 | __mounted = false; 96 | __component = component; 97 | __properties = {}; 98 | __slots = {}; 99 | __children = void 0; 100 | __options = options; 101 | 102 | static observedAttributes = ['props', ...attributes]; 103 | 104 | public connectedCallback() { 105 | onConnected.call(this); 106 | } 107 | 108 | public attributeChangedCallback(...args) { 109 | onAttributeChange.call(this, ...args); 110 | } 111 | 112 | public disconnectedCallback() { 113 | onDisconnected.call(this); 114 | } 115 | }; 116 | } 117 | 118 | /* ----------------------------------- 119 | * 120 | * Connected 121 | * 122 | * -------------------------------- */ 123 | 124 | function onConnected(this: CustomElement) { 125 | const attributes = getElementAttributes.call(this); 126 | const props = this.getAttribute('props'); 127 | const json = this.querySelector('[type="application/json"]'); 128 | const data = parseJson.call(this, props || json?.innerHTML || '{}'); 129 | 130 | json?.remove(); 131 | 132 | let children = this.__children; 133 | 134 | if (!this.__mounted && !this.hasAttribute('server')) { 135 | children = createElement(parseHtml.call(this), {}); 136 | } 137 | 138 | this.__properties = { ...this.__slots, ...data, ...attributes }; 139 | this.__children = children || []; 140 | 141 | this.removeAttribute('server'); 142 | this.innerHTML = ''; 143 | 144 | const response = this.__component(); 145 | 146 | const renderer = (result: ComponentFactory) => 147 | finaliseComponent.call(this, result); 148 | 149 | if (isPromise(response)) { 150 | getAsyncComponent(response, this.tagName).then(renderer); 151 | 152 | return; 153 | } 154 | 155 | renderer(response); 156 | } 157 | 158 | /* ----------------------------------- 159 | * 160 | * Attribute 161 | * 162 | * -------------------------------- */ 163 | 164 | function onAttributeChange(this: CustomElement, name: string, original: string, updated: string) { 165 | if (!this.__mounted) { 166 | return; 167 | } 168 | 169 | updated = updated == null ? void 0 : updated; 170 | 171 | let props = this.__properties; 172 | 173 | if (name === 'props') { 174 | props = { ...props, ...parseJson.call(this, updated) }; 175 | } else { 176 | props[getPropKey(name)] = updated; 177 | } 178 | 179 | this.__properties = props; 180 | 181 | render( 182 | createElement(this.__instance, { ...props, parent: this, children: this.__children }), 183 | this 184 | ); 185 | } 186 | 187 | /* ----------------------------------- 188 | * 189 | * Disconnected 190 | * 191 | * -------------------------------- */ 192 | 193 | function onDisconnected(this: CustomElement) { 194 | render(null, this); 195 | } 196 | 197 | /* ----------------------------------- 198 | * 199 | * Finalise 200 | * 201 | * -------------------------------- */ 202 | 203 | function finaliseComponent(this: CustomElement, component: ComponentFactory) { 204 | const { tagName } = this; 205 | const { wrapComponent } = this.__options; 206 | 207 | if (!component) { 208 | console.error(ErrorTypes.Missing, `: <${tagName.toLowerCase()}>`); 209 | 210 | return; 211 | } 212 | 213 | if (wrapComponent) { 214 | component = wrapComponent(component); 215 | } 216 | 217 | this.__instance = component; 218 | this.__mounted = true; 219 | 220 | const props = { 221 | ...this.__properties, 222 | parent: this, 223 | children: this.__children, 224 | }; 225 | 226 | render(createElement(component, props), this); 227 | } 228 | 229 | /* ----------------------------------- 230 | * 231 | * Export 232 | * 233 | * -------------------------------- */ 234 | 235 | export { define }; 236 | -------------------------------------------------------------------------------- /packages/reactement/src/model.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFactory } from 'react'; 2 | 3 | /* ----------------------------------- 4 | * 5 | * Types 6 | * 7 | * -------------------------------- */ 8 | 9 | type ComponentFunction

= () => ComponentResult

; 10 | type ComponentResult

= ComponentFactory | ComponentAsync

; 11 | type ComponentAsync

= 12 | | Promise> 13 | | Promise<{ [index: string]: ComponentFactory }>; 14 | 15 | /* ----------------------------------- 16 | * 17 | * IOptions 18 | * 19 | * -------------------------------- */ 20 | 21 | interface IOptions { 22 | attributes?: string[]; 23 | formatProps?:

(props: P) => P; 24 | wrapComponent?:

(child: ComponentFactory) => any; 25 | } 26 | 27 | /* ----------------------------------- 28 | * 29 | * Export 30 | * 31 | * -------------------------------- */ 32 | 33 | export { IOptions, ComponentFunction, ComponentResult, ComponentAsync }; 34 | -------------------------------------------------------------------------------- /packages/reactement/src/parse.ts: -------------------------------------------------------------------------------- 1 | import React, { createElement, ComponentFactory, Fragment } from 'react'; 2 | import { 3 | CustomElement, 4 | getDocument, 5 | getAttributeObject, 6 | selfClosingTags, 7 | getPropKey, 8 | } from '@component-elements/shared'; 9 | 10 | /* ----------------------------------- 11 | * 12 | * parseHtml 13 | * 14 | * -------------------------------- */ 15 | 16 | function parseHtml(this: CustomElement): ComponentFactory<{}, any> { 17 | const dom = getDocument(this.innerHTML); 18 | 19 | if (!dom) { 20 | return void 0; 21 | } 22 | 23 | const result = convertToVDom.call(this, dom); 24 | 25 | return () => result; 26 | } 27 | 28 | /* ----------------------------------- 29 | * 30 | * convertToVDom 31 | * 32 | * -------------------------------- */ 33 | 34 | function convertToVDom(this: CustomElement, node: Element) { 35 | if (node.nodeType === 3) { 36 | return node.textContent?.trim() || ''; 37 | } 38 | 39 | if (node.nodeType !== 1) { 40 | return null; 41 | } 42 | 43 | const nodeName = String(node.nodeName).toLowerCase(); 44 | const childNodes = Array.from(node.childNodes); 45 | 46 | const children = () => childNodes.map((child) => convertToVDom.call(this, child)); 47 | const { slot, ...attributes } = getAttributeObject(node.attributes); 48 | const props = { ...attributes, key: Math.random() }; 49 | 50 | if (nodeName === 'script') { 51 | return null; 52 | } 53 | 54 | if (nodeName === 'body') { 55 | return createElement(Fragment, { key: 'body' }, children()); 56 | } 57 | 58 | if (selfClosingTags.includes(nodeName)) { 59 | return createElement(nodeName, props); 60 | } 61 | 62 | if (slot) { 63 | this.__slots[getPropKey(slot)] = getSlotChildren(children()); 64 | 65 | return null; 66 | } 67 | 68 | return createElement(nodeName, props, children()); 69 | } 70 | 71 | /* ----------------------------------- 72 | * 73 | * getSlotChildren 74 | * 75 | * -------------------------------- */ 76 | 77 | function getSlotChildren(children: JSX.Element[]) { 78 | const isString = (item) => typeof item === 'string'; 79 | 80 | if (children.every(isString)) { 81 | return children.join(' '); 82 | } 83 | 84 | return createElement(Fragment, {}, children); 85 | } 86 | 87 | /* ----------------------------------- 88 | * 89 | * Export 90 | * 91 | * -------------------------------- */ 92 | 93 | export { parseHtml }; 94 | -------------------------------------------------------------------------------- /packages/reactement/tests/define.spec.tsx: -------------------------------------------------------------------------------- 1 | import React, { createElement, Fragment, ComponentFactory } from 'react'; 2 | import { mount, shallow } from 'enzyme'; 3 | import { define } from '../src/define'; 4 | 5 | /* ----------------------------------- 6 | * 7 | * Promises 8 | * 9 | * -------------------------------- */ 10 | 11 | function flushPromises() { 12 | return new Promise((resolve) => setTimeout(resolve, 0)); 13 | } 14 | 15 | /* ----------------------------------- 16 | * 17 | * IProps 18 | * 19 | * -------------------------------- */ 20 | 21 | interface IProps { 22 | customTitle?: string; 23 | value: string; 24 | children?: any; 25 | } 26 | 27 | /* ----------------------------------- 28 | * 29 | * Component 30 | * 31 | * -------------------------------- */ 32 | 33 | function Message({ customTitle, value, children }: IProps) { 34 | return ( 35 | 36 | {customTitle &&

{customTitle}

} 37 | {value} 38 | {children} 39 | 40 | ); 41 | } 42 | 43 | /* ----------------------------------- 44 | * 45 | * Define 46 | * 47 | * -------------------------------- */ 48 | 49 | describe('define()', () => { 50 | const { document } = globalThis; 51 | const errorSpy = jest.spyOn(console, 'error').mockImplementation(() => {}); 52 | 53 | describe('when run in the browser', () => { 54 | let root; 55 | 56 | beforeEach(() => { 57 | root = document.createElement('div'); 58 | document.body.appendChild(root); 59 | }); 60 | 61 | afterEach(() => { 62 | document.body.removeChild(root); 63 | }); 64 | 65 | it('validates tag name value with prefix if needed', () => { 66 | const props = { value: 'propsValue' }; 67 | 68 | define('message', () => Message); 69 | 70 | const element = document.createElement('component-message'); 71 | 72 | element.setAttribute('props', JSON.stringify(props)); 73 | 74 | root.appendChild(element); 75 | 76 | expect(root.innerHTML).toContain(`${props.value}`); 77 | }); 78 | 79 | it('renders component correctly when from props attribute', async () => { 80 | const props = { value: 'propsValue' }; 81 | 82 | define('message-one', () => Message); 83 | 84 | const element = document.createElement('message-one'); 85 | 86 | element.setAttribute('props', JSON.stringify(props)); 87 | 88 | root.appendChild(element); 89 | 90 | expect(root.innerHTML).toContain(`${props.value}`); 91 | }); 92 | 93 | it('renders component correctly when from json script block', async () => { 94 | const props = { value: 'jsonValue' }; 95 | const json = ``; 96 | 97 | define('message-two', () => Message); 98 | 99 | const element = document.createElement('message-two'); 100 | 101 | element.innerHTML = json; 102 | 103 | root.appendChild(element); 104 | 105 | expect(root.innerHTML).toContain(`${props.value}`); 106 | }); 107 | 108 | it('sets contained HTML as children prop when not server rendered', async () => { 109 | const props = { value: 'childMarkup' }; 110 | const json = ``; 111 | const html = '

Testing


'; 112 | 113 | define('message-three', () => Message); 114 | 115 | const element = document.createElement('message-three'); 116 | 117 | element.innerHTML = json + html; 118 | 119 | root.appendChild(element); 120 | 121 | expect(root.innerHTML).toContain(`${props.value}${html}`); 122 | }); 123 | 124 | it('does not use contained HTML if server rendered', async () => { 125 | const props = { value: 'serverRender' }; 126 | const json = ``; 127 | const html = '

Server rendered!

'; 128 | 129 | define('message-four', () => Message); 130 | 131 | const element = document.createElement('message-four'); 132 | 133 | element.setAttribute('server', ''); 134 | element.innerHTML = json + html; 135 | 136 | root.appendChild(element); 137 | 138 | expect(root.innerHTML).toContain(`${props.value}`); 139 | }); 140 | 141 | it('renders component asynchronously when provided', async () => { 142 | const props = { value: 'asyncValue' }; 143 | const json = ``; 144 | 145 | define('message-five', () => Promise.resolve(Message)); 146 | 147 | const element = document.createElement('message-five'); 148 | 149 | element.innerHTML = json; 150 | 151 | root.appendChild(element); 152 | 153 | await flushPromises(); 154 | 155 | expect(root.innerHTML).toContain(`${props.value}`); 156 | }); 157 | 158 | it('tries to infer the component if not explicitly returned', async () => { 159 | const props = { value: 'inferValue' }; 160 | const json = ``; 161 | 162 | define('message-six', () => Promise.resolve({ MessageSix: Message })); 163 | 164 | const element = document.createElement('message-six'); 165 | 166 | element.innerHTML = json; 167 | 168 | root.appendChild(element); 169 | 170 | await flushPromises(); 171 | 172 | expect(root.innerHTML).toContain(`${props.value}`); 173 | }); 174 | 175 | it('merges defined attributes in array with component props', () => { 176 | const customTitle = 'customTitle'; 177 | const props = { value: 'attrProps' }; 178 | const json = ``; 179 | 180 | define('message-seven', () => Message, { attributes: ['custom-title'] }); 181 | 182 | const element = document.createElement('message-seven'); 183 | 184 | element.setAttribute('custom-title', customTitle); 185 | element.innerHTML = json; 186 | 187 | root.appendChild(element); 188 | 189 | expect(root.innerHTML).toContain(`

${customTitle}

${props.value}`); 190 | }); 191 | 192 | it('errors if component cannot be found in function', async () => { 193 | define('message-eight', () => Promise.resolve({ Message })); 194 | 195 | const element = document.createElement('message-eight'); 196 | 197 | root.appendChild(element); 198 | 199 | await flushPromises(); 200 | 201 | expect(errorSpy).toBeCalled(); 202 | expect(element.innerHTML).toEqual(''); 203 | }); 204 | 205 | it('updates component props when attributes are changed', () => { 206 | const customTitle = 'customTitle'; 207 | const updatedProp = 'updated!'; 208 | const props = { value: 'attrUpdate' }; 209 | const html = ''; 210 | 211 | define('message-nine', () => Message, { attributes: ['custom-title'] }); 212 | 213 | const element = document.createElement('message-nine'); 214 | 215 | element.setAttribute('custom-title', customTitle); 216 | element.setAttribute('props', JSON.stringify(props)); 217 | 218 | element.innerHTML = html; 219 | 220 | root.appendChild(element); 221 | 222 | expect(root.innerHTML).toContain(`

${customTitle}

${props.value}${html}`); 223 | 224 | element.setAttribute('custom-title', ''); 225 | element.setAttribute('props', JSON.stringify({ ...props, value: updatedProp })); 226 | 227 | expect(root.innerHTML).toContain(`${updatedProp}${html}`); 228 | }); 229 | 230 | it('wraps component in an HOC if provided', () => { 231 | const props = { value: 'wrapComponent' }; 232 | const json = ``; 233 | 234 | const wrapComponent = (child: ComponentFactory) => (props: any) => 235 | createElement('section', {}, createElement(child, props)); 236 | 237 | define('message-ten', () => Message, { wrapComponent }); 238 | 239 | const element = document.createElement('message-ten'); 240 | 241 | element.innerHTML = json; 242 | 243 | root.appendChild(element); 244 | 245 | expect(root.innerHTML).toContain(`
${props.value}
`); 246 | }); 247 | 248 | it('correctly passes props through formatProps if provided', () => { 249 | const props = { Value: 'formatProps' }; 250 | const json = ``; 251 | 252 | const formatProps = (props: any) => { 253 | const keys = Object.keys(props); 254 | 255 | return keys.reduce((result, key) => { 256 | result[key.toLowerCase()] = props[key]; 257 | 258 | return result; 259 | }, {}); 260 | }; 261 | 262 | define('message-eleven', () => Message, { formatProps }); 263 | 264 | const element = document.createElement('message-eleven'); 265 | 266 | element.innerHTML = json; 267 | 268 | root.appendChild(element); 269 | 270 | expect(root.innerHTML).toContain(`${props.Value}`); 271 | }); 272 | 273 | it('correctly segments <* slot="{key}" /> elements into props', () => { 274 | const customTitle = 'customTitle'; 275 | const html = `
${customTitle}
`; 276 | 277 | define('message-twelve', () => Message); 278 | 279 | const element = document.createElement('message-twelve'); 280 | 281 | element.innerHTML = html; 282 | 283 | root.appendChild(element); 284 | 285 | expect(root.innerHTML).toContain(`

${customTitle}

`); 286 | }); 287 | 288 | it('correctly caches children when moved in the DOM', () => { 289 | const customTitle = 'customTitle'; 290 | const customText = 'Lorem ipsum dolor'; 291 | const html = `
${customTitle}

${customText}

`; 292 | 293 | define('message-thirteen', () => Message); 294 | 295 | const element = document.createElement('message-thirteen'); 296 | const wrapper = document.createElement('main'); 297 | 298 | element.innerHTML = html; 299 | 300 | root.appendChild(element); 301 | 302 | element.remove(); 303 | 304 | expect(root.innerHTML).toContain(''); 305 | 306 | root.appendChild(wrapper); 307 | wrapper.appendChild(element); 308 | 309 | expect(root.innerHTML).toContain(`

${customTitle}

${customText}

`); 310 | }); 311 | }); 312 | 313 | describe('when run in the browser (no "Reflect.construct")', () => { 314 | const { document, Reflect } = globalThis; 315 | let root; 316 | 317 | beforeAll(() => { 318 | delete (globalThis as any).Reflect; 319 | }); 320 | 321 | beforeEach(() => { 322 | root = document.createElement('div'); 323 | document.body.appendChild(root); 324 | }); 325 | 326 | afterEach(() => { 327 | document.body.removeChild(root); 328 | }); 329 | 330 | afterAll(() => { 331 | globalThis.Reflect = Reflect; 332 | }); 333 | 334 | it('renders component correctly', () => { 335 | const customTitle = 'customTitle'; 336 | const props = { value: 'attrUpdate' }; 337 | const json = ``; 338 | const html = ''; 339 | 340 | define('message-class', () => Message, { attributes: ['custom-title'] }); 341 | 342 | const element = document.createElement('message-class'); 343 | 344 | element.setAttribute('custom-title', customTitle); 345 | element.innerHTML = json + html; 346 | 347 | root.appendChild(element); 348 | 349 | expect(root.innerHTML).toContain(`

${customTitle}

${props.value}${html}`); 350 | 351 | element.setAttribute('custom-title', ''); 352 | 353 | expect(root.innerHTML).toContain(`${props.value}${html}`); 354 | }); 355 | }); 356 | 357 | describe('when run on the server', () => { 358 | const { window } = globalThis; 359 | 360 | beforeAll(() => { 361 | delete (globalThis as any).window; 362 | }); 363 | 364 | afterAll(() => { 365 | globalThis.window = window; 366 | }); 367 | 368 | it('returns the correct markup', () => { 369 | const props = { value: 'serverValue' }; 370 | const component = define('message-one', () => Message); 371 | 372 | const instance = shallow(createElement(component, props)); 373 | 374 | expect(instance.find('message-one').length).toEqual(1); 375 | expect(instance.find('message-one').prop('server')).toEqual(true); 376 | }); 377 | 378 | it('throws an error when used with a promise', () => { 379 | expect(() => define('message-two', () => Promise.resolve(Message))).toThrow(); 380 | }); 381 | 382 | // it('includes a json script block with props', () => { 383 | // const props = { value: 'serverValue' }; 384 | // const component = define('message-three', () => Message); 385 | 386 | // const instance = shallow(createElement(component, props) as any); 387 | 388 | // expect(instance.find('script').text()).toEqual(JSON.stringify(props)); 389 | // }); 390 | }); 391 | }); 392 | -------------------------------------------------------------------------------- /packages/reactement/tests/parse.spec.ts: -------------------------------------------------------------------------------- 1 | import React, { createElement } from 'react'; 2 | import { mount } from 'enzyme'; 3 | import { parseHtml } from '../src/parse'; 4 | 5 | /* ----------------------------------- 6 | * 7 | * Variables 8 | * 9 | * -------------------------------- */ 10 | 11 | const testHeading = 'testHeading'; 12 | const testWhitespace = ' '; 13 | const testHtml = `

${testHeading}


Hello

`; 14 | const testScript = ``; 15 | const testData = { testHeading }; 16 | 17 | /* ----------------------------------- 18 | * 19 | * Parse 20 | * 21 | * -------------------------------- */ 22 | 23 | describe('parse', () => { 24 | describe('parseHtml()', () => { 25 | it('should correctly handle misformed html', () => { 26 | const testText = 'testText'; 27 | const result = parseHtml.call({ innerHTML: `

${testText}` }); 28 | const instance = mount(createElement(result, {}) as any); 29 | 30 | expect(instance.html()).toEqual(`

${testText}

`); 31 | }); 32 | 33 | it('handles text values witin custom element', () => { 34 | const result = parseHtml.call({ innerHTML: testHeading }); 35 | const instance = mount(createElement(result, {}) as any); 36 | 37 | expect(instance.text()).toEqual(testHeading); 38 | }); 39 | 40 | it('handles whitespace within custom element', () => { 41 | const result = parseHtml.call({ innerHTML: testWhitespace }); 42 | const instance = mount(createElement(result, {}) as any); 43 | 44 | expect(instance.text()).toEqual(''); 45 | expect(instance.html()).toEqual(''); 46 | }); 47 | 48 | it('removes script blocks for security', () => { 49 | const result = parseHtml.call({ innerHTML: testScript }); 50 | const instance = mount(createElement(result, {}) as any); 51 | 52 | expect(instance.text()).toEqual(''); 53 | }); 54 | 55 | it('correctly converts an HTML string into a VDom tree', () => { 56 | const result = parseHtml.call({ innerHTML: testHtml }); 57 | const instance = mount(createElement(result, {}) as any); 58 | 59 | expect(instance.find('h1').text()).toEqual(testHeading); 60 | }); 61 | 62 | it('should remove <* slot="{key}"> and apply to props', () => { 63 | const slots = {}; 64 | const slotKey = 'slotKey'; 65 | const slotValue = 'slotValue'; 66 | 67 | const slotHtml = `${slotValue}`; 68 | const headingHtml = `

${testHeading}

`; 69 | const testHtml = `
${headingHtml}${slotHtml}
`; 70 | 71 | const result = parseHtml.call({ innerHTML: testHtml, __slots: slots }); 72 | const instance = mount(createElement(result, {}) as any); 73 | 74 | expect(instance.html()).toEqual(`
${headingHtml}
`); 75 | expect(slots).toEqual({ [slotKey]: slotValue }); 76 | }); 77 | }); 78 | }); 79 | -------------------------------------------------------------------------------- /packages/reactement/tests/setup.ts: -------------------------------------------------------------------------------- 1 | import { configure } from 'enzyme'; 2 | import Adapter from 'enzyme-adapter-react-16'; 3 | 4 | /* ----------------------------------- 5 | * 6 | * Setup 7 | * 8 | * -------------------------------- */ 9 | 10 | configure({ adapter: new Adapter() }); 11 | -------------------------------------------------------------------------------- /packages/reactement/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "./dist", 5 | "jsx": "react" 6 | }, 7 | "include": ["./src"] 8 | } 9 | -------------------------------------------------------------------------------- /packages/reactement/webpack.config.ts: -------------------------------------------------------------------------------- 1 | import { Configuration, DefinePlugin } from 'webpack'; 2 | import nodeExternals from 'webpack-node-externals'; 3 | import * as path from 'path'; 4 | 5 | /* ----------------------------------- 6 | * 7 | * Output 8 | * 9 | * -------------------------------- */ 10 | 11 | const outputFiles = [ 12 | { target: 'es5', filename: '[name].es5.js' }, 13 | { target: 'es2016', filename: '[name].js' }, 14 | ]; 15 | 16 | /* ----------------------------------- 17 | * 18 | * Default 19 | * 20 | * -------------------------------- */ 21 | 22 | const defaultConfig = { 23 | entry: { 24 | define: path.join(__dirname, './src/define.ts'), 25 | }, 26 | externals: [ 27 | nodeExternals({ 28 | allowlist: ['@component-elements/shared'], 29 | modulesDir: path.resolve(__dirname, '../../node_modules'), 30 | }), 31 | ], 32 | context: path.join(__dirname, './src'), 33 | output: { 34 | path: path.join(__dirname, './dist'), 35 | filename: '[name].js', 36 | libraryTarget: 'umd', 37 | globalObject: 'this', 38 | chunkFormat: 'commonjs', 39 | }, 40 | resolve: { 41 | extensions: ['.js', '.ts', '.tsx', 'json'], 42 | }, 43 | node: { 44 | __filename: true, 45 | __dirname: true, 46 | }, 47 | stats: { 48 | colors: true, 49 | timings: true, 50 | }, 51 | }; 52 | 53 | /* ----------------------------------- 54 | * 55 | * Config 56 | * 57 | * -------------------------------- */ 58 | 59 | const config = ({ mode }): Configuration[] => 60 | outputFiles.map(({ target, filename, ...config }) => ({ 61 | ...defaultConfig, 62 | mode, 63 | target, 64 | devtool: mode === 'development' ? 'eval-source-map' : void 0, 65 | cache: mode === 'development', 66 | output: { 67 | ...defaultConfig.output, 68 | filename, 69 | }, 70 | module: { 71 | rules: [ 72 | { 73 | test: /\.ts$/, 74 | use: [ 75 | { 76 | loader: 'ts-loader', 77 | options: { 78 | compilerOptions: { 79 | target, 80 | }, 81 | }, 82 | }, 83 | ], 84 | }, 85 | { 86 | test: /\.m?js$/, 87 | use: { 88 | loader: 'babel-loader', 89 | options: { 90 | ...(target === 'es5' && { presets: ['@babel/preset-env'] }), 91 | }, 92 | }, 93 | }, 94 | ], 95 | }, 96 | performance: { 97 | hints: mode === 'development' ? 'warning' : void 0, 98 | }, 99 | plugins: [ 100 | new DefinePlugin({ 101 | __DEV__: mode === 'development', 102 | }), 103 | ], 104 | ...config, 105 | })); 106 | 107 | /* ----------------------------------- 108 | * 109 | * Export 110 | * 111 | * -------------------------------- */ 112 | 113 | module.exports = config; 114 | -------------------------------------------------------------------------------- /packages/reactement/yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | "@types/cheerio@*": 6 | "integrity" "sha512-Kt7Cdjjdi2XWSfrZ53v4Of0wG3ZcmaegFXjMmz9tfNrZSkzzo36G0AL1YqSdcIA78Etjt6E609pt5h1xnQkPUw==" 7 | "resolved" "https://registry.npmjs.org/@types/cheerio/-/cheerio-0.22.31.tgz" 8 | "version" "0.22.31" 9 | dependencies: 10 | "@types/node" "*" 11 | 12 | "@types/enzyme@^3.10.11": 13 | "integrity" "sha512-LEtC7zXsQlbGXWGcnnmOI7rTyP+i1QzQv4Va91RKXDEukLDaNyxu0rXlfMiGEhJwfgTPCTb0R+Pnlj//oM9e/w==" 14 | "resolved" "https://registry.npmjs.org/@types/enzyme/-/enzyme-3.10.11.tgz" 15 | "version" "3.10.11" 16 | dependencies: 17 | "@types/cheerio" "*" 18 | "@types/react" "*" 19 | 20 | "@types/node@*": 21 | "integrity" "sha512-eKj4f/BsN/qcculZiRSujogjvp5O/k4lOW5m35NopjZM/QwLOR075a8pJW5hD+Rtdm2DaCVPENS6KtSQnUD6BA==" 22 | "resolved" "https://registry.npmjs.org/@types/node/-/node-17.0.18.tgz" 23 | "version" "17.0.18" 24 | 25 | "@types/prop-types@*": 26 | "integrity" "sha512-rZ5drC/jWjrArrS8BR6SIr4cWpW09RNTYt9AMZo3Jwwif+iacXAqgVjm0B0Bv/S1jhDXKHqRVNCbACkJ89RAnQ==" 27 | "resolved" "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.4.tgz" 28 | "version" "15.7.4" 29 | 30 | "@types/react@*": 31 | "integrity" "sha512-UVavlfAxDd/AgAacMa60Azl7ygyQNRwC/DsHZmKgNvPmRR5p70AJ5Q9EAmL2NWOJmeV+vVUI4IAP7GZrN8h8Ug==" 32 | "resolved" "https://registry.npmjs.org/@types/react/-/react-17.0.39.tgz" 33 | "version" "17.0.39" 34 | dependencies: 35 | "@types/prop-types" "*" 36 | "@types/scheduler" "*" 37 | "csstype" "^3.0.2" 38 | 39 | "@types/scheduler@*": 40 | "integrity" "sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew==" 41 | "resolved" "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.2.tgz" 42 | "version" "0.16.2" 43 | 44 | "airbnb-prop-types@^2.16.0": 45 | "integrity" "sha512-7WHOFolP/6cS96PhKNrslCLMYAI8yB1Pp6u6XmxozQOiZbsI5ycglZr5cHhBFfuRcQQjzCMith5ZPZdYiJCxUg==" 46 | "resolved" "https://registry.npmjs.org/airbnb-prop-types/-/airbnb-prop-types-2.16.0.tgz" 47 | "version" "2.16.0" 48 | dependencies: 49 | "array.prototype.find" "^2.1.1" 50 | "function.prototype.name" "^1.1.2" 51 | "is-regex" "^1.1.0" 52 | "object-is" "^1.1.2" 53 | "object.assign" "^4.1.0" 54 | "object.entries" "^1.1.2" 55 | "prop-types" "^15.7.2" 56 | "prop-types-exact" "^1.2.0" 57 | "react-is" "^16.13.1" 58 | 59 | "array.prototype.filter@^1.0.0": 60 | "integrity" "sha512-Dk3Ty7N42Odk7PjU/Ci3zT4pLj20YvuVnneG/58ICM6bt4Ij5kZaJTVQ9TSaWaIECX2sFyz4KItkVZqHNnciqw==" 61 | "resolved" "https://registry.npmjs.org/array.prototype.filter/-/array.prototype.filter-1.0.1.tgz" 62 | "version" "1.0.1" 63 | dependencies: 64 | "call-bind" "^1.0.2" 65 | "define-properties" "^1.1.3" 66 | "es-abstract" "^1.19.0" 67 | "es-array-method-boxes-properly" "^1.0.0" 68 | "is-string" "^1.0.7" 69 | 70 | "array.prototype.find@^2.1.1": 71 | "integrity" "sha512-sn40qmUiLYAcRb/1HsIQjTTZ1kCy8II8VtZJpMn2Aoen9twULhbWXisfh3HimGqMlHGUul0/TfKCnXg42LuPpQ==" 72 | "resolved" "https://registry.npmjs.org/array.prototype.find/-/array.prototype.find-2.2.0.tgz" 73 | "version" "2.2.0" 74 | dependencies: 75 | "call-bind" "^1.0.2" 76 | "define-properties" "^1.1.3" 77 | "es-abstract" "^1.19.4" 78 | "es-shim-unscopables" "^1.0.0" 79 | 80 | "array.prototype.flat@^1.2.3": 81 | "integrity" "sha512-KaYU+S+ndVqyUnignHftkwc58o3uVU1jzczILJ1tN2YaIZpFIKBiP/x/j97E5MVPsaCloPbqWLB/8qCTVvT2qg==" 82 | "resolved" "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.2.5.tgz" 83 | "version" "1.2.5" 84 | dependencies: 85 | "call-bind" "^1.0.2" 86 | "define-properties" "^1.1.3" 87 | "es-abstract" "^1.19.0" 88 | 89 | "boolbase@^1.0.0": 90 | "integrity" "sha1-aN/1++YMUes3cl6p4+0xDcwed24=" 91 | "resolved" "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz" 92 | "version" "1.0.0" 93 | 94 | "call-bind@^1.0.0", "call-bind@^1.0.2": 95 | "integrity" "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==" 96 | "resolved" "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz" 97 | "version" "1.0.2" 98 | dependencies: 99 | "function-bind" "^1.1.1" 100 | "get-intrinsic" "^1.0.2" 101 | 102 | "cheerio-select@^1.5.0": 103 | "integrity" "sha512-qocaHPv5ypefh6YNxvnbABM07KMxExbtbfuJoIie3iZXX1ERwYmJcIiRrr9H05ucQP1k28dav8rpdDgjQd8drg==" 104 | "resolved" "https://registry.npmjs.org/cheerio-select/-/cheerio-select-1.5.0.tgz" 105 | "version" "1.5.0" 106 | dependencies: 107 | "css-select" "^4.1.3" 108 | "css-what" "^5.0.1" 109 | "domelementtype" "^2.2.0" 110 | "domhandler" "^4.2.0" 111 | "domutils" "^2.7.0" 112 | 113 | "cheerio@^1.0.0-rc.3": 114 | "integrity" "sha512-g0J0q/O6mW8z5zxQ3A8E8J1hUgp4SMOvEoW/x84OwyHKe/Zccz83PVT4y5Crcr530FV6NgmKI1qvGTKVl9XXVw==" 115 | "resolved" "https://registry.npmjs.org/cheerio/-/cheerio-1.0.0-rc.10.tgz" 116 | "version" "1.0.0-rc.10" 117 | dependencies: 118 | "cheerio-select" "^1.5.0" 119 | "dom-serializer" "^1.3.2" 120 | "domhandler" "^4.2.0" 121 | "htmlparser2" "^6.1.0" 122 | "parse5" "^6.0.1" 123 | "parse5-htmlparser2-tree-adapter" "^6.0.1" 124 | "tslib" "^2.2.0" 125 | 126 | "commander@^2.19.0": 127 | "integrity" "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==" 128 | "resolved" "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz" 129 | "version" "2.20.3" 130 | 131 | "css-select@^4.1.3": 132 | "integrity" "sha512-/aUslKhzkTNCQUB2qTX84lVmfia9NyjP3WpDGtj/WxhwBzWBYUV3DgUpurHTme8UTPcPlAD1DJ+b0nN/t50zDQ==" 133 | "resolved" "https://registry.npmjs.org/css-select/-/css-select-4.2.1.tgz" 134 | "version" "4.2.1" 135 | dependencies: 136 | "boolbase" "^1.0.0" 137 | "css-what" "^5.1.0" 138 | "domhandler" "^4.3.0" 139 | "domutils" "^2.8.0" 140 | "nth-check" "^2.0.1" 141 | 142 | "css-what@^5.0.1", "css-what@^5.1.0": 143 | "integrity" "sha512-arSMRWIIFY0hV8pIxZMEfmMI47Wj3R/aWpZDDxWYCPEiOMv6tfOrnpDtgxBYPEQD4V0Y/958+1TdC3iWTFcUPw==" 144 | "resolved" "https://registry.npmjs.org/css-what/-/css-what-5.1.0.tgz" 145 | "version" "5.1.0" 146 | 147 | "csstype@^3.0.2": 148 | "integrity" "sha512-2u44ZG2OcNUO9HDp/Jl8C07x6pU/eTR3ncV91SiK3dhG9TWvRVsCoJw14Ckx5DgWkzGA3waZWO3d7pgqpUI/XA==" 149 | "resolved" "https://registry.npmjs.org/csstype/-/csstype-3.0.10.tgz" 150 | "version" "3.0.10" 151 | 152 | "define-properties@^1.1.3": 153 | "integrity" "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==" 154 | "resolved" "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz" 155 | "version" "1.1.3" 156 | dependencies: 157 | "object-keys" "^1.0.12" 158 | 159 | "discontinuous-range@1.0.0": 160 | "integrity" "sha1-44Mx8IRLukm5qctxx3FYWqsbxlo=" 161 | "resolved" "https://registry.npmjs.org/discontinuous-range/-/discontinuous-range-1.0.0.tgz" 162 | "version" "1.0.0" 163 | 164 | "dom-serializer@^1.0.1", "dom-serializer@^1.3.2": 165 | "integrity" "sha512-5c54Bk5Dw4qAxNOI1pFEizPSjVsx5+bpJKmL2kPn8JhBUq2q09tTCa3mjijun2NfK78NMouDYNMBkOrPZiS+ig==" 166 | "resolved" "https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.3.2.tgz" 167 | "version" "1.3.2" 168 | dependencies: 169 | "domelementtype" "^2.0.1" 170 | "domhandler" "^4.2.0" 171 | "entities" "^2.0.0" 172 | 173 | "domelementtype@^2.0.1", "domelementtype@^2.2.0": 174 | "integrity" "sha512-DtBMo82pv1dFtUmHyr48beiuq792Sxohr+8Hm9zoxklYPfa6n0Z3Byjj2IV7bmr2IyqClnqEQhfgHJJ5QF0R5A==" 175 | "resolved" "https://registry.npmjs.org/domelementtype/-/domelementtype-2.2.0.tgz" 176 | "version" "2.2.0" 177 | 178 | "domhandler@^4.0.0", "domhandler@^4.2.0", "domhandler@^4.3.0": 179 | "integrity" "sha512-fC0aXNQXqKSFTr2wDNZDhsEYjCiYsDWl3D01kwt25hm1YIPyDGHvvi3rw+PLqHAl/m71MaiF7d5zvBr0p5UB2g==" 180 | "resolved" "https://registry.npmjs.org/domhandler/-/domhandler-4.3.0.tgz" 181 | "version" "4.3.0" 182 | dependencies: 183 | "domelementtype" "^2.2.0" 184 | 185 | "domutils@^2.5.2", "domutils@^2.7.0", "domutils@^2.8.0": 186 | "integrity" "sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==" 187 | "resolved" "https://registry.npmjs.org/domutils/-/domutils-2.8.0.tgz" 188 | "version" "2.8.0" 189 | dependencies: 190 | "dom-serializer" "^1.0.1" 191 | "domelementtype" "^2.2.0" 192 | "domhandler" "^4.2.0" 193 | 194 | "entities@^2.0.0": 195 | "integrity" "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==" 196 | "resolved" "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz" 197 | "version" "2.2.0" 198 | 199 | "enzyme-adapter-react-16@^1.15.6": 200 | "integrity" "sha512-yFlVJCXh8T+mcQo8M6my9sPgeGzj85HSHi6Apgf1Cvq/7EL/J9+1JoJmJsRxZgyTvPMAqOEpRSu/Ii/ZpyOk0g==" 201 | "resolved" "https://registry.npmjs.org/enzyme-adapter-react-16/-/enzyme-adapter-react-16-1.15.6.tgz" 202 | "version" "1.15.6" 203 | dependencies: 204 | "enzyme-adapter-utils" "^1.14.0" 205 | "enzyme-shallow-equal" "^1.0.4" 206 | "has" "^1.0.3" 207 | "object.assign" "^4.1.2" 208 | "object.values" "^1.1.2" 209 | "prop-types" "^15.7.2" 210 | "react-is" "^16.13.1" 211 | "react-test-renderer" "^16.0.0-0" 212 | "semver" "^5.7.0" 213 | 214 | "enzyme-adapter-utils@^1.14.0": 215 | "integrity" "sha512-F/z/7SeLt+reKFcb7597IThpDp0bmzcH1E9Oabqv+o01cID2/YInlqHbFl7HzWBl4h3OdZYedtwNDOmSKkk0bg==" 216 | "resolved" "https://registry.npmjs.org/enzyme-adapter-utils/-/enzyme-adapter-utils-1.14.0.tgz" 217 | "version" "1.14.0" 218 | dependencies: 219 | "airbnb-prop-types" "^2.16.0" 220 | "function.prototype.name" "^1.1.3" 221 | "has" "^1.0.3" 222 | "object.assign" "^4.1.2" 223 | "object.fromentries" "^2.0.3" 224 | "prop-types" "^15.7.2" 225 | "semver" "^5.7.1" 226 | 227 | "enzyme-shallow-equal@^1.0.1", "enzyme-shallow-equal@^1.0.4": 228 | "integrity" "sha512-MttIwB8kKxypwHvRynuC3ahyNc+cFbR8mjVIltnmzQ0uKGqmsfO4bfBuLxb0beLNPhjblUEYvEbsg+VSygvF1Q==" 229 | "resolved" "https://registry.npmjs.org/enzyme-shallow-equal/-/enzyme-shallow-equal-1.0.4.tgz" 230 | "version" "1.0.4" 231 | dependencies: 232 | "has" "^1.0.3" 233 | "object-is" "^1.1.2" 234 | 235 | "enzyme@^3.0.0", "enzyme@^3.11.0": 236 | "integrity" "sha512-Dw8/Gs4vRjxY6/6i9wU0V+utmQO9kvh9XLnz3LIudviOnVYDEe2ec+0k+NQoMamn1VrjKgCUOWj5jG/5M5M0Qw==" 237 | "resolved" "https://registry.npmjs.org/enzyme/-/enzyme-3.11.0.tgz" 238 | "version" "3.11.0" 239 | dependencies: 240 | "array.prototype.flat" "^1.2.3" 241 | "cheerio" "^1.0.0-rc.3" 242 | "enzyme-shallow-equal" "^1.0.1" 243 | "function.prototype.name" "^1.1.2" 244 | "has" "^1.0.3" 245 | "html-element-map" "^1.2.0" 246 | "is-boolean-object" "^1.0.1" 247 | "is-callable" "^1.1.5" 248 | "is-number-object" "^1.0.4" 249 | "is-regex" "^1.0.5" 250 | "is-string" "^1.0.5" 251 | "is-subset" "^0.1.1" 252 | "lodash.escape" "^4.0.1" 253 | "lodash.isequal" "^4.5.0" 254 | "object-inspect" "^1.7.0" 255 | "object-is" "^1.0.2" 256 | "object.assign" "^4.1.0" 257 | "object.entries" "^1.1.1" 258 | "object.values" "^1.1.1" 259 | "raf" "^3.4.1" 260 | "rst-selector-parser" "^2.2.3" 261 | "string.prototype.trim" "^1.2.1" 262 | 263 | "es-abstract@^1.19.0", "es-abstract@^1.19.1", "es-abstract@^1.19.4": 264 | "integrity" "sha512-Aa2G2+Rd3b6kxEUKTF4TaW67czBLyAv3z7VOhYRU50YBx+bbsYZ9xQP4lMNazePuFlybXI0V4MruPos7qUo5fA==" 265 | "resolved" "https://registry.npmjs.org/es-abstract/-/es-abstract-1.19.5.tgz" 266 | "version" "1.19.5" 267 | dependencies: 268 | "call-bind" "^1.0.2" 269 | "es-to-primitive" "^1.2.1" 270 | "function-bind" "^1.1.1" 271 | "get-intrinsic" "^1.1.1" 272 | "get-symbol-description" "^1.0.0" 273 | "has" "^1.0.3" 274 | "has-symbols" "^1.0.3" 275 | "internal-slot" "^1.0.3" 276 | "is-callable" "^1.2.4" 277 | "is-negative-zero" "^2.0.2" 278 | "is-regex" "^1.1.4" 279 | "is-shared-array-buffer" "^1.0.2" 280 | "is-string" "^1.0.7" 281 | "is-weakref" "^1.0.2" 282 | "object-inspect" "^1.12.0" 283 | "object-keys" "^1.1.1" 284 | "object.assign" "^4.1.2" 285 | "string.prototype.trimend" "^1.0.4" 286 | "string.prototype.trimstart" "^1.0.4" 287 | "unbox-primitive" "^1.0.1" 288 | 289 | "es-array-method-boxes-properly@^1.0.0": 290 | "integrity" "sha512-wd6JXUmyHmt8T5a2xreUwKcGPq6f1f+WwIJkijUqiGcJz1qqnZgP6XIK+QyIWU5lT7imeNxUll48bziG+TSYcA==" 291 | "resolved" "https://registry.npmjs.org/es-array-method-boxes-properly/-/es-array-method-boxes-properly-1.0.0.tgz" 292 | "version" "1.0.0" 293 | 294 | "es-shim-unscopables@^1.0.0": 295 | "integrity" "sha512-Jm6GPcCdC30eMLbZ2x8z2WuRwAws3zTBBKuusffYVUrNj/GVSUAZ+xKMaUpfNDR5IbyNA5LJbaecoUVbmUcB1w==" 296 | "resolved" "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.0.0.tgz" 297 | "version" "1.0.0" 298 | dependencies: 299 | "has" "^1.0.3" 300 | 301 | "es-to-primitive@^1.2.1": 302 | "integrity" "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==" 303 | "resolved" "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz" 304 | "version" "1.2.1" 305 | dependencies: 306 | "is-callable" "^1.1.4" 307 | "is-date-object" "^1.0.1" 308 | "is-symbol" "^1.0.2" 309 | 310 | "function-bind@^1.1.1": 311 | "integrity" "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" 312 | "resolved" "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz" 313 | "version" "1.1.1" 314 | 315 | "function.prototype.name@^1.1.2", "function.prototype.name@^1.1.3": 316 | "integrity" "sha512-uN7m/BzVKQnCUF/iW8jYea67v++2u7m5UgENbHRtdDVclOUP+FMPlCNdmk0h/ysGyo2tavMJEDqJAkJdRa1vMA==" 317 | "resolved" "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.5.tgz" 318 | "version" "1.1.5" 319 | dependencies: 320 | "call-bind" "^1.0.2" 321 | "define-properties" "^1.1.3" 322 | "es-abstract" "^1.19.0" 323 | "functions-have-names" "^1.2.2" 324 | 325 | "functions-have-names@^1.2.2": 326 | "integrity" "sha512-bLgc3asbWdwPbx2mNk2S49kmJCuQeu0nfmaOgbs8WIyzzkw3r4htszdIi9Q9EMezDPTYuJx2wvjZ/EwgAthpnA==" 327 | "resolved" "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.2.tgz" 328 | "version" "1.2.2" 329 | 330 | "get-intrinsic@^1.0.2", "get-intrinsic@^1.1.0", "get-intrinsic@^1.1.1": 331 | "integrity" "sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q==" 332 | "resolved" "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.1.tgz" 333 | "version" "1.1.1" 334 | dependencies: 335 | "function-bind" "^1.1.1" 336 | "has" "^1.0.3" 337 | "has-symbols" "^1.0.1" 338 | 339 | "get-symbol-description@^1.0.0": 340 | "integrity" "sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==" 341 | "resolved" "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.0.tgz" 342 | "version" "1.0.0" 343 | dependencies: 344 | "call-bind" "^1.0.2" 345 | "get-intrinsic" "^1.1.1" 346 | 347 | "has-bigints@^1.0.1": 348 | "integrity" "sha512-LSBS2LjbNBTf6287JEbEzvJgftkF5qFkmCo9hDRpAzKhUOlJ+hx8dd4USs00SgsUNwc4617J9ki5YtEClM2ffA==" 349 | "resolved" "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.1.tgz" 350 | "version" "1.0.1" 351 | 352 | "has-symbols@^1.0.1", "has-symbols@^1.0.2", "has-symbols@^1.0.3": 353 | "integrity" "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==" 354 | "resolved" "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz" 355 | "version" "1.0.3" 356 | 357 | "has-tostringtag@^1.0.0": 358 | "integrity" "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==" 359 | "resolved" "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz" 360 | "version" "1.0.0" 361 | dependencies: 362 | "has-symbols" "^1.0.2" 363 | 364 | "has@^1.0.3": 365 | "integrity" "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==" 366 | "resolved" "https://registry.npmjs.org/has/-/has-1.0.3.tgz" 367 | "version" "1.0.3" 368 | dependencies: 369 | "function-bind" "^1.1.1" 370 | 371 | "html-element-map@^1.2.0": 372 | "integrity" "sha512-6XMlxrAFX4UEEGxctfFnmrFaaZFNf9i5fNuV5wZ3WWQ4FVaNP1aX1LkX9j2mfEx1NpjeE/rL3nmgEn23GdFmrg==" 373 | "resolved" "https://registry.npmjs.org/html-element-map/-/html-element-map-1.3.1.tgz" 374 | "version" "1.3.1" 375 | dependencies: 376 | "array.prototype.filter" "^1.0.0" 377 | "call-bind" "^1.0.2" 378 | 379 | "htmlparser2@^6.1.0": 380 | "integrity" "sha512-gyyPk6rgonLFEDGoeRgQNaEUvdJ4ktTmmUh/h2t7s+M8oPpIPxgNACWa+6ESR57kXstwqPiCut0V8NRpcwgU7A==" 381 | "resolved" "https://registry.npmjs.org/htmlparser2/-/htmlparser2-6.1.0.tgz" 382 | "version" "6.1.0" 383 | dependencies: 384 | "domelementtype" "^2.0.1" 385 | "domhandler" "^4.0.0" 386 | "domutils" "^2.5.2" 387 | "entities" "^2.0.0" 388 | 389 | "internal-slot@^1.0.3": 390 | "integrity" "sha512-O0DB1JC/sPyZl7cIo78n5dR7eUSwwpYPiXRhTzNxZVAMUuB8vlnRFyLxdrVToks6XPLVnFfbzaVd5WLjhgg+vA==" 391 | "resolved" "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.3.tgz" 392 | "version" "1.0.3" 393 | dependencies: 394 | "get-intrinsic" "^1.1.0" 395 | "has" "^1.0.3" 396 | "side-channel" "^1.0.4" 397 | 398 | "is-bigint@^1.0.1": 399 | "integrity" "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==" 400 | "resolved" "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz" 401 | "version" "1.0.4" 402 | dependencies: 403 | "has-bigints" "^1.0.1" 404 | 405 | "is-boolean-object@^1.0.1", "is-boolean-object@^1.1.0": 406 | "integrity" "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==" 407 | "resolved" "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz" 408 | "version" "1.1.2" 409 | dependencies: 410 | "call-bind" "^1.0.2" 411 | "has-tostringtag" "^1.0.0" 412 | 413 | "is-callable@^1.1.4", "is-callable@^1.1.5", "is-callable@^1.2.4": 414 | "integrity" "sha512-nsuwtxZfMX67Oryl9LCQ+upnC0Z0BgpwntpS89m1H/TLF0zNfzfLMV/9Wa/6MZsj0acpEjAO0KF1xT6ZdLl95w==" 415 | "resolved" "https://registry.npmjs.org/is-callable/-/is-callable-1.2.4.tgz" 416 | "version" "1.2.4" 417 | 418 | "is-date-object@^1.0.1": 419 | "integrity" "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==" 420 | "resolved" "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz" 421 | "version" "1.0.5" 422 | dependencies: 423 | "has-tostringtag" "^1.0.0" 424 | 425 | "is-negative-zero@^2.0.2": 426 | "integrity" "sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==" 427 | "resolved" "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.2.tgz" 428 | "version" "2.0.2" 429 | 430 | "is-number-object@^1.0.4": 431 | "integrity" "sha512-bEVOqiRcvo3zO1+G2lVMy+gkkEm9Yh7cDMRusKKu5ZJKPUYSJwICTKZrNKHA2EbSP0Tu0+6B/emsYNHZyn6K8g==" 432 | "resolved" "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.6.tgz" 433 | "version" "1.0.6" 434 | dependencies: 435 | "has-tostringtag" "^1.0.0" 436 | 437 | "is-regex@^1.0.5", "is-regex@^1.1.0", "is-regex@^1.1.4": 438 | "integrity" "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==" 439 | "resolved" "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz" 440 | "version" "1.1.4" 441 | dependencies: 442 | "call-bind" "^1.0.2" 443 | "has-tostringtag" "^1.0.0" 444 | 445 | "is-shared-array-buffer@^1.0.2": 446 | "integrity" "sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==" 447 | "resolved" "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.2.tgz" 448 | "version" "1.0.2" 449 | dependencies: 450 | "call-bind" "^1.0.2" 451 | 452 | "is-string@^1.0.5", "is-string@^1.0.7": 453 | "integrity" "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==" 454 | "resolved" "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz" 455 | "version" "1.0.7" 456 | dependencies: 457 | "has-tostringtag" "^1.0.0" 458 | 459 | "is-subset@^0.1.1": 460 | "integrity" "sha1-ilkRfZMt4d4A8kX83TnOQ/HpOaY=" 461 | "resolved" "https://registry.npmjs.org/is-subset/-/is-subset-0.1.1.tgz" 462 | "version" "0.1.1" 463 | 464 | "is-symbol@^1.0.2", "is-symbol@^1.0.3": 465 | "integrity" "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==" 466 | "resolved" "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz" 467 | "version" "1.0.4" 468 | dependencies: 469 | "has-symbols" "^1.0.2" 470 | 471 | "is-weakref@^1.0.2": 472 | "integrity" "sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==" 473 | "resolved" "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz" 474 | "version" "1.0.2" 475 | dependencies: 476 | "call-bind" "^1.0.2" 477 | 478 | "js-tokens@^3.0.0 || ^4.0.0": 479 | "integrity" "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" 480 | "resolved" "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz" 481 | "version" "4.0.0" 482 | 483 | "lodash.escape@^4.0.1": 484 | "integrity" "sha1-yQRGkMIeBClL6qUXcS/e0fqI3pg=" 485 | "resolved" "https://registry.npmjs.org/lodash.escape/-/lodash.escape-4.0.1.tgz" 486 | "version" "4.0.1" 487 | 488 | "lodash.flattendeep@^4.4.0": 489 | "integrity" "sha1-+wMJF/hqMTTlvJvsDWngAT3f7bI=" 490 | "resolved" "https://registry.npmjs.org/lodash.flattendeep/-/lodash.flattendeep-4.4.0.tgz" 491 | "version" "4.4.0" 492 | 493 | "lodash.isequal@^4.5.0": 494 | "integrity" "sha1-QVxEePK8wwEgwizhDtMib30+GOA=" 495 | "resolved" "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz" 496 | "version" "4.5.0" 497 | 498 | "loose-envify@^1.1.0", "loose-envify@^1.4.0": 499 | "integrity" "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==" 500 | "resolved" "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz" 501 | "version" "1.4.0" 502 | dependencies: 503 | "js-tokens" "^3.0.0 || ^4.0.0" 504 | 505 | "moo@^0.5.0": 506 | "integrity" "sha512-I1mnb5xn4fO80BH9BLcF0yLypy2UKl+Cb01Fu0hJRkJjlCRtxZMWkTdAtDd5ZqCOxtCkhmRwyI57vWT+1iZ67w==" 507 | "resolved" "https://registry.npmjs.org/moo/-/moo-0.5.1.tgz" 508 | "version" "0.5.1" 509 | 510 | "nearley@^2.7.10": 511 | "integrity" "sha512-+Mc8UaAebFzgV+KpI5n7DasuuQCHA89dmwm7JXw3TV43ukfNQ9DnBH3Mdb2g/I4Fdxc26pwimBWvjIw0UAILSQ==" 512 | "resolved" "https://registry.npmjs.org/nearley/-/nearley-2.20.1.tgz" 513 | "version" "2.20.1" 514 | dependencies: 515 | "commander" "^2.19.0" 516 | "moo" "^0.5.0" 517 | "railroad-diagrams" "^1.0.0" 518 | "randexp" "0.4.6" 519 | 520 | "nth-check@^2.0.1": 521 | "integrity" "sha512-it1vE95zF6dTT9lBsYbxvqh0Soy4SPowchj0UBGj/V6cTPnXXtQOPUbhZ6CmGzAD/rW22LQK6E96pcdJXk4A4w==" 522 | "resolved" "https://registry.npmjs.org/nth-check/-/nth-check-2.0.1.tgz" 523 | "version" "2.0.1" 524 | dependencies: 525 | "boolbase" "^1.0.0" 526 | 527 | "object-assign@^4.1.1": 528 | "integrity" "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" 529 | "resolved" "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz" 530 | "version" "4.1.1" 531 | 532 | "object-inspect@^1.12.0", "object-inspect@^1.7.0", "object-inspect@^1.9.0": 533 | "integrity" "sha512-Ho2z80bVIvJloH+YzRmpZVQe87+qASmBUKZDWgx9cu+KDrX2ZDH/3tMy+gXbZETVGs2M8YdxObOh7XAtim9Y0g==" 534 | "resolved" "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.0.tgz" 535 | "version" "1.12.0" 536 | 537 | "object-is@^1.0.2", "object-is@^1.1.2": 538 | "integrity" "sha512-3cyDsyHgtmi7I7DfSSI2LDp6SK2lwvtbg0p0R1e0RvTqF5ceGx+K2dfSjm1bKDMVCFEDAQvy+o8c6a7VujOddw==" 539 | "resolved" "https://registry.npmjs.org/object-is/-/object-is-1.1.5.tgz" 540 | "version" "1.1.5" 541 | dependencies: 542 | "call-bind" "^1.0.2" 543 | "define-properties" "^1.1.3" 544 | 545 | "object-keys@^1.0.12", "object-keys@^1.1.1": 546 | "integrity" "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==" 547 | "resolved" "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz" 548 | "version" "1.1.1" 549 | 550 | "object.assign@^4.1.0", "object.assign@^4.1.2": 551 | "integrity" "sha512-ixT2L5THXsApyiUPYKmW+2EHpXXe5Ii3M+f4e+aJFAHao5amFRW6J0OO6c/LU8Be47utCx2GL89hxGB6XSmKuQ==" 552 | "resolved" "https://registry.npmjs.org/object.assign/-/object.assign-4.1.2.tgz" 553 | "version" "4.1.2" 554 | dependencies: 555 | "call-bind" "^1.0.0" 556 | "define-properties" "^1.1.3" 557 | "has-symbols" "^1.0.1" 558 | "object-keys" "^1.1.1" 559 | 560 | "object.entries@^1.1.1", "object.entries@^1.1.2": 561 | "integrity" "sha512-TyxmjUoZggd4OrrU1W66FMDG6CuqJxsFvymeyXI51+vQLN67zYfZseptRge703kKQdo4uccgAKebXFcRCzk4+g==" 562 | "resolved" "https://registry.npmjs.org/object.entries/-/object.entries-1.1.5.tgz" 563 | "version" "1.1.5" 564 | dependencies: 565 | "call-bind" "^1.0.2" 566 | "define-properties" "^1.1.3" 567 | "es-abstract" "^1.19.1" 568 | 569 | "object.fromentries@^2.0.3": 570 | "integrity" "sha512-CAyG5mWQRRiBU57Re4FKoTBjXfDoNwdFVH2Y1tS9PqCsfUTymAohOkEMSG3aRNKmv4lV3O7p1et7c187q6bynw==" 571 | "resolved" "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.5.tgz" 572 | "version" "2.0.5" 573 | dependencies: 574 | "call-bind" "^1.0.2" 575 | "define-properties" "^1.1.3" 576 | "es-abstract" "^1.19.1" 577 | 578 | "object.values@^1.1.1", "object.values@^1.1.2": 579 | "integrity" "sha512-QUZRW0ilQ3PnPpbNtgdNV1PDbEqLIiSFB3l+EnGtBQ/8SUTLj1PZwtQHABZtLgwpJZTSZhuGLOGk57Drx2IvYg==" 580 | "resolved" "https://registry.npmjs.org/object.values/-/object.values-1.1.5.tgz" 581 | "version" "1.1.5" 582 | dependencies: 583 | "call-bind" "^1.0.2" 584 | "define-properties" "^1.1.3" 585 | "es-abstract" "^1.19.1" 586 | 587 | "parse5-htmlparser2-tree-adapter@^6.0.1": 588 | "integrity" "sha512-qPuWvbLgvDGilKc5BoicRovlT4MtYT6JfJyBOMDsKoiT+GiuP5qyrPCnR9HcPECIJJmZh5jRndyNThnhhb/vlA==" 589 | "resolved" "https://registry.npmjs.org/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-6.0.1.tgz" 590 | "version" "6.0.1" 591 | dependencies: 592 | "parse5" "^6.0.1" 593 | 594 | "parse5@^6.0.1": 595 | "integrity" "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==" 596 | "resolved" "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz" 597 | "version" "6.0.1" 598 | 599 | "performance-now@^2.1.0": 600 | "integrity" "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=" 601 | "resolved" "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz" 602 | "version" "2.1.0" 603 | 604 | "prop-types-exact@^1.2.0": 605 | "integrity" "sha512-K+Tk3Kd9V0odiXFP9fwDHUYRyvK3Nun3GVyPapSIs5OBkITAm15W0CPFD/YKTkMUAbc0b9CUwRQp2ybiBIq+eA==" 606 | "resolved" "https://registry.npmjs.org/prop-types-exact/-/prop-types-exact-1.2.0.tgz" 607 | "version" "1.2.0" 608 | dependencies: 609 | "has" "^1.0.3" 610 | "object.assign" "^4.1.0" 611 | "reflect.ownkeys" "^0.2.0" 612 | 613 | "prop-types@^15.6.2", "prop-types@^15.7.2": 614 | "integrity" "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==" 615 | "resolved" "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz" 616 | "version" "15.8.1" 617 | dependencies: 618 | "loose-envify" "^1.4.0" 619 | "object-assign" "^4.1.1" 620 | "react-is" "^16.13.1" 621 | 622 | "raf@^3.4.1": 623 | "integrity" "sha512-Sq4CW4QhwOHE8ucn6J34MqtZCeWFP2aQSmrlroYgqAV1PjStIhJXxYuTgUIfkEk7zTLjmIjLmU5q+fbD1NnOJA==" 624 | "resolved" "https://registry.npmjs.org/raf/-/raf-3.4.1.tgz" 625 | "version" "3.4.1" 626 | dependencies: 627 | "performance-now" "^2.1.0" 628 | 629 | "railroad-diagrams@^1.0.0": 630 | "integrity" "sha1-635iZ1SN3t+4mcG5Dlc3RVnN234=" 631 | "resolved" "https://registry.npmjs.org/railroad-diagrams/-/railroad-diagrams-1.0.0.tgz" 632 | "version" "1.0.0" 633 | 634 | "randexp@0.4.6": 635 | "integrity" "sha512-80WNmd9DA0tmZrw9qQa62GPPWfuXJknrmVmLcxvq4uZBdYqb1wYoKTmnlGUchvVWe0XiLupYkBoXVOxz3C8DYQ==" 636 | "resolved" "https://registry.npmjs.org/randexp/-/randexp-0.4.6.tgz" 637 | "version" "0.4.6" 638 | dependencies: 639 | "discontinuous-range" "1.0.0" 640 | "ret" "~0.1.10" 641 | 642 | "react-dom@^16.0.0-0", "react-dom@^16.14.0": 643 | "integrity" "sha512-1gCeQXDLoIqMgqD3IO2Ah9bnf0w9kzhwN5q4FGnHZ67hBm9yePzB5JJAIQCc8x3pFnNlwFq4RidZggNAAkzWWw==" 644 | "resolved" "https://registry.npmjs.org/react-dom/-/react-dom-16.14.0.tgz" 645 | "version" "16.14.0" 646 | dependencies: 647 | "loose-envify" "^1.1.0" 648 | "object-assign" "^4.1.1" 649 | "prop-types" "^15.6.2" 650 | "scheduler" "^0.19.1" 651 | 652 | "react-is@^16.13.1", "react-is@^16.8.6": 653 | "integrity" "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" 654 | "resolved" "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz" 655 | "version" "16.13.1" 656 | 657 | "react-test-renderer@^16.0.0-0": 658 | "integrity" "sha512-L8yPjqPE5CZO6rKsKXRO/rVPiaCOy0tQQJbC+UjPNlobl5mad59lvPjwFsQHTvL03caVDIVr9x9/OSgDe6I5Eg==" 659 | "resolved" "https://registry.npmjs.org/react-test-renderer/-/react-test-renderer-16.14.0.tgz" 660 | "version" "16.14.0" 661 | dependencies: 662 | "object-assign" "^4.1.1" 663 | "prop-types" "^15.6.2" 664 | "react-is" "^16.8.6" 665 | "scheduler" "^0.19.1" 666 | 667 | "react@^0.14 || ^15.0.0 || ^16.0.0-alpha", "react@^16.0.0-0", "react@^16.14.0", "react@0.13.x || 0.14.x || ^15.0.0-0 || ^16.0.0-0": 668 | "integrity" "sha512-0X2CImDkJGApiAlcf0ODKIneSwBPhqJawOa5wCtKbu7ZECrmS26NvtSILynQ66cgkT/RJ4LidJOc3bUESwmU8g==" 669 | "resolved" "https://registry.npmjs.org/react/-/react-16.14.0.tgz" 670 | "version" "16.14.0" 671 | dependencies: 672 | "loose-envify" "^1.1.0" 673 | "object-assign" "^4.1.1" 674 | "prop-types" "^15.6.2" 675 | 676 | "reflect.ownkeys@^0.2.0": 677 | "integrity" "sha1-dJrO7H8/34tj+SegSAnpDFwLNGA=" 678 | "resolved" "https://registry.npmjs.org/reflect.ownkeys/-/reflect.ownkeys-0.2.0.tgz" 679 | "version" "0.2.0" 680 | 681 | "ret@~0.1.10": 682 | "integrity" "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==" 683 | "resolved" "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz" 684 | "version" "0.1.15" 685 | 686 | "rst-selector-parser@^2.2.3": 687 | "integrity" "sha1-gbIw6i/MYGbInjRy3nlChdmwPZE=" 688 | "resolved" "https://registry.npmjs.org/rst-selector-parser/-/rst-selector-parser-2.2.3.tgz" 689 | "version" "2.2.3" 690 | dependencies: 691 | "lodash.flattendeep" "^4.4.0" 692 | "nearley" "^2.7.10" 693 | 694 | "scheduler@^0.19.1": 695 | "integrity" "sha512-n/zwRWRYSUj0/3g/otKDRPMh6qv2SYMWNq85IEa8iZyAv8od9zDYpGSnpBEjNgcMNq6Scbu5KfIPxNF72R/2EA==" 696 | "resolved" "https://registry.npmjs.org/scheduler/-/scheduler-0.19.1.tgz" 697 | "version" "0.19.1" 698 | dependencies: 699 | "loose-envify" "^1.1.0" 700 | "object-assign" "^4.1.1" 701 | 702 | "semver@^5.7.0", "semver@^5.7.1": 703 | "integrity" "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" 704 | "resolved" "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz" 705 | "version" "5.7.1" 706 | 707 | "side-channel@^1.0.4": 708 | "integrity" "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==" 709 | "resolved" "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz" 710 | "version" "1.0.4" 711 | dependencies: 712 | "call-bind" "^1.0.0" 713 | "get-intrinsic" "^1.0.2" 714 | "object-inspect" "^1.9.0" 715 | 716 | "string.prototype.trim@^1.2.1": 717 | "integrity" "sha512-Lnh17webJVsD6ECeovpVN17RlAKjmz4rF9S+8Y45CkMc/ufVpTkU3vZIyIC7sllQ1FCvObZnnCdNs/HXTUOTlg==" 718 | "resolved" "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.5.tgz" 719 | "version" "1.2.5" 720 | dependencies: 721 | "call-bind" "^1.0.2" 722 | "define-properties" "^1.1.3" 723 | "es-abstract" "^1.19.1" 724 | 725 | "string.prototype.trimend@^1.0.4": 726 | "integrity" "sha512-y9xCjw1P23Awk8EvTpcyL2NIr1j7wJ39f+k6lvRnSMz+mz9CGz9NYPelDk42kOz6+ql8xjfK8oYzy3jAP5QU5A==" 727 | "resolved" "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.4.tgz" 728 | "version" "1.0.4" 729 | dependencies: 730 | "call-bind" "^1.0.2" 731 | "define-properties" "^1.1.3" 732 | 733 | "string.prototype.trimstart@^1.0.4": 734 | "integrity" "sha512-jh6e984OBfvxS50tdY2nRZnoC5/mLFKOREQfw8t5yytkoUsJRNxvI/E39qu1sD0OtWI3OC0XgKSmcWwziwYuZw==" 735 | "resolved" "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.4.tgz" 736 | "version" "1.0.4" 737 | dependencies: 738 | "call-bind" "^1.0.2" 739 | "define-properties" "^1.1.3" 740 | 741 | "tslib@^2.2.0": 742 | "integrity" "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==" 743 | "resolved" "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz" 744 | "version" "2.3.1" 745 | 746 | "unbox-primitive@^1.0.1": 747 | "integrity" "sha512-tZU/3NqK3dA5gpE1KtyiJUrEB0lxnGkMFHptJ7q6ewdZ8s12QrODwNbhIJStmJkd1QDXa1NRA8aF2A1zk/Ypyw==" 748 | "resolved" "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.1.tgz" 749 | "version" "1.0.1" 750 | dependencies: 751 | "function-bind" "^1.1.1" 752 | "has-bigints" "^1.0.1" 753 | "has-symbols" "^1.0.2" 754 | "which-boxed-primitive" "^1.0.2" 755 | 756 | "which-boxed-primitive@^1.0.2": 757 | "integrity" "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==" 758 | "resolved" "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz" 759 | "version" "1.0.2" 760 | dependencies: 761 | "is-bigint" "^1.0.1" 762 | "is-boolean-object" "^1.1.0" 763 | "is-number-object" "^1.0.4" 764 | "is-string" "^1.0.5" 765 | "is-symbol" "^1.0.3" 766 | -------------------------------------------------------------------------------- /packages/shared/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | [ 4 | '@babel/preset-env', 5 | { 6 | targets: { 7 | node: 'current', 8 | }, 9 | }, 10 | ], 11 | ], 12 | }; 13 | -------------------------------------------------------------------------------- /packages/shared/jest.config.js: -------------------------------------------------------------------------------- 1 | /* ----------------------------------- 2 | * 3 | * Jest 4 | * 5 | * -------------------------------- */ 6 | 7 | module.exports = { 8 | testEnvironment: 'jsdom', 9 | globals: { __DEV__: true }, 10 | roots: [''], 11 | collectCoverage: true, 12 | collectCoverageFrom: ['/src/**/*.{ts,tsx}'], 13 | coverageDirectory: '/tests/coverage', 14 | coveragePathIgnorePatterns: ['/node_modules/', '(.*).d.ts'], 15 | coverageThreshold: { 16 | global: { 17 | statements: 84, 18 | branches: 73, 19 | functions: 80, 20 | lines: 82, 21 | }, 22 | }, 23 | transform: { 24 | '^.+\\.tsx?$': 'ts-jest', 25 | '^.+\\js$': 'babel-jest', 26 | }, 27 | }; 28 | -------------------------------------------------------------------------------- /packages/shared/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@component-elements/shared", 3 | "version": "1.0.0", 4 | "main": "./dist/index.js", 5 | "types": "./dist/index.d.ts", 6 | "license": "MIT", 7 | "directories": { 8 | "lib": "dist", 9 | "test": "tests" 10 | }, 11 | "scripts": { 12 | "build": "tsc", 13 | "watch": "tsc -w", 14 | "lint": "eslint", 15 | "test": "jest" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /packages/shared/src/element.ts: -------------------------------------------------------------------------------- 1 | import { getAttributeProps } from './parse'; 2 | import { IComponent, CustomElement } from './model'; 3 | 4 | /* ----------------------------------- 5 | * 6 | * Async 7 | * 8 | * -------------------------------- */ 9 | 10 | function getAsyncComponent(component: Promise, tagName: string): Promise { 11 | return component.then((response) => getComponentResult(response, tagName)); 12 | } 13 | 14 | /* ----------------------------------- 15 | * 16 | * Result 17 | * 18 | * -------------------------------- */ 19 | 20 | function getComponentResult(response: IComponent, tagName: string) { 21 | let result = void 0; 22 | 23 | if (typeof response === 'function') { 24 | return response; 25 | } 26 | 27 | if (typeof response === 'object') { 28 | result = response[getNameFromTag(tagName)] || void 0; 29 | } 30 | 31 | return result; 32 | } 33 | 34 | /* ----------------------------------- 35 | * 36 | * Element 37 | * 38 | * -------------------------------- */ 39 | 40 | function getElementTag(tagName: string) { 41 | let result = tagName.toLowerCase(); 42 | 43 | if (result.indexOf('-') < 0) { 44 | result = 'component-' + result; 45 | } 46 | 47 | return result; 48 | } 49 | 50 | /* ----------------------------------- 51 | * 52 | * Tag 53 | * 54 | * -------------------------------- */ 55 | 56 | function getNameFromTag(value: string) { 57 | value = value.toLowerCase(); 58 | 59 | return value.replace(/(^\w|-\w)/g, (item) => item.replace(/-/, '').toUpperCase()); 60 | } 61 | 62 | /* ----------------------------------- 63 | * 64 | * Attributes 65 | * 66 | * -------------------------------- */ 67 | 68 | function getElementAttributes(this: CustomElement) { 69 | const { attributes = [] } = this.__options; 70 | const result = {}; 71 | 72 | if (!this.hasAttributes()) { 73 | return result; 74 | } 75 | 76 | return getAttributeProps(this.attributes, attributes); 77 | } 78 | 79 | /* ----------------------------------- 80 | * 81 | * Export 82 | * 83 | * -------------------------------- */ 84 | 85 | export { getElementTag, getElementAttributes, getAsyncComponent }; 86 | -------------------------------------------------------------------------------- /packages/shared/src/index.ts: -------------------------------------------------------------------------------- 1 | /* ----------------------------------- 2 | * 3 | * Export 4 | * 5 | * -------------------------------- */ 6 | 7 | export * from './element'; 8 | export * from './parse'; 9 | export * from './model'; 10 | -------------------------------------------------------------------------------- /packages/shared/src/model.ts: -------------------------------------------------------------------------------- 1 | /* ----------------------------------- 2 | * 3 | * Component 4 | * 5 | * -------------------------------- */ 6 | 7 | type IComponent = any; 8 | 9 | /* ----------------------------------- 10 | * 11 | * Options 12 | * 13 | * -------------------------------- */ 14 | 15 | interface IOptions { 16 | attributes?: string[]; 17 | formatProps?: (props: any) => F; 18 | wrapComponent?: (child: any) => W; 19 | } 20 | 21 | /* ----------------------------------- 22 | * 23 | * Errors 24 | * 25 | * -------------------------------- */ 26 | 27 | enum ErrorTypes { 28 | Promise = 'Error: Promises cannot be used for SSR', 29 | Missing = 'Error: Cannot find component in provided function', 30 | Json = 'Error: Invalid JSON string passed to component', 31 | } 32 | 33 | /* ----------------------------------- 34 | * 35 | * Element 36 | * 37 | * -------------------------------- */ 38 | 39 | interface CustomElement extends HTMLElement { 40 | __mounted: boolean; 41 | __component: C; 42 | __properties?: IProps; 43 | __slots?: { [index: string]: any }; 44 | __instance?: I; 45 | __children?: any; 46 | __options: IOptions; 47 | } 48 | 49 | /* ----------------------------------- 50 | * 51 | * IProps 52 | * 53 | * -------------------------------- */ 54 | 55 | interface IProps { 56 | [index: string]: any; 57 | } 58 | 59 | /* ----------------------------------- 60 | * 61 | * Guards 62 | * 63 | * -------------------------------- */ 64 | 65 | const isPromise = (input: any): input is Promise => { 66 | return input && typeof input.then === 'function'; 67 | }; 68 | 69 | /* ----------------------------------- 70 | * 71 | * Self Closing 72 | * 73 | * -------------------------------- */ 74 | 75 | const selfClosingTags = [ 76 | 'area', 77 | 'base', 78 | 'br', 79 | 'col', 80 | 'hr', 81 | 'img', 82 | 'input', 83 | 'link', 84 | 'meta', 85 | 'source', 86 | 'embed', 87 | 'param', 88 | 'track', 89 | 'wbr', 90 | ]; 91 | 92 | /* ----------------------------------- 93 | * 94 | * Export 95 | * 96 | * -------------------------------- */ 97 | 98 | export { IComponent, IOptions, IProps, ErrorTypes, CustomElement, isPromise, selfClosingTags }; 99 | -------------------------------------------------------------------------------- /packages/shared/src/parse.ts: -------------------------------------------------------------------------------- 1 | import { IProps, CustomElement, ErrorTypes } from './model'; 2 | 3 | /* ----------------------------------- 4 | * 5 | * parseJson 6 | * 7 | * -------------------------------- */ 8 | 9 | function parseJson(this: CustomElement, value: string) { 10 | const { tagName } = this; 11 | const { formatProps } = this.__options; 12 | 13 | let result = {}; 14 | 15 | try { 16 | result = JSON.parse(value); 17 | } catch { 18 | console.error(ErrorTypes.Json, `: <${tagName.toLowerCase()}>`); 19 | } 20 | 21 | if (formatProps) { 22 | result = formatProps(result); 23 | } 24 | 25 | return result; 26 | } 27 | 28 | /* ----------------------------------- 29 | * 30 | * getDocument 31 | * 32 | * -------------------------------- */ 33 | 34 | function getDocument(html: string) { 35 | const value = `\n${html}`; 36 | 37 | let nodes: Document; 38 | 39 | try { 40 | nodes = new DOMParser().parseFromString(value, 'text/html'); 41 | } catch { 42 | // no-op 43 | } 44 | 45 | if (!nodes) { 46 | return void 0; 47 | } 48 | 49 | return nodes.body; 50 | } 51 | 52 | /* ----------------------------------- 53 | * 54 | * getAttributeObject 55 | * 56 | * -------------------------------- */ 57 | 58 | function getAttributeObject(attributes: NamedNodeMap): IProps { 59 | const result = {}; 60 | 61 | if (!attributes?.length) { 62 | return result; 63 | } 64 | 65 | for (let i = attributes.length - 1; i >= 0; i--) { 66 | const item = attributes[i]; 67 | 68 | result[item.name] = item.value; 69 | } 70 | 71 | return result; 72 | } 73 | 74 | /* ----------------------------------- 75 | * 76 | * getAttributeProps 77 | * 78 | * -------------------------------- */ 79 | 80 | function getAttributeProps(attributes: NamedNodeMap, allowed?: string[]): IProps { 81 | const values = getAttributeObject(attributes); 82 | 83 | let result = {}; 84 | 85 | for (const key of Object.keys(values)) { 86 | if (allowed?.indexOf(key) === -1) { 87 | continue; 88 | } 89 | 90 | result[getPropKey(key)] = values[key]; 91 | } 92 | 93 | return result; 94 | } 95 | 96 | /* ----------------------------------- 97 | * 98 | * Attribute 99 | * 100 | * -------------------------------- */ 101 | 102 | function getPropKey(value: string) { 103 | const sanitised = value.trim().replace(/[\s_]/g, '-'); 104 | 105 | return ( 106 | sanitised.charAt(0).toLowerCase() + 107 | sanitised.slice(1).replace(/-([a-z])/g, ({ 1: value }) => value.toUpperCase()) 108 | ); 109 | } 110 | 111 | /* ----------------------------------- 112 | * 113 | * Export 114 | * 115 | * -------------------------------- */ 116 | 117 | export { parseJson, getDocument, getPropKey, getAttributeObject, getAttributeProps }; 118 | -------------------------------------------------------------------------------- /packages/shared/tests/element.spec.ts: -------------------------------------------------------------------------------- 1 | import { getAsyncComponent, getElementTag, getElementAttributes } from '../src/element'; 2 | 3 | /* ----------------------------------- 4 | * 5 | * Variables 6 | * 7 | * -------------------------------- */ 8 | 9 | const testComponent = () => void 0; 10 | const testObjectKey = 'TagName'; 11 | const testValidTag = 'tag-name'; 12 | const testInvalidTag = 'tag'; 13 | const testAttributeKey = 'testTitle'; 14 | const testProps = { [testAttributeKey]: 'testTitle' }; 15 | const testAttribute = { name: testAttributeKey, value: 'testTitle' }; 16 | 17 | /* ----------------------------------- 18 | * 19 | * Element 20 | * 21 | * -------------------------------- */ 22 | 23 | describe('element', () => { 24 | afterAll(() => jest.clearAllMocks()); 25 | 26 | describe('getAsyncComponent()', () => { 27 | it('resolves a Promise that returns a component function', async () => { 28 | const result = await getAsyncComponent(Promise.resolve(testComponent), testValidTag); 29 | 30 | expect(result).toEqual(testComponent); 31 | }); 32 | 33 | it('resolves a Promise that returns an object with component', async () => { 34 | const result = await getAsyncComponent( 35 | Promise.resolve({ [testObjectKey]: testComponent }), 36 | testValidTag 37 | ); 38 | 39 | expect(result).toEqual(testComponent); 40 | }); 41 | 42 | it('returns undefined if object does not contain matching component', async () => { 43 | const result = await getAsyncComponent( 44 | Promise.resolve({ WrongKey: testComponent }), 45 | testValidTag 46 | ); 47 | 48 | expect(result).toEqual(void 0); 49 | }); 50 | }); 51 | 52 | describe('getElementTag()', () => { 53 | it('accepts a non valid custom element tag and modifies', () => { 54 | const result = getElementTag(testInvalidTag); 55 | 56 | expect(result).toEqual(`component-${testInvalidTag}`); 57 | }); 58 | 59 | it('accepts a valid custom element tag and returns', () => { 60 | const result = getElementTag(testValidTag); 61 | 62 | expect(result).toEqual(testValidTag); 63 | }); 64 | }); 65 | 66 | describe('getElementAttributes()', () => { 67 | const element = { 68 | __options: { attributes: [testAttributeKey] }, 69 | attributes: [testAttribute], 70 | hasAttributes: () => true, 71 | }; 72 | 73 | it('converts defined attributes into props object', () => { 74 | const result = getElementAttributes.call(element); 75 | 76 | expect(result).toEqual(testProps); 77 | }); 78 | 79 | it('ignores attributes that are not defined via attributes option', () => { 80 | const result = getElementAttributes.call({ ...element, __options: [] }); 81 | 82 | expect(result).toEqual({}); 83 | }); 84 | 85 | it('skips conversion if element has no attributes', () => { 86 | const result = getElementAttributes.call({ ...element, hasAttributes: () => false }); 87 | 88 | expect(result).toEqual({}); 89 | }); 90 | }); 91 | }); 92 | -------------------------------------------------------------------------------- /packages/shared/tests/parse.spec.ts: -------------------------------------------------------------------------------- 1 | import { h } from 'preact'; 2 | import { mount } from 'enzyme'; 3 | import { parseJson, getPropKey } from '../src/parse'; 4 | 5 | /* ----------------------------------- 6 | * 7 | * Variables 8 | * 9 | * -------------------------------- */ 10 | 11 | const testHeading = 'testHeading'; 12 | const testData = { testHeading }; 13 | const testJson = JSON.stringify(testData); 14 | 15 | /* ----------------------------------- 16 | * 17 | * Parse 18 | * 19 | * -------------------------------- */ 20 | 21 | describe('parse', () => { 22 | describe('parseJson()', () => { 23 | const errorSpy = jest.spyOn(console, 'error').mockImplementation(() => {}); 24 | 25 | const properties = { 26 | tagName: 'tag-name', 27 | __options: {}, 28 | }; 29 | 30 | afterAll(() => errorSpy.mockClear()); 31 | 32 | it('should correctly parse json', () => { 33 | const result = parseJson.call(properties, testJson); 34 | 35 | expect(result).toEqual(testData); 36 | }); 37 | 38 | it('should handle invalid json', () => { 39 | const result = parseJson.call(properties, '{test:}'); 40 | 41 | expect(result).toEqual({}); 42 | expect(errorSpy).toHaveBeenCalled(); 43 | }); 44 | 45 | it('should run "formatProps" if defined via options', () => { 46 | const formatProps = (props: any) => ({ ...props, format: true }); 47 | const testProps = { ...properties, __options: { ...properties.__options, formatProps } }; 48 | const result = parseJson.call(testProps, testJson); 49 | 50 | expect(result.hasOwnProperty('format')).toBe(true); 51 | expect(result.format).toEqual(true); 52 | }); 53 | }); 54 | 55 | describe('getPropKey', () => { 56 | const testCamel = 'testSlot'; 57 | const testKebab = 'test-slot'; 58 | const testSnake = 'test_slot'; 59 | const testPascal = 'TestSlot'; 60 | const testSentence = 'Test slot'; 61 | 62 | it('should normalise casing from kebab to camel', () => { 63 | const result = getPropKey(testKebab); 64 | 65 | expect(result).toEqual(testCamel); 66 | }); 67 | 68 | it('should normalise casing from snake to camel', () => { 69 | const result = getPropKey(testSnake); 70 | 71 | expect(result).toEqual(testCamel); 72 | }); 73 | 74 | it('should normalise casing from plascal to camel', () => { 75 | const result = getPropKey(testPascal); 76 | 77 | expect(result).toEqual(testCamel); 78 | }); 79 | 80 | it('should normalise casing from sentence to camel', () => { 81 | const result = getPropKey(testSentence); 82 | 83 | expect(result).toEqual(testCamel); 84 | }); 85 | }); 86 | }); 87 | -------------------------------------------------------------------------------- /packages/shared/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "outDir": "./dist", 4 | "module": "esnext", 5 | "target": "esnext", 6 | "declaration": true, 7 | "moduleResolution": "node", 8 | "allowSyntheticDefaultImports": true 9 | }, 10 | "include": ["src"], 11 | "exclude": ["node_modules", "dist"] 12 | } 13 | -------------------------------------------------------------------------------- /packages/shared/yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "declaration": true, 5 | "noImplicitAny": false, 6 | "removeComments": true, 7 | "noLib": false, 8 | "noEmitOnError": true, 9 | "emitDecoratorMetadata": true, 10 | "experimentalDecorators": true, 11 | "esModuleInterop": true, 12 | "moduleResolution": "node", 13 | "target": "ES2018", 14 | "sourceMap": true, 15 | "lib": ["DOM", "ES2015", "ES2016", "ES2017", "ES2018", "ES2019"] 16 | }, 17 | "exclude": ["node_modules", "webpack.config.ts", "tests", "./dist"] 18 | } 19 | --------------------------------------------------------------------------------