@operato/gantt 8.0.0-alpha.33 → 8.0.0-alpha.36
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 +18 -0
- package/dist/src/gantt-chart-controller.d.ts +4 -11
- package/dist/src/gantt-chart-controller.js +72 -137
- package/dist/src/gantt-chart-controller.js.map +1 -1
- package/dist/src/ox-gantt-chart.d.ts +368 -2
- package/dist/src/ox-gantt-chart.js +40 -78
- package/dist/src/ox-gantt-chart.js.map +1 -1
- package/dist/stories/ox-gantt.stories.js +1 -1
- package/dist/stories/ox-gantt.stories.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +2 -2
- package/src/gantt-chart-controller.ts +71 -139
- package/src/ox-gantt-chart.ts +36 -77
- package/stories/ox-gantt.stories.ts +1 -1
|
@@ -4,6 +4,7 @@ import { TimeScale, Task, TaskTag } from './types'
|
|
|
4
4
|
import { OxTaskTooltip } from './ox-task-tooltip'
|
|
5
5
|
import { drawTimeScaleHeader, getScale, getChartWidth } from './draw-time-scale-header'
|
|
6
6
|
import { levelDown, levelUp } from './utils'
|
|
7
|
+
import { GestureEventPinch, GestureEventDoubleTap, GestureEventDrag } from '@operato/utils/gesture-mixin.js'
|
|
7
8
|
|
|
8
9
|
export class GanttChartController implements ReactiveController {
|
|
9
10
|
private host: (ReactiveControllerHost & LitElement) | null
|
|
@@ -12,14 +13,6 @@ export class GanttChartController implements ReactiveController {
|
|
|
12
13
|
private totalDays: number
|
|
13
14
|
private _timeScale?: d3.ScaleTime<number, number, never>
|
|
14
15
|
|
|
15
|
-
private touchStartX: number | null = null
|
|
16
|
-
private touchStartY: number | null = null
|
|
17
|
-
private scrollStartX: number | null = null
|
|
18
|
-
private scrollStartY: number | null = null
|
|
19
|
-
private lastTouchDistance?: number | null
|
|
20
|
-
private draggingThreshold: number = 20
|
|
21
|
-
private dragging: boolean = false
|
|
22
|
-
|
|
23
16
|
private colorProvider?: (task: Task) => string
|
|
24
17
|
|
|
25
18
|
public baseWidth: number = 30
|
|
@@ -199,17 +192,7 @@ export class GanttChartController implements ReactiveController {
|
|
|
199
192
|
.attr('stroke-width', 1)
|
|
200
193
|
|
|
201
194
|
taskBar
|
|
202
|
-
.on('click',
|
|
203
|
-
this.selectedTaskId = task.id
|
|
204
|
-
const customEvent = new CustomEvent('task-clicked', {
|
|
205
|
-
detail: { taskId: task.id },
|
|
206
|
-
bubbles: true,
|
|
207
|
-
composed: true
|
|
208
|
-
})
|
|
209
|
-
this.host?.dispatchEvent(customEvent)
|
|
210
|
-
this.host?.requestUpdate()
|
|
211
|
-
})
|
|
212
|
-
.on('touchstart', e => {
|
|
195
|
+
.on('click', e => {
|
|
213
196
|
this.selectedTaskId = task.id
|
|
214
197
|
const customEvent = new CustomEvent('task-clicked', {
|
|
215
198
|
detail: { taskId: task.id },
|
|
@@ -236,17 +219,7 @@ export class GanttChartController implements ReactiveController {
|
|
|
236
219
|
.attr('fill', 'lightgreen')
|
|
237
220
|
|
|
238
221
|
progressBar
|
|
239
|
-
.on('click',
|
|
240
|
-
this.selectedTaskId = task.id
|
|
241
|
-
const customEvent = new CustomEvent('task-clicked', {
|
|
242
|
-
detail: { taskId: task.id },
|
|
243
|
-
bubbles: true,
|
|
244
|
-
composed: true
|
|
245
|
-
})
|
|
246
|
-
this.host?.dispatchEvent(customEvent)
|
|
247
|
-
this.host?.requestUpdate()
|
|
248
|
-
})
|
|
249
|
-
.on('touchstart', e => {
|
|
222
|
+
.on('click', e => {
|
|
250
223
|
this.selectedTaskId = task.id
|
|
251
224
|
const customEvent = new CustomEvent('task-clicked', {
|
|
252
225
|
detail: { taskId: task.id },
|
|
@@ -317,6 +290,8 @@ export class GanttChartController implements ReactiveController {
|
|
|
317
290
|
event.preventDefault()
|
|
318
291
|
event.stopPropagation()
|
|
319
292
|
|
|
293
|
+
const { offsetHeight } = container
|
|
294
|
+
|
|
320
295
|
const deltaModeFactor =
|
|
321
296
|
event.deltaMode === WheelEvent.DOM_DELTA_LINE
|
|
322
297
|
? 16
|
|
@@ -328,7 +303,8 @@ export class GanttChartController implements ReactiveController {
|
|
|
328
303
|
|
|
329
304
|
// 가로 스크롤 및 세로 스크롤 모두 처리
|
|
330
305
|
container.scrollLeft += deltaX
|
|
331
|
-
container.scrollTop += deltaY
|
|
306
|
+
// container.scrollTop += deltaY
|
|
307
|
+
container.scrollTop = Math.min(this.tasks.length * 30 - (offsetHeight - 40), container.scrollTop + deltaY)
|
|
332
308
|
|
|
333
309
|
const customEvent = new CustomEvent('scroll', {
|
|
334
310
|
detail: {
|
|
@@ -342,129 +318,85 @@ export class GanttChartController implements ReactiveController {
|
|
|
342
318
|
this.host?.dispatchEvent(customEvent)
|
|
343
319
|
}
|
|
344
320
|
|
|
345
|
-
|
|
346
|
-
event.
|
|
347
|
-
event.stopPropagation()
|
|
321
|
+
handlePinchForBody = (event: GestureEventPinch) => {
|
|
322
|
+
const { scale } = event.detail
|
|
348
323
|
|
|
349
|
-
|
|
350
|
-
const x = touch ? event.touches[0].clientX : event.clientX
|
|
351
|
-
const y = touch ? event.touches[0].clientY : event.clientY
|
|
324
|
+
this.baseWidth *= scale
|
|
352
325
|
|
|
353
|
-
this.
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
326
|
+
if (this.baseWidth > 40) {
|
|
327
|
+
if (this.level === 'week-day') {
|
|
328
|
+
this.baseWidth = 40
|
|
329
|
+
} else {
|
|
330
|
+
this.level = levelDown(this.level)
|
|
331
|
+
this.baseWidth = 30
|
|
332
|
+
}
|
|
333
|
+
} else if (this.baseWidth < 30) {
|
|
334
|
+
if (this.level === 'year-quarter') {
|
|
335
|
+
this.baseWidth = 30
|
|
336
|
+
}
|
|
337
|
+
}
|
|
360
338
|
|
|
361
|
-
|
|
362
|
-
this.
|
|
339
|
+
if (this.baseWidth < 20) {
|
|
340
|
+
this.level = levelUp(this.level)
|
|
341
|
+
this.baseWidth = 30
|
|
363
342
|
}
|
|
364
|
-
}
|
|
365
343
|
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
event.stopPropagation()
|
|
344
|
+
this.updateTimeScale()
|
|
345
|
+
this.host?.requestUpdate()
|
|
369
346
|
|
|
370
|
-
const
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
let zoomFactor = currentDistance / this.lastTouchDistance!
|
|
379
|
-
zoomFactor = 1 + (zoomFactor - 1) * zoomSensitivity
|
|
380
|
-
|
|
381
|
-
this.baseWidth *= zoomFactor
|
|
382
|
-
|
|
383
|
-
if (this.baseWidth > 40) {
|
|
384
|
-
if (this.level === 'week-day') {
|
|
385
|
-
this.baseWidth = 40
|
|
386
|
-
} else {
|
|
387
|
-
this.level = levelDown(this.level)
|
|
388
|
-
this.baseWidth = 30
|
|
389
|
-
}
|
|
390
|
-
} else if (this.baseWidth < 30) {
|
|
391
|
-
if (this.level === 'year-quarter') {
|
|
392
|
-
this.baseWidth = 30
|
|
393
|
-
}
|
|
394
|
-
}
|
|
347
|
+
const customEvent = new CustomEvent('timescale-changed', {
|
|
348
|
+
detail: {
|
|
349
|
+
baseWidth: this.baseWidth,
|
|
350
|
+
level: this.level
|
|
351
|
+
},
|
|
352
|
+
bubbles: true,
|
|
353
|
+
composed: true
|
|
354
|
+
})
|
|
395
355
|
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
this.baseWidth = 30
|
|
399
|
-
}
|
|
356
|
+
this.host?.dispatchEvent(customEvent)
|
|
357
|
+
}
|
|
400
358
|
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
this.host?.requestUpdate()
|
|
404
|
-
|
|
405
|
-
const customEvent = new CustomEvent('timescale-changed', {
|
|
406
|
-
detail: {
|
|
407
|
-
baseWidth: this.baseWidth,
|
|
408
|
-
level: this.level
|
|
409
|
-
},
|
|
410
|
-
bubbles: true,
|
|
411
|
-
composed: true
|
|
412
|
-
})
|
|
413
|
-
this.host?.dispatchEvent(customEvent)
|
|
414
|
-
} else if (this.touchStartX !== null) {
|
|
415
|
-
const dx = this.touchStartX! - x
|
|
416
|
-
const dy = this.touchStartY! - y
|
|
417
|
-
|
|
418
|
-
if (!this.dragging) {
|
|
419
|
-
if (Math.sqrt(dx * dx + dy * dy) > this.draggingThreshold) {
|
|
420
|
-
this.dragging = true
|
|
421
|
-
} else {
|
|
422
|
-
return
|
|
423
|
-
}
|
|
424
|
-
}
|
|
359
|
+
handleDoubleTapForBody = (event: GestureEventDoubleTap) => {
|
|
360
|
+
const container = (this.host as any).container
|
|
425
361
|
|
|
426
|
-
|
|
427
|
-
|
|
362
|
+
container.scrollLeft = 0
|
|
363
|
+
container.scrollTop = 0
|
|
428
364
|
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
scrollLeft: container.scrollLeft,
|
|
432
|
-
scrollTop: container.scrollTop
|
|
433
|
-
},
|
|
434
|
-
bubbles: true,
|
|
435
|
-
composed: true
|
|
436
|
-
})
|
|
365
|
+
this.baseWidth = 30
|
|
366
|
+
this.level = 'week-day'
|
|
437
367
|
|
|
438
|
-
|
|
439
|
-
|
|
368
|
+
this.updateTimeScale()
|
|
369
|
+
this.host?.requestUpdate()
|
|
370
|
+
|
|
371
|
+
const customEvent = new CustomEvent('timescale-changed', {
|
|
372
|
+
detail: {
|
|
373
|
+
baseWidth: this.baseWidth,
|
|
374
|
+
level: this.level
|
|
375
|
+
},
|
|
376
|
+
bubbles: true,
|
|
377
|
+
composed: true
|
|
378
|
+
})
|
|
379
|
+
|
|
380
|
+
this.host?.dispatchEvent(customEvent)
|
|
440
381
|
}
|
|
441
382
|
|
|
442
|
-
|
|
443
|
-
event.
|
|
444
|
-
|
|
383
|
+
handleDragForBody = (event: GestureEventDrag) => {
|
|
384
|
+
const { deltaX: dx, deltaY: dy } = event.detail
|
|
385
|
+
const container = (this.host as any).container
|
|
386
|
+
const { offsetHeight } = container
|
|
445
387
|
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
} else if (this.touchStartX !== null) {
|
|
449
|
-
if (!this.dragging) {
|
|
450
|
-
this.host?.dispatchEvent(
|
|
451
|
-
new CustomEvent('date-range-cleared', {
|
|
452
|
-
bubbles: true,
|
|
453
|
-
composed: true
|
|
454
|
-
})
|
|
455
|
-
)
|
|
456
|
-
}
|
|
388
|
+
container.scrollLeft -= dx
|
|
389
|
+
container.scrollTop = Math.min(this.tasks.length * 30 - (offsetHeight - 40), container.scrollTop - dy)
|
|
457
390
|
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
391
|
+
const customEvent = new CustomEvent('scroll', {
|
|
392
|
+
detail: {
|
|
393
|
+
scrollLeft: container.scrollLeft,
|
|
394
|
+
scrollTop: container.scrollTop
|
|
395
|
+
},
|
|
396
|
+
bubbles: true,
|
|
397
|
+
composed: true
|
|
398
|
+
})
|
|
464
399
|
|
|
465
|
-
|
|
466
|
-
const dx = touches[0].clientX - touches[1].clientX
|
|
467
|
-
const dy = touches[0].clientY - touches[1].clientY
|
|
468
|
-
return Math.sqrt(dx * dx + dy * dy)
|
|
400
|
+
this.host?.dispatchEvent(customEvent)
|
|
469
401
|
}
|
|
470
402
|
}
|
package/src/ox-gantt-chart.ts
CHANGED
|
@@ -13,8 +13,10 @@ import { Task, TimeScale } from './types.js'
|
|
|
13
13
|
import { levelDown, levelUp } from './utils'
|
|
14
14
|
import { OxTaskTooltip } from './ox-task-tooltip.js'
|
|
15
15
|
|
|
16
|
+
import { gesture } from '@operato/utils/gesture-mixin.js'
|
|
17
|
+
|
|
16
18
|
@customElement('ox-gantt-chart')
|
|
17
|
-
export class OxGanttChart extends LitElement {
|
|
19
|
+
export class OxGanttChart extends gesture(LitElement) {
|
|
18
20
|
static styles = [
|
|
19
21
|
MDTypeScaleStyles,
|
|
20
22
|
ScrollbarStyles,
|
|
@@ -31,6 +33,7 @@ export class OxGanttChart extends LitElement {
|
|
|
31
33
|
overflow-x: auto;
|
|
32
34
|
overflow-y: hidden;
|
|
33
35
|
overscroll-behavior: none;
|
|
36
|
+
touch-action: none;
|
|
34
37
|
}
|
|
35
38
|
|
|
36
39
|
svg {
|
|
@@ -91,12 +94,7 @@ export class OxGanttChart extends LitElement {
|
|
|
91
94
|
@query('[chart-container]') container!: HTMLDivElement
|
|
92
95
|
@query('ox-task-tooltip') tooltip!: OxTaskTooltip
|
|
93
96
|
|
|
94
|
-
private ganttChartController
|
|
95
|
-
|
|
96
|
-
constructor() {
|
|
97
|
-
super()
|
|
98
|
-
this.ganttChartController = new GanttChartController(this, this.fromDate, this.toDate, this.axisScale)
|
|
99
|
-
}
|
|
97
|
+
private ganttChartController!: GanttChartController
|
|
100
98
|
|
|
101
99
|
protected firstUpdated(_changedProperties: PropertyValueMap<any> | Map<PropertyKey, unknown>): void {
|
|
102
100
|
this.initHeaderGestureHandlers()
|
|
@@ -104,9 +102,22 @@ export class OxGanttChart extends LitElement {
|
|
|
104
102
|
this.addEventListener('date-range-cleared', e => this.clearSelection())
|
|
105
103
|
}
|
|
106
104
|
|
|
105
|
+
connectedCallback(): void {
|
|
106
|
+
super.connectedCallback()
|
|
107
|
+
this.ganttChartController = new GanttChartController(this, this.fromDate, this.toDate, this.axisScale)
|
|
108
|
+
|
|
109
|
+
this.addEventListener('pinch', this.ganttChartController.handlePinchForBody as EventListener)
|
|
110
|
+
this.addEventListener('drag', this.ganttChartController.handleDragForBody as EventListener)
|
|
111
|
+
this.addEventListener('doubletap', this.ganttChartController.handleDoubleTapForBody as EventListener)
|
|
112
|
+
}
|
|
113
|
+
|
|
107
114
|
disconnectedCallback(): void {
|
|
108
115
|
super.disconnectedCallback()
|
|
109
116
|
|
|
117
|
+
this.removeEventListener('pinch', this.ganttChartController.handlePinchForBody as EventListener)
|
|
118
|
+
this.removeEventListener('drag', this.ganttChartController.handleDragForBody as EventListener)
|
|
119
|
+
this.removeEventListener('doubletap', this.ganttChartController.handleDoubleTapForBody as EventListener)
|
|
120
|
+
|
|
110
121
|
this.ganttChartController.dispose()
|
|
111
122
|
}
|
|
112
123
|
|
|
@@ -116,8 +127,9 @@ export class OxGanttChart extends LitElement {
|
|
|
116
127
|
}
|
|
117
128
|
|
|
118
129
|
if (changedProperties.has('tasks')) {
|
|
119
|
-
this.
|
|
120
|
-
this.
|
|
130
|
+
const flattenTasks = this.flattenTasks(this.tasks)
|
|
131
|
+
this.ganttChartController.setTasks(flattenTasks)
|
|
132
|
+
this.svgBody.style.height = Math.max(flattenTasks.length * 30, 2000) + 'px'
|
|
121
133
|
}
|
|
122
134
|
|
|
123
135
|
if (changedProperties.has('locale')) {
|
|
@@ -143,16 +155,7 @@ export class OxGanttChart extends LitElement {
|
|
|
143
155
|
|
|
144
156
|
render() {
|
|
145
157
|
return html`
|
|
146
|
-
<div
|
|
147
|
-
chart-container
|
|
148
|
-
@wheel=${(e: WheelEvent) => this.ganttChartController.handleWheelForBody(e, this.container)}
|
|
149
|
-
@mousedown=${(e: MouseEvent) => this.ganttChartController.handleTouchStartForBody(e, this.container)}
|
|
150
|
-
@mousemove=${(e: MouseEvent) => this.ganttChartController.handleTouchMoveForBody(e, this.container)}
|
|
151
|
-
@mouseup=${(e: MouseEvent) => this.ganttChartController.handleTouchEndForBody(e)}
|
|
152
|
-
@touchstart=${(e: TouchEvent) => this.ganttChartController.handleTouchStartForBody(e, this.container)}
|
|
153
|
-
@touchmove=${(e: TouchEvent) => this.ganttChartController.handleTouchMoveForBody(e, this.container)}
|
|
154
|
-
@touchend=${(e: TouchEvent) => this.ganttChartController.handleTouchEndForBody(e)}
|
|
155
|
-
>
|
|
158
|
+
<div chart-container @wheel=${(e: WheelEvent) => this.ganttChartController.handleWheelForBody(e, this.container)}>
|
|
156
159
|
<svg
|
|
157
160
|
header
|
|
158
161
|
@date-range-selected=${(e: CustomEvent) => this.handleDateRangeSelected(e)}
|
|
@@ -279,88 +282,44 @@ export class OxGanttChart extends LitElement {
|
|
|
279
282
|
initHeaderGestureHandlers() {
|
|
280
283
|
const header = d3.select(this.svgHeader)
|
|
281
284
|
|
|
282
|
-
let isDragging = false
|
|
283
285
|
let startX: number | null = null
|
|
284
286
|
|
|
285
|
-
const
|
|
287
|
+
const handlePointerDown = (event: PointerEvent) => {
|
|
286
288
|
event.preventDefault()
|
|
287
289
|
event.stopPropagation()
|
|
288
290
|
|
|
289
|
-
isDragging = true
|
|
290
|
-
const touch = event.touches[0]
|
|
291
291
|
const containerOffsetX = header.node()!.getBoundingClientRect().left
|
|
292
|
-
startX =
|
|
293
|
-
}
|
|
294
|
-
|
|
295
|
-
const handleTouchMove = (event: TouchEvent) => {
|
|
296
|
-
event.preventDefault()
|
|
297
|
-
event.stopPropagation()
|
|
292
|
+
startX = event.clientX - containerOffsetX
|
|
298
293
|
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
const containerOffsetX = header.node()!.getBoundingClientRect().left
|
|
302
|
-
const currentX = touch.clientX - containerOffsetX
|
|
303
|
-
this.highlightSelection(startX, currentX)
|
|
304
|
-
}
|
|
294
|
+
window.addEventListener('pointermove', handlePointerMove)
|
|
295
|
+
window.addEventListener('pointerup', handlePointerUp)
|
|
305
296
|
}
|
|
306
297
|
|
|
307
|
-
const
|
|
298
|
+
const handlePointerMove = (event: PointerEvent) => {
|
|
308
299
|
event.preventDefault()
|
|
309
300
|
event.stopPropagation()
|
|
310
301
|
|
|
311
|
-
if (!isDragging || startX === null) {
|
|
312
|
-
const touch = event.changedTouches[0]
|
|
313
|
-
const containerOffsetX = header.node()!.getBoundingClientRect().left
|
|
314
|
-
const clickX = touch.clientX - containerOffsetX
|
|
315
|
-
this.finalizeSelection(clickX, clickX)
|
|
316
|
-
return
|
|
317
|
-
}
|
|
318
|
-
|
|
319
|
-
const touch = event.changedTouches[0]
|
|
320
302
|
const containerOffsetX = header.node()!.getBoundingClientRect().left
|
|
321
|
-
const
|
|
322
|
-
this.finalizeSelection(startX, endX)
|
|
323
|
-
|
|
324
|
-
isDragging = false
|
|
325
|
-
startX = null
|
|
326
|
-
}
|
|
303
|
+
const currentX = event.clientX - containerOffsetX
|
|
327
304
|
|
|
328
|
-
|
|
329
|
-
isDragging = true
|
|
330
|
-
const containerOffsetX = header.node()!.getBoundingClientRect().left
|
|
331
|
-
startX = event.clientX - containerOffsetX
|
|
305
|
+
this.highlightSelection(startX!, currentX)
|
|
332
306
|
}
|
|
333
307
|
|
|
334
|
-
const
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
const containerOffsetX = header.node()!.getBoundingClientRect().left
|
|
338
|
-
const currentX = event.clientX - containerOffsetX
|
|
339
|
-
this.highlightSelection(startX, currentX)
|
|
340
|
-
}
|
|
308
|
+
const handlePointerUp = (event: PointerEvent) => {
|
|
309
|
+
event.preventDefault()
|
|
310
|
+
event.stopPropagation()
|
|
341
311
|
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
const containerOffsetX = header.node()!.getBoundingClientRect().left
|
|
345
|
-
const clickX = event.clientX - containerOffsetX
|
|
346
|
-
this.finalizeSelection(clickX, clickX)
|
|
347
|
-
return
|
|
348
|
-
}
|
|
312
|
+
window.removeEventListener('pointermove', handlePointerMove)
|
|
313
|
+
window.removeEventListener('pointerup', handlePointerUp)
|
|
349
314
|
|
|
350
315
|
const containerOffsetX = header.node()!.getBoundingClientRect().left
|
|
351
316
|
const endX = event.clientX - containerOffsetX
|
|
352
|
-
this.finalizeSelection(startX
|
|
317
|
+
this.finalizeSelection(startX!, endX)
|
|
353
318
|
|
|
354
|
-
isDragging = false
|
|
355
319
|
startX = null
|
|
356
320
|
}
|
|
357
321
|
|
|
358
|
-
header.on('
|
|
359
|
-
header.on('mousemove', handleMouseMove)
|
|
360
|
-
header.on('mouseup', handleMouseUp)
|
|
361
|
-
header.on('touchstart', handleTouchStart)
|
|
362
|
-
header.on('touchmove', handleTouchMove)
|
|
363
|
-
header.on('touchend', handleTouchEnd)
|
|
322
|
+
header.on('pointerdown', handlePointerDown)
|
|
364
323
|
}
|
|
365
324
|
|
|
366
325
|
highlightSelection(startX: number, endX: number) {
|