@effect-tui/react 0.2.1 → 0.2.3

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.
Files changed (80) hide show
  1. package/dist/src/components/MultilineTextInput.js +1 -1
  2. package/dist/src/components/MultilineTextInput.js.map +1 -1
  3. package/dist/src/components/text-editing.js +1 -1
  4. package/dist/src/components/text-editing.js.map +1 -1
  5. package/dist/src/console/ConsoleCapture.d.ts +1 -1
  6. package/dist/src/console/ConsoleCapture.d.ts.map +1 -1
  7. package/dist/src/console/ConsoleCapture.js +1 -1
  8. package/dist/src/console/ConsoleCapture.js.map +1 -1
  9. package/dist/src/console/ConsolePopover.d.ts.map +1 -1
  10. package/dist/src/console/ConsolePopover.js +7 -7
  11. package/dist/src/console/ConsolePopover.js.map +1 -1
  12. package/dist/src/debug/DebugOverlay.d.ts +2 -2
  13. package/dist/src/debug/DebugOverlay.d.ts.map +1 -1
  14. package/dist/src/debug/DebugOverlay.js +2 -2
  15. package/dist/src/debug/DebugOverlay.js.map +1 -1
  16. package/dist/src/dev.js +5 -5
  17. package/dist/src/dev.js.map +1 -1
  18. package/dist/src/exit.d.ts +7 -0
  19. package/dist/src/exit.d.ts.map +1 -0
  20. package/dist/src/exit.js +9 -0
  21. package/dist/src/exit.js.map +1 -0
  22. package/dist/src/highlight.d.ts.map +1 -1
  23. package/dist/src/highlight.js +2 -4
  24. package/dist/src/highlight.js.map +1 -1
  25. package/dist/src/hmr-plugin.d.ts +14 -0
  26. package/dist/src/hmr-plugin.d.ts.map +1 -1
  27. package/dist/src/hmr-plugin.js +1 -1
  28. package/dist/src/hmr-plugin.js.map +1 -1
  29. package/dist/src/hooks/use-quit.d.ts.map +1 -1
  30. package/dist/src/hooks/use-quit.js +2 -1
  31. package/dist/src/hooks/use-quit.js.map +1 -1
  32. package/dist/src/hosts/base.d.ts +1 -1
  33. package/dist/src/hosts/base.d.ts.map +1 -1
  34. package/dist/src/hosts/base.js +1 -1
  35. package/dist/src/hosts/base.js.map +1 -1
  36. package/dist/src/hosts/canvas.d.ts +3 -0
  37. package/dist/src/hosts/canvas.d.ts.map +1 -1
  38. package/dist/src/hosts/canvas.js +9 -1
  39. package/dist/src/hosts/canvas.js.map +1 -1
  40. package/dist/src/hosts/single-child.d.ts +1 -2
  41. package/dist/src/hosts/single-child.d.ts.map +1 -1
  42. package/dist/src/hosts/single-child.js +0 -3
  43. package/dist/src/hosts/single-child.js.map +1 -1
  44. package/dist/src/motion/hooks.d.ts.map +1 -1
  45. package/dist/src/motion/hooks.js +4 -2
  46. package/dist/src/motion/hooks.js.map +1 -1
  47. package/dist/src/renderer/modes/InlineRenderer.js +1 -1
  48. package/dist/src/renderer/modes/InlineRenderer.js.map +1 -1
  49. package/dist/src/renderer/modes/StaticContentRenderer.js +1 -1
  50. package/dist/src/renderer/modes/StaticContentRenderer.js.map +1 -1
  51. package/dist/src/renderer-types.d.ts +6 -0
  52. package/dist/src/renderer-types.d.ts.map +1 -1
  53. package/dist/src/renderer.d.ts.map +1 -1
  54. package/dist/src/renderer.js +35 -10
  55. package/dist/src/renderer.js.map +1 -1
  56. package/dist/src/utils/flex-layout.d.ts +14 -0
  57. package/dist/src/utils/flex-layout.d.ts.map +1 -1
  58. package/dist/src/utils/flex-layout.js +76 -23
  59. package/dist/src/utils/flex-layout.js.map +1 -1
  60. package/dist/tsconfig.tsbuildinfo +1 -1
  61. package/package.json +2 -2
  62. package/src/components/MultilineTextInput.tsx +1 -1
  63. package/src/components/text-editing.ts +1 -1
  64. package/src/console/ConsoleCapture.ts +1 -1
  65. package/src/console/ConsolePopover.tsx +87 -95
  66. package/src/debug/DebugOverlay.ts +2 -2
  67. package/src/dev.tsx +7 -7
  68. package/src/exit.ts +8 -0
  69. package/src/highlight.ts +5 -7
  70. package/src/hmr-plugin.ts +2 -1
  71. package/src/hooks/use-quit.ts +2 -1
  72. package/src/hosts/base.ts +1 -1
  73. package/src/hosts/canvas.ts +11 -0
  74. package/src/hosts/single-child.ts +1 -5
  75. package/src/motion/hooks.ts +5 -2
  76. package/src/renderer/modes/InlineRenderer.ts +1 -1
  77. package/src/renderer/modes/StaticContentRenderer.ts +1 -1
  78. package/src/renderer-types.ts +6 -0
  79. package/src/renderer.ts +35 -9
  80. package/src/utils/flex-layout.ts +80 -22
@@ -17,6 +17,13 @@ export interface FlexMeasureResult {
17
17
  /**
18
18
  * Measure children along a flex axis.
19
19
  * Returns cached sizes and total size.
20
+ *
21
+ * SwiftUI-style measure:
22
+ * 1. First measure all non-greedy children with full available space
23
+ * 2. Then measure greedy children with remaining space
24
+ *
25
+ * This ensures non-greedy elements always get their natural size,
26
+ * even when sibling greedy elements have large content.
20
27
  */
21
28
  export function measureFlex(
22
29
  axis: FlexAxis,
@@ -25,26 +32,57 @@ export function measureFlex(
25
32
  maxMain: number,
26
33
  maxCross: number,
27
34
  ): FlexMeasureResult {
28
- const sizes: Size[] = []
29
- let totalMain = 0
35
+ const sizes: Size[] = new Array(children.length)
30
36
  let maxChildCross = 0
37
+ const totalSpacing = Math.max(0, (children.length - 1) * spacing)
31
38
 
39
+ // Pass 1: Measure non-greedy children first with full available space
40
+ let nonGreedyTotal = 0
32
41
  for (let i = 0; i < children.length; i++) {
33
42
  const child = children[i]
34
- const remainingMain = Math.max(0, maxMain - totalMain)
43
+ const greedyWeight = getGreedyWeight(child)
44
+
45
+ if (greedyWeight === 0) {
46
+ // Non-greedy: measure with full available space
47
+ const childMaxW = axis === "vertical" ? maxCross : maxMain
48
+ const childMaxH = axis === "vertical" ? maxMain : maxCross
49
+ const size = child.measure(childMaxW, childMaxH)
50
+ sizes[i] = size
51
+ nonGreedyTotal += mainSize(axis, size)
52
+ maxChildCross = Math.max(maxChildCross, crossSize(axis, size))
53
+ }
54
+ }
35
55
 
36
- // Convert main/cross back to width/height for child measure
37
- const childMaxW = axis === "vertical" ? maxCross : remainingMain
38
- const childMaxH = axis === "vertical" ? remainingMain : maxCross
56
+ // Pass 2: Measure greedy children with remaining space
57
+ const remainingForGreedy = Math.max(0, maxMain - nonGreedyTotal - totalSpacing)
58
+ let totalGreedyWeight = 0
59
+ for (let i = 0; i < children.length; i++) {
60
+ const greedyWeight = getGreedyWeight(children[i])
61
+ if (greedyWeight > 0) totalGreedyWeight += greedyWeight
62
+ }
39
63
 
40
- const size = child.measure(childMaxW, childMaxH)
41
- sizes.push(size)
64
+ let greedyMeasuredTotal = 0
65
+ for (let i = 0; i < children.length; i++) {
66
+ const child = children[i]
67
+ const greedyWeight = getGreedyWeight(child)
42
68
 
43
- totalMain += mainSize(axis, size)
44
- if (i < children.length - 1) totalMain += spacing
45
- maxChildCross = Math.max(maxChildCross, crossSize(axis, size))
69
+ if (greedyWeight > 0) {
70
+ // Greedy: measure with proportional share of remaining space
71
+ const greedyMain =
72
+ totalGreedyWeight > 0 ? (remainingForGreedy * greedyWeight) / totalGreedyWeight : remainingForGreedy
73
+ const childMaxW = axis === "vertical" ? maxCross : greedyMain
74
+ const childMaxH = axis === "vertical" ? greedyMain : maxCross
75
+ const size = child.measure(childMaxW, childMaxH)
76
+ sizes[i] = size
77
+ greedyMeasuredTotal += mainSize(axis, size)
78
+ maxChildCross = Math.max(maxChildCross, crossSize(axis, size))
79
+ }
46
80
  }
47
81
 
82
+ // Calculate total main dimension
83
+ // Use actual measured sizes, not the constraint
84
+ const totalMain = nonGreedyTotal + greedyMeasuredTotal + totalSpacing
85
+
48
86
  // Build total size from main/cross dimensions
49
87
  const totalW = axis === "vertical" ? maxChildCross : totalMain
50
88
  const totalH = axis === "vertical" ? totalMain : maxChildCross
@@ -68,6 +106,13 @@ function getGreedyWeight(child: HostInstance): number {
68
106
 
69
107
  /**
70
108
  * Layout children along a flex axis using cached sizes.
109
+ *
110
+ * SwiftUI-style layout:
111
+ * 1. Non-greedy children get their natural (measured) size
112
+ * 2. Greedy children share the REMAINING space proportionally
113
+ *
114
+ * This ensures non-greedy elements (like footers) are always visible,
115
+ * even when greedy elements (like scroll views) have overflowing content.
71
116
  */
72
117
  export function layoutFlex(
73
118
  axis: FlexAxis,
@@ -78,23 +123,29 @@ export function layoutFlex(
78
123
  alignment: FlexAlignment,
79
124
  stretchCross: boolean,
80
125
  ): void {
81
- // Calculate totals
82
- let totalNaturalMain = 0
83
- let totalGreedyWeight = 0
84
126
  const totalSpacing = Math.max(0, (children.length - 1) * spacing)
127
+ const availableMain = mainDim(axis, rect)
128
+
129
+ // Pass 1: Calculate space needed by non-greedy children
130
+ let nonGreedyTotal = 0
131
+ let totalGreedyWeight = 0
85
132
 
86
133
  for (let i = 0; i < children.length; i++) {
87
134
  const child = children[i]
88
135
  const size = cachedSizes[i] ?? child.measure(rect.w, rect.h)
89
- totalNaturalMain += mainSize(axis, size)
90
- totalGreedyWeight += getGreedyWeight(child)
136
+ const greedyWeight = getGreedyWeight(child)
137
+
138
+ if (greedyWeight === 0) {
139
+ // Non-greedy: use natural size
140
+ nonGreedyTotal += mainSize(axis, size)
141
+ }
142
+ totalGreedyWeight += greedyWeight
91
143
  }
92
144
 
93
- // Calculate extra space to distribute to greedy children
94
- const availableMain = mainDim(axis, rect)
95
- const extraSpace = Math.max(0, availableMain - totalNaturalMain - totalSpacing)
145
+ // Remaining space for greedy children (after non-greedy + spacing)
146
+ const remainingForGreedy = Math.max(0, availableMain - nonGreedyTotal - totalSpacing)
96
147
 
97
- // Layout children
148
+ // Pass 2: Layout all children
98
149
  let curMainPos = mainPos(axis, rect)
99
150
  const crossStartPos = crossPos(axis, rect)
100
151
  const crossDimVal = crossDim(axis, rect)
@@ -103,9 +154,16 @@ export function layoutFlex(
103
154
  const child = children[i]
104
155
  const size = cachedSizes[i] ?? { w: 0, h: 0 }
105
156
  const greedyWeight = getGreedyWeight(child)
106
- const greedyExtra = totalGreedyWeight > 0 ? (extraSpace * greedyWeight) / totalGreedyWeight : 0
107
157
 
108
- const childMainDim = mainSize(axis, size) + greedyExtra
158
+ let childMainDim: number
159
+ if (greedyWeight > 0 && totalGreedyWeight > 0) {
160
+ // Greedy: gets proportional share of remaining space
161
+ childMainDim = (remainingForGreedy * greedyWeight) / totalGreedyWeight
162
+ } else {
163
+ // Non-greedy: gets natural size
164
+ childMainDim = mainSize(axis, size)
165
+ }
166
+
109
167
  const childCrossDim = crossSize(axis, size)
110
168
 
111
169
  // Calculate cross position based on alignment