@dataemergencia/emergency 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.
package/dist/index.js CHANGED
@@ -2,7 +2,7 @@ import { createRoot } from "react-dom/client";
2
2
  import axios, { isAxiosError } from "axios";
3
3
  import Pusher from "pusher-js";
4
4
  import { createStore } from "zustand/vanilla";
5
- import { createContext, useCallback, useContext, useEffect, useId, useRef } from "react";
5
+ import { createContext, useCallback, useContext, useEffect, useId, useMemo, useRef, useState } from "react";
6
6
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
7
7
  import { useStore } from "zustand";
8
8
 
@@ -362,6 +362,7 @@ function createBffClient(options) {
362
362
  const copy = {
363
363
  panelLabel: "Panel de emergencia",
364
364
  close: "Cerrar panel de emergencia",
365
+ back: "Volver",
365
366
  question: "¿Tiene usted una emergencia?",
366
367
  illustrationAlt: "Ícono paramédico",
367
368
  emergency: "Emergencia",
@@ -733,6 +734,7 @@ function createEmergencyStore(config) {
733
734
  lastError: null,
734
735
  geolocation: null,
735
736
  geolocationError: null,
737
+ contactPhone: null,
736
738
  realtimeStatus: "idle",
737
739
  abortResult: null,
738
740
  simulationResult: null,
@@ -1175,7 +1177,8 @@ function createEmergencyController(deps) {
1175
1177
  patch({
1176
1178
  panelOpen: false,
1177
1179
  abortConfirmation: false,
1178
- simulationConfirmation: false
1180
+ simulationConfirmation: false,
1181
+ contactPhone: null
1179
1182
  });
1180
1183
  events.emit("panel:closed", { reason });
1181
1184
  }
@@ -1224,6 +1227,7 @@ function createEmergencyController(deps) {
1224
1227
  lastError: null,
1225
1228
  geolocation: null,
1226
1229
  geolocationError: null,
1230
+ contactPhone: null,
1227
1231
  realtimeStatus: "idle",
1228
1232
  abortResult: null,
1229
1233
  simulationResult: null,
@@ -1265,7 +1269,8 @@ function createEmergencyController(deps) {
1265
1269
  geolocationError: null,
1266
1270
  ...state.geolocationError !== null || state.abortResult?.ok === false || state.simulationResult?.ok === false || state.phase.kind === "ready" && state.lastError !== null ? { lastError: null } : {}
1267
1271
  });
1268
- }
1272
+ },
1273
+ setContactPhone: (phone) => patch({ contactPhone: phone })
1269
1274
  },
1270
1275
  store,
1271
1276
  events
@@ -1440,74 +1445,6 @@ function validateConfig(config) {
1440
1445
  };
1441
1446
  }
1442
1447
 
1443
- // src/react/components/icons/paramedical-mark.tsx
1444
- /**
1445
- * Neutral inline illustration used by the idle panel.
1446
- */
1447
- function ParamedicalMark({ className, title }) {
1448
- return /* @__PURE__ */ jsxs("svg", {
1449
- xmlns: "http://www.w3.org/2000/svg",
1450
- version: "1.0",
1451
- viewBox: "0 0 192 192",
1452
- width: "192",
1453
- height: "192",
1454
- id: "Star of life",
1455
- className,
1456
- "aria-label": title,
1457
- children: [
1458
- /* @__PURE__ */ jsx("defs", { id: "defs2886" }),
1459
- /* @__PURE__ */ jsxs("g", {
1460
- id: "cross",
1461
- children: [/* @__PURE__ */ jsx("path", {
1462
- d: "m 74,133 0,52 44,0 0,-52 44,26 22,-38 L 140,96 184,70 162,32 117,58 117,7 74,7 74,58 30,32 8,70 52,96 8,121 30,159 z",
1463
- id: "outline",
1464
- style: {
1465
- fill: "none",
1466
- stroke: "#3333bb",
1467
- strokeWidth: 6
1468
- }
1469
- }), /* @__PURE__ */ jsx("path", {
1470
- d: "m 74,133 0,52 44,0 0,-52 44,26 22,-38 L 140,96 184,70 162,32 117,58 117,7 74,7 74,58 30,32 8,70 52,96 8,121 30,159 z",
1471
- id: "inner",
1472
- style: {
1473
- fill: "#0000bb",
1474
- fillRule: "evenodd",
1475
- stroke: "#ffffff",
1476
- strokeWidth: 2
1477
- }
1478
- })]
1479
- }),
1480
- /* @__PURE__ */ jsxs("g", {
1481
- id: "symbols",
1482
- style: {
1483
- fill: "#ffffff",
1484
- fillRule: "evenodd",
1485
- stroke: "#3333bb",
1486
- strokeWidth: 2
1487
- },
1488
- children: [/* @__PURE__ */ jsx("path", {
1489
- d: "m 93,166 c 0,7 6,7 6,0 l 3,-138 c 0,-8 -12,-8 -12,0 l 3,135 0,3 z",
1490
- id: "rod"
1491
- }), /* @__PURE__ */ jsxs("g", {
1492
- id: "snake",
1493
- children: [/* @__PURE__ */ jsx("path", {
1494
- d: "m 100,153 c 7,4 3,15 -3,15 2,-5 3,-9 3,-15 z m 1,-40 c 10,9 4,22 -3,26 -6,3 -6,7 -6,15 -9,-10 -4,-19 4,-24 5,-4 5,-10 5,-17 z m 1,-30 c 0,11 -17,14 -17,22 0,3 1,9 6,12 0,-11 9,-12 15,-18 7,-7 7,-19 -4,-28 0,4 0,8 0,12 z M 90,77 C 80,69 76,55 80,46 c 3,-10 20,-11 20,-3 0,8 -20,3 -13,16 l 3,5 0,13 z",
1495
- id: "body"
1496
- }), /* @__PURE__ */ jsx("path", {
1497
- d: "m 95,43 c 7,-3 -7,-3 0,0 z",
1498
- id: "eye",
1499
- style: {
1500
- fill: "#3333bb",
1501
- fillRule: "evenodd",
1502
- stroke: "none"
1503
- }
1504
- })]
1505
- })]
1506
- })
1507
- ]
1508
- });
1509
- }
1510
-
1511
1448
  // src/react/components/Icon/index.tsx
1512
1449
  /** Icons whose artwork does not fit the default 24x24 grid need their own canvas. */
1513
1450
  const viewBoxes = { emergency: "0 0 640 640" };
@@ -1564,6 +1501,23 @@ const paths = {
1564
1501
  /* @__PURE__ */ jsx("path", { d: "M12 7h.01" })
1565
1502
  ] }),
1566
1503
  "arrow-left": /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("path", { d: "M19 12H5" }), /* @__PURE__ */ jsx("path", { d: "m12 19-7-7 7-7" })] }),
1504
+ "map-pin": /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("path", { d: "M12 22s7-6.2 7-12a7 7 0 1 0-14 0c0 5.8 7 12 7 12z" }), /* @__PURE__ */ jsx("circle", {
1505
+ cx: "12",
1506
+ cy: "10",
1507
+ r: "2.5"
1508
+ })] }),
1509
+ "credit-card-plus": /* @__PURE__ */ jsxs(Fragment, { children: [
1510
+ /* @__PURE__ */ jsx("path", { d: "M12 18H4a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2v5" }),
1511
+ /* @__PURE__ */ jsx("path", { d: "M18 12h.01" }),
1512
+ /* @__PURE__ */ jsx("path", { d: "M19 22v-6" }),
1513
+ /* @__PURE__ */ jsx("path", { d: "m22 19-3-3-3 3" }),
1514
+ /* @__PURE__ */ jsx("path", { d: "M6 12h.01" }),
1515
+ /* @__PURE__ */ jsx("circle", {
1516
+ cx: "12",
1517
+ cy: "12",
1518
+ r: "2"
1519
+ })
1520
+ ] }),
1567
1521
  emergency: /* @__PURE__ */ jsx(Fragment, { children: /* @__PURE__ */ jsx("path", { d: "M272.5 96C272.5 78.3 286.8 64 304.5 64L336.5 64C354.2 64 368.5 78.3 368.5 96L368.5 236.9L490.5 166.5C505.8 157.7 525.4 162.9 534.2 178.2L550.2 205.9C559 221.2 553.8 240.8 538.5 249.6L416.5 320L538.5 390.4C553.8 399.2 559.1 418.8 550.2 434.1L534.2 461.8C525.4 477.1 505.8 482.4 490.5 473.5L368.5 403.1L368.5 544C368.5 561.7 354.2 576 336.5 576L304.5 576C286.8 576 272.5 561.7 272.5 544L272.5 403.1L150.5 473.5C135.2 482.3 115.6 477.1 106.8 461.8L90.8 434.1C82 418.8 87.2 399.2 102.5 390.4L224.5 320L102.5 249.6C87.2 240.8 82 221.2 90.8 205.9L106.8 178.2C115.6 162.9 135.2 157.7 150.5 166.5L272.5 236.9L272.5 96z" }) })
1568
1522
  };
1569
1523
  function Icon({ name, className, title }) {
@@ -1593,6 +1547,16 @@ var panel_module_default = {
1593
1547
  "de-emergency-slide-in": "de-emergency-de-emergency-slide-in-nA5Hwa",
1594
1548
  "de-emergency-slide-up": "de-emergency-de-emergency-slide-up-nA5Hwa",
1595
1549
  "de-emergency-spin": "de-emergency-de-emergency-spin-nA5Hwa",
1550
+ "emergency-availability": "de-emergency-emergency-availability-nA5Hwa",
1551
+ "emergency-availability__card": "de-emergency-emergency-availability__card-nA5Hwa",
1552
+ "emergency-availability__card-body": "de-emergency-emergency-availability__card-body-nA5Hwa",
1553
+ "emergency-availability__card-header": "de-emergency-emergency-availability__card-header-nA5Hwa",
1554
+ "emergency-availability__card-title": "de-emergency-emergency-availability__card-title-nA5Hwa",
1555
+ "emergency-availability__details": "de-emergency-emergency-availability__details-nA5Hwa",
1556
+ "emergency-availability__label": "de-emergency-emergency-availability__label-nA5Hwa",
1557
+ "emergency-availability__recharge": "de-emergency-emergency-availability__recharge-nA5Hwa",
1558
+ "emergency-availability__rounds": "de-emergency-emergency-availability__rounds-nA5Hwa",
1559
+ "emergency-availability__rounds-group": "de-emergency-emergency-availability__rounds-group-nA5Hwa",
1596
1560
  "emergency-confirmation": "de-emergency-emergency-confirmation-nA5Hwa",
1597
1561
  "emergency-confirmation__body": "de-emergency-emergency-confirmation__body-nA5Hwa",
1598
1562
  "emergency-confirmation__button": "de-emergency-emergency-confirmation__button-nA5Hwa",
@@ -1617,7 +1581,12 @@ var panel_module_default = {
1617
1581
  "emergency-feedback": "de-emergency-emergency-feedback-nA5Hwa",
1618
1582
  "emergency-feedback__dismiss": "de-emergency-emergency-feedback__dismiss-nA5Hwa",
1619
1583
  "emergency-panel": "de-emergency-emergency-panel-nA5Hwa",
1584
+ "emergency-panel__availability-region": "de-emergency-emergency-panel__availability-region-nA5Hwa",
1620
1585
  "emergency-panel__body": "de-emergency-emergency-panel__body-nA5Hwa",
1586
+ "emergency-panel__body--pre-emergency": "de-emergency-emergency-panel__body--pre-emergency-nA5Hwa",
1587
+ "emergency-panel__body--pricing": "de-emergency-emergency-panel__body--pricing-nA5Hwa",
1588
+ "emergency-panel__body-split": "de-emergency-emergency-panel__body-split-nA5Hwa",
1589
+ "emergency-panel__body-split--with-availability": "de-emergency-emergency-panel__body-split--with-availability-nA5Hwa",
1621
1590
  "emergency-panel__body-title": "de-emergency-emergency-panel__body-title-nA5Hwa",
1622
1591
  "emergency-panel__body-title-container": "de-emergency-emergency-panel__body-title-container-nA5Hwa",
1623
1592
  "emergency-panel__button": "de-emergency-emergency-panel__button-nA5Hwa",
@@ -1625,16 +1594,60 @@ var panel_module_default = {
1625
1594
  "emergency-panel__button--simulation": "de-emergency-emergency-panel__button--simulation-nA5Hwa",
1626
1595
  "emergency-panel__close-button": "de-emergency-emergency-panel__close-button-nA5Hwa",
1627
1596
  "emergency-panel__close-icon": "de-emergency-emergency-panel__close-icon-nA5Hwa",
1597
+ "emergency-panel__divider": "de-emergency-emergency-panel__divider-nA5Hwa",
1628
1598
  "emergency-panel__footer": "de-emergency-emergency-panel__footer-nA5Hwa",
1599
+ "emergency-panel__footer--single": "de-emergency-emergency-panel__footer--single-nA5Hwa",
1629
1600
  "emergency-panel__header": "de-emergency-emergency-panel__header-nA5Hwa",
1601
+ "emergency-panel__header--close-only": "de-emergency-emergency-panel__header--close-only-nA5Hwa",
1602
+ "emergency-panel__prompt-region": "de-emergency-emergency-panel__prompt-region-nA5Hwa",
1603
+ "emergency-panel__sr-only": "de-emergency-emergency-panel__sr-only-nA5Hwa",
1630
1604
  "emergency-panel__title": "de-emergency-emergency-panel__title-nA5Hwa",
1631
1605
  "emergency-panel--hidden": "de-emergency-emergency-panel--hidden-nA5Hwa",
1606
+ "emergency-pre-emergency": "de-emergency-emergency-pre-emergency-nA5Hwa",
1607
+ "emergency-pre-emergency__error": "de-emergency-emergency-pre-emergency__error-nA5Hwa",
1608
+ "emergency-pre-emergency__form": "de-emergency-emergency-pre-emergency__form-nA5Hwa",
1609
+ "emergency-pre-emergency__input": "de-emergency-emergency-pre-emergency__input-nA5Hwa",
1610
+ "emergency-pre-emergency__input--area-code": "de-emergency-emergency-pre-emergency__input--area-code-nA5Hwa",
1611
+ "emergency-pre-emergency__input--invalid": "de-emergency-emergency-pre-emergency__input--invalid-nA5Hwa",
1612
+ "emergency-pre-emergency__input--number": "de-emergency-emergency-pre-emergency__input--number-nA5Hwa",
1613
+ "emergency-pre-emergency__input--valid": "de-emergency-emergency-pre-emergency__input--valid-nA5Hwa",
1614
+ "emergency-pre-emergency__label": "de-emergency-emergency-pre-emergency__label-nA5Hwa",
1615
+ "emergency-pre-emergency__notice": "de-emergency-emergency-pre-emergency__notice-nA5Hwa",
1616
+ "emergency-pre-emergency__notice-icon": "de-emergency-emergency-pre-emergency__notice-icon-nA5Hwa",
1617
+ "emergency-pre-emergency__notice-text": "de-emergency-emergency-pre-emergency__notice-text-nA5Hwa",
1618
+ "emergency-pre-emergency__phone-row": "de-emergency-emergency-pre-emergency__phone-row-nA5Hwa",
1619
+ "emergency-pricing": "de-emergency-emergency-pricing-nA5Hwa",
1620
+ "emergency-pricing__badge": "de-emergency-emergency-pricing__badge-nA5Hwa",
1621
+ "emergency-pricing__heading": "de-emergency-emergency-pricing__heading-nA5Hwa",
1622
+ "emergency-pricing__package": "de-emergency-emergency-pricing__package-nA5Hwa",
1623
+ "emergency-pricing__package--highlighted": "de-emergency-emergency-pricing__package--highlighted-nA5Hwa",
1624
+ "emergency-pricing__package-caption": "de-emergency-emergency-pricing__package-caption-nA5Hwa",
1625
+ "emergency-pricing__package-footnote": "de-emergency-emergency-pricing__package-footnote-nA5Hwa",
1626
+ "emergency-pricing__package-header": "de-emergency-emergency-pricing__package-header-nA5Hwa",
1627
+ "emergency-pricing__package-label": "de-emergency-emergency-pricing__package-label-nA5Hwa",
1628
+ "emergency-pricing__package-name": "de-emergency-emergency-pricing__package-name-nA5Hwa",
1629
+ "emergency-pricing__price": "de-emergency-emergency-pricing__price-nA5Hwa",
1630
+ "emergency-pricing__price-meta": "de-emergency-emergency-pricing__price-meta-nA5Hwa",
1631
+ "emergency-pricing__price-note": "de-emergency-emergency-pricing__price-note-nA5Hwa",
1632
+ "emergency-pricing__price-row": "de-emergency-emergency-pricing__price-row-nA5Hwa",
1633
+ "emergency-pricing__select": "de-emergency-emergency-pricing__select-nA5Hwa",
1634
+ "emergency-pricing__stat": "de-emergency-emergency-pricing__stat-nA5Hwa",
1635
+ "emergency-pricing__stat-divider": "de-emergency-emergency-pricing__stat-divider-nA5Hwa",
1636
+ "emergency-pricing__stat-label": "de-emergency-emergency-pricing__stat-label-nA5Hwa",
1637
+ "emergency-pricing__stat-value": "de-emergency-emergency-pricing__stat-value-nA5Hwa",
1638
+ "emergency-pricing__stats": "de-emergency-emergency-pricing__stats-nA5Hwa",
1639
+ "emergency-pricing__subtitle": "de-emergency-emergency-pricing__subtitle-nA5Hwa",
1640
+ "emergency-pricing__title": "de-emergency-emergency-pricing__title-nA5Hwa",
1641
+ "emergency-pricing__titles": "de-emergency-emergency-pricing__titles-nA5Hwa",
1632
1642
  "emergency-resume-notification-button": "de-emergency-emergency-resume-notification-button-nA5Hwa",
1633
1643
  "emergency-resume-notification-card": "de-emergency-emergency-resume-notification-card-nA5Hwa",
1634
1644
  "emergency-resume-notification-icon": "de-emergency-emergency-resume-notification-icon-nA5Hwa",
1635
1645
  "emergency-resume-notification-message": "de-emergency-emergency-resume-notification-message-nA5Hwa",
1636
1646
  "emergency-resume-notification-overlay": "de-emergency-emergency-resume-notification-overlay-nA5Hwa",
1637
1647
  "emergency-resume-notification-title": "de-emergency-emergency-resume-notification-title-nA5Hwa",
1648
+ "emergency-screen": "de-emergency-emergency-screen-nA5Hwa",
1649
+ "emergency-screen__placeholder": "de-emergency-emergency-screen__placeholder-nA5Hwa",
1650
+ "emergency-screen__placeholder-text": "de-emergency-emergency-screen__placeholder-text-nA5Hwa",
1638
1651
  "emergency-status__abort-btn": "de-emergency-emergency-status__abort-btn-nA5Hwa",
1639
1652
  "emergency-status__body": "de-emergency-emergency-status__body-nA5Hwa",
1640
1653
  "emergency-status__cell": "de-emergency-emergency-status__cell-nA5Hwa",
@@ -1680,6 +1693,532 @@ function cls(...names) {
1680
1693
  return names.filter((name) => Boolean(name)).map((name) => panel_module_default[name] ?? "").filter(Boolean).join(" ");
1681
1694
  }
1682
1695
 
1696
+ // src/react/components/EmergencyConfirmation/index.tsx
1697
+ function EmergencyConfirmation(props) {
1698
+ const { abortConfirmation, simulationConfirmation, onCancelAbort, onCancelSimulation, onConfirmAbort, onConfirmSimulation } = props;
1699
+ const cardRef = useRef(null);
1700
+ const titleId = useId();
1701
+ const danger = abortConfirmation;
1702
+ const open = abortConfirmation || simulationConfirmation;
1703
+ useEffect(() => {
1704
+ if (open) cardRef.current?.querySelector("button")?.focus();
1705
+ }, [open]);
1706
+ if (!open) return null;
1707
+ const title = danger ? copy.abortConfirmTitle : copy.simulateConfirmTitle;
1708
+ const text = danger ? copy.abortConfirmText : copy.simulateConfirmText;
1709
+ const tone = danger ? "danger" : "information";
1710
+ return /* @__PURE__ */ jsx("div", {
1711
+ className: cls("emergency-confirmation"),
1712
+ role: "dialog",
1713
+ "aria-modal": "true",
1714
+ "aria-labelledby": titleId,
1715
+ children: /* @__PURE__ */ jsxs("div", {
1716
+ ref: cardRef,
1717
+ className: cls("emergency-confirmation__card"),
1718
+ children: [/* @__PURE__ */ jsxs("div", {
1719
+ className: cls("emergency-confirmation__body"),
1720
+ children: [
1721
+ /* @__PURE__ */ jsx("div", {
1722
+ className: cls("emergency-confirmation__icon", `emergency-confirmation__icon--${tone}`),
1723
+ children: /* @__PURE__ */ jsx(Icon, { name: danger ? "alert-triangle" : "info" })
1724
+ }),
1725
+ /* @__PURE__ */ jsx("h5", {
1726
+ className: cls("emergency-confirmation__title"),
1727
+ id: titleId,
1728
+ children: title
1729
+ }),
1730
+ /* @__PURE__ */ jsx("p", {
1731
+ className: cls("emergency-confirmation__text"),
1732
+ children: text
1733
+ })
1734
+ ]
1735
+ }), /* @__PURE__ */ jsxs("div", {
1736
+ className: cls("emergency-confirmation__footer"),
1737
+ children: [/* @__PURE__ */ jsx("button", {
1738
+ type: "button",
1739
+ className: cls("emergency-confirmation__button", "emergency-confirmation__button--cancel"),
1740
+ onClick: danger ? onCancelAbort : onCancelSimulation,
1741
+ children: danger ? copy.abortCancel : copy.simulateCancel
1742
+ }), /* @__PURE__ */ jsx("button", {
1743
+ type: "button",
1744
+ className: cls("emergency-confirmation__button", danger ? "emergency-confirmation__button--danger" : "emergency-confirmation__button--confirm"),
1745
+ onClick: danger ? onConfirmAbort : onConfirmSimulation,
1746
+ children: danger ? copy.abortConfirm : copy.simulateConfirm
1747
+ })]
1748
+ })]
1749
+ })
1750
+ });
1751
+ }
1752
+
1753
+ // src/react/hooks/useEscapeKey.ts
1754
+ function useEscapeKey(active, onEscape) {
1755
+ useEffect(() => {
1756
+ if (!active || typeof document === "undefined") return;
1757
+ const handleKeyDown = (event) => {
1758
+ if (event.key === "Escape") {
1759
+ event.stopPropagation();
1760
+ onEscape();
1761
+ }
1762
+ };
1763
+ document.addEventListener("keydown", handleKeyDown);
1764
+ return () => {
1765
+ document.removeEventListener("keydown", handleKeyDown);
1766
+ };
1767
+ }, [active, onEscape]);
1768
+ }
1769
+
1770
+ // src/react/hooks/useFocusTrap.ts
1771
+ const FOCUSABLE_SELECTOR = [
1772
+ "a[href]",
1773
+ "button:not([disabled])",
1774
+ "input:not([disabled])",
1775
+ "select:not([disabled])",
1776
+ "textarea:not([disabled])",
1777
+ "[tabindex]:not([tabindex=\"-1\"])"
1778
+ ].join(",");
1779
+ /**
1780
+ * Keeps keyboard focus inside the dialog while it is open and restores focus to
1781
+ * the element that opened it when it closes.
1782
+ */
1783
+ function useFocusTrap(ref, active) {
1784
+ useEffect(() => {
1785
+ if (!active || typeof document === "undefined") return;
1786
+ const container = ref.current;
1787
+ if (!container) return;
1788
+ const previouslyFocused = document.activeElement instanceof HTMLElement ? document.activeElement : null;
1789
+ const focusable = () => Array.from(container.querySelectorAll(FOCUSABLE_SELECTOR)).filter((element) => !element.hasAttribute("hidden") && element.getAttribute("aria-hidden") !== "true");
1790
+ (focusable()[0] ?? container).focus({ preventScroll: true });
1791
+ const handleKeyDown = (event) => {
1792
+ if (event.key !== "Tab") return;
1793
+ const items = focusable();
1794
+ if (items.length === 0) {
1795
+ event.preventDefault();
1796
+ container.focus({ preventScroll: true });
1797
+ return;
1798
+ }
1799
+ const first = items[0];
1800
+ const last = items[items.length - 1];
1801
+ const current = document.activeElement;
1802
+ if (event.shiftKey && (current === first || !container.contains(current))) {
1803
+ event.preventDefault();
1804
+ if (last) last.focus({ preventScroll: true });
1805
+ return;
1806
+ }
1807
+ if (!event.shiftKey && (current === last || !container.contains(current)) && first) {
1808
+ event.preventDefault();
1809
+ first.focus({ preventScroll: true });
1810
+ }
1811
+ };
1812
+ document.addEventListener("keydown", handleKeyDown, true);
1813
+ return () => {
1814
+ document.removeEventListener("keydown", handleKeyDown, true);
1815
+ if (previouslyFocused?.isConnected) previouslyFocused.focus({ preventScroll: true });
1816
+ };
1817
+ }, [active, ref]);
1818
+ }
1819
+
1820
+ // src/react/screens/context.tsx
1821
+ const EmergencyScreenContext = createContext(null);
1822
+ function EmergencyScreenProvider({ children, initialScreen = "primary" }) {
1823
+ const [state, setState] = useState({
1824
+ screen: initialScreen,
1825
+ history: []
1826
+ });
1827
+ const navigate = useCallback((next) => {
1828
+ setState((current) => {
1829
+ if (current.screen === next) return current;
1830
+ return {
1831
+ screen: next,
1832
+ history: [...current.history, current.screen]
1833
+ };
1834
+ });
1835
+ }, []);
1836
+ const back = useCallback(() => {
1837
+ setState((current) => {
1838
+ const previous = current.history.at(-1);
1839
+ if (!previous) return current;
1840
+ return {
1841
+ screen: previous,
1842
+ history: current.history.slice(0, -1)
1843
+ };
1844
+ });
1845
+ }, []);
1846
+ const reset = useCallback(() => {
1847
+ setState((current) => {
1848
+ if (current.screen === "primary" && current.history.length === 0) return current;
1849
+ return {
1850
+ screen: "primary",
1851
+ history: []
1852
+ };
1853
+ });
1854
+ }, []);
1855
+ const value = useMemo(() => ({
1856
+ screen: state.screen,
1857
+ history: state.history,
1858
+ navigate,
1859
+ back,
1860
+ reset
1861
+ }), [
1862
+ back,
1863
+ navigate,
1864
+ reset,
1865
+ state.history,
1866
+ state.screen
1867
+ ]);
1868
+ return /* @__PURE__ */ jsx(EmergencyScreenContext.Provider, {
1869
+ value,
1870
+ children
1871
+ });
1872
+ }
1873
+ function useScreenNavigation() {
1874
+ const value = useContext(EmergencyScreenContext);
1875
+ if (!value) throw new Error("useScreenNavigation must be used inside EmergencyScreenProvider.");
1876
+ return value;
1877
+ }
1878
+
1879
+ // src/react/components/EmergencyHeader/index.tsx
1880
+ function EmergencyHeader({ titleId, onClose, title = copy.panelLabel, onBack, variant = "default" }) {
1881
+ const closeOnly = variant === "close-only";
1882
+ return /* @__PURE__ */ jsxs("header", {
1883
+ className: cls("emergency-panel__header", closeOnly && "emergency-panel__header--close-only"),
1884
+ children: [
1885
+ onBack && !closeOnly ? /* @__PURE__ */ jsx("button", {
1886
+ type: "button",
1887
+ className: cls("emergency-panel__close-button"),
1888
+ "aria-label": copy.back,
1889
+ onClick: onBack,
1890
+ children: /* @__PURE__ */ jsx(Icon, {
1891
+ name: "arrow-left",
1892
+ className: cls("emergency-panel__close-icon")
1893
+ })
1894
+ }) : null,
1895
+ /* @__PURE__ */ jsx("h2", {
1896
+ className: cls("emergency-panel__title", closeOnly && "emergency-panel__sr-only"),
1897
+ id: titleId,
1898
+ tabIndex: -1,
1899
+ children: title
1900
+ }),
1901
+ /* @__PURE__ */ jsx("button", {
1902
+ type: "button",
1903
+ className: cls("emergency-panel__close-button"),
1904
+ "aria-label": copy.close,
1905
+ onClick: onClose,
1906
+ children: /* @__PURE__ */ jsx(Icon, {
1907
+ name: "close",
1908
+ className: cls("emergency-panel__close-icon")
1909
+ })
1910
+ })
1911
+ ]
1912
+ });
1913
+ }
1914
+
1915
+ // src/react/screens/layout.tsx
1916
+ /**
1917
+ * Shared shell for every screen. The wrapper is `display: contents` so the
1918
+ * header, body and footer stay direct grid rows of the emergency panel.
1919
+ */
1920
+ function EmergencyScreenLayout({ screen, titleId, header, footer, children, bodyClassName }) {
1921
+ return /* @__PURE__ */ jsxs("div", {
1922
+ className: cls("emergency-screen"),
1923
+ "data-screen": screen,
1924
+ children: [
1925
+ header,
1926
+ /* @__PURE__ */ jsx("section", {
1927
+ className: cls("emergency-panel__body", bodyClassName ?? null),
1928
+ "aria-labelledby": titleId,
1929
+ children
1930
+ }),
1931
+ footer
1932
+ ]
1933
+ });
1934
+ }
1935
+
1936
+ // src/react/screens/PlaceholderScreen.tsx
1937
+ /** Minimal in-design screen used to prove navigation before real designs land. */
1938
+ function PlaceholderScreen({ screen, titleId, title, description, onClose, onBack }) {
1939
+ return /* @__PURE__ */ jsx(EmergencyScreenLayout, {
1940
+ screen,
1941
+ titleId,
1942
+ header: /* @__PURE__ */ jsx(EmergencyHeader, {
1943
+ titleId,
1944
+ title,
1945
+ onClose,
1946
+ onBack
1947
+ }),
1948
+ footer: null,
1949
+ children: /* @__PURE__ */ jsx("div", {
1950
+ className: cls("emergency-screen__placeholder"),
1951
+ children: /* @__PURE__ */ jsx("p", {
1952
+ className: cls("emergency-screen__placeholder-text"),
1953
+ children: description
1954
+ })
1955
+ })
1956
+ });
1957
+ }
1958
+
1959
+ // src/react/screens/details/copy.ts
1960
+ const detailsCopy = {
1961
+ title: "Detalles",
1962
+ description: "Aquí podrás consultar el detalle de tus alertas disponibles. Esta pantalla estará disponible próximamente."
1963
+ };
1964
+
1965
+ // src/react/screens/details/index.tsx
1966
+ function DetailsScreen({ titleId, onClose }) {
1967
+ const { back } = useScreenNavigation();
1968
+ return /* @__PURE__ */ jsx(PlaceholderScreen, {
1969
+ screen: "details",
1970
+ titleId,
1971
+ title: detailsCopy.title,
1972
+ description: detailsCopy.description,
1973
+ onClose: () => onClose("close"),
1974
+ onBack: back
1975
+ });
1976
+ }
1977
+
1978
+ // src/core/phone.ts
1979
+ const VENEZUELA_DIALLING_CODE = "+58";
1980
+ const VENEZUELAN_MOBILE_PATTERN = /^4\d{9}$/;
1981
+ function normalizeVenezuelanPhone(input) {
1982
+ return input.replace(/\D/g, "").replace(/^0+/, "");
1983
+ }
1984
+ function formatVenezuelanPhone(input) {
1985
+ const digits = normalizeVenezuelanPhone(input).slice(0, 10);
1986
+ if (digits.length <= 3) return digits;
1987
+ return `${digits.slice(0, 3)} ${digits.slice(3)}`;
1988
+ }
1989
+ function isValidVenezuelanPhone(input) {
1990
+ return VENEZUELAN_MOBILE_PATTERN.test(normalizeVenezuelanPhone(input));
1991
+ }
1992
+ function toVenezuelanE164(input) {
1993
+ const digits = normalizeVenezuelanPhone(input);
1994
+ if (!VENEZUELAN_MOBILE_PATTERN.test(digits)) return null;
1995
+ return `${"+58"}${digits}`;
1996
+ }
1997
+
1998
+ // src/react/components/EmergencyFooter/index.tsx
1999
+ function EmergencyFooter(props) {
2000
+ const { active, busy, aborting, feedbackVisible, showSimulation = true, emergencyDisabled = false, onActivate, onRequestAbort, onRequestSimulation } = props;
2001
+ if (active) return /* @__PURE__ */ jsx("footer", {
2002
+ className: cls("emergency-panel__footer"),
2003
+ children: /* @__PURE__ */ jsxs("button", {
2004
+ type: "button",
2005
+ className: cls("emergency-status__abort-btn"),
2006
+ "aria-label": copy.abort,
2007
+ disabled: aborting || feedbackVisible,
2008
+ onClick: onRequestAbort,
2009
+ children: [copy.abort, /* @__PURE__ */ jsx(Icon, { name: "alert-triangle" })]
2010
+ })
2011
+ });
2012
+ return /* @__PURE__ */ jsxs("footer", {
2013
+ className: cls("emergency-panel__footer", !showSimulation && "emergency-panel__footer--single"),
2014
+ children: [/* @__PURE__ */ jsxs("button", {
2015
+ type: "button",
2016
+ className: cls("emergency-panel__button", "emergency-panel__button--emergency"),
2017
+ "aria-label": copy.emergencyAria,
2018
+ disabled: busy || feedbackVisible || emergencyDisabled,
2019
+ onClick: onActivate,
2020
+ children: [copy.emergency, /* @__PURE__ */ jsx(Icon, { name: "emergency" })]
2021
+ }), showSimulation ? /* @__PURE__ */ jsxs("button", {
2022
+ type: "button",
2023
+ className: cls("emergency-panel__button", "emergency-panel__button--simulation"),
2024
+ "aria-label": copy.testAria,
2025
+ disabled: busy || feedbackVisible,
2026
+ onClick: onRequestSimulation,
2027
+ children: [copy.test, /* @__PURE__ */ jsx(Icon, { name: "list-check" })]
2028
+ }) : null]
2029
+ });
2030
+ }
2031
+
2032
+ // src/react/screens/pre-emergency/copy.ts
2033
+ const preEmergencyCopy = {
2034
+ title: "Pre-emergencia",
2035
+ permissionNotice: "Debes autorizar todos los permisos de ubicación",
2036
+ phoneLabel: "Indique su número de teléfono",
2037
+ phonePlaceholder: "Ej. 0412 000 0000",
2038
+ areaCodeLabel: "Código de país",
2039
+ areaCode: "+58",
2040
+ invalidPhone: "Ingresa un número móvil venezolano válido. Ej. 412 1301234"
2041
+ };
2042
+
2043
+ // src/react/screens/pre-emergency/index.tsx
2044
+ function PreEmergencyScreen(props) {
2045
+ const { back } = useScreenNavigation();
2046
+ const phoneId = useId();
2047
+ const errorId = `${phoneId}-error`;
2048
+ const permissionId = `${phoneId}-permission`;
2049
+ const [digits, setDigits] = useState("");
2050
+ const [touched, setTouched] = useState(false);
2051
+ const valid = isValidVenezuelanPhone(digits);
2052
+ const invalid = touched && digits.length > 0 && !valid;
2053
+ const handleChange = (event) => {
2054
+ const next = normalizeVenezuelanPhone(event.target.value).slice(0, 10);
2055
+ setDigits(next);
2056
+ };
2057
+ const handleSubmit = () => {
2058
+ setTouched(true);
2059
+ if (!valid) return;
2060
+ const phone = toVenezuelanE164(digits);
2061
+ if (phone === null) return;
2062
+ props.onSetContactPhone(phone);
2063
+ props.onActivate();
2064
+ };
2065
+ return /* @__PURE__ */ jsx(EmergencyScreenLayout, {
2066
+ screen: "pre-emergency",
2067
+ titleId: props.titleId,
2068
+ bodyClassName: "emergency-panel__body--pre-emergency",
2069
+ header: /* @__PURE__ */ jsx(EmergencyHeader, {
2070
+ titleId: props.titleId,
2071
+ title: preEmergencyCopy.title,
2072
+ onClose: () => props.onClose("close"),
2073
+ onBack: back
2074
+ }),
2075
+ footer: /* @__PURE__ */ jsx(EmergencyFooter, {
2076
+ active: props.active,
2077
+ busy: props.busy,
2078
+ aborting: props.aborting,
2079
+ feedbackVisible: props.feedbackVisible,
2080
+ emergencyDisabled: !valid,
2081
+ showSimulation: false,
2082
+ onActivate: handleSubmit,
2083
+ onRequestAbort: props.onRequestAbort,
2084
+ onRequestSimulation: props.onRequestSimulation
2085
+ }),
2086
+ children: /* @__PURE__ */ jsxs("div", {
2087
+ className: cls("emergency-pre-emergency"),
2088
+ children: [/* @__PURE__ */ jsxs("section", {
2089
+ role: "note",
2090
+ "aria-labelledby": permissionId,
2091
+ className: cls("emergency-pre-emergency__notice"),
2092
+ children: [/* @__PURE__ */ jsx(Icon, {
2093
+ name: "map-pin",
2094
+ className: cls("emergency-pre-emergency__notice-icon")
2095
+ }), /* @__PURE__ */ jsx("p", {
2096
+ id: permissionId,
2097
+ className: cls("emergency-pre-emergency__notice-text"),
2098
+ children: preEmergencyCopy.permissionNotice
2099
+ })]
2100
+ }), /* @__PURE__ */ jsxs("form", {
2101
+ className: cls("emergency-pre-emergency__form"),
2102
+ noValidate: true,
2103
+ onSubmit: (event) => {
2104
+ event.preventDefault();
2105
+ handleSubmit();
2106
+ },
2107
+ onKeyDown: (event) => {
2108
+ if (event.key === "Enter") {
2109
+ event.preventDefault();
2110
+ handleSubmit();
2111
+ }
2112
+ },
2113
+ children: [
2114
+ /* @__PURE__ */ jsx("label", {
2115
+ htmlFor: phoneId,
2116
+ className: cls("emergency-pre-emergency__label"),
2117
+ children: preEmergencyCopy.phoneLabel
2118
+ }),
2119
+ /* @__PURE__ */ jsxs("div", {
2120
+ className: cls("emergency-pre-emergency__phone-row"),
2121
+ children: [/* @__PURE__ */ jsx("input", {
2122
+ type: "tel",
2123
+ "aria-label": preEmergencyCopy.areaCodeLabel,
2124
+ className: cls("emergency-pre-emergency__input", "emergency-pre-emergency__input--area-code", valid && "emergency-pre-emergency__input--valid"),
2125
+ value: preEmergencyCopy.areaCode,
2126
+ disabled: true,
2127
+ readOnly: true
2128
+ }), /* @__PURE__ */ jsx("input", {
2129
+ id: phoneId,
2130
+ type: "tel",
2131
+ inputMode: "tel",
2132
+ autoComplete: "tel-national",
2133
+ placeholder: preEmergencyCopy.phonePlaceholder,
2134
+ className: cls("emergency-pre-emergency__input", "emergency-pre-emergency__input--number", valid && "emergency-pre-emergency__input--valid", invalid && "emergency-pre-emergency__input--invalid"),
2135
+ value: formatVenezuelanPhone(digits),
2136
+ "aria-invalid": invalid,
2137
+ "aria-describedby": invalid ? errorId : void 0,
2138
+ onChange: handleChange,
2139
+ onBlur: () => setTouched(true)
2140
+ })]
2141
+ }),
2142
+ invalid ? /* @__PURE__ */ jsx("p", {
2143
+ id: errorId,
2144
+ role: "alert",
2145
+ className: cls("emergency-pre-emergency__error"),
2146
+ children: preEmergencyCopy.invalidPhone
2147
+ }) : null
2148
+ ]
2149
+ })]
2150
+ })
2151
+ });
2152
+ }
2153
+
2154
+ // src/react/components/icons/paramedical-mark.tsx
2155
+ /**
2156
+ * Neutral inline illustration used by the idle panel.
2157
+ */
2158
+ function ParamedicalMark({ className, title }) {
2159
+ return /* @__PURE__ */ jsxs("svg", {
2160
+ xmlns: "http://www.w3.org/2000/svg",
2161
+ version: "1.0",
2162
+ viewBox: "0 0 192 192",
2163
+ width: "192",
2164
+ height: "192",
2165
+ id: "Star of life",
2166
+ className,
2167
+ "aria-label": title,
2168
+ children: [
2169
+ /* @__PURE__ */ jsx("defs", { id: "defs2886" }),
2170
+ /* @__PURE__ */ jsxs("g", {
2171
+ id: "cross",
2172
+ children: [/* @__PURE__ */ jsx("path", {
2173
+ d: "m 74,133 0,52 44,0 0,-52 44,26 22,-38 L 140,96 184,70 162,32 117,58 117,7 74,7 74,58 30,32 8,70 52,96 8,121 30,159 z",
2174
+ id: "outline",
2175
+ style: {
2176
+ fill: "none",
2177
+ stroke: "#3333bb",
2178
+ strokeWidth: 6
2179
+ }
2180
+ }), /* @__PURE__ */ jsx("path", {
2181
+ d: "m 74,133 0,52 44,0 0,-52 44,26 22,-38 L 140,96 184,70 162,32 117,58 117,7 74,7 74,58 30,32 8,70 52,96 8,121 30,159 z",
2182
+ id: "inner",
2183
+ style: {
2184
+ fill: "#0000bb",
2185
+ fillRule: "evenodd",
2186
+ stroke: "#ffffff",
2187
+ strokeWidth: 2
2188
+ }
2189
+ })]
2190
+ }),
2191
+ /* @__PURE__ */ jsxs("g", {
2192
+ id: "symbols",
2193
+ style: {
2194
+ fill: "#ffffff",
2195
+ fillRule: "evenodd",
2196
+ stroke: "#3333bb",
2197
+ strokeWidth: 2
2198
+ },
2199
+ children: [/* @__PURE__ */ jsx("path", {
2200
+ d: "m 93,166 c 0,7 6,7 6,0 l 3,-138 c 0,-8 -12,-8 -12,0 l 3,135 0,3 z",
2201
+ id: "rod"
2202
+ }), /* @__PURE__ */ jsxs("g", {
2203
+ id: "snake",
2204
+ children: [/* @__PURE__ */ jsx("path", {
2205
+ d: "m 100,153 c 7,4 3,15 -3,15 2,-5 3,-9 3,-15 z m 1,-40 c 10,9 4,22 -3,26 -6,3 -6,7 -6,15 -9,-10 -4,-19 4,-24 5,-4 5,-10 5,-17 z m 1,-30 c 0,11 -17,14 -17,22 0,3 1,9 6,12 0,-11 9,-12 15,-18 7,-7 7,-19 -4,-28 0,4 0,8 0,12 z M 90,77 C 80,69 76,55 80,46 c 3,-10 20,-11 20,-3 0,8 -20,3 -13,16 l 3,5 0,13 z",
2206
+ id: "body"
2207
+ }), /* @__PURE__ */ jsx("path", {
2208
+ d: "m 95,43 c 7,-3 -7,-3 0,0 z",
2209
+ id: "eye",
2210
+ style: {
2211
+ fill: "#3333bb",
2212
+ fillRule: "evenodd",
2213
+ stroke: "none"
2214
+ }
2215
+ })]
2216
+ })]
2217
+ })
2218
+ ]
2219
+ });
2220
+ }
2221
+
1683
2222
  // src/react/components/MessageSurface/index.tsx
1684
2223
  function MessageSurface(props) {
1685
2224
  const { variant, icon, title, text, details, className, children } = props;
@@ -2004,237 +2543,410 @@ function EmergencyStatusPanel({ responders, refreshing, locale, onRefresh }) {
2004
2543
  });
2005
2544
  }
2006
2545
 
2546
+ // src/react/screens/primary/copy.ts
2547
+ const primaryCopy = {
2548
+ availabilityTitle: "Alertas Disponibles",
2549
+ recharge: "Recargar",
2550
+ details: "Detalles",
2551
+ roundSingular: "ronda disponible",
2552
+ roundPlural: "rondas disponibles"
2553
+ };
2554
+ /** Singular/plural label for the remaining rounds. */
2555
+ function roundsUnit(rounds) {
2556
+ return rounds === 1 ? primaryCopy.roundSingular : primaryCopy.roundPlural;
2557
+ }
2558
+ function formatRounds(rounds) {
2559
+ return `${rounds} ${roundsUnit(rounds)}`;
2560
+ }
2561
+
2562
+ // src/react/screens/primary/data.ts
2563
+ /**
2564
+ * Derives the remaining alert rounds from available messages and designated
2565
+ * responders. Invalid or non-positive input collapses to the minimum of one
2566
+ * round instead of producing `Infinity`, `NaN` or a negative value.
2567
+ */
2568
+ function calculateRounds(messages, responders) {
2569
+ if (!Number.isFinite(messages) || !Number.isFinite(responders)) return 1;
2570
+ const availableMessages = Math.floor(messages);
2571
+ const designatedResponders = Math.floor(responders);
2572
+ if (availableMessages <= 0 || designatedResponders <= 0) return 1;
2573
+ return Math.max(1, Math.floor(availableMessages / designatedResponders));
2574
+ }
2575
+
2576
+ // src/react/screens/primary/Availability.tsx
2577
+ function Availability({ onRecharge, onDetails }) {
2578
+ const rounds = calculateRounds(100, 3);
2579
+ return /* @__PURE__ */ jsxs("section", {
2580
+ className: cls("emergency-availability"),
2581
+ "aria-label": primaryCopy.availabilityTitle,
2582
+ children: [/* @__PURE__ */ jsxs("button", {
2583
+ type: "button",
2584
+ className: cls("emergency-availability__recharge"),
2585
+ onClick: onRecharge,
2586
+ children: [/* @__PURE__ */ jsx(Icon, { name: "credit-card-plus" }), primaryCopy.recharge]
2587
+ }), /* @__PURE__ */ jsxs("div", {
2588
+ className: cls("emergency-availability__card"),
2589
+ children: [/* @__PURE__ */ jsx("div", {
2590
+ className: cls("emergency-availability__card-header"),
2591
+ children: /* @__PURE__ */ jsx("h3", {
2592
+ className: cls("emergency-availability__card-title"),
2593
+ children: primaryCopy.availabilityTitle
2594
+ })
2595
+ }), /* @__PURE__ */ jsxs("div", {
2596
+ className: cls("emergency-availability__card-body"),
2597
+ children: [/* @__PURE__ */ jsxs("div", {
2598
+ className: cls("emergency-availability__rounds-group"),
2599
+ "aria-label": formatRounds(rounds),
2600
+ children: [/* @__PURE__ */ jsx("p", {
2601
+ className: cls("emergency-availability__rounds"),
2602
+ children: rounds
2603
+ }), /* @__PURE__ */ jsx("p", {
2604
+ className: cls("emergency-availability__label"),
2605
+ children: roundsUnit(rounds)
2606
+ })]
2607
+ }), /* @__PURE__ */ jsx("button", {
2608
+ type: "button",
2609
+ className: cls("emergency-availability__details"),
2610
+ onClick: onDetails,
2611
+ children: primaryCopy.details
2612
+ })]
2613
+ })]
2614
+ })]
2615
+ });
2616
+ }
2617
+
2007
2618
  // src/react/components/EmergencyBody/index.tsx
2008
2619
  function EmergencyBody(props) {
2009
- const { active, responders, refreshing, loading, locale, geolocationError, activationError, error, abortResult, simulationResult, onRefresh, onRetry, onDismissFeedback } = props;
2620
+ const { active, responders, refreshing, loading, locale, geolocationError, activationError, error, abortResult, simulationResult, onRefresh, onRetry, onDismissFeedback, onRecharge, onDetails } = props;
2010
2621
  const showingFeedback = geolocationError !== null || Boolean(activationError) || abortResult !== null || simulationResult !== null;
2011
- return /* @__PURE__ */ jsxs(Fragment, { children: [
2012
- !loading && !showingFeedback ? /* @__PURE__ */ jsx("div", {
2013
- className: cls("sos-img__container"),
2014
- id: "sos-img__container",
2015
- children: /* @__PURE__ */ jsx(ParamedicalMark, {
2016
- className: cls("sos-img"),
2017
- title: copy.illustrationAlt
2018
- })
2019
- }) : null,
2020
- active || loading || showingFeedback ? null : /* @__PURE__ */ jsx("div", {
2021
- className: cls("emergency-panel__body-title-container"),
2022
- children: /* @__PURE__ */ jsx("h4", {
2023
- className: cls("emergency-panel__body-title"),
2024
- children: copy.question
2622
+ const idle = !active && !loading && !showingFeedback && error === null;
2623
+ return /* @__PURE__ */ jsxs("div", {
2624
+ className: cls("emergency-panel__body-split", idle && "emergency-panel__body-split--with-availability"),
2625
+ children: [idle ? /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("div", {
2626
+ className: cls("emergency-panel__availability-region"),
2627
+ children: /* @__PURE__ */ jsx(Availability, {
2628
+ onRecharge,
2629
+ onDetails
2025
2630
  })
2631
+ }), /* @__PURE__ */ jsx("hr", {
2632
+ className: cls("emergency-panel__divider"),
2633
+ "aria-hidden": "true"
2634
+ })] }) : null, /* @__PURE__ */ jsxs("div", {
2635
+ className: cls("emergency-panel__prompt-region"),
2636
+ children: [
2637
+ !loading && !showingFeedback ? /* @__PURE__ */ jsx("div", {
2638
+ className: cls("sos-img__container"),
2639
+ id: "sos-img__container",
2640
+ children: /* @__PURE__ */ jsx(ParamedicalMark, {
2641
+ className: cls("sos-img"),
2642
+ title: copy.illustrationAlt
2643
+ })
2644
+ }) : null,
2645
+ active || loading || showingFeedback ? null : /* @__PURE__ */ jsx("div", {
2646
+ className: cls("emergency-panel__body-title-container"),
2647
+ children: /* @__PURE__ */ jsx("h4", {
2648
+ className: cls("emergency-panel__body-title"),
2649
+ children: copy.question
2650
+ })
2651
+ }),
2652
+ active && !showingFeedback ? /* @__PURE__ */ jsx(EmergencyStatusPanel, {
2653
+ responders,
2654
+ refreshing,
2655
+ locale,
2656
+ onRefresh
2657
+ }) : null,
2658
+ loading ? /* @__PURE__ */ jsx(EmergencyLoading, {}) : null,
2659
+ error ? /* @__PURE__ */ jsx(EmergencyError, {
2660
+ error,
2661
+ onRetry
2662
+ }) : null,
2663
+ /* @__PURE__ */ jsx(EmergencyFeedback, {
2664
+ abortResult,
2665
+ simulationResult,
2666
+ geolocationError,
2667
+ activationError,
2668
+ onDismiss: onDismissFeedback
2669
+ })
2670
+ ]
2671
+ })]
2672
+ });
2673
+ }
2674
+
2675
+ // src/react/screens/primary/index.tsx
2676
+ function PrimaryScreen(props) {
2677
+ const { navigate } = useScreenNavigation();
2678
+ const idle = !props.active && !props.loading && !props.feedbackVisible && props.error === null;
2679
+ const needsContactPhone = props.contactPhone === null;
2680
+ return /* @__PURE__ */ jsx(EmergencyScreenLayout, {
2681
+ screen: "primary",
2682
+ titleId: props.titleId,
2683
+ header: /* @__PURE__ */ jsx(EmergencyHeader, {
2684
+ titleId: props.titleId,
2685
+ variant: idle ? "close-only" : "default",
2686
+ onClose: () => props.onClose("close")
2026
2687
  }),
2027
- active && !showingFeedback ? /* @__PURE__ */ jsx(EmergencyStatusPanel, {
2028
- responders,
2029
- refreshing,
2030
- locale,
2031
- onRefresh
2032
- }) : null,
2033
- loading ? /* @__PURE__ */ jsx(EmergencyLoading, {}) : null,
2034
- error ? /* @__PURE__ */ jsx(EmergencyError, {
2035
- error,
2036
- onRetry
2037
- }) : null,
2038
- /* @__PURE__ */ jsx(EmergencyFeedback, {
2039
- abortResult,
2040
- simulationResult,
2041
- geolocationError,
2042
- activationError,
2043
- onDismiss: onDismissFeedback
2688
+ footer: /* @__PURE__ */ jsx(EmergencyFooter, {
2689
+ active: props.active,
2690
+ busy: props.busy,
2691
+ aborting: props.aborting,
2692
+ feedbackVisible: props.feedbackVisible,
2693
+ onActivate: needsContactPhone ? () => navigate("pre-emergency") : props.onActivate,
2694
+ onRequestAbort: props.onRequestAbort,
2695
+ onRequestSimulation: props.onRequestSimulation
2696
+ }),
2697
+ children: /* @__PURE__ */ jsx(EmergencyBody, {
2698
+ active: props.active,
2699
+ responders: props.responders,
2700
+ refreshing: props.refreshing,
2701
+ loading: props.loading,
2702
+ locale: props.locale,
2703
+ geolocationError: props.geolocationError,
2704
+ activationError: props.activationError,
2705
+ error: props.error,
2706
+ abortResult: props.abortResult,
2707
+ simulationResult: props.simulationResult,
2708
+ onRefresh: props.onRefresh,
2709
+ onRetry: props.onRetry,
2710
+ onDismissFeedback: props.onDismissFeedback,
2711
+ onRecharge: () => navigate("pricing"),
2712
+ onDetails: () => navigate("details")
2044
2713
  })
2045
- ] });
2714
+ });
2046
2715
  }
2047
2716
 
2048
- // src/react/components/EmergencyConfirmation/index.tsx
2049
- function EmergencyConfirmation(props) {
2050
- const { abortConfirmation, simulationConfirmation, onCancelAbort, onCancelSimulation, onConfirmAbort, onConfirmSimulation } = props;
2051
- const cardRef = useRef(null);
2052
- const titleId = useId();
2053
- const danger = abortConfirmation;
2054
- const open = abortConfirmation || simulationConfirmation;
2055
- useEffect(() => {
2056
- if (open) cardRef.current?.querySelector("button")?.focus();
2057
- }, [open]);
2058
- if (!open) return null;
2059
- const title = danger ? copy.abortConfirmTitle : copy.simulateConfirmTitle;
2060
- const text = danger ? copy.abortConfirmText : copy.simulateConfirmText;
2061
- const tone = danger ? "danger" : "information";
2062
- return /* @__PURE__ */ jsx("div", {
2063
- className: cls("emergency-confirmation"),
2064
- role: "dialog",
2065
- "aria-modal": "true",
2066
- "aria-labelledby": titleId,
2067
- children: /* @__PURE__ */ jsxs("div", {
2068
- ref: cardRef,
2069
- className: cls("emergency-confirmation__card"),
2070
- children: [/* @__PURE__ */ jsxs("div", {
2071
- className: cls("emergency-confirmation__body"),
2717
+ // src/react/screens/pricing/copy.ts
2718
+ const pricingCopy = {
2719
+ title: "Recargar",
2720
+ heading: "Elige tu paquete",
2721
+ subtitle: "Alertas de WhatsApp para tus contactos",
2722
+ bestValue: "MEJOR VALOR",
2723
+ choose: "Seleccionar",
2724
+ chosen: "Seleccionado",
2725
+ whatsapp: "de WhatsApp",
2726
+ rounds: "rondas de alerta",
2727
+ perMessage: "IVA Incluido",
2728
+ footnote: "Rondas calculadas con 3 contactos de emergencia. Válidas para pruebas y emergencias."
2729
+ };
2730
+
2731
+ // src/react/screens/pricing/data.ts
2732
+ /**
2733
+ * Local demo catalog. Prices and per-message labels are literal values from the
2734
+ * mock; they are not derived from the package totals.
2735
+ */
2736
+ const PRICING_PACKAGES = [{
2737
+ id: "package-1",
2738
+ label: "PAQUETE 1",
2739
+ messages: 100,
2740
+ priceLabel: "$7",
2741
+ priceCaption: "IVA incluído",
2742
+ perMessageLabel: "$0.07",
2743
+ highlighted: false
2744
+ }, {
2745
+ id: "package-2",
2746
+ label: "PAQUETE 2",
2747
+ messages: 200,
2748
+ priceLabel: "12$",
2749
+ priceCaption: "IVA incluído",
2750
+ perMessageLabel: "$0.06",
2751
+ highlighted: true,
2752
+ badge: "MEJOR VALOR"
2753
+ }];
2754
+ /** Derives the alert rounds a package provides for the demo responder count. */
2755
+ function packageRounds(pkg) {
2756
+ return calculateRounds(pkg.messages, 3);
2757
+ }
2758
+
2759
+ // src/react/screens/pricing/PackageCard.tsx
2760
+ function PackageCard({ pkg, selected, onSelect }) {
2761
+ const labelId = useId();
2762
+ const rounds = packageRounds(pkg);
2763
+ return /* @__PURE__ */ jsxs("section", {
2764
+ className: cls("emergency-pricing__package", pkg.highlighted && "emergency-pricing__package--highlighted"),
2765
+ "aria-labelledby": labelId,
2766
+ "data-selected": selected ? "true" : void 0,
2767
+ children: [
2768
+ /* @__PURE__ */ jsxs("div", {
2769
+ className: cls("emergency-pricing__package-header"),
2770
+ children: [/* @__PURE__ */ jsx("span", {
2771
+ className: cls("emergency-pricing__package-label"),
2772
+ id: labelId,
2773
+ children: pkg.label
2774
+ }), pkg.badge ? /* @__PURE__ */ jsx("span", {
2775
+ className: cls("emergency-pricing__badge"),
2776
+ children: pkg.badge
2777
+ }) : null]
2778
+ }),
2779
+ /* @__PURE__ */ jsxs("div", {
2780
+ className: cls("emergency-pricing__price-row"),
2781
+ children: [/* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsxs("h4", {
2782
+ className: cls("emergency-pricing__package-name"),
2783
+ children: [pkg.messages, " alertas"]
2784
+ }), /* @__PURE__ */ jsx("p", {
2785
+ className: cls("emergency-pricing__package-caption"),
2786
+ children: pricingCopy.whatsapp
2787
+ })] }), /* @__PURE__ */ jsxs("div", {
2788
+ className: cls("emergency-pricing__price-meta"),
2789
+ children: [/* @__PURE__ */ jsx("span", {
2790
+ className: cls("emergency-pricing__price"),
2791
+ children: pkg.priceLabel
2792
+ }), /* @__PURE__ */ jsxs("span", {
2793
+ className: cls("emergency-pricing__price-note"),
2794
+ children: [pkg.priceCaption, pkg.priceNote ? ` ${pkg.priceNote}` : null]
2795
+ })]
2796
+ })]
2797
+ }),
2798
+ /* @__PURE__ */ jsxs("div", {
2799
+ className: cls("emergency-pricing__stats"),
2072
2800
  children: [
2073
- /* @__PURE__ */ jsx("div", {
2074
- className: cls("emergency-confirmation__icon", `emergency-confirmation__icon--${tone}`),
2075
- children: /* @__PURE__ */ jsx(Icon, { name: danger ? "alert-triangle" : "info" })
2801
+ /* @__PURE__ */ jsxs("div", {
2802
+ className: cls("emergency-pricing__stat"),
2803
+ children: [/* @__PURE__ */ jsx("p", {
2804
+ className: cls("emergency-pricing__stat-value"),
2805
+ children: rounds
2806
+ }), /* @__PURE__ */ jsx("p", {
2807
+ className: cls("emergency-pricing__stat-label"),
2808
+ children: pricingCopy.rounds
2809
+ })]
2076
2810
  }),
2077
- /* @__PURE__ */ jsx("h5", {
2078
- className: cls("emergency-confirmation__title"),
2079
- id: titleId,
2080
- children: title
2811
+ /* @__PURE__ */ jsx("div", {
2812
+ className: cls("emergency-pricing__stat-divider"),
2813
+ "aria-hidden": "true"
2081
2814
  }),
2082
- /* @__PURE__ */ jsx("p", {
2083
- className: cls("emergency-confirmation__text"),
2084
- children: text
2815
+ /* @__PURE__ */ jsxs("div", {
2816
+ className: cls("emergency-pricing__stat"),
2817
+ children: [/* @__PURE__ */ jsx("p", {
2818
+ className: cls("emergency-pricing__stat-value"),
2819
+ children: pkg.perMessageLabel
2820
+ }), /* @__PURE__ */ jsx("p", {
2821
+ className: cls("emergency-pricing__stat-label"),
2822
+ children: pricingCopy.perMessage
2823
+ })]
2085
2824
  })
2086
2825
  ]
2087
- }), /* @__PURE__ */ jsxs("div", {
2088
- className: cls("emergency-confirmation__footer"),
2089
- children: [/* @__PURE__ */ jsx("button", {
2090
- type: "button",
2091
- className: cls("emergency-confirmation__button", "emergency-confirmation__button--cancel"),
2092
- onClick: danger ? onCancelAbort : onCancelSimulation,
2093
- children: danger ? copy.abortCancel : copy.simulateCancel
2094
- }), /* @__PURE__ */ jsx("button", {
2095
- type: "button",
2096
- className: cls("emergency-confirmation__button", danger ? "emergency-confirmation__button--danger" : "emergency-confirmation__button--confirm"),
2097
- onClick: danger ? onConfirmAbort : onConfirmSimulation,
2098
- children: danger ? copy.abortConfirm : copy.simulateConfirm
2099
- })]
2100
- })]
2101
- })
2826
+ }),
2827
+ /* @__PURE__ */ jsx("p", {
2828
+ className: cls("emergency-pricing__package-footnote"),
2829
+ children: pricingCopy.footnote
2830
+ }),
2831
+ /* @__PURE__ */ jsx("button", {
2832
+ type: "button",
2833
+ className: cls("emergency-pricing__select"),
2834
+ "aria-pressed": selected,
2835
+ onClick: () => onSelect(pkg.id),
2836
+ children: selected ? pricingCopy.chosen : pricingCopy.choose
2837
+ })
2838
+ ]
2102
2839
  });
2103
2840
  }
2104
2841
 
2105
- // src/react/components/EmergencyFooter/index.tsx
2106
- function EmergencyFooter(props) {
2107
- const { active, busy, aborting, feedbackVisible, onActivate, onRequestAbort, onRequestSimulation } = props;
2108
- if (active) return /* @__PURE__ */ jsx("footer", {
2109
- className: cls("emergency-panel__footer"),
2110
- children: /* @__PURE__ */ jsxs("button", {
2111
- type: "button",
2112
- className: cls("emergency-status__abort-btn"),
2113
- "aria-label": copy.abort,
2114
- disabled: aborting || feedbackVisible,
2115
- onClick: onRequestAbort,
2116
- children: [copy.abort, /* @__PURE__ */ jsx(Icon, { name: "alert-triangle" })]
2842
+ // src/react/screens/pricing/index.tsx
2843
+ function PricingScreen(props) {
2844
+ const { back } = useScreenNavigation();
2845
+ const [selected, setSelected] = useState(null);
2846
+ return /* @__PURE__ */ jsx(EmergencyScreenLayout, {
2847
+ screen: "pricing",
2848
+ titleId: props.titleId,
2849
+ bodyClassName: "emergency-panel__body--pricing",
2850
+ header: /* @__PURE__ */ jsx(EmergencyHeader, {
2851
+ titleId: props.titleId,
2852
+ title: pricingCopy.title,
2853
+ onClose: () => props.onClose("close"),
2854
+ onBack: back
2855
+ }),
2856
+ footer: null,
2857
+ children: /* @__PURE__ */ jsxs("div", {
2858
+ className: cls("emergency-pricing"),
2859
+ children: [/* @__PURE__ */ jsx("div", {
2860
+ className: cls("emergency-pricing__heading"),
2861
+ children: /* @__PURE__ */ jsxs("div", {
2862
+ className: cls("emergency-pricing__titles"),
2863
+ children: [/* @__PURE__ */ jsx("h3", {
2864
+ className: cls("emergency-pricing__title"),
2865
+ children: pricingCopy.heading
2866
+ }), /* @__PURE__ */ jsx("p", {
2867
+ className: cls("emergency-pricing__subtitle"),
2868
+ children: pricingCopy.subtitle
2869
+ })]
2870
+ })
2871
+ }), PRICING_PACKAGES.map((pkg) => /* @__PURE__ */ jsx(PackageCard, {
2872
+ pkg,
2873
+ selected: selected === pkg.id,
2874
+ onSelect: setSelected
2875
+ }, pkg.id))]
2117
2876
  })
2118
2877
  });
2119
- return /* @__PURE__ */ jsxs("footer", {
2120
- className: cls("emergency-panel__footer"),
2121
- children: [/* @__PURE__ */ jsxs("button", {
2122
- type: "button",
2123
- className: cls("emergency-panel__button", "emergency-panel__button--emergency"),
2124
- "aria-label": copy.emergencyAria,
2125
- disabled: busy || feedbackVisible,
2126
- onClick: onActivate,
2127
- children: [copy.emergency, /* @__PURE__ */ jsx(Icon, { name: "emergency" })]
2128
- }), /* @__PURE__ */ jsxs("button", {
2129
- type: "button",
2130
- className: cls("emergency-panel__button", "emergency-panel__button--simulation"),
2131
- "aria-label": copy.testAria,
2132
- disabled: busy || feedbackVisible,
2133
- onClick: onRequestSimulation,
2134
- children: [copy.test, /* @__PURE__ */ jsx(Icon, { name: "list-check" })]
2135
- })]
2136
- });
2137
2878
  }
2138
2879
 
2139
- // src/react/components/EmergencyHeader/index.tsx
2140
- function EmergencyHeader({ titleId, onClose }) {
2141
- return /* @__PURE__ */ jsxs("header", {
2142
- className: cls("emergency-panel__header"),
2143
- children: [/* @__PURE__ */ jsx("h2", {
2144
- className: cls("emergency-panel__title"),
2145
- id: titleId,
2146
- children: copy.panelLabel
2147
- }), /* @__PURE__ */ jsx("button", {
2148
- type: "button",
2149
- className: cls("emergency-panel__close-button"),
2150
- "aria-label": copy.close,
2151
- onClick: onClose,
2152
- children: /* @__PURE__ */ jsx(Icon, {
2153
- name: "close",
2154
- className: cls("emergency-panel__close-icon")
2155
- })
2156
- })]
2157
- });
2158
- }
2880
+ // src/react/screens/registry.ts
2881
+ /** Every screen the panel can render. Add one entry to add a screen. */
2882
+ const screenRegistry = {
2883
+ primary: PrimaryScreen,
2884
+ "pre-emergency": PreEmergencyScreen,
2885
+ pricing: PricingScreen,
2886
+ details: DetailsScreen
2887
+ };
2159
2888
 
2160
- // src/react/hooks/useEscapeKey.ts
2161
- function useEscapeKey(active, onEscape) {
2889
+ // src/react/components/EmergencyPanel/index.tsx
2890
+ function ActiveScreen({ screenProps, open, forcePrimary }) {
2891
+ const { screen, reset } = useScreenNavigation();
2892
+ const shouldReset = !open || forcePrimary;
2893
+ const effectiveScreen = shouldReset ? "primary" : screen;
2894
+ const previousOpen = useRef(open);
2895
+ const previousScreen = useRef(effectiveScreen);
2162
2896
  useEffect(() => {
2163
- if (!active || typeof document === "undefined") return;
2164
- const handleKeyDown = (event) => {
2165
- if (event.key === "Escape") {
2166
- event.stopPropagation();
2167
- onEscape();
2168
- }
2169
- };
2170
- document.addEventListener("keydown", handleKeyDown);
2171
- return () => {
2172
- document.removeEventListener("keydown", handleKeyDown);
2173
- };
2174
- }, [active, onEscape]);
2175
- }
2176
-
2177
- // src/react/hooks/useFocusTrap.ts
2178
- const FOCUSABLE_SELECTOR = [
2179
- "a[href]",
2180
- "button:not([disabled])",
2181
- "input:not([disabled])",
2182
- "select:not([disabled])",
2183
- "textarea:not([disabled])",
2184
- "[tabindex]:not([tabindex=\"-1\"])"
2185
- ].join(",");
2186
- /**
2187
- * Keeps keyboard focus inside the dialog while it is open and restores focus to
2188
- * the element that opened it when it closes.
2189
- */
2190
- function useFocusTrap(ref, active) {
2897
+ if (shouldReset) reset();
2898
+ }, [reset, shouldReset]);
2191
2899
  useEffect(() => {
2192
- if (!active || typeof document === "undefined") return;
2193
- const container = ref.current;
2194
- if (!container) return;
2195
- const previouslyFocused = document.activeElement instanceof HTMLElement ? document.activeElement : null;
2196
- const focusable = () => Array.from(container.querySelectorAll(FOCUSABLE_SELECTOR)).filter((element) => !element.hasAttribute("hidden") && element.getAttribute("aria-hidden") !== "true");
2197
- (focusable()[0] ?? container).focus({ preventScroll: true });
2198
- const handleKeyDown = (event) => {
2199
- if (event.key !== "Tab") return;
2200
- const items = focusable();
2201
- if (items.length === 0) {
2202
- event.preventDefault();
2203
- container.focus({ preventScroll: true });
2204
- return;
2205
- }
2206
- const first = items[0];
2207
- const last = items[items.length - 1];
2208
- const current = document.activeElement;
2209
- if (event.shiftKey && (current === first || !container.contains(current))) {
2210
- event.preventDefault();
2211
- if (last) last.focus({ preventScroll: true });
2212
- return;
2213
- }
2214
- if (!event.shiftKey && current === last && first) {
2215
- event.preventDefault();
2216
- first.focus({ preventScroll: true });
2217
- }
2218
- };
2219
- document.addEventListener("keydown", handleKeyDown, true);
2220
- return () => {
2221
- document.removeEventListener("keydown", handleKeyDown, true);
2222
- if (previouslyFocused?.isConnected) previouslyFocused.focus({ preventScroll: true });
2223
- };
2224
- }, [active, ref]);
2900
+ const wasOpen = previousOpen.current;
2901
+ const previousEffectiveScreen = previousScreen.current;
2902
+ previousOpen.current = open;
2903
+ previousScreen.current = effectiveScreen;
2904
+ if (!open || !wasOpen || previousEffectiveScreen === effectiveScreen) return;
2905
+ document.getElementById(screenProps.titleId)?.focus({ preventScroll: true });
2906
+ }, [
2907
+ effectiveScreen,
2908
+ open,
2909
+ screenProps.titleId
2910
+ ]);
2911
+ const Screen = screenRegistry[effectiveScreen];
2912
+ return /* @__PURE__ */ jsx(Screen, { ...screenProps });
2225
2913
  }
2226
-
2227
- // src/react/components/EmergencyPanel/index.tsx
2228
2914
  function EmergencyPanel(props) {
2229
- const { open, theme, active, busy, aborting, loading, locale, responders, refreshing, geolocationError, activationError, error, abortResult, simulationResult, abortConfirmation, simulationConfirmation, onClose, onActivate, onRefresh, onRetry, onDismissFeedback, onRequestAbort, onRequestSimulation, onCancelAbort, onCancelSimulation, onConfirmAbort, onConfirmSimulation } = props;
2915
+ const { open, theme, active, busy, aborting, loading, locale, responders, refreshing, geolocationError, contactPhone, activationError, error, abortResult, simulationResult, abortConfirmation, simulationConfirmation, onClose, onActivate, onRefresh, onRetry, onDismissFeedback, onRequestAbort, onRequestSimulation, onCancelAbort, onCancelSimulation, onConfirmAbort, onConfirmSimulation, onSetContactPhone } = props;
2230
2916
  const titleId = useId();
2231
2917
  const dialogRef = useRef(null);
2232
2918
  const feedbackVisible = geolocationError !== null || Boolean(activationError) || abortResult !== null || simulationResult !== null;
2919
+ const forcePrimary = active || loading || error !== null || feedbackVisible;
2233
2920
  const closeOnEscape = useCallback(() => {
2234
2921
  onClose("escape");
2235
2922
  }, [onClose]);
2236
2923
  useFocusTrap(dialogRef, open);
2237
2924
  useEscapeKey(open, closeOnEscape);
2925
+ const screenProps = {
2926
+ titleId,
2927
+ active,
2928
+ busy,
2929
+ aborting,
2930
+ loading,
2931
+ locale,
2932
+ responders,
2933
+ refreshing,
2934
+ geolocationError,
2935
+ activationError,
2936
+ error,
2937
+ abortResult,
2938
+ simulationResult,
2939
+ feedbackVisible,
2940
+ contactPhone,
2941
+ onClose,
2942
+ onActivate,
2943
+ onRefresh,
2944
+ onRetry,
2945
+ onDismissFeedback,
2946
+ onRequestAbort,
2947
+ onRequestSimulation,
2948
+ onSetContactPhone
2949
+ };
2238
2950
  return /* @__PURE__ */ jsxs("div", {
2239
2951
  ref: dialogRef,
2240
2952
  className: cls("emergency-panel", !open && "emergency-panel--hidden"),
@@ -2245,48 +2957,18 @@ function EmergencyPanel(props) {
2245
2957
  "aria-hidden": !open,
2246
2958
  inert: !open,
2247
2959
  tabIndex: -1,
2248
- children: [
2249
- /* @__PURE__ */ jsx(EmergencyHeader, {
2250
- titleId,
2251
- onClose: () => onClose("close")
2252
- }),
2253
- /* @__PURE__ */ jsx("section", {
2254
- className: cls("emergency-panel__body"),
2255
- "aria-labelledby": titleId,
2256
- children: /* @__PURE__ */ jsx(EmergencyBody, {
2257
- active,
2258
- responders,
2259
- refreshing,
2260
- loading,
2261
- locale,
2262
- geolocationError,
2263
- activationError,
2264
- error,
2265
- abortResult,
2266
- simulationResult,
2267
- onRefresh,
2268
- onRetry,
2269
- onDismissFeedback
2270
- })
2271
- }),
2272
- /* @__PURE__ */ jsx(EmergencyFooter, {
2273
- active,
2274
- busy,
2275
- aborting,
2276
- feedbackVisible,
2277
- onActivate,
2278
- onRequestAbort,
2279
- onRequestSimulation
2280
- }),
2281
- /* @__PURE__ */ jsx(EmergencyConfirmation, {
2282
- abortConfirmation,
2283
- simulationConfirmation,
2284
- onCancelAbort,
2285
- onCancelSimulation,
2286
- onConfirmAbort,
2287
- onConfirmSimulation
2288
- })
2289
- ]
2960
+ children: [/* @__PURE__ */ jsx(EmergencyScreenProvider, { children: /* @__PURE__ */ jsx(ActiveScreen, {
2961
+ screenProps,
2962
+ open,
2963
+ forcePrimary
2964
+ }) }), /* @__PURE__ */ jsx(EmergencyConfirmation, {
2965
+ abortConfirmation,
2966
+ simulationConfirmation,
2967
+ onCancelAbort,
2968
+ onCancelSimulation,
2969
+ onConfirmAbort,
2970
+ onConfirmSimulation
2971
+ })]
2290
2972
  });
2291
2973
  }
2292
2974
 
@@ -2370,6 +3052,7 @@ function EmergencyRoot() {
2370
3052
  const responders = useEmergencyStore((state) => state.responders);
2371
3053
  const resumeAvailable = useEmergencyStore((state) => state.resumeAvailable);
2372
3054
  const geolocationError = useEmergencyStore((state) => state.geolocationError);
3055
+ const contactPhone = useEmergencyStore((state) => state.contactPhone);
2373
3056
  const lastError = useEmergencyStore((state) => state.lastError);
2374
3057
  const abortResult = useEmergencyStore((state) => state.abortResult);
2375
3058
  const simulationResult = useEmergencyStore((state) => state.simulationResult);
@@ -2395,6 +3078,7 @@ function EmergencyRoot() {
2395
3078
  responders,
2396
3079
  refreshing: phase.kind === "refreshing",
2397
3080
  geolocationError,
3081
+ contactPhone,
2398
3082
  activationError,
2399
3083
  error: showError ? error : null,
2400
3084
  abortResult,
@@ -2421,7 +3105,8 @@ function EmergencyRoot() {
2421
3105
  },
2422
3106
  onConfirmSimulation: () => {
2423
3107
  client.simulate();
2424
- }
3108
+ },
3109
+ onSetContactPhone: (phone) => client.setContactPhone(phone)
2425
3110
  })] });
2426
3111
  }
2427
3112
 
@@ -2453,6 +3138,7 @@ function createFailedClient(error) {
2453
3138
  lastError: error,
2454
3139
  geolocation: null,
2455
3140
  geolocationError: null,
3141
+ contactPhone: null,
2456
3142
  realtimeStatus: "idle",
2457
3143
  abortResult: null,
2458
3144
  simulationResult: null,
@@ -2481,7 +3167,8 @@ function createFailedClient(error) {
2481
3167
  cancelAbortConfirmation: noop,
2482
3168
  requestSimulationConfirmation: noop,
2483
3169
  cancelSimulationConfirmation: noop,
2484
- dismissFeedback: noop
3170
+ dismissFeedback: noop,
3171
+ setContactPhone: noop
2485
3172
  };
2486
3173
  }
2487
3174
  function attachTrigger(trigger, client) {
@@ -2570,6 +3257,7 @@ function mount(options) {
2570
3257
  requestSimulationConfirmation: () => client.requestSimulationConfirmation(),
2571
3258
  cancelSimulationConfirmation: () => client.cancelSimulationConfirmation(),
2572
3259
  dismissFeedback: () => client.dismissFeedback(),
3260
+ setContactPhone: (phone) => client.setContactPhone(phone),
2573
3261
  destroy
2574
3262
  };
2575
3263
  }