Skip to content

Commit

Permalink
fix: nested variants in pressables
Browse files Browse the repository at this point in the history
  • Loading branch information
jpudysz committed Dec 10, 2024
1 parent 977099c commit 119166c
Show file tree
Hide file tree
Showing 10 changed files with 73 additions and 18 deletions.
10 changes: 10 additions & 0 deletions cxx/common/Helpers.h
Original file line number Diff line number Diff line change
Expand Up @@ -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<std::string, std::string>& 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);

Expand Down
13 changes: 12 additions & 1 deletion cxx/core/HostStyle.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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();
});
Expand Down
4 changes: 4 additions & 0 deletions cxx/core/HostStyle.h
Original file line number Diff line number Diff line change
Expand Up @@ -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> _styleSheet;
std::shared_ptr<HybridUnistylesRuntime> _unistylesRuntime;
std::vector<std::pair<std::string, std::string>> _variants{};
Expand Down
35 changes: 26 additions & 9 deletions cxx/core/UnistyleWrapper.h
Original file line number Diff line number Diff line change
Expand Up @@ -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<HybridUnistylesRuntime> unistylesRuntime,
Unistyle::Shared unistyle,
std::optional<Variants> variants,
std::optional<jsi::Array> 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<std::vector<folly::dynamic>>(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;
Expand All @@ -71,31 +88,31 @@ inline static std::vector<Unistyle::Shared> 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<Unistyle::Shared> 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<UnistyleWrapper>(rt)->unistyle);
});

return unistyles;
}

Expand All @@ -111,7 +128,7 @@ inline static jsi::Value valueFromUnistyle(jsi::Runtime& rt, std::shared_ptr<Hyb
helpers::defineHiddenProperty(rt, obj, helpers::STYLE_DEPENDENCIES.c_str(), helpers::dependenciesToJSIArray(rt, unistyle->dependencies));
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;
}
Expand Down
2 changes: 1 addition & 1 deletion cxx/parser/Parser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down
3 changes: 2 additions & 1 deletion example/babel.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down
3 changes: 2 additions & 1 deletion expo-example/babel.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down
5 changes: 4 additions & 1 deletion plugin/import.js
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
7 changes: 5 additions & 2 deletions src/core/withUnistyles/useDependencies.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,12 @@ type ListenerProps = {
dependencies: Array<UnistyleDependency>
}

export const useDependencies = (listener: (props: ListenerProps) => VoidFunction) => {
export const useDependencies = (
listener: (props: ListenerProps) => VoidFunction,
stylesDependencies: Array<UnistyleDependency> = []
) => {
const scopedTheme = UnistylesShadowRegistry.getScopedTheme() as UnistylesTheme
const [dependencies] = useState(() => new Set<number>())
const [dependencies] = useState(() => new Set<number>(stylesDependencies))
const [theme, setTheme] = useState(UnistylesRuntime.getTheme(scopedTheme))
const [_, runtimeChanged] = useReducer(() => ({}), {})

Expand Down
9 changes: 7 additions & 2 deletions src/core/withUnistyles/withUnistyles.native.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,11 @@ export const withUnistyles = <TProps extends Record<string, any>, 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]
Expand All @@ -41,7 +46,7 @@ export const withUnistyles = <TProps extends Record<string, any>, TMappings exte
stylesRef.current = {
...stylesRef.current,
// @ts-expect-error - this is hidden from TS
[propName]: props[propName]
[propName]: props[propName].__proto__?.getStyle?.() || props[propName]
}
}
})
Expand All @@ -55,7 +60,7 @@ export const withUnistyles = <TProps extends Record<string, any>, TMappings exte
})

return () => dispose()
})
}, narrowedProps.style?.__proto__.uni__dependencies)

const mappingProps = mappings ? mappingsCallback(mappings) : {}
const unistyleProps = narrowedProps.uniProps ? mappingsCallback(narrowedProps.uniProps) : {}
Expand Down

0 comments on commit 119166c

Please sign in to comment.