editable.ts 0.0.1
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 +571 -0
- package/dist/editable.umd.cjs +2 -0
- package/dist/editable.umd.cjs.map +1 -0
- package/lib/block.d.ts +13 -0
- package/lib/block.d.ts.map +1 -0
- package/lib/block.js +58 -0
- package/lib/block.js.map +1 -0
- package/lib/clipboard.d.ts +22 -0
- package/lib/clipboard.d.ts.map +1 -0
- package/lib/clipboard.js +154 -0
- package/lib/clipboard.js.map +1 -0
- package/lib/config.d.ts +37 -0
- package/lib/config.d.ts.map +1 -0
- package/lib/config.js +64 -0
- package/lib/config.js.map +1 -0
- package/lib/content.d.ts +37 -0
- package/lib/content.d.ts.map +1 -0
- package/lib/content.js +526 -0
- package/lib/content.js.map +1 -0
- package/lib/core.d.ts +97 -0
- package/lib/core.d.ts.map +1 -0
- package/lib/core.js +261 -0
- package/lib/core.js.map +1 -0
- package/lib/create-default-behavior.d.ts +31 -0
- package/lib/create-default-behavior.d.ts.map +1 -0
- package/lib/create-default-behavior.js +178 -0
- package/lib/create-default-behavior.js.map +1 -0
- package/lib/create-default-events.d.ts +152 -0
- package/lib/create-default-events.d.ts.map +1 -0
- package/lib/create-default-events.js +183 -0
- package/lib/create-default-events.js.map +1 -0
- package/lib/cursor.d.ts +68 -0
- package/lib/cursor.d.ts.map +1 -0
- package/lib/cursor.js +354 -0
- package/lib/cursor.js.map +1 -0
- package/lib/dispatcher.d.ts +78 -0
- package/lib/dispatcher.d.ts.map +1 -0
- package/lib/dispatcher.js +416 -0
- package/lib/dispatcher.js.map +1 -0
- package/lib/eventable.d.ts +2 -0
- package/lib/eventable.d.ts.map +1 -0
- package/lib/eventable.js +104 -0
- package/lib/eventable.js.map +1 -0
- package/lib/feature-detection.d.ts +12 -0
- package/lib/feature-detection.d.ts.map +1 -0
- package/lib/feature-detection.js +42 -0
- package/lib/feature-detection.js.map +1 -0
- package/lib/highlight-support.d.ts +24 -0
- package/lib/highlight-support.d.ts.map +1 -0
- package/lib/highlight-support.js +172 -0
- package/lib/highlight-support.js.map +1 -0
- package/lib/highlight-text.d.ts +21 -0
- package/lib/highlight-text.d.ts.map +1 -0
- package/lib/highlight-text.js +147 -0
- package/lib/highlight-text.js.map +1 -0
- package/lib/keyboard.d.ts +33 -0
- package/lib/keyboard.d.ts.map +1 -0
- package/lib/keyboard.js +189 -0
- package/lib/keyboard.js.map +1 -0
- package/lib/monitored-highlighting.d.ts +28 -0
- package/lib/monitored-highlighting.d.ts.map +1 -0
- package/lib/monitored-highlighting.js +194 -0
- package/lib/monitored-highlighting.js.map +1 -0
- package/lib/node-iterator.d.ts +16 -0
- package/lib/node-iterator.d.ts.map +1 -0
- package/lib/node-iterator.js +97 -0
- package/lib/node-iterator.js.map +1 -0
- package/lib/node-type.d.ts +13 -0
- package/lib/node-type.d.ts.map +1 -0
- package/lib/node-type.js +15 -0
- package/lib/node-type.js.map +1 -0
- package/lib/parser.d.ts +89 -0
- package/lib/parser.d.ts.map +1 -0
- package/lib/parser.js +251 -0
- package/lib/parser.js.map +1 -0
- package/lib/plugins/highlighting/match-collection.d.ts +7 -0
- package/lib/plugins/highlighting/match-collection.d.ts.map +1 -0
- package/lib/plugins/highlighting/match-collection.js +62 -0
- package/lib/plugins/highlighting/match-collection.js.map +1 -0
- package/lib/plugins/highlighting/spellcheck-service.d.ts +12 -0
- package/lib/plugins/highlighting/spellcheck-service.d.ts.map +1 -0
- package/lib/plugins/highlighting/spellcheck-service.js +24 -0
- package/lib/plugins/highlighting/spellcheck-service.js.map +1 -0
- package/lib/plugins/highlighting/text-search.d.ts +10 -0
- package/lib/plugins/highlighting/text-search.d.ts.map +1 -0
- package/lib/plugins/highlighting/text-search.js +92 -0
- package/lib/plugins/highlighting/text-search.js.map +1 -0
- package/lib/plugins/highlighting/whitespace-highlighting.d.ts +14 -0
- package/lib/plugins/highlighting/whitespace-highlighting.d.ts.map +1 -0
- package/lib/plugins/highlighting/whitespace-highlighting.js +52 -0
- package/lib/plugins/highlighting/whitespace-highlighting.js.map +1 -0
- package/lib/quotes.d.ts +8 -0
- package/lib/quotes.d.ts.map +1 -0
- package/lib/quotes.js +170 -0
- package/lib/quotes.js.map +1 -0
- package/lib/range-container.d.ts +22 -0
- package/lib/range-container.d.ts.map +1 -0
- package/lib/range-container.js +52 -0
- package/lib/range-container.js.map +1 -0
- package/lib/range-save-restore.d.ts +13 -0
- package/lib/range-save-restore.d.ts.map +1 -0
- package/lib/range-save-restore.js +153 -0
- package/lib/range-save-restore.js.map +1 -0
- package/lib/selection-watcher.d.ts +55 -0
- package/lib/selection-watcher.d.ts.map +1 -0
- package/lib/selection-watcher.js +126 -0
- package/lib/selection-watcher.js.map +1 -0
- package/lib/selection.d.ts +74 -0
- package/lib/selection.d.ts.map +1 -0
- package/lib/selection.js +341 -0
- package/lib/selection.js.map +1 -0
- package/lib/smartQuotes.d.ts +16 -0
- package/lib/smartQuotes.d.ts.map +1 -0
- package/lib/smartQuotes.js +92 -0
- package/lib/smartQuotes.js.map +1 -0
- package/lib/util/binary_search.d.ts +23 -0
- package/lib/util/binary_search.d.ts.map +1 -0
- package/lib/util/binary_search.js +137 -0
- package/lib/util/binary_search.js.map +1 -0
- package/lib/util/clone-deep.d.ts +8 -0
- package/lib/util/clone-deep.d.ts.map +1 -0
- package/lib/util/clone-deep.js +10 -0
- package/lib/util/clone-deep.js.map +1 -0
- package/lib/util/dom.d.ts +43 -0
- package/lib/util/dom.d.ts.map +1 -0
- package/lib/util/dom.js +272 -0
- package/lib/util/dom.js.map +1 -0
- package/lib/util/element.d.ts +10 -0
- package/lib/util/element.d.ts.map +1 -0
- package/lib/util/element.js +29 -0
- package/lib/util/element.js.map +1 -0
- package/lib/util/error.d.ts +2 -0
- package/lib/util/error.d.ts.map +1 -0
- package/lib/util/error.js +16 -0
- package/lib/util/error.js.map +1 -0
- package/lib/util/log.d.ts +2 -0
- package/lib/util/log.d.ts.map +1 -0
- package/lib/util/log.js +18 -0
- package/lib/util/log.js.map +1 -0
- package/lib/util/merge.d.ts +6 -0
- package/lib/util/merge.d.ts.map +1 -0
- package/lib/util/merge.js +31 -0
- package/lib/util/merge.js.map +1 -0
- package/lib/util/string.d.ts +25 -0
- package/lib/util/string.d.ts.map +1 -0
- package/lib/util/string.js +68 -0
- package/lib/util/string.js.map +1 -0
- package/lib/util/viewport.d.ts +6 -0
- package/lib/util/viewport.d.ts.map +1 -0
- package/lib/util/viewport.js +8 -0
- package/lib/util/viewport.js.map +1 -0
- package/package.json +86 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2014 Upfront GmbH, 2015 - 2018 Livingdocs AG
|
|
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,571 @@
|
|
|
1
|
+
# editable.ts
|
|
2
|
+
|
|
3
|
+
A TypeScript library that provides a friendly and browser-consistent API for `contenteditable` elements. Built for block-level rich text editing with a clean, event-driven architecture. It is a fork of [https://github.com/livingdocsIO/editable.js](https://github.com/livingdocsIO/editable.js) but written in typescript.
|
|
4
|
+
|
|
5
|
+
## Summary
|
|
6
|
+
|
|
7
|
+
**editable.ts** is a modern TypeScript rewrite of editable.js, offering a robust abstraction layer over the browser's native `contenteditable` API. It handles cross-browser inconsistencies, provides a structured event system, and enables building rich text editors with minimal boilerplate.
|
|
8
|
+
|
|
9
|
+
### Key Features
|
|
10
|
+
|
|
11
|
+
- **Cross-browser compatibility** - Abstracts away browser differences in Selection and Range APIs
|
|
12
|
+
- **Event-driven architecture** - Clean pub/sub system for handling user interactions
|
|
13
|
+
- **Block-based editing** - Optimized for block-level elements (paragraphs, headings, blockquotes)
|
|
14
|
+
- **Selection & Cursor management** - Powerful APIs for manipulating text selections and cursor positions
|
|
15
|
+
- **Highlighting system** - Built-in support for text highlighting, spellcheck, and custom markers
|
|
16
|
+
- **Default behaviors** - Sensible defaults for common operations (split, merge, insert blocks)
|
|
17
|
+
- **TypeScript support** - Full type definitions and modern TypeScript implementation
|
|
18
|
+
- **Extensible** - Easy to customize and extend with custom event handlers
|
|
19
|
+
|
|
20
|
+
### Use Cases
|
|
21
|
+
|
|
22
|
+
- Rich text editors
|
|
23
|
+
- Content management systems
|
|
24
|
+
- Comment and annotation systems
|
|
25
|
+
- Collaborative editing interfaces
|
|
26
|
+
- Inline editing components
|
|
27
|
+
|
|
28
|
+
Check out the [original editable.js live demo](https://livingdocsio.github.io/editable.js/) for a reference implementation (note: this is the JavaScript version, not this TypeScript fork).
|
|
29
|
+
|
|
30
|
+
## What is it about?
|
|
31
|
+
|
|
32
|
+
A JavaScript API that defines a friendly and browser-consistent content editable interface.
|
|
33
|
+
|
|
34
|
+
Editable is built for block level elements containing only phrasing content. This normally means `p`, `h1`-`h6`, `blockquote` etc. elements. This allows editable to be lean and mean since it is only concerned with formatting and not with layouting.
|
|
35
|
+
|
|
36
|
+
We made editable.ts to support our vision of online document editing. Have a look at [livingdocs.io](http://livingdocs.io/).
|
|
37
|
+
|
|
38
|
+
## Architecture Overview
|
|
39
|
+
|
|
40
|
+
editable.ts follows a layered architecture that separates concerns and provides clear extension points.
|
|
41
|
+
|
|
42
|
+
### High-Level Architecture
|
|
43
|
+
|
|
44
|
+
```mermaid
|
|
45
|
+
graph TB
|
|
46
|
+
subgraph PublicAPI["Public API Layer"]
|
|
47
|
+
Editable[Editable Class]
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
subgraph EventSystem["Event System Layer"]
|
|
51
|
+
Dispatcher[Dispatcher]
|
|
52
|
+
Eventable[Eventable Mixin]
|
|
53
|
+
SelectionWatcher[SelectionWatcher]
|
|
54
|
+
Keyboard[Keyboard Handler]
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
subgraph CoreComponents["Core Components"]
|
|
58
|
+
Block[Block Management]
|
|
59
|
+
Content[Content Management]
|
|
60
|
+
Parser[Parser]
|
|
61
|
+
Clipboard[Clipboard Handler]
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
subgraph SelectionSystem["Selection & Cursor"]
|
|
65
|
+
Cursor[Cursor]
|
|
66
|
+
Selection[Selection]
|
|
67
|
+
RangeContainer[Range Container]
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
subgraph Highlighting["Highlighting System"]
|
|
71
|
+
HighlightSupport[Highlight Support]
|
|
72
|
+
MonitoredHighlighting[Monitored Highlighting]
|
|
73
|
+
Plugins[Highlighting Plugins]
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
subgraph DOMAbstraction["DOM Abstraction Layer"]
|
|
77
|
+
DOMUtils[DOM Utilities]
|
|
78
|
+
ElementUtils[Element Utilities]
|
|
79
|
+
StringUtils[String Utilities]
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
Editable --> Dispatcher
|
|
83
|
+
Editable --> Block
|
|
84
|
+
Editable --> Content
|
|
85
|
+
Editable --> HighlightSupport
|
|
86
|
+
|
|
87
|
+
Dispatcher --> Eventable
|
|
88
|
+
Dispatcher --> SelectionWatcher
|
|
89
|
+
Dispatcher --> Keyboard
|
|
90
|
+
|
|
91
|
+
SelectionWatcher --> Cursor
|
|
92
|
+
SelectionWatcher --> Selection
|
|
93
|
+
SelectionWatcher --> RangeContainer
|
|
94
|
+
|
|
95
|
+
Cursor --> Content
|
|
96
|
+
Cursor --> Parser
|
|
97
|
+
Selection --> Cursor
|
|
98
|
+
|
|
99
|
+
HighlightSupport --> MonitoredHighlighting
|
|
100
|
+
MonitoredHighlighting --> Plugins
|
|
101
|
+
|
|
102
|
+
Content --> Parser
|
|
103
|
+
Content --> DOMUtils
|
|
104
|
+
Parser --> ElementUtils
|
|
105
|
+
Block --> DOMUtils
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
### Core Components
|
|
109
|
+
|
|
110
|
+
#### 1. Editable Class (`core.ts`)
|
|
111
|
+
|
|
112
|
+
The main entry point and public API. Provides a clean, chainable interface for all operations.
|
|
113
|
+
|
|
114
|
+
**Key Responsibilities:**
|
|
115
|
+
- Exposes the public API for end users
|
|
116
|
+
- Manages instance-specific configuration
|
|
117
|
+
- Delegates to specialized modules
|
|
118
|
+
- Provides cursor/selection creation utilities
|
|
119
|
+
- Handles highlighting operations
|
|
120
|
+
|
|
121
|
+
**Key Methods:**
|
|
122
|
+
- `add()` / `remove()` - Enable/disable editable functionality
|
|
123
|
+
- `enable()` / `disable()` - Control editable state
|
|
124
|
+
- `on()` / `off()` - Event subscription
|
|
125
|
+
- `getSelection()` - Get current selection/cursor
|
|
126
|
+
- `highlight()` - Text highlighting functionality
|
|
127
|
+
- `getContent()` - Extract clean content
|
|
128
|
+
|
|
129
|
+
#### 2. Dispatcher (`dispatcher.ts`)
|
|
130
|
+
|
|
131
|
+
Central event coordination hub that bridges native DOM events to the internal event system.
|
|
132
|
+
|
|
133
|
+
**Event Flow:**
|
|
134
|
+
```
|
|
135
|
+
Native DOM Event
|
|
136
|
+
↓
|
|
137
|
+
Dispatcher (setupDocumentListener)
|
|
138
|
+
↓
|
|
139
|
+
Event Handler (filter by editable block)
|
|
140
|
+
↓
|
|
141
|
+
SelectionWatcher (get current selection/cursor)
|
|
142
|
+
↓
|
|
143
|
+
Dispatcher.notify() (emit internal event)
|
|
144
|
+
↓
|
|
145
|
+
Event Handlers (user-defined callbacks)
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
#### 3. Event System (`eventable.ts`)
|
|
149
|
+
|
|
150
|
+
Lightweight publish/subscribe mixin implementing the Observer pattern.
|
|
151
|
+
|
|
152
|
+
**API:**
|
|
153
|
+
- `on(event, handler)` - Subscribe to events
|
|
154
|
+
- `off(event, handler)` - Unsubscribe from events
|
|
155
|
+
- `notify(event, ...args)` - Publish events
|
|
156
|
+
|
|
157
|
+
#### 4. Selection & Cursor System
|
|
158
|
+
|
|
159
|
+
**SelectionWatcher** - Monitors browser Selection API and converts to internal Cursor/Selection objects
|
|
160
|
+
|
|
161
|
+
**Cursor** - Represents a collapsed selection (cursor position) with capabilities for:
|
|
162
|
+
- Position querying (beginning, end, line detection)
|
|
163
|
+
- Content insertion/manipulation
|
|
164
|
+
- Tag detection (bold, italic, links, etc.)
|
|
165
|
+
- Coordinate calculations
|
|
166
|
+
|
|
167
|
+
**Selection** - Extends Cursor, represents a non-collapsed selection with additional capabilities:
|
|
168
|
+
- Text/HTML extraction
|
|
169
|
+
- Selection wrapping (links, formatting)
|
|
170
|
+
- Range validation
|
|
171
|
+
- Multiple rect support
|
|
172
|
+
|
|
173
|
+
#### 5. Block Management (`block.ts`)
|
|
174
|
+
|
|
175
|
+
Manages the lifecycle and state of individual editable block elements.
|
|
176
|
+
|
|
177
|
+
#### 6. Content Management (`content.ts`)
|
|
178
|
+
|
|
179
|
+
Handles all content manipulation, extraction, and normalization:
|
|
180
|
+
- HTML normalization
|
|
181
|
+
- Content extraction (removes internal markers)
|
|
182
|
+
- Fragment creation
|
|
183
|
+
- Tag wrapping/unwrapping
|
|
184
|
+
|
|
185
|
+
#### 7. Highlighting System
|
|
186
|
+
|
|
187
|
+
Comprehensive highlighting support including:
|
|
188
|
+
- Spellcheck integration
|
|
189
|
+
- Text search highlighting
|
|
190
|
+
- Range-based highlighting
|
|
191
|
+
- Highlight persistence during editing
|
|
192
|
+
- Custom highlight types
|
|
193
|
+
|
|
194
|
+
### Data Flow Examples
|
|
195
|
+
|
|
196
|
+
#### User Types Enter Key
|
|
197
|
+
|
|
198
|
+
```mermaid
|
|
199
|
+
sequenceDiagram
|
|
200
|
+
participant User
|
|
201
|
+
participant Browser
|
|
202
|
+
participant Dispatcher
|
|
203
|
+
participant Keyboard
|
|
204
|
+
participant SelectionWatcher
|
|
205
|
+
participant DefaultBehavior
|
|
206
|
+
|
|
207
|
+
User->>Browser: Presses Enter
|
|
208
|
+
Browser->>Dispatcher: keydown event
|
|
209
|
+
Dispatcher->>Keyboard: dispatchKeyEvent()
|
|
210
|
+
Keyboard->>Dispatcher: 'enter' event
|
|
211
|
+
Dispatcher->>SelectionWatcher: getFreshRange()
|
|
212
|
+
SelectionWatcher-->>Dispatcher: Cursor object
|
|
213
|
+
Dispatcher->>DefaultBehavior: notify('split'/'insert')
|
|
214
|
+
DefaultBehavior->>Browser: DOM updated
|
|
215
|
+
Browser-->>User: Cursor positioned
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
#### User Selects Text
|
|
219
|
+
|
|
220
|
+
```mermaid
|
|
221
|
+
sequenceDiagram
|
|
222
|
+
participant User
|
|
223
|
+
participant Browser
|
|
224
|
+
participant Dispatcher
|
|
225
|
+
participant SelectionWatcher
|
|
226
|
+
|
|
227
|
+
User->>Browser: Selects text
|
|
228
|
+
Browser->>Dispatcher: selectionchange event
|
|
229
|
+
Dispatcher->>SelectionWatcher: selectionChanged()
|
|
230
|
+
SelectionWatcher->>SelectionWatcher: getFreshSelection()
|
|
231
|
+
SelectionWatcher-->>Dispatcher: Selection object
|
|
232
|
+
Dispatcher->>Dispatcher: notify('selection')
|
|
233
|
+
Dispatcher-->>User: User handlers execute
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
For a detailed technical deep-dive, see [ARCHITECTURE.md](docs/ARCHITECTURE.md).
|
|
237
|
+
|
|
238
|
+
## Installation
|
|
239
|
+
|
|
240
|
+
Via npm:
|
|
241
|
+
|
|
242
|
+
```shell
|
|
243
|
+
npm install --save editable.ts
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
You can either `import` the module or find a prebuilt file in the npm bundle `dist/editable.umd.cjs`.
|
|
247
|
+
|
|
248
|
+
```typescript
|
|
249
|
+
import { Editable } from 'editable.ts'
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
## Quick Start
|
|
253
|
+
|
|
254
|
+
### Basic Usage
|
|
255
|
+
|
|
256
|
+
To make an element editable:
|
|
257
|
+
|
|
258
|
+
```typescript
|
|
259
|
+
import { Editable } from 'editable.ts'
|
|
260
|
+
|
|
261
|
+
// Create an instance
|
|
262
|
+
const editable = new Editable()
|
|
263
|
+
|
|
264
|
+
// Make an element editable
|
|
265
|
+
const element = document.querySelector('.my-editable')
|
|
266
|
+
editable.add(element)
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
### TypeScript Example
|
|
270
|
+
|
|
271
|
+
```typescript
|
|
272
|
+
import { Editable } from 'editable.ts'
|
|
273
|
+
|
|
274
|
+
const editable = new Editable({
|
|
275
|
+
defaultBehavior: true,
|
|
276
|
+
browserSpellcheck: true
|
|
277
|
+
})
|
|
278
|
+
|
|
279
|
+
// Add editable functionality to elements
|
|
280
|
+
editable.add(document.querySelectorAll('.editable-block'))
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
## Examples
|
|
284
|
+
|
|
285
|
+
### Selection Changes with Toolbar
|
|
286
|
+
|
|
287
|
+
In a `selection` event you get the editable element that triggered the event as well as a selection object. Through the selection object you can get information about the selection like coordinates or the text it contains and you can manipulate the selection.
|
|
288
|
+
|
|
289
|
+
In the following example we show a toolbar on top of the selection whenever the user has selected something inside of an editable element.
|
|
290
|
+
|
|
291
|
+
```typescript
|
|
292
|
+
editable.on('selection', (editableElement: HTMLElement, selection: Selection | null) => {
|
|
293
|
+
if (!selection) {
|
|
294
|
+
toolbar.hide()
|
|
295
|
+
return
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
// Get coordinates relative to the document (suited for absolutely positioned elements)
|
|
299
|
+
const coords = selection.getCoordinates()
|
|
300
|
+
|
|
301
|
+
// Position toolbar
|
|
302
|
+
const top = coords.top - toolbar.outerHeight()
|
|
303
|
+
const left = coords.left + (coords.width / 2) - (toolbar.outerWidth() / 2)
|
|
304
|
+
toolbar.css({top, left}).show()
|
|
305
|
+
})
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
### Cursor Manipulation
|
|
309
|
+
|
|
310
|
+
Create and manipulate cursors programmatically:
|
|
311
|
+
|
|
312
|
+
```typescript
|
|
313
|
+
// Get current cursor/selection
|
|
314
|
+
const cursor = editable.getSelection()
|
|
315
|
+
|
|
316
|
+
if (cursor && cursor.isCursor) {
|
|
317
|
+
// Check if cursor is at beginning of block
|
|
318
|
+
if (cursor.isAtBeginning()) {
|
|
319
|
+
console.log('Cursor is at the beginning')
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
// Insert text at cursor position
|
|
323
|
+
cursor.insert('Hello, World!')
|
|
324
|
+
|
|
325
|
+
// Create cursor at specific position
|
|
326
|
+
const newCursor = editable.createCursor(element, 'end')
|
|
327
|
+
newCursor?.insertAfter('<strong>Bold text</strong>')
|
|
328
|
+
}
|
|
329
|
+
```
|
|
330
|
+
|
|
331
|
+
### Content Extraction
|
|
332
|
+
|
|
333
|
+
Extract clean content from editable elements:
|
|
334
|
+
|
|
335
|
+
```typescript
|
|
336
|
+
// Get clean HTML content (removes internal markers)
|
|
337
|
+
const content = editable.getContent(element)
|
|
338
|
+
console.log(content) // Clean HTML string
|
|
339
|
+
|
|
340
|
+
// Get selection text
|
|
341
|
+
const selection = editable.getSelection(element)
|
|
342
|
+
if (selection && selection.isSelection) {
|
|
343
|
+
const selectedText = selection.text()
|
|
344
|
+
const selectedHtml = selection.html()
|
|
345
|
+
console.log('Selected text:', selectedText)
|
|
346
|
+
console.log('Selected HTML:', selectedHtml)
|
|
347
|
+
}
|
|
348
|
+
```
|
|
349
|
+
|
|
350
|
+
### Event Handling
|
|
351
|
+
|
|
352
|
+
Handle multiple events with a clean API:
|
|
353
|
+
|
|
354
|
+
```typescript
|
|
355
|
+
// Handle focus events
|
|
356
|
+
editable.on('focus', (element: HTMLElement) => {
|
|
357
|
+
console.log('Element focused:', element)
|
|
358
|
+
})
|
|
359
|
+
|
|
360
|
+
// Handle content changes
|
|
361
|
+
editable.on('change', (element: HTMLElement) => {
|
|
362
|
+
console.log('Content changed in:', element)
|
|
363
|
+
// Auto-save, validation, etc.
|
|
364
|
+
})
|
|
365
|
+
|
|
366
|
+
// Handle block splits (Enter key in middle of block)
|
|
367
|
+
editable.on('split', (element: HTMLElement, cursor: Cursor) => {
|
|
368
|
+
console.log('Block split at:', cursor)
|
|
369
|
+
// Custom split behavior
|
|
370
|
+
})
|
|
371
|
+
|
|
372
|
+
// Handle block merges (Backspace/Delete at boundaries)
|
|
373
|
+
editable.on('merge', (element: HTMLElement, cursor: Cursor) => {
|
|
374
|
+
console.log('Blocks merged at:', cursor)
|
|
375
|
+
// Custom merge behavior
|
|
376
|
+
})
|
|
377
|
+
```
|
|
378
|
+
|
|
379
|
+
### Highlighting
|
|
380
|
+
|
|
381
|
+
Add text highlighting and spellcheck:
|
|
382
|
+
|
|
383
|
+
```typescript
|
|
384
|
+
// Highlight specific text
|
|
385
|
+
const highlightId = editable.highlight({
|
|
386
|
+
editableHost: element,
|
|
387
|
+
text: 'search term',
|
|
388
|
+
highlightId: 'search-1',
|
|
389
|
+
type: 'search'
|
|
390
|
+
})
|
|
391
|
+
|
|
392
|
+
// Highlight specific range
|
|
393
|
+
editable.highlight({
|
|
394
|
+
editableHost: element,
|
|
395
|
+
text: 'important',
|
|
396
|
+
highlightId: 'important-1',
|
|
397
|
+
textRange: { start: 10, end: 18 },
|
|
398
|
+
type: 'comment'
|
|
399
|
+
})
|
|
400
|
+
|
|
401
|
+
// Setup spellcheck
|
|
402
|
+
editable.setupSpellcheck({
|
|
403
|
+
throttle: 300,
|
|
404
|
+
spellcheckService: (text: string) => {
|
|
405
|
+
// Your spellcheck service
|
|
406
|
+
return checkSpelling(text)
|
|
407
|
+
}
|
|
408
|
+
})
|
|
409
|
+
|
|
410
|
+
// Remove highlight
|
|
411
|
+
editable.removeHighlight({
|
|
412
|
+
editableHost: element,
|
|
413
|
+
highlightId: 'search-1'
|
|
414
|
+
})
|
|
415
|
+
```
|
|
416
|
+
|
|
417
|
+
### Custom Event Handlers
|
|
418
|
+
|
|
419
|
+
Override default behaviors:
|
|
420
|
+
|
|
421
|
+
```typescript
|
|
422
|
+
// Disable default behavior and implement custom
|
|
423
|
+
const editable = new Editable({
|
|
424
|
+
defaultBehavior: false
|
|
425
|
+
})
|
|
426
|
+
|
|
427
|
+
// Custom Enter key handling
|
|
428
|
+
editable.on('keydown', (element: HTMLElement, event: KeyboardEvent) => {
|
|
429
|
+
if (event.key === 'Enter') {
|
|
430
|
+
event.preventDefault()
|
|
431
|
+
// Custom Enter behavior
|
|
432
|
+
insertCustomBlock(element)
|
|
433
|
+
}
|
|
434
|
+
})
|
|
435
|
+
```
|
|
436
|
+
|
|
437
|
+
## Events Overview
|
|
438
|
+
|
|
439
|
+
editable.ts emits a comprehensive set of events for all user interactions:
|
|
440
|
+
|
|
441
|
+
### Core Events
|
|
442
|
+
|
|
443
|
+
- **focus**
|
|
444
|
+
Fired when an editable element gets focus.
|
|
445
|
+
|
|
446
|
+
- **blur**
|
|
447
|
+
Fired when an editable element loses focus.
|
|
448
|
+
|
|
449
|
+
- **selection**
|
|
450
|
+
Fired when the user selects some text inside an editable element.
|
|
451
|
+
|
|
452
|
+
- **cursor**
|
|
453
|
+
Fired when the cursor position changes.
|
|
454
|
+
|
|
455
|
+
- **change**
|
|
456
|
+
Fired when the user has made a change.
|
|
457
|
+
|
|
458
|
+
- **input**
|
|
459
|
+
Fired on user input.
|
|
460
|
+
|
|
461
|
+
### Content Modification Events
|
|
462
|
+
|
|
463
|
+
- **insert**
|
|
464
|
+
Fired when the user presses `ENTER` at the beginning or end of an editable (For example you can insert a new paragraph after the element if this happens).
|
|
465
|
+
|
|
466
|
+
- **split**
|
|
467
|
+
Fired when the user presses `ENTER` in the middle of an element.
|
|
468
|
+
|
|
469
|
+
- **merge**
|
|
470
|
+
Fired when the user pressed `FORWARD DELETE` at the end or `BACKSPACE` at the beginning of an element.
|
|
471
|
+
|
|
472
|
+
- **newline**
|
|
473
|
+
Fired when the user presses `SHIFT+ENTER` to insert a newline.
|
|
474
|
+
|
|
475
|
+
- **switch**
|
|
476
|
+
Fired when the user pressed an `ARROW KEY` at the top or bottom so that you may want to set the cursor into the preceding or following element.
|
|
477
|
+
|
|
478
|
+
### Clipboard Events
|
|
479
|
+
|
|
480
|
+
- **clipboard**
|
|
481
|
+
Fired for `copy`, `cut` and `paste` events.
|
|
482
|
+
|
|
483
|
+
- **paste**
|
|
484
|
+
Fired specifically on paste operations.
|
|
485
|
+
|
|
486
|
+
### Highlighting Events
|
|
487
|
+
|
|
488
|
+
- **spellcheckUpdated**
|
|
489
|
+
Fired when the spellcheckService has updated the spellcheck highlights.
|
|
490
|
+
|
|
491
|
+
## API Reference
|
|
492
|
+
|
|
493
|
+
For detailed API documentation, see the source files:
|
|
494
|
+
|
|
495
|
+
- **[core.ts](src/core.ts)** - Main Editable class and public API
|
|
496
|
+
- **[cursor.ts](src/cursor.ts)** - Cursor manipulation API
|
|
497
|
+
- **[selection.ts](src/selection.ts)** - Selection manipulation API
|
|
498
|
+
- **[dispatcher.ts](src/dispatcher.ts)** - Event system internals
|
|
499
|
+
- **[create-default-behavior.ts](src/create-default-behavior.ts)** - Default behavior implementation
|
|
500
|
+
|
|
501
|
+
### Type Definitions
|
|
502
|
+
|
|
503
|
+
```typescript
|
|
504
|
+
interface EditableConfig {
|
|
505
|
+
window?: Window
|
|
506
|
+
defaultBehavior?: boolean
|
|
507
|
+
mouseMoveSelectionChanges?: boolean
|
|
508
|
+
browserSpellcheck?: boolean
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
interface HighlightOptions {
|
|
512
|
+
editableHost: HTMLElement
|
|
513
|
+
text: string
|
|
514
|
+
highlightId: string
|
|
515
|
+
textRange?: { start: number; end: number }
|
|
516
|
+
raiseEvents?: boolean
|
|
517
|
+
type?: string
|
|
518
|
+
}
|
|
519
|
+
```
|
|
520
|
+
|
|
521
|
+
## Development
|
|
522
|
+
|
|
523
|
+
### Setup
|
|
524
|
+
|
|
525
|
+
```bash
|
|
526
|
+
# Install node dependencies
|
|
527
|
+
npm install
|
|
528
|
+
```
|
|
529
|
+
|
|
530
|
+
### Development Tasks
|
|
531
|
+
|
|
532
|
+
```bash
|
|
533
|
+
# Development server with demo app (Vite dev server)
|
|
534
|
+
npm start
|
|
535
|
+
|
|
536
|
+
# Run tests (Vitest)
|
|
537
|
+
npm test
|
|
538
|
+
|
|
539
|
+
# Run tests in watch mode
|
|
540
|
+
npm run test:watch
|
|
541
|
+
|
|
542
|
+
# Run tests with coverage
|
|
543
|
+
npm run test:coverage
|
|
544
|
+
|
|
545
|
+
# Run tests with interactive UI
|
|
546
|
+
npm run test:ui
|
|
547
|
+
|
|
548
|
+
# TypeScript/JavaScript linting
|
|
549
|
+
npm run lint
|
|
550
|
+
|
|
551
|
+
# Build editable.ts (TypeScript → lib/, then bundle → dist/)
|
|
552
|
+
npm run build
|
|
553
|
+
|
|
554
|
+
# Build TypeScript only
|
|
555
|
+
npm run build:ts
|
|
556
|
+
|
|
557
|
+
# Build library bundle only
|
|
558
|
+
npm run build:dist
|
|
559
|
+
|
|
560
|
+
# Build examples only
|
|
561
|
+
npm run build:docs
|
|
562
|
+
```
|
|
563
|
+
|
|
564
|
+
### Requirements
|
|
565
|
+
|
|
566
|
+
- Node.js >= 22
|
|
567
|
+
- npm >= 11
|
|
568
|
+
|
|
569
|
+
## License
|
|
570
|
+
|
|
571
|
+
editable.ts is licensed under the [MIT License](LICENSE).
|