@wizardingcode/shibaox-tui 0.1.0 → 0.1.2

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,27 +1,28 @@
1
1
  {
2
2
  "name": "@wizardingcode/shibaox-tui",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "license": "UNLICENSED",
5
5
  "type": "module",
6
6
  "files": [
7
7
  "src",
8
- "bunfig.toml"
8
+ "bunfig.toml",
9
+ "tsconfig.json"
9
10
  ],
10
11
  "exports": {
11
12
  ".": {
12
13
  "types": "./src/app.tsx",
13
14
  "import": "./src/app.tsx"
14
- }
15
+ },
16
+ "./package.json": "./package.json"
15
17
  },
16
18
  "dependencies": {
17
19
  "@opentui/core": "0.5.12",
18
20
  "@opentui/solid": "0.5.12",
19
21
  "fuzzysort": "^4.0.2",
20
- "opentui-spinner": "0.0.7",
21
22
  "solid-js": "1.9.15",
22
- "@wizardingcode/shibaox-core": "0.1.0",
23
- "@wizardingcode/shibaox-daemon": "0.1.0",
24
- "@wizardingcode/shibaox-schemas": "0.1.0"
23
+ "@wizardingcode/shibaox-core": "0.1.2",
24
+ "@wizardingcode/shibaox-daemon": "0.1.2",
25
+ "@wizardingcode/shibaox-schemas": "0.1.2"
25
26
  },
26
27
  "devDependencies": {
27
28
  "@types/bun": "^1.3.0",
@@ -0,0 +1,171 @@
1
+ // A small port of opentui-spinner (MIT, https://github.com/…/opentui-spinner): the package
2
+ // pins its OpenTUI peers to 0.3, which an npm install resolves to a second OpenTUI copy
3
+ // next to ours and the dashboard refuses to start. What we use fits in this file.
4
+ import {
5
+ type ColorInput,
6
+ type OptimizedBuffer,
7
+ parseColor,
8
+ Renderable,
9
+ type RenderableOptions,
10
+ type RenderContext,
11
+ type RGBA,
12
+ resolveRenderLib,
13
+ } from '@opentui/core';
14
+ import { extend } from '@opentui/solid/components';
15
+
16
+ export interface SpinnerOptions extends RenderableOptions<SpinnerRenderable> {
17
+ frames?: string[];
18
+ /** Milliseconds per frame (16–1000). */
19
+ interval?: number;
20
+ autoplay?: boolean;
21
+ color?: ColorInput;
22
+ backgroundColor?: ColorInput;
23
+ }
24
+
25
+ const DEFAULT_FRAMES = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
26
+ const clampInterval = (ms: number) => Math.min(1000, Math.max(1000 / 60, ms));
27
+ type Encoded = ReturnType<ReturnType<typeof resolveRenderLib>['encodeUnicode']>;
28
+
29
+ /** One cell (or a few) that cycles through `frames` every `interval` ms while visible. */
30
+ export class SpinnerRenderable extends Renderable {
31
+ private _frames: string[];
32
+ private _interval: number;
33
+ private _running = false;
34
+ private _index = 0;
35
+ private _timer: ReturnType<typeof setInterval> | undefined;
36
+ private _color: RGBA;
37
+ private _background: RGBA;
38
+ private readonly _lib = resolveRenderLib();
39
+ private _encoded = new Map<string, NonNullable<Encoded>>();
40
+
41
+ constructor(ctx: RenderContext, options: SpinnerOptions) {
42
+ super(ctx, options);
43
+ this._frames = options.frames?.length ? options.frames : DEFAULT_FRAMES;
44
+ this._interval = clampInterval(options.interval ?? 80);
45
+ this._color = parseColor(options.color ?? 'white');
46
+ this._background = parseColor(options.backgroundColor ?? 'transparent');
47
+ this.height = 1;
48
+ this.encode();
49
+ if (options.autoplay ?? true) this.start();
50
+ }
51
+
52
+ private encode(): void {
53
+ let width = 0;
54
+ for (const f of this._frames) {
55
+ const e = this._encoded.get(f) ?? this._lib.encodeUnicode(f, this.ctx.widthMethod);
56
+ if (!e) continue;
57
+ this._encoded.set(f, e);
58
+ width = Math.max(
59
+ width,
60
+ e.data.reduce((n: number, c: { width: number }) => n + c.width, 0),
61
+ );
62
+ }
63
+ this.width = width;
64
+ }
65
+ private free(): void {
66
+ for (const e of this._encoded.values()) this._lib.freeUnicode(e);
67
+ this._encoded.clear();
68
+ }
69
+ private arm(): void {
70
+ this.disarm();
71
+ if (!this._running || !this.visible) return;
72
+ this._timer = setInterval(() => {
73
+ if (this.isDestroyed) return this.disarm();
74
+ this._index = (this._index + 1) % this._frames.length;
75
+ this.requestRender();
76
+ }, this._interval);
77
+ this._timer.unref?.();
78
+ }
79
+ private disarm(): void {
80
+ if (this._timer !== undefined) clearInterval(this._timer);
81
+ this._timer = undefined;
82
+ }
83
+
84
+ get frames(): string[] {
85
+ return this._frames;
86
+ }
87
+ set frames(value: string[]) {
88
+ const next = value.length ? value : DEFAULT_FRAMES;
89
+ if (next.length === this._frames.length && next.every((f, i) => f === this._frames[i])) return;
90
+ this.free();
91
+ this._frames = next;
92
+ this._index = 0;
93
+ this.encode();
94
+ this.requestRender();
95
+ }
96
+ get interval(): number {
97
+ return this._interval;
98
+ }
99
+ set interval(ms: number) {
100
+ this._interval = clampInterval(ms);
101
+ this.arm();
102
+ }
103
+ get color(): RGBA {
104
+ return this._color;
105
+ }
106
+ set color(value: ColorInput) {
107
+ this._color = parseColor(value);
108
+ this.requestRender();
109
+ }
110
+ get backgroundColor(): RGBA {
111
+ return this._background;
112
+ }
113
+ set backgroundColor(value: ColorInput) {
114
+ this._background = parseColor(value);
115
+ this.requestRender();
116
+ }
117
+ get autoplay(): boolean {
118
+ return this._running;
119
+ }
120
+ set autoplay(value: boolean) {
121
+ if (value) this.start();
122
+ else this.stop();
123
+ }
124
+ override get visible(): boolean {
125
+ return super.visible;
126
+ }
127
+ override set visible(value: boolean) {
128
+ super.visible = value;
129
+ this.arm();
130
+ }
131
+ start(): void {
132
+ if (this._running || this.isDestroyed) return;
133
+ this._running = true;
134
+ this.arm();
135
+ }
136
+ stop(): void {
137
+ this._running = false;
138
+ this.disarm();
139
+ }
140
+
141
+ protected override renderSelf(buffer: OptimizedBuffer): void {
142
+ if (!this.visible) return;
143
+ const frame = this._frames[this._index];
144
+ const e = frame ? this._encoded.get(frame) : undefined;
145
+ if (!e) return;
146
+ let x = this.x;
147
+ for (const c of e.data) {
148
+ buffer.drawChar(c.char, x, this.y, this._color, this._background);
149
+ x += c.width;
150
+ }
151
+ }
152
+ protected override destroySelf(): void {
153
+ this.stop();
154
+ this.free();
155
+ super.destroySelf();
156
+ }
157
+ }
158
+
159
+ declare module '@opentui/solid' {
160
+ interface OpenTUIComponents {
161
+ spinner: typeof SpinnerRenderable;
162
+ }
163
+ }
164
+
165
+ let registered = false;
166
+ /** Makes `<spinner>` available to the Solid renderer (once). */
167
+ export function registerSpinner(): void {
168
+ if (registered) return;
169
+ registered = true;
170
+ extend({ spinner: SpinnerRenderable });
171
+ }
@@ -1,13 +1,12 @@
1
1
  // Adapted from opencode (MIT) — https://github.com/anomalyco/opencode
2
2
  import { type ColorInput, parseColor, RGBA } from '@opentui/core';
3
3
  import type { JSX } from '@opentui/solid';
4
- import { getComponentCatalogue } from '@opentui/solid/components';
5
- import { registerSpinner } from 'opentui-spinner/solid';
6
4
  import { createEffect, createMemo, createSignal, onCleanup, Show } from 'solid-js';
7
5
  import { useMotion } from './config.js';
8
6
  import { type OneCellMotion, oneCellFrame } from './one-cell-motion.js';
7
+ import { registerSpinner } from './spinner-renderable.js';
9
8
 
10
- if (!getComponentCatalogue().spinner) registerSpinner();
9
+ registerSpinner();
11
10
 
12
11
  export const SPINNER_FRAMES = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
13
12
 
package/tsconfig.json ADDED
@@ -0,0 +1,19 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "strict": true,
5
+ "skipLibCheck": true,
6
+ "esModuleInterop": true,
7
+ "resolveJsonModule": true,
8
+ "noUncheckedIndexedAccess": true,
9
+ "rootDir": ".",
10
+ "noEmit": true,
11
+ "jsx": "preserve",
12
+ "jsxImportSource": "@opentui/solid",
13
+ "module": "ESNext",
14
+ "moduleResolution": "bundler",
15
+ "types": ["bun", "node"],
16
+ "allowImportingTsExtensions": true
17
+ },
18
+ "include": ["src", "test", "test-vitest"]
19
+ }