@svelterm/core 0.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.
Files changed (99) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +174 -0
  3. package/dist/src/components/spinner.d.ts +11 -0
  4. package/dist/src/components/spinner.js +19 -0
  5. package/dist/src/components/text-buffer.d.ts +21 -0
  6. package/dist/src/components/text-buffer.js +87 -0
  7. package/dist/src/css/animation-runner.d.ts +17 -0
  8. package/dist/src/css/animation-runner.js +72 -0
  9. package/dist/src/css/animation.d.ts +5 -0
  10. package/dist/src/css/animation.js +6 -0
  11. package/dist/src/css/calc.d.ts +5 -0
  12. package/dist/src/css/calc.js +130 -0
  13. package/dist/src/css/color.d.ts +1 -0
  14. package/dist/src/css/color.js +157 -0
  15. package/dist/src/css/compute.d.ts +63 -0
  16. package/dist/src/css/compute.js +606 -0
  17. package/dist/src/css/defaults.d.ts +8 -0
  18. package/dist/src/css/defaults.js +44 -0
  19. package/dist/src/css/incremental.d.ts +9 -0
  20. package/dist/src/css/incremental.js +46 -0
  21. package/dist/src/css/index.d.ts +5 -0
  22. package/dist/src/css/index.js +3 -0
  23. package/dist/src/css/media.d.ts +11 -0
  24. package/dist/src/css/media.js +59 -0
  25. package/dist/src/css/parser.d.ts +20 -0
  26. package/dist/src/css/parser.js +241 -0
  27. package/dist/src/css/selector.d.ts +17 -0
  28. package/dist/src/css/selector.js +272 -0
  29. package/dist/src/css/specificity.d.ts +7 -0
  30. package/dist/src/css/specificity.js +89 -0
  31. package/dist/src/css/values.d.ts +17 -0
  32. package/dist/src/css/values.js +58 -0
  33. package/dist/src/css/variables.d.ts +6 -0
  34. package/dist/src/css/variables.js +42 -0
  35. package/dist/src/debug/console.d.ts +16 -0
  36. package/dist/src/debug/console.js +65 -0
  37. package/dist/src/debug/server.d.ts +22 -0
  38. package/dist/src/debug/server.js +90 -0
  39. package/dist/src/headless.d.ts +21 -0
  40. package/dist/src/headless.js +26 -0
  41. package/dist/src/index.d.ts +18 -0
  42. package/dist/src/index.js +485 -0
  43. package/dist/src/input/dispatch.d.ts +18 -0
  44. package/dist/src/input/dispatch.js +70 -0
  45. package/dist/src/input/focus.d.ts +18 -0
  46. package/dist/src/input/focus.js +81 -0
  47. package/dist/src/input/hit.d.ts +3 -0
  48. package/dist/src/input/hit.js +29 -0
  49. package/dist/src/input/keyboard.d.ts +9 -0
  50. package/dist/src/input/keyboard.js +100 -0
  51. package/dist/src/input/mouse.d.ts +7 -0
  52. package/dist/src/input/mouse.js +35 -0
  53. package/dist/src/input/scroll.d.ts +2 -0
  54. package/dist/src/input/scroll.js +24 -0
  55. package/dist/src/layout/cache.d.ts +4 -0
  56. package/dist/src/layout/cache.js +8 -0
  57. package/dist/src/layout/engine.d.ts +9 -0
  58. package/dist/src/layout/engine.js +455 -0
  59. package/dist/src/layout/flex.d.ts +4 -0
  60. package/dist/src/layout/flex.js +30 -0
  61. package/dist/src/layout/incremental.d.ts +8 -0
  62. package/dist/src/layout/incremental.js +58 -0
  63. package/dist/src/layout/size.d.ts +2 -0
  64. package/dist/src/layout/size.js +25 -0
  65. package/dist/src/layout/text.d.ts +7 -0
  66. package/dist/src/layout/text.js +52 -0
  67. package/dist/src/render/ansi.d.ts +23 -0
  68. package/dist/src/render/ansi.js +108 -0
  69. package/dist/src/render/border.d.ts +4 -0
  70. package/dist/src/render/border.js +60 -0
  71. package/dist/src/render/buffer.d.ts +23 -0
  72. package/dist/src/render/buffer.js +70 -0
  73. package/dist/src/render/context.d.ts +19 -0
  74. package/dist/src/render/context.js +98 -0
  75. package/dist/src/render/diff.d.ts +2 -0
  76. package/dist/src/render/diff.js +53 -0
  77. package/dist/src/render/incremental-paint.d.ts +10 -0
  78. package/dist/src/render/incremental-paint.js +94 -0
  79. package/dist/src/render/paint-text.d.ts +29 -0
  80. package/dist/src/render/paint-text.js +120 -0
  81. package/dist/src/render/paint.d.ts +5 -0
  82. package/dist/src/render/paint.js +220 -0
  83. package/dist/src/render/queue.d.ts +24 -0
  84. package/dist/src/render/queue.js +54 -0
  85. package/dist/src/render/scrollbar.d.ts +3 -0
  86. package/dist/src/render/scrollbar.js +19 -0
  87. package/dist/src/render/snapshot.d.ts +18 -0
  88. package/dist/src/render/snapshot.js +126 -0
  89. package/dist/src/renderer/default.d.ts +3 -0
  90. package/dist/src/renderer/default.js +3 -0
  91. package/dist/src/renderer/index.d.ts +11 -0
  92. package/dist/src/renderer/index.js +116 -0
  93. package/dist/src/renderer/node.d.ts +44 -0
  94. package/dist/src/renderer/node.js +153 -0
  95. package/dist/src/terminal/screen.d.ts +10 -0
  96. package/dist/src/terminal/screen.js +31 -0
  97. package/dist/src/terminal/stdin-router.d.ts +31 -0
  98. package/dist/src/terminal/stdin-router.js +133 -0
  99. package/package.json +64 -0
@@ -0,0 +1,116 @@
1
+ import { createRenderer as svelteCreateRenderer } from 'svelte/renderer';
2
+ import { TermNode } from './node.js';
3
+ export function createTermRenderer() {
4
+ return svelteCreateRenderer({
5
+ createFragment() {
6
+ return new TermNode('fragment');
7
+ },
8
+ createElement(name) {
9
+ return new TermNode('element', name);
10
+ },
11
+ createTextNode(data) {
12
+ return new TermNode('text', data);
13
+ },
14
+ createComment(data) {
15
+ return new TermNode('comment', data);
16
+ },
17
+ nodeType(node) {
18
+ return node.nodeType;
19
+ },
20
+ getNodeValue(node) {
21
+ if (node.nodeType === 'text')
22
+ return node.text ?? null;
23
+ if (node.nodeType === 'comment')
24
+ return node.text ?? null;
25
+ return null;
26
+ },
27
+ getAttribute(element, name) {
28
+ return element.attributes.get(name) ?? null;
29
+ },
30
+ setAttribute(element, key, value) {
31
+ const ctx = element.ctx;
32
+ if (ctx) {
33
+ ctx.onSetAttribute(element, key, String(value));
34
+ }
35
+ else {
36
+ element.attributes.set(key, String(value));
37
+ }
38
+ },
39
+ removeAttribute(element, name) {
40
+ const ctx = element.ctx;
41
+ if (ctx) {
42
+ ctx.onRemoveAttribute(element, name);
43
+ }
44
+ else {
45
+ element.attributes.delete(name);
46
+ }
47
+ },
48
+ hasAttribute(element, name) {
49
+ return element.attributes.has(name);
50
+ },
51
+ setText(node, text) {
52
+ if (node.nodeType === 'text' || node.nodeType === 'comment') {
53
+ const ctx = node.ctx;
54
+ if (ctx) {
55
+ ctx.onSetText(node, text);
56
+ }
57
+ else {
58
+ node.text = text;
59
+ }
60
+ }
61
+ else {
62
+ node.children = [];
63
+ const textNode = new TermNode('text', text);
64
+ textNode.parent = node;
65
+ textNode.ctx = node.ctx;
66
+ node.children.push(textNode);
67
+ node.ctx?.onInsert(node, textNode);
68
+ }
69
+ },
70
+ getFirstChild(element) {
71
+ return element.getFirstChild();
72
+ },
73
+ getLastChild(element) {
74
+ return element.getLastChild();
75
+ },
76
+ getNextSibling(node) {
77
+ return node.getNextSibling();
78
+ },
79
+ insert(parent, node, anchor) {
80
+ parent.insertBefore(node, anchor);
81
+ parent.ctx?.onInsert(parent, node);
82
+ },
83
+ remove(node) {
84
+ const parent = node.parent;
85
+ const ctx = parent?.ctx ?? null;
86
+ node.remove();
87
+ if (ctx && parent)
88
+ ctx.onRemove(node, parent);
89
+ },
90
+ getParent(node) {
91
+ return node.parent;
92
+ },
93
+ addEventListener(target, type, handler) {
94
+ let handlers = target.listeners.get(type);
95
+ if (!handlers) {
96
+ handlers = new Set();
97
+ target.listeners.set(type, handlers);
98
+ }
99
+ handlers.add(handler);
100
+ },
101
+ removeEventListener(target, type, handler) {
102
+ const handlers = target.listeners.get(type);
103
+ if (handlers) {
104
+ handlers.delete(handler);
105
+ }
106
+ },
107
+ });
108
+ }
109
+ /**
110
+ * Keep the custom renderer active globally so Svelte's effects
111
+ * use our renderer methods (setText, setAttribute, etc.) instead
112
+ * of falling back to DOM operations (node.nodeValue, etc.).
113
+ *
114
+ * Call this AFTER renderer.render() which pops the renderer.
115
+ */
116
+ export { TermNode } from './node.js';
@@ -0,0 +1,44 @@
1
+ import type { ResolvedStyle } from '../css/compute.js';
2
+ import type { LayoutBox } from '../layout/engine.js';
3
+ import type { RenderContext } from '../render/context.js';
4
+ import { TextBuffer } from '../components/text-buffer.js';
5
+ export type NodeType = 'element' | 'text' | 'comment' | 'fragment';
6
+ export interface RenderCache {
7
+ resolvedStyle: ResolvedStyle | null;
8
+ layoutBox: LayoutBox | null;
9
+ classAttr: string;
10
+ }
11
+ export declare class TermNode {
12
+ readonly id: number;
13
+ readonly nodeType: NodeType;
14
+ tag: string | undefined;
15
+ text: string | undefined;
16
+ ctx: RenderContext | null;
17
+ parent: TermNode | null;
18
+ children: TermNode[];
19
+ attributes: Map<string, string>;
20
+ listeners: Map<string, Set<(...args: any[]) => void>>;
21
+ scrollTop: number;
22
+ scrollLeft: number;
23
+ textBuffer: TextBuffer | null;
24
+ cache: RenderCache;
25
+ /** DOM compatibility — Svelte's effects set nodeValue directly when renderer is not pushed */
26
+ get nodeValue(): string | null;
27
+ set nodeValue(value: string | null);
28
+ /** DOM compatibility — Svelte may also use textContent */
29
+ get textContent(): string;
30
+ set textContent(value: string);
31
+ constructor(nodeType: NodeType, tagOrText?: string);
32
+ get classes(): Set<string>;
33
+ getFirstChild(): TermNode | null;
34
+ getLastChild(): TermNode | null;
35
+ getNextSibling(): TermNode | null;
36
+ insertBefore(node: TermNode, anchor: TermNode | null): void;
37
+ removeChild(node: TermNode): void;
38
+ remove(): void;
39
+ collectText(): string;
40
+ invalidateStyle(): void;
41
+ invalidateLayout(): void;
42
+ invalidateAll(): void;
43
+ cleanup(): void;
44
+ }
@@ -0,0 +1,153 @@
1
+ let nextId = 1;
2
+ export class TermNode {
3
+ id;
4
+ nodeType;
5
+ tag;
6
+ text;
7
+ ctx = null;
8
+ parent = null;
9
+ children = [];
10
+ attributes = new Map();
11
+ listeners = new Map();
12
+ scrollTop = 0;
13
+ scrollLeft = 0;
14
+ textBuffer = null;
15
+ cache = { resolvedStyle: null, layoutBox: null, classAttr: '' };
16
+ /** DOM compatibility — Svelte's effects set nodeValue directly when renderer is not pushed */
17
+ get nodeValue() {
18
+ if (this.nodeType === 'text' || this.nodeType === 'comment')
19
+ return this.text ?? null;
20
+ return null;
21
+ }
22
+ set nodeValue(value) {
23
+ if (this.nodeType === 'text' || this.nodeType === 'comment') {
24
+ const newText = value ?? '';
25
+ if (this.ctx) {
26
+ this.ctx.onSetText(this, newText);
27
+ }
28
+ else {
29
+ this.text = newText;
30
+ }
31
+ }
32
+ }
33
+ /** DOM compatibility — Svelte may also use textContent */
34
+ get textContent() {
35
+ return this.collectText();
36
+ }
37
+ set textContent(value) {
38
+ if (this.nodeType === 'text') {
39
+ if (this.ctx) {
40
+ this.ctx.onSetText(this, value);
41
+ }
42
+ else {
43
+ this.text = value;
44
+ }
45
+ }
46
+ }
47
+ constructor(nodeType, tagOrText) {
48
+ this.id = nextId++;
49
+ this.nodeType = nodeType;
50
+ if (nodeType === 'element') {
51
+ this.tag = tagOrText;
52
+ }
53
+ else if (nodeType === 'text' || nodeType === 'comment') {
54
+ this.text = tagOrText ?? '';
55
+ }
56
+ }
57
+ get classes() {
58
+ const raw = this.attributes.get('class') ?? '';
59
+ if (raw === '')
60
+ return new Set();
61
+ return new Set(raw.split(/\s+/).filter(Boolean));
62
+ }
63
+ getFirstChild() {
64
+ return this.children[0] ?? null;
65
+ }
66
+ getLastChild() {
67
+ return this.children[this.children.length - 1] ?? null;
68
+ }
69
+ getNextSibling() {
70
+ if (!this.parent)
71
+ return null;
72
+ const siblings = this.parent.children;
73
+ const idx = siblings.indexOf(this);
74
+ return siblings[idx + 1] ?? null;
75
+ }
76
+ insertBefore(node, anchor) {
77
+ this.removeChild(node);
78
+ node.parent = this;
79
+ if (node.nodeType === 'fragment') {
80
+ const fragmentChildren = [...node.children];
81
+ for (const child of fragmentChildren) {
82
+ this.insertBefore(child, anchor);
83
+ }
84
+ return;
85
+ }
86
+ if (anchor === null) {
87
+ this.children.push(node);
88
+ }
89
+ else {
90
+ const idx = this.children.indexOf(anchor);
91
+ if (idx === -1) {
92
+ this.children.push(node);
93
+ }
94
+ else {
95
+ this.children.splice(idx, 0, node);
96
+ }
97
+ }
98
+ if (this.ctx)
99
+ propagateCtx(node, this.ctx);
100
+ }
101
+ removeChild(node) {
102
+ if (node.parent !== this)
103
+ return;
104
+ const idx = this.children.indexOf(node);
105
+ if (idx !== -1) {
106
+ this.children.splice(idx, 1);
107
+ }
108
+ node.parent = null;
109
+ clearCtx(node);
110
+ }
111
+ remove() {
112
+ if (this.parent) {
113
+ this.parent.removeChild(this);
114
+ }
115
+ }
116
+ collectText() {
117
+ if (this.nodeType === 'text')
118
+ return this.text ?? '';
119
+ if (this.nodeType === 'comment')
120
+ return '';
121
+ return this.children.map(c => c.collectText()).join('');
122
+ }
123
+ invalidateStyle() {
124
+ this.cache.resolvedStyle = null;
125
+ this.cache.classAttr = '';
126
+ }
127
+ invalidateLayout() {
128
+ this.cache.layoutBox = null;
129
+ }
130
+ invalidateAll() {
131
+ this.invalidateStyle();
132
+ this.invalidateLayout();
133
+ }
134
+ cleanup() {
135
+ this.listeners.clear();
136
+ this.ctx = null;
137
+ for (const child of this.children) {
138
+ child.cleanup();
139
+ }
140
+ }
141
+ }
142
+ function propagateCtx(node, ctx) {
143
+ node.ctx = ctx;
144
+ for (const child of node.children) {
145
+ propagateCtx(child, ctx);
146
+ }
147
+ }
148
+ function clearCtx(node) {
149
+ node.ctx = null;
150
+ for (const child of node.children) {
151
+ clearCtx(child);
152
+ }
153
+ }
@@ -0,0 +1,10 @@
1
+ export interface TerminalSize {
2
+ width: number;
3
+ height: number;
4
+ }
5
+ export declare function getTerminalSize(): TerminalSize;
6
+ export declare function enterFullscreen(): void;
7
+ export declare function exitFullscreen(): void;
8
+ export declare function enableRawMode(): void;
9
+ export declare function disableRawMode(): void;
10
+ export declare function writeOutput(data: string): void;
@@ -0,0 +1,31 @@
1
+ import * as ansi from '../render/ansi.js';
2
+ export function getTerminalSize() {
3
+ return {
4
+ width: Math.max(1, process.stdout.columns ?? 80),
5
+ height: Math.max(1, process.stdout.rows ?? 24),
6
+ };
7
+ }
8
+ export function enterFullscreen() {
9
+ process.stdout.write(ansi.enterAltScreen());
10
+ process.stdout.write(ansi.hideCursor());
11
+ process.stdout.write(ansi.clearScreen());
12
+ }
13
+ export function exitFullscreen() {
14
+ process.stdout.write(ansi.showCursor());
15
+ process.stdout.write(ansi.exitAltScreen());
16
+ }
17
+ export function enableRawMode() {
18
+ if (process.stdin.isTTY) {
19
+ process.stdin.setRawMode(true);
20
+ process.stdin.resume();
21
+ }
22
+ }
23
+ export function disableRawMode() {
24
+ if (process.stdin.isTTY) {
25
+ process.stdin.setRawMode(false);
26
+ process.stdin.pause();
27
+ }
28
+ }
29
+ export function writeOutput(data) {
30
+ process.stdout.write(ansi.beginSyncUpdate() + data + ansi.endSyncUpdate());
31
+ }
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Single point of control for process.stdin.
3
+ *
4
+ * All stdin data flows through here. Incoming bytes are classified and
5
+ * routed to the appropriate handler. Query-response interactions (OSC 11,
6
+ * DA1, etc.) are serialised — only one query is in-flight at a time.
7
+ */
8
+ export interface StdinHandlers {
9
+ onKey: (data: Buffer) => void;
10
+ onMouse: (data: Buffer) => void;
11
+ onPaste: (text: string) => void;
12
+ }
13
+ export declare class StdinRouter {
14
+ private handlers;
15
+ private pendingQuery;
16
+ private queryQueue;
17
+ private pasteBuffer;
18
+ start(handlers: StdinHandlers): void;
19
+ stop(): void;
20
+ /**
21
+ * Send a query and wait for a matching response.
22
+ * Queries are serialised — queued if one is already in-flight.
23
+ */
24
+ query(write: string, match: (data: string) => string | null, timeoutMs?: number): Promise<string | null>;
25
+ private drainQueryQueue;
26
+ private onData;
27
+ }
28
+ /** Match an OSC 11 response and extract the RGB values */
29
+ export declare function matchOSC11(data: string): string | null;
30
+ /** Parse an OSC 11 RGB response into a color scheme */
31
+ export declare function parseOSC11Scheme(rgb: string): 'dark' | 'light';
@@ -0,0 +1,133 @@
1
+ /**
2
+ * Single point of control for process.stdin.
3
+ *
4
+ * All stdin data flows through here. Incoming bytes are classified and
5
+ * routed to the appropriate handler. Query-response interactions (OSC 11,
6
+ * DA1, etc.) are serialised — only one query is in-flight at a time.
7
+ */
8
+ const OSC_RESPONSE_RE = /\x1b\](\d+);([^\x07\x1b]*?)(?:\x07|\x1b\\)/;
9
+ const SGR_MOUSE_RE = /\x1b\[<\d+;\d+;\d+[Mm]/g;
10
+ const PASTE_START = '\x1b[200~';
11
+ const PASTE_END = '\x1b[201~';
12
+ export class StdinRouter {
13
+ handlers = null;
14
+ pendingQuery = null;
15
+ queryQueue = [];
16
+ pasteBuffer = null;
17
+ start(handlers) {
18
+ this.handlers = handlers;
19
+ process.stdin.on('data', this.onData);
20
+ }
21
+ stop() {
22
+ process.stdin.removeListener('data', this.onData);
23
+ this.handlers = null;
24
+ if (this.pendingQuery) {
25
+ clearTimeout(this.pendingQuery.timer);
26
+ this.pendingQuery = null;
27
+ }
28
+ this.queryQueue = [];
29
+ }
30
+ /**
31
+ * Send a query and wait for a matching response.
32
+ * Queries are serialised — queued if one is already in-flight.
33
+ */
34
+ query(write, match, timeoutMs = 200) {
35
+ return new Promise((resolve, reject) => {
36
+ this.queryQueue.push({ write, match, resolve: resolve, reject, timeoutMs });
37
+ this.drainQueryQueue();
38
+ });
39
+ }
40
+ drainQueryQueue() {
41
+ if (this.pendingQuery || this.queryQueue.length === 0)
42
+ return;
43
+ const { write, match, resolve, timeoutMs } = this.queryQueue.shift();
44
+ const timer = setTimeout(() => {
45
+ this.pendingQuery = null;
46
+ resolve(null);
47
+ this.drainQueryQueue();
48
+ }, timeoutMs);
49
+ this.pendingQuery = {
50
+ match,
51
+ resolve: (response) => {
52
+ clearTimeout(timer);
53
+ this.pendingQuery = null;
54
+ resolve(response);
55
+ this.drainQueryQueue();
56
+ },
57
+ timer,
58
+ };
59
+ process.stdout.write(write);
60
+ }
61
+ onData = (data) => {
62
+ const str = data.toString();
63
+ // Check for pending query response first
64
+ if (this.pendingQuery) {
65
+ const result = this.pendingQuery.match(str);
66
+ if (result !== null) {
67
+ this.pendingQuery.resolve(result);
68
+ return;
69
+ }
70
+ }
71
+ // Bracketed paste accumulation
72
+ if (this.pasteBuffer !== null) {
73
+ const endIdx = str.indexOf(PASTE_END);
74
+ if (endIdx !== -1) {
75
+ this.pasteBuffer += str.substring(0, endIdx);
76
+ this.handlers?.onPaste(this.pasteBuffer);
77
+ this.pasteBuffer = null;
78
+ }
79
+ else {
80
+ this.pasteBuffer += str;
81
+ }
82
+ return;
83
+ }
84
+ if (str.startsWith(PASTE_START)) {
85
+ const endIdx = str.indexOf(PASTE_END);
86
+ if (endIdx !== -1) {
87
+ this.handlers?.onPaste(str.substring(PASTE_START.length, endIdx));
88
+ }
89
+ else {
90
+ this.pasteBuffer = str.substring(PASTE_START.length);
91
+ }
92
+ return;
93
+ }
94
+ // OSC response (not matched by a pending query — discard)
95
+ if (OSC_RESPONSE_RE.test(str))
96
+ return;
97
+ // Mouse events — may be batched in a single chunk
98
+ SGR_MOUSE_RE.lastIndex = 0;
99
+ const mouseMatches = [...str.matchAll(SGR_MOUSE_RE)];
100
+ if (mouseMatches.length > 0) {
101
+ for (const match of mouseMatches) {
102
+ this.handlers?.onMouse(Buffer.from(match[0]));
103
+ }
104
+ // Check if there's non-mouse data remaining
105
+ let remaining = str;
106
+ for (const match of mouseMatches) {
107
+ remaining = remaining.replace(match[0], '');
108
+ }
109
+ if (remaining.length > 0 && remaining.trim().length > 0) {
110
+ this.handlers?.onKey(Buffer.from(remaining));
111
+ }
112
+ return;
113
+ }
114
+ // Everything else is keyboard input
115
+ this.handlers?.onKey(data);
116
+ };
117
+ }
118
+ /** Match an OSC 11 response and extract the RGB values */
119
+ export function matchOSC11(data) {
120
+ const match = data.match(/\x1b\]11;rgb:([0-9a-f]+)\/([0-9a-f]+)\/([0-9a-f]+)/i);
121
+ if (!match)
122
+ return null;
123
+ return `${match[1]}/${match[2]}/${match[3]}`;
124
+ }
125
+ /** Parse an OSC 11 RGB response into a color scheme */
126
+ export function parseOSC11Scheme(rgb) {
127
+ const parts = rgb.split('/');
128
+ const r = parseInt(parts[0].substring(0, 2), 16);
129
+ const g = parseInt(parts[1].substring(0, 2), 16);
130
+ const b = parseInt(parts[2].substring(0, 2), 16);
131
+ const luminance = 0.2126 * r + 0.7152 * g + 0.0722 * b;
132
+ return luminance > 128 ? 'light' : 'dark';
133
+ }
package/package.json ADDED
@@ -0,0 +1,64 @@
1
+ {
2
+ "name": "@svelterm/core",
3
+ "version": "0.1.0",
4
+ "description": "Svelte 5 components rendered to the terminal with real CSS",
5
+ "type": "module",
6
+ "exports": {
7
+ ".": {
8
+ "types": "./dist/src/renderer/default.d.ts",
9
+ "default": "./dist/src/renderer/default.js"
10
+ },
11
+ "./app": {
12
+ "types": "./dist/src/index.d.ts",
13
+ "default": "./dist/src/index.js"
14
+ },
15
+ "./headless": {
16
+ "types": "./dist/src/headless.d.ts",
17
+ "default": "./dist/src/headless.js"
18
+ }
19
+ },
20
+ "main": "./dist/src/renderer/default.js",
21
+ "types": "./dist/src/renderer/default.d.ts",
22
+ "files": [
23
+ "dist/src",
24
+ "README.md",
25
+ "LICENSE"
26
+ ],
27
+ "scripts": {
28
+ "build": "tsc",
29
+ "test": "node --test dist/**/*.test.js",
30
+ "dev": "tsc --watch",
31
+ "demo": "DEMO=${DEMO:-counter} npx vite build && node dist-demo/${DEMO:-counter}/main.js",
32
+ "demo:counter": "DEMO=counter npx vite build && node dist-demo/counter/main.js",
33
+ "demo:dashboard": "DEMO=dashboard npx vite build && node dist-demo/dashboard/main.js",
34
+ "demo:todo": "DEMO=todo npx vite build && node dist-demo/todo/main.js",
35
+ "demo:showcase": "DEMO=showcase npx vite build && node dist-demo/showcase/main.js",
36
+ "demo:keyboard-hero": "DEMO=keyboard-hero npx vite build && node dist-demo/keyboard-hero/main.js",
37
+ "demo:snake": "DEMO=snake npx vite build && node dist-demo/snake/main.js",
38
+ "demo:themes": "DEMO=themes npx vite build && node dist-demo/themes/main.js"
39
+ },
40
+ "peerDependencies": {
41
+ "svelte": "^5.0.0"
42
+ },
43
+ "devDependencies": {
44
+ "@sveltejs/vite-plugin-svelte": "^7.0.0",
45
+ "@types/node": "^25.5.0",
46
+ "@types/ws": "^8.18.1",
47
+ "typescript": "^6.0.2",
48
+ "vite": "^8.0.3"
49
+ },
50
+ "dependencies": {
51
+ "ws": "^8.20.0"
52
+ },
53
+ "keywords": [
54
+ "svelte",
55
+ "terminal",
56
+ "tui",
57
+ "css",
58
+ "cli",
59
+ "ansi",
60
+ "renderer"
61
+ ],
62
+ "author": "Tom Yandell",
63
+ "license": "MIT"
64
+ }