@prosekit/web 0.7.3 → 0.7.5
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/{get-default-state-CIEy7xrl.js → get-default-state-BzBimBWi.js} +2 -1
- package/dist/get-default-state-BzBimBWi.js.map +1 -0
- package/dist/{get-safe-editor-view-DENm4avv.js → get-safe-editor-view-Dt9Amrcn.js} +2 -1
- package/dist/get-safe-editor-view-Dt9Amrcn.js.map +1 -0
- package/dist/{inject-style-D5jj7cme.js → inject-style-Dm6W58W3.js} +6 -9
- package/dist/inject-style-Dm6W58W3.js.map +1 -0
- package/dist/prosekit-web-autocomplete.d.ts +2 -1
- package/dist/prosekit-web-autocomplete.d.ts.map +1 -0
- package/dist/prosekit-web-autocomplete.js +9 -13
- package/dist/prosekit-web-autocomplete.js.map +1 -0
- package/dist/prosekit-web-block-handle.d.ts +2 -1
- package/dist/prosekit-web-block-handle.d.ts.map +1 -0
- package/dist/prosekit-web-block-handle.js +31 -31
- package/dist/prosekit-web-block-handle.js.map +1 -0
- package/dist/prosekit-web-drop-indicator.d.ts +2 -1
- package/dist/prosekit-web-drop-indicator.d.ts.map +1 -0
- package/dist/prosekit-web-drop-indicator.js +6 -6
- package/dist/prosekit-web-drop-indicator.js.map +1 -0
- package/dist/prosekit-web-inline-popover.d.ts +2 -1
- package/dist/prosekit-web-inline-popover.d.ts.map +1 -0
- package/dist/prosekit-web-inline-popover.js +8 -12
- package/dist/prosekit-web-inline-popover.js.map +1 -0
- package/dist/prosekit-web-popover.d.ts +2 -1
- package/dist/prosekit-web-popover.d.ts.map +1 -0
- package/dist/prosekit-web-popover.js +2 -1
- package/dist/prosekit-web-popover.js.map +1 -0
- package/dist/prosekit-web-resizable.d.ts +2 -1
- package/dist/prosekit-web-resizable.d.ts.map +1 -0
- package/dist/prosekit-web-resizable.js +5 -7
- package/dist/prosekit-web-resizable.js.map +1 -0
- package/dist/prosekit-web-table-handle.d.ts +2 -1
- package/dist/prosekit-web-table-handle.d.ts.map +1 -0
- package/dist/prosekit-web-table-handle.js +39 -72
- package/dist/prosekit-web-table-handle.js.map +1 -0
- package/dist/prosekit-web-tooltip.d.ts +2 -1
- package/dist/prosekit-web-tooltip.d.ts.map +1 -0
- package/dist/prosekit-web-tooltip.js +2 -1
- package/dist/prosekit-web-tooltip.js.map +1 -0
- package/dist/prosekit-web.js +1 -0
- package/dist/{use-editor-extension-Cc7ZG7uj.js → use-editor-extension-B2WuUfnd.js} +2 -1
- package/dist/use-editor-extension-B2WuUfnd.js.map +1 -0
- package/dist/{use-scrolling-BNfsQs3S.js → use-scrolling-BOvyjDvH.js} +2 -1
- package/dist/use-scrolling-BOvyjDvH.js.map +1 -0
- package/package.json +21 -21
- package/src/components/autocomplete/autocomplete-empty/element.gen.ts +18 -0
- package/src/components/autocomplete/autocomplete-empty/setup.ts +6 -0
- package/src/components/autocomplete/autocomplete-empty/types.ts +16 -0
- package/src/components/autocomplete/autocomplete-item/element.gen.ts +18 -0
- package/src/components/autocomplete/autocomplete-item/setup.ts +38 -0
- package/src/components/autocomplete/autocomplete-item/types.ts +31 -0
- package/src/components/autocomplete/autocomplete-list/element.gen.ts +18 -0
- package/src/components/autocomplete/autocomplete-list/setup.ts +140 -0
- package/src/components/autocomplete/autocomplete-list/types.ts +30 -0
- package/src/components/autocomplete/autocomplete-popover/element.gen.ts +18 -0
- package/src/components/autocomplete/autocomplete-popover/helpers.spec.ts +21 -0
- package/src/components/autocomplete/autocomplete-popover/helpers.ts +7 -0
- package/src/components/autocomplete/autocomplete-popover/setup.ts +185 -0
- package/src/components/autocomplete/autocomplete-popover/types.ts +103 -0
- package/src/components/autocomplete/context.ts +19 -0
- package/src/components/autocomplete/index.gen.ts +17 -0
- package/src/components/block-handle/block-handle-add/element.gen.ts +18 -0
- package/src/components/block-handle/block-handle-add/setup.ts +37 -0
- package/src/components/block-handle/block-handle-add/types.ts +26 -0
- package/src/components/block-handle/block-handle-draggable/element.gen.ts +18 -0
- package/src/components/block-handle/block-handle-draggable/set-drag-preview.ts +88 -0
- package/src/components/block-handle/block-handle-draggable/setup.ts +133 -0
- package/src/components/block-handle/block-handle-draggable/types.ts +26 -0
- package/src/components/block-handle/block-handle-popover/element.gen.ts +18 -0
- package/src/components/block-handle/block-handle-popover/pointer-move.ts +262 -0
- package/src/components/block-handle/block-handle-popover/setup.ts +88 -0
- package/src/components/block-handle/block-handle-popover/types.ts +84 -0
- package/src/components/block-handle/context.ts +34 -0
- package/src/components/block-handle/index.gen.ts +13 -0
- package/src/components/drop-indicator/drop-indicator/element.gen.ts +18 -0
- package/src/components/drop-indicator/drop-indicator/setup.ts +87 -0
- package/src/components/drop-indicator/drop-indicator/types.ts +34 -0
- package/src/components/drop-indicator/index.gen.ts +5 -0
- package/src/components/inline-popover/index.gen.ts +5 -0
- package/src/components/inline-popover/inline-popover/element.gen.ts +18 -0
- package/src/components/inline-popover/inline-popover/setup.ts +97 -0
- package/src/components/inline-popover/inline-popover/types.ts +115 -0
- package/src/components/inline-popover/inline-popover/virtual-selection-element.ts +75 -0
- package/src/components/popover/index.gen.ts +13 -0
- package/src/components/popover/popover-content/element.gen.ts +18 -0
- package/src/components/popover/popover-content/setup.ts +1 -0
- package/src/components/popover/popover-content/types.ts +12 -0
- package/src/components/popover/popover-root/element.gen.ts +18 -0
- package/src/components/popover/popover-root/setup.ts +1 -0
- package/src/components/popover/popover-root/types.ts +12 -0
- package/src/components/popover/popover-trigger/element.gen.ts +18 -0
- package/src/components/popover/popover-trigger/setup.ts +1 -0
- package/src/components/popover/popover-trigger/types.ts +12 -0
- package/src/components/resizable/context.ts +45 -0
- package/src/components/resizable/index.gen.ts +9 -0
- package/src/components/resizable/resizable-handle/calc-resize.spec.ts +280 -0
- package/src/components/resizable/resizable-handle/calc-resize.ts +121 -0
- package/src/components/resizable/resizable-handle/element.gen.ts +18 -0
- package/src/components/resizable/resizable-handle/setup.ts +112 -0
- package/src/components/resizable/resizable-handle/types.ts +32 -0
- package/src/components/resizable/resizable-root/element.gen.ts +18 -0
- package/src/components/resizable/resizable-root/setup.ts +93 -0
- package/src/components/resizable/resizable-root/types.ts +28 -0
- package/src/components/table-handle/context.ts +49 -0
- package/src/components/table-handle/dnd.ts +135 -0
- package/src/components/table-handle/hooks/use-drop.ts +94 -0
- package/src/components/table-handle/hooks/use-empty-image.ts +30 -0
- package/src/components/table-handle/index.gen.ts +37 -0
- package/src/components/table-handle/table-handle-column-root/element.gen.ts +18 -0
- package/src/components/table-handle/table-handle-column-root/setup.ts +71 -0
- package/src/components/table-handle/table-handle-column-root/types.ts +76 -0
- package/src/components/table-handle/table-handle-column-trigger/element.gen.ts +18 -0
- package/src/components/table-handle/table-handle-column-trigger/setup.ts +75 -0
- package/src/components/table-handle/table-handle-column-trigger/types.ts +23 -0
- package/src/components/table-handle/table-handle-drag-preview/element.gen.ts +18 -0
- package/src/components/table-handle/table-handle-drag-preview/render-preview.ts +80 -0
- package/src/components/table-handle/table-handle-drag-preview/setup.ts +67 -0
- package/src/components/table-handle/table-handle-drag-preview/types.ts +17 -0
- package/src/components/table-handle/table-handle-drag-preview/updater.ts +101 -0
- package/src/components/table-handle/table-handle-drop-indicator/calc-drag-over.ts +44 -0
- package/src/components/table-handle/table-handle-drop-indicator/element.gen.ts +18 -0
- package/src/components/table-handle/table-handle-drop-indicator/setup.ts +56 -0
- package/src/components/table-handle/table-handle-drop-indicator/types.ts +18 -0
- package/src/components/table-handle/table-handle-drop-indicator/updater.ts +110 -0
- package/src/components/table-handle/table-handle-popover-content/element.gen.ts +18 -0
- package/src/components/table-handle/table-handle-popover-content/setup.ts +90 -0
- package/src/components/table-handle/table-handle-popover-content/types.ts +40 -0
- package/src/components/table-handle/table-handle-popover-item/element.gen.ts +18 -0
- package/src/components/table-handle/table-handle-popover-item/setup.ts +23 -0
- package/src/components/table-handle/table-handle-popover-item/types.ts +24 -0
- package/src/components/table-handle/table-handle-root/element.gen.ts +18 -0
- package/src/components/table-handle/table-handle-root/setup.ts +93 -0
- package/src/components/table-handle/table-handle-root/types.ts +26 -0
- package/src/components/table-handle/table-handle-row-root/element.gen.ts +18 -0
- package/src/components/table-handle/table-handle-row-root/setup.ts +77 -0
- package/src/components/table-handle/table-handle-row-root/types.ts +75 -0
- package/src/components/table-handle/table-handle-row-trigger/element.gen.ts +18 -0
- package/src/components/table-handle/table-handle-row-trigger/setup.ts +74 -0
- package/src/components/table-handle/table-handle-row-trigger/types.ts +26 -0
- package/src/components/table-handle/utils.ts +107 -0
- package/src/components/tooltip/index.gen.ts +13 -0
- package/src/components/tooltip/tooltip-content/element.gen.ts +18 -0
- package/src/components/tooltip/tooltip-content/setup.ts +1 -0
- package/src/components/tooltip/tooltip-content/types.ts +12 -0
- package/src/components/tooltip/tooltip-root/element.gen.ts +18 -0
- package/src/components/tooltip/tooltip-root/setup.ts +1 -0
- package/src/components/tooltip/tooltip-root/types.ts +12 -0
- package/src/components/tooltip/tooltip-trigger/element.gen.ts +18 -0
- package/src/components/tooltip/tooltip-trigger/setup.ts +1 -0
- package/src/components/tooltip/tooltip-trigger/types.ts +12 -0
- package/src/hooks/use-editor-extension.ts +19 -0
- package/src/hooks/use-editor-focus-event.ts +23 -0
- package/src/hooks/use-editor-typing.ts +36 -0
- package/src/hooks/use-editor-update-event.ts +23 -0
- package/src/hooks/use-first-rendering.ts +20 -0
- package/src/hooks/use-keymap.ts +20 -0
- package/src/hooks/use-scrolling.ts +33 -0
- package/src/hooks/use-selecting.ts +63 -0
- package/src/index.ts +1 -0
- package/src/utils/assign-styles.ts +14 -0
- package/src/utils/clone-element.ts +110 -0
- package/src/utils/css-feature-detection.ts +9 -0
- package/src/utils/fade-color.ts +15 -0
- package/src/utils/get-box-element.ts +20 -0
- package/src/utils/get-client-rect.ts +35 -0
- package/src/utils/get-default-state.spec.ts +50 -0
- package/src/utils/get-default-state.ts +22 -0
- package/src/utils/get-effective-background-color.ts +21 -0
- package/src/utils/get-safe-editor-view.ts +10 -0
- package/src/utils/inject-style.ts +11 -0
- package/src/utils/is-finite-positive-number.ts +3 -0
- package/src/utils/max-z-index.ts +3 -0
- package/src/utils/throttle.ts +17 -0
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import {
|
|
2
|
+
useAttribute,
|
|
3
|
+
useEffect,
|
|
4
|
+
useEventListener,
|
|
5
|
+
type ConnectableElement,
|
|
6
|
+
type ReadonlySignal,
|
|
7
|
+
type SignalState,
|
|
8
|
+
} from '@aria-ui/core'
|
|
9
|
+
import { isHTMLElement } from '@ocavue/utils'
|
|
10
|
+
import type { Editor } from '@prosekit/core'
|
|
11
|
+
import type { ViewDragging } from '@prosekit/extensions/drop-indicator'
|
|
12
|
+
import {
|
|
13
|
+
Fragment,
|
|
14
|
+
Slice,
|
|
15
|
+
} from '@prosekit/pm/model'
|
|
16
|
+
import { NodeSelection } from '@prosekit/pm/state'
|
|
17
|
+
import type { EditorView } from '@prosekit/pm/view'
|
|
18
|
+
|
|
19
|
+
import { getBoxElement } from '../../../utils/get-box-element'
|
|
20
|
+
import { getSafeEditorView } from '../../../utils/get-safe-editor-view'
|
|
21
|
+
import {
|
|
22
|
+
blockPopoverContext,
|
|
23
|
+
draggingContext,
|
|
24
|
+
type BlockPopoverContext,
|
|
25
|
+
type HoverState,
|
|
26
|
+
} from '../context'
|
|
27
|
+
|
|
28
|
+
import { setDragPreview } from './set-drag-preview'
|
|
29
|
+
import type { BlockHandleDraggableProps } from './types'
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* @internal
|
|
33
|
+
*/
|
|
34
|
+
export function useBlockHandleDraggable(
|
|
35
|
+
host: ConnectableElement,
|
|
36
|
+
{ state }: { state: SignalState<BlockHandleDraggableProps> },
|
|
37
|
+
): void {
|
|
38
|
+
const context = blockPopoverContext.consume(host)
|
|
39
|
+
const dragging = draggingContext.consume(host)
|
|
40
|
+
|
|
41
|
+
useEffect(host, () => {
|
|
42
|
+
host.draggable = true
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
usePointerDownHandler(host, context, state.editor)
|
|
46
|
+
|
|
47
|
+
useEventListener(host, 'dragstart', (event) => {
|
|
48
|
+
dragging.set(true)
|
|
49
|
+
|
|
50
|
+
const view = getSafeEditorView(state.editor.get())
|
|
51
|
+
const hoverState = context.get()
|
|
52
|
+
|
|
53
|
+
if (view && hoverState) {
|
|
54
|
+
view.dom.classList.add('prosekit-dragging')
|
|
55
|
+
createDraggingPreview(view, hoverState, event)
|
|
56
|
+
setViewDragging(view, hoverState)
|
|
57
|
+
}
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
useEventListener(host, 'dragend', () => {
|
|
61
|
+
dragging.set(false)
|
|
62
|
+
|
|
63
|
+
const view = getSafeEditorView(state.editor.get())
|
|
64
|
+
if (view) {
|
|
65
|
+
view.dom.classList.remove('prosekit-dragging')
|
|
66
|
+
}
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
useAttribute(host, 'data-dragging', () => (dragging.get() ? '' : undefined))
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function usePointerDownHandler(
|
|
73
|
+
host: ConnectableElement,
|
|
74
|
+
context: ReadonlySignal<BlockPopoverContext>,
|
|
75
|
+
editor: ReadonlySignal<Editor | null>,
|
|
76
|
+
) {
|
|
77
|
+
useEventListener(host, 'pointerdown', () => {
|
|
78
|
+
const { pos } = context.get() ?? {}
|
|
79
|
+
const { view } = editor.get() ?? {}
|
|
80
|
+
|
|
81
|
+
if (pos == null || view == null) {
|
|
82
|
+
return
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
view.dispatch(
|
|
86
|
+
view.state.tr.setSelection(NodeSelection.create(view.state.doc, pos)),
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
// Clicking the handle will blur the editor, so we need to focus it again.
|
|
90
|
+
// We cannot call `event.preventDefault()` here to prevent the blur
|
|
91
|
+
// because it will prevent the drag event from firing.
|
|
92
|
+
requestAnimationFrame(() => {
|
|
93
|
+
view.focus()
|
|
94
|
+
})
|
|
95
|
+
})
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function createDraggingPreview(view: EditorView, hoverState: HoverState, event: DragEvent): void {
|
|
99
|
+
if (!event.dataTransfer) {
|
|
100
|
+
return
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const { pos } = hoverState
|
|
104
|
+
|
|
105
|
+
const element = view.nodeDOM(pos)
|
|
106
|
+
if (!element || !isHTMLElement(element)) {
|
|
107
|
+
return
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const boxElement = getBoxElement(element)
|
|
111
|
+
if (!boxElement || !isHTMLElement(boxElement)) {
|
|
112
|
+
return
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
event.dataTransfer.clearData()
|
|
116
|
+
event.dataTransfer.setData('text/html', boxElement.outerHTML)
|
|
117
|
+
event.dataTransfer.effectAllowed = 'copyMove'
|
|
118
|
+
setDragPreview(event, boxElement)
|
|
119
|
+
|
|
120
|
+
return
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function setViewDragging(view: EditorView, hoverState: HoverState): void {
|
|
124
|
+
const { node, pos } = hoverState
|
|
125
|
+
|
|
126
|
+
const dragging: ViewDragging = {
|
|
127
|
+
slice: new Slice(Fragment.from(node), 0, 0),
|
|
128
|
+
move: true,
|
|
129
|
+
node: NodeSelection.create(view.state.doc, pos),
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
view.dragging = dragging
|
|
133
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
EventDeclarations,
|
|
3
|
+
PropDeclarations,
|
|
4
|
+
} from '@aria-ui/core'
|
|
5
|
+
import type { Editor } from '@prosekit/core'
|
|
6
|
+
|
|
7
|
+
export interface BlockHandleDraggableProps {
|
|
8
|
+
/**
|
|
9
|
+
* The ProseKit editor instance.
|
|
10
|
+
*
|
|
11
|
+
* @default null
|
|
12
|
+
* @hidden
|
|
13
|
+
*/
|
|
14
|
+
editor: Editor | null
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/** @internal */
|
|
18
|
+
export const blockHandleDraggableProps: PropDeclarations<BlockHandleDraggableProps> = {
|
|
19
|
+
editor: { default: null },
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/** @internal */
|
|
23
|
+
export interface BlockHandleDraggableEvents {}
|
|
24
|
+
|
|
25
|
+
/** @internal */
|
|
26
|
+
export const blockHandleDraggableEvents: EventDeclarations<BlockHandleDraggableEvents> = {}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { defineCustomElement, registerCustomElement, type BaseElementConstructor } from "@aria-ui/core"
|
|
2
|
+
|
|
3
|
+
import { useBlockHandlePopover } from "./setup"
|
|
4
|
+
import { blockHandlePopoverEvents, blockHandlePopoverProps, type BlockHandlePopoverEvents, type BlockHandlePopoverProps } from "./types"
|
|
5
|
+
|
|
6
|
+
const BlockHandlePopoverElementBase: BaseElementConstructor<BlockHandlePopoverProps> = defineCustomElement<
|
|
7
|
+
BlockHandlePopoverProps,
|
|
8
|
+
BlockHandlePopoverEvents
|
|
9
|
+
>({
|
|
10
|
+
props: blockHandlePopoverProps,
|
|
11
|
+
events: blockHandlePopoverEvents,
|
|
12
|
+
setup: useBlockHandlePopover,
|
|
13
|
+
})
|
|
14
|
+
class BlockHandlePopoverElement extends BlockHandlePopoverElementBase {}
|
|
15
|
+
|
|
16
|
+
registerCustomElement('prosekit-block-handle-popover', BlockHandlePopoverElement)
|
|
17
|
+
|
|
18
|
+
export { BlockHandlePopoverElement }
|
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
import type { VirtualElement } from '@floating-ui/dom'
|
|
2
|
+
import {
|
|
3
|
+
isElement,
|
|
4
|
+
isHTMLElement,
|
|
5
|
+
isTextNode,
|
|
6
|
+
} from '@ocavue/utils'
|
|
7
|
+
import {
|
|
8
|
+
defineDOMEventHandler,
|
|
9
|
+
union,
|
|
10
|
+
type PlainExtension,
|
|
11
|
+
} from '@prosekit/core'
|
|
12
|
+
import type { ProseMirrorNode } from '@prosekit/pm/model'
|
|
13
|
+
import type { EditorView } from '@prosekit/pm/view'
|
|
14
|
+
|
|
15
|
+
import { getClientRect } from '../../../utils/get-client-rect'
|
|
16
|
+
import { throttle } from '../../../utils/throttle'
|
|
17
|
+
import type { HoverState } from '../context'
|
|
18
|
+
|
|
19
|
+
export type ElementHoverHandler = (
|
|
20
|
+
reference: VirtualElement | null,
|
|
21
|
+
hoverState: HoverState | null,
|
|
22
|
+
) => void
|
|
23
|
+
|
|
24
|
+
export function defineElementHoverHandler(handler: ElementHoverHandler): PlainExtension {
|
|
25
|
+
const handleElement = (
|
|
26
|
+
node: ProseMirrorNode,
|
|
27
|
+
pos: number,
|
|
28
|
+
element: HTMLElement,
|
|
29
|
+
parentElement?: Node | null,
|
|
30
|
+
) => {
|
|
31
|
+
const reference: VirtualElement = {
|
|
32
|
+
contextElement: element,
|
|
33
|
+
|
|
34
|
+
getBoundingClientRect: () => {
|
|
35
|
+
const rect = findFirstLineRect(parentElement, element)
|
|
36
|
+
return rect ? fulfillRect(rect) : fallbackRect
|
|
37
|
+
},
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
handler(reference, { node, pos })
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
let lastX = -1
|
|
44
|
+
let lastY = -1
|
|
45
|
+
let lastTime = -1
|
|
46
|
+
|
|
47
|
+
const handlePointerEvent = (view: EditorView, event: PointerEvent) => {
|
|
48
|
+
const { x, y } = event
|
|
49
|
+
|
|
50
|
+
// Simple performance optimization. If the pointer is not moving, we don't
|
|
51
|
+
// want to recalculate the block handle position within a short period of
|
|
52
|
+
// time window.
|
|
53
|
+
if (lastX === x && lastY === y) {
|
|
54
|
+
const now = Date.now()
|
|
55
|
+
if (now - lastTime < 100) {
|
|
56
|
+
return
|
|
57
|
+
}
|
|
58
|
+
lastTime = now
|
|
59
|
+
}
|
|
60
|
+
lastX = x
|
|
61
|
+
lastY = y
|
|
62
|
+
|
|
63
|
+
const block = findBlockByCoords(view, x, y)
|
|
64
|
+
if (!block) {
|
|
65
|
+
handler(null, null)
|
|
66
|
+
return
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const { node, pos } = block
|
|
70
|
+
const element = view.nodeDOM(pos)
|
|
71
|
+
if (!element || !isHTMLElement(element)) {
|
|
72
|
+
handler(null, null)
|
|
73
|
+
return
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// If `node` is the first child of another non-doc block node, for example a
|
|
77
|
+
// list node or a blockquote node, we want to put the block handle agains
|
|
78
|
+
// the parent node.
|
|
79
|
+
const $pos = view.state.doc.resolve(pos)
|
|
80
|
+
if ($pos.depth > 0 && $pos.index($pos.depth) === 0) {
|
|
81
|
+
const parentPos = $pos.before($pos.depth)
|
|
82
|
+
const parentNode = $pos.parent
|
|
83
|
+
const parentElement = view.nodeDOM(parentPos)
|
|
84
|
+
handleElement(parentNode, parentPos, element, parentElement)
|
|
85
|
+
} else {
|
|
86
|
+
handleElement(node, pos, element)
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
return union(
|
|
91
|
+
defineDOMEventHandler('pointermove', throttle(handlePointerEvent, 200)),
|
|
92
|
+
defineDOMEventHandler('pointerenter', handlePointerEvent),
|
|
93
|
+
defineDOMEventHandler('pointerout', handlePointerEvent),
|
|
94
|
+
defineDOMEventHandler('keypress', () => handler(null, null)),
|
|
95
|
+
)
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function findBlockByCoords(view: EditorView, x: number, y: number): { node: ProseMirrorNode; pos: number } | undefined {
|
|
99
|
+
const rect = getClientRect(view.dom)
|
|
100
|
+
if (!isWithinRect(rect, x, y)) {
|
|
101
|
+
return
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
let parent: ProseMirrorNode | undefined = view.state.doc
|
|
105
|
+
let pos = -1
|
|
106
|
+
|
|
107
|
+
while (parent) {
|
|
108
|
+
if (parent.isBlock && (parent.isTextblock || parent.isAtom || parent.type.spec.isolating)) {
|
|
109
|
+
return { node: parent, pos }
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Collect all children and their positions
|
|
113
|
+
const children: ProseMirrorNode[] = []
|
|
114
|
+
const positions: number[] = []
|
|
115
|
+
parent.forEach((child, offset) => {
|
|
116
|
+
children.push(child)
|
|
117
|
+
positions.push(offset + pos + 1)
|
|
118
|
+
})
|
|
119
|
+
|
|
120
|
+
let lo = 0
|
|
121
|
+
let hi = children.length - 1
|
|
122
|
+
|
|
123
|
+
while (lo <= hi) {
|
|
124
|
+
const i = hi - ((hi - lo) >> 1)
|
|
125
|
+
const childDOM = view.nodeDOM(positions[i])
|
|
126
|
+
const childRect = getNodeRect(childDOM)
|
|
127
|
+
if (!childRect) {
|
|
128
|
+
console.warn(`[prosekit] Unable to get rect at position: ${positions[i]}`)
|
|
129
|
+
return
|
|
130
|
+
}
|
|
131
|
+
if (childRect.top > y) {
|
|
132
|
+
hi = i - 1
|
|
133
|
+
} else if (childRect.bottom < y) {
|
|
134
|
+
lo = i + 1
|
|
135
|
+
} else {
|
|
136
|
+
lo = i
|
|
137
|
+
break
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
if (lo > hi) {
|
|
142
|
+
return
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
parent = children[lo]
|
|
146
|
+
pos = positions[lo]
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function getNodeRect(node: Node | null | undefined): Rect | undefined {
|
|
151
|
+
if (node && isElement(node) && node.isConnected) {
|
|
152
|
+
return getClientRect(node)
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function isWithinRect(rect: Rect, x: number, y: number) {
|
|
157
|
+
return x >= rect.left && x <= rect.right && y >= rect.top && y <= rect.bottom
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
interface Rect {
|
|
161
|
+
top: number
|
|
162
|
+
right: number
|
|
163
|
+
bottom: number
|
|
164
|
+
left: number
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
function findFirstLineRect(outer?: Node | null, inner?: Node | null): Rect | undefined {
|
|
168
|
+
if (outer && !outer.isConnected) {
|
|
169
|
+
return
|
|
170
|
+
}
|
|
171
|
+
if (inner && !inner.isConnected) {
|
|
172
|
+
return
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
if (outer && inner) {
|
|
176
|
+
const outerRect = findOuterRect(outer)
|
|
177
|
+
const innerRect = findFirstLineRectInNode(inner)
|
|
178
|
+
if (outerRect && innerRect) {
|
|
179
|
+
const { top, bottom } = innerRect
|
|
180
|
+
const { left, right } = outerRect
|
|
181
|
+
return { top, bottom, left, right }
|
|
182
|
+
} else {
|
|
183
|
+
return outerRect || innerRect
|
|
184
|
+
}
|
|
185
|
+
} else if (outer) {
|
|
186
|
+
return findFirstLineRectInNode(outer)
|
|
187
|
+
} else if (inner) {
|
|
188
|
+
return findFirstLineRectInNode(inner)
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
function findOuterRect(node: Node): Rect | undefined {
|
|
193
|
+
if (!isElement(node)) {
|
|
194
|
+
return
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
const rect = getClientRect(node)
|
|
198
|
+
const style = node.ownerDocument.defaultView?.getComputedStyle(node)
|
|
199
|
+
const marginLeft = style && Number.parseInt(style.marginLeft, 10) || 0
|
|
200
|
+
const marginRight = style && Number.parseInt(style.marginRight, 10) || 0
|
|
201
|
+
const left = rect.left - marginLeft
|
|
202
|
+
const right = rect.right + marginRight
|
|
203
|
+
|
|
204
|
+
return { top: rect.top, bottom: rect.bottom, left, right }
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
function findFirstLineRectInNode(node: Node): Rect | undefined {
|
|
208
|
+
if (isElement(node)) {
|
|
209
|
+
return findFirstLineRectInElement(node)
|
|
210
|
+
} else if (isTextNode(node)) {
|
|
211
|
+
return findFirstLineRectInTextNode(node)
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
function findFirstLineRectInTextNode(node: Text): Rect | undefined {
|
|
216
|
+
const ownerDocument = node.ownerDocument
|
|
217
|
+
if (!ownerDocument) {
|
|
218
|
+
return
|
|
219
|
+
}
|
|
220
|
+
const range = ownerDocument.createRange()
|
|
221
|
+
range.setStart(node, 0)
|
|
222
|
+
range.setEnd(node, 0)
|
|
223
|
+
const rects = range.getClientRects()
|
|
224
|
+
return rects[0]
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
function findFirstLineRectInElement(element: Element): Rect | undefined {
|
|
228
|
+
if (element.nodeName === 'BR') {
|
|
229
|
+
return element.getBoundingClientRect()
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
const rect = getClientRect(element)
|
|
233
|
+
const style = element.ownerDocument.defaultView?.getComputedStyle(element)
|
|
234
|
+
const marginLeft = style && Number.parseInt(style.marginLeft, 10) || 0
|
|
235
|
+
const marginRight = style && Number.parseInt(style.marginRight, 10) || 0
|
|
236
|
+
const left = rect.left - marginLeft
|
|
237
|
+
const right = rect.right + marginRight
|
|
238
|
+
|
|
239
|
+
const lineHeight = style && Number.parseInt(style.lineHeight, 10) || 24
|
|
240
|
+
const paddingTop = style && Number.parseInt(style.paddingTop, 10) || 0
|
|
241
|
+
const borderTop = style && Number.parseInt(style.borderTopWidth, 10) || 0
|
|
242
|
+
const top = rect.top + paddingTop + borderTop
|
|
243
|
+
const bottom = top + lineHeight
|
|
244
|
+
|
|
245
|
+
return { top, bottom, left, right }
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
function fulfillRect({ top, right, bottom, left }: Rect) {
|
|
249
|
+
return { top, right, bottom, left, width: right - left, height: bottom - top, x: left, y: top }
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
// A fallback rect that is far away from the screen. It should not be used through.
|
|
253
|
+
const fallbackRect = Object.freeze({
|
|
254
|
+
top: -9999,
|
|
255
|
+
right: -9999,
|
|
256
|
+
bottom: -9999,
|
|
257
|
+
left: -9999,
|
|
258
|
+
width: 0,
|
|
259
|
+
height: 0,
|
|
260
|
+
x: -9999,
|
|
261
|
+
y: -9999,
|
|
262
|
+
})
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createComputed,
|
|
3
|
+
createSignal,
|
|
4
|
+
useAttribute,
|
|
5
|
+
type ConnectableElement,
|
|
6
|
+
type ReadonlySignal,
|
|
7
|
+
type SetupOptions,
|
|
8
|
+
} from '@aria-ui/core'
|
|
9
|
+
import { useOverlayPositionerState } from '@aria-ui/overlay/elements'
|
|
10
|
+
import { usePresence } from '@aria-ui/presence'
|
|
11
|
+
import type { VirtualElement } from '@floating-ui/dom'
|
|
12
|
+
import type { Editor } from '@prosekit/core'
|
|
13
|
+
|
|
14
|
+
import { useEditorExtension } from '../../../hooks/use-editor-extension'
|
|
15
|
+
import { useScrolling } from '../../../hooks/use-scrolling'
|
|
16
|
+
import {
|
|
17
|
+
blockPopoverContext,
|
|
18
|
+
draggingContext,
|
|
19
|
+
type BlockPopoverContext,
|
|
20
|
+
type HoverState,
|
|
21
|
+
} from '../context'
|
|
22
|
+
|
|
23
|
+
import {
|
|
24
|
+
defineElementHoverHandler,
|
|
25
|
+
type ElementHoverHandler,
|
|
26
|
+
} from './pointer-move'
|
|
27
|
+
import type {
|
|
28
|
+
BlockHandlePopoverEvents,
|
|
29
|
+
BlockHandlePopoverProps,
|
|
30
|
+
} from './types'
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* @internal
|
|
34
|
+
*/
|
|
35
|
+
export function useBlockHandlePopover(
|
|
36
|
+
host: ConnectableElement,
|
|
37
|
+
{ state, emit }: SetupOptions<BlockHandlePopoverProps, BlockHandlePopoverEvents>,
|
|
38
|
+
): void {
|
|
39
|
+
const { editor, ...overlayState } = state
|
|
40
|
+
const reference = createSignal<VirtualElement | null>(null)
|
|
41
|
+
useOverlayPositionerState(host, overlayState, { reference })
|
|
42
|
+
|
|
43
|
+
const context = createSignal<BlockPopoverContext>(null)
|
|
44
|
+
blockPopoverContext.provide(host, context)
|
|
45
|
+
|
|
46
|
+
const dragging = createSignal(false)
|
|
47
|
+
draggingContext.provide(host, dragging)
|
|
48
|
+
|
|
49
|
+
const scrolling = useScrolling(host)
|
|
50
|
+
const open = createComputed(() => {
|
|
51
|
+
return !!context.get() && !scrolling.get()
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
useHoverExtension(host, editor, (referenceValue, hoverState) => {
|
|
55
|
+
reference.set(referenceValue)
|
|
56
|
+
context.set(hoverState)
|
|
57
|
+
const stateChangeDetails = hoverState ? { node: hoverState.node, pos: hoverState.pos } : null
|
|
58
|
+
emit('stateChange', stateChangeDetails)
|
|
59
|
+
})
|
|
60
|
+
|
|
61
|
+
useAttribute(host, 'data-state', () => (open.get() ? 'open' : 'closed'))
|
|
62
|
+
usePresence(host, open)
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function useHoverExtension(
|
|
66
|
+
host: ConnectableElement,
|
|
67
|
+
editor: ReadonlySignal<Editor | null>,
|
|
68
|
+
handler: ElementHoverHandler,
|
|
69
|
+
) {
|
|
70
|
+
let prevHoverState: HoverState | null = null
|
|
71
|
+
|
|
72
|
+
const extension = defineElementHoverHandler((reference, hoverState) => {
|
|
73
|
+
if (isHoverStateEqual(prevHoverState, hoverState)) {
|
|
74
|
+
return
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
prevHoverState = hoverState
|
|
78
|
+
handler(reference, hoverState)
|
|
79
|
+
})
|
|
80
|
+
|
|
81
|
+
useEditorExtension(host, editor, extension)
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function isHoverStateEqual(a: HoverState | null, b: HoverState | null) {
|
|
85
|
+
if (!a && !b) return true
|
|
86
|
+
if (!a || !b) return false
|
|
87
|
+
return a.pos === b.pos && a.node.eq(b.node)
|
|
88
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
EventDeclarations,
|
|
3
|
+
PropDeclarations,
|
|
4
|
+
} from '@aria-ui/core'
|
|
5
|
+
import {
|
|
6
|
+
overlayPositionerEvents,
|
|
7
|
+
overlayPositionerProps,
|
|
8
|
+
type OverlayPositionerEvents,
|
|
9
|
+
type OverlayPositionerProps,
|
|
10
|
+
} from '@aria-ui/overlay/elements'
|
|
11
|
+
import type { Placement } from '@floating-ui/dom'
|
|
12
|
+
import type { Editor } from '@prosekit/core'
|
|
13
|
+
import type { ProseMirrorNode } from '@prosekit/pm/model'
|
|
14
|
+
|
|
15
|
+
export interface BlockHandlePopoverProps extends Omit<OverlayPositionerProps, 'placement' | 'hoist' | 'flip' | 'shift' | 'hide'> {
|
|
16
|
+
/**
|
|
17
|
+
* The ProseKit editor instance.
|
|
18
|
+
*
|
|
19
|
+
* @default null
|
|
20
|
+
* @hidden
|
|
21
|
+
*/
|
|
22
|
+
editor: Editor | null
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* The placement of the popover, relative to the hovered block.
|
|
26
|
+
*
|
|
27
|
+
* @default "left"
|
|
28
|
+
*/
|
|
29
|
+
placement: Placement
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Whether to use the browser [Popover API](https://developer.mozilla.org/en-US/docs/Web/API/Popover_API)
|
|
33
|
+
* to place the floating element on top of other page content.
|
|
34
|
+
*
|
|
35
|
+
* @default false
|
|
36
|
+
*/
|
|
37
|
+
hoist: boolean
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* @default false
|
|
41
|
+
* @hidden
|
|
42
|
+
*/
|
|
43
|
+
flip: boolean
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* @default false
|
|
47
|
+
* @hidden
|
|
48
|
+
*/
|
|
49
|
+
shift: boolean
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* @default true
|
|
53
|
+
* @hidden
|
|
54
|
+
*/
|
|
55
|
+
hide: boolean
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** @internal */
|
|
59
|
+
export const blockHandlePopoverProps: PropDeclarations<BlockHandlePopoverProps> = {
|
|
60
|
+
...overlayPositionerProps,
|
|
61
|
+
editor: { default: null },
|
|
62
|
+
placement: { default: 'left' },
|
|
63
|
+
|
|
64
|
+
// Enabling `hoist` will cause the popover to have a small delay when
|
|
65
|
+
// scrolling the page.
|
|
66
|
+
hoist: { default: false },
|
|
67
|
+
|
|
68
|
+
flip: { default: false },
|
|
69
|
+
shift: { default: false },
|
|
70
|
+
hide: { default: true },
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export interface BlockHandlePopoverEvents extends OverlayPositionerEvents {
|
|
74
|
+
/**
|
|
75
|
+
* Fired when the hovered block changes.
|
|
76
|
+
*/
|
|
77
|
+
stateChange: CustomEvent<{ node: ProseMirrorNode; pos: number } | null>
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/** @internal */
|
|
81
|
+
export const blockHandlePopoverEvents: EventDeclarations<BlockHandlePopoverEvents> = {
|
|
82
|
+
...overlayPositionerEvents,
|
|
83
|
+
stateChange: {},
|
|
84
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createContext,
|
|
3
|
+
type Context,
|
|
4
|
+
} from '@aria-ui/core'
|
|
5
|
+
import type { ProseMirrorNode } from '@prosekit/pm/model'
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* @internal
|
|
9
|
+
*/
|
|
10
|
+
export interface HoverState {
|
|
11
|
+
node: ProseMirrorNode
|
|
12
|
+
pos: number
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* @internal
|
|
17
|
+
*/
|
|
18
|
+
export type BlockPopoverContext = HoverState | null
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* @internal
|
|
22
|
+
*/
|
|
23
|
+
export const blockPopoverContext: Context<BlockPopoverContext> = createContext(
|
|
24
|
+
'prosekit-block-popover-context',
|
|
25
|
+
null,
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* @internal
|
|
30
|
+
*/
|
|
31
|
+
export const draggingContext: Context<boolean> = createContext(
|
|
32
|
+
'prosekit-block-handle-dragging-context',
|
|
33
|
+
false,
|
|
34
|
+
)
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
// This file is generated by packages/dev/src/gen-components.ts
|
|
2
|
+
|
|
3
|
+
export { BlockHandleAddElement } from './block-handle-add/element.gen'
|
|
4
|
+
export { blockHandleAddEvents, blockHandleAddProps, type BlockHandleAddEvents, type BlockHandleAddProps } from './block-handle-add/types'
|
|
5
|
+
export { useBlockHandleAdd } from './block-handle-add/setup'
|
|
6
|
+
|
|
7
|
+
export { BlockHandleDraggableElement } from './block-handle-draggable/element.gen'
|
|
8
|
+
export { blockHandleDraggableEvents, blockHandleDraggableProps, type BlockHandleDraggableEvents, type BlockHandleDraggableProps } from './block-handle-draggable/types'
|
|
9
|
+
export { useBlockHandleDraggable } from './block-handle-draggable/setup'
|
|
10
|
+
|
|
11
|
+
export { BlockHandlePopoverElement } from './block-handle-popover/element.gen'
|
|
12
|
+
export { blockHandlePopoverEvents, blockHandlePopoverProps, type BlockHandlePopoverEvents, type BlockHandlePopoverProps } from './block-handle-popover/types'
|
|
13
|
+
export { useBlockHandlePopover } from './block-handle-popover/setup'
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { defineCustomElement, registerCustomElement, type BaseElementConstructor } from "@aria-ui/core"
|
|
2
|
+
|
|
3
|
+
import { useDropIndicator } from "./setup"
|
|
4
|
+
import { dropIndicatorEvents, dropIndicatorProps, type DropIndicatorEvents, type DropIndicatorProps } from "./types"
|
|
5
|
+
|
|
6
|
+
const DropIndicatorElementBase: BaseElementConstructor<DropIndicatorProps> = defineCustomElement<
|
|
7
|
+
DropIndicatorProps,
|
|
8
|
+
DropIndicatorEvents
|
|
9
|
+
>({
|
|
10
|
+
props: dropIndicatorProps,
|
|
11
|
+
events: dropIndicatorEvents,
|
|
12
|
+
setup: useDropIndicator,
|
|
13
|
+
})
|
|
14
|
+
class DropIndicatorElement extends DropIndicatorElementBase {}
|
|
15
|
+
|
|
16
|
+
registerCustomElement('prosekit-drop-indicator', DropIndicatorElement)
|
|
17
|
+
|
|
18
|
+
export { DropIndicatorElement }
|