open-grid 1.0.8 → 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/CHANGELOG.md +24 -0
- package/dist/OpenGrid-5flQwc3W.js +8434 -0
- package/dist/OpenGrid-DahxRY7C.cjs +92 -0
- package/dist/open-grid-base.css +61 -0
- package/dist/open-grid-react.cjs +1 -1
- package/dist/open-grid-react.js +1 -1
- package/dist/open-grid-themes.css +96 -1
- package/dist/open-grid-vue.cjs +1 -1
- package/dist/open-grid-vue.js +1 -1
- package/dist/open-grid.cjs +1 -1
- package/dist/open-grid.js +2 -2
- package/dist/types/core/CellEditManager.d.ts +4 -0
- package/dist/types/core/CellEventHandler.d.ts +6 -0
- package/dist/types/core/ChartManager.d.ts +58 -0
- package/dist/types/core/DataLayer.d.ts +17 -0
- package/dist/types/core/DetailManager.d.ts +72 -0
- package/dist/types/core/FlatRowModel.d.ts +56 -0
- package/dist/types/core/GridRenderer.d.ts +33 -1
- package/dist/types/core/GroupTreeManager.d.ts +13 -0
- package/dist/types/core/KeyboardManager.d.ts +19 -0
- package/dist/types/core/OpenGrid.d.ts +109 -1
- package/dist/types/core/RangeSelectionManager.d.ts +95 -0
- package/dist/types/core/SortFilterManager.d.ts +2 -0
- package/dist/types/core/chart/CanvasAdapter.d.ts +52 -0
- package/dist/types/core/chart/DataExtractor.d.ts +49 -0
- package/dist/types/core/chart/a11y.d.ts +21 -0
- package/dist/types/core/chart/downsample.d.ts +25 -0
- package/dist/types/core/chart/hittest.d.ts +20 -0
- package/dist/types/core/chart/palette.d.ts +56 -0
- package/dist/types/core/chart/scales.d.ts +24 -0
- package/dist/types/core/chart/types.d.ts +181 -0
- package/dist/types/core/detail/DetailGlyph.d.ts +48 -0
- package/dist/types/core/detail/DetailSplice.d.ts +67 -0
- package/dist/types/core/detail/DetailState.d.ts +67 -0
- package/dist/types/core/detail/SubgridCache.d.ts +73 -0
- package/dist/types/core/detail/index.d.ts +14 -0
- package/dist/types/core/formula/FormulaEvaluator.d.ts +15 -0
- package/dist/types/core/formula/FormulaGraph.d.ts +43 -0
- package/dist/types/core/formula/FormulaParser.d.ts +6 -0
- package/dist/types/core/formula/FormulaStore.d.ts +17 -0
- package/dist/types/core/formula/RecalcCoordinator.d.ts +85 -0
- package/dist/types/core/formula/normalizeRefs.d.ts +15 -0
- package/dist/types/core/formula/numericLiteral.d.ts +7 -0
- package/dist/types/core/formula/serializeFormula.d.ts +6 -0
- package/dist/types/core/formula/types.d.ts +104 -0
- package/dist/types/core/range/ClipboardCodec.d.ts +30 -0
- package/dist/types/core/range/FillEngine.d.ts +63 -0
- package/dist/types/core/range/RangeModel.d.ts +53 -0
- package/dist/types/core/range/RangeQuery.d.ts +16 -0
- package/dist/types/core/range/types.d.ts +47 -0
- package/dist/types/core/renderers/CellRenderer.d.ts +2 -0
- package/dist/types/core/types.d.ts +242 -0
- package/dist/types/index.d.ts +2 -1
- package/package.json +1 -1
- package/dist/OpenGrid-CZRcxruq.cjs +0 -90
- package/dist/OpenGrid-Cjv7Os5a.js +0 -4871
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
import { CellRange } from '../types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Phase1 MVP = 'bar'|'line'. 나머지는 Phase2/3 예약(타입만 선언, 미구현).
|
|
4
|
+
*
|
|
5
|
+
* ⚠️ 'bar-stacked': 공개 타입으로 노출되어 있으나 내장(builtin-canvas) 렌더러는 현재 스택
|
|
6
|
+
* 누적을 구현하지 않는다 — 지정해도 'bar-grouped'와 동일하게 그룹형(side-by-side)으로
|
|
7
|
+
* 폴백 렌더된다(CanvasAdapter._computeGeometry). 하위호환을 위해 타입 자체는 유지한다.
|
|
8
|
+
*/
|
|
9
|
+
export type ChartType = 'bar' | 'line' | 'area' | 'pie' | 'doughnut' | 'bar-stacked' | 'bar-grouped';
|
|
10
|
+
export interface ChartSeriesSpec {
|
|
11
|
+
field: string;
|
|
12
|
+
name?: string;
|
|
13
|
+
color?: string;
|
|
14
|
+
pattern?: ChartSeries['pattern'];
|
|
15
|
+
type?: 'bar' | 'line' | 'area';
|
|
16
|
+
}
|
|
17
|
+
export type ChartSource = {
|
|
18
|
+
kind: 'range';
|
|
19
|
+
range?: CellRange;
|
|
20
|
+
} | {
|
|
21
|
+
kind: 'selection';
|
|
22
|
+
} | {
|
|
23
|
+
kind: 'checked';
|
|
24
|
+
} | {
|
|
25
|
+
kind: 'all';
|
|
26
|
+
} | {
|
|
27
|
+
kind: 'columns';
|
|
28
|
+
category?: string;
|
|
29
|
+
series: Array<string | ChartSeriesSpec>;
|
|
30
|
+
};
|
|
31
|
+
export type ChartAggregateOp = 'sum' | 'avg' | 'count' | 'min' | 'max';
|
|
32
|
+
export type ChartAggregate = ChartAggregateOp | ((values: number[], category: string) => number);
|
|
33
|
+
export interface A11yTableModel {
|
|
34
|
+
/** aria-label 요약 캡션 */
|
|
35
|
+
caption: string;
|
|
36
|
+
/** ['category', ...series names] */
|
|
37
|
+
colHeaders: string[];
|
|
38
|
+
/** [category, v1, v2, ...] — locale 포맷 문자열 */
|
|
39
|
+
rows: string[][];
|
|
40
|
+
}
|
|
41
|
+
export interface ChartSeries {
|
|
42
|
+
/** 범례 라벨(기본값 = 원본 컬럼 header/field) */
|
|
43
|
+
name: string;
|
|
44
|
+
/** categories 와 동일 순서로 정렬. 결측=null */
|
|
45
|
+
data: Array<number | null>;
|
|
46
|
+
color?: string;
|
|
47
|
+
/** 색 외 구분(HANMS-19) */
|
|
48
|
+
pattern?: 'solid' | 'hatch' | 'dot' | 'cross';
|
|
49
|
+
}
|
|
50
|
+
export interface ChartDataModelMeta {
|
|
51
|
+
sourceKind: ChartSource['kind'];
|
|
52
|
+
/** 원본 포인트(행) 수 */
|
|
53
|
+
total: number;
|
|
54
|
+
/** LTTB 다운샘플 또는 category 집계로 축약됐는가 → 배지(§C) */
|
|
55
|
+
sampled: boolean;
|
|
56
|
+
sampledFrom?: number;
|
|
57
|
+
sampledTo?: number;
|
|
58
|
+
aggregatedOp?: 'sum' | 'avg' | 'count' | 'min' | 'max' | 'custom';
|
|
59
|
+
pieReducedToFirst?: boolean;
|
|
60
|
+
negativesAbsInPie?: boolean;
|
|
61
|
+
/** SR 폴백 — extractor 가 렌더러와 무관하게 항상 채운다(§B 하드 게이트) */
|
|
62
|
+
a11yTable: A11yTableModel;
|
|
63
|
+
}
|
|
64
|
+
export interface ChartDataModel {
|
|
65
|
+
categories: string[];
|
|
66
|
+
series: ChartSeries[];
|
|
67
|
+
meta: ChartDataModelMeta;
|
|
68
|
+
}
|
|
69
|
+
export interface ChartTheme {
|
|
70
|
+
primary: string;
|
|
71
|
+
border: string;
|
|
72
|
+
text: string;
|
|
73
|
+
bg: string;
|
|
74
|
+
gridLine: string;
|
|
75
|
+
fontFamily: string;
|
|
76
|
+
fontSize: number;
|
|
77
|
+
palette: string[];
|
|
78
|
+
}
|
|
79
|
+
export interface ChartRenderSpec {
|
|
80
|
+
type: ChartType;
|
|
81
|
+
title?: string;
|
|
82
|
+
legend?: boolean | {
|
|
83
|
+
position: 'top' | 'bottom' | 'left' | 'right';
|
|
84
|
+
};
|
|
85
|
+
tooltip?: boolean;
|
|
86
|
+
axis?: {
|
|
87
|
+
xLabel?: string;
|
|
88
|
+
yLabel?: string;
|
|
89
|
+
yMin?: number;
|
|
90
|
+
yMax?: number;
|
|
91
|
+
stacked?: boolean;
|
|
92
|
+
};
|
|
93
|
+
palette?: string[];
|
|
94
|
+
theme: ChartTheme;
|
|
95
|
+
numberFormat?: (v: number, ctx: {
|
|
96
|
+
axis: 'x' | 'y' | 'tooltip' | 'legend';
|
|
97
|
+
field?: string;
|
|
98
|
+
}) => string;
|
|
99
|
+
a11y: A11yTableModel;
|
|
100
|
+
}
|
|
101
|
+
export interface ChartPoint {
|
|
102
|
+
seriesName: string;
|
|
103
|
+
category: string;
|
|
104
|
+
value: number | null;
|
|
105
|
+
index: number;
|
|
106
|
+
rowId?: string;
|
|
107
|
+
}
|
|
108
|
+
export interface ChartAdapter {
|
|
109
|
+
readonly id: string;
|
|
110
|
+
init(host: HTMLElement, spec: ChartRenderSpec): Promise<void>;
|
|
111
|
+
render(model: ChartDataModel, spec: ChartRenderSpec): void;
|
|
112
|
+
resize(width: number, height: number): void;
|
|
113
|
+
toBlob?(mime?: string): Promise<Blob | null>;
|
|
114
|
+
onPointClick?(cb: (p: ChartPoint) => void): void;
|
|
115
|
+
destroy(): void;
|
|
116
|
+
}
|
|
117
|
+
/** createChart 반환 핸들(§6). 구현은 ChartManager. */
|
|
118
|
+
export interface ChartInstance {
|
|
119
|
+
readonly id: string;
|
|
120
|
+
update(patch?: Partial<ChartConfig>): void;
|
|
121
|
+
/**
|
|
122
|
+
* 모델을 재추출하고 렌더 스펙(테마 포함)을 재스냅샷해 재렌더한다(§5.3). 그리드는 테마 변경
|
|
123
|
+
* 이벤트를 발행하지 않으므로(`setTheme`/`setThemeVar`는 CSS 만 갱신) 다크모드 토글처럼 데이터
|
|
124
|
+
* 변화 없는 순수 테마 전환 후에는, 열려 있는 각 차트에 대해 이 메서드를 호출해야 새 테마
|
|
125
|
+
* (색·글꼴·팔레트)가 재적용된다. `update()`/`setType()`도 내부적으로 동일 경로를 탄다.
|
|
126
|
+
*/
|
|
127
|
+
refresh(): void;
|
|
128
|
+
setType(type: ChartType): void;
|
|
129
|
+
destroy(): void;
|
|
130
|
+
toBlob(mime?: string): Promise<Blob | null>;
|
|
131
|
+
getModel(): ChartDataModel;
|
|
132
|
+
on(ev: 'chartRender' | 'chartPointClick', cb: (...a: any[]) => void): void;
|
|
133
|
+
}
|
|
134
|
+
/** GridOptions.chart 로 중첩되는 전역 옵션(C5.1, 최상위 flat 키 금지). */
|
|
135
|
+
export interface ChartGlobalOptions {
|
|
136
|
+
enabled?: boolean;
|
|
137
|
+
defaultEngine?: 'builtin' | 'chartjs' | 'echarts';
|
|
138
|
+
defaultType?: ChartType;
|
|
139
|
+
placement?: 'docked' | 'modal' | 'inline' | 'floating';
|
|
140
|
+
maxPoints?: number;
|
|
141
|
+
debounceMs?: number;
|
|
142
|
+
palette?: string[];
|
|
143
|
+
numberFormat?: (v: number, ctx: {
|
|
144
|
+
axis: 'x' | 'y' | 'tooltip' | 'legend';
|
|
145
|
+
field?: string;
|
|
146
|
+
}) => string;
|
|
147
|
+
onChartCreate?: (i: ChartInstance) => void;
|
|
148
|
+
onChartRender?: (e: {
|
|
149
|
+
id: string;
|
|
150
|
+
model: ChartDataModel;
|
|
151
|
+
}) => void;
|
|
152
|
+
onChartPointClick?: (e: {
|
|
153
|
+
id: string;
|
|
154
|
+
point: ChartPoint;
|
|
155
|
+
}) => void;
|
|
156
|
+
onChartDestroy?: (e: {
|
|
157
|
+
id: string;
|
|
158
|
+
}) => void;
|
|
159
|
+
}
|
|
160
|
+
export interface ChartConfig {
|
|
161
|
+
source: ChartSource;
|
|
162
|
+
type: ChartType;
|
|
163
|
+
engine?: 'builtin' | 'chartjs' | 'echarts' | ChartAdapter;
|
|
164
|
+
placement?: 'docked' | 'modal' | 'inline' | 'floating';
|
|
165
|
+
mount?: HTMLElement;
|
|
166
|
+
category?: string;
|
|
167
|
+
series?: Array<string | ChartSeriesSpec>;
|
|
168
|
+
aggregate?: ChartAggregate;
|
|
169
|
+
maxPoints?: number;
|
|
170
|
+
live?: boolean;
|
|
171
|
+
title?: string;
|
|
172
|
+
legend?: ChartRenderSpec['legend'];
|
|
173
|
+
tooltip?: boolean;
|
|
174
|
+
axis?: ChartRenderSpec['axis'];
|
|
175
|
+
palette?: string[];
|
|
176
|
+
numberFormat?: ChartRenderSpec['numberFormat'];
|
|
177
|
+
size?: {
|
|
178
|
+
width: number;
|
|
179
|
+
height: number;
|
|
180
|
+
};
|
|
181
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DetailGlyph — F2 디테일 expander 글리프/aria 정책 (C10 R-DETAIL-GLYPH, HANMS-06 해소).
|
|
3
|
+
*
|
|
4
|
+
* 계약 근거:
|
|
5
|
+
* - docs/design/grid-features-2026-07/15_cross_contracts.md C10 "R-DETAIL-GLYPH": detail expander는
|
|
6
|
+
* **셰브론(▸/▾) 금지**. 트리 셰브론과 별도 글리프(`⊕/⊖` 또는 `▤ 상세`)+툴팁("상세 보기")+
|
|
7
|
+
* aria-label 로 구분. 트리가 **비활성**일 때만 셰브론 허용.
|
|
8
|
+
* - docs/design/grid-features-2026-07/11_design_F2_v2.md §4.5(글리프/aria 상세), §1.2 NFR-5(접근성
|
|
9
|
+
* 측정가능 AC: aria-expanded 토글, aria-controls 연결), §4.3(panel.id 규칙 = aria-controls 타겟),
|
|
10
|
+
* C8.4(터치 히트 타겟 ≥44×44 CSS px).
|
|
11
|
+
*
|
|
12
|
+
* 렌더 배선(GridRenderer expander 셀)이 이 모듈의 순수 함수/상수를 소비해 실제 DOM 속성을 채운다.
|
|
13
|
+
* 이 파일은 DOM 을 만들지 않는다 — 문자열/속성 값만 생성.
|
|
14
|
+
*/
|
|
15
|
+
/** 트리 셰브론(▸/▾)과 충돌하지 않는 전용 글리프(C10 기본값). */
|
|
16
|
+
export declare const DETAIL_GLYPH_COLLAPSED = "\u2295";
|
|
17
|
+
export declare const DETAIL_GLYPH_EXPANDED = "\u2296";
|
|
18
|
+
/** 라벨형 대안(옵션) — 기본은 원형 글리프, toggle:'first-cell' 등 배치에서 선택적으로 사용. */
|
|
19
|
+
export declare const DETAIL_GLYPH_LABEL_COLLAPSED = "\u25A4 \uC0C1\uC138";
|
|
20
|
+
export declare const DETAIL_GLYPH_TOOLTIP = "\uC0C1\uC138 \uBCF4\uAE30";
|
|
21
|
+
/** C8.4: expander 히트 타겟 최소 CSS px(가로/세로). */
|
|
22
|
+
export declare const DETAIL_EXPANDER_MIN_HIT_TARGET_PX = 44;
|
|
23
|
+
export interface DetailGlyphInfo {
|
|
24
|
+
/** 화면에 그릴 문자(셰브론 아님, C10). */
|
|
25
|
+
glyph: string;
|
|
26
|
+
/** expander 자체 aria-label(NFR-5, HANMS-06 — SR 에서 트리 토글과 구분). */
|
|
27
|
+
ariaLabel: string;
|
|
28
|
+
/** 툴팁 title 속성(C10 "상세 보기" 고정 문구). */
|
|
29
|
+
title: string;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* 펼침 여부에 따른 글리프/aria-label/title 조합을 반환한다. 트리 셰브론과 절대 겹치지 않는
|
|
33
|
+
* 문자셋만 사용(C10 R-DETAIL-GLYPH 하드 룰).
|
|
34
|
+
*/
|
|
35
|
+
export declare function getDetailGlyph(expanded: boolean): DetailGlyphInfo;
|
|
36
|
+
/** NFR-5(1): 마스터 행 `aria-expanded` 값(문자열 — DOM setAttribute 그대로 사용). */
|
|
37
|
+
export declare function getMasterRowAriaExpanded(expanded: boolean): 'true' | 'false';
|
|
38
|
+
/**
|
|
39
|
+
* NFR-5(2)/§4.3: 패널 DOM id 규칙. expander 의 `aria-controls` 와 패널의 `id` 는 반드시 이
|
|
40
|
+
* 한 함수로만 생성해 두 값의 불일치를 구조적으로 차단한다.
|
|
41
|
+
*/
|
|
42
|
+
export declare function getDetailPanelId(rowId: string): string;
|
|
43
|
+
/**
|
|
44
|
+
* §4.5 "트리 동시 활성 시 충돌 해소" 규칙의 판별 헬퍼: 트리가 활성화된 그리드에서는 셰브론을
|
|
45
|
+
* 쓸 수 없다(트리 셰브론과 자리를 공유하지 않도록 렌더 배선이 별도 컬럼/색을 쓰게 강제하는 신호).
|
|
46
|
+
* 트리 비활성일 때만 셰브론 허용 여부를 true 로 반환한다.
|
|
47
|
+
*/
|
|
48
|
+
export declare function isChevronAllowedForDetail(treeActive: boolean): boolean;
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DetailSplice — 상류 flat 배열에 F2 디테일 head/filler 의사행을 끼워 넣는 순수 함수.
|
|
3
|
+
*
|
|
4
|
+
* 계약 근거:
|
|
5
|
+
* - docs/design/grid-features-2026-07/11_design_F2_v2.md §2.2/§3.2(단방향 합성 체인의 마지막 단계:
|
|
6
|
+
* plain → group/tree(GroupTreeManager 소유) → **detail splice(이 파일)** → FlatRowModel.updateFlatModel),
|
|
7
|
+
* §4.1(DetailHeadRow/DetailFillerRow 형태), §7(그룹 헤더엔 미부착·트리 데이터 행엔 허용)
|
|
8
|
+
* - docs/design/grid-features-2026-07/15_cross_contracts.md C0.3(FlatRowRef.kind
|
|
9
|
+
* 'detailHead'/'detailFiller' — filler 에 writeCell 절대 금지의 판별 재료)
|
|
10
|
+
*
|
|
11
|
+
* MVP는 (b) 정수-슬롯: 디테일 높이 = span(=ceil(height/rowHeight)) × rowHeight 개의 **정수 rowHeight
|
|
12
|
+
* 슬롯**으로 표현한다. 1개 head(span 보유) + (span-1)개 filler. VirtualScroll 은 이 슬롯들도 여느
|
|
13
|
+
* 인덱스와 동일한 rowHeight 로 보고 무변경(CON-1)으로 남는다.
|
|
14
|
+
*
|
|
15
|
+
* ⚠️ 이 함수는 실제 GroupTreeManager/FlatRowModel(둘 다 이 작업과 동시에 변경 중)을 import 하지
|
|
16
|
+
* 않는다. 대신 GridRenderer.ts 가 이미 소비하는 런타임 관례(`_isGroup`/`_isTree` 플래그, 트리 노드의
|
|
17
|
+
* `.data`)를 로컬 타입 가드로만 재선언해 상류 flat 항목을 분류한다 — 배선 단계에서 실제 등록
|
|
18
|
+
* 어댑터가 이 함수를 호출한다.
|
|
19
|
+
*/
|
|
20
|
+
/** GroupTreeManager 산출물(GroupRow)과 동형의 최소 구조 — 로컬 판별용(실제 타입 import 안 함). */
|
|
21
|
+
interface GroupLikeItem {
|
|
22
|
+
_isGroup: true;
|
|
23
|
+
}
|
|
24
|
+
/** TreeEngine TreeNode 와 동형의 최소 구조 — 로컬 판별용(실제 타입 import 안 함). */
|
|
25
|
+
interface TreeLikeItem<T = any> {
|
|
26
|
+
_isTree: true;
|
|
27
|
+
data: T;
|
|
28
|
+
}
|
|
29
|
+
/** F2 디테일 head 의사행(§4.1). span 개의 rowHeight 슬롯 중 첫 슬롯 — 실제 패널이 여기 그려진다. */
|
|
30
|
+
export interface DetailHeadItem<T = any> {
|
|
31
|
+
_isDetailHead: true;
|
|
32
|
+
_rowId: string;
|
|
33
|
+
/** 마스터 원본 flat 항목(트리/그룹 컨텍스트 유지용, optional — §4.1). */
|
|
34
|
+
_masterFlatBase?: T | GroupLikeItem | TreeLikeItem<T>;
|
|
35
|
+
/** k = ceil(height/rowHeight). 슬롯 총 개수(head 포함). */
|
|
36
|
+
_span: number;
|
|
37
|
+
}
|
|
38
|
+
/** F2 디테일 filler 의사행(§4.1). span-1 개. renderBody 는 이 인덱스를 만나면 continue(패널이 덮음). */
|
|
39
|
+
export interface DetailFillerItem {
|
|
40
|
+
_isDetailFiller: true;
|
|
41
|
+
_rowId: string;
|
|
42
|
+
}
|
|
43
|
+
export type DetailSplicedItem<T = any> = T | GroupLikeItem | TreeLikeItem<T> | DetailHeadItem<T> | DetailFillerItem;
|
|
44
|
+
/** 행별 슬롯수 콜백(옵션) — 지정 시 고정 height 대신 이 값을 사용(반올림 후 ceil 없이 그대로 슬롯수로 채택). */
|
|
45
|
+
export type SlotCountFn<T = any> = (row: T, rowId: string) => number;
|
|
46
|
+
export interface DetailSpliceOptions<T = any> {
|
|
47
|
+
/** DetailState.expandedRowIds — stable rowId 앵커(정렬/필터 넘어 보존, FR-4/C0.5). */
|
|
48
|
+
expandedRowIds: ReadonlySet<string>;
|
|
49
|
+
/** 마스터 후보 row(T)에서 stable rowId 를 뽑는다. undefined/null 이면 해당 항목은 detail 부착 대상에서 제외. */
|
|
50
|
+
getRowId: (row: T) => string | undefined | null;
|
|
51
|
+
rowHeight: number;
|
|
52
|
+
/** masterDetail.height(px). 기본 200. getSlotCount 미지정 시 이 값으로 ceil(height/rowHeight) 계산(EC-10 양자화). */
|
|
53
|
+
height?: number;
|
|
54
|
+
/** 지정 시 height 대신 이 콜백으로 슬롯수(정수, 최소 1) 결정 — masterDetail.detailRowCount 류 확장 지점. */
|
|
55
|
+
getSlotCount?: SlotCountFn<T>;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* baseFlat(plain rows 또는 GroupTreeManager 산출물)을 순회하며, 펼쳐진 stable rowId 를 만난
|
|
59
|
+
* 마스터 행 **직후**에 `[head, ...filler×(span-1)]` 를 삽입한다(§4.1). 순수 함수 — baseFlat 은
|
|
60
|
+
* 변경하지 않고 새 배열을 반환한다.
|
|
61
|
+
*
|
|
62
|
+
* expandedRowIds 가 비어 있으면(펼침 0/F2 비활성) 항등에 가깝게 동작하되, §3.2 v2 규범대로
|
|
63
|
+
* "DetailManager는 항상 최종 flat 소유자"이므로 얕은 복사본을 반환한다(호출자가 항상 새 배열을
|
|
64
|
+
* updateFlatModel 에 넘기는 계약을 일관되게 유지하기 위함 — 참조 동일성에 의존한 버그 예방).
|
|
65
|
+
*/
|
|
66
|
+
export declare function spliceDetails<T = any>(baseFlat: ReadonlyArray<DetailSplicedItem<T>>, opts: DetailSpliceOptions<T>): DetailSplicedItem<T>[];
|
|
67
|
+
export {};
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DetailState — F2 마스터/디테일 펼침 상태 (헤드리스).
|
|
3
|
+
*
|
|
4
|
+
* 계약 근거:
|
|
5
|
+
* - docs/design/grid-features-2026-07/11_design_F2_v2.md §3.1(DetailManager 상태 부분),
|
|
6
|
+
* §6.1(masterDetail.maxDepth/expandMultiple 기본값), §6.3(rowExpand/rowCollapse payload)
|
|
7
|
+
* - docs/design/grid-features-2026-07/15_cross_contracts.md C0.5(범위/선택 정체성 = stable-id 앵커 —
|
|
8
|
+
* F2 펼침도 동일 원칙), C5.2(이벤트명 `<subject><Verb>` camelCase)
|
|
9
|
+
*
|
|
10
|
+
* 책임 경계: 이 파일은 **펼침 상태(Set<rowId>)와 규칙**만 다룬다. DOM/서브그리드 생명주기는
|
|
11
|
+
* SubgridCache, flat 배열 스플라이스는 DetailSplice 가 담당한다(관심사 분리, Booch 경계).
|
|
12
|
+
*
|
|
13
|
+
* ⚠️ FlatRowModel(src/core/FlatRowModel.ts)은 착륙 중인 별도 파일이라 여기서 직접 import 하지 않는다.
|
|
14
|
+
* rowIndex→rowId 해소는 배선 단계(OpenGrid/DetailManager)에서 FlatRowModel 경유로 수행하고,
|
|
15
|
+
* 이 클래스는 이미 해소된 stable rowId(string)만 입력받는다.
|
|
16
|
+
*/
|
|
17
|
+
/** FR-6 rowExpand/rowCollapse 이벤트 payload 재료. rowIndex/host 는 배선 단계에서 채운다(§6.3). */
|
|
18
|
+
export interface RowExpandEventPayload {
|
|
19
|
+
rowIndex: number;
|
|
20
|
+
rowId: string;
|
|
21
|
+
row: any;
|
|
22
|
+
host: HTMLElement | null;
|
|
23
|
+
}
|
|
24
|
+
export interface DetailStateOptions {
|
|
25
|
+
/** masterDetail.maxDepth, 기본 2 (CON-4/FR-10). */
|
|
26
|
+
maxDepth?: number;
|
|
27
|
+
/** masterDetail.expandMultiple, 기본 true. false=아코디언(펼침 1개만 허용). */
|
|
28
|
+
expandMultiple?: boolean;
|
|
29
|
+
/** 이 그리드 인스턴스의 현재 중첩 깊이(부모가 자식 생성 시 depth+1 주입, CON-4). 기본 0(최상위). */
|
|
30
|
+
depth?: number;
|
|
31
|
+
}
|
|
32
|
+
export type DetailToggleResult = 'expanded' | 'collapsed' | 'rejected';
|
|
33
|
+
/**
|
|
34
|
+
* 펼침 상태 보관소. rowId 는 DataLayer stable id(`_ogRowId` 등) — 정렬/필터로 flat 인덱스가
|
|
35
|
+
* 바뀌어도 이 Set 의 멤버십은 그대로 보존된다(FR-4, C0.5).
|
|
36
|
+
*/
|
|
37
|
+
export declare class DetailState {
|
|
38
|
+
private _expanded;
|
|
39
|
+
private _maxDepth;
|
|
40
|
+
private _expandMultiple;
|
|
41
|
+
private _depth;
|
|
42
|
+
constructor(opts?: DetailStateOptions);
|
|
43
|
+
/** 읽기 전용 뷰. 소유권은 이 클래스가 유지(외부에서 mutate 금지). */
|
|
44
|
+
get expandedRowIds(): ReadonlySet<string>;
|
|
45
|
+
get size(): number;
|
|
46
|
+
get depth(): number;
|
|
47
|
+
get maxDepth(): number;
|
|
48
|
+
get expandMultiple(): boolean;
|
|
49
|
+
isExpanded(rowId: string): boolean;
|
|
50
|
+
/** CON-4/FR-10: depth >= maxDepth 면 이 그리드에서는 더 이상 펼칠 수 없다(중첩 깊이 경계). */
|
|
51
|
+
canExpand(): boolean;
|
|
52
|
+
/**
|
|
53
|
+
* rowId 를 펼침 상태로 표시.
|
|
54
|
+
* - depth 초과(EC-6/FR-10)면 false 반환(거부) — 호출자가 announce() 등 경고를 낸다.
|
|
55
|
+
* - 이미 펼쳐져 있으면 멱등(true, 재적용 없음 — EC-7 빠른 토글 연타 방어).
|
|
56
|
+
* - expandMultiple:false(아코디언)면 기존 펼침을 전부 접고 이 rowId 만 남긴다.
|
|
57
|
+
*/
|
|
58
|
+
expand(rowId: string): boolean;
|
|
59
|
+
/** rowId 를 접음. 이미 접혀 있었으면 false(no-op 신호, 호출자가 이벤트 emit 여부 판단). */
|
|
60
|
+
collapse(rowId: string): boolean;
|
|
61
|
+
/** 토글 1회 = 정확히 하나의 결과(FR-6 "emit 정확히 1회"의 기반). */
|
|
62
|
+
toggle(rowId: string): DetailToggleResult;
|
|
63
|
+
/** collapseAllDetails() 배선용. 접힌 rowId 목록을 반환(호출자가 각각 rowCollapse emit 판단). */
|
|
64
|
+
collapseAll(): string[];
|
|
65
|
+
/** FR-6 payload 조립 헬퍼(순수 데이터 조립, emit 자체는 배선 단계 EventEmitter 몫). */
|
|
66
|
+
buildEventPayload(rowId: string, rowIndex: number, row: any, host: HTMLElement | null): RowExpandEventPayload;
|
|
67
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SubgridCache — F2 임베드 서브그리드(디테일 인스턴스) 생명주기 관리 (헤드리스, DOM 비접촉).
|
|
3
|
+
*
|
|
4
|
+
* 계약 근거:
|
|
5
|
+
* - docs/design/grid-features-2026-07/11_design_F2_v2.md §3.1(DetailEntry: host/instance/built),
|
|
6
|
+
* §5(Mount-once + detached 캐시 + 편집중 skip-rebuild — MCCONNELL-04 해소),
|
|
7
|
+
* §5(5)(collapse: cache!==true 면 destroy, cache:true 면 host 캐시 유지)
|
|
8
|
+
*
|
|
9
|
+
* 핵심 의미론(§5 "핵심 통찰"): `_body.innerHTML=''`(GridRenderer 매 렌더 teardown, CON-2)는
|
|
10
|
+
* "삭제"가 아니라 "분리"다. 이 클래스는 그 통찰을 **mount-once ≠ detach ≠ delete** 3단계로
|
|
11
|
+
* 모델링한다:
|
|
12
|
+
* 1) mount-once — rowId 당 `adapter.create` 는 정확히 1회만 호출(getOrCreate).
|
|
13
|
+
* 2) detach/reattach — 매 렌더 teardown/재조립을 생존(파괴 아님, DOM 이동일 뿐).
|
|
14
|
+
* 3) destroy — collapse(cache:false) 또는 그리드 destroy 시에만 실제 정리.
|
|
15
|
+
*
|
|
16
|
+
* 이 클래스는 DOM 을 직접 만들거나 조작하지 않는다 — 호스트 생성(`hostFactory`)과 실제 마운트/해제
|
|
17
|
+
* 동작은 전부 `SubgridAdapter` 주입 인터페이스 뒤로 위임한다(렌더 배선 단계가 실 DOM/자식 OpenGrid를
|
|
18
|
+
* 연결). §5(4) 편집중 skip-rebuild 훅 자리는 `isEditing()` 로 자리만 마련해 둔다(Phase1 배선 대상).
|
|
19
|
+
*/
|
|
20
|
+
/** 서브그리드(또는 임의 렌더러 산출물) 생명주기 훅 — DOM 조작은 이 어댑터 구현체 책임. */
|
|
21
|
+
export interface SubgridAdapter<T = any> {
|
|
22
|
+
/** rowId 당 정확히 1회 호출(§5 mount-once). host 는 영속 div(재렌더 생존, DetailManager.getDetailHost 산출물). */
|
|
23
|
+
create(rowId: string, host: HTMLElement): T;
|
|
24
|
+
/** teardown 직전: host 를 문서에서 분리하되 instance 는 파괴하지 않는다(§5 "분리≠삭제"). */
|
|
25
|
+
detach(instance: T, host: HTMLElement): void;
|
|
26
|
+
/** 재조립 시: 동일 host/instance 를 새 패널 wrapper 에 재부착. */
|
|
27
|
+
reattach(instance: T, host: HTMLElement): void;
|
|
28
|
+
/** collapse(cache:false) 또는 그리드 destroy 시에만 호출 — 실제 정리(EC-9). */
|
|
29
|
+
destroy(instance: T, host: HTMLElement): void;
|
|
30
|
+
}
|
|
31
|
+
export interface RemoveOptions {
|
|
32
|
+
/** masterDetail.cache. true 면 destroy 대신 detach 만 하고 entry 를 유지(재펼침 시 재사용, §5(5)). */
|
|
33
|
+
cache?: boolean;
|
|
34
|
+
}
|
|
35
|
+
export declare class SubgridCache<T = any> {
|
|
36
|
+
private adapter;
|
|
37
|
+
private _entries;
|
|
38
|
+
constructor(adapter: SubgridAdapter<T>);
|
|
39
|
+
get size(): number;
|
|
40
|
+
has(rowId: string): boolean;
|
|
41
|
+
/**
|
|
42
|
+
* 최초 펼침: `hostFactory()` 로 영속 host 를 만들고 `adapter.create` 를 **정확히 1회** 호출한다.
|
|
43
|
+
* 이미 캐시돼 있으면(재펼침, cache:true 로 남아있던 entry) 기존 entry 를 그대로 반환 — create
|
|
44
|
+
* 재호출 없음(mount-once 보장의 핵심 단언 지점).
|
|
45
|
+
*/
|
|
46
|
+
getOrCreate(rowId: string, hostFactory: () => HTMLElement): {
|
|
47
|
+
host: HTMLElement;
|
|
48
|
+
instance: T;
|
|
49
|
+
};
|
|
50
|
+
/** 매 renderBody 재조립 시: 이미 분리돼 있던 entry 만 재부착(파괴됐던 적 없음, §5 핵심 통찰). */
|
|
51
|
+
reattach(rowId: string): boolean;
|
|
52
|
+
/** teardown 직전 분리 표시. entry 는 Map 에 남아 GC 되지 않는다(참조 유지 = 생존, §5 핵심 통찰). */
|
|
53
|
+
detach(rowId: string): boolean;
|
|
54
|
+
/** 부모 renderBody 진입 시 전체 teardown 흉내(호출자가 skip-rebuild 대상은 미리 걸러 호출). */
|
|
55
|
+
detachAll(): void;
|
|
56
|
+
/**
|
|
57
|
+
* collapse 시 정책 분기(§5(5)): `cache:true` 면 destroy 없이 detach 만(재펼침 시 getOrCreate 가
|
|
58
|
+
* 재사용). 기본(`cache` 미지정/false)이면 `adapter.destroy` 후 entry 제거(EC-9 누수 차단).
|
|
59
|
+
*/
|
|
60
|
+
remove(rowId: string, opts?: RemoveOptions): void;
|
|
61
|
+
getInstance(rowId: string): T | undefined;
|
|
62
|
+
getHost(rowId: string): HTMLElement | undefined;
|
|
63
|
+
isAttached(rowId: string): boolean;
|
|
64
|
+
/**
|
|
65
|
+
* §5(4) 편집중 skip-rebuild 훅 자리(Phase1, FR-8/NFR-2). 렌더 배선 단계에서 자식 인스턴스가
|
|
66
|
+
* `isEditing()` 같은 판정을 노출하면 `isEditingFn` 으로 주입해 teardown 대상에서 제외(hoist)할지
|
|
67
|
+
* 판단하는 데 쓴다. 헤드리스 단계에서는 판정 위임만 제공하고 실제 hoist/원복 로직은 GridRenderer
|
|
68
|
+
* 배선이 담당한다.
|
|
69
|
+
*/
|
|
70
|
+
isEditing(rowId: string, isEditingFn?: (instance: T) => boolean): boolean;
|
|
71
|
+
/** 부모 destroy(EC-9) 또는 collapseAllDetails 시: 남은 전 인스턴스 정리, 누수 차단. */
|
|
72
|
+
destroyAll(): void;
|
|
73
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* src/core/detail — F2 마스터/디테일 헤드리스 코어 배럴.
|
|
3
|
+
* 계약: docs/design/grid-features-2026-07/11_design_F2_v2.md, 15_cross_contracts.md
|
|
4
|
+
*
|
|
5
|
+
* 후속 배선(미포함, 이 디렉터리 범위 밖):
|
|
6
|
+
* - GridRenderer.renderBody 의 `_isDetailHead`/`_isDetailFiller` 분기 + full-width 패널 절대배치
|
|
7
|
+
* - VirtualScroll.setTotalRows 에 detail 슬롯 반영(spliceDetails 결과 길이)
|
|
8
|
+
* - FlatRowModel.registerSplice(spliceDetails 어댑터) 연결
|
|
9
|
+
* - OpenGrid 의 expander 셀 렌더(DetailGlyph 소비) + expandRow/collapseRow/toggleRow API 배선
|
|
10
|
+
*/
|
|
11
|
+
export * from './DetailState.js';
|
|
12
|
+
export * from './DetailSplice.js';
|
|
13
|
+
export * from './SubgridCache.js';
|
|
14
|
+
export * from './DetailGlyph.js';
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { AstNode, CellKey, CellValue, FormulaErrorCode, FormulaGridAccessor } from './types.js';
|
|
2
|
+
export interface EvalOutcome {
|
|
3
|
+
value: CellValue;
|
|
4
|
+
error: FormulaErrorCode | null;
|
|
5
|
+
approx: boolean;
|
|
6
|
+
touched: Set<CellKey>;
|
|
7
|
+
}
|
|
8
|
+
export interface EvalOptions {
|
|
9
|
+
divisionPrecision?: number;
|
|
10
|
+
}
|
|
11
|
+
/** 정규화된 AST 하나를 평가한다. 절대 throw 하지 않는다(에러는 outcome.error 로 격리, F3-R24). */
|
|
12
|
+
export declare function evaluate(ast: AstNode, host: {
|
|
13
|
+
rowId: string;
|
|
14
|
+
field: string;
|
|
15
|
+
}, accessor: FormulaGridAccessor, opts?: EvalOptions): EvalOutcome;
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { CellKey } from './types.js';
|
|
2
|
+
export declare class FormulaGraph {
|
|
3
|
+
/** cellKey(수식) → 그 수식이 참조하는 선행 셀 키 집합. */
|
|
4
|
+
private readonly _precedents;
|
|
5
|
+
/** cellKey(임의: 수식이든 값 리프든) → 그 키를 참조하는 수식 키 집합(dirty 전파 방향). */
|
|
6
|
+
private readonly _dependents;
|
|
7
|
+
/** rowId → 그 rowId의 "어떤 field"든 deps 로 가진 수식 키 집합(행 삭제 역조회, §5.1). */
|
|
8
|
+
private readonly _byRowId;
|
|
9
|
+
/** field → 그 field 를 deps 로 가진 수식 키 집합(열 삭제 역조회). */
|
|
10
|
+
private readonly _byField;
|
|
11
|
+
private _addEdge;
|
|
12
|
+
private _removeEdgesFrom;
|
|
13
|
+
/** key(수식 셀)의 선행 엣지를 deps 로 완전히 교체한다(§5.1 addFormula). */
|
|
14
|
+
addFormula(key: CellKey, deps: Set<CellKey>): void;
|
|
15
|
+
/** 수식 삭제(clearCellFormula) — 엣지 + 역인덱스 정리. */
|
|
16
|
+
removeFormula(key: CellKey): void;
|
|
17
|
+
isFormula(key: CellKey): boolean;
|
|
18
|
+
getPrecedents(key: CellKey): CellKey[];
|
|
19
|
+
getDependents(key: CellKey): CellKey[];
|
|
20
|
+
/**
|
|
21
|
+
* dirty 폐포(BFS): seeds 로부터 도달 가능한 모든 dependents(수식 노드만, dependents 맵의
|
|
22
|
+
* value 는 항상 수식 키이므로 자동 보장). seed 자신이 수식이면 폐포에 포함한다
|
|
23
|
+
* (§5.3 "수식 셀 편집 → 자신 + dependents dirty").
|
|
24
|
+
*/
|
|
25
|
+
getDependentsClosure(seeds: CellKey[]): CellKey[];
|
|
26
|
+
/** Kahn 위상정렬(subset 내부 선행수 기준, §5.4). 잔여 노드 = 사이클 구성원. */
|
|
27
|
+
topoOrder(subset: CellKey[]): {
|
|
28
|
+
order: CellKey[];
|
|
29
|
+
cycles: CellKey[];
|
|
30
|
+
};
|
|
31
|
+
/**
|
|
32
|
+
* 삭제 무효화 역조회(§5.1/F3-R28 P0): 특정 rowId(전체 field, field 생략 시) 또는
|
|
33
|
+
* (rowId,field) 특정 셀을 deps 로 가진 수식 키들. removeRow/removeColumn 후크가
|
|
34
|
+
* 이 결과를 onValuesChanged 의 seed 로 넘기면(대상 rowId/field 는 이미 grid 에서
|
|
35
|
+
* 제거된 상태이므로) 평가 시 accessor.hasRow/hasField 가 false 를 반환해 자연스럽게
|
|
36
|
+
* #REF 로 귀결한다(별도 특수 처리 없이 일반 재계산 경로 재사용).
|
|
37
|
+
*/
|
|
38
|
+
formulasReferencing(rowId: string, field?: string): CellKey[];
|
|
39
|
+
/** 열 삭제 역조회(field 단독 기준, §5.1). */
|
|
40
|
+
formulasReferencingField(field: string): CellKey[];
|
|
41
|
+
/** 등록된 모든 수식 키(recalculateAll 전체 재계산용). */
|
|
42
|
+
allFormulaKeys(): CellKey[];
|
|
43
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { CellKey, FormulaCell } from './types.js';
|
|
2
|
+
export declare class FormulaStore {
|
|
3
|
+
private readonly _cells;
|
|
4
|
+
setFormula(rowId: string, field: string, cell: FormulaCell): void;
|
|
5
|
+
getFormula(rowId: string, field: string): FormulaCell | undefined;
|
|
6
|
+
clearFormula(rowId: string, field: string): void;
|
|
7
|
+
hasFormula(rowId: string, field: string): boolean;
|
|
8
|
+
getFormulaByKey(key: CellKey): FormulaCell | undefined;
|
|
9
|
+
getAllFormulaCells(): Array<{
|
|
10
|
+
rowId: string;
|
|
11
|
+
field: string;
|
|
12
|
+
cell: FormulaCell;
|
|
13
|
+
}>;
|
|
14
|
+
/** applySort/applyFilter 후크가 dirty 대상으로 모아야 할 range-보유 수식 키(§3.5 P0). */
|
|
15
|
+
getRangeBearingKeys(): CellKey[];
|
|
16
|
+
size(): number;
|
|
17
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { CellKey, CellValue, FormulaCell, FormulaErrorCode, FormulaGridAccessor, RefMode } from './types.js';
|
|
2
|
+
import { indexToColLetters } from './normalizeRefs.js';
|
|
3
|
+
import { FormulaStore } from './FormulaStore.js';
|
|
4
|
+
import { FormulaGraph } from './FormulaGraph.js';
|
|
5
|
+
export interface RecalcSummary {
|
|
6
|
+
changed: CellKey[];
|
|
7
|
+
cycles: number;
|
|
8
|
+
ms: number;
|
|
9
|
+
}
|
|
10
|
+
export interface RecalcCoordinatorOptions {
|
|
11
|
+
accessor: FormulaGridAccessor;
|
|
12
|
+
setComputedValue: (rowId: string, field: string, value: CellValue) => void;
|
|
13
|
+
onFormulaError?: (rowId: string, field: string, error: FormulaErrorCode) => void;
|
|
14
|
+
refMode?: RefMode;
|
|
15
|
+
divisionPrecision?: number;
|
|
16
|
+
}
|
|
17
|
+
export declare class RecalcCoordinator {
|
|
18
|
+
readonly store: FormulaStore;
|
|
19
|
+
readonly graph: FormulaGraph;
|
|
20
|
+
private readonly _accessor;
|
|
21
|
+
private readonly _setComputedValue;
|
|
22
|
+
private readonly _onFormulaError;
|
|
23
|
+
private readonly _refMode;
|
|
24
|
+
private readonly _prec;
|
|
25
|
+
constructor(opts: RecalcCoordinatorOptions);
|
|
26
|
+
/** src("=...")를 파싱+정규화한다. 파싱 자체가 실패하면 #ERR/#NAME 을 담은 error AST로 폴백. */
|
|
27
|
+
compile(src: string, host: {
|
|
28
|
+
rowId: string;
|
|
29
|
+
field: string;
|
|
30
|
+
}): {
|
|
31
|
+
ast: FormulaCell['ast'];
|
|
32
|
+
hasRangeRef: boolean;
|
|
33
|
+
};
|
|
34
|
+
/**
|
|
35
|
+
* setCellFormula(§8.2) — 저장 후 즉시 평가하고, 기존 dependents 가 있으면 함께 dirty 처리.
|
|
36
|
+
*
|
|
37
|
+
* ⚠️ 순서가 중요하다(사이클 탐지 정합성): 새 수식을 등록하는 바로 그 순간에 사이클이
|
|
38
|
+
* "완성"될 수 있다(예: A1=B1 이미 있고 지금 B1=A1 을 등록). onValuesChanged 의
|
|
39
|
+
* topoOrder 는 "그래프에 현재 등록된 엣지"만으로 사이클을 판정하므로, 이 새 수식의
|
|
40
|
+
* deps 를 그래프에 등록하기 *전에* 배치를 돌리면 방금 완성된 사이클을 놓친다.
|
|
41
|
+
* 그래서 여기서 먼저 1회 "탐색 평가"(probe)로 실제 deps 를 얻어 그래프 엣지를 확정한
|
|
42
|
+
* 뒤에야 onValuesChanged(공식 배치, 사이클이면 evaluate 재호출 없이 #CYCLE 로 귀결)를
|
|
43
|
+
* 호출한다. 신규 수식 등록 1회당 evaluate 호출이 최대 2회(probe+공식)로 늘지만, 이는
|
|
44
|
+
* "정의 시점" 1회성 비용이며 F3-R21 이 요구하는 "값 편집에 따른 증분 재계산"(steady
|
|
45
|
+
* state)과는 무관하다(§13 f3.perf.closure 는 신규 등록이 아니라 leaf 값 변경 시나리오).
|
|
46
|
+
*/
|
|
47
|
+
setCellFormula(rowId: string, field: string, src: string): RecalcSummary;
|
|
48
|
+
getCellFormula(rowId: string, field: string): string | null;
|
|
49
|
+
hasCellFormula(rowId: string, field: string): boolean;
|
|
50
|
+
/** clearCellFormula(§8.2) — 사이드카+그래프에서 제거. 값(row[field])은 그리드 쪽 책임(값 유지). */
|
|
51
|
+
clearCellFormula(rowId: string, field: string): CellKey[];
|
|
52
|
+
getCellError(rowId: string, field: string): FormulaErrorCode | null;
|
|
53
|
+
/** 디버깅용(§8.2 getDependents) — flat 좌표가 아닌 (rowId,field) 그대로 반환(헤드리스 계층). */
|
|
54
|
+
getDependents(rowId: string, field: string): Array<{
|
|
55
|
+
rowId: string;
|
|
56
|
+
field: string;
|
|
57
|
+
}>;
|
|
58
|
+
/**
|
|
59
|
+
* C2 배치 진입점: keys(값이 바뀐 leaf 셀 또는 재평가가 필요한 수식 셀)로부터 dependents
|
|
60
|
+
* 폐포를 구해 Kahn 위상 1패스로 재계산한다. 삭제 무효화(F3-R28)는 별도 특수 로직 없이,
|
|
61
|
+
* 호출부가 "삭제된 rowId/field 를 이미 grid 에서 제거한 뒤" 그 dependents 를 keys 로
|
|
62
|
+
* 넘기면 evaluate 과정에서 accessor.hasRow/hasField 가 false → 자연스럽게 #REF.
|
|
63
|
+
*/
|
|
64
|
+
onValuesChanged(keys: CellKey[]): RecalcSummary;
|
|
65
|
+
private _evaluateOne;
|
|
66
|
+
/** recalculate()(§8.2) — 전체 수식 노드 위상정렬 후 평가(setData/컬럼 변경 등). */
|
|
67
|
+
recalculateAll(): RecalcSummary;
|
|
68
|
+
/** applySort/applyFilter 후크가 호출할 편의 메서드 — range 보유 수식 전부 dirty(§3.5 P0). */
|
|
69
|
+
recalcRangeBearing(): RecalcSummary;
|
|
70
|
+
/** removeRow 후크 편의 메서드 — 이 rowId 를 deps 로 가진 수식들을 재계산(자연 #REF, §5.1). */
|
|
71
|
+
invalidateRow(rowId: string): RecalcSummary;
|
|
72
|
+
/** removeColumn 후크 편의 메서드 — 이 field 를 deps 로 가진 수식들을 재계산(자연 #REF). */
|
|
73
|
+
invalidateField(field: string): RecalcSummary;
|
|
74
|
+
/**
|
|
75
|
+
* srcRowId/srcField 의 저장된 수식에서 "상대(비-$) 축만" dRow/dCol 만큼 오프셋한 새
|
|
76
|
+
* 수식 원문을 만든다. 절대($) 축은 불변(C3). 대상 위치가 범위를 벗어나면 해당 참조는
|
|
77
|
+
* "#REF!" 텍스트로 대체된다(Excel 관례, 이 문자열은 재파싱 시 #NAME 이 아니라 명시적
|
|
78
|
+
* 깨짐을 사용자가 알아볼 수 있게 하기 위한 것 — 실제 파서가 #REF! 토큰 자체를 정식
|
|
79
|
+
* 문법으로 지원하진 않으므로, F1 배선 시 이 반환값을 그대로 setCellFormula 에 넘기면
|
|
80
|
+
* 파싱 실패 → #ERR 로 귀결한다는 점을 후속 배선 태스크가 인지해야 한다).
|
|
81
|
+
*/
|
|
82
|
+
offsetFormula(srcRowId: string, srcField: string, dRow: number, dCol: number): string;
|
|
83
|
+
private _shiftAst;
|
|
84
|
+
}
|
|
85
|
+
export { indexToColLetters };
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { AstNode, FormulaGridAccessor, RefMode } from './types.js';
|
|
2
|
+
/** 열문자(A,B,...,Z,AA,...) → 0-based visibleLeaves 인덱스. */
|
|
3
|
+
export declare function colLettersToIndex(letters: string): number;
|
|
4
|
+
/** 0-based visibleLeaves 인덱스 → 열문자(A,B,...,Z,AA,...). offsetFormula/serializeFormula 재사용. */
|
|
5
|
+
export declare function indexToColLetters(index: number): string;
|
|
6
|
+
/**
|
|
7
|
+
* 파서가 만든 원시 AST(rawRef/rawRange 포함)를 정규화해 ref/range/error 노드로 치환한다.
|
|
8
|
+
* host = 이 수식이 저장된 셀(현재 행 컨텍스트, FieldRef·상대참조 기준점).
|
|
9
|
+
*/
|
|
10
|
+
export declare function normalizeAst(ast: AstNode, host: {
|
|
11
|
+
rowId: string;
|
|
12
|
+
field: string;
|
|
13
|
+
}, accessor: FormulaGridAccessor, refMode?: RefMode): AstNode;
|
|
14
|
+
/** 이 AST가 범위 참조를 포함하는지(§3.5 hasRangeRef, applySort/applyFilter dirty 대상). */
|
|
15
|
+
export declare function computeHasRangeRef(ast: AstNode): boolean;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/** 수식 리터럴/셀 텍스트가 산술 가능한 숫자 형태인지 판별(정수/소수/과학표기). */
|
|
2
|
+
export declare const NUMERIC_LITERAL_RE: RegExp;
|
|
3
|
+
/**
|
|
4
|
+
* 과학표기(`1e-5`, `2.5E3`)를 정확한 10진 문자열로 변환한다(부호/자릿수 이동만 사용,
|
|
5
|
+
* 부동소수점 경유 없음). 과학표기가 아니면 trim만 하여 그대로 반환.
|
|
6
|
+
*/
|
|
7
|
+
export declare function normalizeNumericLiteral(raw: string): string;
|