terminal-element 1.0.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/LICENSE +21 -0
- package/README.md +136 -0
- package/dist/terminal-element.es.js +293 -0
- package/dist/types/terminal-element.d.ts +76 -0
- package/dist/types/terminal-element.d.ts.map +1 -0
- package/package.json +92 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Akinori Hoshina
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
# terminal-element
|
|
2
|
+
|
|
3
|
+

|
|
4
|
+
|
|
5
|
+
[](https://opensource.org/licenses/MIT) [](https://codecov.io/gh/spider-hand/terminal-element) 
|
|
6
|
+
|
|
7
|
+
A Web Component for rendering terminal-style previews - works with any framework (React, Vue, Angular, Svelte, etc.)
|
|
8
|
+
|
|
9
|
+
## Demo
|
|
10
|
+
|
|
11
|
+
📗 [Storybook](https://main--69c3d55e127974ed658d1728.chromatic.com)
|
|
12
|
+
|
|
13
|
+
## Installation
|
|
14
|
+
|
|
15
|
+
```
|
|
16
|
+
npm install terminal-element
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Usage
|
|
20
|
+
|
|
21
|
+
```html
|
|
22
|
+
<terminal-element></terminal-element>
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Props
|
|
26
|
+
|
|
27
|
+
| Name | Type | Required | Default | Description |
|
|
28
|
+
| ------------------ | ------------------- | -------- | --------- | ---------------------------------------------------- |
|
|
29
|
+
| width | `string` | No | `"600px"` | Width of the terminal |
|
|
30
|
+
| height | `string` | No | `"360px"` | Height of the terminal |
|
|
31
|
+
| theme | `"light" \| "dark"` | No | `"dark"` | Theme of the terminal |
|
|
32
|
+
| currentDirectory | `string` | No | `""` | Current directory displayed in header |
|
|
33
|
+
| prompt | `string` | No | `"$"` | Prompt symbol |
|
|
34
|
+
| content | `Line[]` | No | `[]` | Content to display (see [Line](#line) section) |
|
|
35
|
+
| animated | `boolean` | No | `false` | Enable typing animation |
|
|
36
|
+
| typingSpeed | `number` | No | `100` | Typing speed in ms per character |
|
|
37
|
+
| loop | `boolean` | No | `false` | Enable infinite loop animation |
|
|
38
|
+
| delayAfterComplete | `number` | No | `2000` | Delay after animation completes before clearing (ms) |
|
|
39
|
+
| delayBeforeRestart | `number` | No | `1000` | Delay showing empty screen before restart (ms) |
|
|
40
|
+
|
|
41
|
+
### Line
|
|
42
|
+
|
|
43
|
+
The `content` prop accepts an array of `Line` objects. There are two types:
|
|
44
|
+
|
|
45
|
+
**InputLine** - Displays a command with prompt:
|
|
46
|
+
|
|
47
|
+
```typescript
|
|
48
|
+
type InputLine = {
|
|
49
|
+
type: "input";
|
|
50
|
+
text: string;
|
|
51
|
+
};
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
**OutputLine** - Displays output text (with optional delay for animation):
|
|
55
|
+
|
|
56
|
+
```typescript
|
|
57
|
+
// Simple text output
|
|
58
|
+
type OutputLineText = {
|
|
59
|
+
type: "output";
|
|
60
|
+
text: string;
|
|
61
|
+
delay?: number; // Delay in ms before showing this line (animation only)
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
// Colored segments output
|
|
65
|
+
type OutputLineSegments = {
|
|
66
|
+
type: "output";
|
|
67
|
+
segments: Segment[];
|
|
68
|
+
delay?: number;
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
type Segment = {
|
|
72
|
+
text: string;
|
|
73
|
+
color?: AnsiColorType; // "black" | "red" | "green" | "yellow" | "blue" | "magenta" | "cyan" | "white" and their "-bright" variants
|
|
74
|
+
bg?: AnsiColorType;
|
|
75
|
+
};
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
**Example:**
|
|
79
|
+
|
|
80
|
+
```javascript
|
|
81
|
+
content = [
|
|
82
|
+
{ type: "input", text: "npm install" },
|
|
83
|
+
{ type: "output", text: "" },
|
|
84
|
+
{ type: "output", text: "added 50 packages in 2s", delay: 500 },
|
|
85
|
+
{
|
|
86
|
+
type: "output",
|
|
87
|
+
segments: [
|
|
88
|
+
{ text: "✓", color: "green" },
|
|
89
|
+
{ text: " Done!" },
|
|
90
|
+
],
|
|
91
|
+
},
|
|
92
|
+
];
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
## Styling
|
|
96
|
+
|
|
97
|
+
| Variable | Default |
|
|
98
|
+
| ------------------------------------------- | ---------------------------------- |
|
|
99
|
+
| `--terminal-element-font-size` | `14px` |
|
|
100
|
+
| `--terminal-element-box-shadow` | `rgb(0 0 0 / 56%) 0 22px 70px 4px` |
|
|
101
|
+
| `--terminal-element-border-color` | `#070707` |
|
|
102
|
+
| `--terminal-element-header-bg` | `#323232` |
|
|
103
|
+
| `--terminal-element-header-border` | `#6a6a6a` |
|
|
104
|
+
| `--terminal-element-header-border-bottom` | `#6a6a6a` |
|
|
105
|
+
| `--terminal-element-header-directory-color` | `#afafb4` |
|
|
106
|
+
| `--terminal-element-body-bg` | `#101317` |
|
|
107
|
+
| `--terminal-element-body-border` | `#606060` |
|
|
108
|
+
| `--terminal-element-body-content-color` | `#d4d4d4` |
|
|
109
|
+
| `--terminal-element-caret-color` | `#fff` |
|
|
110
|
+
| `--terminal-element-ansi-black` | `#14191e` |
|
|
111
|
+
| `--terminal-element-ansi-black-bright` | `#676767` |
|
|
112
|
+
| `--terminal-element-ansi-red` | `#b43c29` |
|
|
113
|
+
| `--terminal-element-ansi-red-bright` | `#dc7974` |
|
|
114
|
+
| `--terminal-element-ansi-green` | `#00c200` |
|
|
115
|
+
| `--terminal-element-ansi-green-bright` | `#57e690` |
|
|
116
|
+
| `--terminal-element-ansi-yellow` | `#c7c400` |
|
|
117
|
+
| `--terminal-element-ansi-yellow-bright` | `#ece100` |
|
|
118
|
+
| `--terminal-element-ansi-blue` | `#2743c7` |
|
|
119
|
+
| `--terminal-element-ansi-blue-bright` | `#a6aaf1` |
|
|
120
|
+
| `--terminal-element-ansi-magenta` | `#bf3fbd` |
|
|
121
|
+
| `--terminal-element-ansi-magenta-bright` | `#e07de0` |
|
|
122
|
+
| `--terminal-element-ansi-cyan` | `#00c5c7` |
|
|
123
|
+
| `--terminal-element-ansi-cyan-bright` | `#5ffdff` |
|
|
124
|
+
| `--terminal-element-ansi-white` | `#c7c7c7` |
|
|
125
|
+
| `--terminal-element-ansi-white-bright` | `#feffff` |
|
|
126
|
+
|
|
127
|
+
## Contributing
|
|
128
|
+
|
|
129
|
+
- Bug fix PRs are always welcome.
|
|
130
|
+
- UI changes or new features should not be submitted without prior discussion. Please open an issue first to propose and discuss them.
|
|
131
|
+
|
|
132
|
+
Thanks for your understanding and contributions.
|
|
133
|
+
|
|
134
|
+
## License
|
|
135
|
+
|
|
136
|
+
[MIT](./LICENSE)
|
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
import { LitElement as p, css as b, html as r } from "lit";
|
|
2
|
+
import { customElement as _, property as a, state as c } from "lit/decorators.js";
|
|
3
|
+
function i(s, e, t, l) {
|
|
4
|
+
var m = arguments.length, o = m < 3 ? e : l === null ? l = Object.getOwnPropertyDescriptor(e, t) : l, d;
|
|
5
|
+
if (typeof Reflect == "object" && typeof Reflect.decorate == "function") o = Reflect.decorate(s, e, t, l);
|
|
6
|
+
else for (var h = s.length - 1; h >= 0; h--) (d = s[h]) && (o = (m < 3 ? d(o) : m > 3 ? d(e, t, o) : d(e, t)) || o);
|
|
7
|
+
return m > 3 && o && Object.defineProperty(e, t, o), o;
|
|
8
|
+
}
|
|
9
|
+
var n = class extends p {
|
|
10
|
+
constructor(...e) {
|
|
11
|
+
super(...e), this.width = "600px", this.height = "360px", this.theme = "dark", this.currentDirectory = "", this.prompt = "$", this.content = [], this.animated = !1, this.typingSpeed = 100, this.loop = !1, this.delayAfterComplete = 4e3, this.delayBeforeRestart = 1e3, this._currentLineIndex = 0, this._currentCharInLine = 0, this._isAnimating = !1, this._isWaitingToRestart = !1, this._animationTimer = null;
|
|
12
|
+
}
|
|
13
|
+
static {
|
|
14
|
+
this.styles = b`
|
|
15
|
+
:host {
|
|
16
|
+
display: block;
|
|
17
|
+
width: fit-content;
|
|
18
|
+
height: fit-content;
|
|
19
|
+
|
|
20
|
+
--terminal-element-font-size: 14px;
|
|
21
|
+
--terminal-element-box-shadow: rgb(0 0 0 / 56%) 0 22px 70px 4px;
|
|
22
|
+
|
|
23
|
+
/** UI colors */
|
|
24
|
+
--terminal-element-border-color: #070707;
|
|
25
|
+
--terminal-element-header-bg: #323232;
|
|
26
|
+
--terminal-element-header-border: #6a6a6a;
|
|
27
|
+
--terminal-element-header-border-bottom: #6a6a6a;
|
|
28
|
+
--terminal-element-header-directory-color: #afafb4;
|
|
29
|
+
--terminal-element-body-bg: #101317;
|
|
30
|
+
--terminal-element-body-border: #606060;
|
|
31
|
+
--terminal-element-body-content-color: #d4d4d4;
|
|
32
|
+
--terminal-element-caret-color: #fff;
|
|
33
|
+
|
|
34
|
+
/** ANSI colors */
|
|
35
|
+
--terminal-element-ansi-black: #14191e;
|
|
36
|
+
--terminal-element-ansi-black-bright: #676767;
|
|
37
|
+
--terminal-element-ansi-red: #b43c29;
|
|
38
|
+
--terminal-element-ansi-red-bright: #dc7974;
|
|
39
|
+
--terminal-element-ansi-green: #00c200;
|
|
40
|
+
--terminal-element-ansi-green-bright: #57e690;
|
|
41
|
+
--terminal-element-ansi-yellow: #c7c400;
|
|
42
|
+
--terminal-element-ansi-yellow-bright: #ece100;
|
|
43
|
+
--terminal-element-ansi-blue: #2743c7;
|
|
44
|
+
--terminal-element-ansi-blue-bright: #a6aaf1;
|
|
45
|
+
--terminal-element-ansi-magenta: #bf3fbd;
|
|
46
|
+
--terminal-element-ansi-magenta-bright: #e07de0;
|
|
47
|
+
--terminal-element-ansi-cyan: #00c5c7;
|
|
48
|
+
--terminal-element-ansi-cyan-bright: #5ffdff;
|
|
49
|
+
--terminal-element-ansi-white: #c7c7c7;
|
|
50
|
+
--terminal-element-ansi-white-bright: #feffff;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
:host([theme="light"]) {
|
|
54
|
+
/** UI colors */
|
|
55
|
+
--terminal-element-border-color: #cdcdcd;
|
|
56
|
+
--terminal-element-header-bg: #f4f4f8;
|
|
57
|
+
--terminal-element-header-border: #f1f1f4;
|
|
58
|
+
--terminal-element-header-border-bottom: #dfdfdf;
|
|
59
|
+
--terminal-element-header-directory-color: #393939;
|
|
60
|
+
--terminal-element-body-bg: #fff;
|
|
61
|
+
--terminal-element-body-border: transparent;
|
|
62
|
+
--terminal-element-body-content-color: #0c0c0c;
|
|
63
|
+
--terminal-element-caret-color: #808080;
|
|
64
|
+
|
|
65
|
+
/** ANSI colors */
|
|
66
|
+
--terminal-element-ansi-black: #000;
|
|
67
|
+
--terminal-element-ansi-black-bright: #808080;
|
|
68
|
+
--terminal-element-ansi-red: #900;
|
|
69
|
+
--terminal-element-ansi-red-bright: #e60000;
|
|
70
|
+
--terminal-element-ansi-green: #00a600;
|
|
71
|
+
--terminal-element-ansi-green-bright: #00d900;
|
|
72
|
+
--terminal-element-ansi-yellow: #990;
|
|
73
|
+
--terminal-element-ansi-yellow-bright: #e6e600;
|
|
74
|
+
--terminal-element-ansi-blue: #0000b2;
|
|
75
|
+
--terminal-element-ansi-blue-bright: #00f;
|
|
76
|
+
--terminal-element-ansi-magenta: #b200b2;
|
|
77
|
+
--terminal-element-ansi-magenta-bright: #e600e6;
|
|
78
|
+
--terminal-element-ansi-cyan: #00a6b2;
|
|
79
|
+
--terminal-element-ansi-cyan-bright: #00e6e6;
|
|
80
|
+
--terminal-element-ansi-white: #bfbfbf;
|
|
81
|
+
--terminal-element-ansi-white-bright: #e6e6e6;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
* {
|
|
85
|
+
box-sizing: border-box;
|
|
86
|
+
margin: 0;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
.terminal-element {
|
|
90
|
+
display: flex;
|
|
91
|
+
flex-direction: column;
|
|
92
|
+
overflow: hidden;
|
|
93
|
+
border: 1px solid var(--terminal-element-border-color);
|
|
94
|
+
border-radius: 10px;
|
|
95
|
+
box-shadow: var(--terminal-element-box-shadow);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
.terminal-element__header {
|
|
99
|
+
position: relative;
|
|
100
|
+
display: flex;
|
|
101
|
+
align-items: center;
|
|
102
|
+
justify-content: center;
|
|
103
|
+
height: 28px;
|
|
104
|
+
padding: 0 16px;
|
|
105
|
+
background-color: var(--terminal-element-header-bg);
|
|
106
|
+
border-top: 1px solid var(--terminal-element-header-border);
|
|
107
|
+
border-right: 1px solid var(--terminal-element-header-border);
|
|
108
|
+
border-bottom: 1px solid var(--terminal-element-header-border-bottom);
|
|
109
|
+
border-left: 1px solid var(--terminal-element-header-border);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
.terminal-element__header-controls {
|
|
113
|
+
position: absolute;
|
|
114
|
+
left: 8px;
|
|
115
|
+
display: flex;
|
|
116
|
+
flex-direction: row;
|
|
117
|
+
gap: 8px;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
.terminal-element__header-button {
|
|
121
|
+
width: 12px;
|
|
122
|
+
height: 12px;
|
|
123
|
+
border-radius: 999px;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
.terminal-element__header-button--red {
|
|
127
|
+
background-color: #fb4646;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
.terminal-element__header-button--yellow {
|
|
131
|
+
background-color: #fcae24;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
.terminal-element__header-button--green {
|
|
135
|
+
background-color: #28c132;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
.terminal-element__header-directory {
|
|
139
|
+
font-size: 12px;
|
|
140
|
+
font-weight: 600;
|
|
141
|
+
color: var(--terminal-element-header-directory-color);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
.terminal-element__body {
|
|
145
|
+
flex: 1;
|
|
146
|
+
padding: 4px;
|
|
147
|
+
background-color: var(--terminal-element-body-bg);
|
|
148
|
+
border-right: solid 1px var(--terminal-element-body-border);
|
|
149
|
+
border-bottom: solid 1px var(--terminal-element-body-border);
|
|
150
|
+
border-left: solid 1px var(--terminal-element-body-border);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
.terminal-element__body-content {
|
|
154
|
+
font-family: monospace;
|
|
155
|
+
font-size: var(--terminal-element-font-size);
|
|
156
|
+
font-weight: 400;
|
|
157
|
+
color: var(--terminal-element-body-content-color);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
.terminal-element__body-line {
|
|
161
|
+
word-break: break-all;
|
|
162
|
+
white-space: pre-wrap;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
.terminal-element__body-caret {
|
|
166
|
+
display: inline-block;
|
|
167
|
+
width: 8px;
|
|
168
|
+
height: var(--terminal-element-font-size);
|
|
169
|
+
vertical-align: bottom;
|
|
170
|
+
background-color: var(--terminal-element-caret-color);
|
|
171
|
+
}
|
|
172
|
+
`;
|
|
173
|
+
}
|
|
174
|
+
connectedCallback() {
|
|
175
|
+
super.connectedCallback(), this.animated && this._startAnimation();
|
|
176
|
+
}
|
|
177
|
+
disconnectedCallback() {
|
|
178
|
+
super.disconnectedCallback(), this._stopAnimation();
|
|
179
|
+
}
|
|
180
|
+
updated(e) {
|
|
181
|
+
super.updated(e), e.has("content") && this.animated && e.get("content") !== void 0 && (this._stopAnimation(), this._startAnimation());
|
|
182
|
+
}
|
|
183
|
+
_startAnimation() {
|
|
184
|
+
this._currentLineIndex = 0, this._currentCharInLine = 0, this._isAnimating = !0, this._isWaitingToRestart = !1, this._processCurrentLine();
|
|
185
|
+
}
|
|
186
|
+
_processCurrentLine() {
|
|
187
|
+
if (this._currentLineIndex >= this.content.length) {
|
|
188
|
+
this.loop ? this._animationTimer = setTimeout(() => {
|
|
189
|
+
this._isWaitingToRestart = !0, this.requestUpdate(), this._animationTimer = setTimeout(() => {
|
|
190
|
+
this._startAnimation();
|
|
191
|
+
}, this.delayBeforeRestart);
|
|
192
|
+
}, this.delayAfterComplete) : this._isAnimating = !1;
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
const e = this.content[this._currentLineIndex];
|
|
196
|
+
if (e.type === "input") this._tickInputLine();
|
|
197
|
+
else {
|
|
198
|
+
const t = ("delay" in e ? e.delay : 0) ?? 0;
|
|
199
|
+
t > 0 ? this._animationTimer = setTimeout(() => {
|
|
200
|
+
this._moveToNextLine();
|
|
201
|
+
}, t) : this._moveToNextLine();
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
_tickInputLine() {
|
|
205
|
+
const e = this.content[this._currentLineIndex];
|
|
206
|
+
if (e.type !== "input") return;
|
|
207
|
+
const t = e.text.length;
|
|
208
|
+
this._currentCharInLine < t ? (this._currentCharInLine++, this._animationTimer = setTimeout(() => this._tickInputLine(), this.typingSpeed)) : this._moveToNextLine();
|
|
209
|
+
}
|
|
210
|
+
_moveToNextLine() {
|
|
211
|
+
this._currentLineIndex++, this._currentCharInLine = 0, this._processCurrentLine();
|
|
212
|
+
}
|
|
213
|
+
_stopAnimation() {
|
|
214
|
+
this._animationTimer !== null && (clearTimeout(this._animationTimer), this._animationTimer = null), this._isAnimating = !1, this._isWaitingToRestart = !1;
|
|
215
|
+
}
|
|
216
|
+
_renderContent() {
|
|
217
|
+
return this.animated ? this._isWaitingToRestart ? null : !this._isAnimating && this._currentLineIndex >= this.content.length ? this._renderFullContent() : this._renderPartialContent() : this._renderFullContent();
|
|
218
|
+
}
|
|
219
|
+
_renderFullContent() {
|
|
220
|
+
return this.content.map((e) => e.type === "input" ? r`<div class="terminal-element__body-line"><span>${this.prompt} </span><span class="terminal-element__body-segment">${e.text}</span></div>` : "text" in e ? r`<div class="terminal-element__body-line">${e.text !== "" ? e.text : r` `}</div>` : r`<div class="terminal-element__body-line">${e.segments.length === 0 ? r` ` : e.segments.map((t) => r`<span style="color: ${t.color ? `var(--terminal-element-ansi-${t.color})` : "inherit"}; background-color: ${t.bg ? `var(--terminal-element-ansi-${t.bg})` : "inherit"};">${t.text}</span>`)}</div>`);
|
|
221
|
+
}
|
|
222
|
+
_renderPartialContent() {
|
|
223
|
+
const e = [];
|
|
224
|
+
for (let t = 0; t < this.content.length; t++) {
|
|
225
|
+
const l = this.content[t];
|
|
226
|
+
t < this._currentLineIndex ? e.push(this._renderFullLine(l)) : t === this._currentLineIndex && l.type === "input" && e.push(this._renderPartialInputLine(l));
|
|
227
|
+
}
|
|
228
|
+
return e;
|
|
229
|
+
}
|
|
230
|
+
_renderFullLine(e) {
|
|
231
|
+
return e.type === "input" ? r`<div class="terminal-element__body-line"><span>${this.prompt} </span><span class="terminal-element__body-segment">${e.text}</span></div>` : "text" in e ? r`<div class="terminal-element__body-line">${e.text !== "" ? e.text : r` `}</div>` : r`<div class="terminal-element__body-line">${e.segments.length === 0 ? r` ` : e.segments.map((t) => r`<span style="color: ${t.color ? `var(--terminal-element-ansi-${t.color})` : "inherit"}; background-color: ${t.bg ? `var(--terminal-element-ansi-${t.bg})` : "inherit"};">${t.text}</span>`)}</div>`;
|
|
232
|
+
}
|
|
233
|
+
_renderPartialInputLine(e) {
|
|
234
|
+
const t = e.text.slice(0, this._currentCharInLine);
|
|
235
|
+
return r`<div class="terminal-element__body-line"><span>${this.prompt} </span><span class="terminal-element__body-segment">${t}</span><span class="terminal-element__body-caret"></span></div>`;
|
|
236
|
+
}
|
|
237
|
+
render() {
|
|
238
|
+
return r`
|
|
239
|
+
<div
|
|
240
|
+
class="terminal-element"
|
|
241
|
+
style="width: ${this.width}; height: ${this.height};"
|
|
242
|
+
data-testid="terminal-element"
|
|
243
|
+
>
|
|
244
|
+
<div class="terminal-element__header">
|
|
245
|
+
<div class="terminal-element__header-controls">
|
|
246
|
+
<div
|
|
247
|
+
class="terminal-element__header-button terminal-element__header-button--red"
|
|
248
|
+
></div>
|
|
249
|
+
<div
|
|
250
|
+
class="terminal-element__header-button terminal-element__header-button--yellow"
|
|
251
|
+
></div>
|
|
252
|
+
<div
|
|
253
|
+
class="terminal-element__header-button terminal-element__header-button--green"
|
|
254
|
+
></div>
|
|
255
|
+
</div>
|
|
256
|
+
<div
|
|
257
|
+
class="terminal-element__header-directory"
|
|
258
|
+
data-testid="current-directory"
|
|
259
|
+
>
|
|
260
|
+
${this.currentDirectory}
|
|
261
|
+
</div>
|
|
262
|
+
</div>
|
|
263
|
+
<div class="terminal-element__body">
|
|
264
|
+
<div class="terminal-element__body-content" data-testid="content">
|
|
265
|
+
${this._renderContent()}
|
|
266
|
+
</div>
|
|
267
|
+
</div>
|
|
268
|
+
</div>
|
|
269
|
+
`;
|
|
270
|
+
}
|
|
271
|
+
};
|
|
272
|
+
i([a({ type: String })], n.prototype, "width", void 0);
|
|
273
|
+
i([a({ type: String })], n.prototype, "height", void 0);
|
|
274
|
+
i([a({
|
|
275
|
+
type: String,
|
|
276
|
+
reflect: !0
|
|
277
|
+
})], n.prototype, "theme", void 0);
|
|
278
|
+
i([a({ type: String })], n.prototype, "currentDirectory", void 0);
|
|
279
|
+
i([a({ type: String })], n.prototype, "prompt", void 0);
|
|
280
|
+
i([a({ type: Array })], n.prototype, "content", void 0);
|
|
281
|
+
i([a({ type: Boolean })], n.prototype, "animated", void 0);
|
|
282
|
+
i([a({ type: Number })], n.prototype, "typingSpeed", void 0);
|
|
283
|
+
i([a({ type: Boolean })], n.prototype, "loop", void 0);
|
|
284
|
+
i([a({ type: Number })], n.prototype, "delayAfterComplete", void 0);
|
|
285
|
+
i([a({ type: Number })], n.prototype, "delayBeforeRestart", void 0);
|
|
286
|
+
i([c()], n.prototype, "_currentLineIndex", void 0);
|
|
287
|
+
i([c()], n.prototype, "_currentCharInLine", void 0);
|
|
288
|
+
i([c()], n.prototype, "_isAnimating", void 0);
|
|
289
|
+
i([c()], n.prototype, "_isWaitingToRestart", void 0);
|
|
290
|
+
n = i([_("terminal-element")], n);
|
|
291
|
+
export {
|
|
292
|
+
n as TerminalElement
|
|
293
|
+
};
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { LitElement } from "lit";
|
|
2
|
+
import type { PropertyValues } from "lit";
|
|
3
|
+
export type ThemeType = "light" | "dark";
|
|
4
|
+
export type AnsiColorType = "black" | "black-bright" | "red" | "red-bright" | "green" | "green-bright" | "yellow" | "yellow-bright" | "blue" | "blue-bright" | "magenta" | "magenta-bright" | "cyan" | "cyan-bright" | "white" | "white-bright";
|
|
5
|
+
export type Segment = {
|
|
6
|
+
text: string;
|
|
7
|
+
color?: AnsiColorType;
|
|
8
|
+
bg?: AnsiColorType;
|
|
9
|
+
};
|
|
10
|
+
export type InputLine = {
|
|
11
|
+
type: "input";
|
|
12
|
+
text: string;
|
|
13
|
+
};
|
|
14
|
+
export type OutputLineText = {
|
|
15
|
+
type: "output";
|
|
16
|
+
text: string;
|
|
17
|
+
delay?: number;
|
|
18
|
+
};
|
|
19
|
+
export type OutputLineSegments = {
|
|
20
|
+
type: "output";
|
|
21
|
+
segments: Segment[];
|
|
22
|
+
delay?: number;
|
|
23
|
+
};
|
|
24
|
+
export type Line = InputLine | OutputLineText | OutputLineSegments;
|
|
25
|
+
export interface TerminalElementProps {
|
|
26
|
+
width?: string;
|
|
27
|
+
height?: string;
|
|
28
|
+
theme?: ThemeType;
|
|
29
|
+
currentDirectory?: string;
|
|
30
|
+
prompt?: string;
|
|
31
|
+
content?: Line[];
|
|
32
|
+
animated?: boolean;
|
|
33
|
+
typingSpeed?: number;
|
|
34
|
+
loop?: boolean;
|
|
35
|
+
delayAfterComplete?: number;
|
|
36
|
+
delayBeforeRestart?: number;
|
|
37
|
+
}
|
|
38
|
+
export declare class TerminalElement extends LitElement {
|
|
39
|
+
width: string;
|
|
40
|
+
height: string;
|
|
41
|
+
theme: ThemeType;
|
|
42
|
+
currentDirectory: string;
|
|
43
|
+
prompt: string;
|
|
44
|
+
content: Line[];
|
|
45
|
+
animated: boolean;
|
|
46
|
+
typingSpeed: number;
|
|
47
|
+
loop: boolean;
|
|
48
|
+
delayAfterComplete: number;
|
|
49
|
+
delayBeforeRestart: number;
|
|
50
|
+
private _currentLineIndex;
|
|
51
|
+
private _currentCharInLine;
|
|
52
|
+
private _isAnimating;
|
|
53
|
+
private _isWaitingToRestart;
|
|
54
|
+
private _animationTimer;
|
|
55
|
+
static styles: import("lit").CSSResult;
|
|
56
|
+
connectedCallback(): void;
|
|
57
|
+
disconnectedCallback(): void;
|
|
58
|
+
protected updated(changedProperties: PropertyValues): void;
|
|
59
|
+
private _startAnimation;
|
|
60
|
+
private _processCurrentLine;
|
|
61
|
+
private _tickInputLine;
|
|
62
|
+
private _moveToNextLine;
|
|
63
|
+
private _stopAnimation;
|
|
64
|
+
private _renderContent;
|
|
65
|
+
private _renderFullContent;
|
|
66
|
+
private _renderPartialContent;
|
|
67
|
+
private _renderFullLine;
|
|
68
|
+
private _renderPartialInputLine;
|
|
69
|
+
render(): import("lit").TemplateResult<1>;
|
|
70
|
+
}
|
|
71
|
+
declare global {
|
|
72
|
+
interface HTMLElementTagNameMap {
|
|
73
|
+
"terminal-element": TerminalElement;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
//# sourceMappingURL=terminal-element.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"terminal-element.d.ts","sourceRoot":"","sources":["../../src/terminal-element.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAa,MAAM,KAAK,CAAC;AAC5C,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,KAAK,CAAC;AAG1C,MAAM,MAAM,SAAS,GAAG,OAAO,GAAG,MAAM,CAAC;AAEzC,MAAM,MAAM,aAAa,GACrB,OAAO,GACP,cAAc,GACd,KAAK,GACL,YAAY,GACZ,OAAO,GACP,cAAc,GACd,QAAQ,GACR,eAAe,GACf,MAAM,GACN,aAAa,GACb,SAAS,GACT,gBAAgB,GAChB,MAAM,GACN,aAAa,GACb,OAAO,GACP,cAAc,CAAC;AAEnB,MAAM,MAAM,OAAO,GAAG;IACpB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,aAAa,CAAC;IACtB,EAAE,CAAC,EAAE,aAAa,CAAC;CACpB,CAAC;AAEF,MAAM,MAAM,SAAS,GAAG;IACtB,IAAI,EAAE,OAAO,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;CACd,CAAC;AAEF,MAAM,MAAM,cAAc,GAAG;IAC3B,IAAI,EAAE,QAAQ,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF,MAAM,MAAM,kBAAkB,GAAG;IAC/B,IAAI,EAAE,QAAQ,CAAC;IACf,QAAQ,EAAE,OAAO,EAAE,CAAC;IACpB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF,MAAM,MAAM,IAAI,GAAG,SAAS,GAAG,cAAc,GAAG,kBAAkB,CAAC;AAEnE,MAAM,WAAW,oBAAoB;IACnC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,SAAS,CAAC;IAClB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,IAAI,EAAE,CAAC;IACjB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,kBAAkB,CAAC,EAAE,MAAM,CAAC;CAC7B;AAED,qBACa,eAAgB,SAAQ,UAAU;IACjB,KAAK,SAAW;IAChB,MAAM,SAAW;IACF,KAAK,EAAE,SAAS,CAAU;IACzC,gBAAgB,SAAM;IACtB,MAAM,SAAO;IACd,OAAO,EAAE,IAAI,EAAE,CAAM;IACnB,QAAQ,UAAS;IAClB,WAAW,SAAO;IACjB,IAAI,UAAS;IACd,kBAAkB,SAAQ;IAC1B,kBAAkB,SAAQ;IAE7C,OAAO,CAAC,iBAAiB,CAAK;IAC9B,OAAO,CAAC,kBAAkB,CAAK;IAC/B,OAAO,CAAC,YAAY,CAAS;IAC7B,OAAO,CAAC,mBAAmB,CAAS;IAE7C,OAAO,CAAC,eAAe,CAAuB;IAE9C,MAAM,CAAC,MAAM,0BA8JX;IAEF,iBAAiB;IAOjB,oBAAoB;IAKpB,SAAS,CAAC,OAAO,CAAC,iBAAiB,EAAE,cAAc;IAcnD,OAAO,CAAC,eAAe;IAQvB,OAAO,CAAC,mBAAmB;IAqC3B,OAAO,CAAC,cAAc;IAiBtB,OAAO,CAAC,eAAe;IAMvB,OAAO,CAAC,cAAc;IAStB,OAAO,CAAC,cAAc;IAmBtB,OAAO,CAAC,kBAAkB;IAqB1B,OAAO,CAAC,qBAAqB;IAoB7B,OAAO,CAAC,eAAe;IAkBvB,OAAO,CAAC,uBAAuB;IAM/B,MAAM;CAkCP;AAED,OAAO,CAAC,MAAM,CAAC;IACb,UAAU,qBAAqB;QAC7B,kBAAkB,EAAE,eAAe,CAAC;KACrC;CACF"}
|
package/package.json
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "terminal-element",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "A Web Component for rendering terminal-style previews",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/terminal-element.es.js",
|
|
7
|
+
"module": "dist/terminal-element.es.js",
|
|
8
|
+
"types": "dist/types/terminal-element.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"types": "./dist/types/terminal-element.d.ts",
|
|
12
|
+
"import": "./dist/terminal-element.es.js"
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"files": [
|
|
16
|
+
"dist",
|
|
17
|
+
"README.md",
|
|
18
|
+
"LICENSE"
|
|
19
|
+
],
|
|
20
|
+
"repository": {
|
|
21
|
+
"type": "git",
|
|
22
|
+
"url": "https://github.com/spider-hand/terminal-element.git"
|
|
23
|
+
},
|
|
24
|
+
"keywords": [
|
|
25
|
+
"terminal",
|
|
26
|
+
"cli",
|
|
27
|
+
"web-component",
|
|
28
|
+
"webcomponent",
|
|
29
|
+
"lit"
|
|
30
|
+
],
|
|
31
|
+
"author": "Akinori Hoshina <creative.spider.hand@gmail.com>",
|
|
32
|
+
"license": "MIT",
|
|
33
|
+
"bugs": {
|
|
34
|
+
"url": "https://github.com/spider-hand/terminal-element/issues"
|
|
35
|
+
},
|
|
36
|
+
"homepage": "https://github.com/spider-hand/terminal-element#readme",
|
|
37
|
+
"peerDependencies": {
|
|
38
|
+
"lit": "^3.0.0"
|
|
39
|
+
},
|
|
40
|
+
"devDependencies": {
|
|
41
|
+
"@chromatic-com/storybook": "^5.0.2",
|
|
42
|
+
"@eslint/css": "^1.0.0",
|
|
43
|
+
"@eslint/js": "^10.0.1",
|
|
44
|
+
"@storybook/addon-a11y": "^10.3.1",
|
|
45
|
+
"@storybook/addon-docs": "^10.3.1",
|
|
46
|
+
"@storybook/addon-vitest": "^10.3.1",
|
|
47
|
+
"@storybook/web-components-vite": "^10.3.1",
|
|
48
|
+
"@vitest/browser-playwright": "^4.1.0",
|
|
49
|
+
"@vitest/coverage-v8": "^4.1.0",
|
|
50
|
+
"chromatic": "^16.0.0",
|
|
51
|
+
"eslint": "^10.1.0",
|
|
52
|
+
"eslint-config-prettier": "^10.1.8",
|
|
53
|
+
"eslint-plugin-lit": "^2.2.1",
|
|
54
|
+
"eslint-plugin-storybook": "^10.3.1",
|
|
55
|
+
"globals": "^17.4.0",
|
|
56
|
+
"husky": "^9.1.7",
|
|
57
|
+
"lint-staged": "^16.4.0",
|
|
58
|
+
"lit": "^3.3.2",
|
|
59
|
+
"playwright": "^1.58.2",
|
|
60
|
+
"postcss-lit": "^1.4.1",
|
|
61
|
+
"prettier": "^3.8.1",
|
|
62
|
+
"storybook": "^10.3.1",
|
|
63
|
+
"stylelint": "^17.5.0",
|
|
64
|
+
"stylelint-config-recess-order": "^7.7.0",
|
|
65
|
+
"stylelint-config-standard": "^40.0.0",
|
|
66
|
+
"typescript": "~5.9.3",
|
|
67
|
+
"typescript-eslint": "^8.57.1",
|
|
68
|
+
"vite": "^8.0.1",
|
|
69
|
+
"vitest": "^4.1.0",
|
|
70
|
+
"vitest-browser-lit": "^1.0.1"
|
|
71
|
+
},
|
|
72
|
+
"lint-staged": {
|
|
73
|
+
"*.{js,ts}": [
|
|
74
|
+
"eslint --fix",
|
|
75
|
+
"prettier --write"
|
|
76
|
+
],
|
|
77
|
+
"*.ts": "stylelint --fix"
|
|
78
|
+
},
|
|
79
|
+
"scripts": {
|
|
80
|
+
"dev": "vite",
|
|
81
|
+
"build": "vite build",
|
|
82
|
+
"build:types": "tsc -p tsconfig.build.types.json",
|
|
83
|
+
"build:all": "pnpm run build && pnpm run build:types",
|
|
84
|
+
"preview": "vite preview",
|
|
85
|
+
"lint": "eslint --fix",
|
|
86
|
+
"format": "prettier --write .",
|
|
87
|
+
"storybook": "storybook dev -p 6006",
|
|
88
|
+
"build-storybook": "storybook build",
|
|
89
|
+
"test": "vitest --config=vitest.browser.config.ts",
|
|
90
|
+
"test:coverage": "vitest run --config=vitest.browser.config.ts --coverage"
|
|
91
|
+
}
|
|
92
|
+
}
|