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