react-x11 2.6.1 → 2.8.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 (59) hide show
  1. package/README.md +5 -3
  2. package/package.json +10 -3
  3. package/src/activate.js +12 -0
  4. package/src/anchor.js +6 -0
  5. package/src/appearance.js +351 -28
  6. package/src/appearancehooks.js +5 -2
  7. package/src/application.js +41 -0
  8. package/src/cocoa/app.js +367 -3
  9. package/src/cocoa/bezels.js +51 -1
  10. package/src/cocoa/dnd.js +358 -0
  11. package/src/cocoa/dock.js +39 -0
  12. package/src/cocoa/filepanels.js +155 -0
  13. package/src/cocoa/fonts.js +93 -2
  14. package/src/cocoa/globalmenu.js +41 -33
  15. package/src/cocoa/notifications.js +244 -0
  16. package/src/cocoa/permissions.js +74 -0
  17. package/src/cocoa/presenter.js +274 -33
  18. package/src/cocoa/promotion.js +708 -0
  19. package/src/cocoa/statusitem.js +112 -0
  20. package/src/cocoa/window.js +113 -4
  21. package/src/components/Button.js +20 -1
  22. package/src/components/Checkbox.js +17 -2
  23. package/src/components/Menu.js +108 -38
  24. package/src/components/Radio.js +17 -2
  25. package/src/components/Select.js +159 -27
  26. package/src/components/Switch.js +8 -1
  27. package/src/components/native.js +99 -0
  28. package/src/components/theme.js +37 -20
  29. package/src/desktopsettings.js +34 -2
  30. package/src/dnd.js +137 -11
  31. package/src/errors.js +6 -3
  32. package/src/filedialog.js +81 -16
  33. package/src/index.d.ts +29 -2
  34. package/src/index.js +17 -0
  35. package/src/launcher.js +170 -0
  36. package/src/launcherhooks.js +81 -0
  37. package/src/nodes.js +604 -37
  38. package/src/notificationhooks.js +56 -0
  39. package/src/notifications.js +558 -0
  40. package/src/palette.js +144 -8
  41. package/src/permissionhooks.js +89 -0
  42. package/src/permissions.js +196 -0
  43. package/src/style.d.ts +10 -4
  44. package/src/style.js +1 -0
  45. package/src/styles.js +161 -15
  46. package/src/textselection.js +1 -4
  47. package/src/trayhooks.js +90 -0
  48. package/src/types/appearance.d.ts +24 -0
  49. package/src/types/components.d.ts +10 -0
  50. package/src/types/elements.d.ts +14 -0
  51. package/src/types/events.d.ts +14 -0
  52. package/src/types/filedialog.d.ts +18 -7
  53. package/src/types/launcher.d.ts +43 -0
  54. package/src/types/notifications.d.ts +113 -0
  55. package/src/types/permissions.d.ts +100 -0
  56. package/src/types/style.d.ts +30 -2
  57. package/src/types/system.d.ts +5 -3
  58. package/src/types/tray.d.ts +54 -0
  59. package/src/windowid.js +23 -0
@@ -0,0 +1,113 @@
1
+ /**
2
+ * Desktop notifications — a banner outside the app's own windows. See
3
+ * docs/notifications.md.
4
+ */
5
+
6
+ import type { NtkApp } from './nodes.js';
7
+
8
+ /** Which rung of the ladder answered — or would. */
9
+ export type NotificationBackend =
10
+ 'cocoa' | 'dbus' | 'osascript' | 'notify-send';
11
+
12
+ export type NotificationUrgency = 'low' | 'normal' | 'critical';
13
+
14
+ /** Why a banner went away: the freedesktop reasons. */
15
+ export type NotificationCloseReason =
16
+ 'expired' | 'dismissed' | 'closed' | 'unknown';
17
+
18
+ export interface NotificationAction {
19
+ /** Reported to `onAction`. `'default'` is the click on the banner itself
20
+ * and not a name for an action of your own. */
21
+ key: string;
22
+ label?: string;
23
+ }
24
+
25
+ export interface NotificationOptions {
26
+ /** The one line every daemon shows. Required. */
27
+ summary: string;
28
+ body?: string;
29
+ /** macOS only; folded into the body elsewhere. */
30
+ subtitle?: string;
31
+ /** An icon-theme name, or an absolute path to an image. Rungs that cannot
32
+ * show one ignore it. */
33
+ icon?: string;
34
+ urgency?: NotificationUrgency;
35
+ /** Milliseconds until it expires on its own; `0` never; absent is the
36
+ * daemon's default. */
37
+ timeout?: number;
38
+ /** Buttons on the banner. Dropped, with a development warning, on a daemon
39
+ * without the `actions` capability; never sent on the shell-out rungs. */
40
+ actions?: NotificationAction[];
41
+ onAction?: (key: string) => void;
42
+ onClose?: (reason: NotificationCloseReason) => void;
43
+ /** `true` posts without a sound on macOS. */
44
+ silent?: boolean;
45
+ /** A freedesktop category hint (`'transfer.complete'`, …). */
46
+ category?: string;
47
+ /** Keep the banner after an action is invoked (freedesktop `resident`). */
48
+ resident?: boolean;
49
+ /** The name a daemon shows for the sender; the process title otherwise. */
50
+ appName?: string;
51
+ /** The desktop-entry id the banner is attributed to; the registration's
52
+ * `appId` otherwise. */
53
+ appId?: string;
54
+ /** Opaque data round-tripped on the macOS rung. */
55
+ userInfo?: Record<string, unknown>;
56
+ /** Force a rung, for kiosks and for tests. */
57
+ backend?: NotificationBackend;
58
+ /** The connection whose centre to post through, when there are several. */
59
+ app?: NtkApp;
60
+ }
61
+
62
+ export interface NotificationHandle {
63
+ /** The daemon's id, the centre's identifier, or `null` where the rung has
64
+ * none (a shell-out). */
65
+ readonly id: number | string | null;
66
+ readonly backend: NotificationBackend;
67
+ /** Replace the banner in place. On a shell-out rung without an id this
68
+ * posts a fresh one. */
69
+ update(patch: Partial<NotificationOptions>): Promise<NotificationHandle>;
70
+ /** Take it down. Nothing on a shell-out rung. */
71
+ close(): Promise<void>;
72
+ }
73
+
74
+ /**
75
+ * Nothing on this machine can show a notification. A **typed** rejection,
76
+ * the `NoFileDialogError` rule; `useNotifier().available` is that branch as
77
+ * render state. A centre that exists and *refused* (the user turned the
78
+ * app's notifications off) rejects with the platform's own error instead —
79
+ * a refusal is not fallen through.
80
+ */
81
+ export declare class NoNotificationServiceError extends Error {
82
+ readonly name: 'NoNotificationServiceError';
83
+ readonly cause?: unknown;
84
+ }
85
+
86
+ /** Show a notification on the best rung this machine has. */
87
+ export declare function notify(
88
+ options: NotificationOptions,
89
+ ): Promise<NotificationHandle>;
90
+
91
+ /** Which rung this machine lands on, without posting anything. `null`
92
+ * means {@link notify} would reject. */
93
+ export declare function notificationBackend(
94
+ options?: Pick<NotificationOptions, 'app' | 'backend'>,
95
+ ): Promise<NotificationBackend | null>;
96
+
97
+ export interface Notifier {
98
+ notify(
99
+ options: Omit<NotificationOptions, 'app'>,
100
+ ): Promise<NotificationHandle>;
101
+ /** Settled once the ladder has been probed; false where `notify` would
102
+ * reject. */
103
+ available: boolean;
104
+ backend: NotificationBackend | null;
105
+ }
106
+
107
+ /** Notifications for a component: `notify` bound to the tree's connection,
108
+ * `available` and `backend` as render state. */
109
+ export declare function useNotifier(
110
+ defaults?: Omit<NotificationOptions, 'app' | 'summary'> & {
111
+ summary?: string;
112
+ },
113
+ ): Notifier;
@@ -0,0 +1,100 @@
1
+ /**
2
+ * Permissions: may this app use the camera, the microphone, the screen, the
3
+ * accessibility APIs — and how does it ask? See docs/permissions.md.
4
+ */
5
+
6
+ import type { NtkApp } from './nodes.js';
7
+
8
+ export type PermissionKind =
9
+ | 'camera'
10
+ | 'microphone'
11
+ | 'screen-recording'
12
+ | 'accessibility'
13
+ | 'input-monitoring'
14
+ | 'automation'
15
+ | 'location';
16
+
17
+ /** The panes `openPrivacySettings` reaches: every kind, plus the two with no
18
+ * API because reading the folder is the prompt. */
19
+ export type PrivacyPane =
20
+ PermissionKind | 'files-and-folders' | 'full-disk-access';
21
+
22
+ /**
23
+ * `'granted'`, `'denied'` and `'restricted'` (MDM or parental controls — the
24
+ * user cannot grant it) are the platform's; `'prompt'` is not decided yet, a
25
+ * request would ask; `'unknown'` is "nothing here can say", a fact about the
26
+ * machine rather than the permission.
27
+ */
28
+ export type PermissionStatus =
29
+ 'granted' | 'denied' | 'restricted' | 'prompt' | 'unknown';
30
+
31
+ export interface PermissionOptions {
32
+ /** The connection whose backend to ask, when there are several. */
33
+ app?: NtkApp;
34
+ /** `automation` only: the bundle id of the app to send Apple Events to.
35
+ * Only a running target has an answer. */
36
+ target?: string;
37
+ }
38
+
39
+ /**
40
+ * Nothing on this machine can ask. A **typed** rejection, so a caller keeps
41
+ * the feature behind it rather than crashing; `usePermission().available`
42
+ * is that branch as render state, and `permissionStatus()` foretells it
43
+ * with `'unknown'`.
44
+ */
45
+ export declare class NoPermissionServiceError extends Error {
46
+ readonly name: 'NoPermissionServiceError';
47
+ readonly cause?: unknown;
48
+ }
49
+
50
+ /** Which rung answers here: `'cocoa'`, or `null` where a status would be
51
+ * `'unknown'` and a request would reject. Synchronous. */
52
+ export declare function permissionBackend(
53
+ options?: Pick<PermissionOptions, 'app'>,
54
+ ): 'cocoa' | null;
55
+
56
+ /** Whether this app may use `kind`, without prompting. Never rejects for
57
+ * anything about the machine. */
58
+ export declare function permissionStatus(
59
+ kind: PermissionKind,
60
+ options?: PermissionOptions,
61
+ ): Promise<PermissionStatus>;
62
+
63
+ /**
64
+ * Ask for `kind` — the system's prompt where there is one — and resolve
65
+ * with the status once the user has answered. Rejects with
66
+ * {@link NoPermissionServiceError} where nothing can ask.
67
+ */
68
+ export declare function requestPermission(
69
+ kind: PermissionKind,
70
+ options?: PermissionOptions,
71
+ ): Promise<PermissionStatus>;
72
+
73
+ /**
74
+ * System Settings › Privacy & Security › `pane`, or the Privacy pane with
75
+ * none. Through the bridge where there is one, by `open` on any Mac
76
+ * otherwise. Resolves to whether anything opened — `false` off macOS.
77
+ */
78
+ export declare function openPrivacySettings(
79
+ pane?: PrivacyPane | null,
80
+ options?: Pick<PermissionOptions, 'app'>,
81
+ ): Promise<boolean>;
82
+
83
+ export interface Permission {
84
+ /** `'unknown'` until read; re-read after each `request()`. */
85
+ status: PermissionStatus;
86
+ /** Whether this backend has an authorization API at all. */
87
+ available: boolean;
88
+ /** Ask, and update `status`. A second call while one is in flight
89
+ * returns the same promise. */
90
+ request(): Promise<PermissionStatus>;
91
+ /** Re-read the status on your own cue. */
92
+ refresh(): Promise<PermissionStatus>;
93
+ openSettings(): Promise<boolean>;
94
+ }
95
+
96
+ /** A permission for a component: the status as render state. */
97
+ export declare function usePermission(
98
+ kind: PermissionKind,
99
+ options?: Pick<PermissionOptions, 'target'>,
100
+ ): Permission;
@@ -310,6 +310,16 @@ export interface StyleProperties extends LayoutStyle, PaintStyle, TextStyle {
310
310
  * a 16px control can have a 24px target without the layout moving.
311
311
  */
312
312
  hitSlop?: HitSlop;
313
+ /**
314
+ * Declares this node a container for the `'@container …'` blocks below it
315
+ * — CSS's `container-type` and `container-name` in one property. `true`
316
+ * answers the unnamed queries; a name answers those and the ones that say
317
+ * it, so `'@container sidebar width >= 400'` reaches past any nearer
318
+ * container to the one called `sidebar`. `false` takes back a declaration
319
+ * from earlier in a style array. Changes nothing about this node itself.
320
+ * See docs/styling.md#container-queries.
321
+ */
322
+ container?: boolean | string;
313
323
  }
314
324
 
315
325
  /**
@@ -361,6 +371,21 @@ export type Animation = { [K in keyof StyleProperties]?: AnimationSpec };
361
371
  */
362
372
  export type SizeQuery = `@${'width' | 'height'} ${string}`;
363
373
 
374
+ /**
375
+ * A container query — `'@container width >= 400'`, or
376
+ * `'@container sidebar width >= 400'` naming the container: what a style
377
+ * can ask about the box it is inside rather than the window it is in. The
378
+ * container is the nearest ancestor whose style declares `container` (for
379
+ * the named form, the nearest carrying that name). Like a size query it may
380
+ * set layout properties; unlike one it is answered *after* the layout pass,
381
+ * since a container's size is what the pass produces.
382
+ */
383
+ export type ContainerQuery = `@container ${string}`;
384
+
385
+ /** A capability query — `'@supports transparency'`: what a style can ask
386
+ * about the server. */
387
+ export type SupportsQuery = `@supports ${string}`;
388
+
364
389
  /** The named blocks a style may carry, beside its own properties. */
365
390
  export interface StyleBlocks {
366
391
  transition?: Transition;
@@ -401,10 +426,13 @@ export interface StyleBlocks {
401
426
 
402
427
  /**
403
428
  * A style: the properties themselves, the state blocks, and any number of
404
- * size-query blocks keyed `'@width >= 600'`.
429
+ * query blocks keyed `'@width >= 600'`, `'@container width >= 400'` or
430
+ * `'@supports transparency'`.
405
431
  */
406
432
  export type Style = StyleProperties &
407
- StyleBlocks & { [K in SizeQuery]?: StyleProperties };
433
+ StyleBlocks & {
434
+ [K in SizeQuery | ContainerQuery | SupportsQuery]?: StyleProperties;
435
+ };
408
436
 
409
437
  /**
410
438
  * What a `style` prop accepts: an object, or a nested array of them with
@@ -234,9 +234,11 @@ export interface DesktopSettings {
234
234
  readonly doubleClickDistance: number;
235
235
  /** How far a press moves before it is a drag rather than a click. */
236
236
  readonly dragThreshold: number;
237
- /** `'xsettings'`, or null where no settings daemon answered and these are
238
- * the renderer's own defaults. */
239
- readonly source: 'xsettings' | 'test' | null;
237
+ /** `'xsettings'`; `'macos'` on the Cocoa backend, where only `animations`
238
+ * comes from the system (reduce motion) and the rest are the defaults; or
239
+ * null where no settings daemon answered and these are the renderer's own
240
+ * defaults. */
241
+ readonly source: 'xsettings' | 'macos' | 'test' | null;
240
242
  }
241
243
 
242
244
  /**
@@ -0,0 +1,54 @@
1
+ /**
2
+ * The system tray: `useTray()`. See docs/desktop.md "The tray".
3
+ */
4
+
5
+ import type { MenuItem } from './components.js';
6
+
7
+ export interface TrayClickEvent {
8
+ button: 'left' | 'right' | 'middle';
9
+ /** The item's screen rect, global top-left coordinates in points — the
10
+ * anchor for a popup of your own. */
11
+ x: number;
12
+ y: number;
13
+ width: number;
14
+ height: number;
15
+ clickCount: number;
16
+ shift: boolean;
17
+ control: boolean;
18
+ option: boolean;
19
+ command: boolean;
20
+ }
21
+
22
+ export interface TrayOptions {
23
+ /** An SF Symbol name (`'bell.badge'`), or the bytes of a PNG. Drawn as a
24
+ * template image so it follows the bar's light and dark. */
25
+ icon?: string | Uint8Array | null;
26
+ /** Text beside the icon, or alone. */
27
+ title?: string | null;
28
+ tooltip?: string | null;
29
+ /** The menu a click opens — `MenuBar`'s item vocabulary. Without one,
30
+ * clicks reach `onClick`. */
31
+ menu?: MenuItem[] | null;
32
+ onClick?: (event: TrayClickEvent) => void;
33
+ visible?: boolean;
34
+ /** `false` keeps a PNG's own colours instead of drawing it as a template. */
35
+ template?: boolean;
36
+ /** `'variable'` (default) | `'square'` | a width in points. */
37
+ length?: 'variable' | 'square' | number;
38
+ /** The image's size in points, `[width, height]`. */
39
+ iconSize?: [number, number];
40
+ }
41
+
42
+ export interface TrayState {
43
+ /** Whether this backend has a tray at all — false on X11 today (#353). */
44
+ available: boolean;
45
+ /** Reserved. */
46
+ rect: null;
47
+ }
48
+
49
+ /**
50
+ * An icon in the system tray while this component is mounted; every field
51
+ * follows its value, and the item is removed on unmount. `null` means no
52
+ * item. Inert off the cocoa backend, with `available: false`.
53
+ */
54
+ export declare function useTray(options: TrayOptions | null): TrayState;
package/src/windowid.js CHANGED
@@ -39,6 +39,29 @@ export function windowIdOf(target) {
39
39
  return null;
40
40
  }
41
41
 
42
+ /**
43
+ * The window **object** behind anything `windowIdOf` accepts — ntk's on X11,
44
+ * the cocoa backend's `CocoaWindow` — or `null`. The same walk, one step
45
+ * short of the id: a `<window>` ref holds the object itself, a `<window>`
46
+ * node has it as `window`, a drawn node reaches it through its `root`. A raw
47
+ * XID resolves to nothing, because the number alone does not say which
48
+ * connection issued it.
49
+ *
50
+ * Not public. The file dialog reaches the app a window belongs to through
51
+ * it, which is how a call names the backend whose native panel it wants
52
+ * without ever naming a backend.
53
+ */
54
+ export function windowOf(target) {
55
+ if (target == null || typeof target === 'number') return null;
56
+ if (typeof target === 'object' && 'current' in target && !target.isWindow) {
57
+ return windowOf(target.current);
58
+ }
59
+ if (typeof target.window?.id === 'number') return target.window;
60
+ if (typeof target.root?.window?.id === 'number') return target.root.window;
61
+ if (typeof target.id === 'number') return target;
62
+ return null;
63
+ }
64
+
42
65
  /**
43
66
  * `windowIdOf` bound to a ref: returns a **getter**, stable across renders,
44
67
  * the same shape `useAnchor` has. It is a getter rather than the id itself