@anthodev/pi-tools-style 0.1.2 → 0.1.3

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/package.json CHANGED
@@ -1,8 +1,13 @@
1
1
  {
2
2
  "name": "@anthodev/pi-tools-style",
3
- "version": "0.1.2",
3
+ "version": "0.1.3",
4
4
  "description": "Presentation-only boxes for Pi tool calls and shell commands",
5
- "keywords": ["pi-package", "pi-extension", "tui", "tools"],
5
+ "keywords": [
6
+ "pi-package",
7
+ "pi-extension",
8
+ "tui",
9
+ "tools"
10
+ ],
6
11
  "type": "module",
7
12
  "license": "MIT",
8
13
  "repository": {
@@ -34,6 +34,23 @@ interface StoredConfig extends Omit<RenderDecoratorConfig, "enabled"> {
34
34
  enabled: boolean;
35
35
  }
36
36
 
37
+ interface ResolvedFrameOptions {
38
+ options: FrameOptions;
39
+ styleSignature: string | undefined;
40
+ title: string | undefined;
41
+ }
42
+
43
+ interface RenderCacheEntry {
44
+ config: StoredConfig;
45
+ output: string[];
46
+ rendered: readonly string[];
47
+ styleSignature: string | undefined;
48
+ title: string | undefined;
49
+ width: number;
50
+ }
51
+
52
+ const BORDER_STYLE_PROBE = "╭─╮│╰─╯";
53
+
37
54
  export function installRenderDecorator<T extends Renderable>(
38
55
  component: RenderableConstructor<T>,
39
56
  config: RenderDecoratorConfig<T>,
@@ -96,6 +113,8 @@ function createDecoratedRenderer(
96
113
  key: symbol,
97
114
  renderOriginal: RenderFunction,
98
115
  ): RenderFunction {
116
+ const cache = new WeakMap<Renderable, RenderCacheEntry>();
117
+
99
118
  return function decoratedRender(width: number): string[] {
100
119
  const current = readConfig(key);
101
120
  if (!current?.enabled) return renderOriginal.call(this, width);
@@ -109,22 +128,38 @@ function createDecoratedRenderer(
109
128
  }
110
129
 
111
130
  try {
112
- return decorateLines(this, rendered, width, current);
131
+ const resolved = resolveFrameOptions(this, current);
132
+ const cached = cache.get(this);
133
+ if (
134
+ cached?.config === current &&
135
+ cached.width === width &&
136
+ cached.title === resolved.title &&
137
+ cached.styleSignature === resolved.styleSignature &&
138
+ linesEqual(cached.rendered, rendered)
139
+ ) {
140
+ return cached.output;
141
+ }
142
+
143
+ const output = decorateLines(rendered, width, current, resolved.options);
144
+ cache.set(this, {
145
+ config: current,
146
+ output,
147
+ rendered: [...rendered],
148
+ styleSignature: resolved.styleSignature,
149
+ title: resolved.title,
150
+ width,
151
+ });
152
+ return output;
113
153
  } catch {
114
154
  return renderOriginal.call(this, width);
115
155
  }
116
156
  };
117
157
  }
118
158
 
119
- function decorateLines(
159
+ function resolveFrameOptions(
120
160
  instance: Renderable,
121
- rendered: readonly string[],
122
- width: number,
123
161
  config: StoredConfig,
124
- ): string[] {
125
- const lines = config.trimOuterRules
126
- ? trimOuterRules(rendered)
127
- : [...rendered];
162
+ ): ResolvedFrameOptions {
128
163
  const title = config.title?.(instance);
129
164
  const options: FrameOptions = {};
130
165
 
@@ -137,9 +172,34 @@ function decorateLines(
137
172
  }
138
173
  if (title) options.title = title;
139
174
 
175
+ return {
176
+ options,
177
+ // Theme providers can change border output without changing component content.
178
+ styleSignature: styleBorder?.(BORDER_STYLE_PROBE),
179
+ title,
180
+ };
181
+ }
182
+
183
+ function decorateLines(
184
+ rendered: readonly string[],
185
+ width: number,
186
+ config: StoredConfig,
187
+ options: FrameOptions,
188
+ ): string[] {
189
+ const lines = config.trimOuterRules
190
+ ? trimOuterRules(rendered)
191
+ : [...rendered];
192
+
140
193
  return frameLines(lines, width, options);
141
194
  }
142
195
 
196
+ function linesEqual(left: readonly string[], right: readonly string[]): boolean {
197
+ return (
198
+ left.length === right.length &&
199
+ left.every((line, index) => line === right[index])
200
+ );
201
+ }
202
+
143
203
  function storeConfig<T extends Renderable>(
144
204
  config: RenderDecoratorConfig<T>,
145
205
  ): void {