@nice-code/state 0.3.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -0
- package/build/devtools/browser/index.js +1653 -0
- package/build/index.js +241 -0
- package/build/react/index.js +315 -0
- package/build/types/core/Store.d.ts +135 -0
- package/build/types/core/index.d.ts +1 -0
- package/build/types/devtools/browser/NiceStateDevtools.d.ts +8 -0
- package/build/types/devtools/browser/components/ChangeDetailPanel.d.ts +5 -0
- package/build/types/devtools/browser/components/ChangeList.d.ts +9 -0
- package/build/types/devtools/browser/components/DiffView.d.ts +9 -0
- package/build/types/devtools/browser/components/JsonView.d.ts +7 -0
- package/build/types/devtools/browser/components/PanelChrome.d.ts +54 -0
- package/build/types/devtools/browser/components/SectionLabel.d.ts +4 -0
- package/build/types/devtools/browser/components/StateInspector.d.ts +16 -0
- package/build/types/devtools/browser/components/StoreTabs.d.ts +12 -0
- package/build/types/devtools/browser/components/utils.d.ts +29 -0
- package/build/types/devtools/browser/devtools_dock.d.ts +47 -0
- package/build/types/devtools/browser/index.d.ts +3 -0
- package/build/types/devtools/core/StateDevtools.types.d.ts +43 -0
- package/build/types/devtools/core/StateDevtoolsCore.d.ts +56 -0
- package/build/types/devtools/core/devtools_colors.d.ts +26 -0
- package/build/types/index.d.ts +1 -0
- package/build/types/react/InjectStoreState.d.ts +18 -0
- package/build/types/react/index.d.ts +3 -0
- package/build/types/react/useLocalStore.d.ts +8 -0
- package/build/types/react/useStoreState.d.ts +23 -0
- package/package.json +56 -0
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { CSSProperties } from "react";
|
|
2
|
+
import type { IDevtoolsStateChange } from "../../core/StateDevtools.types";
|
|
3
|
+
export declare function ChangeList({ changes, selectedCuid, onSelect, showStore, style, }: {
|
|
4
|
+
changes: IDevtoolsStateChange[];
|
|
5
|
+
selectedCuid: string | null;
|
|
6
|
+
onSelect: (cuid: string) => void;
|
|
7
|
+
showStore: boolean;
|
|
8
|
+
style?: CSSProperties;
|
|
9
|
+
}): import("react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Test-framework-style diff: only the paths that changed, with removed values in
|
|
3
|
+
* error red on a `−` line and added values in success green on a `+` line. A
|
|
4
|
+
* `changed` entry renders both lines so old → new reads top-to-bottom.
|
|
5
|
+
*/
|
|
6
|
+
export declare function DiffView({ before, after }: {
|
|
7
|
+
before: unknown;
|
|
8
|
+
after: unknown;
|
|
9
|
+
}): import("react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { CSSProperties } from "react";
|
|
2
|
+
export declare function renderColoredJson(text: string): React.ReactNode[];
|
|
3
|
+
export declare function JsonView({ value, indent, style, }: {
|
|
4
|
+
value: unknown;
|
|
5
|
+
indent?: number;
|
|
6
|
+
style?: CSSProperties;
|
|
7
|
+
}): import("react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { type ReactNode } from "react";
|
|
2
|
+
import type { TDevtoolsPosition } from "../../core/StateDevtools.types";
|
|
3
|
+
export type TDockSide = "top" | "bottom" | "left" | "right";
|
|
4
|
+
export declare function getDockSide(pos: TDevtoolsPosition): TDockSide;
|
|
5
|
+
export interface IDevtoolsLauncherItem {
|
|
6
|
+
id: string;
|
|
7
|
+
label: string;
|
|
8
|
+
icon: string;
|
|
9
|
+
badge?: string;
|
|
10
|
+
onOpen: () => void;
|
|
11
|
+
}
|
|
12
|
+
export declare function PanelHeader({ position, onPositionChange, onClose, onClear, paused, onTogglePause, openOthers, children, }: {
|
|
13
|
+
position: TDevtoolsPosition;
|
|
14
|
+
onPositionChange: (p: TDevtoolsPosition) => void;
|
|
15
|
+
onClose: () => void;
|
|
16
|
+
onClear?: () => void;
|
|
17
|
+
paused: boolean;
|
|
18
|
+
onTogglePause: () => void;
|
|
19
|
+
openOthers?: IDevtoolsLauncherItem[];
|
|
20
|
+
children?: ReactNode;
|
|
21
|
+
}): import("react/jsx-runtime").JSX.Element;
|
|
22
|
+
export declare function ResizeHandle({ dockSide, dockedSize, onChange, }: {
|
|
23
|
+
dockSide: TDockSide;
|
|
24
|
+
dockedSize: number;
|
|
25
|
+
onChange: (size: number) => void;
|
|
26
|
+
}): import("react/jsx-runtime").JSX.Element;
|
|
27
|
+
/**
|
|
28
|
+
* Draggable divider between the list and the detail pane. `horizontal` refers to
|
|
29
|
+
* the split axis: a row layout (dock top/bottom) splits horizontally and drags
|
|
30
|
+
* left/right; a column layout (dock left/right) splits vertically. The reported
|
|
31
|
+
* ratio is the fraction of the container the *detail* pane should occupy.
|
|
32
|
+
*/
|
|
33
|
+
export declare function SplitHandle({ horizontal, onRatioChange, }: {
|
|
34
|
+
horizontal: boolean;
|
|
35
|
+
onRatioChange: (ratio: number) => void;
|
|
36
|
+
}): import("react/jsx-runtime").JSX.Element;
|
|
37
|
+
/** A compact segmented toggle used for the Timeline / State mode switch. */
|
|
38
|
+
export declare function SegmentedControl<T extends string>({ options, value, onChange, }: {
|
|
39
|
+
options: {
|
|
40
|
+
value: T;
|
|
41
|
+
label: string;
|
|
42
|
+
}[];
|
|
43
|
+
value: T;
|
|
44
|
+
onChange: (value: T) => void;
|
|
45
|
+
}): import("react/jsx-runtime").JSX.Element;
|
|
46
|
+
/**
|
|
47
|
+
* The combined, page-wide launcher shown while every devtool is collapsed — one
|
|
48
|
+
* grouped pill with a segment per registered devtool, so the buttons never
|
|
49
|
+
* overlap or hide behind each other. Rendered by the coordinator's "primary"
|
|
50
|
+
* devtool only.
|
|
51
|
+
*/
|
|
52
|
+
export declare function DevtoolsLauncher({ items }: {
|
|
53
|
+
items: IDevtoolsLauncherItem[];
|
|
54
|
+
}): import("react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { IDevtoolsStoreInfo } from "../../core/StateDevtools.types";
|
|
2
|
+
/**
|
|
3
|
+
* Live current-state view (top half) plus a direct JSON editor (bottom half) for
|
|
4
|
+
* one store — a fixed 50/50 split so the editor is usable without manual
|
|
5
|
+
* resizing. Editing is the "trigger edits directly for testing" capability: the
|
|
6
|
+
* draft is parsed and handed to {@link StateDevtoolsCore.applyEdit}, which
|
|
7
|
+
* replaces the store state.
|
|
8
|
+
*
|
|
9
|
+
* The draft is seeded from the live state only while it is clean — once the user
|
|
10
|
+
* types, incoming external updates no longer clobber their edit (a "reload"
|
|
11
|
+
* button re-syncs on demand).
|
|
12
|
+
*/
|
|
13
|
+
export declare function StateInspector({ store, onApply, }: {
|
|
14
|
+
store: IDevtoolsStoreInfo;
|
|
15
|
+
onApply: (storeId: string, newState: unknown) => void;
|
|
16
|
+
}): import("react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { IDevtoolsStoreInfo } from "../../core/StateDevtools.types";
|
|
2
|
+
/**
|
|
3
|
+
* Horizontal store filter. `null` selection means "all stores". When
|
|
4
|
+
* `includeAll` is false (the State inspector needs a concrete store) the All
|
|
5
|
+
* pill is omitted.
|
|
6
|
+
*/
|
|
7
|
+
export declare function StoreTabs({ stores, selectedStoreId, onSelect, includeAll, }: {
|
|
8
|
+
stores: IDevtoolsStoreInfo[];
|
|
9
|
+
selectedStoreId: string | null;
|
|
10
|
+
onSelect: (id: string | null) => void;
|
|
11
|
+
includeAll?: boolean;
|
|
12
|
+
}): import("react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { Patch } from "immer";
|
|
2
|
+
import type { IDevtoolsStateChange, TStateChangeSource } from "../../core/StateDevtools.types";
|
|
3
|
+
export declare function safeStringify(value: unknown, indent?: number): string;
|
|
4
|
+
export declare function formatTimestamp(ms: number): string;
|
|
5
|
+
export declare function formatRelativeAge(ms: number): string;
|
|
6
|
+
export declare const SOURCE_LABEL: Record<TStateChangeSource, string>;
|
|
7
|
+
export declare const SOURCE_COLOR: Record<TStateChangeSource, string>;
|
|
8
|
+
/** Render an Immer patch path (`["todos", 0, "done"]`) as `todos.0.done`. */
|
|
9
|
+
export declare function patchPathToString(path: Patch["path"]): string;
|
|
10
|
+
/**
|
|
11
|
+
* A terse, single-line summary of what a change touched, for the timeline row.
|
|
12
|
+
* Prefers a `path: prev → next` form for single scalar replaces, otherwise lists
|
|
13
|
+
* the distinct top-level paths affected.
|
|
14
|
+
*/
|
|
15
|
+
export declare function summarizeChange(change: IDevtoolsStateChange): string;
|
|
16
|
+
export type TDiffKind = "added" | "removed" | "changed";
|
|
17
|
+
export interface IDiffEntry {
|
|
18
|
+
path: string;
|
|
19
|
+
kind: TDiffKind;
|
|
20
|
+
before?: unknown;
|
|
21
|
+
after?: unknown;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Structural diff between two snapshots, flattened to the individual leaf/branch
|
|
25
|
+
* paths that actually changed — the data behind the test-framework-style "Diff"
|
|
26
|
+
* view. Unchanged branches are skipped entirely (Immer's structural sharing
|
|
27
|
+
* means equal branches keep their reference, so this stays cheap).
|
|
28
|
+
*/
|
|
29
|
+
export declare function computeDiff(before: unknown, after: unknown): IDiffEntry[];
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
export type TDockSide = "top" | "bottom" | "left" | "right";
|
|
2
|
+
/** A handle to one registered devtool, used to render launch controls. */
|
|
3
|
+
export interface IDockDevtoolRef {
|
|
4
|
+
id: string;
|
|
5
|
+
label: string;
|
|
6
|
+
icon: string;
|
|
7
|
+
badge?: string;
|
|
8
|
+
onOpen: () => void;
|
|
9
|
+
}
|
|
10
|
+
/** The live, syncable part of a devtool's registration. */
|
|
11
|
+
export interface IDockDevtoolSync {
|
|
12
|
+
side: TDockSide;
|
|
13
|
+
size: number;
|
|
14
|
+
open: boolean;
|
|
15
|
+
badge?: string;
|
|
16
|
+
}
|
|
17
|
+
export interface IDockDevtoolInput extends IDockDevtoolSync {
|
|
18
|
+
id: string;
|
|
19
|
+
label: string;
|
|
20
|
+
icon: string;
|
|
21
|
+
onOpen: () => void;
|
|
22
|
+
}
|
|
23
|
+
export interface IDockView {
|
|
24
|
+
/** Offset (px) from the docked edge — stacks open panels on the same side. */
|
|
25
|
+
dockOffset: number;
|
|
26
|
+
/** Is any devtool on the page currently open? */
|
|
27
|
+
anyOpen: boolean;
|
|
28
|
+
/** First-registered devtool — the one that renders the combined launcher. */
|
|
29
|
+
isPrimary: boolean;
|
|
30
|
+
/** Every registered devtool, for the combined launcher. */
|
|
31
|
+
devtools: IDockDevtoolRef[];
|
|
32
|
+
/** Closed devtools other than this one, for an open panel's header. */
|
|
33
|
+
otherClosed: IDockDevtoolRef[];
|
|
34
|
+
}
|
|
35
|
+
export interface IDevtoolsDockCoordinator {
|
|
36
|
+
version: number;
|
|
37
|
+
register(panel: IDockDevtoolInput): () => void;
|
|
38
|
+
update(id: string, next: IDockDevtoolSync): void;
|
|
39
|
+
getView(id: string): IDockView;
|
|
40
|
+
subscribe(listener: () => void): () => void;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Returns the page-wide dock coordinator, installing it on `window` the first
|
|
44
|
+
* time it is requested. On the server (no `window`) a throwaway instance is
|
|
45
|
+
* returned so callers can use it unconditionally.
|
|
46
|
+
*/
|
|
47
|
+
export declare function getDevtoolsDockCoordinator(): IDevtoolsDockCoordinator;
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
export type { IDevtoolsStateChange, IDevtoolsStoreInfo, IStateDevtoolsSnapshot, TDevtoolsPosition, TStateChangeSource, TStateDevtoolsListener, } from "../core/StateDevtools.types";
|
|
2
|
+
export { type IStateDevtoolsCoreOptions, StateDevtoolsCore, } from "../core/StateDevtoolsCore";
|
|
3
|
+
export { type INiceStateDevtoolsProps, NiceStateDevtools } from "./NiceStateDevtools";
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import type { Patch } from "immer";
|
|
2
|
+
export type TDevtoolsPosition = "dock-bottom" | "dock-top" | "dock-left" | "dock-right";
|
|
3
|
+
/**
|
|
4
|
+
* How a recorded change came to be. `update` is the normal Immer-patch path,
|
|
5
|
+
* `replace` is a whole-state swap with no patches, and the two `devtools-*`
|
|
6
|
+
* sources are writes the devtools itself made (manual edits / reverts) so they
|
|
7
|
+
* can be visually distinguished from real app traffic.
|
|
8
|
+
*/
|
|
9
|
+
export type TStateChangeSource = "update" | "replace" | "devtools-edit" | "devtools-revert";
|
|
10
|
+
/**
|
|
11
|
+
* A single committed mutation of a registered store. Captures both the Immer
|
|
12
|
+
* patches (when available) and full before/after snapshots so the panel can
|
|
13
|
+
* render a precise diff and offer a one-click revert.
|
|
14
|
+
*/
|
|
15
|
+
export interface IDevtoolsStateChange {
|
|
16
|
+
cuid: string;
|
|
17
|
+
storeId: string;
|
|
18
|
+
storeLabel: string;
|
|
19
|
+
timestamp: number;
|
|
20
|
+
patches: Patch[];
|
|
21
|
+
inversePatches: Patch[];
|
|
22
|
+
prevSnapshot: unknown;
|
|
23
|
+
snapshot: unknown;
|
|
24
|
+
source: TStateChangeSource;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Live summary of a registered store, refreshed on every commit so the panel's
|
|
28
|
+
* tabs and state inspector always reflect the current value.
|
|
29
|
+
*/
|
|
30
|
+
export interface IDevtoolsStoreInfo {
|
|
31
|
+
id: string;
|
|
32
|
+
label: string;
|
|
33
|
+
currentState: unknown;
|
|
34
|
+
changeCount: number;
|
|
35
|
+
lastChangeTime?: number;
|
|
36
|
+
}
|
|
37
|
+
export interface IStateDevtoolsSnapshot {
|
|
38
|
+
stores: IDevtoolsStoreInfo[];
|
|
39
|
+
/** Most-recent-first across all registered stores. */
|
|
40
|
+
changes: IDevtoolsStateChange[];
|
|
41
|
+
paused: boolean;
|
|
42
|
+
}
|
|
43
|
+
export type TStateDevtoolsListener = (snapshot: IStateDevtoolsSnapshot) => void;
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import type { Store } from "../../core/Store";
|
|
2
|
+
import type { IDevtoolsStateChange, IStateDevtoolsSnapshot, TStateDevtoolsListener } from "./StateDevtools.types";
|
|
3
|
+
export interface IStateDevtoolsCoreOptions {
|
|
4
|
+
/** Cap on retained changes across all stores (oldest dropped first). */
|
|
5
|
+
maxChanges?: number;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Framework-agnostic collector for nice-state stores.
|
|
9
|
+
*
|
|
10
|
+
* Register any {@link Store} and the core hooks its patch + update streams,
|
|
11
|
+
* pairing the two so each committed mutation becomes a single
|
|
12
|
+
* {@link IDevtoolsStateChange} with patches and full before/after snapshots.
|
|
13
|
+
* The browser panel ({@link NiceStateDevtools}) renders whatever this exposes.
|
|
14
|
+
*
|
|
15
|
+
* Registering a store attaches a patch listener, which makes Immer compute
|
|
16
|
+
* patches for that store's updates — a negligible dev-time cost. Keep this out
|
|
17
|
+
* of production bundles (the panel is dev-gated, but the core is not).
|
|
18
|
+
*/
|
|
19
|
+
export declare class StateDevtoolsCore {
|
|
20
|
+
private readonly _stores;
|
|
21
|
+
private _changes;
|
|
22
|
+
private readonly _listeners;
|
|
23
|
+
private readonly _maxChanges;
|
|
24
|
+
private _paused;
|
|
25
|
+
private _cuidCounter;
|
|
26
|
+
private _sourceOverride;
|
|
27
|
+
constructor(options?: IStateDevtoolsCoreOptions);
|
|
28
|
+
/**
|
|
29
|
+
* Start observing a store under a human-readable label. Returns an
|
|
30
|
+
* unregister function. If the label collides with an existing store, a numeric
|
|
31
|
+
* suffix is appended to keep ids unique.
|
|
32
|
+
*/
|
|
33
|
+
registerStore(label: string, store: Store<any>): () => void;
|
|
34
|
+
unregisterStore(id: string): void;
|
|
35
|
+
/**
|
|
36
|
+
* Replace a registered store's state wholesale — the primary "edit directly
|
|
37
|
+
* for testing" entry point. The resulting change is tagged `devtools-edit`.
|
|
38
|
+
*/
|
|
39
|
+
applyEdit(storeId: string, newState: unknown): void;
|
|
40
|
+
/**
|
|
41
|
+
* Undo a change by applying its inverse patches. Cleanest for the most recent
|
|
42
|
+
* change of a store; older reverts may not apply if later changes touched the
|
|
43
|
+
* same paths. The resulting change is tagged `devtools-revert`.
|
|
44
|
+
*/
|
|
45
|
+
revertChange(change: IDevtoolsStateChange): void;
|
|
46
|
+
setPaused(paused: boolean): void;
|
|
47
|
+
togglePaused(): void;
|
|
48
|
+
/** Drop the recorded timeline; registered stores and their state remain. */
|
|
49
|
+
clear(): void;
|
|
50
|
+
getSnapshot(): IStateDevtoolsSnapshot;
|
|
51
|
+
subscribe(listener: TStateDevtoolsListener): () => void;
|
|
52
|
+
private _recordChange;
|
|
53
|
+
private _uniqueId;
|
|
54
|
+
private _buildSnapshot;
|
|
55
|
+
private _notify;
|
|
56
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export declare const DEVTOOL_COLOR_SEMANTIC_ERROR = "#FF5C5C";
|
|
2
|
+
export declare const DEVTOOL_COLOR_SEMANTIC_SUCCESS = "#A3E635";
|
|
3
|
+
export declare const DEVTOOL_COLOR_SEMANTIC_SYSTEM = "#38BDF8";
|
|
4
|
+
export declare const DEVTOOL_COLOR_SEMANTIC_WARNING = "#FB923C";
|
|
5
|
+
export declare const DEVTOOL_COLOR_SEMANTIC_METADATA = "#A1A1AA";
|
|
6
|
+
export declare const DEVTOOL_COLOR_TEXT_EMPHASIS = "#f1f5f9";
|
|
7
|
+
export declare const DEVTOOL_COLOR_TEXT_SECONDARY = "#cbd5e1";
|
|
8
|
+
export declare const DEVTOOL_COLOR_TEXT_MUTED = "#64748b";
|
|
9
|
+
export declare const DEVTOOL_COLOR_TEXT_FAINT = "#334155";
|
|
10
|
+
export declare const DEVTOOL_LIST_BASE_BACKGROUND = "#0f172a";
|
|
11
|
+
export declare const DEVTOOL_LIST_SELECTED_BACKGROUND = "#1d2942";
|
|
12
|
+
export declare const DEVTOOL_DETAIL_BASE_BACKGROUND = "#0d1729";
|
|
13
|
+
export declare const DEVTOOL_DETAIL_HEADER_BACKGROUND = "#131f35";
|
|
14
|
+
export declare const DEVTOOL_SECTION_BACKGROUND = "#1e293b";
|
|
15
|
+
export declare const DEVTOOL_SECTION_STRING_BACKGROUND = "#0d131f";
|
|
16
|
+
export declare const DEVTOOL_PANEL_BORDER = "#1e293b";
|
|
17
|
+
export declare const DEVTOOL_PANEL_DIVIDER_BORDER = "#1d3352";
|
|
18
|
+
export declare const DEVTOOL_EDITOR_BACKGROUND = "#08101f";
|
|
19
|
+
export declare const DEVTOOL_ERROR_BACKGROUND = "#1e0a0a";
|
|
20
|
+
export declare const DEVTOOL_JSON_KEY = "#a5b4fc";
|
|
21
|
+
export declare const DEVTOOL_JSON_STRING = "#fbbf24";
|
|
22
|
+
export declare const DEVTOOL_JSON_NUMBER = "#34d399";
|
|
23
|
+
export declare const DEVTOOL_JSON_KEYWORD = "#a78bfa";
|
|
24
|
+
export declare const DEVTOOL_JSON_PUNCTUATION = "#475569";
|
|
25
|
+
export declare const MONO_FONT = "ui-monospace, 'Cascadia Code', 'Source Code Pro', monospace";
|
|
26
|
+
export declare const SANS_FONT = "ui-sans-serif, system-ui, sans-serif";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./core";
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { ReactElement } from "react";
|
|
2
|
+
import type { Store } from "../core/Store";
|
|
3
|
+
import { type TEqualityFn } from "./useStoreState";
|
|
4
|
+
export interface IPropsInjectStoreState<S extends object, SS> {
|
|
5
|
+
store: Store<S>;
|
|
6
|
+
on?: (state: S) => SS;
|
|
7
|
+
/**
|
|
8
|
+
* Optional equality function for the selected slice. Defaults to a strict
|
|
9
|
+
* reference check; pass `deepEqual` from `fast-equals` for inline objects.
|
|
10
|
+
*/
|
|
11
|
+
equalityFn?: TEqualityFn<SS>;
|
|
12
|
+
children: (output: SS) => ReactElement;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Render-prop binding: subscribes to `store` (optionally narrowed by `on`) and
|
|
16
|
+
* renders `children` with the selected slice.
|
|
17
|
+
*/
|
|
18
|
+
export declare function InjectStoreState<S extends object, SS = S>({ store, on, equalityFn, children, }: IPropsInjectStoreState<S, SS>): ReactElement;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { Store } from "../core/Store";
|
|
2
|
+
/**
|
|
3
|
+
* Create a component-local {@link Store} that persists across renders. Passing a
|
|
4
|
+
* `deps` array re-creates the store (with a fresh initial state) whenever the
|
|
5
|
+
* dependencies change by shallow comparison.
|
|
6
|
+
*/
|
|
7
|
+
declare function useLocalStore<S extends object>(initialState: (() => S) | S, deps?: ReadonlyArray<unknown>): Store<S>;
|
|
8
|
+
export { useLocalStore };
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { Store } from "../core/Store";
|
|
2
|
+
/**
|
|
3
|
+
* Compares the previously selected value against the next one. Returning `true`
|
|
4
|
+
* tells the hook the value is unchanged, so React is handed the *same*
|
|
5
|
+
* reference and no re-render is scheduled.
|
|
6
|
+
*/
|
|
7
|
+
export type TEqualityFn<SS> = (a: SS, b: SS) => boolean;
|
|
8
|
+
/**
|
|
9
|
+
* Subscribe a React component to a store, optionally narrowing to a derived
|
|
10
|
+
* slice.
|
|
11
|
+
*
|
|
12
|
+
* Built on `useSyncExternalStore`, so it is tear-free under React 18+
|
|
13
|
+
* concurrent rendering with no manual `useState`/`useEffect` subscription loop.
|
|
14
|
+
*
|
|
15
|
+
* A `useRef` cache holds the last selected value. On every store emission the
|
|
16
|
+
* selector is re-evaluated and the result is run through a strict-reference
|
|
17
|
+
* fast-path first; only if the reference differs is `equalityFn` consulted. A
|
|
18
|
+
* new reference is handed back to React exclusively when a genuine change is
|
|
19
|
+
* detected, so equal-but-new selector results never trigger a render.
|
|
20
|
+
*/
|
|
21
|
+
declare function useStoreState<S extends object>(store: Store<S>): S;
|
|
22
|
+
declare function useStoreState<S extends object, SS>(store: Store<S>, getSubState: (state: S) => SS, equalityFn?: TEqualityFn<SS>): SS;
|
|
23
|
+
export { useStoreState };
|
package/package.json
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@nice-code/state",
|
|
3
|
+
"version": "0.3.3",
|
|
4
|
+
"private": false,
|
|
5
|
+
"type": "module",
|
|
6
|
+
"exports": {
|
|
7
|
+
".": {
|
|
8
|
+
"source": "./src/index.ts",
|
|
9
|
+
"types": "./build/types/index.d.ts",
|
|
10
|
+
"import": "./build/index.js"
|
|
11
|
+
},
|
|
12
|
+
"./react": {
|
|
13
|
+
"source": "./src/react/index.ts",
|
|
14
|
+
"types": "./build/types/react/index.d.ts",
|
|
15
|
+
"import": "./build/react/index.js"
|
|
16
|
+
},
|
|
17
|
+
"./devtools/browser": {
|
|
18
|
+
"source": "./src/devtools/browser/index.ts",
|
|
19
|
+
"types": "./build/types/devtools/browser/index.d.ts",
|
|
20
|
+
"import": "./build/devtools/browser/index.js"
|
|
21
|
+
}
|
|
22
|
+
},
|
|
23
|
+
"files": [
|
|
24
|
+
"build",
|
|
25
|
+
"package.json",
|
|
26
|
+
"README.md"
|
|
27
|
+
],
|
|
28
|
+
"publishConfig": {
|
|
29
|
+
"access": "public"
|
|
30
|
+
},
|
|
31
|
+
"scripts": {
|
|
32
|
+
"type-check": "bunx tsc --noEmit",
|
|
33
|
+
"type-check-watch": "bunx tsc --noEmit --watch",
|
|
34
|
+
"clean-build": "bunx rimraf build",
|
|
35
|
+
"vitest": "vitest --typecheck",
|
|
36
|
+
"vitest-agent": "vitest --typecheck --reporter=agent",
|
|
37
|
+
"build": "bun run clean-build && bun run build.ts && bun run build-types",
|
|
38
|
+
"build-watch": "bun run clean-build && bun run build.ts --watch && bun run build-types --watch",
|
|
39
|
+
"build-types": "tsc --project tsconfig.build.json"
|
|
40
|
+
},
|
|
41
|
+
"dependencies": {
|
|
42
|
+
"immer": "11.1.8",
|
|
43
|
+
"fast-equals": "6.0.0"
|
|
44
|
+
},
|
|
45
|
+
"devDependencies": {
|
|
46
|
+
"react": "19.2.7"
|
|
47
|
+
},
|
|
48
|
+
"peerDependencies": {
|
|
49
|
+
"react": ">=19"
|
|
50
|
+
},
|
|
51
|
+
"peerDependenciesMeta": {
|
|
52
|
+
"react": {
|
|
53
|
+
"optional": true
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|