@keenaioc/data-view 0.2.0-alpha.14 → 0.2.0-alpha.16

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/CHANGELOG.md CHANGED
@@ -2,6 +2,27 @@
2
2
 
3
3
  歷史紀錄保留當時版本資訊;部分舊版已下架,歷史安裝/回退指令不代表仍可下載。alpha.8 的回退基準為 alpha.7。
4
4
 
5
+ ## 0.2.0-alpha.16 — 2026-09-09(Alpha)
6
+
7
+ - 修正:文字編輯器不再把中文輸入法選字中的 Enter/Escape 當作儲存/取消;關聯搜尋同時處理 isComposing 與 keyCode 229 的組字邊界,保留一般 Enter 與搜尋游標操作。
8
+ - 修正:欄位/列選單焦點停在容器時仍可接收鍵盤;全部項目停用後恢復可操作,方向鍵可正確到第一/最後項,保留 dialog fallback 與 Tab/Escape 行為。
9
+ - 修正:復原/重做與新的儲存格寫入不再交錯移除錯誤歷史;既有寫入未完成時不開始重播,重播期間阻止新的儲存格、列與欄位結構操作。一般跨列編輯仍可並行;失敗可重試,查詢切換後不恢復失效歷史。
10
+ - 效能:重用相同 zh-Hant/numeric 設定的文字排序器,避免每次比較重建;保留中文、數字、空值及相同值的穩定排序規則。
11
+ - 文件:接入說明及已知限制按目前版本整理,避免把歷史原生關聯選單或已完成能力當成現行待辦。
12
+ - 相容:不新增 props、公開型別或 ViewConfig;沒有資料/設定遷移,沿用 onRowChange、canChangeRow 與 history: skip。
13
+ - 升級:`npm install --save-exact @keenaioc/data-view@0.2.0-alpha.16`,提交 package.json/lockfile 並載入同版 CSS。回退還原 alpha.15 精確依賴與 lockfile;回退會恢復上述缺陷。
14
+ - 驗收:新增輸入法/選單事件、關聯改寫→儲存格編輯→刪除→復原重做及失敗重試、並行重播、查詢往返與文字排序回歸。程式驗證及合成效能量測不代替真實瀏覽器/觸控/讀屏及後端保存驗收。
15
+
16
+ ## 0.2.0-alpha.15 — 2026-09-09(Alpha)
17
+
18
+ - 修正:桌面附件儲存格或數量按鈕按 Enter/F2,開啟同一附件視窗,不再誤進入行內上傳或整筆詳情。唯讀有附件且關閉列詳情時仍可查看,保存中停用入口不會被鍵盤繞過。
19
+ - 修正:附件上傳排隊後,在真正呼叫 host 前重新核對最新列、欄位型別、讀寫權限、查詢、載入狀態及 callback;使用最新 row/field。移除資料、撤銷權限或更換查詢後不再送出過期請求。
20
+ - 修正:欄位新增/重新命名/複製/刪除排隊後,送出前再次檢查最新欄位結構、管理權限及 callback;等待期間成為受保護欄、被移除或權限撤回都會阻擋。
21
+ - 修正:拖曳欄寬期間撤回視圖設定權限、拒絕存取、移除/隱藏目標欄位時停止手勢;元件卸載清理事件,正常拖曳使用最新視圖更新函式。
22
+ - 相容:不新增 props、公開型別或 ViewConfig;不改既有列操作及 history: skip 契約,無資料/設定遷移。前端重新核對不能取代 host 的後端權限與交易檢查,也不會撤銷已送達 host 的請求。
23
+ - 升級:`npm install --save-exact @keenaioc/data-view@0.2.0-alpha.15`,提交 package.json/lockfile,沿用同版本 CSS。回退還原 alpha.14 精確依賴及 lockfile;回退會恢復上述缺陷。
24
+ - 驗收:新增鍵盤派送、排隊權限/資料變更、最新 callback 與欄寬事件生命週期回歸;既有瀏覽器/真機/讀屏及真實 host 保存驗收限制仍保留。
25
+
5
26
  ## 0.2.0-alpha.14 — 2026-09-08(Alpha)
6
27
 
7
28
  - 新增:資料列右鍵選單,提供在上方/下方新增一筆、確認刪除此筆;保留可讀名稱的 ⋯ 按鈕及 Shift+F10/選單鍵入口。桌面、卡片與甘特左表可操作,不劫持輸入框、連結及自訂動作的原生選單。只操作目標永久 row ID,不擴大到其他已選列。
package/INTEGRATION.md CHANGED
@@ -1,6 +1,15 @@
1
1
  # HomeYa 共用資料視圖接入說明
2
2
 
3
- 適用版本 `0.2.0-alpha.7`(Alpha);2026-09-06。alpha.5 不包含本文件的新介面;原規劃的 alpha.6/alpha.7 整合為本版,未另行發布 alpha.6。完整操作驗收仍未完成,正式專案採用前須先試點驗證。
3
+ 適用版本 `0.2.0-alpha.16`(Alpha);2026-09-09。以下基本契約沿用 alpha.7,後續新增介面標明最早版本;目前行為以本文與 [已知限制](./KNOWN-LIMITATIONS.md) 為準,逐版歷史見 [CHANGELOG](./CHANGELOG.md)。
4
+
5
+ ## 目前版本接入
6
+
7
+ `npm install --save-exact @keenaioc/data-view@0.2.0-alpha.16`,提交 package.json/lockfile,載入同版 `@keenaioc/data-view/style.css`。alpha.15 升級無新增必填 props、資料或 ViewConfig 遷移;仍為 schemaVersion: 1。
8
+
9
+ - alpha.14 起:列上下新增/刪除使用 onRowChange 與 permissions.canChangeRow;更換關聯等操作略過歷史,須在 onBatchCommit 保存成功後回傳 `{ history: "skip" }`。
10
+ - alpha.16:文字及關聯搜尋避開輸入法選字按鍵;選單沒有可用項目後恢復時仍可使用鍵盤。復原/重做等待既有寫入結束,重播期間暫停其他儲存格寫入及列/欄位結構操作;一般跨列編輯仍可並行。查詢切換清除歷史。
11
+ - 回退 alpha.15:還原精確依賴與 lockfile,重新建置;無資料遷移,但會恢復本次修正前的缺陷。
12
+ - 接入端仍须驗收實際操作、後端授權/交易與重新載入。
4
13
 
5
14
  ## 所有 WORK 的共同規則
6
15
 
@@ -341,3 +350,12 @@ const rowPermissions: DataViewProps<Row>["permissions"] = {
341
350
  - 預設附件儲存格沒有內容即留白,有內容只顯示總數 `+1`、`+2` 等。點空白可編輯格或數量,開啟附件視窗,沿用既有檔案連結及 onRequestAttachment 保存。空白唯讀格不提供入口;有內容唯讀格可查看。自訂 field.render/Editor/renderQuickView 不強制改寫。附件視窗和資料詳情保留檔名,不把數量當永久附件 ID。
342
351
 
343
352
  精確升級 alpha.14,CSS 必須同版;新介面全為可選,schemaVersion 保持 1。回退還原原套件/lockfile 與 host adapter;無資料或設定遷移。
353
+
354
+
355
+ ## alpha.15 互動與排隊權限修正
356
+
357
+ 精確升級 `@keenaioc/data-view@0.2.0-alpha.15` 即生效,不需新增接入程式或 ViewConfig 遷移。預設桌面附件格及其數量按鈕按 Enter/F2,與點擊相同地開啟附件視窗;自訂 Editor/render/quick view 保持原責任。
358
+
359
+ `onRequestAttachment` 與 `onFieldChange` 在排隊後、呼叫 host 前再次核對最新資料/權限/callback。已撤銷、移除或無效的請求會標示失敗,且不呼叫 host;附件使用最新 row/field,查詢改變時需重新選檔。Host 仍須在後端驗證權限、版本與交易,已送出的請求不會因此取消。正常成功回傳、失敗 throw、列操作與 history: skip 的既有契約不變。
360
+
361
+ 拖曳欄寬中的存取或設定權限撤回會終止該次手勢;重新取得權限後需重新開始拖曳。回退 alpha.14 不需遷移,但會恢復上述缺陷。
@@ -1,82 +1,52 @@
1
- # 已知限制:0.2.0-alpha.7(Alpha)
1
+ # 已知限制:0.2.0-alpha.16
2
2
 
3
- 2026-09-06。本版整合 T01~T19 與資料保護修正。程式、測試場與文件已修改,自動測試及建置通過;實際操作驗收未完成,不能宣稱正式替換工作台已準備完成。
3
+ 2026-09-09。本文描述目前行為;逐版變更見 [CHANGELOG](./CHANGELOG.md),接入契約見 [INTEGRATION](./INTEGRATION.md)。Alpha 的程式、建置與公開安裝檢查,不代表接入專案資料保存或實際操作已驗收。
4
4
 
5
- ## 驗收與接入限制
5
+ ## 驗證範圍
6
6
 
7
- - 程式測試涵蓋欄位/權限、穩定分組、設定契約、無分頁、遠端分批完整性與取消、並行佇列、虛擬位移及SSR,不證明實際DOM高度、滑鼠落點、觸控、焦點或捲動手感。
8
- - 先前Cloud Browser明確以網址政策拒絕操作;本輪未用其他網址、瀏覽器或替代路徑繞過。32px/13px/20px/11px是程式契約,仍需實際量測。
9
- - Lab全部使用合成資料。相容區遠端模式是API契約模擬,沒有真實網路或資料庫效能證據;不得宣稱接入專案已減少下載量。
10
- - 接入專案尚未升級或改後端。真實權限、同步、交易、稽核與上傳須由接入專案驗收。
11
- - 遠端模式需要host實作query/cursor/snapshot/total、權限與索引;只傳rows仍是本地篩選。沒有自動將前端條件轉成資料庫查詢。
12
- - 完整性依賴真實總數及一致快照;固定假snapshot無法保證正確。資料變動可拒絕舊批次並重查。
13
- - 未完整載入時批次只涉及明確已載入ID;沒有groupCounts時群組顯示「已載入」。沒有隱含的全結果批次。
14
- - 所有視圖移除資料分頁。桌面表格與甘特、手機表格有虛擬化;行事曆及甘特手機清單仍使用連續卡片,範圍應由篩選控制。
15
- - 資料列拖曳键盤落點限已呈現視窗,需先捲到目標;指標把手支援邊緣捲動及取消。host更新rows才改順序,需自行定義排序與手動搬移的關係。
16
- - 分層角色/個人狀態是介面,無內建角色資料庫或跨裝置服務;未接onRuntimeChange仍保存完整快照。
17
- - 同時提供onCellCommit/onBatchCommit時單格優先前者,不納入批次復原;需統一歷史可只提供onBatchCommit。流程、附件與欄位結構不支援資料格復原。
18
- - 已送出的舊身分請求不會因切畫面撤銷;後端仍須授權、冪等、結果核對。尚未開始的佇列會取消。
19
- - 自訂renderer/Editor/action與host同程式執行,須遵守權限。internal是可分組但不暴露的授權metadata,不是秘密儲存區。
20
- - 工作台density與色票需host套用;保留36/52/72選擇,不強制切所有使用者密度。底部捲軸外觀受作業系統影響。
21
- - 人員/關聯篩選及分組從名稱改ID,需檢查舊設定;同名不可猜測。pageSize不再作用。
22
- - 甘特僅整日FS依賴,工作天只扣六日;需完整相關資料及原子scheduleGuard,遠端片段不能宣稱完整。沒有關鍵路徑、資源負荷、假日、公式或自動承諾排程。
7
+ - 引擎、React 記憶體事件、SSR、型別及套件完整性有程式驗證。没有新的真實瀏覽器、觸控或讀屏通過證據;既有瀏覽器入口受平台政策限制,未使用替代入口繞過。
8
+ - 事件測試檢查 handler、callback 及焦點呼叫,不模擬原生 Tab 移動、輸入法視窗、dialog/popover、DOM 尺寸或捲動手感。密度、固定欄裁切、放大字體與拖曳仍需實際操作。
9
+ - 效能基準使用合成資料及固定 Node 環境,量測引擎耗時與暫時堆積配置,不代表瀏覽器流暢度、手機 RAM、網路或資料庫效能。Lab 遠端及保存模擬不是正式後端證據。
23
10
 
24
- 套件發布與安裝成功不代表業務接入或操作驗收完成。接入專案各自處理後端查詢、設定遷移、權限及保存;既有 alpha.5 試點不代表本版已驗收。
11
+ ## 資料取得與權限
25
12
 
13
+ - 傳入 rows 是本地模式;遠端模式須由 host 實作查詢、穩定排序、cursor、snapshot、真實 total、授權與索引。不自動轉成 SQL 或保證減少下載量。
14
+ - 完整性依賴一致快照;固定假 snapshot 無法保證正確。部分載入的全選與批次只涵蓋明確載入 ID,不隱含操作後端全部結果;沒有 groupCounts 時標示已載入數量。
15
+ - 身分切換取消尚未開始的佇列。已送出的 host 請求不能由前端撤銷;後端仍須授權、冪等、版本及交易檢查。
16
+ - 排隊附件/欄位操作送出前再次驗證最新資料與權限;附件查詢變更後須重新選檔。
17
+ - 自訂 renderer/Editor/action 與 host 同程式執行,須遵守權限。internal 是授權 metadata,不是秘密儲存區。
26
18
 
27
- ## alpha.8 Alpha 限制
19
+ ## 視圖、欄位與分組
28
20
 
29
- - CORE-01~06 與 T15 修正自 alpha.8 提供;alpha.7 不具備這些修正。本版為公開 Alpha,並非完成操作驗收的穩定版。
30
- - 瀏覽器預覽曾成功顯示基底;本次修正後重載遭平台 URL 政策阻擋,桌機/720px/手機、更多選單鍵盤、實際高度與捲動仍待操作驗收。
31
- - 精簡表頭在空間不足時允許標題/視圖區換行,工具列保持單行。任意 host 插槽內容需自行保持精簡;長警告不裁掉。
32
- - 卡片/議程的觸控目標不強制等同桌機 28px 控制項。自訂欄位元件仍需依接入說明選擇詳情/快速檢視。
33
- - height 百分比需要有高度的父層;此 API 不能修正 host 自己造成的雙重頁面捲動。DataViewWorkspace 的命名視圖列仍存在,需與單一 viewType 切換分別設定。
34
- - 呈現偏好由 host 保存,不自動跨使用者/裝置同步;不啟用進度排程相關進階能力。
21
+ - 設定保存、角色模板、跨裝置同步及衝突由 host 處理。未接 onRuntimeChange 時保存完整視圖快照;拆分後須分別保存版面與個人狀態。
22
+ - 電腦 view.columns 與手機 view.mobile.fieldIds 各自設定,不永久同步,也沒有一鍵複製。手機僅排序已選清單;既有欄位拖曳限電腦,手機可用逐格及頂底按鈕。
23
+ - 電腦欄位按固定左/一般/固定右分區;頂底移動限同分區,保留釘選。整個標題可開選單,但極窄欄仍可能省略文字;不自動加寬。
24
+ - 分組 valueOrder 須隨完整 groups 保存。同一層在所有父組共用順序;不支援父組個別排序。面板只列已載入可讀組,暫時缺席的已存鍵仍保留。新組接在指定組後,空白可主動排序。
25
+ - 人員/關聯/多選以穩定 ID 辨識;舊名稱鍵需 host 明確遷移,同名不能猜測。
26
+ - 欄位重新命名/複製/刪除需管理權限及 onFieldChange。真實值的複製、刪除、交易與稽核由 host 處理;未包含欄位說明編輯、任意整欄填色、通知、自動化、左右插欄與看板。
27
+ - 表頭快捷篩選只編輯此欄第一個條件,完整條件在篩選面板。卡片沒有表頭,沿用設定入口。
35
28
 
29
+ ## 關聯、附件與列操作
36
30
 
37
- ## alpha.9 設定面板修正
31
+ - 單一關聯使用可搜尋視窗,搜尋限 host 提供的授權 options.label;無遠端搜尋、自動載齊選項、選項虛擬化或關聯表格檢視器。選取與寫入仍用永久 ID。
32
+ - 預設附件格空值留白,非空顯示 +N,點開顯示檔名及連結。上傳保存由 host 提供;自訂 renderer/Editor/quick view 不被覆蓋。
33
+ - 列操作需 onRowChange 與明確 canChangeRow 授權。僅單筆上下插入及確認刪除;沒有右鍵批次新增/刪除或列復原,空表没有相對插入參考列。
34
+ - 新 ID、必填預設值、相對順序、關聯級聯與軟刪除由 host 管理。排序/篩選可讓新列顯示別處或暫時不出現;遠端片段不能當完整排序來源。
35
+ - 列拖曳鍵盤落點限已呈現視窗,須先捲到目標;指標把手支援邊緣捲動及取消。host 更新 rows 才改順序,並定義手動搬移與既有排序的關係。
38
36
 
39
- 本次只修正勾選列排列與排序按鈕空間。未新增資料載入限制或改動自動載入;既有 Alpha 的實際瀏覽器及真機驗收限制持續有效。
37
+ ## 儲存格復原
40
38
 
39
+ - 同時提供 onCellCommit/onBatchCommit 時,單格優先前者且不進批次復原。需要統一歷史時只提供 onBatchCommit。
40
+ - 成功新操作回傳 history: skip,略過該整筆操作並保留既有 undo/redo。未回傳保持記錄;重播忽略 skip,仍移轉堆疊。没有批次內逐格策略、歷史重寫或跨實例/查詢保留。
41
+ - skip 若改過舊紀錄同一格,復原仍可能因原值或排程相依衝突而拒絕;不自動重寫歷史。一般新編輯會清除 redo。
42
+ - 復原/重做須等待既有寫入結束;重播期間暫停新的儲存格編輯、欄位結構及列操作,直到歷史移轉完成。一般跨列寫入仍可並行。查詢切換清除歷史,即使切回原條件,也不因舊重播完成而恢復歷史。
43
+ - 最多保留 20 筆儲存格原子歷史。附件、流程、欄位結構、列新增/刪除不加入儲存格復原。成功刪除列會移除涉及該列或其排程 read-set 的整筆歷史,保留其他歷史,不拆開原子批次。
41
44
 
42
- ## alpha.10 欄位排序範圍
45
+ ## 排程與呈現
43
46
 
44
- - 電腦釘選欄仍按固定左/一般/固定右分區;頂底按鈕在同分區內移動,無跨區取消釘選。
45
- - 手機欄位僅排序已選清單,與電腦 columns 分開;沒有永久同步或一鍵複製。鍵盤使用原生 Tab/Enter/Space 操作按鈕,既有滑鼠拖曳限電腦欄位清單。
46
- - 新版的程式及實際操作驗收結果分開記錄;既有 Alpha 全流程與真機限制不因本次排序修正而消失。
47
-
48
- - alpha.10 的直接關聯選擇使用原生 select,展開樣式與鍵盤行為依瀏覽器/作業系統;支援原生輸入定位,沒有新增遠端搜尋或無限載入。選項與權限由 host 提供;自訂 renderer/editor/quick view/action 不會被原生選單覆蓋。
49
- - 本輪自動測試及建置可驗證程式契約;本機瀏覽器操作遭安全政策阻擋,尚未完成頂底/拖曳/鍵盤、圖示尺寸與原生選單的實際畫面驗收。接入端保存、重新載入與真機操作仍須驗收。
50
-
51
- ## alpha.11 分組順序
52
-
53
- - GroupRule 新增可選 valueOrder;Host 若保存時只取 fieldId,重新載入會遺失自訂順序。必須保存完整 groups 設定。
54
- - 同一層在所有父組共用順序;目前不提供父組個別排序。面板僅列已載入的可讀組,暫時缺席的已存鍵保留但不顯示其 ID。
55
- - 新組接在已排序組後;空白不是強制固定底部,可以主動移動。還原預設沿用欄位設定/選項順序/名稱排序。
56
- - 程式驗證不取代瀏覽器或手機拖曳驗收。前輪本機瀏覽器受 URL 安全政策阻擋,本輪未重試同一路徑;鍵盤使用原生按鈕,行動裝置可用逐格及頂底按鈕。
57
-
58
-
59
- ## alpha.11 欄位表頭選單
60
-
61
- 表頭選單與確認表單已加入程式與 SSR 測試,但本輪沒有完成真實瀏覽器懸浮、焦點、Popover/dialog fallback、固定欄裁切、拖曳干擾及觸控驗收。既有瀏覽器 URL 政策限制仍適用。卡片呈現沒有表頭,使用既有設定入口。
62
-
63
- 重命名/複製/刪除需要 host 開放管理權限並實作 onFieldChange;這些入口不能代替後端欄位管理與資料保存。刪除與複製的真實值處理、交易、稽核與衝突由 host 驗收。表頭快捷篩選一次只編輯此欄第一個條件;完整條件仍在篩選面板。欄位說明、任意整欄填色、通知、自動化、左右插入與建立看板未包含於本次。
64
-
65
-
66
- ## alpha.12 關聯搜尋
67
-
68
- 搜尋範圍限 host 提供的授權 options.label;沒有遠端搜尋或自動載入完整關聯資料。清單會捲動但不另做選項虛擬化;本次僅單一關聯,沒有截圖中的關聯表格檢視器。瀏覽器/真機原生 dialog、焦點還原、輸入法、捲動及真實保存/衝突仍待接入驗收。
69
-
70
-
71
- ## alpha.13 單次復原政策
72
-
73
- `BatchCommitResult.history: "skip"` 僅略過成功的新操作,保留整個既有 undo/redo;不支援批次內逐格策略、歷史重寫或跨實例/跨查詢保留。略過操作與舊紀錄若涉及同一格,舊紀錄仍可能被原值/排程衝突檢查擋下,不能保證這種相依歷史可重播。Host 應由一開始就一致採用關聯操作政策,並保留正式衝突處理。正式介面不清除其他格紀錄,也不改 host 保存、草稿與稽核。
74
-
75
- 輔助名稱沿用 host 提供的可讀名稱與權限;套件不能把 host 傳入的 UUID 或不可讀文字自動轉成業務名稱。本輪不宣稱實際讀屏軟體、瀏覽器或接入端保存/重載已驗收。
76
-
77
-
78
- ## alpha.14 列操作與簡化顯示
79
-
80
- 列新增/刪除需要 host 的 onRowChange 及明確 canChangeRow 授權;套件不代管新 ID、必填欄位、相對順序、關聯級聯、軟刪除與保存。排序/篩選可能讓新列出现在別處或不出現,部分遠端清單不能當完整排序來源。空表沒有相對插入參考列。只有單筆確認刪除,沒有右鍵批次新增、批次刪除或列復原;涉及已刪除列的原子儲存格歷史整筆剔除,其他歷史保留。
81
-
82
- 表頭取消箭頭占位,但不自動改欄寬;很窄的欄位仍可能省略文字。附件計數只套用預設儲存格,不覆蓋自訂 renderer/Editor/quick view;點開視窗後仍需檔名與連結以辨識附件。原生 popover/dialog 的瀏覽器、真機與讀屏行為,以及真實 adapter 的新增/刪除/上傳與重載,仍待接入驗收。
47
+ - 空起訖或只有一端日期屬正常待安排資料;甘特左側仍保留,右側没有條。未排程及日期異常入口預設關閉,host 可按 view.schedulePolicy 主動開啟並保存。
48
+ - 甘特僅整日 FS 依賴,已提供改期預覽、確認後原子套用及復原。工作天只扣六日;需完整相依資料及原子 scheduleGuard,遠端片段不能宣稱完整。
49
+ - 尚無關鍵路徑、資源負荷、國定假日、自動承諾日期、公式引擎或無確認的自動連動改期。容量推估與業務範本屬 host。
50
+ - 各視圖無資料分頁。桌面表格/甘特及手機表格有虛擬化;行事曆與甘特手機清單仍連續呈現,範圍由篩選控制。
51
+ - host 決定密度及配色,保留既有列高偏好。高度百分比需要父層高度;套件不能修正 host 自己的雙重捲動。工具列插槽須由 host 保持精簡;捲軸外觀受作業系統影響。
52
+ - 可讀輔助名稱沿用 host 名稱、formatter 與權限;套件無法把 host 傳入的 UUID 自動轉成業務名稱。
package/README.md CHANGED
@@ -4,14 +4,14 @@ HomeYa 共用資料視圖引擎。以 既有工作台的既有表格成果為主
4
4
 
5
5
  套件不包含資料庫、業務流程、報價公式、使用者帳號或專案資料。接入專案透過 `DataView` 的 callbacks 處理讀取、寫入、附件、權限與稽核。
6
6
 
7
- 本文件版本:**`0.2.0-alpha.14`(公開 Alpha 測試版)**。新增列右鍵操作、整個欄位標題開啟選單及附件 `+N` 計數顯示;保留可讀輔助名稱與 `onBatchCommit` 成功回傳 `{ history: "skip" }` 的單次復原紀錄政策,保留其他格的既有復原/重做;DataViewWorkspace 同樣支援。保留關聯搜尋、欄位表頭選單、分組自訂順序及電腦/手機各自設定。程式、型別、建置及打包檢查已通過;修正後瀏覽器操作驗收尚未完成,接入時須重測。
7
+ 本文件版本:**`0.2.0-alpha.16`(公開 Alpha 測試版)**。修正文字/關聯搜尋的輸入法按鍵、選單容器鍵盤操作與復原並行歷史錯位,並重用文字排序器改善大量資料排序。沿用列右鍵操作、整個欄位標題選單、附件 `+N`、可讀名稱、關聯搜尋、自訂分組順序及電腦/手機獨立設定。無新增必要 props 或資料遷移;實際瀏覽器/觸控及 host 保存仍須驗收。
8
8
 
9
9
  既有基底:整合 T01~T19:工作台密度與 renderer、權限及狀態分離、資料列拖曳、並行寫入、手機獨立版面、排程空值、無分頁與遠端查詢契約。原先規劃的 alpha.6/alpha.7 合併為本版;alpha.6 未另行發布。
10
10
 
11
11
  本版保留 Alpha 限制:自動測試與建置通過,完整桌機/手機操作驗收尚未完成。先在測試專案接入,再決定是否升級正式系統。
12
12
 
13
13
  ```sh
14
- npm install --save-exact @keenaioc/data-view@0.2.0-alpha.14
14
+ npm install --save-exact @keenaioc/data-view@0.2.0-alpha.16
15
15
  ```
16
16
 
17
17
  必須固定精確版本;不要使用無版本安裝、`latest`、`next` 或範圍版本。
@@ -52,13 +52,13 @@ export function ActionMenu({ id, label, anchor, point, items, onChoose, onClose
52
52
  event.preventDefault();
53
53
  event.stopPropagation();
54
54
  const index = buttons.indexOf(document.activeElement);
55
- const target = event.key === "Home" ? 0 : event.key === "End" ? buttons.length - 1 : (index + (event.key === "ArrowDown" ? 1 : -1) + buttons.length) % buttons.length;
55
+ const target = event.key === "Home" ? 0 : event.key === "End" ? buttons.length - 1 : index < 0 ? (event.key === "ArrowDown" ? 0 : buttons.length - 1) : (index + (event.key === "ArrowDown" ? 1 : -1) + buttons.length) % buttons.length;
56
56
  buttons[target].focus();
57
57
  }
58
- const body = _jsx("div", { id: fallback ? id : undefined, role: fallback ? "menu" : undefined, "aria-label": fallback ? label : undefined, onKeyDown: keyDown, children: items.map((item) => _jsxs("button", { type: "button", role: "menuitem", tabIndex: fallback ? 0 : -1, disabled: item.disabled, title: item.reason, className: `${item.danger ? "hy-dv-danger" : ""} ${item.separator ? "has-separator" : ""}`, onClick: () => { anchor.focus(); onChoose(item.action); }, children: [_jsx("span", { "aria-hidden": "true", children: item.icon }), _jsx("span", { children: item.label })] }, item.action)) });
58
+ const body = _jsx("div", { id: fallback ? id : undefined, role: fallback ? "menu" : undefined, "aria-label": fallback ? label : undefined, onKeyDown: fallback ? keyDown : undefined, children: items.map((item) => _jsxs("button", { type: "button", role: "menuitem", tabIndex: fallback ? 0 : -1, disabled: item.disabled, title: item.reason, className: `${item.danger ? "hy-dv-danger" : ""} ${item.separator ? "has-separator" : ""}`, onClick: () => { anchor.focus(); onChoose(item.action); }, children: [_jsx("span", { "aria-hidden": "true", children: item.icon }), _jsx("span", { children: item.label })] }, item.action)) });
59
59
  if (fallback)
60
60
  return _jsx(DataViewDialog, { title: label, onClose: () => { anchor.focus(); onClose(); }, children: _jsx("div", { className: "hy-dv-header-menu-fallback", children: body }) });
61
- return _jsx("div", { id: id, ref: ref, popover: "auto", role: "menu", tabIndex: -1, "aria-label": label, className: "hy-dv-header-menu", onBlur: (event) => { if (event.relatedTarget instanceof Node && event.relatedTarget !== anchor && !event.currentTarget.contains(event.relatedTarget))
61
+ return _jsx("div", { id: id, ref: ref, popover: "auto", role: "menu", tabIndex: -1, "aria-label": label, className: "hy-dv-header-menu", onKeyDown: keyDown, onBlur: (event) => { if (event.relatedTarget instanceof Node && event.relatedTarget !== anchor && !event.currentTarget.contains(event.relatedTarget))
62
62
  onClose(); }, children: body });
63
63
  }
64
64
  //# sourceMappingURL=action-menu.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"action-menu.js","sourceRoot":"","sources":["../src/action-menu.tsx"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"action-menu.js","sourceRoot":"","sources":["../src/action-menu.tsx"],"names":[],"mappings":"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"}
package/dist/data-view.js CHANGED
@@ -61,6 +61,8 @@ function DataViewInner(props) {
61
61
  const [operation, setOperation] = useState({ state: "idle" });
62
62
  const [undoStack, setUndoStack] = useState([]);
63
63
  const [redoStack, setRedoStack] = useState([]);
64
+ const [replaying, setReplaying] = useState(false);
65
+ const historyEpoch = useRef(0);
64
66
  const busyRef = useRef(false);
65
67
  const pendingCells = useRef(new Set());
66
68
  const writes = useRef(createWriteQueue());
@@ -117,7 +119,7 @@ function DataViewInner(props) {
117
119
  const configurable = permissions?.canConfigureView !== false;
118
120
  const canManageFields = Boolean(onFieldChange && permissions?.canManageFields === true && access === "granted");
119
121
  const addColumnWidth = canManageFields ? 56 : 0;
120
- const busy = operation.state === "saving";
122
+ const busy = operation.state === "saving" || replaying;
121
123
  const selectedSet = useMemo(() => new Set(selected), [selected]);
122
124
  const selectedRows = useMemo(() => rows.filter((row) => selectedSet.has(getRowId(row))), [rows, selectedSet, getRowId]);
123
125
  const detailRow = detailId ? rows.find((row) => getRowId(row) === detailId) : undefined;
@@ -154,6 +156,10 @@ function DataViewInner(props) {
154
156
  setViewSaveState(e instanceof DataViewConflictError ? "conflict" : "failed"); });
155
157
  }
156
158
  function patchView(patch, scope = "layout") { setView({ ...viewRef.current, ...patch }, scope); }
159
+ const updateView = useRef(setView);
160
+ updateView.current = setView;
161
+ const stopResize = useRef(null);
162
+ useEffect(() => () => stopResize.current?.(), []);
157
163
  function patchLocation(patch) { patchView({ location: { ...viewRef.current.location, ...patch } }, "navigation"); }
158
164
  const query = useMemo(() => createDataViewQuery(activeView), [activeView.filters, activeView.sorts, activeView.groups, search]);
159
165
  const queryKey = dataViewQueryKey(query);
@@ -174,7 +180,7 @@ function DataViewInner(props) {
174
180
  useEffect(() => { if (access === "granted" && !loading && !error)
175
181
  queryCallback.current?.(query); }, [queryKey, props.onQueryChange, access, loading, error]);
176
182
  useEffect(() => { if (previousQuery.current === queryKey)
177
- return; previousQuery.current = queryKey; setLocalSelected([]); onSelectionChange?.([]); setFocus(null); setAnchor(null); setEditing(null); setUndoStack([]); setRedoStack([]); setAttachmentTarget(null); }, [queryKey]);
183
+ return; previousQuery.current = queryKey; historyEpoch.current++; setLocalSelected([]); onSelectionChange?.([]); setFocus(null); setAnchor(null); setEditing(null); setUndoStack([]); setRedoStack([]); setAttachmentTarget(null); }, [queryKey]);
178
184
  const canFilter = permissions?.canFilter ?? configurable;
179
185
  const canSort = permissions?.canSort ?? configurable;
180
186
  const processedRows = useMemo(() => {
@@ -280,10 +286,16 @@ function DataViewInner(props) {
280
286
  }
281
287
  function resizeColumn(fieldId, startWidth, event) {
282
288
  event.preventDefault();
289
+ stopResize.current?.();
283
290
  const startX = event.clientX;
284
291
  function move(pointer) {
292
+ const current = rowContext.current.props;
293
+ if (current.access === "denied" || current.loading || current.error || current.permissions?.canConfigureView === false || !current.fields.some(field => field.id === fieldId && !field.internal) || !viewRef.current.columns.some(column => column.fieldId === fieldId && column.visible)) {
294
+ up();
295
+ return;
296
+ }
285
297
  const width = normalizeColumnWidth(startWidth + pointer.clientX - startX);
286
- setView({
298
+ updateView.current({
287
299
  ...viewRef.current,
288
300
  columns: viewRef.current.columns.map((column) => column.fieldId === fieldId ? { ...column, width } : column),
289
301
  });
@@ -292,7 +304,10 @@ function DataViewInner(props) {
292
304
  window.removeEventListener("pointermove", move);
293
305
  window.removeEventListener("pointerup", up);
294
306
  window.removeEventListener("pointercancel", up);
307
+ if (stopResize.current === up)
308
+ stopResize.current = null;
295
309
  }
310
+ stopResize.current = up;
296
311
  window.addEventListener("pointermove", move);
297
312
  window.addEventListener("pointerup", up);
298
313
  window.addEventListener("pointercancel", up);
@@ -399,7 +414,18 @@ function DataViewInner(props) {
399
414
  setEditing(null);
400
415
  setCellStatuses((current) => ({ ...current, [key]: { state: "saving" } }));
401
416
  try {
402
- await writes.current.run([rowId], () => onRequestAttachment({ row, rowId, field, files }));
417
+ await writes.current.run([rowId], () => {
418
+ const current = rowContext.current;
419
+ const fresh = current.props.rows.find(row => current.props.getRowId(row) === rowId);
420
+ const definition = current.props.fields.find(item => item.id === field.id);
421
+ if (!fresh || !definition || definition.type !== field.type)
422
+ throw new DataViewConflictError("資料列或欄位已變更,請重新選擇附件");
423
+ if (current.queryKey !== queryKey || current.props.remote && current.props.remote.queryKey !== current.queryKey)
424
+ throw new DataViewConflictError("查詢已變更,請重新選擇附件");
425
+ if (!current.props.onRequestAttachment || current.props.access === "denied" || current.props.loading || current.props.error || current.props.remote?.loading || !canWriteField(fresh, definition, current.props.permissions))
426
+ throw new Error("沒有附件上傳權限或資料仍在載入");
427
+ return current.props.onRequestAttachment({ row: fresh, rowId, field: definition, files });
428
+ });
403
429
  setCellStatuses((current) => ({ ...current, [key]: { state: "saved" } }));
404
430
  }
405
431
  catch (uploadError) {
@@ -447,8 +473,8 @@ function DataViewInner(props) {
447
473
  function reportFailure(error) {
448
474
  setOperation({ state: error instanceof DataViewConflictError ? "conflict" : "failed", message: error instanceof Error ? error.message : String(error) });
449
475
  }
450
- async function execute(request) {
451
- if (!onBatchCommit || busyRef.current || latest.current.access !== "granted" || remoteStale)
476
+ async function execute(request, historyReplay = false) {
477
+ if (!onBatchCommit || (busyRef.current && !historyReplay) || latest.current.access !== "granted" || remoteStale)
452
478
  return false;
453
479
  const keys = request.changes.map((c) => `${c.rowId}:${c.field.id}`);
454
480
  keys.forEach((k) => pendingCells.current.add(k));
@@ -591,7 +617,15 @@ function DataViewInner(props) {
591
617
  busyRef.current = true;
592
618
  setOperation({ state: "saving", message: "正在儲存欄位結構" });
593
619
  try {
594
- await writes.current.run(["*"], () => onFieldChange(change));
620
+ await writes.current.run(["*"], () => {
621
+ const current = rowContext.current.props;
622
+ if (!current.onFieldChange || current.access === "denied" || current.loading || current.error)
623
+ throw new Error("沒有管理欄位的權限或資料仍在載入");
624
+ const validation = validateFieldChange(current.fields, change, current.permissions);
625
+ if (validation)
626
+ throw new Error(validation);
627
+ return current.onFieldChange(change);
628
+ });
595
629
  setUndoStack([]);
596
630
  setRedoStack([]);
597
631
  setFocus(null);
@@ -637,23 +671,33 @@ function DataViewInner(props) {
637
671
  }
638
672
  }
639
673
  async function historyAction(direction) {
640
- if (features[direction] === false)
674
+ if (features[direction] === false || busyRef.current || writes.current.pending)
641
675
  return;
642
676
  const stack = direction === "undo" ? undoStack : redoStack;
643
677
  const entry = stack.at(-1);
644
678
  if (!entry)
645
679
  return;
646
680
  const changes = direction === "undo" ? entry.changes.map((c) => ({ ...c, previousValue: c.nextValue, nextValue: c.previousValue })) : entry.changes;
647
- if (await execute({ id: newOperationId(), reason: direction, changes, scheduleGuard: ganttHistoryGuard(entry.scheduleGuard, entry.changes, direction === "undo") })) {
648
- if (direction === "undo") {
649
- setUndoStack((s) => s.slice(0, -1));
650
- setRedoStack((s) => [...s, entry]);
651
- }
652
- else {
653
- setRedoStack((s) => s.slice(0, -1));
654
- setUndoStack((s) => [...s, entry]);
681
+ const replayQuery = rowContext.current.queryKey;
682
+ const replayEpoch = historyEpoch.current;
683
+ busyRef.current = true;
684
+ setReplaying(true);
685
+ try {
686
+ if (await execute({ id: newOperationId(), reason: direction, changes, scheduleGuard: ganttHistoryGuard(entry.scheduleGuard, entry.changes, direction === "undo") }, true) && replayQuery === rowContext.current.queryKey && replayEpoch === historyEpoch.current) {
687
+ if (direction === "undo") {
688
+ setUndoStack((s) => s.filter(item => item !== entry));
689
+ setRedoStack((s) => [...s, entry]);
690
+ }
691
+ else {
692
+ setRedoStack((s) => s.filter(item => item !== entry));
693
+ setUndoStack((s) => [...s, entry]);
694
+ }
655
695
  }
656
696
  }
697
+ finally {
698
+ busyRef.current = false;
699
+ setReplaying(false);
700
+ }
657
701
  }
658
702
  function gridRange() {
659
703
  if (!focus)
@@ -721,6 +765,14 @@ function DataViewInner(props) {
721
765
  event.preventDefault();
722
766
  const row = desktopRows[range.row];
723
767
  const field = visible[range.col].field;
768
+ const attachmentCount = event.target.closest("td")?.querySelector(".hy-dv-attachment-count");
769
+ if (attachmentCount) {
770
+ if (!attachmentCount.disabled) {
771
+ attachmentCount.focus();
772
+ attachmentCount.click();
773
+ }
774
+ return;
775
+ }
724
776
  const relationSelect = event.target.closest("td")?.querySelector(".hy-dv-relation-select");
725
777
  if (relationSelect && !relationSelect.disabled) {
726
778
  relationSelect.focus();