@quadrats/common 0.7.7 → 1.1.0
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/accordion/constants.d.ts +5 -0
- package/accordion/constants.js +10 -0
- package/accordion/createAccordion.d.ts +6 -0
- package/accordion/createAccordion.js +62 -0
- package/accordion/index.cjs.js +76 -0
- package/accordion/index.d.ts +3 -0
- package/accordion/index.js +2 -0
- package/accordion/package.json +7 -0
- package/accordion/typings.d.ts +18 -0
- package/align/constants.d.ts +2 -0
- package/align/constants.js +7 -0
- package/align/createAlign.d.ts +9 -0
- package/align/createAlign.js +39 -0
- package/align/index.cjs.js +46 -0
- package/align/index.d.ts +3 -0
- package/align/index.js +2 -0
- package/align/package.json +7 -0
- package/align/typings.d.ts +1 -0
- package/blockquote/createBlockquote.js +27 -10
- package/blockquote/index.cjs.js +26 -9
- package/card/constants.d.ts +6 -0
- package/card/constants.js +11 -0
- package/card/createCard.d.ts +15 -0
- package/card/createCard.js +147 -0
- package/card/getFilesFromInput.d.ts +4 -0
- package/card/index.cjs.js +163 -0
- package/card/index.d.ts +3 -0
- package/card/index.js +2 -0
- package/card/package.json +7 -0
- package/card/typings.d.ts +73 -0
- package/carousel/_virtual/_tslib.js +33 -0
- package/carousel/constants.d.ts +6 -0
- package/carousel/constants.js +11 -0
- package/carousel/createCarousel.d.ts +16 -0
- package/carousel/createCarousel.js +127 -0
- package/carousel/getFilesFromInput.d.ts +4 -0
- package/carousel/getFilesFromInput.js +30 -0
- package/carousel/index.cjs.js +202 -0
- package/carousel/index.d.ts +3 -0
- package/carousel/index.js +2 -0
- package/carousel/package.json +7 -0
- package/carousel/typings.d.ts +58 -0
- package/embed/constants.d.ts +1 -0
- package/embed/constants.js +2 -1
- package/embed/createEmbed.js +21 -4
- package/embed/index.cjs.js +26 -9
- package/embed/index.js +1 -1
- package/embed/serializeEmbedCode.d.ts +1 -1
- package/embed/serializeEmbedCode.js +5 -7
- package/embed/strategies/podcast-apple/index.cjs.js +1 -1
- package/embed/strategies/podcast-apple/index.js +1 -1
- package/embed/typings.d.ts +16 -1
- package/file-uploader/constants.d.ts +1 -0
- package/file-uploader/constants.js +2 -1
- package/file-uploader/createFileUploader.js +70 -7
- package/file-uploader/getFilesFromInput.js +3 -0
- package/file-uploader/index.cjs.js +73 -5
- package/file-uploader/index.js +1 -1
- package/file-uploader/typings.d.ts +8 -2
- package/heading/typings.d.ts +1 -0
- package/image/createImage.js +12 -11
- package/image/getImageFigureElementCommonProps.d.ts +2 -0
- package/image/getImageFigureElementCommonProps.js +28 -2
- package/image/index.cjs.js +40 -13
- package/image/typings.d.ts +2 -0
- package/list/createList.d.ts +1 -0
- package/list/createList.js +3 -0
- package/list/index.cjs.js +3 -0
- package/list/typings.d.ts +1 -0
- package/package.json +4 -4
- package/paragraph/createParagraph.d.ts +2 -0
- package/paragraph/createParagraph.js +24 -0
- package/paragraph/index.cjs.js +26 -0
- package/paragraph/index.d.ts +2 -0
- package/paragraph/index.js +1 -0
- package/paragraph/package.json +7 -0
- package/paragraph/typings.d.ts +9 -0
- package/table/constants.d.ts +14 -0
- package/table/constants.js +25 -0
- package/table/createTable.d.ts +6 -0
- package/table/createTable.js +429 -0
- package/table/index.cjs.js +714 -0
- package/table/index.d.ts +4 -0
- package/table/index.js +3 -0
- package/table/package.json +7 -0
- package/table/typings.d.ts +70 -0
- package/table/utils.d.ts +68 -0
- package/table/utils.js +243 -0
package/table/index.d.ts
ADDED
package/table/index.js
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
export { MAX_PINNED_COLUMNS_WIDTH_PERCENTAGE, MIN_COLUMN_WIDTH_PERCENTAGE, MIN_COLUMN_WIDTH_PIXEL, TABLE_BODY_TYPE, TABLE_CELL_TYPE, TABLE_DEFAULT_MAX_COLUMNS, TABLE_DEFAULT_MAX_ROWS, TABLE_HEADER_TYPE, TABLE_MAIN_TYPE, TABLE_ROW_TYPE, TABLE_TITLE_TYPE, TABLE_TYPE, TABLE_TYPES } from './constants.js';
|
|
2
|
+
export { calculateTableMinWidth, columnWidthToCSS, getCellLocation, getTableContainers, tryCrossBoundaryMove, tryExtendSelectionHorizontal, tryMoveToAdjacentRow, tryMoveToNextCell } from './utils.js';
|
|
3
|
+
export { createTable } from './createTable.js';
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { Ancestor, Editor, QuadratsElement, Text, Withable, WithElementType } from '@quadrats/core';
|
|
2
|
+
export type TableTypeKey = 'table';
|
|
3
|
+
export type TableTitleTypeKey = 'table_title';
|
|
4
|
+
export type TableMainTypeKey = 'table_main';
|
|
5
|
+
export type TableHeaderTypeKey = 'table_header';
|
|
6
|
+
export type TableBodyTypeKey = 'table_body';
|
|
7
|
+
export type TableRowTypeKey = 'table_row';
|
|
8
|
+
export type TableCellTypeKey = 'table_cell';
|
|
9
|
+
export type TableTypes = Record<TableTypeKey | TableTitleTypeKey | TableMainTypeKey | TableHeaderTypeKey | TableBodyTypeKey | TableRowTypeKey | TableCellTypeKey, string>;
|
|
10
|
+
export type ColumnWidth = {
|
|
11
|
+
type: 'percentage';
|
|
12
|
+
value: number;
|
|
13
|
+
} | {
|
|
14
|
+
type: 'pixel';
|
|
15
|
+
value: number;
|
|
16
|
+
};
|
|
17
|
+
export interface TableElement extends QuadratsElement, WithElementType {
|
|
18
|
+
treatAsTitle?: boolean;
|
|
19
|
+
pinned?: boolean;
|
|
20
|
+
align?: 'left' | 'center' | 'right';
|
|
21
|
+
columnWidths?: ColumnWidth[];
|
|
22
|
+
scrollPosition?: {
|
|
23
|
+
scrollLeft: number;
|
|
24
|
+
scrollTop: number;
|
|
25
|
+
};
|
|
26
|
+
children: {
|
|
27
|
+
type: string;
|
|
28
|
+
treatAsTitle?: boolean;
|
|
29
|
+
pinned?: boolean;
|
|
30
|
+
align?: 'left' | 'center' | 'right';
|
|
31
|
+
children: TableElement['children'] | Text[];
|
|
32
|
+
}[];
|
|
33
|
+
}
|
|
34
|
+
export interface Table<T extends Editor = Editor> extends Withable {
|
|
35
|
+
types: TableTypes;
|
|
36
|
+
createTableElement(rows: number, cols: number): QuadratsElement[];
|
|
37
|
+
insertTable(editor: T, rows: number, cols: number): void;
|
|
38
|
+
moveToNextCell(editor: Editor, types: TableTypes): void;
|
|
39
|
+
moveToRowAbove(editor: Editor, types: TableTypes): void;
|
|
40
|
+
moveToRowBelow(editor: Editor, types: TableTypes): void;
|
|
41
|
+
extendSelectionLeft(editor: Editor, types: TableTypes): void;
|
|
42
|
+
extendSelectionRight(editor: Editor, types: TableTypes): void;
|
|
43
|
+
extendSelectionUp(editor: Editor, types: TableTypes): void;
|
|
44
|
+
extendSelectionDown(editor: Editor, types: TableTypes): void;
|
|
45
|
+
isSelectionInTableMain(editor: T): boolean;
|
|
46
|
+
isSelectionInTableCell(editor: T): boolean;
|
|
47
|
+
isSelectionInTableRow(editor: T): boolean;
|
|
48
|
+
isSelectionInTableHeader(editor: T): boolean;
|
|
49
|
+
isSelectionInTableBody(editor: T): boolean;
|
|
50
|
+
isSelectionInTableList(editor: T): boolean;
|
|
51
|
+
}
|
|
52
|
+
export interface CellLocation {
|
|
53
|
+
cellPath: number[];
|
|
54
|
+
columnIndex: number;
|
|
55
|
+
row: Ancestor;
|
|
56
|
+
rowPath: number[];
|
|
57
|
+
rowIndex: number;
|
|
58
|
+
container: Ancestor;
|
|
59
|
+
containerPath: number[];
|
|
60
|
+
isHeader: boolean;
|
|
61
|
+
isBody: boolean;
|
|
62
|
+
}
|
|
63
|
+
export interface TableContainers {
|
|
64
|
+
tableMain: Ancestor;
|
|
65
|
+
tableMainPath: number[];
|
|
66
|
+
tableHeader: TableElement | null;
|
|
67
|
+
tableBody: TableElement | null;
|
|
68
|
+
tableHeaderIndex: number;
|
|
69
|
+
tableBodyIndex: number;
|
|
70
|
+
}
|
package/table/utils.d.ts
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { Editor } from '@quadrats/core';
|
|
2
|
+
import { CellLocation, ColumnWidth, TableContainers, TableTypes } from './typings';
|
|
3
|
+
/**
|
|
4
|
+
* 將 ColumnWidth 轉換為 CSS 可用的字串
|
|
5
|
+
* @param width - 行寬定義(column width)
|
|
6
|
+
* @returns CSS 寬度字串(例如 "30%" 或 "200px")
|
|
7
|
+
*/
|
|
8
|
+
export declare function columnWidthToCSS(width: ColumnWidth): string;
|
|
9
|
+
/**
|
|
10
|
+
* 計算 table 的總寬度(用於設定 min-width 以支援 overflow)
|
|
11
|
+
* 此函數會將所有 columnWidths 的百分比和 pixel 值加總:
|
|
12
|
+
* - percentage: 保留為百分比
|
|
13
|
+
* - pixel: 直接累加
|
|
14
|
+
*
|
|
15
|
+
* @param columnWidths - 行寬陣列(column widths)
|
|
16
|
+
* @returns 總寬度的 CSS 字串(例如 "calc(50% + 400px)" 或 "100%" 或 "800px")
|
|
17
|
+
*/
|
|
18
|
+
export declare function calculateTableMinWidth(columnWidths: ColumnWidth[]): string;
|
|
19
|
+
/**
|
|
20
|
+
* 獲取 cell 的位置資訊
|
|
21
|
+
* @param editor - Slate editor
|
|
22
|
+
* @param types - Table types
|
|
23
|
+
* @param at - 可選的位置,預設使用 editor.selection
|
|
24
|
+
* @returns cell 的位置資訊,如果找不到則返回 null
|
|
25
|
+
*/
|
|
26
|
+
export declare function getCellLocation(editor: Editor, types: TableTypes, at?: any): CellLocation | null;
|
|
27
|
+
/**
|
|
28
|
+
* 獲取 table main 和相關容器資訊
|
|
29
|
+
* @param editor - Slate editor
|
|
30
|
+
* @param types - Table types
|
|
31
|
+
* @param containerPath - 當前容器的路徑
|
|
32
|
+
* @returns table 容器資訊,如果找不到則返回 null
|
|
33
|
+
*/
|
|
34
|
+
export declare function getTableContainers(editor: Editor, types: TableTypes, containerPath: number[]): TableContainers | null;
|
|
35
|
+
/**
|
|
36
|
+
* 嘗試移動到相鄰列的相同行
|
|
37
|
+
* @param location - 當前 cell 位置資訊
|
|
38
|
+
* @param direction - 移動方向('up' 或 'down')
|
|
39
|
+
* @param selectFn - 選擇函數(用於 move 或 extend 模式)
|
|
40
|
+
* @returns 是否成功移動
|
|
41
|
+
*/
|
|
42
|
+
export declare function tryMoveToAdjacentRow(location: CellLocation, direction: 'up' | 'down', selectFn: (cellPath: number[], position: 'start' | 'end') => void): boolean;
|
|
43
|
+
/**
|
|
44
|
+
* 嘗試跨容器移動(header <-> body)
|
|
45
|
+
* @param containers - Table 容器資訊
|
|
46
|
+
* @param location - 當前 cell 位置資訊
|
|
47
|
+
* @param direction - 移動方向('up' 或 'down')
|
|
48
|
+
* @param selectFn - 選擇函數(用於 move 或 extend 模式)
|
|
49
|
+
* @param targetColumn - 目標行索引,預設為保持當前行,設為 0 可強制移動到第一行
|
|
50
|
+
* @returns 是否成功移動
|
|
51
|
+
*/
|
|
52
|
+
export declare function tryCrossBoundaryMove(containers: TableContainers, location: CellLocation, direction: 'up' | 'down', selectFn: (cellPath: number[], position: 'start' | 'end') => void, targetColumn?: number): boolean;
|
|
53
|
+
/**
|
|
54
|
+
* 嘗試移動到下一個 cell
|
|
55
|
+
* @param location - 當前 cell 位置資訊
|
|
56
|
+
* @param selectFn - 選擇函數
|
|
57
|
+
* @returns 是否成功移動
|
|
58
|
+
*/
|
|
59
|
+
export declare function tryMoveToNextCell(location: CellLocation, selectFn: (cellPath: number[], position: 'start' | 'end') => void): boolean;
|
|
60
|
+
/**
|
|
61
|
+
* 嘗試在水平方向擴展選擇(左右移動)
|
|
62
|
+
* @param editor - Slate editor
|
|
63
|
+
* @param location - 當前 cell 位置資訊
|
|
64
|
+
* @param direction - 移動方向('left' 或 'right')
|
|
65
|
+
* @param anchor - 選擇的起點
|
|
66
|
+
* @returns 是否成功擴展
|
|
67
|
+
*/
|
|
68
|
+
export declare function tryExtendSelectionHorizontal(editor: Editor, location: CellLocation, direction: 'left' | 'right', anchor: any): boolean;
|
package/table/utils.js
ADDED
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
import { Editor, Element, Transforms } from '@quadrats/core';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* 將 ColumnWidth 轉換為 CSS 可用的字串
|
|
5
|
+
* @param width - 行寬定義(column width)
|
|
6
|
+
* @returns CSS 寬度字串(例如 "30%" 或 "200px")
|
|
7
|
+
*/
|
|
8
|
+
function columnWidthToCSS(width) {
|
|
9
|
+
if (width.type === 'percentage') {
|
|
10
|
+
return `${width.value.toFixed(1)}%`;
|
|
11
|
+
}
|
|
12
|
+
return `${width.value}px`;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* 計算 table 的總寬度(用於設定 min-width 以支援 overflow)
|
|
16
|
+
* 此函數會將所有 columnWidths 的百分比和 pixel 值加總:
|
|
17
|
+
* - percentage: 保留為百分比
|
|
18
|
+
* - pixel: 直接累加
|
|
19
|
+
*
|
|
20
|
+
* @param columnWidths - 行寬陣列(column widths)
|
|
21
|
+
* @returns 總寬度的 CSS 字串(例如 "calc(50% + 400px)" 或 "100%" 或 "800px")
|
|
22
|
+
*/
|
|
23
|
+
function calculateTableMinWidth(columnWidths) {
|
|
24
|
+
if (columnWidths.length === 0) {
|
|
25
|
+
return '100%';
|
|
26
|
+
}
|
|
27
|
+
let totalPercentage = 0;
|
|
28
|
+
let totalPixels = 0;
|
|
29
|
+
columnWidths.forEach((width) => {
|
|
30
|
+
if (width.type === 'percentage') {
|
|
31
|
+
totalPercentage += width.value;
|
|
32
|
+
}
|
|
33
|
+
else {
|
|
34
|
+
totalPixels += width.value;
|
|
35
|
+
}
|
|
36
|
+
});
|
|
37
|
+
// 只有 percentage,沒有 pixel
|
|
38
|
+
if (totalPixels === 0) {
|
|
39
|
+
return `${totalPercentage.toFixed(1)}%`;
|
|
40
|
+
}
|
|
41
|
+
// 只有 pixel,沒有 percentage
|
|
42
|
+
if (totalPercentage === 0) {
|
|
43
|
+
return `${totalPixels}px`;
|
|
44
|
+
}
|
|
45
|
+
// 有 percentage 也有 pixel
|
|
46
|
+
// 使用 calc() 來結合兩者
|
|
47
|
+
return `calc(${totalPercentage.toFixed(1)}% + ${totalPixels}px)`;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* 獲取 cell 的位置資訊
|
|
51
|
+
* @param editor - Slate editor
|
|
52
|
+
* @param types - Table types
|
|
53
|
+
* @param at - 可選的位置,預設使用 editor.selection
|
|
54
|
+
* @returns cell 的位置資訊,如果找不到則返回 null
|
|
55
|
+
*/
|
|
56
|
+
function getCellLocation(editor, types, at) {
|
|
57
|
+
const cellEntry = Editor.above(editor, {
|
|
58
|
+
at,
|
|
59
|
+
match: (n) => Element.isElement(n) && n.type === types.table_cell,
|
|
60
|
+
});
|
|
61
|
+
if (!cellEntry)
|
|
62
|
+
return null;
|
|
63
|
+
const [, cellPath] = cellEntry;
|
|
64
|
+
const columnIndex = cellPath[cellPath.length - 1];
|
|
65
|
+
const rowEntry = Editor.above(editor, {
|
|
66
|
+
at: cellPath,
|
|
67
|
+
match: (n) => Element.isElement(n) && n.type === types.table_row,
|
|
68
|
+
});
|
|
69
|
+
if (!rowEntry)
|
|
70
|
+
return null;
|
|
71
|
+
const [row, rowPath] = rowEntry;
|
|
72
|
+
const rowIndex = rowPath[rowPath.length - 1];
|
|
73
|
+
const containerEntry = Editor.above(editor, {
|
|
74
|
+
at: rowPath,
|
|
75
|
+
match: (n) => Element.isElement(n) && [types.table_header, types.table_body].includes(n.type),
|
|
76
|
+
});
|
|
77
|
+
if (!containerEntry)
|
|
78
|
+
return null;
|
|
79
|
+
const [container, containerPath] = containerEntry;
|
|
80
|
+
return {
|
|
81
|
+
cellPath,
|
|
82
|
+
columnIndex,
|
|
83
|
+
row,
|
|
84
|
+
rowPath,
|
|
85
|
+
rowIndex,
|
|
86
|
+
container,
|
|
87
|
+
containerPath,
|
|
88
|
+
isHeader: Element.isElement(container) && container.type === types.table_header,
|
|
89
|
+
isBody: Element.isElement(container) && container.type === types.table_body,
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* 獲取 table main 和相關容器資訊
|
|
94
|
+
* @param editor - Slate editor
|
|
95
|
+
* @param types - Table types
|
|
96
|
+
* @param containerPath - 當前容器的路徑
|
|
97
|
+
* @returns table 容器資訊,如果找不到則返回 null
|
|
98
|
+
*/
|
|
99
|
+
function getTableContainers(editor, types, containerPath) {
|
|
100
|
+
const tableMainEntry = Editor.above(editor, {
|
|
101
|
+
at: containerPath,
|
|
102
|
+
match: (n) => Element.isElement(n) && n.type === types.table_main,
|
|
103
|
+
});
|
|
104
|
+
if (!tableMainEntry)
|
|
105
|
+
return null;
|
|
106
|
+
const [tableMain, tableMainPath] = tableMainEntry;
|
|
107
|
+
const tableHeader = tableMain.children.find((child) => Element.isElement(child) && child.type === types.table_header);
|
|
108
|
+
const tableBody = tableMain.children.find((child) => Element.isElement(child) && child.type === types.table_body);
|
|
109
|
+
const tableHeaderIndex = tableHeader ? tableMain.children.findIndex((child) => child === tableHeader) : -1;
|
|
110
|
+
const tableBodyIndex = tableBody ? tableMain.children.findIndex((child) => child === tableBody) : -1;
|
|
111
|
+
return {
|
|
112
|
+
tableMain,
|
|
113
|
+
tableMainPath,
|
|
114
|
+
tableHeader,
|
|
115
|
+
tableBody,
|
|
116
|
+
tableHeaderIndex,
|
|
117
|
+
tableBodyIndex,
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* 嘗試移動到相鄰列的相同行
|
|
122
|
+
* @param location - 當前 cell 位置資訊
|
|
123
|
+
* @param direction - 移動方向('up' 或 'down')
|
|
124
|
+
* @param selectFn - 選擇函數(用於 move 或 extend 模式)
|
|
125
|
+
* @returns 是否成功移動
|
|
126
|
+
*/
|
|
127
|
+
function tryMoveToAdjacentRow(location, direction, selectFn) {
|
|
128
|
+
const { container, containerPath, rowIndex, columnIndex } = location;
|
|
129
|
+
const targetRowIndex = direction === 'up' ? rowIndex - 1 : rowIndex + 1;
|
|
130
|
+
// 嘗試在當前容器中移動
|
|
131
|
+
if (targetRowIndex >= 0 && targetRowIndex < container.children.length) {
|
|
132
|
+
const targetRow = container.children[targetRowIndex];
|
|
133
|
+
if (Element.isElement(targetRow)) {
|
|
134
|
+
const targetColumnIndex = Math.min(columnIndex, targetRow.children.length - 1);
|
|
135
|
+
const targetCellPath = [...containerPath, targetRowIndex, targetColumnIndex];
|
|
136
|
+
selectFn(targetCellPath, direction === 'up' ? 'start' : 'end');
|
|
137
|
+
return true;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
return false;
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* 嘗試跨容器移動(header <-> body)
|
|
144
|
+
* @param containers - Table 容器資訊
|
|
145
|
+
* @param location - 當前 cell 位置資訊
|
|
146
|
+
* @param direction - 移動方向('up' 或 'down')
|
|
147
|
+
* @param selectFn - 選擇函數(用於 move 或 extend 模式)
|
|
148
|
+
* @param targetColumn - 目標行索引,預設為保持當前行,設為 0 可強制移動到第一行
|
|
149
|
+
* @returns 是否成功移動
|
|
150
|
+
*/
|
|
151
|
+
function tryCrossBoundaryMove(containers, location, direction, selectFn, targetColumn) {
|
|
152
|
+
const { columnIndex, isHeader, isBody } = location;
|
|
153
|
+
const { tableMainPath, tableHeader, tableBody, tableHeaderIndex, tableBodyIndex } = containers;
|
|
154
|
+
// 從 body 向上移動到 header
|
|
155
|
+
if (direction === 'up' && isBody && tableHeader && Element.isElement(tableHeader)) {
|
|
156
|
+
const lastRowIndex = tableHeader.children.length - 1;
|
|
157
|
+
const lastRow = tableHeader.children[lastRowIndex];
|
|
158
|
+
if (Element.isElement(lastRow)) {
|
|
159
|
+
const targetColumnIndex = targetColumn !== undefined ? targetColumn : Math.min(columnIndex, lastRow.children.length - 1);
|
|
160
|
+
const targetCellPath = [...tableMainPath, tableHeaderIndex, lastRowIndex, targetColumnIndex];
|
|
161
|
+
selectFn(targetCellPath, 'start');
|
|
162
|
+
return true;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
// 從 header 向下移動到 body
|
|
166
|
+
if (direction === 'down' && isHeader && tableBody && Element.isElement(tableBody)) {
|
|
167
|
+
const firstRow = tableBody.children[0];
|
|
168
|
+
if (Element.isElement(firstRow)) {
|
|
169
|
+
const targetColumnIndex = targetColumn !== undefined ? targetColumn : Math.min(columnIndex, firstRow.children.length - 1);
|
|
170
|
+
const targetCellPath = [...tableMainPath, tableBodyIndex, 0, targetColumnIndex];
|
|
171
|
+
// Tab 導航時使用 'start',上下鍵導航時使用 'end'
|
|
172
|
+
const position = targetColumn === 0 ? 'start' : 'end';
|
|
173
|
+
selectFn(targetCellPath, position);
|
|
174
|
+
return true;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
return false;
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* 嘗試移動到下一個 cell
|
|
181
|
+
* @param location - 當前 cell 位置資訊
|
|
182
|
+
* @param selectFn - 選擇函數
|
|
183
|
+
* @returns 是否成功移動
|
|
184
|
+
*/
|
|
185
|
+
function tryMoveToNextCell(location, selectFn) {
|
|
186
|
+
const { cellPath, row, rowPath, container, containerPath, rowIndex } = location;
|
|
187
|
+
const currentColumnIndex = cellPath[cellPath.length - 1];
|
|
188
|
+
const nextColumnIndex = currentColumnIndex + 1;
|
|
189
|
+
if (nextColumnIndex < row.children.length) {
|
|
190
|
+
const targetCellPath = [...rowPath, nextColumnIndex];
|
|
191
|
+
selectFn(targetCellPath, 'start');
|
|
192
|
+
return true;
|
|
193
|
+
}
|
|
194
|
+
const nextRowIndex = rowIndex + 1;
|
|
195
|
+
if (nextRowIndex < container.children.length) {
|
|
196
|
+
const nextRow = container.children[nextRowIndex];
|
|
197
|
+
if (Element.isElement(nextRow) && nextRow.children.length > 0) {
|
|
198
|
+
const targetCellPath = [...containerPath, nextRowIndex, 0];
|
|
199
|
+
selectFn(targetCellPath, 'start');
|
|
200
|
+
return true;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
return false;
|
|
204
|
+
}
|
|
205
|
+
/**
|
|
206
|
+
* 嘗試在水平方向擴展選擇(左右移動)
|
|
207
|
+
* @param editor - Slate editor
|
|
208
|
+
* @param location - 當前 cell 位置資訊
|
|
209
|
+
* @param direction - 移動方向('left' 或 'right')
|
|
210
|
+
* @param anchor - 選擇的起點
|
|
211
|
+
* @returns 是否成功擴展
|
|
212
|
+
*/
|
|
213
|
+
function tryExtendSelectionHorizontal(editor, location, direction, anchor) {
|
|
214
|
+
var _a;
|
|
215
|
+
const { cellPath, columnIndex, row, rowPath } = location;
|
|
216
|
+
const focus = (_a = editor.selection) === null || _a === void 0 ? void 0 : _a.focus;
|
|
217
|
+
if (!focus)
|
|
218
|
+
return false;
|
|
219
|
+
const isLeftDirection = direction === 'left';
|
|
220
|
+
const isAtBoundary = isLeftDirection ? columnIndex === 0 : columnIndex >= row.children.length - 1;
|
|
221
|
+
// 如果已經在邊界,嘗試擴展到該 cell 的開頭或結尾
|
|
222
|
+
if (isAtBoundary) {
|
|
223
|
+
const boundaryPoint = isLeftDirection ? Editor.start(editor, cellPath) : Editor.end(editor, cellPath);
|
|
224
|
+
// 只有當 focus 還沒到邊界時才移動
|
|
225
|
+
const shouldMove = isLeftDirection
|
|
226
|
+
? focus.offset > boundaryPoint.offset || focus.path.length !== boundaryPoint.path.length
|
|
227
|
+
: focus.offset < boundaryPoint.offset || focus.path.length !== boundaryPoint.path.length;
|
|
228
|
+
if (shouldMove) {
|
|
229
|
+
Transforms.select(editor, { anchor, focus: boundaryPoint });
|
|
230
|
+
}
|
|
231
|
+
return true;
|
|
232
|
+
}
|
|
233
|
+
// 找到目標 cell
|
|
234
|
+
const targetColumnIndex = isLeftDirection ? columnIndex - 1 : columnIndex + 1;
|
|
235
|
+
const targetCellPath = [...rowPath, targetColumnIndex];
|
|
236
|
+
// 根據方向選擇目標點(左邊用 end,右邊用 start)
|
|
237
|
+
const targetPoint = isLeftDirection ? Editor.end(editor, targetCellPath) : Editor.start(editor, targetCellPath);
|
|
238
|
+
// 保持 anchor 不變,移動 focus
|
|
239
|
+
Transforms.select(editor, { anchor, focus: targetPoint });
|
|
240
|
+
return true;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
export { calculateTableMinWidth, columnWidthToCSS, getCellLocation, getTableContainers, tryCrossBoundaryMove, tryExtendSelectionHorizontal, tryMoveToAdjacentRow, tryMoveToNextCell };
|