@virentia/react 0.1.1 → 0.2.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/README.md +17 -0
- package/dist/index.cjs +68 -11
- package/dist/index.d.cts +18 -5
- package/dist/index.d.mts +18 -5
- package/dist/index.mjs +68 -11
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -75,6 +75,23 @@ export const Counter = component({
|
|
|
75
75
|
});
|
|
76
76
|
```
|
|
77
77
|
|
|
78
|
+
`component` also exposes `.create()` and accepts a `model` prop for controlled usage.
|
|
79
|
+
The created model owns its scope and should be disposed by the caller.
|
|
80
|
+
|
|
81
|
+
```tsx
|
|
82
|
+
const Parent = component({
|
|
83
|
+
model() {
|
|
84
|
+
const counter = Counter.create({ step: 2 });
|
|
85
|
+
|
|
86
|
+
return { counter };
|
|
87
|
+
},
|
|
88
|
+
|
|
89
|
+
view({ model }) {
|
|
90
|
+
return <Counter step={2} model={model.counter} />;
|
|
91
|
+
},
|
|
92
|
+
});
|
|
93
|
+
```
|
|
94
|
+
|
|
78
95
|
## Model Caches
|
|
79
96
|
|
|
80
97
|
Use `createModelCache` when a model should survive unmount and be reused by key: chats, tabs, detail screens, media players, or previews.
|
package/dist/index.cjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
-
let react = require("react");
|
|
3
2
|
let _virentia_core = require("@virentia/core");
|
|
3
|
+
let react = require("react");
|
|
4
4
|
//#region lib/model-cache.ts
|
|
5
5
|
function createModelCache() {
|
|
6
6
|
const byScope = /* @__PURE__ */ new WeakMap();
|
|
@@ -91,6 +91,9 @@ function useProvidedScope() {
|
|
|
91
91
|
if (!scope) throw new Error("[useProvidedScope] Scope is not provided. Wrap your tree with ScopeProvider.");
|
|
92
92
|
return scope;
|
|
93
93
|
}
|
|
94
|
+
function useOptionalProvidedScope() {
|
|
95
|
+
return (0, react.useContext)(ScopeContext);
|
|
96
|
+
}
|
|
94
97
|
//#endregion
|
|
95
98
|
//#region lib/utils.ts
|
|
96
99
|
const useIsomorphicLayoutEffect = typeof window === "undefined" ? react.useEffect : react.useLayoutEffect;
|
|
@@ -177,7 +180,8 @@ function useStoreUnit(unit, scope) {
|
|
|
177
180
|
function useModel(modelOrFactory, props, options) {
|
|
178
181
|
const scope = useProvidedScope();
|
|
179
182
|
if (typeof modelOrFactory !== "function") return useReactiveModel(modelOrFactory, scope);
|
|
180
|
-
|
|
183
|
+
const instance = useModelInstance(modelOrFactory, props, scope, options);
|
|
184
|
+
return useReactiveModel(instance.model, instance.scope);
|
|
181
185
|
}
|
|
182
186
|
function useModelInstance(factory, props, scope, options) {
|
|
183
187
|
const cache = options?.cache;
|
|
@@ -191,6 +195,11 @@ function useModelInstance(factory, props, scope, options) {
|
|
|
191
195
|
key,
|
|
192
196
|
scope
|
|
193
197
|
]);
|
|
198
|
+
useModelInstanceLifecycle(instance, props, { disposeOnUnmount: !cached });
|
|
199
|
+
return instance;
|
|
200
|
+
}
|
|
201
|
+
function useModelInstanceLifecycle(instance, props, options) {
|
|
202
|
+
const scope = instance.scope;
|
|
194
203
|
useIsomorphicLayoutEffect(() => {
|
|
195
204
|
writeStore(instance.props, props, scope);
|
|
196
205
|
}, [
|
|
@@ -208,14 +217,13 @@ function useModelInstance(factory, props, scope, options) {
|
|
|
208
217
|
instance.mounts.value = Math.max(0, instance.mounts.value - 1);
|
|
209
218
|
instance.unmounted();
|
|
210
219
|
});
|
|
211
|
-
if (
|
|
220
|
+
if (options.disposeOnUnmount) instance.dispose();
|
|
212
221
|
};
|
|
213
222
|
}, [
|
|
214
|
-
cached,
|
|
215
223
|
instance,
|
|
224
|
+
options.disposeOnUnmount,
|
|
216
225
|
scope
|
|
217
226
|
]);
|
|
218
|
-
return instance;
|
|
219
227
|
}
|
|
220
228
|
function createModelInstance(factory, props, scope, key) {
|
|
221
229
|
return (0, _virentia_core.owner)((dispose, modelOwner) => {
|
|
@@ -236,34 +244,83 @@ function createModelInstance(factory, props, scope, key) {
|
|
|
236
244
|
};
|
|
237
245
|
});
|
|
238
246
|
}
|
|
247
|
+
function exposeModelInstance(instance) {
|
|
248
|
+
const model = instance.model;
|
|
249
|
+
Object.defineProperty(model, modelInstanceSymbol, {
|
|
250
|
+
configurable: true,
|
|
251
|
+
value: instance
|
|
252
|
+
});
|
|
253
|
+
defineHidden(model, "dispose", () => instance.dispose());
|
|
254
|
+
defineHidden(model, disposeSymbol, () => instance.dispose());
|
|
255
|
+
return model;
|
|
256
|
+
}
|
|
257
|
+
function readExposedModelInstance(model) {
|
|
258
|
+
return model[modelInstanceSymbol] ?? null;
|
|
259
|
+
}
|
|
239
260
|
function useReactiveModel(model, scope) {
|
|
240
261
|
const result = {};
|
|
241
262
|
for (const key of Reflect.ownKeys(model)) {
|
|
242
|
-
|
|
263
|
+
const descriptor = Reflect.getOwnPropertyDescriptor(model, key);
|
|
264
|
+
if (key === "dispose" || key === disposeSymbol || descriptor && !descriptor.enumerable) continue;
|
|
243
265
|
result[key] = useModelValue(Reflect.get(model, key), scope);
|
|
244
266
|
}
|
|
245
267
|
return result;
|
|
246
268
|
}
|
|
247
269
|
const disposeSymbol = typeof Symbol.dispose === "symbol" ? Symbol.dispose : Symbol.for("Symbol.dispose");
|
|
270
|
+
const modelInstanceSymbol = Symbol("virentia.react.modelInstance");
|
|
271
|
+
function defineHidden(target, key, value) {
|
|
272
|
+
if (key in target) return;
|
|
273
|
+
Object.defineProperty(target, key, {
|
|
274
|
+
configurable: true,
|
|
275
|
+
value
|
|
276
|
+
});
|
|
277
|
+
}
|
|
248
278
|
function useModelValue(value, scope) {
|
|
249
279
|
if (isUnitLike(value)) return useUnitWithScope(value, scope);
|
|
280
|
+
if (isComponentModel(value)) return value;
|
|
250
281
|
if (isPlainObject(value)) return useReactiveModel(value, scope);
|
|
251
282
|
return value;
|
|
252
283
|
}
|
|
284
|
+
function isComponentModel(value) {
|
|
285
|
+
return Boolean(value && typeof value === "object" && value[modelInstanceSymbol]);
|
|
286
|
+
}
|
|
253
287
|
//#endregion
|
|
254
288
|
//#region lib/component.ts
|
|
255
289
|
function component(config) {
|
|
256
290
|
const VirentiaComponent = (props) => {
|
|
257
|
-
const model
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
291
|
+
const { model: controlledModel, ...modelProps } = props;
|
|
292
|
+
const providedScope = useOptionalProvidedScope();
|
|
293
|
+
const controlledInstance = controlledModel ? readExposedModelInstance(controlledModel) : null;
|
|
294
|
+
const key = "cache" in config ? config.key(modelProps) : void 0;
|
|
295
|
+
const instance = (0, react.useMemo)(() => {
|
|
296
|
+
if (controlledModel) {
|
|
297
|
+
if (!controlledInstance) throw new Error("[component] The model prop must be created with component.create().");
|
|
298
|
+
return controlledInstance;
|
|
299
|
+
}
|
|
300
|
+
if (!providedScope) throw new Error("[useProvidedScope] Scope is not provided. Wrap your tree with ScopeProvider.");
|
|
301
|
+
if ("cache" in config) return getOrCreateCachedInstance(config.cache, providedScope, key, () => createModelInstance(config.model, modelProps, providedScope, key));
|
|
302
|
+
return createModelInstance(config.model, modelProps, providedScope, void 0);
|
|
303
|
+
}, [
|
|
304
|
+
controlledInstance,
|
|
305
|
+
controlledModel,
|
|
306
|
+
key,
|
|
307
|
+
providedScope
|
|
308
|
+
]);
|
|
309
|
+
const cached = !controlledModel && "cache" in config;
|
|
310
|
+
const model = useReactiveModel(instance.model, instance.scope);
|
|
311
|
+
useModelInstanceLifecycle(instance, modelProps, { disposeOnUnmount: !controlledModel && !cached });
|
|
261
312
|
return (0, react.createElement)(config.view, {
|
|
262
|
-
...
|
|
313
|
+
...modelProps,
|
|
263
314
|
model
|
|
264
315
|
});
|
|
265
316
|
};
|
|
266
317
|
VirentiaComponent.displayName = getComponentName(config.view);
|
|
318
|
+
VirentiaComponent.create = ((props) => {
|
|
319
|
+
const externalScope = (0, _virentia_core.getCurrentScope)();
|
|
320
|
+
if (!externalScope) throw new Error("[component.create] Parent component context is required. Call .create() while creating a parent component model.");
|
|
321
|
+
const key = "cache" in config ? config.key(props) : void 0;
|
|
322
|
+
return exposeModelInstance(createModelInstance(config.model, props, externalScope, key));
|
|
323
|
+
});
|
|
267
324
|
return VirentiaComponent;
|
|
268
325
|
}
|
|
269
326
|
//#endregion
|
package/dist/index.d.cts
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
|
+
import { DisposableOwner, Effect, EffectCallArgs, EventCallable, EventPayload, Owner, Scope, Store, StoreWritable } from "@virentia/core";
|
|
1
2
|
import { ComponentType, FC, ReactNode } from "react";
|
|
2
|
-
import { Effect, EffectCallArgs, EventCallable, EventPayload, Owner, Scope, Store, StoreWritable } from "@virentia/core";
|
|
3
3
|
|
|
4
4
|
//#region lib/types.d.ts
|
|
5
5
|
type UnitLike = Store<any> | StoreWritable<any> | EventCallable<any> | Effect<any, any, any>;
|
|
6
|
+
declare const componentModelBrand: unique symbol;
|
|
6
7
|
type UnitValue<Unit> = Unit extends Store<infer State> | StoreWritable<infer State> ? State : Unit extends EventCallable<infer Payload> ? (...payload: EventPayload<Payload>) => Promise<void> : Unit extends Effect<infer Params, infer Done, any> ? (...args: EffectCallArgs<Params>) => Promise<Done> : never;
|
|
7
|
-
type ReactiveModel<Model> = { readonly [Key in keyof Model as Key extends "dispose" ? never : Key]: Model[Key] extends UnitLike ? UnitValue<Model[Key]> : Model[Key] extends ((...args: any[]) => any) ? Model[Key] : Model[Key] extends object ? ReactiveModel<Model[Key]> : Model[Key] };
|
|
8
|
+
type ReactiveModel<Model> = { readonly [Key in keyof Model as Key extends "dispose" ? never : Key]: Model[Key] extends ComponentModel<infer ChildModel> ? ComponentModel<ChildModel> : Model[Key] extends UnitLike ? UnitValue<Model[Key]> : Model[Key] extends ((...args: any[]) => any) ? Model[Key] : Model[Key] extends object ? ReactiveModel<Model[Key]> : Model[Key] };
|
|
8
9
|
interface ModelContext<Props, Key = undefined> {
|
|
9
10
|
readonly scope: Scope;
|
|
10
11
|
readonly owner: Owner;
|
|
@@ -31,6 +32,18 @@ type CacheOptions<Props, Key, Model extends object> = {
|
|
|
31
32
|
readonly cache: ModelCache<Key, Props, Model>;
|
|
32
33
|
readonly key: Key;
|
|
33
34
|
};
|
|
35
|
+
type ComponentModel<Model extends object> = Model & DisposableOwner & {
|
|
36
|
+
readonly [componentModelBrand]: true;
|
|
37
|
+
};
|
|
38
|
+
type ComponentPublicProps<Props, Model extends object> = Omit<Props, "model"> & {
|
|
39
|
+
readonly model?: ComponentModel<Model>;
|
|
40
|
+
};
|
|
41
|
+
interface ComponentCreate<Props, Model extends object> {
|
|
42
|
+
(props: Props): ComponentModel<Model>;
|
|
43
|
+
}
|
|
44
|
+
type VirentiaComponent<Props, Model extends object> = FC<ComponentPublicProps<Props, Model>> & {
|
|
45
|
+
readonly create: ComponentCreate<Props, Model>;
|
|
46
|
+
};
|
|
34
47
|
type ComponentConfig<Props, Model extends object> = {
|
|
35
48
|
readonly model: ModelFactory<Props, Model>;
|
|
36
49
|
readonly view: ComponentType<Props & {
|
|
@@ -47,8 +60,8 @@ type CachedComponentConfig<Props, Key, Model extends object> = {
|
|
|
47
60
|
};
|
|
48
61
|
//#endregion
|
|
49
62
|
//#region lib/component.d.ts
|
|
50
|
-
declare function component<Props, Model extends object>(config: ComponentConfig<Props, Model>):
|
|
51
|
-
declare function component<Props, Key, Model extends object>(config: CachedComponentConfig<Props, Key, Model>):
|
|
63
|
+
declare function component<Props, Model extends object>(config: ComponentConfig<Props, Model>): VirentiaComponent<Props, Model>;
|
|
64
|
+
declare function component<Props, Key, Model extends object>(config: CachedComponentConfig<Props, Key, Model>): VirentiaComponent<Props, Model>;
|
|
52
65
|
//#endregion
|
|
53
66
|
//#region lib/model-cache.d.ts
|
|
54
67
|
declare function createModelCache<Key, Props, Model extends object>(): ModelCache<Key, Props, Model>;
|
|
@@ -73,4 +86,4 @@ declare function useUnit<Params, Done, Fail>(unit: Effect<Params, Done, Fail>):
|
|
|
73
86
|
declare function useUnit<const Shape extends readonly UnitLike[]>(shape: Shape): UnitShape<Shape>;
|
|
74
87
|
declare function useUnit<const Shape extends Record<string, UnitLike>>(shape: Shape): UnitShape<Shape>;
|
|
75
88
|
//#endregion
|
|
76
|
-
export { type ModelCache, type ModelContext, type ModelFactory, type ModelInstance, type ReactiveModel, ScopeProvider, type UnitLike, type UnitValue, component, createModelCache, useModel, useProvidedScope, useUnit };
|
|
89
|
+
export { type ComponentCreate, type ComponentModel, type ComponentPublicProps, type ModelCache, type ModelContext, type ModelFactory, type ModelInstance, type ReactiveModel, ScopeProvider, type UnitLike, type UnitValue, type VirentiaComponent, component, createModelCache, useModel, useProvidedScope, useUnit };
|
package/dist/index.d.mts
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
|
+
import { DisposableOwner, Effect, EffectCallArgs, EventCallable, EventPayload, Owner, Scope, Store, StoreWritable } from "@virentia/core";
|
|
1
2
|
import { ComponentType, FC, ReactNode } from "react";
|
|
2
|
-
import { Effect, EffectCallArgs, EventCallable, EventPayload, Owner, Scope, Store, StoreWritable } from "@virentia/core";
|
|
3
3
|
|
|
4
4
|
//#region lib/types.d.ts
|
|
5
5
|
type UnitLike = Store<any> | StoreWritable<any> | EventCallable<any> | Effect<any, any, any>;
|
|
6
|
+
declare const componentModelBrand: unique symbol;
|
|
6
7
|
type UnitValue<Unit> = Unit extends Store<infer State> | StoreWritable<infer State> ? State : Unit extends EventCallable<infer Payload> ? (...payload: EventPayload<Payload>) => Promise<void> : Unit extends Effect<infer Params, infer Done, any> ? (...args: EffectCallArgs<Params>) => Promise<Done> : never;
|
|
7
|
-
type ReactiveModel<Model> = { readonly [Key in keyof Model as Key extends "dispose" ? never : Key]: Model[Key] extends UnitLike ? UnitValue<Model[Key]> : Model[Key] extends ((...args: any[]) => any) ? Model[Key] : Model[Key] extends object ? ReactiveModel<Model[Key]> : Model[Key] };
|
|
8
|
+
type ReactiveModel<Model> = { readonly [Key in keyof Model as Key extends "dispose" ? never : Key]: Model[Key] extends ComponentModel<infer ChildModel> ? ComponentModel<ChildModel> : Model[Key] extends UnitLike ? UnitValue<Model[Key]> : Model[Key] extends ((...args: any[]) => any) ? Model[Key] : Model[Key] extends object ? ReactiveModel<Model[Key]> : Model[Key] };
|
|
8
9
|
interface ModelContext<Props, Key = undefined> {
|
|
9
10
|
readonly scope: Scope;
|
|
10
11
|
readonly owner: Owner;
|
|
@@ -31,6 +32,18 @@ type CacheOptions<Props, Key, Model extends object> = {
|
|
|
31
32
|
readonly cache: ModelCache<Key, Props, Model>;
|
|
32
33
|
readonly key: Key;
|
|
33
34
|
};
|
|
35
|
+
type ComponentModel<Model extends object> = Model & DisposableOwner & {
|
|
36
|
+
readonly [componentModelBrand]: true;
|
|
37
|
+
};
|
|
38
|
+
type ComponentPublicProps<Props, Model extends object> = Omit<Props, "model"> & {
|
|
39
|
+
readonly model?: ComponentModel<Model>;
|
|
40
|
+
};
|
|
41
|
+
interface ComponentCreate<Props, Model extends object> {
|
|
42
|
+
(props: Props): ComponentModel<Model>;
|
|
43
|
+
}
|
|
44
|
+
type VirentiaComponent<Props, Model extends object> = FC<ComponentPublicProps<Props, Model>> & {
|
|
45
|
+
readonly create: ComponentCreate<Props, Model>;
|
|
46
|
+
};
|
|
34
47
|
type ComponentConfig<Props, Model extends object> = {
|
|
35
48
|
readonly model: ModelFactory<Props, Model>;
|
|
36
49
|
readonly view: ComponentType<Props & {
|
|
@@ -47,8 +60,8 @@ type CachedComponentConfig<Props, Key, Model extends object> = {
|
|
|
47
60
|
};
|
|
48
61
|
//#endregion
|
|
49
62
|
//#region lib/component.d.ts
|
|
50
|
-
declare function component<Props, Model extends object>(config: ComponentConfig<Props, Model>):
|
|
51
|
-
declare function component<Props, Key, Model extends object>(config: CachedComponentConfig<Props, Key, Model>):
|
|
63
|
+
declare function component<Props, Model extends object>(config: ComponentConfig<Props, Model>): VirentiaComponent<Props, Model>;
|
|
64
|
+
declare function component<Props, Key, Model extends object>(config: CachedComponentConfig<Props, Key, Model>): VirentiaComponent<Props, Model>;
|
|
52
65
|
//#endregion
|
|
53
66
|
//#region lib/model-cache.d.ts
|
|
54
67
|
declare function createModelCache<Key, Props, Model extends object>(): ModelCache<Key, Props, Model>;
|
|
@@ -73,4 +86,4 @@ declare function useUnit<Params, Done, Fail>(unit: Effect<Params, Done, Fail>):
|
|
|
73
86
|
declare function useUnit<const Shape extends readonly UnitLike[]>(shape: Shape): UnitShape<Shape>;
|
|
74
87
|
declare function useUnit<const Shape extends Record<string, UnitLike>>(shape: Shape): UnitShape<Shape>;
|
|
75
88
|
//#endregion
|
|
76
|
-
export { type ModelCache, type ModelContext, type ModelFactory, type ModelInstance, type ReactiveModel, ScopeProvider, type UnitLike, type UnitValue, component, createModelCache, useModel, useProvidedScope, useUnit };
|
|
89
|
+
export { type ComponentCreate, type ComponentModel, type ComponentPublicProps, type ModelCache, type ModelContext, type ModelFactory, type ModelInstance, type ReactiveModel, ScopeProvider, type UnitLike, type UnitValue, type VirentiaComponent, component, createModelCache, useModel, useProvidedScope, useUnit };
|
package/dist/index.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
+
import { event, getCurrentScope, owner, run, scoped, store } from "@virentia/core";
|
|
1
2
|
import { createContext, createElement, useCallback, useContext, useEffect, useLayoutEffect, useMemo, useRef, useSyncExternalStore } from "react";
|
|
2
|
-
import { event, owner, run, scoped, store } from "@virentia/core";
|
|
3
3
|
//#region lib/model-cache.ts
|
|
4
4
|
function createModelCache() {
|
|
5
5
|
const byScope = /* @__PURE__ */ new WeakMap();
|
|
@@ -90,6 +90,9 @@ function useProvidedScope() {
|
|
|
90
90
|
if (!scope) throw new Error("[useProvidedScope] Scope is not provided. Wrap your tree with ScopeProvider.");
|
|
91
91
|
return scope;
|
|
92
92
|
}
|
|
93
|
+
function useOptionalProvidedScope() {
|
|
94
|
+
return useContext(ScopeContext);
|
|
95
|
+
}
|
|
93
96
|
//#endregion
|
|
94
97
|
//#region lib/utils.ts
|
|
95
98
|
const useIsomorphicLayoutEffect = typeof window === "undefined" ? useEffect : useLayoutEffect;
|
|
@@ -176,7 +179,8 @@ function useStoreUnit(unit, scope) {
|
|
|
176
179
|
function useModel(modelOrFactory, props, options) {
|
|
177
180
|
const scope = useProvidedScope();
|
|
178
181
|
if (typeof modelOrFactory !== "function") return useReactiveModel(modelOrFactory, scope);
|
|
179
|
-
|
|
182
|
+
const instance = useModelInstance(modelOrFactory, props, scope, options);
|
|
183
|
+
return useReactiveModel(instance.model, instance.scope);
|
|
180
184
|
}
|
|
181
185
|
function useModelInstance(factory, props, scope, options) {
|
|
182
186
|
const cache = options?.cache;
|
|
@@ -190,6 +194,11 @@ function useModelInstance(factory, props, scope, options) {
|
|
|
190
194
|
key,
|
|
191
195
|
scope
|
|
192
196
|
]);
|
|
197
|
+
useModelInstanceLifecycle(instance, props, { disposeOnUnmount: !cached });
|
|
198
|
+
return instance;
|
|
199
|
+
}
|
|
200
|
+
function useModelInstanceLifecycle(instance, props, options) {
|
|
201
|
+
const scope = instance.scope;
|
|
193
202
|
useIsomorphicLayoutEffect(() => {
|
|
194
203
|
writeStore(instance.props, props, scope);
|
|
195
204
|
}, [
|
|
@@ -207,14 +216,13 @@ function useModelInstance(factory, props, scope, options) {
|
|
|
207
216
|
instance.mounts.value = Math.max(0, instance.mounts.value - 1);
|
|
208
217
|
instance.unmounted();
|
|
209
218
|
});
|
|
210
|
-
if (
|
|
219
|
+
if (options.disposeOnUnmount) instance.dispose();
|
|
211
220
|
};
|
|
212
221
|
}, [
|
|
213
|
-
cached,
|
|
214
222
|
instance,
|
|
223
|
+
options.disposeOnUnmount,
|
|
215
224
|
scope
|
|
216
225
|
]);
|
|
217
|
-
return instance;
|
|
218
226
|
}
|
|
219
227
|
function createModelInstance(factory, props, scope, key) {
|
|
220
228
|
return owner((dispose, modelOwner) => {
|
|
@@ -235,34 +243,83 @@ function createModelInstance(factory, props, scope, key) {
|
|
|
235
243
|
};
|
|
236
244
|
});
|
|
237
245
|
}
|
|
246
|
+
function exposeModelInstance(instance) {
|
|
247
|
+
const model = instance.model;
|
|
248
|
+
Object.defineProperty(model, modelInstanceSymbol, {
|
|
249
|
+
configurable: true,
|
|
250
|
+
value: instance
|
|
251
|
+
});
|
|
252
|
+
defineHidden(model, "dispose", () => instance.dispose());
|
|
253
|
+
defineHidden(model, disposeSymbol, () => instance.dispose());
|
|
254
|
+
return model;
|
|
255
|
+
}
|
|
256
|
+
function readExposedModelInstance(model) {
|
|
257
|
+
return model[modelInstanceSymbol] ?? null;
|
|
258
|
+
}
|
|
238
259
|
function useReactiveModel(model, scope) {
|
|
239
260
|
const result = {};
|
|
240
261
|
for (const key of Reflect.ownKeys(model)) {
|
|
241
|
-
|
|
262
|
+
const descriptor = Reflect.getOwnPropertyDescriptor(model, key);
|
|
263
|
+
if (key === "dispose" || key === disposeSymbol || descriptor && !descriptor.enumerable) continue;
|
|
242
264
|
result[key] = useModelValue(Reflect.get(model, key), scope);
|
|
243
265
|
}
|
|
244
266
|
return result;
|
|
245
267
|
}
|
|
246
268
|
const disposeSymbol = typeof Symbol.dispose === "symbol" ? Symbol.dispose : Symbol.for("Symbol.dispose");
|
|
269
|
+
const modelInstanceSymbol = Symbol("virentia.react.modelInstance");
|
|
270
|
+
function defineHidden(target, key, value) {
|
|
271
|
+
if (key in target) return;
|
|
272
|
+
Object.defineProperty(target, key, {
|
|
273
|
+
configurable: true,
|
|
274
|
+
value
|
|
275
|
+
});
|
|
276
|
+
}
|
|
247
277
|
function useModelValue(value, scope) {
|
|
248
278
|
if (isUnitLike(value)) return useUnitWithScope(value, scope);
|
|
279
|
+
if (isComponentModel(value)) return value;
|
|
249
280
|
if (isPlainObject(value)) return useReactiveModel(value, scope);
|
|
250
281
|
return value;
|
|
251
282
|
}
|
|
283
|
+
function isComponentModel(value) {
|
|
284
|
+
return Boolean(value && typeof value === "object" && value[modelInstanceSymbol]);
|
|
285
|
+
}
|
|
252
286
|
//#endregion
|
|
253
287
|
//#region lib/component.ts
|
|
254
288
|
function component(config) {
|
|
255
289
|
const VirentiaComponent = (props) => {
|
|
256
|
-
const model
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
290
|
+
const { model: controlledModel, ...modelProps } = props;
|
|
291
|
+
const providedScope = useOptionalProvidedScope();
|
|
292
|
+
const controlledInstance = controlledModel ? readExposedModelInstance(controlledModel) : null;
|
|
293
|
+
const key = "cache" in config ? config.key(modelProps) : void 0;
|
|
294
|
+
const instance = useMemo(() => {
|
|
295
|
+
if (controlledModel) {
|
|
296
|
+
if (!controlledInstance) throw new Error("[component] The model prop must be created with component.create().");
|
|
297
|
+
return controlledInstance;
|
|
298
|
+
}
|
|
299
|
+
if (!providedScope) throw new Error("[useProvidedScope] Scope is not provided. Wrap your tree with ScopeProvider.");
|
|
300
|
+
if ("cache" in config) return getOrCreateCachedInstance(config.cache, providedScope, key, () => createModelInstance(config.model, modelProps, providedScope, key));
|
|
301
|
+
return createModelInstance(config.model, modelProps, providedScope, void 0);
|
|
302
|
+
}, [
|
|
303
|
+
controlledInstance,
|
|
304
|
+
controlledModel,
|
|
305
|
+
key,
|
|
306
|
+
providedScope
|
|
307
|
+
]);
|
|
308
|
+
const cached = !controlledModel && "cache" in config;
|
|
309
|
+
const model = useReactiveModel(instance.model, instance.scope);
|
|
310
|
+
useModelInstanceLifecycle(instance, modelProps, { disposeOnUnmount: !controlledModel && !cached });
|
|
260
311
|
return createElement(config.view, {
|
|
261
|
-
...
|
|
312
|
+
...modelProps,
|
|
262
313
|
model
|
|
263
314
|
});
|
|
264
315
|
};
|
|
265
316
|
VirentiaComponent.displayName = getComponentName(config.view);
|
|
317
|
+
VirentiaComponent.create = ((props) => {
|
|
318
|
+
const externalScope = getCurrentScope();
|
|
319
|
+
if (!externalScope) throw new Error("[component.create] Parent component context is required. Call .create() while creating a parent component model.");
|
|
320
|
+
const key = "cache" in config ? config.key(props) : void 0;
|
|
321
|
+
return exposeModelInstance(createModelInstance(config.model, props, externalScope, key));
|
|
322
|
+
});
|
|
266
323
|
return VirentiaComponent;
|
|
267
324
|
}
|
|
268
325
|
//#endregion
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@virentia/react",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "",
|
|
5
5
|
"keywords": [],
|
|
6
6
|
"homepage": "https://movpushmov.dev/virentia/react/",
|
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
"access": "public"
|
|
33
33
|
},
|
|
34
34
|
"dependencies": {
|
|
35
|
-
"@virentia/core": "0.
|
|
35
|
+
"@virentia/core": "0.2.0"
|
|
36
36
|
},
|
|
37
37
|
"devDependencies": {
|
|
38
38
|
"@testing-library/react": "^16.3.0",
|