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