@sonordev/site-kit 2.9.0 → 3.0.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.
Files changed (61) hide show
  1. package/dist/EngageWidget-LRE4VD7O.js +14 -0
  2. package/dist/{EngageWidget-JIXUBG47.js.map → EngageWidget-LRE4VD7O.js.map} +1 -1
  3. package/dist/EngageWidget-OAOLB6ED.mjs +5 -0
  4. package/dist/{EngageWidget-4W5EM4UO.mjs.map → EngageWidget-OAOLB6ED.mjs.map} +1 -1
  5. package/dist/chunk-2FEQ6TYD.mjs +37 -0
  6. package/dist/chunk-2FEQ6TYD.mjs.map +1 -0
  7. package/dist/{chunk-5QLM6LZ2.mjs → chunk-APRAGA5G.mjs} +3 -3
  8. package/dist/{chunk-5QLM6LZ2.mjs.map → chunk-APRAGA5G.mjs.map} +1 -1
  9. package/dist/{chunk-HANRPNKG.js → chunk-FMI65E5Y.js} +3 -3
  10. package/dist/{chunk-HANRPNKG.js.map → chunk-FMI65E5Y.js.map} +1 -1
  11. package/dist/{chunk-SLJJJWMV.js → chunk-FPU2QPO4.js} +338 -33
  12. package/dist/chunk-FPU2QPO4.js.map +1 -0
  13. package/dist/chunk-MLJXELPA.js +693 -0
  14. package/dist/chunk-MLJXELPA.js.map +1 -0
  15. package/dist/{chunk-IAV32YHL.mjs → chunk-RV45YMBJ.mjs} +338 -33
  16. package/dist/chunk-RV45YMBJ.mjs.map +1 -0
  17. package/dist/chunk-TCVHI3JP.mjs +686 -0
  18. package/dist/chunk-TCVHI3JP.mjs.map +1 -0
  19. package/dist/chunk-YBKALLBH.js +46 -0
  20. package/dist/chunk-YBKALLBH.js.map +1 -0
  21. package/dist/engage/index.d.mts +2 -2
  22. package/dist/engage/index.d.ts +2 -2
  23. package/dist/engage/index.js +5 -4
  24. package/dist/engage/index.js.map +1 -1
  25. package/dist/engage/index.mjs +2 -1
  26. package/dist/engage/index.mjs.map +1 -1
  27. package/dist/forms/index.js +19 -683
  28. package/dist/forms/index.js.map +1 -1
  29. package/dist/forms/index.mjs +4 -676
  30. package/dist/forms/index.mjs.map +1 -1
  31. package/dist/index.d.mts +1 -1
  32. package/dist/index.d.ts +1 -1
  33. package/dist/layout/client.js +2 -2
  34. package/dist/layout/client.mjs +1 -1
  35. package/dist/layout/index.js +2 -2
  36. package/dist/layout/index.mjs +1 -1
  37. package/dist/middleware/index.d.mts +62 -1
  38. package/dist/middleware/index.d.ts +62 -1
  39. package/dist/middleware/index.js +105 -0
  40. package/dist/middleware/index.js.map +1 -1
  41. package/dist/middleware/index.mjs +103 -1
  42. package/dist/middleware/index.mjs.map +1 -1
  43. package/dist/slots/contract.d.mts +83 -0
  44. package/dist/slots/contract.d.ts +83 -0
  45. package/dist/slots/contract.js +41 -0
  46. package/dist/slots/contract.js.map +1 -0
  47. package/dist/slots/contract.mjs +4 -0
  48. package/dist/slots/contract.mjs.map +1 -0
  49. package/dist/slots/index.d.mts +104 -0
  50. package/dist/slots/index.d.ts +104 -0
  51. package/dist/slots/index.js +186 -0
  52. package/dist/slots/index.js.map +1 -0
  53. package/dist/slots/index.mjs +146 -0
  54. package/dist/slots/index.mjs.map +1 -0
  55. package/dist/{types-BnCwwUX3.d.ts → types-D_1iUxOQ.d.mts} +18 -1
  56. package/dist/{types-BnCwwUX3.d.mts → types-D_1iUxOQ.d.ts} +18 -1
  57. package/package.json +11 -1
  58. package/dist/EngageWidget-4W5EM4UO.mjs +0 -4
  59. package/dist/EngageWidget-JIXUBG47.js +0 -13
  60. package/dist/chunk-IAV32YHL.mjs.map +0 -1
  61. package/dist/chunk-SLJJJWMV.js.map +0 -1
@@ -0,0 +1,146 @@
1
+ import { isValidSlotId, SLOT_CONTENT_MAX_LENGTH, SLOTS_CONTRACT_VERSION, verifySlotContent } from '../chunk-2FEQ6TYD.mjs';
2
+ export { SLOTS_CONTRACT_VERSION, SLOT_CONTENT_MAX_LENGTH, SLOT_CONTENT_TYPES, SLOT_ID_PATTERN, isValidSlotId, signSlotContent, slotSigningPayload, verifySlotContent } from '../chunk-2FEQ6TYD.mjs';
3
+ import '../chunk-4XPGGLVP.mjs';
4
+ import 'server-only';
5
+ import { cache } from 'react';
6
+ import { jsx, Fragment } from 'react/jsx-runtime';
7
+
8
+ function getApiConfig() {
9
+ const apiUrl = process.env.SONOR_API_URL || "https://api.sonor.io";
10
+ const apiKey = process.env.SONOR_API_KEY || "";
11
+ if (!apiKey) return null;
12
+ return { apiUrl, apiKey };
13
+ }
14
+ var warned = /* @__PURE__ */ new Set();
15
+ function warnOnce(key, message) {
16
+ if (warned.has(key)) return;
17
+ warned.add(key);
18
+ console.warn(message);
19
+ }
20
+ async function fetchSlot(id, site, revalidate, fallback, page) {
21
+ if (!isValidSlotId(id)) {
22
+ warnOnce(`id:${id}`, `[site-kit/slots] Invalid slot id "${id}" (expected ${"`"}[a-z0-9-_.:]${"`"}, lowercase) \u2014 rendering fallback`);
23
+ return null;
24
+ }
25
+ const config = getApiConfig();
26
+ if (!config) {
27
+ warnOnce("no-key", "[site-kit/slots] SONOR_API_KEY is not set \u2014 all slots render their fallbacks");
28
+ return null;
29
+ }
30
+ try {
31
+ const response = await fetch(`${config.apiUrl}/api/public/slots/resolve`, {
32
+ method: "POST",
33
+ headers: {
34
+ "Content-Type": "application/json",
35
+ "x-api-key": config.apiKey
36
+ },
37
+ body: JSON.stringify({
38
+ contract_version: SLOTS_CONTRACT_VERSION,
39
+ slots: [
40
+ {
41
+ id,
42
+ ...fallback ? { fallback: fallback.slice(0, SLOT_CONTENT_MAX_LENGTH) } : {},
43
+ ...page ? { page } : {}
44
+ }
45
+ ],
46
+ ...site ? { site } : {}
47
+ }),
48
+ next: { revalidate, tags: ["sonor-slots"] }
49
+ });
50
+ if (!response.ok) {
51
+ if (response.status !== 404) {
52
+ warnOnce(
53
+ `http:${response.status}`,
54
+ `[site-kit/slots] Resolve returned ${response.status} \u2014 rendering fallbacks`
55
+ );
56
+ }
57
+ await response.text().catch(() => {
58
+ });
59
+ return null;
60
+ }
61
+ const data = await response.json();
62
+ const payload = data?.slots?.[id];
63
+ if (!payload) return null;
64
+ if (!verifySlotContent(payload, config.apiKey)) {
65
+ warnOnce(
66
+ `sig:${id}`,
67
+ `[site-kit/slots] Dropped slot "${id}": payload failed shape/signature verification \u2014 rendering fallback`
68
+ );
69
+ return null;
70
+ }
71
+ return payload;
72
+ } catch (err) {
73
+ warnOnce("network", `[site-kit/slots] Resolve failed (${err?.message}) \u2014 rendering fallbacks`);
74
+ return null;
75
+ }
76
+ }
77
+ var getSlotCached = cache(
78
+ (id, site, revalidate, fallback, page) => fetchSlot(id, site, revalidate, fallback, page)
79
+ );
80
+ function getSlot(id, options = {}) {
81
+ const fallback = options.fallback?.trim() || void 0;
82
+ const page = options.page?.trim() || void 0;
83
+ return getSlotCached(id, options.site, options.revalidate ?? 300, fallback, page);
84
+ }
85
+ async function resolveSlots(ids, options = {}) {
86
+ const entries = await Promise.all(
87
+ ids.map(async (id) => [id, await getSlot(id, options)])
88
+ );
89
+ return Object.fromEntries(entries);
90
+ }
91
+ async function ManagedSlot({
92
+ id,
93
+ children,
94
+ site,
95
+ revalidate,
96
+ page
97
+ }) {
98
+ const fallback = typeof children === "string" ? children : void 0;
99
+ const managed = await getSlot(id, { site, revalidate, fallback, page });
100
+ return /* @__PURE__ */ jsx(Fragment, { children: managed ? managed.content : children });
101
+ }
102
+ var ManagedSlot_default = ManagedSlot;
103
+
104
+ // src/slots/revalidate.ts
105
+ var SLOTS_CACHE_TAG = "sonor-slots";
106
+ function createSlotsRevalidateHandler(secret, options) {
107
+ return async function POST(request) {
108
+ try {
109
+ const authHeader = request.headers.get("authorization");
110
+ const url = new URL(request.url);
111
+ const tokenFromQuery = url.searchParams.get("secret");
112
+ const token = authHeader?.replace(/^Bearer\s+/i, "") || tokenFromQuery;
113
+ if (!secret || token !== secret) {
114
+ return new Response(JSON.stringify({ error: "Invalid secret" }), {
115
+ status: 401,
116
+ headers: { "Content-Type": "application/json" }
117
+ });
118
+ }
119
+ let revalidateTag;
120
+ if (options?.revalidateTag) {
121
+ revalidateTag = options.revalidateTag;
122
+ } else {
123
+ const nextCache = await import('next/cache');
124
+ revalidateTag = nextCache.revalidateTag;
125
+ }
126
+ const tags = [SLOTS_CACHE_TAG, ...options?.extraTags || []];
127
+ for (const tag of tags) {
128
+ revalidateTag(tag);
129
+ }
130
+ return new Response(JSON.stringify({ revalidated: true, tags }), {
131
+ status: 200,
132
+ headers: { "Content-Type": "application/json" }
133
+ });
134
+ } catch (err) {
135
+ console.error("[site-kit/slots] Revalidation failed:", err);
136
+ return new Response(JSON.stringify({ error: "Revalidation failed" }), {
137
+ status: 500,
138
+ headers: { "Content-Type": "application/json" }
139
+ });
140
+ }
141
+ };
142
+ }
143
+
144
+ export { ManagedSlot, SLOTS_CACHE_TAG, createSlotsRevalidateHandler, ManagedSlot_default as default, getSlot, resolveSlots };
145
+ //# sourceMappingURL=index.mjs.map
146
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/slots/server-api.ts","../../src/slots/ManagedSlot.tsx","../../src/slots/revalidate.ts"],"names":[],"mappings":"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","file":"index.mjs","sourcesContent":["/**\n * @sonordev/site-kit/slots — Server-Only Resolution\n *\n * SECURITY: uses the private SONOR_API_KEY. Import only in server code\n * (RSC, route handlers, server actions).\n *\n * Design rules (see docs/SITE-KIT-3.0-SPEC-SLOTS.md):\n * - Static-safe: plain fetch with ISR caching; never touches request state,\n * so pages using slots stay prerenderable.\n * - Fallback-first: EVERY failure mode resolves to null (component renders\n * its children). Slots must never break a build or a page.\n * - Signed: unsigned/tampered payloads are dropped with a warning.\n */\n\nimport 'server-only'\nimport { cache } from 'react'\nimport {\n SLOTS_CONTRACT_VERSION,\n SLOT_CONTENT_MAX_LENGTH,\n isValidSlotId,\n verifySlotContent,\n type SlotContent,\n type SlotsResolveResponse,\n} from './contract'\n\nexport interface SlotResolveOptions {\n /** Multi-site sub-dimension: normalized lowercase host. */\n site?: string\n /** ISR revalidate window in seconds (default 300; instant updates come from the revalidate webhook). */\n revalidate?: number\n /**\n * The slot's current static text, reported on resolve so the dashboard\n * editor shows the live copy. ManagedSlot supplies this automatically when\n * its children are a plain string.\n */\n fallback?: string\n /**\n * The route this slot renders on (static literal, e.g. \"/pricing\") — feeds\n * the Copy Evidence Loop's slot↔page mapping.\n */\n page?: string\n}\n\n// New 3.0 modules are Sonor-only: no deprecated UPTRADE_* fallbacks.\nfunction getApiConfig(): { apiUrl: string; apiKey: string } | null {\n const apiUrl = process.env.SONOR_API_URL || 'https://api.sonor.io'\n const apiKey = process.env.SONOR_API_KEY || ''\n if (!apiKey) return null\n return { apiUrl, apiKey }\n}\n\nconst warned = new Set<string>()\nfunction warnOnce(key: string, message: string): void {\n if (warned.has(key)) return\n warned.add(key)\n console.warn(message)\n}\n\nasync function fetchSlot(\n id: string,\n site: string | undefined,\n revalidate: number,\n fallback: string | undefined,\n page: string | undefined,\n): Promise<SlotContent | null> {\n if (!isValidSlotId(id)) {\n warnOnce(`id:${id}`, `[site-kit/slots] Invalid slot id \"${id}\" (expected ${'`'}[a-z0-9-_.:]${'`'}, lowercase) — rendering fallback`)\n return null\n }\n\n const config = getApiConfig()\n if (!config) {\n warnOnce('no-key', '[site-kit/slots] SONOR_API_KEY is not set — all slots render their fallbacks')\n return null\n }\n\n try {\n const response = await fetch(`${config.apiUrl}/api/public/slots/resolve`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n 'x-api-key': config.apiKey,\n },\n body: JSON.stringify({\n contract_version: SLOTS_CONTRACT_VERSION,\n slots: [\n {\n id,\n ...(fallback ? { fallback: fallback.slice(0, SLOT_CONTENT_MAX_LENGTH) } : {}),\n ...(page ? { page } : {}),\n },\n ],\n ...(site ? { site } : {}),\n }),\n next: { revalidate, tags: ['sonor-slots'] },\n } as RequestInit)\n\n if (!response.ok) {\n // 404 = endpoint not deployed yet; expected during rollout. Anything else is worth one warning.\n if (response.status !== 404) {\n warnOnce(\n `http:${response.status}`,\n `[site-kit/slots] Resolve returned ${response.status} — rendering fallbacks`,\n )\n }\n await response.text().catch(() => {})\n return null\n }\n\n const data = (await response.json()) as SlotsResolveResponse\n const payload = data?.slots?.[id]\n if (!payload) return null\n\n if (!verifySlotContent(payload, config.apiKey)) {\n warnOnce(\n `sig:${id}`,\n `[site-kit/slots] Dropped slot \"${id}\": payload failed shape/signature verification — rendering fallback`,\n )\n return null\n }\n\n return payload\n } catch (err) {\n warnOnce('network', `[site-kit/slots] Resolve failed (${(err as Error)?.message}) — rendering fallbacks`)\n return null\n }\n}\n\n/**\n * Per-render deduped single-slot resolution. Multiple <ManagedSlot id=\"x\">\n * in one render produce one fetch; Next's data cache + the 'sonor-slots' tag\n * make steady-state cost ~one request per unique slot per revalidate window.\n */\nconst getSlotCached = cache(\n (\n id: string,\n site: string | undefined,\n revalidate: number,\n fallback: string | undefined,\n page: string | undefined,\n ) => fetchSlot(id, site, revalidate, fallback, page),\n)\n\n/** Resolve one slot. Null on any failure → caller renders fallback. */\nexport function getSlot(id: string, options: SlotResolveOptions = {}): Promise<SlotContent | null> {\n const fallback = options.fallback?.trim() || undefined\n const page = options.page?.trim() || undefined\n return getSlotCached(id, options.site, options.revalidate ?? 300, fallback, page)\n}\n\n/**\n * Batch resolution for layout-level prefetch. v1 fans out to the per-slot\n * cached resolver (one wire shape, no behavioral difference); the batch\n * endpoint exists in the contract so this can become a single request\n * without a wire change.\n */\nexport async function resolveSlots(\n ids: string[],\n options: SlotResolveOptions = {},\n): Promise<Record<string, SlotContent | null>> {\n const entries = await Promise.all(\n ids.map(async (id) => [id, await getSlot(id, options)] as const),\n )\n return Object.fromEntries(entries)\n}\n","import * as React from 'react'\nimport { getSlot } from './server-api'\nimport type { SlotResolveOptions } from './server-api'\n\nexport interface ManagedSlotProps extends Omit<SlotResolveOptions, 'fallback'> {\n /** Stable slot id, lowercase, page-scoped by convention (e.g. \"home-hero-headline\"). */\n id: string\n /**\n * The developer-owned static content — exactly what the page ships without\n * Sonor. Rendered verbatim whenever no managed content resolves.\n */\n children: React.ReactNode\n}\n\n/**\n * ManagedSlot — a Sonor-managed text region inside an element the developer\n * still owns. Renders a fragment: no wrapper tag, no className opinion — put\n * it INSIDE your element and keep your markup.\n *\n * Server component. Resolution is static-safe (ISR-cached fetch, no request\n * access) and fallback-first: any failure renders the children. Managed\n * content is signature-verified and rendered as escaped text, never HTML.\n *\n * @example\n * ```tsx\n * <h1 className=\"hero-title\">\n * <ManagedSlot id=\"home-hero-headline\" page=\"/\">\n * New Homes in Cincinnati & Northern Kentucky\n * </ManagedSlot>\n * </h1>\n * ```\n */\nexport async function ManagedSlot({\n id,\n children,\n site,\n revalidate,\n page,\n}: ManagedSlotProps): Promise<React.ReactElement> {\n // When the fallback is a plain string, report it on resolve so the\n // dashboard editor shows the site's live copy next to the slot id.\n const fallback = typeof children === 'string' ? children : undefined\n const managed = await getSlot(id, { site, revalidate, fallback, page })\n // Escaped text by contract (type 'text'); React renders it inert.\n return <>{managed ? managed.content : children}</>\n}\n\nexport default ManagedSlot\n","/**\n * @sonordev/site-kit/slots — On-demand Revalidation\n *\n * Sonor calls this when slot content changes (owner edit or approved Signal\n * proposal) so the change is live in seconds instead of at the next ISR\n * window. Mirrors the llms revalidation handler pattern.\n *\n * @example\n * ```ts\n * // app/api/revalidate-slots/route.ts\n * import { createSlotsRevalidateHandler } from '@sonordev/site-kit/slots'\n *\n * export const POST = createSlotsRevalidateHandler(process.env.REVALIDATION_SECRET!)\n * ```\n */\n\ntype RevalidateTagFn = (tag: string) => void\n\nexport const SLOTS_CACHE_TAG = 'sonor-slots'\n\n/**\n * Create a POST handler that busts the slots data cache.\n * Protected by a shared secret. (POST handlers are never statically\n * prerendered, so reading the request here is fine.)\n */\nexport function createSlotsRevalidateHandler(\n secret: string,\n options?: {\n /** Additional cache tags to revalidate alongside slots. */\n extraTags?: string[]\n /** Custom revalidateTag (for testing or frameworks other than Next.js). */\n revalidateTag?: RevalidateTagFn\n },\n): (request: Request) => Promise<Response> {\n return async function POST(request: Request): Promise<Response> {\n try {\n const authHeader = request.headers.get('authorization')\n const url = new URL(request.url)\n const tokenFromQuery = url.searchParams.get('secret')\n const token = authHeader?.replace(/^Bearer\\s+/i, '') || tokenFromQuery\n\n if (!secret || token !== secret) {\n return new Response(JSON.stringify({ error: 'Invalid secret' }), {\n status: 401,\n headers: { 'Content-Type': 'application/json' },\n })\n }\n\n let revalidateTag: RevalidateTagFn\n if (options?.revalidateTag) {\n revalidateTag = options.revalidateTag\n } else {\n // Dynamic import keeps next/cache out of non-Next consumers.\n const nextCache = await import('next/cache')\n revalidateTag = nextCache.revalidateTag\n }\n\n const tags = [SLOTS_CACHE_TAG, ...(options?.extraTags || [])]\n for (const tag of tags) {\n revalidateTag(tag)\n }\n\n return new Response(JSON.stringify({ revalidated: true, tags }), {\n status: 200,\n headers: { 'Content-Type': 'application/json' },\n })\n } catch (err) {\n console.error('[site-kit/slots] Revalidation failed:', err)\n return new Response(JSON.stringify({ error: 'Revalidation failed' }), {\n status: 500,\n headers: { 'Content-Type': 'application/json' },\n })\n }\n }\n}\n"]}
@@ -195,6 +195,23 @@ interface ChatConfig {
195
195
  /** Custom offline message */
196
196
  offlineMessage?: string;
197
197
  }
198
+ /**
199
+ * UI actions the visitor Echo endpoint (POST /echo/public/chat) may return on
200
+ * the JSON response alongside `content` / `conversationId`. The chat widget
201
+ * renders each as an inline card or chip beneath the assistant message.
202
+ *
203
+ * This mirrors the shape emitted by signal-api `echo.service.ts`. It is a LOCAL
204
+ * contract on purpose — never import it from signal-api (separate package).
205
+ *
206
+ * - `show_form` → data.formSlug (+ optional data.prefill) — offer the managed form inline.
207
+ * - `suggest_action` → data.action + data.message — clickable chip (send message or navigate).
208
+ * - `show_portfolio_item` → data.slug — Upforge-specific; the generic widget ignores it (custom Echo chat owns it).
209
+ */
210
+ type UiActionType = 'show_form' | 'suggest_action' | 'show_portfolio_item';
211
+ interface UiAction {
212
+ type: UiActionType | string;
213
+ data: Record<string, unknown>;
214
+ }
198
215
  interface ChatMessage {
199
216
  id: string;
200
217
  conversation_id: string;
@@ -247,4 +264,4 @@ interface EngageAnalytics {
247
264
  created_at: string;
248
265
  }
249
266
 
250
- export { type ActionConfig as A, type ChatConfig as C, DesignRenderer as D, type EngageContext as E, type TargetingRules as T, type WidgetType as W, type DesignDocument as a, type DesignNode as b, type EngageElement as c, type WidgetConfig as d, type TriggerConfig as e, type ChatMessage as f, type ChatConversation as g, type ChatAvailability as h, type EngageConfig as i, type EngageAnalytics as j };
267
+ export { type ActionConfig as A, type ChatConfig as C, DesignRenderer as D, type EngageContext as E, type TargetingRules as T, type UiActionType as U, type WidgetType as W, type DesignDocument as a, type DesignNode as b, type EngageElement as c, type WidgetConfig as d, type TriggerConfig as e, type UiAction as f, type ChatMessage as g, type ChatConversation as h, type ChatAvailability as i, type EngageConfig as j, type EngageAnalytics as k };
@@ -195,6 +195,23 @@ interface ChatConfig {
195
195
  /** Custom offline message */
196
196
  offlineMessage?: string;
197
197
  }
198
+ /**
199
+ * UI actions the visitor Echo endpoint (POST /echo/public/chat) may return on
200
+ * the JSON response alongside `content` / `conversationId`. The chat widget
201
+ * renders each as an inline card or chip beneath the assistant message.
202
+ *
203
+ * This mirrors the shape emitted by signal-api `echo.service.ts`. It is a LOCAL
204
+ * contract on purpose — never import it from signal-api (separate package).
205
+ *
206
+ * - `show_form` → data.formSlug (+ optional data.prefill) — offer the managed form inline.
207
+ * - `suggest_action` → data.action + data.message — clickable chip (send message or navigate).
208
+ * - `show_portfolio_item` → data.slug — Upforge-specific; the generic widget ignores it (custom Echo chat owns it).
209
+ */
210
+ type UiActionType = 'show_form' | 'suggest_action' | 'show_portfolio_item';
211
+ interface UiAction {
212
+ type: UiActionType | string;
213
+ data: Record<string, unknown>;
214
+ }
198
215
  interface ChatMessage {
199
216
  id: string;
200
217
  conversation_id: string;
@@ -247,4 +264,4 @@ interface EngageAnalytics {
247
264
  created_at: string;
248
265
  }
249
266
 
250
- export { type ActionConfig as A, type ChatConfig as C, DesignRenderer as D, type EngageContext as E, type TargetingRules as T, type WidgetType as W, type DesignDocument as a, type DesignNode as b, type EngageElement as c, type WidgetConfig as d, type TriggerConfig as e, type ChatMessage as f, type ChatConversation as g, type ChatAvailability as h, type EngageConfig as i, type EngageAnalytics as j };
267
+ export { type ActionConfig as A, type ChatConfig as C, DesignRenderer as D, type EngageContext as E, type TargetingRules as T, type UiActionType as U, type WidgetType as W, type DesignDocument as a, type DesignNode as b, type EngageElement as c, type WidgetConfig as d, type TriggerConfig as e, type UiAction as f, type ChatMessage as g, type ChatConversation as h, type ChatAvailability as i, type EngageConfig as j, type EngageAnalytics as k };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sonordev/site-kit",
3
- "version": "2.9.0",
3
+ "version": "3.0.0",
4
4
  "description": "Complete client-side integration kit for Sonor - SEO, Analytics, Engage, Forms, Blog",
5
5
  "license": "MIT",
6
6
  "main": "./dist/index.js",
@@ -127,6 +127,16 @@
127
127
  "import": "./dist/llms/contract.mjs",
128
128
  "require": "./dist/llms/contract.js"
129
129
  },
130
+ "./slots": {
131
+ "types": "./dist/slots/index.d.ts",
132
+ "import": "./dist/slots/index.mjs",
133
+ "require": "./dist/slots/index.js"
134
+ },
135
+ "./slots/contract": {
136
+ "types": "./dist/slots/contract.d.ts",
137
+ "import": "./dist/slots/contract.mjs",
138
+ "require": "./dist/slots/contract.js"
139
+ },
130
140
  "./robots": {
131
141
  "types": "./dist/robots/index.d.ts",
132
142
  "import": "./dist/robots/index.mjs",
@@ -1,4 +0,0 @@
1
- export { EngageWidget } from './chunk-IAV32YHL.mjs';
2
- import './chunk-4XPGGLVP.mjs';
3
- //# sourceMappingURL=EngageWidget-4W5EM4UO.mjs.map
4
- //# sourceMappingURL=EngageWidget-4W5EM4UO.mjs.map
@@ -1,13 +0,0 @@
1
- 'use strict';
2
-
3
- var chunkSLJJJWMV_js = require('./chunk-SLJJJWMV.js');
4
- require('./chunk-ZSMWDLMK.js');
5
-
6
-
7
-
8
- Object.defineProperty(exports, "EngageWidget", {
9
- enumerable: true,
10
- get: function () { return chunkSLJJJWMV_js.EngageWidget; }
11
- });
12
- //# sourceMappingURL=EngageWidget-JIXUBG47.js.map
13
- //# sourceMappingURL=EngageWidget-JIXUBG47.js.map