@sigx/runtime-terminal 0.1.3 → 0.1.5

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.js CHANGED
@@ -1,10 +1,18 @@
1
1
  //#region ../runtime-core/src/plugins.ts
2
- const plugins = [];
2
+ const plugins$1 = [];
3
3
  /**
4
4
  * Get all registered plugins (internal use)
5
5
  */
6
- function getComponentPlugins() {
7
- return plugins;
6
+ function getComponentPlugins$1() {
7
+ return plugins$1;
8
+ }
9
+ const contextExtensions = [];
10
+ /**
11
+ * Apply all registered context extensions to a context object.
12
+ * Called internally by the renderer when creating component contexts.
13
+ */
14
+ function applyContextExtensions(ctx) {
15
+ for (const extension of contextExtensions) extension(ctx);
8
16
  }
9
17
 
10
18
  //#endregion
@@ -119,7 +127,7 @@ function onCleanup(fn) {
119
127
  if (currentComponentContext) currentComponentContext.onCleanup(fn);
120
128
  else console.warn("onCleanup called outside of component setup");
121
129
  }
122
- const componentRegistry = /* @__PURE__ */ new Map();
130
+ const componentRegistry$1 = /* @__PURE__ */ new Map();
123
131
  /**
124
132
  * Define a component. Returns a JSX factory function.
125
133
  *
@@ -159,11 +167,11 @@ function defineComponent(setup, options) {
159
167
  factory.__events = null;
160
168
  factory.__ref = null;
161
169
  factory.__slots = null;
162
- componentRegistry.set(factory, {
170
+ componentRegistry$1.set(factory, {
163
171
  name: options?.name,
164
172
  setup
165
173
  });
166
- getComponentPlugins().forEach((p) => p.onDefine?.(options?.name, factory, setup));
174
+ getComponentPlugins$1().forEach((p) => p.onDefine?.(options?.name, factory, setup));
167
175
  return factory;
168
176
  }
169
177
 
@@ -186,15 +194,17 @@ function batch(fn) {
186
194
  }
187
195
  }
188
196
  function runEffect(fn) {
189
- const effect$1 = function() {
190
- cleanup(effect$1);
191
- activeEffect = effect$1;
197
+ const effectFn = function() {
198
+ cleanup(effectFn);
199
+ activeEffect = effectFn;
192
200
  fn();
193
201
  activeEffect = null;
194
202
  };
195
- effect$1.deps = [];
196
- effect$1();
197
- return () => cleanup(effect$1);
203
+ effectFn.deps = [];
204
+ effectFn();
205
+ const runner = (() => effectFn());
206
+ runner.stop = () => cleanup(effectFn);
207
+ return runner;
198
208
  }
199
209
  function cleanup(effect$1) {
200
210
  if (!effect$1.deps) return;
@@ -330,6 +340,292 @@ const arrayInstrumentations = {};
330
340
  const Fragment$1 = Symbol.for("sigx.Fragment");
331
341
  const Text$1 = Symbol.for("sigx.Text");
332
342
 
343
+ //#endregion
344
+ //#region ../runtime-core/src/lazy.tsx
345
+ /**
346
+ * Lazy loading utilities for sigx components.
347
+ *
348
+ * Provides runtime-only lazy loading with no build dependencies.
349
+ * Works with any bundler that supports dynamic import().
350
+ */
351
+ let currentSuspenseBoundary$1 = null;
352
+ /**
353
+ * Register a promise with the current Suspense boundary
354
+ * @internal
355
+ */
356
+ function registerPendingPromise$1(promise) {
357
+ const boundary = currentSuspenseBoundary$1;
358
+ if (boundary) {
359
+ boundary.pending.add(promise);
360
+ promise.finally(() => {
361
+ boundary.pending.delete(promise);
362
+ if (boundary.pending.size === 0) boundary.onResolve();
363
+ });
364
+ return true;
365
+ }
366
+ return false;
367
+ }
368
+ /**
369
+ * Suspense boundary component for handling async loading states.
370
+ *
371
+ * Wraps lazy-loaded components and shows a fallback while they load.
372
+ *
373
+ * @example
374
+ * ```tsx
375
+ * import { lazy, Suspense } from 'sigx';
376
+ *
377
+ * const LazyDashboard = lazy(() => import('./Dashboard'));
378
+ *
379
+ * // Basic usage
380
+ * <Suspense fallback={<div>Loading...</div>}>
381
+ * <LazyDashboard />
382
+ * </Suspense>
383
+ *
384
+ * // With spinner component
385
+ * <Suspense fallback={<Spinner size="large" />}>
386
+ * <LazyDashboard />
387
+ * <LazyCharts />
388
+ * </Suspense>
389
+ * ```
390
+ */
391
+ const Suspense$1 = defineComponent((ctx) => {
392
+ const { props, slots } = ctx;
393
+ const state = ctx.signal({
394
+ isReady: false,
395
+ pendingCount: 0
396
+ });
397
+ const boundary = {
398
+ pending: /* @__PURE__ */ new Set(),
399
+ onResolve: () => {
400
+ state.pendingCount = boundary.pending.size;
401
+ if (boundary.pending.size === 0) state.isReady = true;
402
+ }
403
+ };
404
+ ctx.onMount(() => {
405
+ if (boundary.pending.size === 0) state.isReady = true;
406
+ });
407
+ return () => {
408
+ state.isReady;
409
+ state.pendingCount;
410
+ const prevBoundary = currentSuspenseBoundary$1;
411
+ currentSuspenseBoundary$1 = boundary;
412
+ try {
413
+ const children = slots.default();
414
+ if (boundary.pending.size > 0) {
415
+ const fallback = props.fallback;
416
+ if (typeof fallback === "function") return fallback();
417
+ return fallback ?? null;
418
+ }
419
+ if (Array.isArray(children)) {
420
+ const filtered = children.filter((c) => c != null && c !== false && c !== true);
421
+ if (filtered.length === 0) return null;
422
+ if (filtered.length === 1) return filtered[0];
423
+ return filtered;
424
+ }
425
+ return children;
426
+ } catch (err) {
427
+ if (err instanceof Promise) {
428
+ registerPendingPromise$1(err);
429
+ const fallback = props.fallback;
430
+ if (typeof fallback === "function") return fallback();
431
+ return fallback ?? null;
432
+ }
433
+ throw err;
434
+ } finally {
435
+ currentSuspenseBoundary$1 = prevBoundary;
436
+ }
437
+ };
438
+ }, { name: "Suspense" });
439
+
440
+ //#endregion
441
+ //#region ../runtime-core/src/utils/props-accessor.ts
442
+ /**
443
+ * Creates a props accessor that can be called with defaults or accessed directly.
444
+ * After calling with defaults, direct property access uses those defaults.
445
+ *
446
+ * @example
447
+ * ```ts
448
+ * // In component setup:
449
+ * const props = createPropsAccessor(reactiveProps);
450
+ *
451
+ * // Set defaults
452
+ * props({ count: 0, label: 'Default' });
453
+ *
454
+ * // Access props (falls back to defaults if not provided)
455
+ * const count = props.count;
456
+ * ```
457
+ */
458
+ function createPropsAccessor(reactiveProps) {
459
+ let defaults = {};
460
+ const proxy = new Proxy(function propsAccessor() {}, {
461
+ get(_, key) {
462
+ if (typeof key === "symbol") return void 0;
463
+ const value = reactiveProps[key];
464
+ return value != null ? value : defaults[key];
465
+ },
466
+ apply(_, __, args) {
467
+ if (args[0] && typeof args[0] === "object") defaults = {
468
+ ...defaults,
469
+ ...args[0]
470
+ };
471
+ return proxy;
472
+ },
473
+ has(_, key) {
474
+ if (typeof key === "symbol") return false;
475
+ return key in reactiveProps || key in defaults;
476
+ },
477
+ ownKeys() {
478
+ return [...new Set([...Object.keys(reactiveProps), ...Object.keys(defaults)])];
479
+ },
480
+ getOwnPropertyDescriptor(_, key) {
481
+ if (typeof key === "symbol") return void 0;
482
+ if (key in reactiveProps || key in defaults) return {
483
+ enumerable: true,
484
+ configurable: true,
485
+ writable: false
486
+ };
487
+ }
488
+ });
489
+ return proxy;
490
+ }
491
+
492
+ //#endregion
493
+ //#region ../runtime-core/src/utils/slots.ts
494
+ /**
495
+ * Slots system for component children.
496
+ * Supports default and named slots with reactivity.
497
+ */
498
+ /**
499
+ * Create slots object from children and slots prop.
500
+ * Uses a version signal to trigger re-renders when children change.
501
+ *
502
+ * Supports named slots via:
503
+ * - `slots` prop object (e.g., `slots={{ header: () => <div>...</div> }}`)
504
+ * - `slot` prop on children (e.g., `<div slot="header">...</div>`)
505
+ *
506
+ * @example
507
+ * ```tsx
508
+ * // Parent component
509
+ * <Card slots={{ header: () => <h1>Title</h1> }}>
510
+ * <p>Default content</p>
511
+ * <span slot="footer">Footer text</span>
512
+ * </Card>
513
+ *
514
+ * // Card component setup
515
+ * const slots = createSlots(children, slotsFromProps);
516
+ * return () => (
517
+ * <div>
518
+ * {slots.header()}
519
+ * {slots.default()}
520
+ * {slots.footer()}
521
+ * </div>
522
+ * );
523
+ * ```
524
+ */
525
+ function createSlots(children, slotsFromProps) {
526
+ const versionSignal = signal({ v: 0 });
527
+ function extractNamedSlotsFromChildren(c) {
528
+ const defaultChildren = [];
529
+ const namedSlots = {};
530
+ if (c == null) return {
531
+ defaultChildren,
532
+ namedSlots
533
+ };
534
+ const items = Array.isArray(c) ? c : [c];
535
+ for (const child of items) if (child && typeof child === "object" && child.props && child.props.slot) {
536
+ const slotName = child.props.slot;
537
+ if (!namedSlots[slotName]) namedSlots[slotName] = [];
538
+ namedSlots[slotName].push(child);
539
+ } else defaultChildren.push(child);
540
+ return {
541
+ defaultChildren,
542
+ namedSlots
543
+ };
544
+ }
545
+ const slotsObj = {
546
+ _children: children,
547
+ _slotsFromProps: slotsFromProps || {},
548
+ _version: versionSignal,
549
+ _isPatching: false,
550
+ default: function() {
551
+ this._version.v;
552
+ const c = this._children;
553
+ const { defaultChildren } = extractNamedSlotsFromChildren(c);
554
+ return defaultChildren.filter((child) => child != null && child !== false && child !== true);
555
+ }
556
+ };
557
+ return new Proxy(slotsObj, { get(target, prop) {
558
+ if (prop in target) return target[prop];
559
+ if (typeof prop === "string") return function(scopedProps) {
560
+ target._version.v;
561
+ if (target._slotsFromProps && typeof target._slotsFromProps[prop] === "function") {
562
+ const result = target._slotsFromProps[prop](scopedProps);
563
+ if (result == null) return [];
564
+ return Array.isArray(result) ? result : [result];
565
+ }
566
+ const { namedSlots } = extractNamedSlotsFromChildren(target._children);
567
+ return namedSlots[prop] || [];
568
+ };
569
+ } });
570
+ }
571
+
572
+ //#endregion
573
+ //#region ../runtime-core/src/utils/normalize.ts
574
+ /**
575
+ * VNode normalization utilities.
576
+ * Converts render results into proper VNode structures.
577
+ */
578
+ /**
579
+ * Normalize render result to a VNode (wrapping arrays in Fragment).
580
+ * Handles null, undefined, false, true by returning an empty Text node.
581
+ *
582
+ * This is used to normalize the return value of component render functions
583
+ * into a consistent VNode structure for the renderer to process.
584
+ *
585
+ * @example
586
+ * ```ts
587
+ * // Conditional rendering returns null/false
588
+ * normalizeSubTree(null) // → empty Text node
589
+ * normalizeSubTree(false) // → empty Text node
590
+ *
591
+ * // Arrays become Fragments
592
+ * normalizeSubTree([<A/>, <B/>]) // → Fragment with children
593
+ *
594
+ * // Primitives become Text nodes
595
+ * normalizeSubTree("hello") // → Text node
596
+ * normalizeSubTree(42) // → Text node
597
+ *
598
+ * // VNodes pass through
599
+ * normalizeSubTree(<div/>) // → same VNode
600
+ * ```
601
+ */
602
+ function normalizeSubTree(result) {
603
+ if (result == null || result === false || result === true) return {
604
+ type: Text$1,
605
+ props: {},
606
+ key: null,
607
+ children: [],
608
+ dom: null,
609
+ text: ""
610
+ };
611
+ if (Array.isArray(result)) return {
612
+ type: Fragment$1,
613
+ props: {},
614
+ key: null,
615
+ children: result,
616
+ dom: null
617
+ };
618
+ if (typeof result === "string" || typeof result === "number") return {
619
+ type: Text$1,
620
+ props: {},
621
+ key: null,
622
+ children: [],
623
+ dom: null,
624
+ text: result
625
+ };
626
+ return result;
627
+ }
628
+
333
629
  //#endregion
334
630
  //#region ../runtime-core/src/renderer.ts
335
631
  /**
@@ -340,7 +636,6 @@ function isComponent(type) {
340
636
  }
341
637
  function createRenderer(options) {
342
638
  const { insert: hostInsert, remove: hostRemove, patchProp: hostPatchProp, createElement: hostCreateElement, createText: hostCreateText, createComment: hostCreateComment, setText: hostSetText, setElementText: hostSetElementText, parentNode: hostParentNode, nextSibling: hostNextSibling, cloneNode: hostCloneNode, insertStaticContent: hostInsertStaticContent } = options;
343
- let isPatching = false;
344
639
  let currentAppContext = null;
345
640
  function render$1(element, container, appContext) {
346
641
  if (appContext) currentAppContext = appContext;
@@ -354,6 +649,13 @@ function createRenderer(options) {
354
649
  dom: null,
355
650
  text: element
356
651
  };
652
+ else if (isComponent(element)) vnode = {
653
+ type: element,
654
+ props: {},
655
+ key: null,
656
+ children: [],
657
+ dom: null
658
+ };
357
659
  else vnode = element;
358
660
  if (vnode) {
359
661
  if (oldVNode) patch(oldVNode, vnode, container);
@@ -365,6 +667,7 @@ function createRenderer(options) {
365
667
  }
366
668
  }
367
669
  function mount(vnode, container, before = null) {
670
+ if (vnode == null || vnode === false || vnode === true) return;
368
671
  if (vnode.type === Text$1) {
369
672
  const node = hostCreateText(String(vnode.text));
370
673
  vnode.dom = node;
@@ -376,7 +679,7 @@ function createRenderer(options) {
376
679
  const anchor = hostCreateComment("");
377
680
  vnode.dom = anchor;
378
681
  hostInsert(anchor, container, before);
379
- vnode.children.forEach((child) => mount(child, container, anchor));
682
+ if (vnode.children) vnode.children.forEach((child) => mount(child, container, anchor));
380
683
  return;
381
684
  }
382
685
  if (isComponent(vnode.type)) {
@@ -393,17 +696,18 @@ function createRenderer(options) {
393
696
  else if (typeof vnode.props.ref === "object") vnode.props.ref.current = element;
394
697
  }
395
698
  }
396
- vnode.children.forEach((child) => {
699
+ if (vnode.children) vnode.children.forEach((child) => {
397
700
  child.parent = vnode;
398
701
  mount(child, element);
399
702
  });
400
703
  hostInsert(element, container, before);
401
704
  }
402
705
  function unmount(vnode, container) {
403
- if (vnode._effect) vnode._effect();
706
+ const internalVNode = vnode;
707
+ if (internalVNode._effect) internalVNode._effect.stop();
404
708
  if (vnode.cleanup) vnode.cleanup();
405
709
  if (isComponent(vnode.type)) {
406
- const subTree = vnode._subTree;
710
+ const subTree = internalVNode._subTree;
407
711
  if (subTree) unmount(subTree, container);
408
712
  if (vnode.dom) hostRemove(vnode.dom);
409
713
  if (vnode.props?.ref) {
@@ -413,7 +717,7 @@ function createRenderer(options) {
413
717
  return;
414
718
  }
415
719
  if (vnode.type === Fragment$1) {
416
- vnode.children.forEach((child) => unmount(child, container));
720
+ if (vnode.children) vnode.children.forEach((child) => unmount(child, container));
417
721
  if (vnode.dom) hostRemove(vnode.dom);
418
722
  return;
419
723
  }
@@ -433,13 +737,15 @@ function createRenderer(options) {
433
737
  mount(newVNode, parent, nextSibling);
434
738
  return;
435
739
  }
436
- if (oldVNode._effect) {
740
+ const oldInternal = oldVNode;
741
+ const newInternal = newVNode;
742
+ if (oldInternal._effect) {
437
743
  newVNode.dom = oldVNode.dom;
438
- newVNode._effect = oldVNode._effect;
439
- newVNode._subTree = oldVNode._subTree;
440
- newVNode._slots = oldVNode._slots;
441
- const props = oldVNode._componentProps;
442
- newVNode._componentProps = props;
744
+ newInternal._effect = oldInternal._effect;
745
+ newInternal._subTree = oldInternal._subTree;
746
+ newInternal._slots = oldInternal._slots;
747
+ const props = oldInternal._componentProps;
748
+ newInternal._componentProps = props;
443
749
  if (props) {
444
750
  const newProps$1 = newVNode.props || {};
445
751
  untrack(() => {
@@ -449,20 +755,20 @@ function createRenderer(options) {
449
755
  for (const key in props) if (!(key in newProps$1) && key !== "children" && key !== "key" && key !== "ref") delete props[key];
450
756
  });
451
757
  }
452
- const slotsRef = oldVNode._slots;
758
+ const slotsRef = oldInternal._slots;
453
759
  const newChildren = newVNode.props?.children;
454
760
  const newSlotsFromProps = newVNode.props?.slots;
455
761
  if (slotsRef) {
456
762
  if (newChildren !== void 0) slotsRef._children = newChildren;
457
763
  if (newSlotsFromProps !== void 0) slotsRef._slotsFromProps = newSlotsFromProps;
458
- if (!isPatching) {
459
- isPatching = true;
764
+ if (!slotsRef._isPatching) {
765
+ slotsRef._isPatching = true;
460
766
  try {
461
767
  untrack(() => {
462
768
  slotsRef._version.v++;
463
769
  });
464
770
  } finally {
465
- isPatching = false;
771
+ slotsRef._isPatching = false;
466
772
  }
467
773
  }
468
774
  }
@@ -576,9 +882,10 @@ function createRenderer(options) {
576
882
  let exposeCalled = false;
577
883
  const { children, slots: slotsFromProps, ...propsData } = vnode.props || {};
578
884
  const reactiveProps = signal(propsData);
579
- vnode._componentProps = reactiveProps;
885
+ const internalVNode = vnode;
886
+ internalVNode._componentProps = reactiveProps;
580
887
  const slots = createSlots(children, slotsFromProps);
581
- vnode._slots = slots;
888
+ internalVNode._slots = slots;
582
889
  const mountHooks = [];
583
890
  const cleanupHooks = [];
584
891
  const parentInstance = getCurrentInstance();
@@ -586,7 +893,7 @@ function createRenderer(options) {
586
893
  const ctx = {
587
894
  el: container,
588
895
  signal,
589
- props: reactiveProps,
896
+ props: createPropsAccessor(reactiveProps),
590
897
  slots,
591
898
  emit: (event, ...args) => {
592
899
  const handler = reactiveProps[`on${event[0].toUpperCase() + event.slice(1)}`];
@@ -602,8 +909,11 @@ function createRenderer(options) {
602
909
  expose: (exposedValue) => {
603
910
  exposed = exposedValue;
604
911
  exposeCalled = true;
605
- }
912
+ },
913
+ renderFn: null,
914
+ update: () => {}
606
915
  };
916
+ applyContextExtensions(ctx);
607
917
  ctx.__name = componentName;
608
918
  if (currentAppContext) ctx._appContext = currentAppContext;
609
919
  const componentInstance = {
@@ -614,7 +924,9 @@ function createRenderer(options) {
614
924
  const prev = setCurrentInstance(ctx);
615
925
  let renderFn;
616
926
  try {
617
- renderFn = setup(ctx);
927
+ const setupResult = setup(ctx);
928
+ if (setupResult && typeof setupResult.then === "function") throw new Error(`Async setup in component "${componentName}" is only supported during SSR. On the client, use pre-loaded data from hydration or fetch in onMount.`);
929
+ renderFn = setupResult;
618
930
  notifyComponentCreated(currentAppContext, componentInstance);
619
931
  } catch (err) {
620
932
  if (!handleComponentError(currentAppContext, err, componentInstance, "setup")) throw err;
@@ -626,24 +938,31 @@ function createRenderer(options) {
626
938
  if (typeof vnode.props.ref === "function") vnode.props.ref(refValue);
627
939
  else if (vnode.props.ref && typeof vnode.props.ref === "object") vnode.props.ref.current = refValue;
628
940
  }
629
- if (renderFn) vnode._effect = effect(() => {
630
- const prevInstance = setCurrentInstance(ctx);
631
- try {
632
- const subTreeResult = renderFn();
633
- if (subTreeResult == null) return;
634
- const subTree = normalizeSubTree(subTreeResult);
635
- const prevSubTree = vnode._subTree;
636
- if (prevSubTree) {
637
- patch(prevSubTree, subTree, container);
638
- notifyComponentUpdated(currentAppContext, componentInstance);
639
- } else mount(subTree, container, anchor);
640
- vnode._subTree = subTree;
641
- } catch (err) {
642
- if (!handleComponentError(currentAppContext, err, componentInstance, "render")) throw err;
643
- } finally {
644
- setCurrentInstance(prevInstance);
645
- }
646
- });
941
+ if (renderFn) {
942
+ ctx.renderFn = renderFn;
943
+ const componentEffect = effect(() => {
944
+ const prevInstance = setCurrentInstance(ctx);
945
+ try {
946
+ const subTreeResult = ctx.renderFn();
947
+ if (subTreeResult == null) return;
948
+ const subTree = normalizeSubTree(subTreeResult);
949
+ const prevSubTree = internalVNode._subTree;
950
+ if (prevSubTree) {
951
+ patch(prevSubTree, subTree, container);
952
+ notifyComponentUpdated(currentAppContext, componentInstance);
953
+ } else mount(subTree, container, anchor);
954
+ internalVNode._subTree = subTree;
955
+ } catch (err) {
956
+ if (!handleComponentError(currentAppContext, err, componentInstance, "render")) throw err;
957
+ } finally {
958
+ setCurrentInstance(prevInstance);
959
+ }
960
+ });
961
+ internalVNode._effect = componentEffect;
962
+ ctx.update = () => {
963
+ componentEffect();
964
+ };
965
+ }
647
966
  const mountCtx = { el: container };
648
967
  mountHooks.forEach((hook) => hook(mountCtx));
649
968
  notifyComponentMounted(currentAppContext, componentInstance);
@@ -652,81 +971,12 @@ function createRenderer(options) {
652
971
  cleanupHooks.forEach((hook) => hook(mountCtx));
653
972
  };
654
973
  }
655
- /**
656
- * Create slots object from children and slots prop.
657
- * Uses a version signal to trigger re-renders when children change.
658
- * Supports named slots via:
659
- * - `slots` prop object (e.g., slots={{ header: () => <div>...</div> }})
660
- * - `slot` prop on children (e.g., <div slot="header">...</div>)
661
- */
662
- function createSlots(children, slotsFromProps) {
663
- const versionSignal = signal({ v: 0 });
664
- function extractNamedSlotsFromChildren(c) {
665
- const defaultChildren = [];
666
- const namedSlots = {};
667
- if (c == null) return {
668
- defaultChildren,
669
- namedSlots
670
- };
671
- const items = Array.isArray(c) ? c : [c];
672
- for (const child of items) if (child && typeof child === "object" && child.props && child.props.slot) {
673
- const slotName = child.props.slot;
674
- if (!namedSlots[slotName]) namedSlots[slotName] = [];
675
- namedSlots[slotName].push(child);
676
- } else defaultChildren.push(child);
677
- return {
678
- defaultChildren,
679
- namedSlots
680
- };
681
- }
682
- const slotsObj = {
683
- _children: children,
684
- _slotsFromProps: slotsFromProps || {},
685
- _version: versionSignal,
686
- default: function() {
687
- this._version.v;
688
- const c = this._children;
689
- const { defaultChildren } = extractNamedSlotsFromChildren(c);
690
- return defaultChildren;
691
- }
692
- };
693
- return new Proxy(slotsObj, { get(target, prop) {
694
- if (prop in target) return target[prop];
695
- if (typeof prop === "string") return function(scopedProps) {
696
- target._version.v;
697
- if (target._slotsFromProps && typeof target._slotsFromProps[prop] === "function") {
698
- const result = target._slotsFromProps[prop](scopedProps);
699
- if (result == null) return [];
700
- return Array.isArray(result) ? result : [result];
701
- }
702
- const { namedSlots } = extractNamedSlotsFromChildren(target._children);
703
- return namedSlots[prop] || [];
704
- };
705
- } });
706
- }
707
- /**
708
- * Normalize render result to a VNode (wrapping arrays in Fragment)
709
- */
710
- function normalizeSubTree(result) {
711
- if (Array.isArray(result)) return {
712
- type: Fragment$1,
713
- props: {},
714
- key: null,
715
- children: result,
716
- dom: null
717
- };
718
- if (typeof result === "string" || typeof result === "number") return {
719
- type: Text$1,
720
- props: {},
721
- key: null,
722
- children: [],
723
- dom: null,
724
- text: result
725
- };
726
- return result;
727
- }
728
974
  return {
729
975
  render: render$1,
976
+ patch,
977
+ mount,
978
+ unmount,
979
+ mountComponent,
730
980
  createApp: (rootComponent) => {
731
981
  return { mount(selectorOrContainer) {
732
982
  let container = null;
@@ -811,6 +1061,60 @@ let platformSyncProcessor = null;
811
1061
  function getPlatformSyncProcessor() {
812
1062
  return platformSyncProcessor;
813
1063
  }
1064
+ const plugins = [];
1065
+ /**
1066
+ * Get all registered plugins (internal use)
1067
+ */
1068
+ function getComponentPlugins() {
1069
+ return plugins;
1070
+ }
1071
+ const componentRegistry = /* @__PURE__ */ new Map();
1072
+ /**
1073
+ * Define a component. Returns a JSX factory function.
1074
+ *
1075
+ * @param setup - Setup function that receives context and returns a render function
1076
+ * @param options - Optional configuration (e.g., name for DevTools)
1077
+ *
1078
+ * @example
1079
+ * ```tsx
1080
+ * type CardProps = DefineProp<"title", string> & DefineSlot<"header">;
1081
+ *
1082
+ * export const Card = defineComponent<CardProps>((ctx) => {
1083
+ * const { title } = ctx.props;
1084
+ * const { slots } = ctx;
1085
+ *
1086
+ * return () => (
1087
+ * <div class="card">
1088
+ * {slots.header?.() ?? <h2>{title}</h2>}
1089
+ * {slots.default()}
1090
+ * </div>
1091
+ * );
1092
+ * });
1093
+ * ```
1094
+ */
1095
+ function defineComponent$1(setup, options) {
1096
+ const factory = function(props) {
1097
+ return {
1098
+ type: factory,
1099
+ props: props || {},
1100
+ key: props?.key || null,
1101
+ children: [],
1102
+ dom: null
1103
+ };
1104
+ };
1105
+ factory.__setup = setup;
1106
+ factory.__name = options?.name;
1107
+ factory.__props = null;
1108
+ factory.__events = null;
1109
+ factory.__ref = null;
1110
+ factory.__slots = null;
1111
+ componentRegistry.set(factory, {
1112
+ name: options?.name,
1113
+ setup
1114
+ });
1115
+ getComponentPlugins().forEach((p) => p.onDefine?.(options?.name, factory, setup));
1116
+ return factory;
1117
+ }
814
1118
  const Fragment = Symbol.for("sigx.Fragment");
815
1119
  const Text = Symbol.for("sigx.Text");
816
1120
  function normalizeChildren(children) {
@@ -905,6 +1209,100 @@ function jsx(type, props, key) {
905
1209
  function jsxs(type, props, key) {
906
1210
  return jsx(type, props, key);
907
1211
  }
1212
+ /**
1213
+ * Lazy loading utilities for sigx components.
1214
+ *
1215
+ * Provides runtime-only lazy loading with no build dependencies.
1216
+ * Works with any bundler that supports dynamic import().
1217
+ */
1218
+ let currentSuspenseBoundary = null;
1219
+ /**
1220
+ * Register a promise with the current Suspense boundary
1221
+ * @internal
1222
+ */
1223
+ function registerPendingPromise(promise) {
1224
+ const boundary = currentSuspenseBoundary;
1225
+ if (boundary) {
1226
+ boundary.pending.add(promise);
1227
+ promise.finally(() => {
1228
+ boundary.pending.delete(promise);
1229
+ if (boundary.pending.size === 0) boundary.onResolve();
1230
+ });
1231
+ return true;
1232
+ }
1233
+ return false;
1234
+ }
1235
+ /**
1236
+ * Suspense boundary component for handling async loading states.
1237
+ *
1238
+ * Wraps lazy-loaded components and shows a fallback while they load.
1239
+ *
1240
+ * @example
1241
+ * ```tsx
1242
+ * import { lazy, Suspense } from 'sigx';
1243
+ *
1244
+ * const LazyDashboard = lazy(() => import('./Dashboard'));
1245
+ *
1246
+ * // Basic usage
1247
+ * <Suspense fallback={<div>Loading...</div>}>
1248
+ * <LazyDashboard />
1249
+ * </Suspense>
1250
+ *
1251
+ * // With spinner component
1252
+ * <Suspense fallback={<Spinner size="large" />}>
1253
+ * <LazyDashboard />
1254
+ * <LazyCharts />
1255
+ * </Suspense>
1256
+ * ```
1257
+ */
1258
+ const Suspense = defineComponent$1((ctx) => {
1259
+ const { props, slots } = ctx;
1260
+ const state = ctx.signal({
1261
+ isReady: false,
1262
+ pendingCount: 0
1263
+ });
1264
+ const boundary = {
1265
+ pending: /* @__PURE__ */ new Set(),
1266
+ onResolve: () => {
1267
+ state.pendingCount = boundary.pending.size;
1268
+ if (boundary.pending.size === 0) state.isReady = true;
1269
+ }
1270
+ };
1271
+ ctx.onMount(() => {
1272
+ if (boundary.pending.size === 0) state.isReady = true;
1273
+ });
1274
+ return () => {
1275
+ state.isReady;
1276
+ state.pendingCount;
1277
+ const prevBoundary = currentSuspenseBoundary;
1278
+ currentSuspenseBoundary = boundary;
1279
+ try {
1280
+ const children = slots.default();
1281
+ if (boundary.pending.size > 0) {
1282
+ const fallback = props.fallback;
1283
+ if (typeof fallback === "function") return fallback();
1284
+ return fallback ?? null;
1285
+ }
1286
+ if (Array.isArray(children)) {
1287
+ const filtered = children.filter((c) => c != null && c !== false && c !== true);
1288
+ if (filtered.length === 0) return null;
1289
+ if (filtered.length === 1) return filtered[0];
1290
+ return filtered;
1291
+ }
1292
+ return children;
1293
+ } catch (err) {
1294
+ if (err instanceof Promise) {
1295
+ registerPendingPromise(err);
1296
+ const fallback = props.fallback;
1297
+ if (typeof fallback === "function") return fallback();
1298
+ return fallback ?? null;
1299
+ }
1300
+ throw err;
1301
+ } finally {
1302
+ currentSuspenseBoundary = prevBoundary;
1303
+ }
1304
+ };
1305
+ }, { name: "Suspense" });
908
1306
 
909
1307
  //#endregion
910
1308
  //#region src/components/Input.tsx