@sigx/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
@@ -16,15 +16,17 @@ function batch(fn) {
16
16
  }
17
17
  }
18
18
  function runEffect(fn) {
19
- const effect$1 = function() {
20
- cleanup(effect$1);
21
- activeEffect = effect$1;
19
+ const effectFn = function() {
20
+ cleanup(effectFn);
21
+ activeEffect = effectFn;
22
22
  fn();
23
23
  activeEffect = null;
24
24
  };
25
- effect$1.deps = [];
26
- effect$1();
27
- return () => cleanup(effect$1);
25
+ effectFn.deps = [];
26
+ effectFn();
27
+ const runner = (() => effectFn());
28
+ runner.stop = () => cleanup(effectFn);
29
+ return runner;
28
30
  }
29
31
  function cleanup(effect$1) {
30
32
  if (!effect$1.deps) return;
@@ -158,8 +160,16 @@ function watch(source, cb, options) {
158
160
  let oldValue;
159
161
  let isFirst = true;
160
162
  let cleanupFn = null;
163
+ let paused = false;
164
+ let pendingValue;
165
+ let hasPending = false;
161
166
  const runner = effect(() => {
162
167
  const newValue = typeof source === "function" ? source() : source;
168
+ if (paused) {
169
+ pendingValue = newValue;
170
+ hasPending = true;
171
+ return;
172
+ }
163
173
  if (isFirst) {
164
174
  if (options?.immediate) {
165
175
  if (cleanupFn) cleanupFn();
@@ -173,14 +183,28 @@ function watch(source, cb, options) {
173
183
  oldValue = newValue;
174
184
  });
175
185
  const stop = () => {
176
- runner();
186
+ runner.stop();
177
187
  if (cleanupFn) cleanupFn();
178
188
  };
179
- const handle = stop;
180
- handle.stop = stop;
181
- handle.pause = () => {};
182
- handle.resume = () => {};
183
- return handle;
189
+ const pause = () => {
190
+ paused = true;
191
+ };
192
+ const resume = () => {
193
+ if (!paused) return;
194
+ paused = false;
195
+ if (hasPending && !Object.is(pendingValue, oldValue)) {
196
+ if (cleanupFn) cleanupFn();
197
+ cb(pendingValue, oldValue, (fn) => cleanupFn = fn);
198
+ oldValue = pendingValue;
199
+ }
200
+ hasPending = false;
201
+ pendingValue = void 0;
202
+ };
203
+ return Object.assign(stop, {
204
+ stop,
205
+ pause,
206
+ resume
207
+ });
184
208
  }
185
209
  function effectScope(detached) {
186
210
  const effects = [];
@@ -216,15 +240,38 @@ function getPlatformSyncProcessor() {
216
240
 
217
241
  //#endregion
218
242
  //#region ../runtime-core/src/plugins.ts
219
- const plugins = [];
243
+ const plugins$1 = [];
220
244
  function registerComponentPlugin(plugin) {
221
- plugins.push(plugin);
245
+ plugins$1.push(plugin);
222
246
  }
223
247
  /**
224
248
  * Get all registered plugins (internal use)
225
249
  */
226
250
  function getComponentPlugins() {
227
- return plugins;
251
+ return plugins$1;
252
+ }
253
+ const contextExtensions = [];
254
+ /**
255
+ * Register a function that will be called to extend every component context.
256
+ * Extensions are called in order of registration.
257
+ *
258
+ * @example
259
+ * ```ts
260
+ * // In @sigx/server-renderer/client
261
+ * registerContextExtension((ctx) => {
262
+ * ctx.ssr = { load: () => {} };
263
+ * });
264
+ * ```
265
+ */
266
+ function registerContextExtension(extension) {
267
+ contextExtensions.push(extension);
268
+ }
269
+ /**
270
+ * Apply all registered context extensions to a context object.
271
+ * Called internally by the renderer when creating component contexts.
272
+ */
273
+ function applyContextExtensions(ctx) {
274
+ for (const extension of contextExtensions) extension(ctx);
228
275
  }
229
276
 
230
277
  //#endregion
@@ -442,12 +489,12 @@ function onCleanup(fn) {
442
489
  if (currentComponentContext) currentComponentContext.onCleanup(fn);
443
490
  else console.warn("onCleanup called outside of component setup");
444
491
  }
445
- const componentRegistry = /* @__PURE__ */ new Map();
492
+ const componentRegistry$1 = /* @__PURE__ */ new Map();
446
493
  /**
447
494
  * Get component metadata (for DevTools)
448
495
  */
449
496
  function getComponentMeta(factory) {
450
- return componentRegistry.get(factory);
497
+ return componentRegistry$1.get(factory);
451
498
  }
452
499
  /**
453
500
  * Helper to create a proxy that tracks property access
@@ -497,7 +544,7 @@ function defineComponent(setup, options) {
497
544
  factory.__events = null;
498
545
  factory.__ref = null;
499
546
  factory.__slots = null;
500
- componentRegistry.set(factory, {
547
+ componentRegistry$1.set(factory, {
501
548
  name: options?.name,
502
549
  setup
503
550
  });
@@ -603,6 +650,190 @@ function jsxs(type, props, key) {
603
650
  }
604
651
  const jsxDEV = jsx;
605
652
 
653
+ //#endregion
654
+ //#region ../runtime-core/src/lazy.tsx
655
+ /**
656
+ * Lazy loading utilities for sigx components.
657
+ *
658
+ * Provides runtime-only lazy loading with no build dependencies.
659
+ * Works with any bundler that supports dynamic import().
660
+ */
661
+ let currentSuspenseBoundary$1 = null;
662
+ /**
663
+ * Register a promise with the current Suspense boundary
664
+ * @internal
665
+ */
666
+ function registerPendingPromise(promise) {
667
+ const boundary = currentSuspenseBoundary$1;
668
+ if (boundary) {
669
+ boundary.pending.add(promise);
670
+ promise.finally(() => {
671
+ boundary.pending.delete(promise);
672
+ if (boundary.pending.size === 0) boundary.onResolve();
673
+ });
674
+ return true;
675
+ }
676
+ return false;
677
+ }
678
+ /**
679
+ * Create a lazy-loaded component wrapper.
680
+ *
681
+ * The component will be loaded on first render. Use with `<Suspense>` to show
682
+ * a fallback while loading.
683
+ *
684
+ * @param loader - Function that returns a Promise resolving to the component
685
+ * @returns A component factory that loads the real component on demand
686
+ *
687
+ * @example
688
+ * ```tsx
689
+ * import { lazy, Suspense } from 'sigx';
690
+ *
691
+ * // Component will be in a separate chunk
692
+ * const HeavyChart = lazy(() => import('./components/HeavyChart'));
693
+ *
694
+ * // Usage
695
+ * <Suspense fallback={<Spinner />}>
696
+ * <HeavyChart data={chartData} />
697
+ * </Suspense>
698
+ *
699
+ * // Preload on hover
700
+ * <button onMouseEnter={() => HeavyChart.preload()}>
701
+ * Show Chart
702
+ * </button>
703
+ * ```
704
+ */
705
+ function lazy(loader) {
706
+ let Component = null;
707
+ let promise = null;
708
+ let error = null;
709
+ let state = "pending";
710
+ const LazyWrapper = defineComponent((ctx) => {
711
+ const loadState = ctx.signal({
712
+ state,
713
+ tick: 0
714
+ });
715
+ if (!promise) promise = loader().then((mod) => {
716
+ Component = "default" in mod ? mod.default : mod;
717
+ state = "resolved";
718
+ loadState.state = "resolved";
719
+ loadState.tick++;
720
+ return Component;
721
+ }).catch((err) => {
722
+ error = err instanceof Error ? err : new Error(String(err));
723
+ state = "rejected";
724
+ loadState.state = "rejected";
725
+ loadState.tick++;
726
+ throw error;
727
+ });
728
+ if (state === "resolved" && Component) return () => {
729
+ return jsx(Component, {});
730
+ };
731
+ if (state === "rejected" && error) throw error;
732
+ if (!registerPendingPromise(promise)) promise.catch(() => {});
733
+ return () => {
734
+ const currentState = loadState.state;
735
+ loadState.tick;
736
+ if (currentState === "resolved" && Component) return jsx(Component, {});
737
+ if (currentState === "rejected" && error) throw error;
738
+ return null;
739
+ };
740
+ }, { name: "LazyComponent" });
741
+ LazyWrapper.__lazy = true;
742
+ LazyWrapper.preload = () => {
743
+ if (!promise) promise = loader().then((mod) => {
744
+ Component = "default" in mod ? mod.default : mod;
745
+ state = "resolved";
746
+ return Component;
747
+ }).catch((err) => {
748
+ error = err instanceof Error ? err : new Error(String(err));
749
+ state = "rejected";
750
+ throw error;
751
+ });
752
+ return promise;
753
+ };
754
+ LazyWrapper.isLoaded = () => {
755
+ return state === "resolved";
756
+ };
757
+ return LazyWrapper;
758
+ }
759
+ /**
760
+ * Suspense boundary component for handling async loading states.
761
+ *
762
+ * Wraps lazy-loaded components and shows a fallback while they load.
763
+ *
764
+ * @example
765
+ * ```tsx
766
+ * import { lazy, Suspense } from 'sigx';
767
+ *
768
+ * const LazyDashboard = lazy(() => import('./Dashboard'));
769
+ *
770
+ * // Basic usage
771
+ * <Suspense fallback={<div>Loading...</div>}>
772
+ * <LazyDashboard />
773
+ * </Suspense>
774
+ *
775
+ * // With spinner component
776
+ * <Suspense fallback={<Spinner size="large" />}>
777
+ * <LazyDashboard />
778
+ * <LazyCharts />
779
+ * </Suspense>
780
+ * ```
781
+ */
782
+ const Suspense = defineComponent((ctx) => {
783
+ const { props, slots } = ctx;
784
+ const state = ctx.signal({
785
+ isReady: false,
786
+ pendingCount: 0
787
+ });
788
+ const boundary = {
789
+ pending: /* @__PURE__ */ new Set(),
790
+ onResolve: () => {
791
+ state.pendingCount = boundary.pending.size;
792
+ if (boundary.pending.size === 0) state.isReady = true;
793
+ }
794
+ };
795
+ ctx.onMount(() => {
796
+ if (boundary.pending.size === 0) state.isReady = true;
797
+ });
798
+ return () => {
799
+ state.isReady;
800
+ state.pendingCount;
801
+ const prevBoundary = currentSuspenseBoundary$1;
802
+ currentSuspenseBoundary$1 = boundary;
803
+ try {
804
+ const children = slots.default();
805
+ if (boundary.pending.size > 0) {
806
+ const fallback = props.fallback;
807
+ if (typeof fallback === "function") return fallback();
808
+ return fallback ?? null;
809
+ }
810
+ if (Array.isArray(children)) {
811
+ const filtered = children.filter((c) => c != null && c !== false && c !== true);
812
+ if (filtered.length === 0) return null;
813
+ if (filtered.length === 1) return filtered[0];
814
+ return filtered;
815
+ }
816
+ return children;
817
+ } catch (err) {
818
+ if (err instanceof Promise) {
819
+ registerPendingPromise(err);
820
+ const fallback = props.fallback;
821
+ if (typeof fallback === "function") return fallback();
822
+ return fallback ?? null;
823
+ }
824
+ throw err;
825
+ } finally {
826
+ currentSuspenseBoundary$1 = prevBoundary;
827
+ }
828
+ };
829
+ }, { name: "Suspense" });
830
+ /**
831
+ * Check if a component is a lazy-loaded component
832
+ */
833
+ function isLazyComponent(component) {
834
+ return component && component.__lazy === true;
835
+ }
836
+
606
837
  //#endregion
607
838
  //#region ../runtime-core/src/utils/index.ts
608
839
  var Utils = class {
@@ -617,6 +848,195 @@ function guid$1() {
617
848
  });
618
849
  }
619
850
 
851
+ //#endregion
852
+ //#region ../runtime-core/src/utils/props-accessor.ts
853
+ /**
854
+ * Creates a props accessor that can be called with defaults or accessed directly.
855
+ * After calling with defaults, direct property access uses those defaults.
856
+ *
857
+ * @example
858
+ * ```ts
859
+ * // In component setup:
860
+ * const props = createPropsAccessor(reactiveProps);
861
+ *
862
+ * // Set defaults
863
+ * props({ count: 0, label: 'Default' });
864
+ *
865
+ * // Access props (falls back to defaults if not provided)
866
+ * const count = props.count;
867
+ * ```
868
+ */
869
+ function createPropsAccessor(reactiveProps) {
870
+ let defaults = {};
871
+ const proxy = new Proxy(function propsAccessor() {}, {
872
+ get(_, key) {
873
+ if (typeof key === "symbol") return void 0;
874
+ const value = reactiveProps[key];
875
+ return value != null ? value : defaults[key];
876
+ },
877
+ apply(_, __, args) {
878
+ if (args[0] && typeof args[0] === "object") defaults = {
879
+ ...defaults,
880
+ ...args[0]
881
+ };
882
+ return proxy;
883
+ },
884
+ has(_, key) {
885
+ if (typeof key === "symbol") return false;
886
+ return key in reactiveProps || key in defaults;
887
+ },
888
+ ownKeys() {
889
+ return [...new Set([...Object.keys(reactiveProps), ...Object.keys(defaults)])];
890
+ },
891
+ getOwnPropertyDescriptor(_, key) {
892
+ if (typeof key === "symbol") return void 0;
893
+ if (key in reactiveProps || key in defaults) return {
894
+ enumerable: true,
895
+ configurable: true,
896
+ writable: false
897
+ };
898
+ }
899
+ });
900
+ return proxy;
901
+ }
902
+
903
+ //#endregion
904
+ //#region ../runtime-core/src/utils/slots.ts
905
+ /**
906
+ * Slots system for component children.
907
+ * Supports default and named slots with reactivity.
908
+ */
909
+ /**
910
+ * Create slots object from children and slots prop.
911
+ * Uses a version signal to trigger re-renders when children change.
912
+ *
913
+ * Supports named slots via:
914
+ * - `slots` prop object (e.g., `slots={{ header: () => <div>...</div> }}`)
915
+ * - `slot` prop on children (e.g., `<div slot="header">...</div>`)
916
+ *
917
+ * @example
918
+ * ```tsx
919
+ * // Parent component
920
+ * <Card slots={{ header: () => <h1>Title</h1> }}>
921
+ * <p>Default content</p>
922
+ * <span slot="footer">Footer text</span>
923
+ * </Card>
924
+ *
925
+ * // Card component setup
926
+ * const slots = createSlots(children, slotsFromProps);
927
+ * return () => (
928
+ * <div>
929
+ * {slots.header()}
930
+ * {slots.default()}
931
+ * {slots.footer()}
932
+ * </div>
933
+ * );
934
+ * ```
935
+ */
936
+ function createSlots(children, slotsFromProps) {
937
+ const versionSignal = signal({ v: 0 });
938
+ function extractNamedSlotsFromChildren(c) {
939
+ const defaultChildren = [];
940
+ const namedSlots = {};
941
+ if (c == null) return {
942
+ defaultChildren,
943
+ namedSlots
944
+ };
945
+ const items = Array.isArray(c) ? c : [c];
946
+ for (const child of items) if (child && typeof child === "object" && child.props && child.props.slot) {
947
+ const slotName = child.props.slot;
948
+ if (!namedSlots[slotName]) namedSlots[slotName] = [];
949
+ namedSlots[slotName].push(child);
950
+ } else defaultChildren.push(child);
951
+ return {
952
+ defaultChildren,
953
+ namedSlots
954
+ };
955
+ }
956
+ const slotsObj = {
957
+ _children: children,
958
+ _slotsFromProps: slotsFromProps || {},
959
+ _version: versionSignal,
960
+ _isPatching: false,
961
+ default: function() {
962
+ this._version.v;
963
+ const c = this._children;
964
+ const { defaultChildren } = extractNamedSlotsFromChildren(c);
965
+ return defaultChildren.filter((child) => child != null && child !== false && child !== true);
966
+ }
967
+ };
968
+ return new Proxy(slotsObj, { get(target, prop) {
969
+ if (prop in target) return target[prop];
970
+ if (typeof prop === "string") return function(scopedProps) {
971
+ target._version.v;
972
+ if (target._slotsFromProps && typeof target._slotsFromProps[prop] === "function") {
973
+ const result = target._slotsFromProps[prop](scopedProps);
974
+ if (result == null) return [];
975
+ return Array.isArray(result) ? result : [result];
976
+ }
977
+ const { namedSlots } = extractNamedSlotsFromChildren(target._children);
978
+ return namedSlots[prop] || [];
979
+ };
980
+ } });
981
+ }
982
+
983
+ //#endregion
984
+ //#region ../runtime-core/src/utils/normalize.ts
985
+ /**
986
+ * VNode normalization utilities.
987
+ * Converts render results into proper VNode structures.
988
+ */
989
+ /**
990
+ * Normalize render result to a VNode (wrapping arrays in Fragment).
991
+ * Handles null, undefined, false, true by returning an empty Text node.
992
+ *
993
+ * This is used to normalize the return value of component render functions
994
+ * into a consistent VNode structure for the renderer to process.
995
+ *
996
+ * @example
997
+ * ```ts
998
+ * // Conditional rendering returns null/false
999
+ * normalizeSubTree(null) // → empty Text node
1000
+ * normalizeSubTree(false) // → empty Text node
1001
+ *
1002
+ * // Arrays become Fragments
1003
+ * normalizeSubTree([<A/>, <B/>]) // → Fragment with children
1004
+ *
1005
+ * // Primitives become Text nodes
1006
+ * normalizeSubTree("hello") // → Text node
1007
+ * normalizeSubTree(42) // → Text node
1008
+ *
1009
+ * // VNodes pass through
1010
+ * normalizeSubTree(<div/>) // → same VNode
1011
+ * ```
1012
+ */
1013
+ function normalizeSubTree(result) {
1014
+ if (result == null || result === false || result === true) return {
1015
+ type: Text,
1016
+ props: {},
1017
+ key: null,
1018
+ children: [],
1019
+ dom: null,
1020
+ text: ""
1021
+ };
1022
+ if (Array.isArray(result)) return {
1023
+ type: Fragment,
1024
+ props: {},
1025
+ key: null,
1026
+ children: result,
1027
+ dom: null
1028
+ };
1029
+ if (typeof result === "string" || typeof result === "number") return {
1030
+ type: Text,
1031
+ props: {},
1032
+ key: null,
1033
+ children: [],
1034
+ dom: null,
1035
+ text: result
1036
+ };
1037
+ return result;
1038
+ }
1039
+
620
1040
  //#endregion
621
1041
  //#region ../runtime-core/src/models/index.ts
622
1042
  const guid = guid$1;
@@ -913,7 +1333,6 @@ function isComponent(type) {
913
1333
  }
914
1334
  function createRenderer(options) {
915
1335
  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;
916
- let isPatching = false;
917
1336
  let currentAppContext = null;
918
1337
  function render$1(element, container, appContext) {
919
1338
  if (appContext) currentAppContext = appContext;
@@ -927,6 +1346,13 @@ function createRenderer(options) {
927
1346
  dom: null,
928
1347
  text: element
929
1348
  };
1349
+ else if (isComponent(element)) vnode = {
1350
+ type: element,
1351
+ props: {},
1352
+ key: null,
1353
+ children: [],
1354
+ dom: null
1355
+ };
930
1356
  else vnode = element;
931
1357
  if (vnode) {
932
1358
  if (oldVNode) patch(oldVNode, vnode, container);
@@ -938,6 +1364,7 @@ function createRenderer(options) {
938
1364
  }
939
1365
  }
940
1366
  function mount(vnode, container, before = null) {
1367
+ if (vnode == null || vnode === false || vnode === true) return;
941
1368
  if (vnode.type === Text) {
942
1369
  const node = hostCreateText(String(vnode.text));
943
1370
  vnode.dom = node;
@@ -949,7 +1376,7 @@ function createRenderer(options) {
949
1376
  const anchor = hostCreateComment("");
950
1377
  vnode.dom = anchor;
951
1378
  hostInsert(anchor, container, before);
952
- vnode.children.forEach((child) => mount(child, container, anchor));
1379
+ if (vnode.children) vnode.children.forEach((child) => mount(child, container, anchor));
953
1380
  return;
954
1381
  }
955
1382
  if (isComponent(vnode.type)) {
@@ -966,17 +1393,18 @@ function createRenderer(options) {
966
1393
  else if (typeof vnode.props.ref === "object") vnode.props.ref.current = element;
967
1394
  }
968
1395
  }
969
- vnode.children.forEach((child) => {
1396
+ if (vnode.children) vnode.children.forEach((child) => {
970
1397
  child.parent = vnode;
971
1398
  mount(child, element);
972
1399
  });
973
1400
  hostInsert(element, container, before);
974
1401
  }
975
1402
  function unmount(vnode, container) {
976
- if (vnode._effect) vnode._effect();
1403
+ const internalVNode = vnode;
1404
+ if (internalVNode._effect) internalVNode._effect.stop();
977
1405
  if (vnode.cleanup) vnode.cleanup();
978
1406
  if (isComponent(vnode.type)) {
979
- const subTree = vnode._subTree;
1407
+ const subTree = internalVNode._subTree;
980
1408
  if (subTree) unmount(subTree, container);
981
1409
  if (vnode.dom) hostRemove(vnode.dom);
982
1410
  if (vnode.props?.ref) {
@@ -986,7 +1414,7 @@ function createRenderer(options) {
986
1414
  return;
987
1415
  }
988
1416
  if (vnode.type === Fragment) {
989
- vnode.children.forEach((child) => unmount(child, container));
1417
+ if (vnode.children) vnode.children.forEach((child) => unmount(child, container));
990
1418
  if (vnode.dom) hostRemove(vnode.dom);
991
1419
  return;
992
1420
  }
@@ -1006,13 +1434,15 @@ function createRenderer(options) {
1006
1434
  mount(newVNode, parent, nextSibling);
1007
1435
  return;
1008
1436
  }
1009
- if (oldVNode._effect) {
1437
+ const oldInternal = oldVNode;
1438
+ const newInternal = newVNode;
1439
+ if (oldInternal._effect) {
1010
1440
  newVNode.dom = oldVNode.dom;
1011
- newVNode._effect = oldVNode._effect;
1012
- newVNode._subTree = oldVNode._subTree;
1013
- newVNode._slots = oldVNode._slots;
1014
- const props = oldVNode._componentProps;
1015
- newVNode._componentProps = props;
1441
+ newInternal._effect = oldInternal._effect;
1442
+ newInternal._subTree = oldInternal._subTree;
1443
+ newInternal._slots = oldInternal._slots;
1444
+ const props = oldInternal._componentProps;
1445
+ newInternal._componentProps = props;
1016
1446
  if (props) {
1017
1447
  const newProps$1 = newVNode.props || {};
1018
1448
  untrack(() => {
@@ -1022,20 +1452,20 @@ function createRenderer(options) {
1022
1452
  for (const key in props) if (!(key in newProps$1) && key !== "children" && key !== "key" && key !== "ref") delete props[key];
1023
1453
  });
1024
1454
  }
1025
- const slotsRef = oldVNode._slots;
1455
+ const slotsRef = oldInternal._slots;
1026
1456
  const newChildren = newVNode.props?.children;
1027
1457
  const newSlotsFromProps = newVNode.props?.slots;
1028
1458
  if (slotsRef) {
1029
1459
  if (newChildren !== void 0) slotsRef._children = newChildren;
1030
1460
  if (newSlotsFromProps !== void 0) slotsRef._slotsFromProps = newSlotsFromProps;
1031
- if (!isPatching) {
1032
- isPatching = true;
1461
+ if (!slotsRef._isPatching) {
1462
+ slotsRef._isPatching = true;
1033
1463
  try {
1034
1464
  untrack(() => {
1035
1465
  slotsRef._version.v++;
1036
1466
  });
1037
1467
  } finally {
1038
- isPatching = false;
1468
+ slotsRef._isPatching = false;
1039
1469
  }
1040
1470
  }
1041
1471
  }
@@ -1149,9 +1579,10 @@ function createRenderer(options) {
1149
1579
  let exposeCalled = false;
1150
1580
  const { children, slots: slotsFromProps, ...propsData } = vnode.props || {};
1151
1581
  const reactiveProps = signal(propsData);
1152
- vnode._componentProps = reactiveProps;
1582
+ const internalVNode = vnode;
1583
+ internalVNode._componentProps = reactiveProps;
1153
1584
  const slots = createSlots(children, slotsFromProps);
1154
- vnode._slots = slots;
1585
+ internalVNode._slots = slots;
1155
1586
  const mountHooks = [];
1156
1587
  const cleanupHooks = [];
1157
1588
  const parentInstance = getCurrentInstance();
@@ -1159,7 +1590,7 @@ function createRenderer(options) {
1159
1590
  const ctx = {
1160
1591
  el: container,
1161
1592
  signal,
1162
- props: reactiveProps,
1593
+ props: createPropsAccessor(reactiveProps),
1163
1594
  slots,
1164
1595
  emit: (event, ...args) => {
1165
1596
  const handler = reactiveProps[`on${event[0].toUpperCase() + event.slice(1)}`];
@@ -1175,8 +1606,11 @@ function createRenderer(options) {
1175
1606
  expose: (exposedValue) => {
1176
1607
  exposed = exposedValue;
1177
1608
  exposeCalled = true;
1178
- }
1609
+ },
1610
+ renderFn: null,
1611
+ update: () => {}
1179
1612
  };
1613
+ applyContextExtensions(ctx);
1180
1614
  ctx.__name = componentName;
1181
1615
  if (currentAppContext) ctx._appContext = currentAppContext;
1182
1616
  const componentInstance = {
@@ -1187,7 +1621,9 @@ function createRenderer(options) {
1187
1621
  const prev = setCurrentInstance(ctx);
1188
1622
  let renderFn;
1189
1623
  try {
1190
- renderFn = setup(ctx);
1624
+ const setupResult = setup(ctx);
1625
+ 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.`);
1626
+ renderFn = setupResult;
1191
1627
  notifyComponentCreated(currentAppContext, componentInstance);
1192
1628
  } catch (err) {
1193
1629
  if (!handleComponentError(currentAppContext, err, componentInstance, "setup")) throw err;
@@ -1199,24 +1635,31 @@ function createRenderer(options) {
1199
1635
  if (typeof vnode.props.ref === "function") vnode.props.ref(refValue);
1200
1636
  else if (vnode.props.ref && typeof vnode.props.ref === "object") vnode.props.ref.current = refValue;
1201
1637
  }
1202
- if (renderFn) vnode._effect = effect(() => {
1203
- const prevInstance = setCurrentInstance(ctx);
1204
- try {
1205
- const subTreeResult = renderFn();
1206
- if (subTreeResult == null) return;
1207
- const subTree = normalizeSubTree(subTreeResult);
1208
- const prevSubTree = vnode._subTree;
1209
- if (prevSubTree) {
1210
- patch(prevSubTree, subTree, container);
1211
- notifyComponentUpdated(currentAppContext, componentInstance);
1212
- } else mount(subTree, container, anchor);
1213
- vnode._subTree = subTree;
1214
- } catch (err) {
1215
- if (!handleComponentError(currentAppContext, err, componentInstance, "render")) throw err;
1216
- } finally {
1217
- setCurrentInstance(prevInstance);
1218
- }
1219
- });
1638
+ if (renderFn) {
1639
+ ctx.renderFn = renderFn;
1640
+ const componentEffect = effect(() => {
1641
+ const prevInstance = setCurrentInstance(ctx);
1642
+ try {
1643
+ const subTreeResult = ctx.renderFn();
1644
+ if (subTreeResult == null) return;
1645
+ const subTree = normalizeSubTree(subTreeResult);
1646
+ const prevSubTree = internalVNode._subTree;
1647
+ if (prevSubTree) {
1648
+ patch(prevSubTree, subTree, container);
1649
+ notifyComponentUpdated(currentAppContext, componentInstance);
1650
+ } else mount(subTree, container, anchor);
1651
+ internalVNode._subTree = subTree;
1652
+ } catch (err) {
1653
+ if (!handleComponentError(currentAppContext, err, componentInstance, "render")) throw err;
1654
+ } finally {
1655
+ setCurrentInstance(prevInstance);
1656
+ }
1657
+ });
1658
+ internalVNode._effect = componentEffect;
1659
+ ctx.update = () => {
1660
+ componentEffect();
1661
+ };
1662
+ }
1220
1663
  const mountCtx = { el: container };
1221
1664
  mountHooks.forEach((hook) => hook(mountCtx));
1222
1665
  notifyComponentMounted(currentAppContext, componentInstance);
@@ -1225,81 +1668,12 @@ function createRenderer(options) {
1225
1668
  cleanupHooks.forEach((hook) => hook(mountCtx));
1226
1669
  };
1227
1670
  }
1228
- /**
1229
- * Create slots object from children and slots prop.
1230
- * Uses a version signal to trigger re-renders when children change.
1231
- * Supports named slots via:
1232
- * - `slots` prop object (e.g., slots={{ header: () => <div>...</div> }})
1233
- * - `slot` prop on children (e.g., <div slot="header">...</div>)
1234
- */
1235
- function createSlots(children, slotsFromProps) {
1236
- const versionSignal = signal({ v: 0 });
1237
- function extractNamedSlotsFromChildren(c) {
1238
- const defaultChildren = [];
1239
- const namedSlots = {};
1240
- if (c == null) return {
1241
- defaultChildren,
1242
- namedSlots
1243
- };
1244
- const items = Array.isArray(c) ? c : [c];
1245
- for (const child of items) if (child && typeof child === "object" && child.props && child.props.slot) {
1246
- const slotName = child.props.slot;
1247
- if (!namedSlots[slotName]) namedSlots[slotName] = [];
1248
- namedSlots[slotName].push(child);
1249
- } else defaultChildren.push(child);
1250
- return {
1251
- defaultChildren,
1252
- namedSlots
1253
- };
1254
- }
1255
- const slotsObj = {
1256
- _children: children,
1257
- _slotsFromProps: slotsFromProps || {},
1258
- _version: versionSignal,
1259
- default: function() {
1260
- this._version.v;
1261
- const c = this._children;
1262
- const { defaultChildren } = extractNamedSlotsFromChildren(c);
1263
- return defaultChildren;
1264
- }
1265
- };
1266
- return new Proxy(slotsObj, { get(target, prop) {
1267
- if (prop in target) return target[prop];
1268
- if (typeof prop === "string") return function(scopedProps) {
1269
- target._version.v;
1270
- if (target._slotsFromProps && typeof target._slotsFromProps[prop] === "function") {
1271
- const result = target._slotsFromProps[prop](scopedProps);
1272
- if (result == null) return [];
1273
- return Array.isArray(result) ? result : [result];
1274
- }
1275
- const { namedSlots } = extractNamedSlotsFromChildren(target._children);
1276
- return namedSlots[prop] || [];
1277
- };
1278
- } });
1279
- }
1280
- /**
1281
- * Normalize render result to a VNode (wrapping arrays in Fragment)
1282
- */
1283
- function normalizeSubTree(result) {
1284
- if (Array.isArray(result)) return {
1285
- type: Fragment,
1286
- props: {},
1287
- key: null,
1288
- children: result,
1289
- dom: null
1290
- };
1291
- if (typeof result === "string" || typeof result === "number") return {
1292
- type: Text,
1293
- props: {},
1294
- key: null,
1295
- children: [],
1296
- dom: null,
1297
- text: result
1298
- };
1299
- return result;
1300
- }
1301
1671
  return {
1302
1672
  render: render$1,
1673
+ patch,
1674
+ mount,
1675
+ unmount,
1676
+ mountComponent,
1303
1677
  createApp: (rootComponent) => {
1304
1678
  return { mount(selectorOrContainer) {
1305
1679
  let container = null;
@@ -1384,6 +1758,60 @@ let platformSyncProcessor = null;
1384
1758
  function getPlatformSyncProcessor$1() {
1385
1759
  return platformSyncProcessor;
1386
1760
  }
1761
+ const plugins = [];
1762
+ /**
1763
+ * Get all registered plugins (internal use)
1764
+ */
1765
+ function getComponentPlugins$1() {
1766
+ return plugins;
1767
+ }
1768
+ const componentRegistry = /* @__PURE__ */ new Map();
1769
+ /**
1770
+ * Define a component. Returns a JSX factory function.
1771
+ *
1772
+ * @param setup - Setup function that receives context and returns a render function
1773
+ * @param options - Optional configuration (e.g., name for DevTools)
1774
+ *
1775
+ * @example
1776
+ * ```tsx
1777
+ * type CardProps = DefineProp<"title", string> & DefineSlot<"header">;
1778
+ *
1779
+ * export const Card = defineComponent<CardProps>((ctx) => {
1780
+ * const { title } = ctx.props;
1781
+ * const { slots } = ctx;
1782
+ *
1783
+ * return () => (
1784
+ * <div class="card">
1785
+ * {slots.header?.() ?? <h2>{title}</h2>}
1786
+ * {slots.default()}
1787
+ * </div>
1788
+ * );
1789
+ * });
1790
+ * ```
1791
+ */
1792
+ function defineComponent$1(setup, options) {
1793
+ const factory = function(props) {
1794
+ return {
1795
+ type: factory,
1796
+ props: props || {},
1797
+ key: props?.key || null,
1798
+ children: [],
1799
+ dom: null
1800
+ };
1801
+ };
1802
+ factory.__setup = setup;
1803
+ factory.__name = options?.name;
1804
+ factory.__props = null;
1805
+ factory.__events = null;
1806
+ factory.__ref = null;
1807
+ factory.__slots = null;
1808
+ componentRegistry.set(factory, {
1809
+ name: options?.name,
1810
+ setup
1811
+ });
1812
+ getComponentPlugins$1().forEach((p) => p.onDefine?.(options?.name, factory, setup));
1813
+ return factory;
1814
+ }
1387
1815
  const Fragment$1 = Symbol.for("sigx.Fragment");
1388
1816
  const Text$1 = Symbol.for("sigx.Text");
1389
1817
  function normalizeChildren(children) {
@@ -1478,6 +1906,100 @@ function jsx$1(type, props, key) {
1478
1906
  function jsxs$1(type, props, key) {
1479
1907
  return jsx$1(type, props, key);
1480
1908
  }
1909
+ /**
1910
+ * Lazy loading utilities for sigx components.
1911
+ *
1912
+ * Provides runtime-only lazy loading with no build dependencies.
1913
+ * Works with any bundler that supports dynamic import().
1914
+ */
1915
+ let currentSuspenseBoundary = null;
1916
+ /**
1917
+ * Register a promise with the current Suspense boundary
1918
+ * @internal
1919
+ */
1920
+ function registerPendingPromise$1(promise) {
1921
+ const boundary = currentSuspenseBoundary;
1922
+ if (boundary) {
1923
+ boundary.pending.add(promise);
1924
+ promise.finally(() => {
1925
+ boundary.pending.delete(promise);
1926
+ if (boundary.pending.size === 0) boundary.onResolve();
1927
+ });
1928
+ return true;
1929
+ }
1930
+ return false;
1931
+ }
1932
+ /**
1933
+ * Suspense boundary component for handling async loading states.
1934
+ *
1935
+ * Wraps lazy-loaded components and shows a fallback while they load.
1936
+ *
1937
+ * @example
1938
+ * ```tsx
1939
+ * import { lazy, Suspense } from 'sigx';
1940
+ *
1941
+ * const LazyDashboard = lazy(() => import('./Dashboard'));
1942
+ *
1943
+ * // Basic usage
1944
+ * <Suspense fallback={<div>Loading...</div>}>
1945
+ * <LazyDashboard />
1946
+ * </Suspense>
1947
+ *
1948
+ * // With spinner component
1949
+ * <Suspense fallback={<Spinner size="large" />}>
1950
+ * <LazyDashboard />
1951
+ * <LazyCharts />
1952
+ * </Suspense>
1953
+ * ```
1954
+ */
1955
+ const Suspense$1 = defineComponent$1((ctx) => {
1956
+ const { props, slots } = ctx;
1957
+ const state = ctx.signal({
1958
+ isReady: false,
1959
+ pendingCount: 0
1960
+ });
1961
+ const boundary = {
1962
+ pending: /* @__PURE__ */ new Set(),
1963
+ onResolve: () => {
1964
+ state.pendingCount = boundary.pending.size;
1965
+ if (boundary.pending.size === 0) state.isReady = true;
1966
+ }
1967
+ };
1968
+ ctx.onMount(() => {
1969
+ if (boundary.pending.size === 0) state.isReady = true;
1970
+ });
1971
+ return () => {
1972
+ state.isReady;
1973
+ state.pendingCount;
1974
+ const prevBoundary = currentSuspenseBoundary;
1975
+ currentSuspenseBoundary = boundary;
1976
+ try {
1977
+ const children = slots.default();
1978
+ if (boundary.pending.size > 0) {
1979
+ const fallback = props.fallback;
1980
+ if (typeof fallback === "function") return fallback();
1981
+ return fallback ?? null;
1982
+ }
1983
+ if (Array.isArray(children)) {
1984
+ const filtered = children.filter((c) => c != null && c !== false && c !== true);
1985
+ if (filtered.length === 0) return null;
1986
+ if (filtered.length === 1) return filtered[0];
1987
+ return filtered;
1988
+ }
1989
+ return children;
1990
+ } catch (err) {
1991
+ if (err instanceof Promise) {
1992
+ registerPendingPromise$1(err);
1993
+ const fallback = props.fallback;
1994
+ if (typeof fallback === "function") return fallback();
1995
+ return fallback ?? null;
1996
+ }
1997
+ throw err;
1998
+ } finally {
1999
+ currentSuspenseBoundary = prevBoundary;
2000
+ }
2001
+ };
2002
+ }, { name: "Suspense" });
1481
2003
 
1482
2004
  //#endregion
1483
2005
  //#region ../runtime-terminal/src/components/Input.tsx
@@ -2024,5 +2546,5 @@ const terminalMount = (component, options, appContext) => {
2024
2546
  setDefaultMount(terminalMount);
2025
2547
 
2026
2548
  //#endregion
2027
- export { AppContextKey, Button, Checkbox, Fragment, Input, InstanceLifetimes, ProgressBar, Select, SubscriptionHandler, Text, Utils, batch, createApp, createPropsProxy, createRenderer, createTopic, defineApp, defineComponent, defineFactory, defineInjectable, defineProvide, defineStore, detectAccess, effect, effectScope, exitTerminal, focus, focusNext, focusPrev, focusState, getComponentMeta, getComponentPlugins, getCurrentInstance, getDefaultMount, getPlatformSyncProcessor, guid, handleComponentError, inject, injectApp, jsx, jsxDEV, jsxs, mountTerminal, notifyComponentCreated, notifyComponentMounted, notifyComponentUnmounted, notifyComponentUpdated, onCleanup, onKey, onMount, provide, registerComponentPlugin, registerFocusable, render, renderNodeToLines, renderTerminal, setCurrentInstance, setDefaultMount, setPlatformSyncProcessor, signal, terminalMount, toSubscriber, unregisterFocusable, untrack, valueOf, watch };
2549
+ export { AppContextKey, Button, Checkbox, Fragment, Input, InstanceLifetimes, ProgressBar, Select, SubscriptionHandler, Suspense, Text, Utils, applyContextExtensions, batch, createApp, createPropsAccessor, createPropsProxy, createRenderer, createSlots, createTopic, defineApp, defineComponent, defineFactory, defineInjectable, defineProvide, defineStore, detectAccess, effect, effectScope, exitTerminal, focus, focusNext, focusPrev, focusState, getComponentMeta, getComponentPlugins, getCurrentInstance, getDefaultMount, getPlatformSyncProcessor, guid, handleComponentError, inject, injectApp, isLazyComponent, jsx, jsxDEV, jsxs, lazy, mountTerminal, normalizeSubTree, notifyComponentCreated, notifyComponentMounted, notifyComponentUnmounted, notifyComponentUpdated, onCleanup, onKey, onMount, provide, registerComponentPlugin, registerContextExtension, registerFocusable, registerPendingPromise, render, renderNodeToLines, renderTerminal, setCurrentInstance, setDefaultMount, setPlatformSyncProcessor, signal, terminalMount, toSubscriber, unregisterFocusable, untrack, valueOf, watch };
2028
2550
  //# sourceMappingURL=index.js.map