silvery 0.19.2 → 0.21.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 (218) hide show
  1. package/README.md +9 -4
  2. package/dist/Text-Lq0dmj8-.mjs +239 -0
  3. package/dist/Text-Lq0dmj8-.mjs.map +1 -0
  4. package/dist/UPNG-Bo33r8rA.mjs +3 -0
  5. package/dist/UPNG-DosRPdF4.mjs +5075 -0
  6. package/dist/UPNG-DosRPdF4.mjs.map +1 -0
  7. package/dist/__vite-browser-external-2447137e-D_JM6skp.mjs +6 -0
  8. package/dist/__vite-browser-external-2447137e-D_JM6skp.mjs.map +1 -0
  9. package/dist/{animation-Cn64yepo.mjs → animation-ZMN2_XKv.mjs} +2 -2
  10. package/dist/animation-ZMN2_XKv.mjs.map +1 -0
  11. package/dist/{ansi-Cc33mW54.d.mts → ansi-2Xn0yatP.d.mts} +1 -1
  12. package/dist/{ansi-Cc33mW54.d.mts.map → ansi-2Xn0yatP.d.mts.map} +1 -1
  13. package/dist/{ansi-CLOitHKx.mjs → ansi-D1KQMAbf.mjs} +1 -1
  14. package/dist/{ansi-CLOitHKx.mjs.map → ansi-D1KQMAbf.mjs.map} +1 -1
  15. package/dist/ansi-yC4RyBNY.mjs +22441 -0
  16. package/dist/ansi-yC4RyBNY.mjs.map +1 -0
  17. package/dist/apng-CR08rIaH.mjs +58 -0
  18. package/dist/apng-CR08rIaH.mjs.map +1 -0
  19. package/dist/apng-DaHfVaVI.mjs +3 -0
  20. package/dist/assets/resvgjs.darwin-arm64-BtufyGW1.node +0 -0
  21. package/dist/assets/skia.darwin-arm64-DQs5sT6N.node +0 -0
  22. package/dist/backend-B-WYLUib.mjs +13396 -0
  23. package/dist/backend-B-WYLUib.mjs.map +1 -0
  24. package/dist/backends-CUtan80W.mjs +3 -0
  25. package/dist/backends-DIVYzKqd.mjs +1083 -0
  26. package/dist/backends-DIVYzKqd.mjs.map +1 -0
  27. package/dist/bound-term-0sPrrzH1.d.mts +4640 -0
  28. package/dist/bound-term-0sPrrzH1.d.mts.map +1 -0
  29. package/dist/canvas-1v7dPT-_.mjs +3 -0
  30. package/dist/canvas-CSuPOMNt.mjs +1442 -0
  31. package/dist/canvas-CSuPOMNt.mjs.map +1 -0
  32. package/dist/{chunk-Vs_PY4HZ.mjs → chunk-BSw8zbkd.mjs} +1 -1
  33. package/dist/cli-dvo0r2fs.mjs +4 -0
  34. package/dist/compare-CQodSH4G.mjs +376 -0
  35. package/dist/compare-CQodSH4G.mjs.map +1 -0
  36. package/dist/compare-DHlcxEYA.mjs +3 -0
  37. package/dist/context-BU5LkkIy.mjs.map +1 -1
  38. package/dist/devtools-CJdt5H0X.mjs +2 -0
  39. package/dist/{devtools-DxkSLXDA.mjs → devtools-DcQjgyjL.mjs} +5 -4
  40. package/dist/{devtools-DxkSLXDA.mjs.map → devtools-DcQjgyjL.mjs.map} +1 -1
  41. package/dist/easing-BI-ASGMO.d.mts +24 -0
  42. package/dist/easing-BI-ASGMO.d.mts.map +1 -0
  43. package/dist/{eta-Bb3RH3wh.mjs → eta-CJlGH06n.mjs} +1 -1
  44. package/dist/{eta-Bb3RH3wh.mjs.map → eta-CJlGH06n.mjs.map} +1 -1
  45. package/dist/flexily-zero-adapter-C3Vj0fPt.mjs +306 -0
  46. package/dist/flexily-zero-adapter-C3Vj0fPt.mjs.map +1 -0
  47. package/dist/{flexily-zero-adapter-CMxXhdOL.mjs → flexily-zero-adapter-C4lW_Ov5.mjs} +1 -1
  48. package/dist/fonts-BFmhXDv7.mjs +88 -0
  49. package/dist/fonts-BFmhXDv7.mjs.map +1 -0
  50. package/dist/gif-C_AjaT9d.mjs +188 -0
  51. package/dist/gif-C_AjaT9d.mjs.map +1 -0
  52. package/dist/gif-DaC4XrxA.mjs +3 -0
  53. package/dist/gifenc-BOUT-KFB.mjs +730 -0
  54. package/dist/gifenc-BOUT-KFB.mjs.map +1 -0
  55. package/dist/image-C2Birh2x.mjs +1252 -0
  56. package/dist/image-C2Birh2x.mjs.map +1 -0
  57. package/dist/index-BUMxS65f.d.mts +453 -0
  58. package/dist/index-BUMxS65f.d.mts.map +1 -0
  59. package/dist/{index-D3saHouR.d.mts → index-CSQf13CI.d.mts} +1057 -1133
  60. package/dist/index-CSQf13CI.d.mts.map +1 -0
  61. package/dist/{index-BXslOebb.d.mts → index-Cl9KKjQ_.d.mts} +4919 -3921
  62. package/dist/index-Cl9KKjQ_.d.mts.map +1 -0
  63. package/dist/index-XbNrPhWl.d.mts +336 -0
  64. package/dist/index-XbNrPhWl.d.mts.map +1 -0
  65. package/dist/index.d.mts +8 -5
  66. package/dist/index.d.mts.map +1 -1
  67. package/dist/index.mjs +14 -12
  68. package/dist/index.mjs.map +1 -1
  69. package/dist/key-mapping-CS-YD_cD.mjs +132 -0
  70. package/dist/key-mapping-CS-YD_cD.mjs.map +1 -0
  71. package/dist/key-mapping-Yn-Jgrij.mjs +3 -0
  72. package/dist/{layout-engine-B6Cdz1yZ.mjs → layout-engine-C07LEXWT.mjs} +1 -1
  73. package/dist/layout-engine-C2px0RJE.mjs +67 -0
  74. package/dist/layout-engine-C2px0RJE.mjs.map +1 -0
  75. package/dist/layout-signals-Cnw6xk8Q.mjs +988 -0
  76. package/dist/layout-signals-Cnw6xk8Q.mjs.map +1 -0
  77. package/dist/mouse-events-Dki3ISIp.mjs +1044 -0
  78. package/dist/mouse-events-Dki3ISIp.mjs.map +1 -0
  79. package/dist/{multi-progress-Bq9Oi_WI.mjs → multi-progress-CIRjrzma.mjs} +3 -3
  80. package/dist/{multi-progress-Bq9Oi_WI.mjs.map → multi-progress-CIRjrzma.mjs.map} +1 -1
  81. package/dist/{multi-progress-DAQC7eap.d.mts → multi-progress-DHZ2xUT2.d.mts} +2 -2
  82. package/dist/{multi-progress-DAQC7eap.d.mts.map → multi-progress-DHZ2xUT2.d.mts.map} +1 -1
  83. package/dist/{node-BeWlnCPY.mjs → node-CjM5Rt-M.mjs} +4 -4
  84. package/dist/node-CjM5Rt-M.mjs.map +1 -0
  85. package/dist/playwright-D5YiZcNS.mjs +76397 -0
  86. package/dist/playwright-D5YiZcNS.mjs.map +1 -0
  87. package/dist/png-codec-Dp84742B.mjs +36 -0
  88. package/dist/png-codec-Dp84742B.mjs.map +1 -0
  89. package/dist/png-codec-QwOtJ8Zs.mjs +3 -0
  90. package/dist/progress-DB_Xo071.mjs +675 -0
  91. package/dist/progress-DB_Xo071.mjs.map +1 -0
  92. package/dist/{progress-bar-CXE5Qfkd.mjs → progress-bar-oJwq22CR.mjs} +4 -4
  93. package/dist/{progress-bar-CXE5Qfkd.mjs.map → progress-bar-oJwq22CR.mjs.map} +1 -1
  94. package/dist/rasterizer-BRXrDdWx.mjs +3 -0
  95. package/dist/rasterizer-CpEhJvdR.mjs +296 -0
  96. package/dist/rasterizer-CpEhJvdR.mjs.map +1 -0
  97. package/dist/reconciler-DldIJB93.mjs +2083 -0
  98. package/dist/reconciler-DldIJB93.mjs.map +1 -0
  99. package/dist/{render-string-CDCeYkS3.mjs → render-string-BcoCpjCB.mjs} +1 -1
  100. package/dist/{render-string-Darrg7ku.mjs → render-string-DkQacASz.mjs} +2707 -549
  101. package/dist/render-string-DkQacASz.mjs.map +1 -0
  102. package/dist/resvg-js-DkOndZI3.mjs +203 -0
  103. package/dist/resvg-js-DkOndZI3.mjs.map +1 -0
  104. package/dist/runtime.d.mts +3 -2
  105. package/dist/runtime.mjs +3 -3
  106. package/dist/schemes-JjNp4aSl.mjs +2611 -0
  107. package/dist/schemes-JjNp4aSl.mjs.map +1 -0
  108. package/dist/{spinner-CGo34vyR.d.mts → spinner-CZINHpkV.d.mts} +2 -2
  109. package/dist/{spinner-CGo34vyR.d.mts.map → spinner-CZINHpkV.d.mts.map} +1 -1
  110. package/dist/{spinner-CeOmcuw_.mjs → spinner-D9lrHr8s.mjs} +7 -7
  111. package/dist/spinner-D9lrHr8s.mjs.map +1 -0
  112. package/dist/src-5w9QR6_8.mjs +1071 -0
  113. package/dist/src-5w9QR6_8.mjs.map +1 -0
  114. package/dist/src-BNTToU7l.mjs +4387 -0
  115. package/dist/src-BNTToU7l.mjs.map +1 -0
  116. package/dist/{src-CF-6UN01.mjs → src-BR4xNwdG.mjs} +10436 -2622
  117. package/dist/src-BR4xNwdG.mjs.map +1 -0
  118. package/dist/{types-Bk2yw9Qj.mjs → src-DKp-_OFG.mjs} +34 -94
  119. package/dist/src-DKp-_OFG.mjs.map +1 -0
  120. package/dist/src-bt8wSrfJ.mjs +258 -0
  121. package/dist/src-bt8wSrfJ.mjs.map +1 -0
  122. package/dist/src-e33Y6kNJ.mjs +3 -0
  123. package/dist/src-iDwu25UD.mjs +1814 -0
  124. package/dist/src-iDwu25UD.mjs.map +1 -0
  125. package/dist/steps-Bp2uNqnn.d.mts +202 -0
  126. package/dist/steps-Bp2uNqnn.d.mts.map +1 -0
  127. package/dist/svg-15lZZzxq.mjs +486 -0
  128. package/dist/svg-15lZZzxq.mjs.map +1 -0
  129. package/dist/svg-Cz0UXcDj.mjs +255 -0
  130. package/dist/svg-Cz0UXcDj.mjs.map +1 -0
  131. package/dist/svg-DY72a4HK.mjs +3 -0
  132. package/dist/svg-g1D6ErwR.d.mts +82 -0
  133. package/dist/svg-g1D6ErwR.d.mts.map +1 -0
  134. package/dist/term.d.mts +3 -0
  135. package/dist/term.mjs +9 -0
  136. package/dist/term.mjs.map +1 -0
  137. package/dist/theme.d.mts +95 -2
  138. package/dist/theme.d.mts.map +1 -0
  139. package/dist/theme.mjs +9 -3
  140. package/dist/theme.mjs.map +1 -0
  141. package/dist/{types-BH_v3iMT.d.mts → types-kt_fKR37.d.mts} +2 -15
  142. package/dist/types-kt_fKR37.d.mts.map +1 -0
  143. package/dist/ui/animation.d.mts +2 -1
  144. package/dist/ui/animation.mjs +1 -1
  145. package/dist/ui/ansi.d.mts +1 -1
  146. package/dist/ui/ansi.mjs +1 -1
  147. package/dist/ui/cli.d.mts +3 -3
  148. package/dist/ui/cli.mjs +5 -5
  149. package/dist/ui/display.d.mts +1 -1
  150. package/dist/ui/image.d.mts +2 -2
  151. package/dist/ui/image.mjs +2 -2
  152. package/dist/ui/input.d.mts +1 -1
  153. package/dist/ui/input.mjs +4 -2
  154. package/dist/ui/input.mjs.map +1 -1
  155. package/dist/ui/progress.d.mts +5 -249
  156. package/dist/ui/progress.mjs +5 -858
  157. package/dist/ui/react.d.mts +1 -1
  158. package/dist/ui/react.mjs +2 -2
  159. package/dist/ui/recording-chrome-react.d.mts +21 -0
  160. package/dist/ui/recording-chrome-react.d.mts.map +1 -0
  161. package/dist/ui/recording-chrome-react.mjs +105 -0
  162. package/dist/ui/recording-chrome-react.mjs.map +1 -0
  163. package/dist/ui/recording-chrome.d.mts +2 -0
  164. package/dist/ui/recording-chrome.mjs +2 -0
  165. package/dist/ui/utils.mjs +1 -1
  166. package/dist/ui/wrappers.d.mts +3 -3
  167. package/dist/ui/wrappers.mjs +2 -2
  168. package/dist/ui.d.mts +7 -6
  169. package/dist/ui.mjs +8 -7
  170. package/dist/{useLatest-Bg2x4bfP.d.mts → useLatest-DRDDVwjh.d.mts} +5 -25
  171. package/dist/useLatest-DRDDVwjh.d.mts.map +1 -0
  172. package/dist/{with-text-input-CRfoiFFG.d.mts → with-text-input-YeohVLeo.d.mts} +4 -55
  173. package/dist/with-text-input-YeohVLeo.d.mts.map +1 -0
  174. package/dist/wrapper-C70ATkVv.mjs +3527 -0
  175. package/dist/wrapper-C70ATkVv.mjs.map +1 -0
  176. package/dist/{wrappers-UTADQkSY.mjs → wrappers-BCUYITrY.mjs} +5 -157
  177. package/dist/wrappers-BCUYITrY.mjs.map +1 -0
  178. package/dist/{yoga-adapter-8oRGRw8V.mjs → yoga-adapter-BnZX1PAY.mjs} +28 -2
  179. package/dist/yoga-adapter-BnZX1PAY.mjs.map +1 -0
  180. package/dist/yoga-adapter-DxgsQ_gg.mjs +2 -0
  181. package/dist/zipBundle-3nqeDRtm.mjs +3 -0
  182. package/dist/zipBundle-VNAYFmqJ.mjs +2003 -0
  183. package/dist/zipBundle-VNAYFmqJ.mjs.map +1 -0
  184. package/package.json +20 -9
  185. package/dist/animation-Cn64yepo.mjs.map +0 -1
  186. package/dist/cli-BKp0YtBD.mjs +0 -4
  187. package/dist/devtools-9QY4teqI.mjs +0 -2
  188. package/dist/flexily-zero-adapter-BlQa46nr.mjs +0 -3385
  189. package/dist/flexily-zero-adapter-BlQa46nr.mjs.map +0 -1
  190. package/dist/image-CTII5QWI.mjs +0 -477
  191. package/dist/image-CTII5QWI.mjs.map +0 -1
  192. package/dist/index-BXslOebb.d.mts.map +0 -1
  193. package/dist/index-BnA7mNpo.d.mts +0 -175
  194. package/dist/index-BnA7mNpo.d.mts.map +0 -1
  195. package/dist/index-D3saHouR.d.mts.map +0 -1
  196. package/dist/layout-engine-ClUgv6jB.mjs +0 -50
  197. package/dist/layout-engine-ClUgv6jB.mjs.map +0 -1
  198. package/dist/node-BeWlnCPY.mjs.map +0 -1
  199. package/dist/reconciler-Cwgm8hRR.mjs +0 -8459
  200. package/dist/reconciler-Cwgm8hRR.mjs.map +0 -1
  201. package/dist/render-string-Darrg7ku.mjs.map +0 -1
  202. package/dist/spinner-CeOmcuw_.mjs.map +0 -1
  203. package/dist/src-B5GjfG7g.mjs +0 -4305
  204. package/dist/src-B5GjfG7g.mjs.map +0 -1
  205. package/dist/src-CChwjk0Z.mjs +0 -738
  206. package/dist/src-CChwjk0Z.mjs.map +0 -1
  207. package/dist/src-CF-6UN01.mjs.map +0 -1
  208. package/dist/src-NCKb8kE5.mjs +0 -2660
  209. package/dist/src-NCKb8kE5.mjs.map +0 -1
  210. package/dist/types-BH_v3iMT.d.mts.map +0 -1
  211. package/dist/types-Bk2yw9Qj.mjs.map +0 -1
  212. package/dist/ui/progress.d.mts.map +0 -1
  213. package/dist/ui/progress.mjs.map +0 -1
  214. package/dist/useLatest-Bg2x4bfP.d.mts.map +0 -1
  215. package/dist/with-text-input-CRfoiFFG.d.mts.map +0 -1
  216. package/dist/wrappers-UTADQkSY.mjs.map +0 -1
  217. package/dist/yoga-adapter-8oRGRw8V.mjs.map +0 -1
  218. package/dist/yoga-adapter-D_CcxSt5.mjs +0 -2
@@ -0,0 +1,1252 @@
1
+ import { o as __toESM } from "./chunk-BSw8zbkd.mjs";
2
+ import { r as effect } from "./src-DKp-_OFG.mjs";
3
+ import { a as NodeContext, c as StdoutContext, l as TermContext } from "./context-BU5LkkIy.mjs";
4
+ import { m as rectEqual, o as markObservedLayoutSignal, r as getLayoutSignals } from "./layout-signals-Cnw6xk8Q.mjs";
5
+ import { a as Box, t as Text } from "./Text-Lq0dmj8-.mjs";
6
+ import { M as createTerminalProfile, t as init_src } from "./src-BNTToU7l.mjs";
7
+ import { t as require_UPNG } from "./UPNG-DosRPdF4.mjs";
8
+ import { useCallback, useContext, useEffect, useLayoutEffect, useMemo, useReducer, useRef, useSyncExternalStore } from "react";
9
+ import { jsx } from "react/jsx-runtime";
10
+ import { readFileSync } from "node:fs";
11
+ //#region packages/ag-react/src/hooks/useLayout.ts
12
+ /**
13
+ * Layout Hooks — three coordinate systems for positioning in silvery.
14
+ *
15
+ * Every silvery node has three rects that differ only by how scroll and
16
+ * sticky offsets are applied. Pick the one that matches your use case:
17
+ *
18
+ * - `useBoxRect()` — layout position (border-box sized, minus padding/border).
19
+ * Use for responsive sizing inside a component. Matches
20
+ * CSS `clientWidth`/`clientHeight` for the content area.
21
+ * - `useScrollRect()` — scroll-adjusted position, **pre** sticky clamping.
22
+ * Use when you need the "natural" position of a node
23
+ * in scrolled coordinates (can go off-screen).
24
+ * - `useScreenRect()` — actual paint position on the terminal screen.
25
+ * Use for hit testing, cursor positioning, and
26
+ * cross-component visual navigation. The CSS
27
+ * `getBoundingClientRect()` analogue.
28
+ *
29
+ * ## Deferred semantics (the only contract)
30
+ *
31
+ * Each hook returns the rect as of the **most recent committed layout** —
32
+ * the value as of the last event-batch commit boundary. Within a single
33
+ * batch, the returned value is invariant across every convergence pass;
34
+ * React renders see one value per batch. After the batch's commit boundary
35
+ * fires, the next batch sees the new value.
36
+ *
37
+ * This is the structural fix for the "render reads useBoxRect AND writes
38
+ * a layout-affecting prop based on it" feedback loop. Under the in-flight
39
+ * model that preceded this hook (pre 2026-05-06), the read returned the
40
+ * latest measurement during the same batch, which could differ between
41
+ * the first and second convergence passes — causing the write to flip
42
+ * between branches and the loop to ping-pong until `MAX_CONVERGENCE_PASSES`
43
+ * capped it. Under deferred semantics the read is invariant for the
44
+ * batch, so the loop completes in one pass.
45
+ *
46
+ * **One-frame-late by design.** A component that mounts shows the
47
+ * empty-rect fallback (`{ x: 0, y: 0, width: 0, height: 0 }`) on its
48
+ * first render and the real rect on the next commit boundary. Layout
49
+ * effects that run on the second render see the real rect and can write
50
+ * positioned terminal escapes (Image, decorations) into the next
51
+ * paintFrame.
52
+ *
53
+ * Components that need same-frame measurements must read `node.boxRect`
54
+ * etc. directly via `useAgNode()` and gate on `useLayoutEffect` —
55
+ * recommended only for leaf primitives in the silvery framework itself.
56
+ *
57
+ * For breakpoint logic, prefer `useResponsiveValue()` or
58
+ * `useResponsiveBoxProps()` — bucketing into stable zones gives more
59
+ * predictable behavior than branching on raw widths.
60
+ *
61
+ * See bead `@km/silvery/use-deferred-box-rect-and-post-commit-observers`.
62
+ */
63
+ const EMPTY_RECT = {
64
+ x: 0,
65
+ y: 0,
66
+ width: 0,
67
+ height: 0
68
+ };
69
+ const EMPTY_SIZE = {
70
+ width: 0,
71
+ height: 0
72
+ };
73
+ /**
74
+ * Get the inner content dimensions of a node (border-box minus padding and border).
75
+ * This is the space available for the node's children.
76
+ *
77
+ * `boxRect` is passed in explicitly so the helper derives the inner rect
78
+ * from the committed signal value rather than re-reading `node.boxRect`
79
+ * (which holds the in-flight value mid-batch).
80
+ */
81
+ function deriveInnerRect(node, boxRect) {
82
+ if (!boxRect) return null;
83
+ const props = node.props;
84
+ if (!props || node.type === "silvery-text") return boxRect;
85
+ const pTop = props.paddingTop ?? props.paddingY ?? props.padding ?? 0;
86
+ const pBottom = props.paddingBottom ?? props.paddingY ?? props.padding ?? 0;
87
+ const pLeft = props.paddingLeft ?? props.paddingX ?? props.padding ?? 0;
88
+ const pRight = props.paddingRight ?? props.paddingX ?? props.padding ?? 0;
89
+ let bTop = 0;
90
+ let bBottom = 0;
91
+ let bLeft = 0;
92
+ let bRight = 0;
93
+ if (props.borderStyle) {
94
+ bTop = props.borderTop !== false ? 1 : 0;
95
+ bBottom = props.borderBottom !== false ? 1 : 0;
96
+ bLeft = props.borderLeft !== false ? 1 : 0;
97
+ bRight = props.borderRight !== false ? 1 : 0;
98
+ }
99
+ return {
100
+ x: boxRect.x + pLeft + bLeft,
101
+ y: boxRect.y + pTop + bTop,
102
+ width: Math.max(0, boxRect.width - pLeft - pRight - bLeft - bRight),
103
+ height: Math.max(0, boxRect.height - pTop - pBottom - bTop - bBottom)
104
+ };
105
+ }
106
+ const COMMITTED_RECT_OBSERVED_KEY = {
107
+ boxRectCommitted: "boxRect",
108
+ scrollRectCommitted: "scrollRect",
109
+ screenRectCommitted: "screenRect"
110
+ };
111
+ const IN_FLIGHT_RECT_OBSERVED_KEY = {
112
+ boxRect: "boxRect",
113
+ scrollRect: "scrollRect",
114
+ screenRect: "screenRect"
115
+ };
116
+ /**
117
+ * Reactive rect hook (deferred): subscribes to a committed rect signal and
118
+ * re-renders when the value advances at a commit boundary. Returns the
119
+ * rect derived from the committed value via `getCommittedRect`.
120
+ *
121
+ * Within a single event batch the committed signal does not change — every
122
+ * convergence pass sees the same value, so a render that reads useBoxRect
123
+ * and writes a layout-affecting prop converges in one pass. After the
124
+ * batch's commit boundary (handled by the runtime via
125
+ * `commitLayoutSnapshot`), the next batch's first render sees the new
126
+ * value.
127
+ */
128
+ function useReactiveRect(getCommittedRect, committedSignalKey) {
129
+ const node = useContext(NodeContext);
130
+ const [, forceUpdate] = useReducer((x) => x + 1, 0);
131
+ const prevRef = useRef(null);
132
+ useLayoutEffect(() => {
133
+ if (!node) return;
134
+ markObservedLayoutSignal(node, COMMITTED_RECT_OBSERVED_KEY[committedSignalKey]);
135
+ const rectSignal = getLayoutSignals(node)[committedSignalKey];
136
+ return effect(() => {
137
+ const next = getCommittedRect(rectSignal(), node) ?? null;
138
+ if (!rectEqual(prevRef.current, next)) {
139
+ prevRef.current = next;
140
+ forceUpdate();
141
+ }
142
+ });
143
+ }, [node]);
144
+ if (!node) return EMPTY_RECT;
145
+ markObservedLayoutSignal(node, COMMITTED_RECT_OBSERVED_KEY[committedSignalKey]);
146
+ return getCommittedRect(getLayoutSignals(node)[committedSignalKey](), node) ?? EMPTY_RECT;
147
+ }
148
+ /**
149
+ * **DANGEROUS — measurement read on the render path.** Prefer a declarative
150
+ * primitive (`<Box fitWidth>`, `useResponsiveBoxProps`, `useResponsiveValue`,
151
+ * `useOnBoxRectCommitted`) before reaching for this hook.
152
+ *
153
+ * Returns the inner content dimensions for the current component's nearest
154
+ * Box, as of the most recent committed layout. Width and height reflect
155
+ * the space available for children (border-box minus padding and border),
156
+ * like CSS `clientWidth`/`clientHeight`.
157
+ *
158
+ * ```tsx
159
+ * function Header() {
160
+ * const { width } = useBoxRectDangerously()
161
+ * return <Text>{'='.repeat(Math.max(0, width))}</Text>
162
+ * }
163
+ * ```
164
+ *
165
+ * On first render returns `{ x: 0, y: 0, width: 0, height: 0 }`. After the
166
+ * first commit boundary, automatically re-renders with the measured
167
+ * dimensions. This **first-paint zero-rect transition** is the social cost
168
+ * the rename announces — components that branch layout on this hook see a
169
+ * flush-left / collapsed first paint, then re-flow when the real rect
170
+ * commits one event-loop tick later. Visible jank under streaming / dynamic
171
+ * mount / SIGWINCH burst.
172
+ *
173
+ * Use only when:
174
+ * - You need a measurement read in JS control flow (animation, autoscroll
175
+ * thresholds, hit-testing) that **doesn't drive layout-affecting props**.
176
+ * - No declarative primitive covers the case.
177
+ *
178
+ * Deferred semantics — see this file's docstring for the contract and the
179
+ * one-frame-late behavior.
180
+ *
181
+ * Bead: `@km/silvery/responsive-layout-architecture-reframe` (Phase A.1
182
+ * lands this rename; the full Phase A migrates each consumer to a
183
+ * declarative primitive or, where genuinely needed, to a Suspense-friendly
184
+ * `Promise<Rect>` form coordinated via `RectReadBarrier`).
185
+ */
186
+ function useBoxRectDangerously() {
187
+ return useReactiveRect((committed, node) => deriveInnerRect(node, committed), "boxRectCommitted");
188
+ }
189
+ /**
190
+ * @deprecated Renamed to `useBoxRectDangerously`. The original name read as
191
+ * "the normal hook for getting your rect" — exactly the framing the Phase
192
+ * A.1 rename is intended to break. App authors should reach for declarative
193
+ * primitives first; this alias remains for one release cycle and logs a
194
+ * dev-time warning per call site. Will be removed in the next major.
195
+ *
196
+ * Bead: `@km/silvery/responsive-layout-architecture-reframe`.
197
+ */
198
+ function useBoxRect() {
199
+ if (process.env.NODE_ENV !== "production" && process.env.NODE_ENV !== "test") warnUseBoxRectDeprecation();
200
+ return useReactiveRect((committed, node) => deriveInnerRect(node, committed), "boxRectCommitted");
201
+ }
202
+ /**
203
+ * Internal dimensions-only variant of the deferred box rect read.
204
+ *
205
+ * This is for framework primitives that build width/height-derived text
206
+ * (Divider, ProgressBar, TextArea wrapping) but do not care where their
207
+ * parent sits on screen. It subscribes to the same committed boxRect signal
208
+ * as `useBoxRect()`, but only re-renders when the derived inner
209
+ * `{width,height}` changes. Scrolling and virtual-window spacer movement
210
+ * often change x/y without changing dimensions; full-rect subscriptions wake
211
+ * these primitives on every such frame and steal scroll budget.
212
+ */
213
+ function useBoxSize() {
214
+ const node = useContext(NodeContext);
215
+ const [, forceUpdate] = useReducer((x) => x + 1, 0);
216
+ const prevRef = useRef(null);
217
+ useLayoutEffect(() => {
218
+ if (!node) return;
219
+ markObservedLayoutSignal(node, "boxSize");
220
+ const signals = getLayoutSignals(node);
221
+ return effect(() => {
222
+ const rect = deriveInnerRect(node, signals.boxRectCommitted());
223
+ const next = rect ? {
224
+ width: rect.width,
225
+ height: rect.height
226
+ } : EMPTY_SIZE;
227
+ const prev = prevRef.current;
228
+ if (!prev || prev.width !== next.width || prev.height !== next.height) {
229
+ prevRef.current = next;
230
+ forceUpdate();
231
+ }
232
+ });
233
+ }, [node]);
234
+ if (!node) return EMPTY_SIZE;
235
+ markObservedLayoutSignal(node, "boxSize");
236
+ const rect = deriveInnerRect(node, getLayoutSignals(node).boxRectCommitted());
237
+ return rect ? {
238
+ width: rect.width,
239
+ height: rect.height
240
+ } : EMPTY_SIZE;
241
+ }
242
+ const useBoxRectWarnedCallSites = /* @__PURE__ */ new Set();
243
+ function warnUseBoxRectDeprecation() {
244
+ const lines = ((/* @__PURE__ */ new Error()).stack ?? "").split("\n");
245
+ let consumerFrame = "";
246
+ for (let i = 1; i < lines.length; i++) {
247
+ const line = lines[i] ?? "";
248
+ if (!line.includes("useLayout.ts") && line.trim().startsWith("at ")) {
249
+ consumerFrame = line.trim();
250
+ break;
251
+ }
252
+ }
253
+ const key = consumerFrame || "<unknown>";
254
+ if (useBoxRectWarnedCallSites.has(key)) return;
255
+ useBoxRectWarnedCallSites.add(key);
256
+ console.warn(`[silvery] useBoxRect() is deprecated — rename to useBoxRectDangerously(), or migrate to a declarative primitive (<Box fitWidth>, useResponsiveBoxProps, useResponsiveValue, useOnBoxRectCommitted). See @km/silvery/responsive-layout-architecture-reframe. Call site: ${key}`);
257
+ }
258
+ /**
259
+ * Returns the scroll-adjusted position for the current component, as of
260
+ * the most recent committed layout.
261
+ *
262
+ * This is the node's position in scroll coordinates, *before* sticky
263
+ * clamping. For non-sticky nodes it equals `useScreenRect()`. For sticky
264
+ * nodes, the scrollRect reflects where the node would be without sticky
265
+ * adjustment — so it can go off-screen (negative y, etc.) when scrolled
266
+ * past.
267
+ *
268
+ * ```tsx
269
+ * function Card({ id }) {
270
+ * const { y } = useScrollRect()
271
+ * return <Box>Scroll y: {y}</Box>
272
+ * }
273
+ * ```
274
+ *
275
+ * Deferred semantics — see this file's docstring.
276
+ */
277
+ function useScrollRect() {
278
+ return useReactiveRect((committed) => committed, "scrollRectCommitted");
279
+ }
280
+ /**
281
+ * Returns the actual paint position on the terminal screen as of the most
282
+ * recent committed layout — the silvery analogue of
283
+ * `getBoundingClientRect()`.
284
+ *
285
+ * For non-sticky nodes this equals `useScrollRect()`. For sticky nodes
286
+ * (`position="sticky"`), it reflects the clamped position where pixels
287
+ * actually land on screen.
288
+ *
289
+ * ```tsx
290
+ * function StickyHeader() {
291
+ * const { y } = useScreenRect()
292
+ * return <Box position="sticky" stickyTop={0}>Header at row {y}</Box>
293
+ * }
294
+ * ```
295
+ *
296
+ * Deferred semantics — see this file's docstring.
297
+ */
298
+ function useScreenRect() {
299
+ return useReactiveRect((committed) => committed, "screenRectCommitted");
300
+ }
301
+ /**
302
+ * In-flight reactive rect hook (escape hatch): subscribes to the LIVE rect
303
+ * signal — the value as written by the most recent layout pass within the
304
+ * current convergence cycle. Re-renders when the in-flight value advances,
305
+ * which can happen multiple times per event batch as the convergence loop
306
+ * iterates.
307
+ *
308
+ * Use only inside silvery framework internals where first-paint measurement
309
+ * is required and the consumer does not write layout-affecting props
310
+ * derived from the read. App code must use the deferred form
311
+ * (`useBoxRect()` / `useScrollRect()` / `useScreenRect()`) or
312
+ * `useResponsiveBoxProps`/`useResponsiveValue` instead.
313
+ */
314
+ function useReactiveRectInFlight(getDerivedRect, inFlightSignalKey) {
315
+ const node = useContext(NodeContext);
316
+ const [, forceUpdate] = useReducer((x) => x + 1, 0);
317
+ const prevRef = useRef(null);
318
+ useLayoutEffect(() => {
319
+ if (!node) return;
320
+ markObservedLayoutSignal(node, IN_FLIGHT_RECT_OBSERVED_KEY[inFlightSignalKey]);
321
+ const rectSignal = getLayoutSignals(node)[inFlightSignalKey];
322
+ return effect(() => {
323
+ const next = getDerivedRect(rectSignal(), node) ?? null;
324
+ if (!rectEqual(prevRef.current, next)) {
325
+ prevRef.current = next;
326
+ forceUpdate();
327
+ }
328
+ });
329
+ }, [node]);
330
+ if (!node) return EMPTY_RECT;
331
+ markObservedLayoutSignal(node, IN_FLIGHT_RECT_OBSERVED_KEY[inFlightSignalKey]);
332
+ return getDerivedRect(getLayoutSignals(node)[inFlightSignalKey](), node) ?? EMPTY_RECT;
333
+ }
334
+ /**
335
+ * Returns the inner content dimensions of the current Box from the IN-FLIGHT
336
+ * signal — the value as of the most recent layout pass, which may change
337
+ * between convergence passes within an event batch.
338
+ *
339
+ * Silvery framework internals only — app code must use {@link useBoxRect}
340
+ * (deferred) or `useResponsiveBoxProps`/`useResponsiveValue` instead.
341
+ *
342
+ * Unlike {@link useBoxRect} (the deferred form), this hook returns the
343
+ * measured value on the first render after layout — there is no one-frame
344
+ * fallback. The cost is that a render reading this hook AND writing a
345
+ * layout-affecting prop can form a convergence-loop feedback edge; the
346
+ * lint rule `silvery/no-in-flight-rect-in-app` enforces the call-site
347
+ * scope.
348
+ */
349
+ function useBoxRectInFlight() {
350
+ return useReactiveRectInFlight((raw, node) => deriveInnerRect(node, raw), "boxRect");
351
+ }
352
+ /**
353
+ * Returns the scroll-adjusted position from the IN-FLIGHT signal — the
354
+ * value as of the most recent layout pass, which may change between
355
+ * convergence passes within an event batch.
356
+ *
357
+ * Silvery framework internals only — see {@link useBoxRectInFlight} for
358
+ * the contract and the lint rule that gates app-code use.
359
+ */
360
+ function useScrollRectInFlight() {
361
+ return useReactiveRectInFlight((raw) => raw, "scrollRect");
362
+ }
363
+ /**
364
+ * Returns the actual paint position from the IN-FLIGHT signal — the value
365
+ * as of the most recent layout pass, which may change between convergence
366
+ * passes within an event batch.
367
+ *
368
+ * Silvery framework internals only — see {@link useBoxRectInFlight} for
369
+ * the contract and the lint rule that gates app-code use.
370
+ */
371
+ function useScreenRectInFlight() {
372
+ return useReactiveRectInFlight((raw) => raw, "screenRect");
373
+ }
374
+ /**
375
+ * Subscribes to the committed rect signal and fires `cb(rect)` at each
376
+ * commit boundary, **without** triggering a re-render of the consumer.
377
+ *
378
+ * Use for hot paths where a rect change drives an imperative side effect
379
+ * (cursor store update, registry write, ANSI emission) and there is no
380
+ * render path that needs to reflect the rect.
381
+ *
382
+ * The callback is invoked synchronously from a commit-boundary effect(),
383
+ * with the derived rect (`getDerivedRect` applied to the committed signal
384
+ * value). It is NOT invoked when the derived rect is null. The caller may
385
+ * close over component state via refs; see `useCursor` / `useGridPosition`
386
+ * for canonical patterns.
387
+ */
388
+ function useOnRectCommitted(cb, getDerivedRect, committedSignalKey) {
389
+ const node = useContext(NodeContext);
390
+ const cbRef = useRef(cb);
391
+ cbRef.current = cb;
392
+ useLayoutEffect(() => {
393
+ if (!node) return;
394
+ const rectSignal = getLayoutSignals(node)[committedSignalKey];
395
+ let prev = null;
396
+ return effect(() => {
397
+ const next = getDerivedRect(rectSignal(), node) ?? null;
398
+ if (next == null) return;
399
+ if (rectEqual(prev, next)) return;
400
+ prev = next;
401
+ cbRef.current(next);
402
+ });
403
+ }, [node]);
404
+ }
405
+ /**
406
+ * Subscribes to the committed boxRect (inner content) and fires `cb` at
407
+ * each commit boundary without re-rendering. See {@link useBoxRect} for
408
+ * the deferred-rect contract; this is the observer form.
409
+ */
410
+ function useOnBoxRectCommitted(cb) {
411
+ useOnRectCommitted(cb, (committed, node) => deriveInnerRect(node, committed), "boxRectCommitted");
412
+ }
413
+ /**
414
+ * Subscribes to the committed scrollRect and fires `cb` at each commit
415
+ * boundary without re-rendering.
416
+ */
417
+ function useOnScrollRectCommitted(cb) {
418
+ useOnRectCommitted(cb, (committed) => committed, "scrollRectCommitted");
419
+ }
420
+ /**
421
+ * Subscribes to the committed screenRect and fires `cb` at each commit
422
+ * boundary without re-rendering.
423
+ */
424
+ function useOnScreenRectCommitted(cb) {
425
+ useOnRectCommitted(cb, (committed) => committed, "screenRectCommitted");
426
+ }
427
+ //#endregion
428
+ //#region packages/ag-react/src/hooks/useTerm.ts
429
+ /**
430
+ * Shallow equality comparison for object selectors.
431
+ *
432
+ * @example
433
+ * ```tsx
434
+ * const { cols, rows } = useTerm(t => ({ cols: t.cols, rows: t.rows }), shallow)
435
+ * ```
436
+ */
437
+ function shallow(a, b) {
438
+ if (Object.is(a, b)) return true;
439
+ if (typeof a !== "object" || typeof b !== "object" || a === null || b === null) return false;
440
+ const keysA = Object.keys(a);
441
+ const keysB = Object.keys(b);
442
+ if (keysA.length !== keysB.length) return false;
443
+ for (const key of keysA) if (!Object.is(a[key], b[key])) return false;
444
+ return true;
445
+ }
446
+ function useTerm(selector, equalityFn) {
447
+ const term = useContext(TermContext);
448
+ if (!term) throw new Error("useTerm must be used within a render(element, term) context");
449
+ if (!selector) return term;
450
+ return useTermSelector(term, selector, equalityFn);
451
+ }
452
+ function useTermSelector(term, selector, equalityFn) {
453
+ const prevRef = useRef(void 0);
454
+ const isEqual = equalityFn ?? Object.is;
455
+ const subscribe = useCallback((listener) => {
456
+ return effect(() => {
457
+ term.size.snapshot();
458
+ listener();
459
+ });
460
+ }, [term]);
461
+ const getSnapshot = useCallback(() => {
462
+ const next = selector(term);
463
+ if (prevRef.current !== void 0 && isEqual(prevRef.current, next)) return prevRef.current;
464
+ prevRef.current = next;
465
+ return next;
466
+ }, [
467
+ term,
468
+ selector,
469
+ isEqual
470
+ ]);
471
+ return useSyncExternalStore(subscribe, getSnapshot, getSnapshot);
472
+ }
473
+ //#endregion
474
+ //#region packages/ag-react/src/hooks/useWindowSize.ts
475
+ /**
476
+ * Hook to get the current terminal window size.
477
+ * Re-renders when the terminal is resized.
478
+ *
479
+ * Reads from `term.size` (the Size sub-owner) — always returns defined values
480
+ * via the Size owner's default (80x24 for non-TTY streams).
481
+ *
482
+ * @example
483
+ * ```tsx
484
+ * import { useWindowSize, Box, Text } from '@silvery/ag-react'
485
+ *
486
+ * function StatusBar() {
487
+ * const { columns, rows } = useWindowSize()
488
+ * return <Text>{`${columns}x${rows}`}</Text>
489
+ * }
490
+ * ```
491
+ */
492
+ function useWindowSize() {
493
+ return useTerm((t) => ({
494
+ columns: t.size.cols(),
495
+ rows: t.size.rows()
496
+ }), shallow);
497
+ }
498
+ //#endregion
499
+ //#region packages/ag-react/src/ui/image/kitty-graphics.ts
500
+ init_src();
501
+ const APC_START = "\x1B_G";
502
+ const ST$1 = "\x1B\\";
503
+ /** Maximum base64 bytes per chunk (Kitty recommendation) */
504
+ const MAX_CHUNK_SIZE = 4096;
505
+ /**
506
+ * Encode a PNG image into Kitty graphics protocol escape sequences.
507
+ *
508
+ * The image data is base64-encoded and split into chunks of <= 4096 bytes.
509
+ * Each chunk is wrapped in an APC escape sequence. The first chunk carries
510
+ * the image metadata (action, format, dimensions, ID). Subsequent chunks
511
+ * only carry `m=1` or `m=0` to indicate continuation.
512
+ *
513
+ * @param pngData - Raw PNG image data
514
+ * @param opts - Optional dimensions and ID
515
+ * @returns A string containing the complete escape sequence(s)
516
+ *
517
+ * @example
518
+ * ```ts
519
+ * import { readFileSync } from "fs"
520
+ * import { encodeKittyImage } from "@silvery/ag-react"
521
+ *
522
+ * const png = readFileSync("photo.png")
523
+ * const seq = encodeKittyImage(png, { width: 40, height: 20 })
524
+ * process.stdout.write(seq)
525
+ * ```
526
+ */
527
+ function encodeKittyImage(pngData, opts) {
528
+ const chunks = splitIntoChunks(pngData.toString("base64"), MAX_CHUNK_SIZE);
529
+ if (chunks.length === 0) return `${APC_START}${buildParams(opts, 0)};${ST$1}`;
530
+ if (chunks.length === 1) return `${APC_START}${buildParams(opts, 0)};${chunks[0]}${ST$1}`;
531
+ const parts = [];
532
+ parts.push(`${APC_START}${buildParams(opts, 1)};${chunks[0]}${ST$1}`);
533
+ for (let i = 1; i < chunks.length - 1; i++) parts.push(`${APC_START}m=1;${chunks[i]}${ST$1}`);
534
+ parts.push(`${APC_START}m=0;${chunks[chunks.length - 1]}${ST$1}`);
535
+ return parts.join("");
536
+ }
537
+ /**
538
+ * Generate an escape sequence to delete a Kitty image by ID.
539
+ *
540
+ * Uses `a=d` (delete) with `d=i` (delete by image ID). Removes both the
541
+ * stored image bytes AND every placement of it. Use
542
+ * {@link deleteKittyPlacement} to remove a single placement while keeping
543
+ * the image stored for later re-placement.
544
+ *
545
+ * @param id - The image ID to delete
546
+ * @returns The delete escape sequence
547
+ *
548
+ * @example
549
+ * ```ts
550
+ * process.stdout.write(deleteKittyImage(42))
551
+ * ```
552
+ */
553
+ function deleteKittyImage(id) {
554
+ return `${APC_START}a=d,d=i,i=${id},q=2${ST$1}`;
555
+ }
556
+ /**
557
+ * Delete a single placement of a stored image, keeping the image bytes.
558
+ *
559
+ * Uses `a=d` with `d=i` (image id) and `p=` (placement id). The image
560
+ * remains stored on the terminal — re-place via {@link placeKittyImage}
561
+ * without re-transmitting the PNG.
562
+ *
563
+ * @param id - Image ID
564
+ * @param placementId - Placement ID (defaults to 1)
565
+ */
566
+ function deleteKittyPlacement(id, placementId = 1) {
567
+ return `${APC_START}a=d,d=i,i=${id},p=${placementId},q=2${ST$1}`;
568
+ }
569
+ /**
570
+ * Place an already-transmitted image at the current cursor position.
571
+ *
572
+ * Uses `a=p` (place existing image). The image must have been previously
573
+ * transmitted with `transmitOnly: true` (or `a=T` — which transmits AND
574
+ * places, but you can still re-place separately afterwards). This is the
575
+ * fast path for a moving image: transmit the PNG once, then emit a tiny
576
+ * APC packet for each position update — no re-encoding of base64 bytes.
577
+ *
578
+ * Pair with {@link deleteKittyPlacement} to clear the prior placement
579
+ * before placing at a new cursor position. Skipping the delete leaves a
580
+ * stacked copy at the old position.
581
+ *
582
+ * @example
583
+ * ```ts
584
+ * // Transmit once
585
+ * write(encodeKittyImage(png, { id: 42, transmitOnly: true }))
586
+ * // Place at cursor (which the caller positions via CSI ;H)
587
+ * write(placeKittyImage({ id: 42, width: 40, height: 20 }))
588
+ * // Move: clear old placement, position cursor, place again
589
+ * write(deleteKittyPlacement(42))
590
+ * write(`\x1b[10;5H`) // move cursor
591
+ * write(placeKittyImage({ id: 42, width: 40, height: 20 }))
592
+ * ```
593
+ */
594
+ function placeKittyImage(opts) {
595
+ const placementId = opts.placementId ?? 1;
596
+ const parts = [
597
+ `a=p`,
598
+ `i=${opts.id}`,
599
+ `p=${placementId}`,
600
+ `z=${formatIntParam("zIndex", opts.zIndex ?? 1)}`,
601
+ `C=1`,
602
+ `q=2`
603
+ ];
604
+ if (opts.width != null) parts.push(`c=${opts.width}`);
605
+ if (opts.height != null) parts.push(`r=${opts.height}`);
606
+ appendPlacementParams(parts, opts);
607
+ return `${APC_START}${parts.join(",")};${ST$1}`;
608
+ }
609
+ /**
610
+ * Check if the current terminal likely supports the Kitty graphics protocol.
611
+ *
612
+ * Pass a profile or caps fixture when available. Without one, this falls
613
+ * back to {@link createTerminalProfile} — the canonical single-source-of-
614
+ * truth entry point in `@silvery/ansi/profile`. Direct reads of terminal-
615
+ * signal env vars (TERM / TERM_PROGRAM / …) are banned outside that module
616
+ * — see `scripts/lint-env-reads.ts`.
617
+ *
618
+ * For definitive detection, use a terminal query (send the graphics protocol
619
+ * query and check for a response), but that requires async I/O.
620
+ *
621
+ * Known supporting terminals: Kitty, WezTerm, Ghostty (partial), Konsole (partial).
622
+ *
623
+ * @returns `true` if the terminal likely supports Kitty graphics
624
+ */
625
+ function isKittyGraphicsSupported(profile) {
626
+ if (profile === void 0) return createTerminalProfile().caps.kittyGraphics;
627
+ if ("caps" in profile && profile.caps) return profile.caps.kittyGraphics;
628
+ const resolved = isEmulator(profile) ? profile : profile.emulator;
629
+ if (!resolved) return false;
630
+ const term = resolved.TERM;
631
+ const termProgram = resolved.program;
632
+ if (term === "dumb") return false;
633
+ if (term === "xterm-kitty" || termProgram === "kitty") return true;
634
+ if (termProgram === "WezTerm") return true;
635
+ if (termProgram === "ghostty" || termProgram === "Ghostty") return true;
636
+ if (termProgram === "konsole") return true;
637
+ return false;
638
+ }
639
+ function isEmulator(value) {
640
+ if (value === null || typeof value !== "object") return false;
641
+ const maybe = value;
642
+ return typeof maybe.program === "string" && typeof maybe.TERM === "string";
643
+ }
644
+ /**
645
+ * Build the Kitty graphics protocol parameter string for the first chunk.
646
+ *
647
+ * Important: `KittyImageOptions.width` / `.height` are documented as
648
+ * "terminal columns" / "terminal rows" — i.e. CELL counts. The Kitty
649
+ * protocol uses `c=N` / `r=M` for cell-based display sizing, NOT `s=` /
650
+ * `v=` (those are SOURCE PIXEL dimensions, used only for raw RGB
651
+ * uploads with f=24/32). Sending `s=`/`v=` with f=100 (PNG) leaves
652
+ * display sizing to the PNG's native pixel dimensions, which on a
653
+ * 1536×1024 asset blows up to ~192×64 cells and effectively disappears
654
+ * off-screen. Use `c=`/`r=` so the terminal scales the PNG into the
655
+ * reserved cell viewport.
656
+ */
657
+ function buildParams(opts, more) {
658
+ const parts = [
659
+ opts?.transmitOnly ? `a=t` : `a=T`,
660
+ `f=100`,
661
+ `m=${more}`,
662
+ `z=${formatIntParam("zIndex", opts?.zIndex ?? 1)}`,
663
+ `C=1`,
664
+ `q=2`
665
+ ];
666
+ if (opts?.width != null) parts.push(`c=${opts.width}`);
667
+ if (opts?.height != null) parts.push(`r=${opts.height}`);
668
+ if (opts?.id != null) parts.push(`i=${opts.id}`);
669
+ if (opts) appendPlacementParams(parts, opts);
670
+ return parts.join(",");
671
+ }
672
+ function appendPlacementParams(parts, opts) {
673
+ if (opts.pixelOffset?.x != null) parts.push(`X=${formatNonNegativeIntParam("pixelOffset.x", opts.pixelOffset.x)}`);
674
+ if (opts.pixelOffset?.y != null) parts.push(`Y=${formatNonNegativeIntParam("pixelOffset.y", opts.pixelOffset.y)}`);
675
+ if (opts.sourceRect?.x != null) parts.push(`x=${formatNonNegativeIntParam("sourceRect.x", opts.sourceRect.x)}`);
676
+ if (opts.sourceRect?.y != null) parts.push(`y=${formatNonNegativeIntParam("sourceRect.y", opts.sourceRect.y)}`);
677
+ if (opts.sourceRect?.width != null) parts.push(`w=${formatPositiveIntParam("sourceRect.width", opts.sourceRect.width)}`);
678
+ if (opts.sourceRect?.height != null) parts.push(`h=${formatPositiveIntParam("sourceRect.height", opts.sourceRect.height)}`);
679
+ if (opts.virtualPlacement) parts.push("U=1");
680
+ }
681
+ function formatIntParam(name, value) {
682
+ if (!Number.isInteger(value)) throw new Error(`kitty graphics ${name} must be an integer`);
683
+ return value;
684
+ }
685
+ function formatNonNegativeIntParam(name, value) {
686
+ if (!Number.isInteger(value) || value < 0) throw new Error(`kitty graphics ${name} must be a non-negative integer`);
687
+ return value;
688
+ }
689
+ function formatPositiveIntParam(name, value) {
690
+ if (!Number.isInteger(value) || value <= 0) throw new Error(`kitty graphics ${name} must be a positive integer`);
691
+ return value;
692
+ }
693
+ /**
694
+ * Split a string into chunks of at most `size` characters.
695
+ */
696
+ function splitIntoChunks(str, size) {
697
+ if (str.length === 0) return [];
698
+ const chunks = [];
699
+ for (let i = 0; i < str.length; i += size) chunks.push(str.slice(i, i + size));
700
+ return chunks;
701
+ }
702
+ //#endregion
703
+ //#region packages/ag-react/src/ui/image/sixel-encoder.ts
704
+ init_src();
705
+ var import_UPNG = /* @__PURE__ */ __toESM(require_UPNG(), 1);
706
+ const DCS_START = "\x1BP";
707
+ const ST = "\x1B\\";
708
+ /** Sixel introduces a color with `#<index>;2;<r>;<g>;<b>` (RGB percentages 0-100) */
709
+ const SIXEL_NEWLINE = "-";
710
+ /**
711
+ * Decode a PNG buffer into RGBA pixel data suitable for {@link encodeSixel}.
712
+ *
713
+ * The Sixel protocol cannot transmit PNG directly (unlike Kitty's `f=100`),
714
+ * so terminals that fall back to Sixel need raw RGBA pixels. This helper
715
+ * uses upng-js — a small (~30 KB) pure-JS PNG decoder with one tiny
716
+ * dependency (pako) — to bridge PNG → RGBA.
717
+ *
718
+ * Returns `null` if the buffer is not a valid PNG. Callers should fall back
719
+ * to a text placeholder in that case.
720
+ *
721
+ * Supports all PNG flavors UPNG handles: 8/16-bit depth, RGB/RGBA/grayscale/
722
+ * palette, interlaced. Animated PNGs (APNG) collapse to the first frame —
723
+ * Sixel itself is single-frame.
724
+ *
725
+ * @example
726
+ * ```ts
727
+ * import { readFileSync } from "fs"
728
+ * import { decodePngToRgba, encodeSixel } from "./sixel-encoder"
729
+ *
730
+ * const png = readFileSync("photo.png")
731
+ * const rgba = decodePngToRgba(png)
732
+ * if (rgba) {
733
+ * process.stdout.write(encodeSixel(rgba))
734
+ * }
735
+ * ```
736
+ */
737
+ function decodePngToRgba(pngData) {
738
+ try {
739
+ const view = pngData instanceof Uint8Array ? pngData : new Uint8Array(pngData);
740
+ const ab = view.buffer.slice(view.byteOffset, view.byteOffset + view.byteLength);
741
+ const decoded = import_UPNG.default.decode(ab);
742
+ const frames = import_UPNG.default.toRGBA8(decoded);
743
+ if (frames.length === 0) return null;
744
+ return {
745
+ width: decoded.width,
746
+ height: decoded.height,
747
+ data: new Uint8Array(frames[0])
748
+ };
749
+ } catch {
750
+ return null;
751
+ }
752
+ }
753
+ /**
754
+ * Encode RGBA image data as a Sixel escape sequence.
755
+ *
756
+ * This is a basic implementation that:
757
+ * 1. Quantizes colors to a small palette (up to 256 colors)
758
+ * 2. Encodes 6-row bands as Sixel characters
759
+ * 3. Wraps in a DCS escape sequence
760
+ *
761
+ * For transparent pixels (alpha < 128), the background shows through.
762
+ *
763
+ * @param imageData - Image dimensions and RGBA pixel data
764
+ * @returns A DCS escape sequence containing the Sixel-encoded image
765
+ *
766
+ * @example
767
+ * ```ts
768
+ * const img = { width: 10, height: 12, data: new Uint8Array(10 * 12 * 4) }
769
+ * const seq = encodeSixel(img)
770
+ * process.stdout.write(seq)
771
+ * ```
772
+ */
773
+ function encodeSixel(imageData) {
774
+ const { width, height, data } = imageData;
775
+ if (width === 0 || height === 0 || data.length === 0) return `${DCS_START}q${ST}`;
776
+ const palette = /* @__PURE__ */ new Map();
777
+ const pixelColors = new Uint16Array(width * height);
778
+ let nextColorIndex = 1;
779
+ for (let y = 0; y < height; y++) for (let x = 0; x < width; x++) {
780
+ const offset = (y * width + x) * 4;
781
+ const r = data[offset];
782
+ const g = data[offset + 1];
783
+ const b = data[offset + 2];
784
+ if (data[offset + 3] < 128) continue;
785
+ const key = `${r >> 2 & 63},${g >> 2 & 63},${b >> 2 & 63}`;
786
+ let idx = palette.get(key);
787
+ if (idx == null) if (nextColorIndex >= 256) idx = 1;
788
+ else {
789
+ idx = nextColorIndex++;
790
+ palette.set(key, idx);
791
+ }
792
+ pixelColors[y * width + x] = idx;
793
+ }
794
+ const parts = [];
795
+ parts.push(`"1;1;${width};${height}`);
796
+ for (const [key, idx] of palette) {
797
+ const [qr, qg, qb] = key.split(",").map(Number);
798
+ const rPct = Math.round(qr / 63 * 100);
799
+ const gPct = Math.round(qg / 63 * 100);
800
+ const bPct = Math.round(qb / 63 * 100);
801
+ parts.push(`#${idx};2;${rPct};${gPct};${bPct}`);
802
+ }
803
+ for (let bandY = 0; bandY < height; bandY += 6) {
804
+ if (bandY > 0) parts.push(SIXEL_NEWLINE);
805
+ const bandColors = /* @__PURE__ */ new Set();
806
+ for (let dy = 0; dy < 6 && bandY + dy < height; dy++) for (let x = 0; x < width; x++) {
807
+ const ci = pixelColors[(bandY + dy) * width + x];
808
+ if (ci > 0) bandColors.add(ci);
809
+ }
810
+ let first = true;
811
+ for (const colorIdx of bandColors) {
812
+ if (!first) parts.push("$");
813
+ first = false;
814
+ parts.push(`#${colorIdx}`);
815
+ for (let x = 0; x < width; x++) {
816
+ let sixelBits = 0;
817
+ for (let dy = 0; dy < 6; dy++) {
818
+ const y = bandY + dy;
819
+ if (y < height && pixelColors[y * width + x] === colorIdx) sixelBits |= 1 << dy;
820
+ }
821
+ parts.push(String.fromCharCode(sixelBits + 63));
822
+ }
823
+ }
824
+ }
825
+ return `${DCS_START}q${parts.join("")}${ST}`;
826
+ }
827
+ /**
828
+ * Check if the current terminal likely supports the Sixel protocol.
829
+ *
830
+ * Pass `caps` (from `term.caps` or a {@link TerminalCaps} fixture) when
831
+ * available. Without caps, this falls back to {@link createTerminalProfile}
832
+ * — the canonical single-source-of-truth entry point in
833
+ * `@silvery/ansi/profile`. Direct reads of terminal-signal env vars
834
+ * (TERM / TERM_PROGRAM / …) are banned outside that module — see
835
+ * `scripts/lint-env-reads.ts`.
836
+ *
837
+ * For definitive detection, send a DA1 (Device Attributes) query and check
838
+ * for "4" in the response, but that requires async I/O.
839
+ *
840
+ * Known supporting terminals: xterm (with +sixel), mlterm, foot, mintty,
841
+ * WezTerm, Contour, Sixel-enabled builds of various terminals.
842
+ *
843
+ * @returns `true` if the terminal likely supports Sixel
844
+ */
845
+ function isSixelSupported(emulator) {
846
+ const resolved = emulator ?? createTerminalProfile().emulator;
847
+ const term = resolved.TERM;
848
+ const termProgram = resolved.program;
849
+ if (termProgram === "mlterm" || term.startsWith("mlterm")) return true;
850
+ if (termProgram === "foot" || term === "foot" || term === "foot-extra") return true;
851
+ if (termProgram === "WezTerm") return true;
852
+ if (termProgram === "mintty") return true;
853
+ return false;
854
+ }
855
+ //#endregion
856
+ //#region packages/ag-react/src/ui/image/image-placement.ts
857
+ const CURSOR_HIDE = "\x1B[?25l";
858
+ const CURSOR_SHOW = "\x1B[?25h";
859
+ const CURSOR_SAVE = "\x1B7";
860
+ const CURSOR_RESTORE = "\x1B8";
861
+ function withCursorPreserved(seq) {
862
+ return `${CURSOR_HIDE}${CURSOR_SAVE}${seq}${CURSOR_RESTORE}${CURSOR_SHOW}`;
863
+ }
864
+ function computeVisibleImagePlacement({ rect, imagePixels, sourceRect, pixelOffset, viewport }) {
865
+ if (rect.width <= 0 || rect.height <= 0) return null;
866
+ if (rect.x + rect.width <= 0 || rect.y + rect.height <= 0) return null;
867
+ if (viewport && (rect.x >= viewport.width || rect.y >= viewport.height)) return null;
868
+ const leftClip = Math.max(0, -rect.x);
869
+ const topClip = Math.max(0, -rect.y);
870
+ const rightClip = viewport ? Math.max(0, rect.x + rect.width - viewport.width) : 0;
871
+ const bottomClip = viewport ? Math.max(0, rect.y + rect.height - viewport.height) : 0;
872
+ const visibleWidth = rect.width - leftClip - rightClip;
873
+ const visibleHeight = rect.height - topClip - bottomClip;
874
+ if (visibleWidth <= 0 || visibleHeight <= 0) return null;
875
+ const placement = {
876
+ x: Math.max(0, rect.x),
877
+ y: Math.max(0, rect.y),
878
+ width: visibleWidth,
879
+ height: visibleHeight,
880
+ ...pixelOffset ? { pixelOffset } : {}
881
+ };
882
+ if (!imagePixels || topClip === 0 && leftClip === 0 && rightClip === 0 && bottomClip === 0) return sourceRect ? {
883
+ ...placement,
884
+ sourceRect
885
+ } : placement;
886
+ const srcX = sourceRect?.x ?? 0;
887
+ const srcY = sourceRect?.y ?? 0;
888
+ const srcWidth = sourceRect?.width ?? imagePixels.width;
889
+ const srcHeight = sourceRect?.height ?? imagePixels.height;
890
+ const pixelsPerCol = srcWidth / Math.max(1, rect.width);
891
+ const pixelsPerRow = srcHeight / Math.max(1, rect.height);
892
+ return {
893
+ ...placement,
894
+ sourceRect: {
895
+ x: Math.round(srcX + leftClip * pixelsPerCol),
896
+ y: Math.round(srcY + topClip * pixelsPerRow),
897
+ width: Math.max(1, Math.round(visibleWidth * pixelsPerCol)),
898
+ height: Math.max(1, Math.round(visibleHeight * pixelsPerRow))
899
+ }
900
+ };
901
+ }
902
+ function imagePlacementKey({ placementId, zIndex, pixelOffset, sourceRect, virtualPlacement }) {
903
+ return JSON.stringify({
904
+ placementId,
905
+ zIndex,
906
+ pixelOffset,
907
+ sourceRect,
908
+ virtualPlacement
909
+ });
910
+ }
911
+ function planKittyImagePlacement({ rect, imagePixels, sourceRect, pixelOffset, viewport, placementId, zIndex, virtualPlacement, previousPlacement, srcChanged }) {
912
+ const placement = computeVisibleImagePlacement({
913
+ rect,
914
+ imagePixels,
915
+ sourceRect,
916
+ pixelOffset,
917
+ viewport
918
+ });
919
+ if (!placement) return previousPlacement ? { kind: "delete-placement" } : { kind: "noop" };
920
+ const placementKey = imagePlacementKey({
921
+ placementId,
922
+ zIndex,
923
+ pixelOffset: placement.pixelOffset,
924
+ sourceRect: placement.sourceRect,
925
+ virtualPlacement
926
+ });
927
+ if (previousPlacement !== null && previousPlacement.x === placement.x && previousPlacement.y === placement.y && previousPlacement.width === placement.width && previousPlacement.height === placement.height && previousPlacement.placementKey === placementKey && !srcChanged) return { kind: "noop" };
928
+ return {
929
+ kind: "place",
930
+ placement,
931
+ placementKey,
932
+ transmit: srcChanged,
933
+ deleteImageBeforeTransmit: srcChanged && previousPlacement !== null
934
+ };
935
+ }
936
+ //#endregion
937
+ //#region packages/ag-react/src/ui/image/Image.tsx
938
+ /**
939
+ * Image Component
940
+ *
941
+ * Renders bitmap images in supported terminals using the Kitty graphics
942
+ * protocol (primary) or Sixel (fallback). When neither is supported,
943
+ * displays a text placeholder.
944
+ *
945
+ * Since terminal images are escape-sequence-based and don't fit the cell
946
+ * buffer model, the component reserves visual space with a Box of the
947
+ * requested dimensions and queues typed terminal artifacts after layout.
948
+ *
949
+ * @example
950
+ * ```tsx
951
+ * import { readFileSync } from "fs"
952
+ * import { Image } from "@silvery/ag-react"
953
+ *
954
+ * const png = readFileSync("photo.png")
955
+ * <Image src={png} width={40} height={20} />
956
+ *
957
+ * // With file path
958
+ * <Image src="/path/to/image.png" width={40} height={20} />
959
+ *
960
+ * // Auto-detect protocol, fall back to text
961
+ * <Image src={png} width={40} height={20} fallback="[photo]" />
962
+ * ```
963
+ */
964
+ /**
965
+ * Determine the best available image protocol.
966
+ * Returns null if no image protocol is available.
967
+ */
968
+ function detectProtocol(preferred) {
969
+ if (preferred === "kitty") return isKittyGraphicsSupported() ? "kitty" : null;
970
+ if (preferred === "sixel") return isSixelSupported() ? "sixel" : null;
971
+ if (isKittyGraphicsSupported()) return "kitty";
972
+ if (isSixelSupported()) return "sixel";
973
+ return null;
974
+ }
975
+ /** Incrementing image ID counter for Kitty protocol */
976
+ let nextImageId = 1;
977
+ const KITTY_PLACEHOLDER = String.fromCodePoint(1109742);
978
+ const KITTY_PLACEHOLDER_DIACRITICS = [
979
+ 773,
980
+ 781,
981
+ 782,
982
+ 784,
983
+ 786,
984
+ 829,
985
+ 830,
986
+ 831,
987
+ 838,
988
+ 842,
989
+ 843,
990
+ 844,
991
+ 848,
992
+ 849,
993
+ 850,
994
+ 855,
995
+ 859,
996
+ 867,
997
+ 868,
998
+ 869,
999
+ 870,
1000
+ 871,
1001
+ 872,
1002
+ 873,
1003
+ 874,
1004
+ 875,
1005
+ 876,
1006
+ 877,
1007
+ 878,
1008
+ 879,
1009
+ 1155,
1010
+ 1156,
1011
+ 1157,
1012
+ 1158,
1013
+ 1159,
1014
+ 1426,
1015
+ 1427,
1016
+ 1428,
1017
+ 1429,
1018
+ 1431,
1019
+ 1432,
1020
+ 1433,
1021
+ 1436,
1022
+ 1437,
1023
+ 1438,
1024
+ 1439,
1025
+ 1440,
1026
+ 1441,
1027
+ 1448,
1028
+ 1449,
1029
+ 1451,
1030
+ 1452,
1031
+ 1455,
1032
+ 1476,
1033
+ 1552,
1034
+ 1553,
1035
+ 1554,
1036
+ 1555,
1037
+ 1556,
1038
+ 1557,
1039
+ 1558,
1040
+ 1559,
1041
+ 1623,
1042
+ 1624
1043
+ ].map((codepoint) => String.fromCodePoint(codepoint));
1044
+ function canRenderKittyPlaceholder({ id, width, height }) {
1045
+ return id !== null && id > 0 && id <= 255 && width > 0 && height > 0 && width <= KITTY_PLACEHOLDER_DIACRITICS.length && height <= KITTY_PLACEHOLDER_DIACRITICS.length;
1046
+ }
1047
+ function kittyPlaceholderText(width, height) {
1048
+ return Array.from({ length: height }, (_, row) => Array.from({ length: width }, (_, col) => KITTY_PLACEHOLDER + KITTY_PLACEHOLDER_DIACRITICS[row] + KITTY_PLACEHOLDER_DIACRITICS[col]).join("")).join("\n");
1049
+ }
1050
+ /**
1051
+ * Renders a bitmap image in the terminal.
1052
+ *
1053
+ * The component operates in two phases:
1054
+ * 1. **Layout phase**: Renders a Box that reserves the visual space
1055
+ * (filled with spaces so the cell buffer has the right dimensions).
1056
+ * 2. **Commit phase**: Queues terminal image artifacts positioned over the
1057
+ * reserved space.
1058
+ *
1059
+ * When image protocols are not available, the fallback text is shown instead.
1060
+ */
1061
+ function Image({ src, width: requestedWidth, height: requestedHeight, fallback = "[image]", protocol: preferredProtocol = "auto", placementId, zIndex, pixelOffset, sourceRect, virtualPlacement }) {
1062
+ const parentSize = useBoxSize();
1063
+ const effectiveWidth = requestedWidth ?? parentSize.width;
1064
+ const effectiveHeight = requestedHeight ?? Math.max(1, Math.floor(effectiveWidth / 2));
1065
+ return /* @__PURE__ */ jsx(Box, {
1066
+ width: effectiveWidth,
1067
+ height: effectiveHeight,
1068
+ children: /* @__PURE__ */ jsx(ImagePlacement, {
1069
+ src,
1070
+ width: effectiveWidth,
1071
+ height: effectiveHeight,
1072
+ fallback,
1073
+ protocol: preferredProtocol,
1074
+ placementId,
1075
+ zIndex,
1076
+ pixelOffset,
1077
+ sourceRect,
1078
+ virtualPlacement
1079
+ })
1080
+ });
1081
+ }
1082
+ function ImagePlacement({ src, width: effectiveWidth, height: effectiveHeight, fallback, protocol: preferredProtocol, placementId, zIndex, pixelOffset, sourceRect, virtualPlacement }) {
1083
+ const boxRect = useScreenRect();
1084
+ const stdoutCtx = useContext(StdoutContext);
1085
+ const { columns: viewportWidth, rows: viewportHeight } = useWindowSize();
1086
+ const viewport = {
1087
+ width: viewportWidth,
1088
+ height: viewportHeight
1089
+ };
1090
+ const imageIdRef = useRef(null);
1091
+ const transmittedSrcRef = useRef(null);
1092
+ const lastEmittedRef = useRef(null);
1093
+ const pngData = useMemo(() => {
1094
+ if (Buffer.isBuffer(src)) return src;
1095
+ try {
1096
+ return readFileSync(src);
1097
+ } catch {
1098
+ return null;
1099
+ }
1100
+ }, [src]);
1101
+ const decodedImage = useMemo(() => pngData ? decodePngToRgba(pngData) : null, [pngData]);
1102
+ const activeProtocol = useMemo(() => detectProtocol(preferredProtocol), [preferredProtocol]);
1103
+ if (activeProtocol === "kitty" && imageIdRef.current == null) imageIdRef.current = nextImageId++;
1104
+ useLayoutEffect(() => {
1105
+ if (!pngData || !stdoutCtx || !activeProtocol) return;
1106
+ if (effectiveWidth <= 0 || effectiveHeight <= 0) return;
1107
+ if (boxRect.width <= 0) return;
1108
+ const write = (data, artifact) => {
1109
+ if (stdoutCtx.queueFrameArtifact && artifact) {
1110
+ stdoutCtx.queueFrameArtifact({
1111
+ kind: "terminal-sequence",
1112
+ owner: artifact.owner,
1113
+ zIndex: artifact.zIndex,
1114
+ sequence: data
1115
+ });
1116
+ return;
1117
+ }
1118
+ (stdoutCtx.writeAfterFrame ?? stdoutCtx.write)(data);
1119
+ };
1120
+ if (activeProtocol === "kitty") {
1121
+ const id = imageIdRef.current;
1122
+ if (id == null) return;
1123
+ const srcChanged = transmittedSrcRef.current !== pngData;
1124
+ const plan = planKittyImagePlacement({
1125
+ rect: {
1126
+ x: boxRect.x,
1127
+ y: boxRect.y,
1128
+ width: effectiveWidth,
1129
+ height: effectiveHeight
1130
+ },
1131
+ imagePixels: decodedImage,
1132
+ sourceRect,
1133
+ pixelOffset,
1134
+ viewport,
1135
+ placementId,
1136
+ zIndex,
1137
+ virtualPlacement,
1138
+ previousPlacement: lastEmittedRef.current,
1139
+ srcChanged
1140
+ });
1141
+ const placeVisible = (visible, placementKey) => {
1142
+ write(withCursorPreserved(`\x1b[${visible.y + 1};${visible.x + 1}H` + placeKittyImage({
1143
+ id,
1144
+ width: visible.width,
1145
+ height: visible.height,
1146
+ placementId,
1147
+ zIndex,
1148
+ pixelOffset: visible.pixelOffset,
1149
+ sourceRect: visible.sourceRect,
1150
+ virtualPlacement
1151
+ })), {
1152
+ owner: "image:kitty:place",
1153
+ zIndex
1154
+ });
1155
+ lastEmittedRef.current = {
1156
+ x: visible.x,
1157
+ y: visible.y,
1158
+ width: visible.width,
1159
+ height: visible.height,
1160
+ placementKey
1161
+ };
1162
+ };
1163
+ if (plan.kind === "noop") return;
1164
+ if (plan.kind === "delete-placement") {
1165
+ const id = imageIdRef.current;
1166
+ if (id != null) write(withCursorPreserved(deleteKittyPlacement(id, placementId)), {
1167
+ owner: "image:kitty:delete-placement",
1168
+ zIndex
1169
+ });
1170
+ lastEmittedRef.current = null;
1171
+ return;
1172
+ }
1173
+ if (plan.transmit) {
1174
+ if (plan.deleteImageBeforeTransmit) write(deleteKittyImage(id), {
1175
+ owner: "image:kitty:delete-image",
1176
+ zIndex
1177
+ });
1178
+ write(encodeKittyImage(pngData, {
1179
+ id,
1180
+ transmitOnly: true
1181
+ }), {
1182
+ owner: "image:kitty:transmit",
1183
+ zIndex
1184
+ });
1185
+ transmittedSrcRef.current = pngData;
1186
+ }
1187
+ const visible = plan.placement;
1188
+ placeVisible(visible, plan.placementKey);
1189
+ } else if (activeProtocol === "sixel") {
1190
+ const visible = computeVisibleImagePlacement({
1191
+ rect: {
1192
+ x: boxRect.x,
1193
+ y: boxRect.y,
1194
+ width: effectiveWidth,
1195
+ height: effectiveHeight
1196
+ },
1197
+ imagePixels: decodedImage,
1198
+ sourceRect,
1199
+ pixelOffset,
1200
+ viewport
1201
+ });
1202
+ if (!visible) return;
1203
+ const moveCursor = `\x1b[${visible.y + 1};${visible.x + 1}H`;
1204
+ const rgba = decodePngToRgba(pngData);
1205
+ if (rgba) write(withCursorPreserved(moveCursor + encodeSixel(rgba)), {
1206
+ owner: "image:sixel:place",
1207
+ zIndex
1208
+ });
1209
+ }
1210
+ }, [
1211
+ pngData,
1212
+ stdoutCtx,
1213
+ activeProtocol,
1214
+ effectiveWidth,
1215
+ effectiveHeight,
1216
+ placementId,
1217
+ zIndex,
1218
+ pixelOffset,
1219
+ sourceRect,
1220
+ virtualPlacement,
1221
+ boxRect.x,
1222
+ boxRect.y,
1223
+ boxRect.width,
1224
+ boxRect.height,
1225
+ viewportWidth,
1226
+ viewportHeight,
1227
+ decodedImage
1228
+ ]);
1229
+ useEffect(() => {
1230
+ const id = imageIdRef.current;
1231
+ if (activeProtocol !== "kitty" || id == null || !stdoutCtx) return;
1232
+ return () => {
1233
+ stdoutCtx.write(withCursorPreserved(deleteKittyImage(id)));
1234
+ };
1235
+ }, [activeProtocol, stdoutCtx]);
1236
+ if (!activeProtocol || !pngData) return /* @__PURE__ */ jsx(Text, { children: fallback });
1237
+ const placeholderImageId = imageIdRef.current;
1238
+ if (activeProtocol === "kitty" && virtualPlacement && placeholderImageId !== null && canRenderKittyPlaceholder({
1239
+ id: placeholderImageId,
1240
+ width: effectiveWidth,
1241
+ height: effectiveHeight
1242
+ })) return /* @__PURE__ */ jsx(Text, {
1243
+ color: `ansi256(${placeholderImageId})`,
1244
+ children: kittyPlaceholderText(effectiveWidth, effectiveHeight)
1245
+ });
1246
+ const spaceLine = " ".repeat(Math.max(0, effectiveWidth));
1247
+ return /* @__PURE__ */ jsx(Text, { children: Array.from({ length: Math.max(0, effectiveHeight) }, () => spaceLine).join("\n") });
1248
+ }
1249
+ //#endregion
1250
+ export { useScrollRectInFlight as C, useScrollRect as S, useOnBoxRectCommitted as _, deleteKittyImage as a, useScreenRect as b, isKittyGraphicsSupported as c, shallow as d, useTerm as f, useBoxSize as g, useBoxRectInFlight as h, isSixelSupported as i, placeKittyImage as l, useBoxRectDangerously as m, decodePngToRgba as n, deleteKittyPlacement as o, useBoxRect as p, encodeSixel as r, encodeKittyImage as s, Image as t, useWindowSize as u, useOnScreenRectCommitted as v, useScreenRectInFlight as x, useOnScrollRectCommitted as y };
1251
+
1252
+ //# sourceMappingURL=image-C2Birh2x.mjs.map