@openzeppelin/ui-renderer 1.0.4 → 1.1.1

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
@@ -1442,6 +1442,697 @@ function TransactionForm({ schema, contractSchema, adapter, isWalletConnected =
1442
1442
  });
1443
1443
  }
1444
1444
 
1445
+ //#endregion
1446
+ //#region src/components/AddressBookWidget/AddAliasDialog.tsx
1447
+ /** Dialog for creating a new address alias entry. */
1448
+ function AddAliasDialog({ open, onOpenChange, onSave, currentNetworkId, adapter: defaultAdapter, resolveAdapter, addressPlaceholder: defaultPlaceholder, resolveAddressPlaceholder, resolveNetwork, networks }) {
1449
+ const [saving, setSaving] = (0, react.useState)(false);
1450
+ const initialNetwork = (0, react.useMemo)(() => currentNetworkId && resolveNetwork ? resolveNetwork(currentNetworkId) : void 0, [currentNetworkId, resolveNetwork]);
1451
+ const [selectedNetwork, setSelectedNetwork] = (0, react.useState)(initialNetwork ?? null);
1452
+ const [activeAdapter, setActiveAdapter] = (0, react.useState)(defaultAdapter);
1453
+ const [activePlaceholder, setActivePlaceholder] = (0, react.useState)(defaultPlaceholder);
1454
+ (0, react.useEffect)(() => {
1455
+ if (open) {
1456
+ setSelectedNetwork(initialNetwork ?? null);
1457
+ setActiveAdapter(defaultAdapter);
1458
+ setActivePlaceholder(defaultPlaceholder);
1459
+ }
1460
+ }, [
1461
+ open,
1462
+ initialNetwork,
1463
+ defaultAdapter,
1464
+ defaultPlaceholder
1465
+ ]);
1466
+ const { control, handleSubmit, reset, trigger, formState } = (0, react_hook_form.useForm)({
1467
+ defaultValues: {
1468
+ address: "",
1469
+ alias: ""
1470
+ },
1471
+ mode: "onChange"
1472
+ });
1473
+ const handleNetworkChange = (0, react.useCallback)(async (network) => {
1474
+ setSelectedNetwork(network);
1475
+ if (resolveAddressPlaceholder) setActivePlaceholder(resolveAddressPlaceholder(network));
1476
+ if (resolveAdapter) {
1477
+ setActiveAdapter(await resolveAdapter(network));
1478
+ trigger("address");
1479
+ }
1480
+ }, [
1481
+ resolveAdapter,
1482
+ resolveAddressPlaceholder,
1483
+ trigger
1484
+ ]);
1485
+ const canSubmit = formState.isValid && !saving;
1486
+ const onSubmit = (0, react.useCallback)(async (data) => {
1487
+ const networkId = selectedNetwork?.id;
1488
+ setSaving(true);
1489
+ try {
1490
+ await onSave({
1491
+ address: data.address.trim(),
1492
+ alias: data.alias.trim(),
1493
+ networkId
1494
+ });
1495
+ reset();
1496
+ onOpenChange(false);
1497
+ } finally {
1498
+ setSaving(false);
1499
+ }
1500
+ }, [
1501
+ onOpenChange,
1502
+ onSave,
1503
+ reset,
1504
+ selectedNetwork
1505
+ ]);
1506
+ const handleOpenChange = (0, react.useCallback)((nextOpen) => {
1507
+ if (!nextOpen) {
1508
+ reset();
1509
+ setSelectedNetwork(initialNetwork ?? null);
1510
+ setActiveAdapter(defaultAdapter);
1511
+ setActivePlaceholder(defaultPlaceholder);
1512
+ }
1513
+ onOpenChange(nextOpen);
1514
+ }, [
1515
+ defaultAdapter,
1516
+ defaultPlaceholder,
1517
+ initialNetwork,
1518
+ onOpenChange,
1519
+ reset
1520
+ ]);
1521
+ const hasNetworkSelection = networks && networks.length > 0;
1522
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_openzeppelin_ui_components.Dialog, {
1523
+ open,
1524
+ onOpenChange: handleOpenChange,
1525
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(_openzeppelin_ui_components.DialogContent, {
1526
+ className: "max-w-md",
1527
+ children: [
1528
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(_openzeppelin_ui_components.DialogHeader, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_openzeppelin_ui_components.DialogTitle, { children: "Add Alias" }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_openzeppelin_ui_components.DialogDescription, { children: "Create a human-readable name for an address." })] }),
1529
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("form", {
1530
+ id: "add-alias-form",
1531
+ onSubmit: handleSubmit(onSubmit),
1532
+ className: "space-y-4",
1533
+ children: [
1534
+ hasNetworkSelection && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1535
+ className: "space-y-2",
1536
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_openzeppelin_ui_components.Label, {
1537
+ htmlFor: "alias-network",
1538
+ children: "Network"
1539
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_openzeppelin_ui_components.NetworkSelector, {
1540
+ networks,
1541
+ selectedNetwork,
1542
+ onSelectNetwork: handleNetworkChange,
1543
+ getNetworkLabel: (n) => n.name,
1544
+ getNetworkId: (n) => n.id,
1545
+ getNetworkIcon: (n) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_openzeppelin_ui_components.NetworkIcon, { network: n }),
1546
+ getNetworkType: (n) => n.type,
1547
+ groupByEcosystem: true,
1548
+ getEcosystem: (n) => n.ecosystem.toUpperCase(),
1549
+ placeholder: "Select network…"
1550
+ })]
1551
+ }),
1552
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_openzeppelin_ui_components.AddressField, {
1553
+ id: "new-alias-address",
1554
+ name: "address",
1555
+ label: "Address",
1556
+ placeholder: activePlaceholder,
1557
+ control,
1558
+ validation: { required: true },
1559
+ adapter: activeAdapter
1560
+ }),
1561
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_openzeppelin_ui_components.TextField, {
1562
+ id: "new-alias-name",
1563
+ name: "alias",
1564
+ label: "Alias",
1565
+ placeholder: "e.g. Treasury",
1566
+ control,
1567
+ validation: { required: true }
1568
+ })
1569
+ ]
1570
+ }),
1571
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(_openzeppelin_ui_components.DialogFooter, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_openzeppelin_ui_components.Button, {
1572
+ variant: "outline",
1573
+ size: "sm",
1574
+ onClick: () => handleOpenChange(false),
1575
+ children: "Cancel"
1576
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(_openzeppelin_ui_components.Button, {
1577
+ type: "submit",
1578
+ form: "add-alias-form",
1579
+ size: "sm",
1580
+ disabled: !canSubmit,
1581
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.Plus, {
1582
+ className: "mr-1.5 h-3.5 w-3.5",
1583
+ "aria-hidden": "true"
1584
+ }), saving ? "Adding…" : "Add"]
1585
+ })] })
1586
+ ]
1587
+ })
1588
+ });
1589
+ }
1590
+
1591
+ //#endregion
1592
+ //#region src/components/AddressBookWidget/AliasRow.tsx
1593
+ /** Single row in the Address Book widget displaying an alias record. */
1594
+ function AliasRow({ alias, onSave, onRemove, resolveNetwork, resolveExplorerUrl }) {
1595
+ const [editing, setEditing] = (0, react.useState)(false);
1596
+ const [editValue, setEditValue] = (0, react.useState)(alias.alias);
1597
+ const [busy, setBusy] = (0, react.useState)(false);
1598
+ const network = (0, react.useMemo)(() => alias.networkId && resolveNetwork ? resolveNetwork(alias.networkId) : void 0, [alias.networkId, resolveNetwork]);
1599
+ const explorerUrl = (0, react.useMemo)(() => resolveExplorerUrl?.(alias.address, alias.networkId), [
1600
+ alias.address,
1601
+ alias.networkId,
1602
+ resolveExplorerUrl
1603
+ ]);
1604
+ const handleEdit = (0, react.useCallback)(() => {
1605
+ setEditValue(alias.alias);
1606
+ setEditing(true);
1607
+ }, [alias.alias]);
1608
+ const handleCancel = (0, react.useCallback)(() => {
1609
+ setEditing(false);
1610
+ }, []);
1611
+ const handleSave = (0, react.useCallback)(async () => {
1612
+ const trimmed = editValue.trim();
1613
+ if (!trimmed || trimmed === alias.alias) {
1614
+ setEditing(false);
1615
+ return;
1616
+ }
1617
+ setBusy(true);
1618
+ try {
1619
+ await onSave({
1620
+ address: alias.address,
1621
+ alias: trimmed,
1622
+ networkId: alias.networkId
1623
+ });
1624
+ setEditing(false);
1625
+ } finally {
1626
+ setBusy(false);
1627
+ }
1628
+ }, [
1629
+ alias,
1630
+ editValue,
1631
+ onSave
1632
+ ]);
1633
+ const handleRemove = (0, react.useCallback)(async () => {
1634
+ setBusy(true);
1635
+ try {
1636
+ await onRemove(alias.id);
1637
+ } finally {
1638
+ setBusy(false);
1639
+ }
1640
+ }, [alias.id, onRemove]);
1641
+ const handleKeyDown = (0, react.useCallback)((e) => {
1642
+ if (e.key === "Enter") handleSave();
1643
+ if (e.key === "Escape") handleCancel();
1644
+ }, [handleSave, handleCancel]);
1645
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1646
+ className: "flex items-center gap-3 rounded-md border p-3",
1647
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1648
+ className: "min-w-0 flex-1",
1649
+ children: [editing ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1650
+ className: "mb-2 max-w-sm",
1651
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_openzeppelin_ui_components.Input, {
1652
+ value: editValue,
1653
+ onChange: (e) => setEditValue(e.target.value),
1654
+ onKeyDown: handleKeyDown,
1655
+ className: "h-8 text-base font-semibold",
1656
+ autoFocus: true,
1657
+ disabled: busy
1658
+ })
1659
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
1660
+ className: "mb-2.5 truncate text-base font-semibold text-foreground",
1661
+ children: alias.alias
1662
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1663
+ className: "flex items-center gap-2",
1664
+ children: [alias.networkId && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_openzeppelin_ui_components.NetworkStatusBadge, {
1665
+ network: network ?? null,
1666
+ className: "shrink-0 gap-1.5 px-2 py-1"
1667
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1668
+ className: "min-w-0 flex-1",
1669
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_openzeppelin_ui_components.AddressLabelProvider, {
1670
+ resolveLabel: () => void 0,
1671
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_openzeppelin_ui_components.AddressDisplay, {
1672
+ address: alias.address,
1673
+ truncate: false,
1674
+ showCopyButton: true,
1675
+ explorerUrl,
1676
+ className: "text-xs text-muted-foreground"
1677
+ })
1678
+ })
1679
+ })]
1680
+ })]
1681
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1682
+ className: "flex shrink-0 gap-1",
1683
+ children: editing ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_openzeppelin_ui_components.Button, {
1684
+ variant: "ghost",
1685
+ size: "icon",
1686
+ className: "h-7 w-7",
1687
+ onClick: handleSave,
1688
+ disabled: busy,
1689
+ "aria-label": "Save",
1690
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.Check, { className: "h-3.5 w-3.5" })
1691
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_openzeppelin_ui_components.Button, {
1692
+ variant: "ghost",
1693
+ size: "icon",
1694
+ className: "h-7 w-7",
1695
+ onClick: handleCancel,
1696
+ disabled: busy,
1697
+ "aria-label": "Cancel",
1698
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.X, { className: "h-3.5 w-3.5" })
1699
+ })] }) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_openzeppelin_ui_components.Button, {
1700
+ variant: "ghost",
1701
+ size: "icon",
1702
+ className: "h-7 w-7",
1703
+ onClick: handleEdit,
1704
+ disabled: busy,
1705
+ "aria-label": "Edit alias",
1706
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.Pencil, { className: "h-3.5 w-3.5" })
1707
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_openzeppelin_ui_components.Button, {
1708
+ variant: "ghost",
1709
+ size: "icon",
1710
+ className: "h-7 w-7 text-destructive hover:text-destructive",
1711
+ onClick: handleRemove,
1712
+ disabled: busy,
1713
+ "aria-label": "Remove alias",
1714
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.Trash2, { className: "h-3.5 w-3.5" })
1715
+ })] })
1716
+ })]
1717
+ });
1718
+ }
1719
+
1720
+ //#endregion
1721
+ //#region src/components/AddressBookWidget/ImportExportBar.tsx
1722
+ /** Bar for importing and exporting address book aliases. */
1723
+ function ImportExportBar({ onExport, onImport, exportDisabled }) {
1724
+ const fileInputRef = (0, react.useRef)(null);
1725
+ const handleImportClick = (0, react.useCallback)(() => {
1726
+ fileInputRef.current?.click();
1727
+ }, []);
1728
+ const handleFileChange = (0, react.useCallback)(async (e) => {
1729
+ const file = e.target.files?.[0];
1730
+ if (!file) return;
1731
+ await onImport(file);
1732
+ if (fileInputRef.current) fileInputRef.current.value = "";
1733
+ }, [onImport]);
1734
+ const handleExport = (0, react.useCallback)(async () => {
1735
+ await onExport();
1736
+ }, [onExport]);
1737
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_openzeppelin_ui_components.OverflowMenu, { items: (0, react.useMemo)(() => [{
1738
+ id: "export",
1739
+ label: "Export",
1740
+ icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.Download, { className: "mr-2 h-4 w-4" }),
1741
+ disabled: exportDisabled,
1742
+ onSelect: handleExport
1743
+ }, {
1744
+ id: "import",
1745
+ label: "Import",
1746
+ icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.Upload, { className: "mr-2 h-4 w-4" }),
1747
+ onSelect: handleImportClick
1748
+ }], [
1749
+ exportDisabled,
1750
+ handleExport,
1751
+ handleImportClick
1752
+ ]) }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
1753
+ ref: fileInputRef,
1754
+ type: "file",
1755
+ accept: ".json",
1756
+ className: "hidden",
1757
+ onChange: handleFileChange
1758
+ })] });
1759
+ }
1760
+
1761
+ //#endregion
1762
+ //#region src/components/AddressBookWidget/AddressBookWidget.tsx
1763
+ /** Widget for managing a personal address book with aliases, search, and network filtering. */
1764
+ function AddressBookWidget({ aliases, isLoading, onSave, onRemove, onClear, onExport, onImport, currentNetworkId, resolveNetwork, resolveExplorerUrl, adapter, resolveAdapter, addressPlaceholder, resolveAddressPlaceholder, networks, filterNetworkIds, onFilterNetworkIdsChange, title = "Address Book", className }) {
1765
+ const [search, setSearch] = (0, react.useState)("");
1766
+ const [addDialogOpen, setAddDialogOpen] = (0, react.useState)(false);
1767
+ const [confirmClear, setConfirmClear] = (0, react.useState)(false);
1768
+ const [clearInput, setClearInput] = (0, react.useState)("");
1769
+ const activeFilterIds = (0, react.useMemo)(() => filterNetworkIds ?? [], [filterNetworkIds]);
1770
+ const hasActiveFilter = activeFilterIds.length > 0;
1771
+ const filteredAliases = (0, react.useMemo)(() => {
1772
+ if (!aliases) return void 0;
1773
+ if (!search.trim()) return aliases;
1774
+ const lower = search.toLowerCase();
1775
+ return aliases.filter((a) => a.alias.toLowerCase().includes(lower) || a.address.toLowerCase().includes(lower));
1776
+ }, [aliases, search]);
1777
+ const handleClear = (0, react.useCallback)(async () => {
1778
+ await onClear();
1779
+ setConfirmClear(false);
1780
+ setClearInput("");
1781
+ }, [onClear]);
1782
+ const handleCancelClear = (0, react.useCallback)(() => {
1783
+ setConfirmClear(false);
1784
+ setClearInput("");
1785
+ }, []);
1786
+ const canFilter = networks && networks.length > 0 && onFilterNetworkIdsChange;
1787
+ if (isLoading || aliases === void 0) return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(_openzeppelin_ui_components.Card, {
1788
+ className: (0, _openzeppelin_ui_utils.cn)("w-full", className),
1789
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_openzeppelin_ui_components.CardHeader, { children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(_openzeppelin_ui_components.CardTitle, {
1790
+ className: "flex items-center gap-2 text-lg",
1791
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.BookUser, { className: "h-5 w-5" }), title]
1792
+ }) }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_openzeppelin_ui_components.CardContent, {
1793
+ className: "flex items-center justify-center py-12",
1794
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.Loader2, { className: "h-6 w-6 animate-spin text-muted-foreground" })
1795
+ })]
1796
+ });
1797
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(_openzeppelin_ui_components.Card, {
1798
+ className: (0, _openzeppelin_ui_utils.cn)("w-full", className),
1799
+ children: [
1800
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_openzeppelin_ui_components.CardHeader, { children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1801
+ className: "flex items-center justify-between",
1802
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)(_openzeppelin_ui_components.CardTitle, {
1803
+ className: "flex items-center gap-2 text-lg",
1804
+ children: [
1805
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.BookUser, { className: "h-5 w-5" }),
1806
+ title,
1807
+ aliases.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
1808
+ className: "text-sm font-normal text-muted-foreground",
1809
+ children: [
1810
+ "(",
1811
+ aliases.length,
1812
+ ")"
1813
+ ]
1814
+ })
1815
+ ]
1816
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1817
+ className: "flex items-center gap-2",
1818
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)(_openzeppelin_ui_components.Button, {
1819
+ size: "sm",
1820
+ onClick: () => setAddDialogOpen(true),
1821
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.Plus, {
1822
+ className: "mr-1.5 h-3.5 w-3.5",
1823
+ "aria-hidden": "true"
1824
+ }), "Add Alias"]
1825
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ImportExportBar, {
1826
+ onExport,
1827
+ onImport,
1828
+ exportDisabled: aliases.length === 0
1829
+ })]
1830
+ })]
1831
+ }) }),
1832
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(AddAliasDialog, {
1833
+ open: addDialogOpen,
1834
+ onOpenChange: setAddDialogOpen,
1835
+ onSave,
1836
+ currentNetworkId,
1837
+ adapter,
1838
+ resolveAdapter,
1839
+ addressPlaceholder,
1840
+ resolveAddressPlaceholder,
1841
+ resolveNetwork,
1842
+ networks
1843
+ }),
1844
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(_openzeppelin_ui_components.CardContent, {
1845
+ className: "space-y-4",
1846
+ children: [(aliases.length > 0 || hasActiveFilter) && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
1847
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1848
+ className: "flex items-center gap-2",
1849
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1850
+ className: "relative flex-1",
1851
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.Search, { className: "absolute left-2.5 top-2.5 h-4 w-4 text-muted-foreground" }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_openzeppelin_ui_components.Input, {
1852
+ placeholder: "Search by alias or address…",
1853
+ value: search,
1854
+ onChange: (e) => setSearch(e.target.value),
1855
+ className: "pl-8"
1856
+ })]
1857
+ }), canFilter && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_openzeppelin_ui_components.NetworkSelector, {
1858
+ multiple: true,
1859
+ networks,
1860
+ selectedNetworkIds: activeFilterIds,
1861
+ onSelectionChange: onFilterNetworkIdsChange,
1862
+ getNetworkLabel: (n) => n.name,
1863
+ getNetworkId: (n) => n.id,
1864
+ getNetworkIcon: (n) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_openzeppelin_ui_components.NetworkIcon, {
1865
+ network: n,
1866
+ size: 14
1867
+ }),
1868
+ getNetworkType: (n) => n.type,
1869
+ groupByEcosystem: true,
1870
+ getEcosystem: (n) => n.ecosystem.toUpperCase(),
1871
+ renderTrigger: ({ selectedCount }) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(_openzeppelin_ui_components.Button, {
1872
+ variant: "outline",
1873
+ size: "icon",
1874
+ className: "relative shrink-0",
1875
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.Filter, { className: "h-4 w-4" }), selectedCount > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1876
+ className: "absolute -right-1 -top-1 flex h-4 w-4 items-center justify-center rounded-full bg-primary text-[10px] font-medium text-primary-foreground",
1877
+ children: selectedCount
1878
+ })]
1879
+ })
1880
+ })]
1881
+ }),
1882
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1883
+ className: "space-y-2",
1884
+ children: [filteredAliases?.map((alias) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(AliasRow, {
1885
+ alias,
1886
+ onSave,
1887
+ onRemove,
1888
+ resolveNetwork,
1889
+ resolveExplorerUrl
1890
+ }, alias.id)), filteredAliases?.length === 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1891
+ className: "py-4 text-center text-sm text-muted-foreground",
1892
+ children: hasActiveFilter ? "No aliases match the current filters." : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
1893
+ "No aliases match “",
1894
+ search,
1895
+ "”"
1896
+ ] })
1897
+ })]
1898
+ }),
1899
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1900
+ className: "border-t pt-4",
1901
+ children: confirmClear ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1902
+ className: "space-y-2",
1903
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
1904
+ className: "text-sm text-muted-foreground",
1905
+ children: [
1906
+ "Type ",
1907
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1908
+ className: "font-mono font-semibold",
1909
+ children: "clear"
1910
+ }),
1911
+ " to confirm removing all aliases."
1912
+ ]
1913
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1914
+ className: "flex gap-2",
1915
+ children: [
1916
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_openzeppelin_ui_components.Input, {
1917
+ value: clearInput,
1918
+ onChange: (e) => setClearInput(e.target.value),
1919
+ placeholder: "Type \"clear\"",
1920
+ className: "max-w-[200px]",
1921
+ autoFocus: true
1922
+ }),
1923
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_openzeppelin_ui_components.Button, {
1924
+ variant: "destructive",
1925
+ size: "sm",
1926
+ disabled: clearInput !== "clear",
1927
+ onClick: handleClear,
1928
+ children: "Confirm"
1929
+ }),
1930
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_openzeppelin_ui_components.Button, {
1931
+ variant: "outline",
1932
+ size: "sm",
1933
+ onClick: handleCancelClear,
1934
+ children: "Cancel"
1935
+ })
1936
+ ]
1937
+ })]
1938
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(_openzeppelin_ui_components.Button, {
1939
+ variant: "outline",
1940
+ size: "sm",
1941
+ className: "text-destructive hover:text-destructive",
1942
+ onClick: () => setConfirmClear(true),
1943
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.Trash2, {
1944
+ className: "mr-1.5 h-3.5 w-3.5",
1945
+ "aria-hidden": "true"
1946
+ }), "Clear All"]
1947
+ })
1948
+ })
1949
+ ] }), aliases.length === 0 && !hasActiveFilter && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_openzeppelin_ui_components.EmptyState, {
1950
+ icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.BookUser, { className: "h-10 w-10" }),
1951
+ title: "No aliases yet",
1952
+ description: "Add your first alias above to start building your address book.",
1953
+ size: "small"
1954
+ })]
1955
+ })
1956
+ ]
1957
+ });
1958
+ }
1959
+
1960
+ //#endregion
1961
+ //#region src/components/AddressBookWidget/AliasEditPopover.tsx
1962
+ /**
1963
+ * AliasEditPopover
1964
+ *
1965
+ * Floating popover anchored to the pencil-icon click position.
1966
+ * Allows creating, editing, and removing an alias for a single address.
1967
+ *
1968
+ * This is a presentational component — all storage operations are
1969
+ * provided via callback props so it remains storage-agnostic.
1970
+ */
1971
+ /** Inline popover for creating, editing, or removing an address alias. */
1972
+ function AliasEditPopover({ address, networkId, anchorRect, onClose, onLookup, onSave, onRemove }) {
1973
+ const [alias, setAlias] = (0, react.useState)("");
1974
+ const [existingId, setExistingId] = (0, react.useState)(null);
1975
+ const [busy, setBusy] = (0, react.useState)(false);
1976
+ const [loaded, setLoaded] = (0, react.useState)(false);
1977
+ const inputRef = (0, react.useRef)(null);
1978
+ (0, react.useEffect)(() => {
1979
+ let cancelled = false;
1980
+ onLookup(address, networkId).then((record) => {
1981
+ if (cancelled) return;
1982
+ if (record) {
1983
+ setAlias(record.alias);
1984
+ setExistingId(record.id);
1985
+ }
1986
+ setLoaded(true);
1987
+ }).catch(() => {
1988
+ if (!cancelled) setLoaded(true);
1989
+ });
1990
+ return () => {
1991
+ cancelled = true;
1992
+ };
1993
+ }, [
1994
+ address,
1995
+ networkId,
1996
+ onLookup
1997
+ ]);
1998
+ (0, react.useEffect)(() => {
1999
+ if (loaded) inputRef.current?.focus();
2000
+ }, [loaded]);
2001
+ const handleSave = (0, react.useCallback)(async () => {
2002
+ const trimmed = alias.trim();
2003
+ if (!trimmed) return;
2004
+ setBusy(true);
2005
+ try {
2006
+ await onSave({
2007
+ address,
2008
+ alias: trimmed,
2009
+ networkId
2010
+ });
2011
+ onClose();
2012
+ } finally {
2013
+ setBusy(false);
2014
+ }
2015
+ }, [
2016
+ address,
2017
+ alias,
2018
+ networkId,
2019
+ onSave,
2020
+ onClose
2021
+ ]);
2022
+ const handleRemove = (0, react.useCallback)(async () => {
2023
+ if (!existingId) return;
2024
+ setBusy(true);
2025
+ try {
2026
+ await onRemove(existingId);
2027
+ onClose();
2028
+ } finally {
2029
+ setBusy(false);
2030
+ }
2031
+ }, [
2032
+ existingId,
2033
+ onRemove,
2034
+ onClose
2035
+ ]);
2036
+ const handleKeyDown = (0, react.useCallback)((e) => {
2037
+ if (e.key === "Enter") handleSave();
2038
+ if (e.key === "Escape") onClose();
2039
+ }, [handleSave, onClose]);
2040
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(_openzeppelin_ui_components.Popover, {
2041
+ open: true,
2042
+ onOpenChange: (open) => !open && onClose(),
2043
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_openzeppelin_ui_components.PopoverAnchor, { style: {
2044
+ position: "fixed",
2045
+ left: anchorRect.x,
2046
+ top: anchorRect.y,
2047
+ width: 0,
2048
+ height: 0,
2049
+ pointerEvents: "none"
2050
+ } }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_openzeppelin_ui_components.PopoverContent, {
2051
+ side: "bottom",
2052
+ align: "start",
2053
+ className: "w-64 space-y-3 p-3",
2054
+ children: !loaded ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
2055
+ className: "text-sm text-muted-foreground",
2056
+ children: "Loading…"
2057
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("label", {
2058
+ htmlFor: "alias-edit-input",
2059
+ className: "text-xs font-medium text-muted-foreground",
2060
+ children: "Alias"
2061
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
2062
+ ref: inputRef,
2063
+ id: "alias-edit-input",
2064
+ type: "text",
2065
+ value: alias,
2066
+ onChange: (e) => setAlias(e.target.value),
2067
+ onKeyDown: handleKeyDown,
2068
+ disabled: busy,
2069
+ placeholder: "e.g. Treasury",
2070
+ className: "mt-1 w-full rounded-md border border-input bg-background px-2.5 py-1.5 text-sm shadow-sm placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring"
2071
+ })] }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2072
+ className: "flex justify-between gap-2",
2073
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2074
+ className: "flex gap-2",
2075
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2076
+ type: "button",
2077
+ onClick: handleSave,
2078
+ disabled: busy || !alias.trim(),
2079
+ className: "rounded-md bg-primary px-3 py-1 text-xs font-medium text-primary-foreground shadow-sm hover:bg-primary/90 disabled:opacity-50",
2080
+ children: "Save"
2081
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2082
+ type: "button",
2083
+ onClick: onClose,
2084
+ disabled: busy,
2085
+ className: "rounded-md border px-3 py-1 text-xs font-medium hover:bg-accent disabled:opacity-50",
2086
+ children: "Cancel"
2087
+ })]
2088
+ }), existingId && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2089
+ type: "button",
2090
+ onClick: handleRemove,
2091
+ disabled: busy,
2092
+ className: "rounded-md px-3 py-1 text-xs font-medium text-destructive hover:bg-destructive/10 disabled:opacity-50",
2093
+ children: "Remove"
2094
+ })]
2095
+ })] })
2096
+ })]
2097
+ });
2098
+ }
2099
+
2100
+ //#endregion
2101
+ //#region src/components/AddressBookWidget/useAliasEditState.ts
2102
+ /**
2103
+ * useAliasEditState
2104
+ *
2105
+ * Pure UI state hook for managing the inline alias edit popover.
2106
+ * Tracks which address is being edited and the click anchor position
2107
+ * so the popover can be positioned near the pencil icon.
2108
+ *
2109
+ * Returns an `onEditLabel` callback compatible with `AddressLabelProvider`.
2110
+ */
2111
+ /** Manages the edit state for the inline alias popover. */
2112
+ function useAliasEditState(defaultNetworkId) {
2113
+ const [editing, setEditing] = (0, react.useState)(null);
2114
+ const lastClickRef = (0, react.useRef)({
2115
+ x: 0,
2116
+ y: 0
2117
+ });
2118
+ return {
2119
+ editing,
2120
+ onEditLabel: (0, react.useCallback)((address, networkId) => {
2121
+ const { x, y } = lastClickRef.current;
2122
+ const anchorRect = new DOMRect(x, y, 0, 0);
2123
+ setEditing({
2124
+ address,
2125
+ networkId: networkId ?? defaultNetworkId,
2126
+ anchorRect
2127
+ });
2128
+ }, [defaultNetworkId]),
2129
+ handleClose: (0, react.useCallback)(() => {
2130
+ setEditing(null);
2131
+ }, []),
2132
+ lastClickRef
2133
+ };
2134
+ }
2135
+
1445
2136
  //#endregion
1446
2137
  //#region src/components/ContractStateWidget/components/FunctionResult.tsx
1447
2138
  /**
@@ -2117,6 +2808,8 @@ const WalletConnectionWithSettings = () => {
2117
2808
  };
2118
2809
 
2119
2810
  //#endregion
2811
+ exports.AddressBookWidget = AddressBookWidget;
2812
+ exports.AliasEditPopover = AliasEditPopover;
2120
2813
  exports.ContractActionBar = ContractActionBar;
2121
2814
  exports.ContractStateWidget = ContractStateWidget;
2122
2815
  exports.DynamicFormField = DynamicFormField;
@@ -2139,5 +2832,6 @@ exports.createTransformForFieldType = createTransformForFieldType;
2139
2832
  exports.generateDefaultValue = generateDefaultValue;
2140
2833
  exports.getDefaultValueByFieldType = getDefaultValueByFieldType;
2141
2834
  exports.rendererConfig = rendererConfig;
2835
+ exports.useAliasEditState = useAliasEditState;
2142
2836
  exports.validateField = validateField;
2143
2837
  //# sourceMappingURL=index.cjs.map