diff --git a/.gitignore b/.gitignore
index f3c315a..4dec440 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,17 +1,26 @@
-/target
-/classes
-/checkouts
-pom.xml.asc
-*.jar
+!.yarn/patches
+!.yarn/plugins
+!.yarn/releases
+!.yarn/sdks
+!.yarn/versions
*.class
+*.jar
+.pnp.*
+.shadow-cljs
+.yarn/*
+/*-init.clj
+/*-init.clj
+/.cpcache
/.lein-*
/.nrepl-port
-/*-init.clj
+/.rebel_readline_history
+/.shadow-cljs
+/checkouts
+/classes
/doc/dist
+/nashorn_code_cache
+/node_modules
/out
/repl
-/node_modules
-/nashorn_code_cache
-/.rebel_readline_history
-/.cpcache
-/.shadow-cljs
\ No newline at end of file
+/target
+pom.xml.asc
\ No newline at end of file
diff --git a/.yarnrc.yml b/.yarnrc.yml
new file mode 100644
index 0000000..896c0ee
--- /dev/null
+++ b/.yarnrc.yml
@@ -0,0 +1,9 @@
+enableGlobalCache: true
+
+enableImmutableCache: false
+
+enableImmutableInstalls: false
+
+enableTelemetry: false
+
+nodeLinker: node-modules
diff --git a/CHANGES.md b/CHANGES.md
index 599d672..93b3207 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -1,5 +1,85 @@
# Changelog #
+## Version v2.10
+
+- Add the `::rumext.v2/memo` metadata for simplify the common case for memoization
+- Add the `::rumext.v2.props/expect` metadata for props checking; it
+ accepts a set of props for simple existence checkong or map with
+ predicates for simple type checking
+- Add native js destructuring support on props
+
+
+## Version v2.9.3
+
+- bugfixes
+
+## Version v2.9.2
+
+- bugfixes
+
+## Version v2.9.1
+
+- bugfixes
+
+
+## Version v2.9
+
+- Make the library more lightweight removing unnecesary and duplicated code
+- Add the ability to define more react friendly components (for use
+ outside cljs codebases).
+- Add the ability to define lazy loading components
+- Make `rumext.v2.compiler/compile-concat` public
+- Improve documentation
+
+
+## Version v2.8
+
+- Export `React.lazy` as `lazy` helper
+- Add `lazy-component` macro that joins react lazy with shadow-cljs
+ lazy loading
+
+## Version v2.7
+
+- Update to react>=18
+
+
+## Version v2.6
+
+- Bugfixes
+
+
+## Version v2.5
+
+- Bugfixes
+
+
+## Version v2.4
+
+- Add improve performance of internal css handling
+
+
+## Version v2.3
+
+- Minor updates
+- Add with-fn macro
+
+## Version v2.2
+
+- Add the ability to destructure js props
+
+## Version v2.1
+
+- Make `use-id` available for react < 18.
+- Add `use-equal-memo` hook.
+- Add `use-debouce` hook.
+- Add experimental `use-ssr-effect`.
+
+## Version v2.0
+
+- Change version numbering: simplified.
+- Add v2 namespace that compatible with React18 (still some warnings that will be addressed in next versions)
+
+
## Version 2022.04.19-148
- Fix htmlFor attr handling
diff --git a/README.md b/README.md
index 3870876..885cece 100644
--- a/README.md
+++ b/README.md
@@ -1,187 +1,532 @@
-# rumext #
+# rumext
-Simple and Decomplected UI library based on React.
+Simple and Decomplected UI library based on React >= 18 focused on performance.
+## Installation
-## Using rumext
+Add to `deps.edn`:
-Add to deps.edn:
+```clojure
+funcool/rumext
+{:git/tag "v2.21"
+ :git/sha "072d671"
+ :git/url "https://github.com/funcool/rumext.git"}
+```
+
+## User Guide
+
+Rumext is a tool to build a web UI in ClojureScript.
+
+It's a thin wrapper on [React](https://react.dev/) >= 18, focused on
+performance and offering a Clojure-idiomatic interface.
+
+**API Reference**: http://funcool.github.io/rumext/latest/
+
+It uses Clojure macros to achieve the same goal as [JSX
+format](https://react.dev/learn/writing-markup-with-jsx) without using anything
+but the plain Clojure syntax. The HTML is expressed in a format inspired
+in [hiccup library](https://github.com/weavejester/hiccup), but with its own
+implementation.
+
+HTML code is represented as nested arrays with keywords for tags and
+attributes. Example:
+
+```clojure
+[:div {:class "foobar"
+ :style {:background-color "red"}
+ :on-click some-on-click-fn}
+ "Hello World"]
```
-funcool/rumext {:mvn/version "2022.04.19-148"}
+
+Macros are smart enough to transform attribute names from `lisp-case`
+to `camelCase` and renaming `:class` to `className`. So the compiled javacript
+code for this fragment could be something like:
+
+```js
+React.createElement("div",
+ {className: "foobar",
+ style: {"backgroundColor": "red"},
+ onClick: someOnClickFn},
+ "Hello World");
```
-## Differences with rum
+And this is what will be rendered when the app is loaded in a browser:
-This project is originated as a friendly fork of
-[rum](https://github.com/tonsky/rum) for a personal use but it is
-evolved to be a completly independent library that right now does not
-depend on it. In any case, many thanks to Tonksy for creating rum.
+```html
+
+ Hello World
+
+```
-This is the list of the main differences:
+**WARNING**: it is mainly implemented to be used in
+[Penpot](https://github.com/penpot/penpot) and released as separated project
+for conveniendce. Don't expect compromise for backwards compatibility beyond
+what the penpot project needs.
-- use function based components instead of class based components.
-- a clojurescript friendly abstractions for React Hooks.
-- the component body is compiled statically (never interprets at
- runtime thanks to **hicada**).
-- performance focused, with a goal to offer almost 0 runtime
- overhead on top of React.
+### Instantiating elements and custom components
+
+#### Passing props
+
+As seen above, when using the [Hiccup-like](https://github.com/weavejester/hiccup)
+syntax, you can create a HTML element with a keyword like `:div`, `:span` or
+`:p`. You can also specify a map of attributes, that are converted at compile
+time into a Javascript object.
+
+**IMPORTANT**: a Javascript plain object is different from a Clojure plain map.
+In ClojureScript you can handle mutable JS objects with a specific API, and
+convert forth and back to Clojure maps. You can learn more about it in
+[ClojureScript Unraveled](https://funcool.github.io/clojurescript-unraveled/#javascript-objects)
+book.
+
+Rumext macros have some features to pass properties in a more convenient and
+Clojure idiomatic way. For example, when using the `[:div {...}]` syntax, you
+do not need to add the `#js` prefix, it's added automatically. There are also
+some automatic transformations of property names:
-**WARNING**: this is not intended for general use, it is mainly
-implemented to be used in [penpot](https://github.com/penpot/penpot)
-and released as separated project for conveniendce. Don't expect
-compromise for backward compatibility.
+ * Names in `lisp-case` are transformed to `camelCase`.
+ * Reserved names like `class` are transformed to React convention, like
+ `className`.
+ * Names already in `camelCase` are passed directly without transform.
+ * Properties that begin with `data-` and `aria-` are also passed directly.
+ * Transforms are applied only to `:keyword` properties. You can also send
+ string properties, that are not processed anyway.
+It's important to notice that this transformations are performed at compile time,
+having no impact in runtime performance.
-## Components
-### How to define a component
+#### Dynamic element names and attributes
-Function components as it's name says, are defined using plain
-functions. Rumext exposes a lighweigh macro over a `fn` that convert
-props from js-object to cljs map (shallow) and exposes a facility for
-docorate (wrap) with other higher-order components.
+There are times when we'll need the element name to be chosen dynamically or
+constructed at runtime; the props to be built dynamically or created as an
+element from a user-defined component.
-Let's see a example of how to define a component:
+For this purpose, Rumext exposes a special macro: `:>`, a general-purpose
+handler for passing dynamically defined props to DOM native elements or
+creating elements from user-defined components.
+
+To define the element dynamically, just pass a variable with the name as a
+first parameter of `:>`.
```clojure
-(require '[rumext.alpha :as mf])
+(let [element (if something "div" "span")]
+ [:> element {:class "foobar"
+ :style {:background-color "red"}
+ :on-click some-on-click-fn}
+ "Hello World"])
+```
-(mf/defc title
- [{:keys [name]}]
- [:div {:class "label"} name])
+To give a dynamic map of properties, you may also give a variable as a
+second parameter:
+
+```clojure
+(let [props #js {:className "fooBar"
+ :style #js {:backgroundColor "red"}
+ :onClick some-on-click}]
+ [:> "div" props
+ "Hello World"])
```
-If you don't want the props in cljs data structure, you can disable
-the props conversion passing `::mf/wrap-props false` as metadata:
+**IMPORTANT** if you define the attributes dynamically, outside the `:>` macro,
+there are no automatic transformations. So you need to define the map as a
+plain Javascript object with the `#js` prefix or any other way. You also need
+to use `camelCase` names and remember to use `className` instead of `class`,
+for example.
+
+There are a couple of utilities for managing dynamic attributes in a more
+convenient way.
+
+
+##### `mf/spread-props`
+
+Or shorter alias: `mf/spread`
+
+A macro that allows performing a merge between two props data structures using
+the JS spread operator (`{...props1, ...props2}`). This macro also performs
+name transformations if you pass a literal map as a second parameter.
+
+It is commonly used this way:
```clojure
-(require '[goog.object :as gobj])
+(mf/defc my-label*
+ [{:keys [name class on-click] :rest props}]
+ (let [class (or class "my-label")
+ props (mf/spread-props props {:class class})]
+ [:span {:on-click on-click}
+ [:> :label props name]]))
+```
-(mf/defc title
- [props]
- (let [name (gobj/get props "name")]
- [:div {:class "label"} name]))
+Very similar to `mf/spread-props` but without react flavored props
+transformations you have the `mf/spread-object`.
+
+In both cases, if both arguments are symbols, no transformation
+can be applied because is unknown the structure at compile time.
+
+
+##### `mf/props`
+
+A helper macro to create a Javascript props object from a Clojure map,
+applying name transformations.
+
+An example of how it can be used and combined with `mf/spread-props`:
+
+```clojure
+(mf/defc my-label*
+ [{:keys [name class on-click] :rest props}]
+ (let [class (or class "my-label")
+ new-props (mf/props {:class class})
+ all-props (mf/spread-props props new-props)]
+ [:span {:on-click on-click}
+ [:> :label props name]]))
```
-### First steps with hicada hiccup
-You may be already familiar with hiccup syntax for defining the react
-dom. The intention on this section is explain only the essential part
-of it and the peculiarities of hiccada and rumext.
+##### `mf/object`
-Lets start with simple generic components like `:div`:
+A helper macro for create javascript objects from clojure literals. It works recursiverlly.
```clojure
-[:div {:class "foobar"
- :style {:background-color "red"}
- :on-click some-on-click-fn}
- "Hello World"]
+(mf/object {:a [1 2 3]})
+
+;; Is analogous to
+#js {:a #js [1 2 3]}
+```
+
+
+##### `mfu/map->props`
+
+In some cases you will need to make props from a dynamic Clojure
+object. You can use `mf/map->props` function for it, but be aware that
+it makes the conversion to Javascript and the names transformations in
+runtime, so it adds some overhead in each render. Consider not using
+it if performance is important.
+
+```clojure
+(require '[rumext.v2.utils :as mfu])
+
+(let [clj-props {:class "my-label"}
+ props (mfu/map->props clj-props)]
+ [:> :label props name])
+```
+
+##### `mfu/bean`
+
+A helper that allows create a proxy object from javascript object that
+has the same semantics as clojure map and clojure vectors. Allows
+handle clojure and javascript parameters in a transparent way.
+
+```clojure
+(require '[rumext.v2.utils :as mfu])
+
+(mf/defc my-select*
+ [{:keys [options] :rest props}]
+ (let [options (mfu/bean options)
+ ;; from here, options looks like a clojure vector
+ ;; independently if it passed as clojure vector
+ ;; or js array.
+ ]
+ [:select ...]))
+```
+
+#### Instantiating a custom component
+
+You can pass to `:>` macro the name of a custom component (see [below](#creating-a-react-custom-component))
+to create an instance of it:
+
+```clojure
+(mf/defc my-label*
+ [{:keys [name class on-click] :rest props}]
+ [:span {:on-click on-click}
+ [:> :label props name]])
+
+(mf/defc other-component*
+ []
+ [:> my-label* {:name "foobar" :on-click some-fn}])
```
-Until here, nothing new, looks like any hiccup template. The
+### Creating a React custom component
+
+The `defc` macro is the basic block of a Rumext UI. It's a lightweight utility
+that generates a React **function component** and adds some adaptations for it
+to be more convenient to ClojureScript code, like `camelCase` conversions and
+reserved name changes as explained [above](#passing-props).
+
+For example, this defines a React component:
+```clojure
+(require '[rumext.v2 :as mf])
+
+(mf/defc title*
+ [{:keys [label-text] :as props}]
+ [:div {:class "title"} label-text])
+```
-As you can observe, looks very familiar. On default components the
-props are transformed **recursively** at compile time to a js object
-transforming all keys from kebab-case to camelCase (and rename
-`:class` to `className`); so the result will look aproximatelly like
-this in jsx:
+The compiled javascript for this block will be similar to what would be
+obtained for this JSX block:
```js
-const h = React.createElement;
+function title({labelText}) {
+ return (
+
+ {labelText}
+
+ );
+}
+```
+
+**NOTE**: the `*` in the component name is a mandatory convention for proper
+visual distinction of React components and Clojure functions. It also enables
+the current defaults on how props are handled. If you don't use the `*` suffix,
+the component will behave in legacy mode (see the [FAQs](#faq) below).
+
+The component created this way can be mounted onto the DOM:
-h("div", {className: "foobar",
- style: {"backgroundColor": "red"},
- onClick=someFn},
- "Hello World");
+```clojure
+(ns myname.space
+ (:require
+ [goog.dom :as dom]
+ [rumext.v2 :as mf]))
+
+(def root (mf/create-root (dom/getElement "app")))
+(mf/render! root (mf/html [:> title* {:label-text "hello world"}]))
```
-TODO
+Or you can use `mf/element`, but in this case you need to give the
+attributes in the raw Javascript form, because this macro does not have
+automatic conversions:
-### Higher-Order Components
+```clojure
+(ns myname.space
+ (:require
+ [goog.dom :as dom]
+ [rumext.v2 :as mf]))
-This is the way you have to extend/add additional functionality to a
-function component. Rumext exposes one:
+(def root (mf/create-root (dom/getElement "app")))
+(mf/render! root (mf/element title* #js {:labelText "hello world"}))
+```
-- `mf/memo`: analogous to `React.memo`, adds memoization to the
- component based on props comparison.
+### Reading component props & destructuring
-In order to use the high-order components, you need wrap the component manually
-or passing it as a special property in the metadata:
+When React instantiates a function component, it passes a `props` parameter
+that is a map of the names and values of the attributes defined in the calling
+point.
+
+Normally, Javascript objects cannot be destructured. But the `defc` macro
+implements a destructuring functionality, that is similar to what you can do
+with Clojure maps, but with small differences and convenient enhancements for
+making working with React props and idioms easy, like `camelCase` conversions
+as explained [above](#passing-props).
```clojure
-(mf/defc title
- {::mf/wrap [mf/wrap-memo]}
- [props]
- [:div {:class "label"} (:name props)])
+(mf/defc title*
+ [{:keys [title-name] :as props}]
+ (assert (object? props) "expected object")
+ (assert (string? title-name) "expected string")
+ [:label {:class "label"} title-name])
+```
+
+If the component is called via the `[:>` macro (explained [above](#dynamic-element-names-and-attributes)),
+there will be two compile-time conversion, one when calling and another one when
+destructuring. In the Clojure code all names will be `lisp-case`, but if you
+inspect the generated Javascript code, you will see names in `camelCase`.
+
+#### Default values
+
+Also like usual destructuring, you can give default values to properties by
+using the `:or` construct:
+
+```clojure
+(mf/defc color-input*
+ [{:keys [value select-on-focus] :or {select-on-focus true} :as props}]
+ ...)
```
-By default `identical?` predicate is used for compare props; this is
-how you can pass a custom compare function:
+#### Rest props
+
+An additional idiom (specific to the Rumext component macro and not available
+in standard Clojure destructuring) is the ability to obtain an object with all
+non-destructured props with the `:rest` construct. This allows to extract the
+props that the component has control of and leave the rest in an object that
+can be passed as-is to the next element.
```clojure
-(mf/defc title
- {::mf/wrap [#(mf/wrap-memo % =)]}
+(mf/defc title*
+ [{:keys [name] :rest props}]
+ (assert (object? props) "expected object")
+ (assert (nil? (unchecked-get props "name")) "no name in props")
+
+ ;; See below for the meaning of `:>`
+ [:> :label props name])
+```
+
+#### Reading props without destructuring
+
+Of course the destructure is optional. You can receive the complete `props`
+argument and read the properties later. But in this case you will not have
+the automatic conversions:
+
+```clojure
+(mf/defc color-input*
[props]
- [:div {:class "label"} (:name props)])
+ (let [value (unchecked-get props "value")
+ on-change (unchecked-get props "onChange")
+ on-blur (unchecked-get props "onBlur")
+ on-focus (unchecked-get props "onFocus")
+ select-on-focus? (or (unchecked-get props "selectOnFocus") true)
+ class (or (unchecked-get props "className") "color-input")
```
-If you want create a own high-order component you can use `mf/fnc` macro:
+The recommended way of reading `props` javascript objects is by using the
+Clojurescript core function `unchecked-get`. This is directly translated to
+Javascript `props["propName"]`. As Rumext is performance oriented, this is the
+most efficient way of reading props for the general case. Other methods like
+`obj/get` in Google Closure Library add extra safety checks, but in this case
+it's not necessary since the `props` attribute is guaranteed by React to have a
+value, although it can be an empty object.
+
+#### Forwarding references
+
+In React there is a mechanism to set a reference to the rendered DOM element, if
+you need to manipulate it later. Also it's possible that a component may receive
+this reference and gives it to a inner element. This is called "forward referencing"
+and to do it in Rumext, you need to add the `forward-ref` metadata. Then, the
+reference will come in a second argument to the `defc` macro:
```clojure
-(defn some-factory
- [component param]
- (mf/fnc myhighordercomponent
- {::mf/wrap-props false}
- [props]
- [:section
- [:> component props]]))
+(mf/defc wrapped-input*
+ {::mf/forward-ref true}
+ [props ref]
+ (let [...]
+ [:input {:style {...}
+ :ref ref
+ ...}]))
+```
+
+In React 19 this will not be necessary, since you will be able to pass the ref
+directly inside `props`. But Rumext currently only support React 18.
+
+### Props Checking
+
+The Rumext library comes with two approaches for checking props:
+**simple** and **malli**.
+
+Let's start with the **simple**, which consists of simple existence checks or
+plain predicate checking. For this, we have the `mf/expect` macro that receives
+a Clojure set and throws an exception if any of the props in the set has not
+been given to the component:
+
+```clojure
+(mf/defc button*
+ {::mf/expect #{:name :on-click}}
+ [{:keys [name on-click]}]
+ [:button {:on-click on-click} name])
+```
+
+The prop names obey the same rules as the destructuring so you should use the
+same names.
+
+Sometimes a simple existence check is not enough; for those cases, you can give
+`mf/expect` a map where keys are props and values are predicates:
+
+```clojure
+(mf/defc button*
+ {::mf/expect {:name string?
+ :on-click fn?}}
+ [{:keys [name on-click]}]
+ [:button {:on-click on-click} name])
```
+If that is not enough, you can use `mf/schema` macro that supports
+**[malli](https://github.com/metosin/malli)** schemas as a validation
+mechanism for props:
-### Hooks (React Hooks)
+```clojure
+(def ^:private schema:props
+ [:map {:title "button:props"}
+ [:name string?]
+ [:class {:optional true} string?]
+ [:on-click fn?]])
+
+(mf/defc button*
+ {::mf/schema schema:props}
+ [{:keys [name on-click]}]
+ [:button {:on-click on-click} name])
+```
+
+**IMPORTANT**: The props checking obeys the `:elide-asserts` compiler
+option and by default, they will be removed in production builds if
+the configuration value is not changed explicitly.
+
+### Hooks
+
+You can use React hooks as is, as they are exposed by Rumext as
+`mf/xxx` wrapper functions. Additionaly, Rumext offers several
+specific hooks that adapt React ones to have a more Clojure idiomatic
+interface.
+
+You can use both one and the other interchangeably, depending on which
+type of API you feel most comfortable with. The React hooks are exposed
+as they are in React, with the function name in `camelCase`, and the
+Rumext hooks use the `lisp-case` syntax.
-React hooks is a basic primitive that React exposes for add state and
-side-effects to functional components. Rumext exposes right now only
-three hooks with a ClojureScript based api.
+Only a subset of available hooks is documented here; please refer to
+the [React API reference
+documentation](https://react.dev/reference/react/hooks) for detailed
+information about available hooks.
+#### `use-state`
-#### use-state (React.useState)
+This is analogous to the `React.useState`. It offers the same
+functionality but uses the ClojureScript atom interface.
-Hook used for maintain a local state and in functional
-components. Calling `mf/use-state` returns an atom-like object that
-will deref to the current value and you can call `swap!` and `reset!`
-on it for modify its state.
+Calling `mf/use-state` returns an atom-like object that will deref to
+the current value, and you can call `swap!` and `reset!` on it to
+modify its state. The returned object always has a stable reference
+(no changes between rerenders).
Any mutation will schedule the component to be rerendered.
```clojure
-(require '[rumext.alpha as mf])
+(require '[rumext.v2 as mf])
-(mf/defc local-state
+(mf/defc local-state*
[props]
- (let [local (mf/use-state 0)]
- [:div {:on-click #(swap! local inc)}
- [:span "Clicks: " @local]]))
+ (let [clicks (mf/use-state 0)]
+ [:div {:on-click #(swap! clicks inc)}
+ [:span "Clicks: " @clicks]]))
+```
+
+This is functionally equivalent to using the React hook directly:
-(mf/mount (mf/element local-state) js/document.body)
+```clojure
+(mf/defc local-state*
+ [props]
+ (let [[counter update-counter] (mf/useState 0)]
+ [:div {:on-click (partial update-counter #(inc %))}
+ [:span "Clicks: " counter]]))
```
-#### use-var (React.useRef)
+#### `use-var`
+
+In the same way as `use-state` returns an atom-like object. The unique
+difference is that updating the ref value does not schedule the
+component to rerender. Under the hood, it uses the `useRef` hook.
-In the same way as `use-state` returns an atom like object. The unique
-difference is that updating the ref value does not schedules the
-component to rerender.
+**DEPRECATED:** should not be used
+#### `use-effect`
-#### use-effect (React.useEffect)
+Analogous to the `React.useEffect` hook with a minimal call convention
+change (the order of arguments is inverted).
-This is a primitive that allows incorporate probably efectful code
+This is a primitive that allows incorporating probably effectful code
into a functional component:
```clojure
-(mf/defc local-timer
+(mf/defc local-timer*
[props]
(let [local (mf/use-state 0)]
(mf/use-effect
@@ -189,19 +534,18 @@ into a functional component:
(let [sem (js/setInterval #(swap! local inc) 1000)]
#(js/clearInterval sem))))
[:div "Counter: " @local]))
-
-(mf/mount (mf/element local-state) js/document.body)
```
-The `use-effect` is a two arity function. If you pass a single
-callback function it acts like there are no dependencies, so the
-callback will be executed once per component (analgous to `didMount`
+The `use-effect` is a two-arity function. If you pass a single
+callback function, it acts as though there are no dependencies, so the
+callback will be executed once per component (analogous to `didMount`
and `willUnmount`).
-If you want to pass dependencies you have two ways:
+If you want to pass dependencies, you have two ways:
-- passing an js array
-- using `rumext.alpha/deps` helper
+- passing a JS array as a first argument (like in React but with
+ inverted order).
+- using the `rumext.v2/deps` helper:
```clojure
(mf/use-effect
@@ -210,32 +554,68 @@ If you want to pass dependencies you have two ways:
```
And finally, if you want to execute it on each render, pass `nil` as
-deps (much in the same way as raw useEffect works.
+deps (much in the same way as raw `useEffect` works).
+
+For convenience, there is an `mf/with-effect` macro that drops one
+level of indentation:
+
+```clojure
+(mf/defc local-timer*
+ [props]
+ (let [local (mf/use-state 0)]
+ (mf/with-effect []
+ (let [sem (js/setInterval #(swap! local inc) 1000)]
+ #(js/clearInterval sem)))
+ [:div "Counter: " @local]))
+```
+
+Here, the deps must be passed as elements within the vector (the first
+argument).
+Obviously, you can also use the React hook directly via `mf/useEffect`.
-#### use-memo (React.useMemo)
+#### `use-memo`
-The purpose of this hook is return a memoized value.
+In the same line as the `use-effect`, this hook is analogous to the
+React `useMemo` hook with the order of arguments inverted.
+
+The purpose of this hook is to return a memoized value.
Example:
```clojure
-(mf/defc sample-component
+(mf/defc sample-component*
[{:keys [x]}]
(let [v (mf/use-memo (mf/deps x) #(pow x 10))]
- [:span "Value is:" v]))
+ [:span "Value is: " v]))
```
On each render, while `x` has the same value, the `v` only will be
calculated once.
-There is also the `rumext.alpha/use-callback` for a specific use
-cases.
+This also can be expressed with the `rumext.v2/with-memo` macro that
+removes a level of indentation:
+```clojure
+(mf/defc sample-component*
+ [{:keys [x]}]
+ (let [v (mf/with-memo [x]
+ (pow x 10))]
+ [:span "Value is: " v]))
+```
+
+#### `use-fn`
+
+Is a special case of `use-memo`in that the memoized value is a
+function definition.
-#### deref
+An alias for `use-callback`, that is a wrapper on `React.useCallback`.
-A custom hook that adds ractivity to atom changes to the component.
+#### `deref`
+
+A Rumext custom hook that adds reactivity to atom changes to the
+component. Calling `mf/deref` returns the same value as the Clojure
+`deref`, but also sets a component rerender when the value changes.
Example:
@@ -243,28 +623,116 @@ Example:
(def clock (atom (.getTime (js/Date.))))
(js/setInterval #(reset! clock (.getTime (js/Date.))) 160)
-(mf/defc timer
+(mf/defc timer*
[props]
(let [ts (mf/deref clock)]
- [:div "Timer (deref)" ": "
+ [:div "Timer (deref): "
[:span ts]]))
```
+Internally, it uses the `react.useSyncExternalStore` API together with
+the ability of atom to watch it.
+
+### Higher-Order Components
+
+React allows to create a component that adapts or wraps another component
+to extend it and add additional functionality. Rumext includes a convenient
+mechanism for doing it: the `::mf/wrap` metadata.
+
+Currently Rumext exposes one such component:
+
+- `mf/memo`: analogous to `React.memo`, adds memoization to the
+ component based on props comparison. This allows to completely
+ avoid execution to the component function if props have not changed.
+
+```clojure
+(mf/defc title*
+ {::mf/wrap [mf/memo]}
+ [{:keys [name]}]
+ [:div {:class "label"} name])
+```
+
+By default, the `identical?` predicate is used to compare props; you
+can pass a custom comparator function as a second argument:
+
+```clojure
+(mf/defc title*
+ {::mf/wrap [#(mf/memo % =)]}
+ [{:keys [name]}]
+ [:div {:class "label"} name])
+```
+
+For more convenience, Rumext has a special metadata `::mf/memo` that
+facilitates the general case for component props memoization. If you
+pass `true`, it will behave the same way as `::mf/wrap [mf/memo]` or
+`React.memo(Component)`. You also can pass a set of fields; in this
+case, it will create a specific function for testing the equality of
+that set of props.
+
+If you want to create your own higher-order component, you can use the
+`mf/fnc` macro:
+
+```clojure
+(defn some-factory
+ [component param]
+ (mf/fnc my-high-order-component*
+ [props]
+ [:section
+ [:> component props]]))
+```
+
+### FAQ
+
+#### Differences with RUM
+
+This project was originated as a friendly fork of
+[rum](https://github.com/tonsky/rum) for a personal use but it later
+evolved to be a completly independent library that right now does not
+depend on it and probably no longer preserves any of the original
+code. In any case, many thanks to Tonksy for creating rum.
+
+This is the list of the main differences:
+
+- use function based components instead of class based components.
+- a clojurescript friendly abstractions for React Hooks.
+- the component body is compiled statically (never interprets at
+ runtime thanks to **hicada**).
+- performance focused, with a goal to offer almost 0 runtime
+ overhead on top of React.
+
+
+#### Why the import alias is `mf` in the examples?
+
+The usual convention of importing RUM project was to use `rum/defc` or
+`m/defc`. For Rumext the most straightforward abbreviation would have been
+`mx/defc`. But that preffix was already use for something else. So finally we
+choose `mf/defc`. But this is not mandatory, it's only a convention we follow
+in this manual and in Penpot.
+
-#### Raw Hooks
+#### What is the legacy mode?
-In some circumstances you will want access to the raw react hooks
-functions. For this purpose, rumext exposes the following functions:
-`useState`, `useRef`, `useMemo`, `useCallback`, `useLayoutEffect` and
-`useEffect`.
+In earlier versions of Rumext, components had a default behavior of
+automatically converting the `props` Javascript object coming from
+React to a Clojure object, so it could be read by normal destructuring
+or any other way of reading objects.
-#### Other undocumented stuff
+Additionally you could use `:&` handler instead of `:>` to give a
+Clojure object that was converted into Javascript for passing it to
+React.
-- Error boundaries: `mf/catch` high-order component.
-- Raw `React.memo`: `mf/memo'`.
-- Create element: `mf/element` and `mf/create-element`.
+But both kind of transformations were done in runtime, thus adding
+the conversion overhead to each render of the compoennt. Since Rumex
+is optimized for performance, this behavior is now deprecated. With
+the macro destructuring and other utilities explained above, you can
+do argument passing almost so conveniently, but with all changes done
+in compile time.
+Currently, components whose name does not use `*` as a suffix behave
+in legacy mode. You can activate the new behavior by adding the
+`::mf/props :obj` metadata, but all this is considered deprecated now.
+All new components should use `*` in the name.
-## License ##
+## License
-Licensed under Eclipse Public License (see [LICENSE](LICENSE)).
+Licensed under MPL-2.0 (see [LICENSE](LICENSE) file on the root of the repository)
diff --git a/build.clj b/build.clj
index c774ac7..49c9b1a 100644
--- a/build.clj
+++ b/build.clj
@@ -3,7 +3,7 @@
(:require [clojure.tools.build.api :as b]))
(def lib 'funcool/rumext)
-(def version (format "2022.04.19-%s" (b/git-count-revs nil)))
+(def version (format "v2-%s" (b/git-count-revs nil)))
(def class-dir "target/classes")
(def basis (b/create-basis {:project "deps.edn"}))
(def jar-file (format "target/%s-%s.jar" (name lib) version))
diff --git a/deps.edn b/deps.edn
index e35d15d..840f4f0 100644
--- a/deps.edn
+++ b/deps.edn
@@ -1,4 +1,6 @@
-{:deps {}
+{:deps {metosin/malli {:mvn/version "0.16.0"}
+ funcool/cuerdas {:mvn/version "2023.11.09-407"}
+ cljs-bean/cljs-bean {:mvn/version "1.9.0"}}
:paths ["src"]
:aliases
{:dev
@@ -9,12 +11,18 @@
thheller/shadow-cljs {:mvn/version "RELEASE"}
org.clojure/tools.namespace {:mvn/version "RELEASE"}
org.clojure/test.check {:mvn/version "RELEASE"}
- ;; org.clojure/tools.deps.alpha {:mvn/version "RELEASE"}
- org.clojure/clojure {:mvn/version "1.10.3"}
+ org.clojure/clojure {:mvn/version "RELEASE"}
}}
+ :codox
+ {:extra-deps
+ {codox/codox {:mvn/version "RELEASE"}
+ org.clojure/tools.reader {:mvn/version "RELEASE"}
+ codox-theme-rdash/codox-theme-rdash {:mvn/version "RELEASE"}}}
+
:shadow-cljs
- {:main-opts ["-m" "shadow.cljs.devtools.cli"]}
+ {:main-opts ["-m" "shadow.cljs.devtools.cli"]
+ :jvm-opts ["--sun-misc-unsafe-memory-access=allow"]}
:repl
{:main-opts ["-m" "rebel-readline.main"]}
@@ -25,5 +33,5 @@
:main-opts ["-m" "antq.core"]}
:build
- {:extra-deps {io.github.clojure/tools.build {:git/tag "v0.8.1" :git/sha "7d40500"}}
+ {:extra-deps {io.github.clojure/tools.build {:git/tag "v0.10.3" :git/sha "15ead66"}}
:ns-default build}}}
diff --git a/doc.clj b/doc.clj
new file mode 100644
index 0000000..4a22bcd
--- /dev/null
+++ b/doc.clj
@@ -0,0 +1,11 @@
+(require '[codox.main :as codox])
+
+(codox/generate-docs
+ {:output-path "doc/dist/latest"
+ :metadata {:doc/format :markdown}
+ :language :clojurescript
+ :name "funcool/rumext"
+ :themes [:rdash]
+ :source-paths ["src"]
+ :namespaces [#"^rumext\."]
+ :source-uri "https://github.com/funcool/rumext/blob/v2/{filepath}#L{line}"})
diff --git a/doc/Makefile b/doc/Makefile
new file mode 100644
index 0000000..e476300
--- /dev/null
+++ b/doc/Makefile
@@ -0,0 +1,9 @@
+all: doc
+
+doc:
+ mkdir -p dist/latest/
+ cd ..; clojure -A:dev:codox -M doc.clj;
+
+github: doc
+ ghp-import -m "Generate documentation" -b gh-pages dist/
+ git push origin gh-pages
diff --git a/doc/notes.txt b/doc/notes.txt
new file mode 100644
index 0000000..e1a645d
--- /dev/null
+++ b/doc/notes.txt
@@ -0,0 +1,5 @@
+Build browserified bundle:
+./node_modules/browserify/bin/cmd.js -s Rx -e dist/cjs/Rx.js -o rx.js
+
+Minified bundle:
+./node_modules/uglify-js/bin/uglifyjs rx.js -m -o rx.min.js
diff --git a/examples/rumext/examples/binary_clock.cljs b/examples/rumext/examples/binary_clock.cljs
index c158a96..dc3cf26 100644
--- a/examples/rumext/examples/binary_clock.cljs
+++ b/examples/rumext/examples/binary_clock.cljs
@@ -1,24 +1,27 @@
(ns rumext.examples.binary-clock
- (:require [goog.dom :as dom]
- [rumext.alpha :as mf]
- [rumext.examples.util :as util]))
+ (:require
+ [goog.dom :as dom]
+ [rumext.v2 :as mf]
+ [rumext.examples.util :as util]))
(def *bclock-renders (atom 0))
-(mf/defc render-count
+(mf/defc render-count*
[props]
(let [renders (mf/deref *bclock-renders)]
[:div.stats "Renders: " renders]))
-(mf/defc bit
- [{:keys [n b] :as props}]
- (mf/use-effect (mf/deps n b) #(swap! *bclock-renders inc))
+(mf/defc bit*
+ [{:keys [n b]}]
+ (mf/with-effect [n b]
+ (swap! *bclock-renders inc))
+
(let [color (mf/deref util/*color)]
(if (bit-test n b)
[:td.bclock-bit {:style {:background-color color}}]
[:td.bclock-bit {}])))
-(mf/defc binary-clock
+(mf/defc binary-clock*
[]
(let [ts (mf/deref util/*clock)
msec (mod ts 1000)
@@ -37,41 +40,41 @@
[:table.bclock
[:tbody
[:tr
- [:td] [:& bit {:n hl :b 3}] [:th]
- [:td] [:& bit {:n ml :b 3}] [:th]
- [:td] [:& bit {:n sl :b 3}] [:th]
- [:& bit {:n msh :b 3}]
- [:& bit {:n msm :b 3}]
- [:& bit {:n msl :b 3}]]
+ [:td] [:> bit* {:n hl :b 3}] [:th]
+ [:td] [:> bit* {:n ml :b 3}] [:th]
+ [:td] [:> bit* {:n sl :b 3}] [:th]
+ [:> bit* {:n msh :b 3}]
+ [:> bit* {:n msm :b 3}]
+ [:> bit* {:n msl :b 3}]]
[:tr
- [:td] [:& bit {:n hl :b 2}] [:th]
- [:& bit {:n mh :b 2}]
- [:& bit {:n ml :b 2}] [:th]
- [:& bit {:n sh :b 2}]
- [:& bit {:n sl :b 2}] [:th]
- [:& bit {:n msh :b 2}]
- [:& bit {:n msm :b 2}]
- [:& bit {:n msl :b 2}]]
+ [:td] [:> bit* {:n hl :b 2}] [:th]
+ [:> bit* {:n mh :b 2}]
+ [:> bit* {:n ml :b 2}] [:th]
+ [:> bit* {:n sh :b 2}]
+ [:> bit* {:n sl :b 2}] [:th]
+ [:> bit* {:n msh :b 2}]
+ [:> bit* {:n msm :b 2}]
+ [:> bit* {:n msl :b 2}]]
[:tr
- [:& bit {:n hh :b 1}]
- [:& bit {:n hl :b 1}] [:th]
- [:& bit {:n mh :b 1}]
- [:& bit {:n ml :b 1}] [:th]
- [:& bit {:n sh :b 1}]
- [:& bit {:n sl :b 1}] [:th]
- [:& bit {:n msh :b 1}]
- [:& bit {:n msm :b 1}]
- [:& bit {:n msl :b 1}]]
+ [:> bit* {:n hh :b 1}]
+ [:> bit* {:n hl :b 1}] [:th]
+ [:> bit* {:n mh :b 1}]
+ [:> bit* {:n ml :b 1}] [:th]
+ [:> bit* {:n sh :b 1}]
+ [:> bit* {:n sl :b 1}] [:th]
+ [:> bit* {:n msh :b 1}]
+ [:> bit* {:n msm :b 1}]
+ [:> bit* {:n msl :b 1}]]
[:tr
- [:& bit {:n hh :b 0}]
- [:& bit {:n hl :b 0}] [:th]
- [:& bit {:n mh :b 0}]
- [:& bit {:n ml :b 0}] [:th]
- [:& bit {:n sh :b 0}]
- [:& bit {:n sl :b 0}] [:th]
- [:& bit {:n msh :b 0}]
- [:& bit {:n msm :b 0}]
- [:& bit {:n msl :b 0}]]
+ [:> bit* {:n hh :b 0}]
+ [:> bit* {:n hl :b 0}] [:th]
+ [:> bit* {:n mh :b 0}]
+ [:> bit* {:n ml :b 0}] [:th]
+ [:> bit* {:n sh :b 0}]
+ [:> bit* {:n sl :b 0}] [:th]
+ [:> bit* {:n msh :b 0}]
+ [:> bit* {:n msm :b 0}]
+ [:> bit* {:n msl :b 0}]]
[:tr
[:th hh]
[:th hl]
@@ -87,8 +90,11 @@
[:th msl]]
[:tr
[:th {:col-span 8}
- [:& render-count]]]]]))
+ [:> render-count* {}]]]]]))
+
+(defonce root
+ (mf/create-root (dom/getElement "binary-clock")))
-(defn mount! []
- (mf/mount (mf/element binary-clock) (dom/getElement "binary-clock")))
+(defn ^:after-load mount! []
+ (mf/render! root (mf/element binary-clock*)))
diff --git a/examples/rumext/examples/board.cljs b/examples/rumext/examples/board.cljs
index da73d13..5b06651 100644
--- a/examples/rumext/examples/board.cljs
+++ b/examples/rumext/examples/board.cljs
@@ -1,7 +1,7 @@
(ns rumext.examples.board
(:require
[goog.dom :as dom]
- [rumext.alpha :as mf]
+ [rumext.v2 :as mf]
[rumext.examples.util :as util]
[okulary.core :as l]))
@@ -16,8 +16,9 @@
(l/derived (l/in [y x]) board))
cell (mf/deref ref)
color (mf/deref util/*color)]
- [:div.art-cell
- {:style {:background-color (when cell color)}
+ [:div
+ {:class "art-cell"
+ :style {:background-color (when cell color)}
:on-mouse-over (fn [_] (swap! board update-in [y x] not) nil)}]))
(mf/defc board-reactive
@@ -26,13 +27,15 @@
(for [y (range 0 util/board-height)]
[:div.art-row {:key y}
(for [x (range 0 util/board-width)]
- ;; this is how one can specify React key for component
- [:& cell {:key x :x x :y y}])])])
+ (let [props #js {:key x :x x :y y}]
+ ;; this is how one can specify React key for component
+ [:& cell ^js props]))])])
-(defn mount! []
- (mf/mount (mf/element board-reactive)
- (dom/getElement "board"))
+(defonce root
+ (mf/create-root (dom/getElement "board")))
+
+(defn ^:after-load mount! []
+ (mf/render! root (mf/element board-reactive))
(js/setTimeout (fn []
- (mf/mount (mf/element board-reactive)
- (dom/getElement "board")))
+ (mf/render! root (mf/element board-reactive)))
2000))
diff --git a/examples/rumext/examples/controls.cljs b/examples/rumext/examples/controls.cljs
index 5993b4b..050af9f 100644
--- a/examples/rumext/examples/controls.cljs
+++ b/examples/rumext/examples/controls.cljs
@@ -1,10 +1,10 @@
(ns rumext.examples.controls
- (:require [goog.dom :as dom]
- [rumext.alpha :as mf]
- [rumext.examples.util :as util]))
+ (:require
+ [goog.dom :as dom]
+ [rumext.v2 :as mf]
+ [rumext.examples.util :as util]))
-;; generic “atom editor” component
-(mf/defc input
+(mf/defc input*
[{:keys [color] :as props}]
(let [value (mf/deref color)]
[:input {:type "text"
@@ -13,25 +13,28 @@
:on-change #(reset! color (.. % -target -value))}]))
;; Raw top-level component, everything interesting is happening inside
-(mf/defc controls
+(mf/defc controls*
[props]
[:dl
[:dt "Color: "]
[:dd
- [:& input {:color util/*color}]]
+ [:> input* {:color util/*color}]]
;; Binding another component to the same atom will keep 2 input boxes in sync
[:dt "Clone: "]
[:dd
- (mf/element input {:color util/*color})]
+ (mf/jsx input* #js {:color util/*color})]
[:dt "Color: "]
[:dd {} (util/watches-count {:iref util/*color}) " watches"]
[:dt "Tick: "]
- [:dd [:& input {:color util/*speed}] " ms"]
+ [:dd [:> input* {:color util/*speed}] " ms"]
[:dt "Time:"]
[:dd {} (util/watches-count {:iref util/*clock}) " watches"]
])
-(defn mount! []
- (mf/mount (mf/element controls) (dom/getElement "controls")))
+(defonce root
+ (mf/create-root (dom/getElement "controls")))
+
+(defn ^:after-load mount! []
+ (mf/render! root (mf/element controls*)))
diff --git a/examples/rumext/examples/core.cljs b/examples/rumext/examples/core.cljs
index e8fb7ba..cb4ed71 100644
--- a/examples/rumext/examples/core.cljs
+++ b/examples/rumext/examples/core.cljs
@@ -1,24 +1,24 @@
(ns rumext.examples.core
(:require
- [rumext.alpha :as mf]
[rumext.examples.binary-clock :as binary-clock]
[rumext.examples.timer-reactive :as timer-reactive]
[rumext.examples.local-state :as local-state]
[rumext.examples.refs :as refs]
[rumext.examples.controls :as controls]
+ [rumext.examples.portals :as portals]
[rumext.examples.board :as board]
;; [rumext.examples.errors :as errors]
))
(enable-console-print!)
+(local-state/mount!)
(binary-clock/mount!)
(timer-reactive/mount!)
-(local-state/mount!)
-
(refs/mount!)
(controls/mount!)
(board/mount!)
+(portals/mount!)
(defn main
[& args]
diff --git a/examples/rumext/examples/local_state.cljs b/examples/rumext/examples/local_state.cljs
index ce40315..ebd7fa2 100644
--- a/examples/rumext/examples/local_state.cljs
+++ b/examples/rumext/examples/local_state.cljs
@@ -1,43 +1,53 @@
- (ns rumext.examples.local-state
- (:require [goog.dom :as dom]
- [rumext.alpha :as mf]
- [rumext.examples.util :as util]))
-
-;; (mf/defc label
-;; {:wrap [mf/wrap-memo]}
-;; [{:keys [state] :as props}]
-;; ;; (prn "label" props)
-;; (let [{:keys [title n]} state]
-;; [:div
-;; [:span title ": " n]]))
-
-(def label
- (mf/fnc label
- {::mf/wrap [mf/memo]}
- [{:keys [state] :as props}]
-
- (let [{:keys [title n]} state]
- [:*
- [:div
- [:span title ": " n]]])))
+(ns rumext.examples.local-state
+ (:require
+ [goog.dom :as dom]
+ [malli.core :as m]
+ [rumext.v2 :as mf]
+ [rumext.v2.util :as mfu]
+ [rumext.examples.util :as util]))
+(def schema:label
+ [:map {:title "label:props"}
+ [:on-click {:optional true} fn?]
+ [:my-id {:optional true} :keyword]
+ [:title :string]
+ [:n number?]])
+
+(mf/defc label*
+ {::mf/memo true
+ ::mf/schema schema:label}
+ [{:keys [class title n my-id] :as props :rest others}]
+ (let [ref (mf/use-var nil)
+ props (mf/spread-props others {:class (or class "my-label")})]
+
+ (mf/with-effect []
+ (reset! ref 1))
+
+ [:> :div props
+ [:span title ": " n]]))
(mf/defc local-state
"test docstring"
- {::mf/wrap [mf/memo]}
- [{:keys [title] :as props}]
- (let [local (mf/use-state {:counter1 {:title "Counter 1"
- :n 0}
- :counter2 {:title "Counter 2"
- :n 0}})]
- [:section {:class "counters"}
+ {::mf/memo true
+ ::mf/props :obj}
+ [{:keys [title]}]
+ (let [local (mf/use-state
+ #(-> {:counter1 {:title "Counter 1"
+ :n 0}
+ :counter2 {:title "Counter 2"
+ :n 0}}))]
+
+ [:section {:class "counters" :style {:-webkit-border-radius "10px"}}
[:hr]
- [:& label {:state (:counter1 @local)}]
- [:& label {:state (:counter2 @local)}]
+ (let [{:keys [title n]} (:counter1 @local)]
+ [:> label* {:n n :my-id "should-be-keyword" :title title :data-foobar 1 :on-click identity :id "foobar"}])
+ (let [{:keys [title n]} (:counter2 @local)]
+ [:> label* {:title title :n n :on-click identity}])
[:button {:on-click #(swap! local update-in [:counter1 :n] inc)} "Increment Counter 1"]
[:button {:on-click #(swap! local update-in [:counter2 :n] inc)} "Increment Counter 2"]]))
+(defonce root
+ (mf/create-root (dom/getElement "local-state-1")))
-(defn mount! []
- (mf/mount (mf/element local-state {:title "Clicks count"})
- (dom/getElement "local-state-1")))
+(defn ^:after-load mount! []
+ (mf/render! root (mf/element local-state #js {:title "Clicks count"})))
diff --git a/examples/rumext/examples/portals.cljs b/examples/rumext/examples/portals.cljs
index 00f264e..814c555 100644
--- a/examples/rumext/examples/portals.cljs
+++ b/examples/rumext/examples/portals.cljs
@@ -1,22 +1,26 @@
(ns rumext.examples.portals
- #_(:require [rumext.core :as mx]
- [rumext.examples.util :as util]))
+ (:require
+ [rumext.v2 :as mf]
+ [goog.dom :as dom]))
-;; (mx/defc portal
-;; [*clicks]
-;; [:div {:on-click (fn [_] (swap! *clicks inc))
-;; :style { :user-select "none", :cursor "pointer" }}
-;; "[ PORTAL Clicks: " @*clicks " ]"])
+(mf/defc portal*
+ [{:keys [state]}]
+ [:div {:on-click (fn [_] (swap! state inc))
+ :style { :user-select "none", :cursor "pointer" }}
+ "[ PORTAL Clicks: " @state " ]"])
+(mf/defc portals*
+ []
+ (let [state (mf/use-state 0)]
+ [:div {:on-click (fn [_] (swap! state inc))
+ :style { :user-select "none", :cursor "pointer" }}
+ "[ ROOT Clicks: " @state " ]"
+ (mf/portal
+ (mf/html [:> portal* {:state state}])
+ (dom/getElement "portal-off-root"))]))
-;; (mx/defcs portals
-;; {:mixins [(mx/local 0 ::*clicks)]}
-;; [{*clicks ::*clicks}]
-;; [:div {:on-click (fn [_] (swap! *clicks inc))
-;; :style { :user-select "none", :cursor "pointer" }}
-;; "[ ROOT Clicks: " @*clicks " ]"
-;; (mx/portal (portal *clicks) (util/el "portal-off-root"))])
+(defonce root
+ (mf/create-root (dom/getElement "portals")))
-
-;; (defn mount! [el]
-;; (mx/mount (portals) el))
+(defn ^:after-load mount! []
+ (mf/render! root (mf/element portals*)))
diff --git a/examples/rumext/examples/refs.cljs b/examples/rumext/examples/refs.cljs
index 5fcec27..8275f00 100644
--- a/examples/rumext/examples/refs.cljs
+++ b/examples/rumext/examples/refs.cljs
@@ -1,6 +1,7 @@
(ns rumext.examples.refs
- (:require [goog.dom :as dom]
- [rumext.alpha :as mf]))
+ (:require
+ [goog.dom :as dom]
+ [rumext.v2 :as mf]))
(mf/defc textarea
[props]
@@ -29,5 +30,8 @@
[:div
[:& textarea]])
-(defn mount! []
- (mf/mount (mf/element refs) (dom/getElement "refs")))
+(defonce root
+ (mf/create-root (dom/getElement "refs")))
+
+(defn ^:after-load mount! []
+ (mf/render! root (mf/element refs)))
diff --git a/examples/rumext/examples/timer_reactive.cljs b/examples/rumext/examples/timer_reactive.cljs
index 1b96c89..ea0c2e8 100644
--- a/examples/rumext/examples/timer_reactive.cljs
+++ b/examples/rumext/examples/timer_reactive.cljs
@@ -1,7 +1,8 @@
(ns rumext.examples.timer-reactive
- (:require [goog.dom :as dom]
- [rumext.alpha :as mf]
- [rumext.examples.util :as util]))
+ (:require
+ [goog.dom :as dom]
+ [rumext.v2 :as mf]
+ [rumext.examples.util :as util]))
(defonce components (atom {}))
@@ -21,13 +22,15 @@
[:span {:style {:color @util/*color}}
(util/format-time ts)]])
-(defn mount! []
- (mf/mount (mf/element timer1)
- (dom/getElement "timer1"))
- (mf/mount (mf/element timer2 {:ts @util/*clock})
- (dom/getElement "timer2"))
+(defonce root1
+ (mf/create-root (dom/getElement "timer1")))
+(defonce root2
+ (mf/create-root (dom/getElement "timer2")))
+
+(defn ^:after-load mount! []
+ (mf/render! root1 (mf/jsx timer1 {}))
+ (mf/render! root2 (mf/jsx timer2 #js {:ts @util/*clock}))
(add-watch util/*clock :timer-static
(fn [_ _ _ ts]
- (mf/mount (mf/element timer2 {:ts ts})
- (dom/getElement "timer2")))))
+ (mf/render! root2 (mf/jsx timer2 #js {:ts ts})))))
diff --git a/examples/rumext/examples/util.cljs b/examples/rumext/examples/util.cljs
index 278b23a..8245ea0 100644
--- a/examples/rumext/examples/util.cljs
+++ b/examples/rumext/examples/util.cljs
@@ -1,6 +1,6 @@
(ns rumext.examples.util
(:require
- [rumext.alpha :as mf]
+ [rumext.v2 :as mf]
[goog.dom :as dom]
[okulary.core :as l]))
@@ -23,14 +23,12 @@
(mf/defc watches-count
[{:keys [iref] :as props}]
(let [state (mf/use-state 0)]
- (mf/use-effect
- (mf/deps iref)
- (fn []
- (let [sem (js/setInterval #(swap! state inc) 1000)]
- #(do
- (js/clearInterval sem)))))
-
- [:span (.-size (.-watches iref))]))
+ (mf/with-effect [iref]
+ (let [sem (js/setInterval #(swap! state inc) 1000)]
+ #(do
+ (js/clearInterval sem))))
+
+ [:span (.-size (.-watches ^js iref))]))
;; Generic board utils
diff --git a/package.json b/package.json
index dc5f3f9..87f6825 100644
--- a/package.json
+++ b/package.json
@@ -3,13 +3,12 @@
"version": "1.0.0",
"description": "Simple and Decomplected UI library based on React.",
"dependencies": {
- "react": "17.0.1",
- "react-dom": "17.0.1",
- "shadow-cljs": "2.11.8"
+ "process": "^0.11.10",
+ "react": "19.1.0",
+ "react-dom": "19.1.0"
},
- "devDependencies": {},
"scripts": {
- "test": "echo \"Error: no test specified\" && exit 1"
+ "watch": "clojure -M:dev:shadow-cljs watch examples"
},
"repository": {
"type": "git",
@@ -20,5 +19,6 @@
"bugs": {
"url": "https://github.com/funcool/rumext/issues"
},
- "homepage": "https://github.com/funcool/rumext#readme"
+ "homepage": "https://github.com/funcool/rumext#readme",
+ "packageManager": "yarn@4.9.1+sha512.f95ce356460e05be48d66401c1ae64ef84d163dd689964962c6888a9810865e39097a5e9de748876c2e0bf89b232d583c33982773e9903ae7a76257270986538"
}
diff --git a/pom.xml b/pom.xml
index c4281b4..ffd483b 100644
--- a/pom.xml
+++ b/pom.xml
@@ -36,12 +36,22 @@
org.clojure
clojure
- 1.10.3
+ 1.11.4
- hicada
- hicada
- 0.1.9
+ cljs-bean
+ cljs-bean
+ 1.9.0
+
+
+ funcool
+ cuerdas
+ 2023.11.09-407
+
+
+ metosin
+ malli
+ 0.16.0
diff --git a/shadow-cljs.edn b/shadow-cljs.edn
index 7ba9f55..b278e97 100644
--- a/shadow-cljs.edn
+++ b/shadow-cljs.edn
@@ -1,5 +1,5 @@
{:deps {:aliases [:dev]}
- :dev-http {9500 "classpath:public"}
+ :dev-http {9500 ["public" "classpath:public"]}
:builds
{:examples
@@ -7,8 +7,15 @@
:output-dir "target/public/js"
:asset-path "/js"
:modules {:main {:entries [rumext.examples.core]}}
- :compiler-options {:output-feature-set :es8}
- :release {:compiler-options {:pseudo-names true
- :pretty-print true}}
+ :compiler-options {:output-feature-set :es-next}
+
+ :js-options
+ {:entry-keys ["module" "browser" "main"]
+ :export-conditions ["module" "import", "browser" "require" "default"]}
+
+ :release
+ {:compiler-options
+ {:pseudo-names false
+ :pretty-print true}}
}}}
diff --git a/src/rumext/alpha.clj b/src/rumext/alpha.clj
deleted file mode 100644
index 742fb08..0000000
--- a/src/rumext/alpha.clj
+++ /dev/null
@@ -1,120 +0,0 @@
-;; This Source Code Form is subject to the terms of the Mozilla Public
-;; License, v. 2.0. If a copy of the MPL was not distributed with this
-;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
-;;
-;; Copyright (c) Andrey Antukh
-
-(ns rumext.alpha
- (:require
- [rumext.compiler :as hc]))
-
-(defmacro html
- [body]
- (hc/compile body))
-
-(defn parse-defc
- [args]
- (loop [r {}
- s 0
- v (first args)
- n (rest args)]
- (case s
- 0 (if (symbol? v)
- (recur (assoc r :cname v) (inc s) (first n) (rest n))
- (recur (assoc r :cname (gensym "anonymous-")) (inc s) v n))
- 1 (if (string? v)
- (recur (assoc r :doc v) (inc s) (first n) (rest n))
- (recur r (inc s) v n))
- 2 (if (map? v)
- (recur (assoc r :metadata v) (inc s) (first n) (rest n))
- (recur r (inc s) v n))
- 3 (if (vector? v)
- (recur (assoc r :args v) (inc s) (first n) (rest n))
- (throw (ex-info "Invalid macro definition: expected component args vector" {})))
- 4 {:cname (:cname r)
- :docs (str (:doc r))
- :arg-props (first (:args r))
- :arg-rest (rest (:args r))
- :body (cons v n)
- :meta (:metadata r)})))
-
-(defn- prepare-render
- [{:keys [cname meta arg-props arg-rest body] :as ctx}]
- (let [argsym (gensym "arg")
- args (cons argsym arg-rest)
- fnbody `(fn ~cname [~@(if arg-props args [])]
- (let [~@(cond
- (and arg-props (::wrap-props meta true))
- [arg-props `(rumext.util/wrap-props ~argsym)]
-
- (some? arg-props)
- [arg-props argsym]
-
- :else [])]
- ~@(butlast body)
- (html ~(last body))))]
-
- (if (::forward-ref meta)
- `(rumext.alpha/forward-ref ~fnbody)
- fnbody)))
-
-(defmacro fnc
- [& args]
- (let [{:keys [cname meta] :as ctx} (parse-defc args)
- wrap-with (or (::wrap meta)
- (:wrap meta))
- rfs (gensym "component")]
- `(let [~rfs ~(prepare-render ctx)]
- (set! (.-displayName ~rfs) ~(str cname))
- ~(if (seq wrap-with)
- (reduce (fn [r fi] `(~fi ~r)) rfs wrap-with)
- rfs))))
-
-(defmacro defc
- [& args]
- (let [{:keys [cname docs meta] :as ctx} (parse-defc args)
- wrap-with (or (::wrap meta)
- (:wrap meta))
- rfs (gensym "component")]
- `(let [~rfs ~(prepare-render ctx)]
- (set! (.-displayName ~rfs) ~(str cname))
- (def ~cname ~docs ~(if (seq wrap-with)
- (reduce (fn [r fi] `(~fi ~r)) rfs wrap-with)
- rfs))
- ~(when-let [registry (::register meta)]
- `(swap! ~registry (fn [state#] (assoc state# ~(::register-as meta (keyword (str cname))) ~cname)))))))
-
-(defmacro with-memo
- [deps & body]
- (cond
- (vector? deps)
- `(rumext.alpha/use-memo
- (rumext.alpha/deps ~@deps)
- (fn [] ~@body))
-
-
- (nil? deps)
- `(rumext.alpha/use-memo
- nil
- (fn [] ~@body))
-
- :else
- `(rumext.alpha/use-memo
- (fn [] ~@(cons deps body)))))
-
-(defmacro with-effect
- [deps & body]
- (cond
- (vector? deps)
- `(rumext.alpha/use-effect
- (rumext.alpha/deps ~@deps)
- (fn [] ~@body))
-
- (nil? deps)
- `(rumext.alpha/use-effect
- nil
- (fn [] ~@body))
-
- :else
- `(rumext.alpha/use-effect
- (fn [] ~@(cons deps body)))))
diff --git a/src/rumext/alpha.cljs b/src/rumext/alpha.cljs
deleted file mode 100644
index 910f78c..0000000
--- a/src/rumext/alpha.cljs
+++ /dev/null
@@ -1,370 +0,0 @@
-;; This Source Code Form is subject to the terms of the Mozilla Public
-;; License, v. 2.0. If a copy of the MPL was not distributed with this
-;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
-;;
-;; Copyright (c) Andrey Antukh
-
-(ns rumext.alpha
- (:refer-clojure :exclude [ref deref])
- (:require-macros [rumext.alpha :refer [defc fnc]])
- (:require
- ["react" :as react]
- ["react-dom" :as rdom]
- ["react/jsx-runtime" :as jsxrt]
- [cljs.core :as c]
- [goog.functions :as gf]
- [rumext.util :as util]))
-
-(def ^:const undefined (js* "(void 0)"))
-
-(def Component react/Component)
-(def Fragment react/Fragment)
-(def Profiler react/Profiler)
-
-(extend-type cljs.core.UUID
- INamed
- (-name [this] (js* "\"\" + ~{}" this))
- (-namespace [_] ""))
-
-(defn jsx
- ([type props maybe-key]
- (jsxrt/jsx type props maybe-key))
- ([type props maybe-key children]
- (let [props (js/Object.assign #js {:children children} props)]
- (jsxrt/jsx type props maybe-key))))
-
-(defn jsxs
- ([type props maybe-key]
- (jsxrt/jsxs type props maybe-key))
- ([type props maybe-key children]
- (let [props (js/Object.assign #js {:children children} props)]
- (jsxrt/jsxs type props maybe-key))))
-
-(defn forward-ref
- [component]
- (react/forwardRef component))
-
-;; --- Main Api
-
-(defn mount
- "Add element to the DOM tree. Idempotent. Subsequent mounts will
- just update element."
- [element node]
- (rdom/render element node)
- nil)
-
-(defn unmount
- "Removes component from the DOM tree."
- [node]
- (rdom/unmountComponentAtNode node))
-
-(defn portal
- "Render `element` in a DOM `node` that is ouside of current DOM hierarchy."
- [element node]
- (rdom/createPortal element node))
-
-(defn create-ref
- []
- (react/createRef))
-
-(defn ref-val
- "Given state and ref handle, returns React component."
- [ref]
- (unchecked-get ref "current"))
-
-(defn set-ref-val!
- [ref val]
- (unchecked-set ref "current" val)
- val)
-
-;; --- Context API
-
-(defn create-context
- ([]
- (react/createContext nil))
- ([value]
- (react/createContext value)))
-
-(defn provider
- [ctx]
- (unchecked-get ctx "Provider"))
-
-;; --- Raw Hooks
-
-(defn useRef
- [initial]
- (react/useRef initial))
-
-(defn useState
- [initial]
- (react/useState initial))
-
-(defn useEffect
- [f deps]
- (react/useEffect f deps))
-
-(defn useMemo
- [f deps]
- (react/useMemo f deps))
-
-(defn useCallback
- [f deps]
- (react/useCallback f deps))
-
-(defn useLayoutEffect
- [f deps]
- (react/useLayoutEffect f deps))
-
-(defn useContext
- [ctx]
- (react/useContext ctx))
-
-;; --- Hooks
-
-(defprotocol IDepsAdapter
- (adapt [o] "adapt dep if proceed"))
-
-(extend-protocol IDepsAdapter
- default
- (adapt [o] o)
-
- cljs.core.UUID
- (adapt [o] (.toString ^js o))
-
- cljs.core.Keyword
- (adapt [o] (.toString ^js o)))
-
-;; "A convenience function that translates the list of arguments into a
-;; valid js array for use in the deps list of hooks.
-
-(defn deps
- ([] #js [])
- ([a] #js [(adapt a)])
- ([a b] #js [(adapt a) (adapt b)])
- ([a b c] #js [(adapt a) (adapt b) (adapt c)])
- ([a b c d] #js [(adapt a) (adapt b) (adapt c) (adapt d)])
- ([a b c d e] #js [(adapt a) (adapt b) (adapt c) (adapt d) (adapt e)])
- ([a b c d e f] #js [(adapt a) (adapt b) (adapt c) (adapt d) (adapt e) (adapt f)])
- ([a b c d e f g] #js [(adapt a) (adapt b) (adapt c) (adapt d) (adapt e) (adapt f) (adapt g)])
- ([a b c d e f g h] #js [(adapt a) (adapt b) (adapt c) (adapt d) (adapt e) (adapt f) (adapt g) (adapt h)])
- ([a b c d e f g h & rest] (into-array (map adapt (into [a b c d e f g h] rest)))))
-
-;; The cljs version of use-ref and use-ctx is identical to the raw (no
-;; customizations/adaptations needed)
-
-(def use-ref useRef)
-(def use-ctx useContext)
-
-(defn use-effect
- ([f] (use-effect #js [] f))
- ([deps f]
- (useEffect #(let [r (^function f)] (if (fn? r) r identity)) deps)))
-
-(defn use-layout-effect
- ([f] (use-layout-effect #js [] f))
- ([deps f]
- (useLayoutEffect #(let [r (^function f)] (if (fn? r) r identity)) deps)))
-
-(defn use-memo
- ([f] (useMemo f #js []))
- ([deps f] (useMemo f deps)))
-
-(defn use-callback
- ([f] (useCallback f #js []))
- ([deps f] (useCallback f deps)))
-
-(defn use-fn
- "A convenient alias to useCallback"
- ([f] (useCallback f #js []))
- ([deps f] (useCallback f deps)))
-
-(defn deref
- [iref]
- (let [tmp (useState #(c/deref iref))
- state (aget tmp 0)
- set-state (aget tmp 1)
- key (useMemo
- #(let [key (js/Symbol "rumext.alpha/deref")]
- (add-watch iref key (fn [_ _ _ newv]
- (^function set-state newv)))
- key)
- #js [iref])]
-
- (useEffect
- #(do
- (^function set-state (c/deref iref))
- (fn []
- (remove-watch iref key)))
- #js [iref key])
-
- state))
-
-(defn use-state
- ([] (use-state nil))
- ([initial]
- (let [tmp (useState initial)
- state (aget tmp 0)
- update (aget tmp 1)]
- (use-memo
- #js [state]
- (fn []
- (reify
- c/IReset
- (-reset! [_ value]
- (^function update value))
-
- c/ISwap
- (-swap! [self f]
- (^function update f))
- (-swap! [self f x]
- (^function update #(f % x)))
- (-swap! [self f x y]
- (^function update #(f % x y)))
- (-swap! [self f x y more]
- (^function update #(apply f % x y more)))
-
- c/IDeref
- (-deref [_] state)))))))
-
-(defn use-var
- "A custom hook for define mutable variables that persists
- on renders (based on useRef hook)."
- ([] (use-var nil))
- ([initial]
- (let [ref (useRef initial)]
- (use-memo
- #js []
- #(specify! (fn [val] (set-ref-val! ref val))
- c/IReset
- (-reset! [_ new-value]
- (set-ref-val! ref new-value))
-
- c/ISwap
- (-swap!
- ([self f]
- (set-ref-val! ref (f (ref-val ref))))
- ([self f x]
- (set-ref-val! ref (f (ref-val ref) x)))
- ([self f x y]
- (set-ref-val! ref (f (ref-val ref) x y)))
- ([self f x y more]
- (set-ref-val! ref (apply f (ref-val ref) x y more))))
-
- c/IDeref
- (-deref [self] (ref-val ref)))))))
-
-;; --- Other API
-
-(defn element
- ([klass]
- (jsx klass #js {} undefined))
- ([klass props]
- (let [props (cond
- (object? props) props
- (map? props) (util/map->obj props)
- :else (throw (ex-info "Unexpected props" {:props props})))]
- (jsx klass props undefined))))
-
-;; --- Higher-Order Components
-
-(defn memo'
- "A raw variant of React.memo."
- [component equals?]
- (react/memo component equals?))
-
-(defn memo
- ([component] (react/memo component))
- ([component eq?]
- (react/memo component #(util/props-equals? eq? %1 %2))))
-
-(defn catch
- [component {:keys [fallback on-error]}]
- (let [constructor
- (fn [props]
- (this-as this
- (unchecked-set this "state" #js {})
- (.call Component this props)))
-
- did-catch
- (fn [error info]
- (when (fn? on-error)
- (on-error error info)))
-
- derive-state
- (fn [error]
- #js {:error error})
-
- render
- (fn []
- (this-as this
- (let [state (unchecked-get this "state")
- props (unchecked-get this "props")
- error (unchecked-get state "error")]
- (if error
- (jsx fallback #js {:error error} undefined)
- (jsx component props undefined)))))
-
- _ (goog/inherits constructor Component)
- prototype (unchecked-get constructor "prototype")]
-
- (unchecked-set constructor "displayName" "ErrorBoundary")
- (unchecked-set constructor "getDerivedStateFromError" derive-state)
- (unchecked-set prototype "componentDidCatch" did-catch)
- (unchecked-set prototype "render" render)
- constructor))
-
-(def ^:private schedule
- (or (and (exists? js/window) js/window.requestAnimationFrame)
- #(js/setTimeout % 16)))
-
-(defn deferred
- ([component] (deferred component schedule))
- ([component sfn]
- (fnc deferred
- {::wrap-props false}
- [props]
- (let [tmp (useState false)
- render? (aget tmp 0)
- set-render (aget tmp 1)]
- (use-effect (fn [] (^function sfn #(^function set-render true))))
- (when ^boolean render?
- [:> component props])))))
-
-(defn throttle
- [component ms]
- (fnc throttle
- {::wrap-props false}
- [props]
- (let [tmp (useState props)
- state (aget tmp 0)
- set-state (aget tmp 1)
-
- ref (useRef false)
- render (useMemo
- #(gf/throttle
- (fn [v]
- (when-not ^boolean (ref-val ref)
- (^function set-state v)))
- ms)
- #js [])]
- (useEffect #(^function render props) #js [props])
- (useEffect #(fn [] (set-ref-val! ref true)) #js [])
- [:> component state])))
-
-(defn check-props
- "Utility function to use with `memo'`.
- Will check the `props` keys to see if they are equal.
-
- Usage:
-
- (mf/defc my-component
- {::mf/wrap [#(mf/memo' % (checkprops [\"prop1\" \"prop2\"]))]}
- [props]
- )"
-
- ([props] (check-props props =))
- ([props eqfn?]
- (fn [np op]
- (every? #(eqfn? (unchecked-get np %)
- (unchecked-get op %))
- props))))
diff --git a/src/rumext/compiler.cljc b/src/rumext/compiler.cljc
deleted file mode 100644
index f7bf3a8..0000000
--- a/src/rumext/compiler.cljc
+++ /dev/null
@@ -1,265 +0,0 @@
-(ns rumext.compiler
- "
- Hicada - Hiccup compiler aus dem Allgaeu
-
- NOTE: The code for has been forked like this:
- weavejester/hiccup -> r0man/sablono -> Hicada -> rumext"
- (:refer-clojure :exclude [compile])
- (:require
- [rumext.normalize :as norm]
- [rumext.util :as util]))
-
-(def ^:dynamic *handlers* nil)
-
-(def default-handlers
- {:> (fn [_ klass attrs & children]
- [klass attrs children])
- :& (fn
- ([_ klass]
- (let [klass klass]
- [klass {} nil]))
- ([_ klass props & children]
- (let [klass klass]
- (if (map? props)
- [klass (rumext.util/compile-map->object props) children]
- [klass (list 'rumext.util/map->obj props) children]))))
- :* (fn [_ attrs & children]
- (if (map? attrs)
- ['rumext.alpha/Fragment attrs children]
- ['rumext.alpha/Fragment {} (cons attrs children)]))})
-
-(declare emit-react)
-
-(defn- compile-class-attr
- [value]
- (cond
- (or (nil? value)
- (keyword? value)
- (string? value))
- value
-
- (and (or (sequential? value)
- (set? value))
- (every? string? value))
- (util/join-classes value)
-
- (vector? value)
- (apply util/compile-join-classes value)
-
- :else value))
-
-(defn compile-attr
- [[key val :as kvpair]]
- (cond
- (= key :class) [:className (compile-class-attr val)]
- (= key :style) [key (util/camel-case-keys val)]
- (= key :for) [:htmlFor val]
- (or (keyword? key)
- (symbol? key)) [(util/camel-case key) val]
- :else kvpair))
-
-(declare compile*)
-
-(defmulti compile-form
- "Pre-compile certain standard forms, where possible."
- (fn [form]
- (when (and (seq? form) (symbol? (first form)))
- (name (first form)))))
-
-(defmethod compile-form "do"
- [[_ & forms]]
- `(do ~@(butlast forms) ~(compile* (last forms))))
-
-(defmethod compile-form "array"
- [[_ & forms]]
- `(cljs.core/array ~@(mapv compile* forms)))
-
-(defmethod compile-form "let"
- [[_ bindings & body]]
- `(let ~bindings ~@(butlast body) ~(compile* (last body))))
-
-(defmethod compile-form "let*"
- [[_ bindings & body]]
- `(let* ~bindings ~@(butlast body) ~(compile* (last body))))
-
-(defmethod compile-form "letfn*"
- [[_ bindings & body]]
- `(letfn* ~bindings ~@(butlast body) ~(compile* (last body))))
-
-(defmethod compile-form "for"
- [[_ bindings body]]
- ;; Special optimization: For a simple (for [x xs] ...) we rewrite the for
- ;; to a fast reduce outputting a JS array:
- (if (== 2 (count bindings))
- (let [[item coll] bindings]
- `(reduce (fn [out-arr# ~item]
- (.push out-arr# ~(compile* body))
- out-arr#)
- (cljs.core/array) ~coll))
- ;; Still optimize a little by giving React an array:
- (list 'cljs.core/into-array `(for ~bindings ~(compile* body)))))
-
-(defmethod compile-form "if"
- [[_ condition & body]]
- `(if ~condition ~@(doall (for [x body] (compile* x)))))
-
-(defmethod compile-form "when"
- [[_ bindings & body]]
- `(when ~bindings ~@(doall (for [x body] (compile* x)))))
-
-(defmethod compile-form "when-some"
- [[_ bindings & body]]
- `(when-some ~bindings ~@(butlast body) ~(compile* (last body))))
-
-(defmethod compile-form "when-let"
- [[_ bindings & body]]
- `(when-let ~bindings ~@(butlast body) ~(compile* (last body))))
-
-(defmethod compile-form "when-first"
- [[_ bindings & body]]
- `(when-first ~bindings ~@(butlast body) ~(compile* (last body))))
-
-(defmethod compile-form "when-not"
- [[_ bindings & body]]
- `(when-not ~bindings ~@(doall (for [x body] (compile* x)))))
-
-(defmethod compile-form "if-not"
- [[_ bindings & body]]
- `(if-not ~bindings ~@(doall (for [x body] (compile* x)))))
-
-(defmethod compile-form "if-some"
- [[_ bindings & body]]
- `(if-some ~bindings ~@(doall (for [x body] (compile* x)))))
-
-(defmethod compile-form "if-let"
- [[_ bindings & body]]
- `(if-let ~bindings ~@(doall (for [x body] (compile* x)))))
-
-(defmethod compile-form "letfn"
- [[_ bindings & body]]
- `(letfn ~bindings ~@(butlast body) ~(compile* (last body))))
-
-(defmethod compile-form "fn"
- [[_ params & body]]
- `(fn ~params ~@(butlast body) ~(compile* (last body))))
-
-(defmethod compile-form "case"
- [[_ v & cases]]
- `(case ~v
- ~@(doall (mapcat
- (fn [[test hiccup]]
- (if hiccup
- [test (compile* hiccup)]
- [(compile* test)]))
- (partition-all 2 cases)))))
-
-(defmethod compile-form "condp"
- [[_ f v & cases]]
- `(condp ~f ~v
- ~@(doall (mapcat
- (fn [[test hiccup]]
- (if hiccup
- [test (compile* hiccup)]
- [(compile* test)]))
- (partition-all 2 cases)))))
-
-(defmethod compile-form "cond"
- [[_ & clauses]]
- `(cond ~@(doall
- (mapcat
- (fn [[check expr]] [check (compile* expr)])
- (partition 2 clauses)))))
-
-(defmethod compile-form :default [expr] expr)
-
-(defn compile-element
- "Returns an unevaluated form that will render the supplied vector as a HTML element."
- [[tag attrs & children :as element]]
- (cond
- ;; e.g. [:> Component {:key "xyz", :foo "bar} ch0 ch1]
- (contains? *handlers* tag)
- (let [f (get *handlers* tag)
- [klass attrs children] (apply f element)]
- (emit-react klass attrs (mapv compile* children)))
-
- ;; e.g. [:span {} x]
- (and (util/literal? tag) (map? attrs))
- (let [[tag attrs _] (norm/element [tag attrs])]
- (emit-react tag attrs (mapv compile* children)))
-
- (util/literal? tag)
- ;; We could now interpet this as either:
- ;; 1. First argument is the attributes (in #js{} provided by the user) OR:
- ;; 2. First argument is the first child element.
- ;; We assume #2. Always!
- (compile-element (list* tag {} attrs children))
-
- ;; Problem: [a b c] could be interpreted as:
- ;; 1. The coll of ReactNodes [a b c] OR
- ;; 2. a is a React Element, b are the props and c is the first child
- ;; We default to 1) (handled below) BUT, if b is a map, we know this must be 2)
- ;; since a map doesn't make any sense as a ReactNode.
- ;; [foo {...} ch0 ch1] NEVER makes sense to interpret as a sequence
- (and (vector? element) (map? attrs))
- (emit-react tag attrs (mapv compile* children))
-
- (seq? element)
- (seq (mapv compile* element))
-
- ;; We have nested children
- ;; [[:div "foo"] [:span "foo"]]
- :else
- (mapv compile* element)))
-
-(defn compile*
- "Pre-compile data structures"
- [content]
- (cond
- (vector? content) (compile-element content)
- (util/literal? content) content
- :else (compile-form content)))
-
-(defn tag->el
- [x]
- (assert (or (symbol? x) (keyword? x) (string? x) (seq? x))
- (str "Got: " (#?(:clj class :cljs type) x)))
- (if (keyword? x)
- (name x)
- x))
-
-(def props-xform
- (comp
- (remove (fn [[k v]] (= k :key)))
- (map compile-attr)))
-
-(defn emit-react
- "Emits the final react js code"
- [tag attrs children]
- (let [tag (tag->el tag)
- children (into [] (filter some?) children)
- [key props] (if (map? attrs)
- [(or (:key attrs)
- 'rumext.alpha/undefined)
- (->> (into {} props-xform attrs)
- (util/compile-to-js))]
- ['rumext.alpha/undefined attrs])]
- (cond
- (= 0 (count children))
- (list 'rumext.alpha/jsx tag props key)
-
- (= 1 (count children))
- (list 'rumext.alpha/jsx tag props key (first children))
-
- :else
- (list 'rumext.alpha/jsxs tag props key (apply list 'cljs.core/array children)))))
-
-(defn compile
- "Arguments:
- - content: The hiccup to compile
- - handlers: A map to handle special tags. See default-handlers in this namespace.
- "
- ([content]
- (compile content nil))
- ([content handlers]
- (binding [*handlers* (merge default-handlers handlers)]
- (compile* content))))
diff --git a/src/rumext/util.cljc b/src/rumext/util.cljc
deleted file mode 100644
index adba4f6..0000000
--- a/src/rumext/util.cljc
+++ /dev/null
@@ -1,186 +0,0 @@
-;; This Source Code Form is subject to the terms of the Mozilla Public
-;; License, v. 2.0. If a copy of the MPL was not distributed with this
-;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
-;;
-;; Copyright (c) 2016-2020 Andrey Antukh
-
-(ns rumext.util
- (:require
- [clojure.string :as str]
- [clojure.set :as set]))
-
-(defn compile-to-js
- [form]
- "Compile a statically known data sturcture, recursivelly to js
- expression. Mainly used by macros for create js data structures at
- compile time."
- (cond
- (map? form)
- (if (empty? form)
- (list 'js* "{}")
- (let [key-strs (mapv compile-to-js (keys form))
- non-str (remove string? key-strs)
- _ (assert (empty? non-str)
- (str "Rumext: Props can't be dynamic:"
- (pr-str non-str) "in: " (pr-str form)))
- kvs-str (->> (mapv #(-> (str \' % "':~{}")) key-strs)
- (interpose ",")
- (apply str))]
- (vary-meta
- (list* 'js* (str "{" kvs-str "}") (mapv compile-to-js (vals form)))
- assoc :tag 'object)))
-
- (vector? form)
- (apply list 'cljs.core/array (mapv compile-to-js form))
-
- (keyword? form)
- (name form)
-
- :else form))
-
-(defn compile-map->object
- "Compile a statically known clojure map to js object
- expression. Mainly used by macros for create js objects at
- compile time (component props)."
- [m]
- (if (empty? m)
- (list 'js* "{}")
- (let [key-strs (mapv compile-to-js (keys m))
- non-str (remove string? key-strs)
- _ (assert (empty? non-str)
- (str "Rumext: Props can't be dynamic:"
- (pr-str non-str) "in: " (pr-str m)))
- kvs-str (->> (mapv #(-> (str \' % "':~{}")) key-strs)
- (interpose ",")
- (apply str))]
- (vary-meta
- (list* 'js* (str "{" kvs-str "}") (mapv identity (vals m)))
- assoc :tag 'object))))
-
-#?(:cljs
- (defn obj->map
- "Convert shallowly an js object to cljs map."
- [obj]
- (let [keys (.keys js/Object obj)
- len (alength keys)]
- (loop [i 0
- r (transient {})]
- (if (< i len)
- (let [key (aget keys i)]
- (recur (unchecked-inc i)
- (assoc! r (keyword key) (unchecked-get obj key))))
- (persistent! r))))))
-
-#?(:cljs
- (defn map->obj
- [o]
- (let [m #js {}]
- (run! (fn [[k v]] (unchecked-set m (name k) v)) o)
- m)))
-
-#?(:cljs
- (defn wrap-props
- [props]
- (cond
- (object? props) (obj->map props)
- (map? props) props
- (nil? props) {}
- :else (throw (ex-info "Unexpected props" {:props props})))))
-
-#?(:cljs
- (defn props-equals?
- [eq? new-props old-props]
- (let [old-keys (.keys js/Object old-props)
- new-keys (.keys js/Object new-props)
- old-keys-len (alength old-keys)
- new-keys-len (alength new-keys)]
- (if (identical? old-keys-len new-keys-len)
- (loop [idx (int 0)]
- (if (< idx new-keys-len)
- (let [key (aget new-keys idx)
- new-val (unchecked-get new-props key)
- old-val (unchecked-get old-props key)]
- (if ^boolean (eq? new-val old-val)
- (recur (inc idx))
- false))
- true))
- false))))
-
-#?(:cljs
- (defn symbol-for
- [v]
- (.for js/Symbol v)))
-
-(defn camel-case
- "Returns camel case version of the key, e.g. :http-equiv becomes :httpEquiv."
- [k]
- (if (or (keyword? k)
- (string? k)
- (symbol? k))
- (let [[first-word & words] (str/split (name k) #"-")]
- (if (or (empty? words)
- (= "aria" first-word)
- (= "data" first-word))
- k
- (-> (map str/capitalize words)
- (conj first-word)
- str/join
- keyword)))
- k))
-
-(defn camel-case-keys
- "Recursively transforms all map keys into camel case."
- [m]
- (cond
- (map? m)
- (reduce-kv
- (fn [m k v]
- (assoc m (camel-case k) v))
- {} m)
- ;; React native accepts :style [{:foo-bar ..} other-styles] so camcase those keys:
- (vector? m)
- (mapv camel-case-keys m)
- :else
- m))
-
-(defn element?
- "- is x a vector?
- AND
- - first element is a keyword?"
- [x]
- (and (vector? x) (keyword? (first x))))
-
-(defn unevaluated?
- "True if the expression has not been evaluated.
- - expr is a symbol? OR
- - it's something like (foo bar)"
- [expr]
- (or (symbol? expr)
- (and (seq? expr)
- (not= (first expr) `quote))))
-
-(defn literal?
- "True if x is a literal value that can be rendered as-is."
- [x]
- (and (not (unevaluated? x))
- (or (not (or (vector? x) (map? x)))
- (and (every? literal? x)
- (not (keyword? (first x)))))))
-
-(defn join-classes
- "Join the `classes` with a whitespace."
- [classes]
- (->> (map #(if (string? %) % (seq %)) classes)
- (flatten)
- (remove nil?)
- (str/join " ")))
-
-(defn compile-join-classes
- "Joins strings space separated"
- ([] "")
- ([& xs]
- (let [strs (->> (repeat (count xs) "~{}")
- (interpose ",")
- (apply str))]
- (list* 'js* (str "[" strs "].join(' ')") xs))))
-
diff --git a/src/rumext/v2.clj b/src/rumext/v2.clj
new file mode 100644
index 0000000..e3e9d34
--- /dev/null
+++ b/src/rumext/v2.clj
@@ -0,0 +1,499 @@
+;; This Source Code Form is subject to the terms of the Mozilla Public
+;; License, v. 2.0. If a copy of the MPL was not distributed with this
+;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
+;;
+;; Copyright (c) Andrey Antukh
+
+(ns rumext.v2
+ (:refer-clojure :exclude [simple-ident?])
+ (:require
+ [cljs.core :as-alias c]
+ [clojure.string :as str]
+ [rumext.v2.compiler :as hc]
+ [rumext.v2.util :as util]))
+
+(create-ns 'rumext.v2.util)
+
+(defn ^:no-doc production-build?
+ []
+ (let [env (System/getenv)]
+ (or (= "production" (get env "NODE_ENV"))
+ (= "production" (get env "RUMEXT_ENV"))
+ (= "production" (get env "TARGET_ENV")))))
+
+(defmacro html
+ [body]
+ (hc/compile body))
+
+(defn parse-defc
+ [args]
+ (loop [r {}
+ s 0
+ v (first args)
+ n (rest args)]
+ (case s
+ 0 (if (symbol? v)
+ (recur (assoc r :cname v) (inc s) (first n) (rest n))
+ (recur (assoc r :cname (gensym "anonymous-")) (inc s) v n))
+ 1 (if (string? v)
+ (recur (assoc r :doc v) (inc s) (first n) (rest n))
+ (recur r (inc s) v n))
+ 2 (if (map? v)
+ (recur (assoc r :meta v) (inc s) (first n) (rest n))
+ (recur r (inc s) v n))
+ 3 (if (vector? v)
+ (recur (assoc r :args v) (inc s) (first n) (rest n))
+ (throw (ex-info "Invalid macro definition: expected component args vector" {})))
+
+ (let [psym (with-meta (gensym "props-") {:tag 'js})]
+ {:cname (:cname r)
+ :docs (str (:doc r))
+ :props (first (:args r))
+ :params (into [psym] (rest (:args r)))
+ :body (cons v n)
+ :psym psym
+ :meta (:meta r)}))))
+
+(defn- wrap-props?
+ [{:keys [cname meta]}]
+ (let [default-style (if (str/ends-with? (name cname) "*") :obj :clj)]
+ (cond
+ (contains? meta ::props)
+ (= :clj (get meta ::props default-style))
+
+ (contains? meta ::wrap-props)
+ (get meta ::wrap-props)
+
+ (str/ends-with? (name cname) "*")
+ false
+
+ :else
+ true)))
+
+(defn- react-props?
+ [{:keys [meta cname] :as ctx}]
+ (and (not (wrap-props? ctx))
+ (or (str/ends-with? (name cname) "*")
+ (= (::props meta) :react))))
+
+(defn- simple-ident?
+ [s]
+ (some? (re-matches #"[A-Za-z0-9_]+" s)))
+
+(defn- prepare-let-bindings
+ [{:keys [cname meta props body params] :as ctx}]
+ (let [react-props? (react-props? ctx)
+ psym (first params)]
+ (cond
+ (and (some? props) (wrap-props? ctx))
+ [props (list 'rumext.v2.util/wrap-props psym)]
+
+ (and (map? props) (not (wrap-props? ctx)))
+ (let [alias (get props :as)
+ alts (get props :or)
+ other (or (get props :&)
+ (get props :rest))
+ items (some-> (get props :keys) set)]
+ (cond->> []
+ (symbol? alias)
+ (into [alias psym])
+
+ (symbol? other)
+ (into [other (list 'js* "undefined")])
+
+ (set? items)
+ (concat (mapcat (fn [k]
+ (let [prop-name (if react-props?
+ (util/ident->prop k)
+ (name k))
+ accessor (if (simple-ident? prop-name)
+ (list '. psym (symbol (str "-" prop-name)))
+ (list 'cljs.core/unchecked-get psym prop-name))]
+
+ [(if (symbol? k) k (symbol prop-name))
+ (cond
+ ;; If the other symbol is present, then a
+ ;; different destructuring stragegy will be
+ ;; used so we need to set here the value to
+ ;; 'undefined'
+ (symbol? other)
+ (list 'js* "undefined")
+
+ (contains? alts k)
+ `(~'js* "~{} ?? ~{}" ~accessor ~(get alts k))
+
+ :else
+ accessor)]))
+ items))))
+
+ (symbol? props)
+ [props psym])))
+
+(defn native-destructure
+ "Generates a js var line with native destructuring. Only used when :&
+ used in destructuring."
+ [{:keys [props params props] :as ctx}]
+
+ ;; Emit native destructuring only if the :& key has value
+ (when (or (symbol? (:& props))
+ (symbol? (:rest props)))
+
+ (let [react-props? (react-props? ctx)
+ psym (first params)
+
+ keys-props (:keys props [])
+ all-alias (:as props)
+ rst-alias (or (:& props) (:rest props))
+
+ k-props (dissoc props :keys :as :& :rest)
+ k-props (->> (:keys props [])
+ (map (fn [k]
+ (let [kv (if react-props?
+ (util/ident->prop k)
+ (name k))]
+ [k kv])))
+ (into k-props))
+
+ props []
+ params []
+
+ [props params]
+ (if (seq k-props)
+ (reduce (fn [[props params] [ks kp]]
+ (let [kp (if react-props?
+ (util/ident->prop kp)
+ (name kp))]
+ [(conj props (str "~{}: ~{}"))
+ (conj params kp ks)]))
+ [props params]
+ k-props)
+ [props params])
+
+ [props params]
+ (if (symbol? rst-alias)
+ [(conj props "...~{}") (conj params rst-alias)]
+ [props params])
+
+ tmpl (str "var {"
+ (str/join ", " props)
+ "} = ~{}")
+ params (conj params psym)]
+
+ [(apply list 'js* tmpl params)])))
+
+(defn- prepare-props-checks
+ [{:keys [cname meta params] :as ctx}]
+ (let [react-props? (react-props? ctx)
+ psym (vary-meta (first params) assoc :tag 'js)]
+ (when *assert*
+ (cond
+ (::schema meta)
+ (let [validator-sym (with-meta (symbol (str cname "-validator"))
+ {:tag 'function})]
+ (concat
+ (cons (list 'js* "// ===== start props checking =====") nil)
+ [`(let [res# (~validator-sym ~psym)]
+ (when (some? res#)
+ (let [items# (reduce-kv (fn [result# k# v#]
+ (conj result# (str " -> '" k# "' " v# "")))
+ []
+ res#)
+ msg# (str ~(str "invalid props on component " (str cname) "\n\n")
+ (str/join "\n" items#)
+ "\n")]
+ (throw (js/Error. msg#)))))]
+ (cons (list 'js* "// ===== end props checking =====") nil)))
+
+ (::expect meta)
+ (let [props (::expect meta)]
+ (concat
+ (cons (list 'js* "// ===== start props checking =====") nil)
+ (if (map? props)
+ (->> props
+ (map (fn [[prop pred-sym]]
+ (let [prop (if react-props?
+ (util/ident->prop prop)
+ (name prop))
+
+ accs (if (simple-ident? prop)
+ (list '. psym (symbol (str "-" prop)))
+ (list 'cljs.core/unchecked-get psym prop))
+
+ expr `(~pred-sym ~accs)]
+ `(when-not ~(vary-meta expr assoc :tag 'boolean)
+ (throw (js/Error. ~(str "invalid value for '" prop "'"))))))))
+
+ (->> props
+ (map (fn [prop]
+ (let [prop (if react-props?
+ (util/ident->prop prop)
+ (name prop))
+ expr `(.hasOwnProperty ~psym ~prop)]
+ `(when-not ~(vary-meta expr assoc :tag 'boolean)
+ (throw (js/Error. ~(str "missing prop '" prop "'")))))))))
+ (cons (list 'js* "// ===== end props checking =====") nil)))
+
+ :else
+ []))))
+
+(defn- prepare-render-fn
+ [{:keys [cname meta body params props] :as ctx}]
+ (let [f `(fn ~cname ~params
+ ~@(prepare-props-checks ctx)
+ (let [~@(prepare-let-bindings ctx)]
+ ~@(native-destructure ctx)
+
+ ~@(butlast body)
+ ~(hc/compile (last body))))]
+ (if (::forward-ref meta)
+ `(rumext.v2/forward-ref ~f)
+ f)))
+
+(defn- resolve-wrappers
+ [{:keys [cname docs meta] :as ctx}]
+ (let [wrappers (or (::wrap meta) (:wrap meta) [])
+ react-props? (react-props? ctx)
+ memo (::memo meta)]
+ (cond
+ (set? memo)
+ (let [eq-f (or (::memo-equals ctx) 'cljs.core/=)
+ np-s (with-meta (gensym "new-props-") {:tag 'js})
+ op-s (with-meta (gensym "old-props-") {:tag 'js})
+ op-f (fn [prop]
+ (let [prop (if react-props?
+ (util/ident->prop prop)
+ (name prop))
+ accs (if (simple-ident? prop)
+ (let [prop (symbol (str "-" (name prop)))]
+ (list eq-f
+ (list '.. np-s prop)
+ (list '.. op-s prop)))
+ (list eq-f
+ (list 'cljs.core/unchecked-get np-s prop)
+ (list 'cljs.core/unchecked-get op-s prop)))]
+ (with-meta accs {:tag 'boolean})))]
+ (conj wrappers
+ `(fn [component#]
+ (mf/memo' component# (fn [~np-s ~op-s]
+ (and ~@(map op-f memo)))))))
+
+ (true? memo)
+ (if-let [eq-f (::memo-equals meta)]
+ (conj wrappers `(fn [component#]
+ (mf/memo component# ~eq-f)))
+ (conj wrappers 'rumext.v2/memo'))
+
+ :else wrappers)))
+
+(defmacro fnc
+ "A macro for defining inline component functions. Look the user guide for
+ understand how to use it."
+ [& args]
+ (let [{:keys [cname meta] :as ctx} (parse-defc args)
+ wrappers (resolve-wrappers ctx)
+ rfs (gensym (str cname "__"))]
+ `(let [~rfs ~(if (seq wrappers)
+ (reduce (fn [r fi] `(~fi ~r)) (prepare-render-fn ctx) wrappers)
+ (prepare-render-fn ctx))]
+ ~@(when-not (production-build?)
+ [`(set! (.-displayName ~rfs) ~(str cname))])
+ ~rfs)))
+
+(defmacro defc
+ "A macro for defining component functions. Look the user guide for
+ understand how to use it."
+ [& args]
+ (let [{:keys [cname docs meta] :as ctx} (parse-defc args)
+ wrappers (resolve-wrappers ctx)
+ react-props? (react-props? ctx)
+ cname (if (::private meta)
+ (vary-meta cname assoc :private true)
+ cname)]
+ `(do
+ ~@(when (and (::schema meta) react-props? *assert*)
+ (let [validator-sym (with-meta (symbol (str cname "-validator"))
+ {:tag 'function})]
+ [`(def ~validator-sym (rumext.v2.validation/validator ~(::schema meta)))]))
+
+ (def ~cname ~docs ~(if (seq wrappers)
+ (reduce (fn [r fi] `(~fi ~r)) (prepare-render-fn ctx) wrappers)
+ (prepare-render-fn ctx)))
+
+ ~@(when-not (production-build?)
+ [`(set! (.-displayName ~cname) ~(str cname))])
+
+ ~(when-let [registry (::register meta)]
+ `(swap! ~registry (fn [state#] (assoc state# ~(::register-as meta (keyword (str cname))) ~cname)))))))
+
+(defmacro deps
+ "A convenience macro version of mf/deps function"
+ [& params]
+ `(cljs.core/array ~@(map (fn [s] `(rumext.v2/adapt ~s)) params)))
+
+(defmacro with-memo
+ "A convenience syntactic abstraction (macro) for `useMemo`"
+ [deps & body]
+ (cond
+ (vector? deps)
+ `(rumext.v2/use-memo
+ (rumext.v2/deps ~@deps)
+ (fn [] ~@body))
+
+
+ (nil? deps)
+ `(rumext.v2/use-memo
+ nil
+ (fn [] ~@body))
+
+ :else
+ `(rumext.v2/use-memo
+ (fn [] ~@(cons deps body)))))
+
+(defmacro ^:no-doc with-fn
+ [deps & body]
+ (cond
+ (vector? deps)
+ `(rumext.v2/use-fn
+ (rumext.v2/deps ~@deps)
+ ~@body)
+
+
+ (nil? deps)
+ `(rumext.v2/use-fn
+ nil
+ ~@body)
+
+ :else
+ `(rumext.v2/use-fn
+ ~@(cons deps body))))
+
+(defmacro with-effect
+ "A convenience syntactic abstraction (macro) for `useEffect`"
+ [deps & body]
+ (cond
+ (vector? deps)
+ `(rumext.v2/use-effect
+ (rumext.v2/deps ~@deps)
+ (fn [] ~@body))
+
+ (nil? deps)
+ `(rumext.v2/use-effect
+ nil
+ (fn [] ~@body))
+
+ :else
+ `(rumext.v2/use-effect
+ (fn [] ~@(cons deps body)))))
+
+(defmacro with-layout-effect
+ "A convenience syntactic abstraction (macro) for `useLayoutEffect`"
+ [deps & body]
+ (cond
+ (vector? deps)
+ `(rumext.v2/use-layout-effect
+ (rumext.v2/deps ~@deps)
+ (fn [] ~@body))
+
+ (nil? deps)
+ `(rumext.v2/use-layout-effect
+ nil
+ (fn [] ~@body))
+
+ :else
+ `(rumext.v2/use-layout-effect
+ (fn [] ~@(cons deps body)))))
+
+(defmacro check-props
+ "A macro version of the `check-props` function"
+ [props & [eq-f :as rest]]
+ (if (symbol? props)
+ `(apply rumext.v2/check-props ~props ~rest)
+
+ (let [eq-f (or eq-f 'cljs.core/=)
+ np-s (with-meta (gensym "new-props-") {:tag 'js})
+ op-s (with-meta (gensym "old-props-") {:tag 'js})
+ op-f (fn [prop]
+ (let [prop-access (symbol (str "-" (name prop)))]
+ (with-meta
+ (if (simple-ident? prop)
+ (list eq-f
+ (list '.. np-s prop-access)
+ (list '.. op-s prop-access))
+ (list eq-f
+ (list 'cljs.core/unchecked-get np-s prop)
+ (list 'cljs.core/unchecked-get op-s prop)))
+ {:tag 'boolean})))]
+ `(fn [~np-s ~op-s]
+ (and ~@(map op-f props))))))
+
+(defmacro lazy-component
+ "A macro that helps defining lazy-loading components with the help
+ of shadow-cljs tooling."
+ [ns-sym]
+ (if (production-build?)
+ `(let [loadable# (shadow.lazy/loadable ~ns-sym)]
+ (rumext.v2/lazy (fn []
+ (.then (shadow.lazy/load loadable#)
+ (fn [component#]
+ (cljs.core/js-obj "default" component#))))))
+ `(let [loadable# (shadow.lazy/loadable ~ns-sym)]
+ (rumext.v2/lazy (fn []
+ (.then (shadow.lazy/load loadable#)
+ (fn [_#]
+ (cljs.core/js-obj "default"
+ (rumext.v2/fnc ~'wrapper
+ {:rumext.v2/props :obj}
+ [props#]
+ [:> (deref loadable#) props#])))))))))
+
+(defmacro spread-object
+ "A helper for spread two js objects, adapting compile time known
+ keys to cameCase.
+
+ You can pass `:rumext.v2/transform false` on `other` metadata
+ for disable key casing transformation."
+ [target other]
+ (assert (or (symbol? target)
+ (map? target))
+ "only symbols or maps accepted on target")
+
+ (assert (or (symbol? other)
+ (map? other))
+ "only symbols or map allowed for the spread")
+
+ (let [transform? (get (meta other) ::transform true)
+ compile-prop (if transform?
+ (partial hc/compile-prop 2)
+ identity)]
+ (hc/compile-to-js-spread target other compile-prop)))
+
+(defmacro spread-props
+ "A helper for spread two js objects using react conventions for
+ compile time known props keys names."
+ [target other]
+ (assert (or (symbol? target)
+ (map? target))
+ "only symbols or maps accepted on target")
+
+ (assert (or (symbol? other)
+ (map? other))
+ "only symbols or map allowed for the spread")
+
+ (hc/compile-to-js-spread target other hc/compile-prop))
+
+(defmacro spread
+ "A shorter alias for spread props"
+ [target other]
+ `(spread-props ~target ~other))
+
+(defmacro props
+ "A helper for convert literal datastructures into js data
+ structures at compile time using react props convention."
+ [value]
+ (let [recursive? (get (meta value) ::recursive false)]
+ (hc/compile-props-to-js value ::hc/transform-props-recursive recursive?)))
+
+(defmacro object
+ [value]
+ (let [recursive? (get (meta value) ::recursive true)]
+ (hc/compile-coll-to-js value ::hc/transform-props-recursive recursive?)))
diff --git a/src/rumext/v2.cljs b/src/rumext/v2.cljs
new file mode 100644
index 0000000..64fb489
--- /dev/null
+++ b/src/rumext/v2.cljs
@@ -0,0 +1,511 @@
+;; This Source Code Form is subject to the terms of the Mozilla Public
+;; License, v. 2.0. If a copy of the MPL was not distributed with this
+;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
+;;
+;; Copyright (c) Andrey Antukh
+
+(ns rumext.v2
+ (:refer-clojure :exclude [ref deref use])
+ (:require-macros [rumext.v2 :refer [defc fnc]])
+ (:require
+ ["react" :as react]
+ ["react-dom" :as rdom]
+ ["react-dom/client" :as rdomc]
+ ["react/jsx-runtime" :as jsxrt]
+ [cljs.core :as c]
+ [goog.functions :as gf]
+ [rumext.v2.util :as util]
+ [rumext.v2.validation]
+ [shadow.lazy]))
+
+(def ^:const undefined (js* "(void 0)"))
+
+(def browser-context?
+ "A boolean var, indicates if the current code is running on browser main thread or not."
+ (exists? js/window))
+
+(def Component
+ "The `react.Component` class"
+ react/Component)
+
+(def Fragment
+ "The `react.Fragment class"
+ react/Fragment)
+
+(def Profiler
+ "The `react.Profiler` class"
+ react/Profiler)
+
+(def Suspense
+ "The `react.Suspense` class"
+ react/Suspense)
+
+(extend-type cljs.core.UUID
+ INamed
+ (-name [this] (js* "\"\" + ~{}" this))
+ (-namespace [_] ""))
+
+(def ^:no-doc ^function jsx jsxrt/jsx)
+(def ^:no-doc ^function jsxs jsxrt/jsxs)
+
+(defn merge-props
+ [props1 props2]
+ (js/Object.assign #js {} props1 props2))
+
+(def ^function forward-ref
+ "lets your component expose a DOM node to parent component with a ref."
+ react/forwardRef)
+
+;; --- Main Api
+
+(def ^function portal
+ "Render `element` in a DOM `node` that is ouside of current DOM hierarchy."
+ rdom/createPortal)
+
+(def ^function create-root
+ "Creates react root"
+ rdomc/createRoot)
+
+(def hydrate-root
+ "Lets you display React components inside a browser DOM node whose
+ HTML content was previously generated by react-dom/server"
+ rdomc/hydrateRoot)
+
+(defn render!
+ [root element]
+ (.render ^js root element))
+
+(defn unmount!
+ "Removes component from the DOM tree."
+ [root]
+ (.unmount ^js root))
+
+(def ^function create-ref react/createRef)
+
+(defn ref-val
+ "Given state and ref handle, returns React component."
+ [ref]
+ (unchecked-get ref "current"))
+
+(defn set-ref-val!
+ [ref val]
+ (unchecked-set ref "current" val)
+ val)
+
+(def ^function lazy
+ "A helper for creating lazy loading components."
+ react/lazy)
+
+;; --- Context API
+
+(def ^function create-context
+ "Create a react context"
+ react/createContext)
+
+(defn provider
+ "Get the current provider for specified context"
+ [ctx]
+ (unchecked-get ctx "Provider"))
+
+;; --- Raw Hooks
+
+(def ^function useId
+ "The `react.useId` hook function"
+ react/useId)
+
+(def ^function useRef
+ "The `react.useRef` hook function"
+ react/useRef)
+
+(def ^function useState
+ "The `react.useState` hook function"
+ react/useState)
+
+(def ^function useEffect
+ "The `react.useEffect` hook function"
+ react/useEffect)
+
+(def ^function useInsertionEffect
+ "The react.useInsertionEffect` hook function"
+ react/useInsertionEffect)
+
+(def ^function useLayoutEffect
+ "The `react.useLayoutEffect` hook function"
+ react/useLayoutEffect)
+
+(def ^function useDeferredValue
+ "The `react.useDeferredValue hook function"
+ react/useDeferredValue)
+
+(def ^function useMemo
+ "The `react.useMemo` hook function"
+ react/useMemo)
+
+(def ^function useCallback
+ "The `react.useCallback` hook function"
+ react/useCallback)
+
+(def ^function useContext
+ "The `react.useContext` hook function"
+ react/useContext)
+
+(def ^function useTransition
+ "The `react.useTransition` hook function"
+ react/useTransition)
+
+;; --- Hooks
+
+(def ^function use
+ "The `react.use` helper"
+ react/use)
+
+(def ^:private adapt-sym
+ (js/Symbol "rumext:adapt-fn"))
+
+(unchecked-set (.-prototype cljs.core/UUID)
+ adapt-sym
+ (fn [o] (.-uuid ^cljs.core/UUID o)))
+
+(unchecked-set (.-prototype cljs.core/Keyword)
+ adapt-sym
+ (fn [o] (.toString ^js o)))
+
+(unchecked-set (.-prototype cljs.core/Symbol)
+ adapt-sym
+ (fn [o] (.toString ^js o)))
+
+(defn adapt
+ [o]
+ (when (some? o)
+ (let [adapt-fn (unchecked-get o adapt-sym)]
+ (if ^boolean adapt-fn
+ (^function adapt-fn o)
+ o))))
+
+(defn deps
+ "A helper for creating hook deps array, that handles some
+ adaptations for clojure specific data types such that UUID and
+ keywords"
+ ([] #js [])
+ ([a] #js [(adapt a)])
+ ([a b] #js [(adapt a) (adapt b)])
+ ([a b c] #js [(adapt a) (adapt b) (adapt c)])
+ ([a b c d] #js [(adapt a) (adapt b) (adapt c) (adapt d)])
+ ([a b c d e] #js [(adapt a) (adapt b) (adapt c) (adapt d) (adapt e)])
+ ([a b c d e f] #js [(adapt a) (adapt b) (adapt c) (adapt d) (adapt e) (adapt f)])
+ ([a b c d e f g] #js [(adapt a) (adapt b) (adapt c) (adapt d) (adapt e) (adapt f) (adapt g)])
+ ([a b c d e f g h] #js [(adapt a) (adapt b) (adapt c) (adapt d) (adapt e) (adapt f) (adapt g) (adapt h)])
+ ([a b c d e f g h & rest] (into-array (map adapt (into [a b c d e f g h] rest)))))
+
+(def ^function use-ref
+ "A lisp-case alias for `useRef`"
+ react/useRef)
+
+(def ^function use-ctx
+ "A lisp-case short alias for the `useContext` hook function"
+ react/useContext)
+
+(def ^function use-id
+ "A lisp-case alias fro `useId` hook function"
+ react/useId)
+
+(def ^function start-transition
+ "An alias for react.startTransition function"
+ react/startTransition)
+
+(def noop (constantly nil))
+
+(defn use-effect
+ "A rumext variant of the `useEffect` hook function with order of
+ arguments inverted"
+ ([f] (use-effect #js [] f))
+ ([deps f]
+ (useEffect #(let [r (^function f)] (if (fn? r) r noop)) deps)))
+
+(defn use-insertion-effect
+ "A rumext variant of the `useInsertionEffect` hook function with order
+ of arguments inverted"
+ ([f] (use-insertion-effect #js [] f))
+ ([deps f]
+ (useInsertionEffect #(let [r (^function f)] (if (fn? r) r noop)) deps)))
+
+(defn use-layout-effect
+ "A rumext variant of the `useLayoutEffect` hook function with order
+ of arguments inverted"
+ ([f] (use-layout-effect #js [] f))
+ ([deps f]
+ (useLayoutEffect #(let [r (^function f)] (if (fn? r) r noop)) deps)))
+
+(defn use-ssr-effect
+ "An EXPERIMENTAL use-effect version that detects if we are in a NON
+ browser context and runs the effect fn inmediatelly."
+ [deps effect-fn]
+ (if ^boolean browser-context?
+ (use-effect deps effect-fn)
+ (let [ret (effect-fn)]
+ (when (fn? ret)
+ (ret)))))
+
+(defn use-memo
+ "A rumext variant of the `useMemo` hook function with order
+ of arguments inverted"
+ ([f] (useMemo f #js []))
+ ([deps f] (useMemo f deps)))
+
+(defn use-transition
+ "A rumext version of the `useTransition` hook function. Returns a
+ function object that implements the IPending protocol for check the
+ state of the transition."
+ []
+ (let [tmp (useTransition)
+ is-pending (aget tmp 0)
+ start-fn (aget tmp 1)]
+ (use-memo
+ #js [is-pending]
+ (fn []
+ (specify! (fn [cb-fn]
+ (^function start-fn cb-fn))
+ cljs.core/IPending
+ (-realized? [_] (not ^boolean is-pending)))))))
+
+(defn use-callback
+ "A rumext variant of the `useCallback` hook function with order
+ of arguments inverted"
+ ([f] (useCallback f #js []))
+ ([deps f] (useCallback f deps)))
+
+(defn use-fn
+ "A convenience short alias for `use-callback`"
+ ([f] (useCallback f #js []))
+ ([deps f] (useCallback f deps)))
+
+(defn deref
+ "A rumext hook for deref and watch an atom or atom like object. It
+ internally uses the react.useSyncExternalSource API"
+ [iref]
+ (let [state (use-ref (c/deref iref))
+ key (use-id)
+ get-state (use-fn #js [state] #(unchecked-get state "current"))
+ subscribe (use-fn #js [iref key]
+ (fn [listener-fn]
+ (unchecked-set state "current" (c/deref iref))
+ (add-watch iref key (fn [_ _ _ newv]
+ (unchecked-set state "current" newv)
+ (^function listener-fn)))
+ #(remove-watch iref key)))
+ snapshot (use-fn #js [iref] #(c/deref iref))]
+ (react/useSyncExternalStore subscribe get-state snapshot)))
+
+(deftype State [update-fn value]
+ c/IReset
+ (-reset! [_ value]
+ (^function update-fn value))
+
+ c/ISwap
+ (-swap! [self f]
+ (^function update-fn f))
+ (-swap! [self f x]
+ (^function update-fn #(f % x)))
+ (-swap! [self f x y]
+ (^function update-fn #(f % x y)))
+ (-swap! [self f x y more]
+ (^function update-fn #(apply f % x y more)))
+
+ c/IDeref
+ (-deref [_] value))
+
+(defn use-state
+ "A rumext variant of `useState`. Returns an object that implements
+ the Atom protocols."
+ ([] (use-state nil))
+ ([initial]
+ (let [tmp (useState initial)
+ ref (useRef nil)
+ value (aget tmp 0)
+ update-fn (aget tmp 1)]
+ (use-memo #js [value] #(State. update-fn value)))))
+
+(defn use-var
+ "A rumext custom hook that uses `useRef` under the hood. Returns an
+ object that implements the Atom protocols. The updates does not
+ trigger rerender."
+ ([] (use-var nil))
+ ([initial]
+ (let [ref (useRef nil)]
+ (when (nil? (.-current ^js ref))
+ (let [self (fn [value]
+ (let [target (unchecked-get ref "current")]
+ (unchecked-set target "value" value)))]
+
+ (unchecked-set self "value" initial)
+ (unchecked-set ref "current" self)
+ (specify! self
+ c/IDeref
+ (-deref [this]
+ (.-value ^js this))
+
+ c/IReset
+ (-reset! [this v]
+ (unchecked-set this "value" v))
+
+ c/ISwap
+ (-swap!
+ ([this f]
+ (unchecked-set this "value" (f (.-value ^js this))))
+ ([this f a]
+ (unchecked-set this "value" (f (.-value ^js this) a)))
+ ([this f a b]
+ (unchecked-set this "value" (f (.-value ^js this) a b)))
+ ([this f a b xs]
+ (unchecked-set this "value" (apply f (.-value ^js this) a b xs)))))))
+
+ (.-current ^js ref))))
+
+;; --- Other API
+
+(defn element
+ "Create a react element. This is a public API for the internal `jsx`
+ function"
+ ([klass]
+ (jsx klass #js {} undefined))
+ ([klass props]
+ (let [props (cond
+ (object? props) ^js props
+ (map? props) (util/map->obj props)
+ :else (throw (ex-info "Unexpected props" {:props props})))]
+ (jsx klass props undefined))))
+
+(def ^function create-element react/createElement)
+
+(def ^function element? react/isValidElement)
+
+;; --- Higher-Order Components
+
+(defn memo
+ "High order component for memoizing component props. Is a rumext
+ variant of React.memo what accepts a value comparator
+ function (instead of props comparator)"
+ ([component] (react/memo component))
+ ([component eq?]
+ (react/memo component #(util/props-equals? eq? %1 %2))))
+
+(def ^function memo'
+ "A raw variant of React.memo."
+ react/memo)
+
+(def ^:private schedule
+ (or (and (exists? js/window) js/window.requestAnimationFrame)
+ #(js/setTimeout % 16)))
+
+(defn deferred
+ "A higher-order component that just deffers the first render to the next tick"
+ ([component] (deferred component schedule))
+ ([component sfn]
+ (fnc deferred
+ {::wrap-props false}
+ [props]
+ (let [tmp (useState false)
+ render? (aget tmp 0)
+ set-render (aget tmp 1)]
+ (use-effect (fn [] (^function sfn #(^function set-render true))))
+ (when ^boolean render?
+ [:> component props])))))
+
+(defn throttle
+ "A higher-order component that throttles the rendering"
+ [component ms]
+ (fnc throttle
+ {::wrap-props false}
+ [props]
+ (let [tmp (useState props)
+ state (aget tmp 0)
+ set-state (aget tmp 1)
+
+ ref (useRef false)
+ render (useMemo
+ #(gf/throttle
+ (fn [v]
+ (when-not ^boolean (ref-val ref)
+ (^function set-state v)))
+ ms)
+ #js [])]
+ (useEffect #(^function render props) #js [props])
+ (useEffect #(fn [] (set-ref-val! ref true)) #js [])
+ [:> component state])))
+
+(defn check-props
+ "Utility function to use with `memo'`.
+ Will check the `props` keys to see if they are equal.
+
+ Usage:
+
+ ```clojure
+ (mf/defc my-component
+ {::mf/wrap [#(mf/memo' % (mf/check-props [\"prop1\" \"prop2\"]))]}
+ [props]
+ ```
+ )"
+
+ ([props] (check-props props =))
+ ([props eqfn?]
+ (fn [np op]
+ (every? #(eqfn? (unchecked-get np %)
+ (unchecked-get op %))
+ props))))
+
+(defn use-debounce
+ "A rumext custom hook that debounces the value changes"
+ [ms value]
+ (let [[state update-fn] (useState value)
+ update-fn (useMemo #(gf/debounce update-fn ms) #js [ms])]
+ (useEffect #(update-fn value) #js [value])
+ state))
+
+(defn use-equal-memo
+ "A rumext custom hook that preserves object identity through using a
+ `=` (value equality). Optionally, you can provide your own
+ function."
+ ([val]
+ (let [ref (use-ref nil)]
+ (when-not (= (ref-val ref) val)
+ (set-ref-val! ref val))
+ (ref-val ref)))
+ ([eqfn val]
+ (let [ref (use-ref nil)]
+ (when-not (eqfn (ref-val ref) val)
+ (set-ref-val! ref val))
+ (ref-val ref))))
+
+(def ^function use-deferred
+ "A lisp-case shorter alias for `useDeferredValue`"
+ react/useDeferredValue)
+
+(defn use-previous
+ "A rumext custom hook that returns a value from previous render"
+ [value]
+ (let [ref (use-ref value)]
+ (use-effect #js [value] #(set-ref-val! ref value))
+ (ref-val ref)))
+
+(defn use-update-ref
+ "A rumext custom hook that updates the ref value if the value changes"
+ [value]
+ (let [ref (use-ref value)]
+ (use-effect #js [value] #(set-ref-val! ref value))
+ ref))
+
+(defn use-ref-fn
+ "A rumext custom hook that returns a stable callback pointer what
+ calls the interned callback. The interned callback will be
+ automatically updated on each render if the reference changes and
+ works as noop if the pointer references to nil value."
+ [f]
+ (let [ptr (use-ref nil)]
+ (use-effect #js [f] #(set-ref-val! ptr f))
+ (use-fn (fn []
+ (let [f (ref-val ptr)
+ args (js-arguments)]
+ (when (some? f)
+ (.apply f args)))))))
+
+
diff --git a/src/rumext/v2/compiler.clj b/src/rumext/v2/compiler.clj
new file mode 100644
index 0000000..c320da2
--- /dev/null
+++ b/src/rumext/v2/compiler.clj
@@ -0,0 +1,583 @@
+;; TODO: move to .CLJ file
+
+(ns rumext.v2.compiler
+ "
+ Hicada - Hiccup compiler aus dem Allgaeu
+
+ NOTE: The code for has been forked like this:
+ weavejester/hiccup -> r0man/sablono -> Hicada -> rumext"
+ (:refer-clojure :exclude [compile])
+ (:require
+ [clojure.core :as c]
+ [clojure.string :as str]
+ [rumext.v2 :as-alias mf]
+ [rumext.v2.normalize :as norm]
+ [rumext.v2.util :as util])
+ (:import
+ cljs.tagged_literals.JSValue))
+
+(declare ^:private compile*)
+(declare ^:private compile-map-to-js)
+(declare ^:private compile-prop)
+(declare ^:private compile-to-js)
+(declare ^:private compile-vec-to-js)
+(declare ^:private emit-jsx)
+
+(def ^:dynamic *transform-props-recursive* nil)
+(def ^:dynamic *handlers* nil)
+
+(defn- js-value?
+ [o]
+ (instance? JSValue o))
+
+(defn- valid-props-type?
+ [o]
+ (or (symbol? o)
+ (js-value? o)
+ (seq? o)
+ (nil? o)
+ (map? o)))
+
+(def default-handlers
+ {:> (fn [& [_ tag props :as children]]
+ (when (> 2 (count children))
+ (throw (ex-info "invalid params for `:>` handler, tag and props are mandatory"
+ {:params children})))
+
+ (let [props (or props {})
+ props (if (instance? clojure.lang.IObj props)
+ (let [mdata (meta props)]
+ (vary-meta props assoc
+ ::handler :>
+ ::transform-props-keys true
+ ::transform-props-recursive (get mdata ::mf/recursive false)))
+ props)]
+ [tag props (drop 3 children)]))
+
+ :>> (fn [& [_ tag props :as children]]
+ (when (> 3 (count children))
+ (throw (ex-info "invalid params for `:>` handler, tag and props are mandatory"
+ {:params children})))
+
+ (let [props (or props {})
+ props (if (instance? clojure.lang.IObj props)
+ (vary-meta props assoc
+ ::handler :>>
+ ::transform-props-keys true
+ ::transform-props-recursive true)
+ props)]
+ [tag props (drop 3 children)]))
+
+ :& (fn [& [_ tag props :as children]]
+ (when (> 2 (count children))
+ (throw (ex-info "invalid params for `:&` handler, tag and props are mandatory"
+ {:params children})))
+
+ (when-not (valid-props-type? props)
+ (throw (ex-info "invalid props type: obj, symbol seq or map is allowed"
+ {:props props})))
+
+ (let [props (or props {})
+ props (vary-meta props assoc
+ ::handler :&
+ ::transform-props-keys false
+ ::transform-props-recursive false
+ ::allow-dynamic-transform true)]
+ [tag props (drop 3 children)]))
+
+ :? (fn [& [_ props :as children]]
+ (if (map? props)
+ ['rumext.v2/Suspense props (drop 2 children)]
+ ['rumext.v2/Suspense {} (drop 1 children)]))
+
+ :* (fn [& [_ props :as children]]
+ (if (map? props)
+ ['rumext.v2/Fragment props (drop 2 children)]
+ ['rumext.v2/Fragment {} (drop 1 children)]))})
+
+(defn- unevaluated?
+ "True if the expression has not been evaluated.
+ - expr is a symbol? OR
+ - it's something like (foo bar)"
+ [expr]
+ (or (symbol? expr)
+ (and (seq? expr)
+ (not= (first expr) `quote))))
+
+(defn- literal?
+ "True if x is a literal value that can be rendered as-is."
+ [x]
+ (and (not (unevaluated? x))
+ (or (not (or (vector? x) (map? x)))
+ (and (every? literal? x)
+ (not (keyword? (first x)))))))
+
+(defn- join-classes
+ "Join the `classes` with a whitespace."
+ [classes]
+ (->> (map #(if (string? %) % (seq %)) classes)
+ (flatten)
+ (remove nil?)
+ (str/join " ")))
+
+(defn compile-concat
+ "Compile efficient and performant string concatenation operation"
+ [params & {:keys [safe?]}]
+ (let [xform (comp (filter some?)
+ (if safe?
+ (map (fn [part]
+ (if (string? part)
+ part
+ (list 'js* "(~{} ?? \"\")" part))))
+ (map identity)))
+ params (into [] xform params)]
+
+ (if (= 1 (count params))
+ (first params)
+ (let [templ (->> (repeat (count params) "~{}")
+ (interpose "+")
+ (reduce c/str ""))]
+ (apply list 'js* templ params)))))
+
+(defn- compile-join-classes
+ "Joins strings space separated"
+ ([] "")
+ ([x] x)
+ ([x & xs]
+ (compile-concat (interpose " " (cons x xs)) :safe? true)))
+
+(defn- compile-class-attr-value
+ [value]
+ (cond
+ (or (nil? value)
+ (keyword? value)
+ (string? value))
+ value
+
+ ;; If we know all classes at compile time, we just join them
+ ;; correctly and return.
+ (and (or (sequential? value)
+ (set? value))
+ (every? string? value))
+ (join-classes value)
+
+ ;; If we don't know all classes at compile time (some classes are
+ ;; defined on let bindings per example), then we emit a efficient
+ ;; concatenation code that executes on runtime
+ (vector? value)
+ (apply compile-join-classes value)
+
+ :else value))
+
+(defmulti compile-form
+ "Pre-compile certain standard forms, where possible."
+ (fn [form]
+ (when (and (seq? form) (symbol? (first form)))
+ (name (first form)))))
+
+(defmethod compile-form "do"
+ [[_ & forms]]
+ `(do ~@(butlast forms) ~(compile* (last forms))))
+
+(defmethod compile-form "array"
+ [[_ & forms]]
+ `(cljs.core/array ~@(mapv compile* forms)))
+
+(defmethod compile-form "let"
+ [[_ bindings & body]]
+ `(let ~bindings ~@(butlast body) ~(compile* (last body))))
+
+(defmethod compile-form "let*"
+ [[_ bindings & body]]
+ `(let* ~bindings ~@(butlast body) ~(compile* (last body))))
+
+(defmethod compile-form "letfn*"
+ [[_ bindings & body]]
+ `(letfn* ~bindings ~@(butlast body) ~(compile* (last body))))
+
+(defmethod compile-form "for"
+ [[_ bindings body]]
+ ;; Special optimization: For a simple (for [x xs] ...) we rewrite the for
+ ;; to a fast reduce outputting a JS array:
+ (if (== 2 (count bindings))
+ (let [[item coll] bindings]
+ (if (= 'js (:tag (meta coll)))
+ `(.map ~coll (fn [~item] ~(compile* body)))
+ `(reduce (fn [out-arr# ~item]
+ (.push out-arr# ~(compile* body))
+ out-arr#)
+ (cljs.core/array) ~coll)))
+ ;; Still optimize a little by giving React an array:
+ (list 'cljs.core/into-array `(for ~bindings ~(compile* body)))))
+
+(defmethod compile-form "if"
+ [[_ condition & body]]
+ `(if ~condition ~@(doall (for [x body] (compile* x)))))
+
+(defmethod compile-form "when"
+ [[_ bindings & body]]
+ `(when ~bindings ~@(doall (for [x body] (compile* x)))))
+
+(defmethod compile-form "when-some"
+ [[_ bindings & body]]
+ `(when-some ~bindings ~@(butlast body) ~(compile* (last body))))
+
+(defmethod compile-form "when-let"
+ [[_ bindings & body]]
+ `(when-let ~bindings ~@(butlast body) ~(compile* (last body))))
+
+(defmethod compile-form "when-first"
+ [[_ bindings & body]]
+ `(when-first ~bindings ~@(butlast body) ~(compile* (last body))))
+
+(defmethod compile-form "when-not"
+ [[_ bindings & body]]
+ `(when-not ~bindings ~@(doall (for [x body] (compile* x)))))
+
+(defmethod compile-form "if-not"
+ [[_ bindings & body]]
+ `(if-not ~bindings ~@(doall (for [x body] (compile* x)))))
+
+(defmethod compile-form "if-some"
+ [[_ bindings & body]]
+ `(if-some ~bindings ~@(doall (for [x body] (compile* x)))))
+
+(defmethod compile-form "if-let"
+ [[_ bindings & body]]
+ `(if-let ~bindings ~@(doall (for [x body] (compile* x)))))
+
+(defmethod compile-form "letfn"
+ [[_ bindings & body]]
+ `(letfn ~bindings ~@(butlast body) ~(compile* (last body))))
+
+(defmethod compile-form "fn"
+ [[_ params & body]]
+ `(fn ~params ~@(butlast body) ~(compile* (last body))))
+
+(defmethod compile-form "case"
+ [[_ v & cases]]
+ `(case ~v
+ ~@(doall (mapcat
+ (fn [[test hiccup]]
+ (if hiccup
+ [test (compile* hiccup)]
+ [(compile* test)]))
+ (partition-all 2 cases)))))
+
+(defmethod compile-form "condp"
+ [[_ f v & cases]]
+ `(condp ~f ~v
+ ~@(doall (mapcat
+ (fn [[test hiccup]]
+ (if hiccup
+ [test (compile* hiccup)]
+ [(compile* test)]))
+ (partition-all 2 cases)))))
+
+(defmethod compile-form "cond"
+ [[_ & clauses]]
+ `(cond ~@(doall
+ (mapcat
+ (fn [[check expr]] [check (compile* expr)])
+ (partition 2 clauses)))))
+
+(defmethod compile-form :default [expr] expr)
+
+(defn- compile-element
+ "Returns an unevaluated form that will render the supplied vector as a HTML element."
+ [[tag props & children :as element]]
+ (cond
+ ;; e.g. [:> Component {:key "xyz", :foo "bar} ch0 ch1]
+ (contains? *handlers* tag)
+ (let [f (get *handlers* tag)
+ [tag props children] (apply f element)]
+ (emit-jsx tag props (mapv compile* children)))
+
+ ;; e.g. [:span {} x]
+ (and (literal? tag) (map? props))
+ (let [[tag props _] (norm/element [tag props])]
+ (emit-jsx tag props (mapv compile* children)))
+
+ ;; We could now interpet this as either:
+ ;; 1. First argument is the attributes (in #js{} provided by the user) OR:
+ ;; 2. First argument is the first child element.
+ ;; We assume #2. Always!
+ (literal? tag)
+ (compile-element (list* tag {} props children))
+
+ ;; Problem: [a b c] could be interpreted as:
+ ;; 1. The coll of ReactNodes [a b c] OR
+ ;; 2. a is a React Element, b are the props and c is the first child
+ ;; We default to 1) (handled below) BUT, if b is a map, we know this must be 2)
+ ;; since a map doesn't make any sense as a ReactNode.
+ ;; [foo {...} ch0 ch1] NEVER makes sense to interpret as a sequence
+ (and (vector? element) (map? props))
+ (emit-jsx tag props (mapv compile* children))
+
+ (seq? element)
+ (seq (mapv compile* element))
+
+ ;; We have nested children
+ ;; [[:div "foo"] [:span "foo"]]
+ :else
+ (mapv compile* element)))
+
+(defn- compile*
+ "Pre-compile data structures"
+ [content]
+ (cond
+ (vector? content) (compile-element content)
+ (literal? content) content
+ :else (compile-form content)))
+
+(defn compile-prop-key
+ "Compiles a key to a react compatible key (eg: camelCase)"
+ [k]
+ (if (or (keyword? k) (symbol? k))
+ (util/ident->prop k)
+ k))
+
+(defn compile-prop-inner-key
+ "Compiles a key to a react compatible key (eg: camelCase)"
+ [k]
+ (if (or (keyword? k) (symbol? k))
+ (util/ident->key k)
+ k))
+
+(defn- compile-style-value
+ [m]
+ (cond
+ (map? m)
+ (reduce-kv
+ (fn [m k v]
+ (assoc m (compile-prop-key k) v))
+ {} m)
+ ;; React native accepts :style [{:foo-bar ..} other-styles] so camcase those keys:
+ (vector? m)
+ (mapv compile-style-value m)
+
+ :else
+ m))
+
+(defn compile-prop-value
+ [level val]
+ (cond
+ (not *transform-props-recursive*)
+ val
+
+ (map? val)
+ (->> val
+ (into {} (map (partial compile-prop (inc level))))
+ (compile-map-to-js))
+
+ (vector? val)
+ (->> val
+ (mapv (partial compile-prop-value (inc level)))
+ (compile-vec-to-js))
+
+ :else
+ val))
+
+(defn compile-prop
+ ([prop] (compile-prop 1 prop))
+ ([level [key val :as kvpair]]
+ (let [key (if (= level 1)
+ (compile-prop-key key)
+ (compile-prop-inner-key key))]
+ (cond
+ (and (= level 1)
+ (= key "className"))
+ [key (compile-class-attr-value val)]
+
+ (and (= level 1)
+ (= key "style"))
+ [key (-> val
+ (compile-style-value)
+ (compile-map-to-js))]
+
+ (and (= level 1)
+ (= key "htmlFor"))
+ [key (if (keyword? val)
+ (name val)
+ val)]
+
+ :else
+ [key (compile-prop-value level val)]))))
+
+(defn compile-kv-to-js
+ "A internal method helper for compile kv data structures"
+ [form]
+ (let [valid-key? #(or (keyword? %) (string? %))
+ form (into {} (filter (comp valid-key? key)) form)]
+ [(->> form
+ (map (comp name key))
+ (map #(-> (str \' % "':~{}")))
+ (interpose ",")
+ (apply str))
+ (vec (vals form))]))
+
+(defn compile-map-to-js
+ "Compile a statically known map data sturcture, non-recursivelly to js
+ expression. Mainly used by macros for create js data structures at
+ compile time."
+ [form]
+ (if (map? form)
+ (if (empty? form)
+ (list 'js* "{}")
+ (let [[keys vals] (compile-kv-to-js form)]
+ (-> (apply list 'js* (str "{" keys "}") vals)
+ (vary-meta assoc :tag 'object))))
+ form))
+
+(defn compile-vec-to-js
+ "Compile a statically known map data sturcture, non-recursivelly to js
+ expression. Mainly used by macros for create js data structures at
+ compile time."
+ [form]
+ (if (vector? form)
+ (if (empty? form)
+ (list 'js* "[]")
+ (let [template (->> form
+ (map (constantly "~{}"))
+ (interpose ",")
+ (apply str))]
+ (-> (apply list 'js* (str "[" template "]") form)
+ (vary-meta assoc :tag 'object))))
+ form))
+
+(defn compile-props-to-js
+ "Transform a props map literal to js object props. By default not
+ recursive."
+ [props & {:keys [::transform-props-recursive
+ ::transform-props-keys]
+ :or {transform-props-recursive false
+ transform-props-keys true}
+ :as params}]
+
+ (binding [*transform-props-recursive* transform-props-recursive]
+ (cond->> props
+ (true? transform-props-keys)
+ (into {} (map (partial compile-prop 1)))
+
+ :always
+ (compile-map-to-js))))
+
+(defn compile-coll-to-js
+ "Transform map or vector to js object or js array. Recursive by
+ default."
+ [coll & {:keys [::transform-props-recursive
+ ::transform-props-keys]
+ :or {transform-props-recursive true
+ transform-props-keys true}
+ :as params}]
+ (binding [*transform-props-recursive* transform-props-recursive]
+ (cond
+ (map? coll)
+ (->> coll
+ (into {} (map (partial compile-prop 2)))
+ (compile-map-to-js))
+
+ (vector? coll)
+ (->> coll
+ (mapv (partial compile-prop-value 2))
+ (compile-vec-to-js))
+
+ :else
+ (throw (ex-info "only map or vectors allowed" {})))))
+
+(defn compile-to-js-spread
+ [target other compile-prop]
+ (cond
+ (and (symbol? target)
+ (symbol? other))
+ (list 'js* "{...~{}, ...~{}}" target other)
+
+ (and (symbol? target)
+ (map? other))
+ (let [[keys vals] (->> other
+ (into {} (map compile-prop))
+ (compile-kv-to-js))
+ template (str "{...~{}, " keys "}")]
+ (apply list 'js* template target vals))
+
+ (and (map? target)
+ (symbol? other))
+ (let [[keys vals] (->> target
+ (into {} (map compile-prop))
+ (compile-kv-to-js))
+ template (str "{" keys ", ...~{}}")]
+ (apply list 'js* template (concat vals [other])))
+
+ (and (map? target)
+ (map? other))
+ (compile-map-to-js (->> (merge target other)
+ (into {} (map compile-prop))))
+
+ :else
+ (throw (IllegalArgumentException. "invalid arguments, only symbols or maps allowed"))))
+
+(defn emit-jsx
+ "Emits the final react js code"
+ [tag props children]
+ (let [tag (cond
+ (keyword? tag) (name tag)
+ (string? tag) tag
+ (symbol? tag) tag
+ (seq? tag) tag
+ :else (throw (ex-info "jsx: invalid tag" {:tag tag})))
+
+ children (into [] (filter some?) children)
+ mdata (meta props)
+ jstag? (= (get mdata :tag) 'js)]
+
+ (if (valid-props-type? props)
+ (if (or (map? props) (nil? props))
+ (let [nchild (count children)
+ props (cond
+ (= 0 nchild)
+ (or props {})
+
+ (= 1 nchild)
+ (assoc props :children (peek children))
+
+ :else
+ (assoc props :children (apply list 'cljs.core/array children)))
+
+ key (:key props)
+ props (dissoc props :key)
+ props (compile-props-to-js props mdata)]
+
+ (if key
+ (if (> nchild 1)
+ (list 'rumext.v2/jsxs tag props key)
+ (list 'rumext.v2/jsx tag props key))
+ (if (> nchild 1)
+ (list 'rumext.v2/jsxs tag props)
+ (list 'rumext.v2/jsx tag props))))
+
+ (let [props (if (and (::allow-dynamic-transform mdata) (not jstag?))
+ (list 'rumext.v2.util/map->obj props)
+ props)
+ nchild (count children)]
+ (cond
+ (= 0 nchild)
+ (list 'rumext.v2/create-element tag props)
+
+ (= 1 nchild)
+ (list 'rumext.v2/create-element tag props (first children))
+
+ :else
+ (apply list 'rumext.v2/create-element tag props children))))
+
+ (throw (ex-info "jsx: invalid props type" {:props props})))))
+
+(defn compile
+ "Arguments:
+ - content: The hiccup to compile
+ - handlers: A map to handle special tags. See default-handlers in this namespace.
+ "
+ ([content]
+ (compile content nil))
+ ([content handlers]
+ (binding [*handlers* (merge default-handlers handlers)]
+ (compile* content))))
diff --git a/src/rumext/normalize.cljc b/src/rumext/v2/normalize.clj
similarity index 91%
rename from src/rumext/normalize.cljc
rename to src/rumext/v2/normalize.clj
index 89e89ee..a2e66e7 100644
--- a/src/rumext/normalize.cljc
+++ b/src/rumext/v2/normalize.clj
@@ -1,19 +1,21 @@
-(ns rumext.normalize
- "
- Mostly from sablono + hiccup project.
- "
- (:require
- [rumext.util :as util]))
+(ns rumext.v2.normalize)
+
+(defn- element?
+ "- is x a vector?
+ AND
+ - first element is a keyword?"
+ [x]
+ (and (vector? x) (keyword? (first x))))
(defn compact-map
"Removes all map entries where the value of the entry is empty."
[m]
(reduce
- (fn [m k]
- (let [v (get m k)]
- (if (empty? v)
- (dissoc m k) m)))
- m (keys m)))
+ (fn [m k]
+ (let [v (get m k)]
+ (if (empty? v)
+ (dissoc m k) m)))
+ m (keys m)))
(defn class-name
[x]
@@ -111,7 +113,7 @@
(string? x)
(list x)
- (util/element? x)
+ (element? x)
(list x)
(and (list? x)
@@ -124,7 +126,7 @@
(and (sequential? x)
(sequential? (first x))
(not (string? (first x)))
- (not (util/element? (first x)))
+ (not (element? (first x)))
(= (count x) 1))
(children (first x))
@@ -153,6 +155,7 @@
[tag
(attributes tag-attrs)
(children content)])))
+
#_(element [:div#foo 'a])
#_(element [:div.a#foo])
#_(element [:h1.b {:className "a"}])
diff --git a/src/rumext/v2/util.cljc b/src/rumext/v2/util.cljc
new file mode 100644
index 0000000..601b351
--- /dev/null
+++ b/src/rumext/v2/util.cljc
@@ -0,0 +1,190 @@
+;; This Source Code Form is subject to the terms of the Mozilla Public
+;; License, v. 2.0. If a copy of the MPL was not distributed with this
+;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
+;;
+;; Copyright (c) 2016-2020 Andrey Antukh
+
+(ns ^:no-doc rumext.v2.util
+ "Runtime helpers"
+ (:require
+ #?(:cljs [cljs-bean.core :as bean])
+ [cuerdas.core :as str]
+ [malli.core :as m]
+ [malli.error :as me]))
+
+(defn ident->key
+ [nword]
+ (let [nword (if (string? nword) nword (name nword))]
+ (cond
+ (nil? (str/index-of nword "-"))
+ nword
+
+ (str/starts-with? nword "-")
+ (-> nword str/camel str/capital)
+
+ :else
+ (str/camel nword))))
+
+(defn ident->prop
+ "Compiles a keyword or symbol to string using react prop naming
+ convention"
+ [nword]
+ (let [nword (if (string? nword) nword (name nword))]
+ (cond
+ (= nword "class") "className"
+ (= nword "for") "htmlFor"
+ (str/starts-with? nword "--") nword
+ (str/starts-with? nword "data-") nword
+ (str/starts-with? nword "aria-") nword
+ :else
+ (ident->key nword))))
+
+#?(:cljs
+ (defn obj->map
+ [obj]
+ (let [keys (.keys js/Object obj)
+ len (alength keys)]
+ (loop [i 0
+ r (transient {})]
+ (if (< i len)
+ (let [key (aget keys i)]
+ (recur (unchecked-inc i)
+ (assoc! r (keyword key) (unchecked-get obj key))))
+ (persistent! r))))))
+
+#?(:cljs
+ (defn plain-object?
+ ^boolean
+ [o]
+ (and (some? o)
+ (identical? (.getPrototypeOf js/Object o)
+ (.-prototype js/Object)))))
+
+#?(:cljs
+ (defn map->obj
+ [o]
+ (cond
+ (plain-object? o)
+ o
+
+ (map? o)
+ (let [m #js {}]
+ (run! (fn [[k v]] (unchecked-set m (name k) v)) o)
+ m)
+
+ :else
+ (throw (ex-info "unable to create obj" {:data o})))))
+
+#?(:cljs
+ (defn map->props
+ ([o] (map->props o false))
+ ([o recursive?]
+ (if (object? o)
+ o
+ (let [level (if (true? recursive?) 1 recursive?)]
+ (reduce-kv (fn [res k v]
+ (let [v (if (keyword? v) (name v) v)
+ k (cond
+ (string? k) k
+ (keyword? k) (if (and (int? level) (not= 1 level))
+ (ident->key k)
+ (ident->prop k))
+ :else nil)]
+
+ (when (some? k)
+ (let [v (cond
+ (and (= k "style") (map? v))
+ (map->props v true)
+
+ (and (int? level) (map? v))
+ (map->props v (inc level))
+
+ :else
+ v)]
+ (unchecked-set res k v)))
+
+ res))
+ #js {}
+ o))))))
+
+#?(:cljs
+ (defn wrap-props
+ [props]
+ (cond
+ (object? props) (obj->map props)
+ (map? props) props
+ (nil? props) {}
+ :else (throw (ex-info "Unexpected props" {:props props})))))
+
+#?(:cljs
+ (defn props-equals?
+ [eq? new-props old-props]
+ (let [old-keys (.keys js/Object old-props)
+ new-keys (.keys js/Object new-props)
+ old-keys-len (alength old-keys)
+ new-keys-len (alength new-keys)]
+ (if (identical? old-keys-len new-keys-len)
+ (loop [idx (int 0)]
+ (if (< idx new-keys-len)
+ (let [key (aget new-keys idx)
+ new-val (unchecked-get new-props key)
+ old-val (unchecked-get old-props key)]
+ (if ^boolean (eq? new-val old-val)
+ (recur (inc idx))
+ false))
+ true))
+ false))))
+
+#?(:cljs
+ (defn symbol-for
+ [v]
+ (.for js/Symbol v)))
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;; BEANS
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+#?(:cljs
+ (defn prop->key
+ [k]
+ (if (string? k)
+ (-> k str/kebab keyword)
+ k)))
+
+#?(:cljs
+ (defn react-prop->key
+ [k]
+ (if (string? k)
+ (case k
+ "htmlFor" :for
+ "className" :class
+ (-> k str/kebab keyword))
+ k)))
+
+#?(:cljs
+ (defn- react-key->prop
+ [x]
+ (when (simple-keyword? x)
+ (ident->prop (name x)))))
+
+#?(:cljs
+ (defn- key->prop
+ [x]
+ (when (keyword? x)
+ (str/camel (.-fqn ^cljs.core.Keyword x)))))
+
+#?(:cljs
+ (defn bean
+ [o]
+ (bean/->clj o
+ :prop->key prop->key
+ :key->prop key->prop)))
+
+#?(:cljs
+ (defn props-bean
+ "A props specific bean that properly handles react props naming
+ conventions"
+ [o]
+ (bean/->clj o
+ :prop->key react-prop->key
+ :key->prop react-key->prop)))
diff --git a/src/rumext/v2/validation.cljs b/src/rumext/v2/validation.cljs
new file mode 100644
index 0000000..e82ddb8
--- /dev/null
+++ b/src/rumext/v2/validation.cljs
@@ -0,0 +1,69 @@
+;; This Source Code Form is subject to the terms of the Mozilla Public
+;; License, v. 2.0. If a copy of the MPL was not distributed with this
+;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
+;;
+;; Copyright (c) 2016-2020 Andrey Antukh
+
+(ns ^:no-doc rumext.v2.validation
+ "Runtime helpers"
+ (:require
+ [cuerdas.core :as str]
+ [rumext.v2.util :as util]
+ [malli.core :as m]
+ [malli.transform :as mt]
+ [malli.error :as me]))
+
+(def default-transformer mt/json-transformer)
+
+(defn process-explain-kv
+ [prefix result k v]
+ (let [nm (if (keyword? k)
+ (name k)
+ (str k))
+ pk (if prefix
+ (str prefix "." nm)
+ nm)]
+ (cond
+ (and (vector? v) (every? vector? v))
+ (let [data (into {} (map-indexed vector) v)]
+ (reduce-kv (partial process-explain-kv pk) result data))
+
+ (and (vector? v) (every? map? v))
+ (let [gdata (into {} (comp
+ (map :malli/error)
+ (map-indexed vector)
+ (filter second))
+ v)
+ ndata (into {} (comp
+ (map #(dissoc % :malli/error))
+ (map-indexed vector))
+ v)
+
+ result (reduce-kv (partial process-explain-kv pk) result gdata)
+ result (reduce-kv (partial process-explain-kv pk) result ndata)]
+
+ result)
+
+ (and (vector? v) (every? string? v))
+ (assoc result pk (peek v))
+
+ (map? v)
+ (reduce-kv (partial process-explain-kv pk) result v)
+
+ :else
+ result)))
+
+(defn ^:no-doc validator
+ [schema]
+ (let [validator (delay (m/validator schema))
+ explainer (delay (m/explainer schema))
+ decoder (delay (m/decoder schema default-transformer))]
+ (fn [props]
+ (let [props (util/props-bean props)
+ props (@decoder props)
+ validate (deref validator)]
+ (when-not ^boolean (^function validate props)
+ (let [explainer (deref explainer)
+ explain (^function explainer props)
+ explain (me/humanize explain)]
+ (reduce-kv (partial process-explain-kv nil) {} explain)))))))
diff --git a/yarn.lock b/yarn.lock
index 0d4e11f..b367e11 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1,698 +1,161 @@
-# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
-# yarn lockfile v1
-
-
-asn1.js@^5.2.0:
- version "5.4.1"
- resolved "https://registry.yarnpkg.com/asn1.js/-/asn1.js-5.4.1.tgz#11a980b84ebb91781ce35b0fdc2ee294e3783f07"
- integrity sha512-+I//4cYPccV8LdmBLiX8CYvf9Sp3vQsrqu2QNXRcrbiWvcx/UdlFiqUJJzxRQxgsZmvhXhn4cSKeSmoFjVdupA==
- dependencies:
- bn.js "^4.0.0"
- inherits "^2.0.1"
- minimalistic-assert "^1.0.0"
- safer-buffer "^2.1.0"
-
-assert@^1.1.1:
- version "1.5.0"
- resolved "https://registry.yarnpkg.com/assert/-/assert-1.5.0.tgz#55c109aaf6e0aefdb3dc4b71240c70bf574b18eb"
- integrity sha512-EDsgawzwoun2CZkCgtxJbv392v4nbk9XDD06zI+kQYoBM/3RBWLlEyJARDOmhAAosBjWACEkKL6S+lIZtcAubA==
- dependencies:
- object-assign "^4.1.1"
- util "0.10.3"
-
-async-limiter@~1.0.0:
- version "1.0.1"
- resolved "https://registry.yarnpkg.com/async-limiter/-/async-limiter-1.0.1.tgz#dd379e94f0db8310b08291f9d64c3209766617fd"
- integrity sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ==
-
-base64-js@^1.0.2:
- version "1.3.1"
- resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.3.1.tgz#58ece8cb75dd07e71ed08c736abc5fac4dbf8df1"
- integrity sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g==
-
-bn.js@^4.0.0, bn.js@^4.1.0, bn.js@^4.4.0:
- version "4.11.9"
- resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.11.9.tgz#26d556829458f9d1e81fc48952493d0ba3507828"
- integrity sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw==
-
-bn.js@^5.1.1:
- version "5.1.3"
- resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-5.1.3.tgz#beca005408f642ebebea80b042b4d18d2ac0ee6b"
- integrity sha512-GkTiFpjFtUzU9CbMeJ5iazkCzGL3jrhzerzZIuqLABjbwRaFt33I9tUdSNryIptM+RxDet6OKm2WnLXzW51KsQ==
-
-brorand@^1.0.1:
- version "1.1.0"
- resolved "https://registry.yarnpkg.com/brorand/-/brorand-1.1.0.tgz#12c25efe40a45e3c323eb8675a0a0ce57b22371f"
- integrity sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8=
-
-browserify-aes@^1.0.0, browserify-aes@^1.0.4:
- version "1.2.0"
- resolved "https://registry.yarnpkg.com/browserify-aes/-/browserify-aes-1.2.0.tgz#326734642f403dabc3003209853bb70ad428ef48"
- integrity sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA==
- dependencies:
- buffer-xor "^1.0.3"
- cipher-base "^1.0.0"
- create-hash "^1.1.0"
- evp_bytestokey "^1.0.3"
- inherits "^2.0.1"
- safe-buffer "^5.0.1"
-
-browserify-cipher@^1.0.0:
- version "1.0.1"
- resolved "https://registry.yarnpkg.com/browserify-cipher/-/browserify-cipher-1.0.1.tgz#8d6474c1b870bfdabcd3bcfcc1934a10e94f15f0"
- integrity sha512-sPhkz0ARKbf4rRQt2hTpAHqn47X3llLkUGn+xEJzLjwY8LRs2p0v7ljvI5EyoRO/mexrNunNECisZs+gw2zz1w==
- dependencies:
- browserify-aes "^1.0.4"
- browserify-des "^1.0.0"
- evp_bytestokey "^1.0.0"
-
-browserify-des@^1.0.0:
- version "1.0.2"
- resolved "https://registry.yarnpkg.com/browserify-des/-/browserify-des-1.0.2.tgz#3af4f1f59839403572f1c66204375f7a7f703e9c"
- integrity sha512-BioO1xf3hFwz4kc6iBhI3ieDFompMhrMlnDFC4/0/vd5MokpuAc3R+LYbwTA9A5Yc9pq9UYPqffKpW2ObuwX5A==
- dependencies:
- cipher-base "^1.0.1"
- des.js "^1.0.0"
- inherits "^2.0.1"
- safe-buffer "^5.1.2"
-
-browserify-rsa@^4.0.0, browserify-rsa@^4.0.1:
- version "4.0.1"
- resolved "https://registry.yarnpkg.com/browserify-rsa/-/browserify-rsa-4.0.1.tgz#21e0abfaf6f2029cf2fafb133567a701d4135524"
- integrity sha1-IeCr+vbyApzy+vsTNWenAdQTVSQ=
- dependencies:
- bn.js "^4.1.0"
- randombytes "^2.0.1"
-
-browserify-sign@^4.0.0:
- version "4.2.1"
- resolved "https://registry.yarnpkg.com/browserify-sign/-/browserify-sign-4.2.1.tgz#eaf4add46dd54be3bb3b36c0cf15abbeba7956c3"
- integrity sha512-/vrA5fguVAKKAVTNJjgSm1tRQDHUU6DbwO9IROu/0WAzC8PKhucDSh18J0RMvVeHAn5puMd+QHC2erPRNf8lmg==
- dependencies:
- bn.js "^5.1.1"
- browserify-rsa "^4.0.1"
- create-hash "^1.2.0"
- create-hmac "^1.1.7"
- elliptic "^6.5.3"
- inherits "^2.0.4"
- parse-asn1 "^5.1.5"
- readable-stream "^3.6.0"
- safe-buffer "^5.2.0"
-
-browserify-zlib@^0.2.0:
- version "0.2.0"
- resolved "https://registry.yarnpkg.com/browserify-zlib/-/browserify-zlib-0.2.0.tgz#2869459d9aa3be245fe8fe2ca1f46e2e7f54d73f"
- integrity sha512-Z942RysHXmJrhqk88FmKBVq/v5tqmSkDz7p54G/MGyjMnCFFnC79XWNbg+Vta8W6Wb2qtSZTSxIGkJrRpCFEiA==
- dependencies:
- pako "~1.0.5"
-
-buffer-xor@^1.0.3:
- version "1.0.3"
- resolved "https://registry.yarnpkg.com/buffer-xor/-/buffer-xor-1.0.3.tgz#26e61ed1422fb70dd42e6e36729ed51d855fe8d9"
- integrity sha1-JuYe0UIvtw3ULm42cp7VHYVf6Nk=
-
-buffer@^4.3.0:
- version "4.9.2"
- resolved "https://registry.yarnpkg.com/buffer/-/buffer-4.9.2.tgz#230ead344002988644841ab0244af8c44bbe3ef8"
- integrity sha512-xq+q3SRMOxGivLhBNaUdC64hDTQwejJ+H0T/NB1XMtTVEwNTrfFF3gAxiyW0Bu/xWEGhjVKgUcMhCrUy2+uCWg==
- dependencies:
- base64-js "^1.0.2"
- ieee754 "^1.1.4"
- isarray "^1.0.0"
-
-builtin-status-codes@^3.0.0:
- version "3.0.0"
- resolved "https://registry.yarnpkg.com/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz#85982878e21b98e1c66425e03d0174788f569ee8"
- integrity sha1-hZgoeOIbmOHGZCXgPQF0eI9Wnug=
-
-cipher-base@^1.0.0, cipher-base@^1.0.1, cipher-base@^1.0.3:
- version "1.0.4"
- resolved "https://registry.yarnpkg.com/cipher-base/-/cipher-base-1.0.4.tgz#8760e4ecc272f4c363532f926d874aae2c1397de"
- integrity sha512-Kkht5ye6ZGmwv40uUDZztayT2ThLQGfnj/T71N/XzeZeo3nf8foyW7zGTsPYkEya3m5f3cAypH+qe7YOrM1U2Q==
- dependencies:
- inherits "^2.0.1"
- safe-buffer "^5.0.1"
-
-console-browserify@^1.1.0:
- version "1.2.0"
- resolved "https://registry.yarnpkg.com/console-browserify/-/console-browserify-1.2.0.tgz#67063cef57ceb6cf4993a2ab3a55840ae8c49336"
- integrity sha512-ZMkYO/LkF17QvCPqM0gxw8yUzigAOZOSWSHg91FH6orS7vcEj5dVZTidN2fQ14yBSdg97RqhSNwLUXInd52OTA==
-
-constants-browserify@^1.0.0:
- version "1.0.0"
- resolved "https://registry.yarnpkg.com/constants-browserify/-/constants-browserify-1.0.0.tgz#c20b96d8c617748aaf1c16021760cd27fcb8cb75"
- integrity sha1-wguW2MYXdIqvHBYCF2DNJ/y4y3U=
-
-core-util-is@~1.0.0:
- version "1.0.2"
- resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7"
- integrity sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=
-
-create-ecdh@^4.0.0:
- version "4.0.4"
- resolved "https://registry.yarnpkg.com/create-ecdh/-/create-ecdh-4.0.4.tgz#d6e7f4bffa66736085a0762fd3a632684dabcc4e"
- integrity sha512-mf+TCx8wWc9VpuxfP2ht0iSISLZnt0JgWlrOKZiNqyUZWnjIaCIVNQArMHnCZKfEYRg6IM7A+NeJoN8gf/Ws0A==
- dependencies:
- bn.js "^4.1.0"
- elliptic "^6.5.3"
-
-create-hash@^1.1.0, create-hash@^1.1.2, create-hash@^1.2.0:
- version "1.2.0"
- resolved "https://registry.yarnpkg.com/create-hash/-/create-hash-1.2.0.tgz#889078af11a63756bcfb59bd221996be3a9ef196"
- integrity sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==
- dependencies:
- cipher-base "^1.0.1"
- inherits "^2.0.1"
- md5.js "^1.3.4"
- ripemd160 "^2.0.1"
- sha.js "^2.4.0"
-
-create-hmac@^1.1.0, create-hmac@^1.1.4, create-hmac@^1.1.7:
- version "1.1.7"
- resolved "https://registry.yarnpkg.com/create-hmac/-/create-hmac-1.1.7.tgz#69170c78b3ab957147b2b8b04572e47ead2243ff"
- integrity sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg==
- dependencies:
- cipher-base "^1.0.3"
- create-hash "^1.1.0"
- inherits "^2.0.1"
- ripemd160 "^2.0.0"
- safe-buffer "^5.0.1"
- sha.js "^2.4.8"
-
-crypto-browserify@^3.11.0:
- version "3.12.0"
- resolved "https://registry.yarnpkg.com/crypto-browserify/-/crypto-browserify-3.12.0.tgz#396cf9f3137f03e4b8e532c58f698254e00f80ec"
- integrity sha512-fz4spIh+znjO2VjL+IdhEpRJ3YN6sMzITSBijk6FK2UvTqruSQW+/cCZTSNsMiZNvUeq0CqurF+dAbyiGOY6Wg==
- dependencies:
- browserify-cipher "^1.0.0"
- browserify-sign "^4.0.0"
- create-ecdh "^4.0.0"
- create-hash "^1.1.0"
- create-hmac "^1.1.0"
- diffie-hellman "^5.0.0"
- inherits "^2.0.1"
- pbkdf2 "^3.0.3"
- public-encrypt "^4.0.0"
- randombytes "^2.0.0"
- randomfill "^1.0.3"
-
-des.js@^1.0.0:
- version "1.0.1"
- resolved "https://registry.yarnpkg.com/des.js/-/des.js-1.0.1.tgz#5382142e1bdc53f85d86d53e5f4aa7deb91e0843"
- integrity sha512-Q0I4pfFrv2VPd34/vfLrFOoRmlYj3OV50i7fskps1jZWK1kApMWWT9G6RRUeYedLcBDIhnSDaUvJMb3AhUlaEA==
- dependencies:
- inherits "^2.0.1"
- minimalistic-assert "^1.0.0"
-
-diffie-hellman@^5.0.0:
- version "5.0.3"
- resolved "https://registry.yarnpkg.com/diffie-hellman/-/diffie-hellman-5.0.3.tgz#40e8ee98f55a2149607146921c63e1ae5f3d2875"
- integrity sha512-kqag/Nl+f3GwyK25fhUMYj81BUOrZ9IuJsjIcDE5icNM9FJHAVm3VcUDxdLPoQtTuUylWm6ZIknYJwwaPxsUzg==
- dependencies:
- bn.js "^4.1.0"
- miller-rabin "^4.0.0"
- randombytes "^2.0.0"
-
-domain-browser@^1.1.1:
- version "1.2.0"
- resolved "https://registry.yarnpkg.com/domain-browser/-/domain-browser-1.2.0.tgz#3d31f50191a6749dd1375a7f522e823d42e54eda"
- integrity sha512-jnjyiM6eRyZl2H+W8Q/zLMA481hzi0eszAaBUzIVnmYVDBbnLxVNnfu1HgEBvCbL+71FrxMl3E6lpKH7Ge3OXA==
-
-elliptic@^6.5.3:
- version "6.5.3"
- resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.5.3.tgz#cb59eb2efdaf73a0bd78ccd7015a62ad6e0f93d6"
- integrity sha512-IMqzv5wNQf+E6aHeIqATs0tOLeOTwj1QKbRcS3jBbYkl5oLAserA8yJTT7/VyHUYG91PRmPyeQDObKLPpeS4dw==
- dependencies:
- bn.js "^4.4.0"
- brorand "^1.0.1"
- hash.js "^1.0.0"
- hmac-drbg "^1.0.0"
- inherits "^2.0.1"
- minimalistic-assert "^1.0.0"
- minimalistic-crypto-utils "^1.0.0"
-
-events@^3.0.0:
- version "3.2.0"
- resolved "https://registry.yarnpkg.com/events/-/events-3.2.0.tgz#93b87c18f8efcd4202a461aec4dfc0556b639379"
- integrity sha512-/46HWwbfCX2xTawVfkKLGxMifJYQBWMwY1mjywRtb4c9x8l5NP3KoJtnIOiL1hfdRkIuYhETxQlo62IF8tcnlg==
-
-evp_bytestokey@^1.0.0, evp_bytestokey@^1.0.3:
- version "1.0.3"
- resolved "https://registry.yarnpkg.com/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz#7fcbdb198dc71959432efe13842684e0525acb02"
- integrity sha512-/f2Go4TognH/KvCISP7OUsHn85hT9nUkxxA9BEWxFn+Oj9o8ZNLm/40hdlgSLyuOimsrTKLUMEorQexp/aPQeA==
- dependencies:
- md5.js "^1.3.4"
- safe-buffer "^5.1.1"
-
-hash-base@^3.0.0:
- version "3.1.0"
- resolved "https://registry.yarnpkg.com/hash-base/-/hash-base-3.1.0.tgz#55c381d9e06e1d2997a883b4a3fddfe7f0d3af33"
- integrity sha512-1nmYp/rhMDiE7AYkDw+lLwlAzz0AntGIe51F3RfFfEqyQ3feY2eI/NcwC6umIQVOASPMsWJLJScWKSSvzL9IVA==
- dependencies:
- inherits "^2.0.4"
- readable-stream "^3.6.0"
- safe-buffer "^5.2.0"
-
-hash.js@^1.0.0, hash.js@^1.0.3:
- version "1.1.7"
- resolved "https://registry.yarnpkg.com/hash.js/-/hash.js-1.1.7.tgz#0babca538e8d4ee4a0f8988d68866537a003cf42"
- integrity sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==
- dependencies:
- inherits "^2.0.3"
- minimalistic-assert "^1.0.1"
-
-hmac-drbg@^1.0.0:
- version "1.0.1"
- resolved "https://registry.yarnpkg.com/hmac-drbg/-/hmac-drbg-1.0.1.tgz#d2745701025a6c775a6c545793ed502fc0c649a1"
- integrity sha1-0nRXAQJabHdabFRXk+1QL8DGSaE=
- dependencies:
- hash.js "^1.0.3"
- minimalistic-assert "^1.0.0"
- minimalistic-crypto-utils "^1.0.1"
-
-https-browserify@^1.0.0:
- version "1.0.0"
- resolved "https://registry.yarnpkg.com/https-browserify/-/https-browserify-1.0.0.tgz#ec06c10e0a34c0f2faf199f7fd7fc78fffd03c73"
- integrity sha1-7AbBDgo0wPL68Zn3/X/Hj//QPHM=
-
-ieee754@^1.1.4:
- version "1.1.13"
- resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.1.13.tgz#ec168558e95aa181fd87d37f55c32bbcb6708b84"
- integrity sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg==
-
-inherits@2.0.1:
- version "2.0.1"
- resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.1.tgz#b17d08d326b4423e568eff719f91b0b1cbdf69f1"
- integrity sha1-sX0I0ya0Qj5Wjv9xn5GwscvfafE=
-
-inherits@2.0.3:
- version "2.0.3"
- resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de"
- integrity sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=
-
-inherits@^2.0.1, inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.1, inherits@~2.0.3:
- version "2.0.4"
- resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c"
- integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==
-
-isarray@^1.0.0, isarray@~1.0.0:
- version "1.0.0"
- resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11"
- integrity sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=
-
-isexe@^2.0.0:
- version "2.0.0"
- resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10"
- integrity sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=
-
-"js-tokens@^3.0.0 || ^4.0.0":
- version "4.0.0"
- resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499"
- integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==
-
-loose-envify@^1.1.0:
- version "1.4.0"
- resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf"
- integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==
- dependencies:
- js-tokens "^3.0.0 || ^4.0.0"
-
-md5.js@^1.3.4:
- version "1.3.5"
- resolved "https://registry.yarnpkg.com/md5.js/-/md5.js-1.3.5.tgz#b5d07b8e3216e3e27cd728d72f70d1e6a342005f"
- integrity sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg==
- dependencies:
- hash-base "^3.0.0"
- inherits "^2.0.1"
- safe-buffer "^5.1.2"
-
-miller-rabin@^4.0.0:
- version "4.0.1"
- resolved "https://registry.yarnpkg.com/miller-rabin/-/miller-rabin-4.0.1.tgz#f080351c865b0dc562a8462966daa53543c78a4d"
- integrity sha512-115fLhvZVqWwHPbClyntxEVfVDfl9DLLTuJvq3g2O/Oxi8AiNouAHvDSzHS0viUJc+V5vm3eq91Xwqn9dp4jRA==
- dependencies:
- bn.js "^4.0.0"
- brorand "^1.0.1"
-
-minimalistic-assert@^1.0.0, minimalistic-assert@^1.0.1:
- version "1.0.1"
- resolved "https://registry.yarnpkg.com/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz#2e194de044626d4a10e7f7fbc00ce73e83e4d5c7"
- integrity sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==
-
-minimalistic-crypto-utils@^1.0.0, minimalistic-crypto-utils@^1.0.1:
- version "1.0.1"
- resolved "https://registry.yarnpkg.com/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz#f6c00c1c0b082246e5c4d99dfb8c7c083b2b582a"
- integrity sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo=
-
-node-libs-browser@^2.2.1:
- version "2.2.1"
- resolved "https://registry.yarnpkg.com/node-libs-browser/-/node-libs-browser-2.2.1.tgz#b64f513d18338625f90346d27b0d235e631f6425"
- integrity sha512-h/zcD8H9kaDZ9ALUWwlBUDo6TKF8a7qBSCSEGfjTVIYeqsioSKaAX+BN7NgiMGp6iSIXZ3PxgCu8KS3b71YK5Q==
- dependencies:
- assert "^1.1.1"
- browserify-zlib "^0.2.0"
- buffer "^4.3.0"
- console-browserify "^1.1.0"
- constants-browserify "^1.0.0"
- crypto-browserify "^3.11.0"
- domain-browser "^1.1.1"
- events "^3.0.0"
- https-browserify "^1.0.0"
- os-browserify "^0.3.0"
- path-browserify "0.0.1"
- process "^0.11.10"
- punycode "^1.2.4"
- querystring-es3 "^0.2.0"
- readable-stream "^2.3.3"
- stream-browserify "^2.0.1"
- stream-http "^2.7.2"
- string_decoder "^1.0.0"
- timers-browserify "^2.0.4"
- tty-browserify "0.0.0"
- url "^0.11.0"
- util "^0.11.0"
- vm-browserify "^1.0.1"
-
-object-assign@^4.1.1:
- version "4.1.1"
- resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863"
- integrity sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=
-
-os-browserify@^0.3.0:
- version "0.3.0"
- resolved "https://registry.yarnpkg.com/os-browserify/-/os-browserify-0.3.0.tgz#854373c7f5c2315914fc9bfc6bd8238fdda1ec27"
- integrity sha1-hUNzx/XCMVkU/Jv8a9gjj92h7Cc=
-
-pako@~1.0.5:
- version "1.0.11"
- resolved "https://registry.yarnpkg.com/pako/-/pako-1.0.11.tgz#6c9599d340d54dfd3946380252a35705a6b992bf"
- integrity sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==
-
-parse-asn1@^5.0.0, parse-asn1@^5.1.5:
- version "5.1.6"
- resolved "https://registry.yarnpkg.com/parse-asn1/-/parse-asn1-5.1.6.tgz#385080a3ec13cb62a62d39409cb3e88844cdaed4"
- integrity sha512-RnZRo1EPU6JBnra2vGHj0yhp6ebyjBZpmUCLHWiFhxlzvBCCpAuZ7elsBp1PVAbQN0/04VD/19rfzlBSwLstMw==
- dependencies:
- asn1.js "^5.2.0"
- browserify-aes "^1.0.0"
- evp_bytestokey "^1.0.0"
- pbkdf2 "^3.0.3"
- safe-buffer "^5.1.1"
-
-path-browserify@0.0.1:
- version "0.0.1"
- resolved "https://registry.yarnpkg.com/path-browserify/-/path-browserify-0.0.1.tgz#e6c4ddd7ed3aa27c68a20cc4e50e1a4ee83bbc4a"
- integrity sha512-BapA40NHICOS+USX9SN4tyhq+A2RrN/Ws5F0Z5aMHDp98Fl86lX8Oti8B7uN93L4Ifv4fHOEA+pQw87gmMO/lQ==
-
-pbkdf2@^3.0.3:
- version "3.1.1"
- resolved "https://registry.yarnpkg.com/pbkdf2/-/pbkdf2-3.1.1.tgz#cb8724b0fada984596856d1a6ebafd3584654b94"
- integrity sha512-4Ejy1OPxi9f2tt1rRV7Go7zmfDQ+ZectEQz3VGUQhgq62HtIRPDyG/JtnwIxs6x3uNMwo2V7q1fMvKjb+Tnpqg==
- dependencies:
- create-hash "^1.1.2"
- create-hmac "^1.1.4"
- ripemd160 "^2.0.1"
- safe-buffer "^5.0.1"
- sha.js "^2.4.8"
-
-process-nextick-args@~2.0.0:
- version "2.0.1"
- resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2"
- integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==
-
-process@^0.11.10:
- version "0.11.10"
- resolved "https://registry.yarnpkg.com/process/-/process-0.11.10.tgz#7332300e840161bda3e69a1d1d91a7d4bc16f182"
- integrity sha1-czIwDoQBYb2j5podHZGn1LwW8YI=
-
-public-encrypt@^4.0.0:
- version "4.0.3"
- resolved "https://registry.yarnpkg.com/public-encrypt/-/public-encrypt-4.0.3.tgz#4fcc9d77a07e48ba7527e7cbe0de33d0701331e0"
- integrity sha512-zVpa8oKZSz5bTMTFClc1fQOnyyEzpl5ozpi1B5YcvBrdohMjH2rfsBtyXcuNuwjsDIXmBYlF2N5FlJYhR29t8Q==
- dependencies:
- bn.js "^4.1.0"
- browserify-rsa "^4.0.0"
- create-hash "^1.1.0"
- parse-asn1 "^5.0.0"
- randombytes "^2.0.1"
- safe-buffer "^5.1.2"
-
-punycode@1.3.2:
- version "1.3.2"
- resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.3.2.tgz#9653a036fb7c1ee42342f2325cceefea3926c48d"
- integrity sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0=
-
-punycode@^1.2.4:
- version "1.4.1"
- resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e"
- integrity sha1-wNWmOycYgArY4esPpSachN1BhF4=
-
-querystring-es3@^0.2.0:
- version "0.2.1"
- resolved "https://registry.yarnpkg.com/querystring-es3/-/querystring-es3-0.2.1.tgz#9ec61f79049875707d69414596fd907a4d711e73"
- integrity sha1-nsYfeQSYdXB9aUFFlv2Qek1xHnM=
-
-querystring@0.2.0:
- version "0.2.0"
- resolved "https://registry.yarnpkg.com/querystring/-/querystring-0.2.0.tgz#b209849203bb25df820da756e747005878521620"
- integrity sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=
-
-randombytes@^2.0.0, randombytes@^2.0.1, randombytes@^2.0.5:
- version "2.1.0"
- resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.1.0.tgz#df6f84372f0270dc65cdf6291349ab7a473d4f2a"
- integrity sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==
- dependencies:
- safe-buffer "^5.1.0"
-
-randomfill@^1.0.3:
- version "1.0.4"
- resolved "https://registry.yarnpkg.com/randomfill/-/randomfill-1.0.4.tgz#c92196fc86ab42be983f1bf31778224931d61458"
- integrity sha512-87lcbR8+MhcWcUiQ+9e+Rwx8MyR2P7qnt15ynUlbm3TU/fjbgz4GsvfSUDTemtCCtVCqb4ZcEFlyPNTh9bBTLw==
- dependencies:
- randombytes "^2.0.5"
- safe-buffer "^5.1.0"
-
-react-dom@17.0.1:
- version "17.0.1"
- resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-17.0.1.tgz#1de2560474ec9f0e334285662ede52dbc5426fc6"
- integrity sha512-6eV150oJZ9U2t9svnsspTMrWNyHc6chX0KzDeAOXftRa8bNeOKTTfCJ7KorIwenkHd2xqVTBTCZd79yk/lx/Ug==
- dependencies:
- loose-envify "^1.1.0"
- object-assign "^4.1.1"
- scheduler "^0.20.1"
-
-react@17.0.1:
- version "17.0.1"
- resolved "https://registry.yarnpkg.com/react/-/react-17.0.1.tgz#6e0600416bd57574e3f86d92edba3d9008726127"
- integrity sha512-lG9c9UuMHdcAexXtigOZLX8exLWkW0Ku29qPRU8uhF2R9BN96dLCt0psvzPLlHc5OWkgymP3qwTRgbnw5BKx3w==
- dependencies:
- loose-envify "^1.1.0"
- object-assign "^4.1.1"
-
-readable-stream@^2.0.2, readable-stream@^2.3.3, readable-stream@^2.3.6:
- version "2.3.7"
- resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.7.tgz#1eca1cf711aef814c04f62252a36a62f6cb23b57"
- integrity sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==
- dependencies:
- core-util-is "~1.0.0"
- inherits "~2.0.3"
- isarray "~1.0.0"
- process-nextick-args "~2.0.0"
- safe-buffer "~5.1.1"
- string_decoder "~1.1.1"
- util-deprecate "~1.0.1"
-
-readable-stream@^3.6.0:
- version "3.6.0"
- resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.0.tgz#337bbda3adc0706bd3e024426a286d4b4b2c9198"
- integrity sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==
- dependencies:
- inherits "^2.0.3"
- string_decoder "^1.1.1"
- util-deprecate "^1.0.1"
-
-readline-sync@^1.4.7:
- version "1.4.10"
- resolved "https://registry.yarnpkg.com/readline-sync/-/readline-sync-1.4.10.tgz#41df7fbb4b6312d673011594145705bf56d8873b"
- integrity sha512-gNva8/6UAe8QYepIQH/jQ2qn91Qj0B9sYjMBBs3QOB8F2CXcKgLxQaJRP76sWVRQt+QU+8fAkCbCvjjMFu7Ycw==
-
-ripemd160@^2.0.0, ripemd160@^2.0.1:
- version "2.0.2"
- resolved "https://registry.yarnpkg.com/ripemd160/-/ripemd160-2.0.2.tgz#a1c1a6f624751577ba5d07914cbc92850585890c"
- integrity sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA==
- dependencies:
- hash-base "^3.0.0"
- inherits "^2.0.1"
-
-safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.1, safe-buffer@^5.1.2, safe-buffer@^5.2.0, safe-buffer@~5.2.0:
- version "5.2.1"
- resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6"
- integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==
-
-safe-buffer@~5.1.0, safe-buffer@~5.1.1:
- version "5.1.2"
- resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d"
- integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==
-
-safer-buffer@^2.1.0:
- version "2.1.2"
- resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a"
- integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==
-
-scheduler@^0.20.1:
- version "0.20.1"
- resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.20.1.tgz#da0b907e24026b01181ecbc75efdc7f27b5a000c"
- integrity sha512-LKTe+2xNJBNxu/QhHvDR14wUXHRQbVY5ZOYpOGWRzhydZUqrLb2JBvLPY7cAqFmqrWuDED0Mjk7013SZiOz6Bw==
- dependencies:
- loose-envify "^1.1.0"
- object-assign "^4.1.1"
-
-setimmediate@^1.0.4:
- version "1.0.5"
- resolved "https://registry.yarnpkg.com/setimmediate/-/setimmediate-1.0.5.tgz#290cbb232e306942d7d7ea9b83732ab7856f8285"
- integrity sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU=
-
-sha.js@^2.4.0, sha.js@^2.4.8:
- version "2.4.11"
- resolved "https://registry.yarnpkg.com/sha.js/-/sha.js-2.4.11.tgz#37a5cf0b81ecbc6943de109ba2960d1b26584ae7"
- integrity sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==
- dependencies:
- inherits "^2.0.1"
- safe-buffer "^5.0.1"
-
-shadow-cljs-jar@1.3.2:
- version "1.3.2"
- resolved "https://registry.yarnpkg.com/shadow-cljs-jar/-/shadow-cljs-jar-1.3.2.tgz#97273afe1747b6a2311917c1c88d9e243c81957b"
- integrity sha512-XmeffAZHv8z7451kzeq9oKh8fh278Ak+UIOGGrapyqrFBB773xN8vMQ3O7J7TYLnb9BUwcqadKkmgaq7q6fhZg==
-
-shadow-cljs@2.11.8:
- version "2.11.8"
- resolved "https://registry.yarnpkg.com/shadow-cljs/-/shadow-cljs-2.11.8.tgz#34f579a96f90f79f6fac46ff901d81695c2ea0c0"
- integrity sha512-8k2t6lLHDseWTcqizkIyJNVInYTYcd7v8uEE3CWYrlqlNZ+U3TQ4FsUS2pRXUfosNgvdkM7hw61pvwRk+KB5TA==
- dependencies:
- node-libs-browser "^2.2.1"
- readline-sync "^1.4.7"
- shadow-cljs-jar "1.3.2"
- source-map-support "^0.4.15"
- which "^1.3.1"
- ws "^3.0.0"
-
-source-map-support@^0.4.15:
- version "0.4.18"
- resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.4.18.tgz#0286a6de8be42641338594e97ccea75f0a2c585f"
- integrity sha512-try0/JqxPLF9nOjvSta7tVondkP5dwgyLDjVoyMDlmjugT2lRZ1OfsrYTkCd2hkDnJTKRbO/Rl3orm8vlsUzbA==
- dependencies:
- source-map "^0.5.6"
-
-source-map@^0.5.6:
- version "0.5.7"
- resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc"
- integrity sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=
-
-stream-browserify@^2.0.1:
- version "2.0.2"
- resolved "https://registry.yarnpkg.com/stream-browserify/-/stream-browserify-2.0.2.tgz#87521d38a44aa7ee91ce1cd2a47df0cb49dd660b"
- integrity sha512-nX6hmklHs/gr2FuxYDltq8fJA1GDlxKQCz8O/IM4atRqBH8OORmBNgfvW5gG10GT/qQ9u0CzIvr2X5Pkt6ntqg==
- dependencies:
- inherits "~2.0.1"
- readable-stream "^2.0.2"
-
-stream-http@^2.7.2:
- version "2.8.3"
- resolved "https://registry.yarnpkg.com/stream-http/-/stream-http-2.8.3.tgz#b2d242469288a5a27ec4fe8933acf623de6514fc"
- integrity sha512-+TSkfINHDo4J+ZobQLWiMouQYB+UVYFttRA94FpEzzJ7ZdqcL4uUUQ7WkdkI4DSozGmgBUE/a47L+38PenXhUw==
- dependencies:
- builtin-status-codes "^3.0.0"
- inherits "^2.0.1"
- readable-stream "^2.3.6"
- to-arraybuffer "^1.0.0"
- xtend "^4.0.0"
-
-string_decoder@^1.0.0, string_decoder@^1.1.1:
- version "1.3.0"
- resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e"
- integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==
- dependencies:
- safe-buffer "~5.2.0"
-
-string_decoder@~1.1.1:
- version "1.1.1"
- resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8"
- integrity sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==
- dependencies:
- safe-buffer "~5.1.0"
-
-timers-browserify@^2.0.4:
- version "2.0.11"
- resolved "https://registry.yarnpkg.com/timers-browserify/-/timers-browserify-2.0.11.tgz#800b1f3eee272e5bc53ee465a04d0e804c31211f"
- integrity sha512-60aV6sgJ5YEbzUdn9c8kYGIqOubPoUdqQCul3SBAsRCZ40s6Y5cMcrW4dt3/k/EsbLVJNl9n6Vz3fTc+k2GeKQ==
- dependencies:
- setimmediate "^1.0.4"
-
-to-arraybuffer@^1.0.0:
- version "1.0.1"
- resolved "https://registry.yarnpkg.com/to-arraybuffer/-/to-arraybuffer-1.0.1.tgz#7d229b1fcc637e466ca081180836a7aabff83f43"
- integrity sha1-fSKbH8xjfkZsoIEYCDanqr/4P0M=
-
-tty-browserify@0.0.0:
- version "0.0.0"
- resolved "https://registry.yarnpkg.com/tty-browserify/-/tty-browserify-0.0.0.tgz#a157ba402da24e9bf957f9aa69d524eed42901a6"
- integrity sha1-oVe6QC2iTpv5V/mqadUk7tQpAaY=
-
-ultron@~1.1.0:
- version "1.1.1"
- resolved "https://registry.yarnpkg.com/ultron/-/ultron-1.1.1.tgz#9fe1536a10a664a65266a1e3ccf85fd36302bc9c"
- integrity sha512-UIEXBNeYmKptWH6z8ZnqTeS8fV74zG0/eRU9VGkpzz+LIJNs8W/zM/L+7ctCkRrgbNnnR0xxw4bKOr0cW0N0Og==
-
-url@^0.11.0:
- version "0.11.0"
- resolved "https://registry.yarnpkg.com/url/-/url-0.11.0.tgz#3838e97cfc60521eb73c525a8e55bfdd9e2e28f1"
- integrity sha1-ODjpfPxgUh63PFJajlW/3Z4uKPE=
- dependencies:
- punycode "1.3.2"
- querystring "0.2.0"
-
-util-deprecate@^1.0.1, util-deprecate@~1.0.1:
- version "1.0.2"
- resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf"
- integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=
-
-util@0.10.3:
- version "0.10.3"
- resolved "https://registry.yarnpkg.com/util/-/util-0.10.3.tgz#7afb1afe50805246489e3db7fe0ed379336ac0f9"
- integrity sha1-evsa/lCAUkZInj23/g7TeTNqwPk=
- dependencies:
- inherits "2.0.1"
-
-util@^0.11.0:
- version "0.11.1"
- resolved "https://registry.yarnpkg.com/util/-/util-0.11.1.tgz#3236733720ec64bb27f6e26f421aaa2e1b588d61"
- integrity sha512-HShAsny+zS2TZfaXxD9tYj4HQGlBezXZMZuM/S5PKLLoZkShZiGk9o5CzukI1LVHZvjdvZ2Sj1aW/Ndn2NB/HQ==
- dependencies:
- inherits "2.0.3"
-
-vm-browserify@^1.0.1:
- version "1.1.2"
- resolved "https://registry.yarnpkg.com/vm-browserify/-/vm-browserify-1.1.2.tgz#78641c488b8e6ca91a75f511e7a3b32a86e5dda0"
- integrity sha512-2ham8XPWTONajOR0ohOKOHXkm3+gaBmGut3SRuu75xLd/RRaY6vqgh8NBYYk7+RW3u5AtzPQZG8F10LHkl0lAQ==
-
-which@^1.3.1:
- version "1.3.1"
- resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a"
- integrity sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==
- dependencies:
- isexe "^2.0.0"
-
-ws@^3.0.0:
- version "3.3.3"
- resolved "https://registry.yarnpkg.com/ws/-/ws-3.3.3.tgz#f1cf84fe2d5e901ebce94efaece785f187a228f2"
- integrity sha512-nnWLa/NwZSt4KQJu51MYlCcSQ5g7INpOrOMt4XV8j4dqTXdmlUmSHQ8/oLC069ckre0fRsgfvsKwbTdtKLCDkA==
- dependencies:
- async-limiter "~1.0.0"
- safe-buffer "~5.1.0"
- ultron "~1.1.0"
-
-xtend@^4.0.0:
- version "4.0.2"
- resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54"
- integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==
+# This file is generated by running "yarn install" inside your project.
+# Manual changes might be lost - proceed with caution!
+
+__metadata:
+ version: 8
+ cacheKey: 10c0
+
+"base64-js@npm:^1.3.1":
+ version: 1.5.1
+ resolution: "base64-js@npm:1.5.1"
+ checksum: 10c0/f23823513b63173a001030fae4f2dabe283b99a9d324ade3ad3d148e218134676f1ee8568c877cd79ec1c53158dcf2d2ba527a97c606618928ba99dd930102bf
+ languageName: node
+ linkType: hard
+
+"buffer-from@npm:^1.0.0":
+ version: 1.1.2
+ resolution: "buffer-from@npm:1.1.2"
+ checksum: 10c0/124fff9d66d691a86d3b062eff4663fe437a9d9ee4b47b1b9e97f5a5d14f6d5399345db80f796827be7c95e70a8e765dd404b7c3ff3b3324f98e9b0c8826cc34
+ languageName: node
+ linkType: hard
+
+"buffer@npm:^6.0.3":
+ version: 6.0.3
+ resolution: "buffer@npm:6.0.3"
+ dependencies:
+ base64-js: "npm:^1.3.1"
+ ieee754: "npm:^1.2.1"
+ checksum: 10c0/2a905fbbcde73cc5d8bd18d1caa23715d5f83a5935867c2329f0ac06104204ba7947be098fe1317fbd8830e26090ff8e764f08cd14fefc977bb248c3487bcbd0
+ languageName: node
+ linkType: hard
+
+"ieee754@npm:^1.2.1":
+ version: 1.2.1
+ resolution: "ieee754@npm:1.2.1"
+ checksum: 10c0/b0782ef5e0935b9f12883a2e2aa37baa75da6e66ce6515c168697b42160807d9330de9a32ec1ed73149aea02e0d822e572bca6f1e22bdcbd2149e13b050b17bb
+ languageName: node
+ linkType: hard
+
+"isexe@npm:^3.1.1":
+ version: 3.1.1
+ resolution: "isexe@npm:3.1.1"
+ checksum: 10c0/9ec257654093443eb0a528a9c8cbba9c0ca7616ccb40abd6dde7202734d96bb86e4ac0d764f0f8cd965856aacbff2f4ce23e730dc19dfb41e3b0d865ca6fdcc7
+ languageName: node
+ linkType: hard
+
+"process@npm:^0.11.10":
+ version: 0.11.10
+ resolution: "process@npm:0.11.10"
+ checksum: 10c0/40c3ce4b7e6d4b8c3355479df77aeed46f81b279818ccdc500124e6a5ab882c0cc81ff7ea16384873a95a74c4570b01b120f287abbdd4c877931460eca6084b3
+ languageName: node
+ linkType: hard
+
+"react-dom@npm:19.1.0":
+ version: 19.1.0
+ resolution: "react-dom@npm:19.1.0"
+ dependencies:
+ scheduler: "npm:^0.26.0"
+ peerDependencies:
+ react: ^19.1.0
+ checksum: 10c0/3e26e89bb6c67c9a6aa86cb888c7a7f8258f2e347a6d2a15299c17eb16e04c19194e3452bc3255bd34000a61e45e2cb51e46292392340432f133e5a5d2dfb5fc
+ languageName: node
+ linkType: hard
+
+"react@npm:19.1.0":
+ version: 19.1.0
+ resolution: "react@npm:19.1.0"
+ checksum: 10c0/530fb9a62237d54137a13d2cfb67a7db6a2156faed43eecc423f4713d9b20c6f2728b026b45e28fcd72e8eadb9e9ed4b089e99f5e295d2f0ad3134251bdd3698
+ languageName: node
+ linkType: hard
+
+"readline-sync@npm:^1.4.10":
+ version: 1.4.10
+ resolution: "readline-sync@npm:1.4.10"
+ checksum: 10c0/0a4d0fe4ad501f8f005a3c9cbf3cc0ae6ca2ced93e9a1c7c46f226bdfcb6ef5d3f437ae7e9d2e1098ee13524a3739c830e4c8dbc7f543a693eecd293e41093a3
+ languageName: node
+ linkType: hard
+
+"rumext@workspace:.":
+ version: 0.0.0-use.local
+ resolution: "rumext@workspace:."
+ dependencies:
+ process: "npm:^0.11.10"
+ react: "npm:19.1.0"
+ react-dom: "npm:19.1.0"
+ shadow-cljs: "npm:3.1.3"
+ languageName: unknown
+ linkType: soft
+
+"scheduler@npm:^0.26.0":
+ version: 0.26.0
+ resolution: "scheduler@npm:0.26.0"
+ checksum: 10c0/5b8d5bfddaae3513410eda54f2268e98a376a429931921a81b5c3a2873aab7ca4d775a8caac5498f8cbc7d0daeab947cf923dbd8e215d61671f9f4e392d34356
+ languageName: node
+ linkType: hard
+
+"shadow-cljs-jar@npm:1.3.4":
+ version: 1.3.4
+ resolution: "shadow-cljs-jar@npm:1.3.4"
+ checksum: 10c0/c5548bb5f2bda5e0a90df6f42e4ec3a07ed4c72cdebb87619e8d9a2167bb3d4b60d6f6a305a3e15cbfb379d5fdbe2a989a0e7059b667cfb3911bc198a4489e94
+ languageName: node
+ linkType: hard
+
+"shadow-cljs@npm:3.1.3":
+ version: 3.1.3
+ resolution: "shadow-cljs@npm:3.1.3"
+ dependencies:
+ buffer: "npm:^6.0.3"
+ process: "npm:^0.11.10"
+ readline-sync: "npm:^1.4.10"
+ shadow-cljs-jar: "npm:1.3.4"
+ source-map-support: "npm:^0.5.21"
+ which: "npm:^5.0.0"
+ ws: "npm:^8.18.1"
+ bin:
+ shadow-cljs: cli/runner.js
+ checksum: 10c0/aee011854e0646b7b6f483b47cba573263477cd5b39bd5edb35830233cd7f6c2db4d98a629cdf003f81f9e4818f81fa00b5ccfa684dbf74156889c93ec80a666
+ languageName: node
+ linkType: hard
+
+"source-map-support@npm:^0.5.21":
+ version: 0.5.21
+ resolution: "source-map-support@npm:0.5.21"
+ dependencies:
+ buffer-from: "npm:^1.0.0"
+ source-map: "npm:^0.6.0"
+ checksum: 10c0/9ee09942f415e0f721d6daad3917ec1516af746a8120bba7bb56278707a37f1eb8642bde456e98454b8a885023af81a16e646869975f06afc1a711fb90484e7d
+ languageName: node
+ linkType: hard
+
+"source-map@npm:^0.6.0":
+ version: 0.6.1
+ resolution: "source-map@npm:0.6.1"
+ checksum: 10c0/ab55398007c5e5532957cb0beee2368529618ac0ab372d789806f5718123cc4367d57de3904b4e6a4170eb5a0b0f41373066d02ca0735a0c4d75c7d328d3e011
+ languageName: node
+ linkType: hard
+
+"which@npm:^5.0.0":
+ version: 5.0.0
+ resolution: "which@npm:5.0.0"
+ dependencies:
+ isexe: "npm:^3.1.1"
+ bin:
+ node-which: bin/which.js
+ checksum: 10c0/e556e4cd8b7dbf5df52408c9a9dd5ac6518c8c5267c8953f5b0564073c66ed5bf9503b14d876d0e9c7844d4db9725fb0dcf45d6e911e17e26ab363dc3965ae7b
+ languageName: node
+ linkType: hard
+
+"ws@npm:^8.18.1":
+ version: 8.18.2
+ resolution: "ws@npm:8.18.2"
+ peerDependencies:
+ bufferutil: ^4.0.1
+ utf-8-validate: ">=5.0.2"
+ peerDependenciesMeta:
+ bufferutil:
+ optional: true
+ utf-8-validate:
+ optional: true
+ checksum: 10c0/4b50f67931b8c6943c893f59c524f0e4905bbd183016cfb0f2b8653aa7f28dad4e456b9d99d285bbb67cca4fedd9ce90dfdfaa82b898a11414ebd66ee99141e4
+ languageName: node
+ linkType: hard