@xcelsior/ui-spreadsheets 1.1.12 → 1.1.13

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xcelsior/ui-spreadsheets",
3
- "version": "1.1.12",
3
+ "version": "1.1.13",
4
4
  "main": "dist/index.js",
5
5
  "types": "dist/index.d.ts",
6
6
  "dependencies": {
@@ -1614,10 +1614,123 @@ interface StockData {
1614
1614
  profit: number;
1615
1615
  }
1616
1616
 
1617
- const tickers = ['AAPL', 'MSFT', 'GOOGL', 'AMZN', 'META', 'TSLA', 'NVDA', 'JPM', 'V', 'JNJ', 'WMT', 'PG', 'MA', 'UNH', 'HD', 'DIS', 'BAC', 'XOM', 'PFE', 'KO', 'PEP', 'CSCO', 'ADBE', 'NFLX', 'INTC', 'CRM', 'ABT', 'NKE', 'MRK', 'TMO', 'ORCL', 'ACN', 'MDT', 'COST', 'LLY', 'AVGO', 'TXN', 'NEE', 'UNP', 'DHR', 'QCOM', 'PM', 'BMY', 'RTX', 'HON', 'UPS', 'LOW', 'AMGN', 'SBUX', 'IBM'];
1618
- const companies = ['Apple Inc.', 'Microsoft Corp.', 'Alphabet Inc.', 'Amazon.com Inc.', 'Meta Platforms', 'Tesla Inc.', 'NVIDIA Corp.', 'JPMorgan Chase', 'Visa Inc.', 'Johnson & Johnson', 'Walmart Inc.', 'Procter & Gamble', 'Mastercard', 'UnitedHealth', 'Home Depot', 'Walt Disney', 'Bank of America', 'Exxon Mobil', 'Pfizer Inc.', 'Coca-Cola', 'PepsiCo Inc.', 'Cisco Systems', 'Adobe Inc.', 'Netflix Inc.', 'Intel Corp.', 'Salesforce', 'Abbott Labs', 'Nike Inc.', 'Merck & Co.', 'Thermo Fisher', 'Oracle Corp.', 'Accenture', 'Medtronic', 'Costco', 'Eli Lilly', 'Broadcom', 'Texas Instruments', 'NextEra Energy', 'Union Pacific', 'Danaher', 'Qualcomm', 'Philip Morris', 'Bristol-Myers', 'Raytheon', 'Honeywell', 'UPS', 'Lowes', 'Amgen', 'Starbucks', 'IBM'];
1617
+ const tickers = [
1618
+ 'AAPL',
1619
+ 'MSFT',
1620
+ 'GOOGL',
1621
+ 'AMZN',
1622
+ 'META',
1623
+ 'TSLA',
1624
+ 'NVDA',
1625
+ 'JPM',
1626
+ 'V',
1627
+ 'JNJ',
1628
+ 'WMT',
1629
+ 'PG',
1630
+ 'MA',
1631
+ 'UNH',
1632
+ 'HD',
1633
+ 'DIS',
1634
+ 'BAC',
1635
+ 'XOM',
1636
+ 'PFE',
1637
+ 'KO',
1638
+ 'PEP',
1639
+ 'CSCO',
1640
+ 'ADBE',
1641
+ 'NFLX',
1642
+ 'INTC',
1643
+ 'CRM',
1644
+ 'ABT',
1645
+ 'NKE',
1646
+ 'MRK',
1647
+ 'TMO',
1648
+ 'ORCL',
1649
+ 'ACN',
1650
+ 'MDT',
1651
+ 'COST',
1652
+ 'LLY',
1653
+ 'AVGO',
1654
+ 'TXN',
1655
+ 'NEE',
1656
+ 'UNP',
1657
+ 'DHR',
1658
+ 'QCOM',
1659
+ 'PM',
1660
+ 'BMY',
1661
+ 'RTX',
1662
+ 'HON',
1663
+ 'UPS',
1664
+ 'LOW',
1665
+ 'AMGN',
1666
+ 'SBUX',
1667
+ 'IBM',
1668
+ ];
1669
+ const companies = [
1670
+ 'Apple Inc.',
1671
+ 'Microsoft Corp.',
1672
+ 'Alphabet Inc.',
1673
+ 'Amazon.com Inc.',
1674
+ 'Meta Platforms',
1675
+ 'Tesla Inc.',
1676
+ 'NVIDIA Corp.',
1677
+ 'JPMorgan Chase',
1678
+ 'Visa Inc.',
1679
+ 'Johnson & Johnson',
1680
+ 'Walmart Inc.',
1681
+ 'Procter & Gamble',
1682
+ 'Mastercard',
1683
+ 'UnitedHealth',
1684
+ 'Home Depot',
1685
+ 'Walt Disney',
1686
+ 'Bank of America',
1687
+ 'Exxon Mobil',
1688
+ 'Pfizer Inc.',
1689
+ 'Coca-Cola',
1690
+ 'PepsiCo Inc.',
1691
+ 'Cisco Systems',
1692
+ 'Adobe Inc.',
1693
+ 'Netflix Inc.',
1694
+ 'Intel Corp.',
1695
+ 'Salesforce',
1696
+ 'Abbott Labs',
1697
+ 'Nike Inc.',
1698
+ 'Merck & Co.',
1699
+ 'Thermo Fisher',
1700
+ 'Oracle Corp.',
1701
+ 'Accenture',
1702
+ 'Medtronic',
1703
+ 'Costco',
1704
+ 'Eli Lilly',
1705
+ 'Broadcom',
1706
+ 'Texas Instruments and technology and HSBC',
1707
+ 'NextEra Energy',
1708
+ 'Union Pacific',
1709
+ 'Danaher',
1710
+ 'Qualcomm',
1711
+ 'Philip Morris',
1712
+ 'Bristol-Myers',
1713
+ 'Raytheon',
1714
+ 'Honeywell',
1715
+ 'UPS',
1716
+ 'Lowes',
1717
+ 'Amgen',
1718
+ 'Starbucks',
1719
+ 'IBM',
1720
+ ];
1619
1721
  const sectors = ['Technology', 'Healthcare', 'Financials', 'Consumer', 'Energy', 'Industrials'];
1620
- const industries = ['Software', 'Hardware', 'Biotech', 'Banking', 'Retail', 'Semiconductors', 'Pharma', 'Media', 'Oil & Gas', 'Aerospace'];
1722
+ const industries = [
1723
+ 'Software',
1724
+ 'Hardware',
1725
+ 'Biotech',
1726
+ 'Banking',
1727
+ 'Retail',
1728
+ 'Semiconductors',
1729
+ 'Pharma',
1730
+ 'Media',
1731
+ 'Oil & Gas',
1732
+ 'Aerospace',
1733
+ ];
1621
1734
 
1622
1735
  const sampleStocks: StockData[] = Array.from({ length: 50 }, (_, i) => ({
1623
1736
  id: i + 1,
@@ -1644,31 +1757,171 @@ const sampleStocks: StockData[] = Array.from({ length: 50 }, (_, i) => ({
1644
1757
  const stockColumns: SpreadsheetColumn<StockData>[] = [
1645
1758
  { id: 'ticker', label: 'Ticker', width: 80, sortable: true, filterable: true },
1646
1759
  { id: 'company', label: 'Company', width: 180, sortable: true, filterable: true },
1647
- { id: 'sector', label: 'Sector', width: 120, sortable: true, filterable: true, type: 'select', options: sectors },
1760
+ {
1761
+ id: 'sector',
1762
+ label: 'Sector',
1763
+ width: 120,
1764
+ sortable: true,
1765
+ filterable: true,
1766
+ type: 'select',
1767
+ options: sectors,
1768
+ },
1648
1769
  { id: 'industry', label: 'Industry', width: 140, sortable: true, filterable: true },
1649
- { id: 'marketCap', label: 'Market Cap', width: 130, sortable: true, type: 'number', align: 'right', render: (v) => `$${(v / 1e9).toFixed(1)}B` },
1650
- { id: 'price', label: 'Price', width: 90, sortable: true, type: 'number', align: 'right', render: (v) => `$${v.toFixed(2)}` },
1651
- { id: 'change', label: 'Change', width: 90, sortable: true, type: 'number', align: 'right', render: (v) => `${v >= 0 ? '+' : ''}${v.toFixed(2)}` },
1652
- { id: 'changePercent', label: 'Change %', width: 100, sortable: true, type: 'number', align: 'right', render: (v) => `${v >= 0 ? '+' : ''}${v.toFixed(2)}%` },
1653
- { id: 'volume', label: 'Volume', width: 110, sortable: true, type: 'number', align: 'right', render: (v) => `${(v / 1e6).toFixed(1)}M` },
1654
- { id: 'avgVolume', label: 'Avg Volume', width: 110, sortable: true, type: 'number', align: 'right', render: (v) => `${(v / 1e6).toFixed(1)}M` },
1770
+ {
1771
+ id: 'marketCap',
1772
+ label: 'Market Cap',
1773
+ width: 130,
1774
+ sortable: true,
1775
+ type: 'number',
1776
+ align: 'right',
1777
+ render: (v) => `$${(v / 1e9).toFixed(1)}B`,
1778
+ },
1779
+ {
1780
+ id: 'price',
1781
+ label: 'Price',
1782
+ width: 90,
1783
+ sortable: true,
1784
+ type: 'number',
1785
+ align: 'right',
1786
+ render: (v) => `$${v.toFixed(2)}`,
1787
+ },
1788
+ {
1789
+ id: 'change',
1790
+ label: 'Change',
1791
+ width: 90,
1792
+ sortable: true,
1793
+ type: 'number',
1794
+ align: 'right',
1795
+ render: (v) => `${v >= 0 ? '+' : ''}${v.toFixed(2)}`,
1796
+ },
1797
+ {
1798
+ id: 'changePercent',
1799
+ label: 'Change %',
1800
+ width: 100,
1801
+ sortable: true,
1802
+ type: 'number',
1803
+ align: 'right',
1804
+ render: (v) => `${v >= 0 ? '+' : ''}${v.toFixed(2)}%`,
1805
+ },
1806
+ {
1807
+ id: 'volume',
1808
+ label: 'Volume',
1809
+ width: 110,
1810
+ sortable: true,
1811
+ type: 'number',
1812
+ align: 'right',
1813
+ render: (v) => `${(v / 1e6).toFixed(1)}M`,
1814
+ },
1815
+ {
1816
+ id: 'avgVolume',
1817
+ label: 'Avg Volume',
1818
+ width: 110,
1819
+ sortable: true,
1820
+ type: 'number',
1821
+ align: 'right',
1822
+ render: (v) => `${(v / 1e6).toFixed(1)}M`,
1823
+ },
1655
1824
  { id: 'pe', label: 'P/E', width: 80, sortable: true, type: 'number', align: 'right' },
1656
- { id: 'eps', label: 'EPS', width: 80, sortable: true, type: 'number', align: 'right', render: (v) => `$${v.toFixed(2)}` },
1657
- { id: 'dividend', label: 'Dividend %', width: 100, sortable: true, type: 'number', align: 'right', render: (v) => `${v.toFixed(2)}%` },
1825
+ {
1826
+ id: 'eps',
1827
+ label: 'EPS',
1828
+ width: 80,
1829
+ sortable: true,
1830
+ type: 'number',
1831
+ align: 'right',
1832
+ render: (v) => `$${v.toFixed(2)}`,
1833
+ },
1834
+ {
1835
+ id: 'dividend',
1836
+ label: 'Dividend %',
1837
+ width: 100,
1838
+ sortable: true,
1839
+ type: 'number',
1840
+ align: 'right',
1841
+ render: (v) => `${v.toFixed(2)}%`,
1842
+ },
1658
1843
  { id: 'beta', label: 'Beta', width: 70, sortable: true, type: 'number', align: 'right' },
1659
- { id: 'week52High', label: '52W High', width: 100, sortable: true, type: 'number', align: 'right', render: (v) => `$${v.toFixed(2)}` },
1660
- { id: 'week52Low', label: '52W Low', width: 100, sortable: true, type: 'number', align: 'right', render: (v) => `$${v.toFixed(2)}` },
1661
- { id: 'revenue', label: 'Revenue', width: 120, sortable: true, type: 'number', align: 'right', render: (v) => `$${(v / 1e9).toFixed(1)}B` },
1662
- { id: 'profit', label: 'Net Income', width: 120, sortable: true, type: 'number', align: 'right', render: (v) => `$${(v / 1e9).toFixed(1)}B` },
1844
+ {
1845
+ id: 'week52High',
1846
+ label: '52W High',
1847
+ width: 100,
1848
+ sortable: true,
1849
+ type: 'number',
1850
+ align: 'right',
1851
+ render: (v) => `$${v.toFixed(2)}`,
1852
+ },
1853
+ {
1854
+ id: 'week52Low',
1855
+ label: '52W Low',
1856
+ width: 100,
1857
+ sortable: true,
1858
+ type: 'number',
1859
+ align: 'right',
1860
+ render: (v) => `$${v.toFixed(2)}`,
1861
+ },
1862
+ {
1863
+ id: 'revenue',
1864
+ label: 'Revenue',
1865
+ width: 120,
1866
+ sortable: true,
1867
+ type: 'number',
1868
+ align: 'right',
1869
+ render: (v) => `$${(v / 1e9).toFixed(1)}B`,
1870
+ },
1871
+ {
1872
+ id: 'profit',
1873
+ label: 'Net Income',
1874
+ width: 120,
1875
+ sortable: true,
1876
+ type: 'number',
1877
+ align: 'right',
1878
+ render: (v) => `$${(v / 1e9).toFixed(1)}B`,
1879
+ },
1663
1880
  ];
1664
1881
 
1665
1882
  const stockColumnGroups: SpreadsheetColumnGroup[] = [
1666
- { id: 'identity', label: 'Identity', columns: ['ticker', 'company'], collapsible: true, headerColor: '#dbeafe' },
1667
- { id: 'classification', label: 'Classification', columns: ['sector', 'industry'], collapsible: true, headerColor: '#fef3c7' },
1668
- { id: 'market', label: 'Market Data', columns: ['marketCap', 'price', 'change', 'changePercent', 'volume', 'avgVolume'], collapsible: true, headerColor: '#d1fae5' },
1669
- { id: 'fundamentals', label: 'Fundamentals', columns: ['pe', 'eps', 'dividend', 'beta'], collapsible: true, headerColor: '#fce7f3' },
1670
- { id: 'range', label: '52-Week Range', columns: ['week52High', 'week52Low'], collapsible: true, headerColor: '#e0e7ff' },
1671
- { id: 'financials', label: 'Financials', columns: ['revenue', 'profit'], collapsible: true, headerColor: '#fef9c3' },
1883
+ {
1884
+ id: 'identity',
1885
+ label: 'Identity',
1886
+ columns: ['ticker', 'company'],
1887
+ collapsible: true,
1888
+ headerColor: '#dbeafe',
1889
+ },
1890
+ {
1891
+ id: 'classification',
1892
+ label: 'Classification',
1893
+ columns: ['sector', 'industry'],
1894
+ collapsible: true,
1895
+ headerColor: '#fef3c7',
1896
+ },
1897
+ {
1898
+ id: 'market',
1899
+ label: 'Market Data',
1900
+ columns: ['marketCap', 'price', 'change', 'changePercent', 'volume', 'avgVolume'],
1901
+ collapsible: true,
1902
+ headerColor: '#d1fae5',
1903
+ },
1904
+ {
1905
+ id: 'fundamentals',
1906
+ label: 'Fundamentals',
1907
+ columns: ['pe', 'eps', 'dividend', 'beta'],
1908
+ collapsible: true,
1909
+ headerColor: '#fce7f3',
1910
+ },
1911
+ {
1912
+ id: 'range',
1913
+ label: '52-Week Range',
1914
+ columns: ['week52High', 'week52Low'],
1915
+ collapsible: true,
1916
+ headerColor: '#e0e7ff',
1917
+ },
1918
+ {
1919
+ id: 'financials',
1920
+ label: 'Financials',
1921
+ columns: ['revenue', 'profit'],
1922
+ collapsible: true,
1923
+ headerColor: '#fef9c3',
1924
+ },
1672
1925
  ];
1673
1926
 
1674
1927
  export const PinnedColumnsWithGroups: Story = {
@@ -1686,10 +1939,17 @@ export const PinnedColumnsWithGroups: Story = {
1686
1939
  columns.
1687
1940
  </p>
1688
1941
  <ul className="text-sm text-blue-700 space-y-1 ml-4 list-disc">
1689
- <li><strong>Ticker & Company</strong> are pinned to the left</li>
1690
- <li><strong>19 columns</strong> across 6 collapsible groups</li>
1942
+ <li>
1943
+ <strong>Ticker & Company</strong> are pinned to the left
1944
+ </li>
1945
+ <li>
1946
+ <strong>19 columns</strong> across 6 collapsible groups
1947
+ </li>
1691
1948
  <li>Click the pin icon on any column header to pin/unpin</li>
1692
- <li>Collapse groups to verify pinned columns from collapsed groups still work</li>
1949
+ <li>
1950
+ Collapse groups to verify pinned columns from collapsed groups still
1951
+ work
1952
+ </li>
1693
1953
  </ul>
1694
1954
  </div>
1695
1955
 
@@ -26,7 +26,12 @@ import { useSpreadsheetComments } from '../hooks/useSpreadsheetComments';
26
26
  import { useSpreadsheetUndoRedo } from '../hooks/useSpreadsheetUndoRedo';
27
27
  import { useSpreadsheetKeyboardShortcuts } from '../hooks/useSpreadsheetKeyboardShortcuts';
28
28
  import { useSpreadsheetSelection } from '../hooks/useSpreadsheetSelection';
29
- import type { CellEdit, SpreadsheetColumn, SpreadsheetColumnGroup, SpreadsheetProps } from '../types';
29
+ import type {
30
+ CellEdit,
31
+ SpreadsheetColumn,
32
+ SpreadsheetColumnGroup,
33
+ SpreadsheetProps,
34
+ } from '../types';
30
35
 
31
36
  type SingleCellEdit = {
32
37
  rowId: string | number;
@@ -645,7 +650,11 @@ export function Spreadsheet<T extends Record<string, any>>({
645
650
  }
646
651
 
647
652
  type ColumnItem = { type: 'column'; column: SpreadsheetColumn<T> };
648
- type PlaceholderItem = { type: 'collapsed-placeholder'; groupId: string; headerColor?: string };
653
+ type PlaceholderItem = {
654
+ type: 'collapsed-placeholder';
655
+ groupId: string;
656
+ headerColor?: string;
657
+ };
649
658
  type RenderItem = ColumnItem | PlaceholderItem;
650
659
 
651
660
  const leftPinnedItems: ColumnItem[] = [];
@@ -683,9 +692,7 @@ export function Spreadsheet<T extends Record<string, any>>({
683
692
  }
684
693
 
685
694
  // Add any columns not in any group
686
- const allGroupedIds = new Set(
687
- columnGroups.flatMap((g) => g.columns)
688
- );
695
+ const allGroupedIds = new Set(columnGroups.flatMap((g) => g.columns));
689
696
  for (const col of visibleColumns) {
690
697
  if (!allGroupedIds.has(col.id)) {
691
698
  const pinSide = pinnedColumns.get(col.id);
@@ -842,7 +849,7 @@ export function Spreadsheet<T extends Record<string, any>>({
842
849
  zoom: zoom / 100,
843
850
  }}
844
851
  >
845
- <table className="w-full border-separate border-spacing-0 text-xs select-none">
852
+ <table className="border-separate border-spacing-0 text-xs select-none">
846
853
  <thead>
847
854
  {/* Column Group Headers */}
848
855
  {columnGroups && groupHeaderItems && (
@@ -859,9 +866,7 @@ export function Spreadsheet<T extends Record<string, any>>({
859
866
  />
860
867
  {groupHeaderItems.map((item) => {
861
868
  if (item.type === 'pinned-column') {
862
- const col = columns.find(
863
- (c) => c.id === item.columnId
864
- );
869
+ const col = columns.find((c) => c.id === item.columnId);
865
870
  const isPinnedLeft = item.pinSide === 'left';
866
871
  return (
867
872
  <th
@@ -872,8 +877,7 @@ export function Spreadsheet<T extends Record<string, any>>({
872
877
  )}
873
878
  style={{
874
879
  backgroundColor:
875
- item.headerColor ||
876
- 'rgb(243 244 246)',
880
+ item.headerColor || 'rgb(243 244 246)',
877
881
  position: 'sticky',
878
882
  left: isPinnedLeft
879
883
  ? `${getColumnLeftOffset(item.columnId)}px`
@@ -881,12 +885,7 @@ export function Spreadsheet<T extends Record<string, any>>({
881
885
  right: !isPinnedLeft
882
886
  ? `${getColumnRightOffset(item.columnId)}px`
883
887
  : undefined,
884
- minWidth:
885
- col?.minWidth || col?.width,
886
- width:
887
- col?.minWidth || col?.width,
888
- maxWidth:
889
- col?.minWidth || col?.width,
888
+ minWidth: col?.minWidth || col?.width,
890
889
  }}
891
890
  />
892
891
  );
@@ -947,8 +946,7 @@ export function Spreadsheet<T extends Record<string, any>>({
947
946
  className="border border-gray-200 px-2 py-1 text-center text-gray-400"
948
947
  style={{
949
948
  backgroundColor:
950
- item.headerColor ||
951
- 'rgb(243 244 246)',
949
+ item.headerColor || 'rgb(243 244 246)',
952
950
  minWidth: '30px',
953
951
  }}
954
952
  >
@@ -273,11 +273,6 @@ const SpreadsheetCell: React.FC<SpreadsheetCellProps> = ({
273
273
  style={{
274
274
  backgroundColor: isInSelection ? 'rgb(239 246 255)' : getBackgroundColor(),
275
275
  minWidth: column.minWidth || column.width,
276
- // Pinned columns need fixed width so sticky offset calculations are accurate
277
- ...(isPinned && {
278
- width: column.minWidth || column.width,
279
- maxWidth: column.minWidth || column.width,
280
- }),
281
276
  ...positionStyles,
282
277
  ...selectionBorderStyles,
283
278
  }}
@@ -71,11 +71,6 @@ export const SpreadsheetHeader: React.FC<
71
71
  style={{
72
72
  backgroundColor: highlightColor || 'rgb(243 244 246)', // gray-100
73
73
  minWidth: column.minWidth || column.width,
74
- // Pinned columns need fixed width so sticky offset calculations are accurate
75
- ...(isPinned && {
76
- width: column.minWidth || column.width,
77
- maxWidth: column.minWidth || column.width,
78
- }),
79
74
  top: 0, // For sticky header
80
75
  ...positionStyles,
81
76
  }}