@wix/web5-embed 0.1.4 → 0.1.6
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/dist/hostChrome.d.ts +56 -11
- package/dist/index.cjs +2 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +227 -189
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/hostChrome.d.ts
CHANGED
|
@@ -19,16 +19,43 @@
|
|
|
19
19
|
*/
|
|
20
20
|
/** CSS custom property the measurement is published as. */
|
|
21
21
|
export declare const HOST_BOTTOM_INSET_VAR = "--web5-host-bottom-inset";
|
|
22
|
+
/**
|
|
23
|
+
* Every `position: fixed | sticky` element in the document.
|
|
24
|
+
*
|
|
25
|
+
* Whole-document, not the `body > *` / `body > * > *` pair this used to scan:
|
|
26
|
+
* depth is not a property of host chrome. Measured on a Shopify Horizon store,
|
|
27
|
+
* the theme's sticky add-to-cart bar (`div.sticky-add-to-cart__bar`) sits five
|
|
28
|
+
* levels below `<body>`, so a two-level scan never enumerated it and the
|
|
29
|
+
* widget rendered on top of the merchant's buy button.
|
|
30
|
+
*
|
|
31
|
+
* Depth-independence costs a whole-document style read, which is why
|
|
32
|
+
* {@link trackHostBottomInset} caches what this returns and re-reads the cached
|
|
33
|
+
* elements instead of calling it again.
|
|
34
|
+
*/
|
|
35
|
+
export declare function collectPinnedElements(): Element[];
|
|
22
36
|
/**
|
|
23
37
|
* Total height of the chrome the host pins to the *bottom* of the viewport,
|
|
24
38
|
* excluding anything inside `exclude` (our own surface — otherwise the widget
|
|
25
|
-
* measures itself and
|
|
39
|
+
* measures itself, lifts, and measures the lift).
|
|
40
|
+
*
|
|
41
|
+
* `candidates` is an already-collected pinned set (see
|
|
42
|
+
* {@link collectPinnedElements}); omit it and the document is scanned. Every
|
|
43
|
+
* guard below is re-read per call, so a cached list stays safe — an element the
|
|
44
|
+
* theme has since unpinned, hidden or moved off the fold is rejected here.
|
|
45
|
+
*
|
|
46
|
+
* ## The two guards, and what each rejects
|
|
26
47
|
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
48
|
+
* Measured on a Shopify Horizon store, a naive "everything fixed at the bottom
|
|
49
|
+
* edge" scan returns the theme's `<cart-drawer>` and its overlay alongside the
|
|
50
|
+
* real bar — both full-height, so `MAX_CHROME_VIEWPORT_RATIO` rejects them.
|
|
51
|
+
*
|
|
52
|
+
* The other family is the corner chat bubble, and what separates it from a bar
|
|
53
|
+
* is *where* it sits, not how wide it is — so the test is whether the element
|
|
54
|
+
* crosses the horizontal centre. A width threshold reads as the obvious rule
|
|
55
|
+
* and is the wrong one: at 80% it rejected Horizon's sticky add-to-cart bar,
|
|
56
|
+
* 600px on a 1478px viewport, which is 41% wide, centred, and squarely over the
|
|
57
|
+
* input. Crossing the centre admits a centred toast of any width and still
|
|
58
|
+
* yields nothing to a launcher in the corner.
|
|
32
59
|
*
|
|
33
60
|
* ## Why the widest strip, and not the sum of heights
|
|
34
61
|
*
|
|
@@ -43,7 +70,7 @@ export declare const HOST_BOTTOM_INSET_VAR = "--web5-host-bottom-inset";
|
|
|
43
70
|
* pushed below the fold contributes nothing, and a fixed child of a fixed
|
|
44
71
|
* parent cannot be counted twice.
|
|
45
72
|
*/
|
|
46
|
-
export declare function measureHostBottomInset(exclude?: Element | null): number;
|
|
73
|
+
export declare function measureHostBottomInset(exclude?: Element | null, candidates?: readonly Element[]): number;
|
|
47
74
|
export interface TrackHostBottomInsetOptions {
|
|
48
75
|
/**
|
|
49
76
|
* Element excluded from the scan — normally the widget's own host, so it does
|
|
@@ -61,9 +88,27 @@ export interface TrackHostBottomInsetOptions {
|
|
|
61
88
|
* `calc()` for one that also wants a gap. It is the same variable, and the same
|
|
62
89
|
* contract, `web50-server-ui` already publishes on `:root` for the full page.
|
|
63
90
|
*
|
|
64
|
-
*
|
|
65
|
-
*
|
|
66
|
-
*
|
|
67
|
-
*
|
|
91
|
+
* ## Settle, rather than poll
|
|
92
|
+
*
|
|
93
|
+
* The answer changes when the host mutates or the viewport resizes — and then
|
|
94
|
+
* it keeps changing, because a bar typically slides or fades into place over a
|
|
95
|
+
* few hundred milliseconds. A single measurement on the triggering event
|
|
96
|
+
* catches the bar at its starting position and leaves the input parked half a
|
|
97
|
+
* bar too low; sampling at intervals makes it arrive in visible steps.
|
|
98
|
+
*
|
|
99
|
+
* So a trigger starts a settle pass: measure each frame until the value holds
|
|
100
|
+
* for {@link SETTLE_STABLE_FRAMES}, then stop. The input tracks the animation,
|
|
101
|
+
* and an idle page runs nothing at all — no scroll listener, no timer, no
|
|
102
|
+
* observer callbacks between mutations. A pass costs 0.08ms a frame and ends
|
|
103
|
+
* after ten still frames when nothing is moving.
|
|
104
|
+
*
|
|
105
|
+
* Rebuilding the candidate list is the expensive half (7.7ms — see
|
|
106
|
+
* {@link CANDIDATE_RESCAN_INTERVAL_MS}), so it stays throttled and out of the
|
|
107
|
+
* settle pass, which only re-reads what is already known.
|
|
108
|
+
*
|
|
109
|
+
* Not covered: a `position: sticky` bar that unsticks from scrolling alone
|
|
110
|
+
* changes position with no mutation to notice it by. No host chrome seen so far
|
|
111
|
+
* behaves that way, and catching it would mean watching every frame of every
|
|
112
|
+
* scroll for an event that happens twice a page.
|
|
68
113
|
*/
|
|
69
114
|
export declare function trackHostBottomInset(target: HTMLElement, options?: TrackHostBottomInsetOptions): () => void;
|
package/dist/index.cjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const
|
|
1
|
+
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const w="__web5InputFocusShortcut__",D='input, textarea, [contenteditable=""], [contenteditable="true"]',$='[role="dialog"][aria-modal="true"]';function W(e){if(e.hasAttribute("inert"))return!1;if(typeof e.checkVisibility=="function")return e.checkVisibility({checkOpacity:!0,checkVisibilityCSS:!0});const t=getComputedStyle(e);return!(t.visibility==="hidden"||t.display==="none"||parseFloat(t.opacity)===0)}function K(e){const t=e.composedPath();for(const n of t)if(n instanceof Element&&typeof n.matches=="function"&&n.matches(D))return!0;return!1}function q(){const e=window;if(e[w])return e[w];const t=[];let n=!1;const o=i=>{if(i.defaultPrevented||i.isComposing)return;const c=i.key==="/"&&!i.metaKey&&!i.ctrlKey&&!i.altKey&&!i.shiftKey,u=(i.metaKey||i.ctrlKey)&&!i.altKey&&i.key.toLowerCase()==="k";if(!c&&!u||c&&K(i))return;const p=document.querySelectorAll($);for(const f of p)if(W(f))return;const d=t.find(f=>f.isVisible());if(!d)return;i.preventDefault();const h=document.activeElement instanceof HTMLElement?document.activeElement:null;d.focus(h)},r=()=>{n||(window.addEventListener("keydown",o),n=!0)},s=()=>{!n||t.length>0||(window.removeEventListener("keydown",o),n=!1)},a={register(i){t.includes(i)||t.push(i),r()},unregister(i){const c=t.indexOf(i);c>=0&&t.splice(c,1),s()},__teardown(){t.length=0,n&&(window.removeEventListener("keydown",o),n=!1)}};return e[w]=a,a}function j(){const e=window;e[w]?.__teardown(),delete e[w]}const v="https://www.wixapis.com/web50-server/static-versions",S="__web5_static_versions";async function I(e=v){const t=await fetch(e,{method:"GET",headers:{Accept:"application/json"},mode:"cors",credentials:"omit"});if(!t.ok)throw new Error(`[Web5Embed] static-versions returned ${t.status}`);return t.json()}function y(e,t){const n=e.find(o=>o.artifactId===t);if(!n)throw new Error(`[Web5Embed] artifact ${t} not found in static-versions`);return n.version}function _(){return window[S]}function A(e){window[S]=e}const J={staticVersionsUrl:"https://www.wixapis.com/web50-server/static-versions",tokensCdnBase:"https://static.parastorage.com/services/web50-server-ui",tokensArtifactId:"com.wixpress.web50-server-ui",tokensFileName:"prompt-input-tokens.css",fallbackVersion:"1.459.0"};function k(e){return{...J,...e??{}}}function L(e,t){const{tokensCdnBase:n,tokensFileName:o}=k(t);return`${n}/${e}/${o}`}async function F(e){const t=k(e);try{const n=_();if(n)return y(n,t.tokensArtifactId);const o=await I(t.staticVersionsUrl);return A(o),y(o,t.tokensArtifactId)}catch(n){return console.warn("[Web5Embed] Failed to resolve prompt token version, using fallback:",n),t.fallbackVersion}}async function M(e){const t=await F(e);return L(t,e)}async function X(e,t){if(e.querySelector('link[data-web5-prompt-input-tokens="true"]'))return;const o=document.createElement("link");o.rel="stylesheet",o.dataset.web5PromptInputTokens="true",o.href=await M(t),e.prepend(o)}const Y=`
|
|
2
2
|
<svg class="web5-submit-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="none" aria-hidden="true">
|
|
3
3
|
<path d="M13.0306 7.53114C12.9609 7.60106 12.8781 7.65654 12.787 7.69439C12.6958 7.73224 12.5981 7.75173 12.4993 7.75173C12.4006 7.75173 12.3029 7.73224 12.2117 7.69439C12.1206 7.65654 12.0378 7.60106 11.9681 7.53114L8.74997 4.31301V13.5005C8.74997 13.6994 8.67095 13.8902 8.5303 14.0308C8.38965 14.1715 8.19888 14.2505 7.99997 14.2505C7.80106 14.2505 7.61029 14.1715 7.46964 14.0308C7.32899 13.8902 7.24997 13.6994 7.24997 13.5005V4.31301L4.0306 7.53114C3.8897 7.67203 3.69861 7.75119 3.49935 7.75119C3.30009 7.75119 3.10899 7.67203 2.9681 7.53114C2.8272 7.39024 2.74805 7.19915 2.74805 6.99989C2.74805 6.80063 2.8272 6.60953 2.9681 6.46864L7.4681 1.96864C7.53778 1.89872 7.62057 1.84324 7.71173 1.80539C7.8029 1.76753 7.90064 1.74805 7.99935 1.74805C8.09806 1.74805 8.1958 1.76753 8.28696 1.80539C8.37813 1.84324 8.46092 1.89872 8.5306 1.96864L13.0306 6.46864C13.1005 6.53832 13.156 6.62111 13.1938 6.71227C13.2317 6.80344 13.2512 6.90118 13.2512 6.99989C13.2512 7.0986 13.2317 7.19634 13.1938 7.2875C13.156 7.37867 13.1005 7.46146 13.0306 7.53114Z" fill="currentColor" />
|
|
4
4
|
</svg>
|
|
5
|
-
`;function
|
|
5
|
+
`;function H(e){return`${e}?initialQuestion=`}function z(e){return e.replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""").replace(/'/g,"'")}const G="https://www.wixapis.com/page-enrichment/v1/page-meta-data/chips",O=5e3,Z="https://www.wixapis.com/wix-assistant-web5/v1/turn",Q="pdpChips";async function ee(e,t){if(t.conversation?.web5Id)return P({...t.conversation,timeoutMs:t.timeoutMs,fetchImpl:t.fetchImpl});const n=t.endpoint??G,o=t.timeoutMs??O,r=x(t.fetchImpl),s=new AbortController,a=setTimeout(()=>s.abort(),o);try{const i=await r(`${n}?url=${encodeURIComponent(e)}&sessionId=${encodeURIComponent(t.sessionId)}`,{signal:s.signal});if(!i.ok)throw new Error(`HTTP ${i.status}`);return(await i.json()).chips?.map(u=>u.label).filter(Boolean)??[]}finally{clearTimeout(a)}}async function P(e){const t=e.endpoint??Z,n=e.timeoutMs??O,o=x(e.fetchImpl),r=new AbortController,s=setTimeout(()=>r.abort(),n);try{const a=e.handle?{handle:e.handle}:void 0,i={...e.sessionId?{sessionId:e.sessionId}:{},...e.web5Id?{parentMessageId:e.web5Id}:{},placement:Q,...a?{additionalData:a}:{}},c=await o(t,{method:"POST",headers:{"Content-Type":"application/json",Accept:"text/event-stream"},body:JSON.stringify(i),signal:r.signal});if(!c.ok)throw new Error(`HTTP ${c.status}`);const u=await c.text();return ne(te(u))}finally{clearTimeout(s)}}function x(e){return e||(typeof window<"u"?fetch.bind(window):fetch)}function te(e){let t=e.trim();if(!t)return"";t.endsWith(",")&&(t=t.slice(0,-1)),t.startsWith("[")||(t=`[${t}`),t.endsWith("]")||(t+="]");try{const n=JSON.parse(t);if(!Array.isArray(n))return"";const o=n.find(r=>typeof r?.completed?.markdown=="string")?.completed?.markdown;return o||n.map(r=>typeof r?.markdown=="string"?r.markdown:"").join("")}catch{return""}}function ne(e){const t=e.trim().replace(/^```(?:json)?\s*/i,"").replace(/\s*```$/,"").trim();if(!t)return[];try{return JSON.parse(t).chips?.map(o=>o.label).filter(Boolean)??[]}catch{return[]}}function oe(e){if(e instanceof HTMLElement)return e;if(typeof e=="string"){const o=document.querySelector(e);if(!o)throw new Error(`[Web5Embed] Target element not found: ${e}`);return o}const t=document.currentScript,n=document.createElement("div");return t?.parentElement?t.parentElement.insertBefore(n,t):document.body.appendChild(n),n}function re(e){return e?new URLSearchParams(window.location.search).has(e):!0}function ie(e,t=[]){return!(Array.isArray(e)?e:e!=null?[e]:t).includes(window.location.pathname)}function V(e){if(!e)return{};const t={},n=e.getAttribute("data-target");n&&(t.target=n);const o=e.getAttribute("data-placeholder");o&&(t.placeholder=o);const r=e.getAttribute("data-base-url");r&&(t.baseUrl=r);const s=e.getAttribute("data-require-param");s&&(t.requireParam=s);const a=e.getAttribute("data-suggestions");if(a)try{const i=JSON.parse(a);Array.isArray(i)&&i.length&&(t.suggestions=i)}catch{console.warn("[Web5Embed] Invalid data-suggestions JSON, ignoring.")}return t}function se(e){const t=V(e);if(!e)return t;const n=e.getAttribute("data-visibility-mode");(n==="always"||n==="after-target")&&(t.visibilityMode=n);const o=e.getAttribute("data-show-after-target");o&&(t.showAfterTarget=o);const r=e.getAttribute("data-exclude-paths");return r&&(t.excludePaths=r.includes(",")?r.split(",").map(s=>s.trim()):r),t}function ae(e){if(typeof window>"u")return;const t=window.__web5Loader__;if(!t){console.warn(`[Web5Embed] window.__web5Loader__ not present; widget "${e.widgetId}" will not mount`);return}const n=async o=>e.mount(o,e.parseOptions(o.options));t.register(e.widgetId,n)}function ce(e){return(t,n)=>{const o=n.variant;if(!o||o==="default")return e.default(t,n);const r=e.variants?.[o];return r?r(t,n):(console.warn(`[Web5Embed] unknown variant "${o}" — falling back to default`),e.default(t,n))}}function ue(e){const t=n=>e?.[n];return{isEnabled:n=>{const o=t(n);return o?.status==="SUCCESSFUL"&&o.enabled===!0},get:t}}function le(e){const{input:t,wrapper:n,multilineClass:o,maxHeightPx:r,multilineThresholdMultiplier:s=1,fallbackLineHeightPx:a=32}=e;return{sync(){if(!n)return;const i=window.getComputedStyle(t),c=Number.parseFloat(i.lineHeight)||a;t.style.height="auto";const u=t.value.trim().length===0?c:Math.min(Math.max(t.scrollHeight,c),r);t.style.height=`${u}px`,n.classList.toggle(o,u>c*s)}}}const de=400;function fe(e={}){const t=e.thresholdPx??de;let n=!1;function o(){const r=document.documentElement,s=window.scrollY||r.scrollTop||0,a=s+window.innerHeight,i=r.scrollHeight-a;return r.scrollHeight>window.innerHeight+t&&s>0&&i<=t}return{evaluate(r){return!r.isCoordinatorVisible()||!o()?(n=!1,!1):r.hasFocusInside()||n?r.isCollapsed():!0},markManuallyExpanded(){n=!0},resetManualExpand(){n=!1}}}function me(e){if(!e.baseUrl)throw new Error("[Web5Embed] createSubmitFlow requires a non-empty baseUrl.");const t=H(e.baseUrl),n=e.windowTarget??"_self";return{open(o){return window.open(t+encodeURIComponent(o),n),!0}}}const we=100,pe=.5,he=250,ge=10,be=60,U="--web5-host-bottom-inset";function C(){if(typeof document>"u"||typeof window>"u")return[];const e=[];return document.body?.querySelectorAll("*").forEach(t=>{const n=window.getComputedStyle(t).position;(n==="fixed"||n==="sticky")&&e.push(t)}),e}function R(e,t){if(typeof document>"u"||typeof window>"u")return 0;const n=t??C(),o=window.innerWidth/2;let r=0;for(const s of n){if(e!=null&&(e.contains(s)||s.contains(e)))continue;const a=window.getComputedStyle(s);if(a.position!=="fixed"&&a.position!=="sticky"||a.visibility==="hidden"||a.bottom==="auto")continue;const i=Number.parseFloat(a.bottom);if(!Number.isFinite(i)||i>we)continue;const c=s.getBoundingClientRect();if(c.height<=0||c.left>o||c.right<o)continue;const u=window.innerHeight-c.top;u<=0||u>window.innerHeight*pe||(r=Math.max(r,u))}return Math.round(r)}function ye(e,t={}){if(typeof window>"u")return()=>{};const n=t.exclude===void 0?e:t.exclude;let o=C(),r=null,s=0,a=!1,i=0,c=0,u=0,p=0,d=!1;const h=()=>{const l=R(n,o);return l===r?!1:(r=l,e.style.setProperty(U,`${l}px`),!0)},f=()=>{if(s=0,d){a=!1;return}if(i=h()?0:i+1,c-=1,i>=ge||c<=0){a=!1;return}s=window.requestAnimationFrame(f)},g=()=>{d||(i=0,c=be,!a&&(a=!0,s=window.requestAnimationFrame(f)))},E=()=>{u=0,!d&&(p=Date.now(),o=C(),g())},N=()=>{if(d||u!==0)return;const l=he-(Date.now()-p);if(l<=0){E();return}u=window.setTimeout(E,l)},m=()=>{g(),N()};h(),g(),window.addEventListener("resize",m,{passive:!0}),window.addEventListener("orientationchange",m,{passive:!0});const B=l=>n!=null&&l.every(T=>T.target instanceof Node&&n.contains(T.target));let b;return typeof MutationObserver<"u"&&document.body!=null&&(b=new MutationObserver(l=>{B(l)||m()}),b.observe(document.body,{childList:!0,subtree:!0,attributes:!0})),()=>{d=!0,a&&(window.cancelAnimationFrame(s),a=!1,s=0),u!==0&&(window.clearTimeout(u),u=0),window.removeEventListener("resize",m),window.removeEventListener("orientationchange",m),b?.disconnect()}}exports.DEFAULT_STATIC_VERSIONS_URL=v;exports.HOST_BOTTOM_INSET_VAR=U;exports.__resetFocusShortcutCoordinatorForTests=j;exports.attachPromptInputTokensStylesheet=X;exports.buildUrl=H;exports.cacheVersions=A;exports.createCollapseController=fe;exports.createFeatureToggleReader=ue;exports.createInputHeightSync=le;exports.createSubmitFlow=me;exports.createVariantMount=ce;exports.escapeHtml=z;exports.fetchChipSuggestions=ee;exports.fetchContextualChips=P;exports.fetchStaticVersions=I;exports.findVersion=y;exports.getCachedVersions=_;exports.getFocusShortcutCoordinator=q;exports.getPromptInputTokensUrl=L;exports.measureHostBottomInset=R;exports.readBaseConfigFromScript=V;exports.readBottomConfigFromScript=se;exports.registerWeb5Widget=ae;exports.resolvePromptInputTokensUrl=M;exports.resolvePromptInputTokensVersion=F;exports.resolveTarget=oe;exports.sendIconSvg=Y;exports.shouldInit=re;exports.shouldInitForPath=ie;exports.trackHostBottomInset=ye;
|
|
6
6
|
//# sourceMappingURL=index.cjs.map
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.cjs","sources":["../src/focusShortcut.ts","../src/staticVersions.ts","../src/promptInputTokens.ts","../src/shared.ts","../src/script-tag.ts","../src/loader-register.ts","../src/featureToggles.ts","../src/widget-hooks/inputHeightSync.ts","../src/widget-hooks/collapseController.ts","../src/widget-hooks/submitFlow.ts","../src/hostChrome.ts"],"sourcesContent":["/**\n * Cross-bundle singleton that coordinates \"/\" and Cmd/Ctrl+K focus shortcuts\n * across multiple Web5 widgets on the same host page.\n *\n * Stored on `window` under a global key so widgets from different bundles\n * (legacy embed-input + new per-client embed) can share one keyboard listener.\n */\n\nconst GLOBAL_KEY = '__web5InputFocusShortcut__' as const;\nconst EDITABLE_SELECTOR =\n 'input, textarea, [contenteditable=\"\"], [contenteditable=\"true\"]';\nconst MODAL_SELECTOR = '[role=\"dialog\"][aria-modal=\"true\"]';\n\nexport interface FocusShortcutTarget {\n focus(previousActive: HTMLElement | null): void;\n isVisible(): boolean;\n}\n\nexport interface FocusShortcutCoordinator {\n register(target: FocusShortcutTarget): void;\n unregister(target: FocusShortcutTarget): void;\n}\n\ninterface InternalCoordinator extends FocusShortcutCoordinator {\n __teardown(): void;\n}\n\ninterface CoordinatorWindow {\n [GLOBAL_KEY]?: InternalCoordinator;\n}\n\nfunction isModalVisible(modal: Element): boolean {\n if (modal.hasAttribute('inert')) return false;\n if (typeof (modal as HTMLElement).checkVisibility === 'function') {\n return (modal as HTMLElement).checkVisibility({\n checkOpacity: true,\n checkVisibilityCSS: true,\n });\n }\n const cs = getComputedStyle(modal as HTMLElement);\n if (\n cs.visibility === 'hidden' ||\n cs.display === 'none' ||\n parseFloat(cs.opacity) === 0\n ) {\n return false;\n }\n return true;\n}\n\nfunction pathContainsEditable(event: KeyboardEvent): boolean {\n const path = event.composedPath();\n for (const node of path) {\n if (\n node instanceof Element &&\n typeof node.matches === 'function' &&\n node.matches(EDITABLE_SELECTOR)\n ) {\n return true;\n }\n }\n return false;\n}\n\nexport function getFocusShortcutCoordinator(): FocusShortcutCoordinator {\n const w = window as unknown as CoordinatorWindow;\n if (w[GLOBAL_KEY]) return w[GLOBAL_KEY]!;\n\n const targets: FocusShortcutTarget[] = [];\n let listenerAttached = false;\n\n const handleKeyDown = (e: KeyboardEvent) => {\n if (e.defaultPrevented || e.isComposing) return;\n\n const isSlash =\n e.key === '/' &&\n !e.metaKey &&\n !e.ctrlKey &&\n !e.altKey &&\n !e.shiftKey;\n const isCmdK =\n (e.metaKey || e.ctrlKey) &&\n !e.altKey &&\n e.key.toLowerCase() === 'k';\n\n if (!isSlash && !isCmdK) return;\n\n if (isSlash && pathContainsEditable(e)) return;\n\n const modals = document.querySelectorAll(MODAL_SELECTOR);\n for (const modal of modals) {\n if (isModalVisible(modal)) return;\n }\n\n const target = targets.find((t) => t.isVisible());\n if (!target) return;\n\n e.preventDefault();\n const previousActive =\n document.activeElement instanceof HTMLElement\n ? document.activeElement\n : null;\n target.focus(previousActive);\n };\n\n const ensureListener = () => {\n if (listenerAttached) return;\n window.addEventListener('keydown', handleKeyDown);\n listenerAttached = true;\n };\n\n const dropListenerIfEmpty = () => {\n if (!listenerAttached || targets.length > 0) return;\n window.removeEventListener('keydown', handleKeyDown);\n listenerAttached = false;\n };\n\n const coordinator: InternalCoordinator = {\n register(target) {\n if (!targets.includes(target)) targets.push(target);\n ensureListener();\n },\n unregister(target) {\n const index = targets.indexOf(target);\n if (index >= 0) targets.splice(index, 1);\n dropListenerIfEmpty();\n },\n __teardown() {\n targets.length = 0;\n if (listenerAttached) {\n window.removeEventListener('keydown', handleKeyDown);\n listenerAttached = false;\n }\n },\n };\n\n w[GLOBAL_KEY] = coordinator;\n return coordinator;\n}\n\nexport function __resetFocusShortcutCoordinatorForTests(): void {\n const w = window as unknown as CoordinatorWindow;\n w[GLOBAL_KEY]?.__teardown();\n delete w[GLOBAL_KEY];\n}\n","/**\n * Resolves artifact versions from a remote `static-versions` JSON endpoint.\n *\n * The default URL points at Wix infra (`wixapis.com`), matching legacy\n * embed-input behaviour. Non-Wix consumers pass their own URL.\n */\n\nimport type { ArtifactVersion } from './types';\n\nexport const DEFAULT_STATIC_VERSIONS_URL =\n 'https://www.wixapis.com/web50-server/static-versions';\n\nconst CACHE_KEY = '__web5_static_versions';\n\ninterface CacheWindow {\n [CACHE_KEY]?: ArtifactVersion[];\n}\n\nexport async function fetchStaticVersions(\n url: string = DEFAULT_STATIC_VERSIONS_URL,\n): Promise<ArtifactVersion[]> {\n const res = await fetch(url, {\n method: 'GET',\n headers: { Accept: 'application/json' },\n mode: 'cors',\n credentials: 'omit',\n });\n if (!res.ok) {\n throw new Error(`[Web5Embed] static-versions returned ${res.status}`);\n }\n return res.json();\n}\n\nexport function findVersion(\n versions: ArtifactVersion[],\n artifactId: string,\n): string {\n const entry = versions.find((v) => v.artifactId === artifactId);\n if (!entry) {\n throw new Error(\n `[Web5Embed] artifact ${artifactId} not found in static-versions`,\n );\n }\n return entry.version;\n}\n\nexport function getCachedVersions(): ArtifactVersion[] | undefined {\n return (window as unknown as CacheWindow)[CACHE_KEY];\n}\n\nexport function cacheVersions(versions: ArtifactVersion[]): void {\n (window as unknown as CacheWindow)[CACHE_KEY] = versions;\n}\n","/**\n * Resolves and attaches the `prompt-input-tokens.css` design-token sheet to a\n * widget's shadow root. The sheet is owned by `web50-server-ui` and provides\n * the `--pi-*` CSS variables consumed by widget styles.\n *\n * Defaults match Wix infra; consumers can fully override for non-Wix\n * environments (e.g. self-hosted CSS in CI / partner deployments).\n */\n\nimport {\n cacheVersions,\n fetchStaticVersions,\n findVersion,\n getCachedVersions,\n} from './staticVersions';\n\nexport interface PromptInputTokensOptions {\n /** Static-versions endpoint to resolve the version from. */\n staticVersionsUrl?: string;\n /** CDN base where the tokens artifact is published. */\n tokensCdnBase?: string;\n /** Tokens artifact ID looked up in `static-versions`. */\n tokensArtifactId?: string;\n /** File name of the tokens stylesheet. */\n tokensFileName?: string;\n /** Version used when resolution fails. */\n fallbackVersion?: string;\n}\n\nconst DEFAULTS: Required<PromptInputTokensOptions> = {\n staticVersionsUrl: 'https://www.wixapis.com/web50-server/static-versions',\n tokensCdnBase: 'https://static.parastorage.com/services/web50-server-ui',\n tokensArtifactId: 'com.wixpress.web50-server-ui',\n tokensFileName: 'prompt-input-tokens.css',\n fallbackVersion: '1.459.0',\n};\n\nfunction resolveOptions(\n options?: PromptInputTokensOptions,\n): Required<PromptInputTokensOptions> {\n return { ...DEFAULTS, ...(options ?? {}) };\n}\n\nexport function getPromptInputTokensUrl(\n version: string,\n options?: PromptInputTokensOptions,\n): string {\n const { tokensCdnBase, tokensFileName } = resolveOptions(options);\n return `${tokensCdnBase}/${version}/${tokensFileName}`;\n}\n\nexport async function resolvePromptInputTokensVersion(\n options?: PromptInputTokensOptions,\n): Promise<string> {\n const opts = resolveOptions(options);\n try {\n const cached = getCachedVersions();\n if (cached) return findVersion(cached, opts.tokensArtifactId);\n\n const versions = await fetchStaticVersions(opts.staticVersionsUrl);\n cacheVersions(versions);\n return findVersion(versions, opts.tokensArtifactId);\n } catch (err) {\n console.warn(\n '[Web5Embed] Failed to resolve prompt token version, using fallback:',\n err,\n );\n return opts.fallbackVersion;\n }\n}\n\nexport async function resolvePromptInputTokensUrl(\n options?: PromptInputTokensOptions,\n): Promise<string> {\n const version = await resolvePromptInputTokensVersion(options);\n return getPromptInputTokensUrl(version, options);\n}\n\nexport async function attachPromptInputTokensStylesheet(\n shadowRoot: ShadowRoot,\n options?: PromptInputTokensOptions,\n): Promise<void> {\n const existing = shadowRoot.querySelector<HTMLLinkElement>(\n 'link[data-web5-prompt-input-tokens=\"true\"]',\n );\n if (existing) return;\n\n const link = document.createElement('link');\n link.rel = 'stylesheet';\n link.dataset.web5PromptInputTokens = 'true';\n link.href = await resolvePromptInputTokensUrl(options);\n shadowRoot.prepend(link);\n}\n","/**\n * Shared utilities for widget rendering and chip-suggestion fetching.\n *\n * The default Wix `DEFAULT_BASE_URL` from legacy embed-input is removed —\n * each consumer must supply its own `baseUrl`.\n */\n\nexport interface ChipsApiResponse {\n chips?: { label: string }[];\n}\n\n/** Default arrow-up SVG used as the submit/chip icon. */\nexport const sendIconSvg = `\n <svg class=\"web5-submit-icon\" xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 16 16\" fill=\"none\" aria-hidden=\"true\">\n <path d=\"M13.0306 7.53114C12.9609 7.60106 12.8781 7.65654 12.787 7.69439C12.6958 7.73224 12.5981 7.75173 12.4993 7.75173C12.4006 7.75173 12.3029 7.73224 12.2117 7.69439C12.1206 7.65654 12.0378 7.60106 11.9681 7.53114L8.74997 4.31301V13.5005C8.74997 13.6994 8.67095 13.8902 8.5303 14.0308C8.38965 14.1715 8.19888 14.2505 7.99997 14.2505C7.80106 14.2505 7.61029 14.1715 7.46964 14.0308C7.32899 13.8902 7.24997 13.6994 7.24997 13.5005V4.31301L4.0306 7.53114C3.8897 7.67203 3.69861 7.75119 3.49935 7.75119C3.30009 7.75119 3.10899 7.67203 2.9681 7.53114C2.8272 7.39024 2.74805 7.19915 2.74805 6.99989C2.74805 6.80063 2.8272 6.60953 2.9681 6.46864L7.4681 1.96864C7.53778 1.89872 7.62057 1.84324 7.71173 1.80539C7.8029 1.76753 7.90064 1.74805 7.99935 1.74805C8.09806 1.74805 8.1958 1.76753 8.28696 1.80539C8.37813 1.84324 8.46092 1.89872 8.5306 1.96864L13.0306 6.46864C13.1005 6.53832 13.156 6.62111 13.1938 6.71227C13.2317 6.80344 13.2512 6.90118 13.2512 6.99989C13.2512 7.0986 13.2317 7.19634 13.1938 7.2875C13.156 7.37867 13.1005 7.46146 13.0306 7.53114Z\" fill=\"currentColor\" />\n </svg>\n`;\n\nexport function buildUrl(base: string): string {\n return `${base}?initialQuestion=`;\n}\n\nexport function escapeHtml(value: string): string {\n return value\n .replace(/&/g, '&')\n .replace(/</g, '<')\n .replace(/>/g, '>')\n .replace(/\"/g, '"')\n .replace(/'/g, ''');\n}\n\n/**\n * Conversation identity + context for the warm (conversation-aware) chips\n * path. `web5-embed` is a dependency-light SDK with no `@wix/web5-core`\n * dependency, so the caller (the per-client widget, which already imports\n * web5-core) injects the identity (`chatId` / `sessionId`) it generates with\n * `getChatId` / `getOrCreateSessionId`.\n */\nexport interface ContextualChipsContext {\n /** Conversation trigger id from the page URL (`web5_id`). Maps to `Turn`'s\n * `parentMessageId`. */\n web5Id: string;\n /**\n * Product handle (entity id) for the current page. Sent structurally as\n * `additionalData.handle` (like the `pdpFraming`/`pdpItemsForYou` system\n * placements) so the BE resolves the product from it. Omitted from the\n * request when absent — no user query is sent for chips.\n */\n handle?: string;\n /** Session id (Turn `sessionId`). */\n sessionId?: string;\n /** Override the endpoint (tests). */\n endpoint?: string;\n}\n\nexport interface FetchChipSuggestionsOptions {\n /**\n * Session id (GetChips `sessionId`). Sent on the cold-path request. The\n * dependency-light SDK has no `@wix/web5-core`, so the caller injects the id\n * it generates with `getOrCreateSessionId`.\n */\n sessionId: string;\n /** Endpoint that accepts `?url=...` and returns ChipsApiResponse. */\n endpoint?: string;\n /** Abort timeout in ms. */\n timeoutMs?: number;\n /** Override `fetch` (tests). */\n fetchImpl?: typeof fetch;\n /**\n * When set, the page is \"warm\" (the visitor arrived from a Web5 turn,\n * `?web5_id=` present) and chips are generated conversation-aware via the\n * `/v1/turn` endpoint instead of the URL-only page-enrichment endpoint\n * (B2B-4121).\n */\n conversation?: ContextualChipsContext;\n}\n\nexport interface FetchContextualChipsOptions extends ContextualChipsContext {\n /** Abort timeout in ms. */\n timeoutMs?: number;\n /** Override `fetch` (tests). */\n fetchImpl?: typeof fetch;\n}\n\nconst DEFAULT_CHIPS_ENDPOINT =\n '/_api/page-enrichment/v1/page-meta-data/chips';\nconst DEFAULT_CHIPS_TIMEOUT_MS = 5000;\n\n/**\n * The `wix-assistant-web5` `Turn` endpoint (DL #110). `Turn` takes no\n * per-chat lock, so chips run in parallel with the placement answer — no\n * client-side serialization needed. Hand-rolled here (not via the ambassador\n * npm client) to keep the thin input-widget bundle light, the same reason\n * `placementFetch` hand-rolls.\n */\nconst DEFAULT_TURN_ENDPOINT =\n 'https://www.wixapis.com/wix-assistant-web5/v1/turn';\n\n/** Placement key the BE resolves the chips prompt by (B2B-4121). */\nconst PDP_CHIPS_PLACEMENT = 'pdpChips';\n\nexport async function fetchChipSuggestions(\n pageUrl: string,\n options: FetchChipSuggestionsOptions,\n): Promise<string[]> {\n // Warm page: generate conversation-aware chips from the prompt (B2B-4121).\n if (options.conversation?.web5Id) {\n return fetchContextualChips({\n ...options.conversation,\n timeoutMs: options.timeoutMs,\n fetchImpl: options.fetchImpl,\n });\n }\n\n const endpoint = options.endpoint ?? DEFAULT_CHIPS_ENDPOINT;\n const timeoutMs = options.timeoutMs ?? DEFAULT_CHIPS_TIMEOUT_MS;\n const fetchImpl = resolveFetch(options.fetchImpl);\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), timeoutMs);\n\n try {\n const response = await fetchImpl(\n `${endpoint}?url=${encodeURIComponent(pageUrl)}&sessionId=${encodeURIComponent(\n options.sessionId,\n )}`,\n { signal: controller.signal },\n );\n\n if (!response.ok) {\n throw new Error(`HTTP ${response.status}`);\n }\n\n const data: ChipsApiResponse = await response.json();\n return data.chips?.map((chip) => chip.label).filter(Boolean) ?? [];\n } finally {\n clearTimeout(timeoutId);\n }\n}\n\n/**\n * Generate conversation-aware chip suggestions for the warm path (B2B-4121).\n *\n * POSTs a `TurnRequest` tagged `placement: 'pdpChips'` so the BE resolves the\n * chips prompt by key and links the turn to the prior conversation via\n * `parentMessageId`. The prompt is authored to return JSON in the\n * `ChipsApiResponse` shape, so the labels parse the same way as the URL-only\n * endpoint.\n */\nexport async function fetchContextualChips(\n options: FetchContextualChipsOptions,\n): Promise<string[]> {\n const endpoint = options.endpoint ?? DEFAULT_TURN_ENDPOINT;\n const timeoutMs = options.timeoutMs ?? DEFAULT_CHIPS_TIMEOUT_MS;\n const fetchImpl = resolveFetch(options.fetchImpl);\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), timeoutMs);\n\n try {\n // `pdpChips` is a system placement — it sends NO user query. The BE\n // resolves the product structurally from `placement: 'pdpChips'` (the\n // chips-prompt key) + the handle in `additionalData`, and links to the\n // prior conversation via `parentMessageId`. Mirrors the entity placements\n // in `embed-placement`'s `placementFetch` (`pdpFraming`/`pdpItemsForYou`),\n // which likewise omit `text`.\n const additionalData = options.handle\n ? { handle: options.handle }\n : undefined;\n\n // `TurnRequest` shape (DL #110) — lock-free.\n const body = {\n ...(options.sessionId ? { sessionId: options.sessionId } : {}),\n ...(options.web5Id ? { parentMessageId: options.web5Id } : {}),\n placement: PDP_CHIPS_PLACEMENT,\n ...(additionalData ? { additionalData } : {}),\n };\n\n const response = await fetchImpl(endpoint, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n Accept: 'text/event-stream',\n },\n body: JSON.stringify(body),\n signal: controller.signal,\n });\n\n if (!response.ok) {\n throw new Error(`HTTP ${response.status}`);\n }\n\n // Chips are short — read the stream to completion, then parse the JSON the\n // prompt emitted out of the accumulated markdown.\n const text = await response.text();\n return parseChipsFromJson(accumulateTurnMarkdown(text));\n } finally {\n clearTimeout(timeoutId);\n }\n}\n\nfunction resolveFetch(override?: typeof fetch): typeof fetch {\n if (override) return override;\n return typeof window !== 'undefined' ? fetch.bind(window) : fetch;\n}\n\n/**\n * Concatenate the markdown out of a `Turn` JSON-array stream. Each\n * `TurnResponse` is a oneof: `markdown` (a delta) or `completed.markdown` (the\n * full final text). Prefer `completed.markdown` when present, else join the\n * deltas. (`turn`/`event` variants carry no markdown.) Tolerant enough to parse\n * the completed body in one shot, like `placementFetch`'s array parser.\n */\nfunction accumulateTurnMarkdown(streamText: string): string {\n let candidate = streamText.trim();\n if (!candidate) return '';\n if (candidate.endsWith(',')) candidate = candidate.slice(0, -1);\n if (!candidate.startsWith('[')) candidate = `[${candidate}`;\n if (!candidate.endsWith(']')) candidate += ']';\n try {\n const parsed = JSON.parse(candidate) as {\n markdown?: string;\n completed?: { markdown?: string };\n }[];\n if (!Array.isArray(parsed)) return '';\n const completed = parsed.find(\n (item) => typeof item?.completed?.markdown === 'string',\n )?.completed?.markdown;\n if (completed) return completed;\n return parsed\n .map((item) => (typeof item?.markdown === 'string' ? item.markdown : ''))\n .join('');\n } catch {\n return '';\n }\n}\n\n/**\n * Parse chip labels from the prompt's JSON output (the `ChipsApiResponse`\n * shape), tolerating an optional ```json code fence.\n */\nfunction parseChipsFromJson(markdown: string): string[] {\n const unfenced = markdown\n .trim()\n .replace(/^```(?:json)?\\s*/i, '')\n .replace(/\\s*```$/, '')\n .trim();\n if (!unfenced) return [];\n try {\n const data: ChipsApiResponse = JSON.parse(unfenced);\n return data.chips?.map((chip) => chip.label).filter(Boolean) ?? [];\n } catch {\n return [];\n }\n}\n","/**\n * Helpers shared by every per-client embed `main.ts` for parsing host-page\n * script-tag attributes and gating widget initialization.\n */\n\nimport type { Web5EmbedBaseConfig, Web5EmbedConfig } from './types';\n\nexport function resolveTarget(\n target: string | HTMLElement | undefined,\n): HTMLElement {\n if (target instanceof HTMLElement) return target;\n if (typeof target === 'string') {\n const el = document.querySelector<HTMLElement>(target);\n if (!el) {\n throw new Error(`[Web5Embed] Target element not found: ${target}`);\n }\n return el;\n }\n\n // No target specified — create a container next to the script tag.\n const script = document.currentScript;\n const container = document.createElement('div');\n if (script?.parentElement) {\n script.parentElement.insertBefore(container, script);\n } else {\n document.body.appendChild(container);\n }\n return container;\n}\n\nexport function shouldInit(requireParam?: string): boolean {\n if (!requireParam) return true;\n const params = new URLSearchParams(window.location.search);\n return params.has(requireParam);\n}\n\nexport function shouldInitForPath(\n excludePaths: string | string[] | undefined,\n defaultExcludePaths: string[] = [],\n): boolean {\n const paths = Array.isArray(excludePaths)\n ? excludePaths\n : excludePaths != null\n ? [excludePaths]\n : defaultExcludePaths;\n return !paths.includes(window.location.pathname);\n}\n\n/**\n * Reads the standard `Web5EmbedBaseConfig` data-attributes from a script tag.\n * Per-client `main.ts` files extend this with their own widget-specific\n * attributes (visibility mode, copy props, etc.) by reading additional\n * attributes from the same script.\n */\nexport function readBaseConfigFromScript(\n script: HTMLOrSVGScriptElement | null,\n): Web5EmbedBaseConfig {\n if (!script) return {};\n\n const config: Web5EmbedBaseConfig = {};\n\n const target = script.getAttribute('data-target');\n if (target) config.target = target;\n\n const placeholder = script.getAttribute('data-placeholder');\n if (placeholder) config.placeholder = placeholder;\n\n const baseUrl = script.getAttribute('data-base-url');\n if (baseUrl) config.baseUrl = baseUrl;\n\n const requireParam = script.getAttribute('data-require-param');\n if (requireParam) config.requireParam = requireParam;\n\n const suggestionsRaw = script.getAttribute('data-suggestions');\n if (suggestionsRaw) {\n try {\n const parsed = JSON.parse(suggestionsRaw);\n if (Array.isArray(parsed) && parsed.length) {\n config.suggestions = parsed;\n }\n } catch {\n console.warn('[Web5Embed] Invalid data-suggestions JSON, ignoring.');\n }\n }\n\n return config;\n}\n\n/**\n * Reads the bottom-floating widget's extended config (visibility mode,\n * showAfterTarget, excludePaths) on top of the base config.\n */\nexport function readBottomConfigFromScript(\n script: HTMLOrSVGScriptElement | null,\n): Web5EmbedConfig {\n const config: Web5EmbedConfig = readBaseConfigFromScript(script);\n if (!script) return config;\n\n const visibilityMode = script.getAttribute('data-visibility-mode');\n if (visibilityMode === 'always' || visibilityMode === 'after-target') {\n config.visibilityMode = visibilityMode;\n }\n\n const showAfterTarget = script.getAttribute('data-show-after-target');\n if (showAfterTarget) config.showAfterTarget = showAfterTarget;\n\n const excludePathsRaw = script.getAttribute('data-exclude-paths');\n if (excludePathsRaw) {\n config.excludePaths = excludePathsRaw.includes(',')\n ? excludePathsRaw.split(',').map((p) => p.trim())\n : excludePathsRaw;\n }\n\n return config;\n}\n","/**\n * `registerWeb5Widget` — thin helper that per-client `main.ts` shims use to\n * register a widget against `window.__web5Loader__` (DL #089). Centralizes the\n * \"register-or-warn\" boilerplate and the `ctx.options → typed config` shape\n * coercion so each per-client entry file is a 5-line shim.\n *\n * `createVariantMount` — small dispatch helper for per-client bundles that\n * expose more than one rendering variant of the same widget id (e.g. circana's\n * default bottom-floating input + a homepage hero variant). The SDK does not\n * enumerate variant ids; each client owns its own.\n */\n\nimport type { LoaderContext, WidgetInit, WidgetInstance } from './loader-types';\n\nexport interface RegisterWidgetOptions<TConfig> {\n /**\n * The widget id under which to register. Must match a key in\n * `Configuration.widgetBundles` on the BE / mock config.\n */\n widgetId: string;\n /**\n * Cast / parse the loader's raw `ctx.options` into the client's typed\n * config. The loader passes `spec.options` through verbatim, so this is\n * usually a near-no-op cast.\n */\n parseOptions: (options: Record<string, unknown>) => TConfig;\n /**\n * Mount the widget. Return value supplies the lifecycle hooks the loader\n * uses on SPA route changes. May return synchronously or as a promise —\n * the `WidgetInit` wrapper awaits either form.\n */\n mount: (\n ctx: LoaderContext,\n config: TConfig,\n ) => WidgetInstance | Promise<WidgetInstance>;\n}\n\nexport function registerWeb5Widget<TConfig>(\n opts: RegisterWidgetOptions<TConfig>,\n): void {\n if (typeof window === 'undefined') return;\n const loader = window.__web5Loader__;\n if (!loader) {\n console.warn(\n `[Web5Embed] window.__web5Loader__ not present; widget \"${opts.widgetId}\" will not mount`,\n );\n return;\n }\n const init: WidgetInit = async (ctx) =>\n opts.mount(ctx, opts.parseOptions(ctx.options));\n loader.register(opts.widgetId, init);\n}\n\nexport type VariantMount<TConfig extends { variant?: string }> = (\n ctx: LoaderContext,\n config: TConfig,\n) => WidgetInstance | Promise<WidgetInstance>;\n\nexport interface VariantMountOptions<TConfig extends { variant?: string }> {\n /** Mount used when `config.variant` is undefined, `'default'`, or unknown. */\n default: VariantMount<TConfig>;\n /** Optional named variants, keyed by the string value of `config.variant`. */\n variants?: Record<string, VariantMount<TConfig>>;\n}\n\n/**\n * Build a mount function that dispatches on `config.variant`. Falls back to\n * `default` (with a console warning) when an unknown variant id is supplied —\n * the loader's route rule is the source of truth, and unknown variants\n * typically indicate a config/bundle version mismatch.\n */\nexport function createVariantMount<TConfig extends { variant?: string }>(\n opts: VariantMountOptions<TConfig>,\n): VariantMount<TConfig> {\n return (ctx, config) => {\n const id = config.variant;\n if (!id || id === 'default') {\n return opts.default(ctx, config);\n }\n const handler = opts.variants?.[id];\n if (handler) {\n return handler(ctx, config);\n }\n console.warn(\n `[Web5Embed] unknown variant \"${id}\" — falling back to default`,\n );\n return opts.default(ctx, config);\n };\n}\n","export type EmbedFeatureToggleStatus =\n | 'SUCCESSFUL'\n | 'MISSING_SEED'\n | 'DID_NOT_FIND_ELIGIBLE_FILTER'\n | 'ERROR';\n\nexport interface EmbedFeatureToggleResult {\n id: string;\n enabled: boolean;\n status: EmbedFeatureToggleStatus;\n}\n\nexport interface FeatureToggleReader {\n isEnabled: (id: string) => boolean;\n get: (id: string) => EmbedFeatureToggleResult | undefined;\n}\n\nexport function createFeatureToggleReader(\n featureToggles: Record<string, EmbedFeatureToggleResult> | undefined,\n): FeatureToggleReader {\n const get = (id: string): EmbedFeatureToggleResult | undefined =>\n featureToggles?.[id];\n\n return {\n isEnabled: (id) => {\n const result = get(id);\n return result?.status === 'SUCCESSFUL' && result.enabled === true;\n },\n get,\n };\n}\n","/**\n * Auto-resizing textarea height with a `multiline` class toggle on a wrapper.\n *\n * Pattern: textarea grows from `lineHeight` up to `maxHeightPx`. When growth\n * happens the wrapper element receives `multilineClass` so callers can\n * adjust padding/border-radius for the multi-line state.\n */\n\nexport interface InputHeightSyncOptions {\n input: HTMLTextAreaElement;\n /** Element that should toggle the `multiline` class. */\n wrapper: Element | null;\n /** Class added/removed on `wrapper` when input grows past one line. */\n multilineClass: string;\n /** Max height in px the input is allowed to grow to. */\n maxHeightPx: number;\n /** Multiplier applied to lineHeight to decide when to flip to multiline. Default 1. */\n multilineThresholdMultiplier?: number;\n /** Fallback lineHeight if computed style is unparsable. */\n fallbackLineHeightPx?: number;\n}\n\nexport interface InputHeightSync {\n sync(): void;\n}\n\nexport function createInputHeightSync(\n options: InputHeightSyncOptions,\n): InputHeightSync {\n const {\n input,\n wrapper,\n multilineClass,\n maxHeightPx,\n multilineThresholdMultiplier = 1,\n fallbackLineHeightPx = 32,\n } = options;\n\n return {\n sync() {\n if (!wrapper) return;\n\n const computed = window.getComputedStyle(input);\n const lineHeight =\n Number.parseFloat(computed.lineHeight) || fallbackLineHeightPx;\n\n input.style.height = 'auto';\n const nextHeight =\n input.value.trim().length === 0\n ? lineHeight\n : Math.min(Math.max(input.scrollHeight, lineHeight), maxHeightPx);\n input.style.height = `${nextHeight}px`;\n wrapper.classList.toggle(\n multilineClass,\n nextHeight > lineHeight * multilineThresholdMultiplier,\n );\n },\n };\n}\n","/**\n * Collapse controller for a bottom-fixed prompt widget.\n *\n * Tracks the scroll position relative to the bottom of the document and\n * collapses the widget when within `thresholdPx` of the page bottom — but\n * only if the widget itself doesn't currently hold focus and hasn't already\n * been manually expanded inside the bottom zone.\n *\n * Caller is responsible for binding scroll/resize listeners; this controller\n * just exposes pure state transitions.\n */\n\nexport interface CollapseControllerOptions {\n /** Distance in px from the bottom of the document at which to collapse. */\n thresholdPx?: number;\n}\n\nexport interface CollapseControllerState {\n isCollapsed(): boolean;\n isCoordinatorVisible(): boolean;\n hasFocusInside(): boolean;\n}\n\nexport interface CollapseController {\n /** Should the widget collapse right now, given current state? */\n evaluate(state: CollapseControllerState): boolean;\n /** Mark that the user has expanded the widget while in the bottom zone. */\n markManuallyExpanded(): void;\n /** Reset manual-expand memory (e.g. when leaving the bottom zone). */\n resetManualExpand(): void;\n}\n\nconst DEFAULT_THRESHOLD_PX = 400;\n\nexport function createCollapseController(\n options: CollapseControllerOptions = {},\n): CollapseController {\n const threshold = options.thresholdPx ?? DEFAULT_THRESHOLD_PX;\n let manuallyExpanded = false;\n\n function isBottomZoneVisible(): boolean {\n const doc = document.documentElement;\n const scrollTop = window.scrollY || doc.scrollTop || 0;\n const viewportBottom = scrollTop + window.innerHeight;\n const distanceToBottom = doc.scrollHeight - viewportBottom;\n const hasEnoughContent =\n doc.scrollHeight > window.innerHeight + threshold;\n return (\n hasEnoughContent && scrollTop > 0 && distanceToBottom <= threshold\n );\n }\n\n return {\n evaluate(state) {\n if (!state.isCoordinatorVisible()) {\n manuallyExpanded = false;\n return false;\n }\n\n if (!isBottomZoneVisible()) {\n manuallyExpanded = false;\n return false;\n }\n\n if (state.hasFocusInside()) {\n // Preserve current state; caller should not re-collapse while focused.\n return state.isCollapsed();\n }\n\n if (manuallyExpanded) {\n return state.isCollapsed();\n }\n\n return true;\n },\n markManuallyExpanded() {\n manuallyExpanded = true;\n },\n resetManualExpand() {\n manuallyExpanded = false;\n },\n };\n}\n","/**\n * Submit-flow helper: navigates the host page to `<baseUrl>?initialQuestion=<value>`.\n *\n * Pure side-effect function; pulled out so per-client widgets share the same\n * URL-build + window.open contract.\n */\n\nimport { buildUrl } from '../shared';\n\nexport interface SubmitFlowOptions {\n baseUrl: string;\n /** Window target for the navigation. Defaults to `_self`. */\n windowTarget?: string;\n}\n\nexport interface SubmitFlow {\n open(value: string): boolean;\n}\n\nexport function createSubmitFlow(options: SubmitFlowOptions): SubmitFlow {\n if (!options.baseUrl) {\n throw new Error(\n '[Web5Embed] createSubmitFlow requires a non-empty baseUrl.',\n );\n }\n const prefix = buildUrl(options.baseUrl);\n const target = options.windowTarget ?? '_self';\n\n return {\n open(value) {\n window.open(prefix + encodeURIComponent(value), target);\n return true;\n },\n };\n}\n","/**\n * Host bottom-chrome measurement, for any Web5 surface pinned to the bottom of\n * a host viewport.\n *\n * A `position: fixed; bottom: 0` element sits *under* whatever the host page\n * pins down there — a cookie bar, a sticky add-to-cart bar, Shopify's\n * storefront preview bar. The full-page app has cleared that since DL #131 via\n * `web50-server-ui`'s `hostFit`; the embedded widgets never did, so the\n * floating input renders with its lower edge behind the host's bar.\n *\n * This is that measurement, moved somewhere every surface can reach:\n * `web50-server-ui` re-exports it, and the widget bundles (the seed's\n * `user-query`, circana, feature-com) can call it directly. Deliberately in\n * this package rather than `@wix/web5-core` — a widget IIFE is React-free and\n * ~24 KB, and core is a barrel that pulls React and the markdown stack.\n *\n * Detection is by computed `position`, never by host class names, which is what\n * keeps it working when the merchant changes theme.\n */\n\n/**\n * A `position: sticky|fixed` element only counts as host chrome if it pins near\n * the edge it is measured against. Generous enough for a theme that offsets a\n * bar above another one.\n */\nconst PIN_THRESHOLD_PX = 100;\n\n/** Guards against counting a full-screen fixed overlay (a drawer, a modal). */\nconst MAX_CHROME_VIEWPORT_RATIO = 0.5;\n\n/**\n * A bottom bar spans the page; a chat launcher or a corner cookie widget does\n * not. Only the former is worth lifting the whole input for — yielding to a\n * bubble in one corner would move it for no reason.\n */\nconst MIN_BAR_WIDTH_RATIO = 0.8;\n\n/** CSS custom property the measurement is published as. */\nexport const HOST_BOTTOM_INSET_VAR = '--web5-host-bottom-inset';\n\n/**\n * Total height of the chrome the host pins to the *bottom* of the viewport,\n * excluding anything inside `exclude` (our own surface — otherwise the widget\n * measures itself and walks up the page one frame at a time).\n *\n * It needs two guards a top-edge equivalent would not. Measured on a Shopify\n * Horizon store, a naive \"everything fixed at the bottom edge\" scan returns the\n * theme's `<cart-drawer>` and its overlay alongside the real bar — both\n * full-height, so the viewport-ratio guard rejects them. The width guard\n * rejects the other family of false positive, the corner chat bubble.\n *\n * ## Why the widest strip, and not the sum of heights\n *\n * A top stack grows downward from a fixed origin, so heights add. The bottom\n * has no such origin: a second bar sits *above* the first via its own `bottom`\n * offset, so what we have to clear is the distance from the viewport edge up to\n * the highest bar's top — the strip, not the total ink. Summing would\n * double-count exactly that overlap.\n *\n * Reading the strip off the rect also settles the awkward cases for free: a bar\n * with a `bottom` gap contributes the gap too, a closed sheet that a transform\n * pushed below the fold contributes nothing, and a fixed child of a fixed\n * parent cannot be counted twice.\n */\nexport function measureHostBottomInset(exclude?: Element | null): number {\n if (typeof document === 'undefined' || typeof window === 'undefined') {\n return 0;\n }\n\n const candidates = new Set<Element>();\n for (const selector of ['body > *', 'body > * > *']) {\n document.querySelectorAll(selector).forEach((el) => candidates.add(el));\n }\n\n let inset = 0;\n\n for (const el of candidates) {\n if (exclude != null && (exclude.contains(el) || el.contains(exclude))) {\n continue;\n }\n\n const styles = window.getComputedStyle(el);\n if (styles.position !== 'fixed' && styles.position !== 'sticky') {\n continue;\n }\n if (styles.visibility === 'hidden' || styles.bottom === 'auto') {\n continue;\n }\n const bottom = Number.parseFloat(styles.bottom);\n if (!Number.isFinite(bottom) || bottom > PIN_THRESHOLD_PX) {\n continue;\n }\n\n const rect = el.getBoundingClientRect();\n if (rect.height <= 0 || rect.width < window.innerWidth * MIN_BAR_WIDTH_RATIO) {\n continue;\n }\n\n // How much of the bottom strip this element occupies. Zero or less means it\n // renders below the fold despite the declaration — a closed bottom sheet.\n const covered = window.innerHeight - rect.top;\n if (covered <= 0 || covered > window.innerHeight * MAX_CHROME_VIEWPORT_RATIO) {\n continue;\n }\n\n inset = Math.max(inset, covered);\n }\n\n return Math.round(inset);\n}\n\nexport interface TrackHostBottomInsetOptions {\n /**\n * Element excluded from the scan — normally the widget's own host, so it does\n * not measure itself. Defaults to `target`.\n */\n exclude?: Element | null;\n}\n\n/**\n * Keep {@link HOST_BOTTOM_INSET_VAR} on `target` in sync with the host's bottom\n * chrome, and return a function that stops tracking.\n *\n * Publishing a custom property rather than writing `bottom` directly is what\n * makes this composable: the caller's own stylesheet decides how to spend the\n * value — `bottom: var(--web5-host-bottom-inset, 0px)` for a flush dock, or a\n * `calc()` for one that also wants a gap. It is the same variable, and the same\n * contract, `web50-server-ui` already publishes on `:root` for the full page.\n *\n * Re-measured on the three things that actually change the answer: the viewport\n * resizing, the device rotating, and the host adding or removing nodes (a\n * cookie bar accepted, a sticky bar that appears on scroll). Measurement is\n * cheap but not free, so refreshes are coalesced into an animation frame.\n */\nexport function trackHostBottomInset(\n target: HTMLElement,\n options: TrackHostBottomInsetOptions = {},\n): () => void {\n if (typeof window === 'undefined') {\n return () => undefined;\n }\n\n const exclude = options.exclude === undefined ? target : options.exclude;\n let frame = 0;\n let stopped = false;\n\n const apply = (): void => {\n frame = 0;\n if (stopped) {\n return;\n }\n target.style.setProperty(HOST_BOTTOM_INSET_VAR, `${measureHostBottomInset(exclude)}px`);\n };\n\n const schedule = (): void => {\n if (stopped || frame !== 0) {\n return;\n }\n frame = window.requestAnimationFrame(apply);\n };\n\n apply();\n\n window.addEventListener('resize', schedule, { passive: true });\n window.addEventListener('orientationchange', schedule, { passive: true });\n\n let mutations: MutationObserver | undefined;\n if (typeof MutationObserver !== 'undefined' && document.body != null) {\n mutations = new MutationObserver(schedule);\n mutations.observe(document.body, { childList: true, subtree: false });\n }\n\n return () => {\n stopped = true;\n if (frame !== 0) {\n window.cancelAnimationFrame(frame);\n frame = 0;\n }\n window.removeEventListener('resize', schedule);\n window.removeEventListener('orientationchange', schedule);\n mutations?.disconnect();\n };\n}\n"],"names":["GLOBAL_KEY","EDITABLE_SELECTOR","MODAL_SELECTOR","isModalVisible","modal","cs","pathContainsEditable","event","path","node","getFocusShortcutCoordinator","w","targets","listenerAttached","handleKeyDown","e","isSlash","isCmdK","modals","target","t","previousActive","ensureListener","dropListenerIfEmpty","coordinator","index","__resetFocusShortcutCoordinatorForTests","DEFAULT_STATIC_VERSIONS_URL","CACHE_KEY","fetchStaticVersions","url","res","findVersion","versions","artifactId","entry","v","getCachedVersions","cacheVersions","DEFAULTS","resolveOptions","options","getPromptInputTokensUrl","version","tokensCdnBase","tokensFileName","resolvePromptInputTokensVersion","opts","cached","err","resolvePromptInputTokensUrl","attachPromptInputTokensStylesheet","shadowRoot","link","sendIconSvg","buildUrl","base","escapeHtml","value","DEFAULT_CHIPS_ENDPOINT","DEFAULT_CHIPS_TIMEOUT_MS","DEFAULT_TURN_ENDPOINT","PDP_CHIPS_PLACEMENT","fetchChipSuggestions","pageUrl","fetchContextualChips","endpoint","timeoutMs","fetchImpl","resolveFetch","controller","timeoutId","response","chip","additionalData","body","text","parseChipsFromJson","accumulateTurnMarkdown","override","streamText","candidate","parsed","completed","item","markdown","unfenced","resolveTarget","el","script","container","shouldInit","requireParam","shouldInitForPath","excludePaths","defaultExcludePaths","readBaseConfigFromScript","config","placeholder","baseUrl","suggestionsRaw","readBottomConfigFromScript","visibilityMode","showAfterTarget","excludePathsRaw","p","registerWeb5Widget","loader","init","ctx","createVariantMount","id","handler","createFeatureToggleReader","featureToggles","get","result","createInputHeightSync","input","wrapper","multilineClass","maxHeightPx","multilineThresholdMultiplier","fallbackLineHeightPx","computed","lineHeight","nextHeight","DEFAULT_THRESHOLD_PX","createCollapseController","threshold","manuallyExpanded","isBottomZoneVisible","doc","scrollTop","viewportBottom","distanceToBottom","state","createSubmitFlow","prefix","PIN_THRESHOLD_PX","MAX_CHROME_VIEWPORT_RATIO","MIN_BAR_WIDTH_RATIO","HOST_BOTTOM_INSET_VAR","measureHostBottomInset","exclude","candidates","selector","inset","styles","bottom","rect","covered","trackHostBottomInset","frame","stopped","apply","schedule","mutations"],"mappings":"gFAQA,MAAMA,EAAa,6BACbC,EACJ,kEACIC,EAAiB,qCAoBvB,SAASC,EAAeC,EAAyB,CAC/C,GAAIA,EAAM,aAAa,OAAO,EAAG,MAAO,GACxC,GAAI,OAAQA,EAAsB,iBAAoB,WACpD,OAAQA,EAAsB,gBAAgB,CAC5C,aAAc,GACd,mBAAoB,EAAA,CACrB,EAEH,MAAMC,EAAK,iBAAiBD,CAAoB,EAChD,MACE,EAAAC,EAAG,aAAe,UAClBA,EAAG,UAAY,QACf,WAAWA,EAAG,OAAO,IAAM,EAK/B,CAEA,SAASC,EAAqBC,EAA+B,CAC3D,MAAMC,EAAOD,EAAM,aAAA,EACnB,UAAWE,KAAQD,EACjB,GACEC,aAAgB,SAChB,OAAOA,EAAK,SAAY,YACxBA,EAAK,QAAQR,CAAiB,EAE9B,MAAO,GAGX,MAAO,EACT,CAEO,SAASS,GAAwD,CACtE,MAAMC,EAAI,OACV,GAAIA,EAAEX,CAAU,EAAG,OAAOW,EAAEX,CAAU,EAEtC,MAAMY,EAAiC,CAAA,EACvC,IAAIC,EAAmB,GAEvB,MAAMC,EAAiBC,GAAqB,CAC1C,GAAIA,EAAE,kBAAoBA,EAAE,YAAa,OAEzC,MAAMC,EACJD,EAAE,MAAQ,KACV,CAACA,EAAE,SACH,CAACA,EAAE,SACH,CAACA,EAAE,QACH,CAACA,EAAE,SACCE,GACHF,EAAE,SAAWA,EAAE,UAChB,CAACA,EAAE,QACHA,EAAE,IAAI,YAAA,IAAkB,IAI1B,GAFI,CAACC,GAAW,CAACC,GAEbD,GAAWV,EAAqBS,CAAC,EAAG,OAExC,MAAMG,EAAS,SAAS,iBAAiBhB,CAAc,EACvD,UAAWE,KAASc,EAClB,GAAIf,EAAeC,CAAK,EAAG,OAG7B,MAAMe,EAASP,EAAQ,KAAMQ,GAAMA,EAAE,WAAW,EAChD,GAAI,CAACD,EAAQ,OAEbJ,EAAE,eAAA,EACF,MAAMM,EACJ,SAAS,yBAAyB,YAC9B,SAAS,cACT,KACNF,EAAO,MAAME,CAAc,CAC7B,EAEMC,EAAiB,IAAM,CACvBT,IACJ,OAAO,iBAAiB,UAAWC,CAAa,EAChDD,EAAmB,GACrB,EAEMU,EAAsB,IAAM,CAC5B,CAACV,GAAoBD,EAAQ,OAAS,IAC1C,OAAO,oBAAoB,UAAWE,CAAa,EACnDD,EAAmB,GACrB,EAEMW,EAAmC,CACvC,SAASL,EAAQ,CACVP,EAAQ,SAASO,CAAM,GAAGP,EAAQ,KAAKO,CAAM,EAClDG,EAAA,CACF,EACA,WAAWH,EAAQ,CACjB,MAAMM,EAAQb,EAAQ,QAAQO,CAAM,EAChCM,GAAS,GAAGb,EAAQ,OAAOa,EAAO,CAAC,EACvCF,EAAA,CACF,EACA,YAAa,CACXX,EAAQ,OAAS,EACbC,IACF,OAAO,oBAAoB,UAAWC,CAAa,EACnDD,EAAmB,GAEvB,CAAA,EAGF,OAAAF,EAAEX,CAAU,EAAIwB,EACTA,CACT,CAEO,SAASE,GAAgD,CAC9D,MAAMf,EAAI,OACVA,EAAEX,CAAU,GAAG,WAAA,EACf,OAAOW,EAAEX,CAAU,CACrB,CCvIO,MAAM2B,EACX,uDAEIC,EAAY,yBAMlB,eAAsBC,EACpBC,EAAcH,EACc,CAC5B,MAAMI,EAAM,MAAM,MAAMD,EAAK,CAC3B,OAAQ,MACR,QAAS,CAAE,OAAQ,kBAAA,EACnB,KAAM,OACN,YAAa,MAAA,CACd,EACD,GAAI,CAACC,EAAI,GACP,MAAM,IAAI,MAAM,wCAAwCA,EAAI,MAAM,EAAE,EAEtE,OAAOA,EAAI,KAAA,CACb,CAEO,SAASC,EACdC,EACAC,EACQ,CACR,MAAMC,EAAQF,EAAS,KAAMG,GAAMA,EAAE,aAAeF,CAAU,EAC9D,GAAI,CAACC,EACH,MAAM,IAAI,MACR,wBAAwBD,CAAU,+BAAA,EAGtC,OAAOC,EAAM,OACf,CAEO,SAASE,GAAmD,CACjE,OAAQ,OAAkCT,CAAS,CACrD,CAEO,SAASU,EAAcL,EAAmC,CAC9D,OAAkCL,CAAS,EAAIK,CAClD,CCvBA,MAAMM,EAA+C,CACnD,kBAAmB,uDACnB,cAAe,0DACf,iBAAkB,+BAClB,eAAgB,0BAChB,gBAAiB,SACnB,EAEA,SAASC,EACPC,EACoC,CACpC,MAAO,CAAE,GAAGF,EAAU,GAAIE,GAAW,CAAA,CAAC,CACxC,CAEO,SAASC,EACdC,EACAF,EACQ,CACR,KAAM,CAAE,cAAAG,EAAe,eAAAC,GAAmBL,EAAeC,CAAO,EAChE,MAAO,GAAGG,CAAa,IAAID,CAAO,IAAIE,CAAc,EACtD,CAEA,eAAsBC,EACpBL,EACiB,CACjB,MAAMM,EAAOP,EAAeC,CAAO,EACnC,GAAI,CACF,MAAMO,EAASX,EAAA,EACf,GAAIW,EAAQ,OAAOhB,EAAYgB,EAAQD,EAAK,gBAAgB,EAE5D,MAAMd,EAAW,MAAMJ,EAAoBkB,EAAK,iBAAiB,EACjE,OAAAT,EAAcL,CAAQ,EACfD,EAAYC,EAAUc,EAAK,gBAAgB,CACpD,OAASE,EAAK,CACZ,eAAQ,KACN,sEACAA,CAAA,EAEKF,EAAK,eACd,CACF,CAEA,eAAsBG,EACpBT,EACiB,CACjB,MAAME,EAAU,MAAMG,EAAgCL,CAAO,EAC7D,OAAOC,EAAwBC,EAASF,CAAO,CACjD,CAEA,eAAsBU,EACpBC,EACAX,EACe,CAIf,GAHiBW,EAAW,cAC1B,4CAAA,EAEY,OAEd,MAAMC,EAAO,SAAS,cAAc,MAAM,EAC1CA,EAAK,IAAM,aACXA,EAAK,QAAQ,sBAAwB,OACrCA,EAAK,KAAO,MAAMH,EAA4BT,CAAO,EACrDW,EAAW,QAAQC,CAAI,CACzB,CChFO,MAAMC,EAAc;AAAA;AAAA;AAAA;AAAA,EAMpB,SAASC,EAASC,EAAsB,CAC7C,MAAO,GAAGA,CAAI,mBAChB,CAEO,SAASC,EAAWC,EAAuB,CAChD,OAAOA,EACJ,QAAQ,KAAM,OAAO,EACrB,QAAQ,KAAM,MAAM,EACpB,QAAQ,KAAM,MAAM,EACpB,QAAQ,KAAM,QAAQ,EACtB,QAAQ,KAAM,OAAO,CAC1B,CAuDA,MAAMC,EACJ,gDACIC,EAA2B,IAS3BC,EACJ,qDAGIC,EAAsB,WAE5B,eAAsBC,EACpBC,EACAvB,EACmB,CAEnB,GAAIA,EAAQ,cAAc,OACxB,OAAOwB,EAAqB,CAC1B,GAAGxB,EAAQ,aACX,UAAWA,EAAQ,UACnB,UAAWA,EAAQ,SAAA,CACpB,EAGH,MAAMyB,EAAWzB,EAAQ,UAAYkB,EAC/BQ,EAAY1B,EAAQ,WAAamB,EACjCQ,EAAYC,EAAa5B,EAAQ,SAAS,EAC1C6B,EAAa,IAAI,gBACjBC,EAAY,WAAW,IAAMD,EAAW,MAAA,EAASH,CAAS,EAEhE,GAAI,CACF,MAAMK,EAAW,MAAMJ,EACrB,GAAGF,CAAQ,QAAQ,mBAAmBF,CAAO,CAAC,cAAc,mBAC1DvB,EAAQ,SAAA,CACT,GACD,CAAE,OAAQ6B,EAAW,MAAA,CAAO,EAG9B,GAAI,CAACE,EAAS,GACZ,MAAM,IAAI,MAAM,QAAQA,EAAS,MAAM,EAAE,EAI3C,OAD+B,MAAMA,EAAS,KAAA,GAClC,OAAO,IAAKC,GAASA,EAAK,KAAK,EAAE,OAAO,OAAO,GAAK,CAAA,CAClE,QAAA,CACE,aAAaF,CAAS,CACxB,CACF,CAWA,eAAsBN,EACpBxB,EACmB,CACnB,MAAMyB,EAAWzB,EAAQ,UAAYoB,EAC/BM,EAAY1B,EAAQ,WAAamB,EACjCQ,EAAYC,EAAa5B,EAAQ,SAAS,EAC1C6B,EAAa,IAAI,gBACjBC,EAAY,WAAW,IAAMD,EAAW,MAAA,EAASH,CAAS,EAEhE,GAAI,CAOF,MAAMO,EAAiBjC,EAAQ,OAC3B,CAAE,OAAQA,EAAQ,QAClB,OAGEkC,EAAO,CACX,GAAIlC,EAAQ,UAAY,CAAE,UAAWA,EAAQ,SAAA,EAAc,CAAA,EAC3D,GAAIA,EAAQ,OAAS,CAAE,gBAAiBA,EAAQ,MAAA,EAAW,CAAA,EAC3D,UAAWqB,EACX,GAAIY,EAAiB,CAAE,eAAAA,GAAmB,CAAA,CAAC,EAGvCF,EAAW,MAAMJ,EAAUF,EAAU,CACzC,OAAQ,OACR,QAAS,CACP,eAAgB,mBAChB,OAAQ,mBAAA,EAEV,KAAM,KAAK,UAAUS,CAAI,EACzB,OAAQL,EAAW,MAAA,CACpB,EAED,GAAI,CAACE,EAAS,GACZ,MAAM,IAAI,MAAM,QAAQA,EAAS,MAAM,EAAE,EAK3C,MAAMI,EAAO,MAAMJ,EAAS,KAAA,EAC5B,OAAOK,EAAmBC,EAAuBF,CAAI,CAAC,CACxD,QAAA,CACE,aAAaL,CAAS,CACxB,CACF,CAEA,SAASF,EAAaU,EAAuC,CAC3D,OAAIA,IACG,OAAO,OAAW,IAAc,MAAM,KAAK,MAAM,EAAI,MAC9D,CASA,SAASD,EAAuBE,EAA4B,CAC1D,IAAIC,EAAYD,EAAW,KAAA,EAC3B,GAAI,CAACC,EAAW,MAAO,GACnBA,EAAU,SAAS,GAAG,MAAeA,EAAU,MAAM,EAAG,EAAE,GACzDA,EAAU,WAAW,GAAG,IAAGA,EAAY,IAAIA,CAAS,IACpDA,EAAU,SAAS,GAAG,IAAGA,GAAa,KAC3C,GAAI,CACF,MAAMC,EAAS,KAAK,MAAMD,CAAS,EAInC,GAAI,CAAC,MAAM,QAAQC,CAAM,EAAG,MAAO,GACnC,MAAMC,EAAYD,EAAO,KACtBE,GAAS,OAAOA,GAAM,WAAW,UAAa,QAAA,GAC9C,WAAW,SACd,OAAID,GACGD,EACJ,IAAKE,GAAU,OAAOA,GAAM,UAAa,SAAWA,EAAK,SAAW,EAAG,EACvE,KAAK,EAAE,CACZ,MAAQ,CACN,MAAO,EACT,CACF,CAMA,SAASP,EAAmBQ,EAA4B,CACtD,MAAMC,EAAWD,EACd,KAAA,EACA,QAAQ,oBAAqB,EAAE,EAC/B,QAAQ,UAAW,EAAE,EACrB,KAAA,EACH,GAAI,CAACC,EAAU,MAAO,CAAA,EACtB,GAAI,CAEF,OAD+B,KAAK,MAAMA,CAAQ,EACtC,OAAO,IAAKb,GAASA,EAAK,KAAK,EAAE,OAAO,OAAO,GAAK,CAAA,CAClE,MAAQ,CACN,MAAO,CAAA,CACT,CACF,CCrPO,SAASc,EACdpE,EACa,CACb,GAAIA,aAAkB,YAAa,OAAOA,EAC1C,GAAI,OAAOA,GAAW,SAAU,CAC9B,MAAMqE,EAAK,SAAS,cAA2BrE,CAAM,EACrD,GAAI,CAACqE,EACH,MAAM,IAAI,MAAM,yCAAyCrE,CAAM,EAAE,EAEnE,OAAOqE,CACT,CAGA,MAAMC,EAAS,SAAS,cAClBC,EAAY,SAAS,cAAc,KAAK,EAC9C,OAAID,GAAQ,cACVA,EAAO,cAAc,aAAaC,EAAWD,CAAM,EAEnD,SAAS,KAAK,YAAYC,CAAS,EAE9BA,CACT,CAEO,SAASC,EAAWC,EAAgC,CACzD,OAAKA,EACU,IAAI,gBAAgB,OAAO,SAAS,MAAM,EAC3C,IAAIA,CAAY,EAFJ,EAG5B,CAEO,SAASC,EACdC,EACAC,EAAgC,GACvB,CAMT,MAAO,EALO,MAAM,QAAQD,CAAY,EACpCA,EACAA,GAAgB,KACd,CAACA,CAAY,EACbC,GACQ,SAAS,OAAO,SAAS,QAAQ,CACjD,CAQO,SAASC,EACdP,EACqB,CACrB,GAAI,CAACA,EAAQ,MAAO,CAAA,EAEpB,MAAMQ,EAA8B,CAAA,EAE9B9E,EAASsE,EAAO,aAAa,aAAa,EAC5CtE,MAAe,OAASA,GAE5B,MAAM+E,EAAcT,EAAO,aAAa,kBAAkB,EACtDS,MAAoB,YAAcA,GAEtC,MAAMC,EAAUV,EAAO,aAAa,eAAe,EAC/CU,MAAgB,QAAUA,GAE9B,MAAMP,EAAeH,EAAO,aAAa,oBAAoB,EACzDG,MAAqB,aAAeA,GAExC,MAAMQ,EAAiBX,EAAO,aAAa,kBAAkB,EAC7D,GAAIW,EACF,GAAI,CACF,MAAMlB,EAAS,KAAK,MAAMkB,CAAc,EACpC,MAAM,QAAQlB,CAAM,GAAKA,EAAO,SAClCe,EAAO,YAAcf,EAEzB,MAAQ,CACN,QAAQ,KAAK,sDAAsD,CACrE,CAGF,OAAOe,CACT,CAMO,SAASI,EACdZ,EACiB,CACjB,MAAMQ,EAA0BD,EAAyBP,CAAM,EAC/D,GAAI,CAACA,EAAQ,OAAOQ,EAEpB,MAAMK,EAAiBb,EAAO,aAAa,sBAAsB,GAC7Da,IAAmB,UAAYA,IAAmB,kBACpDL,EAAO,eAAiBK,GAG1B,MAAMC,EAAkBd,EAAO,aAAa,wBAAwB,EAChEc,MAAwB,gBAAkBA,GAE9C,MAAMC,EAAkBf,EAAO,aAAa,oBAAoB,EAChE,OAAIe,IACFP,EAAO,aAAeO,EAAgB,SAAS,GAAG,EAC9CA,EAAgB,MAAM,GAAG,EAAE,IAAKC,GAAMA,EAAE,KAAA,CAAM,EAC9CD,GAGCP,CACT,CC7EO,SAASS,EACd3D,EACM,CACN,GAAI,OAAO,OAAW,IAAa,OACnC,MAAM4D,EAAS,OAAO,eACtB,GAAI,CAACA,EAAQ,CACX,QAAQ,KACN,0DAA0D5D,EAAK,QAAQ,kBAAA,EAEzE,MACF,CACA,MAAM6D,EAAmB,MAAOC,GAC9B9D,EAAK,MAAM8D,EAAK9D,EAAK,aAAa8D,EAAI,OAAO,CAAC,EAChDF,EAAO,SAAS5D,EAAK,SAAU6D,CAAI,CACrC,CAoBO,SAASE,EACd/D,EACuB,CACvB,MAAO,CAAC8D,EAAKZ,IAAW,CACtB,MAAMc,EAAKd,EAAO,QAClB,GAAI,CAACc,GAAMA,IAAO,UAChB,OAAOhE,EAAK,QAAQ8D,EAAKZ,CAAM,EAEjC,MAAMe,EAAUjE,EAAK,WAAWgE,CAAE,EAClC,OAAIC,EACKA,EAAQH,EAAKZ,CAAM,GAE5B,QAAQ,KACN,gCAAgCc,CAAE,6BAAA,EAE7BhE,EAAK,QAAQ8D,EAAKZ,CAAM,EACjC,CACF,CCvEO,SAASgB,GACdC,EACqB,CACrB,MAAMC,EAAOJ,GACXG,IAAiBH,CAAE,EAErB,MAAO,CACL,UAAYA,GAAO,CACjB,MAAMK,EAASD,EAAIJ,CAAE,EACrB,OAAOK,GAAQ,SAAW,cAAgBA,EAAO,UAAY,EAC/D,EACA,IAAAD,CAAA,CAEJ,CCJO,SAASE,GACd5E,EACiB,CACjB,KAAM,CACJ,MAAA6E,EACA,QAAAC,EACA,eAAAC,EACA,YAAAC,EACA,6BAAAC,EAA+B,EAC/B,qBAAAC,EAAuB,EAAA,EACrBlF,EAEJ,MAAO,CACL,MAAO,CACL,GAAI,CAAC8E,EAAS,OAEd,MAAMK,EAAW,OAAO,iBAAiBN,CAAK,EACxCO,EACJ,OAAO,WAAWD,EAAS,UAAU,GAAKD,EAE5CL,EAAM,MAAM,OAAS,OACrB,MAAMQ,EACJR,EAAM,MAAM,KAAA,EAAO,SAAW,EAC1BO,EACA,KAAK,IAAI,KAAK,IAAIP,EAAM,aAAcO,CAAU,EAAGJ,CAAW,EACpEH,EAAM,MAAM,OAAS,GAAGQ,CAAU,KAClCP,EAAQ,UAAU,OAChBC,EACAM,EAAaD,EAAaH,CAAA,CAE9B,CAAA,CAEJ,CC1BA,MAAMK,GAAuB,IAEtB,SAASC,GACdvF,EAAqC,GACjB,CACpB,MAAMwF,EAAYxF,EAAQ,aAAesF,GACzC,IAAIG,EAAmB,GAEvB,SAASC,GAA+B,CACtC,MAAMC,EAAM,SAAS,gBACfC,EAAY,OAAO,SAAWD,EAAI,WAAa,EAC/CE,EAAiBD,EAAY,OAAO,YACpCE,EAAmBH,EAAI,aAAeE,EAG5C,OADEF,EAAI,aAAe,OAAO,YAAcH,GAEpBI,EAAY,GAAKE,GAAoBN,CAE7D,CAEA,MAAO,CACL,SAASO,EAAO,CAMd,MALI,CAACA,EAAM,wBAKP,CAACL,KACHD,EAAmB,GACZ,IAGLM,EAAM,kBAKNN,EACKM,EAAM,YAAA,EAGR,EACT,EACA,sBAAuB,CACrBN,EAAmB,EACrB,EACA,mBAAoB,CAClBA,EAAmB,EACrB,CAAA,CAEJ,CC/DO,SAASO,GAAiBhG,EAAwC,CACvE,GAAI,CAACA,EAAQ,QACX,MAAM,IAAI,MACR,4DAAA,EAGJ,MAAMiG,EAASnF,EAASd,EAAQ,OAAO,EACjCtB,EAASsB,EAAQ,cAAgB,QAEvC,MAAO,CACL,KAAKiB,EAAO,CACV,cAAO,KAAKgF,EAAS,mBAAmBhF,CAAK,EAAGvC,CAAM,EAC/C,EACT,CAAA,CAEJ,CCTA,MAAMwH,GAAmB,IAGnBC,GAA4B,GAO5BC,GAAsB,GAGfC,EAAwB,2BA0B9B,SAASC,EAAuBC,EAAkC,CACvE,GAAI,OAAO,SAAa,KAAe,OAAO,OAAW,IACvD,MAAO,GAGT,MAAMC,MAAiB,IACvB,UAAWC,IAAY,CAAC,WAAY,cAAc,EAChD,SAAS,iBAAiBA,CAAQ,EAAE,QAAS1D,GAAOyD,EAAW,IAAIzD,CAAE,CAAC,EAGxE,IAAI2D,EAAQ,EAEZ,UAAW3D,KAAMyD,EAAY,CAC3B,GAAID,GAAW,OAASA,EAAQ,SAASxD,CAAE,GAAKA,EAAG,SAASwD,CAAO,GACjE,SAGF,MAAMI,EAAS,OAAO,iBAAiB5D,CAAE,EAIzC,GAHI4D,EAAO,WAAa,SAAWA,EAAO,WAAa,UAGnDA,EAAO,aAAe,UAAYA,EAAO,SAAW,OACtD,SAEF,MAAMC,EAAS,OAAO,WAAWD,EAAO,MAAM,EAC9C,GAAI,CAAC,OAAO,SAASC,CAAM,GAAKA,EAASV,GACvC,SAGF,MAAMW,EAAO9D,EAAG,sBAAA,EAChB,GAAI8D,EAAK,QAAU,GAAKA,EAAK,MAAQ,OAAO,WAAaT,GACvD,SAKF,MAAMU,EAAU,OAAO,YAAcD,EAAK,IACtCC,GAAW,GAAKA,EAAU,OAAO,YAAcX,KAInDO,EAAQ,KAAK,IAAIA,EAAOI,CAAO,EACjC,CAEA,OAAO,KAAK,MAAMJ,CAAK,CACzB,CAyBO,SAASK,GACdrI,EACAsB,EAAuC,GAC3B,CACZ,GAAI,OAAO,OAAW,IACpB,MAAO,OAGT,MAAMuG,EAAUvG,EAAQ,UAAY,OAAYtB,EAASsB,EAAQ,QACjE,IAAIgH,EAAQ,EACRC,EAAU,GAEd,MAAMC,EAAQ,IAAY,CACxBF,EAAQ,EACJ,CAAAC,GAGJvI,EAAO,MAAM,YAAY2H,EAAuB,GAAGC,EAAuBC,CAAO,CAAC,IAAI,CACxF,EAEMY,EAAW,IAAY,CACvBF,GAAWD,IAAU,IAGzBA,EAAQ,OAAO,sBAAsBE,CAAK,EAC5C,EAEAA,EAAA,EAEA,OAAO,iBAAiB,SAAUC,EAAU,CAAE,QAAS,GAAM,EAC7D,OAAO,iBAAiB,oBAAqBA,EAAU,CAAE,QAAS,GAAM,EAExE,IAAIC,EACJ,OAAI,OAAO,iBAAqB,KAAe,SAAS,MAAQ,OAC9DA,EAAY,IAAI,iBAAiBD,CAAQ,EACzCC,EAAU,QAAQ,SAAS,KAAM,CAAE,UAAW,GAAM,QAAS,GAAO,GAG/D,IAAM,CACXH,EAAU,GACND,IAAU,IACZ,OAAO,qBAAqBA,CAAK,EACjCA,EAAQ,GAEV,OAAO,oBAAoB,SAAUG,CAAQ,EAC7C,OAAO,oBAAoB,oBAAqBA,CAAQ,EACxDC,GAAW,WAAA,CACb,CACF"}
|
|
1
|
+
{"version":3,"file":"index.cjs","sources":["../src/focusShortcut.ts","../src/staticVersions.ts","../src/promptInputTokens.ts","../src/shared.ts","../src/script-tag.ts","../src/loader-register.ts","../src/featureToggles.ts","../src/widget-hooks/inputHeightSync.ts","../src/widget-hooks/collapseController.ts","../src/widget-hooks/submitFlow.ts","../src/hostChrome.ts"],"sourcesContent":["/**\n * Cross-bundle singleton that coordinates \"/\" and Cmd/Ctrl+K focus shortcuts\n * across multiple Web5 widgets on the same host page.\n *\n * Stored on `window` under a global key so widgets from different bundles\n * (legacy embed-input + new per-client embed) can share one keyboard listener.\n */\n\nconst GLOBAL_KEY = '__web5InputFocusShortcut__' as const;\nconst EDITABLE_SELECTOR =\n 'input, textarea, [contenteditable=\"\"], [contenteditable=\"true\"]';\nconst MODAL_SELECTOR = '[role=\"dialog\"][aria-modal=\"true\"]';\n\nexport interface FocusShortcutTarget {\n focus(previousActive: HTMLElement | null): void;\n isVisible(): boolean;\n}\n\nexport interface FocusShortcutCoordinator {\n register(target: FocusShortcutTarget): void;\n unregister(target: FocusShortcutTarget): void;\n}\n\ninterface InternalCoordinator extends FocusShortcutCoordinator {\n __teardown(): void;\n}\n\ninterface CoordinatorWindow {\n [GLOBAL_KEY]?: InternalCoordinator;\n}\n\nfunction isModalVisible(modal: Element): boolean {\n if (modal.hasAttribute('inert')) return false;\n if (typeof (modal as HTMLElement).checkVisibility === 'function') {\n return (modal as HTMLElement).checkVisibility({\n checkOpacity: true,\n checkVisibilityCSS: true,\n });\n }\n const cs = getComputedStyle(modal as HTMLElement);\n if (\n cs.visibility === 'hidden' ||\n cs.display === 'none' ||\n parseFloat(cs.opacity) === 0\n ) {\n return false;\n }\n return true;\n}\n\nfunction pathContainsEditable(event: KeyboardEvent): boolean {\n const path = event.composedPath();\n for (const node of path) {\n if (\n node instanceof Element &&\n typeof node.matches === 'function' &&\n node.matches(EDITABLE_SELECTOR)\n ) {\n return true;\n }\n }\n return false;\n}\n\nexport function getFocusShortcutCoordinator(): FocusShortcutCoordinator {\n const w = window as unknown as CoordinatorWindow;\n if (w[GLOBAL_KEY]) return w[GLOBAL_KEY]!;\n\n const targets: FocusShortcutTarget[] = [];\n let listenerAttached = false;\n\n const handleKeyDown = (e: KeyboardEvent) => {\n if (e.defaultPrevented || e.isComposing) return;\n\n const isSlash =\n e.key === '/' &&\n !e.metaKey &&\n !e.ctrlKey &&\n !e.altKey &&\n !e.shiftKey;\n const isCmdK =\n (e.metaKey || e.ctrlKey) &&\n !e.altKey &&\n e.key.toLowerCase() === 'k';\n\n if (!isSlash && !isCmdK) return;\n\n if (isSlash && pathContainsEditable(e)) return;\n\n const modals = document.querySelectorAll(MODAL_SELECTOR);\n for (const modal of modals) {\n if (isModalVisible(modal)) return;\n }\n\n const target = targets.find((t) => t.isVisible());\n if (!target) return;\n\n e.preventDefault();\n const previousActive =\n document.activeElement instanceof HTMLElement\n ? document.activeElement\n : null;\n target.focus(previousActive);\n };\n\n const ensureListener = () => {\n if (listenerAttached) return;\n window.addEventListener('keydown', handleKeyDown);\n listenerAttached = true;\n };\n\n const dropListenerIfEmpty = () => {\n if (!listenerAttached || targets.length > 0) return;\n window.removeEventListener('keydown', handleKeyDown);\n listenerAttached = false;\n };\n\n const coordinator: InternalCoordinator = {\n register(target) {\n if (!targets.includes(target)) targets.push(target);\n ensureListener();\n },\n unregister(target) {\n const index = targets.indexOf(target);\n if (index >= 0) targets.splice(index, 1);\n dropListenerIfEmpty();\n },\n __teardown() {\n targets.length = 0;\n if (listenerAttached) {\n window.removeEventListener('keydown', handleKeyDown);\n listenerAttached = false;\n }\n },\n };\n\n w[GLOBAL_KEY] = coordinator;\n return coordinator;\n}\n\nexport function __resetFocusShortcutCoordinatorForTests(): void {\n const w = window as unknown as CoordinatorWindow;\n w[GLOBAL_KEY]?.__teardown();\n delete w[GLOBAL_KEY];\n}\n","/**\n * Resolves artifact versions from a remote `static-versions` JSON endpoint.\n *\n * The default URL points at Wix infra (`wixapis.com`), matching legacy\n * embed-input behaviour. Non-Wix consumers pass their own URL.\n */\n\nimport type { ArtifactVersion } from './types';\n\nexport const DEFAULT_STATIC_VERSIONS_URL =\n 'https://www.wixapis.com/web50-server/static-versions';\n\nconst CACHE_KEY = '__web5_static_versions';\n\ninterface CacheWindow {\n [CACHE_KEY]?: ArtifactVersion[];\n}\n\nexport async function fetchStaticVersions(\n url: string = DEFAULT_STATIC_VERSIONS_URL,\n): Promise<ArtifactVersion[]> {\n const res = await fetch(url, {\n method: 'GET',\n headers: { Accept: 'application/json' },\n mode: 'cors',\n credentials: 'omit',\n });\n if (!res.ok) {\n throw new Error(`[Web5Embed] static-versions returned ${res.status}`);\n }\n return res.json();\n}\n\nexport function findVersion(\n versions: ArtifactVersion[],\n artifactId: string,\n): string {\n const entry = versions.find((v) => v.artifactId === artifactId);\n if (!entry) {\n throw new Error(\n `[Web5Embed] artifact ${artifactId} not found in static-versions`,\n );\n }\n return entry.version;\n}\n\nexport function getCachedVersions(): ArtifactVersion[] | undefined {\n return (window as unknown as CacheWindow)[CACHE_KEY];\n}\n\nexport function cacheVersions(versions: ArtifactVersion[]): void {\n (window as unknown as CacheWindow)[CACHE_KEY] = versions;\n}\n","/**\n * Resolves and attaches the `prompt-input-tokens.css` design-token sheet to a\n * widget's shadow root. The sheet is owned by `web50-server-ui` and provides\n * the `--pi-*` CSS variables consumed by widget styles.\n *\n * Defaults match Wix infra; consumers can fully override for non-Wix\n * environments (e.g. self-hosted CSS in CI / partner deployments).\n */\n\nimport {\n cacheVersions,\n fetchStaticVersions,\n findVersion,\n getCachedVersions,\n} from './staticVersions';\n\nexport interface PromptInputTokensOptions {\n /** Static-versions endpoint to resolve the version from. */\n staticVersionsUrl?: string;\n /** CDN base where the tokens artifact is published. */\n tokensCdnBase?: string;\n /** Tokens artifact ID looked up in `static-versions`. */\n tokensArtifactId?: string;\n /** File name of the tokens stylesheet. */\n tokensFileName?: string;\n /** Version used when resolution fails. */\n fallbackVersion?: string;\n}\n\nconst DEFAULTS: Required<PromptInputTokensOptions> = {\n staticVersionsUrl: 'https://www.wixapis.com/web50-server/static-versions',\n tokensCdnBase: 'https://static.parastorage.com/services/web50-server-ui',\n tokensArtifactId: 'com.wixpress.web50-server-ui',\n tokensFileName: 'prompt-input-tokens.css',\n fallbackVersion: '1.459.0',\n};\n\nfunction resolveOptions(\n options?: PromptInputTokensOptions,\n): Required<PromptInputTokensOptions> {\n return { ...DEFAULTS, ...(options ?? {}) };\n}\n\nexport function getPromptInputTokensUrl(\n version: string,\n options?: PromptInputTokensOptions,\n): string {\n const { tokensCdnBase, tokensFileName } = resolveOptions(options);\n return `${tokensCdnBase}/${version}/${tokensFileName}`;\n}\n\nexport async function resolvePromptInputTokensVersion(\n options?: PromptInputTokensOptions,\n): Promise<string> {\n const opts = resolveOptions(options);\n try {\n const cached = getCachedVersions();\n if (cached) return findVersion(cached, opts.tokensArtifactId);\n\n const versions = await fetchStaticVersions(opts.staticVersionsUrl);\n cacheVersions(versions);\n return findVersion(versions, opts.tokensArtifactId);\n } catch (err) {\n console.warn(\n '[Web5Embed] Failed to resolve prompt token version, using fallback:',\n err,\n );\n return opts.fallbackVersion;\n }\n}\n\nexport async function resolvePromptInputTokensUrl(\n options?: PromptInputTokensOptions,\n): Promise<string> {\n const version = await resolvePromptInputTokensVersion(options);\n return getPromptInputTokensUrl(version, options);\n}\n\nexport async function attachPromptInputTokensStylesheet(\n shadowRoot: ShadowRoot,\n options?: PromptInputTokensOptions,\n): Promise<void> {\n const existing = shadowRoot.querySelector<HTMLLinkElement>(\n 'link[data-web5-prompt-input-tokens=\"true\"]',\n );\n if (existing) return;\n\n const link = document.createElement('link');\n link.rel = 'stylesheet';\n link.dataset.web5PromptInputTokens = 'true';\n link.href = await resolvePromptInputTokensUrl(options);\n shadowRoot.prepend(link);\n}\n","/**\n * Shared utilities for widget rendering and chip-suggestion fetching.\n *\n * The default Wix `DEFAULT_BASE_URL` from legacy embed-input is removed —\n * each consumer must supply its own `baseUrl`.\n */\n\nexport interface ChipsApiResponse {\n chips?: { label: string }[];\n}\n\n/** Default arrow-up SVG used as the submit/chip icon. */\nexport const sendIconSvg = `\n <svg class=\"web5-submit-icon\" xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 16 16\" fill=\"none\" aria-hidden=\"true\">\n <path d=\"M13.0306 7.53114C12.9609 7.60106 12.8781 7.65654 12.787 7.69439C12.6958 7.73224 12.5981 7.75173 12.4993 7.75173C12.4006 7.75173 12.3029 7.73224 12.2117 7.69439C12.1206 7.65654 12.0378 7.60106 11.9681 7.53114L8.74997 4.31301V13.5005C8.74997 13.6994 8.67095 13.8902 8.5303 14.0308C8.38965 14.1715 8.19888 14.2505 7.99997 14.2505C7.80106 14.2505 7.61029 14.1715 7.46964 14.0308C7.32899 13.8902 7.24997 13.6994 7.24997 13.5005V4.31301L4.0306 7.53114C3.8897 7.67203 3.69861 7.75119 3.49935 7.75119C3.30009 7.75119 3.10899 7.67203 2.9681 7.53114C2.8272 7.39024 2.74805 7.19915 2.74805 6.99989C2.74805 6.80063 2.8272 6.60953 2.9681 6.46864L7.4681 1.96864C7.53778 1.89872 7.62057 1.84324 7.71173 1.80539C7.8029 1.76753 7.90064 1.74805 7.99935 1.74805C8.09806 1.74805 8.1958 1.76753 8.28696 1.80539C8.37813 1.84324 8.46092 1.89872 8.5306 1.96864L13.0306 6.46864C13.1005 6.53832 13.156 6.62111 13.1938 6.71227C13.2317 6.80344 13.2512 6.90118 13.2512 6.99989C13.2512 7.0986 13.2317 7.19634 13.1938 7.2875C13.156 7.37867 13.1005 7.46146 13.0306 7.53114Z\" fill=\"currentColor\" />\n </svg>\n`;\n\nexport function buildUrl(base: string): string {\n return `${base}?initialQuestion=`;\n}\n\nexport function escapeHtml(value: string): string {\n return value\n .replace(/&/g, '&')\n .replace(/</g, '<')\n .replace(/>/g, '>')\n .replace(/\"/g, '"')\n .replace(/'/g, ''');\n}\n\n/**\n * Conversation identity + context for the warm (conversation-aware) chips\n * path. `web5-embed` is a dependency-light SDK with no `@wix/web5-core`\n * dependency, so the caller (the per-client widget, which already imports\n * web5-core) injects the identity (`chatId` / `sessionId`) it generates with\n * `getChatId` / `getOrCreateSessionId`.\n */\nexport interface ContextualChipsContext {\n /** Conversation trigger id from the page URL (`web5_id`). Maps to `Turn`'s\n * `parentMessageId`. */\n web5Id: string;\n /**\n * Product handle (entity id) for the current page. Sent structurally as\n * `additionalData.handle` (like the `pdpFraming`/`pdpItemsForYou` system\n * placements) so the BE resolves the product from it. Omitted from the\n * request when absent — no user query is sent for chips.\n */\n handle?: string;\n /** Session id (Turn `sessionId`). */\n sessionId?: string;\n /** Override the endpoint (tests). */\n endpoint?: string;\n}\n\nexport interface FetchChipSuggestionsOptions {\n /**\n * Session id (GetChips `sessionId`). Sent on the cold-path request. The\n * dependency-light SDK has no `@wix/web5-core`, so the caller injects the id\n * it generates with `getOrCreateSessionId`.\n */\n sessionId: string;\n /** Endpoint that accepts `?url=...` and returns ChipsApiResponse. */\n endpoint?: string;\n /** Abort timeout in ms. */\n timeoutMs?: number;\n /** Override `fetch` (tests). */\n fetchImpl?: typeof fetch;\n /**\n * When set, the page is \"warm\" (the visitor arrived from a Web5 turn,\n * `?web5_id=` present) and chips are generated conversation-aware via the\n * `/v1/turn` endpoint instead of the URL-only page-enrichment endpoint\n * (B2B-4121).\n */\n conversation?: ContextualChipsContext;\n}\n\nexport interface FetchContextualChipsOptions extends ContextualChipsContext {\n /** Abort timeout in ms. */\n timeoutMs?: number;\n /** Override `fetch` (tests). */\n fetchImpl?: typeof fetch;\n}\n\nconst DEFAULT_CHIPS_ENDPOINT =\n 'https://www.wixapis.com/page-enrichment/v1/page-meta-data/chips';\nconst DEFAULT_CHIPS_TIMEOUT_MS = 5000;\n\n/**\n * The `wix-assistant-web5` `Turn` endpoint (DL #110). `Turn` takes no\n * per-chat lock, so chips run in parallel with the placement answer — no\n * client-side serialization needed. Hand-rolled here (not via the ambassador\n * npm client) to keep the thin input-widget bundle light, the same reason\n * `placementFetch` hand-rolls.\n */\nconst DEFAULT_TURN_ENDPOINT =\n 'https://www.wixapis.com/wix-assistant-web5/v1/turn';\n\n/** Placement key the BE resolves the chips prompt by (B2B-4121). */\nconst PDP_CHIPS_PLACEMENT = 'pdpChips';\n\nexport async function fetchChipSuggestions(\n pageUrl: string,\n options: FetchChipSuggestionsOptions,\n): Promise<string[]> {\n // Warm page: generate conversation-aware chips from the prompt (B2B-4121).\n if (options.conversation?.web5Id) {\n return fetchContextualChips({\n ...options.conversation,\n timeoutMs: options.timeoutMs,\n fetchImpl: options.fetchImpl,\n });\n }\n\n const endpoint = options.endpoint ?? DEFAULT_CHIPS_ENDPOINT;\n const timeoutMs = options.timeoutMs ?? DEFAULT_CHIPS_TIMEOUT_MS;\n const fetchImpl = resolveFetch(options.fetchImpl);\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), timeoutMs);\n\n try {\n const response = await fetchImpl(\n `${endpoint}?url=${encodeURIComponent(pageUrl)}&sessionId=${encodeURIComponent(\n options.sessionId,\n )}`,\n { signal: controller.signal },\n );\n\n if (!response.ok) {\n throw new Error(`HTTP ${response.status}`);\n }\n\n const data: ChipsApiResponse = await response.json();\n return data.chips?.map((chip) => chip.label).filter(Boolean) ?? [];\n } finally {\n clearTimeout(timeoutId);\n }\n}\n\n/**\n * Generate conversation-aware chip suggestions for the warm path (B2B-4121).\n *\n * POSTs a `TurnRequest` tagged `placement: 'pdpChips'` so the BE resolves the\n * chips prompt by key and links the turn to the prior conversation via\n * `parentMessageId`. The prompt is authored to return JSON in the\n * `ChipsApiResponse` shape, so the labels parse the same way as the URL-only\n * endpoint.\n */\nexport async function fetchContextualChips(\n options: FetchContextualChipsOptions,\n): Promise<string[]> {\n const endpoint = options.endpoint ?? DEFAULT_TURN_ENDPOINT;\n const timeoutMs = options.timeoutMs ?? DEFAULT_CHIPS_TIMEOUT_MS;\n const fetchImpl = resolveFetch(options.fetchImpl);\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), timeoutMs);\n\n try {\n // `pdpChips` is a system placement — it sends NO user query. The BE\n // resolves the product structurally from `placement: 'pdpChips'` (the\n // chips-prompt key) + the handle in `additionalData`, and links to the\n // prior conversation via `parentMessageId`. Mirrors the entity placements\n // in `embed-placement`'s `placementFetch` (`pdpFraming`/`pdpItemsForYou`),\n // which likewise omit `text`.\n const additionalData = options.handle\n ? { handle: options.handle }\n : undefined;\n\n // `TurnRequest` shape (DL #110) — lock-free.\n const body = {\n ...(options.sessionId ? { sessionId: options.sessionId } : {}),\n ...(options.web5Id ? { parentMessageId: options.web5Id } : {}),\n placement: PDP_CHIPS_PLACEMENT,\n ...(additionalData ? { additionalData } : {}),\n };\n\n const response = await fetchImpl(endpoint, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n Accept: 'text/event-stream',\n },\n body: JSON.stringify(body),\n signal: controller.signal,\n });\n\n if (!response.ok) {\n throw new Error(`HTTP ${response.status}`);\n }\n\n // Chips are short — read the stream to completion, then parse the JSON the\n // prompt emitted out of the accumulated markdown.\n const text = await response.text();\n return parseChipsFromJson(accumulateTurnMarkdown(text));\n } finally {\n clearTimeout(timeoutId);\n }\n}\n\nfunction resolveFetch(override?: typeof fetch): typeof fetch {\n if (override) return override;\n return typeof window !== 'undefined' ? fetch.bind(window) : fetch;\n}\n\n/**\n * Concatenate the markdown out of a `Turn` JSON-array stream. Each\n * `TurnResponse` is a oneof: `markdown` (a delta) or `completed.markdown` (the\n * full final text). Prefer `completed.markdown` when present, else join the\n * deltas. (`turn`/`event` variants carry no markdown.) Tolerant enough to parse\n * the completed body in one shot, like `placementFetch`'s array parser.\n */\nfunction accumulateTurnMarkdown(streamText: string): string {\n let candidate = streamText.trim();\n if (!candidate) return '';\n if (candidate.endsWith(',')) candidate = candidate.slice(0, -1);\n if (!candidate.startsWith('[')) candidate = `[${candidate}`;\n if (!candidate.endsWith(']')) candidate += ']';\n try {\n const parsed = JSON.parse(candidate) as {\n markdown?: string;\n completed?: { markdown?: string };\n }[];\n if (!Array.isArray(parsed)) return '';\n const completed = parsed.find(\n (item) => typeof item?.completed?.markdown === 'string',\n )?.completed?.markdown;\n if (completed) return completed;\n return parsed\n .map((item) => (typeof item?.markdown === 'string' ? item.markdown : ''))\n .join('');\n } catch {\n return '';\n }\n}\n\n/**\n * Parse chip labels from the prompt's JSON output (the `ChipsApiResponse`\n * shape), tolerating an optional ```json code fence.\n */\nfunction parseChipsFromJson(markdown: string): string[] {\n const unfenced = markdown\n .trim()\n .replace(/^```(?:json)?\\s*/i, '')\n .replace(/\\s*```$/, '')\n .trim();\n if (!unfenced) return [];\n try {\n const data: ChipsApiResponse = JSON.parse(unfenced);\n return data.chips?.map((chip) => chip.label).filter(Boolean) ?? [];\n } catch {\n return [];\n }\n}\n","/**\n * Helpers shared by every per-client embed `main.ts` for parsing host-page\n * script-tag attributes and gating widget initialization.\n */\n\nimport type { Web5EmbedBaseConfig, Web5EmbedConfig } from './types';\n\nexport function resolveTarget(\n target: string | HTMLElement | undefined,\n): HTMLElement {\n if (target instanceof HTMLElement) return target;\n if (typeof target === 'string') {\n const el = document.querySelector<HTMLElement>(target);\n if (!el) {\n throw new Error(`[Web5Embed] Target element not found: ${target}`);\n }\n return el;\n }\n\n // No target specified — create a container next to the script tag.\n const script = document.currentScript;\n const container = document.createElement('div');\n if (script?.parentElement) {\n script.parentElement.insertBefore(container, script);\n } else {\n document.body.appendChild(container);\n }\n return container;\n}\n\nexport function shouldInit(requireParam?: string): boolean {\n if (!requireParam) return true;\n const params = new URLSearchParams(window.location.search);\n return params.has(requireParam);\n}\n\nexport function shouldInitForPath(\n excludePaths: string | string[] | undefined,\n defaultExcludePaths: string[] = [],\n): boolean {\n const paths = Array.isArray(excludePaths)\n ? excludePaths\n : excludePaths != null\n ? [excludePaths]\n : defaultExcludePaths;\n return !paths.includes(window.location.pathname);\n}\n\n/**\n * Reads the standard `Web5EmbedBaseConfig` data-attributes from a script tag.\n * Per-client `main.ts` files extend this with their own widget-specific\n * attributes (visibility mode, copy props, etc.) by reading additional\n * attributes from the same script.\n */\nexport function readBaseConfigFromScript(\n script: HTMLOrSVGScriptElement | null,\n): Web5EmbedBaseConfig {\n if (!script) return {};\n\n const config: Web5EmbedBaseConfig = {};\n\n const target = script.getAttribute('data-target');\n if (target) config.target = target;\n\n const placeholder = script.getAttribute('data-placeholder');\n if (placeholder) config.placeholder = placeholder;\n\n const baseUrl = script.getAttribute('data-base-url');\n if (baseUrl) config.baseUrl = baseUrl;\n\n const requireParam = script.getAttribute('data-require-param');\n if (requireParam) config.requireParam = requireParam;\n\n const suggestionsRaw = script.getAttribute('data-suggestions');\n if (suggestionsRaw) {\n try {\n const parsed = JSON.parse(suggestionsRaw);\n if (Array.isArray(parsed) && parsed.length) {\n config.suggestions = parsed;\n }\n } catch {\n console.warn('[Web5Embed] Invalid data-suggestions JSON, ignoring.');\n }\n }\n\n return config;\n}\n\n/**\n * Reads the bottom-floating widget's extended config (visibility mode,\n * showAfterTarget, excludePaths) on top of the base config.\n */\nexport function readBottomConfigFromScript(\n script: HTMLOrSVGScriptElement | null,\n): Web5EmbedConfig {\n const config: Web5EmbedConfig = readBaseConfigFromScript(script);\n if (!script) return config;\n\n const visibilityMode = script.getAttribute('data-visibility-mode');\n if (visibilityMode === 'always' || visibilityMode === 'after-target') {\n config.visibilityMode = visibilityMode;\n }\n\n const showAfterTarget = script.getAttribute('data-show-after-target');\n if (showAfterTarget) config.showAfterTarget = showAfterTarget;\n\n const excludePathsRaw = script.getAttribute('data-exclude-paths');\n if (excludePathsRaw) {\n config.excludePaths = excludePathsRaw.includes(',')\n ? excludePathsRaw.split(',').map((p) => p.trim())\n : excludePathsRaw;\n }\n\n return config;\n}\n","/**\n * `registerWeb5Widget` — thin helper that per-client `main.ts` shims use to\n * register a widget against `window.__web5Loader__` (DL #089). Centralizes the\n * \"register-or-warn\" boilerplate and the `ctx.options → typed config` shape\n * coercion so each per-client entry file is a 5-line shim.\n *\n * `createVariantMount` — small dispatch helper for per-client bundles that\n * expose more than one rendering variant of the same widget id (e.g. circana's\n * default bottom-floating input + a homepage hero variant). The SDK does not\n * enumerate variant ids; each client owns its own.\n */\n\nimport type { LoaderContext, WidgetInit, WidgetInstance } from './loader-types';\n\nexport interface RegisterWidgetOptions<TConfig> {\n /**\n * The widget id under which to register. Must match a key in\n * `Configuration.widgetBundles` on the BE / mock config.\n */\n widgetId: string;\n /**\n * Cast / parse the loader's raw `ctx.options` into the client's typed\n * config. The loader passes `spec.options` through verbatim, so this is\n * usually a near-no-op cast.\n */\n parseOptions: (options: Record<string, unknown>) => TConfig;\n /**\n * Mount the widget. Return value supplies the lifecycle hooks the loader\n * uses on SPA route changes. May return synchronously or as a promise —\n * the `WidgetInit` wrapper awaits either form.\n */\n mount: (\n ctx: LoaderContext,\n config: TConfig,\n ) => WidgetInstance | Promise<WidgetInstance>;\n}\n\nexport function registerWeb5Widget<TConfig>(\n opts: RegisterWidgetOptions<TConfig>,\n): void {\n if (typeof window === 'undefined') return;\n const loader = window.__web5Loader__;\n if (!loader) {\n console.warn(\n `[Web5Embed] window.__web5Loader__ not present; widget \"${opts.widgetId}\" will not mount`,\n );\n return;\n }\n const init: WidgetInit = async (ctx) =>\n opts.mount(ctx, opts.parseOptions(ctx.options));\n loader.register(opts.widgetId, init);\n}\n\nexport type VariantMount<TConfig extends { variant?: string }> = (\n ctx: LoaderContext,\n config: TConfig,\n) => WidgetInstance | Promise<WidgetInstance>;\n\nexport interface VariantMountOptions<TConfig extends { variant?: string }> {\n /** Mount used when `config.variant` is undefined, `'default'`, or unknown. */\n default: VariantMount<TConfig>;\n /** Optional named variants, keyed by the string value of `config.variant`. */\n variants?: Record<string, VariantMount<TConfig>>;\n}\n\n/**\n * Build a mount function that dispatches on `config.variant`. Falls back to\n * `default` (with a console warning) when an unknown variant id is supplied —\n * the loader's route rule is the source of truth, and unknown variants\n * typically indicate a config/bundle version mismatch.\n */\nexport function createVariantMount<TConfig extends { variant?: string }>(\n opts: VariantMountOptions<TConfig>,\n): VariantMount<TConfig> {\n return (ctx, config) => {\n const id = config.variant;\n if (!id || id === 'default') {\n return opts.default(ctx, config);\n }\n const handler = opts.variants?.[id];\n if (handler) {\n return handler(ctx, config);\n }\n console.warn(\n `[Web5Embed] unknown variant \"${id}\" — falling back to default`,\n );\n return opts.default(ctx, config);\n };\n}\n","export type EmbedFeatureToggleStatus =\n | 'SUCCESSFUL'\n | 'MISSING_SEED'\n | 'DID_NOT_FIND_ELIGIBLE_FILTER'\n | 'ERROR';\n\nexport interface EmbedFeatureToggleResult {\n id: string;\n enabled: boolean;\n status: EmbedFeatureToggleStatus;\n}\n\nexport interface FeatureToggleReader {\n isEnabled: (id: string) => boolean;\n get: (id: string) => EmbedFeatureToggleResult | undefined;\n}\n\nexport function createFeatureToggleReader(\n featureToggles: Record<string, EmbedFeatureToggleResult> | undefined,\n): FeatureToggleReader {\n const get = (id: string): EmbedFeatureToggleResult | undefined =>\n featureToggles?.[id];\n\n return {\n isEnabled: (id) => {\n const result = get(id);\n return result?.status === 'SUCCESSFUL' && result.enabled === true;\n },\n get,\n };\n}\n","/**\n * Auto-resizing textarea height with a `multiline` class toggle on a wrapper.\n *\n * Pattern: textarea grows from `lineHeight` up to `maxHeightPx`. When growth\n * happens the wrapper element receives `multilineClass` so callers can\n * adjust padding/border-radius for the multi-line state.\n */\n\nexport interface InputHeightSyncOptions {\n input: HTMLTextAreaElement;\n /** Element that should toggle the `multiline` class. */\n wrapper: Element | null;\n /** Class added/removed on `wrapper` when input grows past one line. */\n multilineClass: string;\n /** Max height in px the input is allowed to grow to. */\n maxHeightPx: number;\n /** Multiplier applied to lineHeight to decide when to flip to multiline. Default 1. */\n multilineThresholdMultiplier?: number;\n /** Fallback lineHeight if computed style is unparsable. */\n fallbackLineHeightPx?: number;\n}\n\nexport interface InputHeightSync {\n sync(): void;\n}\n\nexport function createInputHeightSync(\n options: InputHeightSyncOptions,\n): InputHeightSync {\n const {\n input,\n wrapper,\n multilineClass,\n maxHeightPx,\n multilineThresholdMultiplier = 1,\n fallbackLineHeightPx = 32,\n } = options;\n\n return {\n sync() {\n if (!wrapper) return;\n\n const computed = window.getComputedStyle(input);\n const lineHeight =\n Number.parseFloat(computed.lineHeight) || fallbackLineHeightPx;\n\n input.style.height = 'auto';\n const nextHeight =\n input.value.trim().length === 0\n ? lineHeight\n : Math.min(Math.max(input.scrollHeight, lineHeight), maxHeightPx);\n input.style.height = `${nextHeight}px`;\n wrapper.classList.toggle(\n multilineClass,\n nextHeight > lineHeight * multilineThresholdMultiplier,\n );\n },\n };\n}\n","/**\n * Collapse controller for a bottom-fixed prompt widget.\n *\n * Tracks the scroll position relative to the bottom of the document and\n * collapses the widget when within `thresholdPx` of the page bottom — but\n * only if the widget itself doesn't currently hold focus and hasn't already\n * been manually expanded inside the bottom zone.\n *\n * Caller is responsible for binding scroll/resize listeners; this controller\n * just exposes pure state transitions.\n */\n\nexport interface CollapseControllerOptions {\n /** Distance in px from the bottom of the document at which to collapse. */\n thresholdPx?: number;\n}\n\nexport interface CollapseControllerState {\n isCollapsed(): boolean;\n isCoordinatorVisible(): boolean;\n hasFocusInside(): boolean;\n}\n\nexport interface CollapseController {\n /** Should the widget collapse right now, given current state? */\n evaluate(state: CollapseControllerState): boolean;\n /** Mark that the user has expanded the widget while in the bottom zone. */\n markManuallyExpanded(): void;\n /** Reset manual-expand memory (e.g. when leaving the bottom zone). */\n resetManualExpand(): void;\n}\n\nconst DEFAULT_THRESHOLD_PX = 400;\n\nexport function createCollapseController(\n options: CollapseControllerOptions = {},\n): CollapseController {\n const threshold = options.thresholdPx ?? DEFAULT_THRESHOLD_PX;\n let manuallyExpanded = false;\n\n function isBottomZoneVisible(): boolean {\n const doc = document.documentElement;\n const scrollTop = window.scrollY || doc.scrollTop || 0;\n const viewportBottom = scrollTop + window.innerHeight;\n const distanceToBottom = doc.scrollHeight - viewportBottom;\n const hasEnoughContent =\n doc.scrollHeight > window.innerHeight + threshold;\n return (\n hasEnoughContent && scrollTop > 0 && distanceToBottom <= threshold\n );\n }\n\n return {\n evaluate(state) {\n if (!state.isCoordinatorVisible()) {\n manuallyExpanded = false;\n return false;\n }\n\n if (!isBottomZoneVisible()) {\n manuallyExpanded = false;\n return false;\n }\n\n if (state.hasFocusInside()) {\n // Preserve current state; caller should not re-collapse while focused.\n return state.isCollapsed();\n }\n\n if (manuallyExpanded) {\n return state.isCollapsed();\n }\n\n return true;\n },\n markManuallyExpanded() {\n manuallyExpanded = true;\n },\n resetManualExpand() {\n manuallyExpanded = false;\n },\n };\n}\n","/**\n * Submit-flow helper: navigates the host page to `<baseUrl>?initialQuestion=<value>`.\n *\n * Pure side-effect function; pulled out so per-client widgets share the same\n * URL-build + window.open contract.\n */\n\nimport { buildUrl } from '../shared';\n\nexport interface SubmitFlowOptions {\n baseUrl: string;\n /** Window target for the navigation. Defaults to `_self`. */\n windowTarget?: string;\n}\n\nexport interface SubmitFlow {\n open(value: string): boolean;\n}\n\nexport function createSubmitFlow(options: SubmitFlowOptions): SubmitFlow {\n if (!options.baseUrl) {\n throw new Error(\n '[Web5Embed] createSubmitFlow requires a non-empty baseUrl.',\n );\n }\n const prefix = buildUrl(options.baseUrl);\n const target = options.windowTarget ?? '_self';\n\n return {\n open(value) {\n window.open(prefix + encodeURIComponent(value), target);\n return true;\n },\n };\n}\n","/**\n * Host bottom-chrome measurement, for any Web5 surface pinned to the bottom of\n * a host viewport.\n *\n * A `position: fixed; bottom: 0` element sits *under* whatever the host page\n * pins down there — a cookie bar, a sticky add-to-cart bar, Shopify's\n * storefront preview bar. The full-page app has cleared that since DL #131 via\n * `web50-server-ui`'s `hostFit`; the embedded widgets never did, so the\n * floating input renders with its lower edge behind the host's bar.\n *\n * This is that measurement, moved somewhere every surface can reach:\n * `web50-server-ui` re-exports it, and the widget bundles (the seed's\n * `user-query`, circana, feature-com) can call it directly. Deliberately in\n * this package rather than `@wix/web5-core` — a widget IIFE is React-free and\n * ~24 KB, and core is a barrel that pulls React and the markdown stack.\n *\n * Detection is by computed `position`, never by host class names, which is what\n * keeps it working when the merchant changes theme.\n */\n\n/**\n * A `position: sticky|fixed` element only counts as host chrome if it pins near\n * the edge it is measured against. Generous enough for a theme that offsets a\n * bar above another one.\n */\nconst PIN_THRESHOLD_PX = 100;\n\n/** Guards against counting a full-screen fixed overlay (a drawer, a modal). */\nconst MAX_CHROME_VIEWPORT_RATIO = 0.5;\n\n/**\n * Shortest gap between two full scans of the document for pinned elements.\n *\n * A full scan is `getComputedStyle` on every element, and it is the expensive\n * half of this file: measured on a Shopify product page of 3,122 elements, a\n * full scan costs 7.7ms while re-reading the 21 pinned elements it finds costs\n * 0.08ms. Host pages mutate in bursts — a carousel advancing, a theme swapping\n * classes — and the set of pinned elements almost never changes with them, so\n * an unthrottled rescan would re-derive the same list dozens of times for one\n * real change. At 250ms the worst case is four scans a second, and a bar that\n * appears is cleared within a quarter second, which nobody perceives as late.\n */\nconst CANDIDATE_RESCAN_INTERVAL_MS = 250;\n\n/**\n * Consecutive unchanged measurements that end a settle pass, and the hard cap\n * on its length. Ten frames, not two, because the value does not start moving\n * with the trigger: Horizon's bar sits 20px below the fold and slides for three\n * frames before it crosses the viewport edge and the measurement changes at\n * all. Sixty frames is a second, longer than any bar transition and short\n * enough that a pathological host cannot hold the pass open. An idle pass is\n * ten reads of the cached candidates, under a millisecond in total.\n */\nconst SETTLE_STABLE_FRAMES = 10;\nconst SETTLE_MAX_FRAMES = 60;\n\n/** CSS custom property the measurement is published as. */\nexport const HOST_BOTTOM_INSET_VAR = '--web5-host-bottom-inset';\n\n/**\n * Every `position: fixed | sticky` element in the document.\n *\n * Whole-document, not the `body > *` / `body > * > *` pair this used to scan:\n * depth is not a property of host chrome. Measured on a Shopify Horizon store,\n * the theme's sticky add-to-cart bar (`div.sticky-add-to-cart__bar`) sits five\n * levels below `<body>`, so a two-level scan never enumerated it and the\n * widget rendered on top of the merchant's buy button.\n *\n * Depth-independence costs a whole-document style read, which is why\n * {@link trackHostBottomInset} caches what this returns and re-reads the cached\n * elements instead of calling it again.\n */\nexport function collectPinnedElements(): Element[] {\n if (typeof document === 'undefined' || typeof window === 'undefined') {\n return [];\n }\n\n const pinned: Element[] = [];\n document.body?.querySelectorAll('*').forEach((el) => {\n const position = window.getComputedStyle(el).position;\n if (position === 'fixed' || position === 'sticky') {\n pinned.push(el);\n }\n });\n return pinned;\n}\n\n/**\n * Total height of the chrome the host pins to the *bottom* of the viewport,\n * excluding anything inside `exclude` (our own surface — otherwise the widget\n * measures itself, lifts, and measures the lift).\n *\n * `candidates` is an already-collected pinned set (see\n * {@link collectPinnedElements}); omit it and the document is scanned. Every\n * guard below is re-read per call, so a cached list stays safe — an element the\n * theme has since unpinned, hidden or moved off the fold is rejected here.\n *\n * ## The two guards, and what each rejects\n *\n * Measured on a Shopify Horizon store, a naive \"everything fixed at the bottom\n * edge\" scan returns the theme's `<cart-drawer>` and its overlay alongside the\n * real bar — both full-height, so `MAX_CHROME_VIEWPORT_RATIO` rejects them.\n *\n * The other family is the corner chat bubble, and what separates it from a bar\n * is *where* it sits, not how wide it is — so the test is whether the element\n * crosses the horizontal centre. A width threshold reads as the obvious rule\n * and is the wrong one: at 80% it rejected Horizon's sticky add-to-cart bar,\n * 600px on a 1478px viewport, which is 41% wide, centred, and squarely over the\n * input. Crossing the centre admits a centred toast of any width and still\n * yields nothing to a launcher in the corner.\n *\n * ## Why the widest strip, and not the sum of heights\n *\n * A top stack grows downward from a fixed origin, so heights add. The bottom\n * has no such origin: a second bar sits *above* the first via its own `bottom`\n * offset, so what we have to clear is the distance from the viewport edge up to\n * the highest bar's top — the strip, not the total ink. Summing would\n * double-count exactly that overlap.\n *\n * Reading the strip off the rect also settles the awkward cases for free: a bar\n * with a `bottom` gap contributes the gap too, a closed sheet that a transform\n * pushed below the fold contributes nothing, and a fixed child of a fixed\n * parent cannot be counted twice.\n */\nexport function measureHostBottomInset(\n exclude?: Element | null,\n candidates?: readonly Element[],\n): number {\n if (typeof document === 'undefined' || typeof window === 'undefined') {\n return 0;\n }\n\n const pinned = candidates ?? collectPinnedElements();\n const centre = window.innerWidth / 2;\n\n let inset = 0;\n\n for (const el of pinned) {\n if (exclude != null && (exclude.contains(el) || el.contains(exclude))) {\n continue;\n }\n\n const styles = window.getComputedStyle(el);\n if (styles.position !== 'fixed' && styles.position !== 'sticky') {\n continue;\n }\n if (styles.visibility === 'hidden' || styles.bottom === 'auto') {\n continue;\n }\n const bottom = Number.parseFloat(styles.bottom);\n if (!Number.isFinite(bottom) || bottom > PIN_THRESHOLD_PX) {\n continue;\n }\n\n const rect = el.getBoundingClientRect();\n if (rect.height <= 0) {\n continue;\n }\n if (rect.left > centre || rect.right < centre) {\n continue;\n }\n\n // How much of the bottom strip this element occupies. Zero or less means it\n // renders below the fold despite the declaration — a closed bottom sheet.\n const covered = window.innerHeight - rect.top;\n if (covered <= 0 || covered > window.innerHeight * MAX_CHROME_VIEWPORT_RATIO) {\n continue;\n }\n\n inset = Math.max(inset, covered);\n }\n\n return Math.round(inset);\n}\n\nexport interface TrackHostBottomInsetOptions {\n /**\n * Element excluded from the scan — normally the widget's own host, so it does\n * not measure itself. Defaults to `target`.\n */\n exclude?: Element | null;\n}\n\n/**\n * Keep {@link HOST_BOTTOM_INSET_VAR} on `target` in sync with the host's bottom\n * chrome, and return a function that stops tracking.\n *\n * Publishing a custom property rather than writing `bottom` directly is what\n * makes this composable: the caller's own stylesheet decides how to spend the\n * value — `bottom: var(--web5-host-bottom-inset, 0px)` for a flush dock, or a\n * `calc()` for one that also wants a gap. It is the same variable, and the same\n * contract, `web50-server-ui` already publishes on `:root` for the full page.\n *\n * ## Settle, rather than poll\n *\n * The answer changes when the host mutates or the viewport resizes — and then\n * it keeps changing, because a bar typically slides or fades into place over a\n * few hundred milliseconds. A single measurement on the triggering event\n * catches the bar at its starting position and leaves the input parked half a\n * bar too low; sampling at intervals makes it arrive in visible steps.\n *\n * So a trigger starts a settle pass: measure each frame until the value holds\n * for {@link SETTLE_STABLE_FRAMES}, then stop. The input tracks the animation,\n * and an idle page runs nothing at all — no scroll listener, no timer, no\n * observer callbacks between mutations. A pass costs 0.08ms a frame and ends\n * after ten still frames when nothing is moving.\n *\n * Rebuilding the candidate list is the expensive half (7.7ms — see\n * {@link CANDIDATE_RESCAN_INTERVAL_MS}), so it stays throttled and out of the\n * settle pass, which only re-reads what is already known.\n *\n * Not covered: a `position: sticky` bar that unsticks from scrolling alone\n * changes position with no mutation to notice it by. No host chrome seen so far\n * behaves that way, and catching it would mean watching every frame of every\n * scroll for an event that happens twice a page.\n */\nexport function trackHostBottomInset(\n target: HTMLElement,\n options: TrackHostBottomInsetOptions = {},\n): () => void {\n if (typeof window === 'undefined') {\n return () => undefined;\n }\n\n const exclude = options.exclude === undefined ? target : options.exclude;\n let candidates = collectPinnedElements();\n let published: number | null = null;\n let frame = 0;\n let running = false;\n let stableFrames = 0;\n let framesLeft = 0;\n let rescanTimer = 0;\n // Zero, not the current time, so the first host mutation rescans at once and\n // only a burst after it is throttled.\n let lastScanAt = 0;\n let stopped = false;\n\n /** Publish the current inset; true when it moved. */\n const measure = (): boolean => {\n const inset = measureHostBottomInset(exclude, candidates);\n // Writing the same value back still invalidates style on the target, and\n // the target is a fixed-position shadow host. A frame that changes nothing\n // ends here, having only read.\n if (inset === published) {\n return false;\n }\n published = inset;\n target.style.setProperty(HOST_BOTTOM_INSET_VAR, `${inset}px`);\n return true;\n };\n\n const step = (): void => {\n frame = 0;\n if (stopped) {\n running = false;\n return;\n }\n stableFrames = measure() ? 0 : stableFrames + 1;\n framesLeft -= 1;\n if (stableFrames >= SETTLE_STABLE_FRAMES || framesLeft <= 0) {\n running = false;\n return;\n }\n frame = window.requestAnimationFrame(step);\n };\n\n /** Follow the value until it stops moving. Restarts the budget if in flight. */\n const settle = (): void => {\n if (stopped) {\n return;\n }\n stableFrames = 0;\n framesLeft = SETTLE_MAX_FRAMES;\n if (running) {\n return;\n }\n running = true;\n frame = window.requestAnimationFrame(step);\n };\n\n const rescan = (): void => {\n rescanTimer = 0;\n if (stopped) {\n return;\n }\n lastScanAt = Date.now();\n candidates = collectPinnedElements();\n settle();\n };\n\n const scheduleRescan = (): void => {\n if (stopped || rescanTimer !== 0) {\n return;\n }\n const wait = CANDIDATE_RESCAN_INTERVAL_MS - (Date.now() - lastScanAt);\n if (wait <= 0) {\n rescan();\n return;\n }\n rescanTimer = window.setTimeout(rescan, wait);\n };\n\n /**\n * Every trigger does the same two things: follow the value now with the\n * candidates we have, and rebuild that list shortly in case the set changed —\n * a bar inserted, or one a media query just pinned at a new breakpoint.\n */\n const refresh = (): void => {\n settle();\n scheduleRescan();\n };\n\n // Synchronously, so a host that already has its chrome up never paints the\n // input at the wrong height. The settle pass then follows anything still\n // arriving.\n measure();\n settle();\n\n window.addEventListener('resize', refresh, { passive: true });\n window.addEventListener('orientationchange', refresh, { passive: true });\n\n /**\n * Publishing the inset writes `style` on the target, and the target sits\n * inside the observed subtree — so our own measurement looks like a host\n * change and schedules the next one. Records that all come from our surface\n * are dropped; a batch that also carries a real host change is not.\n */\n const isOurOwn = (records: MutationRecord[]): boolean =>\n exclude != null &&\n records.every((r) => r.target instanceof Node && exclude.contains(r.target));\n\n let mutations: MutationObserver | undefined;\n if (typeof MutationObserver !== 'undefined' && document.body != null) {\n mutations = new MutationObserver((records) => {\n if (isOurOwn(records)) {\n return;\n }\n refresh();\n });\n // Every attribute, not a list of likely ones. Themes reveal a bar through\n // whatever they like — Horizon flips `data-stuck`, Dawn a class, others\n // `hidden` or `open` — and a name filter is host-convention guessing of the\n // kind the geometry check exists to avoid. A busy storefront does fire this\n // often; the settle pass and the rescan throttle are what keep that cheap.\n mutations.observe(document.body, {\n childList: true,\n subtree: true,\n attributes: true,\n });\n }\n\n return () => {\n stopped = true;\n if (running) {\n window.cancelAnimationFrame(frame);\n running = false;\n frame = 0;\n }\n if (rescanTimer !== 0) {\n window.clearTimeout(rescanTimer);\n rescanTimer = 0;\n }\n window.removeEventListener('resize', refresh);\n window.removeEventListener('orientationchange', refresh);\n mutations?.disconnect();\n };\n}\n"],"names":["GLOBAL_KEY","EDITABLE_SELECTOR","MODAL_SELECTOR","isModalVisible","modal","cs","pathContainsEditable","event","path","node","getFocusShortcutCoordinator","w","targets","listenerAttached","handleKeyDown","e","isSlash","isCmdK","modals","target","t","previousActive","ensureListener","dropListenerIfEmpty","coordinator","index","__resetFocusShortcutCoordinatorForTests","DEFAULT_STATIC_VERSIONS_URL","CACHE_KEY","fetchStaticVersions","url","res","findVersion","versions","artifactId","entry","v","getCachedVersions","cacheVersions","DEFAULTS","resolveOptions","options","getPromptInputTokensUrl","version","tokensCdnBase","tokensFileName","resolvePromptInputTokensVersion","opts","cached","err","resolvePromptInputTokensUrl","attachPromptInputTokensStylesheet","shadowRoot","link","sendIconSvg","buildUrl","base","escapeHtml","value","DEFAULT_CHIPS_ENDPOINT","DEFAULT_CHIPS_TIMEOUT_MS","DEFAULT_TURN_ENDPOINT","PDP_CHIPS_PLACEMENT","fetchChipSuggestions","pageUrl","fetchContextualChips","endpoint","timeoutMs","fetchImpl","resolveFetch","controller","timeoutId","response","chip","additionalData","body","text","parseChipsFromJson","accumulateTurnMarkdown","override","streamText","candidate","parsed","completed","item","markdown","unfenced","resolveTarget","el","script","container","shouldInit","requireParam","shouldInitForPath","excludePaths","defaultExcludePaths","readBaseConfigFromScript","config","placeholder","baseUrl","suggestionsRaw","readBottomConfigFromScript","visibilityMode","showAfterTarget","excludePathsRaw","p","registerWeb5Widget","loader","init","ctx","createVariantMount","id","handler","createFeatureToggleReader","featureToggles","get","result","createInputHeightSync","input","wrapper","multilineClass","maxHeightPx","multilineThresholdMultiplier","fallbackLineHeightPx","computed","lineHeight","nextHeight","DEFAULT_THRESHOLD_PX","createCollapseController","threshold","manuallyExpanded","isBottomZoneVisible","doc","scrollTop","viewportBottom","distanceToBottom","state","createSubmitFlow","prefix","PIN_THRESHOLD_PX","MAX_CHROME_VIEWPORT_RATIO","CANDIDATE_RESCAN_INTERVAL_MS","SETTLE_STABLE_FRAMES","SETTLE_MAX_FRAMES","HOST_BOTTOM_INSET_VAR","collectPinnedElements","pinned","position","measureHostBottomInset","exclude","candidates","centre","inset","styles","bottom","rect","covered","trackHostBottomInset","published","frame","running","stableFrames","framesLeft","rescanTimer","lastScanAt","stopped","measure","step","settle","rescan","scheduleRescan","wait","refresh","isOurOwn","records","r","mutations"],"mappings":"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"}
|