diff --git a/README.md b/README.md
index 7a367f9f..a89ec04c 100644
--- a/README.md
+++ b/README.md
@@ -235,6 +235,7 @@ The following table compares feature availability with the [previous Rive React
| `useRiveFile()` hook | ✅ | Convenient hook to load a Rive file |
| `RiveView` error handling | ✅ | Error handler for failed view operations |
| `source` .riv file loading | ✅ | Conveniently load .riv files from JS source |
+| Accessibility semantics | ⚠️ | Editor-authored semantics → VoiceOver (iOS; Android in progress) |
| Animation selection | ❌ | Animation playback not planned, use state machines |
| Renderer options | ❌ | Single renderer option available (Rive) |
diff --git a/android/src/legacy/java/com/margelo/nitro/rive/HybridRiveView.kt b/android/src/legacy/java/com/margelo/nitro/rive/HybridRiveView.kt
index 318ad099..b3953629 100644
--- a/android/src/legacy/java/com/margelo/nitro/rive/HybridRiveView.kt
+++ b/android/src/legacy/java/com/margelo/nitro/rive/HybridRiveView.kt
@@ -89,6 +89,10 @@ class HybridRiveView(val context: ThemedReactContext) : HybridRiveViewSpec() {
override var alignment: Alignment? = null
override var fit: Fit? = null
override var layoutScaleFactor: Double? = null
+
+ // Accepted for API parity; semantics are only available in the new Rive
+ // runtime, and Android support is pending upstream (iOS-only for now).
+ override var semantics: Semantics? = null
override var dataBind: Variant_HybridViewModelInstanceSpec_DataBindMode_DataBindByName? = null
set(value) {
if (field != value) {
diff --git a/android/src/new/java/com/margelo/nitro/rive/HybridRiveView.kt b/android/src/new/java/com/margelo/nitro/rive/HybridRiveView.kt
index 518c2871..7cc08ce6 100644
--- a/android/src/new/java/com/margelo/nitro/rive/HybridRiveView.kt
+++ b/android/src/new/java/com/margelo/nitro/rive/HybridRiveView.kt
@@ -96,6 +96,10 @@ class HybridRiveView(val context: ThemedReactContext) : HybridRiveViewSpec() {
override var alignment: Alignment? = null
override var fit: Fit? = null
override var layoutScaleFactor: Double? = null
+
+ // Accepted for API parity; semantics support is pending in the upstream
+ // rive-android runtime (iOS-only for now).
+ override var semantics: Semantics? = null
override var dataBind: Variant_HybridViewModelInstanceSpec_DataBindMode_DataBindByName? = null
set(value) {
if (field != value) {
diff --git a/example/assets/rive/tabtest.riv b/example/assets/rive/tabtest.riv
new file mode 100644
index 00000000..59de6a33
Binary files /dev/null and b/example/assets/rive/tabtest.riv differ
diff --git a/example/src/demos/SemanticsExample.tsx b/example/src/demos/SemanticsExample.tsx
new file mode 100644
index 00000000..89b02daf
--- /dev/null
+++ b/example/src/demos/SemanticsExample.tsx
@@ -0,0 +1,122 @@
+import { useState } from 'react';
+import { View, Text, StyleSheet, TouchableOpacity } from 'react-native';
+import { RiveView, useRiveFile, Semantics } from '@rive-app/react-native';
+import type { Metadata } from '../shared/metadata';
+
+/*
+ Semantics — editor-authored accessibility exposed to VoiceOver.
+
+ tabtest.riv (from rive-runtime's semantics test assets) authors a tab bar
+ with roles, labels and selection state. With semantics enabled, each tab
+ becomes an accessibility element: VoiceOver reads them and can activate
+ them. iOS new (default) backend only; see
+ https://rive.app/docs/runtimes/apple/semantics
+*/
+
+const MODES: { label: string; value: Semantics }[] = [
+ { label: 'Off', value: Semantics.Off },
+ { label: 'On', value: Semantics.On },
+ { label: 'Automatic', value: Semantics.Automatic },
+];
+
+export default function SemanticsExample() {
+ const { riveFile, error } = useRiveFile(
+ require('../../assets/rive/tabtest.riv')
+ );
+ const [semantics, setSemantics] = useState(Semantics.On);
+
+ if (error) {
+ return (
+
+ {error.message}
+
+ );
+ }
+
+ return (
+
+
+ With semantics On (or Automatic + VoiceOver running), the tabs below are
+ exposed to VoiceOver as selectable accessibility elements.
+
+
+ {MODES.map(({ label, value }) => (
+ setSemantics(value)}
+ >
+
+ {label}
+
+
+ ))}
+
+ {riveFile && (
+
+ )}
+
+ );
+}
+
+SemanticsExample.metadata = {
+ name: 'Semantics (VoiceOver)',
+ description:
+ 'Editor-authored accessibility semantics exposed to VoiceOver (iOS new backend)',
+ order: 3,
+} satisfies Metadata;
+
+const styles = StyleSheet.create({
+ container: {
+ flex: 1,
+ padding: 16,
+ backgroundColor: '#fff',
+ },
+ subtitle: {
+ fontSize: 14,
+ color: '#666',
+ textAlign: 'center',
+ marginBottom: 12,
+ },
+ modeRow: {
+ flexDirection: 'row',
+ justifyContent: 'center',
+ gap: 8,
+ marginBottom: 16,
+ },
+ modeButton: {
+ paddingVertical: 8,
+ paddingHorizontal: 16,
+ borderRadius: 8,
+ borderWidth: 1,
+ borderColor: '#ccc',
+ },
+ modeOn: {
+ backgroundColor: '#222',
+ borderColor: '#222',
+ },
+ modeLabel: {
+ fontSize: 14,
+ color: '#333',
+ },
+ modeLabelOn: {
+ color: '#fff',
+ },
+ rive: {
+ flex: 1,
+ },
+ errorText: {
+ color: 'red',
+ },
+});
diff --git a/ios/legacy/HybridRiveView.swift b/ios/legacy/HybridRiveView.swift
index ad82ffbe..8ae0a9bc 100644
--- a/ios/legacy/HybridRiveView.swift
+++ b/ios/legacy/HybridRiveView.swift
@@ -106,6 +106,9 @@ class HybridRiveView: HybridRiveViewSpec {
var alignment: Alignment?
var fit: Fit?
var layoutScaleFactor: Double?
+ // Accepted for API parity; semantics are only available in the new Rive
+ // runtime (the experimental backend).
+ var semantics: Semantics?
var onError: (RiveError) -> Void = { _ in }
func awaitViewReady() throws -> Promise {
diff --git a/ios/new/HybridRiveView.swift b/ios/new/HybridRiveView.swift
index 157e4d49..b63496e5 100644
--- a/ios/new/HybridRiveView.swift
+++ b/ios/new/HybridRiveView.swift
@@ -98,6 +98,7 @@ class HybridRiveView: HybridRiveViewSpec {
var alignment: Alignment?
var fit: Fit?
var layoutScaleFactor: Double?
+ var semantics: Semantics?
var onError: (RiveError) -> Void = { _ in }
func awaitViewReady() throws -> Promise {
@@ -211,6 +212,7 @@ class HybridRiveView: HybridRiveViewSpec {
autoPlay: autoPlay ?? DefaultConfiguration.autoPlay,
file: riveFile,
fit: toRiveFit(fit, alignment: alignment, layoutScaleFactor: layoutScaleFactor),
+ semantics: toRiveSemantics(semantics),
bindData: try dataBind.toBindData()
)
@@ -258,6 +260,15 @@ class HybridRiveView: HybridRiveViewSpec {
}
}
+ private func toRiveSemantics(_ semantics: Semantics?) -> RiveRuntime.Semantics {
+ switch semantics {
+ case .off: return .off
+ case .on: return .on
+ case .automatic: return .automatic
+ case nil: return RiveUIView.Constants.Defaults.semantics
+ }
+ }
+
private func toRiveAlignment(_ alignment: Alignment?) -> RiveRuntime.Alignment? {
guard let alignment = alignment else { return nil }
diff --git a/ios/new/RiveReactNativeView.swift b/ios/new/RiveReactNativeView.swift
index 598db1fb..da1fc47b 100644
--- a/ios/new/RiveReactNativeView.swift
+++ b/ios/new/RiveReactNativeView.swift
@@ -15,6 +15,7 @@ struct ViewConfiguration {
let autoPlay: Bool
let file: File
let fit: RiveRuntime.Fit
+ let semantics: RiveRuntime.Semantics
let bindData: BindData
}
@@ -27,6 +28,9 @@ class RiveReactNativeView: UIView {
private var isViewReady = false
private var configTask: Task?
private var isPaused = false
+ private var semantics: RiveRuntime.Semantics = RiveUIView.Constants.Defaults.semantics {
+ didSet { riveUIView?.semantics = semantics }
+ }
var autoPlay: Bool = true
/// Configure failures are reported here (wired to the onError prop).
@@ -52,6 +56,8 @@ class RiveReactNativeView: UIView {
func configure(_ config: ViewConfiguration, dataBindingChanged: Bool = false, reload: Bool = false, initialUpdate: Bool = false) {
dispatchPrecondition(condition: .onQueue(.main))
+ semantics = config.semantics
+
if reload {
cleanup()
}
@@ -208,8 +214,10 @@ class RiveReactNativeView: UIView {
// not found") from the old MTKView after removeFromSuperview.
existing.rive = rive
existing.isPaused = isPaused
+ existing.semantics = semantics
} else {
let uiView = RiveUIView(rive: rive, isPaused: isPaused)
+ uiView.semantics = semantics
uiView.translatesAutoresizingMaskIntoConstraints = false
addSubview(uiView)
NSLayoutConstraint.activate([
diff --git a/nitrogen/generated/android/c++/JHybridRiveViewSpec.cpp b/nitrogen/generated/android/c++/JHybridRiveViewSpec.cpp
index e19696a1..28b00f64 100644
--- a/nitrogen/generated/android/c++/JHybridRiveViewSpec.cpp
+++ b/nitrogen/generated/android/c++/JHybridRiveViewSpec.cpp
@@ -13,6 +13,8 @@ namespace margelo::nitro::rive { class HybridRiveFileSpec; }
namespace margelo::nitro::rive { enum class Alignment; }
// Forward declaration of `Fit` to properly resolve imports.
namespace margelo::nitro::rive { enum class Fit; }
+// Forward declaration of `Semantics` to properly resolve imports.
+namespace margelo::nitro::rive { enum class Semantics; }
// Forward declaration of `HybridViewModelInstanceSpec` to properly resolve imports.
namespace margelo::nitro::rive { class HybridViewModelInstanceSpec; }
// Forward declaration of `DataBindMode` to properly resolve imports.
@@ -37,6 +39,8 @@ namespace margelo::nitro::rive { enum class RiveEventType; }
#include "JAlignment.hpp"
#include "Fit.hpp"
#include "JFit.hpp"
+#include "Semantics.hpp"
+#include "JSemantics.hpp"
#include "HybridViewModelInstanceSpec.hpp"
#include "DataBindMode.hpp"
#include "DataBindByName.hpp"
@@ -156,6 +160,15 @@ namespace margelo::nitro::rive {
static const auto method = _javaPart->javaClassStatic()->getMethod /* layoutScaleFactor */)>("setLayoutScaleFactor");
method(_javaPart, layoutScaleFactor.has_value() ? jni::JDouble::valueOf(layoutScaleFactor.value()) : nullptr);
}
+ std::optional JHybridRiveViewSpec::getSemantics() {
+ static const auto method = _javaPart->javaClassStatic()->getMethod()>("getSemantics");
+ auto __result = method(_javaPart);
+ return __result != nullptr ? std::make_optional(__result->toCpp()) : std::nullopt;
+ }
+ void JHybridRiveViewSpec::setSemantics(std::optional semantics) {
+ static const auto method = _javaPart->javaClassStatic()->getMethod /* semantics */)>("setSemantics");
+ method(_javaPart, semantics.has_value() ? JSemantics::fromCpp(semantics.value()) : nullptr);
+ }
std::optional, DataBindMode, DataBindByName>> JHybridRiveViewSpec::getDataBind() {
static const auto method = _javaPart->javaClassStatic()->getMethod()>("getDataBind");
auto __result = method(_javaPart);
diff --git a/nitrogen/generated/android/c++/JHybridRiveViewSpec.hpp b/nitrogen/generated/android/c++/JHybridRiveViewSpec.hpp
index 817d66a8..89190c98 100644
--- a/nitrogen/generated/android/c++/JHybridRiveViewSpec.hpp
+++ b/nitrogen/generated/android/c++/JHybridRiveViewSpec.hpp
@@ -64,6 +64,8 @@ namespace margelo::nitro::rive {
void setFit(std::optional fit) override;
std::optional getLayoutScaleFactor() override;
void setLayoutScaleFactor(std::optional layoutScaleFactor) override;
+ std::optional getSemantics() override;
+ void setSemantics(std::optional semantics) override;
std::optional, DataBindMode, DataBindByName>> getDataBind() override;
void setDataBind(const std::optional, DataBindMode, DataBindByName>>& dataBind) override;
std::function getOnError() override;
diff --git a/nitrogen/generated/android/c++/JSemantics.hpp b/nitrogen/generated/android/c++/JSemantics.hpp
new file mode 100644
index 00000000..6ea66093
--- /dev/null
+++ b/nitrogen/generated/android/c++/JSemantics.hpp
@@ -0,0 +1,61 @@
+///
+/// JSemantics.hpp
+/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE.
+/// https://github.com/mrousavy/nitro
+/// Copyright © Marc Rousavy @ Margelo
+///
+
+#pragma once
+
+#include
+#include "Semantics.hpp"
+
+namespace margelo::nitro::rive {
+
+ using namespace facebook;
+
+ /**
+ * The C++ JNI bridge between the C++ enum "Semantics" and the the Kotlin enum "Semantics".
+ */
+ struct JSemantics final: public jni::JavaClass {
+ public:
+ static auto constexpr kJavaDescriptor = "Lcom/margelo/nitro/rive/Semantics;";
+
+ public:
+ /**
+ * Convert this Java/Kotlin-based enum to the C++ enum Semantics.
+ */
+ [[maybe_unused]]
+ [[nodiscard]]
+ Semantics toCpp() const {
+ static const auto clazz = javaClassStatic();
+ static const auto fieldOrdinal = clazz->getField("value");
+ int ordinal = this->getFieldValue(fieldOrdinal);
+ return static_cast(ordinal);
+ }
+
+ public:
+ /**
+ * Create a Java/Kotlin-based enum with the given C++ enum's value.
+ */
+ [[maybe_unused]]
+ static jni::alias_ref fromCpp(Semantics value) {
+ static const auto clazz = javaClassStatic();
+ switch (value) {
+ case Semantics::OFF:
+ static const auto fieldOFF = clazz->getStaticField("OFF");
+ return clazz->getStaticFieldValue(fieldOFF);
+ case Semantics::ON:
+ static const auto fieldON = clazz->getStaticField("ON");
+ return clazz->getStaticFieldValue(fieldON);
+ case Semantics::AUTOMATIC:
+ static const auto fieldAUTOMATIC = clazz->getStaticField("AUTOMATIC");
+ return clazz->getStaticFieldValue(fieldAUTOMATIC);
+ default:
+ std::string stringValue = std::to_string(static_cast(value));
+ throw std::invalid_argument("Invalid enum value (" + stringValue + "!");
+ }
+ }
+ };
+
+} // namespace margelo::nitro::rive
diff --git a/nitrogen/generated/android/c++/views/JHybridRiveViewStateUpdater.cpp b/nitrogen/generated/android/c++/views/JHybridRiveViewStateUpdater.cpp
index f2f3cdf9..4bef850c 100644
--- a/nitrogen/generated/android/c++/views/JHybridRiveViewStateUpdater.cpp
+++ b/nitrogen/generated/android/c++/views/JHybridRiveViewStateUpdater.cpp
@@ -65,6 +65,10 @@ void JHybridRiveViewStateUpdater::updateViewProps(jni::alias_ref /*
hybridView->setLayoutScaleFactor(props->layoutScaleFactor.value);
props->layoutScaleFactor.isDirty = false;
}
+ if (props->semantics.isDirty) {
+ hybridView->setSemantics(props->semantics.value);
+ props->semantics.isDirty = false;
+ }
if (props->dataBind.isDirty) {
hybridView->setDataBind(props->dataBind.value);
props->dataBind.isDirty = false;
diff --git a/nitrogen/generated/android/kotlin/com/margelo/nitro/rive/HybridRiveViewSpec.kt b/nitrogen/generated/android/kotlin/com/margelo/nitro/rive/HybridRiveViewSpec.kt
index b211c235..43700d52 100644
--- a/nitrogen/generated/android/kotlin/com/margelo/nitro/rive/HybridRiveViewSpec.kt
+++ b/nitrogen/generated/android/kotlin/com/margelo/nitro/rive/HybridRiveViewSpec.kt
@@ -69,6 +69,12 @@ abstract class HybridRiveViewSpec: HybridView() {
@set:Keep
abstract var layoutScaleFactor: Double?
+ @get:DoNotStrip
+ @get:Keep
+ @set:DoNotStrip
+ @set:Keep
+ abstract var semantics: Semantics?
+
@get:DoNotStrip
@get:Keep
@set:DoNotStrip
diff --git a/nitrogen/generated/android/kotlin/com/margelo/nitro/rive/Semantics.kt b/nitrogen/generated/android/kotlin/com/margelo/nitro/rive/Semantics.kt
new file mode 100644
index 00000000..8a3f22d7
--- /dev/null
+++ b/nitrogen/generated/android/kotlin/com/margelo/nitro/rive/Semantics.kt
@@ -0,0 +1,24 @@
+///
+/// Semantics.kt
+/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE.
+/// https://github.com/mrousavy/nitro
+/// Copyright © Marc Rousavy @ Margelo
+///
+
+package com.margelo.nitro.rive
+
+import androidx.annotation.Keep
+import com.facebook.proguard.annotations.DoNotStrip
+
+/**
+ * Represents the JavaScript enum/union "Semantics".
+ */
+@DoNotStrip
+@Keep
+enum class Semantics(@DoNotStrip @Keep val value: Int) {
+ OFF(0),
+ ON(1),
+ AUTOMATIC(2);
+
+ companion object
+}
diff --git a/nitrogen/generated/ios/RNRive-Swift-Cxx-Bridge.hpp b/nitrogen/generated/ios/RNRive-Swift-Cxx-Bridge.hpp
index 3be060dd..792ec88d 100644
--- a/nitrogen/generated/ios/RNRive-Swift-Cxx-Bridge.hpp
+++ b/nitrogen/generated/ios/RNRive-Swift-Cxx-Bridge.hpp
@@ -78,6 +78,8 @@ namespace margelo::nitro::rive { enum class RiveErrorType; }
namespace margelo::nitro::rive { struct RiveError; }
// Forward declaration of `RiveEventType` to properly resolve imports.
namespace margelo::nitro::rive { enum class RiveEventType; }
+// Forward declaration of `Semantics` to properly resolve imports.
+namespace margelo::nitro::rive { enum class Semantics; }
// Forward declaration of `UnifiedRiveEvent` to properly resolve imports.
namespace margelo::nitro::rive { struct UnifiedRiveEvent; }
// Forward declaration of `ViewModelPropertyInfo` to properly resolve imports.
@@ -167,6 +169,7 @@ namespace RNRive { class HybridViewModelTriggerPropertySpec_cxx; }
#include "RiveError.hpp"
#include "RiveErrorType.hpp"
#include "RiveEventType.hpp"
+#include "Semantics.hpp"
#include "UnifiedRiveEvent.hpp"
#include "ViewModelPropertyInfo.hpp"
#include "ViewModelPropertyType.hpp"
@@ -955,6 +958,21 @@ namespace margelo::nitro::rive::bridge::swift {
return optional.value();
}
+ // pragma MARK: std::optional
+ /**
+ * Specialized version of `std::optional`.
+ */
+ using std__optional_Semantics_ = std::optional;
+ inline std::optional create_std__optional_Semantics_(const Semantics& value) noexcept {
+ return std::optional(value);
+ }
+ inline bool has_value_std__optional_Semantics_(const std::optional& optional) noexcept {
+ return optional.has_value();
+ }
+ inline Semantics get_std__optional_Semantics_(const std::optional& optional) noexcept {
+ return optional.value();
+ }
+
// pragma MARK: std::shared_ptr
/**
* Specialized version of `std::shared_ptr`.
diff --git a/nitrogen/generated/ios/RNRive-Swift-Cxx-Umbrella.hpp b/nitrogen/generated/ios/RNRive-Swift-Cxx-Umbrella.hpp
index cad39b99..02505b5e 100644
--- a/nitrogen/generated/ios/RNRive-Swift-Cxx-Umbrella.hpp
+++ b/nitrogen/generated/ios/RNRive-Swift-Cxx-Umbrella.hpp
@@ -80,6 +80,8 @@ namespace margelo::nitro::rive { enum class RiveErrorType; }
namespace margelo::nitro::rive { struct RiveError; }
// Forward declaration of `RiveEventType` to properly resolve imports.
namespace margelo::nitro::rive { enum class RiveEventType; }
+// Forward declaration of `Semantics` to properly resolve imports.
+namespace margelo::nitro::rive { enum class Semantics; }
// Forward declaration of `UnifiedRiveEvent` to properly resolve imports.
namespace margelo::nitro::rive { struct UnifiedRiveEvent; }
// Forward declaration of `ViewModelPropertyInfo` to properly resolve imports.
@@ -124,6 +126,7 @@ namespace margelo::nitro::rive { enum class ViewModelPropertyType; }
#include "RiveError.hpp"
#include "RiveErrorType.hpp"
#include "RiveEventType.hpp"
+#include "Semantics.hpp"
#include "UnifiedRiveEvent.hpp"
#include "ViewModelPropertyInfo.hpp"
#include "ViewModelPropertyType.hpp"
diff --git a/nitrogen/generated/ios/c++/HybridRiveViewSpecSwift.hpp b/nitrogen/generated/ios/c++/HybridRiveViewSpecSwift.hpp
index b4f184a0..e2119b22 100644
--- a/nitrogen/generated/ios/c++/HybridRiveViewSpecSwift.hpp
+++ b/nitrogen/generated/ios/c++/HybridRiveViewSpecSwift.hpp
@@ -18,6 +18,8 @@ namespace margelo::nitro::rive { class HybridRiveFileSpec; }
namespace margelo::nitro::rive { enum class Alignment; }
// Forward declaration of `Fit` to properly resolve imports.
namespace margelo::nitro::rive { enum class Fit; }
+// Forward declaration of `Semantics` to properly resolve imports.
+namespace margelo::nitro::rive { enum class Semantics; }
// Forward declaration of `HybridViewModelInstanceSpec` to properly resolve imports.
namespace margelo::nitro::rive { class HybridViewModelInstanceSpec; }
// Forward declaration of `DataBindMode` to properly resolve imports.
@@ -39,6 +41,7 @@ namespace margelo::nitro::rive { enum class RiveEventType; }
#include "HybridRiveFileSpec.hpp"
#include "Alignment.hpp"
#include "Fit.hpp"
+#include "Semantics.hpp"
#include "HybridViewModelInstanceSpec.hpp"
#include "DataBindMode.hpp"
#include "DataBindByName.hpp"
@@ -146,6 +149,13 @@ namespace margelo::nitro::rive {
inline void setLayoutScaleFactor(std::optional layoutScaleFactor) noexcept override {
_swiftPart.setLayoutScaleFactor(layoutScaleFactor);
}
+ inline std::optional getSemantics() noexcept override {
+ auto __result = _swiftPart.getSemantics();
+ return __result;
+ }
+ inline void setSemantics(std::optional semantics) noexcept override {
+ _swiftPart.setSemantics(semantics);
+ }
inline std::optional, DataBindMode, DataBindByName>> getDataBind() noexcept override {
auto __result = _swiftPart.getDataBind();
return __result;
diff --git a/nitrogen/generated/ios/c++/views/HybridRiveViewComponent.mm b/nitrogen/generated/ios/c++/views/HybridRiveViewComponent.mm
index b8b5bba5..d449bd22 100644
--- a/nitrogen/generated/ios/c++/views/HybridRiveViewComponent.mm
+++ b/nitrogen/generated/ios/c++/views/HybridRiveViewComponent.mm
@@ -107,6 +107,11 @@ - (void) updateProps:(const std::shared_ptr&)props
swiftPart.setLayoutScaleFactor(newViewProps.layoutScaleFactor.value);
newViewProps.layoutScaleFactor.isDirty = false;
}
+ // semantics: optional
+ if (newViewProps.semantics.isDirty) {
+ swiftPart.setSemantics(newViewProps.semantics.value);
+ newViewProps.semantics.isDirty = false;
+ }
// dataBind: optional
if (newViewProps.dataBind.isDirty) {
swiftPart.setDataBind(newViewProps.dataBind.value);
diff --git a/nitrogen/generated/ios/swift/HybridRiveViewSpec.swift b/nitrogen/generated/ios/swift/HybridRiveViewSpec.swift
index 42d644fc..e217f1a2 100644
--- a/nitrogen/generated/ios/swift/HybridRiveViewSpec.swift
+++ b/nitrogen/generated/ios/swift/HybridRiveViewSpec.swift
@@ -17,6 +17,7 @@ public protocol HybridRiveViewSpec_protocol: HybridObject, HybridView {
var alignment: Alignment? { get set }
var fit: Fit? { get set }
var layoutScaleFactor: Double? { get set }
+ var semantics: Semantics? { get set }
var dataBind: Variant__any_HybridViewModelInstanceSpec__DataBindMode_DataBindByName? { get set }
var onError: (_ error: RiveError) -> Void { get set }
diff --git a/nitrogen/generated/ios/swift/HybridRiveViewSpec_cxx.swift b/nitrogen/generated/ios/swift/HybridRiveViewSpec_cxx.swift
index 3901ed53..95e15c7f 100644
--- a/nitrogen/generated/ios/swift/HybridRiveViewSpec_cxx.swift
+++ b/nitrogen/generated/ios/swift/HybridRiveViewSpec_cxx.swift
@@ -269,6 +269,23 @@ open class HybridRiveViewSpec_cxx {
}
}
+ public final var semantics: bridge.std__optional_Semantics_ {
+ @inline(__always)
+ get {
+ return { () -> bridge.std__optional_Semantics_ in
+ if let __unwrappedValue = self.__implementation.semantics {
+ return bridge.create_std__optional_Semantics_(__unwrappedValue)
+ } else {
+ return .init()
+ }
+ }()
+ }
+ @inline(__always)
+ set {
+ self.__implementation.semantics = newValue.has_value() ? newValue.pointee : nil
+ }
+ }
+
public final var dataBind: bridge.std__optional_std__variant_std__shared_ptr_HybridViewModelInstanceSpec___DataBindMode__DataBindByName__ {
@inline(__always)
get {
diff --git a/nitrogen/generated/ios/swift/Semantics.swift b/nitrogen/generated/ios/swift/Semantics.swift
new file mode 100644
index 00000000..501888e6
--- /dev/null
+++ b/nitrogen/generated/ios/swift/Semantics.swift
@@ -0,0 +1,44 @@
+///
+/// Semantics.swift
+/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE.
+/// https://github.com/mrousavy/nitro
+/// Copyright © Marc Rousavy @ Margelo
+///
+
+/**
+ * Represents the JS enum `Semantics`, backed by a C++ enum.
+ */
+public typealias Semantics = margelo.nitro.rive.Semantics
+
+public extension Semantics {
+ /**
+ * Get a Semantics for the given String value, or
+ * return `nil` if the given value was invalid/unknown.
+ */
+ init?(fromString string: String) {
+ switch string {
+ case "Off":
+ self = .off
+ case "On":
+ self = .on
+ case "Automatic":
+ self = .automatic
+ default:
+ return nil
+ }
+ }
+
+ /**
+ * Get the String value this Semantics represents.
+ */
+ var stringValue: String {
+ switch self {
+ case .off:
+ return "Off"
+ case .on:
+ return "On"
+ case .automatic:
+ return "Automatic"
+ }
+ }
+}
diff --git a/nitrogen/generated/shared/c++/HybridRiveViewSpec.cpp b/nitrogen/generated/shared/c++/HybridRiveViewSpec.cpp
index 5d1ee0fb..2ef5d7ba 100644
--- a/nitrogen/generated/shared/c++/HybridRiveViewSpec.cpp
+++ b/nitrogen/generated/shared/c++/HybridRiveViewSpec.cpp
@@ -28,6 +28,8 @@ namespace margelo::nitro::rive {
prototype.registerHybridSetter("fit", &HybridRiveViewSpec::setFit);
prototype.registerHybridGetter("layoutScaleFactor", &HybridRiveViewSpec::getLayoutScaleFactor);
prototype.registerHybridSetter("layoutScaleFactor", &HybridRiveViewSpec::setLayoutScaleFactor);
+ prototype.registerHybridGetter("semantics", &HybridRiveViewSpec::getSemantics);
+ prototype.registerHybridSetter("semantics", &HybridRiveViewSpec::setSemantics);
prototype.registerHybridGetter("dataBind", &HybridRiveViewSpec::getDataBind);
prototype.registerHybridSetter("dataBind", &HybridRiveViewSpec::setDataBind);
prototype.registerHybridGetter("onError", &HybridRiveViewSpec::getOnError);
diff --git a/nitrogen/generated/shared/c++/HybridRiveViewSpec.hpp b/nitrogen/generated/shared/c++/HybridRiveViewSpec.hpp
index 1ec43b23..c015acab 100644
--- a/nitrogen/generated/shared/c++/HybridRiveViewSpec.hpp
+++ b/nitrogen/generated/shared/c++/HybridRiveViewSpec.hpp
@@ -19,6 +19,8 @@ namespace margelo::nitro::rive { class HybridRiveFileSpec; }
namespace margelo::nitro::rive { enum class Alignment; }
// Forward declaration of `Fit` to properly resolve imports.
namespace margelo::nitro::rive { enum class Fit; }
+// Forward declaration of `Semantics` to properly resolve imports.
+namespace margelo::nitro::rive { enum class Semantics; }
// Forward declaration of `HybridViewModelInstanceSpec` to properly resolve imports.
namespace margelo::nitro::rive { class HybridViewModelInstanceSpec; }
// Forward declaration of `DataBindMode` to properly resolve imports.
@@ -36,6 +38,7 @@ namespace margelo::nitro::rive { struct UnifiedRiveEvent; }
#include "HybridRiveFileSpec.hpp"
#include "Alignment.hpp"
#include "Fit.hpp"
+#include "Semantics.hpp"
#include "HybridViewModelInstanceSpec.hpp"
#include "DataBindMode.hpp"
#include "DataBindByName.hpp"
@@ -86,6 +89,8 @@ namespace margelo::nitro::rive {
virtual void setFit(std::optional fit) = 0;
virtual std::optional getLayoutScaleFactor() = 0;
virtual void setLayoutScaleFactor(std::optional layoutScaleFactor) = 0;
+ virtual std::optional getSemantics() = 0;
+ virtual void setSemantics(std::optional semantics) = 0;
virtual std::optional, DataBindMode, DataBindByName>> getDataBind() = 0;
virtual void setDataBind(const std::optional, DataBindMode, DataBindByName>>& dataBind) = 0;
virtual std::function getOnError() = 0;
diff --git a/nitrogen/generated/shared/c++/Semantics.hpp b/nitrogen/generated/shared/c++/Semantics.hpp
new file mode 100644
index 00000000..0d96e12e
--- /dev/null
+++ b/nitrogen/generated/shared/c++/Semantics.hpp
@@ -0,0 +1,63 @@
+///
+/// Semantics.hpp
+/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE.
+/// https://github.com/mrousavy/nitro
+/// Copyright © Marc Rousavy @ Margelo
+///
+
+#pragma once
+
+#if __has_include()
+#include
+#else
+#error NitroModules cannot be found! Are you sure you installed NitroModules properly?
+#endif
+#if __has_include()
+#include
+#else
+#error NitroModules cannot be found! Are you sure you installed NitroModules properly?
+#endif
+
+namespace margelo::nitro::rive {
+
+ /**
+ * An enum which can be represented as a JavaScript enum (Semantics).
+ */
+ enum class Semantics {
+ OFF SWIFT_NAME(off) = 0,
+ ON SWIFT_NAME(on) = 1,
+ AUTOMATIC SWIFT_NAME(automatic) = 2,
+ } CLOSED_ENUM;
+
+} // namespace margelo::nitro::rive
+
+namespace margelo::nitro {
+
+ // C++ Semantics <> JS Semantics (enum)
+ template <>
+ struct JSIConverter final {
+ static inline margelo::nitro::rive::Semantics fromJSI(jsi::Runtime& runtime, const jsi::Value& arg) {
+ int enumValue = JSIConverter::fromJSI(runtime, arg);
+ return static_cast(enumValue);
+ }
+ static inline jsi::Value toJSI(jsi::Runtime& runtime, margelo::nitro::rive::Semantics arg) {
+ int enumValue = static_cast(arg);
+ return JSIConverter::toJSI(runtime, enumValue);
+ }
+ static inline bool canConvert(jsi::Runtime&, const jsi::Value& value) {
+ if (!value.isNumber()) {
+ return false;
+ }
+ double number = value.getNumber();
+ int integer = static_cast(number);
+ if (number != integer) {
+ // The integer is not the same value as the double - we truncated floating points.
+ // Enums are all integers, so the input floating point number is obviously invalid.
+ return false;
+ }
+ // Check if we are within the bounds of the enum.
+ return integer >= 0 && integer <= 2;
+ }
+ };
+
+} // namespace margelo::nitro
diff --git a/nitrogen/generated/shared/c++/views/HybridRiveViewComponent.cpp b/nitrogen/generated/shared/c++/views/HybridRiveViewComponent.cpp
index d3f60ffa..048e34e5 100644
--- a/nitrogen/generated/shared/c++/views/HybridRiveViewComponent.cpp
+++ b/nitrogen/generated/shared/c++/views/HybridRiveViewComponent.cpp
@@ -96,6 +96,16 @@ namespace margelo::nitro::rive::views {
throw std::runtime_error(std::string("RiveView.layoutScaleFactor: ") + exc.what());
}
}()),
+ semantics([&]() -> CachedProp> {
+ try {
+ const react::RawValue* rawValue = rawProps.at("semantics", nullptr, nullptr);
+ if (rawValue == nullptr) return sourceProps.semantics;
+ const auto& [runtime, value] = (std::pair)*rawValue;
+ return CachedProp>::fromRawValue(*runtime, value, sourceProps.semantics);
+ } catch (const std::exception& exc) {
+ throw std::runtime_error(std::string("RiveView.semantics: ") + exc.what());
+ }
+ }()),
dataBind([&]() -> CachedProp, DataBindMode, DataBindByName>>> {
try {
const react::RawValue* rawValue = rawProps.at("dataBind", nullptr, nullptr);
@@ -136,6 +146,7 @@ namespace margelo::nitro::rive::views {
case hashString("alignment"): return true;
case hashString("fit"): return true;
case hashString("layoutScaleFactor"): return true;
+ case hashString("semantics"): return true;
case hashString("dataBind"): return true;
case hashString("onError"): return true;
case hashString("hybridRef"): return true;
diff --git a/nitrogen/generated/shared/c++/views/HybridRiveViewComponent.hpp b/nitrogen/generated/shared/c++/views/HybridRiveViewComponent.hpp
index aa25dc25..59742f91 100644
--- a/nitrogen/generated/shared/c++/views/HybridRiveViewComponent.hpp
+++ b/nitrogen/generated/shared/c++/views/HybridRiveViewComponent.hpp
@@ -22,6 +22,7 @@
#include "HybridRiveFileSpec.hpp"
#include "Alignment.hpp"
#include "Fit.hpp"
+#include "Semantics.hpp"
#include "HybridViewModelInstanceSpec.hpp"
#include "DataBindMode.hpp"
#include "DataBindByName.hpp"
@@ -57,6 +58,7 @@ namespace margelo::nitro::rive::views {
CachedProp> alignment;
CachedProp> fit;
CachedProp> layoutScaleFactor;
+ CachedProp> semantics;
CachedProp, DataBindMode, DataBindByName>>> dataBind;
CachedProp> onError;
CachedProp& /* ref */)>>> hybridRef;
diff --git a/nitrogen/generated/shared/json/RiveViewConfig.json b/nitrogen/generated/shared/json/RiveViewConfig.json
index 8eab6292..c2d5d94b 100644
--- a/nitrogen/generated/shared/json/RiveViewConfig.json
+++ b/nitrogen/generated/shared/json/RiveViewConfig.json
@@ -11,6 +11,7 @@
"alignment": true,
"fit": true,
"layoutScaleFactor": true,
+ "semantics": true,
"dataBind": true,
"onError": true,
"hybridRef": true
diff --git a/src/core/Semantics.ts b/src/core/Semantics.ts
new file mode 100644
index 00000000..8613a3dd
--- /dev/null
+++ b/src/core/Semantics.ts
@@ -0,0 +1,19 @@
+export enum Semantics {
+ /**
+ * Accessibility semantics are disabled. No accessibility elements are
+ * created, regardless of screen-reader state.
+ */
+ Off,
+
+ /**
+ * Accessibility semantics are always active. Elements are created and kept
+ * up-to-date on every frame.
+ */
+ On,
+
+ /**
+ * Accessibility semantics activate and deactivate automatically based on
+ * whether a screen reader (e.g. VoiceOver) is currently running.
+ */
+ Automatic,
+}
diff --git a/src/index.tsx b/src/index.tsx
index 80bdd5c8..fa28c7f9 100644
--- a/src/index.tsx
+++ b/src/index.tsx
@@ -42,6 +42,7 @@ export type {
export type { BindableArtboard } from './specs/BindableArtboard.nitro';
export { Fit } from './core/Fit';
export { Alignment } from './core/Alignment';
+export { Semantics } from './core/Semantics';
export { RiveFileFactory } from './core/RiveFile';
export { RiveImages } from './core/RiveImages';
export type { RiveImage } from './specs/RiveImage.nitro';
diff --git a/src/specs/RiveView.nitro.ts b/src/specs/RiveView.nitro.ts
index 8927fc4f..b5a262bc 100644
--- a/src/specs/RiveView.nitro.ts
+++ b/src/specs/RiveView.nitro.ts
@@ -5,6 +5,7 @@ import type {
} from 'react-native-nitro-modules';
import type { RiveFile } from './RiveFile.nitro';
import { Fit } from '../core/Fit';
+import { Semantics } from '../core/Semantics';
import type { ViewModelInstance } from './ViewModel.nitro';
import type { Alignment } from '../core/Alignment';
import type { UnifiedRiveEvent, RiveEvent } from '../core/Events';
@@ -37,6 +38,16 @@ export interface RiveViewProps extends HybridViewProps {
fit?: Fit;
/** The scale factor to apply to the Rive graphic when using Fit.Layout */
layoutScaleFactor?: number;
+ /**
+ * Exposes accessibility semantics authored in the Rive editor to the
+ * platform screen reader (VoiceOver). Defaults to Semantics.Off.
+ *
+ * Only supported on the experimental (default) iOS backend so far; ignored
+ * elsewhere. Android support is pending the upstream rive-android runtime.
+ *
+ * @see https://rive.app/docs/runtimes/apple/semantics
+ */
+ semantics?: Semantics;
/** The view model instance to bind, to the state machine. Defaults to DataBindMode.Auto */
dataBind?: ViewModelInstance | DataBindMode | DataBindByName;
/** Callback function that is called when an error occurs */