@rtif-sdk/web 1.0.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/LICENSE +21 -0
- package/README.md +67 -0
- package/dist/block-drag-handler.d.ts +189 -0
- package/dist/block-drag-handler.d.ts.map +1 -0
- package/dist/block-drag-handler.js +745 -0
- package/dist/block-drag-handler.js.map +1 -0
- package/dist/block-renderer.d.ts +402 -0
- package/dist/block-renderer.d.ts.map +1 -0
- package/dist/block-renderer.js +424 -0
- package/dist/block-renderer.js.map +1 -0
- package/dist/clipboard.d.ts +178 -0
- package/dist/clipboard.d.ts.map +1 -0
- package/dist/clipboard.js +432 -0
- package/dist/clipboard.js.map +1 -0
- package/dist/command-bus.d.ts +113 -0
- package/dist/command-bus.d.ts.map +1 -0
- package/dist/command-bus.js +70 -0
- package/dist/command-bus.js.map +1 -0
- package/dist/composition.d.ts +220 -0
- package/dist/composition.d.ts.map +1 -0
- package/dist/composition.js +271 -0
- package/dist/composition.js.map +1 -0
- package/dist/content-extraction.d.ts +69 -0
- package/dist/content-extraction.d.ts.map +1 -0
- package/dist/content-extraction.js +228 -0
- package/dist/content-extraction.js.map +1 -0
- package/dist/content-handler-file.d.ts +40 -0
- package/dist/content-handler-file.d.ts.map +1 -0
- package/dist/content-handler-file.js +91 -0
- package/dist/content-handler-file.js.map +1 -0
- package/dist/content-handler-image.d.ts +82 -0
- package/dist/content-handler-image.d.ts.map +1 -0
- package/dist/content-handler-image.js +120 -0
- package/dist/content-handler-image.js.map +1 -0
- package/dist/content-handler-url.d.ts +129 -0
- package/dist/content-handler-url.d.ts.map +1 -0
- package/dist/content-handler-url.js +244 -0
- package/dist/content-handler-url.js.map +1 -0
- package/dist/content-handlers.d.ts +67 -0
- package/dist/content-handlers.d.ts.map +1 -0
- package/dist/content-handlers.js +263 -0
- package/dist/content-handlers.js.map +1 -0
- package/dist/content-pipeline.d.ts +383 -0
- package/dist/content-pipeline.d.ts.map +1 -0
- package/dist/content-pipeline.js +232 -0
- package/dist/content-pipeline.js.map +1 -0
- package/dist/cursor-nav.d.ts +149 -0
- package/dist/cursor-nav.d.ts.map +1 -0
- package/dist/cursor-nav.js +230 -0
- package/dist/cursor-nav.js.map +1 -0
- package/dist/cursor-rect.d.ts +65 -0
- package/dist/cursor-rect.d.ts.map +1 -0
- package/dist/cursor-rect.js +98 -0
- package/dist/cursor-rect.js.map +1 -0
- package/dist/drop-indicator.d.ts +108 -0
- package/dist/drop-indicator.d.ts.map +1 -0
- package/dist/drop-indicator.js +236 -0
- package/dist/drop-indicator.js.map +1 -0
- package/dist/editor.d.ts +41 -0
- package/dist/editor.d.ts.map +1 -0
- package/dist/editor.js +710 -0
- package/dist/editor.js.map +1 -0
- package/dist/floating-toolbar.d.ts +93 -0
- package/dist/floating-toolbar.d.ts.map +1 -0
- package/dist/floating-toolbar.js +159 -0
- package/dist/floating-toolbar.js.map +1 -0
- package/dist/index.d.ts +62 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +119 -0
- package/dist/index.js.map +1 -0
- package/dist/input-bridge.d.ts +273 -0
- package/dist/input-bridge.d.ts.map +1 -0
- package/dist/input-bridge.js +884 -0
- package/dist/input-bridge.js.map +1 -0
- package/dist/link-popover.d.ts +38 -0
- package/dist/link-popover.d.ts.map +1 -0
- package/dist/link-popover.js +278 -0
- package/dist/link-popover.js.map +1 -0
- package/dist/mark-renderer.d.ts +275 -0
- package/dist/mark-renderer.d.ts.map +1 -0
- package/dist/mark-renderer.js +210 -0
- package/dist/mark-renderer.js.map +1 -0
- package/dist/perf.d.ts +145 -0
- package/dist/perf.d.ts.map +1 -0
- package/dist/perf.js +260 -0
- package/dist/perf.js.map +1 -0
- package/dist/plugin-kit.d.ts +265 -0
- package/dist/plugin-kit.d.ts.map +1 -0
- package/dist/plugin-kit.js +234 -0
- package/dist/plugin-kit.js.map +1 -0
- package/dist/plugins/alignment-plugin.d.ts +68 -0
- package/dist/plugins/alignment-plugin.d.ts.map +1 -0
- package/dist/plugins/alignment-plugin.js +98 -0
- package/dist/plugins/alignment-plugin.js.map +1 -0
- package/dist/plugins/block-utils.d.ts +113 -0
- package/dist/plugins/block-utils.d.ts.map +1 -0
- package/dist/plugins/block-utils.js +191 -0
- package/dist/plugins/block-utils.js.map +1 -0
- package/dist/plugins/blockquote-plugin.d.ts +39 -0
- package/dist/plugins/blockquote-plugin.d.ts.map +1 -0
- package/dist/plugins/blockquote-plugin.js +88 -0
- package/dist/plugins/blockquote-plugin.js.map +1 -0
- package/dist/plugins/bold-plugin.d.ts +37 -0
- package/dist/plugins/bold-plugin.d.ts.map +1 -0
- package/dist/plugins/bold-plugin.js +48 -0
- package/dist/plugins/bold-plugin.js.map +1 -0
- package/dist/plugins/callout-plugin.d.ts +100 -0
- package/dist/plugins/callout-plugin.d.ts.map +1 -0
- package/dist/plugins/callout-plugin.js +200 -0
- package/dist/plugins/callout-plugin.js.map +1 -0
- package/dist/plugins/code-block-plugin.d.ts +62 -0
- package/dist/plugins/code-block-plugin.d.ts.map +1 -0
- package/dist/plugins/code-block-plugin.js +176 -0
- package/dist/plugins/code-block-plugin.js.map +1 -0
- package/dist/plugins/code-plugin.d.ts +37 -0
- package/dist/plugins/code-plugin.d.ts.map +1 -0
- package/dist/plugins/code-plugin.js +48 -0
- package/dist/plugins/code-plugin.js.map +1 -0
- package/dist/plugins/embed-plugin.d.ts +90 -0
- package/dist/plugins/embed-plugin.d.ts.map +1 -0
- package/dist/plugins/embed-plugin.js +147 -0
- package/dist/plugins/embed-plugin.js.map +1 -0
- package/dist/plugins/font-family-plugin.d.ts +58 -0
- package/dist/plugins/font-family-plugin.d.ts.map +1 -0
- package/dist/plugins/font-family-plugin.js +57 -0
- package/dist/plugins/font-family-plugin.js.map +1 -0
- package/dist/plugins/font-size-plugin.d.ts +57 -0
- package/dist/plugins/font-size-plugin.d.ts.map +1 -0
- package/dist/plugins/font-size-plugin.js +56 -0
- package/dist/plugins/font-size-plugin.js.map +1 -0
- package/dist/plugins/heading-plugin.d.ts +52 -0
- package/dist/plugins/heading-plugin.d.ts.map +1 -0
- package/dist/plugins/heading-plugin.js +114 -0
- package/dist/plugins/heading-plugin.js.map +1 -0
- package/dist/plugins/hr-plugin.d.ts +33 -0
- package/dist/plugins/hr-plugin.d.ts.map +1 -0
- package/dist/plugins/hr-plugin.js +75 -0
- package/dist/plugins/hr-plugin.js.map +1 -0
- package/dist/plugins/image-plugin.d.ts +115 -0
- package/dist/plugins/image-plugin.d.ts.map +1 -0
- package/dist/plugins/image-plugin.js +199 -0
- package/dist/plugins/image-plugin.js.map +1 -0
- package/dist/plugins/indent-plugin.d.ts +62 -0
- package/dist/plugins/indent-plugin.d.ts.map +1 -0
- package/dist/plugins/indent-plugin.js +128 -0
- package/dist/plugins/indent-plugin.js.map +1 -0
- package/dist/plugins/index.d.ts +45 -0
- package/dist/plugins/index.d.ts.map +1 -0
- package/dist/plugins/index.js +42 -0
- package/dist/plugins/index.js.map +1 -0
- package/dist/plugins/italic-plugin.d.ts +37 -0
- package/dist/plugins/italic-plugin.d.ts.map +1 -0
- package/dist/plugins/italic-plugin.js +48 -0
- package/dist/plugins/italic-plugin.js.map +1 -0
- package/dist/plugins/link-plugin.d.ts +129 -0
- package/dist/plugins/link-plugin.d.ts.map +1 -0
- package/dist/plugins/link-plugin.js +212 -0
- package/dist/plugins/link-plugin.js.map +1 -0
- package/dist/plugins/list-plugin.d.ts +53 -0
- package/dist/plugins/list-plugin.d.ts.map +1 -0
- package/dist/plugins/list-plugin.js +309 -0
- package/dist/plugins/list-plugin.js.map +1 -0
- package/dist/plugins/mark-utils.d.ts +173 -0
- package/dist/plugins/mark-utils.d.ts.map +1 -0
- package/dist/plugins/mark-utils.js +425 -0
- package/dist/plugins/mark-utils.js.map +1 -0
- package/dist/plugins/mention-plugin.d.ts +191 -0
- package/dist/plugins/mention-plugin.d.ts.map +1 -0
- package/dist/plugins/mention-plugin.js +295 -0
- package/dist/plugins/mention-plugin.js.map +1 -0
- package/dist/plugins/strikethrough-plugin.d.ts +37 -0
- package/dist/plugins/strikethrough-plugin.d.ts.map +1 -0
- package/dist/plugins/strikethrough-plugin.js +48 -0
- package/dist/plugins/strikethrough-plugin.js.map +1 -0
- package/dist/plugins/text-color-plugin.d.ts +57 -0
- package/dist/plugins/text-color-plugin.d.ts.map +1 -0
- package/dist/plugins/text-color-plugin.js +56 -0
- package/dist/plugins/text-color-plugin.js.map +1 -0
- package/dist/plugins/underline-plugin.d.ts +37 -0
- package/dist/plugins/underline-plugin.d.ts.map +1 -0
- package/dist/plugins/underline-plugin.js +48 -0
- package/dist/plugins/underline-plugin.js.map +1 -0
- package/dist/presets.d.ts +95 -0
- package/dist/presets.d.ts.map +1 -0
- package/dist/presets.js +159 -0
- package/dist/presets.js.map +1 -0
- package/dist/renderer.d.ts +125 -0
- package/dist/renderer.d.ts.map +1 -0
- package/dist/renderer.js +415 -0
- package/dist/renderer.js.map +1 -0
- package/dist/scroll-to-cursor.d.ts +25 -0
- package/dist/scroll-to-cursor.d.ts.map +1 -0
- package/dist/scroll-to-cursor.js +59 -0
- package/dist/scroll-to-cursor.js.map +1 -0
- package/dist/selection-sync.d.ts +159 -0
- package/dist/selection-sync.d.ts.map +1 -0
- package/dist/selection-sync.js +527 -0
- package/dist/selection-sync.js.map +1 -0
- package/dist/shortcut-handler.d.ts +98 -0
- package/dist/shortcut-handler.d.ts.map +1 -0
- package/dist/shortcut-handler.js +155 -0
- package/dist/shortcut-handler.js.map +1 -0
- package/dist/toolbar.d.ts +103 -0
- package/dist/toolbar.d.ts.map +1 -0
- package/dist/toolbar.js +134 -0
- package/dist/toolbar.js.map +1 -0
- package/dist/trigger-manager.d.ts +205 -0
- package/dist/trigger-manager.d.ts.map +1 -0
- package/dist/trigger-manager.js +466 -0
- package/dist/trigger-manager.js.map +1 -0
- package/dist/types.d.ts +216 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/package.json +30 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"selection-sync.d.ts","sourceRoot":"","sources":["../src/selection-sync.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,KAAK,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAE1D,OAAO,KAAK,EAAE,gBAAgB,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAS7D;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,QAAQ,GAAG,gBAAgB,EAAE,CAmBvE;AAMD;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,oBAAoB,CAClC,IAAI,EAAE,WAAW,EACjB,IAAI,EAAE,IAAI,EACV,SAAS,EAAE,MAAM,EACjB,KAAK,EAAE,gBAAgB,EAAE,GACxB,MAAM,GAAG,IAAI,CAoCf;AAoOD;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,oBAAoB,CAClC,IAAI,EAAE,WAAW,EACjB,GAAG,EAAE,QAAQ,EACb,MAAM,EAAE,MAAM,GACb,QAAQ,GAAG,IAAI,CAmBjB;AAuGD;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,gBAAgB,CAC9B,IAAI,EAAE,WAAW,EACjB,KAAK,EAAE,gBAAgB,EAAE,GACxB,SAAS,GAAG,IAAI,CAsBlB;AAMD;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAgB,eAAe,CAC7B,IAAI,EAAE,WAAW,EACjB,GAAG,EAAE,QAAQ,EACb,SAAS,EAAE,SAAS,GACnB,IAAI,CA6BN;AAMD;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,YAAY,IAAI,OAAO,CAEtC;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,gBAAgB,IAAI,IAAI,CAEvC"}
|
|
@@ -0,0 +1,527 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bidirectional DOM Selection <-> RTIF Selection synchronization.
|
|
3
|
+
*
|
|
4
|
+
* Converts between the browser's Selection API (anchorNode/offset,
|
|
5
|
+
* focusNode/offset) and RTIF's absolute integer offset Selection
|
|
6
|
+
* ({anchor: {offset}, focus: {offset}}).
|
|
7
|
+
*
|
|
8
|
+
* Relies on the DOM structure produced by the renderer:
|
|
9
|
+
* - Root: `[data-rtif-root]` contenteditable div
|
|
10
|
+
* - Blocks: `div[data-rtif-block="{id}"]`
|
|
11
|
+
* - Spans: `span[data-rtif-span]` with Text node or `<br>` children
|
|
12
|
+
*/
|
|
13
|
+
import { blockTextLength, resolve } from '@rtif-sdk/core';
|
|
14
|
+
/** Module-level flag to suppress selectionchange feedback loops. */
|
|
15
|
+
let suppressSelectionChange = false;
|
|
16
|
+
// ---------------------------------------------------------------------------
|
|
17
|
+
// buildBlockOffsetCache
|
|
18
|
+
// ---------------------------------------------------------------------------
|
|
19
|
+
/**
|
|
20
|
+
* Build an array of block offset entries for fast DOM <-> RTIF conversion.
|
|
21
|
+
*
|
|
22
|
+
* Each entry records the block's ID, its absolute start offset in the
|
|
23
|
+
* document, and its text length. Block N+1 starts at
|
|
24
|
+
* `block[N].startOffset + block[N].length + 1` (the +1 is the virtual `\n`
|
|
25
|
+
* separator between consecutive blocks).
|
|
26
|
+
*
|
|
27
|
+
* @param doc - The RTIF document
|
|
28
|
+
* @returns Array of offset entries, one per block, in document order
|
|
29
|
+
*
|
|
30
|
+
* @example
|
|
31
|
+
* ```ts
|
|
32
|
+
* const doc = { version: 1, blocks: [
|
|
33
|
+
* { id: 'b1', type: 'text', spans: [{ text: 'hello' }] },
|
|
34
|
+
* { id: 'b2', type: 'text', spans: [{ text: 'world' }] },
|
|
35
|
+
* ]};
|
|
36
|
+
* buildBlockOffsetCache(doc);
|
|
37
|
+
* // => [
|
|
38
|
+
* // { blockId: 'b1', startOffset: 0, length: 5 },
|
|
39
|
+
* // { blockId: 'b2', startOffset: 6, length: 5 },
|
|
40
|
+
* // ]
|
|
41
|
+
* ```
|
|
42
|
+
*/
|
|
43
|
+
export function buildBlockOffsetCache(doc) {
|
|
44
|
+
const entries = [];
|
|
45
|
+
let offset = 0;
|
|
46
|
+
for (let i = 0; i < doc.blocks.length; i++) {
|
|
47
|
+
if (i > 0) {
|
|
48
|
+
offset += 1; // virtual \n separator
|
|
49
|
+
}
|
|
50
|
+
const block = doc.blocks[i];
|
|
51
|
+
const length = blockTextLength(block);
|
|
52
|
+
entries.push({
|
|
53
|
+
blockId: block.id,
|
|
54
|
+
startOffset: offset,
|
|
55
|
+
length,
|
|
56
|
+
});
|
|
57
|
+
offset += length;
|
|
58
|
+
}
|
|
59
|
+
return entries;
|
|
60
|
+
}
|
|
61
|
+
// ---------------------------------------------------------------------------
|
|
62
|
+
// domPointToRtifOffset
|
|
63
|
+
// ---------------------------------------------------------------------------
|
|
64
|
+
/**
|
|
65
|
+
* Convert a DOM position (node + offset) to an absolute RTIF document offset.
|
|
66
|
+
*
|
|
67
|
+
* Walks up from the given node to find the containing block element
|
|
68
|
+
* (`[data-rtif-block]`), then walks the block's span children to compute the
|
|
69
|
+
* local character offset. Adds the block's start offset from the cache.
|
|
70
|
+
*
|
|
71
|
+
* @param root - The editor's root element (`[data-rtif-root]`)
|
|
72
|
+
* @param node - The DOM node containing the position
|
|
73
|
+
* @param domOffset - The offset within the node (character offset for Text nodes,
|
|
74
|
+
* child index for Element nodes)
|
|
75
|
+
* @param cache - Pre-built block offset cache from `buildBlockOffsetCache`
|
|
76
|
+
* @returns The absolute RTIF offset, or `null` if the node is outside the
|
|
77
|
+
* editor or not inside a recognized block
|
|
78
|
+
*
|
|
79
|
+
* @example
|
|
80
|
+
* ```ts
|
|
81
|
+
* const textNode = blockEl.querySelector('[data-rtif-span]')!.firstChild!;
|
|
82
|
+
* const rtifOffset = domPointToRtifOffset(root, textNode, 3, cache);
|
|
83
|
+
* ```
|
|
84
|
+
*/
|
|
85
|
+
export function domPointToRtifOffset(root, node, domOffset, cache) {
|
|
86
|
+
// Special case: node IS the root element.
|
|
87
|
+
// domOffset is a child index referring to block elements.
|
|
88
|
+
if (node === root) {
|
|
89
|
+
return resolveRootOffset(root, domOffset, cache);
|
|
90
|
+
}
|
|
91
|
+
// Verify node is inside root
|
|
92
|
+
if (!root.contains(node)) {
|
|
93
|
+
return null;
|
|
94
|
+
}
|
|
95
|
+
// Find the containing block element
|
|
96
|
+
const blockEl = findContainingBlock(root, node);
|
|
97
|
+
if (blockEl === null) {
|
|
98
|
+
return null;
|
|
99
|
+
}
|
|
100
|
+
const blockId = blockEl.getAttribute('data-rtif-block');
|
|
101
|
+
if (blockId === null) {
|
|
102
|
+
return null;
|
|
103
|
+
}
|
|
104
|
+
// Look up block in cache
|
|
105
|
+
const cacheEntry = cache.find((e) => e.blockId === blockId);
|
|
106
|
+
if (cacheEntry === undefined) {
|
|
107
|
+
return null;
|
|
108
|
+
}
|
|
109
|
+
// Compute local offset within the block
|
|
110
|
+
const localOffset = computeLocalOffset(blockEl, node, domOffset);
|
|
111
|
+
if (localOffset === null) {
|
|
112
|
+
return null;
|
|
113
|
+
}
|
|
114
|
+
return cacheEntry.startOffset + localOffset;
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Handle the case where `node` is the root element itself.
|
|
118
|
+
* `domOffset` is a child index referring to block-level children.
|
|
119
|
+
*/
|
|
120
|
+
function resolveRootOffset(root, childIndex, cache) {
|
|
121
|
+
if (childIndex === 0) {
|
|
122
|
+
// Before first block
|
|
123
|
+
const first = cache[0];
|
|
124
|
+
return first !== undefined ? first.startOffset : null;
|
|
125
|
+
}
|
|
126
|
+
// After the block at childIndex - 1
|
|
127
|
+
const blockEl = root.children[childIndex - 1];
|
|
128
|
+
if (blockEl === undefined) {
|
|
129
|
+
// childIndex is past the last block — end of document
|
|
130
|
+
const last = cache[cache.length - 1];
|
|
131
|
+
return last !== undefined ? last.startOffset + last.length : null;
|
|
132
|
+
}
|
|
133
|
+
const blockId = blockEl.getAttribute('data-rtif-block');
|
|
134
|
+
if (blockId === null) {
|
|
135
|
+
return null;
|
|
136
|
+
}
|
|
137
|
+
const entry = cache.find((e) => e.blockId === blockId);
|
|
138
|
+
if (entry === undefined) {
|
|
139
|
+
return null;
|
|
140
|
+
}
|
|
141
|
+
// After this block: if there's a next block, return its start
|
|
142
|
+
const entryIndex = cache.indexOf(entry);
|
|
143
|
+
const nextEntry = cache[entryIndex + 1];
|
|
144
|
+
if (nextEntry !== undefined) {
|
|
145
|
+
return nextEntry.startOffset;
|
|
146
|
+
}
|
|
147
|
+
// After the last block — end of document
|
|
148
|
+
return entry.startOffset + entry.length;
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* Walk up the DOM from `node` to find the nearest ancestor (or self) that
|
|
152
|
+
* is a block element (`[data-rtif-block]`) within the root.
|
|
153
|
+
*/
|
|
154
|
+
function findContainingBlock(root, node) {
|
|
155
|
+
let current = node;
|
|
156
|
+
while (current !== null && current !== root) {
|
|
157
|
+
if (current instanceof HTMLElement &&
|
|
158
|
+
current.hasAttribute('data-rtif-block')) {
|
|
159
|
+
return current;
|
|
160
|
+
}
|
|
161
|
+
current = current.parentNode;
|
|
162
|
+
}
|
|
163
|
+
return null;
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* Compute the local character offset within a block element.
|
|
167
|
+
*
|
|
168
|
+
* Handles three cases:
|
|
169
|
+
* 1. `node` is the block element itself — `domOffset` is a child (span) index.
|
|
170
|
+
* 2. `node` is a span element (`[data-rtif-span]`) — `domOffset` is a child index.
|
|
171
|
+
* 3. `node` is a Text node or BR — walk preceding spans to count characters.
|
|
172
|
+
*/
|
|
173
|
+
function computeLocalOffset(blockEl, node, domOffset) {
|
|
174
|
+
// Case 1: node IS the block element
|
|
175
|
+
if (node === blockEl) {
|
|
176
|
+
return computeOffsetFromBlockChildIndex(blockEl, domOffset);
|
|
177
|
+
}
|
|
178
|
+
// Case 2: node is a span element with data-rtif-span
|
|
179
|
+
if (node instanceof HTMLElement &&
|
|
180
|
+
node.hasAttribute('data-rtif-span')) {
|
|
181
|
+
return computeOffsetFromSpanChildIndex(blockEl, node, domOffset);
|
|
182
|
+
}
|
|
183
|
+
// Case 3: node is a child of a span (Text node or BR)
|
|
184
|
+
const parentSpan = findParentSpan(node);
|
|
185
|
+
if (parentSpan === null) {
|
|
186
|
+
return null;
|
|
187
|
+
}
|
|
188
|
+
// Count characters in all preceding spans
|
|
189
|
+
const precedingChars = countPrecedingSpanChars(blockEl, parentSpan);
|
|
190
|
+
// For BR nodes (empty spans), the local offset is 0
|
|
191
|
+
if (node instanceof HTMLBRElement) {
|
|
192
|
+
return precedingChars;
|
|
193
|
+
}
|
|
194
|
+
// For Text nodes, add the domOffset within the text
|
|
195
|
+
if (node.nodeType === Node.TEXT_NODE) {
|
|
196
|
+
return precedingChars + domOffset;
|
|
197
|
+
}
|
|
198
|
+
return null;
|
|
199
|
+
}
|
|
200
|
+
/**
|
|
201
|
+
* When the cursor is on the block element itself, `domOffset` is a child index
|
|
202
|
+
* referring to span children. Sum text lengths of spans before that index.
|
|
203
|
+
*/
|
|
204
|
+
function computeOffsetFromBlockChildIndex(blockEl, childIndex) {
|
|
205
|
+
const spans = blockEl.querySelectorAll('[data-rtif-span]');
|
|
206
|
+
let offset = 0;
|
|
207
|
+
for (let i = 0; i < childIndex && i < spans.length; i++) {
|
|
208
|
+
offset += getSpanTextLength(spans[i]);
|
|
209
|
+
}
|
|
210
|
+
return offset;
|
|
211
|
+
}
|
|
212
|
+
/**
|
|
213
|
+
* When the cursor is on a span element, `domOffset` is a child index within
|
|
214
|
+
* that span. Count preceding spans + content up to the child index.
|
|
215
|
+
*/
|
|
216
|
+
function computeOffsetFromSpanChildIndex(blockEl, spanEl, childIndex) {
|
|
217
|
+
const precedingChars = countPrecedingSpanChars(blockEl, spanEl);
|
|
218
|
+
// If childIndex is 0, we're at the start of the span
|
|
219
|
+
if (childIndex === 0) {
|
|
220
|
+
return precedingChars;
|
|
221
|
+
}
|
|
222
|
+
// If childIndex >= 1, we're after the first child.
|
|
223
|
+
// For a span with a Text node child, this means end of text.
|
|
224
|
+
// For a span with a BR child, this still means offset 0 in text terms.
|
|
225
|
+
const firstChild = spanEl.firstChild;
|
|
226
|
+
if (firstChild !== null && firstChild.nodeType === Node.TEXT_NODE) {
|
|
227
|
+
return precedingChars + firstChild.textContent.length;
|
|
228
|
+
}
|
|
229
|
+
// BR or no content
|
|
230
|
+
return precedingChars;
|
|
231
|
+
}
|
|
232
|
+
/**
|
|
233
|
+
* Find the nearest parent `<span data-rtif-span>` element for a node.
|
|
234
|
+
*/
|
|
235
|
+
function findParentSpan(node) {
|
|
236
|
+
let current = node.parentNode;
|
|
237
|
+
while (current !== null) {
|
|
238
|
+
if (current instanceof HTMLElement &&
|
|
239
|
+
current.hasAttribute('data-rtif-span')) {
|
|
240
|
+
return current;
|
|
241
|
+
}
|
|
242
|
+
current = current.parentNode;
|
|
243
|
+
}
|
|
244
|
+
return null;
|
|
245
|
+
}
|
|
246
|
+
/**
|
|
247
|
+
* Count the total text length of all `[data-rtif-span]` elements in a block
|
|
248
|
+
* that appear before the given span element in document order.
|
|
249
|
+
*/
|
|
250
|
+
function countPrecedingSpanChars(blockEl, spanEl) {
|
|
251
|
+
const spans = blockEl.querySelectorAll('[data-rtif-span]');
|
|
252
|
+
let count = 0;
|
|
253
|
+
for (let i = 0; i < spans.length; i++) {
|
|
254
|
+
if (spans[i] === spanEl) {
|
|
255
|
+
break;
|
|
256
|
+
}
|
|
257
|
+
count += getSpanTextLength(spans[i]);
|
|
258
|
+
}
|
|
259
|
+
return count;
|
|
260
|
+
}
|
|
261
|
+
/**
|
|
262
|
+
* Escape a string for safe use inside a CSS attribute selector value.
|
|
263
|
+
* Replaces `\` and `"` with their escaped equivalents.
|
|
264
|
+
* This avoids depending on `CSS.escape()` which is not available in jsdom.
|
|
265
|
+
*/
|
|
266
|
+
function escapeAttrValue(value) {
|
|
267
|
+
return value.replace(/\\/g, '\\\\').replace(/"/g, '\\"');
|
|
268
|
+
}
|
|
269
|
+
/**
|
|
270
|
+
* Get the text length of a span element. A span with only a `<br>` child
|
|
271
|
+
* has length 0.
|
|
272
|
+
*/
|
|
273
|
+
function getSpanTextLength(spanEl) {
|
|
274
|
+
const firstChild = spanEl.firstChild;
|
|
275
|
+
if (firstChild === null) {
|
|
276
|
+
return 0;
|
|
277
|
+
}
|
|
278
|
+
if (firstChild.nodeType === Node.TEXT_NODE) {
|
|
279
|
+
return firstChild.textContent?.length ?? 0;
|
|
280
|
+
}
|
|
281
|
+
// BR or other element — length 0
|
|
282
|
+
return 0;
|
|
283
|
+
}
|
|
284
|
+
// ---------------------------------------------------------------------------
|
|
285
|
+
// rtifOffsetToDomPoint
|
|
286
|
+
// ---------------------------------------------------------------------------
|
|
287
|
+
/**
|
|
288
|
+
* Convert an absolute RTIF document offset to a DOM point (node + offset).
|
|
289
|
+
*
|
|
290
|
+
* Uses `resolve()` from `@rtif-sdk/core` to find the block index and local offset,
|
|
291
|
+
* then walks the block's span elements in the DOM to locate the correct text
|
|
292
|
+
* node and character position.
|
|
293
|
+
*
|
|
294
|
+
* @param root - The editor's root element (`[data-rtif-root]`)
|
|
295
|
+
* @param doc - The current RTIF document (must match the DOM structure)
|
|
296
|
+
* @param offset - The absolute RTIF document offset
|
|
297
|
+
* @returns A DOM point, or `null` if the DOM is out of sync with the document
|
|
298
|
+
*
|
|
299
|
+
* @example
|
|
300
|
+
* ```ts
|
|
301
|
+
* const point = rtifOffsetToDomPoint(root, doc, 5);
|
|
302
|
+
* if (point) {
|
|
303
|
+
* console.log(point.node, point.offset);
|
|
304
|
+
* }
|
|
305
|
+
* ```
|
|
306
|
+
*/
|
|
307
|
+
export function rtifOffsetToDomPoint(root, doc, offset) {
|
|
308
|
+
const resolved = resolve(doc, offset);
|
|
309
|
+
const block = doc.blocks[resolved.blockIndex];
|
|
310
|
+
if (block === undefined) {
|
|
311
|
+
return null;
|
|
312
|
+
}
|
|
313
|
+
const blockEl = root.querySelector(`[data-rtif-block="${escapeAttrValue(block.id)}"]`);
|
|
314
|
+
if (blockEl === null) {
|
|
315
|
+
return null;
|
|
316
|
+
}
|
|
317
|
+
return resolveLocalOffsetToDomPoint(blockEl, block, resolved.localOffset);
|
|
318
|
+
}
|
|
319
|
+
/**
|
|
320
|
+
* Walk span elements within a block to find the DOM point for a local offset.
|
|
321
|
+
*
|
|
322
|
+
* At span boundaries (localOffset equals the cumulative length at the end of
|
|
323
|
+
* a span), the point resolves to the end of that span's text node — consistent
|
|
324
|
+
* with RTIF's `insert_text` mark inheritance behavior.
|
|
325
|
+
*
|
|
326
|
+
* For empty blocks (single span with empty text / `<br>`), returns the span
|
|
327
|
+
* element itself at offset 0.
|
|
328
|
+
*/
|
|
329
|
+
function resolveLocalOffsetToDomPoint(blockEl, block, localOffset) {
|
|
330
|
+
const spanEls = blockEl.querySelectorAll('[data-rtif-span]');
|
|
331
|
+
// Handle empty block: single span with empty text
|
|
332
|
+
if (block.spans.length === 1 &&
|
|
333
|
+
block.spans[0].text === '' &&
|
|
334
|
+
spanEls.length > 0) {
|
|
335
|
+
const spanEl = spanEls[0];
|
|
336
|
+
return { node: spanEl, offset: 0 };
|
|
337
|
+
}
|
|
338
|
+
let remaining = localOffset;
|
|
339
|
+
for (let i = 0; i < spanEls.length; i++) {
|
|
340
|
+
const spanEl = spanEls[i];
|
|
341
|
+
const span = block.spans[i];
|
|
342
|
+
if (span === undefined) {
|
|
343
|
+
return null;
|
|
344
|
+
}
|
|
345
|
+
const spanLen = span.text.length;
|
|
346
|
+
// If remaining fits within (or at end of) this span, this is the target
|
|
347
|
+
if (remaining <= spanLen) {
|
|
348
|
+
// Check if this span is non-editable (e.g., mention chip)
|
|
349
|
+
if (spanEl.getAttribute('contenteditable') === 'false') {
|
|
350
|
+
const container = spanEl.parentElement;
|
|
351
|
+
if (!container)
|
|
352
|
+
return null;
|
|
353
|
+
const childIndex = getChildIndex(container, spanEl);
|
|
354
|
+
if (childIndex < 0)
|
|
355
|
+
return null;
|
|
356
|
+
if (remaining === 0) {
|
|
357
|
+
// Cursor at start of non-editable span → place before it
|
|
358
|
+
return { node: container, offset: childIndex };
|
|
359
|
+
}
|
|
360
|
+
// Cursor at end of or inside non-editable span → place after it
|
|
361
|
+
return { node: container, offset: childIndex + 1 };
|
|
362
|
+
}
|
|
363
|
+
const textNode = findTextNode(spanEl);
|
|
364
|
+
if (textNode === null) {
|
|
365
|
+
// Span has no text node (unexpected for non-empty span)
|
|
366
|
+
return null;
|
|
367
|
+
}
|
|
368
|
+
return { node: textNode, offset: remaining };
|
|
369
|
+
}
|
|
370
|
+
remaining -= spanLen;
|
|
371
|
+
}
|
|
372
|
+
// If we get here, the local offset exceeds the block text length.
|
|
373
|
+
// This shouldn't happen if resolve() works correctly, but handle gracefully.
|
|
374
|
+
return null;
|
|
375
|
+
}
|
|
376
|
+
/**
|
|
377
|
+
* Get the index of a child element within its parent's children.
|
|
378
|
+
* Returns -1 if the child is not found.
|
|
379
|
+
*/
|
|
380
|
+
function getChildIndex(parent, child) {
|
|
381
|
+
const children = parent.children;
|
|
382
|
+
for (let i = 0; i < children.length; i++) {
|
|
383
|
+
if (children[i] === child)
|
|
384
|
+
return i;
|
|
385
|
+
}
|
|
386
|
+
return -1;
|
|
387
|
+
}
|
|
388
|
+
/**
|
|
389
|
+
* Find the first Text node child of an element.
|
|
390
|
+
* Returns null if no text node exists (e.g., span only has a `<br>`).
|
|
391
|
+
*/
|
|
392
|
+
function findTextNode(el) {
|
|
393
|
+
for (let i = 0; i < el.childNodes.length; i++) {
|
|
394
|
+
const child = el.childNodes[i];
|
|
395
|
+
if (child.nodeType === Node.TEXT_NODE) {
|
|
396
|
+
return child;
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
return null;
|
|
400
|
+
}
|
|
401
|
+
// ---------------------------------------------------------------------------
|
|
402
|
+
// readDomSelection
|
|
403
|
+
// ---------------------------------------------------------------------------
|
|
404
|
+
/**
|
|
405
|
+
* Read the browser's current DOM selection and convert it to an RTIF Selection.
|
|
406
|
+
*
|
|
407
|
+
* Returns `null` if:
|
|
408
|
+
* - There is no selection or no ranges
|
|
409
|
+
* - The selection is outside the editor root
|
|
410
|
+
* - Either anchor or focus cannot be resolved to an RTIF offset
|
|
411
|
+
*
|
|
412
|
+
* @param root - The editor's root element (`[data-rtif-root]`)
|
|
413
|
+
* @param cache - Pre-built block offset cache from `buildBlockOffsetCache`
|
|
414
|
+
* @returns The RTIF Selection, or `null` if the selection cannot be resolved
|
|
415
|
+
*
|
|
416
|
+
* @example
|
|
417
|
+
* ```ts
|
|
418
|
+
* const sel = readDomSelection(root, cache);
|
|
419
|
+
* if (sel) {
|
|
420
|
+
* engine.dispatch({ type: 'set_selection', selection: sel });
|
|
421
|
+
* }
|
|
422
|
+
* ```
|
|
423
|
+
*/
|
|
424
|
+
export function readDomSelection(root, cache) {
|
|
425
|
+
const domSel = window.getSelection();
|
|
426
|
+
if (domSel === null || domSel.rangeCount === 0) {
|
|
427
|
+
return null;
|
|
428
|
+
}
|
|
429
|
+
const { anchorNode, anchorOffset, focusNode, focusOffset } = domSel;
|
|
430
|
+
if (anchorNode === null || focusNode === null) {
|
|
431
|
+
return null;
|
|
432
|
+
}
|
|
433
|
+
const anchorRtif = domPointToRtifOffset(root, anchorNode, anchorOffset, cache);
|
|
434
|
+
const focusRtif = domPointToRtifOffset(root, focusNode, focusOffset, cache);
|
|
435
|
+
if (anchorRtif === null || focusRtif === null) {
|
|
436
|
+
return null;
|
|
437
|
+
}
|
|
438
|
+
return {
|
|
439
|
+
anchor: { offset: anchorRtif },
|
|
440
|
+
focus: { offset: focusRtif },
|
|
441
|
+
};
|
|
442
|
+
}
|
|
443
|
+
// ---------------------------------------------------------------------------
|
|
444
|
+
// setDomSelection
|
|
445
|
+
// ---------------------------------------------------------------------------
|
|
446
|
+
/**
|
|
447
|
+
* Set the browser's DOM selection to match an RTIF Selection.
|
|
448
|
+
*
|
|
449
|
+
* Converts the RTIF anchor and focus offsets to DOM points, then calls
|
|
450
|
+
* `setBaseAndExtent()` on the window's Selection object.
|
|
451
|
+
*
|
|
452
|
+
* Sets the suppression flag to `true` before modifying the selection, to
|
|
453
|
+
* prevent the `selectionchange` event handler from triggering a feedback
|
|
454
|
+
* loop. The flag is reset via `requestAnimationFrame`.
|
|
455
|
+
*
|
|
456
|
+
* If either offset cannot be resolved to a DOM point (e.g., DOM is out of
|
|
457
|
+
* sync), the function is a no-op.
|
|
458
|
+
*
|
|
459
|
+
* @param root - The editor's root element (`[data-rtif-root]`)
|
|
460
|
+
* @param doc - The current RTIF document
|
|
461
|
+
* @param selection - The RTIF selection to apply to the DOM
|
|
462
|
+
*
|
|
463
|
+
* @example
|
|
464
|
+
* ```ts
|
|
465
|
+
* setDomSelection(root, doc, { anchor: { offset: 0 }, focus: { offset: 5 } });
|
|
466
|
+
* ```
|
|
467
|
+
*/
|
|
468
|
+
export function setDomSelection(root, doc, selection) {
|
|
469
|
+
const anchorPoint = rtifOffsetToDomPoint(root, doc, selection.anchor.offset);
|
|
470
|
+
const focusPoint = rtifOffsetToDomPoint(root, doc, selection.focus.offset);
|
|
471
|
+
if (anchorPoint === null || focusPoint === null) {
|
|
472
|
+
return;
|
|
473
|
+
}
|
|
474
|
+
const domSel = window.getSelection();
|
|
475
|
+
if (domSel === null) {
|
|
476
|
+
return;
|
|
477
|
+
}
|
|
478
|
+
suppressSelectionChange = true;
|
|
479
|
+
domSel.setBaseAndExtent(anchorPoint.node, anchorPoint.offset, focusPoint.node, focusPoint.offset);
|
|
480
|
+
// Reset suppression in the next animation frame to avoid feedback loops.
|
|
481
|
+
// requestAnimationFrame may not exist in test environments (jsdom), so guard.
|
|
482
|
+
if (typeof requestAnimationFrame === 'function') {
|
|
483
|
+
requestAnimationFrame(() => {
|
|
484
|
+
suppressSelectionChange = false;
|
|
485
|
+
});
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
// ---------------------------------------------------------------------------
|
|
489
|
+
// Suppression helpers
|
|
490
|
+
// ---------------------------------------------------------------------------
|
|
491
|
+
/**
|
|
492
|
+
* Returns `true` if the selection-sync layer is currently suppressing
|
|
493
|
+
* `selectionchange` events to avoid feedback loops.
|
|
494
|
+
*
|
|
495
|
+
* The `selectionchange` handler in the input bridge should check this flag
|
|
496
|
+
* and skip processing when it returns `true`.
|
|
497
|
+
*
|
|
498
|
+
* @returns Whether selectionchange events should be suppressed
|
|
499
|
+
*
|
|
500
|
+
* @example
|
|
501
|
+
* ```ts
|
|
502
|
+
* document.addEventListener('selectionchange', () => {
|
|
503
|
+
* if (isSuppressed()) return;
|
|
504
|
+
* handleUserSelectionChange();
|
|
505
|
+
* });
|
|
506
|
+
* ```
|
|
507
|
+
*/
|
|
508
|
+
export function isSuppressed() {
|
|
509
|
+
return suppressSelectionChange;
|
|
510
|
+
}
|
|
511
|
+
/**
|
|
512
|
+
* Manually reset the suppression flag to `false`.
|
|
513
|
+
*
|
|
514
|
+
* Primarily useful for testing, where `requestAnimationFrame` may not run.
|
|
515
|
+
* Production code should not need to call this.
|
|
516
|
+
*
|
|
517
|
+
* @example
|
|
518
|
+
* ```ts
|
|
519
|
+
* afterEach(() => {
|
|
520
|
+
* resetSuppression();
|
|
521
|
+
* });
|
|
522
|
+
* ```
|
|
523
|
+
*/
|
|
524
|
+
export function resetSuppression() {
|
|
525
|
+
suppressSelectionChange = false;
|
|
526
|
+
}
|
|
527
|
+
//# sourceMappingURL=selection-sync.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"selection-sync.js","sourceRoot":"","sources":["../src/selection-sync.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Keyboard shortcut handler for the RTIF web editor.
|
|
3
|
+
*
|
|
4
|
+
* Reads shortcut bindings from the engine and intercepts `keydown` events
|
|
5
|
+
* to dispatch the corresponding commands through the {@link CommandBus}.
|
|
6
|
+
* Uses platform-aware modifier key detection (Cmd on Mac, Ctrl on Win/Linux).
|
|
7
|
+
*
|
|
8
|
+
* The shortcut handler fires BEFORE the cursor-nav handler's keydown listener,
|
|
9
|
+
* so plugin-registered shortcuts take priority over built-in navigation.
|
|
10
|
+
*
|
|
11
|
+
* @module
|
|
12
|
+
*/
|
|
13
|
+
import type { ShortcutBinding } from '@rtif-sdk/engine';
|
|
14
|
+
import type { CommandBus } from './command-bus.js';
|
|
15
|
+
/**
|
|
16
|
+
* Injected dependencies for the ShortcutHandler.
|
|
17
|
+
*
|
|
18
|
+
* @example
|
|
19
|
+
* ```ts
|
|
20
|
+
* const deps: ShortcutHandlerDeps = {
|
|
21
|
+
* getShortcuts: () => engine.getShortcuts(),
|
|
22
|
+
* commandBus: bus,
|
|
23
|
+
* isComposing: () => compositionHandler.isComposing(),
|
|
24
|
+
* };
|
|
25
|
+
* ```
|
|
26
|
+
*/
|
|
27
|
+
export interface ShortcutHandlerDeps {
|
|
28
|
+
/** Get all registered shortcut bindings from the engine */
|
|
29
|
+
readonly getShortcuts: () => ReadonlyArray<ShortcutBinding>;
|
|
30
|
+
/** The command bus to execute matched commands through */
|
|
31
|
+
readonly commandBus: CommandBus;
|
|
32
|
+
/** Whether an IME composition session is active (shortcuts are suppressed during composition) */
|
|
33
|
+
readonly isComposing: () => boolean;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Intercepts `keydown` events and matches them against engine-registered
|
|
37
|
+
* keyboard shortcuts.
|
|
38
|
+
*
|
|
39
|
+
* When a match is found, the corresponding command is executed via the
|
|
40
|
+
* command bus and the event is prevented/stopped.
|
|
41
|
+
*
|
|
42
|
+
* @example
|
|
43
|
+
* ```ts
|
|
44
|
+
* const handler = new ShortcutHandler(root, {
|
|
45
|
+
* getShortcuts: () => engine.getShortcuts(),
|
|
46
|
+
* commandBus: bus,
|
|
47
|
+
* isComposing: () => compositionHandler.isComposing(),
|
|
48
|
+
* });
|
|
49
|
+
* handler.attach();
|
|
50
|
+
* ```
|
|
51
|
+
*/
|
|
52
|
+
export declare class ShortcutHandler {
|
|
53
|
+
private readonly _root;
|
|
54
|
+
private readonly _deps;
|
|
55
|
+
private _attached;
|
|
56
|
+
private readonly _boundHandler;
|
|
57
|
+
constructor(root: HTMLElement, deps: ShortcutHandlerDeps);
|
|
58
|
+
/**
|
|
59
|
+
* Attach the `keydown` event listener to the root element.
|
|
60
|
+
*
|
|
61
|
+
* Uses `capture: true` so shortcuts fire before other keydown handlers.
|
|
62
|
+
*
|
|
63
|
+
* @example
|
|
64
|
+
* ```ts
|
|
65
|
+
* handler.attach();
|
|
66
|
+
* ```
|
|
67
|
+
*/
|
|
68
|
+
attach(): void;
|
|
69
|
+
/**
|
|
70
|
+
* Remove the `keydown` event listener.
|
|
71
|
+
*
|
|
72
|
+
* @example
|
|
73
|
+
* ```ts
|
|
74
|
+
* handler.detach();
|
|
75
|
+
* ```
|
|
76
|
+
*/
|
|
77
|
+
detach(): void;
|
|
78
|
+
private _handleKeyDown;
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Check whether a keyboard event matches a shortcut binding.
|
|
82
|
+
*
|
|
83
|
+
* Platform-aware: `mod` matches `metaKey` on Mac, `ctrlKey` on other platforms.
|
|
84
|
+
*
|
|
85
|
+
* @param e - The keyboard event
|
|
86
|
+
* @param binding - The shortcut binding to match against
|
|
87
|
+
* @returns Whether the event matches the binding
|
|
88
|
+
*
|
|
89
|
+
* @example
|
|
90
|
+
* ```ts
|
|
91
|
+
* const matches = matchesShortcut(event, {
|
|
92
|
+
* descriptor: { key: 'b', mod: true },
|
|
93
|
+
* command: 'toggleBold',
|
|
94
|
+
* });
|
|
95
|
+
* ```
|
|
96
|
+
*/
|
|
97
|
+
export declare function matchesShortcut(e: KeyboardEvent, binding: ShortcutBinding): boolean;
|
|
98
|
+
//# sourceMappingURL=shortcut-handler.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"shortcut-handler.d.ts","sourceRoot":"","sources":["../src/shortcut-handler.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AACxD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAOnD;;;;;;;;;;;GAWG;AACH,MAAM,WAAW,mBAAmB;IAClC,2DAA2D;IAC3D,QAAQ,CAAC,YAAY,EAAE,MAAM,aAAa,CAAC,eAAe,CAAC,CAAC;IAE5D,0DAA0D;IAC1D,QAAQ,CAAC,UAAU,EAAE,UAAU,CAAC;IAEhC,iGAAiG;IACjG,QAAQ,CAAC,WAAW,EAAE,MAAM,OAAO,CAAC;CACrC;AAMD;;;;;;;;;;;;;;;;GAgBG;AACH,qBAAa,eAAe;IAC1B,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAc;IACpC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAsB;IAC5C,OAAO,CAAC,SAAS,CAAS;IAC1B,OAAO,CAAC,QAAQ,CAAC,aAAa,CAA6B;gBAE/C,IAAI,EAAE,WAAW,EAAE,IAAI,EAAE,mBAAmB;IAMxD;;;;;;;;;OASG;IACH,MAAM,IAAI,IAAI;IAMd;;;;;;;OAOG;IACH,MAAM,IAAI,IAAI;IAMd,OAAO,CAAC,cAAc;CA2BvB;AAMD;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,eAAe,CAAC,CAAC,EAAE,aAAa,EAAE,OAAO,EAAE,eAAe,GAAG,OAAO,CA6BnF"}
|