arthub-table 0.2.6 → 0.2.7-next.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.
@@ -790,6 +790,7 @@ declare class DataGrid {
790
790
  edgeScroller: EdgeScroller;
791
791
  rowIdMap: Map<string, number>;
792
792
  selectedRowIds: Set<string>;
793
+ _groupSelectedRowIds: Map<string, string[]>;
793
794
  lastSelectedRowIndex: number;
794
795
  /** Footer 行高度(showFooter 为 false 时返回 0) */
795
796
  get footerHeight(): number;
@@ -2459,6 +2460,10 @@ declare class DataGrid {
2459
2460
  * - 任务名是否真的存在于 body.data / _fullData 但被规则筛掉了(broadScan 命中数)
2460
2461
  * - 前 3 行的字段集合是什么样(用于人工核对 task name 落在哪个字段)
2461
2462
  *
2463
+ * task=b1464bc3/iter#4 增强:返回新增 `columnsSummary`——top-level 列的
2464
+ * `{index, key, name, fixed, hasChildren}` 摘要(最多 50 条),用于 freeze-column
2465
+ * spec 在 `waitForFieldFrozenState` 超时时精准区分"controller 未生效"vs"grid 未刷新"。
2466
+ *
2462
2467
  * 返回结构刻意保持小(前 3 行 sample, 字段名列表)—— 便于 console.log 整段直接 dump。
2463
2468
  *
2464
2469
  * @param name - 可选;提供时会做一次 case-insensitive includes 扫描,返回命中数和首批样本
@@ -2482,6 +2487,13 @@ declare class DataGrid {
2482
2487
  rowKeyValue: any;
2483
2488
  }>;
2484
2489
  } | null;
2490
+ columnsSummary: Array<{
2491
+ index: number;
2492
+ key: string;
2493
+ name: string;
2494
+ fixed: string;
2495
+ hasChildren: boolean;
2496
+ }>;
2485
2497
  };
2486
2498
  /**
2487
2499
  * Look up a column's business `key` by header text / column title.
@@ -2553,6 +2565,41 @@ declare class DataGrid {
2553
2565
  groupLevel: number;
2554
2566
  isLeafGroup: boolean;
2555
2567
  }>;
2568
+ /**
2569
+ * 列出当前可见的所有 ⊕ AddTaskRow 行的 canvas 几何(task=be29ce06)。
2570
+ *
2571
+ * 仅返回 `body.rows` 中 `instanceof AddTaskRow` 的行——AddTaskRow 是数据驱动
2572
+ * 的特殊 canvas 行(`Body.ts` L905 `if (isAddTaskRow(rowData)) new AddTaskRow(...)`),
2573
+ * 不走 cell renderer 路径,所以 `testGetAllVisibleCells` 不会返回它的 cell。
2574
+ * 这里专门给 e2e 暴露 ⊕ 圆心的精确 canvas 坐标,规避 spec 端按
2575
+ * `originFixedWidth/2` 估算在分组下偏到行号区。
2576
+ *
2577
+ * 几何**严格复刻** `AddTaskRow.isInsideIconArea` 的 `bw + iconX + hitPadding=4`
2578
+ * 计算(一字不差):保证"e2e 鼠标命中点 = AddTaskRow 运行时判定命中点"。
2579
+ *
2580
+ * 返回的 `iconArea` 已经包含 hitPadding:spec 用 `(canvasX + width/2,
2581
+ * canvasY + height/2)` → `canvasToViewport` → `page.mouse.click` 即可命中。
2582
+ *
2583
+ * 这是 LRN-20260512-001 约定的"长期稳定 e2e 锚点 API",不要随手隐藏。
2584
+ */
2585
+ testGetAddTaskRowAreas(): Array<{
2586
+ rowId: string;
2587
+ rowIndex: number;
2588
+ groupId: string | number | null;
2589
+ parentGroupLevel: number | null;
2590
+ rowArea: {
2591
+ canvasX: number;
2592
+ canvasY: number;
2593
+ width: number;
2594
+ height: number;
2595
+ };
2596
+ iconArea: {
2597
+ canvasX: number;
2598
+ canvasY: number;
2599
+ width: number;
2600
+ height: number;
2601
+ };
2602
+ }>;
2556
2603
  /**
2557
2604
  * Get current selection, editing, and checked-row state.
2558
2605
  */
@@ -32,7 +32,7 @@ export interface RichTextViewerData extends CellViewerData {
32
32
  placeholder?: string;
33
33
  }
34
34
  /**
35
- * 解析富文本值,去除 HTML 标签
35
+ * 解析富文本值,去除 HTML 标签 + 解码常见 HTML 实体。
36
36
  * <img> 标签替换为 [图片],<video> 标签替换为 [视频]
37
37
  */
38
38
  export declare function getDisplayValue(value: string | null | undefined): string;
@@ -351,6 +351,21 @@ export interface TestHooksAPI {
351
351
  * Get all visible group rows with their expand/collapse state.
352
352
  */
353
353
  getVisibleGroupRows: () => VisibleGroupRowInfo[];
354
+ /**
355
+ * 列出当前可见的所有 ⊕ AddTaskRow 行的 canvas 几何(task=be29ce06)。
356
+ *
357
+ * 仅返回当前 `body.rows` 中 `instanceof AddTaskRow` 的行——即虚拟滚动已经
358
+ * 实例化的 AddTaskRow(hit-test 必须基于已实例化的行:`isInsideIconArea`
359
+ * 依赖 `row.y` / `row.height` / `data._parentGroupLevel`)。
360
+ *
361
+ * 调用前 spec 应先确保表格已滚动到目标 ⊕ 行所在视口;本 hook 不做隐式 scroll
362
+ * (spec 用 `scrollToRow` 或 wheel 自己定位)。
363
+ *
364
+ * 这是 LRN-20260512-001 约定的"长期稳定 e2e 锚点 API"——业务侧 / 表格侧
365
+ * 重构时不要随手隐藏。详见 CONTROLLER_API_CONTRACT.md `getAddTaskRowAreas`
366
+ * 段。
367
+ */
368
+ getAddTaskRowAreas: () => AddTaskRowArea[];
354
369
  /**
355
370
  * Get current selection, editing, and checked-row state.
356
371
  */
@@ -472,6 +487,110 @@ export interface TestHooksAPI {
472
487
  rowKeyValue: any;
473
488
  }>;
474
489
  } | null;
490
+ /**
491
+ * task=b1464bc3/iter#4:freeze-column 诊断专用——top-level columns 的 fixed
492
+ * 状态摘要。spec 在 canvas 模式调 `__AM_E2E__.controller.freezeColumns(...)`
493
+ * 后会轮询 `grid.columns[i].fixed`,本字段让 spec / verify 在轮询超时时
494
+ * 一次性 dump "key/name/fixed/hasChildren",精准区分"controller 没改"
495
+ * vs "grid 还没把 fixed 广播出去"两种根因。最多 50 条,避免大表 console 爆掉。
496
+ */
497
+ columnsSummary: Array<{
498
+ index: number;
499
+ key: string;
500
+ name: string;
501
+ fixed: string;
502
+ hasChildren: boolean;
503
+ }>;
504
+ };
505
+ /**
506
+ * task=f479647b/iter#1:freeze-column 长期卡死专用诊断探针。
507
+ *
508
+ * 背景:spec `freeze-column.spec.ts` 在 canvas 分支调
509
+ * `freezeFieldsViaController(page, [target])`,candidate 列表里依次尝试
510
+ * `freezeColumns / setFreezeColumns / freezeColumn / toggleFreezeColumn`,
511
+ * 全部 fail 后吐 `controller.toggleFreezeColumn is not a function`——历史 4 轮
512
+ * 都凭这条错误信息推断"AssetMatrix 没暴露 API",但实际业务侧(FillerInitController)
513
+ * 已经 public 暴露 freezeColumn(s) / unfreezeColumn(s) / toggleFreezeColumn / setFreezeColumns
514
+ * 等多个方法。所以问题不在"业务有没有实现",而是 spec 端拿到的
515
+ * `__AM_E2E__.controller` **运行时形状**与预期不符(candidate name 全找不到)。
516
+ *
517
+ * 这个探针不动业务逻辑,仅做"实地探查"——把 spec 失败现场需要看到的事实
518
+ * 一次性 dump 出来:
519
+ *
520
+ * - `available`: window.__AM_E2E__.controller 是否真的挂上了
521
+ * - `constructorName`: ctrl.constructor.name(运行时类名,区分 mainPanel
522
+ * controller vs 子表 dialog controller vs perspectiveTable)
523
+ * - `propsName`: ctrl.props?.name(确认是不是 "mainPanel")
524
+ * - `prototypeChain`: 原型链每一层的 constructor.name(ts-mixer 拼出来的
525
+ * Mixin 链,让我们能看到 FillerInitController / FillerDataGridController
526
+ * / Disposable 等具体到哪层挂的方法)
527
+ * - `freezeMethodsByLevel`: 原型链每一层 own keys 中匹配 /freeze/i 的并集
528
+ * (包含父类被 ts-mixer 拷贝过来的方法名 vs 真实定义所在的层)
529
+ * - `typeofChecks`: spec 实际调的 candidate name 对应的 typeof 结果,让我们
530
+ * 一眼看到"freezeColumns 是 function 吗""toggleFreezeColumn 是 undefined 吗"
531
+ * - `byNameKeys`: __AM_E2E__.byName 里都有哪些子表实例(用于排查
532
+ * "spec 拿到的 controller 是不是任务池主表,而不是 dialog 内的子表")
533
+ *
534
+ * **关键设计**:这个 hook 失败时永不抛错——任何 try/catch 都吞掉;返回结构
535
+ * 字段可能为空但 shape 始终不变(spec 端可放心 JSON.stringify)。
536
+ *
537
+ * spec 调用建议:
538
+ * ```ts
539
+ * const probe = await page.evaluate(() => {
540
+ * return (window as any).__CANVAS_TABLE_TEST_HOOKS__?.probeAmController?.();
541
+ * });
542
+ * console.log('[freeze-column/probe]', JSON.stringify(probe));
543
+ * ```
544
+ * (prefix 必须 `[freeze-column/probe]`,orchestrator 摘 observation 时按此匹配。)
545
+ *
546
+ * @returns 诊断对象;__AM_E2E__ 未挂时 available=false,其他字段为合理空值
547
+ */
548
+ probeAmController: () => {
549
+ available: boolean;
550
+ reason: string;
551
+ constructorName: string;
552
+ propsName: string;
553
+ tableType: string;
554
+ isMainPanel: boolean;
555
+ prototypeChain: string[];
556
+ freezeMethodsByLevel: Array<{
557
+ level: number;
558
+ className: string;
559
+ methods: string[];
560
+ }>;
561
+ freezeMethodsAll: string[];
562
+ typeofChecks: Record<string, string>;
563
+ byNameKeys: string[];
564
+ hasDataGrid: boolean;
565
+ hasTable: boolean;
566
+ /**
567
+ * task=f479647b/iter#3 新增:install 时间戳与计数器,给 spec 端算
568
+ * "spec 调用时机相对 hooks 安装时机"的偏移用。
569
+ *
570
+ * 字段含义:
571
+ * - hooksInstalledAt : Date.now() at first installTestHooks() call.
572
+ * (任何一个 DataGrid 实例第一次 mount 时被钉住)
573
+ * - hooksLastInstalledAt : Date.now() at most recent installTestHooks().
574
+ * (HMR / 多 DataGrid 时持续刷新)
575
+ * - hooksInstallCount : 累计 install 次数。
576
+ * - nowAtProbe : Date.now() at the moment probe is called.
577
+ * (spec 端算 nowAtProbe - hooksInstalledAt 即拿到偏移)
578
+ * - hasHooksAlias : window.__CANVAS_TABLE_TEST_HOOKS__ 是否真的指向某个 hooks。
579
+ * (正常 ≥ 1 install 之后必为 true)
580
+ * - hasGridAlias : window.__CANVAS_TABLE_GRID__ 是否真的指向某 DataGrid。
581
+ * (正常 ≥ 1 install 之后必为 true)
582
+ *
583
+ * 0 / false 表示未安装或浏览器无 Date.now(非常规环境);spec 应把
584
+ * `hooksInstalledAt === 0` 当成"hooks 还没装"信号,不要 probe。
585
+ */
586
+ installTiming: {
587
+ hooksInstalledAt: number;
588
+ hooksLastInstalledAt: number;
589
+ hooksInstallCount: number;
590
+ nowAtProbe: number;
591
+ hasHooksAlias: boolean;
592
+ hasGridAlias: boolean;
593
+ };
475
594
  };
476
595
  findCellAreasByName: (taskName: string, columnDisplayName: string, options?: {
477
596
  scrollIfNeeded?: boolean;
@@ -694,6 +813,46 @@ export interface GroupRowAreas {
694
813
  rowId: string;
695
814
  };
696
815
  }
816
+ /**
817
+ * AddTaskRow(⊕ 新建任务行)的 canvas 几何信息。
818
+ *
819
+ * task=be29ce06:`任务表格最后一行+号快速创建任务` 这个 e2e case 在 canvas 模式下
820
+ * 必须能精确点中 ⊕ 圆心,命中 `AddTaskRow.isInsideIconArea` 触发
821
+ * `grid.onAddRow → onCreateNewRow`。原本 spec 端按 `originFixedWidth/2 + 8` 估算
822
+ * 在 1D / 2D / 3D 分组下分别偏离 0 / 4 / 14 px,会落到行号区,触发 onRowClick 而
823
+ * 不是 onAddRow——本接口暴露的几何与 `AddTaskRow.isInsideIconArea` 一字不差对齐
824
+ * (含 `hitPadding=4`),保证"e2e 鼠标命中点 = AddTaskRow 运行时判定命中点"。
825
+ */
826
+ export interface AddTaskRowArea {
827
+ /** AddTaskRow._rowId(一般是 `add_task_row_<rowIndex>` 或 String(data.id)) */
828
+ rowId: string;
829
+ /** body.data 内的 rowIndex(spec 调试 / log 用,不参与稳定 id 比较) */
830
+ rowIndex: number;
831
+ /** 行所属分组 id(非分组模式 / 该行无 groupId 时为 null) */
832
+ groupId: string | number | null;
833
+ /** _parentGroupLevel(非分组模式或未填时为 null;2D 第一层 = 0,第二层 = 1) */
834
+ parentGroupLevel: number | null;
835
+ /** 整行的 canvas 几何(行号 + 数据区,含右侧 verticalScrollerSize 之外) */
836
+ rowArea: AreaBounds;
837
+ /**
838
+ * ⊕ 图标命中区域(16×16 + hitPadding=4 → 实际 24×24),canvas 相对坐标。
839
+ *
840
+ * 几何与 `AddTaskRow.isInsideIconArea` 一字不差对齐:
841
+ * bw = parentGroupLevel >= 0
842
+ * ? maxGroupLevel === 0 ? 0
843
+ * : (parentGroupLevel >= 2 ? l0w + l1w
844
+ * : parentGroupLevel >= 1
845
+ * ? (maxGroupLevel === 1 ? l0w : l0w + l1w)
846
+ * : l0w)
847
+ * : 0
848
+ * iconX = bw + (originFixedWidth - bw - 16) / 2
849
+ * center = (iconX + 8, rowAbsY + height/2)
850
+ *
851
+ * spec 拿到后用中心点 (canvasX + width/2, canvasY + height/2) → canvasToViewport
852
+ * → page.mouse.click 即可命中。
853
+ */
854
+ iconArea: AreaBounds;
855
+ }
697
856
  /**
698
857
  * Row checkbox area for normal (non-group) data rows.
699
858
  *
@@ -779,5 +938,15 @@ declare global {
779
938
  interface Window {
780
939
  __CANVAS_TABLE_TEST_HOOKS__?: TestHooksAPI;
781
940
  __CANVAS_TABLE_TEST_HOOKS_REGISTRY__?: TestHooksRegistry;
941
+ /**
942
+ * task=f479647b/iter#3:installTestHooks 首次成功安装时的 Date.now()。
943
+ * spec 端可 `await page.waitForFunction(() => typeof window.__CANVAS_TABLE_TEST_HOOKS_INSTALLED_AT__ === 'number')`
944
+ * 等待 hooks 真正就绪,避免"isCanvasReady 早调一拍"的 race。
945
+ */
946
+ __CANVAS_TABLE_TEST_HOOKS_INSTALLED_AT__?: number;
947
+ /** task=f479647b/iter#3:最近一次 installTestHooks 时的 Date.now()(HMR / 多 grid 持续刷新)。 */
948
+ __CANVAS_TABLE_TEST_HOOKS_LAST_INSTALLED_AT__?: number;
949
+ /** task=f479647b/iter#3:installTestHooks 累计调用次数。 */
950
+ __CANVAS_TABLE_TEST_HOOKS_INSTALL_COUNT__?: number;
782
951
  }
783
952
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "arthub-table",
3
- "version": "0.2.6",
3
+ "version": "0.2.7-next.1",
4
4
  "description": "High-performance canvas-based table/grid component for Vue 3 with TypeScript support, featuring virtual scrolling, cell viewers, grouped rows, and nested grids.",
5
5
  "main": "dist/arthub-table.common.js",
6
6
  "module": "dist/arthub-table.umd.min.js",