@operato/data-grist 8.2.19 → 8.2.22
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 +25 -0
- package/dist/src/data-grid/data-grid-body-style.js +1 -1
- package/dist/src/data-grid/data-grid-body-style.js.map +1 -1
- package/dist/src/data-grid/data-grid-body.d.ts +15 -1
- package/dist/src/data-grid/data-grid-body.js +274 -49
- package/dist/src/data-grid/data-grid-body.js.map +1 -1
- package/dist/src/data-grid/event-handlers/data-grid-body-keydown-handler.js +36 -5
- package/dist/src/data-grid/event-handlers/data-grid-body-keydown-handler.js.map +1 -1
- package/dist/src/editors/ox-grist-editor.js +3 -0
- package/dist/src/editors/ox-grist-editor.js.map +1 -1
- package/dist/src/gutters/gutter-row-selector.js +4 -0
- package/dist/src/gutters/gutter-row-selector.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +2 -2
- package/dist/src/editors/ox-grist-editor-json5.d.ts +0 -8
- package/dist/src/editors/ox-grist-editor-json5.js +0 -66
- package/dist/src/editors/ox-grist-editor-json5.js.map +0 -1
- package/dist/src/editors/ox-grist-editor-secret.d.ts +0 -5
- package/dist/src/editors/ox-grist-editor-secret.js +0 -23
- package/dist/src/editors/ox-grist-editor-secret.js.map +0 -1
- package/dist/src/editors/ox-grist-editor-timezone.d.ts +0 -6
- package/dist/src/editors/ox-grist-editor-timezone.js +0 -41
- package/dist/src/editors/ox-grist-editor-timezone.js.map +0 -1
- package/dist/src/editors/ox-popup-code-input.d.ts +0 -14
- package/dist/src/editors/ox-popup-code-input.js +0 -78
- package/dist/src/editors/ox-popup-code-input.js.map +0 -1
- package/dist/src/renderers/ox-grist-renderer-secret.d.ts +0 -2
- package/dist/src/renderers/ox-grist-renderer-secret.js +0 -77
- package/dist/src/renderers/ox-grist-renderer-secret.js.map +0 -1
- package/dist/stories/append-position.stories.d.ts +0 -8
- package/dist/stories/append-position.stories.js +0 -183
- package/dist/stories/append-position.stories.js.map +0 -1
- package/dist/stories/secret.stories.d.ts +0 -37
- package/dist/stories/secret.stories.js +0 -219
- package/dist/stories/secret.stories.js.map +0 -1
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,31 @@
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
|
5
5
|
|
|
6
|
+
### [8.2.22](https://github.com/hatiolab/operato/compare/v8.2.21...v8.2.22) (2026-04-10)
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
### :bug: Bug Fix
|
|
10
|
+
|
|
11
|
+
* 그리드 공백 2개일때 1개로 인식 현상 수정 ([8a4c2cd](https://github.com/hatiolab/operato/commit/8a4c2cd18113b32be2826217b1ff247b4da9c9b4))
|
|
12
|
+
* 그리드 복붙 renderer 케이스 추가 ([61dfd01](https://github.com/hatiolab/operato/commit/61dfd013abb7ac11b68ff8936aa1d2cff614f438))
|
|
13
|
+
* 그리드 복붙시 태그 제거 ([4a8fdc1](https://github.com/hatiolab/operato/commit/4a8fdc1c4198a7bb61afc77a8cafde3adfba453d))
|
|
14
|
+
* 그리드 복붙시 태그 제거 ([a2c572c](https://github.com/hatiolab/operato/commit/a2c572c6f056744f1599bc61967364e273b77236))
|
|
15
|
+
* 그리드 셀 ime-buffer로 한글 입력시 composing 유지 ([7fbcc88](https://github.com/hatiolab/operato/commit/7fbcc880ca80ad65860f5ef36fc54348999e85c3))
|
|
16
|
+
* 그리드 셀 ime-buffer로 한글 입력시 composing 유지 ([690464f](https://github.com/hatiolab/operato/commit/690464f0ae8dddbf94612c573a6a6be8dc51fcea))
|
|
17
|
+
* 복사후 그리드 포커스 빠짐 현상 수정 ([e020535](https://github.com/hatiolab/operato/commit/e020535cf370f659549f81521b06a0c9ffa8523e))
|
|
18
|
+
* 체리픽 보정 ([5bbf3da](https://github.com/hatiolab/operato/commit/5bbf3da644f222212c70e451b84d03dd4e98e73f))
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
### [8.2.20](https://github.com/hatiolab/operato/compare/v8.2.19...v8.2.20) (2026-02-09)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
### :bug: Bug Fix
|
|
26
|
+
|
|
27
|
+
* 그리드 multiple 일때 헤더 체크박스 숨기기 ([c4ce8a4](https://github.com/hatiolab/operato/commit/c4ce8a43d4ff09468cc97a489b351d0262eb0bdb))
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
|
|
6
31
|
### [8.2.19](https://github.com/hatiolab/operato/compare/v8.2.18...v8.2.19) (2026-01-09)
|
|
7
32
|
|
|
8
33
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"data-grid-body-style.js","sourceRoot":"","sources":["../../../src/data-grid/data-grid-body-style.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAAE,MAAM,KAAK,CAAA;AAEzB,MAAM,CAAC,MAAM,iBAAiB,GAAG,GAAG,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAgGnC,CAAA","sourcesContent":["import { css } from 'lit'\n\nexport const dataGridBodyStyle = css`\n :host {\n display: grid;\n grid-template-columns: var(--grid-template-columns);\n grid-auto-rows: var(--grid-record-height, min-content);\n\n overflow: auto;\n outline: none;\n color: var(--grid-record-color);\n position: relative;\n border-bottom: var(--grid-body-bottom-border);\n }\n\n ox-grid-field[odd] {\n background-color: var(--grid-record-odd-background-color);\n }\n\n ox-grid-field[disabled] {\n background-color: var(--grid-record-disabled-background-color, var(--grid-record-background-color));\n color: var(--grid-record-disabled-color, var(--grid-record-color));\n opacity: var(--grid-record-disabled-opacity, 1);\n }\n ox-grid-field[odd][disabled] {\n background-color: var(\n --grid-odd-record-disabled-background-color,\n var(--grid-record-disabled-background-color, var(--grid-record-odd-background-color))\n );\n color: var(--grid-odd-record-disabled-color, var(--grid-record-disabled-color, var(--grid-record-color)));\n opacity: var(--grid-odd-record-disabled-opacity, var(--grid-record-disabled-opacity, 1));\n }\n\n ox-grid-field[selected-row] {\n background-color: var(--grid-record-selected-background-color);\n color: var(--grid-record-selected-color);\n }\n\n ox-grid-field[focused-row] {\n box-shadow: var(--grid-record-focused-box-shadow);\n font-weight: bold;\n color: var(--grid-record-focused-color);\n background-image: var(--focused-background-image);\n background-blend-mode: darken;\n }\n\n ox-grid-field[focused] {\n border: var(--grid-record-focused-cell-border);\n }\n\n ox-grid-field[emphasized-row],\n ox-grid-field[emphasized-row][focused] {\n background-color: var(--grid-record-emphasized-background-color) !important;\n color: var(--grid-record-emphasized-color) !important;\n }\n\n [editing] {\n background-color: var(--grid-record-editing-background-color);\n }\n\n @media print {\n :host {\n grid-template-columns: var(--grid-template-print-columns);\n }\n ox-grid-field[focused] {\n border: none;\n }\n\n ox-grid-field[selected-row] {\n background-color: transparent;\n }\n\n ox-grid-field[emphasized-row] {\n background-color: transparent;\n color: initial;\n }\n\n ox-grid-field[focused-row] {\n background-color: transparent;\n color: initial;\n }\n\n ox-grid-field[editing] {\n background-color: transparent;\n }\n }\n\n @media (prefers-color-scheme: dark) {\n ox-grid-field[focused-row] {\n background-blend-mode: lighten;\n }\n }\n\n @media (prefers-color-scheme: light) {\n ox-grid-field[focused-row] {\n background-blend-mode: darken;\n }\n }\n`\n"]}
|
|
1
|
+
{"version":3,"file":"data-grid-body-style.js","sourceRoot":"","sources":["../../../src/data-grid/data-grid-body-style.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAAE,MAAM,KAAK,CAAA;AAEzB,MAAM,CAAC,MAAM,iBAAiB,GAAG,GAAG,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAgGnC,CAAA","sourcesContent":["import { css } from 'lit'\n\nexport const dataGridBodyStyle = css`\n :host {\n display: grid;\n grid-template-columns: var(--grid-template-columns);\n grid-auto-rows: var(--grid-record-height, min-content);\n\n overflow: auto;\n outline: none;\n color: var(--grid-record-color);\n position: relative;\n border-bottom: var(--grid-body-bottom-border);\n }\n\n ox-grid-field[odd] {\n background-color: var(--grid-record-odd-background-color);\n }\n\n ox-grid-field[disabled] {\n background-color: var(--grid-record-disabled-background-color, var(--grid-record-background-color));\n color: var(--grid-record-disabled-color, var(--grid-record-color));\n opacity: var(--grid-record-disabled-opacity, 1);\n }\n ox-grid-field[odd][disabled] {\n background-color: var(\n --grid-odd-record-disabled-background-color,\n var(--grid-record-disabled-background-color, var(--grid-record-odd-background-color))\n );\n color: var(--grid-odd-record-disabled-color, var(--grid-record-disabled-color, var(--grid-record-color)));\n opacity: var(--grid-odd-record-disabled-opacity, var(--grid-record-disabled-opacity, 1));\n }\n\n ox-grid-field[selected-row] {\n background-color: var(--grid-record-selected-background-color);\n color: var(--grid-record-selected-color);\n }\n\n ox-grid-field[focused-row] {\n box-shadow: var(--grid-record-focused-box-shadow);\n font-weight: bold;\n color: var(--grid-record-focused-color);\n background-image: var(--focused-background-image);\n background-blend-mode: darken;\n }\n\n ox-grid-field[focused] {\n border: var(--grid-record-focused-cell-border);\n }\n\n ox-grid-field[emphasized-row],\n ox-grid-field[emphasized-row][focused] {\n background-color: var(--grid-record-emphasized-background-color) !important;\n color: var(--grid-record-emphasized-color) !important;\n }\n\n ox-grid-field[editing] {\n background-color: var(--grid-record-editing-background-color);\n }\n\n @media print {\n :host {\n grid-template-columns: var(--grid-template-print-columns);\n }\n ox-grid-field[focused] {\n border: none;\n }\n\n ox-grid-field[selected-row] {\n background-color: transparent;\n }\n\n ox-grid-field[emphasized-row] {\n background-color: transparent;\n color: initial;\n }\n\n ox-grid-field[focused-row] {\n background-color: transparent;\n color: initial;\n }\n\n ox-grid-field[editing] {\n background-color: transparent;\n }\n }\n\n @media (prefers-color-scheme: dark) {\n ox-grid-field[focused-row] {\n background-blend-mode: lighten;\n }\n }\n\n @media (prefers-color-scheme: light) {\n ox-grid-field[focused-row] {\n background-blend-mode: darken;\n }\n }\n`\n"]}
|
|
@@ -32,6 +32,9 @@ export declare class DataGridBody extends LitElement {
|
|
|
32
32
|
private _recordViewRow?;
|
|
33
33
|
private _draggable?;
|
|
34
34
|
private _gridElement?;
|
|
35
|
+
private _imeBuffer?;
|
|
36
|
+
_imeComposing: boolean;
|
|
37
|
+
_imeEditing: boolean;
|
|
35
38
|
resetEdit(): void;
|
|
36
39
|
handleOnScroll(e: WheelEvent): void;
|
|
37
40
|
render(): import("lit-html").TemplateResult<1>;
|
|
@@ -48,9 +51,20 @@ export declare class DataGridBody extends LitElement {
|
|
|
48
51
|
row: number;
|
|
49
52
|
record: GristRecord;
|
|
50
53
|
}): void;
|
|
51
|
-
getSelectedBlockValues():
|
|
54
|
+
getSelectedBlockValues(): {
|
|
55
|
+
html: string;
|
|
56
|
+
text: string;
|
|
57
|
+
} | undefined;
|
|
52
58
|
copy(): Promise<void>;
|
|
53
59
|
paste(): Promise<void>;
|
|
54
60
|
setSelectBlock(start?: DataGridField, end?: DataGridField): void;
|
|
55
61
|
buildAccumulatorRecord(): GristRecord;
|
|
62
|
+
/** IME buffer를 포커스된 셀 위에 위치시킨다 */
|
|
63
|
+
private _positionIMEBuffer;
|
|
64
|
+
/** IME buffer 값을 field-change 이벤트로 커밋한다 */
|
|
65
|
+
_commitIMEBuffer(row: number, column: number): void;
|
|
66
|
+
/** IME buffer 취소 (값 버리기) */
|
|
67
|
+
_discardIMEBuffer(): void;
|
|
68
|
+
/** IME buffer 초기화 */
|
|
69
|
+
private _resetIMEBuffer;
|
|
56
70
|
}
|
|
@@ -55,6 +55,8 @@ let DataGridBody = class DataGridBody extends LitElement {
|
|
|
55
55
|
this.from = -1;
|
|
56
56
|
this.to = -1;
|
|
57
57
|
this.fixedLefts = [];
|
|
58
|
+
this._imeComposing = false;
|
|
59
|
+
this._imeEditing = false;
|
|
58
60
|
// 검색시 스크롤 맨 위로
|
|
59
61
|
this._onFetchParamsChange = () => {
|
|
60
62
|
this.scrollTop = 0;
|
|
@@ -156,6 +158,7 @@ let DataGridBody = class DataGridBody extends LitElement {
|
|
|
156
158
|
`
|
|
157
159
|
: nothing}
|
|
158
160
|
${start && end && start !== end ? html ` <div select-block></div> ` : html ``}
|
|
161
|
+
<input id="ime-buffer" type="text" />
|
|
159
162
|
<slot></slot>
|
|
160
163
|
`;
|
|
161
164
|
}
|
|
@@ -164,12 +167,43 @@ let DataGridBody = class DataGridBody extends LitElement {
|
|
|
164
167
|
// this.addEventListener('scroll', this.handleOnScroll.bind(this))
|
|
165
168
|
/* focus() 를 받을 수 있도록 함. */
|
|
166
169
|
this.setAttribute('tabindex', '-1');
|
|
170
|
+
/* 한글 IME composition을 위한 숨겨진 input 버퍼 설정 */
|
|
171
|
+
const imeBuffer = this.renderRoot.querySelector('#ime-buffer');
|
|
172
|
+
this._imeBuffer = imeBuffer;
|
|
173
|
+
imeBuffer.addEventListener('compositionstart', () => {
|
|
174
|
+
this._imeComposing = true;
|
|
175
|
+
this._imeEditing = true;
|
|
176
|
+
imeBuffer.classList.add('active');
|
|
177
|
+
this._positionIMEBuffer();
|
|
178
|
+
});
|
|
179
|
+
imeBuffer.addEventListener('compositionend', () => {
|
|
180
|
+
this._imeComposing = false;
|
|
181
|
+
});
|
|
182
|
+
imeBuffer.addEventListener('input', () => {
|
|
183
|
+
if (!this._imeComposing && !this._imeEditing && imeBuffer.value) {
|
|
184
|
+
/* 영어/숫자: 실제 값이 입력된 경우에만 활성화 */
|
|
185
|
+
this._imeEditing = true;
|
|
186
|
+
imeBuffer.classList.add('active');
|
|
187
|
+
this._positionIMEBuffer();
|
|
188
|
+
}
|
|
189
|
+
});
|
|
167
190
|
/*
|
|
168
191
|
* focusout 으로 property를 변경시키는 경우, focusout에 의해 update가 발생하는 경우에는,
|
|
169
192
|
* 그리드 내부의 컴포넌트가 갱신되는 현상을 초래하게 된다.
|
|
170
193
|
* 따라서, focusout 핸들러에서 update를 유발하는 코드는 강력하게 금지시킨다.
|
|
171
194
|
*/
|
|
172
195
|
this.addEventListener('focusout', e => {
|
|
196
|
+
var _a;
|
|
197
|
+
/* IME buffer로 포커스가 이동하는 경우 keydown listener 유지 */
|
|
198
|
+
const related = e.relatedTarget;
|
|
199
|
+
if (related && ((_a = this.shadowRoot) === null || _a === void 0 ? void 0 : _a.contains(related))) {
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
202
|
+
/* 그리드 외부로 포커스 이동 시 IME buffer 커밋 */
|
|
203
|
+
if (this._imeEditing) {
|
|
204
|
+
const { row = 0, column = 0 } = this.focused || {};
|
|
205
|
+
this._commitIMEBuffer(row, column);
|
|
206
|
+
}
|
|
173
207
|
if (this._focusedListener) {
|
|
174
208
|
this.removeEventListener('keydown', this._focusedListener);
|
|
175
209
|
delete this._focusedListener;
|
|
@@ -203,12 +237,18 @@ let DataGridBody = class DataGridBody extends LitElement {
|
|
|
203
237
|
}
|
|
204
238
|
}));
|
|
205
239
|
});
|
|
206
|
-
this.renderRoot.addEventListener('
|
|
207
|
-
|
|
240
|
+
this.renderRoot.addEventListener('pointerdown', (e) => {
|
|
241
|
+
/* 클릭 시 IME buffer가 활성 상태면 먼저 커밋 */
|
|
242
|
+
if (this._imeEditing) {
|
|
243
|
+
const { row = 0, column = 0 } = this.focused || {};
|
|
244
|
+
this._commitIMEBuffer(row, column);
|
|
245
|
+
}
|
|
208
246
|
this.setSelectBlock();
|
|
209
|
-
if (e.buttons !== 1) {
|
|
247
|
+
if ('buttons' in e && e.buttons !== 1) {
|
|
210
248
|
return;
|
|
211
249
|
}
|
|
250
|
+
e.preventDefault();
|
|
251
|
+
e.stopPropagation();
|
|
212
252
|
this._draggable = true;
|
|
213
253
|
var target = e.target.closest('ox-grid-field');
|
|
214
254
|
var { rowIndex, columnIndex } = target || {};
|
|
@@ -224,11 +264,13 @@ let DataGridBody = class DataGridBody extends LitElement {
|
|
|
224
264
|
this.startEditTarget(rowIndex, columnIndex);
|
|
225
265
|
}
|
|
226
266
|
});
|
|
227
|
-
this.renderRoot.addEventListener('
|
|
267
|
+
this.renderRoot.addEventListener('pointermove', (event) => {
|
|
228
268
|
const e = event;
|
|
229
|
-
if (e.buttons !== 1 || !this._draggable) {
|
|
269
|
+
if (('buttons' in e && e.buttons !== 1) || !this._draggable) {
|
|
230
270
|
return;
|
|
231
271
|
}
|
|
272
|
+
e.preventDefault();
|
|
273
|
+
e.stopPropagation();
|
|
232
274
|
const field = e.target;
|
|
233
275
|
if (!this._selectBlock) {
|
|
234
276
|
this.setSelectBlock(this.focusedField || field, this.focusedField || field);
|
|
@@ -240,7 +282,9 @@ let DataGridBody = class DataGridBody extends LitElement {
|
|
|
240
282
|
this.setSelectBlock(start, end);
|
|
241
283
|
}
|
|
242
284
|
});
|
|
243
|
-
this.renderRoot.addEventListener('
|
|
285
|
+
this.renderRoot.addEventListener('pointerup', (event) => {
|
|
286
|
+
event.preventDefault();
|
|
287
|
+
event.stopPropagation();
|
|
244
288
|
this._draggable = false;
|
|
245
289
|
});
|
|
246
290
|
this.renderRoot.addEventListener('click', dataGridBodyClickHandler.bind(this));
|
|
@@ -292,6 +336,8 @@ let DataGridBody = class DataGridBody extends LitElement {
|
|
|
292
336
|
if (this.editTarget && this.editTarget.row == row && this.editTarget.column == column) {
|
|
293
337
|
return;
|
|
294
338
|
}
|
|
339
|
+
/* 에디터 편집 전환 시 IME buffer 정리 */
|
|
340
|
+
this._resetIMEBuffer();
|
|
295
341
|
this.editTarget = {
|
|
296
342
|
row,
|
|
297
343
|
column,
|
|
@@ -327,6 +373,11 @@ let DataGridBody = class DataGridBody extends LitElement {
|
|
|
327
373
|
if (left !== undefined) {
|
|
328
374
|
this.scrollLeft = left;
|
|
329
375
|
}
|
|
376
|
+
/* IME buffer 위치 업데이트 */
|
|
377
|
+
this._positionIMEBuffer();
|
|
378
|
+
if (!this.editTarget && this._imeBuffer) {
|
|
379
|
+
this._imeBuffer.focus();
|
|
380
|
+
}
|
|
330
381
|
}
|
|
331
382
|
// 페이징 바뀌면 스크롤 맨 위로
|
|
332
383
|
if (changes.has('data')) {
|
|
@@ -342,7 +393,18 @@ let DataGridBody = class DataGridBody extends LitElement {
|
|
|
342
393
|
}
|
|
343
394
|
}
|
|
344
395
|
focus() {
|
|
345
|
-
|
|
396
|
+
if (this.editTarget) {
|
|
397
|
+
/* 에디터 편집 모드에서는 에디터가 자체적으로 포커스를 관리하므로 간섭하지 않는다 */
|
|
398
|
+
return;
|
|
399
|
+
}
|
|
400
|
+
if (this._imeBuffer) {
|
|
401
|
+
/* 비편집 모드에서는 IME buffer로 포커스 리다이렉트 */
|
|
402
|
+
this._positionIMEBuffer();
|
|
403
|
+
this._imeBuffer.focus();
|
|
404
|
+
}
|
|
405
|
+
else {
|
|
406
|
+
super.focus();
|
|
407
|
+
}
|
|
346
408
|
if (this.focused === ZERO_FOCUS) {
|
|
347
409
|
let { records } = this.data;
|
|
348
410
|
let row = records.findIndex(record => record['__selected__']);
|
|
@@ -365,6 +427,7 @@ let DataGridBody = class DataGridBody extends LitElement {
|
|
|
365
427
|
delete this._recordViewRow;
|
|
366
428
|
});
|
|
367
429
|
}
|
|
430
|
+
/* 선택된 셀 블록의 값을 HTML(table)과 plain text(TSV) 두 가지 포맷으로 반환 */
|
|
368
431
|
getSelectedBlockValues() {
|
|
369
432
|
var { start, end } = this._selectBlock || {};
|
|
370
433
|
if (!(start && end)) {
|
|
@@ -378,34 +441,81 @@ let DataGridBody = class DataGridBody extends LitElement {
|
|
|
378
441
|
const endColumnIndex = start.columnIndex < end.columnIndex ? end.columnIndex : start.columnIndex;
|
|
379
442
|
const columnArray = new Array(endColumnIndex - startColumnIndex + 1).fill(startColumnIndex);
|
|
380
443
|
const columns = this.columns.filter(column => !column.hidden);
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
444
|
+
/*
|
|
445
|
+
* 클립보드에 두 가지 포맷(text/html, text/plain)을 동시에 저장한다.
|
|
446
|
+
* 붙여넣기하는 앱이 자신이 지원하는 포맷을 골라 읽는다.
|
|
447
|
+
* 생성되는 HTML:
|
|
448
|
+
* <td type=object data-value="{"id":...}">탈수 절임배추</td>
|
|
449
|
+
* ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^
|
|
450
|
+
* 그리드만 읽는 숨겨진 JSON Excel/메모장이 보는 텍스트
|
|
451
|
+
*/
|
|
452
|
+
const rows = new Array(endRowIndex - startRowIndex + 1).fill(startRowIndex).map((start, index) => {
|
|
453
|
+
const rowIndex = start + index;
|
|
454
|
+
const record = this.data.records[rowIndex];
|
|
455
|
+
const cells = columnArray.map((start, index) => {
|
|
456
|
+
var _a, _b;
|
|
457
|
+
const columnIndex = start + index;
|
|
458
|
+
const column = columns[columnIndex];
|
|
459
|
+
const value = record === null || record === void 0 ? void 0 : record[column.name];
|
|
460
|
+
const type = typeof value;
|
|
461
|
+
/* htmlText: object는 JSON 문자열로 보존 → data-value 속성에 저장되어 그리드 붙여넣기 시 복원됨 */
|
|
462
|
+
const htmlText = value === undefined || value === null ? '' : type == 'object' ? JSON.stringify(value) : value;
|
|
463
|
+
/* plainText: ① null/undefined → renderer ② object → name → renderer ③ 기타 → 값 그대로 */
|
|
464
|
+
let plainText = '';
|
|
465
|
+
if (value === undefined || value === null) {
|
|
466
|
+
/* 값이 없지만 renderer가 record의 다른 경로로 텍스트를 표시할 수 있음 */
|
|
467
|
+
if ((_a = column.record) === null || _a === void 0 ? void 0 : _a.renderer) {
|
|
468
|
+
try {
|
|
469
|
+
const r = column.record.renderer(value, column, record, rowIndex, null);
|
|
470
|
+
if (typeof r === 'string')
|
|
471
|
+
plainText = r;
|
|
472
|
+
}
|
|
473
|
+
catch (e) { }
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
else if (type == 'object') {
|
|
477
|
+
/* object: name 필드 우선, 없으면 renderer */
|
|
478
|
+
plainText = (value === null || value === void 0 ? void 0 : value.name) || '';
|
|
479
|
+
if (!plainText && ((_b = column.record) === null || _b === void 0 ? void 0 : _b.renderer)) {
|
|
480
|
+
try {
|
|
481
|
+
const r = column.record.renderer(value, column, record, rowIndex, null);
|
|
482
|
+
if (typeof r === 'string')
|
|
483
|
+
plainText = r;
|
|
484
|
+
}
|
|
485
|
+
catch (e) { }
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
else {
|
|
489
|
+
plainText = value;
|
|
490
|
+
}
|
|
491
|
+
return { type, htmlText, plainText };
|
|
492
|
+
});
|
|
493
|
+
return cells;
|
|
494
|
+
});
|
|
495
|
+
const html = '<table>' +
|
|
496
|
+
rows
|
|
497
|
+
.map(cells => '<tr>' +
|
|
498
|
+
cells
|
|
499
|
+
.map(c => c.type == 'object' && c.htmlText
|
|
500
|
+
? `<td type=${c.type} data-value="${String(c.htmlText).replace(/"/g, '"')}">${c.plainText}</td>`
|
|
501
|
+
: `<td type=${c.type}>${c.htmlText}</td>`)
|
|
502
|
+
.join('') +
|
|
503
|
+
'</tr>')
|
|
399
504
|
.join('') +
|
|
400
|
-
'</table>'
|
|
505
|
+
'</table>';
|
|
506
|
+
/* 태그 없는 TSV 포맷 (object는 name만) */
|
|
507
|
+
const text = rows.map(cells => cells.map(c => String(c.plainText)).join('\t')).join('\n');
|
|
508
|
+
return { html, text };
|
|
401
509
|
}
|
|
402
510
|
}
|
|
403
511
|
async copy() {
|
|
404
512
|
const copied = this.getSelectedBlockValues();
|
|
513
|
+
if (!copied)
|
|
514
|
+
return;
|
|
405
515
|
await navigator.clipboard.write([
|
|
406
516
|
new ClipboardItem({
|
|
407
|
-
'text/html': new Blob([copied], { type: 'text/html' }),
|
|
408
|
-
'text/plain': new Blob([copied], { type: 'text/plain' })
|
|
517
|
+
'text/html': new Blob([copied.html], { type: 'text/html' }),
|
|
518
|
+
'text/plain': new Blob([copied.text], { type: 'text/plain' })
|
|
409
519
|
})
|
|
410
520
|
]);
|
|
411
521
|
const selectBlock = this.selectBlock || this.focusedField;
|
|
@@ -482,8 +592,8 @@ let DataGridBody = class DataGridBody extends LitElement {
|
|
|
482
592
|
const cells = record.querySelectorAll('td');
|
|
483
593
|
cells.forEach((item, columnIndex) => {
|
|
484
594
|
const targetColumn = columns[column + columnIndex];
|
|
485
|
-
|
|
486
|
-
|
|
595
|
+
/* data-value 속성이 있으면 우선 사용 (그리드에서 복사한 object 데이터 복원용) */
|
|
596
|
+
let value = (item.dataset.value || item.textContent);
|
|
487
597
|
let type = targetColumn.type || item.getAttribute('type') || 'string';
|
|
488
598
|
type = type.includes('object') ? 'object' : type; // 오브젝트 타입 예외처리
|
|
489
599
|
let { editable } = targetColumn.record;
|
|
@@ -551,31 +661,48 @@ let DataGridBody = class DataGridBody extends LitElement {
|
|
|
551
661
|
});
|
|
552
662
|
return;
|
|
553
663
|
}
|
|
554
|
-
else if (
|
|
555
|
-
|
|
556
|
-
const
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
664
|
+
else if (type === 'text/plain') {
|
|
665
|
+
/* TSV 포맷 파싱: 줄바꿈으로 행 분리, 탭으로 열 분리 (엑셀 등 외부 소스 붙여넣기 지원) */
|
|
666
|
+
const tsvRows = content.split('\n');
|
|
667
|
+
tsvRows.forEach((line, rowOffset) => {
|
|
668
|
+
const targetRowIndex = row + rowOffset;
|
|
669
|
+
var targetRecord = records[targetRowIndex] || { __dirty__: '+' };
|
|
670
|
+
if (targetRowIndex >= records.length) {
|
|
671
|
+
records.push(targetRecord);
|
|
672
|
+
}
|
|
673
|
+
const tsvCells = line.split('\t');
|
|
674
|
+
tsvCells.forEach((cellValue, colOffset) => {
|
|
675
|
+
const targetColumn = columns[column + colOffset];
|
|
676
|
+
if (!targetColumn || targetColumn.gutterName)
|
|
677
|
+
return;
|
|
678
|
+
let { editable } = targetColumn.record;
|
|
679
|
+
if (typeof editable === 'function') {
|
|
680
|
+
editable = editable.call(this, cellValue, targetColumn, targetRecord, targetRowIndex, this);
|
|
571
681
|
}
|
|
572
|
-
|
|
573
|
-
|
|
682
|
+
if (editable) {
|
|
683
|
+
this.dispatchEvent(new CustomEvent('field-change', {
|
|
684
|
+
bubbles: true,
|
|
685
|
+
composed: true,
|
|
686
|
+
detail: {
|
|
687
|
+
before: targetRecord[targetColumn.name],
|
|
688
|
+
after: cellValue || undefined,
|
|
689
|
+
column: targetColumn,
|
|
690
|
+
record: targetRecord,
|
|
691
|
+
row: targetRowIndex
|
|
692
|
+
}
|
|
693
|
+
}));
|
|
694
|
+
}
|
|
695
|
+
});
|
|
696
|
+
});
|
|
574
697
|
}
|
|
575
698
|
}
|
|
576
699
|
catch (e) {
|
|
577
700
|
console.log('e : ', e);
|
|
578
701
|
}
|
|
702
|
+
finally {
|
|
703
|
+
/* paste 완료 후 포커스를 그리드로 복원 */
|
|
704
|
+
this.focus();
|
|
705
|
+
}
|
|
579
706
|
}
|
|
580
707
|
setSelectBlock(start, end) {
|
|
581
708
|
var _a;
|
|
@@ -614,12 +741,84 @@ let DataGridBody = class DataGridBody extends LitElement {
|
|
|
614
741
|
return record;
|
|
615
742
|
}, {});
|
|
616
743
|
}
|
|
744
|
+
/** IME buffer를 포커스된 셀 위에 위치시킨다 */
|
|
745
|
+
_positionIMEBuffer() {
|
|
746
|
+
var _a;
|
|
747
|
+
const focusedEl = (_a = this.renderRoot) === null || _a === void 0 ? void 0 : _a.querySelector('[focused]');
|
|
748
|
+
if (focusedEl && this._imeBuffer) {
|
|
749
|
+
this._imeBuffer.style.left = focusedEl.offsetLeft + 'px';
|
|
750
|
+
this._imeBuffer.style.top = focusedEl.offsetTop + 'px';
|
|
751
|
+
this._imeBuffer.style.width = focusedEl.offsetWidth + 'px';
|
|
752
|
+
this._imeBuffer.style.height = focusedEl.offsetHeight + 'px';
|
|
753
|
+
}
|
|
754
|
+
}
|
|
755
|
+
/** IME buffer 값을 field-change 이벤트로 커밋한다 */
|
|
756
|
+
_commitIMEBuffer(row, column) {
|
|
757
|
+
var _a;
|
|
758
|
+
const text = (_a = this._imeBuffer) === null || _a === void 0 ? void 0 : _a.value;
|
|
759
|
+
this._resetIMEBuffer();
|
|
760
|
+
if (!text)
|
|
761
|
+
return;
|
|
762
|
+
const columns = this.columns.filter(c => !c.hidden);
|
|
763
|
+
const col = columns[column];
|
|
764
|
+
if (!col || col.gutterName)
|
|
765
|
+
return;
|
|
766
|
+
const record = this.data.records[row] || { __dirty__: '+' };
|
|
767
|
+
let { editable } = col.record;
|
|
768
|
+
if (typeof editable === 'function') {
|
|
769
|
+
editable = editable.call(this, record[col.name], col, record, row, this);
|
|
770
|
+
}
|
|
771
|
+
if (!editable)
|
|
772
|
+
return;
|
|
773
|
+
/* 타입에 따른 값 변환 */
|
|
774
|
+
let value = text;
|
|
775
|
+
switch (col.type) {
|
|
776
|
+
case 'number':
|
|
777
|
+
case 'float':
|
|
778
|
+
case 'integer':
|
|
779
|
+
case 'progress':
|
|
780
|
+
value = parseToNumberOrNull(text);
|
|
781
|
+
if (value === null)
|
|
782
|
+
return;
|
|
783
|
+
break;
|
|
784
|
+
case 'boolean':
|
|
785
|
+
case 'checkbox':
|
|
786
|
+
return; /* boolean은 키보드 텍스트 입력으로 편집 불가 */
|
|
787
|
+
}
|
|
788
|
+
this.dispatchEvent(new CustomEvent('field-change', {
|
|
789
|
+
bubbles: true,
|
|
790
|
+
composed: true,
|
|
791
|
+
detail: {
|
|
792
|
+
before: record[col.name],
|
|
793
|
+
after: value,
|
|
794
|
+
column: col,
|
|
795
|
+
record,
|
|
796
|
+
row
|
|
797
|
+
}
|
|
798
|
+
}));
|
|
799
|
+
}
|
|
800
|
+
/** IME buffer 취소 (값 버리기) */
|
|
801
|
+
_discardIMEBuffer() {
|
|
802
|
+
this._resetIMEBuffer();
|
|
803
|
+
}
|
|
804
|
+
/** IME buffer 초기화 */
|
|
805
|
+
_resetIMEBuffer() {
|
|
806
|
+
if (this._imeBuffer) {
|
|
807
|
+
this._imeBuffer.value = '';
|
|
808
|
+
this._imeBuffer.classList.remove('active');
|
|
809
|
+
}
|
|
810
|
+
this._imeEditing = false;
|
|
811
|
+
this._imeComposing = false;
|
|
812
|
+
}
|
|
617
813
|
};
|
|
618
814
|
DataGridBody.styles = [
|
|
619
815
|
dataGridBodyStyle,
|
|
620
816
|
css `
|
|
621
817
|
:host {
|
|
622
818
|
font-variation-settings: 'FILL' 1;
|
|
819
|
+
|
|
820
|
+
overscroll-behavior: none;
|
|
821
|
+
user-select: none;
|
|
623
822
|
}
|
|
624
823
|
|
|
625
824
|
[select-block] {
|
|
@@ -631,6 +830,7 @@ DataGridBody.styles = [
|
|
|
631
830
|
border: var(--grid-record-focused-cell-border);
|
|
632
831
|
background-image: var(--focused-background-image);
|
|
633
832
|
pointer-events: none;
|
|
833
|
+
|
|
634
834
|
z-index: 5;
|
|
635
835
|
}
|
|
636
836
|
|
|
@@ -663,6 +863,31 @@ DataGridBody.styles = [
|
|
|
663
863
|
box-shadow: none !important;
|
|
664
864
|
}
|
|
665
865
|
}
|
|
866
|
+
|
|
867
|
+
/* 한글 IME composition을 위한 숨겨진 input 버퍼 — 기존 에디터 셀 스타일과 일치 */
|
|
868
|
+
#ime-buffer {
|
|
869
|
+
position: absolute;
|
|
870
|
+
opacity: 0;
|
|
871
|
+
z-index: 10;
|
|
872
|
+
background-color: var(--grid-record-editing-background-color, var(--grid-record-background-color, #fff));
|
|
873
|
+
color: var(--md-sys-color-on-background, inherit);
|
|
874
|
+
font: inherit;
|
|
875
|
+
font-size: var(--grid-record-wide-fontsize);
|
|
876
|
+
border: 1px solid transparent;
|
|
877
|
+
border-width: 1px 0;
|
|
878
|
+
border-top: var(--grid-record-editing-border, none);
|
|
879
|
+
border-bottom: var(--grid-record-editing-border, none);
|
|
880
|
+
padding: var(--grid-record-padding);
|
|
881
|
+
box-sizing: border-box;
|
|
882
|
+
outline: none;
|
|
883
|
+
overflow: hidden;
|
|
884
|
+
pointer-events: none;
|
|
885
|
+
}
|
|
886
|
+
|
|
887
|
+
#ime-buffer.active {
|
|
888
|
+
opacity: 1;
|
|
889
|
+
pointer-events: auto;
|
|
890
|
+
}
|
|
666
891
|
`
|
|
667
892
|
];
|
|
668
893
|
__decorate([
|