Reg Lite: support mounting inside a shadow DOM (Web Component hosts) - #159
Reg Lite: support mounting inside a shadow DOM (Web Component hosts)#159gcutrini wants to merge 3 commits into
Conversation
Stripe Elements cannot mount inside a shadow root — Stripe reaches its iframes through window.frames, which cannot see into shadow trees. When StripeForm is mounted in a shadow root, render the PaymentElement into a light-DOM node and project it back in-flow through a named <slot>, per Stripe's recommended workaround (stripe/stripe-js#143). A callback ref on the form detects whether it sits in a shadow root and picks the slot path or the inline path accordingly; outside a shadow root the Element mounts inline as before.
|
Important
This repository does not receive automatic reviews because it has fewer than 10 stars. ⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: Comment |
santipalenque
left a comment
There was a problem hiding this comment.
@gcutrini make comments consise, we don't need the full explanation just a hint or reason
| <form className={styles.form} id="payment-form" onSubmit={handleSubmit(onSubmit)}> | ||
| <PaymentElement options={paymentOptions} /> | ||
| <form ref={detectSlotHost} className={styles.form} id="payment-form" onSubmit={handleSubmit(onSubmit)}> | ||
| {slotHost === undefined |
There was a problem hiding this comment.
dont' do chained ternary, move to an auxiliary method please.
There was a problem hiding this comment.
Done in ba815a8. The chained ternary is now renderPaymentElement with three early returns. Also cut the comments down.
| // host element when shadow-mounted. A callback ref resolves it when the form | ||
| // node attaches — during commit, before paint — so the PaymentElement is only | ||
| // rendered once the context is known and never attempts an in-shadow mount. | ||
| const [slotHost, setSlotHost] = useState(undefined); |
There was a problem hiding this comment.
@gcutrini No test covers the new shadow-DOM detection/portal logic (slotHost state + detectSlotHost ref + the slotted-render branch below). stripe-form had zero tests before this PR and still has none, so a regression here (wrong portal target, slot never receiving the projected element) would fail silently in production for any shadow-DOM host, with nothing catching it in CI.
Suggested fix: add a unit test that renders StripeForm inside a real ShadowRoot (jsdom 16.6, already a devDependency, supports element.attachShadow) asserting the PaymentElement wrapper lands as a light-DOM child of the shadow host with slot="stripe-payment", plus one asserting the inline (non-shadow) path is unchanged.
There was a problem hiding this comment.
Added in bb4e081. Four tests, rendering into a real ShadowRoot via attachShadow as you suggested.
They pin the portal target lands on the host and not in the shadow tree, the slot left behind in the form, the inline path unchanged, and that nothing mounts before the callback ref resolves the context.
I checked each one fails on a matching break: portal into the shadow root instead of the host, dropping the slot, dropping the wait for the context, and taking the slot path in the light DOM.
The Element has to reach the light DOM for Stripe to find it, and nothing checked that. Tests pin the portal target, the slot left behind in the form, the unchanged inline path, and that nothing mounts before the mount context is known.
Replace the chained ternary in the render with renderPaymentElement, and cut the comments down to the reason.
ref: https://app.clickup.com/t/86bbm2fzf
Reg Lite can be embedded inside a shadow DOM, for example when a host renders it as a self-contained Web Component. Stripe's Payment Element cannot mount inside a shadow root: Stripe finds its payment iframes through window.frames, which cannot see into shadow trees, so the payment step would render empty.
Reg Lite now detects when it sits inside a shadow root and keeps the Payment Element in the light DOM, projecting it back in-flow through a named slot (Stripe's recommended approach, stripe/stripe-js#143). Outside a shadow root nothing changes: the Element mounts inline as before.
Result: Reg Lite works in encapsulated hosts with payments intact, end to end.