@replit/codemirror-interact
Advanced tools
Comparing version 6.0.3 to 6.0.4
import * as _codemirror_state from '@codemirror/state'; | ||
import { Facet } from '@codemirror/state'; | ||
import { Facet, StateField } from '@codemirror/state'; | ||
interface Target { | ||
pos: number; | ||
text: string; | ||
rule: InteractRule; | ||
} | ||
interface InteractRule { | ||
@@ -38,4 +43,4 @@ regexp: RegExp; | ||
} | ||
declare const interact: (cfg?: InteractConfig) => _codemirror_state.Extension[]; | ||
declare const interact: (cfg?: InteractConfig) => (_codemirror_state.Extension | StateField<Target | null>)[]; | ||
export { InteractRule, interact as default, interactModKey, interactRule }; |
@@ -1,7 +0,50 @@ | ||
import { Decoration, EditorView, ViewPlugin } from '@codemirror/view'; | ||
import { StateEffect, Facet, Compartment, Prec } from '@codemirror/state'; | ||
import { EditorView, Decoration, ViewPlugin } from '@codemirror/view'; | ||
import { StateField, MapMode, StateEffect, Facet } from '@codemirror/state'; | ||
// TODO: custom style | ||
const interactField = /*@__PURE__*/StateField.define({ | ||
create: () => null, | ||
update: (value, tr) => { | ||
for (const e of tr.effects) { | ||
if (e.is(setInteract)) { | ||
return e.value; | ||
} | ||
} | ||
if (!value) { | ||
return null; | ||
} | ||
if (!tr.changes.empty) { | ||
const newPos = tr.changes.mapPos(value.pos, -1, MapMode.TrackDel); | ||
const newEnd = tr.changes.mapPos(value.pos + value.text.length, -1, MapMode.TrackDel); | ||
if (newPos === null || newEnd === null) { | ||
return null; | ||
} | ||
// if the text doesn't match anymore, we'll just return null | ||
// rather than checking if the rule matches again | ||
if (tr.newDoc.sliceString(newPos, newEnd) !== value.text) { | ||
return null; | ||
} | ||
return Object.assign(Object.assign({}, value), { pos: newPos }); | ||
} | ||
return value; | ||
}, | ||
provide: (field) => [ | ||
EditorView.decorations.from(field, (target) => { | ||
if (!target) { | ||
return Decoration.none; | ||
} | ||
const from = target.pos; | ||
const to = target.pos + target.text.length; | ||
return Decoration.set(mark.range(from, to)); | ||
}), | ||
EditorView.contentAttributes.from(field, (target) => { | ||
if (!target || !target.rule.cursor) { | ||
return { style: '' }; | ||
} | ||
return { style: `cursor: ${target.rule.cursor}` }; | ||
}), | ||
] | ||
}); | ||
const setInteract = /*@__PURE__*/StateEffect.define(); | ||
const mark = /*@__PURE__*/Decoration.mark({ class: 'cm-interact' }); | ||
const setInteract = /*@__PURE__*/StateEffect.define(); | ||
const interactTheme = /*@__PURE__*/EditorView.theme({ | ||
@@ -37,14 +80,7 @@ '.cm-interact': { | ||
}); | ||
const setStyle = (style = '') => EditorView.contentAttributes.of({ style }); | ||
const normalCursor = /*@__PURE__*/setStyle(); | ||
const cursorCompartment = /*@__PURE__*/new Compartment(); | ||
const cursorRule = /*@__PURE__*/Prec.highest(/*@__PURE__*/cursorCompartment.of(normalCursor)); | ||
const clearCursor = () => cursorCompartment.reconfigure(normalCursor); | ||
const setCursor = (cursor) => cursor ? [cursorCompartment.reconfigure(setStyle(`cursor: ${cursor}`))] : []; | ||
const interactViewPlugin = /*@__PURE__*/ViewPlugin.define((view) => ({ | ||
dragging: null, | ||
hovering: null, | ||
target: null, | ||
dragging: false, | ||
mouseX: 0, | ||
mouseY: 0, | ||
deco: Decoration.none, | ||
// Get current match under cursor from all rules | ||
@@ -85,2 +121,3 @@ getMatch() { | ||
view.dispatch({ | ||
effects: setInteract.of(Object.assign(Object.assign({}, target), { text })), | ||
changes: { | ||
@@ -92,16 +129,8 @@ from: target.pos, | ||
}); | ||
target.text = text; | ||
}; | ||
}, | ||
// highlight a target (e.g. currently dragging or hovering) | ||
highlight(target) { | ||
view.dispatch({ | ||
effects: [setInteract.of(target), ...setCursor(target.rule.cursor)], | ||
}); | ||
setTarget(target) { | ||
this.target = target; | ||
view.dispatch({ effects: setInteract.of(target) }); | ||
}, | ||
unhighlight() { | ||
view.dispatch({ | ||
effects: [setInteract.of(null), clearCursor()], | ||
}); | ||
}, | ||
isModKeyDown(e) { | ||
@@ -118,8 +147,11 @@ const modkey = view.state.facet(interactModKey); | ||
update(update) { | ||
for (const tr of update.transactions) { | ||
for (const e of tr.effects) { | ||
if (e.is(setInteract)) { | ||
const decos = e.value ? mark.range(e.value.pos, e.value.pos + e.value.text.length) : []; | ||
this.deco = Decoration.set(decos); | ||
} | ||
const target = update.state.field(interactField, false); | ||
// the field isn't mounted | ||
if (target === undefined) { | ||
return; | ||
} | ||
if (this.target !== target) { | ||
this.target = target; | ||
if (target === null) { | ||
this.dragging = false; | ||
} | ||
@@ -129,5 +161,4 @@ } | ||
}), { | ||
decorations: (v) => v.deco, | ||
eventHandlers: { | ||
mousedown(e, view) { | ||
mousedown(e, _view) { | ||
if (!this.isModKeyDown(e)) | ||
@@ -143,52 +174,49 @@ return; | ||
if (match.rule.onDrag) { | ||
this.dragging = { | ||
rule: match.rule, | ||
pos: match.pos, | ||
text: match.text, | ||
}; | ||
this.dragging = true; | ||
} | ||
}, | ||
mousemove(e, view) { | ||
mousemove(e, _view) { | ||
this.mouseX = e.clientX; | ||
this.mouseY = e.clientY; | ||
if (!this.isModKeyDown(e)) | ||
if (!this.isModKeyDown(e)) { | ||
if (this.target) { | ||
this.setTarget(null); | ||
} | ||
return; | ||
if (this.dragging) { | ||
this.highlight(this.dragging); | ||
if (this.dragging.rule.onDrag) { | ||
this.dragging.rule.onDrag(this.dragging.text, this.updateText(this.dragging), e); | ||
} | ||
if (this.target && this.dragging) { | ||
if (this.target.rule.onDrag) { | ||
this.target.rule.onDrag(this.target.text, this.updateText(this.target), e); | ||
} | ||
} | ||
else { | ||
this.hovering = this.getMatch(); | ||
if (this.hovering) { | ||
this.highlight(this.hovering); | ||
} | ||
else { | ||
this.unhighlight(); | ||
} | ||
this.setTarget(this.getMatch()); | ||
} | ||
}, | ||
mouseup(e, view) { | ||
this.dragging = null; | ||
if (!this.hovering) { | ||
this.unhighlight(); | ||
mouseup(e, _view) { | ||
this.dragging = false; | ||
if (this.target && !this.isModKeyDown(e)) { | ||
this.setTarget(null); | ||
} | ||
if (this.isModKeyDown(e)) { | ||
this.setTarget(this.getMatch()); | ||
} | ||
}, | ||
mouseleave(e, view) { | ||
this.hovering = null; | ||
this.dragging = null; | ||
this.unhighlight(); | ||
mouseleave(_e, _view) { | ||
this.dragging = false; | ||
if (this.target) { | ||
this.setTarget(null); | ||
} | ||
}, | ||
keydown(e, view) { | ||
if (!this.isModKeyDown(e)) | ||
return; | ||
this.hovering = this.getMatch(); | ||
if (this.hovering) { | ||
this.highlight(this.hovering); | ||
// TODO: fix these keybindings | ||
// these currently don't do anything because CodeMirror's keybinding | ||
// system prevents these events from firing. | ||
keydown(e, _view) { | ||
if (!this.target && this.isModKeyDown(e)) { | ||
this.setTarget(this.getMatch()); | ||
} | ||
}, | ||
keyup(e, view) { | ||
if (!this.isModKeyDown(e)) { | ||
this.unhighlight(); | ||
keyup(e, _view) { | ||
if (this.target && !this.isModKeyDown(e)) { | ||
this.setTarget(null); | ||
} | ||
@@ -201,6 +229,6 @@ }, | ||
return [ | ||
interactField, | ||
interactTheme, | ||
interactViewPlugin, | ||
interactModKey.of((_a = cfg.key) !== null && _a !== void 0 ? _a : "alt"), | ||
cursorRule, | ||
((_b = cfg.rules) !== null && _b !== void 0 ? _b : []).map((r) => interactRule.of(r)), | ||
@@ -207,0 +235,0 @@ ]; |
{ | ||
"name": "@replit/codemirror-interact", | ||
"description": "Interact with custom values", | ||
"version": "6.0.3", | ||
"version": "6.0.4", | ||
"license": "MIT", | ||
@@ -6,0 +6,0 @@ "author": { |
@@ -7,3 +7,2 @@ // TODO: custom style | ||
PluginValue, | ||
DecorationSet, | ||
Decoration, | ||
@@ -13,5 +12,5 @@ } from '@codemirror/view' | ||
StateEffect, | ||
StateField, | ||
Facet, | ||
Prec, | ||
Compartment, | ||
MapMode, | ||
} from '@codemirror/state' | ||
@@ -33,5 +32,60 @@ | ||
const mark = Decoration.mark({ class: 'cm-interact'}); | ||
const interactField = StateField.define<Target | null>({ | ||
create: () => null, | ||
update: (value, tr) => { | ||
for (const e of tr.effects) { | ||
if (e.is(setInteract)) { | ||
return e.value; | ||
} | ||
} | ||
if (!value) { | ||
return null | ||
} | ||
if (!tr.changes.empty) { | ||
const newPos = tr.changes.mapPos(value.pos, -1, MapMode.TrackDel) | ||
const newEnd = tr.changes.mapPos(value.pos + value.text.length, -1, MapMode.TrackDel) | ||
if (newPos === null || newEnd === null) { | ||
return null | ||
} | ||
// if the text doesn't match anymore, we'll just return null | ||
// rather than checking if the rule matches again | ||
if (tr.newDoc.sliceString(newPos, newEnd) !== value.text) { | ||
return null | ||
} | ||
return { ...value, pos: newPos } | ||
} | ||
return value | ||
}, | ||
provide: (field) => [ | ||
EditorView.decorations.from(field, (target) => { | ||
if (!target) { | ||
return Decoration.none | ||
} | ||
const from = target.pos; | ||
const to = target.pos + target.text.length; | ||
return Decoration.set(mark.range(from, to)) | ||
}), | ||
EditorView.contentAttributes.from(field, (target) => { | ||
if (!target || !target.rule.cursor) { | ||
return { style: '' } | ||
} | ||
return { style: `cursor: ${target.rule.cursor}` } | ||
}), | ||
] | ||
}) | ||
const setInteract = StateEffect.define<Target | null>(); | ||
const mark = Decoration.mark({ class: 'cm-interact' }); | ||
const interactTheme = EditorView.theme({ | ||
@@ -70,24 +124,10 @@ '.cm-interact': { | ||
const setStyle = (style = '') => | ||
EditorView.contentAttributes.of({ style }); | ||
const normalCursor = setStyle(); | ||
const cursorCompartment = new Compartment(); | ||
const cursorRule = Prec.highest(cursorCompartment.of(normalCursor)); | ||
const clearCursor = () => cursorCompartment.reconfigure(normalCursor); | ||
const setCursor = (cursor?: string) => | ||
cursor ? [cursorCompartment.reconfigure(setStyle(`cursor: ${cursor}`))] : []; | ||
interface ViewState extends PluginValue { | ||
dragging: Target | null, | ||
hovering: Target | null, | ||
target: Target | null, | ||
dragging: boolean, | ||
mouseX: number, | ||
mouseY: number, | ||
deco: DecorationSet, | ||
getMatch(): Target | null, | ||
updateText(target: Target): (text: string) => void, | ||
highlight(target: Target): void, | ||
unhighlight(): void, | ||
setTarget(target: Target | null): void, | ||
isModKeyDown(e: KeyboardEvent | MouseEvent): boolean, | ||
@@ -98,7 +138,6 @@ } | ||
dragging: null, | ||
hovering: null, | ||
target: null, | ||
dragging: false, | ||
mouseX: 0, | ||
mouseY: 0, | ||
deco: Decoration.none, | ||
@@ -135,3 +174,2 @@ // Get current match under cursor from all rules | ||
return match; | ||
}, | ||
@@ -142,2 +180,3 @@ | ||
view.dispatch({ | ||
effects: setInteract.of({ ...target, text }), | ||
changes: { | ||
@@ -149,21 +188,13 @@ from: target.pos, | ||
}); | ||
target.text = text; | ||
}; | ||
}, | ||
// highlight a target (e.g. currently dragging or hovering) | ||
highlight(target) { | ||
view.dispatch({ | ||
effects: [setInteract.of(target), ...setCursor(target.rule.cursor)], | ||
}); | ||
setTarget(target) { | ||
this.target = target; | ||
view.dispatch({ effects: setInteract.of(target) }); | ||
}, | ||
unhighlight() { | ||
view.dispatch({ | ||
effects: [setInteract.of(null), clearCursor()], | ||
}); | ||
}, | ||
isModKeyDown(e) { | ||
const modkey = view.state.facet(interactModKey); | ||
switch (modkey) { | ||
@@ -175,2 +206,3 @@ case "alt": return e.altKey; | ||
} | ||
throw new Error(`Invalid mod key: ${modkey}`) | ||
@@ -180,11 +212,13 @@ }, | ||
update(update) { | ||
for (const tr of update.transactions) { | ||
for (const e of tr.effects) { | ||
if (e.is(setInteract)) { | ||
const decos = e.value ? mark.range( | ||
e.value.pos, | ||
e.value.pos + e.value.text.length | ||
) : []; | ||
this.deco = Decoration.set(decos); | ||
} | ||
const target = update.state.field(interactField, false) | ||
// the field isn't mounted | ||
if (target === undefined) { | ||
return | ||
} | ||
if (this.target !== target) { | ||
this.target = target | ||
if (target === null) { | ||
this.dragging = false | ||
} | ||
@@ -195,12 +229,9 @@ } | ||
}), { | ||
decorations: (v) => v.deco, | ||
eventHandlers: { | ||
mousedown(e, _view) { | ||
if (!this.isModKeyDown(e)) return; | ||
mousedown(e, view) { | ||
if (!this.isModKeyDown(e)) return; | ||
const match = this.getMatch(); | ||
if (!match) return; | ||
e.preventDefault(); | ||
@@ -213,30 +244,24 @@ | ||
if (match.rule.onDrag) { | ||
this.dragging = { | ||
rule: match.rule, | ||
pos: match.pos, | ||
text: match.text, | ||
}; | ||
this.dragging = true | ||
} | ||
}, | ||
mousemove(e, view) { | ||
mousemove(e, _view) { | ||
this.mouseX = e.clientX; | ||
this.mouseY = e.clientY; | ||
if (!this.isModKeyDown(e)) return; | ||
if (!this.isModKeyDown(e)) { | ||
if (this.target) { | ||
this.setTarget(null); | ||
} | ||
if (this.dragging) { | ||
this.highlight(this.dragging); | ||
if (this.dragging.rule.onDrag) { | ||
this.dragging.rule.onDrag(this.dragging.text, this.updateText(this.dragging), e); | ||
return | ||
} | ||
if (this.target && this.dragging) { | ||
if (this.target.rule.onDrag) { | ||
this.target.rule.onDrag(this.target.text, this.updateText(this.target), e); | ||
} | ||
} else { | ||
this.hovering = this.getMatch(); | ||
if (this.hovering) { | ||
this.highlight(this.hovering); | ||
} else { | ||
this.unhighlight(); | ||
} | ||
this.setTarget(this.getMatch()); | ||
} | ||
@@ -246,26 +271,34 @@ | ||
mouseup(e, view) { | ||
this.dragging = null; | ||
if (!this.hovering) { | ||
this.unhighlight(); | ||
mouseup(e, _view) { | ||
this.dragging = false | ||
if (this.target && !this.isModKeyDown(e)) { | ||
this.setTarget(null) | ||
} | ||
if (this.isModKeyDown(e)) { | ||
this.setTarget(this.getMatch()); | ||
} | ||
}, | ||
mouseleave(e, view) { | ||
this.hovering = null; | ||
this.dragging = null; | ||
this.unhighlight(); | ||
mouseleave(_e, _view) { | ||
this.dragging = false; | ||
if (this.target) { | ||
this.setTarget(null) | ||
} | ||
}, | ||
keydown(e, view) { | ||
if (!this.isModKeyDown(e)) return; | ||
this.hovering = this.getMatch(); | ||
if (this.hovering) { | ||
this.highlight(this.hovering); | ||
// TODO: fix these keybindings | ||
// these currently don't do anything because CodeMirror's keybinding | ||
// system prevents these events from firing. | ||
keydown(e, _view) { | ||
if (!this.target && this.isModKeyDown(e)) { | ||
this.setTarget(this.getMatch()); | ||
} | ||
}, | ||
keyup(e, view) { | ||
if (!this.isModKeyDown(e)) { | ||
this.unhighlight(); | ||
keyup(e, _view) { | ||
if (this.target && !this.isModKeyDown(e)) { | ||
this.setTarget(null) | ||
} | ||
@@ -289,6 +322,6 @@ }, | ||
const interact = (cfg: InteractConfig = {}) => [ | ||
interactField, | ||
interactTheme, | ||
interactViewPlugin, | ||
interactModKey.of(cfg.key ?? "alt"), | ||
cursorRule, | ||
(cfg.rules ?? []).map((r) => interactRule.of(r)), | ||
@@ -295,0 +328,0 @@ ]; |
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
26355
766