@cimplify/sdk 0.89.0 → 0.92.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 (69) hide show
  1. package/dist/advanced.d.mts +3 -3
  2. package/dist/advanced.d.ts +3 -3
  3. package/dist/advanced.js +20 -20
  4. package/dist/advanced.mjs +1 -1
  5. package/dist/{chunk-JHJTXAPS.js → chunk-3MBMIEWT.js} +4 -4
  6. package/dist/{chunk-DENWOZEA.mjs → chunk-3XD2FVP4.mjs} +163 -43
  7. package/dist/{chunk-M4ALCELT.mjs → chunk-54H2YYIW.mjs} +2 -2
  8. package/dist/{chunk-T2CJ67QV.js → chunk-DCRN67NO.js} +201 -78
  9. package/dist/{chunk-T7Z25EY4.js → chunk-HKN3YG76.js} +84 -0
  10. package/dist/{chunk-GHFDYM5K.js → chunk-IVJEUGWF.js} +10 -1
  11. package/dist/{chunk-ROBTTS3Q.js → chunk-OAHSF542.js} +40 -1
  12. package/dist/{chunk-APBLFZ2I.mjs → chunk-PYBFTMHI.mjs} +40 -1
  13. package/dist/{chunk-OZDDHSIB.mjs → chunk-RRBY35TJ.mjs} +80 -1
  14. package/dist/{chunk-ZU4ZRYUR.mjs → chunk-T7LDBZZZ.mjs} +10 -1
  15. package/dist/chunk-U4MW6PYU.mjs +23 -0
  16. package/dist/chunk-XREIZ56K.js +26 -0
  17. package/dist/{client-CqZgdUtw.d.ts → client-BQbSKlHS.d.mts} +165 -11
  18. package/dist/{client-CAY2NbRG.d.mts → client-Bw4HtLP1.d.mts} +2 -2
  19. package/dist/{client-D8ZFs-sq.d.mts → client-CeW3acve.d.ts} +165 -11
  20. package/dist/{client-CUbwb6Om.d.ts → client-Otzv_rgP.d.ts} +2 -2
  21. package/dist/{index-CmWOz9bi.d.mts → index-CmIboQjl.d.mts} +3 -3
  22. package/dist/{index-Bc5qUxAt.d.ts → index-DgoRwz8l.d.ts} +3 -3
  23. package/dist/index.d.mts +49 -7
  24. package/dist/index.d.ts +49 -7
  25. package/dist/index.js +109 -82
  26. package/dist/index.mjs +11 -5
  27. package/dist/mock/cli.mjs +61 -1
  28. package/dist/mock/library.js +61 -1
  29. package/dist/mock/library.mjs +61 -1
  30. package/dist/mock/msw.js +61 -1
  31. package/dist/mock/msw.mjs +61 -1
  32. package/dist/{payment-Dcgb9n1n.d.mts → payment-BFtLqiG2.d.mts} +1 -1
  33. package/dist/{payment-Xq0WDZXh.d.ts → payment-Cq8FT1B6.d.ts} +1 -1
  34. package/dist/{price-DAAjKgAp.d.ts → price-DaFXT-Lr.d.ts} +1 -1
  35. package/dist/{price--Ma_WO5_.d.mts → price-DleDWk_s.d.mts} +1 -1
  36. package/dist/{product-BakHcsP2.d.ts → product-bZ8G5Z8D.d.mts} +95 -2
  37. package/dist/{product-BakHcsP2.d.mts → product-bZ8G5Z8D.d.ts} +95 -2
  38. package/dist/react.d.mts +471 -8
  39. package/dist/react.d.ts +471 -8
  40. package/dist/react.js +2787 -402
  41. package/dist/react.mjs +2699 -337
  42. package/dist/{server-Bk_hlefX.d.mts → server-D-iK-bNt.d.mts} +8 -1
  43. package/dist/{server-Bei5lBrc.d.ts → server-DwsY9hlX.d.ts} +8 -1
  44. package/dist/server.d.mts +4 -4
  45. package/dist/server.d.ts +4 -4
  46. package/dist/server.js +7 -7
  47. package/dist/server.mjs +2 -2
  48. package/dist/styles.css +1 -1
  49. package/dist/testing/msw.d.mts +2 -2
  50. package/dist/testing/msw.d.ts +2 -2
  51. package/dist/testing/msw.js +3 -2
  52. package/dist/testing/msw.mjs +2 -1
  53. package/dist/testing/suite.d.mts +6 -6
  54. package/dist/testing/suite.d.ts +6 -6
  55. package/dist/testing/suite.js +24 -23
  56. package/dist/testing/suite.mjs +5 -4
  57. package/dist/testing.d.mts +5 -5
  58. package/dist/testing.d.ts +5 -5
  59. package/dist/testing.js +80 -79
  60. package/dist/testing.mjs +6 -5
  61. package/dist/utils.d.mts +4 -4
  62. package/dist/utils.d.ts +4 -4
  63. package/package.json +4 -1
  64. package/registry/booking-page.json +1 -1
  65. package/registry/chat-widget.json +1 -1
  66. package/registry/customer-input-fields.json +1 -1
  67. package/registry/date-slot-picker.json +1 -1
  68. package/registry/product-customizer.json +1 -1
  69. package/registry/rental-service-card.json +1 -1
package/dist/react.js CHANGED
@@ -1,9 +1,10 @@
1
1
  "use client";
2
2
  'use strict';
3
3
 
4
- var chunkT7Z25EY4_js = require('./chunk-T7Z25EY4.js');
5
- var chunkT2CJ67QV_js = require('./chunk-T2CJ67QV.js');
6
- var chunkGHFDYM5K_js = require('./chunk-GHFDYM5K.js');
4
+ var chunkHKN3YG76_js = require('./chunk-HKN3YG76.js');
5
+ var chunkXREIZ56K_js = require('./chunk-XREIZ56K.js');
6
+ var chunkDCRN67NO_js = require('./chunk-DCRN67NO.js');
7
+ var chunkIVJEUGWF_js = require('./chunk-IVJEUGWF.js');
7
8
  var chunkPTPOQVXS_js = require('./chunk-PTPOQVXS.js');
8
9
  var chunkAETVRBKI_js = require('./chunk-AETVRBKI.js');
9
10
  require('./chunk-OWW5GUSB.js');
@@ -25,6 +26,8 @@ var xss = require('xss');
25
26
  var dialog = require('@base-ui/react/dialog');
26
27
  var field = require('@base-ui/react/field');
27
28
  var input = require('@base-ui/react/input');
29
+ var DOMPurify = require('dompurify');
30
+ var marked = require('marked');
28
31
 
29
32
  function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
30
33
 
@@ -48,6 +51,7 @@ function _interopNamespace(e) {
48
51
 
49
52
  var React14__default = /*#__PURE__*/_interopDefault(React14);
50
53
  var ReactDOM__namespace = /*#__PURE__*/_interopNamespace(ReactDOM);
54
+ var DOMPurify__default = /*#__PURE__*/_interopDefault(DOMPurify);
51
55
 
52
56
  // src/ads/identity.ts
53
57
  var COOKIE_NAME = "_cimplify_uid";
@@ -743,6 +747,7 @@ function mapServerCart(serverCart, fallbackCurrency) {
743
747
  scheduledStart: item.scheduled_start || void 0,
744
748
  scheduledEnd: item.scheduled_end || void 0,
745
749
  staffId: item.staff_id || void 0,
750
+ units: item.units || void 0,
746
751
  lineType: item.line_type,
747
752
  confirmationCode: item.confirmation_code || void 0,
748
753
  serviceStatus: item.service_status || void 0,
@@ -790,18 +795,18 @@ function deriveView(base, ops) {
790
795
  };
791
796
  }
792
797
  async function syncFromServer(entry) {
793
- if (entry.syncing) return;
794
- entry.syncing = true;
795
- try {
798
+ const run = async () => {
796
799
  const result = await entry.client.cart.get();
797
800
  if (!result.ok) throw result.error;
798
801
  entry.collection.setBase(mapServerCart(result.value, entry.currency));
799
- } finally {
800
- entry.syncing = false;
801
- }
802
+ };
803
+ const next = entry.syncTail.then(run, run);
804
+ entry.syncTail = next.catch(() => {
805
+ });
806
+ return next;
802
807
  }
803
808
  function emitMutationError(entry, operation, opId, idempotencyKey, error) {
804
- entry.client.events.emit("mutation:error", {
809
+ entry.client.events.emit(chunkDCRN67NO_js.MUTATION_ERROR, {
805
810
  service: "cart",
806
811
  operation,
807
812
  opId,
@@ -838,9 +843,15 @@ function getOrCreateStore(params) {
838
843
  optimisticEnabled,
839
844
  initialized: false,
840
845
  initializePromise: null,
841
- syncing: false,
846
+ syncTail: Promise.resolve(),
842
847
  failedOpRemovers: /* @__PURE__ */ new Set()
843
848
  };
849
+ client.events.on(chunkDCRN67NO_js.SESSION_CLAIMED, () => {
850
+ if (entry.initialized) void syncFromServer(entry);
851
+ });
852
+ client.events.on(chunkDCRN67NO_js.SESSION_CLEARED, () => {
853
+ if (entry.initialized) void syncFromServer(entry);
854
+ });
844
855
  cartStores.set(storeKey, entry);
845
856
  return entry;
846
857
  }
@@ -899,7 +910,8 @@ async function performAddItem(entry, product, quantity, options) {
899
910
  specialInstructions: normalizedOptions.specialInstructions,
900
911
  scheduledStart: normalizedOptions.scheduledStart,
901
912
  scheduledEnd: normalizedOptions.scheduledEnd,
902
- staffId: normalizedOptions.staffId
913
+ staffId: normalizedOptions.staffId,
914
+ units: normalizedOptions.units
903
915
  });
904
916
  const idempotencyKey = `idem_${typeof crypto !== "undefined" && crypto.randomUUID ? crypto.randomUUID() : `${Date.now()}_${Math.random().toString(36).slice(2, 10)}`}`;
905
917
  const patch = { kind: "add_item", tempItem, lineKey };
@@ -921,6 +933,7 @@ async function performAddItem(entry, product, quantity, options) {
921
933
  scheduled_end: normalizedOptions.scheduledEnd,
922
934
  staff_id: normalizedOptions.staffId,
923
935
  resource_id: normalizedOptions.resourceId,
936
+ units: normalizedOptions.units,
924
937
  customer_inputs: normalizedOptions.customerInputs
925
938
  },
926
939
  { idempotencyKey }
@@ -1132,7 +1145,7 @@ var DEFAULT_COUNTRY = "US";
1132
1145
  function createDefaultClient() {
1133
1146
  const processRef = globalThis.process;
1134
1147
  const envPublicKey = processRef?.env?.NEXT_PUBLIC_CIMPLIFY_PUBLIC_KEY || "";
1135
- return chunkT2CJ67QV_js.createCimplifyClient({ publicKey: envPublicKey });
1148
+ return chunkDCRN67NO_js.createCimplifyClient({ publicKey: envPublicKey });
1136
1149
  }
1137
1150
  function getStoredLocationId() {
1138
1151
  if (typeof window === "undefined" || !window.localStorage) {
@@ -1285,7 +1298,7 @@ function CimplifyProvider({
1285
1298
  const apiCredentials = resolvedClient.getCredentialsMode() === "include" ? "use-credentials" : "anonymous";
1286
1299
  preconnectOrigin(resolvedClient.getBaseUrl(), apiCredentials);
1287
1300
  preconnectOrigin(resolvedClient.getLinkApiUrl(), apiCredentials);
1288
- preconnectOrigin(chunkT7Z25EY4_js.DEFAULT_CDN_BASE_URL);
1301
+ preconnectOrigin(chunkHKN3YG76_js.DEFAULT_CDN_BASE_URL);
1289
1302
  }
1290
1303
  const setCurrentLocation = React14.useCallback(
1291
1304
  (location) => {
@@ -3444,7 +3457,7 @@ function CimplifySignInButton({
3444
3457
  const onClick = async () => {
3445
3458
  if (busy || disabled || !signInOpts.clientId) return;
3446
3459
  setBusy(true);
3447
- await chunkGHFDYM5K_js.startSignIn({
3460
+ await chunkIVJEUGWF_js.startSignIn({
3448
3461
  ...signInOpts,
3449
3462
  onSuccess: () => {
3450
3463
  setBusy(false);
@@ -4303,7 +4316,7 @@ function Price({
4303
4316
  ] });
4304
4317
  }
4305
4318
  function createCachedQuery(config) {
4306
- const cache = /* @__PURE__ */ new Map();
4319
+ const cache2 = /* @__PURE__ */ new Map();
4307
4320
  const inflight = /* @__PURE__ */ new Map();
4308
4321
  const gcMaxAge = config.gcMaxAge ?? 3e5;
4309
4322
  let lastSweep = 0;
@@ -4312,8 +4325,8 @@ function createCachedQuery(config) {
4312
4325
  if (now - lastSweep < 6e4) return;
4313
4326
  lastSweep = now;
4314
4327
  const cutoff = now - gcMaxAge;
4315
- for (const [key, entry] of cache) {
4316
- if (entry.ts < cutoff) cache.delete(key);
4328
+ for (const [key, entry] of cache2) {
4329
+ if (entry.ts < cutoff) cache2.delete(key);
4317
4330
  }
4318
4331
  }
4319
4332
  function useCachedQuery(options = {}) {
@@ -4331,13 +4344,13 @@ function createCachedQuery(config) {
4331
4344
  const optionsRef = React14.useRef(options);
4332
4345
  optionsRef.current = options;
4333
4346
  if (config.locationSensitive && previousLocationRef.current !== locationId) {
4334
- cache.clear();
4347
+ cache2.clear();
4335
4348
  inflight.clear();
4336
4349
  previousLocationRef.current = locationId;
4337
4350
  }
4338
4351
  sweep();
4339
4352
  const cacheKey2 = config.key(client, options);
4340
- const cached = cache.get(cacheKey2);
4353
+ const cached = cache2.get(cacheKey2);
4341
4354
  const [data, setData] = React14.useState(cached?.data ?? null);
4342
4355
  const [isLoading, setIsLoading] = React14.useState(enabled && !cached);
4343
4356
  const [error, setError] = React14.useState(null);
@@ -4350,7 +4363,7 @@ function createCachedQuery(config) {
4350
4363
  const nextRequestId = ++requestIdRef.current;
4351
4364
  setError(null);
4352
4365
  if (!force) {
4353
- const hit = cache.get(cacheKey2);
4366
+ const hit = cache2.get(cacheKey2);
4354
4367
  if (hit) {
4355
4368
  setData(hit.data);
4356
4369
  setIsLoading(false);
@@ -4367,7 +4380,7 @@ function createCachedQuery(config) {
4367
4380
  });
4368
4381
  }
4369
4382
  const value = await promise;
4370
- cache.set(cacheKey2, { data: value, ts: Date.now() });
4383
+ cache2.set(cacheKey2, { data: value, ts: Date.now() });
4371
4384
  if (nextRequestId === requestIdRef.current) {
4372
4385
  setData(value);
4373
4386
  setError(null);
@@ -4388,23 +4401,23 @@ function createCachedQuery(config) {
4388
4401
  void load(false);
4389
4402
  }, [load]);
4390
4403
  const refetch = React14.useCallback(async () => {
4391
- cache.delete(cacheKey2);
4404
+ cache2.delete(cacheKey2);
4392
4405
  await load(true);
4393
4406
  }, [cacheKey2, load]);
4394
4407
  return { data, isLoading, error, refetch };
4395
4408
  }
4396
4409
  useCachedQuery.prefetch = (client, options) => {
4397
4410
  const key = config.key(client, options);
4398
- if (cache.has(key) || inflight.has(key)) return void 0;
4411
+ if (cache2.has(key) || inflight.has(key)) return void 0;
4399
4412
  const promise = config.fetch(client, options);
4400
4413
  inflight.set(key, promise);
4401
4414
  return promise.then((value) => {
4402
- cache.set(key, { data: value, ts: Date.now() });
4415
+ cache2.set(key, { data: value, ts: Date.now() });
4403
4416
  }).catch(() => {
4404
4417
  }).finally(() => inflight.delete(key));
4405
4418
  };
4406
4419
  useCachedQuery.clear = () => {
4407
- cache.clear();
4420
+ cache2.clear();
4408
4421
  inflight.clear();
4409
4422
  };
4410
4423
  return useCachedQuery;
@@ -4532,6 +4545,9 @@ function useCategories(options = {}) {
4532
4545
  return { categories: data ?? [], isLoading, error, refetch };
4533
4546
  }
4534
4547
  var TERMINAL_STATUSES = /* @__PURE__ */ new Set(["completed", "cancelled", "refunded"]);
4548
+ function isTerminalOrderStatus(status) {
4549
+ return TERMINAL_STATUSES.has(status);
4550
+ }
4535
4551
  var orderCache = /* @__PURE__ */ new Map();
4536
4552
  var orderInflight = /* @__PURE__ */ new Map();
4537
4553
  function buildOrderCacheKey(client, orderId) {
@@ -5373,6 +5389,9 @@ function useOrders(options = {}) {
5373
5389
  const { data, isLoading, error, refetch } = ordersQuery(options);
5374
5390
  return { orders: data ?? [], isLoading, error, refetch };
5375
5391
  }
5392
+ function clearOrdersCache() {
5393
+ ordersQuery.clear();
5394
+ }
5376
5395
  function useSubscriptions(options = {}) {
5377
5396
  const { client } = useCimplifyClient();
5378
5397
  const { status, enabled = true } = options;
@@ -5468,14 +5487,24 @@ function useServices(options = {}) {
5468
5487
  }
5469
5488
 
5470
5489
  // src/react/hooks/use-available-slots.ts
5490
+ function narrowingUnits(units) {
5491
+ return units != null && units > 1 ? units : void 0;
5492
+ }
5471
5493
  var slotsQuery = createCachedQuery({
5472
5494
  name: "useAvailableSlots",
5473
- key: (client, options) => cacheKey(client.getPublicKey(), options.serviceId, options.date, options.participantCount),
5495
+ key: (client, options) => cacheKey(
5496
+ client.getPublicKey(),
5497
+ options.serviceId,
5498
+ options.date,
5499
+ options.participantCount,
5500
+ narrowingUnits(options.units)
5501
+ ),
5474
5502
  fetch: async (client, options) => {
5475
5503
  const result = await client.scheduling.getAvailableSlots({
5476
5504
  service_id: options.serviceId,
5477
5505
  date: options.date,
5478
- participant_count: options.participantCount
5506
+ participant_count: options.participantCount,
5507
+ units: narrowingUnits(options.units)
5479
5508
  });
5480
5509
  if (!result.ok) throw result.error;
5481
5510
  return result.value;
@@ -5492,6 +5521,9 @@ function useAvailableSlots(serviceId, date, options = {}) {
5492
5521
  }
5493
5522
 
5494
5523
  // src/react/hooks/use-service-availability.ts
5524
+ function narrowingUnits2(units) {
5525
+ return units != null && units > 1 ? units : void 0;
5526
+ }
5495
5527
  var serviceAvailabilityQuery = createCachedQuery({
5496
5528
  name: "useServiceAvailability",
5497
5529
  key: (client, options) => cacheKey(
@@ -5499,7 +5531,8 @@ var serviceAvailabilityQuery = createCachedQuery({
5499
5531
  options.serviceId,
5500
5532
  options.startDate,
5501
5533
  options.endDate,
5502
- options.participantCount
5534
+ options.participantCount,
5535
+ narrowingUnits2(options.units)
5503
5536
  ),
5504
5537
  fetch: async (client, options) => {
5505
5538
  const result = await client.scheduling.getServiceAvailability({
@@ -5507,7 +5540,8 @@ var serviceAvailabilityQuery = createCachedQuery({
5507
5540
  start_date: options.startDate,
5508
5541
  end_date: options.endDate,
5509
5542
  location_id: options.locationId,
5510
- participant_count: options.participantCount
5543
+ participant_count: options.participantCount,
5544
+ units: narrowingUnits2(options.units)
5511
5545
  });
5512
5546
  if (!result.ok) throw result.error;
5513
5547
  return result.value.availability;
@@ -5878,6 +5912,7 @@ var POLL_IDLE_INTERVAL_MS = 15e3;
5878
5912
  var IDLE_TIMEOUT_MS = 6e4;
5879
5913
  var WS_MAX_RECONNECT_ATTEMPTS = 10;
5880
5914
  var WS_PATH = "/api/v1/support/conversation/ws";
5915
+ var AWAIT_REPLY_TIMEOUT_MS = 3e4;
5881
5916
  function useChat(options = {}) {
5882
5917
  const context = useOptionalCimplifyClient();
5883
5918
  const client = options.client ?? context?.client;
@@ -5886,6 +5921,13 @@ function useChat(options = {}) {
5886
5921
  const [isSending, setIsSending] = React14.useState(false);
5887
5922
  const [error, setError] = React14.useState(null);
5888
5923
  const [conversationOpened, setConversationOpened] = React14.useState(false);
5924
+ const [humanRequested, setHumanRequested] = React14.useState(false);
5925
+ const [awaitingReply, setAwaitingReply] = React14.useState(false);
5926
+ const [seenAt, setSeenAt] = React14.useState(null);
5927
+ const [mayHaveOlder, setMayHaveOlder] = React14.useState(false);
5928
+ const [claimedAs, setClaimedAs] = React14.useState(
5929
+ () => client?.support.claimedCustomerName ?? null
5930
+ );
5889
5931
  const pollRef = React14.useRef(null);
5890
5932
  const lastActivityRef = React14.useRef(Date.now());
5891
5933
  const openedRef = React14.useRef(false);
@@ -5894,17 +5936,43 @@ function useChat(options = {}) {
5894
5936
  const wsRef = React14.useRef(null);
5895
5937
  const wsReconnectAttemptRef = React14.useRef(0);
5896
5938
  const wsReconnectTimerRef = React14.useRef(null);
5939
+ const aiEnabledRef = React14.useRef(false);
5940
+ const humanRequestedRef = React14.useRef(false);
5941
+ const awaitReplyTimerRef = React14.useRef(null);
5942
+ const claimUnsubRef = React14.useRef(null);
5897
5943
  messagesRef.current = messages;
5898
5944
  clientRef.current = client;
5945
+ humanRequestedRef.current = humanRequested;
5899
5946
  const pollInterval = options.pollInterval ?? POLL_INTERVAL_MS;
5900
5947
  const pollIdleInterval = options.pollIdleInterval ?? POLL_IDLE_INTERVAL_MS;
5901
- const appendMessages = React14.useCallback((incoming) => {
5902
- setMessages((prev) => {
5903
- const ids = new Set(prev.map((m) => m.id));
5904
- const fresh = incoming.filter((m) => !ids.has(m.id));
5905
- return fresh.length > 0 ? [...prev, ...fresh] : prev;
5906
- });
5948
+ const stopAwaitingReply = React14.useCallback(() => {
5949
+ if (awaitReplyTimerRef.current) {
5950
+ clearTimeout(awaitReplyTimerRef.current);
5951
+ awaitReplyTimerRef.current = null;
5952
+ }
5953
+ setAwaitingReply(false);
5907
5954
  }, []);
5955
+ const startAwaitingReply = React14.useCallback(() => {
5956
+ setAwaitingReply(true);
5957
+ if (awaitReplyTimerRef.current) clearTimeout(awaitReplyTimerRef.current);
5958
+ awaitReplyTimerRef.current = setTimeout(() => {
5959
+ awaitReplyTimerRef.current = null;
5960
+ setAwaitingReply(false);
5961
+ }, AWAIT_REPLY_TIMEOUT_MS);
5962
+ }, []);
5963
+ const appendMessages = React14.useCallback(
5964
+ (incoming) => {
5965
+ if (incoming.some((m) => m.sender_type === "bot" || m.sender_type === "agent")) {
5966
+ stopAwaitingReply();
5967
+ }
5968
+ setMessages((prev) => {
5969
+ const ids = new Set(prev.map((m) => m.id));
5970
+ const fresh = incoming.filter((m) => !ids.has(m.id));
5971
+ return fresh.length > 0 ? [...prev, ...fresh] : prev;
5972
+ });
5973
+ },
5974
+ [stopAwaitingReply]
5975
+ );
5908
5976
  const fetchNewMessages = React14.useCallback(async () => {
5909
5977
  const c = clientRef.current;
5910
5978
  if (!c) return;
@@ -5932,6 +6000,17 @@ function useChat(options = {}) {
5932
6000
  pollRef.current = null;
5933
6001
  }
5934
6002
  }, []);
6003
+ const resyncConversation = React14.useCallback(async () => {
6004
+ const c = clientRef.current;
6005
+ if (!c || !openedRef.current) return;
6006
+ const result = await c.support.openConversation();
6007
+ if (!result.ok) return;
6008
+ setMessages(result.value.messages);
6009
+ aiEnabledRef.current = result.value.conversation.ai_enabled;
6010
+ setHumanRequested(result.value.conversation.needs_human_review ?? false);
6011
+ setSeenAt(result.value.conversation.team_read_at ?? null);
6012
+ setMayHaveOlder(result.value.messages.length >= 50);
6013
+ }, []);
5935
6014
  const connectWebSocket = React14.useCallback(() => {
5936
6015
  const c = clientRef.current;
5937
6016
  if (!c || typeof WebSocket === "undefined") {
@@ -5953,12 +6032,21 @@ function useChat(options = {}) {
5953
6032
  const envelope = JSON.parse(event.data);
5954
6033
  if (envelope.type === "message.created" && envelope.data) {
5955
6034
  appendMessages([envelope.data]);
6035
+ } else if (envelope.type === "thread.read") {
6036
+ const at = envelope.data?.last_read_at;
6037
+ if (at) setSeenAt((prev) => !prev || prev < at ? at : prev);
6038
+ } else if (envelope.type === "thread.responding") {
6039
+ const active = envelope.data?.active;
6040
+ if (active) startAwaitingReply();
6041
+ else stopAwaitingReply();
5956
6042
  } else if (envelope.type === "resync") {
5957
6043
  const latest = envelope.latest_message_at;
5958
6044
  const last = messagesRef.current[messagesRef.current.length - 1];
5959
6045
  if (latest && (!last || new Date(last.created_at).getTime() < new Date(latest).getTime())) {
5960
6046
  fetchNewMessages();
5961
6047
  }
6048
+ } else if (envelope.type === "rekey") {
6049
+ void resyncConversation();
5962
6050
  }
5963
6051
  } catch {
5964
6052
  }
@@ -5978,11 +6066,57 @@ function useChat(options = {}) {
5978
6066
  ws.onerror = () => {
5979
6067
  };
5980
6068
  wsRef.current = ws;
5981
- }, [appendMessages, startPolling, stopPolling, fetchNewMessages]);
6069
+ }, [appendMessages, startPolling, stopPolling, fetchNewMessages, resyncConversation]);
6070
+ const resetConversation = React14.useCallback(() => {
6071
+ stopPolling();
6072
+ if (wsReconnectTimerRef.current) {
6073
+ clearTimeout(wsReconnectTimerRef.current);
6074
+ wsReconnectTimerRef.current = null;
6075
+ }
6076
+ if (wsRef.current) {
6077
+ wsRef.current.onclose = null;
6078
+ wsRef.current.close();
6079
+ wsRef.current = null;
6080
+ }
6081
+ wsReconnectAttemptRef.current = 0;
6082
+ openedRef.current = false;
6083
+ stopAwaitingReply();
6084
+ setMessages([]);
6085
+ setConversationOpened(false);
6086
+ setHumanRequested(false);
6087
+ setSeenAt(null);
6088
+ setMayHaveOlder(false);
6089
+ setError(null);
6090
+ setClaimedAs(null);
6091
+ }, [stopPolling, stopAwaitingReply]);
6092
+ const watchSessionClaims = React14.useCallback(() => {
6093
+ const c = clientRef.current;
6094
+ if (!c || claimUnsubRef.current) return;
6095
+ const unsubClaimed = c.events.on(chunkDCRN67NO_js.SESSION_CLAIMED, (event) => {
6096
+ setClaimedAs(event.customerName || null);
6097
+ if (!openedRef.current) return;
6098
+ connectWebSocket();
6099
+ void resyncConversation();
6100
+ });
6101
+ const unsubCleared = c.events.on(chunkDCRN67NO_js.SESSION_CLEARED, resetConversation);
6102
+ claimUnsubRef.current = () => {
6103
+ unsubClaimed();
6104
+ unsubCleared();
6105
+ };
6106
+ }, [connectWebSocket, resyncConversation, resetConversation]);
6107
+ const signOut = React14.useCallback(async () => {
6108
+ const c = clientRef.current;
6109
+ if (!c) return false;
6110
+ const result = await c.link.logout();
6111
+ return result.ok;
6112
+ }, []);
5982
6113
  React14.useEffect(() => {
5983
6114
  return () => {
5984
6115
  if (pollRef.current) clearInterval(pollRef.current);
5985
6116
  if (wsReconnectTimerRef.current) clearTimeout(wsReconnectTimerRef.current);
6117
+ if (awaitReplyTimerRef.current) clearTimeout(awaitReplyTimerRef.current);
6118
+ claimUnsubRef.current?.();
6119
+ claimUnsubRef.current = null;
5986
6120
  if (wsRef.current) {
5987
6121
  wsRef.current.onclose = null;
5988
6122
  wsRef.current.close();
@@ -5998,35 +6132,53 @@ function useChat(options = {}) {
5998
6132
  if (result.ok) {
5999
6133
  setMessages(result.value.messages);
6000
6134
  setConversationOpened(true);
6135
+ aiEnabledRef.current = result.value.conversation.ai_enabled;
6136
+ setHumanRequested(result.value.conversation.needs_human_review ?? false);
6137
+ setSeenAt(result.value.conversation.team_read_at ?? null);
6138
+ setMayHaveOlder(result.value.messages.length >= 50);
6139
+ setClaimedAs(client.support.claimedCustomerName);
6001
6140
  connectWebSocket();
6141
+ watchSessionClaims();
6002
6142
  } else {
6003
6143
  openedRef.current = false;
6004
6144
  setError(result.error);
6005
6145
  }
6006
6146
  setIsLoading(false);
6007
- }, [client, connectWebSocket]);
6147
+ }, [client, connectWebSocket, watchSessionClaims]);
6008
6148
  const send = React14.useCallback(
6009
- async (content) => {
6010
- if (!client || !content.trim()) return;
6149
+ async (content, opts) => {
6150
+ const attachments = opts?.attachments ?? [];
6151
+ if (!client || !content.trim() && attachments.length === 0) return false;
6011
6152
  setError(null);
6012
6153
  lastActivityRef.current = Date.now();
6013
6154
  if (!openedRef.current) {
6014
6155
  await openConversation();
6015
- if (!openedRef.current) return;
6156
+ if (!openedRef.current) return false;
6016
6157
  }
6158
+ const replyTo = opts?.replyTo;
6017
6159
  const optimisticId = `opt_${Date.now()}`;
6018
6160
  const optimistic = {
6019
6161
  id: optimisticId,
6020
6162
  sender_type: "customer",
6021
6163
  content: content.trim(),
6022
- content_type: "text",
6023
- attachments: [],
6164
+ content_type: attachments.length > 0 ? "image" : "text",
6165
+ attachments,
6024
6166
  metadata: {},
6167
+ // Same shape the server echoes back, so the bubble doesn't reflow
6168
+ // when the confirmed message replaces it.
6169
+ reply_to: replyTo ? {
6170
+ id: replyTo.id,
6171
+ sender_type: replyTo.sender_type,
6172
+ preview: chunkXREIZ56K_js.messagePreview(replyTo.content)
6173
+ } : void 0,
6025
6174
  created_at: (/* @__PURE__ */ new Date()).toISOString()
6026
6175
  };
6027
6176
  setMessages((prev) => [...prev, optimistic]);
6028
6177
  setIsSending(true);
6029
- const result = await client.support.sendMessage(content.trim());
6178
+ const result = await client.support.sendMessage(content.trim(), {
6179
+ replyToMessageId: replyTo?.id,
6180
+ attachments: attachments.length > 0 ? attachments : void 0
6181
+ });
6030
6182
  if (result.ok) {
6031
6183
  const confirmed = result.value;
6032
6184
  setMessages(
@@ -6037,13 +6189,17 @@ function useChat(options = {}) {
6037
6189
  prev.some((m) => m.id === confirmed.id) ? prev.filter((m) => m.id !== optimisticId) : prev.map((m) => m.id === optimisticId ? confirmed : m)
6038
6190
  )
6039
6191
  );
6192
+ if (aiEnabledRef.current && !humanRequestedRef.current) {
6193
+ startAwaitingReply();
6194
+ }
6040
6195
  } else {
6041
6196
  setMessages((prev) => prev.filter((m) => m.id !== optimisticId));
6042
6197
  setError(result.error);
6043
6198
  }
6044
6199
  setIsSending(false);
6200
+ return result.ok;
6045
6201
  },
6046
- [client, openConversation]
6202
+ [client, openConversation, startAwaitingReply]
6047
6203
  );
6048
6204
  const startConversation = React14.useCallback(
6049
6205
  async (starterText) => {
@@ -6054,14 +6210,67 @@ function useChat(options = {}) {
6054
6210
  },
6055
6211
  [openConversation, send]
6056
6212
  );
6213
+ const react = React14.useCallback(
6214
+ async (message, emoji) => {
6215
+ if (!client || message.id.startsWith("opt_")) return false;
6216
+ const result = await client.support.reactToMessage(message.id, emoji);
6217
+ if (result.ok) appendMessages([result.value]);
6218
+ return result.ok;
6219
+ },
6220
+ [client, appendMessages]
6221
+ );
6222
+ const requestHuman = React14.useCallback(async () => {
6223
+ if (!client) return false;
6224
+ setError(null);
6225
+ if (!openedRef.current) {
6226
+ await openConversation();
6227
+ if (!openedRef.current) return false;
6228
+ }
6229
+ const result = await client.support.requestHuman();
6230
+ if (result.ok) {
6231
+ setHumanRequested(true);
6232
+ return true;
6233
+ }
6234
+ setError(result.error);
6235
+ return false;
6236
+ }, [client, openConversation]);
6237
+ const loadOlder = React14.useCallback(async () => {
6238
+ const c = clientRef.current;
6239
+ const earliest = messagesRef.current[0];
6240
+ if (!c || !earliest) return false;
6241
+ const result = await c.support.getMessages({ before: earliest.created_at, limit: 50 });
6242
+ if (!result.ok) {
6243
+ setError(result.error);
6244
+ return false;
6245
+ }
6246
+ const older = result.value;
6247
+ setMayHaveOlder(older.length >= 50);
6248
+ if (older.length === 0) return false;
6249
+ setMessages((prev) => {
6250
+ const ids = new Set(prev.map((m) => m.id));
6251
+ const fresh = older.filter((m) => !ids.has(m.id));
6252
+ return fresh.length > 0 ? [...fresh, ...prev] : prev;
6253
+ });
6254
+ return true;
6255
+ }, []);
6256
+ const agentArrived = messages.some((m) => m.sender_type === "agent");
6057
6257
  return {
6058
6258
  messages,
6059
6259
  isLoading,
6060
6260
  isSending,
6061
6261
  error,
6062
6262
  isActive: conversationOpened && messages.length > 0,
6263
+ humanRequested: humanRequested && !agentArrived,
6264
+ awaitingReply,
6265
+ seenAt,
6266
+ mayHaveOlder,
6267
+ claimedAs,
6268
+ signOut,
6063
6269
  send,
6064
- startConversation
6270
+ startConversation,
6271
+ react,
6272
+ requestHuman,
6273
+ loadOlder
6065
6274
  };
6066
6275
  }
6067
6276
  var ElementsContext = React14.createContext({
@@ -6136,15 +6345,15 @@ function AuthElement({
6136
6345
  }, [onReady, onAuthenticated, onError]);
6137
6346
  React14.useEffect(() => {
6138
6347
  if (!elements || !containerRef.current) return;
6139
- const element = elements.create(chunkGHFDYM5K_js.ELEMENT_TYPES.AUTH, { prefillEmail });
6348
+ const element = elements.create(chunkIVJEUGWF_js.ELEMENT_TYPES.AUTH, { prefillEmail });
6140
6349
  elementRef.current = element;
6141
- element.on(chunkGHFDYM5K_js.EVENT_TYPES.READY, () => onReadyRef.current?.());
6350
+ element.on(chunkIVJEUGWF_js.EVENT_TYPES.READY, () => onReadyRef.current?.());
6142
6351
  element.on(
6143
- chunkGHFDYM5K_js.EVENT_TYPES.AUTHENTICATED,
6352
+ chunkIVJEUGWF_js.EVENT_TYPES.AUTHENTICATED,
6144
6353
  (data) => onAuthenticatedRef.current?.(data)
6145
6354
  );
6146
6355
  element.on(
6147
- chunkGHFDYM5K_js.EVENT_TYPES.ERROR,
6356
+ chunkIVJEUGWF_js.EVENT_TYPES.ERROR,
6148
6357
  (data) => onErrorRef.current?.(data)
6149
6358
  );
6150
6359
  element.mount(containerRef.current);
@@ -6173,15 +6382,15 @@ function AddressElement({
6173
6382
  }, [onReady, onChange, onError]);
6174
6383
  React14.useEffect(() => {
6175
6384
  if (!elements || !containerRef.current) return;
6176
- const element = elements.create(chunkGHFDYM5K_js.ELEMENT_TYPES.ADDRESS, { mode });
6385
+ const element = elements.create(chunkIVJEUGWF_js.ELEMENT_TYPES.ADDRESS, { mode });
6177
6386
  elementRef.current = element;
6178
- element.on(chunkGHFDYM5K_js.EVENT_TYPES.READY, () => onReadyRef.current?.());
6387
+ element.on(chunkIVJEUGWF_js.EVENT_TYPES.READY, () => onReadyRef.current?.());
6179
6388
  element.on(
6180
- chunkGHFDYM5K_js.EVENT_TYPES.CHANGE,
6389
+ chunkIVJEUGWF_js.EVENT_TYPES.CHANGE,
6181
6390
  (data) => onChangeRef.current?.(data)
6182
6391
  );
6183
6392
  element.on(
6184
- chunkGHFDYM5K_js.EVENT_TYPES.ERROR,
6393
+ chunkIVJEUGWF_js.EVENT_TYPES.ERROR,
6185
6394
  (data) => onErrorRef.current?.(data)
6186
6395
  );
6187
6396
  element.mount(containerRef.current);
@@ -6211,15 +6420,15 @@ function PaymentElement({
6211
6420
  }, [onReady, onChange, onError]);
6212
6421
  React14.useEffect(() => {
6213
6422
  if (!elements || !containerRef.current) return;
6214
- const element = elements.create(chunkGHFDYM5K_js.ELEMENT_TYPES.PAYMENT, { amount, currency });
6423
+ const element = elements.create(chunkIVJEUGWF_js.ELEMENT_TYPES.PAYMENT, { amount, currency });
6215
6424
  elementRef.current = element;
6216
- element.on(chunkGHFDYM5K_js.EVENT_TYPES.READY, () => onReadyRef.current?.());
6425
+ element.on(chunkIVJEUGWF_js.EVENT_TYPES.READY, () => onReadyRef.current?.());
6217
6426
  element.on(
6218
- chunkGHFDYM5K_js.EVENT_TYPES.CHANGE,
6427
+ chunkIVJEUGWF_js.EVENT_TYPES.CHANGE,
6219
6428
  (data) => onChangeRef.current?.(data)
6220
6429
  );
6221
6430
  element.on(
6222
- chunkGHFDYM5K_js.EVENT_TYPES.ERROR,
6431
+ chunkIVJEUGWF_js.EVENT_TYPES.ERROR,
6223
6432
  (data) => onErrorRef.current?.(data)
6224
6433
  );
6225
6434
  element.mount(containerRef.current);
@@ -6510,7 +6719,7 @@ function VariantSelector({
6510
6719
  {
6511
6720
  "data-cimplify-variant-name": true,
6512
6721
  className: cn("flex-1 min-w-0 text-sm", classNames?.name),
6513
- children: chunkT7Z25EY4_js.getVariantDisplayName(variant, productName)
6722
+ children: chunkHKN3YG76_js.getVariantDisplayName(variant, productName)
6514
6723
  }
6515
6724
  ),
6516
6725
  /* @__PURE__ */ jsxRuntime.jsxs("span", { "data-cimplify-variant-pricing": true, className: cn("text-sm text-muted-foreground", classNames?.pricing), children: [
@@ -7791,6 +8000,177 @@ function VolumePricing({
7791
8000
  }
7792
8001
  );
7793
8002
  }
8003
+ function createPlacesSessionToken() {
8004
+ if (typeof crypto !== "undefined" && crypto.randomUUID) {
8005
+ return crypto.randomUUID();
8006
+ }
8007
+ return `places_${Date.now()}_${Math.random().toString(16).slice(2)}`;
8008
+ }
8009
+ var MIN_QUERY_LENGTH = 3;
8010
+ var DEBOUNCE_MS = 300;
8011
+ var BLUR_CLOSE_MS = 120;
8012
+ function AddressAutocomplete({
8013
+ value,
8014
+ onSelect,
8015
+ onDirty,
8016
+ placeholder,
8017
+ required,
8018
+ autoFocus,
8019
+ id,
8020
+ className,
8021
+ classNames
8022
+ }) {
8023
+ const clientContext = useOptionalCimplifyClient();
8024
+ const [inputText, setInputText] = React14__default.default.useState(value?.formatted_address ?? "");
8025
+ const [suggestions, setSuggestions] = React14__default.default.useState([]);
8026
+ const [isOpen, setIsOpen] = React14__default.default.useState(false);
8027
+ const [highlightedIndex, setHighlightedIndex] = React14__default.default.useState(-1);
8028
+ const debounceRef = React14__default.default.useRef(null);
8029
+ const seqRef = React14__default.default.useRef(0);
8030
+ const sessionTokenRef = React14__default.default.useRef(createPlacesSessionToken());
8031
+ const blurTimeoutRef = React14__default.default.useRef(null);
8032
+ React14__default.default.useEffect(() => {
8033
+ return () => {
8034
+ if (debounceRef.current) clearTimeout(debounceRef.current);
8035
+ if (blurTimeoutRef.current) clearTimeout(blurTimeoutRef.current);
8036
+ seqRef.current += 1;
8037
+ };
8038
+ }, []);
8039
+ const fetchSuggestions = React14__default.default.useCallback(
8040
+ async (query) => {
8041
+ const client = clientContext?.client;
8042
+ if (!client || query.length < MIN_QUERY_LENGTH) {
8043
+ setSuggestions([]);
8044
+ setIsOpen(false);
8045
+ return;
8046
+ }
8047
+ const seq = ++seqRef.current;
8048
+ const result = await client.places.autocomplete(query, sessionTokenRef.current);
8049
+ if (seqRef.current !== seq) return;
8050
+ if (result.ok && result.value.predictions.length > 0) {
8051
+ setSuggestions(result.value.predictions);
8052
+ setHighlightedIndex(0);
8053
+ setIsOpen(true);
8054
+ } else {
8055
+ setSuggestions([]);
8056
+ setIsOpen(false);
8057
+ }
8058
+ },
8059
+ [clientContext]
8060
+ );
8061
+ const handleInputChange = (nextValue) => {
8062
+ setInputText(nextValue);
8063
+ onDirty?.();
8064
+ if (debounceRef.current) clearTimeout(debounceRef.current);
8065
+ debounceRef.current = setTimeout(() => void fetchSuggestions(nextValue.trim()), DEBOUNCE_MS);
8066
+ };
8067
+ const selectSuggestion = React14__default.default.useCallback(
8068
+ async (suggestion) => {
8069
+ if (debounceRef.current) clearTimeout(debounceRef.current);
8070
+ if (blurTimeoutRef.current) clearTimeout(blurTimeoutRef.current);
8071
+ const fallback = suggestion.description.split(",")[0]?.trim() || suggestion.description;
8072
+ setInputText(fallback);
8073
+ setSuggestions([]);
8074
+ setIsOpen(false);
8075
+ const client = clientContext?.client;
8076
+ if (!client) return;
8077
+ const result = await client.places.details(suggestion.place_id, sessionTokenRef.current);
8078
+ sessionTokenRef.current = createPlacesSessionToken();
8079
+ if (result.ok) {
8080
+ const d = result.value;
8081
+ const address = {
8082
+ formatted_address: d.formatted_address || fallback,
8083
+ street_address: d.street_address,
8084
+ apartment: d.apartment,
8085
+ city: d.city,
8086
+ region: d.region,
8087
+ postal_code: d.postal_code,
8088
+ country: d.country,
8089
+ latitude: d.latitude,
8090
+ longitude: d.longitude,
8091
+ place_id: d.place_id
8092
+ };
8093
+ setInputText(address.formatted_address);
8094
+ onSelect(address);
8095
+ }
8096
+ },
8097
+ [clientContext, onSelect]
8098
+ );
8099
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { "data-cimplify-address-autocomplete": true, className: cn("relative", className, classNames?.root), children: [
8100
+ /* @__PURE__ */ jsxRuntime.jsx(
8101
+ "input",
8102
+ {
8103
+ id,
8104
+ type: "text",
8105
+ value: inputText,
8106
+ onChange: (e) => handleInputChange(e.target.value),
8107
+ onFocus: () => {
8108
+ if (suggestions.length > 0) setIsOpen(true);
8109
+ },
8110
+ onBlur: () => {
8111
+ blurTimeoutRef.current = setTimeout(() => setIsOpen(false), BLUR_CLOSE_MS);
8112
+ },
8113
+ onKeyDown: (e) => {
8114
+ if (e.key === "Escape") {
8115
+ setIsOpen(false);
8116
+ return;
8117
+ }
8118
+ if (!isOpen || suggestions.length === 0) return;
8119
+ if (e.key === "ArrowDown") {
8120
+ e.preventDefault();
8121
+ setHighlightedIndex((i) => i < suggestions.length - 1 ? i + 1 : 0);
8122
+ } else if (e.key === "ArrowUp") {
8123
+ e.preventDefault();
8124
+ setHighlightedIndex((i) => i > 0 ? i - 1 : suggestions.length - 1);
8125
+ } else if (e.key === "Enter" && highlightedIndex >= 0) {
8126
+ e.preventDefault();
8127
+ void selectSuggestion(suggestions[highlightedIndex]);
8128
+ }
8129
+ },
8130
+ placeholder: placeholder ?? "Search for an address...",
8131
+ required,
8132
+ autoFocus,
8133
+ autoComplete: "off",
8134
+ "aria-autocomplete": "list",
8135
+ "aria-expanded": isOpen && suggestions.length > 0,
8136
+ "data-cimplify-address-input": true,
8137
+ className: classNames?.input
8138
+ }
8139
+ ),
8140
+ isOpen && suggestions.length > 0 && /* @__PURE__ */ jsxRuntime.jsx(
8141
+ "div",
8142
+ {
8143
+ role: "listbox",
8144
+ "data-cimplify-address-list": true,
8145
+ className: cn(
8146
+ "absolute left-0 right-0 top-full z-20 mt-1 overflow-hidden rounded-md border border-border bg-background shadow-lg",
8147
+ classNames?.list
8148
+ ),
8149
+ children: suggestions.map((s, i) => /* @__PURE__ */ jsxRuntime.jsx(
8150
+ "button",
8151
+ {
8152
+ type: "button",
8153
+ role: "option",
8154
+ "aria-selected": i === highlightedIndex,
8155
+ onMouseDown: (e) => {
8156
+ e.preventDefault();
8157
+ void selectSuggestion(s);
8158
+ },
8159
+ "data-cimplify-address-option": true,
8160
+ className: cn(
8161
+ "block w-full px-3 py-2.5 text-left text-sm transition-colors",
8162
+ i === highlightedIndex ? "bg-muted text-foreground" : "text-foreground hover:bg-muted/70",
8163
+ classNames?.option,
8164
+ i === highlightedIndex && classNames?.activeOption
8165
+ ),
8166
+ children: s.description
8167
+ },
8168
+ s.place_id
8169
+ ))
8170
+ }
8171
+ )
8172
+ ] });
8173
+ }
7794
8174
  function parseValue(value) {
7795
8175
  if (!value) return void 0;
7796
8176
  const date = dateFns.parseISO(value);
@@ -8132,12 +8512,12 @@ function FieldInput({
8132
8512
  classNames?.input
8133
8513
  );
8134
8514
  switch (field.field_type) {
8135
- case chunkT7Z25EY4_js.INPUT_FIELD_TYPE.Text:
8136
- case chunkT7Z25EY4_js.INPUT_FIELD_TYPE.Url:
8515
+ case chunkHKN3YG76_js.INPUT_FIELD_TYPE.Text:
8516
+ case chunkHKN3YG76_js.INPUT_FIELD_TYPE.Url:
8137
8517
  return /* @__PURE__ */ jsxRuntime.jsx(
8138
8518
  "input",
8139
8519
  {
8140
- type: field.field_type === chunkT7Z25EY4_js.INPUT_FIELD_TYPE.Url ? "url" : "text",
8520
+ type: field.field_type === chunkHKN3YG76_js.INPUT_FIELD_TYPE.Url ? "url" : "text",
8141
8521
  value: typeof value === "string" ? value : "",
8142
8522
  onChange: (e) => onValueChange(e.target.value),
8143
8523
  placeholder: field.placeholder,
@@ -8146,7 +8526,7 @@ function FieldInput({
8146
8526
  className: inputClass
8147
8527
  }
8148
8528
  );
8149
- case chunkT7Z25EY4_js.INPUT_FIELD_TYPE.Textarea:
8529
+ case chunkHKN3YG76_js.INPUT_FIELD_TYPE.Textarea:
8150
8530
  return /* @__PURE__ */ jsxRuntime.jsx(
8151
8531
  "textarea",
8152
8532
  {
@@ -8159,7 +8539,7 @@ function FieldInput({
8159
8539
  className: cn(inputClass, "resize-none", classNames?.textarea)
8160
8540
  }
8161
8541
  );
8162
- case chunkT7Z25EY4_js.INPUT_FIELD_TYPE.Number:
8542
+ case chunkHKN3YG76_js.INPUT_FIELD_TYPE.Number:
8163
8543
  return /* @__PURE__ */ jsxRuntime.jsx(
8164
8544
  "input",
8165
8545
  {
@@ -8173,7 +8553,7 @@ function FieldInput({
8173
8553
  className: inputClass
8174
8554
  }
8175
8555
  );
8176
- case chunkT7Z25EY4_js.INPUT_FIELD_TYPE.Select:
8556
+ case chunkHKN3YG76_js.INPUT_FIELD_TYPE.Select:
8177
8557
  return /* @__PURE__ */ jsxRuntime.jsxs(
8178
8558
  "select",
8179
8559
  {
@@ -8187,7 +8567,7 @@ function FieldInput({
8187
8567
  ]
8188
8568
  }
8189
8569
  );
8190
- case chunkT7Z25EY4_js.INPUT_FIELD_TYPE.Radio:
8570
+ case chunkHKN3YG76_js.INPUT_FIELD_TYPE.Radio:
8191
8571
  return /* @__PURE__ */ jsxRuntime.jsx("div", { className: cn("space-y-2", classNames?.radioGroup), children: (field.options || []).map((opt) => /* @__PURE__ */ jsxRuntime.jsxs(
8192
8572
  "label",
8193
8573
  {
@@ -8212,7 +8592,7 @@ function FieldInput({
8212
8592
  },
8213
8593
  opt
8214
8594
  )) });
8215
- case chunkT7Z25EY4_js.INPUT_FIELD_TYPE.Checkbox:
8595
+ case chunkHKN3YG76_js.INPUT_FIELD_TYPE.Checkbox:
8216
8596
  return /* @__PURE__ */ jsxRuntime.jsxs("label", { className: cn("flex items-center gap-2 text-sm cursor-pointer", classNames?.checkboxLabel), children: [
8217
8597
  /* @__PURE__ */ jsxRuntime.jsx(
8218
8598
  "input",
@@ -8225,7 +8605,7 @@ function FieldInput({
8225
8605
  ),
8226
8606
  field.placeholder || field.name
8227
8607
  ] });
8228
- case chunkT7Z25EY4_js.INPUT_FIELD_TYPE.Color:
8608
+ case chunkHKN3YG76_js.INPUT_FIELD_TYPE.Color:
8229
8609
  return /* @__PURE__ */ jsxRuntime.jsx(
8230
8610
  "input",
8231
8611
  {
@@ -8235,7 +8615,7 @@ function FieldInput({
8235
8615
  className: cn("w-12 h-10 rounded-md border border-input cursor-pointer", classNames?.colorInput)
8236
8616
  }
8237
8617
  );
8238
- case chunkT7Z25EY4_js.INPUT_FIELD_TYPE.Date:
8618
+ case chunkHKN3YG76_js.INPUT_FIELD_TYPE.Date:
8239
8619
  return /* @__PURE__ */ jsxRuntime.jsx(
8240
8620
  DatePicker,
8241
8621
  {
@@ -8246,8 +8626,8 @@ function FieldInput({
8246
8626
  "aria-label": field.name
8247
8627
  }
8248
8628
  );
8249
- case chunkT7Z25EY4_js.INPUT_FIELD_TYPE.File:
8250
- case chunkT7Z25EY4_js.INPUT_FIELD_TYPE.Image:
8629
+ case chunkHKN3YG76_js.INPUT_FIELD_TYPE.File:
8630
+ case chunkHKN3YG76_js.INPUT_FIELD_TYPE.Image:
8251
8631
  return /* @__PURE__ */ jsxRuntime.jsx(
8252
8632
  FileUploadInput,
8253
8633
  {
@@ -8258,7 +8638,7 @@ function FieldInput({
8258
8638
  classNames
8259
8639
  }
8260
8640
  );
8261
- case chunkT7Z25EY4_js.INPUT_FIELD_TYPE.Email:
8641
+ case chunkHKN3YG76_js.INPUT_FIELD_TYPE.Email:
8262
8642
  return /* @__PURE__ */ jsxRuntime.jsx(
8263
8643
  "input",
8264
8644
  {
@@ -8270,7 +8650,7 @@ function FieldInput({
8270
8650
  className: inputClass
8271
8651
  }
8272
8652
  );
8273
- case chunkT7Z25EY4_js.INPUT_FIELD_TYPE.DateTime: {
8653
+ case chunkHKN3YG76_js.INPUT_FIELD_TYPE.DateTime: {
8274
8654
  const stringValue = typeof value === "string" ? value : "";
8275
8655
  const [datePart, timePartRaw] = stringValue.includes("T") ? stringValue.split("T", 2) : [stringValue, ""];
8276
8656
  const timePart = (timePartRaw ?? "").slice(0, 5);
@@ -8309,7 +8689,7 @@ function FieldInput({
8309
8689
  )
8310
8690
  ] });
8311
8691
  }
8312
- case chunkT7Z25EY4_js.INPUT_FIELD_TYPE.Time:
8692
+ case chunkHKN3YG76_js.INPUT_FIELD_TYPE.Time:
8313
8693
  return /* @__PURE__ */ jsxRuntime.jsx(
8314
8694
  TimePicker,
8315
8695
  {
@@ -8320,7 +8700,7 @@ function FieldInput({
8320
8700
  "aria-label": field.name
8321
8701
  }
8322
8702
  );
8323
- case chunkT7Z25EY4_js.INPUT_FIELD_TYPE.MultiSelect:
8703
+ case chunkHKN3YG76_js.INPUT_FIELD_TYPE.MultiSelect:
8324
8704
  return /* @__PURE__ */ jsxRuntime.jsx(
8325
8705
  MultiSelectInput,
8326
8706
  {
@@ -8330,7 +8710,7 @@ function FieldInput({
8330
8710
  classNames
8331
8711
  }
8332
8712
  );
8333
- case chunkT7Z25EY4_js.INPUT_FIELD_TYPE.DateRange:
8713
+ case chunkHKN3YG76_js.INPUT_FIELD_TYPE.DateRange:
8334
8714
  return /* @__PURE__ */ jsxRuntime.jsx(
8335
8715
  DateRangeInput,
8336
8716
  {
@@ -8340,7 +8720,7 @@ function FieldInput({
8340
8720
  classNames
8341
8721
  }
8342
8722
  );
8343
- case chunkT7Z25EY4_js.INPUT_FIELD_TYPE.Address:
8723
+ case chunkHKN3YG76_js.INPUT_FIELD_TYPE.Address:
8344
8724
  return /* @__PURE__ */ jsxRuntime.jsx(
8345
8725
  AddressInput,
8346
8726
  {
@@ -8351,7 +8731,7 @@ function FieldInput({
8351
8731
  inputClass
8352
8732
  }
8353
8733
  );
8354
- case chunkT7Z25EY4_js.INPUT_FIELD_TYPE.Location:
8734
+ case chunkHKN3YG76_js.INPUT_FIELD_TYPE.Location:
8355
8735
  return /* @__PURE__ */ jsxRuntime.jsx(
8356
8736
  LocationInput,
8357
8737
  {
@@ -8362,7 +8742,7 @@ function FieldInput({
8362
8742
  inputClass
8363
8743
  }
8364
8744
  );
8365
- case chunkT7Z25EY4_js.INPUT_FIELD_TYPE.Phone:
8745
+ case chunkHKN3YG76_js.INPUT_FIELD_TYPE.Phone:
8366
8746
  return /* @__PURE__ */ jsxRuntime.jsx(
8367
8747
  PhoneInput,
8368
8748
  {
@@ -8373,7 +8753,7 @@ function FieldInput({
8373
8753
  inputClass
8374
8754
  }
8375
8755
  );
8376
- case chunkT7Z25EY4_js.INPUT_FIELD_TYPE.Signature:
8756
+ case chunkHKN3YG76_js.INPUT_FIELD_TYPE.Signature:
8377
8757
  return /* @__PURE__ */ jsxRuntime.jsx(
8378
8758
  SignatureInput,
8379
8759
  {
@@ -8406,7 +8786,7 @@ function FileUploadInput({
8406
8786
  const [isUploading, setIsUploading] = React14__default.default.useState(false);
8407
8787
  const fileUrl = typeof value === "string" ? value : void 0;
8408
8788
  const acceptedFormats = field.validation?.accepted_formats;
8409
- const accept = acceptedFormats ? acceptedFormats.map((f) => `.${f}`).join(",") : field.field_type === chunkT7Z25EY4_js.INPUT_FIELD_TYPE.Image ? "image/*" : void 0;
8789
+ const accept = acceptedFormats ? acceptedFormats.map((f) => `.${f}`).join(",") : field.field_type === chunkHKN3YG76_js.INPUT_FIELD_TYPE.Image ? "image/*" : void 0;
8410
8790
  const handleFile = async (file) => {
8411
8791
  if (!onFileUpload) return;
8412
8792
  if (field.validation?.max_size_mb) {
@@ -8424,7 +8804,7 @@ function FileUploadInput({
8424
8804
  }
8425
8805
  };
8426
8806
  return /* @__PURE__ */ jsxRuntime.jsx("div", { className: cn("space-y-2", classNames?.fileInput), children: fileUrl ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-3", children: [
8427
- field.field_type === chunkT7Z25EY4_js.INPUT_FIELD_TYPE.Image && /* @__PURE__ */ jsxRuntime.jsx("img", { src: fileUrl, alt: "Uploaded", className: "w-16 h-16 object-cover rounded-md border border-border" }),
8807
+ field.field_type === chunkHKN3YG76_js.INPUT_FIELD_TYPE.Image && /* @__PURE__ */ jsxRuntime.jsx("img", { src: fileUrl, alt: "Uploaded", className: "w-16 h-16 object-cover rounded-md border border-border" }),
8428
8808
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex-1 min-w-0", children: /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm text-foreground truncate", children: fileUrl.split("/").pop() }) }),
8429
8809
  /* @__PURE__ */ jsxRuntime.jsx(
8430
8810
  "button",
@@ -8450,7 +8830,7 @@ function FileUploadInput({
8450
8830
  }
8451
8831
  ),
8452
8832
  isUploading ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-sm text-muted-foreground", children: "Uploading..." }) : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
8453
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-sm text-muted-foreground", children: field.placeholder || `Upload ${field.field_type === chunkT7Z25EY4_js.INPUT_FIELD_TYPE.Image ? "image" : "file"}` }),
8833
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-sm text-muted-foreground", children: field.placeholder || `Upload ${field.field_type === chunkHKN3YG76_js.INPUT_FIELD_TYPE.Image ? "image" : "file"}` }),
8454
8834
  acceptedFormats && /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "text-xs text-muted-foreground/60 mt-1", children: [
8455
8835
  acceptedFormats.map((f) => f.toUpperCase()).join(", "),
8456
8836
  field.validation?.max_size_mb && ` \xB7 Max ${field.validation.max_size_mb}MB`
@@ -8551,129 +8931,18 @@ function AddressInput({
8551
8931
  classNames,
8552
8932
  inputClass
8553
8933
  }) {
8554
- const clientContext = useOptionalCimplifyClient();
8555
8934
  const addr = value && typeof value === "object" ? value : null;
8556
- const [inputText, setInputText] = React14__default.default.useState(addr?.formatted_address ?? "");
8557
- const [suggestions, setSuggestions] = React14__default.default.useState([]);
8558
- const [isOpen, setIsOpen] = React14__default.default.useState(false);
8559
- const [highlightedIndex, setHighlightedIndex] = React14__default.default.useState(-1);
8560
- const debounceRef = React14__default.default.useRef(null);
8561
- const seqRef = React14__default.default.useRef(0);
8562
- const sessionTokenRef = React14__default.default.useRef(createSessionToken());
8563
- const blurTimeoutRef = React14__default.default.useRef(null);
8564
- React14__default.default.useEffect(() => {
8565
- return () => {
8566
- if (debounceRef.current) clearTimeout(debounceRef.current);
8567
- if (blurTimeoutRef.current) clearTimeout(blurTimeoutRef.current);
8568
- seqRef.current += 1;
8569
- };
8570
- }, []);
8571
- const fetchSuggestions = React14__default.default.useCallback(async (query) => {
8572
- const client = clientContext?.client;
8573
- if (!client || query.length < 3) {
8574
- setSuggestions([]);
8575
- setIsOpen(false);
8576
- return;
8577
- }
8578
- const seq = ++seqRef.current;
8579
- const result = await client.places.autocomplete(query, sessionTokenRef.current);
8580
- if (seqRef.current !== seq) return;
8581
- if (result.ok && result.value.predictions.length > 0) {
8582
- setSuggestions(result.value.predictions);
8583
- setHighlightedIndex(0);
8584
- setIsOpen(true);
8585
- } else {
8586
- setSuggestions([]);
8587
- setIsOpen(false);
8588
- }
8589
- }, [clientContext]);
8590
- const handleInputChange = (nextValue) => {
8591
- setInputText(nextValue);
8592
- if (debounceRef.current) clearTimeout(debounceRef.current);
8593
- debounceRef.current = setTimeout(() => void fetchSuggestions(nextValue.trim()), 300);
8594
- };
8595
- const selectSuggestion = React14__default.default.useCallback(async (suggestion) => {
8596
- if (debounceRef.current) clearTimeout(debounceRef.current);
8597
- if (blurTimeoutRef.current) clearTimeout(blurTimeoutRef.current);
8598
- const fallback = suggestion.description.split(",")[0]?.trim() || suggestion.description;
8599
- setInputText(fallback);
8600
- setSuggestions([]);
8601
- setIsOpen(false);
8602
- const client = clientContext?.client;
8603
- if (!client) return;
8604
- const result = await client.places.details(suggestion.place_id, sessionTokenRef.current);
8605
- sessionTokenRef.current = createSessionToken();
8606
- if (result.ok) {
8607
- const d = result.value;
8608
- const addressValue = {
8609
- formatted_address: d.formatted_address || fallback,
8610
- street_address: d.street_address,
8611
- apartment: d.apartment,
8612
- city: d.city,
8613
- region: d.region,
8614
- postal_code: d.postal_code,
8615
- country: d.country,
8616
- latitude: d.latitude,
8617
- longitude: d.longitude,
8618
- place_id: d.place_id
8619
- };
8620
- setInputText(addressValue.formatted_address);
8621
- onValueChange(addressValue);
8622
- }
8623
- }, [clientContext, onValueChange]);
8624
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: cn("space-y-2 relative", classNames?.addressInput), children: [
8935
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: cn("space-y-2", classNames?.addressInput), children: [
8625
8936
  /* @__PURE__ */ jsxRuntime.jsx(
8626
- "input",
8937
+ AddressAutocomplete,
8627
8938
  {
8628
- type: "text",
8629
- value: inputText,
8630
- onChange: (e) => handleInputChange(e.target.value),
8631
- onFocus: () => {
8632
- if (suggestions.length > 0) setIsOpen(true);
8633
- },
8634
- onBlur: () => {
8635
- blurTimeoutRef.current = setTimeout(() => setIsOpen(false), 120);
8636
- },
8637
- onKeyDown: (e) => {
8638
- if (e.key === "Escape") {
8639
- setIsOpen(false);
8640
- return;
8641
- }
8642
- if (!isOpen || suggestions.length === 0) return;
8643
- if (e.key === "ArrowDown") {
8644
- e.preventDefault();
8645
- setHighlightedIndex((i) => i < suggestions.length - 1 ? i + 1 : 0);
8646
- } else if (e.key === "ArrowUp") {
8647
- e.preventDefault();
8648
- setHighlightedIndex((i) => i > 0 ? i - 1 : suggestions.length - 1);
8649
- } else if (e.key === "Enter" && highlightedIndex >= 0) {
8650
- e.preventDefault();
8651
- void selectSuggestion(suggestions[highlightedIndex]);
8652
- }
8653
- },
8939
+ value: addr,
8940
+ onSelect: onValueChange,
8654
8941
  placeholder: field.placeholder || "Search for an address...",
8655
8942
  required: field.is_required,
8656
- autoComplete: "off",
8657
- "aria-autocomplete": "list",
8658
- "aria-expanded": isOpen && suggestions.length > 0,
8659
- className: inputClass
8943
+ classNames: { input: inputClass }
8660
8944
  }
8661
8945
  ),
8662
- isOpen && suggestions.length > 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { role: "listbox", className: "absolute left-0 right-0 top-full z-20 mt-1 overflow-hidden rounded-md border border-border bg-background shadow-lg", children: suggestions.map((s, i) => /* @__PURE__ */ jsxRuntime.jsx(
8663
- "button",
8664
- {
8665
- type: "button",
8666
- role: "option",
8667
- "aria-selected": i === highlightedIndex,
8668
- onMouseDown: (e) => {
8669
- e.preventDefault();
8670
- void selectSuggestion(s);
8671
- },
8672
- className: cn("block w-full px-3 py-2.5 text-left text-sm transition-colors", i === highlightedIndex ? "bg-muted text-foreground" : "text-foreground hover:bg-muted/70"),
8673
- children: s.description
8674
- },
8675
- s.place_id
8676
- )) }),
8677
8946
  addr && /* @__PURE__ */ jsxRuntime.jsx(
8678
8947
  "input",
8679
8948
  {
@@ -8693,121 +8962,22 @@ function LocationInput({
8693
8962
  classNames,
8694
8963
  inputClass
8695
8964
  }) {
8696
- const clientContext = useOptionalCimplifyClient();
8697
8965
  const loc = value && typeof value === "object" ? value : null;
8698
- const [inputText, setInputText] = React14__default.default.useState(loc?.label ?? "");
8699
- const [suggestions, setSuggestions] = React14__default.default.useState([]);
8700
- const [isOpen, setIsOpen] = React14__default.default.useState(false);
8701
- const [highlightedIndex, setHighlightedIndex] = React14__default.default.useState(-1);
8702
- const debounceRef = React14__default.default.useRef(null);
8703
- const seqRef = React14__default.default.useRef(0);
8704
- const sessionTokenRef = React14__default.default.useRef(createSessionToken());
8705
- const blurTimeoutRef = React14__default.default.useRef(null);
8706
- React14__default.default.useEffect(() => {
8707
- return () => {
8708
- if (debounceRef.current) clearTimeout(debounceRef.current);
8709
- if (blurTimeoutRef.current) clearTimeout(blurTimeoutRef.current);
8710
- seqRef.current += 1;
8711
- };
8712
- }, []);
8713
- const fetchSuggestions = React14__default.default.useCallback(async (query) => {
8714
- const client = clientContext?.client;
8715
- if (!client || query.length < 3) {
8716
- setSuggestions([]);
8717
- setIsOpen(false);
8718
- return;
8719
- }
8720
- const seq = ++seqRef.current;
8721
- const result = await client.places.autocomplete(query, sessionTokenRef.current);
8722
- if (seqRef.current !== seq) return;
8723
- if (result.ok && result.value.predictions.length > 0) {
8724
- setSuggestions(result.value.predictions);
8725
- setHighlightedIndex(0);
8726
- setIsOpen(true);
8727
- } else {
8728
- setSuggestions([]);
8729
- setIsOpen(false);
8730
- }
8731
- }, [clientContext]);
8732
- const handleInputChange = (nextValue) => {
8733
- setInputText(nextValue);
8734
- if (debounceRef.current) clearTimeout(debounceRef.current);
8735
- debounceRef.current = setTimeout(() => void fetchSuggestions(nextValue.trim()), 300);
8736
- };
8737
- const selectSuggestion = React14__default.default.useCallback(async (suggestion) => {
8738
- if (debounceRef.current) clearTimeout(debounceRef.current);
8739
- if (blurTimeoutRef.current) clearTimeout(blurTimeoutRef.current);
8740
- const label = suggestion.description.split(",")[0]?.trim() || suggestion.description;
8741
- setInputText(label);
8742
- setSuggestions([]);
8743
- setIsOpen(false);
8744
- const client = clientContext?.client;
8745
- if (!client) return;
8746
- const result = await client.places.details(suggestion.place_id, sessionTokenRef.current);
8747
- sessionTokenRef.current = createSessionToken();
8748
- if (result.ok) {
8749
- const d = result.value;
8750
- onValueChange({
8751
- latitude: d.latitude,
8752
- longitude: d.longitude,
8753
- label: d.formatted_address || label
8754
- });
8755
- setInputText(d.formatted_address || label);
8756
- }
8757
- }, [clientContext, onValueChange]);
8758
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: cn("relative", classNames?.locationInput), children: [
8966
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: cn(classNames?.locationInput), children: [
8759
8967
  /* @__PURE__ */ jsxRuntime.jsx(
8760
- "input",
8968
+ AddressAutocomplete,
8761
8969
  {
8762
- type: "text",
8763
- value: inputText,
8764
- onChange: (e) => handleInputChange(e.target.value),
8765
- onFocus: () => {
8766
- if (suggestions.length > 0) setIsOpen(true);
8767
- },
8768
- onBlur: () => {
8769
- blurTimeoutRef.current = setTimeout(() => setIsOpen(false), 120);
8770
- },
8771
- onKeyDown: (e) => {
8772
- if (e.key === "Escape") {
8773
- setIsOpen(false);
8774
- return;
8775
- }
8776
- if (!isOpen || suggestions.length === 0) return;
8777
- if (e.key === "ArrowDown") {
8778
- e.preventDefault();
8779
- setHighlightedIndex((i) => i < suggestions.length - 1 ? i + 1 : 0);
8780
- } else if (e.key === "ArrowUp") {
8781
- e.preventDefault();
8782
- setHighlightedIndex((i) => i > 0 ? i - 1 : suggestions.length - 1);
8783
- } else if (e.key === "Enter" && highlightedIndex >= 0) {
8784
- e.preventDefault();
8785
- void selectSuggestion(suggestions[highlightedIndex]);
8786
- }
8787
- },
8970
+ value: loc ? { formatted_address: loc.label ?? "", latitude: loc.latitude, longitude: loc.longitude } : null,
8971
+ onSelect: (address) => onValueChange({
8972
+ latitude: address.latitude,
8973
+ longitude: address.longitude,
8974
+ label: address.formatted_address
8975
+ }),
8788
8976
  placeholder: field.placeholder || "Search for a location...",
8789
8977
  required: field.is_required,
8790
- autoComplete: "off",
8791
- "aria-autocomplete": "list",
8792
- "aria-expanded": isOpen && suggestions.length > 0,
8793
- className: inputClass
8978
+ classNames: { input: inputClass }
8794
8979
  }
8795
8980
  ),
8796
- isOpen && suggestions.length > 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { role: "listbox", className: "absolute left-0 right-0 top-full z-20 mt-1 overflow-hidden rounded-md border border-border bg-background shadow-lg", children: suggestions.map((s, i) => /* @__PURE__ */ jsxRuntime.jsx(
8797
- "button",
8798
- {
8799
- type: "button",
8800
- role: "option",
8801
- "aria-selected": i === highlightedIndex,
8802
- onMouseDown: (e) => {
8803
- e.preventDefault();
8804
- void selectSuggestion(s);
8805
- },
8806
- className: cn("block w-full px-3 py-2.5 text-left text-sm transition-colors", i === highlightedIndex ? "bg-muted text-foreground" : "text-foreground hover:bg-muted/70"),
8807
- children: s.description
8808
- },
8809
- s.place_id
8810
- )) }),
8811
8981
  loc && /* @__PURE__ */ jsxRuntime.jsxs("p", { className: "text-xs text-muted-foreground mt-1", children: [
8812
8982
  loc.latitude.toFixed(4),
8813
8983
  ", ",
@@ -8938,12 +9108,6 @@ function SignatureInput({
8938
9108
  !sig?.data_url && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-muted-foreground", children: field.placeholder || "Draw your signature above" })
8939
9109
  ] });
8940
9110
  }
8941
- function createSessionToken() {
8942
- if (typeof crypto !== "undefined" && crypto.randomUUID) {
8943
- return crypto.randomUUID();
8944
- }
8945
- return `places_${Date.now()}_${Math.random().toString(16).slice(2)}`;
8946
- }
8947
9111
  function getTimeOfDay(timeStr) {
8948
9112
  const hour = parseInt(timeStr.split("T").pop()?.split(":")[0] ?? timeStr.split(":")[0], 10);
8949
9113
  if (hour < 12) return "morning";
@@ -9189,6 +9353,7 @@ function DateSlotPicker({
9189
9353
  serviceId,
9190
9354
  daysToShow = 7,
9191
9355
  participantCount,
9356
+ units,
9192
9357
  selectedSlot,
9193
9358
  onSlotSelect,
9194
9359
  availability: availabilityProp,
@@ -9223,6 +9388,7 @@ function DateSlotPicker({
9223
9388
  dateRange.endDate,
9224
9389
  {
9225
9390
  participantCount,
9391
+ units,
9226
9392
  enabled: availabilityProp === void 0
9227
9393
  }
9228
9394
  );
@@ -9359,6 +9525,7 @@ function ProductCustomizer({
9359
9525
  onAddToCart,
9360
9526
  onVariantChange,
9361
9527
  showSpecialInstructions = true,
9528
+ submitLabel,
9362
9529
  className,
9363
9530
  classNames
9364
9531
  }) {
@@ -9379,6 +9546,7 @@ function ProductCustomizer({
9379
9546
  const [specialInstructions, setSpecialInstructions] = React14.useState("");
9380
9547
  const [selectedSlot, setSelectedSlot] = React14.useState(null);
9381
9548
  const [selectedStaffId, setSelectedStaffId] = React14.useState(null);
9549
+ const [unitsCount, setUnitsCount] = React14.useState(1);
9382
9550
  const cart = useCart();
9383
9551
  const currency = chunkPTPOQVXS_js.getProductCurrency(product);
9384
9552
  const productType = product.type || "product";
@@ -9386,6 +9554,7 @@ function ProductCustomizer({
9386
9554
  const isBundle = productType === "bundle";
9387
9555
  const isDigital = productType === "digital";
9388
9556
  const isService = productType === "service";
9557
+ const offersUnits = isService && chunkHKN3YG76_js.isMultiDayService(product);
9389
9558
  const isStandard = !isComposite && !isBundle;
9390
9559
  const hasVariants = isStandard && product.variants && product.variants.length > 0;
9391
9560
  const hasAddOns = isStandard && product.add_ons && product.add_ons.length > 0;
@@ -9407,6 +9576,7 @@ function ProductCustomizer({
9407
9576
  setSpecialInstructions("");
9408
9577
  setSelectedSlot(null);
9409
9578
  setSelectedStaffId(null);
9579
+ setUnitsCount(1);
9410
9580
  }, [product.id, product.min_order_quantity]);
9411
9581
  const slotStaff = React14.useMemo(
9412
9582
  () => (selectedSlot?.available_staff ?? []).map((member) => ({
@@ -9496,7 +9666,17 @@ function ProductCustomizer({
9496
9666
  if (perUnit === void 0 || perUnit === null) return void 0;
9497
9667
  return chunkPTPOQVXS_js.parsePrice(perUnit) * quantity;
9498
9668
  }, [quote, quantity]);
9499
- const displayTotalPrice = quotedTotalPrice ?? localTotalPrice;
9669
+ const displayTotalPrice = React14.useMemo(() => {
9670
+ const total = quotedTotalPrice ?? localTotalPrice;
9671
+ const multiplier = chunkHKN3YG76_js.serviceBillingMultiplier(product, {
9672
+ quantity,
9673
+ units: unitsCount,
9674
+ scheduledStart: selectedSlot?.start_time,
9675
+ scheduledEnd: selectedSlot?.end_time
9676
+ });
9677
+ if (multiplier === null) return total;
9678
+ return total / Math.max(1, quantity) * multiplier;
9679
+ }, [quotedTotalPrice, localTotalPrice, product, quantity, unitsCount, selectedSlot]);
9500
9680
  const handleVariantChange = React14.useCallback(
9501
9681
  (variantId, variant) => {
9502
9682
  setSelectedVariantId(variantId);
@@ -9528,7 +9708,8 @@ function ProductCustomizer({
9528
9708
  scheduledStart: selectedSlot.start_time,
9529
9709
  scheduledEnd: selectedSlot.end_time,
9530
9710
  staffId: selectedStaffId ?? selectedSlot.available_staff?.[0]?.staff_id,
9531
- resourceId: selectedSlot.available_resources?.[0]?.resource_id
9711
+ resourceId: selectedSlot.available_resources?.[0]?.resource_id,
9712
+ units: unitsCount > 1 ? unitsCount : void 0
9532
9713
  } : {},
9533
9714
  customerInputs: Object.keys(customerInputValues).length > 0 ? Object.entries(customerInputValues).filter(([, v]) => v !== void 0 && v !== "").map(([fieldId, value]) => ({ field_id: fieldId, value })) : void 0,
9534
9715
  specialInstructions: specialInstructions.trim() || void 0
@@ -9549,7 +9730,7 @@ function ProductCustomizer({
9549
9730
  } finally {
9550
9731
  setIsSubmitting(false);
9551
9732
  }
9552
- }, [product, quantity, selectedVariantId, selectedVariant, quoteId, normalizedAddOnOptionIds, selectedAddOnOptions, isComposite, compositeSelections, isBundle, bundleSelections, selectedBillingPlan, customerInputValues, specialInstructions, isService, selectedSlot, selectedStaffId, isSubmitting, onAddToCart, cart]);
9733
+ }, [product, quantity, selectedVariantId, selectedVariant, quoteId, normalizedAddOnOptionIds, selectedAddOnOptions, isComposite, compositeSelections, isBundle, bundleSelections, selectedBillingPlan, customerInputValues, specialInstructions, isService, selectedSlot, selectedStaffId, unitsCount, isSubmitting, onAddToCart, cart]);
9553
9734
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { "data-cimplify-customizer": true, className: cn("space-y-6", className, classNames?.root), children: [
9554
9735
  isComposite && product.groups && product.composite_id && /* @__PURE__ */ jsxRuntime.jsx(
9555
9736
  CompositeSelector,
@@ -9586,9 +9767,17 @@ function ProductCustomizer({
9586
9767
  setSelectedStaffId(null);
9587
9768
  },
9588
9769
  participantCount: quantity,
9589
- currency
9770
+ units: offersUnits ? unitsCount : void 0,
9771
+ currency,
9772
+ schedulingMode: product.scheduling_mode ?? (offersUnits ? "multi_day" : "intraday"),
9773
+ durationUnit: product.duration_unit,
9774
+ durationValue: product.duration_value
9590
9775
  }
9591
9776
  ),
9777
+ offersUnits && /* @__PURE__ */ jsxRuntime.jsxs("div", { "data-cimplify-customizer-units": true, className: classNames?.units, children: [
9778
+ /* @__PURE__ */ jsxRuntime.jsx("label", { className: "block text-xs font-medium uppercase tracking-wider text-muted-foreground mb-3", children: "Units" }),
9779
+ /* @__PURE__ */ jsxRuntime.jsx(QuantitySelector, { value: unitsCount, onChange: setUnitsCount, min: 1 })
9780
+ ] }),
9592
9781
  isService && slotStaff.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs("div", { "data-cimplify-customizer-staff": true, children: [
9593
9782
  /* @__PURE__ */ jsxRuntime.jsx("label", { className: "block text-xs font-medium uppercase tracking-wider text-muted-foreground mb-3", children: "Choose your specialist" }),
9594
9783
  /* @__PURE__ */ jsxRuntime.jsx(
@@ -9747,8 +9936,10 @@ function ProductCustomizer({
9747
9936
  isAdded && classNames?.submitButtonAdded,
9748
9937
  classNames?.submitButton
9749
9938
  ),
9750
- children: isAdded ? "Added to Cart" : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
9751
- "Add to Cart \xB7 ",
9939
+ children: isAdded ? "Added" : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
9940
+ submitLabel ?? "Add to Cart",
9941
+ " \xB7",
9942
+ " ",
9752
9943
  /* @__PURE__ */ jsxRuntime.jsx(Price, { amount: displayTotalPrice, currency })
9753
9944
  ] })
9754
9945
  }
@@ -9908,13 +10099,13 @@ function StoreVideo({
9908
10099
  autoplay = true,
9909
10100
  loop = true,
9910
10101
  muted = true,
9911
- lazy = true,
10102
+ lazy: lazy2 = true,
9912
10103
  className
9913
10104
  }) {
9914
10105
  const ref = React14.useRef(null);
9915
- const [inView, setInView] = React14.useState(!lazy);
10106
+ const [inView, setInView] = React14.useState(!lazy2);
9916
10107
  React14.useEffect(() => {
9917
- if (!lazy || inView) return;
10108
+ if (!lazy2 || inView) return;
9918
10109
  const node = ref.current;
9919
10110
  if (!node || typeof IntersectionObserver === "undefined") {
9920
10111
  setInView(true);
@@ -9931,7 +10122,7 @@ function StoreVideo({
9931
10122
  );
9932
10123
  observer.observe(node);
9933
10124
  return () => observer.disconnect();
9934
- }, [lazy, inView]);
10125
+ }, [lazy2, inView]);
9935
10126
  return /* @__PURE__ */ jsxRuntime.jsx(
9936
10127
  "div",
9937
10128
  {
@@ -9949,7 +10140,7 @@ function StoreVideo({
9949
10140
  muted,
9950
10141
  playsInline: true,
9951
10142
  controls,
9952
- preload: lazy ? "metadata" : "auto",
10143
+ preload: lazy2 ? "metadata" : "auto",
9953
10144
  "aria-label": alt,
9954
10145
  style: { width: "100%", height: "100%", objectFit: "cover", display: "block" },
9955
10146
  children: poster ? /* @__PURE__ */ jsxRuntime.jsx(
@@ -10191,7 +10382,7 @@ function CartLineItemRow({
10191
10382
  const isOptimistic = item.isOptimistic === true;
10192
10383
  const lineTotal = chunkPTPOQVXS_js.parsePrice(unitPrice) * item.quantity;
10193
10384
  const imageUrl = item.product.image_url;
10194
- const variantLabel2 = item.variant ? chunkT7Z25EY4_js.getVariantDisplayName(item.variant, item.product.name) : null;
10385
+ const variantLabel2 = item.variant ? chunkHKN3YG76_js.getVariantDisplayName(item.variant, item.product.name) : null;
10195
10386
  return /* @__PURE__ */ jsxRuntime.jsxs(
10196
10387
  "div",
10197
10388
  {
@@ -10291,7 +10482,7 @@ function CartLineItemRow({
10291
10482
  input.field_name,
10292
10483
  ":"
10293
10484
  ] }),
10294
- /* @__PURE__ */ jsxRuntime.jsx("dd", { "data-cimplify-cart-input-value": true, className: "text-foreground m-0 truncate", children: input.field_type === chunkT7Z25EY4_js.INPUT_FIELD_TYPE.File || input.field_type === chunkT7Z25EY4_js.INPUT_FIELD_TYPE.Image ? String(input.value).split("/").pop() || "Uploaded file" : String(input.value) })
10485
+ /* @__PURE__ */ jsxRuntime.jsx("dd", { "data-cimplify-cart-input-value": true, className: "text-foreground m-0 truncate", children: input.field_type === chunkHKN3YG76_js.INPUT_FIELD_TYPE.File || input.field_type === chunkHKN3YG76_js.INPUT_FIELD_TYPE.Image ? String(input.value).split("/").pop() || "Uploaded file" : String(input.value) })
10295
10486
  ] }, input.field_id)) }),
10296
10487
  (item.lineType === "service" || item.product.type === "service") && item.scheduledStart && /* @__PURE__ */ jsxRuntime.jsxs("div", { "data-cimplify-cart-service-info": true, className: cn("mt-1.5 flex items-center gap-1.5 text-xs text-muted-foreground", classNames?.serviceInfo), children: [
10297
10488
  /* @__PURE__ */ jsxRuntime.jsx("svg", { className: "w-3.5 h-3.5 shrink-0", fill: "none", stroke: "currentColor", strokeWidth: "2", viewBox: "0 0 24 24", "aria-hidden": true, children: /* @__PURE__ */ jsxRuntime.jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z" }) }),
@@ -11176,7 +11367,7 @@ function FoodProductCard({
11176
11367
  const tags = product.tags || [];
11177
11368
  const isSignature = product.metadata?.is_signature === true;
11178
11369
  const isNew = product.metadata?.is_new === true;
11179
- const isComposite = product.type === chunkT7Z25EY4_js.PRODUCT_TYPE.Composite;
11370
+ const isComposite = product.type === chunkHKN3YG76_js.PRODUCT_TYPE.Composite;
11180
11371
  return /* @__PURE__ */ jsxRuntime.jsxs(CardShell, { product, renderLink, className, children: [
11181
11372
  /* @__PURE__ */ jsxRuntime.jsxs(CardImage, { src: image, alt: product.name, aspectRatio: "square", renderImage, children: [
11182
11373
  isSignature && /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "absolute top-3 left-3 inline-flex items-center gap-1 text-[11px] font-semibold tracking-wide bg-amber-50 text-amber-700 border border-amber-200/60 px-2 py-0.5 rounded-md", children: [
@@ -11640,18 +11831,18 @@ function RentalServiceCard({
11640
11831
  const currency = chunkPTPOQVXS_js.getProductCurrency(product);
11641
11832
  const hasDeposit = product.deposit_type && product.deposit_type !== "none" && product.deposit_amount;
11642
11833
  const unit = product.duration_unit ? UNIT_LABELS2[product.duration_unit] || product.duration_unit : "day";
11834
+ const priceUnit = product.price_basis === "per_person" ? "guest" : product.price_basis === "flat" ? "" : unit;
11643
11835
  const status = product.inventory_status;
11644
11836
  return /* @__PURE__ */ jsxRuntime.jsxs(CardShell, { product, renderLink, className, children: [
11645
11837
  /* @__PURE__ */ jsxRuntime.jsxs(CardImage, { src: image, alt: product.name, aspectRatio: "16/9", renderImage, children: [
11646
11838
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute inset-0 bg-gradient-to-t from-black/50 to-transparent pointer-events-none" }),
11647
- /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "absolute bottom-3 left-3 inline-flex items-center text-[12px] font-semibold px-2.5 py-1 rounded-lg bg-background/92 backdrop-blur-xl text-foreground shadow-sm", children: [
11839
+ priceUnit && /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "absolute bottom-3 left-3 inline-flex items-center text-[12px] font-semibold px-2.5 py-1 rounded-lg bg-background/92 backdrop-blur-xl text-foreground shadow-sm", children: [
11648
11840
  "Per ",
11649
- unit
11841
+ priceUnit
11650
11842
  ] }),
11651
11843
  /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "absolute bottom-3 right-3 text-white font-bold text-lg drop-shadow-sm", children: [
11652
11844
  /* @__PURE__ */ jsxRuntime.jsx(Price, { amount: product.default_price, currency }),
11653
- "/",
11654
- unit
11845
+ priceUnit && `/${priceUnit}`
11655
11846
  ] }),
11656
11847
  product.general_service_capacity != null && product.general_service_capacity > 1 && /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "absolute top-3 left-3 text-[11px] font-semibold tracking-wide bg-blue-50/90 text-blue-700 border border-blue-200/50 backdrop-blur px-2 py-0.5 rounded-md", children: [
11657
11848
  "Up to ",
@@ -11943,36 +12134,36 @@ var BUILT_IN_CARDS = {
11943
12134
  ["composite" /* Composite */]: CompositeProductCard,
11944
12135
  ["subscription" /* Subscription */]: SubscriptionCard
11945
12136
  };
11946
- var RENTAL_UNITS = /* @__PURE__ */ new Set([chunkT7Z25EY4_js.DURATION_UNIT.Days, chunkT7Z25EY4_js.DURATION_UNIT.Hours]);
11947
- var LEASE_UNITS = /* @__PURE__ */ new Set([chunkT7Z25EY4_js.DURATION_UNIT.Weeks, chunkT7Z25EY4_js.DURATION_UNIT.Months, chunkT7Z25EY4_js.DURATION_UNIT.Years]);
12137
+ var RENTAL_UNITS = /* @__PURE__ */ new Set([chunkHKN3YG76_js.DURATION_UNIT.Days, chunkHKN3YG76_js.DURATION_UNIT.Hours]);
12138
+ var LEASE_UNITS = /* @__PURE__ */ new Set([chunkHKN3YG76_js.DURATION_UNIT.Weeks, chunkHKN3YG76_js.DURATION_UNIT.Months, chunkHKN3YG76_js.DURATION_UNIT.Years]);
11948
12139
  function resolveCardVariant(product) {
11949
- if (product.type === chunkT7Z25EY4_js.PRODUCT_TYPE.Bundle) {
12140
+ if (product.type === chunkHKN3YG76_js.PRODUCT_TYPE.Bundle) {
11950
12141
  return "bundle" /* Bundle */;
11951
12142
  }
11952
- if (product.type === chunkT7Z25EY4_js.PRODUCT_TYPE.Composite) {
12143
+ if (product.type === chunkHKN3YG76_js.PRODUCT_TYPE.Composite) {
11953
12144
  return "composite" /* Composite */;
11954
12145
  }
11955
12146
  if (product.quantity_pricing && product.quantity_pricing.length > 1) {
11956
12147
  return "wholesale" /* Wholesale */;
11957
12148
  }
11958
- if (product.type === chunkT7Z25EY4_js.PRODUCT_TYPE.Digital) {
12149
+ if (product.type === chunkHKN3YG76_js.PRODUCT_TYPE.Digital) {
11959
12150
  return "digital" /* Digital */;
11960
12151
  }
11961
- if (product.type === chunkT7Z25EY4_js.PRODUCT_TYPE.Service) {
12152
+ if (product.type === chunkHKN3YG76_js.PRODUCT_TYPE.Service) {
11962
12153
  switch (product.boundary_kind) {
11963
- case chunkT7Z25EY4_js.BOUNDARY_KIND.CheckInOut:
12154
+ case chunkHKN3YG76_js.BOUNDARY_KIND.CheckInOut:
11964
12155
  return "accommodation" /* Accommodation */;
11965
- case chunkT7Z25EY4_js.BOUNDARY_KIND.MoveInOut:
12156
+ case chunkHKN3YG76_js.BOUNDARY_KIND.MoveInOut:
11966
12157
  return "lease" /* Lease */;
11967
- case chunkT7Z25EY4_js.BOUNDARY_KIND.PickupReturn:
11968
- case chunkT7Z25EY4_js.BOUNDARY_KIND.DeliveryCollection:
11969
- case chunkT7Z25EY4_js.BOUNDARY_KIND.AccessVacate:
12158
+ case chunkHKN3YG76_js.BOUNDARY_KIND.PickupReturn:
12159
+ case chunkHKN3YG76_js.BOUNDARY_KIND.DeliveryCollection:
12160
+ case chunkHKN3YG76_js.BOUNDARY_KIND.AccessVacate:
11970
12161
  return "rental" /* Rental */;
11971
12162
  }
11972
12163
  if (product.duration_unit && RENTAL_UNITS.has(product.duration_unit)) {
11973
12164
  return "rental" /* Rental */;
11974
12165
  }
11975
- if (product.duration_unit === chunkT7Z25EY4_js.DURATION_UNIT.Nights) {
12166
+ if (product.duration_unit === chunkHKN3YG76_js.DURATION_UNIT.Nights) {
11976
12167
  return "accommodation" /* Accommodation */;
11977
12168
  }
11978
12169
  if (product.duration_unit && LEASE_UNITS.has(product.duration_unit)) {
@@ -11983,10 +12174,10 @@ function resolveCardVariant(product) {
11983
12174
  }
11984
12175
  return "standard" /* Standard */;
11985
12176
  }
11986
- if (product.render_hint === chunkT7Z25EY4_js.RENDER_HINT.Food) {
12177
+ if (product.render_hint === chunkHKN3YG76_js.RENDER_HINT.Food) {
11987
12178
  return "food" /* Food */;
11988
12179
  }
11989
- if (product.render_hint === chunkT7Z25EY4_js.RENDER_HINT.Physical) {
12180
+ if (product.render_hint === chunkHKN3YG76_js.RENDER_HINT.Physical) {
11990
12181
  return "retail" /* Retail */;
11991
12182
  }
11992
12183
  return "retail" /* Retail */;
@@ -12245,6 +12436,15 @@ function ProductGrid({
12245
12436
  ] });
12246
12437
  }
12247
12438
 
12439
+ // src/react/utils/attachment.ts
12440
+ var IMAGE_EXTENSIONS = [".jpg", ".jpeg", ".png", ".webp", ".gif", ".bmp", ".svg"];
12441
+ function isImageAttachment(attachment) {
12442
+ if (attachment.mime_type?.startsWith("image/")) return true;
12443
+ if (attachment.mime_type) return false;
12444
+ const path = attachment.url.split("?")[0].split("#")[0].toLowerCase();
12445
+ return IMAGE_EXTENSIONS.some((extension) => path.endsWith(extension));
12446
+ }
12447
+
12248
12448
  // src/react/utils/boundary-labels.ts
12249
12449
  var PRESET_LABELS = {
12250
12450
  check_in_out: { start: "Check-in", end: "Check-out" },
@@ -12254,14 +12454,14 @@ var PRESET_LABELS = {
12254
12454
  move_in_out: { start: "Move-in", end: "Move-out" }
12255
12455
  };
12256
12456
  function defaultBoundaryKind(unit) {
12257
- return unit === chunkT7Z25EY4_js.DURATION_UNIT.Nights ? chunkT7Z25EY4_js.BOUNDARY_KIND.CheckInOut : chunkT7Z25EY4_js.BOUNDARY_KIND.PickupReturn;
12457
+ return unit === chunkHKN3YG76_js.DURATION_UNIT.Nights ? chunkHKN3YG76_js.BOUNDARY_KIND.CheckInOut : chunkHKN3YG76_js.BOUNDARY_KIND.PickupReturn;
12258
12458
  }
12259
12459
  function resolveBoundaryKind(product) {
12260
12460
  return product.boundary_kind ?? defaultBoundaryKind(product.duration_unit);
12261
12461
  }
12262
12462
  function getBoundaryLabels(product) {
12263
12463
  const kind = resolveBoundaryKind(product);
12264
- if (kind === chunkT7Z25EY4_js.BOUNDARY_KIND.Custom) {
12464
+ if (kind === chunkHKN3YG76_js.BOUNDARY_KIND.Custom) {
12265
12465
  return {
12266
12466
  start: product.boundary_start_label?.trim() || "Start",
12267
12467
  end: product.boundary_end_label?.trim() || "End"
@@ -13426,6 +13626,7 @@ function BookingPage({
13426
13626
  const [selectedDate, setSelectedDate] = React14.useState(null);
13427
13627
  const [selectedStaffId, setSelectedStaffId] = React14.useState(null);
13428
13628
  const [selectedResourceId, setSelectedResourceId] = React14.useState(null);
13629
+ const [bookingUnits, setBookingUnits] = React14.useState(1);
13429
13630
  const [isSubmitting, setIsSubmitting] = React14.useState(false);
13430
13631
  const [error, setError] = React14.useState(null);
13431
13632
  const hasResourceStep = resources && resources.length > 0;
@@ -13519,7 +13720,8 @@ function BookingPage({
13519
13720
  scheduledStart: selectedSlot.start_time,
13520
13721
  scheduledEnd: selectedSlot.end_time,
13521
13722
  staffId: selectedStaffId || void 0,
13522
- resourceId: selectedResourceId || void 0
13723
+ resourceId: selectedResourceId || void 0,
13724
+ units: bookingUnits > 1 ? bookingUnits : void 0
13523
13725
  });
13524
13726
  onBooked?.(selectedSlot, selectedStaffId);
13525
13727
  }
@@ -13615,6 +13817,31 @@ function BookingPage({
13615
13817
  ] }),
13616
13818
  step === STEP.CONFIRM && selectedSlot && /* @__PURE__ */ jsxRuntime.jsxs("div", { "data-cimplify-booking-step": "confirm", className: classNames?.step, children: [
13617
13819
  /* @__PURE__ */ jsxRuntime.jsx("h2", { "data-cimplify-booking-step-title": true, className: classNames?.stepTitle, children: "Confirm your booking" }),
13820
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { "data-cimplify-booking-units": true, className: classNames?.summaryRow, children: [
13821
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: "Units" }),
13822
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { "data-cimplify-booking-units-stepper": true, children: [
13823
+ /* @__PURE__ */ jsxRuntime.jsx(
13824
+ "button",
13825
+ {
13826
+ type: "button",
13827
+ "data-cimplify-booking-units-decrement": true,
13828
+ onClick: () => setBookingUnits((current) => Math.max(1, current - 1)),
13829
+ disabled: bookingUnits <= 1,
13830
+ children: "\u2212"
13831
+ }
13832
+ ),
13833
+ /* @__PURE__ */ jsxRuntime.jsx("span", { "data-cimplify-booking-units-value": true, children: bookingUnits }),
13834
+ /* @__PURE__ */ jsxRuntime.jsx(
13835
+ "button",
13836
+ {
13837
+ type: "button",
13838
+ "data-cimplify-booking-units-increment": true,
13839
+ onClick: () => setBookingUnits((current) => current + 1),
13840
+ children: "+"
13841
+ }
13842
+ )
13843
+ ] })
13844
+ ] }),
13618
13845
  /* @__PURE__ */ jsxRuntime.jsxs("div", { "data-cimplify-booking-summary": true, className: classNames?.summary, children: [
13619
13846
  /* @__PURE__ */ jsxRuntime.jsxs("div", { "data-cimplify-booking-summary-row": true, className: classNames?.summaryRow, children: [
13620
13847
  /* @__PURE__ */ jsxRuntime.jsx("span", { children: "Service" }),
@@ -14322,7 +14549,7 @@ function DefaultProductLayout({
14322
14549
  className
14323
14550
  }) {
14324
14551
  const [selectedVariantId, setSelectedVariantId] = React14__default.default.useState();
14325
- const images = chunkT7Z25EY4_js.resolveGallery(product, selectedVariantId);
14552
+ const images = chunkHKN3YG76_js.resolveGallery(product, selectedVariantId);
14326
14553
  const currency = chunkPTPOQVXS_js.getProductCurrency(product);
14327
14554
  const hasRange = product.quantity_pricing && product.quantity_pricing.length > 1 || product.variants && product.variants.length > 1;
14328
14555
  const onSale = chunkPTPOQVXS_js.isOnSale(product);
@@ -14969,25 +15196,25 @@ function resolveTemplateKey(product) {
14969
15196
  if (typeof metaTemplate === "string" && metaTemplate.trim()) {
14970
15197
  return metaTemplate.trim();
14971
15198
  }
14972
- if (product.type === chunkT7Z25EY4_js.PRODUCT_TYPE.Bundle) {
15199
+ if (product.type === chunkHKN3YG76_js.PRODUCT_TYPE.Bundle) {
14973
15200
  return "bundle" /* Bundle */;
14974
15201
  }
14975
- if (product.type === chunkT7Z25EY4_js.PRODUCT_TYPE.Composite) {
15202
+ if (product.type === chunkHKN3YG76_js.PRODUCT_TYPE.Composite) {
14976
15203
  return "composite" /* Composite */;
14977
15204
  }
14978
15205
  if (product.quantity_pricing && product.quantity_pricing.length > 1) {
14979
15206
  return "wholesale" /* Wholesale */;
14980
15207
  }
14981
- if (product.type === chunkT7Z25EY4_js.PRODUCT_TYPE.Service) {
15208
+ if (product.type === chunkHKN3YG76_js.PRODUCT_TYPE.Service) {
14982
15209
  return "service" /* Service */;
14983
15210
  }
14984
- if (product.type === chunkT7Z25EY4_js.PRODUCT_TYPE.Digital) {
15211
+ if (product.type === chunkHKN3YG76_js.PRODUCT_TYPE.Digital) {
14985
15212
  return "digital" /* Digital */;
14986
15213
  }
14987
- if (product.render_hint === chunkT7Z25EY4_js.RENDER_HINT.Food) {
15214
+ if (product.render_hint === chunkHKN3YG76_js.RENDER_HINT.Food) {
14988
15215
  return "food" /* Food */;
14989
15216
  }
14990
- if (product.render_hint === chunkT7Z25EY4_js.RENDER_HINT.Physical) {
15217
+ if (product.render_hint === chunkHKN3YG76_js.RENDER_HINT.Physical) {
14991
15218
  return "physical" /* Physical */;
14992
15219
  }
14993
15220
  return "default" /* Default */;
@@ -16751,6 +16978,112 @@ function CurrencySelector({
16751
16978
  );
16752
16979
  }) });
16753
16980
  }
16981
+ var Streamdown = React14.lazy(() => import('streamdown').then((m) => ({ default: m.Streamdown })));
16982
+ function detectLookbehindSupport() {
16983
+ try {
16984
+ new RegExp("(?<=a)b");
16985
+ return true;
16986
+ } catch {
16987
+ return false;
16988
+ }
16989
+ }
16990
+ var supportsRegExpLookbehind = detectLookbehindSupport();
16991
+ marked.marked.setOptions({
16992
+ gfm: true,
16993
+ breaks: true
16994
+ });
16995
+ var ALLOWED_TAGS = [
16996
+ "a",
16997
+ "b",
16998
+ "blockquote",
16999
+ "br",
17000
+ "code",
17001
+ "del",
17002
+ "em",
17003
+ "h1",
17004
+ "h2",
17005
+ "h3",
17006
+ "h4",
17007
+ "hr",
17008
+ "i",
17009
+ "li",
17010
+ "ol",
17011
+ "p",
17012
+ "pre",
17013
+ "strong",
17014
+ "table",
17015
+ "tbody",
17016
+ "td",
17017
+ "th",
17018
+ "thead",
17019
+ "tr",
17020
+ "ul"
17021
+ ];
17022
+ var ALLOWED_ATTR = ["href", "rel", "target", "title", "start"];
17023
+ var hooksInstalled = false;
17024
+ function installHooks() {
17025
+ if (hooksInstalled) return;
17026
+ hooksInstalled = true;
17027
+ DOMPurify__default.default.addHook("afterSanitizeAttributes", (node) => {
17028
+ if (!(node instanceof HTMLAnchorElement)) return;
17029
+ if (!node.getAttribute("href")) return;
17030
+ node.setAttribute("rel", "noreferrer noopener");
17031
+ node.setAttribute("target", "_blank");
17032
+ });
17033
+ }
17034
+ var CACHE_LIMIT = 300;
17035
+ var cache = /* @__PURE__ */ new Map();
17036
+ function renderMarkdown(text) {
17037
+ const cached = cache.get(text);
17038
+ if (cached !== void 0) return cached;
17039
+ installHooks();
17040
+ const html = DOMPurify__default.default.sanitize(marked.marked.parse(text), {
17041
+ ALLOWED_TAGS,
17042
+ ALLOWED_ATTR
17043
+ });
17044
+ if (cache.size >= CACHE_LIMIT) {
17045
+ const oldest = cache.keys().next().value;
17046
+ if (oldest !== void 0) cache.delete(oldest);
17047
+ }
17048
+ cache.set(text, html);
17049
+ return html;
17050
+ }
17051
+ function SettledMarkdown({ text, className }) {
17052
+ const html = React14.useMemo(() => renderMarkdown(text), [text]);
17053
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className, dangerouslySetInnerHTML: { __html: html } });
17054
+ }
17055
+ var ChatMarkdown = React14.memo(function ChatMarkdown2({
17056
+ text,
17057
+ className,
17058
+ streaming = false
17059
+ }) {
17060
+ if (streaming && supportsRegExpLookbehind) {
17061
+ return (
17062
+ // While the streamdown chunk loads, the settled renderer keeps the
17063
+ // text on screen — no blank frame at stream start.
17064
+ /* @__PURE__ */ jsxRuntime.jsx(React14.Suspense, { fallback: /* @__PURE__ */ jsxRuntime.jsx(SettledMarkdown, { text, className }), children: /* @__PURE__ */ jsxRuntime.jsx(Streamdown, { className, children: text }) })
17065
+ );
17066
+ }
17067
+ return /* @__PURE__ */ jsxRuntime.jsx(SettledMarkdown, { text, className });
17068
+ });
17069
+
17070
+ // src/react/chat-model.ts
17071
+ function partitionMessages(messages) {
17072
+ const bubbles = [];
17073
+ const reactionsByTarget = /* @__PURE__ */ new Map();
17074
+ for (const message of messages) {
17075
+ if (message.content_type === "reaction") {
17076
+ const targetId = message.reply_to?.id;
17077
+ if (!targetId) continue;
17078
+ const list = reactionsByTarget.get(targetId);
17079
+ if (list) list.push(message);
17080
+ else reactionsByTarget.set(targetId, [message]);
17081
+ continue;
17082
+ }
17083
+ bubbles.push(message);
17084
+ }
17085
+ return { bubbles, reactionsByTarget };
17086
+ }
16754
17087
  function ChatWidget({
16755
17088
  client: clientProp,
16756
17089
  businessName = "Support",
@@ -16767,24 +17100,46 @@ function ChatWidget({
16767
17100
  const context = useOptionalCimplifyClient();
16768
17101
  const client = clientProp ?? context?.client;
16769
17102
  const [open, setOpen] = React14.useState(defaultOpen);
16770
- const [unread, setUnread] = React14.useState(0);
16771
- const { messages, isLoading, isSending, error, isActive, send, startConversation } = useChat({ client, pollInterval });
16772
- const prevCountRef = React14.useRef(messages.length);
16773
- React14.useEffect(() => {
16774
- if (!open && messages.length > prevCountRef.current) {
16775
- const newMessages = messages.slice(prevCountRef.current);
16776
- const incomingCount = newMessages.filter(
16777
- (m) => m.sender_type !== "customer"
16778
- ).length;
16779
- if (incomingCount > 0) setUnread((n) => n + incomingCount);
16780
- }
16781
- prevCountRef.current = messages.length;
16782
- }, [messages, open]);
17103
+ const [readCount, setReadCount] = React14.useState(0);
17104
+ const {
17105
+ messages,
17106
+ isLoading,
17107
+ isSending,
17108
+ awaitingReply,
17109
+ seenAt,
17110
+ error,
17111
+ isActive,
17112
+ claimedAs,
17113
+ signOut,
17114
+ send,
17115
+ react,
17116
+ startConversation
17117
+ } = useChat({ client, pollInterval });
17118
+ const showTyping = isSending || awaitingReply;
17119
+ const attachPhoto = React14.useCallback(
17120
+ async (file) => {
17121
+ if (!client || !file.type.startsWith("image/") || file.size > 5 * 1024 * 1024) return;
17122
+ const uploaded = await client.uploads.upload(file);
17123
+ if (!uploaded.ok) return;
17124
+ const attachment = {
17125
+ upload_id: uploaded.value.id,
17126
+ url: uploaded.value.url,
17127
+ mime_type: uploaded.value.content_type,
17128
+ filename: uploaded.value.filename,
17129
+ size: uploaded.value.size_bytes
17130
+ };
17131
+ await send("", { attachments: [attachment] });
17132
+ },
17133
+ [client, send]
17134
+ );
17135
+ const unread = open ? 0 : messages.slice(readCount).filter((m) => m.sender_type !== "customer").length;
16783
17136
  const handleOpen = React14.useCallback(() => {
16784
17137
  setOpen(true);
16785
- setUnread(0);
16786
17138
  }, []);
16787
- const handleClose = React14.useCallback(() => setOpen(false), []);
17139
+ const handleClose = React14.useCallback(() => {
17140
+ setReadCount(messages.length);
17141
+ setOpen(false);
17142
+ }, [messages.length]);
16788
17143
  const isLeft = position === "bottom-left";
16789
17144
  return /* @__PURE__ */ jsxRuntime.jsxs(
16790
17145
  "div",
@@ -16815,11 +17170,17 @@ function ChatWidget({
16815
17170
  messages,
16816
17171
  isLoading,
16817
17172
  isSending,
17173
+ isTyping: showTyping,
17174
+ seenAt,
16818
17175
  error,
16819
17176
  isActive,
16820
17177
  isLeft,
17178
+ claimedAs,
17179
+ onSignOut: () => void signOut(),
16821
17180
  onClose: handleClose,
16822
17181
  onSend: send,
17182
+ onReact: react,
17183
+ onAttach: attachPhoto,
16823
17184
  onStartConversation: startConversation,
16824
17185
  classNames
16825
17186
  }
@@ -16872,11 +17233,17 @@ function Panel({
16872
17233
  messages,
16873
17234
  isLoading,
16874
17235
  isSending,
17236
+ isTyping,
17237
+ seenAt,
16875
17238
  error,
16876
17239
  isActive,
16877
17240
  isLeft,
17241
+ claimedAs,
17242
+ onSignOut,
16878
17243
  onClose,
16879
17244
  onSend,
17245
+ onReact,
17246
+ onAttach,
16880
17247
  onStartConversation,
16881
17248
  classNames
16882
17249
  }) {
@@ -16896,6 +17263,8 @@ function Panel({
16896
17263
  Header,
16897
17264
  {
16898
17265
  businessName,
17266
+ claimedAs,
17267
+ onSignOut,
16899
17268
  onClose,
16900
17269
  className: classNames?.header
16901
17270
  }
@@ -16914,7 +17283,9 @@ function Panel({
16914
17283
  {
16915
17284
  messages,
16916
17285
  isLoading,
16917
- isSending,
17286
+ isSending: isTyping,
17287
+ seenAt,
17288
+ onReact,
16918
17289
  className: classNames?.messages
16919
17290
  }
16920
17291
  ),
@@ -16925,6 +17296,7 @@ function Panel({
16925
17296
  placeholder,
16926
17297
  isSending,
16927
17298
  onSend,
17299
+ onAttach,
16928
17300
  className: classNames?.input
16929
17301
  }
16930
17302
  ),
@@ -16939,6 +17311,8 @@ function Panel({
16939
17311
  }
16940
17312
  function Header({
16941
17313
  businessName,
17314
+ claimedAs,
17315
+ onSignOut,
16942
17316
  onClose,
16943
17317
  className
16944
17318
  }) {
@@ -16953,7 +17327,22 @@ function Header({
16953
17327
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex h-9 w-9 flex-shrink-0 items-center justify-center rounded-[10px] bg-white/15", children: /* @__PURE__ */ jsxRuntime.jsx(ChatIcon, { className: "h-5 w-5" }) }),
16954
17328
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "min-w-0 flex-1", children: [
16955
17329
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: "truncate text-[15px] font-semibold leading-tight", children: businessName }),
16956
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-1.5 text-xs text-white/60", children: [
17330
+ claimedAs ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-1 truncate text-xs text-white/60", children: [
17331
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "truncate", children: [
17332
+ "Chatting as ",
17333
+ claimedAs
17334
+ ] }),
17335
+ /* @__PURE__ */ jsxRuntime.jsx("span", { "aria-hidden": "true", children: "\xB7" }),
17336
+ /* @__PURE__ */ jsxRuntime.jsx(
17337
+ "button",
17338
+ {
17339
+ type: "button",
17340
+ onClick: onSignOut,
17341
+ className: "flex-shrink-0 underline underline-offset-2 hover:text-white",
17342
+ children: "Not you?"
17343
+ }
17344
+ )
17345
+ ] }) : /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-1.5 text-xs text-white/60", children: [
16957
17346
  /* @__PURE__ */ jsxRuntime.jsx("span", { className: "h-[7px] w-[7px] flex-shrink-0 rounded-full bg-emerald-400" }),
16958
17347
  "Online"
16959
17348
  ] })
@@ -17020,16 +17409,25 @@ function Welcome({
17020
17409
  }
17021
17410
  );
17022
17411
  }
17412
+ var QUICK_REACTIONS = ["\u{1F44D}", "\u2764\uFE0F"];
17413
+ function lastSeenCustomerMessageId(bubbles, seenAt) {
17414
+ if (!seenAt || !bubbles.some((m) => m.sender_type === "agent")) return null;
17415
+ for (let i = bubbles.length - 1; i >= 0; i--) {
17416
+ if (bubbles[i].sender_type !== "customer") continue;
17417
+ return bubbles[i].created_at <= seenAt ? bubbles[i].id : null;
17418
+ }
17419
+ return null;
17420
+ }
17023
17421
  function MessageList({
17024
17422
  messages,
17025
17423
  isLoading,
17026
17424
  isSending,
17425
+ seenAt,
17426
+ onReact,
17027
17427
  className
17028
17428
  }) {
17029
- const bottomRef = React14.useRef(null);
17030
- React14.useEffect(() => {
17031
- bottomRef.current?.scrollIntoView({ behavior: "smooth" });
17032
- }, [messages.length, isSending]);
17429
+ const { bubbles, reactionsByTarget } = partitionMessages(messages);
17430
+ const seenMessageId = lastSeenCustomerMessageId(bubbles, seenAt);
17033
17431
  return /* @__PURE__ */ jsxRuntime.jsxs(
17034
17432
  "div",
17035
17433
  {
@@ -17039,44 +17437,123 @@ function MessageList({
17039
17437
  ),
17040
17438
  children: [
17041
17439
  isLoading && messages.length === 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex flex-1 items-center justify-center", children: /* @__PURE__ */ jsxRuntime.jsx(TypingDots, {}) }),
17042
- messages.map((msg, i) => {
17440
+ bubbles.map((msg, i) => {
17043
17441
  const isCustomer = msg.sender_type === "customer";
17044
- const prev = messages[i - 1];
17442
+ const prev = bubbles[i - 1];
17045
17443
  const sameSenderAsPrev = prev?.sender_type === msg.sender_type;
17046
17444
  const isOptimistic = msg.id.startsWith("opt_");
17445
+ const reactions = reactionsByTarget.get(msg.id);
17047
17446
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
17048
17447
  !sameSenderAsPrev && i > 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-3" }),
17049
- /* @__PURE__ */ jsxRuntime.jsx(
17448
+ /* @__PURE__ */ jsxRuntime.jsxs(
17050
17449
  "div",
17051
17450
  {
17052
17451
  className: cn(
17053
- "flex max-w-[85%]",
17452
+ "group relative flex max-w-[85%]",
17054
17453
  isCustomer ? "ml-auto" : "mr-auto"
17055
17454
  ),
17056
- children: /* @__PURE__ */ jsxRuntime.jsxs(
17057
- "div",
17058
- {
17059
- className: cn(
17060
- "rounded-2xl px-3.5 py-2.5 text-sm leading-relaxed",
17061
- isCustomer ? "bg-foreground text-background" : "border border-border bg-background text-foreground",
17062
- isCustomer ? sameSenderAsPrev ? "rounded-tr" : "rounded-br" : sameSenderAsPrev ? "rounded-tl" : "rounded-bl",
17063
- isOptimistic && "opacity-70"
17064
- ),
17065
- children: [
17066
- !isCustomer && !sameSenderAsPrev && /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "mb-1 inline-flex items-center gap-1 rounded bg-violet-100 px-1.5 py-0.5 text-[10px] font-semibold text-violet-600", children: [
17067
- /* @__PURE__ */ jsxRuntime.jsx(SparkleIcon, { className: "h-[11px] w-[11px]" }),
17068
- "AI"
17069
- ] }),
17070
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "whitespace-pre-wrap", children: msg.content })
17071
- ]
17072
- }
17073
- )
17455
+ children: [
17456
+ !isCustomer && !isOptimistic && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute -top-3 right-0 z-10 hidden gap-0.5 rounded-full border border-border bg-background px-1 py-0.5 shadow-sm group-hover:flex", children: QUICK_REACTIONS.map((emoji) => /* @__PURE__ */ jsxRuntime.jsx(
17457
+ "button",
17458
+ {
17459
+ type: "button",
17460
+ "aria-label": `React ${emoji}`,
17461
+ onClick: () => void onReact(msg, emoji),
17462
+ className: "flex h-6 w-6 items-center justify-center rounded-full text-[13px] hover:bg-muted",
17463
+ children: emoji
17464
+ },
17465
+ emoji
17466
+ )) }),
17467
+ /* @__PURE__ */ jsxRuntime.jsxs(
17468
+ "div",
17469
+ {
17470
+ className: cn(
17471
+ "rounded-2xl px-3.5 py-2.5 text-sm leading-relaxed",
17472
+ isCustomer ? "bg-foreground text-background" : "border border-border bg-background text-foreground",
17473
+ isCustomer ? sameSenderAsPrev ? "rounded-tr" : "rounded-br" : sameSenderAsPrev ? "rounded-tl" : "rounded-bl",
17474
+ isOptimistic && "opacity-70"
17475
+ ),
17476
+ children: [
17477
+ !isCustomer && !sameSenderAsPrev && /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "mb-1 inline-flex items-center gap-1 rounded bg-violet-100 px-1.5 py-0.5 text-[10px] font-semibold text-violet-600", children: [
17478
+ /* @__PURE__ */ jsxRuntime.jsx(SparkleIcon, { className: "h-[11px] w-[11px]" }),
17479
+ "AI"
17480
+ ] }),
17481
+ msg.reply_to && /* @__PURE__ */ jsxRuntime.jsxs(
17482
+ "div",
17483
+ {
17484
+ className: cn(
17485
+ "mb-1.5 flex gap-1.5 rounded-lg px-2 py-1 text-xs",
17486
+ isCustomer ? "bg-background/15" : "bg-muted"
17487
+ ),
17488
+ children: [
17489
+ /* @__PURE__ */ jsxRuntime.jsx(
17490
+ "span",
17491
+ {
17492
+ className: cn(
17493
+ "w-[3px] shrink-0 self-stretch rounded-full",
17494
+ isCustomer ? "bg-background/40" : "bg-muted-foreground/40"
17495
+ )
17496
+ }
17497
+ ),
17498
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "min-w-0 truncate opacity-80", children: msg.reply_to.preview })
17499
+ ]
17500
+ }
17501
+ ),
17502
+ msg.attachments.length > 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { className: cn("flex flex-wrap gap-1.5", msg.content && "mb-1.5"), children: msg.attachments.map(
17503
+ (attachment, index) => attachment.mime_type?.startsWith("image/") ? /* @__PURE__ */ jsxRuntime.jsx("a", { href: attachment.url, target: "_blank", rel: "noreferrer", children: /* @__PURE__ */ jsxRuntime.jsx(
17504
+ "img",
17505
+ {
17506
+ src: attachment.url,
17507
+ alt: attachment.filename ?? "Photo",
17508
+ loading: "lazy",
17509
+ className: "max-h-48 max-w-full rounded-lg object-cover"
17510
+ }
17511
+ ) }, index) : /* @__PURE__ */ jsxRuntime.jsx(
17512
+ "a",
17513
+ {
17514
+ href: attachment.url,
17515
+ target: "_blank",
17516
+ rel: "noreferrer",
17517
+ className: "truncate text-xs underline",
17518
+ children: attachment.filename ?? "Attachment"
17519
+ },
17520
+ index
17521
+ )
17522
+ ) }),
17523
+ isCustomer ? msg.content && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "whitespace-pre-wrap", children: msg.content }) : /* @__PURE__ */ jsxRuntime.jsx(
17524
+ ChatMarkdown,
17525
+ {
17526
+ text: msg.content,
17527
+ className: "[&_p]:my-0 [&_p+p]:mt-1.5 [&_ul]:my-1 [&_ul]:list-disc [&_ul]:pl-4 [&_ol]:my-1 [&_ol]:list-decimal [&_ol]:pl-4 [&_li]:my-0.5 [&_h1]:text-sm [&_h1]:font-bold [&_h2]:text-sm [&_h2]:font-bold [&_h3]:text-sm [&_h3]:font-semibold [&_hr]:my-2 [&_table]:my-1 [&_table]:w-full [&_th]:pb-1 [&_th]:text-left [&_th]:text-[10px] [&_th]:font-semibold [&_th]:uppercase [&_td]:py-1 [&_td:last-child]:text-right [&_td:last-child]:font-semibold [&_td:last-child]:tabular-nums [&_a]:underline"
17528
+ }
17529
+ )
17530
+ ]
17531
+ }
17532
+ )
17533
+ ]
17074
17534
  }
17075
- )
17535
+ ),
17536
+ reactions && reactions.length > 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { className: cn("mt-0.5 flex gap-1", isCustomer ? "justify-end" : "justify-start"), children: reactions.map((reaction) => /* @__PURE__ */ jsxRuntime.jsx(
17537
+ "span",
17538
+ {
17539
+ className: "rounded-full border border-border bg-background px-1.5 py-0.5 text-xs shadow-sm",
17540
+ children: reaction.content
17541
+ },
17542
+ reaction.id
17543
+ )) }),
17544
+ msg.id === seenMessageId && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mt-0.5 text-right text-[10px] font-medium text-muted-foreground", children: "Seen" })
17076
17545
  ] }, msg.id);
17077
17546
  }),
17078
17547
  isSending && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mr-auto mt-1", children: /* @__PURE__ */ jsxRuntime.jsx(TypingDots, {}) }),
17079
- /* @__PURE__ */ jsxRuntime.jsx("div", { ref: bottomRef })
17548
+ /* @__PURE__ */ jsxRuntime.jsx(
17549
+ "div",
17550
+ {
17551
+ ref: (node) => {
17552
+ node?.scrollIntoView({ behavior: "smooth" });
17553
+ }
17554
+ },
17555
+ `${messages.length}:${isSending}`
17556
+ )
17080
17557
  ]
17081
17558
  }
17082
17559
  );
@@ -17085,10 +17562,24 @@ function ChatInput({
17085
17562
  placeholder,
17086
17563
  isSending,
17087
17564
  onSend,
17565
+ onAttach,
17088
17566
  className
17089
17567
  }) {
17090
17568
  const [value, setValue] = React14.useState("");
17569
+ const [uploading, setUploading] = React14.useState(false);
17091
17570
  const textareaRef = React14.useRef(null);
17571
+ const fileRef = React14.useRef(null);
17572
+ const handleAttach = React14.useCallback(
17573
+ async (file) => {
17574
+ setUploading(true);
17575
+ try {
17576
+ await onAttach(file);
17577
+ } finally {
17578
+ setUploading(false);
17579
+ }
17580
+ },
17581
+ [onAttach]
17582
+ );
17092
17583
  const handleSend = React14.useCallback(() => {
17093
17584
  const trimmed = value.trim();
17094
17585
  if (!trimmed || isSending) return;
@@ -17122,6 +17613,37 @@ function ChatInput({
17122
17613
  className
17123
17614
  ),
17124
17615
  children: [
17616
+ /* @__PURE__ */ jsxRuntime.jsx(
17617
+ "input",
17618
+ {
17619
+ ref: fileRef,
17620
+ type: "file",
17621
+ accept: "image/*",
17622
+ className: "hidden",
17623
+ "aria-hidden": "true",
17624
+ tabIndex: -1,
17625
+ onChange: (e) => {
17626
+ const file = e.target.files?.[0];
17627
+ e.target.value = "";
17628
+ if (file) void handleAttach(file);
17629
+ }
17630
+ }
17631
+ ),
17632
+ /* @__PURE__ */ jsxRuntime.jsx(
17633
+ "button",
17634
+ {
17635
+ type: "button",
17636
+ "aria-label": "Attach a photo",
17637
+ disabled: uploading,
17638
+ onClick: () => fileRef.current?.click(),
17639
+ className: cn(
17640
+ "flex h-10 w-10 flex-shrink-0 items-center justify-center rounded-[10px]",
17641
+ "bg-muted text-muted-foreground transition-colors hover:text-foreground",
17642
+ uploading && "animate-pulse"
17643
+ ),
17644
+ children: /* @__PURE__ */ jsxRuntime.jsx(ImageIcon, { className: "h-[18px] w-[18px]" })
17645
+ }
17646
+ ),
17125
17647
  /* @__PURE__ */ jsxRuntime.jsx(
17126
17648
  "div",
17127
17649
  {
@@ -17224,6 +17746,25 @@ function SendIcon({ className }) {
17224
17746
  }
17225
17747
  );
17226
17748
  }
17749
+ function ImageIcon({ className }) {
17750
+ return /* @__PURE__ */ jsxRuntime.jsxs(
17751
+ "svg",
17752
+ {
17753
+ className,
17754
+ viewBox: "0 0 24 24",
17755
+ fill: "none",
17756
+ stroke: "currentColor",
17757
+ strokeWidth: 2,
17758
+ strokeLinecap: "round",
17759
+ strokeLinejoin: "round",
17760
+ children: [
17761
+ /* @__PURE__ */ jsxRuntime.jsx("rect", { x: "3", y: "3", width: "18", height: "18", rx: "2", ry: "2" }),
17762
+ /* @__PURE__ */ jsxRuntime.jsx("circle", { cx: "8.5", cy: "8.5", r: "1.5" }),
17763
+ /* @__PURE__ */ jsxRuntime.jsx("polyline", { points: "21 15 16 10 5 21" })
17764
+ ]
17765
+ }
17766
+ );
17767
+ }
17227
17768
  function SparkleIcon({ className }) {
17228
17769
  return /* @__PURE__ */ jsxRuntime.jsx(
17229
17770
  "svg",
@@ -17238,9 +17779,1838 @@ function SparkleIcon({ className }) {
17238
17779
  );
17239
17780
  }
17240
17781
 
17241
- Object.defineProperty(exports, "getVariantDisplayName", {
17782
+ // src/react/genui/catalog.ts
17783
+ function genUICatalog(registry) {
17784
+ return new Map(
17785
+ registry.components.map((component) => [
17786
+ component.name,
17787
+ {
17788
+ params: component.params.map((param) => ({
17789
+ name: param.name,
17790
+ required: param.required ?? false
17791
+ }))
17792
+ }
17793
+ ])
17794
+ );
17795
+ }
17796
+
17797
+ // src/genui/lang/parser/ast.ts
17798
+ function isRuntimeExpr(node) {
17799
+ switch (node.k) {
17800
+ case "StateRef":
17801
+ case "RuntimeRef":
17802
+ case "BinOp":
17803
+ case "UnaryOp":
17804
+ case "Ternary":
17805
+ case "Member":
17806
+ case "Index":
17807
+ case "Assign":
17808
+ return true;
17809
+ default:
17810
+ return false;
17811
+ }
17812
+ }
17813
+ var AST_KINDS = /* @__PURE__ */ new Set([
17814
+ "Comp",
17815
+ "Ref",
17816
+ "StateRef",
17817
+ "RuntimeRef",
17818
+ "BinOp",
17819
+ "UnaryOp",
17820
+ "Ternary",
17821
+ "Member",
17822
+ "Index",
17823
+ "Assign",
17824
+ "Str",
17825
+ "Num",
17826
+ "Bool",
17827
+ "Null",
17828
+ "Arr",
17829
+ "Obj",
17830
+ "Ph"
17831
+ ]);
17832
+ function isASTNode(value) {
17833
+ if (!value || typeof value !== "object" || Array.isArray(value)) return false;
17834
+ return AST_KINDS.has(value.k);
17835
+ }
17836
+ function walkAST(node, visit) {
17837
+ const walk = (current) => {
17838
+ visit(current);
17839
+ switch (current.k) {
17840
+ case "Comp":
17841
+ current.args.forEach(walk);
17842
+ Object.values(current.mappedProps ?? {}).forEach(walk);
17843
+ break;
17844
+ case "Arr":
17845
+ current.els.forEach(walk);
17846
+ break;
17847
+ case "Obj":
17848
+ current.entries.forEach(([, value]) => walk(value));
17849
+ break;
17850
+ case "BinOp":
17851
+ walk(current.left);
17852
+ walk(current.right);
17853
+ break;
17854
+ case "UnaryOp":
17855
+ walk(current.operand);
17856
+ break;
17857
+ case "Ternary":
17858
+ walk(current.cond);
17859
+ walk(current.then);
17860
+ walk(current.else);
17861
+ break;
17862
+ case "Member":
17863
+ walk(current.obj);
17864
+ break;
17865
+ case "Index":
17866
+ walk(current.obj);
17867
+ walk(current.index);
17868
+ break;
17869
+ case "Assign":
17870
+ walk(current.value);
17871
+ break;
17872
+ }
17873
+ };
17874
+ walk(node);
17875
+ }
17876
+
17877
+ // src/genui/lang/parser/builtins.ts
17878
+ function resolveField(obj, path) {
17879
+ if (!path || obj == null) return void 0;
17880
+ if (!path.includes(".")) return obj[path];
17881
+ let cur = obj;
17882
+ for (const p of path.split(".")) {
17883
+ if (cur == null) return void 0;
17884
+ cur = cur[p];
17885
+ }
17886
+ return cur;
17887
+ }
17888
+ function toNumber2(val) {
17889
+ if (typeof val === "number") return val;
17890
+ if (typeof val === "string") {
17891
+ const n = Number(val);
17892
+ return isNaN(n) ? 0 : n;
17893
+ }
17894
+ if (typeof val === "boolean") return val ? 1 : 0;
17895
+ return 0;
17896
+ }
17897
+ var BUILTINS = {
17898
+ Count: {
17899
+ name: "Count",
17900
+ signature: "Count(array) \u2192 number",
17901
+ description: "Returns array length",
17902
+ fn: (arr) => Array.isArray(arr) ? arr.length : 0
17903
+ },
17904
+ First: {
17905
+ name: "First",
17906
+ signature: "First(array) \u2192 element",
17907
+ description: "Returns first element of array",
17908
+ fn: (arr) => Array.isArray(arr) ? arr[0] ?? null : null
17909
+ },
17910
+ Last: {
17911
+ name: "Last",
17912
+ signature: "Last(array) \u2192 element",
17913
+ description: "Returns last element of array",
17914
+ fn: (arr) => Array.isArray(arr) ? arr[arr.length - 1] ?? null : null
17915
+ },
17916
+ Sum: {
17917
+ name: "Sum",
17918
+ signature: "Sum(numbers[]) \u2192 number",
17919
+ description: "Sum of numeric array",
17920
+ fn: (arr) => Array.isArray(arr) ? arr.reduce((a, b) => a + toNumber2(b), 0) : 0
17921
+ },
17922
+ Avg: {
17923
+ name: "Avg",
17924
+ signature: "Avg(numbers[]) \u2192 number",
17925
+ description: "Average of numeric array",
17926
+ fn: (arr) => Array.isArray(arr) && arr.length ? arr.reduce((a, b) => a + toNumber2(b), 0) / arr.length : 0
17927
+ },
17928
+ Min: {
17929
+ name: "Min",
17930
+ signature: "Min(numbers[]) \u2192 number",
17931
+ description: "Minimum value in array",
17932
+ fn: (arr) => Array.isArray(arr) && arr.length ? arr.reduce((acc, b) => Math.min(acc, toNumber2(b)), toNumber2(arr[0])) : 0
17933
+ },
17934
+ Max: {
17935
+ name: "Max",
17936
+ signature: "Max(numbers[]) \u2192 number",
17937
+ description: "Maximum value in array",
17938
+ fn: (arr) => Array.isArray(arr) && arr.length ? arr.reduce((acc, b) => Math.max(acc, toNumber2(b)), toNumber2(arr[0])) : 0
17939
+ },
17940
+ Sort: {
17941
+ name: "Sort",
17942
+ signature: "Sort(array, field, direction?) \u2192 sorted array",
17943
+ description: 'Sort array by field. Direction: "asc" (default) or "desc"',
17944
+ fn: (arr, field, dir) => {
17945
+ if (!Array.isArray(arr)) return arr;
17946
+ const f = String(field ?? "");
17947
+ const desc = String(dir ?? "asc") === "desc";
17948
+ return [...arr].sort((a, b) => {
17949
+ const av = f ? resolveField(a, f) : a;
17950
+ const bv = f ? resolveField(b, f) : b;
17951
+ const aIsNumeric = typeof av === "number" || typeof av === "string" && !isNaN(Number(av)) && av !== "";
17952
+ const bIsNumeric = typeof bv === "number" || typeof bv === "string" && !isNaN(Number(bv)) && bv !== "";
17953
+ if (aIsNumeric && bIsNumeric) {
17954
+ const diff = toNumber2(av) - toNumber2(bv);
17955
+ return desc ? -diff : diff;
17956
+ }
17957
+ const cmp = String(av ?? "").localeCompare(String(bv ?? ""));
17958
+ return desc ? -cmp : cmp;
17959
+ });
17960
+ }
17961
+ },
17962
+ Filter: {
17963
+ name: "Filter",
17964
+ signature: 'Filter(array, field, operator: "==" | "!=" | ">" | "<" | ">=" | "<=" | "contains", value) \u2192 filtered array',
17965
+ description: "Filter array by field value",
17966
+ fn: (arr, field, op, value) => {
17967
+ if (!Array.isArray(arr)) return [];
17968
+ const f = String(field ?? "");
17969
+ const o = String(op ?? "==");
17970
+ return arr.filter((item) => {
17971
+ const v = f ? resolveField(item, f) : item;
17972
+ switch (o) {
17973
+ case "==":
17974
+ return v == value;
17975
+ case "!=":
17976
+ return v != value;
17977
+ case ">":
17978
+ return toNumber2(v) > toNumber2(value);
17979
+ case "<":
17980
+ return toNumber2(v) < toNumber2(value);
17981
+ case ">=":
17982
+ return toNumber2(v) >= toNumber2(value);
17983
+ case "<=":
17984
+ return toNumber2(v) <= toNumber2(value);
17985
+ case "contains":
17986
+ return String(v ?? "").includes(String(value ?? ""));
17987
+ default:
17988
+ return false;
17989
+ }
17990
+ });
17991
+ }
17992
+ },
17993
+ Round: {
17994
+ name: "Round",
17995
+ signature: "Round(number, decimals?) \u2192 number",
17996
+ description: "Round to N decimal places (default 0)",
17997
+ fn: (n, decimals) => {
17998
+ const num = toNumber2(n);
17999
+ const d = decimals != null ? toNumber2(decimals) : 0;
18000
+ const factor = Math.pow(10, d);
18001
+ return Math.round(num * factor) / factor;
18002
+ }
18003
+ },
18004
+ Abs: {
18005
+ name: "Abs",
18006
+ signature: "Abs(number) \u2192 number",
18007
+ description: "Absolute value",
18008
+ fn: (n) => Math.abs(toNumber2(n))
18009
+ },
18010
+ Floor: {
18011
+ name: "Floor",
18012
+ signature: "Floor(number) \u2192 number",
18013
+ description: "Round down to nearest integer",
18014
+ fn: (n) => Math.floor(toNumber2(n))
18015
+ },
18016
+ Ceil: {
18017
+ name: "Ceil",
18018
+ signature: "Ceil(number) \u2192 number",
18019
+ description: "Round up to nearest integer",
18020
+ fn: (n) => Math.ceil(toNumber2(n))
18021
+ }
18022
+ };
18023
+ var LAZY_BUILTINS = /* @__PURE__ */ new Set(["Each"]);
18024
+ var ACTION_STEPS = {
18025
+ Run: "run",
18026
+ ToAssistant: "continue_conversation",
18027
+ OpenUrl: "open_url",
18028
+ Set: "set",
18029
+ Reset: "reset"
18030
+ };
18031
+ var ACTION_NAMES = /* @__PURE__ */ new Set(["Action", ...Object.keys(ACTION_STEPS)]);
18032
+ var BUILTIN_NAMES = /* @__PURE__ */ new Set([
18033
+ ...Object.keys(BUILTINS),
18034
+ ...LAZY_BUILTINS,
18035
+ ...ACTION_NAMES
18036
+ ]);
18037
+ function isBuiltin(name) {
18038
+ return BUILTIN_NAMES.has(name);
18039
+ }
18040
+ var RESERVED_CALLS = { Query: "Query", Mutation: "Mutation" };
18041
+ function isReservedCall(name) {
18042
+ return name in RESERVED_CALLS;
18043
+ }
18044
+
18045
+ // src/genui/lang/parser/expressions.ts
18046
+ var PREC_TERNARY = 1;
18047
+ var PREC_OR = 2;
18048
+ var PREC_AND = 3;
18049
+ var PREC_EQ = 4;
18050
+ var PREC_CMP = 5;
18051
+ var PREC_ADD = 6;
18052
+ var PREC_MUL = 7;
18053
+ var PREC_UNARY = 8;
18054
+ var PREC_MEMBER = 9;
18055
+ function parseExpression(tokens) {
18056
+ let pos = 0;
18057
+ const cur = () => tokens[pos] ?? { t: 13 /* EOF */ };
18058
+ const adv = () => {
18059
+ const tok = cur();
18060
+ pos++;
18061
+ return tok;
18062
+ };
18063
+ const eat = (kind) => {
18064
+ if (cur().t === kind) pos++;
18065
+ };
18066
+ function getInfixPrec(tok) {
18067
+ switch (tok.t) {
18068
+ case 34 /* Question */:
18069
+ return PREC_TERNARY;
18070
+ case 32 /* Or */:
18071
+ return PREC_OR;
18072
+ case 31 /* And */:
18073
+ return PREC_AND;
18074
+ case 25 /* EqEq */:
18075
+ case 26 /* NotEq */:
18076
+ return PREC_EQ;
18077
+ case 27 /* Greater */:
18078
+ case 28 /* Less */:
18079
+ case 29 /* GreaterEq */:
18080
+ case 30 /* LessEq */:
18081
+ return PREC_CMP;
18082
+ case 20 /* Plus */:
18083
+ case 21 /* Minus */:
18084
+ return PREC_ADD;
18085
+ case 22 /* Star */:
18086
+ case 23 /* Slash */:
18087
+ case 24 /* Percent */:
18088
+ return PREC_MUL;
18089
+ case 19 /* Dot */:
18090
+ case 3 /* LBrack */:
18091
+ return PREC_MEMBER;
18092
+ default:
18093
+ return 0;
18094
+ }
18095
+ }
18096
+ function parseExpr(minPrec = 0) {
18097
+ let left = parsePrefix();
18098
+ while (getInfixPrec(cur()) > minPrec) {
18099
+ left = parseInfix(left);
18100
+ }
18101
+ return left;
18102
+ }
18103
+ function parsePrefix() {
18104
+ const tok = cur();
18105
+ if (tok.t === 14 /* Str */) {
18106
+ adv();
18107
+ return { k: "Str", v: tok.v };
18108
+ }
18109
+ if (tok.t === 15 /* Num */) {
18110
+ adv();
18111
+ return { k: "Num", v: tok.v };
18112
+ }
18113
+ if (tok.t === 10 /* True */) {
18114
+ adv();
18115
+ return { k: "Bool", v: true };
18116
+ }
18117
+ if (tok.t === 11 /* False */) {
18118
+ adv();
18119
+ return { k: "Bool", v: false };
18120
+ }
18121
+ if (tok.t === 12 /* Null */) {
18122
+ adv();
18123
+ return { k: "Null" };
18124
+ }
18125
+ if (tok.t === 3 /* LBrack */) return parseArr();
18126
+ if (tok.t === 5 /* LBrace */) return parseObj();
18127
+ if (tok.t === 18 /* StateVar */) {
18128
+ const name = tok.v;
18129
+ adv();
18130
+ if (cur().t === 9 /* Equals */) {
18131
+ adv();
18132
+ const value = parseExpr(0);
18133
+ return { k: "Assign", target: name, value };
18134
+ }
18135
+ return { k: "StateRef", n: name };
18136
+ }
18137
+ if (tok.t === 17 /* Type */) {
18138
+ const name = tok.v;
18139
+ if (tokens[pos + 1]?.t === 1 /* LParen */ && (!isBuiltin(name) || name === "Action"))
18140
+ return parseComp();
18141
+ adv();
18142
+ return { k: "Ref", n: name };
18143
+ }
18144
+ if (tok.t === 35 /* BuiltinCall */) {
18145
+ if (tokens[pos + 1]?.t === 1 /* LParen */) return parseComp();
18146
+ adv();
18147
+ return { k: "Ref", n: tok.v };
18148
+ }
18149
+ if (tok.t === 16 /* Ident */) {
18150
+ adv();
18151
+ return { k: "Ref", n: tok.v };
18152
+ }
18153
+ if (tok.t === 33 /* Not */) {
18154
+ adv();
18155
+ return { k: "UnaryOp", op: "!", operand: parseExpr(PREC_UNARY) };
18156
+ }
18157
+ if (tok.t === 21 /* Minus */) {
18158
+ adv();
18159
+ return { k: "UnaryOp", op: "-", operand: parseExpr(PREC_UNARY) };
18160
+ }
18161
+ if (tok.t === 1 /* LParen */) {
18162
+ adv();
18163
+ const inner = parseExpr(0);
18164
+ eat(2 /* RParen */);
18165
+ return inner;
18166
+ }
18167
+ adv();
18168
+ return { k: "Null" };
18169
+ }
18170
+ function parseInfix(left) {
18171
+ const tok = cur();
18172
+ if (tok.t === 20 /* Plus */) {
18173
+ adv();
18174
+ return { k: "BinOp", op: "+", left, right: parseExpr(PREC_ADD) };
18175
+ }
18176
+ if (tok.t === 21 /* Minus */) {
18177
+ adv();
18178
+ return { k: "BinOp", op: "-", left, right: parseExpr(PREC_ADD) };
18179
+ }
18180
+ if (tok.t === 22 /* Star */) {
18181
+ adv();
18182
+ return { k: "BinOp", op: "*", left, right: parseExpr(PREC_MUL) };
18183
+ }
18184
+ if (tok.t === 23 /* Slash */) {
18185
+ adv();
18186
+ return { k: "BinOp", op: "/", left, right: parseExpr(PREC_MUL) };
18187
+ }
18188
+ if (tok.t === 24 /* Percent */) {
18189
+ adv();
18190
+ return { k: "BinOp", op: "%", left, right: parseExpr(PREC_MUL) };
18191
+ }
18192
+ if (tok.t === 25 /* EqEq */) {
18193
+ adv();
18194
+ return { k: "BinOp", op: "==", left, right: parseExpr(PREC_EQ) };
18195
+ }
18196
+ if (tok.t === 26 /* NotEq */) {
18197
+ adv();
18198
+ return { k: "BinOp", op: "!=", left, right: parseExpr(PREC_EQ) };
18199
+ }
18200
+ if (tok.t === 27 /* Greater */) {
18201
+ adv();
18202
+ return { k: "BinOp", op: ">", left, right: parseExpr(PREC_CMP) };
18203
+ }
18204
+ if (tok.t === 28 /* Less */) {
18205
+ adv();
18206
+ return { k: "BinOp", op: "<", left, right: parseExpr(PREC_CMP) };
18207
+ }
18208
+ if (tok.t === 29 /* GreaterEq */) {
18209
+ adv();
18210
+ return { k: "BinOp", op: ">=", left, right: parseExpr(PREC_CMP) };
18211
+ }
18212
+ if (tok.t === 30 /* LessEq */) {
18213
+ adv();
18214
+ return { k: "BinOp", op: "<=", left, right: parseExpr(PREC_CMP) };
18215
+ }
18216
+ if (tok.t === 31 /* And */) {
18217
+ adv();
18218
+ return { k: "BinOp", op: "&&", left, right: parseExpr(PREC_AND) };
18219
+ }
18220
+ if (tok.t === 32 /* Or */) {
18221
+ adv();
18222
+ return { k: "BinOp", op: "||", left, right: parseExpr(PREC_OR) };
18223
+ }
18224
+ if (tok.t === 34 /* Question */) {
18225
+ adv();
18226
+ const then = parseExpr(0);
18227
+ eat(8 /* Colon */);
18228
+ const els = parseExpr(0);
18229
+ return { k: "Ternary", cond: left, then, else: els };
18230
+ }
18231
+ if (tok.t === 19 /* Dot */) {
18232
+ adv();
18233
+ const fieldTok = cur();
18234
+ const field = fieldTok.t === 16 /* Ident */ || fieldTok.t === 17 /* Type */ || fieldTok.t === 14 /* Str */ || fieldTok.t === 15 /* Num */ ? (adv(), String(fieldTok.v)) : fieldTok.t === 18 /* StateVar */ ? (adv(), fieldTok.v.replace(/^\$/, "")) : (adv(), "?");
18235
+ return { k: "Member", obj: left, field };
18236
+ }
18237
+ if (tok.t === 3 /* LBrack */) {
18238
+ adv();
18239
+ const index = parseExpr(0);
18240
+ eat(4 /* RBrack */);
18241
+ return { k: "Index", obj: left, index };
18242
+ }
18243
+ return left;
18244
+ }
18245
+ function parseComp() {
18246
+ const name = cur().v;
18247
+ adv();
18248
+ eat(1 /* LParen */);
18249
+ const args = [];
18250
+ while (cur().t !== 2 /* RParen */ && cur().t !== 13 /* EOF */) {
18251
+ args.push(parseExpr(0));
18252
+ if (cur().t === 7 /* Comma */) adv();
18253
+ }
18254
+ eat(2 /* RParen */);
18255
+ return { k: "Comp", name, args };
18256
+ }
18257
+ function parseArr() {
18258
+ adv();
18259
+ const els = [];
18260
+ while (cur().t !== 4 /* RBrack */ && cur().t !== 13 /* EOF */) {
18261
+ els.push(parseExpr(0));
18262
+ if (cur().t === 7 /* Comma */) adv();
18263
+ }
18264
+ eat(4 /* RBrack */);
18265
+ return { k: "Arr", els };
18266
+ }
18267
+ function parseObj() {
18268
+ adv();
18269
+ const entries = [];
18270
+ while (cur().t !== 6 /* RBrace */ && cur().t !== 13 /* EOF */) {
18271
+ const kt = cur();
18272
+ const key = kt.t === 16 /* Ident */ || kt.t === 14 /* Str */ || kt.t === 17 /* Type */ || kt.t === 15 /* Num */ ? (adv(), String(kt.v)) : kt.t === 18 /* StateVar */ ? (adv(), kt.v.replace(/^\$/, "")) : (adv(), "?");
18273
+ eat(8 /* Colon */);
18274
+ entries.push([key, parseExpr(0)]);
18275
+ if (cur().t === 7 /* Comma */) adv();
18276
+ }
18277
+ eat(6 /* RBrace */);
18278
+ return { k: "Obj", entries };
18279
+ }
18280
+ return parseExpr(0);
18281
+ }
18282
+
18283
+ // src/genui/lang/parser/lexer.ts
18284
+ function tokenize(src) {
18285
+ const tokens = [];
18286
+ let i = 0;
18287
+ const n = src.length;
18288
+ while (i < n) {
18289
+ while (i < n && (src[i] === " " || src[i] === " " || src[i] === "\r")) i++;
18290
+ if (i >= n) break;
18291
+ const c = src[i];
18292
+ if (c === "\n") {
18293
+ tokens.push({ t: 0 /* Newline */ });
18294
+ i++;
18295
+ continue;
18296
+ }
18297
+ if (c === "(") {
18298
+ tokens.push({ t: 1 /* LParen */ });
18299
+ i++;
18300
+ continue;
18301
+ }
18302
+ if (c === ")") {
18303
+ tokens.push({ t: 2 /* RParen */ });
18304
+ i++;
18305
+ continue;
18306
+ }
18307
+ if (c === "[") {
18308
+ tokens.push({ t: 3 /* LBrack */ });
18309
+ i++;
18310
+ continue;
18311
+ }
18312
+ if (c === "]") {
18313
+ tokens.push({ t: 4 /* RBrack */ });
18314
+ i++;
18315
+ continue;
18316
+ }
18317
+ if (c === "{") {
18318
+ tokens.push({ t: 5 /* LBrace */ });
18319
+ i++;
18320
+ continue;
18321
+ }
18322
+ if (c === "}") {
18323
+ tokens.push({ t: 6 /* RBrace */ });
18324
+ i++;
18325
+ continue;
18326
+ }
18327
+ if (c === ",") {
18328
+ tokens.push({ t: 7 /* Comma */ });
18329
+ i++;
18330
+ continue;
18331
+ }
18332
+ if (c === ":") {
18333
+ tokens.push({ t: 8 /* Colon */ });
18334
+ i++;
18335
+ continue;
18336
+ }
18337
+ if (c === "=") {
18338
+ if (i + 1 < n && src[i + 1] === "=") {
18339
+ tokens.push({ t: 25 /* EqEq */ });
18340
+ i += 2;
18341
+ } else {
18342
+ tokens.push({ t: 9 /* Equals */ });
18343
+ i++;
18344
+ }
18345
+ continue;
18346
+ }
18347
+ if (c === "!") {
18348
+ if (i + 1 < n && src[i + 1] === "=") {
18349
+ tokens.push({ t: 26 /* NotEq */ });
18350
+ i += 2;
18351
+ } else {
18352
+ tokens.push({ t: 33 /* Not */ });
18353
+ i++;
18354
+ }
18355
+ continue;
18356
+ }
18357
+ if (c === ">") {
18358
+ if (i + 1 < n && src[i + 1] === "=") {
18359
+ tokens.push({ t: 29 /* GreaterEq */ });
18360
+ i += 2;
18361
+ } else {
18362
+ tokens.push({ t: 27 /* Greater */ });
18363
+ i++;
18364
+ }
18365
+ continue;
18366
+ }
18367
+ if (c === "<") {
18368
+ if (i + 1 < n && src[i + 1] === "=") {
18369
+ tokens.push({ t: 30 /* LessEq */ });
18370
+ i += 2;
18371
+ } else {
18372
+ tokens.push({ t: 28 /* Less */ });
18373
+ i++;
18374
+ }
18375
+ continue;
18376
+ }
18377
+ if (c === "&") {
18378
+ if (i + 1 < n && src[i + 1] === "&") {
18379
+ tokens.push({ t: 31 /* And */ });
18380
+ i += 2;
18381
+ } else {
18382
+ tokens.push({ t: 31 /* And */ });
18383
+ i++;
18384
+ }
18385
+ continue;
18386
+ }
18387
+ if (c === "|") {
18388
+ if (i + 1 < n && src[i + 1] === "|") {
18389
+ tokens.push({ t: 32 /* Or */ });
18390
+ i += 2;
18391
+ } else {
18392
+ tokens.push({ t: 32 /* Or */ });
18393
+ i++;
18394
+ }
18395
+ continue;
18396
+ }
18397
+ if (c === ".") {
18398
+ tokens.push({ t: 19 /* Dot */ });
18399
+ i++;
18400
+ continue;
18401
+ }
18402
+ if (c === "?") {
18403
+ tokens.push({ t: 34 /* Question */ });
18404
+ i++;
18405
+ continue;
18406
+ }
18407
+ if (c === "+") {
18408
+ tokens.push({ t: 20 /* Plus */ });
18409
+ i++;
18410
+ continue;
18411
+ }
18412
+ if (c === "*") {
18413
+ tokens.push({ t: 22 /* Star */ });
18414
+ i++;
18415
+ continue;
18416
+ }
18417
+ if (c === "/") {
18418
+ tokens.push({ t: 23 /* Slash */ });
18419
+ i++;
18420
+ continue;
18421
+ }
18422
+ if (c === "%") {
18423
+ tokens.push({ t: 24 /* Percent */ });
18424
+ i++;
18425
+ continue;
18426
+ }
18427
+ if (c === '"') {
18428
+ const start = i;
18429
+ i++;
18430
+ let isClosed = false;
18431
+ while (i < n) {
18432
+ if (src[i] === "\\") {
18433
+ i += 2;
18434
+ } else if (src[i] === '"') {
18435
+ i++;
18436
+ isClosed = true;
18437
+ break;
18438
+ } else {
18439
+ i++;
18440
+ }
18441
+ }
18442
+ const rawString = src.slice(start, i);
18443
+ try {
18444
+ const validJsonString = isClosed ? rawString : rawString + '"';
18445
+ tokens.push({ t: 14 /* Str */, v: JSON.parse(validJsonString) });
18446
+ } catch {
18447
+ const stripped = rawString.replace(/^"|"$/g, "");
18448
+ tokens.push({ t: 14 /* Str */, v: stripped });
18449
+ }
18450
+ continue;
18451
+ }
18452
+ if (c === "'") {
18453
+ i++;
18454
+ let result = "";
18455
+ while (i < n) {
18456
+ if (src[i] === "\\") {
18457
+ i++;
18458
+ if (i < n) {
18459
+ const esc = src[i];
18460
+ if (esc === "'") result += "'";
18461
+ else if (esc === "\\") result += "\\";
18462
+ else if (esc === "n") result += "\n";
18463
+ else if (esc === "t") result += " ";
18464
+ else result += esc;
18465
+ i++;
18466
+ }
18467
+ } else if (src[i] === "'") {
18468
+ i++;
18469
+ break;
18470
+ } else {
18471
+ result += src[i];
18472
+ i++;
18473
+ }
18474
+ }
18475
+ tokens.push({ t: 14 /* Str */, v: result });
18476
+ continue;
18477
+ }
18478
+ if (c === "-") {
18479
+ const prev = tokens.length > 0 ? tokens[tokens.length - 1] : null;
18480
+ const afterValue = prev != null && (prev.t === 15 /* Num */ || prev.t === 14 /* Str */ || prev.t === 16 /* Ident */ || prev.t === 17 /* Type */ || prev.t === 2 /* RParen */ || prev.t === 4 /* RBrack */ || prev.t === 10 /* True */ || prev.t === 11 /* False */ || prev.t === 12 /* Null */ || prev.t === 18 /* StateVar */ || prev.t === 35 /* BuiltinCall */);
18481
+ if (!afterValue && i + 1 < n && src[i + 1] >= "0" && src[i + 1] <= "9") ; else {
18482
+ tokens.push({ t: 21 /* Minus */ });
18483
+ i++;
18484
+ continue;
18485
+ }
18486
+ }
18487
+ const isDigit = c >= "0" && c <= "9";
18488
+ const isNegDigit = c === "-" && i + 1 < n && src[i + 1] >= "0" && src[i + 1] <= "9";
18489
+ if (isDigit || isNegDigit) {
18490
+ const start = i;
18491
+ if (src[i] === "-") i++;
18492
+ while (i < n && src[i] >= "0" && src[i] <= "9") i++;
18493
+ if (i < n && src[i] === "." && i + 1 < n && src[i + 1] >= "0" && src[i + 1] <= "9") {
18494
+ i++;
18495
+ while (i < n && src[i] >= "0" && src[i] <= "9") i++;
18496
+ }
18497
+ if (i < n && (src[i] === "e" || src[i] === "E")) {
18498
+ i++;
18499
+ if (i < n && (src[i] === "+" || src[i] === "-")) i++;
18500
+ while (i < n && src[i] >= "0" && src[i] <= "9") i++;
18501
+ }
18502
+ tokens.push({ t: 15 /* Num */, v: +src.slice(start, i) });
18503
+ continue;
18504
+ }
18505
+ if (c === "$" && i + 1 < n && (src[i + 1] >= "a" && src[i + 1] <= "z" || src[i + 1] >= "A" && src[i + 1] <= "Z" || src[i + 1] === "_")) {
18506
+ const start = i;
18507
+ i++;
18508
+ while (i < n && (src[i] >= "a" && src[i] <= "z" || src[i] >= "A" && src[i] <= "Z" || src[i] >= "0" && src[i] <= "9" || src[i] === "_"))
18509
+ i++;
18510
+ tokens.push({ t: 18 /* StateVar */, v: src.slice(start, i) });
18511
+ continue;
18512
+ }
18513
+ const isAlpha = c >= "a" && c <= "z" || c >= "A" && c <= "Z" || c === "_";
18514
+ if (isAlpha) {
18515
+ const start = i;
18516
+ while (i < n && (src[i] >= "a" && src[i] <= "z" || src[i] >= "A" && src[i] <= "Z" || src[i] >= "0" && src[i] <= "9" || src[i] === "_"))
18517
+ i++;
18518
+ const word = src.slice(start, i);
18519
+ if (word === "true") {
18520
+ tokens.push({ t: 10 /* True */ });
18521
+ continue;
18522
+ }
18523
+ if (word === "false") {
18524
+ tokens.push({ t: 11 /* False */ });
18525
+ continue;
18526
+ }
18527
+ if (word === "null") {
18528
+ tokens.push({ t: 12 /* Null */ });
18529
+ continue;
18530
+ }
18531
+ const kind = c >= "A" && c <= "Z" ? 17 /* Type */ : 16 /* Ident */;
18532
+ tokens.push({ t: kind, v: word });
18533
+ continue;
18534
+ }
18535
+ if (c === "@" && i + 1 < n && (src[i + 1] >= "a" && src[i + 1] <= "z" || src[i + 1] >= "A" && src[i + 1] <= "Z" || src[i + 1] === "_")) {
18536
+ i++;
18537
+ const start = i;
18538
+ while (i < n && (src[i] >= "a" && src[i] <= "z" || src[i] >= "A" && src[i] <= "Z" || src[i] >= "0" && src[i] <= "9" || src[i] === "_"))
18539
+ i++;
18540
+ tokens.push({ t: 35 /* BuiltinCall */, v: src.slice(start, i) });
18541
+ continue;
18542
+ }
18543
+ i++;
18544
+ }
18545
+ tokens.push({ t: 13 /* EOF */ });
18546
+ return tokens;
18547
+ }
18548
+
18549
+ // src/genui/lang/parser/types.ts
18550
+ function isElementNode(value) {
18551
+ if (!value || typeof value !== "object" || Array.isArray(value)) return false;
18552
+ const node = value;
18553
+ return node.type === "element" && typeof node.typeName === "string" && typeof node.props === "object" && node.props !== null && typeof node.partial === "boolean";
18554
+ }
18555
+
18556
+ // src/genui/lang/parser/materialize.ts
18557
+ function containsDynamicValue(v) {
18558
+ if (v == null || typeof v !== "object") return false;
18559
+ if (isASTNode(v)) return true;
18560
+ if (Array.isArray(v)) return v.some(containsDynamicValue);
18561
+ if (isElementNode(v)) {
18562
+ return Object.values(v.props).some(containsDynamicValue);
18563
+ }
18564
+ const obj = v;
18565
+ return Object.values(obj).some(containsDynamicValue);
18566
+ }
18567
+ function resolveRef(name, ctx, mode) {
18568
+ if (ctx.visited.has(name)) {
18569
+ ctx.unres.push(name);
18570
+ return mode === "expr" ? { k: "Ph", n: name } : null;
18571
+ }
18572
+ if (!ctx.syms.has(name)) {
18573
+ ctx.unres.push(name);
18574
+ return mode === "expr" ? { k: "Ph", n: name } : null;
18575
+ }
18576
+ const target = ctx.syms.get(name);
18577
+ ctx.unreached?.delete(name);
18578
+ if (target.k === "Comp" && isReservedCall(target.name)) {
18579
+ const refType = target.name === RESERVED_CALLS.Mutation ? "mutation" : "query";
18580
+ return { k: "RuntimeRef", n: name, refType };
18581
+ }
18582
+ ctx.visited.add(name);
18583
+ const prevStatementId = ctx.currentStatementId;
18584
+ ctx.currentStatementId = name;
18585
+ try {
18586
+ const result = mode === "value" ? materializeValue(target, ctx) : materializeExpr(target, ctx);
18587
+ if (mode === "value" && isElementNode(result)) {
18588
+ result.statementId = name;
18589
+ }
18590
+ return result;
18591
+ } finally {
18592
+ ctx.currentStatementId = prevStatementId;
18593
+ ctx.visited.delete(name);
18594
+ }
18595
+ }
18596
+ function materializeLazyBuiltin(node, ctx, scopedRefs) {
18597
+ if (!LAZY_BUILTINS.has(node.name) || node.args.length < 3) return null;
18598
+ const varArg = node.args[1];
18599
+ const varName = varArg.k === "Ref" ? varArg.n : varArg.k === "Str" ? varArg.v : null;
18600
+ if (!varName) return null;
18601
+ const nextScopedRefs = new Set(scopedRefs);
18602
+ nextScopedRefs.add(varName);
18603
+ const recursedArgs = node.args.map(
18604
+ (a, i) => i === 1 ? a : materializeExprInternal(a, ctx, nextScopedRefs)
18605
+ );
18606
+ return { ...node, args: recursedArgs };
18607
+ }
18608
+ function materializeExprInternal(node, ctx, scopedRefs) {
18609
+ switch (node.k) {
18610
+ case "Ref":
18611
+ return scopedRefs.has(node.n) ? node : resolveRef(node.n, ctx, "expr");
18612
+ case "Ph":
18613
+ return node;
18614
+ case "Comp": {
18615
+ const lazy2 = materializeLazyBuiltin(node, ctx, scopedRefs);
18616
+ if (lazy2) return lazy2;
18617
+ const recursedArgs = node.args.map((a) => materializeExprInternal(a, ctx, scopedRefs));
18618
+ if (isBuiltin(node.name) || isReservedCall(node.name)) {
18619
+ return { ...node, args: recursedArgs };
18620
+ }
18621
+ const def = ctx.cat?.get(node.name);
18622
+ if (def) {
18623
+ const mappedProps = {};
18624
+ for (let i = 0; i < def.params.length && i < recursedArgs.length; i++) {
18625
+ mappedProps[def.params[i].name] = recursedArgs[i];
18626
+ }
18627
+ return { ...node, args: recursedArgs, mappedProps };
18628
+ }
18629
+ ctx.errors.push({
18630
+ code: "unknown-component",
18631
+ component: node.name,
18632
+ path: "",
18633
+ message: `Unknown component "${node.name}" \u2014 not found in catalog or builtins`,
18634
+ statementId: ctx.currentStatementId
18635
+ });
18636
+ return { ...node, args: recursedArgs };
18637
+ }
18638
+ case "Arr":
18639
+ return { ...node, els: node.els.map((e) => materializeExprInternal(e, ctx, scopedRefs)) };
18640
+ case "Obj":
18641
+ return {
18642
+ ...node,
18643
+ entries: node.entries.map(
18644
+ ([k, v]) => [k, materializeExprInternal(v, ctx, scopedRefs)]
18645
+ )
18646
+ };
18647
+ case "BinOp":
18648
+ return {
18649
+ ...node,
18650
+ left: materializeExprInternal(node.left, ctx, scopedRefs),
18651
+ right: materializeExprInternal(node.right, ctx, scopedRefs)
18652
+ };
18653
+ case "UnaryOp":
18654
+ return { ...node, operand: materializeExprInternal(node.operand, ctx, scopedRefs) };
18655
+ case "Ternary":
18656
+ return {
18657
+ ...node,
18658
+ cond: materializeExprInternal(node.cond, ctx, scopedRefs),
18659
+ then: materializeExprInternal(node.then, ctx, scopedRefs),
18660
+ else: materializeExprInternal(node.else, ctx, scopedRefs)
18661
+ };
18662
+ case "Member":
18663
+ return { ...node, obj: materializeExprInternal(node.obj, ctx, scopedRefs) };
18664
+ case "Index":
18665
+ return {
18666
+ ...node,
18667
+ obj: materializeExprInternal(node.obj, ctx, scopedRefs),
18668
+ index: materializeExprInternal(node.index, ctx, scopedRefs)
18669
+ };
18670
+ case "Assign":
18671
+ return { ...node, value: materializeExprInternal(node.value, ctx, scopedRefs) };
18672
+ // Literals, StateRef, RuntimeRef — pass through unchanged
18673
+ default:
18674
+ return node;
18675
+ }
18676
+ }
18677
+ function materializeExpr(node, ctx) {
18678
+ return materializeExprInternal(node, ctx, /* @__PURE__ */ new Set());
18679
+ }
18680
+ function materializeValue(node, ctx) {
18681
+ switch (node.k) {
18682
+ // ── Ref resolution ───────────────────────────────────────────────────
18683
+ case "Ref":
18684
+ return resolveRef(node.n, ctx, "value");
18685
+ // ── Literals → plain values ──────────────────────────────────────────
18686
+ case "Str":
18687
+ return node.v;
18688
+ case "Num":
18689
+ return node.v;
18690
+ case "Bool":
18691
+ return node.v;
18692
+ case "Null":
18693
+ return null;
18694
+ case "Ph":
18695
+ return null;
18696
+ // ── Collections ──────────────────────────────────────────────────────
18697
+ case "Arr": {
18698
+ const items = [];
18699
+ for (const e of node.els) {
18700
+ if (e.k === "Ph") continue;
18701
+ const value = materializeValue(e, ctx);
18702
+ if (value === null && (e.k === "Comp" || e.k === "Ref")) continue;
18703
+ items.push(value);
18704
+ }
18705
+ return items;
18706
+ }
18707
+ case "Obj": {
18708
+ const o = {};
18709
+ for (const [k, v] of node.entries) o[k] = materializeValue(v, ctx);
18710
+ return o;
18711
+ }
18712
+ // ── Component nodes ──────────────────────────────────────────────────
18713
+ case "Comp": {
18714
+ const { name, args } = node;
18715
+ if (isBuiltin(name)) {
18716
+ const lazy2 = materializeLazyBuiltin(node, ctx, /* @__PURE__ */ new Set());
18717
+ if (lazy2) return lazy2;
18718
+ return { ...node, args: args.map((a) => materializeExpr(a, ctx)) };
18719
+ }
18720
+ if (isReservedCall(name)) {
18721
+ ctx.errors.push({
18722
+ code: "inline-reserved",
18723
+ component: name,
18724
+ path: "",
18725
+ message: `${name}() must be declared as a top-level statement, not used inline as a value`,
18726
+ statementId: ctx.currentStatementId
18727
+ });
18728
+ return null;
18729
+ }
18730
+ const def = ctx.cat?.get(name);
18731
+ const props = {};
18732
+ if (def) {
18733
+ for (let i = 0; i < def.params.length && i < args.length; i++) {
18734
+ props[def.params[i].name] = materializeValue(args[i], ctx);
18735
+ }
18736
+ if (args.length > def.params.length) {
18737
+ const excessCount = args.length - def.params.length;
18738
+ ctx.errors.push({
18739
+ code: "excess-args",
18740
+ component: name,
18741
+ path: "",
18742
+ message: `${name} takes ${def.params.length} arg(s), got ${args.length} (${excessCount} excess dropped)`,
18743
+ statementId: ctx.currentStatementId
18744
+ });
18745
+ }
18746
+ const missingRequired = def.params.filter(
18747
+ (p) => p.required && (!(p.name in props) || props[p.name] === null)
18748
+ );
18749
+ if (missingRequired.length) {
18750
+ const stillInvalid = missingRequired.filter((p) => {
18751
+ if (p.defaultValue !== void 0) {
18752
+ props[p.name] = p.defaultValue;
18753
+ return false;
18754
+ }
18755
+ return true;
18756
+ });
18757
+ if (stillInvalid.length) {
18758
+ for (const p of stillInvalid) {
18759
+ const isNull = p.name in props;
18760
+ ctx.errors.push({
18761
+ code: isNull ? "null-required" : "missing-required",
18762
+ component: name,
18763
+ path: `/${p.name}`,
18764
+ message: isNull ? `required field "${p.name}" cannot be null` : `missing required field "${p.name}"`,
18765
+ statementId: ctx.currentStatementId
18766
+ });
18767
+ }
18768
+ return null;
18769
+ }
18770
+ }
18771
+ } else if (!isBuiltin(name) && !isReservedCall(name)) {
18772
+ ctx.errors.push({
18773
+ code: "unknown-component",
18774
+ component: name,
18775
+ path: "",
18776
+ message: `Unknown component "${name}" \u2014 not found in catalog or builtins`,
18777
+ statementId: ctx.currentStatementId
18778
+ });
18779
+ return null;
18780
+ }
18781
+ const hasDynamicProps = Object.values(props).some((v) => containsDynamicValue(v));
18782
+ return { type: "element", typeName: name, props, partial: ctx.partial, hasDynamicProps };
18783
+ }
18784
+ // ── Runtime expression nodes → preserve as ASTNode, normalize children ─
18785
+ default: {
18786
+ if (isRuntimeExpr(node)) {
18787
+ return materializeExpr(node, ctx);
18788
+ }
18789
+ return node;
18790
+ }
18791
+ }
18792
+ }
18793
+
18794
+ // src/genui/lang/parser/statements.ts
18795
+ function autoClose(input) {
18796
+ const stack = [];
18797
+ let inStr = false;
18798
+ let esc = false;
18799
+ for (let i = 0; i < input.length; i++) {
18800
+ const c = input[i];
18801
+ if (esc) {
18802
+ esc = false;
18803
+ continue;
18804
+ }
18805
+ if (c === "\\" && inStr) {
18806
+ esc = true;
18807
+ continue;
18808
+ }
18809
+ if (inStr) {
18810
+ if (c === inStr) inStr = false;
18811
+ continue;
18812
+ }
18813
+ if (c === '"' || c === "'") {
18814
+ inStr = c;
18815
+ continue;
18816
+ }
18817
+ if (c === "(" || c === "[" || c === "{") stack.push(c);
18818
+ else if (c === ")" && stack[stack.length - 1] === "(") stack.pop();
18819
+ else if (c === "]" && stack[stack.length - 1] === "[") stack.pop();
18820
+ else if (c === "}" && stack[stack.length - 1] === "{") stack.pop();
18821
+ }
18822
+ const wasIncomplete = !!inStr || stack.length > 0;
18823
+ if (!wasIncomplete) return { text: input, wasIncomplete: false };
18824
+ let out = input;
18825
+ if (inStr) {
18826
+ if (esc) out += "\\";
18827
+ out += inStr;
18828
+ }
18829
+ for (let j = stack.length - 1; j >= 0; j--)
18830
+ out += stack[j] === "(" ? ")" : stack[j] === "[" ? "]" : "}";
18831
+ return { text: out, wasIncomplete: true };
18832
+ }
18833
+ function split(tokens) {
18834
+ const stmts = [];
18835
+ let pos = 0;
18836
+ while (pos < tokens.length) {
18837
+ while (pos < tokens.length && tokens[pos].t === 0 /* Newline */) pos++;
18838
+ if (pos >= tokens.length || tokens[pos].t === 13 /* EOF */) break;
18839
+ const tok = tokens[pos];
18840
+ if (tok.t !== 16 /* Ident */ && tok.t !== 17 /* Type */ && tok.t !== 18 /* StateVar */) {
18841
+ while (pos < tokens.length && tokens[pos].t !== 0 /* Newline */ && tokens[pos].t !== 13 /* EOF */) pos++;
18842
+ continue;
18843
+ }
18844
+ const id = tok.v;
18845
+ const idTokenType = tok.t;
18846
+ pos++;
18847
+ if (pos >= tokens.length || tokens[pos].t !== 9 /* Equals */) {
18848
+ while (pos < tokens.length && tokens[pos].t !== 0 /* Newline */ && tokens[pos].t !== 13 /* EOF */) pos++;
18849
+ continue;
18850
+ }
18851
+ pos++;
18852
+ const expr = [];
18853
+ let depth = 0;
18854
+ let ternaryDepth = 0;
18855
+ while (pos < tokens.length && tokens[pos].t !== 13 /* EOF */) {
18856
+ const tt = tokens[pos].t;
18857
+ if (tt === 0 /* Newline */ && depth <= 0 && ternaryDepth <= 0) {
18858
+ let peek = pos + 1;
18859
+ while (peek < tokens.length && tokens[peek].t === 0 /* Newline */) peek++;
18860
+ const nextT = peek < tokens.length ? tokens[peek].t : 13 /* EOF */;
18861
+ if (nextT === 34 /* Question */ || nextT === 8 /* Colon */ && ternaryDepth > 0) {
18862
+ pos++;
18863
+ continue;
18864
+ }
18865
+ break;
18866
+ }
18867
+ if (tt === 0 /* Newline */) {
18868
+ pos++;
18869
+ continue;
18870
+ }
18871
+ if (tt === 1 /* LParen */ || tt === 3 /* LBrack */ || tt === 5 /* LBrace */) depth++;
18872
+ else if ((tt === 2 /* RParen */ || tt === 4 /* RBrack */ || tt === 6 /* RBrace */) && depth > 0) depth--;
18873
+ else if (tt === 34 /* Question */ && depth === 0) ternaryDepth++;
18874
+ else if (tt === 8 /* Colon */ && depth === 0 && ternaryDepth > 0) ternaryDepth--;
18875
+ expr.push(tokens[pos++]);
18876
+ }
18877
+ if (expr.length) stmts.push({ id, idTokenType, tokens: expr });
18878
+ }
18879
+ return stmts;
18880
+ }
18881
+
18882
+ // src/genui/lang/parser/parser.ts
18883
+ function emptyResult(incomplete = true) {
18884
+ return {
18885
+ root: null,
18886
+ meta: {
18887
+ incomplete,
18888
+ unresolved: [],
18889
+ orphaned: [],
18890
+ statementCount: 0,
18891
+ errors: []
18892
+ },
18893
+ stateDeclarations: {},
18894
+ queryStatements: [],
18895
+ mutationStatements: []
18896
+ };
18897
+ }
18898
+ function collectQueryDeps(node) {
18899
+ if (!isASTNode(node)) return [];
18900
+ const refs = /* @__PURE__ */ new Set();
18901
+ walkAST(node, (current) => {
18902
+ if (current.k === "StateRef") refs.add(current.n);
18903
+ });
18904
+ return [...refs];
18905
+ }
18906
+ function classifyStatement(raw, expr) {
18907
+ if (expr.k === "Comp" && expr.name === RESERVED_CALLS.Query) {
18908
+ const deps = collectQueryDeps(expr.args[1]);
18909
+ return {
18910
+ kind: "query",
18911
+ id: raw.id,
18912
+ call: { callee: RESERVED_CALLS.Query, args: expr.args },
18913
+ expr,
18914
+ deps: deps.length > 0 ? deps : void 0
18915
+ };
18916
+ }
18917
+ if (expr.k === "Comp" && expr.name === RESERVED_CALLS.Mutation) {
18918
+ return {
18919
+ kind: "mutation",
18920
+ id: raw.id,
18921
+ call: { callee: RESERVED_CALLS.Mutation, args: expr.args },
18922
+ expr
18923
+ };
18924
+ }
18925
+ if (raw.idTokenType === 18 /* StateVar */) {
18926
+ return { kind: "state", id: raw.id, init: expr };
18927
+ }
18928
+ return { kind: "value", id: raw.id, expr };
18929
+ }
18930
+ function extractStatements(stmts, ctx) {
18931
+ const stateDeclarations = {};
18932
+ const queryStatements = [];
18933
+ const mutationStatements = [];
18934
+ for (const stmt of stmts) {
18935
+ switch (stmt.kind) {
18936
+ case "state":
18937
+ stateDeclarations[stmt.id] = materializeValue(stmt.init, ctx);
18938
+ break;
18939
+ case "query":
18940
+ queryStatements.push({
18941
+ statementId: stmt.id,
18942
+ toolAST: stmt.call.args[0] ?? null,
18943
+ argsAST: stmt.call.args[1] ?? null,
18944
+ defaultsAST: stmt.call.args[2] ?? null,
18945
+ refreshAST: stmt.call.args[3] ?? null,
18946
+ deps: stmt.deps,
18947
+ complete: true
18948
+ });
18949
+ break;
18950
+ case "mutation":
18951
+ mutationStatements.push({
18952
+ statementId: stmt.id,
18953
+ toolAST: stmt.call.args[0] ?? null,
18954
+ argsAST: stmt.call.args[1] ?? null
18955
+ });
18956
+ break;
18957
+ }
18958
+ }
18959
+ for (const stmt of stmts) {
18960
+ const nodes = stmt.kind === "state" ? [stmt.init] : stmt.kind === "value" ? [stmt.expr] : stmt.kind === "query" || stmt.kind === "mutation" ? stmt.call.args : [];
18961
+ for (const node of nodes) {
18962
+ for (const dep of collectQueryDeps(node)) {
18963
+ if (!(dep in stateDeclarations)) {
18964
+ stateDeclarations[dep] = null;
18965
+ }
18966
+ }
18967
+ }
18968
+ }
18969
+ return { stateDeclarations, queryStatements, mutationStatements };
18970
+ }
18971
+ var DEFAULT_ROOT_STATEMENT_ID = "root";
18972
+ function isComponentStatement(stmt) {
18973
+ return stmt.kind === "value" && stmt.expr.k === "Comp" && !isBuiltin(stmt.expr.name) && stmt.expr.name !== RESERVED_CALLS.Query && stmt.expr.name !== RESERVED_CALLS.Mutation;
18974
+ }
18975
+ function pickEntryId(stmtMap, typedStmts, firstId, rootName) {
18976
+ if (stmtMap.has(DEFAULT_ROOT_STATEMENT_ID)) return DEFAULT_ROOT_STATEMENT_ID;
18977
+ if (rootName && stmtMap.has(rootName)) return rootName;
18978
+ const preferredComponent = rootName ? typedStmts.find((stmt) => isComponentStatement(stmt) && stmt.expr.name === rootName) : void 0;
18979
+ if (preferredComponent) return preferredComponent.id;
18980
+ const firstComponent = typedStmts.find(isComponentStatement);
18981
+ return firstComponent?.id ?? firstId;
18982
+ }
18983
+ function buildResult(stmtMap, typedStmts, firstId, wasIncomplete, stmtCount, cat, rootName) {
18984
+ const entryId = pickEntryId(stmtMap, typedStmts, firstId, rootName);
18985
+ if (!stmtMap.has(entryId)) return emptyResult(wasIncomplete);
18986
+ const syms = /* @__PURE__ */ new Map();
18987
+ for (const [id, stmt] of stmtMap) {
18988
+ syms.set(id, stmt.kind === "state" ? stmt.init : stmt.expr);
18989
+ }
18990
+ const unres = [];
18991
+ const errors = [];
18992
+ const unreached = /* @__PURE__ */ new Set();
18993
+ for (const [id, stmt] of stmtMap) {
18994
+ if (id === entryId) continue;
18995
+ if (stmt.kind === "state" || stmt.kind === "query" || stmt.kind === "mutation") continue;
18996
+ unreached.add(id);
18997
+ }
18998
+ const ctx = {
18999
+ syms,
19000
+ cat,
19001
+ errors,
19002
+ unres,
19003
+ visited: /* @__PURE__ */ new Set(),
19004
+ partial: wasIncomplete,
19005
+ currentStatementId: entryId,
19006
+ unreached
19007
+ };
19008
+ const materialized = materializeValue(syms.get(entryId), ctx);
19009
+ const root = isElementNode(materialized) ? materialized : null;
19010
+ if (root) root.statementId = entryId;
19011
+ const { stateDeclarations, queryStatements, mutationStatements } = extractStatements(
19012
+ typedStmts,
19013
+ ctx
19014
+ );
19015
+ const orphaned = [...unreached];
19016
+ return {
19017
+ root,
19018
+ meta: {
19019
+ incomplete: wasIncomplete,
19020
+ unresolved: unres,
19021
+ orphaned,
19022
+ statementCount: stmtCount,
19023
+ errors
19024
+ },
19025
+ stateDeclarations,
19026
+ queryStatements,
19027
+ mutationStatements
19028
+ };
19029
+ }
19030
+ function skipString(input, start) {
19031
+ if (input[start] !== '"') return start;
19032
+ let i = start + 1;
19033
+ while (i < input.length) {
19034
+ const c = input[i];
19035
+ if (c === "\\") {
19036
+ i += 2;
19037
+ } else if (c === '"') {
19038
+ return i + 1;
19039
+ } else {
19040
+ i++;
19041
+ }
19042
+ }
19043
+ return i;
19044
+ }
19045
+ function stripFences(input) {
19046
+ const blocks = [];
19047
+ let i = 0;
19048
+ while (i < input.length) {
19049
+ let fenceStart = -1;
19050
+ while (i < input.length) {
19051
+ const nextI = skipString(input, i);
19052
+ if (nextI > i) {
19053
+ i = nextI;
19054
+ continue;
19055
+ }
19056
+ const c = input[i];
19057
+ if (c === "`" && i + 1 < input.length && input[i + 1] === "`" && i + 2 < input.length && input[i + 2] === "`") {
19058
+ fenceStart = i;
19059
+ break;
19060
+ }
19061
+ i++;
19062
+ }
19063
+ if (fenceStart === -1) break;
19064
+ let j = fenceStart + 3;
19065
+ while (j < input.length && input[j] !== "\n") j++;
19066
+ if (j >= input.length) {
19067
+ blocks.push(input.slice(fenceStart + 3).replace(/^[^\n]*\n?/, ""));
19068
+ i = input.length;
19069
+ break;
19070
+ }
19071
+ j++;
19072
+ let closePos = -1;
19073
+ let k = j;
19074
+ while (k < input.length) {
19075
+ const nextK = skipString(input, k);
19076
+ if (nextK > k) {
19077
+ k = nextK;
19078
+ continue;
19079
+ }
19080
+ const c = input[k];
19081
+ if (c === "`" && k + 1 < input.length && input[k + 1] === "`" && k + 2 < input.length && input[k + 2] === "`") {
19082
+ closePos = k;
19083
+ break;
19084
+ }
19085
+ k++;
19086
+ }
19087
+ if (closePos !== -1) {
19088
+ blocks.push(input.slice(j, closePos));
19089
+ i = closePos + 3;
19090
+ } else {
19091
+ blocks.push(input.slice(j));
19092
+ i = input.length;
19093
+ }
19094
+ }
19095
+ if (blocks.length > 0) return blocks.join("\n");
19096
+ if (input.startsWith("```")) {
19097
+ let j = 3;
19098
+ while (j < input.length && input[j] !== "\n") j++;
19099
+ const start = j < input.length ? j + 1 : 3;
19100
+ const body = input.slice(start);
19101
+ const trailingFence = body.lastIndexOf("```");
19102
+ if (trailingFence !== -1) {
19103
+ return body.slice(0, trailingFence);
19104
+ }
19105
+ return body;
19106
+ }
19107
+ return input;
19108
+ }
19109
+ function stripComments(input) {
19110
+ let inStr = false;
19111
+ return input.split("\n").map((line) => {
19112
+ for (let i = 0; i < line.length; i++) {
19113
+ const c = line[i];
19114
+ if (inStr) {
19115
+ if (c === "\\" && i + 1 < line.length) {
19116
+ i++;
19117
+ continue;
19118
+ }
19119
+ if (c === inStr) inStr = false;
19120
+ continue;
19121
+ }
19122
+ if (c === '"' || c === "'") {
19123
+ inStr = c;
19124
+ continue;
19125
+ }
19126
+ if (c === "/" && line[i + 1] === "/") {
19127
+ return line.substring(0, i).trimEnd();
19128
+ }
19129
+ if (c === "#") {
19130
+ return line.substring(0, i).trimEnd();
19131
+ }
19132
+ }
19133
+ return line;
19134
+ }).join("\n");
19135
+ }
19136
+ function preprocess(input) {
19137
+ return stripComments(stripFences(input.trim())).trim();
19138
+ }
19139
+ function parse(input, cat, rootName) {
19140
+ const trimmed = preprocess(input);
19141
+ if (!trimmed) return emptyResult();
19142
+ const { text, wasIncomplete } = autoClose(trimmed);
19143
+ const stmts = split(tokenize(text));
19144
+ if (!stmts.length) return emptyResult(wasIncomplete);
19145
+ const stmtMap = /* @__PURE__ */ new Map();
19146
+ let firstId = "";
19147
+ for (const s of stmts) {
19148
+ const expr = parseExpression(s.tokens);
19149
+ const stmt = classifyStatement(s, expr);
19150
+ stmtMap.set(s.id, stmt);
19151
+ if (!firstId) firstId = s.id;
19152
+ }
19153
+ const typedStmts = [...stmtMap.values()];
19154
+ return buildResult(stmtMap, typedStmts, firstId, wasIncomplete, stmtMap.size, cat, rootName);
19155
+ }
19156
+ function createStreamParser(cat, rootName) {
19157
+ let buf = "";
19158
+ let cleaned = "";
19159
+ let completedEnd = 0;
19160
+ const completedStmtMap = /* @__PURE__ */ new Map();
19161
+ let firstId = "";
19162
+ function addStmt(text) {
19163
+ const t = text.trim();
19164
+ if (!t) return;
19165
+ for (const s of split(tokenize(t))) {
19166
+ const expr = parseExpression(s.tokens);
19167
+ const stmt = classifyStatement(s, expr);
19168
+ completedStmtMap.set(s.id, stmt);
19169
+ if (!firstId) firstId = s.id;
19170
+ }
19171
+ }
19172
+ function refreshCleaned() {
19173
+ const next = preprocess(buf);
19174
+ if (!next.startsWith(cleaned.slice(0, completedEnd))) {
19175
+ completedEnd = 0;
19176
+ completedStmtMap.clear();
19177
+ firstId = "";
19178
+ }
19179
+ cleaned = next;
19180
+ }
19181
+ function scanNewCompleted() {
19182
+ let depth = 0, ternaryDepth = 0, inStr = false, esc = false;
19183
+ let stmtStart = completedEnd;
19184
+ for (let i = completedEnd; i < cleaned.length; i++) {
19185
+ const c = cleaned[i];
19186
+ if (esc) {
19187
+ esc = false;
19188
+ continue;
19189
+ }
19190
+ if (c === "\\" && inStr) {
19191
+ esc = true;
19192
+ continue;
19193
+ }
19194
+ if (inStr) {
19195
+ if (c === inStr) inStr = false;
19196
+ continue;
19197
+ }
19198
+ if (c === '"' || c === "'") {
19199
+ inStr = c;
19200
+ continue;
19201
+ }
19202
+ if (c === "(" || c === "[" || c === "{") depth++;
19203
+ else if (c === ")" || c === "]" || c === "}") depth = Math.max(0, depth - 1);
19204
+ else if (c === "?" && depth === 0) ternaryDepth++;
19205
+ else if (c === ":" && depth === 0 && ternaryDepth > 0) ternaryDepth--;
19206
+ else if (c === "\n" && depth <= 0 && ternaryDepth <= 0) {
19207
+ let peek = i + 1;
19208
+ while (peek < cleaned.length && (cleaned[peek] === " " || cleaned[peek] === " " || cleaned[peek] === "\r" || cleaned[peek] === "\n"))
19209
+ peek++;
19210
+ if (peek < cleaned.length && (cleaned[peek] === "?" || cleaned[peek] === ":" && ternaryDepth > 0)) {
19211
+ continue;
19212
+ }
19213
+ const t = cleaned.slice(stmtStart, i).trim();
19214
+ if (t) addStmt(t);
19215
+ stmtStart = i + 1;
19216
+ completedEnd = i + 1;
19217
+ }
19218
+ }
19219
+ return stmtStart;
19220
+ }
19221
+ function currentResult() {
19222
+ refreshCleaned();
19223
+ const pendingStart = scanNewCompleted();
19224
+ const pendingText = cleaned.slice(pendingStart).trim();
19225
+ if (!pendingText) {
19226
+ if (completedStmtMap.size === 0) return emptyResult();
19227
+ return buildResult(
19228
+ completedStmtMap,
19229
+ [...completedStmtMap.values()],
19230
+ firstId,
19231
+ false,
19232
+ completedStmtMap.size,
19233
+ cat,
19234
+ rootName
19235
+ );
19236
+ }
19237
+ const { text: closed, wasIncomplete } = autoClose(pendingText);
19238
+ const stmts = split(tokenize(closed));
19239
+ if (!stmts.length) {
19240
+ if (completedStmtMap.size === 0) return emptyResult(wasIncomplete);
19241
+ return buildResult(
19242
+ completedStmtMap,
19243
+ [...completedStmtMap.values()],
19244
+ firstId,
19245
+ wasIncomplete,
19246
+ completedStmtMap.size,
19247
+ cat,
19248
+ rootName
19249
+ );
19250
+ }
19251
+ const allStmtMap = new Map(completedStmtMap);
19252
+ for (const s of stmts) {
19253
+ const expr = parseExpression(s.tokens);
19254
+ const stmt = classifyStatement(s, expr);
19255
+ allStmtMap.set(s.id, stmt);
19256
+ }
19257
+ const allTypedStmts = [...allStmtMap.values()];
19258
+ const fid = firstId || stmts[0].id;
19259
+ return buildResult(
19260
+ allStmtMap,
19261
+ allTypedStmts,
19262
+ fid,
19263
+ wasIncomplete,
19264
+ allStmtMap.size,
19265
+ cat,
19266
+ rootName
19267
+ );
19268
+ }
19269
+ function reset() {
19270
+ buf = "";
19271
+ cleaned = "";
19272
+ completedEnd = 0;
19273
+ completedStmtMap.clear();
19274
+ firstId = "";
19275
+ }
19276
+ return {
19277
+ push(chunk) {
19278
+ buf += chunk;
19279
+ return currentResult();
19280
+ },
19281
+ set(fullText) {
19282
+ if (fullText.length < buf.length || !fullText.startsWith(buf)) {
19283
+ reset();
19284
+ }
19285
+ const delta = fullText.slice(buf.length);
19286
+ if (delta) buf += delta;
19287
+ return currentResult();
19288
+ },
19289
+ getResult: currentResult
19290
+ };
19291
+ }
19292
+ function useGenUIProgram(text, catalog) {
19293
+ const parserRef = React14.useRef(
19294
+ null
19295
+ );
19296
+ if (!parserRef.current || parserRef.current.catalog !== catalog) {
19297
+ parserRef.current = { catalog, parser: createStreamParser(catalog, "root") };
19298
+ }
19299
+ const lastGoodRef = React14.useRef(null);
19300
+ return React14.useMemo(() => {
19301
+ const result = parserRef.current.parser.set(text);
19302
+ if (result.root) {
19303
+ lastGoodRef.current = result.root;
19304
+ }
19305
+ return { result, root: result.root ?? lastGoodRef.current };
19306
+ }, [text, catalog]);
19307
+ }
19308
+ var noop = () => {
19309
+ };
19310
+ function childKey(value, index) {
19311
+ if (isElementNode(value) && value.statementId) return `s:${value.statementId}`;
19312
+ return `i:${index}`;
19313
+ }
19314
+ var RenderDepthContext = React14.createContext(0);
19315
+ var MAX_DEPTH = 64;
19316
+ var GenUINode = React14.memo(function GenUINode2({
19317
+ node,
19318
+ components,
19319
+ onAction,
19320
+ isStreaming,
19321
+ onTelemetry
19322
+ }) {
19323
+ const depth = React14.useContext(RenderDepthContext);
19324
+ if (depth > MAX_DEPTH) {
19325
+ onTelemetry?.({ kind: "dropped-node", typeName: node.typeName });
19326
+ return null;
19327
+ }
19328
+ const renderer = components[node.typeName];
19329
+ if (!renderer) {
19330
+ onTelemetry?.({ kind: "unknown-component", typeName: node.typeName });
19331
+ return null;
19332
+ }
19333
+ const render = (value) => {
19334
+ if (value == null) return null;
19335
+ if (isElementNode(value)) {
19336
+ return /* @__PURE__ */ jsxRuntime.jsx(RenderDepthContext.Provider, { value: depth + 1, children: /* @__PURE__ */ jsxRuntime.jsx(
19337
+ GenUINode2,
19338
+ {
19339
+ node: value,
19340
+ components,
19341
+ onAction,
19342
+ isStreaming,
19343
+ onTelemetry
19344
+ }
19345
+ ) });
19346
+ }
19347
+ if (Array.isArray(value)) {
19348
+ return value.map((item, index) => /* @__PURE__ */ jsxRuntime.jsx(React14__default.default.Fragment, { children: render(item) }, childKey(item, index)));
19349
+ }
19350
+ if (typeof value === "object" && isRuntimeExpr(value)) return null;
19351
+ if (typeof value === "string" || typeof value === "number") return String(value);
19352
+ return null;
19353
+ };
19354
+ return renderer({ node, ctx: { render, onAction, isStreaming } });
19355
+ });
19356
+ function GenUIView({
19357
+ root,
19358
+ components,
19359
+ onAction = noop,
19360
+ isStreaming = false,
19361
+ onTelemetry,
19362
+ placeholder = null
19363
+ }) {
19364
+ if (!root) return placeholder;
19365
+ return /* @__PURE__ */ jsxRuntime.jsx(
19366
+ GenUINode,
19367
+ {
19368
+ node: root,
19369
+ components,
19370
+ onAction,
19371
+ isStreaming,
19372
+ onTelemetry
19373
+ }
19374
+ );
19375
+ }
19376
+
19377
+ // src/genui/registry.ts
19378
+ var CIMPLIFY_GENUI_REGISTRY = {
19379
+ version: "3",
19380
+ components: [
19381
+ {
19382
+ name: "Stack",
19383
+ description: 'Layout container. direction: "column" (default) or "row"; gap in points (default 12)',
19384
+ params: [{ name: "children", required: true }, { name: "direction" }, { name: "gap" }]
19385
+ },
19386
+ {
19387
+ name: "Card",
19388
+ description: "Elevated surface grouping related content, with an optional title",
19389
+ params: [{ name: "children", required: true }, { name: "title" }]
19390
+ },
19391
+ {
19392
+ name: "TextContent",
19393
+ description: 'Text block. size: "display", "heading", "title", "body" (default), "label", "caption"',
19394
+ params: [{ name: "text", required: true }, { name: "size" }]
19395
+ },
19396
+ {
19397
+ name: "Stat",
19398
+ description: "Compact stat: small label over a bold value, optional sub line and tone",
19399
+ params: [
19400
+ { name: "label", required: true },
19401
+ { name: "value", required: true },
19402
+ { name: "sub" },
19403
+ { name: "tone" }
19404
+ ]
19405
+ },
19406
+ {
19407
+ name: "Metric",
19408
+ description: "KPI card with label, value, optional percent change and SF Symbol icon",
19409
+ params: [
19410
+ { name: "label", required: true },
19411
+ { name: "value", required: true },
19412
+ { name: "change" },
19413
+ { name: "icon" }
19414
+ ]
19415
+ },
19416
+ {
19417
+ name: "KeyValue",
19418
+ description: "Label/value row; set mono true for numeric values",
19419
+ params: [
19420
+ { name: "label", required: true },
19421
+ { name: "value", required: true },
19422
+ { name: "mono" }
19423
+ ]
19424
+ },
19425
+ {
19426
+ name: "Badge",
19427
+ description: "Small status chip. tone: neutral, positive, info, indigo, violet, warning, danger",
19428
+ params: [{ name: "label", required: true }, { name: "tone" }]
19429
+ },
19430
+ {
19431
+ name: "Callout",
19432
+ description: "Inline alert with title, optional message. tone: info (default), success, warning, danger, accent, neutral",
19433
+ params: [{ name: "title", required: true }, { name: "message" }, { name: "tone" }]
19434
+ },
19435
+ {
19436
+ name: "Button",
19437
+ description: 'Action button. action: "action:name" string or Action([...]) steps. variant: primary, cta, secondary (default), ghost, destructive',
19438
+ params: [
19439
+ { name: "label", required: true },
19440
+ { name: "action", required: true },
19441
+ { name: "variant" }
19442
+ ]
19443
+ },
19444
+ {
19445
+ name: "Buttons",
19446
+ description: 'Row (default) or column of Buttons; direction: "row" or "column"',
19447
+ params: [{ name: "children", required: true }, { name: "direction" }]
19448
+ },
19449
+ {
19450
+ name: "Divider",
19451
+ description: "Hairline separator",
19452
+ params: []
19453
+ },
19454
+ {
19455
+ name: "Table",
19456
+ description: "Data table: headers is an array of strings, rows an array of cell arrays",
19457
+ params: [
19458
+ { name: "headers", required: true },
19459
+ { name: "rows", required: true }
19460
+ ]
19461
+ },
19462
+ {
19463
+ name: "BarChart",
19464
+ description: "Bar chart: labels (x axis strings) and values (numbers), optional title",
19465
+ params: [
19466
+ { name: "labels", required: true },
19467
+ { name: "values", required: true },
19468
+ { name: "title" }
19469
+ ]
19470
+ },
19471
+ {
19472
+ name: "LineChart",
19473
+ description: "Line chart with area fill: labels and values, optional title",
19474
+ params: [
19475
+ { name: "labels", required: true },
19476
+ { name: "values", required: true },
19477
+ { name: "title" }
19478
+ ]
19479
+ },
19480
+ {
19481
+ name: "Money",
19482
+ description: "Formatted money amount, never abbreviated. currency: ISO code (default USD)",
19483
+ params: [{ name: "amount", required: true }, { name: "currency" }]
19484
+ },
19485
+ {
19486
+ name: "Image",
19487
+ description: "Remote image by URL with optional caption",
19488
+ params: [{ name: "url", required: true }, { name: "caption" }]
19489
+ },
19490
+ {
19491
+ name: "ListRow",
19492
+ description: "Entity row for orders/customers/products/invoices: initials avatar (or SF Symbol tile via symbol), title + subtitle, trailing value and badge with tone",
19493
+ params: [
19494
+ { name: "title", required: true },
19495
+ { name: "subtitle" },
19496
+ { name: "value" },
19497
+ { name: "badge" },
19498
+ { name: "tone" },
19499
+ { name: "symbol" }
19500
+ ]
19501
+ },
19502
+ {
19503
+ name: "Timeline",
19504
+ description: "Event history (order journey, payment trail): events is an array of {title, detail?, time?, tone?} objects, newest first",
19505
+ params: [{ name: "events", required: true }]
19506
+ },
19507
+ {
19508
+ name: "Progress",
19509
+ description: "Amount toward a bound (goal, stock cover, capacity): numeric value and target (default 100), optional detail caption and tone",
19510
+ params: [
19511
+ { name: "label", required: true },
19512
+ { name: "value", required: true },
19513
+ { name: "target" },
19514
+ { name: "detail" },
19515
+ { name: "tone" }
19516
+ ]
19517
+ },
19518
+ {
19519
+ name: "DonutChart",
19520
+ description: "Share of a whole (sales by channel, revenue by category): labels + values, optional title and center display string (e.g. the formatted total)",
19521
+ params: [
19522
+ { name: "labels", required: true },
19523
+ { name: "values", required: true },
19524
+ { name: "title" },
19525
+ { name: "center" }
19526
+ ]
19527
+ },
19528
+ {
19529
+ name: "StatusTrack",
19530
+ description: "Stage tracker (order fulfilment, invoice lifecycle): steps are stage names, current is the active step name or index",
19531
+ params: [
19532
+ { name: "steps", required: true },
19533
+ { name: "current", required: true }
19534
+ ]
19535
+ },
19536
+ {
19537
+ name: "LineItems",
19538
+ description: "Order/quote/invoice money block: items is an array of {qty?, name, amount} with display-ready amount strings, adjustments an array of {label, amount, tone?}, total the anchored display string",
19539
+ params: [
19540
+ { name: "items", required: true },
19541
+ { name: "total", required: true },
19542
+ { name: "adjustments" }
19543
+ ]
19544
+ },
19545
+ {
19546
+ name: "Grid",
19547
+ description: "Tile layout for Stat/Metric children, columns default 2",
19548
+ params: [{ name: "children", required: true }, { name: "columns" }]
19549
+ },
19550
+ {
19551
+ name: "Banner",
19552
+ description: "Hero-stat moment on a tonal wash: big value, label, optional percent delta. tone: positive (default), warning, danger, info, neutral",
19553
+ params: [
19554
+ { name: "value", required: true },
19555
+ { name: "label", required: true },
19556
+ { name: "delta" },
19557
+ { name: "tone" }
19558
+ ]
19559
+ },
19560
+ {
19561
+ name: "EmptyState",
19562
+ description: "Nothing-to-show state with an SF Symbol and optional message",
19563
+ params: [{ name: "title", required: true }, { name: "message" }, { name: "symbol" }]
19564
+ }
19565
+ ]
19566
+ };
19567
+
19568
+ // src/genui/manifest.ts
19569
+ function genUIManifest(registry) {
19570
+ return {
19571
+ version: registry.version,
19572
+ components: registry.components.map((component) => ({
19573
+ name: component.name,
19574
+ description: component.description,
19575
+ params: component.params.map((param) => ({
19576
+ name: param.name,
19577
+ required: param.required ?? false
19578
+ }))
19579
+ }))
19580
+ };
19581
+ }
19582
+ function encodeString(value) {
19583
+ return JSON.stringify(value).replace(/\//g, "\\/");
19584
+ }
19585
+ function canonicalize(value) {
19586
+ if (value === null) return "null";
19587
+ if (typeof value === "string") return encodeString(value);
19588
+ if (typeof value === "number" || typeof value === "boolean") return String(value);
19589
+ if (Array.isArray(value)) return `[${value.map(canonicalize).join(",")}]`;
19590
+ const keys = Object.keys(value).sort();
19591
+ const entries = keys.map((key) => `${encodeString(key)}:${canonicalize(value[key])}`);
19592
+ return `{${entries.join(",")}}`;
19593
+ }
19594
+ function genUIManifestJson(manifest) {
19595
+ return canonicalize(manifest);
19596
+ }
19597
+ async function genUIFingerprint(manifest) {
19598
+ const bytes = new TextEncoder().encode(genUIManifestJson(manifest));
19599
+ const digest = await crypto.subtle.digest("SHA-256", bytes);
19600
+ return Array.from(new Uint8Array(digest)).map((byte) => byte.toString(16).padStart(2, "0")).join("").slice(0, 16);
19601
+ }
19602
+
19603
+ Object.defineProperty(exports, "getVariantDisplayName", {
19604
+ enumerable: true,
19605
+ get: function () { return chunkHKN3YG76_js.getVariantDisplayName; }
19606
+ });
19607
+ Object.defineProperty(exports, "messagePreview", {
19608
+ enumerable: true,
19609
+ get: function () { return chunkXREIZ56K_js.messagePreview; }
19610
+ });
19611
+ Object.defineProperty(exports, "splitGenUIContent", {
17242
19612
  enumerable: true,
17243
- get: function () { return chunkT7Z25EY4_js.getVariantDisplayName; }
19613
+ get: function () { return chunkXREIZ56K_js.splitGenUIContent; }
17244
19614
  });
17245
19615
  exports.AccommodationCard = AccommodationCard;
17246
19616
  exports.AccountContent = AccountContent;
@@ -17261,6 +19631,7 @@ exports.AccountSubscriptionsPage = AccountSubscriptionsPage;
17261
19631
  exports.Ad = Ad;
17262
19632
  exports.AdProvider = AdProvider;
17263
19633
  exports.AddOnSelector = AddOnSelector;
19634
+ exports.AddressAutocomplete = AddressAutocomplete;
17264
19635
  exports.AddressElement = AddressElement;
17265
19636
  exports.AuthElement = AuthElement;
17266
19637
  exports.AvailabilityBadge = AvailabilityBadge;
@@ -17276,6 +19647,7 @@ exports.BundleProductCard = BundleProductCard;
17276
19647
  exports.BundleProductLayout = BundleProductLayout;
17277
19648
  exports.BundleSelector = BundleSelector;
17278
19649
  exports.Button = Button;
19650
+ exports.CIMPLIFY_GENUI_REGISTRY = CIMPLIFY_GENUI_REGISTRY;
17279
19651
  exports.CardImage = CardImage;
17280
19652
  exports.CardShell = CardShell;
17281
19653
  exports.CardVariant = CardVariant;
@@ -17289,6 +19661,7 @@ exports.CataloguePage = CataloguePage;
17289
19661
  exports.CatalogueTemplate = CatalogueTemplate;
17290
19662
  exports.CategoryFilter = CategoryFilter;
17291
19663
  exports.CategoryGrid = CategoryGrid;
19664
+ exports.ChatMarkdown = ChatMarkdown;
17292
19665
  exports.ChatWidget = ChatWidget;
17293
19666
  exports.CheckIcon = CheckIcon;
17294
19667
  exports.CheckoutPage = CheckoutPage;
@@ -17334,6 +19707,7 @@ exports.FeaturedCollectionLayout = FeaturedCollectionLayout;
17334
19707
  exports.FilterTabs = FilterTabs;
17335
19708
  exports.FoodProductCard = FoodProductCard;
17336
19709
  exports.FoodProductLayout = FoodProductLayout;
19710
+ exports.GenUIView = GenUIView;
17337
19711
  exports.HappeningCard = HappeningCard;
17338
19712
  exports.IdentityIcon = IdentityIcon;
17339
19713
  exports.IdentityOfframp = IdentityOfframp;
@@ -17419,9 +19793,19 @@ exports.WelcomeCard = WelcomeCard;
17419
19793
  exports.WholesaleProductCard = WholesaleProductCard;
17420
19794
  exports.WholesaleProductLayout = WholesaleProductLayout;
17421
19795
  exports.WishlistButton = WishlistButton;
19796
+ exports.clearOrdersCache = clearOrdersCache;
17422
19797
  exports.cn = cn;
19798
+ exports.createPlacesSessionToken = createPlacesSessionToken;
17423
19799
  exports.defaultBoundaryKind = defaultBoundaryKind;
19800
+ exports.genUICatalog = genUICatalog;
19801
+ exports.genUIFingerprint = genUIFingerprint;
19802
+ exports.genUIManifest = genUIManifest;
19803
+ exports.genUIManifestJson = genUIManifestJson;
17424
19804
  exports.getBoundaryLabels = getBoundaryLabels;
19805
+ exports.isImageAttachment = isImageAttachment;
19806
+ exports.isTerminalOrderStatus = isTerminalOrderStatus;
19807
+ exports.parseGenUIProgram = parse;
19808
+ exports.partitionMessages = partitionMessages;
17425
19809
  exports.prefetchProduct = prefetchProduct;
17426
19810
  exports.prefetchProducts = prefetchProducts;
17427
19811
  exports.resolveBoundaryKind = resolveBoundaryKind;
@@ -17455,6 +19839,7 @@ exports.useDeliveryOptions = useDeliveryOptions;
17455
19839
  exports.useElements = useElements;
17456
19840
  exports.useElementsReady = useElementsReady;
17457
19841
  exports.useFxRate = useFxRate;
19842
+ exports.useGenUIProgram = useGenUIProgram;
17458
19843
  exports.useLocations = useLocations;
17459
19844
  exports.useOptionalCimplify = useOptionalCimplify;
17460
19845
  exports.useOrder = useOrder;