UNPKG

11.6 kBJavaScriptView Raw
1import "core-js/modules/es.error.cause.js";
2function _classPrivateMethodInitSpec(e, a) { _checkPrivateRedeclaration(e, a), a.add(e); }
3function _classPrivateFieldInitSpec(e, t, a) { _checkPrivateRedeclaration(e, t), t.set(e, a); }
4function _checkPrivateRedeclaration(e, t) { if (t.has(e)) throw new TypeError("Cannot initialize the same private elements twice on an object"); }
5function _classPrivateFieldGet(s, a) { return s.get(_assertClassBrand(s, a)); }
6function _classPrivateFieldSet(s, a, r) { return s.set(_assertClassBrand(s, a), r), r; }
7function _assertClassBrand(e, t, n) { if ("function" == typeof e ? e === t : e.has(t)) return arguments.length < 3 ? t : n; throw new TypeError("Private element is not present on this object"); }
8import { warn } from "./helpers/console.mjs";
9import { isOutsideInput } from "./helpers/dom/element.mjs";
10import { debounce } from "./helpers/function.mjs";
11/**
12 * Possible focus modes.
13 * - CELL - The browser's focus stays on the lastly selected cell element.
14 * - MIXED - The browser's focus switches from the lastly selected cell element to the currently active editor's
15 * `TEXTAREA` element after a delay defined in the manager.
16 *
17 * @type {{CELL: string, MIXED: string}}
18 */
19const FOCUS_MODES = Object.freeze({
20 CELL: 'cell',
21 MIXED: 'mixed'
22});
23
24/**
25 * Manages the browser's focus in the table.
26 */
27var _hot = /*#__PURE__*/new WeakMap();
28var _focusMode = /*#__PURE__*/new WeakMap();
29var _refocusDelay = /*#__PURE__*/new WeakMap();
30var _refocusElementGetter = /*#__PURE__*/new WeakMap();
31var _debouncedSelect = /*#__PURE__*/new WeakMap();
32var _FocusManager_brand = /*#__PURE__*/new WeakSet();
33export class FocusManager {
34 constructor(hotInstance) {
35 var _this = this;
36 /**
37 * Get and return the currently selected and highlighted cell/header element.
38 *
39 * @param {Function} callback Callback function to be called after the cell element is retrieved.
40 */
41 _classPrivateMethodInitSpec(this, _FocusManager_brand);
42 /**
43 * The Handsontable instance.
44 */
45 _classPrivateFieldInitSpec(this, _hot, void 0);
46 /**
47 * The currently enabled focus mode.
48 * Can be either:
49 *
50 * - 'cell' - The browser's focus stays on the lastly selected cell element.
51 * - 'mixed' - The browser's focus switches from the lastly selected cell element to the currently active editor's
52 * `TEXTAREA` element after a delay defined in the manager.
53 *
54 * @type {'cell' | 'mixed'}
55 */
56 _classPrivateFieldInitSpec(this, _focusMode, void 0);
57 /**
58 * The delay after which the focus switches from the lastly selected cell to the active editor's `TEXTAREA`
59 * element if the focus mode is set to 'mixed'.
60 *
61 * @type {number}
62 */
63 _classPrivateFieldInitSpec(this, _refocusDelay, 1);
64 /**
65 * Getter function for the element to be used when refocusing the browser after a delay. If `null`, the active
66 * editor's `TEXTAREA` element will be used.
67 *
68 * @type {null|Function}
69 */
70 _classPrivateFieldInitSpec(this, _refocusElementGetter, null);
71 /**
72 * Map of the debounced `select` functions.
73 *
74 * @type {Map<number, Function>}
75 */
76 _classPrivateFieldInitSpec(this, _debouncedSelect, new Map());
77 const hotSettings = hotInstance.getSettings();
78 _classPrivateFieldSet(_hot, this, hotInstance);
79 _classPrivateFieldSet(_focusMode, this, hotSettings.imeFastEdit ? FOCUS_MODES.MIXED : FOCUS_MODES.CELL);
80 _classPrivateFieldGet(_hot, this).addHook('afterUpdateSettings', function () {
81 for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
82 args[_key] = arguments[_key];
83 }
84 return _assertClassBrand(_FocusManager_brand, _this, _onUpdateSettings).call(_this, ...args);
85 });
86 _classPrivateFieldGet(_hot, this).addHook('afterSelection', function () {
87 for (var _len2 = arguments.length, args = new Array(_len2), _key2 = 0; _key2 < _len2; _key2++) {
88 args[_key2] = arguments[_key2];
89 }
90 return _assertClassBrand(_FocusManager_brand, _this, _focusCell).call(_this, ...args);
91 });
92 _classPrivateFieldGet(_hot, this).addHook('afterSelectionFocusSet', function () {
93 for (var _len3 = arguments.length, args = new Array(_len3), _key3 = 0; _key3 < _len3; _key3++) {
94 args[_key3] = arguments[_key3];
95 }
96 return _assertClassBrand(_FocusManager_brand, _this, _focusCell).call(_this, ...args);
97 });
98 _classPrivateFieldGet(_hot, this).addHook('afterSelectionEnd', function () {
99 for (var _len4 = arguments.length, args = new Array(_len4), _key4 = 0; _key4 < _len4; _key4++) {
100 args[_key4] = arguments[_key4];
101 }
102 return _assertClassBrand(_FocusManager_brand, _this, _focusEditorElement).call(_this, ...args);
103 });
104 }
105
106 /**
107 * Get the current focus mode.
108 *
109 * @returns {'cell' | 'mixed'}
110 */
111 getFocusMode() {
112 return _classPrivateFieldGet(_focusMode, this);
113 }
114
115 /**
116 * Set the focus mode.
117 *
118 * @param {'cell' | 'mixed'} focusMode The new focus mode.
119 */
120 setFocusMode(focusMode) {
121 if (Object.values(FOCUS_MODES).includes(focusMode)) {
122 _classPrivateFieldSet(_focusMode, this, focusMode);
123 } else {
124 warn(`"${focusMode}" is not a valid focus mode.`);
125 }
126 }
127
128 /**
129 * Get the delay after which the focus will change from the cell elements to the active editor's `TEXTAREA`
130 * element if the focus mode is set to 'mixed'.
131 *
132 * @returns {number} Delay in milliseconds.
133 */
134 getRefocusDelay() {
135 return _classPrivateFieldGet(_refocusDelay, this);
136 }
137
138 /**
139 * Set the delay after which the focus will change from the cell elements to the active editor's `TEXTAREA`
140 * element if the focus mode is set to 'mixed'.
141 *
142 * @param {number} delay Delay in milliseconds.
143 */
144 setRefocusDelay(delay) {
145 _classPrivateFieldSet(_refocusDelay, this, delay);
146 }
147
148 /**
149 * Set the function to be used as the "refocus element" getter. It should return a focusable HTML element.
150 *
151 * @param {Function} getRefocusElementFunction The refocus element getter.
152 */
153 setRefocusElementGetter(getRefocusElementFunction) {
154 _classPrivateFieldSet(_refocusElementGetter, this, getRefocusElementFunction);
155 }
156
157 /**
158 * Get the element to be used when refocusing the browser after a delay in case of the focus mode being 'mixed'.
159 *
160 * @returns {HTMLTextAreaElement|HTMLElement|undefined}
161 */
162 getRefocusElement() {
163 if (typeof _classPrivateFieldGet(_refocusElementGetter, this) === 'function') {
164 return _classPrivateFieldGet(_refocusElementGetter, this).call(this);
165 } else {
166 var _classPrivateFieldGet2;
167 return (_classPrivateFieldGet2 = _classPrivateFieldGet(_hot, this).getActiveEditor()) === null || _classPrivateFieldGet2 === void 0 ? void 0 : _classPrivateFieldGet2.TEXTAREA;
168 }
169 }
170
171 /**
172 * Set the browser's focus to the highlighted cell of the last selection.
173 *
174 * @param {HTMLTableCellElement} [selectedCell] The highlighted cell/header element.
175 */
176 focusOnHighlightedCell(selectedCell) {
177 const focusElement = element => {
178 var _classPrivateFieldGet3, _classPrivateFieldGet4;
179 const currentHighlightCoords = (_classPrivateFieldGet3 = _classPrivateFieldGet(_hot, this).getSelectedRangeLast()) === null || _classPrivateFieldGet3 === void 0 ? void 0 : _classPrivateFieldGet3.highlight;
180 if (!currentHighlightCoords) {
181 return;
182 }
183 let elementToBeFocused = _classPrivateFieldGet(_hot, this).runHooks('modifyFocusedElement', currentHighlightCoords.row, currentHighlightCoords.col, element);
184 if (!(elementToBeFocused instanceof HTMLElement)) {
185 elementToBeFocused = element;
186 }
187 if (elementToBeFocused && !((_classPrivateFieldGet4 = _classPrivateFieldGet(_hot, this).getActiveEditor()) !== null && _classPrivateFieldGet4 !== void 0 && _classPrivateFieldGet4.isOpened())) {
188 elementToBeFocused.focus({
189 preventScroll: true
190 });
191 }
192 };
193 if (selectedCell) {
194 focusElement(selectedCell);
195 } else {
196 _assertClassBrand(_FocusManager_brand, this, _getSelectedCell).call(this, element => focusElement(element));
197 }
198 }
199
200 /**
201 * Set the focus to the active editor's `TEXTAREA` element after the provided delay. If no delay is provided, it
202 * will be taken from the manager's configuration.
203 *
204 * @param {number} [delay] Delay in milliseconds.
205 */
206 refocusToEditorTextarea() {
207 var _classPrivateFieldGet5;
208 let delay = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : _classPrivateFieldGet(_refocusDelay, this);
209 const refocusElement = this.getRefocusElement();
210
211 // Re-focus on the editor's `TEXTAREA` element (or a predefined element) if the `imeFastEdit` option is enabled.
212 if (_classPrivateFieldGet(_hot, this).getSettings().imeFastEdit && !((_classPrivateFieldGet5 = _classPrivateFieldGet(_hot, this).getActiveEditor()) !== null && _classPrivateFieldGet5 !== void 0 && _classPrivateFieldGet5.isOpened()) && !!refocusElement) {
213 if (!_classPrivateFieldGet(_debouncedSelect, this).has(delay)) {
214 _classPrivateFieldGet(_debouncedSelect, this).set(delay, debounce(() => {
215 refocusElement.select();
216 }, delay));
217 }
218 _classPrivateFieldGet(_debouncedSelect, this).get(delay)();
219 }
220 }
221}
222function _getSelectedCell(callback) {
223 var _classPrivateFieldGet6;
224 const highlight = (_classPrivateFieldGet6 = _classPrivateFieldGet(_hot, this).getSelectedRangeLast()) === null || _classPrivateFieldGet6 === void 0 ? void 0 : _classPrivateFieldGet6.highlight;
225 if (!highlight || !_classPrivateFieldGet(_hot, this).selection.isCellVisible(highlight)) {
226 callback(null);
227 return;
228 }
229 const cell = _classPrivateFieldGet(_hot, this).getCell(highlight.row, highlight.col, true);
230 if (cell === null) {
231 _classPrivateFieldGet(_hot, this).addHookOnce('afterScroll', () => {
232 callback(_classPrivateFieldGet(_hot, this).getCell(highlight.row, highlight.col, true));
233 });
234 } else {
235 callback(cell);
236 }
237}
238/**
239 * Manage the browser's focus after each cell selection change.
240 */
241function _focusCell() {
242 _assertClassBrand(_FocusManager_brand, this, _getSelectedCell).call(this, selectedCell => {
243 const {
244 activeElement
245 } = _classPrivateFieldGet(_hot, this).rootDocument;
246
247 // Blurring the `activeElement` removes the unwanted border around the focusable element (#6877)
248 // and resets the `document.activeElement` property. The blurring should happen only when the
249 // previously selected input element has not belonged to the Handsontable editor. If blurring is
250 // triggered for all elements, there is a problem with the disappearing IME editor (#9672).
251 if (activeElement && isOutsideInput(activeElement)) {
252 activeElement.blur();
253 }
254 this.focusOnHighlightedCell(selectedCell);
255 });
256}
257/**
258 * Manage the browser's focus after cell selection end.
259 */
260function _focusEditorElement() {
261 _assertClassBrand(_FocusManager_brand, this, _getSelectedCell).call(this, selectedCell => {
262 if (this.getFocusMode() === FOCUS_MODES.MIXED && selectedCell.nodeName === 'TD') {
263 this.refocusToEditorTextarea();
264 }
265 });
266}
267/**
268 * Update the manager configuration after calling `updateSettings`.
269 *
270 * @param {object} newSettings The new settings passed to the `updateSettings` method.
271 */
272function _onUpdateSettings(newSettings) {
273 if (typeof newSettings.imeFastEdit === 'boolean') {
274 this.setFocusMode(newSettings.imeFastEdit ? FOCUS_MODES.MIXED : FOCUS_MODES.CELL);
275 }
276}
\No newline at end of file