@operato/data-grist 1.13.2 → 2.0.0-alpha.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 (81) hide show
  1. package/CHANGELOG.md +14 -2
  2. package/dist/src/configure/column-builder.js +3 -2
  3. package/dist/src/configure/column-builder.js.map +1 -1
  4. package/dist/src/configure/zero-config.js +3 -1
  5. package/dist/src/configure/zero-config.js.map +1 -1
  6. package/dist/src/data-grid/data-grid-accum-field.d.ts +1 -0
  7. package/dist/src/data-grid/data-grid-accum-field.js +8 -0
  8. package/dist/src/data-grid/data-grid-accum-field.js.map +1 -1
  9. package/dist/src/data-grid/data-grid-body.js +24 -2
  10. package/dist/src/data-grid/data-grid-body.js.map +1 -1
  11. package/dist/src/data-grid/data-grid-field.d.ts +1 -0
  12. package/dist/src/data-grid/data-grid-field.js +5 -0
  13. package/dist/src/data-grid/data-grid-field.js.map +1 -1
  14. package/dist/src/data-grid/event-handlers/data-grid-body-click-handler.js +2 -2
  15. package/dist/src/data-grid/event-handlers/data-grid-body-click-handler.js.map +1 -1
  16. package/dist/src/data-grid/event-handlers/data-grid-body-contextmenu-handler.d.ts +7 -0
  17. package/dist/src/data-grid/event-handlers/data-grid-body-contextmenu-handler.js +25 -0
  18. package/dist/src/data-grid/event-handlers/data-grid-body-contextmenu-handler.js.map +1 -0
  19. package/dist/src/data-grid/event-handlers/data-grid-body-dblclick-handler.js +2 -2
  20. package/dist/src/data-grid/event-handlers/data-grid-body-dblclick-handler.js.map +1 -1
  21. package/dist/src/data-grid/event-handlers/data-grid-body-focus-change-handler copy.d.ts +7 -0
  22. package/dist/src/data-grid/event-handlers/data-grid-body-focus-change-handler copy.js +19 -0
  23. package/dist/src/data-grid/event-handlers/data-grid-body-focus-change-handler copy.js.map +1 -0
  24. package/dist/src/data-grid/event-handlers/data-grid-body-focus-change-handler.js +2 -2
  25. package/dist/src/data-grid/event-handlers/data-grid-body-focus-change-handler.js.map +1 -1
  26. package/dist/src/data-manipulator.d.ts +3 -1
  27. package/dist/src/data-manipulator.js +20 -7
  28. package/dist/src/data-manipulator.js.map +1 -1
  29. package/dist/src/editors/ox-grist-editor-tree.d.ts +6 -0
  30. package/dist/src/editors/ox-grist-editor-tree.js +27 -0
  31. package/dist/src/editors/ox-grist-editor-tree.js.map +1 -0
  32. package/dist/src/editors/ox-grist-editor.d.ts +1 -0
  33. package/dist/src/editors/ox-grist-editor.js +3 -0
  34. package/dist/src/editors/ox-grist-editor.js.map +1 -1
  35. package/dist/src/editors/ox-input-tree.d.ts +20 -0
  36. package/dist/src/editors/ox-input-tree.js +221 -0
  37. package/dist/src/editors/ox-input-tree.js.map +1 -0
  38. package/dist/src/editors/registry.js +3 -1
  39. package/dist/src/editors/registry.js.map +1 -1
  40. package/dist/src/handlers/contextmenu-tree-mutation.d.ts +3 -0
  41. package/dist/src/handlers/contextmenu-tree-mutation.js +82 -0
  42. package/dist/src/handlers/contextmenu-tree-mutation.js.map +1 -0
  43. package/dist/src/handlers/contextmenu-tree.d.ts +3 -0
  44. package/dist/src/handlers/contextmenu-tree.js +30 -0
  45. package/dist/src/handlers/contextmenu-tree.js.map +1 -0
  46. package/dist/src/handlers/move-up copy.d.ts +3 -0
  47. package/dist/src/handlers/move-up copy.js +26 -0
  48. package/dist/src/handlers/move-up copy.js.map +1 -0
  49. package/dist/src/handlers/registry.js +3 -1
  50. package/dist/src/handlers/registry.js.map +1 -1
  51. package/dist/src/renderers/ox-grist-renderer-tree.d.ts +1 -0
  52. package/dist/src/renderers/ox-grist-renderer-tree.js +5 -2
  53. package/dist/src/renderers/ox-grist-renderer-tree.js.map +1 -1
  54. package/dist/src/types.d.ts +2 -1
  55. package/dist/src/types.js.map +1 -1
  56. package/dist/stories/tree-column-with-checkbox.stories.js +8 -3
  57. package/dist/stories/tree-column-with-checkbox.stories.js.map +1 -1
  58. package/dist/stories/tree-column.stories.js +8 -3
  59. package/dist/stories/tree-column.stories.js.map +1 -1
  60. package/dist/tsconfig.tsbuildinfo +1 -1
  61. package/package.json +8 -8
  62. package/src/configure/column-builder.ts +3 -2
  63. package/src/configure/zero-config.ts +3 -1
  64. package/src/data-grid/data-grid-accum-field.ts +7 -0
  65. package/src/data-grid/data-grid-body.ts +30 -2
  66. package/src/data-grid/data-grid-field.ts +6 -0
  67. package/src/data-grid/event-handlers/data-grid-body-click-handler.ts +2 -2
  68. package/src/data-grid/event-handlers/data-grid-body-contextmenu-handler.ts +32 -0
  69. package/src/data-grid/event-handlers/data-grid-body-dblclick-handler.ts +2 -2
  70. package/src/data-grid/event-handlers/data-grid-body-focus-change-handler.ts +2 -2
  71. package/src/data-manipulator.ts +25 -8
  72. package/src/editors/ox-grist-editor-tree.ts +27 -0
  73. package/src/editors/ox-grist-editor.ts +4 -0
  74. package/src/editors/ox-input-tree.ts +226 -0
  75. package/src/editors/registry.ts +3 -1
  76. package/src/handlers/contextmenu-tree-mutation.ts +98 -0
  77. package/src/handlers/registry.ts +3 -1
  78. package/src/renderers/ox-grist-renderer-tree.ts +6 -2
  79. package/src/types.ts +3 -1
  80. package/stories/tree-column-with-checkbox.stories.ts +8 -3
  81. package/stories/tree-column.stories.ts +8 -3
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@operato/data-grist",
3
- "version": "1.13.2",
3
+ "version": "2.0.0-alpha.2",
4
4
  "description": "User interface for grid (desktop) and list (mobile)",
5
5
  "author": "heartyoh",
6
6
  "main": "dist/index.js",
@@ -56,12 +56,12 @@
56
56
  },
57
57
  "dependencies": {
58
58
  "@material/mwc-icon": "^0.27.0",
59
- "@operato/headroom": "^1.4.64",
60
- "@operato/input": "^1.12.7",
61
- "@operato/popup": "^1.12.7",
62
- "@operato/pull-to-refresh": "^1.4.64",
63
- "@operato/styles": "^1.12.3",
64
- "@operato/utils": "^1.12.7",
59
+ "@operato/headroom": "^2.0.0-alpha.0",
60
+ "@operato/input": "^2.0.0-alpha.2",
61
+ "@operato/popup": "^2.0.0-alpha.2",
62
+ "@operato/pull-to-refresh": "^2.0.0-alpha.0",
63
+ "@operato/styles": "^2.0.0-alpha.0",
64
+ "@operato/utils": "^2.0.0-alpha.0",
65
65
  "i18next": "^21.5.4",
66
66
  "json5": "^2.2.0",
67
67
  "lit": "^2.5.0",
@@ -100,5 +100,5 @@
100
100
  "prettier --write"
101
101
  ]
102
102
  },
103
- "gitHead": "dc07eae72667c595a84c2c08c3ea9e6f3b0e2ee6"
103
+ "gitHead": "b940cc5dd44c22458ec03189e6bc8dae7f04f32e"
104
104
  }
@@ -100,12 +100,13 @@ export const buildColumn = (column: any): ColumnConfig => {
100
100
  }
101
101
 
102
102
  /* handler */
103
- var { click, dblclick, focus } = handlers || {}
103
+ var { click, dblclick, focus, contextmenu } = handlers || {}
104
104
 
105
105
  compiled.handlers = {
106
106
  click: click && getGristEventHandler(click),
107
107
  dblclick: dblclick && getGristEventHandler(dblclick),
108
- focus: focus && getGristEventHandler(focus)
108
+ focus: focus && getGristEventHandler(focus),
109
+ contextmenu: contextmenu && getGristEventHandler(contextmenu)
109
110
  }
110
111
 
111
112
  return compiled
@@ -18,7 +18,9 @@ export const ZERO_FIELD_RENDERER: FieldRenderer = (value, column, record, rowInd
18
18
 
19
19
  export const ZERO_EVENTHANDLERSET: GristEventHandlerSet = {
20
20
  click: undefined,
21
- dblclick: undefined
21
+ dblclick: undefined,
22
+ focus: undefined,
23
+ contextmenu: undefined
22
24
  }
23
25
  export const ZERO_COLUMNS: ColumnConfig[] = []
24
26
  export const ZERO_GROUPS: GroupConfig[] = []
@@ -58,6 +58,7 @@ export class DataGridAccumField extends LitElement {
58
58
  @property({ type: Boolean }) editing = false
59
59
  @property({ type: Object }) value = {}
60
60
  @property({ attribute: false }) emphasized: any = false
61
+ @property({ type: String }) fixed?: string
61
62
 
62
63
  render(): TemplateResult {
63
64
  if (!this.column || !this.column.accumulator) {
@@ -90,5 +91,11 @@ export class DataGridAccumField extends LitElement {
90
91
  this.style.setProperty('--data-grid-field-text-align', align)
91
92
  }
92
93
  }
94
+
95
+ if (changes.has('fixed')) {
96
+ if (this.fixed) {
97
+ this.style.left = this.fixed + 'px'
98
+ }
99
+ }
93
100
  }
94
101
  }
@@ -18,6 +18,7 @@ import { DataGridField } from './data-grid-field'
18
18
  import { dataGridBodyClickHandler } from './event-handlers/data-grid-body-click-handler'
19
19
  import { dataGridBodyDblclickHandler } from './event-handlers/data-grid-body-dblclick-handler'
20
20
  import { dataGridBodyFocusChangeHandler } from './event-handlers/data-grid-body-focus-change-handler'
21
+ import { dataGridBodyContextMenuHandler } from './event-handlers/data-grid-body-contextmenu-handler'
21
22
  import { dataGridBodyKeydownHandler } from './event-handlers/data-grid-body-keydown-handler'
22
23
  import { accumulate } from '../accumulator/accumulator'
23
24
 
@@ -75,7 +76,7 @@ export class DataGridBody extends LitElement {
75
76
  [fixed] {
76
77
  position: sticky;
77
78
  background-color: var(--grid-record-background-color);
78
- z-index: 1; /* 고정된 열을 다른 열 위에 표시. */
79
+ z-index: 2; /* 고정된 열을 다른 열 위에 표시. */
79
80
  }
80
81
 
81
82
  :host([raised]) [fixed] {
@@ -88,6 +89,10 @@ export class DataGridBody extends LitElement {
88
89
  bottom: 0;
89
90
  z-index: 1;
90
91
  }
92
+
93
+ ox-grid-accum-field[fixed] {
94
+ background-color: var(--grid-accum-record-background-color, #ccc);
95
+ }
91
96
  `
92
97
  ]
93
98
 
@@ -208,6 +213,7 @@ export class DataGridBody extends LitElement {
208
213
  .column=${column}
209
214
  .record=${accumRecord!}
210
215
  .value=${accumRecord[column.name]}
216
+ fixed=${ifDefined(this.fixedLefts[idxColumn])}
211
217
  ></ox-grid-accum-field>
212
218
  `
213
219
  )}
@@ -261,6 +267,27 @@ export class DataGridBody extends LitElement {
261
267
  this.setSelectBlock(start, end)
262
268
  })
263
269
 
270
+ this.renderRoot.addEventListener('contextmenu', (event: Event) => {
271
+ const e = event as MouseEvent
272
+ this.setSelectBlock()
273
+
274
+ this._draggable = false
275
+
276
+ var target = (e.target as Element).closest('ox-grid-field') as DataGridField
277
+ var { rowIndex, columnIndex } = target || {}
278
+
279
+ this.dispatchEvent(
280
+ new CustomEvent('focus-change', {
281
+ bubbles: true,
282
+ composed: true,
283
+ detail: {
284
+ row: rowIndex,
285
+ column: columnIndex
286
+ }
287
+ })
288
+ )
289
+ })
290
+
264
291
  this.renderRoot.addEventListener('mousedown', (event: Event) => {
265
292
  const e = event as MouseEvent
266
293
  this.setSelectBlock()
@@ -285,7 +312,7 @@ export class DataGridBody extends LitElement {
285
312
  })
286
313
  )
287
314
 
288
- if (!isNaN(rowIndex) && !isNaN(columnIndex)) {
315
+ if (columnIndex >= 0 && target.editableOnClick && !isNaN(rowIndex) && !isNaN(columnIndex)) {
289
316
  this.startEditTarget(rowIndex, columnIndex)
290
317
  }
291
318
  })
@@ -318,6 +345,7 @@ export class DataGridBody extends LitElement {
318
345
 
319
346
  this.renderRoot.addEventListener('click', dataGridBodyClickHandler.bind(this))
320
347
  this.renderRoot.addEventListener('dblclick', dataGridBodyDblclickHandler.bind(this))
348
+ this.renderRoot.addEventListener('contextmenu', dataGridBodyContextMenuHandler.bind(this))
321
349
 
322
350
  this.addEventListener('focus-change', dataGridBodyFocusChangeHandler.bind(this))
323
351
 
@@ -173,4 +173,10 @@ export class DataGridField extends LitElement {
173
173
  this.removeAttribute('emphasized-row')
174
174
  }
175
175
  }
176
+
177
+ get editableOnClick() {
178
+ const renderer = this.renderRoot.firstElementChild as HTMLElement
179
+ //@ts-ignore
180
+ return renderer && 'editableOnClick' in renderer ? renderer.editableOnClick : true
181
+ }
176
182
  }
@@ -56,10 +56,10 @@ export function dataGridBodyClickHandler(this: DataGridBody, e: Event): void {
56
56
  /* do column click handler */
57
57
  if (column) {
58
58
  var { click } = column.handlers
59
- click && click(this.columns, this.data, column, record, rowIndex, target)
59
+ click && click(this.columns, this.data, column, record, rowIndex, target, e)
60
60
  }
61
61
 
62
62
  /* do rows click handler */
63
63
  var { click: rowsClick } = this.config.rows.handlers
64
- rowsClick && rowsClick(this.columns, this.data, column, record, rowIndex, target)
64
+ rowsClick && rowsClick(this.columns, this.data, column, record, rowIndex, target, e)
65
65
  }
@@ -0,0 +1,32 @@
1
+ import { DataGridBody } from '../data-grid-body'
2
+ import { DataGridField } from '../data-grid-field'
3
+
4
+ /**
5
+ * ox-grid-body 의 focus-change handler
6
+ *
7
+ * - handler의 this 는 ox-grid-body임.
8
+ */
9
+ export async function dataGridBodyContextMenuHandler(this: DataGridBody, e: Event): Promise<void> {
10
+ e.stopPropagation()
11
+
12
+ if (this.editTarget) {
13
+ /* editTarget이 새로 설정되지 않았다면, 이후 기능이 실행된다. */
14
+ return
15
+ }
16
+
17
+ /* target should be 'ox-grid-field' */
18
+ var target = (e.target as Element).closest('ox-grid-field') as DataGridField
19
+ var { column, record, rowIndex, columnIndex } = target || {}
20
+
21
+ var { column, record } = target || {}
22
+
23
+ /* do column contextmenu handler */
24
+ if (column) {
25
+ var { contextmenu } = column.handlers
26
+ contextmenu && contextmenu(this.columns, this.data, column, record, rowIndex, target, e)
27
+ }
28
+
29
+ /* do rows contextmenu handler */
30
+ var { contextmenu: rowsContextMenu } = this.config.rows.handlers
31
+ rowsContextMenu && rowsContextMenu(this.columns, this.data, column, record, rowIndex, target, e)
32
+ }
@@ -33,10 +33,10 @@ export async function dataGridBodyDblclickHandler(this: DataGridBody, e: Event):
33
33
  /* do column dblclick handler */
34
34
  if (column) {
35
35
  var { dblclick } = column.handlers
36
- dblclick && dblclick(this.columns, this.data, column, record, rowIndex, target)
36
+ dblclick && dblclick(this.columns, this.data, column, record, rowIndex, target, e)
37
37
  }
38
38
 
39
39
  /* do rows dblclick handler */
40
40
  var { dblclick: rowsDblclick } = this.config.rows.handlers
41
- rowsDblclick && rowsDblclick(this.columns, this.data, column, record, rowIndex, target)
41
+ rowsDblclick && rowsDblclick(this.columns, this.data, column, record, rowIndex, target, e)
42
42
  }
@@ -15,10 +15,10 @@ export async function dataGridBodyFocusChangeHandler(this: DataGridBody, e: Even
15
15
  /* do column focus handler */
16
16
  if (column) {
17
17
  var { focus } = column.handlers
18
- focus && focus(this.columns, this.data, column, record, rowIndex, target)
18
+ focus && focus(this.columns, this.data, column, record, rowIndex, target, e)
19
19
  }
20
20
 
21
21
  /* do rows focus handler */
22
22
  var { focus: rowsFocus } = this.config.rows.handlers
23
- rowsFocus && rowsFocus(this.columns, this.data, column, record, rowIndex, target)
23
+ rowsFocus && rowsFocus(this.columns, this.data, column, record, rowIndex, target, e)
24
24
  }
@@ -15,8 +15,8 @@ import {
15
15
  export class DataManipulator extends LitElement {
16
16
  @property({ type: Object }) config: GristConfig = ZERO_CONFIG
17
17
  @property({ type: Object }) data: GristData = ZERO_DATA
18
- @property({ type: Object }) sorters: SortersConfig = []
19
- @property({ type: Object }) filters: FilterValue[] = []
18
+ @property({ type: Array }) sorters: SortersConfig = []
19
+ @property({ type: Array }) filters: FilterValue[] = []
20
20
  @property({ type: Object }) pagination: PaginationConfig = {}
21
21
 
22
22
  constructor() {
@@ -64,6 +64,8 @@ export class DataManipulator extends LitElement {
64
64
  })
65
65
 
66
66
  /* tree processing */
67
+ this.addEventListener('collapse-all', (e: Event) => this.onCollapseAll(e as CustomEvent))
68
+ this.addEventListener('expand-all', (e: Event) => this.onExpandAll(e as CustomEvent))
67
69
  this.addEventListener('collapsed', (e: Event) => this.onCollapsed(e as CustomEvent))
68
70
  this.addEventListener('expanded', (e: Event) => this.onExpanded(e as CustomEvent))
69
71
  this.addEventListener('check-in-tree', (e: Event) => this.onCheckInTree(e as CustomEvent))
@@ -213,6 +215,14 @@ export class DataManipulator extends LitElement {
213
215
  this.requestUpdate()
214
216
  }
215
217
 
218
+ onCollapseAll(e: CustomEvent) {
219
+ this.refresh(false)
220
+ }
221
+
222
+ onExpandAll(e: CustomEvent) {
223
+ this.refresh(true)
224
+ }
225
+
216
226
  onCollapsed(e: CustomEvent) {
217
227
  const record = e.detail
218
228
  record.__expanded__ = false
@@ -228,8 +238,6 @@ export class DataManipulator extends LitElement {
228
238
  }
229
239
 
230
240
  onCheckInTree(e: CustomEvent) {
231
- const self = this
232
-
233
241
  function walkTreeCheckedUpdate(record: GristRecord, checked: 'checked' | 'unchecked') {
234
242
  const children = record.__children__
235
243
 
@@ -287,7 +295,7 @@ export class DataManipulator extends LitElement {
287
295
  * Therefore, it will be deprecated.
288
296
  * @method
289
297
  */
290
- refresh() {
298
+ refresh(forceExpandOrCollapse?: boolean) {
291
299
  /*
292
300
  - TODO 여기에서 TREE 형태 데이터의 접고, 펴는 것을 재구성한다.
293
301
  - 동적으로 서브항목을 fetch 하는 기능은 제공하지 않는다.
@@ -302,15 +310,24 @@ export class DataManipulator extends LitElement {
302
310
  ) /* __depth__ 가 설정되지 않았거나, 0 인 경우만 수집 */
303
311
  this.data = {
304
312
  ...this.data,
305
- records: ([] as GristRecord[]).concat(...toplevelRecords.map(record => this.traverseRefresh(record)))
313
+ records: ([] as GristRecord[]).concat(
314
+ ...toplevelRecords.map(record => this.traverseRefresh(record, forceExpandOrCollapse))
315
+ )
306
316
  }
307
317
  }
308
318
 
309
- private traverseRefresh(record: GristRecord): GristRecord[] {
319
+ private traverseRefresh(record: GristRecord, forceExpandOrCollapse?: boolean): GristRecord[] {
320
+ if (forceExpandOrCollapse !== undefined) {
321
+ record = {
322
+ ...record,
323
+ __expanded__: forceExpandOrCollapse
324
+ }
325
+ }
326
+
310
327
  const { __expanded__, __children__ = [] } = record
311
328
 
312
329
  if (__expanded__ && __children__.length > 0) {
313
- return [record].concat(...__children__.map(child => this.traverseRefresh(child)))
330
+ return [record].concat(...__children__.map(child => this.traverseRefresh(child, forceExpandOrCollapse)))
314
331
  } else {
315
332
  return [record]
316
333
  }
@@ -0,0 +1,27 @@
1
+ import './ox-input-tree'
2
+
3
+ import { html, css } from 'lit'
4
+ import { customElement, query } from 'lit/decorators.js'
5
+
6
+ import { OxGristEditor } from './ox-grist-editor.js'
7
+
8
+ @customElement('ox-grist-editor-tree')
9
+ export class OxGristEditorTree extends OxGristEditor {
10
+ static styles = [
11
+ css`
12
+ :host {
13
+ flex: 1;
14
+ }
15
+
16
+ ox-input-tree {
17
+ flex: 1;
18
+ }
19
+ `
20
+ ]
21
+
22
+ get editorTemplate() {
23
+ var { selectable } = this.column.record.options || {}
24
+
25
+ return html`<ox-input-tree .value=${this.value} .record=${this.record} ?selectable=${selectable}></ox-input-tree>`
26
+ }
27
+ }
@@ -91,6 +91,10 @@ export class OxGristEditor extends LitElement {
91
91
  return this.renderRoot.firstElementChild as HTMLElement
92
92
  }
93
93
 
94
+ get directEditable() {
95
+ return true
96
+ }
97
+
94
98
  async firstUpdated() {
95
99
  this.renderRoot.addEventListener('change', this._onchange.bind(this))
96
100
  this.renderRoot.addEventListener('focusout', this._onfocusout.bind(this))
@@ -0,0 +1,226 @@
1
+ /**
2
+ * @license Copyright © HatioLab Inc. All rights reserved.
3
+ */
4
+
5
+ import { PropertyValues, css, html, nothing } from 'lit'
6
+ import { customElement, property, query, state } from 'lit/decorators.js'
7
+ import { ifDefined } from 'lit/directives/if-defined.js'
8
+
9
+ import { OxFormField } from '@operato/input'
10
+ import { GristRecord } from '../types'
11
+
12
+ @customElement('ox-input-tree')
13
+ export class OxInputTree extends OxFormField {
14
+ static styles = css`
15
+ :host {
16
+ overflow: hidden;
17
+ }
18
+
19
+ div[wrap] {
20
+ flex: 1;
21
+
22
+ position: relative;
23
+
24
+ display: flex;
25
+ align-items: center;
26
+ gap: 6px;
27
+
28
+ padding-left: calc(var(--tree-depth, 0) * 18px);
29
+ }
30
+
31
+ span[expander] {
32
+ display: inline-block;
33
+ vertical-align: middle;
34
+ width: 12px;
35
+ height: 20px;
36
+ cursor: pointer;
37
+ position: relative;
38
+ }
39
+
40
+ span[expander][collapsed]::before {
41
+ position: absolute;
42
+ top: 50%;
43
+ left: 50%;
44
+ transform: translate(-25%, -50%) rotate(-90deg);
45
+ content: ' ';
46
+ border: 5px solid transparent;
47
+ border-top: 5px solid var(--primary-color, #1890ff);
48
+ }
49
+
50
+ span[expander][expanded]::before {
51
+ position: absolute;
52
+ top: 50%;
53
+ left: 50%;
54
+ transform: translate(-50%, -25%);
55
+ content: ' ';
56
+ border: 5px solid transparent;
57
+ border-top: 5px solid var(--primary-color, #1890ff);
58
+ }
59
+
60
+ span[checkbox] {
61
+ display: inline-block;
62
+ vertical-align: middle;
63
+ width: 12px;
64
+ height: 20px;
65
+ cursor: pointer;
66
+ position: relative;
67
+ }
68
+
69
+ span[checkbox]::before {
70
+ cursor: pointer;
71
+ position: absolute;
72
+ top: 50%;
73
+ left: 50%;
74
+ transform: translate(-50%, -50%);
75
+ content: ' ';
76
+ display: block;
77
+ width: 10px;
78
+ height: 10px;
79
+ border: 1px solid var(--primary-color, #1890ff);
80
+ border-radius: 2px;
81
+ }
82
+
83
+ span[checkbox][checked='checked']::before {
84
+ background-color: var(--primary-color, #1890ff);
85
+ border-color: var(--primary-color, #1890ff);
86
+ }
87
+
88
+ span[checkbox][checked='checked']::after {
89
+ position: absolute;
90
+ content: ' ';
91
+ display: block;
92
+ top: 50%;
93
+ left: 50%;
94
+ width: 3px;
95
+ height: 7px;
96
+ border: 2px solid #fff;
97
+ border-top: none;
98
+ border-left: none;
99
+ -webkit-transform: translate(-50%, -50%) rotate(45deg);
100
+ -ms-transform: translate(-50%, -50%) rotate(45deg);
101
+ transform: translate(-50%, -50%) rotate(45deg);
102
+ }
103
+
104
+ span[checkbox][checked='half-checked']::before {
105
+ background-color: var(--primary-color, #1890ff);
106
+ border-color: var(--primary-color, #1890ff);
107
+ }
108
+
109
+ span[checkbox][checked='half-checked']::after {
110
+ position: absolute;
111
+ content: ' ';
112
+ display: block;
113
+ top: 50%;
114
+ left: 50%;
115
+ transform: translate(-50%, -50%);
116
+ width: 10px;
117
+ height: 2px;
118
+ background-color: #fff;
119
+ }
120
+
121
+ span[label] {
122
+ flex: 1;
123
+ }
124
+
125
+ input {
126
+ width: 100%;
127
+ height: 100%;
128
+ border: 0;
129
+ background-color: transparent;
130
+ box-sizing: border-box;
131
+ }
132
+
133
+ input:focus {
134
+ outline: none;
135
+ }
136
+ `
137
+
138
+ @property({ type: Object }) record!: GristRecord
139
+ @property({ type: Boolean }) selectable?: boolean
140
+
141
+ @state() private checked?: 'checked' | 'half-checked' | 'unchecked'
142
+ @state() private expanded?: boolean = false
143
+
144
+ @query('input') input!: HTMLInputElement
145
+
146
+ render() {
147
+ var { __children__ } = this.record
148
+
149
+ const expandable = __children__ && __children__.length > 0
150
+
151
+ return html`
152
+ <div wrap>
153
+ ${expandable
154
+ ? html`
155
+ <span
156
+ expander
157
+ @click=${this.onClickExpander.bind(this)}
158
+ ?expanded=${this.expanded}
159
+ ?collapsed=${!this.expanded}
160
+ ></span>
161
+ `
162
+ : html`<span expander></span>`}
163
+ ${this.selectable
164
+ ? html` <span checkbox @click=${this.onClickCheckbox.bind(this)} checked=${ifDefined(this.checked)}></span>`
165
+ : nothing}
166
+
167
+ <span label
168
+ ><input
169
+ value=${ifDefined(this.value)}
170
+ @change=${(e: Event) => {
171
+ e.stopPropagation()
172
+ this.value = (e.target as HTMLInputElement).value
173
+ this.dispatchEvent(new CustomEvent('change', { bubbles: true, composed: true }))
174
+ }}
175
+ focus
176
+ /></span>
177
+
178
+ <!-- <span label>${this.value}</span> -->
179
+ </div>
180
+ `
181
+ }
182
+
183
+ updated(changes: PropertyValues<this>) {
184
+ var { __depth__, __check_in_tree__, __expanded__ } = this.record
185
+ this.checked = __check_in_tree__
186
+ this.expanded = __expanded__
187
+
188
+ this.style.setProperty('--tree-depth', String(__depth__))
189
+ }
190
+
191
+ focus() {
192
+ this.input.focus()
193
+ }
194
+
195
+ select() {
196
+ this.input.select()
197
+ }
198
+
199
+ onClickCheckbox(e: MouseEvent) {
200
+ e.stopPropagation()
201
+
202
+ this.dispatchEvent(
203
+ new CustomEvent('check-in-tree', {
204
+ bubbles: true,
205
+ composed: true,
206
+ detail: this.record
207
+ })
208
+ )
209
+
210
+ this.requestUpdate()
211
+ }
212
+
213
+ onClickExpander(e: MouseEvent) {
214
+ e.stopPropagation()
215
+
216
+ this.dispatchEvent(
217
+ new CustomEvent(this.record.__expanded__ ? 'collapsed' : 'expanded', {
218
+ bubbles: true,
219
+ composed: true,
220
+ detail: this.record
221
+ })
222
+ )
223
+
224
+ this.requestUpdate()
225
+ }
226
+ }
@@ -17,6 +17,7 @@ import { OxGristEditorTel } from './ox-grist-editor-tel'
17
17
  import { OxGristEditorText } from './ox-grist-editor-text'
18
18
  import { OxGristEditorTextarea } from './ox-grist-editor-textarea'
19
19
  import { OxGristEditorTime } from './ox-grist-editor-time'
20
+ import { OxGristEditorTree } from './ox-grist-editor-tree'
20
21
  import { OxGristEditorWeek } from './ox-grist-editor-week'
21
22
 
22
23
  var EDITORS: { [name: string]: { new (): OxGristEditor } } = {
@@ -42,7 +43,8 @@ var EDITORS: { [name: string]: { new (): OxGristEditor } } = {
42
43
  link: OxGristEditorText,
43
44
  image: OxGristEditorImage,
44
45
  file: OxGristEditorFile,
45
- 'string[]': OxGristEditorMultipleSelect
46
+ 'string[]': OxGristEditorMultipleSelect,
47
+ tree: OxGristEditorTree
46
48
  }
47
49
 
48
50
  export function registerEditor(type: string, editor: { new (): OxGristEditor }) {