Files
fiddle/public/js/autocomplete.js
root 6ca8519250 Add responsive preview, editor themes, template gallery, devtools, and autocomplete
- 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
2026-02-27 01:22:16 -06:00

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,
})),
};
},
});
}
}