@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.cjs CHANGED
@@ -391,6 +391,7 @@ function createBffClient(options) {
391
391
  const copy = {
392
392
  panelLabel: "Panel de emergencia",
393
393
  close: "Cerrar panel de emergencia",
394
+ back: "Volver",
394
395
  question: "¿Tiene usted una emergencia?",
395
396
  illustrationAlt: "Ícono paramédico",
396
397
  emergency: "Emergencia",
@@ -762,6 +763,7 @@ function createEmergencyStore(config) {
762
763
  lastError: null,
763
764
  geolocation: null,
764
765
  geolocationError: null,
766
+ contactPhone: null,
765
767
  realtimeStatus: "idle",
766
768
  abortResult: null,
767
769
  simulationResult: null,
@@ -1204,7 +1206,8 @@ function createEmergencyController(deps) {
1204
1206
  patch({
1205
1207
  panelOpen: false,
1206
1208
  abortConfirmation: false,
1207
- simulationConfirmation: false
1209
+ simulationConfirmation: false,
1210
+ contactPhone: null
1208
1211
  });
1209
1212
  events.emit("panel:closed", { reason });
1210
1213
  }
@@ -1253,6 +1256,7 @@ function createEmergencyController(deps) {
1253
1256
  lastError: null,
1254
1257
  geolocation: null,
1255
1258
  geolocationError: null,
1259
+ contactPhone: null,
1256
1260
  realtimeStatus: "idle",
1257
1261
  abortResult: null,
1258
1262
  simulationResult: null,
@@ -1294,7 +1298,8 @@ function createEmergencyController(deps) {
1294
1298
  geolocationError: null,
1295
1299
  ...state.geolocationError !== null || state.abortResult?.ok === false || state.simulationResult?.ok === false || state.phase.kind === "ready" && state.lastError !== null ? { lastError: null } : {}
1296
1300
  });
1297
- }
1301
+ },
1302
+ setContactPhone: (phone) => patch({ contactPhone: phone })
1298
1303
  },
1299
1304
  store,
1300
1305
  events
@@ -1469,74 +1474,6 @@ function validateConfig(config) {
1469
1474
  };
1470
1475
  }
1471
1476
 
1472
- // src/react/components/icons/paramedical-mark.tsx
1473
- /**
1474
- * Neutral inline illustration used by the idle panel.
1475
- */
1476
- function ParamedicalMark({ className, title }) {
1477
- return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("svg", {
1478
- xmlns: "http://www.w3.org/2000/svg",
1479
- version: "1.0",
1480
- viewBox: "0 0 192 192",
1481
- width: "192",
1482
- height: "192",
1483
- id: "Star of life",
1484
- className,
1485
- "aria-label": title,
1486
- children: [
1487
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("defs", { id: "defs2886" }),
1488
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("g", {
1489
- id: "cross",
1490
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
1491
- 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",
1492
- id: "outline",
1493
- style: {
1494
- fill: "none",
1495
- stroke: "#3333bb",
1496
- strokeWidth: 6
1497
- }
1498
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
1499
- 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",
1500
- id: "inner",
1501
- style: {
1502
- fill: "#0000bb",
1503
- fillRule: "evenodd",
1504
- stroke: "#ffffff",
1505
- strokeWidth: 2
1506
- }
1507
- })]
1508
- }),
1509
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("g", {
1510
- id: "symbols",
1511
- style: {
1512
- fill: "#ffffff",
1513
- fillRule: "evenodd",
1514
- stroke: "#3333bb",
1515
- strokeWidth: 2
1516
- },
1517
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
1518
- 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",
1519
- id: "rod"
1520
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("g", {
1521
- id: "snake",
1522
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
1523
- 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",
1524
- id: "body"
1525
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
1526
- d: "m 95,43 c 7,-3 -7,-3 0,0 z",
1527
- id: "eye",
1528
- style: {
1529
- fill: "#3333bb",
1530
- fillRule: "evenodd",
1531
- stroke: "none"
1532
- }
1533
- })]
1534
- })]
1535
- })
1536
- ]
1537
- });
1538
- }
1539
-
1540
1477
  // src/react/components/Icon/index.tsx
1541
1478
  /** Icons whose artwork does not fit the default 24x24 grid need their own canvas. */
1542
1479
  const viewBoxes = { emergency: "0 0 640 640" };
@@ -1593,6 +1530,23 @@ const paths = {
1593
1530
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "M12 7h.01" })
1594
1531
  ] }),
1595
1532
  "arrow-left": /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "M19 12H5" }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "m12 19-7-7 7-7" })] }),
1533
+ "map-pin": /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "M12 22s7-6.2 7-12a7 7 0 1 0-14 0c0 5.8 7 12 7 12z" }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("circle", {
1534
+ cx: "12",
1535
+ cy: "10",
1536
+ r: "2.5"
1537
+ })] }),
1538
+ "credit-card-plus": /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
1539
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "M12 18H4a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2v5" }),
1540
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "M18 12h.01" }),
1541
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "M19 22v-6" }),
1542
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "m22 19-3-3-3 3" }),
1543
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "M6 12h.01" }),
1544
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("circle", {
1545
+ cx: "12",
1546
+ cy: "12",
1547
+ r: "2"
1548
+ })
1549
+ ] }),
1596
1550
  emergency: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(react_jsx_runtime.Fragment, { children: /* @__PURE__ */ (0, react_jsx_runtime.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" }) })
1597
1551
  };
1598
1552
  function Icon({ name, className, title }) {
@@ -1622,6 +1576,16 @@ var panel_module_default = {
1622
1576
  "de-emergency-slide-in": "de-emergency-de-emergency-slide-in-nA5Hwa",
1623
1577
  "de-emergency-slide-up": "de-emergency-de-emergency-slide-up-nA5Hwa",
1624
1578
  "de-emergency-spin": "de-emergency-de-emergency-spin-nA5Hwa",
1579
+ "emergency-availability": "de-emergency-emergency-availability-nA5Hwa",
1580
+ "emergency-availability__card": "de-emergency-emergency-availability__card-nA5Hwa",
1581
+ "emergency-availability__card-body": "de-emergency-emergency-availability__card-body-nA5Hwa",
1582
+ "emergency-availability__card-header": "de-emergency-emergency-availability__card-header-nA5Hwa",
1583
+ "emergency-availability__card-title": "de-emergency-emergency-availability__card-title-nA5Hwa",
1584
+ "emergency-availability__details": "de-emergency-emergency-availability__details-nA5Hwa",
1585
+ "emergency-availability__label": "de-emergency-emergency-availability__label-nA5Hwa",
1586
+ "emergency-availability__recharge": "de-emergency-emergency-availability__recharge-nA5Hwa",
1587
+ "emergency-availability__rounds": "de-emergency-emergency-availability__rounds-nA5Hwa",
1588
+ "emergency-availability__rounds-group": "de-emergency-emergency-availability__rounds-group-nA5Hwa",
1625
1589
  "emergency-confirmation": "de-emergency-emergency-confirmation-nA5Hwa",
1626
1590
  "emergency-confirmation__body": "de-emergency-emergency-confirmation__body-nA5Hwa",
1627
1591
  "emergency-confirmation__button": "de-emergency-emergency-confirmation__button-nA5Hwa",
@@ -1646,7 +1610,12 @@ var panel_module_default = {
1646
1610
  "emergency-feedback": "de-emergency-emergency-feedback-nA5Hwa",
1647
1611
  "emergency-feedback__dismiss": "de-emergency-emergency-feedback__dismiss-nA5Hwa",
1648
1612
  "emergency-panel": "de-emergency-emergency-panel-nA5Hwa",
1613
+ "emergency-panel__availability-region": "de-emergency-emergency-panel__availability-region-nA5Hwa",
1649
1614
  "emergency-panel__body": "de-emergency-emergency-panel__body-nA5Hwa",
1615
+ "emergency-panel__body--pre-emergency": "de-emergency-emergency-panel__body--pre-emergency-nA5Hwa",
1616
+ "emergency-panel__body--pricing": "de-emergency-emergency-panel__body--pricing-nA5Hwa",
1617
+ "emergency-panel__body-split": "de-emergency-emergency-panel__body-split-nA5Hwa",
1618
+ "emergency-panel__body-split--with-availability": "de-emergency-emergency-panel__body-split--with-availability-nA5Hwa",
1650
1619
  "emergency-panel__body-title": "de-emergency-emergency-panel__body-title-nA5Hwa",
1651
1620
  "emergency-panel__body-title-container": "de-emergency-emergency-panel__body-title-container-nA5Hwa",
1652
1621
  "emergency-panel__button": "de-emergency-emergency-panel__button-nA5Hwa",
@@ -1654,16 +1623,60 @@ var panel_module_default = {
1654
1623
  "emergency-panel__button--simulation": "de-emergency-emergency-panel__button--simulation-nA5Hwa",
1655
1624
  "emergency-panel__close-button": "de-emergency-emergency-panel__close-button-nA5Hwa",
1656
1625
  "emergency-panel__close-icon": "de-emergency-emergency-panel__close-icon-nA5Hwa",
1626
+ "emergency-panel__divider": "de-emergency-emergency-panel__divider-nA5Hwa",
1657
1627
  "emergency-panel__footer": "de-emergency-emergency-panel__footer-nA5Hwa",
1628
+ "emergency-panel__footer--single": "de-emergency-emergency-panel__footer--single-nA5Hwa",
1658
1629
  "emergency-panel__header": "de-emergency-emergency-panel__header-nA5Hwa",
1630
+ "emergency-panel__header--close-only": "de-emergency-emergency-panel__header--close-only-nA5Hwa",
1631
+ "emergency-panel__prompt-region": "de-emergency-emergency-panel__prompt-region-nA5Hwa",
1632
+ "emergency-panel__sr-only": "de-emergency-emergency-panel__sr-only-nA5Hwa",
1659
1633
  "emergency-panel__title": "de-emergency-emergency-panel__title-nA5Hwa",
1660
1634
  "emergency-panel--hidden": "de-emergency-emergency-panel--hidden-nA5Hwa",
1635
+ "emergency-pre-emergency": "de-emergency-emergency-pre-emergency-nA5Hwa",
1636
+ "emergency-pre-emergency__error": "de-emergency-emergency-pre-emergency__error-nA5Hwa",
1637
+ "emergency-pre-emergency__form": "de-emergency-emergency-pre-emergency__form-nA5Hwa",
1638
+ "emergency-pre-emergency__input": "de-emergency-emergency-pre-emergency__input-nA5Hwa",
1639
+ "emergency-pre-emergency__input--area-code": "de-emergency-emergency-pre-emergency__input--area-code-nA5Hwa",
1640
+ "emergency-pre-emergency__input--invalid": "de-emergency-emergency-pre-emergency__input--invalid-nA5Hwa",
1641
+ "emergency-pre-emergency__input--number": "de-emergency-emergency-pre-emergency__input--number-nA5Hwa",
1642
+ "emergency-pre-emergency__input--valid": "de-emergency-emergency-pre-emergency__input--valid-nA5Hwa",
1643
+ "emergency-pre-emergency__label": "de-emergency-emergency-pre-emergency__label-nA5Hwa",
1644
+ "emergency-pre-emergency__notice": "de-emergency-emergency-pre-emergency__notice-nA5Hwa",
1645
+ "emergency-pre-emergency__notice-icon": "de-emergency-emergency-pre-emergency__notice-icon-nA5Hwa",
1646
+ "emergency-pre-emergency__notice-text": "de-emergency-emergency-pre-emergency__notice-text-nA5Hwa",
1647
+ "emergency-pre-emergency__phone-row": "de-emergency-emergency-pre-emergency__phone-row-nA5Hwa",
1648
+ "emergency-pricing": "de-emergency-emergency-pricing-nA5Hwa",
1649
+ "emergency-pricing__badge": "de-emergency-emergency-pricing__badge-nA5Hwa",
1650
+ "emergency-pricing__heading": "de-emergency-emergency-pricing__heading-nA5Hwa",
1651
+ "emergency-pricing__package": "de-emergency-emergency-pricing__package-nA5Hwa",
1652
+ "emergency-pricing__package--highlighted": "de-emergency-emergency-pricing__package--highlighted-nA5Hwa",
1653
+ "emergency-pricing__package-caption": "de-emergency-emergency-pricing__package-caption-nA5Hwa",
1654
+ "emergency-pricing__package-footnote": "de-emergency-emergency-pricing__package-footnote-nA5Hwa",
1655
+ "emergency-pricing__package-header": "de-emergency-emergency-pricing__package-header-nA5Hwa",
1656
+ "emergency-pricing__package-label": "de-emergency-emergency-pricing__package-label-nA5Hwa",
1657
+ "emergency-pricing__package-name": "de-emergency-emergency-pricing__package-name-nA5Hwa",
1658
+ "emergency-pricing__price": "de-emergency-emergency-pricing__price-nA5Hwa",
1659
+ "emergency-pricing__price-meta": "de-emergency-emergency-pricing__price-meta-nA5Hwa",
1660
+ "emergency-pricing__price-note": "de-emergency-emergency-pricing__price-note-nA5Hwa",
1661
+ "emergency-pricing__price-row": "de-emergency-emergency-pricing__price-row-nA5Hwa",
1662
+ "emergency-pricing__select": "de-emergency-emergency-pricing__select-nA5Hwa",
1663
+ "emergency-pricing__stat": "de-emergency-emergency-pricing__stat-nA5Hwa",
1664
+ "emergency-pricing__stat-divider": "de-emergency-emergency-pricing__stat-divider-nA5Hwa",
1665
+ "emergency-pricing__stat-label": "de-emergency-emergency-pricing__stat-label-nA5Hwa",
1666
+ "emergency-pricing__stat-value": "de-emergency-emergency-pricing__stat-value-nA5Hwa",
1667
+ "emergency-pricing__stats": "de-emergency-emergency-pricing__stats-nA5Hwa",
1668
+ "emergency-pricing__subtitle": "de-emergency-emergency-pricing__subtitle-nA5Hwa",
1669
+ "emergency-pricing__title": "de-emergency-emergency-pricing__title-nA5Hwa",
1670
+ "emergency-pricing__titles": "de-emergency-emergency-pricing__titles-nA5Hwa",
1661
1671
  "emergency-resume-notification-button": "de-emergency-emergency-resume-notification-button-nA5Hwa",
1662
1672
  "emergency-resume-notification-card": "de-emergency-emergency-resume-notification-card-nA5Hwa",
1663
1673
  "emergency-resume-notification-icon": "de-emergency-emergency-resume-notification-icon-nA5Hwa",
1664
1674
  "emergency-resume-notification-message": "de-emergency-emergency-resume-notification-message-nA5Hwa",
1665
1675
  "emergency-resume-notification-overlay": "de-emergency-emergency-resume-notification-overlay-nA5Hwa",
1666
1676
  "emergency-resume-notification-title": "de-emergency-emergency-resume-notification-title-nA5Hwa",
1677
+ "emergency-screen": "de-emergency-emergency-screen-nA5Hwa",
1678
+ "emergency-screen__placeholder": "de-emergency-emergency-screen__placeholder-nA5Hwa",
1679
+ "emergency-screen__placeholder-text": "de-emergency-emergency-screen__placeholder-text-nA5Hwa",
1667
1680
  "emergency-status__abort-btn": "de-emergency-emergency-status__abort-btn-nA5Hwa",
1668
1681
  "emergency-status__body": "de-emergency-emergency-status__body-nA5Hwa",
1669
1682
  "emergency-status__cell": "de-emergency-emergency-status__cell-nA5Hwa",
@@ -1709,6 +1722,532 @@ function cls(...names) {
1709
1722
  return names.filter((name) => Boolean(name)).map((name) => panel_module_default[name] ?? "").filter(Boolean).join(" ");
1710
1723
  }
1711
1724
 
1725
+ // src/react/components/EmergencyConfirmation/index.tsx
1726
+ function EmergencyConfirmation(props) {
1727
+ const { abortConfirmation, simulationConfirmation, onCancelAbort, onCancelSimulation, onConfirmAbort, onConfirmSimulation } = props;
1728
+ const cardRef = (0, react.useRef)(null);
1729
+ const titleId = (0, react.useId)();
1730
+ const danger = abortConfirmation;
1731
+ const open = abortConfirmation || simulationConfirmation;
1732
+ (0, react.useEffect)(() => {
1733
+ if (open) cardRef.current?.querySelector("button")?.focus();
1734
+ }, [open]);
1735
+ if (!open) return null;
1736
+ const title = danger ? copy.abortConfirmTitle : copy.simulateConfirmTitle;
1737
+ const text = danger ? copy.abortConfirmText : copy.simulateConfirmText;
1738
+ const tone = danger ? "danger" : "information";
1739
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1740
+ className: cls("emergency-confirmation"),
1741
+ role: "dialog",
1742
+ "aria-modal": "true",
1743
+ "aria-labelledby": titleId,
1744
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1745
+ ref: cardRef,
1746
+ className: cls("emergency-confirmation__card"),
1747
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1748
+ className: cls("emergency-confirmation__body"),
1749
+ children: [
1750
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1751
+ className: cls("emergency-confirmation__icon", `emergency-confirmation__icon--${tone}`),
1752
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon, { name: danger ? "alert-triangle" : "info" })
1753
+ }),
1754
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("h5", {
1755
+ className: cls("emergency-confirmation__title"),
1756
+ id: titleId,
1757
+ children: title
1758
+ }),
1759
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1760
+ className: cls("emergency-confirmation__text"),
1761
+ children: text
1762
+ })
1763
+ ]
1764
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1765
+ className: cls("emergency-confirmation__footer"),
1766
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1767
+ type: "button",
1768
+ className: cls("emergency-confirmation__button", "emergency-confirmation__button--cancel"),
1769
+ onClick: danger ? onCancelAbort : onCancelSimulation,
1770
+ children: danger ? copy.abortCancel : copy.simulateCancel
1771
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1772
+ type: "button",
1773
+ className: cls("emergency-confirmation__button", danger ? "emergency-confirmation__button--danger" : "emergency-confirmation__button--confirm"),
1774
+ onClick: danger ? onConfirmAbort : onConfirmSimulation,
1775
+ children: danger ? copy.abortConfirm : copy.simulateConfirm
1776
+ })]
1777
+ })]
1778
+ })
1779
+ });
1780
+ }
1781
+
1782
+ // src/react/hooks/useEscapeKey.ts
1783
+ function useEscapeKey(active, onEscape) {
1784
+ (0, react.useEffect)(() => {
1785
+ if (!active || typeof document === "undefined") return;
1786
+ const handleKeyDown = (event) => {
1787
+ if (event.key === "Escape") {
1788
+ event.stopPropagation();
1789
+ onEscape();
1790
+ }
1791
+ };
1792
+ document.addEventListener("keydown", handleKeyDown);
1793
+ return () => {
1794
+ document.removeEventListener("keydown", handleKeyDown);
1795
+ };
1796
+ }, [active, onEscape]);
1797
+ }
1798
+
1799
+ // src/react/hooks/useFocusTrap.ts
1800
+ const FOCUSABLE_SELECTOR = [
1801
+ "a[href]",
1802
+ "button:not([disabled])",
1803
+ "input:not([disabled])",
1804
+ "select:not([disabled])",
1805
+ "textarea:not([disabled])",
1806
+ "[tabindex]:not([tabindex=\"-1\"])"
1807
+ ].join(",");
1808
+ /**
1809
+ * Keeps keyboard focus inside the dialog while it is open and restores focus to
1810
+ * the element that opened it when it closes.
1811
+ */
1812
+ function useFocusTrap(ref, active) {
1813
+ (0, react.useEffect)(() => {
1814
+ if (!active || typeof document === "undefined") return;
1815
+ const container = ref.current;
1816
+ if (!container) return;
1817
+ const previouslyFocused = document.activeElement instanceof HTMLElement ? document.activeElement : null;
1818
+ const focusable = () => Array.from(container.querySelectorAll(FOCUSABLE_SELECTOR)).filter((element) => !element.hasAttribute("hidden") && element.getAttribute("aria-hidden") !== "true");
1819
+ (focusable()[0] ?? container).focus({ preventScroll: true });
1820
+ const handleKeyDown = (event) => {
1821
+ if (event.key !== "Tab") return;
1822
+ const items = focusable();
1823
+ if (items.length === 0) {
1824
+ event.preventDefault();
1825
+ container.focus({ preventScroll: true });
1826
+ return;
1827
+ }
1828
+ const first = items[0];
1829
+ const last = items[items.length - 1];
1830
+ const current = document.activeElement;
1831
+ if (event.shiftKey && (current === first || !container.contains(current))) {
1832
+ event.preventDefault();
1833
+ if (last) last.focus({ preventScroll: true });
1834
+ return;
1835
+ }
1836
+ if (!event.shiftKey && (current === last || !container.contains(current)) && first) {
1837
+ event.preventDefault();
1838
+ first.focus({ preventScroll: true });
1839
+ }
1840
+ };
1841
+ document.addEventListener("keydown", handleKeyDown, true);
1842
+ return () => {
1843
+ document.removeEventListener("keydown", handleKeyDown, true);
1844
+ if (previouslyFocused?.isConnected) previouslyFocused.focus({ preventScroll: true });
1845
+ };
1846
+ }, [active, ref]);
1847
+ }
1848
+
1849
+ // src/react/screens/context.tsx
1850
+ const EmergencyScreenContext = (0, react.createContext)(null);
1851
+ function EmergencyScreenProvider({ children, initialScreen = "primary" }) {
1852
+ const [state, setState] = (0, react.useState)({
1853
+ screen: initialScreen,
1854
+ history: []
1855
+ });
1856
+ const navigate = (0, react.useCallback)((next) => {
1857
+ setState((current) => {
1858
+ if (current.screen === next) return current;
1859
+ return {
1860
+ screen: next,
1861
+ history: [...current.history, current.screen]
1862
+ };
1863
+ });
1864
+ }, []);
1865
+ const back = (0, react.useCallback)(() => {
1866
+ setState((current) => {
1867
+ const previous = current.history.at(-1);
1868
+ if (!previous) return current;
1869
+ return {
1870
+ screen: previous,
1871
+ history: current.history.slice(0, -1)
1872
+ };
1873
+ });
1874
+ }, []);
1875
+ const reset = (0, react.useCallback)(() => {
1876
+ setState((current) => {
1877
+ if (current.screen === "primary" && current.history.length === 0) return current;
1878
+ return {
1879
+ screen: "primary",
1880
+ history: []
1881
+ };
1882
+ });
1883
+ }, []);
1884
+ const value = (0, react.useMemo)(() => ({
1885
+ screen: state.screen,
1886
+ history: state.history,
1887
+ navigate,
1888
+ back,
1889
+ reset
1890
+ }), [
1891
+ back,
1892
+ navigate,
1893
+ reset,
1894
+ state.history,
1895
+ state.screen
1896
+ ]);
1897
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(EmergencyScreenContext.Provider, {
1898
+ value,
1899
+ children
1900
+ });
1901
+ }
1902
+ function useScreenNavigation() {
1903
+ const value = (0, react.useContext)(EmergencyScreenContext);
1904
+ if (!value) throw new Error("useScreenNavigation must be used inside EmergencyScreenProvider.");
1905
+ return value;
1906
+ }
1907
+
1908
+ // src/react/components/EmergencyHeader/index.tsx
1909
+ function EmergencyHeader({ titleId, onClose, title = copy.panelLabel, onBack, variant = "default" }) {
1910
+ const closeOnly = variant === "close-only";
1911
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("header", {
1912
+ className: cls("emergency-panel__header", closeOnly && "emergency-panel__header--close-only"),
1913
+ children: [
1914
+ onBack && !closeOnly ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1915
+ type: "button",
1916
+ className: cls("emergency-panel__close-button"),
1917
+ "aria-label": copy.back,
1918
+ onClick: onBack,
1919
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon, {
1920
+ name: "arrow-left",
1921
+ className: cls("emergency-panel__close-icon")
1922
+ })
1923
+ }) : null,
1924
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("h2", {
1925
+ className: cls("emergency-panel__title", closeOnly && "emergency-panel__sr-only"),
1926
+ id: titleId,
1927
+ tabIndex: -1,
1928
+ children: title
1929
+ }),
1930
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1931
+ type: "button",
1932
+ className: cls("emergency-panel__close-button"),
1933
+ "aria-label": copy.close,
1934
+ onClick: onClose,
1935
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon, {
1936
+ name: "close",
1937
+ className: cls("emergency-panel__close-icon")
1938
+ })
1939
+ })
1940
+ ]
1941
+ });
1942
+ }
1943
+
1944
+ // src/react/screens/layout.tsx
1945
+ /**
1946
+ * Shared shell for every screen. The wrapper is `display: contents` so the
1947
+ * header, body and footer stay direct grid rows of the emergency panel.
1948
+ */
1949
+ function EmergencyScreenLayout({ screen, titleId, header, footer, children, bodyClassName }) {
1950
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1951
+ className: cls("emergency-screen"),
1952
+ "data-screen": screen,
1953
+ children: [
1954
+ header,
1955
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("section", {
1956
+ className: cls("emergency-panel__body", bodyClassName ?? null),
1957
+ "aria-labelledby": titleId,
1958
+ children
1959
+ }),
1960
+ footer
1961
+ ]
1962
+ });
1963
+ }
1964
+
1965
+ // src/react/screens/PlaceholderScreen.tsx
1966
+ /** Minimal in-design screen used to prove navigation before real designs land. */
1967
+ function PlaceholderScreen({ screen, titleId, title, description, onClose, onBack }) {
1968
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(EmergencyScreenLayout, {
1969
+ screen,
1970
+ titleId,
1971
+ header: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(EmergencyHeader, {
1972
+ titleId,
1973
+ title,
1974
+ onClose,
1975
+ onBack
1976
+ }),
1977
+ footer: null,
1978
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1979
+ className: cls("emergency-screen__placeholder"),
1980
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1981
+ className: cls("emergency-screen__placeholder-text"),
1982
+ children: description
1983
+ })
1984
+ })
1985
+ });
1986
+ }
1987
+
1988
+ // src/react/screens/details/copy.ts
1989
+ const detailsCopy = {
1990
+ title: "Detalles",
1991
+ description: "Aquí podrás consultar el detalle de tus alertas disponibles. Esta pantalla estará disponible próximamente."
1992
+ };
1993
+
1994
+ // src/react/screens/details/index.tsx
1995
+ function DetailsScreen({ titleId, onClose }) {
1996
+ const { back } = useScreenNavigation();
1997
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(PlaceholderScreen, {
1998
+ screen: "details",
1999
+ titleId,
2000
+ title: detailsCopy.title,
2001
+ description: detailsCopy.description,
2002
+ onClose: () => onClose("close"),
2003
+ onBack: back
2004
+ });
2005
+ }
2006
+
2007
+ // src/core/phone.ts
2008
+ const VENEZUELA_DIALLING_CODE = "+58";
2009
+ const VENEZUELAN_MOBILE_PATTERN = /^4\d{9}$/;
2010
+ function normalizeVenezuelanPhone(input) {
2011
+ return input.replace(/\D/g, "").replace(/^0+/, "");
2012
+ }
2013
+ function formatVenezuelanPhone(input) {
2014
+ const digits = normalizeVenezuelanPhone(input).slice(0, 10);
2015
+ if (digits.length <= 3) return digits;
2016
+ return `${digits.slice(0, 3)} ${digits.slice(3)}`;
2017
+ }
2018
+ function isValidVenezuelanPhone(input) {
2019
+ return VENEZUELAN_MOBILE_PATTERN.test(normalizeVenezuelanPhone(input));
2020
+ }
2021
+ function toVenezuelanE164(input) {
2022
+ const digits = normalizeVenezuelanPhone(input);
2023
+ if (!VENEZUELAN_MOBILE_PATTERN.test(digits)) return null;
2024
+ return `${"+58"}${digits}`;
2025
+ }
2026
+
2027
+ // src/react/components/EmergencyFooter/index.tsx
2028
+ function EmergencyFooter(props) {
2029
+ const { active, busy, aborting, feedbackVisible, showSimulation = true, emergencyDisabled = false, onActivate, onRequestAbort, onRequestSimulation } = props;
2030
+ if (active) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("footer", {
2031
+ className: cls("emergency-panel__footer"),
2032
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
2033
+ type: "button",
2034
+ className: cls("emergency-status__abort-btn"),
2035
+ "aria-label": copy.abort,
2036
+ disabled: aborting || feedbackVisible,
2037
+ onClick: onRequestAbort,
2038
+ children: [copy.abort, /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon, { name: "alert-triangle" })]
2039
+ })
2040
+ });
2041
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("footer", {
2042
+ className: cls("emergency-panel__footer", !showSimulation && "emergency-panel__footer--single"),
2043
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
2044
+ type: "button",
2045
+ className: cls("emergency-panel__button", "emergency-panel__button--emergency"),
2046
+ "aria-label": copy.emergencyAria,
2047
+ disabled: busy || feedbackVisible || emergencyDisabled,
2048
+ onClick: onActivate,
2049
+ children: [copy.emergency, /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon, { name: "emergency" })]
2050
+ }), showSimulation ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
2051
+ type: "button",
2052
+ className: cls("emergency-panel__button", "emergency-panel__button--simulation"),
2053
+ "aria-label": copy.testAria,
2054
+ disabled: busy || feedbackVisible,
2055
+ onClick: onRequestSimulation,
2056
+ children: [copy.test, /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon, { name: "list-check" })]
2057
+ }) : null]
2058
+ });
2059
+ }
2060
+
2061
+ // src/react/screens/pre-emergency/copy.ts
2062
+ const preEmergencyCopy = {
2063
+ title: "Pre-emergencia",
2064
+ permissionNotice: "Debes autorizar todos los permisos de ubicación",
2065
+ phoneLabel: "Indique su número de teléfono",
2066
+ phonePlaceholder: "Ej. 0412 000 0000",
2067
+ areaCodeLabel: "Código de país",
2068
+ areaCode: "+58",
2069
+ invalidPhone: "Ingresa un número móvil venezolano válido. Ej. 412 1301234"
2070
+ };
2071
+
2072
+ // src/react/screens/pre-emergency/index.tsx
2073
+ function PreEmergencyScreen(props) {
2074
+ const { back } = useScreenNavigation();
2075
+ const phoneId = (0, react.useId)();
2076
+ const errorId = `${phoneId}-error`;
2077
+ const permissionId = `${phoneId}-permission`;
2078
+ const [digits, setDigits] = (0, react.useState)("");
2079
+ const [touched, setTouched] = (0, react.useState)(false);
2080
+ const valid = isValidVenezuelanPhone(digits);
2081
+ const invalid = touched && digits.length > 0 && !valid;
2082
+ const handleChange = (event) => {
2083
+ const next = normalizeVenezuelanPhone(event.target.value).slice(0, 10);
2084
+ setDigits(next);
2085
+ };
2086
+ const handleSubmit = () => {
2087
+ setTouched(true);
2088
+ if (!valid) return;
2089
+ const phone = toVenezuelanE164(digits);
2090
+ if (phone === null) return;
2091
+ props.onSetContactPhone(phone);
2092
+ props.onActivate();
2093
+ };
2094
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(EmergencyScreenLayout, {
2095
+ screen: "pre-emergency",
2096
+ titleId: props.titleId,
2097
+ bodyClassName: "emergency-panel__body--pre-emergency",
2098
+ header: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(EmergencyHeader, {
2099
+ titleId: props.titleId,
2100
+ title: preEmergencyCopy.title,
2101
+ onClose: () => props.onClose("close"),
2102
+ onBack: back
2103
+ }),
2104
+ footer: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(EmergencyFooter, {
2105
+ active: props.active,
2106
+ busy: props.busy,
2107
+ aborting: props.aborting,
2108
+ feedbackVisible: props.feedbackVisible,
2109
+ emergencyDisabled: !valid,
2110
+ showSimulation: false,
2111
+ onActivate: handleSubmit,
2112
+ onRequestAbort: props.onRequestAbort,
2113
+ onRequestSimulation: props.onRequestSimulation
2114
+ }),
2115
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2116
+ className: cls("emergency-pre-emergency"),
2117
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
2118
+ role: "note",
2119
+ "aria-labelledby": permissionId,
2120
+ className: cls("emergency-pre-emergency__notice"),
2121
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon, {
2122
+ name: "map-pin",
2123
+ className: cls("emergency-pre-emergency__notice-icon")
2124
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
2125
+ id: permissionId,
2126
+ className: cls("emergency-pre-emergency__notice-text"),
2127
+ children: preEmergencyCopy.permissionNotice
2128
+ })]
2129
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("form", {
2130
+ className: cls("emergency-pre-emergency__form"),
2131
+ noValidate: true,
2132
+ onSubmit: (event) => {
2133
+ event.preventDefault();
2134
+ handleSubmit();
2135
+ },
2136
+ onKeyDown: (event) => {
2137
+ if (event.key === "Enter") {
2138
+ event.preventDefault();
2139
+ handleSubmit();
2140
+ }
2141
+ },
2142
+ children: [
2143
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("label", {
2144
+ htmlFor: phoneId,
2145
+ className: cls("emergency-pre-emergency__label"),
2146
+ children: preEmergencyCopy.phoneLabel
2147
+ }),
2148
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2149
+ className: cls("emergency-pre-emergency__phone-row"),
2150
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
2151
+ type: "tel",
2152
+ "aria-label": preEmergencyCopy.areaCodeLabel,
2153
+ className: cls("emergency-pre-emergency__input", "emergency-pre-emergency__input--area-code", valid && "emergency-pre-emergency__input--valid"),
2154
+ value: preEmergencyCopy.areaCode,
2155
+ disabled: true,
2156
+ readOnly: true
2157
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
2158
+ id: phoneId,
2159
+ type: "tel",
2160
+ inputMode: "tel",
2161
+ autoComplete: "tel-national",
2162
+ placeholder: preEmergencyCopy.phonePlaceholder,
2163
+ className: cls("emergency-pre-emergency__input", "emergency-pre-emergency__input--number", valid && "emergency-pre-emergency__input--valid", invalid && "emergency-pre-emergency__input--invalid"),
2164
+ value: formatVenezuelanPhone(digits),
2165
+ "aria-invalid": invalid,
2166
+ "aria-describedby": invalid ? errorId : void 0,
2167
+ onChange: handleChange,
2168
+ onBlur: () => setTouched(true)
2169
+ })]
2170
+ }),
2171
+ invalid ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
2172
+ id: errorId,
2173
+ role: "alert",
2174
+ className: cls("emergency-pre-emergency__error"),
2175
+ children: preEmergencyCopy.invalidPhone
2176
+ }) : null
2177
+ ]
2178
+ })]
2179
+ })
2180
+ });
2181
+ }
2182
+
2183
+ // src/react/components/icons/paramedical-mark.tsx
2184
+ /**
2185
+ * Neutral inline illustration used by the idle panel.
2186
+ */
2187
+ function ParamedicalMark({ className, title }) {
2188
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("svg", {
2189
+ xmlns: "http://www.w3.org/2000/svg",
2190
+ version: "1.0",
2191
+ viewBox: "0 0 192 192",
2192
+ width: "192",
2193
+ height: "192",
2194
+ id: "Star of life",
2195
+ className,
2196
+ "aria-label": title,
2197
+ children: [
2198
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("defs", { id: "defs2886" }),
2199
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("g", {
2200
+ id: "cross",
2201
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
2202
+ 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",
2203
+ id: "outline",
2204
+ style: {
2205
+ fill: "none",
2206
+ stroke: "#3333bb",
2207
+ strokeWidth: 6
2208
+ }
2209
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
2210
+ 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",
2211
+ id: "inner",
2212
+ style: {
2213
+ fill: "#0000bb",
2214
+ fillRule: "evenodd",
2215
+ stroke: "#ffffff",
2216
+ strokeWidth: 2
2217
+ }
2218
+ })]
2219
+ }),
2220
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("g", {
2221
+ id: "symbols",
2222
+ style: {
2223
+ fill: "#ffffff",
2224
+ fillRule: "evenodd",
2225
+ stroke: "#3333bb",
2226
+ strokeWidth: 2
2227
+ },
2228
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
2229
+ 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",
2230
+ id: "rod"
2231
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("g", {
2232
+ id: "snake",
2233
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
2234
+ 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",
2235
+ id: "body"
2236
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
2237
+ d: "m 95,43 c 7,-3 -7,-3 0,0 z",
2238
+ id: "eye",
2239
+ style: {
2240
+ fill: "#3333bb",
2241
+ fillRule: "evenodd",
2242
+ stroke: "none"
2243
+ }
2244
+ })]
2245
+ })]
2246
+ })
2247
+ ]
2248
+ });
2249
+ }
2250
+
1712
2251
  // src/react/components/MessageSurface/index.tsx
1713
2252
  function MessageSurface(props) {
1714
2253
  const { variant, icon, title, text, details, className, children } = props;
@@ -2033,237 +2572,410 @@ function EmergencyStatusPanel({ responders, refreshing, locale, onRefresh }) {
2033
2572
  });
2034
2573
  }
2035
2574
 
2575
+ // src/react/screens/primary/copy.ts
2576
+ const primaryCopy = {
2577
+ availabilityTitle: "Alertas Disponibles",
2578
+ recharge: "Recargar",
2579
+ details: "Detalles",
2580
+ roundSingular: "ronda disponible",
2581
+ roundPlural: "rondas disponibles"
2582
+ };
2583
+ /** Singular/plural label for the remaining rounds. */
2584
+ function roundsUnit(rounds) {
2585
+ return rounds === 1 ? primaryCopy.roundSingular : primaryCopy.roundPlural;
2586
+ }
2587
+ function formatRounds(rounds) {
2588
+ return `${rounds} ${roundsUnit(rounds)}`;
2589
+ }
2590
+
2591
+ // src/react/screens/primary/data.ts
2592
+ /**
2593
+ * Derives the remaining alert rounds from available messages and designated
2594
+ * responders. Invalid or non-positive input collapses to the minimum of one
2595
+ * round instead of producing `Infinity`, `NaN` or a negative value.
2596
+ */
2597
+ function calculateRounds(messages, responders) {
2598
+ if (!Number.isFinite(messages) || !Number.isFinite(responders)) return 1;
2599
+ const availableMessages = Math.floor(messages);
2600
+ const designatedResponders = Math.floor(responders);
2601
+ if (availableMessages <= 0 || designatedResponders <= 0) return 1;
2602
+ return Math.max(1, Math.floor(availableMessages / designatedResponders));
2603
+ }
2604
+
2605
+ // src/react/screens/primary/Availability.tsx
2606
+ function Availability({ onRecharge, onDetails }) {
2607
+ const rounds = calculateRounds(100, 3);
2608
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
2609
+ className: cls("emergency-availability"),
2610
+ "aria-label": primaryCopy.availabilityTitle,
2611
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
2612
+ type: "button",
2613
+ className: cls("emergency-availability__recharge"),
2614
+ onClick: onRecharge,
2615
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon, { name: "credit-card-plus" }), primaryCopy.recharge]
2616
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2617
+ className: cls("emergency-availability__card"),
2618
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2619
+ className: cls("emergency-availability__card-header"),
2620
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
2621
+ className: cls("emergency-availability__card-title"),
2622
+ children: primaryCopy.availabilityTitle
2623
+ })
2624
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2625
+ className: cls("emergency-availability__card-body"),
2626
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2627
+ className: cls("emergency-availability__rounds-group"),
2628
+ "aria-label": formatRounds(rounds),
2629
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
2630
+ className: cls("emergency-availability__rounds"),
2631
+ children: rounds
2632
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
2633
+ className: cls("emergency-availability__label"),
2634
+ children: roundsUnit(rounds)
2635
+ })]
2636
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2637
+ type: "button",
2638
+ className: cls("emergency-availability__details"),
2639
+ onClick: onDetails,
2640
+ children: primaryCopy.details
2641
+ })]
2642
+ })]
2643
+ })]
2644
+ });
2645
+ }
2646
+
2036
2647
  // src/react/components/EmergencyBody/index.tsx
2037
2648
  function EmergencyBody(props) {
2038
- const { active, responders, refreshing, loading, locale, geolocationError, activationError, error, abortResult, simulationResult, onRefresh, onRetry, onDismissFeedback } = props;
2649
+ const { active, responders, refreshing, loading, locale, geolocationError, activationError, error, abortResult, simulationResult, onRefresh, onRetry, onDismissFeedback, onRecharge, onDetails } = props;
2039
2650
  const showingFeedback = geolocationError !== null || Boolean(activationError) || abortResult !== null || simulationResult !== null;
2040
- return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
2041
- !loading && !showingFeedback ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2042
- className: cls("sos-img__container"),
2043
- id: "sos-img__container",
2044
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ParamedicalMark, {
2045
- className: cls("sos-img"),
2046
- title: copy.illustrationAlt
2047
- })
2048
- }) : null,
2049
- active || loading || showingFeedback ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2050
- className: cls("emergency-panel__body-title-container"),
2051
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("h4", {
2052
- className: cls("emergency-panel__body-title"),
2053
- children: copy.question
2651
+ const idle = !active && !loading && !showingFeedback && error === null;
2652
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2653
+ className: cls("emergency-panel__body-split", idle && "emergency-panel__body-split--with-availability"),
2654
+ children: [idle ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2655
+ className: cls("emergency-panel__availability-region"),
2656
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Availability, {
2657
+ onRecharge,
2658
+ onDetails
2054
2659
  })
2660
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("hr", {
2661
+ className: cls("emergency-panel__divider"),
2662
+ "aria-hidden": "true"
2663
+ })] }) : null, /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2664
+ className: cls("emergency-panel__prompt-region"),
2665
+ children: [
2666
+ !loading && !showingFeedback ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2667
+ className: cls("sos-img__container"),
2668
+ id: "sos-img__container",
2669
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ParamedicalMark, {
2670
+ className: cls("sos-img"),
2671
+ title: copy.illustrationAlt
2672
+ })
2673
+ }) : null,
2674
+ active || loading || showingFeedback ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2675
+ className: cls("emergency-panel__body-title-container"),
2676
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("h4", {
2677
+ className: cls("emergency-panel__body-title"),
2678
+ children: copy.question
2679
+ })
2680
+ }),
2681
+ active && !showingFeedback ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(EmergencyStatusPanel, {
2682
+ responders,
2683
+ refreshing,
2684
+ locale,
2685
+ onRefresh
2686
+ }) : null,
2687
+ loading ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(EmergencyLoading, {}) : null,
2688
+ error ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(EmergencyError, {
2689
+ error,
2690
+ onRetry
2691
+ }) : null,
2692
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(EmergencyFeedback, {
2693
+ abortResult,
2694
+ simulationResult,
2695
+ geolocationError,
2696
+ activationError,
2697
+ onDismiss: onDismissFeedback
2698
+ })
2699
+ ]
2700
+ })]
2701
+ });
2702
+ }
2703
+
2704
+ // src/react/screens/primary/index.tsx
2705
+ function PrimaryScreen(props) {
2706
+ const { navigate } = useScreenNavigation();
2707
+ const idle = !props.active && !props.loading && !props.feedbackVisible && props.error === null;
2708
+ const needsContactPhone = props.contactPhone === null;
2709
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(EmergencyScreenLayout, {
2710
+ screen: "primary",
2711
+ titleId: props.titleId,
2712
+ header: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(EmergencyHeader, {
2713
+ titleId: props.titleId,
2714
+ variant: idle ? "close-only" : "default",
2715
+ onClose: () => props.onClose("close")
2055
2716
  }),
2056
- active && !showingFeedback ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(EmergencyStatusPanel, {
2057
- responders,
2058
- refreshing,
2059
- locale,
2060
- onRefresh
2061
- }) : null,
2062
- loading ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(EmergencyLoading, {}) : null,
2063
- error ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(EmergencyError, {
2064
- error,
2065
- onRetry
2066
- }) : null,
2067
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(EmergencyFeedback, {
2068
- abortResult,
2069
- simulationResult,
2070
- geolocationError,
2071
- activationError,
2072
- onDismiss: onDismissFeedback
2717
+ footer: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(EmergencyFooter, {
2718
+ active: props.active,
2719
+ busy: props.busy,
2720
+ aborting: props.aborting,
2721
+ feedbackVisible: props.feedbackVisible,
2722
+ onActivate: needsContactPhone ? () => navigate("pre-emergency") : props.onActivate,
2723
+ onRequestAbort: props.onRequestAbort,
2724
+ onRequestSimulation: props.onRequestSimulation
2725
+ }),
2726
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(EmergencyBody, {
2727
+ active: props.active,
2728
+ responders: props.responders,
2729
+ refreshing: props.refreshing,
2730
+ loading: props.loading,
2731
+ locale: props.locale,
2732
+ geolocationError: props.geolocationError,
2733
+ activationError: props.activationError,
2734
+ error: props.error,
2735
+ abortResult: props.abortResult,
2736
+ simulationResult: props.simulationResult,
2737
+ onRefresh: props.onRefresh,
2738
+ onRetry: props.onRetry,
2739
+ onDismissFeedback: props.onDismissFeedback,
2740
+ onRecharge: () => navigate("pricing"),
2741
+ onDetails: () => navigate("details")
2073
2742
  })
2074
- ] });
2743
+ });
2075
2744
  }
2076
2745
 
2077
- // src/react/components/EmergencyConfirmation/index.tsx
2078
- function EmergencyConfirmation(props) {
2079
- const { abortConfirmation, simulationConfirmation, onCancelAbort, onCancelSimulation, onConfirmAbort, onConfirmSimulation } = props;
2080
- const cardRef = (0, react.useRef)(null);
2081
- const titleId = (0, react.useId)();
2082
- const danger = abortConfirmation;
2083
- const open = abortConfirmation || simulationConfirmation;
2084
- (0, react.useEffect)(() => {
2085
- if (open) cardRef.current?.querySelector("button")?.focus();
2086
- }, [open]);
2087
- if (!open) return null;
2088
- const title = danger ? copy.abortConfirmTitle : copy.simulateConfirmTitle;
2089
- const text = danger ? copy.abortConfirmText : copy.simulateConfirmText;
2090
- const tone = danger ? "danger" : "information";
2091
- return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2092
- className: cls("emergency-confirmation"),
2093
- role: "dialog",
2094
- "aria-modal": "true",
2095
- "aria-labelledby": titleId,
2096
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2097
- ref: cardRef,
2098
- className: cls("emergency-confirmation__card"),
2099
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2100
- className: cls("emergency-confirmation__body"),
2746
+ // src/react/screens/pricing/copy.ts
2747
+ const pricingCopy = {
2748
+ title: "Recargar",
2749
+ heading: "Elige tu paquete",
2750
+ subtitle: "Alertas de WhatsApp para tus contactos",
2751
+ bestValue: "MEJOR VALOR",
2752
+ choose: "Seleccionar",
2753
+ chosen: "Seleccionado",
2754
+ whatsapp: "de WhatsApp",
2755
+ rounds: "rondas de alerta",
2756
+ perMessage: "IVA Incluido",
2757
+ footnote: "Rondas calculadas con 3 contactos de emergencia. Válidas para pruebas y emergencias."
2758
+ };
2759
+
2760
+ // src/react/screens/pricing/data.ts
2761
+ /**
2762
+ * Local demo catalog. Prices and per-message labels are literal values from the
2763
+ * mock; they are not derived from the package totals.
2764
+ */
2765
+ const PRICING_PACKAGES = [{
2766
+ id: "package-1",
2767
+ label: "PAQUETE 1",
2768
+ messages: 100,
2769
+ priceLabel: "$7",
2770
+ priceCaption: "IVA incluído",
2771
+ perMessageLabel: "$0.07",
2772
+ highlighted: false
2773
+ }, {
2774
+ id: "package-2",
2775
+ label: "PAQUETE 2",
2776
+ messages: 200,
2777
+ priceLabel: "12$",
2778
+ priceCaption: "IVA incluído",
2779
+ perMessageLabel: "$0.06",
2780
+ highlighted: true,
2781
+ badge: "MEJOR VALOR"
2782
+ }];
2783
+ /** Derives the alert rounds a package provides for the demo responder count. */
2784
+ function packageRounds(pkg) {
2785
+ return calculateRounds(pkg.messages, 3);
2786
+ }
2787
+
2788
+ // src/react/screens/pricing/PackageCard.tsx
2789
+ function PackageCard({ pkg, selected, onSelect }) {
2790
+ const labelId = (0, react.useId)();
2791
+ const rounds = packageRounds(pkg);
2792
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
2793
+ className: cls("emergency-pricing__package", pkg.highlighted && "emergency-pricing__package--highlighted"),
2794
+ "aria-labelledby": labelId,
2795
+ "data-selected": selected ? "true" : void 0,
2796
+ children: [
2797
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2798
+ className: cls("emergency-pricing__package-header"),
2799
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2800
+ className: cls("emergency-pricing__package-label"),
2801
+ id: labelId,
2802
+ children: pkg.label
2803
+ }), pkg.badge ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2804
+ className: cls("emergency-pricing__badge"),
2805
+ children: pkg.badge
2806
+ }) : null]
2807
+ }),
2808
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2809
+ className: cls("emergency-pricing__price-row"),
2810
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("h4", {
2811
+ className: cls("emergency-pricing__package-name"),
2812
+ children: [pkg.messages, " alertas"]
2813
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
2814
+ className: cls("emergency-pricing__package-caption"),
2815
+ children: pricingCopy.whatsapp
2816
+ })] }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2817
+ className: cls("emergency-pricing__price-meta"),
2818
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2819
+ className: cls("emergency-pricing__price"),
2820
+ children: pkg.priceLabel
2821
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
2822
+ className: cls("emergency-pricing__price-note"),
2823
+ children: [pkg.priceCaption, pkg.priceNote ? ` ${pkg.priceNote}` : null]
2824
+ })]
2825
+ })]
2826
+ }),
2827
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2828
+ className: cls("emergency-pricing__stats"),
2101
2829
  children: [
2102
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2103
- className: cls("emergency-confirmation__icon", `emergency-confirmation__icon--${tone}`),
2104
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon, { name: danger ? "alert-triangle" : "info" })
2830
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2831
+ className: cls("emergency-pricing__stat"),
2832
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
2833
+ className: cls("emergency-pricing__stat-value"),
2834
+ children: rounds
2835
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
2836
+ className: cls("emergency-pricing__stat-label"),
2837
+ children: pricingCopy.rounds
2838
+ })]
2105
2839
  }),
2106
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("h5", {
2107
- className: cls("emergency-confirmation__title"),
2108
- id: titleId,
2109
- children: title
2840
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2841
+ className: cls("emergency-pricing__stat-divider"),
2842
+ "aria-hidden": "true"
2110
2843
  }),
2111
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
2112
- className: cls("emergency-confirmation__text"),
2113
- children: text
2844
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2845
+ className: cls("emergency-pricing__stat"),
2846
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
2847
+ className: cls("emergency-pricing__stat-value"),
2848
+ children: pkg.perMessageLabel
2849
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
2850
+ className: cls("emergency-pricing__stat-label"),
2851
+ children: pricingCopy.perMessage
2852
+ })]
2114
2853
  })
2115
2854
  ]
2116
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2117
- className: cls("emergency-confirmation__footer"),
2118
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2119
- type: "button",
2120
- className: cls("emergency-confirmation__button", "emergency-confirmation__button--cancel"),
2121
- onClick: danger ? onCancelAbort : onCancelSimulation,
2122
- children: danger ? copy.abortCancel : copy.simulateCancel
2123
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2124
- type: "button",
2125
- className: cls("emergency-confirmation__button", danger ? "emergency-confirmation__button--danger" : "emergency-confirmation__button--confirm"),
2126
- onClick: danger ? onConfirmAbort : onConfirmSimulation,
2127
- children: danger ? copy.abortConfirm : copy.simulateConfirm
2128
- })]
2129
- })]
2130
- })
2855
+ }),
2856
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
2857
+ className: cls("emergency-pricing__package-footnote"),
2858
+ children: pricingCopy.footnote
2859
+ }),
2860
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2861
+ type: "button",
2862
+ className: cls("emergency-pricing__select"),
2863
+ "aria-pressed": selected,
2864
+ onClick: () => onSelect(pkg.id),
2865
+ children: selected ? pricingCopy.chosen : pricingCopy.choose
2866
+ })
2867
+ ]
2131
2868
  });
2132
2869
  }
2133
2870
 
2134
- // src/react/components/EmergencyFooter/index.tsx
2135
- function EmergencyFooter(props) {
2136
- const { active, busy, aborting, feedbackVisible, onActivate, onRequestAbort, onRequestSimulation } = props;
2137
- if (active) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("footer", {
2138
- className: cls("emergency-panel__footer"),
2139
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
2140
- type: "button",
2141
- className: cls("emergency-status__abort-btn"),
2142
- "aria-label": copy.abort,
2143
- disabled: aborting || feedbackVisible,
2144
- onClick: onRequestAbort,
2145
- children: [copy.abort, /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon, { name: "alert-triangle" })]
2871
+ // src/react/screens/pricing/index.tsx
2872
+ function PricingScreen(props) {
2873
+ const { back } = useScreenNavigation();
2874
+ const [selected, setSelected] = (0, react.useState)(null);
2875
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(EmergencyScreenLayout, {
2876
+ screen: "pricing",
2877
+ titleId: props.titleId,
2878
+ bodyClassName: "emergency-panel__body--pricing",
2879
+ header: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(EmergencyHeader, {
2880
+ titleId: props.titleId,
2881
+ title: pricingCopy.title,
2882
+ onClose: () => props.onClose("close"),
2883
+ onBack: back
2884
+ }),
2885
+ footer: null,
2886
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2887
+ className: cls("emergency-pricing"),
2888
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2889
+ className: cls("emergency-pricing__heading"),
2890
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2891
+ className: cls("emergency-pricing__titles"),
2892
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
2893
+ className: cls("emergency-pricing__title"),
2894
+ children: pricingCopy.heading
2895
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
2896
+ className: cls("emergency-pricing__subtitle"),
2897
+ children: pricingCopy.subtitle
2898
+ })]
2899
+ })
2900
+ }), PRICING_PACKAGES.map((pkg) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(PackageCard, {
2901
+ pkg,
2902
+ selected: selected === pkg.id,
2903
+ onSelect: setSelected
2904
+ }, pkg.id))]
2146
2905
  })
2147
2906
  });
2148
- return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("footer", {
2149
- className: cls("emergency-panel__footer"),
2150
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
2151
- type: "button",
2152
- className: cls("emergency-panel__button", "emergency-panel__button--emergency"),
2153
- "aria-label": copy.emergencyAria,
2154
- disabled: busy || feedbackVisible,
2155
- onClick: onActivate,
2156
- children: [copy.emergency, /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon, { name: "emergency" })]
2157
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
2158
- type: "button",
2159
- className: cls("emergency-panel__button", "emergency-panel__button--simulation"),
2160
- "aria-label": copy.testAria,
2161
- disabled: busy || feedbackVisible,
2162
- onClick: onRequestSimulation,
2163
- children: [copy.test, /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon, { name: "list-check" })]
2164
- })]
2165
- });
2166
2907
  }
2167
2908
 
2168
- // src/react/components/EmergencyHeader/index.tsx
2169
- function EmergencyHeader({ titleId, onClose }) {
2170
- return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("header", {
2171
- className: cls("emergency-panel__header"),
2172
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h2", {
2173
- className: cls("emergency-panel__title"),
2174
- id: titleId,
2175
- children: copy.panelLabel
2176
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2177
- type: "button",
2178
- className: cls("emergency-panel__close-button"),
2179
- "aria-label": copy.close,
2180
- onClick: onClose,
2181
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon, {
2182
- name: "close",
2183
- className: cls("emergency-panel__close-icon")
2184
- })
2185
- })]
2186
- });
2187
- }
2909
+ // src/react/screens/registry.ts
2910
+ /** Every screen the panel can render. Add one entry to add a screen. */
2911
+ const screenRegistry = {
2912
+ primary: PrimaryScreen,
2913
+ "pre-emergency": PreEmergencyScreen,
2914
+ pricing: PricingScreen,
2915
+ details: DetailsScreen
2916
+ };
2188
2917
 
2189
- // src/react/hooks/useEscapeKey.ts
2190
- function useEscapeKey(active, onEscape) {
2918
+ // src/react/components/EmergencyPanel/index.tsx
2919
+ function ActiveScreen({ screenProps, open, forcePrimary }) {
2920
+ const { screen, reset } = useScreenNavigation();
2921
+ const shouldReset = !open || forcePrimary;
2922
+ const effectiveScreen = shouldReset ? "primary" : screen;
2923
+ const previousOpen = (0, react.useRef)(open);
2924
+ const previousScreen = (0, react.useRef)(effectiveScreen);
2191
2925
  (0, react.useEffect)(() => {
2192
- if (!active || typeof document === "undefined") return;
2193
- const handleKeyDown = (event) => {
2194
- if (event.key === "Escape") {
2195
- event.stopPropagation();
2196
- onEscape();
2197
- }
2198
- };
2199
- document.addEventListener("keydown", handleKeyDown);
2200
- return () => {
2201
- document.removeEventListener("keydown", handleKeyDown);
2202
- };
2203
- }, [active, onEscape]);
2204
- }
2205
-
2206
- // src/react/hooks/useFocusTrap.ts
2207
- const FOCUSABLE_SELECTOR = [
2208
- "a[href]",
2209
- "button:not([disabled])",
2210
- "input:not([disabled])",
2211
- "select:not([disabled])",
2212
- "textarea:not([disabled])",
2213
- "[tabindex]:not([tabindex=\"-1\"])"
2214
- ].join(",");
2215
- /**
2216
- * Keeps keyboard focus inside the dialog while it is open and restores focus to
2217
- * the element that opened it when it closes.
2218
- */
2219
- function useFocusTrap(ref, active) {
2926
+ if (shouldReset) reset();
2927
+ }, [reset, shouldReset]);
2220
2928
  (0, react.useEffect)(() => {
2221
- if (!active || typeof document === "undefined") return;
2222
- const container = ref.current;
2223
- if (!container) return;
2224
- const previouslyFocused = document.activeElement instanceof HTMLElement ? document.activeElement : null;
2225
- const focusable = () => Array.from(container.querySelectorAll(FOCUSABLE_SELECTOR)).filter((element) => !element.hasAttribute("hidden") && element.getAttribute("aria-hidden") !== "true");
2226
- (focusable()[0] ?? container).focus({ preventScroll: true });
2227
- const handleKeyDown = (event) => {
2228
- if (event.key !== "Tab") return;
2229
- const items = focusable();
2230
- if (items.length === 0) {
2231
- event.preventDefault();
2232
- container.focus({ preventScroll: true });
2233
- return;
2234
- }
2235
- const first = items[0];
2236
- const last = items[items.length - 1];
2237
- const current = document.activeElement;
2238
- if (event.shiftKey && (current === first || !container.contains(current))) {
2239
- event.preventDefault();
2240
- if (last) last.focus({ preventScroll: true });
2241
- return;
2242
- }
2243
- if (!event.shiftKey && current === last && first) {
2244
- event.preventDefault();
2245
- first.focus({ preventScroll: true });
2246
- }
2247
- };
2248
- document.addEventListener("keydown", handleKeyDown, true);
2249
- return () => {
2250
- document.removeEventListener("keydown", handleKeyDown, true);
2251
- if (previouslyFocused?.isConnected) previouslyFocused.focus({ preventScroll: true });
2252
- };
2253
- }, [active, ref]);
2929
+ const wasOpen = previousOpen.current;
2930
+ const previousEffectiveScreen = previousScreen.current;
2931
+ previousOpen.current = open;
2932
+ previousScreen.current = effectiveScreen;
2933
+ if (!open || !wasOpen || previousEffectiveScreen === effectiveScreen) return;
2934
+ document.getElementById(screenProps.titleId)?.focus({ preventScroll: true });
2935
+ }, [
2936
+ effectiveScreen,
2937
+ open,
2938
+ screenProps.titleId
2939
+ ]);
2940
+ const Screen = screenRegistry[effectiveScreen];
2941
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Screen, { ...screenProps });
2254
2942
  }
2255
-
2256
- // src/react/components/EmergencyPanel/index.tsx
2257
2943
  function EmergencyPanel(props) {
2258
- 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;
2944
+ 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;
2259
2945
  const titleId = (0, react.useId)();
2260
2946
  const dialogRef = (0, react.useRef)(null);
2261
2947
  const feedbackVisible = geolocationError !== null || Boolean(activationError) || abortResult !== null || simulationResult !== null;
2948
+ const forcePrimary = active || loading || error !== null || feedbackVisible;
2262
2949
  const closeOnEscape = (0, react.useCallback)(() => {
2263
2950
  onClose("escape");
2264
2951
  }, [onClose]);
2265
2952
  useFocusTrap(dialogRef, open);
2266
2953
  useEscapeKey(open, closeOnEscape);
2954
+ const screenProps = {
2955
+ titleId,
2956
+ active,
2957
+ busy,
2958
+ aborting,
2959
+ loading,
2960
+ locale,
2961
+ responders,
2962
+ refreshing,
2963
+ geolocationError,
2964
+ activationError,
2965
+ error,
2966
+ abortResult,
2967
+ simulationResult,
2968
+ feedbackVisible,
2969
+ contactPhone,
2970
+ onClose,
2971
+ onActivate,
2972
+ onRefresh,
2973
+ onRetry,
2974
+ onDismissFeedback,
2975
+ onRequestAbort,
2976
+ onRequestSimulation,
2977
+ onSetContactPhone
2978
+ };
2267
2979
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2268
2980
  ref: dialogRef,
2269
2981
  className: cls("emergency-panel", !open && "emergency-panel--hidden"),
@@ -2274,48 +2986,18 @@ function EmergencyPanel(props) {
2274
2986
  "aria-hidden": !open,
2275
2987
  inert: !open,
2276
2988
  tabIndex: -1,
2277
- children: [
2278
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(EmergencyHeader, {
2279
- titleId,
2280
- onClose: () => onClose("close")
2281
- }),
2282
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("section", {
2283
- className: cls("emergency-panel__body"),
2284
- "aria-labelledby": titleId,
2285
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(EmergencyBody, {
2286
- active,
2287
- responders,
2288
- refreshing,
2289
- loading,
2290
- locale,
2291
- geolocationError,
2292
- activationError,
2293
- error,
2294
- abortResult,
2295
- simulationResult,
2296
- onRefresh,
2297
- onRetry,
2298
- onDismissFeedback
2299
- })
2300
- }),
2301
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(EmergencyFooter, {
2302
- active,
2303
- busy,
2304
- aborting,
2305
- feedbackVisible,
2306
- onActivate,
2307
- onRequestAbort,
2308
- onRequestSimulation
2309
- }),
2310
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(EmergencyConfirmation, {
2311
- abortConfirmation,
2312
- simulationConfirmation,
2313
- onCancelAbort,
2314
- onCancelSimulation,
2315
- onConfirmAbort,
2316
- onConfirmSimulation
2317
- })
2318
- ]
2989
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(EmergencyScreenProvider, { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ActiveScreen, {
2990
+ screenProps,
2991
+ open,
2992
+ forcePrimary
2993
+ }) }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(EmergencyConfirmation, {
2994
+ abortConfirmation,
2995
+ simulationConfirmation,
2996
+ onCancelAbort,
2997
+ onCancelSimulation,
2998
+ onConfirmAbort,
2999
+ onConfirmSimulation
3000
+ })]
2319
3001
  });
2320
3002
  }
2321
3003
 
@@ -2399,6 +3081,7 @@ function EmergencyRoot() {
2399
3081
  const responders = useEmergencyStore((state) => state.responders);
2400
3082
  const resumeAvailable = useEmergencyStore((state) => state.resumeAvailable);
2401
3083
  const geolocationError = useEmergencyStore((state) => state.geolocationError);
3084
+ const contactPhone = useEmergencyStore((state) => state.contactPhone);
2402
3085
  const lastError = useEmergencyStore((state) => state.lastError);
2403
3086
  const abortResult = useEmergencyStore((state) => state.abortResult);
2404
3087
  const simulationResult = useEmergencyStore((state) => state.simulationResult);
@@ -2424,6 +3107,7 @@ function EmergencyRoot() {
2424
3107
  responders,
2425
3108
  refreshing: phase.kind === "refreshing",
2426
3109
  geolocationError,
3110
+ contactPhone,
2427
3111
  activationError,
2428
3112
  error: showError ? error : null,
2429
3113
  abortResult,
@@ -2450,7 +3134,8 @@ function EmergencyRoot() {
2450
3134
  },
2451
3135
  onConfirmSimulation: () => {
2452
3136
  client.simulate();
2453
- }
3137
+ },
3138
+ onSetContactPhone: (phone) => client.setContactPhone(phone)
2454
3139
  })] });
2455
3140
  }
2456
3141
 
@@ -2482,6 +3167,7 @@ function createFailedClient(error) {
2482
3167
  lastError: error,
2483
3168
  geolocation: null,
2484
3169
  geolocationError: null,
3170
+ contactPhone: null,
2485
3171
  realtimeStatus: "idle",
2486
3172
  abortResult: null,
2487
3173
  simulationResult: null,
@@ -2510,7 +3196,8 @@ function createFailedClient(error) {
2510
3196
  cancelAbortConfirmation: noop,
2511
3197
  requestSimulationConfirmation: noop,
2512
3198
  cancelSimulationConfirmation: noop,
2513
- dismissFeedback: noop
3199
+ dismissFeedback: noop,
3200
+ setContactPhone: noop
2514
3201
  };
2515
3202
  }
2516
3203
  function attachTrigger(trigger, client) {
@@ -2599,6 +3286,7 @@ function mount(options) {
2599
3286
  requestSimulationConfirmation: () => client.requestSimulationConfirmation(),
2600
3287
  cancelSimulationConfirmation: () => client.cancelSimulationConfirmation(),
2601
3288
  dismissFeedback: () => client.dismissFeedback(),
3289
+ setContactPhone: (phone) => client.setContactPhone(phone),
2602
3290
  destroy
2603
3291
  };
2604
3292
  }