@bacano/sdk 1.0.1 → 1.1.8

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 (72) hide show
  1. package/README.md +439 -18
  2. package/STOREFRONT_CLARITY.md +140 -0
  3. package/STOREFRONT_CONSTRUCTION.md +105 -0
  4. package/STOREFRONT_LEADS.md +130 -0
  5. package/STOREFRONT_LEGAL.md +66 -0
  6. package/STOREFRONT_MARKETING.md +291 -0
  7. package/STOREFRONT_PERFORMANCE.md +304 -0
  8. package/STOREFRONT_PREVIEW.md +499 -0
  9. package/STOREFRONT_PRODUCT_SPECIFICATIONS.md +127 -0
  10. package/STOREFRONT_VOLUME_PRICING.md +131 -0
  11. package/client.d.ts +8 -1
  12. package/errors.d.ts +9 -1
  13. package/graphql.cjs +266 -3
  14. package/graphql.d.ts +2 -2
  15. package/graphql.js +266 -3
  16. package/index.cjs +2035 -255
  17. package/index.d.ts +8 -6
  18. package/index.js +2035 -255
  19. package/marketing/attribution.d.ts +53 -0
  20. package/marketing/clarity.d.ts +3 -0
  21. package/marketing/domain.d.ts +1 -0
  22. package/marketing/ga4.d.ts +2 -0
  23. package/marketing/index.d.ts +5 -0
  24. package/marketing/runtime.d.ts +2 -0
  25. package/marketing/sanitize.d.ts +2 -0
  26. package/marketing/types.d.ts +111 -0
  27. package/media.d.ts +45 -2
  28. package/modules/auth.d.ts +3 -2
  29. package/modules/availability.d.ts +2 -2
  30. package/modules/cart-errors.d.ts +2 -0
  31. package/modules/cart-session.d.ts +2 -2
  32. package/modules/cart.d.ts +4 -3
  33. package/modules/catalog.d.ts +15 -3
  34. package/modules/content.d.ts +2 -2
  35. package/modules/marketing.d.ts +5 -0
  36. package/modules/orders.d.ts +2 -2
  37. package/modules/profile.d.ts +1 -1
  38. package/modules/static-catalog.d.ts +13 -0
  39. package/modules/website.d.ts +7 -1
  40. package/package.json +14 -3
  41. package/react/context-registry.d.ts +9 -0
  42. package/react/context.d.ts +1 -1
  43. package/react/hooks.d.ts +24 -2
  44. package/react/marketing-context.d.ts +3 -0
  45. package/react/marketing-provider.d.ts +10 -0
  46. package/react/preview/cache.d.ts +23 -0
  47. package/react/preview/context.d.ts +5 -0
  48. package/react/preview/navigation.d.ts +9 -0
  49. package/react/preview/provider.d.ts +3 -0
  50. package/react/preview/shell.d.ts +3 -0
  51. package/react/preview/toolbar.d.ts +3 -0
  52. package/react/preview/types.d.ts +60 -0
  53. package/react/preview.cjs +742 -0
  54. package/react/preview.d.ts +14 -0
  55. package/react/preview.js +730 -0
  56. package/react/website-gate.d.ts +22 -0
  57. package/react.cjs +3893 -1795
  58. package/react.d.ts +8 -4
  59. package/react.js +3891 -1789
  60. package/skills/bacano-web/SKILL.md +116 -0
  61. package/skills/bacano-web/agents/openai.yaml +4 -0
  62. package/skills/bacano-web/references/architecture-runtime.md +109 -0
  63. package/skills/bacano-web/references/common.md +92 -0
  64. package/skills/bacano-web/references/informational.md +39 -0
  65. package/skills/bacano-web/references/report.md +70 -0
  66. package/skills/bacano-web/references/storefront.md +116 -0
  67. package/skills/bacano-web/scripts/audit-http.mjs +284 -0
  68. package/skills/bacano-web/scripts/cli.mjs +222 -0
  69. package/skills/bacano-web/scripts/postinstall.mjs +8 -0
  70. package/transport/documents.d.ts +23 -18
  71. package/transport/graphql.d.ts +13 -1
  72. package/types.d.ts +338 -0
@@ -0,0 +1,730 @@
1
+ // libs/sdk/src/react/preview/context.ts
2
+ import { useContext } from "react";
3
+
4
+ // libs/sdk/src/react/context-registry.ts
5
+ import { createContext } from "react";
6
+ var REACT_CONTEXT_REGISTRY = /* @__PURE__ */ Symbol.for(
7
+ "@bacano/sdk/react-context-registry/v1"
8
+ );
9
+ function getReactContextRegistry() {
10
+ const globalRegistry = globalThis;
11
+ const existing = globalRegistry[REACT_CONTEXT_REGISTRY];
12
+ if (existing instanceof Map) {
13
+ return existing;
14
+ }
15
+ const registry = /* @__PURE__ */ new Map();
16
+ globalRegistry[REACT_CONTEXT_REGISTRY] = registry;
17
+ return registry;
18
+ }
19
+ function getOrCreateReactContext(key, defaultValue) {
20
+ const registry = getReactContextRegistry();
21
+ const existing = registry.get(key);
22
+ if (existing) {
23
+ return existing;
24
+ }
25
+ const context = createContext(defaultValue);
26
+ registry.set(key, context);
27
+ return context;
28
+ }
29
+
30
+ // libs/sdk/src/react/preview/context.ts
31
+ var BacanoPreviewModeContext = getOrCreateReactContext(
32
+ "bacano-preview-mode-v1",
33
+ false
34
+ );
35
+ var BacanoPreviewContext = getOrCreateReactContext(
36
+ "bacano-preview-state-v1",
37
+ null
38
+ );
39
+ function useBacanoPreview() {
40
+ const context = useContext(BacanoPreviewContext);
41
+ if (!context) {
42
+ throw new Error(
43
+ "useBacanoPreview must be used inside BacanoPreviewProvider."
44
+ );
45
+ }
46
+ return context;
47
+ }
48
+ function useBacanoPreviewMode() {
49
+ return useContext(BacanoPreviewModeContext);
50
+ }
51
+
52
+ // libs/sdk/src/react/preview/navigation.ts
53
+ function normalizePreviewRoute(value) {
54
+ if (!value) return "/";
55
+ let decoded = value.trim();
56
+ try {
57
+ decoded = decodeURIComponent(decoded);
58
+ } catch {
59
+ return "/";
60
+ }
61
+ if (!decoded.startsWith("/") || decoded.startsWith("//")) return "/";
62
+ const [pathname] = decoded.split(/[?#]/, 1);
63
+ if (!pathname || pathname.includes("\\") || Array.from(pathname).some((character) => character.charCodeAt(0) < 32)) {
64
+ return "/";
65
+ }
66
+ return pathname === "/" ? pathname : pathname.replace(/\/+$/, "");
67
+ }
68
+ function interceptPreviewLink(event, options) {
69
+ if (event.defaultPrevented || event.button !== 0 || event.metaKey || event.ctrlKey || event.shiftKey || event.altKey || typeof Element === "undefined") {
70
+ return false;
71
+ }
72
+ if (!(event.target instanceof Element)) return false;
73
+ const anchor = event.target.closest("a");
74
+ if (!(anchor instanceof HTMLAnchorElement) || anchor.target === "_blank" || anchor.hasAttribute("download")) {
75
+ return false;
76
+ }
77
+ const currentOrigin = options.origin ?? (typeof window === "undefined" ? void 0 : window.location.origin);
78
+ if (!currentOrigin) return false;
79
+ const url = new URL(anchor.href, currentOrigin);
80
+ if (url.origin !== currentOrigin) return false;
81
+ const normalize = options.normalizeRoute ?? normalizePreviewRoute;
82
+ const route = normalize(url.pathname);
83
+ if (options.isPreviewableRoute?.(route) === false) return false;
84
+ event.preventDefault();
85
+ options.navigate(route);
86
+ return true;
87
+ }
88
+
89
+ // libs/sdk/src/react/preview/provider.tsx
90
+ import {
91
+ useCallback,
92
+ useContext as useContext2,
93
+ useEffect,
94
+ useMemo,
95
+ useRef,
96
+ useState
97
+ } from "react";
98
+
99
+ // libs/sdk/src/react/context.ts
100
+ var BacanoReactContext = getOrCreateReactContext("bacano-client-v1", {
101
+ client: null,
102
+ loading: true,
103
+ error: null
104
+ });
105
+
106
+ // libs/sdk/src/react/marketing-context.ts
107
+ var NOOP_MARKETING_RUNTIME = {
108
+ enabled: false,
109
+ track: () => void 0,
110
+ captureAttribution: () => null,
111
+ getAttribution: () => null,
112
+ destroy: () => void 0
113
+ };
114
+ var BacanoMarketingContext = getOrCreateReactContext(
115
+ "bacano-marketing-v1",
116
+ NOOP_MARKETING_RUNTIME
117
+ );
118
+
119
+ // libs/sdk/src/react/preview/cache.ts
120
+ var PreviewRouteCache = class {
121
+ constructor(maxEntries) {
122
+ this.maxEntries = maxEntries;
123
+ }
124
+ maxEntries;
125
+ values = /* @__PURE__ */ new Map();
126
+ inFlight = /* @__PURE__ */ new Map();
127
+ load(input) {
128
+ const pending = this.inFlight.get(input.route);
129
+ if (pending) return pending;
130
+ if (!input.force) {
131
+ const cached = this.values.get(input.route);
132
+ if (cached) {
133
+ this.values.delete(input.route);
134
+ this.values.set(input.route, cached);
135
+ return Promise.resolve(cached);
136
+ }
137
+ }
138
+ const request = input.loader({
139
+ route: input.route,
140
+ force: input.force,
141
+ client: input.client
142
+ }).then((document) => {
143
+ if (document === null || document === void 0) {
144
+ throw new EmptyPreviewDocumentError();
145
+ }
146
+ const value = { document, refreshedAt: /* @__PURE__ */ new Date() };
147
+ this.values.delete(input.route);
148
+ this.values.set(input.route, value);
149
+ this.trim();
150
+ return value;
151
+ });
152
+ this.inFlight.set(input.route, request);
153
+ const clearInFlight = () => {
154
+ if (this.inFlight.get(input.route) === request) {
155
+ this.inFlight.delete(input.route);
156
+ }
157
+ };
158
+ void request.then(clearInFlight, clearInFlight);
159
+ return request;
160
+ }
161
+ clear() {
162
+ this.values.clear();
163
+ this.inFlight.clear();
164
+ }
165
+ trim() {
166
+ while (this.values.size > this.maxEntries) {
167
+ const oldestKey = this.values.keys().next().value;
168
+ if (typeof oldestKey !== "string") return;
169
+ this.values.delete(oldestKey);
170
+ }
171
+ }
172
+ };
173
+ var EmptyPreviewDocumentError = class extends Error {
174
+ constructor() {
175
+ super("Preview loader returned an empty document.");
176
+ this.name = "EmptyPreviewDocumentError";
177
+ }
178
+ };
179
+
180
+ // libs/sdk/src/react/preview/provider.tsx
181
+ import { jsx } from "react/jsx-runtime";
182
+ var DEFAULT_CACHE_MAX_ENTRIES = 50;
183
+ function createInitialState() {
184
+ return {
185
+ status: "idle",
186
+ document: null,
187
+ error: null,
188
+ refreshedAt: null
189
+ };
190
+ }
191
+ function getStorageKey(websiteSlug) {
192
+ return `@bacano/sdk:preview:${encodeURIComponent(websiteSlug)}:ui:v1`;
193
+ }
194
+ function isToolbarPosition(value) {
195
+ return value === "top-left" || value === "top-right" || value === "bottom-left" || value === "bottom-right";
196
+ }
197
+ function readPersistedToolbarState(websiteSlug) {
198
+ if (typeof window === "undefined") return null;
199
+ try {
200
+ const raw = window.sessionStorage.getItem(getStorageKey(websiteSlug));
201
+ if (!raw) return null;
202
+ const value = JSON.parse(raw);
203
+ return {
204
+ expanded: typeof value.expanded === "boolean" ? value.expanded : void 0,
205
+ position: isToolbarPosition(value.position) ? value.position : void 0
206
+ };
207
+ } catch {
208
+ return null;
209
+ }
210
+ }
211
+ function persistToolbarState(websiteSlug, state) {
212
+ if (typeof window === "undefined") return;
213
+ try {
214
+ window.sessionStorage.setItem(
215
+ getStorageKey(websiteSlug),
216
+ JSON.stringify(state)
217
+ );
218
+ } catch {
219
+ }
220
+ }
221
+ function safeLoaderError(error) {
222
+ if (error instanceof EmptyPreviewDocumentError) {
223
+ return {
224
+ code: "EMPTY_DOCUMENT",
225
+ message: "No encontramos contenido publicado para esta ruta."
226
+ };
227
+ }
228
+ return {
229
+ code: "LOADER_FAILED",
230
+ message: "No pudimos consultar el contenido publicado de Bacano."
231
+ };
232
+ }
233
+ function BacanoPreviewProvider({
234
+ websiteSlug,
235
+ route: requestedRoute,
236
+ loadRoute,
237
+ navigate: navigateStorefront,
238
+ normalizeRoute = normalizePreviewRoute,
239
+ isPreviewableRoute,
240
+ initialExpanded = true,
241
+ initialPosition = "top-right",
242
+ cacheMaxEntries = DEFAULT_CACHE_MAX_ENTRIES,
243
+ children
244
+ }) {
245
+ const clientState = useContext2(BacanoReactContext);
246
+ const route = useMemo(
247
+ () => normalizeRoute(requestedRoute),
248
+ [normalizeRoute, requestedRoute]
249
+ );
250
+ const [state, setState] = useState(createInitialState);
251
+ const [isExpanded, setExpandedState] = useState(initialExpanded);
252
+ const [position, setPositionState] = useState(initialPosition);
253
+ const mountedRef = useRef(false);
254
+ const requestSequenceRef = useRef(0);
255
+ const cacheRef = useRef(null);
256
+ const cacheSizeRef = useRef(cacheMaxEntries);
257
+ if (!cacheRef.current || cacheSizeRef.current !== cacheMaxEntries) {
258
+ cacheRef.current = new PreviewRouteCache(
259
+ Math.max(1, cacheMaxEntries)
260
+ );
261
+ cacheSizeRef.current = cacheMaxEntries;
262
+ }
263
+ useEffect(() => {
264
+ mountedRef.current = true;
265
+ return () => {
266
+ mountedRef.current = false;
267
+ requestSequenceRef.current += 1;
268
+ };
269
+ }, []);
270
+ useEffect(() => {
271
+ const persisted = readPersistedToolbarState(websiteSlug);
272
+ setExpandedState(persisted?.expanded ?? initialExpanded);
273
+ setPositionState(persisted?.position ?? initialPosition);
274
+ }, [initialExpanded, initialPosition, websiteSlug]);
275
+ const load = useCallback(
276
+ async (force, preserveDocument) => {
277
+ const client = clientState.client;
278
+ if (!client) {
279
+ if (!clientState.loading) {
280
+ setState({
281
+ status: "error",
282
+ document: null,
283
+ error: {
284
+ code: "CLIENT_UNAVAILABLE",
285
+ message: "No pudimos inicializar la conexi\xF3n con Bacano."
286
+ },
287
+ refreshedAt: null
288
+ });
289
+ }
290
+ return;
291
+ }
292
+ if (isPreviewableRoute?.(route) === false) {
293
+ setState({
294
+ status: "error",
295
+ document: null,
296
+ error: {
297
+ code: "ROUTE_NOT_PREVIEWABLE",
298
+ message: "Esta ruta no est\xE1 disponible en la vista previa."
299
+ },
300
+ refreshedAt: null
301
+ });
302
+ return;
303
+ }
304
+ const sequence = ++requestSequenceRef.current;
305
+ setState((current) => ({
306
+ ...current,
307
+ status: "loading",
308
+ document: preserveDocument ? current.document : null,
309
+ error: null
310
+ }));
311
+ try {
312
+ const cache = cacheRef.current;
313
+ if (!cache) return;
314
+ const result = await cache.load({
315
+ route,
316
+ force,
317
+ client,
318
+ loader: loadRoute
319
+ });
320
+ if (!mountedRef.current || sequence !== requestSequenceRef.current) {
321
+ return;
322
+ }
323
+ setState({
324
+ status: "ready",
325
+ document: result.document,
326
+ error: null,
327
+ refreshedAt: result.refreshedAt
328
+ });
329
+ } catch (error) {
330
+ if (!mountedRef.current || sequence !== requestSequenceRef.current)
331
+ return;
332
+ setState((current) => ({
333
+ ...current,
334
+ status: "error",
335
+ document: preserveDocument ? current.document : null,
336
+ error: safeLoaderError(error)
337
+ }));
338
+ }
339
+ },
340
+ [
341
+ clientState.client,
342
+ clientState.loading,
343
+ isPreviewableRoute,
344
+ loadRoute,
345
+ route
346
+ ]
347
+ );
348
+ useEffect(() => {
349
+ if (clientState.loading) {
350
+ setState((current) => ({ ...current, status: "idle", error: null }));
351
+ return;
352
+ }
353
+ void load(false, false);
354
+ return () => {
355
+ requestSequenceRef.current += 1;
356
+ };
357
+ }, [clientState.loading, load]);
358
+ const refresh = useCallback(() => load(true, true), [load]);
359
+ const navigate = useCallback(
360
+ (nextRoute) => {
361
+ const normalized = normalizeRoute(nextRoute);
362
+ if (isPreviewableRoute?.(normalized) === false) {
363
+ setState((current) => ({
364
+ ...current,
365
+ status: "error",
366
+ error: {
367
+ code: "ROUTE_NOT_PREVIEWABLE",
368
+ message: "Esta ruta no est\xE1 disponible en la vista previa."
369
+ }
370
+ }));
371
+ return;
372
+ }
373
+ navigateStorefront(normalized);
374
+ },
375
+ [isPreviewableRoute, navigateStorefront, normalizeRoute]
376
+ );
377
+ const setExpanded = useCallback(
378
+ (expanded) => {
379
+ setExpandedState(expanded);
380
+ persistToolbarState(websiteSlug, { expanded, position });
381
+ },
382
+ [position, websiteSlug]
383
+ );
384
+ const setPosition = useCallback(
385
+ (nextPosition) => {
386
+ setPositionState(nextPosition);
387
+ persistToolbarState(websiteSlug, {
388
+ expanded: isExpanded,
389
+ position: nextPosition
390
+ });
391
+ },
392
+ [isExpanded, websiteSlug]
393
+ );
394
+ const value = useMemo(
395
+ () => ({
396
+ isPreview: true,
397
+ websiteSlug,
398
+ route,
399
+ ...state,
400
+ isExpanded,
401
+ position,
402
+ refresh,
403
+ navigate,
404
+ setExpanded,
405
+ setPosition
406
+ }),
407
+ [
408
+ isExpanded,
409
+ navigate,
410
+ position,
411
+ refresh,
412
+ route,
413
+ setExpanded,
414
+ setPosition,
415
+ state,
416
+ websiteSlug
417
+ ]
418
+ );
419
+ return /* @__PURE__ */ jsx(BacanoPreviewModeContext.Provider, { value: true, children: /* @__PURE__ */ jsx(BacanoMarketingContext.Provider, { value: NOOP_MARKETING_RUNTIME, children: /* @__PURE__ */ jsx(
420
+ BacanoPreviewContext.Provider,
421
+ {
422
+ value,
423
+ children
424
+ }
425
+ ) }) });
426
+ }
427
+
428
+ // libs/sdk/src/react/preview/toolbar.tsx
429
+ import {
430
+ useEffect as useEffect2,
431
+ useId,
432
+ useState as useState2
433
+ } from "react";
434
+ import { Fragment, jsx as jsx2, jsxs } from "react/jsx-runtime";
435
+ var positionOptions = [
436
+ { value: "top-left", label: "Arriba a la izquierda", symbol: "\u2196" },
437
+ { value: "top-right", label: "Arriba a la derecha", symbol: "\u2197" },
438
+ { value: "bottom-left", label: "Abajo a la izquierda", symbol: "\u2199" },
439
+ { value: "bottom-right", label: "Abajo a la derecha", symbol: "\u2198" }
440
+ ];
441
+ function BacanoPreviewToolbar({
442
+ websiteName,
443
+ logo,
444
+ primaryColor = "#172033",
445
+ accentColor = "#2f80ed",
446
+ backgroundColor = "#ffffff",
447
+ locale = "es-CO",
448
+ zIndex = 2147483e3,
449
+ suggestedRoutes = []
450
+ }) {
451
+ const preview = useBacanoPreview();
452
+ const [routeInput, setRouteInput] = useState2(preview.route);
453
+ const [positionMenuOpen, setPositionMenuOpen] = useState2(false);
454
+ const controlsId = useId();
455
+ const positionMenuId = useId();
456
+ const routesListId = useId();
457
+ const style = {
458
+ "--bacano-preview-primary": primaryColor,
459
+ "--bacano-preview-accent": accentColor,
460
+ "--bacano-preview-background": backgroundColor,
461
+ zIndex
462
+ };
463
+ useEffect2(() => setRouteInput(preview.route), [preview.route]);
464
+ if (!preview.isExpanded) {
465
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
466
+ /* @__PURE__ */ jsx2("style", { children: previewToolbarStyles }),
467
+ /* @__PURE__ */ jsx2(
468
+ "div",
469
+ {
470
+ className: "bacano-preview-root bacano-preview-root--collapsed",
471
+ "data-position": preview.position,
472
+ style,
473
+ children: /* @__PURE__ */ jsxs(
474
+ "button",
475
+ {
476
+ type: "button",
477
+ className: "bacano-preview-tab",
478
+ "aria-controls": controlsId,
479
+ "aria-expanded": "false",
480
+ "aria-label": `Mostrar vista previa de ${preview.route}`,
481
+ title: "Mostrar barra de vista previa",
482
+ onClick: () => preview.setExpanded(true),
483
+ children: [
484
+ /* @__PURE__ */ jsx2(PreviewIcon, {}),
485
+ /* @__PURE__ */ jsx2("span", { children: "Vista previa" }),
486
+ /* @__PURE__ */ jsx2(ChevronIcon, { direction: "down" })
487
+ ]
488
+ }
489
+ )
490
+ }
491
+ )
492
+ ] });
493
+ }
494
+ const statusMessage = getStatusMessage(
495
+ preview.status,
496
+ preview.error?.message
497
+ );
498
+ function submitRoute(event) {
499
+ event.preventDefault();
500
+ preview.navigate(routeInput);
501
+ }
502
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
503
+ /* @__PURE__ */ jsx2("style", { children: previewToolbarStyles }),
504
+ /* @__PURE__ */ jsx2(
505
+ "div",
506
+ {
507
+ className: "bacano-preview-root",
508
+ "data-position": preview.position,
509
+ style,
510
+ children: /* @__PURE__ */ jsxs(
511
+ "section",
512
+ {
513
+ id: controlsId,
514
+ className: "bacano-preview-toolbar",
515
+ "aria-label": "Controles de vista previa de Bacano",
516
+ children: [
517
+ /* @__PURE__ */ jsxs("div", { className: "bacano-preview-summary", children: [
518
+ /* @__PURE__ */ jsx2(
519
+ "span",
520
+ {
521
+ className: "bacano-preview-logo",
522
+ "aria-hidden": logo ? void 0 : true,
523
+ children: logo ?? /* @__PURE__ */ jsx2(PreviewIcon, {})
524
+ }
525
+ ),
526
+ /* @__PURE__ */ jsxs("span", { className: "bacano-preview-copy", children: [
527
+ /* @__PURE__ */ jsxs("strong", { children: [
528
+ "Vista en vivo",
529
+ websiteName ? ` \xB7 ${websiteName}` : "",
530
+ ":",
531
+ " ",
532
+ preview.route
533
+ ] }),
534
+ /* @__PURE__ */ jsx2("span", { role: "status", "aria-live": "polite", children: preview.refreshedAt && preview.status !== "error" ? `Contenido p\xFAblico consultado a las ${formatTime(
535
+ preview.refreshedAt,
536
+ locale
537
+ )}.` : statusMessage })
538
+ ] })
539
+ ] }),
540
+ /* @__PURE__ */ jsxs("form", { className: "bacano-preview-route", onSubmit: submitRoute, children: [
541
+ /* @__PURE__ */ jsx2("label", { htmlFor: `${controlsId}-route`, children: "Ruta para visualizar" }),
542
+ /* @__PURE__ */ jsxs("div", { children: [
543
+ /* @__PURE__ */ jsx2(
544
+ "input",
545
+ {
546
+ id: `${controlsId}-route`,
547
+ type: "text",
548
+ value: routeInput,
549
+ list: suggestedRoutes.length > 0 ? routesListId : void 0,
550
+ autoComplete: "off",
551
+ onChange: (event) => setRouteInput(event.target.value)
552
+ }
553
+ ),
554
+ suggestedRoutes.length > 0 ? /* @__PURE__ */ jsx2("datalist", { id: routesListId, children: suggestedRoutes.map((route) => /* @__PURE__ */ jsx2("option", { value: route }, route)) }) : null,
555
+ /* @__PURE__ */ jsx2("button", { type: "submit", "aria-label": "Abrir ruta", title: "Abrir ruta", children: /* @__PURE__ */ jsx2(ArrowIcon, {}) })
556
+ ] })
557
+ ] }),
558
+ /* @__PURE__ */ jsx2("div", { className: "bacano-preview-actions", children: /* @__PURE__ */ jsxs(
559
+ "button",
560
+ {
561
+ type: "button",
562
+ disabled: preview.status === "loading",
563
+ onClick: () => void preview.refresh(),
564
+ children: [
565
+ /* @__PURE__ */ jsx2(RefreshIcon, { spinning: preview.status === "loading" }),
566
+ /* @__PURE__ */ jsx2("span", { children: preview.status === "loading" ? "Actualizando" : "Actualizar" })
567
+ ]
568
+ }
569
+ ) }),
570
+ /* @__PURE__ */ jsxs("div", { className: "bacano-preview-corner-actions", children: [
571
+ /* @__PURE__ */ jsx2(
572
+ "button",
573
+ {
574
+ type: "button",
575
+ "aria-label": "Cambiar posici\xF3n de la barra",
576
+ "aria-controls": positionMenuId,
577
+ "aria-expanded": positionMenuOpen,
578
+ title: "Cambiar posici\xF3n",
579
+ onClick: () => setPositionMenuOpen((current) => !current),
580
+ children: /* @__PURE__ */ jsx2(MoveIcon, {})
581
+ }
582
+ ),
583
+ /* @__PURE__ */ jsx2(
584
+ "button",
585
+ {
586
+ type: "button",
587
+ "aria-label": "Ocultar barra de vista previa",
588
+ "aria-controls": controlsId,
589
+ "aria-expanded": "true",
590
+ title: "Ocultar barra",
591
+ onClick: () => {
592
+ setPositionMenuOpen(false);
593
+ preview.setExpanded(false);
594
+ },
595
+ children: /* @__PURE__ */ jsx2(ChevronIcon, { direction: "up" })
596
+ }
597
+ )
598
+ ] }),
599
+ positionMenuOpen ? /* @__PURE__ */ jsx2(
600
+ "div",
601
+ {
602
+ id: positionMenuId,
603
+ className: "bacano-preview-position-menu",
604
+ role: "group",
605
+ "aria-label": "Posici\xF3n de la barra",
606
+ onKeyDown: (event) => {
607
+ if (event.key === "Escape") setPositionMenuOpen(false);
608
+ },
609
+ children: positionOptions.map((option) => /* @__PURE__ */ jsx2(
610
+ "button",
611
+ {
612
+ type: "button",
613
+ "aria-label": option.label,
614
+ "aria-pressed": preview.position === option.value,
615
+ title: option.label,
616
+ onClick: () => {
617
+ preview.setPosition(option.value);
618
+ setPositionMenuOpen(false);
619
+ },
620
+ children: option.symbol
621
+ },
622
+ option.value
623
+ ))
624
+ }
625
+ ) : null
626
+ ]
627
+ }
628
+ )
629
+ }
630
+ )
631
+ ] });
632
+ }
633
+ function getStatusMessage(status, error) {
634
+ if (status === "error") return error ?? "No pudimos actualizar el contenido.";
635
+ if (status === "loading") return "Consultando el contenido p\xFAblico actual.";
636
+ if (status === "ready") return "Contenido p\xFAblico actualizado.";
637
+ return "Preparando la conexi\xF3n con Bacano.";
638
+ }
639
+ function formatTime(value, locale) {
640
+ try {
641
+ return new Intl.DateTimeFormat(locale, {
642
+ hour: "numeric",
643
+ minute: "2-digit",
644
+ second: "2-digit"
645
+ }).format(value);
646
+ } catch {
647
+ return value.toLocaleTimeString();
648
+ }
649
+ }
650
+ function SvgIcon(props) {
651
+ return /* @__PURE__ */ jsx2("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", ...props, children: props.children });
652
+ }
653
+ function PreviewIcon() {
654
+ return /* @__PURE__ */ jsxs(SvgIcon, { "aria-hidden": "true", children: [
655
+ /* @__PURE__ */ jsx2("path", { d: "M2.5 12s3.5-6 9.5-6 9.5 6 9.5 6-3.5 6-9.5 6-9.5-6-9.5-6Z" }),
656
+ /* @__PURE__ */ jsx2("circle", { cx: "12", cy: "12", r: "2.5" })
657
+ ] });
658
+ }
659
+ function RefreshIcon({ spinning }) {
660
+ return /* @__PURE__ */ jsxs(
661
+ SvgIcon,
662
+ {
663
+ "aria-hidden": "true",
664
+ className: spinning ? "is-spinning" : void 0,
665
+ children: [
666
+ /* @__PURE__ */ jsx2("path", { d: "M20 6v5h-5M4 18v-5h5" }),
667
+ /* @__PURE__ */ jsx2("path", { d: "M18 9a7 7 0 0 0-12-2L4 11M6 15a7 7 0 0 0 12 2l2-4" })
668
+ ]
669
+ }
670
+ );
671
+ }
672
+ function ArrowIcon() {
673
+ return /* @__PURE__ */ jsx2(SvgIcon, { "aria-hidden": "true", children: /* @__PURE__ */ jsx2("path", { d: "M5 12h14M13 6l6 6-6 6" }) });
674
+ }
675
+ function MoveIcon() {
676
+ return /* @__PURE__ */ jsx2(SvgIcon, { "aria-hidden": "true", children: /* @__PURE__ */ jsx2("path", { d: "m12 2 3 3-3 3M12 2 9 5M22 12l-3-3-3 3M22 12l-3 3M12 22l3-3-3-3M12 22l-3-3M2 12l3-3 3 3M2 12l3 3" }) });
677
+ }
678
+ function ChevronIcon({ direction }) {
679
+ return /* @__PURE__ */ jsx2(SvgIcon, { "aria-hidden": "true", children: /* @__PURE__ */ jsx2("path", { d: direction === "up" ? "m6 15 6-6 6 6" : "m6 9 6 6 6-6" }) });
680
+ }
681
+ var previewToolbarStyles = `
682
+ .bacano-preview-root{position:fixed;pointer-events:none;width:calc(100% - 24px);max-width:1240px;font-family:Inter,ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;color:var(--bacano-preview-primary);line-height:1.35}
683
+ .bacano-preview-root[data-position="top-left"]{top:12px;left:12px}.bacano-preview-root[data-position="top-right"]{top:12px;right:12px}.bacano-preview-root[data-position="bottom-left"]{bottom:12px;left:12px}.bacano-preview-root[data-position="bottom-right"]{right:12px;bottom:12px}
684
+ .bacano-preview-root--collapsed{width:auto;max-width:calc(100% - 24px)}
685
+ .bacano-preview-toolbar,.bacano-preview-tab{pointer-events:auto;border:1px solid color-mix(in srgb,var(--bacano-preview-accent) 34%,transparent);background:color-mix(in srgb,var(--bacano-preview-background) 96%,transparent);box-shadow:0 16px 42px color-mix(in srgb,var(--bacano-preview-primary) 18%,transparent);backdrop-filter:blur(16px)}
686
+ .bacano-preview-toolbar{position:relative;display:grid;grid-template-columns:minmax(220px,1fr) minmax(280px,460px) auto;align-items:center;gap:12px;border-radius:16px;padding:14px 92px 14px 16px}
687
+ .bacano-preview-summary{display:flex;min-width:0;align-items:center;gap:10px}.bacano-preview-logo{display:grid;width:34px;height:34px;flex:0 0 auto;place-items:center;overflow:hidden;border-radius:10px;background:color-mix(in srgb,var(--bacano-preview-accent) 12%,transparent);color:var(--bacano-preview-accent)}.bacano-preview-logo>svg{width:20px;height:20px;stroke-width:1.8}.bacano-preview-logo img{display:block;max-width:100%;max-height:100%;object-fit:contain}
688
+ .bacano-preview-copy{display:flex;min-width:0;flex-direction:column;gap:2px}.bacano-preview-copy strong{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:14px}.bacano-preview-copy span{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:12px;opacity:.68}
689
+ .bacano-preview-route label{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}.bacano-preview-route>div{display:flex;gap:6px}.bacano-preview-route input{min-width:0;min-height:42px;flex:1;border:1px solid color-mix(in srgb,var(--bacano-preview-primary) 20%,transparent);border-radius:10px;background:var(--bacano-preview-background);padding:0 12px;color:var(--bacano-preview-primary);font:inherit;font-size:14px}.bacano-preview-route button,.bacano-preview-actions button,.bacano-preview-corner-actions button,.bacano-preview-position-menu button,.bacano-preview-tab{border:0;font:inherit;cursor:pointer}
690
+ .bacano-preview-route button{display:grid;width:42px;height:42px;flex:0 0 auto;place-items:center;border-radius:10px;background:var(--bacano-preview-primary);color:var(--bacano-preview-background)}.bacano-preview-route svg,.bacano-preview-actions svg,.bacano-preview-corner-actions svg,.bacano-preview-tab svg{width:18px;height:18px;stroke-width:1.8}
691
+ .bacano-preview-actions button{display:flex;min-height:42px;align-items:center;justify-content:center;gap:8px;border-radius:10px;background:var(--bacano-preview-accent);padding:0 16px;color:var(--bacano-preview-background);font-weight:700}.bacano-preview-actions button:disabled{cursor:wait;opacity:.68}
692
+ .bacano-preview-corner-actions{position:absolute;top:8px;right:8px;display:flex;gap:2px}.bacano-preview-corner-actions button{display:grid;width:36px;height:36px;place-items:center;border-radius:9px;background:transparent;color:var(--bacano-preview-primary)}.bacano-preview-corner-actions button:hover{background:color-mix(in srgb,var(--bacano-preview-primary) 7%,transparent)}
693
+ .bacano-preview-position-menu{position:absolute;right:48px;top:48px;display:grid;grid-template-columns:repeat(2,44px);gap:4px;border:1px solid color-mix(in srgb,var(--bacano-preview-primary) 16%,transparent);border-radius:12px;background:var(--bacano-preview-background);padding:6px;box-shadow:0 12px 30px color-mix(in srgb,var(--bacano-preview-primary) 18%,transparent)}.bacano-preview-root[data-position^="bottom"] .bacano-preview-position-menu{top:auto;bottom:48px}.bacano-preview-position-menu button{width:44px;height:40px;border-radius:8px;background:transparent;color:var(--bacano-preview-primary);font-size:18px}.bacano-preview-position-menu button[aria-pressed="true"]{background:color-mix(in srgb,var(--bacano-preview-accent) 14%,transparent);color:var(--bacano-preview-accent)}
694
+ .bacano-preview-tab{display:flex;min-height:44px;max-width:100%;align-items:center;gap:8px;border-radius:999px;padding:0 16px;color:var(--bacano-preview-primary);font-weight:750}.bacano-preview-tab span{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
695
+ .bacano-preview-root button:focus-visible,.bacano-preview-root input:focus-visible{outline:3px solid color-mix(in srgb,var(--bacano-preview-accent) 42%,transparent);outline-offset:2px}.bacano-preview-root svg.is-spinning{animation:bacano-preview-spin .8s linear infinite}@keyframes bacano-preview-spin{to{transform:rotate(360deg)}}
696
+ @media(max-width:900px){.bacano-preview-toolbar{grid-template-columns:1fr auto;padding-right:88px}.bacano-preview-route{grid-column:1/-1;grid-row:2}.bacano-preview-actions{grid-column:2;grid-row:1}.bacano-preview-copy span{white-space:normal}.bacano-preview-actions button span{display:none}}
697
+ @media(max-width:520px){.bacano-preview-root{width:calc(100% - 16px)}.bacano-preview-root[data-position="top-left"],.bacano-preview-root[data-position="top-right"]{top:8px;left:8px;right:auto}.bacano-preview-root[data-position="bottom-left"],.bacano-preview-root[data-position="bottom-right"]{bottom:8px;left:8px;right:auto}.bacano-preview-toolbar{grid-template-columns:minmax(0,1fr);gap:10px;border-radius:14px;padding:12px 80px 12px 12px}.bacano-preview-actions{grid-column:1;grid-row:3}.bacano-preview-actions button{width:100%}.bacano-preview-actions button span{display:inline}.bacano-preview-route{grid-column:1;grid-row:2}}
698
+ @media(prefers-reduced-motion:reduce){.bacano-preview-root *{scroll-behavior:auto!important;animation-duration:.01ms!important;animation-iteration-count:1!important;transition-duration:.01ms!important}}
699
+ `;
700
+
701
+ // libs/sdk/src/react/preview/shell.tsx
702
+ import { Fragment as Fragment2, jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
703
+ function BacanoPreviewShell({
704
+ toolbar = {},
705
+ children,
706
+ ...providerProps
707
+ }) {
708
+ return /* @__PURE__ */ jsx3(BacanoPreviewProvider, { ...providerProps, children: /* @__PURE__ */ jsx3(BacanoPreviewShellContent, { toolbar, render: children }) });
709
+ }
710
+ function BacanoPreviewShellContent({
711
+ toolbar,
712
+ render
713
+ }) {
714
+ const preview = useBacanoPreview();
715
+ return /* @__PURE__ */ jsxs2(Fragment2, { children: [
716
+ toolbar === false ? null : /* @__PURE__ */ jsx3(BacanoPreviewToolbar, { ...toolbar }),
717
+ render(preview)
718
+ ] });
719
+ }
720
+ export {
721
+ BacanoPreviewContext,
722
+ BacanoPreviewModeContext,
723
+ BacanoPreviewProvider,
724
+ BacanoPreviewShell,
725
+ BacanoPreviewToolbar,
726
+ interceptPreviewLink,
727
+ normalizePreviewRoute,
728
+ useBacanoPreview,
729
+ useBacanoPreviewMode
730
+ };