資料視覺敘事
試算表網格
可編輯的試算表網格:方向鍵移動選取、雙擊或 Enter 編輯、公式列顯示原始內容、支援簡易公式,首列首欄凍結。
一張可以直接打字的小型試算表:方向鍵移動、Shift+方向鍵框選、雙擊或 Enter 編輯,頂部公式列顯示作用格的原始內容(公式就顯示 =B2*C2,而不是結果)。內建一個自寫的小公式引擎,支援四則運算、儲存格參照、範圍與 SUM/AVERAGE/MIN/MAX/COUNT,並以依賴圖只重算受影響的格。選取框以 spring 滑到新位置,提交後該格與所有依賴它的公式格一起閃一下,改一個數字就看得出連動到哪裡。注音、倉頡等輸入法的第一鍵也能直接開始編輯,不會遺失。
把這個效果裝進你的專案
在專案資料夾執行指令,CLI 會加入原始碼與依賴套件,再依下方使用範例引用元件。
npx webberui@latest add "https://webberui.com/r/spreadsheet-grid.json?t=<安裝 token>"Playground
即時調整 props、程式碼片段同步更新——直接試出你要的樣子再複製。
20
8
0
600
編輯中的呼叫預設屬性
<SpreadsheetGrid/>
完整範例包含預覽的排版與資料。先安裝元件,再把範例檔貼入你的專案。
安裝
npx webberui@latest add "https://webberui.com/r/spreadsheet-grid.json?t=<安裝 token>"或在 components.json 設定 registries 後,改用 @webberui/spreadsheet-grid 安裝。
使用
import * as React from "react";
import { SpreadsheetGrid } from "@/components/ui/spreadsheet-grid";
export function Budget() {
const [cells, setCells] = React.useState<Record<string, string>>({
A1: "品項",
B1: "數量",
C1: "單價",
D1: "小計",
A2: "便當",
B2: "32",
C2: "110",
D2: "=B2*C2",
A3: "合計",
D3: "=SUM(D2:D2)",
});
return (
<SpreadsheetGrid
rows={20}
cols={6}
value={cells}
onChange={setCells}
columnWidths={{ A: 140 }}
frozenRows={1}
frozenCols={1}
onSelectionChange={(s) => console.log(s.range, s.display)}
/>
);
}Props
| Prop | 型別 | 預設值 | 說明 |
|---|---|---|---|
rows | number | 20 | 資料列數(不含欄標頭) |
cols | number | 8 | 欄數,欄名依序為 A、B、C…Z、AA |
value | Record<string, string> | — | 受控的儲存格內容;鍵為 A1 位址、值為原始輸入,以 = 開頭為公式 |
defaultValue | Record<string, string> | — | 非受控模式的初始內容 |
onChange | (value: Record<string, string>) => void | — | 任何儲存格變動時回呼,參數為整份新內容;清成空字串的格會被移除 |
columnWidths | Record<string, number> | — | 各欄寬度(px),鍵為欄字母;未指定的欄為 96。拖曳欄標頭右緣可調整(40–480) |
onColumnResize | (column: string, width: number) => void | — | 拖曳調整欄寬、放開時回呼 |
onSelectionChange | (selection: SpreadsheetSelection) => void | — | 作用格或選取範圍改變時回呼 |
frozenRows | number | 1 | 除欄標頭外,前 N 列資料捲動時固定在上方 |
frozenCols | number | 0 | 除列號外,前 N 欄資料捲動時固定在左側 |
readOnly | boolean | false | 唯讀:可選取與複製,不能編輯、清除或貼上 |
formatNumber | (value: number) => string | zh-TW 千分位 | 數字顯示格式;預設最多 10 位小數 |
maxHeight | number | 360 | 網格捲動區域的最大高度(px) |
label | string | "試算表" | 網格的無障礙名稱 |
className | string | — | 併入最外層容器 |
SpreadsheetSelection
| 欄位 | 型別 | 預設值 | 說明 |
|---|---|---|---|
active | string | — | 作用格位址,例如 B3 |
range | string | — | 選取範圍,例如 B3:D5;單格時同 active |
raw | string | — | 作用格的原始輸入 |
display | string | — | 作用格的顯示值(公式為計算結果或錯誤代碼) |
另外具名匯出 SpreadsheetCellValue、SpreadsheetErrorCode 與 SpreadsheetGridProps 型別。
CSS 變數
| 欄位 | 型別 | 預設值 | 說明 |
|---|---|---|---|
--wb-spreadsheet-grid-stiffness | number | 600 | 選取框 spring 的 stiffness(damping 固定 40) |
--wb-spreadsheet-grid-flash | time | 400ms | 提交後強調色閃爍的淡出時間 |
--wb-spreadsheet-grid-accent | color | #2563eb | 選取框與閃爍的強調色 |
細節
- 公式:以
=開頭。支援數字、儲存格參照($絕對參照記號可寫但會被忽略)、範圍A1:B3、+ - * /、括號與負號,以及SUM、AVERAGE、MIN、MAX、COUNT(不分大小寫,參數以逗號分隔)。範圍內的文字與空白會被略過;在四則運算中,空白視為 0、文字回#VALUE! - 錯誤代碼:參照超出網格為
#REF!、除以零為#DIV/0!、未知函式或名稱為#NAME?、循環參照為#CYCLE!、語法錯誤為#ERROR!;錯誤會往依賴它的格傳遞,並以 rose 色顯示 - 只重算受影響的格:元件維護一張依賴圖,內容改變時只重算被改的格與遞移依賴它們的公式;
rows/cols改變時才整張重建。解析結果依原始字串快取 - 鍵盤:方向鍵移動、Shift+方向鍵延伸範圍、Ctrl/⌘+方向鍵跳到邊界、Home/End 到列首尾;Tab/Shift+Tab 左右移;Enter 開始編輯、編輯中 Enter 提交後下移(Shift+Enter 上移);F2 編輯;直接打字覆蓋原內容;Esc 取消;Delete/Backspace 清空選取範圍;Ctrl/⌘+A 全選
- 複製貼上:Ctrl/⌘+C 以 TSV 複製選取範圍的原始內容(公式保留為公式文字,不做相對參照位移);Ctrl/⌘+V 從選取範圍左上角貼上 TSV,超出網格的部分會被截掉。使用 copy/paste 事件的
clipboardData,不需要剪貼簿權限 - 滑鼠:點擊選格、拖曳或 Shift+點擊框選、雙擊編輯;點欄標頭選整欄、點列號選整列、點左上角全選
- 凍結:欄標頭與列號永遠固定;
frozenRows/frozenCols再額外固定前 N 列/欄資料 - 效能:所有儲存格直接渲染,
rows × cols建議在 2,000 格以內;更大的資料請在外層分頁,元件不做虛擬捲動 columnWidths同步:拖曳後的寬度保留在元件內部;傳入新的columnWidths物件時會覆蓋回 prop 值,因此請用 state 或常數保存,不要每次 render 建立新物件
可及性
- 網格為
role="grid",帶aria-rowcount/aria-colcount/aria-multiselectable;每列role="row"、欄標頭role="columnheader"、列號role="rowheader",儲存格role="gridcell"並帶aria-rowindex、aria-colindex與aria-selected - roving tabindex:只有作用格
tabIndex=0,Tab 進入網格即落在作用格;焦點實際交給作用格內的隱藏輸入框,aria-label會讀出「儲存格 B3:值」 - 公式列輸入框的無障礙名稱為「儲存格 B3 內容」,在第一欄按 Shift+Tab 會回到公式列,在最後一欄按 Tab 會離開網格
- 所有 Enter 提交都會略過 IME 組字中的按鍵(含 Safari 的 keyCode 229),注音選字不會誤送出;直接用輸入法打字時,compositionstart 就會進入編輯
- 狀態列(選取範圍、加總、平均、計數)為
aria-live="polite" - 使用者系統開啟「減少動態效果」時,選取框直接跳到新位置,不播 spring,也不顯示提交後的閃爍