@theia/core 1.71.0-next.6 → 1.71.0-next.64

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 (184) hide show
  1. package/README.md +13 -13
  2. package/i18n/nls.cs.json +4 -4
  3. package/i18n/nls.de.json +4 -4
  4. package/i18n/nls.es.json +4 -4
  5. package/i18n/nls.fr.json +4 -4
  6. package/i18n/nls.hu.json +4 -4
  7. package/i18n/nls.it.json +4 -4
  8. package/i18n/nls.ja.json +4 -4
  9. package/i18n/nls.ko.json +4 -4
  10. package/i18n/nls.pl.json +4 -4
  11. package/i18n/nls.pt-br.json +4 -4
  12. package/i18n/nls.ru.json +4 -4
  13. package/i18n/nls.tr.json +4 -4
  14. package/i18n/nls.zh-cn.json +4 -4
  15. package/i18n/nls.zh-tw.json +4 -4
  16. package/lib/browser/catalog.json +149 -8
  17. package/lib/browser/common-frontend-contribution.js +2 -2
  18. package/lib/browser/common-frontend-contribution.js.map +1 -1
  19. package/lib/browser/components/card.d.ts.map +1 -1
  20. package/lib/browser/components/card.js +11 -3
  21. package/lib/browser/components/card.js.map +1 -1
  22. package/lib/browser/connection-status-service.js +1 -1
  23. package/lib/browser/connection-status-service.js.map +1 -1
  24. package/lib/browser/keyboard/index.d.ts +1 -0
  25. package/lib/browser/keyboard/index.d.ts.map +1 -1
  26. package/lib/browser/keyboard/index.js +1 -0
  27. package/lib/browser/keyboard/index.js.map +1 -1
  28. package/lib/browser/keyboard/keyboard-utils.d.ts +17 -0
  29. package/lib/browser/keyboard/keyboard-utils.d.ts.map +1 -0
  30. package/lib/browser/keyboard/keyboard-utils.js +40 -0
  31. package/lib/browser/keyboard/keyboard-utils.js.map +1 -0
  32. package/lib/browser/menu/browser-menu-plugin.d.ts.map +1 -1
  33. package/lib/browser/menu/browser-menu-plugin.js +8 -1
  34. package/lib/browser/menu/browser-menu-plugin.js.map +1 -1
  35. package/lib/browser/messaging/messaging-frontend-module.d.ts.map +1 -1
  36. package/lib/browser/messaging/messaging-frontend-module.js +3 -0
  37. package/lib/browser/messaging/messaging-frontend-module.js.map +1 -1
  38. package/lib/browser/messaging/ws-connection-source.d.ts +2 -1
  39. package/lib/browser/messaging/ws-connection-source.d.ts.map +1 -1
  40. package/lib/browser/messaging/ws-connection-source.js +5 -2
  41. package/lib/browser/messaging/ws-connection-source.js.map +1 -1
  42. package/lib/browser/saveable-service.d.ts +9 -2
  43. package/lib/browser/saveable-service.d.ts.map +1 -1
  44. package/lib/browser/saveable-service.js +34 -25
  45. package/lib/browser/saveable-service.js.map +1 -1
  46. package/lib/browser/shell/tab-bar-toolbar/tab-bar-toolbar.d.ts +1 -0
  47. package/lib/browser/shell/tab-bar-toolbar/tab-bar-toolbar.d.ts.map +1 -1
  48. package/lib/browser/shell/tab-bar-toolbar/tab-bar-toolbar.js +10 -1
  49. package/lib/browser/shell/tab-bar-toolbar/tab-bar-toolbar.js.map +1 -1
  50. package/lib/browser/shell/tab-bar-toolbar/tab-toolbar-item.d.ts +2 -0
  51. package/lib/browser/shell/tab-bar-toolbar/tab-toolbar-item.d.ts.map +1 -1
  52. package/lib/browser/shell/tab-bar-toolbar/tab-toolbar-item.js +11 -2
  53. package/lib/browser/shell/tab-bar-toolbar/tab-toolbar-item.js.map +1 -1
  54. package/lib/browser/window/browser-window-module.d.ts.map +1 -1
  55. package/lib/browser/window/browser-window-module.js +2 -0
  56. package/lib/browser/window/browser-window-module.js.map +1 -1
  57. package/lib/browser/window/default-secondary-window-service.d.ts +2 -0
  58. package/lib/browser/window/default-secondary-window-service.d.ts.map +1 -1
  59. package/lib/browser/window/default-secondary-window-service.js +7 -0
  60. package/lib/browser/window/default-secondary-window-service.js.map +1 -1
  61. package/lib/browser/window/window-focus-service.d.ts +71 -0
  62. package/lib/browser/window/window-focus-service.d.ts.map +1 -0
  63. package/lib/browser/window/window-focus-service.js +173 -0
  64. package/lib/browser/window/window-focus-service.js.map +1 -0
  65. package/lib/common/event.d.ts +16 -0
  66. package/lib/common/event.d.ts.map +1 -1
  67. package/lib/common/event.js +20 -2
  68. package/lib/common/event.js.map +1 -1
  69. package/lib/common/event.spec.js +63 -0
  70. package/lib/common/event.spec.js.map +1 -1
  71. package/lib/common/glob.d.ts +2 -0
  72. package/lib/common/glob.d.ts.map +1 -1
  73. package/lib/common/glob.js +8 -7
  74. package/lib/common/glob.js.map +1 -1
  75. package/lib/common/message-rpc/channel.d.ts +1 -1
  76. package/lib/common/message-rpc/channel.d.ts.map +1 -1
  77. package/lib/common/message-rpc/channel.js +20 -12
  78. package/lib/common/message-rpc/channel.js.map +1 -1
  79. package/lib/common/message-rpc/channel.spec.d.ts.map +1 -1
  80. package/lib/common/message-rpc/channel.spec.js +94 -0
  81. package/lib/common/message-rpc/channel.spec.js.map +1 -1
  82. package/lib/common/message-rpc/rpc-protocol.d.ts.map +1 -1
  83. package/lib/common/message-rpc/rpc-protocol.js +13 -3
  84. package/lib/common/message-rpc/rpc-protocol.js.map +1 -1
  85. package/lib/common/message-rpc/uint8-array-message-buffer.d.ts.map +1 -1
  86. package/lib/common/message-rpc/uint8-array-message-buffer.js +1 -1
  87. package/lib/common/message-rpc/uint8-array-message-buffer.js.map +1 -1
  88. package/lib/common/messaging/index.d.ts +1 -0
  89. package/lib/common/messaging/index.d.ts.map +1 -1
  90. package/lib/common/messaging/index.js +1 -0
  91. package/lib/common/messaging/index.js.map +1 -1
  92. package/lib/common/messaging/socket-write-buffer.d.ts +4 -3
  93. package/lib/common/messaging/socket-write-buffer.d.ts.map +1 -1
  94. package/lib/common/messaging/socket-write-buffer.js +14 -4
  95. package/lib/common/messaging/socket-write-buffer.js.map +1 -1
  96. package/lib/common/preferences/index.d.ts +1 -0
  97. package/lib/common/preferences/index.d.ts.map +1 -1
  98. package/lib/common/preferences/index.js +1 -0
  99. package/lib/common/preferences/index.js.map +1 -1
  100. package/lib/common/preferences/preference-utils.d.ts +6 -0
  101. package/lib/common/preferences/preference-utils.d.ts.map +1 -0
  102. package/lib/common/preferences/preference-utils.js +29 -0
  103. package/lib/common/preferences/preference-utils.js.map +1 -0
  104. package/lib/common/resource.d.ts +2 -0
  105. package/lib/common/resource.d.ts.map +1 -1
  106. package/lib/common/resource.js +7 -3
  107. package/lib/common/resource.js.map +1 -1
  108. package/lib/electron-browser/menu/electron-main-menu-factory.d.ts.map +1 -1
  109. package/lib/electron-browser/menu/electron-main-menu-factory.js +5 -1
  110. package/lib/electron-browser/menu/electron-main-menu-factory.js.map +1 -1
  111. package/lib/electron-browser/messaging/electron-messaging-frontend-module.d.ts.map +1 -1
  112. package/lib/electron-browser/messaging/electron-messaging-frontend-module.js +3 -0
  113. package/lib/electron-browser/messaging/electron-messaging-frontend-module.js.map +1 -1
  114. package/lib/electron-browser/window/electron-window-module.d.ts.map +1 -1
  115. package/lib/electron-browser/window/electron-window-module.js +2 -0
  116. package/lib/electron-browser/window/electron-window-module.js.map +1 -1
  117. package/lib/electron-main/electron-api-main.d.ts.map +1 -1
  118. package/lib/electron-main/electron-api-main.js +4 -2
  119. package/lib/electron-main/electron-api-main.js.map +1 -1
  120. package/lib/electron-main/theia-electron-window.d.ts.map +1 -1
  121. package/lib/electron-main/theia-electron-window.js +3 -0
  122. package/lib/electron-main/theia-electron-window.js.map +1 -1
  123. package/lib/node/messaging/default-messaging-service.d.ts.map +1 -1
  124. package/lib/node/messaging/default-messaging-service.js +1 -0
  125. package/lib/node/messaging/default-messaging-service.js.map +1 -1
  126. package/lib/node/messaging/index.d.ts +1 -0
  127. package/lib/node/messaging/index.d.ts.map +1 -1
  128. package/lib/node/messaging/index.js +1 -0
  129. package/lib/node/messaging/index.js.map +1 -1
  130. package/lib/node/messaging/messaging-backend-module.d.ts.map +1 -1
  131. package/lib/node/messaging/messaging-backend-module.js +4 -0
  132. package/lib/node/messaging/messaging-backend-module.js.map +1 -1
  133. package/lib/node/messaging/test/default-messaging-service.spec.d.ts +2 -0
  134. package/lib/node/messaging/test/default-messaging-service.spec.d.ts.map +1 -0
  135. package/lib/node/messaging/test/default-messaging-service.spec.js +81 -0
  136. package/lib/node/messaging/test/default-messaging-service.spec.js.map +1 -0
  137. package/lib/node/messaging/websocket-frontend-connection-service.d.ts +9 -5
  138. package/lib/node/messaging/websocket-frontend-connection-service.d.ts.map +1 -1
  139. package/lib/node/messaging/websocket-frontend-connection-service.js +21 -5
  140. package/lib/node/messaging/websocket-frontend-connection-service.js.map +1 -1
  141. package/lib/node/process-utils.d.ts.map +1 -1
  142. package/lib/node/process-utils.js +9 -1
  143. package/lib/node/process-utils.js.map +1 -1
  144. package/package.json +32 -32
  145. package/src/browser/common-frontend-contribution.ts +2 -2
  146. package/src/browser/components/card.tsx +13 -2
  147. package/src/browser/connection-status-service.ts +1 -1
  148. package/src/browser/keyboard/index.ts +1 -0
  149. package/src/browser/keyboard/keyboard-utils.ts +37 -0
  150. package/src/browser/menu/browser-menu-plugin.ts +8 -1
  151. package/src/browser/messaging/messaging-frontend-module.ts +3 -0
  152. package/src/browser/messaging/ws-connection-source.ts +3 -2
  153. package/src/browser/saveable-service.ts +34 -27
  154. package/src/browser/shell/tab-bar-toolbar/tab-bar-toolbar.tsx +14 -1
  155. package/src/browser/shell/tab-bar-toolbar/tab-toolbar-item.tsx +13 -2
  156. package/src/browser/style/card.css +4 -2
  157. package/src/browser/style/hover-service.css +7 -0
  158. package/src/browser/window/browser-window-module.ts +2 -0
  159. package/src/browser/window/default-secondary-window-service.ts +6 -0
  160. package/src/browser/window/window-focus-service.ts +187 -0
  161. package/src/common/event.spec.ts +80 -0
  162. package/src/common/event.ts +31 -2
  163. package/src/common/glob.ts +2 -2
  164. package/src/common/i18n/nls.metadata.json +4254 -1058
  165. package/src/common/message-rpc/channel.spec.ts +116 -0
  166. package/src/common/message-rpc/channel.ts +15 -11
  167. package/src/common/message-rpc/rpc-protocol.ts +12 -3
  168. package/src/common/message-rpc/uint8-array-message-buffer.ts +1 -1
  169. package/src/common/messaging/index.ts +1 -0
  170. package/src/common/messaging/socket-write-buffer.ts +10 -4
  171. package/src/common/preferences/index.ts +1 -0
  172. package/src/common/preferences/preference-utils.ts +28 -0
  173. package/src/common/resource.ts +8 -2
  174. package/src/electron-browser/menu/electron-main-menu-factory.ts +5 -1
  175. package/src/electron-browser/messaging/electron-messaging-frontend-module.ts +3 -0
  176. package/src/electron-browser/window/electron-window-module.ts +2 -0
  177. package/src/electron-main/electron-api-main.ts +4 -2
  178. package/src/electron-main/theia-electron-window.ts +3 -0
  179. package/src/node/messaging/default-messaging-service.ts +1 -0
  180. package/src/node/messaging/index.ts +1 -0
  181. package/src/node/messaging/messaging-backend-module.ts +5 -1
  182. package/src/node/messaging/test/default-messaging-service.spec.ts +85 -0
  183. package/src/node/messaging/websocket-frontend-connection-service.ts +20 -7
  184. package/src/node/process-utils.ts +9 -1
package/package.json CHANGED
@@ -1,11 +1,11 @@
1
1
  {
2
2
  "name": "@theia/core",
3
- "version": "1.71.0-next.6+8b1f84c1b",
3
+ "version": "1.71.0-next.64+5f7caff6d",
4
4
  "description": "Theia is a cloud & desktop IDE framework implemented in TypeScript.",
5
5
  "main": "lib/common/index.js",
6
6
  "typings": "lib/common/index.d.ts",
7
7
  "dependencies": {
8
- "@babel/runtime": "^7.10.0",
8
+ "@babel/runtime": "^7.29.2",
9
9
  "@lumino/algorithm": "^2.0.4",
10
10
  "@lumino/commands": "^2.3.3",
11
11
  "@lumino/coreutils": "^2.2.2",
@@ -16,31 +16,31 @@
16
16
  "@lumino/signaling": "^2.1.5",
17
17
  "@lumino/virtualdom": "^2.0.4",
18
18
  "@lumino/widgets": "2.7.5",
19
- "@parcel/watcher": "^2.5.0",
20
- "@theia/application-package": "1.71.0-next.6+8b1f84c1b",
21
- "@theia/request": "1.71.0-next.6+8b1f84c1b",
22
- "@types/body-parser": "^1.16.4",
19
+ "@parcel/watcher": "^2.5.6",
20
+ "@theia/application-package": "1.71.0-next.64+5f7caff6d",
21
+ "@theia/request": "1.71.0-next.64+5f7caff6d",
22
+ "@types/body-parser": "^1.19.6",
23
23
  "@types/express": "^4.17.21",
24
24
  "@types/fs-extra": "^4.0.2",
25
25
  "@types/lodash.debounce": "4.0.3",
26
- "@types/lodash.throttle": "^4.1.3",
27
- "@types/markdown-it": "^14.1.0",
26
+ "@types/lodash.throttle": "^4.1.9",
27
+ "@types/markdown-it": "^14.1.2",
28
28
  "@types/markdown-it-emoji": "^3.0.1",
29
- "@types/react": "^18.0.15",
30
- "@types/react-dom": "^18.0.6",
31
- "@types/route-parser": "^0.1.1",
32
- "@types/safer-buffer": "^2.1.0",
29
+ "@types/react": "^18.3.28",
30
+ "@types/react-dom": "^18.3.7",
31
+ "@types/route-parser": "^0.1.7",
32
+ "@types/safer-buffer": "^2.1.3",
33
33
  "@types/uuid": "^9.0.8",
34
- "@types/ws": "^8.5.5",
34
+ "@types/ws": "^8.18.1",
35
35
  "@types/yargs": "^15",
36
36
  "@vscode/codicons": "0.0.45",
37
- "ajv": "^6.5.3",
38
- "async-mutex": "^0.4.0",
39
- "body-parser": "^1.17.2",
40
- "cookie": "^1.0.2",
41
- "dompurify": "^3.2.4",
37
+ "ajv": "^6.14.0",
38
+ "async-mutex": "^0.4.1",
39
+ "body-parser": "^1.20.4",
40
+ "cookie": "^1.1.1",
41
+ "dompurify": "^3.3.3",
42
42
  "drivelist": "^12.0.2",
43
- "express": "^4.21.0",
43
+ "express": "^4.22.1",
44
44
  "fast-json-stable-stringify": "^2.1.0",
45
45
  "file-icons-js": "~1.0.3",
46
46
  "font-awesome": "^4.7.0",
@@ -48,32 +48,32 @@
48
48
  "fuzzy": "^0.1.3",
49
49
  "http-proxy-agent": "^5.0.0",
50
50
  "https-proxy-agent": "^5.0.0",
51
- "iconv-lite": "^0.6.0",
52
- "inversify": "^6.1.3",
53
- "jschardet": "^2.1.1",
51
+ "iconv-lite": "^0.6.3",
52
+ "inversify": "^6.2.2",
53
+ "jschardet": "^2.3.0",
54
54
  "keytar": "7.9.0",
55
55
  "lodash.debounce": "^4.0.8",
56
56
  "lodash.throttle": "^4.1.1",
57
- "markdown-it": "^14.1.0",
57
+ "markdown-it": "^14.1.1",
58
58
  "markdown-it-anchor": "^9.2.0",
59
59
  "markdown-it-emoji": "^3.0.0",
60
- "msgpackr": "^1.10.2",
60
+ "msgpackr": "^1.11.9",
61
61
  "p-debounce": "^2.1.0",
62
62
  "perfect-scrollbar": "1.5.5",
63
- "react": "^18.2.0",
64
- "react-dom": "^18.2.0",
65
- "react-tooltip": "^4.2.21",
66
- "react-virtuoso": "^2.17.0",
63
+ "react": "^18.3.1",
64
+ "react-dom": "^18.3.1",
65
+ "react-tooltip": "^4.5.1",
66
+ "react-virtuoso": "^2.19.1",
67
67
  "reflect-metadata": "^0.2.2",
68
68
  "route-parser": "^0.0.5",
69
69
  "safer-buffer": "^2.1.2",
70
- "socket.io": "^4.5.3",
71
- "socket.io-client": "^4.5.3",
70
+ "socket.io": "^4.8.3",
71
+ "socket.io-client": "^4.8.3",
72
72
  "tslib": "^2.6.2",
73
73
  "uuid": "^9.0.1",
74
74
  "vscode-languageserver-protocol": "3.17.5",
75
75
  "vscode-uri": "3.0.8",
76
- "ws": "^8.17.1",
76
+ "ws": "^8.20.0",
77
77
  "yargs": "^15.3.1"
78
78
  },
79
79
  "peerDependencies": {
@@ -221,5 +221,5 @@
221
221
  "nyc": {
222
222
  "extends": "../../configs/nyc.json"
223
223
  },
224
- "gitHead": "8b1f84c1b0114a76a1f343f3a2935d68faca6927"
224
+ "gitHead": "5f7caff6d4a136452787442612656962b5c5099e"
225
225
  }
@@ -718,8 +718,8 @@ export class CommonFrontendContribution implements FrontendApplicationContributi
718
718
  execute: title => title?.owner && this.shell.toggleMaximized(title?.owner),
719
719
  }));
720
720
  commandRegistry.registerCommand(CommonCommands.SHOW_MENU_BAR, {
721
- isEnabled: () => !isOSX,
722
- isVisible: () => !isOSX,
721
+ isEnabled: () => !this.isElectron() || !isOSX,
722
+ isVisible: () => !this.isElectron() || !isOSX,
723
723
  execute: () => {
724
724
  const menuBarVisibility = 'window.menuBarVisibility';
725
725
  const visibility = this.preferences[menuBarVisibility];
@@ -15,6 +15,7 @@
15
15
  // *****************************************************************************
16
16
 
17
17
  import * as React from 'react';
18
+ import { buttonKeyboardProps, isActivationKey } from '../keyboard/keyboard-utils';
18
19
 
19
20
  export interface CardActionButton {
20
21
  /** Icon class (e.g., codicon) */
@@ -69,14 +70,22 @@ export const Card = React.memo(function Card(props: CardProps): React.ReactEleme
69
70
  } = props;
70
71
 
71
72
  const isInteractive = onClick !== undefined;
73
+ const [hasFocus, setHasFocus] = React.useState(false);
72
74
 
73
75
  const handleKeyDown = React.useCallback((e: React.KeyboardEvent<HTMLDivElement>) => {
74
- if (onClick && (e.key === 'Enter' || e.key === ' ')) {
76
+ if (onClick && isActivationKey(e)) {
75
77
  e.preventDefault();
76
78
  onClick();
77
79
  }
78
80
  }, [onClick]);
79
81
 
82
+ const handleFocus = React.useCallback(() => setHasFocus(true), []);
83
+ const handleBlur = React.useCallback((e: React.FocusEvent<HTMLDivElement>) => {
84
+ if (!e.currentTarget.contains(e.relatedTarget as Node)) {
85
+ setHasFocus(false);
86
+ }
87
+ }, []);
88
+
80
89
  const cardClasses = [
81
90
  'theia-Card',
82
91
  isInteractive && 'theia-Card-interactive',
@@ -94,6 +103,8 @@ export const Card = React.memo(function Card(props: CardProps): React.ReactEleme
94
103
  role={isInteractive ? 'button' : undefined}
95
104
  tabIndex={isInteractive ? 0 : undefined}
96
105
  onKeyDown={isInteractive ? handleKeyDown : undefined}
106
+ onFocus={actionButtons ? handleFocus : undefined}
107
+ onBlur={actionButtons ? handleBlur : undefined}
97
108
  >
98
109
  {icon && (
99
110
  <div className={`theia-Card-icon ${icon}`}></div>
@@ -115,7 +126,7 @@ export const Card = React.memo(function Card(props: CardProps): React.ReactEleme
115
126
  key={i}
116
127
  className={`theia-Card-action-btn ${btn.iconClass}`}
117
128
  title={btn.title}
118
- aria-label={btn.title}
129
+ {...buttonKeyboardProps(btn.title, hasFocus ? 0 : -1)}
119
130
  onClick={btn.onClick}
120
131
  />
121
132
  ))}
@@ -205,7 +205,7 @@ export class ApplicationConnectionStatusContribution extends DefaultFrontendAppl
205
205
  protected handleOffline(): void {
206
206
  this.statusBar.setElement(this.statusbarId, {
207
207
  alignment: StatusBarAlignment.LEFT,
208
- text: nls.localize('theia/core/offline', 'Offline'),
208
+ text: nls.localizeByDefault('Offline'),
209
209
  tooltip: nls.localize('theia/localize/offlineTooltip', 'Cannot connect to backend.'),
210
210
  priority: 5000
211
211
  });
@@ -18,3 +18,4 @@ export * from './keys';
18
18
  export * from './keyboard-layout-service';
19
19
  export * from './browser-keyboard-layout-provider';
20
20
  export * from './browser-keyboard-frontend-contribution';
21
+ export * from './keyboard-utils';
@@ -0,0 +1,37 @@
1
+ // *****************************************************************************
2
+ // Copyright (C) 2026 EclipseSource and others.
3
+ //
4
+ // This program and the accompanying materials are made available under the
5
+ // terms of the Eclipse Public License v. 2.0 which is available at
6
+ // http://www.eclipse.org/legal/epl-2.0.
7
+ //
8
+ // This Source Code may also be made available under the following Secondary
9
+ // Licenses when the conditions for such availability set forth in the Eclipse
10
+ // Public License v. 2.0 are satisfied: GNU General Public License, version 2
11
+ // with the GNU Classpath Exception which is available at
12
+ // https://www.gnu.org/software/classpath/license.html.
13
+ //
14
+ // SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
15
+ // *****************************************************************************
16
+
17
+ /**
18
+ * Query whether the keyboard event represents an element activation.
19
+ * That is, whether the user pressed `Enter` or `Space` on a focusable
20
+ * element with `role="button"` or similar interactive role.
21
+ */
22
+ export function isActivationKey(e: React.KeyboardEvent | KeyboardEvent): boolean {
23
+ return e.key === 'Enter' || e.key === ' ';
24
+ }
25
+
26
+ /**
27
+ * Returns the ARIA/accessibility props that make a non-button HTML element
28
+ * keyboard-navigable and screen-reader-accessible as a button.
29
+ *
30
+ * Spread these onto the element alongside `onClick` and `onKeyDown`:
31
+ * ```tsx
32
+ * <div {...buttonKeyboardProps(label)} onClick={handler} onKeyDown={e => isActivationKey(e) && handler()} />
33
+ * ```
34
+ */
35
+ export function buttonKeyboardProps(ariaLabel: string, tabIndex = 0): React.HTMLAttributes<HTMLElement> {
36
+ return { tabIndex, role: 'button', 'aria-label': ariaLabel };
37
+ }
@@ -143,7 +143,14 @@ export class DynamicMenuBarWidget extends MenuBarWidget {
143
143
  protected previousFocusedElement: HTMLElement | undefined;
144
144
 
145
145
  constructor() {
146
- super();
146
+ // Disable Lumino's overflow menu feature. The feature has a bug where
147
+ // `onUpdateRequest` consumes a stale `_overflowIndex` (only recomputed at the
148
+ // end of the method), which causes a RangeError when the menu bar is rendered
149
+ // at zero width. Additionally, Theia's CSS does not constrain the menu bar's
150
+ // offsetWidth to the available space, so the overflow detection never triggers.
151
+ // See https://github.com/eclipse-theia/theia/issues/17352
152
+ // See https://github.com/jupyterlab/lumino/issues/811
153
+ super({ overflowMenuOptions: { isVisible: false } });
147
154
  // HACK we need to hook in on private method _openChildMenu. Don't do this at home!
148
155
  DynamicMenuBarWidget.prototype['_openChildMenu'] = () => {
149
156
  if (this.activeMenu instanceof DynamicMenuWidget) {
@@ -21,10 +21,13 @@ import { LocalConnectionProvider, RemoteConnectionProvider, ServiceConnectionPro
21
21
  import { ConnectionSource } from './connection-source';
22
22
  import { ConnectionCloseService, connectionCloseServicePath } from '../../common/messaging/connection-management';
23
23
  import { WebSocketConnectionProvider } from './ws-connection-provider';
24
+ import { SocketWriteBuffer } from '../../common/messaging/socket-write-buffer';
24
25
 
25
26
  const backendServiceProvider = Symbol('backendServiceProvider');
26
27
 
27
28
  export const messagingFrontendModule = new ContainerModule(bind => {
29
+ // Transient: each connection source gets its own private buffer instance.
30
+ bind(SocketWriteBuffer).toSelf();
28
31
  bind(ConnectionCloseService).toDynamicValue(ctx => WebSocketConnectionProvider.createProxy(ctx.container, connectionCloseServicePath)).inSingletonScope();
29
32
  bind(BrowserFrontendIdProvider).toSelf().inSingletonScope();
30
33
  bind(FrontendIdProvider).toService(BrowserFrontendIdProvider);
@@ -33,7 +33,8 @@ export class WebSocketConnectionSource implements ConnectionSource {
33
33
  @inject(FrontendIdProvider)
34
34
  protected readonly frontendIdProvider: FrontendIdProvider;
35
35
 
36
- private readonly writeBuffer = new SocketWriteBuffer();
36
+ @inject(SocketWriteBuffer)
37
+ protected readonly writeBuffer: SocketWriteBuffer;
37
38
 
38
39
  private _socket: Socket;
39
40
  get socket(): Socket {
@@ -135,8 +136,8 @@ export class WebSocketConnectionSource implements ConnectionSource {
135
136
 
136
137
  connectNewChannel(): void {
137
138
  if (this.currentChannel) {
138
- this.currentChannel.close();
139
139
  this.currentChannel.onCloseEmitter.fire({ reason: 'reconnecting channel' });
140
+ this.currentChannel.close();
140
141
  }
141
142
  this.writeBuffer.drain();
142
143
  this.currentChannel = this.createChannel();
@@ -22,6 +22,7 @@ import { AutoSaveMode, Saveable, SaveableSource, SaveableWidget, SaveOptions, Sa
22
22
  import { waitForClosed, Widget } from './widgets';
23
23
  import { FrontendApplicationContribution } from './frontend-application-contribution';
24
24
  import { FrontendApplication } from './frontend-application';
25
+ import { WindowFocusService } from './window/window-focus-service';
25
26
  import throttle = require('lodash.throttle');
26
27
 
27
28
  export const SaveErrorChecker = Symbol('SaveErrorChecker');
@@ -49,6 +50,9 @@ export class SaveableService implements FrontendApplicationContribution {
49
50
  @inject(ContributionProvider) @named(SaveErrorChecker)
50
51
  protected readonly errorCheckers: ContributionProvider<SaveErrorChecker>;
51
52
 
53
+ @inject(WindowFocusService)
54
+ protected readonly windowFocusService: WindowFocusService;
55
+
52
56
  protected saveThrottles = new Map<Widget, AutoSaveThrottle>();
53
57
  protected saveMode: AutoSaveMode = 'off';
54
58
  protected saveDelay = 1000;
@@ -131,6 +135,35 @@ export class SaveableService implements FrontendApplicationContribution {
131
135
  this.saveThrottles.get(e)?.dispose();
132
136
  this.saveThrottles.delete(e);
133
137
  });
138
+ // Save all dirty editors when any application window loses focus
139
+ this.windowFocusService.onDidWindowChangeFocus(({ hasFocus }) => {
140
+ if (!hasFocus) {
141
+ this.saveOnWindowBlur();
142
+ }
143
+ });
144
+ }
145
+
146
+ /**
147
+ * Save all dirty saveables when the application window loses focus.
148
+ * Triggered for both `onFocusChange` and `onWindowChange` auto-save modes,
149
+ * matching VSCode's behavior where a window focus loss is a superset of
150
+ * editor focus loss.
151
+ */
152
+ protected saveOnWindowBlur(): void {
153
+ if (this.saveMode !== 'onWindowChange' && this.saveMode !== 'onFocusChange') {
154
+ return;
155
+ }
156
+ if (!this.shell) {
157
+ return;
158
+ }
159
+ for (const widget of this.shell.widgets) {
160
+ const saveable = Saveable.get(widget);
161
+ if (saveable && this.shouldAutoSave(widget, saveable)) {
162
+ saveable.save({
163
+ saveReason: SaveReason.FocusChange
164
+ });
165
+ }
166
+ }
134
167
  }
135
168
 
136
169
  protected updateAutoSaveMode(mode: AutoSaveMode): void {
@@ -169,39 +202,13 @@ export class SaveableService implements FrontendApplicationContribution {
169
202
  saveReason: SaveReason.AfterDelay
170
203
  });
171
204
  }
172
- },
173
- this.addBlurListener(widget, saveable)
205
+ }
174
206
  );
175
207
  this.saveThrottles.set(widget, saveThrottle);
176
208
  this.applySaveableWidget(widget, saveable);
177
209
  return saveThrottle;
178
210
  }
179
211
 
180
- protected addBlurListener(widget: Widget, saveable: Saveable): Disposable {
181
- const document = widget.node.ownerDocument;
182
- const listener = (() => {
183
- if (this.saveMode === 'onWindowChange' && !this.windowHasFocus(document) && this.shouldAutoSave(widget, saveable)) {
184
- saveable.save({
185
- saveReason: SaveReason.FocusChange
186
- });
187
- }
188
- }).bind(this);
189
- document.addEventListener('blur', listener);
190
- return Disposable.create(() => {
191
- document.removeEventListener('blur', listener);
192
- });
193
- }
194
-
195
- protected windowHasFocus(document: Document): boolean {
196
- if (document.visibilityState === 'hidden') {
197
- return false;
198
- } else if (document.hasFocus()) {
199
- return true;
200
- }
201
- // TODO: Add support for iframes
202
- return false;
203
- }
204
-
205
212
  protected shouldAutoSave(widget: Widget, saveable: Saveable): boolean {
206
213
  const uri = NavigatableWidget.getUri(widget);
207
214
  if (uri?.scheme === UNTITLED_SCHEME) {
@@ -16,6 +16,7 @@
16
16
 
17
17
  import { inject, injectable, postConstruct } from 'inversify';
18
18
  import * as React from 'react';
19
+ import { buttonKeyboardProps, isActivationKey } from '../../keyboard/keyboard-utils';
19
20
  import { ContextKeyService } from '../../context-key-service';
20
21
  import { CommandRegistry, Disposable, DisposableCollection, nls } from '../../../common';
21
22
  import { Anchor, ContextMenuAccess, ContextMenuRenderer } from '../../context-menu-renderer';
@@ -150,7 +151,10 @@ export class TabBarToolbar extends ReactWidget {
150
151
 
151
152
  protected renderMore(): React.ReactNode {
152
153
  return !!this.more.size && <div key='__more__' className={TabBarToolbar.Styles.TAB_BAR_TOOLBAR_ITEM + ' enabled'}>
153
- <div id='__more__' className={codicon('ellipsis', true)} onClick={this.showMoreContextMenu}
154
+ <div id='__more__' className={codicon('ellipsis', true)}
155
+ {...buttonKeyboardProps(nls.localizeByDefault('More Actions...'))}
156
+ onClick={this.showMoreContextMenu}
157
+ onKeyDown={this.handleMoreKeyDown}
154
158
  title={nls.localizeByDefault('More Actions...')} />
155
159
  </div>;
156
160
  }
@@ -162,6 +166,15 @@ export class TabBarToolbar extends ReactWidget {
162
166
  this.renderMoreContextMenu(anchor);
163
167
  };
164
168
 
169
+ protected handleMoreKeyDown = (event: React.KeyboardEvent) => {
170
+ if (isActivationKey(event)) {
171
+ event.preventDefault();
172
+ event.stopPropagation();
173
+ const { left, bottom } = (event.currentTarget as HTMLElement).getBoundingClientRect();
174
+ this.renderMoreContextMenu({ x: left, y: bottom });
175
+ }
176
+ };
177
+
165
178
  renderMoreContextMenu(anchor: Anchor): ContextMenuAccess {
166
179
  const toDisposeOnHide = new DisposableCollection();
167
180
  this.addClass('menu-open');
@@ -23,6 +23,7 @@ import { KeybindingRegistry } from '../../keybinding';
23
23
  import { ACTION_ITEM } from '../../widgets';
24
24
  import { TabBarToolbar } from './tab-bar-toolbar';
25
25
  import * as React from 'react';
26
+ import { buttonKeyboardProps, isActivationKey } from '../../keyboard/keyboard-utils';
26
27
  import { ActionMenuNode, GroupImpl, MenuNode } from '../../../common/menu';
27
28
 
28
29
  export interface TabBarToolbarItem {
@@ -195,16 +196,24 @@ export class RenderedToolbarItemImpl extends AbstractToolbarItemImpl<RenderedToo
195
196
  };
196
197
 
197
198
  protected executeCommand(e: React.MouseEvent<HTMLElement>, widget: Widget): void {
199
+ this.doExecuteCommand(e, widget);
200
+ };
201
+
202
+ protected doExecuteCommand(e: React.SyntheticEvent<HTMLElement>, widget: Widget): void {
198
203
  e.preventDefault();
199
204
  e.stopPropagation();
200
-
201
205
  if (!this.isEnabled(widget)) {
202
206
  return;
203
207
  }
204
-
205
208
  if (this.action.command) {
206
209
  this.commandRegistry.executeCommand(this.action.command, widget);
207
210
  }
211
+ }
212
+
213
+ protected onKeyDownEvent = (e: React.KeyboardEvent<HTMLElement>, widget: Widget) => {
214
+ if (isActivationKey(e)) {
215
+ this.doExecuteCommand(e, widget);
216
+ }
208
217
  };
209
218
 
210
219
  protected renderItem(widget: Widget): React.ReactNode {
@@ -249,7 +258,9 @@ export class RenderedToolbarItemImpl extends AbstractToolbarItemImpl<RenderedToo
249
258
  onMouseUp={this.onMouseUpEvent}
250
259
  onMouseOut={this.onMouseUpEvent} >
251
260
  <div id={this.action.id} className={classNames.join(' ')}
261
+ {...buttonKeyboardProps(tooltip)}
252
262
  onClick={e => this.executeCommand(e, widget)}
263
+ onKeyDown={e => this.onKeyDownEvent(e, widget)}
253
264
  title={tooltip} > {innerText}
254
265
  </div>
255
266
  </div>;
@@ -101,11 +101,13 @@
101
101
  transition: opacity 0.15s;
102
102
  }
103
103
 
104
- .theia-Card:hover .theia-Card-footer-time {
104
+ .theia-Card:hover .theia-Card-footer-time,
105
+ .theia-Card:focus-within .theia-Card-footer-time {
105
106
  opacity: 0;
106
107
  }
107
108
 
108
- .theia-Card:hover .theia-Card-footer-actions {
109
+ .theia-Card:hover .theia-Card-footer-actions,
110
+ .theia-Card:focus-within .theia-Card-footer-actions {
109
111
  opacity: 1;
110
112
  pointer-events: auto;
111
113
  }
@@ -30,6 +30,8 @@
30
30
  padding: var(--theia-ui-padding);
31
31
  max-width: var(--theia-hover-max-width);
32
32
  user-select: text;
33
+ --vscode-scmGraph-historyItemHoverAdditionsForeground: var(--theia-scmGraph-historyItemHoverAdditionsForeground);
34
+ --vscode-scmGraph-historyItemHoverDeletionsForeground: var(--theia-scmGraph-historyItemHoverDeletionsForeground);
33
35
  }
34
36
 
35
37
  /* overwrite potentially different default user agent styles */
@@ -60,6 +62,11 @@
60
62
  background-color: var(--theia-editorHoverWidget-statusBarBackground);
61
63
  }
62
64
 
65
+ .theia-hover .codicon {
66
+ vertical-align: middle;
67
+ margin-bottom: 2px;
68
+ }
69
+
63
70
  .theia-hover code {
64
71
  background-color: var(--theia-textCodeBlock-background);
65
72
  font-family: var(--theia-code-font-family);
@@ -24,6 +24,7 @@ import { SecondaryWindowService } from './secondary-window-service';
24
24
  import { DefaultSecondaryWindowService } from './default-secondary-window-service';
25
25
  import { bindRootContributionProvider } from '../../common';
26
26
  import { WindowTitleContribution } from './window-title-service';
27
+ import { WindowFocusService } from './window-focus-service';
27
28
 
28
29
  export default new ContainerModule(bind => {
29
30
  bind(DefaultWindowService).toSelf().inSingletonScope();
@@ -31,5 +32,6 @@ export default new ContainerModule(bind => {
31
32
  bind(FrontendApplicationContribution).toService(DefaultWindowService);
32
33
  bind(ClipboardService).to(BrowserClipboardService).inSingletonScope();
33
34
  bind(SecondaryWindowService).to(DefaultSecondaryWindowService).inSingletonScope();
35
+ bind(WindowFocusService).toSelf().inSingletonScope();
34
36
  bindRootContributionProvider(bind, WindowTitleContribution);
35
37
  });
@@ -22,6 +22,7 @@ import { Saveable } from '../saveable';
22
22
  import { Emitter, environment, Event, PreferenceService } from '../../common';
23
23
  import { SaveableService } from '../saveable-service';
24
24
  import { getAllWidgetsFromSecondaryWindow, getDefaultRestoreArea } from '../secondary-window-handler';
25
+ import { WindowFocusService } from './window-focus-service';
25
26
 
26
27
  @injectable()
27
28
  export class DefaultSecondaryWindowService implements SecondaryWindowService {
@@ -53,6 +54,9 @@ export class DefaultSecondaryWindowService implements SecondaryWindowService {
53
54
  @inject(SaveableService)
54
55
  protected readonly saveResourceService: SaveableService;
55
56
 
57
+ @inject(WindowFocusService)
58
+ protected readonly windowFocusService: WindowFocusService;
59
+
56
60
  @postConstruct()
57
61
  init(): void {
58
62
  // Set up messaging with secondary windows
@@ -106,6 +110,7 @@ export class DefaultSecondaryWindowService implements SecondaryWindowService {
106
110
  this.secondaryWindows.push(newWindow);
107
111
  this.onWindowOpenedEmitter.fire(newWindow);
108
112
  newWindow.addEventListener('DOMContentLoaded', () => {
113
+ const focusRegistration = this.windowFocusService.registerWindow(newWindow);
109
114
  newWindow.addEventListener('beforeunload', evt => {
110
115
  const widgets = getAllWidgetsFromSecondaryWindow(newWindow) ?? [widget];
111
116
  for (const w of widgets) {
@@ -120,6 +125,7 @@ export class DefaultSecondaryWindowService implements SecondaryWindowService {
120
125
  }, { capture: true });
121
126
 
122
127
  newWindow.addEventListener('unload', () => {
128
+ focusRegistration.dispose();
123
129
  const extIndex = this.secondaryWindows.indexOf(newWindow);
124
130
  if (extIndex > -1) {
125
131
  this.onWindowClosedEmitter.fire(newWindow);