jaml-ui 0.22.5 → 0.24.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
@@ -1,147 +1,147 @@
1
- # jaml-ui
2
-
3
- React components, UI tokens, sprites, and utilities for Balatro/JAML apps.
4
-
5
- ## Install
6
-
7
- ```bash
8
- npm install jaml-ui react react-dom
9
- ```
10
-
11
- ## Package exports
12
-
13
- | Entry | Contents |
14
- |-------|----------|
15
- | `jaml-ui` | Game card components, JAML IDE, Analyzer Explorer, hooks |
16
- | `jaml-ui/ui` | Jimbo design system — JimboPanel, JimboButton, JimboModal, tokens |
17
- | `jaml-ui/core` | Pure asset helpers, sprite metadata, decode utilities (no React) |
18
- | `jaml-ui/motely` | motely-wasm decode helpers (requires `motely-wasm` peer) |
19
- | `jaml-ui/r3f` | 3D card component via React Three Fiber (requires r3f peers) |
20
-
21
- ## Quick start
22
-
23
- ```tsx
24
- import { JamlGameCard, AnalyzerExplorer, JamlIde } from "jaml-ui";
25
- import { JimboPanel, JimboButton } from "jaml-ui/ui";
26
- ```
27
-
28
- ### Game card
29
-
30
- ```tsx
31
- import { JamlGameCard } from "jaml-ui";
32
-
33
- <JamlGameCard
34
- type="joker"
35
- card={{ name: "Blueprint", edition: "Foil", isEternal: true, scale: 1.5 }}
36
- />
37
- ```
38
-
39
- ### Jimbo UI (Balatro design system)
40
-
41
- ```tsx
42
- import { JimboPanel, JimboButton, JimboModal } from "jaml-ui/ui";
43
- import { JimboColorOption } from "jaml-ui/ui";
44
-
45
- <JimboPanel sway onBack={() => setOpen(false)}>
46
- <JimboButton variant="primary" onClick={handleSearch}>Search</JimboButton>
47
- </JimboPanel>
48
- ```
49
-
50
- Available variants: `primary`, `secondary`, `danger`, `back`, `ghost`
51
-
52
- ### JAML IDE
53
-
54
- ```tsx
55
- import { JamlIde } from "jaml-ui";
56
-
57
- <JamlIde
58
- jaml={jaml}
59
- onChange={setJaml}
60
- searchResults={results}
61
- onSearch={handleSearch}
62
- isSearching={isSearching}
63
- />
64
- ```
65
-
66
- ### Analyzer Explorer
67
-
68
- ```tsx
69
- import { AnalyzerExplorer } from "jaml-ui";
70
-
71
- // antes: AnalyzerAnteView[] — stream from motely-wasm createSearchContext
72
- <AnalyzerExplorer antes={antes} totalAntes={8} highlights={highlights} />
73
- ```
74
-
75
- ### JAML Map Preview
76
-
77
- ```tsx
78
- import { JamlMapPreview } from "jaml-ui";
79
-
80
- <JamlMapPreview jaml={jaml} />
81
- ```
82
-
83
- ## Asset handling
84
-
85
- By default sprites resolve from the package `assets/` directory via `import.meta.url`.
86
-
87
- Override at app startup:
88
-
89
- ```ts
90
- import { setJamlAssetBaseUrl, clearJamlAssetBaseUrl } from "jaml-ui";
91
-
92
- setJamlAssetBaseUrl("/vendor/jaml-ui/"); // custom CDN
93
- clearJamlAssetBaseUrl(); // back to default
94
- ```
95
-
96
- ## Core utilities
97
-
98
- ```ts
99
- import { SPRITE_SHEETS, getSpriteData, resolveJamlAssetUrl } from "jaml-ui/core";
100
- ```
101
-
102
- ## Motely decode helpers
103
-
104
- ```ts
105
- import { decodeMotelyItemName, motelyItemTypeName } from "jaml-ui/motely";
106
- ```
107
-
108
- ## 3D card (optional)
109
-
110
- ```bash
111
- npm install three @react-three/fiber @react-three/drei @react-spring/three
112
- ```
113
-
114
- ```tsx
115
- import { Card3D } from "jaml-ui/r3f";
116
-
117
- <Card3D itemName="Blueprint" />
118
- ```
119
-
120
- ## Next.js
121
-
122
- Import pure helpers from `jaml-ui/core` for server components. For local workspace installs add:
123
-
124
- ```ts
125
- // next.config.ts
126
- const nextConfig = { transpilePackages: ["jaml-ui"] };
127
- ```
128
-
129
- ## Search Worker Architecture
130
-
131
- The library provides `useAnalyzer` to interact with `motely-wasm`'s search context natively.
132
- **Important:** As of `motely-wasm@14.3.3`, the search worker utilizes Vite's `?worker&inline` pattern. You no longer need to pass `motelyWasmUrl`.
133
-
134
- Ensure `motely-wasm` is imported and booted at the module level in your application:
135
-
136
- ```tsx
137
- import { boot } from "motely-wasm";
138
- boot(); // Call early in your application lifecycle
139
- ```
140
-
141
- ## Peer dependencies
142
-
143
- | Peer | Required for |
144
- |------|-------------|
145
- | `react`, `react-dom` | All components |
146
- | `motely-wasm ^14.3.3` | `jaml-ui/motely`, `AnalyzerExplorer`, `useAnalyzer` data |
147
- | `three`, `@react-three/fiber`, `@react-three/drei`, `@react-spring/three` | `jaml-ui/r3f` only |
1
+ # jaml-ui
2
+
3
+ React components, UI tokens, sprites, and utilities for Balatro/JAML apps.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ npm install jaml-ui react react-dom
9
+ ```
10
+
11
+ ## Package exports
12
+
13
+ | Entry | Contents |
14
+ |-------|----------|
15
+ | `jaml-ui` | Game card components, JAML IDE, Analyzer Explorer, hooks |
16
+ | `jaml-ui/ui` | Jimbo design system — JimboPanel, JimboButton, JimboModal, tokens |
17
+ | `jaml-ui/core` | Pure asset helpers, sprite metadata, decode utilities (no React) |
18
+ | `jaml-ui/motely` | motely-wasm decode helpers (requires `motely-wasm` peer) |
19
+ | `jaml-ui/r3f` | 3D card component via React Three Fiber (requires r3f peers) |
20
+
21
+ ## Quick start
22
+
23
+ ```tsx
24
+ import { JamlGameCard, AnalyzerExplorer, JamlIde } from "jaml-ui";
25
+ import { JimboPanel, JimboButton } from "jaml-ui/ui";
26
+ ```
27
+
28
+ ### Game card
29
+
30
+ ```tsx
31
+ import { JamlGameCard } from "jaml-ui";
32
+
33
+ <JamlGameCard
34
+ type="joker"
35
+ card={{ name: "Blueprint", edition: "Foil", isEternal: true, scale: 1.5 }}
36
+ />
37
+ ```
38
+
39
+ ### Jimbo UI (Balatro design system)
40
+
41
+ ```tsx
42
+ import { JimboPanel, JimboButton, JimboModal } from "jaml-ui/ui";
43
+ import { JimboColorOption } from "jaml-ui/ui";
44
+
45
+ <JimboPanel sway onBack={() => setOpen(false)}>
46
+ <JimboButton variant="primary" onClick={handleSearch}>Search</JimboButton>
47
+ </JimboPanel>
48
+ ```
49
+
50
+ Available variants: `primary`, `secondary`, `danger`, `back`, `ghost`
51
+
52
+ ### JAML IDE
53
+
54
+ ```tsx
55
+ import { JamlIde } from "jaml-ui";
56
+
57
+ <JamlIde
58
+ jaml={jaml}
59
+ onChange={setJaml}
60
+ searchResults={results}
61
+ onSearch={handleSearch}
62
+ isSearching={isSearching}
63
+ />
64
+ ```
65
+
66
+ ### Analyzer Explorer
67
+
68
+ ```tsx
69
+ import { AnalyzerExplorer } from "jaml-ui";
70
+
71
+ // antes: AnalyzerAnteView[] — stream from motely-wasm createSearchContext
72
+ <AnalyzerExplorer antes={antes} totalAntes={8} highlights={highlights} />
73
+ ```
74
+
75
+ ### JAML Map Preview
76
+
77
+ ```tsx
78
+ import { JamlMapPreview } from "jaml-ui";
79
+
80
+ <JamlMapPreview jaml={jaml} />
81
+ ```
82
+
83
+ ## Asset handling
84
+
85
+ By default sprites resolve from the package `assets/` directory via `import.meta.url`.
86
+
87
+ Override at app startup:
88
+
89
+ ```ts
90
+ import { setJamlAssetBaseUrl, clearJamlAssetBaseUrl } from "jaml-ui";
91
+
92
+ setJamlAssetBaseUrl("/vendor/jaml-ui/"); // custom CDN
93
+ clearJamlAssetBaseUrl(); // back to default
94
+ ```
95
+
96
+ ## Core utilities
97
+
98
+ ```ts
99
+ import { SPRITE_SHEETS, getSpriteData, resolveJamlAssetUrl } from "jaml-ui/core";
100
+ ```
101
+
102
+ ## Motely decode helpers
103
+
104
+ ```ts
105
+ import { decodeMotelyItemName, motelyItemTypeName } from "jaml-ui/motely";
106
+ ```
107
+
108
+ ## 3D card (optional)
109
+
110
+ ```bash
111
+ npm install three @react-three/fiber @react-three/drei @react-spring/three
112
+ ```
113
+
114
+ ```tsx
115
+ import { Card3D } from "jaml-ui/r3f";
116
+
117
+ <Card3D itemName="Blueprint" />
118
+ ```
119
+
120
+ ## Next.js
121
+
122
+ Import pure helpers from `jaml-ui/core` for server components. For local workspace installs add:
123
+
124
+ ```ts
125
+ // next.config.ts
126
+ const nextConfig = { transpilePackages: ["jaml-ui"] };
127
+ ```
128
+
129
+ ## Search Worker Architecture
130
+
131
+ The library provides `useAnalyzer` to interact with `motely-wasm`'s search context natively.
132
+ **Important:** As of `motely-wasm@14.3.3`, the search worker utilizes Vite's `?worker&inline` pattern. You no longer need to pass `motelyWasmUrl`.
133
+
134
+ Ensure `motely-wasm` is imported and booted at the module level in your application:
135
+
136
+ ```tsx
137
+ import { boot } from "motely-wasm";
138
+ boot(); // Call early in your application lifecycle
139
+ ```
140
+
141
+ ## Peer dependencies
142
+
143
+ | Peer | Required for |
144
+ |------|-------------|
145
+ | `react`, `react-dom` | All components |
146
+ | `motely-wasm ^14.3.3` | `jaml-ui/motely`, `AnalyzerExplorer`, `useAnalyzer` data |
147
+ | `three`, `@react-three/fiber`, `@react-three/drei`, `@react-spring/three` | `jaml-ui/r3f` only |
@@ -4,4 +4,4 @@ export interface JamlCodeEditorProps {
4
4
  placeholder?: string;
5
5
  minHeight?: number;
6
6
  }
7
- export declare function JamlCodeEditor({ value, onChange, minHeight, }: JamlCodeEditorProps): import("react/jsx-runtime").JSX.Element;
7
+ export declare function JamlCodeEditor({ value, onChange, placeholder, minHeight, }: JamlCodeEditorProps): import("react/jsx-runtime").JSX.Element;
@@ -1,102 +1,131 @@
1
1
  "use client";
2
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { jsx as _jsx } from "react/jsx-runtime";
3
3
  import { useEffect, useRef } from "react";
4
- import Editor from "@monaco-editor/react";
4
+ import { EditorView, keymap, lineNumbers, highlightActiveLine, drawSelection, placeholder as cmPlaceholder } from "@codemirror/view";
5
+ import { EditorState } from "@codemirror/state";
6
+ import { yaml } from "@codemirror/lang-yaml";
7
+ import { defaultKeymap, history, historyKeymap, indentWithTab } from "@codemirror/commands";
8
+ import { syntaxHighlighting, HighlightStyle } from "@codemirror/language";
9
+ import { tags } from "@lezer/highlight";
5
10
  import { JimboColorOption } from "../ui/tokens.js";
6
- // Monaco needs hex strings for its colors API. We strip the leading `#` from
7
- // JimboColor tokens where Monaco expects raw hex for syntax rules (token
8
- // foreground), and pass the full `#...` form for UI colors. Alpha suffix
9
- // (e.g. WHITE + "20") is valid for Monaco colors but not for rules.
10
- const hex = (token) => token.replace(/^#/, "");
11
- const defineBalatroTheme = (monaco) => {
12
- monaco.editor.defineTheme("jaml-balatro-dark", {
13
- base: "vs-dark",
14
- inherit: true,
15
- rules: [
16
- { token: "comment", foreground: hex(JimboColorOption.GREY), fontStyle: "italic" },
17
- { token: "keyword", foreground: hex(JimboColorOption.RED) },
18
- { token: "string", foreground: hex(JimboColorOption.GOLD_TEXT) },
19
- { token: "number", foreground: hex(JimboColorOption.BLUE) },
20
- { token: "type", foreground: hex(JimboColorOption.GREEN_TEXT) },
21
- ],
22
- colors: {
23
- "editor.background": JimboColorOption.DARKEST,
24
- "editor.foreground": JimboColorOption.WHITE,
25
- "editorLineNumber.foreground": JimboColorOption.GREY,
26
- "editorLineNumber.activeForeground": JimboColorOption.GOLD_TEXT,
27
- "editor.selectionBackground": `${JimboColorOption.WHITE}20`,
28
- "editor.inactiveSelectionBackground": `${JimboColorOption.WHITE}10`,
29
- "editor.lineHighlightBackground": `${JimboColorOption.BLACK}20`,
30
- "editorCursor.foreground": JimboColorOption.GOLD_TEXT,
31
- "editorWidget.background": JimboColorOption.DARK_GREY,
32
- "editorWidget.border": `${JimboColorOption.WHITE}20`,
33
- "editorWidget.foreground": JimboColorOption.WHITE,
34
- "list.activeSelectionBackground": JimboColorOption.GOLD,
35
- "list.activeSelectionForeground": JimboColorOption.DARKEST,
36
- "list.hoverBackground": JimboColorOption.PANEL_EDGE,
37
- "list.hoverForeground": JimboColorOption.WHITE,
38
- "list.focusBackground": JimboColorOption.GOLD,
39
- "list.focusForeground": JimboColorOption.DARKEST,
40
- },
41
- });
42
- };
43
- export function JamlCodeEditor({ value, onChange, minHeight = 320, }) {
44
- const editorRef = useRef(null);
45
- const monacoRef = useRef(null);
46
- // Suppress our onChange while we're applying a programmatic edit, so the
47
- // streamed parent value doesn't loop back through onChange and bounce.
11
+ const balatroHighlight = HighlightStyle.define([
12
+ { tag: tags.comment, color: JimboColorOption.GREY, fontStyle: "italic" },
13
+ { tag: tags.keyword, color: JimboColorOption.RED },
14
+ { tag: tags.string, color: JimboColorOption.GOLD_TEXT },
15
+ { tag: tags.number, color: JimboColorOption.BLUE },
16
+ { tag: tags.bool, color: JimboColorOption.BLUE },
17
+ { tag: tags.null, color: JimboColorOption.GREY },
18
+ { tag: tags.propertyName, color: JimboColorOption.GREEN_TEXT },
19
+ { tag: tags.typeName, color: JimboColorOption.GREEN_TEXT },
20
+ ]);
21
+ const balatroTheme = EditorView.theme({
22
+ "&": {
23
+ backgroundColor: JimboColorOption.DARKEST,
24
+ color: JimboColorOption.WHITE,
25
+ fontSize: "13px",
26
+ height: "100%",
27
+ },
28
+ ".cm-content": {
29
+ fontFamily: "'m6x11mono', 'm6x11plus', ui-monospace, monospace",
30
+ lineHeight: "22px",
31
+ padding: "12px 0",
32
+ caretColor: JimboColorOption.GOLD_TEXT,
33
+ minHeight: "100%",
34
+ },
35
+ ".cm-gutters": {
36
+ backgroundColor: JimboColorOption.DARKEST,
37
+ color: JimboColorOption.GREY,
38
+ border: "none",
39
+ },
40
+ ".cm-lineNumbers .cm-gutterElement": {
41
+ minWidth: "2ch",
42
+ padding: "0 6px 0 8px",
43
+ },
44
+ ".cm-activeLineGutter": {
45
+ color: JimboColorOption.GOLD_TEXT,
46
+ backgroundColor: "transparent",
47
+ },
48
+ ".cm-activeLine": {
49
+ backgroundColor: `${JimboColorOption.BLACK}20`,
50
+ },
51
+ ".cm-selectionBackground": {
52
+ backgroundColor: `${JimboColorOption.WHITE}20 !important`,
53
+ },
54
+ "&.cm-focused .cm-selectionBackground": {
55
+ backgroundColor: `${JimboColorOption.WHITE}20`,
56
+ },
57
+ ".cm-cursor, .cm-dropCursor": {
58
+ borderLeftColor: JimboColorOption.GOLD_TEXT,
59
+ },
60
+ ".cm-scroller": {
61
+ overflow: "auto",
62
+ },
63
+ ".cm-placeholder": {
64
+ color: JimboColorOption.GREY,
65
+ fontStyle: "italic",
66
+ },
67
+ }, { dark: true });
68
+ export function JamlCodeEditor({ value, onChange, placeholder = "", minHeight = 320, }) {
69
+ const containerRef = useRef(null);
70
+ const viewRef = useRef(null);
48
71
  const suppressEmitRef = useRef(false);
49
- // Capture initial value for the uncontrolled editor mount; subsequent
50
- // updates flow through the useEffect below.
51
- const initialValueRef = useRef(value);
52
- // Track value across renders so we can apply only the streamed delta when
53
- // the new value is a strict suffix-extension of what's already in the model.
54
72
  const lastSyncedValueRef = useRef(value);
55
- const handleMount = (editor, m) => {
56
- editorRef.current = editor;
57
- monacoRef.current = m;
58
- };
73
+ const onChangeRef = useRef(onChange);
74
+ useEffect(() => { onChangeRef.current = onChange; });
59
75
  useEffect(() => {
60
- const editor = editorRef.current;
61
- const m = monacoRef.current;
62
- if (!editor || !m)
76
+ if (!containerRef.current)
63
77
  return;
64
- const model = editor.getModel();
65
- if (!model)
78
+ const view = new EditorView({
79
+ state: EditorState.create({
80
+ doc: lastSyncedValueRef.current,
81
+ extensions: [
82
+ history(),
83
+ lineNumbers(),
84
+ highlightActiveLine(),
85
+ drawSelection(),
86
+ yaml(),
87
+ syntaxHighlighting(balatroHighlight),
88
+ balatroTheme,
89
+ EditorView.lineWrapping,
90
+ keymap.of([indentWithTab, ...defaultKeymap, ...historyKeymap]),
91
+ ...(placeholder ? [cmPlaceholder(placeholder)] : []),
92
+ EditorView.updateListener.of((update) => {
93
+ if (update.docChanged && !suppressEmitRef.current) {
94
+ onChangeRef.current(update.state.doc.toString());
95
+ }
96
+ }),
97
+ ],
98
+ }),
99
+ parent: containerRef.current,
100
+ });
101
+ viewRef.current = view;
102
+ return () => {
103
+ view.destroy();
104
+ viewRef.current = null;
105
+ };
106
+ // eslint-disable-next-line react-hooks/exhaustive-deps
107
+ }, []);
108
+ useEffect(() => {
109
+ const view = viewRef.current;
110
+ if (!view)
66
111
  return;
67
- const current = editor.getValue();
112
+ const current = view.state.doc.toString();
68
113
  if (current === value) {
69
114
  lastSyncedValueRef.current = value;
70
115
  return;
71
116
  }
72
117
  suppressEmitRef.current = true;
73
118
  try {
74
- // Streaming-friendly path: when the new value just appends to what
75
- // Monaco already has, push an insert at end-of-document. Monaco
76
- // re-tokenizes only from the insertion point — no full-doc churn,
77
- // no syntax-color strobe, no cursor reset.
119
+ // Streaming-friendly: append only the suffix when new value extends current.
78
120
  if (value.length > current.length && value.startsWith(current)) {
79
- const suffix = value.slice(current.length);
80
- const lastLine = model.getLineCount();
81
- const lastCol = model.getLineMaxColumn(lastLine);
82
- model.applyEdits([
83
- {
84
- range: new m.Range(lastLine, lastCol, lastLine, lastCol),
85
- text: suffix,
86
- forceMoveMarkers: false,
87
- },
88
- ]);
121
+ view.dispatch({
122
+ changes: { from: view.state.doc.length, insert: value.slice(current.length) },
123
+ });
89
124
  }
90
125
  else {
91
- // Real replacement (paste from outside, parent rewrite, undo, etc.).
92
- editor.executeEdits("", [
93
- {
94
- range: model.getFullModelRange(),
95
- text: value,
96
- forceMoveMarkers: true,
97
- },
98
- ]);
99
- editor.pushUndoStop();
126
+ view.dispatch({
127
+ changes: { from: 0, to: view.state.doc.length, insert: value },
128
+ });
100
129
  }
101
130
  }
102
131
  finally {
@@ -104,35 +133,5 @@ export function JamlCodeEditor({ value, onChange, minHeight = 320, }) {
104
133
  lastSyncedValueRef.current = value;
105
134
  }
106
135
  }, [value]);
107
- return (_jsxs("div", { style: { width: "100%", minHeight, background: JimboColorOption.DARKEST }, children: [_jsx("style", { children: `
108
- .monaco-editor .iPadShowKeyboard,
109
- .monaco-editor [class*="iPadShowKeyboard"] { display: none !important; }
110
- ` }), _jsx(Editor, { height: `${minHeight}px`, defaultLanguage: "yaml", defaultValue: initialValueRef.current, theme: "jaml-balatro-dark", onChange: (next) => {
111
- if (suppressEmitRef.current)
112
- return;
113
- onChange(next ?? "");
114
- }, onMount: handleMount, beforeMount: defineBalatroTheme, options: {
115
- minimap: { enabled: false },
116
- scrollBeyondLastLine: false,
117
- fontSize: 13,
118
- lineHeight: 22,
119
- fontFamily: "ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace",
120
- lineNumbers: "on",
121
- lineNumbersMinChars: 2,
122
- lineDecorationsWidth: 4,
123
- glyphMargin: false,
124
- folding: false,
125
- automaticLayout: true,
126
- padding: { top: 12, bottom: 12 },
127
- wordWrap: "on",
128
- formatOnPaste: true,
129
- formatOnType: true,
130
- renderLineHighlight: "line",
131
- scrollbar: { verticalScrollbarSize: 8, horizontalScrollbarSize: 8 },
132
- // Mobile/chat-WebView UX: kill the context menu ("Change All Occurrences" etc. covering half
133
- // the file on long-press) and the accessibility-help keyboard widget that's just clutter when
134
- // the OS keyboard is already open.
135
- contextmenu: false,
136
- accessibilitySupport: "off",
137
- } })] }));
136
+ return (_jsx("div", { ref: containerRef, style: { width: "100%", minHeight, background: JimboColorOption.DARKEST } }));
138
137
  }