mobx-mantle 0.3.7 → 0.6.1

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/dist/index.cjs CHANGED
@@ -1,65 +1,31 @@
1
- "use strict";
2
- var __create = Object.create;
3
- var __defProp = Object.defineProperty;
4
- var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
- var __getOwnPropNames = Object.getOwnPropertyNames;
6
- var __getProtoOf = Object.getPrototypeOf;
7
- var __hasOwnProp = Object.prototype.hasOwnProperty;
8
- var __export = (target, all) => {
9
- for (var name in all)
10
- __defProp(target, name, { get: all[name], enumerable: true });
11
- };
12
- var __copyProps = (to, from, except, desc) => {
13
- if (from && typeof from === "object" || typeof from === "function") {
14
- for (let key of __getOwnPropNames(from))
15
- if (!__hasOwnProp.call(to, key) && key !== except)
16
- __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
- }
18
- return to;
19
- };
20
- var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
- // If the importer is in node compatibility mode or this is not an ESM
22
- // file that has been converted to a CommonJS file using a Babel-
23
- // compatible transform (i.e. "__esModule" has not been set), then set
24
- // "default" to the CommonJS "module.exports" for node compatibility.
25
- isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
- mod
27
- ));
28
- var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
-
30
- // src/index.ts
31
- var index_exports = {};
32
- __export(index_exports, {
33
- Behavior: () => Behavior,
34
- View: () => View,
35
- ViewModel: () => View,
36
- action: () => action3,
37
- computed: () => computed3,
38
- configure: () => configure,
39
- createBehavior: () => createBehavior,
40
- createView: () => createView,
41
- observable: () => observable3
42
- });
43
- module.exports = __toCommonJS(index_exports);
1
+ "use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { newObj[key] = obj[key]; } } } newObj.default = obj; return newObj; } } function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } } function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; }
44
2
 
45
- // src/mantle.tsx
46
- var import_react = require("react");
47
- var import_mobx2 = require("mobx");
48
- var import_mobx_react_lite = require("mobx-react-lite");
3
+ var _chunkWNF647U6cjs = require('./chunk-WNF647U6.cjs');
49
4
 
50
- // src/behavior.ts
51
- var import_mobx = require("mobx");
5
+ // src/services.tsx
6
+ var _react = require('react'); var _react2 = _interopRequireDefault(_react);
52
7
 
53
8
  // src/config.ts
9
+ var _mobx = require('mobx'); var mobx = _interopRequireWildcard(_mobx);
54
10
  var globalConfig = {
55
- autoObservable: true
11
+ autoObservable: true,
12
+ cacheAnnotations: true,
13
+ manageMobxActions: false
56
14
  };
15
+ var actionPolicyApplied = false;
16
+ function applyMobxActionPolicy() {
17
+ if (actionPolicyApplied) return;
18
+ actionPolicyApplied = true;
19
+ if (globalConfig.manageMobxActions === true) {
20
+ _mobx.configure.call(void 0, { enforceActions: "never" });
21
+ }
22
+ }
57
23
  function reportError(error, context) {
58
24
  if (globalConfig.onError) {
59
25
  globalConfig.onError(error, context);
60
26
  } else {
61
27
  console.error(
62
- `[mobx-mantle] Error in ${context.isBehavior ? "behavior" : "view"} ${context.name}.${context.phase}():`,
28
+ `[mobx-mantle] Error in ${context.isBehavior ? "behavior" : "component"} ${context.name}.${context.phase}():`,
63
29
  error
64
30
  );
65
31
  }
@@ -68,18 +34,349 @@ function configure(config) {
68
34
  Object.assign(globalConfig, config);
69
35
  }
70
36
 
37
+ // src/services.tsx
38
+ var _jsxruntime = require('react/jsx-runtime');
39
+ function createServiceToken(description) {
40
+ return Symbol(description);
41
+ }
42
+ var ServiceContext = _react2.default.createContext(void 0);
43
+ function ServiceProvider({ resolve, children }) {
44
+ return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, ServiceContext.Provider, { value: resolve, children });
45
+ }
46
+ var constructingScope;
47
+ var scopes = /* @__PURE__ */ new WeakMap();
48
+ function runWithScope(scope, construct) {
49
+ const previous = constructingScope;
50
+ constructingScope = scope;
51
+ try {
52
+ return construct();
53
+ } finally {
54
+ constructingScope = previous;
55
+ }
56
+ }
57
+ function withInjectionScope(resolve, construct) {
58
+ return runWithScope({ resolve: _nullishCoalesce(resolve, () => ( globalConfig.resolveService)), values: /* @__PURE__ */ new WeakSet() }, construct);
59
+ }
60
+ function captureServiceScope(owner) {
61
+ scopes.set(owner, _nullishCoalesce(constructingScope, () => ( { resolve: globalConfig.resolveService, values: /* @__PURE__ */ new WeakSet() })));
62
+ }
63
+ function resolveService(owner, token) {
64
+ return resolveInScope(scopes.get(owner), token);
65
+ }
66
+ function inject(token) {
67
+ if (!constructingScope) {
68
+ throw new Error("[mobx-mantle] inject() requires an active construction scope. Use this.inject() in later methods, or withInjectionScope() outside React.");
69
+ }
70
+ return resolveInScope(constructingScope, token);
71
+ }
72
+ function resolveInScope(scope, token) {
73
+ if (!_optionalChain([scope, 'optionalAccess', _2 => _2.resolve])) throw new Error("[mobx-mantle] Service injection requires configure({ resolveService }), a ServiceProvider, or withInjectionScope() during construction.");
74
+ const value = scope.resolve(token);
75
+ if ((typeof value !== "object" || value === null) && typeof value !== "function") {
76
+ throw new Error("[mobx-mantle] Services must be objects or functions; wrap primitive configuration in an object.");
77
+ }
78
+ scope.values.add(value);
79
+ return value;
80
+ }
81
+ function isServiceValue(owner, value) {
82
+ return _nullishCoalesce(_optionalChain([scopes, 'access', _3 => _3.get, 'call', _4 => _4(owner), 'optionalAccess', _5 => _5.values, 'access', _6 => _6.has, 'call', _7 => _7(value)]), () => ( false));
83
+ }
84
+ function withOwnerServiceScope(owner, construct) {
85
+ const scope = scopes.get(owner);
86
+ if (!scope) throw new Error("[mobx-mantle] Missing owner service scope.");
87
+ return runWithScope(scope, construct);
88
+ }
89
+
90
+ // src/model-scope.tsx
91
+
92
+
93
+ var ModelContext = _react2.default.createContext(void 0);
94
+ function ModelProvider({ models, children }) {
95
+ const [snapshot] = _react2.default.useState(() => new Map(Array.from(models, ([type, replacement]) => [type, { ...replacement }])));
96
+ return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, ModelContext.Provider, { value: snapshot, children });
97
+ }
98
+ var identities = /* @__PURE__ */ new WeakMap();
99
+ function setModelIdentity(model, type) {
100
+ identities.set(model, type);
101
+ }
102
+ function isModel(model, type) {
103
+ const logical = identities.get(model);
104
+ return model instanceof type || logical === type || !!logical && logical.prototype instanceof type;
105
+ }
106
+
107
+ // src/mantle.tsx
108
+
109
+
110
+
111
+ // src/observer.ts
112
+
113
+
114
+ var useIsomorphicLayoutEffect = typeof window !== "undefined" ? _react.useLayoutEffect : _react.useEffect;
115
+ var uncommittedReactionRegistry = typeof FinalizationRegistry !== "undefined" ? new FinalizationRegistry((adm) => {
116
+ _optionalChain([adm, 'access', _8 => _8.reaction, 'optionalAccess', _9 => _9.dispose, 'call', _10 => _10()]);
117
+ adm.reaction = null;
118
+ }) : void 0;
119
+ if (process.env.NODE_ENV !== "production" && !uncommittedReactionRegistry) {
120
+ console.warn(
121
+ "[mobx-mantle] FinalizationRegistry is not available in this environment. Reactions created for renders that React discards cannot be cleaned up, which can leak under Suspense and concurrent rendering."
122
+ );
123
+ }
124
+ var isServer = typeof window === "undefined";
125
+ function createReaction(adm) {
126
+ adm.reaction = new (0, _mobx.Reaction)(`mantle-observer(${adm.name})`, () => {
127
+ adm.stateVersion = /* @__PURE__ */ Symbol();
128
+ _optionalChain([adm, 'access', _11 => _11.onStoreChange, 'optionalCall', _12 => _12()]);
129
+ });
130
+ if (adm.holder) {
131
+ adm.holder.current = adm.reaction;
132
+ }
133
+ }
134
+ function useMantleObserver(render, name, holder) {
135
+ if (isServer) {
136
+ return render();
137
+ }
138
+ const admRef = _react.useRef.call(void 0, null);
139
+ if (!admRef.current) {
140
+ const adm2 = {
141
+ reaction: null,
142
+ onStoreChange: null,
143
+ stateVersion: /* @__PURE__ */ Symbol(),
144
+ name,
145
+ holder,
146
+ subscribe(onStoreChange) {
147
+ _optionalChain([uncommittedReactionRegistry, 'optionalAccess', _13 => _13.unregister, 'call', _14 => _14(adm2)]);
148
+ adm2.onStoreChange = onStoreChange;
149
+ if (!adm2.reaction) {
150
+ createReaction(adm2);
151
+ adm2.stateVersion = /* @__PURE__ */ Symbol();
152
+ onStoreChange();
153
+ }
154
+ return () => {
155
+ adm2.onStoreChange = null;
156
+ _optionalChain([adm2, 'access', _15 => _15.reaction, 'optionalAccess', _16 => _16.dispose, 'call', _17 => _17()]);
157
+ adm2.reaction = null;
158
+ if (adm2.holder) {
159
+ adm2.holder.current = null;
160
+ }
161
+ };
162
+ },
163
+ getSnapshot() {
164
+ return adm2.stateVersion;
165
+ }
166
+ };
167
+ admRef.current = adm2;
168
+ }
169
+ const adm = admRef.current;
170
+ adm.holder = holder;
171
+ if (!adm.reaction) {
172
+ createReaction(adm);
173
+ _optionalChain([uncommittedReactionRegistry, 'optionalAccess', _18 => _18.register, 'call', _19 => _19(admRef, adm, adm)]);
174
+ }
175
+ _react.useSyncExternalStore.call(void 0, adm.subscribe, adm.getSnapshot, adm.getSnapshot);
176
+ let result;
177
+ let didThrow = false;
178
+ let thrown;
179
+ adm.reaction.track(() => {
180
+ try {
181
+ result = render();
182
+ } catch (e) {
183
+ didThrow = true;
184
+ thrown = e;
185
+ }
186
+ });
187
+ if (didThrow) {
188
+ throw thrown;
189
+ }
190
+ return result;
191
+ }
192
+ function observer(fc) {
193
+ const name = fc.displayName || fc.name || "observed";
194
+ const Observed = (props) => useMantleObserver(() => fc(props), name);
195
+ Observed.displayName = `observer(${name})`;
196
+ return _react.memo.call(void 0, Observed);
197
+ }
198
+
199
+ // src/decorators.ts
200
+
201
+ var ANNOTATIONS = /* @__PURE__ */ Symbol("mantle:annotations");
202
+ var legacy = /* @__PURE__ */ new WeakMap();
203
+ function annotation(value, kind) {
204
+ return function(_target, key, descriptor) {
205
+ let map;
206
+ let name;
207
+ if (typeof key === "object") {
208
+ if (key.static || key.private || key.kind !== kind) {
209
+ throw new Error(`[mobx-mantle] This decorator requires a public instance ${kind}. Use an ordinary field, not an auto-accessor, for @observable.`);
210
+ }
211
+ if (!key.metadata) throw new Error("[mobx-mantle] TC39 decorators require Symbol.metadata. Load its polyfill before decorated classes.");
212
+ if (!Object.prototype.hasOwnProperty.call(key.metadata, ANNOTATIONS)) key.metadata[ANNOTATIONS] = /* @__PURE__ */ Object.create(null);
213
+ map = key.metadata[ANNOTATIONS];
214
+ name = key.name;
215
+ } else {
216
+ if (typeof _target === "function" || kind === "method" && typeof _optionalChain([descriptor, 'optionalAccess', _20 => _20.value]) !== "function" || kind === "getter" && !_optionalChain([descriptor, 'optionalAccess', _21 => _21.get]) || kind === "field" && descriptor) {
217
+ throw new Error(`[mobx-mantle] This decorator requires a public instance ${kind}.`);
218
+ }
219
+ map = _nullishCoalesce(legacy.get(_target), () => ( /* @__PURE__ */ Object.create(null)));
220
+ legacy.set(_target, map);
221
+ name = key;
222
+ }
223
+ map[name] = value;
224
+ };
225
+ }
226
+ var observable2 = Object.assign(annotation(mobx.observable, "field"), {
227
+ ref: annotation(mobx.observable.ref, "field"),
228
+ shallow: annotation(mobx.observable.shallow, "field"),
229
+ deep: annotation(mobx.observable.deep, "field"),
230
+ struct: annotation(mobx.observable.struct, "field")
231
+ });
232
+ var actionMethod = annotation(mobx.action.bound, "method");
233
+ var actionField = annotation(mobx.action, "field");
234
+ var action2 = function(target, key, descriptor) {
235
+ const isField = typeof key === "object" ? key.kind === "field" : !descriptor;
236
+ return (isField ? actionField : actionMethod)(target, key, descriptor);
237
+ };
238
+ var computed2 = annotation(mobx.computed, "getter");
239
+ function getAnnotations(instance) {
240
+ const chain = [];
241
+ for (let proto = Object.getPrototypeOf(instance); proto && proto !== Object.prototype; proto = Object.getPrototypeOf(proto)) chain.unshift(proto);
242
+ const result = /* @__PURE__ */ Object.create(null);
243
+ for (const proto of chain) {
244
+ const constructor = proto.constructor;
245
+ const symbol = Symbol.metadata;
246
+ const metadata = symbol && Object.prototype.hasOwnProperty.call(constructor, symbol) ? constructor[symbol] : void 0;
247
+ const modern = metadata && Object.prototype.hasOwnProperty.call(metadata, ANNOTATIONS) ? metadata[ANNOTATIONS] : void 0;
248
+ for (const own of [legacy.get(proto), modern]) {
249
+ if (!own) continue;
250
+ for (const key of Reflect.ownKeys(own)) {
251
+ if (key in result && result[key] !== own[key]) throw new Error(`[mobx-mantle] Cannot change the inherited annotation of ${String(key)}.`);
252
+ result[key] = own[key];
253
+ }
254
+ }
255
+ }
256
+ for (const key of Reflect.ownKeys(result)) {
257
+ const own = Object.getOwnPropertyDescriptor(instance, key);
258
+ if (own && "value" in own && isServiceValue(instance, own.value)) throw new Error(`[mobx-mantle] Leave service field ${String(key)} undecorated; its resolver owns observability.`);
259
+ }
260
+ return Reflect.ownKeys(result).length ? result : void 0;
261
+ }
262
+
263
+ // src/behavior.ts
264
+
265
+
266
+ // src/internals.ts
267
+
268
+ function smartBind(fn, context) {
269
+ return function(...args) {
270
+ const globalState = _mobx._getGlobalState.call(void 0, );
271
+ const isInTrackingContext = globalState.trackingDerivation !== null;
272
+ if (isInTrackingContext) {
273
+ return fn.apply(context, args);
274
+ }
275
+ return _mobx.runInAction.call(void 0, () => fn.apply(context, args));
276
+ };
277
+ }
278
+ if (process.env.NODE_ENV !== "production") {
279
+ const globalState = _mobx._getGlobalState.call(void 0, );
280
+ if (!globalState || !("trackingDerivation" in globalState)) {
281
+ console.warn(
282
+ "[mobx-mantle] MobX internal state no longer exposes trackingDerivation. smartBind cannot detect tracking contexts and props reads cannot skip self-notification, so method calls may not batch correctly and prop changes may double-render. Check mobx version compatibility."
283
+ );
284
+ }
285
+ }
286
+ function toWatchExpression(source, fireImmediately, ownerName) {
287
+ if (typeof source === "function") {
288
+ return source;
289
+ }
290
+ if (process.env.NODE_ENV !== "production" && !fireImmediately) {
291
+ console.warn(
292
+ `[mobx-mantle] ${ownerName}.watch() received a plain value, which can never change \u2014 the callback will never fire. Pass a getter (() => expr) to watch a live source, or add { fireImmediately: true } if a single immediate call is intended.`
293
+ );
294
+ }
295
+ return () => source;
296
+ }
297
+ function registerReactive(host, create) {
298
+ if (host._mounted) {
299
+ return create();
300
+ }
301
+ const spec = { create, stopped: false, dispose: null };
302
+ host._reactiveSpecs.push(spec);
303
+ return () => {
304
+ spec.stopped = true;
305
+ _optionalChain([spec, 'access', _22 => _22.dispose, 'optionalCall', _23 => _23()]);
306
+ };
307
+ }
308
+ function activateSpecs(host) {
309
+ if (host._wasUnmounted) {
310
+ host._wasUnmounted = false;
311
+ for (const spec of host._reactiveSpecs) {
312
+ if (!spec.stopped) {
313
+ spec.dispose = spec.create();
314
+ }
315
+ }
316
+ } else if (!host._mounted) {
317
+ for (const spec of host._reactiveSpecs) {
318
+ if (!spec.stopped && spec.dispose === null) {
319
+ spec.dispose = spec.create();
320
+ }
321
+ }
322
+ }
323
+ host._mounted = true;
324
+ }
325
+ function collectProtoInfo(instance, stopProto, excludes, cache) {
326
+ const ctor = instance.constructor;
327
+ const useCache = globalConfig.cacheAnnotations !== false;
328
+ if (useCache) {
329
+ const cached = cache.get(ctor);
330
+ if (cached) return cached;
331
+ }
332
+ const info = { computedKeys: [], methodKeys: [] };
333
+ const seen = /* @__PURE__ */ new Set();
334
+ let proto = Object.getPrototypeOf(instance);
335
+ while (proto && proto !== stopProto && proto !== Object.prototype) {
336
+ const descriptors = Object.getOwnPropertyDescriptors(proto);
337
+ for (const [key, descriptor] of Object.entries(descriptors)) {
338
+ if (excludes.has(key) || seen.has(key)) continue;
339
+ seen.add(key);
340
+ if (descriptor.get) {
341
+ info.computedKeys.push(key);
342
+ } else if (typeof descriptor.value === "function") {
343
+ info.methodKeys.push(key);
344
+ }
345
+ }
346
+ proto = Object.getPrototypeOf(proto);
347
+ }
348
+ if (useCache) cache.set(ctor, info);
349
+ return info;
350
+ }
351
+
71
352
  // src/behavior.ts
72
- var BEHAVIOR_MARKER = /* @__PURE__ */ Symbol("behavior");
353
+ var BEHAVIOR_MARKER = /* @__PURE__ */ Symbol.for("mobx-mantle.behavior");
73
354
  var BEHAVIOR_EXCLUDES = /* @__PURE__ */ new Set([
74
355
  "onCreate",
75
356
  "onLayoutMount",
76
357
  "onMount",
77
358
  "onUnmount",
359
+ "addCleanup",
360
+ "_addCleanup",
78
361
  "watch",
362
+ "effect",
363
+ "sync",
79
364
  "constructor",
365
+ "inject",
80
366
  "_watchDisposers",
81
- "_disposeWatchers"
367
+ "_disposeWatchers",
368
+ "_reactiveSpecs",
369
+ "_behaviors",
370
+ "_pendingSyncs",
371
+ "_syncWriting",
372
+ "_mounted",
373
+ "_wasUnmounted"
82
374
  ]);
375
+ var SyncMarker = class {
376
+ constructor(source) {
377
+ this.source = source;
378
+ }
379
+ };
83
380
  function isRefLike(value) {
84
381
  if (value === null || typeof value !== "object") return false;
85
382
  if (Array.isArray(value)) return false;
@@ -89,16 +386,72 @@ var Behavior = class {
89
386
  constructor() {
90
387
  /** @internal */
91
388
  this._watchDisposers = [];
389
+ /** @internal — pre-mount watch/effect registrations, materialized at mount */
390
+ this._reactiveSpecs = [];
391
+ /** @internal — child behaviors declared as fields, collected at construction */
392
+ this._behaviors = [];
393
+ /** @internal — true after the parent Component first mounts */
394
+ this._mounted = false;
395
+ /** @internal — set when effects clean up so StrictMode replay can resurrect pre-mount watchers */
396
+ this._wasUnmounted = false;
397
+ /** @internal — sync() markers awaiting the post-onCreate field scan; null once construction completes */
398
+ this._pendingSyncs = [];
399
+ /** @internal — true while the hidden sync effect writes a mirrored field (dev write-guard) */
400
+ this._syncWriting = false;
401
+ captureServiceScope(this);
402
+ }
403
+ inject(token) {
404
+ return resolveService(this, token);
405
+ }
406
+ /**
407
+ * Register a cleanup function to run automatically on unmount.
408
+ * Returns a function that can be called for early cleanup.
409
+ *
410
+ * Cleanups are one-shot: they are not re-created during React StrictMode's
411
+ * same-instance effect replay. Call this from onMount (which is replayed),
412
+ * or use effect() for a replay-safe setup/teardown pair.
413
+ */
414
+ addCleanup(cleanup) {
415
+ if (process.env.NODE_ENV !== "production" && !this._mounted) {
416
+ console.warn(
417
+ `[mobx-mantle] ${this.constructor.name}.addCleanup() called before mount. Cleanups are one-shot: they run when mount effects clean up and are not re-created during React StrictMode's same-instance effect replay. Acquire resources in onMount(), or use effect() for a replay-safe setup/teardown pair.`
418
+ );
419
+ }
420
+ return this._addCleanup(cleanup);
421
+ }
422
+ /** @internal — addCleanup without the pre-mount dev warning (used by watch/effect) */
423
+ _addCleanup(cleanup) {
424
+ let active = true;
425
+ const dispose = () => {
426
+ if (!active) return;
427
+ active = false;
428
+ try {
429
+ _mobx.runInAction.call(void 0, cleanup);
430
+ } finally {
431
+ const idx = this._watchDisposers.indexOf(dispose);
432
+ if (idx !== -1) this._watchDisposers.splice(idx, 1);
433
+ }
434
+ };
435
+ this._watchDisposers.push(dispose);
436
+ return dispose;
92
437
  }
93
438
  /**
94
439
  * Watch a reactive expression and run a callback when it changes.
95
- * Automatically disposed on unmount.
96
- *
97
- * @param expr - Reactive expression (getter) to watch
440
+ * Automatically disposed on unmount and re-created during React StrictMode's
441
+ * same-instance effect replay.
442
+ *
443
+ * Safe to call from onCreate: registration is recorded and comes alive
444
+ * when the parent commits (just before first paint), so the watcher
445
+ * tracks observable fields correctly and renders that React discards
446
+ * never leak reactions.
447
+ *
448
+ * @param source - Reactive expression (getter) to watch, or a MaybeGetter
449
+ * argument passed through as-is. To watch a value that is itself a
450
+ * function, wrap it: `watch(() => this.callback, …)`
98
451
  * @param callback - Called when the expression result changes
99
452
  * @param options - Optional configuration (delay, fireImmediately)
100
453
  * @returns Dispose function for early teardown
101
- *
454
+ *
102
455
  * @example
103
456
  * ```tsx
104
457
  * onCreate(url: string) {
@@ -107,36 +460,119 @@ var Behavior = class {
107
460
  * }
108
461
  * ```
109
462
  */
110
- watch(expr, callback, options) {
111
- const dispose = (0, import_mobx.reaction)(
112
- expr,
113
- (value, prevValue) => {
114
- try {
115
- callback(value, prevValue);
116
- } catch (e) {
117
- reportError(e, { phase: "watch", name: this.constructor.name, isBehavior: true });
463
+ watch(source, callback, options) {
464
+ const expr = toWatchExpression(source, _optionalChain([options, 'optionalAccess', _24 => _24.fireImmediately]), this.constructor.name);
465
+ return registerReactive(this, () => {
466
+ const dispose = _mobx.reaction.call(void 0,
467
+ expr,
468
+ (value, prevValue) => {
469
+ try {
470
+ callback(value, prevValue);
471
+ } catch (e) {
472
+ reportError(e, { phase: "watch", name: this.constructor.name, isBehavior: true });
473
+ }
474
+ },
475
+ {
476
+ delay: _optionalChain([options, 'optionalAccess', _25 => _25.delay]),
477
+ fireImmediately: _optionalChain([options, 'optionalAccess', _26 => _26.fireImmediately])
118
478
  }
119
- },
120
- {
121
- delay: options?.delay,
122
- fireImmediately: options?.fireImmediately
479
+ );
480
+ return this._addCleanup(dispose);
481
+ });
482
+ }
483
+ /**
484
+ * Run a side effect that auto-tracks reactive dependencies.
485
+ * Re-runs whenever any accessed observable changes.
486
+ * Automatically disposed on unmount.
487
+ *
488
+ * Best for simple synchronization (DOM updates, logging). For complex
489
+ * side effects with explicit triggers, prefer `watch()`.
490
+ *
491
+ * @param fn - Effect function. May return a cleanup function.
492
+ * @param options - Optional configuration (delay)
493
+ * @returns Dispose function for early teardown
494
+ *
495
+ * @example
496
+ * ```tsx
497
+ * onCreate() {
498
+ * this.effect(() => {
499
+ * console.log('Current value:', this.value);
500
+ * });
501
+ * }
502
+ * ```
503
+ */
504
+ effect(fn, options) {
505
+ return registerReactive(this, () => {
506
+ let cleanup;
507
+ const dispose = _mobx.autorun.call(void 0,
508
+ () => {
509
+ _mobx.runInAction.call(void 0, () => _optionalChain([cleanup, 'optionalCall', _27 => _27()]));
510
+ cleanup = void 0;
511
+ try {
512
+ const result = fn();
513
+ if (typeof result === "function") {
514
+ cleanup = result;
515
+ }
516
+ } catch (e) {
517
+ reportError(e, { phase: "effect", name: this.constructor.name, isBehavior: true });
518
+ }
519
+ },
520
+ { delay: _optionalChain([options, 'optionalAccess', _28 => _28.delay]) }
521
+ );
522
+ return this._addCleanup(() => {
523
+ _mobx.runInAction.call(void 0, () => _optionalChain([cleanup, 'optionalCall', _29 => _29()]));
524
+ dispose();
525
+ });
526
+ });
527
+ }
528
+ /**
529
+ * Normalize a MaybeGetter argument into an ordinary field that stays
530
+ * current. Call once in onCreate (or a field initializer) and assign the
531
+ * result to a field; everywhere else, read the field like normal state:
532
+ *
533
+ * ```tsx
534
+ * url!: string;
535
+ *
536
+ * onCreate(url: MaybeGetter<string>) {
537
+ * this.url = this.sync(url); // the one convention line
538
+ * this.watch(() => this.url, (u) => this.load(u));
539
+ * }
540
+ * refresh() { this.load(this.url); } // plain read, always current
541
+ * ```
542
+ *
543
+ * - Plain value → the field just holds it (no effect registered).
544
+ * - Getter → a hidden effect keeps the field updated whenever the getter's
545
+ * observable sources change (alive from mount, like watch/effect).
546
+ *
547
+ * The sync is one-way (argument → field). Don't write to the field from
548
+ * outside: the next source change overwrites it (dev mode warns).
549
+ */
550
+ sync(source) {
551
+ if (typeof source !== "function") {
552
+ return source;
553
+ }
554
+ if (this._pendingSyncs === null) {
555
+ if (process.env.NODE_ENV !== "production") {
556
+ console.warn(
557
+ `[mobx-mantle] ${this.constructor.name}.sync() called after construction. sync() only works in field initializers or onCreate; returning a one-time snapshot instead. Use watch()/effect() for post-mount reactions.`
558
+ );
123
559
  }
124
- );
125
- this._watchDisposers.push(dispose);
126
- return () => {
127
- dispose();
128
- const idx = this._watchDisposers.indexOf(dispose);
129
- if (idx !== -1) this._watchDisposers.splice(idx, 1);
130
- };
560
+ return _chunkWNF647U6cjs.toValue.call(void 0, source);
561
+ }
562
+ const marker = new SyncMarker(source);
563
+ this._pendingSyncs.push(marker);
564
+ return marker;
131
565
  }
132
566
  /** @internal */
133
567
  _disposeWatchers() {
134
- for (const dispose of this._watchDisposers) {
568
+ const disposers = this._watchDisposers.slice();
569
+ this._watchDisposers.length = 0;
570
+ for (const dispose of disposers) {
135
571
  dispose();
136
572
  }
137
- this._watchDisposers.length = 0;
138
573
  }
139
574
  };
575
+ var behaviorProtoInfo = /* @__PURE__ */ new WeakMap();
140
576
  function makeBehaviorObservable(instance) {
141
577
  const annotations = {};
142
578
  const ownKeys = /* @__PURE__ */ new Set([
@@ -147,42 +583,117 @@ function makeBehaviorObservable(instance) {
147
583
  if (BEHAVIOR_EXCLUDES.has(key)) continue;
148
584
  if (key in annotations) continue;
149
585
  const value = instance[key];
150
- if (typeof value === "function") continue;
586
+ if (typeof value === "function" || isServiceValue(instance, value)) continue;
587
+ if (isBehavior(value)) {
588
+ annotations[key] = _mobx.observable.ref;
589
+ continue;
590
+ }
151
591
  if (isRefLike(value)) {
152
- annotations[key] = import_mobx.observable.ref;
592
+ annotations[key] = _mobx.observable.ref;
153
593
  } else {
154
- annotations[key] = import_mobx.observable;
594
+ annotations[key] = _mobx.observable;
155
595
  }
156
596
  }
157
- let proto = Object.getPrototypeOf(instance);
158
- while (proto && proto !== Behavior.prototype && proto !== Object.prototype) {
159
- const descriptors = Object.getOwnPropertyDescriptors(proto);
160
- for (const [key, descriptor] of Object.entries(descriptors)) {
161
- if (BEHAVIOR_EXCLUDES.has(key)) continue;
162
- if (key in annotations) continue;
163
- if (descriptor.get) {
164
- annotations[key] = import_mobx.computed;
165
- } else if (typeof descriptor.value === "function") {
166
- annotations[key] = import_mobx.action.bound;
167
- }
597
+ const protoInfo = collectProtoInfo(instance, Behavior.prototype, BEHAVIOR_EXCLUDES, behaviorProtoInfo);
598
+ for (const key of protoInfo.computedKeys) {
599
+ if (key in annotations) continue;
600
+ annotations[key] = _mobx.computed;
601
+ }
602
+ _mobx.makeObservable.call(void 0, instance, annotations);
603
+ for (const key of protoInfo.methodKeys) {
604
+ if (key in annotations) continue;
605
+ const method = instance[key];
606
+ if (typeof method === "function") {
607
+ instance[key] = smartBind(method, instance);
168
608
  }
169
- proto = Object.getPrototypeOf(proto);
170
609
  }
171
- (0, import_mobx.makeObservable)(instance, annotations);
172
610
  }
173
611
  function createBehavior(Def, options) {
174
612
  var _a, _b, _c;
175
613
  const BehaviorClass = (_c = class extends (_b = Def, _a = BEHAVIOR_MARKER, _b) {
176
614
  constructor(...args) {
615
+ applyMobxActionPolicy();
177
616
  super(...args);
178
617
  if (typeof this.onCreate === "function") {
179
- this.onCreate(...args);
618
+ withOwnerServiceScope(this, () => _mobx.runInAction.call(void 0, () => this.onCreate(...args)));
180
619
  }
181
- const autoObservable = options?.autoObservable ?? globalConfig.autoObservable;
182
- if (autoObservable) {
620
+ const children = [];
621
+ for (const key of Object.keys(this)) {
622
+ if (key.startsWith("_")) continue;
623
+ const value = this[key];
624
+ if (isBehavior(value) && !isServiceValue(this, value)) {
625
+ children.push({ instance: value });
626
+ }
627
+ }
628
+ this._behaviors = children;
629
+ const syncedFields = [];
630
+ const pending = _nullishCoalesce(this._pendingSyncs, () => ( null));
631
+ if (Array.isArray(pending) && pending.length > 0) {
632
+ for (const key of Object.keys(this)) {
633
+ const value = this[key];
634
+ if (value instanceof SyncMarker) {
635
+ syncedFields.push({ key, source: value.source });
636
+ this[key] = value.source();
637
+ }
638
+ }
639
+ if (process.env.NODE_ENV !== "production" && syncedFields.length !== pending.length) {
640
+ console.warn(
641
+ `[mobx-mantle] ${Def.name}: ${pending.length - syncedFields.length} sync() result(s) were not assigned to a field. Assign sync() directly to a field (this.url = this.sync(url)) so Mantle can keep it updated.`
642
+ );
643
+ }
644
+ }
645
+ this._pendingSyncs = null;
646
+ const autoObservable = _nullishCoalesce(_optionalChain([options, 'optionalAccess', _30 => _30.autoObservable]), () => ( globalConfig.autoObservable));
647
+ const annotations = getAnnotations(this);
648
+ if (annotations) {
649
+ _mobx.makeObservable.call(void 0, this, annotations);
650
+ const info = collectProtoInfo(this, Behavior.prototype, BEHAVIOR_EXCLUDES, behaviorProtoInfo);
651
+ for (const key of info.methodKeys) if (!(key in annotations)) this[key] = smartBind(this[key], this);
652
+ } else if (autoObservable) {
183
653
  makeBehaviorObservable(this);
184
654
  } else {
185
- (0, import_mobx.makeObservable)(this);
655
+ _mobx.makeObservable.call(void 0, this);
656
+ }
657
+ if (syncedFields.length > 0) {
658
+ const self = this;
659
+ const specs = syncedFields.map(({ key, source }) => ({
660
+ create: () => {
661
+ const dispose = _mobx.autorun.call(void 0, () => {
662
+ try {
663
+ const next = source();
664
+ self._syncWriting = true;
665
+ try {
666
+ _mobx.runInAction.call(void 0, () => {
667
+ self[key] = next;
668
+ });
669
+ } finally {
670
+ self._syncWriting = false;
671
+ }
672
+ } catch (e) {
673
+ reportError(e, { phase: "sync", name: self.constructor.name, isBehavior: true });
674
+ }
675
+ });
676
+ return self._addCleanup(dispose);
677
+ },
678
+ stopped: false,
679
+ dispose: null
680
+ }));
681
+ self._reactiveSpecs.unshift(...specs);
682
+ if (process.env.NODE_ENV !== "production") {
683
+ for (const { key } of syncedFields) {
684
+ try {
685
+ _mobx.intercept.call(void 0, self, key, (change) => {
686
+ if (!self._syncWriting) {
687
+ console.warn(
688
+ `[mobx-mantle] ${Def.name}.${key} is a one-way synced field (this.sync()). Writing to it from outside will be overwritten by the next source change. Derive with a computed getter, or own the state in a plain field instead.`
689
+ );
690
+ }
691
+ return change;
692
+ });
693
+ } catch (e2) {
694
+ }
695
+ }
696
+ }
186
697
  }
187
698
  }
188
699
  }, _c[_a] = true, _c);
@@ -195,35 +706,57 @@ function createBehavior(Def, options) {
195
706
  }
196
707
  function isBehavior(value) {
197
708
  if (value === null || typeof value !== "object") return false;
198
- return value.constructor?.[BEHAVIOR_MARKER] === true;
709
+ return _optionalChain([value, 'access', _31 => _31.constructor, 'optionalAccess', _32 => _32[BEHAVIOR_MARKER]]) === true;
710
+ }
711
+ function childBehaviors(inst) {
712
+ return Array.isArray(inst._behaviors) ? inst._behaviors : [];
199
713
  }
200
- function layoutMountBehavior(behavior) {
714
+ function layoutMountBehavior(behavior, visited = /* @__PURE__ */ new WeakSet()) {
201
715
  const inst = behavior.instance;
716
+ if (visited.has(inst)) return;
717
+ visited.add(inst);
718
+ for (const child of childBehaviors(inst)) {
719
+ layoutMountBehavior(child, visited);
720
+ }
721
+ if (Array.isArray(inst._reactiveSpecs)) {
722
+ activateSpecs(inst);
723
+ } else {
724
+ inst._mounted = true;
725
+ }
202
726
  if ("onLayoutMount" in inst && typeof inst.onLayoutMount === "function") {
203
727
  try {
204
- behavior.layoutCleanup = inst.onLayoutMount() ?? void 0;
728
+ const result = _mobx.runInAction.call(void 0, () => inst.onLayoutMount());
729
+ behavior.layoutCleanup = typeof result === "function" ? result : void 0;
205
730
  } catch (e) {
206
731
  reportError(e, { phase: "onLayoutMount", name: inst.constructor.name, isBehavior: true });
207
732
  }
208
733
  }
209
734
  }
210
- function mountBehavior(behavior) {
735
+ function mountBehavior(behavior, visited = /* @__PURE__ */ new WeakSet()) {
211
736
  const inst = behavior.instance;
737
+ if (visited.has(inst)) return;
738
+ visited.add(inst);
739
+ for (const child of childBehaviors(inst)) {
740
+ mountBehavior(child, visited);
741
+ }
212
742
  if ("onMount" in inst && typeof inst.onMount === "function") {
213
743
  try {
214
- behavior.cleanup = inst.onMount() ?? void 0;
744
+ const result = _mobx.runInAction.call(void 0, () => inst.onMount());
745
+ behavior.cleanup = typeof result === "function" ? result : void 0;
215
746
  } catch (e) {
216
747
  reportError(e, { phase: "onMount", name: inst.constructor.name, isBehavior: true });
217
748
  }
218
749
  }
219
750
  }
220
- function unmountBehavior(behavior) {
221
- behavior.layoutCleanup?.();
222
- behavior.cleanup?.();
751
+ function unmountBehavior(behavior, visited = /* @__PURE__ */ new WeakSet()) {
223
752
  const inst = behavior.instance;
753
+ if (visited.has(inst)) return;
754
+ visited.add(inst);
755
+ _mobx.runInAction.call(void 0, () => _optionalChain([behavior, 'access', _33 => _33.layoutCleanup, 'optionalCall', _34 => _34()]));
756
+ _mobx.runInAction.call(void 0, () => _optionalChain([behavior, 'access', _35 => _35.cleanup, 'optionalCall', _36 => _36()]));
224
757
  if ("onUnmount" in inst && typeof inst.onUnmount === "function") {
225
758
  try {
226
- inst.onUnmount();
759
+ _mobx.runInAction.call(void 0, () => inst.onUnmount());
227
760
  } catch (e) {
228
761
  reportError(e, { phase: "onUnmount", name: inst.constructor.name, isBehavior: true });
229
762
  }
@@ -231,44 +764,63 @@ function unmountBehavior(behavior) {
231
764
  if (typeof inst._disposeWatchers === "function") {
232
765
  inst._disposeWatchers();
233
766
  }
767
+ inst._wasUnmounted = true;
768
+ const children = childBehaviors(inst);
769
+ for (let i = children.length - 1; i >= 0; i--) {
770
+ unmountBehavior(children[i], visited);
771
+ }
234
772
  }
235
-
236
- // src/decorators.ts
237
- var mobx = __toESM(require("mobx"), 1);
238
- var ANNOTATIONS = /* @__PURE__ */ Symbol("mantle:annotations");
239
- function setAnnotation(context, annotation) {
240
- var _a;
241
- (_a = context.metadata)[ANNOTATIONS] ?? (_a[ANNOTATIONS] = {});
242
- context.metadata[ANNOTATIONS][context.name] = annotation;
243
- }
244
- function observable3(_value, context) {
245
- setAnnotation(context, mobx.observable);
246
- }
247
- observable3.ref = function(_value, context) {
248
- setAnnotation(context, mobx.observable.ref);
249
- };
250
- observable3.shallow = function(_value, context) {
251
- setAnnotation(context, mobx.observable.shallow);
252
- };
253
- observable3.struct = function(_value, context) {
254
- setAnnotation(context, mobx.observable.struct);
255
- };
256
- observable3.deep = function(_value, context) {
257
- setAnnotation(context, mobx.observable.deep);
258
- };
259
- function action3(_value, context) {
260
- setAnnotation(context, mobx.action.bound);
261
- }
262
- function computed3(_value, context) {
263
- setAnnotation(context, mobx.computed);
264
- }
265
- function getAnnotations(instance) {
266
- const metadata = instance.constructor[Symbol.metadata];
267
- return metadata?.[ANNOTATIONS];
773
+ function warnUncollectedBehaviors(host, hostName, entries, visited = /* @__PURE__ */ new WeakSet()) {
774
+ if (visited.has(host)) return;
775
+ visited.add(host);
776
+ const collected = new Set(entries.map((e) => e.instance));
777
+ for (const key of Object.keys(host)) {
778
+ if (key.startsWith("_")) continue;
779
+ const value = host[key];
780
+ if (isBehavior(value) && !isServiceValue(host, value) && !collected.has(value)) {
781
+ console.warn(
782
+ `[mobx-mantle] ${hostName}.${key} holds a behavior that was assigned after construction, so its lifecycle (onMount, watchers, cleanup) will never run. Create behaviors as class fields so they are collected when the host is constructed.`
783
+ );
784
+ }
785
+ }
786
+ for (const entry of entries) {
787
+ warnUncollectedBehaviors(
788
+ entry.instance,
789
+ entry.instance.constructor.name,
790
+ childBehaviors(entry.instance),
791
+ visited
792
+ );
793
+ }
268
794
  }
269
795
 
270
796
  // src/mantle.tsx
271
- var viewRefs = /* @__PURE__ */ new WeakSet();
797
+
798
+ var componentRefs = /* @__PURE__ */ new WeakSet();
799
+ var ComponentParentContext = _react2.default.createContext(void 0);
800
+ var componentParents = /* @__PURE__ */ new WeakMap();
801
+ var activeComponentConstruction;
802
+ function constructComponent(ComponentClass, props, parentHandle) {
803
+ const previousScope = activeComponentConstruction;
804
+ const scope = {
805
+ expectedClass: ComponentClass,
806
+ parentHandle,
807
+ consumed: false
808
+ };
809
+ activeComponentConstruction = scope;
810
+ try {
811
+ return new ComponentClass(props);
812
+ } finally {
813
+ activeComponentConstruction = previousScope;
814
+ }
815
+ }
816
+ function captureConstructingComponentParent(instance) {
817
+ const scope = activeComponentConstruction;
818
+ if (!scope || scope.consumed || instance.constructor !== scope.expectedClass) {
819
+ return;
820
+ }
821
+ scope.consumed = true;
822
+ componentParents.set(instance, scope.parentHandle);
823
+ }
272
824
  function shallowEqual(a, b) {
273
825
  if (a === b) return true;
274
826
  if (!a || !b) return false;
@@ -280,37 +832,140 @@ function shallowEqual(a, b) {
280
832
  }
281
833
  return true;
282
834
  }
283
- var View = class {
284
- constructor() {
835
+ var PropsBox = class {
836
+ constructor(value, name) {
837
+ /**
838
+ * @internal The owning component's render reaction. Reads coming from it
839
+ * are not tracked: React already re-renders the component on prop change
840
+ * (via memo), so letting the render reaction observe the props atom would
841
+ * only schedule a redundant second render after every prop change.
842
+ * Computeds, watchers, and other components track normally.
843
+ */
844
+ this._renderReaction = null;
845
+ this._value = value;
846
+ this._atom = _mobx.createAtom.call(void 0, name);
847
+ }
848
+ get() {
849
+ const own = _optionalChain([this, 'access', _37 => _37._renderReaction, 'optionalAccess', _38 => _38.current]);
850
+ if (!own || _mobx._getGlobalState.call(void 0, ).trackingDerivation !== own) {
851
+ this._atom.reportObserved();
852
+ }
853
+ return this._value;
854
+ }
855
+ setSilently(value) {
856
+ this._value = value;
857
+ }
858
+ set(value) {
859
+ this._value = value;
860
+ this._atom.reportChanged();
861
+ }
862
+ };
863
+ var Component = class {
864
+ constructor(props) {
285
865
  /** @internal */
286
866
  this._behaviors = [];
287
867
  /** @internal */
288
868
  this._watchDisposers = [];
869
+ /** @internal — pre-mount watch/effect registrations, materialized at mount */
870
+ this._reactiveSpecs = [];
871
+ /** @internal — true after the first (layout) mount */
872
+ this._mounted = false;
873
+ /** @internal — set when effects clean up so StrictMode replay can resurrect watchers */
874
+ this._wasUnmounted = false;
875
+ this._propsBox = new PropsBox(props, `${this.constructor.name}.props`);
876
+ captureConstructingComponentParent(this);
877
+ captureServiceScope(this);
289
878
  }
290
879
  get props() {
291
880
  return this._propsBox.get();
292
881
  }
293
- /** @internal — called by createView to silently update props during render */
882
+ /** Resolve through the scope captured during construction; preserve service identity. */
883
+ inject(token) {
884
+ return resolveService(this, token);
885
+ }
886
+ /** Return the immediate ancestor in the live Mantle view tree. */
887
+ getParent() {
888
+ return _optionalChain([componentParents, 'access', _39 => _39.get, 'call', _40 => _40(this), 'optionalAccess', _41 => _41.current]);
889
+ }
890
+ /** Find the nearest parent with the supplied logical model identity. */
891
+ findParent(type) {
892
+ for (const parent of this.getParents()) {
893
+ if (isModel(parent, type)) return parent;
894
+ }
895
+ return void 0;
896
+ }
897
+ /** Return all Mantle parents, ordered from the immediate parent to the root. */
898
+ getParents() {
899
+ const parents = [];
900
+ let current = this.getParent();
901
+ while (current) {
902
+ parents.push(current);
903
+ current = current.getParent();
904
+ }
905
+ return parents;
906
+ }
907
+ /** Return the highest Mantle ancestor, or this instance when already root. */
908
+ getRoot() {
909
+ const parents = this.getParents();
910
+ return _nullishCoalesce(parents[parents.length - 1], () => ( this));
911
+ }
912
+ /** @internal — called by createComponent to silently update props during render */
294
913
  _syncProps(value) {
295
- this._propsBox.value_ = value;
914
+ this._propsBox.setSilently(value);
296
915
  }
297
916
  ref() {
298
917
  const r = { current: null };
299
- viewRefs.add(r);
918
+ componentRefs.add(r);
300
919
  return r;
301
920
  }
921
+ /**
922
+ * Register a cleanup function to run automatically on unmount.
923
+ * Returns a function that can be called for early cleanup.
924
+ *
925
+ * Cleanups are one-shot: they are not re-created during React StrictMode's
926
+ * same-instance effect replay. Call this from onMount (which is replayed),
927
+ * or use effect() for a replay-safe setup/teardown pair.
928
+ */
929
+ addCleanup(cleanup) {
930
+ if (process.env.NODE_ENV !== "production" && !this._mounted) {
931
+ console.warn(
932
+ `[mobx-mantle] ${this.constructor.name}.addCleanup() called before mount. Cleanups are one-shot: they run when mount effects clean up and are not re-created during React StrictMode's same-instance effect replay. Acquire resources in onMount(), or use effect() for a replay-safe setup/teardown pair.`
933
+ );
934
+ }
935
+ return this._addCleanup(cleanup);
936
+ }
937
+ /** @internal — addCleanup without the pre-mount dev warning (used by watch/effect) */
938
+ _addCleanup(cleanup) {
939
+ let active = true;
940
+ const dispose = () => {
941
+ if (!active) return;
942
+ active = false;
943
+ try {
944
+ _mobx.runInAction.call(void 0, cleanup);
945
+ } finally {
946
+ const idx = this._watchDisposers.indexOf(dispose);
947
+ if (idx !== -1) this._watchDisposers.splice(idx, 1);
948
+ }
949
+ };
950
+ this._watchDisposers.push(dispose);
951
+ return dispose;
952
+ }
302
953
  /**
303
954
  * Watch a reactive expression and run a callback when it changes.
304
- * Automatically disposed on unmount.
305
- *
306
- * @param expr - Reactive expression (getter) to watch
955
+ * Automatically disposed on unmount. Watchers declared before mount
956
+ * (field initializers, onCreate) are re-created during React StrictMode's
957
+ * same-instance effect replay.
958
+ *
959
+ * @param source - Reactive expression (getter) to watch, or a MaybeGetter
960
+ * argument passed through as-is. To watch a value that is itself a
961
+ * function, wrap it: `watch(() => this.callback, …)`
307
962
  * @param callback - Called when the expression result changes
308
963
  * @param options - Optional configuration (delay, fireImmediately)
309
964
  * @returns Dispose function for early teardown
310
965
  *
311
966
  * @example
312
967
  * ```tsx
313
- * onCreate() {
968
+ * onMount() {
314
969
  * this.watch(
315
970
  * () => this.query,
316
971
  * async (query) => {
@@ -323,41 +978,98 @@ var View = class {
323
978
  * }
324
979
  * ```
325
980
  */
326
- watch(expr, callback, options) {
327
- const dispose = (0, import_mobx2.reaction)(
328
- expr,
329
- (value, prevValue) => {
330
- try {
331
- callback(value, prevValue);
332
- } catch (e) {
333
- reportError(e, { phase: "watch", name: this.constructor.name, isBehavior: false });
981
+ watch(source, callback, options) {
982
+ const expr = toWatchExpression(source, _optionalChain([options, 'optionalAccess', _42 => _42.fireImmediately]), this.constructor.name);
983
+ return registerReactive(this, () => {
984
+ const dispose = _mobx.reaction.call(void 0,
985
+ expr,
986
+ (value, prevValue) => {
987
+ try {
988
+ callback(value, prevValue);
989
+ } catch (e) {
990
+ reportError(e, { phase: "watch", name: this.constructor.name, isBehavior: false });
991
+ }
992
+ },
993
+ {
994
+ delay: _optionalChain([options, 'optionalAccess', _43 => _43.delay]),
995
+ fireImmediately: _optionalChain([options, 'optionalAccess', _44 => _44.fireImmediately])
334
996
  }
335
- },
336
- {
337
- delay: options?.delay,
338
- fireImmediately: options?.fireImmediately
339
- }
340
- );
341
- this._watchDisposers.push(dispose);
342
- return () => {
343
- dispose();
344
- const idx = this._watchDisposers.indexOf(dispose);
345
- if (idx !== -1) this._watchDisposers.splice(idx, 1);
346
- };
997
+ );
998
+ return this._addCleanup(dispose);
999
+ });
1000
+ }
1001
+ /**
1002
+ * Run a side effect that auto-tracks reactive dependencies.
1003
+ * Re-runs whenever any accessed observable changes.
1004
+ * Automatically disposed on unmount. Effects declared before mount
1005
+ * (field initializers, onCreate) are re-created during React StrictMode's
1006
+ * same-instance effect replay.
1007
+ *
1008
+ * Best for simple synchronization (DOM updates, logging). For complex
1009
+ * side effects with explicit triggers, prefer `watch()`.
1010
+ *
1011
+ * @param fn - Effect function. May return a cleanup function.
1012
+ * @param options - Optional configuration (delay)
1013
+ * @returns Dispose function for early teardown
1014
+ *
1015
+ * @example
1016
+ * ```tsx
1017
+ * onMount() {
1018
+ * this.effect(() => {
1019
+ * document.title = `${this.items.length} items`;
1020
+ * });
1021
+ * }
1022
+ * ```
1023
+ *
1024
+ * @example With cleanup
1025
+ * ```tsx
1026
+ * onMount() {
1027
+ * this.effect(() => {
1028
+ * const handler = () => console.log(this.count);
1029
+ * window.addEventListener('click', handler);
1030
+ * return () => window.removeEventListener('click', handler);
1031
+ * });
1032
+ * }
1033
+ * ```
1034
+ */
1035
+ effect(fn, options) {
1036
+ return registerReactive(this, () => {
1037
+ let cleanup;
1038
+ const dispose = _mobx.autorun.call(void 0,
1039
+ () => {
1040
+ _mobx.runInAction.call(void 0, () => _optionalChain([cleanup, 'optionalCall', _45 => _45()]));
1041
+ cleanup = void 0;
1042
+ try {
1043
+ const result = fn();
1044
+ if (typeof result === "function") {
1045
+ cleanup = result;
1046
+ }
1047
+ } catch (e) {
1048
+ reportError(e, { phase: "effect", name: this.constructor.name, isBehavior: false });
1049
+ }
1050
+ },
1051
+ { delay: _optionalChain([options, 'optionalAccess', _46 => _46.delay]) }
1052
+ );
1053
+ return this._addCleanup(() => {
1054
+ _mobx.runInAction.call(void 0, () => _optionalChain([cleanup, 'optionalCall', _47 => _47()]));
1055
+ dispose();
1056
+ });
1057
+ });
347
1058
  }
348
1059
  /** @internal */
349
1060
  _disposeWatchers() {
350
- for (const dispose of this._watchDisposers) {
1061
+ const disposers = this._watchDisposers.slice();
1062
+ this._watchDisposers.length = 0;
1063
+ for (const dispose of disposers) {
351
1064
  dispose();
352
1065
  }
353
- this._watchDisposers.length = 0;
354
1066
  }
355
1067
  /** @internal - Scan own properties for behavior instances and register them */
356
1068
  _collectBehaviors() {
357
1069
  for (const key of Object.keys(this)) {
358
1070
  if (key.startsWith("_")) continue;
359
1071
  const value = this[key];
360
- if (isBehavior(value)) {
1072
+ if (isBehavior(value) && !isServiceValue(this, value)) {
361
1073
  this._behaviors.push({ instance: value });
362
1074
  }
363
1075
  }
@@ -383,6 +1095,10 @@ var View = class {
383
1095
  };
384
1096
  var BASE_EXCLUDES = /* @__PURE__ */ new Set([
385
1097
  "props",
1098
+ "getParent",
1099
+ "findParent",
1100
+ "getParents",
1101
+ "getRoot",
386
1102
  "_propsBox",
387
1103
  "forwardRef",
388
1104
  "onCreate",
@@ -392,8 +1108,12 @@ var BASE_EXCLUDES = /* @__PURE__ */ new Set([
392
1108
  "onUnmount",
393
1109
  "render",
394
1110
  "ref",
1111
+ "addCleanup",
1112
+ "_addCleanup",
395
1113
  "watch",
1114
+ "effect",
396
1115
  "constructor",
1116
+ "inject",
397
1117
  "_behaviors",
398
1118
  "_collectBehaviors",
399
1119
  "_layoutMountBehaviors",
@@ -401,12 +1121,16 @@ var BASE_EXCLUDES = /* @__PURE__ */ new Set([
401
1121
  "_unmountBehaviors",
402
1122
  "_syncProps",
403
1123
  "_watchDisposers",
404
- "_disposeWatchers"
1124
+ "_disposeWatchers",
1125
+ "_reactiveSpecs",
1126
+ "_mounted",
1127
+ "_wasUnmounted"
405
1128
  ]);
406
- function isViewRef(value) {
407
- return value !== null && typeof value === "object" && viewRefs.has(value);
1129
+ function isComponentRef(value) {
1130
+ return value !== null && typeof value === "object" && componentRefs.has(value);
408
1131
  }
409
- function makeViewObservable(instance, autoBind) {
1132
+ var componentProtoInfo = /* @__PURE__ */ new WeakMap();
1133
+ function makeComponentObservable(instance, autoBind) {
410
1134
  const annotations = {};
411
1135
  const ownKeys = /* @__PURE__ */ new Set([
412
1136
  ...Object.keys(instance),
@@ -416,71 +1140,97 @@ function makeViewObservable(instance, autoBind) {
416
1140
  if (BASE_EXCLUDES.has(key)) continue;
417
1141
  if (key in annotations) continue;
418
1142
  const value = instance[key];
419
- if (typeof value === "function") continue;
1143
+ if (typeof value === "function" || isServiceValue(instance, value)) continue;
420
1144
  if (isBehavior(value)) {
421
- annotations[key] = import_mobx2.observable.ref;
1145
+ annotations[key] = _mobx.observable.ref;
422
1146
  continue;
423
1147
  }
424
- if (isViewRef(value)) {
425
- annotations[key] = import_mobx2.observable.ref;
1148
+ if (isComponentRef(value)) {
1149
+ annotations[key] = _mobx.observable.ref;
426
1150
  } else {
427
- annotations[key] = import_mobx2.observable;
1151
+ annotations[key] = _mobx.observable;
428
1152
  }
429
1153
  }
430
- let proto = Object.getPrototypeOf(instance);
431
- while (proto && proto !== View.prototype) {
432
- const descriptors = Object.getOwnPropertyDescriptors(proto);
433
- for (const [key, descriptor] of Object.entries(descriptors)) {
434
- if (BASE_EXCLUDES.has(key)) continue;
1154
+ const protoInfo = collectProtoInfo(instance, Component.prototype, BASE_EXCLUDES, componentProtoInfo);
1155
+ for (const key of protoInfo.computedKeys) {
1156
+ if (key in annotations) continue;
1157
+ annotations[key] = _mobx.computed;
1158
+ }
1159
+ _mobx.makeObservable.call(void 0, instance, annotations);
1160
+ if (autoBind) {
1161
+ for (const key of protoInfo.methodKeys) {
435
1162
  if (key in annotations) continue;
436
- if (descriptor.get) {
437
- annotations[key] = import_mobx2.computed;
438
- } else if (typeof descriptor.value === "function") {
439
- annotations[key] = autoBind ? import_mobx2.action.bound : import_mobx2.action;
1163
+ const method = instance[key];
1164
+ if (typeof method === "function") {
1165
+ instance[key] = smartBind(method, instance);
440
1166
  }
441
1167
  }
442
- proto = Object.getPrototypeOf(proto);
443
1168
  }
444
- (0, import_mobx2.makeObservable)(instance, annotations);
445
1169
  }
446
- function createView(ViewClass, templateOrOptions) {
1170
+ var SubstituteComponent = class extends Component {
1171
+ };
1172
+ function installSubstituteState(instance, state) {
1173
+ const annotations = {};
1174
+ for (const key of Reflect.ownKeys(state)) {
1175
+ if (typeof key !== "string" || key.startsWith("_") || key in instance || BASE_EXCLUDES.has(key)) throw new Error("[mobx-mantle] Substitute state cannot replace framework member " + String(key));
1176
+ const descriptor = Object.getOwnPropertyDescriptor(state, key);
1177
+ if (!("value" in descriptor)) throw new Error("[mobx-mantle] Substitute state must contain data/callbacks, not getters.");
1178
+ Object.defineProperty(instance, key, { value: descriptor.value, enumerable: true, writable: true, configurable: true });
1179
+ annotations[key] = _mobx.observable.ref;
1180
+ }
1181
+ _mobx.makeObservable.call(void 0, instance, annotations);
1182
+ }
1183
+ function createComponent(ComponentClass, templateOrOptions) {
447
1184
  const template = typeof templateOrOptions === "function" ? templateOrOptions : void 0;
448
1185
  const options = typeof templateOrOptions === "object" ? templateOrOptions : {};
449
1186
  const { autoObservable = globalConfig.autoObservable } = options;
450
- const Component = (0, import_react.forwardRef)((props, ref) => {
451
- const vmRef = (0, import_react.useRef)(null);
452
- const classRef = (0, import_react.useRef)(ViewClass);
453
- const prevPropsRef = (0, import_react.useRef)(null);
454
- const propsNotifyingRef = (0, import_react.useRef)(false);
455
- if (vmRef.current && classRef.current !== ViewClass) {
456
- classRef.current = ViewClass;
1187
+ const ReactComponent = _react.forwardRef.call(void 0, (props, ref) => {
1188
+ const parentHandle = _react2.default.useContext(ComponentParentContext);
1189
+ const resolver = _react2.default.useContext(ServiceContext);
1190
+ const substitutions = _react2.default.useContext(ModelContext);
1191
+ const substitutedTemplate = _react.useRef.call(void 0, void 0);
1192
+ const vmRef = _react.useRef.call(void 0, null);
1193
+ const componentHandleRef = _react.useRef.call(void 0, null);
1194
+ const classRef = _react.useRef.call(void 0, ComponentClass);
1195
+ const prevPropsRef = _react.useRef.call(void 0, null);
1196
+ const propsNotifyingRef = _react.useRef.call(void 0, false);
1197
+ const renderReactionRef = _react.useRef.call(void 0, { current: null });
1198
+ if (!componentHandleRef.current) {
1199
+ componentHandleRef.current = { current: void 0 };
1200
+ }
1201
+ const componentHandle = componentHandleRef.current;
1202
+ if (vmRef.current && classRef.current !== ComponentClass) {
1203
+ classRef.current = ComponentClass;
457
1204
  vmRef.current = null;
458
1205
  }
459
1206
  if (!vmRef.current) {
460
- const instance = new ViewClass();
461
- instance._propsBox = import_mobx2.observable.box(props, { deep: false });
1207
+ applyMobxActionPolicy();
1208
+ const substitution = _optionalChain([substitutions, 'optionalAccess', _48 => _48.get, 'call', _49 => _49(ComponentClass)]);
1209
+ if (substitution && !substitution.template && !template) throw new Error("[mobx-mantle] A substituted integrated model requires an explicit test template.");
1210
+ const instance = withInjectionScope(resolver, () => substitution ? constructComponent(SubstituteComponent, props, parentHandle) : constructComponent(ComponentClass, props, parentHandle));
1211
+ substitutedTemplate.current = _optionalChain([substitution, 'optionalAccess', _50 => _50.template]);
1212
+ setModelIdentity(instance, ComponentClass);
1213
+ if (substitution) installSubstituteState(instance, substitution.state());
462
1214
  instance.forwardRef = ref;
463
- instance._collectBehaviors();
1215
+ instance._propsBox._renderReaction = renderReactionRef.current;
1216
+ if (!substitution) instance._collectBehaviors();
464
1217
  const decoratorAnnotations = getAnnotations(instance);
465
- if (decoratorAnnotations) {
1218
+ if (substitution) {
1219
+ } else if (decoratorAnnotations) {
466
1220
  const annotations = { ...decoratorAnnotations };
467
- let proto = Object.getPrototypeOf(instance);
468
- while (proto && proto !== View.prototype) {
469
- const descriptors = Object.getOwnPropertyDescriptors(proto);
470
- for (const [key, descriptor] of Object.entries(descriptors)) {
471
- if (BASE_EXCLUDES.has(key)) continue;
472
- if (key in annotations) continue;
473
- if (typeof descriptor.value === "function") {
474
- annotations[key] = import_mobx2.action.bound;
475
- }
1221
+ _mobx.makeObservable.call(void 0, instance, annotations);
1222
+ const protoInfo = collectProtoInfo(instance, Component.prototype, BASE_EXCLUDES, componentProtoInfo);
1223
+ for (const key of protoInfo.methodKeys) {
1224
+ if (key in annotations) continue;
1225
+ const method = instance[key];
1226
+ if (typeof method === "function") {
1227
+ instance[key] = smartBind(method, instance);
476
1228
  }
477
- proto = Object.getPrototypeOf(proto);
478
1229
  }
479
- (0, import_mobx2.makeObservable)(instance, annotations);
480
1230
  } else if (autoObservable) {
481
- makeViewObservable(instance, true);
1231
+ makeComponentObservable(instance, true);
482
1232
  } else {
483
- (0, import_mobx2.makeObservable)(instance);
1233
+ _mobx.makeObservable.call(void 0, instance);
484
1234
  }
485
1235
  const reactiveProps = new Proxy({}, {
486
1236
  get: (_, key) => instance.props[key],
@@ -488,100 +1238,264 @@ function createView(ViewClass, templateOrOptions) {
488
1238
  ownKeys: () => Reflect.ownKeys(instance.props),
489
1239
  getOwnPropertyDescriptor: (_, key) => Reflect.getOwnPropertyDescriptor(instance.props, key)
490
1240
  });
491
- instance.onCreate?.(reactiveProps);
1241
+ withOwnerServiceScope(instance, () => _mobx.runInAction.call(void 0, () => _optionalChain([instance, 'access', _51 => _51.onCreate, 'optionalCall', _52 => _52(reactiveProps)])));
1242
+ if (process.env.NODE_ENV !== "production" && autoObservable && !decoratorAnnotations && !substitution) {
1243
+ for (const key of Object.keys(instance)) {
1244
+ if (BASE_EXCLUDES.has(key) || key.startsWith("_")) continue;
1245
+ const value = instance[key];
1246
+ if (typeof value === "function" || isServiceValue(instance, value)) continue;
1247
+ if (!_mobx.isObservableProp.call(void 0, instance, key)) {
1248
+ console.warn(
1249
+ `[mobx-mantle] ${ComponentClass.name}.${key} was first assigned in onCreate() and is not reactive. Declare it as a class field so it can be made observable.`
1250
+ );
1251
+ }
1252
+ }
1253
+ }
492
1254
  vmRef.current = instance;
493
1255
  prevPropsRef.current = props;
494
1256
  }
495
1257
  const vm = vmRef.current;
1258
+ const renderTemplate = _nullishCoalesce(substitutedTemplate.current, () => ( template));
1259
+ componentParents.set(vm, parentHandle);
1260
+ componentHandle.current = vm;
496
1261
  if (process.env.NODE_ENV !== "production" && propsNotifyingRef.current) {
497
1262
  console.warn(
498
- `[mobx-mantle] ${ViewClass.name}: A reaction to a prop change modified observable state, which caused an extra re-render. Consider using a computed getter instead.`
1263
+ `[mobx-mantle] ${ComponentClass.name}: A reaction to a prop change modified observable state, which caused an extra re-render. Consider using a computed getter instead.`
499
1264
  );
500
1265
  propsNotifyingRef.current = false;
501
1266
  }
502
1267
  vm._syncProps(props);
503
1268
  vm.forwardRef = ref;
504
- (0, import_react.useLayoutEffect)(() => {
1269
+ useIsomorphicLayoutEffect(() => {
505
1270
  if (!shallowEqual(prevPropsRef.current, props)) {
506
1271
  prevPropsRef.current = props;
507
1272
  propsNotifyingRef.current = true;
508
- (0, import_mobx2.runInAction)(() => {
1273
+ _mobx.runInAction.call(void 0, () => {
509
1274
  vm._propsBox.set(props);
510
1275
  });
511
1276
  propsNotifyingRef.current = false;
512
1277
  }
513
1278
  });
514
- (0, import_react.useLayoutEffect)(() => {
1279
+ useIsomorphicLayoutEffect(() => {
1280
+ activateSpecs(vm);
515
1281
  vm._layoutMountBehaviors();
516
1282
  let cleanup;
517
1283
  try {
518
- const result = vm.onLayoutMount?.();
1284
+ const result = _mobx.runInAction.call(void 0, () => _optionalChain([vm, 'access', _53 => _53.onLayoutMount, 'optionalCall', _54 => _54()]));
519
1285
  if (process.env.NODE_ENV !== "production" && result instanceof Promise) {
520
1286
  console.error(
521
- `[mobx-mantle] ${ViewClass.name}.onLayoutMount() returned a Promise. Lifecycle methods must be synchronous. Use a sync onLayoutMount that calls an async method instead.`
1287
+ `[mobx-mantle] ${ComponentClass.name}.onLayoutMount() returned a Promise. Lifecycle methods must be synchronous. Use a sync onLayoutMount that calls an async method instead.`
522
1288
  );
523
1289
  }
524
- cleanup = result;
1290
+ cleanup = typeof result === "function" ? result : void 0;
525
1291
  } catch (e) {
526
- reportError(e, { phase: "onLayoutMount", name: ViewClass.name, isBehavior: false });
1292
+ reportError(e, { phase: "onLayoutMount", name: ComponentClass.name, isBehavior: false });
527
1293
  }
528
1294
  return () => {
529
- cleanup?.();
1295
+ _mobx.runInAction.call(void 0, () => _optionalChain([cleanup, 'optionalCall', _55 => _55()]));
530
1296
  };
531
1297
  }, [vm]);
532
- (0, import_react.useEffect)(() => {
1298
+ _react.useEffect.call(void 0, () => {
533
1299
  vm._mountBehaviors();
534
1300
  let cleanup;
535
1301
  try {
536
- const result = vm.onMount?.();
1302
+ const result = _mobx.runInAction.call(void 0, () => _optionalChain([vm, 'access', _56 => _56.onMount, 'optionalCall', _57 => _57()]));
537
1303
  if (process.env.NODE_ENV !== "production" && result instanceof Promise) {
538
1304
  console.error(
539
- `[mobx-mantle] ${ViewClass.name}.onMount() returned a Promise. Lifecycle methods must be synchronous. Use a sync onMount that calls an async method instead.`
1305
+ `[mobx-mantle] ${ComponentClass.name}.onMount() returned a Promise. Lifecycle methods must be synchronous. Use a sync onMount that calls an async method instead.`
540
1306
  );
541
1307
  }
542
- cleanup = result;
1308
+ cleanup = typeof result === "function" ? result : void 0;
543
1309
  } catch (e) {
544
- reportError(e, { phase: "onMount", name: ViewClass.name, isBehavior: false });
1310
+ reportError(e, { phase: "onMount", name: ComponentClass.name, isBehavior: false });
1311
+ }
1312
+ if (process.env.NODE_ENV !== "production") {
1313
+ warnUncollectedBehaviors(vm, ComponentClass.name, vm._behaviors);
545
1314
  }
546
1315
  return () => {
547
- cleanup?.();
1316
+ _mobx.runInAction.call(void 0, () => _optionalChain([cleanup, 'optionalCall', _58 => _58()]));
548
1317
  try {
549
- vm.onUnmount?.();
1318
+ _mobx.runInAction.call(void 0, () => _optionalChain([vm, 'access', _59 => _59.onUnmount, 'optionalCall', _60 => _60()]));
550
1319
  } catch (e) {
551
- reportError(e, { phase: "onUnmount", name: ViewClass.name, isBehavior: false });
1320
+ reportError(e, { phase: "onUnmount", name: ComponentClass.name, isBehavior: false });
552
1321
  }
553
1322
  vm._disposeWatchers();
554
1323
  vm._unmountBehaviors();
1324
+ vm._wasUnmounted = true;
555
1325
  };
556
1326
  }, [vm]);
557
- (0, import_react.useEffect)(() => {
1327
+ _react.useEffect.call(void 0, () => {
558
1328
  try {
559
- vm.onUpdate?.();
1329
+ _mobx.runInAction.call(void 0, () => _optionalChain([vm, 'access', _61 => _61.onUpdate, 'optionalCall', _62 => _62()]));
560
1330
  } catch (e) {
561
- reportError(e, { phase: "onUpdate", name: ViewClass.name, isBehavior: false });
1331
+ reportError(e, { phase: "onUpdate", name: ComponentClass.name, isBehavior: false });
562
1332
  }
563
1333
  });
564
- if (!template && !vm.render) {
1334
+ if (!renderTemplate && !vm.render) {
565
1335
  throw new Error(
566
- `[mobx-mantle] ${ViewClass.name}: Missing render() method. Either define render() in your View class or pass a template function to createView().`
1336
+ `[mobx-mantle] ${ComponentClass.name}: Missing render() method. Either define render() in your Component class or pass a template function to createComponent().`
567
1337
  );
568
1338
  }
569
- return (0, import_mobx_react_lite.useObserver)(() => {
570
- return template ? template(vm) : vm.render();
571
- });
1339
+ const rendered = useMantleObserver(
1340
+ () => renderTemplate ? renderTemplate(vm) : vm.render(),
1341
+ ComponentClass.name,
1342
+ renderReactionRef.current
1343
+ );
1344
+ return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, ComponentParentContext.Provider, { value: componentHandle, children: rendered });
572
1345
  });
573
- return (0, import_react.memo)(Component);
574
- }
575
- // Annotate the CommonJS export names for ESM import in node:
576
- 0 && (module.exports = {
577
- Behavior,
578
- View,
579
- ViewModel,
580
- action,
581
- computed,
582
- configure,
583
- createBehavior,
584
- createView,
585
- observable
586
- });
1346
+ return _react.memo.call(void 0, ReactComponent);
1347
+ }
1348
+ function createForwardRef(ComponentClass, templateOrOptions) {
1349
+ return createComponent(ComponentClass, templateOrOptions);
1350
+ }
1351
+
1352
+ // src/useBehavior.ts
1353
+
1354
+
1355
+ function useBehavior(create) {
1356
+ const resolver = _react.useContext.call(void 0, ServiceContext);
1357
+ const [entry] = _react.useState.call(void 0, () => {
1358
+ const instance = withInjectionScope(resolver, create);
1359
+ if (process.env.NODE_ENV !== "production" && !isBehavior(instance)) {
1360
+ console.warn(
1361
+ "[mobx-mantle] useBehavior() expected the factory to return a behavior instance (created with a factory from createBehavior). Lifecycle relay will be skipped for this value."
1362
+ );
1363
+ }
1364
+ return { instance };
1365
+ });
1366
+ useIsomorphicLayoutEffect(() => {
1367
+ if (!isBehavior(entry.instance)) return;
1368
+ layoutMountBehavior(entry);
1369
+ }, [entry]);
1370
+ _react.useEffect.call(void 0, () => {
1371
+ if (!isBehavior(entry.instance)) return;
1372
+ mountBehavior(entry);
1373
+ return () => unmountBehavior(entry);
1374
+ }, [entry]);
1375
+ return entry.instance;
1376
+ }
1377
+
1378
+ // src/shared.ts
1379
+
1380
+ var registry = /* @__PURE__ */ new WeakMap();
1381
+ function ownDataFields(store) {
1382
+ return Object.keys(store).filter((key) => {
1383
+ if (key.startsWith("_")) return false;
1384
+ if (typeof store[key] === "function") return false;
1385
+ return _mobx.isObservableProp.call(void 0, store, key);
1386
+ });
1387
+ }
1388
+ function serialize(store, fields) {
1389
+ const values = {};
1390
+ for (const field of fields) values[field] = _mobx.toJS.call(void 0, store[field]);
1391
+ return JSON.stringify(values);
1392
+ }
1393
+ function apply(store, fields, json) {
1394
+ let parsed;
1395
+ try {
1396
+ parsed = JSON.parse(json);
1397
+ } catch (e3) {
1398
+ return false;
1399
+ }
1400
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return false;
1401
+ const values = parsed;
1402
+ _mobx.runInAction.call(void 0, () => {
1403
+ for (const field of fields) {
1404
+ if (field in values) store[field] = values[field];
1405
+ }
1406
+ });
1407
+ return true;
1408
+ }
1409
+ function safeStorage() {
1410
+ try {
1411
+ return typeof localStorage === "undefined" ? null : localStorage;
1412
+ } catch (e4) {
1413
+ return null;
1414
+ }
1415
+ }
1416
+ function shared(store, key, options = {}) {
1417
+ if (registry.has(store) || typeof window === "undefined") return store;
1418
+ const target = store;
1419
+ const fields = options.fields ? [...options.fields] : ownDataFields(store);
1420
+ const storage = options.persist === false ? null : safeStorage();
1421
+ const channel = typeof BroadcastChannel === "function" ? new BroadcastChannel(`mantle-shared:${key}`) : null;
1422
+ _optionalChain([channel, 'optionalAccess', _63 => _63.unref, 'optionalCall', _64 => _64()]);
1423
+ let stored = null;
1424
+ try {
1425
+ stored = _nullishCoalesce(_optionalChain([storage, 'optionalAccess', _65 => _65.getItem, 'call', _66 => _66(key)]), () => ( null));
1426
+ } catch (e5) {
1427
+ stored = null;
1428
+ }
1429
+ if (stored !== null) apply(target, fields, stored);
1430
+ let current = serialize(target, fields);
1431
+ const stopPublish = _mobx.reaction.call(void 0,
1432
+ () => serialize(target, fields),
1433
+ (json) => {
1434
+ if (json === current) return;
1435
+ current = json;
1436
+ try {
1437
+ _optionalChain([storage, 'optionalAccess', _67 => _67.setItem, 'call', _68 => _68(key, json)]);
1438
+ } catch (e6) {
1439
+ }
1440
+ _optionalChain([channel, 'optionalAccess', _69 => _69.postMessage, 'call', _70 => _70(json)]);
1441
+ }
1442
+ );
1443
+ const receive = (json) => {
1444
+ if (json === current) return;
1445
+ current = json;
1446
+ apply(target, fields, json);
1447
+ };
1448
+ const onMessage = (event) => {
1449
+ if (typeof event.data === "string") receive(event.data);
1450
+ };
1451
+ const onStorage = (event) => {
1452
+ if (event.key === key && event.newValue !== null) receive(event.newValue);
1453
+ };
1454
+ _optionalChain([channel, 'optionalAccess', _71 => _71.addEventListener, 'call', _72 => _72("message", onMessage)]);
1455
+ window.addEventListener("storage", onStorage);
1456
+ registry.set(store, {
1457
+ dispose() {
1458
+ stopPublish();
1459
+ _optionalChain([channel, 'optionalAccess', _73 => _73.removeEventListener, 'call', _74 => _74("message", onMessage)]);
1460
+ _optionalChain([channel, 'optionalAccess', _75 => _75.close, 'call', _76 => _76()]);
1461
+ window.removeEventListener("storage", onStorage);
1462
+ registry.delete(store);
1463
+ }
1464
+ });
1465
+ return store;
1466
+ }
1467
+ function unshare(store) {
1468
+ _optionalChain([registry, 'access', _77 => _77.get, 'call', _78 => _78(store), 'optionalAccess', _79 => _79.dispose, 'call', _80 => _80()]);
1469
+ }
1470
+ function shareStores(stores, options = {}) {
1471
+ const { prefix = "shared", ...rest } = options;
1472
+ for (const [name, store] of Object.entries(stores)) {
1473
+ shared(store, `${prefix}:${name}`, rest);
1474
+ }
1475
+ return stores;
1476
+ }
1477
+
1478
+
1479
+
1480
+
1481
+
1482
+
1483
+
1484
+
1485
+
1486
+
1487
+
1488
+
1489
+
1490
+
1491
+
1492
+
1493
+
1494
+
1495
+
1496
+
1497
+
1498
+
1499
+
1500
+ exports.Behavior = Behavior; exports.Component = Component; exports.ModelProvider = ModelProvider; exports.ServiceProvider = ServiceProvider; exports.ViewModel = Component; exports.action = action2; exports.computed = computed2; exports.configure = configure; exports.createBehavior = createBehavior; exports.createComponent = createComponent; exports.createForwardRef = createForwardRef; exports.createServiceToken = createServiceToken; exports.inject = inject; exports.isModel = isModel; exports.observable = observable2; exports.observer = observer; exports.shareStores = shareStores; exports.shared = shared; exports.toValue = _chunkWNF647U6cjs.toValue; exports.unshare = unshare; exports.useBehavior = useBehavior; exports.withInjectionScope = withInjectionScope;
587
1501
  //# sourceMappingURL=index.cjs.map