diff --git a/cxx/common/Helpers.h b/cxx/common/Helpers.h index 0b133425..e8959428 100644 --- a/cxx/common/Helpers.h +++ b/cxx/common/Helpers.h @@ -127,6 +127,16 @@ inline Variants variantsToPairs(jsi::Runtime& rt, jsi::Object&& variants) { return pairs; } +inline jsi::Object pairsToVariantsValue(jsi::Runtime& rt, Variants& pairs) { + auto variantsValue = jsi::Object(rt); + + std::for_each(pairs.begin(), pairs.end(), [&rt, &variantsValue](std::pair& pair){ + variantsValue.setProperty(rt, jsi::PropNameID::forUtf8(rt, pair.first), jsi::String::createFromUtf8(rt, pair.second)); + }); + + return variantsValue; +} + inline jsi::Object variantsToValue(jsi::Runtime& rt, Variants& variants) { jsi::Object rawVariants = jsi::Object(rt); diff --git a/cxx/core/HostStyle.cpp b/cxx/core/HostStyle.cpp index d5194b1d..c6589736 100644 --- a/cxx/core/HostStyle.cpp +++ b/cxx/core/HostStyle.cpp @@ -45,7 +45,18 @@ jsi::Function HostStyle::createAddVariantsProxyFunction(jsi::Runtime& rt) { 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."); - this->_variants = helpers::variantsToPairs(rt, arguments[0].asObject(rt)); + auto parser = parser::Parser(this->_unistylesRuntime); + auto pairs = helpers::variantsToPairs(rt, arguments[0].asObject(rt)); + + if (this->hasVariantsSet && pairs == this->_variants) { + return jsi::Value::undefined(); + } + + // new variants or empty variants + this->hasVariantsSet = true; + this->_variants = pairs; + + parser.rebuildUnistylesWithVariants(rt, this->_styleSheet, this->_variants); return jsi::Value::undefined(); }); diff --git a/cxx/core/HostStyle.h b/cxx/core/HostStyle.h index 5a7303fe..71bb0d9d 100644 --- a/cxx/core/HostStyle.h +++ b/cxx/core/HostStyle.h @@ -21,6 +21,10 @@ struct JSI_EXPORT HostStyle : public jsi::HostObject { jsi::Function createAddVariantsProxyFunction(jsi::Runtime& rt); private: + // to additionally distinguish between empty variants + // that are set at the beginning + bool hasVariantsSet = false; + std::shared_ptr _styleSheet; std::shared_ptr _unistylesRuntime; std::vector> _variants{}; diff --git a/cxx/core/UnistyleWrapper.h b/cxx/core/UnistyleWrapper.h index d0d009eb..5c0502d5 100644 --- a/cxx/core/UnistyleWrapper.h +++ b/cxx/core/UnistyleWrapper.h @@ -58,10 +58,27 @@ customStyleProp={[styles.container, styles.otherProp]} Copying a Unistyle style outside of a JSX element will remove its internal C++ state, leading to unexpected behavior.)"); } -inline static jsi::Object generateUnistylesPrototype(jsi::Runtime& rt, Unistyle::Shared unistyle) { +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); + }); + + 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 proto; @@ -71,31 +88,31 @@ inline static std::vector unistyleFromValue(jsi::Runtime& rt, if (value.isNull() || !value.isObject()) { return {}; } - + auto maybeArray = value.asObject(rt); - + helpers::assertThat(rt, maybeArray.isArray(rt), "Unistyles: can't retrieve Unistyle state from node as it's not an array."); - + std::vector unistyles; jsi::Array unistylesArray = maybeArray.asArray(rt); - + helpers::iterateJSIArray(rt, unistylesArray, [&rt, &unistyles](size_t index, jsi::Value& value){ auto obj = value.getObject(rt); // possible if user used React Native styles or inline styles or did spread styles if (!obj.hasNativeState(rt)) { auto exoticUnistyles = unistylesFromNonExistentNativeState(rt, obj); - + for (auto& exoticUnistyle: exoticUnistyles) { unistyles.emplace_back(exoticUnistyle); } - + return; } unistyles.emplace_back(value.getObject(rt).getNativeState(rt)->unistyle); }); - + return unistyles; } @@ -111,7 +128,7 @@ inline static jsi::Value valueFromUnistyle(jsi::Runtime& rt, std::shared_ptrdependencies)); helpers::mergeJSIObjects(rt, obj, unistyle->parsedStyle.value()); - obj.setProperty(rt, "__proto__", generateUnistylesPrototype(rt, unistyle)); + obj.setProperty(rt, "__proto__", generateUnistylesPrototype(rt, unistylesRuntime, unistyle, std::nullopt, std::nullopt)); return obj; } diff --git a/cxx/parser/Parser.cpp b/cxx/parser/Parser.cpp index 4caddb8f..f8213243 100644 --- a/cxx/parser/Parser.cpp +++ b/cxx/parser/Parser.cpp @@ -491,7 +491,7 @@ jsi::Function parser::Parser::createDynamicFunctionProxy(jsi::Runtime& rt, Unist jsi::Object style = jsi::Value(rt, unistyleFn->parsedStyle.value()).asObject(rt); // include dependencies for createUnistylesComponent - style.setProperty(rt, "__proto__", generateUnistylesPrototype(rt, unistyle)); + style.setProperty(rt, "__proto__", generateUnistylesPrototype(rt, unistylesRuntime, unistyle, variants, helpers::functionArgumentsToArray(rt, args, count))); jsi::Object secrets = jsi::Object(rt); diff --git a/example/babel.config.js b/example/babel.config.js index 195ad154..38bc6814 100644 --- a/example/babel.config.js +++ b/example/babel.config.js @@ -8,7 +8,8 @@ module.exports = api => { presets: ['module:@react-native/babel-preset'], plugins: [ [path.join(__dirname, '../plugin'), { - debug: true + debug: true, + isLocal: true }], [ 'module-resolver', diff --git a/expo-example/babel.config.js b/expo-example/babel.config.js index 87bad8f7..77441927 100644 --- a/expo-example/babel.config.js +++ b/expo-example/babel.config.js @@ -8,7 +8,8 @@ module.exports = function (api) { presets: ['babel-preset-expo'], plugins: [ [path.join(__dirname, '../plugin'), { - debug: true + debug: true, + isLocal: true }], [ 'module-resolver', diff --git a/plugin/import.js b/plugin/import.js index 4cee2eb5..c9ca7213 100644 --- a/plugin/import.js +++ b/plugin/import.js @@ -29,7 +29,10 @@ function addUnistylesImport(t, path, state) { pairs.forEach(([localName, name]) => { const newImport = t.importDeclaration( [t.importSpecifier(t.identifier(localName), t.identifier(name))], - t.stringLiteral(`react-native-unistyles/src/components/native/${name}`) + t.stringLiteral(state.opts.isLocal + ? `react-native-unistyles/../components/native/${name}` + : `react-native-unistyles/src/components/native/${name}` + ) ) path.node.body.unshift(newImport) diff --git a/src/core/withUnistyles/useDependencies.ts b/src/core/withUnistyles/useDependencies.ts index 66de5dcf..64decb02 100644 --- a/src/core/withUnistyles/useDependencies.ts +++ b/src/core/withUnistyles/useDependencies.ts @@ -31,9 +31,12 @@ type ListenerProps = { dependencies: Array } -export const useDependencies = (listener: (props: ListenerProps) => VoidFunction) => { +export const useDependencies = ( + listener: (props: ListenerProps) => VoidFunction, + stylesDependencies: Array = [] +) => { const scopedTheme = UnistylesShadowRegistry.getScopedTheme() as UnistylesTheme - const [dependencies] = useState(() => new Set()) + const [dependencies] = useState(() => new Set(stylesDependencies)) const [theme, setTheme] = useState(UnistylesRuntime.getTheme(scopedTheme)) const [_, runtimeChanged] = useReducer(() => ({}), {}) diff --git a/src/core/withUnistyles/withUnistyles.native.tsx b/src/core/withUnistyles/withUnistyles.native.tsx index 8f9eece2..6fa370e4 100644 --- a/src/core/withUnistyles/withUnistyles.native.tsx +++ b/src/core/withUnistyles/withUnistyles.native.tsx @@ -23,6 +23,11 @@ export const withUnistyles = , TMappings exte console.error(`🦄 Unistyles: createUnistylesComponent requires ${propName} to be an object. Please check props for component: ${Component.displayName}`) } + // @ts-expect-error - this is hidden from TS + if (props[propName].__unistyles_name && !props[propName].__proto__?.getStyle) { + console.error(`🦄 Unistyles: createUnistylesComponent received style that is not bound. You likely used the spread operator on a Unistyle style. Please check props for component: ${Component.displayName}`) + } + stylesRef.current = { ...stylesRef.current, [propName]: narrowedProps[propName] @@ -41,7 +46,7 @@ export const withUnistyles = , TMappings exte stylesRef.current = { ...stylesRef.current, // @ts-expect-error - this is hidden from TS - [propName]: props[propName] + [propName]: props[propName].__proto__?.getStyle?.() || props[propName] } } }) @@ -55,7 +60,7 @@ export const withUnistyles = , TMappings exte }) return () => dispose() - }) + }, narrowedProps.style?.__proto__.uni__dependencies) const mappingProps = mappings ? mappingsCallback(mappings) : {} const unistyleProps = narrowedProps.uniProps ? mappingsCallback(narrowedProps.uniProps) : {}