@mercury-fx/effector
v3.0.0
Published
Mercury Effector for mhs (mercury hyperscript) — theme store, toast model + Toaster, and a form/field factory. Components stay presentational.
Readme
@mercury-fx/effector
Effector-backed state adapters for
@mercury-fx/ui: a theme store,
scope-aware toast queues + <Toaster/>, form, disclosure, selection, and table
models, password strength, and a cooldown timer. State lives outside React, so the components stay
presentational.
Install
npm i @mercury-fx/effector
npm i @mercury-fx/ui react effector effector-react # peer dependenciesUse
import "@mercury-fx/ui/styles.css";
import { Toaster, toast, useTheme, toggleTheme } from "@mercury-fx/effector";
function App() {
const theme = useTheme(); // "light" | "dark"
return (
<>
<button onClick={() => toggleTheme()}>{theme}</button>
<button onClick={() => toast.success("Saved")}>Save</button>
<Toaster />
</>
);
}Controlled components with Effector scopes
Create models outside React. Bind callbacks through the model hooks inside the
Effector Provider; imperative singleton helpers target the global scope.
Mercury UI remains independent of Effector.
import { Dialog, Tabs } from "@mercury-fx/ui";
import { createDisclosure, createSelectionModel } from "@mercury-fx/effector";
const details = createDisclosure();
const section = createSelectionModel({ initialValue: "overview" });
function Details() {
const dialog = details.useProps(); // open, onOpenChange, onClose
const controls = details.useDisclosure(); // isOpen, open, close, toggle, setOpen
const tabs = section.useProps(); // value, onChange
return <>
<button onClick={controls.open}>Open details</button>
<Dialog {...dialog} title="Details">
<Tabs {...tabs} tabs={[
{ value: "overview", label: "Overview", content: "Overview content" },
{ value: "history", label: "History", content: "History content" },
]} />
</Dialog>
</>;
}createSelectionModel accepts an optional pure isAllowed(value) guard and
exposes $value, changed, and reset. Its binding also fits Segmented and
RadioGroup where their value types match. Native Select emits a DOM event, so
adapt it explicitly: onChange={event => binding.onChange(event.target.value)}
for a string-valued model, alongside value={binding.value}.
Table filtering, sorting, selection, and pages
import { Input, Pagination, Table } from "@mercury-fx/ui";
import { createTableModel } from "@mercury-fx/effector";
const people = createTableModel({
rows: [{ id: "ada", name: "Ada", score: 10 }],
getRowKey: (row) => row.id,
pageSize: 20,
selectionMode: "multiple",
filter: (row, query) => row.name.toLowerCase().includes(query.toLowerCase()),
comparators: { name: (a, b) => a.name.localeCompare(b.name), score: (a, b) => a.score - b.score },
});
function People() {
const table = people.useTable();
const pagination = people.usePagination();
const query = people.useQuery();
return <>
<Input aria-label="Filter people" value={query.value} onChange={(event) => query.onChange(event.target.value)} />
<Table {...table} columns={[
{ key: "name", label: "Name", sortable: true },
{ key: "score", label: "Score", sortable: true },
]} />
<Pagination {...pagination} />
</>;
}rows accepts an array or an Effector store. rowsChanged replaces array data;
for a supplied store it overrides data until that store next updates. Filtering
precedes stable sorting and pagination. Sort values use ascending/descending.
Pages are 1-based, clamp after data changes, and reset after query, sort, or page-size
changes. Empty results retain page 1. Selection survives filtering and paging,
but removed or disabled rows lose selection. Row keys must be unique and stable.
A missing comparator leaves the original ordering unchanged.
The model exposes $rows, $query, $sort, $page, $pageSize, $pageCount,
$total, $filteredRows, $sortedRows, $pageRows, and $selectedKeys, together
with rowsChanged, queryChanged, sortChanged, selectionChanged, pageChanged,
pageSizeChanged, and reset. Reset restores control defaults and clears selection;
it retains current source rows.
Forms and numeric controls
createForm keeps its existing useField and useForm APIs. Their callbacks and
submit effect follow the Provider scope. Submission captures that scope's
values, validates, and marks fields touched. Invalid or duplicate pending
submissions resolve false; successful submissions resolve true. Rejections
remain visible to the caller and in $submitError / useForm().submitError.
Reset clears the pending/error state and invalidates old settlements without
canceling the application's underlying request.
import { MoneyInput } from "@mercury-fx/ui";
import { createForm } from "@mercury-fx/effector";
const payment = createForm({ initialValues: { amount: null as number | null } });
function Amount() {
const amount = payment.useField("amount");
return <MoneyInput label="Amount" numericValue={amount.value}
onValueChange={amount.onChange} onBlur={amount.onBlur} />;
}Notification queues
import { Toaster, createToastModel } from "@mercury-fx/effector";
const notifications = createToastModel({ duration: 5000, limit: 5 });
function Save() {
const toast = notifications.useToast();
return <>
<button onClick={() => toast.success("Saved")}>Save</button>
<Toaster model={notifications} label="Notifications" />
</>;
}Mount one Toaster per model and scope. Timers and IDs are isolated across forks;
manual dismissal, queue eviction, clear, and dispose cancel timers. Duration
0 or Infinity keeps a notification open. Hover, focus, a blurred window, and
a hidden document pause timers; elapsed time is retained and independent pause
reasons must all release before timers resume. F8 focuses the labeled notification
region. Mercury Alert supplies the live status/alert announcement.
createToastModel exposes $toasts, $paused, show, dismiss, clear, pause,
resume, dispose, useToasts, and useToast. Call pause(reason) and
resume(reason) with a stable application-specific reason for additional pause
conditions. Dispose clears the queue and timers; a model can be reused afterward.
The global toast, showToast, dismissToast, clearToasts, $toasts, and
useToasts exports remain available. The top-level useToast binds the default
queue to the active Provider scope.
License
MIT
