Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions packages/react-wrappers/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
# CHANGELOG

## 1.7.5

- Simplified `ref` handling
- Fixed duplicate event handlers

## 1.7.4

- Fixed kebab case attribute string mapping fallback
Expand Down
2 changes: 1 addition & 1 deletion packages/react-wrappers/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "custom-element-react-wrappers",
"version": "1.7.4",
"version": "1.7.5",
"description": "A tool for generating react-compatible wrappers for custom elements",
"main": "./dist/index.cjs",
"module": "./dist/index.js",
Expand Down
64 changes: 50 additions & 14 deletions packages/react-wrappers/src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -104,10 +104,29 @@ export const RESERVED_WORDS = [

export function saveReactUtils(outdir: string, ssrSafe?: boolean) {
const reactUtils = `
import { useEffect, useLayoutEffect } from "react";
import { useEffect, useLayoutEffect, useRef } from "react";

${ssrSafe ? `const useIsomorphicLayoutEffect = typeof window !== 'undefined' ? useLayoutEffect : useEffect` : ""}

export function mergeRefs(target, forwardedRef) {
if (!forwardedRef) {
return;
}

if (typeof forwardedRef === "function") {
forwardedRef(target);
} else {
forwardedRef.current = target;
}
}

export function createForwardedRefHandler(localRef, forwardedRef) {
return (node) => {
localRef.current = node;
mergeRefs(node, forwardedRef);
};
}

export function useProperties(targetElement, propName, value) {
useEffect(() => {
if (
Expand All @@ -126,19 +145,36 @@ export function useProperties(targetElement, propName, value) {
}

export function useEventListener(targetElement, eventName, eventHandler) {
// keep a ref to the latest handler so we don't need to re-register the event listener
// whenever the handler changes (avoids duplicate listeners on re-renders)
const handlerRef = useRef(eventHandler);
handlerRef.current = eventHandler;

${ssrSafe ? "useIsomorphicLayoutEffect" : "useLayoutEffect"}(() => {
if (eventHandler !== undefined) {
targetElement?.current?.addEventListener(eventName, eventHandler);
const element = targetElement?.current;
if (!element || eventName === undefined) {
return;
}

// capture the handler at the time the listener is attached so we can call cancel on it
const attachedHandler = handlerRef.current;

const eventListener = (event) => {
if (handlerRef.current) {
handlerRef.current(event);
}
};

element.addEventListener(eventName, eventListener);

return () => {
if (eventHandler?.cancel) {
eventHandler.cancel();
if (attachedHandler?.cancel) {
attachedHandler.cancel();
}

targetElement?.current?.removeEventListener(eventName, eventHandler);
element.removeEventListener(eventName, eventListener);
};
}, [eventName, eventHandler, targetElement.current]);
}, [eventName, targetElement?.current]);
}

`;
Expand All @@ -163,17 +199,17 @@ export function ScopeProvider({ prefix, suffix, children }) {
`;

const scopeProviderTypes = `
export type ScopeProps = {
export type ScopeProps = {
/** Adds a prefix to the custom element tag name */
prefix?: string,
prefix?: string,
/** Adds a prefix to the custom element tag name */
suffix?: string,
children?: React.ReactNode
suffix?: string,
children?: React.ReactNode
};

/**
* Provides a mechanism to add a custom prefix or suffix to to child components.
* This prevents tag name collisions with components from different versions of the same library.
/**
* Provides a mechanism to add a custom prefix or suffix to to child components.
* This prevents tag name collisions with components from different versions of the same library.
*/
export function ScopeProvider(props: ScopeProps): JSX.Element;
`;
Expand Down
14 changes: 4 additions & 10 deletions packages/react-wrappers/src/wrapper-generator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -337,10 +337,11 @@ function getReactComponentTemplate(
} ${config.scopedTags ? ", useContext" : ""} } from "react";
${!config.ssrSafe ? `import '${modulePath}';` : ""}
${
has(eventTemplates) || has(propTemplates)
useEffect
? `import {
${has(eventTemplates) ? "useEventListener," : ""}
${has(propTemplates) ? "useProperties" : ""}
${has(propTemplates) ? "useProperties," : ""}
createForwardedRefHandler
} from './react-utils.js';`
: ""
}
Expand Down Expand Up @@ -385,14 +386,7 @@ function getReactComponentTemplate(
{
${
useEffect
? `ref: (node) => {
ref.current = node;
if (typeof forwardedRef === "function") {
forwardedRef(node);
} else if (forwardedRef) {
forwardedRef.current = node;
}
},`
? `ref: createForwardedRefHandler(ref, forwardedRef),`
: ""
}
${has(unusedProps) ? "...filteredProps" : "...props"},
Expand Down