@neo4j-cypher/react-codemirror
Advanced tools
Comparing version 2.0.0-canary-9fcb804 to 2.0.0-canary-9fd0ae1
# @neo4j-cypher/react-codemirror | ||
## 2.0.0-next.12 | ||
### Patch Changes | ||
- dcbe67d: Expose moveFocusOnTab property on the CypherEditor component to conditionally disable tab keymappings | ||
- e9621c8: Re-export language support from react codemirror | ||
- Updated dependencies [2e72ac8] | ||
- @neo4j-cypher/language-support@2.0.0-next.9 | ||
## 2.0.0-next.11 | ||
### Patch Changes | ||
- Updated dependencies [05663bd] | ||
- @neo4j-cypher/language-support@2.0.0-next.8 | ||
## 2.0.0-next.10 | ||
### Patch Changes | ||
- bb7e9d3: Simplify detection and handling of value prop updates | ||
## 2.0.0-next.9 | ||
@@ -4,0 +26,0 @@ |
@@ -138,5 +138,14 @@ import { EditorState, Extension } from '@codemirror/state'; | ||
/** | ||
* String value to assign to the aria-label attribute of the editor | ||
* String value to assign to the aria-label attribute of the editor. | ||
*/ | ||
ariaLabel?: string; | ||
/** | ||
* Whether keybindings for inserting indents with the Tab key should be disabled. | ||
* | ||
* true will not create keybindings for inserting indents. | ||
* false will create keybindings for inserting indents. | ||
* | ||
* @default false | ||
*/ | ||
moveFocusOnTab?: boolean; | ||
} | ||
@@ -160,3 +169,2 @@ type CypherEditorState = { | ||
private schemaRef; | ||
private latestDispatchedValue; | ||
/** | ||
@@ -163,0 +171,0 @@ * Focus the editor |
@@ -7,2 +7,3 @@ import { jsx as _jsx } from "react/jsx-runtime"; | ||
import { Component, createRef } from 'react'; | ||
import { DEBOUNCE_TIME } from './constants'; | ||
import { replaceHistory, replMode as historyNavigation, } from './historyNavigation'; | ||
@@ -91,3 +92,2 @@ import { cypher } from './lang-cypher/langCypher'; | ||
schemaRef = createRef(); | ||
latestDispatchedValue; | ||
/** | ||
@@ -134,8 +134,8 @@ * Focus the editor | ||
newLineOnEnter: false, | ||
moveFocusOnTab: false, | ||
}; | ||
debouncedOnChange = this.props.onChange | ||
? debounce(((value, viewUpdate) => { | ||
this.latestDispatchedValue = value; | ||
this.props.onChange(value, viewUpdate); | ||
}), 200) | ||
}), DEBOUNCE_TIME) | ||
: undefined; | ||
@@ -172,11 +172,10 @@ componentDidMount() { | ||
: []; | ||
this.latestDispatchedValue = this.props.value; | ||
this.editorState.current = EditorState.create({ | ||
extensions: [ | ||
keyBindingCompartment.of(keymap.of([ | ||
...executeKeybinding(onExecute, newLineOnEnter, () => this.debouncedOnChange.flush()), | ||
...executeKeybinding(onExecute, newLineOnEnter, () => this.debouncedOnChange?.flush()), | ||
...extraKeybindings, | ||
])), | ||
historyNavigation(this.props), | ||
basicNeo4jSetup(), | ||
basicNeo4jSetup(this.props), | ||
themeCompartment.of(themeExtension), | ||
@@ -222,5 +221,6 @@ changeListener, | ||
const currentCmValue = this.editorView.current.state?.doc.toString() ?? ''; | ||
if (this.props.value !== undefined && | ||
this.props.value !== this.latestDispatchedValue) { | ||
this.debouncedOnChange?.cancel(); | ||
if (this.props.value !== undefined && // If the component becomes uncontolled, we just leave the value as is | ||
this.props.value !== prevProps.value && // The value prop has changed, we need to update the editor | ||
this.props.value !== currentCmValue // No need to dispatch an update if the value is the same | ||
) { | ||
this.editorView.current.dispatch({ | ||
@@ -266,3 +266,3 @@ changes: { | ||
effects: keyBindingCompartment.reconfigure(keymap.of([ | ||
...executeKeybinding(this.props.onExecute, this.props.newLineOnEnter, () => this.debouncedOnChange.flush()), | ||
...executeKeybinding(this.props.onExecute, this.props.newLineOnEnter, () => this.debouncedOnChange?.flush()), | ||
...this.props.extraKeybindings, | ||
@@ -269,0 +269,0 @@ ])), |
@@ -41,2 +41,20 @@ import { jsx as _jsx } from "react/jsx-runtime"; | ||
}); | ||
test('get completions when typing in controlled component', async ({ mount, page, }) => { | ||
let value = ''; | ||
const onChange = (val) => { | ||
value = val; | ||
void component.update(_jsx(CypherEditor, { value: val, onChange: onChange })); | ||
}; | ||
const component = await mount(_jsx(CypherEditor, { value: value, onChange: onChange })); | ||
const textField = page.getByRole('textbox'); | ||
await textField.fill('RETU'); | ||
await page.waitForTimeout(500); // wait for debounce | ||
await expect(page.locator('.cm-tooltip-autocomplete').getByText('RETURN')).toBeVisible(); | ||
// We need to wait for the editor to realise there is a completion open | ||
// so that it does not just indent with tab key | ||
await page.waitForTimeout(500); | ||
await textField.press('Tab'); | ||
await expect(page.locator('.cm-tooltip-autocomplete')).not.toBeVisible(); | ||
await expect(component).toContainText('RETURN'); | ||
}); | ||
test('can complete labels', async ({ mount, page }) => { | ||
@@ -43,0 +61,0 @@ const component = await mount(_jsx(CypherEditor, { schema: { |
import { jsx as _jsx } from "react/jsx-runtime"; | ||
import { expect, test } from '@playwright/experimental-ct-react'; | ||
import { DEBOUNCE_TIME } from '../constants'; | ||
import { CypherEditor } from '../CypherEditor'; | ||
import { CypherEditorPage } from './e2eUtils'; | ||
const DEBOUNCE_TIMER = 200; | ||
test('external updates should override debounced updates', async ({ mount, page, }) => { | ||
const DEBOUNCE_TIME_WITH_MARGIN = DEBOUNCE_TIME + 100; | ||
// value updates from outside onExecute are overwritten by pending updates | ||
test.fail('external updates should override debounced updates', async ({ mount, page }) => { | ||
const editorPage = new CypherEditorPage(page); | ||
@@ -16,3 +18,3 @@ let value = ''; | ||
onChange('foo'); | ||
await page.waitForTimeout(DEBOUNCE_TIMER); | ||
await page.waitForTimeout(DEBOUNCE_TIME_WITH_MARGIN); | ||
await expect(component).toContainText('foo'); | ||
@@ -33,4 +35,4 @@ }); | ||
await editorPage.getEditor().pressSequentially('RETURN 1'); | ||
await editorPage.getEditor().press('Control+Enter'); | ||
await page.waitForTimeout(DEBOUNCE_TIMER); | ||
await editorPage.getEditor().press('Enter'); | ||
await page.waitForTimeout(DEBOUNCE_TIME_WITH_MARGIN); | ||
await expect(component).not.toContainText('RETURN 1'); | ||
@@ -40,4 +42,4 @@ await editorPage.getEditor().pressSequentially('RETURN 1'); | ||
await editorPage.getEditor().pressSequentially('RETURN 1'); | ||
await editorPage.getEditor().press('Control+Enter'); | ||
await page.waitForTimeout(DEBOUNCE_TIMER); | ||
await editorPage.getEditor().press('Enter'); | ||
await page.waitForTimeout(DEBOUNCE_TIME_WITH_MARGIN); | ||
await expect(component).not.toContainText('RETURN 1'); | ||
@@ -59,6 +61,6 @@ }); | ||
await editorPage.getEditor().fill('RETURN 1'); | ||
await editorPage.getEditor().press('Control+Enter'); | ||
await editorPage.getEditor().press('Enter'); | ||
await editorPage.getEditor().fill('RETURN 2'); | ||
await editorPage.getEditor().press('Control+Enter'); | ||
await page.waitForTimeout(DEBOUNCE_TIMER); | ||
await editorPage.getEditor().press('Enter'); | ||
await page.waitForTimeout(DEBOUNCE_TIME_WITH_MARGIN); | ||
await expect(component).toContainText('RETURN 2'); | ||
@@ -65,0 +67,0 @@ expect(executedCommand).toBe('RETURN 2'); |
@@ -24,3 +24,5 @@ import { jsx as _jsx } from "react/jsx-runtime"; | ||
const textField = page.getByRole('textbox'); | ||
await textField.fill(''); | ||
await textField.fill('RETURN 12'); | ||
await expect(textField).toHaveText('RETURN 12'); | ||
// editor update is debounced, retry wait for debounced | ||
@@ -30,6 +32,2 @@ await expect(() => { | ||
}).toPass({ intervals: [300, 300, 1000] }); | ||
await page.keyboard.type('34'); | ||
await expect(() => { | ||
expect(editorValueCopy).toBe('RETURN 12'); | ||
}).toPass({ intervals: [300, 300, 1000] }); | ||
}); | ||
@@ -36,0 +34,0 @@ test('can complete RETURN', async ({ page, mount }) => { |
@@ -1,4 +0,4 @@ | ||
export { CypherParser, _internalFeatureFlags, } from '@neo4j-cypher/language-support'; | ||
export * as LanguageSupport from '@neo4j-cypher/language-support'; | ||
export { CypherEditor } from './CypherEditor'; | ||
export { cypher } from './lang-cypher/langCypher'; | ||
export { darkThemeConstants, lightThemeConstants } from './themes'; |
@@ -1,2 +0,2 @@ | ||
export { CypherParser, _internalFeatureFlags, } from '@neo4j-cypher/language-support'; | ||
export * as LanguageSupport from '@neo4j-cypher/language-support'; | ||
export { CypherEditor } from './CypherEditor'; | ||
@@ -3,0 +3,0 @@ export { cypher } from './lang-cypher/langCypher'; |
import { tags } from '@lezer/highlight'; | ||
import { applySyntaxColouring, CypherTokenType, } from '@neo4j-cypher/language-support'; | ||
import { expect, test } from 'vitest'; | ||
import { tokenTypeToStyleTag } from './constants'; | ||
@@ -4,0 +5,0 @@ const cypherQueryWithAllTokenTypes = `MATCH (variable :Label)-[:REL_TYPE]->() |
@@ -5,5 +5,3 @@ import { linter } from '@codemirror/lint'; | ||
import workerpool from 'workerpool'; | ||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment | ||
// @ts-ignore ignore: https://v3.vitejs.dev/guide/features.html#import-with-query-suffixes | ||
import WorkerURL from './lintWorker?url&worker'; | ||
import WorkerURL from './lintWorker?worker&url'; | ||
const pool = workerpool.pool(WorkerURL, { | ||
@@ -41,4 +39,3 @@ minWorkers: 2, | ||
const statements = parse.statementsParsing; | ||
const anySyntacticError = statements.filter((statement) => statement.diagnostics.length !== 0) | ||
.length > 0; | ||
const anySyntacticError = statements.some((statement) => statement.syntaxErrors.length !== 0); | ||
if (anySyntacticError) { | ||
@@ -45,0 +42,0 @@ return []; |
import { tokens } from '@neo4j-ndl/base'; | ||
import { expect, test } from 'vitest'; | ||
import { tokens as tokensCopy } from './ndlTokensCopy'; | ||
@@ -3,0 +4,0 @@ /* |
import { Extension } from '@codemirror/state'; | ||
export declare const basicNeo4jSetup: () => Extension[]; | ||
type SetupProps = { | ||
moveFocusOnTab?: boolean; | ||
}; | ||
export declare const basicNeo4jSetup: ({ moveFocusOnTab, }: SetupProps) => Extension[]; | ||
export {}; |
@@ -27,3 +27,3 @@ import { acceptCompletion, autocompletion, clearSnippet, closeBrackets, closeBracketsKeymap, closeCompletion, completionKeymap, nextSnippetField, prevSnippetField, snippetKeymap, } from '@codemirror/autocomplete'; | ||
}; | ||
export const basicNeo4jSetup = () => { | ||
export const basicNeo4jSetup = ({ moveFocusOnTab = false, }) => { | ||
const keymaps = [ | ||
@@ -37,18 +37,18 @@ closeBracketsKeymap, | ||
lintKeymap, | ||
{ | ||
].flat(); | ||
if (!moveFocusOnTab) { | ||
keymaps.push({ | ||
key: 'Tab', | ||
preventDefault: true, | ||
run: acceptCompletion, | ||
}, | ||
{ | ||
}, { | ||
key: 'Tab', | ||
preventDefault: true, | ||
run: insertTab, | ||
}, | ||
{ | ||
}, { | ||
key: 'Shift-Tab', | ||
preventDefault: true, | ||
run: indentLess, | ||
}, | ||
].flat(); | ||
}); | ||
} | ||
const extensions = []; | ||
@@ -55,0 +55,0 @@ extensions.push(highlightSpecialChars()); |
@@ -20,3 +20,3 @@ { | ||
], | ||
"version": "2.0.0-canary-9fcb804", | ||
"version": "2.0.0-canary-9fd0ae1", | ||
"main": "./dist/index.js", | ||
@@ -29,3 +29,3 @@ "types": "./dist/index.d.ts", | ||
"clean": "rm -rf dist", | ||
"test": "jest", | ||
"test": "vitest run", | ||
"test:e2e": "playwright test -c playwright-ct.config.ts", | ||
@@ -53,6 +53,6 @@ "test:e2e-ui": "playwright test -c playwright-ct.config.ts --ui", | ||
"@codemirror/state": "^6.4.1", | ||
"@codemirror/view": "^6.28.4", | ||
"@codemirror/view": "^6.29.1", | ||
"@lezer/common": "^1.0.2", | ||
"@lezer/highlight": "^1.1.3", | ||
"@neo4j-cypher/language-support": "2.0.0-canary-9fcb804", | ||
"@neo4j-cypher/language-support": "2.0.0-canary-9fd0ae1", | ||
"@types/prismjs": "^1.26.3", | ||
@@ -69,13 +69,15 @@ "@types/workerpool": "^6.4.7", | ||
"@neo4j-ndl/base": "^1.10.1", | ||
"@playwright/experimental-ct-react": "^1.39.0", | ||
"@playwright/test": "^1.36.2", | ||
"@playwright/experimental-ct-react": "^1.45.3", | ||
"@playwright/test": "^1.45.3", | ||
"@types/lodash.debounce": "^4.0.9", | ||
"@types/react": "^18.0.28", | ||
"@vitejs/plugin-react": "^3.1.0", | ||
"@vitejs/plugin-react": "^4.3.1", | ||
"concurrently": "^8.2.1", | ||
"esbuild": "^0.19.4", | ||
"jsdom": "^24.1.1", | ||
"lodash": "^4.17.21", | ||
"playwright": "^1.36.2", | ||
"playwright": "^1.45.3", | ||
"react": "^18.2.0", | ||
"typescript": "^4.9.5" | ||
"typescript": "^4.9.5", | ||
"vite": "^5.3.5" | ||
}, | ||
@@ -82,0 +84,0 @@ "peerDependencies": { |
@@ -1,7 +0,4 @@ | ||
export { | ||
CypherParser, | ||
_internalFeatureFlags, | ||
} from '@neo4j-cypher/language-support'; | ||
export * as LanguageSupport from '@neo4j-cypher/language-support'; | ||
export { CypherEditor } from './CypherEditor'; | ||
export { cypher } from './lang-cypher/langCypher'; | ||
export { darkThemeConstants, lightThemeConstants } from './themes'; |
@@ -6,2 +6,3 @@ import { tags } from '@lezer/highlight'; | ||
} from '@neo4j-cypher/language-support'; | ||
import { expect, test } from 'vitest'; | ||
import { tokenTypeToStyleTag } from './constants'; | ||
@@ -8,0 +9,0 @@ |
@@ -8,8 +8,5 @@ import { Diagnostic, linter } from '@codemirror/lint'; | ||
import type { LinterTask, LintWorker } from './lintWorker'; | ||
import WorkerURL from './lintWorker?worker&url'; | ||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment | ||
// @ts-ignore ignore: https://v3.vitejs.dev/guide/features.html#import-with-query-suffixes | ||
import WorkerURL from './lintWorker?url&worker'; | ||
const pool = workerpool.pool(WorkerURL as string, { | ||
const pool = workerpool.pool(WorkerURL, { | ||
minWorkers: 2, | ||
@@ -61,5 +58,5 @@ workerOpts: { type: 'module' }, | ||
const anySyntacticError = | ||
statements.filter((statement) => statement.diagnostics.length !== 0) | ||
.length > 0; | ||
const anySyntacticError = statements.some( | ||
(statement) => statement.syntaxErrors.length !== 0, | ||
); | ||
@@ -66,0 +63,0 @@ if (anySyntacticError) { |
import { tokens } from '@neo4j-ndl/base'; | ||
import { expect, test } from 'vitest'; | ||
import { tokens as tokensCopy } from './ndlTokensCopy'; | ||
@@ -3,0 +4,0 @@ |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
671838
138
8964
14
+ Added@neo4j-cypher/language-support@2.0.0-canary-9fd0ae1(transitive)
- Removed@neo4j-cypher/language-support@2.0.0-canary-9fcb804(transitive)
Updated@codemirror/view@^6.29.1