@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.cjs CHANGED
@@ -13263,6 +13263,19 @@ var Pause = createLucideIcon("Pause", [
13263
13263
  ["rect", { x: "6", y: "4", width: "4", height: "16", rx: "1", key: "1okwgv" }]
13264
13264
  ]);
13265
13265
 
13266
+ // ../../node_modules/lucide-react/dist/esm/icons/phone-forwarded.js
13267
+ var PhoneForwarded = createLucideIcon("PhoneForwarded", [
13268
+ ["polyline", { points: "18 2 22 6 18 10", key: "6vjanh" }],
13269
+ ["line", { x1: "14", x2: "22", y1: "6", y2: "6", key: "1jsywh" }],
13270
+ [
13271
+ "path",
13272
+ {
13273
+ 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",
13274
+ key: "foiqr5"
13275
+ }
13276
+ ]
13277
+ ]);
13278
+
13266
13279
  // ../../node_modules/lucide-react/dist/esm/icons/phone-incoming.js
13267
13280
  var PhoneIncoming = createLucideIcon("PhoneIncoming", [
13268
13281
  ["polyline", { points: "16 2 16 8 22 8", key: "1ygljm" }],
@@ -13439,6 +13452,31 @@ function useIsLeader() {
13439
13452
  React.useCallback(() => phone.isLeader, [phone])
13440
13453
  );
13441
13454
  }
13455
+ function useLine() {
13456
+ const phone = useSoftphone();
13457
+ const cache = React.useRef(null);
13458
+ return React.useSyncExternalStore(
13459
+ React.useCallback(
13460
+ (cb) => {
13461
+ const offLine = phone.on("lineChanged" /* LineChanged */, () => cb());
13462
+ const offClaim = phone.on("lineClaimChanged" /* LineClaimChanged */, () => cb());
13463
+ return () => {
13464
+ offLine();
13465
+ offClaim();
13466
+ };
13467
+ },
13468
+ [phone]
13469
+ ),
13470
+ React.useCallback(() => {
13471
+ const { line, lineWhere: where, lineClaim: claim } = phone;
13472
+ const cached = cache.current;
13473
+ if (cached?.line === line && cached.where === where && cached.claim === claim) return cached;
13474
+ const view = { line, where, claim };
13475
+ cache.current = view;
13476
+ return view;
13477
+ }, [phone])
13478
+ );
13479
+ }
13442
13480
  function useActiveCall() {
13443
13481
  const phone = useSoftphone();
13444
13482
  return React.useSyncExternalStore(
@@ -13540,6 +13578,36 @@ var PACKED = "AC247 AD376 AE971 AF93 AG1 AI1 AL355 AM374 AO244 AR54 AS1 AT43 AU6
13540
13578
  var DIAL_CODES = new Map(
13541
13579
  PACKED.split(" ").map((token) => [token.slice(0, 2), Number(token.slice(2))])
13542
13580
  );
13581
+ var REGIONS_BY_CODE = (() => {
13582
+ const map = /* @__PURE__ */ new Map();
13583
+ for (const [region, code] of DIAL_CODES) map.set(code, [...map.get(code) ?? [], region]);
13584
+ return map;
13585
+ })();
13586
+ var MAIN_REGION = {
13587
+ 1: "US",
13588
+ 7: "RU",
13589
+ 39: "IT",
13590
+ 44: "GB",
13591
+ 47: "NO",
13592
+ 61: "AU",
13593
+ 212: "MA",
13594
+ 262: "RE",
13595
+ 290: "SH",
13596
+ 358: "FI",
13597
+ 590: "GP",
13598
+ 599: "CW"
13599
+ };
13600
+ function regionOfInternational(typed) {
13601
+ const text = String(typed ?? "").trim();
13602
+ if (!text.startsWith("+")) return null;
13603
+ const digits = text.slice(1).replace(/\D/g, "");
13604
+ for (let length = 1; length <= Math.min(3, digits.length); length++) {
13605
+ const code = Number(digits.slice(0, length));
13606
+ const regions = REGIONS_BY_CODE.get(code);
13607
+ if (regions) return MAIN_REGION[code] ?? regions[0] ?? null;
13608
+ }
13609
+ return null;
13610
+ }
13543
13611
  var DEFAULT_COUNTRY = "ES";
13544
13612
  var REGION_NAMES = (() => {
13545
13613
  try {
@@ -13591,12 +13659,16 @@ function CountryMark({ code }) {
13591
13659
  }
13592
13660
  function CountryPicker({
13593
13661
  value,
13594
- onChange
13662
+ onChange,
13663
+ fromNumber = false
13595
13664
  }) {
13596
13665
  const [open, setOpen] = React.useState(false);
13597
13666
  const [query, setQuery] = React.useState("");
13598
13667
  const rootRef = React.useRef(null);
13599
13668
  const inputRef = React.useRef(null);
13669
+ React.useEffect(() => {
13670
+ if (fromNumber) setOpen(false);
13671
+ }, [fromNumber]);
13600
13672
  const results = React.useMemo(() => {
13601
13673
  const q = query.trim().toLowerCase().replace(/^\+/, "");
13602
13674
  if (!q) return COUNTRIES;
@@ -13625,6 +13697,12 @@ function CountryPicker({
13625
13697
  else setQuery("");
13626
13698
  }, [open]);
13627
13699
  const dial = DIAL_CODES.get(value);
13700
+ if (fromNumber) {
13701
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "cv-cp", ref: rootRef, children: /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "cv-cp-chip", "data-from-number": true, title: "El prefijo viene del n\xFAmero marcado", part: "country-chip", children: [
13702
+ /* @__PURE__ */ jsxRuntime.jsx(CountryMark, { code: value || "+" }),
13703
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "cv-sr-only", children: dial ? `Prefijo del n\xFAmero marcado: ${countryName(value)} (+${dial})` : "Prefijo del n\xFAmero marcado" })
13704
+ ] }) });
13705
+ }
13628
13706
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "cv-cp", ref: rootRef, children: [
13629
13707
  /* @__PURE__ */ jsxRuntime.jsxs(
13630
13708
  "button",
@@ -14191,18 +14269,384 @@ function playRingback() {
14191
14269
  }
14192
14270
  };
14193
14271
  }
14272
+
14273
+ // src/core/outbound-numbers.ts
14274
+ function isOutboundNumberRefused(value) {
14275
+ const refusal = value?.refusal;
14276
+ return value instanceof Error && !!refusal && (refusal.reason === "platform" || refusal.reason === "user") && typeof refusal.retryAfterSeconds === "number";
14277
+ }
14278
+ var stores = /* @__PURE__ */ new WeakMap();
14279
+ function outboundNumberStore(softphone) {
14280
+ const source = softphone.sources?.outboundNumbers;
14281
+ if (!source) return null;
14282
+ let store = stores.get(softphone);
14283
+ if (!store) {
14284
+ store = createOutboundNumberStore(source);
14285
+ stores.set(softphone, store);
14286
+ }
14287
+ return store;
14288
+ }
14289
+ function createOutboundNumberStore(source) {
14290
+ let settings = null;
14291
+ let inFlight = null;
14292
+ const listeners = /* @__PURE__ */ new Set();
14293
+ const apply = (next) => {
14294
+ settings = next;
14295
+ for (const l of listeners) l();
14296
+ };
14297
+ return {
14298
+ getSettings: () => settings,
14299
+ refresh() {
14300
+ inFlight ??= (async () => {
14301
+ if (!settings && source.cached) {
14302
+ const early = await source.cached().catch(() => null);
14303
+ if (early && !settings) apply(early);
14304
+ }
14305
+ try {
14306
+ apply(await source.get());
14307
+ } catch (error) {
14308
+ console.warn("[softphone] outbound numbers could not be loaded; keeping the previous ones", error);
14309
+ } finally {
14310
+ inFlight = null;
14311
+ }
14312
+ })();
14313
+ return inFlight;
14314
+ },
14315
+ async setMine(id) {
14316
+ apply(await source.setMine(id));
14317
+ },
14318
+ subscribe(listener) {
14319
+ listeners.add(listener);
14320
+ return () => listeners.delete(listener);
14321
+ }
14322
+ };
14323
+ }
14324
+ var noSubscribe = () => () => {
14325
+ };
14326
+ var noSettings = () => null;
14327
+ function useOutboundNumbers(store) {
14328
+ const settings = React.useSyncExternalStore(store?.subscribe ?? noSubscribe, store?.getSettings ?? noSettings);
14329
+ const [failed, setFailed] = React.useState(false);
14330
+ const load = React.useCallback(() => {
14331
+ if (!store) return () => {
14332
+ };
14333
+ let live = true;
14334
+ setFailed(false);
14335
+ void store.refresh().then(() => {
14336
+ if (live) setFailed(store.getSettings() === null);
14337
+ });
14338
+ return () => {
14339
+ live = false;
14340
+ };
14341
+ }, [store]);
14342
+ React.useEffect(load, [load]);
14343
+ return { settings, failed, retry: load };
14344
+ }
14345
+ function useKnownOutboundNumbers(store) {
14346
+ return React.useSyncExternalStore(store?.subscribe ?? noSubscribe, store?.getSettings ?? noSettings);
14347
+ }
14348
+ function useCountdown(until) {
14349
+ const [, setTick] = React.useState(0);
14350
+ React.useEffect(() => {
14351
+ if (!until) return void 0;
14352
+ const timer = setInterval(() => {
14353
+ setTick((n) => n + 1);
14354
+ if (secondsLeft(until) === 0) clearInterval(timer);
14355
+ }, 1e3);
14356
+ return () => clearInterval(timer);
14357
+ }, [until]);
14358
+ return secondsLeft(until);
14359
+ }
14360
+ function secondsLeft(until) {
14361
+ return until ? Math.max(0, Math.ceil((until - Date.now()) / 1e3)) : 0;
14362
+ }
14363
+ function numberName(n, format = formatIntl) {
14364
+ if (!n) return "N\xFAmero de la organizaci\xF3n";
14365
+ return n.label || format(`+${n.number}`);
14366
+ }
14367
+ function effectiveNumber(settings) {
14368
+ return settings?.numbers.find((n) => n.id === settings.effective);
14369
+ }
14370
+
14371
+ // src/ui/outbound/outbound-change.ts
14372
+ var KIND_TAG = {
14373
+ mobile: { tag: "M\xF3vil", hint: "No usar para atenci\xF3n al cliente ni para llamadas comerciales no solicitadas." },
14374
+ commercial: { tag: "Comercial (400)", hint: "Solo para llamadas comerciales. No recibe llamadas." }
14375
+ };
14376
+ function noticeText(situation, seconds, current) {
14377
+ switch (situation) {
14378
+ case "platform":
14379
+ 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}.`;
14380
+ case "user":
14381
+ 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}.`;
14382
+ case "wait":
14383
+ return `Podr\xE1s cambiar el n\xFAmero de salida en ${seconds} s.`;
14384
+ case "pending":
14385
+ 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.";
14386
+ }
14387
+ }
14388
+ function useOutboundNumberChange(store, settings, current) {
14389
+ const [busy, setBusy] = React.useState(false);
14390
+ const [notice, setNotice] = React.useState(null);
14391
+ const [waitUntil, setWaitUntil] = React.useState(null);
14392
+ React.useEffect(() => {
14393
+ const s = settings?.changeAvailableInSeconds ?? 0;
14394
+ setWaitUntil(s > 0 ? Date.now() + s * 1e3 : null);
14395
+ }, [settings]);
14396
+ const refusedUntil = notice?.kind === "refused" ? notice.until : null;
14397
+ const blockedFor = useCountdown(refusedUntil ?? waitUntil);
14398
+ React.useEffect(() => {
14399
+ if (notice?.kind === "refused" && blockedFor === 0) setNotice(null);
14400
+ }, [notice, blockedFor]);
14401
+ const locked = busy || blockedFor > 0;
14402
+ const choose = async (id) => {
14403
+ if (!store || !settings || locked) return false;
14404
+ if (id === settings.mine) return true;
14405
+ setBusy(true);
14406
+ setNotice(null);
14407
+ try {
14408
+ await store.setMine(id);
14409
+ return true;
14410
+ } catch (error) {
14411
+ setNotice(
14412
+ isOutboundNumberRefused(error) ? { kind: "refused", reason: error.refusal.reason, until: Date.now() + error.refusal.retryAfterSeconds * 1e3 } : { kind: "error", text: errorText(error) }
14413
+ );
14414
+ return false;
14415
+ } finally {
14416
+ setBusy(false);
14417
+ }
14418
+ };
14419
+ return {
14420
+ busy,
14421
+ locked,
14422
+ choose,
14423
+ /** What to say about the choice right now, if anything. */
14424
+ message: settings ? currentMessage(notice, settings.state, blockedFor, current) : null,
14425
+ /** The message is an error (the change failed), not information (wait, pending). */
14426
+ isError: notice?.kind === "error"
14427
+ };
14428
+ }
14429
+ function currentMessage(notice, state, blockedFor, current) {
14430
+ if (notice?.kind === "refused") return blockedFor > 0 ? noticeText(notice.reason, blockedFor, current) : null;
14431
+ if (notice?.kind === "error") return notice.text;
14432
+ if (state === "pending" || state === "failed") return noticeText("pending", blockedFor, current);
14433
+ return blockedFor > 0 ? noticeText("wait", blockedFor, current) : null;
14434
+ }
14435
+ function errorText(error) {
14436
+ const message = error instanceof Error ? error.message : "";
14437
+ return message && !/HTTP \d+/.test(message) ? message : "No se ha podido cambiar el n\xFAmero de salida. Int\xE9ntalo de nuevo.";
14438
+ }
14439
+ var CALLOUT_MS = 6e3;
14440
+ function OutboundNumberPicker({ onBusyChange }) {
14441
+ const softphone = useSoftphone();
14442
+ const store = outboundNumberStore(softphone);
14443
+ const { settings } = useOutboundNumbers(store);
14444
+ const formatIntl2 = useIntlFormatter();
14445
+ const current = settings ? numberName(effectiveNumber(settings), formatIntl2) : "";
14446
+ const { busy, locked, choose: apply, message, isError } = useOutboundNumberChange(store, settings, current);
14447
+ const [open, setOpen] = React.useState(false);
14448
+ const [applying, setApplying] = React.useState(void 0);
14449
+ const [callout, setCallout] = React.useState(false);
14450
+ const rootRef = React.useRef(null);
14451
+ const chipRef = React.useRef(null);
14452
+ const keys = useListKeyboard(open);
14453
+ React.useEffect(() => onBusyChange?.(busy), [busy, onBusyChange]);
14454
+ React.useEffect(() => {
14455
+ if (!message) setCallout(false);
14456
+ }, [message]);
14457
+ React.useEffect(() => {
14458
+ if (!callout) return void 0;
14459
+ const timer = setTimeout(() => setCallout(false), CALLOUT_MS);
14460
+ return () => clearTimeout(timer);
14461
+ }, [callout]);
14462
+ const floating = open || callout;
14463
+ React.useEffect(() => {
14464
+ if (!floating) return;
14465
+ const close = () => {
14466
+ setOpen(false);
14467
+ setCallout(false);
14468
+ };
14469
+ const root = rootRef.current?.getRootNode() ?? document;
14470
+ const onDown = (e) => {
14471
+ if (!rootRef.current?.contains(e.target)) close();
14472
+ };
14473
+ const onKey = (e) => {
14474
+ if (e.key !== "Escape") return;
14475
+ if (rootRef.current?.contains(e.target)) chipRef.current?.focus();
14476
+ close();
14477
+ };
14478
+ root.addEventListener("pointerdown", onDown);
14479
+ document.addEventListener("keydown", onKey);
14480
+ return () => {
14481
+ root.removeEventListener("pointerdown", onDown);
14482
+ document.removeEventListener("keydown", onKey);
14483
+ };
14484
+ }, [floating]);
14485
+ if (!store || !settings || settings.numbers.length < 2) return null;
14486
+ const orgDefault = settings.numbers.find((n) => n.id === settings.orgDefault);
14487
+ const applyingNumber = applying === null ? orgDefault : settings.numbers.find((n) => n.id === applying);
14488
+ const shown = applying === void 0 ? current : numberName(applyingNumber, formatIntl2);
14489
+ const choose = async (id) => {
14490
+ if (locked) return;
14491
+ setOpen(false);
14492
+ chipRef.current?.focus();
14493
+ if (id === settings.mine) return;
14494
+ setCallout(false);
14495
+ setApplying(id);
14496
+ const applied = await apply(id);
14497
+ setApplying(void 0);
14498
+ if (!applied) setCallout(true);
14499
+ };
14500
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "cv-on", ref: rootRef, children: [
14501
+ /* @__PURE__ */ jsxRuntime.jsxs(
14502
+ "button",
14503
+ {
14504
+ ref: chipRef,
14505
+ type: "button",
14506
+ className: "cv-on-chip",
14507
+ onClick: () => {
14508
+ setCallout(false);
14509
+ setOpen((o) => !o);
14510
+ },
14511
+ "aria-haspopup": "listbox",
14512
+ "aria-expanded": open,
14513
+ "aria-label": `Llamar desde: ${shown}${busy ? " (aplicando)" : ""}. Cambiar n\xFAmero de salida`,
14514
+ "aria-busy": busy,
14515
+ part: "outbound-number-chip",
14516
+ children: [
14517
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "cv-on-label", children: "Llamar desde:" }),
14518
+ busy ? /* @__PURE__ */ jsxRuntime.jsx(LoaderCircle, { size: 13, className: "cv-on-icon cv-on-spin", "aria-hidden": true }) : null,
14519
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "cv-on-current", children: shown }),
14520
+ /* @__PURE__ */ jsxRuntime.jsx(ChevronDown, { size: 13, className: "cv-cp-caret", "data-open": open, "aria-hidden": true })
14521
+ ]
14522
+ }
14523
+ ),
14524
+ open ? /* @__PURE__ */ jsxRuntime.jsxs(
14525
+ "div",
14526
+ {
14527
+ className: "cv-cp-pop cv-on-pop",
14528
+ role: "dialog",
14529
+ "aria-label": "Llamar desde",
14530
+ part: "outbound-number-list",
14531
+ onBlur: (e) => {
14532
+ if (!e.currentTarget.contains(e.relatedTarget)) setOpen(false);
14533
+ },
14534
+ children: [
14535
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "cv-on-title", children: "Llamar desde" }),
14536
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "cv-cp-list", role: "listbox", "aria-disabled": locked, ref: keys.listRef, onKeyDown: keys.onKeyDown, children: [
14537
+ /* @__PURE__ */ jsxRuntime.jsx(
14538
+ OptionRow,
14539
+ {
14540
+ selected: settings.mine === null,
14541
+ disabled: locked,
14542
+ title: "Por defecto de la organizaci\xF3n",
14543
+ sub: orgDefault ? numberName(orgDefault, formatIntl2) : "Sin n\xFAmero por defecto",
14544
+ onSelect: () => void choose(null)
14545
+ }
14546
+ ),
14547
+ settings.numbers.map((n) => /* @__PURE__ */ jsxRuntime.jsx(
14548
+ OptionRow,
14549
+ {
14550
+ selected: settings.mine === n.id,
14551
+ disabled: locked,
14552
+ title: numberName(n, formatIntl2),
14553
+ sub: n.label ? formatIntl2(`+${n.number}`) : null,
14554
+ tag: KIND_TAG[n.kind],
14555
+ onSelect: () => void choose(n.id)
14556
+ },
14557
+ n.id
14558
+ ))
14559
+ ] }),
14560
+ message ? /* @__PURE__ */ jsxRuntime.jsx("p", { className: "cv-on-notice", role: "status", "data-kind": isError ? "error" : "info", children: message }) : null
14561
+ ]
14562
+ }
14563
+ ) : null,
14564
+ callout && !open && message ? /* @__PURE__ */ jsxRuntime.jsx("p", { className: "cv-on-callout", role: "status", "data-kind": isError ? "error" : "info", part: "outbound-number-notice", children: message }) : null
14565
+ ] });
14566
+ }
14567
+ function OptionRow({
14568
+ selected,
14569
+ disabled,
14570
+ title,
14571
+ sub,
14572
+ tag,
14573
+ onSelect
14574
+ }) {
14575
+ return /* @__PURE__ */ jsxRuntime.jsxs(
14576
+ "button",
14577
+ {
14578
+ type: "button",
14579
+ role: "option",
14580
+ "aria-selected": selected,
14581
+ "aria-disabled": disabled,
14582
+ className: "cv-cp-item cv-on-item",
14583
+ tabIndex: -1,
14584
+ onClick: onSelect,
14585
+ children: [
14586
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "cv-on-text", children: [
14587
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "cv-on-line", children: [
14588
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "cv-cp-name", children: title }),
14589
+ tag ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "cv-on-tag", children: tag.tag }) : null
14590
+ ] }),
14591
+ sub ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "cv-on-sub", children: sub }) : null,
14592
+ tag ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "cv-on-hint", children: tag.hint }) : null
14593
+ ] }),
14594
+ /* @__PURE__ */ jsxRuntime.jsx(Check, { size: 14, className: "cv-cp-check", "data-on": selected, "aria-hidden": true })
14595
+ ]
14596
+ }
14597
+ );
14598
+ }
14599
+ var optionsOf = (list) => list ? [...list.querySelectorAll('[role="option"]')] : [];
14600
+ var fold = (text) => text.normalize("NFD").replace(/\p{Diacritic}/gu, "").toLowerCase();
14601
+ var TYPEAHEAD_MS = 700;
14602
+ function useListKeyboard(open) {
14603
+ const listRef = React.useRef(null);
14604
+ const typed = React.useRef({ text: "", at: 0 });
14605
+ React.useEffect(() => {
14606
+ if (!open) return;
14607
+ const options = optionsOf(listRef.current);
14608
+ (options.find((o) => o.getAttribute("aria-selected") === "true") ?? options[0])?.focus();
14609
+ }, [open]);
14610
+ const onKeyDown = (e) => {
14611
+ const options = optionsOf(listRef.current);
14612
+ const at = options.indexOf(e.target);
14613
+ const move = {
14614
+ ArrowDown: Math.min(at + 1, options.length - 1),
14615
+ ArrowUp: Math.max(at - 1, 0),
14616
+ Home: 0,
14617
+ End: options.length - 1
14618
+ };
14619
+ let next = move[e.key];
14620
+ if (next === void 0 && e.key.length === 1 && !e.ctrlKey && !e.metaKey && !e.altKey) {
14621
+ const now = Date.now();
14622
+ const text = now - typed.current.at < TYPEAHEAD_MS ? typed.current.text + e.key : e.key;
14623
+ typed.current = { text, at: now };
14624
+ const wanted = fold(text);
14625
+ const match = options.findIndex((o) => fold(o.querySelector(".cv-cp-name")?.textContent ?? "").startsWith(wanted));
14626
+ next = match >= 0 ? match : void 0;
14627
+ }
14628
+ if (next === void 0) return;
14629
+ e.preventDefault();
14630
+ options[next]?.focus();
14631
+ };
14632
+ return { listRef, onKeyDown };
14633
+ }
14194
14634
  var NOT_DIALABLE = /* @__PURE__ */ new Set(["*", "#"]);
14635
+ var DISPLAY_SIZES = ["1.375rem", "1.25rem", "1.125rem", "1rem"];
14195
14636
  function Dialpad({ outcome = null, onIntent, keyTones = true } = {}) {
14196
14637
  const softphone = useSoftphone();
14197
14638
  const state = useSoftphoneState();
14198
14639
  const [value, setValue] = React.useState("");
14199
14640
  const [country2, setCountry] = React.useState(DEFAULT_COUNTRY);
14641
+ const [changingNumber, setChangingNumber] = React.useState(false);
14200
14642
  const format = useNumberFormatter();
14201
14643
  const inputRef = React.useRef(null);
14202
14644
  const caretRef = React.useRef(null);
14203
14645
  const display = format(country2, value);
14204
14646
  const target = toE164(country2, value);
14205
- const canCall = state === "online" && target.length > 1;
14647
+ const canCall = state === "online" && target.length > 1 && !changingNumber;
14648
+ const typedInternational = value.trim().startsWith("+");
14649
+ const numberRegion = regionOfInternational(value);
14206
14650
  React.useLayoutEffect(() => {
14207
14651
  const caret = caretRef.current;
14208
14652
  if (caret === null || !inputRef.current) return;
@@ -14210,6 +14654,14 @@ function Dialpad({ outcome = null, onIntent, keyTones = true } = {}) {
14210
14654
  const pos = caretAfterDigits(display, caret);
14211
14655
  inputRef.current.setSelectionRange(pos, pos);
14212
14656
  }, [display]);
14657
+ React.useLayoutEffect(() => {
14658
+ const input = inputRef.current;
14659
+ if (!input) return;
14660
+ for (const size of DISPLAY_SIZES) {
14661
+ input.style.fontSize = size;
14662
+ if (input.scrollWidth <= input.clientWidth) break;
14663
+ }
14664
+ }, [display]);
14213
14665
  const append = (key) => {
14214
14666
  onIntent?.();
14215
14667
  setValue((v) => v + key);
@@ -14225,9 +14677,16 @@ function Dialpad({ outcome = null, onIntent, keyTones = true } = {}) {
14225
14677
  void softphone.call(target);
14226
14678
  };
14227
14679
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "cv-view", children: [
14228
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "cv-dial-prefix", children: /* @__PURE__ */ jsxRuntime.jsx(CountryPicker, { value: country2, onChange: setCountry }) }),
14680
+ /* @__PURE__ */ jsxRuntime.jsx(OutboundNumberPicker, { onBusyChange: setChangingNumber }),
14229
14681
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "cv-dial-head", children: [
14230
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "cv-dial-side", "aria-hidden": true }),
14682
+ /* @__PURE__ */ jsxRuntime.jsx(
14683
+ CountryPicker,
14684
+ {
14685
+ value: numberRegion ?? country2,
14686
+ onChange: setCountry,
14687
+ fromNumber: typedInternational
14688
+ }
14689
+ ),
14231
14690
  /* @__PURE__ */ jsxRuntime.jsx(
14232
14691
  "input",
14233
14692
  {
@@ -14251,6 +14710,8 @@ function Dialpad({ outcome = null, onIntent, keyTones = true } = {}) {
14251
14710
  type: "button",
14252
14711
  className: "cv-dial-side",
14253
14712
  "data-hidden": value.length === 0,
14713
+ tabIndex: value.length === 0 ? -1 : void 0,
14714
+ "aria-hidden": value.length === 0 || void 0,
14254
14715
  onClick: backspace,
14255
14716
  "aria-label": "Borrar",
14256
14717
  children: /* @__PURE__ */ jsxRuntime.jsx(Delete, { size: 19 })
@@ -14424,6 +14885,22 @@ var ERROR_TEXT = {
14424
14885
  // thinks they dropped it will hang up on a client to "fix" it.
14425
14886
  "call/transfer-failed": { title: "No se pudo transferir. Sigues en la llamada" },
14426
14887
  "call/unsupported": { title: "Esta funci\xF3n no est\xE1 disponible" },
14888
+ // ── Line owner ──────────────────────────────────────────────────────────────
14889
+ /*
14890
+ Not call outcomes: they answer "Recibir aquí" on `LineGate`, which has ONE line for them where the
14891
+ explanation was, so it reads the pair as two sentences — `${title}. ${body}`. Split anyway, so the
14892
+ title stands alone wherever there is only room for a headline (a host's own toast), under the same
14893
+ one-line rules as every other non-fatal title. "Allí" because the gate has just said where: the
14894
+ other softphone, the one that has the calls.
14895
+ */
14896
+ "line/claim-refused": {
14897
+ title: "Est\xE1s en una llamada all\xED",
14898
+ body: "Podr\xE1s recibirlas aqu\xED cuando cuelgues."
14899
+ },
14900
+ "line/unavailable": {
14901
+ title: "No se ha podido cambiar",
14902
+ body: "Int\xE9ntalo de nuevo."
14903
+ },
14427
14904
  // ── Provider ────────────────────────────────────────────────────────────────
14428
14905
  "provider/provision-failed": {
14429
14906
  title: "No se pudo activar el tel\xE9fono",
@@ -14675,6 +15152,9 @@ function CallScreen({
14675
15152
  }) {
14676
15153
  const view = useCall(call);
14677
15154
  const softphone = useSoftphone();
15155
+ const outbound = useOutboundNumbers(call.direction === "outbound" ? outboundNumberStore(softphone) : null).settings;
15156
+ const formatIntl2 = useIntlFormatter();
15157
+ const fromName = outbound && outbound.numbers.length > 1 ? numberName(effectiveNumber(outbound), formatIntl2) : null;
14678
15158
  const [panel, setPanel] = React.useState(null);
14679
15159
  const showTransfer = panel === "transfer";
14680
15160
  const showKeypad = panel === "keypad";
@@ -14885,6 +15365,13 @@ function CallScreen({
14885
15365
  ] }),
14886
15366
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: "cv-call-name", children: name }),
14887
15367
  showNumber ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "cv-call-number", children: number }) : null,
15368
+ fromName ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "cv-call-from", children: [
15369
+ /* @__PURE__ */ jsxRuntime.jsx(PhoneOutgoing, { size: 11, "aria-hidden": true }),
15370
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { children: [
15371
+ "Desde ",
15372
+ fromName
15373
+ ] })
15374
+ ] }) : null,
14888
15375
  s === "active" ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "cv-timer", "aria-label": "Duraci\xF3n de la llamada", children: [
14889
15376
  /* @__PURE__ */ jsxRuntime.jsx("b", { children: mm }),
14890
15377
  /* @__PURE__ */ jsxRuntime.jsx("i", { children: ":" }),
@@ -15300,12 +15787,23 @@ function HandlerChain({ handlers }) {
15300
15787
  ] }, `${h.extension}-${i}`);
15301
15788
  }) });
15302
15789
  }
15790
+ function outboundFrom(entry, known, formatIntl2) {
15791
+ if (entry.direction !== "outbound" || !entry.from) return null;
15792
+ const number = formatIntl2(`+${entry.from}`);
15793
+ const label = known?.numbers.find((n) => n.number === entry.from)?.label;
15794
+ if (!label) return number;
15795
+ return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
15796
+ label,
15797
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "cv-detail-row-sub", children: number })
15798
+ ] });
15799
+ }
15303
15800
  function HistoryDetail({
15304
15801
  entry,
15305
15802
  onBack
15306
15803
  }) {
15307
15804
  const softphone = useSoftphone();
15308
15805
  const formatIntl2 = useIntlFormatter();
15806
+ const knownNumbers = useKnownOutboundNumbers(outboundNumberStore(softphone));
15309
15807
  const [detail, setDetail] = React.useState(null);
15310
15808
  const [loadingDetail, setLoadingDetail] = React.useState(false);
15311
15809
  React.useEffect(() => {
@@ -15321,6 +15819,7 @@ function HistoryDetail({
15321
15819
  }, [entry]);
15322
15820
  const formatted = formatIntl2(entry.number);
15323
15821
  const name = entry.name ?? formatted;
15822
+ const from = outboundFrom(entry, knownNumbers, formatIntl2);
15324
15823
  const handledBy = detail?.handledBy ?? entry.handledBy;
15325
15824
  const recordingUrls = detail?.recordingUrls ?? [];
15326
15825
  const loadingRecording = entry.recorded && loadingDetail && recordingUrls.length === 0;
@@ -15357,7 +15856,8 @@ function HistoryDetail({
15357
15856
  entry.durationSec > 0 ? /* @__PURE__ */ jsxRuntime.jsx(DetailRow, { icon: Timer, eyebrow: "Duraci\xF3n", text: formatDuration(entry.durationSec) }) : null,
15358
15857
  /* @__PURE__ */ jsxRuntime.jsx(DetailRow, { icon: Phone, eyebrow: "N\xFAmero", text: formatted }),
15359
15858
  handledBy.length > 0 ? /* @__PURE__ */ jsxRuntime.jsx(DetailRow, { icon: User, eyebrow: handlerLabel, text: /* @__PURE__ */ jsxRuntime.jsx(HandlerChain, { handlers: handledBy }) }) : null,
15360
- entry.direction === "inbound" && entry.did ? /* @__PURE__ */ jsxRuntime.jsx(DetailRow, { icon: Hash, eyebrow: "N\xFAmero marcado (DID)", text: formatIntl2(entry.did) }) : null
15859
+ entry.direction === "inbound" && entry.did ? /* @__PURE__ */ jsxRuntime.jsx(DetailRow, { icon: Hash, eyebrow: "N\xFAmero marcado (DID)", text: formatIntl2(entry.did) }) : null,
15860
+ from ? /* @__PURE__ */ jsxRuntime.jsx(DetailRow, { icon: PhoneOutgoing, eyebrow: "Llamada desde", text: from }) : null
15361
15861
  ] })
15362
15862
  ] }),
15363
15863
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: "cv-detail-foot", children: /* @__PURE__ */ jsxRuntime.jsxs(
@@ -15552,14 +16052,14 @@ function HistoryTab({ source }) {
15552
16052
  }
15553
16053
 
15554
16054
  // src/ui/audio/ringtone-store.ts
15555
- var stores = /* @__PURE__ */ new WeakMap();
16055
+ var stores2 = /* @__PURE__ */ new WeakMap();
15556
16056
  function ringtoneStore(softphone) {
15557
16057
  const source = softphone.sources?.ringtones;
15558
16058
  if (!source) return null;
15559
- let store = stores.get(softphone);
16059
+ let store = stores2.get(softphone);
15560
16060
  if (!store) {
15561
16061
  store = createRingtoneStore(source);
15562
- stores.set(softphone, store);
16062
+ stores2.set(softphone, store);
15563
16063
  }
15564
16064
  return store;
15565
16065
  }
@@ -15685,17 +16185,19 @@ function ChoiceList({
15685
16185
  choices,
15686
16186
  value,
15687
16187
  onSelect,
15688
- onReselect
16188
+ onReselect,
16189
+ disabled = false
15689
16190
  }) {
15690
16191
  const name = React.useId();
15691
16192
  const pointer = React.useRef(false);
15692
- return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "cv-choices", role: "radiogroup", "aria-label": label, children: choices.map((c) => {
16193
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "cv-choices", role: "radiogroup", "aria-label": label, "aria-disabled": disabled || void 0, children: choices.map((c) => {
15693
16194
  const checked = c.value === value;
15694
16195
  return /* @__PURE__ */ jsxRuntime.jsxs(
15695
16196
  "label",
15696
16197
  {
15697
16198
  className: "cv-choice",
15698
16199
  "data-checked": checked || void 0,
16200
+ "data-disabled": disabled || void 0,
15699
16201
  onPointerDown: () => {
15700
16202
  pointer.current = true;
15701
16203
  },
@@ -15711,19 +16213,23 @@ function ChoiceList({
15711
16213
  onChange: () => {
15712
16214
  const via = pointer.current ? "pointer" : "keyboard";
15713
16215
  pointer.current = false;
15714
- onSelect(c.value, via);
16216
+ if (!disabled) onSelect(c.value, via);
15715
16217
  },
15716
16218
  onClick: () => {
15717
16219
  if (c.value === value) {
15718
16220
  pointer.current = false;
15719
- onReselect?.(c.value);
16221
+ if (!disabled) onReselect?.(c.value);
15720
16222
  }
15721
16223
  }
15722
16224
  }
15723
16225
  ),
15724
16226
  /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "cv-choice-text", children: [
15725
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "cv-choice-label", children: c.label }),
15726
- c.sub ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "cv-choice-sub", children: c.sub }) : null
16227
+ c.tag ? /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "cv-choice-line", children: [
16228
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "cv-choice-label", children: c.label }),
16229
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "cv-choice-tag", children: c.tag })
16230
+ ] }) : /* @__PURE__ */ jsxRuntime.jsx("span", { className: "cv-choice-label", children: c.label }),
16231
+ c.sub ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "cv-choice-sub", children: c.sub }) : null,
16232
+ c.hint ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "cv-choice-hint", children: c.hint }) : null
15727
16233
  ] }),
15728
16234
  c.trailing,
15729
16235
  /* @__PURE__ */ jsxRuntime.jsx(Check, { size: 16, className: "cv-choice-check", "aria-hidden": true })
@@ -15885,11 +16391,11 @@ function previewRingtone(ringtone, options = {}) {
15885
16391
  }
15886
16392
 
15887
16393
  // src/ui/views/settings/ringtone-state.ts
15888
- var noSubscribe = () => () => {
16394
+ var noSubscribe2 = () => () => {
15889
16395
  };
15890
- var noSettings = () => null;
16396
+ var noSettings2 = () => null;
15891
16397
  function useRingtoneSettings(store) {
15892
- const settings = React.useSyncExternalStore(store?.subscribe ?? noSubscribe, store?.getSettings ?? noSettings);
16398
+ const settings = React.useSyncExternalStore(store?.subscribe ?? noSubscribe2, store?.getSettings ?? noSettings2);
15893
16399
  const [failed, setFailed] = React.useState(false);
15894
16400
  const load = React.useCallback(() => {
15895
16401
  if (!store) return () => {
@@ -16090,11 +16596,109 @@ function useSerialSave(store, handlers) {
16090
16596
  });
16091
16597
  return save;
16092
16598
  }
16599
+ var ORG_DEFAULT2 = "";
16600
+ var KEYBOARD_SETTLE_MS2 = 800;
16601
+ function OutboundNumberPage({
16602
+ store,
16603
+ settings,
16604
+ onBack
16605
+ }) {
16606
+ const formatIntl2 = useIntlFormatter();
16607
+ const current = numberName(effectiveNumber(settings), formatIntl2);
16608
+ const change = useOutboundNumberChange(store, settings, current);
16609
+ const [pending, setPending] = React.useState(void 0);
16610
+ const settle = React.useRef(null);
16611
+ const choose = React.useRef(change.choose);
16612
+ choose.current = change.choose;
16613
+ React.useEffect(
16614
+ () => () => {
16615
+ if (settle.current) clearTimeout(settle.current);
16616
+ },
16617
+ []
16618
+ );
16619
+ const notice = React.useRef(null);
16620
+ const apply = async (value) => {
16621
+ setPending(value);
16622
+ const applied = await choose.current(value || null);
16623
+ setPending(void 0);
16624
+ if (!applied) requestAnimationFrame(() => notice.current?.scrollIntoView?.({ block: "nearest" }));
16625
+ };
16626
+ const onSelect = (value, via) => {
16627
+ if (settle.current) clearTimeout(settle.current);
16628
+ settle.current = null;
16629
+ if (via === "pointer") {
16630
+ void apply(value);
16631
+ return;
16632
+ }
16633
+ setPending(value);
16634
+ settle.current = setTimeout(() => {
16635
+ settle.current = null;
16636
+ void apply(value);
16637
+ }, KEYBOARD_SETTLE_MS2);
16638
+ };
16639
+ return /* @__PURE__ */ jsxRuntime.jsxs(
16640
+ SettingsPage,
16641
+ {
16642
+ title: "N\xFAmero de salida",
16643
+ 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.",
16644
+ onBack,
16645
+ children: [
16646
+ change.message ? /* @__PURE__ */ jsxRuntime.jsx("p", { className: "cv-settings-notice", role: "status", "data-kind": change.isError ? "error" : "info", ref: notice, children: change.message }) : null,
16647
+ /* @__PURE__ */ jsxRuntime.jsx(
16648
+ ChoiceList,
16649
+ {
16650
+ label: "N\xFAmero de salida",
16651
+ choices: choicesFor2(settings, formatIntl2, change.busy ? pending : void 0),
16652
+ value: pending ?? currentValue2(settings),
16653
+ onSelect,
16654
+ disabled: change.locked
16655
+ }
16656
+ )
16657
+ ]
16658
+ }
16659
+ );
16660
+ }
16661
+ function currentValue2(settings) {
16662
+ return settings.mine && settings.numbers.some((n) => n.id === settings.mine) ? settings.mine : ORG_DEFAULT2;
16663
+ }
16664
+ function choicesFor2(settings, formatIntl2, applying) {
16665
+ const orgDefault = settings.numbers.find((n) => n.id === settings.orgDefault);
16666
+ const spinner = (value) => applying === value ? /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "cv-choice-applying", children: [
16667
+ /* @__PURE__ */ jsxRuntime.jsx(LoaderCircle, { size: 15, className: "cv-choice-applying-icon", "aria-hidden": true }),
16668
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "cv-sr-only", children: "Aplicando" })
16669
+ ] }) : void 0;
16670
+ return [
16671
+ {
16672
+ value: ORG_DEFAULT2,
16673
+ label: "Por defecto de la organizaci\xF3n",
16674
+ sub: orgDefault ? numberName(orgDefault, formatIntl2) : "Sin n\xFAmero por defecto",
16675
+ trailing: spinner(ORG_DEFAULT2)
16676
+ },
16677
+ ...settings.numbers.map((n) => {
16678
+ const kind = KIND_TAG[n.kind];
16679
+ return {
16680
+ value: n.id,
16681
+ label: numberName(n, formatIntl2),
16682
+ ...n.label ? { sub: formatIntl2(`+${n.number}`) } : {},
16683
+ ...kind ? { tag: kind.tag, hint: kind.hint } : {},
16684
+ trailing: spinner(n.id)
16685
+ };
16686
+ })
16687
+ ];
16688
+ }
16689
+ function outboundSummary(settings, formatIntl2) {
16690
+ const effective = effectiveNumber(settings);
16691
+ const name = numberName(effective, formatIntl2);
16692
+ return settings.mine || !effective ? name : `${name} \xB7 organizaci\xF3n`;
16693
+ }
16093
16694
  function SettingsTab() {
16094
16695
  const softphone = useSoftphone();
16095
16696
  const devices = useDevices();
16096
16697
  const store = ringtoneStore(softphone);
16097
16698
  const ringtone = useRingtoneSettings(store);
16699
+ const outStore = outboundNumberStore(softphone);
16700
+ const outbound = useOutboundNumbers(outStore).settings;
16701
+ const formatIntl2 = useIntlFormatter();
16098
16702
  const [page, setPage] = React.useState(null);
16099
16703
  const list = React.useRef(null);
16100
16704
  const rows = React.useRef({});
@@ -16129,6 +16733,16 @@ function SettingsTab() {
16129
16733
  rowRef: rowRef("tone")
16130
16734
  }
16131
16735
  ) }) : null,
16736
+ outStore && outbound && outbound.numbers.length > 1 ? /* @__PURE__ */ jsxRuntime.jsx(SettingsSection, { title: "Llamadas salientes", children: /* @__PURE__ */ jsxRuntime.jsx(
16737
+ SettingsNavRow,
16738
+ {
16739
+ icon: PhoneOutgoing,
16740
+ title: "N\xFAmero de salida",
16741
+ value: outboundSummary(outbound, formatIntl2),
16742
+ onOpen: open("outbound"),
16743
+ rowRef: rowRef("outbound")
16744
+ }
16745
+ ) }) : null,
16132
16746
  /* @__PURE__ */ jsxRuntime.jsxs(SettingsSection, { title: "Dispositivos", children: [
16133
16747
  /* @__PURE__ */ jsxRuntime.jsx(
16134
16748
  SettingsNavRow,
@@ -16182,7 +16796,7 @@ function SettingsTab() {
16182
16796
  onRetry: ringtone.retry,
16183
16797
  onBack: back
16184
16798
  }
16185
- ) : page !== null && page !== "tone" ? /* @__PURE__ */ jsxRuntime.jsx(DevicePage, { kind: page, onBack: back }) : null
16799
+ ) : page === "outbound" && outStore && outbound ? /* @__PURE__ */ jsxRuntime.jsx(OutboundNumberPage, { store: outStore, settings: outbound, onBack: back }) : page !== null && page !== "tone" && page !== "outbound" ? /* @__PURE__ */ jsxRuntime.jsx(DevicePage, { kind: page, onBack: back }) : null
16186
16800
  ] });
16187
16801
  }
16188
16802
 
@@ -16379,6 +16993,40 @@ function LeaderGate() {
16379
16993
  /* @__PURE__ */ jsxRuntime.jsx("p", { part: "leader-body", children: "Solo una pesta\xF1a puede usar el tel\xE9fono a la vez. Cierra la otra para activar esta." })
16380
16994
  ] });
16381
16995
  }
16996
+ var BODY = "Las llamadas entran all\xED. Si prefieres recibirlas aqu\xED, pulsa \xABRecibir aqu\xED\xBB.";
16997
+ var sentence = (code) => `${ERROR_TEXT[code].title}. ${ERROR_TEXT[code].body ?? ""}`.trim();
16998
+ function LineGate() {
16999
+ const softphone = useSoftphone();
17000
+ const { where, claim } = useLine();
17001
+ const take = () => {
17002
+ softphone.takeLine().catch(() => {
17003
+ });
17004
+ };
17005
+ const body = claim === "refused" ? sentence("line/claim-refused") : claim === "failed" ? sentence("line/unavailable") : BODY;
17006
+ return (
17007
+ /*
17008
+ `part` on all four, like `ErrorGate`: it replaces the whole dialpad, and inside `<coventia-softphone>`
17009
+ a shadow root makes it otherwise unreachable from the host's CSS. `data-claim` is for the light-DOM
17010
+ `/ui` path and for debugging — it cannot cross the shadow boundary either.
17011
+ */
17012
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "cv-gate", "data-claim": claim, part: "line-gate", children: [
17013
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "cv-gate-mark", children: /* @__PURE__ */ jsxRuntime.jsx(PhoneForwarded, { size: 22 }) }),
17014
+ /* @__PURE__ */ jsxRuntime.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" }),
17015
+ /* @__PURE__ */ jsxRuntime.jsx("p", { role: "status", part: "line-body", children: body }),
17016
+ /* @__PURE__ */ jsxRuntime.jsx(
17017
+ "button",
17018
+ {
17019
+ className: "cv-btn cv-btn--accent",
17020
+ onClick: take,
17021
+ disabled: claim === "claiming",
17022
+ "aria-busy": claim === "claiming",
17023
+ part: "line-claim",
17024
+ children: claim === "claiming" ? "Cambiando\u2026" : "Recibir aqu\xED"
17025
+ }
17026
+ )
17027
+ ] })
17028
+ );
17029
+ }
16382
17030
  var BACKSTOP_MS = 6e3;
16383
17031
  function useCallOutcome() {
16384
17032
  const softphone = useSoftphone();
@@ -16610,11 +17258,12 @@ function SoftphoneWidget(props) {
16610
17258
  const isLeader = useIsLeader();
16611
17259
  const softphoneState = useSoftphoneState();
16612
17260
  const isFollower = !isLeader && softphoneState === "starting";
17261
+ const isStandby = softphoneState === "standby";
16613
17262
  const mic = useMicPermission();
16614
17263
  useCallAudio(sounds);
16615
17264
  const blocked = requireMic && mic.state !== "ready";
16616
17265
  const fatal = useLastError();
16617
- const screenIsFree = !call && !blocked && !fatal && !isFollower;
17266
+ const screenIsFree = !call && !blocked && !fatal && !isFollower && !isStandby;
16618
17267
  const { outcome, clear: clearOutcome } = useCallOutcome();
16619
17268
  const { notices: transferNotices, dismiss: dismissTransferNotice } = useTransferNotices();
16620
17269
  const retry = () => {
@@ -16641,7 +17290,7 @@ function SoftphoneWidget(props) {
16641
17290
  ...theme === "system" ? {} : { "data-cv-theme": theme },
16642
17291
  children: [
16643
17292
  screenIsFree ? /* @__PURE__ */ jsxRuntime.jsx(TransferNotices, { notices: transferNotices, dismiss: dismissTransferNotice }) : null,
16644
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "cv-body", children: isFollower ? /* @__PURE__ */ jsxRuntime.jsx(LeaderGate, {}) : blocked ? /* @__PURE__ */ jsxRuntime.jsx(MicPrompt, { state: mic.state, onRequest: () => void mic.request() }) : call ? /* @__PURE__ */ jsxRuntime.jsx(CallScreen, { call, keyTones: sounds, ...incomingCallAction ? { incomingCallAction } : {} }) : fatal ? /* @__PURE__ */ jsxRuntime.jsx(ErrorGate, { error: fatal, onRetry: retry }) : tab === "contacts" && contactsSource ? /* @__PURE__ */ jsxRuntime.jsx(ContactsTab, { source: contactsSource, ...contactAction ? { contactAction } : {} }) : tab === "history" && historySource ? /* @__PURE__ */ jsxRuntime.jsx(HistoryTab, { source: historySource }) : tab === "settings" ? /* @__PURE__ */ jsxRuntime.jsx(SettingsTab, {}) : /* @__PURE__ */ jsxRuntime.jsx(Dialpad, { outcome, onIntent: clearOutcome, keyTones: sounds }) }),
17293
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "cv-body", children: isFollower ? /* @__PURE__ */ jsxRuntime.jsx(LeaderGate, {}) : isStandby && !call ? /* @__PURE__ */ jsxRuntime.jsx(LineGate, {}) : blocked ? /* @__PURE__ */ jsxRuntime.jsx(MicPrompt, { state: mic.state, onRequest: () => void mic.request() }) : call ? /* @__PURE__ */ jsxRuntime.jsx(CallScreen, { call, keyTones: sounds, ...incomingCallAction ? { incomingCallAction } : {} }) : fatal ? /* @__PURE__ */ jsxRuntime.jsx(ErrorGate, { error: fatal, onRetry: retry }) : tab === "contacts" && contactsSource ? /* @__PURE__ */ jsxRuntime.jsx(ContactsTab, { source: contactsSource, ...contactAction ? { contactAction } : {} }) : tab === "history" && historySource ? /* @__PURE__ */ jsxRuntime.jsx(HistoryTab, { source: historySource }) : tab === "settings" ? /* @__PURE__ */ jsxRuntime.jsx(SettingsTab, {}) : /* @__PURE__ */ jsxRuntime.jsx(Dialpad, { outcome, onIntent: clearOutcome, keyTones: sounds }) }),
16645
17294
  screenIsFree ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "cv-tabs", role: "tablist", children: tabs.map(({ id, icon: Icon3, label }) => /* @__PURE__ */ jsxRuntime.jsxs(
16646
17295
  "button",
16647
17296
  {
@@ -16687,6 +17336,7 @@ lucide-react/dist/esm/icons/mic.js:
16687
17336
  lucide-react/dist/esm/icons/monitor-smartphone.js:
16688
17337
  lucide-react/dist/esm/icons/music.js:
16689
17338
  lucide-react/dist/esm/icons/pause.js:
17339
+ lucide-react/dist/esm/icons/phone-forwarded.js:
16690
17340
  lucide-react/dist/esm/icons/phone-incoming.js:
16691
17341
  lucide-react/dist/esm/icons/phone-missed.js:
16692
17342
  lucide-react/dist/esm/icons/phone-off.js: