pythonnative 0.35.0__py3-none-any.whl → 0.37.0__py3-none-any.whl

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 (176) hide show
  1. pythonnative/__init__.py +6 -4
  2. pythonnative/_ios_log.py +1 -1
  3. pythonnative/alerts.py +34 -88
  4. pythonnative/bootstrap.py +90 -0
  5. pythonnative/bridge/__init__.py +306 -0
  6. pythonnative/bridge/android.py +78 -0
  7. pythonnative/bridge/codec.py +164 -0
  8. pythonnative/bridge/fake.py +236 -0
  9. pythonnative/bridge/ios.py +138 -0
  10. pythonnative/cli/pn.py +24 -6
  11. pythonnative/components/layout.py +65 -3
  12. pythonnative/gestures.py +13 -10
  13. pythonnative/hooks.py +41 -26
  14. pythonnative/hosts/__init__.py +13 -36
  15. pythonnative/hosts/native.py +348 -0
  16. pythonnative/images.py +6 -5
  17. pythonnative/mutations.py +21 -0
  18. pythonnative/native_modules/__init__.py +30 -29
  19. pythonnative/native_modules/app_state.py +10 -6
  20. pythonnative/native_modules/battery.py +20 -84
  21. pythonnative/native_modules/biometrics.py +11 -124
  22. pythonnative/native_modules/camera.py +15 -291
  23. pythonnative/native_modules/clipboard.py +14 -109
  24. pythonnative/native_modules/desktop.py +322 -0
  25. pythonnative/native_modules/file_system.py +15 -21
  26. pythonnative/native_modules/haptics.py +19 -129
  27. pythonnative/native_modules/linking.py +24 -137
  28. pythonnative/native_modules/location.py +28 -180
  29. pythonnative/native_modules/net_info.py +25 -135
  30. pythonnative/native_modules/notifications.py +36 -257
  31. pythonnative/native_modules/permissions.py +20 -184
  32. pythonnative/native_modules/registry.py +513 -0
  33. pythonnative/native_modules/secure_store.py +18 -155
  34. pythonnative/native_modules/share.py +8 -113
  35. pythonnative/native_views/__init__.py +67 -86
  36. pythonnative/native_views/bridge_backend.py +264 -0
  37. pythonnative/platform.py +19 -15
  38. pythonnative/platform_metrics.py +3 -3
  39. pythonnative/project/android.py +0 -3
  40. pythonnative/project/builder.py +38 -16
  41. pythonnative/project/config.py +12 -2
  42. pythonnative/project/plugins.py +366 -0
  43. pythonnative/py.typed +0 -0
  44. pythonnative/runtime.py +35 -203
  45. pythonnative/sdk/__init__.py +48 -33
  46. pythonnative/sdk/_components.py +57 -76
  47. pythonnative/storage.py +18 -243
  48. pythonnative/templates/android_template/app/build.gradle +6 -9
  49. pythonnative/templates/android_template/app/src/main/java/com/pythonnative/android_template/MainActivity.kt +34 -166
  50. pythonnative/templates/android_template/app/src/main/java/com/pythonnative/android_template/ScreenFragment.kt +13 -151
  51. pythonnative/templates/android_template/pythonnative/build.gradle +48 -0
  52. pythonnative/templates/android_template/pythonnative/src/main/AndroidManifest.xml +11 -0
  53. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/PNBridge.kt +214 -0
  54. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/PythonHost.kt +20 -0
  55. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/animation/AnimationSpecs.kt +141 -0
  56. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/animation/PNAnimator.kt +249 -0
  57. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/bridge/JsonUtil.kt +175 -0
  58. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/bridge/MainThread.kt +32 -0
  59. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/bridge/PNLog.kt +33 -0
  60. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/bridge/PNRegistry.kt +82 -0
  61. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/bridge/PNTransaction.kt +80 -0
  62. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/bridge/TransactionApplier.kt +115 -0
  63. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/bridge/ViewRegistry.kt +74 -0
  64. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/BuiltinComponents.kt +35 -0
  65. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/ButtonManager.kt +33 -0
  66. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/ComponentManager.kt +213 -0
  67. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/ContainerManagers.kt +69 -0
  68. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/ControlManagers.kt +186 -0
  69. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/DatePickerManager.kt +123 -0
  70. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/ImageLoader.kt +141 -0
  71. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/ImageManager.kt +156 -0
  72. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/ModalManager.kt +134 -0
  73. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/PNColor.kt +201 -0
  74. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/PickerManager.kt +87 -0
  75. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/PortalManager.kt +55 -0
  76. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/PressableManager.kt +122 -0
  77. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/ScrollViewManager.kt +214 -0
  78. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/SegmentedControlManager.kt +113 -0
  79. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/StatusBarManager.kt +52 -0
  80. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/TabBarManager.kt +131 -0
  81. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/TextInputManager.kt +245 -0
  82. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/TextManager.kt +233 -0
  83. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/ViewStyler.kt +355 -0
  84. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/VirtualListManager.kt +179 -0
  85. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/WebViewManager.kt +149 -0
  86. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/gestures/GestureArbiter.kt +233 -0
  87. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/gestures/GestureCoordinator.kt +127 -0
  88. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/gestures/GestureRecognizers.kt +527 -0
  89. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/modules/BuiltinModules.kt +73 -0
  90. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/modules/DeviceModules.kt +316 -0
  91. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/modules/HostModule.kt +97 -0
  92. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/modules/MediaModules.kt +200 -0
  93. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/modules/NativeModule.kt +135 -0
  94. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/modules/NotificationsModule.kt +100 -0
  95. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/modules/PermissionsModule.kt +88 -0
  96. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/modules/StorageModules.kt +89 -0
  97. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/modules/SystemModules.kt +178 -0
  98. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/plugins/GeneratedPlugins.kt +16 -0
  99. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/screens/Navigator.kt +116 -0
  100. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/screens/PNScreenFragment.kt +223 -0
  101. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/screens/ScreenRegistry.kt +80 -0
  102. pythonnative/templates/android_template/{app/src/main/java/com/pythonnative/android_template → pythonnative/src/main/java/com/pythonnative/runtime/views}/PNAccessibilityDelegate.kt +5 -4
  103. pythonnative/templates/android_template/{app/src/main/java/com/pythonnative/android_template → pythonnative/src/main/java/com/pythonnative/runtime/views}/PNBorderDrawable.kt +12 -3
  104. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/views/PNEditText.kt +18 -0
  105. pythonnative/templates/android_template/{app/src/main/java/com/pythonnative/android_template → pythonnative/src/main/java/com/pythonnative/runtime/views}/PNFrameLayout.kt +9 -10
  106. pythonnative/templates/android_template/{app/src/main/java/com/pythonnative/android_template → pythonnative/src/main/java/com/pythonnative/runtime/views}/PNVirtualListView.java +9 -12
  107. pythonnative/templates/android_template/pythonnative/src/test/java/com/pythonnative/runtime/AnimationSpecsTest.kt +75 -0
  108. pythonnative/templates/android_template/pythonnative/src/test/java/com/pythonnative/runtime/GestureArbiterTest.kt +197 -0
  109. pythonnative/templates/android_template/pythonnative/src/test/java/com/pythonnative/runtime/PNColorTest.kt +47 -0
  110. pythonnative/templates/android_template/pythonnative/src/test/java/com/pythonnative/runtime/PNTransactionTest.kt +78 -0
  111. pythonnative/templates/android_template/pythonnative/src/test/java/com/pythonnative/runtime/PromiseTest.kt +75 -0
  112. pythonnative/templates/android_template/settings.gradle +1 -0
  113. pythonnative/templates/ios_template/PythonNativeKit/Package.swift +21 -0
  114. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Animation/PNAnimator.swift +301 -0
  115. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Bridge/PNBridge.swift +191 -0
  116. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Bridge/PNRegistry.swift +140 -0
  117. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Bridge/PNTransaction.swift +165 -0
  118. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Bridge/PNViewRegistry.swift +53 -0
  119. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNButtonManagers.swift +206 -0
  120. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNColor.swift +173 -0
  121. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNComponentManager.swift +183 -0
  122. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNContainerView.swift +56 -0
  123. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNControlManagers.swift +281 -0
  124. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNImageManager.swift +221 -0
  125. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNOverlayManagers.swift +243 -0
  126. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNPressableManager.swift +114 -0
  127. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNScrollViewManager.swift +197 -0
  128. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNTabBarManager.swift +106 -0
  129. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNTextInputManager.swift +289 -0
  130. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNTextManager.swift +230 -0
  131. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNTransform.swift +82 -0
  132. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNViewManager.swift +86 -0
  133. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNViewState.swift +87 -0
  134. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNViewStyler.swift +340 -0
  135. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNVirtualListManager.swift +257 -0
  136. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNWebViewManager.swift +124 -0
  137. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Gestures/PNGestureCoordinator.swift +260 -0
  138. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Modules/AlertModule.swift +92 -0
  139. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Modules/HostModule.swift +158 -0
  140. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Modules/LifecycleModules.swift +174 -0
  141. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Modules/MediaModules.swift +177 -0
  142. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Modules/PNNativeModule.swift +125 -0
  143. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Modules/PermissionModules.swift +251 -0
  144. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Modules/SystemModules.swift +251 -0
  145. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Plugins/PNPluginRegistration.swift +8 -0
  146. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Screens/PNScreenRegistry.swift +46 -0
  147. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Screens/PNViewController.swift +251 -0
  148. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Support/PNCompat.swift +46 -0
  149. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Support/PNJSON.swift +173 -0
  150. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Support/PNLog.swift +47 -0
  151. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Support/PNWindow.swift +51 -0
  152. pythonnative/templates/ios_template/PythonNativeKit/Tests/PythonNativeKitTests/PNColorTests.swift +50 -0
  153. pythonnative/templates/ios_template/PythonNativeKit/Tests/PythonNativeKitTests/PNGestureTests.swift +101 -0
  154. pythonnative/templates/ios_template/PythonNativeKit/Tests/PythonNativeKitTests/PNManagerTests.swift +124 -0
  155. pythonnative/templates/ios_template/PythonNativeKit/Tests/PythonNativeKitTests/PNModuleTests.swift +98 -0
  156. pythonnative/templates/ios_template/PythonNativeKit/Tests/PythonNativeKitTests/PNTransactionTests.swift +91 -0
  157. pythonnative/templates/ios_template/ios_template/AppDelegate.swift +8 -50
  158. pythonnative/templates/ios_template/ios_template/Info.plist +4 -4
  159. pythonnative/templates/ios_template/ios_template/PythonRuntime.swift +38 -46
  160. pythonnative/templates/ios_template/ios_template/SceneDelegate.swift +11 -16
  161. pythonnative/templates/ios_template/ios_template/ViewController.swift +12 -142
  162. pythonnative/templates/ios_template/ios_template.xcodeproj/project.pbxproj +47 -1
  163. pythonnative/utils.py +4 -82
  164. pythonnative/virtual_rows.py +3 -3
  165. {pythonnative-0.35.0.dist-info → pythonnative-0.37.0.dist-info}/METADATA +3 -5
  166. pythonnative-0.37.0.dist-info/RECORD +263 -0
  167. pythonnative/hosts/android.py +0 -269
  168. pythonnative/hosts/ios.py +0 -404
  169. pythonnative/native_views/android.py +0 -3702
  170. pythonnative/native_views/ios.py +0 -5253
  171. pythonnative/templates/android_template/app/src/main/java/com/pythonnative/android_template/Navigator.kt +0 -43
  172. pythonnative-0.35.0.dist-info/RECORD +0 -155
  173. {pythonnative-0.35.0.dist-info → pythonnative-0.37.0.dist-info}/WHEEL +0 -0
  174. {pythonnative-0.35.0.dist-info → pythonnative-0.37.0.dist-info}/entry_points.txt +0 -0
  175. {pythonnative-0.35.0.dist-info → pythonnative-0.37.0.dist-info}/licenses/LICENSE +0 -0
  176. {pythonnative-0.35.0.dist-info → pythonnative-0.37.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,165 @@
1
+ import UIKit
2
+
3
+ /// Decodes and applies reconciler transactions.
4
+ ///
5
+ /// A transaction is a JSON array of ops:
6
+ ///
7
+ /// - `["c", tag, "Type", {props}]` create a view
8
+ /// - `["u", tag, {changed}]` apply changed props (`null` removes a prop)
9
+ /// - `["i", parent, child, index]` ensure `child` is at `index` under `parent`
10
+ /// - `["d", tag]` destroy the view
11
+ /// - `["f", tag, x, y, w, h]` set the frame in points
12
+ ///
13
+ /// Ops are applied strictly in order; a failing op is logged and skipped
14
+ /// so one bad prop can't desync the rest of the tree.
15
+ public enum PNTransaction {
16
+ /// A decoded op.
17
+ public enum Op: Equatable {
18
+ case create(tag: Int64, type: String, props: [String: Any])
19
+ case update(tag: Int64, changed: [String: Any])
20
+ case insert(parent: Int64, child: Int64, index: Int)
21
+ case destroy(tag: Int64)
22
+ case frame(tag: Int64, x: Double, y: Double, w: Double, h: Double)
23
+
24
+ public static func == (lhs: Op, rhs: Op) -> Bool {
25
+ switch (lhs, rhs) {
26
+ case let (.create(t1, ty1, p1), .create(t2, ty2, p2)):
27
+ return t1 == t2 && ty1 == ty2 && NSDictionary(dictionary: p1).isEqual(to: p2)
28
+ case let (.update(t1, p1), .update(t2, p2)):
29
+ return t1 == t2 && NSDictionary(dictionary: p1).isEqual(to: p2)
30
+ case let (.insert(p1, c1, i1), .insert(p2, c2, i2)):
31
+ return p1 == p2 && c1 == c2 && i1 == i2
32
+ case let (.destroy(t1), .destroy(t2)):
33
+ return t1 == t2
34
+ case let (.frame(t1, x1, y1, w1, h1), .frame(t2, x2, y2, w2, h2)):
35
+ return t1 == t2 && x1 == x2 && y1 == y2 && w1 == w2 && h1 == h2
36
+ default:
37
+ return false
38
+ }
39
+ }
40
+ }
41
+
42
+ /// Errors raised while decoding a single op.
43
+ public enum DecodeError: Error, Equatable {
44
+ case notAnArray
45
+ case malformedOp(index: Int)
46
+ case unknownOpcode(String)
47
+ }
48
+
49
+ // MARK: - Decoding
50
+
51
+ /// Decode a transaction document into ops. Malformed ops are skipped
52
+ /// (and logged) rather than failing the whole batch.
53
+ public static func decode(_ json: String) throws -> [Op] {
54
+ guard let root = PNJSON.decode(json) as? [Any] else { throw DecodeError.notAnArray }
55
+ var ops: [Op] = []
56
+ ops.reserveCapacity(root.count)
57
+ for (index, raw) in root.enumerated() {
58
+ do {
59
+ ops.append(try decodeOp(raw, index: index))
60
+ } catch {
61
+ PNLog.rateLimited(PNLog.bridge, key: "decode-op", "skipping malformed op #\(index): \(error)")
62
+ }
63
+ }
64
+ return ops
65
+ }
66
+
67
+ static func decodeOp(_ raw: Any, index: Int) throws -> Op {
68
+ guard let parts = raw as? [Any], let code = parts.first as? String else {
69
+ throw DecodeError.malformedOp(index: index)
70
+ }
71
+ func tag(_ i: Int) throws -> Int64 {
72
+ guard i < parts.count, let value = PNProps.double(parts[i]), value.isFinite else {
73
+ throw DecodeError.malformedOp(index: index)
74
+ }
75
+ return Int64(value)
76
+ }
77
+ func number(_ i: Int) throws -> Double {
78
+ guard i < parts.count, let value = PNProps.double(parts[i]) else {
79
+ throw DecodeError.malformedOp(index: index)
80
+ }
81
+ return value
82
+ }
83
+ switch code {
84
+ case "c":
85
+ guard parts.count >= 4, let type = parts[2] as? String else { throw DecodeError.malformedOp(index: index) }
86
+ let props = (PNJSON.resolveInfinity(parts[3]) as? [String: Any]) ?? [:]
87
+ return .create(tag: try tag(1), type: type, props: props)
88
+ case "u":
89
+ guard parts.count >= 3 else { throw DecodeError.malformedOp(index: index) }
90
+ let changed = (PNJSON.resolveInfinity(parts[2]) as? [String: Any]) ?? [:]
91
+ return .update(tag: try tag(1), changed: changed)
92
+ case "i":
93
+ guard parts.count >= 4 else { throw DecodeError.malformedOp(index: index) }
94
+ return .insert(parent: try tag(1), child: try tag(2), index: Int(try number(3)))
95
+ case "d":
96
+ return .destroy(tag: try tag(1))
97
+ case "f":
98
+ guard parts.count >= 6 else { throw DecodeError.malformedOp(index: index) }
99
+ return .frame(tag: try tag(1), x: try number(2), y: try number(3), w: try number(4), h: try number(5))
100
+ default:
101
+ throw DecodeError.unknownOpcode(code)
102
+ }
103
+ }
104
+
105
+ // MARK: - Applying
106
+
107
+ /// Decode and apply a transaction document.
108
+ public static func apply(_ json: String) {
109
+ let ops: [Op]
110
+ do {
111
+ ops = try decode(json)
112
+ } catch {
113
+ PNLog.bridge.error("transaction rejected: \(String(describing: error))")
114
+ return
115
+ }
116
+ apply(ops)
117
+ }
118
+
119
+ /// Apply already-decoded ops in order with per-op error isolation.
120
+ public static func apply(_ ops: [Op]) {
121
+ let registry = PNViewRegistry.shared
122
+ for op in ops {
123
+ switch op {
124
+ case let .create(tag, type, props):
125
+ if registry.resolve(tag) != nil {
126
+ PNLog.rateLimited(PNLog.bridge, key: "dup-create", "create for tag \(tag) ignored: already exists")
127
+ continue
128
+ }
129
+ let manager = PNRegistry.shared.manager(for: type)
130
+ let view = manager.createView(tag: tag, props: props)
131
+ registry.register(PNViewRecord(tag: tag, typeName: type, view: view, manager: manager))
132
+ manager.didCreate(view: view, tag: tag, props: props)
133
+
134
+ case let .update(tag, changed):
135
+ guard let record = registry.resolve(tag) else {
136
+ PNLog.rateLimited(PNLog.bridge, key: "missing-update", "update for unknown tag \(tag)")
137
+ continue
138
+ }
139
+ record.manager.update(view: record.view, changed: changed)
140
+
141
+ case let .insert(parent, child, index):
142
+ guard let parentRecord = registry.resolve(parent), let childRecord = registry.resolve(child) else {
143
+ PNLog.rateLimited(PNLog.bridge, key: "missing-insert", "insert \(child) into \(parent): unknown tag")
144
+ continue
145
+ }
146
+ parentRecord.manager.insertChild(parent: parentRecord.view, child: childRecord.view, index: index)
147
+
148
+ case let .destroy(tag):
149
+ guard let record = registry.unregister(tag) else { continue }
150
+ if let parent = record.view.superview,
151
+ let parentState = PNViewState.existing(for: parent),
152
+ let parentRecord = registry.resolve(parentState.tag)
153
+ {
154
+ parentRecord.manager.removeChild(parent: parent, child: record.view)
155
+ }
156
+ record.manager.destroy(view: record.view)
157
+ PNViewState.detach(record.view)
158
+
159
+ case let .frame(tag, x, y, w, h):
160
+ guard let record = registry.resolve(tag) else { continue }
161
+ record.manager.setFrame(view: record.view, x: x, y: y, w: w, h: h)
162
+ }
163
+ }
164
+ }
165
+ }
@@ -0,0 +1,53 @@
1
+ import UIKit
2
+
3
+ /// One live native view addressed by its reconciler tag.
4
+ public final class PNViewRecord {
5
+ public let tag: Int64
6
+ public let typeName: String
7
+ public let view: UIView
8
+ public let manager: PNComponentManager
9
+
10
+ init(tag: Int64, typeName: String, view: UIView, manager: PNComponentManager) {
11
+ self.tag = tag
12
+ self.typeName = typeName
13
+ self.view = view
14
+ self.manager = manager
15
+ }
16
+ }
17
+
18
+ /// Tag -> view table. Every view created through a transaction is
19
+ /// registered here until its `d` op arrives.
20
+ public final class PNViewRegistry {
21
+ public static let shared = PNViewRegistry()
22
+
23
+ private var records: [Int64: PNViewRecord] = [:]
24
+
25
+ private init() {}
26
+
27
+ /// The record for `tag`, or `nil` when no such view exists.
28
+ public func resolve(_ tag: Int64) -> PNViewRecord? {
29
+ records[tag]
30
+ }
31
+
32
+ /// The registered view for `tag`.
33
+ public func view(for tag: Int64) -> UIView? {
34
+ records[tag]?.view
35
+ }
36
+
37
+ /// Number of live records (used by tests and diagnostics).
38
+ public var count: Int { records.count }
39
+
40
+ func register(_ record: PNViewRecord) {
41
+ records[record.tag] = record
42
+ }
43
+
44
+ @discardableResult
45
+ func unregister(_ tag: Int64) -> PNViewRecord? {
46
+ records.removeValue(forKey: tag)
47
+ }
48
+
49
+ /// Drop every record. Intended for tests.
50
+ public func removeAll() {
51
+ records.removeAll()
52
+ }
53
+ }
@@ -0,0 +1,206 @@
1
+ import UIKit
2
+
3
+ /// A closure-backed `UIControl` target with the duplicate-send filter the
4
+ /// simulator's event injection needs (one `UIEvent`, two action sends).
5
+ public final class PNActionTarget: NSObject {
6
+ private let handler: () -> Void
7
+ private var lastTimestamp: TimeInterval?
8
+
9
+ public init(_ handler: @escaping () -> Void) {
10
+ self.handler = handler
11
+ }
12
+
13
+ /// Attach `handler` to `control` for `events`, retained by the view's state.
14
+ @discardableResult
15
+ public static func attach(_ control: UIControl, events: UIControl.Event, _ handler: @escaping () -> Void) -> PNActionTarget {
16
+ let target = PNActionTarget(handler)
17
+ control.addTarget(target, action: #selector(fire(_:forEvent:)), for: events)
18
+ PNViewState.existing(for: control)?.retained.append(target)
19
+ return target
20
+ }
21
+
22
+ @objc func fire(_ sender: Any?, forEvent event: UIEvent?) {
23
+ if let timestamp = event?.timestamp {
24
+ if lastTimestamp == timestamp {
25
+ PNLog.once(PNLog.components, key: "control-dedupe", "dropped a duplicated control action (one UIEvent, two sends)")
26
+ return
27
+ }
28
+ lastTimestamp = timestamp
29
+ }
30
+ handler()
31
+ }
32
+ }
33
+
34
+ /// `Button`: a system `UIButton` emitting `on_press`.
35
+ public final class PNButtonManager: PNComponentManager {
36
+ public override func makeView(props: [String: Any]) -> UIView {
37
+ UIButton(type: .system)
38
+ }
39
+
40
+ public override func createView(tag: Int64, props: [String: Any]) -> UIView {
41
+ let view = super.createView(tag: tag, props: props)
42
+ if let button = view as? UIButton {
43
+ PNActionTarget.attach(button, events: .touchUpInside) { [weak button] in
44
+ guard let button = button else { return }
45
+ PNEvents.emit(button, "on_press")
46
+ }
47
+ }
48
+ return view
49
+ }
50
+
51
+ public override func apply(view: UIView, props: [String: Any], initial: Bool) {
52
+ guard let button = view as? UIButton else { return }
53
+ if PNProps.has(props, "title") {
54
+ button.setTitle(PNProps.string(PNProps.value(props, "title")) ?? "", for: .normal)
55
+ }
56
+ if let size = PNProps.double(PNProps.value(props, "font_size")) {
57
+ button.titleLabel?.font = UIFont.systemFont(ofSize: CGFloat(size))
58
+ }
59
+ if let color = PNColor.parse(PNProps.value(props, "background_color")) {
60
+ button.backgroundColor = color
61
+ if !PNProps.has(props, "color") {
62
+ button.setTitleColor(.white, for: .normal)
63
+ }
64
+ }
65
+ if let color = PNColor.parse(PNProps.value(props, "color")) {
66
+ button.setTitleColor(color, for: .normal)
67
+ }
68
+ if PNProps.has(props, "enabled") {
69
+ button.isEnabled = PNProps.bool(PNProps.value(props, "enabled")) ?? true
70
+ }
71
+ PNViewStyler.applyDecoration(button, props)
72
+ }
73
+
74
+ public override func measure(view: UIView, maxW: CGFloat, maxH: CGFloat) -> CGSize {
75
+ let intrinsic = view.intrinsicContentSize
76
+ var w = intrinsic.width + 24
77
+ var h = intrinsic.height + 12
78
+ if maxW.isFinite { w = min(w, maxW) }
79
+ if maxH.isFinite { h = min(h, maxH) }
80
+ return CGSize(width: max(w, 44), height: max(h, 32))
81
+ }
82
+ }
83
+
84
+ /// `Checkbox`: an SF Symbol button toggling checked / unchecked.
85
+ public final class PNCheckboxManager: PNComponentManager {
86
+ public override func makeView(props: [String: Any]) -> UIView {
87
+ UIButton(type: .custom)
88
+ }
89
+
90
+ public override func createView(tag: Int64, props: [String: Any]) -> UIView {
91
+ let view = super.createView(tag: tag, props: props)
92
+ if let button = view as? UIButton {
93
+ PNActionTarget.attach(button, events: .touchUpInside) { [weak self, weak button] in
94
+ guard let self = self, let button = button else { return }
95
+ self.toggle(button)
96
+ }
97
+ }
98
+ return view
99
+ }
100
+
101
+ public override func apply(view: UIView, props: [String: Any], initial: Bool) {
102
+ guard let button = view as? UIButton, let state = PNViewState.existing(for: button) else { return }
103
+ if initial {
104
+ let ink = PNColor.parse("#111111") ?? .label
105
+ button.setTitleColor(ink, for: .normal)
106
+ button.tintColor = ink
107
+ }
108
+ if PNProps.has(props, "value") {
109
+ state.extras["value"] = PNProps.bool(PNProps.value(props, "value")) ?? false
110
+ }
111
+ if PNProps.has(props, "label") {
112
+ let label = PNProps.string(PNProps.value(props, "label")) ?? ""
113
+ button.setTitle(label, for: .normal)
114
+ button.accessibilityLabel = label
115
+ }
116
+ if PNProps.has(props, "disabled") {
117
+ button.isEnabled = !(PNProps.bool(PNProps.value(props, "disabled")) ?? false)
118
+ }
119
+ updateImage(button)
120
+ PNViewStyler.applyAccessibility(button, props)
121
+ }
122
+
123
+ private func toggle(_ button: UIButton) {
124
+ guard let state = PNViewState.existing(for: button) else { return }
125
+ if PNProps.bool(PNProps.value(state.props, "disabled")) == true { return }
126
+ let newValue = !state.flag("value")
127
+ // Optimistic flip so the box feels instant; the `value` prop re-syncs it.
128
+ state.extras["value"] = newValue
129
+ updateImage(button)
130
+ PNEvents.emit(button, "on_change", [newValue])
131
+ }
132
+
133
+ private func updateImage(_ button: UIButton) {
134
+ guard let state = PNViewState.existing(for: button) else { return }
135
+ let checked = state.flag("value")
136
+ guard var image = UIImage(systemName: checked ? "checkmark.square.fill" : "square") else { return }
137
+ if checked, let color = PNColor.parse(PNProps.value(state.props, "color")) {
138
+ image = image.withTintColor(color, renderingMode: .alwaysOriginal)
139
+ }
140
+ button.setImage(image, for: .normal)
141
+ }
142
+
143
+ public override func measure(view: UIView, maxW: CGFloat, maxH: CGFloat) -> CGSize {
144
+ let size = view.sizeThatFits(CGSize(width: PNComponentManager.clampConstraint(maxW), height: PNComponentManager.clampConstraint(maxH)))
145
+ var w = max(size.width + 8, 28)
146
+ if maxW.isFinite { w = min(w, maxW) }
147
+ return CGSize(width: w, height: max(size.height, 28))
148
+ }
149
+ }
150
+
151
+ /// `Picker`: a button that presents an action sheet listing the options.
152
+ public final class PNPickerManager: PNComponentManager {
153
+ public override func makeView(props: [String: Any]) -> UIView {
154
+ UIButton(type: .system)
155
+ }
156
+
157
+ public override func createView(tag: Int64, props: [String: Any]) -> UIView {
158
+ let view = super.createView(tag: tag, props: props)
159
+ if let button = view as? UIButton {
160
+ PNActionTarget.attach(button, events: .touchUpInside) { [weak self, weak button] in
161
+ guard let self = self, let button = button else { return }
162
+ self.presentSheet(button)
163
+ }
164
+ }
165
+ return view
166
+ }
167
+
168
+ public override func apply(view: UIView, props: [String: Any], initial: Bool) {
169
+ guard let button = view as? UIButton else { return }
170
+ button.setTitle(PNPickerManager.title(for: mergedProps(button)), for: .normal)
171
+ PNViewStyler.applyAccessibility(button, props)
172
+ }
173
+
174
+ static func items(_ props: [String: Any]) -> [[String: Any]] {
175
+ ((PNProps.value(props, "items") as? [Any]) ?? []).compactMap { $0 as? [String: Any] }
176
+ }
177
+
178
+ static func title(for props: [String: Any]) -> String {
179
+ let selected = PNProps.value(props, "value")
180
+ for item in items(props) {
181
+ if let value = PNProps.value(item, "value"), PNJSON.encode(value) == PNJSON.encode(selected) {
182
+ return PNProps.string(item["label"]) ?? PNProps.string(item["value"]) ?? ""
183
+ }
184
+ }
185
+ return PNProps.string(PNProps.value(props, "placeholder")) ?? "Select…"
186
+ }
187
+
188
+ private func presentSheet(_ button: UIButton) {
189
+ let merged = mergedProps(button)
190
+ let items = PNPickerManager.items(merged)
191
+ var buttons: [[String: Any]] = items.map { ["label": PNProps.string($0["label"]) ?? PNProps.string($0["value"]) ?? ""] }
192
+ buttons.append(["label": "Cancel", "style": "cancel"])
193
+ PNAlertPresenter.present(
194
+ title: PNProps.string(PNProps.value(merged, "placeholder")) ?? "Select…",
195
+ message: nil, buttons: buttons, style: "action_sheet"
196
+ ) { index in
197
+ guard index >= 0, index < items.count else { return }
198
+ PNEvents.emit(button, "on_change", [PNProps.value(items[index], "value")])
199
+ }
200
+ }
201
+
202
+ public override func measure(view: UIView, maxW: CGFloat, maxH: CGFloat) -> CGSize {
203
+ let size = view.sizeThatFits(CGSize(width: PNComponentManager.clampConstraint(maxW), height: PNComponentManager.clampConstraint(maxH)))
204
+ return CGSize(width: size.width + 16, height: size.height + 8)
205
+ }
206
+ }
@@ -0,0 +1,173 @@
1
+ import UIKit
2
+
3
+ /// Color parsing shared by every manager.
4
+ ///
5
+ /// Accepts `#RGB`, `#RGBA`, `#RRGGBB`, `#AARRGGBB` (the PythonNative
6
+ /// convention: alpha first), `rgb(r, g, b)`, `rgba(r, g, b, a)`, CSS
7
+ /// named colors, `"transparent"`, raw ARGB integers, and
8
+ /// `{"light": c, "dark": c}` dictionaries for dynamic colors.
9
+ public enum PNColor {
10
+ /// Parse `value` into a `UIColor`, or `nil` when it isn't a color.
11
+ public static func parse(_ value: Any?) -> UIColor? {
12
+ switch value {
13
+ case nil, is NSNull:
14
+ return nil
15
+ case let dict as [String: Any]:
16
+ return dynamic(dict)
17
+ case let number as NSNumber where !(value is Bool):
18
+ return fromARGB(number.int64Value)
19
+ case let int as Int:
20
+ return fromARGB(Int64(int))
21
+ case let string as String:
22
+ return parse(string: string)
23
+ default:
24
+ return nil
25
+ }
26
+ }
27
+
28
+ /// Parse a color string; see `parse(_:)` for the accepted grammar.
29
+ public static func parse(string raw: String) -> UIColor? {
30
+ let text = raw.trimmingCharacters(in: .whitespacesAndNewlines)
31
+ if text.isEmpty { return nil }
32
+ let lower = text.lowercased()
33
+ if lower == "transparent" || lower == "clear" || lower == "none" {
34
+ return UIColor.clear
35
+ }
36
+ if text.hasPrefix("#") {
37
+ return parseHex(String(text.dropFirst()))
38
+ }
39
+ if lower.hasPrefix("rgb") {
40
+ return parseRGBFunction(lower)
41
+ }
42
+ if let named = PNNamedColors.table[lower] {
43
+ return fromARGB(named)
44
+ }
45
+ if let hex = parseHex(text), text.count == 6 || text.count == 8 {
46
+ return hex
47
+ }
48
+ return nil
49
+ }
50
+
51
+ /// Components (0...1) of `color` in the sRGB space.
52
+ public static func components(_ color: UIColor) -> (r: CGFloat, g: CGFloat, b: CGFloat, a: CGFloat) {
53
+ var r: CGFloat = 0, g: CGFloat = 0, b: CGFloat = 0, a: CGFloat = 0
54
+ if color.getRed(&r, green: &g, blue: &b, alpha: &a) {
55
+ return (r, g, b, a)
56
+ }
57
+ var white: CGFloat = 0
58
+ if color.getWhite(&white, alpha: &a) {
59
+ return (white, white, white, a)
60
+ }
61
+ return (0, 0, 0, 1)
62
+ }
63
+
64
+ /// `#AARRGGBB` string for `color`, matching the Python interpolation output.
65
+ public static func hexString(_ color: UIColor) -> String {
66
+ let c = components(color)
67
+ func byte(_ v: CGFloat) -> Int { Int((max(0, min(1, v)) * 255).rounded()) }
68
+ return String(format: "#%02X%02X%02X%02X", byte(c.a), byte(c.r), byte(c.g), byte(c.b))
69
+ }
70
+
71
+ /// Build a color from a 32-bit ARGB integer (signed or unsigned).
72
+ public static func fromARGB(_ value: Int64) -> UIColor {
73
+ var argb = value
74
+ if argb < 0 { argb += 0x1_0000_0000 }
75
+ let a = CGFloat((argb >> 24) & 0xFF) / 255
76
+ let r = CGFloat((argb >> 16) & 0xFF) / 255
77
+ let g = CGFloat((argb >> 8) & 0xFF) / 255
78
+ let b = CGFloat(argb & 0xFF) / 255
79
+ return UIColor(red: r, green: g, blue: b, alpha: a)
80
+ }
81
+
82
+ // MARK: - Private
83
+
84
+ private static func parseHex(_ digits: String) -> UIColor? {
85
+ var hex = digits
86
+ switch hex.count {
87
+ case 3:
88
+ hex = "FF" + hex.map { "\($0)\($0)" }.joined()
89
+ case 4:
90
+ // #RGBA short form (CSS): expand then move alpha to the front.
91
+ let expanded = hex.map { "\($0)\($0)" }
92
+ hex = expanded[3] + expanded[0] + expanded[1] + expanded[2]
93
+ case 6:
94
+ hex = "FF" + hex
95
+ case 8:
96
+ break
97
+ default:
98
+ return nil
99
+ }
100
+ guard let value = Int64(hex, radix: 16) else { return nil }
101
+ return fromARGB(value)
102
+ }
103
+
104
+ private static func parseRGBFunction(_ text: String) -> UIColor? {
105
+ guard let open = text.firstIndex(of: "("), let close = text.lastIndex(of: ")"), open < close else { return nil }
106
+ let inner = text[text.index(after: open)..<close]
107
+ let parts = inner.split(whereSeparator: { $0 == "," || $0 == " " || $0 == "/" })
108
+ .map { $0.trimmingCharacters(in: .whitespaces) }
109
+ .filter { !$0.isEmpty }
110
+ guard parts.count == 3 || parts.count == 4 else { return nil }
111
+ func channel(_ s: String) -> CGFloat? {
112
+ if s.hasSuffix("%"), let v = Double(s.dropLast()) { return CGFloat(v / 100) }
113
+ guard let v = Double(s) else { return nil }
114
+ return CGFloat(v / 255)
115
+ }
116
+ func alpha(_ s: String) -> CGFloat? {
117
+ if s.hasSuffix("%"), let v = Double(s.dropLast()) { return CGFloat(v / 100) }
118
+ guard let v = Double(s) else { return nil }
119
+ return CGFloat(v)
120
+ }
121
+ guard let r = channel(parts[0]), let g = channel(parts[1]), let b = channel(parts[2]) else { return nil }
122
+ let a = parts.count == 4 ? (alpha(parts[3]) ?? 1) : 1
123
+ return UIColor(red: clamp(r), green: clamp(g), blue: clamp(b), alpha: clamp(a))
124
+ }
125
+
126
+ private static func dynamic(_ dict: [String: Any]) -> UIColor? {
127
+ let light = parse(dict["light"] ?? dict["default"])
128
+ let dark = parse(dict["dark"])
129
+ guard light != nil || dark != nil else { return nil }
130
+ return UIColor { traits in
131
+ if traits.userInterfaceStyle == .dark {
132
+ return dark ?? light ?? .clear
133
+ }
134
+ return light ?? dark ?? .clear
135
+ }
136
+ }
137
+
138
+ private static func clamp(_ v: CGFloat) -> CGFloat { max(0, min(1, v)) }
139
+ }
140
+
141
+ /// CSS named colors as ARGB integers.
142
+ enum PNNamedColors {
143
+ static let table: [String: Int64] = [
144
+ "black": 0xFF00_0000, "white": 0xFFFF_FFFF, "red": 0xFFFF_0000, "green": 0xFF00_8000,
145
+ "blue": 0xFF00_00FF, "yellow": 0xFFFF_FF00, "cyan": 0xFF00_FFFF, "aqua": 0xFF00_FFFF,
146
+ "magenta": 0xFFFF_00FF, "fuchsia": 0xFFFF_00FF, "gray": 0xFF80_8080, "grey": 0xFF80_8080,
147
+ "silver": 0xFFC0_C0C0, "maroon": 0xFF80_0000, "olive": 0xFF80_8000, "lime": 0xFF00_FF00,
148
+ "teal": 0xFF00_8080, "navy": 0xFF00_0080, "purple": 0xFF80_0080, "orange": 0xFFFF_A500,
149
+ "pink": 0xFFFF_C0CB, "brown": 0xFFA5_2A2A, "gold": 0xFFFF_D700, "indigo": 0xFF4B_0082,
150
+ "violet": 0xFFEE_82EE, "tomato": 0xFFFF_6347, "coral": 0xFFFF_7F50, "salmon": 0xFFFA_8072,
151
+ "crimson": 0xFFDC_143C, "khaki": 0xFFF0_E68C, "beige": 0xFFF5_F5DC, "ivory": 0xFFFF_FFF0,
152
+ "lavender": 0xFFE6_E6FA, "plum": 0xFFDD_A0DD, "orchid": 0xFFDA_70D6, "turquoise": 0xFF40_E0D0,
153
+ "tan": 0xFFD2_B48C, "chocolate": 0xFFD2_691E, "firebrick": 0xFFB2_2222, "darkred": 0xFF8B_0000,
154
+ "darkgreen": 0xFF00_6400, "darkblue": 0xFF00_008B, "darkgray": 0xFFA9_A9A9, "darkgrey": 0xFFA9_A9A9,
155
+ "dimgray": 0xFF69_6969, "dimgrey": 0xFF69_6969, "lightgray": 0xFFD3_D3D3, "lightgrey": 0xFFD3_D3D3,
156
+ "lightblue": 0xFFAD_D8E6, "lightgreen": 0xFF90_EE90, "lightyellow": 0xFFFF_FFE0, "lightpink": 0xFFFF_B6C1,
157
+ "skyblue": 0xFF87_CEEB, "steelblue": 0xFF46_82B4, "royalblue": 0xFF41_69E1, "dodgerblue": 0xFF1E_90FF,
158
+ "deepskyblue": 0xFF00_BFFF, "slategray": 0xFF70_8090, "slategrey": 0xFF70_8090, "whitesmoke": 0xFFF5_F5F5,
159
+ "gainsboro": 0xFFDC_DCDC, "snow": 0xFFFF_FAFA, "seagreen": 0xFF2E_8B57, "forestgreen": 0xFF22_8B22,
160
+ "limegreen": 0xFF32_CD32, "springgreen": 0xFF00_FF7F, "mintcream": 0xFFF5_FFFA, "aquamarine": 0xFF7F_FFD4,
161
+ "hotpink": 0xFFFF_69B4, "deeppink": 0xFFFF_1493, "orangered": 0xFFFF_4500, "darkorange": 0xFFFF_8C00,
162
+ "goldenrod": 0xFFDA_A520, "sienna": 0xFFA0_522D, "peru": 0xFFCD_853F, "wheat": 0xFFF5_DEB3,
163
+ "linen": 0xFFFA_F0E6, "azure": 0xFFF0_FFFF, "honeydew": 0xFFF0_FFF0, "midnightblue": 0xFF19_1970,
164
+ "rebeccapurple": 0xFF66_3399, "mediumpurple": 0xFF93_70DB, "slateblue": 0xFF6A_5ACD, "cadetblue": 0xFF5F_9EA0,
165
+ "cornflowerblue": 0xFF64_95ED, "darkcyan": 0xFF00_8B8B, "darkmagenta": 0xFF8B_008B, "darkviolet": 0xFF94_00D3,
166
+ "darkslategray": 0xFF2F_4F4F, "darkslategrey": 0xFF2F_4F4F, "lightcoral": 0xFFF0_8080, "lightsalmon": 0xFFFF_A07A,
167
+ "lightseagreen": 0xFF20_B2AA, "lightskyblue": 0xFF87_CEFA, "lightslategray": 0xFF77_8899, "lightslategrey": 0xFF77_8899,
168
+ "lightsteelblue": 0xFFB0_C4DE, "mediumblue": 0xFF00_00CD, "mediumseagreen": 0xFF3C_B371, "mediumvioletred": 0xFFC7_1585,
169
+ "olivedrab": 0xFF6B_8E23, "palegreen": 0xFF98_FB98, "paleturquoise": 0xFFAF_EEEE, "palevioletred": 0xFFDB_7093,
170
+ "powderblue": 0xFFB0_E0E6, "rosybrown": 0xFFBC_8F8F, "saddlebrown": 0xFF8B_4513, "sandybrown": 0xFFF4_A460,
171
+ "thistle": 0xFFD8_BFD8, "yellowgreen": 0xFF9A_CD32, "chartreuse": 0xFF7F_FF00, "greenyellow": 0xFFAD_FF2F,
172
+ ]
173
+ }