react-x11 2.51.1 → 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.
- package/README.md +87 -54
- package/docs/README.md +410 -0
- package/docs/accessibility.md +628 -0
- package/docs/appearance.md +416 -0
- package/docs/architecture/accessibility.md +470 -0
- package/docs/architecture/animation.md +707 -0
- package/docs/architecture/container-queries.md +525 -0
- package/docs/architecture/custom-layout.md +334 -0
- package/docs/architecture/drag-and-drop.md +941 -0
- package/docs/architecture/element-layer-contents.md +622 -0
- package/docs/architecture/frame-pacing.md +255 -0
- package/docs/architecture/grid-layout.md +358 -0
- package/docs/architecture/protocol-efficiency.md +357 -0
- package/docs/architecture/video.md +1149 -0
- package/docs/architecture/windowed-regions.md +854 -0
- package/docs/click-to-component.md +125 -0
- package/docs/clipboard.md +281 -0
- package/docs/components.md +1749 -0
- package/docs/dbus.md +277 -0
- package/docs/debugging.md +464 -0
- package/docs/desktop-calendar.md +284 -0
- package/docs/desktop.md +648 -0
- package/docs/devtools.md +250 -0
- package/docs/drag-and-drop.md +663 -0
- package/docs/ecosystem/animation.md +394 -0
- package/docs/ecosystem/data-fetching.md +201 -0
- package/docs/ecosystem/dev-tooling.md +436 -0
- package/docs/ecosystem/forms.md +313 -0
- package/docs/ecosystem/headless.md +219 -0
- package/docs/ecosystem/i18n.md +89 -0
- package/docs/ecosystem/icons.md +410 -0
- package/docs/ecosystem/layout.md +216 -0
- package/docs/ecosystem/routing.md +146 -0
- package/docs/ecosystem/state.md +361 -0
- package/docs/ecosystem/testing.md +500 -0
- package/docs/ecosystem/theming.md +540 -0
- package/docs/ecosystem.md +473 -0
- package/docs/elements.md +2762 -0
- package/docs/embedding.md +288 -0
- package/docs/events.md +1025 -0
- package/docs/extending.md +2242 -0
- package/docs/eyedropper.md +223 -0
- package/docs/filedialog.md +258 -0
- package/docs/frame.md +580 -0
- package/docs/gl.md +170 -0
- package/docs/globalmenu.md +278 -0
- package/docs/glx.md +137 -0
- package/docs/macos.md +3295 -0
- package/docs/notifications.md +164 -0
- package/docs/packaging.md +611 -0
- package/docs/permissions.md +148 -0
- package/docs/react-features.md +430 -0
- package/docs/remote.md +223 -0
- package/docs/scale.md +339 -0
- package/docs/security.md +132 -0
- package/docs/styling.md +1928 -0
- package/docs/system.md +599 -0
- package/docs/testing.md +514 -0
- package/docs/typescript.md +117 -0
- package/docs/uri-schemes.md +380 -0
- package/docs/wayland-backend.md +591 -0
- package/docs/wayland.md +545 -0
- package/docs/windows-embedding.md +271 -0
- package/docs/windows-gl.md +221 -0
- package/docs/windows-integrations.md +233 -0
- package/docs/windows.md +1404 -0
- package/package.json +9 -2
- package/src/bus.js +39 -26
- package/src/cli/create.d.ts +43 -0
- package/src/cli/create.js +305 -0
- package/src/cli/env.js +484 -0
- package/src/cli/index.js +29 -2
- package/src/cli/templates/AGENTS.template.md +195 -0
- package/src/cli/templates/DESIGN.template.md +269 -0
- package/src/desktopintegration.js +2 -1
- package/src/desktopsettings.js +193 -24
- package/src/filedialog.js +3 -3
- package/src/keyboardstate.js +28 -0
- package/src/node.d.ts +10 -10
- package/src/portal.js +31 -0
- package/src/screencolor.js +5 -10
- package/src/types/system.d.ts +8 -6
- package/src/wayland/app.js +59 -0
- package/src/wayland/clipboard.js +14 -7
- package/src/wayland/connection.js +3 -0
- package/src/wayland/context2d.js +129 -10
- package/src/wayland/protocols/index.json +6 -0
- package/src/wayland/protocols/xdg-foreign-unstable-v2.json +1 -0
- package/src/wayland/seat.js +28 -1
- package/src/wayland/swapchain.js +23 -0
- 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.
|