mvc-kit 2.12.5 → 2.13.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.
- package/agent-config/bin/postinstall.mjs +4 -3
- package/agent-config/bin/setup.mjs +5 -1
- package/agent-config/claude-code/agents/mvc-kit-architect.md +11 -8
- package/agent-config/claude-code/skills/guide/SKILL.md +20 -7
- package/agent-config/claude-code/skills/guide/patterns.md +12 -0
- package/agent-config/claude-code/skills/guide/recipes.md +510 -0
- package/agent-config/claude-code/skills/guide/testing.md +297 -0
- package/agent-config/claude-code/skills/review/SKILL.md +3 -13
- package/agent-config/claude-code/skills/review/checklist.md +30 -5
- package/agent-config/claude-code/skills/scaffold/SKILL.md +4 -13
- package/agent-config/lib/install-claude.mjs +84 -25
- package/dist/Channel.cjs +276 -300
- package/dist/Channel.cjs.map +1 -1
- package/dist/Channel.js +275 -299
- package/dist/Channel.js.map +1 -1
- package/dist/Collection.cjs +424 -504
- package/dist/Collection.cjs.map +1 -1
- package/dist/Collection.js +423 -503
- package/dist/Collection.js.map +1 -1
- package/dist/Controller.cjs +70 -67
- package/dist/Controller.cjs.map +1 -1
- package/dist/Controller.js +69 -66
- package/dist/Controller.js.map +1 -1
- package/dist/EventBus.cjs +77 -88
- package/dist/EventBus.cjs.map +1 -1
- package/dist/EventBus.js +76 -87
- package/dist/EventBus.js.map +1 -1
- package/dist/Feed.cjs +81 -77
- package/dist/Feed.cjs.map +1 -1
- package/dist/Feed.js +80 -76
- package/dist/Feed.js.map +1 -1
- package/dist/Model.cjs +181 -207
- package/dist/Model.cjs.map +1 -1
- package/dist/Model.js +179 -205
- package/dist/Model.js.map +1 -1
- package/dist/Pagination.cjs +75 -73
- package/dist/Pagination.cjs.map +1 -1
- package/dist/Pagination.js +74 -72
- package/dist/Pagination.js.map +1 -1
- package/dist/Pending.cjs +255 -287
- package/dist/Pending.cjs.map +1 -1
- package/dist/Pending.js +253 -285
- package/dist/Pending.js.map +1 -1
- package/dist/PersistentCollection.cjs +242 -285
- package/dist/PersistentCollection.cjs.map +1 -1
- package/dist/PersistentCollection.js +241 -284
- package/dist/PersistentCollection.js.map +1 -1
- package/dist/Resource.cjs +166 -174
- package/dist/Resource.cjs.map +1 -1
- package/dist/Resource.js +164 -172
- package/dist/Resource.js.map +1 -1
- package/dist/Selection.cjs +84 -94
- package/dist/Selection.cjs.map +1 -1
- package/dist/Selection.js +83 -93
- package/dist/Selection.js.map +1 -1
- package/dist/Service.cjs +54 -55
- package/dist/Service.cjs.map +1 -1
- package/dist/Service.js +53 -54
- package/dist/Service.js.map +1 -1
- package/dist/Sorting.cjs +102 -101
- package/dist/Sorting.cjs.map +1 -1
- package/dist/Sorting.js +102 -101
- package/dist/Sorting.js.map +1 -1
- package/dist/Trackable.cjs +112 -80
- package/dist/Trackable.cjs.map +1 -1
- package/dist/Trackable.js +111 -79
- package/dist/Trackable.js.map +1 -1
- package/dist/ViewModel.cjs +528 -576
- package/dist/ViewModel.cjs.map +1 -1
- package/dist/ViewModel.js +525 -573
- package/dist/ViewModel.js.map +1 -1
- package/dist/bindPublicMethods.cjs +43 -24
- package/dist/bindPublicMethods.cjs.map +1 -1
- package/dist/bindPublicMethods.js +43 -24
- package/dist/bindPublicMethods.js.map +1 -1
- package/dist/errors.cjs +67 -68
- package/dist/errors.cjs.map +1 -1
- package/dist/errors.js +68 -71
- package/dist/errors.js.map +1 -1
- package/dist/mvc-kit.cjs +44 -46
- package/dist/mvc-kit.js +5 -32
- package/dist/produceDraft.cjs +105 -95
- package/dist/produceDraft.cjs.map +1 -1
- package/dist/produceDraft.js +106 -97
- package/dist/produceDraft.js.map +1 -1
- package/dist/react/components/CardList.cjs +30 -40
- package/dist/react/components/CardList.cjs.map +1 -1
- package/dist/react/components/CardList.js +31 -41
- package/dist/react/components/CardList.js.map +1 -1
- package/dist/react/components/DataTable.cjs +146 -169
- package/dist/react/components/DataTable.cjs.map +1 -1
- package/dist/react/components/DataTable.js +147 -170
- package/dist/react/components/DataTable.js.map +1 -1
- package/dist/react/components/InfiniteScroll.cjs +51 -42
- package/dist/react/components/InfiniteScroll.cjs.map +1 -1
- package/dist/react/components/InfiniteScroll.js +52 -43
- package/dist/react/components/InfiniteScroll.js.map +1 -1
- package/dist/react/components/types.cjs +10 -6
- package/dist/react/components/types.cjs.map +1 -1
- package/dist/react/components/types.js +11 -9
- package/dist/react/components/types.js.map +1 -1
- package/dist/react/guards.cjs +10 -6
- package/dist/react/guards.cjs.map +1 -1
- package/dist/react/guards.js +11 -9
- package/dist/react/guards.js.map +1 -1
- package/dist/react/provider.cjs +23 -20
- package/dist/react/provider.cjs.map +1 -1
- package/dist/react/provider.js +23 -21
- package/dist/react/provider.js.map +1 -1
- package/dist/react/use-event-bus.cjs +24 -20
- package/dist/react/use-event-bus.cjs.map +1 -1
- package/dist/react/use-event-bus.js +24 -21
- package/dist/react/use-event-bus.js.map +1 -1
- package/dist/react/use-instance.cjs +43 -36
- package/dist/react/use-instance.cjs.map +1 -1
- package/dist/react/use-instance.js +43 -36
- package/dist/react/use-instance.js.map +1 -1
- package/dist/react/use-local.cjs +48 -64
- package/dist/react/use-local.cjs.map +1 -1
- package/dist/react/use-local.js +47 -63
- package/dist/react/use-local.js.map +1 -1
- package/dist/react/use-model.cjs +84 -98
- package/dist/react/use-model.cjs.map +1 -1
- package/dist/react/use-model.js +84 -100
- package/dist/react/use-model.js.map +1 -1
- package/dist/react/use-singleton.cjs +19 -23
- package/dist/react/use-singleton.cjs.map +1 -1
- package/dist/react/use-singleton.js +16 -20
- package/dist/react/use-singleton.js.map +1 -1
- package/dist/react/use-subscribe-only.cjs +28 -22
- package/dist/react/use-subscribe-only.cjs.map +1 -1
- package/dist/react/use-subscribe-only.js +28 -22
- package/dist/react/use-subscribe-only.js.map +1 -1
- package/dist/react/use-teardown.cjs +20 -19
- package/dist/react/use-teardown.cjs.map +1 -1
- package/dist/react/use-teardown.js +20 -19
- package/dist/react/use-teardown.js.map +1 -1
- package/dist/react-native/NativeCollection.cjs +98 -78
- package/dist/react-native/NativeCollection.cjs.map +1 -1
- package/dist/react-native/NativeCollection.js +97 -77
- package/dist/react-native/NativeCollection.js.map +1 -1
- package/dist/react-native.cjs +2 -4
- package/dist/react-native.js +1 -4
- package/dist/react.cjs +24 -26
- package/dist/react.js +1 -17
- package/dist/singleton.cjs +28 -22
- package/dist/singleton.cjs.map +1 -1
- package/dist/singleton.js +29 -26
- package/dist/singleton.js.map +1 -1
- package/dist/walkPrototypeChain.cjs +20 -12
- package/dist/walkPrototypeChain.cjs.map +1 -1
- package/dist/walkPrototypeChain.js +21 -13
- package/dist/walkPrototypeChain.js.map +1 -1
- package/dist/web/IndexedDBCollection.cjs +53 -36
- package/dist/web/IndexedDBCollection.cjs.map +1 -1
- package/dist/web/IndexedDBCollection.js +52 -35
- package/dist/web/IndexedDBCollection.js.map +1 -1
- package/dist/web/WebStorageCollection.cjs +82 -84
- package/dist/web/WebStorageCollection.cjs.map +1 -1
- package/dist/web/WebStorageCollection.js +81 -83
- package/dist/web/WebStorageCollection.js.map +1 -1
- package/dist/web/idb.cjs +107 -99
- package/dist/web/idb.cjs.map +1 -1
- package/dist/web/idb.js +108 -105
- package/dist/web/idb.js.map +1 -1
- package/dist/web.cjs +4 -6
- package/dist/web.js +1 -5
- package/dist/wrapAsyncMethods.cjs +141 -168
- package/dist/wrapAsyncMethods.cjs.map +1 -1
- package/dist/wrapAsyncMethods.js +141 -168
- package/dist/wrapAsyncMethods.js.map +1 -1
- package/package.json +8 -8
- package/src/Pending.test.ts +1 -2
- package/src/Sorting.test.ts +1 -1
- package/src/produceDraft.test.ts +3 -3
- package/src/react/components/CardList.test.tsx +1 -1
- package/src/react/components/DataTable.test.tsx +1 -1
- package/src/react/components/InfiniteScroll.test.tsx +5 -5
- package/dist/mvc-kit.cjs.map +0 -1
- package/dist/mvc-kit.js.map +0 -1
- package/dist/react-native.cjs.map +0 -1
- package/dist/react-native.js.map +0 -1
- package/dist/react.cjs.map +0 -1
- package/dist/react.js.map +0 -1
- package/dist/web.cjs.map +0 -1
- package/dist/web.js.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-model.cjs","sources":["../../src/react/use-model.ts"],"sourcesContent":["import { useRef, useEffect, useSyncExternalStore, useCallback } from 'react';\nimport type { Model } from '../Model';\nimport type { ValidationErrors } from '../types';\nimport type { StateOf } from './types';\nimport { isInitializable } from './guards';\n\n/** Return type of `useModel`, providing state, validation, and model access. */\nexport interface ModelHandle<S extends object, M extends Model<S>> {\n state: S;\n errors: ValidationErrors<S>;\n valid: boolean;\n dirty: boolean;\n model: M;\n}\n\n/**\n * Bind to a component-scoped Model with validation and dirty state exposed.\n */\nexport function useModel<M extends Model<any>>(\n factory: () => M\n): ModelHandle<StateOf<M>, M> {\n const modelRef = useRef<M | null>(null);\n const mountedRef = useRef(false);\n\n if (!modelRef.current || modelRef.current.disposed) {\n modelRef.current = factory();\n }\n\n const modelSubscribe = useCallback(\n (onStoreChange: () => void) => modelRef.current!.subscribe(onStoreChange),\n []\n );\n const modelSnapshot = useCallback(\n () => modelRef.current!.state,\n []\n );\n useSyncExternalStore(modelSubscribe, modelSnapshot, modelSnapshot);\n\n useEffect(() => {\n mountedRef.current = true;\n if (isInitializable(modelRef.current)) {\n modelRef.current.init();\n }\n return () => {\n mountedRef.current = false;\n setTimeout(() => {\n if (!mountedRef.current) {\n modelRef.current?.dispose();\n }\n }, 0);\n };\n }, []);\n\n const model = modelRef.current;\n\n return {\n state: model.state,\n errors: model.errors,\n valid: model.valid,\n dirty: model.dirty,\n model,\n };\n}\n\n/**\n * Create a component-scoped Model with lifecycle management (init + dispose)\n * but NO state subscription. The parent component never re-renders from\n * model state changes.\n *\n * Designed for the per-field isolation pattern: parent creates the model\n * via `useModelRef`, children subscribe to individual fields via `useField`.\n */\nexport function useModelRef<M extends Model<any>>(factory: () => M): M {\n const modelRef = useRef<M | null>(null);\n const mountedRef = useRef(false);\n\n if (!modelRef.current || modelRef.current.disposed) {\n modelRef.current = factory();\n }\n\n useEffect(() => {\n mountedRef.current = true;\n if (isInitializable(modelRef.current)) {\n modelRef.current.init();\n }\n return () => {\n mountedRef.current = false;\n setTimeout(() => {\n if (!mountedRef.current) {\n modelRef.current?.dispose();\n }\n }, 0);\n };\n }, []);\n\n return modelRef.current;\n}\n\n/** Return type of `useField`, providing a single field's value, error, and setter. */\nexport interface FieldHandle<V> {\n value: V;\n error: string | undefined;\n set: (value: V) => void;\n}\n\n/**\n * Bind to a single Model field with surgical re-renders.\n */\nexport function useField<S extends object, K extends keyof S>(\n model: Model<S>,\n field: K\n): FieldHandle<S[K]> {\n // Track the field value and error for comparison\n const getSnapshot = useCallback(() => {\n return {\n value: model.state[field],\n error: model.errors[field],\n };\n }, [model, field]);\n\n // Use object comparison for subscription\n const cachedRef = useRef(getSnapshot());\n\n const subscribe = useCallback(\n (onStoreChange: () => void) => {\n return model.subscribe(() => {\n const next = getSnapshot();\n const current = cachedRef.current;\n\n // Only trigger re-render if field value or error changed\n if (next.value !== current.value || next.error !== current.error) {\n cachedRef.current = next;\n onStoreChange();\n }\n });\n },\n [model, getSnapshot]\n );\n\n const snapshot = useSyncExternalStore(\n subscribe,\n () => cachedRef.current,\n () => cachedRef.current\n );\n\n const set = useCallback(\n (value: S[K]) => {\n // Access the protected set method through type assertion\n // The Model subclass should expose a setter method\n const partial: Partial<S> = { [field]: value } as unknown as Partial<S>;\n (model as unknown as { set: (partial: Partial<S>) => void }).set(partial);\n },\n [model, field]\n );\n\n return {\n value: snapshot.value,\n error: snapshot.error,\n set,\n };\n}\n"],"
|
|
1
|
+
{"version":3,"file":"use-model.cjs","names":[],"sources":["../../src/react/use-model.ts"],"sourcesContent":["import { useRef, useEffect, useSyncExternalStore, useCallback } from 'react';\nimport type { Model } from '../Model';\nimport type { ValidationErrors } from '../types';\nimport type { StateOf } from './types';\nimport { isInitializable } from './guards';\n\n/** Return type of `useModel`, providing state, validation, and model access. */\nexport interface ModelHandle<S extends object, M extends Model<S>> {\n state: S;\n errors: ValidationErrors<S>;\n valid: boolean;\n dirty: boolean;\n model: M;\n}\n\n/**\n * Bind to a component-scoped Model with validation and dirty state exposed.\n */\nexport function useModel<M extends Model<any>>(\n factory: () => M\n): ModelHandle<StateOf<M>, M> {\n const modelRef = useRef<M | null>(null);\n const mountedRef = useRef(false);\n\n if (!modelRef.current || modelRef.current.disposed) {\n modelRef.current = factory();\n }\n\n const modelSubscribe = useCallback(\n (onStoreChange: () => void) => modelRef.current!.subscribe(onStoreChange),\n []\n );\n const modelSnapshot = useCallback(\n () => modelRef.current!.state,\n []\n );\n useSyncExternalStore(modelSubscribe, modelSnapshot, modelSnapshot);\n\n useEffect(() => {\n mountedRef.current = true;\n if (isInitializable(modelRef.current)) {\n modelRef.current.init();\n }\n return () => {\n mountedRef.current = false;\n setTimeout(() => {\n if (!mountedRef.current) {\n modelRef.current?.dispose();\n }\n }, 0);\n };\n }, []);\n\n const model = modelRef.current;\n\n return {\n state: model.state,\n errors: model.errors,\n valid: model.valid,\n dirty: model.dirty,\n model,\n };\n}\n\n/**\n * Create a component-scoped Model with lifecycle management (init + dispose)\n * but NO state subscription. The parent component never re-renders from\n * model state changes.\n *\n * Designed for the per-field isolation pattern: parent creates the model\n * via `useModelRef`, children subscribe to individual fields via `useField`.\n */\nexport function useModelRef<M extends Model<any>>(factory: () => M): M {\n const modelRef = useRef<M | null>(null);\n const mountedRef = useRef(false);\n\n if (!modelRef.current || modelRef.current.disposed) {\n modelRef.current = factory();\n }\n\n useEffect(() => {\n mountedRef.current = true;\n if (isInitializable(modelRef.current)) {\n modelRef.current.init();\n }\n return () => {\n mountedRef.current = false;\n setTimeout(() => {\n if (!mountedRef.current) {\n modelRef.current?.dispose();\n }\n }, 0);\n };\n }, []);\n\n return modelRef.current;\n}\n\n/** Return type of `useField`, providing a single field's value, error, and setter. */\nexport interface FieldHandle<V> {\n value: V;\n error: string | undefined;\n set: (value: V) => void;\n}\n\n/**\n * Bind to a single Model field with surgical re-renders.\n */\nexport function useField<S extends object, K extends keyof S>(\n model: Model<S>,\n field: K\n): FieldHandle<S[K]> {\n // Track the field value and error for comparison\n const getSnapshot = useCallback(() => {\n return {\n value: model.state[field],\n error: model.errors[field],\n };\n }, [model, field]);\n\n // Use object comparison for subscription\n const cachedRef = useRef(getSnapshot());\n\n const subscribe = useCallback(\n (onStoreChange: () => void) => {\n return model.subscribe(() => {\n const next = getSnapshot();\n const current = cachedRef.current;\n\n // Only trigger re-render if field value or error changed\n if (next.value !== current.value || next.error !== current.error) {\n cachedRef.current = next;\n onStoreChange();\n }\n });\n },\n [model, getSnapshot]\n );\n\n const snapshot = useSyncExternalStore(\n subscribe,\n () => cachedRef.current,\n () => cachedRef.current\n );\n\n const set = useCallback(\n (value: S[K]) => {\n // Access the protected set method through type assertion\n // The Model subclass should expose a setter method\n const partial: Partial<S> = { [field]: value } as unknown as Partial<S>;\n (model as unknown as { set: (partial: Partial<S>) => void }).set(partial);\n },\n [model, field]\n );\n\n return {\n value: snapshot.value,\n error: snapshot.error,\n set,\n };\n}\n"],"mappings":";;;;;;AAkBA,SAAgB,SACd,SAC4B;CAC5B,MAAM,YAAA,GAAA,MAAA,QAA4B,KAAK;CACvC,MAAM,cAAA,GAAA,MAAA,QAAoB,MAAM;AAEhC,KAAI,CAAC,SAAS,WAAW,SAAS,QAAQ,SACxC,UAAS,UAAU,SAAS;CAG9B,MAAM,kBAAA,GAAA,MAAA,cACH,kBAA8B,SAAS,QAAS,UAAU,cAAc,EACzE,EAAE,CACH;CACD,MAAM,iBAAA,GAAA,MAAA,mBACE,SAAS,QAAS,OACxB,EAAE,CACH;AACD,EAAA,GAAA,MAAA,sBAAqB,gBAAgB,eAAe,cAAc;AAElE,EAAA,GAAA,MAAA,iBAAgB;AACd,aAAW,UAAU;AACrB,MAAI,eAAA,gBAAgB,SAAS,QAAQ,CACnC,UAAS,QAAQ,MAAM;AAEzB,eAAa;AACX,cAAW,UAAU;AACrB,oBAAiB;AACf,QAAI,CAAC,WAAW,QACd,UAAS,SAAS,SAAS;MAE5B,EAAE;;IAEN,EAAE,CAAC;CAEN,MAAM,QAAQ,SAAS;AAEvB,QAAO;EACL,OAAO,MAAM;EACb,QAAQ,MAAM;EACd,OAAO,MAAM;EACb,OAAO,MAAM;EACb;EACD;;;;;;;;;;AAWH,SAAgB,YAAkC,SAAqB;CACrE,MAAM,YAAA,GAAA,MAAA,QAA4B,KAAK;CACvC,MAAM,cAAA,GAAA,MAAA,QAAoB,MAAM;AAEhC,KAAI,CAAC,SAAS,WAAW,SAAS,QAAQ,SACxC,UAAS,UAAU,SAAS;AAG9B,EAAA,GAAA,MAAA,iBAAgB;AACd,aAAW,UAAU;AACrB,MAAI,eAAA,gBAAgB,SAAS,QAAQ,CACnC,UAAS,QAAQ,MAAM;AAEzB,eAAa;AACX,cAAW,UAAU;AACrB,oBAAiB;AACf,QAAI,CAAC,WAAW,QACd,UAAS,SAAS,SAAS;MAE5B,EAAE;;IAEN,EAAE,CAAC;AAEN,QAAO,SAAS;;;;;AAalB,SAAgB,SACd,OACA,OACmB;CAEnB,MAAM,eAAA,GAAA,MAAA,mBAAgC;AACpC,SAAO;GACL,OAAO,MAAM,MAAM;GACnB,OAAO,MAAM,OAAO;GACrB;IACA,CAAC,OAAO,MAAM,CAAC;CAGlB,MAAM,aAAA,GAAA,MAAA,QAAmB,aAAa,CAAC;CAkBvC,MAAM,YAAA,GAAA,MAAA,uBAAA,GAAA,MAAA,cAfH,kBAA8B;AAC7B,SAAO,MAAM,gBAAgB;GAC3B,MAAM,OAAO,aAAa;GAC1B,MAAM,UAAU,UAAU;AAG1B,OAAI,KAAK,UAAU,QAAQ,SAAS,KAAK,UAAU,QAAQ,OAAO;AAChE,cAAU,UAAU;AACpB,mBAAe;;IAEjB;IAEJ,CAAC,OAAO,YAAY,CACrB,QAIO,UAAU,eACV,UAAU,QACjB;CAED,MAAM,OAAA,GAAA,MAAA,cACH,UAAgB;EAGf,MAAM,UAAsB,GAAG,QAAQ,OAAO;AAC7C,QAA4D,IAAI,QAAQ;IAE3E,CAAC,OAAO,MAAM,CACf;AAED,QAAO;EACL,OAAO,SAAS;EAChB,OAAO,SAAS;EAChB;EACD"}
|
package/dist/react/use-model.js
CHANGED
|
@@ -1,107 +1,91 @@
|
|
|
1
|
-
import { useCallback, useRef, useSyncExternalStore, useEffect } from "react";
|
|
2
1
|
import { isInitializable } from "./guards.js";
|
|
2
|
+
import { useCallback, useEffect, useRef, useSyncExternalStore } from "react";
|
|
3
|
+
//#region src/react/use-model.ts
|
|
4
|
+
/**
|
|
5
|
+
* Bind to a component-scoped Model with validation and dirty state exposed.
|
|
6
|
+
*/
|
|
3
7
|
function useModel(factory) {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
}
|
|
29
|
-
}, 0);
|
|
30
|
-
};
|
|
31
|
-
}, []);
|
|
32
|
-
const model = modelRef.current;
|
|
33
|
-
return {
|
|
34
|
-
state: model.state,
|
|
35
|
-
errors: model.errors,
|
|
36
|
-
valid: model.valid,
|
|
37
|
-
dirty: model.dirty,
|
|
38
|
-
model
|
|
39
|
-
};
|
|
8
|
+
const modelRef = useRef(null);
|
|
9
|
+
const mountedRef = useRef(false);
|
|
10
|
+
if (!modelRef.current || modelRef.current.disposed) modelRef.current = factory();
|
|
11
|
+
const modelSubscribe = useCallback((onStoreChange) => modelRef.current.subscribe(onStoreChange), []);
|
|
12
|
+
const modelSnapshot = useCallback(() => modelRef.current.state, []);
|
|
13
|
+
useSyncExternalStore(modelSubscribe, modelSnapshot, modelSnapshot);
|
|
14
|
+
useEffect(() => {
|
|
15
|
+
mountedRef.current = true;
|
|
16
|
+
if (isInitializable(modelRef.current)) modelRef.current.init();
|
|
17
|
+
return () => {
|
|
18
|
+
mountedRef.current = false;
|
|
19
|
+
setTimeout(() => {
|
|
20
|
+
if (!mountedRef.current) modelRef.current?.dispose();
|
|
21
|
+
}, 0);
|
|
22
|
+
};
|
|
23
|
+
}, []);
|
|
24
|
+
const model = modelRef.current;
|
|
25
|
+
return {
|
|
26
|
+
state: model.state,
|
|
27
|
+
errors: model.errors,
|
|
28
|
+
valid: model.valid,
|
|
29
|
+
dirty: model.dirty,
|
|
30
|
+
model
|
|
31
|
+
};
|
|
40
32
|
}
|
|
33
|
+
/**
|
|
34
|
+
* Create a component-scoped Model with lifecycle management (init + dispose)
|
|
35
|
+
* but NO state subscription. The parent component never re-renders from
|
|
36
|
+
* model state changes.
|
|
37
|
+
*
|
|
38
|
+
* Designed for the per-field isolation pattern: parent creates the model
|
|
39
|
+
* via `useModelRef`, children subscribe to individual fields via `useField`.
|
|
40
|
+
*/
|
|
41
41
|
function useModelRef(factory) {
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
modelRef.current?.dispose();
|
|
57
|
-
}
|
|
58
|
-
}, 0);
|
|
59
|
-
};
|
|
60
|
-
}, []);
|
|
61
|
-
return modelRef.current;
|
|
42
|
+
const modelRef = useRef(null);
|
|
43
|
+
const mountedRef = useRef(false);
|
|
44
|
+
if (!modelRef.current || modelRef.current.disposed) modelRef.current = factory();
|
|
45
|
+
useEffect(() => {
|
|
46
|
+
mountedRef.current = true;
|
|
47
|
+
if (isInitializable(modelRef.current)) modelRef.current.init();
|
|
48
|
+
return () => {
|
|
49
|
+
mountedRef.current = false;
|
|
50
|
+
setTimeout(() => {
|
|
51
|
+
if (!mountedRef.current) modelRef.current?.dispose();
|
|
52
|
+
}, 0);
|
|
53
|
+
};
|
|
54
|
+
}, []);
|
|
55
|
+
return modelRef.current;
|
|
62
56
|
}
|
|
57
|
+
/**
|
|
58
|
+
* Bind to a single Model field with surgical re-renders.
|
|
59
|
+
*/
|
|
63
60
|
function useField(model, field) {
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
(value) => {
|
|
91
|
-
const partial = { [field]: value };
|
|
92
|
-
model.set(partial);
|
|
93
|
-
},
|
|
94
|
-
[model, field]
|
|
95
|
-
);
|
|
96
|
-
return {
|
|
97
|
-
value: snapshot.value,
|
|
98
|
-
error: snapshot.error,
|
|
99
|
-
set
|
|
100
|
-
};
|
|
61
|
+
const getSnapshot = useCallback(() => {
|
|
62
|
+
return {
|
|
63
|
+
value: model.state[field],
|
|
64
|
+
error: model.errors[field]
|
|
65
|
+
};
|
|
66
|
+
}, [model, field]);
|
|
67
|
+
const cachedRef = useRef(getSnapshot());
|
|
68
|
+
const snapshot = useSyncExternalStore(useCallback((onStoreChange) => {
|
|
69
|
+
return model.subscribe(() => {
|
|
70
|
+
const next = getSnapshot();
|
|
71
|
+
const current = cachedRef.current;
|
|
72
|
+
if (next.value !== current.value || next.error !== current.error) {
|
|
73
|
+
cachedRef.current = next;
|
|
74
|
+
onStoreChange();
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
}, [model, getSnapshot]), () => cachedRef.current, () => cachedRef.current);
|
|
78
|
+
const set = useCallback((value) => {
|
|
79
|
+
const partial = { [field]: value };
|
|
80
|
+
model.set(partial);
|
|
81
|
+
}, [model, field]);
|
|
82
|
+
return {
|
|
83
|
+
value: snapshot.value,
|
|
84
|
+
error: snapshot.error,
|
|
85
|
+
set
|
|
86
|
+
};
|
|
101
87
|
}
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
};
|
|
107
|
-
//# sourceMappingURL=use-model.js.map
|
|
88
|
+
//#endregion
|
|
89
|
+
export { useField, useModel, useModelRef };
|
|
90
|
+
|
|
91
|
+
//# sourceMappingURL=use-model.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-model.js","sources":["../../src/react/use-model.ts"],"sourcesContent":["import { useRef, useEffect, useSyncExternalStore, useCallback } from 'react';\nimport type { Model } from '../Model';\nimport type { ValidationErrors } from '../types';\nimport type { StateOf } from './types';\nimport { isInitializable } from './guards';\n\n/** Return type of `useModel`, providing state, validation, and model access. */\nexport interface ModelHandle<S extends object, M extends Model<S>> {\n state: S;\n errors: ValidationErrors<S>;\n valid: boolean;\n dirty: boolean;\n model: M;\n}\n\n/**\n * Bind to a component-scoped Model with validation and dirty state exposed.\n */\nexport function useModel<M extends Model<any>>(\n factory: () => M\n): ModelHandle<StateOf<M>, M> {\n const modelRef = useRef<M | null>(null);\n const mountedRef = useRef(false);\n\n if (!modelRef.current || modelRef.current.disposed) {\n modelRef.current = factory();\n }\n\n const modelSubscribe = useCallback(\n (onStoreChange: () => void) => modelRef.current!.subscribe(onStoreChange),\n []\n );\n const modelSnapshot = useCallback(\n () => modelRef.current!.state,\n []\n );\n useSyncExternalStore(modelSubscribe, modelSnapshot, modelSnapshot);\n\n useEffect(() => {\n mountedRef.current = true;\n if (isInitializable(modelRef.current)) {\n modelRef.current.init();\n }\n return () => {\n mountedRef.current = false;\n setTimeout(() => {\n if (!mountedRef.current) {\n modelRef.current?.dispose();\n }\n }, 0);\n };\n }, []);\n\n const model = modelRef.current;\n\n return {\n state: model.state,\n errors: model.errors,\n valid: model.valid,\n dirty: model.dirty,\n model,\n };\n}\n\n/**\n * Create a component-scoped Model with lifecycle management (init + dispose)\n * but NO state subscription. The parent component never re-renders from\n * model state changes.\n *\n * Designed for the per-field isolation pattern: parent creates the model\n * via `useModelRef`, children subscribe to individual fields via `useField`.\n */\nexport function useModelRef<M extends Model<any>>(factory: () => M): M {\n const modelRef = useRef<M | null>(null);\n const mountedRef = useRef(false);\n\n if (!modelRef.current || modelRef.current.disposed) {\n modelRef.current = factory();\n }\n\n useEffect(() => {\n mountedRef.current = true;\n if (isInitializable(modelRef.current)) {\n modelRef.current.init();\n }\n return () => {\n mountedRef.current = false;\n setTimeout(() => {\n if (!mountedRef.current) {\n modelRef.current?.dispose();\n }\n }, 0);\n };\n }, []);\n\n return modelRef.current;\n}\n\n/** Return type of `useField`, providing a single field's value, error, and setter. */\nexport interface FieldHandle<V> {\n value: V;\n error: string | undefined;\n set: (value: V) => void;\n}\n\n/**\n * Bind to a single Model field with surgical re-renders.\n */\nexport function useField<S extends object, K extends keyof S>(\n model: Model<S>,\n field: K\n): FieldHandle<S[K]> {\n // Track the field value and error for comparison\n const getSnapshot = useCallback(() => {\n return {\n value: model.state[field],\n error: model.errors[field],\n };\n }, [model, field]);\n\n // Use object comparison for subscription\n const cachedRef = useRef(getSnapshot());\n\n const subscribe = useCallback(\n (onStoreChange: () => void) => {\n return model.subscribe(() => {\n const next = getSnapshot();\n const current = cachedRef.current;\n\n // Only trigger re-render if field value or error changed\n if (next.value !== current.value || next.error !== current.error) {\n cachedRef.current = next;\n onStoreChange();\n }\n });\n },\n [model, getSnapshot]\n );\n\n const snapshot = useSyncExternalStore(\n subscribe,\n () => cachedRef.current,\n () => cachedRef.current\n );\n\n const set = useCallback(\n (value: S[K]) => {\n // Access the protected set method through type assertion\n // The Model subclass should expose a setter method\n const partial: Partial<S> = { [field]: value } as unknown as Partial<S>;\n (model as unknown as { set: (partial: Partial<S>) => void }).set(partial);\n },\n [model, field]\n );\n\n return {\n value: snapshot.value,\n error: snapshot.error,\n set,\n };\n}\n"],"
|
|
1
|
+
{"version":3,"file":"use-model.js","names":[],"sources":["../../src/react/use-model.ts"],"sourcesContent":["import { useRef, useEffect, useSyncExternalStore, useCallback } from 'react';\nimport type { Model } from '../Model';\nimport type { ValidationErrors } from '../types';\nimport type { StateOf } from './types';\nimport { isInitializable } from './guards';\n\n/** Return type of `useModel`, providing state, validation, and model access. */\nexport interface ModelHandle<S extends object, M extends Model<S>> {\n state: S;\n errors: ValidationErrors<S>;\n valid: boolean;\n dirty: boolean;\n model: M;\n}\n\n/**\n * Bind to a component-scoped Model with validation and dirty state exposed.\n */\nexport function useModel<M extends Model<any>>(\n factory: () => M\n): ModelHandle<StateOf<M>, M> {\n const modelRef = useRef<M | null>(null);\n const mountedRef = useRef(false);\n\n if (!modelRef.current || modelRef.current.disposed) {\n modelRef.current = factory();\n }\n\n const modelSubscribe = useCallback(\n (onStoreChange: () => void) => modelRef.current!.subscribe(onStoreChange),\n []\n );\n const modelSnapshot = useCallback(\n () => modelRef.current!.state,\n []\n );\n useSyncExternalStore(modelSubscribe, modelSnapshot, modelSnapshot);\n\n useEffect(() => {\n mountedRef.current = true;\n if (isInitializable(modelRef.current)) {\n modelRef.current.init();\n }\n return () => {\n mountedRef.current = false;\n setTimeout(() => {\n if (!mountedRef.current) {\n modelRef.current?.dispose();\n }\n }, 0);\n };\n }, []);\n\n const model = modelRef.current;\n\n return {\n state: model.state,\n errors: model.errors,\n valid: model.valid,\n dirty: model.dirty,\n model,\n };\n}\n\n/**\n * Create a component-scoped Model with lifecycle management (init + dispose)\n * but NO state subscription. The parent component never re-renders from\n * model state changes.\n *\n * Designed for the per-field isolation pattern: parent creates the model\n * via `useModelRef`, children subscribe to individual fields via `useField`.\n */\nexport function useModelRef<M extends Model<any>>(factory: () => M): M {\n const modelRef = useRef<M | null>(null);\n const mountedRef = useRef(false);\n\n if (!modelRef.current || modelRef.current.disposed) {\n modelRef.current = factory();\n }\n\n useEffect(() => {\n mountedRef.current = true;\n if (isInitializable(modelRef.current)) {\n modelRef.current.init();\n }\n return () => {\n mountedRef.current = false;\n setTimeout(() => {\n if (!mountedRef.current) {\n modelRef.current?.dispose();\n }\n }, 0);\n };\n }, []);\n\n return modelRef.current;\n}\n\n/** Return type of `useField`, providing a single field's value, error, and setter. */\nexport interface FieldHandle<V> {\n value: V;\n error: string | undefined;\n set: (value: V) => void;\n}\n\n/**\n * Bind to a single Model field with surgical re-renders.\n */\nexport function useField<S extends object, K extends keyof S>(\n model: Model<S>,\n field: K\n): FieldHandle<S[K]> {\n // Track the field value and error for comparison\n const getSnapshot = useCallback(() => {\n return {\n value: model.state[field],\n error: model.errors[field],\n };\n }, [model, field]);\n\n // Use object comparison for subscription\n const cachedRef = useRef(getSnapshot());\n\n const subscribe = useCallback(\n (onStoreChange: () => void) => {\n return model.subscribe(() => {\n const next = getSnapshot();\n const current = cachedRef.current;\n\n // Only trigger re-render if field value or error changed\n if (next.value !== current.value || next.error !== current.error) {\n cachedRef.current = next;\n onStoreChange();\n }\n });\n },\n [model, getSnapshot]\n );\n\n const snapshot = useSyncExternalStore(\n subscribe,\n () => cachedRef.current,\n () => cachedRef.current\n );\n\n const set = useCallback(\n (value: S[K]) => {\n // Access the protected set method through type assertion\n // The Model subclass should expose a setter method\n const partial: Partial<S> = { [field]: value } as unknown as Partial<S>;\n (model as unknown as { set: (partial: Partial<S>) => void }).set(partial);\n },\n [model, field]\n );\n\n return {\n value: snapshot.value,\n error: snapshot.error,\n set,\n };\n}\n"],"mappings":";;;;;;AAkBA,SAAgB,SACd,SAC4B;CAC5B,MAAM,WAAW,OAAiB,KAAK;CACvC,MAAM,aAAa,OAAO,MAAM;AAEhC,KAAI,CAAC,SAAS,WAAW,SAAS,QAAQ,SACxC,UAAS,UAAU,SAAS;CAG9B,MAAM,iBAAiB,aACpB,kBAA8B,SAAS,QAAS,UAAU,cAAc,EACzE,EAAE,CACH;CACD,MAAM,gBAAgB,kBACd,SAAS,QAAS,OACxB,EAAE,CACH;AACD,sBAAqB,gBAAgB,eAAe,cAAc;AAElE,iBAAgB;AACd,aAAW,UAAU;AACrB,MAAI,gBAAgB,SAAS,QAAQ,CACnC,UAAS,QAAQ,MAAM;AAEzB,eAAa;AACX,cAAW,UAAU;AACrB,oBAAiB;AACf,QAAI,CAAC,WAAW,QACd,UAAS,SAAS,SAAS;MAE5B,EAAE;;IAEN,EAAE,CAAC;CAEN,MAAM,QAAQ,SAAS;AAEvB,QAAO;EACL,OAAO,MAAM;EACb,QAAQ,MAAM;EACd,OAAO,MAAM;EACb,OAAO,MAAM;EACb;EACD;;;;;;;;;;AAWH,SAAgB,YAAkC,SAAqB;CACrE,MAAM,WAAW,OAAiB,KAAK;CACvC,MAAM,aAAa,OAAO,MAAM;AAEhC,KAAI,CAAC,SAAS,WAAW,SAAS,QAAQ,SACxC,UAAS,UAAU,SAAS;AAG9B,iBAAgB;AACd,aAAW,UAAU;AACrB,MAAI,gBAAgB,SAAS,QAAQ,CACnC,UAAS,QAAQ,MAAM;AAEzB,eAAa;AACX,cAAW,UAAU;AACrB,oBAAiB;AACf,QAAI,CAAC,WAAW,QACd,UAAS,SAAS,SAAS;MAE5B,EAAE;;IAEN,EAAE,CAAC;AAEN,QAAO,SAAS;;;;;AAalB,SAAgB,SACd,OACA,OACmB;CAEnB,MAAM,cAAc,kBAAkB;AACpC,SAAO;GACL,OAAO,MAAM,MAAM;GACnB,OAAO,MAAM,OAAO;GACrB;IACA,CAAC,OAAO,MAAM,CAAC;CAGlB,MAAM,YAAY,OAAO,aAAa,CAAC;CAkBvC,MAAM,WAAW,qBAhBC,aACf,kBAA8B;AAC7B,SAAO,MAAM,gBAAgB;GAC3B,MAAM,OAAO,aAAa;GAC1B,MAAM,UAAU,UAAU;AAG1B,OAAI,KAAK,UAAU,QAAQ,SAAS,KAAK,UAAU,QAAQ,OAAO;AAChE,cAAU,UAAU;AACpB,mBAAe;;IAEjB;IAEJ,CAAC,OAAO,YAAY,CACrB,QAIO,UAAU,eACV,UAAU,QACjB;CAED,MAAM,MAAM,aACT,UAAgB;EAGf,MAAM,UAAsB,GAAG,QAAQ,OAAO;AAC7C,QAA4D,IAAI,QAAQ;IAE3E,CAAC,OAAO,MAAM,CACf;AAED,QAAO;EACL,OAAO,SAAS;EAChB,OAAO,SAAS;EAChB;EACD"}
|
|
@@ -1,26 +1,22 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
const
|
|
4
|
-
const
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
const useSubscribeOnly = require("./use-subscribe-only.cjs");
|
|
1
|
+
const require_singleton = require("../singleton.cjs");
|
|
2
|
+
const require_use_instance = require("./use-instance.cjs");
|
|
3
|
+
const require_guards = require("./guards.cjs");
|
|
4
|
+
const require_use_subscribe_only = require("./use-subscribe-only.cjs");
|
|
5
|
+
let react = require("react");
|
|
6
|
+
//#region src/react/use-singleton.ts
|
|
8
7
|
function useSingleton(Class, ...args) {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
if (guards.isSubscribeOnly(instance)) {
|
|
20
|
-
useSubscribeOnly.useSubscribeOnly(instance);
|
|
21
|
-
return instance;
|
|
22
|
-
}
|
|
23
|
-
return instance;
|
|
8
|
+
const instance = require_singleton.singleton(Class, ...args);
|
|
9
|
+
(0, react.useEffect)(() => {
|
|
10
|
+
if (require_guards.isInitializable(instance)) instance.init();
|
|
11
|
+
}, [instance]);
|
|
12
|
+
if (require_guards.isSubscribable(instance)) return [require_use_instance.useInstance(instance), instance];
|
|
13
|
+
if (require_guards.isSubscribeOnly(instance)) {
|
|
14
|
+
require_use_subscribe_only.useSubscribeOnly(instance);
|
|
15
|
+
return instance;
|
|
16
|
+
}
|
|
17
|
+
return instance;
|
|
24
18
|
}
|
|
19
|
+
//#endregion
|
|
25
20
|
exports.useSingleton = useSingleton;
|
|
26
|
-
|
|
21
|
+
|
|
22
|
+
//# sourceMappingURL=use-singleton.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-singleton.cjs","sources":["../../src/react/use-singleton.ts"],"sourcesContent":["import { useEffect } from 'react';\nimport type { Subscribable, Disposable } from '../types';\nimport { singleton } from '../singleton';\nimport { useInstance } from './use-instance';\nimport { isSubscribable, isSubscribeOnly, isInitializable } from './guards';\nimport { useSubscribeOnly } from './use-subscribe-only';\nimport type { StateOf } from './types';\n\n/**\n * Get singleton Subscribable with DEFAULT_STATE — no constructor args needed.\n */\nexport function useSingleton<\n T extends Subscribable<S> & Disposable, S = StateOf<T>,\n>(Class: (new (...args: any[]) => T) & { DEFAULT_STATE: unknown }): [S, T];\n\n/**\n * Get singleton Disposable with DEFAULT_STATE — no constructor args needed.\n */\nexport function useSingleton<T extends Disposable>(\n Class: (new (...args: any[]) => T) & { DEFAULT_STATE: unknown },\n): T;\n\n/**\n * Get singleton Subscribable instance and subscribe to its state.\n * Returns [state, instance] tuple.\n */\nexport function useSingleton<\n T extends Subscribable<S> & Disposable,\n S = StateOf<T>,\n Args extends unknown[] = unknown[]\n>(\n Class: new (...args: Args) => T,\n ...args: Args\n): [S, T];\n\n/**\n * Get singleton Disposable instance (non-Subscribable).\n * Returns the instance directly.\n */\nexport function useSingleton<T extends Disposable, Args extends unknown[] = unknown[]>(\n Class: new (...args: Args) => T,\n ...args: Args\n): T;\n\n// Implementation\nexport function useSingleton<T extends Disposable, S = StateOf<T>, Args extends unknown[] = unknown[]>(\n Class: new (...args: Args) => T,\n ...args: Args\n): [S, T] | T {\n const instance = singleton(Class, ...args);\n\n useEffect(() => {\n if (isInitializable(instance)) {\n instance.init();\n }\n }, [instance]);\n\n if (isSubscribable(instance)) {\n const state = useInstance(instance) as S;\n return [state, instance];\n }\n\n if (isSubscribeOnly(instance)) {\n useSubscribeOnly(instance);\n return instance;\n }\n\n return instance;\n}\n"],"
|
|
1
|
+
{"version":3,"file":"use-singleton.cjs","names":[],"sources":["../../src/react/use-singleton.ts"],"sourcesContent":["import { useEffect } from 'react';\nimport type { Subscribable, Disposable } from '../types';\nimport { singleton } from '../singleton';\nimport { useInstance } from './use-instance';\nimport { isSubscribable, isSubscribeOnly, isInitializable } from './guards';\nimport { useSubscribeOnly } from './use-subscribe-only';\nimport type { StateOf } from './types';\n\n/**\n * Get singleton Subscribable with DEFAULT_STATE — no constructor args needed.\n */\nexport function useSingleton<\n T extends Subscribable<S> & Disposable, S = StateOf<T>,\n>(Class: (new (...args: any[]) => T) & { DEFAULT_STATE: unknown }): [S, T];\n\n/**\n * Get singleton Disposable with DEFAULT_STATE — no constructor args needed.\n */\nexport function useSingleton<T extends Disposable>(\n Class: (new (...args: any[]) => T) & { DEFAULT_STATE: unknown },\n): T;\n\n/**\n * Get singleton Subscribable instance and subscribe to its state.\n * Returns [state, instance] tuple.\n */\nexport function useSingleton<\n T extends Subscribable<S> & Disposable,\n S = StateOf<T>,\n Args extends unknown[] = unknown[]\n>(\n Class: new (...args: Args) => T,\n ...args: Args\n): [S, T];\n\n/**\n * Get singleton Disposable instance (non-Subscribable).\n * Returns the instance directly.\n */\nexport function useSingleton<T extends Disposable, Args extends unknown[] = unknown[]>(\n Class: new (...args: Args) => T,\n ...args: Args\n): T;\n\n// Implementation\nexport function useSingleton<T extends Disposable, S = StateOf<T>, Args extends unknown[] = unknown[]>(\n Class: new (...args: Args) => T,\n ...args: Args\n): [S, T] | T {\n const instance = singleton(Class, ...args);\n\n useEffect(() => {\n if (isInitializable(instance)) {\n instance.init();\n }\n }, [instance]);\n\n if (isSubscribable(instance)) {\n const state = useInstance(instance) as S;\n return [state, instance];\n }\n\n if (isSubscribeOnly(instance)) {\n useSubscribeOnly(instance);\n return instance;\n }\n\n return instance;\n}\n"],"mappings":";;;;;;AA6CA,SAAgB,aACd,OACA,GAAG,MACS;CACZ,MAAM,WAAW,kBAAA,UAAU,OAAO,GAAG,KAAK;AAE1C,EAAA,GAAA,MAAA,iBAAgB;AACd,MAAI,eAAA,gBAAgB,SAAS,CAC3B,UAAS,MAAM;IAEhB,CAAC,SAAS,CAAC;AAEd,KAAI,eAAA,eAAe,SAAS,CAE1B,QAAO,CADO,qBAAA,YAAY,SAAS,EACpB,SAAS;AAG1B,KAAI,eAAA,gBAAgB,SAAS,EAAE;AAC7B,6BAAA,iBAAiB,SAAS;AAC1B,SAAO;;AAGT,QAAO"}
|
|
@@ -1,26 +1,22 @@
|
|
|
1
|
-
import { useEffect } from "react";
|
|
2
1
|
import { singleton } from "../singleton.js";
|
|
3
2
|
import { useInstance } from "./use-instance.js";
|
|
4
3
|
import { isInitializable, isSubscribable, isSubscribeOnly } from "./guards.js";
|
|
5
4
|
import { useSubscribeOnly } from "./use-subscribe-only.js";
|
|
5
|
+
import { useEffect } from "react";
|
|
6
|
+
//#region src/react/use-singleton.ts
|
|
6
7
|
function useSingleton(Class, ...args) {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
if (isSubscribeOnly(instance)) {
|
|
18
|
-
useSubscribeOnly(instance);
|
|
19
|
-
return instance;
|
|
20
|
-
}
|
|
21
|
-
return instance;
|
|
8
|
+
const instance = singleton(Class, ...args);
|
|
9
|
+
useEffect(() => {
|
|
10
|
+
if (isInitializable(instance)) instance.init();
|
|
11
|
+
}, [instance]);
|
|
12
|
+
if (isSubscribable(instance)) return [useInstance(instance), instance];
|
|
13
|
+
if (isSubscribeOnly(instance)) {
|
|
14
|
+
useSubscribeOnly(instance);
|
|
15
|
+
return instance;
|
|
16
|
+
}
|
|
17
|
+
return instance;
|
|
22
18
|
}
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
//# sourceMappingURL=use-singleton.js.map
|
|
19
|
+
//#endregion
|
|
20
|
+
export { useSingleton };
|
|
21
|
+
|
|
22
|
+
//# sourceMappingURL=use-singleton.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-singleton.js","sources":["../../src/react/use-singleton.ts"],"sourcesContent":["import { useEffect } from 'react';\nimport type { Subscribable, Disposable } from '../types';\nimport { singleton } from '../singleton';\nimport { useInstance } from './use-instance';\nimport { isSubscribable, isSubscribeOnly, isInitializable } from './guards';\nimport { useSubscribeOnly } from './use-subscribe-only';\nimport type { StateOf } from './types';\n\n/**\n * Get singleton Subscribable with DEFAULT_STATE — no constructor args needed.\n */\nexport function useSingleton<\n T extends Subscribable<S> & Disposable, S = StateOf<T>,\n>(Class: (new (...args: any[]) => T) & { DEFAULT_STATE: unknown }): [S, T];\n\n/**\n * Get singleton Disposable with DEFAULT_STATE — no constructor args needed.\n */\nexport function useSingleton<T extends Disposable>(\n Class: (new (...args: any[]) => T) & { DEFAULT_STATE: unknown },\n): T;\n\n/**\n * Get singleton Subscribable instance and subscribe to its state.\n * Returns [state, instance] tuple.\n */\nexport function useSingleton<\n T extends Subscribable<S> & Disposable,\n S = StateOf<T>,\n Args extends unknown[] = unknown[]\n>(\n Class: new (...args: Args) => T,\n ...args: Args\n): [S, T];\n\n/**\n * Get singleton Disposable instance (non-Subscribable).\n * Returns the instance directly.\n */\nexport function useSingleton<T extends Disposable, Args extends unknown[] = unknown[]>(\n Class: new (...args: Args) => T,\n ...args: Args\n): T;\n\n// Implementation\nexport function useSingleton<T extends Disposable, S = StateOf<T>, Args extends unknown[] = unknown[]>(\n Class: new (...args: Args) => T,\n ...args: Args\n): [S, T] | T {\n const instance = singleton(Class, ...args);\n\n useEffect(() => {\n if (isInitializable(instance)) {\n instance.init();\n }\n }, [instance]);\n\n if (isSubscribable(instance)) {\n const state = useInstance(instance) as S;\n return [state, instance];\n }\n\n if (isSubscribeOnly(instance)) {\n useSubscribeOnly(instance);\n return instance;\n }\n\n return instance;\n}\n"],"
|
|
1
|
+
{"version":3,"file":"use-singleton.js","names":[],"sources":["../../src/react/use-singleton.ts"],"sourcesContent":["import { useEffect } from 'react';\nimport type { Subscribable, Disposable } from '../types';\nimport { singleton } from '../singleton';\nimport { useInstance } from './use-instance';\nimport { isSubscribable, isSubscribeOnly, isInitializable } from './guards';\nimport { useSubscribeOnly } from './use-subscribe-only';\nimport type { StateOf } from './types';\n\n/**\n * Get singleton Subscribable with DEFAULT_STATE — no constructor args needed.\n */\nexport function useSingleton<\n T extends Subscribable<S> & Disposable, S = StateOf<T>,\n>(Class: (new (...args: any[]) => T) & { DEFAULT_STATE: unknown }): [S, T];\n\n/**\n * Get singleton Disposable with DEFAULT_STATE — no constructor args needed.\n */\nexport function useSingleton<T extends Disposable>(\n Class: (new (...args: any[]) => T) & { DEFAULT_STATE: unknown },\n): T;\n\n/**\n * Get singleton Subscribable instance and subscribe to its state.\n * Returns [state, instance] tuple.\n */\nexport function useSingleton<\n T extends Subscribable<S> & Disposable,\n S = StateOf<T>,\n Args extends unknown[] = unknown[]\n>(\n Class: new (...args: Args) => T,\n ...args: Args\n): [S, T];\n\n/**\n * Get singleton Disposable instance (non-Subscribable).\n * Returns the instance directly.\n */\nexport function useSingleton<T extends Disposable, Args extends unknown[] = unknown[]>(\n Class: new (...args: Args) => T,\n ...args: Args\n): T;\n\n// Implementation\nexport function useSingleton<T extends Disposable, S = StateOf<T>, Args extends unknown[] = unknown[]>(\n Class: new (...args: Args) => T,\n ...args: Args\n): [S, T] | T {\n const instance = singleton(Class, ...args);\n\n useEffect(() => {\n if (isInitializable(instance)) {\n instance.init();\n }\n }, [instance]);\n\n if (isSubscribable(instance)) {\n const state = useInstance(instance) as S;\n return [state, instance];\n }\n\n if (isSubscribeOnly(instance)) {\n useSubscribeOnly(instance);\n return instance;\n }\n\n return instance;\n}\n"],"mappings":";;;;;;AA6CA,SAAgB,aACd,OACA,GAAG,MACS;CACZ,MAAM,WAAW,UAAU,OAAO,GAAG,KAAK;AAE1C,iBAAgB;AACd,MAAI,gBAAgB,SAAS,CAC3B,UAAS,MAAM;IAEhB,CAAC,SAAS,CAAC;AAEd,KAAI,eAAe,SAAS,CAE1B,QAAO,CADO,YAAY,SAAS,EACpB,SAAS;AAG1B,KAAI,gBAAgB,SAAS,EAAE;AAC7B,mBAAiB,SAAS;AAC1B,SAAO;;AAGT,QAAO"}
|
|
@@ -1,25 +1,31 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
1
|
+
let react = require("react");
|
|
2
|
+
//#region src/react/use-subscribe-only.ts
|
|
3
|
+
var SERVER_SNAPSHOT = () => 0;
|
|
4
|
+
/**
|
|
5
|
+
* Subscribe to a notification-only object (has subscribe() but no state).
|
|
6
|
+
* Triggers React re-renders via version counter when the target notifies.
|
|
7
|
+
* @internal
|
|
8
|
+
*/
|
|
5
9
|
function useSubscribeOnly(target) {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
10
|
+
const ref = (0, react.useRef)(null);
|
|
11
|
+
if (!ref.current || ref.current.target !== target) {
|
|
12
|
+
const version = { current: ref.current?.version ?? 0 };
|
|
13
|
+
ref.current = {
|
|
14
|
+
target,
|
|
15
|
+
version: version.current,
|
|
16
|
+
subscribe: (onStoreChange) => {
|
|
17
|
+
return target.subscribe(() => {
|
|
18
|
+
version.current++;
|
|
19
|
+
ref.current.version = version.current;
|
|
20
|
+
onStoreChange();
|
|
21
|
+
});
|
|
22
|
+
},
|
|
23
|
+
getSnapshot: () => version.current
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
(0, react.useSyncExternalStore)(ref.current.subscribe, ref.current.getSnapshot, SERVER_SNAPSHOT);
|
|
23
27
|
}
|
|
28
|
+
//#endregion
|
|
24
29
|
exports.useSubscribeOnly = useSubscribeOnly;
|
|
25
|
-
|
|
30
|
+
|
|
31
|
+
//# sourceMappingURL=use-subscribe-only.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-subscribe-only.cjs","sources":["../../src/react/use-subscribe-only.ts"],"sourcesContent":["import { useSyncExternalStore, useRef } from 'react';\n\ninterface SubscribeOnlyRef {\n target: { subscribe(cb: () => void): () => void };\n version: number;\n subscribe: (onStoreChange: () => void) => () => void;\n getSnapshot: () => number;\n}\n\nconst SERVER_SNAPSHOT = () => 0;\n\n/**\n * Subscribe to a notification-only object (has subscribe() but no state).\n * Triggers React re-renders via version counter when the target notifies.\n * @internal\n */\nexport function useSubscribeOnly(\n target: { subscribe(cb: () => void): () => void },\n): void {\n const ref = useRef<SubscribeOnlyRef | null>(null);\n\n if (!ref.current || ref.current.target !== target) {\n const version = { current: ref.current?.version ?? 0 };\n ref.current = {\n target,\n version: version.current,\n subscribe: (onStoreChange: () => void) => {\n return target.subscribe(() => {\n version.current++;\n ref.current!.version = version.current;\n onStoreChange();\n });\n },\n getSnapshot: () => version.current,\n };\n }\n\n useSyncExternalStore(ref.current.subscribe, ref.current.getSnapshot, SERVER_SNAPSHOT);\n}\n"],"
|
|
1
|
+
{"version":3,"file":"use-subscribe-only.cjs","names":[],"sources":["../../src/react/use-subscribe-only.ts"],"sourcesContent":["import { useSyncExternalStore, useRef } from 'react';\n\ninterface SubscribeOnlyRef {\n target: { subscribe(cb: () => void): () => void };\n version: number;\n subscribe: (onStoreChange: () => void) => () => void;\n getSnapshot: () => number;\n}\n\nconst SERVER_SNAPSHOT = () => 0;\n\n/**\n * Subscribe to a notification-only object (has subscribe() but no state).\n * Triggers React re-renders via version counter when the target notifies.\n * @internal\n */\nexport function useSubscribeOnly(\n target: { subscribe(cb: () => void): () => void },\n): void {\n const ref = useRef<SubscribeOnlyRef | null>(null);\n\n if (!ref.current || ref.current.target !== target) {\n const version = { current: ref.current?.version ?? 0 };\n ref.current = {\n target,\n version: version.current,\n subscribe: (onStoreChange: () => void) => {\n return target.subscribe(() => {\n version.current++;\n ref.current!.version = version.current;\n onStoreChange();\n });\n },\n getSnapshot: () => version.current,\n };\n }\n\n useSyncExternalStore(ref.current.subscribe, ref.current.getSnapshot, SERVER_SNAPSHOT);\n}\n"],"mappings":";;AASA,IAAM,wBAAwB;;;;;;AAO9B,SAAgB,iBACd,QACM;CACN,MAAM,OAAA,GAAA,MAAA,QAAsC,KAAK;AAEjD,KAAI,CAAC,IAAI,WAAW,IAAI,QAAQ,WAAW,QAAQ;EACjD,MAAM,UAAU,EAAE,SAAS,IAAI,SAAS,WAAW,GAAG;AACtD,MAAI,UAAU;GACZ;GACA,SAAS,QAAQ;GACjB,YAAY,kBAA8B;AACxC,WAAO,OAAO,gBAAgB;AAC5B,aAAQ;AACR,SAAI,QAAS,UAAU,QAAQ;AAC/B,oBAAe;MACf;;GAEJ,mBAAmB,QAAQ;GAC5B;;AAGH,EAAA,GAAA,MAAA,sBAAqB,IAAI,QAAQ,WAAW,IAAI,QAAQ,aAAa,gBAAgB"}
|
|
@@ -1,25 +1,31 @@
|
|
|
1
1
|
import { useRef, useSyncExternalStore } from "react";
|
|
2
|
-
|
|
2
|
+
//#region src/react/use-subscribe-only.ts
|
|
3
|
+
var SERVER_SNAPSHOT = () => 0;
|
|
4
|
+
/**
|
|
5
|
+
* Subscribe to a notification-only object (has subscribe() but no state).
|
|
6
|
+
* Triggers React re-renders via version counter when the target notifies.
|
|
7
|
+
* @internal
|
|
8
|
+
*/
|
|
3
9
|
function useSubscribeOnly(target) {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
10
|
+
const ref = useRef(null);
|
|
11
|
+
if (!ref.current || ref.current.target !== target) {
|
|
12
|
+
const version = { current: ref.current?.version ?? 0 };
|
|
13
|
+
ref.current = {
|
|
14
|
+
target,
|
|
15
|
+
version: version.current,
|
|
16
|
+
subscribe: (onStoreChange) => {
|
|
17
|
+
return target.subscribe(() => {
|
|
18
|
+
version.current++;
|
|
19
|
+
ref.current.version = version.current;
|
|
20
|
+
onStoreChange();
|
|
21
|
+
});
|
|
22
|
+
},
|
|
23
|
+
getSnapshot: () => version.current
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
useSyncExternalStore(ref.current.subscribe, ref.current.getSnapshot, SERVER_SNAPSHOT);
|
|
21
27
|
}
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
//# sourceMappingURL=use-subscribe-only.js.map
|
|
28
|
+
//#endregion
|
|
29
|
+
export { useSubscribeOnly };
|
|
30
|
+
|
|
31
|
+
//# sourceMappingURL=use-subscribe-only.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-subscribe-only.js","sources":["../../src/react/use-subscribe-only.ts"],"sourcesContent":["import { useSyncExternalStore, useRef } from 'react';\n\ninterface SubscribeOnlyRef {\n target: { subscribe(cb: () => void): () => void };\n version: number;\n subscribe: (onStoreChange: () => void) => () => void;\n getSnapshot: () => number;\n}\n\nconst SERVER_SNAPSHOT = () => 0;\n\n/**\n * Subscribe to a notification-only object (has subscribe() but no state).\n * Triggers React re-renders via version counter when the target notifies.\n * @internal\n */\nexport function useSubscribeOnly(\n target: { subscribe(cb: () => void): () => void },\n): void {\n const ref = useRef<SubscribeOnlyRef | null>(null);\n\n if (!ref.current || ref.current.target !== target) {\n const version = { current: ref.current?.version ?? 0 };\n ref.current = {\n target,\n version: version.current,\n subscribe: (onStoreChange: () => void) => {\n return target.subscribe(() => {\n version.current++;\n ref.current!.version = version.current;\n onStoreChange();\n });\n },\n getSnapshot: () => version.current,\n };\n }\n\n useSyncExternalStore(ref.current.subscribe, ref.current.getSnapshot, SERVER_SNAPSHOT);\n}\n"],"
|
|
1
|
+
{"version":3,"file":"use-subscribe-only.js","names":[],"sources":["../../src/react/use-subscribe-only.ts"],"sourcesContent":["import { useSyncExternalStore, useRef } from 'react';\n\ninterface SubscribeOnlyRef {\n target: { subscribe(cb: () => void): () => void };\n version: number;\n subscribe: (onStoreChange: () => void) => () => void;\n getSnapshot: () => number;\n}\n\nconst SERVER_SNAPSHOT = () => 0;\n\n/**\n * Subscribe to a notification-only object (has subscribe() but no state).\n * Triggers React re-renders via version counter when the target notifies.\n * @internal\n */\nexport function useSubscribeOnly(\n target: { subscribe(cb: () => void): () => void },\n): void {\n const ref = useRef<SubscribeOnlyRef | null>(null);\n\n if (!ref.current || ref.current.target !== target) {\n const version = { current: ref.current?.version ?? 0 };\n ref.current = {\n target,\n version: version.current,\n subscribe: (onStoreChange: () => void) => {\n return target.subscribe(() => {\n version.current++;\n ref.current!.version = version.current;\n onStoreChange();\n });\n },\n getSnapshot: () => version.current,\n };\n }\n\n useSyncExternalStore(ref.current.subscribe, ref.current.getSnapshot, SERVER_SNAPSHOT);\n}\n"],"mappings":";;AASA,IAAM,wBAAwB;;;;;;AAO9B,SAAgB,iBACd,QACM;CACN,MAAM,MAAM,OAAgC,KAAK;AAEjD,KAAI,CAAC,IAAI,WAAW,IAAI,QAAQ,WAAW,QAAQ;EACjD,MAAM,UAAU,EAAE,SAAS,IAAI,SAAS,WAAW,GAAG;AACtD,MAAI,UAAU;GACZ;GACA,SAAS,QAAQ;GACjB,YAAY,kBAA8B;AACxC,WAAO,OAAO,gBAAgB;AAC5B,aAAQ;AACR,SAAI,QAAS,UAAU,QAAQ;AAC/B,oBAAe;MACf;;GAEJ,mBAAmB,QAAQ;GAC5B;;AAGH,sBAAqB,IAAI,QAAQ,WAAW,IAAI,QAAQ,aAAa,gBAAgB"}
|
|
@@ -1,22 +1,23 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
1
|
+
const require_singleton = require("../singleton.cjs");
|
|
2
|
+
let react = require("react");
|
|
3
|
+
//#region src/react/use-teardown.ts
|
|
4
|
+
/**
|
|
5
|
+
* Teardown singleton class(es) on unmount.
|
|
6
|
+
* Uses deferred disposal to handle StrictMode's double-mount cycle.
|
|
7
|
+
*/
|
|
5
8
|
function useTeardown(...Classes) {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
}
|
|
17
|
-
}, 0);
|
|
18
|
-
};
|
|
19
|
-
}, []);
|
|
9
|
+
const mountedRef = (0, react.useRef)(false);
|
|
10
|
+
(0, react.useEffect)(() => {
|
|
11
|
+
mountedRef.current = true;
|
|
12
|
+
return () => {
|
|
13
|
+
mountedRef.current = false;
|
|
14
|
+
setTimeout(() => {
|
|
15
|
+
if (!mountedRef.current) for (const Class of Classes) require_singleton.teardown(Class);
|
|
16
|
+
}, 0);
|
|
17
|
+
};
|
|
18
|
+
}, []);
|
|
20
19
|
}
|
|
20
|
+
//#endregion
|
|
21
21
|
exports.useTeardown = useTeardown;
|
|
22
|
-
|
|
22
|
+
|
|
23
|
+
//# sourceMappingURL=use-teardown.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-teardown.cjs","sources":["../../src/react/use-teardown.ts"],"sourcesContent":["import { useEffect, useRef } from 'react';\nimport type { Disposable } from '../types';\nimport { teardown } from '../singleton';\n\n/**\n * Teardown singleton class(es) on unmount.\n * Uses deferred disposal to handle StrictMode's double-mount cycle.\n */\nexport function useTeardown(\n ...Classes: Array<new (...args: unknown[]) => Disposable>\n): void {\n const mountedRef = useRef(false);\n\n useEffect(() => {\n mountedRef.current = true;\n return () => {\n mountedRef.current = false;\n setTimeout(() => {\n if (!mountedRef.current) {\n for (const Class of Classes) {\n teardown(Class);\n }\n }\n }, 0);\n };\n }, []); // eslint-disable-line react-hooks/exhaustive-deps\n}\n"],"
|
|
1
|
+
{"version":3,"file":"use-teardown.cjs","names":[],"sources":["../../src/react/use-teardown.ts"],"sourcesContent":["import { useEffect, useRef } from 'react';\nimport type { Disposable } from '../types';\nimport { teardown } from '../singleton';\n\n/**\n * Teardown singleton class(es) on unmount.\n * Uses deferred disposal to handle StrictMode's double-mount cycle.\n */\nexport function useTeardown(\n ...Classes: Array<new (...args: unknown[]) => Disposable>\n): void {\n const mountedRef = useRef(false);\n\n useEffect(() => {\n mountedRef.current = true;\n return () => {\n mountedRef.current = false;\n setTimeout(() => {\n if (!mountedRef.current) {\n for (const Class of Classes) {\n teardown(Class);\n }\n }\n }, 0);\n };\n }, []); // eslint-disable-line react-hooks/exhaustive-deps\n}\n"],"mappings":";;;;;;;AAQA,SAAgB,YACd,GAAG,SACG;CACN,MAAM,cAAA,GAAA,MAAA,QAAoB,MAAM;AAEhC,EAAA,GAAA,MAAA,iBAAgB;AACd,aAAW,UAAU;AACrB,eAAa;AACX,cAAW,UAAU;AACrB,oBAAiB;AACf,QAAI,CAAC,WAAW,QACd,MAAK,MAAM,SAAS,QAClB,mBAAA,SAAS,MAAM;MAGlB,EAAE;;IAEN,EAAE,CAAC"}
|