@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.
- package/CHANGELOG.md +113 -0
- package/dist/src/gantt-chart-controller.d.ts +5 -6
- package/dist/src/gantt-chart-controller.js +92 -37
- package/dist/src/gantt-chart-controller.js.map +1 -1
- package/dist/src/ox-gantt-chart.d.ts +378 -3
- package/dist/src/ox-gantt-chart.js +86 -180
- package/dist/src/ox-gantt-chart.js.map +1 -1
- package/dist/src/ox-gantt-resize-splitter.d.ts +6 -2
- package/dist/src/ox-gantt-resize-splitter.js +70 -39
- package/dist/src/ox-gantt-resize-splitter.js.map +1 -1
- package/dist/src/ox-gantt.js +11 -11
- package/dist/src/ox-gantt.js.map +1 -1
- package/dist/src/ox-resource-chart.js +2 -0
- package/dist/src/ox-resource-chart.js.map +1 -1
- package/dist/src/ox-task-tooltip.d.ts +3 -1
- package/dist/src/ox-task-tooltip.js +36 -11
- package/dist/src/ox-task-tooltip.js.map +1 -1
- package/dist/src/types.d.ts +1 -0
- package/dist/src/types.js.map +1 -1
- package/dist/stories/ox-gantt.stories.js +37 -1
- package/dist/stories/ox-gantt.stories.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +13 -13
- package/src/gantt-chart-controller.ts +100 -34
- package/src/ox-gantt-chart.ts +92 -192
- package/src/ox-gantt-resize-splitter.ts +72 -44
- package/src/ox-gantt.ts +12 -9
- package/src/ox-resource-chart.ts +2 -0
- package/src/ox-task-tooltip.ts +31 -9
- package/src/types.ts +1 -0
- package/stories/ox-gantt.stories.ts +37 -1
- package/themes/calendar-theme.css +3 -1
- package/assets/images/icon-editor-gradient-direction.png +0 -0
- package/assets/images/icon-properties-label.png +0 -0
- package/assets/images/icon-properties-line-type.png +0 -0
- package/assets/images/icon-properties-table.png +0 -0
package/src/ox-gantt-chart.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
57
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
97
|
-
|
|
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.
|
|
107
|
-
this.
|
|
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
|
-
|
|
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-
|
|
146
|
-
|
|
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
|
|
281
|
+
const handlePointerDown = (event: PointerEvent) => {
|
|
272
282
|
event.preventDefault()
|
|
283
|
+
event.stopPropagation()
|
|
273
284
|
|
|
274
|
-
|
|
275
|
-
|
|
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
|
-
|
|
288
|
-
|
|
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
|
|
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
|
|
348
|
-
this.finalizeSelection(startX, endX)
|
|
349
|
-
|
|
350
|
-
isDragging = false
|
|
351
|
-
startX = null
|
|
352
|
-
}
|
|
297
|
+
const currentX = event.clientX - containerOffsetX
|
|
353
298
|
|
|
354
|
-
|
|
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
|
|
361
|
-
|
|
362
|
-
|
|
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
|
-
|
|
375
|
-
|
|
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
|
|
311
|
+
this.finalizeSelection(startX!, endX)
|
|
385
312
|
|
|
386
|
-
isDragging = false
|
|
387
313
|
startX = null
|
|
388
314
|
}
|
|
389
315
|
|
|
390
|
-
header.on('
|
|
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
|
-
|
|
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
|
|
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.
|
|
47
|
-
|
|
48
|
-
|
|
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
|
|
67
|
-
|
|
96
|
+
private onMouseMove = (e: MouseEvent) => {
|
|
97
|
+
this.moveDrag(e.clientX, e.clientY)
|
|
98
|
+
}
|
|
68
99
|
|
|
69
|
-
private
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
100
|
+
private onTouchMove = (e: TouchEvent) => {
|
|
101
|
+
const touch = e.touches[0]
|
|
102
|
+
this.moveDrag(touch.clientX, touch.clientY)
|
|
103
|
+
}
|
|
73
104
|
|
|
74
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
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 {
|
|
150
|
-
|
|
151
|
-
|
|
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 [
|
package/src/ox-resource-chart.ts
CHANGED
|
@@ -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] {
|