handsontable 16.1.1 → 16.2.0-next-216dbd0-20251112

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.
Files changed (296) hide show
  1. package/3rdparty/walkontable/src/overlays.js +1 -1
  2. package/3rdparty/walkontable/src/overlays.mjs +1 -1
  3. package/3rdparty/walkontable/src/selection/border/border.js +19 -3
  4. package/3rdparty/walkontable/src/selection/border/border.mjs +19 -3
  5. package/CHANGELOG.md +39 -0
  6. package/base.js +2 -2
  7. package/base.mjs +2 -2
  8. package/core/coordsMapper/index.js +11 -0
  9. package/core/coordsMapper/index.mjs +1 -0
  10. package/core/hooks/bucket.js +7 -1
  11. package/core/hooks/bucket.mjs +7 -1
  12. package/core/hooks/constants.js +54 -0
  13. package/core/hooks/constants.mjs +54 -0
  14. package/core/hooks/index.d.ts +6 -0
  15. package/core/index.js +10 -4
  16. package/core/index.mjs +2 -1
  17. package/core.d.ts +3 -2
  18. package/core.js +67 -26
  19. package/core.mjs +56 -15
  20. package/dataMap/metaManager/metaSchema.js +197 -20
  21. package/dataMap/metaManager/metaSchema.mjs +197 -20
  22. package/dist/handsontable.css +175 -3
  23. package/dist/handsontable.full.css +175 -3
  24. package/dist/handsontable.full.js +10864 -8426
  25. package/dist/handsontable.full.min.css +5 -4
  26. package/dist/handsontable.full.min.js +194 -193
  27. package/dist/handsontable.js +7126 -4827
  28. package/dist/handsontable.min.css +4 -4
  29. package/dist/handsontable.min.js +43 -42
  30. package/dist/languages/all.js +168 -21
  31. package/dist/languages/all.min.js +1 -1
  32. package/dist/languages/ar-AR.js +8 -1
  33. package/dist/languages/ar-AR.min.js +1 -1
  34. package/dist/languages/cs-CZ.js +8 -1
  35. package/dist/languages/cs-CZ.min.js +1 -1
  36. package/dist/languages/de-CH.js +8 -1
  37. package/dist/languages/de-CH.min.js +1 -1
  38. package/dist/languages/de-DE.js +8 -1
  39. package/dist/languages/de-DE.min.js +1 -1
  40. package/dist/languages/en-US.js +8 -1
  41. package/dist/languages/en-US.min.js +1 -1
  42. package/dist/languages/es-MX.js +8 -1
  43. package/dist/languages/es-MX.min.js +1 -1
  44. package/dist/languages/fa-IR.js +8 -1
  45. package/dist/languages/fa-IR.min.js +1 -1
  46. package/dist/languages/fr-FR.js +8 -1
  47. package/dist/languages/fr-FR.min.js +1 -1
  48. package/dist/languages/hr-HR.js +8 -1
  49. package/dist/languages/hr-HR.min.js +1 -1
  50. package/dist/languages/it-IT.js +8 -1
  51. package/dist/languages/it-IT.min.js +1 -1
  52. package/dist/languages/ja-JP.js +8 -1
  53. package/dist/languages/ja-JP.min.js +1 -1
  54. package/dist/languages/ko-KR.js +8 -1
  55. package/dist/languages/ko-KR.min.js +1 -1
  56. package/dist/languages/lv-LV.js +8 -1
  57. package/dist/languages/lv-LV.min.js +1 -1
  58. package/dist/languages/nb-NO.js +8 -1
  59. package/dist/languages/nb-NO.min.js +1 -1
  60. package/dist/languages/nl-NL.js +8 -1
  61. package/dist/languages/nl-NL.min.js +1 -1
  62. package/dist/languages/pl-PL.js +8 -1
  63. package/dist/languages/pl-PL.min.js +1 -1
  64. package/dist/languages/pt-BR.js +8 -1
  65. package/dist/languages/pt-BR.min.js +1 -1
  66. package/dist/languages/ru-RU.js +8 -1
  67. package/dist/languages/ru-RU.min.js +1 -1
  68. package/dist/languages/sr-SP.js +8 -1
  69. package/dist/languages/sr-SP.min.js +1 -1
  70. package/dist/languages/zh-CN.js +8 -1
  71. package/dist/languages/zh-CN.min.js +1 -1
  72. package/dist/languages/zh-TW.js +8 -1
  73. package/dist/languages/zh-TW.min.js +1 -1
  74. package/editors/autocompleteEditor/autocompleteEditor.js +21 -1
  75. package/editors/autocompleteEditor/autocompleteEditor.mjs +22 -2
  76. package/focusManager/constants.js +25 -0
  77. package/focusManager/constants.mjs +22 -0
  78. package/focusManager/eventListener.js +107 -0
  79. package/focusManager/eventListener.mjs +103 -0
  80. package/{focusManager.d.ts → focusManager/grid.d.ts} +1 -1
  81. package/{focusManager.js → focusManager/grid.js} +48 -17
  82. package/{focusManager.mjs → focusManager/grid.mjs} +47 -16
  83. package/focusManager/index.d.ts +2 -0
  84. package/focusManager/index.js +20 -0
  85. package/focusManager/index.mjs +20 -0
  86. package/focusManager/scope.js +133 -0
  87. package/focusManager/scope.mjs +129 -0
  88. package/focusManager/scopeManager.d.ts +19 -0
  89. package/focusManager/scopeManager.js +268 -0
  90. package/focusManager/scopeManager.mjs +263 -0
  91. package/focusManager/scopes/grid.js +120 -0
  92. package/focusManager/scopes/grid.mjs +116 -0
  93. package/focusManager/scopes/index.js +13 -0
  94. package/focusManager/scopes/index.mjs +9 -0
  95. package/{utils → focusManager/utils}/focusDetector.js +21 -31
  96. package/{utils → focusManager/utils}/focusDetector.mjs +21 -31
  97. package/focusManager/utils/utils.js +95 -0
  98. package/focusManager/utils/utils.mjs +89 -0
  99. package/helpers/dom/element.js +1 -1
  100. package/helpers/dom/element.mjs +2 -2
  101. package/helpers/dom/event.js +1 -1
  102. package/helpers/dom/event.mjs +1 -1
  103. package/helpers/mixed.js +2 -65
  104. package/helpers/mixed.mjs +2 -63
  105. package/i18n/constants.js +10 -1
  106. package/i18n/constants.mjs +10 -1
  107. package/i18n/languages/ar-AR.js +8 -1
  108. package/i18n/languages/ar-AR.mjs +8 -1
  109. package/i18n/languages/cs-CZ.js +8 -1
  110. package/i18n/languages/cs-CZ.mjs +8 -1
  111. package/i18n/languages/de-CH.js +8 -1
  112. package/i18n/languages/de-CH.mjs +8 -1
  113. package/i18n/languages/de-DE.js +8 -1
  114. package/i18n/languages/de-DE.mjs +8 -1
  115. package/i18n/languages/en-US.js +8 -1
  116. package/i18n/languages/en-US.mjs +8 -1
  117. package/i18n/languages/es-MX.js +8 -1
  118. package/i18n/languages/es-MX.mjs +8 -1
  119. package/i18n/languages/fa-IR.js +8 -1
  120. package/i18n/languages/fa-IR.mjs +8 -1
  121. package/i18n/languages/fr-FR.js +8 -1
  122. package/i18n/languages/fr-FR.mjs +8 -1
  123. package/i18n/languages/hr-HR.js +8 -1
  124. package/i18n/languages/hr-HR.mjs +8 -1
  125. package/i18n/languages/it-IT.js +8 -1
  126. package/i18n/languages/it-IT.mjs +8 -1
  127. package/i18n/languages/ja-JP.js +8 -1
  128. package/i18n/languages/ja-JP.mjs +8 -1
  129. package/i18n/languages/ko-KR.js +8 -1
  130. package/i18n/languages/ko-KR.mjs +8 -1
  131. package/i18n/languages/lv-LV.js +8 -1
  132. package/i18n/languages/lv-LV.mjs +8 -1
  133. package/i18n/languages/nb-NO.js +8 -1
  134. package/i18n/languages/nb-NO.mjs +8 -1
  135. package/i18n/languages/nl-NL.js +8 -1
  136. package/i18n/languages/nl-NL.mjs +8 -1
  137. package/i18n/languages/pl-PL.js +8 -1
  138. package/i18n/languages/pl-PL.mjs +8 -1
  139. package/i18n/languages/pt-BR.js +8 -1
  140. package/i18n/languages/pt-BR.mjs +8 -1
  141. package/i18n/languages/ru-RU.js +8 -1
  142. package/i18n/languages/ru-RU.mjs +8 -1
  143. package/i18n/languages/sr-SP.js +8 -1
  144. package/i18n/languages/sr-SP.mjs +8 -1
  145. package/i18n/languages/zh-CN.js +8 -1
  146. package/i18n/languages/zh-CN.mjs +8 -1
  147. package/i18n/languages/zh-TW.js +8 -1
  148. package/i18n/languages/zh-TW.mjs +8 -1
  149. package/index.d.ts +9 -0
  150. package/languages/all.js +168 -21
  151. package/languages/ar-AR.js +8 -1
  152. package/languages/ar-AR.mjs +8 -1
  153. package/languages/cs-CZ.js +8 -1
  154. package/languages/cs-CZ.mjs +8 -1
  155. package/languages/de-CH.js +8 -1
  156. package/languages/de-CH.mjs +8 -1
  157. package/languages/de-DE.js +8 -1
  158. package/languages/de-DE.mjs +8 -1
  159. package/languages/en-US.js +8 -1
  160. package/languages/en-US.mjs +8 -1
  161. package/languages/es-MX.js +8 -1
  162. package/languages/es-MX.mjs +8 -1
  163. package/languages/fa-IR.js +8 -1
  164. package/languages/fa-IR.mjs +8 -1
  165. package/languages/fr-FR.js +8 -1
  166. package/languages/fr-FR.mjs +8 -1
  167. package/languages/hr-HR.js +8 -1
  168. package/languages/hr-HR.mjs +8 -1
  169. package/languages/index.js +168 -21
  170. package/languages/it-IT.js +8 -1
  171. package/languages/it-IT.mjs +8 -1
  172. package/languages/ja-JP.js +8 -1
  173. package/languages/ja-JP.mjs +8 -1
  174. package/languages/ko-KR.js +8 -1
  175. package/languages/ko-KR.mjs +8 -1
  176. package/languages/lv-LV.js +8 -1
  177. package/languages/lv-LV.mjs +8 -1
  178. package/languages/nb-NO.js +8 -1
  179. package/languages/nb-NO.mjs +8 -1
  180. package/languages/nl-NL.js +8 -1
  181. package/languages/nl-NL.mjs +8 -1
  182. package/languages/pl-PL.js +8 -1
  183. package/languages/pl-PL.mjs +8 -1
  184. package/languages/pt-BR.js +8 -1
  185. package/languages/pt-BR.mjs +8 -1
  186. package/languages/ru-RU.js +8 -1
  187. package/languages/ru-RU.mjs +8 -1
  188. package/languages/sr-SP.js +8 -1
  189. package/languages/sr-SP.mjs +8 -1
  190. package/languages/zh-CN.js +8 -1
  191. package/languages/zh-CN.mjs +8 -1
  192. package/languages/zh-TW.js +8 -1
  193. package/languages/zh-TW.mjs +8 -1
  194. package/package.json +25 -7
  195. package/plugins/autoRowSize/autoRowSize.js +8 -1
  196. package/plugins/autoRowSize/autoRowSize.mjs +8 -1
  197. package/plugins/base/base.js +36 -10
  198. package/plugins/base/base.mjs +36 -10
  199. package/plugins/columnSummary/endpoints.js +13 -3
  200. package/plugins/columnSummary/endpoints.mjs +13 -3
  201. package/plugins/customBorders/customBorders.d.ts +1 -0
  202. package/plugins/customBorders/customBorders.js +32 -2
  203. package/plugins/customBorders/customBorders.mjs +32 -2
  204. package/plugins/dialog/constants.js +7 -0
  205. package/plugins/dialog/constants.mjs +4 -0
  206. package/plugins/dialog/dialog.d.ts +22 -2
  207. package/plugins/dialog/dialog.js +197 -81
  208. package/plugins/dialog/dialog.mjs +196 -81
  209. package/plugins/dialog/templates/base.js +60 -0
  210. package/plugins/dialog/templates/base.mjs +56 -0
  211. package/plugins/dialog/templates/confirm.js +106 -0
  212. package/plugins/dialog/templates/confirm.mjs +102 -0
  213. package/plugins/dialog/templates/index.js +6 -0
  214. package/plugins/dialog/templates/index.mjs +4 -0
  215. package/plugins/dialog/ui.js +125 -41
  216. package/plugins/dialog/ui.mjs +119 -35
  217. package/plugins/emptyDataState/emptyDataState.d.ts +24 -0
  218. package/plugins/emptyDataState/emptyDataState.js +526 -0
  219. package/plugins/emptyDataState/emptyDataState.mjs +521 -0
  220. package/plugins/emptyDataState/index.d.ts +1 -0
  221. package/plugins/emptyDataState/index.js +7 -0
  222. package/plugins/emptyDataState/index.mjs +1 -0
  223. package/plugins/emptyDataState/ui.js +282 -0
  224. package/plugins/emptyDataState/ui.mjs +278 -0
  225. package/plugins/filters/component/value.js +16 -1
  226. package/plugins/filters/component/value.mjs +16 -1
  227. package/plugins/filters/filters.d.ts +5 -1
  228. package/plugins/filters/filters.js +22 -1
  229. package/plugins/filters/filters.mjs +22 -1
  230. package/plugins/filters/ui/multipleSelect.js +90 -79
  231. package/plugins/filters/ui/multipleSelect.mjs +90 -79
  232. package/plugins/index.d.ts +3 -0
  233. package/plugins/index.js +3 -0
  234. package/plugins/index.mjs +3 -1
  235. package/plugins/mergeCells/utils.js +1 -5
  236. package/plugins/mergeCells/utils.mjs +1 -5
  237. package/plugins/pagination/pagination.js +37 -175
  238. package/plugins/pagination/pagination.mjs +37 -175
  239. package/plugins/pagination/strategies/autoPageSize.js +2 -2
  240. package/plugins/pagination/strategies/autoPageSize.mjs +2 -2
  241. package/plugins/pagination/ui.js +6 -10
  242. package/plugins/pagination/ui.mjs +7 -11
  243. package/plugins/stretchColumns/calculator.js +3 -1
  244. package/plugins/stretchColumns/calculator.mjs +3 -1
  245. package/plugins/undoRedo/undoRedo.js +16 -6
  246. package/plugins/undoRedo/undoRedo.mjs +16 -5
  247. package/renderers/checkboxRenderer/checkboxRenderer.js +12 -15
  248. package/renderers/checkboxRenderer/checkboxRenderer.mjs +12 -15
  249. package/selection/selection.js +1 -1
  250. package/selection/selection.mjs +1 -1
  251. package/settings.d.ts +3 -0
  252. package/shortcutContexts/commands/index.js +2 -1
  253. package/shortcutContexts/commands/index.mjs +2 -1
  254. package/shortcutContexts/commands/tabNavigation.js +51 -0
  255. package/shortcutContexts/commands/tabNavigation.mjs +48 -0
  256. package/shortcutContexts/constants.js +16 -1
  257. package/shortcutContexts/constants.mjs +16 -1
  258. package/shortcutContexts/editor.js +2 -2
  259. package/shortcutContexts/editor.mjs +3 -3
  260. package/shortcutContexts/grid.js +19 -3
  261. package/shortcutContexts/grid.mjs +20 -4
  262. package/shortcuts/manager.d.ts +1 -0
  263. package/shortcuts/manager.js +17 -2
  264. package/shortcuts/manager.mjs +17 -2
  265. package/styles/handsontable.css +192 -35
  266. package/styles/handsontable.min.css +3 -3
  267. package/styles/ht-icons-horizon.css +233 -0
  268. package/styles/ht-icons-horizon.min.css +30 -0
  269. package/styles/ht-icons-main.css +233 -0
  270. package/styles/ht-icons-main.min.css +30 -0
  271. package/styles/ht-theme-classic-no-icons.css +399 -0
  272. package/styles/ht-theme-classic-no-icons.min.css +30 -0
  273. package/styles/ht-theme-classic.css +308 -556
  274. package/styles/ht-theme-classic.min.css +3 -3
  275. package/styles/ht-theme-horizon-no-icons.css +405 -0
  276. package/styles/ht-theme-horizon-no-icons.min.css +30 -0
  277. package/styles/ht-theme-horizon.css +312 -556
  278. package/styles/ht-theme-horizon.min.css +3 -3
  279. package/styles/ht-theme-main-no-icons.css +396 -0
  280. package/styles/ht-theme-main-no-icons.min.css +30 -0
  281. package/styles/ht-theme-main.css +303 -556
  282. package/styles/ht-theme-main.min.css +3 -3
  283. package/tableView.js +23 -5
  284. package/tableView.mjs +23 -5
  285. package/utils/dataStructures/uniqueMap.js +10 -0
  286. package/utils/dataStructures/uniqueMap.mjs +10 -0
  287. package/utils/ghostTable.js +0 -3
  288. package/utils/ghostTable.mjs +0 -3
  289. package/utils/stylesHandler.js +19 -4
  290. package/utils/stylesHandler.mjs +19 -4
  291. package/core/focusCatcher/index.js +0 -131
  292. package/core/focusCatcher/index.mjs +0 -127
  293. package/core/focusCatcher/utils.js +0 -31
  294. package/core/focusCatcher/utils.mjs +0 -27
  295. package/plugins/pagination/focusController.js +0 -27
  296. package/plugins/pagination/focusController.mjs +0 -23
@@ -0,0 +1,268 @@
1
+ "use strict";
2
+
3
+ exports.__esModule = true;
4
+ exports.createFocusScopeManager = createFocusScopeManager;
5
+ require("core-js/modules/es.error.cause.js");
6
+ require("core-js/modules/esnext.iterator.constructor.js");
7
+ require("core-js/modules/esnext.iterator.filter.js");
8
+ require("core-js/modules/esnext.iterator.for-each.js");
9
+ var _uniqueMap = require("../utils/dataStructures/uniqueMap");
10
+ var _scope = require("./scope");
11
+ var _eventListener = require("./eventListener");
12
+ var _constants = require("./constants");
13
+ var _element = require("../helpers/dom/element");
14
+ /**
15
+ * @typedef {object} FocusScopeManager
16
+ * @property {function(): string | null} getActiveScopeId Returns the ID of the active scope.
17
+ * @property {function(string, HTMLElement, object): void} registerScope Registers a new focus scope.
18
+ * @property {function(string): void} unregisterScope Unregisters a scope by its ID.
19
+ * @property {function(string): void} activateScope Activates a focus scope by its ID.
20
+ * @property {function(string): void} deactivateScope Deactivates a scope by its ID.
21
+ * @property {function(): void} destroy Destroys the focus scope manager.
22
+ */
23
+
24
+ /**
25
+ * Creates a focus scope manager for a Handsontable instance. The manager handles focus
26
+ * scopes by listening to keydown, focusin, and click events on the document. Based on
27
+ * the currently focused element, it activates or deactivates the appropriate scope.
28
+ * Focus scope contains its own boundaries and logic that once activated allows to focus
29
+ * specific focusable element within the scope container element and/or switch to specific
30
+ * shortcuts context.
31
+ *
32
+ * The manager also automatically updates the {@link Core#isListening} state of the Handsontable
33
+ * instance based on the current state of the scopes.
34
+ *
35
+ * @alias FocusScopeManager
36
+ * @class FocusScopeManager
37
+ * @param {Core} hotInstance The Handsontable instance.
38
+ */
39
+ function createFocusScopeManager(hotInstance) {
40
+ const SCOPES = (0, _uniqueMap.createUniqueMap)({
41
+ errorIdExists: name => `The "${name}" focus scope is already registered.`
42
+ });
43
+ const shortcutManager = hotInstance.getShortcutManager();
44
+ let activeScope = null;
45
+
46
+ /**
47
+ * Returns the ID of the active scope.
48
+ *
49
+ * @memberof FocusScopeManager#
50
+ * @returns {string | null} The ID of the active scope.
51
+ */
52
+ function getActiveScopeId() {
53
+ if (!activeScope) {
54
+ return null;
55
+ }
56
+ return SCOPES.getId(activeScope);
57
+ }
58
+
59
+ /**
60
+ * Registers a new focus scope.
61
+ *
62
+ * @memberof FocusScopeManager#
63
+ * @param {string} scopeId Unique identifier for the scope.
64
+ * @param {HTMLElement} container Container element for the scope.
65
+ * @param {object} [options] Configuration options.
66
+ * @param {string} [options.shortcutsContextName='grid'] The name of the shortcuts context to switch to when
67
+ * the scope is activated.
68
+ * @param {'modal' | 'inline'} [options.type='inline'] The type of the scope:<br/>
69
+ * - `modal`: The scope is modal and blocks the rest of the grid from receiving focus.<br/>
70
+ * - `inline`: The scope is inline and allows the rest of the grid to receive focus in the order of the rendered elements in the DOM.
71
+ * @param {function(): boolean} [options.runOnlyIf] Whether the scope is enabled or not depends on the custom logic.
72
+ * @param {function(HTMLElement): boolean} [options.contains] Whether the target element is within the scope. If the option is not
73
+ * provided, the scope will be activated if the target element is within the container element.
74
+ * @param {function(): void} [options.onActivate] Callback function to be called when the scope is activated.
75
+ * The first argument is the source of the activation:<br/>
76
+ * - `unknown`: The scope is activated by an unknown source.<br/>
77
+ * - `click`: The scope is activated by a click event.<br/>
78
+ * - `tab_from_above`: The scope is activated by a tab key press.<br/>
79
+ * - `tab_from_below`: The scope is activated by a shift+tab key press.
80
+ * @param {function(): void} [options.onDeactivate] Callback function to be called when the scope is deactivated.
81
+ *
82
+ * @example
83
+ * For regular element (inline scope)
84
+ *
85
+ * ```js
86
+ * hot.getFocusScopeManager().registerScope('myPluginName', containerElement, {
87
+ * shortcutsContextName: 'plugin:myPluginName',
88
+ * onActivate: (focusSource) => {
89
+ * // Focus the internal focusable element within the plugin UI element
90
+ * },
91
+ * });
92
+ * ```
93
+ *
94
+ * or for modal scope
95
+ *
96
+ * ```js
97
+ * hot.getFocusScopeManager().registerScope('myPluginName', containerElement, {
98
+ * shortcutsContextName: 'plugin:myPluginName',
99
+ * type: 'modal',
100
+ * runOnlyIf: () => isDialogOpened(),
101
+ * onActivate: (focusSource) => {
102
+ * // Focus the internal focusable element within the plugin UI element
103
+ * },
104
+ * });
105
+ * ```
106
+ */
107
+ function registerScope(scopeId, container) {
108
+ let options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
109
+ if (SCOPES.hasItem(scopeId)) {
110
+ throw new Error(`Scope with id "${scopeId}" already registered`);
111
+ }
112
+ const scope = (0, _scope.createFocusScope)(hotInstance, container, options);
113
+ SCOPES.addItem(scopeId, scope);
114
+ shortcutManager.getOrCreateContext(scope.getShortcutsContextName());
115
+ }
116
+
117
+ /**
118
+ * Unregisters a scope completely.
119
+ *
120
+ * @memberof FocusScopeManager#
121
+ * @param {string} scopeId The scope to remove.
122
+ */
123
+ function unregisterScope(scopeId) {
124
+ if (!SCOPES.hasItem(scopeId)) {
125
+ throw new Error(`Scope with id "${scopeId}" not found`);
126
+ }
127
+ const scope = SCOPES.getItem(scopeId);
128
+ scope.destroy();
129
+ SCOPES.removeItem(scopeId);
130
+ }
131
+
132
+ /**
133
+ * Activates a focus scope by its ID.
134
+ *
135
+ * @memberof FocusScopeManager#
136
+ * @alias FocusScopeManager#activateScope
137
+ * @param {string} scopeId The ID of the scope to activate.
138
+ */
139
+ function activateScopeById(scopeId) {
140
+ if (!SCOPES.hasItem(scopeId)) {
141
+ throw new Error(`Scope with id "${scopeId}" not found`);
142
+ }
143
+ activateScope(SCOPES.getItem(scopeId));
144
+ }
145
+
146
+ /**
147
+ * Deactivates a scope by its ID.
148
+ *
149
+ * @memberof FocusScopeManager#
150
+ * @alias FocusScopeManager#deactivateScope
151
+ * @param {string} scopeId The ID of the scope to deactivate.
152
+ */
153
+ function deactivateScopeById(scopeId) {
154
+ if (!SCOPES.hasItem(scopeId)) {
155
+ throw new Error(`Scope with id "${scopeId}" not found`);
156
+ }
157
+ deactivateScope(SCOPES.getItem(scopeId));
158
+ }
159
+
160
+ /**
161
+ * Activates a specific scope.
162
+ *
163
+ * @param {object} scope The scope to activate.
164
+ * @param {'unknown' | 'click' | 'tab_from_above' | 'tab_from_below'} focusSource The source of the focus event.
165
+ */
166
+ function activateScope(scope) {
167
+ let focusSource = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : _constants.FOCUS_SOURCES.UNKNOWN;
168
+ if (activeScope === scope) {
169
+ return;
170
+ }
171
+ if (activeScope !== null) {
172
+ deactivateScope(activeScope);
173
+ }
174
+ activeScope = scope;
175
+ activeScope.activate(focusSource);
176
+ shortcutManager.setActiveContextName(scope.getShortcutsContextName());
177
+ }
178
+
179
+ /**
180
+ * Deactivates a scope by its ID.
181
+ *
182
+ * @param {object} scope The scope to deactivate.
183
+ */
184
+ function deactivateScope(scope) {
185
+ updateScopesFocusVisibilityState();
186
+ if (activeScope !== scope) {
187
+ return;
188
+ }
189
+ activeScope = null;
190
+ scope.deactivate();
191
+ }
192
+
193
+ /**
194
+ * Updates the focus scopes state by enabling or disabling them or their focus catchers to make sure
195
+ * that the next native focus move won't be disturbed.
196
+ */
197
+ function updateScopesFocusVisibilityState() {
198
+ const scopes = SCOPES.getValues();
199
+ const modalScopes = scopes.filter(scope => scope.runOnlyIf() && scope.getType() === 'modal');
200
+ scopes.forEach(scope => {
201
+ if (modalScopes.length > 0 && modalScopes.includes(scope) || modalScopes.length === 0 || scope.hasContainerDetached()) {
202
+ scope.enable();
203
+ } else {
204
+ scope.disable();
205
+ }
206
+ if (scope === activeScope) {
207
+ if (scope.contains(hotInstance.rootDocument.activeElement)) {
208
+ scope.deactivateFocusCatchers();
209
+ } else {
210
+ scope.activateFocusCatchers();
211
+ }
212
+ } else if (scope.runOnlyIf()) {
213
+ scope.activateFocusCatchers();
214
+ } else {
215
+ scope.deactivateFocusCatchers();
216
+ }
217
+ });
218
+ }
219
+
220
+ /**
221
+ * Activates or deactivates the appropriate scope based on the target element that was
222
+ * triggered by the focus or click event.
223
+ *
224
+ * @param {HTMLElement} target The target element.
225
+ * @param {'unknown' | 'click' | 'tab_from_above' | 'tab_from_below'} focusSource The source of the focus event.
226
+ */
227
+ function processScopes(target, focusSource) {
228
+ if (!target.isConnected || !(0, _element.isVisible)(target)) {
229
+ return;
230
+ }
231
+ const allEnabledScopes = SCOPES.getValues().filter(scope => scope.runOnlyIf());
232
+ let hasActiveScope = false;
233
+ allEnabledScopes.forEach(scope => {
234
+ if (!hasActiveScope && scope.contains(target)) {
235
+ hasActiveScope = true;
236
+ if (focusSource !== _constants.FOCUS_SOURCES.UNKNOWN) {
237
+ hotInstance.listen();
238
+ }
239
+ activateScope(scope, focusSource);
240
+ }
241
+ });
242
+ if (!hasActiveScope && activeScope) {
243
+ deactivateScope(activeScope);
244
+ hotInstance.unlisten();
245
+ }
246
+ }
247
+ const eventListener = (0, _eventListener.useEventListener)(hotInstance.rootWindow, {
248
+ onFocus: event => {
249
+ var _event$target$dataset;
250
+ processScopes(event.target, (_event$target$dataset = event.target.dataset.htFocusSource) !== null && _event$target$dataset !== void 0 ? _event$target$dataset : _constants.FOCUS_SOURCES.UNKNOWN);
251
+ },
252
+ onClick: event => {
253
+ processScopes(event.target, _constants.FOCUS_SOURCES.CLICK);
254
+ },
255
+ onTabKeyDown: () => {
256
+ updateScopesFocusVisibilityState();
257
+ }
258
+ });
259
+ eventListener.mount();
260
+ return {
261
+ getActiveScopeId,
262
+ registerScope,
263
+ unregisterScope,
264
+ activateScope: scopeId => activateScopeById(scopeId),
265
+ deactivateScope: scopeId => deactivateScopeById(scopeId),
266
+ destroy: () => eventListener.unmount()
267
+ };
268
+ }
@@ -0,0 +1,263 @@
1
+ import "core-js/modules/es.error.cause.js";
2
+ import "core-js/modules/esnext.iterator.constructor.js";
3
+ import "core-js/modules/esnext.iterator.filter.js";
4
+ import "core-js/modules/esnext.iterator.for-each.js";
5
+ import { createUniqueMap } from "../utils/dataStructures/uniqueMap.mjs";
6
+ import { createFocusScope } from "./scope.mjs";
7
+ import { useEventListener } from "./eventListener.mjs";
8
+ import { FOCUS_SOURCES } from "./constants.mjs";
9
+ import { isVisible } from "../helpers/dom/element.mjs";
10
+ /**
11
+ * @typedef {object} FocusScopeManager
12
+ * @property {function(): string | null} getActiveScopeId Returns the ID of the active scope.
13
+ * @property {function(string, HTMLElement, object): void} registerScope Registers a new focus scope.
14
+ * @property {function(string): void} unregisterScope Unregisters a scope by its ID.
15
+ * @property {function(string): void} activateScope Activates a focus scope by its ID.
16
+ * @property {function(string): void} deactivateScope Deactivates a scope by its ID.
17
+ * @property {function(): void} destroy Destroys the focus scope manager.
18
+ */
19
+ /**
20
+ * Creates a focus scope manager for a Handsontable instance. The manager handles focus
21
+ * scopes by listening to keydown, focusin, and click events on the document. Based on
22
+ * the currently focused element, it activates or deactivates the appropriate scope.
23
+ * Focus scope contains its own boundaries and logic that once activated allows to focus
24
+ * specific focusable element within the scope container element and/or switch to specific
25
+ * shortcuts context.
26
+ *
27
+ * The manager also automatically updates the {@link Core#isListening} state of the Handsontable
28
+ * instance based on the current state of the scopes.
29
+ *
30
+ * @alias FocusScopeManager
31
+ * @class FocusScopeManager
32
+ * @param {Core} hotInstance The Handsontable instance.
33
+ */
34
+ export function createFocusScopeManager(hotInstance) {
35
+ const SCOPES = createUniqueMap({
36
+ errorIdExists: name => `The "${name}" focus scope is already registered.`
37
+ });
38
+ const shortcutManager = hotInstance.getShortcutManager();
39
+ let activeScope = null;
40
+
41
+ /**
42
+ * Returns the ID of the active scope.
43
+ *
44
+ * @memberof FocusScopeManager#
45
+ * @returns {string | null} The ID of the active scope.
46
+ */
47
+ function getActiveScopeId() {
48
+ if (!activeScope) {
49
+ return null;
50
+ }
51
+ return SCOPES.getId(activeScope);
52
+ }
53
+
54
+ /**
55
+ * Registers a new focus scope.
56
+ *
57
+ * @memberof FocusScopeManager#
58
+ * @param {string} scopeId Unique identifier for the scope.
59
+ * @param {HTMLElement} container Container element for the scope.
60
+ * @param {object} [options] Configuration options.
61
+ * @param {string} [options.shortcutsContextName='grid'] The name of the shortcuts context to switch to when
62
+ * the scope is activated.
63
+ * @param {'modal' | 'inline'} [options.type='inline'] The type of the scope:<br/>
64
+ * - `modal`: The scope is modal and blocks the rest of the grid from receiving focus.<br/>
65
+ * - `inline`: The scope is inline and allows the rest of the grid to receive focus in the order of the rendered elements in the DOM.
66
+ * @param {function(): boolean} [options.runOnlyIf] Whether the scope is enabled or not depends on the custom logic.
67
+ * @param {function(HTMLElement): boolean} [options.contains] Whether the target element is within the scope. If the option is not
68
+ * provided, the scope will be activated if the target element is within the container element.
69
+ * @param {function(): void} [options.onActivate] Callback function to be called when the scope is activated.
70
+ * The first argument is the source of the activation:<br/>
71
+ * - `unknown`: The scope is activated by an unknown source.<br/>
72
+ * - `click`: The scope is activated by a click event.<br/>
73
+ * - `tab_from_above`: The scope is activated by a tab key press.<br/>
74
+ * - `tab_from_below`: The scope is activated by a shift+tab key press.
75
+ * @param {function(): void} [options.onDeactivate] Callback function to be called when the scope is deactivated.
76
+ *
77
+ * @example
78
+ * For regular element (inline scope)
79
+ *
80
+ * ```js
81
+ * hot.getFocusScopeManager().registerScope('myPluginName', containerElement, {
82
+ * shortcutsContextName: 'plugin:myPluginName',
83
+ * onActivate: (focusSource) => {
84
+ * // Focus the internal focusable element within the plugin UI element
85
+ * },
86
+ * });
87
+ * ```
88
+ *
89
+ * or for modal scope
90
+ *
91
+ * ```js
92
+ * hot.getFocusScopeManager().registerScope('myPluginName', containerElement, {
93
+ * shortcutsContextName: 'plugin:myPluginName',
94
+ * type: 'modal',
95
+ * runOnlyIf: () => isDialogOpened(),
96
+ * onActivate: (focusSource) => {
97
+ * // Focus the internal focusable element within the plugin UI element
98
+ * },
99
+ * });
100
+ * ```
101
+ */
102
+ function registerScope(scopeId, container) {
103
+ let options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
104
+ if (SCOPES.hasItem(scopeId)) {
105
+ throw new Error(`Scope with id "${scopeId}" already registered`);
106
+ }
107
+ const scope = createFocusScope(hotInstance, container, options);
108
+ SCOPES.addItem(scopeId, scope);
109
+ shortcutManager.getOrCreateContext(scope.getShortcutsContextName());
110
+ }
111
+
112
+ /**
113
+ * Unregisters a scope completely.
114
+ *
115
+ * @memberof FocusScopeManager#
116
+ * @param {string} scopeId The scope to remove.
117
+ */
118
+ function unregisterScope(scopeId) {
119
+ if (!SCOPES.hasItem(scopeId)) {
120
+ throw new Error(`Scope with id "${scopeId}" not found`);
121
+ }
122
+ const scope = SCOPES.getItem(scopeId);
123
+ scope.destroy();
124
+ SCOPES.removeItem(scopeId);
125
+ }
126
+
127
+ /**
128
+ * Activates a focus scope by its ID.
129
+ *
130
+ * @memberof FocusScopeManager#
131
+ * @alias FocusScopeManager#activateScope
132
+ * @param {string} scopeId The ID of the scope to activate.
133
+ */
134
+ function activateScopeById(scopeId) {
135
+ if (!SCOPES.hasItem(scopeId)) {
136
+ throw new Error(`Scope with id "${scopeId}" not found`);
137
+ }
138
+ activateScope(SCOPES.getItem(scopeId));
139
+ }
140
+
141
+ /**
142
+ * Deactivates a scope by its ID.
143
+ *
144
+ * @memberof FocusScopeManager#
145
+ * @alias FocusScopeManager#deactivateScope
146
+ * @param {string} scopeId The ID of the scope to deactivate.
147
+ */
148
+ function deactivateScopeById(scopeId) {
149
+ if (!SCOPES.hasItem(scopeId)) {
150
+ throw new Error(`Scope with id "${scopeId}" not found`);
151
+ }
152
+ deactivateScope(SCOPES.getItem(scopeId));
153
+ }
154
+
155
+ /**
156
+ * Activates a specific scope.
157
+ *
158
+ * @param {object} scope The scope to activate.
159
+ * @param {'unknown' | 'click' | 'tab_from_above' | 'tab_from_below'} focusSource The source of the focus event.
160
+ */
161
+ function activateScope(scope) {
162
+ let focusSource = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : FOCUS_SOURCES.UNKNOWN;
163
+ if (activeScope === scope) {
164
+ return;
165
+ }
166
+ if (activeScope !== null) {
167
+ deactivateScope(activeScope);
168
+ }
169
+ activeScope = scope;
170
+ activeScope.activate(focusSource);
171
+ shortcutManager.setActiveContextName(scope.getShortcutsContextName());
172
+ }
173
+
174
+ /**
175
+ * Deactivates a scope by its ID.
176
+ *
177
+ * @param {object} scope The scope to deactivate.
178
+ */
179
+ function deactivateScope(scope) {
180
+ updateScopesFocusVisibilityState();
181
+ if (activeScope !== scope) {
182
+ return;
183
+ }
184
+ activeScope = null;
185
+ scope.deactivate();
186
+ }
187
+
188
+ /**
189
+ * Updates the focus scopes state by enabling or disabling them or their focus catchers to make sure
190
+ * that the next native focus move won't be disturbed.
191
+ */
192
+ function updateScopesFocusVisibilityState() {
193
+ const scopes = SCOPES.getValues();
194
+ const modalScopes = scopes.filter(scope => scope.runOnlyIf() && scope.getType() === 'modal');
195
+ scopes.forEach(scope => {
196
+ if (modalScopes.length > 0 && modalScopes.includes(scope) || modalScopes.length === 0 || scope.hasContainerDetached()) {
197
+ scope.enable();
198
+ } else {
199
+ scope.disable();
200
+ }
201
+ if (scope === activeScope) {
202
+ if (scope.contains(hotInstance.rootDocument.activeElement)) {
203
+ scope.deactivateFocusCatchers();
204
+ } else {
205
+ scope.activateFocusCatchers();
206
+ }
207
+ } else if (scope.runOnlyIf()) {
208
+ scope.activateFocusCatchers();
209
+ } else {
210
+ scope.deactivateFocusCatchers();
211
+ }
212
+ });
213
+ }
214
+
215
+ /**
216
+ * Activates or deactivates the appropriate scope based on the target element that was
217
+ * triggered by the focus or click event.
218
+ *
219
+ * @param {HTMLElement} target The target element.
220
+ * @param {'unknown' | 'click' | 'tab_from_above' | 'tab_from_below'} focusSource The source of the focus event.
221
+ */
222
+ function processScopes(target, focusSource) {
223
+ if (!target.isConnected || !isVisible(target)) {
224
+ return;
225
+ }
226
+ const allEnabledScopes = SCOPES.getValues().filter(scope => scope.runOnlyIf());
227
+ let hasActiveScope = false;
228
+ allEnabledScopes.forEach(scope => {
229
+ if (!hasActiveScope && scope.contains(target)) {
230
+ hasActiveScope = true;
231
+ if (focusSource !== FOCUS_SOURCES.UNKNOWN) {
232
+ hotInstance.listen();
233
+ }
234
+ activateScope(scope, focusSource);
235
+ }
236
+ });
237
+ if (!hasActiveScope && activeScope) {
238
+ deactivateScope(activeScope);
239
+ hotInstance.unlisten();
240
+ }
241
+ }
242
+ const eventListener = useEventListener(hotInstance.rootWindow, {
243
+ onFocus: event => {
244
+ var _event$target$dataset;
245
+ processScopes(event.target, (_event$target$dataset = event.target.dataset.htFocusSource) !== null && _event$target$dataset !== void 0 ? _event$target$dataset : FOCUS_SOURCES.UNKNOWN);
246
+ },
247
+ onClick: event => {
248
+ processScopes(event.target, FOCUS_SOURCES.CLICK);
249
+ },
250
+ onTabKeyDown: () => {
251
+ updateScopesFocusVisibilityState();
252
+ }
253
+ });
254
+ eventListener.mount();
255
+ return {
256
+ getActiveScopeId,
257
+ registerScope,
258
+ unregisterScope,
259
+ activateScope: scopeId => activateScopeById(scopeId),
260
+ deactivateScope: scopeId => deactivateScopeById(scopeId),
261
+ destroy: () => eventListener.unmount()
262
+ };
263
+ }
@@ -0,0 +1,120 @@
1
+ "use strict";
2
+
3
+ exports.__esModule = true;
4
+ exports.focusGridScope = focusGridScope;
5
+ var _utils = require("../utils/utils");
6
+ var _shortcutContexts = require("../../shortcutContexts");
7
+ /**
8
+ * @param {Handsontable} hot The Handsontable instance.
9
+ */
10
+ function focusGridScope(hot) {
11
+ var _hot$rootGridElement;
12
+ const clampCoordsIfNeeded = (0, _utils.normalizeCoordsIfNeeded)(hot);
13
+ const rowWrapState = {
14
+ wrapped: false,
15
+ flipped: false
16
+ };
17
+ let recentlyAddedFocusCoords;
18
+ let isSavingCoordsEnabled = true;
19
+ let isEmptyDataStateActive = false;
20
+ hot.addHook('afterSelection', () => {
21
+ if (isSavingCoordsEnabled) {
22
+ var _hot$getSelectedRange;
23
+ recentlyAddedFocusCoords = (_hot$getSelectedRange = hot.getSelectedRangeActive()) === null || _hot$getSelectedRange === void 0 ? void 0 : _hot$getSelectedRange.highlight;
24
+ }
25
+ });
26
+ hot.addHook('beforeRowWrap', (interruptedByAutoInsertMode, newCoords, isFlipped) => {
27
+ rowWrapState.wrapped = true;
28
+ rowWrapState.flipped = isFlipped;
29
+ });
30
+ hot.addHook('beforeEmptyDataStateShow', () => {
31
+ isEmptyDataStateActive = true;
32
+ });
33
+ hot.addHook('beforeEmptyDataStateHide', () => {
34
+ isEmptyDataStateActive = false;
35
+ });
36
+ const context = hot.getShortcutManager().getContext(_shortcutContexts.GRID_SCOPE);
37
+ context.addShortcuts([{
38
+ keys: [['Tab'], ['Shift', 'Tab']],
39
+ preventDefault: false,
40
+ stopPropagation: false,
41
+ relativeToGroup: _shortcutContexts.GRID_GROUP,
42
+ group: _shortcutContexts.GRID_TAB_NAVIGATION_GROUP,
43
+ position: 'before',
44
+ callback() {
45
+ const {
46
+ tabNavigation
47
+ } = hot.getSettings();
48
+ if (hot.getSelectedRangeActive() && !tabNavigation) {
49
+ isSavingCoordsEnabled = false;
50
+ }
51
+ }
52
+ }, {
53
+ keys: [['Tab'], ['Shift', 'Tab']],
54
+ preventDefault: false,
55
+ stopPropagation: false,
56
+ relativeToGroup: _shortcutContexts.GRID_GROUP,
57
+ group: _shortcutContexts.GRID_TAB_NAVIGATION_GROUP,
58
+ callback(event) {
59
+ const {
60
+ tabNavigation,
61
+ autoWrapRow
62
+ } = hot.getSettings();
63
+ isSavingCoordsEnabled = true;
64
+ if (!tabNavigation || !hot.selection.isSelected() || autoWrapRow && rowWrapState.wrapped && rowWrapState.flipped || !autoWrapRow && rowWrapState.wrapped) {
65
+ if (autoWrapRow && rowWrapState.wrapped && rowWrapState.flipped) {
66
+ recentlyAddedFocusCoords = event.shiftKey ? (0, _utils.getMostTopStartPosition)(hot) : (0, _utils.getMostBottomEndPosition)(hot);
67
+ }
68
+ rowWrapState.wrapped = false;
69
+ rowWrapState.flipped = false;
70
+ }
71
+ },
72
+ position: 'after'
73
+ }]);
74
+ const container = (_hot$rootGridElement = hot.rootGridElement) !== null && _hot$rootGridElement !== void 0 ? _hot$rootGridElement : hot.rootElement;
75
+ hot.getFocusScopeManager().registerScope('grid', container, {
76
+ contains: target => {
77
+ if (container === target || container.contains(target)) {
78
+ return true;
79
+ }
80
+ if (target.closest('.htMenu') !== null) {
81
+ // TODO: Skip switching focus scope to 'grid' for context and dropdown menus since
82
+ // focus management is not implemented for them. Their focus management
83
+ // is handled manually.
84
+ return false;
85
+ }
86
+ return hot.rootPortalElement.contains(target);
87
+ },
88
+ runOnlyIf: () => {
89
+ const {
90
+ navigableHeaders
91
+ } = hot.getSettings();
92
+ if ((isEmptyDataStateActive || !navigableHeaders) && hot.countRenderedRows() === 0 && hot.countRenderedCols() === 0 && hot.countRowHeaders() > 0 && hot.countColHeaders() > 0) {
93
+ // When the corner is only rendered, and the EmptyDataState is active, deactivate the scope.
94
+ return false;
95
+ }
96
+ return !navigableHeaders && hot.countRenderedRows() > 0 && hot.countRenderedCols() > 0 || navigableHeaders && (hot.countRowHeaders() > 0 || hot.countColHeaders() > 0);
97
+ },
98
+ onActivate: focusSource => {
99
+ if (focusSource === 'tab_from_above') {
100
+ var _clampCoordsIfNeeded;
101
+ const mostTopStartCoords = (_clampCoordsIfNeeded = clampCoordsIfNeeded(recentlyAddedFocusCoords)) !== null && _clampCoordsIfNeeded !== void 0 ? _clampCoordsIfNeeded : (0, _utils.getMostTopStartPosition)(hot);
102
+ if (mostTopStartCoords) {
103
+ const result = hot.runHooks('modifyFocusOnTabNavigation', 'from_above', mostTopStartCoords);
104
+ if (result !== false) {
105
+ hot.selectCell(mostTopStartCoords.row, mostTopStartCoords.col);
106
+ }
107
+ }
108
+ } else if (focusSource === 'tab_from_below') {
109
+ var _clampCoordsIfNeeded2;
110
+ const mostBottomEndCoords = (_clampCoordsIfNeeded2 = clampCoordsIfNeeded(recentlyAddedFocusCoords)) !== null && _clampCoordsIfNeeded2 !== void 0 ? _clampCoordsIfNeeded2 : (0, _utils.getMostBottomEndPosition)(hot);
111
+ if (mostBottomEndCoords) {
112
+ const result = hot.runHooks('modifyFocusOnTabNavigation', 'from_below', mostBottomEndCoords);
113
+ if (result !== false) {
114
+ hot.selectCell(mostBottomEndCoords.row, mostBottomEndCoords.col);
115
+ }
116
+ }
117
+ }
118
+ }
119
+ });
120
+ }