@nice-code/state 0.7.0 → 0.9.0

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.
Files changed (37) hide show
  1. package/build/Store-B65MojT2.d.ts +201 -0
  2. package/build/Store-CI9N0P6I.js +366 -0
  3. package/build/Store-CI9N0P6I.js.map +1 -0
  4. package/build/Store-PjfFkZ2I.js +349 -0
  5. package/build/Store-PjfFkZ2I.js.map +1 -0
  6. package/build/devtools/browser/index.d.ts +120 -0
  7. package/build/devtools/browser/index.js +2750 -2357
  8. package/build/devtools/browser/index.js.map +1 -0
  9. package/build/index.d.ts +2 -0
  10. package/build/index.js +2 -244
  11. package/build/react/index.d.ts +58 -0
  12. package/build/react/index.js +59 -308
  13. package/build/react/index.js.map +1 -0
  14. package/package.json +29 -26
  15. package/build/types/core/Store.d.ts +0 -136
  16. package/build/types/core/index.d.ts +0 -1
  17. package/build/types/devtools/browser/NiceStateDevtools.d.ts +0 -10
  18. package/build/types/devtools/browser/components/ChangeDetailPanel.d.ts +0 -12
  19. package/build/types/devtools/browser/components/ChangeList.d.ts +0 -9
  20. package/build/types/devtools/browser/components/DiffView.d.ts +0 -13
  21. package/build/types/devtools/browser/components/JsonDiffView.d.ts +0 -24
  22. package/build/types/devtools/browser/components/JsonView.d.ts +0 -7
  23. package/build/types/devtools/browser/components/PanelChrome.d.ts +0 -54
  24. package/build/types/devtools/browser/components/SectionLabel.d.ts +0 -4
  25. package/build/types/devtools/browser/components/StateInspector.d.ts +0 -16
  26. package/build/types/devtools/browser/components/StoreTabs.d.ts +0 -12
  27. package/build/types/devtools/browser/components/utils.d.ts +0 -98
  28. package/build/types/devtools/browser/devtools_dock.d.ts +0 -54
  29. package/build/types/devtools/browser/index.d.ts +0 -3
  30. package/build/types/devtools/core/StateDevtools.types.d.ts +0 -43
  31. package/build/types/devtools/core/StateDevtoolsCore.d.ts +0 -56
  32. package/build/types/devtools/core/devtools_colors.d.ts +0 -26
  33. package/build/types/index.d.ts +0 -1
  34. package/build/types/react/InjectStoreState.d.ts +0 -18
  35. package/build/types/react/index.d.ts +0 -3
  36. package/build/types/react/useLocalStore.d.ts +0 -8
  37. package/build/types/react/useStoreState.d.ts +0 -23
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","names":[],"sources":["../../src/react/useStoreState.ts","../../src/react/InjectStoreState.ts","../../src/react/useLocalStore.ts"],"sourcesContent":["import { useCallback, useRef, useSyncExternalStore } from \"react\";\r\nimport type { Store } from \"../core/Store\";\r\n\r\n/**\r\n * Compares the previously selected value against the next one. Returning `true`\r\n * tells the hook the value is unchanged, so React is handed the *same*\r\n * reference and no re-render is scheduled.\r\n */\r\nexport type TEqualityFn<SS> = (a: SS, b: SS) => boolean;\r\n\r\n// Default equality is a strict reference check — the fastest possible path, and\r\n// exactly right when selectors return state branches (Immer's structural\r\n// sharing keeps unchanged branches referentially stable). Pass `deepEqual` from\r\n// `fast-equals` when a selector builds a fresh object/array each call.\r\nfunction strictEqual(a: unknown, b: unknown): boolean {\r\n return a === b;\r\n}\r\n\r\ninterface ISelectionCache<R> {\r\n value: R;\r\n}\r\n\r\n/**\r\n * Subscribe a React component to a store, optionally narrowing to a derived\r\n * slice.\r\n *\r\n * Built on `useSyncExternalStore`, so it is tear-free under React 18+\r\n * concurrent rendering with no manual `useState`/`useEffect` subscription loop.\r\n *\r\n * A `useRef` cache holds the last selected value. On every store emission the\r\n * selector is re-evaluated and the result is run through a strict-reference\r\n * fast-path first; only if the reference differs is `equalityFn` consulted. A\r\n * new reference is handed back to React exclusively when a genuine change is\r\n * detected, so equal-but-new selector results never trigger a render.\r\n */\r\nfunction useStoreState<S extends object>(store: Store<S>): S;\r\nfunction useStoreState<S extends object, SS>(\r\n store: Store<S>,\r\n getSubState: (state: S) => SS,\r\n equalityFn?: TEqualityFn<SS>,\r\n): SS;\r\nfunction useStoreState<S extends object, SS>(\r\n store: Store<S>,\r\n getSubState?: (state: S) => SS,\r\n equalityFn: (a: S | SS, b: S | SS) => boolean = strictEqual,\r\n): S | SS {\r\n // Keep the latest selector/equality without forcing a resubscribe when a\r\n // caller passes inline functions. The store-emission cache below guarantees\r\n // referential stability regardless of identity churn here.\r\n const selectorRef = useRef(getSubState);\r\n const equalityRef = useRef(equalityFn);\r\n selectorRef.current = getSubState;\r\n equalityRef.current = equalityFn;\r\n\r\n const cacheRef = useRef<ISelectionCache<S | SS> | null>(null);\r\n\r\n const getSnapshot = useCallback((): S | SS => {\r\n const rawState = store.getRawState();\r\n const selector = selectorRef.current;\r\n const nextValue: S | SS = selector ? selector(rawState) : rawState;\r\n\r\n const cache = cacheRef.current;\r\n\r\n if (cache === null) {\r\n cacheRef.current = { value: nextValue };\r\n return nextValue;\r\n }\r\n\r\n const lastValue = cache.value;\r\n\r\n // Fast-path: reference is identical → return the cached value untouched.\r\n if (nextValue === lastValue) {\r\n return lastValue;\r\n }\r\n\r\n // Reference changed → consult the configurable equality function. When it\r\n // reports equality, keep the old reference so React skips the render.\r\n if (equalityRef.current(lastValue, nextValue)) {\r\n return lastValue;\r\n }\r\n\r\n cache.value = nextValue;\r\n return nextValue;\r\n }, [store]);\r\n\r\n const subscribe = useCallback(\r\n (onStoreChange: () => void): (() => void) => store.subscribe(onStoreChange),\r\n [store],\r\n );\r\n\r\n // Same snapshot getter serves the server: it reads the store's current value,\r\n // which on the server is the SSR-seeded state.\r\n return useSyncExternalStore(subscribe, getSnapshot, getSnapshot);\r\n}\r\n\r\nexport { useStoreState };\r\n","import type { ReactElement } from \"react\";\r\nimport type { Store } from \"../core/Store\";\r\nimport { type TEqualityFn, useStoreState } from \"./useStoreState\";\r\n\r\nexport interface IPropsInjectStoreState<S extends object, SS> {\r\n store: Store<S>;\r\n on?: (state: S) => SS;\r\n /**\r\n * Optional equality function for the selected slice. Defaults to a strict\r\n * reference check; pass `deepEqual` from `fast-equals` for inline objects.\r\n */\r\n equalityFn?: TEqualityFn<SS>;\r\n children: (output: SS) => ReactElement;\r\n}\r\n\r\n/**\r\n * Render-prop binding: subscribes to `store` (optionally narrowed by `on`) and\r\n * renders `children` with the selected slice.\r\n */\r\nexport function InjectStoreState<S extends object, SS = S>({\r\n store,\r\n on,\r\n equalityFn,\r\n children,\r\n}: IPropsInjectStoreState<S, SS>): ReactElement {\r\n const state = useStoreState(store, on ?? ((s: S) => s as unknown as SS), equalityFn);\r\n return children(state);\r\n}\r\n","import { useRef } from \"react\";\r\nimport { Store } from \"../core/Store\";\r\n\r\n/**\r\n * Standard shallow comparison of dependency arrays — the same semantics React\r\n * uses for its own hook dependency lists. No deep traversal.\r\n */\r\nfunction shallowEqualDeps(\r\n a: ReadonlyArray<unknown> | undefined,\r\n b: ReadonlyArray<unknown> | undefined,\r\n): boolean {\r\n if (a === b) {\r\n return true;\r\n }\r\n if (a == null || b == null || a.length !== b.length) {\r\n return false;\r\n }\r\n for (let i = 0; i < a.length; i++) {\r\n if (a[i] !== b[i]) {\r\n return false;\r\n }\r\n }\r\n return true;\r\n}\r\n\r\n/**\r\n * Create a component-local {@link Store} that persists across renders. Passing a\r\n * `deps` array re-creates the store (with a fresh initial state) whenever the\r\n * dependencies change by shallow comparison.\r\n */\r\nfunction useLocalStore<S extends object>(\r\n initialState: (() => S) | S,\r\n deps?: ReadonlyArray<unknown>,\r\n): Store<S> {\r\n const storeRef = useRef<Store<S> | null>(null);\r\n const depsRef = useRef<ReadonlyArray<unknown> | undefined>(deps);\r\n\r\n if (storeRef.current === null) {\r\n storeRef.current = new Store(initialState);\r\n } else if (deps !== undefined && !shallowEqualDeps(depsRef.current, deps)) {\r\n storeRef.current = new Store(initialState);\r\n depsRef.current = deps;\r\n }\r\n\r\n return storeRef.current;\r\n}\r\n\r\nexport { useLocalStore };\r\n"],"mappings":";;;AAcA,SAAS,YAAY,GAAY,GAAqB;CACpD,OAAO,MAAM;AACf;AAyBA,SAAS,cACP,OACA,aACA,aAAgD,aACxC;CAIR,MAAM,cAAc,OAAO,WAAW;CACtC,MAAM,cAAc,OAAO,UAAU;CACrC,YAAY,UAAU;CACtB,YAAY,UAAU;CAEtB,MAAM,WAAW,OAAuC,IAAI;CAE5D,MAAM,cAAc,kBAA0B;EAC5C,MAAM,WAAW,MAAM,YAAY;EACnC,MAAM,WAAW,YAAY;EAC7B,MAAM,YAAoB,WAAW,SAAS,QAAQ,IAAI;EAE1D,MAAM,QAAQ,SAAS;EAEvB,IAAI,UAAU,MAAM;GAClB,SAAS,UAAU,EAAE,OAAO,UAAU;GACtC,OAAO;EACT;EAEA,MAAM,YAAY,MAAM;EAGxB,IAAI,cAAc,WAChB,OAAO;EAKT,IAAI,YAAY,QAAQ,WAAW,SAAS,GAC1C,OAAO;EAGT,MAAM,QAAQ;EACd,OAAO;CACT,GAAG,CAAC,KAAK,CAAC;CASV,OAAO,qBAPW,aACf,kBAA4C,MAAM,UAAU,aAAa,GAC1E,CAAC,KAAK,CAK4B,GAAG,aAAa,WAAW;AACjE;;;;;;;AC1EA,SAAgB,iBAA2C,EACzD,OACA,IACA,YACA,YAC8C;CAE9C,OAAO,SADO,cAAc,OAAO,QAAQ,MAAS,IAAqB,UACrD,CAAC;AACvB;;;;;;;ACpBA,SAAS,iBACP,GACA,GACS;CACT,IAAI,MAAM,GACR,OAAO;CAET,IAAI,KAAK,QAAQ,KAAK,QAAQ,EAAE,WAAW,EAAE,QAC3C,OAAO;CAET,KAAK,IAAI,IAAI,GAAG,IAAI,EAAE,QAAQ,KAC5B,IAAI,EAAE,OAAO,EAAE,IACb,OAAO;CAGX,OAAO;AACT;;;;;;AAOA,SAAS,cACP,cACA,MACU;CACV,MAAM,WAAW,OAAwB,IAAI;CAC7C,MAAM,UAAU,OAA2C,IAAI;CAE/D,IAAI,SAAS,YAAY,MACvB,SAAS,UAAU,IAAI,MAAM,YAAY;MACpC,IAAI,SAAS,KAAA,KAAa,CAAC,iBAAiB,QAAQ,SAAS,IAAI,GAAG;EACzE,SAAS,UAAU,IAAI,MAAM,YAAY;EACzC,QAAQ,UAAU;CACpB;CAEA,OAAO,SAAS;AAClB"}
package/package.json CHANGED
@@ -1,23 +1,21 @@
1
1
  {
2
2
  "name": "@nice-code/state",
3
- "version": "0.7.0",
4
- "private": false,
5
- "type": "module",
3
+ "version": "0.9.0",
6
4
  "exports": {
7
5
  ".": {
8
6
  "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"
7
+ "import": "./build/index.js",
8
+ "types": "./build/index.d.ts"
16
9
  },
17
10
  "./devtools/browser": {
18
11
  "source": "./src/devtools/browser/index.ts",
19
- "types": "./build/types/devtools/browser/index.d.ts",
20
- "import": "./build/devtools/browser/index.js"
12
+ "import": "./build/devtools/browser/index.js",
13
+ "types": "./build/devtools/browser/index.d.ts"
14
+ },
15
+ "./react": {
16
+ "source": "./src/react/index.ts",
17
+ "import": "./build/react/index.js",
18
+ "types": "./build/react/index.d.ts"
21
19
  }
22
20
  },
23
21
  "files": [
@@ -25,32 +23,37 @@
25
23
  "package.json",
26
24
  "README.md"
27
25
  ],
26
+ "peerDependencies": {
27
+ "react": ">=19"
28
+ },
29
+ "peerDependenciesMeta": {
30
+ "react": {
31
+ "optional": true
32
+ }
33
+ },
34
+ "private": false,
28
35
  "publishConfig": {
29
36
  "access": "public"
30
37
  },
31
38
  "scripts": {
39
+ "build-bun": "bun run clean-build && bun run build.ts && bun run build-types",
40
+ "build": "bun run clean-build && tsdown",
41
+ "build-types": "tsc --project tsconfig.build.json",
42
+ "build-watch": "bun run clean-build && bun run build.ts --watch && bun run build-types --watch",
43
+ "clean-build": "bunx rimraf build",
32
44
  "type-check": "bunx tsc --noEmit",
33
45
  "type-check-watch": "bunx tsc --noEmit --watch",
34
- "clean-build": "bunx rimraf build",
35
46
  "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"
47
+ "vitest-agent": "vitest --typecheck --reporter=agent"
40
48
  },
49
+ "type": "module",
41
50
  "dependencies": {
51
+ "@tanstack/react-virtual": "^3.13.26",
52
+ "fast-equals": "6.0.0",
42
53
  "immer": "11.1.8",
43
- "fast-equals": "6.0.0"
54
+ "nice-devtools-shared": "0.9.0"
44
55
  },
45
56
  "devDependencies": {
46
57
  "react": "19.2.7"
47
- },
48
- "peerDependencies": {
49
- "react": ">=19"
50
- },
51
- "peerDependenciesMeta": {
52
- "react": {
53
- "optional": true
54
- }
55
58
  }
56
59
  }
@@ -1,136 +0,0 @@
1
- import { type Draft, type Patch, type PatchListener } from "immer";
2
- /**
3
- * A plain pub/sub listener. Fired (in the order added) every time the store's
4
- * root state reference changes. This is the vanilla integration point that any
5
- * ecosystem — including the React adapter's `useSyncExternalStore` — builds on.
6
- */
7
- export type TUpdateListener = () => void;
8
- /**
9
- * @typeParam S The store's state
10
- * @param draft The mutable draft to change during this update (Immer proxy).
11
- * @param original A readonly snapshot of the state as it was before this update.
12
- */
13
- export type TUpdateFunction<S> = (draft: Draft<S>, original: S) => void;
14
- /**
15
- * A selector that derives a watched slice `T` from the full store state `S`.
16
- */
17
- export type TStoreWatch<S extends object, T> = (state: S) => T;
18
- /**
19
- * Fired by {@link Store.watch} whenever the watched slice changes structurally.
20
- */
21
- export type TStoreSubscriptionListener<S extends object, T> = (watched: T, allState: S, previousWatched: T) => void;
22
- /**
23
- * Runs inside an Immer `produce` whenever a watched slice changes, letting the
24
- * store derive further state from its own mutations.
25
- */
26
- export type TReactionFunction<S extends object, T> = (watched: T, draft: Draft<S>, original: S, previousWatched: T) => void;
27
- export type TStoreActionUpdate<S extends object> = (updater: TUpdateFunction<S> | TUpdateFunction<S>[], patchesCallback?: TPatchesCallback) => void;
28
- export type TStoreAction<S extends object> = (update: TStoreActionUpdate<S>) => void;
29
- export type TPatchesCallback = (patches: Patch[], inversePatches: Patch[]) => void;
30
- export interface IStoreInternalOptions<S extends object> {
31
- ssr: boolean;
32
- reactionCreators?: TReactionCreator<S>[];
33
- }
34
- export interface ICreateReactionOptions {
35
- runNow?: boolean;
36
- runNowWithSideEffects?: boolean;
37
- }
38
- /**
39
- * A framework-agnostic, Immer-backed state container.
40
- *
41
- * The store owns a single immutable state value and a `Set` of plain
42
- * listeners. Every mutation runs through Immer's `produce`, and listeners are
43
- * only notified when the resulting root reference actually changes — making
44
- * "no-op" updates genuinely free for subscribers.
45
- *
46
- * @typeParam S Your store's state interface.
47
- */
48
- export declare class Store<S extends object = object> {
49
- private currentState;
50
- private readonly createInitialState;
51
- private ssr;
52
- /** Plain pub/sub listeners — a Set both dedupes and dispatches fast. */
53
- private readonly listeners;
54
- private reactionCreators;
55
- private reactions;
56
- private clientSubscriptions;
57
- constructor(initialState: S | (() => S));
58
- get state(): S;
59
- /**
60
- * Returns the raw state object contained within this store at this moment.
61
- *
62
- * ---
63
- * ** WARNING **
64
- *
65
- * Most of the time, if you're using this in your app, there's probably a
66
- * better way to do it (a selector subscription or the React adapter).
67
- * ---
68
- */
69
- getRawState(): S;
70
- /**
71
- * Subscribe a plain listener to store mutations. The listener fires once per
72
- * committed update (root reference change) and receives no arguments — read
73
- * the latest value with {@link getRawState}.
74
- *
75
- * This is the low-level primitive that vanilla code and framework adapters
76
- * (e.g. React's `useSyncExternalStore`) build upon.
77
- *
78
- * @returns An unsubscribe function.
79
- */
80
- subscribe(listener: TUpdateListener): () => void;
81
- /**
82
- * Subscribe to a derived slice of state. The `listener` only fires when the
83
- * watched value changes structurally (per the Fast-Path rule).
84
- *
85
- * @returns An unsubscribe function.
86
- */
87
- watch<T>(watch: TStoreWatch<S, T>, listener: TStoreSubscriptionListener<S, T>): () => void;
88
- /**
89
- * Register a reaction: when the watched slice changes, the `reaction` recipe
90
- * runs inside Immer to derive further state on the same store.
91
- *
92
- * @returns A function that removes the reaction.
93
- */
94
- createReaction<T>(watch: TStoreWatch<S, T>, reaction: TReactionFunction<S, T>, { runNow, runNowWithSideEffects }?: ICreateReactionOptions): () => void;
95
- /**
96
- * Subscribe to the Immer patches produced by each update.
97
- *
98
- * @returns An unsubscribe function.
99
- */
100
- listenToPatches(patchListener: PatchListener): () => void;
101
- /**
102
- * Mutate the store via one or more Immer update functions.
103
- *
104
- * @param updater A single update function, or an array applied in order.
105
- * @param patchesCallback Optional callback receiving the patches for this update.
106
- */
107
- update(updater: TUpdateFunction<S> | TUpdateFunction<S>[], patchesCallback?: TPatchesCallback): void;
108
- /**
109
- * Replace the store's state entirely with a new state value.
110
- */
111
- replace(newState: S): void;
112
- /**
113
- * Replace the store's state by mapping from the current state.
114
- */
115
- replaceFromCurrent(replacer: (state: S) => S): void;
116
- /**
117
- * Apply a set of Immer patches to the store.
118
- */
119
- applyPatches(patches: Patch[]): void;
120
- }
121
- /**
122
- * Apply Immer patches to a store, committing only if the root reference changes.
123
- */
124
- export declare function applyPatchesToStore<S extends object = object>(store: Store<S>, patches: Patch[]): void;
125
- /**
126
- * Mutate a store via one or more Immer update functions.
127
- *
128
- * Patches are only computed when there's a consumer for them (a registered
129
- * patch listener or a `patchesCallback`), keeping the common path allocation-free.
130
- * Listeners are notified only when the root reference actually changes.
131
- *
132
- * @param store The store to update.
133
- * @param updater A single update function, or an array applied in order.
134
- * @param patchesCallback Optional callback receiving the patches for this update.
135
- */
136
- export declare function update<S extends object = object>(store: Store<S>, updater: TUpdateFunction<S> | TUpdateFunction<S>[], patchesCallback?: TPatchesCallback): void;
@@ -1 +0,0 @@
1
- export { applyPatchesToStore, type ICreateReactionOptions, type IStoreInternalOptions, Store, type TPatchesCallback, type TReactionFunction, type TStoreAction, type TStoreActionUpdate, type TStoreSubscriptionListener, type TStoreWatch, type TUpdateFunction, type TUpdateListener, update, } from "./Store";
@@ -1,10 +0,0 @@
1
- import type { TDevtoolsPosition } from "../core/StateDevtools.types";
2
- import type { StateDevtoolsCore } from "../core/StateDevtoolsCore";
3
- export interface INiceStateDevtoolsProps {
4
- core: StateDevtoolsCore;
5
- position?: TDevtoolsPosition;
6
- initialOpen?: boolean;
7
- /** Show the panel even when NODE_ENV is not "development". */
8
- forceEnable?: boolean;
9
- }
10
- export declare function NiceStateDevtools({ forceEnable, ...props }: INiceStateDevtoolsProps): import("react/jsx-runtime").JSX.Element | null;
@@ -1,12 +0,0 @@
1
- import type { IDevtoolsStateChange } from "../../core/StateDevtools.types";
2
- export type TChangeView = "diff" | "props" | "before" | "after";
3
- export declare function ChangeDetailPanel({ change, onRevert, view, onViewChange, compress, onCompressChange, latestFirst, onLatestFirstChange, }: {
4
- change: IDevtoolsStateChange;
5
- onRevert: (change: IDevtoolsStateChange) => void;
6
- view: TChangeView;
7
- onViewChange: (view: TChangeView) => void;
8
- compress: boolean;
9
- onCompressChange: (compress: boolean) => void;
10
- latestFirst: boolean;
11
- onLatestFirstChange: (latestFirst: boolean) => void;
12
- }): import("react/jsx-runtime").JSX.Element;
@@ -1,9 +0,0 @@
1
- import type { CSSProperties } from "react";
2
- import { type IChangeGroup } from "./utils";
3
- export declare function ChangeList({ groups, selectedCuid, onSelect, showStore, style, }: {
4
- groups: IChangeGroup[];
5
- selectedCuid: string | null;
6
- onSelect: (cuid: string) => void;
7
- showStore: boolean;
8
- style?: CSSProperties;
9
- }): import("react/jsx-runtime").JSX.Element;
@@ -1,13 +0,0 @@
1
- /**
2
- * Test-framework-style diff: only the paths that changed. Changed paths are
3
- * grouped into a hierarchy by their shared parents — `countHistory.0` and
4
- * `countHistory.1` nest under a single `countHistory` node, each shown by its
5
- * direct key only. Single-child chains collapse to a dotted path (`a.b.c`) so
6
- * lone deep changes stay on one line. Short values render inline with the key
7
- * (`count: 0 → 1`); long ones keep the removed/added line block.
8
- */
9
- export declare function DiffView({ before, after, latestFirst, }: {
10
- before: unknown;
11
- after: unknown;
12
- latestFirst?: boolean;
13
- }): import("react/jsx-runtime").JSX.Element;
@@ -1,24 +0,0 @@
1
- /**
2
- * Unified, git-style diff of the entire state snapshot. Both sides are rendered
3
- * as pretty JSON and line-diffed, so unchanged structure stays visible for
4
- * context while removed lines (red, `−`) and added lines (green, `+`) call out
5
- * exactly which sections of the whole state moved.
6
- */
7
- export declare function JsonDiffView({ before, after, compress, latestFirst, }: {
8
- before: unknown;
9
- after: unknown;
10
- compress?: boolean;
11
- latestFirst?: boolean;
12
- }): import("react/jsx-runtime").JSX.Element;
13
- /**
14
- * The full JSON of one snapshot with the lines that differ from the other side
15
- * highlighted in place — red behind removed lines on the "before" side, green
16
- * behind added lines on the "after" side. Lines common to both render plainly so
17
- * the highlight reads as "here is what changed within the whole state".
18
- */
19
- export declare function HighlightedJsonView({ before, after, side, compress, }: {
20
- before: unknown;
21
- after: unknown;
22
- side: "before" | "after";
23
- compress?: boolean;
24
- }): import("react/jsx-runtime").JSX.Element;
@@ -1,7 +0,0 @@
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;
@@ -1,54 +0,0 @@
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;
@@ -1,4 +0,0 @@
1
- export declare function SectionLabel({ label, color, }: {
2
- label: string;
3
- color?: string;
4
- }): import("react/jsx-runtime").JSX.Element;
@@ -1,16 +0,0 @@
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;
@@ -1,12 +0,0 @@
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;
@@ -1,98 +0,0 @@
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
- /** A run of consecutive, structurally-equal changes collapsed into one row. */
17
- export interface IChangeGroup {
18
- /** Newest change in the run — the one the row renders and selects. */
19
- representative: IDevtoolsStateChange;
20
- /**
21
- * Oldest change in the run — the one that actually moved the state (every later
22
- * member re-applied the same mutation as a no-op). Its `prevSnapshot` is the
23
- * real "before" of the whole run, so the detail panel uses it to show what the
24
- * initial update changed instead of the representative's empty self-diff.
25
- */
26
- oldest: IDevtoolsStateChange;
27
- /** How many changes the run collapses (1 = ungrouped). */
28
- count: number;
29
- }
30
- /**
31
- * Structural identity of a change by its *resulting* state: same store, same
32
- * snapshot. The feature collapses updates that "didn't change the state from the
33
- * previous update", so we compare the committed state itself rather than the
34
- * mutation that produced it. Keying on patches would miss the no-op cases this is
35
- * meant to catch — a fresh object reference with identical content, or a
36
- * same-value write from a different source (e.g. a devtools edit followed by an
37
- * app update). Those carry patches yet leave the state untouched, so they belong
38
- * in the run that already reached this state regardless of source.
39
- */
40
- export declare function changeGroupKey(change: IDevtoolsStateChange): string;
41
- /** Collapse consecutive structurally-equal changes (list is newest-first). */
42
- export declare function groupChanges(changes: IDevtoolsStateChange[]): IChangeGroup[];
43
- export type TDiffKind = "added" | "removed" | "changed";
44
- export interface IDiffEntry {
45
- path: string;
46
- segments: (string | number)[];
47
- kind: TDiffKind;
48
- before?: unknown;
49
- after?: unknown;
50
- }
51
- /**
52
- * Structural diff between two snapshots, flattened to the individual leaf/branch
53
- * paths that actually changed — the data behind the test-framework-style "Diff"
54
- * view. Unchanged branches are skipped entirely (Immer's structural sharing
55
- * means equal branches keep their reference, so this stays cheap).
56
- */
57
- export declare function computeDiff(before: unknown, after: unknown): IDiffEntry[];
58
- export type TLineDiffKind = "common" | "removed" | "added";
59
- export interface ILineDiffOp {
60
- kind: TLineDiffKind;
61
- text: string;
62
- }
63
- /**
64
- * Classic LCS line diff between two blocks of text — the data behind the unified
65
- * git-style "Diff View" and the per-line highlighting in the Before / After
66
- * panels. Snapshots rendered as pretty JSON stay small, so the O(n·m) table is
67
- * comfortably cheap.
68
- */
69
- export declare function computeLineDiff(beforeText: string, afterText: string): ILineDiffOp[];
70
- /**
71
- * Reorder each contiguous changed hunk so additions sit above removals when
72
- * `latestFirst` is set (the new state on top). Common lines anchor the hunks in
73
- * place; only the −/+ lines within a run are regrouped, so the surrounding
74
- * context never moves. With `latestFirst` off the ops are returned untouched.
75
- */
76
- export declare function orderLineDiffOps(ops: ILineDiffOp[], latestFirst: boolean): ILineDiffOp[];
77
- export type TCompressedTone = "common" | "added" | "removed" | "placeholder";
78
- /** Which document a compressed diff is being rendered for. */
79
- export type TDiffSide = "unified" | "before" | "after";
80
- export interface ICompressedLine {
81
- /** Nesting depth — the renderer turns this into leading indentation. */
82
- depth: number;
83
- /** Gutter glyph: `+` added, `−` removed, or a space for structure/placeholder. */
84
- sign: string;
85
- tone: TCompressedTone;
86
- /** The line's JSON (or placeholder) text, without indentation. */
87
- text: string;
88
- }
89
- /**
90
- * A structure-aware "address" diff: the JSON tree pruned down to just the
91
- * branches that actually changed. Parents of a change keep their brackets so the
92
- * path stays legible, and each run of untouched siblings collapses into a single
93
- * `… N unchanged …` placeholder (which, for arrays, naturally reports the counts
94
- * before and after a change). `side` picks which document we're rebuilding —
95
- * `"unified"` shows removed (−) and added (+) together, while `"before"` /
96
- * `"after"` show only the lines that belong to that one snapshot.
97
- */
98
- export declare function computeCompressedDiff(before: unknown, after: unknown, side: TDiffSide, latestFirst?: boolean): ICompressedLine[];
@@ -1,54 +0,0 @@
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
- /**
27
- * True when this panel shares its dock side with another open panel (stacked
28
- * either nearer or further from the edge). Stacked panels square off all their
29
- * corners so they sit flush as one continuous block — only a panel alone on
30
- * its side keeps its rounded, page-facing corners.
31
- */
32
- stacked: boolean;
33
- /** Is any devtool on the page currently open? */
34
- anyOpen: boolean;
35
- /** First-registered devtool — the one that renders the combined launcher. */
36
- isPrimary: boolean;
37
- /** Every registered devtool, for the combined launcher. */
38
- devtools: IDockDevtoolRef[];
39
- /** Closed devtools other than this one, for an open panel's header. */
40
- otherClosed: IDockDevtoolRef[];
41
- }
42
- export interface IDevtoolsDockCoordinator {
43
- version: number;
44
- register(panel: IDockDevtoolInput): () => void;
45
- update(id: string, next: IDockDevtoolSync): void;
46
- getView(id: string): IDockView;
47
- subscribe(listener: () => void): () => void;
48
- }
49
- /**
50
- * Returns the page-wide dock coordinator, installing it on `window` the first
51
- * time it is requested. On the server (no `window`) a throwaway instance is
52
- * returned so callers can use it unconditionally.
53
- */
54
- export declare function getDevtoolsDockCoordinator(): IDevtoolsDockCoordinator;
@@ -1,3 +0,0 @@
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";
@@ -1,43 +0,0 @@
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;