react-x11 2.51.0 → 2.52.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 (91) hide show
  1. package/README.md +87 -54
  2. package/docs/README.md +410 -0
  3. package/docs/accessibility.md +628 -0
  4. package/docs/appearance.md +416 -0
  5. package/docs/architecture/accessibility.md +470 -0
  6. package/docs/architecture/animation.md +707 -0
  7. package/docs/architecture/container-queries.md +525 -0
  8. package/docs/architecture/custom-layout.md +334 -0
  9. package/docs/architecture/drag-and-drop.md +941 -0
  10. package/docs/architecture/element-layer-contents.md +622 -0
  11. package/docs/architecture/frame-pacing.md +255 -0
  12. package/docs/architecture/grid-layout.md +358 -0
  13. package/docs/architecture/protocol-efficiency.md +357 -0
  14. package/docs/architecture/video.md +1149 -0
  15. package/docs/architecture/windowed-regions.md +854 -0
  16. package/docs/click-to-component.md +125 -0
  17. package/docs/clipboard.md +281 -0
  18. package/docs/components.md +1749 -0
  19. package/docs/dbus.md +277 -0
  20. package/docs/debugging.md +464 -0
  21. package/docs/desktop-calendar.md +284 -0
  22. package/docs/desktop.md +648 -0
  23. package/docs/devtools.md +250 -0
  24. package/docs/drag-and-drop.md +663 -0
  25. package/docs/ecosystem/animation.md +394 -0
  26. package/docs/ecosystem/data-fetching.md +201 -0
  27. package/docs/ecosystem/dev-tooling.md +436 -0
  28. package/docs/ecosystem/forms.md +313 -0
  29. package/docs/ecosystem/headless.md +219 -0
  30. package/docs/ecosystem/i18n.md +89 -0
  31. package/docs/ecosystem/icons.md +410 -0
  32. package/docs/ecosystem/layout.md +216 -0
  33. package/docs/ecosystem/routing.md +146 -0
  34. package/docs/ecosystem/state.md +361 -0
  35. package/docs/ecosystem/testing.md +500 -0
  36. package/docs/ecosystem/theming.md +540 -0
  37. package/docs/ecosystem.md +473 -0
  38. package/docs/elements.md +2762 -0
  39. package/docs/embedding.md +288 -0
  40. package/docs/events.md +1025 -0
  41. package/docs/extending.md +2242 -0
  42. package/docs/eyedropper.md +223 -0
  43. package/docs/filedialog.md +258 -0
  44. package/docs/frame.md +580 -0
  45. package/docs/gl.md +170 -0
  46. package/docs/globalmenu.md +278 -0
  47. package/docs/glx.md +137 -0
  48. package/docs/macos.md +3295 -0
  49. package/docs/notifications.md +164 -0
  50. package/docs/packaging.md +611 -0
  51. package/docs/permissions.md +148 -0
  52. package/docs/react-features.md +430 -0
  53. package/docs/remote.md +223 -0
  54. package/docs/scale.md +339 -0
  55. package/docs/security.md +132 -0
  56. package/docs/styling.md +1928 -0
  57. package/docs/system.md +599 -0
  58. package/docs/testing.md +514 -0
  59. package/docs/typescript.md +117 -0
  60. package/docs/uri-schemes.md +380 -0
  61. package/docs/wayland-backend.md +591 -0
  62. package/docs/wayland.md +545 -0
  63. package/docs/windows-embedding.md +271 -0
  64. package/docs/windows-gl.md +221 -0
  65. package/docs/windows-integrations.md +233 -0
  66. package/docs/windows.md +1404 -0
  67. package/package.json +10 -3
  68. package/src/bus.js +39 -26
  69. package/src/cli/create.d.ts +43 -0
  70. package/src/cli/create.js +305 -0
  71. package/src/cli/env.js +484 -0
  72. package/src/cli/index.js +29 -2
  73. package/src/cli/templates/AGENTS.template.md +195 -0
  74. package/src/cli/templates/DESIGN.template.md +269 -0
  75. package/src/desktopintegration.js +2 -1
  76. package/src/desktopsettings.js +193 -24
  77. package/src/filedialog.js +3 -3
  78. package/src/keyboardstate.js +28 -0
  79. package/src/node.d.ts +10 -10
  80. package/src/portal.js +31 -0
  81. package/src/screencolor.js +5 -10
  82. package/src/types/system.d.ts +8 -6
  83. package/src/wayland/app.js +59 -0
  84. package/src/wayland/clipboard.js +14 -7
  85. package/src/wayland/connection.js +3 -0
  86. package/src/wayland/context2d.js +129 -10
  87. package/src/wayland/protocols/index.json +6 -0
  88. package/src/wayland/protocols/xdg-foreign-unstable-v2.json +1 -0
  89. package/src/wayland/seat.js +28 -1
  90. package/src/wayland/swapchain.js +23 -0
  91. package/src/wayland/xkb.js +137 -0
@@ -0,0 +1,284 @@
1
+ # The desktop's calendar
2
+
3
+ The user's real events — iCloud, Google, Microsoft, CalDAV, a subscribed
4
+ feed, the local one — without asking for a single credential and without an
5
+ OAuth flow, because the desktop already did all of that. Every account in
6
+ System Settings › Internet Accounts, every account in GNOME's Online
7
+ Accounts, is in a store this can read.
8
+
9
+ ```jsx
10
+ import { useDesktopCalendarEvents } from 'react-x11';
11
+
12
+ function Month({ from, to }) {
13
+ const { byDay, status, openSettings } = useDesktopCalendarEvents({
14
+ from,
15
+ to,
16
+ watch: true,
17
+ });
18
+
19
+ if (status === 'unavailable') return null; // no calendar on this machine
20
+ if (status === 'denied')
21
+ return <Button label="Open Settings" onPress={openSettings} />;
22
+
23
+ return (
24
+ <Calendar
25
+ dayContent={(day) =>
26
+ byDay
27
+ .get(day)
28
+ ?.slice(0, 3)
29
+ .map((ev) => (
30
+ <box
31
+ key={ev.uid}
32
+ style={{
33
+ width: 4,
34
+ height: 4,
35
+ borderRadius: 2,
36
+ backgroundColor: ev.calendar.color ?? '$accent',
37
+ }}
38
+ />
39
+ ))
40
+ }
41
+ />
42
+ );
43
+ }
44
+ ```
45
+
46
+ `<Calendar>` is the grid from
47
+ [`@react-x11/components`](ecosystem.md); the `'YYYY-MM-DD'` keys are the one
48
+ contract between the two packages, which is why `byDay` is here and the grid
49
+ is there. Nothing here draws anything.
50
+
51
+ ## The ladder
52
+
53
+ The [file dialog](filedialog.md)'s shape again, chosen in order and never by
54
+ naming a backend:
55
+
56
+ | | | |
57
+ | --- | -------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
58
+ | 1 | **EventKit through the bridge** | The [cocoa backend](macos.md), where the app the tree renders through carries `calendars` (`@windowkit/appkit` >= 0.8). One `EKEventStore` for every account, recurrences expanded by the framework, and one notification for any change. |
59
+ | 2 | **EventKit through `osascript`** | A Mac with no bridge — every XQuartz install, and a cocoa app over an older bridge. A long-lived `osascript -l JavaScript` child holds the same store and answers JSON lines. Same framework, slower transport. |
60
+ | 3 | **EDS over the session bus** | `org.gnome.evolution.dataserver.Sources5` / `Calendar8` — the store every GNOME calendar UI reads, and the one GNOME Online Accounts feeds. Recurrences are expanded here, with `ical.js`. |
61
+ | 4 | **nothing** | `desktopCalendar()` answers `null` and the hook says `'unavailable'`. An ordinary answer about a machine, not a failure. |
62
+
63
+ ```ts
64
+ await calendarBackend(); // 'cocoa' | 'osascript' | 'eds' | null
65
+ ```
66
+
67
+ `calendarBackend()` reads nothing, spawns nothing and prompts nothing, so it
68
+ is safe on a settings screen that wants to say where the events come from.
69
+
70
+ **Rung 2 outranks rung 3 on a Mac**, which is deliberate: EventKit is that
71
+ machine's real store, and an Evolution Data Server installed beside it would
72
+ be a second, emptier one.
73
+
74
+ ## The imperative half
75
+
76
+ ```ts
77
+ import { desktopCalendar } from 'react-x11';
78
+
79
+ const cal = await desktopCalendar(); // DesktopCalendar | null, never rejects
80
+ if (!cal) return; // nothing on this machine
81
+ try {
82
+ const calendars = await cal.listCalendars();
83
+ const { events, errors } = await cal.eventsBetween(from, to, { calendars });
84
+ const stop = await cal.watch(from, to, () => reload());
85
+ } finally {
86
+ await cal.close();
87
+ }
88
+ ```
89
+
90
+ `close()` matters on every rung: it stops the views EDS handed out, drops
91
+ this handle's share of the `osascript` child, and releases the bus reference.
92
+ `useDesktopCalendarEvents` does it for you.
93
+
94
+ `{ required: true }` turns the `null` into a typed `NoCalendarServiceError`
95
+ for a caller who would rather branch in a `catch`, and `{ backend }` pins one
96
+ rung and fails rather than falling past it — which is what a test and a "why
97
+ is this slow" investigation both want.
98
+
99
+ ## One shape, whichever rung answered
100
+
101
+ ```ts
102
+ interface DesktopCalendarInfo {
103
+ uid: string;
104
+ name: string;
105
+ enabled: boolean; // always true on macOS: no such state to report
106
+ color?: string; // '#3584e4'
107
+ backend?: string; // 'caldav' | 'local' | 'exchange' | 'subscription' | …
108
+ readOnly: boolean;
109
+ account?: string; // the Online Accounts / EKSource entry it came from
110
+ }
111
+
112
+ interface DesktopEvent {
113
+ uid: string;
114
+ summary: string;
115
+ location?: string;
116
+ description?: string;
117
+ start: Date;
118
+ end: Date; // exclusive
119
+ allDay: boolean;
120
+ recurring: boolean;
121
+ calendar: { uid: string; name: string; color?: string };
122
+ }
123
+ ```
124
+
125
+ Three things the rungs had to agree on, and they are the whole of why this is
126
+ one module rather than three:
127
+
128
+ - **`end` is exclusive.** EventKit reports an all-day event as ending at
129
+ 23:59:59 of its last day; iCalendar (so EDS) ends it at the next midnight.
130
+ An app cannot branch on which store it happens to be reading, so the macOS
131
+ rungs normalise on the way out and `byDay` may assume it.
132
+ - **A change is "something moved", not a patch.** EDS names the objects that
133
+ changed and EventKit's notification names nothing at all — and neither can
134
+ say what a recurrence master edited three months away did to the range on
135
+ screen. So `watch` reports a change and the only correct answer is to query
136
+ again.
137
+ - **"Not allowed to look" is not "no events".** Without the grant EventKit
138
+ answers an empty list, which is a lie an app would draw.
139
+
140
+ ## `byDay`
141
+
142
+ ```ts
143
+ const days = byDay(events); // Map<'YYYY-MM-DD', DesktopEvent[]>
144
+ days.get(dayKey(new Date())); // today's
145
+ ```
146
+
147
+ Keyed by **local** day, because a grid draws the user's days. An event lands
148
+ under every day it touches, not only under its start — an all-day span
149
+ crosses several and a timed one can cross midnight. The hook returns the same
150
+ map as `byDay` on its result, already memoised.
151
+
152
+ ## The grant
153
+
154
+ Only the macOS rungs have one; EDS is reachable or it is not.
155
+
156
+ ```ts
157
+ await cal.access(); // the permission vocabulary; never prompts
158
+ await cal.requestAccess(); // the system's prompt, once
159
+ await cal.openSettings(); // System Settings › Privacy & Security › Calendars
160
+ ```
161
+
162
+ **The first read asks.** `listCalendars()` and `eventsBetween()` raise the
163
+ prompt when the status is `'prompt'`, the way the notification centre's first
164
+ post asks — so a picker the user never opens never prompts, and
165
+ `enabled: false` on the hook is how a button defers it. A refusal is a typed
166
+ `CalendarAccessError` carrying the status, never an empty list.
167
+
168
+ `'denied'` is the **user's** answer and `'unavailable'` is the **machine's**.
169
+ They are separate words because only one of them has a switch behind it: show
170
+ an Open Settings button on `'denied'` and on nothing else
171
+ ([permissions.md](permissions.md) has the same rule for `'unknown'`).
172
+
173
+ `'write-only'` — macOS 14's partial grant — is a refusal to a reader, so it
174
+ rejects like a denial, but by its own name: an app that also saves events can
175
+ tell the two apart.
176
+
177
+ ### Attribution, and what raises the prompt
178
+
179
+ **The grant does not belong to `osascript`.** It belongs to whatever owns the
180
+ process tree. Measured on macOS 15.2 by raising the prompt for real through
181
+ the second rung and reading `tccd`'s own record of it
182
+ (`log show --predicate 'subsystem == "com.apple.TCC"'`), the attribution is
183
+ three processes, not one:
184
+
185
+ ```
186
+ AUTHREQ_ATTRIBUTION: service=kTCCServiceCalendar
187
+ responsible = the app that owns the tree (Terminal, iTerm, the IDE — here
188
+ com.anthropic.claude-code)
189
+ accessing = com.apple.osascript (/usr/bin/osascript — the child)
190
+ requesting = com.apple.calaccessd (CalendarDaemon, doing the check)
191
+ ```
192
+
193
+ `responsible` is what TCC keys the decision on and what the dialog names, so:
194
+
195
+ - The second rung's grant is **shared with everything that terminal or IDE
196
+ runs** — not with every `osascript` on the Mac, and not with this app
197
+ alone. Two react-x11 apps started from the same terminal share one answer;
198
+ the same app launched from Finder asks again.
199
+ - A bare `node` on the bridge rung lands in the same place: attributed to its
200
+ responsible process, or to `node` itself where it has none.
201
+ - **A bundled app is its own responsible process**, which is the only way to
202
+ get a prompt that names the app and a grant that is the app's — and it must
203
+ carry `NSCalendarsFullAccessUsageDescription` (plus
204
+ `NSCalendarsWriteOnlyAccessUsageDescription` where it asks for the narrower
205
+ grant) or the request never prompts. See [packaging.md](packaging.md).
206
+
207
+ Also measured on macOS 15.2, with the grant undecided:
208
+
209
+ - `EKEventStore.authorizationStatusForEntityType` answers from an unbundled
210
+ process with **no prompt** — a status read is always safe.
211
+ - Creating the store never prompts either; only a request does.
212
+ - Without the grant, `calendarsForEntityType` answers an **empty array** and
213
+ `eventsMatchingPredicate` an empty one — no error. That is the whole reason
214
+ the ladder checks the status first rather than reporting "no events".
215
+ - The `osascript` child starts and answers its first line in ~130 ms.
216
+
217
+ ## `useDesktopCalendarEvents()`
218
+
219
+ ```ts
220
+ const {
221
+ events,
222
+ byDay,
223
+ calendars,
224
+ errors,
225
+ status,
226
+ backend,
227
+ error,
228
+ refresh,
229
+ openSettings,
230
+ } = useDesktopCalendarEvents({ from, to, calendars?, watch?, enabled? });
231
+ ```
232
+
233
+ | `status` | |
234
+ | --------------- | ---------------------------------------------------------------------------------------------------------------------- |
235
+ | `'idle'` | `enabled: false` — nothing has been asked, including the grant |
236
+ | `'loading'` | a read is in flight; the first one may be showing the system's prompt |
237
+ | `'ready'` | `events` is this range |
238
+ | `'denied'` | the user's answer. `openSettings()` is the way back |
239
+ | `'unavailable'` | the machine's answer: no rung — or a request TCC would not even ask (above). Hide the feature rather than reporting it |
240
+
241
+ `errors` is none of those: calendars that would not answer while others did.
242
+ One unreachable CalDAV server must not blank a whole month of the user's own
243
+ local calendar, so it is reported beside the events rather than instead of
244
+ them. It is always empty on the macOS rungs, where there is one store.
245
+
246
+ **`from` and `to` are read by their timestamps, not their identity**, so
247
+ `from={new Date(y, m, 1)}` inline in the render body is fine and does not
248
+ re-query every paint. That is the mistake this hook is shaped to survive.
249
+
250
+ ## What differs between the rungs
251
+
252
+ - **Recurrences.** EventKit expands them; EDS answers with unexpanded masters
253
+ and `ical.js` expands them here. A malformed `RRULE` with no `UNTIL` is
254
+ guarded at 2000 occurrences on that rung.
255
+ - **`enabled`.** EDS reports whether the user switched a calendar off;
256
+ EventKit has no such state — a calendar unchecked in Calendar.app is still
257
+ in the store — so it is always `true` there.
258
+ - **The change signal.** EDS names the calendar, the kind (`ObjectsAdded`,
259
+ `ObjectsModified`, `ObjectsRemoved`) and a count; EventKit's is one
260
+ `'changed'` with a null calendar and no count, because its notification
261
+ names nothing and a rung must not invent one.
262
+ - **The range.** EventKit's predicate spans at most four years, so a longer
263
+ one is asked for in pieces and merged (an occurrence on a seam is reported
264
+ once). EDS takes any range.
265
+ - **What the app is on the bus.** The EDS rung borrows react-x11's shared
266
+ session bus ([dbus.md](dbus.md)), so an app is one name on the bus however
267
+ many features it turns on.
268
+
269
+ ## `ical.js`
270
+
271
+ A regular dependency, loaded behind one lazy `import('ical.js')` with a
272
+ static specifier: it is 268 KB of a 1.2 MB package, has no dependencies of
273
+ its own, no native code and no install script (MPL-2.0, file-level copyleft).
274
+ The `import()` keeps it off the startup path — an app that never opens a
275
+ calendar never parses it — while a static specifier is something a bundler
276
+ or a single-executable build can still follow, which the run-time-built
277
+ specifier an optional dependency needs is not. Neither macOS rung loads it.
278
+
279
+ ## Writing
280
+
281
+ Not yet — [#504](https://github.com/sidorares/react-x11/issues/504)'s third
282
+ milestone. The bridge has `saveEvent`/`removeEvent` with the recurrence span
283
+ (`@windowkit/appkit` >= 0.8) and EDS has `CreateObjects`/`ModifyObjects`/
284
+ `RemoveObjects`, so the shape is known; what is here reads.