@operato/scene-table 9.1.1 → 9.2.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.
|
@@ -21,12 +21,36 @@ var DataListLayout = {
|
|
|
21
21
|
var paddingTop = container.getState('paddingTop') || 0;
|
|
22
22
|
var width_unit = inside.width / widths_sum;
|
|
23
23
|
var height_unit = inside.height / heights_sum;
|
|
24
|
-
|
|
25
|
-
var
|
|
24
|
+
// Calculate row height (assuming uniform height for now based on first row config)
|
|
25
|
+
var rowHeight = (heights ? heights[0] : 1) * height_unit;
|
|
26
26
|
var components = container.components;
|
|
27
27
|
components.forEach((component, idx) => {
|
|
28
|
-
|
|
28
|
+
if (component.hidden) {
|
|
29
|
+
// Position hidden components far outside the viewport
|
|
30
|
+
component.bounds = {
|
|
31
|
+
left: -10000,
|
|
32
|
+
top: -10000,
|
|
33
|
+
width: 0,
|
|
34
|
+
height: 0
|
|
35
|
+
};
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
// @ts-ignore
|
|
39
|
+
var rowIndex = component._rowIndex;
|
|
40
|
+
// Fallback for non-virtualized or initial state
|
|
41
|
+
if (rowIndex === undefined) {
|
|
42
|
+
rowIndex = Math.floor(idx / columns);
|
|
43
|
+
}
|
|
44
|
+
var colIndex = idx % columns;
|
|
45
|
+
let w = widths ? widths[colIndex] : 1;
|
|
29
46
|
let h = heights ? heights[0] : 1;
|
|
47
|
+
// Calculate x position based on column widths
|
|
48
|
+
let x = 0;
|
|
49
|
+
for (let i = 0; i < colIndex; i++) {
|
|
50
|
+
x += (widths ? widths[i] : 1) * width_unit;
|
|
51
|
+
}
|
|
52
|
+
// Calculate y position based on rowIndex and offset
|
|
53
|
+
let y = rowIndex * rowHeight + offset.y;
|
|
30
54
|
let left = paddingLeft + x;
|
|
31
55
|
let top = paddingTop + y;
|
|
32
56
|
let width = width_unit * w;
|
|
@@ -38,13 +62,6 @@ var DataListLayout = {
|
|
|
38
62
|
height
|
|
39
63
|
};
|
|
40
64
|
component.set('rotation', 0);
|
|
41
|
-
if (idx % columns == columns - 1) {
|
|
42
|
-
x = 0;
|
|
43
|
-
y += h * height_unit;
|
|
44
|
-
}
|
|
45
|
-
else {
|
|
46
|
-
x += w * width_unit;
|
|
47
|
-
}
|
|
48
65
|
});
|
|
49
66
|
},
|
|
50
67
|
capturables: function (container) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"data-list-layout.js","sourceRoot":"","sources":["../../src/data-list/data-list-layout.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAwB,MAAM,EAAE,MAAM,wBAAwB,CAAA;AAKrE,IAAI,cAAc,GAAG;IACnB,MAAM,EAAE,UAAU,SAAoB;QACpC,IAAI,YAAY,GAAG,SAAS,CAAC,QAAQ,CAAC,cAAc,CAAC,CAAA;QAErD,IAAI,OAAO,GAAG,CAAC,YAAY,IAAI,YAAY,CAAC,OAAO,CAAC,IAAI,SAAS,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAA;QACrF,IAAI,IAAI,GAAG,CAAC,YAAY,IAAI,YAAY,CAAC,IAAI,CAAC,IAAI,SAAS,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAA;QAC5E,IAAI,MAAM,GAAG,CAAC,YAAY,IAAI,YAAY,CAAC,MAAM,CAAC,IAAI,SAAS,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAA;QAClF,IAAI,OAAO,GAAG,CAAC,YAAY,IAAI,YAAY,CAAC,OAAO,CAAC,IAAI,SAAS,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAA;QACrF,IAAI,EAAE,MAAM,GAAG,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,EAAE,GAAG,SAAS,CAAC,KAAK,CAAA;QAEjD,IAAI,UAAU,GAAG,MAAM;YACrB,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,KAAa,EAAE,CAAS,EAAE,EAAE,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,MAAM,CAAC,CAAC,GAAW,EAAE,KAAa,EAAE,EAAE,CAAC,GAAG,GAAG,KAAK,EAAE,CAAC,CAAC;YACjH,CAAC,CAAC,OAAO,CAAA;QACX,IAAI,WAAW,GAAG,OAAO;YACvB,CAAC,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,MAAc,EAAE,CAAS,EAAE,EAAE,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC,MAAM,CAAC,CAAC,GAAW,EAAE,MAAc,EAAE,EAAE,CAAC,GAAG,GAAG,MAAM,EAAE,CAAC,CAAC;YAClH,CAAC,CAAC,IAAI,CAAA;QAER,IAAI,MAAM,GAAG,SAAS,CAAC,UAAU,CAAA;QACjC,IAAI,WAAW,GAAG,SAAS,CAAC,QAAQ,CAAC,aAAa,CAAC,IAAI,CAAC,CAAA;QACxD,IAAI,UAAU,GAAG,SAAS,CAAC,QAAQ,CAAC,YAAY,CAAC,IAAI,CAAC,CAAA;QAEtD,IAAI,UAAU,GAAG,MAAM,CAAC,KAAK,GAAG,UAAU,CAAA;QAC1C,IAAI,WAAW,GAAG,MAAM,CAAC,MAAM,GAAG,WAAW,CAAA;QAE7C,IAAI,
|
|
1
|
+
{"version":3,"file":"data-list-layout.js","sourceRoot":"","sources":["../../src/data-list/data-list-layout.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * Copyright © HatioLab Inc. All rights reserved.\n */\n\nimport { Component, Container, Layout } from '@hatiolab/things-scene'\n\nimport DataCell from './data-cell.js'\nimport DataList from './data-list.js'\n\nvar DataListLayout = {\n reflow: function (container: Container) {\n var layoutConfig = container.getState('layoutConfig')\n\n var columns = (layoutConfig && layoutConfig.columns) || container.getState('columns')\n var rows = (layoutConfig && layoutConfig.rows) || container.getState('rows')\n var widths = (layoutConfig && layoutConfig.widths) || container.getState('widths')\n var heights = (layoutConfig && layoutConfig.heights) || container.getState('heights')\n var { offset = { x: 0, y: 0 } } = container.state\n\n var widths_sum = widths\n ? widths.filter((width: number, i: number) => i < columns).reduce((sum: number, width: number) => sum + width, 0)\n : columns\n var heights_sum = heights\n ? heights.filter((height: number, i: number) => i < rows).reduce((sum: number, height: number) => sum + height, 0)\n : rows\n\n var inside = container.textBounds\n var paddingLeft = container.getState('paddingLeft') || 0\n var paddingTop = container.getState('paddingTop') || 0\n\n var width_unit = inside.width / widths_sum\n var height_unit = inside.height / heights_sum\n\n // Calculate row height (assuming uniform height for now based on first row config)\n var rowHeight = (heights ? heights[0] : 1) * height_unit\n\n var components = container.components\n\n components.forEach((component, idx) => {\n if (component.hidden) {\n // Position hidden components far outside the viewport\n component.bounds = {\n left: -10000,\n top: -10000,\n width: 0,\n height: 0\n }\n return\n }\n\n // @ts-ignore\n var rowIndex = component._rowIndex\n\n // Fallback for non-virtualized or initial state\n if (rowIndex === undefined) {\n rowIndex = Math.floor(idx / columns)\n }\n\n var colIndex = idx % columns\n\n let w = widths ? widths[colIndex] : 1\n let h = heights ? heights[0] : 1\n\n // Calculate x position based on column widths\n let x = 0\n for (let i = 0; i < colIndex; i++) {\n x += (widths ? widths[i] : 1) * width_unit\n }\n\n // Calculate y position based on rowIndex and offset\n let y = rowIndex * rowHeight + offset.y\n\n let left = paddingLeft + x\n let top = paddingTop + y\n let width = width_unit * w\n let height = height_unit * h\n\n component.bounds = {\n left,\n top,\n width,\n height\n }\n component.set('rotation', 0)\n })\n },\n\n capturables: function (container: Container) {\n return container.components\n },\n\n drawables: function (container: Container) {\n return container.components\n },\n\n isStuck: function (component: Component) {\n return true\n },\n\n /*\n * 레이아웃별로, 키보드 방향키 등을 사용해서 네비게이션 할 수 있는 기능을 제공할 수 있다.\n * 하나의 컴포넌트만 선택되어있고, 키보드 이벤트가 발생했을 때 호출되게 된다.\n * keyNavigate 메쏘드가 정의되어 있지 않으면, 'Tab' 키에 대한 네비게이션만 작동한다.\n * 'Tab'키에 의한 네비게이션은 모든 레이아웃에 공통으로 적용된다.\n */\n keyNavigate: function (container: Container, component: Component, e: KeyboardEvent) {\n var layoutConfig = container.getState('layoutConfig')\n\n var columns = (layoutConfig && layoutConfig.columns) || container.getState('columns')\n var rows = (layoutConfig && layoutConfig.rows) || container.getState('rows')\n\n var { row, column } = (container as DataList).getRowColumn(component as DataCell)\n\n switch (e.code) {\n case 'ArrowUp':\n if (row > 0) return container.getAt((row - 1) * columns + column)\n break\n case 'ArrowDown':\n if (row < rows - 1) return container.getAt((row + 1) * columns + column)\n break\n case 'ArrowRight':\n if (column < columns - 1) return container.getAt(row * columns + column + 1)\n break\n case 'ArrowLeft':\n if (column > 0) return container.getAt(row * columns + column - 1)\n break\n default:\n return component\n }\n },\n\n /*\n * 하위 컴포넌트를 영역으로 선택하는 경우에, 바운드에 join만 되어도 선택된 것으로 판단하도록 한다.\n * joinType이 false이거나, 정의되어있지 않으면, 바운드에 포함되어야 선택된 것으로 판단한다.\n */\n joinType: true\n}\n\nLayout.register('data-list', DataListLayout)\n\nexport default DataListLayout\n"]}
|
|
@@ -5,7 +5,20 @@ import { WHERE } from '../helper-functions.js';
|
|
|
5
5
|
export default class DataList extends Container {
|
|
6
6
|
reflowing: boolean;
|
|
7
7
|
postrender(context: CanvasRenderingContext2D): void;
|
|
8
|
+
private _renderStart;
|
|
9
|
+
private _renderEnd;
|
|
8
10
|
renderScrollbar(context: CanvasRenderingContext2D): void;
|
|
11
|
+
private _scrollbarBounds?;
|
|
12
|
+
private _scrollbarTopIconBounds?;
|
|
13
|
+
private _scrollbarBottomIconBounds?;
|
|
14
|
+
private _isDraggingScrollbar;
|
|
15
|
+
private _dragStartY;
|
|
16
|
+
private _startOffsetY;
|
|
17
|
+
ondragstart(e: MouseEvent): void;
|
|
18
|
+
onclick(e: MouseEvent): void;
|
|
19
|
+
ondragmove(e: MouseEvent): void;
|
|
20
|
+
ondragend(e: MouseEvent): void;
|
|
21
|
+
onkeydown(e: KeyboardEvent): void;
|
|
9
22
|
created(): void;
|
|
10
23
|
_onwheel(e: WheelEvent): void;
|
|
11
24
|
private __START_OFFSET?;
|
|
@@ -37,6 +50,10 @@ export default class DataList extends Container {
|
|
|
37
50
|
touchstart: (e: TouchEvent) => void;
|
|
38
51
|
touchmove: (e: TouchEvent) => void;
|
|
39
52
|
touchend: (e: TouchEvent) => void;
|
|
53
|
+
dragstart: (e: MouseEvent) => void;
|
|
54
|
+
dragmove: (e: MouseEvent) => void;
|
|
55
|
+
dragend: (e: MouseEvent) => void;
|
|
56
|
+
click: (e: MouseEvent) => void;
|
|
40
57
|
};
|
|
41
58
|
};
|
|
42
59
|
};
|
|
@@ -45,6 +62,7 @@ export default class DataList extends Container {
|
|
|
45
62
|
}): void;
|
|
46
63
|
reflow(): void;
|
|
47
64
|
setCellsData(): void;
|
|
65
|
+
updateViewport(): void;
|
|
48
66
|
setCellsStyle(cells: Component[], style: Style, where: WHERE, clearBefore?: boolean): void;
|
|
49
67
|
buildCells(newcolumns: number, oldcolumns: number): void;
|
|
50
68
|
getRowColumn(cell: Component): {
|
|
@@ -25,6 +25,14 @@ let DataList = class DataList extends Container {
|
|
|
25
25
|
constructor() {
|
|
26
26
|
super(...arguments);
|
|
27
27
|
this.reflowing = false;
|
|
28
|
+
/*
|
|
29
|
+
* Virtualization State
|
|
30
|
+
*/
|
|
31
|
+
this._renderStart = 0;
|
|
32
|
+
this._renderEnd = 0;
|
|
33
|
+
this._isDraggingScrollbar = false;
|
|
34
|
+
this._dragStartY = 0;
|
|
35
|
+
this._startOffsetY = 0;
|
|
28
36
|
}
|
|
29
37
|
postrender(context) {
|
|
30
38
|
super.postrender(context);
|
|
@@ -42,13 +50,263 @@ let DataList = class DataList extends Container {
|
|
|
42
50
|
}
|
|
43
51
|
var start = (-offset.y / fullHeight) * height;
|
|
44
52
|
var end = ((-offset.y + height) / fullHeight) * height;
|
|
53
|
+
// Calculate scroll position boundaries
|
|
54
|
+
const rowHeight = (this.heights[0] / this.heights_sum) * height;
|
|
55
|
+
const minOffsetY = Math.min(-fullHeight + height, 0);
|
|
56
|
+
const isAtTop = offset.y >= 0 || offset.y >= -rowHeight / 2;
|
|
57
|
+
const isAtBottom = offset.y <= minOffsetY || offset.y <= minOffsetY + rowHeight / 2;
|
|
58
|
+
// Store scrollbar bounds for hit testing (absolute coordinates)
|
|
59
|
+
this._scrollbarBounds = {
|
|
60
|
+
left: left + width - 10,
|
|
61
|
+
top: top + start,
|
|
62
|
+
width: 10,
|
|
63
|
+
height: end - start
|
|
64
|
+
};
|
|
65
|
+
// Render Scrollbar Track
|
|
66
|
+
context.beginPath();
|
|
67
|
+
context.strokeStyle = '#e0e0e0';
|
|
68
|
+
context.lineWidth = 16; // Wider than thumb
|
|
69
|
+
context.globalAlpha = 0.2;
|
|
70
|
+
context.moveTo(left + width - 10, top);
|
|
71
|
+
context.lineTo(left + width - 10, top + height);
|
|
72
|
+
context.stroke();
|
|
73
|
+
// Render Scrollbar Thumb
|
|
74
|
+
context.beginPath();
|
|
45
75
|
context.strokeStyle = 'gray';
|
|
46
76
|
context.lineWidth = 10;
|
|
47
|
-
context.globalAlpha = 0.
|
|
48
|
-
context.beginPath();
|
|
77
|
+
context.globalAlpha = 0.5;
|
|
49
78
|
context.moveTo(left + width - 10, top + start);
|
|
50
79
|
context.lineTo(left + width - 10, top + end);
|
|
51
80
|
context.stroke();
|
|
81
|
+
// Icon button dimensions
|
|
82
|
+
const iconSize = 20;
|
|
83
|
+
const iconPadding = 4;
|
|
84
|
+
// Render TOP icon (upward triangle - HOME) only if not at top
|
|
85
|
+
if (!isAtTop) {
|
|
86
|
+
// Store icon bounds for hit testing (absolute coordinates)
|
|
87
|
+
this._scrollbarTopIconBounds = {
|
|
88
|
+
left: left + width - 10 - iconSize / 2,
|
|
89
|
+
top: top + iconPadding,
|
|
90
|
+
width: iconSize,
|
|
91
|
+
height: iconSize
|
|
92
|
+
};
|
|
93
|
+
context.globalAlpha = 0.8;
|
|
94
|
+
context.fillStyle = 'gray';
|
|
95
|
+
// Draw horizontal line at top (representing the "end/boundary")
|
|
96
|
+
context.lineWidth = 2;
|
|
97
|
+
context.strokeStyle = 'gray';
|
|
98
|
+
context.beginPath();
|
|
99
|
+
const topIconCenterX = left + width - 10;
|
|
100
|
+
const topIconCenterY = top + iconPadding + iconSize / 2;
|
|
101
|
+
const lineY = topIconCenterY - iconSize / 3;
|
|
102
|
+
context.moveTo(topIconCenterX - iconSize / 3, lineY);
|
|
103
|
+
context.lineTo(topIconCenterX + iconSize / 3, lineY);
|
|
104
|
+
context.stroke();
|
|
105
|
+
// Draw upward triangle (touching the line)
|
|
106
|
+
context.beginPath();
|
|
107
|
+
context.moveTo(topIconCenterX, lineY); // top point (touching line)
|
|
108
|
+
context.lineTo(topIconCenterX - iconSize / 3, topIconCenterY + iconSize / 4); // bottom left
|
|
109
|
+
context.lineTo(topIconCenterX + iconSize / 3, topIconCenterY + iconSize / 4); // bottom right
|
|
110
|
+
context.closePath();
|
|
111
|
+
context.fill();
|
|
112
|
+
}
|
|
113
|
+
else {
|
|
114
|
+
// Clear top icon bounds when not visible
|
|
115
|
+
this._scrollbarTopIconBounds = undefined;
|
|
116
|
+
}
|
|
117
|
+
// Render BOTTOM icon (downward triangle - END) only if not at bottom
|
|
118
|
+
if (!isAtBottom) {
|
|
119
|
+
// Store icon bounds for hit testing (absolute coordinates)
|
|
120
|
+
this._scrollbarBottomIconBounds = {
|
|
121
|
+
left: left + width - 10 - iconSize / 2,
|
|
122
|
+
top: top + height - iconSize - iconPadding,
|
|
123
|
+
width: iconSize,
|
|
124
|
+
height: iconSize
|
|
125
|
+
};
|
|
126
|
+
context.globalAlpha = 0.8;
|
|
127
|
+
context.fillStyle = 'gray';
|
|
128
|
+
// Draw downward triangle
|
|
129
|
+
context.beginPath();
|
|
130
|
+
const bottomIconCenterX = left + width - 10;
|
|
131
|
+
const bottomIconCenterY = top + height - iconPadding - iconSize / 2;
|
|
132
|
+
const bottomLineY = bottomIconCenterY + iconSize / 3;
|
|
133
|
+
context.moveTo(bottomIconCenterX, bottomLineY); // bottom point (touching line)
|
|
134
|
+
context.lineTo(bottomIconCenterX - iconSize / 3, bottomIconCenterY - iconSize / 4); // top left
|
|
135
|
+
context.lineTo(bottomIconCenterX + iconSize / 3, bottomIconCenterY - iconSize / 4); // top right
|
|
136
|
+
context.closePath();
|
|
137
|
+
context.fill();
|
|
138
|
+
// Draw horizontal line at bottom (representing the "end/boundary")
|
|
139
|
+
context.lineWidth = 2;
|
|
140
|
+
context.strokeStyle = 'gray';
|
|
141
|
+
context.beginPath();
|
|
142
|
+
context.moveTo(bottomIconCenterX - iconSize / 3, bottomLineY);
|
|
143
|
+
context.lineTo(bottomIconCenterX + iconSize / 3, bottomLineY);
|
|
144
|
+
context.stroke();
|
|
145
|
+
}
|
|
146
|
+
else {
|
|
147
|
+
// Clear bottom icon bounds when not visible
|
|
148
|
+
this._scrollbarBottomIconBounds = undefined;
|
|
149
|
+
}
|
|
150
|
+
// Reset alpha
|
|
151
|
+
context.globalAlpha = 1.0;
|
|
152
|
+
}
|
|
153
|
+
ondragstart(e) {
|
|
154
|
+
var _a;
|
|
155
|
+
if (this._scrollbarBounds) {
|
|
156
|
+
const { x, y } = this.transcoordC2S(e.offsetX, e.offsetY);
|
|
157
|
+
const { left, top, width, height } = this._scrollbarBounds;
|
|
158
|
+
// Check if click is in the scrollbar track area (right side)
|
|
159
|
+
// Using a wider hit area for the track
|
|
160
|
+
if (x >= left - 20 && x <= left + width + 10) {
|
|
161
|
+
// Check if click is on the thumb
|
|
162
|
+
if (y >= top && y <= top + height) {
|
|
163
|
+
this._isDraggingScrollbar = true;
|
|
164
|
+
this._dragStartY = y;
|
|
165
|
+
this._startOffsetY = ((_a = this.state.offset) === null || _a === void 0 ? void 0 : _a.y) || 0;
|
|
166
|
+
e.stopPropagation();
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
onclick(e) {
|
|
173
|
+
const { x, y } = this.transcoordC2S(e.offsetX, e.offsetY);
|
|
174
|
+
// Check for TOP icon click (HOME functionality)
|
|
175
|
+
if (this._scrollbarTopIconBounds) {
|
|
176
|
+
const { left, top, width, height } = this._scrollbarTopIconBounds;
|
|
177
|
+
if (x >= left && x <= left + width && y >= top && y <= top + height) {
|
|
178
|
+
// Scroll to top (HOME)
|
|
179
|
+
this.setState({
|
|
180
|
+
offset: {
|
|
181
|
+
x: 0,
|
|
182
|
+
y: 0
|
|
183
|
+
}
|
|
184
|
+
});
|
|
185
|
+
e.stopPropagation();
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
// Check for BOTTOM icon click (END functionality)
|
|
190
|
+
if (this._scrollbarBottomIconBounds) {
|
|
191
|
+
const { left, top, width, height } = this._scrollbarBottomIconBounds;
|
|
192
|
+
if (x >= left && x <= left + width && y >= top && y <= top + height) {
|
|
193
|
+
// Scroll to bottom (END)
|
|
194
|
+
const { height: containerHeight } = this.bounds;
|
|
195
|
+
const { data = [] } = this.state;
|
|
196
|
+
const rowHeight = (this.heights[0] / this.heights_sum) * containerHeight;
|
|
197
|
+
const fullHeight = data.length * rowHeight;
|
|
198
|
+
const minOffsetY = Math.min(-fullHeight + containerHeight, 0);
|
|
199
|
+
this.setState({
|
|
200
|
+
offset: {
|
|
201
|
+
x: 0,
|
|
202
|
+
y: minOffsetY
|
|
203
|
+
}
|
|
204
|
+
});
|
|
205
|
+
e.stopPropagation();
|
|
206
|
+
return;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
// Check for scrollbar track clicks (Page Up/Down)
|
|
210
|
+
if (this._scrollbarBounds) {
|
|
211
|
+
const { left, top, width, height } = this._scrollbarBounds;
|
|
212
|
+
const { height: containerHeight, top: containerTop } = this.bounds;
|
|
213
|
+
// Check if click is in the scrollbar track area (right side)
|
|
214
|
+
if (x >= left - 20 && x <= left + width + 10) {
|
|
215
|
+
// Check for Page Up (click above thumb within track)
|
|
216
|
+
if (y < top && y >= containerTop) {
|
|
217
|
+
const { offset = { x: 0, y: 0 } } = this.state;
|
|
218
|
+
const newOffsetY = Math.min(0, offset.y + containerHeight);
|
|
219
|
+
this.setState({
|
|
220
|
+
offset: {
|
|
221
|
+
x: 0,
|
|
222
|
+
y: newOffsetY
|
|
223
|
+
}
|
|
224
|
+
});
|
|
225
|
+
e.stopPropagation();
|
|
226
|
+
return;
|
|
227
|
+
}
|
|
228
|
+
// Check for Page Down (click below thumb within track)
|
|
229
|
+
if (y > top + height && y <= containerTop + containerHeight) {
|
|
230
|
+
const { offset = { x: 0, y: 0 }, data = [] } = this.state;
|
|
231
|
+
const rowHeight = (this.heights[0] / this.heights_sum) * containerHeight;
|
|
232
|
+
const fullHeight = data.length * rowHeight;
|
|
233
|
+
const minOffsetY = Math.min(-fullHeight + containerHeight, 0);
|
|
234
|
+
const newOffsetY = Math.max(minOffsetY, offset.y - containerHeight);
|
|
235
|
+
this.setState({
|
|
236
|
+
offset: {
|
|
237
|
+
x: 0,
|
|
238
|
+
y: newOffsetY
|
|
239
|
+
}
|
|
240
|
+
});
|
|
241
|
+
e.stopPropagation();
|
|
242
|
+
return;
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
ondragmove(e) {
|
|
248
|
+
if (this._isDraggingScrollbar) {
|
|
249
|
+
const { height } = this.bounds;
|
|
250
|
+
const { data } = this.state;
|
|
251
|
+
const fullHeight = ((data && data.length) || 0) * (this.heights[0] / this.heights_sum) * height;
|
|
252
|
+
if (fullHeight <= height)
|
|
253
|
+
return;
|
|
254
|
+
const { y } = this.transcoordC2S(e.offsetX, e.offsetY);
|
|
255
|
+
const deltaY = y - this._dragStartY;
|
|
256
|
+
const scrollRatio = fullHeight / height;
|
|
257
|
+
// Calculate new offset based on scrollbar movement
|
|
258
|
+
// Moving scrollbar down means content moves up (negative offset)
|
|
259
|
+
let newOffsetY = this._startOffsetY - deltaY * scrollRatio;
|
|
260
|
+
const minOffsetY = Math.min(-fullHeight + height, 0);
|
|
261
|
+
newOffsetY = Math.max(Math.min(0, newOffsetY), minOffsetY);
|
|
262
|
+
this.setState({
|
|
263
|
+
offset: {
|
|
264
|
+
x: 0,
|
|
265
|
+
y: newOffsetY
|
|
266
|
+
}
|
|
267
|
+
});
|
|
268
|
+
e.stopPropagation();
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
ondragend(e) {
|
|
272
|
+
if (this._isDraggingScrollbar) {
|
|
273
|
+
this._isDraggingScrollbar = false;
|
|
274
|
+
e.stopPropagation();
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
onkeydown(e) {
|
|
278
|
+
const { height } = this.bounds;
|
|
279
|
+
const { offset = { x: 0, y: 0 }, data = [] } = this.state;
|
|
280
|
+
const rowHeight = (this.heights[0] / this.heights_sum) * height;
|
|
281
|
+
const fullHeight = data.length * rowHeight;
|
|
282
|
+
const minOffsetY = Math.min(-fullHeight + height, 0);
|
|
283
|
+
let newOffsetY = offset.y;
|
|
284
|
+
switch (e.code) {
|
|
285
|
+
case 'PageUp':
|
|
286
|
+
newOffsetY = Math.min(0, offset.y + height);
|
|
287
|
+
break;
|
|
288
|
+
case 'PageDown':
|
|
289
|
+
newOffsetY = Math.max(minOffsetY, offset.y - height);
|
|
290
|
+
break;
|
|
291
|
+
case 'Home':
|
|
292
|
+
newOffsetY = 0;
|
|
293
|
+
break;
|
|
294
|
+
case 'End':
|
|
295
|
+
newOffsetY = minOffsetY;
|
|
296
|
+
break;
|
|
297
|
+
default:
|
|
298
|
+
return;
|
|
299
|
+
}
|
|
300
|
+
if (newOffsetY !== offset.y) {
|
|
301
|
+
this.setState({
|
|
302
|
+
offset: {
|
|
303
|
+
x: 0,
|
|
304
|
+
y: newOffsetY
|
|
305
|
+
}
|
|
306
|
+
});
|
|
307
|
+
e.stopPropagation();
|
|
308
|
+
e.preventDefault();
|
|
309
|
+
}
|
|
52
310
|
}
|
|
53
311
|
created() {
|
|
54
312
|
this.set('rows', 2);
|
|
@@ -229,6 +487,9 @@ let DataList = class DataList extends Container {
|
|
|
229
487
|
let { columns } = this;
|
|
230
488
|
this.buildCells(columns, Number(before.columns));
|
|
231
489
|
}
|
|
490
|
+
if ('offset' in after || 'height' in after) {
|
|
491
|
+
this.updateViewport();
|
|
492
|
+
}
|
|
232
493
|
}
|
|
233
494
|
get eventMap() {
|
|
234
495
|
return {
|
|
@@ -240,7 +501,11 @@ let DataList = class DataList extends Container {
|
|
|
240
501
|
wheel: this._onwheel,
|
|
241
502
|
touchstart: this._ontouchstart,
|
|
242
503
|
touchmove: this._ontouchmove,
|
|
243
|
-
touchend: this._ontouchend
|
|
504
|
+
touchend: this._ontouchend,
|
|
505
|
+
dragstart: this.ondragstart,
|
|
506
|
+
dragmove: this.ondragmove,
|
|
507
|
+
dragend: this.ondragend,
|
|
508
|
+
click: this.onclick
|
|
244
509
|
}
|
|
245
510
|
}
|
|
246
511
|
};
|
|
@@ -277,35 +542,11 @@ let DataList = class DataList extends Container {
|
|
|
277
542
|
if (!(data instanceof Array)) {
|
|
278
543
|
data = [data];
|
|
279
544
|
}
|
|
280
|
-
/* 기존의 레코드를 모두 삭제 (템플릿 레코드만 남긴다.) */
|
|
281
|
-
this.remove(this.components.slice(this.columns), true /* silent - do not let root-container refreshMappings */);
|
|
282
545
|
/* template 레코드의 데이터를 클리어시킨다 */
|
|
283
546
|
var templates = this.getCellsByRow(0);
|
|
284
547
|
templates.forEach(field => {
|
|
285
548
|
field.data = '';
|
|
286
549
|
});
|
|
287
|
-
/* 데이터의 크기만큼 새로운 레코드를 만든다 */
|
|
288
|
-
if (data.length > 1) {
|
|
289
|
-
let newbies = [];
|
|
290
|
-
for (let i = 1; i < data.length; i++) {
|
|
291
|
-
newbies = newbies.concat(templates.map(field => {
|
|
292
|
-
const { refid, id, data, ...org } = field.model;
|
|
293
|
-
return Model.compile(org, this.app);
|
|
294
|
-
}));
|
|
295
|
-
}
|
|
296
|
-
this.add(newbies, true /* silent - do not let root-container refreshMappings */);
|
|
297
|
-
}
|
|
298
|
-
data.forEach((record, idx) => {
|
|
299
|
-
let data = {
|
|
300
|
-
_idx: idx,
|
|
301
|
-
...record
|
|
302
|
-
};
|
|
303
|
-
let row = this.getCellsByRow(idx);
|
|
304
|
-
row.forEach(field => {
|
|
305
|
-
// @ts-ignore TODO - remove this comment later
|
|
306
|
-
field.value = data;
|
|
307
|
-
});
|
|
308
|
-
});
|
|
309
550
|
/* 스크롤 위치를 조정 */
|
|
310
551
|
var { height } = this.bounds;
|
|
311
552
|
var fullHeight = data.length * (this.heights[0] / this.heights_sum) * height;
|
|
@@ -329,6 +570,84 @@ let DataList = class DataList extends Container {
|
|
|
329
570
|
}
|
|
330
571
|
});
|
|
331
572
|
}
|
|
573
|
+
this.updateViewport();
|
|
574
|
+
}
|
|
575
|
+
updateViewport() {
|
|
576
|
+
if (!this.app.isViewMode)
|
|
577
|
+
return;
|
|
578
|
+
const { height } = this.bounds;
|
|
579
|
+
const { offset = { x: 0, y: 0 }, data = [] } = this.state;
|
|
580
|
+
// Calculate row height based on the first row's relative height
|
|
581
|
+
const rowHeight = (this.heights[0] / this.heights_sum) * height;
|
|
582
|
+
const totalRows = data.length;
|
|
583
|
+
if (totalRows === 0) {
|
|
584
|
+
// Clear all data rows if no data
|
|
585
|
+
this.remove(this.components.slice(this.columns), true);
|
|
586
|
+
return;
|
|
587
|
+
}
|
|
588
|
+
// Calculate visible range
|
|
589
|
+
const scrollTop = -offset.y;
|
|
590
|
+
const startIndex = Math.floor(scrollTop / rowHeight);
|
|
591
|
+
// Calculate how many rows fit in the viewport
|
|
592
|
+
const visibleRows = Math.ceil(height / rowHeight);
|
|
593
|
+
const endIndex = Math.min(totalRows, startIndex + visibleRows);
|
|
594
|
+
// Add buffer
|
|
595
|
+
const buffer = 1;
|
|
596
|
+
const renderStart = Math.max(0, startIndex - buffer);
|
|
597
|
+
const renderEnd = Math.min(totalRows, endIndex + buffer);
|
|
598
|
+
this._renderStart = renderStart;
|
|
599
|
+
this._renderEnd = renderEnd;
|
|
600
|
+
// Hide templates (first row)
|
|
601
|
+
const templates = this.getCellsByRow(0);
|
|
602
|
+
templates.forEach(cell => {
|
|
603
|
+
cell.hidden = true;
|
|
604
|
+
});
|
|
605
|
+
// Sync components
|
|
606
|
+
const neededRows = renderEnd - renderStart;
|
|
607
|
+
const currentRows = Math.floor((this.components.length - this.columns) / this.columns);
|
|
608
|
+
// 1. Ensure we have enough components
|
|
609
|
+
if (currentRows < neededRows) {
|
|
610
|
+
const rowsToAdd = neededRows - currentRows;
|
|
611
|
+
let newbies = [];
|
|
612
|
+
for (let i = 0; i < rowsToAdd; i++) {
|
|
613
|
+
newbies = newbies.concat(templates.map(field => {
|
|
614
|
+
const { refid, id, data, ...org } = field.model;
|
|
615
|
+
return Model.compile({
|
|
616
|
+
...org,
|
|
617
|
+
hidden: false // Ensure new components are visible
|
|
618
|
+
}, this.app);
|
|
619
|
+
}));
|
|
620
|
+
}
|
|
621
|
+
this.add(newbies, true);
|
|
622
|
+
}
|
|
623
|
+
else if (currentRows > neededRows) {
|
|
624
|
+
// Optional: Remove excess components to save memory, or keep them as pool
|
|
625
|
+
// For now, let's remove them to keep logic simple and avoid rendering invisible items
|
|
626
|
+
const rowsToRemove = currentRows - neededRows;
|
|
627
|
+
const removeStart = this.columns + neededRows * this.columns;
|
|
628
|
+
const componentsToRemove = this.components.slice(removeStart);
|
|
629
|
+
this.remove(componentsToRemove, true);
|
|
630
|
+
}
|
|
631
|
+
// 2. Update data for each visible row
|
|
632
|
+
for (let i = 0; i < neededRows; i++) {
|
|
633
|
+
const dataIndex = renderStart + i;
|
|
634
|
+
const record = data[dataIndex];
|
|
635
|
+
const rowData = {
|
|
636
|
+
_idx: dataIndex,
|
|
637
|
+
...record
|
|
638
|
+
};
|
|
639
|
+
const rowStartComponentIndex = this.columns + i * this.columns;
|
|
640
|
+
for (let j = 0; j < this.columns; j++) {
|
|
641
|
+
const cell = this.components[rowStartComponentIndex + j];
|
|
642
|
+
// @ts-ignore
|
|
643
|
+
cell.value = rowData;
|
|
644
|
+
// Store the data index on the component for layout
|
|
645
|
+
// @ts-ignore
|
|
646
|
+
cell._rowIndex = dataIndex;
|
|
647
|
+
}
|
|
648
|
+
}
|
|
649
|
+
// Trigger layout update
|
|
650
|
+
this.invalidate();
|
|
332
651
|
}
|
|
333
652
|
setCellsStyle(cells, style, where, clearBefore = true) {
|
|
334
653
|
var components = this.components;
|