diff --git a/.changeset/strange-taxis-talk.md b/.changeset/strange-taxis-talk.md new file mode 100644 index 000000000..aca629e68 --- /dev/null +++ b/.changeset/strange-taxis-talk.md @@ -0,0 +1,5 @@ +--- +"@skip-go/widget": patch +--- + +Update web-component to allow passing props via javascript properties diff --git a/docs/widget/web-component.mdx b/docs/widget/web-component.mdx index b83f1c800..fcb3608db 100644 --- a/docs/widget/web-component.mdx +++ b/docs/widget/web-component.mdx @@ -33,20 +33,24 @@ This can be added to npm scripts in `package.json`, a `.env file`, or used when ## Usage -Props are the same as [`WidgetProps`](./configuration), but passed as attributes in kebab-case. Use strings or stringified objects for complex props. +Props are the exact same as [`WidgetProps`](./configuration) but you are required to pass them to the element via javascript/typescript. ```tsx
- +
+ ``` ## Performance Considerations diff --git a/examples/nuxtjs/app.vue b/examples/nuxtjs/app.vue index 151fad1ab..87fd987fb 100644 --- a/examples/nuxtjs/app.vue +++ b/examples/nuxtjs/app.vue @@ -1,19 +1,17 @@ + + \ No newline at end of file diff --git a/examples/raw-html.html b/examples/raw-html.html index 8f90d301c..07b466dab 100644 --- a/examples/raw-html.html +++ b/examples/raw-html.html @@ -7,15 +7,17 @@ type="module" >
- +
+ + \ No newline at end of file diff --git a/packages/widget/src/devMode/loadWidget.tsx b/packages/widget/src/devMode/loadWidget.tsx index b2de11693..51097e3f9 100644 --- a/packages/widget/src/devMode/loadWidget.tsx +++ b/packages/widget/src/devMode/loadWidget.tsx @@ -1,16 +1,18 @@ -import { StrictMode, useState } from "react"; +import { StrictMode, useEffect, useMemo, useState } from "react"; import { createRoot } from "react-dom/client"; -import { Widget } from "@/widget/Widget"; +import "../web-component"; import { Column, Row } from "@/components/Layout"; import "./global.css"; -import { defaultTheme, lightTheme } from "@/widget/theme"; import { resetWidget } from "@/state/swapPage"; +import { defaultTheme, lightTheme } from "@/widget/theme"; +import { Widget, WidgetProps } from "@/widget/Widget"; const DevMode = () => { const [theme, setTheme] = useState<"dark" | "light">("dark"); const [apiUrl, setApiUrl] = useState<"prod" | "dev">("prod"); const [testnet, setTestnet] = useState(false); const [disableShadowDom, setDisableShadowDom] = useState(true); + const [renderWebComponent, setRenderWebComponent] = useState(false); const toggleTheme = () => { if (theme === "dark") { @@ -20,6 +22,65 @@ const DevMode = () => { } }; + const widgetProps: WidgetProps = useMemo(() => { + return { + theme: { + ...(theme === "dark" ? defaultTheme : lightTheme), + brandTextColor: "black", + brandColor: "#FF66FF", + }, + settings: { + useUnlimitedApproval: true, + }, + disableShadowDom, + onlyTestnet: testnet, + routeConfig: { + experimentalFeatures: ["eureka"], + }, + apiUrl: + apiUrl === "prod" ? "https://go.skip.build/api/skip" : "https://dev.go.skip.build/api/skip", + ibcEurekaHighlightedAssets: { + USDC: ["cosmoshub-4"], + USDT: undefined, + }, + assetSymbolsSortedToTop: [ + "LBTC", + "ATOM", + "USDC", + "USDT", + "ETH", + "TIA", + "OSMO", + "NTRN", + "INJ", + ], + filterOut: { + destination: { + "pacific-1": ["ibc/6C00E4AA0CC7618370F81F7378638AE6C48EFF8C9203CE1C2357012B440EBDB7"], + "1329": ["0xB75D0B03c06A926e488e2659DF1A861F860bD3d1"], + "1": ["0xbf45a5029d081333407cc52a84be5ed40e181c46"], + }, + }, + filterOutUnlessUserHasBalance: { + source: { + "1": ["0xbf45a5029d081333407cc52a84be5ed40e181c46"], + }, + }, + }; + }, [apiUrl, disableShadowDom, testnet, theme]); + + useEffect(() => { + const skipWidget = document.querySelector("skip-widget"); + if (skipWidget) { + Object.entries(widgetProps).forEach(([key, value]) => { + // @ts-expect-error this is like the equivalent of + // spreading the props to the web-component + // but we dont expect users to do it like this + skipWidget[key as keyof WidgetProps] = value; + }); + } + }, [widgetProps]); + return ( @@ -35,6 +96,9 @@ const DevMode = () => { + { padding: "0 10px", }} > - + {renderWebComponent ? : } diff --git a/packages/widget/src/web-component.tsx b/packages/widget/src/web-component.tsx index 9f165ff70..7bfd1e5c2 100644 --- a/packages/widget/src/web-component.tsx +++ b/packages/widget/src/web-component.tsx @@ -1,49 +1,58 @@ /* eslint-disable @typescript-eslint/consistent-type-definitions */ -/* eslint-disable @typescript-eslint/no-namespace */ + import toWebComponent from "@r2wc/react-to-web-component"; -import { Widget } from "./widget/Widget"; +import { NewSkipClientOptions, Widget, WidgetProps } from "./widget/Widget"; -type WebComponentProps = { - container: { - attributes: Record[]; - }; -}; +const WEB_COMPONENT_NAME = "skip-widget"; -function isJsonString(str: string) { - try { - JSON.parse(str); - } catch (_err) { - return false; - } - return true; -} +type WebComponentProps = WidgetProps & NewSkipClientOptions; -const camelize = (inputString: string) => { - inputString = inputString.toLowerCase(); - return inputString.replace(/-./g, (x) => x[1].toUpperCase()); +type PropDescriptors = { + [K in keyof WebComponentProps]: "any"; }; -const WidgetWithProvider = (props: WebComponentProps) => { - const parsedProps = Array.from(props.container.attributes).map(({ name, value }) => { - return { key: name, value }; - }); - - const realProps = parsedProps.reduce( - (accumulator, initialValue) => { - const { key, value } = initialValue; - - accumulator[camelize(key)] = isJsonString(value) ? JSON.parse(value) : value; - return accumulator; - }, - {} as Record, - ); - - return ; +const widgetPropTypes: Required = { + rootId: "any", + theme: "any", + brandColor: "any", + onlyTestnet: "any", + defaultRoute: "any", + settings: "any", + routeConfig: "any", + filter: "any", + filterOut: "any", + filterOutUnlessUserHasBalance: "any", + walletConnect: "any", + enableSentrySessionReplays: "any", + enableAmplitudeAnalytics: "any", + connectedAddresses: "any", + simulate: "any", + disableShadowDom: "any", + ibcEurekaHighlightedAssets: "any", + assetSymbolsSortedToTop: "any", + hideAssetsUnlessWalletTypeConnected: "any", + apiUrl: "any", + apiKey: "any", + endpointOptions: "any", + aminoTypes: "any", + registryTypes: "any", + chainIdsToAffiliates: "any", + cacheDurationMs: "any", + getCosmosSigner: "any", + getEVMSigner: "any", + getSVMSigner: "any", + onWalletConnected: "any", + onWalletDisconnected: "any", + onTransactionBroadcasted: "any", + onTransactionComplete: "any", + onTransactionFailed: "any", + onRouteUpdated: "any", }; -const WEB_COMPONENT_NAME = "skip-widget"; - -const WebComponent = toWebComponent(WidgetWithProvider); +const WebComponent = toWebComponent(Widget, { + // @ts-expect-error any is not one of the valid types but it works + props: widgetPropTypes, +}); function initializeSkipWidget() { if (!customElements.get(WEB_COMPONENT_NAME)) { @@ -51,23 +60,15 @@ function initializeSkipWidget() { } // Upgrade any existing skip-widget elements - document.querySelectorAll(WEB_COMPONENT_NAME).forEach((el) => { - customElements.upgrade(el); - }); + document.querySelectorAll(WEB_COMPONENT_NAME).forEach((el) => customElements.upgrade(el as Node)); } initializeSkipWidget(); export default WebComponent; -type Stringify = { - [K in keyof T]?: string; -}; - declare global { - namespace JSX { - interface IntrinsicElements { - [WEB_COMPONENT_NAME]: Stringify; - } + interface HTMLElementTagNameMap { + [WEB_COMPONENT_NAME]: WidgetProps; } } diff --git a/packages/widget/src/widget/Widget.tsx b/packages/widget/src/widget/Widget.tsx index 968a2eb1d..e28b5e697 100644 --- a/packages/widget/src/widget/Widget.tsx +++ b/packages/widget/src/widget/Widget.tsx @@ -97,7 +97,7 @@ type NewSwapVenueRequest = { chainId: string; }; -type NewSkipClientOptions = Omit & { +export type NewSkipClientOptions = Omit & { apiUrl?: string; chainIdsToAffiliates?: Record; };