@operato/data-grist 1.1.53 → 1.1.55

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.
@@ -88,7 +88,6 @@ export class DataGridBody extends LitElement {
88
88
  private _focusedListener?: (e: KeyboardEvent) => void
89
89
  private _recordView?: any
90
90
  private _recordViewRow?: number
91
- private _selectBlockWillBeReset: boolean = true
92
91
 
93
92
  resetEdit() {
94
93
  this.editTarget = null
@@ -163,7 +162,7 @@ export class DataGridBody extends LitElement {
163
162
  ></ox-grid-field>
164
163
  `
165
164
  })}
166
- ${start && end ? html` <div select-block></div> ` : html``}
165
+ ${start && end && start !== end ? html` <div select-block></div> ` : html``}
167
166
  <slot></slot>
168
167
  `
169
168
  }
@@ -205,41 +204,49 @@ export class DataGridBody extends LitElement {
205
204
  this.setSelectBlock(start, end)
206
205
  })
207
206
 
208
- this.renderRoot.addEventListener('mousemove', (event: Event) => {
207
+ this.renderRoot.addEventListener('mousedown', (event: Event) => {
209
208
  const e = event as MouseEvent
209
+ this.setSelectBlock()
210
+
210
211
  if (e.buttons !== 1) {
211
- this._selectBlockWillBeReset = true
212
212
  return
213
213
  }
214
214
 
215
- const field = e.target as DataGridField
215
+ var target = (e.target as Element).closest('ox-grid-field') as DataGridField
216
+ var { rowIndex, columnIndex } = target || {}
216
217
 
217
- if (!this._selectBlock || this._selectBlockWillBeReset) {
218
- this._selectBlockWillBeReset = false
219
- this.setSelectBlock(field)
218
+ this.dispatchEvent(
219
+ new CustomEvent('focus-change', {
220
+ bubbles: true,
221
+ composed: true,
222
+ detail: {
223
+ row: rowIndex,
224
+ column: columnIndex
225
+ }
226
+ })
227
+ )
220
228
 
221
- // this.dispatchEvent(
222
- // new CustomEvent('focus-change', {
223
- // bubbles: true,
224
- // composed: true,
225
- // detail: undefined
226
- // })
227
- // )
229
+ if (!isNaN(rowIndex) && !isNaN(columnIndex)) {
230
+ this.startEditTarget(rowIndex, columnIndex)
231
+ }
232
+ })
228
233
 
234
+ this.renderRoot.addEventListener('mousemove', (event: Event) => {
235
+ const e = event as MouseEvent
236
+ if (e.buttons !== 1) {
229
237
  return
230
238
  }
231
239
 
232
- var { start, end } = this._selectBlock || {}
240
+ const field = e.target as DataGridField
233
241
 
234
- if (start !== field && !end) {
235
- /* cancel all selected text */
236
- window.getSelection()?.removeAllRanges()
237
- }
242
+ if (!this._selectBlock) {
243
+ this.setSelectBlock(field, field)
238
244
 
239
- if (start === field && !end) {
240
245
  return
241
246
  }
242
247
 
248
+ var { start, end } = this._selectBlock || {}
249
+
243
250
  if (start && end !== field) {
244
251
  end = field
245
252
 
@@ -247,9 +254,7 @@ export class DataGridBody extends LitElement {
247
254
  }
248
255
  })
249
256
 
250
- this.renderRoot.addEventListener('mouseup', (event: Event) => {
251
- this._selectBlockWillBeReset = true
252
- })
257
+ this.renderRoot.addEventListener('mouseup', (event: Event) => {})
253
258
 
254
259
  this.renderRoot.addEventListener('click', dataGridBodyClickHandler.bind(this))
255
260
 
@@ -323,8 +328,6 @@ export class DataGridBody extends LitElement {
323
328
  return
324
329
  }
325
330
 
326
- this.setSelectBlock()
327
-
328
331
  let { top, left } = calcScrollPos(this, element)
329
332
  // TODO this.scroll()을 사용하면, 효과가 좋으나 left 계산에 문제가 있는 것 같음.
330
333
  // this.scroll({
@@ -388,11 +391,6 @@ export class DataGridBody extends LitElement {
388
391
  if (!(start && end)) {
389
392
  start = this.focusedField
390
393
 
391
- if (typeof start!.value === 'string' || typeof start!.value === 'number') {
392
- const selection = document.getSelection()
393
- return selection?.toString() || this.focusedField?.value
394
- }
395
-
396
394
  end = start
397
395
  }
398
396
 
@@ -405,100 +403,169 @@ export class DataGridBody extends LitElement {
405
403
  const columnArray = new Array(endColumnIndex - startColumnIndex + 1).fill(startColumnIndex)
406
404
  const columns = this.columns.filter(column => !column.hidden)
407
405
 
408
- return new Array(endRowIndex - startRowIndex + 1).fill(startRowIndex).map((start, index) => {
409
- const rowIndex = start + index
410
- const record = this.data.records[rowIndex]
411
-
412
- return columnArray.map((start, index) => {
413
- const columnIndex = start + index
414
- const column = columns[columnIndex]
415
-
416
- return record?.[column.name]
417
- })
418
- })
406
+ return (
407
+ '<table>' +
408
+ new Array(endRowIndex - startRowIndex + 1)
409
+ .fill(startRowIndex)
410
+ .map((start, index) => {
411
+ const rowIndex = start + index
412
+ const record = this.data.records[rowIndex]
413
+
414
+ const tds = columnArray
415
+ .map((start, index) => {
416
+ const columnIndex = start + index
417
+ const column = columns[columnIndex]
418
+ const value = record?.[column.name]
419
+ const text = value === undefined ? '' : typeof value == 'object' ? JSON.stringify(value) : value
420
+
421
+ return `<td>${text}</td>`
422
+ })
423
+ .join('')
424
+ return `<tr>${tds}</tr>`
425
+ })
426
+ .join('') +
427
+ '</table>'
428
+ )
419
429
  }
420
430
  }
421
431
 
422
432
  async copy() {
423
433
  const copied = this.getSelectedBlockValues()
424
- await navigator.clipboard.writeText(
425
- copied instanceof Array ? JSON.stringify(this.getSelectedBlockValues()) : copied
426
- )
427
434
 
428
- const selectBlock = this.selectBlock
435
+ await navigator.clipboard.write([
436
+ new ClipboardItem({
437
+ 'text/html': new Blob([copied], { type: 'text/html' }),
438
+ 'text/plain': new Blob([copied], { type: 'text/plain' })
439
+ })
440
+ ])
441
+
442
+ const selectBlock = this.selectBlock || this.focusedField
429
443
  if (selectBlock) {
444
+ const backgroundColor = selectBlock.style.backgroundColor
445
+ const opacity = selectBlock.style.opacity
446
+
430
447
  selectBlock.setAttribute('data-tooltip', 'copied to clipboard!')
431
448
  selectBlock.style.backgroundColor = 'red'
432
449
  selectBlock.style.opacity = '0.5'
433
450
  await sleep(500)
434
451
  selectBlock.removeAttribute('data-tooltip')
435
- selectBlock.style.backgroundColor = ''
436
- selectBlock.style.opacity = ''
452
+ selectBlock.style.backgroundColor = backgroundColor
453
+ selectBlock.style.opacity = opacity
437
454
  }
438
455
  }
439
456
 
440
457
  async paste() {
441
458
  try {
442
- const text = await navigator.clipboard.readText()
443
- if (!text) {
459
+ const selection = window.getSelection()
460
+
461
+ const clipboardItems = await navigator.clipboard.read()
462
+ if (!clipboardItems) {
444
463
  return
445
464
  }
446
465
 
447
- const { row, column } = this.focused
448
- const { records } = this.data
449
- const columns = this.columns.filter(column => !column.hidden)
450
- var block: Array<Array<any>>
466
+ var type: string | undefined
467
+ var content: string | undefined
468
+
469
+ for (const clipboardItem of clipboardItems) {
470
+ try {
471
+ var blob = await clipboardItem.getType('text/html')
472
+ content = blob && (await blob.text())
473
+ type = 'text/html'
474
+ } catch (e) {
475
+ try {
476
+ blob = await clipboardItem.getType('text/plain')
477
+ content = blob && (await blob.text())
478
+ type = 'text/plain'
479
+ } catch (e) {}
480
+ }
451
481
 
452
- try {
453
- var parsed = JSON.parse(text)
454
- } catch (ex) {
455
- parsed = text
482
+ break
456
483
  }
457
484
 
458
- if (!(parsed instanceof Array)) {
459
- block = [[parsed]]
460
- } else {
461
- block = parsed
485
+ if (!content) {
486
+ return
462
487
  }
463
488
 
464
- block.forEach((record, rowIndex) => {
465
- if (!(record instanceof Array)) {
489
+ const { row, column } = this.focused
490
+ const { records } = this.data
491
+ const columns = this.columns.filter(column => !column.hidden)
492
+
493
+ if (type === 'text/html') {
494
+ const div = document.createElement('div')
495
+ div.innerHTML = content!.trim()
496
+ const table = div.querySelector('table') as HTMLTableElement
497
+ if (!table) {
466
498
  return
467
499
  }
468
500
 
469
- var targetRecord = records[row + rowIndex] || { __dirty__: '+' }
470
- if (row + rowIndex >= records.length) {
471
- records.push(targetRecord)
501
+ if (selection) {
502
+ this.resetEdit()
503
+ selection.removeAllRanges()
504
+ await this.updateComplete
472
505
  }
506
+ const rows = table.querySelectorAll('tr')
473
507
 
474
- record.map((item, columnIndex) => {
475
- const targetColumn = columns[column + columnIndex]
476
- if (targetColumn && !targetColumn.gutterName && targetColumn.record.editable) {
477
- this.dispatchEvent(
478
- new CustomEvent('field-change', {
479
- bubbles: true,
480
- composed: true,
481
- detail: {
482
- before: targetRecord[targetColumn.name],
483
- after: item,
484
- column: targetColumn,
485
- record: targetRecord,
486
- row: row + rowIndex
487
- }
488
- })
489
- )
508
+ rows.forEach((record, rowIndex) => {
509
+ if (!(record instanceof HTMLTableRowElement)) {
510
+ return
490
511
  }
512
+
513
+ var targetRecord = records[row + rowIndex] || { __dirty__: '+' }
514
+ if (row + rowIndex >= records.length) {
515
+ records.push(targetRecord)
516
+ }
517
+
518
+ const cells = record.querySelectorAll('td')
519
+ cells.forEach((item, columnIndex) => {
520
+ const targetColumn = columns[column + columnIndex]
521
+ if (targetColumn && !targetColumn.gutterName && targetColumn.record.editable) {
522
+ this.dispatchEvent(
523
+ new CustomEvent('field-change', {
524
+ bubbles: true,
525
+ composed: true,
526
+ detail: {
527
+ before: targetRecord[targetColumn.name],
528
+ after: item.textContent,
529
+ column: targetColumn,
530
+ record: targetRecord,
531
+ row: row + rowIndex
532
+ }
533
+ })
534
+ )
535
+ }
536
+ })
491
537
  })
492
- })
493
- } catch (e) {
494
- console.error(e)
495
- }
538
+
539
+ return
540
+ } else if (!selection && type === 'text/plain') {
541
+ const targetRecord = records[row] || { __dirty__: '+' }
542
+ const targetColumn = columns[column]
543
+
544
+ if (targetColumn && !targetColumn.gutterName && targetColumn.record.editable) {
545
+ this.dispatchEvent(
546
+ new CustomEvent('field-change', {
547
+ bubbles: true,
548
+ composed: true,
549
+ detail: {
550
+ before: targetRecord[targetColumn.name],
551
+ after: content,
552
+ column: targetColumn,
553
+ record: targetRecord,
554
+ row: row
555
+ }
556
+ })
557
+ )
558
+ }
559
+ }
560
+ } catch (e) {}
496
561
  }
497
562
 
498
563
  setSelectBlock(start?: DataGridField, end?: DataGridField) {
499
564
  this._selectBlock = start && { start, end }
500
565
 
501
566
  if (start && end && start !== end) {
567
+ window.getSelection()?.removeAllRanges()
568
+
502
569
  const left = start.columnIndex < end.columnIndex ? start : end
503
570
  const right = left === start ? end : start
504
571
  const top = start.rowIndex < end.rowIndex ? start : end
@@ -518,7 +585,10 @@ export class DataGridBody extends LitElement {
518
585
  new CustomEvent('focus-change', {
519
586
  bubbles: true,
520
587
  composed: true,
521
- detail: undefined
588
+ detail: {
589
+ row: end.rowIndex,
590
+ column: end.columnIndex
591
+ }
522
592
  })
523
593
  )
524
594
  }
@@ -10,6 +10,11 @@ import { DataGridField } from '../data-grid-field'
10
10
  export function dataGridBodyClickHandler(this: DataGridBody, e: Event): void {
11
11
  e.stopPropagation()
12
12
 
13
+ if (this.editTarget) {
14
+ /* editTarget이 새로 설정되지 않았다면, 이후 기능이 실행된다. */
15
+ return
16
+ }
17
+
13
18
  /* target should be 'ox-grid-field' */
14
19
  var target = (e.target as Element).closest('ox-grid-field') as DataGridField
15
20
  var { column, record, rowIndex, columnIndex } = target || {}
@@ -19,16 +24,16 @@ export function dataGridBodyClickHandler(this: DataGridBody, e: Event): void {
19
24
  return
20
25
  }
21
26
 
22
- this.dispatchEvent(
23
- new CustomEvent('focus-change', {
24
- bubbles: true,
25
- composed: true,
26
- detail: {
27
- row: rowIndex,
28
- column: columnIndex
29
- }
30
- })
31
- )
27
+ // this.dispatchEvent(
28
+ // new CustomEvent('focus-change', {
29
+ // bubbles: true,
30
+ // composed: true,
31
+ // detail: {
32
+ // row: rowIndex,
33
+ // column: columnIndex
34
+ // }
35
+ // })
36
+ // )
32
37
 
33
38
  /* 만약, 클릭 이벤트에 포커스만 바꾸고 싶다면, 아래 코멘트를 제거한다. */
34
39
  /*
@@ -36,12 +41,16 @@ export function dataGridBodyClickHandler(this: DataGridBody, e: Event): void {
36
41
  */
37
42
 
38
43
  /* 만약, 클릭 이벤트에 포커스만 바꾸고 싶다면, 아래 부분을 코멘트처리한다. */
39
- if (!isNaN(rowIndex) && !isNaN(columnIndex)) {
40
- this.startEditTarget(rowIndex, columnIndex)
41
- } else {
42
- console.error('should not be here.')
43
- return
44
- }
44
+ // if (!isNaN(rowIndex) && !isNaN(columnIndex)) {
45
+ // this.startEditTarget(rowIndex, columnIndex)
46
+ // if (this.editTarget) {
47
+ // /* editTarget이 새로 설정되지 않았다면, 이후 기능이 실행된다. */
48
+ // return
49
+ // }
50
+ // } else {
51
+ // console.error('should not be here.')
52
+ // return
53
+ // }
45
54
  /* 만약, 클릭 이벤트에 포커스만 바꾸고 싶다면, 여기까지 코멘트 처리한다. */
46
55
 
47
56
  /* do column click handler */
@@ -9,6 +9,11 @@ import { DataGridField } from '../data-grid-field'
9
9
  export async function dataGridBodyDblclickHandler(this: DataGridBody, e: Event): Promise<void> {
10
10
  e.stopPropagation()
11
11
 
12
+ if (this.editTarget) {
13
+ /* editTarget이 새로 설정되지 않았다면, 이후 기능이 실행된다. */
14
+ return
15
+ }
16
+
12
17
  /* target should be 'ox-grid-field' */
13
18
  var target = (e.target as Element).closest('ox-grid-field') as DataGridField
14
19
  var { column, record, rowIndex, columnIndex } = target || {}
@@ -18,12 +23,12 @@ export async function dataGridBodyDblclickHandler(this: DataGridBody, e: Event):
18
23
  return
19
24
  }
20
25
 
21
- if (!isNaN(rowIndex) && !isNaN(columnIndex)) {
22
- this.startEditTarget(rowIndex, columnIndex)
23
- } else {
24
- console.error('should not be here.')
25
- return
26
- }
26
+ // if (!isNaN(rowIndex) && !isNaN(columnIndex)) {
27
+ // this.startEditTarget(rowIndex, columnIndex)
28
+ // } else {
29
+ // console.error('should not be here.')
30
+ // return
31
+ // }
27
32
 
28
33
  /* do column dblclick handler */
29
34
  var { dblclick } = column.handlers
@@ -20,6 +20,7 @@ export async function dataGridBodyKeydownHandler(this: DataGridBody, e: Keyboard
20
20
  /* TODO 편집이 취소되어야 한다. */
21
21
  case 'Enter':
22
22
  this.editTarget = null
23
+
23
24
  this.focus()
24
25
  return
25
26
 
@@ -49,12 +50,32 @@ export async function dataGridBodyKeydownHandler(this: DataGridBody, e: Keyboard
49
50
  break
50
51
 
51
52
  default:
53
+ if (e.metaKey || e.ctrlKey) {
54
+ /* Window System에 선택된 영역이 없다면, 셀을 복사/붙여넣기 한다. */
55
+ switch (e.key) {
56
+ case 'c':
57
+ const selection = window.getSelection()
58
+ if (!selection || !selection.anchorNode || selection.type != 'Range') {
59
+ /*
60
+ editor가 select 인 경우 자체 복사 기능이 없으므로 this.copy()를 사용하도록 하기 위해서, selection.type != 'Range' 조건을 추가했다.
61
+ select인 경우에는 selection.type 이 'Caret' 이었다.
62
+ */
63
+ this.copy()
64
+ e.preventDefault()
65
+ }
66
+ break
67
+ case 'v':
68
+ this.paste()
69
+ break
70
+ }
71
+ }
52
72
  return
53
73
  }
54
74
  } else {
55
75
  switch (key) {
56
76
  case 'Up':
57
77
  case 'ArrowUp':
78
+ this.setSelectBlock()
58
79
  if (e.metaKey || e.ctrlKey) {
59
80
  row = 0
60
81
  } else {
@@ -64,6 +85,7 @@ export async function dataGridBodyKeydownHandler(this: DataGridBody, e: Keyboard
64
85
 
65
86
  case 'Down':
66
87
  case 'ArrowDown':
88
+ this.setSelectBlock()
67
89
  if (e.metaKey || e.ctrlKey) {
68
90
  row = maxrow
69
91
  } else {
@@ -72,12 +94,14 @@ export async function dataGridBodyKeydownHandler(this: DataGridBody, e: Keyboard
72
94
  break
73
95
 
74
96
  case 'Enter':
97
+ this.setSelectBlock()
75
98
  this.startEditTarget(row, column)
76
99
  return
77
100
 
78
101
  case 'Left':
79
102
  case 'ArrowLeft':
80
103
  case 'Backspace':
104
+ this.setSelectBlock()
81
105
  if (e.metaKey || e.ctrlKey) {
82
106
  column = 0
83
107
  } else {
@@ -87,6 +111,7 @@ export async function dataGridBodyKeydownHandler(this: DataGridBody, e: Keyboard
87
111
 
88
112
  case 'Right':
89
113
  case 'ArrowRight':
114
+ this.setSelectBlock()
90
115
  if (e.metaKey || e.ctrlKey) {
91
116
  column = maxcolumn
92
117
  } else {
@@ -96,6 +121,7 @@ export async function dataGridBodyKeydownHandler(this: DataGridBody, e: Keyboard
96
121
 
97
122
  case 'Tab':
98
123
  this.editTarget = null
124
+ this.setSelectBlock()
99
125
  if (e.shiftKey) {
100
126
  column = Math.max(0, column - 1)
101
127
  } else {
@@ -106,23 +132,28 @@ export async function dataGridBodyKeydownHandler(this: DataGridBody, e: Keyboard
106
132
 
107
133
  case 'PageUp':
108
134
  /* TODO 페이지당 레코드의 수를 계산해서 증감시켜야 한다. */
135
+ this.setSelectBlock()
109
136
  row = Math.max(0, row - 10)
110
137
  break
111
138
 
112
139
  case 'PageDown':
113
140
  case ' ':
141
+ this.setSelectBlock()
114
142
  row = Math.min(maxrow, row + 10)
115
143
  break
116
144
 
117
145
  case 'Esc':
118
146
  case 'Escape':
147
+ this.setSelectBlock()
119
148
  return
120
149
 
121
150
  case 'End':
151
+ this.setSelectBlock()
122
152
  row = maxrow
123
153
  break
124
154
 
125
155
  case 'Home':
156
+ this.setSelectBlock()
126
157
  row = 0
127
158
  break
128
159
 
@@ -130,13 +161,23 @@ export async function dataGridBodyKeydownHandler(this: DataGridBody, e: Keyboard
130
161
  if (e.metaKey || e.ctrlKey) {
131
162
  switch (e.key) {
132
163
  case 'c':
133
- this.copy()
164
+ const selection = window.getSelection()
165
+ if (!selection || !selection.anchorNode || selection.type != 'Range') {
166
+ /*
167
+ 에디터 상태가 아닌 경우에도, selection.type != 'Range' 조건을 추가해서,
168
+ 셀렉션이 된 상태에서는 기본 동작으로 텍스트 복사를 할 수 있도록 했다.
169
+ 보통은, 싱글 필드가 셀렉션박스로 지정된 경우에 자체 텍스트의 일부를 복사하기 위해서 필요하다.
170
+ */
171
+ this.copy()
172
+ e.preventDefault()
173
+ }
134
174
  break
135
175
  case 'v':
136
176
  this.paste()
137
177
  break
138
178
  }
139
179
  } else if (key.length === 1) {
180
+ this.setSelectBlock()
140
181
  this.startEditTarget(row, column)
141
182
  }
142
183
  return