react-x11 2.38.0 → 2.40.0
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.
- package/package.json +3 -3
- package/src/acceleratorhooks.js +24 -8
- package/src/accelerators.js +26 -9
- package/src/backend/context2d.js +292 -35
- package/src/backend/filter.js +285 -0
- package/src/cocoa/app.js +7 -0
- package/src/cocoa/globalmenu.js +32 -1
- package/src/cocoa/panehost.js +117 -1
- package/src/cocoa/sprites.js +95 -18
- package/src/components/Menu.js +57 -11
- package/src/components/PasswordInput.js +11 -6
- package/src/editmenu.js +9 -2
- package/src/events.js +24 -0
- package/src/frame/index.js +1 -1
- package/src/globalmenu.js +21 -3
- package/src/index.d.ts +4 -1
- package/src/keysyms.d.ts +30 -0
- package/src/keysyms.js +29 -0
- package/src/menuitem.js +63 -17
- package/src/node.d.ts +44 -3
- package/src/nodes/editmenupopup.js +21 -2
- package/src/nodes/sprites.js +6 -3
- package/src/nodes/textarea.js +3 -1
- package/src/nodes/textinput.js +13 -3
- package/src/textselection.js +7 -7
- package/src/types/components.d.ts +8 -2
- package/src/types/elements.d.ts +1 -1
- package/src/types/events.d.ts +21 -3
- package/src/types/nodes.d.ts +6 -0
package/src/menuitem.js
CHANGED
|
@@ -112,21 +112,62 @@ const KEY_LABELS = {
|
|
|
112
112
|
Next: 'PgDn',
|
|
113
113
|
};
|
|
114
114
|
|
|
115
|
+
// The Mac prints a chord as glyphs, in one fixed order whatever order the
|
|
116
|
+
// chord was written in — ⌃⌥⇧⌘ — and with nothing between them: `⇧⌘Z`, never
|
|
117
|
+
// `Cmd+Shift+Z`. `Control` and `Super` are both ⌘ there, for the reason
|
|
118
|
+
// `primaryModifier` gives below: a cross-platform chord is written in the
|
|
119
|
+
// primary modifier, and the Mac's is Command.
|
|
120
|
+
const MAC_MODIFIER_ORDER = ['Alt', 'Shift', 'Command'];
|
|
121
|
+
const MAC_MODIFIER_GLYPHS = { Alt: '⌥', Shift: '⇧', Command: '⌘' };
|
|
122
|
+
|
|
123
|
+
// The keys a Mac menu draws as a glyph rather than a word. The rest are
|
|
124
|
+
// `KEY_LABELS`' answer, which is already the Mac's for punctuation.
|
|
125
|
+
const MAC_KEY_LABELS = {
|
|
126
|
+
Return: '↩',
|
|
127
|
+
KP_Enter: '⌤',
|
|
128
|
+
Tab: '⇥',
|
|
129
|
+
BackSpace: '⌫',
|
|
130
|
+
Delete: '⌦',
|
|
131
|
+
Escape: '⎋',
|
|
132
|
+
Left: '←',
|
|
133
|
+
Right: '→',
|
|
134
|
+
Up: '↑',
|
|
135
|
+
Down: '↓',
|
|
136
|
+
Prior: '⇞',
|
|
137
|
+
Next: '⇟',
|
|
138
|
+
Home: '↖',
|
|
139
|
+
End: '↘',
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
const keyLabel = (key, labels) =>
|
|
143
|
+
labels[key] ??
|
|
144
|
+
KEY_LABELS[key] ??
|
|
145
|
+
(key.length === 1 ? key.toUpperCase() : key);
|
|
146
|
+
|
|
115
147
|
/**
|
|
116
148
|
* One `aas` alternative — `['Control', 'S']` — as the string a menu row
|
|
117
|
-
* shows: `Ctrl+S
|
|
149
|
+
* shows: `Ctrl+S`, or `⌘S` where the primary modifier is ⌘.
|
|
118
150
|
*
|
|
119
151
|
* A single-character key is upper-cased because that is how every toolkit
|
|
120
152
|
* prints one, and because dbusmenu carries the *key*, not the character it
|
|
121
153
|
* types: `['Control', 's']` and `['Control', 'S']` are the same binding and
|
|
122
154
|
* must not render differently.
|
|
123
155
|
*/
|
|
124
|
-
function formatChord(chord) {
|
|
156
|
+
function formatChord(chord, primary) {
|
|
125
157
|
const { modifiers, key } = splitChord(chord);
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
158
|
+
if (primary === 'Super') {
|
|
159
|
+
const held = new Set(
|
|
160
|
+
[...modifiers].map((token) =>
|
|
161
|
+
token === 'Control' || token === 'Super' ? 'Command' : token,
|
|
162
|
+
),
|
|
163
|
+
);
|
|
164
|
+
const glyphs = MAC_MODIFIER_ORDER.filter((token) => held.has(token))
|
|
165
|
+
.map((token) => MAC_MODIFIER_GLYPHS[token])
|
|
166
|
+
.join('');
|
|
167
|
+
return glyphs + (key == null ? '' : keyLabel(key, MAC_KEY_LABELS));
|
|
129
168
|
}
|
|
169
|
+
const parts = [...modifiers].map((token) => MODIFIER_LABELS[token]);
|
|
170
|
+
if (key != null) parts.push(keyLabel(key, {}));
|
|
130
171
|
return parts.join('+');
|
|
131
172
|
}
|
|
132
173
|
|
|
@@ -137,11 +178,14 @@ function formatChord(chord) {
|
|
|
137
178
|
* modifier tokens ending in the key. Only the first alternative is drawn —
|
|
138
179
|
* a row has one shortcut column, and a menu that printed `Ctrl+S / F2` in it
|
|
139
180
|
* would be wider than the menu for the benefit of the rarer binding.
|
|
181
|
+
*
|
|
182
|
+
* `primary` is the app's `primaryModifier` (keysyms.js): `'Super'` draws the
|
|
183
|
+
* chord the way a Mac menu does, `⇧⌘Z` for `['Control', 'Shift', 'Z']`.
|
|
140
184
|
*/
|
|
141
|
-
export function formatShortcut(shortcut) {
|
|
185
|
+
export function formatShortcut(shortcut, { primary = 'Control' } = {}) {
|
|
142
186
|
const first = shortcut?.[0];
|
|
143
187
|
if (!Array.isArray(first) || first.length === 0) return '';
|
|
144
|
-
return formatChord(first);
|
|
188
|
+
return formatChord(first, primary);
|
|
145
189
|
}
|
|
146
190
|
|
|
147
191
|
// What UI Events calls the modifiers, which is what `aria-keyshortcuts` is
|
|
@@ -160,19 +204,21 @@ const ARIA_MODIFIERS = {
|
|
|
160
204
|
* dbusmenu's `aas` already has — so unlike the drawn menu, **every**
|
|
161
205
|
* alternative is announced rather than only the first: a screen reader is
|
|
162
206
|
* reading them out, not fitting them in a column.
|
|
207
|
+
*
|
|
208
|
+
* `primary` is `formatShortcut`'s: under `'Super'` a `Control` is ⌘, which
|
|
209
|
+
* UI Events calls `Meta`, so what is announced is what is drawn and bound.
|
|
163
210
|
*/
|
|
164
|
-
export function ariaKeyShortcuts(shortcut) {
|
|
211
|
+
export function ariaKeyShortcuts(shortcut, { primary = 'Control' } = {}) {
|
|
165
212
|
if (!isValidShortcut(shortcut)) return undefined;
|
|
213
|
+
const name = (token) => {
|
|
214
|
+
if (!MODIFIER_TOKENS.has(token)) return KEY_LABELS[token] ?? token;
|
|
215
|
+
return primary === 'Super' && token === 'Control'
|
|
216
|
+
? 'Meta'
|
|
217
|
+
: ARIA_MODIFIERS[token];
|
|
218
|
+
};
|
|
219
|
+
// a chord naming both `Control` and `Super` is one ⌘ there, said once
|
|
166
220
|
return shortcut
|
|
167
|
-
.map((chord) =>
|
|
168
|
-
chord
|
|
169
|
-
.map((token) =>
|
|
170
|
-
MODIFIER_TOKENS.has(token)
|
|
171
|
-
? ARIA_MODIFIERS[token]
|
|
172
|
-
: (KEY_LABELS[token] ?? token),
|
|
173
|
-
)
|
|
174
|
-
.join('+'),
|
|
175
|
-
)
|
|
221
|
+
.map((chord) => [...new Set(chord.map(name))].join('+'))
|
|
176
222
|
.join(' ');
|
|
177
223
|
}
|
|
178
224
|
|
package/src/node.d.ts
CHANGED
|
@@ -73,6 +73,29 @@ export interface Context2D {
|
|
|
73
73
|
* `'globalCompositeOperation' in ctx`.
|
|
74
74
|
*/
|
|
75
75
|
globalCompositeOperation?: string;
|
|
76
|
+
/**
|
|
77
|
+
* Canvas's: whether an image drawn scaled or turned is smoothed, or
|
|
78
|
+
* drawn as its nearest pixels. Native contexts only (macOS, Windows), and
|
|
79
|
+
* only where the bridge can set it: assign and read back to know.
|
|
80
|
+
*/
|
|
81
|
+
imageSmoothingEnabled?: boolean;
|
|
82
|
+
/**
|
|
83
|
+
* Canvas's: how it is smoothed, `'low'` being bilinear. A native context
|
|
84
|
+
* starts at `'medium'`, which on macOS resamples the whole source for a
|
|
85
|
+
* draw through a matrix whatever the clip — so a surface drawn a tile at
|
|
86
|
+
* a time is far cheaper at `'low'`.
|
|
87
|
+
*/
|
|
88
|
+
imageSmoothingQuality?: 'low' | 'medium' | 'high';
|
|
89
|
+
/**
|
|
90
|
+
* Canvas's: a CSS filter list applied to what is drawn after it. Native
|
|
91
|
+
* contexts only (macOS, Windows), and only the colour functions —
|
|
92
|
+
* `grayscale()`, `sepia()`, `saturate()`, `hue-rotate()`, `invert()`,
|
|
93
|
+
* `brightness()`, `contrast()` and `opacity()` — over a bridge that reads
|
|
94
|
+
* a surface's pixels back. Anything else does not stick: assign and read
|
|
95
|
+
* back to know. Absent on X11, whose server runs no colour matrix and
|
|
96
|
+
* answers a read a round trip later.
|
|
97
|
+
*/
|
|
98
|
+
filter?: string;
|
|
76
99
|
save(): void;
|
|
77
100
|
restore(): void;
|
|
78
101
|
translate(x: number, y: number): void;
|
|
@@ -237,6 +260,23 @@ export interface PaintCachePlan {
|
|
|
237
260
|
*/
|
|
238
261
|
export type SpriteMatrix = [number, number, number, number, number, number];
|
|
239
262
|
|
|
263
|
+
/**
|
|
264
|
+
* CSS's `matrix3d()`: sixteen numbers, column by column, a point
|
|
265
|
+
* (x, y, z, 1) going to the first three rows over the fourth — a turn out
|
|
266
|
+
* of the plane and the perspective it is seen in, about the sprite's
|
|
267
|
+
* `origin`. Its translation (the 13th to 15th) is in device pixels, and so
|
|
268
|
+
* is the distance its perspective row divides by. A presenter lifts a
|
|
269
|
+
* sprite with one only where none of its corners is behind the viewer, at
|
|
270
|
+
* rest or in any keyframe, and where its bridge takes one.
|
|
271
|
+
*/
|
|
272
|
+
// prettier-ignore
|
|
273
|
+
export type SpriteMatrix3D = [
|
|
274
|
+
number, number, number, number,
|
|
275
|
+
number, number, number, number,
|
|
276
|
+
number, number, number, number,
|
|
277
|
+
number, number, number, number,
|
|
278
|
+
];
|
|
279
|
+
|
|
240
280
|
/** A cubic-bezier's control points `[x1, y1, x2, y2]`, `x1` and `x2` in
|
|
241
281
|
* 0..1, or one of Core Animation's curve names. */
|
|
242
282
|
export type SpriteTiming = [number, number, number, number] | string;
|
|
@@ -252,8 +292,9 @@ export interface SpriteAnimation {
|
|
|
252
292
|
id: string;
|
|
253
293
|
property: 'opacity' | 'transform';
|
|
254
294
|
/** At least two: opacities, or matrices for a transform — sampled densely
|
|
255
|
-
* where the element interpolates in a way the render server would not.
|
|
256
|
-
|
|
295
|
+
* where the element interpolates in a way the render server would not.
|
|
296
|
+
* A transform's may be `matrix3d`s, in any mix with `matrix`es. */
|
|
297
|
+
values: number[] | (SpriteMatrix | SpriteMatrix3D)[];
|
|
257
298
|
/** One per value, 0 to 1, never decreasing; evenly spaced when left out. */
|
|
258
299
|
keyTimes?: number[];
|
|
259
300
|
/** One curve per segment, so one fewer than `values`; linear when left
|
|
@@ -321,7 +362,7 @@ export interface Sprite {
|
|
|
321
362
|
/** What the layer shows when no animation runs on it; 1 when left out. */
|
|
322
363
|
opacity?: number;
|
|
323
364
|
/** …and the transform, about `origin`; the identity when left out. */
|
|
324
|
-
transform?: SpriteMatrix;
|
|
365
|
+
transform?: SpriteMatrix | SpriteMatrix3D;
|
|
325
366
|
/** Device pixels, window coordinates; `rect`'s centre when left out. */
|
|
326
367
|
origin?: { x: number; y: number };
|
|
327
368
|
animations?: SpriteAnimation[];
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
import { deviceAnchorArea, windowOrigin } from '../anchor.js';
|
|
6
6
|
import { armPasteState, canPaste } from '../pastestate.js';
|
|
7
7
|
import {
|
|
8
|
+
primaryModifier,
|
|
8
9
|
XK_RETURN,
|
|
9
10
|
XK_KP_ENTER,
|
|
10
11
|
XK_UP,
|
|
@@ -125,6 +126,7 @@ export function openEditMenu(node, at, actions = {}) {
|
|
|
125
126
|
items,
|
|
126
127
|
(text) => app?.fonts?.layout(text, style)?.width,
|
|
127
128
|
s,
|
|
129
|
+
{ primary: primaryModifier(app) },
|
|
128
130
|
);
|
|
129
131
|
const deviceAt = at && {
|
|
130
132
|
...at,
|
|
@@ -221,6 +223,17 @@ export function openEditMenu(node, at, actions = {}) {
|
|
|
221
223
|
node._editMenu = popup;
|
|
222
224
|
// read *before* the menu takes the keyboard, and handed back on close
|
|
223
225
|
node._editMenuRestore = node._focusManager()?.focused ?? null;
|
|
226
|
+
// The window losing focus closes it — the user went to another
|
|
227
|
+
// application, ⌘-Tab or a click on its window. Neither reaches us as a
|
|
228
|
+
// press: the grab redirects presses only between windows of ours on Cocoa,
|
|
229
|
+
// and a popup panel never takes the keyboard there, so this is the one
|
|
230
|
+
// event that says so. The node keeps its focus across the blur, so nothing
|
|
231
|
+
// in the tree hears it either; the same answer `Menu` and `Select` give
|
|
232
|
+
// (`useDismissOnWindowBlur`, components/anchor.js).
|
|
233
|
+
node._editMenuOffBlur =
|
|
234
|
+
node.root?.onWindowFocusChange?.((focused) => {
|
|
235
|
+
if (!focused) closeEditMenu(node);
|
|
236
|
+
}) ?? null;
|
|
224
237
|
// the menu takes the keyboard so arrows and Escape reach it rather than
|
|
225
238
|
// the element behind it
|
|
226
239
|
popup.events?.focus?.(canvas);
|
|
@@ -241,6 +254,8 @@ export function closeEditMenu(node) {
|
|
|
241
254
|
const popup = node?._editMenu;
|
|
242
255
|
if (!popup) return;
|
|
243
256
|
node._editMenu = null;
|
|
257
|
+
node._editMenuOffBlur?.();
|
|
258
|
+
node._editMenuOffBlur = null;
|
|
244
259
|
const restore = node._editMenuRestore;
|
|
245
260
|
node._editMenuRestore = null;
|
|
246
261
|
// the popup is a child, so a node destroyed while its menu was up took the
|
|
@@ -255,6 +270,10 @@ export function closeEditMenu(node) {
|
|
|
255
270
|
// menu and a ring appearing on the way back would be news to nobody. A
|
|
256
271
|
// surface that was not focusable in the first place, or that stopped being
|
|
257
272
|
// on screen while the menu was up, gets nothing back rather than the
|
|
258
|
-
// destroyed menu canvas keeping the keyboard.
|
|
259
|
-
|
|
273
|
+
// destroyed menu canvas keeping the keyboard. And a restore, which takes
|
|
274
|
+
// the window no keyboard: a menu closed because the user went to another
|
|
275
|
+
// application must not drag them back to this one.
|
|
276
|
+
events.focus(events._canRestoreTo(restore) ? restore : null, 'pointer', {
|
|
277
|
+
restoring: true,
|
|
278
|
+
});
|
|
260
279
|
}
|
package/src/nodes/sprites.js
CHANGED
|
@@ -25,9 +25,12 @@ export class NodeSprites {
|
|
|
25
25
|
* - `version`: compared with `===` from frame to frame; a change paints
|
|
26
26
|
* the part again. Left out, it is painted once.
|
|
27
27
|
* - `opacity`, and `transform` — CSS's `matrix(a, b, c, d, e, f)` as an
|
|
28
|
-
* array, `e` and `f` in device pixels
|
|
29
|
-
*
|
|
30
|
-
*
|
|
28
|
+
* array, `e` and `f` in device pixels, or `matrix3d()`'s sixteen
|
|
29
|
+
* numbers for a part turned out of the plane and seen in a perspective
|
|
30
|
+
* — about `origin`, a point in device pixels that defaults to `rect`'s
|
|
31
|
+
* centre: the values the layer shows when no animation is running on
|
|
32
|
+
* it. A matrix3d part is lifted only where none of its corners is behind
|
|
33
|
+
* the viewer, at rest or in any keyframe.
|
|
31
34
|
* - `animations`: what the render server runs on it, each `{ id,
|
|
32
35
|
* property: 'opacity' | 'transform', values, keyTimes, timings,
|
|
33
36
|
* duration, delay, repeat, autoreverse, hold }`, with times in
|
package/src/nodes/textarea.js
CHANGED
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
XK_PAGE_UP,
|
|
11
11
|
XK_PAGE_DOWN,
|
|
12
12
|
XK_END,
|
|
13
|
+
primaryModifierHeld,
|
|
13
14
|
} from '../keysyms.js';
|
|
14
15
|
import {
|
|
15
16
|
scrollbarGeometry,
|
|
@@ -235,7 +236,8 @@ export class TextAreaNode extends TextInputNode {
|
|
|
235
236
|
const layout = this._valueLayout();
|
|
236
237
|
|
|
237
238
|
if (k === XK_RETURN || k === XK_KP_ENTER) {
|
|
238
|
-
|
|
239
|
+
// Ctrl+Enter, ⌘↩ on the Cocoa backend
|
|
240
|
+
if (primaryModifierHeld(ev, this.app)) {
|
|
239
241
|
this._fireValueEvent('onSubmit', this.value, ev.nativeEvent);
|
|
240
242
|
return true;
|
|
241
243
|
}
|
package/src/nodes/textinput.js
CHANGED
|
@@ -14,6 +14,7 @@ import { lastInputTime } from '../inputtime.js';
|
|
|
14
14
|
import {
|
|
15
15
|
ctrlChordLetter,
|
|
16
16
|
MOD,
|
|
17
|
+
primaryModifierHeld,
|
|
17
18
|
XK_BACKSPACE,
|
|
18
19
|
XK_RETURN,
|
|
19
20
|
XK_KP_ENTER,
|
|
@@ -550,7 +551,9 @@ export class TextInputNode extends Node {
|
|
|
550
551
|
this._moveCaret(this._chars().length, ev.shiftKey);
|
|
551
552
|
return true;
|
|
552
553
|
}
|
|
553
|
-
|
|
554
|
+
// Ctrl, or ⌘ on the Cocoa backend (`primaryModifierHeld`): the keys the
|
|
555
|
+
// edit menu prints beside its rows are the keys that do what they say
|
|
556
|
+
if (primaryModifierHeld(ev, this.app)) {
|
|
554
557
|
const letter = ctrlChordLetter(ev);
|
|
555
558
|
if (letter === 0x61 /* a */) {
|
|
556
559
|
this._selectAll();
|
|
@@ -562,10 +565,12 @@ export class TextInputNode extends Node {
|
|
|
562
565
|
} else if (letter === 0x76 /* v */) {
|
|
563
566
|
this._pasteFrom();
|
|
564
567
|
} else if (letter === 0x7a /* z */) {
|
|
565
|
-
// Ctrl+Shift+Z redoes, the way it does in GTK and Qt
|
|
568
|
+
// Ctrl+Shift+Z redoes, the way it does in GTK and Qt — and ⇧⌘Z is
|
|
569
|
+
// the Mac's own redo
|
|
566
570
|
if (ev.shiftKey) this.redo();
|
|
567
571
|
else this.undo();
|
|
568
|
-
} else if (letter === 0x79 /* y */) {
|
|
572
|
+
} else if (letter === 0x79 /* y */ && !ev.metaKey) {
|
|
573
|
+
// Ctrl+Y is Windows' redo; ⌘Y is nobody's, so it is not taken
|
|
569
574
|
this.redo();
|
|
570
575
|
} else {
|
|
571
576
|
// A chord this field has no answer for is not the field's: Ctrl+S
|
|
@@ -576,6 +581,11 @@ export class TextInputNode extends Node {
|
|
|
576
581
|
}
|
|
577
582
|
return true;
|
|
578
583
|
}
|
|
584
|
+
// …and a chord on the other one is not text either. AppKit hands over
|
|
585
|
+
// the key's bare character with ⌘ or ⌃ held, so ⌘S in a focused field
|
|
586
|
+
// typed an `s` before this, and ⌃A typed an `a` once ⌘ became the
|
|
587
|
+
// primary — Ctrl and Super chords are commands on every backend.
|
|
588
|
+
if (ev.ctrlKey || ev.metaKey) return false;
|
|
579
589
|
if (ev.codepoint != null && ev.codepoint >= 0x20 && ev.codepoint !== 0x7f) {
|
|
580
590
|
const ch = String.fromCodePoint(ev.codepoint);
|
|
581
591
|
this._insert(ch, 'type');
|
package/src/textselection.js
CHANGED
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
|
|
31
31
|
import { callHandler } from './errors.js';
|
|
32
32
|
import { lastInputTime } from './inputtime.js';
|
|
33
|
-
import { ctrlChordLetter } from './keysyms.js';
|
|
33
|
+
import { ctrlChordLetter, primaryModifierHeld } from './keysyms.js';
|
|
34
34
|
import { codePoints, wordRangeAt } from './textrange.js';
|
|
35
35
|
// --- who is showing a selection ------------------------------------------
|
|
36
36
|
//
|
|
@@ -201,14 +201,14 @@ export class TextSelection {
|
|
|
201
201
|
}
|
|
202
202
|
|
|
203
203
|
/**
|
|
204
|
-
* Ctrl+A and Ctrl+C
|
|
205
|
-
*
|
|
206
|
-
*
|
|
207
|
-
*
|
|
208
|
-
* default.
|
|
204
|
+
* Ctrl+A and Ctrl+C — ⌘A and ⌘C on the Cocoa backend — run as this
|
|
205
|
+
* element's default action, which is why a surface is a focus target
|
|
206
|
+
* (a11y.js): the copy has to have somewhere to arrive. Nothing else is
|
|
207
|
+
* bound. A read-only document has no caret, so shift+arrows would be caret
|
|
208
|
+
* browsing, which is a mode rather than a default.
|
|
209
209
|
*/
|
|
210
210
|
keyDown(ev) {
|
|
211
|
-
if (!ev.
|
|
211
|
+
if (!primaryModifierHeld(ev, this.node.app)) return;
|
|
212
212
|
const letter = ctrlChordLetter(ev);
|
|
213
213
|
if (letter === 0x61 /* a */) {
|
|
214
214
|
this.selectAll();
|
|
@@ -579,6 +579,10 @@ export interface MenuItem {
|
|
|
579
579
|
* the menu is mounted, without the menu being opened (#351). `enabled`
|
|
580
580
|
* and `visible` gate the binding with the row. `accelerators={false}` on
|
|
581
581
|
* the menu turns that off for an app with a dispatcher of its own.
|
|
582
|
+
*
|
|
583
|
+
* `Control` is the primary modifier: on the Cocoa backend `[['Control',
|
|
584
|
+
* 'S']]` is drawn `⌘S`, bound to ⌘S, and installed in the macOS menu bar
|
|
585
|
+
* as ⌘S — which then answers it by itself (docs/events.md).
|
|
582
586
|
*/
|
|
583
587
|
shortcut?: MenuShortcut;
|
|
584
588
|
/** With `toggleState`, draws a check mark or a radio dot in the gutter. */
|
|
@@ -654,8 +658,10 @@ export interface MenuBarProps extends WidgetProps {
|
|
|
654
658
|
/**
|
|
655
659
|
* Honour the items' own `shortcut`s while this bar is mounted — including
|
|
656
660
|
* when the desktop's panel has taken the menu over, since the panel draws
|
|
657
|
-
* the rows but the key is pressed in this window.
|
|
658
|
-
*
|
|
661
|
+
* the rows but the key is pressed in this window. The macOS menu bar is the
|
|
662
|
+
* exception: it answers the ⌘ chords it holds as key equivalents itself,
|
|
663
|
+
* and the window leaves those alone. On by default; `false` leaves the
|
|
664
|
+
* chords to an app that dispatches its own.
|
|
659
665
|
*/
|
|
660
666
|
accelerators?: boolean;
|
|
661
667
|
fontSize?: number;
|
package/src/types/elements.d.ts
CHANGED
|
@@ -739,7 +739,7 @@ export interface TextInputProps extends DrawnProps<TextInputNode> {
|
|
|
739
739
|
* both read naturally.
|
|
740
740
|
*/
|
|
741
741
|
onChange?: (ev: ChangeEvent<TextInputNode>) => void;
|
|
742
|
-
/** Enter — or Ctrl+Enter in a `<textarea
|
|
742
|
+
/** Enter — or Ctrl+Enter in a `<textarea>` (⌘↩ on the Cocoa backend). */
|
|
743
743
|
onSubmit?: (ev: SubmitEvent<TextInputNode>) => void;
|
|
744
744
|
placeholder?: string;
|
|
745
745
|
placeholderColor?: Color;
|
package/src/types/events.d.ts
CHANGED
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
import type { RefObject } from 'react';
|
|
8
8
|
import type { DrawnNode, NtkWindow, TextInputNode } from './nodes.js';
|
|
9
9
|
import type { MenuShortcut } from './components.js';
|
|
10
|
+
import type { PrimaryModifier } from '../keysyms.js';
|
|
10
11
|
|
|
11
12
|
/** The raw ntk/X11 event a synthetic one was made from. */
|
|
12
13
|
export interface NativeEvent {
|
|
@@ -331,7 +332,7 @@ export interface ChangeEvent<T = TextInputNode> extends Omit<
|
|
|
331
332
|
}
|
|
332
333
|
|
|
333
334
|
/**
|
|
334
|
-
* `<textinput onSubmit>` — Enter, or Ctrl+Enter in a `<textarea
|
|
335
|
+
* `<textinput onSubmit>` — Enter, or Ctrl+Enter in a `<textarea>` (⌘↩ on Cocoa). Same
|
|
335
336
|
* shape as {@link ChangeEvent}; `nativeEvent` is the X key event.
|
|
336
337
|
*/
|
|
337
338
|
export interface SubmitEvent<T = TextInputNode> extends Omit<
|
|
@@ -556,7 +557,8 @@ export interface AcceleratorOptions {
|
|
|
556
557
|
* Exact on Control/Alt/Shift/Super, indifferent to Caps Lock and Num Lock,
|
|
557
558
|
* matched against the Latin keysym so a layout switch does not turn it off,
|
|
558
559
|
* and behind whatever a focused element consumed with `preventDefault()`.
|
|
559
|
-
*
|
|
560
|
+
* `Control` is the app's primary modifier: ⌘ on the Cocoa backend, the key
|
|
561
|
+
* its menus print. See docs/events.md.
|
|
560
562
|
*
|
|
561
563
|
* With no `scope` the binding belongs to the tree's top-level `<window>`, or,
|
|
562
564
|
* in an app that has none, to the root-level `<popup>` holding the keyboard —
|
|
@@ -569,10 +571,25 @@ export function useAccelerator(
|
|
|
569
571
|
options?: AcceleratorOptions,
|
|
570
572
|
): void;
|
|
571
573
|
|
|
574
|
+
export interface MatchesShortcutOptions {
|
|
575
|
+
/**
|
|
576
|
+
* The app's primary modifier — `primaryModifier(app)` from
|
|
577
|
+
* `react-x11/keysyms`. `'Super'` reads a chord's `Control` (and its
|
|
578
|
+
* `Super`) as ⌘ and wants ⌃ up, which is the Cocoa backend's rule;
|
|
579
|
+
* `'Control'`, the default, reads the chord literally.
|
|
580
|
+
*/
|
|
581
|
+
primary?: PrimaryModifier;
|
|
582
|
+
}
|
|
583
|
+
|
|
572
584
|
/**
|
|
573
585
|
* Whether a key event presses any alternative of a `shortcut` — the matcher
|
|
574
586
|
* the menus and {@link useAccelerator} both run on, for an application
|
|
575
|
-
* dispatching chords its own way.
|
|
587
|
+
* dispatching chords its own way. Pass the app's primary modifier to match
|
|
588
|
+
* the way they do:
|
|
589
|
+
*
|
|
590
|
+
* ```ts
|
|
591
|
+
* matchesShortcut(ev, [['Control', 'S']], { primary: primaryModifier(app) });
|
|
592
|
+
* ```
|
|
576
593
|
*/
|
|
577
594
|
export function matchesShortcut(
|
|
578
595
|
ev: {
|
|
@@ -584,4 +601,5 @@ export function matchesShortcut(
|
|
|
584
601
|
metaKey?: boolean;
|
|
585
602
|
},
|
|
586
603
|
shortcut: MenuShortcut | undefined,
|
|
604
|
+
options?: MatchesShortcutOptions,
|
|
587
605
|
): boolean;
|
package/src/types/nodes.d.ts
CHANGED
|
@@ -243,5 +243,11 @@ export interface NtkApp {
|
|
|
243
243
|
createWindow(args?: Record<string, unknown>): NtkWindow;
|
|
244
244
|
rootWindow(screen?: number): NtkWindow;
|
|
245
245
|
close(): Promise<void>;
|
|
246
|
+
/**
|
|
247
|
+
* The modifier shortcuts are pressed with on this backend: `'Super'` (⌘)
|
|
248
|
+
* on Cocoa, absent — Ctrl — everywhere else. Read it through
|
|
249
|
+
* `primaryModifierHeld` from `react-x11/keysyms`.
|
|
250
|
+
*/
|
|
251
|
+
readonly primaryModifier?: 'Control' | 'Super';
|
|
246
252
|
[key: string]: any;
|
|
247
253
|
}
|