diff --git a/components/native/ActivityIndicator/package.json b/components/native/ActivityIndicator/package.json index c120c13c..d8bcd2b3 100644 --- a/components/native/ActivityIndicator/package.json +++ b/components/native/ActivityIndicator/package.json @@ -1,4 +1,5 @@ { "main": "../../../lib/commonjs/components/native/ActivityIndicator.js", - "module": "../../../lib/module/components/native/ActivityIndicator.js" + "module": "../../../lib/module/components/native/ActivityIndicator.js", + "react-native": "../../../src/components/native/ActivityIndicator.tsx" } diff --git a/components/native/FlatList/package.json b/components/native/FlatList/package.json index 5320f2b8..6b15aaab 100644 --- a/components/native/FlatList/package.json +++ b/components/native/FlatList/package.json @@ -1,4 +1,5 @@ { "main": "../../../lib/commonjs/components/native/FlatList.js", - "module": "../../../lib/module/components/native/FlatList.js" + "module": "../../../lib/module/components/native/FlatList.js", + "react-native": "../../../src/components/native/FlatList.tsx" } diff --git a/components/native/Image/package.json b/components/native/Image/package.json index 5052384e..7381b61b 100644 --- a/components/native/Image/package.json +++ b/components/native/Image/package.json @@ -1,4 +1,5 @@ { "main": "../../../lib/commonjs/components/native/Image.js", - "module": "../../../lib/module/components/native/Image.js" + "module": "../../../lib/module/components/native/Image.js", + "react-native": "../../../src/components/native/Image.tsx" } diff --git a/components/native/ImageBackground/package.json b/components/native/ImageBackground/package.json index 0a82c59b..a5ea8301 100644 --- a/components/native/ImageBackground/package.json +++ b/components/native/ImageBackground/package.json @@ -1,4 +1,5 @@ { "main": "../../../lib/commonjs/components/native/ImageBackground.js", - "module": "../../../lib/module/components/native/ImageBackground.js" + "module": "../../../lib/module/components/native/ImageBackground.js", + "react-native": "../../../src/components/native/ImageBackground.native.tsx" } diff --git a/components/native/KeyboardAvoidingView/package.json b/components/native/KeyboardAvoidingView/package.json index 044a62e3..b4dd6b9b 100644 --- a/components/native/KeyboardAvoidingView/package.json +++ b/components/native/KeyboardAvoidingView/package.json @@ -1,4 +1,5 @@ { "main": "../../../lib/commonjs/components/native/KeyboardAvoidingView.js", - "module": "../../../lib/module/components/native/KeyboardAvoidingView.js" + "module": "../../../lib/module/components/native/KeyboardAvoidingView.js", + "react-native": "../../../src/components/native/KeyboardAvoidingView.tsx" } diff --git a/components/native/Pressable/package.json b/components/native/Pressable/package.json index 45ff0f70..12952aa3 100644 --- a/components/native/Pressable/package.json +++ b/components/native/Pressable/package.json @@ -1,5 +1,5 @@ { "main": "../../../lib/commonjs/components/native/Pressable.js", "module": "../../../lib/module/components/native/Pressable.js", - "react-native": "../../../lib/commonjs/components/native/Pressable.native.js" + "react-native": "../../../src/components/native/Pressable.native.tsx" } diff --git a/components/native/RefreshControl/package.json b/components/native/RefreshControl/package.json index 5138f48a..fd4eed59 100644 --- a/components/native/RefreshControl/package.json +++ b/components/native/RefreshControl/package.json @@ -1,4 +1,5 @@ { "main": "../../../lib/commonjs/components/native/RefreshControl.js", - "module": "../../../lib/module/components/native/RefreshControl.js" + "module": "../../../lib/module/components/native/RefreshControl.js", + "react-native": "../../../src/components/native/RefreshControl.tsx" } diff --git a/components/native/ScrollView/package.json b/components/native/ScrollView/package.json index ac6c5bf6..eb0a73ff 100644 --- a/components/native/ScrollView/package.json +++ b/components/native/ScrollView/package.json @@ -1,4 +1,5 @@ { "main": "../../../lib/commonjs/components/native/ScrollView.js", - "module": "../../../lib/module/components/native/ScrollView.js" + "module": "../../../lib/module/components/native/ScrollView.js", + "react-native": "../../../src/components/native/ScrollView.tsx" } diff --git a/components/native/SectionList/package.json b/components/native/SectionList/package.json index c8c80ec1..a222a37e 100644 --- a/components/native/SectionList/package.json +++ b/components/native/SectionList/package.json @@ -1,4 +1,5 @@ { "main": "../../../lib/commonjs/components/native/SectionList.js", - "module": "../../../lib/module/components/native/SectionList.js" + "module": "../../../lib/module/components/native/SectionList.js", + "react-native": "../../../src/components/native/SectionList.tsx" } diff --git a/components/native/Switch/package.json b/components/native/Switch/package.json index fcb0d895..bb9c8629 100644 --- a/components/native/Switch/package.json +++ b/components/native/Switch/package.json @@ -1,4 +1,5 @@ { "main": "../../../lib/commonjs/components/native/Switch.js", - "module": "../../../lib/module/components/native/Switch.js" + "module": "../../../lib/module/components/native/Switch.js", + "react-native": "../../../src/components/native/Switch.tsx" } diff --git a/components/native/Text/package.json b/components/native/Text/package.json index 4536cd41..b3e77379 100644 --- a/components/native/Text/package.json +++ b/components/native/Text/package.json @@ -1,4 +1,5 @@ { "main": "../../../lib/commonjs/components/native/Text.js", - "module": "../../../lib/module/components/native/Text.js" + "module": "../../../lib/module/components/native/Text.js", + "react-native": "../../../src/components/native/Text.tsx" } diff --git a/components/native/TextInput/package.json b/components/native/TextInput/package.json index aa720daa..a6670ec8 100644 --- a/components/native/TextInput/package.json +++ b/components/native/TextInput/package.json @@ -1,4 +1,5 @@ { "main": "../../../lib/commonjs/components/native/TextInput.js", - "module": "../../../lib/module/components/native/TextInput.js" + "module": "../../../lib/module/components/native/TextInput.js", + "react-native": "../../../src/components/native/TextInput.tsx" } diff --git a/components/native/TouchableHighlight/package.json b/components/native/TouchableHighlight/package.json index cf342106..f129b578 100644 --- a/components/native/TouchableHighlight/package.json +++ b/components/native/TouchableHighlight/package.json @@ -1,4 +1,5 @@ { "main": "../../../lib/commonjs/components/native/TouchableHighlight.js", - "module": "../../../lib/module/components/native/TouchableHighlight.js" + "module": "../../../lib/module/components/native/TouchableHighlight.js", + "react-native": "../../../src/components/native/TouchableHighlight.tsx" } diff --git a/components/native/TouchableOpacity/package.json b/components/native/TouchableOpacity/package.json index 9576a871..2e477220 100644 --- a/components/native/TouchableOpacity/package.json +++ b/components/native/TouchableOpacity/package.json @@ -1,4 +1,5 @@ { "main": "../../../lib/commonjs/components/native/TouchableOpacity.js", - "module": "../../../lib/module/components/native/TouchableOpacity.js" + "module": "../../../lib/module/components/native/TouchableOpacity.js", + "react-native": "../../../src/components/native/TouchableOpacity.tsx" } diff --git a/components/native/View/package.json b/components/native/View/package.json index b16fa6f9..7b6a7a96 100644 --- a/components/native/View/package.json +++ b/components/native/View/package.json @@ -1,4 +1,5 @@ { "main": "../../../lib/commonjs/components/native/View.js", - "module": "../../../lib/module/components/native/View.js" + "module": "../../../lib/module/components/native/View.js", + "react-native": "../../../src/components/native/View.tsx" } diff --git a/components/native/VirtualizedList/package.json b/components/native/VirtualizedList/package.json index 1dfdac0c..3175f079 100644 --- a/components/native/VirtualizedList/package.json +++ b/components/native/VirtualizedList/package.json @@ -1,4 +1,5 @@ { "main": "../../../lib/commonjs/components/native/VirtualizedList.js", - "module": "../../../lib/module/components/native/VirtualizedList.js" + "module": "../../../lib/module/components/native/VirtualizedList.js", + "react-native": "../../../src/components/native/VirtualizedList.tsx" } diff --git a/cxx/common/Constants.h b/cxx/common/Constants.h index cc8b817a..942175a6 100644 --- a/cxx/common/Constants.h +++ b/cxx/common/Constants.h @@ -2,13 +2,15 @@ namespace margelo::nitro::unistyles::helpers { -static const std::string UNISTYLES_ID = "__unid"; +static const std::string STYLESHEET_ID = "__stylesheetID"; +static const std::string UNISTYLE_ID = "__unistyleID"; static const std::string ADD_VARIANTS_FN = "useVariants"; static const std::string STYLE_DEPENDENCIES = "uni__dependencies"; -static const std::string STYLE_VARIANTS = "uni__variants"; +static const std::string STYLESHEET_VARIANTS = "__stylesheetVariants"; static const std::string WEB_STYLE_KEY = "_web"; static const std::string EXOTIC_STYLE_KEY = "_exotic"; -static const std::string NAME_STYLE_KEY = "__unistyles_name"; + +// todo verify it static const std::string SECRETS = "__uni__secrets"; static const std::string ARGUMENTS = "__uni__args"; diff --git a/cxx/core/IDGenerator.h b/cxx/core/IDGenerator.h new file mode 100644 index 00000000..8bd2f152 --- /dev/null +++ b/cxx/core/IDGenerator.h @@ -0,0 +1,16 @@ +#include + +namespace margelo::nitro::unistyles::helpers { + +struct IDGenerator { + IDGenerator(unsigned int start = 1): _currentID(start) {} + + int next() { + return _currentID.fetch_add(1, std::memory_order_relaxed); + } + +private: + std::atomic _currentID; +}; + +} diff --git a/cxx/core/RNStyle.h b/cxx/core/RNStyle.h index 4dd2ce17..64d4c3e1 100644 --- a/cxx/core/RNStyle.h +++ b/cxx/core/RNStyle.h @@ -2,30 +2,60 @@ #include #include "Helpers.h" +#include "Parser.h" #include "UnistyleWrapper.h" namespace margelo::nitro::unistyles::core { -jsi::Object toRNStyle(jsi::Runtime& rt, std::shared_ptr styleSheet, std::shared_ptr unistylesRuntime, Variants&& variants); - jsi::Function createAddVariantsProxyFunction(jsi::Runtime& rt, std::shared_ptr stylesheet, std::shared_ptr unistylesRuntime) { auto useVariantsFnName = jsi::PropNameID::forUtf8(rt, helpers::ADD_VARIANTS_FN); + auto parser = parser::Parser(unistylesRuntime); - return jsi::Function::createFromHostFunction(rt, useVariantsFnName, 1, [stylesheet, unistylesRuntime](jsi::Runtime &rt, const jsi::Value &thisVal, const jsi::Value *arguments, size_t count){ + return jsi::Function::createFromHostFunction(rt, useVariantsFnName, 1, [stylesheet, unistylesRuntime, &parser](jsi::Runtime &rt, const jsi::Value &thisVal, const jsi::Value *arguments, size_t count){ helpers::assertThat(rt, count == 1, "Unistyles: useVariants expected to be called with one argument."); helpers::assertThat(rt, arguments[0].isObject(), "Unistyles: useVariants expected to be called with object."); - // using variants == cloning stylesheet - return toRNStyle(rt, stylesheet, unistylesRuntime, helpers::variantsToPairs(rt, arguments[0].asObject(rt))); + jsi::Object rnStyle = jsi::Object(rt); + Variants variants = helpers::variantsToPairs(rt, arguments[0].asObject(rt)); + + // copy hidden properties as they are not enumerable + helpers::defineHiddenProperty(rt, rnStyle, helpers::STYLESHEET_ID.c_str(), jsi::Value(stylesheet->tag)); + helpers::defineHiddenProperty(rt, rnStyle, helpers::STYLESHEET_VARIANTS.c_str(), arguments[0].asObject(rt)); + + // copy unaffected styles, or compute new + helpers::enumerateJSIObject(rt, thisVal.asObject(rt), [&, stylesheet](const std::string& name, jsi::Value& value){ + if (name == helpers::ADD_VARIANTS_FN) { + rnStyle.setProperty(rt, helpers::ADD_VARIANTS_FN.c_str(), std::move(value)); + + return; + } + + if (!stylesheet->unistyles.contains(name)) { + return; + } + + auto unistyle = stylesheet->unistyles[name]; + + if (!unistyle->dependsOn(UnistyleDependency::VARIANTS)) { + rnStyle.setProperty(rt, name.c_str(), std::move(value)); + + return; + } + + // compute new value + parser.rebuildUnistyle(rt, stylesheet, unistyle, variants, std::nullopt); + rnStyle.setProperty(rt, name.c_str(), valueFromUnistyle(rt, unistylesRuntime, unistyle, variants)); + }); + + return rnStyle; }); } jsi::Object toRNStyle(jsi::Runtime& rt, std::shared_ptr stylesheet, std::shared_ptr unistylesRuntime, Variants&& variants) { jsi::Object rnStyle = jsi::Object(rt); - - helpers::defineHiddenProperty(rt, rnStyle, helpers::UNISTYLES_ID.c_str(), jsi::Value(stylesheet->tag)); - helpers::defineHiddenProperty(rt, rnStyle, helpers::STYLE_VARIANTS.c_str(), helpers::variantsToValue(rt, variants)); - + + helpers::defineHiddenProperty(rt, rnStyle, helpers::STYLESHEET_ID.c_str(), jsi::Value(stylesheet->tag)); + helpers::defineHiddenProperty(rt, rnStyle, helpers::STYLESHEET_VARIANTS.c_str(), helpers::variantsToValue(rt, variants)); rnStyle.setProperty(rt, helpers::ADD_VARIANTS_FN.c_str(), createAddVariantsProxyFunction(rt, stylesheet, unistylesRuntime)); for (auto& pair: stylesheet->unistyles) { @@ -33,7 +63,7 @@ jsi::Object toRNStyle(jsi::Runtime& rt, std::shared_ptr stylesheet, rnStyle.setProperty(rt, propertyName.c_str(), valueFromUnistyle(rt, unistylesRuntime, unistyle, variants)); } - + return rnStyle; } diff --git a/cxx/core/Unistyle.h b/cxx/core/Unistyle.h index 86421074..2152d0da 100644 --- a/cxx/core/Unistyle.h +++ b/cxx/core/Unistyle.h @@ -19,8 +19,8 @@ enum class UnistyleType { struct Unistyle { using Shared = std::shared_ptr; - Unistyle(UnistyleType type, std::string styleKey, jsi::Object& rawObject, std::shared_ptr styleSheet) - : styleKey{styleKey}, type{type}, rawValue{std::move(rawObject)}, parent{styleSheet} {} + Unistyle(unsigned int unid, UnistyleType type, std::string styleKey, jsi::Object& rawObject, std::shared_ptr styleSheet) + : unid{unid}, styleKey{styleKey}, type{type}, rawValue{std::move(rawObject)}, parent{styleSheet} {} virtual ~Unistyle() = default; Unistyle(const Unistyle&) = delete; @@ -28,6 +28,7 @@ struct Unistyle { UnistyleType type; std::string styleKey; + unsigned int unid; jsi::Object rawValue; std::optional parsedStyle; std::vector dependencies{}; @@ -65,8 +66,8 @@ struct UnistyleDynamicFunction: public Unistyle { // unprocessedValue <- object generated after calling proxy and user's original function // parsedStyle <- parsed with Unistyle's parser - UnistyleDynamicFunction(UnistyleType type, std::string styleKey, jsi::Object& rawObject, std::shared_ptr styleSheet) - : Unistyle(type, styleKey, rawObject, styleSheet) {} + UnistyleDynamicFunction(unsigned int unid, UnistyleType type, std::string styleKey, jsi::Object& rawObject, std::shared_ptr styleSheet) + : Unistyle(unid, type, styleKey, rawObject, styleSheet) {} UnistyleDynamicFunction(const UnistyleDynamicFunction&) = delete; UnistyleDynamicFunction(UnistyleDynamicFunction&& other) = delete; diff --git a/cxx/core/UnistyleWrapper.h b/cxx/core/UnistyleWrapper.h index b4a0d67a..6bb88ed3 100644 --- a/cxx/core/UnistyleWrapper.h +++ b/cxx/core/UnistyleWrapper.h @@ -21,6 +21,7 @@ struct UnistyleWrapper: public jsi::NativeState { inline static Unistyle::Shared unistyleFromStaticStyleSheet(jsi::Runtime& rt, jsi::Object& value) { auto exoticUnistyle = std::make_shared( + 0, UnistyleType::Object, helpers::EXOTIC_STYLE_KEY, value, @@ -32,56 +33,56 @@ inline static Unistyle::Shared unistyleFromStaticStyleSheet(jsi::Runtime& rt, js return exoticUnistyle; } -inline static std::vector unistylesFromNonExistentNativeState(jsi::Runtime& rt, jsi::Object& value) { - auto hasUnistyleName = value.hasProperty(rt, helpers::NAME_STYLE_KEY.c_str()); +inline static std::optional extractUnistyleId(const std::string& maybeUnistyleKey) { + size_t pos = maybeUnistyleKey.find_last_of('_'); - // return wrapped RN/inline style - if (!hasUnistyleName) { - return {unistyleFromStaticStyleSheet(rt, value)}; + if (pos == std::string::npos) { + return std::nullopt; } - throw jsi::JSError(rt, R"(Unistyles: Style is not bound! + // extract the substring after the underscore + std::string numberPart = maybeUnistyleKey.substr(pos + 1); -Potential reasons: -- You likely used the spread operator on a Unistyle style outside of a JSX component + return static_cast(std::stoul(numberPart)); +} -If you need to merge styles, do it within the style prop of your JSX component: +inline static Unistyle::Shared unistyleFromID(jsi::Runtime& rt, jsi::Object& value) { + auto maybeUnistyleKey = value.getProperty(rt, helpers::UNISTYLE_ID.c_str()); -style={{...styles.container, ...styles.otherProp}} -or -style={[styles.container, styles.otherProp]} + if (!maybeUnistyleKey.isString()) { + return nullptr; + } -If you pass computed style prop to component use array syntax: + auto unistyleId = extractUnistyleId(maybeUnistyleKey.asString(rt).utf8(rt)); -customStyleProp={[styles.container, styles.otherProp]} + // ID: 0 is for exotic styles + if (!unistyleId.has_value() || unistyleId == 0) { + return nullptr; + } -Copying a Unistyle style outside of a JSX element will remove its internal C++ state, leading to unexpected behavior.)"); + auto& registry = UnistylesRegistry::get(); + + return registry.getUnistyleById(rt, unistyleId.value()); } -inline static jsi::Object generateUnistylesPrototype( - jsi::Runtime& rt, - std::shared_ptr unistylesRuntime, - Unistyle::Shared unistyle, - std::optional variants, - std::optional arguments -) { - // add prototype metadata for createUnistylesComponent - auto proto = jsi::Object(rt); - auto hostFn = jsi::Function::createFromHostFunction(rt, jsi::PropNameID::forUtf8(rt, "getStyle"), 0, [unistyle, unistylesRuntime](jsi::Runtime &rt, const jsi::Value &thisValue, const jsi::Value *args, size_t count){ - auto variants = helpers::variantsToPairs(rt, thisValue.asObject(rt).getProperty(rt, "variants").asObject(rt)); - auto arguments = helpers::parseDynamicFunctionArguments(rt, thisValue.asObject(rt).getProperty(rt, "arguments").asObject(rt).asArray(rt)); - - parser::Parser(unistylesRuntime).rebuildUnistyle(rt, unistyle->parent, unistyle, variants, std::make_optional>(arguments)); - - return jsi::Value(rt, unistyle->parsedStyle.value()).asObject(rt); - }); +inline static std::vector unistylesFromNonExistentNativeState(jsi::Runtime& rt, jsi::Object& value) { + auto hasUnistyleID = value.hasProperty(rt, helpers::UNISTYLE_ID.c_str()); - proto.setProperty(rt, "getStyle", std::move(hostFn)); - proto.setProperty(rt, "arguments", arguments.has_value() ? std::move(arguments.value()) : jsi::Array(rt, 0)); - proto.setProperty(rt, "variants", variants.has_value() ? helpers::pairsToVariantsValue(rt, variants.value()) : jsi::Object(rt)); - proto.setProperty(rt, helpers::STYLE_DEPENDENCIES.c_str(), helpers::dependenciesToJSIArray(rt, unistyle->dependencies)); + // return wrapped RN/inline style + if (!hasUnistyleID) { + return {unistyleFromStaticStyleSheet(rt, value)}; + } - return proto; + // last chance to fallback and get unistyle based on ID + auto maybeUnistyle = unistyleFromID(rt, value); + + if (maybeUnistyle != nullptr) { + return {maybeUnistyle}; + } + + throw jsi::JSError(rt, R"(Unistyles: Style is not bound! + +You likely altered __unistyleID and we're not able to recover C++ state attached to this node.)"); } inline static std::vector unistyleFromValue(jsi::Runtime& rt, const jsi::Value& value) { @@ -116,28 +117,33 @@ inline static std::vector unistyleFromValue(jsi::Runtime& rt, return unistyles; } -inline static jsi::Value valueFromUnistyle(jsi::Runtime& rt, std::shared_ptr unistylesRuntime, Unistyle::Shared unistyle, Variants& variants) { +inline static jsi::Value objectFromUnistyle(jsi::Runtime& rt, std::shared_ptr unistylesRuntime, Unistyle::Shared unistyle, Variants& variants) { auto wrappedUnistyle = std::make_shared(unistyle); + auto unistyleID = jsi::String::createFromUtf8(rt, unistyle->styleKey + "_" + std::to_string(unistyle->unid)); - if (unistyle->type == UnistyleType::Object) { - jsi::Object obj = jsi::Object(rt); + jsi::Object obj = jsi::Object(rt); - obj.setNativeState(rt, std::move(wrappedUnistyle)); - obj.setProperty(rt, helpers::NAME_STYLE_KEY.c_str(), jsi::String::createFromUtf8(rt, unistyle->styleKey)); + obj.setNativeState(rt, std::move(wrappedUnistyle)); + obj.setProperty(rt, helpers::UNISTYLE_ID.c_str(), unistyleID); - helpers::defineHiddenProperty(rt, obj, helpers::STYLE_DEPENDENCIES.c_str(), helpers::dependenciesToJSIArray(rt, unistyle->dependencies)); - helpers::mergeJSIObjects(rt, obj, unistyle->parsedStyle.value()); + helpers::mergeJSIObjects(rt, obj, unistyle->parsedStyle.value()); - obj.setProperty(rt, "__proto__", generateUnistylesPrototype(rt, unistylesRuntime, unistyle, variants, std::nullopt)); + return obj; +} - return obj; +inline static jsi::Value valueFromUnistyle(jsi::Runtime& rt, std::shared_ptr unistylesRuntime, Unistyle::Shared unistyle, Variants& variants) { + if (unistyle->type == UnistyleType::Object) { + return objectFromUnistyle(rt, unistylesRuntime, unistyle, variants); } + auto wrappedUnistyle = std::make_shared(unistyle); + auto unistyleID = jsi::String::createFromUtf8(rt, unistyle->styleKey + "_" + std::to_string(unistyle->unid)); + auto unistyleFn = std::dynamic_pointer_cast(unistyle); auto hostFn = jsi::Value(rt, unistyleFn->proxiedFunction.value()).asObject(rt).asFunction(rt); hostFn.setNativeState(rt, std::move(wrappedUnistyle)); - hostFn.setProperty(rt, helpers::NAME_STYLE_KEY.c_str(), jsi::String::createFromUtf8(rt, unistyleFn->styleKey)); + hostFn.setProperty(rt, helpers::UNISTYLE_ID.c_str(), unistyleID); return std::move(hostFn); } diff --git a/cxx/core/UnistylesRegistry.cpp b/cxx/core/UnistylesRegistry.cpp index 98363c11..3a3d597e 100644 --- a/cxx/core/UnistylesRegistry.cpp +++ b/cxx/core/UnistylesRegistry.cpp @@ -75,7 +75,7 @@ void core::UnistylesRegistry::linkShadowNodeWithUnistyle( ) { shadow::ShadowLeafUpdates updates; auto parser = parser::Parser(nullptr); - + std::for_each(unistylesData.begin(), unistylesData.end(), [this, &rt, shadowNodeFamily](std::shared_ptr unistyleData){ this->_shadowRegistry[&rt][shadowNodeFamily].emplace_back(unistyleData); }); @@ -159,7 +159,7 @@ std::vector> core::UnistylesRegistry::getStyle UnistyleDependency::THEME); auto themeDidChange = themeDidChangeIt != unistylesDependencies.end(); auto runtimeDidChange = (themeDidChange && unistylesDependencies.size() > 1) || unistylesDependencies.size() > 0; - + // if nothing changed, skip further lookup if (!themeDidChange && !runtimeDidChange) { return stylesheetsToRefresh; @@ -173,7 +173,7 @@ std::vector> core::UnistylesRegistry::getStyle if (styleSheet->type == StyleSheetType::ThemableWithMiniRuntime) { for (const auto& unistylePair: styleSheet->unistyles) { auto& [_, unistyle] = unistylePair; - + bool hasAnyOfDependencies = std::any_of( unistyle->dependencies.begin(), unistyle->dependencies.end(), @@ -181,10 +181,10 @@ std::vector> core::UnistylesRegistry::getStyle return std::find(unistylesDependencies.begin(), unistylesDependencies.end(), dep) != unistylesDependencies.end(); } ); - + if (hasAnyOfDependencies) { stylesheetsToRefresh.emplace_back(styleSheet); - + return; } } @@ -198,6 +198,26 @@ std::vector> core::UnistylesRegistry::getStyle return stylesheetsToRefresh; } +core::Unistyle::Shared core::UnistylesRegistry::getUnistyleById(jsi::Runtime& rt, unsigned int unistyleID) { + for (auto& pair: this->_styleSheetRegistry[&rt]) { + auto [_, stylesheet] = pair; + + for (auto unistylePair: stylesheet->unistyles) { + auto [_, unistyle] = unistylePair; + + if (unistyle->unid == unistyleID) { + return unistyle; + } + } + } + + return nullptr; +} + +unsigned int core::UnistylesRegistry::getNextUnistyleId() { + return this->_idGenerator.next(); +} + const std::optional core::UnistylesRegistry::getScopedTheme() { return this->_scopedTheme; } diff --git a/cxx/core/UnistylesRegistry.h b/cxx/core/UnistylesRegistry.h index 3d0eeae1..036213f4 100644 --- a/cxx/core/UnistylesRegistry.h +++ b/cxx/core/UnistylesRegistry.h @@ -12,6 +12,7 @@ #include "Unistyle.h" #include "UnistyleData.h" #include "ShadowTrafficController.h" +#include "IDGenerator.h" namespace margelo::nitro::unistyles::core { @@ -45,10 +46,13 @@ struct UnistylesRegistry: public StyleSheetRegistry { shadow::ShadowTrafficController trafficController{}; const std::optional getScopedTheme(); void setScopedTheme(std::optional themeName); + unsigned int getNextUnistyleId(); + core::Unistyle::Shared getUnistyleById(jsi::Runtime& rt, unsigned int unistyleID); private: UnistylesRegistry() = default; + helpers::IDGenerator _idGenerator{}; std::optional _scopedTheme{}; std::unordered_map _states{}; std::unordered_map>> _styleSheetRegistry{}; diff --git a/cxx/hybridObjects/HybridShadowRegistry.cpp b/cxx/hybridObjects/HybridShadowRegistry.cpp index bd8273d6..f184528a 100644 --- a/cxx/hybridObjects/HybridShadowRegistry.cpp +++ b/cxx/hybridObjects/HybridShadowRegistry.cpp @@ -7,23 +7,26 @@ jsi::Value HybridShadowRegistry::link(jsi::Runtime &rt, const jsi::Value &thisVa helpers::assertThat(rt, count == 2, "Unistyles: Invalid babel transform 'ShadowRegistry link' expected 2 arguments."); ShadowNode::Shared shadowNodeWrapper = shadowNodeFromValue(rt, args[0]); + std::vector unistyleWrappers = core::unistyleFromValue(rt, args[1]); std::vector> arguments; auto& registry = core::UnistylesRegistry::get(); for (size_t i = 0; i < unistyleWrappers.size(); i++) { if (unistyleWrappers[i]->type == core::UnistyleType::DynamicFunction) { - auto rawStyle = args[1].asObject(rt).asArray(rt).getValueAtIndex(rt, i); - - helpers::assertThat(rt, rawStyle.isObject(), "Unistyles: Dynamic function is not bound!"); - - auto maybeSecrets = rawStyle.getObject(rt).getProperty(rt, helpers::SECRETS.c_str()); - - helpers::assertThat(rt, maybeSecrets.isObject(), "Unistyles: Dynamic function is not bound!"); - - auto secrets = maybeSecrets.asObject(rt).getProperty(rt, helpers::ARGUMENTS.c_str()); - - arguments.push_back(helpers::parseDynamicFunctionArguments(rt, secrets.asObject(rt).asArray(rt))); + // todo +// auto rawStyle = args[1].asObject(rt).asArray(rt).getValueAtIndex(rt, i); +// +// helpers::assertThat(rt, rawStyle.isObject(), "Unistyles: Dynamic function is not bound!"); +// +// auto maybeSecrets = rawStyle.getObject(rt).getProperty(rt, helpers::SECRETS.c_str()); +// +// helpers::assertThat(rt, maybeSecrets.isObject(), "Unistyles: Dynamic function is not bound!"); +// +// auto secrets = maybeSecrets.asObject(rt).getProperty(rt, helpers::ARGUMENTS.c_str()); +// +// arguments.push_back(helpers::parseDynamicFunctionArguments(rt, secrets.asObject(rt).asArray(rt))); + arguments.push_back({}); continue; } @@ -65,7 +68,8 @@ jsi::Value HybridShadowRegistry::link(jsi::Runtime &rt, const jsi::Value &thisVa parser.rebuildUnistyleWithScopedTheme(rt, parsedStyleSheet, unistyleData); } else { // for other styles, not scoped to theme we need to compute variants value - parser.rebuildUnistyleWithVariants(rt, unistyleData); + // todo, do we need it? + // parser.rebuildUnistyleWithVariants(rt, unistyleData); } unistylesData.emplace_back(unistyleData); diff --git a/cxx/parser/Parser.cpp b/cxx/parser/Parser.cpp index 3933cbf8..975b27f7 100644 --- a/cxx/parser/Parser.cpp +++ b/cxx/parser/Parser.cpp @@ -10,6 +10,7 @@ using Variants = std::vector>; // called only once while processing StyleSheet.create void parser::Parser::buildUnistyles(jsi::Runtime& rt, std::shared_ptr styleSheet) { jsi::Object unwrappedStyleSheet = this->unwrapStyleSheet(rt, styleSheet, std::nullopt); + auto& registry = core::UnistylesRegistry::get(); helpers::enumerateJSIObject(rt, unwrappedStyleSheet, [&](const std::string& styleKey, jsi::Value& propertyValue){ helpers::assertThat(rt, propertyValue.isObject(), "Unistyles: Style with name '" + styleKey + "' is not a function or object."); @@ -18,6 +19,7 @@ void parser::Parser::buildUnistyles(jsi::Runtime& rt, std::shared_ptrunistyles[styleKey] = std::make_shared( + registry.getNextUnistyleId(), UnistyleType::DynamicFunction, styleKey, styleValue, @@ -28,6 +30,7 @@ void parser::Parser::buildUnistyles(jsi::Runtime& rt, std::shared_ptrunistyles[styleKey] = std::make_shared( + registry.getNextUnistyleId(), UnistyleType::Object, styleKey, styleValue, @@ -511,33 +514,16 @@ jsi::Function parser::Parser::createDynamicFunctionProxy(jsi::Runtime& rt, Unist unistyleFn->unprocessedValue = jsi::Value(rt, result).asObject(rt); - jsi::Value rawVariants = thisObject.hasProperty(rt, helpers::STYLE_VARIANTS.c_str()) - ? thisObject.getProperty(rt, helpers::STYLE_VARIANTS.c_str()) - : jsi::Value::undefined(); + jsi::Value rawVariants = thisObject.hasProperty(rt, helpers::STYLESHEET_VARIANTS.c_str()) + ? thisObject.getProperty(rt, helpers::STYLESHEET_VARIANTS.c_str()) + : jsi::Object(rt); - std::optional variants = rawVariants.isUndefined() - ? std::nullopt - : std::optional(helpers::variantsToPairs(rt, rawVariants.asObject(rt))); + Variants variants = helpers::variantsToPairs(rt, rawVariants.asObject(rt)); unistyleFn->parsedStyle = this->parseFirstLevel(rt, unistyleFn, variants); unistyleFn->seal(); - jsi::Object style = jsi::Value(rt, unistyleFn->parsedStyle.value()).asObject(rt); - - // include dependencies for createUnistylesComponent - style.setProperty(rt, "__proto__", generateUnistylesPrototype(rt, unistylesRuntime, unistyle, variants, helpers::functionArgumentsToArray(rt, args, count))); - - jsi::Object secrets = jsi::Object(rt); - - secrets.setProperty(rt, helpers::ARGUMENTS.c_str(), helpers::functionArgumentsToArray(rt, args, count)); - - helpers::defineHiddenProperty(rt, style, helpers::SECRETS.c_str(), secrets); - - auto wrappedUnistyle = std::make_shared(unistyle); - - style.setNativeState(rt, std::move(wrappedUnistyle)); - - return style; + return core::objectFromUnistyle(rt, unistylesRuntime, unistyle, variants); }); } diff --git a/example/App.tsx b/example/App.tsx index 3d187871..46653bdf 100644 --- a/example/App.tsx +++ b/example/App.tsx @@ -78,39 +78,39 @@ export const App = () => { ))} - - - - - + {/* */} + {/* */} + {/* */} + {/* */} + {/* */} setCount(prevState => prevState + 1)} /> - {Array.from({ length: 3}).map((_, index) => ( - [styles.pressable(event, 1), { marginRight: 10 * index}]} - onPress={() => {}} - > - - Pressable test {index + 1} - - - ))} + {/* {Array.from({ length: 3}).map((_, index) => ( */} + {/* [styles.pressable(event, 1), { marginRight: 10 * index}]} */} + {/* onPress={() => {}} */} + {/* > */} + {/* */} + {/* Pressable test {index + 1} */} + {/* */} + {/* */} + {/* ))} */} I'm scoped to premium - - - I'm scoped to light - - + {/* */} + {/* */} + {/* I'm scoped to light */} + {/* */} + {/* */} ) diff --git a/plugin/__tests__/dependencies.spec.js b/plugin/__tests__/dependencies.spec.js index d392ddf4..a5dff519 100644 --- a/plugin/__tests__/dependencies.spec.js +++ b/plugin/__tests__/dependencies.spec.js @@ -51,7 +51,7 @@ pluginTester({ export const Example = () => { return ( - + Hello world ) @@ -109,7 +109,7 @@ pluginTester({ export const Example = () => { return ( - + Hello world ) @@ -161,7 +161,7 @@ pluginTester({ export const Example = () => { return ( - + Hello world ) @@ -271,7 +271,7 @@ pluginTester({ export const Example = () => { return ( - + Hello world ) @@ -393,7 +393,7 @@ pluginTester({ export const Example = () => { return ( - + Hello world ) @@ -448,7 +448,7 @@ pluginTester({ export const Example = () => { return ( - + Hello world ) @@ -495,7 +495,7 @@ pluginTester({ export const Example = () => { return ( - + Hello world ) @@ -545,7 +545,7 @@ pluginTester({ export const Example = () => { return ( - + Hello world ) @@ -615,7 +615,7 @@ pluginTester({ export const Example = () => { return ( - + Hello world ) @@ -687,7 +687,7 @@ pluginTester({ export const Example = () => { return ( - + Hello world ) diff --git a/plugin/__tests__/ref.spec.js b/plugin/__tests__/ref.spec.js index 46b0bbd5..c33a4ece 100644 --- a/plugin/__tests__/ref.spec.js +++ b/plugin/__tests__/ref.spec.js @@ -75,7 +75,7 @@ pluginTester({ export const Example = () => { return ( - + Hello world ) @@ -118,7 +118,7 @@ pluginTester({ export const Example = () => { return ( - + Hello world ) @@ -171,7 +171,7 @@ pluginTester({ let ref = React.useRef() return ( - + Hello world ) @@ -232,7 +232,7 @@ pluginTester({ doSomething(ref) myRef.current = ref }} - style={[styles.container]} + style={styles.container} > Hello world @@ -302,7 +302,7 @@ pluginTester({ customCleanup() } }} - style={[styles.container]} + style={styles.container} > Hello world @@ -372,7 +372,7 @@ pluginTester({ } return ( - + Hello world ) @@ -441,7 +441,7 @@ pluginTester({ } return ( - + Hello world ) @@ -456,153 +456,6 @@ pluginTester({ 92366683 ) ` - }, - { - title: 'Should keep order of spreads', - code: ` - import { View } from 'react-native' - import { StyleSheet } from 'react-native-unistyles' - - export const Example = () => { - return ( - - ) - } - - const styles = StyleSheet.create(theme => ({ - container: { - flex: 1, - alignItems: 'center', - justifyContent: 'center', - backgroundColor: theme.colors.backgroundColor - }, - secondProp: { - marginHorizontal: theme.gap(10), - backgroundColor: 'red' - }, - thirdProp: { - backgroundColor: 'blue' - } - })) - `, - output: ` - import { View } from 'react-native-unistyles/components/native/View' - - import { StyleSheet } from 'react-native-unistyles' - - export const Example = () => { - return - } - - const styles = StyleSheet.create( - theme => ({ - container: { - flex: 1, - alignItems: 'center', - justifyContent: 'center', - backgroundColor: theme.colors.backgroundColor, - uni__dependencies: [0] - }, - secondProp: { - marginHorizontal: theme.gap(10), - backgroundColor: 'red', - uni__dependencies: [0] - }, - thirdProp: { - backgroundColor: 'blue' - } - }), - 92366683 - ) - ` - }, - { - title: 'Should support nested styles', - code: ` - import { View } from 'react-native' - import { StyleSheet } from 'react-native-unistyles' - - export const Example = ({ styles }) => { - return ( - - ) - } - - const styles = StyleSheet.create(theme => ({ - container: { - flex: 1, - alignItems: 'center', - justifyContent: 'center', - backgroundColor: theme.colors.backgroundColor - } - })) - `, - output: ` - import { View } from 'react-native-unistyles/components/native/View' - - import { StyleSheet } from 'react-native-unistyles' - - export const Example = ({ styles }) => { - return - } - - const styles = StyleSheet.create( - theme => ({ - container: { - flex: 1, - alignItems: 'center', - justifyContent: 'center', - backgroundColor: theme.colors.backgroundColor, - uni__dependencies: [0] - } - }), - 92366683 - ) - ` - }, - { - title: 'Should support conditional styles', - code: ` - import { View } from 'react-native' - import { StyleSheet } from 'react-native-unistyles' - - export const Example = ({ condition }) => { - return ( - - ) - } - - const styles = StyleSheet.create(theme => ({ - container: { - flex: 1, - alignItems: 'center', - justifyContent: 'center', - backgroundColor: theme.colors.backgroundColor - } - })) - `, - output: ` - import { View } from 'react-native-unistyles/components/native/View' - - import { StyleSheet } from 'react-native-unistyles' - - export const Example = ({ condition }) => { - return - } - - const styles = StyleSheet.create( - theme => ({ - container: { - flex: 1, - alignItems: 'center', - justifyContent: 'center', - backgroundColor: theme.colors.backgroundColor, - uni__dependencies: [0] - } - }), - 92366683 - ) - ` } ] }) diff --git a/plugin/__tests__/stylesheet.spec.js b/plugin/__tests__/stylesheet.spec.js index 8d2a0075..9adaf556 100644 --- a/plugin/__tests__/stylesheet.spec.js +++ b/plugin/__tests__/stylesheet.spec.js @@ -41,7 +41,7 @@ pluginTester({ export const Example = () => { return ( - + Hello world ) @@ -85,7 +85,7 @@ pluginTester({ export const Example = () => { return ( - + Hello world ) @@ -130,7 +130,7 @@ pluginTester({ export const Example = () => { return ( - + Hello world ) @@ -176,7 +176,7 @@ pluginTester({ export const Example = () => { return ( - + Hello world ) @@ -223,7 +223,7 @@ pluginTester({ export const Example = () => { return ( - + Hello world ) @@ -271,7 +271,7 @@ pluginTester({ export const Example = () => { return ( - + Hello world ) @@ -320,7 +320,7 @@ pluginTester({ export const Example = () => { return ( - + Hello world ) @@ -371,7 +371,7 @@ pluginTester({ export const Example = () => { return ( - + Hello world ) @@ -430,7 +430,7 @@ pluginTester({ export const Example = () => { return ( - + Hello world ) @@ -496,7 +496,7 @@ pluginTester({ export const Example = () => { return ( - + styles.pressable}> Hello world @@ -528,7 +528,7 @@ pluginTester({ export const Example = ({ height }) => { return ( - [styles.sectionItem, styles.other(1), { height }, pressed && styles.pressed]}> + styles.sectionItem, styles.other(1), { height }, pressed && styles.pressed)}> Hello world @@ -555,8 +555,8 @@ pluginTester({ export const Example = ({ height }) => { return ( - - [styles.sectionItem, styles.other(1), { height }, pressed && styles.pressed]}> + + styles.sectionItem, styles.other(1), { height }, pressed && styles.pressed)}> Hello world @@ -616,7 +616,7 @@ pluginTester({ export const Example = ({ height }) => { return ( - + [ styles.sectionItem, diff --git a/plugin/__tests__/variants.spec.js b/plugin/__tests__/variants.spec.js index ef7ed5bd..88f6dc61 100644 --- a/plugin/__tests__/variants.spec.js +++ b/plugin/__tests__/variants.spec.js @@ -68,7 +68,7 @@ pluginTester({ }) return ( - + Hello world ) @@ -136,7 +136,7 @@ pluginTester({ }) return ( - + Hello world ) @@ -197,7 +197,7 @@ pluginTester({ }) return ( - + Hello world {[1, 2, 3].map((_, index) => { const _styles3 = styles @@ -206,7 +206,7 @@ pluginTester({ size: 'large' }) - return + return } })} diff --git a/plugin/index.js b/plugin/index.js index e5bbc8dc..9a1139fc 100644 --- a/plugin/index.js +++ b/plugin/index.js @@ -1,5 +1,4 @@ const { addUnistylesImport, isInsideNodeModules } = require('./import') -const { getStyleMetadata, getStyleAttribute, styleAttributeToArray, handlePressable } = require('./style') const { hasStringRef } = require('./ref') const { isUnistylesStyleSheet, analyzeDependencies, addStyleSheetTag, getUnistyles } = require('./stylesheet') const { extractVariants } = require('./variants') @@ -142,27 +141,6 @@ module.exports = function ({ types: t }) { return } - const styleAttr = getStyleAttribute(t, path) - - // component has no style prop - if (!styleAttr) { - return - } - - const metadata = getStyleMetadata(t, styleAttr.value.expression, null, state) - - if (openingElementName === 'Pressable') { - return handlePressable(t, path, styleAttr, metadata, state) - } - - // style prop is using unexpected expression - if (metadata.length === 0) { - return - } - - styleAttributeToArray(t, path) - - // to add import state.file.hasAnyUnistyle = true if (hasStringRef(t, path)) { diff --git a/plugin/style.js b/plugin/style.js deleted file mode 100644 index 7b083d5f..00000000 --- a/plugin/style.js +++ /dev/null @@ -1,168 +0,0 @@ -function getStyleMetadata(t, node, dynamicFunction = null, state) { - // {styles.container} - if (t.isMemberExpression(node)) { - const members = t.isMemberExpression(node.object) - ? [node.object.object.name, node.object.property.name, node.property.name] - : [node.object.name, node.property.name] - - return [ - { - members: members.filter(Boolean), - inlineStyle: undefined, - dynamicFunction, - conditionalExpression: undefined, - logicalExpression: undefined - } - ] - } - - // [styles.container] - if (t.isArrayExpression(node)) { - return node.elements.flatMap(element => getStyleMetadata(t, element, null, state)) - } - - // [...styles.container] - if (t.isSpreadElement(node)) { - return getStyleMetadata(t, node.argument, null, state) - } - - // {{ ...styles.container }} - if (t.isObjectExpression(node)) { - return node - .properties - .flatMap(prop => { - // handle inline styles - if (t.isObjectProperty(prop)) { - return [{ - members: [], - inlineStyle: t.objectExpression([prop]), - dynamicFunction: undefined, - conditionalExpression: undefined, - logicalExpression: undefined - }] - } - - return getStyleMetadata(t, prop.argument, null, state) - }) - .filter(Boolean) - } - - // {styles.container(arg1, arg2)} - if (t.isCallExpression(node)) { - return getStyleMetadata(t, node.callee, node, state) - } - - if (t.isIdentifier(node)) { - return [{ - members: [node.name], - inlineStyle: undefined, - dynamicFunction: undefined, - conditionalExpression: undefined, - logicalExpression: undefined - }] - } - - if (t.isConditionalExpression(node)) { - return [{ - members: [], - inlineStyle: undefined, - dynamicFunction: undefined, - conditionalExpression: node, - logicalExpression: undefined - }] - } - - if (t.isArrowFunctionExpression(node)) { - return getStyleMetadata(t, node.body, node, state) - } - - // {condition && styles.container} - if (t.isLogicalExpression(node)) { - return [{ - members: [], - inlineStyle: undefined, - dynamicFunction: undefined, - conditionalExpression: undefined, - logicalExpression: node - }] - } - - // only when pressable is used - if (t.isBlockStatement(node) && state.file.shouldIncludePressable) { - const returnStatement = node.body.find(t.isReturnStatement) - - return returnStatement - ? getStyleMetadata(t, returnStatement.argument, null, state) - : [] - } - - return [] -} - -function getStyleAttribute(t, path) { - return path.node.openingElement.attributes.find(attr => - t.isJSXAttribute(attr) && - t.isJSXIdentifier(attr.name, { name: 'style' }) && - t.isJSXExpressionContainer(attr.value) - ) -} - -function styleAttributeToArray(t, path) { - const styleAttribute = getStyleAttribute(t, path) - - // {{...style.container, ...style.container}} - if (t.isObjectExpression(styleAttribute.value.expression)) { - const properties = styleAttribute.value.expression.properties - .map(property => t.isSpreadElement(property) - ? property.argument - : t.objectExpression([property]) - ) - - styleAttribute.value.expression = t.arrayExpression(properties) - - return - } - - // [{...style.container, ...style.container}] - if (t.isArrayExpression(styleAttribute.value.expression)) { - const properties = styleAttribute.value.expression.elements - .flatMap(property => { - if (t.isSpreadElement(property)) { - return property.argument - } - return property - }) - - styleAttribute.value.expression = t.arrayExpression(properties) - - return - } - - styleAttribute.value.expression = t.arrayExpression([styleAttribute.value.expression]) -} - -function handlePressable(t, path, styleAttr, metadata, state) { - // add variants - if (state.file.hasVariants) { - const variants = t.jsxAttribute( - t.jsxIdentifier('variants'), - t.jsxExpressionContainer(t.identifier('__uni__variants')) - ) - - path.node.openingElement.attributes.push(variants) - } - - // to add import - state.file.hasAnyUnistyle = true - - if (t.isMemberExpression(styleAttr.value.expression)) { - // styleAttr.value.expression = handlePressableFromMemberExpression(t, path, metadata, true) - } -} - -module.exports = { - getStyleMetadata, - getStyleAttribute, - styleAttributeToArray, - handlePressable -} diff --git a/src/specs/ShadowRegistry/index.ts b/src/specs/ShadowRegistry/index.ts index 4760fa27..638e73e7 100644 --- a/src/specs/ShadowRegistry/index.ts +++ b/src/specs/ShadowRegistry/index.ts @@ -32,12 +32,12 @@ const findShadowNodeForHandle = (handle: ViewHandle) => { HybridShadowRegistry.add = (handle, styles) => { // virtualized nodes can be null - if (!handle || !styles || !Array.isArray(styles)) { + if (!handle || !styles) { return } // filter Reanimated styles and styles that are undefined - const filteredStyles = styles + const filteredStyles = (Array.isArray(styles) ? styles : [styles]) .filter(style => !style?.initial?.updater) .filter(Boolean)