@hanzo/ui 8.27.82 → 8.27.84
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/dist/backends/gui/code-editor.cjs +80 -84
- package/dist/backends/gui/code-editor.d.ts +8 -6
- package/dist/backends/gui/code-editor.d.ts.map +1 -1
- package/dist/backends/gui/code-editor.js +83 -87
- package/dist/backends/gui/code-editor.js.map +1 -1
- package/dist/backends/gui/grip.cjs +132 -0
- package/dist/backends/gui/grip.d.ts +28 -0
- package/dist/backends/gui/grip.d.ts.map +1 -0
- package/dist/backends/gui/grip.js +96 -0
- package/dist/backends/gui/grip.js.map +1 -0
- package/dist/backends/gui/json-tree.cjs +110 -15
- package/dist/backends/gui/json-tree.d.ts +43 -9
- package/dist/backends/gui/json-tree.d.ts.map +1 -1
- package/dist/backends/gui/json-tree.js +110 -16
- package/dist/backends/gui/json-tree.js.map +1 -1
- package/dist/backends/gui/resizable.cjs +12 -3
- package/dist/backends/gui/resizable.d.ts +4 -2
- package/dist/backends/gui/resizable.d.ts.map +1 -1
- package/dist/backends/gui/resizable.js +12 -3
- package/dist/backends/gui/resizable.js.map +1 -1
- package/dist/code-theme.cjs +44 -0
- package/dist/code-theme.d.ts +46 -0
- package/dist/code-theme.d.ts.map +1 -0
- package/dist/code-theme.js +42 -0
- package/dist/code-theme.js.map +1 -0
- package/dist/core/index.cjs +4 -1
- package/dist/core/index.d.ts +1 -0
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js +1 -0
- package/dist/core/index.js.map +1 -1
- package/dist/gallery.html +250 -250
- package/dist/gui-config.cjs +3 -0
- package/dist/gui-config.d.ts +218 -209
- package/dist/gui-config.d.ts.map +1 -1
- package/dist/gui-config.js +3 -0
- package/dist/gui-config.js.map +1 -1
- package/dist/index.cjs +8 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -0
- package/dist/index.js.map +1 -1
- package/dist/index.node.cjs +8 -1
- package/dist/index.node.js +5 -0
- package/dist/styles.css +124 -124
- package/package.json +1 -1
|
@@ -50,16 +50,18 @@ const jsx_runtime_1 = require("react/jsx-runtime");
|
|
|
50
50
|
*
|
|
51
51
|
* JSON. `language="json"` colours the syntax and checks the text on every
|
|
52
52
|
* change with `checkJson`: a broken document tints the failing line, marks the
|
|
53
|
-
* failing character and names line, column and reason in the footer.
|
|
53
|
+
* failing character and names line, column and reason in the footer. Every
|
|
54
|
+
* object and array that spans lines folds from a marker in the gutter, and a
|
|
55
|
+
* folded one reads `{…}` or `[…]` with how many keys or items it hides. With
|
|
54
56
|
* `allowText`, text that does not open an object or an array is plain text and
|
|
55
57
|
* passes. Format (or Shift-Alt-F) re-indents valid JSON by two spaces. Other
|
|
56
58
|
* languages are edited as plain text: the package carries one grammar.
|
|
57
59
|
*
|
|
58
|
-
* Colour.
|
|
59
|
-
* hairline, the selection and the error state —
|
|
60
|
-
*
|
|
61
|
-
*
|
|
62
|
-
*
|
|
60
|
+
* Colour. The frame's colours are theme rungs over design tokens — the ink, the
|
|
61
|
+
* hairline, the selection and the error state — and the syntax is the code
|
|
62
|
+
* theme (`code-theme.ts`: Dracula on dark, GitHub Light on light), each token a
|
|
63
|
+
* theme key of its own. Both follow the page into light or dark with nothing to
|
|
64
|
+
* configure.
|
|
63
65
|
*/
|
|
64
66
|
const gui_1 = require("@hanzo/gui");
|
|
65
67
|
const lucide_icons_2_1 = require("@hanzogui/lucide-icons-2");
|
|
@@ -71,6 +73,7 @@ const view_1 = require("@codemirror/view");
|
|
|
71
73
|
const highlight_1 = require("@lezer/highlight");
|
|
72
74
|
const React = __importStar(require("react"));
|
|
73
75
|
const button_1 = require("./button.cjs");
|
|
76
|
+
const grip_1 = require("./grip.cjs");
|
|
74
77
|
const json_1 = require("./json.cjs");
|
|
75
78
|
const select_1 = require("./select.cjs");
|
|
76
79
|
const slot_1 = require("./slot.cjs");
|
|
@@ -130,10 +133,6 @@ const LABEL = {
|
|
|
130
133
|
};
|
|
131
134
|
/** Line height as a multiple of the font size. */
|
|
132
135
|
const LEADING = 1.6;
|
|
133
|
-
/** How far one ArrowUp / ArrowDown on the footer moves the height. */
|
|
134
|
-
const STEP = 24;
|
|
135
|
-
/** The separator's stated maximum, in px: the drag itself has none. */
|
|
136
|
-
const RESIZE_CEILING = 4096;
|
|
137
136
|
const MONO = "var(--font-mono, 'Zen Mono', ui-monospace, SFMono-Regular, Menlo, Consolas, monospace)";
|
|
138
137
|
/**
|
|
139
138
|
* The editor's surface. Every colour is one of the theme's named rungs — gui emits
|
|
@@ -180,6 +179,18 @@ const FRAME = /* @__PURE__ */ view_1.EditorView.theme({
|
|
|
180
179
|
},
|
|
181
180
|
'.cm-nonmatchingBracket, &.cm-focused .cm-nonmatchingBracket': { backgroundColor: 'transparent' },
|
|
182
181
|
'.cm-placeholder': { color: 'var(--dim)' },
|
|
182
|
+
'.cm-foldGutter .cm-gutterElement': { padding: '0 4px', color: 'var(--dim)', cursor: 'pointer' },
|
|
183
|
+
'.cm-foldGutter .cm-gutterElement:hover': { color: 'var(--ink)' },
|
|
184
|
+
'.cm-foldPlaceholder': {
|
|
185
|
+
backgroundColor: 'var(--raised)',
|
|
186
|
+
border: '1px solid var(--edge)',
|
|
187
|
+
borderRadius: '4px',
|
|
188
|
+
color: 'var(--codePunctuation)',
|
|
189
|
+
padding: '0 4px',
|
|
190
|
+
margin: '0 2px',
|
|
191
|
+
cursor: 'pointer',
|
|
192
|
+
},
|
|
193
|
+
'.cm-foldCount': { color: 'var(--codeComment)' },
|
|
183
194
|
'.cm-fault': { backgroundColor: 'var(--state-error-bg, rgb(239 68 68 / .1))' },
|
|
184
195
|
'.cm-gutterElement.cm-fault': { color: 'var(--bad)' },
|
|
185
196
|
'.cm-fault-at': {
|
|
@@ -188,13 +199,55 @@ const FRAME = /* @__PURE__ */ view_1.EditorView.theme({
|
|
|
188
199
|
textUnderlineOffset: '3px',
|
|
189
200
|
},
|
|
190
201
|
});
|
|
191
|
-
/** The syntax
|
|
192
|
-
const
|
|
193
|
-
{ tag: highlight_1.tags.propertyName, color: 'var(--
|
|
194
|
-
{ tag: highlight_1.tags.string, color: 'var(--
|
|
195
|
-
{ tag:
|
|
196
|
-
{ tag:
|
|
202
|
+
/** The syntax in the code theme's colours: one theme key per kind of token, from `code-theme.ts`. */
|
|
203
|
+
const SYNTAX = /* @__PURE__ */ language_1.HighlightStyle.define([
|
|
204
|
+
{ tag: highlight_1.tags.propertyName, color: 'var(--codeKey)' },
|
|
205
|
+
{ tag: highlight_1.tags.string, color: 'var(--codeString)' },
|
|
206
|
+
{ tag: highlight_1.tags.number, color: 'var(--codeNumber)' },
|
|
207
|
+
{ tag: highlight_1.tags.bool, color: 'var(--codeBoolean)' },
|
|
208
|
+
{ tag: highlight_1.tags.null, color: 'var(--codeNull)' },
|
|
209
|
+
{ tag: [highlight_1.tags.punctuation, highlight_1.tags.separator, highlight_1.tags.brace, highlight_1.tags.squareBracket], color: 'var(--codePunctuation)' },
|
|
210
|
+
{ tag: highlight_1.tags.comment, color: 'var(--codeComment)' },
|
|
211
|
+
{ tag: highlight_1.tags.keyword, color: 'var(--codeKeyword)' },
|
|
197
212
|
]);
|
|
213
|
+
/**
|
|
214
|
+
* JSON folds from its opening bracket through its closing one, so a folded block
|
|
215
|
+
* is drawn whole as `{…}` or `[…]` and has room to say what it holds. lang-json
|
|
216
|
+
* folds inside the brackets, which leaves them standing around an ellipsis.
|
|
217
|
+
*/
|
|
218
|
+
const JSON_GRAMMAR = /* @__PURE__ */ new language_1.LanguageSupport(lang_json_1.jsonLanguage.configure({
|
|
219
|
+
props: [language_1.foldNodeProp.add({ 'Object Array': (node) => ({ from: node.from, to: node.to }) })],
|
|
220
|
+
}));
|
|
221
|
+
const words = (n, one) => `${n} ${one}${n === 1 ? '' : 's'}`;
|
|
222
|
+
/** What a folded range holds: its brackets and how many keys or items sit inside. */
|
|
223
|
+
function measureFold(state, range) {
|
|
224
|
+
let node = (0, language_1.syntaxTree)(state).resolveInner(range.from, 1);
|
|
225
|
+
while (node.parent && node.name !== 'Object' && node.name !== 'Array')
|
|
226
|
+
node = node.parent;
|
|
227
|
+
if (node.name === 'Object')
|
|
228
|
+
return { open: '{', close: '}', count: words(node.getChildren('Property').length, 'key') };
|
|
229
|
+
let n = 0;
|
|
230
|
+
for (let c = node.firstChild; c; c = c.nextSibling)
|
|
231
|
+
if (!/^[[\],]$/.test(c.name) && !c.type.isError)
|
|
232
|
+
n += 1;
|
|
233
|
+
return { open: '[', close: ']', count: words(n, 'item') };
|
|
234
|
+
}
|
|
235
|
+
const FOLDING = /* @__PURE__ */ (0, language_1.codeFolding)({
|
|
236
|
+
preparePlaceholder: measureFold,
|
|
237
|
+
placeholderDOM: (_view, onclick, fold) => {
|
|
238
|
+
const el = document.createElement('span');
|
|
239
|
+
el.className = 'cm-foldPlaceholder';
|
|
240
|
+
el.title = 'Unfold';
|
|
241
|
+
el.setAttribute('aria-label', `Folded, ${fold.count}`);
|
|
242
|
+
el.textContent = `${fold.open}…${fold.close}`;
|
|
243
|
+
const count = document.createElement('span');
|
|
244
|
+
count.className = 'cm-foldCount';
|
|
245
|
+
count.textContent = ` ${fold.count}`;
|
|
246
|
+
el.append(count);
|
|
247
|
+
el.onclick = onclick;
|
|
248
|
+
return el;
|
|
249
|
+
},
|
|
250
|
+
});
|
|
198
251
|
/** How much deeper than its line a wrapped continuation starts, in characters. */
|
|
199
252
|
const HANG = 2;
|
|
200
253
|
/** A line's own indentation in characters, a tab counted at the editor's tab width. */
|
|
@@ -279,13 +332,16 @@ function CodeEditor({ value, defaultValue = '', language = 'javascript', height,
|
|
|
279
332
|
const [text, setText] = React.useState(value ?? defaultValue);
|
|
280
333
|
const [check, setCheck] = React.useState(null);
|
|
281
334
|
const [copied, setCopied] = React.useState(false);
|
|
282
|
-
const [dragged, setDragged] = React.useState(null);
|
|
283
335
|
const host = React.useRef(null);
|
|
284
|
-
const grip = React.useRef(null);
|
|
285
|
-
const knob = React.useRef(null);
|
|
286
336
|
/** Every text reported to `onChange` and not yet seen back as `value`. */
|
|
287
337
|
const sent = React.useRef([]);
|
|
288
338
|
const view = React.useRef(null);
|
|
339
|
+
const sized = (0, grip_1.useGrip)({
|
|
340
|
+
enabled: resizable,
|
|
341
|
+
min: minHeight,
|
|
342
|
+
measure: () => view.current?.dom.getBoundingClientRect().height ?? minHeight,
|
|
343
|
+
});
|
|
344
|
+
const dragged = sized.height;
|
|
289
345
|
const timer = React.useRef(undefined);
|
|
290
346
|
const listeners = React.useRef({ onChange, onCheck });
|
|
291
347
|
listeners.current = { onChange, onCheck };
|
|
@@ -294,7 +350,7 @@ function CodeEditor({ value, defaultValue = '', language = 'javascript', height,
|
|
|
294
350
|
const field = React.useMemo(() => verdict(allowText), [allowText]);
|
|
295
351
|
const judged = React.useRef({ field });
|
|
296
352
|
judged.current.field = field;
|
|
297
|
-
const grammar = () => (isJson ? [(0,
|
|
353
|
+
const grammar = () => (isJson ? [JSON_GRAMMAR, field, FOLDING, (0, language_1.foldGutter)()] : []);
|
|
298
354
|
const gutter = () => (lineNumbers ? [(0, view_1.lineNumbers)(), (0, view_1.highlightActiveLineGutter)()] : []);
|
|
299
355
|
const wrap = () => (wordWrap === 'off' ? [] : [view_1.EditorView.lineWrapping, hang]);
|
|
300
356
|
const edit = () => [state_1.EditorState.readOnly.of(readOnly), view_1.EditorView.editable.of(!readOnly)];
|
|
@@ -344,10 +400,11 @@ function CodeEditor({ value, defaultValue = '', language = 'javascript', height,
|
|
|
344
400
|
(0, language_1.indentOnInput)(),
|
|
345
401
|
(0, language_1.bracketMatching)(),
|
|
346
402
|
(0, view_1.highlightActiveLine)(),
|
|
347
|
-
(0, language_1.syntaxHighlighting)(
|
|
403
|
+
(0, language_1.syntaxHighlighting)(SYNTAX),
|
|
348
404
|
view_1.keymap.of([
|
|
349
405
|
{ key: 'Shift-Alt-f', run: format },
|
|
350
406
|
commands_1.indentWithTab,
|
|
407
|
+
...language_1.foldKeymap,
|
|
351
408
|
...commands_1.defaultKeymap,
|
|
352
409
|
...commands_1.historyKeymap,
|
|
353
410
|
{
|
|
@@ -420,57 +477,6 @@ function CodeEditor({ value, defaultValue = '', language = 'javascript', height,
|
|
|
420
477
|
dom.style.maxHeight = fixed ? '' : `${maxHeight}px`;
|
|
421
478
|
view.current?.requestMeasure();
|
|
422
479
|
}, [dragged, height, minHeight, maxHeight, fontSize]);
|
|
423
|
-
React.useEffect(() => {
|
|
424
|
-
const el = grip.current;
|
|
425
|
-
const handle = knob.current;
|
|
426
|
-
if (!el || !handle || !resizable)
|
|
427
|
-
return;
|
|
428
|
-
let pointer = -1;
|
|
429
|
-
let startY = 0;
|
|
430
|
-
let startH = 0;
|
|
431
|
-
const current = () => view.current?.dom.getBoundingClientRect().height ?? minHeight;
|
|
432
|
-
const down = (e) => {
|
|
433
|
-
if (e.button !== 0)
|
|
434
|
-
return;
|
|
435
|
-
e.preventDefault();
|
|
436
|
-
pointer = e.pointerId;
|
|
437
|
-
startY = e.clientY;
|
|
438
|
-
startH = current();
|
|
439
|
-
el.setPointerCapture?.(pointer);
|
|
440
|
-
};
|
|
441
|
-
const move = (e) => {
|
|
442
|
-
if (e.pointerId !== pointer)
|
|
443
|
-
return;
|
|
444
|
-
setDragged(Math.max(minHeight, Math.round(startH + e.clientY - startY)));
|
|
445
|
-
};
|
|
446
|
-
const up = (e) => {
|
|
447
|
-
if (e.pointerId !== pointer)
|
|
448
|
-
return;
|
|
449
|
-
el.releasePointerCapture?.(pointer);
|
|
450
|
-
pointer = -1;
|
|
451
|
-
};
|
|
452
|
-
const key = (e) => {
|
|
453
|
-
if (e.key !== 'ArrowUp' && e.key !== 'ArrowDown')
|
|
454
|
-
return;
|
|
455
|
-
e.preventDefault();
|
|
456
|
-
setDragged(Math.max(minHeight, Math.round(current() + (e.key === 'ArrowDown' ? STEP : -STEP))));
|
|
457
|
-
};
|
|
458
|
-
const reset = () => setDragged(null);
|
|
459
|
-
el.addEventListener('pointerdown', down);
|
|
460
|
-
el.addEventListener('pointermove', move);
|
|
461
|
-
el.addEventListener('pointerup', up);
|
|
462
|
-
el.addEventListener('pointercancel', up);
|
|
463
|
-
el.addEventListener('dblclick', reset);
|
|
464
|
-
handle.addEventListener('keydown', key);
|
|
465
|
-
return () => {
|
|
466
|
-
el.removeEventListener('pointerdown', down);
|
|
467
|
-
el.removeEventListener('pointermove', move);
|
|
468
|
-
el.removeEventListener('pointerup', up);
|
|
469
|
-
el.removeEventListener('pointercancel', up);
|
|
470
|
-
el.removeEventListener('dblclick', reset);
|
|
471
|
-
handle.removeEventListener('keydown', key);
|
|
472
|
-
};
|
|
473
|
-
}, [resizable, minHeight]);
|
|
474
480
|
const copy = React.useCallback(async () => {
|
|
475
481
|
try {
|
|
476
482
|
await navigator.clipboard.writeText(text);
|
|
@@ -489,19 +495,9 @@ function CodeEditor({ value, defaultValue = '', language = 'javascript', height,
|
|
|
489
495
|
const formattable = showFormatButton ?? (isJson && !readOnly);
|
|
490
496
|
const toolbar = showLanguageSelector || showCopyButton || formattable;
|
|
491
497
|
const footer = resizable || (isJson && check !== null);
|
|
492
|
-
return ((0, jsx_runtime_1.jsxs)(gui_1.YStack, { ...(0, slot_1.slot)('code-editor'), borderWidth: 1, borderColor: "$borderColor", rounded: "$3", overflow: "hidden", bg: "$background", focusWithinStyle: { borderColor: '$outlineColor' }, ...props, children: [toolbar && ((0, jsx_runtime_1.jsxs)(gui_1.XStack, { ...(0, slot_1.slot)('code-editor-toolbar'), items: "center", justify: "space-between", flexWrap: "wrap", gap: "$2", borderBottomWidth: 1, borderColor: "$borderColor", bg: "$panel", px: "$2", py: "$1", children: [showLanguageSelector ? ((0, jsx_runtime_1.jsxs)(select_1.Select, { value: selected, onValueChange: setSelected, children: [(0, jsx_runtime_1.jsx)(select_1.SelectTrigger, { ...(0, slot_1.slot)('code-editor-language-trigger'), width: "auto", minHeight: 32, borderWidth: 0, bg: "transparent", gap: "$1.5", px: "$2", children: (0, jsx_runtime_1.jsx)(gui_1.SizableText, { size: "$1", fontFamily: "$mono", children: LABEL[selected] ?? selected }) }), (0, jsx_runtime_1.jsx)(select_1.SelectContent, { ...(0, slot_1.slot)('code-editor-language-content'), children: availableLanguages.map((lang) => ((0, jsx_runtime_1.jsx)(select_1.SelectItem, { value: lang, children: LABEL[lang] ?? lang }, lang))) })] })) : ((0, jsx_runtime_1.jsx)(gui_1.SizableText, { ...(0, slot_1.slot)('code-editor-language'), size: "$1", fontFamily: "$mono", color: "$soft", px: "$2", children: LABEL[selected] ?? selected })), (0, jsx_runtime_1.jsxs)(gui_1.XStack, { items: "center", justify: "flex-end", flexWrap: "wrap", gap: "$1", shrink: 1, minW: 0, children: [formattable && ((0, jsx_runtime_1.jsxs)(button_1.Button, { ...(0, slot_1.slot)('code-editor-format-button'), variant: "ghost", size: "sm", disabled: check?.kind !== 'json', onClick: format, children: [(0, jsx_runtime_1.jsx)(lucide_icons_2_1.AlignLeft, { size: 14 }), "Format"] })), showCopyButton && ((0, jsx_runtime_1.jsxs)(button_1.Button, { ...(0, slot_1.slot)('code-editor-copy-button'), variant: "ghost", size: "sm", disabled: !text, onClick: copy, children: [copied ? (0, jsx_runtime_1.jsx)(lucide_icons_2_1.Check, { size: 14 }) : (0, jsx_runtime_1.jsx)(lucide_icons_2_1.Copy, { size: 14 }), copied ? 'Copied!' : 'Copy'] }))] })] })), (0, jsx_runtime_1.jsx)(gui_1.YStack, { ref: host, ...(0, slot_1.slot)('code-editor-body'), ...(fills && { flex: 1, minH: 0 }) }), footer && ((0, jsx_runtime_1.jsxs)(gui_1.XStack, { ref: grip, ...(0, slot_1.slot)('code-editor-footer'), cursor: resizable ? 'row-resize' : undefined, items: "center", justify: "space-between", gap: "$2", minH: 28, px: "$3", py: "$1", borderTopWidth: 1, borderColor: "$borderColor", bg: "$panel", select: "none",
|
|
498
|
+
return ((0, jsx_runtime_1.jsxs)(gui_1.YStack, { ...(0, slot_1.slot)('code-editor'), borderWidth: 1, borderColor: "$borderColor", rounded: "$3", overflow: "hidden", bg: "$background", focusWithinStyle: { borderColor: '$outlineColor' }, ...props, children: [toolbar && ((0, jsx_runtime_1.jsxs)(gui_1.XStack, { ...(0, slot_1.slot)('code-editor-toolbar'), items: "center", justify: "space-between", flexWrap: "wrap", gap: "$2", borderBottomWidth: 1, borderColor: "$borderColor", bg: "$panel", px: "$2", py: "$1", children: [showLanguageSelector ? ((0, jsx_runtime_1.jsxs)(select_1.Select, { value: selected, onValueChange: setSelected, children: [(0, jsx_runtime_1.jsx)(select_1.SelectTrigger, { ...(0, slot_1.slot)('code-editor-language-trigger'), width: "auto", minHeight: 32, borderWidth: 0, bg: "transparent", gap: "$1.5", px: "$2", children: (0, jsx_runtime_1.jsx)(gui_1.SizableText, { size: "$1", fontFamily: "$mono", children: LABEL[selected] ?? selected }) }), (0, jsx_runtime_1.jsx)(select_1.SelectContent, { ...(0, slot_1.slot)('code-editor-language-content'), children: availableLanguages.map((lang) => ((0, jsx_runtime_1.jsx)(select_1.SelectItem, { value: lang, children: LABEL[lang] ?? lang }, lang))) })] })) : ((0, jsx_runtime_1.jsx)(gui_1.SizableText, { ...(0, slot_1.slot)('code-editor-language'), size: "$1", fontFamily: "$mono", color: "$soft", px: "$2", children: LABEL[selected] ?? selected })), (0, jsx_runtime_1.jsxs)(gui_1.XStack, { items: "center", justify: "flex-end", flexWrap: "wrap", gap: "$1", shrink: 1, minW: 0, children: [formattable && ((0, jsx_runtime_1.jsxs)(button_1.Button, { ...(0, slot_1.slot)('code-editor-format-button'), variant: "ghost", size: "sm", disabled: check?.kind !== 'json', onClick: format, children: [(0, jsx_runtime_1.jsx)(lucide_icons_2_1.AlignLeft, { size: 14 }), "Format"] })), showCopyButton && ((0, jsx_runtime_1.jsxs)(button_1.Button, { ...(0, slot_1.slot)('code-editor-copy-button'), variant: "ghost", size: "sm", disabled: !text, onClick: copy, children: [copied ? (0, jsx_runtime_1.jsx)(lucide_icons_2_1.Check, { size: 14 }) : (0, jsx_runtime_1.jsx)(lucide_icons_2_1.Copy, { size: 14 }), copied ? 'Copied!' : 'Copy'] }))] })] })), (0, jsx_runtime_1.jsx)(gui_1.YStack, { ref: host, ...(0, slot_1.slot)('code-editor-body'), ...(fills && { flex: 1, minH: 0 }) }), footer && ((0, jsx_runtime_1.jsxs)(gui_1.XStack, { ref: sized.grip, ...(0, slot_1.slot)('code-editor-footer'), cursor: resizable ? 'row-resize' : undefined, items: "center", justify: "space-between", gap: "$2", minH: 28, px: "$3", py: "$1", borderTopWidth: 1, borderColor: "$borderColor", bg: "$panel", select: "none",
|
|
493
499
|
// A touch drag would otherwise scroll the page and cancel the pointer.
|
|
494
|
-
style: resizable ? { touchAction: 'none' } : undefined, children: [(0, jsx_runtime_1.jsx)(Status, { check: isJson ? check : null }), resizable && ((0, jsx_runtime_1.jsx)(
|
|
495
|
-
role: 'separator',
|
|
496
|
-
'aria-orientation': 'horizontal',
|
|
497
|
-
'aria-label': `Resize ${label}`,
|
|
498
|
-
'aria-valuemin': minHeight,
|
|
499
|
-
// A drag has no ceiling; this stands in for "as tall as you like".
|
|
500
|
-
'aria-valuemax': RESIZE_CEILING,
|
|
501
|
-
'aria-valuenow': dragged ?? minHeight,
|
|
502
|
-
'aria-valuetext': dragged === null ? 'Fits its text' : `${dragged} pixels`,
|
|
503
|
-
tabIndex: 0,
|
|
504
|
-
}, rounded: "$2", p: "$1", focusVisibleStyle: { bg: '$hover' }, children: (0, jsx_runtime_1.jsx)(lucide_icons_2_1.GripHorizontal, { size: 14, color: "$soft" }) }))] }))] }));
|
|
500
|
+
style: resizable ? { touchAction: 'none' } : undefined, children: [(0, jsx_runtime_1.jsx)(Status, { check: isJson ? check : null }), resizable && ((0, jsx_runtime_1.jsx)(grip_1.Grip, { knob: sized.knob, name: "code-editor-resize", label: label, min: minHeight, height: dragged }))] }))] }));
|
|
505
501
|
}
|
|
506
502
|
/** The footer's JSON verdict, announced politely when it changes. */
|
|
507
503
|
function Status({ check }) {
|
|
@@ -12,16 +12,18 @@
|
|
|
12
12
|
*
|
|
13
13
|
* JSON. `language="json"` colours the syntax and checks the text on every
|
|
14
14
|
* change with `checkJson`: a broken document tints the failing line, marks the
|
|
15
|
-
* failing character and names line, column and reason in the footer.
|
|
15
|
+
* failing character and names line, column and reason in the footer. Every
|
|
16
|
+
* object and array that spans lines folds from a marker in the gutter, and a
|
|
17
|
+
* folded one reads `{…}` or `[…]` with how many keys or items it hides. With
|
|
16
18
|
* `allowText`, text that does not open an object or an array is plain text and
|
|
17
19
|
* passes. Format (or Shift-Alt-F) re-indents valid JSON by two spaces. Other
|
|
18
20
|
* languages are edited as plain text: the package carries one grammar.
|
|
19
21
|
*
|
|
20
|
-
* Colour.
|
|
21
|
-
* hairline, the selection and the error state —
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
22
|
+
* Colour. The frame's colours are theme rungs over design tokens — the ink, the
|
|
23
|
+
* hairline, the selection and the error state — and the syntax is the code
|
|
24
|
+
* theme (`code-theme.ts`: Dracula on dark, GitHub Light on light), each token a
|
|
25
|
+
* theme key of its own. Both follow the page into light or dark with nothing to
|
|
26
|
+
* configure.
|
|
25
27
|
*/
|
|
26
28
|
import { type YStackProps } from '@hanzo/gui';
|
|
27
29
|
import { EditorView } from '@codemirror/view';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"code-editor.d.ts","sourceRoot":"","sources":["../../../src/backends/gui/code-editor.tsx"],"names":[],"mappings":"AAEA
|
|
1
|
+
{"version":3,"file":"code-editor.d.ts","sourceRoot":"","sources":["../../../src/backends/gui/code-editor.tsx"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,OAAO,EAA+B,KAAK,WAAW,EAAE,MAAM,YAAY,CAAA;AAiB1E,OAAO,EAEL,UAAU,EAYX,MAAM,kBAAkB,CAAA;AAEzB,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAI9B,OAAO,EAAyB,KAAK,SAAS,EAAE,MAAM,QAAQ,CAAA;AAK9D,MAAM,MAAM,kBAAkB,GAAG,IAAI,GAAG,KAAK,GAAG,gBAAgB,GAAG,SAAS,CAAA;AA4Q5E,MAAM,WAAW,eACf,SAAQ,IAAI,CAAC,WAAW,EAAE,UAAU,GAAG,QAAQ,GAAG,WAAW,GAAG,WAAW,GAAG,UAAU,CAAC;IACzF,yFAAyF;IACzF,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,mGAAmG;IACnG,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,CAAA;IACxB,oEAAoE;IACpE,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,0EAA0E;IAC1E,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,sFAAsF;IACtF,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB,4FAA4F;IAC5F,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAA;IAClC,4EAA4E;IAC5E,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,SAAS,KAAK,IAAI,CAAA;IACpC,0DAA0D;IAC1D,OAAO,CAAC,EAAE,CAAC,IAAI,EAAE,UAAU,KAAK,IAAI,CAAA;IACpC,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,WAAW,CAAC,EAAE,OAAO,CAAA;IACrB,QAAQ,CAAC,EAAE,kBAAkB,CAAA;IAC7B,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,uCAAuC;IACvC,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,cAAc,CAAC,EAAE,OAAO,CAAA;IACxB,0CAA0C;IAC1C,gBAAgB,CAAC,EAAE,OAAO,CAAA;IAC1B,oBAAoB,CAAC,EAAE,OAAO,CAAA;IAC9B,kBAAkB,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;IACtC;;;;OAIG;IACH,KAAK,CAAC,EAAE,MAAM,CAAA;CACf;AAED,wBAAgB,UAAU,CAAC,EACzB,KAAK,EACL,YAAiB,EACjB,QAAuB,EACvB,MAAM,EACN,SAAc,EACd,SAAe,EACf,SAAgC,EAChC,SAAiB,EACjB,QAAQ,EACR,OAAO,EACP,OAAO,EACP,QAAgB,EAChB,WAAkB,EAClB,QAAe,EACf,QAAa,EACb,WAAW,EACX,cAAqB,EACrB,gBAAgB,EAChB,oBAA2B,EAC3B,kBAA8B,EAC9B,KAAc,EACd,GAAG,KAAK,EACT,EAAE,eAAe,qBA2RjB"}
|
|
@@ -14,27 +14,30 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
14
14
|
*
|
|
15
15
|
* JSON. `language="json"` colours the syntax and checks the text on every
|
|
16
16
|
* change with `checkJson`: a broken document tints the failing line, marks the
|
|
17
|
-
* failing character and names line, column and reason in the footer.
|
|
17
|
+
* failing character and names line, column and reason in the footer. Every
|
|
18
|
+
* object and array that spans lines folds from a marker in the gutter, and a
|
|
19
|
+
* folded one reads `{…}` or `[…]` with how many keys or items it hides. With
|
|
18
20
|
* `allowText`, text that does not open an object or an array is plain text and
|
|
19
21
|
* passes. Format (or Shift-Alt-F) re-indents valid JSON by two spaces. Other
|
|
20
22
|
* languages are edited as plain text: the package carries one grammar.
|
|
21
23
|
*
|
|
22
|
-
* Colour.
|
|
23
|
-
* hairline, the selection and the error state —
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
24
|
+
* Colour. The frame's colours are theme rungs over design tokens — the ink, the
|
|
25
|
+
* hairline, the selection and the error state — and the syntax is the code
|
|
26
|
+
* theme (`code-theme.ts`: Dracula on dark, GitHub Light on light), each token a
|
|
27
|
+
* theme key of its own. Both follow the page into light or dark with nothing to
|
|
28
|
+
* configure.
|
|
27
29
|
*/
|
|
28
30
|
import { SizableText, XStack, YStack } from '@hanzo/gui';
|
|
29
|
-
import { AlignLeft, Check, CircleAlert, CircleCheck, Copy,
|
|
31
|
+
import { AlignLeft, Check, CircleAlert, CircleCheck, Copy, Type } from '@hanzogui/lucide-icons-2';
|
|
30
32
|
import { defaultKeymap, history, historyKeymap, indentWithTab } from '@codemirror/commands';
|
|
31
|
-
import {
|
|
32
|
-
import { HighlightStyle, bracketMatching, indentOnInput, syntaxHighlighting } from '@codemirror/language';
|
|
33
|
+
import { jsonLanguage } from '@codemirror/lang-json';
|
|
34
|
+
import { HighlightStyle, LanguageSupport, bracketMatching, codeFolding, foldGutter, foldKeymap, foldNodeProp, indentOnInput, syntaxHighlighting, syntaxTree, } from '@codemirror/language';
|
|
33
35
|
import { Annotation, Compartment, EditorState, RangeSet, RangeSetBuilder, StateField } from '@codemirror/state';
|
|
34
36
|
import { Decoration, EditorView, GutterMarker, drawSelection, gutterLineClass, highlightActiveLine, highlightActiveLineGutter, keymap, lineNumbers as numbering, placeholder as hint, ViewPlugin, } from '@codemirror/view';
|
|
35
37
|
import { tags } from '@lezer/highlight';
|
|
36
38
|
import * as React from 'react';
|
|
37
39
|
import { Button } from './button.js';
|
|
40
|
+
import { Grip, useGrip } from './grip.js';
|
|
38
41
|
import { checkJson, formatJson } from './json.js';
|
|
39
42
|
import { Select, SelectContent, SelectItem, SelectTrigger } from './select.js';
|
|
40
43
|
import { slot } from './slot.js';
|
|
@@ -94,10 +97,6 @@ const LABEL = {
|
|
|
94
97
|
};
|
|
95
98
|
/** Line height as a multiple of the font size. */
|
|
96
99
|
const LEADING = 1.6;
|
|
97
|
-
/** How far one ArrowUp / ArrowDown on the footer moves the height. */
|
|
98
|
-
const STEP = 24;
|
|
99
|
-
/** The separator's stated maximum, in px: the drag itself has none. */
|
|
100
|
-
const RESIZE_CEILING = 4096;
|
|
101
100
|
const MONO = "var(--font-mono, 'Zen Mono', ui-monospace, SFMono-Regular, Menlo, Consolas, monospace)";
|
|
102
101
|
/**
|
|
103
102
|
* The editor's surface. Every colour is one of the theme's named rungs — gui emits
|
|
@@ -144,6 +143,18 @@ const FRAME = /* @__PURE__ */ EditorView.theme({
|
|
|
144
143
|
},
|
|
145
144
|
'.cm-nonmatchingBracket, &.cm-focused .cm-nonmatchingBracket': { backgroundColor: 'transparent' },
|
|
146
145
|
'.cm-placeholder': { color: 'var(--dim)' },
|
|
146
|
+
'.cm-foldGutter .cm-gutterElement': { padding: '0 4px', color: 'var(--dim)', cursor: 'pointer' },
|
|
147
|
+
'.cm-foldGutter .cm-gutterElement:hover': { color: 'var(--ink)' },
|
|
148
|
+
'.cm-foldPlaceholder': {
|
|
149
|
+
backgroundColor: 'var(--raised)',
|
|
150
|
+
border: '1px solid var(--edge)',
|
|
151
|
+
borderRadius: '4px',
|
|
152
|
+
color: 'var(--codePunctuation)',
|
|
153
|
+
padding: '0 4px',
|
|
154
|
+
margin: '0 2px',
|
|
155
|
+
cursor: 'pointer',
|
|
156
|
+
},
|
|
157
|
+
'.cm-foldCount': { color: 'var(--codeComment)' },
|
|
147
158
|
'.cm-fault': { backgroundColor: 'var(--state-error-bg, rgb(239 68 68 / .1))' },
|
|
148
159
|
'.cm-gutterElement.cm-fault': { color: 'var(--bad)' },
|
|
149
160
|
'.cm-fault-at': {
|
|
@@ -152,13 +163,55 @@ const FRAME = /* @__PURE__ */ EditorView.theme({
|
|
|
152
163
|
textUnderlineOffset: '3px',
|
|
153
164
|
},
|
|
154
165
|
});
|
|
155
|
-
/** The syntax
|
|
156
|
-
const
|
|
157
|
-
{ tag: tags.propertyName, color: 'var(--
|
|
158
|
-
{ tag: tags.string, color: 'var(--
|
|
159
|
-
{ tag:
|
|
160
|
-
{ tag:
|
|
166
|
+
/** The syntax in the code theme's colours: one theme key per kind of token, from `code-theme.ts`. */
|
|
167
|
+
const SYNTAX = /* @__PURE__ */ HighlightStyle.define([
|
|
168
|
+
{ tag: tags.propertyName, color: 'var(--codeKey)' },
|
|
169
|
+
{ tag: tags.string, color: 'var(--codeString)' },
|
|
170
|
+
{ tag: tags.number, color: 'var(--codeNumber)' },
|
|
171
|
+
{ tag: tags.bool, color: 'var(--codeBoolean)' },
|
|
172
|
+
{ tag: tags.null, color: 'var(--codeNull)' },
|
|
173
|
+
{ tag: [tags.punctuation, tags.separator, tags.brace, tags.squareBracket], color: 'var(--codePunctuation)' },
|
|
174
|
+
{ tag: tags.comment, color: 'var(--codeComment)' },
|
|
175
|
+
{ tag: tags.keyword, color: 'var(--codeKeyword)' },
|
|
161
176
|
]);
|
|
177
|
+
/**
|
|
178
|
+
* JSON folds from its opening bracket through its closing one, so a folded block
|
|
179
|
+
* is drawn whole as `{…}` or `[…]` and has room to say what it holds. lang-json
|
|
180
|
+
* folds inside the brackets, which leaves them standing around an ellipsis.
|
|
181
|
+
*/
|
|
182
|
+
const JSON_GRAMMAR = /* @__PURE__ */ new LanguageSupport(jsonLanguage.configure({
|
|
183
|
+
props: [foldNodeProp.add({ 'Object Array': (node) => ({ from: node.from, to: node.to }) })],
|
|
184
|
+
}));
|
|
185
|
+
const words = (n, one) => `${n} ${one}${n === 1 ? '' : 's'}`;
|
|
186
|
+
/** What a folded range holds: its brackets and how many keys or items sit inside. */
|
|
187
|
+
function measureFold(state, range) {
|
|
188
|
+
let node = syntaxTree(state).resolveInner(range.from, 1);
|
|
189
|
+
while (node.parent && node.name !== 'Object' && node.name !== 'Array')
|
|
190
|
+
node = node.parent;
|
|
191
|
+
if (node.name === 'Object')
|
|
192
|
+
return { open: '{', close: '}', count: words(node.getChildren('Property').length, 'key') };
|
|
193
|
+
let n = 0;
|
|
194
|
+
for (let c = node.firstChild; c; c = c.nextSibling)
|
|
195
|
+
if (!/^[[\],]$/.test(c.name) && !c.type.isError)
|
|
196
|
+
n += 1;
|
|
197
|
+
return { open: '[', close: ']', count: words(n, 'item') };
|
|
198
|
+
}
|
|
199
|
+
const FOLDING = /* @__PURE__ */ codeFolding({
|
|
200
|
+
preparePlaceholder: measureFold,
|
|
201
|
+
placeholderDOM: (_view, onclick, fold) => {
|
|
202
|
+
const el = document.createElement('span');
|
|
203
|
+
el.className = 'cm-foldPlaceholder';
|
|
204
|
+
el.title = 'Unfold';
|
|
205
|
+
el.setAttribute('aria-label', `Folded, ${fold.count}`);
|
|
206
|
+
el.textContent = `${fold.open}…${fold.close}`;
|
|
207
|
+
const count = document.createElement('span');
|
|
208
|
+
count.className = 'cm-foldCount';
|
|
209
|
+
count.textContent = ` ${fold.count}`;
|
|
210
|
+
el.append(count);
|
|
211
|
+
el.onclick = onclick;
|
|
212
|
+
return el;
|
|
213
|
+
},
|
|
214
|
+
});
|
|
162
215
|
/** How much deeper than its line a wrapped continuation starts, in characters. */
|
|
163
216
|
const HANG = 2;
|
|
164
217
|
/** A line's own indentation in characters, a tab counted at the editor's tab width. */
|
|
@@ -243,13 +296,16 @@ export function CodeEditor({ value, defaultValue = '', language = 'javascript',
|
|
|
243
296
|
const [text, setText] = React.useState(value ?? defaultValue);
|
|
244
297
|
const [check, setCheck] = React.useState(null);
|
|
245
298
|
const [copied, setCopied] = React.useState(false);
|
|
246
|
-
const [dragged, setDragged] = React.useState(null);
|
|
247
299
|
const host = React.useRef(null);
|
|
248
|
-
const grip = React.useRef(null);
|
|
249
|
-
const knob = React.useRef(null);
|
|
250
300
|
/** Every text reported to `onChange` and not yet seen back as `value`. */
|
|
251
301
|
const sent = React.useRef([]);
|
|
252
302
|
const view = React.useRef(null);
|
|
303
|
+
const sized = useGrip({
|
|
304
|
+
enabled: resizable,
|
|
305
|
+
min: minHeight,
|
|
306
|
+
measure: () => view.current?.dom.getBoundingClientRect().height ?? minHeight,
|
|
307
|
+
});
|
|
308
|
+
const dragged = sized.height;
|
|
253
309
|
const timer = React.useRef(undefined);
|
|
254
310
|
const listeners = React.useRef({ onChange, onCheck });
|
|
255
311
|
listeners.current = { onChange, onCheck };
|
|
@@ -258,7 +314,7 @@ export function CodeEditor({ value, defaultValue = '', language = 'javascript',
|
|
|
258
314
|
const field = React.useMemo(() => verdict(allowText), [allowText]);
|
|
259
315
|
const judged = React.useRef({ field });
|
|
260
316
|
judged.current.field = field;
|
|
261
|
-
const grammar = () => (isJson ? [
|
|
317
|
+
const grammar = () => (isJson ? [JSON_GRAMMAR, field, FOLDING, foldGutter()] : []);
|
|
262
318
|
const gutter = () => (lineNumbers ? [numbering(), highlightActiveLineGutter()] : []);
|
|
263
319
|
const wrap = () => (wordWrap === 'off' ? [] : [EditorView.lineWrapping, hang]);
|
|
264
320
|
const edit = () => [EditorState.readOnly.of(readOnly), EditorView.editable.of(!readOnly)];
|
|
@@ -308,10 +364,11 @@ export function CodeEditor({ value, defaultValue = '', language = 'javascript',
|
|
|
308
364
|
indentOnInput(),
|
|
309
365
|
bracketMatching(),
|
|
310
366
|
highlightActiveLine(),
|
|
311
|
-
syntaxHighlighting(
|
|
367
|
+
syntaxHighlighting(SYNTAX),
|
|
312
368
|
keymap.of([
|
|
313
369
|
{ key: 'Shift-Alt-f', run: format },
|
|
314
370
|
indentWithTab,
|
|
371
|
+
...foldKeymap,
|
|
315
372
|
...defaultKeymap,
|
|
316
373
|
...historyKeymap,
|
|
317
374
|
{
|
|
@@ -384,57 +441,6 @@ export function CodeEditor({ value, defaultValue = '', language = 'javascript',
|
|
|
384
441
|
dom.style.maxHeight = fixed ? '' : `${maxHeight}px`;
|
|
385
442
|
view.current?.requestMeasure();
|
|
386
443
|
}, [dragged, height, minHeight, maxHeight, fontSize]);
|
|
387
|
-
React.useEffect(() => {
|
|
388
|
-
const el = grip.current;
|
|
389
|
-
const handle = knob.current;
|
|
390
|
-
if (!el || !handle || !resizable)
|
|
391
|
-
return;
|
|
392
|
-
let pointer = -1;
|
|
393
|
-
let startY = 0;
|
|
394
|
-
let startH = 0;
|
|
395
|
-
const current = () => view.current?.dom.getBoundingClientRect().height ?? minHeight;
|
|
396
|
-
const down = (e) => {
|
|
397
|
-
if (e.button !== 0)
|
|
398
|
-
return;
|
|
399
|
-
e.preventDefault();
|
|
400
|
-
pointer = e.pointerId;
|
|
401
|
-
startY = e.clientY;
|
|
402
|
-
startH = current();
|
|
403
|
-
el.setPointerCapture?.(pointer);
|
|
404
|
-
};
|
|
405
|
-
const move = (e) => {
|
|
406
|
-
if (e.pointerId !== pointer)
|
|
407
|
-
return;
|
|
408
|
-
setDragged(Math.max(minHeight, Math.round(startH + e.clientY - startY)));
|
|
409
|
-
};
|
|
410
|
-
const up = (e) => {
|
|
411
|
-
if (e.pointerId !== pointer)
|
|
412
|
-
return;
|
|
413
|
-
el.releasePointerCapture?.(pointer);
|
|
414
|
-
pointer = -1;
|
|
415
|
-
};
|
|
416
|
-
const key = (e) => {
|
|
417
|
-
if (e.key !== 'ArrowUp' && e.key !== 'ArrowDown')
|
|
418
|
-
return;
|
|
419
|
-
e.preventDefault();
|
|
420
|
-
setDragged(Math.max(minHeight, Math.round(current() + (e.key === 'ArrowDown' ? STEP : -STEP))));
|
|
421
|
-
};
|
|
422
|
-
const reset = () => setDragged(null);
|
|
423
|
-
el.addEventListener('pointerdown', down);
|
|
424
|
-
el.addEventListener('pointermove', move);
|
|
425
|
-
el.addEventListener('pointerup', up);
|
|
426
|
-
el.addEventListener('pointercancel', up);
|
|
427
|
-
el.addEventListener('dblclick', reset);
|
|
428
|
-
handle.addEventListener('keydown', key);
|
|
429
|
-
return () => {
|
|
430
|
-
el.removeEventListener('pointerdown', down);
|
|
431
|
-
el.removeEventListener('pointermove', move);
|
|
432
|
-
el.removeEventListener('pointerup', up);
|
|
433
|
-
el.removeEventListener('pointercancel', up);
|
|
434
|
-
el.removeEventListener('dblclick', reset);
|
|
435
|
-
handle.removeEventListener('keydown', key);
|
|
436
|
-
};
|
|
437
|
-
}, [resizable, minHeight]);
|
|
438
444
|
const copy = React.useCallback(async () => {
|
|
439
445
|
try {
|
|
440
446
|
await navigator.clipboard.writeText(text);
|
|
@@ -453,19 +459,9 @@ export function CodeEditor({ value, defaultValue = '', language = 'javascript',
|
|
|
453
459
|
const formattable = showFormatButton ?? (isJson && !readOnly);
|
|
454
460
|
const toolbar = showLanguageSelector || showCopyButton || formattable;
|
|
455
461
|
const footer = resizable || (isJson && check !== null);
|
|
456
|
-
return (_jsxs(YStack, { ...slot('code-editor'), borderWidth: 1, borderColor: "$borderColor", rounded: "$3", overflow: "hidden", bg: "$background", focusWithinStyle: { borderColor: '$outlineColor' }, ...props, children: [toolbar && (_jsxs(XStack, { ...slot('code-editor-toolbar'), items: "center", justify: "space-between", flexWrap: "wrap", gap: "$2", borderBottomWidth: 1, borderColor: "$borderColor", bg: "$panel", px: "$2", py: "$1", children: [showLanguageSelector ? (_jsxs(Select, { value: selected, onValueChange: setSelected, children: [_jsx(SelectTrigger, { ...slot('code-editor-language-trigger'), width: "auto", minHeight: 32, borderWidth: 0, bg: "transparent", gap: "$1.5", px: "$2", children: _jsx(SizableText, { size: "$1", fontFamily: "$mono", children: LABEL[selected] ?? selected }) }), _jsx(SelectContent, { ...slot('code-editor-language-content'), children: availableLanguages.map((lang) => (_jsx(SelectItem, { value: lang, children: LABEL[lang] ?? lang }, lang))) })] })) : (_jsx(SizableText, { ...slot('code-editor-language'), size: "$1", fontFamily: "$mono", color: "$soft", px: "$2", children: LABEL[selected] ?? selected })), _jsxs(XStack, { items: "center", justify: "flex-end", flexWrap: "wrap", gap: "$1", shrink: 1, minW: 0, children: [formattable && (_jsxs(Button, { ...slot('code-editor-format-button'), variant: "ghost", size: "sm", disabled: check?.kind !== 'json', onClick: format, children: [_jsx(AlignLeft, { size: 14 }), "Format"] })), showCopyButton && (_jsxs(Button, { ...slot('code-editor-copy-button'), variant: "ghost", size: "sm", disabled: !text, onClick: copy, children: [copied ? _jsx(Check, { size: 14 }) : _jsx(Copy, { size: 14 }), copied ? 'Copied!' : 'Copy'] }))] })] })), _jsx(YStack, { ref: host, ...slot('code-editor-body'), ...(fills && { flex: 1, minH: 0 }) }), footer && (_jsxs(XStack, { ref: grip, ...slot('code-editor-footer'), cursor: resizable ? 'row-resize' : undefined, items: "center", justify: "space-between", gap: "$2", minH: 28, px: "$3", py: "$1", borderTopWidth: 1, borderColor: "$borderColor", bg: "$panel", select: "none",
|
|
462
|
+
return (_jsxs(YStack, { ...slot('code-editor'), borderWidth: 1, borderColor: "$borderColor", rounded: "$3", overflow: "hidden", bg: "$background", focusWithinStyle: { borderColor: '$outlineColor' }, ...props, children: [toolbar && (_jsxs(XStack, { ...slot('code-editor-toolbar'), items: "center", justify: "space-between", flexWrap: "wrap", gap: "$2", borderBottomWidth: 1, borderColor: "$borderColor", bg: "$panel", px: "$2", py: "$1", children: [showLanguageSelector ? (_jsxs(Select, { value: selected, onValueChange: setSelected, children: [_jsx(SelectTrigger, { ...slot('code-editor-language-trigger'), width: "auto", minHeight: 32, borderWidth: 0, bg: "transparent", gap: "$1.5", px: "$2", children: _jsx(SizableText, { size: "$1", fontFamily: "$mono", children: LABEL[selected] ?? selected }) }), _jsx(SelectContent, { ...slot('code-editor-language-content'), children: availableLanguages.map((lang) => (_jsx(SelectItem, { value: lang, children: LABEL[lang] ?? lang }, lang))) })] })) : (_jsx(SizableText, { ...slot('code-editor-language'), size: "$1", fontFamily: "$mono", color: "$soft", px: "$2", children: LABEL[selected] ?? selected })), _jsxs(XStack, { items: "center", justify: "flex-end", flexWrap: "wrap", gap: "$1", shrink: 1, minW: 0, children: [formattable && (_jsxs(Button, { ...slot('code-editor-format-button'), variant: "ghost", size: "sm", disabled: check?.kind !== 'json', onClick: format, children: [_jsx(AlignLeft, { size: 14 }), "Format"] })), showCopyButton && (_jsxs(Button, { ...slot('code-editor-copy-button'), variant: "ghost", size: "sm", disabled: !text, onClick: copy, children: [copied ? _jsx(Check, { size: 14 }) : _jsx(Copy, { size: 14 }), copied ? 'Copied!' : 'Copy'] }))] })] })), _jsx(YStack, { ref: host, ...slot('code-editor-body'), ...(fills && { flex: 1, minH: 0 }) }), footer && (_jsxs(XStack, { ref: sized.grip, ...slot('code-editor-footer'), cursor: resizable ? 'row-resize' : undefined, items: "center", justify: "space-between", gap: "$2", minH: 28, px: "$3", py: "$1", borderTopWidth: 1, borderColor: "$borderColor", bg: "$panel", select: "none",
|
|
457
463
|
// A touch drag would otherwise scroll the page and cancel the pointer.
|
|
458
|
-
style: resizable ? { touchAction: 'none' } : undefined, children: [_jsx(Status, { check: isJson ? check : null }), resizable && (_jsx(
|
|
459
|
-
role: 'separator',
|
|
460
|
-
'aria-orientation': 'horizontal',
|
|
461
|
-
'aria-label': `Resize ${label}`,
|
|
462
|
-
'aria-valuemin': minHeight,
|
|
463
|
-
// A drag has no ceiling; this stands in for "as tall as you like".
|
|
464
|
-
'aria-valuemax': RESIZE_CEILING,
|
|
465
|
-
'aria-valuenow': dragged ?? minHeight,
|
|
466
|
-
'aria-valuetext': dragged === null ? 'Fits its text' : `${dragged} pixels`,
|
|
467
|
-
tabIndex: 0,
|
|
468
|
-
}, rounded: "$2", p: "$1", focusVisibleStyle: { bg: '$hover' }, children: _jsx(GripHorizontal, { size: 14, color: "$soft" }) }))] }))] }));
|
|
464
|
+
style: resizable ? { touchAction: 'none' } : undefined, children: [_jsx(Status, { check: isJson ? check : null }), resizable && (_jsx(Grip, { knob: sized.knob, name: "code-editor-resize", label: label, min: minHeight, height: dragged }))] }))] }));
|
|
469
465
|
}
|
|
470
466
|
/** The footer's JSON verdict, announced politely when it changes. */
|
|
471
467
|
function Status({ check }) {
|