|
1 | | -import {createKeyboardShortcut} from '@portabletext/keyboard-shortcuts' |
2 | 1 | import {isTextBlock} from '@portabletext/schema' |
3 | 2 | import {defaultKeyboardShortcuts} from '../keyboard-shortcuts/default-keyboard-shortcuts' |
| 3 | +import {getFocusBlockObject} from '../selectors' |
4 | 4 | import {getFocusBlock} from '../selectors/selector.get-focus-block' |
5 | 5 | import {getFocusInlineObject} from '../selectors/selector.get-focus-inline-object' |
| 6 | +import {getFocusTextBlock} from '../selectors/selector.get-focus-text-block' |
| 7 | +import {getNextBlock} from '../selectors/selector.get-next-block' |
6 | 8 | import {getPreviousBlock} from '../selectors/selector.get-previous-block' |
7 | 9 | import {isSelectionCollapsed} from '../selectors/selector.is-selection-collapsed' |
8 | 10 | import {isSelectionExpanded} from '../selectors/selector.is-selection-expanded' |
| 11 | +import {isEqualSelectionPoints} from '../utils' |
9 | 12 | import {getBlockEndPoint} from '../utils/util.get-block-end-point' |
| 13 | +import {getBlockStartPoint} from '../utils/util.get-block-start-point' |
10 | 14 | import {isEmptyTextBlock} from '../utils/util.is-empty-text-block' |
11 | 15 | import {raise} from './behavior.types.action' |
12 | 16 | import {defineBehavior} from './behavior.types.behavior' |
13 | 17 |
|
14 | | -const shiftLeft = createKeyboardShortcut({ |
15 | | - default: [ |
16 | | - { |
17 | | - key: 'ArrowLeft', |
18 | | - shift: true, |
19 | | - meta: false, |
20 | | - ctrl: false, |
21 | | - alt: false, |
22 | | - }, |
23 | | - ], |
24 | | -}) |
25 | | - |
26 | 18 | export const abstractKeyboardBehaviors = [ |
27 | 19 | /** |
28 | 20 | * When Backspace is pressed on an inline object, Slate will raise a |
@@ -130,7 +122,10 @@ export const abstractKeyboardBehaviors = [ |
130 | 122 | defineBehavior({ |
131 | 123 | on: 'keyboard.keydown', |
132 | 124 | guard: ({snapshot, event}) => { |
133 | | - if (!snapshot.context.selection || !shiftLeft.guard(event.originEvent)) { |
| 125 | + if ( |
| 126 | + !snapshot.context.selection || |
| 127 | + !defaultKeyboardShortcuts.shiftLeft.guard(event.originEvent) |
| 128 | + ) { |
134 | 129 | return false |
135 | 130 | } |
136 | 131 |
|
@@ -186,4 +181,260 @@ export const abstractKeyboardBehaviors = [ |
186 | 181 | ], |
187 | 182 | ], |
188 | 183 | }), |
| 184 | + |
| 185 | + defineBehavior({ |
| 186 | + on: 'keyboard.keydown', |
| 187 | + guard: ({snapshot, event}) => { |
| 188 | + if ( |
| 189 | + !snapshot.context.selection || |
| 190 | + !defaultKeyboardShortcuts.shiftDown.guard(event.originEvent) |
| 191 | + ) { |
| 192 | + return false |
| 193 | + } |
| 194 | + |
| 195 | + const focusBlockObject = getFocusBlockObject(snapshot) |
| 196 | + |
| 197 | + if (!focusBlockObject) { |
| 198 | + return false |
| 199 | + } |
| 200 | + |
| 201 | + const nextBlock = getNextBlock(snapshot) |
| 202 | + |
| 203 | + if (!nextBlock) { |
| 204 | + return false |
| 205 | + } |
| 206 | + |
| 207 | + if (!isTextBlock(snapshot.context, nextBlock.node)) { |
| 208 | + return { |
| 209 | + nextBlockEndPoint: getBlockEndPoint({ |
| 210 | + context: snapshot.context, |
| 211 | + block: nextBlock, |
| 212 | + }), |
| 213 | + selection: snapshot.context.selection, |
| 214 | + } |
| 215 | + } |
| 216 | + |
| 217 | + const nextNextBlock = getNextBlock({ |
| 218 | + ...snapshot, |
| 219 | + context: { |
| 220 | + ...snapshot.context, |
| 221 | + selection: { |
| 222 | + anchor: { |
| 223 | + path: nextBlock.path, |
| 224 | + offset: 0, |
| 225 | + }, |
| 226 | + focus: { |
| 227 | + path: nextBlock.path, |
| 228 | + offset: 0, |
| 229 | + }, |
| 230 | + }, |
| 231 | + }, |
| 232 | + }) |
| 233 | + |
| 234 | + const nextBlockEndPoint = |
| 235 | + nextNextBlock && isTextBlock(snapshot.context, nextNextBlock.node) |
| 236 | + ? getBlockStartPoint({ |
| 237 | + context: snapshot.context, |
| 238 | + block: nextNextBlock, |
| 239 | + }) |
| 240 | + : getBlockEndPoint({ |
| 241 | + context: snapshot.context, |
| 242 | + block: nextBlock, |
| 243 | + }) |
| 244 | + |
| 245 | + return {nextBlockEndPoint, selection: snapshot.context.selection} |
| 246 | + }, |
| 247 | + actions: [ |
| 248 | + (_, {nextBlockEndPoint, selection}) => [ |
| 249 | + raise({ |
| 250 | + type: 'select', |
| 251 | + at: {anchor: selection.anchor, focus: nextBlockEndPoint}, |
| 252 | + }), |
| 253 | + ], |
| 254 | + ], |
| 255 | + }), |
| 256 | + |
| 257 | + defineBehavior({ |
| 258 | + on: 'keyboard.keydown', |
| 259 | + guard: ({snapshot, event, dom}) => { |
| 260 | + if ( |
| 261 | + !snapshot.context.selection || |
| 262 | + !defaultKeyboardShortcuts.shiftDown.guard(event.originEvent) |
| 263 | + ) { |
| 264 | + return false |
| 265 | + } |
| 266 | + |
| 267 | + const focusTextBlock = getFocusTextBlock(snapshot) |
| 268 | + |
| 269 | + if (!focusTextBlock) { |
| 270 | + return false |
| 271 | + } |
| 272 | + |
| 273 | + const nextBlock = getNextBlock(snapshot) |
| 274 | + |
| 275 | + if (!nextBlock) { |
| 276 | + return false |
| 277 | + } |
| 278 | + |
| 279 | + if (isTextBlock(snapshot.context, nextBlock.node)) { |
| 280 | + return false |
| 281 | + } |
| 282 | + |
| 283 | + const focusBlockEndPoint = getBlockEndPoint({ |
| 284 | + context: snapshot.context, |
| 285 | + block: focusTextBlock, |
| 286 | + }) |
| 287 | + |
| 288 | + if ( |
| 289 | + isEqualSelectionPoints( |
| 290 | + snapshot.context.selection.focus, |
| 291 | + focusBlockEndPoint, |
| 292 | + ) |
| 293 | + ) { |
| 294 | + return false |
| 295 | + } |
| 296 | + |
| 297 | + // Find the DOM position of the current focus point |
| 298 | + const focusRect = dom.getSelectionRect({ |
| 299 | + ...snapshot, |
| 300 | + context: { |
| 301 | + ...snapshot.context, |
| 302 | + selection: { |
| 303 | + anchor: snapshot.context.selection.focus, |
| 304 | + focus: snapshot.context.selection.focus, |
| 305 | + }, |
| 306 | + }, |
| 307 | + }) |
| 308 | + // Find the DOM position of the focus block end point |
| 309 | + const endPointRect = dom.getSelectionRect({ |
| 310 | + ...snapshot, |
| 311 | + context: { |
| 312 | + ...snapshot.context, |
| 313 | + selection: { |
| 314 | + anchor: focusBlockEndPoint, |
| 315 | + focus: focusBlockEndPoint, |
| 316 | + }, |
| 317 | + }, |
| 318 | + }) |
| 319 | + |
| 320 | + if (!focusRect || !endPointRect) { |
| 321 | + return false |
| 322 | + } |
| 323 | + |
| 324 | + if (endPointRect.top > focusRect.top) { |
| 325 | + // If the end point is positioned further from the top than the current |
| 326 | + // focus point, then we can deduce that the end point is on the next |
| 327 | + // line. In this case, we don't want to interfere since the browser |
| 328 | + // does right thing and expands the selection to the end of the current |
| 329 | + // line. |
| 330 | + return false |
| 331 | + } |
| 332 | + |
| 333 | + // If the end point is positioned at the same level as the current focus |
| 334 | + // point, then we can deduce that the end point is on the same line. In |
| 335 | + // this case, we want to expand the selection to the end point. |
| 336 | + // This mitigates a Firefox bug where Shift+ArrowDown can expand |
| 337 | + // further into the next block. |
| 338 | + return {focusBlockEndPoint, selection: snapshot.context.selection} |
| 339 | + }, |
| 340 | + actions: [ |
| 341 | + (_, {focusBlockEndPoint, selection}) => [ |
| 342 | + raise({ |
| 343 | + type: 'select', |
| 344 | + at: { |
| 345 | + anchor: selection.anchor, |
| 346 | + focus: focusBlockEndPoint, |
| 347 | + }, |
| 348 | + }), |
| 349 | + ], |
| 350 | + ], |
| 351 | + }), |
| 352 | + |
| 353 | + defineBehavior({ |
| 354 | + on: 'keyboard.keydown', |
| 355 | + guard: ({snapshot, event, dom}) => { |
| 356 | + if ( |
| 357 | + !snapshot.context.selection || |
| 358 | + !defaultKeyboardShortcuts.shiftDown.guard(event.originEvent) |
| 359 | + ) { |
| 360 | + return false |
| 361 | + } |
| 362 | + |
| 363 | + const focusTextBlock = getFocusTextBlock(snapshot) |
| 364 | + |
| 365 | + if (!focusTextBlock) { |
| 366 | + return false |
| 367 | + } |
| 368 | + |
| 369 | + const nextBlock = getNextBlock(snapshot) |
| 370 | + |
| 371 | + if (!nextBlock) { |
| 372 | + return false |
| 373 | + } |
| 374 | + |
| 375 | + const focusBlockEndPoint = getBlockEndPoint({ |
| 376 | + context: snapshot.context, |
| 377 | + block: focusTextBlock, |
| 378 | + }) |
| 379 | + |
| 380 | + // Find the DOM position of the current focus point |
| 381 | + const focusRect = dom.getSelectionRect({ |
| 382 | + ...snapshot, |
| 383 | + context: { |
| 384 | + ...snapshot.context, |
| 385 | + selection: { |
| 386 | + anchor: snapshot.context.selection.focus, |
| 387 | + focus: snapshot.context.selection.focus, |
| 388 | + }, |
| 389 | + }, |
| 390 | + }) |
| 391 | + // Find the DOM position of the focus block end point |
| 392 | + const endPointRect = dom.getSelectionRect({ |
| 393 | + ...snapshot, |
| 394 | + context: { |
| 395 | + ...snapshot.context, |
| 396 | + selection: { |
| 397 | + anchor: focusBlockEndPoint, |
| 398 | + focus: focusBlockEndPoint, |
| 399 | + }, |
| 400 | + }, |
| 401 | + }) |
| 402 | + |
| 403 | + if (!focusRect || !endPointRect) { |
| 404 | + return false |
| 405 | + } |
| 406 | + |
| 407 | + if (endPointRect.top > focusRect.top) { |
| 408 | + // If the end point is positioned further from the top than the current |
| 409 | + // focus point, then we can deduce that the end point is on the next |
| 410 | + // line. In this case, we don't want to interfere since the browser |
| 411 | + // does right thing and expands the selection to the end of the current |
| 412 | + // line. |
| 413 | + return false |
| 414 | + } |
| 415 | + |
| 416 | + // If the end point is positioned at the same level as the current focus |
| 417 | + // point, then we can deduce that the end point is on the same line. In |
| 418 | + // this case, we want to expand the selection to the end of the start |
| 419 | + // block. This mitigates a Chromium bug where Shift+ArrowDown can expand |
| 420 | + // further into the next block. |
| 421 | + const nextBlockStartPoint = getBlockStartPoint({ |
| 422 | + context: snapshot.context, |
| 423 | + block: nextBlock, |
| 424 | + }) |
| 425 | + |
| 426 | + return {nextBlockStartPoint, selection: snapshot.context.selection} |
| 427 | + }, |
| 428 | + actions: [ |
| 429 | + (_, {nextBlockStartPoint, selection}) => [ |
| 430 | + raise({ |
| 431 | + type: 'select', |
| 432 | + at: { |
| 433 | + anchor: selection.anchor, |
| 434 | + focus: nextBlockStartPoint, |
| 435 | + }, |
| 436 | + }), |
| 437 | + ], |
| 438 | + ], |
| 439 | + }), |
189 | 440 | ] |
0 commit comments