@operato/gantt 8.0.0-alpha.6 → 8.0.0-beta.0

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 (36) hide show
  1. package/CHANGELOG.md +113 -0
  2. package/dist/src/gantt-chart-controller.d.ts +5 -6
  3. package/dist/src/gantt-chart-controller.js +92 -37
  4. package/dist/src/gantt-chart-controller.js.map +1 -1
  5. package/dist/src/ox-gantt-chart.d.ts +378 -3
  6. package/dist/src/ox-gantt-chart.js +86 -180
  7. package/dist/src/ox-gantt-chart.js.map +1 -1
  8. package/dist/src/ox-gantt-resize-splitter.d.ts +6 -2
  9. package/dist/src/ox-gantt-resize-splitter.js +70 -39
  10. package/dist/src/ox-gantt-resize-splitter.js.map +1 -1
  11. package/dist/src/ox-gantt.js +11 -11
  12. package/dist/src/ox-gantt.js.map +1 -1
  13. package/dist/src/ox-resource-chart.js +2 -0
  14. package/dist/src/ox-resource-chart.js.map +1 -1
  15. package/dist/src/ox-task-tooltip.d.ts +3 -1
  16. package/dist/src/ox-task-tooltip.js +36 -11
  17. package/dist/src/ox-task-tooltip.js.map +1 -1
  18. package/dist/src/types.d.ts +1 -0
  19. package/dist/src/types.js.map +1 -1
  20. package/dist/stories/ox-gantt.stories.js +37 -1
  21. package/dist/stories/ox-gantt.stories.js.map +1 -1
  22. package/dist/tsconfig.tsbuildinfo +1 -1
  23. package/package.json +13 -13
  24. package/src/gantt-chart-controller.ts +100 -34
  25. package/src/ox-gantt-chart.ts +92 -192
  26. package/src/ox-gantt-resize-splitter.ts +72 -44
  27. package/src/ox-gantt.ts +12 -9
  28. package/src/ox-resource-chart.ts +2 -0
  29. package/src/ox-task-tooltip.ts +31 -9
  30. package/src/types.ts +1 -0
  31. package/stories/ox-gantt.stories.ts +37 -1
  32. package/themes/calendar-theme.css +3 -1
  33. package/assets/images/icon-editor-gradient-direction.png +0 -0
  34. package/assets/images/icon-properties-label.png +0 -0
  35. package/assets/images/icon-properties-line-type.png +0 -0
  36. package/assets/images/icon-properties-table.png +0 -0
@@ -2,7 +2,7 @@ import './ox-task-tooltip.js'
2
2
  import './ox-resource-chart.js'
3
3
 
4
4
  import { LitElement, html, css, PropertyValues, PropertyValueMap } from 'lit'
5
- import { customElement, property, query } from 'lit/decorators.js'
5
+ import { customElement, property, query, state } from 'lit/decorators.js'
6
6
  import * as d3 from 'd3'
7
7
  import { locales } from './locales'
8
8
 
@@ -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,
@@ -23,15 +25,21 @@ export class OxGanttChart extends LitElement {
23
25
  display: flex;
24
26
  overflow: hidden;
25
27
  position: relative;
28
+ user-select: none;
26
29
  }
30
+
27
31
  div[chart-container] {
28
32
  display: block;
29
33
  overflow-x: auto;
30
34
  overflow-y: hidden;
35
+ overscroll-behavior: none;
36
+ touch-action: none;
31
37
  }
38
+
32
39
  svg {
33
40
  min-width: 100%;
34
41
  }
42
+
35
43
  svg[header] {
36
44
  display: block;
37
45
  min-width: 100%;
@@ -44,27 +52,27 @@ export class OxGanttChart extends LitElement {
44
52
  top: 0;
45
53
  z-index: 1;
46
54
  }
47
- .scroll-buttons {
48
- position: absolute;
49
- top: 44px; /* 헤더 바로 아래 위치 */
50
- right: 4px;
51
- display: flex;
52
- align-items: center;
53
- gap: 2px;
54
- }
55
+
55
56
  .scroll-button {
56
- width: 10px;
57
- height: 10px;
57
+ position: absolute;
58
+ top: 46px; /* 헤더 바로 아래 위치 */
59
+ width: 14px;
60
+ height: 14px;
58
61
  background-color: var(--ox-gantt-header-color, var(--md-sys-color-on-secondary-container));
62
+ opacity: 0.5;
59
63
  clip-path: polygon(50% 0%, 0% 100%, 100% 100%);
60
64
  cursor: pointer;
61
65
  transform: rotate(90deg);
62
66
  }
67
+
63
68
  .scroll-button.left {
69
+ left: 6px;
64
70
  transform: rotate(-90deg);
65
71
  }
72
+
66
73
  .scroll-button.right {
67
74
  transform: rotate(90deg);
75
+ right: 6px;
68
76
  }
69
77
  `
70
78
  ]
@@ -81,20 +89,38 @@ export class OxGanttChart extends LitElement {
81
89
  @property({ type: String }) locale: string = 'en-US'
82
90
  @property({ type: Object }) colorProvider!: (task: Task) => string
83
91
 
92
+ @state() dateRangeSelected?: { start: Date; end: Date }
93
+
84
94
  @query('svg[header]') svgHeader!: SVGElement
85
95
  @query('svg[body]') svgBody!: SVGElement
86
96
  @query('[chart-container]') container!: HTMLDivElement
87
97
  @query('ox-task-tooltip') tooltip!: OxTaskTooltip
88
98
 
89
- private ganttChartController: GanttChartController
99
+ private ganttChartController!: GanttChartController
100
+
101
+ protected firstUpdated(_changedProperties: PropertyValueMap<any> | Map<PropertyKey, unknown>): void {
102
+ this.initHeaderGestureHandlers()
103
+
104
+ // this.addEventListener('date-range-cleared', e => this.clearSelection())
105
+ }
90
106
 
91
- constructor() {
92
- super()
107
+ connectedCallback(): void {
108
+ super.connectedCallback()
93
109
  this.ganttChartController = new GanttChartController(this, this.fromDate, this.toDate, this.axisScale)
110
+
111
+ this.addEventListener('pinch', this.ganttChartController.handlePinchForBody as EventListener)
112
+ this.addEventListener('drag', this.ganttChartController.handleDragForBody as EventListener)
113
+ this.addEventListener('doubletap', this.ganttChartController.handleDoubleTapForBody as EventListener)
94
114
  }
95
115
 
96
- protected firstUpdated(_changedProperties: PropertyValueMap<any> | Map<PropertyKey, unknown>): void {
97
- this.initHeaderGestureHandlers()
116
+ disconnectedCallback(): void {
117
+ super.disconnectedCallback()
118
+
119
+ this.removeEventListener('pinch', this.ganttChartController.handlePinchForBody as EventListener)
120
+ this.removeEventListener('drag', this.ganttChartController.handleDragForBody as EventListener)
121
+ this.removeEventListener('doubletap', this.ganttChartController.handleDoubleTapForBody as EventListener)
122
+
123
+ this.ganttChartController.dispose()
98
124
  }
99
125
 
100
126
  updated(changedProperties: PropertyValues<this>) {
@@ -103,8 +129,9 @@ export class OxGanttChart extends LitElement {
103
129
  }
104
130
 
105
131
  if (changedProperties.has('tasks')) {
106
- this.ganttChartController.setTasks(this.flattenTasks(this.tasks))
107
- this.svgBody.style.height = Math.max(this.tasks.length * 30, 2000) + 'px'
132
+ const flattenTasks = this.flattenTasks(this.tasks)
133
+ this.ganttChartController.setTasks(flattenTasks)
134
+ this.svgBody.style.height = Math.max(flattenTasks.length * 30, 2000) + 'px'
108
135
  }
109
136
 
110
137
  if (changedProperties.has('locale')) {
@@ -126,26 +153,16 @@ export class OxGanttChart extends LitElement {
126
153
 
127
154
  this.ganttChartController.drawAxes(this.svgHeader)
128
155
  this.ganttChartController.drawBody(this.svgBody, this.tooltip)
156
+ this.drawSelectedRange()
129
157
  }
130
158
 
131
159
  render() {
132
160
  return html`
133
- <div
134
- chart-container
135
- @wheel=${(e: WheelEvent) => this.ganttChartController.handleWheelForBody(e, this.container)}
136
- @touchstart=${(e: TouchEvent) => this.ganttChartController.handleTouchStartForBody(e, this.container)}
137
- @touchmove=${(e: TouchEvent) => this.ganttChartController.handleTouchMoveForBody(e, this.container)}
138
- >
139
- <svg
140
- header
141
- @date-range-selected=${(e: CustomEvent) => this.handleDateRangeSelected(e)}
142
- @wheel=${this.handleWheelForHeader.bind(this)}
143
- ></svg>
161
+ <div chart-container @wheel=${(e: WheelEvent) => this.ganttChartController.handleWheelForBody(e, this.container)}>
162
+ <svg header @wheel=${this.handleWheelForHeader.bind(this)}></svg>
144
163
  <svg body></svg>
145
- <div class="scroll-buttons">
146
- <div class="scroll-button left" @click=${this.scrollToStart}></div>
147
- <div class="scroll-button right" @click=${this.scrollToEnd}></div>
148
- </div>
164
+ <div class="scroll-button left" @click=${this.scrollToStart} @touchstart=${this.scrollToStart}></div>
165
+ <div class="scroll-button right" @click=${this.scrollToEnd} @touchstart=${this.scrollToEnd}></div>
149
166
  <ox-task-tooltip></ox-task-tooltip>
150
167
  </div>
151
168
  `
@@ -248,11 +265,6 @@ export class OxGanttChart extends LitElement {
248
265
  this.dispatchEvent(customEvent)
249
266
  }
250
267
 
251
- handleDateRangeSelected(event: CustomEvent) {
252
- const detail = event.detail
253
- this.dispatchEvent(new CustomEvent('date-range-selected', { detail }))
254
- }
255
-
256
268
  updateLocale(locale: string) {
257
269
  const localeDefinition = (locales as any)[locale || 'en-US']
258
270
  if (localeDefinition) {
@@ -264,141 +276,47 @@ export class OxGanttChart extends LitElement {
264
276
  initHeaderGestureHandlers() {
265
277
  const header = d3.select(this.svgHeader)
266
278
 
267
- let isDragging = false
268
279
  let startX: number | null = null
269
- let lastTouchDistance: number | null = null
270
280
 
271
- const handleTouchStart = (event: TouchEvent) => {
281
+ const handlePointerDown = (event: PointerEvent) => {
272
282
  event.preventDefault()
283
+ event.stopPropagation()
273
284
 
274
- if (event.touches.length === 2) {
275
- lastTouchDistance = getTouchDistance(event.touches)
276
- } else {
277
- isDragging = true
278
- const touch = event.touches[0]
279
- const containerOffsetX = header.node()!.getBoundingClientRect().left
280
- startX = touch.clientX - containerOffsetX
281
- }
282
- }
283
-
284
- const handleTouchMove = (event: TouchEvent) => {
285
- event.preventDefault()
285
+ const containerOffsetX = header.node()!.getBoundingClientRect().left
286
+ startX = event.clientX - containerOffsetX
286
287
 
287
- if (event.touches.length === 2 && lastTouchDistance !== null) {
288
- const currentDistance = getTouchDistance(event.touches)
289
- const zoomFactor = currentDistance / lastTouchDistance
290
- this.ganttChartController.baseWidth *= zoomFactor
291
-
292
- if (this.ganttChartController.baseWidth > 60) {
293
- if (this.ganttChartController.level == 'year-quarter') {
294
- this.ganttChartController.baseWidth = 60
295
- } else {
296
- this.ganttChartController.level = levelUp(this.ganttChartController.level)
297
- this.ganttChartController.baseWidth = 30
298
- }
299
- } else if (this.ganttChartController.baseWidth < 30) {
300
- if (this.ganttChartController.level == 'week-day') {
301
- this.ganttChartController.baseWidth = 30
302
- } else {
303
- this.ganttChartController.level = levelDown(this.ganttChartController.level)
304
- this.ganttChartController.baseWidth = 60
305
- }
306
- }
307
-
308
- lastTouchDistance = currentDistance
309
- this.ganttChartController.updateTimeScale()
310
- this.requestUpdate()
311
-
312
- // Dispatch custom event to sync with ox-resource-chart
313
- const customEvent = new CustomEvent('timescale-changed', {
314
- detail: {
315
- baseWidth: this.ganttChartController.baseWidth,
316
- level: this.ganttChartController.level
317
- },
318
- bubbles: true,
319
- composed: true
320
- })
321
- this.dispatchEvent(customEvent)
322
- } else if (isDragging && startX !== null) {
323
- const touch = event.touches[0]
324
- const containerOffsetX = header.node()!.getBoundingClientRect().left
325
- const currentX = touch.clientX - containerOffsetX
326
- this.highlightSelection(startX, currentX)
327
- }
288
+ window.addEventListener('pointermove', handlePointerMove)
289
+ window.addEventListener('pointerup', handlePointerUp)
328
290
  }
329
291
 
330
- const handleTouchEnd = (event: TouchEvent) => {
292
+ const handlePointerMove = (event: PointerEvent) => {
331
293
  event.preventDefault()
294
+ event.stopPropagation()
332
295
 
333
- if (event.touches.length < 2) {
334
- lastTouchDistance = null
335
- }
336
-
337
- if (!isDragging || startX === null) {
338
- const touch = event.changedTouches[0]
339
- const containerOffsetX = header.node()!.getBoundingClientRect().left
340
- const clickX = touch.clientX - containerOffsetX
341
- this.finalizeSelection(clickX, clickX)
342
- return
343
- }
344
-
345
- const touch = event.changedTouches[0]
346
296
  const containerOffsetX = header.node()!.getBoundingClientRect().left
347
- const endX = touch.clientX - containerOffsetX
348
- this.finalizeSelection(startX, endX)
349
-
350
- isDragging = false
351
- startX = null
352
- }
297
+ const currentX = event.clientX - containerOffsetX
353
298
 
354
- const getTouchDistance = (touches: TouchList): number => {
355
- const dx = touches[0].clientX - touches[1].clientX
356
- const dy = touches[0].clientY - touches[1].clientY
357
- return Math.sqrt(dx * dx + dy * dy)
299
+ this.highlightSelection(startX!, currentX)
358
300
  }
359
301
 
360
- const handleMouseDown = (event: MouseEvent) => {
361
- isDragging = true
362
- const containerOffsetX = header.node()!.getBoundingClientRect().left
363
- startX = event.clientX - containerOffsetX
364
- }
365
-
366
- const handleMouseMove = (event: MouseEvent) => {
367
- if (!isDragging || startX === null) return
368
-
369
- const containerOffsetX = header.node()!.getBoundingClientRect().left
370
- const currentX = event.clientX - containerOffsetX
371
- this.highlightSelection(startX, currentX)
372
- }
302
+ const handlePointerUp = (event: PointerEvent) => {
303
+ event.preventDefault()
304
+ event.stopPropagation()
373
305
 
374
- const handleMouseUp = (event: MouseEvent) => {
375
- if (!isDragging || startX === null) {
376
- const containerOffsetX = header.node()!.getBoundingClientRect().left
377
- const clickX = event.clientX - containerOffsetX
378
- this.finalizeSelection(clickX, clickX)
379
- return
380
- }
306
+ window.removeEventListener('pointermove', handlePointerMove)
307
+ window.removeEventListener('pointerup', handlePointerUp)
381
308
 
382
309
  const containerOffsetX = header.node()!.getBoundingClientRect().left
383
310
  const endX = event.clientX - containerOffsetX
384
- this.finalizeSelection(startX, endX)
311
+ this.finalizeSelection(startX!, endX)
385
312
 
386
- isDragging = false
387
313
  startX = null
388
314
  }
389
315
 
390
- header.on('mousedown', handleMouseDown)
391
- header.on('mousemove', handleMouseMove)
392
- header.on('mouseup', handleMouseUp)
393
- header.on('touchstart', handleTouchStart)
394
- header.on('touchmove', handleTouchMove)
395
- header.on('touchend', handleTouchEnd)
316
+ header.on('pointerdown', handlePointerDown)
396
317
  }
397
318
 
398
319
  highlightSelection(startX: number, endX: number) {
399
- const header = d3.select(this.svgHeader)
400
- const body = d3.select(this.svgBody)
401
-
402
320
  let startDate = this.ganttChartController.timeScale.invert(startX)
403
321
  let endDate = this.ganttChartController.timeScale.invert(endX)
404
322
 
@@ -418,44 +336,10 @@ export class OxGanttChart extends LitElement {
418
336
  end = d3.timeDay.offset(d3.timeMonth.every(3)!.ceil(d3.max([startDate, endDate])!), -1)
419
337
  }
420
338
 
421
- header.selectAll('.selection-rect').remove()
422
- body.selectAll('.selection-rect').remove()
423
-
424
- header
425
- .append('rect')
426
- .attr('class', 'selection-rect')
427
- .attr('x', this.ganttChartController.timeScale(start!))
428
- .attr('y', 20)
429
- .attr(
430
- 'width',
431
- this.ganttChartController.timeScale(end!) -
432
- this.ganttChartController.timeScale(start!) +
433
- this.ganttChartController.baseWidth / this.ganttChartController.getScale()
434
- )
435
- .attr('height', 20)
436
- .attr('fill', 'var(--ox-gantt-selection-color, var(--md-sys-color-on-primary-container))')
437
- .attr('opacity', '.2')
438
-
439
- body
440
- .append('rect')
441
- .attr('class', 'selection-rect')
442
- .attr('x', this.ganttChartController.timeScale(start!))
443
- .attr('y', 0)
444
- .attr(
445
- 'width',
446
- this.ganttChartController.timeScale(end!) -
447
- this.ganttChartController.timeScale(start!) +
448
- this.ganttChartController.baseWidth / this.ganttChartController.getScale()
449
- )
450
- .attr('height', '100%')
451
- .attr('fill', 'var(--ox-gantt-selection-color, var(--md-sys-color-on-primary-container))')
452
- .attr('opacity', '.2')
339
+ this.dateRangeSelected = { start: start!, end: end! }
453
340
  }
454
341
 
455
342
  finalizeSelection(startX: number, endX: number) {
456
- const header = d3.select(this.svgHeader)
457
- const body = d3.select(this.svgBody)
458
-
459
343
  let startDate = this.ganttChartController.timeScale.invert(startX)
460
344
  let endDate = this.ganttChartController.timeScale.invert(endX)
461
345
 
@@ -475,6 +359,29 @@ export class OxGanttChart extends LitElement {
475
359
  end = d3.timeDay.offset(d3.timeMonth.every(3)!.ceil(d3.max([startDate, endDate])!), -1)
476
360
  }
477
361
 
362
+ this.dateRangeSelected = { start: start!, end: end! }
363
+
364
+ const formatDate = d3.timeFormat('%Y-%m-%d')
365
+
366
+ this.dispatchEvent(
367
+ new CustomEvent('date-range-selected', {
368
+ detail: { start: formatDate(start!), end: formatDate(end!) },
369
+ bubbles: true,
370
+ composed: true
371
+ })
372
+ )
373
+ }
374
+
375
+ drawSelectedRange() {
376
+ if (!this.dateRangeSelected) {
377
+ return
378
+ }
379
+
380
+ let { start, end } = this.dateRangeSelected
381
+
382
+ const header = d3.select(this.svgHeader)
383
+ const body = d3.select(this.svgBody)
384
+
478
385
  header.selectAll('.selection-rect').remove()
479
386
  body.selectAll('.selection-rect').remove()
480
387
 
@@ -507,12 +414,5 @@ export class OxGanttChart extends LitElement {
507
414
  .attr('height', '100%')
508
415
  .attr('fill', 'var(--ox-gantt-selection-color, var(--md-sys-color-on-primary-container))')
509
416
  .attr('opacity', '.2')
510
-
511
- const formatDate = d3.timeFormat('%Y-%m-%d')
512
- const customEvent = new CustomEvent('date-range-selected', {
513
- detail: { start: formatDate(start!), end: formatDate(end!) }
514
- })
515
-
516
- header.node()?.dispatchEvent(customEvent)
517
417
  }
518
418
  }
@@ -10,15 +10,29 @@ export class OxGanttResizeSplitter extends LitElement {
10
10
  background-color: var(--splitter-background-color);
11
11
  width: var(--width);
12
12
  height: var(--height);
13
+ z-index: 2;
13
14
  }
14
15
 
15
16
  :host(:hover) {
16
17
  background-color: var(--splitter-hover-background-color);
17
18
  }
18
19
 
20
+ /* 터치 영역을 넓히기 위해 실제 터치 영역을 더 크게 설정 */
19
21
  div {
20
22
  width: var(--width);
21
23
  height: var(--height);
24
+ touch-action: none; /* 터치 이벤트 처리에 방해되지 않도록 설정 */
25
+ }
26
+
27
+ div.touch-target {
28
+ width: var(--width);
29
+ height: var(--height);
30
+ transform: scale(1, 5);
31
+ transform-origin: center;
32
+ }
33
+
34
+ :host([vertical]) div.touch-target {
35
+ transform: scale(5, 1); /* 세로 스플리터는 세로 방향으로 터치 영역을 확장 */
22
36
  }
23
37
  `
24
38
 
@@ -39,14 +53,37 @@ export class OxGanttResizeSplitter extends LitElement {
39
53
  }
40
54
 
41
55
  render() {
42
- return html`<div @mousedown=${(e: MouseEvent) => this.onMouseDown(e)}></div>`
56
+ return html`
57
+ <div
58
+ class="touch-target"
59
+ @mousedown=${(e: MouseEvent) => this.onMouseDown(e)}
60
+ @touchstart=${this.onTouchStart}
61
+ ></div>
62
+ <div></div>
63
+ <!-- 실제 보이는 스플리터 -->
64
+ `
43
65
  }
44
66
 
45
67
  private onMouseDown(e: MouseEvent) {
46
- this.dragstart = {
47
- x: e.clientX,
48
- y: e.clientY
68
+ this.startDrag(e.clientX, e.clientY)
69
+ window.addEventListener('mousemove', this.onMouseMove)
70
+ window.addEventListener('mouseup', this.onMouseUp)
71
+ e.stopPropagation()
72
+ }
73
+
74
+ private onTouchStart(e: TouchEvent) {
75
+ // 터치가 touch-target에서 시작된 경우에만 작동
76
+ if ((e.target as HTMLElement).classList.contains('touch-target')) {
77
+ const touch = e.touches[0]
78
+ this.startDrag(touch.clientX, touch.clientY)
79
+ window.addEventListener('touchmove', this.onTouchMove)
80
+ window.addEventListener('touchend', this.onTouchEnd)
49
81
  }
82
+ e.stopPropagation()
83
+ }
84
+
85
+ private startDrag(clientX: number, clientY: number) {
86
+ this.dragstart = { x: clientX, y: clientY }
50
87
 
51
88
  this.dispatchEvent(
52
89
  new CustomEvent('splitter-dragstart', {
@@ -54,44 +91,28 @@ export class OxGanttResizeSplitter extends LitElement {
54
91
  composed: true
55
92
  })
56
93
  )
57
-
58
- this.parentElement!.addEventListener('mousemove', this.onMouseMoveHandler)
59
- this.parentElement!.addEventListener('mouseup', this.onMouseUpHandler)
60
- window.addEventListener('mousemove', this.onMouseMoveHandler)
61
- window.addEventListener('mouseup', this.onMouseUpHandler)
62
-
63
- e.stopPropagation()
64
94
  }
65
95
 
66
- private onMouseMoveHandler = (e: MouseEvent) => this.onMouseMove(e)
67
- private onMouseUpHandler = (e: MouseEvent) => this.onMouseUp(e)
96
+ private onMouseMove = (e: MouseEvent) => {
97
+ this.moveDrag(e.clientX, e.clientY)
98
+ }
68
99
 
69
- private onMouseMove(e: MouseEvent) {
70
- if (!this.dragstart) {
71
- return
72
- }
100
+ private onTouchMove = (e: TouchEvent) => {
101
+ const touch = e.touches[0]
102
+ this.moveDrag(touch.clientX, touch.clientY)
103
+ }
73
104
 
74
- const parentRect = this.parentElement!.getBoundingClientRect()
75
- if (
76
- e.clientX < parentRect.left ||
77
- e.clientX > parentRect.right ||
78
- e.clientY < parentRect.top ||
79
- e.clientY > parentRect.bottom
80
- ) {
81
- this.onMouseUp(e)
82
- return
83
- }
105
+ private moveDrag(clientX: number, clientY: number) {
106
+ if (!this.dragstart) return
84
107
 
85
- e.preventDefault()
108
+ const deltaX = clientX - this.dragstart.x
109
+ const deltaY = clientY - this.dragstart.y
86
110
 
87
111
  this.dispatchEvent(
88
112
  new CustomEvent('splitter-drag', {
89
113
  bubbles: true,
90
114
  composed: true,
91
- detail: {
92
- x: e.clientX - this.dragstart!.x,
93
- y: e.clientY - this.dragstart!.y
94
- }
115
+ detail: { x: deltaX, y: deltaY }
95
116
  })
96
117
  )
97
118
 
@@ -102,30 +123,37 @@ export class OxGanttResizeSplitter extends LitElement {
102
123
  }
103
124
 
104
125
  document.body.style.userSelect = 'none'
126
+ }
105
127
 
106
- e.stopPropagation()
128
+ private onMouseUp = (e: MouseEvent) => {
129
+ this.endDrag(e.clientX, e.clientY)
130
+ window.removeEventListener('mousemove', this.onMouseMove)
131
+ window.removeEventListener('mouseup', this.onMouseUp)
132
+ }
133
+
134
+ private onTouchEnd = (e: TouchEvent) => {
135
+ const touch = e.changedTouches[0]
136
+ this.endDrag(touch.clientX, touch.clientY)
137
+ window.removeEventListener('touchmove', this.onTouchMove)
138
+ window.removeEventListener('touchend', this.onTouchEnd)
107
139
  }
108
140
 
109
- private onMouseUp(e: MouseEvent) {
110
- this.parentElement!.removeEventListener('mousemove', this.onMouseMoveHandler)
111
- this.parentElement!.removeEventListener('mouseup', this.onMouseUpHandler)
112
- window.removeEventListener('mousemove', this.onMouseMoveHandler)
113
- window.removeEventListener('mouseup', this.onMouseUpHandler)
141
+ private endDrag(clientX: number, clientY: number) {
142
+ if (!this.dragstart) return
143
+
144
+ const deltaX = clientX - this.dragstart.x
145
+ const deltaY = clientY - this.dragstart.y
114
146
 
115
147
  this.dispatchEvent(
116
148
  new CustomEvent('splitter-dragend', {
117
149
  bubbles: true,
118
150
  composed: true,
119
- detail: {
120
- x: e.clientX - this.dragstart!.x,
121
- y: e.clientY - this.dragstart!.y
122
- }
151
+ detail: { x: deltaX, y: deltaY }
123
152
  })
124
153
  )
125
154
 
155
+ this.dragstart = undefined
126
156
  document.body.style.cursor = ''
127
157
  document.body.style.userSelect = ''
128
-
129
- e.stopPropagation()
130
158
  }
131
159
  }
package/src/ox-gantt.ts CHANGED
@@ -23,6 +23,7 @@ export class OxGantt extends localize(i18next)(LitElement) {
23
23
  font-family: Arial, sans-serif;
24
24
  text-wrap: nowrap;
25
25
  position: relative;
26
+ user-select: none;
26
27
  }
27
28
 
28
29
  .container {
@@ -49,6 +50,7 @@ export class OxGantt extends localize(i18next)(LitElement) {
49
50
  overflow-y: overlay;
50
51
  box-sizing: border-box;
51
52
  border: 1px solid var(--ox-gantt-border-color, var(--md-sys-color-outline));
53
+ overscroll-behavior: none; /* diabled rubber-band effect */
52
54
  }
53
55
 
54
56
  .resource-chart-container {
@@ -56,6 +58,7 @@ export class OxGantt extends localize(i18next)(LitElement) {
56
58
  padding: 0;
57
59
  box-sizing: border-box;
58
60
  border: 1px solid var(--ox-gantt-border-color, var(--md-sys-color-outline));
61
+ overscroll-behavior: none;
59
62
  }
60
63
 
61
64
  .task-container {
@@ -64,6 +67,8 @@ export class OxGantt extends localize(i18next)(LitElement) {
64
67
  display: flex;
65
68
  overflow: hidden;
66
69
  box-sizing: border-box;
70
+ user-select: none;
71
+ overscroll-behavior: none;
67
72
  }
68
73
 
69
74
  .task-table-container {
@@ -72,6 +77,7 @@ export class OxGantt extends localize(i18next)(LitElement) {
72
77
  overflow-y: overlay;
73
78
  box-sizing: border-box;
74
79
  border: 1px solid var(--ox-gantt-border-color, var(--md-sys-color-outline));
80
+ overscroll-behavior: none; /* diabled rubber-band effect */
75
81
  }
76
82
 
77
83
  .task-chart-container {
@@ -104,6 +110,9 @@ export class OxGantt extends localize(i18next)(LitElement) {
104
110
  border: 1px solid var(--ox-gantt-border-color, var(--md-sys-color-outline));
105
111
  padding: 4px;
106
112
  text-align: left;
113
+ white-space: nowrap;
114
+ overflow: hidden;
115
+ text-overflow: ellipsis;
107
116
  }
108
117
 
109
118
  th {
@@ -146,15 +155,9 @@ export class OxGantt extends localize(i18next)(LitElement) {
146
155
  @property({ type: Boolean, attribute: 'extend-grid-lines' }) extendGridLines: boolean = false
147
156
  @property({ type: String, attribute: 'time-scale' }) timeScale: TimeScale = 'week-day'
148
157
  @property({ type: Object }) colorProvider: (task: Task) => string = (task: Task) => {
149
- const { tags = [] } = task
150
- if (tags.includes('critical')) {
151
- return 'red'
152
- } else if (tags.includes('milestone')) {
153
- return 'blue'
154
- } else if (tags.includes('done')) {
155
- return 'green'
156
- }
157
- return 'grey'
158
+ const { style } = task
159
+
160
+ return style || 'gray'
158
161
  }
159
162
  @property({ type: Object }) columnConfigProvider: () => ColumnConfig[] = function () {
160
163
  return [
@@ -16,12 +16,14 @@ export class OxResourceChart extends LitElement {
16
16
  display: flex;
17
17
  overflow: hidden;
18
18
  position: relative;
19
+ user-select: none;
19
20
  }
20
21
 
21
22
  div[chart-container] {
22
23
  flex-direction: column;
23
24
  overflow-x: auto;
24
25
  overflow-y: hidden;
26
+ overscroll-behavior: none;
25
27
  }
26
28
 
27
29
  svg[header] {