@vectojs/core 1.32.7 → 1.34.0
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/{chunk-FQ2565IT.js → chunk-UEIZDNK7.js} +57 -30
- package/dist/{chunk-YTGGL4I4.mjs → chunk-VFQMETOR.mjs} +27 -0
- package/dist/index.js +3082 -2280
- package/dist/index.mjs +2834 -2032
- package/dist/text.js +2 -2
- package/dist/text.mjs +1 -1
- package/dist/tree/Entity.d.ts +41 -0
- package/dist/tree/Scene.d.ts +199 -472
- package/dist/tree/scene/A11yProjectionManager.d.ts +147 -0
- package/dist/tree/scene/CanvasGeometry.d.ts +117 -0
- package/dist/tree/scene/ContentGridProjector.d.ts +63 -0
- package/dist/tree/scene/ContentProjectionManager.d.ts +217 -0
- package/dist/tree/scene/DirtyTracker.d.ts +107 -0
- package/dist/tree/scene/DriverTicker.d.ts +101 -0
- package/dist/tree/scene/HitTester.d.ts +113 -0
- package/dist/tree/scene/PhaseTimer.d.ts +114 -0
- package/dist/tree/scene/WasmBackendFacade.d.ts +271 -0
- package/dist/tree/scene/a11y-dom.d.ts +78 -0
- package/dist/tree/scene/content-break-carrier.d.ts +77 -0
- package/dist/tree/scene/content-caret.d.ts +28 -0
- package/dist/tree/scene/content-line-window.d.ts +64 -0
- package/package.json +1 -1
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* How a projected line carries its trailing hard break.
|
|
3
|
+
*
|
|
4
|
+
* A projected line must hold its own line break in the DOM: copy, find-in-page
|
|
5
|
+
* and screen readers all read the projection, and a block whose lines are
|
|
6
|
+
* separate absolutely-positioned carriers has no flow structure a browser could
|
|
7
|
+
* synthesize a newline from. So the break character is written out explicitly.
|
|
8
|
+
*
|
|
9
|
+
* Writing it as an ordinary inline character is what this module exists to stop.
|
|
10
|
+
* The carriers are `white-space: pre`, so a trailing `\n` is a real preserved
|
|
11
|
+
* character in an inline context, and a browser gives it a selection rectangle
|
|
12
|
+
* of **zero width and full line height** at the end of the line. Chrome paints
|
|
13
|
+
* that rectangle, so selecting a line drew a caret-like vertical bar just past
|
|
14
|
+
* the last glyph — ink the canvas never drew, at a position no glyph occupies.
|
|
15
|
+
*
|
|
16
|
+
* Measured in real headed Chrome (DPR 1.76) on a live page, selecting one
|
|
17
|
+
* paragraph line of `1. 极致的性能与定制化:\n`: four selection rects, the last
|
|
18
|
+
* `x 495.18, w 0, h 31.82`. The same line with the break character removed
|
|
19
|
+
* produced one rect and no bar. Both projection paths were affected — a
|
|
20
|
+
* `CodeBlock` fixture reported one such rect on every line that owned a break
|
|
21
|
+
* (3 of 4 rows, including the empty row whose entire content is the break).
|
|
22
|
+
*
|
|
23
|
+
* ## Why the character stays in the DOM
|
|
24
|
+
*
|
|
25
|
+
* Deleting it fixes the bar and breaks copy: measured on the same page,
|
|
26
|
+
* stripping the trailing `\n` from every line of a block made
|
|
27
|
+
* `getSelection().toString()` return the block as one unbroken run. The break
|
|
28
|
+
* has to remain selectable text; it merely must not paint.
|
|
29
|
+
*
|
|
30
|
+
* `font-size: 0` is what separates those two. A zero font size leaves the
|
|
31
|
+
* character in the text content and in the selected string, while collapsing the
|
|
32
|
+
* line box it would otherwise contribute — measured on the same block, the
|
|
33
|
+
* zero-width full-height rect became `w 0, h 0` and the selected string still
|
|
34
|
+
* ended in `\n`.
|
|
35
|
+
*
|
|
36
|
+
* Rejected alternatives, each measured on the same live block:
|
|
37
|
+
*
|
|
38
|
+
* - **An absolutely positioned zero-size box** (`position: absolute; width: 0;
|
|
39
|
+
* height: 0; overflow: hidden`) still produced the full-height rect, *and*
|
|
40
|
+
* dropped the newline from the selected string — out of flow, the break no
|
|
41
|
+
* longer serialises. Worse on both counts.
|
|
42
|
+
* - **Removing the character** and relying on the carriers' own box structure.
|
|
43
|
+
* The line carriers do compute to `display: block`, so it is a reasonable
|
|
44
|
+
* guess that the browser would synthesize the break — it does not. Copy came
|
|
45
|
+
* back unbroken (see above).
|
|
46
|
+
*
|
|
47
|
+
* ## Why a shared module
|
|
48
|
+
*
|
|
49
|
+
* Both projection branches own break text and both had the defect:
|
|
50
|
+
* `Scene.syncContentProjection`'s carrier branch (plain and positioned-run
|
|
51
|
+
* lines) and {@link ContentGridProjector.syncGrid} (the last cell of a line, and
|
|
52
|
+
* an empty line's whole content). This follows the `content-line-window`
|
|
53
|
+
* precedent — a stateless helper both sides of the grid cut call, rather than a
|
|
54
|
+
* member on either — so the two cannot drift into disagreeing about how a break
|
|
55
|
+
* is represented, which would make copy fidelity depend on which branch drew a
|
|
56
|
+
* given block.
|
|
57
|
+
*
|
|
58
|
+
* Stateless by design: no `Scene`, no entities, no state beyond the node passed
|
|
59
|
+
* in.
|
|
60
|
+
*/
|
|
61
|
+
/**
|
|
62
|
+
* Append `breakText` to `owner`, suppressing its paint only if it is a hard
|
|
63
|
+
* break.
|
|
64
|
+
*
|
|
65
|
+
* A no-op for empty text, so callers can pass a separator that may legitimately
|
|
66
|
+
* be absent (the document's last line owns no break) without branching.
|
|
67
|
+
*
|
|
68
|
+
* A hard break goes into a `font-size: 0` span: still selectable, copyable and
|
|
69
|
+
* announced, but contributing no line box. Any other separator — a soft-wrap
|
|
70
|
+
* space — is appended as a plain text node, because its width is part of the
|
|
71
|
+
* line the canvas drew.
|
|
72
|
+
*
|
|
73
|
+
* The span is deliberately **not** `aria-hidden` and does **not** set
|
|
74
|
+
* `user-select: none`: the break is real content that a screen reader and a copy
|
|
75
|
+
* both want. Only its painted geometry is suppressed.
|
|
76
|
+
*/
|
|
77
|
+
export declare function appendContentBreak(owner: Node, breakText: string): void;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Caret addressing inside a content projection: the shared vocabulary for
|
|
3
|
+
* describing "this exact position in this projected text".
|
|
4
|
+
*
|
|
5
|
+
* Extracted as a **shared module** rather than as part of any one collaborator
|
|
6
|
+
* because both sides of the extraction-3 cut need it. The selection-preservation
|
|
7
|
+
* pass that moves to {@link ContentSelectionManager} converts a live `Selection`
|
|
8
|
+
* endpoint into a linear character offset and back, while the pointer-to-caret
|
|
9
|
+
* resolution that stays in `Scene.ts` (`nearestTextPositionInProjection` and its
|
|
10
|
+
* per-line helpers) builds the same positions from hit coordinates. Duplicating
|
|
11
|
+
* the offset walk in both places would let the two drift, and a caret that
|
|
12
|
+
* disagrees with the offset it was derived from restores a selection to the wrong
|
|
13
|
+
* text.
|
|
14
|
+
*
|
|
15
|
+
* Every symbol here was module-private in `Scene.ts` and stays module-private to
|
|
16
|
+
* the package: `packages/core/src/index.ts` is `export * from './tree/Scene'`, so
|
|
17
|
+
* re-exporting would silently widen the public API. `DEC-0019` rule 3.
|
|
18
|
+
*
|
|
19
|
+
* Stateless and DOM-only by design — no `Scene`, no entities, no engine state.
|
|
20
|
+
*/
|
|
21
|
+
/** A concrete text caret position, usable as a Selection anchor or focus. */
|
|
22
|
+
export interface TextCaretPosition {
|
|
23
|
+
node: Text;
|
|
24
|
+
offset: number;
|
|
25
|
+
}
|
|
26
|
+
export declare function collectTextNodes(root: HTMLElement): Text[];
|
|
27
|
+
export declare function projectionAbsoluteOffset(root: HTMLElement, caret: TextCaretPosition): number | null;
|
|
28
|
+
export declare function projectionCaretAt(root: HTMLElement, absoluteOffset: number, affinity: 'forward' | 'backward'): TextCaretPosition | null;
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Which projected lines a content block materializes for a given visible band.
|
|
3
|
+
*
|
|
4
|
+
* Extracted as a **shared module** rather than onto a collaborator because both
|
|
5
|
+
* sides of the grid cut need it. `Scene.syncContentProjection` calls
|
|
6
|
+
* {@link projectionLineWindow} for the plain carrier branch and stays on the
|
|
7
|
+
* facade; {@link ContentGridProjector} calls {@link projectionGridLineWindow} for
|
|
8
|
+
* the grid branch and has moved. Duplicating the scan would let the two branches
|
|
9
|
+
* disagree about which lines are present, and a window that disagrees with the
|
|
10
|
+
* carriers it drives serves stale geometry to selection and find-in-page.
|
|
11
|
+
*
|
|
12
|
+
* This is the `content-caret` precedent from `DEC-0022`: a stateless helper that
|
|
13
|
+
* outlived the class it was private to.
|
|
14
|
+
*
|
|
15
|
+
* Every symbol here was module-private in `Scene.ts` and stays module-private to
|
|
16
|
+
* the package. The core barrel re-exports everything from `./tree/Scene`, so
|
|
17
|
+
* re-exporting from there would silently widen the public API (`DEC-0019` rule 3).
|
|
18
|
+
*
|
|
19
|
+
* Stateless by design — no `Scene`, no entities, no DOM.
|
|
20
|
+
*/
|
|
21
|
+
import type { PreparedContentGrid } from '@vectojs/text';
|
|
22
|
+
import type { ContentProjection } from '../Entity';
|
|
23
|
+
/** Half-open range of projected line indices to materialize. */
|
|
24
|
+
export interface ProjectionLineWindow {
|
|
25
|
+
start: number;
|
|
26
|
+
/** Exclusive. */
|
|
27
|
+
end: number;
|
|
28
|
+
/** False when the whole document is being projected. */
|
|
29
|
+
gated: boolean;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* The contiguous run of lines overlapping `band`, in entity-local y.
|
|
33
|
+
*
|
|
34
|
+
* **Contiguous on purpose.** A gap would break selection: the DOM order of
|
|
35
|
+
* carriers is what the browser walks when extending a selection or serialising
|
|
36
|
+
* a copy, so materializing lines 0-9 and 90-99 with nothing between them would
|
|
37
|
+
* let a drag from line 5 to line 95 silently splice out 80 lines of text. A
|
|
38
|
+
* single window can only lose text at its *edges*, where the user cannot reach
|
|
39
|
+
* without scrolling, and scrolling rebuilds the window.
|
|
40
|
+
*
|
|
41
|
+
* Falls back to the whole document whenever the answer is not clearly better:
|
|
42
|
+
* a null band, a document that fits, or a window that would cover everything
|
|
43
|
+
* anyway. Emitting nothing is never correct — projected text is what serves
|
|
44
|
+
* find-in-page, copy and, for static text, the screen reader.
|
|
45
|
+
*/
|
|
46
|
+
export declare function projectionLineWindow(lines: ReadonlyArray<{
|
|
47
|
+
y: number;
|
|
48
|
+
lineHeight?: number;
|
|
49
|
+
}>, band: {
|
|
50
|
+
minY: number;
|
|
51
|
+
maxY: number;
|
|
52
|
+
} | null, fallbackLineHeight: number): ProjectionLineWindow;
|
|
53
|
+
/**
|
|
54
|
+
* {@link projectionLineWindow} for a prepared grid.
|
|
55
|
+
*
|
|
56
|
+
* A grid line's y comes from the parallel `projection.lines` entry when present
|
|
57
|
+
* and otherwise from `lineIndex * grid.lineHeight`, which is the same fallback
|
|
58
|
+
* the materialization loop uses for positioning — so the window and the carriers
|
|
59
|
+
* always agree on where a line is.
|
|
60
|
+
*/
|
|
61
|
+
export declare function projectionGridLineWindow(grid: PreparedContentGrid, projectionLines: ContentProjection['lines'], band: {
|
|
62
|
+
minY: number;
|
|
63
|
+
maxY: number;
|
|
64
|
+
} | null): ProjectionLineWindow;
|