@pyreon/vue-compat 0.13.1 → 0.15.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 CHANGED
@@ -163,3 +163,23 @@ app.mount('#app')
163
163
 
164
164
  - **`h` / `Fragment`** -- JSX runtime.
165
165
  - **`batch(fn)`** -- coalesce multiple signal writes.
166
+
167
+ ## Composing Pyreon framework components inside vue-compat
168
+
169
+ Pyreon's framework components (`RouterView`, `PyreonUI`, `FormProvider`, `QueryClientProvider`, …) ship marked with `nativeCompat()` from `@pyreon/core` — vue-compat's JSX runtime detects the marker and routes them through Pyreon's setup frame instead of the compat wrapper. **You don't need to do anything** for the 24 components shipped marked.
170
+
171
+ If you write your **own** Pyreon-flavored helper that uses `provide()` / `onMount()` / `onUnmount()` / `effect()` at component-body scope and use it in a vue-compat app, mark it explicitly:
172
+
173
+ ```tsx
174
+ import { nativeCompat, provide, createContext } from '@pyreon/core'
175
+
176
+ const MyCtx = createContext<string>('default')
177
+
178
+ function MyProvider(props: { value: string; children?: unknown }) {
179
+ provide(MyCtx, props.value)
180
+ return props.children as never
181
+ }
182
+ nativeCompat(MyProvider) // ← required for compat-mode apps
183
+ ```
184
+
185
+ Without the marker, the wrapper relocates the body's render context and `provide()` lands in a torn-down context stack — descendants read the default. See [`packages/core/core/src/compat-marker.ts`](../../core/core/src/compat-marker.ts) for details.
@@ -5386,7 +5386,7 @@ var drawChart = (function (exports) {
5386
5386
  </script>
5387
5387
  <script>
5388
5388
  /*<!--*/
5389
- const data = {"version":2,"tree":{"name":"root","children":[{"name":"index.js","children":[{"name":"src","children":[{"uid":"d4e3dc19-1","name":"jsx-runtime.ts"},{"uid":"d4e3dc19-3","name":"index.ts"}]}]}],"isRoot":true},"nodeParts":{"d4e3dc19-1":{"renderedLength":186,"gzipLength":138,"brotliLength":0,"metaUid":"d4e3dc19-0"},"d4e3dc19-3":{"renderedLength":12003,"gzipLength":2957,"brotliLength":0,"metaUid":"d4e3dc19-2"}},"nodeMetas":{"d4e3dc19-0":{"id":"/src/jsx-runtime.ts","moduleParts":{"index.js":"d4e3dc19-1"},"imported":[{"uid":"d4e3dc19-4"},{"uid":"d4e3dc19-5"}],"importedBy":[{"uid":"d4e3dc19-2"}]},"d4e3dc19-2":{"id":"/src/index.ts","moduleParts":{"index.js":"d4e3dc19-3"},"imported":[{"uid":"d4e3dc19-4"},{"uid":"d4e3dc19-5"},{"uid":"d4e3dc19-6"},{"uid":"d4e3dc19-0"}],"importedBy":[],"isEntry":true},"d4e3dc19-4":{"id":"@pyreon/core","moduleParts":{},"imported":[],"importedBy":[{"uid":"d4e3dc19-2"},{"uid":"d4e3dc19-0"}]},"d4e3dc19-5":{"id":"@pyreon/reactivity","moduleParts":{},"imported":[],"importedBy":[{"uid":"d4e3dc19-2"},{"uid":"d4e3dc19-0"}]},"d4e3dc19-6":{"id":"@pyreon/runtime-dom","moduleParts":{},"imported":[],"importedBy":[{"uid":"d4e3dc19-2"}]}},"env":{"rollup":"4.23.0"},"options":{"gzip":true,"brotli":false,"sourcemap":false}};
5389
+ const data = {"version":2,"tree":{"name":"root","children":[{"name":"index.js","children":[{"name":"src","children":[{"uid":"2431af31-1","name":"jsx-runtime.ts"},{"uid":"2431af31-3","name":"index.ts"}]}]}],"isRoot":true},"nodeParts":{"2431af31-1":{"renderedLength":186,"gzipLength":138,"brotliLength":0,"metaUid":"2431af31-0"},"2431af31-3":{"renderedLength":21829,"gzipLength":5180,"brotliLength":0,"metaUid":"2431af31-2"}},"nodeMetas":{"2431af31-0":{"id":"/src/jsx-runtime.ts","moduleParts":{"index.js":"2431af31-1"},"imported":[{"uid":"2431af31-4"},{"uid":"2431af31-5"}],"importedBy":[{"uid":"2431af31-2"}]},"2431af31-2":{"id":"/src/index.ts","moduleParts":{"index.js":"2431af31-3"},"imported":[{"uid":"2431af31-4"},{"uid":"2431af31-5"},{"uid":"2431af31-6"},{"uid":"2431af31-0"}],"importedBy":[],"isEntry":true},"2431af31-4":{"id":"@pyreon/core","moduleParts":{},"imported":[],"importedBy":[{"uid":"2431af31-2"},{"uid":"2431af31-0"}]},"2431af31-5":{"id":"@pyreon/reactivity","moduleParts":{},"imported":[],"importedBy":[{"uid":"2431af31-2"},{"uid":"2431af31-0"}]},"2431af31-6":{"id":"@pyreon/runtime-dom","moduleParts":{},"imported":[],"importedBy":[{"uid":"2431af31-2"}]}},"env":{"rollup":"4.23.0"},"options":{"gzip":true,"brotli":false,"sourcemap":false}};
5390
5390
 
5391
5391
  const run = () => {
5392
5392
  const width = window.innerWidth;
@@ -5386,7 +5386,7 @@ var drawChart = (function (exports) {
5386
5386
  </script>
5387
5387
  <script>
5388
5388
  /*<!--*/
5389
- const data = {"version":2,"tree":{"name":"root","children":[{"name":"jsx-runtime.js","children":[{"name":"src","children":[{"uid":"d37de618-1","name":"jsx-runtime.ts"},{"uid":"d37de618-3","name":"jsx-dev-runtime.ts"}]}]}],"isRoot":true},"nodeParts":{"d37de618-1":{"renderedLength":2340,"gzipLength":841,"brotliLength":0,"metaUid":"d37de618-0"},"d37de618-3":{"renderedLength":0,"gzipLength":0,"brotliLength":0,"metaUid":"d37de618-2"}},"nodeMetas":{"d37de618-0":{"id":"/src/jsx-runtime.ts","moduleParts":{"jsx-runtime.js":"d37de618-1"},"imported":[{"uid":"d37de618-4"},{"uid":"d37de618-5"}],"importedBy":[{"uid":"d37de618-2"}]},"d37de618-2":{"id":"/src/jsx-dev-runtime.ts","moduleParts":{"jsx-runtime.js":"d37de618-3"},"imported":[{"uid":"d37de618-0"}],"importedBy":[],"isEntry":true},"d37de618-4":{"id":"@pyreon/core","moduleParts":{},"imported":[],"importedBy":[{"uid":"d37de618-0"}]},"d37de618-5":{"id":"@pyreon/reactivity","moduleParts":{},"imported":[],"importedBy":[{"uid":"d37de618-0"}]}},"env":{"rollup":"4.23.0"},"options":{"gzip":true,"brotli":false,"sourcemap":false}};
5389
+ const data = {"version":2,"tree":{"name":"root","children":[{"name":"jsx-runtime.js","children":[{"name":"src","children":[{"uid":"5769505c-1","name":"jsx-runtime.ts"},{"uid":"5769505c-3","name":"jsx-dev-runtime.ts"}]}]}],"isRoot":true},"nodeParts":{"5769505c-1":{"renderedLength":2715,"gzipLength":981,"brotliLength":0,"metaUid":"5769505c-0"},"5769505c-3":{"renderedLength":0,"gzipLength":0,"brotliLength":0,"metaUid":"5769505c-2"}},"nodeMetas":{"5769505c-0":{"id":"/src/jsx-runtime.ts","moduleParts":{"jsx-runtime.js":"5769505c-1"},"imported":[{"uid":"5769505c-4"},{"uid":"5769505c-5"}],"importedBy":[{"uid":"5769505c-2"}]},"5769505c-2":{"id":"/src/jsx-dev-runtime.ts","moduleParts":{"jsx-runtime.js":"5769505c-3"},"imported":[{"uid":"5769505c-0"}],"importedBy":[],"isEntry":true},"5769505c-4":{"id":"@pyreon/core","moduleParts":{},"imported":[],"importedBy":[{"uid":"5769505c-0"}]},"5769505c-5":{"id":"@pyreon/reactivity","moduleParts":{},"imported":[],"importedBy":[{"uid":"5769505c-0"}]}},"env":{"rollup":"4.23.0"},"options":{"gzip":true,"brotli":false,"sourcemap":false}};
5390
5390
 
5391
5391
  const run = () => {
5392
5392
  const width = window.innerWidth;
package/lib/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { Fragment, createContext, h as pyreonH, onMount, onUnmount, onUpdate, popContext, pushContext, useContext } from "@pyreon/core";
1
+ import { Fragment, Portal, createContext, h as pyreonH, onMount, onUnmount, onUpdate, popContext, pushContext, useContext } from "@pyreon/core";
2
2
  import { batch, computed as computed$1, createStore, effect, nextTick as nextTick$1, signal } from "@pyreon/reactivity";
3
3
  import { mount } from "@pyreon/runtime-dom";
4
4
 
@@ -14,8 +14,9 @@ function getHookIndex() {
14
14
 
15
15
  //#endregion
16
16
  //#region src/index.ts
17
- const V_IS_REF = Symbol("__v_isRef");
17
+ const V_IS_REF = Symbol.for("__v_isRef");
18
18
  const V_IS_READONLY = Symbol("__v_isReadonly");
19
+ const V_SKIP = Symbol("__v_skip");
19
20
  const V_RAW = Symbol("__v_raw");
20
21
  /**
21
22
  * Creates a reactive ref wrapping the given value.
@@ -40,6 +41,7 @@ function ref(value) {
40
41
  s.set(v);
41
42
  scheduleRerender();
42
43
  },
44
+ /** @internal — access underlying signal for triggerRef */
43
45
  _signal: s,
44
46
  _scheduleRerender: scheduleRerender
45
47
  };
@@ -55,6 +57,7 @@ function ref(value) {
55
57
  set value(v) {
56
58
  s.set(v);
57
59
  },
60
+ /** @internal — access underlying signal for triggerRef */
58
61
  _signal: s
59
62
  };
60
63
  }
@@ -88,6 +91,15 @@ function isRef(val) {
88
91
  function unref(r) {
89
92
  return isRef(r) ? r.value : r;
90
93
  }
94
+ /**
95
+ * Unwraps a ref, calls a getter, or returns the value as-is.
96
+ * Vue 3.3+ API for normalizing ref/getter/value inputs.
97
+ */
98
+ function toValue(source) {
99
+ if (isRef(source)) return source.value;
100
+ if (typeof source === "function") return source();
101
+ return source;
102
+ }
91
103
  function computed(fnOrOptions) {
92
104
  const ctx = getCurrentCtx();
93
105
  if (ctx) {
@@ -134,6 +146,7 @@ const rawMap = /* @__PURE__ */ new WeakMap();
134
146
  * call `scheduleRerender()`.
135
147
  */
136
148
  function reactive(obj) {
149
+ if (obj[V_SKIP]) return obj;
137
150
  const ctx = getCurrentCtx();
138
151
  if (ctx) {
139
152
  const idx = getHookIndex();
@@ -183,7 +196,22 @@ function readonly(obj) {
183
196
  }
184
197
  return _createReadonlyProxy(obj);
185
198
  }
186
- function _createReadonlyProxy(obj) {
199
+ /**
200
+ * Returns a shallow readonly proxy — only top-level properties throw on set.
201
+ * Nested objects are NOT wrapped in readonly (unlike `readonly()`).
202
+ */
203
+ function shallowReadonly(obj) {
204
+ const ctx = getCurrentCtx();
205
+ if (ctx) {
206
+ const idx = getHookIndex();
207
+ if (idx < ctx.hooks.length) return ctx.hooks[idx];
208
+ const proxy = _createShallowReadonlyProxy(obj);
209
+ ctx.hooks[idx] = proxy;
210
+ return proxy;
211
+ }
212
+ return _createShallowReadonlyProxy(obj);
213
+ }
214
+ function _createShallowReadonlyProxy(obj) {
187
215
  return new Proxy(obj, {
188
216
  get(target, key) {
189
217
  if (key === V_IS_READONLY) return true;
@@ -199,6 +227,24 @@ function _createReadonlyProxy(obj) {
199
227
  }
200
228
  });
201
229
  }
230
+ function _createReadonlyProxy(obj) {
231
+ return new Proxy(obj, {
232
+ get(target, key) {
233
+ if (key === V_IS_READONLY) return true;
234
+ if (key === V_RAW) return target;
235
+ const value = Reflect.get(target, key);
236
+ if (value !== null && typeof value === "object" && !isRef(value)) return _createReadonlyProxy(value);
237
+ return value;
238
+ },
239
+ set(_target, key) {
240
+ if (key === V_IS_READONLY || key === V_RAW) return true;
241
+ throw new Error(`Cannot set property "${String(key)}" on a readonly object`);
242
+ },
243
+ deleteProperty(_target, key) {
244
+ throw new Error(`Cannot delete property "${String(key)}" from a readonly object`);
245
+ }
246
+ });
247
+ }
202
248
  /**
203
249
  * Returns the raw (unwrapped) object behind a reactive or readonly proxy.
204
250
  */
@@ -255,12 +301,102 @@ function toRefs(obj) {
255
301
  for (const key of Object.keys(obj)) result[key] = _createToRef(obj, key);
256
302
  return result;
257
303
  }
258
- /**
259
- * Watches a reactive source and calls `cb` when it changes.
260
- *
261
- * Inside a component: hook-indexed, created once. Disposed on unmount.
262
- */
263
304
  function watch(source, cb, options) {
305
+ if (Array.isArray(source)) return _watchArray(source, cb, options);
306
+ return _watchSingle(source, cb, options);
307
+ }
308
+ function _watchArray(sources, cb, options) {
309
+ const getters = sources.map((s) => isRef(s) ? () => s.value : s);
310
+ let cleanupFn;
311
+ const onCleanup = (fn) => {
312
+ cleanupFn = fn;
313
+ };
314
+ const runCleanup = () => {
315
+ if (cleanupFn) {
316
+ cleanupFn();
317
+ cleanupFn = void 0;
318
+ }
319
+ };
320
+ const ctx = getCurrentCtx();
321
+ if (ctx) {
322
+ const idx = getHookIndex();
323
+ if (idx < ctx.hooks.length) return ctx.hooks[idx];
324
+ let oldValues;
325
+ let initialized = false;
326
+ if (options?.immediate) {
327
+ const current = getters.map((g) => g());
328
+ cb(current, getters.map(() => void 0), onCleanup);
329
+ oldValues = current;
330
+ initialized = true;
331
+ }
332
+ let running = false;
333
+ const combined = computed$1(() => getters.map((g) => g()));
334
+ const e = effect(() => {
335
+ if (running) return;
336
+ running = true;
337
+ try {
338
+ const newValues = combined();
339
+ if (initialized) {
340
+ runCleanup();
341
+ cb([...newValues], oldValues ? [...oldValues] : getters.map(() => void 0), onCleanup);
342
+ }
343
+ oldValues = [...newValues];
344
+ initialized = true;
345
+ } finally {
346
+ running = false;
347
+ }
348
+ });
349
+ const stop = () => {
350
+ runCleanup();
351
+ e.dispose();
352
+ };
353
+ ctx.hooks[idx] = stop;
354
+ ctx.unmountCallbacks.push(stop);
355
+ return stop;
356
+ }
357
+ let oldValues;
358
+ let initialized = false;
359
+ if (options?.immediate) {
360
+ const current = getters.map((g) => g());
361
+ cb(current, getters.map(() => void 0), onCleanup);
362
+ oldValues = current;
363
+ initialized = true;
364
+ }
365
+ let running = false;
366
+ const combined = computed$1(() => getters.map((g) => g()));
367
+ const e = effect(() => {
368
+ if (running) return;
369
+ running = true;
370
+ try {
371
+ const newValues = combined();
372
+ if (initialized) {
373
+ runCleanup();
374
+ cb([...newValues], oldValues ? [...oldValues] : getters.map(() => void 0), onCleanup);
375
+ }
376
+ oldValues = [...newValues];
377
+ initialized = true;
378
+ } finally {
379
+ running = false;
380
+ }
381
+ });
382
+ const stop = () => {
383
+ runCleanup();
384
+ e.dispose();
385
+ };
386
+ if (_currentEffectScope) _currentEffectScope._cleanups.push(stop);
387
+ return stop;
388
+ }
389
+ function _watchSingle(source, cb, options) {
390
+ let cleanupFn;
391
+ const onCleanup = (fn) => {
392
+ cleanupFn = fn;
393
+ };
394
+ const runCleanup = () => {
395
+ if (cleanupFn) {
396
+ cleanupFn();
397
+ cleanupFn = void 0;
398
+ }
399
+ };
264
400
  const ctx = getCurrentCtx();
265
401
  if (ctx) {
266
402
  const idx = getHookIndex();
@@ -271,7 +407,7 @@ function watch(source, cb, options) {
271
407
  if (options?.immediate) {
272
408
  oldValue = void 0;
273
409
  const current = getter();
274
- cb(current, oldValue);
410
+ cb(current, oldValue, onCleanup);
275
411
  oldValue = current;
276
412
  initialized = true;
277
413
  }
@@ -281,14 +417,20 @@ function watch(source, cb, options) {
281
417
  running = true;
282
418
  try {
283
419
  const newValue = getter();
284
- if (initialized) cb(newValue, oldValue);
420
+ if (initialized) {
421
+ runCleanup();
422
+ cb(newValue, oldValue, onCleanup);
423
+ }
285
424
  oldValue = newValue;
286
425
  initialized = true;
287
426
  } finally {
288
427
  running = false;
289
428
  }
290
429
  });
291
- const stop = () => e.dispose();
430
+ const stop = () => {
431
+ runCleanup();
432
+ e.dispose();
433
+ };
292
434
  ctx.hooks[idx] = stop;
293
435
  ctx.unmountCallbacks.push(stop);
294
436
  return stop;
@@ -299,7 +441,7 @@ function watch(source, cb, options) {
299
441
  if (options?.immediate) {
300
442
  oldValue = void 0;
301
443
  const current = getter();
302
- cb(current, oldValue);
444
+ cb(current, oldValue, onCleanup);
303
445
  oldValue = current;
304
446
  initialized = true;
305
447
  }
@@ -309,23 +451,43 @@ function watch(source, cb, options) {
309
451
  running = true;
310
452
  try {
311
453
  const newValue = getter();
312
- if (initialized) cb(newValue, oldValue);
454
+ if (initialized) {
455
+ runCleanup();
456
+ cb(newValue, oldValue, onCleanup);
457
+ }
313
458
  oldValue = newValue;
314
459
  initialized = true;
315
460
  } finally {
316
461
  running = false;
317
462
  }
318
463
  });
319
- return () => e.dispose();
464
+ const stop = () => {
465
+ runCleanup();
466
+ e.dispose();
467
+ };
468
+ if (_currentEffectScope) _currentEffectScope._cleanups.push(stop);
469
+ return stop;
320
470
  }
321
471
  /**
322
472
  * Runs the given function reactively — re-executes whenever its tracked
323
- * dependencies change.
473
+ * dependencies change. Passes an `onCleanup` registration function to the
474
+ * callback, matching Vue 3's `watchEffect((onCleanup) => { ... })` API.
324
475
  *
325
476
  * Inside a component: hook-indexed, created once. Disposed on unmount.
326
477
  */
327
478
  function watchEffect(fn) {
328
479
  const ctx = getCurrentCtx();
480
+ let cleanupFn;
481
+ const onCleanup = (cleanup) => {
482
+ cleanupFn = cleanup;
483
+ };
484
+ const runEffect = () => {
485
+ if (cleanupFn) {
486
+ cleanupFn();
487
+ cleanupFn = void 0;
488
+ }
489
+ fn(onCleanup);
490
+ };
329
491
  if (ctx) {
330
492
  const idx = getHookIndex();
331
493
  if (idx < ctx.hooks.length) return ctx.hooks[idx];
@@ -334,12 +496,15 @@ function watchEffect(fn) {
334
496
  if (running) return;
335
497
  running = true;
336
498
  try {
337
- fn();
499
+ runEffect();
338
500
  } finally {
339
501
  running = false;
340
502
  }
341
503
  });
342
- const stop = () => e.dispose();
504
+ const stop = () => {
505
+ if (cleanupFn) cleanupFn();
506
+ e.dispose();
507
+ };
343
508
  ctx.hooks[idx] = stop;
344
509
  ctx.unmountCallbacks.push(stop);
345
510
  return stop;
@@ -349,12 +514,17 @@ function watchEffect(fn) {
349
514
  if (running) return;
350
515
  running = true;
351
516
  try {
352
- fn();
517
+ runEffect();
353
518
  } finally {
354
519
  running = false;
355
520
  }
356
521
  });
357
- return () => e.dispose();
522
+ const stop = () => {
523
+ if (cleanupFn) cleanupFn();
524
+ e.dispose();
525
+ };
526
+ if (_currentEffectScope) _currentEffectScope._cleanups.push(stop);
527
+ return stop;
358
528
  }
359
529
  /**
360
530
  * Registers a callback to run after the component is mounted.
@@ -463,10 +633,14 @@ function provide(key, value) {
463
633
  }
464
634
  /**
465
635
  * Injects a value provided by an ancestor component.
636
+ * Supports Vue 3's factory default pattern: `inject(key, () => expensiveDefault, true)`.
466
637
  */
467
- function inject(key, defaultValue) {
638
+ function inject(key, defaultValue, treatDefaultAsFactory) {
468
639
  const value = useContext(getOrCreateContext(key));
469
- return value !== void 0 ? value : defaultValue;
640
+ if (value !== void 0) return value;
641
+ if (defaultValue === void 0) return void 0;
642
+ if (treatDefaultAsFactory && typeof defaultValue === "function") return defaultValue();
643
+ return defaultValue;
470
644
  }
471
645
  /**
472
646
  * Defines a component using Vue 3 Composition API style.
@@ -475,7 +649,16 @@ function inject(key, defaultValue) {
475
649
  function defineComponent(options) {
476
650
  if (typeof options === "function") return options;
477
651
  const comp = (props) => {
478
- const result = options.setup(props);
652
+ const children = props.children;
653
+ const setupCtx = {
654
+ emit: (event, ...args) => {
655
+ const handler = props[`on${event.charAt(0).toUpperCase()}${event.slice(1)}`];
656
+ if (typeof handler === "function") handler(...args);
657
+ },
658
+ slots: { default: children !== void 0 ? (() => children) : void 0 },
659
+ attrs: props
660
+ };
661
+ const result = options.setup(props, setupCtx);
479
662
  if (typeof result === "function") return result();
480
663
  return result;
481
664
  };
@@ -483,16 +666,215 @@ function defineComponent(options) {
483
666
  return comp;
484
667
  }
485
668
  /**
669
+ * Defines an async component that lazily loads on first use.
670
+ * Supports both a bare loader function and an options object with
671
+ * loadingComponent, errorComponent, delay, and timeout.
672
+ *
673
+ * Returns a ComponentFn with a `__loading` property for Suspense integration.
674
+ */
675
+ function defineAsyncComponent(loader) {
676
+ const load = typeof loader === "function" ? loader : loader.loader;
677
+ const loaded = signal(null);
678
+ const error = signal(null);
679
+ let promise = null;
680
+ const startLoad = () => {
681
+ if (promise) return;
682
+ promise = load().then((mod) => loaded.set(mod.default), (err) => error.set(err instanceof Error ? err : new Error(String(err))));
683
+ };
684
+ const AsyncComp = ((props) => {
685
+ startLoad();
686
+ const err = error();
687
+ if (err) throw err;
688
+ const comp = loaded();
689
+ if (!comp) return null;
690
+ return comp(props);
691
+ });
692
+ AsyncComp.__loading = () => {
693
+ const isLoading = loaded() === null && error() === null;
694
+ if (isLoading) startLoad();
695
+ return isLoading;
696
+ };
697
+ return AsyncComp;
698
+ }
699
+ /**
486
700
  * Creates a Pyreon application instance — Vue 3 `createApp()` compatible.
487
701
  */
488
702
  function createApp(component, props) {
489
- return { mount(el) {
490
- const container = typeof el === "string" ? document.querySelector(el) : el;
491
- if (!container) throw new Error(`Cannot find mount target: ${el}`);
492
- return mount(pyreonH(component, props ?? null), container);
493
- } };
703
+ const provisions = [];
704
+ const app = {
705
+ mount(el) {
706
+ const container = typeof el === "string" ? document.querySelector(el) : el;
707
+ if (!container) throw new Error(`Cannot find mount target: ${el}`);
708
+ for (const { key, value } of provisions) {
709
+ const ctx = getOrCreateContext(key);
710
+ pushContext(new Map([[ctx.id, value]]));
711
+ }
712
+ return mount(pyreonH(component, props ?? null), container);
713
+ },
714
+ use(plugin) {
715
+ plugin.install(app);
716
+ return app;
717
+ },
718
+ provide(key, value) {
719
+ provisions.push({
720
+ key,
721
+ value
722
+ });
723
+ return app;
724
+ }
725
+ };
726
+ return app;
494
727
  }
728
+ /**
729
+ * Returns `true` if the value was created by `reactive()`.
730
+ */
731
+ function isReactive(value) {
732
+ return value !== null && typeof value === "object" && rawMap.has(value);
733
+ }
734
+ /**
735
+ * Returns `true` if the value was created by `readonly()`.
736
+ */
737
+ function isReadonly(value) {
738
+ return value !== null && typeof value === "object" && value[V_IS_READONLY] === true;
739
+ }
740
+ /**
741
+ * Returns `true` if the value is either reactive or readonly.
742
+ */
743
+ function isProxy(value) {
744
+ return isReactive(value) || isReadonly(value);
745
+ }
746
+ /**
747
+ * Marks an object so that `reactive()` will return it as-is (not wrapped).
748
+ */
749
+ function markRaw(obj) {
750
+ obj[V_SKIP] = true;
751
+ return obj;
752
+ }
753
+ let _currentEffectScope = null;
754
+ /**
755
+ * Creates an effect scope that collects reactive effects for grouped disposal.
756
+ *
757
+ * @param detached - If true, the scope is not collected by a parent scope.
758
+ */
759
+ function effectScope(detached) {
760
+ const cleanups = [];
761
+ let active = true;
762
+ const scope = {
763
+ get active() {
764
+ return active;
765
+ },
766
+ run(fn) {
767
+ if (!active) return void 0;
768
+ const prev = _currentEffectScope;
769
+ _currentEffectScope = scope;
770
+ try {
771
+ return fn();
772
+ } finally {
773
+ _currentEffectScope = prev;
774
+ }
775
+ },
776
+ stop() {
777
+ if (!active) return;
778
+ active = false;
779
+ for (const fn of cleanups) fn();
780
+ cleanups.length = 0;
781
+ }
782
+ };
783
+ if (!detached && _currentEffectScope) {
784
+ const parentCleanups = _currentEffectScope._cleanups;
785
+ if (parentCleanups) parentCleanups.push(() => scope.stop());
786
+ }
787
+ scope._cleanups = cleanups;
788
+ return scope;
789
+ }
790
+ /**
791
+ * Returns the current active effect scope, or undefined if none.
792
+ */
793
+ function getCurrentScope() {
794
+ return _currentEffectScope ?? void 0;
795
+ }
796
+ /**
797
+ * Registers a cleanup function on the current effect scope.
798
+ */
799
+ function onScopeDispose(fn) {
800
+ if (_currentEffectScope) _currentEffectScope._cleanups.push(fn);
801
+ }
802
+ /**
803
+ * Registers an error capture handler.
804
+ * No direct equivalent in Pyreon — stored but not actively used.
805
+ */
806
+ function onErrorCaptured(fn) {
807
+ const ctx = getCurrentCtx();
808
+ if (ctx) {
809
+ const idx = getHookIndex();
810
+ if (idx < ctx.hooks.length) return;
811
+ ctx.hooks[idx] = fn;
812
+ }
813
+ }
814
+ /**
815
+ * Dev-only lifecycle hook — no-op in Pyreon.
816
+ */
817
+ function onRenderTracked(_fn) {}
818
+ /**
819
+ * Dev-only lifecycle hook — no-op in Pyreon.
820
+ */
821
+ function onRenderTriggered(_fn) {}
822
+ /**
823
+ * Teleport — renders children into a different DOM element.
824
+ * Maps to Pyreon's Portal.
825
+ */
826
+ function Teleport(props) {
827
+ const target = typeof props.to === "string" ? document.querySelector(props.to) : props.to;
828
+ if (!target) return props.children ?? null;
829
+ return Portal({
830
+ target,
831
+ children: props.children ?? null
832
+ });
833
+ }
834
+ /**
835
+ * KeepAlive — not supported in Pyreon. Renders children as-is.
836
+ */
837
+ function KeepAlive(props) {
838
+ return props.children ?? null;
839
+ }
840
+ /**
841
+ * Runs a watchEffect that flushes after DOM updates.
842
+ * In Pyreon, same as `watchEffect()`.
843
+ */
844
+ function watchPostEffect(fn) {
845
+ return watchEffect(fn);
846
+ }
847
+ /**
848
+ * Runs a watchEffect that flushes synchronously.
849
+ * In Pyreon, same as `watchEffect()`.
850
+ */
851
+ function watchSyncEffect(fn) {
852
+ return watchEffect(fn);
853
+ }
854
+ /**
855
+ * Creates a customized ref with explicit control over dependency tracking
856
+ * and update triggering.
857
+ */
858
+ function customRef(factory) {
859
+ const s = signal(0);
860
+ const { get, set } = factory(() => {
861
+ s();
862
+ }, () => s.set(s.peek() + 1));
863
+ return {
864
+ [V_IS_REF]: true,
865
+ get value() {
866
+ return get();
867
+ },
868
+ set value(v) {
869
+ set(v);
870
+ }
871
+ };
872
+ }
873
+ /**
874
+ * Compatibility version string — indicates Vue 3 API compatibility.
875
+ */
876
+ const version = "3.5.0-pyreon";
495
877
 
496
878
  //#endregion
497
- export { Fragment, batch, computed, createApp, defineComponent, pyreonH as h, inject, isRef, nextTick, onBeforeMount, onBeforeUnmount, onMounted, onUnmounted, onUpdated, provide, reactive, readonly, ref, shallowReactive, shallowRef, toRaw, toRef, toRefs, triggerRef, unref, watch, watchEffect };
879
+ export { Fragment, KeepAlive, Teleport, batch, computed, createApp, customRef, defineAsyncComponent, defineComponent, effectScope, getCurrentScope, pyreonH as h, inject, isProxy, isReactive, isReadonly, isRef, markRaw, nextTick, onBeforeMount, onBeforeUnmount, onErrorCaptured, onMounted, onRenderTracked, onRenderTriggered, onScopeDispose, onUnmounted, onUpdated, provide, reactive, readonly, ref, shallowReactive, shallowReadonly, shallowRef, toRaw, toRef, toRefs, toValue, triggerRef, unref, version, watch, watchEffect, watchPostEffect, watchSyncEffect };
498
880
  //# sourceMappingURL=index.js.map
@@ -1,4 +1,4 @@
1
- import { Fragment, h, onUnmount } from "@pyreon/core";
1
+ import { Fragment, h, isNativeCompat, onUnmount } from "@pyreon/core";
2
2
  import { runUntracked, signal } from "@pyreon/reactivity";
3
3
 
4
4
  //#region src/jsx-runtime.ts
@@ -80,10 +80,23 @@ function jsx(type, props, key) {
80
80
  ...rest,
81
81
  key
82
82
  } : rest;
83
- if (typeof type === "function") return h(wrapCompatComponent(type), children !== void 0 ? {
84
- ...propsWithKey,
85
- children
86
- } : propsWithKey);
83
+ if (typeof type === "function") {
84
+ const componentProps = children !== void 0 ? {
85
+ ...propsWithKey,
86
+ children
87
+ } : propsWithKey;
88
+ if (isNativeCompat(type)) return h(type, componentProps);
89
+ return h(wrapCompatComponent(type), componentProps);
90
+ }
91
+ if (typeof type === "string" && propsWithKey.ref != null) {
92
+ const r = propsWithKey.ref;
93
+ if (typeof r === "object" && r !== null && r[Symbol.for("__v_isRef")] === true) {
94
+ const vueRef = r;
95
+ propsWithKey.ref = (el) => {
96
+ vueRef.value = el;
97
+ };
98
+ }
99
+ }
87
100
  return h(type, propsWithKey, ...children === void 0 ? [] : Array.isArray(children) ? children : [children]);
88
101
  }
89
102
  const jsxs = jsx;