@silverassist/leadcapture-form 0.1.2 → 0.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +16 -1
- package/dist/index.js +9 -39
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +9 -39
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -2,7 +2,22 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
|
|
5
|
-
## [0.1.
|
|
5
|
+
## [0.1.3] - Unreleased
|
|
6
|
+
|
|
7
|
+
### Changed
|
|
8
|
+
|
|
9
|
+
- Bumped `@silverassist/next-script-loader` to `^0.1.1` and removed the
|
|
10
|
+
package-local generation map (`bumpGeneration`/`currentGeneration`) and
|
|
11
|
+
the 0.1.2 `hasHandledRemountRef` guard, now that both are handled at the
|
|
12
|
+
source: `ScriptLoader.reload()` itself shares the in-flight promise for
|
|
13
|
+
a same-variant reload already in progress (the actual fix for the
|
|
14
|
+
React Strict Mode double-render), and `ScriptLoader.getGeneration()` /
|
|
15
|
+
`unload(atGeneration?)` replace the hand-rolled generation tracking this
|
|
16
|
+
package carried since 0.1.0. No behavior change for consumers — same
|
|
17
|
+
fix, now enforced one layer down so every `ScriptLoader` consumer gets
|
|
18
|
+
it, not just this one.
|
|
19
|
+
|
|
20
|
+
## [0.1.2] - 2026-08-31
|
|
6
21
|
|
|
7
22
|
### Fixed
|
|
8
23
|
|
package/dist/index.js
CHANGED
|
@@ -24,26 +24,6 @@ let react_jsx_runtime = require("react/jsx-runtime");
|
|
|
24
24
|
* the README for the workaround.
|
|
25
25
|
*/
|
|
26
26
|
const leadCaptureLoader = new _silverassist_next_script_loader.ScriptLoader();
|
|
27
|
-
/**
|
|
28
|
-
* Generation counter per variant, incremented on every {@link ScriptLoader.load}/
|
|
29
|
-
* {@link ScriptLoader.reload} call. A delayed on-unmount cleanup captures the
|
|
30
|
-
* generation at mount time and skips its `unload()` call if the generation has
|
|
31
|
-
* since advanced — i.e. a new mount already reloaded the script before the old
|
|
32
|
-
* mount's delayed cleanup ran (a page-navigation remount, not a real teardown).
|
|
33
|
-
* `ScriptLoader`'s own ref-counting handles the common case; this guards the
|
|
34
|
-
* one case it doesn't: `reload()` doesn't change the reference count, so a
|
|
35
|
-
* stale `unload()` after a `reload()` could drop the count to zero and tear
|
|
36
|
-
* down a script a fresh mount is now depending on.
|
|
37
|
-
*/
|
|
38
|
-
const generationByVariant = /* @__PURE__ */ new Map();
|
|
39
|
-
function bumpGeneration(variant) {
|
|
40
|
-
const next = (generationByVariant.get(variant) ?? 0) + 1;
|
|
41
|
-
generationByVariant.set(variant, next);
|
|
42
|
-
return next;
|
|
43
|
-
}
|
|
44
|
-
function currentGeneration(variant) {
|
|
45
|
-
return generationByVariant.get(variant) ?? 0;
|
|
46
|
-
}
|
|
47
27
|
const DEFAULT_LEADCAPTURE_SCRIPT_URL = "https://api.useleadbot.com/lead-bots/get-pixel-script.js";
|
|
48
28
|
/**
|
|
49
29
|
* LeadCaptureForm — renders a LeadCapture IO form with support for
|
|
@@ -78,7 +58,6 @@ function LeadCaptureForm({ formVariant, formTokens, scriptUrl, usageContext, isM
|
|
|
78
58
|
const formRef = (0, react.useRef)(null);
|
|
79
59
|
const mountGenRef = (0, react.useRef)(0);
|
|
80
60
|
const isMountedRef = (0, react.useRef)(true);
|
|
81
|
-
const hasHandledRemountRef = (0, react.useRef)(false);
|
|
82
61
|
const containerId = `leadcapture-container-${formVariant}-${usageContext}`;
|
|
83
62
|
(0, react.useEffect)(() => {
|
|
84
63
|
isMountedRef.current = true;
|
|
@@ -118,7 +97,6 @@ function LeadCaptureForm({ formVariant, formTokens, scriptUrl, usageContext, isM
|
|
|
118
97
|
if (!(usageContext === "modal" ? isModalOpen === true : isInViewport)) return;
|
|
119
98
|
const load = () => {
|
|
120
99
|
window.form_token = formTokens[formVariant];
|
|
121
|
-
bumpGeneration(formVariant);
|
|
122
100
|
leadCaptureLoader.load(formVariant).then(() => {
|
|
123
101
|
if (!isMountedRef.current) return;
|
|
124
102
|
leadCaptureLoader.forceSetOwner(containerId);
|
|
@@ -163,27 +141,20 @@ function LeadCaptureForm({ formVariant, formTokens, scriptUrl, usageContext, isM
|
|
|
163
141
|
* that case immediately, using this variant's own load history instead
|
|
164
142
|
* of the interaction gate above.
|
|
165
143
|
*
|
|
166
|
-
*
|
|
167
|
-
*
|
|
168
|
-
*
|
|
169
|
-
*
|
|
170
|
-
*
|
|
171
|
-
*
|
|
172
|
-
* the current script execute and each populate the container, rendering
|
|
173
|
-
* the widget twice. Claiming ownership isn't a substitute for this guard
|
|
174
|
-
* -- `setOwner` is idempotent for the same id, so a second call from the
|
|
175
|
-
* same instance succeeds too.
|
|
144
|
+
* Safe to call on every render this condition holds, including twice in
|
|
145
|
+
* a row for the same mount under React Strict Mode's dev-only effect
|
|
146
|
+
* replay: `ScriptLoader.reload()` (0.1.1+) shares the in-flight promise
|
|
147
|
+
* for a same-variant reload already in progress instead of tearing the
|
|
148
|
+
* script down again, so a second call here is a no-op rather than a
|
|
149
|
+
* second script execution.
|
|
176
150
|
*/
|
|
177
151
|
(0, react.useEffect)(() => {
|
|
178
152
|
if (usageContext !== "onPage") return;
|
|
179
|
-
if (
|
|
180
|
-
if (currentGeneration(formVariant) === 0) return;
|
|
153
|
+
if (leadCaptureLoader.getGeneration() === 0) return;
|
|
181
154
|
if (!leadCaptureLoader.setOwner(containerId)) return;
|
|
182
155
|
const container = formRef.current?.querySelector(".leadforms-embd-form");
|
|
183
156
|
if (!container || container.children.length > 0) return;
|
|
184
|
-
hasHandledRemountRef.current = true;
|
|
185
157
|
window.form_token = formTokens[formVariant];
|
|
186
|
-
bumpGeneration(formVariant);
|
|
187
158
|
leadCaptureLoader.reload(formVariant).then(() => {
|
|
188
159
|
if (!isMountedRef.current) return;
|
|
189
160
|
leadCaptureLoader.forceSetOwner(containerId);
|
|
@@ -199,7 +170,7 @@ function LeadCaptureForm({ formVariant, formTokens, scriptUrl, usageContext, isM
|
|
|
199
170
|
* already reloaded) is a no-op.
|
|
200
171
|
*/
|
|
201
172
|
(0, react.useEffect)(() => {
|
|
202
|
-
mountGenRef.current =
|
|
173
|
+
mountGenRef.current = leadCaptureLoader.getGeneration();
|
|
203
174
|
}, [formVariant]);
|
|
204
175
|
/**
|
|
205
176
|
* Cleanup on unmount only.
|
|
@@ -213,8 +184,7 @@ function LeadCaptureForm({ formVariant, formTokens, scriptUrl, usageContext, isM
|
|
|
213
184
|
if (usageContext === "onPage") {
|
|
214
185
|
const gen = mountGenRef.current;
|
|
215
186
|
setTimeout(() => {
|
|
216
|
-
|
|
217
|
-
leadCaptureLoader.unload();
|
|
187
|
+
leadCaptureLoader.unload(gen);
|
|
218
188
|
}, 100);
|
|
219
189
|
}
|
|
220
190
|
};
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":["ScriptLoader","useState","useRef"],"sources":["../src/index.tsx"],"sourcesContent":["/**\n * @packageDocumentation\n * LeadCapture IO form integration for Next.js — a variant-switching,\n * ownership-arbitrated `LeadCaptureForm` component built on\n * `@silverassist/next-script-loader`.\n */\n\n\"use client\";\n\nimport { ScriptLoader } from \"@silverassist/next-script-loader\";\nimport { useEffect, useRef, useState } from \"react\";\n\nexport type UsageContext = \"modal\" | \"onPage\";\n\n/**\n * Module-level singleton: every `LeadCaptureForm` instance on the page\n * shares one loader. `ScriptLoader` tracks a single active variant at a\n * time — switching variants tears down the previous one — which matches\n * how this form is actually used in the fleet (one device/territory\n * variant active per page). A page that genuinely needs two different\n * variants loaded simultaneously (e.g. a modal on \"desktop\" and an on-page\n * form on \"mobile\" at once) isn't supported by this shared instance; see\n * the README for the workaround.\n */\nexport const leadCaptureLoader = new ScriptLoader();\n\n/**\n * Generation counter per variant, incremented on every {@link ScriptLoader.load}/\n * {@link ScriptLoader.reload} call. A delayed on-unmount cleanup captures the\n * generation at mount time and skips its `unload()` call if the generation has\n * since advanced — i.e. a new mount already reloaded the script before the old\n * mount's delayed cleanup ran (a page-navigation remount, not a real teardown).\n * `ScriptLoader`'s own ref-counting handles the common case; this guards the\n * one case it doesn't: `reload()` doesn't change the reference count, so a\n * stale `unload()` after a `reload()` could drop the count to zero and tear\n * down a script a fresh mount is now depending on.\n */\nconst generationByVariant = new Map<string, number>();\n\nfunction bumpGeneration(variant: string): number {\n const next = (generationByVariant.get(variant) ?? 0) + 1;\n generationByVariant.set(variant, next);\n return next;\n}\n\nfunction currentGeneration(variant: string): number {\n return generationByVariant.get(variant) ?? 0;\n}\n\nexport interface LeadCaptureFormProps {\n /**\n * Form variant to render (e.g. \"desktop\", \"mobile\", \"itt\", \"oot\"). Must\n * match a key configured in `formTokens`.\n */\n formVariant: string;\n\n /** Map of variant names to LeadCapture IO form tokens. */\n formTokens: Record<string, string>;\n\n /** Script URL override, if not using LeadCapture IO's default CDN. */\n scriptUrl?: string;\n\n /**\n * Usage context — determines loading behavior.\n * - `modal`: loads when the modal opens\n * - `onPage`: loads when in viewport, after minimal interaction\n */\n usageContext: UsageContext;\n\n /** Controls whether the modal is open (only relevant for `usageContext=\"modal\"`). */\n isModalOpen: boolean;\n\n /** Optional additional CSS classes. */\n className?: string;\n\n /**\n * Optional embed target id for the inner `.leadforms-embd-form` div. Must\n * match the WordPress `embed_target_id` when the site sources form\n * placement from WordPress.\n */\n embedTargetId?: string;\n}\n\nconst DEFAULT_LEADCAPTURE_SCRIPT_URL = \"https://api.useleadbot.com/lead-bots/get-pixel-script.js\";\n\n/**\n * LeadCaptureForm — renders a LeadCapture IO form with support for\n * configurable variants, built on `@silverassist/next-script-loader`'s\n * singleton, reference-counted, ownership-arbitrated script lifecycle.\n *\n * A single form can render in multiple DOM locations via the\n * `.leadforms-embd-form` class — LeadCapture IO's script populates every\n * matching div once it loads, it doesn't re-run per div.\n *\n * @example\n * ```tsx\n * // Modal usage\n * <LeadCaptureForm\n * formVariant=\"desktop\"\n * formTokens={{ desktop: \"GLFT-XXXX\", mobile: \"GLFT-YYYY\" }}\n * usageContext=\"modal\"\n * isModalOpen={isOpen}\n * />\n *\n * // On-page usage\n * <LeadCaptureForm\n * formVariant=\"mobile\"\n * formTokens={{ desktop: \"GLFT-XXXX\", mobile: \"GLFT-YYYY\" }}\n * usageContext=\"onPage\"\n * isModalOpen={true}\n * />\n * ```\n */\nexport default function LeadCaptureForm({\n formVariant,\n formTokens,\n scriptUrl,\n usageContext,\n isModalOpen,\n className = \"\",\n embedTargetId,\n}: LeadCaptureFormProps) {\n const [isInViewport, setIsInViewport] = useState(false);\n const formRef = useRef<HTMLDivElement>(null);\n const mountGenRef = useRef<number>(0);\n const isMountedRef = useRef<boolean>(true);\n const hasHandledRemountRef = useRef(false);\n const containerId = `leadcapture-container-${formVariant}-${usageContext}`;\n\n useEffect(() => {\n isMountedRef.current = true;\n return () => {\n isMountedRef.current = false;\n };\n }, []);\n\n useEffect(() => {\n leadCaptureLoader.configure({\n urls: { [formVariant]: scriptUrl ?? DEFAULT_LEADCAPTURE_SCRIPT_URL },\n });\n }, [formVariant, scriptUrl]);\n\n /**\n * Intersection Observer for onPage forms — loads when near viewport.\n */\n useEffect(() => {\n if (usageContext !== \"onPage\" || !formRef.current) return;\n\n const observer = new IntersectionObserver(\n (entries) => {\n entries.forEach((entry) => {\n if (entry.isIntersecting) {\n setIsInViewport(true);\n observer.disconnect();\n }\n });\n },\n { rootMargin: \"100px\", threshold: 0.1 },\n );\n\n observer.observe(formRef.current);\n return () => observer.disconnect();\n }, [usageContext]);\n\n /**\n * Script loading with minimal interaction pattern.\n * - Modal: loads immediately when the modal opens.\n * - OnPage: loads on the first of focus/mousemove/scroll/touchstart, once\n * near viewport.\n */\n useEffect(() => {\n const shouldLoad = usageContext === \"modal\" ? isModalOpen === true : isInViewport;\n\n if (!shouldLoad) return;\n\n const load = () => {\n // LeadCapture IO serves one shared script for every variant and reads\n // which form to render from a global set just before the script\n // loads, rather than varying the script URL itself per variant.\n (window as Window & { form_token?: string }).form_token = formTokens[formVariant];\n\n bumpGeneration(formVariant);\n leadCaptureLoader\n .load(formVariant)\n .then(() => {\n if (!isMountedRef.current) return;\n leadCaptureLoader.forceSetOwner(containerId);\n })\n .catch(() => {\n // Silently degrade — the surrounding page stays usable without\n // the embed.\n });\n };\n\n if (usageContext === \"modal\") {\n load();\n return;\n }\n\n const events = [\"focus\", \"mousemove\", \"scroll\", \"touchstart\"] as const;\n const loadOnce = () => {\n load();\n events.forEach((event) => document.removeEventListener(event, loadOnce));\n };\n events.forEach((event) => {\n document.addEventListener(event, loadOnce, { once: true });\n });\n\n return () => {\n events.forEach((event) => document.removeEventListener(event, loadOnce));\n };\n }, [isModalOpen, isInViewport, formVariant, usageContext, containerId, formTokens]);\n\n /**\n * Handles a client-side navigation remount. The vendor script only scans\n * the DOM for `.leadforms-embd-form` divs once, when it first loads (see\n * the container comment below) -- it never repopulates a div added by a\n * later mount. Without this, a second page's form waits forever for a\n * *fresh* minimal-interaction event, which the click that triggered the\n * navigation doesn't itself produce (no new focus/mousemove/scroll/\n * touchstart fires on the new page unless the user moves again). Detect\n * that case immediately, using this variant's own load history instead\n * of the interaction gate above.\n *\n * Guarded by `hasHandledRemountRef` so this can only ever act once per\n * real component instance: `reload()` tears down and recreates the\n * `<script>` element, and removing a `<script>` doesn't reliably cancel\n * its in-flight network request, so calling it twice in a row for the\n * same mount (e.g. React Strict Mode's dev-only effect-cleanup-effect\n * replay, which reuses this same ref) can let both the superseded and\n * the current script execute and each populate the container, rendering\n * the widget twice. Claiming ownership isn't a substitute for this guard\n * -- `setOwner` is idempotent for the same id, so a second call from the\n * same instance succeeds too.\n */\n useEffect(() => {\n if (usageContext !== \"onPage\") return;\n if (hasHandledRemountRef.current) return;\n if (currentGeneration(formVariant) === 0) return;\n if (!leadCaptureLoader.setOwner(containerId)) return;\n\n const container = formRef.current?.querySelector(\".leadforms-embd-form\");\n if (!container || container.children.length > 0) return;\n\n hasHandledRemountRef.current = true;\n (window as Window & { form_token?: string }).form_token = formTokens[formVariant];\n bumpGeneration(formVariant);\n leadCaptureLoader\n .reload(formVariant)\n .then(() => {\n if (!isMountedRef.current) return;\n leadCaptureLoader.forceSetOwner(containerId);\n })\n .catch(() => {\n // Silently degrade -- the surrounding page stays usable without the embed.\n });\n }, [formVariant, containerId, usageContext]);\n\n /**\n * Capture the current generation at mount time — passed to the delayed\n * unload on cleanup so a stale unmount (superseded by a fresh mount that\n * already reloaded) is a no-op.\n */\n useEffect(() => {\n mountGenRef.current = currentGeneration(formVariant);\n }, [formVariant]);\n\n /**\n * Cleanup on unmount only.\n * Modal: only releases ownership, doesn't unload the script.\n * OnPage: releases ownership and unloads after a short delay, skipped if\n * a newer mount has already reloaded the script in the meantime.\n */\n useEffect(() => {\n return () => {\n leadCaptureLoader.releaseOwnership(containerId);\n\n if (usageContext === \"onPage\") {\n const gen = mountGenRef.current;\n setTimeout(() => {\n if (gen < currentGeneration(formVariant)) return;\n leadCaptureLoader.unload();\n }, 100);\n }\n };\n }, []);\n\n return (\n <div ref={formRef} id={containerId} className={className}>\n {/*\n LeadCapture IO embed container.\n\n CRITICAL: this div must exist BEFORE the script loads -- LeadCapture IO\n only populates `.leadforms-embd-form` divs present at load time, it\n doesn't detect ones added later.\n */}\n <div className=\"leadforms-embd-form\" {...(embedTargetId ? { id: embedTargetId } : {})}>\n {/* Form renders here */}\n </div>\n </div>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","names":["ScriptLoader","useState","useRef"],"sources":["../src/index.tsx"],"sourcesContent":["/**\n * @packageDocumentation\n * LeadCapture IO form integration for Next.js — a variant-switching,\n * ownership-arbitrated `LeadCaptureForm` component built on\n * `@silverassist/next-script-loader`.\n */\n\n\"use client\";\n\nimport { ScriptLoader } from \"@silverassist/next-script-loader\";\nimport { useEffect, useRef, useState } from \"react\";\n\nexport type UsageContext = \"modal\" | \"onPage\";\n\n/**\n * Module-level singleton: every `LeadCaptureForm` instance on the page\n * shares one loader. `ScriptLoader` tracks a single active variant at a\n * time — switching variants tears down the previous one — which matches\n * how this form is actually used in the fleet (one device/territory\n * variant active per page). A page that genuinely needs two different\n * variants loaded simultaneously (e.g. a modal on \"desktop\" and an on-page\n * form on \"mobile\" at once) isn't supported by this shared instance; see\n * the README for the workaround.\n */\nexport const leadCaptureLoader = new ScriptLoader();\n\nexport interface LeadCaptureFormProps {\n /**\n * Form variant to render (e.g. \"desktop\", \"mobile\", \"itt\", \"oot\"). Must\n * match a key configured in `formTokens`.\n */\n formVariant: string;\n\n /** Map of variant names to LeadCapture IO form tokens. */\n formTokens: Record<string, string>;\n\n /** Script URL override, if not using LeadCapture IO's default CDN. */\n scriptUrl?: string;\n\n /**\n * Usage context — determines loading behavior.\n * - `modal`: loads when the modal opens\n * - `onPage`: loads when in viewport, after minimal interaction\n */\n usageContext: UsageContext;\n\n /** Controls whether the modal is open (only relevant for `usageContext=\"modal\"`). */\n isModalOpen: boolean;\n\n /** Optional additional CSS classes. */\n className?: string;\n\n /**\n * Optional embed target id for the inner `.leadforms-embd-form` div. Must\n * match the WordPress `embed_target_id` when the site sources form\n * placement from WordPress.\n */\n embedTargetId?: string;\n}\n\nconst DEFAULT_LEADCAPTURE_SCRIPT_URL = \"https://api.useleadbot.com/lead-bots/get-pixel-script.js\";\n\n/**\n * LeadCaptureForm — renders a LeadCapture IO form with support for\n * configurable variants, built on `@silverassist/next-script-loader`'s\n * singleton, reference-counted, ownership-arbitrated script lifecycle.\n *\n * A single form can render in multiple DOM locations via the\n * `.leadforms-embd-form` class — LeadCapture IO's script populates every\n * matching div once it loads, it doesn't re-run per div.\n *\n * @example\n * ```tsx\n * // Modal usage\n * <LeadCaptureForm\n * formVariant=\"desktop\"\n * formTokens={{ desktop: \"GLFT-XXXX\", mobile: \"GLFT-YYYY\" }}\n * usageContext=\"modal\"\n * isModalOpen={isOpen}\n * />\n *\n * // On-page usage\n * <LeadCaptureForm\n * formVariant=\"mobile\"\n * formTokens={{ desktop: \"GLFT-XXXX\", mobile: \"GLFT-YYYY\" }}\n * usageContext=\"onPage\"\n * isModalOpen={true}\n * />\n * ```\n */\nexport default function LeadCaptureForm({\n formVariant,\n formTokens,\n scriptUrl,\n usageContext,\n isModalOpen,\n className = \"\",\n embedTargetId,\n}: LeadCaptureFormProps) {\n const [isInViewport, setIsInViewport] = useState(false);\n const formRef = useRef<HTMLDivElement>(null);\n const mountGenRef = useRef<number>(0);\n const isMountedRef = useRef<boolean>(true);\n const containerId = `leadcapture-container-${formVariant}-${usageContext}`;\n\n useEffect(() => {\n isMountedRef.current = true;\n return () => {\n isMountedRef.current = false;\n };\n }, []);\n\n useEffect(() => {\n leadCaptureLoader.configure({\n urls: { [formVariant]: scriptUrl ?? DEFAULT_LEADCAPTURE_SCRIPT_URL },\n });\n }, [formVariant, scriptUrl]);\n\n /**\n * Intersection Observer for onPage forms — loads when near viewport.\n */\n useEffect(() => {\n if (usageContext !== \"onPage\" || !formRef.current) return;\n\n const observer = new IntersectionObserver(\n (entries) => {\n entries.forEach((entry) => {\n if (entry.isIntersecting) {\n setIsInViewport(true);\n observer.disconnect();\n }\n });\n },\n { rootMargin: \"100px\", threshold: 0.1 },\n );\n\n observer.observe(formRef.current);\n return () => observer.disconnect();\n }, [usageContext]);\n\n /**\n * Script loading with minimal interaction pattern.\n * - Modal: loads immediately when the modal opens.\n * - OnPage: loads on the first of focus/mousemove/scroll/touchstart, once\n * near viewport.\n */\n useEffect(() => {\n const shouldLoad = usageContext === \"modal\" ? isModalOpen === true : isInViewport;\n\n if (!shouldLoad) return;\n\n const load = () => {\n // LeadCapture IO serves one shared script for every variant and reads\n // which form to render from a global set just before the script\n // loads, rather than varying the script URL itself per variant.\n (window as Window & { form_token?: string }).form_token = formTokens[formVariant];\n\n leadCaptureLoader\n .load(formVariant)\n .then(() => {\n if (!isMountedRef.current) return;\n leadCaptureLoader.forceSetOwner(containerId);\n })\n .catch(() => {\n // Silently degrade — the surrounding page stays usable without\n // the embed.\n });\n };\n\n if (usageContext === \"modal\") {\n load();\n return;\n }\n\n const events = [\"focus\", \"mousemove\", \"scroll\", \"touchstart\"] as const;\n const loadOnce = () => {\n load();\n events.forEach((event) => document.removeEventListener(event, loadOnce));\n };\n events.forEach((event) => {\n document.addEventListener(event, loadOnce, { once: true });\n });\n\n return () => {\n events.forEach((event) => document.removeEventListener(event, loadOnce));\n };\n }, [isModalOpen, isInViewport, formVariant, usageContext, containerId, formTokens]);\n\n /**\n * Handles a client-side navigation remount. The vendor script only scans\n * the DOM for `.leadforms-embd-form` divs once, when it first loads (see\n * the container comment below) -- it never repopulates a div added by a\n * later mount. Without this, a second page's form waits forever for a\n * *fresh* minimal-interaction event, which the click that triggered the\n * navigation doesn't itself produce (no new focus/mousemove/scroll/\n * touchstart fires on the new page unless the user moves again). Detect\n * that case immediately, using this variant's own load history instead\n * of the interaction gate above.\n *\n * Safe to call on every render this condition holds, including twice in\n * a row for the same mount under React Strict Mode's dev-only effect\n * replay: `ScriptLoader.reload()` (0.1.1+) shares the in-flight promise\n * for a same-variant reload already in progress instead of tearing the\n * script down again, so a second call here is a no-op rather than a\n * second script execution.\n */\n useEffect(() => {\n if (usageContext !== \"onPage\") return;\n if (leadCaptureLoader.getGeneration() === 0) return;\n if (!leadCaptureLoader.setOwner(containerId)) return;\n\n const container = formRef.current?.querySelector(\".leadforms-embd-form\");\n if (!container || container.children.length > 0) return;\n\n (window as Window & { form_token?: string }).form_token = formTokens[formVariant];\n leadCaptureLoader\n .reload(formVariant)\n .then(() => {\n if (!isMountedRef.current) return;\n leadCaptureLoader.forceSetOwner(containerId);\n })\n .catch(() => {\n // Silently degrade -- the surrounding page stays usable without the embed.\n });\n }, [formVariant, containerId, usageContext]);\n\n /**\n * Capture the current generation at mount time — passed to the delayed\n * unload on cleanup so a stale unmount (superseded by a fresh mount that\n * already reloaded) is a no-op.\n */\n useEffect(() => {\n mountGenRef.current = leadCaptureLoader.getGeneration();\n }, [formVariant]);\n\n /**\n * Cleanup on unmount only.\n * Modal: only releases ownership, doesn't unload the script.\n * OnPage: releases ownership and unloads after a short delay, skipped if\n * a newer mount has already reloaded the script in the meantime.\n */\n useEffect(() => {\n return () => {\n leadCaptureLoader.releaseOwnership(containerId);\n\n if (usageContext === \"onPage\") {\n const gen = mountGenRef.current;\n setTimeout(() => {\n leadCaptureLoader.unload(gen);\n }, 100);\n }\n };\n }, []);\n\n return (\n <div ref={formRef} id={containerId} className={className}>\n {/*\n LeadCapture IO embed container.\n\n CRITICAL: this div must exist BEFORE the script loads -- LeadCapture IO\n only populates `.leadforms-embd-form` divs present at load time, it\n doesn't detect ones added later.\n */}\n <div className=\"leadforms-embd-form\" {...(embedTargetId ? { id: embedTargetId } : {})}>\n {/* Form renders here */}\n </div>\n </div>\n );\n}\n"],"mappings":"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"}
|
package/dist/index.mjs
CHANGED
|
@@ -23,26 +23,6 @@ import { jsx } from "react/jsx-runtime";
|
|
|
23
23
|
* the README for the workaround.
|
|
24
24
|
*/
|
|
25
25
|
const leadCaptureLoader = new ScriptLoader();
|
|
26
|
-
/**
|
|
27
|
-
* Generation counter per variant, incremented on every {@link ScriptLoader.load}/
|
|
28
|
-
* {@link ScriptLoader.reload} call. A delayed on-unmount cleanup captures the
|
|
29
|
-
* generation at mount time and skips its `unload()` call if the generation has
|
|
30
|
-
* since advanced — i.e. a new mount already reloaded the script before the old
|
|
31
|
-
* mount's delayed cleanup ran (a page-navigation remount, not a real teardown).
|
|
32
|
-
* `ScriptLoader`'s own ref-counting handles the common case; this guards the
|
|
33
|
-
* one case it doesn't: `reload()` doesn't change the reference count, so a
|
|
34
|
-
* stale `unload()` after a `reload()` could drop the count to zero and tear
|
|
35
|
-
* down a script a fresh mount is now depending on.
|
|
36
|
-
*/
|
|
37
|
-
const generationByVariant = /* @__PURE__ */ new Map();
|
|
38
|
-
function bumpGeneration(variant) {
|
|
39
|
-
const next = (generationByVariant.get(variant) ?? 0) + 1;
|
|
40
|
-
generationByVariant.set(variant, next);
|
|
41
|
-
return next;
|
|
42
|
-
}
|
|
43
|
-
function currentGeneration(variant) {
|
|
44
|
-
return generationByVariant.get(variant) ?? 0;
|
|
45
|
-
}
|
|
46
26
|
const DEFAULT_LEADCAPTURE_SCRIPT_URL = "https://api.useleadbot.com/lead-bots/get-pixel-script.js";
|
|
47
27
|
/**
|
|
48
28
|
* LeadCaptureForm — renders a LeadCapture IO form with support for
|
|
@@ -77,7 +57,6 @@ function LeadCaptureForm({ formVariant, formTokens, scriptUrl, usageContext, isM
|
|
|
77
57
|
const formRef = useRef(null);
|
|
78
58
|
const mountGenRef = useRef(0);
|
|
79
59
|
const isMountedRef = useRef(true);
|
|
80
|
-
const hasHandledRemountRef = useRef(false);
|
|
81
60
|
const containerId = `leadcapture-container-${formVariant}-${usageContext}`;
|
|
82
61
|
useEffect(() => {
|
|
83
62
|
isMountedRef.current = true;
|
|
@@ -117,7 +96,6 @@ function LeadCaptureForm({ formVariant, formTokens, scriptUrl, usageContext, isM
|
|
|
117
96
|
if (!(usageContext === "modal" ? isModalOpen === true : isInViewport)) return;
|
|
118
97
|
const load = () => {
|
|
119
98
|
window.form_token = formTokens[formVariant];
|
|
120
|
-
bumpGeneration(formVariant);
|
|
121
99
|
leadCaptureLoader.load(formVariant).then(() => {
|
|
122
100
|
if (!isMountedRef.current) return;
|
|
123
101
|
leadCaptureLoader.forceSetOwner(containerId);
|
|
@@ -162,27 +140,20 @@ function LeadCaptureForm({ formVariant, formTokens, scriptUrl, usageContext, isM
|
|
|
162
140
|
* that case immediately, using this variant's own load history instead
|
|
163
141
|
* of the interaction gate above.
|
|
164
142
|
*
|
|
165
|
-
*
|
|
166
|
-
*
|
|
167
|
-
*
|
|
168
|
-
*
|
|
169
|
-
*
|
|
170
|
-
*
|
|
171
|
-
* the current script execute and each populate the container, rendering
|
|
172
|
-
* the widget twice. Claiming ownership isn't a substitute for this guard
|
|
173
|
-
* -- `setOwner` is idempotent for the same id, so a second call from the
|
|
174
|
-
* same instance succeeds too.
|
|
143
|
+
* Safe to call on every render this condition holds, including twice in
|
|
144
|
+
* a row for the same mount under React Strict Mode's dev-only effect
|
|
145
|
+
* replay: `ScriptLoader.reload()` (0.1.1+) shares the in-flight promise
|
|
146
|
+
* for a same-variant reload already in progress instead of tearing the
|
|
147
|
+
* script down again, so a second call here is a no-op rather than a
|
|
148
|
+
* second script execution.
|
|
175
149
|
*/
|
|
176
150
|
useEffect(() => {
|
|
177
151
|
if (usageContext !== "onPage") return;
|
|
178
|
-
if (
|
|
179
|
-
if (currentGeneration(formVariant) === 0) return;
|
|
152
|
+
if (leadCaptureLoader.getGeneration() === 0) return;
|
|
180
153
|
if (!leadCaptureLoader.setOwner(containerId)) return;
|
|
181
154
|
const container = formRef.current?.querySelector(".leadforms-embd-form");
|
|
182
155
|
if (!container || container.children.length > 0) return;
|
|
183
|
-
hasHandledRemountRef.current = true;
|
|
184
156
|
window.form_token = formTokens[formVariant];
|
|
185
|
-
bumpGeneration(formVariant);
|
|
186
157
|
leadCaptureLoader.reload(formVariant).then(() => {
|
|
187
158
|
if (!isMountedRef.current) return;
|
|
188
159
|
leadCaptureLoader.forceSetOwner(containerId);
|
|
@@ -198,7 +169,7 @@ function LeadCaptureForm({ formVariant, formTokens, scriptUrl, usageContext, isM
|
|
|
198
169
|
* already reloaded) is a no-op.
|
|
199
170
|
*/
|
|
200
171
|
useEffect(() => {
|
|
201
|
-
mountGenRef.current =
|
|
172
|
+
mountGenRef.current = leadCaptureLoader.getGeneration();
|
|
202
173
|
}, [formVariant]);
|
|
203
174
|
/**
|
|
204
175
|
* Cleanup on unmount only.
|
|
@@ -212,8 +183,7 @@ function LeadCaptureForm({ formVariant, formTokens, scriptUrl, usageContext, isM
|
|
|
212
183
|
if (usageContext === "onPage") {
|
|
213
184
|
const gen = mountGenRef.current;
|
|
214
185
|
setTimeout(() => {
|
|
215
|
-
|
|
216
|
-
leadCaptureLoader.unload();
|
|
186
|
+
leadCaptureLoader.unload(gen);
|
|
217
187
|
}, 100);
|
|
218
188
|
}
|
|
219
189
|
};
|
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","names":[],"sources":["../src/index.tsx"],"sourcesContent":["/**\n * @packageDocumentation\n * LeadCapture IO form integration for Next.js — a variant-switching,\n * ownership-arbitrated `LeadCaptureForm` component built on\n * `@silverassist/next-script-loader`.\n */\n\n\"use client\";\n\nimport { ScriptLoader } from \"@silverassist/next-script-loader\";\nimport { useEffect, useRef, useState } from \"react\";\n\nexport type UsageContext = \"modal\" | \"onPage\";\n\n/**\n * Module-level singleton: every `LeadCaptureForm` instance on the page\n * shares one loader. `ScriptLoader` tracks a single active variant at a\n * time — switching variants tears down the previous one — which matches\n * how this form is actually used in the fleet (one device/territory\n * variant active per page). A page that genuinely needs two different\n * variants loaded simultaneously (e.g. a modal on \"desktop\" and an on-page\n * form on \"mobile\" at once) isn't supported by this shared instance; see\n * the README for the workaround.\n */\nexport const leadCaptureLoader = new ScriptLoader();\n\n/**\n * Generation counter per variant, incremented on every {@link ScriptLoader.load}/\n * {@link ScriptLoader.reload} call. A delayed on-unmount cleanup captures the\n * generation at mount time and skips its `unload()` call if the generation has\n * since advanced — i.e. a new mount already reloaded the script before the old\n * mount's delayed cleanup ran (a page-navigation remount, not a real teardown).\n * `ScriptLoader`'s own ref-counting handles the common case; this guards the\n * one case it doesn't: `reload()` doesn't change the reference count, so a\n * stale `unload()` after a `reload()` could drop the count to zero and tear\n * down a script a fresh mount is now depending on.\n */\nconst generationByVariant = new Map<string, number>();\n\nfunction bumpGeneration(variant: string): number {\n const next = (generationByVariant.get(variant) ?? 0) + 1;\n generationByVariant.set(variant, next);\n return next;\n}\n\nfunction currentGeneration(variant: string): number {\n return generationByVariant.get(variant) ?? 0;\n}\n\nexport interface LeadCaptureFormProps {\n /**\n * Form variant to render (e.g. \"desktop\", \"mobile\", \"itt\", \"oot\"). Must\n * match a key configured in `formTokens`.\n */\n formVariant: string;\n\n /** Map of variant names to LeadCapture IO form tokens. */\n formTokens: Record<string, string>;\n\n /** Script URL override, if not using LeadCapture IO's default CDN. */\n scriptUrl?: string;\n\n /**\n * Usage context — determines loading behavior.\n * - `modal`: loads when the modal opens\n * - `onPage`: loads when in viewport, after minimal interaction\n */\n usageContext: UsageContext;\n\n /** Controls whether the modal is open (only relevant for `usageContext=\"modal\"`). */\n isModalOpen: boolean;\n\n /** Optional additional CSS classes. */\n className?: string;\n\n /**\n * Optional embed target id for the inner `.leadforms-embd-form` div. Must\n * match the WordPress `embed_target_id` when the site sources form\n * placement from WordPress.\n */\n embedTargetId?: string;\n}\n\nconst DEFAULT_LEADCAPTURE_SCRIPT_URL = \"https://api.useleadbot.com/lead-bots/get-pixel-script.js\";\n\n/**\n * LeadCaptureForm — renders a LeadCapture IO form with support for\n * configurable variants, built on `@silverassist/next-script-loader`'s\n * singleton, reference-counted, ownership-arbitrated script lifecycle.\n *\n * A single form can render in multiple DOM locations via the\n * `.leadforms-embd-form` class — LeadCapture IO's script populates every\n * matching div once it loads, it doesn't re-run per div.\n *\n * @example\n * ```tsx\n * // Modal usage\n * <LeadCaptureForm\n * formVariant=\"desktop\"\n * formTokens={{ desktop: \"GLFT-XXXX\", mobile: \"GLFT-YYYY\" }}\n * usageContext=\"modal\"\n * isModalOpen={isOpen}\n * />\n *\n * // On-page usage\n * <LeadCaptureForm\n * formVariant=\"mobile\"\n * formTokens={{ desktop: \"GLFT-XXXX\", mobile: \"GLFT-YYYY\" }}\n * usageContext=\"onPage\"\n * isModalOpen={true}\n * />\n * ```\n */\nexport default function LeadCaptureForm({\n formVariant,\n formTokens,\n scriptUrl,\n usageContext,\n isModalOpen,\n className = \"\",\n embedTargetId,\n}: LeadCaptureFormProps) {\n const [isInViewport, setIsInViewport] = useState(false);\n const formRef = useRef<HTMLDivElement>(null);\n const mountGenRef = useRef<number>(0);\n const isMountedRef = useRef<boolean>(true);\n const hasHandledRemountRef = useRef(false);\n const containerId = `leadcapture-container-${formVariant}-${usageContext}`;\n\n useEffect(() => {\n isMountedRef.current = true;\n return () => {\n isMountedRef.current = false;\n };\n }, []);\n\n useEffect(() => {\n leadCaptureLoader.configure({\n urls: { [formVariant]: scriptUrl ?? DEFAULT_LEADCAPTURE_SCRIPT_URL },\n });\n }, [formVariant, scriptUrl]);\n\n /**\n * Intersection Observer for onPage forms — loads when near viewport.\n */\n useEffect(() => {\n if (usageContext !== \"onPage\" || !formRef.current) return;\n\n const observer = new IntersectionObserver(\n (entries) => {\n entries.forEach((entry) => {\n if (entry.isIntersecting) {\n setIsInViewport(true);\n observer.disconnect();\n }\n });\n },\n { rootMargin: \"100px\", threshold: 0.1 },\n );\n\n observer.observe(formRef.current);\n return () => observer.disconnect();\n }, [usageContext]);\n\n /**\n * Script loading with minimal interaction pattern.\n * - Modal: loads immediately when the modal opens.\n * - OnPage: loads on the first of focus/mousemove/scroll/touchstart, once\n * near viewport.\n */\n useEffect(() => {\n const shouldLoad = usageContext === \"modal\" ? isModalOpen === true : isInViewport;\n\n if (!shouldLoad) return;\n\n const load = () => {\n // LeadCapture IO serves one shared script for every variant and reads\n // which form to render from a global set just before the script\n // loads, rather than varying the script URL itself per variant.\n (window as Window & { form_token?: string }).form_token = formTokens[formVariant];\n\n bumpGeneration(formVariant);\n leadCaptureLoader\n .load(formVariant)\n .then(() => {\n if (!isMountedRef.current) return;\n leadCaptureLoader.forceSetOwner(containerId);\n })\n .catch(() => {\n // Silently degrade — the surrounding page stays usable without\n // the embed.\n });\n };\n\n if (usageContext === \"modal\") {\n load();\n return;\n }\n\n const events = [\"focus\", \"mousemove\", \"scroll\", \"touchstart\"] as const;\n const loadOnce = () => {\n load();\n events.forEach((event) => document.removeEventListener(event, loadOnce));\n };\n events.forEach((event) => {\n document.addEventListener(event, loadOnce, { once: true });\n });\n\n return () => {\n events.forEach((event) => document.removeEventListener(event, loadOnce));\n };\n }, [isModalOpen, isInViewport, formVariant, usageContext, containerId, formTokens]);\n\n /**\n * Handles a client-side navigation remount. The vendor script only scans\n * the DOM for `.leadforms-embd-form` divs once, when it first loads (see\n * the container comment below) -- it never repopulates a div added by a\n * later mount. Without this, a second page's form waits forever for a\n * *fresh* minimal-interaction event, which the click that triggered the\n * navigation doesn't itself produce (no new focus/mousemove/scroll/\n * touchstart fires on the new page unless the user moves again). Detect\n * that case immediately, using this variant's own load history instead\n * of the interaction gate above.\n *\n * Guarded by `hasHandledRemountRef` so this can only ever act once per\n * real component instance: `reload()` tears down and recreates the\n * `<script>` element, and removing a `<script>` doesn't reliably cancel\n * its in-flight network request, so calling it twice in a row for the\n * same mount (e.g. React Strict Mode's dev-only effect-cleanup-effect\n * replay, which reuses this same ref) can let both the superseded and\n * the current script execute and each populate the container, rendering\n * the widget twice. Claiming ownership isn't a substitute for this guard\n * -- `setOwner` is idempotent for the same id, so a second call from the\n * same instance succeeds too.\n */\n useEffect(() => {\n if (usageContext !== \"onPage\") return;\n if (hasHandledRemountRef.current) return;\n if (currentGeneration(formVariant) === 0) return;\n if (!leadCaptureLoader.setOwner(containerId)) return;\n\n const container = formRef.current?.querySelector(\".leadforms-embd-form\");\n if (!container || container.children.length > 0) return;\n\n hasHandledRemountRef.current = true;\n (window as Window & { form_token?: string }).form_token = formTokens[formVariant];\n bumpGeneration(formVariant);\n leadCaptureLoader\n .reload(formVariant)\n .then(() => {\n if (!isMountedRef.current) return;\n leadCaptureLoader.forceSetOwner(containerId);\n })\n .catch(() => {\n // Silently degrade -- the surrounding page stays usable without the embed.\n });\n }, [formVariant, containerId, usageContext]);\n\n /**\n * Capture the current generation at mount time — passed to the delayed\n * unload on cleanup so a stale unmount (superseded by a fresh mount that\n * already reloaded) is a no-op.\n */\n useEffect(() => {\n mountGenRef.current = currentGeneration(formVariant);\n }, [formVariant]);\n\n /**\n * Cleanup on unmount only.\n * Modal: only releases ownership, doesn't unload the script.\n * OnPage: releases ownership and unloads after a short delay, skipped if\n * a newer mount has already reloaded the script in the meantime.\n */\n useEffect(() => {\n return () => {\n leadCaptureLoader.releaseOwnership(containerId);\n\n if (usageContext === \"onPage\") {\n const gen = mountGenRef.current;\n setTimeout(() => {\n if (gen < currentGeneration(formVariant)) return;\n leadCaptureLoader.unload();\n }, 100);\n }\n };\n }, []);\n\n return (\n <div ref={formRef} id={containerId} className={className}>\n {/*\n LeadCapture IO embed container.\n\n CRITICAL: this div must exist BEFORE the script loads -- LeadCapture IO\n only populates `.leadforms-embd-form` divs present at load time, it\n doesn't detect ones added later.\n */}\n <div className=\"leadforms-embd-form\" {...(embedTargetId ? { id: embedTargetId } : {})}>\n {/* Form renders here */}\n </div>\n </div>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.mjs","names":[],"sources":["../src/index.tsx"],"sourcesContent":["/**\n * @packageDocumentation\n * LeadCapture IO form integration for Next.js — a variant-switching,\n * ownership-arbitrated `LeadCaptureForm` component built on\n * `@silverassist/next-script-loader`.\n */\n\n\"use client\";\n\nimport { ScriptLoader } from \"@silverassist/next-script-loader\";\nimport { useEffect, useRef, useState } from \"react\";\n\nexport type UsageContext = \"modal\" | \"onPage\";\n\n/**\n * Module-level singleton: every `LeadCaptureForm` instance on the page\n * shares one loader. `ScriptLoader` tracks a single active variant at a\n * time — switching variants tears down the previous one — which matches\n * how this form is actually used in the fleet (one device/territory\n * variant active per page). A page that genuinely needs two different\n * variants loaded simultaneously (e.g. a modal on \"desktop\" and an on-page\n * form on \"mobile\" at once) isn't supported by this shared instance; see\n * the README for the workaround.\n */\nexport const leadCaptureLoader = new ScriptLoader();\n\nexport interface LeadCaptureFormProps {\n /**\n * Form variant to render (e.g. \"desktop\", \"mobile\", \"itt\", \"oot\"). Must\n * match a key configured in `formTokens`.\n */\n formVariant: string;\n\n /** Map of variant names to LeadCapture IO form tokens. */\n formTokens: Record<string, string>;\n\n /** Script URL override, if not using LeadCapture IO's default CDN. */\n scriptUrl?: string;\n\n /**\n * Usage context — determines loading behavior.\n * - `modal`: loads when the modal opens\n * - `onPage`: loads when in viewport, after minimal interaction\n */\n usageContext: UsageContext;\n\n /** Controls whether the modal is open (only relevant for `usageContext=\"modal\"`). */\n isModalOpen: boolean;\n\n /** Optional additional CSS classes. */\n className?: string;\n\n /**\n * Optional embed target id for the inner `.leadforms-embd-form` div. Must\n * match the WordPress `embed_target_id` when the site sources form\n * placement from WordPress.\n */\n embedTargetId?: string;\n}\n\nconst DEFAULT_LEADCAPTURE_SCRIPT_URL = \"https://api.useleadbot.com/lead-bots/get-pixel-script.js\";\n\n/**\n * LeadCaptureForm — renders a LeadCapture IO form with support for\n * configurable variants, built on `@silverassist/next-script-loader`'s\n * singleton, reference-counted, ownership-arbitrated script lifecycle.\n *\n * A single form can render in multiple DOM locations via the\n * `.leadforms-embd-form` class — LeadCapture IO's script populates every\n * matching div once it loads, it doesn't re-run per div.\n *\n * @example\n * ```tsx\n * // Modal usage\n * <LeadCaptureForm\n * formVariant=\"desktop\"\n * formTokens={{ desktop: \"GLFT-XXXX\", mobile: \"GLFT-YYYY\" }}\n * usageContext=\"modal\"\n * isModalOpen={isOpen}\n * />\n *\n * // On-page usage\n * <LeadCaptureForm\n * formVariant=\"mobile\"\n * formTokens={{ desktop: \"GLFT-XXXX\", mobile: \"GLFT-YYYY\" }}\n * usageContext=\"onPage\"\n * isModalOpen={true}\n * />\n * ```\n */\nexport default function LeadCaptureForm({\n formVariant,\n formTokens,\n scriptUrl,\n usageContext,\n isModalOpen,\n className = \"\",\n embedTargetId,\n}: LeadCaptureFormProps) {\n const [isInViewport, setIsInViewport] = useState(false);\n const formRef = useRef<HTMLDivElement>(null);\n const mountGenRef = useRef<number>(0);\n const isMountedRef = useRef<boolean>(true);\n const containerId = `leadcapture-container-${formVariant}-${usageContext}`;\n\n useEffect(() => {\n isMountedRef.current = true;\n return () => {\n isMountedRef.current = false;\n };\n }, []);\n\n useEffect(() => {\n leadCaptureLoader.configure({\n urls: { [formVariant]: scriptUrl ?? DEFAULT_LEADCAPTURE_SCRIPT_URL },\n });\n }, [formVariant, scriptUrl]);\n\n /**\n * Intersection Observer for onPage forms — loads when near viewport.\n */\n useEffect(() => {\n if (usageContext !== \"onPage\" || !formRef.current) return;\n\n const observer = new IntersectionObserver(\n (entries) => {\n entries.forEach((entry) => {\n if (entry.isIntersecting) {\n setIsInViewport(true);\n observer.disconnect();\n }\n });\n },\n { rootMargin: \"100px\", threshold: 0.1 },\n );\n\n observer.observe(formRef.current);\n return () => observer.disconnect();\n }, [usageContext]);\n\n /**\n * Script loading with minimal interaction pattern.\n * - Modal: loads immediately when the modal opens.\n * - OnPage: loads on the first of focus/mousemove/scroll/touchstart, once\n * near viewport.\n */\n useEffect(() => {\n const shouldLoad = usageContext === \"modal\" ? isModalOpen === true : isInViewport;\n\n if (!shouldLoad) return;\n\n const load = () => {\n // LeadCapture IO serves one shared script for every variant and reads\n // which form to render from a global set just before the script\n // loads, rather than varying the script URL itself per variant.\n (window as Window & { form_token?: string }).form_token = formTokens[formVariant];\n\n leadCaptureLoader\n .load(formVariant)\n .then(() => {\n if (!isMountedRef.current) return;\n leadCaptureLoader.forceSetOwner(containerId);\n })\n .catch(() => {\n // Silently degrade — the surrounding page stays usable without\n // the embed.\n });\n };\n\n if (usageContext === \"modal\") {\n load();\n return;\n }\n\n const events = [\"focus\", \"mousemove\", \"scroll\", \"touchstart\"] as const;\n const loadOnce = () => {\n load();\n events.forEach((event) => document.removeEventListener(event, loadOnce));\n };\n events.forEach((event) => {\n document.addEventListener(event, loadOnce, { once: true });\n });\n\n return () => {\n events.forEach((event) => document.removeEventListener(event, loadOnce));\n };\n }, [isModalOpen, isInViewport, formVariant, usageContext, containerId, formTokens]);\n\n /**\n * Handles a client-side navigation remount. The vendor script only scans\n * the DOM for `.leadforms-embd-form` divs once, when it first loads (see\n * the container comment below) -- it never repopulates a div added by a\n * later mount. Without this, a second page's form waits forever for a\n * *fresh* minimal-interaction event, which the click that triggered the\n * navigation doesn't itself produce (no new focus/mousemove/scroll/\n * touchstart fires on the new page unless the user moves again). Detect\n * that case immediately, using this variant's own load history instead\n * of the interaction gate above.\n *\n * Safe to call on every render this condition holds, including twice in\n * a row for the same mount under React Strict Mode's dev-only effect\n * replay: `ScriptLoader.reload()` (0.1.1+) shares the in-flight promise\n * for a same-variant reload already in progress instead of tearing the\n * script down again, so a second call here is a no-op rather than a\n * second script execution.\n */\n useEffect(() => {\n if (usageContext !== \"onPage\") return;\n if (leadCaptureLoader.getGeneration() === 0) return;\n if (!leadCaptureLoader.setOwner(containerId)) return;\n\n const container = formRef.current?.querySelector(\".leadforms-embd-form\");\n if (!container || container.children.length > 0) return;\n\n (window as Window & { form_token?: string }).form_token = formTokens[formVariant];\n leadCaptureLoader\n .reload(formVariant)\n .then(() => {\n if (!isMountedRef.current) return;\n leadCaptureLoader.forceSetOwner(containerId);\n })\n .catch(() => {\n // Silently degrade -- the surrounding page stays usable without the embed.\n });\n }, [formVariant, containerId, usageContext]);\n\n /**\n * Capture the current generation at mount time — passed to the delayed\n * unload on cleanup so a stale unmount (superseded by a fresh mount that\n * already reloaded) is a no-op.\n */\n useEffect(() => {\n mountGenRef.current = leadCaptureLoader.getGeneration();\n }, [formVariant]);\n\n /**\n * Cleanup on unmount only.\n * Modal: only releases ownership, doesn't unload the script.\n * OnPage: releases ownership and unloads after a short delay, skipped if\n * a newer mount has already reloaded the script in the meantime.\n */\n useEffect(() => {\n return () => {\n leadCaptureLoader.releaseOwnership(containerId);\n\n if (usageContext === \"onPage\") {\n const gen = mountGenRef.current;\n setTimeout(() => {\n leadCaptureLoader.unload(gen);\n }, 100);\n }\n };\n }, []);\n\n return (\n <div ref={formRef} id={containerId} className={className}>\n {/*\n LeadCapture IO embed container.\n\n CRITICAL: this div must exist BEFORE the script loads -- LeadCapture IO\n only populates `.leadforms-embd-form` divs present at load time, it\n doesn't detect ones added later.\n */}\n <div className=\"leadforms-embd-form\" {...(embedTargetId ? { id: embedTargetId } : {})}>\n {/* Form renders here */}\n </div>\n </div>\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;AAwBA,MAAa,oBAAoB,IAAI,aAAa;AAoClD,MAAM,iCAAiC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA8BvC,SAAwB,gBAAgB,EACtC,aACA,YACA,WACA,cACA,aACA,YAAY,IACZ,iBACuB;CACvB,MAAM,CAAC,cAAc,mBAAmB,SAAS,KAAK;CACtD,MAAM,UAAU,OAAuB,IAAI;CAC3C,MAAM,cAAc,OAAe,CAAC;CACpC,MAAM,eAAe,OAAgB,IAAI;CACzC,MAAM,cAAc,yBAAyB,YAAY,GAAG;CAE5D,gBAAgB;EACd,aAAa,UAAU;EACvB,aAAa;GACX,aAAa,UAAU;EACzB;CACF,GAAG,CAAC,CAAC;CAEL,gBAAgB;EACd,kBAAkB,UAAU,EAC1B,MAAM,GAAG,cAAc,aAAa,+BAA+B,EACrE,CAAC;CACH,GAAG,CAAC,aAAa,SAAS,CAAC;;;;CAK3B,gBAAgB;EACd,IAAI,iBAAiB,YAAY,CAAC,QAAQ,SAAS;EAEnD,MAAM,WAAW,IAAI,sBAClB,YAAY;GACX,QAAQ,SAAS,UAAU;IACzB,IAAI,MAAM,gBAAgB;KACxB,gBAAgB,IAAI;KACpB,SAAS,WAAW;IACtB;GACF,CAAC;EACH,GACA;GAAE,YAAY;GAAS,WAAW;EAAI,CACxC;EAEA,SAAS,QAAQ,QAAQ,OAAO;EAChC,aAAa,SAAS,WAAW;CACnC,GAAG,CAAC,YAAY,CAAC;;;;;;;CAQjB,gBAAgB;EAGd,IAAI,EAFe,iBAAiB,UAAU,gBAAgB,OAAO,eAEpD;EAEjB,MAAM,aAAa;GAIjB,AAAC,OAA4C,aAAa,WAAW;GAErE,kBACG,KAAK,WAAW,CAAC,CACjB,WAAW;IACV,IAAI,CAAC,aAAa,SAAS;IAC3B,kBAAkB,cAAc,WAAW;GAC7C,CAAC,CAAC,CACD,YAAY,CAGb,CAAC;EACL;EAEA,IAAI,iBAAiB,SAAS;GAC5B,KAAK;GACL;EACF;EAEA,MAAM,SAAS;GAAC;GAAS;GAAa;GAAU;EAAY;EAC5D,MAAM,iBAAiB;GACrB,KAAK;GACL,OAAO,SAAS,UAAU,SAAS,oBAAoB,OAAO,QAAQ,CAAC;EACzE;EACA,OAAO,SAAS,UAAU;GACxB,SAAS,iBAAiB,OAAO,UAAU,EAAE,MAAM,KAAK,CAAC;EAC3D,CAAC;EAED,aAAa;GACX,OAAO,SAAS,UAAU,SAAS,oBAAoB,OAAO,QAAQ,CAAC;EACzE;CACF,GAAG;EAAC;EAAa;EAAc;EAAa;EAAc;EAAa;CAAU,CAAC;;;;;;;;;;;;;;;;;;;CAoBlF,gBAAgB;EACd,IAAI,iBAAiB,UAAU;EAC/B,IAAI,kBAAkB,cAAc,MAAM,GAAG;EAC7C,IAAI,CAAC,kBAAkB,SAAS,WAAW,GAAG;EAE9C,MAAM,YAAY,QAAQ,SAAS,cAAc,sBAAsB;EACvE,IAAI,CAAC,aAAa,UAAU,SAAS,SAAS,GAAG;EAEjD,AAAC,OAA4C,aAAa,WAAW;EACrE,kBACG,OAAO,WAAW,CAAC,CACnB,WAAW;GACV,IAAI,CAAC,aAAa,SAAS;GAC3B,kBAAkB,cAAc,WAAW;EAC7C,CAAC,CAAC,CACD,YAAY,CAEb,CAAC;CACL,GAAG;EAAC;EAAa;EAAa;CAAY,CAAC;;;;;;CAO3C,gBAAgB;EACd,YAAY,UAAU,kBAAkB,cAAc;CACxD,GAAG,CAAC,WAAW,CAAC;;;;;;;CAQhB,gBAAgB;EACd,aAAa;GACX,kBAAkB,iBAAiB,WAAW;GAE9C,IAAI,iBAAiB,UAAU;IAC7B,MAAM,MAAM,YAAY;IACxB,iBAAiB;KACf,kBAAkB,OAAO,GAAG;IAC9B,GAAG,GAAG;GACR;EACF;CACF,GAAG,CAAC,CAAC;CAEL,OACE,oBAAC,OAAD;EAAK,KAAK;EAAS,IAAI;EAAwB;YAQ7C,oBAAC,OAAD;GAAK,WAAU;GAAsB,GAAK,gBAAgB,EAAE,IAAI,cAAc,IAAI,CAAC;EAE9E;CACF;AAET"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@silverassist/leadcapture-form",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.3",
|
|
4
4
|
"description": "LeadCapture IO form integration for Next.js — variant switching, ownership arbitration, and ref-counted script lifecycle via @silverassist/next-script-loader",
|
|
5
5
|
"author": "Miguel Colmenares <me@miguelcolmenares.com>",
|
|
6
6
|
"license": "PolyForm-Noncommercial-1.0.0",
|
|
@@ -71,7 +71,7 @@
|
|
|
71
71
|
"react-dom": ">=18.0.0"
|
|
72
72
|
},
|
|
73
73
|
"dependencies": {
|
|
74
|
-
"@silverassist/next-script-loader": "^0.1.
|
|
74
|
+
"@silverassist/next-script-loader": "^0.1.1"
|
|
75
75
|
},
|
|
76
76
|
"devDependencies": {
|
|
77
77
|
"@playwright/test": "^1.62.1",
|