@onodocs/canvas 0.2.0 → 0.3.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/README.md +277 -225
- package/SDK.md +277 -225
- package/index.js +87 -14
- package/package.json +3 -3
- package/types/canvas/index.d.ts +7 -0
- package/types/canvas/presentation.d.ts +5 -1
- package/types/canvas/text-selection.d.ts +4 -2
- package/types/foundation/page-interaction.d.ts +9 -0
- package/types/render-api/index.d.ts +1 -1
package/index.js
CHANGED
|
@@ -1578,7 +1578,7 @@ __name(fillExactSectionColumnSeparator, "fillExactSectionColumnSeparator");
|
|
|
1578
1578
|
|
|
1579
1579
|
// packages/canvas/src/text-selection.ts
|
|
1580
1580
|
var nextView = 0;
|
|
1581
|
-
function mountTextSelection(pageText, viewport, shells, pageBounds, nearby, signal) {
|
|
1581
|
+
function mountTextSelection(pageText, viewport, shells, pageBounds, nearby, signal, input, onSelectionChange) {
|
|
1582
1582
|
const document2 = viewport.ownerDocument;
|
|
1583
1583
|
const window = document2.defaultView;
|
|
1584
1584
|
const svg = "http://www.w3.org/2000/svg";
|
|
@@ -1607,7 +1607,7 @@ function mountTextSelection(pageText, viewport, shells, pageBounds, nearby, sign
|
|
|
1607
1607
|
for (const caret of value.carets) {
|
|
1608
1608
|
const offset = offsets[caret.scalarOffset - value.scalarStart];
|
|
1609
1609
|
if (offset === void 0) continue;
|
|
1610
|
-
const stop = { line, offset: length + offset, x: caret.x, index: stops.length };
|
|
1610
|
+
const stop = { line, offset: length + offset, x: caret.x, index: stops.length, ...caret.paragraphOffset === void 0 ? {} : { position: { paragraphId: value.paragraphId, offset: caret.paragraphOffset } } };
|
|
1611
1611
|
line.stops.push(stop);
|
|
1612
1612
|
stops.push(stop);
|
|
1613
1613
|
}
|
|
@@ -1639,6 +1639,10 @@ function mountTextSelection(pageText, viewport, shells, pageBounds, nearby, sign
|
|
|
1639
1639
|
let menu;
|
|
1640
1640
|
viewport.tabIndex = 0;
|
|
1641
1641
|
viewport.style.userSelect = "none";
|
|
1642
|
+
if (input) {
|
|
1643
|
+
viewport.style.position = "relative";
|
|
1644
|
+
viewport.append(input);
|
|
1645
|
+
}
|
|
1642
1646
|
for (const shell of shells) shell.style.cursor = "text";
|
|
1643
1647
|
function paint() {
|
|
1644
1648
|
for (const [index, layer] of layers) {
|
|
@@ -1646,7 +1650,13 @@ function mountTextSelection(pageText, viewport, shells, pageBounds, nearby, sign
|
|
|
1646
1650
|
layer.replaceChildren();
|
|
1647
1651
|
if (!nearby.has(index)) layers.delete(index);
|
|
1648
1652
|
}
|
|
1649
|
-
if (!anchor || !focus || document2.activeElement !== viewport && !menu?.contains(document2.activeElement)) return;
|
|
1653
|
+
if (!anchor || !focus || !input && document2.activeElement !== viewport && !menu?.contains(document2.activeElement)) return;
|
|
1654
|
+
if (input) {
|
|
1655
|
+
const shell = shells[focus.line.item.pageIndex], bounds = shell.getBoundingClientRect(), origin = viewport.getBoundingClientRect();
|
|
1656
|
+
const point = focus.line.transform.transformPoint({ x: focus.x, y: focus.line.item.selection.top });
|
|
1657
|
+
const scale = bounds.width / pageBounds[focus.line.item.pageIndex].width;
|
|
1658
|
+
Object.assign(input.style, { position: "absolute", left: `${bounds.left - origin.left + point.x * scale}px`, top: `${bounds.top - origin.top + point.y * scale}px`, height: `${focus.line.item.selection.height * scale}px` });
|
|
1659
|
+
}
|
|
1650
1660
|
const start = Math.min(anchor.offset, focus.offset), end = Math.max(anchor.offset, focus.offset);
|
|
1651
1661
|
for (const index of nearby) for (const line of pages[index] ?? []) {
|
|
1652
1662
|
let rectangle2 = function(x, width, caret = false) {
|
|
@@ -1707,7 +1717,7 @@ function mountTextSelection(pageText, viewport, shells, pageBounds, nearby, sign
|
|
|
1707
1717
|
__name(paint, "paint");
|
|
1708
1718
|
function nearest(clientX, clientY) {
|
|
1709
1719
|
if (!logical.length) return;
|
|
1710
|
-
let result,
|
|
1720
|
+
let result, bestY = Infinity, bestX = Infinity;
|
|
1711
1721
|
let first = 0, last = shells.length - 1;
|
|
1712
1722
|
while (first < last) {
|
|
1713
1723
|
const middle = first + last >>> 1;
|
|
@@ -1732,9 +1742,9 @@ function mountTextSelection(pageText, viewport, shells, pageBounds, nearby, sign
|
|
|
1732
1742
|
const dy = Math.max(value.top - y, 0, y - value.top - value.height) * box.height / height;
|
|
1733
1743
|
for (const stop of line.stops) {
|
|
1734
1744
|
const dx = Math.abs(stop.x - x) * box.width / width;
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
1745
|
+
if (dy < bestY || dy === bestY && dx < bestX) {
|
|
1746
|
+
bestY = dy;
|
|
1747
|
+
bestX = dx;
|
|
1738
1748
|
result = stop;
|
|
1739
1749
|
}
|
|
1740
1750
|
}
|
|
@@ -1748,6 +1758,8 @@ function mountTextSelection(pageText, viewport, shells, pageBounds, nearby, sign
|
|
|
1748
1758
|
focus = stop;
|
|
1749
1759
|
if (!extend || !anchor) anchor = stop;
|
|
1750
1760
|
paint();
|
|
1761
|
+
onSelectionChange?.(currentSelection());
|
|
1762
|
+
if (input && document2.activeElement === viewport) input.focus({ preventScroll: true });
|
|
1751
1763
|
if (scroll) {
|
|
1752
1764
|
const shell = shells[stop.line.item.pageIndex];
|
|
1753
1765
|
const point = stop.line.transform.transformPoint({ x: stop.x, y: stop.line.item.selection.top });
|
|
@@ -1788,13 +1800,23 @@ function mountTextSelection(pageText, viewport, shells, pageBounds, nearby, sign
|
|
|
1788
1800
|
pointerPosition = { x: event.clientX, y: event.clientY };
|
|
1789
1801
|
select(nearest(event.clientX, event.clientY), true);
|
|
1790
1802
|
}, { signal });
|
|
1803
|
+
viewport.addEventListener("dblclick", (event) => {
|
|
1804
|
+
if (event.target.closest("button,input,textarea,select,a,[data-onodocs-attachment]")) return;
|
|
1805
|
+
const stop = nearest(event.clientX, event.clientY);
|
|
1806
|
+
if (!stop) return;
|
|
1807
|
+
const segment = [...new Intl.Segmenter(void 0, { granularity: "word" }).segment(text)].find((value) => stop.offset >= value.index && stop.offset < value.index + value.segment.length);
|
|
1808
|
+
if (!segment) return;
|
|
1809
|
+
event.preventDefault();
|
|
1810
|
+
anchor = logical.find((value) => value.offset >= segment.index);
|
|
1811
|
+
select(logical.find((value) => value.offset >= segment.index + segment.segment.length), true);
|
|
1812
|
+
}, { signal });
|
|
1791
1813
|
for (const type of ["pointerup", "pointercancel", "lostpointercapture"]) viewport.addEventListener(type, (event) => {
|
|
1792
1814
|
if (event.pointerId !== pointer) return;
|
|
1793
1815
|
pointer = void 0;
|
|
1794
1816
|
window.cancelAnimationFrame(frame);
|
|
1795
1817
|
}, { signal });
|
|
1796
1818
|
viewport.addEventListener("keydown", (event) => {
|
|
1797
|
-
if (event.target !== viewport || !logical.length) return;
|
|
1819
|
+
if (event.target !== viewport && event.target !== input || !logical.length || event.isComposing) return;
|
|
1798
1820
|
const command = event.ctrlKey || event.metaKey;
|
|
1799
1821
|
if (command && event.key.toLowerCase() === "a") {
|
|
1800
1822
|
event.preventDefault();
|
|
@@ -1813,7 +1835,11 @@ function mountTextSelection(pageText, viewport, shells, pageBounds, nearby, sign
|
|
|
1813
1835
|
else if (event.key === "ArrowLeft" || event.key === "ArrowRight") {
|
|
1814
1836
|
const delta = event.key === "ArrowLeft" ? -1 : 1;
|
|
1815
1837
|
if (!event.shiftKey && anchor.offset !== focus.offset) next = delta < 0 ? anchor.offset < focus.offset ? anchor : focus : anchor.offset > focus.offset ? anchor : focus;
|
|
1816
|
-
else {
|
|
1838
|
+
else if (command) {
|
|
1839
|
+
const words = [...new Intl.Segmenter(void 0, { granularity: "word" }).segment(text)].filter((value) => value.isWordLike);
|
|
1840
|
+
const offset = delta < 0 ? words.filter((value) => value.index < focus.offset).at(-1)?.index ?? 0 : words.find((value) => value.index > focus.offset)?.index ?? text.length;
|
|
1841
|
+
next = logical.find((value) => value.offset >= offset) ?? logical.at(-1);
|
|
1842
|
+
} else {
|
|
1817
1843
|
let index = logicalIndex.get(focus) + delta;
|
|
1818
1844
|
while (logical[index]?.offset === focus.offset) index += delta;
|
|
1819
1845
|
next = logical[Math.max(0, Math.min(logical.length - 1, index))];
|
|
@@ -1830,7 +1856,7 @@ function mountTextSelection(pageText, viewport, shells, pageBounds, nearby, sign
|
|
|
1830
1856
|
select(next, event.shiftKey, true);
|
|
1831
1857
|
}, { signal });
|
|
1832
1858
|
document2.addEventListener("copy", (event) => {
|
|
1833
|
-
if (document2.activeElement !== viewport && !menu?.contains(document2.activeElement) || !focus || anchor.offset === focus.offset) return;
|
|
1859
|
+
if (document2.activeElement !== viewport && document2.activeElement !== input && !menu?.contains(document2.activeElement) || !focus || anchor.offset === focus.offset) return;
|
|
1834
1860
|
event.clipboardData?.setData("text/plain", selectedText());
|
|
1835
1861
|
event.preventDefault();
|
|
1836
1862
|
}, { signal });
|
|
@@ -1918,7 +1944,25 @@ function mountTextSelection(pageText, viewport, shells, pageBounds, nearby, sign
|
|
|
1918
1944
|
window.cancelAnimationFrame(frame);
|
|
1919
1945
|
for (const layer of layers.values()) layer.remove();
|
|
1920
1946
|
}, { once: true });
|
|
1921
|
-
|
|
1947
|
+
function currentSelection() {
|
|
1948
|
+
if (!anchor?.position || !focus?.position) return;
|
|
1949
|
+
return anchor.offset <= focus.offset ? { start: anchor.position, end: focus.position } : { start: focus.position, end: anchor.position };
|
|
1950
|
+
}
|
|
1951
|
+
__name(currentSelection, "currentSelection");
|
|
1952
|
+
const byParagraph = /* @__PURE__ */ new Map();
|
|
1953
|
+
for (const stop of logical) if (stop.position) {
|
|
1954
|
+
let offsets = byParagraph.get(stop.position.paragraphId);
|
|
1955
|
+
if (!offsets) byParagraph.set(stop.position.paragraphId, offsets = /* @__PURE__ */ new Map());
|
|
1956
|
+
offsets.set(stop.position.offset, stop);
|
|
1957
|
+
}
|
|
1958
|
+
return { paint, get selection() {
|
|
1959
|
+
return currentSelection();
|
|
1960
|
+
}, setSelection(value) {
|
|
1961
|
+
const start = byParagraph.get(value.start.paragraphId)?.get(value.start.offset), end = byParagraph.get(value.end.paragraphId)?.get(value.end.offset);
|
|
1962
|
+
if (!start || !end) return;
|
|
1963
|
+
anchor = start;
|
|
1964
|
+
select(end, true, true);
|
|
1965
|
+
} };
|
|
1922
1966
|
}
|
|
1923
1967
|
__name(mountTextSelection, "mountTextSelection");
|
|
1924
1968
|
|
|
@@ -1965,10 +2009,10 @@ function mountDocument(document2, container, options = {}, onDispose) {
|
|
|
1965
2009
|
let selection;
|
|
1966
2010
|
let selectionLifetime;
|
|
1967
2011
|
function prepareSelection(event) {
|
|
1968
|
-
if (event
|
|
2012
|
+
if (event?.target?.closest("[data-onodocs-attachment]")) return;
|
|
1969
2013
|
if (selection) return;
|
|
1970
2014
|
selectionLifetime = new AbortController();
|
|
1971
|
-
selection = mountTextSelection(document2.pages.flatMap((page) => page.interaction.text), element, shells.map((entry) => entry.shell), document2.pages.map((page) => page.size), nearby, AbortSignal.any([lifetime.signal, selectionLifetime.signal]));
|
|
2015
|
+
selection = mountTextSelection(document2.pages.flatMap((page) => page.interaction.text), element, shells.map((entry) => entry.shell), document2.pages.map((page) => page.size), nearby, AbortSignal.any([lifetime.signal, selectionLifetime.signal]), options.input, options.onSelectionChange);
|
|
1972
2016
|
}
|
|
1973
2017
|
__name(prepareSelection, "prepareSelection");
|
|
1974
2018
|
for (const type of ["pointerdown", "keydown", "contextmenu", "focus"]) element.addEventListener(type, prepareSelection, { capture: true, signal: lifetime.signal });
|
|
@@ -2041,6 +2085,14 @@ function mountDocument(document2, container, options = {}, onDispose) {
|
|
|
2041
2085
|
}
|
|
2042
2086
|
const view = {
|
|
2043
2087
|
element,
|
|
2088
|
+
get selection() {
|
|
2089
|
+
return selection?.selection;
|
|
2090
|
+
},
|
|
2091
|
+
setSelection(value) {
|
|
2092
|
+
checkOpen();
|
|
2093
|
+
prepareSelection();
|
|
2094
|
+
selection.setSelection(value);
|
|
2095
|
+
},
|
|
2044
2096
|
get disposed() {
|
|
2045
2097
|
return disposed;
|
|
2046
2098
|
},
|
|
@@ -2420,9 +2472,30 @@ function createDocument(source, options = {}) {
|
|
|
2420
2472
|
__name(loadFont, "loadFont");
|
|
2421
2473
|
}
|
|
2422
2474
|
__name(createDocument, "createDocument");
|
|
2475
|
+
async function openDocument(url, options = {}) {
|
|
2476
|
+
const endpoint = new URL(url, document.baseURI);
|
|
2477
|
+
const manifest = await load(endpoint, options.signal);
|
|
2478
|
+
return createDocument({ pages: manifest.pages.map((page) => ({
|
|
2479
|
+
...page,
|
|
2480
|
+
load(signal) {
|
|
2481
|
+
const url2 = new URL(endpoint);
|
|
2482
|
+
url2.pathname = `${url2.pathname.replace(/\/$/, "")}/pages/${page.index}`;
|
|
2483
|
+
return load(url2, signal);
|
|
2484
|
+
}
|
|
2485
|
+
})) }, options);
|
|
2486
|
+
async function load(url2, signal) {
|
|
2487
|
+
const signals = [signal, options.signal].filter((value) => value !== void 0);
|
|
2488
|
+
const response = await fetch(url2, { cache: "no-store", ...options.request, method: "GET", signal: AbortSignal.any(signals) });
|
|
2489
|
+
if (!response.ok) throw new Error(`Document request failed (HTTP ${response.status}).`);
|
|
2490
|
+
return decode(await response.text());
|
|
2491
|
+
}
|
|
2492
|
+
__name(load, "load");
|
|
2493
|
+
}
|
|
2494
|
+
__name(openDocument, "openDocument");
|
|
2423
2495
|
export {
|
|
2424
2496
|
createDocument,
|
|
2425
2497
|
decode,
|
|
2426
2498
|
encode,
|
|
2427
|
-
materializePageDisplayListToCanvas
|
|
2499
|
+
materializePageDisplayListToCanvas,
|
|
2500
|
+
openDocument
|
|
2428
2501
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@onodocs/canvas",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "OnoDocs canvas viewer, selection and HTML attachments for prepared Word document pages.",
|
|
6
6
|
"homepage": "https://onodocs.com/developers/",
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
"publishConfig": {
|
|
16
16
|
"access": "public"
|
|
17
17
|
},
|
|
18
|
-
"onodocsReleaseDate": "2026-10-
|
|
18
|
+
"onodocsReleaseDate": "2026-10-08",
|
|
19
19
|
"onodocsEnvironment": "live",
|
|
20
20
|
"type": "module",
|
|
21
21
|
"sideEffects": false,
|
|
@@ -40,5 +40,5 @@
|
|
|
40
40
|
"type": "git",
|
|
41
41
|
"url": "https://github.com/onodocs/onodocs.git"
|
|
42
42
|
},
|
|
43
|
-
"gitHead": "
|
|
43
|
+
"gitHead": "cf082bcfda5fc7581332b4251fa27c39a9a66ec7"
|
|
44
44
|
}
|
package/types/canvas/index.d.ts
CHANGED
|
@@ -27,4 +27,11 @@ export declare function createDocument(source: DocumentSource, options?: Readonl
|
|
|
27
27
|
container?: HTMLElement;
|
|
28
28
|
viewOptions?: ViewOptions;
|
|
29
29
|
}>): CanvasDocument;
|
|
30
|
+
export interface RemoteDocumentOptions {
|
|
31
|
+
readonly container?: HTMLElement;
|
|
32
|
+
readonly viewOptions?: ViewOptions;
|
|
33
|
+
readonly signal?: AbortSignal;
|
|
34
|
+
readonly request?: Omit<RequestInit, "method" | "body" | "signal">;
|
|
35
|
+
}
|
|
36
|
+
export declare function openDocument(url: string | URL, options?: RemoteDocumentOptions): Promise<CanvasDocument>;
|
|
30
37
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
/** Spec: pipeline.semantic.document-update */
|
|
2
2
|
/** Spec: pipeline.sdk.presentation */
|
|
3
|
-
import type { DocumentFragment, DocumentHit, DocumentPoint } from "../render-api/index.js";
|
|
3
|
+
import type { DocumentFragment, DocumentHit, DocumentPoint, DocumentSelection } from "../render-api/index.js";
|
|
4
4
|
import type { DocumentPage, PageSize } from "./index.js";
|
|
5
5
|
export interface ViewOptions {
|
|
6
6
|
readonly zoom?: number | "fit-width";
|
|
7
7
|
readonly gap?: number;
|
|
8
8
|
readonly title?: string;
|
|
9
|
+
readonly input?: HTMLTextAreaElement;
|
|
10
|
+
readonly onSelectionChange?: (selection: DocumentSelection | undefined) => void;
|
|
9
11
|
}
|
|
10
12
|
export interface DocumentAttachment {
|
|
11
13
|
readonly element: HTMLElement;
|
|
@@ -42,6 +44,8 @@ export interface DocumentView {
|
|
|
42
44
|
point: DocumentPoint;
|
|
43
45
|
}> | undefined;
|
|
44
46
|
hitTest(point: DocumentPoint): DocumentHit | undefined;
|
|
47
|
+
readonly selection: DocumentSelection | undefined;
|
|
48
|
+
setSelection(selection: DocumentSelection): void;
|
|
45
49
|
scrollTo(anchor: ViewAnchor, options?: ScrollIntoViewOptions): boolean;
|
|
46
50
|
attach(element: HTMLElement, options: AttachmentOptions): DocumentAttachment;
|
|
47
51
|
dispose(): void;
|
|
@@ -1,7 +1,9 @@
|
|
|
1
|
-
import type { PageTextLine } from "../render-api/index.js";
|
|
1
|
+
import type { DocumentSelection, PageTextLine } from "../render-api/index.js";
|
|
2
2
|
import type { DocumentPage } from "./index.js";
|
|
3
3
|
/** Spec: pipeline.sdk.presentation */
|
|
4
|
-
export declare function mountTextSelection(pageText: readonly PageTextLine[], viewport: HTMLElement, shells: readonly HTMLElement[], pageBounds: readonly DocumentPage["size"][], nearby: ReadonlySet<number>, signal: AbortSignal): {
|
|
4
|
+
export declare function mountTextSelection(pageText: readonly PageTextLine[], viewport: HTMLElement, shells: readonly HTMLElement[], pageBounds: readonly DocumentPage["size"][], nearby: ReadonlySet<number>, signal: AbortSignal, input?: HTMLTextAreaElement, onSelectionChange?: (selection: DocumentSelection | undefined) => void): {
|
|
5
5
|
paint: () => void;
|
|
6
|
+
readonly selection: DocumentSelection | undefined;
|
|
7
|
+
setSelection(value: DocumentSelection): void;
|
|
6
8
|
};
|
|
7
9
|
//# sourceMappingURL=text-selection.d.ts.map
|
|
@@ -3,6 +3,14 @@ export interface DocumentPoint {
|
|
|
3
3
|
readonly x: number;
|
|
4
4
|
readonly y: number;
|
|
5
5
|
}
|
|
6
|
+
export interface ParagraphPosition {
|
|
7
|
+
readonly paragraphId: string;
|
|
8
|
+
readonly offset: number;
|
|
9
|
+
}
|
|
10
|
+
export interface DocumentSelection {
|
|
11
|
+
readonly start: ParagraphPosition;
|
|
12
|
+
readonly end: ParagraphPosition;
|
|
13
|
+
}
|
|
6
14
|
export interface PageRectangle extends DocumentPoint {
|
|
7
15
|
readonly width: number;
|
|
8
16
|
readonly height: number;
|
|
@@ -54,6 +62,7 @@ export interface PageTextLine {
|
|
|
54
62
|
carets: readonly Readonly<{
|
|
55
63
|
scalarOffset: number;
|
|
56
64
|
x: number;
|
|
65
|
+
paragraphOffset?: number;
|
|
57
66
|
}>[];
|
|
58
67
|
}>;
|
|
59
68
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
export * from "./page-display-list.js";
|
|
2
2
|
export * from "./page-source.js";
|
|
3
|
-
export type { DocumentFragment, DocumentHit, DocumentPoint, PageTextLine, PageInteraction, FontFaceResource, ImageResourceData } from "../foundation/index.js";
|
|
3
|
+
export type { DocumentFragment, DocumentHit, DocumentPoint, DocumentSelection, ParagraphPosition, PageTextLine, PageInteraction, FontFaceResource, ImageResourceData } from "../foundation/index.js";
|
|
4
4
|
//# sourceMappingURL=index.d.ts.map
|