@craft-native/ios 0.0.209 → 0.0.210
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/README.md +7 -1
- package/package.json +1 -1
- package/templates/CraftApp.swift +16 -11
- package/templates/CraftNativeScreen.swift +20 -4
package/README.md
CHANGED
|
@@ -137,7 +137,13 @@ and linear gradients are applied by UIKit with the same style keys as Android;
|
|
|
137
137
|
`elevation` remains Android-specific. Gradient layers follow the native view's
|
|
138
138
|
bounds and corners through layout changes.
|
|
139
139
|
|
|
140
|
-
The opt-in `native` renderer hosts compiled `.stx` screens in JavaScriptCore and renders their `View`, `SafeAreaView`, `Text`, `Button`, `Link`, `Checkbox`, `TextInput`, `Picker`, `DatePicker`, `Switch`, `Slider`, `ActivityIndicator`, `Icon`, `Image`, `ImageBackground`, `Video`, `Map`, `Svg`, `G`, `SvgText`, `ScrollView`, `SectionList`, and recycling `FlatList` nodes as UIKit controls. `Map` uses MapKit for trail polylines and marker taps; its overlays carry flat latitude/longitude `geometry` arrays, `strokeWidth` is expressed in logical points, and the camera supports controlled `region`, one-time `initialRegion`, and `fitToBounds`. `Svg` is a DOM-free drawing surface; grouped `G` and `SvgText` nodes are painted inside it. `Icon` uses SF Symbols with the shared symbol/name contract and accessibility metadata. `SectionList` uses the same native scroll host for ordinary `.stx` slot children; grouped section data should use `FlatList` until the section-data protocol is available. `FlatList` uses `UICollectionView` with stable keyed rows, incremental diffs, grids, horizontal and inverted layouts, list chrome, and end-reached pagination. Native views support `onLayout` callbacks with point-based frame measurements that refresh after safe-area or orientation changes. Flex items support `flex`, `flexGrow`, `flexShrink`, and numeric `flexBasis` for main-axis space distribution. Native screens also expose the versioned capability bridge: JSON storage and SQLite survive relaunch, lifecycle and deep-link events are scoped to the top route, and local notifications use `UNUserNotificationCenter`. Enable each capability in `craft.config.json`; disabled calls preserve Craft's `CAPABILITY_DISABLED` rejection code and every request has deterministic timeout/cancellation behavior. The default remains `web`; existing iOS apps are unchanged. `Checkbox` supports controlled `checked`/`value`, indeterminate state, and boolean `onValueChange`; `Picker` renders `<select>`/`<option>` values with `onValueChange` or `onChange`; `DatePicker` emits local date/time values plus ISO and millisecond
|
|
140
|
+
The opt-in `native` renderer hosts compiled `.stx` screens in JavaScriptCore and renders their `View`, `SafeAreaView`, `KeyboardAvoidingView`, `StatusBar`, `RefreshControl`, `Text`, `Button`, `Link`, `Checkbox`, `TextInput`, `Picker`, `DatePicker`, `Switch`, `Slider`, `ActivityIndicator`, `Icon`, `Image`, `ImageBackground`, `Video`, `Map`, `Svg`, `G`, `SvgText`, `ScrollView`, `SectionList`, and recycling `FlatList` nodes as UIKit controls. `StatusBar` and `RefreshControl` are screen/scroll utility nodes rather than visible children. `Map` uses MapKit for trail polylines and marker taps; its overlays carry flat latitude/longitude `geometry` arrays, `strokeWidth` is expressed in logical points, and the camera supports controlled `region`, one-time `initialRegion`, and `fitToBounds`. `Svg` is a DOM-free drawing surface; grouped `G` and `SvgText` nodes are painted inside it. `Icon` uses SF Symbols with the shared symbol/name contract and accessibility metadata. `SectionList` uses the same native scroll host for ordinary `.stx` slot children; grouped section data should use `FlatList` until the section-data protocol is available. `FlatList` uses `UICollectionView` with stable keyed rows, incremental diffs, grids, horizontal and inverted layouts, list chrome, and end-reached pagination. Native views support `onLayout` callbacks with point-based frame measurements that refresh after safe-area or orientation changes. Flex items support `flex`, `flexGrow`, `flexShrink`, and numeric `flexBasis` for main-axis space distribution. Native screens also expose the versioned capability bridge: JSON storage and SQLite survive relaunch, lifecycle and deep-link events are scoped to the top route, and local notifications use `UNUserNotificationCenter`. Enable each capability in `craft.config.json`; disabled calls preserve Craft's `CAPABILITY_DISABLED` rejection code and every request has deterministic timeout/cancellation behavior. The default remains `web`; existing iOS apps are unchanged. `Checkbox` supports controlled `checked`/`value`, indeterminate state, and boolean `onValueChange`; `Picker` renders `<select>`/`<option>` values with `onValueChange` or `onChange`; `DatePicker` emits local date/time values plus ISO and millisecond `timestamp` fields; `Switch` supports state-aware `trackColor`, `thumbColor`, and iOS `ios_backgroundColor`; `Slider` supports track/thumb tint props and `step` snapping; `Image` reports load lifecycle events and structured failures; `Video` uses an `AVPlayerViewController` attached to the native screen for URI playback, native controls, and route-safe teardown. `craft.navigation.setOptions` also accepts a `tabAccessory` destination beside the tab pill; it uses an app-local `path`, `title`, `symbol`, optional `badge`, and `accessibilityLabel`, and `null` clears it. The portable component, capability, list, style, image-source, event, accessibility, and reconciliation behavior is specified in the [Native STX guide](../../docs/guides/native-stx.md).
|
|
141
|
+
|
|
142
|
+
JavaScriptCore has no DOM `window`; Craft aliases `globalThis.window` to
|
|
143
|
+
`globalThis` before installing the native bridge so browser-safe SDK calls such
|
|
144
|
+
as `window.craft.dialog.actionSheet()` reach the native capability host. This
|
|
145
|
+
does not add `document` or DOM rendering; routes that do not opt into `native`
|
|
146
|
+
continue using the WebView renderer.
|
|
141
147
|
|
|
142
148
|
```bash
|
|
143
149
|
craft ios init NativeDemo --renderer native --output ./ios-native
|
package/package.json
CHANGED
package/templates/CraftApp.swift
CHANGED
|
@@ -928,10 +928,12 @@ struct CraftTabBarView: View {
|
|
|
928
928
|
bar
|
|
929
929
|
if let accessory = chrome.tabAccessory {
|
|
930
930
|
Button { CraftHybrid.shared.open(accessory.id) } label: {
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
931
|
+
surface {
|
|
932
|
+
Image(systemName: accessory.symbol)
|
|
933
|
+
.font(.system(size: iconSize))
|
|
934
|
+
.foregroundStyle(Color.primary)
|
|
935
|
+
.frame(width: 62, height: 62)
|
|
936
|
+
}
|
|
935
937
|
.overlay(alignment: .topTrailing) {
|
|
936
938
|
if let badge = accessory.badge {
|
|
937
939
|
Text(badge)
|
|
@@ -967,24 +969,27 @@ struct CraftTabBarView: View {
|
|
|
967
969
|
chrome.layoutChanged(max(0, screen - proxy.frame(in: .global).minY))
|
|
968
970
|
}
|
|
969
971
|
|
|
970
|
-
|
|
972
|
+
private var bar: some View {
|
|
973
|
+
surface { items.padding(4) }
|
|
974
|
+
}
|
|
975
|
+
|
|
976
|
+
/// Both the tab capsule and its circular accessory use the same material,
|
|
977
|
+
/// outline and shadow. A square capsule forms the accessory's circle.
|
|
978
|
+
@ViewBuilder private func surface<Content: View>(@ViewBuilder content: () -> Content) -> some View {
|
|
971
979
|
#if compiler(>=6.2)
|
|
972
980
|
if #available(iOS 26.0, *) {
|
|
973
981
|
GlassEffectContainer {
|
|
974
|
-
|
|
975
|
-
.padding(4)
|
|
982
|
+
content()
|
|
976
983
|
.glassEffect(.regular.interactive(), in: .capsule)
|
|
977
984
|
}
|
|
978
985
|
} else {
|
|
979
|
-
|
|
980
|
-
.padding(4)
|
|
986
|
+
content()
|
|
981
987
|
.background(.ultraThinMaterial, in: Capsule())
|
|
982
988
|
.overlay(Capsule().strokeBorder(Color.white.opacity(0.35), lineWidth: 0.5))
|
|
983
989
|
.shadow(color: .black.opacity(0.12), radius: 18, y: 6)
|
|
984
990
|
}
|
|
985
991
|
#else
|
|
986
|
-
|
|
987
|
-
.padding(4)
|
|
992
|
+
content()
|
|
988
993
|
.background(.ultraThinMaterial, in: Capsule())
|
|
989
994
|
.overlay(Capsule().strokeBorder(Color.white.opacity(0.35), lineWidth: 0.5))
|
|
990
995
|
.shadow(color: .black.opacity(0.12), radius: 18, y: 6)
|
|
@@ -4151,6 +4151,10 @@ final class CraftNativeScreenController: UIViewController, UIScrollViewDelegate,
|
|
|
4151
4151
|
let appVersion = Self.jsonString(info?["CFBundleShortVersionString"] as? String ?? "")
|
|
4152
4152
|
let appBuild = Self.jsonString(info?["CFBundleVersion"] as? String ?? "")
|
|
4153
4153
|
jsContext.evaluateScript("""
|
|
4154
|
+
// JavaScriptCore has no DOM window, but browser-safe SDK bridges
|
|
4155
|
+
// address the host through `window.craft`. Keep native screens
|
|
4156
|
+
// compatible without pretending a document exists.
|
|
4157
|
+
if (typeof globalThis.window === "undefined") { globalThis.window = globalThis; }
|
|
4154
4158
|
globalThis.__stxNativeCallback = null;
|
|
4155
4159
|
globalThis.__stxNativeBridge = {
|
|
4156
4160
|
platform: "ios",
|
|
@@ -4279,7 +4283,7 @@ final class CraftNativeScreenController: UIViewController, UIScrollViewDelegate,
|
|
|
4279
4283
|
/// `craft.secureStorage.set` wrote.
|
|
4280
4284
|
/// - `craft.navigation.setOptions({ title, largeTitle, hidden, backTitle,
|
|
4281
4285
|
/// tabBarHidden, swipeBack, contentSwipeBack, tabSwipeEnabled,
|
|
4282
|
-
/// backgroundColor, navigationBarSolid, rightButtons })`;
|
|
4286
|
+
/// backgroundColor, navigationBarSolid, rightButtons, tabAccessory })`;
|
|
4283
4287
|
/// a right button's tap is the event `navButton`.
|
|
4284
4288
|
///
|
|
4285
4289
|
/// A runtime that assigns `craft.storage` (or the others) afterwards keeps
|
|
@@ -5731,7 +5735,6 @@ final class CraftNativeScreenController: UIViewController, UIScrollViewDelegate,
|
|
|
5731
5735
|
? nil : number(props["activeOpacity"]) ?? 0.5
|
|
5732
5736
|
pressable.pressBaseOpacity = number(style["opacity"]) ?? 1
|
|
5733
5737
|
pressable.pressUnderlayColor = type == "TouchableHighlight" && !disabled ? color(props["underlayColor"]) : nil
|
|
5734
|
-
pressable.pressBaseBackgroundColor = pressable.backgroundColor
|
|
5735
5738
|
case "Switch":
|
|
5736
5739
|
let toggle = result as! UISwitch
|
|
5737
5740
|
toggle.isOn = props["value"] as? Bool ?? props["checked"] as? Bool ?? false
|
|
@@ -6689,6 +6692,10 @@ final class CraftNativeScreenController: UIViewController, UIScrollViewDelegate,
|
|
|
6689
6692
|
updateDimension(width, constraint: &node.widthConstraint, anchor: view.widthAnchor)
|
|
6690
6693
|
updateDimension(height, constraint: &node.heightConstraint, anchor: view.heightAnchor)
|
|
6691
6694
|
view.backgroundColor = color(style["backgroundColor"]) ?? .clear
|
|
6695
|
+
// Every flow container can receive press feedback, including ordinary
|
|
6696
|
+
// Views. Capture its current authored fill before feedback cleanup or
|
|
6697
|
+
// release restores it; otherwise avatars and button wrappers go clear.
|
|
6698
|
+
(view as? CraftNativeFlowView)?.pressBaseBackgroundColor = view.backgroundColor
|
|
6692
6699
|
// A field the style dresses itself (`border-0`, a border or a
|
|
6693
6700
|
// background, as a search field inside its own pill is) draws no
|
|
6694
6701
|
// system box of its own; a bare one keeps the rounded rectangle.
|
|
@@ -6940,7 +6947,7 @@ final class CraftNativeScreenController: UIViewController, UIScrollViewDelegate,
|
|
|
6940
6947
|
private func textFont(_ style: [String: Any], default fallback: UIFont) -> UIFont {
|
|
6941
6948
|
let size = number(style["fontSize"]) ?? fallback.pointSize
|
|
6942
6949
|
let weight: UIFont.Weight
|
|
6943
|
-
switch style["fontWeight"]
|
|
6950
|
+
switch craftNativeFontWeight(style["fontWeight"]) {
|
|
6944
6951
|
case "100": weight = .ultraLight
|
|
6945
6952
|
case "200": weight = .thin
|
|
6946
6953
|
case "300": weight = .light
|
|
@@ -6962,7 +6969,7 @@ final class CraftNativeScreenController: UIViewController, UIScrollViewDelegate,
|
|
|
6962
6969
|
?? .systemFont(ofSize: scaledSize, weight: weight)
|
|
6963
6970
|
if style["fontStyle"] as? String == "italic",
|
|
6964
6971
|
let descriptor = font.fontDescriptor.withSymbolicTraits(.traitItalic) {
|
|
6965
|
-
font = UIFont(descriptor: descriptor, size:
|
|
6972
|
+
font = UIFont(descriptor: descriptor, size: scaledSize)
|
|
6966
6973
|
}
|
|
6967
6974
|
return font
|
|
6968
6975
|
}
|
|
@@ -7162,7 +7169,16 @@ final class CraftNativeScreenController: UIViewController, UIScrollViewDelegate,
|
|
|
7162
7169
|
|| controlledVideo
|
|
7163
7170
|
view.isAccessibilityElement = !container && role != "none" && (view.accessibilityLabel != nil || role != nil || ["Text", "Button", "Link", "Checkbox", "Image", "ImageBackground", "Video", "TextInput", "Picker", "DatePicker", "Switch", "Slider"].contains(type))
|
|
7164
7171
|
view.accessibilityContainerType = container && (view.accessibilityLabel != nil || controlledVideo) ? .semanticGroup : .none
|
|
7172
|
+
// Keep the interaction trait native controls expose. Rebuilding this
|
|
7173
|
+
// set from the STX role alone otherwise turns a Slider, Picker,
|
|
7174
|
+
// DatePicker, or Switch into a static VoiceOver item whenever the
|
|
7175
|
+
// screen omits redundant accessibility metadata.
|
|
7165
7176
|
var traits: UIAccessibilityTraits = []
|
|
7177
|
+
switch type {
|
|
7178
|
+
case "Slider", "Picker", "DatePicker": traits.insert(.adjustable)
|
|
7179
|
+
case "Switch": traits.insert(.button)
|
|
7180
|
+
default: break
|
|
7181
|
+
}
|
|
7166
7182
|
switch role ?? type.lowercased() {
|
|
7167
7183
|
case "tablist": traits.insert(.tabBar)
|
|
7168
7184
|
case "button", "checkbox": traits.insert(.button)
|