react-x11 2.52.0 → 2.53.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.
Files changed (112) hide show
  1. package/README.md +10 -10
  2. package/docs/README.md +160 -314
  3. package/docs/accessibility.md +176 -229
  4. package/docs/appearance.md +77 -176
  5. package/docs/click-to-component.md +18 -51
  6. package/docs/clipboard.md +40 -59
  7. package/docs/components.md +518 -1036
  8. package/docs/dbus.md +61 -133
  9. package/docs/debugging.md +152 -245
  10. package/docs/desktop-calendar.md +51 -88
  11. package/docs/desktop.md +222 -347
  12. package/docs/devtools.md +50 -167
  13. package/docs/drag-and-drop.md +126 -204
  14. package/docs/ecosystem/animation.md +91 -125
  15. package/docs/ecosystem/data-fetching.md +38 -53
  16. package/docs/ecosystem/dev-tooling.md +159 -202
  17. package/docs/ecosystem/forms.md +61 -97
  18. package/docs/ecosystem/headless.md +64 -89
  19. package/docs/ecosystem/i18n.md +18 -21
  20. package/docs/ecosystem/icons.md +93 -126
  21. package/docs/ecosystem/layout.md +42 -52
  22. package/docs/ecosystem/routing.md +18 -29
  23. package/docs/ecosystem/state.md +71 -88
  24. package/docs/ecosystem/testing.md +131 -157
  25. package/docs/ecosystem/theming.md +119 -165
  26. package/docs/ecosystem.md +145 -179
  27. package/docs/elements.md +1091 -1774
  28. package/docs/embedding.md +83 -132
  29. package/docs/events.md +412 -695
  30. package/docs/extending.md +784 -1326
  31. package/docs/eyedropper.md +63 -122
  32. package/docs/filedialog.md +71 -107
  33. package/docs/frame.md +195 -339
  34. package/docs/gl.md +65 -62
  35. package/docs/globalmenu.md +63 -146
  36. package/docs/macos.md +531 -3234
  37. package/docs/notifications.md +33 -47
  38. package/docs/packaging.md +166 -309
  39. package/docs/permissions.md +9 -14
  40. package/docs/react-features.md +81 -106
  41. package/docs/remote.md +32 -76
  42. package/docs/scale.md +72 -186
  43. package/docs/styling.md +329 -547
  44. package/docs/system.md +200 -280
  45. package/docs/testing.md +63 -83
  46. package/docs/typescript.md +6 -48
  47. package/docs/uri-schemes.md +67 -107
  48. package/docs/wayland-backend.md +200 -459
  49. package/docs/windows-integrations.md +75 -131
  50. package/docs/windows.md +343 -1367
  51. package/package.json +3 -2
  52. package/src/Reconciler.js +2 -2
  53. package/src/a11y.js +1 -1
  54. package/src/backend/context2d.js +3 -3
  55. package/src/bootstrap.js +1 -1
  56. package/src/cocoa/app.js +6 -6
  57. package/src/cocoa/bezels.js +1 -1
  58. package/src/cocoa/fonts.js +1 -1
  59. package/src/cocoa/globalmenu.js +1 -1
  60. package/src/cocoa/keymap.js +1 -1
  61. package/src/cocoa/panewindow.js +1 -1
  62. package/src/cocoa/player.js +1 -1
  63. package/src/cocoa/presenter.js +2 -2
  64. package/src/cocoa/promotion.js +1 -1
  65. package/src/cocoa/relaunch.js +1 -1
  66. package/src/cocoa/threaded.js +1 -1
  67. package/src/cocoa/video.js +1 -1
  68. package/src/cocoa/window.js +4 -4
  69. package/src/components/Menu.js +7 -2
  70. package/src/components/Switch.js +1 -1
  71. package/src/components/native.js +1 -1
  72. package/src/dnd.js +2 -2
  73. package/src/glnodes.js +1 -1
  74. package/src/grid.js +1 -1
  75. package/src/layouts.js +1 -1
  76. package/src/nodes/animation.js +1 -1
  77. package/src/nodes/invalidate.js +1 -1
  78. package/src/nodes/layouthost.js +1 -1
  79. package/src/nodes/video.js +19 -8
  80. package/src/nodes/window/droptarget.js +1 -1
  81. package/src/pacing.js +1 -1
  82. package/src/refresh/index.js +2 -1
  83. package/src/types/video.d.ts +5 -2
  84. package/src/videobackend.js +28 -0
  85. package/src/videoframes.js +29 -16
  86. package/src/wayland/app.js +27 -0
  87. package/src/wayland/video.js +376 -0
  88. package/src/win32/a11y.js +1 -1
  89. package/src/win32/app.js +2 -2
  90. package/src/win32/fonts.js +1 -1
  91. package/src/win32/glarea.js +4 -4
  92. package/src/win32/overlay.js +1 -1
  93. package/src/win32/panewindow.js +3 -3
  94. package/src/win32/shell.js +1 -1
  95. package/src/win32/window.js +2 -2
  96. package/src/windowid.js +1 -1
  97. package/src/xvideo.js +189 -0
  98. package/docs/architecture/accessibility.md +0 -470
  99. package/docs/architecture/animation.md +0 -707
  100. package/docs/architecture/container-queries.md +0 -525
  101. package/docs/architecture/custom-layout.md +0 -334
  102. package/docs/architecture/drag-and-drop.md +0 -941
  103. package/docs/architecture/element-layer-contents.md +0 -622
  104. package/docs/architecture/frame-pacing.md +0 -255
  105. package/docs/architecture/grid-layout.md +0 -358
  106. package/docs/architecture/protocol-efficiency.md +0 -357
  107. package/docs/architecture/video.md +0 -1149
  108. package/docs/architecture/windowed-regions.md +0 -854
  109. package/docs/glx.md +0 -137
  110. package/docs/wayland.md +0 -545
  111. package/docs/windows-embedding.md +0 -271
  112. package/docs/windows-gl.md +0 -221
@@ -18,10 +18,9 @@ Both work on any drawn element and on `<window>` / `<popup>`. Their
18
18
  _presence_ is what registers the node — there is no provider to mount and
19
19
  no context to thread.
20
20
 
21
- > The design rationale — why the API is shaped this way, why the XDND
22
- > advertisement is unconditional, what was rejected — is a separate
23
- > document: [architecture/drag-and-drop.md](architecture/drag-and-drop.md).
24
- > This page is the reference.
21
+ Design notes — why the API is shaped this way, what was rejected, the
22
+ protocol details: [design/architecture/drag-and-drop.md](../design/architecture/drag-and-drop.md)
23
+ and [design/drag-and-drop.md](../design/drag-and-drop.md).
25
24
 
26
25
  ## Accepting drops
27
26
 
@@ -52,11 +51,10 @@ What the node will take. Four shapes:
52
51
  | `(types) => boolean` | your own predicate, given the offered type names |
53
52
  | _absent_ | anything — a bare `onDrop` is a valid dropzone |
54
53
 
55
- The three groups exist because the same payload arrives under different
56
- names depending on who is dragging, and exact-match filtering silently
57
- misses most of the desktop. The clipboard uses the same table, on purpose —
58
- what a file manager offers on a drop is what it offers on a copy, so
59
- [`clipboard.read('files')`](clipboard.md#reading) resolves it the same way:
54
+ The groups exist because the same payload arrives under different names
55
+ depending on who is dragging, and exact-match filtering misses most of the
56
+ desktop. The clipboard uses the same table, so
57
+ [`clipboard.read('files')`](clipboard.md#reading) resolves the same way:
60
58
 
61
59
  | group | matches |
62
60
  | --------- | ---------------------------------------------------------------------------------------------------------- |
@@ -71,11 +69,10 @@ catches only the last of those. Groups do not overlap: a Firefox link is
71
69
  `text/x-moz-url`, which is in `uris`, so accept `['uris', 'text']` if you
72
70
  want both. A group only ever expands to its own list.
73
71
 
74
- `dropAccept` is **data, not a callback**, and that is load-bearing rather
75
- than stylistic: the protocol's accept/reject answer is computed straight
76
- from it, inside the X message handler, without entering React. A window
77
- that is slow to answer stalls the drag cursor in _the other application_,
78
- which is a user-visible freeze in a program you do not own.
72
+ `dropAccept` is **data, not a callback**: the accept/reject answer to
73
+ another application is computed straight from it, without entering React,
74
+ because a window that is slow to answer freezes the drag cursor in _the
75
+ other application_.
79
76
 
80
77
  ### The drag events
81
78
 
@@ -108,7 +105,7 @@ and `dropped: false` — so releasing state there is safe.
108
105
  Which node is _the_ target, when they nest: the accept decision picks the
109
106
  **deepest** node whose `dropAccept` matches, skipping any node marked
110
107
  `disabled`, so a `<window>`-level `dropAccept` is a fallback that loses to
111
- any matching descendant. Two wrinkles worth knowing:
108
+ any matching descendant. Two wrinkles:
112
109
 
113
110
  - `onDrop` still dispatches capture → bubble along the whole ancestor path
114
111
  of the node under the pointer, so an ancestor with an `onDrop` sees the
@@ -149,14 +146,12 @@ rather than whether it would be taken (see [The drop](#the-drop)):
149
146
 
150
147
  `onDrop` may be `async`. For a drop from **another application** the
151
148
  protocol reply is held until the returned promise settles, so `await`ing a
152
- write is correct; a watchdog sends the reply anyway after 10 s, because a
153
- forgotten `await` in your app must not hang the drag gesture in someone
154
- else's.
149
+ write is correct; a watchdog sends the reply anyway after 10 s, so a
150
+ forgotten `await` cannot hang the other application's drag.
155
151
 
156
- For an **in-app** drop there is no protocol reply to hold, so nothing waits:
157
- `onDragEnd` fires immediately and an async `onDrop` finishes on its own
158
- afterwards. Do not rely on the drag being "over" only once your handler has
159
- resolved — if the source needs to know, have the handler tell it.
152
+ For an **in-app** drop nothing waits: `onDragEnd` fires immediately and an
153
+ async `onDrop` finishes on its own afterwards. If the source needs to know
154
+ when the handler resolved, have the handler tell it.
160
155
 
161
156
  The event adds three ways to reach the payload:
162
157
 
@@ -187,10 +182,9 @@ onDrop={async (e) => {
187
182
  applications, which is also how you tell the two apart without checking
188
183
  `e.source`.
189
184
 
190
- There is deliberately **no `e.dataTransfer`**. X selection transfer is
191
- asynchronous and the DOM's `getData()` is synchronous; a lookalike
192
- returning a promise would stringify to `"[object Promise]"` in template
193
- literals with no error anywhere. The different name is the warning.
185
+ There is deliberately **no `e.dataTransfer`**: X selection transfer is
186
+ asynchronous, and a DOM lookalike whose `getData()` returned a promise
187
+ would stringify to `"[object Promise]"` with no error anywhere.
194
188
 
195
189
  `onDrop` can also settle what the drop _did_, which is what the source is
196
190
  told afterwards:
@@ -215,17 +209,13 @@ what it was given, so **the drop is refused**, exactly as if it had called
215
209
  ([events.md](events.md#when-a-handler-throws)): the handler name, the
216
210
  element, and the component that rendered it.
217
211
 
218
- Refusing is the part worth knowing about. A `move` source deletes its own
219
- copy on the strength of being told the drop was taken, so a failed import
220
- that reported success is how a file goes missing. A later handler on the
221
- path can still `accept()`; the refusal is the failed one's answer, not the
222
- whole dispatch's.
212
+ Refusing matters because a `move` source deletes its own copy when told
213
+ the drop was taken. A later handler on the path can still `accept()`; the
214
+ refusal is the failed one's answer, not the whole dispatch's.
223
215
 
224
216
  An `async` failure is reported when the promise rejects. For a drop from
225
217
  another application that is before the protocol reply goes out, so the
226
- source hears about it. In-app it is after `onDragEnd` has already fired,
227
- for the same reason nothing else in that path waits — which is why it is
228
- reported rather than only returned.
218
+ source hears about it; in-app it is after `onDragEnd` has already fired.
229
219
 
230
220
  ### Sources that ask
231
221
 
@@ -248,14 +238,11 @@ onDrop={async (e) => {
248
238
  `actionDescriptions` is positional and the source's own wording — a `null`
249
239
  means it offered that action without describing it, so use your own label.
250
240
  Both arrays are empty for every other kind of drag, so they can be read
251
- without guarding, and nothing is read off the source's window unless a
252
- position actually asks.
241
+ without guarding.
253
242
 
254
- The menu is yours to draw. A built-in one would have to hold the protocol
255
- reply open for as long as it stayed up, and that reply is the only thing
256
- keeping the other application's drag cursor from freezing — so the choice
257
- of how long to make someone wait belongs to the app, not the toolkit.
258
- `ContextMenu` is the obvious thing to build it from.
243
+ The menu is yours to draw (`ContextMenu` is the obvious thing to build it
244
+ from): the protocol reply is held while it is open, and how long the other
245
+ application's drag cursor waits is the app's choice.
259
246
 
260
247
  ### `:drag-over`
261
248
 
@@ -265,12 +252,9 @@ follows, resolved the same way (a repaint, no React render). For most
265
252
  dropzones that is the whole highlight feature, with no handler and no
266
253
  state.
267
254
 
268
- One thing it does **not** do: it is not filtered by `dropAccept`. A node
269
- lights up because the pointer is over it, not because it would take the
270
- drop, so a zone that accepts only `image/png` still highlights while a text
271
- drag passes over. Where the difference matters — showing "yes" versus "no"
272
- feedback — use `useDropTarget` and style from `isAccepted`, which is
273
- exactly the distinction it exists to draw:
255
+ It is **not** filtered by `dropAccept`: a zone that accepts only
256
+ `image/png` still highlights while a text drag passes over. For "yes"
257
+ versus "no" feedback, use `useDropTarget` and style from `isAccepted`:
274
258
 
275
259
  ```jsx
276
260
  <box {...dropProps} style={[s.zone, isOver && (isAccepted ? s.yes : s.no)]} />
@@ -284,23 +268,20 @@ Nothing opts in: it applies to any scroll container a drag passes over,
284
268
  from this application or another one.
285
269
 
286
270
  The viewport it scrolls is the nearest scroll _container_ enclosing the
287
- pointer, walked out the same way the wheel walks. Something that scrolls
288
- pixels of its own without being a container — `<textarea>` and anything else
289
- that answers `canScroll` alone
290
- ([extending.md](extending.md#scrolling-content-you-painted)) — is
291
- deliberately excluded: nothing can be dropped into one, so scrolling it
292
- during a drag would only move content out of reach.
271
+ pointer, found the way the wheel finds one. Something that scrolls pixels
272
+ of its own without being a container — `<textarea>` and anything else that
273
+ answers `canScroll` alone
274
+ ([extending.md](extending.md#scrolling-content-you-painted)) — is excluded:
275
+ nothing can be dropped into one.
293
276
 
294
- Two consequences worth knowing:
277
+ Two consequences:
295
278
 
296
279
  - **The content moves under a stationary pointer**, so `onDragEnter`,
297
280
  `onDragLeave` and `onDragOver` keep firing while it scrolls, and
298
- `:drag-over` follows. That is the point — the node under the pointer
299
- really is changing.
300
- - **Do not `e.freeze()` a zone inside a scroll container.** Freezing tells the
301
- source to stop sending positions, and for a drag from another
302
- application those positions are what start the scrolling in the first
303
- place.
281
+ `:drag-over` follows.
282
+ - **Do not `e.freeze()` a zone inside a scroll container.** For a drag from
283
+ another application, the positions freezing stops are what drive the
284
+ scrolling.
304
285
 
305
286
  ### `useDropTarget`
306
287
 
@@ -322,11 +303,9 @@ return (
322
303
  );
323
304
  ```
324
305
 
325
- `accept` is the hook's name for `dropAccept` (inside a hook called
326
- `useDropTarget` the shorter word is unambiguous, and it is what
327
- react-dropzone users expect); `onDragOver`, `onDragEnter` and `onDragLeave`
328
- pass through. `isOver` is true whenever a drag is over the node,
329
- `isAccepted` only when it matches.
306
+ `accept` is the hook's name for `dropAccept` (as in react-dropzone);
307
+ `onDragOver`, `onDragEnter` and `onDragLeave` pass through. `isOver` is
308
+ true whenever a drag is over the node, `isAccepted` only when it matches.
330
309
 
331
310
  ## Starting drags
332
311
 
@@ -361,12 +340,13 @@ happens to it:
361
340
  | any other object | **by reference**, on `e.items` | `JSON.stringify`d |
362
341
  | a function (thunk) | called on drop | called when the drag first leaves your windows |
363
342
 
364
- Thunks are why a payload nobody asks for is never built. Offer
343
+ Thunks keep a payload nobody asks for from being built. Offer
365
344
  `'text/uri-list': () => buildUriList(selection)` and the list is assembled
366
- only when it is needed — at the drop for an in-app drag, and at the moment
367
- the pointer first leaves your windows for an external one. Note the second:
368
- crossing the window edge is enough to resolve every thunk, whether or not
369
- the drag is ever dropped.
345
+ at the drop for an in-app drag, and the moment the pointer first leaves your
346
+ windows for an external one — crossing the window edge resolves every
347
+ thunk, whether or not the drag is ever dropped. (On win32 every thunk
348
+ resolves when the drag starts; see
349
+ [platform notes](#interoperating-with-other-applications).)
370
350
 
371
351
  Type names are yours to choose. Use real MIME types for anything another
372
352
  application might want (`text/uri-list` is what every file manager reads),
@@ -385,18 +365,16 @@ target is told the one action you prefer, not the list, so
385
365
  choice. In-app targets see the same single requested action on `e.action`
386
366
  and can still answer with any of them.
387
367
 
388
- Note what this does _not_ include: **there is no modifier-key
389
- negotiation.** Holding Ctrl or Shift during a drag does not switch copy to
390
- move the way a desktop file manager does — the requested action is fixed
391
- when the drag starts, and only the target can change it.
368
+ **There is no modifier-key negotiation.** Holding Ctrl or Shift during a
369
+ drag does not switch copy to move the way a desktop file manager does — the
370
+ requested action is fixed when the drag starts, and only the target can
371
+ change it.
392
372
 
393
- Modifier state is readable on the **source** side, where the events carry
394
- the real X motion event: `e.ctrlKey` and `e.shiftKey` are meaningful in
395
- `onDrag`. On the **target** side they are always `false` —
396
- `onDragEnter`/`onDragOver`/`onDragLeave`/`onDrop` are built from a
397
- synthesized event with no button mask — so a dropzone cannot see what the
398
- user is holding. Decide the action from what is being dragged and where it
399
- lands.
373
+ Modifier state is readable on the **source** side: `e.ctrlKey` and
374
+ `e.shiftKey` are meaningful in `onDrag`. On the **target** side
375
+ (`onDragEnter`/`onDragOver`/`onDragLeave`/`onDrop`) they are always
376
+ `false`, so a dropzone cannot see what the user is holding — decide the
377
+ action from what is being dragged and where it lands.
400
378
 
401
379
  ### The gesture
402
380
 
@@ -456,16 +434,14 @@ return (
456
434
  ```
457
435
 
458
436
  `position` is `{ x, y, accepted }` in screen coordinates while dragging and
459
- `null` otherwise. **`dragPreview` on the `<popup>` is required**: it tells
460
- the router that this window is the preview, not something under the
461
- pointer, and without it the drag would immediately land on its own preview.
462
-
463
- `transparent` is not required, but a preview usually wants it: the popup is
464
- a real window, so a card with a `borderRadius` on an opaque one shows the
465
- window's own ground in the four corners the radius gives up. With it, the
466
- corners are the desktop, antialiased — and where no compositor is running
467
- the window falls back to filling itself square, which is what it looked
468
- like anyway. See
437
+ `null` otherwise. **`dragPreview` on the `<popup>` is required**: it marks
438
+ the window as the preview, not something under the pointer — without it
439
+ the drag would immediately land on its own preview.
440
+
441
+ `transparent` is optional but usually wanted: the popup is a real window,
442
+ so a card with a `borderRadius` shows the window's ground in its corners
443
+ unless the window is transparent. Where no compositor is running the window
444
+ falls back to filling itself square. See
469
445
  [`transparent`](elements.md#transparent--rounded-corners-and-translucency).
470
446
 
471
447
  ## The two transports
@@ -483,17 +459,14 @@ apart unless they ask.
483
459
 
484
460
  The switch is automatic and reversible: leave your windows and the drag is
485
461
  promoted to XDND; come back and it demotes again. Reorderable lists, trees
486
- and kanban boards therefore cost nothing on the wire, while the _same_
487
- component can also be dropped into GIMP.
488
-
489
- On the [cocoa backend](macos.md) the external transport is AppKit's own —
490
- `NSDraggingSource` and `NSDraggingDestination` on every window — and the
491
- switch is drawn differently: once your press crosses the threshold, AppKit
492
- tracks the gesture everywhere, and a drop on one of your own windows comes
493
- back through that window's destination with the payload still by reference
494
- (`e.source === 'internal'`, `e.items` live), so the table above holds. What
495
- another application reads is the pasteboard, where a `dragData` thunk is a
496
- promise the system asks for only when a consumer actually reads that type.
462
+ and kanban boards cost nothing on the wire, while the _same_ component can
463
+ also be dropped into GIMP.
464
+
465
+ On the [cocoa backend](macos.md) the external transport is AppKit's drag
466
+ session, and the table still holds: a drop on one of your own windows
467
+ arrives with `e.source === 'internal'` and `e.items` live. Another
468
+ application reads the pasteboard, where a `dragData` thunk is resolved only
469
+ when a consumer actually reads that type.
497
470
 
498
471
  ## Interoperating with other applications
499
472
 
@@ -513,75 +486,46 @@ managers, editors and upload widgets will take it.
513
486
  Platform notes:
514
487
 
515
488
  - **Linux / XWayland** — works both directions, including to and from
516
- native Wayland applications, which is what makes this worth having on a
517
- modern desktop.
518
- - **The Wayland backend** — works both directions too, over
519
- `wl_data_device` (`src/wayland/dnd.js`): a drag from another application
520
- arrives as the same `DropSession` the X11 path drives, and a drag out is a
521
- `wl_data_source` the compositor carries from the press serial on. As on
522
- the cocoa backend, the payload is read at the drop.
523
- - **Windows, the win32 backend** — works both directions with the desktop,
524
- over OLE: `IDropTarget` on every window with a `dropAccept` under it,
525
- `DoDragDrop` out of one. Files arrive as CF_HDROP and become one
526
- `text/uri-list`, text as CF_UNICODETEXT, and anything else as a clipboard
527
- format registered under its MIME name — which two react-x11 apps agree on
528
- for free and nothing else will, exactly as an X11 atom behaves.
529
-
530
- Two things differ. A `dragData` **thunk is not lazy**: every type's bytes
531
- are resolved when the drag starts, because a data object that asked JS for
532
- them from inside `DoDragDrop`'s modal loop would be reaching across the
533
- bridge's thread split at the one moment its UI thread cannot wait. And the
534
- shell's question "may this be dropped here" is answered one motion late —
535
- it picks a cursor, and the bridge returns the previous answer rather than
536
- blocking. The drop itself is answered exactly, because whether it was a
537
- copy or a move decides whether the source deletes its original.
538
-
539
- - **macOS, the cocoa backend** — works both directions with the desktop:
540
- files from the Finder arrive as `['files']` (each file is a pasteboard
541
- item of one `public.file-url`; they become one `text/uri-list`), text from
542
- any app as `['text']`, a URL from Safari as `['uris']`. Pasteboard types
543
- are UTIs, and react-x11's MIME vocabulary maps onto them through the OS's
544
- own type database — a private `application/x-myapp-…` travels between two
545
- react-x11 apps as a `dyn.*` type every process computes alike. Two things
546
- differ from X11. The payload is read **at the drop** (the pasteboard is the
547
- source's promise, and it may withdraw it once its session ends), so
548
- `getData()` answers from that read. And the gesture runs inside AppKit's
549
- own tracking loop, which owns the thread from the threshold to the
550
- release: no timer of ours fires and no frame clock ticks in between, so
551
- every frame of a drag is painted from the callback that reported it, and
552
- every render a drag schedules — a `<popup dragPreview>` following
553
- `useDragSource().position`, a `useDropTarget` hint, an insertion marker —
554
- is dispatched at discrete priority and landed there. The preview follows
555
- the pointer as it does on X11, and because a `<popup>` here is a
556
- non-activating panel at the pop-up-menu window level — above every other
557
- application's windows, which sit at the normal level — it is what the
558
- desktop sees while the pointer is over Finder or a browser. It is also
559
- the first window the window server finds when the pointer comes back
560
- over one of yours, and AppKit does not look past it: a window with no
561
- dragged types registered is still the one found, the drag then has no
562
- destination, and the window beneath never hears of it. So the popup is
563
- made transparent to the pointer — `dragPreview` sets the bridge's
564
- `ignoresMouseEvents` (@windowkit/appkit 0.6.0) — and the hit passes
565
- through it to the window it covers: the same exclusion the router makes
566
- on X11, made where the window server can see it. It registers no
567
- pasteboard types either, having nothing to accept with. What AppKit's own
568
- session carries is still a blank image: the drag has no bitmap of the
569
- system's, only the preview you draw. For the same reason the session's
570
- slide-back is off: left on, AppKit animates that blank image home over
571
- about a second _before_ it reports the release, so a drop nothing took
572
- reached `onDragEnd` — and the preview stayed frozen where the pointer let
573
- go — only after a pause spent animating nothing. The release ends the
574
- gesture the moment it happens, as on X11, and a return animation is the
575
- app's to draw from `onDragEnd`.
489
+ native Wayland applications.
490
+ - **The Wayland backend** — works both directions, over `wl_data_device`.
491
+ As on the cocoa backend, the payload is read at the drop.
492
+ - **Windows, the win32 backend** — works both directions, over OLE. Files
493
+ arrive as CF_HDROP and become one `text/uri-list`, text as
494
+ CF_UNICODETEXT, and any other type as a clipboard format registered under
495
+ its MIME name — which two react-x11 apps agree on and nothing else will.
496
+ Two differences: a `dragData` **thunk is not lazy** — every type's bytes
497
+ are resolved when the drag starts — and the shell's "may this be dropped
498
+ here" (the cursor) is answered one motion late. The drop itself is
499
+ answered exactly.
500
+ - **macOS, the cocoa backend** — works both directions: Finder files arrive
501
+ as `['files']` (one `text/uri-list`), text as `['text']`, a Safari URL as
502
+ `['uris']`. MIME names map onto UTIs through the OS's type database; a
503
+ private `application/x-myapp-…` travels between two react-x11 apps as a
504
+ `dyn.*` type. Differences from X11:
505
+ - The payload is read **at the drop**, and `getData()` answers from that
506
+ read.
507
+ - The gesture runs inside AppKit's tracking loop, from the threshold to
508
+ the release: **no timer fires and no frame clock ticks** in between.
509
+ Renders a drag schedules (a `<popup dragPreview>` following
510
+ `useDragSource().position`, a `useDropTarget` hint, an insertion marker)
511
+ are dispatched at discrete priority and painted from the drag callback.
512
+ - The `<popup dragPreview>` sits above every other application's windows,
513
+ so it stays visible over the Finder or a browser, and lets the pointer
514
+ pass through to the window beneath. The system draws no drag image of
515
+ its own — only your preview.
516
+ - There is no slide-back animation: the release reaches `onDragEnd` the
517
+ moment it happens, as on X11, and a return animation is the app's to
518
+ draw from there.
576
519
  - **macOS / XQuartz** — X client to X client works. Dragging from **Finder**
577
- into an X11 window does not, and never has:
578
- [XQuartz#173](https://github.com/XQuartz/XQuartz/issues/173). That is the
579
- X server's gap, not react-x11's; test against another X client — or run
580
- the cocoa backend, which has the Finder.
520
+ into an X11 window does not
521
+ ([XQuartz#173](https://github.com/XQuartz/XQuartz/issues/173)); test
522
+ against another X client, or run the cocoa backend.
581
523
  - **Over `ssh -X`** — each pointer position during an external drag costs a
582
- few round trips while resolving the foreign window under the cursor, so a
583
- drag feels heavier on a link than a click does. In-app drags cost
584
- nothing extra. See [remote.md](remote.md).
524
+ few round trips, so a drag feels heavier on a link than a click does.
525
+ In-app drags cost nothing extra. See [remote.md](remote.md).
526
+
527
+ Design notes on each platform:
528
+ [design/drag-and-drop.md](../design/drag-and-drop.md#interoperating-with-other-applications).
585
529
 
586
530
  ## Testing
587
531
 
@@ -606,8 +550,12 @@ await act(async () => {
606
550
 
607
551
  `onDragStart`, `onDrop` and `onDragEnd` all run, and `e.items` carries the
608
552
  live payload. Drops from _other_ applications need a second X connection
609
- playing the foreign source — `test/dnd.test.js` in this repo does exactly
610
- that if you need the pattern. See [testing.md](testing.md).
553
+ playing the foreign source — `test/dnd.test.js` in the react-x11 repository
554
+ shows the pattern. See [testing.md](testing.md).
555
+
556
+ Runnable demonstrations of both halves, as two separate processes, are
557
+ [`examples/dnd-source.jsx`](../examples/dnd-source.jsx) and
558
+ [`examples/dnd-target.jsx`](../examples/dnd-target.jsx).
611
559
 
612
560
  ## Limits and defaults
613
561
 
@@ -620,44 +568,18 @@ that if you need the pattern. See [testing.md](testing.md).
620
568
  | XDND protocol version | 5 (the current one; older sources are negotiated down) |
621
569
  | requires | ntk ≥ 5.4.0 |
622
570
 
571
+ On X11 every top-level window advertises XDND, whether or not it mounts a
572
+ drop target; one with none mounted refuses drags over it. Child windows
573
+ (`<glarea>`, a nested `<window>`) and a `<popup dragPreview>` never
574
+ advertise — drags over a child arrive at its top-level and are routed down.
575
+
623
576
  Not supported, deliberately or not yet:
624
577
 
625
578
  - **Motif drag and drop**, the pre-XDND protocol. Some legacy Motif and
626
579
  AWT applications speak only that and will not interoperate.
627
580
  - **XdndDirectSave (XDS)** — "drag out of the app to save a file into the
628
581
  file manager". A separate protocol layered on XDND.
629
- - **A built-in menu for `XdndActionAsk`.** The choices are handed to you —
630
- see [Sources that ask](#sources-that-ask) — but the menu itself is yours
631
- to draw. A built-in one would have to hold the reply while it was open,
632
- and the reply is what keeps another application's drag from freezing.
582
+ - **A built-in menu for `XdndActionAsk`** — the choices are handed to you
583
+ ([Sources that ask](#sources-that-ask)); the menu is yours to draw.
633
584
  - **`react-dnd`** and other DOM drag libraries — see
634
585
  [ecosystem.md](ecosystem.md).
635
-
636
- ## Protocol notes
637
-
638
- For anyone watching the wire, or debugging against another toolkit:
639
-
640
- - `XdndAware` (version 5) is written on every **top-level** window
641
- unconditionally at realize time. Child windows — `<glarea>`, a nested
642
- `<window>` — never advertise, per XDND v3+; drags over them arrive at the
643
- top-level and are routed down in JavaScript. A `<popup dragPreview>`
644
- never advertises either: it follows the pointer, and a window that is
645
- never under it has nothing to accept.
646
- - A window with **no drop targets mounted** answers with a refusal covering
647
- the whole window, so a well-behaved source stops asking while the pointer
648
- stays inside it. What an app that never uses drag and drop still pays:
649
- the `XdndAware` property, one batch of atom interns, and a session object
650
- per top-level window, all once at realize — plus one refusal per drag
651
- that crosses it.
652
- - The drop conversion uses the timestamp from `XdndDrop`, as the protocol
653
- requires, and `XdndSelection` is released at drag end with the timestamp
654
- it was acquired with (ICCCM 2.3.1).
655
- - As a source, the foreign window under the pointer is resolved per motion
656
- frame rather than from a cache built at drag start — correct, and a few
657
- round trips heavier on a remote display.
658
-
659
- The reasoning behind each of those, and the measurements that produced
660
- them, are in [architecture/drag-and-drop.md](architecture/drag-and-drop.md).
661
- Runnable demonstrations of both halves, as two separate processes, are
662
- [`examples/dnd-source.jsx`](../examples/dnd-source.jsx) and
663
- [`examples/dnd-target.jsx`](../examples/dnd-target.jsx).