@tslite/react 0.1.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/dist/full.cjs +1 -0
- package/dist/full.d.cts +74 -0
- package/dist/full.d.ts +74 -0
- package/dist/full.js +1 -0
- package/dist/index.cjs +1 -0
- package/dist/index.d.cts +95 -0
- package/dist/index.d.ts +95 -0
- package/dist/index.js +1 -0
- package/dist/useExpressionField-CA__y0bO.d.cts +159 -0
- package/dist/useExpressionField-CA__y0bO.d.ts +159 -0
- package/package.json +98 -0
package/dist/full.cjs
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
'use strict';var search=require('@tslite/codemirror/search'),format=require('@tslite/format'),editor=require('@tslite/editor');function y(t={}){let{dark:s=true,layout:e="inline",mode:o="expression",gutters:n=e==="block",search:i=e==="block",extensions:r=[]}=t;return {layout:e,mode:o,gutters:n,format:o==="program"?format.format:format.formatExpression,features:{chrome:{dark:s},theme:editor.tsliteTokenStyles},extensions:i?[search.searchEditing(),...r]:[...r]}}Object.defineProperty(exports,"searchEditing",{enumerable:true,get:function(){return search.searchEditing}});Object.defineProperty(exports,"format",{enumerable:true,get:function(){return format.format}});Object.defineProperty(exports,"formatExpression",{enumerable:true,get:function(){return format.formatExpression}});Object.defineProperty(exports,"tsliteTokenStyles",{enumerable:true,get:function(){return editor.tsliteTokenStyles}});exports.expressionFieldProps=y;
|
package/dist/full.d.cts
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { Extension } from '@codemirror/state';
|
|
2
|
+
import { U as UseExpressionFieldOptions } from './useExpressionField-CA__y0bO.cjs';
|
|
3
|
+
export { searchEditing } from '@tslite/codemirror/search';
|
|
4
|
+
export { format, formatExpression } from '@tslite/format';
|
|
5
|
+
export { tsliteTokenStyles } from '@tslite/editor';
|
|
6
|
+
import 'react';
|
|
7
|
+
import '@codemirror/view';
|
|
8
|
+
import '@tslite/codemirror';
|
|
9
|
+
import '@tslite/language-service';
|
|
10
|
+
|
|
11
|
+
/** O que o preset deixa você escolher. Tudo tem padrão. */
|
|
12
|
+
interface PresetOptions {
|
|
13
|
+
/**
|
|
14
|
+
* O editor está num tema ESCURO? Padrão: `true`.
|
|
15
|
+
*
|
|
16
|
+
* Não muda cor nossa — todas saem de CSS var e já acompanham o seu tema. O que
|
|
17
|
+
* ele muda é o que o **CodeMirror** faz sozinho: várias regras dele são
|
|
18
|
+
* declaradas em `&light`/`&dark`, e sem a marca ele aplica as claras. O
|
|
19
|
+
* sintoma é um editor escuro com a régua de busca e o painel de problemas com
|
|
20
|
+
* cara de tema claro.
|
|
21
|
+
*/
|
|
22
|
+
readonly dark?: boolean;
|
|
23
|
+
/**
|
|
24
|
+
* Como o campo se apresenta. Padrão: `"inline"`.
|
|
25
|
+
*
|
|
26
|
+
* `"block"` é o campo EXPANDIDO (tipicamente num modal): ganha o `Tab` que
|
|
27
|
+
* indenta, a quebra de linha e — por este preset — a régua de linhas.
|
|
28
|
+
*/
|
|
29
|
+
readonly layout?: "inline" | "block";
|
|
30
|
+
/**
|
|
31
|
+
* O que o código É. Padrão: `"expression"`.
|
|
32
|
+
*
|
|
33
|
+
* Decide também qual formatador entra: um programa é reimpresso com `;` e
|
|
34
|
+
* quebra de statement; uma expressão, não. Injetar o de expressão num programa
|
|
35
|
+
* faz o `parseExpression` lançar, e o serviço então declara que não sabe
|
|
36
|
+
* formatar — em silêncio. Foi um defeito real, e é por isso que o preset
|
|
37
|
+
* escolhe por você.
|
|
38
|
+
*/
|
|
39
|
+
readonly mode?: "expression" | "program";
|
|
40
|
+
/** Régua de linhas. Padrão: ligada no `"block"`, desligada no `"inline"`. */
|
|
41
|
+
readonly gutters?: boolean;
|
|
42
|
+
/** Busca e `Ctrl+D`. Padrão: ligadas no `"block"`, desligadas no `"inline"`. */
|
|
43
|
+
readonly search?: boolean;
|
|
44
|
+
/** Extensões suas, acrescentadas às do preset. */
|
|
45
|
+
readonly extensions?: readonly Extension[];
|
|
46
|
+
}
|
|
47
|
+
/** As props que o preset resolve — o resto (`value`, `onChange`, `env`) é seu. */
|
|
48
|
+
type PresetProps = Pick<UseExpressionFieldOptions, "layout" | "mode" | "gutters" | "format" | "features" | "extensions">;
|
|
49
|
+
/**
|
|
50
|
+
* As props de um campo que já nasce certo.
|
|
51
|
+
*
|
|
52
|
+
* ```tsx
|
|
53
|
+
* import { ExpressionInput } from "@tslite/react";
|
|
54
|
+
* import { expressionFieldProps } from "@tslite/react/full";
|
|
55
|
+
*
|
|
56
|
+
* <ExpressionInput
|
|
57
|
+
* {...expressionFieldProps()}
|
|
58
|
+
* value={v}
|
|
59
|
+
* onChange={setV}
|
|
60
|
+
* env={env}
|
|
61
|
+
* />
|
|
62
|
+
* ```
|
|
63
|
+
*
|
|
64
|
+
* O campo EXPANDIDO é o mesmo componente com outra roupa:
|
|
65
|
+
*
|
|
66
|
+
* ```tsx
|
|
67
|
+
* <ExpressionInput {...expressionFieldProps({ layout: "block" })} … />
|
|
68
|
+
* ```
|
|
69
|
+
*
|
|
70
|
+
* Qualquer prop depois do spread vence — é assim que se ajusta sem bifurcar.
|
|
71
|
+
*/
|
|
72
|
+
declare function expressionFieldProps(options?: PresetOptions): PresetProps;
|
|
73
|
+
|
|
74
|
+
export { type PresetOptions, type PresetProps, expressionFieldProps };
|
package/dist/full.d.ts
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { Extension } from '@codemirror/state';
|
|
2
|
+
import { U as UseExpressionFieldOptions } from './useExpressionField-CA__y0bO.js';
|
|
3
|
+
export { searchEditing } from '@tslite/codemirror/search';
|
|
4
|
+
export { format, formatExpression } from '@tslite/format';
|
|
5
|
+
export { tsliteTokenStyles } from '@tslite/editor';
|
|
6
|
+
import 'react';
|
|
7
|
+
import '@codemirror/view';
|
|
8
|
+
import '@tslite/codemirror';
|
|
9
|
+
import '@tslite/language-service';
|
|
10
|
+
|
|
11
|
+
/** O que o preset deixa você escolher. Tudo tem padrão. */
|
|
12
|
+
interface PresetOptions {
|
|
13
|
+
/**
|
|
14
|
+
* O editor está num tema ESCURO? Padrão: `true`.
|
|
15
|
+
*
|
|
16
|
+
* Não muda cor nossa — todas saem de CSS var e já acompanham o seu tema. O que
|
|
17
|
+
* ele muda é o que o **CodeMirror** faz sozinho: várias regras dele são
|
|
18
|
+
* declaradas em `&light`/`&dark`, e sem a marca ele aplica as claras. O
|
|
19
|
+
* sintoma é um editor escuro com a régua de busca e o painel de problemas com
|
|
20
|
+
* cara de tema claro.
|
|
21
|
+
*/
|
|
22
|
+
readonly dark?: boolean;
|
|
23
|
+
/**
|
|
24
|
+
* Como o campo se apresenta. Padrão: `"inline"`.
|
|
25
|
+
*
|
|
26
|
+
* `"block"` é o campo EXPANDIDO (tipicamente num modal): ganha o `Tab` que
|
|
27
|
+
* indenta, a quebra de linha e — por este preset — a régua de linhas.
|
|
28
|
+
*/
|
|
29
|
+
readonly layout?: "inline" | "block";
|
|
30
|
+
/**
|
|
31
|
+
* O que o código É. Padrão: `"expression"`.
|
|
32
|
+
*
|
|
33
|
+
* Decide também qual formatador entra: um programa é reimpresso com `;` e
|
|
34
|
+
* quebra de statement; uma expressão, não. Injetar o de expressão num programa
|
|
35
|
+
* faz o `parseExpression` lançar, e o serviço então declara que não sabe
|
|
36
|
+
* formatar — em silêncio. Foi um defeito real, e é por isso que o preset
|
|
37
|
+
* escolhe por você.
|
|
38
|
+
*/
|
|
39
|
+
readonly mode?: "expression" | "program";
|
|
40
|
+
/** Régua de linhas. Padrão: ligada no `"block"`, desligada no `"inline"`. */
|
|
41
|
+
readonly gutters?: boolean;
|
|
42
|
+
/** Busca e `Ctrl+D`. Padrão: ligadas no `"block"`, desligadas no `"inline"`. */
|
|
43
|
+
readonly search?: boolean;
|
|
44
|
+
/** Extensões suas, acrescentadas às do preset. */
|
|
45
|
+
readonly extensions?: readonly Extension[];
|
|
46
|
+
}
|
|
47
|
+
/** As props que o preset resolve — o resto (`value`, `onChange`, `env`) é seu. */
|
|
48
|
+
type PresetProps = Pick<UseExpressionFieldOptions, "layout" | "mode" | "gutters" | "format" | "features" | "extensions">;
|
|
49
|
+
/**
|
|
50
|
+
* As props de um campo que já nasce certo.
|
|
51
|
+
*
|
|
52
|
+
* ```tsx
|
|
53
|
+
* import { ExpressionInput } from "@tslite/react";
|
|
54
|
+
* import { expressionFieldProps } from "@tslite/react/full";
|
|
55
|
+
*
|
|
56
|
+
* <ExpressionInput
|
|
57
|
+
* {...expressionFieldProps()}
|
|
58
|
+
* value={v}
|
|
59
|
+
* onChange={setV}
|
|
60
|
+
* env={env}
|
|
61
|
+
* />
|
|
62
|
+
* ```
|
|
63
|
+
*
|
|
64
|
+
* O campo EXPANDIDO é o mesmo componente com outra roupa:
|
|
65
|
+
*
|
|
66
|
+
* ```tsx
|
|
67
|
+
* <ExpressionInput {...expressionFieldProps({ layout: "block" })} … />
|
|
68
|
+
* ```
|
|
69
|
+
*
|
|
70
|
+
* Qualquer prop depois do spread vence — é assim que se ajusta sem bifurcar.
|
|
71
|
+
*/
|
|
72
|
+
declare function expressionFieldProps(options?: PresetOptions): PresetProps;
|
|
73
|
+
|
|
74
|
+
export { type PresetOptions, type PresetProps, expressionFieldProps };
|
package/dist/full.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import {searchEditing}from'@tslite/codemirror/search';export{searchEditing}from'@tslite/codemirror/search';import {format,formatExpression}from'@tslite/format';export{format,formatExpression}from'@tslite/format';import {tsliteTokenStyles}from'@tslite/editor';export{tsliteTokenStyles}from'@tslite/editor';function y(t={}){let{dark:s=true,layout:e="inline",mode:o="expression",gutters:n=e==="block",search:i=e==="block",extensions:r=[]}=t;return {layout:e,mode:o,gutters:n,format:o==="program"?format:formatExpression,features:{chrome:{dark:s},theme:tsliteTokenStyles},extensions:i?[searchEditing(),...r]:[...r]}}export{y as expressionFieldProps};
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
'use strict';var o=require('react'),state=require('@codemirror/state'),view=require('@codemirror/view'),commands=require('@codemirror/commands'),codemirror=require('@tslite/codemirror'),editor=require('@tslite/editor'),languageService=require('@tslite/language-service'),jsxRuntime=require('react/jsx-runtime');function _interopNamespace(e){if(e&&e.__esModule)return e;var n=Object.create(null);if(e){Object.keys(e).forEach(function(k){if(k!=='default'){var d=Object.getOwnPropertyDescriptor(e,k);Object.defineProperty(n,k,d.get?d:{enumerable:true,get:function(){return e[k]}});}})}n.default=e;return Object.freeze(n)}var o__namespace=/*#__PURE__*/_interopNamespace(o);var te={mustache:languageService.mustache,whole:languageService.whole},oe=e=>typeof e=="function"?e:te[e];function P(e){let{env:n,service:i,segmenter:t="mustache",mode:u="expression",profile:c,limits:l,unused:d,format:s}=e;return o__namespace.useMemo(()=>i||(n?editor.createHostService({env:n,segmenter:oe(t),mode:u,...c?{profile:c}:{},...l?{limits:l}:{},...d!==void 0?{unused:d}:{},...s?{format:s}:{}}):null),[i,n,t,u,c,l,d,s])}var me=view.EditorView.theme({".cm-scroller":{overflowX:"auto",overflowY:"hidden"},".cm-content":{padding:"6px 8px"},".cm-line":{padding:"0"},"&.cm-focused":{outline:"none"}}),Ee=view.EditorView.theme({"&":{height:"100%"},".cm-scroller":{overflowY:"auto"},".cm-content":{padding:"8px"},"&.cm-focused":{outline:"none"}}),Y=e=>e.replace(/\r?\n/g," "),ye=(e,n)=>{if(e===false)return [];let i=n==="block"?{}:{tab:false,indentOnInput:false};return codemirror.codeEditing(typeof e=="object"?{...i,...e}:i)},ve=e=>e?codemirror.gutters(typeof e=="object"?e:{}):[];function M(e){let{value:n,onChange:i,layout:t="inline",wrap:u=t==="block",editing:c,gutters:l,readOnly:d=false,placeholder:s,features:y,tooltipParent:E,onTokenAction:A,isTokenActionable:D,definition:k,extensions:W}=e,v=P(e),L=o__namespace.useRef(null),g=o__namespace.useRef(null),[z,I]=o__namespace.useState(null),[X,$]=o__namespace.useState(false),b=o__namespace.useRef({onChange:i,onTokenAction:A,isTokenActionable:D});b.current={onChange:i,onTokenAction:A,isTokenActionable:D};let p=o__namespace.useRef({service:new state.Compartment,layout:new state.Compartment,editable:new state.Compartment,actions:new state.Compartment}).current,C=o__namespace.useCallback(r=>r?codemirror.languageService(r,{theme:editor.tsliteTokenStyles,...E!==null?{tooltipParent:E??(typeof document<"u"?document.body:void 0)}:{},...y}):[],[y,E]),w=o__namespace.useCallback((r,a,S,h)=>[r==="block"?[...a?[view.EditorView.lineWrapping]:[],Ee]:[codemirror.singleLine,me],ye(S,r),ve(h)],[]),H=o__namespace.useCallback(()=>{let r=[];return b.current.onTokenAction&&r.push(codemirror.tokenAction({actionable:a=>b.current.isTokenActionable?.(a)??true,onAction:a=>b.current.onTokenAction?.(a)})),k&&v?.definitionAt&&r.push(codemirror.gotoDefinition(k)),r},[k,v]);o__namespace.useEffect(()=>{if(!L.current)return;let r=new view.EditorView({parent:L.current,state:state.EditorState.create({doc:t==="inline"?Y(n):n,extensions:[commands.history(),p.service.of(C(v)),p.layout.of(w(t,u,c,l)),p.editable.of(view.EditorView.editable.of(!d)),p.actions.of(H()),...s?[view.placeholder(s)]:[],view.keymap.of([...commands.historyKeymap,...commands.defaultKeymap]),view.EditorView.updateListener.of(a=>{if(a.focusChanged&&$(a.view.hasFocus),!a.docChanged)return;a.transactions.some(h=>h.annotation(codemirror.externalChange))||b.current.onChange?.(a.state.doc.toString());}),view.EditorView.contentAttributes.of({"aria-label":s??"expression"}),...W??[]]})});return g.current=r,I(r),()=>{r.destroy(),g.current=null,I(null);}},[]),o__namespace.useEffect(()=>{g.current?.dispatch({effects:p.service.reconfigure(C(v))});},[v,C,p]),o__namespace.useEffect(()=>{g.current?.dispatch({effects:p.layout.reconfigure(w(t,u,c,l))});},[t,u,c,l,w,p]),o__namespace.useEffect(()=>{g.current?.dispatch({effects:p.editable.reconfigure(view.EditorView.editable.of(!d))});},[d,p]),o__namespace.useEffect(()=>{g.current?.dispatch({effects:p.actions.reconfigure(H())});},[H,p]),o__namespace.useEffect(()=>{let r=g.current;if(!r)return;let a=t==="inline"?Y(n):n;if(a===r.state.doc.toString())return;let{anchor:S,head:h}=r.state.selection.main,U=J=>Math.min(J,a.length);r.dispatch({changes:{from:0,to:r.state.doc.length,insert:a},selection:{anchor:U(S),head:U(h)},annotations:codemirror.externalChange.of(true)});},[n,t]);let q=o__namespace.useCallback(()=>g.current?.focus(),[]);return {ref:L,view:z,focused:X,service:v,focus:q}}var G=o__namespace.createContext(null);function K(){let e=o__namespace.useContext(G);if(!e)throw new Error("useFieldContext must be used inside <ExpressionField.Root>");return e}var be=["value","onChange","layout","wrap","editing","gutters","readOnly","placeholder","features","tooltipParent","onTokenAction","isTokenActionable","definition","extensions","env","service","segmenter","mode","profile","limits","unused","format"];var N=o__namespace.forwardRef(function(n,i){let{children:t,...u}=n,c={},l={};for(let[s,y]of Object.entries(u))(be.includes(s)?c:l)[s]=y;let d=M(c);return jsxRuntime.jsx(G.Provider,{value:d,children:jsxRuntime.jsx("div",{ref:i,...l,children:typeof t=="function"?t(d):t})})}),j=o__namespace.forwardRef(function(n,i){let t=K();return jsxRuntime.jsx("div",{ref:t.ref,...n})}),B=o__namespace.forwardRef(function({children:n,...i},t){return jsxRuntime.jsx("button",{ref:t,type:"button",...i,children:n})}),R={Root:N,Editor:j,ExpandTrigger:B};var f={fg:"var(--foreground, #cccccc)",mutedFg:"var(--muted-foreground, #8b8b8b)",border:"var(--border, #2b2b2b)",ring:"var(--ring, #0078d4)",input:"var(--input, transparent)",destructive:"var(--destructive, #f14c4c)",font:"var(--font-mono, ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace)"},he=["--foreground","--muted-foreground","--border","--ring","--input","--destructive","--font-mono","--muted","--popover","--popover-foreground","--primary","--primary-foreground","--secondary","--accent"];var Re=jsxRuntime.jsx("svg",{width:"14",height:"14",viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round",strokeLinejoin:"round","aria-hidden":"true",children:jsxRuntime.jsx("path",{d:"M15 3h6v6M9 21H3v-6M21 3l-7 7M3 21l7-7"})});function Fe({invalid:e,className:n,style:i,onExpand:t,expandIcon:u,multiline:c,layout:l,...d}){let s=l??(c?"block":"inline");return jsxRuntime.jsx(R.Root,{...d,layout:s,className:n,children:y=>jsxRuntime.jsxs("div",{style:{position:"relative",border:`1px solid ${e?f.destructive:y.focused?f.ring:f.border}`,borderRadius:"6px",backgroundColor:f.input,color:f.fg,fontFamily:f.font,fontSize:"13px",overflow:"hidden",...s==="block"?{height:"100%",display:"flex",flexDirection:"column"}:{},...i},children:[jsxRuntime.jsx(R.Editor,{style:s==="block"?{flex:1,minHeight:0}:{paddingRight:t?"24px":void 0}}),t&&jsxRuntime.jsx(R.ExpandTrigger,{onClick:t,"aria-label":"Expandir",title:"Expandir",onMouseEnter:E=>E.currentTarget.style.opacity="1",onMouseLeave:E=>E.currentTarget.style.opacity="0.55",style:{position:"absolute",top:s==="block"?"6px":"50%",right:"4px",transform:s==="block"?void 0:"translateY(-50%)",display:"flex",alignItems:"center",justifyContent:"center",width:"20px",height:"20px",padding:0,lineHeight:0,border:"none",borderRadius:"4px",background:f.input,color:f.mutedFg,cursor:"pointer",opacity:.55},children:u??Re})]})})}exports.CSS_VARS=he;exports.ExpressionField=R;exports.ExpressionFieldEditor=j;exports.ExpressionFieldExpandTrigger=B;exports.ExpressionFieldRoot=N;exports.ExpressionInput=Fe;exports.V=f;exports.useExpressionField=M;exports.useFieldContext=K;exports.useLanguageService=P;
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { U as UseExpressionFieldOptions, E as ExpressionFieldHandle } from './useExpressionField-CA__y0bO.cjs';
|
|
3
|
+
export { F as FieldLayout, L as LanguageServiceOptions, S as SegmenterOption, u as useExpressionField, a as useLanguageService } from './useExpressionField-CA__y0bO.cjs';
|
|
4
|
+
export { TokenActionTarget } from '@tslite/codemirror';
|
|
5
|
+
import '@codemirror/state';
|
|
6
|
+
import '@codemirror/view';
|
|
7
|
+
import '@tslite/language-service';
|
|
8
|
+
import '@tslite/editor';
|
|
9
|
+
|
|
10
|
+
interface ExpressionInputProps extends UseExpressionFieldOptions {
|
|
11
|
+
/** Borda destrutiva — ex.: um erro de validação que veio de fora. */
|
|
12
|
+
readonly invalid?: boolean;
|
|
13
|
+
/** Classe extra no container. */
|
|
14
|
+
readonly className?: string;
|
|
15
|
+
/** Estilo extra no container (mesclado por cima do padrão). */
|
|
16
|
+
readonly style?: React.CSSProperties;
|
|
17
|
+
/**
|
|
18
|
+
* Ícone de EXPANDIR no canto → chama isto ao clicar (o consumidor abre um
|
|
19
|
+
* modal maior, estilo n8n). Sem esta prop, sem ícone.
|
|
20
|
+
*/
|
|
21
|
+
readonly onExpand?: () => void;
|
|
22
|
+
/** O ícone do botão. Padrão: um SVG inline (sem dependência de lib de ícone). */
|
|
23
|
+
readonly expandIcon?: React.ReactNode;
|
|
24
|
+
/**
|
|
25
|
+
* @deprecated Use `layout="block"`. Mantido porque a prop antiga era booleana
|
|
26
|
+
* e trocá-la de uma vez quebraria quem já a usa.
|
|
27
|
+
*/
|
|
28
|
+
readonly multiline?: boolean;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* O campo de expressão pronto para uso.
|
|
32
|
+
*
|
|
33
|
+
* ```tsx
|
|
34
|
+
* <ExpressionInput value={v} onChange={setV} env={env} /> // "Olá {{ user.name }}"
|
|
35
|
+
* <ExpressionInput value={v} onChange={setV} env={env} segmenter="whole" /> // "foo.bar >= 32"
|
|
36
|
+
* <ExpressionInput value={v} onChange={setV} env={env} layout="block" /> // o modal expandido
|
|
37
|
+
* ```
|
|
38
|
+
*/
|
|
39
|
+
declare function ExpressionInput({ invalid, className, style, onExpand, expandIcon, multiline, layout, ...options }: ExpressionInputProps): React.JSX.Element;
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* O campo em volta — para um botão, um badge, um contador saberem o estado
|
|
43
|
+
* (foco, `view`, serviço) sem receberem props.
|
|
44
|
+
*/
|
|
45
|
+
declare function useFieldContext(): ExpressionFieldHandle;
|
|
46
|
+
interface RootProps extends UseExpressionFieldOptions, Omit<React.HTMLAttributes<HTMLDivElement>, "onChange" | "children"> {
|
|
47
|
+
/**
|
|
48
|
+
* Os filhos. Como FUNÇÃO, recebe o estado do campo — é o atalho para desenhar
|
|
49
|
+
* a borda de foco sem precisar de um componente só para ler o contexto.
|
|
50
|
+
*/
|
|
51
|
+
readonly children?: React.ReactNode | ((field: ExpressionFieldHandle) => React.ReactNode);
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* A raiz: monta o campo e publica o estado dele. Renderiza um `<div>` **sem
|
|
55
|
+
* estilo nenhum**.
|
|
56
|
+
*/
|
|
57
|
+
declare const Root: React.ForwardRefExoticComponent<RootProps & React.RefAttributes<HTMLDivElement>>;
|
|
58
|
+
type EditorProps = React.HTMLAttributes<HTMLDivElement>;
|
|
59
|
+
/**
|
|
60
|
+
* Onde o editor monta. Um `<div>` vazio — o CodeMirror preenche.
|
|
61
|
+
*
|
|
62
|
+
* Precisa existir como peça própria (e não ser a `Root`) porque quase todo
|
|
63
|
+
* layout põe algo ao lado do editor: um botão de expandir, um badge de tipo, um
|
|
64
|
+
* indicador de erro.
|
|
65
|
+
*/
|
|
66
|
+
declare const Editor: React.ForwardRefExoticComponent<EditorProps & React.RefAttributes<HTMLDivElement>>;
|
|
67
|
+
interface ExpandTriggerProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
|
|
68
|
+
readonly children?: React.ReactNode;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Um `<button type="button">` sem estilo. Existe só para carregar o padrão
|
|
72
|
+
* acessível (o `type`, que sem ele submete formulário) e o vocabulário.
|
|
73
|
+
*/
|
|
74
|
+
declare const ExpandTrigger: React.ForwardRefExoticComponent<ExpandTriggerProps & React.RefAttributes<HTMLButtonElement>>;
|
|
75
|
+
/** As peças, agrupadas — `<ExpressionField.Root>`, `.Editor`, `.ExpandTrigger`. */
|
|
76
|
+
declare const ExpressionField: {
|
|
77
|
+
Root: React.ForwardRefExoticComponent<RootProps & React.RefAttributes<HTMLDivElement>>;
|
|
78
|
+
Editor: React.ForwardRefExoticComponent<EditorProps & React.RefAttributes<HTMLDivElement>>;
|
|
79
|
+
ExpandTrigger: React.ForwardRefExoticComponent<ExpandTriggerProps & React.RefAttributes<HTMLButtonElement>>;
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
/** As cores de chrome. Exportadas para o consumidor reusar no que ele desenhar. */
|
|
83
|
+
declare const V: {
|
|
84
|
+
readonly fg: "var(--foreground, #cccccc)";
|
|
85
|
+
readonly mutedFg: "var(--muted-foreground, #8b8b8b)";
|
|
86
|
+
readonly border: "var(--border, #2b2b2b)";
|
|
87
|
+
readonly ring: "var(--ring, #0078d4)";
|
|
88
|
+
readonly input: "var(--input, transparent)";
|
|
89
|
+
readonly destructive: "var(--destructive, #f14c4c)";
|
|
90
|
+
readonly font: "var(--font-mono, ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace)";
|
|
91
|
+
};
|
|
92
|
+
/** As vars que o componente lê — para documentar e para o consumidor conferir. */
|
|
93
|
+
declare const CSS_VARS: readonly ["--foreground", "--muted-foreground", "--border", "--ring", "--input", "--destructive", "--font-mono", "--muted", "--popover", "--popover-foreground", "--primary", "--primary-foreground", "--secondary", "--accent"];
|
|
94
|
+
|
|
95
|
+
export { CSS_VARS, ExpressionField, Editor as ExpressionFieldEditor, type EditorProps as ExpressionFieldEditorProps, ExpandTrigger as ExpressionFieldExpandTrigger, type ExpandTriggerProps as ExpressionFieldExpandTriggerProps, ExpressionFieldHandle, Root as ExpressionFieldRoot, type RootProps as ExpressionFieldRootProps, ExpressionInput, type ExpressionInputProps, UseExpressionFieldOptions, V, useFieldContext };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { U as UseExpressionFieldOptions, E as ExpressionFieldHandle } from './useExpressionField-CA__y0bO.js';
|
|
3
|
+
export { F as FieldLayout, L as LanguageServiceOptions, S as SegmenterOption, u as useExpressionField, a as useLanguageService } from './useExpressionField-CA__y0bO.js';
|
|
4
|
+
export { TokenActionTarget } from '@tslite/codemirror';
|
|
5
|
+
import '@codemirror/state';
|
|
6
|
+
import '@codemirror/view';
|
|
7
|
+
import '@tslite/language-service';
|
|
8
|
+
import '@tslite/editor';
|
|
9
|
+
|
|
10
|
+
interface ExpressionInputProps extends UseExpressionFieldOptions {
|
|
11
|
+
/** Borda destrutiva — ex.: um erro de validação que veio de fora. */
|
|
12
|
+
readonly invalid?: boolean;
|
|
13
|
+
/** Classe extra no container. */
|
|
14
|
+
readonly className?: string;
|
|
15
|
+
/** Estilo extra no container (mesclado por cima do padrão). */
|
|
16
|
+
readonly style?: React.CSSProperties;
|
|
17
|
+
/**
|
|
18
|
+
* Ícone de EXPANDIR no canto → chama isto ao clicar (o consumidor abre um
|
|
19
|
+
* modal maior, estilo n8n). Sem esta prop, sem ícone.
|
|
20
|
+
*/
|
|
21
|
+
readonly onExpand?: () => void;
|
|
22
|
+
/** O ícone do botão. Padrão: um SVG inline (sem dependência de lib de ícone). */
|
|
23
|
+
readonly expandIcon?: React.ReactNode;
|
|
24
|
+
/**
|
|
25
|
+
* @deprecated Use `layout="block"`. Mantido porque a prop antiga era booleana
|
|
26
|
+
* e trocá-la de uma vez quebraria quem já a usa.
|
|
27
|
+
*/
|
|
28
|
+
readonly multiline?: boolean;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* O campo de expressão pronto para uso.
|
|
32
|
+
*
|
|
33
|
+
* ```tsx
|
|
34
|
+
* <ExpressionInput value={v} onChange={setV} env={env} /> // "Olá {{ user.name }}"
|
|
35
|
+
* <ExpressionInput value={v} onChange={setV} env={env} segmenter="whole" /> // "foo.bar >= 32"
|
|
36
|
+
* <ExpressionInput value={v} onChange={setV} env={env} layout="block" /> // o modal expandido
|
|
37
|
+
* ```
|
|
38
|
+
*/
|
|
39
|
+
declare function ExpressionInput({ invalid, className, style, onExpand, expandIcon, multiline, layout, ...options }: ExpressionInputProps): React.JSX.Element;
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* O campo em volta — para um botão, um badge, um contador saberem o estado
|
|
43
|
+
* (foco, `view`, serviço) sem receberem props.
|
|
44
|
+
*/
|
|
45
|
+
declare function useFieldContext(): ExpressionFieldHandle;
|
|
46
|
+
interface RootProps extends UseExpressionFieldOptions, Omit<React.HTMLAttributes<HTMLDivElement>, "onChange" | "children"> {
|
|
47
|
+
/**
|
|
48
|
+
* Os filhos. Como FUNÇÃO, recebe o estado do campo — é o atalho para desenhar
|
|
49
|
+
* a borda de foco sem precisar de um componente só para ler o contexto.
|
|
50
|
+
*/
|
|
51
|
+
readonly children?: React.ReactNode | ((field: ExpressionFieldHandle) => React.ReactNode);
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* A raiz: monta o campo e publica o estado dele. Renderiza um `<div>` **sem
|
|
55
|
+
* estilo nenhum**.
|
|
56
|
+
*/
|
|
57
|
+
declare const Root: React.ForwardRefExoticComponent<RootProps & React.RefAttributes<HTMLDivElement>>;
|
|
58
|
+
type EditorProps = React.HTMLAttributes<HTMLDivElement>;
|
|
59
|
+
/**
|
|
60
|
+
* Onde o editor monta. Um `<div>` vazio — o CodeMirror preenche.
|
|
61
|
+
*
|
|
62
|
+
* Precisa existir como peça própria (e não ser a `Root`) porque quase todo
|
|
63
|
+
* layout põe algo ao lado do editor: um botão de expandir, um badge de tipo, um
|
|
64
|
+
* indicador de erro.
|
|
65
|
+
*/
|
|
66
|
+
declare const Editor: React.ForwardRefExoticComponent<EditorProps & React.RefAttributes<HTMLDivElement>>;
|
|
67
|
+
interface ExpandTriggerProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
|
|
68
|
+
readonly children?: React.ReactNode;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Um `<button type="button">` sem estilo. Existe só para carregar o padrão
|
|
72
|
+
* acessível (o `type`, que sem ele submete formulário) e o vocabulário.
|
|
73
|
+
*/
|
|
74
|
+
declare const ExpandTrigger: React.ForwardRefExoticComponent<ExpandTriggerProps & React.RefAttributes<HTMLButtonElement>>;
|
|
75
|
+
/** As peças, agrupadas — `<ExpressionField.Root>`, `.Editor`, `.ExpandTrigger`. */
|
|
76
|
+
declare const ExpressionField: {
|
|
77
|
+
Root: React.ForwardRefExoticComponent<RootProps & React.RefAttributes<HTMLDivElement>>;
|
|
78
|
+
Editor: React.ForwardRefExoticComponent<EditorProps & React.RefAttributes<HTMLDivElement>>;
|
|
79
|
+
ExpandTrigger: React.ForwardRefExoticComponent<ExpandTriggerProps & React.RefAttributes<HTMLButtonElement>>;
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
/** As cores de chrome. Exportadas para o consumidor reusar no que ele desenhar. */
|
|
83
|
+
declare const V: {
|
|
84
|
+
readonly fg: "var(--foreground, #cccccc)";
|
|
85
|
+
readonly mutedFg: "var(--muted-foreground, #8b8b8b)";
|
|
86
|
+
readonly border: "var(--border, #2b2b2b)";
|
|
87
|
+
readonly ring: "var(--ring, #0078d4)";
|
|
88
|
+
readonly input: "var(--input, transparent)";
|
|
89
|
+
readonly destructive: "var(--destructive, #f14c4c)";
|
|
90
|
+
readonly font: "var(--font-mono, ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace)";
|
|
91
|
+
};
|
|
92
|
+
/** As vars que o componente lê — para documentar e para o consumidor conferir. */
|
|
93
|
+
declare const CSS_VARS: readonly ["--foreground", "--muted-foreground", "--border", "--ring", "--input", "--destructive", "--font-mono", "--muted", "--popover", "--popover-foreground", "--primary", "--primary-foreground", "--secondary", "--accent"];
|
|
94
|
+
|
|
95
|
+
export { CSS_VARS, ExpressionField, Editor as ExpressionFieldEditor, type EditorProps as ExpressionFieldEditorProps, ExpandTrigger as ExpressionFieldExpandTrigger, type ExpandTriggerProps as ExpressionFieldExpandTriggerProps, ExpressionFieldHandle, Root as ExpressionFieldRoot, type RootProps as ExpressionFieldRootProps, ExpressionInput, type ExpressionInputProps, UseExpressionFieldOptions, V, useFieldContext };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import*as o from'react';import {Compartment,EditorState}from'@codemirror/state';import {EditorView,placeholder,keymap}from'@codemirror/view';import {history,historyKeymap,defaultKeymap}from'@codemirror/commands';import {languageService,singleLine,tokenAction,gotoDefinition,externalChange,codeEditing,gutters}from'@tslite/codemirror';import {tsliteTokenStyles,createHostService}from'@tslite/editor';import {whole,mustache}from'@tslite/language-service';import {jsx,jsxs}from'react/jsx-runtime';var te={mustache:mustache,whole:whole},oe=e=>typeof e=="function"?e:te[e];function P(e){let{env:n,service:i,segmenter:t="mustache",mode:u="expression",profile:c,limits:l,unused:d,format:s}=e;return o.useMemo(()=>i||(n?createHostService({env:n,segmenter:oe(t),mode:u,...c?{profile:c}:{},...l?{limits:l}:{},...d!==void 0?{unused:d}:{},...s?{format:s}:{}}):null),[i,n,t,u,c,l,d,s])}var me=EditorView.theme({".cm-scroller":{overflowX:"auto",overflowY:"hidden"},".cm-content":{padding:"6px 8px"},".cm-line":{padding:"0"},"&.cm-focused":{outline:"none"}}),Ee=EditorView.theme({"&":{height:"100%"},".cm-scroller":{overflowY:"auto"},".cm-content":{padding:"8px"},"&.cm-focused":{outline:"none"}}),Y=e=>e.replace(/\r?\n/g," "),ye=(e,n)=>{if(e===false)return [];let i=n==="block"?{}:{tab:false,indentOnInput:false};return codeEditing(typeof e=="object"?{...i,...e}:i)},ve=e=>e?gutters(typeof e=="object"?e:{}):[];function M(e){let{value:n,onChange:i,layout:t="inline",wrap:u=t==="block",editing:c,gutters:l,readOnly:d=false,placeholder:s,features:y,tooltipParent:E,onTokenAction:A,isTokenActionable:D,definition:k,extensions:W}=e,v=P(e),L=o.useRef(null),g=o.useRef(null),[z,I]=o.useState(null),[X,$]=o.useState(false),b=o.useRef({onChange:i,onTokenAction:A,isTokenActionable:D});b.current={onChange:i,onTokenAction:A,isTokenActionable:D};let p=o.useRef({service:new Compartment,layout:new Compartment,editable:new Compartment,actions:new Compartment}).current,C=o.useCallback(r=>r?languageService(r,{theme:tsliteTokenStyles,...E!==null?{tooltipParent:E??(typeof document<"u"?document.body:void 0)}:{},...y}):[],[y,E]),w=o.useCallback((r,a,S,h)=>[r==="block"?[...a?[EditorView.lineWrapping]:[],Ee]:[singleLine,me],ye(S,r),ve(h)],[]),H=o.useCallback(()=>{let r=[];return b.current.onTokenAction&&r.push(tokenAction({actionable:a=>b.current.isTokenActionable?.(a)??true,onAction:a=>b.current.onTokenAction?.(a)})),k&&v?.definitionAt&&r.push(gotoDefinition(k)),r},[k,v]);o.useEffect(()=>{if(!L.current)return;let r=new EditorView({parent:L.current,state:EditorState.create({doc:t==="inline"?Y(n):n,extensions:[history(),p.service.of(C(v)),p.layout.of(w(t,u,c,l)),p.editable.of(EditorView.editable.of(!d)),p.actions.of(H()),...s?[placeholder(s)]:[],keymap.of([...historyKeymap,...defaultKeymap]),EditorView.updateListener.of(a=>{if(a.focusChanged&&$(a.view.hasFocus),!a.docChanged)return;a.transactions.some(h=>h.annotation(externalChange))||b.current.onChange?.(a.state.doc.toString());}),EditorView.contentAttributes.of({"aria-label":s??"expression"}),...W??[]]})});return g.current=r,I(r),()=>{r.destroy(),g.current=null,I(null);}},[]),o.useEffect(()=>{g.current?.dispatch({effects:p.service.reconfigure(C(v))});},[v,C,p]),o.useEffect(()=>{g.current?.dispatch({effects:p.layout.reconfigure(w(t,u,c,l))});},[t,u,c,l,w,p]),o.useEffect(()=>{g.current?.dispatch({effects:p.editable.reconfigure(EditorView.editable.of(!d))});},[d,p]),o.useEffect(()=>{g.current?.dispatch({effects:p.actions.reconfigure(H())});},[H,p]),o.useEffect(()=>{let r=g.current;if(!r)return;let a=t==="inline"?Y(n):n;if(a===r.state.doc.toString())return;let{anchor:S,head:h}=r.state.selection.main,U=J=>Math.min(J,a.length);r.dispatch({changes:{from:0,to:r.state.doc.length,insert:a},selection:{anchor:U(S),head:U(h)},annotations:externalChange.of(true)});},[n,t]);let q=o.useCallback(()=>g.current?.focus(),[]);return {ref:L,view:z,focused:X,service:v,focus:q}}var G=o.createContext(null);function K(){let e=o.useContext(G);if(!e)throw new Error("useFieldContext must be used inside <ExpressionField.Root>");return e}var be=["value","onChange","layout","wrap","editing","gutters","readOnly","placeholder","features","tooltipParent","onTokenAction","isTokenActionable","definition","extensions","env","service","segmenter","mode","profile","limits","unused","format"];var N=o.forwardRef(function(n,i){let{children:t,...u}=n,c={},l={};for(let[s,y]of Object.entries(u))(be.includes(s)?c:l)[s]=y;let d=M(c);return jsx(G.Provider,{value:d,children:jsx("div",{ref:i,...l,children:typeof t=="function"?t(d):t})})}),j=o.forwardRef(function(n,i){let t=K();return jsx("div",{ref:t.ref,...n})}),B=o.forwardRef(function({children:n,...i},t){return jsx("button",{ref:t,type:"button",...i,children:n})}),R={Root:N,Editor:j,ExpandTrigger:B};var f={fg:"var(--foreground, #cccccc)",mutedFg:"var(--muted-foreground, #8b8b8b)",border:"var(--border, #2b2b2b)",ring:"var(--ring, #0078d4)",input:"var(--input, transparent)",destructive:"var(--destructive, #f14c4c)",font:"var(--font-mono, ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace)"},he=["--foreground","--muted-foreground","--border","--ring","--input","--destructive","--font-mono","--muted","--popover","--popover-foreground","--primary","--primary-foreground","--secondary","--accent"];var Re=jsx("svg",{width:"14",height:"14",viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round",strokeLinejoin:"round","aria-hidden":"true",children:jsx("path",{d:"M15 3h6v6M9 21H3v-6M21 3l-7 7M3 21l7-7"})});function Fe({invalid:e,className:n,style:i,onExpand:t,expandIcon:u,multiline:c,layout:l,...d}){let s=l??(c?"block":"inline");return jsx(R.Root,{...d,layout:s,className:n,children:y=>jsxs("div",{style:{position:"relative",border:`1px solid ${e?f.destructive:y.focused?f.ring:f.border}`,borderRadius:"6px",backgroundColor:f.input,color:f.fg,fontFamily:f.font,fontSize:"13px",overflow:"hidden",...s==="block"?{height:"100%",display:"flex",flexDirection:"column"}:{},...i},children:[jsx(R.Editor,{style:s==="block"?{flex:1,minHeight:0}:{paddingRight:t?"24px":void 0}}),t&&jsx(R.ExpandTrigger,{onClick:t,"aria-label":"Expandir",title:"Expandir",onMouseEnter:E=>E.currentTarget.style.opacity="1",onMouseLeave:E=>E.currentTarget.style.opacity="0.55",style:{position:"absolute",top:s==="block"?"6px":"50%",right:"4px",transform:s==="block"?void 0:"translateY(-50%)",display:"flex",alignItems:"center",justifyContent:"center",width:"20px",height:"20px",padding:0,lineHeight:0,border:"none",borderRadius:"4px",background:f.input,color:f.mutedFg,cursor:"pointer",opacity:.55},children:u??Re})]})})}export{he as CSS_VARS,R as ExpressionField,j as ExpressionFieldEditor,B as ExpressionFieldExpandTrigger,N as ExpressionFieldRoot,Fe as ExpressionInput,f as V,M as useExpressionField,K as useFieldContext,P as useLanguageService};
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { Extension } from '@codemirror/state';
|
|
3
|
+
import { EditorView } from '@codemirror/view';
|
|
4
|
+
import { CodeEditingOptions, GuttersOptions, LanguageServiceOptions as LanguageServiceOptions$1, TokenActionTarget, GotoDefinitionOptions } from '@tslite/codemirror';
|
|
5
|
+
import { LanguageService, Segmenter, Token } from '@tslite/language-service';
|
|
6
|
+
import { HostServiceOptions } from '@tslite/editor';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Onde, no texto, mora o código.
|
|
10
|
+
*
|
|
11
|
+
* - `"mustache"` — ilhas `{{ … }}` dentro de texto de host (`"Olá {{ user.name }}"`);
|
|
12
|
+
* - `"whole"` — o campo INTEIRO é código (`"foo.bar >= 32"`);
|
|
13
|
+
* - um `Segmenter` seu, para outra convenção de delimitador.
|
|
14
|
+
*/
|
|
15
|
+
type SegmenterOption = "mustache" | "whole" | Segmenter;
|
|
16
|
+
/** O que define o cérebro do campo. */
|
|
17
|
+
interface LanguageServiceOptions {
|
|
18
|
+
/**
|
|
19
|
+
* O ambiente de tipos — o shape dos dados no escopo das expressões.
|
|
20
|
+
*
|
|
21
|
+
* ⚠️ **Memoize-o.** Um objeto novo a cada render reconstrói o serviço e joga
|
|
22
|
+
* fora o cache de análise. `useMemo` no seu lado resolve.
|
|
23
|
+
*/
|
|
24
|
+
readonly env?: HostServiceOptions["env"];
|
|
25
|
+
/**
|
|
26
|
+
* Um serviço pronto, no lugar do `env`. Tem precedência — e é o caminho para
|
|
27
|
+
* trocar o cérebro por outro (outra linguagem, outra implementação).
|
|
28
|
+
*/
|
|
29
|
+
readonly service?: LanguageService;
|
|
30
|
+
/** Onde o código está. Padrão: `"mustache"`. */
|
|
31
|
+
readonly segmenter?: SegmenterOption;
|
|
32
|
+
/**
|
|
33
|
+
* O que o código É. Padrão: `"expression"`.
|
|
34
|
+
*
|
|
35
|
+
* Ortogonal ao `segmenter`: uma ilha pode conter um programa, e um campo
|
|
36
|
+
* inteiro pode ser uma expressão. São dois botões porque nada os amarra.
|
|
37
|
+
*/
|
|
38
|
+
readonly mode?: "expression" | "program";
|
|
39
|
+
/**
|
|
40
|
+
* Capability — **desinstala** features da Linguagem.
|
|
41
|
+
*
|
|
42
|
+
* Sem ela o campo só tipa, e uma atribuição (`{{ user.name = "ok" }}`) passa:
|
|
43
|
+
* `=` existe na Linguagem completa. Não há padrão implícito aqui, porque qual
|
|
44
|
+
* profile cabe num campo é decisão de produto.
|
|
45
|
+
*/
|
|
46
|
+
readonly profile?: HostServiceOptions["profile"];
|
|
47
|
+
/** Limites estruturais (anti-DoS). */
|
|
48
|
+
readonly limits?: HostServiceOptions["limits"];
|
|
49
|
+
/** Bindings nunca lidos. Ligado por padrão (é API de editor). */
|
|
50
|
+
readonly unused?: HostServiceOptions["unused"];
|
|
51
|
+
/** O formatador (`formatExpression` do `@tslite/format`). Sem ele, não formata. */
|
|
52
|
+
readonly format?: HostServiceOptions["format"];
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* O `LanguageService` do campo. `null` quando não há `env` nem `service` — o
|
|
56
|
+
* editor continua editando texto, só sem inteligência.
|
|
57
|
+
*/
|
|
58
|
+
declare function useLanguageService(options: LanguageServiceOptions): LanguageService | null;
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Como o campo se APRESENTA — ortogonal ao que ele contém.
|
|
62
|
+
*
|
|
63
|
+
* - `"inline"` — uma linha, estilo input: sem quebra, rolagem horizontal;
|
|
64
|
+
* - `"block"` — várias linhas, estilo editor: quebra de linha e rolagem vertical.
|
|
65
|
+
*/
|
|
66
|
+
type FieldLayout = "inline" | "block";
|
|
67
|
+
interface UseExpressionFieldOptions extends LanguageServiceOptions {
|
|
68
|
+
/** O texto do campo (controlado). */
|
|
69
|
+
readonly value: string;
|
|
70
|
+
/** Chamado a cada edição do USUÁRIO — nunca por sincronização externa. */
|
|
71
|
+
readonly onChange?: (value: string) => void;
|
|
72
|
+
/** Como o campo se apresenta. Padrão: `"inline"`. */
|
|
73
|
+
readonly layout?: FieldLayout;
|
|
74
|
+
/**
|
|
75
|
+
* Quebrar linha longa em vez de rolar na horizontal. Padrão: `true` no layout
|
|
76
|
+
* `"block"`.
|
|
77
|
+
*
|
|
78
|
+
* É opção à parte do `layout` porque são decisões diferentes: um CAMPO de
|
|
79
|
+
* várias linhas quer quebrar (o texto é o conteúdo); um editor de ARQUIVO
|
|
80
|
+
* normalmente não — a coluna importa, e é o que o VS Code faz por padrão. No
|
|
81
|
+
* `"inline"` não se aplica: ali é uma linha só.
|
|
82
|
+
*/
|
|
83
|
+
readonly wrap?: boolean;
|
|
84
|
+
/**
|
|
85
|
+
* As capacidades de EDIÇÃO do CodeMirror — pares que se fecham, seleção
|
|
86
|
+
* desenhada, multi-cursor e o `Tab` que indenta. Padrão: **ligadas**, com uma
|
|
87
|
+
* diferença que segue o layout:
|
|
88
|
+
*
|
|
89
|
+
* | layout | `Tab` | por quê |
|
|
90
|
+
* | --- | --- | --- |
|
|
91
|
+
* | `"block"` | indenta | é um editor: não há para onde o foco ir |
|
|
92
|
+
* | `"inline"` | **navega** | num campo de formulário, roubar o `Tab` prende o teclado — o usuário não sai sem o mouse |
|
|
93
|
+
*
|
|
94
|
+
* Passe `false` para não instalar nada, ou um objeto para ajustar (ele é
|
|
95
|
+
* mesclado sobre o padrão do layout).
|
|
96
|
+
*
|
|
97
|
+
* A BUSCA (`Ctrl+D`, `Ctrl+F`) **não** entra aqui: ela vive num peer opcional.
|
|
98
|
+
* Quem a quiser instala `@codemirror/search` e passa a extensão:
|
|
99
|
+
*
|
|
100
|
+
* ```tsx
|
|
101
|
+
* import { searchEditing } from "@tslite/codemirror/search";
|
|
102
|
+
* <ExpressionInput layout="block" extensions={[searchEditing()]} />
|
|
103
|
+
* ```
|
|
104
|
+
*/
|
|
105
|
+
readonly editing?: boolean | CodeEditingOptions;
|
|
106
|
+
/**
|
|
107
|
+
* Régua de linhas e realce da linha ativa. Padrão: **desligadas** — um campo
|
|
108
|
+
* não as quer. Num campo EXPANDIDO (o modal), normalmente sim.
|
|
109
|
+
*/
|
|
110
|
+
readonly gutters?: boolean | GuttersOptions;
|
|
111
|
+
/** Somente leitura (ainda navegável, selecionável e com hover). */
|
|
112
|
+
readonly readOnly?: boolean;
|
|
113
|
+
/** Texto mostrado quando o campo está vazio. */
|
|
114
|
+
readonly placeholder?: string;
|
|
115
|
+
/** Quais capacidades da superfície ligar. Ver `@tslite/codemirror`. */
|
|
116
|
+
readonly features?: LanguageServiceOptions$1;
|
|
117
|
+
/**
|
|
118
|
+
* Onde os tooltips (hover, dropdown) são inseridos no DOM.
|
|
119
|
+
*
|
|
120
|
+
* Padrão: **`document.body`**. Um campo de expressão vive dentro de modal,
|
|
121
|
+
* drawer e painel — e quase todo modal centraliza com `transform`, o que faz o
|
|
122
|
+
* CodeMirror abandonar o `position: fixed` dos tooltips e cair no absoluto: a
|
|
123
|
+
* caixa nasce longe do cursor e o mouse não a alcança.
|
|
124
|
+
*
|
|
125
|
+
* Passe `null` para deixar o CodeMirror decidir (o tooltip fica dentro do
|
|
126
|
+
* editor), ou um elemento seu se precisar controlar o empilhamento.
|
|
127
|
+
*/
|
|
128
|
+
readonly tooltipParent?: HTMLElement | null;
|
|
129
|
+
/**
|
|
130
|
+
* Modificador + clique num token → o consumidor decide o que fazer (abrir um
|
|
131
|
+
* inspetor, a doc de um operador). Sem esta função, o gesto fica desligado.
|
|
132
|
+
*/
|
|
133
|
+
readonly onTokenAction?: (target: TokenActionTarget) => void;
|
|
134
|
+
/** Quais tokens são acionáveis. Padrão: todos. */
|
|
135
|
+
readonly isTokenActionable?: (token: Token) => boolean;
|
|
136
|
+
/**
|
|
137
|
+
* Navegação por definição. Desligada por padrão: ela exige decisões que só o
|
|
138
|
+
* consumidor toma (qual tecla, e o que fazer com um alvo externo).
|
|
139
|
+
*/
|
|
140
|
+
readonly definition?: GotoDefinitionOptions | false;
|
|
141
|
+
/** Extensões extras do CodeMirror — a escotilha de fuga. */
|
|
142
|
+
readonly extensions?: readonly Extension[];
|
|
143
|
+
}
|
|
144
|
+
/** O que o hook devolve. */
|
|
145
|
+
interface ExpressionFieldHandle {
|
|
146
|
+
/** Ponha num elemento vazio: é onde o editor monta. */
|
|
147
|
+
readonly ref: React.RefObject<HTMLDivElement>;
|
|
148
|
+
/** O `EditorView`, depois da montagem. Para comandos e integrações. */
|
|
149
|
+
readonly view: EditorView | null;
|
|
150
|
+
/** O campo está com foco? (para desenhar o anel, sem `:focus-within`) */
|
|
151
|
+
readonly focused: boolean;
|
|
152
|
+
/** O serviço em uso, ou `null`. */
|
|
153
|
+
readonly service: LanguageService | null;
|
|
154
|
+
/** Põe o foco no editor. */
|
|
155
|
+
focus(): void;
|
|
156
|
+
}
|
|
157
|
+
declare function useExpressionField(options: UseExpressionFieldOptions): ExpressionFieldHandle;
|
|
158
|
+
|
|
159
|
+
export { type ExpressionFieldHandle as E, type FieldLayout as F, type LanguageServiceOptions as L, type SegmenterOption as S, type UseExpressionFieldOptions as U, useLanguageService as a, useExpressionField as u };
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { Extension } from '@codemirror/state';
|
|
3
|
+
import { EditorView } from '@codemirror/view';
|
|
4
|
+
import { CodeEditingOptions, GuttersOptions, LanguageServiceOptions as LanguageServiceOptions$1, TokenActionTarget, GotoDefinitionOptions } from '@tslite/codemirror';
|
|
5
|
+
import { LanguageService, Segmenter, Token } from '@tslite/language-service';
|
|
6
|
+
import { HostServiceOptions } from '@tslite/editor';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Onde, no texto, mora o código.
|
|
10
|
+
*
|
|
11
|
+
* - `"mustache"` — ilhas `{{ … }}` dentro de texto de host (`"Olá {{ user.name }}"`);
|
|
12
|
+
* - `"whole"` — o campo INTEIRO é código (`"foo.bar >= 32"`);
|
|
13
|
+
* - um `Segmenter` seu, para outra convenção de delimitador.
|
|
14
|
+
*/
|
|
15
|
+
type SegmenterOption = "mustache" | "whole" | Segmenter;
|
|
16
|
+
/** O que define o cérebro do campo. */
|
|
17
|
+
interface LanguageServiceOptions {
|
|
18
|
+
/**
|
|
19
|
+
* O ambiente de tipos — o shape dos dados no escopo das expressões.
|
|
20
|
+
*
|
|
21
|
+
* ⚠️ **Memoize-o.** Um objeto novo a cada render reconstrói o serviço e joga
|
|
22
|
+
* fora o cache de análise. `useMemo` no seu lado resolve.
|
|
23
|
+
*/
|
|
24
|
+
readonly env?: HostServiceOptions["env"];
|
|
25
|
+
/**
|
|
26
|
+
* Um serviço pronto, no lugar do `env`. Tem precedência — e é o caminho para
|
|
27
|
+
* trocar o cérebro por outro (outra linguagem, outra implementação).
|
|
28
|
+
*/
|
|
29
|
+
readonly service?: LanguageService;
|
|
30
|
+
/** Onde o código está. Padrão: `"mustache"`. */
|
|
31
|
+
readonly segmenter?: SegmenterOption;
|
|
32
|
+
/**
|
|
33
|
+
* O que o código É. Padrão: `"expression"`.
|
|
34
|
+
*
|
|
35
|
+
* Ortogonal ao `segmenter`: uma ilha pode conter um programa, e um campo
|
|
36
|
+
* inteiro pode ser uma expressão. São dois botões porque nada os amarra.
|
|
37
|
+
*/
|
|
38
|
+
readonly mode?: "expression" | "program";
|
|
39
|
+
/**
|
|
40
|
+
* Capability — **desinstala** features da Linguagem.
|
|
41
|
+
*
|
|
42
|
+
* Sem ela o campo só tipa, e uma atribuição (`{{ user.name = "ok" }}`) passa:
|
|
43
|
+
* `=` existe na Linguagem completa. Não há padrão implícito aqui, porque qual
|
|
44
|
+
* profile cabe num campo é decisão de produto.
|
|
45
|
+
*/
|
|
46
|
+
readonly profile?: HostServiceOptions["profile"];
|
|
47
|
+
/** Limites estruturais (anti-DoS). */
|
|
48
|
+
readonly limits?: HostServiceOptions["limits"];
|
|
49
|
+
/** Bindings nunca lidos. Ligado por padrão (é API de editor). */
|
|
50
|
+
readonly unused?: HostServiceOptions["unused"];
|
|
51
|
+
/** O formatador (`formatExpression` do `@tslite/format`). Sem ele, não formata. */
|
|
52
|
+
readonly format?: HostServiceOptions["format"];
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* O `LanguageService` do campo. `null` quando não há `env` nem `service` — o
|
|
56
|
+
* editor continua editando texto, só sem inteligência.
|
|
57
|
+
*/
|
|
58
|
+
declare function useLanguageService(options: LanguageServiceOptions): LanguageService | null;
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Como o campo se APRESENTA — ortogonal ao que ele contém.
|
|
62
|
+
*
|
|
63
|
+
* - `"inline"` — uma linha, estilo input: sem quebra, rolagem horizontal;
|
|
64
|
+
* - `"block"` — várias linhas, estilo editor: quebra de linha e rolagem vertical.
|
|
65
|
+
*/
|
|
66
|
+
type FieldLayout = "inline" | "block";
|
|
67
|
+
interface UseExpressionFieldOptions extends LanguageServiceOptions {
|
|
68
|
+
/** O texto do campo (controlado). */
|
|
69
|
+
readonly value: string;
|
|
70
|
+
/** Chamado a cada edição do USUÁRIO — nunca por sincronização externa. */
|
|
71
|
+
readonly onChange?: (value: string) => void;
|
|
72
|
+
/** Como o campo se apresenta. Padrão: `"inline"`. */
|
|
73
|
+
readonly layout?: FieldLayout;
|
|
74
|
+
/**
|
|
75
|
+
* Quebrar linha longa em vez de rolar na horizontal. Padrão: `true` no layout
|
|
76
|
+
* `"block"`.
|
|
77
|
+
*
|
|
78
|
+
* É opção à parte do `layout` porque são decisões diferentes: um CAMPO de
|
|
79
|
+
* várias linhas quer quebrar (o texto é o conteúdo); um editor de ARQUIVO
|
|
80
|
+
* normalmente não — a coluna importa, e é o que o VS Code faz por padrão. No
|
|
81
|
+
* `"inline"` não se aplica: ali é uma linha só.
|
|
82
|
+
*/
|
|
83
|
+
readonly wrap?: boolean;
|
|
84
|
+
/**
|
|
85
|
+
* As capacidades de EDIÇÃO do CodeMirror — pares que se fecham, seleção
|
|
86
|
+
* desenhada, multi-cursor e o `Tab` que indenta. Padrão: **ligadas**, com uma
|
|
87
|
+
* diferença que segue o layout:
|
|
88
|
+
*
|
|
89
|
+
* | layout | `Tab` | por quê |
|
|
90
|
+
* | --- | --- | --- |
|
|
91
|
+
* | `"block"` | indenta | é um editor: não há para onde o foco ir |
|
|
92
|
+
* | `"inline"` | **navega** | num campo de formulário, roubar o `Tab` prende o teclado — o usuário não sai sem o mouse |
|
|
93
|
+
*
|
|
94
|
+
* Passe `false` para não instalar nada, ou um objeto para ajustar (ele é
|
|
95
|
+
* mesclado sobre o padrão do layout).
|
|
96
|
+
*
|
|
97
|
+
* A BUSCA (`Ctrl+D`, `Ctrl+F`) **não** entra aqui: ela vive num peer opcional.
|
|
98
|
+
* Quem a quiser instala `@codemirror/search` e passa a extensão:
|
|
99
|
+
*
|
|
100
|
+
* ```tsx
|
|
101
|
+
* import { searchEditing } from "@tslite/codemirror/search";
|
|
102
|
+
* <ExpressionInput layout="block" extensions={[searchEditing()]} />
|
|
103
|
+
* ```
|
|
104
|
+
*/
|
|
105
|
+
readonly editing?: boolean | CodeEditingOptions;
|
|
106
|
+
/**
|
|
107
|
+
* Régua de linhas e realce da linha ativa. Padrão: **desligadas** — um campo
|
|
108
|
+
* não as quer. Num campo EXPANDIDO (o modal), normalmente sim.
|
|
109
|
+
*/
|
|
110
|
+
readonly gutters?: boolean | GuttersOptions;
|
|
111
|
+
/** Somente leitura (ainda navegável, selecionável e com hover). */
|
|
112
|
+
readonly readOnly?: boolean;
|
|
113
|
+
/** Texto mostrado quando o campo está vazio. */
|
|
114
|
+
readonly placeholder?: string;
|
|
115
|
+
/** Quais capacidades da superfície ligar. Ver `@tslite/codemirror`. */
|
|
116
|
+
readonly features?: LanguageServiceOptions$1;
|
|
117
|
+
/**
|
|
118
|
+
* Onde os tooltips (hover, dropdown) são inseridos no DOM.
|
|
119
|
+
*
|
|
120
|
+
* Padrão: **`document.body`**. Um campo de expressão vive dentro de modal,
|
|
121
|
+
* drawer e painel — e quase todo modal centraliza com `transform`, o que faz o
|
|
122
|
+
* CodeMirror abandonar o `position: fixed` dos tooltips e cair no absoluto: a
|
|
123
|
+
* caixa nasce longe do cursor e o mouse não a alcança.
|
|
124
|
+
*
|
|
125
|
+
* Passe `null` para deixar o CodeMirror decidir (o tooltip fica dentro do
|
|
126
|
+
* editor), ou um elemento seu se precisar controlar o empilhamento.
|
|
127
|
+
*/
|
|
128
|
+
readonly tooltipParent?: HTMLElement | null;
|
|
129
|
+
/**
|
|
130
|
+
* Modificador + clique num token → o consumidor decide o que fazer (abrir um
|
|
131
|
+
* inspetor, a doc de um operador). Sem esta função, o gesto fica desligado.
|
|
132
|
+
*/
|
|
133
|
+
readonly onTokenAction?: (target: TokenActionTarget) => void;
|
|
134
|
+
/** Quais tokens são acionáveis. Padrão: todos. */
|
|
135
|
+
readonly isTokenActionable?: (token: Token) => boolean;
|
|
136
|
+
/**
|
|
137
|
+
* Navegação por definição. Desligada por padrão: ela exige decisões que só o
|
|
138
|
+
* consumidor toma (qual tecla, e o que fazer com um alvo externo).
|
|
139
|
+
*/
|
|
140
|
+
readonly definition?: GotoDefinitionOptions | false;
|
|
141
|
+
/** Extensões extras do CodeMirror — a escotilha de fuga. */
|
|
142
|
+
readonly extensions?: readonly Extension[];
|
|
143
|
+
}
|
|
144
|
+
/** O que o hook devolve. */
|
|
145
|
+
interface ExpressionFieldHandle {
|
|
146
|
+
/** Ponha num elemento vazio: é onde o editor monta. */
|
|
147
|
+
readonly ref: React.RefObject<HTMLDivElement>;
|
|
148
|
+
/** O `EditorView`, depois da montagem. Para comandos e integrações. */
|
|
149
|
+
readonly view: EditorView | null;
|
|
150
|
+
/** O campo está com foco? (para desenhar o anel, sem `:focus-within`) */
|
|
151
|
+
readonly focused: boolean;
|
|
152
|
+
/** O serviço em uso, ou `null`. */
|
|
153
|
+
readonly service: LanguageService | null;
|
|
154
|
+
/** Põe o foco no editor. */
|
|
155
|
+
focus(): void;
|
|
156
|
+
}
|
|
157
|
+
declare function useExpressionField(options: UseExpressionFieldOptions): ExpressionFieldHandle;
|
|
158
|
+
|
|
159
|
+
export { type ExpressionFieldHandle as E, type FieldLayout as F, type LanguageServiceOptions as L, type SegmenterOption as S, type UseExpressionFieldOptions as U, useLanguageService as a, useExpressionField as u };
|
package/package.json
ADDED
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@tslite/react",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "React bindings for TSLite — a headless expression field (CodeMirror 6) with type-aware highlighting, completion, diagnostics and quick-fixes, driven by the TSLite checker.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.cjs",
|
|
7
|
+
"module": "./dist/index.js",
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"types": "./dist/index.d.ts",
|
|
12
|
+
"import": "./dist/index.js",
|
|
13
|
+
"require": "./dist/index.cjs"
|
|
14
|
+
},
|
|
15
|
+
"./full": {
|
|
16
|
+
"types": "./dist/full.d.ts",
|
|
17
|
+
"import": "./dist/full.js",
|
|
18
|
+
"require": "./dist/full.cjs"
|
|
19
|
+
}
|
|
20
|
+
},
|
|
21
|
+
"files": [
|
|
22
|
+
"dist"
|
|
23
|
+
],
|
|
24
|
+
"sideEffects": false,
|
|
25
|
+
"engines": {
|
|
26
|
+
"node": ">=18"
|
|
27
|
+
},
|
|
28
|
+
"repository": {
|
|
29
|
+
"type": "git",
|
|
30
|
+
"url": "https://github.com/andersondrosa/tslite.git",
|
|
31
|
+
"directory": "packages/react"
|
|
32
|
+
},
|
|
33
|
+
"keywords": [
|
|
34
|
+
"react",
|
|
35
|
+
"codemirror",
|
|
36
|
+
"expression",
|
|
37
|
+
"input",
|
|
38
|
+
"headless",
|
|
39
|
+
"autocomplete",
|
|
40
|
+
"mustache",
|
|
41
|
+
"nocode",
|
|
42
|
+
"typescript"
|
|
43
|
+
],
|
|
44
|
+
"author": "Anderson D. Rosa <andersondrosa@outlook.com>",
|
|
45
|
+
"license": "MIT",
|
|
46
|
+
"publishConfig": {
|
|
47
|
+
"access": "public"
|
|
48
|
+
},
|
|
49
|
+
"dependencies": {
|
|
50
|
+
"@tslite/codemirror": "0.1.0",
|
|
51
|
+
"@tslite/editor": "0.4.0",
|
|
52
|
+
"@tslite/format": "0.3.0",
|
|
53
|
+
"@tslite/language-service": "0.1.0"
|
|
54
|
+
},
|
|
55
|
+
"peerDependencies": {
|
|
56
|
+
"@codemirror/autocomplete": "^6.0.0",
|
|
57
|
+
"@codemirror/commands": "^6.0.0",
|
|
58
|
+
"@codemirror/language": "^6.0.0",
|
|
59
|
+
"@codemirror/lint": "^6.0.0",
|
|
60
|
+
"@codemirror/search": "^6.0.0",
|
|
61
|
+
"@codemirror/state": "^6.0.0",
|
|
62
|
+
"@codemirror/view": "^6.0.0",
|
|
63
|
+
"react": ">=18",
|
|
64
|
+
"react-dom": ">=18"
|
|
65
|
+
},
|
|
66
|
+
"devDependencies": {
|
|
67
|
+
"@codemirror/autocomplete": "^6.20.3",
|
|
68
|
+
"@codemirror/commands": "^6.10.4",
|
|
69
|
+
"@codemirror/language": "^6.12.4",
|
|
70
|
+
"@codemirror/lint": "^6.9.7",
|
|
71
|
+
"@codemirror/state": "^6.7.1",
|
|
72
|
+
"@codemirror/view": "^6.43.7",
|
|
73
|
+
"@testing-library/dom": "^10.4.1",
|
|
74
|
+
"@testing-library/react": "^16.1.0",
|
|
75
|
+
"@types/react": "^18.3.12",
|
|
76
|
+
"@types/react-dom": "^18.3.1",
|
|
77
|
+
"react": "^18.3.1",
|
|
78
|
+
"react-dom": "^18.3.1",
|
|
79
|
+
"@tslite/checker": "0.7.0"
|
|
80
|
+
},
|
|
81
|
+
"peerDependenciesMeta": {
|
|
82
|
+
"@codemirror/search": {
|
|
83
|
+
"optional": true
|
|
84
|
+
}
|
|
85
|
+
},
|
|
86
|
+
"scripts": {
|
|
87
|
+
"build": "tsup",
|
|
88
|
+
"dev": "tsup --watch",
|
|
89
|
+
"test": "vitest run",
|
|
90
|
+
"test:watch": "vitest",
|
|
91
|
+
"typecheck": "tsc --noEmit",
|
|
92
|
+
"lint": "eslint src",
|
|
93
|
+
"lint:fix": "eslint src --fix",
|
|
94
|
+
"clean": "rm -rf dist",
|
|
95
|
+
"format": "prettier --write \"{src,tests}/**/*.{ts,tsx}\"",
|
|
96
|
+
"format:check": "prettier --check \"{src,tests}/**/*.{ts,tsx}\""
|
|
97
|
+
}
|
|
98
|
+
}
|