@rtif-sdk/engine 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 +79 -0
- package/dist/commands.d.ts +29 -0
- package/dist/commands.d.ts.map +1 -0
- package/dist/commands.js +29 -0
- package/dist/commands.js.map +1 -0
- package/dist/engine.d.ts +137 -0
- package/dist/engine.d.ts.map +1 -0
- package/dist/engine.js +527 -0
- package/dist/engine.js.map +1 -0
- package/dist/errors.d.ts +34 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +21 -0
- package/dist/errors.js.map +1 -0
- package/dist/history.d.ts +130 -0
- package/dist/history.d.ts.map +1 -0
- package/dist/history.js +156 -0
- package/dist/history.js.map +1 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +7 -0
- package/dist/index.js.map +1 -0
- package/dist/plugin-host.d.ts +34 -0
- package/dist/plugin-host.d.ts.map +1 -0
- package/dist/plugin-host.js +6 -0
- package/dist/plugin-host.js.map +1 -0
- package/dist/registries.d.ts +130 -0
- package/dist/registries.d.ts.map +1 -0
- package/dist/registries.js +23 -0
- package/dist/registries.js.map +1 -0
- package/dist/selection-transform.d.ts +23 -0
- package/dist/selection-transform.d.ts.map +1 -0
- package/dist/selection-transform.js +87 -0
- package/dist/selection-transform.js.map +1 -0
- package/package.json +28 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Cory Robinson
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
# @rtif-sdk/engine
|
|
2
|
+
|
|
3
|
+
Editor engine for RTIF (Rich Text Input Format). Manages document state, dispatches operations through a plugin lifecycle, and provides undo/redo history.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @rtif-sdk/engine
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Usage
|
|
12
|
+
|
|
13
|
+
### Create an engine
|
|
14
|
+
|
|
15
|
+
```typescript
|
|
16
|
+
import { createEngine } from '@rtif-sdk/engine';
|
|
17
|
+
import type { Document } from '@rtif-sdk/core';
|
|
18
|
+
|
|
19
|
+
const doc: Document = {
|
|
20
|
+
version: 1,
|
|
21
|
+
blocks: [{ id: 'b1', type: 'text', spans: [{ text: '' }] }],
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
const engine = createEngine(doc);
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
### Dispatch operations
|
|
28
|
+
|
|
29
|
+
```typescript
|
|
30
|
+
engine.dispatch({ type: 'insert_text', offset: 0, text: 'Hello' });
|
|
31
|
+
|
|
32
|
+
console.log(engine.state.doc.blocks[0].spans[0].text); // 'Hello'
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
### Undo / Redo
|
|
36
|
+
|
|
37
|
+
```typescript
|
|
38
|
+
engine.undo(); // reverts to empty document
|
|
39
|
+
engine.redo(); // re-applies 'Hello'
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
### Subscribe to changes
|
|
43
|
+
|
|
44
|
+
```typescript
|
|
45
|
+
const unsubscribe = engine.onChange((state) => {
|
|
46
|
+
console.log('Document changed:', state.doc);
|
|
47
|
+
});
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
### Plugins
|
|
51
|
+
|
|
52
|
+
```typescript
|
|
53
|
+
import type { Plugin } from '@rtif-sdk/engine';
|
|
54
|
+
|
|
55
|
+
const myPlugin: Plugin = {
|
|
56
|
+
id: 'my-plugin',
|
|
57
|
+
init(engine) {
|
|
58
|
+
engine.registerCommand('greet', (eng) => {
|
|
59
|
+
eng.dispatch({ type: 'insert_text', offset: 0, text: 'Hi! ' });
|
|
60
|
+
});
|
|
61
|
+
},
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
engine.use(myPlugin);
|
|
65
|
+
engine.exec('greet');
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
## Plugin Lifecycle
|
|
69
|
+
|
|
70
|
+
1. `beforeApply` hooks — inspect, modify, or cancel operations
|
|
71
|
+
2. `apply()` — core applies operations to the document
|
|
72
|
+
3. `afterApply` hooks — observe changes, trigger side effects
|
|
73
|
+
4. `onChange` listeners — UI re-renders
|
|
74
|
+
|
|
75
|
+
Plugin errors are caught and isolated — a failing plugin never breaks the editor.
|
|
76
|
+
|
|
77
|
+
## License
|
|
78
|
+
|
|
79
|
+
MIT
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Type-safe command name constants for built-in engine commands.
|
|
3
|
+
*
|
|
4
|
+
* Use these constants instead of raw strings when calling `engine.exec()`
|
|
5
|
+
* to get IDE autocomplete and catch typos at write-time.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```ts
|
|
9
|
+
* import { EngineCommands } from '@rtif-sdk/engine';
|
|
10
|
+
* engine.exec(EngineCommands.UNDO);
|
|
11
|
+
* engine.exec(EngineCommands.REDO);
|
|
12
|
+
* ```
|
|
13
|
+
*
|
|
14
|
+
* @module
|
|
15
|
+
*/
|
|
16
|
+
/**
|
|
17
|
+
* Command name constants for built-in engine commands (undo/redo).
|
|
18
|
+
*
|
|
19
|
+
* @example
|
|
20
|
+
* ```ts
|
|
21
|
+
* engine.exec(EngineCommands.UNDO);
|
|
22
|
+
* engine.exec(EngineCommands.REDO);
|
|
23
|
+
* ```
|
|
24
|
+
*/
|
|
25
|
+
export declare const EngineCommands: {
|
|
26
|
+
readonly UNDO: "undo";
|
|
27
|
+
readonly REDO: "redo";
|
|
28
|
+
};
|
|
29
|
+
//# sourceMappingURL=commands.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"commands.d.ts","sourceRoot":"","sources":["../src/commands.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH;;;;;;;;GAQG;AACH,eAAO,MAAM,cAAc;;;CAGjB,CAAC"}
|
package/dist/commands.js
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Type-safe command name constants for built-in engine commands.
|
|
3
|
+
*
|
|
4
|
+
* Use these constants instead of raw strings when calling `engine.exec()`
|
|
5
|
+
* to get IDE autocomplete and catch typos at write-time.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```ts
|
|
9
|
+
* import { EngineCommands } from '@rtif-sdk/engine';
|
|
10
|
+
* engine.exec(EngineCommands.UNDO);
|
|
11
|
+
* engine.exec(EngineCommands.REDO);
|
|
12
|
+
* ```
|
|
13
|
+
*
|
|
14
|
+
* @module
|
|
15
|
+
*/
|
|
16
|
+
/**
|
|
17
|
+
* Command name constants for built-in engine commands (undo/redo).
|
|
18
|
+
*
|
|
19
|
+
* @example
|
|
20
|
+
* ```ts
|
|
21
|
+
* engine.exec(EngineCommands.UNDO);
|
|
22
|
+
* engine.exec(EngineCommands.REDO);
|
|
23
|
+
* ```
|
|
24
|
+
*/
|
|
25
|
+
export const EngineCommands = {
|
|
26
|
+
UNDO: 'undo',
|
|
27
|
+
REDO: 'redo',
|
|
28
|
+
};
|
|
29
|
+
//# sourceMappingURL=commands.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"commands.js","sourceRoot":"","sources":["../src/commands.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH;;;;;;;;GAQG;AACH,MAAM,CAAC,MAAM,cAAc,GAAG;IAC5B,IAAI,EAAE,MAAM;IACZ,IAAI,EAAE,MAAM;CACJ,CAAC"}
|
package/dist/engine.d.ts
ADDED
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* EditorEngine — the state machine that drives all document mutations.
|
|
3
|
+
* See docs/spec/plugin-contract.md for specification.
|
|
4
|
+
*/
|
|
5
|
+
import type { Document, Block, Selection, Operation, RangeMarksResult } from '@rtif-sdk/core';
|
|
6
|
+
import type { Plugin } from './plugin-host.js';
|
|
7
|
+
import type { ShortcutBinding } from './registries.js';
|
|
8
|
+
import type { PluginError } from './errors.js';
|
|
9
|
+
/** Runtime editor state */
|
|
10
|
+
export interface EditorState {
|
|
11
|
+
readonly doc: Document;
|
|
12
|
+
readonly selection: Selection;
|
|
13
|
+
/** Plugins can store state here, keyed by plugin ID */
|
|
14
|
+
readonly pluginState: Record<string, unknown>;
|
|
15
|
+
}
|
|
16
|
+
/** The engine interface — the only way to produce new states */
|
|
17
|
+
export interface IEditorEngine {
|
|
18
|
+
/** Current state (readonly from outside) */
|
|
19
|
+
readonly state: EditorState;
|
|
20
|
+
/** Dispatch an operation or a batch of operations */
|
|
21
|
+
dispatch(ops: Operation | Operation[]): void;
|
|
22
|
+
/** Dispatch a high-level command by name */
|
|
23
|
+
exec(command: string, payload?: unknown): void;
|
|
24
|
+
/**
|
|
25
|
+
* Check whether a command can execute in the current state.
|
|
26
|
+
* Returns true if the command exists and its canExecute returns true (or is not defined).
|
|
27
|
+
* Returns false if the command is not registered.
|
|
28
|
+
*
|
|
29
|
+
* @param command - The command name
|
|
30
|
+
* @param payload - Optional payload passed to canExecute
|
|
31
|
+
* @returns Whether the command can execute
|
|
32
|
+
*/
|
|
33
|
+
canExecute(command: string, payload?: unknown): boolean;
|
|
34
|
+
/**
|
|
35
|
+
* Check whether a command's effect is currently active.
|
|
36
|
+
* Returns false if the command is not registered or has no isActive handler.
|
|
37
|
+
*
|
|
38
|
+
* @param command - The command name
|
|
39
|
+
* @param payload - Optional payload passed to isActive
|
|
40
|
+
* @returns Whether the command is active
|
|
41
|
+
*/
|
|
42
|
+
isActive(command: string, payload?: unknown): boolean;
|
|
43
|
+
/** Undo the last operation group */
|
|
44
|
+
undo(): void;
|
|
45
|
+
/** Redo the last undone operation group */
|
|
46
|
+
redo(): void;
|
|
47
|
+
/** Register a plugin */
|
|
48
|
+
use(plugin: Plugin): void;
|
|
49
|
+
/** Subscribe to state changes */
|
|
50
|
+
onChange(listener: (state: EditorState) => void): () => void;
|
|
51
|
+
/**
|
|
52
|
+
* Subscribe to selection-only changes (cursor moves that don't modify the document).
|
|
53
|
+
* Fires when setSelection() is called with a different selection.
|
|
54
|
+
* Does NOT fire during dispatch/undo/redo (those fire onChange instead).
|
|
55
|
+
*
|
|
56
|
+
* @param listener - Function called with the new state after selection change
|
|
57
|
+
* @returns Unsubscribe function
|
|
58
|
+
*
|
|
59
|
+
* @example
|
|
60
|
+
* ```ts
|
|
61
|
+
* const unsub = engine.onSelectionChange((state) => {
|
|
62
|
+
* updateToolbar(state.selection);
|
|
63
|
+
* });
|
|
64
|
+
* ```
|
|
65
|
+
*/
|
|
66
|
+
onSelectionChange(listener: (state: EditorState) => void): () => void;
|
|
67
|
+
/** Subscribe to plugin error events */
|
|
68
|
+
onError(listener: (error: PluginError) => void): () => void;
|
|
69
|
+
/**
|
|
70
|
+
* Update the engine's selection state without dispatching any operations.
|
|
71
|
+
*
|
|
72
|
+
* Used by platform implementations to sync cursor/selection changes that
|
|
73
|
+
* originate from user interaction (clicks, arrow keys, touch handles)
|
|
74
|
+
* back into the engine state, so that subsequent operations use the
|
|
75
|
+
* correct selection.
|
|
76
|
+
*
|
|
77
|
+
* Does NOT fire onChange listeners (the document did not change).
|
|
78
|
+
*
|
|
79
|
+
* @param selection - The new selection to store
|
|
80
|
+
*
|
|
81
|
+
* @example
|
|
82
|
+
* ```ts
|
|
83
|
+
* // After reading a DOM selectionchange event
|
|
84
|
+
* engine.setSelection({ anchor: { offset: 5 }, focus: { offset: 5 } });
|
|
85
|
+
* ```
|
|
86
|
+
*/
|
|
87
|
+
setSelection(selection: Selection): void;
|
|
88
|
+
/**
|
|
89
|
+
* Destroy the engine, calling destroy() on all plugins and cleaning up.
|
|
90
|
+
* After calling destroy(), no further operations can be dispatched.
|
|
91
|
+
*/
|
|
92
|
+
destroy(): void;
|
|
93
|
+
/** Get the formatting marks at a specific document offset */
|
|
94
|
+
getMarksAtOffset(offset: number): Record<string, unknown>;
|
|
95
|
+
/** Get the common and mixed marks across a text range */
|
|
96
|
+
getMarksInRange(offset: number, count: number): RangeMarksResult;
|
|
97
|
+
/** Get the block containing a specific document offset */
|
|
98
|
+
getBlockAtOffset(offset: number): Block;
|
|
99
|
+
/** Get all registered keyboard shortcuts */
|
|
100
|
+
getShortcuts(): ReadonlyArray<ShortcutBinding>;
|
|
101
|
+
/**
|
|
102
|
+
* Set a pending mark that will be applied to the next inserted text.
|
|
103
|
+
* Pending marks are ephemeral — not part of EditorState, not serialized, not in undo history.
|
|
104
|
+
*
|
|
105
|
+
* @param mark - The mark key (e.g., 'bold')
|
|
106
|
+
* @param value - The mark value (e.g., true)
|
|
107
|
+
*/
|
|
108
|
+
setPendingMark(mark: string, value: unknown): void;
|
|
109
|
+
/**
|
|
110
|
+
* Toggle a pending mark. If the mark is set, remove it. If not, set it to `value` (default: true).
|
|
111
|
+
*
|
|
112
|
+
* @param mark - The mark key
|
|
113
|
+
* @param value - The value to set if toggling on (default: true)
|
|
114
|
+
*/
|
|
115
|
+
togglePendingMark(mark: string, value?: unknown): void;
|
|
116
|
+
/** Get a shallow copy of the current pending marks */
|
|
117
|
+
getPendingMarks(): Readonly<Record<string, unknown>>;
|
|
118
|
+
/** Clear all pending marks */
|
|
119
|
+
clearPendingMarks(): void;
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Create a new EditorEngine with the given initial document.
|
|
123
|
+
*
|
|
124
|
+
* @param doc - The initial document
|
|
125
|
+
* @returns A new engine instance
|
|
126
|
+
*
|
|
127
|
+
* @example
|
|
128
|
+
* ```ts
|
|
129
|
+
* const engine = createEngine({
|
|
130
|
+
* version: 1,
|
|
131
|
+
* blocks: [{ id: 'b1', type: 'text', spans: [{ text: '' }] }],
|
|
132
|
+
* });
|
|
133
|
+
* engine.dispatch({ type: 'insert_text', offset: 0, text: 'hello' });
|
|
134
|
+
* ```
|
|
135
|
+
*/
|
|
136
|
+
export declare function createEngine(doc: Document): IEditorEngine;
|
|
137
|
+
//# sourceMappingURL=engine.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"engine.d.ts","sourceRoot":"","sources":["../src/engine.ts"],"names":[],"mappings":"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"}
|