├── .github └── README.md ├── .gitignore ├── .npmignore ├── CODEGOLF.md ├── LICENSE ├── README.md ├── bits ├── index.es2017.cjs.svg ├── index.es2020.mjs.svg ├── jsx-helpers.es2017.cjs.svg ├── jsx-helpers.es2020.mjs.svg ├── performance-helpers.es2017.cjs.svg ├── performance-helpers.es2020.mjs.svg ├── performance-helpers.mjs.svg ├── reflex.es2017.min.cjs.svg ├── reflex.es2017.min.js.svg ├── reflex.es2020.min.js.svg ├── reflex.es2022.min.js.svg ├── render-to-string.es2017.cjs.svg ├── render-to-string.es2020.mjs.svg ├── render-to-string.mjs.svg ├── renderToString.es2017.cjs.svg └── renderToString.es2020.mjs.svg ├── deopt └── src │ └── index.tsx ├── docs ├── .nojekyll ├── api │ ├── README.md │ ├── _assets │ │ ├── docsify.js │ │ ├── prism-bash.js │ │ ├── prism-javascript.js │ │ ├── prism-json.js │ │ ├── prism-jsx.js │ │ ├── prism-markup.js │ │ ├── prism-tsx.js │ │ ├── prism-typescript.js │ │ └── vue.css │ ├── _images │ │ ├── benchmark.jpg │ │ └── example.gif │ ├── _sidebar.md │ ├── basics │ │ ├── 00.core-concept.md │ │ ├── 01.simple-dom-rendering.md │ │ ├── 02.stateless-components.md │ │ ├── 03.stateful-components.md │ │ ├── 04.props.md │ │ ├── 05.forward-ref.md │ │ └── README.md │ ├── factory-extensions │ │ ├── 01.state.md │ │ ├── 02.ref.md │ │ ├── 03.refs.md │ │ ├── 04.mount-unmount.md │ │ ├── 05.changed.md │ │ ├── 06.custom-extensions.md │ │ └── README.md │ ├── index.html │ └── reflex │ │ ├── 00.performances.md │ │ ├── 01.features.md │ │ └── 02.history.md ├── learn │ ├── index-4e761b3d.css │ ├── index-6dd90969.js │ ├── index.html │ └── manifest.json └── perfs │ ├── Screenshot 2024-04-01 at 17.15.51.png │ ├── Screenshot 2024-04-01 at 17.16.02.png │ ├── Screenshot 2024-04-01 at 17.16.08.png │ └── krausest.github.io_js-framework-benchmark_current.html.png ├── package-lock.json ├── package.json ├── reflex-dev ├── package-lock.json ├── package.json ├── src │ ├── 00.v20 │ │ ├── main.tsx │ │ └── v20.tsx │ ├── 01.props │ │ ├── PropsDemoApp.tsx │ │ ├── UserComponent.tsx │ │ └── index.tsx │ ├── 02.state-hmr │ │ ├── OtherComponent.tsx │ │ ├── StateHMR.tsx │ │ └── index.tsx │ ├── 03.effects │ │ ├── Effects.tsx │ │ └── index.tsx │ ├── demoHelpers.ts │ ├── index.html │ └── index.tsx ├── tsconfig.json └── vite.config.js ├── reflex-learn ├── package-lock.json ├── package.json ├── src │ ├── components │ │ ├── App.module.less │ │ └── App.tsx │ ├── index.html │ ├── index.less │ ├── index.tsx │ └── stack │ │ ├── index.html │ │ ├── index.tsx │ │ ├── package.json │ │ ├── ready │ │ ├── steps │ │ ├── 00.render.md │ │ ├── 00.render.tsx │ │ ├── 01.props.tsx │ │ ├── 02.state.tsx │ │ ├── 03.lifecycle.tsx │ │ ├── 04.refs.tsx │ │ ├── 05.locals.tsx │ │ ├── 10.effects.tsx │ │ ├── 11.computes.tsx │ │ ├── 12.performances.tsx │ │ └── 13.extensions.tsx │ │ ├── tsconfig.json │ │ └── vite.config.js ├── tsconfig.json └── vite.config.js ├── src ├── common.ts ├── component.ts ├── debug.ts ├── diff.ts ├── hmr-plugin.ts ├── hmr-runtime.ts ├── hooks-wip.ts ├── index.ts ├── jsx-types.ts ├── jsx.ts ├── performance-helpers.ts ├── ref.ts ├── render-to-string.ts ├── render.ts └── states.ts └── tsconfig.json /.github/README.md: -------------------------------------------------------------------------------- 1 | # Reflex 2 | 3 | __Reflex__ is a tiny and fast reactive UI library. 4 | 5 | - 🦋 Super lightweight and **0 dependency**, about ![~4kb](../bits/reflex.es2022.min.js.svg) min-zipped 6 | - 🏎 Highly performant diffing algorithm 7 | - 🔋 Batteries included with **factory extensions** 8 | - ⚛️ Truly reactive, states are Signals by design 9 | - 🔍 Fine grained DOM updates for even better performances, components are rendered only when needed 10 | - 🤓 Typed JSX 11 | - 🍰 Easy to learn 12 | - 🤘️ HMR Support for Vite 13 | - 🚀 Super fast hydration support 14 | 15 | [![npm](https://img.shields.io/npm/v/reflex-dom.svg)](http://npm.im/reflex-dom) 16 | ![](https://img.shields.io/badge/Status-Release_Candidate-green) 17 |
18 | ![](https://img.shields.io/badge/Build-passing-success) 19 | ![](https://img.shields.io/badge/0-dependency-success) 20 |
21 | [![gzip size](http://img.badgesize.io/https://unpkg.com/reflex-dom/dist/reflex.es2022.min.js?compression=gzip&label=gzip)](https://unpkg.com/reflex-dom/dist/reflex.es2022.min.js) 22 | [![brotli size](http://img.badgesize.io/https://unpkg.com/reflex-dom/dist/reflex.es2022.min.js?compression=brotli&label=brotli)](https://unpkg.com/reflex-dom/dist/reflex.es2022.min.js) 23 | 24 | --- 25 | 26 | ## Quick example 27 | 28 | ```shell 29 | # Install with npm 30 | npm i reflex-dom 31 | ``` 32 | 33 | ```typescript jsx 34 | import { h, render, state } from 'reflex-dom'; 35 | export function App (props) { 36 | // State management 37 | const counter = state(0); 38 | const increment = () => counter.value ++; 39 | const reset = () => counter.value = 0; 40 | // Components returns a render function 41 | return () =>
42 |

Hello from {props.name} 👋

43 |   44 |   45 | Counter : {counter} 46 |
47 | } 48 | render( , document.body ); 49 | ``` 50 | 51 | ![](../docs/api/_images/example.gif) 52 | 53 | --- 54 | 55 | ## [🎮 - Play with this example on StackBlitz](https://stackblitz.com/edit/node-freprp?file=index.tsx) 56 | ## [👨‍🏫 - Learn Reflex in 5 minutes](https://zouloux.github.io/reflex/learn/) 57 | ## [📘 - See API documentation](https://zouloux.github.io/reflex/api/) 58 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | .DS_Store 3 | 4 | node_modules 5 | dist/ 6 | /deopt/v8-deopt-viewer -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | /_ 2 | .github 3 | .idea 4 | .DS_Store 5 | 6 | /bits/ 7 | /docs 8 | /reflex-dev 9 | /src 10 | /reflex-learn 11 | /.gitignore 12 | /CODEGOLF.md 13 | /LICENSE 14 | /tsconfig.json 15 | /v8-deopt-viewer 16 | /reflex-deopt -------------------------------------------------------------------------------- /CODEGOLF.md: -------------------------------------------------------------------------------- 1 | # About code golfing 2 | 3 | Main rules are to create the lightest bundle possible BUT to keep the code as 4 | much as performant possible AND readable. Code golf optimization which will ruin 5 | readability to slim 2 bytes from the bundle will be prohibited. 6 | 7 | ### Underscore and terser 8 | 9 | Terser is configured in `tsbundle` to mangle properties starting with a `_`. 10 | So any member of object, or module starting with `_` will be renamed into a 11 | shorter version. Variable names with or without underscore will be mangled. 12 | So it's important to use `_name` prop names if the property is meant to be private. 13 | 14 | #### Ex with object : 15 | ```js 16 | const _member = { // _member will be mangler because it's a var and not a prop 17 | keep : "", // Will be kept as it 18 | _mangled : true // Will be mangled 19 | } 20 | ``` 21 | Will become : 22 | ```js 23 | const a = { 24 | keep : "", 25 | b : true 26 | } 27 | ``` 28 | 29 | ##### Ex with module : 30 | ```js 31 | export const _TEXT_NODE_TYPE_NAME = "#T" 32 | export const publicProperty = "Public" 33 | ``` 34 | Will become : 35 | ```js 36 | export const a = "#T" 37 | export const publicProperty = "Public" 38 | ``` 39 | 40 | ### Source code vs dist code 41 | 42 | It's important to keep source code readable. Terser will compress and mangle, 43 | so no need to compress var names in source code, use plain variable names. 44 | 45 | ### NODE_ENV 46 | Instructions like `process.env.NODE_ENV != "production"` will be parsed and dead 47 | code will be removed by terser. 48 | 49 | ### Beware of induced helpers 50 | 51 | If you use typescript code which can induce transpiling, typescript can add helpers. 52 | Ex, if you use `const { a, b, ..rest } = baseObject` which is not available in 53 | base target (`es2017`), generated source code can be heavier than writing 54 | assignment manually. 55 | 56 | 57 | ### Export glob from import 58 | 59 | - This will force TS compiler to include a helper. 60 | - + It may include unwanted members. 61 | 62 | - 🚫 `export * from "./state"` 63 | - ✅ `export { state, asyncState } from "./state"` 64 | 65 | ### Typeof something 66 | 67 | To check if some property is undefined : 68 | - ✅ `typeof self < 'u' ? isNotUndefined : isUndefined` 69 | 70 | > Be aware that it destroy performances and should only be used outside loops and critical code 71 | 72 | ### Casting 73 | 74 | ##### Convert to string 75 | - 🚫 `something.toString()` 76 | - ✅ `something+''` 77 | 78 | ##### Convert to number 79 | - `let numberAsString = "40"` 80 | - 🚫 `let number = parseInt(numberAsString, 10)` 81 | - ✅ `let number = +numberAsString` 82 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Alexis Bouhet 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 | # Reflex 2 | 3 | __Reflex__ is a tiny and fast reactive UI library. 4 | 5 | - 🦋 Super lightweight and **0 dependency**, about **4kb** min-zipped 6 | - 🏎 Highly performant diffing algorithm 7 | - 🔋 Batteries included with **factory extensions** 8 | - ⚛️ Truly reactive, states are Signals by design 9 | - 🔍 Fine grained DOM updates for even better performances, components are rendered only when needed 10 | - 🤓 Typed JSX 11 | - 🍰 Easy to learn 12 | - 🤘️ HMR Support for Vite 13 | - 🚀 Super fast hydration support 14 | 15 | --- 16 | 17 | ## Quick example 18 | 19 | ```shell 20 | # Install with npm 21 | npm i reflex-dom 22 | ``` 23 | 24 | ```typescript jsx 25 | import { h, render, state } from 'reflex-dom'; 26 | export function App (props) { 27 | // State management 28 | const counter = state(0); 29 | const increment = () => counter.value ++; 30 | const reset = () => counter.value = 0; 31 | // Components returns a render function 32 | return () =>
33 |

Hello from {props.name} 👋

34 |   35 |   36 | Counter : {counter} 37 |
38 | } 39 | render( , document.body ); 40 | ``` 41 | 42 | Check API and tutorials on [Github](https://github.com/zouloux/reflex) -------------------------------------------------------------------------------- /bits/index.es2017.cjs.svg: -------------------------------------------------------------------------------- 1 | 16.32kb -------------------------------------------------------------------------------- /bits/index.es2020.mjs.svg: -------------------------------------------------------------------------------- 1 | 15.08kb -------------------------------------------------------------------------------- /bits/jsx-helpers.es2017.cjs.svg: -------------------------------------------------------------------------------- 1 | 380b -------------------------------------------------------------------------------- /bits/jsx-helpers.es2020.mjs.svg: -------------------------------------------------------------------------------- 1 | 277b -------------------------------------------------------------------------------- /bits/performance-helpers.es2017.cjs.svg: -------------------------------------------------------------------------------- 1 | 1.67kb -------------------------------------------------------------------------------- /bits/performance-helpers.es2020.mjs.svg: -------------------------------------------------------------------------------- 1 | 1.55kb -------------------------------------------------------------------------------- /bits/performance-helpers.mjs.svg: -------------------------------------------------------------------------------- 1 | 1.51kb -------------------------------------------------------------------------------- /bits/reflex.es2017.min.cjs.svg: -------------------------------------------------------------------------------- 1 | 3.97kb -------------------------------------------------------------------------------- /bits/reflex.es2017.min.js.svg: -------------------------------------------------------------------------------- 1 | 4.43kb -------------------------------------------------------------------------------- /bits/reflex.es2020.min.js.svg: -------------------------------------------------------------------------------- 1 | 4.28kb -------------------------------------------------------------------------------- /bits/reflex.es2022.min.js.svg: -------------------------------------------------------------------------------- 1 | 4.26kb -------------------------------------------------------------------------------- /bits/render-to-string.es2017.cjs.svg: -------------------------------------------------------------------------------- 1 | 1.03kb -------------------------------------------------------------------------------- /bits/render-to-string.es2020.mjs.svg: -------------------------------------------------------------------------------- 1 | 1.45kb -------------------------------------------------------------------------------- /bits/render-to-string.mjs.svg: -------------------------------------------------------------------------------- 1 | 1.44kb -------------------------------------------------------------------------------- /bits/renderToString.es2017.cjs.svg: -------------------------------------------------------------------------------- 1 | 1.03kb -------------------------------------------------------------------------------- /bits/renderToString.es2020.mjs.svg: -------------------------------------------------------------------------------- 1 | 1.45kb -------------------------------------------------------------------------------- /deopt/src/index.tsx: -------------------------------------------------------------------------------- 1 | import { compute, h, state } from "../../src" 2 | import { renderToString } from "../../src/render-to-string" 3 | import { For } from "../../src/performance-helpers"; 4 | 5 | 6 | function Item ( props ) { 7 | const $class = compute(() => props.isSelected ? "selected" : "" ) 8 | return
9 | ITEM 10 | { props.label } 11 |
12 | } 13 | 14 | function getState ( start = 0, total = 1000 ) { 15 | return Array.from({ length: total }).map( (_, i) => ({ 16 | key: start + i, 17 | label: `Label ${start + i}`, 18 | isSelected: (start + i) % 2 == 0, 19 | })) 20 | } 21 | 22 | const $s1 = state( () => getState(0, 10) ) 23 | 24 | function App () { 25 | return
26 |

Deoptigate test

27 | 28 | { item => } 29 | 30 |
31 | } 32 | 33 | (async function () { 34 | const a = 35 | const c1 = renderToString( a ) 36 | await $s1.set( () => getState(5, 20) ) 37 | const c2 = renderToString( a ) 38 | 39 | console.log( c1 ) 40 | console.log("-----") 41 | console.log( c2 ) 42 | })() 43 | -------------------------------------------------------------------------------- /docs/.nojekyll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zouloux/reflex-dom/0a0675785267bad989975c945462085d4caf284c/docs/.nojekyll -------------------------------------------------------------------------------- /docs/api/README.md: -------------------------------------------------------------------------------- 1 | # Reflex API documentation 2 | 3 | ## About 4 | 5 | ### History 6 | 7 | Reflex idea comes from 2018 when React proposed [React Hooks](https://github.com/reactjs/rfcs/pull/68). 8 | After digging hooks for some months, a [lot of people](https://github.com/zouloux/prehook-proof-of-concept/issues/1) talked about the benefits of having a __Factory Phase__ with a render function returned instead of all in one function. 9 | [I proposed a proof of concept of a factory component system](https://github.com/zouloux/prehook-proof-of-concept) based on [Preact](https://github.com/preactjs/preact). 10 | Years after using React hooks (with Preact a lot), I finally had time to get this idea working into a standalone lib ✨ 11 | 12 | ### Things missing 13 | 14 | Here is the list of things missing from React : 15 | - React suspense (loading fallback) - is planned 16 | - renderToString - is planned 17 | - React fiber and asynchronous rendering - not planned 18 | - Class components - not planned 19 | - A lot of other stuff 20 | 21 | Things missing from Solid : 22 | - Crazy performances thanks to compiling - not planned 23 | - A lot of other stuff 24 | 25 | Things missing from Preact : 26 | - Not so much I guess? 27 | 28 | ### Performances 29 | 30 | Reflex goal is to be __as performant as possible__ and __as light as possible__. 31 | Reflex will never be as performant than [Solid](https://github.com/solidjs) (because of the Virtual DOM bottleneck), but targets to be more performant than Preact and React in a lot of cases. 32 | Library weight will be around `3kb gzipped`. It may grow a bit over time if we add some useful features. Features not used in your project can be tree-shaken if you use a bundler (like Parcel or Vite). 33 | [See note](./CODEGOLF.md) about code golfing. 34 | 35 | ![Benchmark](./_images/benchmark.jpg) 36 | 37 | > Check official benchmark on [js-framework-benchmark](https://krausest.github.io/js-framework-benchmark/2022/table_chrome_104.0.5112.79.html) 38 | 39 | For now Reflex performances are between petit-dom and Preact. It can be greatly improved since Reflex is still in beta! 40 | About size, see [Reflex bundle](https://unpkg.com/reflex-dom) vs [Preact bundle](https://unpkg.com/preact) (without states) 41 | 42 | ### Unpkg and Esmsh 43 | 44 | __Reflex__ is available on [Unpkg](https://unpkg.com/reflex-dom) ![](./bits/reflex.es2017.min.js.svg) 45 | - [see unpkg usage example](https://zouloux.github.io/reflex/demos/5-no-bundler-demo/index.html) 46 | 47 | Also available on Esm.sh 48 | - [Esm.sh](https://esm.sh/reflex-dom) 49 | 50 | > Its better to specify used version in your code to avoid code breaking and also for cache and response time. 51 | 52 | ## Roadmap 53 | 54 | #### Done 55 | - [x] A lot of research about how v-dom works 56 | - [x] Actual Virtual dom implementation 57 | - [x] Diffing with complex trees 58 | - [x] Props as dom attributes 59 | - [x] Lifecycle events 60 | - [x] Lifecycle extensions (mounted / unmounted) 61 | - [x] Ref 62 | - [x] Refs 63 | - [x] State 64 | - [x] Automatic memo 65 | - [x] Better performances 66 | - [x] Diff algorithm inspired by [petit-dom](https://github.com/yelouafi/petit-dom/) and Preact 67 | - [x] Props as proxy only if needed (not on functional components) 68 | - [x] SVG support 69 | - [x] renderToString 70 | - [x] JSX Types and runtime 71 | - [x] State invalidation refacto 72 | - [x] New `changed` API which can listen to `states`, `props` and custom handlers with simple API 73 | - [x] Props is not a proxy anymore but a mutated object 74 | 75 | #### Work in progress / TODO 76 | - [ ] WIP - Imperative handles through component instance 77 | - [ ] WIP - Shared ref between parent and child + component ref + refacto component interface for refs and public API 78 | - [ ] WIP - Atomic rendering 79 | - [ ] Crazy performances 80 | - [ ] Hydration 81 | - [ ] Web components integration 82 | - [ ] `npm create reflex-app` 83 | - [ ] Server components streaming 84 | - [ ] Better docs 85 | - [ ] Should update 86 | - [ ] Render to string doc 87 | - [ ] Imperative methods 88 | - [ ] Forward refs and component ref 89 | - [ ] Memo on functional components and shouldUpdate 90 | - [ ] New states 91 | - [ ] Babel examples in doc 92 | - [ ] Code-sandboxes 93 | - [ ] __Release RC-1.0_ and move everything to a Reflex organisation ? 94 | 95 | [//]: # (#### Next, other subjects) 96 | [//]: # (- [ ] Reflex store (compatible with Preact and React)) 97 | [//]: # (- [ ] Reflex router (compatible with Preact and React)) 98 | [//]: # (- [ ] Reflex Run with SSR ?) 99 | [//]: # (- [ ] Reflex UIKit ?) 100 | 101 | 102 | ### Thanks 103 | 104 | - [xdev1](https://github.com/xdev1) for your feedbacks 🙌 ( and the name of __Factory Extensions__ ) 105 | - [Preact](https://github.com/preactjs/preact) for the inspiration and some chunk of codes 106 | - [Petit-DOM](https://github.com/yelouafi/petit-dom) for the inspiration 107 | - [Jason Miller](https://github.com/developit), [Marvin Hagemeister](https://github.com/marvinhagemeister), [Dan Abramov](https://github.com/gaearon), [Sophie Alpert](https://github.com/sophiebits) for your amazing ideas 🙏 108 | -------------------------------------------------------------------------------- /docs/api/_assets/prism-bash.js: -------------------------------------------------------------------------------- 1 | !function(e){var t="\\b(?:BASH|BASHOPTS|BASH_ALIASES|BASH_ARGC|BASH_ARGV|BASH_CMDS|BASH_COMPLETION_COMPAT_DIR|BASH_LINENO|BASH_REMATCH|BASH_SOURCE|BASH_VERSINFO|BASH_VERSION|COLORTERM|COLUMNS|COMP_WORDBREAKS|DBUS_SESSION_BUS_ADDRESS|DEFAULTS_PATH|DESKTOP_SESSION|DIRSTACK|DISPLAY|EUID|GDMSESSION|GDM_LANG|GNOME_KEYRING_CONTROL|GNOME_KEYRING_PID|GPG_AGENT_INFO|GROUPS|HISTCONTROL|HISTFILE|HISTFILESIZE|HISTSIZE|HOME|HOSTNAME|HOSTTYPE|IFS|INSTANCE|JOB|LANG|LANGUAGE|LC_ADDRESS|LC_ALL|LC_IDENTIFICATION|LC_MEASUREMENT|LC_MONETARY|LC_NAME|LC_NUMERIC|LC_PAPER|LC_TELEPHONE|LC_TIME|LESSCLOSE|LESSOPEN|LINES|LOGNAME|LS_COLORS|MACHTYPE|MAILCHECK|MANDATORY_PATH|NO_AT_BRIDGE|OLDPWD|OPTERR|OPTIND|ORBIT_SOCKETDIR|OSTYPE|PAPERSIZE|PATH|PIPESTATUS|PPID|PS1|PS2|PS3|PS4|PWD|RANDOM|REPLY|SECONDS|SELINUX_INIT|SESSION|SESSIONTYPE|SESSION_MANAGER|SHELL|SHELLOPTS|SHLVL|SSH_AUTH_SOCK|TERM|UID|UPSTART_EVENTS|UPSTART_INSTANCE|UPSTART_JOB|UPSTART_SESSION|USER|WINDOWID|XAUTHORITY|XDG_CONFIG_DIRS|XDG_CURRENT_DESKTOP|XDG_DATA_DIRS|XDG_GREETER_DATA_DIR|XDG_MENU_PREFIX|XDG_RUNTIME_DIR|XDG_SEAT|XDG_SEAT_PATH|XDG_SESSION_DESKTOP|XDG_SESSION_ID|XDG_SESSION_PATH|XDG_SESSION_TYPE|XDG_VTNR|XMODIFIERS)\\b",a={pattern:/(^(["']?)\w+\2)[ \t]+\S.*/,lookbehind:!0,alias:"punctuation",inside:null},n={bash:a,environment:{pattern:RegExp("\\$"+t),alias:"constant"},variable:[{pattern:/\$?\(\([\s\S]+?\)\)/,greedy:!0,inside:{variable:[{pattern:/(^\$\(\([\s\S]+)\)\)/,lookbehind:!0},/^\$\(\(/],number:/\b0x[\dA-Fa-f]+\b|(?:\b\d+(?:\.\d*)?|\B\.\d+)(?:[Ee]-?\d+)?/,operator:/--|\+\+|\*\*=?|<<=?|>>=?|&&|\|\||[=!+\-*/%<>^&|]=?|[?~:]/,punctuation:/\(\(?|\)\)?|,|;/}},{pattern:/\$\((?:\([^)]+\)|[^()])+\)|`[^`]+`/,greedy:!0,inside:{variable:/^\$\(|^`|\)$|`$/}},{pattern:/\$\{[^}]+\}/,greedy:!0,inside:{operator:/:[-=?+]?|[!\/]|##?|%%?|\^\^?|,,?/,punctuation:/[\[\]]/,environment:{pattern:RegExp("(\\{)"+t),lookbehind:!0,alias:"constant"}}},/\$(?:\w+|[#?*!@$])/],entity:/\\(?:[abceEfnrtv\\"]|O?[0-7]{1,3}|U[0-9a-fA-F]{8}|u[0-9a-fA-F]{4}|x[0-9a-fA-F]{1,2})/};e.languages.bash={shebang:{pattern:/^#!\s*\/.*/,alias:"important"},comment:{pattern:/(^|[^"{\\$])#.*/,lookbehind:!0},"function-name":[{pattern:/(\bfunction\s+)[\w-]+(?=(?:\s*\(?:\s*\))?\s*\{)/,lookbehind:!0,alias:"function"},{pattern:/\b[\w-]+(?=\s*\(\s*\)\s*\{)/,alias:"function"}],"for-or-select":{pattern:/(\b(?:for|select)\s+)\w+(?=\s+in\s)/,alias:"variable",lookbehind:!0},"assign-left":{pattern:/(^|[\s;|&]|[<>]\()\w+(?:\.\w+)*(?=\+?=)/,inside:{environment:{pattern:RegExp("(^|[\\s;|&]|[<>]\\()"+t),lookbehind:!0,alias:"constant"}},alias:"variable",lookbehind:!0},parameter:{pattern:/(^|\s)-{1,2}(?:\w+:[+-]?)?\w+(?:\.\w+)*(?=[=\s]|$)/,alias:"variable",lookbehind:!0},string:[{pattern:/((?:^|[^<])<<-?\s*)(\w+)\s[\s\S]*?(?:\r?\n|\r)\2/,lookbehind:!0,greedy:!0,inside:n},{pattern:/((?:^|[^<])<<-?\s*)(["'])(\w+)\2\s[\s\S]*?(?:\r?\n|\r)\3/,lookbehind:!0,greedy:!0,inside:{bash:a}},{pattern:/(^|[^\\](?:\\\\)*)"(?:\\[\s\S]|\$\([^)]+\)|\$(?!\()|`[^`]+`|[^"\\`$])*"/,lookbehind:!0,greedy:!0,inside:n},{pattern:/(^|[^$\\])'[^']*'/,lookbehind:!0,greedy:!0},{pattern:/\$'(?:[^'\\]|\\[\s\S])*'/,greedy:!0,inside:{entity:n.entity}}],environment:{pattern:RegExp("\\$?"+t),alias:"constant"},variable:n.variable,function:{pattern:/(^|[\s;|&]|[<>]\()(?:add|apropos|apt|apt-cache|apt-get|aptitude|aspell|automysqlbackup|awk|basename|bash|bc|bconsole|bg|bzip2|cal|cargo|cat|cfdisk|chgrp|chkconfig|chmod|chown|chroot|cksum|clear|cmp|column|comm|composer|cp|cron|crontab|csplit|curl|cut|date|dc|dd|ddrescue|debootstrap|df|diff|diff3|dig|dir|dircolors|dirname|dirs|dmesg|docker|docker-compose|du|egrep|eject|env|ethtool|expand|expect|expr|fdformat|fdisk|fg|fgrep|file|find|fmt|fold|format|free|fsck|ftp|fuser|gawk|git|gparted|grep|groupadd|groupdel|groupmod|groups|grub-mkconfig|gzip|halt|head|hg|history|host|hostname|htop|iconv|id|ifconfig|ifdown|ifup|import|install|ip|java|jobs|join|kill|killall|less|link|ln|locate|logname|logrotate|look|lpc|lpr|lprint|lprintd|lprintq|lprm|ls|lsof|lynx|make|man|mc|mdadm|mkconfig|mkdir|mke2fs|mkfifo|mkfs|mkisofs|mknod|mkswap|mmv|more|most|mount|mtools|mtr|mutt|mv|nano|nc|netstat|nice|nl|node|nohup|notify-send|npm|nslookup|op|open|parted|passwd|paste|pathchk|ping|pkill|pnpm|podman|podman-compose|popd|pr|printcap|printenv|ps|pushd|pv|quota|quotacheck|quotactl|ram|rar|rcp|reboot|remsync|rename|renice|rev|rm|rmdir|rpm|rsync|scp|screen|sdiff|sed|sendmail|seq|service|sftp|sh|shellcheck|shuf|shutdown|sleep|slocate|sort|split|ssh|stat|strace|su|sudo|sum|suspend|swapon|sync|sysctl|tac|tail|tar|tee|time|timeout|top|touch|tr|traceroute|tsort|tty|umount|uname|unexpand|uniq|units|unrar|unshar|unzip|update-grub|uptime|useradd|userdel|usermod|users|uudecode|uuencode|v|vcpkg|vdir|vi|vim|virsh|vmstat|wait|watch|wc|wget|whereis|which|who|whoami|write|xargs|xdg-open|yarn|yes|zenity|zip|zsh|zypper)(?=$|[)\s;|&])/,lookbehind:!0},keyword:{pattern:/(^|[\s;|&]|[<>]\()(?:case|do|done|elif|else|esac|fi|for|function|if|in|select|then|until|while)(?=$|[)\s;|&])/,lookbehind:!0},builtin:{pattern:/(^|[\s;|&]|[<>]\()(?:\.|:|alias|bind|break|builtin|caller|cd|command|continue|declare|echo|enable|eval|exec|exit|export|getopts|hash|help|let|local|logout|mapfile|printf|pwd|read|readarray|readonly|return|set|shift|shopt|source|test|times|trap|type|typeset|ulimit|umask|unalias|unset)(?=$|[)\s;|&])/,lookbehind:!0,alias:"class-name"},boolean:{pattern:/(^|[\s;|&]|[<>]\()(?:false|true)(?=$|[)\s;|&])/,lookbehind:!0},"file-descriptor":{pattern:/\B&\d\b/,alias:"important"},operator:{pattern:/\d?<>|>\||\+=|=[=~]?|!=?|<<[<-]?|[&\d]?>>|\d[<>]&?|[<>][&=]?|&[>&]?|\|[&|]?/,inside:{"file-descriptor":{pattern:/^\d/,alias:"important"}}},punctuation:/\$?\(\(?|\)\)?|\.\.|[{}[\];\\]/,number:{pattern:/(^|\s)(?:[1-9]\d*|0)(?:[.,]\d+)?\b/,lookbehind:!0}},a.inside=e.languages.bash;for(var s=["comment","function-name","for-or-select","assign-left","parameter","string","environment","function","keyword","builtin","boolean","file-descriptor","operator","punctuation","number"],o=n.variable[1].inside,i=0;i|&&=?|\|\|=?|[!=]==|<<=?|>>>?=?|[-+*/%&|^!=<>]=?|\.{3}|\?\?=?|\?\.?|[~:]/}),Prism.languages.javascript["class-name"][0].pattern=/(\b(?:class|extends|implements|instanceof|interface|new)\s+)[\w.\\]+/,Prism.languages.insertBefore("javascript","keyword",{regex:{pattern:RegExp("((?:^|[^$\\w\\xA0-\\uFFFF.\"'\\])\\s]|\\b(?:return|yield))\\s*)/(?:(?:\\[(?:[^\\]\\\\\r\n]|\\\\.)*\\]|\\\\.|[^/\\\\\\[\r\n])+/[dgimyus]{0,7}|(?:\\[(?:[^[\\]\\\\\r\n]|\\\\.|\\[(?:[^[\\]\\\\\r\n]|\\\\.|\\[(?:[^[\\]\\\\\r\n]|\\\\.)*\\])*\\])*\\]|\\\\.|[^/\\\\\\[\r\n])+/[dgimyus]{0,7}v[dgimyus]{0,7})(?=(?:\\s|/\\*(?:[^*]|\\*(?!/))*\\*/)*(?:$|[\r\n,.;:})\\]]|//))"),lookbehind:!0,greedy:!0,inside:{"regex-source":{pattern:/^(\/)[\s\S]+(?=\/[a-z]*$)/,lookbehind:!0,alias:"language-regex",inside:Prism.languages.regex},"regex-delimiter":/^\/|\/$/,"regex-flags":/^[a-z]+$/}},"function-variable":{pattern:/#?(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*(?=\s*[=:]\s*(?:async\s*)?(?:\bfunction\b|(?:\((?:[^()]|\([^()]*\))*\)|(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*)\s*=>))/,alias:"function"},parameter:[{pattern:/(function(?:\s+(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*)?\s*\(\s*)(?!\s)(?:[^()\s]|\s+(?![\s)])|\([^()]*\))+(?=\s*\))/,lookbehind:!0,inside:Prism.languages.javascript},{pattern:/(^|[^$\w\xA0-\uFFFF])(?!\s)[_$a-z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*(?=\s*=>)/i,lookbehind:!0,inside:Prism.languages.javascript},{pattern:/(\(\s*)(?!\s)(?:[^()\s]|\s+(?![\s)])|\([^()]*\))+(?=\s*\)\s*=>)/,lookbehind:!0,inside:Prism.languages.javascript},{pattern:/((?:\b|\s|^)(?!(?:as|async|await|break|case|catch|class|const|continue|debugger|default|delete|do|else|enum|export|extends|finally|for|from|function|get|if|implements|import|in|instanceof|interface|let|new|null|of|package|private|protected|public|return|set|static|super|switch|this|throw|try|typeof|undefined|var|void|while|with|yield)(?![$\w\xA0-\uFFFF]))(?:(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*\s*)\(\s*|\]\s*\(\s*)(?!\s)(?:[^()\s]|\s+(?![\s)])|\([^()]*\))+(?=\s*\)\s*\{)/,lookbehind:!0,inside:Prism.languages.javascript}],constant:/\b[A-Z](?:[A-Z_]|\dx?)*\b/}),Prism.languages.insertBefore("javascript","string",{hashbang:{pattern:/^#!.*/,greedy:!0,alias:"comment"},"template-string":{pattern:/`(?:\\[\s\S]|\$\{(?:[^{}]|\{(?:[^{}]|\{[^}]*\})*\})+\}|(?!\$\{)[^\\`])*`/,greedy:!0,inside:{"template-punctuation":{pattern:/^`|`$/,alias:"string"},interpolation:{pattern:/((?:^|[^\\])(?:\\{2})*)\$\{(?:[^{}]|\{(?:[^{}]|\{[^}]*\})*\})+\}/,lookbehind:!0,inside:{"interpolation-punctuation":{pattern:/^\$\{|\}$/,alias:"punctuation"},rest:Prism.languages.javascript}},string:/[\s\S]+/}},"string-property":{pattern:/((?:^|[,{])[ \t]*)(["'])(?:\\(?:\r\n|[\s\S])|(?!\2)[^\\\r\n])*\2(?=\s*:)/m,lookbehind:!0,greedy:!0,alias:"property"}}),Prism.languages.insertBefore("javascript","operator",{"literal-property":{pattern:/((?:^|[,{])[ \t]*)(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*(?=\s*:)/m,lookbehind:!0,alias:"property"}}),Prism.languages.markup&&(Prism.languages.markup.tag.addInlined("script","javascript"),Prism.languages.markup.tag.addAttribute("on(?:abort|blur|change|click|composition(?:end|start|update)|dblclick|error|focus(?:in|out)?|key(?:down|up)|load|mouse(?:down|enter|leave|move|out|over|up)|reset|resize|scroll|select|slotchange|submit|unload|wheel)","javascript")),Prism.languages.js=Prism.languages.javascript; -------------------------------------------------------------------------------- /docs/api/_assets/prism-json.js: -------------------------------------------------------------------------------- 1 | Prism.languages.json={property:{pattern:/(^|[^\\])"(?:\\.|[^\\"\r\n])*"(?=\s*:)/,lookbehind:!0,greedy:!0},string:{pattern:/(^|[^\\])"(?:\\.|[^\\"\r\n])*"(?!\s*:)/,lookbehind:!0,greedy:!0},comment:{pattern:/\/\/.*|\/\*[\s\S]*?(?:\*\/|$)/,greedy:!0},number:/-?\b\d+(?:\.\d+)?(?:e[+-]?\d+)?\b/i,punctuation:/[{}[\],]/,operator:/:/,boolean:/\b(?:false|true)\b/,null:{pattern:/\bnull\b/,alias:"keyword"}},Prism.languages.webmanifest=Prism.languages.json; -------------------------------------------------------------------------------- /docs/api/_assets/prism-jsx.js: -------------------------------------------------------------------------------- 1 | !function(t){var n=t.util.clone(t.languages.javascript),e="(?:\\{*\\.{3}(?:[^{}]|)*\\})";function a(t,n){return t=t.replace(//g,(function(){return"(?:\\s|//.*(?!.)|/\\*(?:[^*]|\\*(?!/))\\*/)"})).replace(//g,(function(){return"(?:\\{(?:\\{(?:\\{[^{}]*\\}|[^{}])*\\}|[^{}])*\\})"})).replace(//g,(function(){return e})),RegExp(t,n)}e=a(e).source,t.languages.jsx=t.languages.extend("markup",n),t.languages.jsx.tag.pattern=a("+(?:[\\w.:$-]+(?:=(?:\"(?:\\\\[^]|[^\\\\\"])*\"|'(?:\\\\[^]|[^\\\\'])*'|[^\\s{'\"/>=]+|))?|))**/?)?>"),t.languages.jsx.tag.inside.tag.pattern=/^<\/?[^\s>\/]*/,t.languages.jsx.tag.inside["attr-value"].pattern=/=(?!\{)(?:"(?:\\[\s\S]|[^\\"])*"|'(?:\\[\s\S]|[^\\'])*'|[^\s'">]+)/,t.languages.jsx.tag.inside.tag.inside["class-name"]=/^[A-Z]\w*(?:\.[A-Z]\w*)*$/,t.languages.jsx.tag.inside.comment=n.comment,t.languages.insertBefore("inside","attr-name",{spread:{pattern:a(""),inside:t.languages.jsx}},t.languages.jsx.tag),t.languages.insertBefore("inside","special-attr",{script:{pattern:a("="),alias:"language-javascript",inside:{"script-punctuation":{pattern:/^=(?=\{)/,alias:"punctuation"},rest:t.languages.jsx}}},t.languages.jsx.tag);var s=function(t){return t?"string"==typeof t?t:"string"==typeof t.content?t.content:t.content.map(s).join(""):""},g=function(n){for(var e=[],a=0;a0&&e[e.length-1].tagName===s(o.content[0].content[1])&&e.pop():"/>"===o.content[o.content.length-1].content||e.push({tagName:s(o.content[0].content[1]),openedBraces:0}):e.length>0&&"punctuation"===o.type&&"{"===o.content?e[e.length-1].openedBraces++:e.length>0&&e[e.length-1].openedBraces>0&&"punctuation"===o.type&&"}"===o.content?e[e.length-1].openedBraces--:i=!0),(i||"string"==typeof o)&&e.length>0&&0===e[e.length-1].openedBraces){var r=s(o);a0&&("string"==typeof n[a-1]||"plain-text"===n[a-1].type)&&(r=s(n[a-1])+r,n.splice(a-1,1),a--),n[a]=new t.Token("plain-text",r,null,r)}o.content&&"string"!=typeof o.content&&g(o.content)}};t.hooks.add("after-tokenize",(function(t){"jsx"!==t.language&&"tsx"!==t.language||g(t.tokens)}))}(Prism); -------------------------------------------------------------------------------- /docs/api/_assets/prism-markup.js: -------------------------------------------------------------------------------- 1 | Prism.languages.markup={comment:{pattern://,greedy:!0},prolog:{pattern:/<\?[\s\S]+?\?>/,greedy:!0},doctype:{pattern:/"'[\]]|"[^"]*"|'[^']*')+(?:\[(?:[^<"'\]]|"[^"]*"|'[^']*'|<(?!!--)|)*\]\s*)?>/i,greedy:!0,inside:{"internal-subset":{pattern:/(^[^\[]*\[)[\s\S]+(?=\]>$)/,lookbehind:!0,greedy:!0,inside:null},string:{pattern:/"[^"]*"|'[^']*'/,greedy:!0},punctuation:/^$|[[\]]/,"doctype-tag":/^DOCTYPE/i,name:/[^\s<>'"]+/}},cdata:{pattern://i,greedy:!0},tag:{pattern:/<\/?(?!\d)[^\s>\/=$<%]+(?:\s(?:\s*[^\s>\/=]+(?:\s*=\s*(?:"[^"]*"|'[^']*'|[^\s'">=]+(?=[\s>]))|(?=[\s/>])))+)?\s*\/?>/,greedy:!0,inside:{tag:{pattern:/^<\/?[^\s>\/]+/,inside:{punctuation:/^<\/?/,namespace:/^[^\s>\/:]+:/}},"special-attr":[],"attr-value":{pattern:/=\s*(?:"[^"]*"|'[^']*'|[^\s'">=]+)/,inside:{punctuation:[{pattern:/^=/,alias:"attr-equals"},{pattern:/^(\s*)["']|["']$/,lookbehind:!0}]}},punctuation:/\/?>/,"attr-name":{pattern:/[^\s>\/]+/,inside:{namespace:/^[^\s>\/:]+:/}}}},entity:[{pattern:/&[\da-z]{1,8};/i,alias:"named-entity"},/&#x?[\da-f]{1,8};/i]},Prism.languages.markup.tag.inside["attr-value"].inside.entity=Prism.languages.markup.entity,Prism.languages.markup.doctype.inside["internal-subset"].inside=Prism.languages.markup,Prism.hooks.add("wrap",(function(a){"entity"===a.type&&(a.attributes.title=a.content.replace(/&/,"&"))})),Object.defineProperty(Prism.languages.markup.tag,"addInlined",{value:function(a,e){var s={};s["language-"+e]={pattern:/(^$)/i,lookbehind:!0,inside:Prism.languages[e]},s.cdata=/^$/i;var t={"included-cdata":{pattern://i,inside:s}};t["language-"+e]={pattern:/[\s\S]+/,inside:Prism.languages[e]};var n={};n[a]={pattern:RegExp("(<__[^>]*>)(?:))*\\]\\]>|(?!)".replace(/__/g,(function(){return a})),"i"),lookbehind:!0,greedy:!0,inside:t},Prism.languages.insertBefore("markup","cdata",n)}}),Object.defineProperty(Prism.languages.markup.tag,"addAttribute",{value:function(a,e){Prism.languages.markup.tag.inside["special-attr"].push({pattern:RegExp("(^|[\"'\\s])(?:"+a+")\\s*=\\s*(?:\"[^\"]*\"|'[^']*'|[^\\s'\">=]+(?=[\\s>]))","i"),lookbehind:!0,inside:{"attr-name":/^[^\s=]+/,"attr-value":{pattern:/=[\s\S]+/,inside:{value:{pattern:/(^=\s*(["']|(?!["'])))\S[\s\S]*(?=\2$)/,lookbehind:!0,alias:[e,"language-"+e],inside:Prism.languages[e]},punctuation:[{pattern:/^=/,alias:"attr-equals"},/"|'/]}}}})}}),Prism.languages.html=Prism.languages.markup,Prism.languages.mathml=Prism.languages.markup,Prism.languages.svg=Prism.languages.markup,Prism.languages.xml=Prism.languages.extend("markup",{}),Prism.languages.ssml=Prism.languages.xml,Prism.languages.atom=Prism.languages.xml,Prism.languages.rss=Prism.languages.xml; -------------------------------------------------------------------------------- /docs/api/_assets/prism-tsx.js: -------------------------------------------------------------------------------- 1 | !function(e){var a=e.util.clone(e.languages.typescript);e.languages.tsx=e.languages.extend("jsx",a),delete e.languages.tsx.parameter,delete e.languages.tsx["literal-property"];var t=e.languages.tsx.tag;t.pattern=RegExp("(^|[^\\w$]|(?=]|<(?:[^<>]|<[^<>]*>)*>)*>)?/,lookbehind:!0,greedy:!0,inside:null},builtin:/\b(?:Array|Function|Promise|any|boolean|console|never|number|string|symbol|unknown)\b/}),e.languages.typescript.keyword.push(/\b(?:abstract|declare|is|keyof|readonly|require)\b/,/\b(?:asserts|infer|interface|module|namespace|type)\b(?=\s*(?:[{_$a-zA-Z\xA0-\uFFFF]|$))/,/\btype\b(?=\s*(?:[\{*]|$))/),delete e.languages.typescript.parameter,delete e.languages.typescript["literal-property"];var s=e.languages.extend("typescript",{});delete s["class-name"],e.languages.typescript["class-name"].inside=s,e.languages.insertBefore("typescript","function",{decorator:{pattern:/@[$\w\xA0-\uFFFF]+/,inside:{at:{pattern:/^@/,alias:"operator"},function:/^[\s\S]+/}},"generic-function":{pattern:/#?(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*\s*<(?:[^<>]|<(?:[^<>]|<[^<>]*>)*>)*>(?=\s*\()/,greedy:!0,inside:{function:/^#?(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*/,generic:{pattern:/<[\s\S]+/,alias:"class-name",inside:s}}}}),e.languages.ts=e.languages.typescript}(Prism); -------------------------------------------------------------------------------- /docs/api/_assets/vue.css: -------------------------------------------------------------------------------- 1 | @import url("https://fonts.googleapis.com/css?family=Roboto+Mono|Source+Sans+Pro:300,400,600");*{-webkit-font-smoothing:antialiased;-webkit-overflow-scrolling:touch;-webkit-tap-highlight-color:rgba(0,0,0,0);-webkit-text-size-adjust:none;-webkit-touch-callout:none;box-sizing:border-box}body:not(.ready){overflow:hidden}body:not(.ready) .app-nav,body:not(.ready)>nav,body:not(.ready) [data-cloak]{display:none}div#app{font-size:30px;font-weight:lighter;margin:40vh auto;text-align:center}div#app:empty:before{content:"Loading..."}.emoji{height:1.2rem;vertical-align:middle}.progress{background-color:var(--theme-color,#42b983);height:2px;left:0;position:fixed;right:0;top:0;transition:width .2s,opacity .4s;width:0;z-index:999999}.search a:hover{color:var(--theme-color,#42b983)}.search .search-keyword{color:var(--theme-color,#42b983);font-style:normal;font-weight:700}body,html{height:100%}body{-moz-osx-font-smoothing:grayscale;-webkit-font-smoothing:antialiased;color:#34495e;font-family:Source Sans Pro,Helvetica Neue,Arial,sans-serif;font-size:15px;letter-spacing:0;margin:0;overflow-x:hidden}img{max-width:100%}a[disabled]{cursor:not-allowed;opacity:.6}kbd{border:1px solid #ccc;border-radius:3px;display:inline-block;font-size:12px!important;line-height:12px;margin-bottom:3px;padding:3px 5px;vertical-align:middle}li input[type=checkbox]{margin:0 .2em .25em 0;vertical-align:middle}.app-nav{margin:25px 60px 0 0;position:absolute;right:0;text-align:right;z-index:10}.app-nav.no-badge{margin-right:25px}.app-nav p{margin:0}.app-nav>a{margin:0 1rem;padding:5px 0}.app-nav li,.app-nav ul{display:inline-block;list-style:none;margin:0}.app-nav a{color:inherit;font-size:16px;text-decoration:none;transition:color .3s}.app-nav a:hover{color:var(--theme-color,#42b983)}.app-nav a.active{border-bottom:2px solid var(--theme-color,#42b983);color:var(--theme-color,#42b983)}.app-nav li{display:inline-block;margin:0 1rem;padding:5px 0;position:relative;cursor:pointer}.app-nav li ul{background-color:#fff;border:1px solid;border-color:#ddd #ddd #ccc;border-radius:4px;box-sizing:border-box;display:none;max-height:calc(100vh - 61px);overflow-y:auto;padding:10px 0;position:absolute;right:-15px;text-align:left;top:100%;white-space:nowrap}.app-nav li ul li{display:block;font-size:14px;line-height:1rem;margin:8px 14px;white-space:nowrap}.app-nav li ul a{display:block;font-size:inherit;margin:0;padding:0}.app-nav li ul a.active{border-bottom:0}.app-nav li:hover ul{display:block}.github-corner{border-bottom:0;position:fixed;right:0;text-decoration:none;top:0;z-index:1}.github-corner:hover .octo-arm{-webkit-animation:octocat-wave .56s ease-in-out;animation:octocat-wave .56s ease-in-out}.github-corner svg{color:#fff;fill:var(--theme-color,#42b983);height:80px;width:80px}main{display:block;position:relative;width:100vw;height:100%;z-index:0}main.hidden{display:none}.anchor{display:inline-block;text-decoration:none;transition:all .3s}.anchor span{color:#34495e}.anchor:hover{text-decoration:underline}.sidebar{border-right:1px solid rgba(0,0,0,.07);overflow-y:auto;padding:40px 0 0;position:absolute;top:0;bottom:0;left:0;transition:transform .25s ease-out;width:300px;z-index:20}.sidebar>h1{margin:0 auto 1rem;font-size:1.5rem;font-weight:300;text-align:center}.sidebar>h1 a{color:inherit;text-decoration:none}.sidebar>h1 .app-nav{display:block;position:static}.sidebar .sidebar-nav{line-height:2em;padding-bottom:40px}.sidebar li.collapse .app-sub-sidebar{display:none}.sidebar ul{margin:0 0 0 15px;padding:0}.sidebar li>p{font-weight:700;margin:0}.sidebar ul,.sidebar ul li{list-style:none}.sidebar ul li a{border-bottom:none;display:block}.sidebar ul li ul{padding-left:20px}.sidebar::-webkit-scrollbar{width:4px}.sidebar::-webkit-scrollbar-thumb{background:transparent;border-radius:4px}.sidebar:hover::-webkit-scrollbar-thumb{background:hsla(0,0%,53.3%,.4)}.sidebar:hover::-webkit-scrollbar-track{background:hsla(0,0%,53.3%,.1)}.sidebar-toggle{background-color:transparent;background-color:hsla(0,0%,100%,.8);border:0;outline:none;padding:10px;position:absolute;bottom:0;left:0;text-align:center;transition:opacity .3s;width:284px;z-index:30;cursor:pointer}.sidebar-toggle:hover .sidebar-toggle-button{opacity:.4}.sidebar-toggle span{background-color:var(--theme-color,#42b983);display:block;margin-bottom:4px;width:16px;height:2px}body.sticky .sidebar,body.sticky .sidebar-toggle{position:fixed}.content{padding-top:60px;position:absolute;top:0;right:0;bottom:0;left:300px;transition:left .25s ease}.markdown-section{margin:0 auto;max-width:80%;padding:30px 15px 40px;position:relative}.markdown-section>*{box-sizing:border-box;font-size:inherit}.markdown-section>:first-child{margin-top:0!important}.markdown-section hr{border:none;border-bottom:1px solid #eee;margin:2em 0}.markdown-section iframe{border:1px solid #eee;width:1px;min-width:100%}.markdown-section table{border-collapse:collapse;border-spacing:0;display:block;margin-bottom:1rem;overflow:auto;width:100%}.markdown-section th{font-weight:700}.markdown-section td,.markdown-section th{border:1px solid #ddd;padding:6px 13px}.markdown-section tr{border-top:1px solid #ccc}.markdown-section tr:nth-child(2n){background-color:#f8f8f8}.markdown-section p.tip{background-color:#f8f8f8;border-bottom-right-radius:2px;border-left:4px solid #f66;border-top-right-radius:2px;margin:2em 0;padding:12px 24px 12px 30px;position:relative}.markdown-section p.tip:before{background-color:#f66;border-radius:100%;color:#fff;content:"!";font-family:Dosis,Source Sans Pro,Helvetica Neue,Arial,sans-serif;font-size:14px;font-weight:700;left:-12px;line-height:20px;position:absolute;height:20px;width:20px;text-align:center;top:14px}.markdown-section p.tip code{background-color:#efefef}.markdown-section p.tip em{color:#34495e}.markdown-section p.warn{background:rgba(66,185,131,.1);border-radius:2px;padding:1rem}.markdown-section ul.task-list>li{list-style-type:none}body.close .sidebar{transform:translateX(-300px)}body.close .sidebar-toggle{width:auto}body.close .content{left:0}@media print{.app-nav,.github-corner,.sidebar,.sidebar-toggle{display:none}}@media screen and (max-width:768px){.github-corner,.sidebar,.sidebar-toggle{position:fixed}.app-nav{margin-top:16px}.app-nav li ul{top:30px}main{height:auto;min-height:100vh;overflow-x:hidden}.sidebar{left:-300px;transition:transform .25s ease-out}.content{left:0;max-width:100vw;position:static;padding-top:20px;transition:transform .25s ease}.app-nav,.github-corner{transition:transform .25s ease-out}.sidebar-toggle{background-color:transparent;width:auto;padding:30px 30px 10px 10px}body.close .sidebar{transform:translateX(300px)}body.close .sidebar-toggle{background-color:hsla(0,0%,100%,.8);transition:background-color 1s;width:284px;padding:10px}body.close .content{transform:translateX(300px)}body.close .app-nav,body.close .github-corner{display:none}.github-corner:hover .octo-arm{-webkit-animation:none;animation:none}.github-corner .octo-arm{-webkit-animation:octocat-wave .56s ease-in-out;animation:octocat-wave .56s ease-in-out}}@-webkit-keyframes octocat-wave{0%,to{transform:rotate(0)}20%,60%{transform:rotate(-25deg)}40%,80%{transform:rotate(10deg)}}@keyframes octocat-wave{0%,to{transform:rotate(0)}20%,60%{transform:rotate(-25deg)}40%,80%{transform:rotate(10deg)}}section.cover{align-items:center;background-position:50%;background-repeat:no-repeat;background-size:cover;height:100vh;width:100vw;display:none}section.cover.show{display:flex}section.cover.has-mask .mask{background-color:#fff;opacity:.8;position:absolute;top:0;height:100%;width:100%}section.cover .cover-main{flex:1;margin:-20px 16px 0;text-align:center;position:relative}section.cover a{color:inherit;text-decoration:none}section.cover a:hover{text-decoration:none}section.cover p{line-height:1.5rem;margin:1em 0}section.cover h1{color:inherit;font-size:2.5rem;font-weight:300;margin:.625rem 0 2.5rem;position:relative;text-align:center}section.cover h1 a{display:block}section.cover h1 small{bottom:-.4375rem;font-size:1rem;position:absolute}section.cover blockquote{font-size:1.5rem;text-align:center}section.cover ul{line-height:1.8;list-style-type:none;margin:1em auto;max-width:500px;padding:0}section.cover .cover-main>p:last-child a{border-radius:2rem;border:1px solid var(--theme-color,#42b983);box-sizing:border-box;color:var(--theme-color,#42b983);display:inline-block;font-size:1.05rem;letter-spacing:.1rem;margin:.5rem 1rem;padding:.75em 2rem;text-decoration:none;transition:all .15s ease}section.cover .cover-main>p:last-child a:last-child{background-color:var(--theme-color,#42b983);color:#fff}section.cover .cover-main>p:last-child a:last-child:hover{color:inherit;opacity:.8}section.cover .cover-main>p:last-child a:hover{color:inherit}section.cover blockquote>p>a{border-bottom:2px solid var(--theme-color,#42b983);transition:color .3s}section.cover blockquote>p>a:hover{color:var(--theme-color,#42b983)}.sidebar,body{background-color:#fff}.sidebar{color:#364149}.sidebar li{margin:6px 0}.sidebar ul li a{color:#505d6b;font-size:14px;font-weight:400;overflow:hidden;text-decoration:none;text-overflow:ellipsis;white-space:nowrap}.sidebar ul li a:hover{text-decoration:underline}.sidebar ul li ul{padding:0}.sidebar ul li.active>a{border-right:2px solid;color:var(--theme-color,#42b983);font-weight:600}.app-sub-sidebar li:before{content:"-";padding-right:4px;float:left}.markdown-section h1,.markdown-section h2,.markdown-section h3,.markdown-section h4,.markdown-section strong{color:#2c3e50;font-weight:600}.markdown-section a{color:var(--theme-color,#42b983);font-weight:600}.markdown-section h1{font-size:2rem;margin:0 0 1rem}.markdown-section h2{font-size:1.75rem;margin:45px 0 .8rem}.markdown-section h3{font-size:1.5rem;margin:40px 0 .6rem}.markdown-section h4{font-size:1.25rem}.markdown-section h5{font-size:1rem}.markdown-section h6{color:#777;font-size:1rem}.markdown-section figure,.markdown-section p{margin:1.2em 0}.markdown-section ol,.markdown-section p,.markdown-section ul{line-height:1.6rem;word-spacing:.05rem}.markdown-section ol,.markdown-section ul{padding-left:1.5rem}.markdown-section blockquote{border-left:4px solid var(--theme-color,#42b983);color:#858585;margin:2em 0;padding-left:20px}.markdown-section blockquote p{font-weight:600;margin-left:0}.markdown-section iframe{margin:1em 0}.markdown-section em{color:#7f8c8d}.markdown-section code,.markdown-section output:after,.markdown-section pre{font-family:Roboto Mono,Monaco,courier,monospace}.markdown-section code,.markdown-section pre{background-color:#f8f8f8}.markdown-section output,.markdown-section pre{margin:1.2em 0;position:relative}.markdown-section output,.markdown-section pre>code{border-radius:2px;display:block}.markdown-section output:after,.markdown-section pre>code{-moz-osx-font-smoothing:initial;-webkit-font-smoothing:initial}.markdown-section code{border-radius:2px;color:#e96900;margin:0 2px;padding:3px 5px;white-space:pre-wrap}.markdown-section>:not(h1):not(h2):not(h3):not(h4):not(h5):not(h6) code{font-size:.8rem}.markdown-section pre{padding:0 1.4rem;line-height:1.5rem;overflow:auto;word-wrap:normal}.markdown-section pre>code{color:#525252;font-size:.8rem;padding:2.2em 5px;line-height:inherit;margin:0 2px;max-width:inherit;overflow:inherit;white-space:inherit}.markdown-section output{padding:1.7rem 1.4rem;border:1px dotted #ccc}.markdown-section output>:first-child{margin-top:0}.markdown-section output>:last-child{margin-bottom:0}.markdown-section code:after,.markdown-section code:before,.markdown-section output:after,.markdown-section output:before{letter-spacing:.05rem}.markdown-section output:after,.markdown-section pre:after{color:#ccc;font-size:.6rem;font-weight:600;height:15px;line-height:15px;padding:5px 10px 0;position:absolute;right:0;text-align:right;top:0}.markdown-section output:after,.markdown-section pre:after{content:attr(data-lang)}.token.cdata,.token.comment,.token.doctype,.token.prolog{color:#8e908c}.token.namespace{opacity:.7}.token.boolean,.token.number{color:#c76b29}.token.punctuation{color:#525252}.token.property{color:#c08b30}.token.tag{color:#2973b7}.token.string{color:var(--theme-color,#42b983)}.token.selector{color:#6679cc}.token.attr-name{color:#2973b7}.language-css .token.string,.style .token.string,.token.entity,.token.url{color:#22a2c9}.token.attr-value,.token.control,.token.directive,.token.unit{color:var(--theme-color,#42b983)}.token.function,.token.keyword{color:#e96900}.token.atrule,.token.regex,.token.statement{color:#22a2c9}.token.placeholder,.token.variable{color:#3d8fd1}.token.deleted{text-decoration:line-through}.token.inserted{border-bottom:1px dotted #202746;text-decoration:none}.token.italic{font-style:italic}.token.bold,.token.important{font-weight:700}.token.important{color:#c94922}.token.entity{cursor:help}code .token{-moz-osx-font-smoothing:initial;-webkit-font-smoothing:initial;min-height:1.5rem;position:relative;left:auto} -------------------------------------------------------------------------------- /docs/api/_images/benchmark.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zouloux/reflex-dom/0a0675785267bad989975c945462085d4caf284c/docs/api/_images/benchmark.jpg -------------------------------------------------------------------------------- /docs/api/_images/example.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zouloux/reflex-dom/0a0675785267bad989975c945462085d4caf284c/docs/api/_images/example.gif -------------------------------------------------------------------------------- /docs/api/_sidebar.md: -------------------------------------------------------------------------------- 1 | 2 | - [What is reflex](/) 3 | 4 | - Basics 5 | - [Core Concept](/basics/00.core-concept.md) 6 | - [Simple DOM rendering](/basics/01.simple-dom-rendering.md) 7 | - [Stateless components](/basics/02.stateless-components.md) 8 | - [Stateful components](/basics/03.stateful-components.md) 9 | - [Props](/basics/04.props.md) 10 | 11 | - Factory Extensions 12 | - [State](/factory-extensions/01.state.md) 13 | - [Ref](/factory-extensions/02.ref.md) 14 | - [Refs aka Multi-Refs](/factory-extensions/03.refs.md) 15 | - [Mount and Unmount](/factory-extensions/04.mount-unmount.md) 16 | - [Changed](/factory-extensions/05.changed.md) 17 | - [Custom Extensions](/factory-extensions/06.custom-extensions.md) 18 | 19 | - More about reflex 20 | - [Performances](/reflex/00.performances.md) 21 | - [Features](/reflex/01.features.md) 22 | - [History](/reflex/02.history.md) 23 | --- 24 | 25 | - [Learn Reflex in 5 minutes ↗︎](https://zouloux.github.io/reflex/learn/) 26 | - [Github ↗︎](https://github.com/zouloux/reflex) 27 | 28 | [//]: # (- [Get started with a new project](/get-started/)) 29 | 30 | -------------------------------------------------------------------------------- /docs/api/basics/00.core-concept.md: -------------------------------------------------------------------------------- 1 | 2 | ## API Concept 3 | 4 | ```typescript jsx 5 | return () =>
6 | ``` 7 | 8 | **Stateful components** will return a __render function__ instead of virtual-nodes directly. 9 | Scope is shared between the factory and the render function. 10 | 11 | ```tsx 12 | function StatefulComponent ( props ) { 13 | // Factory extensions and component logic goes here ( factory phase ) 14 | // This part is only executed once, when component is created. 15 | const number = state( 0 ) 16 | // ... 17 | // Render function returning node tree goes there ( in a function ) 18 | return () =>
Current number is { number }
19 | } 20 | ``` 21 | 22 | Reflex allows **functional pattern** for **stateless components**, like React and Preact. 23 | 24 | ```tsx 25 | function StatelessComponent ( props ) { 26 | // No state ? No need for factory function here 27 | return
Hello { props.name }
28 | } 29 | ``` 30 | 31 | #### Improvements 👍 32 | - __Simpler__ : Classic React Hooks like `useCallback`, `useEvent` and `useMemo` becomes __useless__ and does not exist in __Reflex__.
33 | - __Fewer bugs__ : [Stale closure issues](https://dmitripavlutin.com/react-hooks-stale-closures/) vanishes.
34 | - __Cleaner__ : Also, hooks dependencies array to keep state scopes ([#1](https://itnext.io/how-to-work-with-intervals-in-react-hooks-f29892d650f2), [#2](https://overreacted.io/a-complete-guide-to-useeffect/)) are not needed with __[factory extensions](#factory-extensions)__. 35 | - __Back to basics__ : Using `refs` to store stateless values does not exist anymore. In __Reflex__, `ref` is used only to target dom node or components, `let` is used to declare local variables like it would normally do. 36 | 37 | #### Tradeoffs 👎 38 | - __Stateless vs stateful__ : When a component is evolving from stateless to stateful, the `return
...` needs to be refactored to `return () =>
...`. But stateless components **can** be implemented with factory function. 39 | - __Props__ : Props cannot be destructured in the factory phase [because props is mutated](#props) 40 | - Surely more but I got biases :) 41 | -------------------------------------------------------------------------------- /docs/api/basics/01.simple-dom-rendering.md: -------------------------------------------------------------------------------- 1 | 2 | ## Simple DOM rendering 3 | 4 | ```tsx 5 | // Import Reflex like you would import Preact for example. 6 | import { h, render } from "reflex"; 7 | // Optionnaly type props with typescript 8 | interface IAppProps { 9 | greetings:string 10 | } 11 | // App is a stateless function, no need for factory pattern 12 | function App ( props:IAppProps ) { 13 | return
14 |

{ props.greetings }

15 |
16 | } 17 | 18 | render( , document.body ) 19 | // Note : if you call render again, it will update state of previously rendered app 20 | render( , document.body ) 21 | ``` 22 | -------------------------------------------------------------------------------- /docs/api/basics/02.stateless-components.md: -------------------------------------------------------------------------------- 1 | 2 | ## Stateless and pure components 3 | 4 | Stateless, or components without logic can avoid the factory pattern. Simply return the virtual-dom tree derived from props like you would do it in React or Preact. 5 | Stateless functions are [__pure__](https://en.wikipedia.org/wiki/Pure_function), which means it has always the same output (returned elements) for a specific input (props). 6 | ```tsx 7 | function StatelessComponent ( props ) { 8 | return
9 | Hello { props.name } 👋 10 |
11 | } 12 | ``` 13 | 14 | > Because Stateless and Stateful components are written differently, Reflex can optimize render of Stateless components by keeping old virtual-node tree, if props did not change between renders. We have better performances without adding any logic to our app. 15 | 16 | > Also, `shouldUpdate` can be set by component (Stateful or Stateless), if you have specific optimization logic to avoid useless renders. 17 | -------------------------------------------------------------------------------- /docs/api/basics/03.stateful-components.md: -------------------------------------------------------------------------------- 1 | 2 | ## Stateful components with factory pattern 3 | 4 | This is where it changes from React. Stateful components in Reflex follows the __Factory Component Pattern__. __[Factory extensions](#factory-extensions)__ are used __only__ in the "factory phase" of the component. 5 | 6 | ```tsx 7 | function StatefulComponent ( props ) { 8 | // This is the "factory phase" 9 | // This part of the component is executed once, when component is created, and not at each render. 10 | 11 | // Create a state for this component, like in React or Solid 12 | const currentNumber = state( 0 ) 13 | const incrementNumber = () => currentNumber.value ++ 14 | 15 | // The component needs to return a function which will render the component 16 | return () =>
17 | {/* Update state when button is clicked */} 18 | 21 |
22 | } 23 | ``` 24 | 25 | > Stateful components are not __pure__. Output differs even with the same props as input. 26 | -------------------------------------------------------------------------------- /docs/api/basics/04.props.md: -------------------------------------------------------------------------------- 1 | 2 | ## Props 3 | 4 | In Stateful components, `props` is an object which is mutated from props given by parent component. Because factory phase is executed once at component's creation (like in Solid). 5 | 6 | ```tsx 7 | function PropsComponent ( props ) { 8 | function logName () { 9 | // ✅ Will log latest name, even if parent component changed the props 10 | console.log( props.name ) 11 | } 12 | return () =>
13 | 14 |
15 | } 16 | ``` 17 | 18 | > The main tradeoff is that props destructuring is not possible anymore. Or destructed props will be equal to the first props value and will never change. 19 | 20 | ```tsx 21 | function PropsComponent ( props ) { 22 | // 🚫 Here name will never change even if the component is updated by its parent 23 | const { name } = props 24 | function logName () { 25 | console.log( name ) 26 | } 27 | return () =>
28 | } 29 | ``` 30 | 31 | ## Default props 32 | 33 | Default props are configurable in factory and pure functional components the same way, using `defaultProps`. 34 | 35 | ```tsx 36 | 37 | // For stateful components 38 | function FactoryComponent (props, component) { 39 | defaultProps(props, { 40 | title: "Default title" 41 | }) 42 | console.log("Factory", props.title) 43 | return () =>
{ props.title }
44 | } 45 | 46 | // Also available in Stateless components 47 | function PureComponent ( props ) { 48 | defaultProps(props, { 49 | title: "Default title" 50 | }) 51 | console.log("Render", props.title) 52 | return
{ props.title }
53 | } 54 | ``` 55 | 56 | ## CSS classes as array 57 | 58 | CSS classes can be set as an array. Falsy values will be automatically filtered out. 59 | 60 | ```tsx 61 | function PureComponent ( props ) { 62 | const classes = [ 63 | "PureComponent", 64 | props.modifier ? `PureComponent-${props.modifier}` : null, 65 | props.disabled && "disabled", 66 | ...props.classes 67 | ] 68 | return
69 | } 70 | // Will have class="PureComponent PureComponent-big ParentComponent_pureComponent" 71 | // Disabled is filtered out because props.disabled is not defined 72 | const component = 76 | ``` 77 | -------------------------------------------------------------------------------- /docs/api/basics/05.forward-ref.md: -------------------------------------------------------------------------------- 1 | 2 | ### Automatic forwardRef 3 | 4 | When attaching a ref from inside the component, and from the parent, it will just work as expected. 5 | 6 | > No need for forward ref 7 | 8 | ```tsx 9 | function Child () { 10 | // Works, will have component instance and div element 11 | const root = ref() 12 | return () =>
13 | } 14 | function Parent () { 15 | // Also works without forwardRef 16 | // will have component instance and div element 17 | const child = ref() 18 | return () =>
19 | 20 |
21 | } 22 | ``` 23 | > /!\ This feature is WIP and will certainly change in RC 24 | -------------------------------------------------------------------------------- /docs/api/basics/README.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zouloux/reflex-dom/0a0675785267bad989975c945462085d4caf284c/docs/api/basics/README.md -------------------------------------------------------------------------------- /docs/api/factory-extensions/01.state.md: -------------------------------------------------------------------------------- 1 | 2 | ## State 3 | 4 | ```tsx 5 | // Create a new state 6 | const myState = state( initialState ) 7 | 8 | // Get current state value 9 | console.log( myState.value ) 10 | 11 | // Set new value (will trigger a component update) 12 | myState.value = newValue 13 | // -> Dom not updated yet 14 | 15 | // After setting using .value, component is not refreshed instantanously 16 | // Use .set to wait for component invalidation 17 | await myState.set( newValue ) 18 | // -> Now the dom is updated 19 | ``` 20 | 21 | > __Note__ : setting a new state is asynchronous because all state changes of a component are stacked and component renders only once for better performances. 22 | > After the component is refreshed, the `await state.set( value )` promise will be resolved. 23 | 24 | Additional options for state are 25 | - `filter` to change value when set, useful to avoid invalid values 26 | - `directInvalidation` is called after the associated component is rendered 27 | 28 | ```typescript 29 | const myState = state( 0, { 30 | // Value is filtered when "myState.value = something" or "myState.set( something )" is used. 31 | // Here value cannot be set bellow 0 32 | filter: (newValue, oldValue) => Math.max( newValue, 0 ), 33 | // Will force component to be rendered after each set 34 | // It can be useful if you need sync data rendering 35 | // But be careful about data flow, it can destroy performances in loops ! 36 | directInvalidation: true 37 | }) 38 | 39 | // Value is 0 and DOM is directly updated 40 | myState.value = -2 41 | 42 | // Because of directInvalidation, it will render component at each loop and your app will certainly crash 43 | for ( let i = 0; i < 10000; ++i) 44 | myState.value ++ 45 | ``` 46 | -------------------------------------------------------------------------------- /docs/api/factory-extensions/02.ref.md: -------------------------------------------------------------------------------- 1 | 2 | ## Ref 3 | 4 | Like in React, we can use ref to target rendered components. 5 | 6 | ```tsx 7 | function MyComponent () { 8 | // Create a new ref 9 | const otherComponentRef = ref() 10 | 11 | function showref () { 12 | // Log component dom element 13 | console.log('DOM', otherComponentRef.dom ) 14 | // Log component instance 15 | console.log('Component', otherComponentRef.component ) 16 | } 17 | 18 | return () =>
19 | 20 | 21 |
22 | } 23 | ``` 24 | 25 | > The main difference with React is that ref are useless to create locally scoped component variables. 26 | 27 | To create a locally scoped prop that will not trigger rendering, just use `let` 28 | 29 | ```tsx 30 | function MyComponent () { 31 | // Create a local variable (no need for ref here) 32 | let localVariable = 0 33 | 34 | function updateLocalVariable () { 35 | // Update this variable will not trigger a render 36 | localVariable ++ 37 | console.log( localVariable ); 38 | } 39 | 40 | return () =>
41 | 42 |
43 | } 44 | ``` 45 | -------------------------------------------------------------------------------- /docs/api/factory-extensions/03.refs.md: -------------------------------------------------------------------------------- 1 | 2 | ## Refs aka Multi-Ref 3 | 4 | Multi ref in Reflex is `ref` as an array of components. Very handy when dealing with lists! 5 | 6 | ```tsx 7 | function List ( props ) { 8 | // Create ref for list 9 | const itemRefs = refs() 10 | 11 | function showListItemElements () { 12 | // Will be an array of all refs 13 | console.log( itemsRefs.list ); 14 | } 15 | 16 | return () =>
    17 | {props.items.map( item => 18 |
  • {item.name}
  • 19 | )} 20 |
21 | } 22 | ``` 23 | 24 | > Refs are populated in order of rendering. So if you are using a list which can render in another order than from 0 to length, you can specify the index ( [see example](./demos/common/CodeViewer/CodeViewer.tsx) ) 25 | 26 | ```tsx 27 | function List ( props ) { 28 | const itemRefs = refs() 29 | return () =>
    30 | {props.items.map( (item, i) => 31 | // Here item.ready can render elements in the wrong order 32 | // refs.atIndex( index ) will force index and patch this issue 33 | item.ready &&
  • {item.name}
  • 34 | )} 35 |
36 | } 37 | ``` 38 | 39 | -------------------------------------------------------------------------------- /docs/api/factory-extensions/04.mount-unmount.md: -------------------------------------------------------------------------------- 1 | 2 | ## Mounted and Unmounted 3 | 4 | Pretty self-explanatory, will be called when mounting or unmounting the component. 5 | 6 | ```tsx 7 | function MountUnmount ( props ) { 8 | const root = ref() 9 | 10 | mounted(() => { 11 | console.log("Component just mounted, refs are ready.", root.dom) 12 | // Can return an unmount function 13 | return () => { 14 | console.log("Will be called just before component unmount.", root.dom) 15 | } 16 | }) 17 | 18 | unmounted( () => { 19 | console.log("Will be called just before component unmount.", root.dom) 20 | }) 21 | 22 | return () =>
...
23 | } 24 | ``` 25 | 26 | > mounted can also return an array of handlers. 27 | 28 | 29 | ```tsx 30 | 31 | // Note, here on() returns the remove handler 32 | // @see https://github.com/zouloux/yadl 33 | 34 | mounted(() => { 35 | return [ 36 | // Listen clicks and remove listen on unmount 37 | on(document, 'click', clickedHandler), 38 | // will be filtered out if false 39 | doIt && on(window, 'resize', resizeHandler), 40 | // called on unmount 41 | function () { 42 | } 43 | ] 44 | }) 45 | 46 | ``` -------------------------------------------------------------------------------- /docs/api/factory-extensions/05.changed.md: -------------------------------------------------------------------------------- 1 | 2 | ## Changed 3 | 4 | __Changed__ factory hook is useful to detect changes into a component. With only one handler as argument, it will be called after each component render. 5 | 6 | ```tsx 7 | function ChangedComponent ( props ) { 8 | const root = ref() 9 | const number = state(0) 10 | changed(() => { 11 | // Called after each render 12 | // Ref and state are available 13 | console.log("Component updated", root.dom, number.value) 14 | }) 15 | return () =>
16 | 19 |
20 | } 21 | ``` 22 | 23 | __Changed__ can have a first argument to detect changes state changes. 24 | 25 | ```tsx 26 | function ChangedComponent ( props ) { 27 | const stateA = state() 28 | const stateB = state() 29 | // This function detect changes only when stateA changes. stateB is ignored. 30 | changed( [stateA], (aValue) => { 31 | // StateA is updated 32 | console.log( aValue ) 33 | }) 34 | return () =>
...
35 | } 36 | ``` 37 | > Because we are targeting the state here, we do not need to specify `stateA.value` 38 | 39 | Changed can also detect changes on arbitrary values or props. 40 | The detect function returns an array of dependencies to check. 41 | 42 | ```tsx 43 | function ChangedComponent ( props ) { 44 | const stateA = state() 45 | const stateB = state() 46 | changed( 47 | // The function detect changes in stateA and props.name, stateB is ignored 48 | () => [stateA.value, props.name], 49 | // Called when change is detected in stateA OR props.name 50 | // Both new state and old state values are concatenated into arguments 51 | // new array | old array // 52 | (newStateA, newPropsName, oldStateA, oldPropsName) => { 53 | // Values array here are the last and previous returned array 54 | // Useful to detect changes, or pub-sub any other component or model 55 | console.log( newStateA, newPropsName, oldStateA, oldPropsName ) 56 | } 57 | ) 58 | return () =>
...
59 | } 60 | ``` 61 | 62 | > Because we are in __Factory phase__, raw props or values can't be used directly. __Note__ that the check function __always returns an array__. 63 | 64 | 65 | __Changed__ handler has the same return behavior than `mount` and `unmount`. 66 | 67 | ```tsx 68 | function ChangedComponent ( props ) { 69 | const state = state() 70 | changed( () => [state.value], newValue => { 71 | // After change and at first render 72 | console.log("New value", newValue) 73 | return oldValue => { 74 | // Before change and before unmount 75 | console.log("Old value", oldValue) 76 | } 77 | }) 78 | return () =>
...
79 | } 80 | ``` 81 | -------------------------------------------------------------------------------- /docs/api/factory-extensions/06.custom-extensions.md: -------------------------------------------------------------------------------- 1 | ### Custom extensions 2 | 3 | Like React Hooks, extensions can be used externally and can compose other extensions. 4 | 5 | ?> Here is an example for a frameEntered hook 6 | 7 | ```tsx 8 | export function frameEntered ( handler:() => void ) { 9 | let isMounted = true 10 | let frameHandler; 11 | function frame () { 12 | if ( !isMounted ) return; 13 | handler() 14 | frameHandler = window.requestAnimationFrame( frame ) 15 | } 16 | mounted(() => { 17 | frame(); 18 | // Unmount 19 | return () => { 20 | cancelAnimationFrame( frameHandler ) 21 | isMounted = false 22 | } 23 | }) 24 | } 25 | 26 | ``` 27 | 28 | > And its usage 29 | 30 | ```tsx 31 | export function AnimatedComponent () { 32 | const $base = ref() 33 | let frameCounted = 0 34 | frameEntered(() => { 35 | // This handler is called at each frame while mounted 36 | frameCounted ++ 37 | // Shake this component on the X axis 38 | const position = Math.sin( frameCounted / 20 ) * 2 39 | $base.style.transform = `translateX(${position}px)` 40 | }) 41 | return () =>
42 | (((( Shaking )))) 43 |
44 | } 45 | ``` -------------------------------------------------------------------------------- /docs/api/factory-extensions/README.md: -------------------------------------------------------------------------------- 1 | 2 | # Factory extensions 3 | 4 | Here is a list of all base __factory extensions__ available. 5 | Remember, __factory extensions__ are only usable in __factory phase__ of components and not available in Stateless components. 6 | Like in React, __factory extensions__ are composable into other functions easily. 7 | 8 | 9 | ```tsx 10 | function StatefulComponent () { 11 | // ✅ Can use extensions here 12 | state(...) 13 | changed(...) 14 | mounted(...) 15 | unmounted(...) 16 | 17 | return () => { 18 | // ⛔️ Cannot use extensions here 19 | } 20 | } 21 | 22 | function StatelessComponent () { 23 | // ⛔️ Cannot use extensions here 24 | return
25 | {/* ⛔️ Cannot use extensions here */} 26 |
27 | } 28 | ``` 29 | -------------------------------------------------------------------------------- /docs/api/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Reflex API Documentation 5 | 6 | 7 | 8 | 9 | 10 | 11 |
Loading
12 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /docs/api/reflex/00.performances.md: -------------------------------------------------------------------------------- 1 | # Reflex performances -------------------------------------------------------------------------------- /docs/api/reflex/01.features.md: -------------------------------------------------------------------------------- 1 | # Reflex features -------------------------------------------------------------------------------- /docs/api/reflex/02.history.md: -------------------------------------------------------------------------------- 1 | # Reflex history -------------------------------------------------------------------------------- /docs/learn/index-4e761b3d.css: -------------------------------------------------------------------------------- 1 | ._App_ka7wa_1{width:100vw;height:100vh}.__iframe_ka7wa_5{border:none;position:absolute;top:0;left:33vw;right:0;width:67vw!important;height:100vh}*{margin:0;padding:0}html{overflow:hidden;font-family:Arial,sans-serif} 2 | -------------------------------------------------------------------------------- /docs/learn/index-6dd90969.js: -------------------------------------------------------------------------------- 1 | (function(){const t=document.createElement("link").relList;if(t&&t.supports&&t.supports("modulepreload"))return;for(const s of document.querySelectorAll('link[rel="modulepreload"]'))r(s);new MutationObserver(s=>{for(const c of s)if(c.type==="childList")for(const o of c.addedNodes)o.tagName==="LINK"&&o.rel==="modulepreload"&&r(o)}).observe(document,{childList:!0,subtree:!0});function n(s){const c={};return s.integrity&&(c.integrity=s.integrity),s.referrerPolicy&&(c.referrerPolicy=s.referrerPolicy),s.crossOrigin==="use-credentials"?c.credentials="include":s.crossOrigin==="anonymous"?c.credentials="omit":c.credentials="same-origin",c}function r(s){if(s.ep)return;s.ep=!0;const c=n(s);fetch(s.href,c)}})();function se(e,t){let n=e;return t==null&&(t=e,n=document.documentElement),[n,t]}function me(e,t){const[n,r]=se(e,t);return n.querySelector(r)}function _e(e,t){const[n,r]=se(e,t);return Array.from(n.querySelectorAll(r))}function q(e,t,n,r,s={}){const{dispatchAtInit:c}=s;delete s.dispatchAtInit;function o(i,u){e?u.addEventListener(i,r,s):u.removeEventListener(i,r,s)}t.map(i=>Array.isArray(n)?n.map(u=>o(u,i)):o(n,i)),c&&r(null)}function he(e){return typeof e=="string"?e=_e(e):Array.isArray(e)||(e=[e]),e}function ye(e,t,n,r){const s=he(e),c=()=>q(!1,s,t,o,r);function o(...i){c(),n.apply(null,i)}return q(!0,s,t,o,r),c}async function ge(){return new Promise(e=>{document.readyState=="loading"?ye(document,"DOMContentLoaded",e):e()})}const h=(e,...t)=>e.map(n=>n==null?void 0:n(...t)),ve=queueMicrotask??(e=>setTimeout(e,0));function ie(e){const t=new Set;let n=[];function r(){for(const s of t)e(s);h(n),t.clear(),n=[]}return(s,c)=>{t.size||ve(r),t.add(s),n.push(c)}}let T=[];function B(e){var t;(t=D())==null||t._mountHandlers.push(e)}function oe(e){var t;return(t=D())==null||t._unmountHandlers.push(e),e}const be=(e,t)=>Object.keys(e).length===Object.keys(t).length&&Object.keys(e).every(n=>t.hasOwnProperty(n)&&e[n]===t[n]),I="_l",Ee=/acit|ex(?:s|g|n|p|$)|rph|grid|ows|mnc|ntw|ine[ch]|zoo|^ord|itera/i,Se=/Capture$/,xe="http://www.w3.org/2000/svg";let _=null;function D(){return _}function W(e,t){const n=e!==(e=e.replace(Se,"")),r=e.toLowerCase(),s=(r in t?r:e).slice(2),c=s+(n?"C":"");return{eventName:s,eventKey:c,useCapture:n}}function Ae(e,t,n){t[0]==="-"?e.setProperty(t,n):n==null?e[t]="":typeof n!="number"||Ee.test(t)?e[t]=n:e[t]=n+"px"}function ce(e){var t,n;(n=(t=e==null?void 0:e.props)==null?void 0:t.ref)==null||n._setFromVNode(e)}let w;const Te=()=>w;function P(e,t,n){t=="className"&&(t="class"),n===!1||n===null?e.removeAttribute(t):n===!0?e.setAttribute(t,""):t=="style"&&typeof n=="object"?Object.keys(n).forEach(r=>Ae(e.style,r,n[r])):(t=="class"&&Array.isArray(n)&&(n=n.flat(1).filter(r=>r!==!0&&!!r).join(" ").trim()),e.setAttribute(t,n))}function we(e,t,n){const{type:r,value:s}=e,c=r===1||r===3;let o;if(n)o=n,c&&(w=e,n.nodeValue!=s&&(n.nodeValue=s));else if(t)o=t.dom,c&&t.value!==s&&(o.nodeValue=s);else{s==="svg"&&(e._isSVG=!0);const i=e._document;r===0?o=i.createComment(""):c?(w=e,o=i.createTextNode(s)):r===6&&(o=e._isSVG?i.createElementNS(xe,s):i.createElement(s))}if(r===0||r===1||r===3)return o;if(r===8)return ue(e,t,o),o;t&&Object.keys(t.props).filter(i=>i!=="children"&&i!=="key"&&i!=="ref"&&!(i in e.props&&e.props[i]===t.props[i])).forEach(i=>{if(i=="innerHTML")o.innerHTML="";else if(i.startsWith("on")){const{eventName:u,eventKey:l,useCapture:p}=W(i,o);o.removeEventListener(u,o[I].get(l),p)}else o.removeAttribute(i)});for(let i in e.props){let u=e.props[i];if(!(u==null||i==="children"||i==="key"||i==="ref"||t&&i in t.props&&t.props[i]===u))if(i==="innerHTML")o.innerHTML=u;else if(i.startsWith("on")){const{eventName:l,eventKey:p,useCapture:a}=W(i,o);o[I]??(o[I]=new Map),o[I].set(p,u),o.addEventListener(l,u,a)}else u!==null&&typeof u=="object"&&u.type===3&&(w={type:2,value:u,_propertyName:i,dom:o},u=u.value),n||P(o,i,u)}return o}function le(e,t,n,r=!1){const s=h(T,2,e,t,n,r);e&&(b(e,t,n,r),j(e,!0),h(s))}function j(e,t){if(e.type===7){const{component:n}=e;if(n)if(j(n.children,t),t&&!n.isMounted){if(n.isMounted=!0,e.value.isFactory!==!1){const r=n._mountHandlers.length;for(let s=0;so).map(o=>n._unmountHandlers.push(o))}n._mountHandlers=[],h(n._renderHandlers),h(n._nextRenderHandlers),n._nextRenderHandlers=[]}h(T,1,n,!0)}else!t&&n.isMounted&&(h(T,1,!1),h(n._unmountHandlers),n.isMounted=!1,delete e.component,delete n.vnode)}else if(e.type>4){const n=e.props.children.length;for(let r=0;r0){j(t,!1),s.innerHTML="";return}if(o===0)return;let l;if(!t||u===0){let f=!1;for(l=0;lReflect.get(t.value,s)});_._proxy=n.proxy,_._unmountHandlers.push(n.revoke)}return _._render.apply(_,[_._proxy??e.props,_])}function b(e,t,n,r=!1){t&&t===e&&(e={...t},e.props={...t.props}),t&&(e._isSVG=t._isSVG,e._document=t._document,e._id=t._id);const s=e.type;if(s>=0&&s<=3||s===6)e.dom=we(e,t,n),w=null;else if(s===7){const c=e.value;let o=t==null?void 0:t.component,i;if(o)e.component=o,o.vnode=e;else{o={vnode:e,isMounted:!1,_render:c,_mountHandlers:[],_renderHandlers:[],_nextRenderHandlers:[],_unmountHandlers:[],_shouldUpdate:c.shouldUpdate},e.component=o,o._render=c;const l=X(e),p=typeof l=="function";c.isFactory=p,o._render=p?l:c,p||(i=l)}let u=!0;!r&&!i&&t&&(u=o._shouldUpdate?o._shouldUpdate(e.props,t.props):!be(e.props,t.props),u&&c.isFactory&&(o._propState.set({...o._defaultProps,...e.props}),u=!1),u||(e.dom=t.dom)),!i&&u&&(i=X(e)),i&&(i._isSVG=e._isSVG,i._document=e._document,b(i,o.children,n),o.children=i,e.dom=i.dom),_=null}ce(e),e.type>=5&&ue(e,t,n)}const Y=(e,t)=>typeof e=="function"?e(t):e,je=ie(H),Ie=ie(e=>le(e.vnode,e.vnode,null,!0));function Ce(e,t){let n;e.type===3?e.dom.nodeValue=t:(n=e._propertyName,e.dom instanceof HTMLImageElement&&n==="src"&&P(e.dom,n,""),P(e.dom,n,t)),h(T,3,e,n)}let E,R=new Set;function k(e){e=Y(e);let t=new Set,n=new Set,r=new Set;E&&t.add(E);async function s(i,u=!1){var p;if(i===e&&!u)return;for(const a of t)(p=a._dispose)==null||p.call(a);e=i;for(const a of t)!a._dom&&H(a);for(const a of n)r.has(a.component)||Ce(a,e);const l=[];for(const a of r)l.push(new Promise(m=>Ie(a,m)));r.clear(),await Promise.all(l);for(const a of t)a._dom&&je(a)}const c=oe(()=>{t.clear(),n.clear(),r.clear(),t=null,n=null,r=null}),o={get value(){const i=Te(),u=D();return E?(t.add(E),R.add(this)):i&&(i.type===3||i.type===2)&&i.value===this?(i.component=u,n.add(i)):u&&r.add(u),e},set value(i){s(i)},set:(i,u=!1)=>s(Y(i,e),u),peek(){return e},sneak(i){e=i},get type(){return 3},toString(){return this.value+""},valueOf(){return this.value},dispose:c,_removeEffect(i){t==null||t.delete(i)}};return h(T,4,o),o}function De(e,t){for(const n of e)n._removeEffect(t)}function Fe(){const e=Array.from(R);return R.clear(),e}function Q(e,t){const n=e._handler(...e._values);e._values=t,e._dispose=typeof n=="function"?n:null}function H(e,t=!1){if(t&&(E=e),e._check){const n=e._check().map(r=>r&&r.type===3?r.value:r);t&&(E=null),(!e._values.length||n.find((r,s)=>e._values[s]!==r)!=null)&&Q(e,n)}else Q(e,[!1]),t&&(E=null)}function Le(e,t,n){const r={_handler:e,_check:n,_dom:t,_values:n?[]:[!0],_dispose:null};let s;function c(){H(r,!0),s=Fe()}return t?D()._nextRenderHandlers.push(c):c(),oe(()=>De(s,r))}function Pe(e){return Le(e,!0)}const Z="__v";function Re(e,t,n=document,r=null){const s={type:5,value:null,props:{children:[e]},_isSVG:!1,_document:n};s.dom=t;const c=t[Z];le(s,c,r),h(T,0,c,s),t[Z]=s}function ke(e,t,n=document){Re(e,t,n)}function g(e,t,...n){t==null&&(t={}),t.children??(t.children=n);const r=t.children,s=r.length;for(let o=0;o 2 | 3 | Learn Reflex 4 | 6 | 7 | 8 | 9 |
10 | 11 | -------------------------------------------------------------------------------- /docs/learn/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "index.css": { 3 | "file": "index-4e761b3d.css", 4 | "src": "index.css" 5 | }, 6 | "index.html": { 7 | "css": [ 8 | "index-4e761b3d.css" 9 | ], 10 | "file": "index-6dd90969.js", 11 | "isEntry": true, 12 | "src": "index.html" 13 | } 14 | } -------------------------------------------------------------------------------- /docs/perfs/Screenshot 2024-04-01 at 17.15.51.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zouloux/reflex-dom/0a0675785267bad989975c945462085d4caf284c/docs/perfs/Screenshot 2024-04-01 at 17.15.51.png -------------------------------------------------------------------------------- /docs/perfs/Screenshot 2024-04-01 at 17.16.02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zouloux/reflex-dom/0a0675785267bad989975c945462085d4caf284c/docs/perfs/Screenshot 2024-04-01 at 17.16.02.png -------------------------------------------------------------------------------- /docs/perfs/Screenshot 2024-04-01 at 17.16.08.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zouloux/reflex-dom/0a0675785267bad989975c945462085d4caf284c/docs/perfs/Screenshot 2024-04-01 at 17.16.08.png -------------------------------------------------------------------------------- /docs/perfs/krausest.github.io_js-framework-benchmark_current.html.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zouloux/reflex-dom/0a0675785267bad989975c945462085d4caf284c/docs/perfs/krausest.github.io_js-framework-benchmark_current.html.png -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "reflex-dom", 3 | "type": "module", 4 | "version": "0.25.3", 5 | "description": "Reflex is a tiny and fast reactive UI library.", 6 | "author": "Alexis Bouhet (ZoulouX)", 7 | "license": "MIT", 8 | "main": "./dist/index.mjs", 9 | "module": "./dist/index.mjs", 10 | "unpkg": "./dist/reflex.es2017.min.js", 11 | "types": "./dist/index.d.ts", 12 | "exports": { 13 | ".": "./dist/index.mjs", 14 | "./hmr-plugin": "./dist/hmr-plugin.mjs", 15 | "./hmr-runtime": "./dist/hmr-runtime.mjs", 16 | "./debug": "./dist/debug.mjs", 17 | "./render-to-string": "./dist/render-to-string.mjs", 18 | "./performance-helpers": "./dist/performance-helpers.mjs" 19 | }, 20 | "typesVersions": { 21 | "*": { 22 | "hmr-plugin": [ 23 | "dist/hmr-plugin.d.ts" 24 | ], 25 | "hmr-runtime": [ 26 | "dist/hmr-runtime.d.ts" 27 | ], 28 | "debug": [ 29 | "dist/debug.d.ts" 30 | ], 31 | "render-to-string": [ 32 | "dist/render-to-string.d.ts" 33 | ], 34 | "performance-helpers": [ 35 | "dist/performance-helpers.d.ts" 36 | ] 37 | } 38 | }, 39 | "scripts": { 40 | "reset": "rm -rf node_modules package-lock.json && npm i", 41 | "clean": "rm -rf ./dist/*", 42 | "build": "npm run clean --silent && tsbundle build", 43 | "docs": "static-http docs/api/", 44 | "dev": "cd reflex-dev && npm run dev --silent", 45 | "dev-build": "cd reflex-dev && npm run build", 46 | "learn": "cd reflex-learn && npm run build --silent", 47 | "learn-dev": "cd reflex-learn && npm run dev --silent", 48 | "__publish": "npm run clean --silent && npm run learn --silent && tsbundle publish", 49 | "publish": "npm run clean --silent && tsbundle publish", 50 | "deopt-build": "rm -rf ./deopt/dist ./v8-deopt-viewer && esbuild deopt/src/index.tsx --bundle --outfile=deopt/dist/index.js", 51 | "deopt-run": "v8-deopt-viewer deopt/dist/index.js -o ./deopt/v8-deopt-viewer && npx @zouloux/static-http ./deopt/v8-deopt-viewer", 52 | "deopt": "npm run deopt-build && npm run deopt-run" 53 | }, 54 | "devDependencies": { 55 | "@zouloux/static-http": "^1.0.1", 56 | "@zouloux/store": "^0.1.2", 57 | "@zouloux/tsbundle": "^1.5.1", 58 | "esbuild": "^0.19.5", 59 | "typescript": "^4.9.5", 60 | "v8-deopt-viewer": "^0.3.0", 61 | "vite": "^5.1.5" 62 | }, 63 | "tsbundle": { 64 | "exportMap": { 65 | "Reflex": "./index" 66 | }, 67 | "output": "./dist", 68 | "formats": [ 69 | "es2020.mjs" 70 | ], 71 | "files": [ 72 | { 73 | "input": "./src/index.ts", 74 | "formats": [ 75 | "es2017.min.js", 76 | "es2020.min.js", 77 | "es2022.min.js" 78 | ], 79 | "outName": "reflex", 80 | "exportBits": true 81 | }, 82 | { 83 | "input": "./src/index.ts" 84 | }, 85 | { 86 | "input": "./src/debug.ts" 87 | }, 88 | { 89 | "input": "./src/render-to-string.ts", 90 | "filterGlob": "render-to-string.*", 91 | "exportBits": true 92 | }, 93 | { 94 | "input": "./src/hmr-plugin.ts", 95 | "outName": "hmr-plugin", 96 | "filterGlob": "hmr-plugin.*" 97 | }, 98 | { 99 | "input": "./src/hmr-runtime.ts", 100 | "outName": "hmr-runtime", 101 | "filterGlob": "hmr-runtime.*" 102 | }, 103 | { 104 | "input": "./src/performance-helpers.ts", 105 | "outName": "performance-helpers", 106 | "filterGlob": "performance-helpers.*", 107 | "exportBits": true 108 | } 109 | ] 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /reflex-dev/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "reflex-dev", 3 | "type": "module", 4 | "scripts": { 5 | "dev": "vite --force", 6 | "build": "vite build --emptyOutDir" 7 | }, 8 | "devDependencies": { 9 | "@types/node": "^18.11.19", 10 | "less": "^4.1.3", 11 | "typescript": "^4.9.5", 12 | "vite": "^4.1.1" 13 | }, 14 | "dependencies": { 15 | "@stackblitz/sdk": "^1.8.2", 16 | "@zouloux/ecma-core": "^0.2.3", 17 | "yadl": "^0.3.0" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /reflex-dev/src/00.v20/main.tsx: -------------------------------------------------------------------------------- 1 | // import { h, render, state, DefaultReflexBaseProps, shouldUpdate } from "reflex-dom" 2 | import { h, state } from "../../../src" 3 | import { For, atom, particle } from "../../../src/performance-helpers" 4 | import { IAtom } from "../../../src/states"; 5 | 6 | // ----------------------------------------------------------------------------- DEBUG 7 | 8 | // @ts-ignore 9 | // let memoryDebugger 10 | // import { drawReflexDebug, MemoryUsage } from "../../../src/debug"; 11 | // if ( process.env.NODE_ENV !== 'production' ) { 12 | // drawReflexDebug(); 13 | // memoryDebugger = 14 | // } 15 | 16 | // ----------------------------------------------------------------------------- DATA HELPERS 17 | 18 | const A = [ 19 | "pretty", "large", "big", "small", "tall", "short", "long", "handsome", 20 | "plain", "quaint", "clean", "elegant", "easy", "angry", "crazy", "helpful", 21 | "mushy", "odd", "unsightly", "adorable", "important", "inexpensive", 22 | "cheap", "expensive", "fancy" 23 | ]; 24 | const C = [ 25 | "red", "yellow", "blue", "green", "pink", "brown", "purple", "brown", 26 | "white", "black", "orange" 27 | ]; 28 | const N = [ 29 | "table", "chair", "house", "bbq", "desk", "car", "pony", "cookie", 30 | "sandwich", "burger", "pizza", "mouse", "keyboard" 31 | ]; 32 | 33 | const _pick = array => array[Math.floor(Math.random() * array.length)] 34 | 35 | // ----------------------------------------------------------------------------- STRUCT & STATES 36 | 37 | interface IDataItem 38 | { 39 | id :number 40 | label :IAtom 41 | } 42 | 43 | const $data = state([]) 44 | const $selected = state( null ) 45 | 46 | // ----------------------------------------------------------------------------- DATA ACTIONS 47 | 48 | const run = () => $data.set( buildData(1000) ) 49 | const runLots = () => $data.set( buildData(10000) ) 50 | const add1 = () => $data.set( d => [...buildData(100), ...d] ) 51 | const add2 = () => $data.set( d => [...d, ...buildData(100)] ) 52 | const update = () => { 53 | const list = $data.peek() 54 | for ( let i = 0; i < list.length; i += 10 ) 55 | list[i].label.value += ' !!!'; 56 | } 57 | const clear = () => $data.set([]) 58 | const swapRows = () => $data.set( d => { 59 | if ( d.length > 998 ) { 60 | let tmp = d[1]; 61 | d[1] = d[998]; 62 | d[998] = tmp; 63 | return [...d]; 64 | } 65 | return d 66 | }) 67 | const remove = id => $data.set(d => { 68 | const idx = d.findIndex( d => d.id === id ); 69 | return [ ...d.slice(0, idx), ...d.slice(idx + 1) ]; 70 | }) 71 | const toggleSelection = ( id:number ) => { 72 | $selected.set( $selected.value === id ? null : id ) 73 | } 74 | 75 | // ----------------------------------------------------------------------------- BUILD DATA 76 | 77 | let _counter = 1; 78 | const buildData = (count:number) => { 79 | // eslint-disable-next-line unicorn/no-new-array 80 | const data = new Array(count); 81 | for ( let i = 0; i < count; ++i ) { 82 | data[i] = { 83 | id: _counter++, 84 | label: atom( `${_pick(A)} ${_pick(C)} ${_pick(N)}` ), 85 | }; 86 | } 87 | return data; 88 | }; 89 | 90 | // ----------------------------------------------------------------------------- BUTTON 91 | 92 | const Button = ({ id, onClick, title }) => 93 |
94 |
101 | 102 | // ----------------------------------------------------------------------------- ROW 103 | 104 | const Row = ( props ) => { 105 | // const classes = atomic( () => $selected.value === props.id ? "danger" : "" ) 106 | // return 107 | const classes = particle( () => $selected.value === props.id ? "danger" : "" ) 108 | // return $selected.value === props.id ? "danger" : "" ) }> 109 | // return 110 | return 111 | {Math.random()} 112 | { props.id } 113 | 114 | toggleSelection( props.id ) }> 115 | { props.label } 116 | 117 | 118 | 119 | remove( props.id ) }> 120 | 122 | 123 | 124 | 125 | } 126 | 127 | // Row.shouldUpdate = (newProps, oldProps) => oldProps.label !== newProps.label 128 | Row.shouldUpdate = (newProps, oldProps) => false 129 | 130 | // ----------------------------------------------------------------------------- JUMBOTRON 131 | 132 | const Jumbotron = () => 133 |
134 |
135 |
136 |

Reflex-DOM keyed

137 |
138 |
139 |
140 |
148 |
149 |
150 |
151 | 152 | // ----------------------------------------------------------------------------- APP 153 | 154 | export const App = () => 155 |
156 | 12 | 13 | 14 |
15 | 16 | 17 |

Title

Parent 1
Children
Parent 2

Parent 1
Children
Parent 2
abc

OK

18 | 19 | -------------------------------------------------------------------------------- /reflex-dev/src/index.tsx: -------------------------------------------------------------------------------- 1 | import { startProps } from "./01.props"; 2 | import { onReady } from "yadl"; 3 | import { startStateHMR } from "./02.state-hmr"; 4 | import { startEffects } from "./03.effects"; 5 | import { startV20 } from "./00.v20/v20"; 6 | 7 | onReady().then( () => { 8 | if ( window['__alreadyStarted'] ) 9 | return 10 | window['__alreadyStarted'] = true 11 | // startProps() 12 | // startStateHMR() 13 | // startEffects(); 14 | startV20(); 15 | }) -------------------------------------------------------------------------------- /reflex-dev/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target" : "esnext", 4 | "module": "esnext", 5 | "isolatedModules": false, 6 | "useDefineForClassFields" : true, 7 | "types": ["vite/client", "node"], 8 | "jsxFactory": "h", 9 | "jsx": "react", 10 | "moduleResolution": "node", 11 | "lib": ["DOM", "ESNext"], 12 | "allowJs": true, 13 | "allowSyntheticDefaultImports": true 14 | }, 15 | "include" : [ 16 | "src/", 17 | ], 18 | "exclude": [ 19 | "dist", 20 | "node_modules", 21 | ] 22 | } 23 | -------------------------------------------------------------------------------- /reflex-dev/vite.config.js: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite' 2 | import { reflexRefresh } from "../src/hmr-plugin"; 3 | import { resolve } from "path"; 4 | 5 | export default defineConfig( () => { 6 | return { 7 | root: resolve("./src"), 8 | build: { 9 | outDir: resolve("./dist"), 10 | manifest: true, 11 | // assetsDir: "./", 12 | rollupOptions: { 13 | input: [ 14 | resolve("./src/index.html") 15 | ], 16 | }, 17 | }, 18 | plugins: [ 19 | reflexRefresh({ 20 | enabledHMRDevMode: true 21 | }) 22 | ] 23 | } 24 | }) 25 | -------------------------------------------------------------------------------- /reflex-learn/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "reflex-learn", 3 | "type": "module", 4 | "scripts": { 5 | "dev": "vite", 6 | "build": "vite build --emptyOutDir" 7 | }, 8 | "devDependencies": { 9 | "@types/node": "^20.8.9", 10 | "less": "^4.2.0", 11 | "typescript": "^5.2.2", 12 | "vite": "^4.5.0" 13 | }, 14 | "dependencies": { 15 | "@stackblitz/sdk": "^1.9.0", 16 | "@zouloux/ecma-core": "^0.2.3", 17 | "marked": "^12.0.1", 18 | "reflex-dom": "^0.21.0", 19 | "yadl": "^0.3.4" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /reflex-learn/src/components/App.module.less: -------------------------------------------------------------------------------- 1 | 2 | @_navigationSize: 33vw; 3 | 4 | .App { 5 | width: 100vw; 6 | height: 100vh; 7 | } 8 | 9 | ._iframe { 10 | border: none; 11 | position: absolute; 12 | top: 0; 13 | left: @_navigationSize; 14 | right: 0; 15 | width: calc( 100vw - @_navigationSize ) !important; 16 | height: 100vh; 17 | } -------------------------------------------------------------------------------- /reflex-learn/src/components/App.tsx: -------------------------------------------------------------------------------- 1 | import { changed, h, mounted, state } from "reflex-dom"; 2 | import sdk from '@stackblitz/sdk'; 3 | import { VM } from "@stackblitz/sdk/types/vm"; 4 | import { limitRange } from "@zouloux/ecma-core"; 5 | import S from "./App.module.less" 6 | import { marked } from "marked"; 7 | 8 | 9 | function loadStackFiles () { 10 | // Load all stack files as raw text 11 | const __globFiles = import.meta.glob('../stack/**/**.*', { as: 'raw', eager: true }); 12 | // const file = import.meta.glob('../stack/steps/01.props.tsx', { as: 'raw', eager: true }); 13 | // console.log( file ); 14 | // Patch keys 15 | const stackFiles:Record = {} 16 | Object.keys( __globFiles ).map( fileName => { 17 | const value = __globFiles[ fileName ] 18 | fileName = fileName.substring(9, fileName.length) 19 | stackFiles[ fileName ] = value 20 | }) 21 | return stackFiles 22 | } 23 | 24 | export function App ( props ) { 25 | let _editor:VM 26 | let _stepFiles:string[] = [] 27 | let _docsFiles:string[] = [] 28 | let stackFiles = loadStackFiles() 29 | mounted( async () => { 30 | const filteredStackFiles = Object.keys( stackFiles ).filter( f => f.startsWith('steps/') ); 31 | _stepFiles = filteredStackFiles.filter( f =>f.endsWith('.tsx') ) 32 | _docsFiles = filteredStackFiles.filter( f =>f.endsWith('.md') ) 33 | _editor = await sdk.embedProject( 34 | 'iframe', { 35 | title: 'Learn Reflex', 36 | description: 'Learn Reflex tutorial', 37 | template: 'node', 38 | files: { 39 | ...stackFiles, 40 | 'loading' : 'Stackblitz is loading' 41 | } 42 | }, { 43 | clickToLoad: false, 44 | openFile: 'loading', 45 | terminalHeight: 6, 46 | hideExplorer: true, 47 | hideNavigation: true, 48 | theme: "dark", 49 | showSidebar: false, 50 | devToolsHeight: 0, 51 | hideDevTools: true, 52 | }, 53 | ); 54 | }) 55 | 56 | const isReady = state( false ) 57 | 58 | mounted(() => { 59 | const clear = () => clearInterval( interval ) 60 | const interval = setInterval( async () => { 61 | if ( !_editor ) return 62 | const fs = await _editor.getFsSnapshot() 63 | if ( !('ready' in fs) ) return 64 | isReady.value = true 65 | step.value = 0; 66 | clear(); 67 | }, 500) 68 | return clear 69 | }) 70 | 71 | const step = state( -1 ) 72 | 73 | let isFirst = true 74 | 75 | const docContent = state("") 76 | 77 | changed( async () => { 78 | let filePath = _stepFiles[ step.value ] 79 | if ( !_editor ) 80 | return 81 | await _editor.editor.openFile( filePath ) 82 | if ( isFirst ) { 83 | isFirst = false 84 | return 85 | } 86 | // Replace import in index.tsx 87 | const indexLines = stackFiles["index.tsx"].split("\n") 88 | filePath = filePath.split(".tsx")[0] 89 | indexLines[1] = `import('./${filePath}');` 90 | const indexRaw = indexLines.join("\n") 91 | await _editor.applyFsDiff({ 92 | destroy: [], 93 | create: { 'index.tsx' : indexRaw }, 94 | }) 95 | }) 96 | 97 | changed(() => { 98 | console.log(">>", isReady.value, step.value) 99 | if ( !isReady.value ) 100 | return 101 | // Replace markdown documentation 102 | const docPath = _docsFiles[ step.value ] 103 | docContent.value = marked( stackFiles[docPath] ?? "", { 104 | async: false 105 | }) as string 106 | console.log( docContent.value ); 107 | }, () => [isReady.value, step.value]) 108 | 109 | function changeStep ( delta:number ) { 110 | step.value = limitRange( 0, step.value + delta, 2 ) 111 | } 112 | const prevStep = () => changeStep(-1) 113 | const nextStep = () => changeStep(+1) 114 | const reset = async () => { 115 | const filePath = _stepFiles[ step.value ] 116 | const fileValue = stackFiles[ filePath ] 117 | const create = {} 118 | create[ filePath ] = fileValue 119 | await _editor.applyFsDiff({ destroy: [], create }) 120 | } 121 | 122 | return () =>
123 | 126 |