@nativescript/core 8.6.0-vision.2 → 8.6.0-vision.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 (203) hide show
  1. package/abortcontroller/index.d.ts +1 -1
  2. package/abortcontroller/index.js +4 -4
  3. package/abortcontroller/index.js.map +1 -1
  4. package/application/application-common.d.ts +21 -1
  5. package/application/application-common.js +23 -2
  6. package/application/application-common.js.map +1 -1
  7. package/application/application-shims.js.map +1 -1
  8. package/application/application.android.js +124 -59
  9. package/application/application.android.js.map +1 -1
  10. package/application/application.d.ts +8 -0
  11. package/application/application.ios.d.ts +2 -0
  12. package/application/application.ios.js +33 -2
  13. package/application/application.ios.js.map +1 -1
  14. package/core-types/index.d.ts +7 -0
  15. package/core-types/index.js +7 -0
  16. package/core-types/index.js.map +1 -1
  17. package/css/CSS3Parser.js.map +1 -1
  18. package/css/system-classes.d.ts +2 -2
  19. package/css/system-classes.js +4 -1
  20. package/css/system-classes.js.map +1 -1
  21. package/data/observable/index.js.map +1 -1
  22. package/debugger/InspectorBackendCommands.ios.js.map +1 -1
  23. package/debugger/webinspector-css.ios.js.map +1 -1
  24. package/debugger/webinspector-dom.ios.js.map +1 -1
  25. package/debugger/webinspector-network.ios.js.map +1 -1
  26. package/package.json +1 -1
  27. package/platforms/android/widgets-release.aar +0 -0
  28. package/platforms/ios/TNSWidgets.xcframework/Info.plist +18 -10
  29. package/platforms/ios/TNSWidgets.xcframework/ios-arm64/dSYMs/TNSWidgets.framework.dSYM/Contents/Resources/Relocations/aarch64/TNSWidgets.yml +77 -77
  30. package/platforms/ios/TNSWidgets.xcframework/ios-arm64_x86_64-maccatalyst/TNSWidgets.framework/Resources/Info.plist +3 -3
  31. package/platforms/ios/TNSWidgets.xcframework/ios-arm64_x86_64-maccatalyst/TNSWidgets.framework/Versions/A/Resources/Info.plist +3 -3
  32. package/platforms/ios/TNSWidgets.xcframework/ios-arm64_x86_64-maccatalyst/dSYMs/TNSWidgets.framework.dSYM/Contents/Resources/Relocations/aarch64/TNSWidgets.yml +77 -77
  33. package/platforms/ios/TNSWidgets.xcframework/ios-arm64_x86_64-maccatalyst/dSYMs/TNSWidgets.framework.dSYM/Contents/Resources/Relocations/x86_64/TNSWidgets.yml +77 -77
  34. package/platforms/ios/TNSWidgets.xcframework/ios-arm64_x86_64-simulator/TNSWidgets.framework/_CodeSignature/CodeResources +1 -1
  35. package/platforms/ios/TNSWidgets.xcframework/ios-arm64_x86_64-simulator/dSYMs/TNSWidgets.framework.dSYM/Contents/Resources/Relocations/aarch64/TNSWidgets.yml +77 -77
  36. package/platforms/ios/TNSWidgets.xcframework/ios-arm64_x86_64-simulator/dSYMs/TNSWidgets.framework.dSYM/Contents/Resources/Relocations/x86_64/TNSWidgets.yml +77 -77
  37. package/platforms/ios/TNSWidgets.xcframework/xros-arm64_x86_64-simulator/TNSWidgets.framework/Info.plist +0 -0
  38. package/platforms/ios/TNSWidgets.xcframework/xros-arm64_x86_64-simulator/TNSWidgets.framework/TNSWidgets +0 -0
  39. package/platforms/ios/TNSWidgets.xcframework/xros-arm64_x86_64-simulator/TNSWidgets.framework/_CodeSignature/CodeResources +1 -1
  40. package/platforms/ios/TNSWidgets.xcframework/xros-arm64_x86_64-simulator/dSYMs/TNSWidgets.framework.dSYM/Contents/Resources/DWARF/TNSWidgets +0 -0
  41. package/platforms/ios/TNSWidgets.xcframework/xros-arm64_x86_64-simulator/dSYMs/TNSWidgets.framework.dSYM/Contents/Resources/Relocations/aarch64/TNSWidgets.yml +77 -77
  42. package/platforms/ios/TNSWidgets.xcframework/xros-arm64_x86_64-simulator/dSYMs/TNSWidgets.framework.dSYM/Contents/Resources/Relocations/x86_64/TNSWidgets.yml +77 -77
  43. package/ui/action-bar/action-bar-common.js.map +1 -1
  44. package/ui/action-bar/index.android.js +3 -1
  45. package/ui/action-bar/index.android.js.map +1 -1
  46. package/ui/action-bar/index.ios.js.map +1 -1
  47. package/ui/activity-indicator/activity-indicator-common.js.map +1 -1
  48. package/ui/animation/index.ios.d.ts +1 -0
  49. package/ui/animation/index.ios.js +175 -6
  50. package/ui/animation/index.ios.js.map +1 -1
  51. package/ui/builder/xml2ui.js.map +1 -1
  52. package/ui/button/button-common.js.map +1 -1
  53. package/ui/button/index.android.js +3 -1
  54. package/ui/button/index.android.js.map +1 -1
  55. package/ui/button/index.ios.d.ts +1 -0
  56. package/ui/button/index.ios.js +27 -11
  57. package/ui/button/index.ios.js.map +1 -1
  58. package/ui/core/view/index.android.d.ts +2 -2
  59. package/ui/core/view/index.android.js +7 -5
  60. package/ui/core/view/index.android.js.map +1 -1
  61. package/ui/core/view/index.d.ts +2 -2
  62. package/ui/core/view/index.ios.d.ts +1 -1
  63. package/ui/core/view/index.ios.js +78 -21
  64. package/ui/core/view/index.ios.js.map +1 -1
  65. package/ui/core/view/view-common.d.ts +3 -3
  66. package/ui/core/view/view-common.js.map +1 -1
  67. package/ui/core/view-base/index.js +5 -0
  68. package/ui/core/view-base/index.js.map +1 -1
  69. package/ui/date-picker/date-picker-common.js.map +1 -1
  70. package/ui/date-picker/index.android.js +4 -2
  71. package/ui/date-picker/index.android.js.map +1 -1
  72. package/ui/date-picker/index.ios.js +29 -21
  73. package/ui/date-picker/index.ios.js.map +1 -1
  74. package/ui/editable-text-base/editable-text-base-common.js.map +1 -1
  75. package/ui/editable-text-base/index.android.js +3 -1
  76. package/ui/editable-text-base/index.android.js.map +1 -1
  77. package/ui/frame/fragment.transitions.android.js +5 -2
  78. package/ui/frame/fragment.transitions.android.js.map +1 -1
  79. package/ui/frame/frame-common.js.map +1 -1
  80. package/ui/frame/index.android.js +1 -1
  81. package/ui/frame/index.android.js.map +1 -1
  82. package/ui/frame/index.ios.js +17 -4
  83. package/ui/frame/index.ios.js.map +1 -1
  84. package/ui/gestures/gestures-common.d.ts +232 -7
  85. package/ui/gestures/gestures-common.js +98 -4
  86. package/ui/gestures/gestures-common.js.map +1 -1
  87. package/ui/gestures/index.d.ts +2 -289
  88. package/ui/html-view/html-view-common.js.map +1 -1
  89. package/ui/image/image-common.js.map +1 -1
  90. package/ui/image/index.android.js +3 -1
  91. package/ui/image/index.android.js.map +1 -1
  92. package/ui/image-cache/image-cache-common.js.map +1 -1
  93. package/ui/image-cache/index.ios.js +13 -13
  94. package/ui/image-cache/index.ios.js.map +1 -1
  95. package/ui/index.d.ts +1 -1
  96. package/ui/index.js +1 -1
  97. package/ui/index.js.map +1 -1
  98. package/ui/label/index.android.js.map +1 -1
  99. package/ui/label/index.ios.d.ts +1 -0
  100. package/ui/label/index.ios.js +30 -8
  101. package/ui/label/index.ios.js.map +1 -1
  102. package/ui/layouts/absolute-layout/absolute-layout-common.js.map +1 -1
  103. package/ui/layouts/dock-layout/dock-layout-common.js.map +1 -1
  104. package/ui/layouts/flexbox-layout/flexbox-layout-common.js.map +1 -1
  105. package/ui/layouts/grid-layout/grid-layout-common.js.map +1 -1
  106. package/ui/layouts/root-layout/index.ios.js +7 -2
  107. package/ui/layouts/root-layout/index.ios.js.map +1 -1
  108. package/ui/layouts/root-layout/root-layout-common.js.map +1 -1
  109. package/ui/layouts/stack-layout/stack-layout-common.js.map +1 -1
  110. package/ui/layouts/wrap-layout/wrap-layout-common.js.map +1 -1
  111. package/ui/list-picker/index.android.js +6 -2
  112. package/ui/list-picker/index.android.js.map +1 -1
  113. package/ui/list-picker/index.ios.js +1 -7
  114. package/ui/list-picker/index.ios.js.map +1 -1
  115. package/ui/list-picker/list-picker-common.js.map +1 -1
  116. package/ui/list-view/index.android.js +8 -2
  117. package/ui/list-view/index.android.js.map +1 -1
  118. package/ui/list-view/index.ios.js.map +1 -1
  119. package/ui/list-view/list-view-common.js.map +1 -1
  120. package/ui/page/index.android.js.map +1 -1
  121. package/ui/page/index.d.ts +4 -4
  122. package/ui/page/index.ios.js +12 -3
  123. package/ui/page/index.ios.js.map +1 -1
  124. package/ui/page/page-common.js.map +1 -1
  125. package/ui/placeholder/index.android.js.map +1 -1
  126. package/ui/progress/progress-common.js.map +1 -1
  127. package/ui/proxy-view-container/index.js.map +1 -1
  128. package/ui/repeater/index.js.map +1 -1
  129. package/ui/scroll-view/index.android.d.ts +1 -1
  130. package/ui/scroll-view/index.android.js +9 -5
  131. package/ui/scroll-view/index.android.js.map +1 -1
  132. package/ui/scroll-view/index.ios.d.ts +1 -2
  133. package/ui/scroll-view/index.ios.js +6 -8
  134. package/ui/scroll-view/index.ios.js.map +1 -1
  135. package/ui/scroll-view/scroll-view-common.d.ts +2 -4
  136. package/ui/scroll-view/scroll-view-common.js +17 -24
  137. package/ui/scroll-view/scroll-view-common.js.map +1 -1
  138. package/ui/search-bar/index.android.js +6 -2
  139. package/ui/search-bar/index.android.js.map +1 -1
  140. package/ui/search-bar/search-bar-common.js.map +1 -1
  141. package/ui/segmented-bar/index.android.js +6 -2
  142. package/ui/segmented-bar/index.android.js.map +1 -1
  143. package/ui/segmented-bar/segmented-bar-common.js.map +1 -1
  144. package/ui/slider/index.android.js +2 -1
  145. package/ui/slider/index.android.js.map +1 -1
  146. package/ui/slider/slider-common.js.map +1 -1
  147. package/ui/styling/background-common.d.ts +5 -4
  148. package/ui/styling/background-common.js +3 -0
  149. package/ui/styling/background-common.js.map +1 -1
  150. package/ui/styling/background.d.ts +13 -4
  151. package/ui/styling/background.ios.d.ts +26 -0
  152. package/ui/styling/background.ios.js +681 -372
  153. package/ui/styling/background.ios.js.map +1 -1
  154. package/ui/styling/box-shadow.d.ts +1 -0
  155. package/ui/styling/box-shadow.js.map +1 -1
  156. package/ui/styling/css-selector.js.map +1 -1
  157. package/ui/styling/css-shadow.d.ts +3 -3
  158. package/ui/styling/css-shadow.js +1 -1
  159. package/ui/styling/style/index.d.ts +4 -3
  160. package/ui/styling/style/index.js.map +1 -1
  161. package/ui/styling/style-properties.js +10 -1
  162. package/ui/styling/style-properties.js.map +1 -1
  163. package/ui/styling/style-scope.js.map +1 -1
  164. package/ui/switch/index.android.js +3 -1
  165. package/ui/switch/index.android.js.map +1 -1
  166. package/ui/switch/switch-common.js.map +1 -1
  167. package/ui/tab-view/index.ios.js +8 -2
  168. package/ui/tab-view/index.ios.js.map +1 -1
  169. package/ui/tab-view/tab-view-common.js.map +1 -1
  170. package/ui/text-base/index.android.d.ts +1 -0
  171. package/ui/text-base/index.android.js +24 -5
  172. package/ui/text-base/index.android.js.map +1 -1
  173. package/ui/text-base/index.d.ts +9 -3
  174. package/ui/text-base/index.ios.d.ts +2 -2
  175. package/ui/text-base/index.ios.js +5 -10
  176. package/ui/text-base/index.ios.js.map +1 -1
  177. package/ui/text-base/span.js.map +1 -1
  178. package/ui/text-base/text-base-common.d.ts +7 -4
  179. package/ui/text-base/text-base-common.js +15 -0
  180. package/ui/text-base/text-base-common.js.map +1 -1
  181. package/ui/text-field/text-field-common.js.map +1 -1
  182. package/ui/text-view/index.android.js.map +1 -1
  183. package/ui/text-view/index.ios.js.map +1 -1
  184. package/ui/time-picker/index.android.js +3 -1
  185. package/ui/time-picker/index.android.js.map +1 -1
  186. package/ui/time-picker/time-picker-common.js.map +1 -1
  187. package/ui/transition/shared-transition-helper.ios.js +15 -0
  188. package/ui/transition/shared-transition-helper.ios.js.map +1 -1
  189. package/ui/transition/shared-transition.d.ts +6 -2
  190. package/ui/transition/shared-transition.js.map +1 -1
  191. package/ui/utils.d.ts +16 -19
  192. package/ui/utils.ios.d.ts +11 -10
  193. package/ui/utils.ios.js +37 -37
  194. package/ui/utils.ios.js.map +1 -1
  195. package/ui/web-view/web-view-common.js.map +1 -1
  196. package/utils/ios/index.d.ts +0 -1
  197. package/utils/ios/index.js +0 -65
  198. package/utils/ios/index.js.map +1 -1
  199. package/utils/native-helper.d.ts +0 -7
  200. package/utils/number-utils.d.ts +11 -0
  201. package/utils/number-utils.js +18 -0
  202. package/utils/number-utils.js.map +1 -1
  203. package/xhr/index.js.map +1 -1
@@ -1,12 +1,13 @@
1
1
  import { LinearGradient } from './linear-gradient';
2
- import { Color } from '../../color';
3
- import { iOSNativeHelper, isDataURI, isFileOrResourcePath, layout } from '../../utils';
2
+ import { Screen } from '../../platform';
3
+ import { isDataURI, isFileOrResourcePath, layout } from '../../utils';
4
+ import { extendPointsToTargetY } from '../../utils/number-utils';
4
5
  import { ios as iosViewUtils } from '../utils';
5
6
  import { ImageSource } from '../../image-source';
6
7
  import { parse as cssParse } from '../../css-value';
7
- import { Length } from './style-properties';
8
8
  export * from './background-common';
9
9
  const clearCGColor = UIColor.clearColor.CGColor;
10
+ const uriPattern = /url\(('|")(.*?)\1\)/;
10
11
  const symbolUrl = Symbol('backgroundImageUrl');
11
12
  export var CacheMode;
12
13
  (function (CacheMode) {
@@ -20,59 +21,266 @@ export var ios;
20
21
  if (!nativeView) {
21
22
  return;
22
23
  }
23
- if (background.clearFlags & 2 /* BackgroundClearFlags.CLEAR_BOX_SHADOW */) {
24
- // clear box shadow if it has been removed!
25
- view.setProperty('clipToBounds', true);
26
- clearBoxShadow(nativeView);
24
+ // Unset this in case another layer handles background color (e.g. gradient)
25
+ nativeView.layer.backgroundColor = null;
26
+ // Cleanup of previous values
27
+ clearBackgroundVisualEffects(view);
28
+ // Borders, shadows, etc
29
+ drawBackgroundVisualEffects(view);
30
+ if (!background.image) {
31
+ callback(background?.color?.ios);
27
32
  }
28
- if (nativeView.hasNonUniformBorder) {
29
- unsubscribeFromScrollNotifications(view);
30
- clearNonUniformBorders(nativeView);
33
+ else {
34
+ if (!(background.image instanceof LinearGradient)) {
35
+ setUIColorFromImage(view, nativeView, callback, flip);
36
+ }
31
37
  }
32
- iosViewUtils.clearGradient(nativeView);
38
+ }
39
+ ios.createBackgroundUIColor = createBackgroundUIColor;
40
+ function drawBackgroundVisualEffects(view) {
41
+ const background = view.style.backgroundInternal;
42
+ const nativeView = view.nativeViewProtected;
43
+ const layer = nativeView.layer;
44
+ let needsLayerAdjustmentOnScroll = false;
45
+ // Add new gradient layer or update existing one
33
46
  if (background.image instanceof LinearGradient) {
34
- iosViewUtils.drawGradient(nativeView, background.image);
35
- }
36
- const hasNonUniformBorderWidths = background.hasBorderWidth() && !background.hasUniformBorder();
37
- const hasNonUniformBorderRadiuses = background.hasBorderRadius() && !background.hasUniformBorderRadius();
38
- if (background.hasUniformBorderColor() && (hasNonUniformBorderWidths || hasNonUniformBorderRadiuses)) {
39
- drawUniformColorNonUniformBorders(nativeView, background);
40
- subscribeForScrollNotifications(view);
47
+ if (!nativeView.gradientLayer) {
48
+ nativeView.gradientLayer = CAGradientLayer.new();
49
+ layer.insertSublayerAtIndex(nativeView.gradientLayer, 0);
50
+ }
51
+ iosViewUtils.drawGradient(nativeView, nativeView.gradientLayer, background.image);
52
+ needsLayerAdjustmentOnScroll = true;
41
53
  }
42
- else if (background.hasUniformBorder()) {
43
- const layer = nativeView.layer;
54
+ // Initialize clipping mask (usually for clip-path and non-uniform rounded borders)
55
+ maskLayerIfNeeded(nativeView, background);
56
+ if (background.hasUniformBorder()) {
44
57
  const borderColor = background.getUniformBorderColor();
45
- layer.borderColor = !borderColor ? undefined : borderColor.ios.CGColor;
58
+ layer.borderColor = borderColor?.ios?.CGColor;
46
59
  layer.borderWidth = layout.toDeviceIndependentPixels(background.getUniformBorderWidth());
47
- const renderSize = view.getActualSize() || { width: 0, height: 0 };
48
- const cornerRadius = layout.toDeviceIndependentPixels(background.getUniformBorderRadius());
49
- layer.cornerRadius = Math.min(Math.min(renderSize.width / 2, renderSize.height / 2), cornerRadius);
60
+ layer.cornerRadius = getUniformBorderRadius(view, layer.bounds);
50
61
  }
51
62
  else {
52
- drawNoRadiusNonUniformBorders(nativeView, background);
53
- subscribeForScrollNotifications(view);
63
+ drawNonUniformBorders(nativeView, background);
64
+ needsLayerAdjustmentOnScroll = true;
54
65
  }
55
- // Clip-path should be called after borders are applied.
56
- // It will eventually move them to different layer if uniform.
57
- if (background.clipPath) {
58
- drawClipPath(nativeView, background);
66
+ // Clip-path should be called after borders are applied
67
+ if (nativeView.maskType === iosViewUtils.LayerMask.CLIP_PATH && layer.mask instanceof CAShapeLayer) {
68
+ layer.mask.path = generateClipPath(view, layer.bounds);
59
69
  }
60
- if (!background.image || background.image instanceof LinearGradient) {
61
- const uiColor = background.color ? background.color.ios : undefined;
62
- callback(uiColor);
70
+ if (background.hasBoxShadow()) {
71
+ drawBoxShadow(view);
72
+ needsLayerAdjustmentOnScroll = true;
63
73
  }
64
- else {
65
- setUIColorFromImage(view, nativeView, callback, flip);
74
+ if (needsLayerAdjustmentOnScroll) {
75
+ registerAdjustLayersOnScrollListener(view);
66
76
  }
67
- if (background.hasBoxShadow()) {
68
- drawBoxShadow(nativeView, view, background.getBoxShadow(), background);
77
+ }
78
+ ios.drawBackgroundVisualEffects = drawBackgroundVisualEffects;
79
+ function clearBackgroundVisualEffects(view) {
80
+ const nativeView = view.nativeViewProtected;
81
+ if (!nativeView) {
82
+ return;
69
83
  }
70
- // reset clear flags
84
+ const background = view.style.backgroundInternal;
85
+ const hasGradientBackground = background.image && background.image instanceof LinearGradient;
86
+ // Remove mask if there is no clip path or non-uniform border with radius
87
+ let needsMask;
88
+ switch (nativeView.maskType) {
89
+ case iosViewUtils.LayerMask.BORDER:
90
+ needsMask = !background.hasUniformBorder() && background.hasBorderRadius();
91
+ break;
92
+ case iosViewUtils.LayerMask.CLIP_PATH:
93
+ needsMask = !!background.clipPath;
94
+ break;
95
+ default:
96
+ needsMask = false;
97
+ break;
98
+ }
99
+ if (!needsMask) {
100
+ clearLayerMask(nativeView, background);
101
+ }
102
+ // Clear box shadow if it's no longer needed
103
+ if (background.clearFlags & 2 /* BackgroundClearFlags.CLEAR_BOX_SHADOW */) {
104
+ clearBoxShadow(nativeView);
105
+ }
106
+ // Non-uniform borders cleanup
107
+ if (nativeView.hasNonUniformBorder) {
108
+ if (nativeView.hasNonUniformBorderColor && background.hasUniformBorderColor()) {
109
+ clearNonUniformColorBorders(nativeView);
110
+ }
111
+ if (background.hasUniformBorder()) {
112
+ clearNonUniformBorders(nativeView);
113
+ }
114
+ }
115
+ if (nativeView.gradientLayer && !hasGradientBackground) {
116
+ nativeView.gradientLayer.removeFromSuperlayer();
117
+ nativeView.gradientLayer = null;
118
+ }
119
+ // Force unset scroll listener
120
+ unregisterAdjustLayersOnScrollListener(view);
121
+ // Reset clear flags
71
122
  background.clearFlags = 0 /* BackgroundClearFlags.NONE */;
72
123
  }
73
- ios.createBackgroundUIColor = createBackgroundUIColor;
124
+ ios.clearBackgroundVisualEffects = clearBackgroundVisualEffects;
125
+ function generateShadowLayerPaths(view, bounds) {
126
+ const background = view.style.backgroundInternal;
127
+ const nativeView = view.nativeViewProtected;
128
+ const layer = nativeView.layer;
129
+ const boxShadow = background.getBoxShadow();
130
+ const spreadRadius = layout.toDeviceIndependentPixels(boxShadow.spreadRadius);
131
+ const { width, height } = bounds.size;
132
+ let innerPath, shadowPath;
133
+ // Generate more detailed paths if view has border radius
134
+ if (background.hasBorderRadius()) {
135
+ if (background.hasUniformBorder()) {
136
+ const cornerRadius = layer.cornerRadius;
137
+ const cappedRadius = getBorderCapRadius(cornerRadius, width / 2, height / 2);
138
+ const cappedOuterRadii = {
139
+ topLeft: cappedRadius,
140
+ topRight: cappedRadius,
141
+ bottomLeft: cappedRadius,
142
+ bottomRight: cappedRadius,
143
+ };
144
+ const cappedOuterRadiiWithSpread = {
145
+ topLeft: cappedRadius + spreadRadius,
146
+ topRight: cappedRadius + spreadRadius,
147
+ bottomLeft: cappedRadius + spreadRadius,
148
+ bottomRight: cappedRadius + spreadRadius,
149
+ };
150
+ innerPath = generateNonUniformBorderOuterClipPath(bounds, cappedOuterRadii);
151
+ shadowPath = generateNonUniformBorderOuterClipPath(bounds, cappedOuterRadiiWithSpread, spreadRadius);
152
+ }
153
+ else {
154
+ const outerTopLeftRadius = layout.toDeviceIndependentPixels(background.borderTopLeftRadius);
155
+ const outerTopRightRadius = layout.toDeviceIndependentPixels(background.borderTopRightRadius);
156
+ const outerBottomRightRadius = layout.toDeviceIndependentPixels(background.borderBottomRightRadius);
157
+ const outerBottomLeftRadius = layout.toDeviceIndependentPixels(background.borderBottomLeftRadius);
158
+ const topRadii = outerTopLeftRadius + outerTopRightRadius;
159
+ const rightRadii = outerTopRightRadius + outerBottomRightRadius;
160
+ const bottomRadii = outerBottomRightRadius + outerBottomLeftRadius;
161
+ const leftRadii = outerBottomLeftRadius + outerTopLeftRadius;
162
+ const cappedOuterRadii = {
163
+ topLeft: getBorderCapRadius(outerTopLeftRadius, (outerTopLeftRadius / topRadii) * width, (outerTopLeftRadius / leftRadii) * height),
164
+ topRight: getBorderCapRadius(outerTopRightRadius, (outerTopRightRadius / topRadii) * width, (outerTopRightRadius / rightRadii) * height),
165
+ bottomLeft: getBorderCapRadius(outerBottomLeftRadius, (outerBottomLeftRadius / bottomRadii) * width, (outerBottomLeftRadius / leftRadii) * height),
166
+ bottomRight: getBorderCapRadius(outerBottomRightRadius, (outerBottomRightRadius / bottomRadii) * width, (outerBottomRightRadius / rightRadii) * height),
167
+ };
168
+ // Add spread radius to corners that actually have radius as shadow has grown larger
169
+ // than view itself and needs to be rounded accordingly
170
+ const cappedOuterRadiiWithSpread = {
171
+ topLeft: cappedOuterRadii.topLeft > 0 ? cappedOuterRadii.topLeft + spreadRadius : cappedOuterRadii.topLeft,
172
+ topRight: cappedOuterRadii.topRight > 0 ? cappedOuterRadii.topRight + spreadRadius : cappedOuterRadii.topRight,
173
+ bottomLeft: cappedOuterRadii.bottomLeft > 0 ? cappedOuterRadii.bottomLeft + spreadRadius : cappedOuterRadii.bottomLeft,
174
+ bottomRight: cappedOuterRadii.bottomRight > 0 ? cappedOuterRadii.bottomRight + spreadRadius : cappedOuterRadii.bottomRight,
175
+ };
176
+ innerPath = generateNonUniformBorderOuterClipPath(bounds, cappedOuterRadii);
177
+ shadowPath = generateNonUniformBorderOuterClipPath(bounds, cappedOuterRadiiWithSpread, spreadRadius);
178
+ }
179
+ }
180
+ else {
181
+ innerPath = CGPathCreateWithRect(bounds, null);
182
+ shadowPath = CGPathCreateWithRect(CGRectInset(bounds, -spreadRadius, -spreadRadius), null);
183
+ }
184
+ return {
185
+ maskPath: generateShadowMaskPath(bounds, boxShadow, innerPath),
186
+ shadowPath,
187
+ };
188
+ }
189
+ ios.generateShadowLayerPaths = generateShadowLayerPaths;
190
+ function generateClipPath(view, bounds) {
191
+ const background = view.style.backgroundInternal;
192
+ const { origin, size } = bounds;
193
+ const position = {
194
+ left: origin.x,
195
+ top: origin.y,
196
+ bottom: size.height,
197
+ right: size.width,
198
+ };
199
+ if (position.right === 0 || position.bottom === 0) {
200
+ return;
201
+ }
202
+ let path;
203
+ const clipPath = background.clipPath;
204
+ const functionName = clipPath.substring(0, clipPath.indexOf('('));
205
+ const value = clipPath.replace(`${functionName}(`, '').replace(')', '');
206
+ switch (functionName) {
207
+ case 'rect':
208
+ path = rectPath(value, position);
209
+ break;
210
+ case 'inset':
211
+ path = insetPath(value, position);
212
+ break;
213
+ case 'circle':
214
+ path = circlePath(value, position);
215
+ break;
216
+ case 'ellipse':
217
+ path = ellipsePath(value, position);
218
+ break;
219
+ case 'polygon':
220
+ path = polygonPath(value, position);
221
+ break;
222
+ }
223
+ return path;
224
+ }
225
+ ios.generateClipPath = generateClipPath;
226
+ function getUniformBorderRadius(view, bounds) {
227
+ const background = view.style.backgroundInternal;
228
+ const { width, height } = bounds.size;
229
+ const cornerRadius = layout.toDeviceIndependentPixels(background.getUniformBorderRadius());
230
+ return Math.min(Math.min(width / 2, height / 2), cornerRadius);
231
+ }
232
+ ios.getUniformBorderRadius = getUniformBorderRadius;
233
+ function generateNonUniformBorderInnerClipRoundedPath(view, bounds) {
234
+ const background = view.style.backgroundInternal;
235
+ const nativeView = view.nativeViewProtected;
236
+ const cappedOuterRadii = calculateNonUniformBorderCappedRadii(bounds, background);
237
+ return generateNonUniformBorderInnerClipPath(bounds, background, cappedOuterRadii);
238
+ }
239
+ ios.generateNonUniformBorderInnerClipRoundedPath = generateNonUniformBorderInnerClipRoundedPath;
240
+ function generateNonUniformBorderOuterClipRoundedPath(view, bounds) {
241
+ const background = view.style.backgroundInternal;
242
+ const nativeView = view.nativeViewProtected;
243
+ const cappedOuterRadii = calculateNonUniformBorderCappedRadii(bounds, background);
244
+ return generateNonUniformBorderOuterClipPath(bounds, cappedOuterRadii);
245
+ }
246
+ ios.generateNonUniformBorderOuterClipRoundedPath = generateNonUniformBorderOuterClipRoundedPath;
247
+ function generateNonUniformMultiColorBorderRoundedPaths(view, bounds) {
248
+ const background = view.style.backgroundInternal;
249
+ const nativeView = view.nativeViewProtected;
250
+ const cappedOuterRadii = calculateNonUniformBorderCappedRadii(bounds, background);
251
+ return generateNonUniformMultiColorBorderPaths(bounds, background, cappedOuterRadii);
252
+ }
253
+ ios.generateNonUniformMultiColorBorderRoundedPaths = generateNonUniformMultiColorBorderRoundedPaths;
74
254
  })(ios || (ios = {}));
75
- function onScroll(args) {
255
+ function maskLayerIfNeeded(nativeView, background) {
256
+ const layer = nativeView.layer;
257
+ // Check if layer should be masked
258
+ if (!(layer.mask instanceof CAShapeLayer)) {
259
+ // Since layers can only accept up to a single mask at a time, clip path is given more priority
260
+ if (background.clipPath) {
261
+ nativeView.maskType = iosViewUtils.LayerMask.CLIP_PATH;
262
+ }
263
+ else if (!background.hasUniformBorder() && background.hasBorderRadius()) {
264
+ nativeView.maskType = iosViewUtils.LayerMask.BORDER;
265
+ }
266
+ else {
267
+ nativeView.maskType = null;
268
+ }
269
+ if (nativeView.maskType != null) {
270
+ nativeView.originalMask = layer.mask;
271
+ layer.mask = CAShapeLayer.new();
272
+ }
273
+ }
274
+ }
275
+ function clearLayerMask(nativeView, background) {
276
+ if (nativeView.outerShadowContainerLayer) {
277
+ nativeView.outerShadowContainerLayer.mask = null;
278
+ }
279
+ nativeView.layer.mask = nativeView.originalMask;
280
+ nativeView.originalMask = null;
281
+ nativeView.maskType = null;
282
+ }
283
+ function onBackgroundViewScroll(args) {
76
284
  const view = args.object;
77
285
  const nativeView = view.nativeViewProtected;
78
286
  if (nativeView instanceof UIScrollView) {
@@ -80,61 +288,62 @@ function onScroll(args) {
80
288
  }
81
289
  }
82
290
  function adjustLayersForScrollView(nativeView) {
83
- const layer = nativeView.borderLayer;
84
- if (layer instanceof CALayer) {
85
- // Compensates with transition for the background layers for scrolling in ScrollView based controls.
86
- CATransaction.begin();
87
- CATransaction.setValueForKey(kCFBooleanTrue, kCATransactionDisableActions);
88
- const offset = nativeView.contentOffset;
89
- const transform = {
90
- a: 1,
91
- b: 0,
92
- c: 0,
93
- d: 1,
94
- tx: offset.x,
95
- ty: offset.y,
96
- };
97
- layer.setAffineTransform(transform);
98
- if (nativeView.layer.mask) {
99
- nativeView.layer.mask.setAffineTransform(transform);
100
- }
101
- CATransaction.commit();
291
+ // Compensates with transition for the background layers for scrolling in ScrollView based controls.
292
+ CATransaction.begin();
293
+ CATransaction.setDisableActions(true);
294
+ const offset = nativeView.contentOffset;
295
+ const transform = {
296
+ a: 1,
297
+ b: 0,
298
+ c: 0,
299
+ d: 1,
300
+ tx: offset.x,
301
+ ty: offset.y,
302
+ };
303
+ if (nativeView.layer.mask) {
304
+ nativeView.layer.mask.setAffineTransform(transform);
102
305
  }
306
+ // Nested layers
307
+ if (nativeView.gradientLayer) {
308
+ nativeView.gradientLayer.setAffineTransform(transform);
309
+ }
310
+ if (nativeView.borderLayer) {
311
+ nativeView.borderLayer.setAffineTransform(transform);
312
+ }
313
+ if (nativeView.outerShadowContainerLayer) {
314
+ // Update bounds of shadow layer as it belongs to parent view
315
+ nativeView.outerShadowContainerLayer.bounds = nativeView.bounds;
316
+ nativeView.outerShadowContainerLayer.setAffineTransform(transform);
317
+ }
318
+ CATransaction.setDisableActions(false);
319
+ CATransaction.commit();
103
320
  }
104
- function unsubscribeFromScrollNotifications(view) {
321
+ function unregisterAdjustLayersOnScrollListener(view) {
105
322
  if (view.nativeViewProtected instanceof UIScrollView) {
106
- view.off('scroll', onScroll);
323
+ view.off('scroll', onBackgroundViewScroll);
107
324
  }
108
325
  }
109
- function subscribeForScrollNotifications(view) {
326
+ function registerAdjustLayersOnScrollListener(view) {
110
327
  if (view.nativeViewProtected instanceof UIScrollView) {
111
- view.on('scroll', onScroll);
328
+ view.off('scroll', onBackgroundViewScroll);
329
+ view.on('scroll', onBackgroundViewScroll);
112
330
  adjustLayersForScrollView(view.nativeViewProtected);
113
331
  }
114
332
  }
333
+ function clearNonUniformColorBorders(nativeView) {
334
+ if (nativeView.borderLayer) {
335
+ nativeView.borderLayer.mask = null;
336
+ nativeView.borderLayer.sublayers = null;
337
+ }
338
+ nativeView.hasNonUniformBorderColor = false;
339
+ }
115
340
  function clearNonUniformBorders(nativeView) {
116
341
  if (nativeView.borderLayer) {
117
342
  nativeView.borderLayer.removeFromSuperlayer();
343
+ nativeView.borderLayer = null;
118
344
  }
119
- if (nativeView.hasBorderMask) {
120
- nativeView.layer.mask = nativeView.borderOriginalMask;
121
- nativeView.hasBorderMask = false;
122
- nativeView.borderOriginalMask = null;
123
- }
124
- if (nativeView.topBorderLayer) {
125
- nativeView.topBorderLayer.removeFromSuperlayer();
126
- }
127
- if (nativeView.rightBorderLayer) {
128
- nativeView.rightBorderLayer.removeFromSuperlayer();
129
- }
130
- if (nativeView.bottomBorderLayer) {
131
- nativeView.bottomBorderLayer.removeFromSuperlayer();
132
- }
133
- if (nativeView.leftBorderLayer) {
134
- nativeView.leftBorderLayer.removeFromSuperlayer();
135
- }
345
+ nativeView.hasNonUniformBorder = false;
136
346
  }
137
- const pattern = /url\(('|")(.*?)\1\)/;
138
347
  function setUIColorFromImage(view, nativeView, callback, flip) {
139
348
  const frame = nativeView.frame;
140
349
  const boundsWidth = view.scaleX ? frame.size.width / view.scaleX : frame.size.width;
@@ -146,7 +355,7 @@ function setUIColorFromImage(view, nativeView, callback, flip) {
146
355
  const background = style.backgroundInternal;
147
356
  let imageUri = background.image;
148
357
  if (imageUri) {
149
- const match = imageUri.match(pattern);
358
+ const match = imageUri.match(uriPattern);
150
359
  if (match && match[2]) {
151
360
  imageUri = match[2];
152
361
  }
@@ -314,11 +523,11 @@ function getDrawParams(image, background, width, height) {
314
523
  }
315
524
  function uiColorFromImage(img, view, callback, flip) {
316
525
  const background = view.style.backgroundInternal;
317
- if (!img || !view.nativeViewProtected) {
526
+ const nativeView = view.nativeViewProtected;
527
+ if (!img || !nativeView) {
318
528
  callback(background.color && background.color.ios);
319
529
  return;
320
530
  }
321
- const nativeView = view.nativeViewProtected;
322
531
  const frame = nativeView.frame;
323
532
  const boundsWidth = view.scaleX ? frame.size.width / view.scaleX : frame.size.width;
324
533
  const boundsHeight = view.scaleY ? frame.size.height / view.scaleY : frame.size.height;
@@ -384,29 +593,12 @@ function cssValueToDeviceIndependentPixels(source, total) {
384
593
  return parseFloat(source);
385
594
  }
386
595
  }
387
- function drawUniformColorNonUniformBorders(nativeView, background) {
388
- const layer = nativeView.layer;
389
- layer.backgroundColor = undefined;
390
- layer.borderColor = undefined;
391
- layer.borderWidth = 0;
392
- layer.cornerRadius = 0;
393
- const { width, height } = layer.bounds.size;
394
- const { x, y } = layer.bounds.origin;
395
- const left = x;
396
- const top = y;
397
- const right = x + width;
398
- const bottom = y + height;
399
- const { min, max } = Math;
400
- const borderTopWidth = max(0, layout.toDeviceIndependentPixels(background.borderTopWidth));
401
- const borderRightWidth = max(0, layout.toDeviceIndependentPixels(background.borderRightWidth));
402
- const borderBottomWidth = max(0, layout.toDeviceIndependentPixels(background.borderBottomWidth));
403
- const borderLeftWidth = max(0, layout.toDeviceIndependentPixels(background.borderLeftWidth));
404
- const borderVWidth = borderTopWidth + borderBottomWidth;
405
- const borderHWidth = borderLeftWidth + borderRightWidth;
406
- const cappedBorderTopWidth = borderTopWidth && borderTopWidth * min(1, height / borderVWidth);
407
- const cappedBorderRightWidth = borderRightWidth && borderRightWidth * min(1, width / borderHWidth);
408
- const cappedBorderBottomWidth = borderBottomWidth && borderBottomWidth * min(1, height / borderVWidth);
409
- const cappedBorderLeftWidth = borderLeftWidth && borderLeftWidth * min(1, width / borderHWidth);
596
+ function getBorderCapRadius(a, b, c) {
597
+ return a && Math.min(a, Math.min(b, c));
598
+ }
599
+ function calculateNonUniformBorderCappedRadii(bounds, background) {
600
+ const { width, height } = bounds.size;
601
+ const { x, y } = bounds.origin;
410
602
  const outerTopLeftRadius = layout.toDeviceIndependentPixels(background.borderTopLeftRadius);
411
603
  const outerTopRightRadius = layout.toDeviceIndependentPixels(background.borderTopRightRadius);
412
604
  const outerBottomRightRadius = layout.toDeviceIndependentPixels(background.borderBottomRightRadius);
@@ -415,310 +607,427 @@ function drawUniformColorNonUniformBorders(nativeView, background) {
415
607
  const rightRadii = outerTopRightRadius + outerBottomRightRadius;
416
608
  const bottomRadii = outerBottomRightRadius + outerBottomLeftRadius;
417
609
  const leftRadii = outerBottomLeftRadius + outerTopLeftRadius;
418
- function capRadius(a, b, c) {
419
- return a && Math.min(a, Math.min(b, c));
420
- }
421
- const cappedOuterTopLeftRadius = capRadius(outerTopLeftRadius, (outerTopLeftRadius / topRadii) * width, (outerTopLeftRadius / leftRadii) * height);
422
- const cappedOuterTopRightRadius = capRadius(outerTopRightRadius, (outerTopRightRadius / topRadii) * width, (outerTopRightRadius / rightRadii) * height);
423
- const cappedOuterBottomRightRadius = capRadius(outerBottomRightRadius, (outerBottomRightRadius / bottomRadii) * width, (outerBottomRightRadius / rightRadii) * height);
424
- const cappedOuterBottomLeftRadius = capRadius(outerBottomLeftRadius, (outerBottomLeftRadius / bottomRadii) * width, (outerBottomLeftRadius / leftRadii) * height);
425
- // Outer contour
426
- const clipPath = CGPathCreateMutable();
427
- CGPathMoveToPoint(clipPath, null, left + cappedOuterTopLeftRadius, top);
428
- CGPathAddArcToPoint(clipPath, null, right, top, right, top + cappedOuterTopRightRadius, cappedOuterTopRightRadius);
429
- CGPathAddArcToPoint(clipPath, null, right, bottom, right - cappedOuterBottomRightRadius, bottom, cappedOuterBottomRightRadius);
430
- CGPathAddArcToPoint(clipPath, null, left, bottom, left, bottom - cappedOuterBottomLeftRadius, cappedOuterBottomLeftRadius);
431
- CGPathAddArcToPoint(clipPath, null, left, top, left + cappedOuterTopLeftRadius, top, cappedOuterTopLeftRadius);
432
- CGPathCloseSubpath(clipPath);
433
- nativeView.borderOriginalMask = layer.mask;
434
- const clipShapeLayer = CAShapeLayer.layer();
435
- clipShapeLayer.path = clipPath;
436
- layer.mask = clipShapeLayer;
437
- nativeView.hasBorderMask = true;
438
- if (cappedBorderLeftWidth > 0 || cappedBorderTopWidth > 0 || cappedBorderRightWidth > 0 || cappedBorderBottomWidth > 0) {
439
- const borderPath = CGPathCreateMutable();
440
- CGPathAddRect(borderPath, null, CGRectMake(left, top, width, height));
441
- // Inner contour
442
- if (cappedBorderTopWidth > 0 || cappedBorderLeftWidth > 0) {
443
- CGPathMoveToPoint(borderPath, null, left + cappedOuterTopLeftRadius, top + cappedBorderTopWidth);
444
- }
445
- else {
446
- CGPathMoveToPoint(borderPath, null, left, top);
447
- }
448
- if (cappedBorderTopWidth > 0 || cappedBorderRightWidth > 0) {
449
- const innerTopRightWRadius = max(0, cappedOuterTopRightRadius - cappedBorderRightWidth);
450
- const innerTopRightHRadius = max(0, cappedOuterTopRightRadius - cappedBorderTopWidth);
451
- const innerTopRightMaxRadius = max(innerTopRightWRadius, innerTopRightHRadius);
452
- const innerTopRightTransform = CGAffineTransformMake(innerTopRightMaxRadius && innerTopRightWRadius / innerTopRightMaxRadius, 0, 0, innerTopRightMaxRadius && innerTopRightHRadius / innerTopRightMaxRadius, right - cappedBorderRightWidth - innerTopRightWRadius, top + cappedBorderTopWidth + innerTopRightHRadius);
453
- CGPathAddArc(borderPath, innerTopRightTransform, 0, 0, innerTopRightMaxRadius, (Math.PI * 3) / 2, 0, false);
454
- }
455
- else {
456
- CGPathMoveToPoint(borderPath, null, right, top);
457
- }
458
- if (cappedBorderBottomWidth > 0 || cappedBorderRightWidth > 0) {
459
- const innerBottomRightWRadius = max(0, cappedOuterBottomRightRadius - cappedBorderRightWidth);
460
- const innerBottomRightHRadius = max(0, cappedOuterBottomRightRadius - cappedBorderBottomWidth);
461
- const innerBottomRightMaxRadius = max(innerBottomRightWRadius, innerBottomRightHRadius);
462
- const innerBottomRightTransform = CGAffineTransformMake(innerBottomRightMaxRadius && innerBottomRightWRadius / innerBottomRightMaxRadius, 0, 0, innerBottomRightMaxRadius && innerBottomRightHRadius / innerBottomRightMaxRadius, right - cappedBorderRightWidth - innerBottomRightWRadius, bottom - cappedBorderBottomWidth - innerBottomRightHRadius);
463
- CGPathAddArc(borderPath, innerBottomRightTransform, 0, 0, innerBottomRightMaxRadius, 0, Math.PI / 2, false);
464
- }
465
- else {
466
- CGPathAddLineToPoint(borderPath, null, right, bottom);
467
- }
468
- if (cappedBorderBottomWidth > 0 || cappedBorderLeftWidth > 0) {
469
- const innerBottomLeftWRadius = max(0, cappedOuterBottomLeftRadius - cappedBorderLeftWidth);
470
- const innerBottomLeftHRadius = max(0, cappedOuterBottomLeftRadius - cappedBorderBottomWidth);
471
- const innerBottomLeftMaxRadius = max(innerBottomLeftWRadius, innerBottomLeftHRadius);
472
- const innerBottomLeftTransform = CGAffineTransformMake(innerBottomLeftMaxRadius && innerBottomLeftWRadius / innerBottomLeftMaxRadius, 0, 0, innerBottomLeftMaxRadius && innerBottomLeftHRadius / innerBottomLeftMaxRadius, left + cappedBorderLeftWidth + innerBottomLeftWRadius, bottom - cappedBorderBottomWidth - innerBottomLeftHRadius);
473
- CGPathAddArc(borderPath, innerBottomLeftTransform, 0, 0, innerBottomLeftMaxRadius, Math.PI / 2, Math.PI, false);
474
- }
475
- else {
476
- CGPathAddLineToPoint(borderPath, null, left, bottom);
610
+ const cappedOuterRadii = {
611
+ topLeft: getBorderCapRadius(outerTopLeftRadius, (outerTopLeftRadius / topRadii) * width, (outerTopLeftRadius / leftRadii) * height),
612
+ topRight: getBorderCapRadius(outerTopRightRadius, (outerTopRightRadius / topRadii) * width, (outerTopRightRadius / rightRadii) * height),
613
+ bottomLeft: getBorderCapRadius(outerBottomLeftRadius, (outerBottomLeftRadius / bottomRadii) * width, (outerBottomLeftRadius / leftRadii) * height),
614
+ bottomRight: getBorderCapRadius(outerBottomRightRadius, (outerBottomRightRadius / bottomRadii) * width, (outerBottomRightRadius / rightRadii) * height),
615
+ };
616
+ return cappedOuterRadii;
617
+ }
618
+ function drawNonUniformBorders(nativeView, background) {
619
+ const layer = nativeView.layer;
620
+ const layerBounds = layer.bounds;
621
+ layer.borderColor = null;
622
+ layer.borderWidth = 0;
623
+ layer.cornerRadius = 0;
624
+ const cappedOuterRadii = calculateNonUniformBorderCappedRadii(layerBounds, background);
625
+ if (nativeView.maskType === iosViewUtils.LayerMask.BORDER && layer.mask instanceof CAShapeLayer) {
626
+ layer.mask.path = generateNonUniformBorderOuterClipPath(layerBounds, cappedOuterRadii);
627
+ }
628
+ if (background.hasBorderWidth()) {
629
+ if (!nativeView.hasNonUniformBorder) {
630
+ nativeView.borderLayer = CAShapeLayer.new();
631
+ nativeView.borderLayer.fillRule = kCAFillRuleEvenOdd;
632
+ layer.addSublayer(nativeView.borderLayer);
633
+ nativeView.hasNonUniformBorder = true;
477
634
  }
478
- if (cappedBorderTopWidth > 0 || cappedBorderLeftWidth > 0) {
479
- const innerTopLeftWRadius = max(0, cappedOuterTopLeftRadius - cappedBorderLeftWidth);
480
- const innerTopLeftHRadius = max(0, cappedOuterTopLeftRadius - cappedBorderTopWidth);
481
- const innerTopLeftMaxRadius = max(innerTopLeftWRadius, innerTopLeftHRadius);
482
- const innerTopLeftTransform = CGAffineTransformMake(innerTopLeftMaxRadius && innerTopLeftWRadius / innerTopLeftMaxRadius, 0, 0, innerTopLeftMaxRadius && innerTopLeftHRadius / innerTopLeftMaxRadius, left + cappedBorderLeftWidth + innerTopLeftWRadius, top + cappedBorderTopWidth + innerTopLeftHRadius);
483
- CGPathAddArc(borderPath, innerTopLeftTransform, 0, 0, innerTopLeftMaxRadius, Math.PI, (Math.PI * 3) / 2, false);
635
+ if (background.hasUniformBorderColor()) {
636
+ nativeView.borderLayer.fillColor = background.borderTopColor?.ios?.CGColor || UIColor.blackColor.CGColor;
637
+ nativeView.borderLayer.path = generateNonUniformBorderInnerClipPath(layerBounds, background, cappedOuterRadii);
484
638
  }
485
639
  else {
486
- CGPathAddLineToPoint(borderPath, null, left, top);
640
+ // Non-uniform borders need more layers in order to display multiple colors at the same time
641
+ let borderTopLayer, borderRightLayer, borderBottomLayer, borderLeftLayer;
642
+ if (!nativeView.hasNonUniformBorderColor) {
643
+ const maskLayer = CAShapeLayer.new();
644
+ maskLayer.fillRule = kCAFillRuleEvenOdd;
645
+ nativeView.borderLayer.mask = maskLayer;
646
+ borderTopLayer = CAShapeLayer.new();
647
+ borderRightLayer = CAShapeLayer.new();
648
+ borderBottomLayer = CAShapeLayer.new();
649
+ borderLeftLayer = CAShapeLayer.new();
650
+ nativeView.borderLayer.addSublayer(borderTopLayer);
651
+ nativeView.borderLayer.addSublayer(borderRightLayer);
652
+ nativeView.borderLayer.addSublayer(borderBottomLayer);
653
+ nativeView.borderLayer.addSublayer(borderLeftLayer);
654
+ nativeView.hasNonUniformBorderColor = true;
655
+ }
656
+ else {
657
+ borderTopLayer = nativeView.borderLayer.sublayers[0];
658
+ borderRightLayer = nativeView.borderLayer.sublayers[1];
659
+ borderBottomLayer = nativeView.borderLayer.sublayers[2];
660
+ borderLeftLayer = nativeView.borderLayer.sublayers[3];
661
+ }
662
+ const paths = generateNonUniformMultiColorBorderPaths(layerBounds, background, cappedOuterRadii);
663
+ borderTopLayer.fillColor = background.borderTopColor?.ios?.CGColor || UIColor.blackColor.CGColor;
664
+ borderTopLayer.path = paths[0];
665
+ borderRightLayer.fillColor = background.borderRightColor?.ios?.CGColor || UIColor.blackColor.CGColor;
666
+ borderRightLayer.path = paths[1];
667
+ borderBottomLayer.fillColor = background.borderBottomColor?.ios?.CGColor || UIColor.blackColor.CGColor;
668
+ borderBottomLayer.path = paths[2];
669
+ borderLeftLayer.fillColor = background.borderLeftColor?.ios?.CGColor || UIColor.blackColor.CGColor;
670
+ borderLeftLayer.path = paths[3];
671
+ // Clip inner area to create borders
672
+ if (nativeView.borderLayer.mask instanceof CAShapeLayer) {
673
+ nativeView.borderLayer.mask.path = generateNonUniformBorderInnerClipPath(layerBounds, background, cappedOuterRadii);
674
+ }
487
675
  }
488
- CGPathCloseSubpath(borderPath);
489
- const borderLayer = CAShapeLayer.layer();
490
- borderLayer.fillColor = (background.borderTopColor && background.borderTopColor.ios.CGColor) || UIColor.blackColor.CGColor;
491
- borderLayer.fillRule = kCAFillRuleEvenOdd;
492
- borderLayer.path = borderPath;
493
- layer.addSublayer(borderLayer);
494
- nativeView.borderLayer = borderLayer;
495
676
  }
496
- nativeView.hasNonUniformBorder = true;
497
677
  }
498
- function drawNoRadiusNonUniformBorders(nativeView, background) {
499
- const borderLayer = CALayer.layer();
500
- nativeView.layer.addSublayer(borderLayer);
501
- nativeView.borderLayer = borderLayer;
502
- borderLayer.borderColor = undefined;
503
- borderLayer.borderWidth = 0;
504
- borderLayer.cornerRadius = 0;
505
- const layerBounds = nativeView.layer.bounds;
506
- const layerOrigin = layerBounds.origin;
507
- const layerSize = layerBounds.size;
508
- const nativeViewLayerBounds = {
509
- left: layerOrigin.x,
510
- top: layerOrigin.y,
511
- bottom: layerSize.height,
512
- right: layerSize.width,
678
+ function calculateInnerBorderClipRadius(radius, insetX, insetY) {
679
+ const innerXRadius = Math.max(0, radius - insetX);
680
+ const innerYRadius = Math.max(0, radius - insetY);
681
+ const innerMaxRadius = Math.max(innerXRadius, innerYRadius);
682
+ return {
683
+ xRadius: innerXRadius,
684
+ yRadius: innerYRadius,
685
+ maxRadius: innerMaxRadius,
686
+ };
687
+ }
688
+ /**
689
+ * Generates a path that represents the rounded view area.
690
+ *
691
+ * @param bounds
692
+ * @param cappedRadii
693
+ * @param offset
694
+ * @returns
695
+ */
696
+ export function generateNonUniformBorderOuterClipPath(bounds, cappedRadii, offset = 0) {
697
+ const { width, height } = bounds.size;
698
+ const { x, y } = bounds.origin;
699
+ const left = x - offset;
700
+ const top = y - offset;
701
+ const right = x + width + offset;
702
+ const bottom = y + height + offset;
703
+ const clipPath = CGPathCreateMutable();
704
+ CGPathMoveToPoint(clipPath, null, left + cappedRadii.topLeft, top);
705
+ CGPathAddArcToPoint(clipPath, null, right, top, right, top + cappedRadii.topRight, cappedRadii.topRight);
706
+ CGPathAddArcToPoint(clipPath, null, right, bottom, right - cappedRadii.bottomRight, bottom, cappedRadii.bottomRight);
707
+ CGPathAddArcToPoint(clipPath, null, left, bottom, left, bottom - cappedRadii.bottomLeft, cappedRadii.bottomLeft);
708
+ CGPathAddArcToPoint(clipPath, null, left, top, left + cappedRadii.topLeft, top, cappedRadii.topLeft);
709
+ CGPathCloseSubpath(clipPath);
710
+ return clipPath;
711
+ }
712
+ /**
713
+ * Generates a path that represents the area inside borders.
714
+ *
715
+ * @param bounds
716
+ * @param background
717
+ * @param cappedOuterRadii
718
+ * @returns
719
+ */
720
+ function generateNonUniformBorderInnerClipPath(bounds, background, cappedOuterRadii) {
721
+ const { width, height } = bounds.size;
722
+ const { x, y } = bounds.origin;
723
+ const position = {
724
+ left: x,
725
+ top: y,
726
+ bottom: y + height,
727
+ right: x + width,
728
+ };
729
+ const borderTopWidth = Math.max(0, layout.toDeviceIndependentPixels(background.borderTopWidth));
730
+ const borderRightWidth = Math.max(0, layout.toDeviceIndependentPixels(background.borderRightWidth));
731
+ const borderBottomWidth = Math.max(0, layout.toDeviceIndependentPixels(background.borderBottomWidth));
732
+ const borderLeftWidth = Math.max(0, layout.toDeviceIndependentPixels(background.borderLeftWidth));
733
+ const borderVWidth = borderTopWidth + borderBottomWidth;
734
+ const borderHWidth = borderLeftWidth + borderRightWidth;
735
+ const cappedBorderTopWidth = borderTopWidth && borderTopWidth * Math.min(1, height / borderVWidth);
736
+ const cappedBorderRightWidth = borderRightWidth && borderRightWidth * Math.min(1, width / borderHWidth);
737
+ const cappedBorderBottomWidth = borderBottomWidth && borderBottomWidth * Math.min(1, height / borderVWidth);
738
+ const cappedBorderLeftWidth = borderLeftWidth && borderLeftWidth * Math.min(1, width / borderHWidth);
739
+ const clipPath = CGPathCreateMutable();
740
+ CGPathAddRect(clipPath, null, CGRectMake(x, y, width, height));
741
+ // Inner clip paths
742
+ if (cappedBorderTopWidth > 0 || cappedBorderLeftWidth > 0) {
743
+ CGPathMoveToPoint(clipPath, null, position.left + cappedOuterRadii.topLeft, position.top + cappedBorderTopWidth);
744
+ }
745
+ else {
746
+ CGPathMoveToPoint(clipPath, null, position.left, position.top);
747
+ }
748
+ if (cappedBorderTopWidth > 0 || cappedBorderRightWidth > 0) {
749
+ const { xRadius, yRadius, maxRadius } = calculateInnerBorderClipRadius(cappedOuterRadii.topRight, cappedBorderRightWidth, cappedBorderTopWidth);
750
+ const innerTopRightTransform = CGAffineTransformMake(maxRadius && xRadius / maxRadius, 0, 0, maxRadius && yRadius / maxRadius, position.right - cappedBorderRightWidth - xRadius, position.top + cappedBorderTopWidth + yRadius);
751
+ CGPathAddArc(clipPath, innerTopRightTransform, 0, 0, maxRadius, (Math.PI * 3) / 2, 0, false);
752
+ }
753
+ else {
754
+ CGPathAddLineToPoint(clipPath, null, position.right, position.top);
755
+ }
756
+ if (cappedBorderBottomWidth > 0 || cappedBorderRightWidth > 0) {
757
+ const { xRadius, yRadius, maxRadius } = calculateInnerBorderClipRadius(cappedOuterRadii.bottomRight, cappedBorderRightWidth, cappedBorderBottomWidth);
758
+ const innerBottomRightTransform = CGAffineTransformMake(maxRadius && xRadius / maxRadius, 0, 0, maxRadius && yRadius / maxRadius, position.right - cappedBorderRightWidth - xRadius, position.bottom - cappedBorderBottomWidth - yRadius);
759
+ CGPathAddArc(clipPath, innerBottomRightTransform, 0, 0, maxRadius, 0, Math.PI / 2, false);
760
+ }
761
+ else {
762
+ CGPathAddLineToPoint(clipPath, null, position.right, position.bottom);
763
+ }
764
+ if (cappedBorderBottomWidth > 0 || cappedBorderLeftWidth > 0) {
765
+ const { xRadius, yRadius, maxRadius } = calculateInnerBorderClipRadius(cappedOuterRadii.bottomLeft, cappedBorderLeftWidth, cappedBorderBottomWidth);
766
+ const innerBottomLeftTransform = CGAffineTransformMake(maxRadius && xRadius / maxRadius, 0, 0, maxRadius && yRadius / maxRadius, position.left + cappedBorderLeftWidth + xRadius, position.bottom - cappedBorderBottomWidth - yRadius);
767
+ CGPathAddArc(clipPath, innerBottomLeftTransform, 0, 0, maxRadius, Math.PI / 2, Math.PI, false);
768
+ }
769
+ else {
770
+ CGPathAddLineToPoint(clipPath, null, position.left, position.bottom);
771
+ }
772
+ if (cappedBorderTopWidth > 0 || cappedBorderLeftWidth > 0) {
773
+ const { xRadius, yRadius, maxRadius } = calculateInnerBorderClipRadius(cappedOuterRadii.topLeft, cappedBorderLeftWidth, cappedBorderTopWidth);
774
+ const innerTopLeftTransform = CGAffineTransformMake(maxRadius && xRadius / maxRadius, 0, 0, maxRadius && yRadius / maxRadius, position.left + cappedBorderLeftWidth + xRadius, position.top + cappedBorderTopWidth + yRadius);
775
+ CGPathAddArc(clipPath, innerTopLeftTransform, 0, 0, maxRadius, Math.PI, (Math.PI * 3) / 2, false);
776
+ }
777
+ else {
778
+ CGPathAddLineToPoint(clipPath, null, position.left, position.top);
779
+ }
780
+ CGPathCloseSubpath(clipPath);
781
+ return clipPath;
782
+ }
783
+ /**
784
+ * Generates paths for visualizing borders with different color per side.
785
+ * This is achieved by extending all borders enough to consume entire view size and
786
+ * use an inner path along with even-odd fill rule to render borders according to their corresponding width.
787
+ *
788
+ * @param bounds
789
+ * @param background
790
+ * @param cappedOuterRadii
791
+ * @returns
792
+ */
793
+ function generateNonUniformMultiColorBorderPaths(bounds, background, cappedOuterRadii) {
794
+ const { width, height } = bounds.size;
795
+ const { x, y } = bounds.origin;
796
+ const position = {
797
+ left: x,
798
+ top: y,
799
+ bottom: y + height,
800
+ right: x + width,
513
801
  };
514
- const top = layout.toDeviceIndependentPixels(background.borderTopWidth);
515
- const right = layout.toDeviceIndependentPixels(background.borderRightWidth);
516
- const bottom = layout.toDeviceIndependentPixels(background.borderBottomWidth);
517
- const left = layout.toDeviceIndependentPixels(background.borderLeftWidth);
802
+ const topWidth = layout.toDeviceIndependentPixels(background.borderTopWidth);
803
+ const rightWidth = layout.toDeviceIndependentPixels(background.borderRightWidth);
804
+ const bottomWidth = layout.toDeviceIndependentPixels(background.borderBottomWidth);
805
+ const leftWidth = layout.toDeviceIndependentPixels(background.borderLeftWidth);
806
+ // These values have 1 as fallback in order to handler borders with zero values
807
+ const safeTopWidth = Math.max(topWidth, 1);
808
+ const safeRightWidth = Math.max(rightWidth, 1);
809
+ const safeBottomWidth = Math.max(bottomWidth, 1);
810
+ const safeLeftWidth = Math.max(leftWidth, 1);
811
+ const paths = new Array(4);
518
812
  const lto = {
519
- x: nativeViewLayerBounds.left,
520
- y: nativeViewLayerBounds.top,
813
+ x: position.left,
814
+ y: position.top,
521
815
  }; // left-top-outside
522
816
  const lti = {
523
- x: nativeViewLayerBounds.left + left,
524
- y: nativeViewLayerBounds.top + top,
817
+ x: position.left + safeLeftWidth,
818
+ y: position.top + safeTopWidth,
525
819
  }; // left-top-inside
526
820
  const rto = {
527
- x: nativeViewLayerBounds.right,
528
- y: nativeViewLayerBounds.top,
821
+ x: position.right,
822
+ y: position.top,
529
823
  }; // right-top-outside
530
824
  const rti = {
531
- x: nativeViewLayerBounds.right - right,
532
- y: nativeViewLayerBounds.top + top,
825
+ x: position.right - safeRightWidth,
826
+ y: position.top + safeTopWidth,
533
827
  }; // right-top-inside
534
828
  const rbo = {
535
- x: nativeViewLayerBounds.right,
536
- y: nativeViewLayerBounds.bottom,
829
+ x: position.right,
830
+ y: position.bottom,
537
831
  }; // right-bottom-outside
538
832
  const rbi = {
539
- x: nativeViewLayerBounds.right - right,
540
- y: nativeViewLayerBounds.bottom - bottom,
833
+ x: position.right - safeRightWidth,
834
+ y: position.bottom - safeBottomWidth,
541
835
  }; // right-bottom-inside
542
836
  const lbo = {
543
- x: nativeViewLayerBounds.left,
544
- y: nativeViewLayerBounds.bottom,
837
+ x: position.left,
838
+ y: position.bottom,
545
839
  }; // left-bottom-outside
546
840
  const lbi = {
547
- x: nativeViewLayerBounds.left + left,
548
- y: nativeViewLayerBounds.bottom - bottom,
841
+ x: position.left + safeLeftWidth,
842
+ y: position.bottom - safeBottomWidth,
549
843
  }; // left-bottom-inside
550
- let hasNonUniformBorder;
844
+ const centerX = position.right / 2;
845
+ const centerY = position.bottom / 2;
846
+ // These values help calculate the size that each border shape should consume
847
+ const averageHorizontalBorderWidth = Math.max((leftWidth + rightWidth) / 2, 1);
848
+ const averageVerticalBorderWidth = Math.max((topWidth + bottomWidth) / 2, 1);
849
+ const viewRatioMultiplier = width > 0 && height > 0 ? width / height : 1;
551
850
  const borderTopColor = background.borderTopColor;
552
- if (top > 0 && borderTopColor && borderTopColor.ios) {
851
+ const borderRightColor = background.borderRightColor;
852
+ const borderBottomColor = background.borderBottomColor;
853
+ const borderLeftColor = background.borderLeftColor;
854
+ let borderTopY = centerY * (safeTopWidth / averageHorizontalBorderWidth) * viewRatioMultiplier;
855
+ let borderRightX = position.right - (centerX * (safeRightWidth / averageVerticalBorderWidth)) / viewRatioMultiplier;
856
+ let borderBottomY = position.bottom - centerY * (safeBottomWidth / averageHorizontalBorderWidth) * viewRatioMultiplier;
857
+ let borderLeftX = (centerX * (safeLeftWidth / averageVerticalBorderWidth)) / viewRatioMultiplier;
858
+ // Adjust border triangle width in case of borders colliding between each other or borders being less than 4
859
+ const hasHorizontalIntersection = borderLeftX > borderRightX;
860
+ const hasVerticalIntersection = borderTopY > borderBottomY;
861
+ if (hasVerticalIntersection) {
862
+ borderTopY = extendPointsToTargetY(lto.y, lto.x, lti.y, lti.x, borderLeftX);
863
+ borderBottomY = extendPointsToTargetY(lbo.y, lbo.x, lbi.y, lbi.x, borderLeftX);
864
+ }
865
+ else if (hasHorizontalIntersection) {
866
+ borderLeftX = extendPointsToTargetY(lto.x, lto.y, lti.x, lti.y, borderTopY);
867
+ borderRightX = extendPointsToTargetY(rto.x, rto.y, rti.x, rti.y, borderTopY);
868
+ }
869
+ if (topWidth > 0 && borderTopColor?.ios) {
553
870
  const topBorderPath = CGPathCreateMutable();
871
+ const borderTopLeftX = extendPointsToTargetY(lto.x, lto.y, lti.x, lti.y, borderTopY);
872
+ const borderTopRightX = extendPointsToTargetY(rto.x, rto.y, rti.x, rti.y, borderTopY);
554
873
  CGPathMoveToPoint(topBorderPath, null, lto.x, lto.y);
555
874
  CGPathAddLineToPoint(topBorderPath, null, rto.x, rto.y);
556
- CGPathAddLineToPoint(topBorderPath, null, rti.x, rti.y);
557
- CGPathAddLineToPoint(topBorderPath, null, lti.x, lti.y);
875
+ CGPathAddLineToPoint(topBorderPath, null, borderTopRightX, borderTopY);
876
+ if (borderTopRightX !== borderTopLeftX) {
877
+ CGPathAddLineToPoint(topBorderPath, null, borderTopLeftX, borderTopY);
878
+ }
558
879
  CGPathAddLineToPoint(topBorderPath, null, lto.x, lto.y);
559
- const topBorderLayer = CAShapeLayer.layer();
560
- topBorderLayer.fillColor = background.borderTopColor.ios.CGColor;
561
- topBorderLayer.path = topBorderPath;
562
- borderLayer.addSublayer(topBorderLayer);
563
- nativeView.topBorderLayer = topBorderLayer;
564
- hasNonUniformBorder = true;
880
+ paths[0] = topBorderPath;
565
881
  }
566
- const borderRightColor = background.borderRightColor;
567
- if (right > 0 && borderRightColor && borderRightColor.ios) {
882
+ if (rightWidth > 0 && borderRightColor?.ios) {
568
883
  const rightBorderPath = CGPathCreateMutable();
884
+ const borderRightBottomY = extendPointsToTargetY(rbo.y, rbo.x, rbi.y, rbi.x, borderRightX);
885
+ const borderRightTopY = extendPointsToTargetY(rto.y, rto.x, rti.y, rti.x, borderRightX);
569
886
  CGPathMoveToPoint(rightBorderPath, null, rto.x, rto.y);
570
887
  CGPathAddLineToPoint(rightBorderPath, null, rbo.x, rbo.y);
571
- CGPathAddLineToPoint(rightBorderPath, null, rbi.x, rbi.y);
572
- CGPathAddLineToPoint(rightBorderPath, null, rti.x, rti.y);
888
+ CGPathAddLineToPoint(rightBorderPath, null, borderRightX, borderRightBottomY);
889
+ if (borderRightBottomY !== borderRightTopY) {
890
+ CGPathAddLineToPoint(rightBorderPath, null, borderRightX, borderRightTopY);
891
+ }
573
892
  CGPathAddLineToPoint(rightBorderPath, null, rto.x, rto.y);
574
- const rightBorderLayer = CAShapeLayer.layer();
575
- rightBorderLayer.fillColor = background.borderRightColor.ios.CGColor;
576
- rightBorderLayer.path = rightBorderPath;
577
- borderLayer.addSublayer(rightBorderLayer);
578
- nativeView.rightBorderLayer = rightBorderLayer;
579
- hasNonUniformBorder = true;
893
+ paths[1] = rightBorderPath;
580
894
  }
581
- const borderBottomColor = background.borderBottomColor;
582
- if (bottom > 0 && borderBottomColor && borderBottomColor.ios) {
895
+ if (bottomWidth > 0 && borderBottomColor?.ios) {
583
896
  const bottomBorderPath = CGPathCreateMutable();
897
+ const borderBottomLeftX = extendPointsToTargetY(lbo.x, lbo.y, lbi.x, lbi.y, borderBottomY);
898
+ const borderBottomRightX = extendPointsToTargetY(rbo.x, rbo.y, rbi.x, rbi.y, borderBottomY);
584
899
  CGPathMoveToPoint(bottomBorderPath, null, rbo.x, rbo.y);
585
900
  CGPathAddLineToPoint(bottomBorderPath, null, lbo.x, lbo.y);
586
- CGPathAddLineToPoint(bottomBorderPath, null, lbi.x, lbi.y);
587
- CGPathAddLineToPoint(bottomBorderPath, null, rbi.x, rbi.y);
901
+ CGPathAddLineToPoint(bottomBorderPath, null, borderBottomLeftX, borderBottomY);
902
+ if (borderBottomLeftX !== borderBottomRightX) {
903
+ CGPathAddLineToPoint(bottomBorderPath, null, borderBottomRightX, borderBottomY);
904
+ }
588
905
  CGPathAddLineToPoint(bottomBorderPath, null, rbo.x, rbo.y);
589
- const bottomBorderLayer = CAShapeLayer.layer();
590
- bottomBorderLayer.fillColor = background.borderBottomColor.ios.CGColor;
591
- bottomBorderLayer.path = bottomBorderPath;
592
- borderLayer.addSublayer(bottomBorderLayer);
593
- nativeView.bottomBorderLayer = bottomBorderLayer;
594
- hasNonUniformBorder = true;
906
+ paths[2] = bottomBorderPath;
595
907
  }
596
- const borderLeftColor = background.borderLeftColor;
597
- if (left > 0 && borderLeftColor && borderLeftColor.ios) {
908
+ if (leftWidth > 0 && borderLeftColor?.ios) {
598
909
  const leftBorderPath = CGPathCreateMutable();
910
+ const borderLeftTopY = extendPointsToTargetY(lto.y, lto.x, lti.y, lti.x, borderLeftX);
911
+ const borderLeftBottomY = extendPointsToTargetY(lbo.y, lbo.x, lbi.y, lbi.x, borderLeftX);
599
912
  CGPathMoveToPoint(leftBorderPath, null, lbo.x, lbo.y);
600
913
  CGPathAddLineToPoint(leftBorderPath, null, lto.x, lto.y);
601
- CGPathAddLineToPoint(leftBorderPath, null, lti.x, lti.y);
602
- CGPathAddLineToPoint(leftBorderPath, null, lbi.x, lbi.y);
914
+ CGPathAddLineToPoint(leftBorderPath, null, borderLeftX, borderLeftTopY);
915
+ if (borderLeftTopY !== borderLeftBottomY) {
916
+ CGPathAddLineToPoint(leftBorderPath, null, borderLeftX, borderLeftBottomY);
917
+ }
603
918
  CGPathAddLineToPoint(leftBorderPath, null, lbo.x, lbo.y);
604
- const leftBorderLayer = CAShapeLayer.layer();
605
- leftBorderLayer.fillColor = background.borderLeftColor.ios.CGColor;
606
- leftBorderLayer.path = leftBorderPath;
607
- borderLayer.addSublayer(leftBorderLayer);
608
- nativeView.leftBorderLayer = leftBorderLayer;
609
- hasNonUniformBorder = true;
610
- }
611
- nativeView.hasNonUniformBorder = hasNonUniformBorder;
919
+ paths[3] = leftBorderPath;
920
+ }
921
+ return paths;
612
922
  }
613
- // TODO: use sublayer if its applied to a layout
614
- function drawBoxShadow(nativeView, view, boxShadow, background, useSubLayer = false) {
615
- const layer = iOSNativeHelper.getShadowLayer(nativeView, 'ns-box-shadow');
616
- layer.masksToBounds = false;
617
- nativeView.clipsToBounds = false;
618
- // this is required (if not, shadow will get cutoff at parent's dimensions)
619
- // nativeView.clipsToBounds doesn't work
620
- view.setProperty('clipToBounds', false);
621
- if (!background.color?.a) {
622
- // add white background if view has a transparent background
623
- layer.backgroundColor = UIColor.whiteColor.CGColor;
624
- }
625
- // shadow opacity is handled on the shadow's color instance
626
- layer.shadowOpacity = boxShadow.color?.a ? boxShadow.color?.a / 255 : 1;
627
- layer.shadowRadius = Length.toDevicePixels(boxShadow.blurRadius, 0.0);
628
- layer.shadowColor = boxShadow.color.ios.CGColor;
629
- // prettier-ignore
630
- layer.shadowOffset = CGSizeMake(Length.toDevicePixels(boxShadow.offsetX, 0.0), Length.toDevicePixels(boxShadow.offsetY, 0.0));
631
- // this should match the view's border radius
632
- let cornerRadius;
633
- if (typeof view.style.borderRadius !== 'number') {
634
- cornerRadius = Length.toDevicePixels(view.style.borderRadius, 0.0);
923
+ function drawBoxShadow(view) {
924
+ const background = view.style.backgroundInternal;
925
+ const nativeView = view.nativeViewProtected;
926
+ const layer = nativeView.layer;
927
+ // There is no parent to add shadow to
928
+ if (!layer.superlayer) {
929
+ return;
930
+ }
931
+ const bounds = nativeView.bounds;
932
+ const viewFrame = nativeView.frame;
933
+ const boxShadow = background.getBoxShadow();
934
+ // Initialize outer shadows
935
+ let outerShadowContainerLayer;
936
+ if (nativeView.outerShadowContainerLayer) {
937
+ outerShadowContainerLayer = nativeView.outerShadowContainerLayer;
635
938
  }
636
939
  else {
637
- cornerRadius = view.style.borderRadius;
940
+ outerShadowContainerLayer = CALayer.new();
941
+ // TODO: Make this dynamic when views get support for multiple shadows
942
+ const shadowLayer = CALayer.new();
943
+ // This mask is necessary to maintain transparent background
944
+ const maskLayer = CAShapeLayer.new();
945
+ maskLayer.fillRule = kCAFillRuleEvenOdd;
946
+ shadowLayer.mask = maskLayer;
947
+ outerShadowContainerLayer.addSublayer(shadowLayer);
948
+ // Instead of nesting it, add shadow container layer underneath view so that it's not affected by border masking
949
+ layer.superlayer.insertSublayerBelow(outerShadowContainerLayer, layer);
950
+ nativeView.outerShadowContainerLayer = outerShadowContainerLayer;
951
+ }
952
+ // Apply clip path to shadow
953
+ if (nativeView.maskType === iosViewUtils.LayerMask.CLIP_PATH && layer.mask instanceof CAShapeLayer) {
954
+ if (!outerShadowContainerLayer.mask) {
955
+ outerShadowContainerLayer.mask = CAShapeLayer.new();
956
+ }
957
+ if (outerShadowContainerLayer.mask instanceof CAShapeLayer) {
958
+ outerShadowContainerLayer.mask.path = layer.mask.path;
959
+ }
960
+ }
961
+ outerShadowContainerLayer.bounds = bounds;
962
+ outerShadowContainerLayer.anchorPoint = layer.anchorPoint;
963
+ // Since shadow uses superlayer's coordinate system, we have to be more specific about shadow layer position
964
+ const { x: originX, y: originY } = outerShadowContainerLayer.anchorPoint;
965
+ outerShadowContainerLayer.position = CGPointMake(viewFrame.origin.x + viewFrame.size.width * originX, viewFrame.origin.y + viewFrame.size.height * originY);
966
+ // Inherit view visibility values
967
+ outerShadowContainerLayer.opacity = layer.opacity;
968
+ outerShadowContainerLayer.hidden = layer.hidden;
969
+ const outerShadowLayers = outerShadowContainerLayer.sublayers;
970
+ if (outerShadowLayers?.count) {
971
+ for (let i = 0, count = outerShadowLayers.count; i < count; i++) {
972
+ const shadowLayer = outerShadowLayers[i];
973
+ const shadowRadius = layout.toDeviceIndependentPixels(boxShadow.blurRadius);
974
+ const spreadRadius = layout.toDeviceIndependentPixels(boxShadow.spreadRadius);
975
+ const offsetX = layout.toDeviceIndependentPixels(boxShadow.offsetX);
976
+ const offsetY = layout.toDeviceIndependentPixels(boxShadow.offsetY);
977
+ const { maskPath, shadowPath } = ios.generateShadowLayerPaths(view, bounds);
978
+ shadowLayer.allowsEdgeAntialiasing = true;
979
+ shadowLayer.contentsScale = Screen.mainScreen.scale;
980
+ // Shadow opacity is handled on the shadow's color instance
981
+ shadowLayer.shadowOpacity = boxShadow.color?.a ? boxShadow.color.a / 255 : 1;
982
+ shadowLayer.shadowRadius = shadowRadius;
983
+ shadowLayer.shadowColor = boxShadow.color?.ios?.CGColor;
984
+ shadowLayer.shadowOffset = CGSizeMake(offsetX, offsetY);
985
+ // Apply spread radius by expanding shadow layer bounds (this has a nice glow with radii set to 0)
986
+ shadowLayer.shadowPath = shadowPath;
987
+ // A mask that ensures that view maintains transparent background
988
+ if (shadowLayer.mask instanceof CAShapeLayer) {
989
+ shadowLayer.mask.path = maskPath;
990
+ }
991
+ }
638
992
  }
639
- // apply spreadRadius by expanding shadow layer bounds
640
- // prettier-ignore
641
- const bounds = CGRectInset(nativeView.bounds, -Length.toDevicePixels(boxShadow.spreadRadius, 0.0), -Length.toDevicePixels(boxShadow.spreadRadius, 0.0));
642
- // This has the nice glow with box shadow of 0,0
643
- layer.shadowPath = UIBezierPath.bezierPathWithRoundedRectCornerRadius(bounds, cornerRadius).CGPath;
644
993
  }
645
994
  function clearBoxShadow(nativeView) {
646
- nativeView.clipsToBounds = true;
647
- const layer = iOSNativeHelper.getShadowLayer(nativeView, 'ns-box-shadow', false);
648
- if (!layer) {
649
- return;
995
+ if (nativeView.outerShadowContainerLayer) {
996
+ nativeView.outerShadowContainerLayer.removeFromSuperlayer();
997
+ nativeView.outerShadowContainerLayer = null;
650
998
  }
651
- layer.masksToBounds = true;
652
- layer.shadowOffset = CGSizeMake(0, 0);
653
- layer.shadowColor = UIColor.clearColor.CGColor;
654
- layer.cornerRadius = 0.0;
655
- layer.shadowRadius = 0.0;
656
- layer.shadowOpacity = 0.0;
657
999
  }
658
- function drawClipPath(nativeView, background) {
659
- const layer = nativeView.layer;
660
- const layerBounds = layer.bounds;
661
- const layerOrigin = layerBounds.origin;
662
- const layerSize = layerBounds.size;
663
- const bounds = {
664
- left: layerOrigin.x,
665
- top: layerOrigin.y,
666
- bottom: layerSize.height,
667
- right: layerSize.width,
668
- };
669
- if (bounds.right === 0 || bounds.bottom === 0) {
670
- return;
671
- }
672
- let path;
673
- const clipPath = background.clipPath;
674
- const functionName = clipPath.substring(0, clipPath.indexOf('('));
675
- const value = clipPath.replace(`${functionName}(`, '').replace(')', '');
676
- switch (functionName) {
677
- case 'rect':
678
- path = rectPath(value, bounds);
679
- break;
680
- case 'inset':
681
- path = insetPath(value, bounds);
682
- break;
683
- case 'circle':
684
- path = circlePath(value, bounds);
685
- break;
686
- case 'ellipse':
687
- path = ellipsePath(value, bounds);
688
- break;
689
- case 'polygon':
690
- path = polygonPath(value, bounds);
691
- break;
692
- }
693
- if (path) {
694
- const shape = CAShapeLayer.layer();
695
- shape.path = path;
696
- layer.mask = shape;
697
- nativeView.clipsToBounds = true;
698
- const borderWidth = background.getUniformBorderWidth();
699
- const borderColor = background.getUniformBorderColor();
700
- if (borderWidth > 0 && borderColor instanceof Color) {
701
- const borderLayer = CAShapeLayer.layer();
702
- borderLayer.path = path;
703
- borderLayer.lineWidth = borderWidth * 2;
704
- borderLayer.strokeColor = borderColor.ios.CGColor;
705
- borderLayer.fillColor = clearCGColor;
706
- borderLayer.frame = nativeView.bounds;
707
- layer.borderColor = undefined;
708
- layer.borderWidth = 0;
709
- layer.addSublayer(borderLayer);
710
- }
711
- }
1000
+ /**
1001
+ * Creates a mask that ensures no shadow will be displayed underneath transparent backgrounds.
1002
+ *
1003
+ * @param bounds
1004
+ * @param boxShadow
1005
+ * @param bordersClipPath
1006
+ * @returns
1007
+ */
1008
+ function generateShadowMaskPath(bounds, boxShadow, innerClipPath) {
1009
+ const shadowRadius = layout.toDeviceIndependentPixels(boxShadow.blurRadius);
1010
+ const spreadRadius = layout.toDeviceIndependentPixels(boxShadow.spreadRadius);
1011
+ const offsetX = layout.toDeviceIndependentPixels(boxShadow.offsetX);
1012
+ const offsetY = layout.toDeviceIndependentPixels(boxShadow.offsetY);
1013
+ // This value has to be large enough to avoid clipping shadow halo effect
1014
+ const outerRectRadius = shadowRadius * 3 + spreadRadius;
1015
+ const maskPath = CGPathCreateMutable();
1016
+ // Proper clip position and size
1017
+ const outerRect = CGRectOffset(CGRectInset(bounds, -outerRectRadius, -outerRectRadius), offsetX, offsetY);
1018
+ CGPathAddPath(maskPath, null, innerClipPath);
1019
+ CGPathAddRect(maskPath, null, outerRect);
1020
+ return maskPath;
712
1021
  }
713
- function rectPath(value, bounds) {
1022
+ function rectPath(value, position) {
714
1023
  const arr = value.split(/[\s]+/);
715
- const top = cssValueToDeviceIndependentPixels(arr[0], bounds.top);
716
- const right = cssValueToDeviceIndependentPixels(arr[1], bounds.right);
717
- const bottom = cssValueToDeviceIndependentPixels(arr[2], bounds.bottom);
718
- const left = cssValueToDeviceIndependentPixels(arr[3], bounds.left);
1024
+ const top = cssValueToDeviceIndependentPixels(arr[0], position.top);
1025
+ const right = cssValueToDeviceIndependentPixels(arr[1], position.right);
1026
+ const bottom = cssValueToDeviceIndependentPixels(arr[2], position.bottom);
1027
+ const left = cssValueToDeviceIndependentPixels(arr[3], position.left);
719
1028
  return UIBezierPath.bezierPathWithRect(CGRectMake(left, top, right - left, bottom - top)).CGPath;
720
1029
  }
721
- function insetPath(value, bounds) {
1030
+ function insetPath(value, position) {
722
1031
  const arr = value.split(/[\s]+/);
723
1032
  let topString;
724
1033
  let rightString;
@@ -742,40 +1051,40 @@ function insetPath(value, bounds) {
742
1051
  bottomString = arr[2];
743
1052
  leftString = arr[3];
744
1053
  }
745
- const top = cssValueToDeviceIndependentPixels(topString, bounds.bottom);
746
- const right = cssValueToDeviceIndependentPixels('100%', bounds.right) - cssValueToDeviceIndependentPixels(rightString, bounds.right);
747
- const bottom = cssValueToDeviceIndependentPixels('100%', bounds.bottom) - cssValueToDeviceIndependentPixels(bottomString, bounds.bottom);
748
- const left = cssValueToDeviceIndependentPixels(leftString, bounds.right);
1054
+ const top = cssValueToDeviceIndependentPixels(topString, position.bottom);
1055
+ const right = cssValueToDeviceIndependentPixels('100%', position.right) - cssValueToDeviceIndependentPixels(rightString, position.right);
1056
+ const bottom = cssValueToDeviceIndependentPixels('100%', position.bottom) - cssValueToDeviceIndependentPixels(bottomString, position.bottom);
1057
+ const left = cssValueToDeviceIndependentPixels(leftString, position.right);
749
1058
  return UIBezierPath.bezierPathWithRect(CGRectMake(left, top, right - left, bottom - top)).CGPath;
750
1059
  }
751
- function circlePath(value, bounds) {
1060
+ function circlePath(value, position) {
752
1061
  const arr = value.split(/[\s]+/);
753
- const radius = cssValueToDeviceIndependentPixels(arr[0], (bounds.right > bounds.bottom ? bounds.bottom : bounds.right) / 2);
754
- const y = cssValueToDeviceIndependentPixels(arr[2], bounds.bottom);
755
- const x = cssValueToDeviceIndependentPixels(arr[3], bounds.right);
1062
+ const radius = cssValueToDeviceIndependentPixels(arr[0], (position.right > position.bottom ? position.bottom : position.right) / 2);
1063
+ const y = cssValueToDeviceIndependentPixels(arr[2], position.bottom);
1064
+ const x = cssValueToDeviceIndependentPixels(arr[3], position.right);
756
1065
  return UIBezierPath.bezierPathWithArcCenterRadiusStartAngleEndAngleClockwise(CGPointMake(x, y), radius, 0, 360, true).CGPath;
757
1066
  }
758
- function ellipsePath(value, bounds) {
1067
+ function ellipsePath(value, position) {
759
1068
  const arr = value.split(/[\s]+/);
760
- const rX = cssValueToDeviceIndependentPixels(arr[0], bounds.right);
761
- const rY = cssValueToDeviceIndependentPixels(arr[1], bounds.bottom);
762
- const cX = cssValueToDeviceIndependentPixels(arr[3], bounds.right);
763
- const cY = cssValueToDeviceIndependentPixels(arr[4], bounds.bottom);
1069
+ const rX = cssValueToDeviceIndependentPixels(arr[0], position.right);
1070
+ const rY = cssValueToDeviceIndependentPixels(arr[1], position.bottom);
1071
+ const cX = cssValueToDeviceIndependentPixels(arr[3], position.right);
1072
+ const cY = cssValueToDeviceIndependentPixels(arr[4], position.bottom);
764
1073
  const left = cX - rX;
765
1074
  const top = cY - rY;
766
1075
  const width = rX * 2;
767
1076
  const height = rY * 2;
768
1077
  return UIBezierPath.bezierPathWithOvalInRect(CGRectMake(left, top, width, height)).CGPath;
769
1078
  }
770
- function polygonPath(value, bounds) {
1079
+ function polygonPath(value, position) {
771
1080
  const path = CGPathCreateMutable();
772
1081
  let firstPoint;
773
1082
  const arr = value.split(/[,]+/);
774
1083
  for (let i = 0; i < arr.length; i++) {
775
1084
  const xy = arr[i].trim().split(/[\s]+/);
776
1085
  const point = {
777
- x: cssValueToDeviceIndependentPixels(xy[0], bounds.right),
778
- y: cssValueToDeviceIndependentPixels(xy[1], bounds.bottom),
1086
+ x: cssValueToDeviceIndependentPixels(xy[0], position.right),
1087
+ y: cssValueToDeviceIndependentPixels(xy[1], position.bottom),
779
1088
  };
780
1089
  if (!firstPoint) {
781
1090
  firstPoint = point;