| 1 | /* Fit columns to displayed values, using each cell's actual font and padding. |
| 2 | * Raw formulas are deliberately not measured: their displayed results are. */ |
| 3 | (function () { |
| 4 | 'use strict'; |
| 5 | var host = document.querySelector('[data-sheet-grid]'); |
| 6 | if (!host) return; |
| 7 | var canvas = document.createElement('canvas'); |
| 8 | var context = canvas.getContext('2d'); |
| 9 | var fitting = false; |
| 10 | |
| 11 | function width(el) { |
| 12 | if (!el || !el.textContent) return 0; |
| 13 | var style = getComputedStyle(el); |
| 14 | context.font = style.font || [style.fontStyle, style.fontWeight, style.fontSize, style.fontFamily].join(' '); |
| 15 | var spacing = parseFloat(style.letterSpacing) || 0; |
| 16 | var widest = 0; |
| 17 | el.textContent.split(/\r?\n/).forEach(function (line) { |
| 18 | widest = Math.max(widest, context.measureText(line).width + Math.max(0, line.length - 1) * spacing); |
| 19 | }); |
| 20 | return widest + (parseFloat(style.paddingLeft) || 0) + (parseFloat(style.paddingRight) || 0) + |
| 21 | (parseFloat(style.borderLeftWidth) || 0) + (parseFloat(style.borderRightWidth) || 0) + 4; |
| 22 | } |
| 23 | |
| 24 | window.SheetsAutoFit = async function (column) { |
| 25 | var g = window.SheetsGrid; |
| 26 | if (!g || !g.mayWrite || fitting || !context) return; |
| 27 | var ws = g.worksheet(), s = g.selection(); |
| 28 | if (!ws || (!s && typeof column !== 'number')) return; |
| 29 | var first = s ? Math.min(s.c, s.c2) : column; |
| 30 | var last = s ? Math.max(s.c, s.c2) : column; |
| 31 | // A divider outside the selection fits just its own column. |
| 32 | if (typeof column === 'number' && (column < first || column > last)) first = last = column; |
| 33 | fitting = true; |
| 34 | try { |
| 35 | if (document.fonts) await document.fonts.ready; |
| 36 | if (window.SheetsSizes) await window.SheetsSizes.settled(); |
| 37 | for (var c = first; c <= last; c++) { |
| 38 | var size = Math.max(16, width(ws.headers[c])); |
| 39 | for (var r = 0; r < g.rows; r++) { |
| 40 | var td = ws.records[r] && ws.records[r][c] && ws.records[r][c].element; |
| 41 | size = Math.max(size, width(td)); |
| 42 | } |
| 43 | // The server's existing width bounds also govern auto-fit. |
| 44 | if (!await g.post('/size', { col: c, size: Math.min(2000, Math.ceil(size)) })) break; |
| 45 | } |
| 46 | var surface = host.closest('[data-sheet-surface]'); |
| 47 | if (surface.dataset.windowTruncated && window.SheetsNote) { |
| 48 | window.SheetsNote.say('Columns fitted to the loaded cells; this sheet extends beyond the grid window.', 'warn'); |
| 49 | } |
| 50 | } finally { fitting = false; } |
| 51 | }; |
| 52 | |
| 53 | // The vendor treats even an unmoved resize click as a group resize. Suppress |
| 54 | // that no-op so the two clicks before auto-fit don't first equalize widths. |
| 55 | document.addEventListener('mouseup', function () { |
| 56 | var g = window.SheetsGrid, ws = g && g.worksheet(); |
| 57 | var resize = ws && ws.resizing; |
| 58 | if (!resize || resize.column === undefined) return; |
| 59 | var col = ws.cols[resize.column]; |
| 60 | if (!col || Number(col.colElement.getAttribute('width')) !== Number(resize.width)) return; |
| 61 | host.querySelectorAll('.resizing').forEach(function (el) { el.classList.remove('resizing'); }); |
| 62 | ws.resizing = null; |
| 63 | }, true); |
| 64 | |
| 65 | host.addEventListener('dblclick', function (e) { |
| 66 | var td = e.target.closest('thead td[data-x]'); |
| 67 | if (!td) return; |
| 68 | var rect = td.getBoundingClientRect(); |
| 69 | var col = Number(td.dataset.x); |
| 70 | // Match the vendor's resize handle so mousedown preserves the selection. |
| 71 | if (rect.right - e.clientX > 6) return; |
| 72 | e.preventDefault(); |
| 73 | e.stopImmediatePropagation(); |
| 74 | window.SheetsAutoFit(col); |
| 75 | }, true); |
| 76 | }()); |
| 77 | |