diff --git a/README.md b/README.md
new file mode 100644
index 0000000..5f17dcc
--- /dev/null
+++ b/README.md
@@ -0,0 +1,145 @@
+# react-superaction
+
+Turn the virtual-dom into a declarative event-bus.
+
+(a port of [superaction](https://github.com/w-lfpup/superaction-js) for React)
+
+## Install
+
+Install via npm.
+
+```sh
+npm install --save-dev @w-lfpup/react-superaction
+```
+
+Or install directly from github.
+
+```sh
+npm install --save-dev https://github.com/w-lfpup/react-superaction
+```
+
+## Setup
+
+Add a `SuperActionProvider` component to broadcast action events.
+
+The `SuperActionProvider` component below listens for click events. React developers can access action events.
+
+```tsx
+import React from "react";
+import ReactDOM from "react-dom/client";
+import { SuperActionProvider } from "@w-lfpup/react-superaction";
+import { Counter } from "./counter.js";
+
+let rootEl = document.querySelector("##root")!;
+const root = ReactDOM.createRoot(rootEl);
+
+let eventNames: string[] = ["click"];
+
+root.render(
+
+
+ ,
+);
+```
+
+## Declare
+
+Add an attribute with the pattern `event-=action`.
+
+```html
++
+```
+
+Now the button dispatches ActionEvents when clicked.
+
+## Listen
+
+The `useSuperAction` hook connects action events to react-land.
+
+```tsx
+import React, { useState } from "react";
+import { ActionInterface, useSuperAction } from "@w-lfpup/react-superaction";
+
+export function Counter() {
+ let [count, setCount] = useState(0);
+
+ useSuperAction((action: ActionInterface) => {
+ if ("increment" === action.type) setCount(count + 1);
+ });
+
+ return {count}
+}
+```
+
+The action object has several properties related to an action event including:
+
+- the action type
+- the original dom event
+- the action event target
+- associated formData
+
+```ts
+let { type, event, target, formData } = action;
+```
+
+Form data is available when an action event originates from a element.
+
+## Event stacking
+
+`Superaction-js` listens to any DOM event that bubbles. It also dispatches all actions found along the composed path of a DOM event.
+
+Turns out that's [all UI Events](https://www.w3.org/TR/uievents/#events-uievents). Which is a lot of events!
+
+Consider the following example:
+
+```html
+
+
+ hai :3
+
+
+```
+
+When a person clicks the button above, the order of action events is:
+
+- Action "C"
+- Action "B"
+- Action "A"
+
+## Propagation
+
+Action events propagate similar to DOM events. Their declarative API reflects their DOM Event counterpart:
+
+- `event-prevent-default`
+- `event-stop-propagation`
+- `event-stop-immediate-propagation`
+
+Consider the following example:
+
+```html
+
+
+
+```
+
+So when a person clicks the buttons above, the order of actions is:
+
+Click button C:
+
+- Action "C" dispatched
+- `preventDefault()` is called on the original `PointerEvent`
+- Action "B" dispatched
+- Action propagation is stopped similar to `event.stopImmediatePropagation()`
+- Action "A" does _not_ dispatchß
+
+Click button D:
+
+- Action "D" dispatched
+- Action event propagation stopped similar to `event.stopPropagation()`
+
+## License
+
+React-superaction is released under the BSD-3 Clause License.
diff --git a/examples/counter/bundle.js b/examples/counter/bundle.js
index 29e0209..cce2d46 100644
--- a/examples/counter/bundle.js
+++ b/examples/counter/bundle.js
@@ -30281,7 +30281,6 @@ class SuperAction {
}
#dispatch = this.#unboundDispatch.bind(this);
#unboundDispatch(event) {
- console.log(event);
let { type, currentTarget, target } = event;
if (!currentTarget)
return;
diff --git a/examples/form/bundle.js b/examples/form/bundle.js
index cb2739b..569b8d3 100644
--- a/examples/form/bundle.js
+++ b/examples/form/bundle.js
@@ -30281,7 +30281,6 @@ class SuperAction {
}
#dispatch = this.#unboundDispatch.bind(this);
#unboundDispatch(event) {
- console.log(event);
let { type, currentTarget, target } = event;
if (!currentTarget)
return;
@@ -30341,7 +30340,7 @@ function useSuperAction(cb) {
cb(action);
}
-function CustomForm() {
+function Form() {
let [formAsJSON, setFormAsJSON] = reactExports.useState("");
useSuperAction((action) => {
let { type, formData } = action;
@@ -30365,5 +30364,5 @@ let rootEl = document.querySelector("#root");
if (rootEl) {
const root = ReactDOM.createRoot(rootEl);
root.render(React.createElement(SuperActionProvider, { eventNames: eventNames },
- React.createElement(CustomForm, null)));
+ React.createElement(Form, null)));
}
diff --git a/examples/form/custom_form.js b/examples/form/form.js
similarity index 96%
rename from examples/form/custom_form.js
rename to examples/form/form.js
index 81c96ed..dda811e 100644
--- a/examples/form/custom_form.js
+++ b/examples/form/form.js
@@ -1,6 +1,6 @@
import React, { useState } from "react";
import { useSuperAction } from "../../dist/mod.js";
-export function CustomForm() {
+export function Form() {
let [formAsJSON, setFormAsJSON] = useState("");
useSuperAction((action) => {
let { type, formData } = action;
diff --git a/examples/form/custom_form.tsx b/examples/form/form.tsx
similarity index 96%
rename from examples/form/custom_form.tsx
rename to examples/form/form.tsx
index 405c801..56c12d1 100644
--- a/examples/form/custom_form.tsx
+++ b/examples/form/form.tsx
@@ -2,7 +2,7 @@ import React, { useState } from "react";
import { useSuperAction, useAction } from "../../dist/mod.js";
import { ActionInterface } from "@w-lfpup/superaction";
-export function CustomForm() {
+export function Form() {
let [formAsJSON, setFormAsJSON] = useState("");
useSuperAction((action: ActionInterface) => {
diff --git a/examples/form/root.js b/examples/form/root.js
index 1ea6899..e88de0e 100644
--- a/examples/form/root.js
+++ b/examples/form/root.js
@@ -1,11 +1,11 @@
import React from "react";
import ReactDOM from "react-dom/client";
import { SuperActionProvider } from "../../dist/mod.js";
-import { CustomForm } from "./custom_form.js";
+import { Form } from "./form.js";
let eventNames = ["submit"];
let rootEl = document.querySelector("#root");
if (rootEl) {
const root = ReactDOM.createRoot(rootEl);
root.render(React.createElement(SuperActionProvider, { eventNames: eventNames },
- React.createElement(CustomForm, null)));
+ React.createElement(Form, null)));
}
diff --git a/examples/form/root.tsx b/examples/form/root.tsx
index 6e43eb5..6390dc0 100644
--- a/examples/form/root.tsx
+++ b/examples/form/root.tsx
@@ -1,7 +1,7 @@
import React from "react";
import ReactDOM from "react-dom/client";
import { SuperActionProvider } from "../../dist/mod.js";
-import { CustomForm } from "./custom_form.js";
+import { Form } from "./form.js";
let eventNames: string[] = ["submit"];
@@ -10,7 +10,7 @@ if (rootEl) {
const root = ReactDOM.createRoot(rootEl);
root.render(
-
+
,
);
}
diff --git a/package-lock.json b/package-lock.json
index 1248fe0..973f68b 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,19 +1,27 @@
{
- "name": "superaction-react",
+ "name": "@w-lfpup/react-superaction",
+ "version": "0.1.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
+ "name": "@w-lfpup/react-superaction",
+ "version": "0.1.0",
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "@w-lfpup/superaction": "github:w-lfpup/superaction-js"
+ },
"devDependencies": {
+ "@reduxjs/toolkit": "^2.12.0",
"@rollup/plugin-commonjs": "^29.0.2",
"@rollup/plugin-node-resolve": "^16.0.3",
"@rollup/plugin-replace": "^6.0.3",
"@types/react": "^19.2.15",
"@types/react-dom": "^19.2.3",
- "@w-lfpup/superaction": "github:w-lfpup/superaction-js",
"prettier": "^3.8.3",
"react": "^19.2.6",
"react-dom": "^19.2.6",
+ "react-redux": "^9.3.0",
"rollup": "^4.60.4",
"typescript": "^6.0.3"
}
@@ -25,6 +33,33 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/@reduxjs/toolkit": {
+ "version": "2.12.0",
+ "resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-2.12.0.tgz",
+ "integrity": "sha512-KiT+RzZbp6mQET+Mg+h2c97+9j1sNflUxQkIHI7Yuzf6Peu+OYpmkn6nbHWmLLWj+1ZODUJFwGZ7gx3L9R9EOw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@standard-schema/spec": "^1.0.0",
+ "@standard-schema/utils": "^0.3.0",
+ "immer": "^11.0.0",
+ "redux": "^5.0.1",
+ "redux-thunk": "^3.1.0",
+ "reselect": "^5.1.0"
+ },
+ "peerDependencies": {
+ "react": "^16.9.0 || ^17.0.0 || ^18 || ^19",
+ "react-redux": "^7.2.1 || ^8.1.3 || ^9.0.0"
+ },
+ "peerDependenciesMeta": {
+ "react": {
+ "optional": true
+ },
+ "react-redux": {
+ "optional": true
+ }
+ }
+ },
"node_modules/@rollup/plugin-commonjs": {
"version": "29.0.2",
"resolved": "https://registry.npmjs.org/@rollup/plugin-commonjs/-/plugin-commonjs-29.0.2.tgz",
@@ -214,9 +249,6 @@
"arm"
],
"dev": true,
- "libc": [
- "glibc"
- ],
"license": "MIT",
"optional": true,
"os": [
@@ -231,9 +263,6 @@
"arm"
],
"dev": true,
- "libc": [
- "musl"
- ],
"license": "MIT",
"optional": true,
"os": [
@@ -248,9 +277,6 @@
"arm64"
],
"dev": true,
- "libc": [
- "glibc"
- ],
"license": "MIT",
"optional": true,
"os": [
@@ -265,9 +291,6 @@
"arm64"
],
"dev": true,
- "libc": [
- "musl"
- ],
"license": "MIT",
"optional": true,
"os": [
@@ -282,9 +305,6 @@
"loong64"
],
"dev": true,
- "libc": [
- "glibc"
- ],
"license": "MIT",
"optional": true,
"os": [
@@ -299,9 +319,6 @@
"loong64"
],
"dev": true,
- "libc": [
- "musl"
- ],
"license": "MIT",
"optional": true,
"os": [
@@ -316,9 +333,6 @@
"ppc64"
],
"dev": true,
- "libc": [
- "glibc"
- ],
"license": "MIT",
"optional": true,
"os": [
@@ -333,9 +347,6 @@
"ppc64"
],
"dev": true,
- "libc": [
- "musl"
- ],
"license": "MIT",
"optional": true,
"os": [
@@ -350,9 +361,6 @@
"riscv64"
],
"dev": true,
- "libc": [
- "glibc"
- ],
"license": "MIT",
"optional": true,
"os": [
@@ -367,9 +375,6 @@
"riscv64"
],
"dev": true,
- "libc": [
- "musl"
- ],
"license": "MIT",
"optional": true,
"os": [
@@ -384,9 +389,6 @@
"s390x"
],
"dev": true,
- "libc": [
- "glibc"
- ],
"license": "MIT",
"optional": true,
"os": [
@@ -401,9 +403,6 @@
"x64"
],
"dev": true,
- "libc": [
- "glibc"
- ],
"license": "MIT",
"optional": true,
"os": [
@@ -418,9 +417,6 @@
"x64"
],
"dev": true,
- "libc": [
- "musl"
- ],
"license": "MIT",
"optional": true,
"os": [
@@ -511,6 +507,20 @@
"win32"
]
},
+ "node_modules/@standard-schema/spec": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz",
+ "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@standard-schema/utils": {
+ "version": "0.3.0",
+ "resolved": "https://registry.npmjs.org/@standard-schema/utils/-/utils-0.3.0.tgz",
+ "integrity": "sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/@types/estree": {
"version": "1.0.9",
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz",
@@ -545,10 +555,16 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/@types/use-sync-external-store": {
+ "version": "0.0.6",
+ "resolved": "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.6.tgz",
+ "integrity": "sha512-zFDAD+tlpf2r4asuHEj0XH6pY6i0g5NeAHPn+15wk3BV6JA69eERFXC1gyGThDkVa1zCyKr5jox1+2LbV/AMLg==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/@w-lfpup/superaction": {
"version": "0.4.3",
"resolved": "git+ssh://git@github.com/w-lfpup/superaction-js.git#c9afc93bcd2b482154990db4c0fb864e0a7d03c6",
- "dev": true,
"license": "BSD-3-Clause"
},
"node_modules/commondir": {
@@ -648,6 +664,17 @@
"node": ">= 0.4"
}
},
+ "node_modules/immer": {
+ "version": "11.1.8",
+ "resolved": "https://registry.npmjs.org/immer/-/immer-11.1.8.tgz",
+ "integrity": "sha512-/tbkHMW7y10Lx6i1crLjD4/OhNkRG+Fo7byZHtah0547nIeXYcpIXaUh0IAQY6gO5459qpGGYapcEOHtFXkIuA==",
+ "dev": true,
+ "license": "MIT",
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/immer"
+ }
+ },
"node_modules/is-core-module": {
"version": "2.16.2",
"resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.2.tgz",
@@ -750,6 +777,54 @@
"react": "^19.2.6"
}
},
+ "node_modules/react-redux": {
+ "version": "9.3.0",
+ "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-9.3.0.tgz",
+ "integrity": "sha512-KQopgqFo/p/fgmAs5qz6p5RWaNAzq40WAu7fJIXnQpYxFPbJYtsJPWvGeF2rOBaY/kEuV77AVsX8TsQzKm+A/g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/use-sync-external-store": "^0.0.6",
+ "use-sync-external-store": "^1.4.0"
+ },
+ "peerDependencies": {
+ "@types/react": "^18.2.25 || ^19",
+ "react": "^18.0 || ^19",
+ "redux": "^5.0.0"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "redux": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/redux": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz",
+ "integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/redux-thunk": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-3.1.0.tgz",
+ "integrity": "sha512-NW2r5T6ksUKXCabzhL9z+h206HQw/NJkcLm1GPImRQ8IzfXwRGqjVhKJGauHirT0DAuyy6hjdnMZaRoAcy0Klw==",
+ "dev": true,
+ "license": "MIT",
+ "peerDependencies": {
+ "redux": "^5.0.0"
+ }
+ },
+ "node_modules/reselect": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/reselect/-/reselect-5.2.0.tgz",
+ "integrity": "sha512-AgZ3UOZm3YndfrJ4OYjgrT7bmCm/1iqkjvEfH/oYjzh6PD2qw4QuT3jjnXIrpdt4MTpMXclMT3lXbmRY+XRakw==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/resolve": {
"version": "1.22.12",
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.12.tgz",
@@ -857,6 +932,16 @@
"engines": {
"node": ">=14.17"
}
+ },
+ "node_modules/use-sync-external-store": {
+ "version": "1.6.0",
+ "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.6.0.tgz",
+ "integrity": "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==",
+ "dev": true,
+ "license": "MIT",
+ "peerDependencies": {
+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
+ }
}
}
}
diff --git a/package.json b/package.json
index b12ffbc..4151ffc 100644
--- a/package.json
+++ b/package.json
@@ -1,7 +1,12 @@
{
- "name": "react-superaction",
+ "name": "@w-lfpup/react-superaction",
"type": "module",
+ "main": "dist/mod.js",
+ "description": "Turn the virtual-dom into a declarative event-bus",
+ "license": "BSD-3-Clause",
+ "version": "0.1.0",
"scripts": {
+ "prepare": "npm run build",
"build": "npm run build:core && npm run build:examples",
"build:core": "npx tsc --project src/",
"build:examples": "npx tsc --project examples/",
@@ -12,6 +17,7 @@
"@w-lfpup/superaction": "github:w-lfpup/superaction-js"
},
"devDependencies": {
+ "@reduxjs/toolkit": "^2.12.0",
"@rollup/plugin-commonjs": "^29.0.2",
"@rollup/plugin-node-resolve": "^16.0.3",
"@rollup/plugin-replace": "^6.0.3",
@@ -20,7 +26,12 @@
"prettier": "^3.8.3",
"react": "^19.2.6",
"react-dom": "^19.2.6",
+ "react-redux": "^9.3.0",
"rollup": "^4.60.4",
"typescript": "^6.0.3"
+ },
+ "repository": {
+ "type": "git",
+ "url": "git+https://github.com/w-lfpup/react-superaction.git"
}
}
diff --git a/src/hook.tsx b/src/hook.tsx
index f642ea8..a363da6 100644
--- a/src/hook.tsx
+++ b/src/hook.tsx
@@ -16,19 +16,14 @@ export function useSuperAction(cb: Cb) {
if (action) cb(action);
}
-// single action hook useAction("howdy")
export function useAction(type: string, cb: Cb): ActionInterface | undefined {
let action = useContext(SuperContext);
let [prevAction, setPrevAction] = useState(
undefined,
);
- if (action === prevAction) return;
-
- if (type === action?.type) {
- setPrevAction(action);
- return action;
- }
+ if (action === prevAction || type === action?.type) return;
+ setPrevAction(action);
if (action) cb(action);
}