silvery 0.18.2 → 0.19.1
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/dist/{animation-DhINOJk8.mjs → animation-Cn64yepo.mjs} +1 -1
- package/dist/{animation-DhINOJk8.mjs.map → animation-Cn64yepo.mjs.map} +1 -1
- package/dist/{ansi-C6Qs1Wn2.mjs → ansi-CLOitHKx.mjs} +1 -1
- package/dist/ansi-CLOitHKx.mjs.map +1 -0
- package/dist/{ansi-CsjnZtAw.d.mts → ansi-Cc33mW54.d.mts} +1 -1
- package/dist/{ansi-CsjnZtAw.d.mts.map → ansi-Cc33mW54.d.mts.map} +1 -1
- package/dist/{chunk-BSw8zbkd.mjs → chunk-Vs_PY4HZ.mjs} +1 -1
- package/dist/cli-BKp0YtBD.mjs +4 -0
- package/dist/{context-BjWgrikx.mjs → context-BU5LkkIy.mjs} +8 -7
- package/dist/context-BU5LkkIy.mjs.map +1 -0
- package/dist/devtools-9QY4teqI.mjs +2 -0
- package/dist/{devtools-CeO9X_uv.mjs → devtools-DxkSLXDA.mjs} +4 -5
- package/dist/devtools-DxkSLXDA.mjs.map +1 -0
- package/dist/{eta-BnQSZcWf.mjs → eta-Bb3RH3wh.mjs} +1 -1
- package/dist/{eta-BnQSZcWf.mjs.map → eta-Bb3RH3wh.mjs.map} +1 -1
- package/dist/{flexily-zero-adapter-BOM0cl8R.mjs → flexily-zero-adapter-BlQa46nr.mjs} +21 -64
- package/dist/flexily-zero-adapter-BlQa46nr.mjs.map +1 -0
- package/dist/{flexily-zero-adapter-V8R3HQtK.mjs → flexily-zero-adapter-CMxXhdOL.mjs} +1 -1
- package/dist/{image-B0zMbVUr.mjs → image-CTII5QWI.mjs} +3 -3
- package/dist/image-CTII5QWI.mjs.map +1 -0
- package/dist/{index-Bh3U1K09.d.mts → index-BXslOebb.d.mts} +547 -137
- package/dist/index-BXslOebb.d.mts.map +1 -0
- package/dist/{index-C4vrhbud.d.mts → index-BnA7mNpo.d.mts} +1 -1
- package/dist/{index-C4vrhbud.d.mts.map → index-BnA7mNpo.d.mts.map} +1 -1
- package/dist/index-D3saHouR.d.mts +1392 -0
- package/dist/index-D3saHouR.d.mts.map +1 -0
- package/dist/index.d.mts +5 -33
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +13 -13
- package/dist/{layout-engine--drvrWjD.mjs → layout-engine-B6Cdz1yZ.mjs} +1 -1
- package/dist/{layout-engine-Dr3cY5U4.mjs → layout-engine-ClUgv6jB.mjs} +3 -3
- package/dist/{layout-engine-Dr3cY5U4.mjs.map → layout-engine-ClUgv6jB.mjs.map} +1 -1
- package/dist/{multi-progress-CcdqJFlf.mjs → multi-progress-Bq9Oi_WI.mjs} +3 -3
- package/dist/{multi-progress-CcdqJFlf.mjs.map → multi-progress-Bq9Oi_WI.mjs.map} +1 -1
- package/dist/{multi-progress-DQ-uUzLf.d.mts → multi-progress-DAQC7eap.d.mts} +2 -2
- package/dist/{multi-progress-DQ-uUzLf.d.mts.map → multi-progress-DAQC7eap.d.mts.map} +1 -1
- package/dist/{node-CP5WChgr.mjs → node-BeWlnCPY.mjs} +4 -4
- package/dist/node-BeWlnCPY.mjs.map +1 -0
- package/dist/{progress-bar-IrUjkLfU.mjs → progress-bar-CXE5Qfkd.mjs} +4 -4
- package/dist/progress-bar-CXE5Qfkd.mjs.map +1 -0
- package/dist/reconciler-Cwgm8hRR.mjs +8459 -0
- package/dist/reconciler-Cwgm8hRR.mjs.map +1 -0
- package/dist/{render-string-BwLG7rIX.mjs → render-string-0mN37DLf.mjs} +1 -1
- package/dist/{render-string-DVfgc8xr.mjs → render-string-X-CxpTdZ.mjs} +935 -136
- package/dist/render-string-X-CxpTdZ.mjs.map +1 -0
- package/dist/runtime.d.mts +2 -2
- package/dist/runtime.mjs +3 -3
- package/dist/{spinner-BRkaJI0N.d.mts → spinner-CGo34vyR.d.mts} +2 -2
- package/dist/{spinner-BRkaJI0N.d.mts.map → spinner-CGo34vyR.d.mts.map} +1 -1
- package/dist/{spinner-BmldKx0M.mjs → spinner-CeOmcuw_.mjs} +3 -3
- package/dist/spinner-CeOmcuw_.mjs.map +1 -0
- package/dist/src-B5GjfG7g.mjs +4305 -0
- package/dist/src-B5GjfG7g.mjs.map +1 -0
- package/dist/{src-CJPXf3fC.mjs → src-Bd7ezSgG.mjs} +7560 -6474
- package/dist/src-Bd7ezSgG.mjs.map +1 -0
- package/dist/{src-D8kLrQBT.mjs → src-CChwjk0Z.mjs} +8 -86
- package/dist/src-CChwjk0Z.mjs.map +1 -0
- package/dist/{src-D_BS-as7.mjs → src-NCKb8kE5.mjs} +777 -776
- package/dist/src-NCKb8kE5.mjs.map +1 -0
- package/dist/theme.d.mts +2 -130
- package/dist/theme.mjs +3 -8
- package/dist/{types-B4A8Ebba.d.mts → types-BH_v3iMT.d.mts} +1 -1
- package/dist/{types-B4A8Ebba.d.mts.map → types-BH_v3iMT.d.mts.map} +1 -1
- package/dist/{types-e4dpfbSa.mjs → types-Bk2yw9Qj.mjs} +3 -3
- package/dist/types-Bk2yw9Qj.mjs.map +1 -0
- package/dist/ui/animation.d.mts +1 -1
- package/dist/ui/animation.mjs +1 -1
- package/dist/ui/ansi.d.mts +1 -1
- package/dist/ui/ansi.mjs +1 -1
- package/dist/ui/cli.d.mts +3 -3
- package/dist/ui/cli.mjs +5 -5
- package/dist/ui/display.d.mts +1 -1
- package/dist/ui/display.mjs.map +1 -1
- package/dist/ui/image.d.mts +1 -1
- package/dist/ui/image.mjs +1 -1
- package/dist/ui/input.d.mts +1 -1
- package/dist/ui/input.d.mts.map +1 -1
- package/dist/ui/input.mjs +2 -4
- package/dist/ui/input.mjs.map +1 -1
- package/dist/ui/progress.d.mts +3 -3
- package/dist/ui/progress.d.mts.map +1 -1
- package/dist/ui/progress.mjs +3 -3
- package/dist/ui/progress.mjs.map +1 -1
- package/dist/ui/react.d.mts +1 -1
- package/dist/ui/react.d.mts.map +1 -1
- package/dist/ui/react.mjs +2 -2
- package/dist/ui/react.mjs.map +1 -1
- package/dist/ui/utils.mjs +1 -1
- package/dist/ui/wrappers.d.mts +2 -2
- package/dist/ui/wrappers.mjs +1 -1
- package/dist/ui.d.mts +5 -5
- package/dist/ui.mjs +6 -6
- package/dist/{useLatest-6xqnGIU6.d.mts → useLatest-Bg2x4bfP.d.mts} +1 -1
- package/dist/{useLatest-6xqnGIU6.d.mts.map → useLatest-Bg2x4bfP.d.mts.map} +1 -1
- package/dist/{with-text-input-lUh9gYAG.d.mts → with-text-input-CRfoiFFG.d.mts} +3 -3
- package/dist/with-text-input-CRfoiFFG.d.mts.map +1 -0
- package/dist/{wrappers-JrEYTuKA.mjs → wrappers-UTADQkSY.mjs} +4 -4
- package/dist/wrappers-UTADQkSY.mjs.map +1 -0
- package/dist/{yoga-adapter-Bc8XT9cN.mjs → yoga-adapter-8oRGRw8V.mjs} +2 -2
- package/dist/{yoga-adapter-Bc8XT9cN.mjs.map → yoga-adapter-8oRGRw8V.mjs.map} +1 -1
- package/dist/yoga-adapter-D_CcxSt5.mjs +2 -0
- package/package.json +3 -3
- package/dist/UPNG-DvKjM6wE.mjs +0 -5076
- package/dist/UPNG-DvKjM6wE.mjs.map +0 -1
- package/dist/__vite-browser-external-2447137e-DPKHHqQK.mjs +0 -6
- package/dist/__vite-browser-external-2447137e-DPKHHqQK.mjs.map +0 -1
- package/dist/ansi-C6Qs1Wn2.mjs.map +0 -1
- package/dist/apng-CvSlLBtc.mjs +0 -3
- package/dist/apng-DFFVOItr.mjs +0 -70
- package/dist/apng-DFFVOItr.mjs.map +0 -1
- package/dist/assets/resvgjs.darwin-arm64-BtufyGW1.node +0 -0
- package/dist/backend-DU0Y938U.mjs +0 -13396
- package/dist/backend-DU0Y938U.mjs.map +0 -1
- package/dist/backends-BihMKFY_.mjs +0 -1181
- package/dist/backends-BihMKFY_.mjs.map +0 -1
- package/dist/backends-Dk_5G_gC.mjs +0 -3
- package/dist/cli-GwJ0S2In.mjs +0 -4
- package/dist/context-BjWgrikx.mjs.map +0 -1
- package/dist/derive-O_Kb1Bk_.d.mts +0 -28
- package/dist/derive-O_Kb1Bk_.d.mts.map +0 -1
- package/dist/devtools-CeO9X_uv.mjs.map +0 -1
- package/dist/devtools-nX4tj6OH.mjs +0 -2
- package/dist/flexily-zero-adapter-BOM0cl8R.mjs.map +0 -1
- package/dist/gif-B9Uq4qZA.mjs +0 -73
- package/dist/gif-B9Uq4qZA.mjs.map +0 -1
- package/dist/gif-BdrLRBmM.mjs +0 -3
- package/dist/gifenc-DfhOb4xr.mjs +0 -730
- package/dist/gifenc-DfhOb4xr.mjs.map +0 -1
- package/dist/image-B0zMbVUr.mjs.map +0 -1
- package/dist/index-Bh3U1K09.d.mts.map +0 -1
- package/dist/index-dehZ18K-.d.mts +0 -679
- package/dist/index-dehZ18K-.d.mts.map +0 -1
- package/dist/key-mapping-7k2ufK2b.mjs +0 -3
- package/dist/key-mapping-WLUmxjx1.mjs +0 -132
- package/dist/key-mapping-WLUmxjx1.mjs.map +0 -1
- package/dist/node-CP5WChgr.mjs.map +0 -1
- package/dist/progress-bar-IrUjkLfU.mjs.map +0 -1
- package/dist/reconciler-B8uxQxaU.mjs +0 -16482
- package/dist/reconciler-B8uxQxaU.mjs.map +0 -1
- package/dist/render-string-DVfgc8xr.mjs.map +0 -1
- package/dist/resvg-js-Cwipz-_J.mjs +0 -203
- package/dist/resvg-js-Cwipz-_J.mjs.map +0 -1
- package/dist/spinner-BmldKx0M.mjs.map +0 -1
- package/dist/src-C0sOQW-t.mjs +0 -3866
- package/dist/src-C0sOQW-t.mjs.map +0 -1
- package/dist/src-CJPXf3fC.mjs.map +0 -1
- package/dist/src-D8kLrQBT.mjs.map +0 -1
- package/dist/src-D_BS-as7.mjs.map +0 -1
- package/dist/theme.d.mts.map +0 -1
- package/dist/theme.mjs.map +0 -1
- package/dist/types-e4dpfbSa.mjs.map +0 -1
- package/dist/with-text-input-lUh9gYAG.d.mts.map +0 -1
- package/dist/wrapper-CE6GQ27z.mjs +0 -3527
- package/dist/wrapper-CE6GQ27z.mjs.map +0 -1
- package/dist/wrappers-JrEYTuKA.mjs.map +0 -1
- package/dist/yoga-adapter-B8LZpQcE.mjs +0 -2
|
@@ -1,13 +1,16 @@
|
|
|
1
|
-
import { c as signal, i as syncRectSignals, o as computed, t as rectEqual } from "./types-
|
|
2
|
-
import { c as
|
|
3
|
-
import { At as bufferToText, Dt as TerminalBuffer, Et as DEFAULT_BG, Ft as createTextFrame, H as ensureEmojiPresentation, I as canBreakAnywhere, It as init_buffer, K as graphemeWidth, Lt as isDefaultBg, Ot as ansi256ToRgb, Pt as createMutableCell, U as getActiveLineHeight, V as displayWidthAnsi, _t as wrapText, a as hostConfig, at as parseAnsiText, b as createTerm, c as clearDirtyTracking, d as collectPlainText, et as isWordBoundary, f as advanceRenderEpoch, ft as splitGraphemes, g as isDirty, h as isCurrentEpoch, kt as bufferToStyledText, l as hasScrollDirty, lt as sliceByWidth, m as isAnyDirty, ot as runWithMeasurer, p as getRenderEpoch, pt as splitGraphemesAnsiAware, q as hasAnsi, r as getContainerRoot, t as createContainer, u as measureStats, ut as sliceByWidthFromEnd } from "./reconciler-
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
1
|
+
import { c as signal, i as syncRectSignals, o as computed, t as rectEqual } from "./types-Bk2yw9Qj.mjs";
|
|
2
|
+
import { c as StdoutContext, l as TermContext, s as StderrContext } from "./context-BU5LkkIy.mjs";
|
|
3
|
+
import { At as bufferToText, Dt as TerminalBuffer, Et as DEFAULT_BG, Ft as createTextFrame, H as ensureEmojiPresentation, I as canBreakAnywhere, It as init_buffer, K as graphemeWidth, Lt as isDefaultBg, Ot as ansi256ToRgb, Pt as createMutableCell, U as getActiveLineHeight, V as displayWidthAnsi, Z as isLikelyEmoji, _t as wrapText, a as hostConfig, at as parseAnsiText, b as createTerm, c as clearDirtyTracking, d as collectPlainText, et as isWordBoundary, f as advanceRenderEpoch, ft as splitGraphemes, g as isDirty, h as isCurrentEpoch, kt as bufferToStyledText, l as hasScrollDirty, lt as sliceByWidth, m as isAnyDirty, ot as runWithMeasurer, p as getRenderEpoch, pt as splitGraphemesAnsiAware, q as hasAnsi, r as getContainerRoot, t as createContainer, u as measureStats, ut as sliceByWidthFromEnd } from "./reconciler-Cwgm8hRR.mjs";
|
|
4
|
+
import { A as backdropPlacementId, F as kittyUploadScrimImage, M as cupTo, N as kittyDeleteAllScrimPlacements, P as kittyPlaceAt, j as buildScrimPixels, k as resolveThemeColor, t as init_src, w as monoAttrsForColorString } from "./src-NCKb8kE5.mjs";
|
|
5
|
+
import { i as isLayoutEngineInitialized, r as getLayoutEngine } from "./layout-engine-ClUgv6jB.mjs";
|
|
6
|
+
import { x as ansi16DarkTheme } from "./src-B5GjfG7g.mjs";
|
|
7
7
|
import React, { act } from "react";
|
|
8
8
|
import { createLogger } from "loggily";
|
|
9
|
+
import * as Upstream from "@silvery/color";
|
|
10
|
+
import { relativeLuminance } from "@silvery/color";
|
|
9
11
|
import Reconciler from "react-reconciler";
|
|
10
12
|
//#region packages/ag-term/src/pipeline/prepared-text.ts
|
|
13
|
+
init_buffer();
|
|
11
14
|
const MAX_FORMAT_ENTRIES = 4;
|
|
12
15
|
/** Content-affecting flags that invalidate plain text. */
|
|
13
16
|
const PLAIN_TEXT_DIRTY = 9;
|
|
@@ -29,6 +32,7 @@ function getOrCreate(node) {
|
|
|
29
32
|
plainTextLineCount: 0,
|
|
30
33
|
collected: null,
|
|
31
34
|
collectedMaxDisplayWidth: void 0,
|
|
35
|
+
collectedContextTheme: null,
|
|
32
36
|
formats: [],
|
|
33
37
|
analysis: null
|
|
34
38
|
};
|
|
@@ -61,9 +65,15 @@ function setCachedPlainText(node, text, lineCount) {
|
|
|
61
65
|
}
|
|
62
66
|
/**
|
|
63
67
|
* Get cached collected text (from collectTextWithBg).
|
|
64
|
-
* Invalidated by content, children, style, or bg changes,
|
|
68
|
+
* Invalidated by content, children, style, or bg changes, maxDisplayWidth mismatch,
|
|
69
|
+
* or a context theme change (ancestor ThemeProvider changed token values).
|
|
70
|
+
*
|
|
71
|
+
* @param contextTheme - The active theme at the time of rendering (from getActiveTheme()).
|
|
72
|
+
* Used as a cache key so that when the nearest-ancestor ThemeProvider changes its
|
|
73
|
+
* merged theme, text nodes that embed $token ANSI codes in their collected text
|
|
74
|
+
* are re-collected with the new token values. Pass null when no theme context exists.
|
|
65
75
|
*/
|
|
66
|
-
function getCachedCollectedText(node, maxDisplayWidth) {
|
|
76
|
+
function getCachedCollectedText(node, maxDisplayWidth, contextTheme) {
|
|
67
77
|
if (_cacheDisabled) return null;
|
|
68
78
|
const entry = textCaches.get(node);
|
|
69
79
|
if (!entry?.collected) return null;
|
|
@@ -79,13 +89,20 @@ function getCachedCollectedText(node, maxDisplayWidth) {
|
|
|
79
89
|
entry.analysis = null;
|
|
80
90
|
return null;
|
|
81
91
|
}
|
|
92
|
+
if (entry.collectedContextTheme !== contextTheme) {
|
|
93
|
+
entry.collected = null;
|
|
94
|
+
entry.formats = [];
|
|
95
|
+
entry.analysis = null;
|
|
96
|
+
return null;
|
|
97
|
+
}
|
|
82
98
|
return entry.collected;
|
|
83
99
|
}
|
|
84
100
|
/** Store collected text in cache. */
|
|
85
|
-
function setCachedCollectedText(node, result, maxDisplayWidth) {
|
|
101
|
+
function setCachedCollectedText(node, result, maxDisplayWidth, contextTheme) {
|
|
86
102
|
const entry = getOrCreate(node);
|
|
87
103
|
entry.collected = result;
|
|
88
104
|
entry.collectedMaxDisplayWidth = maxDisplayWidth;
|
|
105
|
+
entry.collectedContextTheme = contextTheme;
|
|
89
106
|
}
|
|
90
107
|
/**
|
|
91
108
|
* Get cached formatted lines for the given width/wrap/trim.
|
|
@@ -851,6 +868,44 @@ function scrollPhase(root, options = {}) {
|
|
|
851
868
|
});
|
|
852
869
|
}
|
|
853
870
|
/**
|
|
871
|
+
* Snap scroll offset so the first visible child (after the top overflow
|
|
872
|
+
* indicator's reserved row) aligns with a child-top boundary.
|
|
873
|
+
*
|
|
874
|
+
* When scrolling "down to show the target at the bottom," the raw offset
|
|
875
|
+
* `target.bottom - effectiveHeight` assumes the entire viewport above the
|
|
876
|
+
* bottom-indicator is usable content. But the TOP overflow indicator also
|
|
877
|
+
* consumes a row when `hiddenAbove > 0`, rendering at viewport row 0 on top
|
|
878
|
+
* of whatever child starts there. If that row is a card's top border, the
|
|
879
|
+
* border is overwritten — users see a "headless" card and perceive the
|
|
880
|
+
* column as "gotten shorter" (see km-tui `column-top-disappears`).
|
|
881
|
+
*
|
|
882
|
+
* This snap shifts the offset so `offset + 1 === firstFullyVisibleChild.top`:
|
|
883
|
+
* the top-indicator row coincides with the 1-row gap ABOVE the first child,
|
|
884
|
+
* not with that child's content. When children have heterogeneous heights,
|
|
885
|
+
* this means moving the viewport DOWN by a few rows (so an earlier, shorter
|
|
886
|
+
* child scrolls fully off-screen and the next child starts cleanly).
|
|
887
|
+
*
|
|
888
|
+
* Guardrails:
|
|
889
|
+
* - Never snap past the target's own top (keeps the target visible).
|
|
890
|
+
* - If no suitable boundary exists above `rawOffset + 1` and ≤ `target.top`,
|
|
891
|
+
* returns `rawOffset` unchanged (scroll behaves as before).
|
|
892
|
+
* - Returns 0 unchanged — offset=0 means no top indicator, no conflict.
|
|
893
|
+
*/
|
|
894
|
+
function snapOffsetToChildTop(rawOffset, childPositions, target) {
|
|
895
|
+
if (rawOffset <= 0) return rawOffset;
|
|
896
|
+
let bestChildTop = -1;
|
|
897
|
+
for (const cp of childPositions) {
|
|
898
|
+
if (cp.isSticky) continue;
|
|
899
|
+
if (cp.top === cp.bottom) continue;
|
|
900
|
+
if (cp.top > rawOffset && cp.top <= target.top) {
|
|
901
|
+
if (bestChildTop === -1 || cp.top < bestChildTop) bestChildTop = cp.top;
|
|
902
|
+
}
|
|
903
|
+
}
|
|
904
|
+
if (bestChildTop === -1) return rawOffset;
|
|
905
|
+
const snapped = bestChildTop - 1;
|
|
906
|
+
return snapped >= rawOffset ? snapped : rawOffset;
|
|
907
|
+
}
|
|
908
|
+
/**
|
|
854
909
|
* Calculate scroll state for a single scrollable container.
|
|
855
910
|
*/
|
|
856
911
|
function calculateScrollState(node, props, skipStateUpdates) {
|
|
@@ -894,8 +949,8 @@ function calculateScrollState(node, props, skipStateUpdates) {
|
|
|
894
949
|
const effectiveHeight = viewportHeight - indicatorReserve;
|
|
895
950
|
const visibleTop = scrollOffset;
|
|
896
951
|
const visibleBottom = scrollOffset + effectiveHeight;
|
|
897
|
-
if (target.top < visibleTop) scrollOffset = target.top;
|
|
898
|
-
else if (target.bottom > visibleBottom) scrollOffset = target.bottom - effectiveHeight;
|
|
952
|
+
if (target.top < visibleTop) scrollOffset = target.top > 0 ? target.top - 1 : 0;
|
|
953
|
+
else if (target.bottom > visibleBottom) scrollOffset = snapOffsetToChildTop(target.bottom - effectiveHeight, childPositions, target);
|
|
899
954
|
}
|
|
900
955
|
}
|
|
901
956
|
scrollOffset = Math.max(0, scrollOffset);
|
|
@@ -1209,10 +1264,66 @@ function detectPipelineFeatures(root) {
|
|
|
1209
1264
|
};
|
|
1210
1265
|
}
|
|
1211
1266
|
//#endregion
|
|
1267
|
+
//#region packages/ag-term/src/pipeline/state.ts
|
|
1268
|
+
/**
|
|
1269
|
+
* Safe fallback theme. Never mutated — the theme flows via the AgNode tree
|
|
1270
|
+
* (Box theme= prop + pushContextTheme/popContextTheme in render-phase.ts).
|
|
1271
|
+
* This is only returned by getActiveTheme() when called from a code path that
|
|
1272
|
+
* has no pushContextTheme frame on the stack, e.g. a bare test that renders
|
|
1273
|
+
* without ThemeProvider.
|
|
1274
|
+
*
|
|
1275
|
+
* `@silvery/theme`'s `ansi16DarkTheme` ships with Sterling flat tokens baked
|
|
1276
|
+
* in, so bare-test render paths resolve `$fg-accent` / `$bg-surface-subtle` /
|
|
1277
|
+
* etc. without needing an explicit ThemeProvider.
|
|
1278
|
+
*/
|
|
1279
|
+
const _activeTheme = ansi16DarkTheme;
|
|
1280
|
+
/** Get the active theme (fallback to ansi16DarkTheme when no context stack entry exists). */
|
|
1281
|
+
function getActiveTheme() {
|
|
1282
|
+
return _contextStack.length > 0 ? _contextStack[_contextStack.length - 1] : _activeTheme;
|
|
1283
|
+
}
|
|
1284
|
+
let _activeColorLevel = "truecolor";
|
|
1285
|
+
/** Set the active color level (called by the runtime based on TerminalCaps). */
|
|
1286
|
+
function setActiveColorLevel(level) {
|
|
1287
|
+
_activeColorLevel = level;
|
|
1288
|
+
}
|
|
1289
|
+
/** Get the active color level (called by parseColor / getTextStyle in render-helpers). */
|
|
1290
|
+
function getActiveColorLevel() {
|
|
1291
|
+
return _activeColorLevel;
|
|
1292
|
+
}
|
|
1293
|
+
/**
|
|
1294
|
+
* Stack of per-subtree theme overrides, pushed/popped during render phase
|
|
1295
|
+
* tree walk. When a Box has a `theme` prop, its theme is pushed before
|
|
1296
|
+
* rendering children and popped after. getActiveTheme() checks this stack
|
|
1297
|
+
* first, falling back to _activeTheme.
|
|
1298
|
+
*
|
|
1299
|
+
* This enables CSS custom property-like cascading: the nearest ancestor
|
|
1300
|
+
* Box with a theme prop determines $token resolution for its subtree.
|
|
1301
|
+
* ThemeProvider (in @silvery/ag-react) renders a <Box theme={merged}>
|
|
1302
|
+
* wrapper, so its theme is naturally pushed via this mechanism.
|
|
1303
|
+
*/
|
|
1304
|
+
const _contextStack = [];
|
|
1305
|
+
/** Push a context theme (called by render phase for Box nodes with theme prop). */
|
|
1306
|
+
function pushContextTheme(theme) {
|
|
1307
|
+
_contextStack.push(theme);
|
|
1308
|
+
}
|
|
1309
|
+
/** Pop a context theme (called by render phase after processing Box subtree). */
|
|
1310
|
+
function popContextTheme() {
|
|
1311
|
+
_contextStack.pop();
|
|
1312
|
+
}
|
|
1313
|
+
//#endregion
|
|
1212
1314
|
//#region packages/ag-term/src/pipeline/render-helpers.ts
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1315
|
+
/**
|
|
1316
|
+
* Render Helpers - Pure utility functions for content rendering.
|
|
1317
|
+
*
|
|
1318
|
+
* Contains:
|
|
1319
|
+
* - Color parsing (parseColor)
|
|
1320
|
+
* - Border character definitions (getBorderChars)
|
|
1321
|
+
* - Style extraction (getTextStyle)
|
|
1322
|
+
* - Text width utilities (getTextWidth)
|
|
1323
|
+
*
|
|
1324
|
+
* Re-exports layout helpers from helpers.ts:
|
|
1325
|
+
* - getPadding, getBorderSize
|
|
1326
|
+
*/
|
|
1216
1327
|
init_src();
|
|
1217
1328
|
const namedColors = {
|
|
1218
1329
|
black: 0,
|
|
@@ -2142,7 +2253,8 @@ function renderText(node, buffer, layout, props, nodeState, inheritedBg, inherit
|
|
|
2142
2253
|
inverse: props.inverse,
|
|
2143
2254
|
strikethrough: props.strikethrough
|
|
2144
2255
|
};
|
|
2145
|
-
const
|
|
2256
|
+
const contextTheme = getActiveTheme();
|
|
2257
|
+
const cachedCollected = getCachedCollectedText(node, maxDisplayWidth, contextTheme);
|
|
2146
2258
|
if (cachedCollected) {
|
|
2147
2259
|
text = cachedCollected.text;
|
|
2148
2260
|
bgSegments = cachedCollected.bgSegments;
|
|
@@ -2152,7 +2264,7 @@ function renderText(node, buffer, layout, props, nodeState, inheritedBg, inherit
|
|
|
2152
2264
|
text = collected.text;
|
|
2153
2265
|
bgSegments = collected.bgSegments;
|
|
2154
2266
|
childSpans = collected.childSpans;
|
|
2155
|
-
setCachedCollectedText(node, collected, maxDisplayWidth);
|
|
2267
|
+
setCachedCollectedText(node, collected, maxDisplayWidth, contextTheme);
|
|
2156
2268
|
}
|
|
2157
2269
|
const style = getTextStyle(props);
|
|
2158
2270
|
if (style.fg === null && inheritedFg !== void 0) style.fg = inheritedFg;
|
|
@@ -2247,12 +2359,20 @@ function computeInlineRects(childSpans, lineOffsets, parentX, parentY, lineCount
|
|
|
2247
2359
|
//#region packages/ag-term/src/pipeline/render-box.ts
|
|
2248
2360
|
/**
|
|
2249
2361
|
* Get the effective background color string for a Box.
|
|
2250
|
-
* Returns explicit backgroundColor if set, otherwise
|
|
2362
|
+
* Returns explicit `backgroundColor` if set, otherwise the Theme's root
|
|
2363
|
+
* surface background — Sterling's `bg-surface-default` if present, falling
|
|
2364
|
+
* back to the legacy `bg` root for any pre-Sterling Theme shape.
|
|
2251
2365
|
* Used by both renderBox (paint fill) and render-phase (cascade logic).
|
|
2252
2366
|
*/
|
|
2253
2367
|
function getEffectiveBg(props) {
|
|
2254
2368
|
if (props.backgroundColor) return props.backgroundColor;
|
|
2255
|
-
if (props.theme)
|
|
2369
|
+
if (props.theme) {
|
|
2370
|
+
const theme = props.theme;
|
|
2371
|
+
const sterlingBg = theme["bg-surface-default"];
|
|
2372
|
+
if (typeof sterlingBg === "string") return sterlingBg;
|
|
2373
|
+
const legacyBg = theme["bg"];
|
|
2374
|
+
if (typeof legacyBg === "string") return legacyBg;
|
|
2375
|
+
}
|
|
2256
2376
|
}
|
|
2257
2377
|
/**
|
|
2258
2378
|
* Render a Box node.
|
|
@@ -2575,7 +2695,8 @@ function walk(node, buffer, scrollOffset, clipBounds, inheritedBg, snapshots) {
|
|
|
2575
2695
|
if (y >= buffer.height || y + layout.height <= 0) return;
|
|
2576
2696
|
const effectiveBg = getEffectiveBg(props);
|
|
2577
2697
|
const theme = props.theme;
|
|
2578
|
-
const
|
|
2698
|
+
const themeBg = theme && typeof theme["bg-surface-default"] === "string" ? theme["bg-surface-default"] : theme && typeof theme["bg"] === "string" ? theme["bg"] : void 0;
|
|
2699
|
+
const childInheritedBg = effectiveBg ? { color: parseColor(effectiveBg) } : themeBg !== void 0 ? { color: parseColor(themeBg) } : inheritedBg;
|
|
2579
2700
|
if (node.type === "silvery-box" && props.outlineStyle) {
|
|
2580
2701
|
const boxInheritedBg = effectiveBg ? void 0 : inheritedBg.color;
|
|
2581
2702
|
const positions = collectOutlineCells(layout.x, y, layout.width, layout.height, props, clipBounds, buffer);
|
|
@@ -2920,7 +3041,6 @@ function getReactiveState(node) {
|
|
|
2920
3041
|
* Region clearing: clearNodeRegion, clearExcessArea, clippedFill
|
|
2921
3042
|
*/
|
|
2922
3043
|
init_buffer();
|
|
2923
|
-
init_state();
|
|
2924
3044
|
const contentLog = createLogger("silvery:content");
|
|
2925
3045
|
const traceLog = createLogger("silvery:content:trace");
|
|
2926
3046
|
const cellLog = createLogger("silvery:content:cell");
|
|
@@ -3459,12 +3579,17 @@ function renderScrollContainerChildren(node, buffer, props, nodeState, contentRe
|
|
|
3459
3579
|
right: 0
|
|
3460
3580
|
};
|
|
3461
3581
|
const padding = getPadding(props);
|
|
3462
|
-
const
|
|
3582
|
+
const viewportClipBounds = computeChildClipBounds(layout, props, clipBounds, 0, false, true);
|
|
3583
|
+
const childClipBounds = props.overflowIndicator === true && !props.borderStyle && (ss.hiddenAbove > 0 || ss.hiddenBelow > 0) ? {
|
|
3584
|
+
...viewportClipBounds,
|
|
3585
|
+
top: ss.hiddenAbove > 0 ? viewportClipBounds.top + 1 : viewportClipBounds.top,
|
|
3586
|
+
bottom: ss.hiddenBelow > 0 ? viewportClipBounds.bottom - 1 : viewportClipBounds.bottom
|
|
3587
|
+
} : viewportClipBounds;
|
|
3463
3588
|
const scrollOffsetChanged = ss.offset !== ss.prevOffset;
|
|
3464
3589
|
const hasStickyChildren = !!(ss.stickyChildren && ss.stickyChildren.length > 0);
|
|
3465
3590
|
const visibleRangeChanged = ss.firstVisibleChild !== ss.prevFirstVisibleChild || ss.lastVisibleChild !== ss.prevLastVisibleChild;
|
|
3466
|
-
const clearY =
|
|
3467
|
-
const clearHeight =
|
|
3591
|
+
const clearY = viewportClipBounds.top;
|
|
3592
|
+
const clearHeight = viewportClipBounds.bottom - viewportClipBounds.top;
|
|
3468
3593
|
const contentX = layout.x + border.left + padding.left;
|
|
3469
3594
|
const contentWidth = layout.width - border.left - border.right - padding.left - padding.right;
|
|
3470
3595
|
const scrollBg = scrollOffsetChanged || isDirty(node.dirtyBits, node.dirtyEpoch, 8) || childrenNeedFreshRender || visibleRangeChanged ? getEffectiveBg(props) ? parseColor(getEffectiveBg(props)) : inheritedBg.color : null;
|
|
@@ -4020,17 +4145,163 @@ function clippedFill(buffer, x, width, top, bottom, clipBounds, outerBottom, bg)
|
|
|
4020
4145
|
});
|
|
4021
4146
|
}
|
|
4022
4147
|
//#endregion
|
|
4023
|
-
//#region packages/ag-term/src/pipeline/backdrop
|
|
4024
|
-
init_src$1();
|
|
4148
|
+
//#region packages/ag-term/src/pipeline/backdrop/color.ts
|
|
4025
4149
|
init_buffer();
|
|
4026
|
-
|
|
4027
|
-
|
|
4150
|
+
/** Convert a buffer Color to a `#rrggbb` hex string, or null if unresolvable. */
|
|
4151
|
+
function colorToHex(color) {
|
|
4152
|
+
if (color === null) return null;
|
|
4153
|
+
if (typeof color === "number") {
|
|
4154
|
+
const rgb = ansi256ToRgb(color);
|
|
4155
|
+
return rgbToHex(rgb.r, rgb.g, rgb.b);
|
|
4156
|
+
}
|
|
4157
|
+
if (isDefaultBg(color)) return null;
|
|
4158
|
+
return rgbToHex(color.r, color.g, color.b);
|
|
4159
|
+
}
|
|
4160
|
+
function rgbToHex(r, g, b) {
|
|
4161
|
+
const clamp = (n) => {
|
|
4162
|
+
return Math.max(0, Math.min(255, Math.round(n))).toString(16).padStart(2, "0");
|
|
4163
|
+
};
|
|
4164
|
+
return `#${clamp(r)}${clamp(g)}${clamp(b)}`;
|
|
4165
|
+
}
|
|
4028
4166
|
/**
|
|
4029
|
-
*
|
|
4167
|
+
* Parse `#rrggbb` or `#rgb` (any case, with or without leading `#`) into
|
|
4168
|
+
* `{ r, g, b }`. Returns null when the input is not a hex color.
|
|
4030
4169
|
*
|
|
4031
|
-
*
|
|
4032
|
-
*
|
|
4170
|
+
* Strict character-class validation — `parseInt("0g", 16)` returns `0`
|
|
4171
|
+
* silently, which would accept malformed hex values. Regex guard rejects
|
|
4172
|
+
* anything outside `[0-9a-f]` regardless of case.
|
|
4033
4173
|
*/
|
|
4174
|
+
function hexToRgb$1(hex) {
|
|
4175
|
+
if (typeof hex !== "string") return null;
|
|
4176
|
+
let s = hex.trim().toLowerCase();
|
|
4177
|
+
if (s.startsWith("#")) s = s.slice(1);
|
|
4178
|
+
if (s.length === 3) {
|
|
4179
|
+
if (!/^[0-9a-f]{3}$/.test(s)) return null;
|
|
4180
|
+
s = s[0] + s[0] + s[1] + s[1] + s[2] + s[2];
|
|
4181
|
+
} else if (!/^[0-9a-f]{6}$/.test(s)) return null;
|
|
4182
|
+
return {
|
|
4183
|
+
r: parseInt(s.slice(0, 2), 16),
|
|
4184
|
+
g: parseInt(s.slice(2, 4), 16),
|
|
4185
|
+
b: parseInt(s.slice(4, 6), 16)
|
|
4186
|
+
};
|
|
4187
|
+
}
|
|
4188
|
+
/**
|
|
4189
|
+
* Normalize any permissible hex input to a canonical `#rrggbb` lowercase
|
|
4190
|
+
* string. Handles `#abc` → `#aabbcc` expansion, case folding, optional
|
|
4191
|
+
* leading `#`, and surrounding whitespace. Returns null when the input is
|
|
4192
|
+
* not a hex color.
|
|
4193
|
+
*
|
|
4194
|
+
* Applied by `buildPlan` to every user-provided color option
|
|
4195
|
+
* (`defaultBg`, `defaultFg`, `scrimColor`) exactly once so downstream
|
|
4196
|
+
* comparisons (`scrim === defaultBg`, etc.) work regardless of input
|
|
4197
|
+
* casing or shorthand.
|
|
4198
|
+
*/
|
|
4199
|
+
function normalizeHex(hex) {
|
|
4200
|
+
if (hex === null || hex === void 0) return null;
|
|
4201
|
+
const rgb = hexToRgb$1(hex);
|
|
4202
|
+
if (!rgb) return null;
|
|
4203
|
+
return rgbToHex(rgb.r, rgb.g, rgb.b);
|
|
4204
|
+
}
|
|
4205
|
+
//#endregion
|
|
4206
|
+
//#region packages/ag-term/src/pipeline/backdrop/plan.ts
|
|
4207
|
+
/**
|
|
4208
|
+
* Backdrop fade — stage 1: build the immutable `Plan`.
|
|
4209
|
+
*
|
|
4210
|
+
* `buildPlan(root, options)` is a PURE, capability-independent pass that
|
|
4211
|
+
* walks the tree, collects `data-backdrop-fade` / `data-backdrop-fade-excluded`
|
|
4212
|
+
* markers, enforces the single-amount invariant, and resolves the scrim +
|
|
4213
|
+
* default colors. The realizers (`./realize-buffer.ts`, `./realize-kitty.ts`)
|
|
4214
|
+
* trust the plan: they do NOT re-walk the tree, re-resolve the scrim, or
|
|
4215
|
+
* re-validate amounts. This module is the single source of truth.
|
|
4216
|
+
*
|
|
4217
|
+
* ## The model: per-channel alpha scrim with perceptually-aware fg
|
|
4218
|
+
*
|
|
4219
|
+
* The pass fades every covered cell by blending BOTH fg AND bg toward a
|
|
4220
|
+
* neutral scrim color at the caller's `amount`. Default scrim: pure black
|
|
4221
|
+
* for dark themes (Apple `colorWithWhite:0.0 alpha:0.4`), pure white for
|
|
4222
|
+
* light. Default amount: `DEFAULT_AMOUNT` (0.25) — calibrated against
|
|
4223
|
+
* macOS 0.20, Material 3 0.32, iOS 0.40, Flutter 0.54.
|
|
4224
|
+
*
|
|
4225
|
+
* ### Two operations, one per channel
|
|
4226
|
+
*
|
|
4227
|
+
* fg' = deemphasizeOklchToward(fg, amount, towardLight)
|
|
4228
|
+
* // OKLCH: L toward 0 or 1,
|
|
4229
|
+
* // C *= (1-α)²
|
|
4230
|
+
* bg' = mixSrgb(bg, scrim, amount) // sRGB source-over alpha
|
|
4231
|
+
*
|
|
4232
|
+
* Fg uses OKLCH deemphasize with explicit polarity so colored text
|
|
4233
|
+
* deemphasizes toward the correct theme neutral — toward black on dark
|
|
4234
|
+
* themes (same formula we've always used), toward white on light themes
|
|
4235
|
+
* (new — see `./color-compat.ts` for the math). The quadratic chroma
|
|
4236
|
+
* falloff compensates for the human-vision nonlinearity that reads chroma
|
|
4237
|
+
* relative to luminance. Bg uses sRGB source-over because the Kitty
|
|
4238
|
+
* graphics scrim overlay composites in sRGB at alpha at the hardware
|
|
4239
|
+
* level.
|
|
4240
|
+
*
|
|
4241
|
+
* ### Uniform amount per channel, heaviness tuned at call site
|
|
4242
|
+
*
|
|
4243
|
+
* Both fg and bg use the same `amount`. An earlier revision halved bg
|
|
4244
|
+
* amount to prevent "scene drowning" — that caused border/panel brightness
|
|
4245
|
+
* inversion (fg-dominated border darkens faster than bg-dominated fill).
|
|
4246
|
+
* Heaviness is controlled by `amount`, not by asymmetric math.
|
|
4247
|
+
*
|
|
4248
|
+
* ## Scrim color
|
|
4249
|
+
*
|
|
4250
|
+
* - Dark themes: pure black (`#000000`) — Apple's modal-sheet dimming color.
|
|
4251
|
+
* - Light themes: pure white (`#ffffff`) — the sign-flipped equivalent.
|
|
4252
|
+
*
|
|
4253
|
+
* Null-bg cells are resolved to `defaultBg` first, then `mixSrgb` toward
|
|
4254
|
+
* the scrim — empty cells darken at the same rate as explicitly-colored ones.
|
|
4255
|
+
*
|
|
4256
|
+
* Tiers (`colorLevel`): a single code path for all supported tiers. For
|
|
4257
|
+
* `"none"` (monochrome) the pass short-circuits to a no-op. For `basic`,
|
|
4258
|
+
* `256`, and `truecolor`, the per-cell operation is identical — the output
|
|
4259
|
+
* phase quantizes the mixed truecolor hex to the tier's palette on emit.
|
|
4260
|
+
*
|
|
4261
|
+
* ## Purity
|
|
4262
|
+
*
|
|
4263
|
+
* This module is pure: no console I/O, no buffer access, no mutable module
|
|
4264
|
+
* state. `buildPlan` returns a `Plan` whose `mixedAmounts` flag signals
|
|
4265
|
+
* multi-amount frames; the orchestrator (`./index.ts`) emits the dev-mode
|
|
4266
|
+
* warning so stage 1 remains a pure function of its inputs.
|
|
4267
|
+
*/
|
|
4268
|
+
/** Marker prop key for include rects (fade cells INSIDE the node's rect). */
|
|
4269
|
+
const BACKDROP_FADE_ATTR = "data-backdrop-fade";
|
|
4270
|
+
/** Marker prop key for exclude rects (fade everything OUTSIDE the node's rect). */
|
|
4271
|
+
const BACKDROP_FADE_EXCLUDE_ATTR = "data-backdrop-fade-excluded";
|
|
4272
|
+
/**
|
|
4273
|
+
* Luminance threshold for dark/light theme detection.
|
|
4274
|
+
*
|
|
4275
|
+
* 0.18 is well below the WCAG midpoint. Standard dark terminal themes
|
|
4276
|
+
* (Catppuccin Mocha bg #1e1e2e, luminance ≈ 0.012; Tokyo Night bg #1a1b26,
|
|
4277
|
+
* ≈ 0.010) are well below. Light themes (GitHub Light #ffffff = 1.0) above.
|
|
4278
|
+
*/
|
|
4279
|
+
const DARK_LUMINANCE_THRESHOLD = .18;
|
|
4280
|
+
/** Canonical scrim colors — Apple's `colorWithWhite:0.0` / `:1.0`. */
|
|
4281
|
+
const DARK_SCRIM = "#000000";
|
|
4282
|
+
const LIGHT_SCRIM = "#ffffff";
|
|
4283
|
+
/**
|
|
4284
|
+
* Default fade amount — the calibrated baseline used when a marker
|
|
4285
|
+
* materializes as a presence attribute (`<Backdrop fade />`,
|
|
4286
|
+
* `data-backdrop-fade=""`, `data-backdrop-fade={true}`) without an explicit
|
|
4287
|
+
* numeric value. Calibrated against macOS 0.20, Material 3 0.32, iOS 0.40,
|
|
4288
|
+
* Flutter 0.54. Re-exported from `index.ts` so downstream callers can
|
|
4289
|
+
* reference the same constant.
|
|
4290
|
+
*/
|
|
4291
|
+
const DEFAULT_AMOUNT = .25;
|
|
4292
|
+
/** Sentinel "nothing to do" plan — reused across frames to avoid allocations. */
|
|
4293
|
+
const INACTIVE_PLAN = Object.freeze({
|
|
4294
|
+
active: false,
|
|
4295
|
+
amount: 0,
|
|
4296
|
+
scrim: null,
|
|
4297
|
+
defaultBg: null,
|
|
4298
|
+
defaultFg: null,
|
|
4299
|
+
includes: Object.freeze([]),
|
|
4300
|
+
excludes: Object.freeze([]),
|
|
4301
|
+
mixedAmounts: false,
|
|
4302
|
+
scrimTowardLight: false,
|
|
4303
|
+
kittyEnabled: false
|
|
4304
|
+
});
|
|
4034
4305
|
/**
|
|
4035
4306
|
* Quick check: does the tree contain any backdrop markers? Used as a gate so
|
|
4036
4307
|
* we don't clone the buffer every frame when no fade is active. Walks the
|
|
@@ -4039,106 +4310,432 @@ const FADE_EXCLUDE_ATTR = "data-backdrop-fade-excluded";
|
|
|
4039
4310
|
*/
|
|
4040
4311
|
function hasBackdropMarkers(root) {
|
|
4041
4312
|
const props = root.props;
|
|
4042
|
-
if (props[
|
|
4313
|
+
if (props["data-backdrop-fade"] !== void 0 || props["data-backdrop-fade-excluded"] !== void 0) return true;
|
|
4043
4314
|
for (const child of root.children) if (hasBackdropMarkers(child)) return true;
|
|
4044
4315
|
return false;
|
|
4045
4316
|
}
|
|
4046
|
-
|
|
4047
|
-
|
|
4048
|
-
|
|
4317
|
+
/**
|
|
4318
|
+
* Stage 1 — build the immutable `Plan`.
|
|
4319
|
+
*
|
|
4320
|
+
* Pure function of `(tree markers, options)`. No buffer access, no Kitty
|
|
4321
|
+
* capability knowledge, no console I/O. The realizers read from the plan
|
|
4322
|
+
* exclusively; the orchestrator (`./index.ts`) handles dev-mode diagnostics
|
|
4323
|
+
* derived from `plan.mixedAmounts`.
|
|
4324
|
+
*
|
|
4325
|
+
* Returns `INACTIVE_PLAN` when:
|
|
4326
|
+
* - `colorLevel === "none"` (monochrome terminal — pass is a no-op).
|
|
4327
|
+
* - The tree has no backdrop markers, OR all markers have `amount <= 0`.
|
|
4328
|
+
*/
|
|
4329
|
+
function buildPlan(root, options) {
|
|
4330
|
+
if ((options?.colorLevel ?? "truecolor") === "none") return INACTIVE_PLAN;
|
|
4049
4331
|
const includes = [];
|
|
4050
4332
|
const excludes = [];
|
|
4051
|
-
|
|
4052
|
-
|
|
4053
|
-
|
|
4054
|
-
|
|
4055
|
-
|
|
4056
|
-
|
|
4057
|
-
|
|
4058
|
-
|
|
4059
|
-
|
|
4060
|
-
|
|
4061
|
-
|
|
4062
|
-
|
|
4063
|
-
|
|
4064
|
-
|
|
4065
|
-
|
|
4066
|
-
|
|
4067
|
-
|
|
4068
|
-
|
|
4333
|
+
const includeAmounts = [];
|
|
4334
|
+
const excludeAmounts = [];
|
|
4335
|
+
collectBackdropMarkers(root, includes, excludes, includeAmounts, excludeAmounts);
|
|
4336
|
+
if (includes.length === 0 && excludes.length === 0) return INACTIVE_PLAN;
|
|
4337
|
+
const defaultBg = normalizeHex(options?.defaultBg ?? null);
|
|
4338
|
+
const scrimColorOpt = options?.scrimColor;
|
|
4339
|
+
const scrim = (typeof scrimColorOpt === "string" && scrimColorOpt !== "auto" ? normalizeHex(scrimColorOpt) : null) ?? deriveAutoScrimColor(defaultBg);
|
|
4340
|
+
const scrimTowardLight = isLightScrim(scrim);
|
|
4341
|
+
const defaultFg = normalizeHex(options?.defaultFg) ?? (scrim === null ? null : scrimTowardLight ? "#000000" : "#ffffff");
|
|
4342
|
+
const { amount, hasMixedAmounts } = assertSingleAmount(includeAmounts, excludeAmounts);
|
|
4343
|
+
return {
|
|
4344
|
+
active: true,
|
|
4345
|
+
amount,
|
|
4346
|
+
scrim,
|
|
4347
|
+
defaultBg,
|
|
4348
|
+
defaultFg,
|
|
4349
|
+
includes,
|
|
4350
|
+
excludes,
|
|
4351
|
+
mixedAmounts: hasMixedAmounts,
|
|
4352
|
+
scrimTowardLight,
|
|
4353
|
+
kittyEnabled: options?.kittyGraphics === true && scrim !== null
|
|
4354
|
+
};
|
|
4355
|
+
}
|
|
4356
|
+
/**
|
|
4357
|
+
* Detect the single-amount invariant across all markers. Returns the
|
|
4358
|
+
* clamped first-observed amount AND a flag indicating whether any later
|
|
4359
|
+
* marker differed. The orchestrator uses the flag to emit a dev-mode warn.
|
|
4360
|
+
*
|
|
4361
|
+
* Mixed amounts currently break the Kitty overlay (one image, one alpha)
|
|
4362
|
+
* and have unclear composition semantics (max? source-over compound?).
|
|
4363
|
+
* Production behavior is first-wins but will look wrong until the markers
|
|
4364
|
+
* are reconciled to a single value.
|
|
4365
|
+
*/
|
|
4366
|
+
function assertSingleAmount(includeAmounts, excludeAmounts) {
|
|
4367
|
+
const first = includeAmounts.length > 0 ? includeAmounts[0] : excludeAmounts.length > 0 ? excludeAmounts[0] : 0;
|
|
4368
|
+
let hasMixedAmounts = false;
|
|
4369
|
+
for (const a of includeAmounts) if (Math.abs(a - first) > 1e-6) {
|
|
4370
|
+
hasMixedAmounts = true;
|
|
4371
|
+
break;
|
|
4372
|
+
}
|
|
4373
|
+
if (!hasMixedAmounts) {
|
|
4374
|
+
for (const a of excludeAmounts) if (Math.abs(a - first) > 1e-6) {
|
|
4375
|
+
hasMixedAmounts = true;
|
|
4376
|
+
break;
|
|
4069
4377
|
}
|
|
4070
4378
|
}
|
|
4071
|
-
return
|
|
4379
|
+
return {
|
|
4380
|
+
amount: Math.max(0, Math.min(1, first)),
|
|
4381
|
+
hasMixedAmounts
|
|
4382
|
+
};
|
|
4383
|
+
}
|
|
4384
|
+
/**
|
|
4385
|
+
* Derive the auto scrim color from a normalized bg hex. Dark themes scrim
|
|
4386
|
+
* toward `DARK_SCRIM`; light themes scrim toward `LIGHT_SCRIM`. Returns
|
|
4387
|
+
* `null` when `bg` is absent or unparseable — signals legacy single-
|
|
4388
|
+
* channel fallback in `fadeCell`.
|
|
4389
|
+
*/
|
|
4390
|
+
function deriveAutoScrimColor(bg) {
|
|
4391
|
+
if (!bg) return null;
|
|
4392
|
+
const lum = relativeLuminance(bg);
|
|
4393
|
+
if (lum === null) return null;
|
|
4394
|
+
return lum < .18 ? DARK_SCRIM : LIGHT_SCRIM;
|
|
4072
4395
|
}
|
|
4073
|
-
|
|
4396
|
+
/**
|
|
4397
|
+
* Polarity detection for an arbitrary scrim color. Returns `true` when the
|
|
4398
|
+
* scrim is on the LIGHT side of the luminance threshold (fg should drift
|
|
4399
|
+
* toward white), `false` otherwise. Uses luminance, not string equality,
|
|
4400
|
+
* so tinted scrims (mid-gray neutrals, etc.) land on the correct branch.
|
|
4401
|
+
* Null scrim defaults to false (dark-theme fallback behavior).
|
|
4402
|
+
*/
|
|
4403
|
+
function isLightScrim(scrim) {
|
|
4404
|
+
if (scrim === null) return false;
|
|
4405
|
+
const lum = relativeLuminance(scrim);
|
|
4406
|
+
if (lum === null) return false;
|
|
4407
|
+
return lum >= DARK_LUMINANCE_THRESHOLD;
|
|
4408
|
+
}
|
|
4409
|
+
function collectBackdropMarkers(node, includes, excludes, includeAmounts, excludeAmounts) {
|
|
4074
4410
|
const props = node.props;
|
|
4075
|
-
const includeRaw = props[
|
|
4076
|
-
const excludeRaw = props[
|
|
4411
|
+
const includeRaw = props[BACKDROP_FADE_ATTR];
|
|
4412
|
+
const excludeRaw = props[BACKDROP_FADE_EXCLUDE_ATTR];
|
|
4077
4413
|
if (includeRaw !== void 0 || excludeRaw !== void 0) {
|
|
4078
4414
|
const rect = node.screenRect ?? node.scrollRect ?? node.boxRect;
|
|
4079
4415
|
if (rect && rect.width > 0 && rect.height > 0) {
|
|
4080
4416
|
const inc = parseFade(includeRaw);
|
|
4081
|
-
if (inc !== null)
|
|
4082
|
-
rect
|
|
4083
|
-
|
|
4084
|
-
}
|
|
4417
|
+
if (inc !== null) {
|
|
4418
|
+
includes.push({ rect });
|
|
4419
|
+
includeAmounts.push(inc);
|
|
4420
|
+
}
|
|
4085
4421
|
const exc = parseFade(excludeRaw);
|
|
4086
|
-
if (exc !== null)
|
|
4087
|
-
rect
|
|
4088
|
-
|
|
4089
|
-
}
|
|
4422
|
+
if (exc !== null) {
|
|
4423
|
+
excludes.push({ rect });
|
|
4424
|
+
excludeAmounts.push(exc);
|
|
4425
|
+
}
|
|
4090
4426
|
}
|
|
4091
4427
|
}
|
|
4092
|
-
for (const child of node.children) collectBackdropMarkers(child, includes, excludes);
|
|
4428
|
+
for (const child of node.children) collectBackdropMarkers(child, includes, excludes, includeAmounts, excludeAmounts);
|
|
4093
4429
|
}
|
|
4430
|
+
/**
|
|
4431
|
+
* Coerce a marker attribute value into a fade amount in (0, 1], or `null`
|
|
4432
|
+
* when the marker is absent / disabled.
|
|
4433
|
+
*
|
|
4434
|
+
* Accepted inputs:
|
|
4435
|
+
*
|
|
4436
|
+
* - `undefined`, `null`, `false` → `null` (marker absent)
|
|
4437
|
+
* - `true` → `DEFAULT_AMOUNT` (presence attribute, e.g. `<Backdrop fade />`)
|
|
4438
|
+
* - `""` → `DEFAULT_AMOUNT` (HTML-attribute presence idiom)
|
|
4439
|
+
* - finite numeric or numeric-string (including in scientific notation):
|
|
4440
|
+
* - `<= 0` → `null` (explicit opt-out)
|
|
4441
|
+
* - `> 1` → `1` (clamped)
|
|
4442
|
+
* - otherwise → the numeric value itself
|
|
4443
|
+
* - any other non-numeric string (e.g. `"bad"`) → `null`
|
|
4444
|
+
*
|
|
4445
|
+
* The presence-attribute idiom lets components emit `data-backdrop-fade`
|
|
4446
|
+
* without threading a numeric value through when the default is fine. The
|
|
4447
|
+
* React `Backdrop.tsx` / `ModalDialog.tsx` today always emit a numeric
|
|
4448
|
+
* attribute, but the semantic is forward-compatible so nothing breaks if
|
|
4449
|
+
* a future component (or a hand-written JSX usage) prefers presence-only.
|
|
4450
|
+
*/
|
|
4094
4451
|
function parseFade(raw) {
|
|
4095
4452
|
if (raw === void 0 || raw === null) return null;
|
|
4453
|
+
if (raw === false) return null;
|
|
4454
|
+
if (raw === true) return DEFAULT_AMOUNT;
|
|
4455
|
+
if (raw === "") return DEFAULT_AMOUNT;
|
|
4096
4456
|
const n = typeof raw === "number" ? raw : Number(raw);
|
|
4097
4457
|
if (!Number.isFinite(n)) return null;
|
|
4098
4458
|
if (n <= 0) return null;
|
|
4099
4459
|
return n > 1 ? 1 : n;
|
|
4100
4460
|
}
|
|
4101
|
-
|
|
4102
|
-
|
|
4103
|
-
|
|
4104
|
-
|
|
4105
|
-
|
|
4106
|
-
|
|
4107
|
-
|
|
4108
|
-
|
|
4109
|
-
|
|
4110
|
-
|
|
4111
|
-
|
|
4112
|
-
|
|
4113
|
-
|
|
4114
|
-
|
|
4115
|
-
|
|
4116
|
-
|
|
4117
|
-
|
|
4118
|
-
|
|
4119
|
-
|
|
4120
|
-
|
|
4121
|
-
|
|
4122
|
-
|
|
4123
|
-
|
|
4124
|
-
|
|
4125
|
-
|
|
4126
|
-
|
|
4461
|
+
//#endregion
|
|
4462
|
+
//#region packages/ag-term/src/pipeline/backdrop/color-compat.ts
|
|
4463
|
+
/**
|
|
4464
|
+
* Backdrop fade — `@silvery/color` compatibility shim.
|
|
4465
|
+
*
|
|
4466
|
+
* Temporary shim while `@silvery/color` lags behind on publish cycles.
|
|
4467
|
+
* `@silvery/color` does export `mixSrgb` and `deemphasize` from source; this
|
|
4468
|
+
* module prefers the upstream versions at runtime and falls back to a
|
|
4469
|
+
* local implementation when an upstream export is missing — e.g., when a
|
|
4470
|
+
* new helper is introduced in the same release cycle as the silvery
|
|
4471
|
+
* package that imports it (the published `@silvery/color` dist doesn't
|
|
4472
|
+
* ship the new name until its next publish, breaking CI verify).
|
|
4473
|
+
*
|
|
4474
|
+
* The fallback implementations are byte-identical to the upstream ones.
|
|
4475
|
+
* Once all downstream consumers of silvery are on a published version of
|
|
4476
|
+
* `@silvery/color` that exports every name we reference, delete the
|
|
4477
|
+
* `local*` fallbacks and collapse each export to a direct re-export.
|
|
4478
|
+
*
|
|
4479
|
+
* Light-theme-aware deemphasize (`deemphasizeOklchToward`) is NOT in
|
|
4480
|
+
* upstream yet — it's only shipped by this module. When it lands in
|
|
4481
|
+
* `@silvery/color`, replace the local implementation with an upstream
|
|
4482
|
+
* re-export behind the same shim.
|
|
4483
|
+
*
|
|
4484
|
+
* @see ./color.ts for hex↔rgb adapter helpers and `HexColor` type.
|
|
4485
|
+
*/
|
|
4486
|
+
/**
|
|
4487
|
+
* sRGB source-over alpha mix. `out = a * (1 - t) + b * t`.
|
|
4488
|
+
*
|
|
4489
|
+
* Prefers `@silvery/color`'s published export; falls back to the local copy
|
|
4490
|
+
* when upstream doesn't ship the name yet. The local implementation matches
|
|
4491
|
+
* `@silvery/color/src/color.ts` byte-for-byte and is safe to use while the
|
|
4492
|
+
* publish train catches up.
|
|
4493
|
+
*/
|
|
4494
|
+
function localMixSrgb(a, b, t) {
|
|
4495
|
+
const ra = hexToRgb$1(a);
|
|
4496
|
+
const rb = hexToRgb$1(b);
|
|
4497
|
+
if (!ra || !rb) return a;
|
|
4498
|
+
const u = Math.max(0, Math.min(1, t));
|
|
4499
|
+
return rgbToHex(ra.r * (1 - u) + rb.r * u, ra.g * (1 - u) + rb.g * u, ra.b * (1 - u) + rb.b * u);
|
|
4500
|
+
}
|
|
4501
|
+
/** sRGB source-over mix. Prefers upstream `@silvery/color`; falls back to the local copy. */
|
|
4502
|
+
const mixSrgb = Upstream.mixSrgb ?? localMixSrgb;
|
|
4503
|
+
/**
|
|
4504
|
+
* OKLCH-native deemphasize that drifts toward EITHER black (dark themes)
|
|
4505
|
+
* OR white (light themes). `towardLight` controls the lightness target;
|
|
4506
|
+
* the chroma falloff is identical in both directions.
|
|
4507
|
+
*
|
|
4508
|
+
* towardLight=false (dark themes):
|
|
4509
|
+
* L' = L × (1 - amount) // linear toward black
|
|
4510
|
+
* towardLight=true (light themes):
|
|
4511
|
+
* L' = L + (1 - L) × amount // linear toward white
|
|
4512
|
+
* (both branches):
|
|
4513
|
+
* C' = C × (1 - amount)² // quadratic chroma falloff
|
|
4514
|
+
* H' = H // hue preserved
|
|
4515
|
+
*
|
|
4516
|
+
* The asymmetric chroma falloff corrects for a perceptual nonlinearity:
|
|
4517
|
+
* the human visual system reads chroma RELATIVE to luminance, so a modest
|
|
4518
|
+
* OKLCH C at extreme L *appears* distinctly more chromatic than the same C
|
|
4519
|
+
* near mid-L. Proportional L+C scaling (`C *= 1-α`, preserving C/L) feels
|
|
4520
|
+
* "more saturated when darkened" to viewers — the exact complaint that
|
|
4521
|
+
* prompted the quadratic version.
|
|
4522
|
+
*
|
|
4523
|
+
* Using `(1-α)²` for chroma reduces saturation faster than lightness on
|
|
4524
|
+
* both polarities:
|
|
4525
|
+
*
|
|
4526
|
+
* α=0.25 → C *= 0.563 (C/L drops to 75% of original)
|
|
4527
|
+
* α=0.40 → C *= 0.360 (C/L drops to 60%)
|
|
4528
|
+
* α=0.50 → C *= 0.250 (C/L drops to 50%)
|
|
4529
|
+
* α=1.00 → C *= 0 (fully faded to the target luminance).
|
|
4530
|
+
*
|
|
4531
|
+
* Light-theme case (towardLight=true): a bright colored text on a light
|
|
4532
|
+
* backdrop is made paler by raising L toward 1 and dropping C — the
|
|
4533
|
+
* symmetric "fade toward the page color" behavior macOS ships in light
|
|
4534
|
+
* mode. Without the polarity flip, the dark-only formula `L *= (1 - α)`
|
|
4535
|
+
* would darken colored text on a light bg, which reads as "text popping"
|
|
4536
|
+
* against the faded scrim rather than receding.
|
|
4537
|
+
*/
|
|
4538
|
+
function localDeemphasizeOklchToward(hex, amount, towardLight) {
|
|
4539
|
+
const o = upstreamHexToOklch(hex);
|
|
4540
|
+
if (!o) return hex;
|
|
4541
|
+
const a = Math.max(0, Math.min(1, amount));
|
|
4542
|
+
const chromaFactor = (1 - a) * (1 - a);
|
|
4543
|
+
const L = towardLight ? o.L + (1 - o.L) * a : o.L * (1 - a);
|
|
4544
|
+
return upstreamOklchToHex({
|
|
4545
|
+
L: Math.max(0, Math.min(1, L)),
|
|
4546
|
+
C: Math.max(0, o.C * chromaFactor),
|
|
4547
|
+
H: o.H
|
|
4548
|
+
});
|
|
4549
|
+
}
|
|
4550
|
+
const upstreamHexToOklch = Upstream.hexToOklch;
|
|
4551
|
+
const upstreamOklchToHex = Upstream.oklchToHex;
|
|
4552
|
+
const deemphasizeOklchToward = Upstream.deemphasizeOklchToward ?? localDeemphasizeOklchToward;
|
|
4553
|
+
//#endregion
|
|
4554
|
+
//#region packages/ag-term/src/pipeline/backdrop/region.ts
|
|
4555
|
+
/**
|
|
4556
|
+
* Walk every cell covered by the plan's include and exclude rects and
|
|
4557
|
+
* invoke `visit(x, y)` for each unique cell.
|
|
4558
|
+
*
|
|
4559
|
+
* `includes` cells are those INSIDE any include rect.
|
|
4560
|
+
* `excludes` cells are those OUTSIDE any exclude rect (i.e., excluded from
|
|
4561
|
+
* the exclude's interior — the modal "cuts a hole" pattern).
|
|
4562
|
+
*
|
|
4563
|
+
* Rects are clipped to the buffer bounds (`[0, bufferWidth)` ×
|
|
4564
|
+
* `[0, bufferHeight)`). Zero-size rects are skipped. Cells are deduped
|
|
4565
|
+
* across all rects via a `Uint8Array` bitset — a cell belonging to two
|
|
4566
|
+
* overlapping includes is visited once, not twice.
|
|
4567
|
+
*
|
|
4568
|
+
* Returns the count of unique cells visited. Useful for short-circuiting
|
|
4569
|
+
* the "was any cell modified?" signal in realizers.
|
|
4570
|
+
*/
|
|
4571
|
+
function forEachFadeRegionCell(bufferWidth, bufferHeight, includes, excludes, visit) {
|
|
4572
|
+
if (bufferWidth <= 0 || bufferHeight <= 0) return 0;
|
|
4573
|
+
if (includes.length === 0 && excludes.length === 0) return 0;
|
|
4574
|
+
const seen = new Uint8Array(bufferWidth * bufferHeight);
|
|
4575
|
+
let count = 0;
|
|
4576
|
+
const once = (x, y) => {
|
|
4577
|
+
const i = y * bufferWidth + x;
|
|
4578
|
+
if (seen[i] !== 0) return;
|
|
4579
|
+
seen[i] = 1;
|
|
4580
|
+
count += 1;
|
|
4581
|
+
visit(x, y);
|
|
4582
|
+
};
|
|
4583
|
+
for (const { rect } of includes) {
|
|
4584
|
+
const x0 = Math.max(0, rect.x);
|
|
4585
|
+
const y0 = Math.max(0, rect.y);
|
|
4586
|
+
const x1 = Math.min(bufferWidth, rect.x + rect.width);
|
|
4587
|
+
const y1 = Math.min(bufferHeight, rect.y + rect.height);
|
|
4588
|
+
if (x0 >= x1 || y0 >= y1) continue;
|
|
4589
|
+
for (let y = y0; y < y1; y++) for (let x = x0; x < x1; x++) once(x, y);
|
|
4590
|
+
}
|
|
4591
|
+
if (excludes.length > 0) {
|
|
4592
|
+
const clipped = [];
|
|
4593
|
+
for (const { rect } of excludes) {
|
|
4594
|
+
const x0 = Math.max(0, rect.x);
|
|
4595
|
+
const y0 = Math.max(0, rect.y);
|
|
4596
|
+
const x1 = Math.min(bufferWidth, rect.x + rect.width);
|
|
4597
|
+
const y1 = Math.min(bufferHeight, rect.y + rect.height);
|
|
4598
|
+
if (x0 < x1 && y0 < y1) clipped.push({
|
|
4599
|
+
x0,
|
|
4600
|
+
y0,
|
|
4601
|
+
x1,
|
|
4602
|
+
y1
|
|
4603
|
+
});
|
|
4604
|
+
}
|
|
4605
|
+
if (clipped.length > 0) for (let y = 0; y < bufferHeight; y++) for (let x = 0; x < bufferWidth; x++) {
|
|
4606
|
+
let insideAnyExclude = false;
|
|
4607
|
+
for (const r of clipped) if (x >= r.x0 && x < r.x1 && y >= r.y0 && y < r.y1) {
|
|
4608
|
+
insideAnyExclude = true;
|
|
4609
|
+
break;
|
|
4610
|
+
}
|
|
4611
|
+
if (!insideAnyExclude) once(x, y);
|
|
4612
|
+
}
|
|
4613
|
+
else for (let y = 0; y < bufferHeight; y++) for (let x = 0; x < bufferWidth; x++) once(x, y);
|
|
4614
|
+
}
|
|
4615
|
+
return count;
|
|
4616
|
+
}
|
|
4617
|
+
//#endregion
|
|
4618
|
+
//#region packages/ag-term/src/pipeline/backdrop/realize-buffer.ts
|
|
4619
|
+
/**
|
|
4620
|
+
* Stage 2a — apply the plan's cell-level transform to the buffer.
|
|
4621
|
+
*
|
|
4622
|
+
* Walks every include + exclude cell once via `forEachFadeRegionCell` and
|
|
4623
|
+
* applies `fadeCell` with the plan's single `amount`. The buffer is mutated
|
|
4624
|
+
* in place.
|
|
4625
|
+
*
|
|
4626
|
+
* When `plan.kittyEnabled === true`, emoji cells (detected via
|
|
4627
|
+
* `isLikelyEmoji(cell.char)`) are SKIPPED — the Kitty overlay realizer
|
|
4628
|
+
* composites the scrim on top of the unmixed cell. When
|
|
4629
|
+
* `plan.kittyEnabled === false`, emoji cells go through the per-cell mix
|
|
4630
|
+
* AND get SGR 2 (`attrs.dim`) stamped on lead + continuation.
|
|
4631
|
+
*
|
|
4632
|
+
* Returns `true` when at least one buffer cell was mutated.
|
|
4633
|
+
*/
|
|
4634
|
+
function realizeToBuffer(plan, buffer) {
|
|
4635
|
+
if (!plan.active) return false;
|
|
4636
|
+
if (plan.amount <= 0) return false;
|
|
4637
|
+
let modified = false;
|
|
4638
|
+
forEachFadeRegionCell(buffer.width, buffer.height, plan.includes, plan.excludes, (x, y) => {
|
|
4639
|
+
if (fadeCell(buffer, x, y, plan)) modified = true;
|
|
4640
|
+
});
|
|
4641
|
+
return modified;
|
|
4127
4642
|
}
|
|
4128
4643
|
/**
|
|
4129
4644
|
* Fade a single cell. Returns true if the cell was modified.
|
|
4130
4645
|
*
|
|
4131
|
-
* -
|
|
4132
|
-
*
|
|
4133
|
-
*
|
|
4646
|
+
* Two-channel transform (see `./plan.ts` for the full color model):
|
|
4647
|
+
*
|
|
4648
|
+
* fg' = deemphasizeOklchToward(fg, amount, scrimTowardLight)
|
|
4649
|
+
* bg' = mixSrgb(bg, scrim, amount)
|
|
4134
4650
|
*
|
|
4135
|
-
*
|
|
4136
|
-
*
|
|
4651
|
+
* Fg uses OKLCH deemphasize (not sRGB mixing) so colored text deemphasizes
|
|
4652
|
+
* perceptually — pale lavender becomes dull slate on dark themes, pale
|
|
4653
|
+
* grey on light themes. The polarity flag `scrimTowardLight` (from the
|
|
4654
|
+
* plan) steers L toward 0 or 1; chroma falloff is symmetric. Bg uses sRGB
|
|
4655
|
+
* source-over because the Kitty graphics scrim overlay composites in sRGB
|
|
4656
|
+
* at alpha at the hardware level.
|
|
4657
|
+
*
|
|
4658
|
+
* `null`/`DEFAULT_BG` cells are resolved to `plan.defaultBg` first (that
|
|
4659
|
+
* IS the color the terminal paints), then mixed toward the scrim — so
|
|
4660
|
+
* empty cells darken at the same rate as explicitly-colored cells.
|
|
4661
|
+
*
|
|
4662
|
+
* Uniform amounts for fg + bg preserve relative brightness ordering across
|
|
4663
|
+
* borders vs fills. Heaviness is controlled by `plan.amount` (default
|
|
4664
|
+
* 0.25, calibrated against macOS 0.20, Material 3 0.32, iOS 0.40, Flutter
|
|
4665
|
+
* 0.54).
|
|
4666
|
+
*
|
|
4667
|
+
* The `scrim !== null` gate activates the full two-channel path: fg always
|
|
4668
|
+
* deemphasizes, and bg mixes toward the scrim when a resolvable bg hex is
|
|
4669
|
+
* available (`cell.bg` non-null OR `defaultBg` non-null). When both
|
|
4670
|
+
* `scrim` and a resolvable bg are null (no theme context at all): falls
|
|
4671
|
+
* back to mixing fg toward `cell.bg` so the cell still reads as "receded"
|
|
4672
|
+
* without needing external theme info.
|
|
4673
|
+
*
|
|
4674
|
+
* ### Wide-char / emoji handling
|
|
4675
|
+
*
|
|
4676
|
+
* Terminals render emoji using the glyph's own bitmap colors — the fg mix
|
|
4677
|
+
* has no visible effect on the emoji glyph. Two paths, mutually exclusive:
|
|
4678
|
+
*
|
|
4679
|
+
* 1. Kitty graphics available: `fadeCell` SKIPS emoji wide cells entirely.
|
|
4680
|
+
* The Kitty overlay composites the scrim at alpha=amount on top, landing
|
|
4681
|
+
* at `cell * (1 - amount) + scrim * amount` — same as surrounding cells.
|
|
4682
|
+
* 2. Kitty unavailable: mix the cell bg + stamp `attrs.dim` on lead +
|
|
4683
|
+
* continuation. Terminals honoring SGR 2 on emoji fade the glyph. Wide
|
|
4684
|
+
* TEXT (CJK etc.) goes through the normal deemphasize path on both
|
|
4685
|
+
* branches — the fg mix works fine and SGR 2 on CJK over-fades.
|
|
4137
4686
|
*/
|
|
4138
|
-
function fadeCell(buffer, x, y,
|
|
4687
|
+
function fadeCell(buffer, x, y, plan) {
|
|
4139
4688
|
if (buffer.isCellContinuation(x, y)) return false;
|
|
4140
4689
|
const cell = buffer.getCell(x, y);
|
|
4141
|
-
|
|
4690
|
+
const isEmojiGlyph = cell.wide && isLikelyEmoji(cell.char ?? "");
|
|
4691
|
+
if (plan.kittyEnabled && isEmojiGlyph) return false;
|
|
4692
|
+
const { amount, scrim, defaultBg, defaultFg, scrimTowardLight } = plan;
|
|
4693
|
+
const rawFgHex = colorToHex(cell.fg);
|
|
4694
|
+
if (scrim !== null) {
|
|
4695
|
+
const fgHex = rawFgHex ?? defaultFg ?? (scrimTowardLight ? "#000000" : "#ffffff");
|
|
4696
|
+
const bgHex = colorToHex(cell.bg) ?? defaultBg;
|
|
4697
|
+
const mixedBgHex = bgHex !== null ? mixSrgb(bgHex, scrim, amount) : null;
|
|
4698
|
+
const mixedBg = mixedBgHex !== null ? hexToRgb$1(mixedBgHex) : null;
|
|
4699
|
+
const stampEmojiDim = isEmojiGlyph;
|
|
4700
|
+
const newAttrs = stampEmojiDim && !cell.attrs.dim ? {
|
|
4701
|
+
...cell.attrs,
|
|
4702
|
+
dim: true
|
|
4703
|
+
} : cell.attrs;
|
|
4704
|
+
const mixedFg = hexToRgb$1(deemphasizeOklchToward(fgHex, amount, scrimTowardLight));
|
|
4705
|
+
if (mixedFg) {
|
|
4706
|
+
if (mixedBg) {
|
|
4707
|
+
buffer.setCell(x, y, {
|
|
4708
|
+
...cell,
|
|
4709
|
+
fg: mixedFg,
|
|
4710
|
+
bg: mixedBg,
|
|
4711
|
+
attrs: newAttrs
|
|
4712
|
+
});
|
|
4713
|
+
propagateToContinuation(buffer, cell, x, y, {
|
|
4714
|
+
bg: mixedBg,
|
|
4715
|
+
dim: stampEmojiDim
|
|
4716
|
+
});
|
|
4717
|
+
return true;
|
|
4718
|
+
}
|
|
4719
|
+
buffer.setCell(x, y, {
|
|
4720
|
+
...cell,
|
|
4721
|
+
fg: mixedFg,
|
|
4722
|
+
attrs: newAttrs
|
|
4723
|
+
});
|
|
4724
|
+
if (stampEmojiDim) propagateToContinuation(buffer, cell, x, y, { dim: true });
|
|
4725
|
+
return true;
|
|
4726
|
+
}
|
|
4727
|
+
if (mixedBg) {
|
|
4728
|
+
buffer.setCell(x, y, {
|
|
4729
|
+
...cell,
|
|
4730
|
+
bg: mixedBg,
|
|
4731
|
+
attrs: newAttrs
|
|
4732
|
+
});
|
|
4733
|
+
propagateToContinuation(buffer, cell, x, y, {
|
|
4734
|
+
bg: mixedBg,
|
|
4735
|
+
dim: stampEmojiDim
|
|
4736
|
+
});
|
|
4737
|
+
return true;
|
|
4738
|
+
}
|
|
4142
4739
|
if (cell.attrs.dim) return false;
|
|
4143
4740
|
buffer.setCell(x, y, {
|
|
4144
4741
|
...cell,
|
|
@@ -4149,14 +4746,14 @@ function fadeCell(buffer, x, y, amount, strategy) {
|
|
|
4149
4746
|
});
|
|
4150
4747
|
return true;
|
|
4151
4748
|
}
|
|
4152
|
-
const fgHex =
|
|
4749
|
+
const fgHex = rawFgHex;
|
|
4153
4750
|
const bgHex = colorToHex(cell.bg);
|
|
4154
4751
|
if (fgHex && bgHex) {
|
|
4155
|
-
const
|
|
4156
|
-
if (!
|
|
4752
|
+
const mixedRgb = hexToRgb$1(mixSrgb(fgHex, bgHex, amount));
|
|
4753
|
+
if (!mixedRgb) return false;
|
|
4157
4754
|
buffer.setCell(x, y, {
|
|
4158
4755
|
...cell,
|
|
4159
|
-
fg:
|
|
4756
|
+
fg: mixedRgb
|
|
4160
4757
|
});
|
|
4161
4758
|
return true;
|
|
4162
4759
|
}
|
|
@@ -4168,42 +4765,167 @@ function fadeCell(buffer, x, y, amount, strategy) {
|
|
|
4168
4765
|
dim: true
|
|
4169
4766
|
}
|
|
4170
4767
|
});
|
|
4768
|
+
if (cell.wide && x + 1 < buffer.width) {
|
|
4769
|
+
const cont = buffer.getCell(x + 1, y);
|
|
4770
|
+
if (!cont.attrs.dim) buffer.setCell(x + 1, y, {
|
|
4771
|
+
...cont,
|
|
4772
|
+
attrs: {
|
|
4773
|
+
...cont.attrs,
|
|
4774
|
+
dim: true
|
|
4775
|
+
}
|
|
4776
|
+
});
|
|
4777
|
+
}
|
|
4171
4778
|
return true;
|
|
4172
4779
|
}
|
|
4173
|
-
/**
|
|
4174
|
-
|
|
4175
|
-
|
|
4176
|
-
|
|
4177
|
-
|
|
4178
|
-
|
|
4179
|
-
|
|
4180
|
-
|
|
4181
|
-
|
|
4780
|
+
/**
|
|
4781
|
+
* Propagate lead-cell updates to the continuation cell of a wide char.
|
|
4782
|
+
*
|
|
4783
|
+
* When a wide char (emoji, CJK) has its bg or dim attribute changed on the
|
|
4784
|
+
* lead cell, the continuation cell at `x+1` must track in lockstep or the
|
|
4785
|
+
* two halves of the glyph render inconsistently (different bg → visually
|
|
4786
|
+
* split glyph; missing dim → half-faded emoji).
|
|
4787
|
+
*
|
|
4788
|
+
* `patch.bg` copies the mixed bg onto the continuation. `patch.dim` stamps
|
|
4789
|
+
* `attrs.dim`. Either or both may be provided; the function is a no-op
|
|
4790
|
+
* when neither is set.
|
|
4791
|
+
*/
|
|
4792
|
+
function propagateToContinuation(buffer, leadCell, x, y, patch) {
|
|
4793
|
+
if (!leadCell.wide) return;
|
|
4794
|
+
if (x + 1 >= buffer.width) return;
|
|
4795
|
+
const cont = buffer.getCell(x + 1, y);
|
|
4796
|
+
if (!cont.continuation) return;
|
|
4797
|
+
const stampDim = patch.dim === true && !cont.attrs.dim;
|
|
4798
|
+
const writeBg = patch.bg !== void 0;
|
|
4799
|
+
if (!stampDim && !writeBg) return;
|
|
4800
|
+
const attrs = stampDim ? {
|
|
4801
|
+
...cont.attrs,
|
|
4802
|
+
dim: true
|
|
4803
|
+
} : cont.attrs;
|
|
4804
|
+
if (writeBg) buffer.setCell(x + 1, y, {
|
|
4805
|
+
...cont,
|
|
4806
|
+
bg: patch.bg,
|
|
4807
|
+
attrs
|
|
4808
|
+
});
|
|
4809
|
+
else buffer.setCell(x + 1, y, {
|
|
4810
|
+
...cont,
|
|
4811
|
+
attrs
|
|
4812
|
+
});
|
|
4182
4813
|
}
|
|
4183
|
-
|
|
4184
|
-
|
|
4185
|
-
|
|
4814
|
+
//#endregion
|
|
4815
|
+
//#region packages/ag-term/src/pipeline/backdrop/realize-kitty.ts
|
|
4816
|
+
init_src();
|
|
4817
|
+
/**
|
|
4818
|
+
* Stage 2b — emit the Kitty graphics overlay for the plan.
|
|
4819
|
+
*
|
|
4820
|
+
* The output always begins with `CURSOR_SAVE + kittyDeleteAllScrimPlacements()
|
|
4821
|
+
* + CURSOR_RESTORE` when `plan.active` is true — even when zero emoji cells
|
|
4822
|
+
* fall inside the faded region this frame. The unconditional clear is what
|
|
4823
|
+
* erases stale placements from a previous frame.
|
|
4824
|
+
*
|
|
4825
|
+
* Returns `""` when `plan.active` is false. Callers that also need to
|
|
4826
|
+
* suppress the overlay because Kitty graphics are not available should NOT
|
|
4827
|
+
* call this function at all — the orchestrator (`./index.ts`) guards the
|
|
4828
|
+
* call site with `plan.kittyEnabled`.
|
|
4829
|
+
*/
|
|
4830
|
+
function realizeToKitty(plan, buffer) {
|
|
4831
|
+
if (!plan.active) return "";
|
|
4832
|
+
const cells = collectEmojiCellsInFadeRegion(buffer, plan);
|
|
4833
|
+
const tint = hexToRgb$1(plan.scrim ?? plan.defaultBg ?? "#000000") ?? {
|
|
4834
|
+
r: 0,
|
|
4835
|
+
g: 0,
|
|
4836
|
+
b: 0
|
|
4186
4837
|
};
|
|
4187
|
-
|
|
4838
|
+
const scrimAlpha = Math.max(0, Math.min(255, Math.round(plan.amount * 255)));
|
|
4839
|
+
const parts = [];
|
|
4840
|
+
parts.push("\x1B7");
|
|
4841
|
+
if (cells.length === 0) {
|
|
4842
|
+
parts.push(kittyDeleteAllScrimPlacements());
|
|
4843
|
+
parts.push("\x1B8");
|
|
4844
|
+
return parts.join("");
|
|
4845
|
+
}
|
|
4846
|
+
const pixels = buildScrimPixels(tint, scrimAlpha);
|
|
4847
|
+
parts.push(kittyUploadScrimImage(pixels, 2, 2));
|
|
4848
|
+
parts.push(kittyDeleteAllScrimPlacements());
|
|
4849
|
+
for (const { x, y } of cells) {
|
|
4850
|
+
parts.push(cupTo(x, y));
|
|
4851
|
+
parts.push(kittyPlaceAt({
|
|
4852
|
+
placementId: backdropPlacementId(x, y),
|
|
4853
|
+
cols: 2,
|
|
4854
|
+
rows: 1,
|
|
4855
|
+
z: 1
|
|
4856
|
+
}));
|
|
4857
|
+
}
|
|
4858
|
+
parts.push("\x1B8");
|
|
4859
|
+
return parts.join("");
|
|
4188
4860
|
}
|
|
4189
|
-
|
|
4190
|
-
|
|
4191
|
-
|
|
4192
|
-
|
|
4193
|
-
|
|
4194
|
-
|
|
4195
|
-
|
|
4196
|
-
|
|
4197
|
-
|
|
4198
|
-
|
|
4861
|
+
/**
|
|
4862
|
+
* Collect the coordinates of every EMOJI lead cell inside the plan's faded
|
|
4863
|
+
* region. CJK and other wide TEXT cells are excluded — they respond to fg
|
|
4864
|
+
* color mixing like normal text and don't need the Kitty overlay. Only
|
|
4865
|
+
* bitmap-glyph cells (detected via `isLikelyEmoji(cell.char)`) need an
|
|
4866
|
+
* overlay because their rendering ignores the fg color.
|
|
4867
|
+
*
|
|
4868
|
+
* The iteration order is deterministic (delegated to
|
|
4869
|
+
* `forEachFadeRegionCell`), matching the buffer realizer's order. STRICT
|
|
4870
|
+
* mode compares the overlay string across fresh and incremental paths —
|
|
4871
|
+
* any drift in this order would fail the comparison.
|
|
4872
|
+
*/
|
|
4873
|
+
function collectEmojiCellsInFadeRegion(buffer, plan) {
|
|
4874
|
+
const out = [];
|
|
4875
|
+
forEachFadeRegionCell(buffer.width, buffer.height, plan.includes, plan.excludes, (x, y) => {
|
|
4876
|
+
if (x + 1 >= buffer.width) return;
|
|
4877
|
+
if (!buffer.isCellWide(x, y)) return;
|
|
4878
|
+
if (buffer.isCellContinuation(x, y)) return;
|
|
4879
|
+
if (!isLikelyEmoji(buffer.getCell(x, y).char ?? "")) return;
|
|
4880
|
+
out.push({
|
|
4881
|
+
x,
|
|
4882
|
+
y
|
|
4883
|
+
});
|
|
4884
|
+
});
|
|
4885
|
+
return out;
|
|
4886
|
+
}
|
|
4887
|
+
//#endregion
|
|
4888
|
+
//#region packages/ag-term/src/pipeline/backdrop/index.ts
|
|
4889
|
+
const EMPTY_RESULT = Object.freeze({
|
|
4890
|
+
modified: false,
|
|
4891
|
+
overlay: ""
|
|
4892
|
+
});
|
|
4893
|
+
/**
|
|
4894
|
+
* Apply backdrop-fade to the buffer based on tree markers.
|
|
4895
|
+
*
|
|
4896
|
+
* Thin orchestrator over the mask → realize stages:
|
|
4897
|
+
*
|
|
4898
|
+
* plan = buildPlan(root, options)
|
|
4899
|
+
* modified = realizeToBuffer(plan, buffer)
|
|
4900
|
+
* overlay = plan.kittyEnabled ? realizeToKitty(plan, buffer) : ""
|
|
4901
|
+
*
|
|
4902
|
+
* Returns a `BackdropResult`:
|
|
4903
|
+
* - `modified` — any buffer cells changed.
|
|
4904
|
+
* - `overlay` — out-of-band ANSI escapes. Non-empty only when the plan is
|
|
4905
|
+
* active AND Kitty graphics are enabled. An active overlay always begins
|
|
4906
|
+
* with a delete-all command so last-frame placements get erased even if
|
|
4907
|
+
* this frame has no wide cells.
|
|
4908
|
+
*
|
|
4909
|
+
* **Inactive frames are silent.** When `plan.active` is false this returns
|
|
4910
|
+
* `EMPTY_RESULT` regardless of `options.kittyGraphics`. Stale scrim
|
|
4911
|
+
* placements from a prior active frame must be cleaned up at the
|
|
4912
|
+
* deactivation EDGE by the caller (e.g., `ag.ts` tracks `_kittyActive`
|
|
4913
|
+
* across frames and emits a one-shot delete-all when active→inactive).
|
|
4914
|
+
* Emitting the delete-all every inactive frame here would spam the
|
|
4915
|
+
* terminal — Modal's default `fade={0}` would push a cleanup string every
|
|
4916
|
+
* frame indefinitely.
|
|
4917
|
+
*/
|
|
4918
|
+
function applyBackdrop(root, buffer, options) {
|
|
4919
|
+
const plan = buildPlan(root, options);
|
|
4920
|
+
if (plan.mixedAmounts && process.env.NODE_ENV !== "production") console.warn(`[silvery:backdrop] multiple fade amounts in one frame (using ${plan.amount}); Kitty overlay will use the first observed amount. See plan.ts / assertSingleAmount.`);
|
|
4921
|
+
if (!plan.active) return EMPTY_RESULT;
|
|
4199
4922
|
return {
|
|
4200
|
-
|
|
4201
|
-
|
|
4202
|
-
b
|
|
4923
|
+
modified: realizeToBuffer(plan, buffer),
|
|
4924
|
+
overlay: plan.kittyEnabled ? realizeToKitty(plan, buffer) : ""
|
|
4203
4925
|
};
|
|
4204
4926
|
}
|
|
4205
4927
|
//#endregion
|
|
4206
|
-
//#region \0@oxc-project+runtime@0.
|
|
4928
|
+
//#region \0@oxc-project+runtime@0.126.0/helpers/usingCtx.js
|
|
4207
4929
|
function _usingCtx() {
|
|
4208
4930
|
var r = "function" == typeof SuppressedError ? SuppressedError : function(r, e) {
|
|
4209
4931
|
var n = Error();
|
|
@@ -4278,13 +5000,78 @@ function _usingCtx() {
|
|
|
4278
5000
|
* ```
|
|
4279
5001
|
*/
|
|
4280
5002
|
init_buffer();
|
|
5003
|
+
init_src();
|
|
4281
5004
|
const log = createLogger("silvery:render");
|
|
4282
5005
|
const baseLog = createLogger("@silvery/ag-react");
|
|
5006
|
+
/**
|
|
5007
|
+
* Walk the ag tree top-down to find the root ThemeProvider's background color.
|
|
5008
|
+
*
|
|
5009
|
+
* ThemeProvider in @silvery/ag-react renders a `<Box theme={merged}>` wrapper.
|
|
5010
|
+
* The render phase pushes/pops this theme via pushContextTheme/popContextTheme,
|
|
5011
|
+
* so the module-level theme stack is empty after the render phase completes.
|
|
5012
|
+
* We walk the tree directly to recover the root bg without requiring the
|
|
5013
|
+
* render phase to be running.
|
|
5014
|
+
*
|
|
5015
|
+
* Returns the first Box node's Sterling `bg-surface-default` (with legacy `bg`
|
|
5016
|
+
* fallback for backdrop-only Themes that pre-date Sterling's flat surface
|
|
5017
|
+
* tokens) found in a depth-first walk, or `null` if no theme node is present
|
|
5018
|
+
* (bare tests without ThemeProvider).
|
|
5019
|
+
*/
|
|
5020
|
+
function findRootThemeBg(root) {
|
|
5021
|
+
const props = root.props;
|
|
5022
|
+
if (props.theme) {
|
|
5023
|
+
const theme = props.theme;
|
|
5024
|
+
const sterlingBg = theme["bg-surface-default"];
|
|
5025
|
+
if (typeof sterlingBg === "string") return sterlingBg;
|
|
5026
|
+
const legacyBg = theme["bg"];
|
|
5027
|
+
if (typeof legacyBg === "string") return legacyBg;
|
|
5028
|
+
}
|
|
5029
|
+
for (const child of root.children) {
|
|
5030
|
+
const found = findRootThemeBg(child);
|
|
5031
|
+
if (found !== null) return found;
|
|
5032
|
+
}
|
|
5033
|
+
return null;
|
|
5034
|
+
}
|
|
5035
|
+
/**
|
|
5036
|
+
* Env heuristic: should the backdrop-fade pass emit Kitty graphics overlays?
|
|
5037
|
+
*
|
|
5038
|
+
* This is the MVP gate — a lightweight capability detector used when the
|
|
5039
|
+
* caller doesn't pass `kittyGraphics` explicitly. Matches the Option C design
|
|
5040
|
+
* intent: emit only on modern terminals where Kitty graphics are known to
|
|
5041
|
+
* work (Kitty, Ghostty, WezTerm), NOT inside tmux (DCS passthrough is
|
|
5042
|
+
* unreliable), with an explicit `SILVERY_KITTY_GRAPHICS` override.
|
|
5043
|
+
*
|
|
5044
|
+
* - `SILVERY_KITTY_GRAPHICS=0` → always off
|
|
5045
|
+
* - `SILVERY_KITTY_GRAPHICS=1` → always on (bypasses tmux + term checks)
|
|
5046
|
+
* - `TMUX` env var present → off (unless forced on above)
|
|
5047
|
+
* - `TERM_PROGRAM` in {Ghostty, WezTerm} → on
|
|
5048
|
+
* - `TERM` contains "kitty" → on
|
|
5049
|
+
* - `KITTY_WINDOW_ID` set → on
|
|
5050
|
+
* - otherwise → off
|
|
5051
|
+
*
|
|
5052
|
+
* The long-term plan is to promote this to a `TerminalCaps.kittyGraphics`
|
|
5053
|
+
* consumer. That field exists (see `@silvery/ansi` detectTerminalCaps) but
|
|
5054
|
+
* isn't threaded into the render pipeline yet — tracked as a follow-up.
|
|
5055
|
+
*/
|
|
5056
|
+
function isKittyGraphicsEnabledFromEnv() {
|
|
5057
|
+
const env = typeof process !== "undefined" ? process.env : {};
|
|
5058
|
+
const override = env.SILVERY_KITTY_GRAPHICS;
|
|
5059
|
+
if (override === "0" || override === "false") return false;
|
|
5060
|
+
if (override === "1" || override === "true") return true;
|
|
5061
|
+
if (env.TMUX) return false;
|
|
5062
|
+
const program = env.TERM_PROGRAM ?? "";
|
|
5063
|
+
if (program === "ghostty" || program === "Ghostty" || program === "WezTerm") return true;
|
|
5064
|
+
if ((env.TERM ?? "").includes("kitty")) return true;
|
|
5065
|
+
if (env.KITTY_WINDOW_ID) return true;
|
|
5066
|
+
return false;
|
|
5067
|
+
}
|
|
4283
5068
|
function createAg(root, options) {
|
|
4284
5069
|
const measurer = options?.measurer;
|
|
4285
5070
|
const colorLevel = options?.colorLevel ?? "truecolor";
|
|
5071
|
+
const kittyGraphics = options?.kittyGraphics !== void 0 ? options.kittyGraphics : isKittyGraphicsEnabledFromEnv();
|
|
4286
5072
|
const ctx = measurer ? { measurer } : void 0;
|
|
4287
5073
|
let _prevBuffer = null;
|
|
5074
|
+
let _kittyActive = false;
|
|
4288
5075
|
let hasScroll = false;
|
|
4289
5076
|
let hasSticky = false;
|
|
4290
5077
|
function doLayout(cols, rows, opts) {
|
|
@@ -4411,14 +5198,24 @@ function createAg(root, options) {
|
|
|
4411
5198
|
log.debug?.(`content: ${tContent.toFixed(2)}ms`);
|
|
4412
5199
|
}
|
|
4413
5200
|
let carryForwardBuffer;
|
|
4414
|
-
|
|
5201
|
+
let overlay = "";
|
|
5202
|
+
const backdropActive = hasBackdropMarkers(root);
|
|
5203
|
+
if (backdropActive) {
|
|
4415
5204
|
carryForwardBuffer = buffer.clone();
|
|
4416
5205
|
if (!opts?.fresh) _prevBuffer = carryForwardBuffer;
|
|
4417
|
-
|
|
5206
|
+
const defaultBg = findRootThemeBg(root) ?? void 0;
|
|
5207
|
+
overlay = applyBackdrop(root, buffer, {
|
|
5208
|
+
colorLevel,
|
|
5209
|
+
defaultBg,
|
|
5210
|
+
kittyGraphics
|
|
5211
|
+
}).overlay;
|
|
4418
5212
|
} else {
|
|
4419
5213
|
carryForwardBuffer = buffer;
|
|
4420
5214
|
if (!opts?.fresh) _prevBuffer = buffer;
|
|
4421
5215
|
}
|
|
5216
|
+
const kittyActiveThisFrame = backdropActive && overlay.length > 0;
|
|
5217
|
+
if (_kittyActive && !kittyActiveThisFrame) overlay = "\x1B7" + kittyDeleteAllScrimPlacements() + "\x1B8";
|
|
5218
|
+
_kittyActive = kittyActiveThisFrame;
|
|
4422
5219
|
clearDirtyTracking();
|
|
4423
5220
|
const acc = globalThis.__silvery_bench_phases;
|
|
4424
5221
|
if (acc) {
|
|
@@ -4430,7 +5227,8 @@ function createAg(root, options) {
|
|
|
4430
5227
|
buffer,
|
|
4431
5228
|
carryForwardBuffer,
|
|
4432
5229
|
prevBuffer,
|
|
4433
|
-
tContent
|
|
5230
|
+
tContent,
|
|
5231
|
+
overlay
|
|
4434
5232
|
};
|
|
4435
5233
|
}
|
|
4436
5234
|
function agCreateNode(type, props) {
|
|
@@ -4482,7 +5280,8 @@ function createAg(root, options) {
|
|
|
4482
5280
|
frame: result.frame,
|
|
4483
5281
|
buffer: result.buffer,
|
|
4484
5282
|
carryForwardBuffer: result.carryForwardBuffer,
|
|
4485
|
-
prevBuffer: result.prevBuffer
|
|
5283
|
+
prevBuffer: result.prevBuffer,
|
|
5284
|
+
overlay: result.overlay
|
|
4486
5285
|
};
|
|
4487
5286
|
},
|
|
4488
5287
|
resetBuffer() {
|
|
@@ -4563,7 +5362,7 @@ init_buffer();
|
|
|
4563
5362
|
let engineInitialized = false;
|
|
4564
5363
|
async function ensureLayoutEngine() {
|
|
4565
5364
|
if (engineInitialized || isLayoutEngineInitialized()) return;
|
|
4566
|
-
const { ensureDefaultLayoutEngine } = await import("./layout-engine
|
|
5365
|
+
const { ensureDefaultLayoutEngine } = await import("./layout-engine-B6Cdz1yZ.mjs");
|
|
4567
5366
|
await ensureDefaultLayoutEngine();
|
|
4568
5367
|
engineInitialized = true;
|
|
4569
5368
|
}
|
|
@@ -4710,6 +5509,6 @@ function withActEnvironment(fn) {
|
|
|
4710
5509
|
}
|
|
4711
5510
|
}
|
|
4712
5511
|
//#endregion
|
|
4713
|
-
export { _usingCtx as i, renderStringSync as n, createAg as r, renderString as t };
|
|
5512
|
+
export { setActiveColorLevel as a, _usingCtx as i, renderStringSync as n, createAg as r, renderString as t };
|
|
4714
5513
|
|
|
4715
|
-
//# sourceMappingURL=render-string-
|
|
5514
|
+
//# sourceMappingURL=render-string-X-CxpTdZ.mjs.map
|