@opentui/solid 0.2.3 → 0.2.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
@@ -102,12 +102,30 @@ export class TextSlotRenderable extends TextNodeRenderable3 {
102
102
  constructor(id: any, parent: any);
103
103
  slotParent: any;
104
104
  destroyed: boolean;
105
+ detachFromSlot(): void;
106
+ disposeWithoutSlotCascade(): void;
105
107
  }
106
108
  export class SlotRenderable extends SlotBaseRenderable {
107
- layoutNode: any;
108
- textNode: any;
109
109
  destroyed: boolean;
110
+ layoutNodesByParent: Map<any, any>;
111
+ textNodesByParent: Map<any, any>;
112
+ layoutNodeCount: number;
113
+ textNodeCount: number;
114
+ get layoutNode(): any;
115
+ get textNode(): any;
116
+ isTextSlotParent(parent: any): parent is TextRenderable3 | TextNodeRenderable3;
117
+ getCurrentSlotChild(nodesByParent: any): any;
118
+ getTextNodeForParent(parent: any): any;
119
+ getLayoutNodeForParent(parent: any): any;
120
+ takeReusableTextNode(parent: any): any;
121
+ takeReusableLayoutNode(parent: any): any;
122
+ disposeDetachedTextNodes(): void;
123
+ disposeDetachedIncompatibleLayoutNodes(parent: any): void;
124
+ getAttachedSlotParent(excludedNode: any): any;
125
+ hasOtherAttachedSlotChildren(excludedNode: any): boolean;
110
126
  getSlotChild(parent: any): any;
127
+ getSlotChildForRemoval(parent: any): any;
128
+ didRemoveSlotChild(parent: any, child: any): void;
111
129
  }
112
130
  export function Slot(props: any): import("solid-js").Accessor<any>;
113
131
  export var RendererContext: import("solid-js").Context<any>;
@@ -118,14 +136,20 @@ export class LineBreakRenderable extends SpanRenderable {
118
136
  add(): number;
119
137
  }
120
138
  export class LayoutSlotRenderable extends SlotBaseRenderable {
121
- constructor(id: any, parent: any);
122
- yogaNode: Yoga.Node;
139
+ constructor(id: any, parent: any, layoutParent: any);
140
+ yogaNode: any;
123
141
  slotParent: any;
124
142
  destroyed: boolean;
125
- getLayoutNode(): Yoga.Node;
143
+ yogaNodeConstructor: any;
144
+ yogaNodeFreed: boolean;
145
+ getLayoutNode(): any;
126
146
  updateFromLayout(): void;
127
147
  updateLayout(): void;
128
148
  onRemove(): void;
149
+ isCompatibleWith(layoutParent: any): boolean;
150
+ detachFromSlot(): void;
151
+ freeYogaNode(): void;
152
+ disposeWithoutSlotCascade(): void;
129
153
  }
130
154
  export class ItalicSpanRenderable extends TextModifierRenderable {
131
155
  constructor(options: any);
@@ -165,6 +189,5 @@ declare class SlotBaseRenderable extends BaseRenderable {
165
189
  getRenderable(id: any): void;
166
190
  findDescendantById(id: any): void;
167
191
  }
168
- import { Yoga } from "@opentui/core";
169
192
  import { BaseRenderable } from "@opentui/core";
170
193
  export { mergeProps3 as mergeProps, memo2 as memo, createComponent2 as createComponent };
package/index.js CHANGED
@@ -483,11 +483,20 @@ function _insertNode(parent, node, anchor) {
483
483
  }
484
484
  function _removeNode(parent, node) {
485
485
  log("Removing node:", logId(node), "from parent:", logId(parent));
486
+ let slotParent;
486
487
  if (node instanceof SlotRenderable) {
487
- node.parent = null;
488
- node = node.getSlotChild(parent);
488
+ slotParent = node;
489
+ const slotChild = slotParent.getSlotChildForRemoval(parent);
490
+ if (!slotChild) {
491
+ if (slotParent.parent === parent) {
492
+ slotParent.parent = null;
493
+ }
494
+ return;
495
+ }
496
+ node = slotChild;
489
497
  }
490
498
  parent.remove(node.id);
499
+ slotParent?.didRemoveSlotChild(parent, node);
491
500
  process.nextTick(() => {
492
501
  if (node instanceof BaseRenderable && !node.parent) {
493
502
  node.destroyRecursively();
@@ -753,6 +762,13 @@ function Dynamic(props) {
753
762
  }
754
763
  // src/elements/slot.ts
755
764
  import { BaseRenderable as BaseRenderable2, isTextNodeRenderable as isTextNodeRenderable2, TextNodeRenderable as TextNodeRenderable2, TextRenderable as TextRenderable2, Yoga } from "@opentui/core";
765
+ function getLayoutNodeConstructor(parent) {
766
+ const parentLayoutNode = parent?.getLayoutNode?.();
767
+ return parentLayoutNode?.constructor;
768
+ }
769
+ function createLayoutSlotYogaNode(parentNodeConstructor) {
770
+ return parentNodeConstructor?.create?.() ?? Yoga.default.Node.create();
771
+ }
756
772
 
757
773
  class SlotBaseRenderable extends BaseRenderable2 {
758
774
  constructor(id) {
@@ -790,12 +806,24 @@ class TextSlotRenderable extends TextNodeRenderable2 {
790
806
  this._visible = false;
791
807
  this.slotParent = parent;
792
808
  }
809
+ detachFromSlot() {
810
+ this.slotParent = undefined;
811
+ }
812
+ disposeWithoutSlotCascade() {
813
+ if (this.destroyed) {
814
+ return;
815
+ }
816
+ this.destroyed = true;
817
+ this.detachFromSlot();
818
+ }
793
819
  destroy() {
794
820
  if (this.destroyed) {
795
821
  return;
796
822
  }
797
823
  this.destroyed = true;
798
- this.slotParent?.destroy();
824
+ const slotParent = this.slotParent;
825
+ this.slotParent = undefined;
826
+ slotParent?.destroy();
799
827
  super.destroy();
800
828
  }
801
829
  }
@@ -804,11 +832,14 @@ class LayoutSlotRenderable extends SlotBaseRenderable {
804
832
  yogaNode;
805
833
  slotParent;
806
834
  destroyed = false;
807
- constructor(id, parent) {
835
+ yogaNodeConstructor;
836
+ yogaNodeFreed = false;
837
+ constructor(id, parent, layoutParent) {
808
838
  super(id);
809
839
  this._visible = false;
810
840
  this.slotParent = parent;
811
- this.yogaNode = Yoga.default.Node.create();
841
+ this.yogaNodeConstructor = getLayoutNodeConstructor(layoutParent);
842
+ this.yogaNode = createLayoutSlotYogaNode(this.yogaNodeConstructor);
812
843
  this.yogaNode.setDisplay(Yoga.Display.None);
813
844
  }
814
845
  getLayoutNode() {
@@ -817,46 +848,216 @@ class LayoutSlotRenderable extends SlotBaseRenderable {
817
848
  updateFromLayout() {}
818
849
  updateLayout() {}
819
850
  onRemove() {}
851
+ isCompatibleWith(layoutParent) {
852
+ return this.yogaNodeConstructor === getLayoutNodeConstructor(layoutParent);
853
+ }
854
+ detachFromSlot() {
855
+ this.slotParent = undefined;
856
+ }
857
+ freeYogaNode() {
858
+ if (this.yogaNodeFreed) {
859
+ return;
860
+ }
861
+ this.yogaNodeFreed = true;
862
+ try {
863
+ this.yogaNode.free();
864
+ } catch {}
865
+ }
866
+ disposeWithoutSlotCascade() {
867
+ if (this.destroyed) {
868
+ return;
869
+ }
870
+ this.destroyed = true;
871
+ this.detachFromSlot();
872
+ this.freeYogaNode();
873
+ }
820
874
  destroy() {
821
875
  if (this.destroyed) {
822
876
  return;
823
877
  }
824
878
  this.destroyed = true;
825
- super.destroy();
826
- this.slotParent?.destroy();
879
+ const slotParent = this.slotParent;
880
+ this.slotParent = undefined;
881
+ this.freeYogaNode();
882
+ slotParent?.destroy();
827
883
  }
828
884
  }
829
885
 
830
886
  class SlotRenderable extends SlotBaseRenderable {
831
- layoutNode;
832
- textNode;
833
887
  destroyed = false;
888
+ layoutNodesByParent = new Map;
889
+ textNodesByParent = new Map;
890
+ layoutNodeCount = 0;
891
+ textNodeCount = 0;
834
892
  constructor(id) {
835
893
  super(id);
836
894
  this._visible = false;
837
895
  }
896
+ get layoutNode() {
897
+ return this.getCurrentSlotChild(this.layoutNodesByParent);
898
+ }
899
+ get textNode() {
900
+ return this.getCurrentSlotChild(this.textNodesByParent);
901
+ }
902
+ isTextSlotParent(parent) {
903
+ return isTextNodeRenderable2(parent) || parent instanceof TextRenderable2;
904
+ }
905
+ getCurrentSlotChild(nodesByParent) {
906
+ for (const node of nodesByParent.values()) {
907
+ if (node.parent) {
908
+ return node;
909
+ }
910
+ }
911
+ return nodesByParent.values().next().value;
912
+ }
913
+ getTextNodeForParent(parent) {
914
+ const mappedNode = this.textNodesByParent.get(parent);
915
+ if (mappedNode) {
916
+ return mappedNode;
917
+ }
918
+ for (const [mappedParent, textNode] of this.textNodesByParent) {
919
+ if (textNode.parent !== parent) {
920
+ continue;
921
+ }
922
+ this.textNodesByParent.delete(mappedParent);
923
+ this.textNodesByParent.set(parent, textNode);
924
+ return textNode;
925
+ }
926
+ }
927
+ getLayoutNodeForParent(parent) {
928
+ const mappedNode = this.layoutNodesByParent.get(parent);
929
+ if (mappedNode) {
930
+ return mappedNode;
931
+ }
932
+ for (const [mappedParent, layoutNode] of this.layoutNodesByParent) {
933
+ if (layoutNode.parent !== parent) {
934
+ continue;
935
+ }
936
+ this.layoutNodesByParent.delete(mappedParent);
937
+ this.layoutNodesByParent.set(parent, layoutNode);
938
+ return layoutNode;
939
+ }
940
+ }
941
+ takeReusableTextNode(parent) {
942
+ for (const [mappedParent, textNode] of this.textNodesByParent) {
943
+ if (textNode.parent) {
944
+ continue;
945
+ }
946
+ this.textNodesByParent.delete(mappedParent);
947
+ this.textNodesByParent.set(parent, textNode);
948
+ return textNode;
949
+ }
950
+ }
951
+ takeReusableLayoutNode(parent) {
952
+ for (const [mappedParent, layoutNode] of this.layoutNodesByParent) {
953
+ if (layoutNode.parent) {
954
+ continue;
955
+ }
956
+ if (!layoutNode.isCompatibleWith(parent)) {
957
+ continue;
958
+ }
959
+ this.layoutNodesByParent.delete(mappedParent);
960
+ this.layoutNodesByParent.set(parent, layoutNode);
961
+ return layoutNode;
962
+ }
963
+ }
964
+ disposeDetachedTextNodes() {
965
+ for (const [parent, textNode] of this.textNodesByParent) {
966
+ if (textNode.parent) {
967
+ continue;
968
+ }
969
+ this.textNodesByParent.delete(parent);
970
+ textNode.disposeWithoutSlotCascade();
971
+ }
972
+ }
973
+ disposeDetachedIncompatibleLayoutNodes(parent) {
974
+ for (const [mappedParent, layoutNode] of this.layoutNodesByParent) {
975
+ if (layoutNode.parent || layoutNode.isCompatibleWith(parent)) {
976
+ continue;
977
+ }
978
+ this.layoutNodesByParent.delete(mappedParent);
979
+ layoutNode.disposeWithoutSlotCascade();
980
+ }
981
+ }
982
+ getAttachedSlotParent(excludedNode) {
983
+ for (const textNode of this.textNodesByParent.values()) {
984
+ if (textNode !== excludedNode && textNode.parent) {
985
+ return textNode.parent;
986
+ }
987
+ }
988
+ for (const layoutNode of this.layoutNodesByParent.values()) {
989
+ if (layoutNode !== excludedNode && layoutNode.parent) {
990
+ return layoutNode.parent;
991
+ }
992
+ }
993
+ return null;
994
+ }
995
+ hasOtherAttachedSlotChildren(excludedNode) {
996
+ return this.getAttachedSlotParent(excludedNode) !== null;
997
+ }
838
998
  getSlotChild(parent) {
839
- if (isTextNodeRenderable2(parent) || parent instanceof TextRenderable2) {
840
- if (!this.textNode) {
841
- this.textNode = new TextSlotRenderable(`slot-text-${this.id}`, this);
999
+ if (this.isTextSlotParent(parent)) {
1000
+ const existingTextNode = this.getTextNodeForParent(parent);
1001
+ if (existingTextNode) {
1002
+ return existingTextNode;
1003
+ }
1004
+ const reusableTextNode = this.takeReusableTextNode(parent);
1005
+ if (reusableTextNode) {
1006
+ return reusableTextNode;
842
1007
  }
843
- return this.textNode;
1008
+ this.disposeDetachedIncompatibleLayoutNodes(parent);
1009
+ const textNode = new TextSlotRenderable(`slot-text-${this.id}-${++this.textNodeCount}`, this);
1010
+ this.textNodesByParent.set(parent, textNode);
1011
+ return textNode;
1012
+ }
1013
+ const existingLayoutNode = this.getLayoutNodeForParent(parent);
1014
+ if (existingLayoutNode) {
1015
+ return existingLayoutNode;
1016
+ }
1017
+ const reusableLayoutNode = this.takeReusableLayoutNode(parent);
1018
+ if (reusableLayoutNode) {
1019
+ return reusableLayoutNode;
1020
+ }
1021
+ this.disposeDetachedTextNodes();
1022
+ this.disposeDetachedIncompatibleLayoutNodes(parent);
1023
+ const layoutNode = new LayoutSlotRenderable(`slot-layout-${this.id}-${++this.layoutNodeCount}`, this, parent);
1024
+ this.layoutNodesByParent.set(parent, layoutNode);
1025
+ return layoutNode;
1026
+ }
1027
+ getSlotChildForRemoval(parent) {
1028
+ if (this.isTextSlotParent(parent)) {
1029
+ return this.getTextNodeForParent(parent);
1030
+ }
1031
+ return this.getLayoutNodeForParent(parent);
1032
+ }
1033
+ didRemoveSlotChild(parent, child) {
1034
+ const hasOtherAttachedSlotChildren = this.hasOtherAttachedSlotChildren(child);
1035
+ if (hasOtherAttachedSlotChildren && child instanceof TextSlotRenderable && this.getTextNodeForParent(parent) === child) {
1036
+ this.textNodesByParent.delete(parent);
1037
+ child.disposeWithoutSlotCascade();
1038
+ }
1039
+ if (hasOtherAttachedSlotChildren && child instanceof LayoutSlotRenderable && this.getLayoutNodeForParent(parent) === child) {
1040
+ this.layoutNodesByParent.delete(parent);
1041
+ child.disposeWithoutSlotCascade();
844
1042
  }
845
- if (!this.layoutNode) {
846
- this.layoutNode = new LayoutSlotRenderable(`slot-layout-${this.id}`, this);
1043
+ if (this.parent === parent) {
1044
+ this.parent = this.getAttachedSlotParent(child);
847
1045
  }
848
- return this.layoutNode;
849
1046
  }
850
1047
  destroy() {
851
1048
  if (this.destroyed) {
852
1049
  return;
853
1050
  }
854
1051
  this.destroyed = true;
855
- if (this.layoutNode) {
856
- this.layoutNode.destroy();
1052
+ const layoutNodes = new Set(this.layoutNodesByParent.values());
1053
+ this.layoutNodesByParent.clear();
1054
+ for (const layoutNode of layoutNodes) {
1055
+ layoutNode.destroy();
857
1056
  }
858
- if (this.textNode) {
859
- this.textNode.destroy();
1057
+ const textNodes = new Set(this.textNodesByParent.values());
1058
+ this.textNodesByParent.clear();
1059
+ for (const textNode of textNodes) {
1060
+ textNode.destroy();
860
1061
  }
861
1062
  }
862
1063
  }
package/package.json CHANGED
@@ -4,7 +4,7 @@
4
4
  "main": "index.js",
5
5
  "types": "index.d.ts",
6
6
  "type": "module",
7
- "version": "0.2.3",
7
+ "version": "0.2.5",
8
8
  "description": "SolidJS renderer for OpenTUI",
9
9
  "license": "MIT",
10
10
  "repository": {
@@ -39,7 +39,7 @@
39
39
  "dependencies": {
40
40
  "@babel/core": "7.28.0",
41
41
  "@babel/preset-typescript": "7.27.1",
42
- "@opentui/core": "0.2.3",
42
+ "@opentui/core": "0.2.5",
43
43
  "babel-plugin-module-resolver": "5.0.2",
44
44
  "babel-preset-solid": "1.9.12",
45
45
  "entities": "7.0.1",
@@ -14,25 +14,49 @@ export declare class TextSlotRenderable extends TextNodeRenderable {
14
14
  protected slotParent?: SlotRenderable;
15
15
  protected destroyed: boolean;
16
16
  constructor(id: string, parent?: SlotRenderable);
17
+ detachFromSlot(): void;
18
+ disposeWithoutSlotCascade(): void;
17
19
  destroy(): void;
18
20
  }
19
21
  export declare class LayoutSlotRenderable extends SlotBaseRenderable {
20
22
  protected yogaNode: Yoga.Node;
21
23
  protected slotParent?: SlotRenderable;
22
24
  protected destroyed: boolean;
23
- constructor(id: string, parent?: SlotRenderable);
25
+ private yogaNodeConstructor;
26
+ private yogaNodeFreed;
27
+ constructor(id: string, parent?: SlotRenderable, layoutParent?: BaseRenderable);
24
28
  getLayoutNode(): Yoga.Node;
25
29
  updateFromLayout(): void;
26
30
  updateLayout(): void;
27
31
  onRemove(): void;
32
+ isCompatibleWith(layoutParent?: BaseRenderable): boolean;
33
+ detachFromSlot(): void;
34
+ private freeYogaNode;
35
+ disposeWithoutSlotCascade(): void;
28
36
  destroy(): void;
29
37
  }
30
38
  export declare class SlotRenderable extends SlotBaseRenderable {
31
- layoutNode?: LayoutSlotRenderable;
32
- textNode?: TextSlotRenderable;
33
39
  protected destroyed: boolean;
40
+ private readonly layoutNodesByParent;
41
+ private readonly textNodesByParent;
42
+ private layoutNodeCount;
43
+ private textNodeCount;
34
44
  constructor(id: string);
45
+ get layoutNode(): LayoutSlotRenderable | undefined;
46
+ get textNode(): TextSlotRenderable | undefined;
47
+ private isTextSlotParent;
48
+ private getCurrentSlotChild;
49
+ private getTextNodeForParent;
50
+ private getLayoutNodeForParent;
51
+ private takeReusableTextNode;
52
+ private takeReusableLayoutNode;
53
+ private disposeDetachedTextNodes;
54
+ private disposeDetachedIncompatibleLayoutNodes;
55
+ private getAttachedSlotParent;
56
+ private hasOtherAttachedSlotChildren;
35
57
  getSlotChild(parent: BaseRenderable): TextSlotRenderable | LayoutSlotRenderable;
58
+ getSlotChildForRemoval(parent: BaseRenderable): BaseRenderable | undefined;
59
+ didRemoveSlotChild(parent: BaseRenderable, child: BaseRenderable): void;
36
60
  destroy(): void;
37
61
  }
38
62
  export {};