craft-native 0.0.141 → 0.0.143
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/android/templates/MainActivityNative.kt.template +59 -16
- package/dist/cli.js +1 -1
- package/dist/index.cjs +1 -1
- package/dist/index.js +1 -1
- package/dist/ios/templates/CraftNativeActions.swift +18 -1
- package/dist/ios/templates/CraftNativeFlatList.swift +1 -1
- package/dist/ios/templates/CraftNativeScreen.swift +47 -12
- package/package.json +1 -1
|
@@ -13,6 +13,7 @@ import android.graphics.BitmapFactory
|
|
|
13
13
|
import android.graphics.Canvas
|
|
14
14
|
import android.graphics.Color
|
|
15
15
|
import android.graphics.DashPathEffect
|
|
16
|
+
import android.graphics.Matrix
|
|
16
17
|
import android.graphics.Paint
|
|
17
18
|
import android.graphics.Path
|
|
18
19
|
import android.graphics.Typeface
|
|
@@ -217,16 +218,36 @@ private class CraftNativeSvgView(context: Context) : View(context) {
|
|
|
217
218
|
setMeasuredDimension(resolveSize(width, widthMeasureSpec), resolveSize(height, heightMeasureSpec))
|
|
218
219
|
}
|
|
219
220
|
|
|
221
|
+
/** viewBox units to the view's pixels, set by each draw. */
|
|
222
|
+
private val viewTransform = Matrix()
|
|
223
|
+
private val inverseTransform = Matrix()
|
|
224
|
+
|
|
220
225
|
override fun onDraw(canvas: Canvas) {
|
|
221
226
|
super.onDraw(canvas)
|
|
222
227
|
val box = viewBox() ?: return
|
|
223
228
|
if (box.first <= 0 || box.second <= 0) return
|
|
224
|
-
|
|
225
|
-
|
|
229
|
+
// Centred at one scale (SVG's default, `xMidYMid meet`), or each axis
|
|
230
|
+
// stretched to fill (`preserveAspectRatio="none"`).
|
|
231
|
+
val stretch = drawing.optJSONObject("props")?.optString("preserveAspectRatio")?.trim() == "none"
|
|
232
|
+
var sx = width.toFloat() / box.first
|
|
233
|
+
var sy = height.toFloat() / box.second
|
|
234
|
+
var dx = 0f
|
|
235
|
+
var dy = 0f
|
|
236
|
+
if (!stretch) {
|
|
237
|
+
val scale = min(sx, sy)
|
|
238
|
+
sx = scale
|
|
239
|
+
sy = scale
|
|
240
|
+
dx = (width - box.first * scale) / 2f
|
|
241
|
+
dy = (height - box.second * scale) / 2f
|
|
242
|
+
}
|
|
243
|
+
if (!sx.isFinite() || !sy.isFinite() || sx <= 0f || sy <= 0f) return
|
|
244
|
+
viewTransform.reset()
|
|
245
|
+
viewTransform.postTranslate(-box.third, -box.fourth)
|
|
246
|
+
viewTransform.postScale(sx, sy)
|
|
247
|
+
viewTransform.postTranslate(dx, dy)
|
|
248
|
+
viewTransform.invert(inverseTransform)
|
|
226
249
|
canvas.save()
|
|
227
|
-
canvas.
|
|
228
|
-
canvas.scale(scale, scale)
|
|
229
|
-
canvas.translate(-box.third, -box.fourth)
|
|
250
|
+
canvas.concat(viewTransform)
|
|
230
251
|
drawNodes(canvas, drawing.optJSONArray("children") ?: JSONArray(), SvgPaint().merge(drawing))
|
|
231
252
|
canvas.restore()
|
|
232
253
|
}
|
|
@@ -310,7 +331,17 @@ private class CraftNativeSvgView(context: Context) : View(context) {
|
|
|
310
331
|
}
|
|
311
332
|
}
|
|
312
333
|
}
|
|
313
|
-
|
|
334
|
+
// `vector-effect="non-scaling-stroke"`: the stroke is in the view's
|
|
335
|
+
// pixels, so a stretched chart's line keeps its width.
|
|
336
|
+
if (props.optString("vectorEffect") == "non-scaling-stroke") {
|
|
337
|
+
val line = Path(path).apply { transform(viewTransform) }
|
|
338
|
+
canvas.save()
|
|
339
|
+
canvas.concat(inverseTransform)
|
|
340
|
+
canvas.drawPath(line, stroke)
|
|
341
|
+
canvas.restore()
|
|
342
|
+
} else {
|
|
343
|
+
canvas.drawPath(path, stroke)
|
|
344
|
+
}
|
|
314
345
|
}
|
|
315
346
|
}
|
|
316
347
|
|
|
@@ -443,7 +474,8 @@ private class CraftNativeFlexLayout(context: Context) : LinearLayout(context) {
|
|
|
443
474
|
}.toFloatArray()
|
|
444
475
|
val sharedLineIds = CraftNative.flexLines(availableMain.toFloat(), gap.toFloat(), wrap, basis, margins)
|
|
445
476
|
val lineIds = sharedLineIds?.takeIf { ids ->
|
|
446
|
-
ids.size >= pending.size && ids.firstOrNull() == 0 &&
|
|
477
|
+
ids.size >= pending.size && ids.firstOrNull() == 0 &&
|
|
478
|
+
ids.indices.drop(1).all { index -> ids[index] >= ids[index - 1] }
|
|
447
479
|
}
|
|
448
480
|
var line = Line()
|
|
449
481
|
pending.forEachIndexed { index, entry ->
|
|
@@ -1896,6 +1928,7 @@ class MainActivity : AppCompatActivity() {
|
|
|
1896
1928
|
if (!screen.controls.containsKey("id:$target")) return false
|
|
1897
1929
|
makeView(screen, document, target, screen.controls)
|
|
1898
1930
|
val updated = screen.controls["id:$target"] ?: return false
|
|
1931
|
+
screen.lastLayoutBounds.remove(updated)
|
|
1899
1932
|
if (!updateParentLayoutStyle(updated, document.optJSONObject("style") ?: JSONObject())) return false
|
|
1900
1933
|
}
|
|
1901
1934
|
if (applied.isNotEmpty()) {
|
|
@@ -1908,14 +1941,16 @@ class MainActivity : AppCompatActivity() {
|
|
|
1908
1941
|
if (!screen.active) return
|
|
1909
1942
|
val observer = screen.view.viewTreeObserver
|
|
1910
1943
|
if (observer.isAlive) {
|
|
1911
|
-
val listener = object : ViewTreeObserver.
|
|
1912
|
-
override fun
|
|
1913
|
-
if (observer.isAlive) observer.
|
|
1944
|
+
val listener = object : ViewTreeObserver.OnPreDrawListener {
|
|
1945
|
+
override fun onPreDraw(): Boolean {
|
|
1946
|
+
if (observer.isAlive) observer.removeOnPreDrawListener(this)
|
|
1914
1947
|
if (screen.active) emitLayoutEvents(screen)
|
|
1948
|
+
return true
|
|
1915
1949
|
}
|
|
1916
1950
|
}
|
|
1917
|
-
observer.
|
|
1951
|
+
observer.addOnPreDrawListener(listener)
|
|
1918
1952
|
}
|
|
1953
|
+
screen.view.forceLayout()
|
|
1919
1954
|
screen.view.requestLayout()
|
|
1920
1955
|
// A tree that is already settled may not produce a global-layout
|
|
1921
1956
|
// callback; keep the event path live for that case as well.
|
|
@@ -1929,15 +1964,23 @@ class MainActivity : AppCompatActivity() {
|
|
|
1929
1964
|
*/
|
|
1930
1965
|
private fun updateParentLayoutStyle(view: View, style: JSONObject): Boolean {
|
|
1931
1966
|
var parent = view.parent
|
|
1967
|
+
var found = false
|
|
1932
1968
|
while (parent != null) {
|
|
1933
|
-
|
|
1934
|
-
|
|
1935
|
-
|
|
1936
|
-
|
|
1969
|
+
when (parent) {
|
|
1970
|
+
is CraftNativeFlexLayout -> {
|
|
1971
|
+
if (!found) parent.setChildStyle(view, style, resources.displayMetrics.density)
|
|
1972
|
+
parent.forceLayout()
|
|
1973
|
+
parent.requestLayout()
|
|
1974
|
+
found = true
|
|
1975
|
+
}
|
|
1976
|
+
is CraftNativeScrollView -> {
|
|
1977
|
+
parent.forceLayout()
|
|
1978
|
+
parent.requestLayout()
|
|
1979
|
+
}
|
|
1937
1980
|
}
|
|
1938
1981
|
parent = parent.parent
|
|
1939
1982
|
}
|
|
1940
|
-
return
|
|
1983
|
+
return found
|
|
1941
1984
|
}
|
|
1942
1985
|
|
|
1943
1986
|
private fun renderCommitted(screen: Screen, document: JSONObject) {
|
package/dist/cli.js
CHANGED
|
@@ -3303,7 +3303,7 @@ function pinCraftNativeDependency(packagePath, craftVersion) {
|
|
|
3303
3303
|
`);
|
|
3304
3304
|
}
|
|
3305
3305
|
// package.json
|
|
3306
|
-
var version = "0.0.
|
|
3306
|
+
var version = "0.0.143";
|
|
3307
3307
|
|
|
3308
3308
|
// bin/cli.ts
|
|
3309
3309
|
var spawnedFrom = process6.env[CRAFT_CLI_SPAWN_MARKER];
|
package/dist/index.cjs
CHANGED
package/dist/index.js
CHANGED
|
@@ -38,7 +38,7 @@ enum CraftNativeActions {
|
|
|
38
38
|
private static let sqliteTransient = unsafeBitCast(-1, to: sqlite3_destructor_type.self)
|
|
39
39
|
|
|
40
40
|
static func capabilities(config: CraftConfig) -> [String] {
|
|
41
|
-
var values = ["device", "clipboard", "haptics", "storage", "lifecycle", "fetch"]
|
|
41
|
+
var values = ["device", "clipboard", "haptics", "storage", "lifecycle", "fetch", "dialog"]
|
|
42
42
|
if config.enableBiometric { values.append("biometric") }
|
|
43
43
|
if config.enableSecureStorage { values.append("secureStorage") }
|
|
44
44
|
if config.enableLocalDatabase { values.append("database") }
|
|
@@ -146,6 +146,23 @@ enum CraftNativeActions {
|
|
|
146
146
|
}
|
|
147
147
|
databaseQueue.async { complete(requestToken, database(method: method, args: args), completion) }
|
|
148
148
|
return
|
|
149
|
+
case ("Dialog", _):
|
|
150
|
+
// The system's own alert, confirmation and action sheet, the same
|
|
151
|
+
// ones a page gets (CraftNativeUI.swift), over the native screen.
|
|
152
|
+
let body = args.first as? [String: Any] ?? [:]
|
|
153
|
+
DispatchQueue.main.async {
|
|
154
|
+
switch method {
|
|
155
|
+
case "alert":
|
|
156
|
+
CraftDialogs.alert(body, from: nil) { complete(requestToken, .success(NSNull()), completion) }
|
|
157
|
+
case "confirm":
|
|
158
|
+
CraftDialogs.confirm(body, from: nil) { complete(requestToken, .success($0), completion) }
|
|
159
|
+
case "actionSheet":
|
|
160
|
+
CraftDialogs.actionSheet(body, from: nil) { complete(requestToken, .success($0 ?? NSNull()), completion) }
|
|
161
|
+
default:
|
|
162
|
+
complete(requestToken, failure("UNKNOWN_ACTION", "Unsupported Dialog method"), completion)
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
return
|
|
149
166
|
case ("Health", _):
|
|
150
167
|
// Apple Health, the same as a page's `craft.health` (CraftHealth.swift).
|
|
151
168
|
guard config.enableHealthKit else {
|
|
@@ -525,7 +525,7 @@ final class CraftNativeFlatList: UICollectionView, UICollectionViewDelegateFlowL
|
|
|
525
525
|
|
|
526
526
|
private static func identity(for node: [String: Any]) -> String? {
|
|
527
527
|
let props = node["props"] as? [String: Any] ?? [:]
|
|
528
|
-
return [node["
|
|
528
|
+
return [node["key"], props["key"], props["testID"], node["id"]]
|
|
529
529
|
.compactMap { $0 as? String }
|
|
530
530
|
.first { !$0.isEmpty }
|
|
531
531
|
}
|
|
@@ -1789,14 +1789,36 @@ private final class CraftNativeSvgView: UIView {
|
|
|
1789
1789
|
}
|
|
1790
1790
|
}
|
|
1791
1791
|
|
|
1792
|
+
/// viewBox units to the view's points, set by each draw.
|
|
1793
|
+
private var viewTransform = CGAffineTransform.identity
|
|
1794
|
+
|
|
1795
|
+
/// viewBox to view: centred at one scale (SVG's default, `xMidYMid meet`),
|
|
1796
|
+
/// or each axis stretched to fill (`preserveAspectRatio="none"`), as a
|
|
1797
|
+
/// chart a page sizes to its width and a fixed height asks for.
|
|
1798
|
+
static func transform(box: CGRect, bounds: CGSize, preserveAspectRatio: String?) -> CGAffineTransform? {
|
|
1799
|
+
var sx = bounds.width / box.width
|
|
1800
|
+
var sy = bounds.height / box.height
|
|
1801
|
+
var dx: CGFloat = 0
|
|
1802
|
+
var dy: CGFloat = 0
|
|
1803
|
+
if preserveAspectRatio?.trimmingCharacters(in: .whitespaces) != "none" {
|
|
1804
|
+
let scale = min(sx, sy)
|
|
1805
|
+
sx = scale
|
|
1806
|
+
sy = scale
|
|
1807
|
+
dx = (bounds.width - box.width * scale) / 2
|
|
1808
|
+
dy = (bounds.height - box.height * scale) / 2
|
|
1809
|
+
}
|
|
1810
|
+
guard sx.isFinite, sy.isFinite, sx > 0, sy > 0 else { return nil }
|
|
1811
|
+
return CGAffineTransform(translationX: dx, y: dy).scaledBy(x: sx, y: sy).translatedBy(x: -box.minX, y: -box.minY)
|
|
1812
|
+
}
|
|
1813
|
+
|
|
1792
1814
|
override func draw(_ rect: CGRect) {
|
|
1793
1815
|
guard let context = UIGraphicsGetCurrentContext() else { return }
|
|
1816
|
+
viewTransform = .identity
|
|
1794
1817
|
if let box = viewBox {
|
|
1795
|
-
let
|
|
1796
|
-
guard
|
|
1797
|
-
|
|
1798
|
-
context.
|
|
1799
|
-
context.translateBy(x: -box.minX, y: -box.minY)
|
|
1818
|
+
let props = drawing["props"] as? [String: Any] ?? [:]
|
|
1819
|
+
guard let transform = CraftNativeSvgView.transform(box: box, bounds: bounds.size, preserveAspectRatio: props["preserveAspectRatio"] as? String) else { return }
|
|
1820
|
+
viewTransform = transform
|
|
1821
|
+
context.concatenate(transform)
|
|
1800
1822
|
}
|
|
1801
1823
|
draw(children: drawing["children"] as? [Any] ?? [], paint: Paint().merged(drawing), in: context)
|
|
1802
1824
|
}
|
|
@@ -1820,12 +1842,25 @@ private final class CraftNativeSvgView: UIView {
|
|
|
1820
1842
|
path.fill()
|
|
1821
1843
|
}
|
|
1822
1844
|
if let stroke = own.resolve(own.stroke), own.strokeWidth > 0 {
|
|
1845
|
+
// `vector-effect="non-scaling-stroke"`: the stroke is in the
|
|
1846
|
+
// view's points, so a stretched chart's line keeps its width
|
|
1847
|
+
// and a round cap stays a round dot.
|
|
1848
|
+
let nonScaling = (shape["props"] as? [String: Any])?["vectorEffect"] as? String == "non-scaling-stroke"
|
|
1849
|
+
let line: UIBezierPath
|
|
1850
|
+
if nonScaling, let copy = path.copy() as? UIBezierPath {
|
|
1851
|
+
copy.apply(viewTransform)
|
|
1852
|
+
context.concatenate(viewTransform.inverted())
|
|
1853
|
+
line = copy
|
|
1854
|
+
}
|
|
1855
|
+
else {
|
|
1856
|
+
line = path
|
|
1857
|
+
}
|
|
1823
1858
|
stroke.setStroke()
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
|
|
1827
|
-
if let dashes = own.dashes, !dashes.isEmpty {
|
|
1828
|
-
|
|
1859
|
+
line.lineWidth = own.strokeWidth
|
|
1860
|
+
line.lineCapStyle = own.linecap == "round" ? .round : own.linecap == "square" ? .square : .butt
|
|
1861
|
+
line.lineJoinStyle = own.linejoin == "round" ? .round : own.linejoin == "bevel" ? .bevel : .miter
|
|
1862
|
+
if let dashes = own.dashes, !dashes.isEmpty { line.setLineDash(dashes, count: dashes.count, phase: 0) }
|
|
1863
|
+
line.stroke()
|
|
1829
1864
|
}
|
|
1830
1865
|
context.restoreGState()
|
|
1831
1866
|
}
|
|
@@ -3493,7 +3528,7 @@ final class CraftNativeScreenController: UIViewController, UIScrollViewDelegate,
|
|
|
3493
3528
|
private func explicitKey(_ node: [String: Any], props: [String: Any]) -> String? {
|
|
3494
3529
|
// The current @stacksjs/stx native compiler emits key in props; accept the IR
|
|
3495
3530
|
// field too so keyed children keep working when the compiler adopts it.
|
|
3496
|
-
[node["
|
|
3531
|
+
[node["key"], props["key"], props["testID"], node["id"]]
|
|
3497
3532
|
.compactMap { $0 as? String }
|
|
3498
3533
|
.first { !$0.isEmpty }
|
|
3499
3534
|
}
|
|
@@ -4293,7 +4328,7 @@ final class CraftNativeScreenController: UIViewController, UIScrollViewDelegate,
|
|
|
4293
4328
|
view.accessibilityValue = nil
|
|
4294
4329
|
}
|
|
4295
4330
|
let role = props["accessibilityRole"] as? String
|
|
4296
|
-
view.isAccessibilityElement = role != "none" && (view.accessibilityLabel != nil || role != nil || ["Text", "Button", "Link", "Image", "TextInput", "Picker", "Switch", "Slider"].contains(type))
|
|
4331
|
+
view.isAccessibilityElement = role != "none" && (view.accessibilityLabel != nil || role != nil || ["Text", "Button", "Link", "Image", "Video", "TextInput", "Picker", "Switch", "Slider"].contains(type))
|
|
4297
4332
|
var traits: UIAccessibilityTraits = []
|
|
4298
4333
|
switch role ?? type.lowercased() {
|
|
4299
4334
|
case "button": traits.insert(.button)
|