@sigx/runtime-terminal 0.1.4 → 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.d.ts CHANGED
@@ -13,8 +13,8 @@ export interface TerminalNode {
13
13
  children: TerminalNode[];
14
14
  parentNode?: TerminalNode | null;
15
15
  }
16
- export declare const render: (element: import("@sigx/runtime-core").JSXElement, container: TerminalNode, appContext?: import("@sigx/runtime-core").AppContext) => void, createApp: (rootComponent: any) => {
17
- mount(selectorOrContainer: string | TerminalNode): void;
16
+ export declare const render: import("@sigx/runtime-core").RootRenderFunction<TerminalNode, TerminalNode>, createApp: (rootComponent: any) => {
17
+ mount: (selectorOrContainer: string | TerminalNode) => void;
18
18
  };
19
19
  export declare function renderNodeToLines(node: TerminalNode): string[];
20
20
  type KeyHandler = (key: string) => void;
package/dist/index.js CHANGED
@@ -6,6 +6,14 @@ const plugins$1 = [];
6
6
  function getComponentPlugins$1() {
7
7
  return plugins$1;
8
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);
16
+ }
9
17
 
10
18
  //#endregion
11
19
  //#region ../runtime-core/src/app.ts
@@ -429,6 +437,195 @@ const Suspense$1 = defineComponent((ctx) => {
429
437
  };
430
438
  }, { name: "Suspense" });
431
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
+
432
629
  //#endregion
433
630
  //#region ../runtime-core/src/renderer.ts
434
631
  /**
@@ -676,43 +873,6 @@ function createRenderer(options) {
676
873
  for (let i = beginIdx; i <= endIdx; i++) if (children[i] && isSameVNode(children[i], newChild)) return i;
677
874
  return null;
678
875
  }
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
- }
716
876
  function mountComponent(vnode, container, before, setup) {
717
877
  const anchor = hostCreateComment("");
718
878
  vnode.dom = anchor;
@@ -753,6 +913,7 @@ function createRenderer(options) {
753
913
  renderFn: null,
754
914
  update: () => {}
755
915
  };
916
+ applyContextExtensions(ctx);
756
917
  ctx.__name = componentName;
757
918
  if (currentAppContext) ctx._appContext = currentAppContext;
758
919
  const componentInstance = {
@@ -763,7 +924,9 @@ function createRenderer(options) {
763
924
  const prev = setCurrentInstance(ctx);
764
925
  let renderFn;
765
926
  try {
766
- 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;
767
930
  notifyComponentCreated(currentAppContext, componentInstance);
768
931
  } catch (err) {
769
932
  if (!handleComponentError(currentAppContext, err, componentInstance, "setup")) throw err;
@@ -808,84 +971,12 @@ function createRenderer(options) {
808
971
  cleanupHooks.forEach((hook) => hook(mountCtx));
809
972
  };
810
973
  }
811
- /**
812
- * Create slots object from children and slots prop.
813
- * Uses a version signal to trigger re-renders when children change.
814
- * Supports named slots via:
815
- * - `slots` prop object (e.g., slots={{ header: () => <div>...</div> }})
816
- * - `slot` prop on children (e.g., <div slot="header">...</div>)
817
- */
818
- function createSlots(children, slotsFromProps) {
819
- const versionSignal = signal({ v: 0 });
820
- function extractNamedSlotsFromChildren(c) {
821
- const defaultChildren = [];
822
- const namedSlots = {};
823
- if (c == null) return {
824
- defaultChildren,
825
- namedSlots
826
- };
827
- const items = Array.isArray(c) ? c : [c];
828
- for (const child of items) if (child && typeof child === "object" && child.props && child.props.slot) {
829
- const slotName = child.props.slot;
830
- if (!namedSlots[slotName]) namedSlots[slotName] = [];
831
- namedSlots[slotName].push(child);
832
- } else defaultChildren.push(child);
833
- return {
834
- defaultChildren,
835
- namedSlots
836
- };
837
- }
838
- const slotsObj = {
839
- _children: children,
840
- _slotsFromProps: slotsFromProps || {},
841
- _version: versionSignal,
842
- _isPatching: false,
843
- default: function() {
844
- this._version.v;
845
- const c = this._children;
846
- const { defaultChildren } = extractNamedSlotsFromChildren(c);
847
- return defaultChildren.filter((child) => child != null && child !== false && child !== true);
848
- }
849
- };
850
- return new Proxy(slotsObj, { get(target, prop) {
851
- if (prop in target) return target[prop];
852
- if (typeof prop === "string") return function(scopedProps) {
853
- target._version.v;
854
- if (target._slotsFromProps && typeof target._slotsFromProps[prop] === "function") {
855
- const result = target._slotsFromProps[prop](scopedProps);
856
- if (result == null) return [];
857
- return Array.isArray(result) ? result : [result];
858
- }
859
- const { namedSlots } = extractNamedSlotsFromChildren(target._children);
860
- return namedSlots[prop] || [];
861
- };
862
- } });
863
- }
864
- /**
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.
868
- */
869
- function normalizeSubTree(result) {
870
- if (Array.isArray(result)) return {
871
- type: Fragment$1,
872
- props: {},
873
- key: null,
874
- children: result,
875
- dom: null
876
- };
877
- if (typeof result === "string" || typeof result === "number") return {
878
- type: Text$1,
879
- props: {},
880
- key: null,
881
- children: [],
882
- dom: null,
883
- text: result
884
- };
885
- return result;
886
- }
887
974
  return {
888
975
  render: render$1,
976
+ patch,
977
+ mount,
978
+ unmount,
979
+ mountComponent,
889
980
  createApp: (rootComponent) => {
890
981
  return { mount(selectorOrContainer) {
891
982
  let container = null;