react-x11 1.2.0 → 2.0.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 (158) hide show
  1. package/README.md +330 -81
  2. package/package.json +132 -14
  3. package/src/ClickToComponent.js +9 -2
  4. package/src/DevToolsIntegration.js +440 -32
  5. package/src/Reconciler.js +463 -165
  6. package/src/a11y.js +1299 -0
  7. package/src/acceleratorhooks.js +86 -0
  8. package/src/accelerators.js +128 -0
  9. package/src/activate.js +168 -0
  10. package/src/anchor.js +360 -0
  11. package/src/appcontext.js +161 -0
  12. package/src/appearance.js +748 -0
  13. package/src/appearancehooks.js +96 -0
  14. package/src/apphooks.js +73 -0
  15. package/src/application.js +748 -0
  16. package/src/atspi.js +1953 -0
  17. package/src/bus.js +545 -0
  18. package/src/bushooks.js +136 -0
  19. package/src/clientmessage.js +140 -0
  20. package/src/clipboard.js +121 -0
  21. package/src/components/Button.js +148 -33
  22. package/src/components/Checkbox.js +66 -32
  23. package/src/components/Dialog.js +96 -29
  24. package/src/components/FileDialog.js +491 -0
  25. package/src/components/Icon.js +329 -0
  26. package/src/components/Menu.js +797 -133
  27. package/src/components/PasswordInput.js +434 -0
  28. package/src/components/ProgressBar.js +91 -12
  29. package/src/components/Radio.js +78 -35
  30. package/src/components/Select.js +255 -65
  31. package/src/components/Slider.js +102 -41
  32. package/src/components/SplitPane.js +206 -0
  33. package/src/components/Switch.js +100 -22
  34. package/src/components/Table.js +529 -0
  35. package/src/components/Tabs.js +211 -0
  36. package/src/components/Tooltip.js +528 -54
  37. package/src/components/anchor.js +154 -107
  38. package/src/components/change.js +34 -0
  39. package/src/components/dnd.js +103 -0
  40. package/src/components/index.js +16 -3
  41. package/src/components/keys.js +17 -20
  42. package/src/components/scribble.js +247 -0
  43. package/src/components/theme.js +350 -42
  44. package/src/components/typeahead.js +12 -2
  45. package/src/compose.js +868 -0
  46. package/src/compositing.js +224 -0
  47. package/src/dbusmenu.js +384 -0
  48. package/src/debug.d.ts +63 -0
  49. package/src/debug.js +642 -0
  50. package/src/decorations.js +486 -0
  51. package/src/desktopsettings.js +209 -0
  52. package/src/desktopsettingshooks.js +62 -0
  53. package/src/dnd.js +1725 -0
  54. package/src/editmenu.js +272 -0
  55. package/src/errors.js +98 -0
  56. package/src/events.js +1406 -162
  57. package/src/extensions.js +45 -0
  58. package/src/filedialog.js +375 -0
  59. package/src/filedialoghooks.js +132 -0
  60. package/src/fonthooks.js +64 -0
  61. package/src/fonts.js +301 -0
  62. package/src/foreignnodes.js +519 -0
  63. package/src/frame/child.js +33 -0
  64. package/src/frame/childmain.js +274 -0
  65. package/src/frame/env.js +140 -0
  66. package/src/frame/index.js +444 -0
  67. package/src/frame/lifecycle.js +67 -0
  68. package/src/frame/protocol.js +179 -0
  69. package/src/frames.js +73 -0
  70. package/src/glbackend.js +114 -0
  71. package/src/glnodes.js +125 -67
  72. package/src/globalmenu.js +683 -0
  73. package/src/host.d.ts +88 -0
  74. package/src/host.js +42 -0
  75. package/src/idle.js +490 -0
  76. package/src/idlehooks.js +100 -0
  77. package/src/imagesource.js +349 -0
  78. package/src/index.d.ts +475 -0
  79. package/src/index.js +83 -9
  80. package/src/inputtime.js +206 -0
  81. package/src/jsx-dev-runtime.d.ts +21 -0
  82. package/src/jsx-dev-runtime.js +2 -0
  83. package/src/jsx-runtime.d.ts +39 -0
  84. package/src/jsx-runtime.js +6 -0
  85. package/src/keyboard.js +256 -0
  86. package/src/keyboardstate.js +278 -0
  87. package/src/keyboardstatehooks.js +58 -0
  88. package/src/keysyms.d.ts +149 -0
  89. package/src/keysyms.js +270 -0
  90. package/src/locale.js +170 -0
  91. package/src/localehooks.js +47 -0
  92. package/src/menuitem.js +223 -0
  93. package/src/node.d.ts +597 -0
  94. package/src/node.js +44 -0
  95. package/src/nodes.js +9546 -690
  96. package/src/ntk.d.ts +44 -0
  97. package/src/ntk.js +25 -0
  98. package/src/paintcache.js +366 -0
  99. package/src/palette.js +380 -0
  100. package/src/pastestate.js +66 -0
  101. package/src/portal.js +461 -0
  102. package/src/priority.js +26 -0
  103. package/src/refresh/index.d.ts +40 -0
  104. package/src/refresh/index.js +122 -0
  105. package/src/refresh/loader.d.ts +37 -0
  106. package/src/refresh/loader.js +401 -0
  107. package/src/refresh/register.d.ts +5 -0
  108. package/src/refresh/register.js +13 -0
  109. package/src/registry.js +232 -0
  110. package/src/scale.js +626 -0
  111. package/src/scalehooks.js +27 -0
  112. package/src/screencolor.js +640 -0
  113. package/src/screencolorhooks.js +101 -0
  114. package/src/screens.js +754 -0
  115. package/src/screenshooks.js +137 -0
  116. package/src/startup.js +302 -0
  117. package/src/style.d.ts +126 -0
  118. package/src/style.js +33 -0
  119. package/src/styles.js +1482 -6
  120. package/src/svgnodes.js +306 -0
  121. package/src/testing/a11y.js +484 -0
  122. package/src/testing/components.js +414 -0
  123. package/src/testing/events.js +407 -0
  124. package/src/testing/harness.js +455 -0
  125. package/src/testing/index.d.ts +558 -0
  126. package/src/testing/index.js +85 -0
  127. package/src/testing/mock-app.js +463 -0
  128. package/src/testing/pixels.js +152 -0
  129. package/src/testing/queries.js +224 -0
  130. package/src/textrange.js +83 -0
  131. package/src/textselection.js +439 -0
  132. package/src/trace-registry.js +63 -0
  133. package/src/transfer.js +93 -0
  134. package/src/types/appearance.d.ts +84 -0
  135. package/src/types/application.d.ts +173 -0
  136. package/src/types/components.d.ts +820 -0
  137. package/src/types/dbus.d.ts +177 -0
  138. package/src/types/elements.d.ts +897 -0
  139. package/src/types/events.d.ts +549 -0
  140. package/src/types/filedialog.d.ts +260 -0
  141. package/src/types/fonts.d.ts +124 -0
  142. package/src/types/frame.d.ts +146 -0
  143. package/src/types/globalmenu.d.ts +42 -0
  144. package/src/types/nodes.d.ts +199 -0
  145. package/src/types/screencolor.d.ts +84 -0
  146. package/src/types/style.d.ts +434 -0
  147. package/src/types/system.d.ts +287 -0
  148. package/src/windowid.js +151 -0
  149. package/src/windowstate.js +393 -0
  150. package/src/xsettings.js +336 -0
  151. package/src/yoga.d.ts +55 -0
  152. package/src/yoga.js +135 -0
  153. package/src/components/Canvas3D.js +0 -28
  154. package/src/geometry3d.js +0 -223
  155. package/src/pointer3d.js +0 -158
  156. package/src/raycast3d.js +0 -146
  157. package/src/richnodes.js +0 -436
  158. package/src/scene3d.js +0 -683
@@ -0,0 +1,820 @@
1
+ /**
2
+ * The widget set: plain React over the host primitives, themable through
3
+ * `ThemeProvider`. See docs/components.md.
4
+ */
5
+
6
+ import type { ComponentType, ReactNode, Ref, RefObject } from 'react';
7
+ import type { ColorSchemePreference } from './appearance.js';
8
+ import type { Color, StyleProp } from './style.js';
9
+ import type {
10
+ BoxProps,
11
+ CanvasProps,
12
+ DrawInfo,
13
+ GlAreaProps,
14
+ } from './elements.js';
15
+ import type { DrawnNode, Rect } from './nodes.js';
16
+ import type {
17
+ DragEndEvent,
18
+ DragEvent,
19
+ DragSourceEvent,
20
+ DragSourceProps,
21
+ DropAccept,
22
+ DropEvent,
23
+ DropTargetProps,
24
+ KeyboardEvent,
25
+ MouseEvent,
26
+ } from './events.js';
27
+
28
+ /**
29
+ * The palette every widget reads. A theme overrides what it cares about and
30
+ * inherits the rest; the shape tokens are what let a theme be more than a
31
+ * recolour.
32
+ */
33
+ export interface Theme {
34
+ border: string;
35
+ /** The border of a focused control. Named for focus and not for `:active`,
36
+ * which is the pressed state — the `…Active` tokens below. */
37
+ borderFocus: string;
38
+ /** The ground: what the window itself is painted with, under everything. */
39
+ background: string;
40
+ /** What is raised off it — a control's fill, a card, the sheet a menu or a
41
+ * dialog is drawn on. The same colour as `background` in the light palette
42
+ * and a step lighter in the dark one, where a card at the ground's own
43
+ * colour is a card you cannot see. */
44
+ surface: string;
45
+ text: string;
46
+ /** Secondary ink: a placeholder, a caption, a disabled label. */
47
+ textMuted: string;
48
+ hoverBackground: string;
49
+ hoverText: string;
50
+ accent: string;
51
+ accentHover: string;
52
+ /** The letters on an `accent` fill. Derived from it when a palette names
53
+ * the fill and not the ink — the more legible of this palette's own `text`
54
+ * and `background` — so an accent light enough that white disappears on it
55
+ * gets dark letters without being asked. */
56
+ accentText: string;
57
+ surfaceHover: string;
58
+ track: string;
59
+ /** What a screen says with colour: this failed, this worked, look at this,
60
+ * here is a note. Each works as ink as well as fill — 4.5:1 against the
61
+ * palette's own ground — so a validation message and the badge beside it
62
+ * are one token. `…Text` is the letters on the fill, derived by contrast
63
+ * like `accentText`. */
64
+ danger: string;
65
+ dangerHover: string;
66
+ dangerText: string;
67
+ success: string;
68
+ successText: string;
69
+ warning: string;
70
+ warningText: string;
71
+ info: string;
72
+ infoText: string;
73
+ /** The pressed step of each fill family: rest → `…Hover` → `…Active`.
74
+ * A control activates on the release, so this is the only thing it can
75
+ * show while it is being held. `danger` is the only status colour with
76
+ * one: a destructive button is the only status fill anyone presses. */
77
+ accentActive: string;
78
+ surfaceActive: string;
79
+ textMutedActive: string;
80
+ dangerActive: string;
81
+ /** The keyboard focus ring every focusable node under this palette draws
82
+ * on `:focus-visible` — read by the renderer, not by the widgets. */
83
+ focusRing: string;
84
+ focusRingWidth: number;
85
+ focusRingOffset: number;
86
+ radius: number;
87
+ radiusSmall: number;
88
+ /** A floating surface — a menu, a dropdown — which rounds on a wider
89
+ * scale than a control does: half the text size, and derived from
90
+ * `fontSize` for any palette that moves it without naming this. */
91
+ radiusPopup: number;
92
+ /** A row inside one, a step tighter so the highlight reads as a pill on
93
+ * the sheet rather than as a second edge just inside its own. */
94
+ radiusPopupItem: number;
95
+ /** A tooltip bubble — the smallest floating surface there is. */
96
+ radiusTooltip: number;
97
+ borderWidth: number;
98
+ /** The size unstyled text comes out at, and what the popup radii are
99
+ * derived from. */
100
+ fontSize: number;
101
+ /** The UI face, as a CSS-style family list. What a `<text>` that names no
102
+ * `fontFamily` inherits. */
103
+ fontFamily: string;
104
+ /** The face code surfaces use — a `<Code>`, a log pane, a hex dump. Not
105
+ * read by anything unstyled; it is there so `fontFamily: '$monoFamily'`
106
+ * says "this app's mono face" in one place. */
107
+ monoFamily: string;
108
+ /**
109
+ * Which way this app reads. Seeded from the environment's locale, so an app
110
+ * started under an RTL locale is mirrored without being asked; set it to
111
+ * mirror a whole UI from one place — the provider plants the matching
112
+ * `direction` style property in the tree, so the boxes and the widgets
113
+ * follow it together.
114
+ */
115
+ direction: 'ltr' | 'rtl';
116
+ paddingX: number;
117
+ paddingY: number;
118
+ }
119
+
120
+ export interface ThemeProviderProps {
121
+ /**
122
+ * Merges over the scheme in force — the desktop's palette, or an outer
123
+ * provider's. So a partial palette names what this app changes and
124
+ * everything else keeps following the desktop.
125
+ */
126
+ value?: Partial<Theme>;
127
+ /**
128
+ * A palette layered on only when the scheme in force is dark — for a design
129
+ * whose two schemes are not one recolour of the other. Usually unnecessary:
130
+ * `value` already layers over the desktop's own light or dark palette.
131
+ */
132
+ dark?: Partial<Theme>;
133
+ /**
134
+ * `'system'` (the default) follows the desktop. `'light'` and `'dark'` pin
135
+ * this subtree, for an app whose own settings own the choice — and pinning
136
+ * is the complete opt-out: nothing under a pinned provider asks the desktop
137
+ * anything.
138
+ */
139
+ colorScheme?: ColorSchemePreference;
140
+ /**
141
+ * Style for the box the provider renders to carry the palette into the
142
+ * node tree. Defaults to `{ flexGrow: 1 }` — an app-level provider fills
143
+ * its parent. No box is rendered above a `<window>`, which may not sit
144
+ * inside one; the palette goes on the window itself.
145
+ */
146
+ style?: StyleProp;
147
+ children?: ReactNode;
148
+ }
149
+
150
+ /**
151
+ * Themes everything below it, by both routes at once: widgets read the
152
+ * palette through {@link useTheme}, and a `$token` in a style resolves
153
+ * against the `theme` prop the provider plants in the node tree.
154
+ */
155
+ export const ThemeProvider: ComponentType<ThemeProviderProps>;
156
+
157
+ /**
158
+ * The palette as a bag of tokens, which is what it is at runtime: a theme
159
+ * merges over whatever is above it and a `$name` in a style resolves against
160
+ * the result, so a component that reads tokens by name — a code block asking
161
+ * for `surface`/`text`/`textMuted`, a renderer resolving a palette it was handed
162
+ * — can index it. The named tokens keep their types; anything else is
163
+ * `unknown` and has to be narrowed, which is the honest answer for a name
164
+ * only the caller knows.
165
+ *
166
+ * `<ThemeProvider value>` stays closed (`Partial<Theme>`) on purpose: a typo
167
+ * in a palette is a bug, and there is nowhere else to catch it.
168
+ */
169
+ export interface ThemeTokens extends Theme {
170
+ readonly [token: string]: unknown;
171
+ }
172
+
173
+ /** The palette in force: merged over the defaults and over any outer
174
+ * provider, and the same object `$token` resolution sees. */
175
+ export function useTheme(): ThemeTokens;
176
+
177
+ /**
178
+ * Which way the widgets here read. Yoga mirrors the boxes on its own from the
179
+ * `direction` style property, so this is for what it cannot decide: which way
180
+ * an arrow key steps, which way a glyph points, which side a menu opens on.
181
+ */
182
+ export function useDirection(): 'ltr' | 'rtl';
183
+
184
+ /** Props a widget passes through to the `<box>` it renders. */
185
+ type WidgetProps = Omit<BoxProps, 'children' | 'style' | 'ref'>;
186
+
187
+ /**
188
+ * What a value widget's `onChange` receives — one signature across the
189
+ * library, so `onChange={formik.handleChange}` wires to a `Checkbox` exactly
190
+ * as it does to a `<textinput>`.
191
+ *
192
+ * The new value is `ev.value`, so `onChange={(ev) => setChecked(ev.value)}`
193
+ * is the plain-React form.
194
+ *
195
+ * `target` is a plain descriptor, not a node: a widget is several nodes and
196
+ * none of them holds its value, so there is nothing honest to point at. Its
197
+ * shape is what formik's `handleChange` and react-hook-form's event reader
198
+ * destructure, `type` included.
199
+ */
200
+ export interface WidgetChangeEvent<V = unknown> {
201
+ type: 'change';
202
+ target: {
203
+ /** `'checkbox'`, `'radio'`, `'select-one'`, `'range'`. */
204
+ type: string;
205
+ name?: string;
206
+ value: V;
207
+ /** Checkbox and switch only. */
208
+ checked?: boolean;
209
+ };
210
+ currentTarget: WidgetChangeEvent<V>['target'];
211
+ name?: string;
212
+ value: V;
213
+ }
214
+
215
+ /** Widgets that are one form field: they take a `name` and echo it back. */
216
+ interface NamedWidget {
217
+ /**
218
+ * Field name, echoed on the change event as `ev.name` and
219
+ * `ev.target.name`. Nothing in react-x11 reads it — it is there so a form
220
+ * library has somewhere to put one.
221
+ */
222
+ name?: string;
223
+ }
224
+
225
+ /**
226
+ * The system icon set: the affordance glyphs core's own widgets are drawn
227
+ * with. Affordances only — no nouns; a folder or a save icon belongs to an
228
+ * icon theme. See docs/components.md#system-icons.
229
+ */
230
+ export type IconName =
231
+ | 'chevronRight'
232
+ | 'chevronLeft'
233
+ | 'chevronDown'
234
+ | 'chevronUp'
235
+ | 'check'
236
+ | 'dash'
237
+ | 'dot'
238
+ | 'close'
239
+ | 'plus'
240
+ | 'moreVertical'
241
+ | 'eye'
242
+ | 'eyeOff';
243
+
244
+ export interface IconProps extends Omit<CanvasProps, 'onDraw' | 'cacheKey'> {
245
+ name: IconName;
246
+ /**
247
+ * The size of the mark, not of a grid it sits in — the ink runs corner to
248
+ * corner, unlike lucide and its descendants. A chevron's arms are at 45°,
249
+ * so it is `size` along the way it points across and half that the other
250
+ * way. Default: a shade under the palette's `fontSize`.
251
+ */
252
+ size?: number;
253
+ /** Default: the ink of the element around it, and under that the palette's
254
+ * `text` — `color` inherits, so an icon in a row that dims itself dims
255
+ * with it. Pass this only to say something the surrounding text does not. */
256
+ color?: Color;
257
+ style?: StyleProp;
258
+ }
259
+ export const Icon: ComponentType<IconProps>;
260
+
261
+ /**
262
+ * The drawings themselves, keyed by name — each one an `onDraw` for
263
+ * `<canvas mono>`, for a widget that wants the glyph without the component.
264
+ */
265
+ export const icons: Readonly<
266
+ Record<IconName, (ctx: any, info: DrawInfo) => void>
267
+ >;
268
+ export const iconNames: readonly IconName[];
269
+ /** The default icon box for a given `fontSize`. */
270
+ export function iconSize(fontSize: number): number;
271
+
272
+ export interface ButtonProps extends WidgetProps {
273
+ children?: ReactNode;
274
+ label?: string;
275
+ onPress?: (ev: MouseEvent<DrawnNode>) => void;
276
+ primary?: boolean;
277
+ /** How much chrome: a fill, a border on nothing, or neither. */
278
+ variant?: 'solid' | 'outline' | 'ghost';
279
+ /** `'small'` is the compact metric for toolbars and inline rows. */
280
+ size?: 'medium' | 'small';
281
+ disabled?: boolean;
282
+ style?: StyleProp;
283
+ }
284
+ export const Button: ComponentType<ButtonProps>;
285
+
286
+ export interface PasswordMaskInfo {
287
+ /** How wide the mask should be — what the field worked out from the
288
+ * length, not the width of the box. */
289
+ width: number;
290
+ height: number;
291
+ /** Seeded from the window and a hash of the value, so it changes on every
292
+ * keystroke and repaints identically in between. */
293
+ seed: number;
294
+ color: Color;
295
+ length: number;
296
+ }
297
+
298
+ export interface PasswordInputProps extends WidgetProps, NamedWidget {
299
+ value?: string;
300
+ defaultValue?: string;
301
+ onChange?: (ev: WidgetChangeEvent<string>) => void;
302
+ /** Enter. */
303
+ onSubmit?: (value: string) => void;
304
+ placeholder?: string;
305
+ /** Show the reveal eye at all. Default `true`, as GTK's peek icon is. */
306
+ revealable?: boolean;
307
+ /** Drive the reveal yourself — for a toggle the keyboard can reach, the
308
+ * built-in eye being a pointer affordance. */
309
+ revealed?: boolean;
310
+ onRevealChange?: (revealed: boolean) => void;
311
+ /** In code points, not UTF-16 units. */
312
+ maxLength?: number;
313
+ /** Draw the mask instead of the scribble. `ctx` is ntk's canvas-like 2d
314
+ * context, translated to the mask's origin. */
315
+ drawMask?: (ctx: any, info: PasswordMaskInfo) => void;
316
+ disabled?: boolean;
317
+ style?: StyleProp;
318
+ }
319
+ export const PasswordInput: ComponentType<PasswordInputProps>;
320
+
321
+ export interface CheckboxProps extends WidgetProps, NamedWidget {
322
+ children?: ReactNode;
323
+ label?: string;
324
+ checked?: boolean;
325
+ onChange?: (ev: WidgetChangeEvent<boolean>) => void;
326
+ disabled?: boolean;
327
+ style?: StyleProp;
328
+ }
329
+ export const Checkbox: ComponentType<CheckboxProps>;
330
+
331
+ export interface RadioGroupProps<T = unknown> extends WidgetProps, NamedWidget {
332
+ value?: T;
333
+ onChange?: (ev: WidgetChangeEvent<T>) => void;
334
+ children?: ReactNode;
335
+ style?: StyleProp;
336
+ }
337
+ export function RadioGroup<T = unknown>(props: RadioGroupProps<T>): ReactNode;
338
+
339
+ export interface RadioProps<T = unknown> {
340
+ value: T;
341
+ children?: ReactNode;
342
+ label?: string;
343
+ disabled?: boolean;
344
+ }
345
+ export function Radio<T = unknown>(props: RadioProps<T>): ReactNode;
346
+
347
+ export interface SwitchProps extends WidgetProps, NamedWidget {
348
+ checked?: boolean;
349
+ onChange?: (ev: WidgetChangeEvent<boolean>) => void;
350
+ disabled?: boolean;
351
+ style?: StyleProp;
352
+ }
353
+ export const Switch: ComponentType<SwitchProps>;
354
+
355
+ export interface ProgressBarProps extends WidgetProps {
356
+ /** 0 to 1. Ignored when {@link ProgressBarProps.indeterminate} is set. */
357
+ value?: number;
358
+ /**
359
+ * Working, with no idea how far along: a block slides across the track
360
+ * forever and the bar says `aria-busy` with no value. Honours the
361
+ * desktop's reduced-motion setting — see docs/components.md.
362
+ */
363
+ indeterminate?: boolean;
364
+ color?: Color;
365
+ trackColor?: Color;
366
+ height?: number;
367
+ style?: StyleProp;
368
+ }
369
+ export const ProgressBar: ComponentType<ProgressBarProps>;
370
+
371
+ export interface SliderProps extends WidgetProps, NamedWidget {
372
+ value?: number;
373
+ min?: number;
374
+ max?: number;
375
+ step?: number;
376
+ onChange?: (ev: WidgetChangeEvent<number>) => void;
377
+ disabled?: boolean;
378
+ height?: number;
379
+ style?: StyleProp;
380
+ }
381
+ export const Slider: ComponentType<SliderProps>;
382
+
383
+ export type Placement = 'top' | 'bottom' | 'left' | 'right';
384
+
385
+ export interface TooltipProps extends WidgetProps {
386
+ /**
387
+ * The hint. A string is measured and the popup sized around it; an
388
+ * element **self-sizes too** — the popup is rendered once hidden at its
389
+ * natural size, read back, and placed before it is ever mapped — so a card
390
+ * whose height depends on its content needs no numbers. It gets the
391
+ * bubble to fill and draws its own padding.
392
+ */
393
+ label: ReactNode;
394
+ children?: ReactNode;
395
+ /**
396
+ * Which side of the trigger to open on. `'auto'` (the default) takes the
397
+ * first side the hint fits on, preferring above; a named side is still a
398
+ * preference rather than a promise, since it flips to its opposite rather
399
+ * than opening off-screen.
400
+ */
401
+ direction?: Placement | 'auto';
402
+ /** The older name for {@link TooltipProps.direction}; wins where given. */
403
+ placement?: Placement;
404
+ /** ms before it appears (default 500). */
405
+ delay?: number;
406
+ fontSize?: number;
407
+ /**
408
+ * Pin an axis of the bubble exactly. For text they override the measured
409
+ * size; for an element, a pinned axis stays pinned and the other is
410
+ * measured for it — `width={340}` is a fixed column whose height fits the
411
+ * message. Give both and nothing is measured at all.
412
+ */
413
+ width?: number;
414
+ height?: number;
415
+ /**
416
+ * Cap what an element label may measure out to, on top of the screen's
417
+ * own cap. Inert on an axis pinned by {@link TooltipProps.width} /
418
+ * {@link TooltipProps.height}, and on a text label, which stays one line.
419
+ */
420
+ maxWidth?: number;
421
+ maxHeight?: number;
422
+ style?: StyleProp;
423
+ }
424
+ export const Tooltip: ComponentType<TooltipProps>;
425
+
426
+ export interface DialogProps extends WidgetProps {
427
+ open?: boolean;
428
+ title?: string;
429
+ children?: ReactNode;
430
+ /**
431
+ * `true` (the default) makes the dialog a **window-manager-managed**
432
+ * window with `WM_TRANSIENT_FOR` pointing at its owner: framed, movable,
433
+ * closable through the WM, out of the taskbar and alt-tab list, and — on a
434
+ * full EWMH window manager — stacked above its owner and iconified with
435
+ * it. A press outside does **not** close it, because a real dialog does
436
+ * not close that way; Escape and the WM close button both call
437
+ * {@link DialogProps.onClose}.
438
+ *
439
+ * `false` is the override-redirect popup 1.x shipped: no frame, not
440
+ * movable, and a press anywhere outside dismisses it (a pointer grab).
441
+ * Right for a transient confirmation on a display with no window manager.
442
+ */
443
+ managed?: boolean;
444
+ onClose?: () => void;
445
+ actions?: ReactNode;
446
+ width?: number;
447
+ height?: number;
448
+ style?: StyleProp;
449
+ }
450
+ export const Dialog: ComponentType<DialogProps>;
451
+
452
+ /** An option, or a plain value used as both value and label. */
453
+ export type SelectOption<T = unknown> = { value: T; label: string } | T;
454
+
455
+ export interface SelectProps<T = unknown> extends WidgetProps, NamedWidget {
456
+ value?: T;
457
+ options?: readonly SelectOption<T>[];
458
+ onChange?: (ev: WidgetChangeEvent<T>) => void;
459
+ placeholder?: string;
460
+ style?: StyleProp;
461
+ }
462
+ export function Select<T = unknown>(props: SelectProps<T>): ReactNode;
463
+
464
+ /** dbusmenu's `toggle-type`. */
465
+ export type MenuToggleType = 'checkmark' | 'radio';
466
+
467
+ /** dbusmenu's `toggle-state`: off, on, and "on for part of the selection". */
468
+ export type MenuToggleState = 0 | 1 | -1;
469
+
470
+ /** dbusmenu's `disposition` — what kind of attention the item wants. */
471
+ export type MenuDisposition = 'normal' | 'informative' | 'warning' | 'alert';
472
+
473
+ /**
474
+ * One `aas` shortcut: a list of **alternatives**, each a list of modifier
475
+ * tokens (`Control`, `Alt`, `Shift`, `Super`) ending in the key.
476
+ *
477
+ * ```ts
478
+ * shortcut: [['Control', 'S']]
479
+ * ```
480
+ *
481
+ * The key is named the way GDK names it — `plus`, not `+` — because that is
482
+ * what a panel's importer parses. Menus print the friendly form.
483
+ */
484
+ export type MenuShortcut = readonly (readonly string[])[];
485
+
486
+ /**
487
+ * An item in a menu.
488
+ *
489
+ * The vocabulary is `com.canonical.dbusmenu`'s, so the identical array
490
+ * serialises to the desktop's global menu with no translation and there is
491
+ * no second authoring model. See docs/globalmenu.md.
492
+ */
493
+ export interface MenuItem {
494
+ label?: string;
495
+ /** `'separator'` draws a rule instead of a row. Absent means `'standard'`. */
496
+ type?: 'standard' | 'separator';
497
+ /** Present and non-empty makes this a submenu parent. */
498
+ items?: MenuItem[];
499
+ /** Defaults to `true`; `false` dims the row and makes it inert. */
500
+ enabled?: boolean;
501
+ /** Defaults to `true`; `false` removes the row from the menu entirely. */
502
+ visible?: boolean;
503
+ /**
504
+ * Drawn right-aligned, announced as `aria-keyshortcuts`, sent to the
505
+ * panel — **and bound**: pressing it fires this item's `onSelect` while
506
+ * the menu is mounted, without the menu being opened (#351). `enabled`
507
+ * and `visible` gate the binding with the row. `accelerators={false}` on
508
+ * the menu turns that off for an app with a dispatcher of its own.
509
+ */
510
+ shortcut?: MenuShortcut;
511
+ /** With `toggleState`, draws a check mark or a radio dot in the gutter. */
512
+ toggleType?: MenuToggleType;
513
+ toggleState?: MenuToggleState;
514
+ /** An icon-theme name, for the desktop's menu. Ignored by the drawn one. */
515
+ iconName?: string;
516
+ /** Raw PNG bytes, for an icon that is not in a theme. */
517
+ iconData?: Uint8Array;
518
+ disposition?: MenuDisposition;
519
+ /**
520
+ * Drawn in the 16px column left of the label — the same column the toggle
521
+ * mark uses, so an item that is both checked and iconned shows the check.
522
+ *
523
+ * **Local only**, and the one deliberate departure from the dbusmenu
524
+ * vocabulary: the desktop cannot call a function, so an item that wants an
525
+ * icon in both menus carries `icon` for ours and `iconName` for the panel's.
526
+ *
527
+ * A string is drawn as text, which is a one-liner but only as good as the
528
+ * font: an exotic glyph is tofu on a machine without it. A **function**
529
+ * is called with the colour the row's label is being drawn in and the
530
+ * size the gutter allows, which is what a `<canvas onDraw>` icon needs —
531
+ * it has to pick a stroke colour, and nothing else can tell it whether
532
+ * its row is highlighted, disabled or at rest. An element renders as-is.
533
+ */
534
+ icon?:
535
+ | string
536
+ | number
537
+ | ReactNode
538
+ | ((state: { color: string; size: number }) => ReactNode);
539
+ /** Identity for React keys and for the exported item's stable integer id. */
540
+ key?: string;
541
+ onSelect?: (item: MenuItem) => void;
542
+ [key: string]: unknown;
543
+ }
544
+
545
+ export interface ContextMenuProps extends WidgetProps {
546
+ items?: readonly MenuItem[];
547
+ children?: ReactNode;
548
+ onSelect?: (item: MenuItem) => void;
549
+ /**
550
+ * Honour the items' own `shortcut`s while this menu is mounted. On by
551
+ * default; `false` leaves the chords to an app that dispatches its own.
552
+ */
553
+ accelerators?: boolean;
554
+ fontSize?: number;
555
+ style?: StyleProp;
556
+ }
557
+ export const ContextMenu: ComponentType<ContextMenuProps>;
558
+
559
+ export interface MenuBarMenu {
560
+ label: string;
561
+ items: MenuItem[];
562
+ visible?: boolean;
563
+ key?: string;
564
+ }
565
+
566
+ export interface MenuBarProps extends WidgetProps {
567
+ menus?: readonly MenuBarMenu[];
568
+ onSelect?: (item: MenuItem) => void;
569
+ /**
570
+ * Hand the bar to the desktop's panel where there is one. On by default,
571
+ * and `false` keeps it in the window. See docs/globalmenu.md.
572
+ */
573
+ globalMenu?: boolean;
574
+ /**
575
+ * Called with `true` when the desktop's panel takes the menu and `false`
576
+ * when it gives it back. The one fact about this an app cannot work out for
577
+ * itself: calling `useGlobalMenu` beside a `MenuBar` would export the menu
578
+ * twice.
579
+ */
580
+ onGlobalMenuChange?: (exported: boolean) => void;
581
+ /**
582
+ * Honour the items' own `shortcut`s while this bar is mounted — including
583
+ * when the desktop's panel has taken the menu over, since the panel draws
584
+ * the rows but the key is pressed in this window. On by default; `false`
585
+ * leaves the chords to an app that dispatches its own.
586
+ */
587
+ accelerators?: boolean;
588
+ fontSize?: number;
589
+ style?: StyleProp;
590
+ }
591
+ export const MenuBar: ComponentType<MenuBarProps>;
592
+
593
+ export interface TabItem {
594
+ id: string;
595
+ label: string;
596
+ content?: ReactNode;
597
+ disabled?: boolean;
598
+ }
599
+
600
+ export interface TabsProps extends WidgetProps {
601
+ items?: readonly TabItem[];
602
+ value?: string;
603
+ defaultValue?: string;
604
+ onChange?: (id: string) => void;
605
+ orientation?: 'horizontal' | 'vertical';
606
+ /** Arrow keys move the highlight without selecting until Enter/Space. */
607
+ manual?: boolean;
608
+ style?: StyleProp;
609
+ }
610
+ export const Tabs: ComponentType<TabsProps>;
611
+
612
+ export interface TableColumn<Row = any> {
613
+ id: string;
614
+ label?: string;
615
+ width?: number;
616
+ align?: 'left' | 'right' | 'center';
617
+ /** Pull the cell value out of a row; defaults to `row[id]`. */
618
+ value?: (row: Row) => unknown;
619
+ /**
620
+ * Draw the cell yourself. `selected` says the cell is on the selected row —
621
+ * a filled bar — so a colour chosen against the resting background has to
622
+ * give way to `hoverText` there.
623
+ */
624
+ render?: (
625
+ row: Row,
626
+ context: { selected: boolean; column: TableColumn<Row> },
627
+ ) => ReactNode;
628
+ }
629
+
630
+ export interface TableSort {
631
+ column: string;
632
+ direction: 'asc' | 'desc';
633
+ }
634
+
635
+ export interface TableProps<Row = any> extends WidgetProps {
636
+ columns?: readonly TableColumn<Row>[];
637
+ rows?: readonly Row[];
638
+ rowHeight?: number;
639
+ sort?: TableSort | null;
640
+ defaultSort?: TableSort | null;
641
+ onSortChange?: (sort: TableSort | null) => void;
642
+ selected?: string | number | null;
643
+ defaultSelected?: string | number | null;
644
+ onSelect?: (id: string | number, row: Row) => void;
645
+ /** Double click, or Enter on the selected row. */
646
+ onActivate?: (id: string | number, row: Row) => void;
647
+ onColumnResize?: (id: string, width: number) => void;
648
+ style?: StyleProp;
649
+ }
650
+ export function Table<Row = any>(props: TableProps<Row>): ReactNode;
651
+
652
+ export interface SplitPaneProps extends WidgetProps {
653
+ direction?: 'row' | 'column';
654
+ size?: number;
655
+ defaultSize?: number;
656
+ /** Minimum size of the first pane. */
657
+ min?: number;
658
+ /** Minimum size of the second. */
659
+ minSecond?: number;
660
+ onResize?: (size: number) => void;
661
+ /** Exactly two children: the two panes. */
662
+ children?: ReactNode;
663
+ style?: StyleProp;
664
+ }
665
+ export const SplitPane: ComponentType<SplitPaneProps>;
666
+
667
+ // --- anchoring helpers -----------------------------------------------------
668
+
669
+ /**
670
+ * The side a popup is asked for. `'start'`/`'end'` are the **logical** ones
671
+ * and mirror with the anchor's direction, which is what a submenu wants;
672
+ * `'left'`/`'right'` stay physical.
673
+ */
674
+ export type AnchorPlacement = Placement | 'start' | 'end';
675
+
676
+ export interface AnchorOptions {
677
+ /** Preferred side, flipped at a screen edge (default `'bottom'`). */
678
+ placement?: AnchorPlacement;
679
+ /** Which edges line up on the cross axis (default `'start'`). */
680
+ align?: 'start' | 'center' | 'end';
681
+ /** Gap from the anchor along the placement axis, in px (default 2). */
682
+ offset?: number;
683
+ /**
684
+ * Shift along the *alignment* axis, applied before the popup is clamped
685
+ * into the screen. For lining up what is drawn in a surface rather than
686
+ * the surface itself: a submenu level with the row that opened it has its
687
+ * first item a border and a padding lower, and the eye lines up the items.
688
+ */
689
+ alignOffset?: number;
690
+ /**
691
+ * Anchor to a rect **inside** the node, in the node's own coordinates:
692
+ * a caret, a table cell, a chart datapoint. Everything reads it — the
693
+ * side that flips, the edge that aligns, the gap `offset` leaves — so a
694
+ * popup at a caret behaves like one at a small widget that happens to be
695
+ * there. `width`/`height` default to 0, so `{x, y}` alone is a point.
696
+ */
697
+ at?: { x: number; y: number; width?: number; height?: number };
698
+ /** The size of the popup being placed. `width` defaults to the anchor's. */
699
+ width?: number;
700
+ height?: number;
701
+ /** Take the alignment from this node instead — a submenu lines up with
702
+ * the row that opened it while hanging off the menu's outer edge. */
703
+ alignTo?: DrawnNode | null;
704
+ /** Overrides the direction read off the anchoring node. */
705
+ direction?: 'ltr' | 'rtl';
706
+ }
707
+
708
+ /** An anchored rect, and the side it actually ended up on. */
709
+ export interface AnchorRect extends Rect {
710
+ placement: Placement;
711
+ }
712
+
713
+ /**
714
+ * Where to put a `<popup>` relative to a node, in screen coordinates,
715
+ * flipped at screen edges. `null` for a node that has not been laid out.
716
+ */
717
+ export function anchorRect(
718
+ node: DrawnNode | null,
719
+ options?: AnchorOptions,
720
+ ): AnchorRect | null;
721
+
722
+ /** Centre a popup of this size on the node's screen. */
723
+ export function centerRect(
724
+ node: DrawnNode,
725
+ size: { width: number; height: number },
726
+ ): Rect;
727
+
728
+ /**
729
+ * The area a popup anchored to this node may be placed in: the usable part
730
+ * of the monitor it is on — per-monitor, minus the panels, the same answer
731
+ * `<window width="auto">` is capped by. What a widget sizing its own surface
732
+ * needs (a menu is never wider than the screen it opens on). `null` where
733
+ * there is no display to ask, which includes the headless mock.
734
+ */
735
+ export function anchorArea(node: DrawnNode | null): Rect | null;
736
+
737
+ /**
738
+ * A node's laid-out rect in screen coordinates — where a popup's *trigger*
739
+ * is, rather than where to put the popup. What a surface has to know when
740
+ * it points back at the thing it belongs to: a tooltip's arrow aims at the
741
+ * middle of the trigger, and that stops being the middle of the tooltip as
742
+ * soon as a screen edge slides one of the two.
743
+ */
744
+ export function screenRect(node: DrawnNode): Rect | null;
745
+
746
+ /** `anchorRect` bound to a ref, recomputed on demand. */
747
+ export function useAnchor(
748
+ ref: RefObject<DrawnNode | null>,
749
+ ): (options?: AnchorOptions) => AnchorRect | null;
750
+
751
+ /**
752
+ * Keeps an `anchorRect` live for as long as `active`, instead of measuring
753
+ * once at open time: a scrolled ancestor, the node's own layout moving it,
754
+ * or the owner window being repositioned by the WM or a script each call
755
+ * `setRect` again with the new rect. `getOptions` is read fresh on every
756
+ * change, so it need not be memoized. `setRect` is only called when the
757
+ * measured rect actually differs from the previous one.
758
+ *
759
+ * If the anchor node scrolls (or is laid out) entirely past a clipping
760
+ * ancestor or the owner window, `onOutOfView` is called instead of moving
761
+ * the popup there — a popup is a real window, not a web element clipped by
762
+ * its ancestors, so following a trigger that is no longer visible would
763
+ * leave it pointing at nothing. Typically `onOutOfView` closes the popup.
764
+ * With an `at` in the options that test is about the sub-rect: a caret
765
+ * leaves the viewport long before the editor around it does.
766
+ */
767
+ export function useAnchorTracking(
768
+ ref: RefObject<DrawnNode | null>,
769
+ active: boolean,
770
+ getOptions: () => AnchorOptions | null | undefined,
771
+ setRect: (
772
+ next: AnchorRect | null | ((prev: AnchorRect | null) => AnchorRect | null),
773
+ ) => void,
774
+ onOutOfView?: () => void,
775
+ ): void;
776
+
777
+ // --- drag and drop ---------------------------------------------------------
778
+
779
+ export interface UseDropTargetOptions {
780
+ /** Maps to the `dropAccept` host prop. */
781
+ accept?: DropAccept;
782
+ onDrop?: (ev: DropEvent) => void | Promise<void>;
783
+ onDragOver?: (ev: DragEvent) => void;
784
+ onDragEnter?: (ev: DragEvent) => void;
785
+ onDragLeave?: (ev: DragEvent) => void;
786
+ }
787
+
788
+ /**
789
+ * The react-dropzone-shaped convenience over the drop-target host props:
790
+ * spread `dropProps` on any drawn element or window. Only needed when the
791
+ * render itself changes with the drag — a `':drag-over'` style block
792
+ * highlights without any state.
793
+ */
794
+ export function useDropTarget(options?: UseDropTargetOptions): {
795
+ dropProps: DropTargetProps;
796
+ isOver: boolean;
797
+ isAccepted: boolean;
798
+ };
799
+
800
+ export interface UseDragSourceOptions {
801
+ /** Maps to the `dragData` host prop. */
802
+ data?: DragSourceProps['dragData'];
803
+ /** Maps to `dragActions`. */
804
+ actions?: Array<'copy' | 'move' | 'link'>;
805
+ onDragStart?: (ev: DragSourceEvent) => void;
806
+ onDrag?: (ev: DragSourceEvent) => void;
807
+ onDragEnd?: (ev: DragEndEvent) => void;
808
+ }
809
+
810
+ /**
811
+ * The source-side convenience: spread `dragProps` on the node to drag.
812
+ * `position` (screen coordinates, non-null while dragging) is what a
813
+ * `<popup dragPreview>` follows — the drag preview is a live React tree,
814
+ * not a bitmap.
815
+ */
816
+ export function useDragSource(options?: UseDragSourceOptions): {
817
+ dragProps: DragSourceProps;
818
+ isDragging: boolean;
819
+ position: { x: number; y: number; accepted: boolean } | null;
820
+ };