@prosekit/web 0.5.3 → 0.5.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/_tsup-dts-rollup.d.ts +26 -2
- package/dist/prosekit-web-block-handle.js +202 -44
- package/package.json +11 -11
|
@@ -333,7 +333,7 @@ declare const blockHandlePopoverEvents: EventDeclarations<BlockHandlePopoverEven
|
|
|
333
333
|
export { blockHandlePopoverEvents }
|
|
334
334
|
export { blockHandlePopoverEvents as blockHandlePopoverEvents_alias_1 }
|
|
335
335
|
|
|
336
|
-
declare interface BlockHandlePopoverProps extends Omit<OverlayPositionerProps, 'placement'> {
|
|
336
|
+
declare interface BlockHandlePopoverProps extends Omit<OverlayPositionerProps, 'placement' | 'hoist'> {
|
|
337
337
|
/**
|
|
338
338
|
* The ProseKit editor instance.
|
|
339
339
|
*
|
|
@@ -344,9 +344,16 @@ declare interface BlockHandlePopoverProps extends Omit<OverlayPositionerProps, '
|
|
|
344
344
|
/**
|
|
345
345
|
* The placement of the popover, relative to the hovered block.
|
|
346
346
|
*
|
|
347
|
-
* @default "left
|
|
347
|
+
* @default "left"
|
|
348
348
|
*/
|
|
349
349
|
placement: Placement;
|
|
350
|
+
/**
|
|
351
|
+
* Whether to use the browser [Popover API](https://developer.mozilla.org/en-US/docs/Web/API/Popover_API)
|
|
352
|
+
* to place the floating element on top of other page content.
|
|
353
|
+
*
|
|
354
|
+
* @default false
|
|
355
|
+
*/
|
|
356
|
+
hoist: boolean;
|
|
350
357
|
}
|
|
351
358
|
export { BlockHandlePopoverProps }
|
|
352
359
|
export { BlockHandlePopoverProps as BlockHandlePopoverProps_alias_1 }
|
|
@@ -376,6 +383,16 @@ export declare function defineElementHoverHandler(handler: ElementHoverHandler):
|
|
|
376
383
|
|
|
377
384
|
export declare type ElementHoverHandler = (reference: VirtualElement | null, hoverState: HoverState | null) => void;
|
|
378
385
|
|
|
386
|
+
/**
|
|
387
|
+
* Similar to `element.getBoundingClientRect`, but handles `display: contents` elements.
|
|
388
|
+
*/
|
|
389
|
+
export declare function getClientRect(element: Element): {
|
|
390
|
+
top: number;
|
|
391
|
+
right: number;
|
|
392
|
+
bottom: number;
|
|
393
|
+
left: number;
|
|
394
|
+
};
|
|
395
|
+
|
|
379
396
|
export declare function getHoveringCell(view: EditorView, event: MouseEvent): HoveringCellInfo | undefined;
|
|
380
397
|
|
|
381
398
|
export declare function getStateWithDefaults<Props extends Record<string, any> = Record<string, any>>(state: Partial<SignalState<Props>>, props: PropDeclarations<Props>): SignalState<Props>;
|
|
@@ -597,6 +614,13 @@ export { popoverTriggerProps as popoverTriggerProps_alias_1 }
|
|
|
597
614
|
|
|
598
615
|
export declare const queryContext: Context<string>;
|
|
599
616
|
|
|
617
|
+
export declare interface Rect {
|
|
618
|
+
top: number;
|
|
619
|
+
right: number;
|
|
620
|
+
bottom: number;
|
|
621
|
+
left: number;
|
|
622
|
+
}
|
|
623
|
+
|
|
600
624
|
declare class ResizableHandleElement extends ResizableHandleElement_base {
|
|
601
625
|
}
|
|
602
626
|
export { ResizableHandleElement }
|
|
@@ -155,6 +155,30 @@ import {
|
|
|
155
155
|
union
|
|
156
156
|
} from "@prosekit/core";
|
|
157
157
|
|
|
158
|
+
// src/utils/get-client-rect.ts
|
|
159
|
+
function getClientRect(element) {
|
|
160
|
+
const rect = element.getBoundingClientRect();
|
|
161
|
+
if (rect.width === 0 && rect.height === 0 && rect.x === 0 && rect.y === 0) {
|
|
162
|
+
if (element.getClientRects().length === 0) {
|
|
163
|
+
const children = Array.from(element.children);
|
|
164
|
+
const rects = children.map((child) => getClientRect(child)).filter((x) => !!x);
|
|
165
|
+
if (rects.length === 0) {
|
|
166
|
+
return rect;
|
|
167
|
+
}
|
|
168
|
+
if (rects.length === 1) {
|
|
169
|
+
return rects[0];
|
|
170
|
+
}
|
|
171
|
+
return {
|
|
172
|
+
top: Math.min(...rects.map((rect2) => rect2.top)),
|
|
173
|
+
right: Math.max(...rects.map((rect2) => rect2.right)),
|
|
174
|
+
bottom: Math.max(...rects.map((rect2) => rect2.bottom)),
|
|
175
|
+
left: Math.min(...rects.map((rect2) => rect2.left))
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
return rect;
|
|
180
|
+
}
|
|
181
|
+
|
|
158
182
|
// src/utils/throttle.ts
|
|
159
183
|
function throttle(callback, wait) {
|
|
160
184
|
let lastTime = 0;
|
|
@@ -168,56 +192,39 @@ function throttle(callback, wait) {
|
|
|
168
192
|
}
|
|
169
193
|
|
|
170
194
|
// src/components/block-handle/block-handle-popover/pointer-move.ts
|
|
195
|
+
var TEXT_NODE = 3;
|
|
196
|
+
var ELEMENT_NODE = 1;
|
|
171
197
|
function defineElementHoverHandler(handler) {
|
|
198
|
+
const handleElement = (element, node, pos, parentElement) => {
|
|
199
|
+
const reference = {
|
|
200
|
+
contextElement: element,
|
|
201
|
+
getBoundingClientRect: () => {
|
|
202
|
+
const rect = findFirstLineRect(parentElement, element);
|
|
203
|
+
return rect ? fulfillRect(rect) : fallbackRect;
|
|
204
|
+
}
|
|
205
|
+
};
|
|
206
|
+
handler(reference, { element, node, pos });
|
|
207
|
+
};
|
|
172
208
|
const handlePointerEvent = (view, event) => {
|
|
173
|
-
const
|
|
174
|
-
const
|
|
175
|
-
|
|
176
|
-
// Use the center of the editor
|
|
177
|
-
left: rect.left + rect.width / 2
|
|
178
|
-
})?.inside;
|
|
179
|
-
if (pos == null || pos < 0) {
|
|
209
|
+
const { x, y } = event;
|
|
210
|
+
const block = findBlockByCoordinate(view, x, y);
|
|
211
|
+
if (!block) {
|
|
180
212
|
handler(null, null);
|
|
181
213
|
return;
|
|
182
214
|
}
|
|
183
|
-
const
|
|
184
|
-
const
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
const ancestorPos = $pos.before($pos.depth);
|
|
188
|
-
const node2 = view.state.doc.nodeAt(ancestorPos);
|
|
189
|
-
const element2 = view.nodeDOM(ancestorPos);
|
|
190
|
-
if (!element2 || !node2) {
|
|
191
|
-
handler(null, null);
|
|
192
|
-
return;
|
|
193
|
-
}
|
|
194
|
-
const reference = {
|
|
195
|
-
contextElement: element2,
|
|
196
|
-
// Get the bounding client rect of the parent node, including its
|
|
197
|
-
// margins.
|
|
198
|
-
getBoundingClientRect: () => {
|
|
199
|
-
const rect2 = element2.getBoundingClientRect();
|
|
200
|
-
const style = window.getComputedStyle(element2);
|
|
201
|
-
const marginTop = Number.parseInt(style.marginTop, 10) || 0;
|
|
202
|
-
const marginRight = Number.parseInt(style.marginRight, 10) || 0;
|
|
203
|
-
const marginBottom = Number.parseInt(style.marginBottom, 10) || 0;
|
|
204
|
-
const marginLeft = Number.parseInt(style.marginLeft, 10) || 0;
|
|
205
|
-
return {
|
|
206
|
-
top: rect2.top - marginTop,
|
|
207
|
-
right: rect2.right + marginRight,
|
|
208
|
-
bottom: rect2.bottom + marginBottom,
|
|
209
|
-
left: rect2.left - marginLeft,
|
|
210
|
-
width: rect2.width + marginLeft + marginRight,
|
|
211
|
-
height: rect2.height + marginTop + marginBottom,
|
|
212
|
-
x: rect2.x - marginLeft,
|
|
213
|
-
y: rect2.y - marginTop
|
|
214
|
-
};
|
|
215
|
-
}
|
|
216
|
-
};
|
|
217
|
-
handler(reference, { element: element2, node: node2, pos: ancestorPos });
|
|
215
|
+
const { node, pos } = block;
|
|
216
|
+
const element = getElementAtPos(view, pos);
|
|
217
|
+
if (!element) {
|
|
218
|
+
handler(null, null);
|
|
218
219
|
return;
|
|
219
220
|
}
|
|
220
|
-
|
|
221
|
+
const $pos = view.state.doc.resolve(pos);
|
|
222
|
+
let parentElement;
|
|
223
|
+
if ($pos.depth > 0 && $pos.index($pos.depth) === 0) {
|
|
224
|
+
const parentPos = $pos.before($pos.depth);
|
|
225
|
+
parentElement = view.nodeDOM(parentPos);
|
|
226
|
+
}
|
|
227
|
+
handleElement(element, node, pos, parentElement);
|
|
221
228
|
};
|
|
222
229
|
return union(
|
|
223
230
|
defineDOMEventHandler("pointermove", throttle(handlePointerEvent, 200)),
|
|
@@ -225,6 +232,154 @@ function defineElementHoverHandler(handler) {
|
|
|
225
232
|
defineDOMEventHandler("keypress", () => handler(null, null))
|
|
226
233
|
);
|
|
227
234
|
}
|
|
235
|
+
function getElementAtPos(view, pos) {
|
|
236
|
+
const node = view.nodeDOM(pos);
|
|
237
|
+
if (node && node.nodeType === ELEMENT_NODE) {
|
|
238
|
+
return node;
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
function findBlockByCoordinate(view, x, y) {
|
|
242
|
+
const dom = view.dom;
|
|
243
|
+
const rect = getClientRect(dom);
|
|
244
|
+
if (!isWithinRect(rect, x, y)) {
|
|
245
|
+
return;
|
|
246
|
+
}
|
|
247
|
+
let parent = view.state.doc;
|
|
248
|
+
let pos = -1;
|
|
249
|
+
while (parent) {
|
|
250
|
+
if (parent.isBlock && (parent.isTextblock || parent.isAtom)) {
|
|
251
|
+
return { node: parent, pos };
|
|
252
|
+
}
|
|
253
|
+
const children = [];
|
|
254
|
+
const positions = [];
|
|
255
|
+
parent.forEach((child, offset) => {
|
|
256
|
+
children.push(child);
|
|
257
|
+
positions.push(offset + pos + 1);
|
|
258
|
+
});
|
|
259
|
+
let lo = 0;
|
|
260
|
+
let hi = children.length - 1;
|
|
261
|
+
while (lo <= hi) {
|
|
262
|
+
const i = hi - (hi - lo >> 1);
|
|
263
|
+
const childDOM = view.nodeDOM(positions[i]);
|
|
264
|
+
const childRect = getNodeRect(childDOM);
|
|
265
|
+
if (!childRect) {
|
|
266
|
+
console.warn("[prosekit] Unable to get rect at position", positions[i]);
|
|
267
|
+
return;
|
|
268
|
+
}
|
|
269
|
+
if (childRect.top > y) {
|
|
270
|
+
hi = i - 1;
|
|
271
|
+
} else if (childRect.bottom < y) {
|
|
272
|
+
lo = i + 1;
|
|
273
|
+
} else {
|
|
274
|
+
lo = i;
|
|
275
|
+
break;
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
if (lo > hi) {
|
|
279
|
+
return;
|
|
280
|
+
}
|
|
281
|
+
parent = children[lo];
|
|
282
|
+
pos = positions[lo];
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
function getNodeRect(node) {
|
|
286
|
+
if (!node || node.nodeType !== ELEMENT_NODE) {
|
|
287
|
+
return;
|
|
288
|
+
}
|
|
289
|
+
const element = node;
|
|
290
|
+
if (!element.isConnected) {
|
|
291
|
+
return;
|
|
292
|
+
}
|
|
293
|
+
return getClientRect(element);
|
|
294
|
+
}
|
|
295
|
+
function isWithinRect(rect, x, y) {
|
|
296
|
+
return x >= rect.left && x <= rect.right && y >= rect.top && y <= rect.bottom;
|
|
297
|
+
}
|
|
298
|
+
function findFirstLineRect(outer, inner) {
|
|
299
|
+
if (outer && !outer.isConnected) {
|
|
300
|
+
return;
|
|
301
|
+
}
|
|
302
|
+
if (inner && !inner.isConnected) {
|
|
303
|
+
return;
|
|
304
|
+
}
|
|
305
|
+
if (outer && inner) {
|
|
306
|
+
const outerRect = findOuterRect(outer);
|
|
307
|
+
const innerRect = findFirstLineRectInNode(inner);
|
|
308
|
+
if (outerRect && innerRect) {
|
|
309
|
+
const { top, bottom } = innerRect;
|
|
310
|
+
const { left, right } = outerRect;
|
|
311
|
+
return { top, bottom, left, right };
|
|
312
|
+
} else {
|
|
313
|
+
return outerRect || innerRect;
|
|
314
|
+
}
|
|
315
|
+
} else if (outer) {
|
|
316
|
+
return findFirstLineRectInNode(outer);
|
|
317
|
+
} else if (inner) {
|
|
318
|
+
return findFirstLineRectInNode(inner);
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
function findOuterRect(node) {
|
|
322
|
+
if (node.nodeType !== ELEMENT_NODE) {
|
|
323
|
+
return;
|
|
324
|
+
}
|
|
325
|
+
const element = node;
|
|
326
|
+
const rect = getClientRect(element);
|
|
327
|
+
const style = element.ownerDocument.defaultView?.getComputedStyle(element);
|
|
328
|
+
const marginLeft = style && Number.parseInt(style.marginLeft, 10) || 0;
|
|
329
|
+
const marginRight = style && Number.parseInt(style.marginRight, 10) || 0;
|
|
330
|
+
const left = rect.left - marginLeft;
|
|
331
|
+
const right = rect.right + marginRight;
|
|
332
|
+
return { top: rect.top, bottom: rect.bottom, left, right };
|
|
333
|
+
}
|
|
334
|
+
function findFirstLineRectInNode(node) {
|
|
335
|
+
switch (node.nodeType) {
|
|
336
|
+
case TEXT_NODE:
|
|
337
|
+
return findFirstLineRectInTextNode(node);
|
|
338
|
+
case ELEMENT_NODE:
|
|
339
|
+
return findFirstLineRectInElement(node);
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
function findFirstLineRectInTextNode(node) {
|
|
343
|
+
const ownerDocument = node.ownerDocument;
|
|
344
|
+
if (!ownerDocument) {
|
|
345
|
+
return;
|
|
346
|
+
}
|
|
347
|
+
const range = ownerDocument.createRange();
|
|
348
|
+
range.setStart(node, 0);
|
|
349
|
+
range.setEnd(node, 0);
|
|
350
|
+
const rects = range.getClientRects();
|
|
351
|
+
return rects[0];
|
|
352
|
+
}
|
|
353
|
+
function findFirstLineRectInElement(element) {
|
|
354
|
+
if (element.nodeName === "BR") {
|
|
355
|
+
return element.getBoundingClientRect();
|
|
356
|
+
}
|
|
357
|
+
const rect = getClientRect(element);
|
|
358
|
+
const style = element.ownerDocument.defaultView?.getComputedStyle(element);
|
|
359
|
+
const marginLeft = style && Number.parseInt(style.marginLeft, 10) || 0;
|
|
360
|
+
const marginRight = style && Number.parseInt(style.marginRight, 10) || 0;
|
|
361
|
+
const left = rect.left - marginLeft;
|
|
362
|
+
const right = rect.right + marginRight;
|
|
363
|
+
const lineHeight = style && Number.parseInt(style.lineHeight, 10) || 24;
|
|
364
|
+
const paddingTop = style && Number.parseInt(style.paddingTop, 10) || 0;
|
|
365
|
+
const borderTop = style && Number.parseInt(style.borderTopWidth, 10) || 0;
|
|
366
|
+
const top = rect.top + paddingTop + borderTop;
|
|
367
|
+
const bottom = top + lineHeight;
|
|
368
|
+
return { top, bottom, left, right };
|
|
369
|
+
}
|
|
370
|
+
function fulfillRect({ top, right, bottom, left }) {
|
|
371
|
+
return { top, right, bottom, left, width: right - left, height: bottom - top, x: left, y: top };
|
|
372
|
+
}
|
|
373
|
+
var fallbackRect = Object.freeze({
|
|
374
|
+
top: -9999,
|
|
375
|
+
right: -9999,
|
|
376
|
+
bottom: -9999,
|
|
377
|
+
left: -9999,
|
|
378
|
+
width: 0,
|
|
379
|
+
height: 0,
|
|
380
|
+
x: -9999,
|
|
381
|
+
y: -9999
|
|
382
|
+
});
|
|
228
383
|
|
|
229
384
|
// src/components/block-handle/block-handle-popover/setup.ts
|
|
230
385
|
function useBlockHandlePopover(host, { state }) {
|
|
@@ -268,7 +423,10 @@ import {
|
|
|
268
423
|
var blockHandlePopoverProps = {
|
|
269
424
|
...overlayPositionerProps,
|
|
270
425
|
editor: { default: null },
|
|
271
|
-
placement: { default: "left
|
|
426
|
+
placement: { default: "left" },
|
|
427
|
+
// Enabling `hoist` will cause the block handle to have a small delay when
|
|
428
|
+
// scrolling the page.
|
|
429
|
+
hoist: { default: false }
|
|
272
430
|
};
|
|
273
431
|
var blockHandlePopoverEvents = {};
|
|
274
432
|
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@prosekit/web",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.5.
|
|
4
|
+
"version": "0.5.5",
|
|
5
5
|
"private": false,
|
|
6
6
|
"author": {
|
|
7
7
|
"name": "ocavue",
|
|
@@ -72,25 +72,25 @@
|
|
|
72
72
|
"dependencies": {
|
|
73
73
|
"@aria-ui/collection": "^0.0.4",
|
|
74
74
|
"@aria-ui/core": "^0.0.20",
|
|
75
|
-
"@aria-ui/listbox": "^0.0.
|
|
76
|
-
"@aria-ui/menu": "^0.0.
|
|
75
|
+
"@aria-ui/listbox": "^0.0.23",
|
|
76
|
+
"@aria-ui/menu": "^0.0.18",
|
|
77
77
|
"@aria-ui/overlay": "^0.0.23",
|
|
78
|
-
"@aria-ui/popover": "^0.0.
|
|
79
|
-
"@aria-ui/presence": "^0.0.
|
|
80
|
-
"@aria-ui/tooltip": "^0.0.
|
|
81
|
-
"@floating-ui/dom": "^1.6.
|
|
82
|
-
"@zag-js/dom-query": "^0.
|
|
78
|
+
"@aria-ui/popover": "^0.0.26",
|
|
79
|
+
"@aria-ui/presence": "^0.0.18",
|
|
80
|
+
"@aria-ui/tooltip": "^0.0.28",
|
|
81
|
+
"@floating-ui/dom": "^1.6.13",
|
|
82
|
+
"@zag-js/dom-query": "^0.81.2",
|
|
83
83
|
"just-map-values": "^3.2.0",
|
|
84
84
|
"just-omit": "^2.2.0",
|
|
85
85
|
"prosemirror-tables": "^1.6.2",
|
|
86
|
-
"@prosekit/core": "^0.7.
|
|
86
|
+
"@prosekit/core": "^0.7.15",
|
|
87
87
|
"@prosekit/pm": "^0.1.9",
|
|
88
|
-
"@prosekit/extensions": "^0.7.
|
|
88
|
+
"@prosekit/extensions": "^0.7.24"
|
|
89
89
|
},
|
|
90
90
|
"devDependencies": {
|
|
91
91
|
"tsup": "^8.3.5",
|
|
92
92
|
"typescript": "~5.7.2",
|
|
93
|
-
"vitest": "^
|
|
93
|
+
"vitest": "^3.0.4",
|
|
94
94
|
"@prosekit/dev": "0.0.0"
|
|
95
95
|
},
|
|
96
96
|
"scripts": {
|