- Device breakpoint toggles (mobile 375px / tablet 768px / desktop 100%) - Editor theme selector with 6 themes (VS Dark/Light, High Contrast, Monokai, Dracula, GitHub Dark) - Starter template gallery with 8 pre-built templates (Todo, API Fetch, CSS Animation, etc.) - Code autocomplete with DOM/React type definitions and snippet completions - Devtools panels: console, network, elements, performance - Code formatter (Prettier), diff view, and linter integration
143 lines
7.3 KiB
JavaScript
143 lines
7.3 KiB
JavaScript
// Code autocomplete: type definitions + snippet completions
|
|
|
|
let reactTypesAdded = false;
|
|
|
|
// Add DOM lib so document.*, window.*, etc. autocomplete in JS/TS
|
|
export function configureTypeDefaults() {
|
|
const jsOpts = {
|
|
target: monaco.languages.typescript.ScriptTarget.ESNext,
|
|
allowNonTsExtensions: true,
|
|
allowJs: true,
|
|
checkJs: false,
|
|
noEmit: true,
|
|
lib: ['esnext', 'dom', 'dom.iterable'],
|
|
};
|
|
monaco.languages.typescript.javascriptDefaults.setCompilerOptions(jsOpts);
|
|
monaco.languages.typescript.typescriptDefaults.setCompilerOptions({
|
|
...jsOpts,
|
|
allowJs: undefined,
|
|
checkJs: undefined,
|
|
});
|
|
|
|
// Relax diagnostics for playground context
|
|
monaco.languages.typescript.javascriptDefaults.setDiagnosticsOptions({
|
|
noSemanticValidation: true,
|
|
noSyntaxValidation: false,
|
|
});
|
|
monaco.languages.typescript.typescriptDefaults.setDiagnosticsOptions({
|
|
noSemanticValidation: false,
|
|
noSyntaxValidation: false,
|
|
});
|
|
}
|
|
|
|
// Add React/ReactDOM type stubs for IntelliSense
|
|
export function addReactTypes() {
|
|
if (reactTypesAdded) return;
|
|
reactTypesAdded = true;
|
|
|
|
const reactTypes = `
|
|
declare namespace React {
|
|
type ReactNode = string | number | boolean | null | undefined | ReactElement | ReactNode[];
|
|
interface ReactElement { type: any; props: any; key: string | null; }
|
|
interface RefObject<T> { current: T | null; }
|
|
type FC<P = {}> = (props: P) => ReactElement | null;
|
|
type ChangeEvent<T = Element> = { target: T; currentTarget: T; preventDefault(): void; stopPropagation(): void; };
|
|
type FormEvent<T = Element> = ChangeEvent<T>;
|
|
type MouseEvent<T = Element> = ChangeEvent<T> & { clientX: number; clientY: number; pageX: number; pageY: number; };
|
|
type KeyboardEvent<T = Element> = ChangeEvent<T> & { key: string; code: string; altKey: boolean; ctrlKey: boolean; shiftKey: boolean; metaKey: boolean; };
|
|
type CSSProperties = { [key: string]: string | number | undefined; };
|
|
type Dispatch<A> = (value: A) => void;
|
|
type SetStateAction<S> = S | ((prevState: S) => S);
|
|
|
|
function createElement(type: any, props?: any, ...children: any[]): ReactElement;
|
|
function useState<S>(initialState: S | (() => S)): [S, Dispatch<SetStateAction<S>>];
|
|
function useState<S = undefined>(): [S | undefined, Dispatch<SetStateAction<S | undefined>>];
|
|
function useEffect(effect: () => void | (() => void), deps?: any[]): void;
|
|
function useRef<T>(initialValue: T): RefObject<T>;
|
|
function useRef<T = undefined>(): RefObject<T | undefined>;
|
|
function useMemo<T>(factory: () => T, deps: any[]): T;
|
|
function useCallback<T extends (...args: any[]) => any>(callback: T, deps: any[]): T;
|
|
function useContext<T>(context: React.Context<T>): T;
|
|
function useReducer<S, A>(reducer: (state: S, action: A) => S, initialState: S): [S, Dispatch<A>];
|
|
function useId(): string;
|
|
function memo<P>(component: FC<P>): FC<P>;
|
|
function forwardRef<T, P = {}>(render: (props: P, ref: RefObject<T>) => ReactElement | null): FC<P>;
|
|
function createContext<T>(defaultValue: T): Context<T>;
|
|
function Fragment(props: { children?: ReactNode }): ReactElement;
|
|
interface Context<T> { Provider: FC<{ value: T; children?: ReactNode }>; Consumer: FC<{ children: (value: T) => ReactNode }>; }
|
|
}
|
|
|
|
declare namespace ReactDOM {
|
|
function createRoot(container: Element | null): { render(element: any): void; unmount(): void; };
|
|
function render(element: any, container: Element | null): void;
|
|
}
|
|
`;
|
|
|
|
monaco.languages.typescript.javascriptDefaults.addExtraLib(reactTypes, 'react-global.d.ts');
|
|
monaco.languages.typescript.typescriptDefaults.addExtraLib(reactTypes, 'react-global.d.ts');
|
|
}
|
|
|
|
// Register JS snippet completion provider
|
|
export function registerSnippetProviders() {
|
|
const jsSnippets = [
|
|
{ label: 'log', insert: "console.log($1);", doc: 'console.log()' },
|
|
{ label: 'qs', insert: "document.querySelector('$1')", doc: 'document.querySelector()' },
|
|
{ label: 'qsa', insert: "document.querySelectorAll('$1')", doc: 'document.querySelectorAll()' },
|
|
{ label: 'gid', insert: "document.getElementById('$1')", doc: 'document.getElementById()' },
|
|
{ label: 'ael', insert: "$1.addEventListener('$2', ($3) => {\n\t$4\n});", doc: 'addEventListener' },
|
|
{ label: 'afn', insert: "($1) => {\n\t$2\n}", doc: 'Arrow function' },
|
|
{ label: 'afni', insert: "($1) => $2", doc: 'Arrow function (inline)' },
|
|
{ label: 'fn', insert: "function $1($2) {\n\t$3\n}", doc: 'Function declaration' },
|
|
{ label: 'forof', insert: "for (const $1 of $2) {\n\t$3\n}", doc: 'for...of loop' },
|
|
{ label: 'forin', insert: "for (const $1 in $2) {\n\t$3\n}", doc: 'for...in loop' },
|
|
{ label: 'fore', insert: "$1.forEach(($2) => {\n\t$3\n});", doc: 'forEach loop' },
|
|
{ label: 'map', insert: "$1.map(($2) => $3)", doc: 'Array.map()' },
|
|
{ label: 'filter', insert: "$1.filter(($2) => $3)", doc: 'Array.filter()' },
|
|
{ label: 'reduce', insert: "$1.reduce(($2, $3) => $4, $5)", doc: 'Array.reduce()' },
|
|
{ label: 'fetch', insert: "const res = await fetch('$1');\nconst data = await res.json();", doc: 'Fetch API' },
|
|
{ label: 'promise', insert: "new Promise((resolve, reject) => {\n\t$1\n})", doc: 'New Promise' },
|
|
{ label: 'timeout', insert: "setTimeout(() => {\n\t$1\n}, $2);", doc: 'setTimeout' },
|
|
{ label: 'interval', insert: "setInterval(() => {\n\t$1\n}, $2);", doc: 'setInterval' },
|
|
{ label: 'raf', insert: "requestAnimationFrame($1);", doc: 'requestAnimationFrame' },
|
|
{ label: 'trycatch', insert: "try {\n\t$1\n} catch (err) {\n\t$2\n}", doc: 'try/catch block' },
|
|
{ label: 'class', insert: "class $1 {\n\tconstructor($2) {\n\t\t$3\n\t}\n}", doc: 'Class declaration' },
|
|
{ label: 'imp', insert: "import { $2 } from '$1';", doc: 'Import statement' },
|
|
{ label: 'cel', insert: "document.createElement('$1')", doc: 'createElement' },
|
|
];
|
|
|
|
const reactSnippets = [
|
|
{ label: 'ustate', insert: "const [$1, set${1/(.*)/${1:/capitalize}/}] = React.useState($2);", doc: 'React.useState' },
|
|
{ label: 'ueffect', insert: "React.useEffect(() => {\n\t$1\n\treturn () => { $2 };\n}, [$3]);", doc: 'React.useEffect' },
|
|
{ label: 'uref', insert: "const $1 = React.useRef($2);", doc: 'React.useRef' },
|
|
{ label: 'umemo', insert: "const $1 = React.useMemo(() => $2, [$3]);", doc: 'React.useMemo' },
|
|
{ label: 'ucallback', insert: "const $1 = React.useCallback(($2) => {\n\t$3\n}, [$4]);", doc: 'React.useCallback' },
|
|
{ label: 'comp', insert: "const $1 = ($2) => {\n\treturn (\n\t\t<div>\n\t\t\t$3\n\t\t</div>\n\t);\n};", doc: 'React component' },
|
|
];
|
|
|
|
// JS/TS snippets
|
|
for (const lang of ['javascript', 'typescript']) {
|
|
monaco.languages.registerCompletionItemProvider(lang, {
|
|
provideCompletionItems(model, position) {
|
|
const word = model.getWordUntilPosition(position);
|
|
const range = {
|
|
startLineNumber: position.lineNumber,
|
|
endLineNumber: position.lineNumber,
|
|
startColumn: word.startColumn,
|
|
endColumn: word.endColumn,
|
|
};
|
|
const all = [...jsSnippets, ...reactSnippets];
|
|
return {
|
|
suggestions: all.map(s => ({
|
|
label: s.label,
|
|
kind: monaco.languages.CompletionItemKind.Snippet,
|
|
documentation: s.doc,
|
|
insertText: s.insert,
|
|
insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet,
|
|
range,
|
|
})),
|
|
};
|
|
},
|
|
});
|
|
}
|
|
}
|