@adland/embed 0.2.2 → 0.3.0

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/index.cjs CHANGED
@@ -177,6 +177,15 @@ function makeAdlandSlot(adapter) {
177
177
  ];
178
178
  shadowInited = false;
179
179
  cleanupImpression;
180
+ /**
181
+ * The address this element is actually showing.
182
+ *
183
+ * The `slot` attribute when there is one, otherwise whatever `slot-key`
184
+ * resolved to. Everything below the fetch needs an address — the impression
185
+ * is recorded against one and the empty-slot CTA links to one — and a name
186
+ * is not an address until the module has answered.
187
+ */
188
+ resolvedSlot;
180
189
  messageHandler;
181
190
  renderToken = 0;
182
191
  connectedCallback() {
@@ -213,8 +222,16 @@ function makeAdlandSlot(adapter) {
213
222
  }
214
223
  }
215
224
  const slot = this.attr("slot");
216
- if (!slot) return { data: null, cid: null };
225
+ const slotKey = this.attr("slot-key");
226
+ if (!slot && !slotKey) return { data: null, cid: null };
217
227
  const client = core.createReadClient(this.chainId(), this.attr("rpc-url"));
228
+ if (!slot && slotKey) {
229
+ const snap = await core.fetchAdByKey(client, slotKey, {
230
+ gateway: this.attr("gateway"),
231
+ adModule: this.attr("ad-module")
232
+ });
233
+ return snap ? { data: snap.data, cid: snap.cid, slot: snap.slot } : { data: null, cid: null };
234
+ }
218
235
  const uri = await core.fetchMetadataURI(client, slot);
219
236
  if (!uri) return { data: null, cid: null };
220
237
  return core.fetchAdFromURI(uri, this.attr("gateway"));
@@ -234,11 +251,12 @@ function makeAdlandSlot(adapter) {
234
251
  const mode = this.attr("render-mode") === "inline" ? "inline" : "isolated";
235
252
  if (mode === "inline") this.renderInline(inner, result.data);
236
253
  else this.renderIsolated(inner, result.data);
237
- const slot = this.attr("slot");
238
- if (slot) {
254
+ const resolved = this.attr("slot") ?? result.slot;
255
+ this.resolvedSlot = resolved;
256
+ if (resolved) {
239
257
  const track = this.attr("verified") != null ? core.trackVerifiedImpression : core.trackImpression;
240
258
  this.cleanupImpression = track(this, {
241
- slot,
259
+ slot: resolved,
242
260
  chainId: this.chainId(),
243
261
  placement: this.attr("placement"),
244
262
  cid: result.cid,
@@ -288,12 +306,12 @@ function makeAdlandSlot(adapter) {
288
306
  return this.attr("base-link-url") ?? DEFAULT_BASE_LINK;
289
307
  }
290
308
  emptyUrl() {
291
- const slot = this.attr("slot");
309
+ const slot = this.resolvedSlot ?? this.attr("slot");
292
310
  if (!slot) return null;
293
311
  return `${this.baseLink()}/slots/${slot}?chain=${this.chainId()}`;
294
312
  }
295
313
  handleClick(data, alreadyOpened) {
296
- const slot = this.attr("slot");
314
+ const slot = this.resolvedSlot ?? this.attr("slot");
297
315
  const shared = {
298
316
  chainId: this.chainId(),
299
317
  placement: this.attr("placement"),
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/render.ts","../src/theme.ts","../src/element.ts","../src/register.ts","../src/index.ts"],"names":["getAdImage","getAdTitle","getAdDescription","getAdType","createReadClient","fetchMetadataURI","fetchAdFromURI","trackVerifiedImpression","trackImpression","trackClick","WebActionAdapter"],"mappings":"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","file":"index.cjs","sourcesContent":["import type { AdData } from \"@adland/data\";\nimport {\n getAdDescription,\n getAdImage,\n getAdTitle,\n getAdType,\n} from \"@adland/react/core\";\n\n/** Part names exposed for `::part()` styling in inline (shadow DOM) mode. */\nexport const AD_PARTS = [\n \"card\",\n \"media\",\n \"body\",\n \"title\",\n \"description\",\n \"badge\",\n \"label\",\n] as const;\n\nconst TYPE_LABELS: Record<string, string> = {\n link: \"Link\",\n cast: \"Cast\",\n miniapp: \"Mini App\",\n token: \"Token\",\n farcasterProfile: \"Profile\",\n};\n\n/** Escape text for safe interpolation into HTML element content. */\nexport function escapeHtml(value: string): string {\n return value.replace(\n /[&<>\"']/g,\n (c) =>\n ({\n \"&\": \"&amp;\",\n \"<\": \"&lt;\",\n \">\": \"&gt;\",\n '\"': \"&quot;\",\n \"'\": \"&#39;\",\n })[c] as string,\n );\n}\n\n/** Escape a value for use inside a double-quoted HTML attribute. */\nexport function escapeAttr(value: string): string {\n return value.replace(/\"/g, \"&quot;\").replace(/</g, \"&lt;\");\n}\n\n/**\n * Default ad-card styles. Every visual is driven by a `--adland-*` custom\n * property with a sensible fallback, so a publisher can re-theme the card\n * without overriding a single rule. Custom properties inherit through the\n * shadow boundary (inline mode) and are copied into the iframe (isolated mode).\n */\nexport const BASE_STYLES = `\n:host { display: block; }\n* { box-sizing: border-box; }\n.adland-card {\n position: relative;\n display: flex;\n gap: var(--adland-gap, 12px);\n align-items: center;\n padding: var(--adland-padding, 12px);\n border: var(--adland-border-width, 1px) solid var(--adland-border, #e5e7eb);\n border-radius: var(--adland-radius, 12px);\n background: var(--adland-bg, #ffffff);\n color: var(--adland-fg, #111827);\n font-family: var(--adland-font, system-ui, -apple-system, sans-serif);\n cursor: pointer;\n text-decoration: none;\n transition: border-color 0.15s ease, box-shadow 0.15s ease;\n}\n.adland-card:hover {\n border-color: var(--adland-border-hover, #d1d5db);\n box-shadow: var(--adland-shadow-hover, 0 1px 3px rgba(0, 0, 0, 0.08));\n}\n.adland-media {\n width: var(--adland-media-size, 44px);\n height: var(--adland-media-size, 44px);\n border-radius: var(--adland-media-radius, 8px);\n object-fit: cover;\n flex: none;\n background: var(--adland-media-bg, #f3f4f6);\n}\n.adland-body { min-width: 0; flex: 1; }\n.adland-title {\n margin: 0;\n font-size: var(--adland-title-size, 14px);\n font-weight: var(--adland-title-weight, 600);\n line-height: 1.3;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n.adland-description {\n margin: 2px 0 0;\n font-size: var(--adland-description-size, 12px);\n color: var(--adland-muted, #6b7280);\n line-height: 1.4;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n.adland-badge {\n flex: none;\n font-size: 10px;\n font-weight: 600;\n padding: 2px 6px;\n border-radius: var(--adland-badge-radius, 6px);\n background: var(--adland-accent-bg, #eef2ff);\n color: var(--adland-accent-fg, #4338ca);\n}\n.adland-label {\n position: absolute;\n top: 6px;\n right: 8px;\n font-size: 9px;\n letter-spacing: 0.04em;\n text-transform: uppercase;\n color: var(--adland-label, #9ca3af);\n}\n.adland-empty { justify-content: center; color: var(--adland-muted, #6b7280); }\n`;\n\n/** Inner card markup (parts + classes) for a loaded ad. */\nexport function cardMarkup(ad: AdData): string {\n const image = getAdImage(ad);\n const title = getAdTitle(ad);\n const description = getAdDescription(ad);\n const type = getAdType(ad);\n const badge = type ? (TYPE_LABELS[type] ?? type) : \"\";\n\n return `<div part=\"card\" class=\"adland-card\">${\n image\n ? `<img part=\"media\" class=\"adland-media\" src=\"${escapeAttr(image)}\" alt=\"\" loading=\"lazy\" />`\n : \"\"\n }<div part=\"body\" class=\"adland-body\">${\n title ? `<p part=\"title\" class=\"adland-title\">${escapeHtml(title)}</p>` : \"\"\n }${\n description\n ? `<p part=\"description\" class=\"adland-description\">${escapeHtml(description)}</p>`\n : \"\"\n }</div>${\n badge\n ? `<span part=\"badge\" class=\"adland-badge\">${escapeHtml(badge)}</span>`\n : \"\"\n }<span part=\"label\" class=\"adland-label\">Ad</span></div>`;\n}\n\n/** Inner card markup for an empty slot (the \"Your ad here\" CTA). */\nexport function emptyMarkup(): string {\n return `<div part=\"card\" class=\"adland-card adland-empty\"><span part=\"description\" class=\"adland-description\">Your ad here</span><span part=\"label\" class=\"adland-label\">Ad</span></div>`;\n}\n\n/** The external URL an ad maps to on the web, or null if it has none. */\nexport function adWebUrl(ad: AdData): string | null {\n if (ad.type === \"link\" || ad.type === \"miniapp\") {\n const url = (ad.data as { url?: unknown }).url;\n return typeof url === \"string\" ? url : null;\n }\n return null;\n}\n","/**\n * The `--adland-*` custom properties the card reads. Custom properties inherit\n * through the shadow boundary automatically (inline mode), but NOT across an\n * iframe document boundary — so for isolated mode we read whichever of these\n * are set on the host and copy them into the iframe's `:root`.\n */\nexport const THEME_VARS = [\n \"--adland-bg\",\n \"--adland-fg\",\n \"--adland-muted\",\n \"--adland-label\",\n \"--adland-border\",\n \"--adland-border-hover\",\n \"--adland-border-width\",\n \"--adland-radius\",\n \"--adland-padding\",\n \"--adland-gap\",\n \"--adland-font\",\n \"--adland-shadow-hover\",\n \"--adland-media-size\",\n \"--adland-media-radius\",\n \"--adland-media-bg\",\n \"--adland-title-size\",\n \"--adland-title-weight\",\n \"--adland-description-size\",\n \"--adland-badge-radius\",\n \"--adland-accent-bg\",\n \"--adland-accent-fg\",\n] as const;\n\n/**\n * Serialize the theme custom properties currently set on `host` into a CSS\n * declaration string (e.g. `--adland-bg:#000;`). Empty when none are set.\n */\nexport function readThemeVars(host: Element): string {\n const styles = getComputedStyle(host);\n let out = \"\";\n for (const name of THEME_VARS) {\n const value = styles.getPropertyValue(name).trim();\n if (value) out += `${name}:${value};`;\n }\n return out;\n}\n","import type { AdData } from \"@adland/data\";\nimport {\n type ActionAdapter,\n type AdConsent,\n createReadClient,\n fetchAdFromURI,\n fetchMetadataURI,\n trackClick,\n trackImpression,\n trackVerifiedImpression,\n} from \"@adland/react/core\";\nimport {\n adWebUrl,\n BASE_STYLES,\n cardMarkup,\n emptyMarkup,\n escapeAttr,\n} from \"./render\";\nimport { readThemeVars } from \"./theme\";\n\nconst DEFAULT_CHAIN = 8453;\n/**\n * Where an unsold slot sends a reader.\n *\n * The 0xSlots app moved off its own subdomain and now lives under a path on the\n * apex domain, so the base carries `/app` and the slot path is appended to it.\n */\nconst DEFAULT_BASE_LINK = \"https://0xslots.org/app\";\nconst RESERVED_HEIGHT = \"68px\";\n\n/**\n * Build the `<adland-slot>` custom-element class bound to a click-routing\n * adapter. The web CDN bundle binds the web adapter (no Farcaster); the\n * Farcaster bundle binds the Mini App adapter.\n */\nexport function makeAdlandSlot(\n adapter: ActionAdapter,\n): CustomElementConstructor {\n return class AdlandSlot extends HTMLElement {\n static observedAttributes = [\n \"slot\",\n \"chain\",\n \"placement\",\n \"render-mode\",\n \"gateway\",\n \"endpoint\",\n \"rpc-url\",\n \"consent\",\n \"verified\",\n \"base-link-url\",\n \"data-ad\",\n ];\n\n private shadowInited = false;\n private cleanupImpression?: () => void;\n private messageHandler?: (e: MessageEvent) => void;\n private renderToken = 0;\n\n connectedCallback() {\n void this.render();\n }\n\n disconnectedCallback() {\n this.teardown();\n }\n\n attributeChangedCallback() {\n if (this.isConnected) void this.render();\n }\n\n private attr(name: string): string | undefined {\n return this.getAttribute(name) ?? undefined;\n }\n\n private chainId(): number {\n return Number(this.attr(\"chain\") ?? DEFAULT_CHAIN) || DEFAULT_CHAIN;\n }\n\n private teardown() {\n this.cleanupImpression?.();\n this.cleanupImpression = undefined;\n if (this.messageHandler) {\n window.removeEventListener(\"message\", this.messageHandler);\n this.messageHandler = undefined;\n }\n }\n\n /** Resolve the ad: inline `data-ad` JSON, else an on-chain read. */\n private async loadAd(): Promise<{\n data: AdData | null;\n cid: string | null;\n }> {\n const staticData = this.attr(\"data-ad\");\n if (staticData) {\n try {\n return { data: JSON.parse(staticData) as AdData, cid: null };\n } catch {\n return { data: null, cid: null };\n }\n }\n\n const slot = this.attr(\"slot\");\n if (!slot) return { data: null, cid: null };\n\n const client = createReadClient(this.chainId(), this.attr(\"rpc-url\"));\n const uri = await fetchMetadataURI(client, slot);\n if (!uri) return { data: null, cid: null };\n return fetchAdFromURI(uri, this.attr(\"gateway\"));\n }\n\n private async render() {\n const token = ++this.renderToken;\n this.teardown();\n\n // Reserve height up front so an async load doesn't shift the layout.\n if (!this.style.minHeight) this.style.minHeight = RESERVED_HEIGHT;\n\n let result: { data: AdData | null; cid: string | null };\n try {\n result = await this.loadAd();\n } catch {\n result = { data: null, cid: null };\n }\n // A newer render superseded this one while awaiting — bail.\n if (token !== this.renderToken || !this.isConnected) return;\n\n const inner = result.data ? cardMarkup(result.data) : emptyMarkup();\n const mode =\n this.attr(\"render-mode\") === \"inline\" ? \"inline\" : \"isolated\";\n if (mode === \"inline\") this.renderInline(inner, result.data);\n else this.renderIsolated(inner, result.data);\n\n const slot = this.attr(\"slot\");\n if (slot) {\n const track =\n this.attr(\"verified\") != null\n ? trackVerifiedImpression\n : trackImpression;\n this.cleanupImpression = track(this, {\n slot,\n chainId: this.chainId(),\n placement: this.attr(\"placement\"),\n cid: result.cid,\n empty: !result.data,\n endpoint: this.attr(\"endpoint\"),\n consent: this.attr(\"consent\") as AdConsent | undefined,\n });\n }\n }\n\n private renderInline(inner: string, data: AdData | null) {\n const root =\n this.shadowRoot ??\n (this.shadowInited ? null : this.attachShadow({ mode: \"open\" }));\n if (!root) return;\n this.shadowInited = true;\n root.innerHTML = `<style>${BASE_STYLES}</style>${inner}`;\n const card = root.querySelector<HTMLElement>(\".adland-card\");\n card?.addEventListener(\"click\", () => this.handleClick(data, false));\n }\n\n private renderIsolated(inner: string, data: AdData | null) {\n this.replaceChildren();\n\n const url = data ? adWebUrl(data) : this.emptyUrl();\n const body = url\n ? `<a href=\"${escapeAttr(url)}\" target=\"_blank\" rel=\"noopener noreferrer\" style=\"display:block;text-decoration:none;color:inherit\">${inner}</a>`\n : inner;\n const theme = readThemeVars(this);\n\n const iframe = document.createElement(\"iframe\");\n iframe.setAttribute(\n \"sandbox\",\n \"allow-popups allow-popups-to-escape-sandbox allow-scripts\",\n );\n iframe.setAttribute(\"title\", \"Advertisement\");\n iframe.setAttribute(\"scrolling\", \"no\");\n iframe.style.cssText = \"width:100%;border:0;display:block;height:100%\";\n iframe.srcdoc = `<!doctype html><html><head><meta charset=\"utf-8\"><style>:root{${theme}}html,body{margin:0;padding:0}${BASE_STYLES}</style></head><body>${body}<script>(function(){function send(t,x){parent.postMessage(Object.assign({__adland:t},x||{}),'*')}addEventListener('click',function(){send('click')});function size(){send('size',{height:document.documentElement.scrollHeight})}new ResizeObserver(size).observe(document.documentElement);size()})()</script></body></html>`;\n\n const handler = (e: MessageEvent) => {\n if (e.source !== iframe.contentWindow) return;\n const msg = e.data as { __adland?: string; height?: number };\n if (msg?.__adland === \"size\" && typeof msg.height === \"number\") {\n iframe.style.height = `${msg.height}px`;\n this.style.minHeight = \"\";\n } else if (msg?.__adland === \"click\") {\n // The iframe's anchor already opened the URL — only track here.\n this.handleClick(data, true);\n }\n };\n this.messageHandler = handler;\n window.addEventListener(\"message\", handler);\n this.appendChild(iframe);\n }\n\n private baseLink(): string {\n return this.attr(\"base-link-url\") ?? DEFAULT_BASE_LINK;\n }\n\n private emptyUrl(): string | null {\n const slot = this.attr(\"slot\");\n if (!slot) return null;\n return `${this.baseLink()}/slots/${slot}?chain=${this.chainId()}`;\n }\n\n private handleClick(data: AdData | null, alreadyOpened: boolean) {\n const slot = this.attr(\"slot\");\n const shared = {\n chainId: this.chainId(),\n placement: this.attr(\"placement\"),\n endpoint: this.attr(\"endpoint\"),\n consent: this.attr(\"consent\") as AdConsent | undefined,\n };\n\n if (data) {\n if (slot) trackClick(\"click\", { slot, cid: null, ...shared });\n if (!alreadyOpened) void adapter.performAdAction(data);\n } else if (slot) {\n void trackClick(\"click-empty\", { slot, ...shared });\n if (!alreadyOpened) {\n void adapter.performEmptyAdAction(\n slot,\n this.chainId(),\n this.baseLink(),\n );\n }\n }\n }\n };\n}\n","import type { ActionAdapter } from \"@adland/react/core\";\nimport { makeAdlandSlot } from \"./element\";\n\n/**\n * Define the `<adland-slot>` custom element bound to a click-routing adapter.\n * Safe to call more than once and in non-DOM environments — it no-ops if the\n * element is already defined or `customElements` is unavailable (SSR).\n */\nexport function registerAdlandSlot(\n adapter: ActionAdapter,\n tag = \"adland-slot\",\n): void {\n if (typeof customElements === \"undefined\") return;\n if (customElements.get(tag)) return;\n customElements.define(tag, makeAdlandSlot(adapter));\n}\n","import { WebActionAdapter } from \"@adland/react/core\";\nimport { registerAdlandSlot } from \"./register\";\n\nexport { WebActionAdapter } from \"@adland/react/core\";\nexport { makeAdlandSlot } from \"./element\";\nexport { registerAdlandSlot } from \"./register\";\nexport { AD_PARTS } from \"./render\";\nexport { THEME_VARS } from \"./theme\";\n\n// Convenience: importing the package auto-registers <adland-slot> with the web\n// adapter (no Farcaster). For Farcaster routing, call registerAdlandSlot with\n// FarcasterActionAdapter from \"@adland/react/farcaster\" instead.\nregisterAdlandSlot(WebActionAdapter);\n"]}
1
+ {"version":3,"sources":["../src/render.ts","../src/theme.ts","../src/element.ts","../src/register.ts","../src/index.ts"],"names":["getAdImage","getAdTitle","getAdDescription","getAdType","createReadClient","fetchAdByKey","fetchMetadataURI","fetchAdFromURI","trackVerifiedImpression","trackImpression","trackClick","WebActionAdapter"],"mappings":"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","file":"index.cjs","sourcesContent":["import type { AdData } from \"@adland/data\";\nimport {\n getAdDescription,\n getAdImage,\n getAdTitle,\n getAdType,\n} from \"@adland/react/core\";\n\n/** Part names exposed for `::part()` styling in inline (shadow DOM) mode. */\nexport const AD_PARTS = [\n \"card\",\n \"media\",\n \"body\",\n \"title\",\n \"description\",\n \"badge\",\n \"label\",\n] as const;\n\nconst TYPE_LABELS: Record<string, string> = {\n link: \"Link\",\n cast: \"Cast\",\n miniapp: \"Mini App\",\n token: \"Token\",\n farcasterProfile: \"Profile\",\n};\n\n/** Escape text for safe interpolation into HTML element content. */\nexport function escapeHtml(value: string): string {\n return value.replace(\n /[&<>\"']/g,\n (c) =>\n ({\n \"&\": \"&amp;\",\n \"<\": \"&lt;\",\n \">\": \"&gt;\",\n '\"': \"&quot;\",\n \"'\": \"&#39;\",\n })[c] as string,\n );\n}\n\n/** Escape a value for use inside a double-quoted HTML attribute. */\nexport function escapeAttr(value: string): string {\n return value.replace(/\"/g, \"&quot;\").replace(/</g, \"&lt;\");\n}\n\n/**\n * Default ad-card styles. Every visual is driven by a `--adland-*` custom\n * property with a sensible fallback, so a publisher can re-theme the card\n * without overriding a single rule. Custom properties inherit through the\n * shadow boundary (inline mode) and are copied into the iframe (isolated mode).\n */\nexport const BASE_STYLES = `\n:host { display: block; }\n* { box-sizing: border-box; }\n.adland-card {\n position: relative;\n display: flex;\n gap: var(--adland-gap, 12px);\n align-items: center;\n padding: var(--adland-padding, 12px);\n border: var(--adland-border-width, 1px) solid var(--adland-border, #e5e7eb);\n border-radius: var(--adland-radius, 12px);\n background: var(--adland-bg, #ffffff);\n color: var(--adland-fg, #111827);\n font-family: var(--adland-font, system-ui, -apple-system, sans-serif);\n cursor: pointer;\n text-decoration: none;\n transition: border-color 0.15s ease, box-shadow 0.15s ease;\n}\n.adland-card:hover {\n border-color: var(--adland-border-hover, #d1d5db);\n box-shadow: var(--adland-shadow-hover, 0 1px 3px rgba(0, 0, 0, 0.08));\n}\n.adland-media {\n width: var(--adland-media-size, 44px);\n height: var(--adland-media-size, 44px);\n border-radius: var(--adland-media-radius, 8px);\n object-fit: cover;\n flex: none;\n background: var(--adland-media-bg, #f3f4f6);\n}\n.adland-body { min-width: 0; flex: 1; }\n.adland-title {\n margin: 0;\n font-size: var(--adland-title-size, 14px);\n font-weight: var(--adland-title-weight, 600);\n line-height: 1.3;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n.adland-description {\n margin: 2px 0 0;\n font-size: var(--adland-description-size, 12px);\n color: var(--adland-muted, #6b7280);\n line-height: 1.4;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n.adland-badge {\n flex: none;\n font-size: 10px;\n font-weight: 600;\n padding: 2px 6px;\n border-radius: var(--adland-badge-radius, 6px);\n background: var(--adland-accent-bg, #eef2ff);\n color: var(--adland-accent-fg, #4338ca);\n}\n.adland-label {\n position: absolute;\n top: 6px;\n right: 8px;\n font-size: 9px;\n letter-spacing: 0.04em;\n text-transform: uppercase;\n color: var(--adland-label, #9ca3af);\n}\n.adland-empty { justify-content: center; color: var(--adland-muted, #6b7280); }\n`;\n\n/** Inner card markup (parts + classes) for a loaded ad. */\nexport function cardMarkup(ad: AdData): string {\n const image = getAdImage(ad);\n const title = getAdTitle(ad);\n const description = getAdDescription(ad);\n const type = getAdType(ad);\n const badge = type ? (TYPE_LABELS[type] ?? type) : \"\";\n\n return `<div part=\"card\" class=\"adland-card\">${\n image\n ? `<img part=\"media\" class=\"adland-media\" src=\"${escapeAttr(image)}\" alt=\"\" loading=\"lazy\" />`\n : \"\"\n }<div part=\"body\" class=\"adland-body\">${\n title ? `<p part=\"title\" class=\"adland-title\">${escapeHtml(title)}</p>` : \"\"\n }${\n description\n ? `<p part=\"description\" class=\"adland-description\">${escapeHtml(description)}</p>`\n : \"\"\n }</div>${\n badge\n ? `<span part=\"badge\" class=\"adland-badge\">${escapeHtml(badge)}</span>`\n : \"\"\n }<span part=\"label\" class=\"adland-label\">Ad</span></div>`;\n}\n\n/** Inner card markup for an empty slot (the \"Your ad here\" CTA). */\nexport function emptyMarkup(): string {\n return `<div part=\"card\" class=\"adland-card adland-empty\"><span part=\"description\" class=\"adland-description\">Your ad here</span><span part=\"label\" class=\"adland-label\">Ad</span></div>`;\n}\n\n/** The external URL an ad maps to on the web, or null if it has none. */\nexport function adWebUrl(ad: AdData): string | null {\n if (ad.type === \"link\" || ad.type === \"miniapp\") {\n const url = (ad.data as { url?: unknown }).url;\n return typeof url === \"string\" ? url : null;\n }\n return null;\n}\n","/**\n * The `--adland-*` custom properties the card reads. Custom properties inherit\n * through the shadow boundary automatically (inline mode), but NOT across an\n * iframe document boundary — so for isolated mode we read whichever of these\n * are set on the host and copy them into the iframe's `:root`.\n */\nexport const THEME_VARS = [\n \"--adland-bg\",\n \"--adland-fg\",\n \"--adland-muted\",\n \"--adland-label\",\n \"--adland-border\",\n \"--adland-border-hover\",\n \"--adland-border-width\",\n \"--adland-radius\",\n \"--adland-padding\",\n \"--adland-gap\",\n \"--adland-font\",\n \"--adland-shadow-hover\",\n \"--adland-media-size\",\n \"--adland-media-radius\",\n \"--adland-media-bg\",\n \"--adland-title-size\",\n \"--adland-title-weight\",\n \"--adland-description-size\",\n \"--adland-badge-radius\",\n \"--adland-accent-bg\",\n \"--adland-accent-fg\",\n] as const;\n\n/**\n * Serialize the theme custom properties currently set on `host` into a CSS\n * declaration string (e.g. `--adland-bg:#000;`). Empty when none are set.\n */\nexport function readThemeVars(host: Element): string {\n const styles = getComputedStyle(host);\n let out = \"\";\n for (const name of THEME_VARS) {\n const value = styles.getPropertyValue(name).trim();\n if (value) out += `${name}:${value};`;\n }\n return out;\n}\n","import type { AdData } from \"@adland/data\";\nimport {\n type ActionAdapter,\n type AdConsent,\n createReadClient,\n fetchAdFromURI,\n fetchAdByKey,\n fetchMetadataURI,\n trackClick,\n trackImpression,\n trackVerifiedImpression,\n} from \"@adland/react/core\";\nimport {\n adWebUrl,\n BASE_STYLES,\n cardMarkup,\n emptyMarkup,\n escapeAttr,\n} from \"./render\";\nimport { readThemeVars } from \"./theme\";\n\nconst DEFAULT_CHAIN = 8453;\n/**\n * Where an unsold slot sends a reader.\n *\n * The 0xSlots app moved off its own subdomain and now lives under a path on the\n * apex domain, so the base carries `/app` and the slot path is appended to it.\n */\nconst DEFAULT_BASE_LINK = \"https://0xslots.org/app\";\nconst RESERVED_HEIGHT = \"68px\";\n\n/**\n * Build the `<adland-slot>` custom-element class bound to a click-routing\n * adapter. The web CDN bundle binds the web adapter (no Farcaster); the\n * Farcaster bundle binds the Mini App adapter.\n */\nexport function makeAdlandSlot(\n adapter: ActionAdapter,\n): CustomElementConstructor {\n return class AdlandSlot extends HTMLElement {\n static observedAttributes = [\n \"slot\",\n \"chain\",\n \"placement\",\n \"render-mode\",\n \"gateway\",\n \"endpoint\",\n \"rpc-url\",\n \"consent\",\n \"verified\",\n \"base-link-url\",\n \"data-ad\",\n ];\n\n private shadowInited = false;\n private cleanupImpression?: () => void;\n /**\n * The address this element is actually showing.\n *\n * The `slot` attribute when there is one, otherwise whatever `slot-key`\n * resolved to. Everything below the fetch needs an address — the impression\n * is recorded against one and the empty-slot CTA links to one — and a name\n * is not an address until the module has answered.\n */\n private resolvedSlot?: string;\n private messageHandler?: (e: MessageEvent) => void;\n private renderToken = 0;\n\n connectedCallback() {\n void this.render();\n }\n\n disconnectedCallback() {\n this.teardown();\n }\n\n attributeChangedCallback() {\n if (this.isConnected) void this.render();\n }\n\n private attr(name: string): string | undefined {\n return this.getAttribute(name) ?? undefined;\n }\n\n private chainId(): number {\n return Number(this.attr(\"chain\") ?? DEFAULT_CHAIN) || DEFAULT_CHAIN;\n }\n\n private teardown() {\n this.cleanupImpression?.();\n this.cleanupImpression = undefined;\n if (this.messageHandler) {\n window.removeEventListener(\"message\", this.messageHandler);\n this.messageHandler = undefined;\n }\n }\n\n /** Resolve the ad: inline `data-ad` JSON, else an on-chain read. */\n private async loadAd(): Promise<{\n data: AdData | null;\n cid: string | null;\n /** Set when a NAME was resolved; the address it pointed at. */\n slot?: string;\n }> {\n const staticData = this.attr(\"data-ad\");\n if (staticData) {\n try {\n return { data: JSON.parse(staticData) as AdData, cid: null };\n } catch {\n return { data: null, cid: null };\n }\n }\n\n const slot = this.attr(\"slot\");\n const slotKey = this.attr(\"slot-key\");\n if (!slot && !slotKey) return { data: null, cid: null };\n\n const client = createReadClient(this.chainId(), this.attr(\"rpc-url\"));\n\n // A NAME, resolved through the metadata module, in one call.\n //\n // Preferred over an address for the reason the whole registry exists: an\n // address pasted into a page is one nobody can ever change, so redeploying\n // a slot leaves every embed carrying it showing a dead space. `slot` still\n // wins when both are set, because a publisher who pinned an address did so\n // to opt out of trusting us with the pointer.\n if (!slot && slotKey) {\n const snap = await fetchAdByKey(client, slotKey, {\n gateway: this.attr(\"gateway\"),\n adModule: this.attr(\"ad-module\"),\n });\n // The resolved address travels with it: the impression below is\n // recorded against a slot, and a name is not one.\n return snap\n ? { data: snap.data, cid: snap.cid, slot: snap.slot }\n : { data: null, cid: null };\n }\n\n const uri = await fetchMetadataURI(client, slot as string);\n if (!uri) return { data: null, cid: null };\n return fetchAdFromURI(uri, this.attr(\"gateway\"));\n }\n\n private async render() {\n const token = ++this.renderToken;\n this.teardown();\n\n // Reserve height up front so an async load doesn't shift the layout.\n if (!this.style.minHeight) this.style.minHeight = RESERVED_HEIGHT;\n\n let result: { data: AdData | null; cid: string | null; slot?: string };\n try {\n result = await this.loadAd();\n } catch {\n result = { data: null, cid: null };\n }\n // A newer render superseded this one while awaiting — bail.\n if (token !== this.renderToken || !this.isConnected) return;\n\n const inner = result.data ? cardMarkup(result.data) : emptyMarkup();\n const mode =\n this.attr(\"render-mode\") === \"inline\" ? \"inline\" : \"isolated\";\n if (mode === \"inline\") this.renderInline(inner, result.data);\n else this.renderIsolated(inner, result.data);\n\n // The attribute if there was one, otherwise the address the name\n // resolved to. Recording an impression needs an address either way.\n const resolved = this.attr(\"slot\") ?? result.slot;\n this.resolvedSlot = resolved;\n if (resolved) {\n const track =\n this.attr(\"verified\") != null\n ? trackVerifiedImpression\n : trackImpression;\n this.cleanupImpression = track(this, {\n slot: resolved,\n chainId: this.chainId(),\n placement: this.attr(\"placement\"),\n cid: result.cid,\n empty: !result.data,\n endpoint: this.attr(\"endpoint\"),\n consent: this.attr(\"consent\") as AdConsent | undefined,\n });\n }\n }\n\n private renderInline(inner: string, data: AdData | null) {\n const root =\n this.shadowRoot ??\n (this.shadowInited ? null : this.attachShadow({ mode: \"open\" }));\n if (!root) return;\n this.shadowInited = true;\n root.innerHTML = `<style>${BASE_STYLES}</style>${inner}`;\n const card = root.querySelector<HTMLElement>(\".adland-card\");\n card?.addEventListener(\"click\", () => this.handleClick(data, false));\n }\n\n private renderIsolated(inner: string, data: AdData | null) {\n this.replaceChildren();\n\n const url = data ? adWebUrl(data) : this.emptyUrl();\n const body = url\n ? `<a href=\"${escapeAttr(url)}\" target=\"_blank\" rel=\"noopener noreferrer\" style=\"display:block;text-decoration:none;color:inherit\">${inner}</a>`\n : inner;\n const theme = readThemeVars(this);\n\n const iframe = document.createElement(\"iframe\");\n iframe.setAttribute(\n \"sandbox\",\n \"allow-popups allow-popups-to-escape-sandbox allow-scripts\",\n );\n iframe.setAttribute(\"title\", \"Advertisement\");\n iframe.setAttribute(\"scrolling\", \"no\");\n iframe.style.cssText = \"width:100%;border:0;display:block;height:100%\";\n iframe.srcdoc = `<!doctype html><html><head><meta charset=\"utf-8\"><style>:root{${theme}}html,body{margin:0;padding:0}${BASE_STYLES}</style></head><body>${body}<script>(function(){function send(t,x){parent.postMessage(Object.assign({__adland:t},x||{}),'*')}addEventListener('click',function(){send('click')});function size(){send('size',{height:document.documentElement.scrollHeight})}new ResizeObserver(size).observe(document.documentElement);size()})()</script></body></html>`;\n\n const handler = (e: MessageEvent) => {\n if (e.source !== iframe.contentWindow) return;\n const msg = e.data as { __adland?: string; height?: number };\n if (msg?.__adland === \"size\" && typeof msg.height === \"number\") {\n iframe.style.height = `${msg.height}px`;\n this.style.minHeight = \"\";\n } else if (msg?.__adland === \"click\") {\n // The iframe's anchor already opened the URL — only track here.\n this.handleClick(data, true);\n }\n };\n this.messageHandler = handler;\n window.addEventListener(\"message\", handler);\n this.appendChild(iframe);\n }\n\n private baseLink(): string {\n return this.attr(\"base-link-url\") ?? DEFAULT_BASE_LINK;\n }\n\n private emptyUrl(): string | null {\n const slot = this.resolvedSlot ?? this.attr(\"slot\");\n if (!slot) return null;\n return `${this.baseLink()}/slots/${slot}?chain=${this.chainId()}`;\n }\n\n private handleClick(data: AdData | null, alreadyOpened: boolean) {\n const slot = this.resolvedSlot ?? this.attr(\"slot\");\n const shared = {\n chainId: this.chainId(),\n placement: this.attr(\"placement\"),\n endpoint: this.attr(\"endpoint\"),\n consent: this.attr(\"consent\") as AdConsent | undefined,\n };\n\n if (data) {\n if (slot) trackClick(\"click\", { slot, cid: null, ...shared });\n if (!alreadyOpened) void adapter.performAdAction(data);\n } else if (slot) {\n void trackClick(\"click-empty\", { slot, ...shared });\n if (!alreadyOpened) {\n void adapter.performEmptyAdAction(\n slot,\n this.chainId(),\n this.baseLink(),\n );\n }\n }\n }\n };\n}\n","import type { ActionAdapter } from \"@adland/react/core\";\nimport { makeAdlandSlot } from \"./element\";\n\n/**\n * Define the `<adland-slot>` custom element bound to a click-routing adapter.\n * Safe to call more than once and in non-DOM environments — it no-ops if the\n * element is already defined or `customElements` is unavailable (SSR).\n */\nexport function registerAdlandSlot(\n adapter: ActionAdapter,\n tag = \"adland-slot\",\n): void {\n if (typeof customElements === \"undefined\") return;\n if (customElements.get(tag)) return;\n customElements.define(tag, makeAdlandSlot(adapter));\n}\n","import { WebActionAdapter } from \"@adland/react/core\";\nimport { registerAdlandSlot } from \"./register\";\n\nexport { WebActionAdapter } from \"@adland/react/core\";\nexport { makeAdlandSlot } from \"./element\";\nexport { registerAdlandSlot } from \"./register\";\nexport { AD_PARTS } from \"./render\";\nexport { THEME_VARS } from \"./theme\";\n\n// Convenience: importing the package auto-registers <adland-slot> with the web\n// adapter (no Farcaster). For Farcaster routing, call registerAdlandSlot with\n// FarcasterActionAdapter from \"@adland/react/farcaster\" instead.\nregisterAdlandSlot(WebActionAdapter);\n"]}
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { createReadClient, fetchMetadataURI, fetchAdFromURI, trackVerifiedImpression, trackImpression, trackClick, getAdImage, getAdTitle, getAdDescription, getAdType, WebActionAdapter } from '@adland/react/core';
1
+ import { createReadClient, fetchAdByKey, fetchMetadataURI, fetchAdFromURI, trackVerifiedImpression, trackImpression, trackClick, getAdImage, getAdTitle, getAdDescription, getAdType, WebActionAdapter } from '@adland/react/core';
2
2
  export { WebActionAdapter } from '@adland/react/core';
3
3
 
4
4
  // src/index.ts
@@ -176,6 +176,15 @@ function makeAdlandSlot(adapter) {
176
176
  ];
177
177
  shadowInited = false;
178
178
  cleanupImpression;
179
+ /**
180
+ * The address this element is actually showing.
181
+ *
182
+ * The `slot` attribute when there is one, otherwise whatever `slot-key`
183
+ * resolved to. Everything below the fetch needs an address — the impression
184
+ * is recorded against one and the empty-slot CTA links to one — and a name
185
+ * is not an address until the module has answered.
186
+ */
187
+ resolvedSlot;
179
188
  messageHandler;
180
189
  renderToken = 0;
181
190
  connectedCallback() {
@@ -212,8 +221,16 @@ function makeAdlandSlot(adapter) {
212
221
  }
213
222
  }
214
223
  const slot = this.attr("slot");
215
- if (!slot) return { data: null, cid: null };
224
+ const slotKey = this.attr("slot-key");
225
+ if (!slot && !slotKey) return { data: null, cid: null };
216
226
  const client = createReadClient(this.chainId(), this.attr("rpc-url"));
227
+ if (!slot && slotKey) {
228
+ const snap = await fetchAdByKey(client, slotKey, {
229
+ gateway: this.attr("gateway"),
230
+ adModule: this.attr("ad-module")
231
+ });
232
+ return snap ? { data: snap.data, cid: snap.cid, slot: snap.slot } : { data: null, cid: null };
233
+ }
217
234
  const uri = await fetchMetadataURI(client, slot);
218
235
  if (!uri) return { data: null, cid: null };
219
236
  return fetchAdFromURI(uri, this.attr("gateway"));
@@ -233,11 +250,12 @@ function makeAdlandSlot(adapter) {
233
250
  const mode = this.attr("render-mode") === "inline" ? "inline" : "isolated";
234
251
  if (mode === "inline") this.renderInline(inner, result.data);
235
252
  else this.renderIsolated(inner, result.data);
236
- const slot = this.attr("slot");
237
- if (slot) {
253
+ const resolved = this.attr("slot") ?? result.slot;
254
+ this.resolvedSlot = resolved;
255
+ if (resolved) {
238
256
  const track = this.attr("verified") != null ? trackVerifiedImpression : trackImpression;
239
257
  this.cleanupImpression = track(this, {
240
- slot,
258
+ slot: resolved,
241
259
  chainId: this.chainId(),
242
260
  placement: this.attr("placement"),
243
261
  cid: result.cid,
@@ -287,12 +305,12 @@ function makeAdlandSlot(adapter) {
287
305
  return this.attr("base-link-url") ?? DEFAULT_BASE_LINK;
288
306
  }
289
307
  emptyUrl() {
290
- const slot = this.attr("slot");
308
+ const slot = this.resolvedSlot ?? this.attr("slot");
291
309
  if (!slot) return null;
292
310
  return `${this.baseLink()}/slots/${slot}?chain=${this.chainId()}`;
293
311
  }
294
312
  handleClick(data, alreadyOpened) {
295
- const slot = this.attr("slot");
313
+ const slot = this.resolvedSlot ?? this.attr("slot");
296
314
  const shared = {
297
315
  chainId: this.chainId(),
298
316
  placement: this.attr("placement"),
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/render.ts","../src/theme.ts","../src/element.ts","../src/register.ts","../src/index.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["import type { AdData } from \"@adland/data\";\nimport {\n getAdDescription,\n getAdImage,\n getAdTitle,\n getAdType,\n} from \"@adland/react/core\";\n\n/** Part names exposed for `::part()` styling in inline (shadow DOM) mode. */\nexport const AD_PARTS = [\n \"card\",\n \"media\",\n \"body\",\n \"title\",\n \"description\",\n \"badge\",\n \"label\",\n] as const;\n\nconst TYPE_LABELS: Record<string, string> = {\n link: \"Link\",\n cast: \"Cast\",\n miniapp: \"Mini App\",\n token: \"Token\",\n farcasterProfile: \"Profile\",\n};\n\n/** Escape text for safe interpolation into HTML element content. */\nexport function escapeHtml(value: string): string {\n return value.replace(\n /[&<>\"']/g,\n (c) =>\n ({\n \"&\": \"&amp;\",\n \"<\": \"&lt;\",\n \">\": \"&gt;\",\n '\"': \"&quot;\",\n \"'\": \"&#39;\",\n })[c] as string,\n );\n}\n\n/** Escape a value for use inside a double-quoted HTML attribute. */\nexport function escapeAttr(value: string): string {\n return value.replace(/\"/g, \"&quot;\").replace(/</g, \"&lt;\");\n}\n\n/**\n * Default ad-card styles. Every visual is driven by a `--adland-*` custom\n * property with a sensible fallback, so a publisher can re-theme the card\n * without overriding a single rule. Custom properties inherit through the\n * shadow boundary (inline mode) and are copied into the iframe (isolated mode).\n */\nexport const BASE_STYLES = `\n:host { display: block; }\n* { box-sizing: border-box; }\n.adland-card {\n position: relative;\n display: flex;\n gap: var(--adland-gap, 12px);\n align-items: center;\n padding: var(--adland-padding, 12px);\n border: var(--adland-border-width, 1px) solid var(--adland-border, #e5e7eb);\n border-radius: var(--adland-radius, 12px);\n background: var(--adland-bg, #ffffff);\n color: var(--adland-fg, #111827);\n font-family: var(--adland-font, system-ui, -apple-system, sans-serif);\n cursor: pointer;\n text-decoration: none;\n transition: border-color 0.15s ease, box-shadow 0.15s ease;\n}\n.adland-card:hover {\n border-color: var(--adland-border-hover, #d1d5db);\n box-shadow: var(--adland-shadow-hover, 0 1px 3px rgba(0, 0, 0, 0.08));\n}\n.adland-media {\n width: var(--adland-media-size, 44px);\n height: var(--adland-media-size, 44px);\n border-radius: var(--adland-media-radius, 8px);\n object-fit: cover;\n flex: none;\n background: var(--adland-media-bg, #f3f4f6);\n}\n.adland-body { min-width: 0; flex: 1; }\n.adland-title {\n margin: 0;\n font-size: var(--adland-title-size, 14px);\n font-weight: var(--adland-title-weight, 600);\n line-height: 1.3;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n.adland-description {\n margin: 2px 0 0;\n font-size: var(--adland-description-size, 12px);\n color: var(--adland-muted, #6b7280);\n line-height: 1.4;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n.adland-badge {\n flex: none;\n font-size: 10px;\n font-weight: 600;\n padding: 2px 6px;\n border-radius: var(--adland-badge-radius, 6px);\n background: var(--adland-accent-bg, #eef2ff);\n color: var(--adland-accent-fg, #4338ca);\n}\n.adland-label {\n position: absolute;\n top: 6px;\n right: 8px;\n font-size: 9px;\n letter-spacing: 0.04em;\n text-transform: uppercase;\n color: var(--adland-label, #9ca3af);\n}\n.adland-empty { justify-content: center; color: var(--adland-muted, #6b7280); }\n`;\n\n/** Inner card markup (parts + classes) for a loaded ad. */\nexport function cardMarkup(ad: AdData): string {\n const image = getAdImage(ad);\n const title = getAdTitle(ad);\n const description = getAdDescription(ad);\n const type = getAdType(ad);\n const badge = type ? (TYPE_LABELS[type] ?? type) : \"\";\n\n return `<div part=\"card\" class=\"adland-card\">${\n image\n ? `<img part=\"media\" class=\"adland-media\" src=\"${escapeAttr(image)}\" alt=\"\" loading=\"lazy\" />`\n : \"\"\n }<div part=\"body\" class=\"adland-body\">${\n title ? `<p part=\"title\" class=\"adland-title\">${escapeHtml(title)}</p>` : \"\"\n }${\n description\n ? `<p part=\"description\" class=\"adland-description\">${escapeHtml(description)}</p>`\n : \"\"\n }</div>${\n badge\n ? `<span part=\"badge\" class=\"adland-badge\">${escapeHtml(badge)}</span>`\n : \"\"\n }<span part=\"label\" class=\"adland-label\">Ad</span></div>`;\n}\n\n/** Inner card markup for an empty slot (the \"Your ad here\" CTA). */\nexport function emptyMarkup(): string {\n return `<div part=\"card\" class=\"adland-card adland-empty\"><span part=\"description\" class=\"adland-description\">Your ad here</span><span part=\"label\" class=\"adland-label\">Ad</span></div>`;\n}\n\n/** The external URL an ad maps to on the web, or null if it has none. */\nexport function adWebUrl(ad: AdData): string | null {\n if (ad.type === \"link\" || ad.type === \"miniapp\") {\n const url = (ad.data as { url?: unknown }).url;\n return typeof url === \"string\" ? url : null;\n }\n return null;\n}\n","/**\n * The `--adland-*` custom properties the card reads. Custom properties inherit\n * through the shadow boundary automatically (inline mode), but NOT across an\n * iframe document boundary — so for isolated mode we read whichever of these\n * are set on the host and copy them into the iframe's `:root`.\n */\nexport const THEME_VARS = [\n \"--adland-bg\",\n \"--adland-fg\",\n \"--adland-muted\",\n \"--adland-label\",\n \"--adland-border\",\n \"--adland-border-hover\",\n \"--adland-border-width\",\n \"--adland-radius\",\n \"--adland-padding\",\n \"--adland-gap\",\n \"--adland-font\",\n \"--adland-shadow-hover\",\n \"--adland-media-size\",\n \"--adland-media-radius\",\n \"--adland-media-bg\",\n \"--adland-title-size\",\n \"--adland-title-weight\",\n \"--adland-description-size\",\n \"--adland-badge-radius\",\n \"--adland-accent-bg\",\n \"--adland-accent-fg\",\n] as const;\n\n/**\n * Serialize the theme custom properties currently set on `host` into a CSS\n * declaration string (e.g. `--adland-bg:#000;`). Empty when none are set.\n */\nexport function readThemeVars(host: Element): string {\n const styles = getComputedStyle(host);\n let out = \"\";\n for (const name of THEME_VARS) {\n const value = styles.getPropertyValue(name).trim();\n if (value) out += `${name}:${value};`;\n }\n return out;\n}\n","import type { AdData } from \"@adland/data\";\nimport {\n type ActionAdapter,\n type AdConsent,\n createReadClient,\n fetchAdFromURI,\n fetchMetadataURI,\n trackClick,\n trackImpression,\n trackVerifiedImpression,\n} from \"@adland/react/core\";\nimport {\n adWebUrl,\n BASE_STYLES,\n cardMarkup,\n emptyMarkup,\n escapeAttr,\n} from \"./render\";\nimport { readThemeVars } from \"./theme\";\n\nconst DEFAULT_CHAIN = 8453;\n/**\n * Where an unsold slot sends a reader.\n *\n * The 0xSlots app moved off its own subdomain and now lives under a path on the\n * apex domain, so the base carries `/app` and the slot path is appended to it.\n */\nconst DEFAULT_BASE_LINK = \"https://0xslots.org/app\";\nconst RESERVED_HEIGHT = \"68px\";\n\n/**\n * Build the `<adland-slot>` custom-element class bound to a click-routing\n * adapter. The web CDN bundle binds the web adapter (no Farcaster); the\n * Farcaster bundle binds the Mini App adapter.\n */\nexport function makeAdlandSlot(\n adapter: ActionAdapter,\n): CustomElementConstructor {\n return class AdlandSlot extends HTMLElement {\n static observedAttributes = [\n \"slot\",\n \"chain\",\n \"placement\",\n \"render-mode\",\n \"gateway\",\n \"endpoint\",\n \"rpc-url\",\n \"consent\",\n \"verified\",\n \"base-link-url\",\n \"data-ad\",\n ];\n\n private shadowInited = false;\n private cleanupImpression?: () => void;\n private messageHandler?: (e: MessageEvent) => void;\n private renderToken = 0;\n\n connectedCallback() {\n void this.render();\n }\n\n disconnectedCallback() {\n this.teardown();\n }\n\n attributeChangedCallback() {\n if (this.isConnected) void this.render();\n }\n\n private attr(name: string): string | undefined {\n return this.getAttribute(name) ?? undefined;\n }\n\n private chainId(): number {\n return Number(this.attr(\"chain\") ?? DEFAULT_CHAIN) || DEFAULT_CHAIN;\n }\n\n private teardown() {\n this.cleanupImpression?.();\n this.cleanupImpression = undefined;\n if (this.messageHandler) {\n window.removeEventListener(\"message\", this.messageHandler);\n this.messageHandler = undefined;\n }\n }\n\n /** Resolve the ad: inline `data-ad` JSON, else an on-chain read. */\n private async loadAd(): Promise<{\n data: AdData | null;\n cid: string | null;\n }> {\n const staticData = this.attr(\"data-ad\");\n if (staticData) {\n try {\n return { data: JSON.parse(staticData) as AdData, cid: null };\n } catch {\n return { data: null, cid: null };\n }\n }\n\n const slot = this.attr(\"slot\");\n if (!slot) return { data: null, cid: null };\n\n const client = createReadClient(this.chainId(), this.attr(\"rpc-url\"));\n const uri = await fetchMetadataURI(client, slot);\n if (!uri) return { data: null, cid: null };\n return fetchAdFromURI(uri, this.attr(\"gateway\"));\n }\n\n private async render() {\n const token = ++this.renderToken;\n this.teardown();\n\n // Reserve height up front so an async load doesn't shift the layout.\n if (!this.style.minHeight) this.style.minHeight = RESERVED_HEIGHT;\n\n let result: { data: AdData | null; cid: string | null };\n try {\n result = await this.loadAd();\n } catch {\n result = { data: null, cid: null };\n }\n // A newer render superseded this one while awaiting — bail.\n if (token !== this.renderToken || !this.isConnected) return;\n\n const inner = result.data ? cardMarkup(result.data) : emptyMarkup();\n const mode =\n this.attr(\"render-mode\") === \"inline\" ? \"inline\" : \"isolated\";\n if (mode === \"inline\") this.renderInline(inner, result.data);\n else this.renderIsolated(inner, result.data);\n\n const slot = this.attr(\"slot\");\n if (slot) {\n const track =\n this.attr(\"verified\") != null\n ? trackVerifiedImpression\n : trackImpression;\n this.cleanupImpression = track(this, {\n slot,\n chainId: this.chainId(),\n placement: this.attr(\"placement\"),\n cid: result.cid,\n empty: !result.data,\n endpoint: this.attr(\"endpoint\"),\n consent: this.attr(\"consent\") as AdConsent | undefined,\n });\n }\n }\n\n private renderInline(inner: string, data: AdData | null) {\n const root =\n this.shadowRoot ??\n (this.shadowInited ? null : this.attachShadow({ mode: \"open\" }));\n if (!root) return;\n this.shadowInited = true;\n root.innerHTML = `<style>${BASE_STYLES}</style>${inner}`;\n const card = root.querySelector<HTMLElement>(\".adland-card\");\n card?.addEventListener(\"click\", () => this.handleClick(data, false));\n }\n\n private renderIsolated(inner: string, data: AdData | null) {\n this.replaceChildren();\n\n const url = data ? adWebUrl(data) : this.emptyUrl();\n const body = url\n ? `<a href=\"${escapeAttr(url)}\" target=\"_blank\" rel=\"noopener noreferrer\" style=\"display:block;text-decoration:none;color:inherit\">${inner}</a>`\n : inner;\n const theme = readThemeVars(this);\n\n const iframe = document.createElement(\"iframe\");\n iframe.setAttribute(\n \"sandbox\",\n \"allow-popups allow-popups-to-escape-sandbox allow-scripts\",\n );\n iframe.setAttribute(\"title\", \"Advertisement\");\n iframe.setAttribute(\"scrolling\", \"no\");\n iframe.style.cssText = \"width:100%;border:0;display:block;height:100%\";\n iframe.srcdoc = `<!doctype html><html><head><meta charset=\"utf-8\"><style>:root{${theme}}html,body{margin:0;padding:0}${BASE_STYLES}</style></head><body>${body}<script>(function(){function send(t,x){parent.postMessage(Object.assign({__adland:t},x||{}),'*')}addEventListener('click',function(){send('click')});function size(){send('size',{height:document.documentElement.scrollHeight})}new ResizeObserver(size).observe(document.documentElement);size()})()</script></body></html>`;\n\n const handler = (e: MessageEvent) => {\n if (e.source !== iframe.contentWindow) return;\n const msg = e.data as { __adland?: string; height?: number };\n if (msg?.__adland === \"size\" && typeof msg.height === \"number\") {\n iframe.style.height = `${msg.height}px`;\n this.style.minHeight = \"\";\n } else if (msg?.__adland === \"click\") {\n // The iframe's anchor already opened the URL — only track here.\n this.handleClick(data, true);\n }\n };\n this.messageHandler = handler;\n window.addEventListener(\"message\", handler);\n this.appendChild(iframe);\n }\n\n private baseLink(): string {\n return this.attr(\"base-link-url\") ?? DEFAULT_BASE_LINK;\n }\n\n private emptyUrl(): string | null {\n const slot = this.attr(\"slot\");\n if (!slot) return null;\n return `${this.baseLink()}/slots/${slot}?chain=${this.chainId()}`;\n }\n\n private handleClick(data: AdData | null, alreadyOpened: boolean) {\n const slot = this.attr(\"slot\");\n const shared = {\n chainId: this.chainId(),\n placement: this.attr(\"placement\"),\n endpoint: this.attr(\"endpoint\"),\n consent: this.attr(\"consent\") as AdConsent | undefined,\n };\n\n if (data) {\n if (slot) trackClick(\"click\", { slot, cid: null, ...shared });\n if (!alreadyOpened) void adapter.performAdAction(data);\n } else if (slot) {\n void trackClick(\"click-empty\", { slot, ...shared });\n if (!alreadyOpened) {\n void adapter.performEmptyAdAction(\n slot,\n this.chainId(),\n this.baseLink(),\n );\n }\n }\n }\n };\n}\n","import type { ActionAdapter } from \"@adland/react/core\";\nimport { makeAdlandSlot } from \"./element\";\n\n/**\n * Define the `<adland-slot>` custom element bound to a click-routing adapter.\n * Safe to call more than once and in non-DOM environments — it no-ops if the\n * element is already defined or `customElements` is unavailable (SSR).\n */\nexport function registerAdlandSlot(\n adapter: ActionAdapter,\n tag = \"adland-slot\",\n): void {\n if (typeof customElements === \"undefined\") return;\n if (customElements.get(tag)) return;\n customElements.define(tag, makeAdlandSlot(adapter));\n}\n","import { WebActionAdapter } from \"@adland/react/core\";\nimport { registerAdlandSlot } from \"./register\";\n\nexport { WebActionAdapter } from \"@adland/react/core\";\nexport { makeAdlandSlot } from \"./element\";\nexport { registerAdlandSlot } from \"./register\";\nexport { AD_PARTS } from \"./render\";\nexport { THEME_VARS } from \"./theme\";\n\n// Convenience: importing the package auto-registers <adland-slot> with the web\n// adapter (no Farcaster). For Farcaster routing, call registerAdlandSlot with\n// FarcasterActionAdapter from \"@adland/react/farcaster\" instead.\nregisterAdlandSlot(WebActionAdapter);\n"]}
1
+ {"version":3,"sources":["../src/render.ts","../src/theme.ts","../src/element.ts","../src/register.ts","../src/index.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["import type { AdData } from \"@adland/data\";\nimport {\n getAdDescription,\n getAdImage,\n getAdTitle,\n getAdType,\n} from \"@adland/react/core\";\n\n/** Part names exposed for `::part()` styling in inline (shadow DOM) mode. */\nexport const AD_PARTS = [\n \"card\",\n \"media\",\n \"body\",\n \"title\",\n \"description\",\n \"badge\",\n \"label\",\n] as const;\n\nconst TYPE_LABELS: Record<string, string> = {\n link: \"Link\",\n cast: \"Cast\",\n miniapp: \"Mini App\",\n token: \"Token\",\n farcasterProfile: \"Profile\",\n};\n\n/** Escape text for safe interpolation into HTML element content. */\nexport function escapeHtml(value: string): string {\n return value.replace(\n /[&<>\"']/g,\n (c) =>\n ({\n \"&\": \"&amp;\",\n \"<\": \"&lt;\",\n \">\": \"&gt;\",\n '\"': \"&quot;\",\n \"'\": \"&#39;\",\n })[c] as string,\n );\n}\n\n/** Escape a value for use inside a double-quoted HTML attribute. */\nexport function escapeAttr(value: string): string {\n return value.replace(/\"/g, \"&quot;\").replace(/</g, \"&lt;\");\n}\n\n/**\n * Default ad-card styles. Every visual is driven by a `--adland-*` custom\n * property with a sensible fallback, so a publisher can re-theme the card\n * without overriding a single rule. Custom properties inherit through the\n * shadow boundary (inline mode) and are copied into the iframe (isolated mode).\n */\nexport const BASE_STYLES = `\n:host { display: block; }\n* { box-sizing: border-box; }\n.adland-card {\n position: relative;\n display: flex;\n gap: var(--adland-gap, 12px);\n align-items: center;\n padding: var(--adland-padding, 12px);\n border: var(--adland-border-width, 1px) solid var(--adland-border, #e5e7eb);\n border-radius: var(--adland-radius, 12px);\n background: var(--adland-bg, #ffffff);\n color: var(--adland-fg, #111827);\n font-family: var(--adland-font, system-ui, -apple-system, sans-serif);\n cursor: pointer;\n text-decoration: none;\n transition: border-color 0.15s ease, box-shadow 0.15s ease;\n}\n.adland-card:hover {\n border-color: var(--adland-border-hover, #d1d5db);\n box-shadow: var(--adland-shadow-hover, 0 1px 3px rgba(0, 0, 0, 0.08));\n}\n.adland-media {\n width: var(--adland-media-size, 44px);\n height: var(--adland-media-size, 44px);\n border-radius: var(--adland-media-radius, 8px);\n object-fit: cover;\n flex: none;\n background: var(--adland-media-bg, #f3f4f6);\n}\n.adland-body { min-width: 0; flex: 1; }\n.adland-title {\n margin: 0;\n font-size: var(--adland-title-size, 14px);\n font-weight: var(--adland-title-weight, 600);\n line-height: 1.3;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n.adland-description {\n margin: 2px 0 0;\n font-size: var(--adland-description-size, 12px);\n color: var(--adland-muted, #6b7280);\n line-height: 1.4;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n.adland-badge {\n flex: none;\n font-size: 10px;\n font-weight: 600;\n padding: 2px 6px;\n border-radius: var(--adland-badge-radius, 6px);\n background: var(--adland-accent-bg, #eef2ff);\n color: var(--adland-accent-fg, #4338ca);\n}\n.adland-label {\n position: absolute;\n top: 6px;\n right: 8px;\n font-size: 9px;\n letter-spacing: 0.04em;\n text-transform: uppercase;\n color: var(--adland-label, #9ca3af);\n}\n.adland-empty { justify-content: center; color: var(--adland-muted, #6b7280); }\n`;\n\n/** Inner card markup (parts + classes) for a loaded ad. */\nexport function cardMarkup(ad: AdData): string {\n const image = getAdImage(ad);\n const title = getAdTitle(ad);\n const description = getAdDescription(ad);\n const type = getAdType(ad);\n const badge = type ? (TYPE_LABELS[type] ?? type) : \"\";\n\n return `<div part=\"card\" class=\"adland-card\">${\n image\n ? `<img part=\"media\" class=\"adland-media\" src=\"${escapeAttr(image)}\" alt=\"\" loading=\"lazy\" />`\n : \"\"\n }<div part=\"body\" class=\"adland-body\">${\n title ? `<p part=\"title\" class=\"adland-title\">${escapeHtml(title)}</p>` : \"\"\n }${\n description\n ? `<p part=\"description\" class=\"adland-description\">${escapeHtml(description)}</p>`\n : \"\"\n }</div>${\n badge\n ? `<span part=\"badge\" class=\"adland-badge\">${escapeHtml(badge)}</span>`\n : \"\"\n }<span part=\"label\" class=\"adland-label\">Ad</span></div>`;\n}\n\n/** Inner card markup for an empty slot (the \"Your ad here\" CTA). */\nexport function emptyMarkup(): string {\n return `<div part=\"card\" class=\"adland-card adland-empty\"><span part=\"description\" class=\"adland-description\">Your ad here</span><span part=\"label\" class=\"adland-label\">Ad</span></div>`;\n}\n\n/** The external URL an ad maps to on the web, or null if it has none. */\nexport function adWebUrl(ad: AdData): string | null {\n if (ad.type === \"link\" || ad.type === \"miniapp\") {\n const url = (ad.data as { url?: unknown }).url;\n return typeof url === \"string\" ? url : null;\n }\n return null;\n}\n","/**\n * The `--adland-*` custom properties the card reads. Custom properties inherit\n * through the shadow boundary automatically (inline mode), but NOT across an\n * iframe document boundary — so for isolated mode we read whichever of these\n * are set on the host and copy them into the iframe's `:root`.\n */\nexport const THEME_VARS = [\n \"--adland-bg\",\n \"--adland-fg\",\n \"--adland-muted\",\n \"--adland-label\",\n \"--adland-border\",\n \"--adland-border-hover\",\n \"--adland-border-width\",\n \"--adland-radius\",\n \"--adland-padding\",\n \"--adland-gap\",\n \"--adland-font\",\n \"--adland-shadow-hover\",\n \"--adland-media-size\",\n \"--adland-media-radius\",\n \"--adland-media-bg\",\n \"--adland-title-size\",\n \"--adland-title-weight\",\n \"--adland-description-size\",\n \"--adland-badge-radius\",\n \"--adland-accent-bg\",\n \"--adland-accent-fg\",\n] as const;\n\n/**\n * Serialize the theme custom properties currently set on `host` into a CSS\n * declaration string (e.g. `--adland-bg:#000;`). Empty when none are set.\n */\nexport function readThemeVars(host: Element): string {\n const styles = getComputedStyle(host);\n let out = \"\";\n for (const name of THEME_VARS) {\n const value = styles.getPropertyValue(name).trim();\n if (value) out += `${name}:${value};`;\n }\n return out;\n}\n","import type { AdData } from \"@adland/data\";\nimport {\n type ActionAdapter,\n type AdConsent,\n createReadClient,\n fetchAdFromURI,\n fetchAdByKey,\n fetchMetadataURI,\n trackClick,\n trackImpression,\n trackVerifiedImpression,\n} from \"@adland/react/core\";\nimport {\n adWebUrl,\n BASE_STYLES,\n cardMarkup,\n emptyMarkup,\n escapeAttr,\n} from \"./render\";\nimport { readThemeVars } from \"./theme\";\n\nconst DEFAULT_CHAIN = 8453;\n/**\n * Where an unsold slot sends a reader.\n *\n * The 0xSlots app moved off its own subdomain and now lives under a path on the\n * apex domain, so the base carries `/app` and the slot path is appended to it.\n */\nconst DEFAULT_BASE_LINK = \"https://0xslots.org/app\";\nconst RESERVED_HEIGHT = \"68px\";\n\n/**\n * Build the `<adland-slot>` custom-element class bound to a click-routing\n * adapter. The web CDN bundle binds the web adapter (no Farcaster); the\n * Farcaster bundle binds the Mini App adapter.\n */\nexport function makeAdlandSlot(\n adapter: ActionAdapter,\n): CustomElementConstructor {\n return class AdlandSlot extends HTMLElement {\n static observedAttributes = [\n \"slot\",\n \"chain\",\n \"placement\",\n \"render-mode\",\n \"gateway\",\n \"endpoint\",\n \"rpc-url\",\n \"consent\",\n \"verified\",\n \"base-link-url\",\n \"data-ad\",\n ];\n\n private shadowInited = false;\n private cleanupImpression?: () => void;\n /**\n * The address this element is actually showing.\n *\n * The `slot` attribute when there is one, otherwise whatever `slot-key`\n * resolved to. Everything below the fetch needs an address — the impression\n * is recorded against one and the empty-slot CTA links to one — and a name\n * is not an address until the module has answered.\n */\n private resolvedSlot?: string;\n private messageHandler?: (e: MessageEvent) => void;\n private renderToken = 0;\n\n connectedCallback() {\n void this.render();\n }\n\n disconnectedCallback() {\n this.teardown();\n }\n\n attributeChangedCallback() {\n if (this.isConnected) void this.render();\n }\n\n private attr(name: string): string | undefined {\n return this.getAttribute(name) ?? undefined;\n }\n\n private chainId(): number {\n return Number(this.attr(\"chain\") ?? DEFAULT_CHAIN) || DEFAULT_CHAIN;\n }\n\n private teardown() {\n this.cleanupImpression?.();\n this.cleanupImpression = undefined;\n if (this.messageHandler) {\n window.removeEventListener(\"message\", this.messageHandler);\n this.messageHandler = undefined;\n }\n }\n\n /** Resolve the ad: inline `data-ad` JSON, else an on-chain read. */\n private async loadAd(): Promise<{\n data: AdData | null;\n cid: string | null;\n /** Set when a NAME was resolved; the address it pointed at. */\n slot?: string;\n }> {\n const staticData = this.attr(\"data-ad\");\n if (staticData) {\n try {\n return { data: JSON.parse(staticData) as AdData, cid: null };\n } catch {\n return { data: null, cid: null };\n }\n }\n\n const slot = this.attr(\"slot\");\n const slotKey = this.attr(\"slot-key\");\n if (!slot && !slotKey) return { data: null, cid: null };\n\n const client = createReadClient(this.chainId(), this.attr(\"rpc-url\"));\n\n // A NAME, resolved through the metadata module, in one call.\n //\n // Preferred over an address for the reason the whole registry exists: an\n // address pasted into a page is one nobody can ever change, so redeploying\n // a slot leaves every embed carrying it showing a dead space. `slot` still\n // wins when both are set, because a publisher who pinned an address did so\n // to opt out of trusting us with the pointer.\n if (!slot && slotKey) {\n const snap = await fetchAdByKey(client, slotKey, {\n gateway: this.attr(\"gateway\"),\n adModule: this.attr(\"ad-module\"),\n });\n // The resolved address travels with it: the impression below is\n // recorded against a slot, and a name is not one.\n return snap\n ? { data: snap.data, cid: snap.cid, slot: snap.slot }\n : { data: null, cid: null };\n }\n\n const uri = await fetchMetadataURI(client, slot as string);\n if (!uri) return { data: null, cid: null };\n return fetchAdFromURI(uri, this.attr(\"gateway\"));\n }\n\n private async render() {\n const token = ++this.renderToken;\n this.teardown();\n\n // Reserve height up front so an async load doesn't shift the layout.\n if (!this.style.minHeight) this.style.minHeight = RESERVED_HEIGHT;\n\n let result: { data: AdData | null; cid: string | null; slot?: string };\n try {\n result = await this.loadAd();\n } catch {\n result = { data: null, cid: null };\n }\n // A newer render superseded this one while awaiting — bail.\n if (token !== this.renderToken || !this.isConnected) return;\n\n const inner = result.data ? cardMarkup(result.data) : emptyMarkup();\n const mode =\n this.attr(\"render-mode\") === \"inline\" ? \"inline\" : \"isolated\";\n if (mode === \"inline\") this.renderInline(inner, result.data);\n else this.renderIsolated(inner, result.data);\n\n // The attribute if there was one, otherwise the address the name\n // resolved to. Recording an impression needs an address either way.\n const resolved = this.attr(\"slot\") ?? result.slot;\n this.resolvedSlot = resolved;\n if (resolved) {\n const track =\n this.attr(\"verified\") != null\n ? trackVerifiedImpression\n : trackImpression;\n this.cleanupImpression = track(this, {\n slot: resolved,\n chainId: this.chainId(),\n placement: this.attr(\"placement\"),\n cid: result.cid,\n empty: !result.data,\n endpoint: this.attr(\"endpoint\"),\n consent: this.attr(\"consent\") as AdConsent | undefined,\n });\n }\n }\n\n private renderInline(inner: string, data: AdData | null) {\n const root =\n this.shadowRoot ??\n (this.shadowInited ? null : this.attachShadow({ mode: \"open\" }));\n if (!root) return;\n this.shadowInited = true;\n root.innerHTML = `<style>${BASE_STYLES}</style>${inner}`;\n const card = root.querySelector<HTMLElement>(\".adland-card\");\n card?.addEventListener(\"click\", () => this.handleClick(data, false));\n }\n\n private renderIsolated(inner: string, data: AdData | null) {\n this.replaceChildren();\n\n const url = data ? adWebUrl(data) : this.emptyUrl();\n const body = url\n ? `<a href=\"${escapeAttr(url)}\" target=\"_blank\" rel=\"noopener noreferrer\" style=\"display:block;text-decoration:none;color:inherit\">${inner}</a>`\n : inner;\n const theme = readThemeVars(this);\n\n const iframe = document.createElement(\"iframe\");\n iframe.setAttribute(\n \"sandbox\",\n \"allow-popups allow-popups-to-escape-sandbox allow-scripts\",\n );\n iframe.setAttribute(\"title\", \"Advertisement\");\n iframe.setAttribute(\"scrolling\", \"no\");\n iframe.style.cssText = \"width:100%;border:0;display:block;height:100%\";\n iframe.srcdoc = `<!doctype html><html><head><meta charset=\"utf-8\"><style>:root{${theme}}html,body{margin:0;padding:0}${BASE_STYLES}</style></head><body>${body}<script>(function(){function send(t,x){parent.postMessage(Object.assign({__adland:t},x||{}),'*')}addEventListener('click',function(){send('click')});function size(){send('size',{height:document.documentElement.scrollHeight})}new ResizeObserver(size).observe(document.documentElement);size()})()</script></body></html>`;\n\n const handler = (e: MessageEvent) => {\n if (e.source !== iframe.contentWindow) return;\n const msg = e.data as { __adland?: string; height?: number };\n if (msg?.__adland === \"size\" && typeof msg.height === \"number\") {\n iframe.style.height = `${msg.height}px`;\n this.style.minHeight = \"\";\n } else if (msg?.__adland === \"click\") {\n // The iframe's anchor already opened the URL — only track here.\n this.handleClick(data, true);\n }\n };\n this.messageHandler = handler;\n window.addEventListener(\"message\", handler);\n this.appendChild(iframe);\n }\n\n private baseLink(): string {\n return this.attr(\"base-link-url\") ?? DEFAULT_BASE_LINK;\n }\n\n private emptyUrl(): string | null {\n const slot = this.resolvedSlot ?? this.attr(\"slot\");\n if (!slot) return null;\n return `${this.baseLink()}/slots/${slot}?chain=${this.chainId()}`;\n }\n\n private handleClick(data: AdData | null, alreadyOpened: boolean) {\n const slot = this.resolvedSlot ?? this.attr(\"slot\");\n const shared = {\n chainId: this.chainId(),\n placement: this.attr(\"placement\"),\n endpoint: this.attr(\"endpoint\"),\n consent: this.attr(\"consent\") as AdConsent | undefined,\n };\n\n if (data) {\n if (slot) trackClick(\"click\", { slot, cid: null, ...shared });\n if (!alreadyOpened) void adapter.performAdAction(data);\n } else if (slot) {\n void trackClick(\"click-empty\", { slot, ...shared });\n if (!alreadyOpened) {\n void adapter.performEmptyAdAction(\n slot,\n this.chainId(),\n this.baseLink(),\n );\n }\n }\n }\n };\n}\n","import type { ActionAdapter } from \"@adland/react/core\";\nimport { makeAdlandSlot } from \"./element\";\n\n/**\n * Define the `<adland-slot>` custom element bound to a click-routing adapter.\n * Safe to call more than once and in non-DOM environments — it no-ops if the\n * element is already defined or `customElements` is unavailable (SSR).\n */\nexport function registerAdlandSlot(\n adapter: ActionAdapter,\n tag = \"adland-slot\",\n): void {\n if (typeof customElements === \"undefined\") return;\n if (customElements.get(tag)) return;\n customElements.define(tag, makeAdlandSlot(adapter));\n}\n","import { WebActionAdapter } from \"@adland/react/core\";\nimport { registerAdlandSlot } from \"./register\";\n\nexport { WebActionAdapter } from \"@adland/react/core\";\nexport { makeAdlandSlot } from \"./element\";\nexport { registerAdlandSlot } from \"./register\";\nexport { AD_PARTS } from \"./render\";\nexport { THEME_VARS } from \"./theme\";\n\n// Convenience: importing the package auto-registers <adland-slot> with the web\n// adapter (no Farcaster). For Farcaster routing, call registerAdlandSlot with\n// FarcasterActionAdapter from \"@adland/react/farcaster\" instead.\nregisterAdlandSlot(WebActionAdapter);\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adland/embed",
3
- "version": "0.2.2",
3
+ "version": "0.3.0",
4
4
  "private": false,
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -27,8 +27,8 @@
27
27
  "dependencies": {
28
28
  "@0xslots/sdk": "^0.27.0",
29
29
  "viem": "^2.0.0",
30
- "@adland/data": "0.19.0",
31
- "@adland/react": "0.22.0"
30
+ "@adland/data": "0.20.0",
31
+ "@adland/react": "0.23.1"
32
32
  },
33
33
  "devDependencies": {
34
34
  "@types/node": "^22",