@operato/data-grist 10.0.0-beta.12 → 10.0.0-beta.21
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 +10 -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 +197 -24
- 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 +35 -5
- package/dist/src/data-grid/event-handlers/data-grid-body-keydown-handler.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,16 @@
|
|
|
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
|
+
## [10.0.0-beta.21](https://github.com/hatiolab/operato/compare/v10.0.0-beta.20...v10.0.0-beta.21) (2026-04-02)
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
### :bug: Bug Fix
|
|
10
|
+
|
|
11
|
+
* 그리드 복붙시 태그 제거 ([1c5c677](https://github.com/hatiolab/operato/commit/1c5c6773972d5339b65d3c2bf7849cc86dedec63))
|
|
12
|
+
* 그리드 셀 ime-buffer로 한글 입력시 composing 유지 ([584ebe5](https://github.com/hatiolab/operato/commit/584ebe58178e03ca4669694ea156590eb3919784))
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
|
|
6
16
|
## [10.0.0-beta.12](https://github.com/hatiolab/operato/compare/v10.0.0-beta.11...v10.0.0-beta.12) (2026-03-17)
|
|
7
17
|
|
|
8
18
|
|
|
@@ -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);\n color: var(--grid-record-emphasized-color);\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);\n color: var(--grid-record-emphasized-color);\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
|
}
|
|
@@ -56,6 +56,8 @@ let DataGridBody = class DataGridBody extends LitElement {
|
|
|
56
56
|
this.from = -1;
|
|
57
57
|
this.to = -1;
|
|
58
58
|
this.fixedLefts = [];
|
|
59
|
+
this._imeComposing = false;
|
|
60
|
+
this._imeEditing = false;
|
|
59
61
|
// 검색시 스크롤 맨 위로
|
|
60
62
|
this._onFetchParamsChange = () => {
|
|
61
63
|
this.scrollTop = 0;
|
|
@@ -157,6 +159,7 @@ let DataGridBody = class DataGridBody extends LitElement {
|
|
|
157
159
|
`
|
|
158
160
|
: nothing}
|
|
159
161
|
${start && end && start !== end ? html ` <div select-block></div> ` : html ``}
|
|
162
|
+
<input id="ime-buffer" type="text" />
|
|
160
163
|
<slot></slot>
|
|
161
164
|
`;
|
|
162
165
|
}
|
|
@@ -165,12 +168,43 @@ let DataGridBody = class DataGridBody extends LitElement {
|
|
|
165
168
|
// this.addEventListener('scroll', this.handleOnScroll.bind(this))
|
|
166
169
|
/* focus() 를 받을 수 있도록 함. */
|
|
167
170
|
this.setAttribute('tabindex', '-1');
|
|
171
|
+
/* 한글 IME composition을 위한 숨겨진 input 버퍼 설정 */
|
|
172
|
+
const imeBuffer = this.renderRoot.querySelector('#ime-buffer');
|
|
173
|
+
this._imeBuffer = imeBuffer;
|
|
174
|
+
imeBuffer.addEventListener('compositionstart', () => {
|
|
175
|
+
this._imeComposing = true;
|
|
176
|
+
this._imeEditing = true;
|
|
177
|
+
imeBuffer.classList.add('active');
|
|
178
|
+
this._positionIMEBuffer();
|
|
179
|
+
});
|
|
180
|
+
imeBuffer.addEventListener('compositionend', () => {
|
|
181
|
+
this._imeComposing = false;
|
|
182
|
+
});
|
|
183
|
+
imeBuffer.addEventListener('input', () => {
|
|
184
|
+
if (!this._imeComposing && !this._imeEditing && imeBuffer.value) {
|
|
185
|
+
/* 영어/숫자: 실제 값이 입력된 경우에만 활성화 */
|
|
186
|
+
this._imeEditing = true;
|
|
187
|
+
imeBuffer.classList.add('active');
|
|
188
|
+
this._positionIMEBuffer();
|
|
189
|
+
}
|
|
190
|
+
});
|
|
168
191
|
/*
|
|
169
192
|
* focusout 으로 property를 변경시키는 경우, focusout에 의해 update가 발생하는 경우에는,
|
|
170
193
|
* 그리드 내부의 컴포넌트가 갱신되는 현상을 초래하게 된다.
|
|
171
194
|
* 따라서, focusout 핸들러에서 update를 유발하는 코드는 강력하게 금지시킨다.
|
|
172
195
|
*/
|
|
173
196
|
this.addEventListener('focusout', e => {
|
|
197
|
+
var _a;
|
|
198
|
+
/* IME buffer로 포커스가 이동하는 경우 keydown listener 유지 */
|
|
199
|
+
const related = e.relatedTarget;
|
|
200
|
+
if (related && ((_a = this.shadowRoot) === null || _a === void 0 ? void 0 : _a.contains(related))) {
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
/* 그리드 외부로 포커스 이동 시 IME buffer 커밋 */
|
|
204
|
+
if (this._imeEditing) {
|
|
205
|
+
const { row = 0, column = 0 } = this.focused || {};
|
|
206
|
+
this._commitIMEBuffer(row, column);
|
|
207
|
+
}
|
|
174
208
|
if (this._focusedListener) {
|
|
175
209
|
this.removeEventListener('keydown', this._focusedListener);
|
|
176
210
|
delete this._focusedListener;
|
|
@@ -205,6 +239,11 @@ let DataGridBody = class DataGridBody extends LitElement {
|
|
|
205
239
|
}));
|
|
206
240
|
});
|
|
207
241
|
this.renderRoot.addEventListener('pointerdown', (e) => {
|
|
242
|
+
/* 클릭 시 IME buffer가 활성 상태면 먼저 커밋 */
|
|
243
|
+
if (this._imeEditing) {
|
|
244
|
+
const { row = 0, column = 0 } = this.focused || {};
|
|
245
|
+
this._commitIMEBuffer(row, column);
|
|
246
|
+
}
|
|
208
247
|
this.setSelectBlock();
|
|
209
248
|
if ('buttons' in e && e.buttons !== 1) {
|
|
210
249
|
return;
|
|
@@ -298,6 +337,8 @@ let DataGridBody = class DataGridBody extends LitElement {
|
|
|
298
337
|
if (this.editTarget && this.editTarget.row == row && this.editTarget.column == column) {
|
|
299
338
|
return;
|
|
300
339
|
}
|
|
340
|
+
/* 에디터 편집 전환 시 IME buffer 정리 */
|
|
341
|
+
this._resetIMEBuffer();
|
|
301
342
|
this.editTarget = {
|
|
302
343
|
row,
|
|
303
344
|
column,
|
|
@@ -333,6 +374,11 @@ let DataGridBody = class DataGridBody extends LitElement {
|
|
|
333
374
|
if (left !== undefined) {
|
|
334
375
|
this.scrollLeft = left;
|
|
335
376
|
}
|
|
377
|
+
/* IME buffer 위치 업데이트 */
|
|
378
|
+
this._positionIMEBuffer();
|
|
379
|
+
if (!this.editTarget && this._imeBuffer) {
|
|
380
|
+
this._imeBuffer.focus();
|
|
381
|
+
}
|
|
336
382
|
}
|
|
337
383
|
// 페이징 바뀌면 스크롤 맨 위로
|
|
338
384
|
if (changes.has('data')) {
|
|
@@ -348,7 +394,18 @@ let DataGridBody = class DataGridBody extends LitElement {
|
|
|
348
394
|
}
|
|
349
395
|
}
|
|
350
396
|
focus() {
|
|
351
|
-
|
|
397
|
+
if (this.editTarget) {
|
|
398
|
+
/* 에디터 편집 모드에서는 에디터가 자체적으로 포커스를 관리하므로 간섭하지 않는다 */
|
|
399
|
+
return;
|
|
400
|
+
}
|
|
401
|
+
if (this._imeBuffer) {
|
|
402
|
+
/* 비편집 모드에서는 IME buffer로 포커스 리다이렉트 */
|
|
403
|
+
this._positionIMEBuffer();
|
|
404
|
+
this._imeBuffer.focus();
|
|
405
|
+
}
|
|
406
|
+
else {
|
|
407
|
+
super.focus();
|
|
408
|
+
}
|
|
352
409
|
if (this.focused === ZERO_FOCUS) {
|
|
353
410
|
let { records } = this.data;
|
|
354
411
|
let row = records.findIndex(record => record['__selected__']);
|
|
@@ -371,6 +428,7 @@ let DataGridBody = class DataGridBody extends LitElement {
|
|
|
371
428
|
delete this._recordViewRow;
|
|
372
429
|
});
|
|
373
430
|
}
|
|
431
|
+
/* 선택된 셀 블록의 값을 HTML(table)과 plain text(TSV) 두 가지 포맷으로 반환 */
|
|
374
432
|
getSelectedBlockValues() {
|
|
375
433
|
var { start, end } = this._selectBlock || {};
|
|
376
434
|
if (!(start && end)) {
|
|
@@ -384,34 +442,55 @@ let DataGridBody = class DataGridBody extends LitElement {
|
|
|
384
442
|
const endColumnIndex = start.columnIndex < end.columnIndex ? end.columnIndex : start.columnIndex;
|
|
385
443
|
const columnArray = new Array(endColumnIndex - startColumnIndex + 1).fill(startColumnIndex);
|
|
386
444
|
const columns = this.columns.filter(column => !column.hidden);
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
445
|
+
/*
|
|
446
|
+
* 클립보드에 두 가지 포맷(text/html, text/plain)을 동시에 저장한다.
|
|
447
|
+
* 붙여넣기하는 앱이 자신이 지원하는 포맷을 골라 읽는다.
|
|
448
|
+
* 생성되는 HTML:
|
|
449
|
+
* <td type=object data-value="{"id":...}">탈수 절임배추</td>
|
|
450
|
+
* ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^
|
|
451
|
+
* 그리드만 읽는 숨겨진 JSON Excel/메모장이 보는 텍스트
|
|
452
|
+
*/
|
|
453
|
+
const rows = new Array(endRowIndex - startRowIndex + 1).fill(startRowIndex).map((start, index) => {
|
|
454
|
+
const rowIndex = start + index;
|
|
455
|
+
const record = this.data.records[rowIndex];
|
|
456
|
+
const cells = columnArray.map((start, index) => {
|
|
457
|
+
var _a;
|
|
458
|
+
const columnIndex = start + index;
|
|
459
|
+
const column = columns[columnIndex];
|
|
460
|
+
const value = record === null || record === void 0 ? void 0 : record[column.name];
|
|
461
|
+
const type = typeof value;
|
|
462
|
+
/* htmlText: object는 JSON 문자열로 보존 → data-value 속성에 저장되어 그리드 붙여넣기 시 복원됨 */
|
|
463
|
+
const htmlText = value === undefined || value === null ? '' : type == 'object' ? JSON.stringify(value) : value;
|
|
464
|
+
/* plainText: object는 name 필드만 추출 → HTML의 textContent와 TSV에 사용됨 (Excel, 메모장 등) */
|
|
465
|
+
const plainText = value === undefined || value === null ? '' : type == 'object' ? ((_a = value === null || value === void 0 ? void 0 : value.name) !== null && _a !== void 0 ? _a : '') : value;
|
|
466
|
+
return { type, htmlText, plainText };
|
|
467
|
+
});
|
|
468
|
+
return cells;
|
|
469
|
+
});
|
|
470
|
+
const html = '<table>' +
|
|
471
|
+
rows
|
|
472
|
+
.map(cells => '<tr>' +
|
|
473
|
+
cells
|
|
474
|
+
.map(c => c.type == 'object' && c.htmlText
|
|
475
|
+
? `<td type=${c.type} data-value="${String(c.htmlText).replace(/"/g, '"')}">${c.plainText}</td>`
|
|
476
|
+
: `<td type=${c.type}>${c.htmlText}</td>`)
|
|
477
|
+
.join('') +
|
|
478
|
+
'</tr>')
|
|
405
479
|
.join('') +
|
|
406
|
-
'</table>'
|
|
480
|
+
'</table>';
|
|
481
|
+
/* 태그 없는 TSV 포맷 (object는 name만) */
|
|
482
|
+
const text = rows.map(cells => cells.map(c => String(c.plainText)).join('\t')).join('\n');
|
|
483
|
+
return { html, text };
|
|
407
484
|
}
|
|
408
485
|
}
|
|
409
486
|
async copy() {
|
|
410
487
|
const copied = this.getSelectedBlockValues();
|
|
488
|
+
if (!copied)
|
|
489
|
+
return;
|
|
411
490
|
await navigator.clipboard.write([
|
|
412
491
|
new ClipboardItem({
|
|
413
|
-
'text/html': new Blob([copied], { type: 'text/html' }),
|
|
414
|
-
'text/plain': new Blob([copied], { type: 'text/plain' })
|
|
492
|
+
'text/html': new Blob([copied.html], { type: 'text/html' }),
|
|
493
|
+
'text/plain': new Blob([copied.text], { type: 'text/plain' })
|
|
415
494
|
})
|
|
416
495
|
]);
|
|
417
496
|
const selectBlock = this.selectBlock || this.focusedField;
|
|
@@ -488,8 +567,8 @@ let DataGridBody = class DataGridBody extends LitElement {
|
|
|
488
567
|
const cells = record.querySelectorAll('td');
|
|
489
568
|
cells.forEach((item, columnIndex) => {
|
|
490
569
|
const targetColumn = columns[column + columnIndex];
|
|
491
|
-
|
|
492
|
-
|
|
570
|
+
/* data-value 속성이 있으면 우선 사용 (그리드에서 복사한 object 데이터 복원용) */
|
|
571
|
+
let value = (item.dataset.value || item.textContent);
|
|
493
572
|
let type = targetColumn.type || item.getAttribute('type') || 'string';
|
|
494
573
|
type = type.includes('object') ? 'object' : type; // 오브젝트 타입 예외처리
|
|
495
574
|
let { editable } = targetColumn.record;
|
|
@@ -620,6 +699,75 @@ let DataGridBody = class DataGridBody extends LitElement {
|
|
|
620
699
|
return record;
|
|
621
700
|
}, {});
|
|
622
701
|
}
|
|
702
|
+
/** IME buffer를 포커스된 셀 위에 위치시킨다 */
|
|
703
|
+
_positionIMEBuffer() {
|
|
704
|
+
var _a;
|
|
705
|
+
const focusedEl = (_a = this.renderRoot) === null || _a === void 0 ? void 0 : _a.querySelector('[focused]');
|
|
706
|
+
if (focusedEl && this._imeBuffer) {
|
|
707
|
+
this._imeBuffer.style.left = focusedEl.offsetLeft + 'px';
|
|
708
|
+
this._imeBuffer.style.top = focusedEl.offsetTop + 'px';
|
|
709
|
+
this._imeBuffer.style.width = focusedEl.offsetWidth + 'px';
|
|
710
|
+
this._imeBuffer.style.height = focusedEl.offsetHeight + 'px';
|
|
711
|
+
}
|
|
712
|
+
}
|
|
713
|
+
/** IME buffer 값을 field-change 이벤트로 커밋한다 */
|
|
714
|
+
_commitIMEBuffer(row, column) {
|
|
715
|
+
var _a;
|
|
716
|
+
const text = (_a = this._imeBuffer) === null || _a === void 0 ? void 0 : _a.value;
|
|
717
|
+
this._resetIMEBuffer();
|
|
718
|
+
if (!text)
|
|
719
|
+
return;
|
|
720
|
+
const columns = this.columns.filter(c => !c.hidden);
|
|
721
|
+
const col = columns[column];
|
|
722
|
+
if (!col || col.gutterName)
|
|
723
|
+
return;
|
|
724
|
+
const record = this.data.records[row] || { __dirty__: '+' };
|
|
725
|
+
let { editable } = col.record;
|
|
726
|
+
if (typeof editable === 'function') {
|
|
727
|
+
editable = editable.call(this, record[col.name], col, record, row, this);
|
|
728
|
+
}
|
|
729
|
+
if (!editable)
|
|
730
|
+
return;
|
|
731
|
+
/* 타입에 따른 값 변환 */
|
|
732
|
+
let value = text;
|
|
733
|
+
switch (col.type) {
|
|
734
|
+
case 'number':
|
|
735
|
+
case 'float':
|
|
736
|
+
case 'integer':
|
|
737
|
+
case 'progress':
|
|
738
|
+
value = parseToNumberOrNull(text);
|
|
739
|
+
if (value === null)
|
|
740
|
+
return;
|
|
741
|
+
break;
|
|
742
|
+
case 'boolean':
|
|
743
|
+
case 'checkbox':
|
|
744
|
+
return; /* boolean은 키보드 텍스트 입력으로 편집 불가 */
|
|
745
|
+
}
|
|
746
|
+
this.dispatchEvent(new CustomEvent('field-change', {
|
|
747
|
+
bubbles: true,
|
|
748
|
+
composed: true,
|
|
749
|
+
detail: {
|
|
750
|
+
before: record[col.name],
|
|
751
|
+
after: value,
|
|
752
|
+
column: col,
|
|
753
|
+
record,
|
|
754
|
+
row
|
|
755
|
+
}
|
|
756
|
+
}));
|
|
757
|
+
}
|
|
758
|
+
/** IME buffer 취소 (값 버리기) */
|
|
759
|
+
_discardIMEBuffer() {
|
|
760
|
+
this._resetIMEBuffer();
|
|
761
|
+
}
|
|
762
|
+
/** IME buffer 초기화 */
|
|
763
|
+
_resetIMEBuffer() {
|
|
764
|
+
if (this._imeBuffer) {
|
|
765
|
+
this._imeBuffer.value = '';
|
|
766
|
+
this._imeBuffer.classList.remove('active');
|
|
767
|
+
}
|
|
768
|
+
this._imeEditing = false;
|
|
769
|
+
this._imeComposing = false;
|
|
770
|
+
}
|
|
623
771
|
};
|
|
624
772
|
DataGridBody.styles = [
|
|
625
773
|
dataGridBodyStyle,
|
|
@@ -673,6 +821,31 @@ DataGridBody.styles = [
|
|
|
673
821
|
box-shadow: none !important;
|
|
674
822
|
}
|
|
675
823
|
}
|
|
824
|
+
|
|
825
|
+
/* 한글 IME composition을 위한 숨겨진 input 버퍼 — 기존 에디터 셀 스타일과 일치 */
|
|
826
|
+
#ime-buffer {
|
|
827
|
+
position: absolute;
|
|
828
|
+
opacity: 0;
|
|
829
|
+
z-index: 10;
|
|
830
|
+
background-color: var(--grid-record-editing-background-color, var(--grid-record-background-color, #fff));
|
|
831
|
+
color: var(--md-sys-color-on-background, inherit);
|
|
832
|
+
font: inherit;
|
|
833
|
+
font-size: var(--grid-record-wide-fontsize);
|
|
834
|
+
border: 1px solid transparent;
|
|
835
|
+
border-width: 1px 0;
|
|
836
|
+
border-top: var(--grid-record-editing-border, none);
|
|
837
|
+
border-bottom: var(--grid-record-editing-border, none);
|
|
838
|
+
padding: var(--grid-record-padding);
|
|
839
|
+
box-sizing: border-box;
|
|
840
|
+
outline: none;
|
|
841
|
+
overflow: hidden;
|
|
842
|
+
pointer-events: none;
|
|
843
|
+
}
|
|
844
|
+
|
|
845
|
+
#ime-buffer.active {
|
|
846
|
+
opacity: 1;
|
|
847
|
+
pointer-events: auto;
|
|
848
|
+
}
|
|
676
849
|
`
|
|
677
850
|
];
|
|
678
851
|
__decorate([
|