From 8a59f92d031963e23ecc84b75feecf43eb4dd146 Mon Sep 17 00:00:00 2001
From: "netop://ウィビ" <paul@webb.page>
Date: Fri, 24 Apr 2026 11:33:25 -0700
Subject: 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
---
 source/library/components/Editor.svelte | 136 ++++++++++++++++++++++++++++++++
 1 file changed, 136 insertions(+)
 create mode 100644 source/library/components/Editor.svelte

(limited to 'source/library/components/Editor.svelte')

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>
-- 
cgit v1.2.3

