@react-x11/components 0.3.0 → 0.4.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 (107) hide show
  1. package/README.md +6 -0
  2. package/dist/index.d.ts +4 -0
  3. package/dist/index.d.ts.map +1 -1
  4. package/dist/index.js +5 -0
  5. package/dist/index.js.map +1 -1
  6. package/dist/qml/containers.d.ts +3 -0
  7. package/dist/qml/containers.d.ts.map +1 -0
  8. package/dist/qml/containers.js +331 -0
  9. package/dist/qml/containers.js.map +1 -0
  10. package/dist/qml/controls.d.ts +12 -0
  11. package/dist/qml/controls.d.ts.map +1 -0
  12. package/dist/qml/controls.js +21 -0
  13. package/dist/qml/controls.js.map +1 -0
  14. package/dist/qml/globals.d.ts +6 -0
  15. package/dist/qml/globals.d.ts.map +1 -0
  16. package/dist/qml/globals.js +25 -0
  17. package/dist/qml/globals.js.map +1 -0
  18. package/dist/qml/index.d.ts +15 -0
  19. package/dist/qml/index.d.ts.map +1 -0
  20. package/dist/qml/index.js +25 -0
  21. package/dist/qml/index.js.map +1 -0
  22. package/dist/qml/interaction.d.ts +12 -0
  23. package/dist/qml/interaction.d.ts.map +1 -0
  24. package/dist/qml/interaction.js +246 -0
  25. package/dist/qml/interaction.js.map +1 -0
  26. package/dist/qml/ir.d.ts +106 -0
  27. package/dist/qml/ir.d.ts.map +1 -0
  28. package/dist/qml/ir.js +7 -0
  29. package/dist/qml/ir.js.map +1 -0
  30. package/dist/qml/layouts.d.ts +3 -0
  31. package/dist/qml/layouts.d.ts.map +1 -0
  32. package/dist/qml/layouts.js +122 -0
  33. package/dist/qml/layouts.js.map +1 -0
  34. package/dist/qml/models.d.ts +14 -0
  35. package/dist/qml/models.d.ts.map +1 -0
  36. package/dist/qml/models.js +80 -0
  37. package/dist/qml/models.js.map +1 -0
  38. package/dist/qml/objects.d.ts +228 -0
  39. package/dist/qml/objects.d.ts.map +1 -0
  40. package/dist/qml/objects.js +1103 -0
  41. package/dist/qml/objects.js.map +1 -0
  42. package/dist/qml/parse.d.ts +10 -0
  43. package/dist/qml/parse.d.ts.map +1 -0
  44. package/dist/qml/parse.js +607 -0
  45. package/dist/qml/parse.js.map +1 -0
  46. package/dist/qml/qtquick.d.ts +7 -0
  47. package/dist/qml/qtquick.d.ts.map +1 -0
  48. package/dist/qml/qtquick.js +347 -0
  49. package/dist/qml/qtquick.js.map +1 -0
  50. package/dist/qml/react.d.ts +83 -0
  51. package/dist/qml/react.d.ts.map +1 -0
  52. package/dist/qml/react.js +323 -0
  53. package/dist/qml/react.js.map +1 -0
  54. package/dist/qml/resolver.d.ts +38 -0
  55. package/dist/qml/resolver.d.ts.map +1 -0
  56. package/dist/qml/resolver.js +64 -0
  57. package/dist/qml/resolver.js.map +1 -0
  58. package/dist/qml/slots.d.ts +65 -0
  59. package/dist/qml/slots.d.ts.map +1 -0
  60. package/dist/qml/slots.js +230 -0
  61. package/dist/qml/slots.js.map +1 -0
  62. package/dist/qml/states.d.ts +5 -0
  63. package/dist/qml/states.d.ts.map +1 -0
  64. package/dist/qml/states.js +276 -0
  65. package/dist/qml/states.js.map +1 -0
  66. package/dist/qml/view-utils.d.ts +42 -0
  67. package/dist/qml/view-utils.d.ts.map +1 -0
  68. package/dist/qml/view-utils.js +121 -0
  69. package/dist/qml/view-utils.js.map +1 -0
  70. package/dist/tabs/hx.d.ts +18 -0
  71. package/dist/tabs/hx.d.ts.map +1 -0
  72. package/dist/tabs/hx.js +10 -0
  73. package/dist/tabs/hx.js.map +1 -0
  74. package/dist/tabs/index.d.ts +143 -0
  75. package/dist/tabs/index.d.ts.map +1 -0
  76. package/dist/tabs/index.js +602 -0
  77. package/dist/tabs/index.js.map +1 -0
  78. package/dist/terminal/vt/index.d.ts +1 -1
  79. package/dist/terminal/vt/index.d.ts.map +1 -1
  80. package/dist/terminal/vt/index.js +3 -3
  81. package/dist/terminal/vt/index.js.map +1 -1
  82. package/dist/terminal/vt/pty.d.ts +16 -0
  83. package/dist/terminal/vt/pty.d.ts.map +1 -1
  84. package/dist/terminal/vt/pty.js +186 -0
  85. package/dist/terminal/vt/pty.js.map +1 -1
  86. package/package.json +11 -1
  87. package/src/index.ts +44 -0
  88. package/src/qml/containers.tsx +380 -0
  89. package/src/qml/controls.tsx +47 -0
  90. package/src/qml/globals.ts +33 -0
  91. package/src/qml/index.ts +67 -0
  92. package/src/qml/interaction.tsx +317 -0
  93. package/src/qml/ir.ts +80 -0
  94. package/src/qml/layouts.tsx +141 -0
  95. package/src/qml/models.ts +101 -0
  96. package/src/qml/objects.ts +1431 -0
  97. package/src/qml/parse.ts +624 -0
  98. package/src/qml/qtquick.tsx +413 -0
  99. package/src/qml/react.tsx +454 -0
  100. package/src/qml/resolver.ts +99 -0
  101. package/src/qml/slots.ts +249 -0
  102. package/src/qml/states.ts +318 -0
  103. package/src/qml/view-utils.ts +140 -0
  104. package/src/tabs/hx.ts +31 -0
  105. package/src/tabs/index.ts +850 -0
  106. package/src/terminal/vt/index.ts +4 -2
  107. package/src/terminal/vt/pty.ts +243 -0
@@ -33,7 +33,7 @@ import type { EmbedStatus, ExitInfo } from '../../embed/index.js';
33
33
  import type { TerminalColors } from '../backends.js';
34
34
  import { ELEMENT, VtTermNode } from './node.js';
35
35
  import type { VtTermProps } from './node.js';
36
- import { PtyUnavailableError, nodePtyHost } from './pty.js';
36
+ import { PtyUnavailableError, defaultPtyHost } from './pty.js';
37
37
  import type { PtyHost, PtySession } from './pty.js';
38
38
  import { startTimeout, stopTimeout } from '../../embed/timers.js';
39
39
  import type { TimerId } from '../../embed/timers.js';
@@ -45,6 +45,8 @@ export type { VtTermProps };
45
45
  export {
46
46
  PTY_MODULES,
47
47
  PtyUnavailableError,
48
+ bunPtyHost,
49
+ defaultPtyHost,
48
50
  defaultShell,
49
51
  nodePtyHost,
50
52
  ptyLoadError,
@@ -203,7 +205,7 @@ export function VtTerminal(props: VtTerminalProps): ReactElement {
203
205
  );
204
206
  return;
205
207
  }
206
- const host = handlers.current.pty ?? nodePtyHost();
208
+ const host = handlers.current.pty ?? defaultPtyHost();
207
209
  if (!(await host.available())) {
208
210
  // The message separates "nothing installed" from "installed but it
209
211
  // would not load" — a native module built for another Node ABI looks
@@ -332,3 +332,246 @@ export function nodePtyHost(): PtyHost {
332
332
  sharedHost = host;
333
333
  return host;
334
334
  }
335
+
336
+ // --- the Bun implementation ------------------------------------------------
337
+
338
+ /**
339
+ * Bun's built-in pty, as of Bun 1.4: `Bun.spawn(argv, { terminal })` hands
340
+ * back a `Terminal` on the subprocess.
341
+ *
342
+ * This exists for the reason the whole file exists in the first place — a pty
343
+ * should not cost 64 MB and a native build. Under Bun it costs nothing: the
344
+ * pty is the runtime's, so `node-pty` is not installed, not probed, and not
345
+ * loaded, and the vt backend works out of the box on a machine with no C
346
+ * toolchain. Same argument as `@xterm/headless` being lazy: an app should not
347
+ * pay for what its runtime already provides.
348
+ *
349
+ * Written structurally, like `NodePty` above and for the same two reasons:
350
+ * `src/` may not name another runtime's types, and `@types/bun` is not
351
+ * installed here (nor in a Node app that merely imports this package).
352
+ */
353
+ interface BunTerminalHandle {
354
+ write(data: string | Uint8Array): void;
355
+ resize(cols: number, rows: number): void;
356
+ close(): void;
357
+ }
358
+
359
+ interface BunSubprocess {
360
+ readonly pid: number;
361
+ readonly exitCode: number | null;
362
+ readonly signalCode: string | null;
363
+ readonly exited: Promise<number>;
364
+ readonly terminal?: BunTerminalHandle | null;
365
+ kill(signal?: string | number): void;
366
+ }
367
+
368
+ interface BunRuntime {
369
+ /** The pty class. Present from 1.4 — this is the feature detector. */
370
+ Terminal?: unknown;
371
+ spawn(
372
+ argv: readonly string[],
373
+ options: {
374
+ cwd?: string;
375
+ env?: Record<string, string | undefined>;
376
+ terminal?: {
377
+ cols?: number;
378
+ rows?: number;
379
+ data?(terminal: BunTerminalHandle, chunk: Uint8Array): void;
380
+ };
381
+ },
382
+ ): BunSubprocess;
383
+ }
384
+
385
+ /**
386
+ * The Bun runtime, if this is Bun *and* its Bun is new enough to have a pty.
387
+ *
388
+ * `Bun.Terminal` rather than a parse of `Bun.version`: the `terminal` option
389
+ * is ignored rather than rejected by a Bun that predates it, so a version
390
+ * comparison is the difference between falling through to node-pty and
391
+ * spawning a child whose output goes nowhere.
392
+ */
393
+ function bunRuntime(): BunRuntime | null {
394
+ const bun = (globalThis as { Bun?: BunRuntime }).Bun;
395
+ if (!bun || typeof bun.spawn !== 'function') return null;
396
+ return typeof bun.Terminal === 'function' ? bun : null;
397
+ }
398
+
399
+ class BunPtySession implements PtySession {
400
+ #proc: BunSubprocess;
401
+ #term: BunTerminalHandle;
402
+ #alive = true;
403
+ // Bun takes the output callback as a *spawn option*, so bytes can land
404
+ // before `openPty` has even returned — let alone before the caller has
405
+ // called `onData`. Anything that arrives in that window is held here and
406
+ // flushed on attach; without it the shell's first prompt is a coin flip.
407
+ #backlog: Uint8Array[] = [];
408
+ #data: ((chunk: Uint8Array) => void) | null = null;
409
+ // Same race, one step later: a program that exits immediately settles
410
+ // `exited` before `onExit` is attached.
411
+ #exit: ExitInfo | null = null;
412
+ #onExit: ((info: ExitInfo) => void) | null = null;
413
+
414
+ constructor(proc: BunSubprocess, term: BunTerminalHandle) {
415
+ this.#proc = proc;
416
+ this.#term = term;
417
+ void proc.exited.then(() => {
418
+ this.#alive = false;
419
+ // Bun reports the signal by *name* (`'SIGTERM'`) and nulls `exitCode`
420
+ // when one ended the child — which is `ExitInfo` exactly, so unlike the
421
+ // node-pty path there is no number to stringify and no name table to
422
+ // get wrong per platform.
423
+ const info: ExitInfo = {
424
+ code: proc.signalCode ? null : (proc.exitCode ?? 0),
425
+ signal: proc.signalCode ?? null,
426
+ };
427
+ if (this.#onExit) this.#onExit(info);
428
+ else this.#exit = info;
429
+ });
430
+ }
431
+
432
+ /** Called from the spawn-time `data` callback. */
433
+ receive(chunk: Uint8Array): void {
434
+ if (this.#data) this.#data(chunk);
435
+ else this.#backlog.push(chunk);
436
+ }
437
+
438
+ get pid(): number | null {
439
+ return this.#alive ? this.#proc.pid : null;
440
+ }
441
+
442
+ write(data: string): void {
443
+ if (!this.#alive) return;
444
+ try {
445
+ this.#term.write(data);
446
+ } catch {
447
+ // the child exited between the keystroke and this write
448
+ }
449
+ }
450
+
451
+ resize(cols: number, rows: number): void {
452
+ if (!this.#alive || cols <= 0 || rows <= 0) return;
453
+ try {
454
+ this.#term.resize(cols, rows);
455
+ } catch {
456
+ // same race as write
457
+ }
458
+ }
459
+
460
+ kill(signal = 'SIGTERM'): boolean {
461
+ if (!this.#alive) return false;
462
+ try {
463
+ this.#proc.kill(signal);
464
+ return true;
465
+ } catch {
466
+ return false;
467
+ }
468
+ }
469
+
470
+ onData(listener: (chunk: Uint8Array) => void): void {
471
+ this.#data = listener;
472
+ // Bytes, not a string: Bun hands over a `Buffer`, and the emulator's
473
+ // decoder is stateful across chunks. Decoding here would split a
474
+ // multi-byte character on whatever boundary the pty read returned.
475
+ const held = this.#backlog;
476
+ this.#backlog = [];
477
+ for (const chunk of held) listener(chunk);
478
+ }
479
+
480
+ onExit(listener: (info: ExitInfo) => void): void {
481
+ this.#onExit = listener;
482
+ if (this.#exit) {
483
+ const info = this.#exit;
484
+ this.#exit = null;
485
+ listener(info);
486
+ }
487
+ }
488
+
489
+ // No `pause`/`resume`: Bun's terminal has no flow control to expose, and
490
+ // the interface makes them optional for exactly this case. The emulator's
491
+ // own write queue still bounds parse work per tick; only the pipe's
492
+ // buffered bytes grow.
493
+ }
494
+
495
+ let sharedBunHost: PtyHost | null = null;
496
+
497
+ /**
498
+ * The runtime's own pty. Null-free: callers reach it through
499
+ * `defaultPtyHost()`, and `available()` answers honestly under Node.
500
+ */
501
+ export function bunPtyHost(): PtyHost {
502
+ if (sharedBunHost) return sharedBunHost;
503
+
504
+ const host: PtyHost = {
505
+ environment() {
506
+ return nodeProcess()?.env ?? {};
507
+ },
508
+
509
+ async available() {
510
+ return bunRuntime() !== null;
511
+ },
512
+
513
+ async openPty(argv, options) {
514
+ const bun = bunRuntime();
515
+ if (!bun) throw new PtyUnavailableError();
516
+ const ambient = host.environment?.() ?? {};
517
+ const merged: Record<string, string> = {};
518
+ for (const [key, value] of Object.entries({
519
+ ...ambient,
520
+ ...options.env,
521
+ })) {
522
+ // An explicit `undefined` removes a variable, as `SpawnOptions` says
523
+ if (value !== undefined) merged[key] = String(value);
524
+ }
525
+ // Bun sets no `TERM` of its own — the pty is a device, not a profile —
526
+ // so the honest advertisement is made here, as the node-pty path makes
527
+ // it through node-pty's `name`.
528
+ merged.TERM ??= 'xterm-256color';
529
+ const file = argv[0] ?? defaultShell(ambient);
530
+ let session: BunPtySession | null = null;
531
+ const proc = bun.spawn([file, ...argv.slice(1)], {
532
+ cwd: options.cwd,
533
+ env: merged,
534
+ terminal: {
535
+ cols: options.cols,
536
+ rows: options.rows,
537
+ data(_terminal, chunk) {
538
+ session?.receive(chunk);
539
+ },
540
+ },
541
+ });
542
+ const term = proc.terminal;
543
+ if (!term) {
544
+ // A Bun that has `Terminal` but did not give us one back: kill the
545
+ // child rather than leak it, and report it as a load failure so the
546
+ // caller renders `fallback` instead of an empty terminal.
547
+ try {
548
+ proc.kill();
549
+ } catch {
550
+ // already gone
551
+ }
552
+ throw new PtyUnavailableError(
553
+ new Error('Bun.spawn returned no terminal for a pty request'),
554
+ );
555
+ }
556
+ session = new BunPtySession(proc, term);
557
+ return session;
558
+ },
559
+ };
560
+
561
+ sharedBunHost = host;
562
+ return host;
563
+ }
564
+
565
+ /**
566
+ * The pty the current runtime should use: Bun's own where there is one,
567
+ * `node-pty` otherwise.
568
+ *
569
+ * Bun wins on purpose even when node-pty is installed and would load (Bun's
570
+ * N-API support is good enough that it does). The built-in needs no native
571
+ * build, no ABI match and no 64 MB, and "use what the runtime provides" is
572
+ * the same call this package makes everywhere else. An app that wants the
573
+ * other one back passes `pty={nodePtyHost()}`, which is what the seam is for.
574
+ */
575
+ export function defaultPtyHost(): PtyHost {
576
+ return bunRuntime() ? bunPtyHost() : nodePtyHost();
577
+ }