@sigx/runtime-terminal 0.1.3 → 0.1.4

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,10 @@
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
8
  }
9
9
 
10
10
  //#endregion
@@ -119,7 +119,7 @@ function onCleanup(fn) {
119
119
  if (currentComponentContext) currentComponentContext.onCleanup(fn);
120
120
  else console.warn("onCleanup called outside of component setup");
121
121
  }
122
- const componentRegistry = /* @__PURE__ */ new Map();
122
+ const componentRegistry$1 = /* @__PURE__ */ new Map();
123
123
  /**
124
124
  * Define a component. Returns a JSX factory function.
125
125
  *
@@ -159,11 +159,11 @@ function defineComponent(setup, options) {
159
159
  factory.__events = null;
160
160
  factory.__ref = null;
161
161
  factory.__slots = null;
162
- componentRegistry.set(factory, {
162
+ componentRegistry$1.set(factory, {
163
163
  name: options?.name,
164
164
  setup
165
165
  });
166
- getComponentPlugins().forEach((p) => p.onDefine?.(options?.name, factory, setup));
166
+ getComponentPlugins$1().forEach((p) => p.onDefine?.(options?.name, factory, setup));
167
167
  return factory;
168
168
  }
169
169
 
@@ -186,15 +186,17 @@ function batch(fn) {
186
186
  }
187
187
  }
188
188
  function runEffect(fn) {
189
- const effect$1 = function() {
190
- cleanup(effect$1);
191
- activeEffect = effect$1;
189
+ const effectFn = function() {
190
+ cleanup(effectFn);
191
+ activeEffect = effectFn;
192
192
  fn();
193
193
  activeEffect = null;
194
194
  };
195
- effect$1.deps = [];
196
- effect$1();
197
- return () => cleanup(effect$1);
195
+ effectFn.deps = [];
196
+ effectFn();
197
+ const runner = (() => effectFn());
198
+ runner.stop = () => cleanup(effectFn);
199
+ return runner;
198
200
  }
199
201
  function cleanup(effect$1) {
200
202
  if (!effect$1.deps) return;
@@ -330,6 +332,103 @@ const arrayInstrumentations = {};
330
332
  const Fragment$1 = Symbol.for("sigx.Fragment");
331
333
  const Text$1 = Symbol.for("sigx.Text");
332
334
 
335
+ //#endregion
336
+ //#region ../runtime-core/src/lazy.tsx
337
+ /**
338
+ * Lazy loading utilities for sigx components.
339
+ *
340
+ * Provides runtime-only lazy loading with no build dependencies.
341
+ * Works with any bundler that supports dynamic import().
342
+ */
343
+ let currentSuspenseBoundary$1 = null;
344
+ /**
345
+ * Register a promise with the current Suspense boundary
346
+ * @internal
347
+ */
348
+ function registerPendingPromise$1(promise) {
349
+ const boundary = currentSuspenseBoundary$1;
350
+ if (boundary) {
351
+ boundary.pending.add(promise);
352
+ promise.finally(() => {
353
+ boundary.pending.delete(promise);
354
+ if (boundary.pending.size === 0) boundary.onResolve();
355
+ });
356
+ return true;
357
+ }
358
+ return false;
359
+ }
360
+ /**
361
+ * Suspense boundary component for handling async loading states.
362
+ *
363
+ * Wraps lazy-loaded components and shows a fallback while they load.
364
+ *
365
+ * @example
366
+ * ```tsx
367
+ * import { lazy, Suspense } from 'sigx';
368
+ *
369
+ * const LazyDashboard = lazy(() => import('./Dashboard'));
370
+ *
371
+ * // Basic usage
372
+ * <Suspense fallback={<div>Loading...</div>}>
373
+ * <LazyDashboard />
374
+ * </Suspense>
375
+ *
376
+ * // With spinner component
377
+ * <Suspense fallback={<Spinner size="large" />}>
378
+ * <LazyDashboard />
379
+ * <LazyCharts />
380
+ * </Suspense>
381
+ * ```
382
+ */
383
+ const Suspense$1 = defineComponent((ctx) => {
384
+ const { props, slots } = ctx;
385
+ const state = ctx.signal({
386
+ isReady: false,
387
+ pendingCount: 0
388
+ });
389
+ const boundary = {
390
+ pending: /* @__PURE__ */ new Set(),
391
+ onResolve: () => {
392
+ state.pendingCount = boundary.pending.size;
393
+ if (boundary.pending.size === 0) state.isReady = true;
394
+ }
395
+ };
396
+ ctx.onMount(() => {
397
+ if (boundary.pending.size === 0) state.isReady = true;
398
+ });
399
+ return () => {
400
+ state.isReady;
401
+ state.pendingCount;
402
+ const prevBoundary = currentSuspenseBoundary$1;
403
+ currentSuspenseBoundary$1 = boundary;
404
+ try {
405
+ const children = slots.default();
406
+ if (boundary.pending.size > 0) {
407
+ const fallback = props.fallback;
408
+ if (typeof fallback === "function") return fallback();
409
+ return fallback ?? null;
410
+ }
411
+ if (Array.isArray(children)) {
412
+ const filtered = children.filter((c) => c != null && c !== false && c !== true);
413
+ if (filtered.length === 0) return null;
414
+ if (filtered.length === 1) return filtered[0];
415
+ return filtered;
416
+ }
417
+ return children;
418
+ } catch (err) {
419
+ if (err instanceof Promise) {
420
+ registerPendingPromise$1(err);
421
+ const fallback = props.fallback;
422
+ if (typeof fallback === "function") return fallback();
423
+ return fallback ?? null;
424
+ }
425
+ throw err;
426
+ } finally {
427
+ currentSuspenseBoundary$1 = prevBoundary;
428
+ }
429
+ };
430
+ }, { name: "Suspense" });
431
+
333
432
  //#endregion
334
433
  //#region ../runtime-core/src/renderer.ts
335
434
  /**
@@ -340,7 +439,6 @@ function isComponent(type) {
340
439
  }
341
440
  function createRenderer(options) {
342
441
  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
442
  let currentAppContext = null;
345
443
  function render$1(element, container, appContext) {
346
444
  if (appContext) currentAppContext = appContext;
@@ -354,6 +452,13 @@ function createRenderer(options) {
354
452
  dom: null,
355
453
  text: element
356
454
  };
455
+ else if (isComponent(element)) vnode = {
456
+ type: element,
457
+ props: {},
458
+ key: null,
459
+ children: [],
460
+ dom: null
461
+ };
357
462
  else vnode = element;
358
463
  if (vnode) {
359
464
  if (oldVNode) patch(oldVNode, vnode, container);
@@ -365,6 +470,7 @@ function createRenderer(options) {
365
470
  }
366
471
  }
367
472
  function mount(vnode, container, before = null) {
473
+ if (vnode == null || vnode === false || vnode === true) return;
368
474
  if (vnode.type === Text$1) {
369
475
  const node = hostCreateText(String(vnode.text));
370
476
  vnode.dom = node;
@@ -376,7 +482,7 @@ function createRenderer(options) {
376
482
  const anchor = hostCreateComment("");
377
483
  vnode.dom = anchor;
378
484
  hostInsert(anchor, container, before);
379
- vnode.children.forEach((child) => mount(child, container, anchor));
485
+ if (vnode.children) vnode.children.forEach((child) => mount(child, container, anchor));
380
486
  return;
381
487
  }
382
488
  if (isComponent(vnode.type)) {
@@ -393,17 +499,18 @@ function createRenderer(options) {
393
499
  else if (typeof vnode.props.ref === "object") vnode.props.ref.current = element;
394
500
  }
395
501
  }
396
- vnode.children.forEach((child) => {
502
+ if (vnode.children) vnode.children.forEach((child) => {
397
503
  child.parent = vnode;
398
504
  mount(child, element);
399
505
  });
400
506
  hostInsert(element, container, before);
401
507
  }
402
508
  function unmount(vnode, container) {
403
- if (vnode._effect) vnode._effect();
509
+ const internalVNode = vnode;
510
+ if (internalVNode._effect) internalVNode._effect.stop();
404
511
  if (vnode.cleanup) vnode.cleanup();
405
512
  if (isComponent(vnode.type)) {
406
- const subTree = vnode._subTree;
513
+ const subTree = internalVNode._subTree;
407
514
  if (subTree) unmount(subTree, container);
408
515
  if (vnode.dom) hostRemove(vnode.dom);
409
516
  if (vnode.props?.ref) {
@@ -413,7 +520,7 @@ function createRenderer(options) {
413
520
  return;
414
521
  }
415
522
  if (vnode.type === Fragment$1) {
416
- vnode.children.forEach((child) => unmount(child, container));
523
+ if (vnode.children) vnode.children.forEach((child) => unmount(child, container));
417
524
  if (vnode.dom) hostRemove(vnode.dom);
418
525
  return;
419
526
  }
@@ -433,13 +540,15 @@ function createRenderer(options) {
433
540
  mount(newVNode, parent, nextSibling);
434
541
  return;
435
542
  }
436
- if (oldVNode._effect) {
543
+ const oldInternal = oldVNode;
544
+ const newInternal = newVNode;
545
+ if (oldInternal._effect) {
437
546
  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;
547
+ newInternal._effect = oldInternal._effect;
548
+ newInternal._subTree = oldInternal._subTree;
549
+ newInternal._slots = oldInternal._slots;
550
+ const props = oldInternal._componentProps;
551
+ newInternal._componentProps = props;
443
552
  if (props) {
444
553
  const newProps$1 = newVNode.props || {};
445
554
  untrack(() => {
@@ -449,20 +558,20 @@ function createRenderer(options) {
449
558
  for (const key in props) if (!(key in newProps$1) && key !== "children" && key !== "key" && key !== "ref") delete props[key];
450
559
  });
451
560
  }
452
- const slotsRef = oldVNode._slots;
561
+ const slotsRef = oldInternal._slots;
453
562
  const newChildren = newVNode.props?.children;
454
563
  const newSlotsFromProps = newVNode.props?.slots;
455
564
  if (slotsRef) {
456
565
  if (newChildren !== void 0) slotsRef._children = newChildren;
457
566
  if (newSlotsFromProps !== void 0) slotsRef._slotsFromProps = newSlotsFromProps;
458
- if (!isPatching) {
459
- isPatching = true;
567
+ if (!slotsRef._isPatching) {
568
+ slotsRef._isPatching = true;
460
569
  try {
461
570
  untrack(() => {
462
571
  slotsRef._version.v++;
463
572
  });
464
573
  } finally {
465
- isPatching = false;
574
+ slotsRef._isPatching = false;
466
575
  }
467
576
  }
468
577
  }
@@ -567,6 +676,43 @@ function createRenderer(options) {
567
676
  for (let i = beginIdx; i <= endIdx; i++) if (children[i] && isSameVNode(children[i], newChild)) return i;
568
677
  return null;
569
678
  }
679
+ /**
680
+ * Creates a props accessor that can be called with defaults or accessed directly.
681
+ * After calling with defaults, direct property access uses those defaults.
682
+ */
683
+ function createPropsAccessor(reactiveProps) {
684
+ let defaults = {};
685
+ const proxy = new Proxy(function propsAccessor() {}, {
686
+ get(_, key) {
687
+ if (typeof key === "symbol") return void 0;
688
+ const value = reactiveProps[key];
689
+ return value != null ? value : defaults[key];
690
+ },
691
+ apply(_, __, args) {
692
+ if (args[0] && typeof args[0] === "object") defaults = {
693
+ ...defaults,
694
+ ...args[0]
695
+ };
696
+ return proxy;
697
+ },
698
+ has(_, key) {
699
+ if (typeof key === "symbol") return false;
700
+ return key in reactiveProps || key in defaults;
701
+ },
702
+ ownKeys() {
703
+ return [...new Set([...Object.keys(reactiveProps), ...Object.keys(defaults)])];
704
+ },
705
+ getOwnPropertyDescriptor(_, key) {
706
+ if (typeof key === "symbol") return void 0;
707
+ if (key in reactiveProps || key in defaults) return {
708
+ enumerable: true,
709
+ configurable: true,
710
+ writable: false
711
+ };
712
+ }
713
+ });
714
+ return proxy;
715
+ }
570
716
  function mountComponent(vnode, container, before, setup) {
571
717
  const anchor = hostCreateComment("");
572
718
  vnode.dom = anchor;
@@ -576,9 +722,10 @@ function createRenderer(options) {
576
722
  let exposeCalled = false;
577
723
  const { children, slots: slotsFromProps, ...propsData } = vnode.props || {};
578
724
  const reactiveProps = signal(propsData);
579
- vnode._componentProps = reactiveProps;
725
+ const internalVNode = vnode;
726
+ internalVNode._componentProps = reactiveProps;
580
727
  const slots = createSlots(children, slotsFromProps);
581
- vnode._slots = slots;
728
+ internalVNode._slots = slots;
582
729
  const mountHooks = [];
583
730
  const cleanupHooks = [];
584
731
  const parentInstance = getCurrentInstance();
@@ -586,7 +733,7 @@ function createRenderer(options) {
586
733
  const ctx = {
587
734
  el: container,
588
735
  signal,
589
- props: reactiveProps,
736
+ props: createPropsAccessor(reactiveProps),
590
737
  slots,
591
738
  emit: (event, ...args) => {
592
739
  const handler = reactiveProps[`on${event[0].toUpperCase() + event.slice(1)}`];
@@ -602,7 +749,9 @@ function createRenderer(options) {
602
749
  expose: (exposedValue) => {
603
750
  exposed = exposedValue;
604
751
  exposeCalled = true;
605
- }
752
+ },
753
+ renderFn: null,
754
+ update: () => {}
606
755
  };
607
756
  ctx.__name = componentName;
608
757
  if (currentAppContext) ctx._appContext = currentAppContext;
@@ -626,24 +775,31 @@ function createRenderer(options) {
626
775
  if (typeof vnode.props.ref === "function") vnode.props.ref(refValue);
627
776
  else if (vnode.props.ref && typeof vnode.props.ref === "object") vnode.props.ref.current = refValue;
628
777
  }
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
- });
778
+ if (renderFn) {
779
+ ctx.renderFn = renderFn;
780
+ const componentEffect = effect(() => {
781
+ const prevInstance = setCurrentInstance(ctx);
782
+ try {
783
+ const subTreeResult = ctx.renderFn();
784
+ if (subTreeResult == null) return;
785
+ const subTree = normalizeSubTree(subTreeResult);
786
+ const prevSubTree = internalVNode._subTree;
787
+ if (prevSubTree) {
788
+ patch(prevSubTree, subTree, container);
789
+ notifyComponentUpdated(currentAppContext, componentInstance);
790
+ } else mount(subTree, container, anchor);
791
+ internalVNode._subTree = subTree;
792
+ } catch (err) {
793
+ if (!handleComponentError(currentAppContext, err, componentInstance, "render")) throw err;
794
+ } finally {
795
+ setCurrentInstance(prevInstance);
796
+ }
797
+ });
798
+ internalVNode._effect = componentEffect;
799
+ ctx.update = () => {
800
+ componentEffect();
801
+ };
802
+ }
647
803
  const mountCtx = { el: container };
648
804
  mountHooks.forEach((hook) => hook(mountCtx));
649
805
  notifyComponentMounted(currentAppContext, componentInstance);
@@ -683,11 +839,12 @@ function createRenderer(options) {
683
839
  _children: children,
684
840
  _slotsFromProps: slotsFromProps || {},
685
841
  _version: versionSignal,
842
+ _isPatching: false,
686
843
  default: function() {
687
844
  this._version.v;
688
845
  const c = this._children;
689
846
  const { defaultChildren } = extractNamedSlotsFromChildren(c);
690
- return defaultChildren;
847
+ return defaultChildren.filter((child) => child != null && child !== false && child !== true);
691
848
  }
692
849
  };
693
850
  return new Proxy(slotsObj, { get(target, prop) {
@@ -706,6 +863,8 @@ function createRenderer(options) {
706
863
  }
707
864
  /**
708
865
  * Normalize render result to a VNode (wrapping arrays in Fragment)
866
+ * Note: Falsy values (null, undefined, false, true) from conditional rendering
867
+ * are handled by mount() which guards against them, so no filtering needed here.
709
868
  */
710
869
  function normalizeSubTree(result) {
711
870
  if (Array.isArray(result)) return {
@@ -811,6 +970,60 @@ let platformSyncProcessor = null;
811
970
  function getPlatformSyncProcessor() {
812
971
  return platformSyncProcessor;
813
972
  }
973
+ const plugins = [];
974
+ /**
975
+ * Get all registered plugins (internal use)
976
+ */
977
+ function getComponentPlugins() {
978
+ return plugins;
979
+ }
980
+ const componentRegistry = /* @__PURE__ */ new Map();
981
+ /**
982
+ * Define a component. Returns a JSX factory function.
983
+ *
984
+ * @param setup - Setup function that receives context and returns a render function
985
+ * @param options - Optional configuration (e.g., name for DevTools)
986
+ *
987
+ * @example
988
+ * ```tsx
989
+ * type CardProps = DefineProp<"title", string> & DefineSlot<"header">;
990
+ *
991
+ * export const Card = defineComponent<CardProps>((ctx) => {
992
+ * const { title } = ctx.props;
993
+ * const { slots } = ctx;
994
+ *
995
+ * return () => (
996
+ * <div class="card">
997
+ * {slots.header?.() ?? <h2>{title}</h2>}
998
+ * {slots.default()}
999
+ * </div>
1000
+ * );
1001
+ * });
1002
+ * ```
1003
+ */
1004
+ function defineComponent$1(setup, options) {
1005
+ const factory = function(props) {
1006
+ return {
1007
+ type: factory,
1008
+ props: props || {},
1009
+ key: props?.key || null,
1010
+ children: [],
1011
+ dom: null
1012
+ };
1013
+ };
1014
+ factory.__setup = setup;
1015
+ factory.__name = options?.name;
1016
+ factory.__props = null;
1017
+ factory.__events = null;
1018
+ factory.__ref = null;
1019
+ factory.__slots = null;
1020
+ componentRegistry.set(factory, {
1021
+ name: options?.name,
1022
+ setup
1023
+ });
1024
+ getComponentPlugins().forEach((p) => p.onDefine?.(options?.name, factory, setup));
1025
+ return factory;
1026
+ }
814
1027
  const Fragment = Symbol.for("sigx.Fragment");
815
1028
  const Text = Symbol.for("sigx.Text");
816
1029
  function normalizeChildren(children) {
@@ -905,6 +1118,100 @@ function jsx(type, props, key) {
905
1118
  function jsxs(type, props, key) {
906
1119
  return jsx(type, props, key);
907
1120
  }
1121
+ /**
1122
+ * Lazy loading utilities for sigx components.
1123
+ *
1124
+ * Provides runtime-only lazy loading with no build dependencies.
1125
+ * Works with any bundler that supports dynamic import().
1126
+ */
1127
+ let currentSuspenseBoundary = null;
1128
+ /**
1129
+ * Register a promise with the current Suspense boundary
1130
+ * @internal
1131
+ */
1132
+ function registerPendingPromise(promise) {
1133
+ const boundary = currentSuspenseBoundary;
1134
+ if (boundary) {
1135
+ boundary.pending.add(promise);
1136
+ promise.finally(() => {
1137
+ boundary.pending.delete(promise);
1138
+ if (boundary.pending.size === 0) boundary.onResolve();
1139
+ });
1140
+ return true;
1141
+ }
1142
+ return false;
1143
+ }
1144
+ /**
1145
+ * Suspense boundary component for handling async loading states.
1146
+ *
1147
+ * Wraps lazy-loaded components and shows a fallback while they load.
1148
+ *
1149
+ * @example
1150
+ * ```tsx
1151
+ * import { lazy, Suspense } from 'sigx';
1152
+ *
1153
+ * const LazyDashboard = lazy(() => import('./Dashboard'));
1154
+ *
1155
+ * // Basic usage
1156
+ * <Suspense fallback={<div>Loading...</div>}>
1157
+ * <LazyDashboard />
1158
+ * </Suspense>
1159
+ *
1160
+ * // With spinner component
1161
+ * <Suspense fallback={<Spinner size="large" />}>
1162
+ * <LazyDashboard />
1163
+ * <LazyCharts />
1164
+ * </Suspense>
1165
+ * ```
1166
+ */
1167
+ const Suspense = defineComponent$1((ctx) => {
1168
+ const { props, slots } = ctx;
1169
+ const state = ctx.signal({
1170
+ isReady: false,
1171
+ pendingCount: 0
1172
+ });
1173
+ const boundary = {
1174
+ pending: /* @__PURE__ */ new Set(),
1175
+ onResolve: () => {
1176
+ state.pendingCount = boundary.pending.size;
1177
+ if (boundary.pending.size === 0) state.isReady = true;
1178
+ }
1179
+ };
1180
+ ctx.onMount(() => {
1181
+ if (boundary.pending.size === 0) state.isReady = true;
1182
+ });
1183
+ return () => {
1184
+ state.isReady;
1185
+ state.pendingCount;
1186
+ const prevBoundary = currentSuspenseBoundary;
1187
+ currentSuspenseBoundary = boundary;
1188
+ try {
1189
+ const children = slots.default();
1190
+ if (boundary.pending.size > 0) {
1191
+ const fallback = props.fallback;
1192
+ if (typeof fallback === "function") return fallback();
1193
+ return fallback ?? null;
1194
+ }
1195
+ if (Array.isArray(children)) {
1196
+ const filtered = children.filter((c) => c != null && c !== false && c !== true);
1197
+ if (filtered.length === 0) return null;
1198
+ if (filtered.length === 1) return filtered[0];
1199
+ return filtered;
1200
+ }
1201
+ return children;
1202
+ } catch (err) {
1203
+ if (err instanceof Promise) {
1204
+ registerPendingPromise(err);
1205
+ const fallback = props.fallback;
1206
+ if (typeof fallback === "function") return fallback();
1207
+ return fallback ?? null;
1208
+ }
1209
+ throw err;
1210
+ } finally {
1211
+ currentSuspenseBoundary = prevBoundary;
1212
+ }
1213
+ };
1214
+ }, { name: "Suspense" });
908
1215
 
909
1216
  //#endregion
910
1217
  //#region src/components/Input.tsx