@coldsmirk/inkstone-sql 0.31.0 → 0.32.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -8,7 +8,7 @@ Part of [inkstone](https://github.com/coldsmirk/inkstone). Extracted from dragom
8
8
 
9
9
  - **`@coldsmirk/inkstone-sql`** — the framework-free core. Zero editor dependencies; runs anywhere.
10
10
  - **`@coldsmirk/inkstone-sql/monaco`** — `registerSqlLanguage(monaco, modelPath, hooks)`: the Monaco providers (completion, hover, signature help, formatting) over an assist read per-request through a hook. Peer: `monaco-editor`.
11
- - **`@coldsmirk/inkstone-sql/react`** — `<SqlEditor>`: a controlled SQL editor built on `@coldsmirk/inkstone-react`'s `<MonacoEditor>`, with a run key (Ctrl/Cmd-Enter handing over the live caret), a runnable-statement gutter mark, caret tracking, and an insert/format handle for schema trees and toolbars. Peers: `monaco-editor`, `react`.
11
+ - **`@coldsmirk/inkstone-sql/react`** — `<SqlEditor>`: a controlled SQL editor built on `@coldsmirk/inkstone-react`'s `<MonacoConsoleEditor>`, with a run key (Ctrl/Cmd-Enter handing over the live caret), a runnable-statement gutter mark, caret tracking, and an insert/focus/format handle for schema trees and toolbars. Peers: `monaco-editor`, `react`.
12
12
 
13
13
  ## The shape
14
14
 
package/dist/react.d.ts CHANGED
@@ -1,92 +1,33 @@
1
1
  import { t as SqlAssist } from "./assist-BM4bNDhw.js";
2
- import { MonacoModule } from "./monaco.js";
2
+ import { ConsoleCaret, ConsoleEditorHandle, ConsoleEditorProps } from "@coldsmirk/inkstone-react/monaco";
3
3
  import { ReactNode } from "react";
4
- import { editor } from "monaco-editor";
5
4
  //#region src/react.d.ts
6
5
  /**
7
6
  * What a host needs to reach into the editor for: inserting at the caret (a schema tree is an
8
- * input device, not a browser), and reformatting from a toolbar — which goes through Monaco's own
9
- * formatting action, so the button and `Shift+Alt+F` are one path and one undo.
7
+ * input device, not a browser), the focus, and reformatting from a toolbar — which goes through
8
+ * Monaco's own formatting action, so the button and `Shift+Alt+F` are one path and one undo.
10
9
  */
11
- export interface SqlEditorHandle {
12
- insert: (text: string) => void;
10
+ export interface SqlEditorHandle extends ConsoleEditorHandle {
13
11
  format: () => void;
14
12
  }
15
13
  /**
16
14
  * Where the operator is in the document, in offsets — everything a host needs to decide which
17
- * statement a run should send, and nothing about SQL. `from` and `to` are the selection's own
18
- * bounds and are equal where nothing is selected: a caret is a selection of nothing.
19
- *
20
- * It rides with the text rather than beside it because the two must belong to the same instant.
21
- * The controlled `value` is one React render behind the keystroke that triggered a run, and a
22
- * console must run the statement the operator is looking at.
15
+ * statement a run should send, and nothing about SQL.
23
16
  */
24
- export interface SqlCaret {
25
- text: string;
26
- from: number;
27
- to: number;
28
- }
29
- export interface SqlEditorProps {
30
- value: string;
31
- onChange: (value: string) => void;
32
- /**
33
- * Render with the dark theme. Like every inkstone editor, the component never reads the OS
34
- * `prefers-color-scheme`; wire this to the app's color-scheme state.
35
- *
36
- * @default false
37
- */
38
- dark?: boolean;
39
- /**
40
- * Editor height in px, or `"fill"` to consume the parent's resolved height (the parent must be
41
- * a `min-height: 0` flex item). Monaco's loading placeholder is `height: 100%`, so a height
42
- * that resolves to nothing collapses the field to one line while the editor loads.
43
- */
44
- height?: number | "fill";
45
- /**
46
- * Editor font size in px. Omit to keep the inkstone default.
47
- */
48
- fontSize?: number;
49
- /**
50
- * UI locale for Monaco's built-in chrome — page-global and locked by the first editor to
51
- * mount, exactly as on `<MonacoEditor>`.
52
- */
53
- locale?: "en" | "zh-cn";
54
- placeholder?: string;
55
- /**
56
- * The editor's accessible name, set on Monaco's own input surface — several SQL editors on one
57
- * page are indistinguishable to assistive tech without one.
58
- */
59
- ariaLabel: string;
17
+ export type SqlCaret = ConsoleCaret;
18
+ export interface SqlEditorProps extends ConsoleEditorProps<SqlEditorHandle> {
60
19
  /**
61
20
  * The Monaco language id the model registers under.
62
21
  *
63
22
  * @default "sql"
64
23
  */
65
24
  languageId?: string;
66
- /**
67
- * Shown while the Monaco host loads / when it fails to load — passed through to
68
- * `<MonacoEditor>`, whose English defaults apply when omitted.
69
- */
70
- loading?: ReactNode;
71
- failure?: ReactNode;
72
- /**
73
- * Ctrl/Cmd-Enter — the run key, bound inside the editor because that is where the operator's
74
- * hands are. It is handed the editor's live caret, from which the host works out what to send.
75
- * Read through a ref at every press, so it always runs against the connection the *current*
76
- * render is bound to.
77
- */
78
- onRun?: (caret: SqlCaret) => void;
79
25
  /**
80
26
  * The label the run action shows in Monaco's own chrome (command palette, context menu).
81
27
  *
82
28
  * @default "Run statement"
83
29
  */
84
30
  runActionLabel?: string;
85
- /**
86
- * The caret moved, or the text under it changed. The host tracks it to show what a run would
87
- * send; the run key does not depend on that tracking having caught up.
88
- */
89
- onCaret?: (caret: SqlCaret) => void;
90
31
  /**
91
32
  * The span to mark as the one a run would send. Null leaves the gutter clean, which is the
92
33
  * right answer while there is nothing to choose between.
@@ -108,35 +49,19 @@ export interface SqlEditorProps {
108
49
  * says so — a deliberate keystroke that changes nothing needs to explain itself.
109
50
  */
110
51
  onFormatRefused?: () => void;
111
- onReady?: (handle: SqlEditorHandle) => void;
112
- /**
113
- * Runs after this component's own mount wiring, with the live editor and the monaco module —
114
- * the escape hatch for host-specific concerns (focus shields, extra actions).
115
- */
116
- onMount?: (instance: editor.IStandaloneCodeEditor, monaco: MonacoModule) => void;
117
- /**
118
- * Extra construction options, merged under this editor's own (`ariaLabel`): the escape hatch
119
- * a host reaches for a construction option no prop names — `overflowWidgetsDomNode`, where
120
- * the page's own layers would otherwise stand over the editor's floating widgets (hover,
121
- * completion, parameter hints) left inside it.
122
- */
123
- options?: editor.IStandaloneEditorConstructionOptions;
124
52
  /**
125
53
  * What the editor's language features answer from — the schema and dialect of the connection
126
- * being edited (`createSqlAssist`). Read through a ref at every request, so the host can swap
127
- * it without re-registering anything with Monaco.
54
+ * being edited (`createSqlAssist`). Read at every request, so the host can swap it without
55
+ * re-registering anything with Monaco.
128
56
  */
129
57
  assist?: SqlAssist;
130
58
  }
131
59
  /**
132
- * The SQL editing surface: Monaco in `sql`, highlighted by the same shared Shiki grammar every
133
- * other inkstone surface uses. It is deliberately not a script editor — a TypeScript language
134
- * service and an ambient profile have no meaning for a vendor's SQL. Its language features are
135
- * the host's: the `assist` knows the catalog and the dialect and this component only wires it to
136
- * Monaco (`registerSqlLanguage`) — it never guesses at SQL itself.
137
- *
138
- * The editor draws no chrome of its own: it is a band of its host's surface, and the separation
139
- * is the host's hairline rules — a border here would dress a pane up as a form input.
60
+ * The SQL editing surface: `<MonacoConsoleEditor>` in `sql`, highlighted by the same shared Shiki
61
+ * grammar every other inkstone surface uses. It is deliberately not a script editor — a
62
+ * TypeScript language service and an ambient profile have no meaning for a vendor's SQL. Its
63
+ * language features are the host's: the `assist` knows the catalog and the dialect and this
64
+ * component only wires it to Monaco (`registerSqlLanguage`) — it never guesses at SQL itself.
140
65
  */
141
- export declare function SqlEditor({ value, onChange, dark, height, fontSize, locale, placeholder, ariaLabel, languageId, loading, failure, onRun, runActionLabel, onCaret, highlight, highlightClassName, onFormatRefused, onReady, onMount, options, assist }: SqlEditorProps): import("react").JSX.Element;
66
+ export declare function SqlEditor({ languageId, runActionLabel, assist, onFormatRefused, onReady, options, ...props }: SqlEditorProps): ReactNode;
142
67
  //#endregion
package/dist/react.js CHANGED
@@ -1,135 +1,46 @@
1
1
  import { t as registerSqlLanguage } from "./monaco-DEMsSuzD.js";
2
- import { MonacoEditor } from "@coldsmirk/inkstone-react/monaco";
3
- import { useEffect, useInsertionEffect, useRef, useState } from "react";
2
+ import { MonacoConsoleEditor } from "@coldsmirk/inkstone-react/monaco";
3
+ import { useMemo, useRef } from "react";
4
4
  import { jsx } from "react/jsx-runtime";
5
5
  //#region src/react.tsx
6
- const EDITOR_ID_KEY = Symbol.for("coldsmirk.inkstone.sql.editorId");
7
- function nextEditorId() {
8
- const holder = globalThis;
9
- const id = holder[EDITOR_ID_KEY] ?? 0;
10
- holder[EDITOR_ID_KEY] = id + 1;
11
- return id;
12
- }
13
6
  const FORMAT_CHORD_CONTEXT_KEY = "inkstoneSqlFormatChord";
14
- function useLatest(value) {
15
- const ref = useRef(value);
16
- useInsertionEffect(() => {
17
- ref.current = value;
18
- });
19
- return ref;
20
- }
21
- function caretOf(instance) {
22
- const model = instance.getModel();
23
- const selection = instance.getSelection();
24
- if (model === null || selection === null) return {
25
- text: "",
26
- from: 0,
27
- to: 0
28
- };
29
- return {
30
- text: model.getValue(),
31
- from: model.getOffsetAt(selection.getStartPosition()),
32
- to: model.getOffsetAt(selection.getEndPosition())
33
- };
34
- }
35
- function monacoRange(model, span) {
36
- const from = model.getPositionAt(span.from);
37
- const to = model.getPositionAt(span.to);
38
- return {
39
- startLineNumber: from.lineNumber,
40
- startColumn: from.column,
41
- endLineNumber: to.lineNumber,
42
- endColumn: to.column
43
- };
44
- }
45
- function SqlEditor({ value, onChange, dark = false, height = 200, fontSize, locale, placeholder, ariaLabel, languageId = "sql", loading, failure, onRun, runActionLabel = "Run statement", onCaret, highlight = null, highlightClassName = "inkstone-sql-runnable", onFormatRefused, onReady, onMount, options, assist }) {
46
- const [modelPath] = useState(() => `file:///inkstone-sql/${nextEditorId()}.sql`);
47
- const bindings = useRef([]);
48
- const assistRef = useLatest(assist ?? null);
49
- const formatRefusedRef = useLatest(onFormatRefused);
50
- const runRef = useLatest(onRun);
51
- const caretChangedRef = useLatest(onCaret);
52
- const readyRef = useLatest(onReady);
53
- const mountRef = useLatest(onMount);
54
- const instance = useRef(null);
55
- const marked = useRef(null);
56
- const [mounted, setMounted] = useState(false);
57
- useEffect(() => {
58
- const model = instance.current?.getModel();
59
- if (model === null || model === void 0) return;
60
- marked.current?.clear();
61
- marked.current = highlight === null ? null : instance.current.createDecorationsCollection([{
62
- range: monacoRange(model, highlight),
63
- options: {
64
- linesDecorationsClassName: highlightClassName,
65
- isWholeLine: true
66
- }
67
- }]);
68
- }, [
69
- highlight,
70
- highlightClassName,
71
- mounted
72
- ]);
73
- useEffect(() => () => {
74
- for (const binding of bindings.current) binding.dispose();
75
- bindings.current = [];
76
- }, []);
77
- const fill = height === "fill";
78
- return /* @__PURE__ */ jsx("div", {
79
- style: fill ? {
80
- height: "100%",
81
- minHeight: 0
82
- } : { height },
83
- children: /* @__PURE__ */ jsx(MonacoEditor, {
84
- showLineNumbers: true,
85
- dark,
86
- failure,
87
- fontSize,
88
- height: fill ? "100%" : height,
89
- language: languageId,
90
- loading,
91
- locale,
92
- path: modelPath,
93
- placeholder,
94
- value,
95
- options: {
96
- ...options,
97
- ariaLabel,
98
- suggest: { snippetsPreventQuickSuggestions: false }
99
- },
100
- onChange,
101
- onMount: (live, monaco) => {
102
- instance.current = live;
103
- live.createContextKey(FORMAT_CHORD_CONTEXT_KEY, true);
104
- bindings.current.push(live.onDidChangeCursorSelection(() => caretChangedRef.current?.(caretOf(live))), live.addAction({
105
- id: "inkstone-sql.run",
106
- label: runActionLabel,
107
- keybindings: [monaco.KeyMod.CtrlCmd | monaco.KeyCode.Enter],
108
- run: (current) => runRef.current?.(caretOf(current))
109
- }), monaco.editor.addKeybindingRule({
110
- keybinding: monaco.KeyMod.Shift | monaco.KeyMod.Alt | monaco.KeyCode.KeyF,
111
- command: "editor.action.formatDocument",
112
- when: `${FORMAT_CHORD_CONTEXT_KEY} && editorTextFocus && !editorReadonly`
113
- }), ...registerSqlLanguage(monaco, modelPath, {
114
- assist: () => assistRef.current,
115
- onFormatRefused: () => formatRefusedRef.current?.()
116
- }, languageId));
117
- readyRef.current?.({
118
- format: () => void live.getAction("editor.action.formatDocument")?.run(),
119
- insert: (text) => {
120
- const selection = live.getSelection();
121
- if (selection) live.executeEdits("inkstone-sql-insert", [{
122
- range: selection,
123
- text,
124
- forceMoveMarkers: true
125
- }]);
126
- live.focus();
127
- }
128
- });
129
- mountRef.current?.(live, monaco);
130
- setMounted(true);
131
- }
132
- })
7
+ function SqlEditor({ languageId = "sql", runActionLabel = "Run statement", assist, onFormatRefused, onReady, options, ...props }) {
8
+ const live = useRef(null);
9
+ const sqlOptions = useMemo(() => {
10
+ return {
11
+ ...options,
12
+ suggest: { snippetsPreventQuickSuggestions: false }
13
+ };
14
+ }, [options]);
15
+ return /* @__PURE__ */ jsx(MonacoConsoleEditor, {
16
+ ...props,
17
+ context: {
18
+ assist,
19
+ onFormatRefused
20
+ },
21
+ extension: "sql",
22
+ language: languageId,
23
+ namespace: "inkstone-sql",
24
+ options: sqlOptions,
25
+ runActionLabel,
26
+ register: ({ monaco, editor: instance, path, context }) => {
27
+ live.current = instance;
28
+ instance.createContextKey(FORMAT_CHORD_CONTEXT_KEY, true);
29
+ return [monaco.editor.addKeybindingRule({
30
+ keybinding: monaco.KeyMod.Shift | monaco.KeyMod.Alt | monaco.KeyCode.KeyF,
31
+ command: "editor.action.formatDocument",
32
+ when: `${FORMAT_CHORD_CONTEXT_KEY} && editorTextFocus && !editorReadonly`
33
+ }), ...registerSqlLanguage(monaco, path, {
34
+ assist: () => context().assist ?? null,
35
+ onFormatRefused: () => context().onFormatRefused?.()
36
+ }, languageId)];
37
+ },
38
+ onReady: (handle) => {
39
+ onReady?.({
40
+ ...handle,
41
+ format: () => void live.current?.getAction("editor.action.formatDocument")?.run()
42
+ });
43
+ }
133
44
  });
134
45
  }
135
46
  //#endregion
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@coldsmirk/inkstone-sql",
3
- "version": "0.31.0",
3
+ "version": "0.32.0",
4
4
  "description": "Schema-aware SQL editing intelligence for Monaco: lexical statement reading under per-dialect noise profiles, catalog-driven completion/hover/signature help, unified-placeholder awareness, formatting, and a drop-in React SQL editor.",
5
5
  "keywords": [
6
6
  "sql",
@@ -48,12 +48,12 @@
48
48
  "devDependencies": {
49
49
  "monaco-editor": "^0.57.0",
50
50
  "react": "^19.3.0",
51
- "@coldsmirk/inkstone-react": "^0.31.0"
51
+ "@coldsmirk/inkstone-react": "^0.32.0"
52
52
  },
53
53
  "peerDependencies": {
54
54
  "monaco-editor": "^0.57.0",
55
55
  "react": ">=19",
56
- "@coldsmirk/inkstone-react": "^0.31.0"
56
+ "@coldsmirk/inkstone-react": "^0.32.0"
57
57
  },
58
58
  "peerDependenciesMeta": {
59
59
  "@coldsmirk/inkstone-react": {