aboutsummaryrefslogtreecommitdiff
path: root/source/library/components/Editor.svelte
diff options
context:
space:
mode:
authornetop://ウィビ <paul@webb.page>2026-04-24 11:33:25 -0700
committernetop://ウィビ <paul@webb.page>2026-04-24 11:33:25 -0700
commit8a59f92d031963e23ecc84b75feecf43eb4dd146 (patch)
tree75de5768885583897061a3b1795e4c987ce90039 /source/library/components/Editor.svelte
downloadgraphiql-8a59f92d031963e23ecc84b75feecf43eb4dd146.tar.gz
graphiql-8a59f92d031963e23ecc84b75feecf43eb4dd146.zip
Initial commit: @eol/graphiql v0.3
Svelte 5 GraphiQL alternative for JSR. Covers: - HTTP fetcher with injectable fetch; SSE/WS stubs - Session store with tabs, auto-titling, persistence, rename - Operation detection via graphql parse(); Toolbar picker - CodeMirror 6 editor via cm6-graphql with theme prop - Light theme preset (hand-rolled EditorView.theme) - Doc explorer with breadcrumb nav and type guards - History panel with 100-entry cap, favorite pinning - Deno tests for operations, storage, and history eviction
Diffstat (limited to 'source/library/components/Editor.svelte')
-rw-r--r--source/library/components/Editor.svelte136
1 files changed, 136 insertions, 0 deletions
diff --git a/source/library/components/Editor.svelte b/source/library/components/Editor.svelte
new file mode 100644
index 0000000..f2bf82d
--- /dev/null
+++ b/source/library/components/Editor.svelte
@@ -0,0 +1,136 @@
+<script lang="ts">
+ /*** IMPORT ------------------------------------------- ***/
+
+ import { browser } from "$app/environment";
+ import { onMount } from "svelte";
+ import type { Extension } from "@codemirror/state";
+ import type { EditorView } from "@codemirror/view";
+ import type { GraphQLSchema } from "graphql";
+
+ /*** UTILITY ------------------------------------------ ***/
+
+ type Props = {
+ language?: "graphql" | "json";
+ onChange: (value: string) => void;
+ readOnly?: boolean;
+ schema?: string;
+ theme?: Extension;
+ value: string;
+ };
+
+ let {
+ language = "graphql",
+ onChange,
+ readOnly = false,
+ schema,
+ theme,
+ value
+ }: Props = $props();
+
+ let buildSchemaFn = $state<((sdl: string) => GraphQLSchema) | null>(null);
+ let container: HTMLDivElement;
+ let updateSchemaFn = $state<((v: EditorView, s: GraphQLSchema) => void) | null>(null);
+ let view = $state<EditorView | null>(null);
+
+ onMount(() => {
+ if (!browser)
+ return;
+
+ let disposed = false;
+
+ (async () => {
+ const [
+ { EditorView: EV, keymap, lineNumbers, highlightActiveLine },
+ { EditorState },
+ { defaultKeymap, history, historyKeymap },
+ { syntaxHighlighting, defaultHighlightStyle, bracketMatching, indentOnInput },
+ { closeBrackets, closeBracketsKeymap },
+ { graphql, updateSchema },
+ { json },
+ { buildSchema }
+ ] = await Promise.all([
+ import("@codemirror/view"),
+ import("@codemirror/state"),
+ import("@codemirror/commands"),
+ import("@codemirror/language"),
+ import("@codemirror/autocomplete"),
+ import("cm6-graphql"),
+ import("@codemirror/lang-json"),
+ import("graphql")
+ ]);
+
+ if (disposed)
+ return;
+
+ const themeExt: Extension = theme ?? (await import("@codemirror/theme-one-dark")).oneDark;
+
+ const languageExt = language === "graphql" ?
+ graphql(schema ? buildSchema(schema) : undefined) :
+ json();
+
+ const instance = new EV({
+ parent: container,
+ state: EditorState.create({
+ doc: value,
+ extensions: [
+ lineNumbers(),
+ highlightActiveLine(),
+ history(),
+ bracketMatching(),
+ closeBrackets(),
+ indentOnInput(),
+ syntaxHighlighting(defaultHighlightStyle, { fallback: true }),
+ keymap.of([...closeBracketsKeymap, ...defaultKeymap, ...historyKeymap]),
+ languageExt,
+ themeExt,
+ EV.editable.of(!readOnly),
+ EV.updateListener.of((u) => {
+ if (u.docChanged)
+ onChange(u.state.doc.toString());
+ })
+ ]
+ })
+ });
+
+ view = instance;
+ updateSchemaFn = updateSchema;
+ buildSchemaFn = buildSchema;
+ })();
+
+ return () => {
+ disposed = true;
+ view?.destroy();
+ };
+ });
+
+ $effect(() => {
+ if (!view || !updateSchemaFn || !buildSchemaFn)
+ return;
+
+ if (language !== "graphql" || !schema)
+ return;
+
+ try {
+ updateSchemaFn(view, buildSchemaFn(schema));
+ } catch {
+ // Invalid SDL — silently skip; editor keeps working without schema awareness
+ }
+ });
+</script>
+
+<style lang="scss">
+ .editor {
+ height: 100%;
+ width: 100%;
+
+ :global(.cm-editor) {
+ height: 100%;
+ }
+
+ :global(.cm-scroller) {
+ font-family: inherit;
+ }
+ }
+</style>
+
+<div bind:this={container} class="editor"></div>