Feature #44608 » 0002-Auto-fit-Gantt-column-widths.patch
| app/assets/stylesheets/gantt.css | ||
|---|---|---|
| 51 | 51 |
max-inline-size: max(100px, calc(100% - 100px)); |
| 52 | 52 |
} |
| 53 | 53 | |
| 54 |
/* On a narrow chart, dragging may make the subject column wider than the available space. */ |
|
| 54 |
/* On a narrow chart, dragging or auto-fit may make the subject column wider than the available space. */
|
|
| 55 | 55 |
.gantt-chart.is-manually-resized [data-gantt-column="subjects"] {
|
| 56 | 56 |
flex: 0 0 var(--gantt-subject-display-width); |
| 57 | 57 |
inline-size: var(--gantt-subject-display-width); |
| ... | ... | |
| 60 | 60 | |
| 61 | 61 |
.gantt-chart .ui-resizable-e {
|
| 62 | 62 |
cursor: ew-resize; |
| 63 |
z-index: 30; |
|
| 63 | 64 |
} |
| 64 | 65 | |
| 65 | 66 |
.gantt-pane {
|
| ... | ... | |
| 218 | 219 |
white-space: nowrap; |
| 219 | 220 |
} |
| 220 | 221 | |
| 222 |
/* Applied temporarily when auto-fitting a column to measure its header and row content widths. */ |
|
| 223 |
.gantt-column-measuring .gantt-pane > header, |
|
| 224 |
.gantt-column-measuring .gantt-row {
|
|
| 225 |
inline-size: max-content; |
|
| 226 |
} |
|
| 227 | ||
| 221 | 228 |
.gantt-row-hover {
|
| 222 | 229 |
background-color: rgba(var(--oc-yellow-1-rgb), 0.5); |
| 223 | 230 |
} |
| app/helpers/gantt_helper.rb | ||
|---|---|---|
| 259 | 259 |
'subjects', min_width: 100, width: 'var(--gantt-subject-width)', |
| 260 | 260 |
data: {
|
| 261 | 261 |
'gantt--chart-target': 'subjectColumn', |
| 262 |
# When the subject column is dragged on a narrow chart, |
|
| 262 |
# When the subject column is dragged or auto-fitted on a narrow chart,
|
|
| 263 | 263 |
# let the user's chosen width take precedence over automatic shrinking. |
| 264 | 264 |
action: %w( |
| 265 | 265 |
gantt--column:resize-start->gantt--chart#handleSubjectColumnResizeStart |
| ... | ... | |
| 284 | 284 | |
| 285 | 285 |
tag.div(**options, &) |
| 286 | 286 |
end |
| 287 | ||
| 288 |
def gantt_column_resize_handle_tag |
|
| 289 |
tag.div( |
|
| 290 |
class: 'ui-resizable-handle ui-resizable-e', |
|
| 291 |
data: {
|
|
| 292 |
'gantt--column-target': 'handle', |
|
| 293 |
action: 'dblclick->gantt--column#fitWidth:prevent' |
|
| 294 |
}, |
|
| 295 |
aria: {hidden: true}
|
|
| 296 |
) |
|
| 297 |
end |
|
| 287 | 298 |
end |
| app/javascript/controllers/gantt/column_controller.js | ||
|---|---|---|
| 44 | 44 |
} |
| 45 | 45 | |
| 46 | 46 |
export default class extends Controller {
|
| 47 |
static targets = ["handle"] |
|
| 48 | ||
| 47 | 49 |
static values = {
|
| 48 | 50 |
minWidth: Number, |
| 49 | 51 |
// Local value |
| ... | ... | |
| 89 | 91 |
return this.element.dataset.ganttColumn |
| 90 | 92 |
} |
| 91 | 93 | |
| 94 |
fitWidth() {
|
|
| 95 |
const extraPadding = 10 |
|
| 96 |
let width = 0 |
|
| 97 | ||
| 98 |
// Temporarily apply this class to measure the content width needed to fit the column. |
|
| 99 |
this.element.classList.add("gantt-column-measuring")
|
|
| 100 |
try {
|
|
| 101 |
this.element.querySelectorAll(".gantt-pane > header, .gantt-row:not([hidden])").forEach((content) => {
|
|
| 102 |
width = Math.max(width, content.getBoundingClientRect().width) |
|
| 103 |
}) |
|
| 104 |
} finally {
|
|
| 105 |
this.element.classList.remove("gantt-column-measuring")
|
|
| 106 |
} |
|
| 107 | ||
| 108 |
width = Math.max(this.minWidthValue, Math.ceil(width) + extraPadding) |
|
| 109 |
this.dispatch("resize-start")
|
|
| 110 |
this.#setWidth(width) |
|
| 111 |
this.#saveWidth(width) |
|
| 112 |
} |
|
| 113 | ||
| 92 | 114 |
#setupResizable() {
|
| 93 | 115 |
const options = {
|
| 94 |
handles: "e",
|
|
| 116 |
handles: { e: this.handleTarget },
|
|
| 95 | 117 |
minWidth: this.minWidthValue, |
| 96 |
zIndex: 30, |
|
| 97 | 118 |
start: () => {
|
| 98 | 119 |
this.dispatch("resize-start")
|
| 99 | 120 |
}, |
| 100 | 121 |
resize: (_event, ui) => {
|
| 101 |
this.element.style.setProperty("--gantt-column-width", `${ui.size.width}px`)
|
|
| 102 |
this.element.style.removeProperty("width")
|
|
| 103 |
this.dispatch("resize", { detail: { width: ui.size.width } })
|
|
| 122 |
this.#setWidth(ui.size.width) |
|
| 104 | 123 |
}, |
| 105 | 124 |
stop: (_event, ui) => {
|
| 106 | 125 |
if (ui.originalSize.width !== ui.size.width) {
|
| ... | ... | |
| 112 | 131 |
this.#$element.resizable(options) |
| 113 | 132 |
} |
| 114 | 133 | |
| 134 |
#setWidth(width) {
|
|
| 135 |
this.element.style.setProperty("--gantt-column-width", `${width}px`)
|
|
| 136 |
this.element.style.removeProperty("width")
|
|
| 137 |
this.dispatch("resize", { detail: { width } })
|
|
| 138 |
} |
|
| 139 | ||
| 115 | 140 |
#restoreWidth() {
|
| 116 | 141 |
const width = this.#columnWidthStore.get(this.#columnName) |
| 117 | 142 | |
| app/views/gantts/_chart.html.erb | ||
|---|---|---|
| 23 | 23 |
<% end %> |
| 24 | 24 |
</div> |
| 25 | 25 |
</section> |
| 26 |
<%= gantt_column_resize_handle_tag %> |
|
| 26 | 27 |
<% end %> |
| 27 | 28 | |
| 28 | 29 |
<% |
| ... | ... | |
| 39 | 40 |
<%= gantt.selected_column_content(column: column, top: layout.content_top, zoom: layout.zoom, g_width: layout.chart_width).html_safe %> |
| 40 | 41 |
</div> |
| 41 | 42 |
</section> |
| 43 |
<%= gantt_column_resize_handle_tag %> |
|
| 42 | 44 |
<% end %> |
| 43 | 45 |
<% end %> |
| 44 | 46 | |
| test/system/gantts_test.rb | ||
|---|---|---|
| 155 | 155 |
assert_selector(subject_column) { column_width('subjects') > narrow_width }
|
| 156 | 156 |
end |
| 157 | 157 | |
| 158 |
test 'automatic column width fitting' do |
|
| 159 |
Issue.find(3).update!( |
|
| 160 |
subject: 'Cannot save a recipe after changing the ingredients and selecting a different category' |
|
| 161 |
) |
|
| 162 |
visit_gantt |
|
| 163 |
assert_selector '.gantt-column[data-gantt-column="subjects"].ui-resizable' |
|
| 164 | ||
| 165 |
expected_width = page.evaluate_script(<<~JS) |
|
| 166 |
(() => {
|
|
| 167 |
const row = document.querySelector('[data-gantt-column="subjects"] .gantt-row[data-gantt-row-key="issue-3"]');
|
|
| 168 |
const range = document.createRange(); |
|
| 169 |
range.selectNodeContents(row); |
|
| 170 |
return Math.ceil(range.getBoundingClientRect().right - row.getBoundingClientRect().left) + 10; |
|
| 171 |
})() |
|
| 172 |
JS |
|
| 173 | ||
| 174 |
find('.gantt-column[data-gantt-column="subjects"] .ui-resizable-e').double_click
|
|
| 175 |
assert_equal expected_width, column_width('subjects')
|
|
| 176 | ||
| 177 |
page.refresh |
|
| 178 |
assert_selector '.gantt-column[data-gantt-column="subjects"].ui-resizable' |
|
| 179 |
assert_equal expected_width, column_width('subjects')
|
|
| 180 |
end |
|
| 181 | ||
| 158 | 182 |
test 'context menu and tooltip interactions' do |
| 159 | 183 |
visit_gantt |
| 160 | 184 | |