diff --git a/ivette/src/dome/renderer/text/richtext.tsx b/ivette/src/dome/renderer/text/richtext.tsx index 93d1934a6fc93e802cfcaa4e72795f5085e7b855..a78353fff8101d4ac008ff0e6e2cd065473562a2 100644 --- a/ivette/src/dome/renderer/text/richtext.tsx +++ b/ivette/src/dome/renderer/text/richtext.tsx @@ -439,9 +439,7 @@ Viewport.pack( /* --- Hovering Listener --- */ /* -------------------------------------------------------------------------- */ -export type HoverCallback = (pos: Position | null) => void; - -const OnHover = new Field<HoverCallback|null>(null); +export type MouseCallback = (pos: Position | null, evt: MouseEvent) => void; function getPosition(evt: MouseEvent, view: CM.EditorView): Position | null { @@ -461,15 +459,42 @@ function getPosition(evt: MouseEvent, view: CM.EditorView): Position | null return null; } -OnHover.pack( +class MouseCallbackField extends Field<MouseCallback|null> { + constructor() { + super(null); + this.callback = this.callback.bind(this); + } + + callback(evt: MouseEvent, view: CM.EditorView): boolean { + const fn = view.state.field(this.field); + if (fn) { + const pos = getPosition(evt, view); + if (fn) fn(pos, evt); + } + return false; + } +} + +const OnClick = new MouseCallbackField(); +const OnPopup = new MouseCallbackField(); +const OnHover = new MouseCallbackField(); +const OnDouble = new MouseCallbackField(); + +const MouseEvents : CS.Extension = [ + OnClick, + OnHover, + OnPopup, + OnDouble, CM.EditorView.domEventHandlers({ - mousemove: _.debounce( - (evt: MouseEvent, view: CM.EditorView) => { - const fn = view.state.field(OnHover.field); - if (fn !== null) fn(getPosition(evt, view)); - return false; - }, 10) -})); + click: (evt: MouseEvent, view: CM.EditorView) => { + OnClick.callback(evt, view); + if (evt.detail > 1) OnDouble.callback(evt, view); + return false; + }, + contextmenu: OnPopup.callback, + mousemove: _.debounce(OnHover.callback, 10), + }), +]; /* -------------------------------------------------------------------------- */ /* --- Decorations --- */ @@ -763,8 +788,8 @@ function createView(parent: Element): CM.EditorView { ReadOnly, OnChange, OnSelect, - OnHover, Viewport, + MouseEvents, Decorations, ]; const state = CS.EditorState.create({ extensions }); @@ -782,7 +807,10 @@ export interface TextViewProps { selection?: Range; onViewport?: SelectionCallback; onSelection?: SelectionCallback; - onHover?: HoverCallback; + onClick?: MouseCallback; + onPopup?: MouseCallback; + onHover?: MouseCallback; + onDoubleClick?: MouseCallback; decorations?: Decorations; lineNumbers?: boolean; showCurrentLine?: boolean; @@ -807,19 +835,25 @@ export function TextView(props: TextViewProps) : JSX.Element { // ---- Fields Props const { + onClick = null, + onPopup = null, onHover = null, onChange = null, readOnly = false, onViewport: onReview = null, onSelection: onSelect = null, + onDoubleClick: onDouble = null, lineNumbers: lines, showCurrentLine: active, } = props; + React.useEffect(() => OnClick.dispatch(view, onClick), [view, onClick]); + React.useEffect(() => OnPopup.dispatch(view, onPopup), [view, onPopup]); React.useEffect(() => OnHover.dispatch(view, onHover), [view, onHover]); - React.useEffect(() => ReadOnly.dispatch(view, readOnly), [view, readOnly]); + React.useEffect(() => OnDouble.dispatch(view, onDouble), [view, onDouble]); React.useEffect(() => OnChange.dispatch(view, onChange), [view, onChange]); React.useEffect(() => OnSelect.dispatch(view, onSelect), [view, onSelect]); React.useEffect(() => Viewport.dispatch(view, onReview), [view, onReview]); + React.useEffect(() => ReadOnly.dispatch(view, readOnly), [view, readOnly]); React.useEffect(() => ActiveLine.dispatch(view, active), [view, active]); React.useEffect(() => LineNumbers.dispatch(view, lines), [view, lines]); diff --git a/ivette/src/sandbox/text.tsx b/ivette/src/sandbox/text.tsx index 4da3fd984a0e46fdf79fcfb6cfd37473352de8df..6b588b943c30365dd6147c4809a18460bc61e25f 100644 --- a/ivette/src/sandbox/text.tsx +++ b/ivette/src/sandbox/text.tsx @@ -56,6 +56,7 @@ function UseText(): JSX.Element { const [s, onSelection] = React.useState(emptySelection); const [v, onViewport] = React.useState(emptySelection); const [h, onHover] = React.useState<Position | null>(null); + const [evt, setEvent] = React.useState(''); const proxy = React.useMemo(() => new TextProxy(), []); const buffer = React.useMemo(() => new TextBuffer(), []); const text = useProxy ? proxy : buffer; @@ -120,6 +121,32 @@ function UseText(): JSX.Element { return [...decorations, { line: h.line, className: 'hover' }]; }, [decorations, h]); + const clearEvent = React.useCallback(() => setEvent(''), []); + const triggerCancelEvent = Dome.useDebounced(clearEvent, 1000); + + const onClick = React.useCallback( + (pos: Position | null, evt: MouseEvent) => { + const name = + evt.altKey ? 'Alt-Click' : + evt.ctrlKey ? 'Ctrl-Click' : + evt.metaKey ? 'Meta-Click' : + 'Click'; + setEvent(`${name} ${pos ? pos.offset : 'null'}`); + triggerCancelEvent(); + }, [triggerCancelEvent]); + + const onPopup = React.useCallback( + (pos: Position | null) => { + setEvent(`Popup ${pos ? pos.offset : 'null'}`); + triggerCancelEvent(); + }, [triggerCancelEvent]); + + const onDoubleClick = React.useCallback( + (pos: Position | null) => { + setEvent(`Double-Click ${pos ? pos.offset : 'null'}`); + triggerCancelEvent(); + }, [triggerCancelEvent]); + return ( <> <ToolBar> @@ -184,17 +211,23 @@ function UseText(): JSX.Element { onChange={onChange} onSelection={onSelection} onHover={onHover} + onClick={onClick} + onPopup={onPopup} + onDoubleClick={onDoubleClick} onViewport={onViewport} decorations={allDecorations} lineNumbers={useLines} showCurrentLine={useCurrent} /> <ToolBar> - <Code label={`Offset ${s.offset}-${s.offset + s.length} / ${length}`} /> - <Code label={`Line ${s.fromLine}-${s.toLine} / ${lines}`} /> + <Code label={`Length ${length}`} /> + <Code label={`Lines ${lines}`} /> + <Code label={`Offset ${s.offset}-${s.offset + s.length}`} /> + <Code label={`Line ${s.fromLine}-${s.toLine}`} /> <Code label={`View ${v.fromLine}-${v.toLine}`} /> <Code label={`Hover ${h ? h.offset : '-'}:${h ? h.line : '-'}`} /> <Filler /> + <Code>{evt}</Code> <Code>{`Changes: ${changes}`}</Code> </ToolBar> </>