mirror of
https://github.com/lockin-bot/react-telegram.git
synced 2026-01-12 15:13:56 +08:00
feat: update types package
This commit is contained in:
@@ -6,7 +6,10 @@
|
||||
"Bash(bun run:*)",
|
||||
"Bash(bun info:*)",
|
||||
"Bash(npm view:*)",
|
||||
"Bash(find:*)"
|
||||
"Bash(find:*)",
|
||||
"Bash(bun remove:*)",
|
||||
"Bash(bunx tsc:*)",
|
||||
"Bash(rm:*)"
|
||||
],
|
||||
"deny": []
|
||||
}
|
||||
|
||||
4
bun.lock
4
bun.lock
@@ -9,8 +9,8 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/bun": "latest",
|
||||
"@types/react": "^19.1.8",
|
||||
"@types/react-reconciler": "^0.32.0",
|
||||
"pure-react-types": "^0.1.4",
|
||||
"vitest": "^3.2.4",
|
||||
},
|
||||
"peerDependencies": {
|
||||
@@ -187,6 +187,8 @@
|
||||
|
||||
"postcss": ["postcss@8.5.6", "", { "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg=="],
|
||||
|
||||
"pure-react-types": ["pure-react-types@0.1.4", "", {}, "sha512-8y1P/kWmo839jDqhLnaTAatjwU2SVjs34iwOwYtiMdcfGLBGiOrUTPjpb9hOgTB3cPNuAT0q90AtmPEIp1zJxA=="],
|
||||
|
||||
"react": ["react@19.1.0", "", {}, "sha512-FS+XFBNvn3GTAWq26joslQgWNoFu08F4kl0J4CgdNKADkdSGXQyTCnKteIAJy96Br6YbpEU1LSzV5dYtjMkMDg=="],
|
||||
|
||||
"react-reconciler": ["react-reconciler@0.32.0", "", { "dependencies": { "scheduler": "^0.26.0" }, "peerDependencies": { "react": "^19.1.0" } }, "sha512-2NPMOzgTlG0ZWdIf3qG+dcbLSoAc/uLfOwckc3ofy5sSK0pLJqnQLpUFxvGcN2rlXSjnVtGeeFLNimCQEj5gOQ=="],
|
||||
|
||||
@@ -5,8 +5,8 @@
|
||||
"private": true,
|
||||
"devDependencies": {
|
||||
"@types/bun": "latest",
|
||||
"@types/react": "^19.1.8",
|
||||
"@types/react-reconciler": "^0.32.0",
|
||||
"pure-react-types": "^0.1.4",
|
||||
"vitest": "^3.2.4"
|
||||
},
|
||||
"peerDependencies": {
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
/// <reference path="./jsx.d.ts" />
|
||||
import React, { useState } from 'react';
|
||||
import { createContainer } from './reconciler';
|
||||
|
||||
|
||||
19
src/index.ts
19
src/index.ts
@@ -8,5 +8,20 @@ export type {
|
||||
BlockQuoteNode,
|
||||
ButtonNode,
|
||||
RowNode,
|
||||
RootNode
|
||||
} from './reconciler';
|
||||
RootNode,
|
||||
Node
|
||||
} from './reconciler';
|
||||
|
||||
// Also export the Telegram-prefixed types from jsx.d.ts
|
||||
export type {
|
||||
TelegramTextNode,
|
||||
TelegramFormattedNode,
|
||||
TelegramLinkNode,
|
||||
TelegramEmojiNode,
|
||||
TelegramCodeBlockNode,
|
||||
TelegramBlockQuoteNode,
|
||||
TelegramButtonNode,
|
||||
TelegramRowNode,
|
||||
TelegramRootNode,
|
||||
TelegramNode
|
||||
} from './jsx';
|
||||
140
src/jsx.d.ts
vendored
Normal file
140
src/jsx.d.ts
vendored
Normal file
@@ -0,0 +1,140 @@
|
||||
import type { ReactNode } from 'react';
|
||||
|
||||
declare module 'react' {
|
||||
namespace JSX {
|
||||
interface IntrinsicElements {
|
||||
// Text formatting elements
|
||||
b: { children?: ReactNode };
|
||||
strong: { children?: ReactNode };
|
||||
i: { children?: ReactNode };
|
||||
em: { children?: ReactNode };
|
||||
u: { children?: ReactNode };
|
||||
ins: { children?: ReactNode };
|
||||
s: { children?: ReactNode };
|
||||
strike: { children?: ReactNode };
|
||||
del: { children?: ReactNode };
|
||||
code: { children?: ReactNode };
|
||||
|
||||
// Span with special className
|
||||
span: {
|
||||
className?: 'tg-spoiler' | string;
|
||||
children?: ReactNode;
|
||||
};
|
||||
|
||||
// Custom Telegram elements
|
||||
'tg-spoiler': { children?: ReactNode };
|
||||
'tg-emoji': {
|
||||
emojiId: string;
|
||||
'emoji-id'?: string; // Alternative attribute name
|
||||
children?: ReactNode; // Fallback emoji
|
||||
};
|
||||
|
||||
// Link element
|
||||
a: {
|
||||
href: string;
|
||||
children?: ReactNode;
|
||||
};
|
||||
|
||||
// Code blocks
|
||||
pre: {
|
||||
children?: ReactNode;
|
||||
};
|
||||
|
||||
// Blockquote
|
||||
blockquote: {
|
||||
expandable?: boolean;
|
||||
children?: ReactNode;
|
||||
};
|
||||
|
||||
// Interactive elements
|
||||
row: {
|
||||
children?: ReactNode;
|
||||
};
|
||||
|
||||
button: {
|
||||
onClick?: () => void;
|
||||
children?: ReactNode;
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Export types for the reconciler output
|
||||
export interface TelegramTextNode {
|
||||
type: 'text';
|
||||
content: string;
|
||||
formatting?: {
|
||||
bold?: boolean;
|
||||
italic?: boolean;
|
||||
underline?: boolean;
|
||||
strikethrough?: boolean;
|
||||
spoiler?: boolean;
|
||||
code?: boolean;
|
||||
};
|
||||
}
|
||||
|
||||
export interface TelegramFormattedNode {
|
||||
type: 'formatted';
|
||||
format: 'bold' | 'italic' | 'underline' | 'strikethrough' | 'spoiler' | 'code';
|
||||
children: (TelegramTextNode | TelegramFormattedNode | TelegramLinkNode)[];
|
||||
}
|
||||
|
||||
export interface TelegramLinkNode {
|
||||
type: 'link';
|
||||
href: string;
|
||||
children: (TelegramTextNode | TelegramFormattedNode)[];
|
||||
}
|
||||
|
||||
export interface TelegramEmojiNode {
|
||||
type: 'emoji';
|
||||
emojiId: string;
|
||||
fallback?: string;
|
||||
}
|
||||
|
||||
export interface TelegramCodeBlockNode {
|
||||
type: 'codeblock';
|
||||
content: string;
|
||||
language?: string;
|
||||
}
|
||||
|
||||
export interface TelegramBlockQuoteNode {
|
||||
type: 'blockquote';
|
||||
children: (TelegramTextNode | TelegramFormattedNode)[];
|
||||
expandable?: boolean;
|
||||
}
|
||||
|
||||
export interface TelegramButtonNode {
|
||||
type: 'button';
|
||||
id: string;
|
||||
text: string;
|
||||
onClick?: () => void;
|
||||
}
|
||||
|
||||
export interface TelegramRowNode {
|
||||
type: 'row';
|
||||
children: TelegramButtonNode[];
|
||||
}
|
||||
|
||||
export interface TelegramRootNode {
|
||||
type: 'root';
|
||||
children: (
|
||||
| TelegramTextNode
|
||||
| TelegramFormattedNode
|
||||
| TelegramLinkNode
|
||||
| TelegramEmojiNode
|
||||
| TelegramCodeBlockNode
|
||||
| TelegramBlockQuoteNode
|
||||
| TelegramRowNode
|
||||
)[];
|
||||
}
|
||||
|
||||
export type TelegramNode =
|
||||
| TelegramTextNode
|
||||
| TelegramFormattedNode
|
||||
| TelegramLinkNode
|
||||
| TelegramEmojiNode
|
||||
| TelegramCodeBlockNode
|
||||
| TelegramBlockQuoteNode
|
||||
| TelegramButtonNode
|
||||
| TelegramRowNode
|
||||
| TelegramRootNode;
|
||||
@@ -1,3 +1,4 @@
|
||||
/// <reference path="./jsx.d.ts" />
|
||||
import { describe, it, expect, vi } from 'vitest';
|
||||
import React, { useState } from 'react';
|
||||
import { createContainer } from './reconciler';
|
||||
@@ -162,10 +163,12 @@ describe('Telegram Reconciler', () => {
|
||||
|
||||
expect(container.root.children).toHaveLength(1);
|
||||
const row = container.root.children[0];
|
||||
expect(row.type).toBe('row');
|
||||
expect(row.children).toHaveLength(2);
|
||||
expect(row.children[0].id).toBe('0-0');
|
||||
expect(row.children[1].id).toBe('0-1');
|
||||
expect(row?.type).toBe('row');
|
||||
if (row?.type === 'row') {
|
||||
expect(row.children).toHaveLength(2);
|
||||
expect(row.children[0]?.id).toBe('0-0');
|
||||
expect(row.children[1]?.id).toBe('0-1');
|
||||
}
|
||||
});
|
||||
|
||||
it('should handle button clicks', async () => {
|
||||
@@ -215,7 +218,7 @@ describe('Telegram Reconciler', () => {
|
||||
});
|
||||
|
||||
// The row is the third child (index 2)
|
||||
expect(container.root.children[2].type).toBe('row');
|
||||
expect(container.root.children[2]?.type).toBe('row');
|
||||
|
||||
// Click increase button
|
||||
clickButton('0-1');
|
||||
|
||||
@@ -1,67 +1,31 @@
|
||||
import ReactReconciler from 'react-reconciler';
|
||||
import { DefaultEventPriority, NoEventPriority } from 'react-reconciler/constants';
|
||||
import type {
|
||||
TelegramTextNode as TextNode,
|
||||
TelegramFormattedNode as FormattedNode,
|
||||
TelegramLinkNode as LinkNode,
|
||||
TelegramEmojiNode as EmojiNode,
|
||||
TelegramCodeBlockNode as CodeBlockNode,
|
||||
TelegramBlockQuoteNode as BlockQuoteNode,
|
||||
TelegramButtonNode as ButtonNode,
|
||||
TelegramRowNode as RowNode,
|
||||
TelegramRootNode as RootNode,
|
||||
TelegramNode as Node
|
||||
} from './jsx';
|
||||
|
||||
export interface TextNode {
|
||||
type: 'text';
|
||||
content: string;
|
||||
formatting?: {
|
||||
bold?: boolean;
|
||||
italic?: boolean;
|
||||
underline?: boolean;
|
||||
strikethrough?: boolean;
|
||||
spoiler?: boolean;
|
||||
code?: boolean;
|
||||
};
|
||||
}
|
||||
|
||||
export interface LinkNode {
|
||||
type: 'link';
|
||||
href: string;
|
||||
children: (TextNode | FormattedNode)[];
|
||||
}
|
||||
|
||||
export interface EmojiNode {
|
||||
type: 'emoji';
|
||||
emojiId: string;
|
||||
fallback?: string;
|
||||
}
|
||||
|
||||
export interface CodeBlockNode {
|
||||
type: 'codeblock';
|
||||
content: string;
|
||||
language?: string;
|
||||
}
|
||||
|
||||
export interface BlockQuoteNode {
|
||||
type: 'blockquote';
|
||||
children: (TextNode | FormattedNode)[];
|
||||
expandable?: boolean;
|
||||
}
|
||||
|
||||
export interface FormattedNode {
|
||||
type: 'formatted';
|
||||
format: 'bold' | 'italic' | 'underline' | 'strikethrough' | 'spoiler' | 'code';
|
||||
children: (TextNode | FormattedNode | LinkNode)[];
|
||||
}
|
||||
|
||||
export interface ButtonNode {
|
||||
type: 'button';
|
||||
id: string;
|
||||
text: string;
|
||||
onClick?: () => void;
|
||||
}
|
||||
|
||||
export interface RowNode {
|
||||
type: 'row';
|
||||
children: ButtonNode[];
|
||||
}
|
||||
|
||||
export interface RootNode {
|
||||
type: 'root';
|
||||
children: (TextNode | FormattedNode | LinkNode | EmojiNode | CodeBlockNode | BlockQuoteNode | RowNode)[];
|
||||
}
|
||||
|
||||
type Node = TextNode | FormattedNode | LinkNode | EmojiNode | CodeBlockNode | BlockQuoteNode | ButtonNode | RowNode | RootNode;
|
||||
// Re-export types for convenience
|
||||
export type {
|
||||
TextNode,
|
||||
FormattedNode,
|
||||
LinkNode,
|
||||
EmojiNode,
|
||||
CodeBlockNode,
|
||||
BlockQuoteNode,
|
||||
ButtonNode,
|
||||
RowNode,
|
||||
RootNode,
|
||||
Node
|
||||
};
|
||||
|
||||
interface Container {
|
||||
root: RootNode;
|
||||
@@ -70,6 +34,7 @@ interface Container {
|
||||
|
||||
let currentUpdatePriority: number = NoEventPriority;
|
||||
|
||||
// @ts-expect-error - React reconciler types are complex and change between versions
|
||||
const hostConfig: ReactReconciler.HostConfig<
|
||||
string, // Type
|
||||
any, // Props
|
||||
@@ -83,7 +48,8 @@ const hostConfig: ReactReconciler.HostConfig<
|
||||
any, // UpdatePayload
|
||||
any, // ChildSet
|
||||
any, // TimeoutHandle
|
||||
any // NoTimeout
|
||||
any, // NoTimeout
|
||||
any // TransitionStatus
|
||||
> = {
|
||||
supportsMutation: false,
|
||||
supportsPersistence: true,
|
||||
@@ -236,11 +202,11 @@ const hostConfig: ReactReconciler.HostConfig<
|
||||
},
|
||||
|
||||
cloneHiddenInstance(instance: any) {
|
||||
return this.cloneInstance(instance);
|
||||
return hostConfig.cloneInstance(instance);
|
||||
},
|
||||
|
||||
cloneHiddenTextInstance(instance: any) {
|
||||
return this.cloneInstance(instance);
|
||||
return hostConfig.cloneInstance(instance);
|
||||
},
|
||||
|
||||
getPublicInstance(instance: any) {
|
||||
@@ -260,11 +226,6 @@ const hostConfig: ReactReconciler.HostConfig<
|
||||
if (!parent.children) parent.children = [];
|
||||
parent.children.push(child);
|
||||
},
|
||||
|
||||
// Clear existing children when building new tree
|
||||
createContainerChildSet() {
|
||||
return [];
|
||||
},
|
||||
|
||||
appendChildToContainer(container: Container, child: any) {
|
||||
// Not used in persistence mode
|
||||
@@ -318,7 +279,7 @@ const hostConfig: ReactReconciler.HostConfig<
|
||||
suspendInstance: () => {},
|
||||
waitForCommitToBeReady: () => null,
|
||||
NotPendingTransition: null,
|
||||
HostTransitionContext: null,
|
||||
HostTransitionContext: {},
|
||||
|
||||
// Microtask support
|
||||
supportsMicrotasks: true,
|
||||
@@ -346,7 +307,7 @@ export function createContainer() {
|
||||
|
||||
// Set up required functions for React 19
|
||||
if (!TelegramReconciler.injectIntoDevTools) {
|
||||
TelegramReconciler.injectIntoDevTools = () => {};
|
||||
TelegramReconciler.injectIntoDevTools = () => false;
|
||||
}
|
||||
|
||||
return {
|
||||
|
||||
@@ -1,47 +0,0 @@
|
||||
import React from 'react';
|
||||
import { createContainer } from './reconciler';
|
||||
|
||||
// Test without vitest first
|
||||
console.log('Testing reconciler...\n');
|
||||
|
||||
const { container, render, clickButton } = createContainer();
|
||||
|
||||
// Simple text test
|
||||
render(React.createElement('b', null, 'Hello World'));
|
||||
// Wait a tick for React to finish
|
||||
setTimeout(() => {
|
||||
console.log('Simple bold text:');
|
||||
console.log(JSON.stringify(container.root, null, 2));
|
||||
|
||||
// Clear for next test
|
||||
container.root.children = [];
|
||||
|
||||
// Complex nested test
|
||||
render(
|
||||
React.createElement(React.Fragment, null,
|
||||
React.createElement('b', null,
|
||||
'Bold ',
|
||||
React.createElement('i', null, 'italic'),
|
||||
' text'
|
||||
),
|
||||
'\n',
|
||||
React.createElement('row', null,
|
||||
React.createElement('button', { onClick: () => console.log('Button 1 clicked') }, 'Button 1'),
|
||||
React.createElement('button', { onClick: () => console.log('Button 2 clicked') }, 'Button 2')
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
setTimeout(() => {
|
||||
console.log('\nComplex nested structure:');
|
||||
console.log(JSON.stringify(container.root, null, 2));
|
||||
|
||||
// Check button handlers
|
||||
console.log('\nButton handlers:', container.buttonHandlers);
|
||||
|
||||
// Test button click
|
||||
console.log('\nTesting button clicks...');
|
||||
clickButton('0-0');
|
||||
clickButton('0-1');
|
||||
}, 10);
|
||||
}, 0);
|
||||
@@ -1,50 +0,0 @@
|
||||
import React, { useState } from 'react';
|
||||
import { createContainer } from './reconciler';
|
||||
|
||||
const { container, render, clickButton } = createContainer();
|
||||
|
||||
const App = () => {
|
||||
const [count, setCount] = useState(0);
|
||||
console.log('App render, count:', count);
|
||||
|
||||
return (
|
||||
<>
|
||||
count {count}
|
||||
<row>
|
||||
<button onClick={() => {
|
||||
console.log('Decrease clicked');
|
||||
setCount(p => p - 1);
|
||||
}}>Decrease</button>
|
||||
<button onClick={() => {
|
||||
console.log('Increase clicked');
|
||||
setCount(p => p + 1);
|
||||
}}>Increase</button>
|
||||
</row>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
console.log('Initial render');
|
||||
render(<App />);
|
||||
|
||||
setTimeout(() => {
|
||||
console.log('\nInitial state:');
|
||||
console.log(JSON.stringify(container.root, null, 2));
|
||||
console.log('Button handlers:', container.buttonHandlers.size);
|
||||
|
||||
console.log('\nClicking increase (0-1)');
|
||||
clickButton('0-1');
|
||||
|
||||
setTimeout(() => {
|
||||
console.log('\nAfter increase:');
|
||||
console.log(JSON.stringify(container.root, null, 2));
|
||||
|
||||
console.log('\nClicking decrease (0-0)');
|
||||
clickButton('0-0');
|
||||
|
||||
setTimeout(() => {
|
||||
console.log('\nAfter decrease:');
|
||||
console.log(JSON.stringify(container.root, null, 2));
|
||||
}, 10);
|
||||
}, 10);
|
||||
}, 10);
|
||||
111
src/typed-example.tsx
Normal file
111
src/typed-example.tsx
Normal file
@@ -0,0 +1,111 @@
|
||||
/// <reference path="./jsx.d.ts" />
|
||||
import React, { useState } from 'react';
|
||||
import { createContainer } from './reconciler';
|
||||
import type { RootNode } from './reconciler';
|
||||
|
||||
// This example demonstrates TypeScript support with custom JSX elements
|
||||
|
||||
const TypedApp: React.FC = () => {
|
||||
const [showSpoiler, setShowSpoiler] = useState(false);
|
||||
|
||||
return (
|
||||
<>
|
||||
<b>TypeScript Example</b>
|
||||
{'\n'}
|
||||
<i>All custom elements are properly typed!</i>
|
||||
{'\n\n'}
|
||||
|
||||
{/* Text formatting */}
|
||||
<u>Underlined text</u>
|
||||
{' '}
|
||||
<s>Strikethrough</s>
|
||||
{' '}
|
||||
<code>inline code</code>
|
||||
{'\n\n'}
|
||||
|
||||
{/* Spoiler */}
|
||||
{showSpoiler ? (
|
||||
<tg-spoiler>Secret message!</tg-spoiler>
|
||||
) : (
|
||||
<span className="tg-spoiler">Hidden content</span>
|
||||
)}
|
||||
{'\n\n'}
|
||||
|
||||
{/* Links */}
|
||||
<a href="https://telegram.org">Telegram Website</a>
|
||||
{' | '}
|
||||
<a href="tg://user?id=123456">User mention</a>
|
||||
{'\n\n'}
|
||||
|
||||
{/* Emoji */}
|
||||
<tg-emoji emojiId="5368324170671202286">👍</tg-emoji>
|
||||
{'\n\n'}
|
||||
|
||||
{/* Code block */}
|
||||
<pre>
|
||||
<code className="language-typescript">
|
||||
{`const message = "Hello, Telegram!";
|
||||
console.log(message);`}
|
||||
</code>
|
||||
</pre>
|
||||
{'\n'}
|
||||
|
||||
{/* Blockquote */}
|
||||
<blockquote expandable>
|
||||
This is an expandable quote.
|
||||
It can contain multiple lines.
|
||||
</blockquote>
|
||||
{'\n\n'}
|
||||
|
||||
{/* Interactive buttons */}
|
||||
<row>
|
||||
<button onClick={() => setShowSpoiler(!showSpoiler)}>
|
||||
{showSpoiler ? 'Hide' : 'Show'} Spoiler
|
||||
</button>
|
||||
<button onClick={() => console.log('Clicked!')}>
|
||||
Log Message
|
||||
</button>
|
||||
</row>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
// Create container with proper typing
|
||||
const { render, container } = createContainer();
|
||||
|
||||
// Render the app
|
||||
render(<TypedApp />);
|
||||
|
||||
// Wait for render to complete
|
||||
setTimeout(() => {
|
||||
// Access the typed output
|
||||
const output: RootNode = container.root;
|
||||
|
||||
console.log('Typed output structure:');
|
||||
console.log(JSON.stringify(output, null, 2));
|
||||
|
||||
// Type-safe access to nodes
|
||||
console.log('\nAnalyzing node types:');
|
||||
output.children.forEach(child => {
|
||||
switch (child.type) {
|
||||
case 'formatted':
|
||||
console.log(`- Found ${child.format} formatting`);
|
||||
break;
|
||||
case 'row':
|
||||
console.log(`- Found row with ${child.children.length} buttons`);
|
||||
break;
|
||||
case 'link':
|
||||
console.log(`- Found link to ${child.href}`);
|
||||
break;
|
||||
case 'emoji':
|
||||
console.log(`- Found emoji with ID ${child.emojiId}`);
|
||||
break;
|
||||
case 'codeblock':
|
||||
console.log(`- Found code block with language: ${child.language || 'none'}`);
|
||||
break;
|
||||
case 'blockquote':
|
||||
console.log(`- Found ${child.expandable ? 'expandable' : 'regular'} blockquote`);
|
||||
break;
|
||||
}
|
||||
});
|
||||
}, 0);
|
||||
@@ -24,6 +24,9 @@
|
||||
// Some stricter flags (disabled by default)
|
||||
"noUnusedLocals": false,
|
||||
"noUnusedParameters": false,
|
||||
"noPropertyAccessFromIndexSignature": false
|
||||
"noPropertyAccessFromIndexSignature": false,
|
||||
|
||||
// Types
|
||||
"types": ["bun-types", "pure-react-types"]
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user