@thelacanians/vue-native-cli 0.4.15 → 0.6.0

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 (116) hide show
  1. package/dist/cli.js +329 -15
  2. package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Bridge/NativeBridge.kt +118 -0
  3. package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Components/Factories/VViewFactory.kt +178 -1
  4. package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Modules/GeneratedModuleRegistry.kt +28 -0
  5. package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Modules/NativeModuleRegistry.kt +3 -0
  6. package/native/android/VueNativeCore/src/test/kotlin/com/vuenative/core/ComponentFactoryTest.kt +674 -0
  7. package/native/android/VueNativeCore/src/test/kotlin/com/vuenative/core/ErrorOverlayViewTest.kt +183 -0
  8. package/native/android/VueNativeCore/src/test/kotlin/com/vuenative/core/EventThrottleTest.kt +203 -0
  9. package/native/android/VueNativeCore/src/test/kotlin/com/vuenative/core/HotReloadManagerTest.kt +162 -0
  10. package/native/android/VueNativeCore/src/test/kotlin/com/vuenative/core/JSPolyfillsTest.kt +153 -0
  11. package/native/android/VueNativeCore/src/test/kotlin/com/vuenative/core/NativeBridgeTest.kt +6 -3
  12. package/native/android/VueNativeCore/src/test/kotlin/com/vuenative/core/NativeModuleTest.kt +475 -0
  13. package/native/android/gradle.properties +1 -0
  14. package/native/android/gradlew +1 -1
  15. package/native/ios/VueNativeCore/Package.swift +1 -1
  16. package/native/ios/VueNativeCore/Sources/VueNativeCore/Bridge/EventThrottle.swift +1 -0
  17. package/native/ios/VueNativeCore/Sources/VueNativeCore/Bridge/NativeBridge.swift +143 -5
  18. package/native/ios/VueNativeCore/Sources/VueNativeCore/Components/Factories/VTextFactory.swift +43 -0
  19. package/native/ios/VueNativeCore/Sources/VueNativeCore/Components/Factories/VViewFactory.swift +116 -4
  20. package/native/ios/VueNativeCore/Sources/VueNativeCore/Helpers/GestureWrapper.swift +100 -0
  21. package/native/ios/VueNativeCore/Sources/VueNativeCore/Modules/GeneratedModuleRegistry.swift +28 -0
  22. package/native/ios/VueNativeCore/Sources/VueNativeCore/Modules/NativeModuleRegistry.swift +3 -0
  23. package/native/ios/VueNativeCore/Tests/VueNativeCoreTests/CertificatePinningTests.swift +190 -0
  24. package/native/ios/VueNativeCore/Tests/VueNativeCoreTests/ComponentFactoryTests.swift +585 -0
  25. package/native/ios/VueNativeCore/Tests/VueNativeCoreTests/EventThrottleTests.swift +161 -0
  26. package/native/ios/VueNativeCore/Tests/VueNativeCoreTests/HotReloadManagerTests.swift +88 -0
  27. package/native/ios/VueNativeCore/Tests/VueNativeCoreTests/JSPolyfillsTests.swift +319 -0
  28. package/native/ios/VueNativeCore/Tests/VueNativeCoreTests/NativeModuleTests.swift +400 -0
  29. package/native/macos/VueNativeMacOS/Package.swift +34 -0
  30. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Bridge/ErrorOverlayView.swift +112 -0
  31. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Bridge/EventThrottle.swift +58 -0
  32. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Bridge/HotReloadManager.swift +153 -0
  33. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Bridge/JSPolyfills.swift +696 -0
  34. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Bridge/JSRuntime.swift +347 -0
  35. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Bridge/NativeBridge.swift +877 -0
  36. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Bridge/VueNativeWindowController.swift +125 -0
  37. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/ComponentRegistry.swift +209 -0
  38. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VActionSheetFactory.swift +155 -0
  39. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VActivityIndicatorFactory.swift +85 -0
  40. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VAlertDialogFactory.swift +132 -0
  41. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VButtonFactory.swift +83 -0
  42. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VCheckboxFactory.swift +108 -0
  43. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VDropdownFactory.swift +155 -0
  44. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VImageFactory.swift +270 -0
  45. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VInputFactory.swift +257 -0
  46. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VKeyboardAvoidingFactory.swift +22 -0
  47. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VListFactory.swift +324 -0
  48. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VModalFactory.swift +231 -0
  49. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VOutlineViewFactory.swift +276 -0
  50. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VPickerFactory.swift +134 -0
  51. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VPressableFactory.swift +120 -0
  52. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VProgressBarFactory.swift +71 -0
  53. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VRadioFactory.swift +193 -0
  54. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VRefreshControlFactory.swift +25 -0
  55. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VSafeAreaFactory.swift +46 -0
  56. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VScrollViewFactory.swift +190 -0
  57. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VSectionListFactory.swift +374 -0
  58. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VSegmentedControlFactory.swift +125 -0
  59. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VSliderFactory.swift +131 -0
  60. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VSplitViewFactory.swift +215 -0
  61. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VStatusBarFactory.swift +25 -0
  62. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VSwitchFactory.swift +92 -0
  63. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VTextFactory.swift +336 -0
  64. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VToolbarFactory.swift +212 -0
  65. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VVideoFactory.swift +245 -0
  66. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VViewFactory.swift +314 -0
  67. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VWebViewFactory.swift +162 -0
  68. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/NativeComponentFactory.swift +54 -0
  69. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Helpers/ClickableView.swift +100 -0
  70. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Helpers/Extensions.swift +23 -0
  71. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Helpers/GestureWrapper.swift +183 -0
  72. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Helpers/NSColor+Hex.swift +78 -0
  73. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Layout/FlippedView.swift +19 -0
  74. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Layout/LayoutNode.swift +493 -0
  75. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Modules/AnimationModule.swift +354 -0
  76. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Modules/AppStateModule.swift +62 -0
  77. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Modules/BiometryModule.swift +60 -0
  78. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Modules/CameraModule.swift +167 -0
  79. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Modules/ClipboardModule.swift +34 -0
  80. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Modules/DeviceInfoModule.swift +49 -0
  81. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Modules/DragDropModule.swift +50 -0
  82. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Modules/FileDialogModule.swift +86 -0
  83. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Modules/HapticsModule.swift +42 -0
  84. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Modules/KeyboardModule.swift +28 -0
  85. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Modules/LinkingModule.swift +49 -0
  86. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Modules/MenuModule.swift +95 -0
  87. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Modules/NativeModuleRegistry.swift +63 -0
  88. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Modules/NotificationsModule.swift +112 -0
  89. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Modules/PermissionsModule.swift +149 -0
  90. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Modules/ShareModule.swift +37 -0
  91. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Modules/WindowModule.swift +71 -0
  92. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Resources/vue-native-placeholder.js +2 -0
  93. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Styling/StyleEngine.swift +885 -0
  94. package/native/macos/VueNativeMacOS/Tests/VueNativeMacOSTests/ComponentFactoryTests.swift +80 -0
  95. package/native/macos/VueNativeMacOS/Tests/VueNativeMacOSTests/VueNativeMacOSTests.swift +149 -0
  96. package/native/shared/VueNativeShared/AGENTS.md +129 -0
  97. package/native/shared/VueNativeShared/Package.swift +14 -0
  98. package/native/shared/VueNativeShared/Sources/VueNativeShared/CertificatePinning.swift +134 -0
  99. package/native/shared/VueNativeShared/Sources/VueNativeShared/EventThrottle.swift +78 -0
  100. package/native/shared/VueNativeShared/Sources/VueNativeShared/HotReloadManager.swift +162 -0
  101. package/native/shared/VueNativeShared/Sources/VueNativeShared/JSRuntime.swift +412 -0
  102. package/native/shared/VueNativeShared/Sources/VueNativeShared/Modules/AsyncStorageModule.swift +68 -0
  103. package/native/shared/VueNativeShared/Sources/VueNativeShared/Modules/AudioModule.swift +359 -0
  104. package/native/shared/VueNativeShared/Sources/VueNativeShared/Modules/DatabaseModule.swift +259 -0
  105. package/native/shared/VueNativeShared/Sources/VueNativeShared/Modules/FileSystemModule.swift +233 -0
  106. package/native/shared/VueNativeShared/Sources/VueNativeShared/Modules/GeolocationModule.swift +156 -0
  107. package/native/shared/VueNativeShared/Sources/VueNativeShared/Modules/NetworkModule.swift +59 -0
  108. package/native/shared/VueNativeShared/Sources/VueNativeShared/Modules/PerformanceModule.swift +113 -0
  109. package/native/shared/VueNativeShared/Sources/VueNativeShared/Modules/SecureStorageModule.swift +119 -0
  110. package/native/shared/VueNativeShared/Sources/VueNativeShared/Modules/WebSocketModule.swift +212 -0
  111. package/native/shared/VueNativeShared/Sources/VueNativeShared/NativeEventDispatcher.swift +6 -0
  112. package/native/shared/VueNativeShared/Sources/VueNativeShared/NativeModule.swift +26 -0
  113. package/native/shared/VueNativeShared/Sources/VueNativeShared/NativeModuleRegistry.swift +37 -0
  114. package/native/shared/VueNativeShared/Sources/VueNativeShared/SharedJSPolyfills.swift +673 -0
  115. package/native/shared/VueNativeShared/Tests/VueNativeSharedTests/VueNativeSharedTests.swift +44 -0
  116. package/package.json +8 -2
@@ -0,0 +1,493 @@
1
+ import AppKit
2
+ import ObjectiveC
3
+
4
+ // MARK: - Layout Value
5
+
6
+ /// Represents a layout dimension value that can be points, percent, or auto.
7
+ public enum LayoutValue: Equatable {
8
+ case points(CGFloat)
9
+ case percent(CGFloat)
10
+ case auto
11
+ case undefined
12
+
13
+ var isUndefined: Bool {
14
+ if case .undefined = self { return true }
15
+ return false
16
+ }
17
+
18
+ func resolve(relativeTo parent: CGFloat) -> CGFloat? {
19
+ switch self {
20
+ case .points(let v): return v
21
+ case .percent(let v): return parent * v / 100.0
22
+ case .auto, .undefined: return nil
23
+ }
24
+ }
25
+ }
26
+
27
+ // MARK: - Layout Enums
28
+
29
+ public enum FlexDirection {
30
+ case column, row, columnReverse, rowReverse
31
+
32
+ var isRow: Bool {
33
+ self == .row || self == .rowReverse
34
+ }
35
+ var isReverse: Bool {
36
+ self == .columnReverse || self == .rowReverse
37
+ }
38
+ }
39
+
40
+ public enum JustifyContent {
41
+ case flexStart, flexEnd, center, spaceBetween, spaceAround, spaceEvenly
42
+ }
43
+
44
+ public enum AlignItems {
45
+ case stretch, flexStart, flexEnd, center, baseline
46
+ }
47
+
48
+ public enum AlignSelf {
49
+ case auto, stretch, flexStart, flexEnd, center, baseline
50
+ }
51
+
52
+ public enum PositionType {
53
+ case relative, absolute
54
+ }
55
+
56
+ public enum DisplayType {
57
+ case flex, none
58
+ }
59
+
60
+ public enum FlexWrap {
61
+ case noWrap, wrap, wrapReverse
62
+ }
63
+
64
+ // MARK: - Edges
65
+
66
+ /// Stores inset values (top, right, bottom, left).
67
+ public struct EdgeInsets: Equatable {
68
+ public var top: CGFloat
69
+ public var right: CGFloat
70
+ public var bottom: CGFloat
71
+ public var left: CGFloat
72
+
73
+ public static let zero = EdgeInsets(top: 0, right: 0, bottom: 0, left: 0)
74
+
75
+ public var horizontal: CGFloat { left + right }
76
+ public var vertical: CGFloat { top + bottom }
77
+ }
78
+
79
+ // MARK: - LayoutNode
80
+
81
+ /// A simplified flexbox layout node. Stores flex properties and computes frames
82
+ /// for its children. Associates with an NSView via objc_setAssociatedObject.
83
+ ///
84
+ /// This implements a subset of the CSS Flexbox specification sufficient for
85
+ /// Phase 1 (counter app): direction, justify, align, padding, margin, gap,
86
+ /// flex grow/shrink, and basic dimensions.
87
+ @MainActor
88
+ public final class LayoutNode {
89
+
90
+ // MARK: - Flex container properties
91
+
92
+ public var flexDirection: FlexDirection = .column
93
+ public var justifyContent: JustifyContent = .flexStart
94
+ public var alignItems: AlignItems = .stretch
95
+ public var alignContent: AlignItems = .stretch
96
+ public var flexWrap: FlexWrap = .noWrap
97
+
98
+ // MARK: - Flex item properties
99
+
100
+ public var flexGrow: CGFloat = 0
101
+ public var flexShrink: CGFloat = 1
102
+ public var flexBasis: LayoutValue = .undefined
103
+ public var alignSelf: AlignSelf = .auto
104
+
105
+ // MARK: - Dimensions
106
+
107
+ public var width: LayoutValue = .undefined
108
+ public var height: LayoutValue = .undefined
109
+ public var minWidth: LayoutValue = .undefined
110
+ public var minHeight: LayoutValue = .undefined
111
+ public var maxWidth: LayoutValue = .undefined
112
+ public var maxHeight: LayoutValue = .undefined
113
+ public var aspectRatio: CGFloat?
114
+
115
+ // MARK: - Spacing
116
+
117
+ public var padding: EdgeInsets = .zero
118
+ public var margin: EdgeInsets = .zero
119
+
120
+ /// Main axis gap between children (columnGap for row, rowGap for column).
121
+ public var gap: CGFloat = 0
122
+ public var rowGap: CGFloat?
123
+ public var columnGap: CGFloat?
124
+
125
+ // MARK: - Position
126
+
127
+ public var positionType: PositionType = .relative
128
+ public var positionTop: LayoutValue = .undefined
129
+ public var positionRight: LayoutValue = .undefined
130
+ public var positionBottom: LayoutValue = .undefined
131
+ public var positionLeft: LayoutValue = .undefined
132
+
133
+ // MARK: - Display
134
+
135
+ public var display: DisplayType = .flex
136
+ public var isEnabled: Bool = true
137
+
138
+ // MARK: - Direction (RTL/LTR)
139
+
140
+ public var layoutDirection: NSUserInterfaceLayoutDirection = .leftToRight
141
+
142
+ // MARK: - View association
143
+
144
+ weak var view: NSView?
145
+ var isDirty: Bool = true
146
+
147
+ // MARK: - Computed results
148
+
149
+ /// The computed frame after layout. Relative to parent's content area.
150
+ public var computedFrame: CGRect = .zero
151
+
152
+ // MARK: - Children
153
+
154
+ /// Returns the LayoutNode children by inspecting the view's subviews.
155
+ var children: [LayoutNode] {
156
+ guard let view = view else { return [] }
157
+ return view.subviews.compactMap { $0.layoutNode }
158
+ }
159
+
160
+ // MARK: - Mark dirty
161
+
162
+ public func markDirty() {
163
+ isDirty = true
164
+ view?.needsLayout = true
165
+ }
166
+
167
+ // MARK: - Layout algorithm
168
+
169
+ /// Perform layout calculation. Sets frames on all child views recursively.
170
+ /// - Parameters:
171
+ /// - availableWidth: The width available from the parent.
172
+ /// - availableHeight: The height available from the parent.
173
+ public func layout(availableWidth: CGFloat, availableHeight: CGFloat) {
174
+ guard display != .none else { return }
175
+
176
+ let resolvedWidth = width.resolve(relativeTo: availableWidth) ?? availableWidth
177
+ let resolvedHeight = height.resolve(relativeTo: availableHeight) ?? availableHeight
178
+
179
+ let constrainedWidth = constrain(resolvedWidth, min: minWidth.resolve(relativeTo: availableWidth), max: maxWidth.resolve(relativeTo: availableWidth))
180
+ let constrainedHeight = constrain(resolvedHeight, min: minHeight.resolve(relativeTo: availableHeight), max: maxHeight.resolve(relativeTo: availableHeight))
181
+
182
+ let contentWidth = constrainedWidth - padding.horizontal
183
+ let contentHeight = constrainedHeight - padding.vertical
184
+
185
+ // Separate children into relative (flex) and absolute positioned
186
+ let allChildren = children
187
+ let relativeChildren = allChildren.filter { $0.positionType == .relative && $0.display != .none }
188
+ let absoluteChildren = allChildren.filter { $0.positionType == .absolute && $0.display != .none }
189
+
190
+ // Compute main axis gap
191
+ let mainGap: CGFloat
192
+ if flexDirection.isRow {
193
+ mainGap = columnGap ?? gap
194
+ } else {
195
+ mainGap = rowGap ?? gap
196
+ }
197
+
198
+ // Phase 1: Measure children along main axis (hypothetical size)
199
+ let isRow = flexDirection.isRow
200
+ let mainSize = isRow ? contentWidth : contentHeight
201
+ let crossSize = isRow ? contentHeight : contentWidth
202
+
203
+ struct ChildMeasure {
204
+ let node: LayoutNode
205
+ var mainHypothetical: CGFloat
206
+ var crossHypothetical: CGFloat
207
+ var flexBasis: CGFloat
208
+ var mainFinal: CGFloat = 0
209
+ var crossFinal: CGFloat = 0
210
+ var mainMarginBefore: CGFloat
211
+ var mainMarginAfter: CGFloat
212
+ var crossMarginBefore: CGFloat
213
+ var crossMarginAfter: CGFloat
214
+ }
215
+
216
+ var measures: [ChildMeasure] = relativeChildren.map { child in
217
+ // Resolve flex basis
218
+ let basis: CGFloat
219
+ if let b = child.flexBasis.resolve(relativeTo: mainSize), !child.flexBasis.isUndefined {
220
+ basis = b
221
+ } else if isRow, let w = child.width.resolve(relativeTo: contentWidth) {
222
+ basis = w
223
+ } else if !isRow, let h = child.height.resolve(relativeTo: contentHeight) {
224
+ basis = h
225
+ } else {
226
+ // Content-based sizing: use the view's fittingSize as an estimate
227
+ basis = 0
228
+ }
229
+
230
+ let crossHyp: CGFloat
231
+ if isRow {
232
+ crossHyp = child.height.resolve(relativeTo: contentHeight) ?? crossSize
233
+ } else {
234
+ crossHyp = child.width.resolve(relativeTo: contentWidth) ?? crossSize
235
+ }
236
+
237
+ let mainMarginBefore: CGFloat
238
+ let mainMarginAfter: CGFloat
239
+ let crossMarginBefore: CGFloat
240
+ let crossMarginAfter: CGFloat
241
+ if isRow {
242
+ mainMarginBefore = child.margin.left
243
+ mainMarginAfter = child.margin.right
244
+ crossMarginBefore = child.margin.top
245
+ crossMarginAfter = child.margin.bottom
246
+ } else {
247
+ mainMarginBefore = child.margin.top
248
+ mainMarginAfter = child.margin.bottom
249
+ crossMarginBefore = child.margin.left
250
+ crossMarginAfter = child.margin.right
251
+ }
252
+
253
+ return ChildMeasure(
254
+ node: child,
255
+ mainHypothetical: basis,
256
+ crossHypothetical: crossHyp,
257
+ flexBasis: basis,
258
+ mainMarginBefore: mainMarginBefore,
259
+ mainMarginAfter: mainMarginAfter,
260
+ crossMarginBefore: crossMarginBefore,
261
+ crossMarginAfter: crossMarginAfter
262
+ )
263
+ }
264
+
265
+ // Phase 2: Flex grow/shrink
266
+ let totalGaps = measures.count > 1 ? mainGap * CGFloat(measures.count - 1) : 0
267
+ let totalMargins = measures.reduce(CGFloat(0)) { $0 + $1.mainMarginBefore + $1.mainMarginAfter }
268
+ let usedSpace = measures.reduce(CGFloat(0)) { $0 + $1.flexBasis } + totalGaps + totalMargins
269
+ let freeSpace = mainSize - usedSpace
270
+
271
+ let totalGrow = measures.reduce(CGFloat(0)) { $0 + $1.node.flexGrow }
272
+ let totalShrink = measures.reduce(CGFloat(0)) { $0 + ($1.node.flexShrink * $1.flexBasis) }
273
+
274
+ for i in measures.indices {
275
+ if freeSpace > 0 && totalGrow > 0 {
276
+ measures[i].mainFinal = measures[i].flexBasis + (freeSpace * measures[i].node.flexGrow / totalGrow)
277
+ } else if freeSpace < 0 && totalShrink > 0 {
278
+ let shrinkRatio = (measures[i].node.flexShrink * measures[i].flexBasis) / totalShrink
279
+ measures[i].mainFinal = measures[i].flexBasis + (freeSpace * shrinkRatio)
280
+ } else {
281
+ measures[i].mainFinal = measures[i].flexBasis
282
+ }
283
+
284
+ // Clamp to min/max
285
+ let child = measures[i].node
286
+ if isRow {
287
+ measures[i].mainFinal = constrain(measures[i].mainFinal, min: child.minWidth.resolve(relativeTo: contentWidth), max: child.maxWidth.resolve(relativeTo: contentWidth))
288
+ } else {
289
+ measures[i].mainFinal = constrain(measures[i].mainFinal, min: child.minHeight.resolve(relativeTo: contentHeight), max: child.maxHeight.resolve(relativeTo: contentHeight))
290
+ }
291
+
292
+ // Ensure non-negative
293
+ measures[i].mainFinal = max(0, measures[i].mainFinal)
294
+ }
295
+
296
+ // Phase 3: Cross axis sizing (alignItems)
297
+ for i in measures.indices {
298
+ let child = measures[i].node
299
+ let resolvedAlign = child.alignSelf == .auto ? alignItems : alignSelfToAlignItems(child.alignSelf)
300
+
301
+ if resolvedAlign == .stretch {
302
+ measures[i].crossFinal = crossSize - measures[i].crossMarginBefore - measures[i].crossMarginAfter
303
+ } else {
304
+ measures[i].crossFinal = measures[i].crossHypothetical
305
+ }
306
+
307
+ // Apply aspect ratio
308
+ if let ar = child.aspectRatio, ar > 0 {
309
+ if isRow {
310
+ measures[i].crossFinal = measures[i].mainFinal / ar
311
+ } else {
312
+ measures[i].crossFinal = measures[i].mainFinal * ar
313
+ }
314
+ }
315
+
316
+ // Clamp cross to min/max
317
+ if isRow {
318
+ measures[i].crossFinal = constrain(measures[i].crossFinal, min: child.minHeight.resolve(relativeTo: contentHeight), max: child.maxHeight.resolve(relativeTo: contentHeight))
319
+ } else {
320
+ measures[i].crossFinal = constrain(measures[i].crossFinal, min: child.minWidth.resolve(relativeTo: contentWidth), max: child.maxWidth.resolve(relativeTo: contentWidth))
321
+ }
322
+
323
+ measures[i].crossFinal = max(0, measures[i].crossFinal)
324
+ }
325
+
326
+ // Phase 4: Main axis positioning (justifyContent)
327
+ let totalChildMainSize = measures.reduce(CGFloat(0)) { $0 + $1.mainFinal + $1.mainMarginBefore + $1.mainMarginAfter }
328
+ let remainingMain = mainSize - totalChildMainSize - totalGaps
329
+
330
+ var mainOffset: CGFloat = 0
331
+ var mainSpacing: CGFloat = mainGap
332
+
333
+ switch justifyContent {
334
+ case .flexStart:
335
+ mainOffset = 0
336
+ case .flexEnd:
337
+ mainOffset = remainingMain
338
+ case .center:
339
+ mainOffset = remainingMain / 2
340
+ case .spaceBetween:
341
+ mainOffset = 0
342
+ if measures.count > 1 {
343
+ mainSpacing = mainGap + remainingMain / CGFloat(measures.count - 1)
344
+ }
345
+ case .spaceAround:
346
+ let space = remainingMain / CGFloat(measures.count)
347
+ mainOffset = space / 2
348
+ mainSpacing = mainGap + space
349
+ case .spaceEvenly:
350
+ let space = remainingMain / CGFloat(measures.count + 1)
351
+ mainOffset = space
352
+ mainSpacing = mainGap + space
353
+ }
354
+
355
+ // Phase 5: Position children
356
+ var currentMain = mainOffset
357
+ let orderedMeasures = flexDirection.isReverse ? measures.reversed() : Array(measures)
358
+
359
+ for measure in orderedMeasures {
360
+ let child = measure.node
361
+ currentMain += measure.mainMarginBefore
362
+
363
+ let resolvedAlign = child.alignSelf == .auto ? alignItems : alignSelfToAlignItems(child.alignSelf)
364
+
365
+ let crossOffset: CGFloat
366
+ switch resolvedAlign {
367
+ case .flexStart:
368
+ crossOffset = measure.crossMarginBefore
369
+ case .flexEnd:
370
+ crossOffset = crossSize - measure.crossFinal - measure.crossMarginAfter
371
+ case .center:
372
+ crossOffset = (crossSize - measure.crossFinal) / 2
373
+ case .stretch, .baseline:
374
+ crossOffset = measure.crossMarginBefore
375
+ }
376
+
377
+ let x: CGFloat
378
+ let y: CGFloat
379
+ let w: CGFloat
380
+ let h: CGFloat
381
+
382
+ if isRow {
383
+ x = padding.left + currentMain
384
+ y = padding.top + crossOffset
385
+ w = measure.mainFinal
386
+ h = measure.crossFinal
387
+ } else {
388
+ x = padding.left + crossOffset
389
+ y = padding.top + currentMain
390
+ w = measure.crossFinal
391
+ h = measure.mainFinal
392
+ }
393
+
394
+ child.computedFrame = CGRect(x: x, y: y, width: w, height: h)
395
+ child.view?.frame = child.computedFrame
396
+
397
+ // Recurse into child
398
+ child.layout(availableWidth: w, availableHeight: h)
399
+
400
+ currentMain += measure.mainFinal + measure.mainMarginAfter + mainSpacing
401
+ }
402
+
403
+ // Phase 6: Absolute positioned children
404
+ for child in absoluteChildren {
405
+ let childW = child.width.resolve(relativeTo: constrainedWidth) ?? 0
406
+ let childH = child.height.resolve(relativeTo: constrainedHeight) ?? 0
407
+
408
+ var x: CGFloat = padding.left
409
+ var y: CGFloat = padding.top
410
+
411
+ if let left = child.positionLeft.resolve(relativeTo: constrainedWidth) {
412
+ x = left + child.margin.left
413
+ } else if let right = child.positionRight.resolve(relativeTo: constrainedWidth) {
414
+ x = constrainedWidth - right - childW - child.margin.right
415
+ }
416
+
417
+ if let top = child.positionTop.resolve(relativeTo: constrainedHeight) {
418
+ y = top + child.margin.top
419
+ } else if let bottom = child.positionBottom.resolve(relativeTo: constrainedHeight) {
420
+ y = constrainedHeight - bottom - childH - child.margin.bottom
421
+ }
422
+
423
+ child.computedFrame = CGRect(x: x, y: y, width: childW, height: childH)
424
+ child.view?.frame = child.computedFrame
425
+
426
+ child.layout(availableWidth: childW, availableHeight: childH)
427
+ }
428
+
429
+ isDirty = false
430
+ }
431
+
432
+ // MARK: - Helpers
433
+
434
+ private func constrain(_ value: CGFloat, min: CGFloat?, max: CGFloat?) -> CGFloat {
435
+ var result = value
436
+ if let min = min { result = Swift.max(result, min) }
437
+ if let max = max { result = Swift.min(result, max) }
438
+ return result
439
+ }
440
+
441
+ private func alignSelfToAlignItems(_ alignSelf: AlignSelf) -> AlignItems {
442
+ switch alignSelf {
443
+ case .auto: return .stretch
444
+ case .stretch: return .stretch
445
+ case .flexStart: return .flexStart
446
+ case .flexEnd: return .flexEnd
447
+ case .center: return .center
448
+ case .baseline: return .baseline
449
+ }
450
+ }
451
+ }
452
+
453
+ // MARK: - NSView extension
454
+
455
+ private var layoutNodeKey: UInt8 = 0
456
+
457
+ extension NSView {
458
+ /// Access the LayoutNode associated with this view. Creates one on first access.
459
+ public var layoutNode: LayoutNode? {
460
+ get {
461
+ objc_getAssociatedObject(self, &layoutNodeKey) as? LayoutNode
462
+ }
463
+ set {
464
+ objc_setAssociatedObject(self, &layoutNodeKey, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
465
+ newValue?.view = self
466
+ }
467
+ }
468
+
469
+ /// Convenience: returns or creates a LayoutNode for this view.
470
+ @discardableResult
471
+ public func ensureLayoutNode() -> LayoutNode {
472
+ if let existing = layoutNode { return existing }
473
+ let node = LayoutNode()
474
+ self.layoutNode = node
475
+ return node
476
+ }
477
+ }
478
+
479
+ // MARK: - Percentage postfix operator
480
+
481
+ postfix operator %
482
+
483
+ public postfix func % (value: CGFloat) -> LayoutValue {
484
+ return .percent(value)
485
+ }
486
+
487
+ public postfix func % (value: Int) -> LayoutValue {
488
+ return .percent(CGFloat(value))
489
+ }
490
+
491
+ public postfix func % (value: Double) -> LayoutValue {
492
+ return .percent(CGFloat(value))
493
+ }