@coventia/softphone 0.3.0 → 0.5.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 (60) hide show
  1. package/dist/{call-CaPDWVk3.d.cts → call-FqcvDg1t.d.cts} +168 -12
  2. package/dist/{call-CaPDWVk3.d.ts → call-FqcvDg1t.d.ts} +168 -12
  3. package/dist/chunk-5E5OEXS2.js +14 -0
  4. package/dist/chunk-5E5OEXS2.js.map +1 -0
  5. package/dist/{chunk-EYEDPD7Y.js → chunk-6RR6G35E.js} +92 -17
  6. package/dist/chunk-6RR6G35E.js.map +1 -0
  7. package/dist/{chunk-2ZSOGGPO.js → chunk-FRXHEHQA.js} +15 -2
  8. package/dist/chunk-FRXHEHQA.js.map +1 -0
  9. package/dist/{chunk-UHNLEBBO.js → chunk-I3RSC52R.js} +28 -3
  10. package/dist/chunk-I3RSC52R.js.map +1 -0
  11. package/dist/chunk-M76SAXXM.js +13 -0
  12. package/dist/chunk-M76SAXXM.js.map +1 -0
  13. package/dist/{chunk-7R6LOOAI.js → chunk-N4PLEYFJ.js} +4 -2
  14. package/dist/chunk-N4PLEYFJ.js.map +1 -0
  15. package/dist/element/index.d.ts +198 -6
  16. package/dist/element/index.js +1160 -489
  17. package/dist/element/index.js.map +1 -1
  18. package/dist/{engine-BgBsIcEa.d.ts → engine-CsNRD7B7.d.ts} +1 -1
  19. package/dist/{engine-CU2rEo3g.d.cts → engine-CzOCrfY9.d.cts} +1 -1
  20. package/dist/index.cjs +675 -53
  21. package/dist/index.cjs.map +1 -1
  22. package/dist/index.d.cts +16 -6
  23. package/dist/index.d.ts +16 -6
  24. package/dist/index.js +453 -37
  25. package/dist/index.js.map +1 -1
  26. package/dist/react/index.cjs +26 -0
  27. package/dist/react/index.cjs.map +1 -1
  28. package/dist/react/index.d.cts +24 -5
  29. package/dist/react/index.d.ts +24 -5
  30. package/dist/react/index.js +2 -2
  31. package/dist/{softphone-DEt0TtQw.d.ts → softphone-By9sizlV.d.ts} +44 -3
  32. package/dist/{softphone-dZMkW8V1.d.cts → softphone-JZvJdzU3.d.cts} +44 -3
  33. package/dist/softphone.css +254 -23
  34. package/dist/{sources-M3GYMS6V.js → sources-QL2LRZVH.js} +93 -8
  35. package/dist/sources-QL2LRZVH.js.map +1 -0
  36. package/dist/testing/index.cjs +13 -0
  37. package/dist/testing/index.cjs.map +1 -1
  38. package/dist/testing/index.d.cts +2 -2
  39. package/dist/testing/index.d.ts +2 -2
  40. package/dist/testing/index.js +1 -1
  41. package/dist/ui/index.cjs +670 -20
  42. package/dist/ui/index.cjs.map +1 -1
  43. package/dist/ui/index.d.cts +2 -2
  44. package/dist/ui/index.d.ts +2 -2
  45. package/dist/ui/index.js +642 -22
  46. package/dist/ui/index.js.map +1 -1
  47. package/dist/zadarma/index.cjs +111 -14
  48. package/dist/zadarma/index.cjs.map +1 -1
  49. package/dist/zadarma/index.d.cts +5 -6
  50. package/dist/zadarma/index.d.ts +5 -6
  51. package/dist/zadarma/index.js +3 -2
  52. package/dist/zadarma-engine-TIUE7PZ7.js +6 -0
  53. package/dist/{zadarma-engine-L5DEFXUS.js.map → zadarma-engine-TIUE7PZ7.js.map} +1 -1
  54. package/package.json +1 -1
  55. package/dist/chunk-2ZSOGGPO.js.map +0 -1
  56. package/dist/chunk-7R6LOOAI.js.map +0 -1
  57. package/dist/chunk-EYEDPD7Y.js.map +0 -1
  58. package/dist/chunk-UHNLEBBO.js.map +0 -1
  59. package/dist/sources-M3GYMS6V.js.map +0 -1
  60. package/dist/zadarma-engine-L5DEFXUS.js +0 -5
package/dist/ui/index.js CHANGED
@@ -1,6 +1,7 @@
1
1
  import { isWebRtcSupported } from '../chunk-KAXEMJ7E.js';
2
- import { useSoftphone, useSoftphoneState, useCall, usePresence, useActiveCall, useIsLeader, useLastError, useDevices } from '../chunk-UHNLEBBO.js';
3
- import '../chunk-7R6LOOAI.js';
2
+ import { useSoftphone, useSoftphoneState, useCall, usePresence, useActiveCall, useIsLeader, useLastError, useDevices, useLine } from '../chunk-I3RSC52R.js';
3
+ import '../chunk-N4PLEYFJ.js';
4
+ import { isOutboundNumberRefused } from '../chunk-5E5OEXS2.js';
4
5
  import { forwardRef, createElement, useState, useRef, useLayoutEffect, useEffect, useCallback, useMemo, useSyncExternalStore, useId } from 'react';
5
6
  import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
6
7
 
@@ -224,6 +225,19 @@ var Pause = createLucideIcon("Pause", [
224
225
  ["rect", { x: "6", y: "4", width: "4", height: "16", rx: "1", key: "1okwgv" }]
225
226
  ]);
226
227
 
228
+ // ../../node_modules/lucide-react/dist/esm/icons/phone-forwarded.js
229
+ var PhoneForwarded = createLucideIcon("PhoneForwarded", [
230
+ ["polyline", { points: "18 2 22 6 18 10", key: "6vjanh" }],
231
+ ["line", { x1: "14", x2: "22", y1: "6", y2: "6", key: "1jsywh" }],
232
+ [
233
+ "path",
234
+ {
235
+ d: "M22 16.92v3a2 2 0 0 1-2.18 2 19.79 19.79 0 0 1-8.63-3.07 19.5 19.5 0 0 1-6-6 19.79 19.79 0 0 1-3.07-8.67A2 2 0 0 1 4.11 2h3a2 2 0 0 1 2 1.72 12.84 12.84 0 0 0 .7 2.81 2 2 0 0 1-.45 2.11L8.09 9.91a16 16 0 0 0 6 6l1.27-1.27a2 2 0 0 1 2.11-.45 12.84 12.84 0 0 0 2.81.7A2 2 0 0 1 22 16.92z",
236
+ key: "foiqr5"
237
+ }
238
+ ]
239
+ ]);
240
+
227
241
  // ../../node_modules/lucide-react/dist/esm/icons/phone-incoming.js
228
242
  var PhoneIncoming = createLucideIcon("PhoneIncoming", [
229
243
  ["polyline", { points: "16 2 16 8 22 8", key: "1ygljm" }],
@@ -367,6 +381,36 @@ var PACKED = "AC247 AD376 AE971 AF93 AG1 AI1 AL355 AM374 AO244 AR54 AS1 AT43 AU6
367
381
  var DIAL_CODES = new Map(
368
382
  PACKED.split(" ").map((token) => [token.slice(0, 2), Number(token.slice(2))])
369
383
  );
384
+ var REGIONS_BY_CODE = (() => {
385
+ const map = /* @__PURE__ */ new Map();
386
+ for (const [region, code] of DIAL_CODES) map.set(code, [...map.get(code) ?? [], region]);
387
+ return map;
388
+ })();
389
+ var MAIN_REGION = {
390
+ 1: "US",
391
+ 7: "RU",
392
+ 39: "IT",
393
+ 44: "GB",
394
+ 47: "NO",
395
+ 61: "AU",
396
+ 212: "MA",
397
+ 262: "RE",
398
+ 290: "SH",
399
+ 358: "FI",
400
+ 590: "GP",
401
+ 599: "CW"
402
+ };
403
+ function regionOfInternational(typed) {
404
+ const text = String(typed ?? "").trim();
405
+ if (!text.startsWith("+")) return null;
406
+ const digits = text.slice(1).replace(/\D/g, "");
407
+ for (let length = 1; length <= Math.min(3, digits.length); length++) {
408
+ const code = Number(digits.slice(0, length));
409
+ const regions = REGIONS_BY_CODE.get(code);
410
+ if (regions) return MAIN_REGION[code] ?? regions[0] ?? null;
411
+ }
412
+ return null;
413
+ }
370
414
  var DEFAULT_COUNTRY = "ES";
371
415
  var REGION_NAMES = (() => {
372
416
  try {
@@ -418,12 +462,16 @@ function CountryMark({ code }) {
418
462
  }
419
463
  function CountryPicker({
420
464
  value,
421
- onChange
465
+ onChange,
466
+ fromNumber = false
422
467
  }) {
423
468
  const [open, setOpen] = useState(false);
424
469
  const [query, setQuery] = useState("");
425
470
  const rootRef = useRef(null);
426
471
  const inputRef = useRef(null);
472
+ useEffect(() => {
473
+ if (fromNumber) setOpen(false);
474
+ }, [fromNumber]);
427
475
  const results = useMemo(() => {
428
476
  const q = query.trim().toLowerCase().replace(/^\+/, "");
429
477
  if (!q) return COUNTRIES;
@@ -452,6 +500,12 @@ function CountryPicker({
452
500
  else setQuery("");
453
501
  }, [open]);
454
502
  const dial = DIAL_CODES.get(value);
503
+ if (fromNumber) {
504
+ return /* @__PURE__ */ jsx("div", { className: "cv-cp", ref: rootRef, children: /* @__PURE__ */ jsxs("span", { className: "cv-cp-chip", "data-from-number": true, title: "El prefijo viene del n\xFAmero marcado", part: "country-chip", children: [
505
+ /* @__PURE__ */ jsx(CountryMark, { code: value || "+" }),
506
+ /* @__PURE__ */ jsx("span", { className: "cv-sr-only", children: dial ? `Prefijo del n\xFAmero marcado: ${countryName(value)} (+${dial})` : "Prefijo del n\xFAmero marcado" })
507
+ ] }) });
508
+ }
455
509
  return /* @__PURE__ */ jsxs("div", { className: "cv-cp", ref: rootRef, children: [
456
510
  /* @__PURE__ */ jsxs(
457
511
  "button",
@@ -1018,18 +1072,378 @@ function playRingback() {
1018
1072
  }
1019
1073
  };
1020
1074
  }
1075
+ var stores = /* @__PURE__ */ new WeakMap();
1076
+ function outboundNumberStore(softphone) {
1077
+ const source = softphone.sources?.outboundNumbers;
1078
+ if (!source) return null;
1079
+ let store = stores.get(softphone);
1080
+ if (!store) {
1081
+ store = createOutboundNumberStore(source);
1082
+ stores.set(softphone, store);
1083
+ }
1084
+ return store;
1085
+ }
1086
+ function createOutboundNumberStore(source) {
1087
+ let settings = null;
1088
+ let inFlight = null;
1089
+ const listeners = /* @__PURE__ */ new Set();
1090
+ const apply = (next) => {
1091
+ settings = next;
1092
+ for (const l of listeners) l();
1093
+ };
1094
+ return {
1095
+ getSettings: () => settings,
1096
+ refresh() {
1097
+ inFlight ??= (async () => {
1098
+ if (!settings && source.cached) {
1099
+ const early = await source.cached().catch(() => null);
1100
+ if (early && !settings) apply(early);
1101
+ }
1102
+ try {
1103
+ apply(await source.get());
1104
+ } catch (error) {
1105
+ console.warn("[softphone] outbound numbers could not be loaded; keeping the previous ones", error);
1106
+ } finally {
1107
+ inFlight = null;
1108
+ }
1109
+ })();
1110
+ return inFlight;
1111
+ },
1112
+ async setMine(id) {
1113
+ apply(await source.setMine(id));
1114
+ },
1115
+ subscribe(listener) {
1116
+ listeners.add(listener);
1117
+ return () => listeners.delete(listener);
1118
+ }
1119
+ };
1120
+ }
1121
+ var noSubscribe = () => () => {
1122
+ };
1123
+ var noSettings = () => null;
1124
+ function useOutboundNumbers(store) {
1125
+ const settings = useSyncExternalStore(store?.subscribe ?? noSubscribe, store?.getSettings ?? noSettings);
1126
+ const [failed, setFailed] = useState(false);
1127
+ const load = useCallback(() => {
1128
+ if (!store) return () => {
1129
+ };
1130
+ let live = true;
1131
+ setFailed(false);
1132
+ void store.refresh().then(() => {
1133
+ if (live) setFailed(store.getSettings() === null);
1134
+ });
1135
+ return () => {
1136
+ live = false;
1137
+ };
1138
+ }, [store]);
1139
+ useEffect(load, [load]);
1140
+ return { settings, failed, retry: load };
1141
+ }
1142
+ function useKnownOutboundNumbers(store) {
1143
+ return useSyncExternalStore(store?.subscribe ?? noSubscribe, store?.getSettings ?? noSettings);
1144
+ }
1145
+ function useCountdown(until) {
1146
+ const [, setTick] = useState(0);
1147
+ useEffect(() => {
1148
+ if (!until) return void 0;
1149
+ const timer = setInterval(() => {
1150
+ setTick((n) => n + 1);
1151
+ if (secondsLeft(until) === 0) clearInterval(timer);
1152
+ }, 1e3);
1153
+ return () => clearInterval(timer);
1154
+ }, [until]);
1155
+ return secondsLeft(until);
1156
+ }
1157
+ function secondsLeft(until) {
1158
+ return until ? Math.max(0, Math.ceil((until - Date.now()) / 1e3)) : 0;
1159
+ }
1160
+ function numberName(n, format = formatIntl) {
1161
+ if (!n) return "N\xFAmero de la organizaci\xF3n";
1162
+ return n.label || format(`+${n.number}`);
1163
+ }
1164
+ function effectiveNumber(settings) {
1165
+ return settings?.numbers.find((n) => n.id === settings.effective);
1166
+ }
1167
+
1168
+ // src/ui/outbound/outbound-change.ts
1169
+ var KIND_TAG = {
1170
+ mobile: { tag: "M\xF3vil", hint: "No usar para atenci\xF3n al cliente ni para llamadas comerciales no solicitadas." },
1171
+ commercial: { tag: "Comercial (400)", hint: "Solo para llamadas comerciales. No recibe llamadas." }
1172
+ };
1173
+ function noticeText(situation, seconds, current) {
1174
+ switch (situation) {
1175
+ case "platform":
1176
+ return `El cambio de n\xFAmero de salida no est\xE1 disponible temporalmente. Vuelve a intentarlo en ${seconds} s. Hasta entonces, tus llamadas seguir\xE1n saliendo desde ${current}.`;
1177
+ case "user":
1178
+ return `Acabas de cambiar el n\xFAmero de salida. Podr\xE1s cambiarlo de nuevo en ${seconds} s. Hasta entonces, tus llamadas saldr\xE1n desde ${current}.`;
1179
+ case "wait":
1180
+ return `Podr\xE1s cambiar el n\xFAmero de salida en ${seconds} s.`;
1181
+ case "pending":
1182
+ return seconds > 0 ? `Tu n\xFAmero de salida todav\xEDa no se ha aplicado. Podr\xE1s volver a elegirlo en ${seconds} s.` : "Tu n\xFAmero de salida todav\xEDa no se ha aplicado. Vuelve a elegirlo para aplicarlo.";
1183
+ }
1184
+ }
1185
+ function useOutboundNumberChange(store, settings, current) {
1186
+ const [busy, setBusy] = useState(false);
1187
+ const [notice, setNotice] = useState(null);
1188
+ const [waitUntil, setWaitUntil] = useState(null);
1189
+ useEffect(() => {
1190
+ const s = settings?.changeAvailableInSeconds ?? 0;
1191
+ setWaitUntil(s > 0 ? Date.now() + s * 1e3 : null);
1192
+ }, [settings]);
1193
+ const refusedUntil = notice?.kind === "refused" ? notice.until : null;
1194
+ const blockedFor = useCountdown(refusedUntil ?? waitUntil);
1195
+ useEffect(() => {
1196
+ if (notice?.kind === "refused" && blockedFor === 0) setNotice(null);
1197
+ }, [notice, blockedFor]);
1198
+ const locked = busy || blockedFor > 0;
1199
+ const choose = async (id) => {
1200
+ if (!store || !settings || locked) return false;
1201
+ if (id === settings.mine) return true;
1202
+ setBusy(true);
1203
+ setNotice(null);
1204
+ try {
1205
+ await store.setMine(id);
1206
+ return true;
1207
+ } catch (error) {
1208
+ setNotice(
1209
+ isOutboundNumberRefused(error) ? { kind: "refused", reason: error.refusal.reason, until: Date.now() + error.refusal.retryAfterSeconds * 1e3 } : { kind: "error", text: errorText(error) }
1210
+ );
1211
+ return false;
1212
+ } finally {
1213
+ setBusy(false);
1214
+ }
1215
+ };
1216
+ return {
1217
+ busy,
1218
+ locked,
1219
+ choose,
1220
+ /** What to say about the choice right now, if anything. */
1221
+ message: settings ? currentMessage(notice, settings.state, blockedFor, current) : null,
1222
+ /** The message is an error (the change failed), not information (wait, pending). */
1223
+ isError: notice?.kind === "error"
1224
+ };
1225
+ }
1226
+ function currentMessage(notice, state, blockedFor, current) {
1227
+ if (notice?.kind === "refused") return blockedFor > 0 ? noticeText(notice.reason, blockedFor, current) : null;
1228
+ if (notice?.kind === "error") return notice.text;
1229
+ if (state === "pending" || state === "failed") return noticeText("pending", blockedFor, current);
1230
+ return blockedFor > 0 ? noticeText("wait", blockedFor, current) : null;
1231
+ }
1232
+ function errorText(error) {
1233
+ const message = error instanceof Error ? error.message : "";
1234
+ return message && !/HTTP \d+/.test(message) ? message : "No se ha podido cambiar el n\xFAmero de salida. Int\xE9ntalo de nuevo.";
1235
+ }
1236
+ var CALLOUT_MS = 6e3;
1237
+ function OutboundNumberPicker({ onBusyChange }) {
1238
+ const softphone = useSoftphone();
1239
+ const store = outboundNumberStore(softphone);
1240
+ const { settings } = useOutboundNumbers(store);
1241
+ const formatIntl2 = useIntlFormatter();
1242
+ const current = settings ? numberName(effectiveNumber(settings), formatIntl2) : "";
1243
+ const { busy, locked, choose: apply, message, isError } = useOutboundNumberChange(store, settings, current);
1244
+ const [open, setOpen] = useState(false);
1245
+ const [applying, setApplying] = useState(void 0);
1246
+ const [callout, setCallout] = useState(false);
1247
+ const rootRef = useRef(null);
1248
+ const chipRef = useRef(null);
1249
+ const keys = useListKeyboard(open);
1250
+ useEffect(() => onBusyChange?.(busy), [busy, onBusyChange]);
1251
+ useEffect(() => {
1252
+ if (!message) setCallout(false);
1253
+ }, [message]);
1254
+ useEffect(() => {
1255
+ if (!callout) return void 0;
1256
+ const timer = setTimeout(() => setCallout(false), CALLOUT_MS);
1257
+ return () => clearTimeout(timer);
1258
+ }, [callout]);
1259
+ const floating = open || callout;
1260
+ useEffect(() => {
1261
+ if (!floating) return;
1262
+ const close = () => {
1263
+ setOpen(false);
1264
+ setCallout(false);
1265
+ };
1266
+ const root = rootRef.current?.getRootNode() ?? document;
1267
+ const onDown = (e) => {
1268
+ if (!rootRef.current?.contains(e.target)) close();
1269
+ };
1270
+ const onKey = (e) => {
1271
+ if (e.key !== "Escape") return;
1272
+ if (rootRef.current?.contains(e.target)) chipRef.current?.focus();
1273
+ close();
1274
+ };
1275
+ root.addEventListener("pointerdown", onDown);
1276
+ document.addEventListener("keydown", onKey);
1277
+ return () => {
1278
+ root.removeEventListener("pointerdown", onDown);
1279
+ document.removeEventListener("keydown", onKey);
1280
+ };
1281
+ }, [floating]);
1282
+ if (!store || !settings || settings.numbers.length < 2) return null;
1283
+ const orgDefault = settings.numbers.find((n) => n.id === settings.orgDefault);
1284
+ const applyingNumber = applying === null ? orgDefault : settings.numbers.find((n) => n.id === applying);
1285
+ const shown = applying === void 0 ? current : numberName(applyingNumber, formatIntl2);
1286
+ const choose = async (id) => {
1287
+ if (locked) return;
1288
+ setOpen(false);
1289
+ chipRef.current?.focus();
1290
+ if (id === settings.mine) return;
1291
+ setCallout(false);
1292
+ setApplying(id);
1293
+ const applied = await apply(id);
1294
+ setApplying(void 0);
1295
+ if (!applied) setCallout(true);
1296
+ };
1297
+ return /* @__PURE__ */ jsxs("div", { className: "cv-on", ref: rootRef, children: [
1298
+ /* @__PURE__ */ jsxs(
1299
+ "button",
1300
+ {
1301
+ ref: chipRef,
1302
+ type: "button",
1303
+ className: "cv-on-chip",
1304
+ onClick: () => {
1305
+ setCallout(false);
1306
+ setOpen((o) => !o);
1307
+ },
1308
+ "aria-haspopup": "listbox",
1309
+ "aria-expanded": open,
1310
+ "aria-label": `Llamar desde: ${shown}${busy ? " (aplicando)" : ""}. Cambiar n\xFAmero de salida`,
1311
+ "aria-busy": busy,
1312
+ part: "outbound-number-chip",
1313
+ children: [
1314
+ /* @__PURE__ */ jsx("span", { className: "cv-on-label", children: "Llamar desde:" }),
1315
+ busy ? /* @__PURE__ */ jsx(LoaderCircle, { size: 13, className: "cv-on-icon cv-on-spin", "aria-hidden": true }) : null,
1316
+ /* @__PURE__ */ jsx("span", { className: "cv-on-current", children: shown }),
1317
+ /* @__PURE__ */ jsx(ChevronDown, { size: 13, className: "cv-cp-caret", "data-open": open, "aria-hidden": true })
1318
+ ]
1319
+ }
1320
+ ),
1321
+ open ? /* @__PURE__ */ jsxs(
1322
+ "div",
1323
+ {
1324
+ className: "cv-cp-pop cv-on-pop",
1325
+ role: "dialog",
1326
+ "aria-label": "Llamar desde",
1327
+ part: "outbound-number-list",
1328
+ onBlur: (e) => {
1329
+ if (!e.currentTarget.contains(e.relatedTarget)) setOpen(false);
1330
+ },
1331
+ children: [
1332
+ /* @__PURE__ */ jsx("p", { className: "cv-on-title", children: "Llamar desde" }),
1333
+ /* @__PURE__ */ jsxs("div", { className: "cv-cp-list", role: "listbox", "aria-disabled": locked, ref: keys.listRef, onKeyDown: keys.onKeyDown, children: [
1334
+ /* @__PURE__ */ jsx(
1335
+ OptionRow,
1336
+ {
1337
+ selected: settings.mine === null,
1338
+ disabled: locked,
1339
+ title: "Por defecto de la organizaci\xF3n",
1340
+ sub: orgDefault ? numberName(orgDefault, formatIntl2) : "Sin n\xFAmero por defecto",
1341
+ onSelect: () => void choose(null)
1342
+ }
1343
+ ),
1344
+ settings.numbers.map((n) => /* @__PURE__ */ jsx(
1345
+ OptionRow,
1346
+ {
1347
+ selected: settings.mine === n.id,
1348
+ disabled: locked,
1349
+ title: numberName(n, formatIntl2),
1350
+ sub: n.label ? formatIntl2(`+${n.number}`) : null,
1351
+ tag: KIND_TAG[n.kind],
1352
+ onSelect: () => void choose(n.id)
1353
+ },
1354
+ n.id
1355
+ ))
1356
+ ] }),
1357
+ message ? /* @__PURE__ */ jsx("p", { className: "cv-on-notice", role: "status", "data-kind": isError ? "error" : "info", children: message }) : null
1358
+ ]
1359
+ }
1360
+ ) : null,
1361
+ callout && !open && message ? /* @__PURE__ */ jsx("p", { className: "cv-on-callout", role: "status", "data-kind": isError ? "error" : "info", part: "outbound-number-notice", children: message }) : null
1362
+ ] });
1363
+ }
1364
+ function OptionRow({
1365
+ selected,
1366
+ disabled,
1367
+ title,
1368
+ sub,
1369
+ tag,
1370
+ onSelect
1371
+ }) {
1372
+ return /* @__PURE__ */ jsxs(
1373
+ "button",
1374
+ {
1375
+ type: "button",
1376
+ role: "option",
1377
+ "aria-selected": selected,
1378
+ "aria-disabled": disabled,
1379
+ className: "cv-cp-item cv-on-item",
1380
+ tabIndex: -1,
1381
+ onClick: onSelect,
1382
+ children: [
1383
+ /* @__PURE__ */ jsxs("span", { className: "cv-on-text", children: [
1384
+ /* @__PURE__ */ jsxs("span", { className: "cv-on-line", children: [
1385
+ /* @__PURE__ */ jsx("span", { className: "cv-cp-name", children: title }),
1386
+ tag ? /* @__PURE__ */ jsx("span", { className: "cv-on-tag", children: tag.tag }) : null
1387
+ ] }),
1388
+ sub ? /* @__PURE__ */ jsx("span", { className: "cv-on-sub", children: sub }) : null,
1389
+ tag ? /* @__PURE__ */ jsx("span", { className: "cv-on-hint", children: tag.hint }) : null
1390
+ ] }),
1391
+ /* @__PURE__ */ jsx(Check, { size: 14, className: "cv-cp-check", "data-on": selected, "aria-hidden": true })
1392
+ ]
1393
+ }
1394
+ );
1395
+ }
1396
+ var optionsOf = (list) => list ? [...list.querySelectorAll('[role="option"]')] : [];
1397
+ var fold = (text) => text.normalize("NFD").replace(/\p{Diacritic}/gu, "").toLowerCase();
1398
+ var TYPEAHEAD_MS = 700;
1399
+ function useListKeyboard(open) {
1400
+ const listRef = useRef(null);
1401
+ const typed = useRef({ text: "", at: 0 });
1402
+ useEffect(() => {
1403
+ if (!open) return;
1404
+ const options = optionsOf(listRef.current);
1405
+ (options.find((o) => o.getAttribute("aria-selected") === "true") ?? options[0])?.focus();
1406
+ }, [open]);
1407
+ const onKeyDown = (e) => {
1408
+ const options = optionsOf(listRef.current);
1409
+ const at = options.indexOf(e.target);
1410
+ const move = {
1411
+ ArrowDown: Math.min(at + 1, options.length - 1),
1412
+ ArrowUp: Math.max(at - 1, 0),
1413
+ Home: 0,
1414
+ End: options.length - 1
1415
+ };
1416
+ let next = move[e.key];
1417
+ if (next === void 0 && e.key.length === 1 && !e.ctrlKey && !e.metaKey && !e.altKey) {
1418
+ const now = Date.now();
1419
+ const text = now - typed.current.at < TYPEAHEAD_MS ? typed.current.text + e.key : e.key;
1420
+ typed.current = { text, at: now };
1421
+ const wanted = fold(text);
1422
+ const match = options.findIndex((o) => fold(o.querySelector(".cv-cp-name")?.textContent ?? "").startsWith(wanted));
1423
+ next = match >= 0 ? match : void 0;
1424
+ }
1425
+ if (next === void 0) return;
1426
+ e.preventDefault();
1427
+ options[next]?.focus();
1428
+ };
1429
+ return { listRef, onKeyDown };
1430
+ }
1021
1431
  var NOT_DIALABLE = /* @__PURE__ */ new Set(["*", "#"]);
1432
+ var DISPLAY_SIZES = ["1.375rem", "1.25rem", "1.125rem", "1rem"];
1022
1433
  function Dialpad({ outcome = null, onIntent, keyTones = true } = {}) {
1023
1434
  const softphone = useSoftphone();
1024
1435
  const state = useSoftphoneState();
1025
1436
  const [value, setValue] = useState("");
1026
1437
  const [country, setCountry] = useState(DEFAULT_COUNTRY);
1438
+ const [changingNumber, setChangingNumber] = useState(false);
1027
1439
  const format = useNumberFormatter();
1028
1440
  const inputRef = useRef(null);
1029
1441
  const caretRef = useRef(null);
1030
1442
  const display = format(country, value);
1031
1443
  const target = toE164(country, value);
1032
- const canCall = state === "online" && target.length > 1;
1444
+ const canCall = state === "online" && target.length > 1 && !changingNumber;
1445
+ const typedInternational = value.trim().startsWith("+");
1446
+ const numberRegion = regionOfInternational(value);
1033
1447
  useLayoutEffect(() => {
1034
1448
  const caret = caretRef.current;
1035
1449
  if (caret === null || !inputRef.current) return;
@@ -1037,6 +1451,14 @@ function Dialpad({ outcome = null, onIntent, keyTones = true } = {}) {
1037
1451
  const pos = caretAfterDigits(display, caret);
1038
1452
  inputRef.current.setSelectionRange(pos, pos);
1039
1453
  }, [display]);
1454
+ useLayoutEffect(() => {
1455
+ const input = inputRef.current;
1456
+ if (!input) return;
1457
+ for (const size of DISPLAY_SIZES) {
1458
+ input.style.fontSize = size;
1459
+ if (input.scrollWidth <= input.clientWidth) break;
1460
+ }
1461
+ }, [display]);
1040
1462
  const append = (key) => {
1041
1463
  onIntent?.();
1042
1464
  setValue((v) => v + key);
@@ -1052,9 +1474,16 @@ function Dialpad({ outcome = null, onIntent, keyTones = true } = {}) {
1052
1474
  void softphone.call(target);
1053
1475
  };
1054
1476
  return /* @__PURE__ */ jsxs("div", { className: "cv-view", children: [
1055
- /* @__PURE__ */ jsx("div", { className: "cv-dial-prefix", children: /* @__PURE__ */ jsx(CountryPicker, { value: country, onChange: setCountry }) }),
1477
+ /* @__PURE__ */ jsx(OutboundNumberPicker, { onBusyChange: setChangingNumber }),
1056
1478
  /* @__PURE__ */ jsxs("div", { className: "cv-dial-head", children: [
1057
- /* @__PURE__ */ jsx("span", { className: "cv-dial-side", "aria-hidden": true }),
1479
+ /* @__PURE__ */ jsx(
1480
+ CountryPicker,
1481
+ {
1482
+ value: numberRegion ?? country,
1483
+ onChange: setCountry,
1484
+ fromNumber: typedInternational
1485
+ }
1486
+ ),
1058
1487
  /* @__PURE__ */ jsx(
1059
1488
  "input",
1060
1489
  {
@@ -1078,6 +1507,8 @@ function Dialpad({ outcome = null, onIntent, keyTones = true } = {}) {
1078
1507
  type: "button",
1079
1508
  className: "cv-dial-side",
1080
1509
  "data-hidden": value.length === 0,
1510
+ tabIndex: value.length === 0 ? -1 : void 0,
1511
+ "aria-hidden": value.length === 0 || void 0,
1081
1512
  onClick: backspace,
1082
1513
  "aria-label": "Borrar",
1083
1514
  children: /* @__PURE__ */ jsx(Delete, { size: 19 })
@@ -1251,6 +1682,22 @@ var ERROR_TEXT = {
1251
1682
  // thinks they dropped it will hang up on a client to "fix" it.
1252
1683
  "call/transfer-failed": { title: "No se pudo transferir. Sigues en la llamada" },
1253
1684
  "call/unsupported": { title: "Esta funci\xF3n no est\xE1 disponible" },
1685
+ // ── Line owner ──────────────────────────────────────────────────────────────
1686
+ /*
1687
+ Not call outcomes: they answer "Recibir aquí" on `LineGate`, which has ONE line for them where the
1688
+ explanation was, so it reads the pair as two sentences — `${title}. ${body}`. Split anyway, so the
1689
+ title stands alone wherever there is only room for a headline (a host's own toast), under the same
1690
+ one-line rules as every other non-fatal title. "Allí" because the gate has just said where: the
1691
+ other softphone, the one that has the calls.
1692
+ */
1693
+ "line/claim-refused": {
1694
+ title: "Est\xE1s en una llamada all\xED",
1695
+ body: "Podr\xE1s recibirlas aqu\xED cuando cuelgues."
1696
+ },
1697
+ "line/unavailable": {
1698
+ title: "No se ha podido cambiar",
1699
+ body: "Int\xE9ntalo de nuevo."
1700
+ },
1254
1701
  // ── Provider ────────────────────────────────────────────────────────────────
1255
1702
  "provider/provision-failed": {
1256
1703
  title: "No se pudo activar el tel\xE9fono",
@@ -1502,6 +1949,9 @@ function CallScreen({
1502
1949
  }) {
1503
1950
  const view = useCall(call);
1504
1951
  const softphone = useSoftphone();
1952
+ const outbound = useOutboundNumbers(call.direction === "outbound" ? outboundNumberStore(softphone) : null).settings;
1953
+ const formatIntl2 = useIntlFormatter();
1954
+ const fromName = outbound && outbound.numbers.length > 1 ? numberName(effectiveNumber(outbound), formatIntl2) : null;
1505
1955
  const [panel, setPanel] = useState(null);
1506
1956
  const showTransfer = panel === "transfer";
1507
1957
  const showKeypad = panel === "keypad";
@@ -1712,6 +2162,13 @@ function CallScreen({
1712
2162
  ] }),
1713
2163
  /* @__PURE__ */ jsx("div", { className: "cv-call-name", children: name }),
1714
2164
  showNumber ? /* @__PURE__ */ jsx("div", { className: "cv-call-number", children: number }) : null,
2165
+ fromName ? /* @__PURE__ */ jsxs("div", { className: "cv-call-from", children: [
2166
+ /* @__PURE__ */ jsx(PhoneOutgoing, { size: 11, "aria-hidden": true }),
2167
+ /* @__PURE__ */ jsxs("span", { children: [
2168
+ "Desde ",
2169
+ fromName
2170
+ ] })
2171
+ ] }) : null,
1715
2172
  s === "active" ? /* @__PURE__ */ jsxs("div", { className: "cv-timer", "aria-label": "Duraci\xF3n de la llamada", children: [
1716
2173
  /* @__PURE__ */ jsx("b", { children: mm }),
1717
2174
  /* @__PURE__ */ jsx("i", { children: ":" }),
@@ -2127,12 +2584,23 @@ function HandlerChain({ handlers }) {
2127
2584
  ] }, `${h.extension}-${i}`);
2128
2585
  }) });
2129
2586
  }
2587
+ function outboundFrom(entry, known, formatIntl2) {
2588
+ if (entry.direction !== "outbound" || !entry.from) return null;
2589
+ const number = formatIntl2(`+${entry.from}`);
2590
+ const label = known?.numbers.find((n) => n.number === entry.from)?.label;
2591
+ if (!label) return number;
2592
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
2593
+ label,
2594
+ /* @__PURE__ */ jsx("span", { className: "cv-detail-row-sub", children: number })
2595
+ ] });
2596
+ }
2130
2597
  function HistoryDetail({
2131
2598
  entry,
2132
2599
  onBack
2133
2600
  }) {
2134
2601
  const softphone = useSoftphone();
2135
2602
  const formatIntl2 = useIntlFormatter();
2603
+ const knownNumbers = useKnownOutboundNumbers(outboundNumberStore(softphone));
2136
2604
  const [detail, setDetail] = useState(null);
2137
2605
  const [loadingDetail, setLoadingDetail] = useState(false);
2138
2606
  useEffect(() => {
@@ -2148,6 +2616,7 @@ function HistoryDetail({
2148
2616
  }, [entry]);
2149
2617
  const formatted = formatIntl2(entry.number);
2150
2618
  const name = entry.name ?? formatted;
2619
+ const from = outboundFrom(entry, knownNumbers, formatIntl2);
2151
2620
  const handledBy = detail?.handledBy ?? entry.handledBy;
2152
2621
  const recordingUrls = detail?.recordingUrls ?? [];
2153
2622
  const loadingRecording = entry.recorded && loadingDetail && recordingUrls.length === 0;
@@ -2184,7 +2653,8 @@ function HistoryDetail({
2184
2653
  entry.durationSec > 0 ? /* @__PURE__ */ jsx(DetailRow, { icon: Timer, eyebrow: "Duraci\xF3n", text: formatDuration(entry.durationSec) }) : null,
2185
2654
  /* @__PURE__ */ jsx(DetailRow, { icon: Phone, eyebrow: "N\xFAmero", text: formatted }),
2186
2655
  handledBy.length > 0 ? /* @__PURE__ */ jsx(DetailRow, { icon: User, eyebrow: handlerLabel, text: /* @__PURE__ */ jsx(HandlerChain, { handlers: handledBy }) }) : null,
2187
- entry.direction === "inbound" && entry.did ? /* @__PURE__ */ jsx(DetailRow, { icon: Hash, eyebrow: "N\xFAmero marcado (DID)", text: formatIntl2(entry.did) }) : null
2656
+ entry.direction === "inbound" && entry.did ? /* @__PURE__ */ jsx(DetailRow, { icon: Hash, eyebrow: "N\xFAmero marcado (DID)", text: formatIntl2(entry.did) }) : null,
2657
+ from ? /* @__PURE__ */ jsx(DetailRow, { icon: PhoneOutgoing, eyebrow: "Llamada desde", text: from }) : null
2188
2658
  ] })
2189
2659
  ] }),
2190
2660
  /* @__PURE__ */ jsx("div", { className: "cv-detail-foot", children: /* @__PURE__ */ jsxs(
@@ -2379,14 +2849,14 @@ function HistoryTab({ source }) {
2379
2849
  }
2380
2850
 
2381
2851
  // src/ui/audio/ringtone-store.ts
2382
- var stores = /* @__PURE__ */ new WeakMap();
2852
+ var stores2 = /* @__PURE__ */ new WeakMap();
2383
2853
  function ringtoneStore(softphone) {
2384
2854
  const source = softphone.sources?.ringtones;
2385
2855
  if (!source) return null;
2386
- let store = stores.get(softphone);
2856
+ let store = stores2.get(softphone);
2387
2857
  if (!store) {
2388
2858
  store = createRingtoneStore(source);
2389
- stores.set(softphone, store);
2859
+ stores2.set(softphone, store);
2390
2860
  }
2391
2861
  return store;
2392
2862
  }
@@ -2512,17 +2982,19 @@ function ChoiceList({
2512
2982
  choices,
2513
2983
  value,
2514
2984
  onSelect,
2515
- onReselect
2985
+ onReselect,
2986
+ disabled = false
2516
2987
  }) {
2517
2988
  const name = useId();
2518
2989
  const pointer = useRef(false);
2519
- return /* @__PURE__ */ jsx("div", { className: "cv-choices", role: "radiogroup", "aria-label": label, children: choices.map((c) => {
2990
+ return /* @__PURE__ */ jsx("div", { className: "cv-choices", role: "radiogroup", "aria-label": label, "aria-disabled": disabled || void 0, children: choices.map((c) => {
2520
2991
  const checked = c.value === value;
2521
2992
  return /* @__PURE__ */ jsxs(
2522
2993
  "label",
2523
2994
  {
2524
2995
  className: "cv-choice",
2525
2996
  "data-checked": checked || void 0,
2997
+ "data-disabled": disabled || void 0,
2526
2998
  onPointerDown: () => {
2527
2999
  pointer.current = true;
2528
3000
  },
@@ -2538,19 +3010,23 @@ function ChoiceList({
2538
3010
  onChange: () => {
2539
3011
  const via = pointer.current ? "pointer" : "keyboard";
2540
3012
  pointer.current = false;
2541
- onSelect(c.value, via);
3013
+ if (!disabled) onSelect(c.value, via);
2542
3014
  },
2543
3015
  onClick: () => {
2544
3016
  if (c.value === value) {
2545
3017
  pointer.current = false;
2546
- onReselect?.(c.value);
3018
+ if (!disabled) onReselect?.(c.value);
2547
3019
  }
2548
3020
  }
2549
3021
  }
2550
3022
  ),
2551
3023
  /* @__PURE__ */ jsxs("span", { className: "cv-choice-text", children: [
2552
- /* @__PURE__ */ jsx("span", { className: "cv-choice-label", children: c.label }),
2553
- c.sub ? /* @__PURE__ */ jsx("span", { className: "cv-choice-sub", children: c.sub }) : null
3024
+ c.tag ? /* @__PURE__ */ jsxs("span", { className: "cv-choice-line", children: [
3025
+ /* @__PURE__ */ jsx("span", { className: "cv-choice-label", children: c.label }),
3026
+ /* @__PURE__ */ jsx("span", { className: "cv-choice-tag", children: c.tag })
3027
+ ] }) : /* @__PURE__ */ jsx("span", { className: "cv-choice-label", children: c.label }),
3028
+ c.sub ? /* @__PURE__ */ jsx("span", { className: "cv-choice-sub", children: c.sub }) : null,
3029
+ c.hint ? /* @__PURE__ */ jsx("span", { className: "cv-choice-hint", children: c.hint }) : null
2554
3030
  ] }),
2555
3031
  c.trailing,
2556
3032
  /* @__PURE__ */ jsx(Check, { size: 16, className: "cv-choice-check", "aria-hidden": true })
@@ -2712,11 +3188,11 @@ function previewRingtone(ringtone, options = {}) {
2712
3188
  }
2713
3189
 
2714
3190
  // src/ui/views/settings/ringtone-state.ts
2715
- var noSubscribe = () => () => {
3191
+ var noSubscribe2 = () => () => {
2716
3192
  };
2717
- var noSettings = () => null;
3193
+ var noSettings2 = () => null;
2718
3194
  function useRingtoneSettings(store) {
2719
- const settings = useSyncExternalStore(store?.subscribe ?? noSubscribe, store?.getSettings ?? noSettings);
3195
+ const settings = useSyncExternalStore(store?.subscribe ?? noSubscribe2, store?.getSettings ?? noSettings2);
2720
3196
  const [failed, setFailed] = useState(false);
2721
3197
  const load = useCallback(() => {
2722
3198
  if (!store) return () => {
@@ -2917,11 +3393,109 @@ function useSerialSave(store, handlers) {
2917
3393
  });
2918
3394
  return save;
2919
3395
  }
3396
+ var ORG_DEFAULT2 = "";
3397
+ var KEYBOARD_SETTLE_MS2 = 800;
3398
+ function OutboundNumberPage({
3399
+ store,
3400
+ settings,
3401
+ onBack
3402
+ }) {
3403
+ const formatIntl2 = useIntlFormatter();
3404
+ const current = numberName(effectiveNumber(settings), formatIntl2);
3405
+ const change = useOutboundNumberChange(store, settings, current);
3406
+ const [pending, setPending] = useState(void 0);
3407
+ const settle = useRef(null);
3408
+ const choose = useRef(change.choose);
3409
+ choose.current = change.choose;
3410
+ useEffect(
3411
+ () => () => {
3412
+ if (settle.current) clearTimeout(settle.current);
3413
+ },
3414
+ []
3415
+ );
3416
+ const notice = useRef(null);
3417
+ const apply = async (value) => {
3418
+ setPending(value);
3419
+ const applied = await choose.current(value || null);
3420
+ setPending(void 0);
3421
+ if (!applied) requestAnimationFrame(() => notice.current?.scrollIntoView?.({ block: "nearest" }));
3422
+ };
3423
+ const onSelect = (value, via) => {
3424
+ if (settle.current) clearTimeout(settle.current);
3425
+ settle.current = null;
3426
+ if (via === "pointer") {
3427
+ void apply(value);
3428
+ return;
3429
+ }
3430
+ setPending(value);
3431
+ settle.current = setTimeout(() => {
3432
+ settle.current = null;
3433
+ void apply(value);
3434
+ }, KEYBOARD_SETTLE_MS2);
3435
+ };
3436
+ return /* @__PURE__ */ jsxs(
3437
+ SettingsPage,
3438
+ {
3439
+ title: "N\xFAmero de salida",
3440
+ intro: "El n\xFAmero que ve la persona a la que llamas. Es el mismo en todos tus dispositivos y tambi\xE9n puedes cambiarlo desde el teclado.",
3441
+ onBack,
3442
+ children: [
3443
+ change.message ? /* @__PURE__ */ jsx("p", { className: "cv-settings-notice", role: "status", "data-kind": change.isError ? "error" : "info", ref: notice, children: change.message }) : null,
3444
+ /* @__PURE__ */ jsx(
3445
+ ChoiceList,
3446
+ {
3447
+ label: "N\xFAmero de salida",
3448
+ choices: choicesFor2(settings, formatIntl2, change.busy ? pending : void 0),
3449
+ value: pending ?? currentValue2(settings),
3450
+ onSelect,
3451
+ disabled: change.locked
3452
+ }
3453
+ )
3454
+ ]
3455
+ }
3456
+ );
3457
+ }
3458
+ function currentValue2(settings) {
3459
+ return settings.mine && settings.numbers.some((n) => n.id === settings.mine) ? settings.mine : ORG_DEFAULT2;
3460
+ }
3461
+ function choicesFor2(settings, formatIntl2, applying) {
3462
+ const orgDefault = settings.numbers.find((n) => n.id === settings.orgDefault);
3463
+ const spinner = (value) => applying === value ? /* @__PURE__ */ jsxs("span", { className: "cv-choice-applying", children: [
3464
+ /* @__PURE__ */ jsx(LoaderCircle, { size: 15, className: "cv-choice-applying-icon", "aria-hidden": true }),
3465
+ /* @__PURE__ */ jsx("span", { className: "cv-sr-only", children: "Aplicando" })
3466
+ ] }) : void 0;
3467
+ return [
3468
+ {
3469
+ value: ORG_DEFAULT2,
3470
+ label: "Por defecto de la organizaci\xF3n",
3471
+ sub: orgDefault ? numberName(orgDefault, formatIntl2) : "Sin n\xFAmero por defecto",
3472
+ trailing: spinner(ORG_DEFAULT2)
3473
+ },
3474
+ ...settings.numbers.map((n) => {
3475
+ const kind = KIND_TAG[n.kind];
3476
+ return {
3477
+ value: n.id,
3478
+ label: numberName(n, formatIntl2),
3479
+ ...n.label ? { sub: formatIntl2(`+${n.number}`) } : {},
3480
+ ...kind ? { tag: kind.tag, hint: kind.hint } : {},
3481
+ trailing: spinner(n.id)
3482
+ };
3483
+ })
3484
+ ];
3485
+ }
3486
+ function outboundSummary(settings, formatIntl2) {
3487
+ const effective = effectiveNumber(settings);
3488
+ const name = numberName(effective, formatIntl2);
3489
+ return settings.mine || !effective ? name : `${name} \xB7 organizaci\xF3n`;
3490
+ }
2920
3491
  function SettingsTab() {
2921
3492
  const softphone = useSoftphone();
2922
3493
  const devices = useDevices();
2923
3494
  const store = ringtoneStore(softphone);
2924
3495
  const ringtone = useRingtoneSettings(store);
3496
+ const outStore = outboundNumberStore(softphone);
3497
+ const outbound = useOutboundNumbers(outStore).settings;
3498
+ const formatIntl2 = useIntlFormatter();
2925
3499
  const [page, setPage] = useState(null);
2926
3500
  const list = useRef(null);
2927
3501
  const rows = useRef({});
@@ -2956,6 +3530,16 @@ function SettingsTab() {
2956
3530
  rowRef: rowRef("tone")
2957
3531
  }
2958
3532
  ) }) : null,
3533
+ outStore && outbound && outbound.numbers.length > 1 ? /* @__PURE__ */ jsx(SettingsSection, { title: "Llamadas salientes", children: /* @__PURE__ */ jsx(
3534
+ SettingsNavRow,
3535
+ {
3536
+ icon: PhoneOutgoing,
3537
+ title: "N\xFAmero de salida",
3538
+ value: outboundSummary(outbound, formatIntl2),
3539
+ onOpen: open("outbound"),
3540
+ rowRef: rowRef("outbound")
3541
+ }
3542
+ ) }) : null,
2959
3543
  /* @__PURE__ */ jsxs(SettingsSection, { title: "Dispositivos", children: [
2960
3544
  /* @__PURE__ */ jsx(
2961
3545
  SettingsNavRow,
@@ -3009,7 +3593,7 @@ function SettingsTab() {
3009
3593
  onRetry: ringtone.retry,
3010
3594
  onBack: back
3011
3595
  }
3012
- ) : page !== null && page !== "tone" ? /* @__PURE__ */ jsx(DevicePage, { kind: page, onBack: back }) : null
3596
+ ) : page === "outbound" && outStore && outbound ? /* @__PURE__ */ jsx(OutboundNumberPage, { store: outStore, settings: outbound, onBack: back }) : page !== null && page !== "tone" && page !== "outbound" ? /* @__PURE__ */ jsx(DevicePage, { kind: page, onBack: back }) : null
3013
3597
  ] });
3014
3598
  }
3015
3599
  function useMicPermission() {
@@ -3196,6 +3780,40 @@ function LeaderGate() {
3196
3780
  /* @__PURE__ */ jsx("p", { part: "leader-body", children: "Solo una pesta\xF1a puede usar el tel\xE9fono a la vez. Cierra la otra para activar esta." })
3197
3781
  ] });
3198
3782
  }
3783
+ var BODY = "Las llamadas entran all\xED. Si prefieres recibirlas aqu\xED, pulsa \xABRecibir aqu\xED\xBB.";
3784
+ var sentence = (code) => `${ERROR_TEXT[code].title}. ${ERROR_TEXT[code].body ?? ""}`.trim();
3785
+ function LineGate() {
3786
+ const softphone = useSoftphone();
3787
+ const { where, claim } = useLine();
3788
+ const take = () => {
3789
+ softphone.takeLine().catch(() => {
3790
+ });
3791
+ };
3792
+ const body = claim === "refused" ? sentence("line/claim-refused") : claim === "failed" ? sentence("line/unavailable") : BODY;
3793
+ return (
3794
+ /*
3795
+ `part` on all four, like `ErrorGate`: it replaces the whole dialpad, and inside `<coventia-softphone>`
3796
+ a shadow root makes it otherwise unreachable from the host's CSS. `data-claim` is for the light-DOM
3797
+ `/ui` path and for debugging — it cannot cross the shadow boundary either.
3798
+ */
3799
+ /* @__PURE__ */ jsxs("div", { className: "cv-gate", "data-claim": claim, part: "line-gate", children: [
3800
+ /* @__PURE__ */ jsx("span", { className: "cv-gate-mark", children: /* @__PURE__ */ jsx(PhoneForwarded, { size: 22 }) }),
3801
+ /* @__PURE__ */ jsx("h3", { className: "cv-gate-title", part: "line-title", children: where === "desktop" ? "Tu tel\xE9fono est\xE1 activo en la app de escritorio" : "Tu tel\xE9fono est\xE1 activo en otro sitio" }),
3802
+ /* @__PURE__ */ jsx("p", { role: "status", part: "line-body", children: body }),
3803
+ /* @__PURE__ */ jsx(
3804
+ "button",
3805
+ {
3806
+ className: "cv-btn cv-btn--accent",
3807
+ onClick: take,
3808
+ disabled: claim === "claiming",
3809
+ "aria-busy": claim === "claiming",
3810
+ part: "line-claim",
3811
+ children: claim === "claiming" ? "Cambiando\u2026" : "Recibir aqu\xED"
3812
+ }
3813
+ )
3814
+ ] })
3815
+ );
3816
+ }
3199
3817
  var BACKSTOP_MS = 6e3;
3200
3818
  function useCallOutcome() {
3201
3819
  const softphone = useSoftphone();
@@ -3427,11 +4045,12 @@ function SoftphoneWidget(props) {
3427
4045
  const isLeader = useIsLeader();
3428
4046
  const softphoneState = useSoftphoneState();
3429
4047
  const isFollower = !isLeader && softphoneState === "starting";
4048
+ const isStandby = softphoneState === "standby";
3430
4049
  const mic = useMicPermission();
3431
4050
  useCallAudio(sounds);
3432
4051
  const blocked = requireMic && mic.state !== "ready";
3433
4052
  const fatal = useLastError();
3434
- const screenIsFree = !call && !blocked && !fatal && !isFollower;
4053
+ const screenIsFree = !call && !blocked && !fatal && !isFollower && !isStandby;
3435
4054
  const { outcome, clear: clearOutcome } = useCallOutcome();
3436
4055
  const { notices: transferNotices, dismiss: dismissTransferNotice } = useTransferNotices();
3437
4056
  const retry = () => {
@@ -3458,7 +4077,7 @@ function SoftphoneWidget(props) {
3458
4077
  ...theme === "system" ? {} : { "data-cv-theme": theme },
3459
4078
  children: [
3460
4079
  screenIsFree ? /* @__PURE__ */ jsx(TransferNotices, { notices: transferNotices, dismiss: dismissTransferNotice }) : null,
3461
- /* @__PURE__ */ jsx("div", { className: "cv-body", children: isFollower ? /* @__PURE__ */ jsx(LeaderGate, {}) : blocked ? /* @__PURE__ */ jsx(MicPrompt, { state: mic.state, onRequest: () => void mic.request() }) : call ? /* @__PURE__ */ jsx(CallScreen, { call, keyTones: sounds, ...incomingCallAction ? { incomingCallAction } : {} }) : fatal ? /* @__PURE__ */ jsx(ErrorGate, { error: fatal, onRetry: retry }) : tab === "contacts" && contactsSource ? /* @__PURE__ */ jsx(ContactsTab, { source: contactsSource, ...contactAction ? { contactAction } : {} }) : tab === "history" && historySource ? /* @__PURE__ */ jsx(HistoryTab, { source: historySource }) : tab === "settings" ? /* @__PURE__ */ jsx(SettingsTab, {}) : /* @__PURE__ */ jsx(Dialpad, { outcome, onIntent: clearOutcome, keyTones: sounds }) }),
4080
+ /* @__PURE__ */ jsx("div", { className: "cv-body", children: isFollower ? /* @__PURE__ */ jsx(LeaderGate, {}) : isStandby && !call ? /* @__PURE__ */ jsx(LineGate, {}) : blocked ? /* @__PURE__ */ jsx(MicPrompt, { state: mic.state, onRequest: () => void mic.request() }) : call ? /* @__PURE__ */ jsx(CallScreen, { call, keyTones: sounds, ...incomingCallAction ? { incomingCallAction } : {} }) : fatal ? /* @__PURE__ */ jsx(ErrorGate, { error: fatal, onRetry: retry }) : tab === "contacts" && contactsSource ? /* @__PURE__ */ jsx(ContactsTab, { source: contactsSource, ...contactAction ? { contactAction } : {} }) : tab === "history" && historySource ? /* @__PURE__ */ jsx(HistoryTab, { source: historySource }) : tab === "settings" ? /* @__PURE__ */ jsx(SettingsTab, {}) : /* @__PURE__ */ jsx(Dialpad, { outcome, onIntent: clearOutcome, keyTones: sounds }) }),
3462
4081
  screenIsFree ? /* @__PURE__ */ jsx("div", { className: "cv-tabs", role: "tablist", children: tabs.map(({ id, icon: Icon3, label }) => /* @__PURE__ */ jsxs(
3463
4082
  "button",
3464
4083
  {
@@ -3504,6 +4123,7 @@ lucide-react/dist/esm/icons/mic.js:
3504
4123
  lucide-react/dist/esm/icons/monitor-smartphone.js:
3505
4124
  lucide-react/dist/esm/icons/music.js:
3506
4125
  lucide-react/dist/esm/icons/pause.js:
4126
+ lucide-react/dist/esm/icons/phone-forwarded.js:
3507
4127
  lucide-react/dist/esm/icons/phone-incoming.js:
3508
4128
  lucide-react/dist/esm/icons/phone-missed.js:
3509
4129
  lucide-react/dist/esm/icons/phone-off.js: