@operato/data-grist 8.0.0-beta.0 → 8.0.0-beta.2

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.
Files changed (168) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/package.json +10 -10
  3. package/.storybook/main.js +0 -3
  4. package/.storybook/preview.js +0 -52
  5. package/.storybook/server.mjs +0 -8
  6. package/demo/data-grist-test.html +0 -468
  7. package/demo/favicon.ico +0 -0
  8. package/demo/index.html +0 -541
  9. package/demo/report-test.html +0 -249
  10. package/src/accumulator/accumulator.ts +0 -63
  11. package/src/configure/column-builder.ts +0 -114
  12. package/src/configure/config-builder.ts +0 -40
  13. package/src/configure/filters-option-builder.ts +0 -8
  14. package/src/configure/imex-option-builder.ts +0 -5
  15. package/src/configure/list-option-builder.ts +0 -9
  16. package/src/configure/rows-option-builder.ts +0 -38
  17. package/src/configure/tree-option-builder.ts +0 -22
  18. package/src/configure/zero-config.ts +0 -83
  19. package/src/const.ts +0 -1
  20. package/src/data-card/data-card-field.ts +0 -94
  21. package/src/data-card/data-card-gutter-menu.ts +0 -94
  22. package/src/data-card/data-card-gutter.ts +0 -103
  23. package/src/data-card/data-card.ts +0 -154
  24. package/src/data-card/event-handlers/record-card-click-handler.ts +0 -34
  25. package/src/data-card/event-handlers/record-card-dblclick-handler.ts +0 -34
  26. package/src/data-card/record-card.ts +0 -298
  27. package/src/data-consumer.ts +0 -11
  28. package/src/data-grid/data-grid-accum-field.ts +0 -109
  29. package/src/data-grid/data-grid-body-style.ts +0 -85
  30. package/src/data-grid/data-grid-body.ts +0 -765
  31. package/src/data-grid/data-grid-field.ts +0 -227
  32. package/src/data-grid/data-grid-footer.ts +0 -119
  33. package/src/data-grid/data-grid-header.ts +0 -578
  34. package/src/data-grid/data-grid.ts +0 -293
  35. package/src/data-grid/event-handlers/data-grid-body-click-handler.ts +0 -69
  36. package/src/data-grid/event-handlers/data-grid-body-contextmenu-handler.ts +0 -32
  37. package/src/data-grid/event-handlers/data-grid-body-dblclick-handler.ts +0 -42
  38. package/src/data-grid/event-handlers/data-grid-body-focus-change-handler.ts +0 -24
  39. package/src/data-grid/event-handlers/data-grid-body-keydown-handler.ts +0 -234
  40. package/src/data-grist.ts +0 -1233
  41. package/src/data-list/data-list-field.ts +0 -82
  42. package/src/data-list/data-list-gutter.ts +0 -108
  43. package/src/data-list/data-list.ts +0 -145
  44. package/src/data-list/event-handlers/record-partial-click-handler.ts +0 -34
  45. package/src/data-list/event-handlers/record-partial-dblclick-handler.ts +0 -33
  46. package/src/data-list/event-handlers/record-partial-long-press-handler.ts +0 -33
  47. package/src/data-list/record-partial.ts +0 -264
  48. package/src/data-manipulator.ts +0 -426
  49. package/src/data-provider.ts +0 -271
  50. package/src/data-report/data-report-body-style.ts +0 -58
  51. package/src/data-report/data-report-body.ts +0 -189
  52. package/src/data-report/data-report-component.ts +0 -138
  53. package/src/data-report/data-report-field.ts +0 -83
  54. package/src/data-report/data-report-header.ts +0 -242
  55. package/src/data-report/event-handlers/data-report-body-click-handler.ts +0 -38
  56. package/src/data-report/event-handlers/data-report-body-dblclick-handler.ts +0 -25
  57. package/src/data-report/event-handlers/data-report-body-keydown-handler.ts +0 -68
  58. package/src/data-report.ts +0 -424
  59. package/src/editors/index.ts +0 -4
  60. package/src/editors/ox-grist-editor-checkbox.ts +0 -28
  61. package/src/editors/ox-grist-editor-color.ts +0 -10
  62. package/src/editors/ox-grist-editor-date.ts +0 -10
  63. package/src/editors/ox-grist-editor-datetime.ts +0 -27
  64. package/src/editors/ox-grist-editor-email.ts +0 -10
  65. package/src/editors/ox-grist-editor-file.ts +0 -28
  66. package/src/editors/ox-grist-editor-image.ts +0 -31
  67. package/src/editors/ox-grist-editor-month.ts +0 -10
  68. package/src/editors/ox-grist-editor-multiple-select.ts +0 -57
  69. package/src/editors/ox-grist-editor-number.ts +0 -27
  70. package/src/editors/ox-grist-editor-password.ts +0 -10
  71. package/src/editors/ox-grist-editor-select.ts +0 -55
  72. package/src/editors/ox-grist-editor-tel.ts +0 -10
  73. package/src/editors/ox-grist-editor-text.ts +0 -14
  74. package/src/editors/ox-grist-editor-textarea.ts +0 -16
  75. package/src/editors/ox-grist-editor-time.ts +0 -10
  76. package/src/editors/ox-grist-editor-tree.ts +0 -27
  77. package/src/editors/ox-grist-editor-varname.ts +0 -36
  78. package/src/editors/ox-grist-editor-week.ts +0 -10
  79. package/src/editors/ox-grist-editor.ts +0 -207
  80. package/src/editors/ox-input-tree.ts +0 -226
  81. package/src/editors/registry.ts +0 -82
  82. package/src/empty-note.ts +0 -46
  83. package/src/filters/filter-checkbox.ts +0 -49
  84. package/src/filters/filter-input-barcode.ts +0 -34
  85. package/src/filters/filter-input.ts +0 -30
  86. package/src/filters/filter-range-date.ts +0 -81
  87. package/src/filters/filter-range-number.ts +0 -64
  88. package/src/filters/filter-select-buttons.ts +0 -60
  89. package/src/filters/filter-select.ts +0 -68
  90. package/src/filters/filter-styles.ts +0 -119
  91. package/src/filters/filters-form.ts +0 -476
  92. package/src/filters/index.ts +0 -10
  93. package/src/filters/registry.ts +0 -56
  94. package/src/formatters/date-formatter.ts +0 -3
  95. package/src/formatters/index.ts +0 -1
  96. package/src/formatters/number-formatter.ts +0 -3
  97. package/src/formatters/registry.ts +0 -30
  98. package/src/formatters/text-formatter.ts +0 -3
  99. package/src/gutters/gutter-button.ts +0 -51
  100. package/src/gutters/gutter-dirty.ts +0 -96
  101. package/src/gutters/gutter-row-selector.ts +0 -89
  102. package/src/gutters/gutter-sequence.ts +0 -54
  103. package/src/gutters/index.ts +0 -1
  104. package/src/gutters/registry.ts +0 -32
  105. package/src/handlers/contextmenu-tree-mutation.ts +0 -80
  106. package/src/handlers/index.ts +0 -1
  107. package/src/handlers/move-down.ts +0 -44
  108. package/src/handlers/move-up.ts +0 -44
  109. package/src/handlers/record-copy.ts +0 -38
  110. package/src/handlers/record-delete.ts +0 -30
  111. package/src/handlers/record-view-handler.ts +0 -27
  112. package/src/handlers/registry.ts +0 -42
  113. package/src/handlers/select-row-toggle.ts +0 -30
  114. package/src/handlers/select-row.ts +0 -27
  115. package/src/index.ts +0 -17
  116. package/src/personalizer/index.ts +0 -1
  117. package/src/personalizer/ox-grist-filter-personalizer.ts +0 -192
  118. package/src/personalizer/ox-grist-personalizer.ts +0 -226
  119. package/src/record-view/event-handlers/record-view-body-click-handler.ts +0 -33
  120. package/src/record-view/event-handlers/record-view-body-keydown-handler.ts +0 -26
  121. package/src/record-view/index.ts +0 -2
  122. package/src/record-view/ox-record-creator.ts +0 -289
  123. package/src/record-view/record-view-body.ts +0 -257
  124. package/src/record-view/record-view-handler.ts +0 -86
  125. package/src/record-view/record-view.ts +0 -122
  126. package/src/renderers/index.ts +0 -14
  127. package/src/renderers/ox-grist-renderer-boolean.ts +0 -43
  128. package/src/renderers/ox-grist-renderer-color.ts +0 -15
  129. package/src/renderers/ox-grist-renderer-date.ts +0 -62
  130. package/src/renderers/ox-grist-renderer-file.ts +0 -31
  131. package/src/renderers/ox-grist-renderer-image.ts +0 -27
  132. package/src/renderers/ox-grist-renderer-json5.ts +0 -36
  133. package/src/renderers/ox-grist-renderer-link.ts +0 -17
  134. package/src/renderers/ox-grist-renderer-password.ts +0 -7
  135. package/src/renderers/ox-grist-renderer-progress.ts +0 -45
  136. package/src/renderers/ox-grist-renderer-select.ts +0 -58
  137. package/src/renderers/ox-grist-renderer-text.ts +0 -16
  138. package/src/renderers/ox-grist-renderer-textarea.ts +0 -7
  139. package/src/renderers/ox-grist-renderer-tree.ts +0 -189
  140. package/src/renderers/ox-grist-renderer.ts +0 -35
  141. package/src/renderers/registry.ts +0 -111
  142. package/src/sorters/sorters-control.ts +0 -143
  143. package/src/types.ts +0 -813
  144. package/src/utils/index.ts +0 -2
  145. package/src/utils/list-param.ts +0 -72
  146. package/src/utils/supports-passive.ts +0 -13
  147. package/stories/accumulator-format.stories.ts +0 -276
  148. package/stories/barcode-input-filter.stories.ts +0 -216
  149. package/stories/bounded-select-filters.stories.ts +0 -333
  150. package/stories/bounded-select-record.stories.ts +0 -336
  151. package/stories/click-event-custom.stories.ts +0 -287
  152. package/stories/click-event.stories.ts +0 -283
  153. package/stories/creatable-only-column.stories.ts +0 -253
  154. package/stories/default-filters.stories.ts +0 -241
  155. package/stories/dynamic-editable.stories.ts +0 -313
  156. package/stories/empty-sorters.stories.ts +0 -180
  157. package/stories/explicit-fetch.stories.ts +0 -186
  158. package/stories/fixed-column.stories.ts +0 -416
  159. package/stories/grid-setting.stories.ts +0 -501
  160. package/stories/grist-modes.stories.ts +0 -451
  161. package/stories/group-header.stories.ts +0 -442
  162. package/stories/record-view.stories.ts +0 -143
  163. package/stories/textarea.stories.ts +0 -261
  164. package/stories/tree-column-with-checkbox.stories.ts +0 -297
  165. package/stories/tree-column.stories.ts +0 -296
  166. package/tsconfig.json +0 -26
  167. package/web-dev-server.config.mjs +0 -27
  168. package/web-test-runner.config.mjs +0 -45
@@ -1,765 +0,0 @@
1
- import './data-grid-field'
2
- import './data-grid-accum-field'
3
-
4
- import { css, html, LitElement, nothing, PropertyValues } from 'lit'
5
- import { customElement, property, query, state } from 'lit/decorators.js'
6
- import { ifDefined } from 'lit/directives/if-defined.js'
7
- import debounce from 'lodash-es/debounce'
8
-
9
- import { sleep, parseToNumberOrNull } from '@operato/utils'
10
-
11
- import { ZERO_CONFIG, ZERO_DATA } from '../configure/zero-config'
12
- import { RecordViewHandler } from '../record-view/record-view-handler'
13
- import { ColumnConfig, GristConfig, GristData, GristRecord } from '../types'
14
- import { supportsPassive } from '../utils'
15
- import { dataGridBodyStyle } from './data-grid-body-style'
16
- import { DataGridField } from './data-grid-field'
17
- import { dataGridBodyClickHandler } from './event-handlers/data-grid-body-click-handler'
18
- import { dataGridBodyDblclickHandler } from './event-handlers/data-grid-body-dblclick-handler'
19
- import { dataGridBodyFocusChangeHandler } from './event-handlers/data-grid-body-focus-change-handler'
20
- import { dataGridBodyContextMenuHandler } from './event-handlers/data-grid-body-contextmenu-handler'
21
- import { dataGridBodyKeydownHandler } from './event-handlers/data-grid-body-keydown-handler'
22
- import { accumulate } from '../accumulator/accumulator'
23
-
24
- const THRESHOLD = 300
25
- const DATA_PADDING = 3
26
- const ROW_HEIGHT = 40
27
- const GAP_SIZE = 1
28
-
29
- function calcScrollPos(parent: DataGridBody, child: Element) {
30
- /* getBoundingClientRect는 safari에서 스크롤 상태에서 다른 브라우저와는 다른 값을 리턴함 - 사파리는 약간 이상 작동함. */
31
- var { top: ct, left: cl, right: cr, bottom: cb } = child.getBoundingClientRect()
32
- var { top: pt, left: pl, right: pr, bottom: pb } = parent.getBoundingClientRect()
33
- var { scrollLeft, scrollTop } = parent
34
- var scrollbarWidth = parent.clientWidth - parent.offsetWidth
35
- var scrollbarHeight = parent.clientHeight - parent.offsetHeight
36
-
37
- return {
38
- left: cl < pl ? scrollLeft - (pl - cl) : cr > pr ? scrollLeft - (pr - cr) - scrollbarWidth : undefined,
39
- top: ct < pt ? scrollTop - (pt - ct) : cb > pb ? scrollTop - (pb - cb) - scrollbarHeight : undefined
40
- }
41
- }
42
-
43
- const ZERO_FOCUS = {
44
- row: 0,
45
- column: 0
46
- }
47
-
48
- @customElement('ox-grid-body')
49
- export class DataGridBody extends LitElement {
50
- debounce = debounce((scrollTop: number, clientHeight: number) => {
51
- const maxVisibleRows = Math.ceil(clientHeight / (ROW_HEIGHT + GAP_SIZE))
52
- const from = Math.max(0, Math.floor(scrollTop / (ROW_HEIGHT + GAP_SIZE)) - maxVisibleRows * DATA_PADDING)
53
- const to = Math.min(this.data.records?.length || 0, from + maxVisibleRows * (DATA_PADDING * 2 + 1))
54
-
55
- this.from = from
56
- this.to = to
57
- }, THRESHOLD)
58
-
59
- static styles = [
60
- dataGridBodyStyle,
61
- css`
62
- :host {
63
- font-variation-settings: 'FILL' 1;
64
-
65
- overscroll-behavior: none;
66
- user-select: none;
67
- }
68
-
69
- [select-block] {
70
- position: absolute;
71
- left: var(--select-box-left);
72
- top: var(--select-box-top);
73
- width: var(--select-box-width);
74
- height: var(--select-box-height);
75
- border: var(--grid-record-focused-cell-border);
76
- background-image: var(--focused-background-image);
77
- pointer-events: none;
78
-
79
- z-index: 5;
80
- }
81
-
82
- [fixed] {
83
- position: sticky;
84
- background-color: var(--grid-record-background-color);
85
- z-index: 2; /* 고정된 열을 다른 열 위에 표시. */
86
- }
87
-
88
- :host([raised]) [fixed] {
89
- /* 고정 컬럼이 살짝 올라와 있는 느낌을 표현 */
90
- box-shadow: 3px 0 3px rgba(0, 0, 0, 0.1);
91
- }
92
-
93
- ox-grid-accum-field {
94
- position: sticky;
95
- bottom: 0;
96
- z-index: 1;
97
- }
98
-
99
- ox-grid-accum-field[fixed] {
100
- background-color: var(--md-sys-color-primary-container);
101
- }
102
- `
103
- ]
104
-
105
- @property({ type: Object }) config: GristConfig = ZERO_CONFIG
106
- @property({ type: Array }) columns: ColumnConfig[] = []
107
- @property({ type: Object }) data: GristData = ZERO_DATA
108
- @property({ type: Object }) focused: { row: number; column: number } = ZERO_FOCUS
109
- @property({ type: Object }) editTarget: { row: number; column: number; valueWith: string | null } | null = null
110
- @property({ type: Number }) from = -1
111
- @property({ type: Number }) to = -1
112
- @property({ type: Array }) fixedLefts: number[] = []
113
-
114
- @state() _selectBlock?: {
115
- start: DataGridField
116
- end?: DataGridField
117
- }
118
-
119
- @query('[select-block]') selectBlock?: HTMLDivElement
120
- @query('ox-grid-field[focused]') focusedField?: DataGridField
121
-
122
- private _focusedListener?: (e: KeyboardEvent) => void
123
- private _recordView?: any
124
- private _recordViewRow?: number
125
- private _draggable?: boolean
126
-
127
- resetEdit() {
128
- this.editTarget = null
129
- }
130
-
131
- handleOnScroll(e: WheelEvent) {
132
- const { scrollTop, clientHeight } = e.target as HTMLElement
133
- this.debounce(scrollTop, clientHeight)
134
- }
135
-
136
- // issue #13
137
- // renderOptimisticRow() {
138
- // return
139
- // }
140
-
141
- render() {
142
- // block이 선택되어 있으면, focused row/column은 표현하지 않는다.
143
- var { row: focusedRow, column: focusedColumn } = (!this._selectBlock && this.focused) || {}
144
- var { row: editingRow, column: editingColumn, valueWith = null } = this.editTarget || {}
145
-
146
- var columns = this.columns.filter(column => !column.hidden)
147
- var data = this.data
148
- var { records } = data
149
- var { appendable, classifier, accumulator } = this.config.rows
150
- const { start, end } = this._selectBlock || {}
151
-
152
- /*
153
- * 레코드를 추가할 수 있는 경우에는 항상 추가 레코드를 보여준다.
154
- * 만약, 이전 방식처럼, 커서를 옮겨야만 새로운 레코드가 보이게 하고 싶다면, 조건부를 다음의 코드로 대체한다.
155
- * -- if (focusedRow == records.length)
156
- */
157
- if (appendable) {
158
- records = [...records, { __dirty__: '+' }]
159
- }
160
-
161
- if (accumulator) {
162
- var accumRecord = this.buildAccumulatorRecord()
163
- }
164
-
165
- return html`
166
- ${records.map((record, idxRow) => {
167
- var attrFocusedRow = idxRow === focusedRow
168
- var attrSelected = record['__selected__']
169
- var attrOdd = idxRow % 2
170
- var dirtyFields = record['__dirtyfields__'] || {}
171
- var { emphasized } = classifier.call(null, record, idxRow) || {}
172
-
173
- return html`
174
- ${columns.map(
175
- (column, idxColumn) => html`
176
- <ox-grid-field
177
- .data=${data}
178
- .type=${column.type}
179
- .rowIndex=${idxRow}
180
- .columnIndex=${idxColumn}
181
- .column=${column}
182
- .record=${record}
183
- .checked=${record.__selected__ ? 'checked' : record.__check_in_tree__}
184
- .emphasized=${emphasized}
185
- ?gutter=${column.type == 'gutter'}
186
- ?odd=${attrOdd}
187
- ?focused-row=${attrFocusedRow}
188
- ?selected-row=${attrSelected}
189
- ?focused=${idxRow === focusedRow && idxColumn === focusedColumn}
190
- ?editing=${idxRow === editingRow && idxColumn === editingColumn}
191
- .valueWith=${valueWith}
192
- .value=${record[column.name]}
193
- ?dirty=${!!dirtyFields[column.name]}
194
- fixed=${ifDefined(this.fixedLefts[idxColumn])}
195
- ></ox-grid-field>
196
- `
197
- )}
198
- <ox-grid-field
199
- .data=${data}
200
- .rowIndex=${idxRow}
201
- .columnIndex=${-1}
202
- .record=${record}
203
- .emphasized=${emphasized}
204
- ?odd=${attrOdd}
205
- ?focused-row=${attrFocusedRow}
206
- ?selected-row=${attrSelected}
207
- ></ox-grid-field>
208
- `
209
- })}
210
- ${accumulator
211
- ? html`
212
- ${columns.map(
213
- (column, idxColumn) => html`
214
- <ox-grid-accum-field
215
- .data=${data}
216
- .columnIndex=${idxColumn}
217
- .rowIndex=${records.length}
218
- .column=${column}
219
- .record=${accumRecord!}
220
- .value=${accumRecord[column.name]}
221
- fixed=${ifDefined(this.fixedLefts[idxColumn])}
222
- ></ox-grid-accum-field>
223
- `
224
- )}
225
- <ox-grid-accum-field
226
- .data=${data}
227
- .columnIndex=${-1}
228
- .rowIndex=${records.length}
229
- .record=${accumRecord!}
230
- ></ox-grid-accum-field>
231
- `
232
- : nothing}
233
- ${start && end && start !== end ? html` <div select-block></div> ` : html``}
234
- <slot></slot>
235
- `
236
- }
237
-
238
- firstUpdated() {
239
- // TODO issue #13
240
- // this.addEventListener('scroll', this.handleOnScroll.bind(this))
241
-
242
- /* focus() 를 받을 수 있도록 함. */
243
- this.setAttribute('tabindex', '-1')
244
-
245
- /*
246
- * focusout 으로 property를 변경시키는 경우, focusout에 의해 update가 발생하는 경우에는,
247
- * 그리드 내부의 컴포넌트가 갱신되는 현상을 초래하게 된다.
248
- * 따라서, focusout 핸들러에서 update를 유발하는 코드는 강력하게 금지시킨다.
249
- */
250
- this.addEventListener('focusout', e => {
251
- if (this._focusedListener) {
252
- this.removeEventListener('keydown', this._focusedListener)
253
- delete this._focusedListener
254
- }
255
- })
256
-
257
- this.addEventListener('focusin', e => {
258
- if (!this._focusedListener) {
259
- this._focusedListener = dataGridBodyKeydownHandler.bind(this)
260
- this.addEventListener('keydown', this._focusedListener)
261
- }
262
- })
263
-
264
- this.addEventListener('set-select-block', async e => {
265
- e.stopPropagation()
266
-
267
- const { startRow = -1, startColumn = -1, endRow = -1, endColumn = -1 } = ((e as CustomEvent).detail as any) || {}
268
-
269
- const start = this.getFieldByIndex(startRow, startColumn) as DataGridField
270
- const end = this.getFieldByIndex(endRow, endColumn) as DataGridField
271
-
272
- this.setSelectBlock(start, end)
273
- })
274
-
275
- this.renderRoot.addEventListener('contextmenu', (event: Event) => {
276
- const e = event as MouseEvent
277
- this.setSelectBlock()
278
-
279
- this._draggable = false
280
-
281
- var target = (e.target as Element).closest('ox-grid-field') as DataGridField
282
- var { rowIndex, columnIndex } = target || {}
283
-
284
- this.dispatchEvent(
285
- new CustomEvent('focus-change', {
286
- bubbles: true,
287
- composed: true,
288
- detail: {
289
- row: rowIndex,
290
- column: columnIndex
291
- }
292
- })
293
- )
294
- })
295
-
296
- this.renderRoot.addEventListener('pointerdown', (e: Event) => {
297
- this.setSelectBlock()
298
-
299
- if ('buttons' in e && e.buttons !== 1) {
300
- return
301
- }
302
-
303
- e.preventDefault()
304
- e.stopPropagation()
305
-
306
- this._draggable = true
307
-
308
- var target = (e.target as Element).closest('ox-grid-field') as DataGridField
309
- var { rowIndex, columnIndex } = target || {}
310
-
311
- this.dispatchEvent(
312
- new CustomEvent('focus-change', {
313
- bubbles: true,
314
- composed: true,
315
- detail: {
316
- row: rowIndex,
317
- column: columnIndex
318
- }
319
- })
320
- )
321
-
322
- if (columnIndex >= 0 && target.editableOnClick && !isNaN(rowIndex) && !isNaN(columnIndex)) {
323
- this.startEditTarget(rowIndex, columnIndex)
324
- }
325
- })
326
-
327
- this.renderRoot.addEventListener('pointermove', (event: Event) => {
328
- const e = event as MouseEvent
329
- if (('buttons' in e && e.buttons !== 1) || !this._draggable) {
330
- return
331
- }
332
-
333
- e.preventDefault()
334
- e.stopPropagation()
335
-
336
- const field = e.target as DataGridField
337
- if (!this._selectBlock) {
338
- this.setSelectBlock(this.focusedField || field, this.focusedField || field)
339
-
340
- return
341
- }
342
-
343
- var { start, end } = this._selectBlock || {}
344
-
345
- if (start && end !== field) {
346
- end = field
347
-
348
- this.setSelectBlock(start, end)
349
- }
350
- })
351
-
352
- this.renderRoot.addEventListener('pointerup', (event: Event) => {
353
- event.preventDefault()
354
- event.stopPropagation()
355
-
356
- this._draggable = false
357
- })
358
-
359
- this.renderRoot.addEventListener('click', dataGridBodyClickHandler.bind(this))
360
- this.renderRoot.addEventListener('dblclick', dataGridBodyDblclickHandler.bind(this))
361
- this.renderRoot.addEventListener('contextmenu', dataGridBodyContextMenuHandler.bind(this))
362
-
363
- this.addEventListener('focus-change', dataGridBodyFocusChangeHandler.bind(this))
364
-
365
- requestAnimationFrame(() => {
366
- const primaryColor = getComputedStyle(this).getPropertyValue('--md-sys-color-primary')
367
-
368
- this.style.setProperty(
369
- '--focused-background-image',
370
- `url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='10' height='10'><rect fill='${primaryColor}' fill-opacity='0.2' x='0' y='0' width='100%' height='100%'/></svg>")`
371
- )
372
- })
373
-
374
- this.addEventListener('show-record-view', (e: Event) =>
375
- this.popupRecordView((e as CustomEvent).detail as { row: number; record: GristRecord })
376
- )
377
-
378
- this.addEventListener('scroll', function () {
379
- this.scrollLeft == 0 ? this.removeAttribute('raised') : this.setAttribute('raised', '')
380
- })
381
- }
382
-
383
- getFieldByIndex(rowIndex: number, columnIndex: number, residue: boolean = true) {
384
- if (rowIndex < 0) {
385
- return
386
- }
387
-
388
- var columns = this.columns.filter(column => !column.hidden).length
389
- if (!residue && columnIndex >= columns) {
390
- return
391
- }
392
-
393
- return this.renderRoot.children.item(
394
- rowIndex * (columns + 1) /* 1 means last dummy column */ + ((columnIndex + columns) % columns)
395
- ) as DataGridField
396
- }
397
-
398
- startEditTarget(row: number, column: number, valueWith: string | null = null) {
399
- var { editable } = this.columns.filter(column => !column.hidden)[column].record
400
- if (typeof editable === 'function') {
401
- const curRow = this.data.records[row] || {}
402
- const curCol = this.columns[column + 1]
403
- editable = editable.call(this, curRow[curCol.name], curCol, curRow, row, this)
404
- }
405
-
406
- if (!editable) {
407
- return
408
- }
409
-
410
- if (this.editTarget && this.editTarget.row == row && this.editTarget.column == column) {
411
- return
412
- }
413
-
414
- this.editTarget = {
415
- row,
416
- column,
417
- valueWith
418
- }
419
- }
420
-
421
- shouldUpdate(changes: any) {
422
- if (!changes.has('editTarget')) {
423
- /*
424
- * 큰 변화에 대해서는 실제 update가 발생되기 전에 editTarget을 초기화한다.
425
- */
426
- this.editTarget = null
427
- }
428
-
429
- return super.shouldUpdate(changes)
430
- }
431
-
432
- updated(changes: PropertyValues<this>) {
433
- if (changes.has('focused')) {
434
- let element = this.renderRoot?.querySelector('[focused]')
435
- if (!element) {
436
- return
437
- }
438
-
439
- let { top, left } = calcScrollPos(this, element)
440
- // TODO this.scroll()을 사용하면, 효과가 좋으나 left 계산에 문제가 있는 것 같음.
441
- // this.scroll({
442
- // top,
443
- // left,
444
- // behavior: 'smooth'
445
- // })
446
- if (top !== undefined) {
447
- this.scrollTop = top
448
- }
449
- if (left !== undefined) {
450
- this.scrollLeft = left
451
- }
452
- }
453
-
454
- if (this._recordView) {
455
- this._recordView.record = this.data.records[this._recordViewRow!]
456
- }
457
- }
458
-
459
- focus() {
460
- super.focus()
461
-
462
- if (this.focused === ZERO_FOCUS) {
463
- let { records } = this.data
464
- let row = records.findIndex(record => record['__selected__'])
465
-
466
- this.focused = { row: row == -1 ? 0 : row, column: 0 }
467
- }
468
- }
469
-
470
- popupRecordView({ record, row }: { row: number; record: GristRecord }) {
471
- var titleField = this.config.list.fields[0] || 'name'
472
- var title = record[titleField]
473
-
474
- /* field가 오브젝트형인 경우에는 렌더러를 타이틀로 사용한다. */
475
- if (typeof title == 'object') {
476
- var column = this.config.columns.find(column => column.name == titleField)
477
- title = column?.record.renderer(title, column, record, row, this /* cautious */)
478
- }
479
-
480
- this._recordViewRow = row
481
- this._recordView = RecordViewHandler(
482
- this.config.columns,
483
- record,
484
- row,
485
- this,
486
- {
487
- title
488
- },
489
- () => {
490
- delete this._recordView
491
- delete this._recordViewRow
492
- }
493
- )
494
- }
495
-
496
- getSelectedBlockValues(): Array<Array<any>> | any | undefined {
497
- var { start, end } = this._selectBlock || {}
498
-
499
- if (!(start && end)) {
500
- start = this.focusedField
501
-
502
- end = start
503
- }
504
-
505
- if (start && end) {
506
- const startRowIndex = start.rowIndex < end.rowIndex ? start.rowIndex : end.rowIndex
507
- const endRowIndex = start.rowIndex < end.rowIndex ? end.rowIndex : start.rowIndex
508
- const startColumnIndex = start.columnIndex < end.columnIndex ? start.columnIndex : end.columnIndex
509
- const endColumnIndex = start.columnIndex < end.columnIndex ? end.columnIndex : start.columnIndex
510
-
511
- const columnArray = new Array(endColumnIndex - startColumnIndex + 1).fill(startColumnIndex)
512
- const columns = this.columns.filter(column => !column.hidden)
513
-
514
- return (
515
- '<table>' +
516
- new Array(endRowIndex - startRowIndex + 1)
517
- .fill(startRowIndex)
518
- .map((start, index) => {
519
- const rowIndex = start + index
520
- const record = this.data.records[rowIndex]
521
-
522
- const tds = columnArray
523
- .map((start, index) => {
524
- const columnIndex = start + index
525
- const column = columns[columnIndex]
526
- const value = record?.[column.name]
527
- const type = typeof value
528
- const text =
529
- value === undefined || value === null ? '' : type == 'object' ? JSON.stringify(value) : value
530
-
531
- return `<td type=${type}>${text}</td>`
532
- })
533
- .join('')
534
- return `<tr>${tds}</tr>`
535
- })
536
- .join('') +
537
- '</table>'
538
- )
539
- }
540
- }
541
-
542
- async copy() {
543
- const copied = this.getSelectedBlockValues()
544
-
545
- await navigator.clipboard.write([
546
- new ClipboardItem({
547
- 'text/html': new Blob([copied], { type: 'text/html' }),
548
- 'text/plain': new Blob([copied], { type: 'text/plain' })
549
- })
550
- ])
551
-
552
- const selectBlock = this.selectBlock || this.focusedField
553
- if (selectBlock) {
554
- const backgroundColor = selectBlock.style.backgroundColor
555
- const opacity = selectBlock.style.opacity
556
-
557
- selectBlock.setAttribute('data-tooltip', 'copied to clipboard!')
558
- const rect = selectBlock.getBoundingClientRect()
559
- selectBlock.style.setProperty('--tooltip-top', `${rect.top}px`)
560
- selectBlock.style.setProperty('--tooltip-left', `${rect.left}px`)
561
-
562
- selectBlock.style.backgroundColor = 'red'
563
- selectBlock.style.opacity = '0.5'
564
- await sleep(500)
565
- selectBlock.removeAttribute('data-tooltip')
566
- selectBlock.style.backgroundColor = backgroundColor
567
- selectBlock.style.opacity = opacity
568
- }
569
- }
570
-
571
- async paste() {
572
- try {
573
- const selection = window.getSelection()
574
-
575
- const clipboardItems = await navigator.clipboard.read()
576
- if (!clipboardItems) {
577
- return
578
- }
579
-
580
- var type: string | undefined
581
- var content: string | undefined
582
-
583
- for (const clipboardItem of clipboardItems) {
584
- try {
585
- var blob = await clipboardItem.getType('text/html')
586
- content = blob && (await blob.text())
587
- type = 'text/html'
588
- } catch (e) {
589
- try {
590
- blob = await clipboardItem.getType('text/plain')
591
- content = blob && (await blob.text())
592
- type = 'text/plain'
593
- } catch (e) {}
594
- }
595
-
596
- break
597
- }
598
-
599
- if (!content) {
600
- return
601
- }
602
-
603
- const { row, column } = this.focused
604
- const { records } = this.data
605
- const columns = this.columns.filter(column => !column.hidden)
606
-
607
- if (type === 'text/html') {
608
- const div = document.createElement('div')
609
- div.innerHTML = content!.trim()
610
- const table = div.querySelector('table') as HTMLTableElement
611
- if (!table) {
612
- return
613
- }
614
-
615
- if (selection) {
616
- this.resetEdit()
617
- selection.removeAllRanges()
618
-
619
- // 포커스가 빠지기 전에 isWorking으로 focusout 이벤트에 대한 값 변경을 막음
620
- this.focusedField!.isWorking = true
621
- await this.updateComplete
622
- this.focusedField!.isWorking = false
623
- }
624
- const rows = table.querySelectorAll('tr')
625
-
626
- rows.forEach((record, rowIndex) => {
627
- if (!(record instanceof HTMLTableRowElement)) {
628
- return
629
- }
630
-
631
- var targetRecord = records[row + rowIndex] || { __dirty__: '+' }
632
- if (row + rowIndex >= records.length) {
633
- records.push(targetRecord)
634
- }
635
-
636
- const cells = record.querySelectorAll('td')
637
- cells.forEach((item, columnIndex) => {
638
- const targetColumn = columns[column + columnIndex]
639
- var value = item.textContent?.trim() as any
640
- let type = targetColumn.type || item.getAttribute('type') || 'string'
641
- type = type.includes('object') ? 'object' : type // 오브젝트 타입 예외처리
642
- let { editable } = targetColumn.record
643
- if (typeof editable === 'function') {
644
- editable = editable.call(this, value, targetColumn, targetRecord, row, this)
645
- }
646
-
647
- switch (type) {
648
- case 'object':
649
- case 'parameters':
650
- try {
651
- value = JSON.parse(value || 'null')
652
- } catch (err) {}
653
- break
654
- case 'boolean':
655
- case 'checkbox':
656
- value = !!value && !!String(value).match(/true/i)
657
- break
658
- case 'number':
659
- case 'float':
660
- case 'integer':
661
- case 'progress':
662
- value = parseToNumberOrNull(value)
663
- break
664
- default:
665
- try {
666
- value = JSON.parse(value)
667
- } catch (err) {
668
- value = value
669
- }
670
- }
671
-
672
- if (targetColumn && !targetColumn.gutterName && editable) {
673
- this.dispatchEvent(
674
- new CustomEvent('field-change', {
675
- bubbles: true,
676
- composed: true,
677
- detail: {
678
- before: targetRecord[targetColumn.name],
679
- after: value,
680
- column: targetColumn,
681
- record: targetRecord,
682
- row: row + rowIndex
683
- }
684
- })
685
- )
686
- }
687
- })
688
- })
689
-
690
- return
691
- } else if (!selection && type === 'text/plain') {
692
- const targetRecord = records[row] || { __dirty__: '+' }
693
- const targetColumn = columns[column]
694
- let { editable } = targetColumn.record
695
- if (typeof editable === 'function') {
696
- editable = editable.call(this, content, targetColumn, targetRecord, row, this)
697
- }
698
-
699
- if (targetColumn && !targetColumn.gutterName && editable) {
700
- this.dispatchEvent(
701
- new CustomEvent('field-change', {
702
- bubbles: true,
703
- composed: true,
704
- detail: {
705
- before: targetRecord[targetColumn.name],
706
- after: content,
707
- column: targetColumn,
708
- record: targetRecord,
709
- row: row
710
- }
711
- })
712
- )
713
- }
714
- }
715
- } catch (e) {
716
- console.log('e : ', e)
717
- }
718
- }
719
-
720
- setSelectBlock(start?: DataGridField, end?: DataGridField) {
721
- if (start?.columnIndex == -1) {
722
- start = this.getFieldByIndex(start.rowIndex, this.columns.filter(column => !column.hidden).length - 1)
723
- }
724
-
725
- if (end?.columnIndex == -1) {
726
- end = this.getFieldByIndex(end.rowIndex, this.columns.filter(column => !column.hidden).length - 1)
727
- }
728
-
729
- this._selectBlock = start && { start, end }
730
-
731
- if (start && end) {
732
- window.getSelection()?.removeAllRanges()
733
-
734
- if (start !== end) {
735
- const left = start.columnIndex < end.columnIndex ? start : end
736
- const right = left === start ? end : start
737
- const top = start.rowIndex < end.rowIndex ? start : end
738
- const bottom = top === start ? end : start
739
-
740
- const { offsetLeft } = left
741
- const { offsetTop } = top
742
- const width = right.offsetLeft - offsetLeft + right.offsetWidth
743
- const height = bottom.offsetTop - offsetTop + bottom.offsetHeight
744
-
745
- this.style.setProperty('--select-box-left', offsetLeft - 1 + 'px')
746
- this.style.setProperty('--select-box-top', offsetTop - 1 + 'px')
747
- this.style.setProperty('--select-box-width', width + 'px')
748
- this.style.setProperty('--select-box-height', height + 'px')
749
- }
750
-
751
- this.focus()
752
- }
753
- }
754
-
755
- buildAccumulatorRecord(): GristRecord {
756
- var columns = this.columns.filter(column => !column.hidden)
757
-
758
- return columns.reduce((record, column) => {
759
- if (column.accumulator) {
760
- record[column.name] = accumulate(this.data, column, column.accumulator)
761
- }
762
- return record
763
- }, {} as GristRecord)
764
- }
765
- }