| 1 | /* Point at cells while entering a formula, without committing the edited cell. |
| 2 | * This inserts references only; calculation remains on the server. */ |
| 3 | (function () { |
| 4 | 'use strict'; |
| 5 | var host = document.querySelector('[data-sheet-grid]'); |
| 6 | if (!host) return; |
| 7 | var drag = null; |
| 8 | |
| 9 | function editor() { |
| 10 | var el = document.activeElement; |
| 11 | if (!el || !el.matches('input, textarea') || el.readOnly) return null; |
| 12 | if (!el.matches('[data-formula-input]') && !host.contains(el)) return null; |
| 13 | if (el.value.charAt(0) !== '=' || el.selectionStart === null) return null; |
| 14 | return el; |
| 15 | } |
| 16 | function cell(target) { |
| 17 | var td = target.closest && target.closest('td[data-x][data-y]'); |
| 18 | return td && host.contains(td) ? td : null; |
| 19 | } |
| 20 | function ref(c, r) { |
| 21 | var name = ''; |
| 22 | for (c++; c; c = Math.floor((c - 1) / 26)) { |
| 23 | name = String.fromCharCode(65 + (c - 1) % 26) + name; |
| 24 | } |
| 25 | return name + (r + 1); |
| 26 | } |
| 27 | function clear() { |
| 28 | host.querySelectorAll('[data-formula-reference]').forEach(function (td) { |
| 29 | td.removeAttribute('data-formula-reference'); |
| 30 | }); |
| 31 | } |
| 32 | function point(td) { |
| 33 | var c = Number(td.dataset.x), r = Number(td.dataset.y); |
| 34 | var c1 = Math.min(c, drag.c), r1 = Math.min(r, drag.r); |
| 35 | var c2 = Math.max(c, drag.c), r2 = Math.max(r, drag.r); |
| 36 | var text = ref(c1, r1); |
| 37 | if (c1 !== c2 || r1 !== r2) text += ':' + ref(c2, r2); |
| 38 | drag.el.value = drag.before + text + drag.after; |
| 39 | drag.end = drag.before.length + text.length; |
| 40 | drag.el.setSelectionRange(drag.end, drag.end); |
| 41 | drag.el.dispatchEvent(new Event('input', { bubbles: true })); |
| 42 | clear(); |
| 43 | host.querySelectorAll('td[data-x][data-y]').forEach(function (item) { |
| 44 | if (+item.dataset.x >= c1 && +item.dataset.x <= c2 && |
| 45 | +item.dataset.y >= r1 && +item.dataset.y <= r2) { |
| 46 | item.setAttribute('data-formula-reference', ''); |
| 47 | } |
| 48 | }); |
| 49 | } |
| 50 | document.addEventListener('mousedown', function (e) { |
| 51 | if (e.button !== 0) return; |
| 52 | var el = editor(), td = cell(e.target); |
| 53 | if (!el || !td || td.contains(el)) return; |
| 54 | var start = el.selectionStart, end = el.selectionEnd; |
| 55 | if (start < 1) return; |
| 56 | // Point mode begins where an operand belongs, or replaces selected text. |
| 57 | // Else leave ordinary clicks alone so a completed formula can be committed. |
| 58 | if (start === end && !/[=+\-*/^(,:<>;&]\s*$/.test(el.value.slice(0, start))) return; |
| 59 | e.preventDefault(); |
| 60 | e.stopImmediatePropagation(); |
| 61 | drag = { el: el, before: el.value.slice(0, start), after: el.value.slice(end), |
| 62 | c: Number(td.dataset.x), r: Number(td.dataset.y) }; |
| 63 | point(td); |
| 64 | }, true); |
| 65 | document.addEventListener('mousemove', function (e) { |
| 66 | if (!drag) return; |
| 67 | e.preventDefault(); |
| 68 | e.stopImmediatePropagation(); |
| 69 | var td = cell(e.target); |
| 70 | if (td) point(td); |
| 71 | }, true); |
| 72 | document.addEventListener('mouseup', function (e) { |
| 73 | if (!drag) return; |
| 74 | e.preventDefault(); |
| 75 | e.stopImmediatePropagation(); |
| 76 | drag.el.focus(); |
| 77 | drag.el.setSelectionRange(drag.end, drag.end); |
| 78 | drag = null; |
| 79 | }, true); |
| 80 | document.addEventListener('keydown', function () { if (!drag) clear(); }, true); |
| 81 | document.addEventListener('focusout', function () { if (!drag) clear(); }); |
| 82 | window.addEventListener('blur', function () { drag = null; clear(); }); |
| 83 | }()); |
| 84 | |