craft-native 0.0.120 → 0.0.122
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 +378 -54
- package/dist/cli.js +1 -1
- package/dist/components/index.d.ts +9 -0
- package/dist/index.cjs +1 -1
- package/dist/index.js +1 -1
- package/dist/ios/src/index.d.ts +12 -0
- package/dist/ios/src/index.js +56 -3
- package/dist/ios/templates/CraftNativeScreen.swift +414 -52
- package/dist/ios/templates/Info.plist.template +2 -0
- package/dist/ios/templates/project.yml.template +17 -0
- package/package.json +1 -1
|
@@ -22,6 +22,9 @@ import android.util.Log
|
|
|
22
22
|
import android.util.Base64
|
|
23
23
|
import android.util.TypedValue
|
|
24
24
|
import android.view.Gravity
|
|
25
|
+
import android.view.KeyEvent
|
|
26
|
+
import android.view.inputmethod.EditorInfo
|
|
27
|
+
import android.text.InputType
|
|
25
28
|
import android.view.View
|
|
26
29
|
import android.view.ViewGroup
|
|
27
30
|
import android.widget.Button
|
|
@@ -50,16 +53,221 @@ import java.net.URL
|
|
|
50
53
|
import java.util.concurrent.Executors
|
|
51
54
|
import java.util.concurrent.Future
|
|
52
55
|
import java.util.UUID
|
|
56
|
+
import kotlin.math.max
|
|
57
|
+
import kotlin.math.min
|
|
53
58
|
|
|
54
59
|
private const val CRAFT_NATIVE_CAPABILITY_TIMEOUT_MS = 30_000L
|
|
55
60
|
|
|
61
|
+
private data class CraftNativeLayoutStyle(
|
|
62
|
+
val width: Int? = null,
|
|
63
|
+
val height: Int? = null,
|
|
64
|
+
val minWidth: Int? = null,
|
|
65
|
+
val maxWidth: Int? = null,
|
|
66
|
+
val minHeight: Int? = null,
|
|
67
|
+
val maxHeight: Int? = null,
|
|
68
|
+
val position: String = "relative",
|
|
69
|
+
val top: Int? = null,
|
|
70
|
+
val right: Int? = null,
|
|
71
|
+
val bottom: Int? = null,
|
|
72
|
+
val left: Int? = null,
|
|
73
|
+
val alignSelf: String? = null,
|
|
74
|
+
) {
|
|
75
|
+
companion object {
|
|
76
|
+
fun from(raw: JSONObject, density: Float): CraftNativeLayoutStyle {
|
|
77
|
+
fun number(name: String): Int? = if (raw.has(name)) (raw.optDouble(name) * density).toInt() else null
|
|
78
|
+
return CraftNativeLayoutStyle(
|
|
79
|
+
width = number("width"), height = number("height"),
|
|
80
|
+
minWidth = number("minWidth"), maxWidth = number("maxWidth"),
|
|
81
|
+
minHeight = number("minHeight"), maxHeight = number("maxHeight"),
|
|
82
|
+
position = raw.optString("position", "relative"),
|
|
83
|
+
top = number("top"), right = number("right"), bottom = number("bottom"), left = number("left"),
|
|
84
|
+
alignSelf = raw.optString("alignSelf").takeIf { it.isNotBlank() },
|
|
85
|
+
)
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/** A deterministic flex-shaped container shared by normal views and scroll content. */
|
|
91
|
+
private class CraftNativeFlexLayout(context: Context) : LinearLayout(context) {
|
|
92
|
+
private val styles = mutableMapOf<View, CraftNativeLayoutStyle>()
|
|
93
|
+
private var wrap = false
|
|
94
|
+
private var alignItems = "stretch"
|
|
95
|
+
private var justifyContent = "flex-start"
|
|
96
|
+
private var rowGap = 0
|
|
97
|
+
private var columnGap = 0
|
|
98
|
+
|
|
99
|
+
fun configure(style: JSONObject, density: Float) {
|
|
100
|
+
orientation = if (style.optString("flexDirection") in setOf("row", "row-reverse")) HORIZONTAL else VERTICAL
|
|
101
|
+
wrap = style.optString("flexWrap") == "wrap" || style.optBoolean("flexWrap", false)
|
|
102
|
+
alignItems = style.optString("alignItems", "stretch")
|
|
103
|
+
justifyContent = style.optString("justifyContent", "flex-start")
|
|
104
|
+
val gap = (style.optDouble("gap", 0.0) * density).toInt()
|
|
105
|
+
rowGap = (style.optDouble("rowGap", gap.toDouble() / density.toDouble()) * density).toInt()
|
|
106
|
+
columnGap = (style.optDouble("columnGap", gap.toDouble() / density.toDouble()) * density).toInt()
|
|
107
|
+
val padding = (style.optDouble("padding", 0.0) * density).toInt()
|
|
108
|
+
val horizontal = (style.optDouble("paddingHorizontal", padding.toDouble() / density.toDouble()) * density).toInt()
|
|
109
|
+
val vertical = (style.optDouble("paddingVertical", padding.toDouble() / density.toDouble()) * density).toInt()
|
|
110
|
+
setPadding(
|
|
111
|
+
(style.optDouble("paddingLeft", horizontal.toDouble() / density.toDouble()) * density).toInt(),
|
|
112
|
+
(style.optDouble("paddingTop", vertical.toDouble() / density.toDouble()) * density).toInt(),
|
|
113
|
+
(style.optDouble("paddingRight", horizontal.toDouble() / density.toDouble()) * density).toInt(),
|
|
114
|
+
(style.optDouble("paddingBottom", vertical.toDouble() / density.toDouble()) * density).toInt(),
|
|
115
|
+
)
|
|
116
|
+
requestLayout()
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
fun setChildStyle(view: View, style: JSONObject, density: Float) {
|
|
120
|
+
styles[view] = CraftNativeLayoutStyle.from(style, density)
|
|
121
|
+
requestLayout()
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
override fun onViewRemoved(child: View) {
|
|
125
|
+
styles.remove(child)
|
|
126
|
+
super.onViewRemoved(child)
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
private data class Entry(val view: View, val style: CraftNativeLayoutStyle, val width: Int, val height: Int)
|
|
130
|
+
private data class Line(val entries: MutableList<Entry> = mutableListOf(), var main: Int = 0, var cross: Int = 0)
|
|
131
|
+
|
|
132
|
+
private fun entries(width: Int, height: Int): Pair<List<Line>, List<Entry>> {
|
|
133
|
+
val availableMain = if (orientation == HORIZONTAL) max(0, width - paddingLeft - paddingRight)
|
|
134
|
+
else max(0, height - paddingTop - paddingBottom)
|
|
135
|
+
val gap = if (orientation == HORIZONTAL) columnGap else rowGap
|
|
136
|
+
val lines = mutableListOf<Line>()
|
|
137
|
+
val absolute = mutableListOf<Entry>()
|
|
138
|
+
var line = Line()
|
|
139
|
+
for (index in 0 until childCount) {
|
|
140
|
+
val child = getChildAt(index)
|
|
141
|
+
if (child.visibility == GONE) continue
|
|
142
|
+
val style = styles[child] ?: CraftNativeLayoutStyle()
|
|
143
|
+
val lp = child.layoutParams as? MarginLayoutParams
|
|
144
|
+
val horizontalMargins = (lp?.leftMargin ?: 0) + (lp?.rightMargin ?: 0)
|
|
145
|
+
val verticalMargins = (lp?.topMargin ?: 0) + (lp?.bottomMargin ?: 0)
|
|
146
|
+
val childWidth = measureChildWidth(child, style, width - paddingLeft - paddingRight, horizontalMargins)
|
|
147
|
+
val childHeight = measureChildHeight(child, style, height - paddingTop - paddingBottom, verticalMargins)
|
|
148
|
+
val entry = Entry(child, style, childWidth, childHeight)
|
|
149
|
+
if (style.position == "absolute") { absolute += entry; continue }
|
|
150
|
+
val childMain = (if (orientation == HORIZONTAL) childWidth + horizontalMargins else childHeight + verticalMargins)
|
|
151
|
+
val childCross = (if (orientation == HORIZONTAL) childHeight + verticalMargins else childWidth + horizontalMargins)
|
|
152
|
+
val next = if (line.entries.isEmpty()) childMain else line.main + gap + childMain
|
|
153
|
+
if (wrap && line.entries.isNotEmpty() && next > availableMain) {
|
|
154
|
+
lines += line
|
|
155
|
+
line = Line()
|
|
156
|
+
}
|
|
157
|
+
line.entries += entry
|
|
158
|
+
line.main = if (line.entries.size == 1) childMain else line.main + gap + childMain
|
|
159
|
+
line.cross = max(line.cross, childCross)
|
|
160
|
+
}
|
|
161
|
+
if (line.entries.isNotEmpty()) lines += line
|
|
162
|
+
return lines to absolute
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
private fun measureChildWidth(child: View, style: CraftNativeLayoutStyle, available: Int, margins: Int): Int {
|
|
166
|
+
val explicit = style.width ?: (child.layoutParams?.width ?: ViewGroup.LayoutParams.WRAP_CONTENT).takeIf { it >= 0 }
|
|
167
|
+
val target = explicit ?: max(0, available - margins)
|
|
168
|
+
val spec = if (explicit != null) View.MeasureSpec.makeMeasureSpec(target, View.MeasureSpec.EXACTLY)
|
|
169
|
+
else View.MeasureSpec.makeMeasureSpec(max(0, available - margins), View.MeasureSpec.AT_MOST)
|
|
170
|
+
child.measure(spec, View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED))
|
|
171
|
+
return clamp(if (explicit != null) target else child.measuredWidth, style.minWidth, style.maxWidth)
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
private fun measureChildHeight(child: View, style: CraftNativeLayoutStyle, available: Int, margins: Int): Int {
|
|
175
|
+
val explicit = style.height ?: (child.layoutParams?.height ?: ViewGroup.LayoutParams.WRAP_CONTENT).takeIf { it >= 0 }
|
|
176
|
+
val target = explicit ?: max(0, available - margins)
|
|
177
|
+
val spec = if (explicit != null) View.MeasureSpec.makeMeasureSpec(target, View.MeasureSpec.EXACTLY)
|
|
178
|
+
else View.MeasureSpec.makeMeasureSpec(max(0, available - margins), View.MeasureSpec.AT_MOST)
|
|
179
|
+
child.measure(child.measuredWidth.let { View.MeasureSpec.makeMeasureSpec(max(0, it), View.MeasureSpec.EXACTLY) }, spec)
|
|
180
|
+
return clamp(if (explicit != null) target else child.measuredHeight, style.minHeight, style.maxHeight)
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
private fun clamp(value: Int, lower: Int?, upper: Int?): Int {
|
|
184
|
+
var result = value
|
|
185
|
+
if (lower != null) result = max(result, lower)
|
|
186
|
+
if (upper != null) result = min(result, upper)
|
|
187
|
+
return result
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
|
|
191
|
+
val width = View.MeasureSpec.getSize(widthMeasureSpec).takeUnless { View.MeasureSpec.getMode(widthMeasureSpec) == View.MeasureSpec.UNSPECIFIED } ?: 1_000_000
|
|
192
|
+
val height = View.MeasureSpec.getSize(heightMeasureSpec).takeUnless { View.MeasureSpec.getMode(heightMeasureSpec) == View.MeasureSpec.UNSPECIFIED } ?: 1_000_000
|
|
193
|
+
val (lines, absolute) = entries(width, height)
|
|
194
|
+
val main = lines.maxOfOrNull { it.main } ?: 0
|
|
195
|
+
val cross = lines.sumOf { it.cross } + max(0, lines.size - 1) * (if (orientation == HORIZONTAL) rowGap else columnGap)
|
|
196
|
+
val desiredWidth = if (orientation == HORIZONTAL) main else cross
|
|
197
|
+
val desiredHeight = if (orientation == HORIZONTAL) cross else main
|
|
198
|
+
val absWidth = absolute.maxOfOrNull { it.width } ?: 0
|
|
199
|
+
val absHeight = absolute.maxOfOrNull { it.height } ?: 0
|
|
200
|
+
setMeasuredDimension(
|
|
201
|
+
resolveSize(max(desiredWidth + paddingLeft + paddingRight, absWidth + paddingLeft + paddingRight), widthMeasureSpec),
|
|
202
|
+
resolveSize(max(desiredHeight + paddingTop + paddingBottom, absHeight + paddingTop + paddingBottom), heightMeasureSpec),
|
|
203
|
+
)
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
|
|
207
|
+
val contentWidth = width - paddingLeft - paddingRight
|
|
208
|
+
val contentHeight = height - paddingTop - paddingBottom
|
|
209
|
+
val (lines, absolute) = entries(width, height)
|
|
210
|
+
val gap = if (orientation == HORIZONTAL) columnGap else rowGap
|
|
211
|
+
var crossOffset = if (orientation == HORIZONTAL) paddingTop else paddingLeft
|
|
212
|
+
for (line in lines) {
|
|
213
|
+
val free = max(0, (if (orientation == HORIZONTAL) contentWidth else contentHeight) - line.main)
|
|
214
|
+
val (leading, between) = distribution(free, line.entries.size)
|
|
215
|
+
var mainOffset = if (orientation == HORIZONTAL) paddingLeft else paddingTop
|
|
216
|
+
mainOffset += leading
|
|
217
|
+
for (entry in line.entries) {
|
|
218
|
+
val lp = entry.view.layoutParams as? MarginLayoutParams
|
|
219
|
+
val marginStart = if (orientation == HORIZONTAL) lp?.leftMargin ?: 0 else lp?.topMargin ?: 0
|
|
220
|
+
val marginEnd = if (orientation == HORIZONTAL) lp?.rightMargin ?: 0 else lp?.bottomMargin ?: 0
|
|
221
|
+
val crossSize = if ((entry.style.alignSelf ?: alignItems) == "stretch" && (if (orientation == HORIZONTAL) entry.style.height else entry.style.width) == null)
|
|
222
|
+
max(0, line.cross - (if (orientation == HORIZONTAL) (lp?.topMargin ?: 0) + (lp?.bottomMargin ?: 0) else (lp?.leftMargin ?: 0) + (lp?.rightMargin ?: 0)))
|
|
223
|
+
else if (orientation == HORIZONTAL) entry.height else entry.width
|
|
224
|
+
val align = entry.style.alignSelf ?: alignItems
|
|
225
|
+
val crossPosition = when (align) {
|
|
226
|
+
"center" -> (line.cross - crossSize) / 2
|
|
227
|
+
"flex-end" -> line.cross - crossSize
|
|
228
|
+
else -> 0
|
|
229
|
+
}
|
|
230
|
+
val x = if (orientation == HORIZONTAL) mainOffset + marginStart else crossOffset + crossPosition + (lp?.leftMargin ?: 0)
|
|
231
|
+
val y = if (orientation == HORIZONTAL) crossOffset + crossPosition + (lp?.topMargin ?: 0) else mainOffset + marginStart
|
|
232
|
+
val w = if (orientation == HORIZONTAL) entry.width else crossSize
|
|
233
|
+
val h = if (orientation == HORIZONTAL) crossSize else entry.height
|
|
234
|
+
entry.view.layout(x, y, x + w, y + h)
|
|
235
|
+
mainOffset += (if (orientation == HORIZONTAL) entry.width else entry.height) + marginStart + marginEnd + gap + between
|
|
236
|
+
}
|
|
237
|
+
crossOffset += line.cross + gap
|
|
238
|
+
}
|
|
239
|
+
for (entry in absolute) {
|
|
240
|
+
val widthValue = if (entry.style.left != null && entry.style.right != null) contentWidth - entry.style.left!! - entry.style.right!! else entry.width
|
|
241
|
+
val heightValue = if (entry.style.top != null && entry.style.bottom != null) contentHeight - entry.style.top!! - entry.style.bottom!! else entry.height
|
|
242
|
+
val x = paddingLeft + (entry.style.left ?: (entry.style.right?.let { contentWidth - it - widthValue } ?: 0))
|
|
243
|
+
val y = paddingTop + (entry.style.top ?: (entry.style.bottom?.let { contentHeight - it - heightValue } ?: 0))
|
|
244
|
+
entry.view.layout(x, y, x + max(0, widthValue), y + max(0, heightValue))
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
private fun distribution(free: Int, count: Int): Pair<Int, Int> = when (justifyContent) {
|
|
249
|
+
"center" -> free / 2 to 0
|
|
250
|
+
"flex-end" -> free to 0
|
|
251
|
+
"space-between" -> 0 to if (count > 1) free / (count - 1) else 0
|
|
252
|
+
"space-around" -> free / (count * 2) to free / count
|
|
253
|
+
"space-evenly" -> free / (count + 1) to free / (count + 1)
|
|
254
|
+
else -> 0 to 0
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
56
258
|
private class CraftNativeScrollView(context: Context) : FrameLayout(context) {
|
|
57
|
-
val content =
|
|
259
|
+
val content = CraftNativeFlexLayout(context)
|
|
58
260
|
private val vertical = ScrollView(context)
|
|
59
261
|
private val horizontal = HorizontalScrollView(context)
|
|
60
262
|
private var horizontalMode: Boolean? = null
|
|
61
263
|
|
|
62
|
-
init {
|
|
264
|
+
init {
|
|
265
|
+
vertical.isNestedScrollingEnabled = true
|
|
266
|
+
horizontal.isNestedScrollingEnabled = true
|
|
267
|
+
vertical.isFillViewport = true
|
|
268
|
+
horizontal.isFillViewport = true
|
|
269
|
+
setHorizontal(false)
|
|
270
|
+
}
|
|
63
271
|
|
|
64
272
|
fun setHorizontal(value: Boolean) {
|
|
65
273
|
if (horizontalMode == value) return
|
|
@@ -74,6 +282,40 @@ private class CraftNativeScrollView(context: Context) : FrameLayout(context) {
|
|
|
74
282
|
else ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT))
|
|
75
283
|
addView(scroller, LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT))
|
|
76
284
|
}
|
|
285
|
+
|
|
286
|
+
fun setNativeNestedScrollingEnabled(value: Boolean) {
|
|
287
|
+
vertical.isNestedScrollingEnabled = value
|
|
288
|
+
horizontal.isNestedScrollingEnabled = value
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
fun setScrollEnabled(value: Boolean) {
|
|
292
|
+
vertical.isEnabled = value
|
|
293
|
+
horizontal.isEnabled = value
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
fun setScrollIndicators(verticalVisible: Boolean, horizontalVisible: Boolean) {
|
|
297
|
+
vertical.isVerticalScrollBarEnabled = verticalVisible
|
|
298
|
+
horizontal.isHorizontalScrollBarEnabled = horizontalVisible
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
fun setScrollListeners(
|
|
302
|
+
onScroll: ((Int, Int) -> Unit)?,
|
|
303
|
+
onBeginDrag: (() -> Unit)?,
|
|
304
|
+
onEndDrag: (() -> Unit)?,
|
|
305
|
+
) {
|
|
306
|
+
fun attach(scroller: View) {
|
|
307
|
+
scroller.setOnScrollChangeListener { _, x, y, _, _ -> onScroll?.invoke(x, y) }
|
|
308
|
+
scroller.setOnTouchListener { _, event ->
|
|
309
|
+
when (event.actionMasked) {
|
|
310
|
+
android.view.MotionEvent.ACTION_DOWN -> onBeginDrag?.invoke()
|
|
311
|
+
android.view.MotionEvent.ACTION_UP, android.view.MotionEvent.ACTION_CANCEL -> onEndDrag?.invoke()
|
|
312
|
+
}
|
|
313
|
+
false
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
attach(vertical)
|
|
317
|
+
attach(horizontal)
|
|
318
|
+
}
|
|
77
319
|
}
|
|
78
320
|
|
|
79
321
|
private class CraftNativeFlexSpacer(context: Context) : View(context)
|
|
@@ -83,13 +325,19 @@ class MainActivity : AppCompatActivity() {
|
|
|
83
325
|
private class Screen(
|
|
84
326
|
val route: String?,
|
|
85
327
|
val params: JSONObject,
|
|
86
|
-
val view:
|
|
328
|
+
val view: CraftNativeFlexLayout,
|
|
87
329
|
val isolate: JavaScriptIsolate,
|
|
88
330
|
val port: MessagePort?
|
|
89
331
|
) {
|
|
90
332
|
val drafts = mutableMapOf<String, String>()
|
|
91
333
|
val controls = mutableMapOf<String, View>()
|
|
92
334
|
val handlers = mutableMapOf<View, String>()
|
|
335
|
+
val scrollHandlers = mutableMapOf<View, String>()
|
|
336
|
+
val scrollBeginHandlers = mutableMapOf<View, String>()
|
|
337
|
+
val scrollEndHandlers = mutableMapOf<View, String>()
|
|
338
|
+
val focusHandlers = mutableMapOf<View, String>()
|
|
339
|
+
val blurHandlers = mutableMapOf<View, String>()
|
|
340
|
+
val submitHandlers = mutableMapOf<View, String>()
|
|
93
341
|
val flatListRows = mutableMapOf<CraftNativeFlatList, MutableMap<String, MutableMap<String, View>>>()
|
|
94
342
|
val flatListOwners = mutableMapOf<String, String>()
|
|
95
343
|
val flatListOwnerByView = mutableMapOf<CraftNativeFlatList, String>()
|
|
@@ -199,7 +447,7 @@ class MainActivity : AppCompatActivity() {
|
|
|
199
447
|
private fun openScreen(route: String?, params: JSONObject = JSONObject()) {
|
|
200
448
|
val engine = sandbox ?: return
|
|
201
449
|
val isolate = engine.createIsolate()
|
|
202
|
-
val view =
|
|
450
|
+
val view = CraftNativeFlexLayout(this).apply {
|
|
203
451
|
orientation = LinearLayout.VERTICAL
|
|
204
452
|
layoutParams = FrameLayout.LayoutParams(-1, -1)
|
|
205
453
|
}
|
|
@@ -399,6 +647,7 @@ class MainActivity : AppCompatActivity() {
|
|
|
399
647
|
screen.controls.clear()
|
|
400
648
|
screen.controls.putAll(next)
|
|
401
649
|
screen.handlers.keys.retainAll(retainedViews(screen))
|
|
650
|
+
screen.view.setChildStyle(tree, document.optJSONObject("style") ?: JSONObject(), resources.displayMetrics.density)
|
|
402
651
|
}
|
|
403
652
|
|
|
404
653
|
private fun clearRenderedTree(screen: Screen) {
|
|
@@ -445,10 +694,37 @@ class MainActivity : AppCompatActivity() {
|
|
|
445
694
|
}
|
|
446
695
|
"TextInput" -> (previous as? EditText ?: EditText(this)).apply {
|
|
447
696
|
hint = props.optString("placeholder")
|
|
697
|
+
inputType = inputTypeFor(props)
|
|
698
|
+
imeOptions = imeOptionsFor(props)
|
|
699
|
+
isSingleLine = !props.optBoolean("multiline", false)
|
|
700
|
+
isEnabled = props.optBoolean("editable", true)
|
|
448
701
|
if (props.has("value") && text.toString() != props.optString("value")) setText(props.optString("value"))
|
|
449
702
|
else if (this !== previous && screen.drafts.containsKey(identity)) setText(screen.drafts[identity])
|
|
450
703
|
val handler = events.optString("onChange").ifBlank { events.optString("onChangeText") }
|
|
451
704
|
if (handler.isNotBlank()) screen.handlers[this] = handler else screen.handlers.remove(this)
|
|
705
|
+
val onFocus = events.optString("onFocus")
|
|
706
|
+
if (onFocus.isNotBlank()) screen.focusHandlers[this] = onFocus else screen.focusHandlers.remove(this)
|
|
707
|
+
val onBlur = events.optString("onBlur").ifBlank { events.optString("onEndEditing") }
|
|
708
|
+
if (onBlur.isNotBlank()) screen.blurHandlers[this] = onBlur else screen.blurHandlers.remove(this)
|
|
709
|
+
val onSubmit = events.optString("onSubmitEditing")
|
|
710
|
+
if (onSubmit.isNotBlank()) screen.submitHandlers[this] = onSubmit else screen.submitHandlers.remove(this)
|
|
711
|
+
setOnFocusChangeListener { view, focused ->
|
|
712
|
+
val current = if (focused) screen.focusHandlers[view] else screen.blurHandlers[view]
|
|
713
|
+
if (current == null) return@setOnFocusChangeListener
|
|
714
|
+
send(screen, "EVENT", JSONObject().put("handlerName", current).put("nativeEvent", JSONObject().put("text", text.toString())))
|
|
715
|
+
}
|
|
716
|
+
setOnEditorActionListener { view, actionId, event ->
|
|
717
|
+
val submitted = actionId != EditorInfo.IME_ACTION_NONE || event?.keyCode == KeyEvent.KEYCODE_ENTER
|
|
718
|
+
if (!submitted) return@setOnEditorActionListener false
|
|
719
|
+
val current = screen.submitHandlers[view] ?: return@setOnEditorActionListener false
|
|
720
|
+
send(screen, "EVENT", JSONObject().put("handlerName", current).put("nativeEvent", JSONObject().put("text", text.toString())))
|
|
721
|
+
true
|
|
722
|
+
}
|
|
723
|
+
if (props.optBoolean("autoFocus", false)) post {
|
|
724
|
+
requestFocus()
|
|
725
|
+
(getSystemService(INPUT_METHOD_SERVICE) as? android.view.inputmethod.InputMethodManager)
|
|
726
|
+
?.showSoftInput(this, android.view.inputmethod.InputMethodManager.SHOW_IMPLICIT)
|
|
727
|
+
}
|
|
452
728
|
if (this !== previous) addTextChangedListener(object : TextWatcher {
|
|
453
729
|
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {}
|
|
454
730
|
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
|
|
@@ -475,13 +751,47 @@ class MainActivity : AppCompatActivity() {
|
|
|
475
751
|
"ScrollView" -> (previous as? CraftNativeScrollView ?: CraftNativeScrollView(this)).apply {
|
|
476
752
|
val horizontal = props.optBoolean("horizontal", false) || style.optString("flexDirection") == "row"
|
|
477
753
|
setHorizontal(horizontal)
|
|
754
|
+
setNativeNestedScrollingEnabled(true)
|
|
755
|
+
setScrollIndicators(
|
|
756
|
+
props.optBoolean("showsVerticalScrollIndicator", true),
|
|
757
|
+
props.optBoolean("showsHorizontalScrollIndicator", true),
|
|
758
|
+
)
|
|
759
|
+
setScrollEnabled(props.optBoolean("scrollEnabled", true))
|
|
760
|
+
val onScroll = events.optString("onScroll")
|
|
761
|
+
if (onScroll.isNotBlank()) screen.scrollHandlers[this] = onScroll else screen.scrollHandlers.remove(this)
|
|
762
|
+
val onBegin = events.optString("onScrollBeginDrag")
|
|
763
|
+
if (onBegin.isNotBlank()) screen.scrollBeginHandlers[this] = onBegin else screen.scrollBeginHandlers.remove(this)
|
|
764
|
+
val onEnd = events.optString("onScrollEndDrag")
|
|
765
|
+
if (onEnd.isNotBlank()) screen.scrollEndHandlers[this] = onEnd else screen.scrollEndHandlers.remove(this)
|
|
766
|
+
val nativeScroll = this
|
|
767
|
+
setScrollListeners(
|
|
768
|
+
onScroll = { scrollX, scrollY ->
|
|
769
|
+
screen.scrollHandlers[nativeScroll]?.let { current ->
|
|
770
|
+
val contentView = nativeScroll.content
|
|
771
|
+
send(screen, "EVENT", JSONObject().put("handlerName", current).put("nativeEvent", JSONObject()
|
|
772
|
+
.put("contentOffset", JSONObject().put("x", scrollX).put("y", scrollY))
|
|
773
|
+
.put("contentSize", JSONObject().put("width", contentView.width).put("height", contentView.height))
|
|
774
|
+
.put("layoutMeasurement", JSONObject().put("width", nativeScroll.width).put("height", nativeScroll.height))))
|
|
775
|
+
}
|
|
776
|
+
},
|
|
777
|
+
onBeginDrag = {
|
|
778
|
+
screen.scrollBeginHandlers[nativeScroll]?.let { current ->
|
|
779
|
+
send(screen, "EVENT", JSONObject().put("handlerName", current).put("nativeEvent", JSONObject()))
|
|
780
|
+
}
|
|
781
|
+
},
|
|
782
|
+
onEndDrag = {
|
|
783
|
+
screen.scrollEndHandlers[nativeScroll]?.let { current ->
|
|
784
|
+
send(screen, "EVENT", JSONObject().put("handlerName", current).put("nativeEvent", JSONObject()))
|
|
785
|
+
}
|
|
786
|
+
},
|
|
787
|
+
)
|
|
478
788
|
configureContainer(content, style)
|
|
479
789
|
reconcileChildren(screen, content, children, path, next, style, previousControls)
|
|
480
790
|
}
|
|
481
791
|
"FlatList" -> (previous as? CraftNativeFlatList ?: CraftNativeFlatList(this)).apply {
|
|
482
792
|
reconcileFlatList(screen, this, children, path, protocolId ?: path, props, events)
|
|
483
793
|
}
|
|
484
|
-
else -> (previous as?
|
|
794
|
+
else -> (previous as? CraftNativeFlexLayout ?: CraftNativeFlexLayout(this)).apply {
|
|
485
795
|
configureContainer(this, style)
|
|
486
796
|
reconcileChildren(screen, this, children, path, next, style, previousControls)
|
|
487
797
|
}
|
|
@@ -506,7 +816,7 @@ class MainActivity : AppCompatActivity() {
|
|
|
506
816
|
next[identity] = control
|
|
507
817
|
applyViewStyle(control, style)
|
|
508
818
|
control.layoutParams = LinearLayout.LayoutParams(
|
|
509
|
-
if (style.has("width")) dp(style.optDouble("width")) else ViewGroup.LayoutParams.
|
|
819
|
+
if (style.has("width")) dp(style.optDouble("width")) else ViewGroup.LayoutParams.WRAP_CONTENT,
|
|
510
820
|
if (style.has("height")) dp(style.optDouble("height")) else ViewGroup.LayoutParams.WRAP_CONTENT
|
|
511
821
|
).apply {
|
|
512
822
|
val margin = style.optDouble("margin", 0.0)
|
|
@@ -529,30 +839,13 @@ class MainActivity : AppCompatActivity() {
|
|
|
529
839
|
}
|
|
530
840
|
}
|
|
531
841
|
|
|
532
|
-
private fun configureContainer(layout:
|
|
533
|
-
|
|
534
|
-
layout.orientation = if (direction == "row" || direction == "row-reverse")
|
|
535
|
-
LinearLayout.HORIZONTAL else LinearLayout.VERTICAL
|
|
536
|
-
val cross = when (style.optString("alignItems")) {
|
|
537
|
-
"center" -> if (layout.orientation == LinearLayout.HORIZONTAL) Gravity.CENTER_VERTICAL else Gravity.CENTER_HORIZONTAL
|
|
538
|
-
"flex-end" -> if (layout.orientation == LinearLayout.HORIZONTAL) Gravity.BOTTOM else Gravity.END
|
|
539
|
-
else -> if (layout.orientation == LinearLayout.HORIZONTAL) Gravity.TOP else Gravity.START
|
|
540
|
-
}
|
|
541
|
-
layout.gravity = cross
|
|
542
|
-
val padding = style.optDouble("padding", 0.0)
|
|
543
|
-
val horizontal = style.optDouble("paddingHorizontal", padding)
|
|
544
|
-
val vertical = style.optDouble("paddingVertical", padding)
|
|
545
|
-
layout.setPadding(
|
|
546
|
-
dp(style.optDouble("paddingLeft", horizontal)),
|
|
547
|
-
dp(style.optDouble("paddingTop", vertical)),
|
|
548
|
-
dp(style.optDouble("paddingRight", horizontal)),
|
|
549
|
-
dp(style.optDouble("paddingBottom", vertical))
|
|
550
|
-
)
|
|
842
|
+
private fun configureContainer(layout: CraftNativeFlexLayout, style: JSONObject) {
|
|
843
|
+
layout.configure(style, resources.displayMetrics.density)
|
|
551
844
|
}
|
|
552
845
|
|
|
553
846
|
private fun reconcileChildren(
|
|
554
847
|
screen: Screen,
|
|
555
|
-
layout:
|
|
848
|
+
layout: CraftNativeFlexLayout,
|
|
556
849
|
children: JSONArray,
|
|
557
850
|
path: String,
|
|
558
851
|
next: MutableMap<String, View>,
|
|
@@ -571,6 +864,9 @@ class MainActivity : AppCompatActivity() {
|
|
|
571
864
|
else -> null
|
|
572
865
|
}
|
|
573
866
|
if (childView != null) desired.add(childView)
|
|
867
|
+
if (childView != null && child is JSONObject) {
|
|
868
|
+
layout.setChildStyle(childView, child.optJSONObject("style") ?: JSONObject(), resources.displayMetrics.density)
|
|
869
|
+
}
|
|
574
870
|
}
|
|
575
871
|
if (style.optString("flexDirection").endsWith("-reverse")) desired.reverse()
|
|
576
872
|
val desiredSet = desired.toSet()
|
|
@@ -580,16 +876,12 @@ class MainActivity : AppCompatActivity() {
|
|
|
580
876
|
(childView.parent as? ViewGroup)?.removeView(childView)
|
|
581
877
|
layout.addView(childView, index)
|
|
582
878
|
}
|
|
583
|
-
val gapName = if (layout.orientation == LinearLayout.HORIZONTAL) "columnGap" else "rowGap"
|
|
584
|
-
val gap = dp(style.optDouble(gapName, style.optDouble("gap", 0.0)))
|
|
585
879
|
val params = childView.layoutParams as? LinearLayout.LayoutParams
|
|
586
880
|
?: LinearLayout.LayoutParams(childView.layoutParams?.width ?: -1, childView.layoutParams?.height ?: -2)
|
|
587
|
-
params.leftMargin = if (layout.orientation == LinearLayout.HORIZONTAL && index > 0) gap else 0
|
|
588
|
-
params.topMargin = if (layout.orientation == LinearLayout.VERTICAL && index > 0) gap else 0
|
|
589
881
|
childView.layoutParams = params
|
|
590
882
|
}
|
|
591
883
|
while (layout.childCount > desired.size) layout.removeViewAt(layout.childCount - 1)
|
|
592
|
-
|
|
884
|
+
layout.requestLayout()
|
|
593
885
|
}
|
|
594
886
|
|
|
595
887
|
private fun reconcileFlatList(
|
|
@@ -644,28 +936,7 @@ class MainActivity : AppCompatActivity() {
|
|
|
644
936
|
}
|
|
645
937
|
}
|
|
646
938
|
|
|
647
|
-
private fun addJustificationSpacers(layout:
|
|
648
|
-
if (layout.childCount == 0 || value == "flex-start" || value.isBlank()) return
|
|
649
|
-
fun spacer(weight: Float): CraftNativeFlexSpacer = CraftNativeFlexSpacer(this).apply {
|
|
650
|
-
layoutParams = if (layout.orientation == LinearLayout.HORIZONTAL)
|
|
651
|
-
LinearLayout.LayoutParams(0, ViewGroup.LayoutParams.MATCH_PARENT, weight)
|
|
652
|
-
else LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 0, weight)
|
|
653
|
-
}
|
|
654
|
-
when (value) {
|
|
655
|
-
"flex-end" -> layout.addView(spacer(1f), 0)
|
|
656
|
-
"center" -> {
|
|
657
|
-
layout.addView(spacer(1f), 0)
|
|
658
|
-
layout.addView(spacer(1f))
|
|
659
|
-
}
|
|
660
|
-
"space-between" -> for (index in layout.childCount - 1 downTo 1) layout.addView(spacer(1f), index)
|
|
661
|
-
"space-around", "space-evenly" -> {
|
|
662
|
-
val edgeWeight = if (value == "space-around") 0.5f else 1f
|
|
663
|
-
for (index in layout.childCount - 1 downTo 1) layout.addView(spacer(1f), index)
|
|
664
|
-
layout.addView(spacer(edgeWeight), 0)
|
|
665
|
-
layout.addView(spacer(edgeWeight))
|
|
666
|
-
}
|
|
667
|
-
}
|
|
668
|
-
}
|
|
939
|
+
private fun addJustificationSpacers(layout: CraftNativeFlexLayout, value: String) { }
|
|
669
940
|
|
|
670
941
|
private fun applyViewStyle(view: View, style: JSONObject) {
|
|
671
942
|
view.alpha = style.optDouble("opacity", 1.0).toFloat()
|
|
@@ -791,8 +1062,12 @@ class MainActivity : AppCompatActivity() {
|
|
|
791
1062
|
private fun applyAccessibility(view: View, props: JSONObject, type: String, fallbackLabel: String) {
|
|
792
1063
|
view.tag = fallbackLabel
|
|
793
1064
|
view.contentDescription = props.optString("accessibilityLabel").ifBlank { fallbackLabel }
|
|
1065
|
+
props.opt("accessibilityValue")?.takeUnless { it == JSONObject.NULL }?.let { view.contentDescription = "${view.contentDescription}, $it" }
|
|
794
1066
|
props.optString("accessibilityHint").takeIf { it.isNotBlank() }?.let { view.tooltipText = it }
|
|
795
1067
|
val role = props.optString("accessibilityRole").ifBlank { type.lowercase() }
|
|
1068
|
+
val state = props.optJSONObject("accessibilityState")
|
|
1069
|
+
val disabled = state?.optBoolean("disabled", false) == true
|
|
1070
|
+
if (state != null && state.has("disabled")) view.isEnabled = !disabled
|
|
796
1071
|
ViewCompat.setAccessibilityDelegate(view, object : AccessibilityDelegateCompat() {
|
|
797
1072
|
override fun onInitializeAccessibilityNodeInfo(host: View, info: AccessibilityNodeInfoCompat) {
|
|
798
1073
|
super.onInitializeAccessibilityNodeInfo(host, info)
|
|
@@ -805,14 +1080,51 @@ class MainActivity : AppCompatActivity() {
|
|
|
805
1080
|
}
|
|
806
1081
|
info.isHeading = role == "header"
|
|
807
1082
|
info.roleDescription = role.takeIf { it !in setOf("view", "text", "textinput", "scrollview") }
|
|
1083
|
+
info.isEnabled = !disabled
|
|
1084
|
+
info.isSelected = state?.optBoolean("selected", false) == true
|
|
1085
|
+
info.isChecked = state?.optBoolean("checked", false) == true
|
|
808
1086
|
}
|
|
809
1087
|
})
|
|
810
|
-
ViewCompat.setImportantForAccessibility(
|
|
1088
|
+
ViewCompat.setImportantForAccessibility(
|
|
1089
|
+
view,
|
|
1090
|
+
if (role == "none") ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_NO else ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_YES,
|
|
1091
|
+
)
|
|
811
1092
|
}
|
|
812
1093
|
|
|
813
1094
|
private fun dp(value: Double) = (value * resources.displayMetrics.density).toInt()
|
|
814
1095
|
private fun color(raw: String, fallback: Int) = runCatching { Color.parseColor(raw) }.getOrDefault(fallback)
|
|
815
1096
|
|
|
1097
|
+
private fun inputTypeFor(props: JSONObject): Int {
|
|
1098
|
+
val base = if (props.optBoolean("multiline", false)) InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_FLAG_MULTI_LINE
|
|
1099
|
+
else InputType.TYPE_CLASS_TEXT
|
|
1100
|
+
val keyboard = when (props.optString("keyboardType")) {
|
|
1101
|
+
"email-address" -> InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS
|
|
1102
|
+
"url" -> InputType.TYPE_TEXT_VARIATION_URI
|
|
1103
|
+
"numeric" -> InputType.TYPE_CLASS_NUMBER
|
|
1104
|
+
"decimal-pad" -> InputType.TYPE_CLASS_NUMBER or InputType.TYPE_NUMBER_FLAG_DECIMAL
|
|
1105
|
+
"phone-pad" -> InputType.TYPE_CLASS_PHONE
|
|
1106
|
+
else -> 0
|
|
1107
|
+
}
|
|
1108
|
+
val capitalization = when (props.optString("autoCapitalize")) {
|
|
1109
|
+
"characters" -> InputType.TYPE_TEXT_FLAG_CAP_CHARACTERS
|
|
1110
|
+
"words" -> InputType.TYPE_TEXT_FLAG_CAP_WORDS
|
|
1111
|
+
"sentences" -> InputType.TYPE_TEXT_FLAG_CAP_SENTENCES
|
|
1112
|
+
else -> 0
|
|
1113
|
+
}
|
|
1114
|
+
val correction = if (props.optBoolean("autoCorrect", true)) InputType.TYPE_TEXT_FLAG_AUTO_CORRECT else 0
|
|
1115
|
+
val secure = if (props.optBoolean("secureTextEntry", false)) InputType.TYPE_TEXT_VARIATION_PASSWORD else 0
|
|
1116
|
+
return (if (keyboard == 0) base else keyboard) or capitalization or correction or secure
|
|
1117
|
+
}
|
|
1118
|
+
|
|
1119
|
+
private fun imeOptionsFor(props: JSONObject): Int = when (props.optString("returnKeyType")) {
|
|
1120
|
+
"done" -> EditorInfo.IME_ACTION_DONE
|
|
1121
|
+
"go" -> EditorInfo.IME_ACTION_GO
|
|
1122
|
+
"next" -> EditorInfo.IME_ACTION_NEXT
|
|
1123
|
+
"search" -> EditorInfo.IME_ACTION_SEARCH
|
|
1124
|
+
"send" -> EditorInfo.IME_ACTION_SEND
|
|
1125
|
+
else -> EditorInfo.IME_ACTION_NONE
|
|
1126
|
+
}
|
|
1127
|
+
|
|
816
1128
|
private fun answerAPI(screen: Screen, message: JSONObject, payload: JSONObject) {
|
|
817
1129
|
val id = message.optString("id")
|
|
818
1130
|
if (id.isBlank()) return
|
|
@@ -974,6 +1286,12 @@ class MainActivity : AppCompatActivity() {
|
|
|
974
1286
|
}
|
|
975
1287
|
releaseImages(released)
|
|
976
1288
|
screen.handlers.keys.removeAll(released)
|
|
1289
|
+
screen.scrollHandlers.keys.removeAll(released)
|
|
1290
|
+
screen.scrollBeginHandlers.keys.removeAll(released)
|
|
1291
|
+
screen.scrollEndHandlers.keys.removeAll(released)
|
|
1292
|
+
screen.focusHandlers.keys.removeAll(released)
|
|
1293
|
+
screen.blurHandlers.keys.removeAll(released)
|
|
1294
|
+
screen.submitHandlers.keys.removeAll(released)
|
|
977
1295
|
}
|
|
978
1296
|
|
|
979
1297
|
private fun closeScreen(screen: Screen) {
|
|
@@ -984,6 +1302,12 @@ class MainActivity : AppCompatActivity() {
|
|
|
984
1302
|
releaseViewState(screen, retainedViews(screen).toList())
|
|
985
1303
|
screen.controls.clear()
|
|
986
1304
|
screen.handlers.clear()
|
|
1305
|
+
screen.scrollHandlers.clear()
|
|
1306
|
+
screen.scrollBeginHandlers.clear()
|
|
1307
|
+
screen.scrollEndHandlers.clear()
|
|
1308
|
+
screen.focusHandlers.clear()
|
|
1309
|
+
screen.blurHandlers.clear()
|
|
1310
|
+
screen.submitHandlers.clear()
|
|
987
1311
|
screen.flatListRows.clear()
|
|
988
1312
|
screen.flatListOwners.clear()
|
|
989
1313
|
screen.flatListOwnerByView.clear()
|
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.122";
|
|
3307
3307
|
|
|
3308
3308
|
// bin/cli.ts
|
|
3309
3309
|
var spawnedFrom = process6.env[CRAFT_CLI_SPAWN_MARKER];
|
|
@@ -285,11 +285,20 @@ export interface BaseProps {
|
|
|
285
285
|
accessibilityLabel?: string;
|
|
286
286
|
/** Accessibility hint */
|
|
287
287
|
accessibilityHint?: string;
|
|
288
|
+
/** Accessibility value announced by the native host */
|
|
289
|
+
accessibilityValue?: string | number;
|
|
290
|
+
/** Accessibility state exposed to assistive technologies */
|
|
291
|
+
accessibilityState?: AccessibilityState;
|
|
288
292
|
/** Accessibility role */
|
|
289
293
|
accessibilityRole?: 'none' | 'button' | 'link' | 'image' | 'text' | 'header' | 'search' | 'menu';
|
|
290
294
|
/** Children elements */
|
|
291
295
|
children?: any;
|
|
292
296
|
}
|
|
297
|
+
export interface AccessibilityState {
|
|
298
|
+
disabled?: boolean;
|
|
299
|
+
selected?: boolean;
|
|
300
|
+
checked?: boolean;
|
|
301
|
+
}
|
|
293
302
|
/**
|
|
294
303
|
* View component props.
|
|
295
304
|
*/
|
package/dist/index.cjs
CHANGED
package/dist/index.js
CHANGED
package/dist/ios/src/index.d.ts
CHANGED
|
@@ -85,6 +85,13 @@ export interface CraftConfig {
|
|
|
85
85
|
associatedDomains?: string[];
|
|
86
86
|
appGroups?: string[];
|
|
87
87
|
appIconPath?: string;
|
|
88
|
+
/**
|
|
89
|
+
* Whether the app uses encryption beyond the exempt kinds (HTTPS, the
|
|
90
|
+
* system's own). Written as `ITSAppUsesNonExemptEncryption`, so App Store
|
|
91
|
+
* Connect does not ask the export-compliance question on every build.
|
|
92
|
+
* Default false: a WebView app that talks HTTPS is exempt.
|
|
93
|
+
*/
|
|
94
|
+
usesNonExemptEncryption?: boolean;
|
|
88
95
|
privacy?: CraftPrivacyManifest;
|
|
89
96
|
orientations?: Array<'portrait' | 'landscape-left' | 'landscape-right' | 'portrait-upside-down'>;
|
|
90
97
|
deviceFamilies?: Array<'iphone' | 'ipad'>;
|
|
@@ -193,6 +200,11 @@ export declare function renderWatchEntitlements(config: CraftConfig): string;
|
|
|
193
200
|
*/
|
|
194
201
|
export declare function renderRuntimeSettings(): string;
|
|
195
202
|
export declare function renderPrivacyManifest(config: CraftConfig): string;
|
|
203
|
+
/**
|
|
204
|
+
* Whether a PNG carries an alpha channel, from its header: colour type 4
|
|
205
|
+
* (grey + alpha) or 6 (RGB + alpha), or a tRNS chunk before the image data.
|
|
206
|
+
*/
|
|
207
|
+
export declare function pngHasAlpha(bytes: Uint8Array): boolean;
|
|
196
208
|
/** Replace the bundled web application atomically so removed assets cannot linger. */
|
|
197
209
|
export declare function syncWebAssets(source: string, output: string): void;
|
|
198
210
|
/**
|