@valyrianjs/terminal 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.
Files changed (107) hide show
  1. package/README.md +105 -55
  2. package/dist/ansi.d.ts +20 -4
  3. package/dist/ansi.d.ts.map +1 -1
  4. package/dist/ansi.js +171 -47
  5. package/dist/ansi.js.map +1 -1
  6. package/dist/editor-state.d.ts +22 -0
  7. package/dist/editor-state.d.ts.map +1 -0
  8. package/dist/editor-state.js +110 -0
  9. package/dist/editor-state.js.map +1 -0
  10. package/dist/events.d.ts +1 -4
  11. package/dist/events.d.ts.map +1 -1
  12. package/dist/events.js +15 -38
  13. package/dist/events.js.map +1 -1
  14. package/dist/index.d.ts +4 -2
  15. package/dist/index.d.ts.map +1 -1
  16. package/dist/index.js +3 -1
  17. package/dist/index.js.map +1 -1
  18. package/dist/keymap.d.ts +7 -0
  19. package/dist/keymap.d.ts.map +1 -0
  20. package/dist/keymap.js +133 -0
  21. package/dist/keymap.js.map +1 -0
  22. package/dist/layout.d.ts +10 -1
  23. package/dist/layout.d.ts.map +1 -1
  24. package/dist/layout.js +97 -7
  25. package/dist/layout.js.map +1 -1
  26. package/dist/mouse.d.ts +1 -0
  27. package/dist/mouse.d.ts.map +1 -1
  28. package/dist/mouse.js +24 -1
  29. package/dist/mouse.js.map +1 -1
  30. package/dist/output-writer.d.ts +9 -0
  31. package/dist/output-writer.d.ts.map +1 -0
  32. package/dist/output-writer.js +79 -0
  33. package/dist/output-writer.js.map +1 -0
  34. package/dist/paste.d.ts +7 -0
  35. package/dist/paste.d.ts.map +1 -0
  36. package/dist/paste.js +18 -0
  37. package/dist/paste.js.map +1 -0
  38. package/dist/primitives.d.ts +8 -1
  39. package/dist/primitives.d.ts.map +1 -1
  40. package/dist/primitives.js +9 -1
  41. package/dist/primitives.js.map +1 -1
  42. package/dist/render.d.ts +8 -3
  43. package/dist/render.d.ts.map +1 -1
  44. package/dist/render.js +840 -67
  45. package/dist/render.js.map +1 -1
  46. package/dist/runtime.d.ts +29 -0
  47. package/dist/runtime.d.ts.map +1 -0
  48. package/dist/runtime.js +215 -0
  49. package/dist/runtime.js.map +1 -0
  50. package/dist/scheduler.d.ts +8 -0
  51. package/dist/scheduler.d.ts.map +1 -0
  52. package/dist/scheduler.js +24 -0
  53. package/dist/scheduler.js.map +1 -0
  54. package/dist/session.d.ts.map +1 -1
  55. package/dist/session.js +729 -199
  56. package/dist/session.js.map +1 -1
  57. package/dist/stream-log.d.ts +40 -0
  58. package/dist/stream-log.d.ts.map +1 -0
  59. package/dist/stream-log.js +73 -0
  60. package/dist/stream-log.js.map +1 -0
  61. package/dist/text.d.ts +3 -0
  62. package/dist/text.d.ts.map +1 -0
  63. package/dist/text.js +19 -0
  64. package/dist/text.js.map +1 -0
  65. package/dist/theme.d.ts +7 -0
  66. package/dist/theme.d.ts.map +1 -0
  67. package/dist/theme.js +254 -0
  68. package/dist/theme.js.map +1 -0
  69. package/dist/tree.d.ts +2 -0
  70. package/dist/tree.d.ts.map +1 -1
  71. package/dist/tree.js +42 -1
  72. package/dist/tree.js.map +1 -1
  73. package/dist/types.d.ts +183 -18
  74. package/dist/types.d.ts.map +1 -1
  75. package/docs/api-reference.md +302 -136
  76. package/docs/assets/quick-note.svg +13 -0
  77. package/docs/cookbook.md +297 -202
  78. package/docs/core-concepts.md +143 -55
  79. package/docs/getting-started.md +209 -90
  80. package/docs/interaction-model.md +95 -61
  81. package/docs/primitive-gallery.md +365 -0
  82. package/docs/session-runtime.md +132 -363
  83. package/docs/valyrian-modules.md +3196 -0
  84. package/llms-full.txt +5357 -0
  85. package/package.json +21 -8
  86. package/src/ansi.ts +269 -0
  87. package/src/clipboard.ts +76 -0
  88. package/src/editor-state.ts +162 -0
  89. package/src/events.ts +163 -0
  90. package/src/index.ts +92 -0
  91. package/src/keymap.ts +151 -0
  92. package/src/layout.ts +282 -0
  93. package/src/mouse.ts +68 -0
  94. package/src/output-writer.ts +93 -0
  95. package/src/paste.ts +23 -0
  96. package/src/primitives.ts +52 -0
  97. package/src/render.ts +1107 -0
  98. package/src/runtime.ts +273 -0
  99. package/src/scheduler.ts +33 -0
  100. package/src/session.ts +1260 -0
  101. package/src/stream-log.ts +96 -0
  102. package/src/text.ts +20 -0
  103. package/src/theme.ts +263 -0
  104. package/src/tree.ts +169 -0
  105. package/src/types.ts +523 -0
  106. package/tsconfig.json +4 -7
  107. package/docs/local-demo.md +0 -28
package/src/runtime.ts ADDED
@@ -0,0 +1,273 @@
1
+ import { document, Event } from "valyrian.js/node";
2
+ import { mount, setPropNameReserved, unmount, update, v } from "valyrian.js";
3
+ import { createEffect } from "valyrian.js/pulses";
4
+
5
+ import type { TerminalNode, TerminalPrimitiveTag } from "./types.js";
6
+
7
+ type DomNode = {
8
+ nodeType: number;
9
+ nodeName: string;
10
+ nodeValue?: string;
11
+ childNodes?: DomNode[];
12
+ attributes?: Array<{ nodeName: string; nodeValue: any }>;
13
+ vnode?: { props?: Record<string, any> };
14
+ dispatchEvent?(event: Event): boolean;
15
+ };
16
+
17
+ type ProjectionCallback = () => void;
18
+
19
+ type TerminalEventPayload = Record<string, any>;
20
+
21
+ const wrappedComponents = new WeakMap<object, any>();
22
+ let nextRuntimeId = 1;
23
+ let currentRuntimeId: number | null = null;
24
+
25
+ setPropNameReserved("style");
26
+
27
+ function isTerminalTag(tag: string): tag is TerminalPrimitiveTag {
28
+ return tag.startsWith("terminal-");
29
+ }
30
+
31
+ function propsFromDom(node: DomNode) {
32
+ if (node.vnode?.props?.__terminalProps) {
33
+ return { ...node.vnode.props.__terminalProps };
34
+ }
35
+ if (node.vnode?.props) {
36
+ return { ...node.vnode.props };
37
+ }
38
+
39
+ const props: Record<string, any> = {};
40
+ for (const attr of node.attributes || []) {
41
+ props[attr.nodeName] = attr.nodeValue;
42
+ }
43
+ return props;
44
+ }
45
+
46
+ function collectTerminalDomNodes(node: DomNode, registry: Map<string, DomNode>) {
47
+ if (node.nodeType === 1 && isTerminalTag(node.nodeName.toLowerCase())) {
48
+ const id = propsFromDom(node).id;
49
+ if (typeof id === "string" && id.length > 0) {
50
+ registry.set(id, node);
51
+ }
52
+ }
53
+
54
+ for (const child of node.childNodes || []) {
55
+ collectTerminalDomNodes(child, registry);
56
+ }
57
+ }
58
+
59
+ function hasEventListener(node: DomNode, eventName: string) {
60
+ return typeof node.vnode?.props?.[`on${eventName}`] === "function";
61
+ }
62
+
63
+ function resolveDispatchEventName(node: DomNode, eventName: string) {
64
+ if (eventName === "press" && !hasEventListener(node, "press") && hasEventListener(node, "click")) {
65
+ return "click";
66
+ }
67
+ if (eventName === "change" && !hasEventListener(node, "change") && hasEventListener(node, "input")) {
68
+ return "input";
69
+ }
70
+ return eventName;
71
+ }
72
+
73
+ function createTerminalEvent(eventName: string, payload: TerminalEventPayload) {
74
+ const event = new Event(eventName, { bubbles: true, cancelable: true }) as Event & TerminalEventPayload;
75
+ for (const key of ["bubbles", "cancelable", "currentTarget", "defaultPrevented", "options", "propagationStopped", "target"] as const) {
76
+ Object.defineProperty(event, key, { enumerable: false, configurable: true, writable: true, value: event[key] });
77
+ }
78
+ Object.assign(event, payload, { type: eventName });
79
+ return event;
80
+ }
81
+
82
+ function domNodeToTerminalNodes(node: DomNode): TerminalNode[] {
83
+ if (node.nodeType === 3) {
84
+ const value = String(node.nodeValue ?? "");
85
+ return value.length ? [{ type: "text", value }] : [];
86
+ }
87
+
88
+ if (node.nodeType !== 1 && node.nodeType !== 9 && node.nodeType !== 11) {
89
+ return [];
90
+ }
91
+
92
+ const tag = node.nodeName.toLowerCase();
93
+ const children = Array.from(node.childNodes || []).flatMap(domNodeToTerminalNodes);
94
+
95
+ if (!isTerminalTag(tag)) {
96
+ return children;
97
+ }
98
+
99
+ const props = propsFromDom(node);
100
+ Reflect.deleteProperty(props, "v-update");
101
+
102
+ return [{ type: "element", tag, props, children }];
103
+ }
104
+
105
+
106
+ function isVnodeLike(value: any): value is { tag: any; props: Record<string, any> | null; children: any[]; key?: string | number } {
107
+ return Boolean(value && typeof value === "object" && "tag" in value && "children" in value);
108
+ }
109
+
110
+ function isPojoComponent(value: any): value is { view: Function } {
111
+ return Boolean(value && typeof value === "object" && typeof value.view === "function");
112
+ }
113
+
114
+ function wrapComponent(component: any) {
115
+ const key = (typeof component === "function" || isPojoComponent(component)) ? component : null;
116
+ if (!key) {
117
+ return component;
118
+ }
119
+
120
+ const existing = wrappedComponents.get(key);
121
+ if (existing) {
122
+ return existing;
123
+ }
124
+
125
+ const wrapped = function TerminalValyrianComponent(props: Record<string, any>, children: any[]) {
126
+ const result = isPojoComponent(component)
127
+ ? component.view.call(component, props, children)
128
+ : component(props, children);
129
+ return normalizeValyrianInput(result);
130
+ };
131
+
132
+ wrappedComponents.set(key, wrapped);
133
+ return wrapped;
134
+ }
135
+
136
+ function normalizeValyrianInput(input: any): any {
137
+ if (Array.isArray(input)) {
138
+ return input.map(normalizeValyrianInput);
139
+ }
140
+
141
+ if (typeof input === "function" || isPojoComponent(input)) {
142
+ return v(wrapComponent(input), {}, []);
143
+ }
144
+
145
+ if (!isVnodeLike(input)) {
146
+ return input;
147
+ }
148
+
149
+ const props = { ...((input.props && input.props.__terminalProps) || input.props || {}) };
150
+ const children = Array.isArray(input.children)
151
+ ? input.children.map((child) => {
152
+ if (props["v-for"] && typeof child === "function") {
153
+ return (...args: any[]) => normalizeValyrianInput(child(...args));
154
+ }
155
+ return normalizeValyrianInput(child);
156
+ })
157
+ : [];
158
+ const tag = typeof input.tag === "string" ? input.tag : wrapComponent(input.tag);
159
+ if (typeof tag === "string" && isTerminalTag(tag)) {
160
+ const terminalProps = { ...props };
161
+ if (tag === "terminal-button" && typeof props.action === "function" && typeof props.onpress !== "function" && typeof props.onclick !== "function") {
162
+ props.onclick = props.action;
163
+ }
164
+ if (typeof props.style === "object") {
165
+ Reflect.deleteProperty(props, "style");
166
+ }
167
+ props.__terminalProps = terminalProps;
168
+ }
169
+ return v(tag, props, ...children);
170
+ }
171
+
172
+ export function projectTerminalDom(container: DomNode): TerminalNode[] {
173
+ return Array.from(container.childNodes || []).flatMap(domNodeToTerminalNodes);
174
+ }
175
+
176
+ export interface ValyrianTerminalRuntime {
177
+ update(): void;
178
+ project(): TerminalNode[];
179
+ dispatchTerminalEvent(id: string, eventName: string, payload: TerminalEventPayload): boolean;
180
+ destroy(): void;
181
+ }
182
+
183
+ function createContainer() {
184
+ return document.createElement("div") as unknown as DomNode;
185
+ }
186
+
187
+ function mountIntoContainer(container: DomNode, input: any) {
188
+ const TerminalRoot = () => v("div", {}, normalizeValyrianInput(input));
189
+ mount(container as any, TerminalRoot);
190
+ }
191
+
192
+ export function createValyrianTerminalRuntime(input: any, onUpdated: ProjectionCallback): ValyrianTerminalRuntime {
193
+ const container = createContainer();
194
+ const nodeById = new Map<string, DomNode>();
195
+ let stopEffect: (() => void) | null = null;
196
+ const runtimeId = nextRuntimeId++;
197
+
198
+ const refreshRegistry = () => {
199
+ nodeById.clear();
200
+ collectTerminalDomNodes(container, nodeById);
201
+ };
202
+
203
+ const ensureMounted = () => {
204
+ if (currentRuntimeId !== runtimeId) {
205
+ mountIntoContainer(container, input);
206
+ currentRuntimeId = runtimeId;
207
+ }
208
+ };
209
+
210
+ const cleanupRuntime = () => {
211
+ stopEffect?.();
212
+ if (currentRuntimeId === runtimeId) {
213
+ unmount();
214
+ currentRuntimeId = null;
215
+ }
216
+ };
217
+
218
+ try {
219
+ ensureMounted();
220
+ const snapshotMode = globalThis.process?.argv?.includes("--snapshot") || globalThis.process?.env?.VALYRIAN_TERMINAL_EXAMPLE_SNAPSHOT === "1";
221
+ if (!snapshotMode) {
222
+ stopEffect = createEffect(() => {
223
+ ensureMounted();
224
+ update();
225
+ onUpdated();
226
+ });
227
+ }
228
+ } catch (error) {
229
+ cleanupRuntime();
230
+ throw error;
231
+ }
232
+
233
+ return {
234
+ update() {
235
+ ensureMounted();
236
+ update();
237
+ refreshRegistry();
238
+ },
239
+ project() {
240
+ refreshRegistry();
241
+ return projectTerminalDom(container);
242
+ },
243
+ dispatchTerminalEvent(id: string, eventName: string, payload: TerminalEventPayload) {
244
+ ensureMounted();
245
+ refreshRegistry();
246
+ const node = nodeById.get(id);
247
+ if (!node || typeof node.dispatchEvent !== "function") {
248
+ return false;
249
+ }
250
+ const resolvedEventName = resolveDispatchEventName(node, eventName);
251
+ if (!hasEventListener(node, resolvedEventName)) {
252
+ return false;
253
+ }
254
+ return node.dispatchEvent(createTerminalEvent(resolvedEventName, payload));
255
+ },
256
+ destroy() {
257
+ nodeById.clear();
258
+ cleanupRuntime();
259
+ }
260
+ };
261
+ }
262
+
263
+ export function renderValyrianTerminal(input: any): TerminalNode[] {
264
+ const container = createContainer();
265
+
266
+ try {
267
+ mountIntoContainer(container, input);
268
+ currentRuntimeId = null;
269
+ return projectTerminalDom(container);
270
+ } finally {
271
+ unmount();
272
+ }
273
+ }
@@ -0,0 +1,33 @@
1
+ export interface RenderScheduler {
2
+ requestRender(): void;
3
+ flush(): void;
4
+ cancel(): void;
5
+ }
6
+
7
+ export type ScheduleRenderCallback = (callback: () => void) => void;
8
+
9
+ export function createRenderScheduler(render: () => void, schedule: ScheduleRenderCallback = queueMicrotask): RenderScheduler {
10
+ let pending = false;
11
+
12
+ function flush() {
13
+ if (!pending) {
14
+ return;
15
+ }
16
+ pending = false;
17
+ render();
18
+ }
19
+
20
+ return {
21
+ requestRender() {
22
+ if (pending) {
23
+ return;
24
+ }
25
+ pending = true;
26
+ schedule(flush);
27
+ },
28
+ flush,
29
+ cancel() {
30
+ pending = false;
31
+ }
32
+ };
33
+ }