tidepool 1.0.6 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/screen.js CHANGED
@@ -2,59 +2,133 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.TideScreen = void 0;
4
4
  class TideScreen {
5
- constructor(width, height, backgroundColor = 'transparent') {
5
+ constructor(width, height, backgroundColor = "transparent") {
6
6
  this.fps = null;
7
7
  this.updateFunction = null;
8
+ this.needsSort = true;
9
+ this.resizeListener = null;
10
+ this.didInitialClear = false;
8
11
  this.width = width;
9
12
  this.height = height;
10
- this.screen = Array.from({ length: height }, () => Array(width).fill(' '));
11
- this.buffer = Array.from({ length: height }, () => Array(width).fill(' '));
13
+ this.frontBuffer = this.createBuffer(width, height);
14
+ this.backBuffer = this.createBuffer(width, height);
12
15
  this.components = [];
13
16
  this.backgroundColor = backgroundColor;
14
17
  }
15
- clearScreen() {
16
- this.screen = Array.from({ length: this.height }, () => Array(this.width).fill(' '));
18
+ createBuffer(width, height) {
19
+ return Array.from({ length: height }, () => Array(width).fill(" "));
17
20
  }
18
- clearBuffer() {
19
- this.buffer = Array.from({ length: this.height }, () => Array(this.width).fill(' '));
21
+ clearBuffer(buffer, dirtyRows) {
22
+ for (let i = 0; i < this.height; i++) {
23
+ for (let j = 0; j < this.width; j++) {
24
+ buffer[i][j] = " ";
25
+ }
26
+ if (dirtyRows) {
27
+ dirtyRows.add(i);
28
+ }
29
+ }
20
30
  }
21
31
  addComponent(component) {
22
32
  this.components.push(component);
33
+ this.needsSort = true;
34
+ }
35
+ invalidateSort() {
36
+ this.needsSort = true;
23
37
  }
24
38
  getBackgroundColorCode(color) {
25
39
  const colors = {
26
- black: '\x1b[40m',
27
- red: '\x1b[41m',
28
- green: '\x1b[42m',
29
- yellow: '\x1b[43m',
30
- blue: '\x1b[44m',
31
- magenta: '\x1b[45m',
32
- cyan: '\x1b[46m',
33
- white: '\x1b[47m',
34
- transparent: '\x1b[49m',
35
- random: '\x1b[48;5;' + Math.floor(Math.random() * 256) + 'm',
40
+ black: "\x1b[40m",
41
+ red: "\x1b[41m",
42
+ green: "\x1b[42m",
43
+ yellow: "\x1b[43m",
44
+ blue: "\x1b[44m",
45
+ magenta: "\x1b[45m",
46
+ cyan: "\x1b[46m",
47
+ white: "\x1b[47m",
48
+ transparent: "\x1b[49m",
49
+ random: "\x1b[48;5;" + Math.floor(Math.random() * 256) + "m",
36
50
  };
37
- return colors[color.toLowerCase()] || colors['black'];
51
+ return colors[color.toLowerCase()] || colors["black"];
38
52
  }
39
53
  renderBuffer() {
40
- this.clearBuffer();
41
- this.components.sort((a, b) => b.zIndex - a.zIndex);
42
- this.components.forEach(component => component.draw(this.buffer));
54
+ const dirtyRows = new Set();
55
+ this.clearBuffer(this.backBuffer, dirtyRows);
56
+ if (this.needsSort) {
57
+ this.components.sort((a, b) => b.zIndex - a.zIndex);
58
+ this.needsSort = false;
59
+ }
60
+ const baseContext = {
61
+ buffer: this.backBuffer,
62
+ origin: { x: 0, y: 0 },
63
+ clip: { x: 0, y: 0, width: this.width, height: this.height },
64
+ screenSize: { width: this.width, height: this.height },
65
+ dirtyRows,
66
+ };
67
+ this.components.forEach((component) => component.draw(baseContext));
68
+ return dirtyRows;
43
69
  }
44
- applyBackgroundColor() {
70
+ applyBackgroundColor(dirtyRows) {
71
+ if (this.backgroundColor.toLowerCase() === "transparent") {
72
+ return;
73
+ }
45
74
  const bgColorCode = this.getBackgroundColorCode(this.backgroundColor);
46
- const resetCode = '\x1b[0m';
75
+ const resetCode = "\x1b[0m";
47
76
  for (let i = 0; i < this.height; i++) {
48
77
  for (let j = 0; j < this.width; j++) {
49
- this.buffer[i][j] = `${bgColorCode}${this.buffer[i][j] || ' '}${resetCode}`;
78
+ this.backBuffer[i][j] = `${bgColorCode}${this.backBuffer[i][j] || " "}${resetCode}`;
79
+ if (dirtyRows) {
80
+ dirtyRows.add(i);
81
+ }
50
82
  }
51
83
  }
52
84
  }
53
- render() {
54
- this.applyBackgroundColor();
55
- console.clear();
56
- const output = this.buffer.map(row => row.join('')).join('\n');
57
- console.log(output);
85
+ resize(width, height) {
86
+ this.width = width;
87
+ this.height = height;
88
+ this.frontBuffer = this.createBuffer(width, height);
89
+ this.backBuffer = this.createBuffer(width, height);
90
+ }
91
+ enableAutoResize(getSize = () => ({
92
+ width: process.stdout.columns - 1,
93
+ height: process.stdout.rows - 1,
94
+ })) {
95
+ if (this.resizeListener) {
96
+ return;
97
+ }
98
+ this.resizeListener = () => {
99
+ const size = getSize();
100
+ this.resize(size.width, size.height);
101
+ };
102
+ process.on("SIGWINCH", this.resizeListener);
103
+ }
104
+ disableAutoResize() {
105
+ if (!this.resizeListener) {
106
+ return;
107
+ }
108
+ process.off("SIGWINCH", this.resizeListener);
109
+ this.resizeListener = null;
110
+ }
111
+ render(dirtyRows) {
112
+ if (!this.didInitialClear) {
113
+ process.stdout.write("\x1b[2J\x1b[H");
114
+ this.didInitialClear = true;
115
+ }
116
+ this.applyBackgroundColor(dirtyRows);
117
+ process.stdout.write("\x1b[H");
118
+ const rowsToRender = dirtyRows && dirtyRows.size > 0
119
+ ? Array.from(dirtyRows)
120
+ : dirtyRows
121
+ ? []
122
+ : Array.from({ length: this.height }, (_, i) => i);
123
+ for (const i of rowsToRender) {
124
+ for (let j = 0; j < this.width; j++) {
125
+ if (this.backBuffer[i][j] !== this.frontBuffer[i][j]) {
126
+ process.stdout.write(`\x1b[${i + 1};${j + 1}H`);
127
+ process.stdout.write(this.backBuffer[i][j]);
128
+ this.frontBuffer[i][j] = this.backBuffer[i][j];
129
+ }
130
+ }
131
+ }
58
132
  }
59
133
  setUpdateFunction(fn) {
60
134
  this.updateFunction = fn;
@@ -66,10 +140,10 @@ class TideScreen {
66
140
  if (this.updateFunction) {
67
141
  this.updateFunction();
68
142
  }
69
- this.renderBuffer();
70
- this.render();
143
+ const dirtyRows = this.renderBuffer();
144
+ this.render(dirtyRows);
71
145
  }
72
- renderCycle() {
146
+ renderCycle(beforeRender, afterRender) {
73
147
  if (this.fps === null) {
74
148
  throw new Error("FPS must be set before starting the render cycle.");
75
149
  }
@@ -78,11 +152,21 @@ class TideScreen {
78
152
  if (this.updateFunction) {
79
153
  this.updateFunction();
80
154
  }
81
- this.renderBuffer();
82
- this.render();
155
+ if (beforeRender) {
156
+ beforeRender();
157
+ }
158
+ const dirtyRows = this.renderBuffer();
159
+ this.render(dirtyRows);
160
+ if (afterRender) {
161
+ afterRender();
162
+ }
83
163
  setTimeout(loop, interval);
84
164
  };
85
165
  loop();
166
+ process.on("SIGINT", () => {
167
+ process.stdout.write("\x1b[?25h");
168
+ process.exit();
169
+ });
86
170
  }
87
171
  }
88
172
  exports.TideScreen = TideScreen;
package/dist/util.d.ts CHANGED
@@ -1,3 +1,6 @@
1
+ import type { Rect } from "./interfaces";
1
2
  export declare function getColorCode(color: string): string;
2
3
  export declare function wrapText(text: string, maxWidth: number): string[];
4
+ export declare function intersectRect(a: Rect, b: Rect): Rect | null;
5
+ export declare function setCell(buffer: string[][], x: number, y: number, value: string, clip?: Rect, dirtyRows?: Set<number>): void;
3
6
  //# sourceMappingURL=util.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"util.d.ts","sourceRoot":"","sources":["../src/util.ts"],"names":[],"mappings":"AAAA,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,UAezC;AAED,wBAAgB,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,YAetD"}
1
+ {"version":3,"file":"util.d.ts","sourceRoot":"","sources":["../src/util.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,cAAc,CAAC;AAEzC,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,UAgBzC;AAED,wBAAgB,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,YAetD;AAED,wBAAgB,aAAa,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,IAAI,GAAG,IAAI,GAAG,IAAI,CAW3D;AAED,wBAAgB,OAAO,CACrB,MAAM,EAAE,MAAM,EAAE,EAAE,EAClB,CAAC,EAAE,MAAM,EACT,CAAC,EAAE,MAAM,EACT,KAAK,EAAE,MAAM,EACb,IAAI,CAAC,EAAE,IAAI,EACX,SAAS,CAAC,EAAE,GAAG,CAAC,MAAM,CAAC,QAmBxB"}
package/dist/util.js CHANGED
@@ -2,20 +2,23 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.getColorCode = getColorCode;
4
4
  exports.wrapText = wrapText;
5
+ exports.intersectRect = intersectRect;
6
+ exports.setCell = setCell;
5
7
  function getColorCode(color) {
6
8
  const colors = {
7
- reset: '\x1b[0m',
8
- black: '\x1b[30m',
9
- red: '\x1b[31m',
10
- green: '\x1b[32m',
11
- yellow: '\x1b[33m',
12
- blue: '\x1b[34m',
13
- magenta: '\x1b[35m',
14
- cyan: '\x1b[36m',
15
- white: '\x1b[37m',
16
- random: '\x1b[38;5;' + Math.floor(Math.random() * 256) + 'm',
9
+ reset: "\x1b[0m",
10
+ black: "\x1b[30m",
11
+ red: "\x1b[31m",
12
+ green: "\x1b[32m",
13
+ yellow: "\x1b[33m",
14
+ blue: "\x1b[34m",
15
+ magenta: "\x1b[35m",
16
+ cyan: "\x1b[36m",
17
+ white: "\x1b[37m",
18
+ gray: "\x1b[90m",
19
+ random: "\x1b[38;5;" + Math.floor(Math.random() * 256) + "m",
17
20
  };
18
- return colors[color.toLowerCase()] || colors['reset'];
21
+ return colors[color.toLowerCase()] || colors["reset"];
19
22
  }
20
23
  function wrapText(text, maxWidth) {
21
24
  const lines = [];
@@ -32,3 +35,30 @@ function wrapText(text, maxWidth) {
32
35
  lines.push(currentLine.trim());
33
36
  return lines;
34
37
  }
38
+ function intersectRect(a, b) {
39
+ const x1 = Math.max(a.x, b.x);
40
+ const y1 = Math.max(a.y, b.y);
41
+ const x2 = Math.min(a.x + a.width, b.x + b.width);
42
+ const y2 = Math.min(a.y + a.height, b.y + b.height);
43
+ if (x2 <= x1 || y2 <= y1) {
44
+ return null;
45
+ }
46
+ return { x: x1, y: y1, width: x2 - x1, height: y2 - y1 };
47
+ }
48
+ function setCell(buffer, x, y, value, clip, dirtyRows) {
49
+ if (y < 0 || x < 0 || y >= buffer.length || x >= buffer[0].length) {
50
+ return;
51
+ }
52
+ if (clip) {
53
+ if (x < clip.x ||
54
+ y < clip.y ||
55
+ x >= clip.x + clip.width ||
56
+ y >= clip.y + clip.height) {
57
+ return;
58
+ }
59
+ }
60
+ buffer[y][x] = value;
61
+ if (dirtyRows) {
62
+ dirtyRows.add(y);
63
+ }
64
+ }
package/package.json CHANGED
@@ -1,15 +1,16 @@
1
1
  {
2
2
  "name": "tidepool",
3
- "version": "1.0.6",
3
+ "version": "2.1.0",
4
4
  "author": "stuncs69",
5
5
  "main": "dist/index.js",
6
+ "types": "dist/index.d.ts",
6
7
  "repository": {
7
8
  "type": "git",
8
- "url": "https://github.com/your-username/tidepool.git"
9
+ "url": "https://github.com/stuncs69/tidepool.git"
9
10
  },
10
11
  "devDependencies": {
11
12
  "@eslint/js": "^9.9.0",
12
- "@types/node": "^22.2.0",
13
+ "@types/node": "^25.0.8",
13
14
  "@typescript-eslint/eslint-plugin": "^8.1.0",
14
15
  "@typescript-eslint/parser": "^8.1.0",
15
16
  "eslint": "^9.9.0",
@@ -20,7 +21,24 @@
20
21
  "description": "A CLI rendering engine in TypeScript.",
21
22
  "license": "ISC",
22
23
  "scripts": {
23
- "test": "echo \"Error: no test specified\" && exit 1",
24
- "lint": "eslint ./src/*"
24
+ "test": "npm run build && node --test test/*.test.js",
25
+ "lint": "eslint ./src/*",
26
+ "build": "tsc",
27
+ "prepublishOnly": "npm run build",
28
+ "demo:build": "tsc -p demo/tsconfig.json",
29
+ "demo": "npm run build && npm run demo:build && node dist-demo/demo/demo.js",
30
+ "demo:stack": "npm run build && npm run demo:build && node dist-demo/demo/stacking.js",
31
+ "demo:form": "npm run build && npm run demo:build && node dist-demo/demo/form.js",
32
+ "demo:chat": "npm run build && npm run demo:build && node dist-demo/demo/chat.js"
33
+ },
34
+ "files": [
35
+ "dist"
36
+ ],
37
+ "exports": {
38
+ ".": {
39
+ "import": "./dist/index.js",
40
+ "require": "./dist/index.js",
41
+ "types": "./dist/index.d.ts"
42
+ }
25
43
  }
26
44
  }
@@ -1,50 +0,0 @@
1
- # This workflow uses actions that are not certified by GitHub.
2
- # They are provided by a third-party and are governed by
3
- # separate terms of service, privacy policy, and support
4
- # documentation.
5
- # ESLint is a tool for identifying and reporting on patterns
6
- # found in ECMAScript/JavaScript code.
7
- # More details at https://github.com/eslint/eslint
8
- # and https://eslint.org
9
-
10
- name: ESLint
11
-
12
- on:
13
- push:
14
- branches: [ "main" ]
15
- pull_request:
16
- # The branches below must be a subset of the branches above
17
- branches: [ "main" ]
18
- schedule:
19
- - cron: '21 0 * * 3'
20
-
21
- jobs:
22
- eslint:
23
- name: Run eslint scanning
24
- runs-on: ubuntu-latest
25
- permissions:
26
- contents: read
27
- security-events: write
28
- actions: read # only required for a private repository by github/codeql-action/upload-sarif to get the Action run status
29
- steps:
30
- - name: Checkout code
31
- uses: actions/checkout@v4
32
-
33
- - name: Install ESLint
34
- run: |
35
- npm install eslint@8.10.0
36
- npm install @microsoft/eslint-formatter-sarif@2.1.7
37
-
38
- - name: Run ESLint
39
- run: npx eslint .
40
- --config .eslintrc.js
41
- --ext .js,.jsx,.ts,.tsx
42
- --format @microsoft/eslint-formatter-sarif
43
- --output-file eslint-results.sarif
44
- continue-on-error: true
45
-
46
- - name: Upload analysis results to GitHub
47
- uses: github/codeql-action/upload-sarif@v3
48
- with:
49
- sarif_file: eslint-results.sarif
50
- wait-for-processing: true
package/eslint.config.mjs DELETED
@@ -1,38 +0,0 @@
1
- import typescriptEslint from "@typescript-eslint/eslint-plugin";
2
- import globals from "globals";
3
- import tsParser from "@typescript-eslint/parser";
4
- import path from "node:path";
5
- import { fileURLToPath } from "node:url";
6
- import js from "@eslint/js";
7
- import { FlatCompat } from "@eslint/eslintrc";
8
-
9
- const __filename = fileURLToPath(import.meta.url);
10
- const __dirname = path.dirname(__filename);
11
- const compat = new FlatCompat({
12
- baseDirectory: __dirname,
13
- recommendedConfig: js.configs.recommended,
14
- allConfig: js.configs.all
15
- });
16
-
17
- export default [
18
- ...compat.extends("eslint:recommended", "plugin:@typescript-eslint/recommended"),
19
- {
20
- plugins: {
21
- "@typescript-eslint": typescriptEslint,
22
- },
23
-
24
- languageOptions: {
25
- globals: {
26
- ...globals.node,
27
- },
28
-
29
- parser: tsParser,
30
- ecmaVersion: 12,
31
- sourceType: "module",
32
- },
33
-
34
- rules: {
35
- "@typescript-eslint/no-unsafe-function-type": "off",
36
- },
37
- },
38
- ];