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