@@ -61,6 +61,36 @@ export default class Mapper {
6161 * @member {Map}
6262 */
6363 this . _viewToModelLengthCallbacks = new Map ( ) ;
64+
65+ // Default mapper algorithm for mapping model position to view position.
66+ this . on ( 'modelToViewPosition' , ( evt , data ) => {
67+ if ( data . viewPosition ) {
68+ return ;
69+ }
70+
71+ let viewContainer = this . _modelToViewMapping . get ( data . modelPosition . parent ) ;
72+
73+ data . viewPosition = this . _findPositionIn ( viewContainer , data . modelPosition . offset ) ;
74+ } , { priority : 'low' } ) ;
75+
76+ // Default mapper algorithm for mapping view position to model position.
77+ this . on ( 'viewToModelPosition' , ( evt , data ) => {
78+ if ( data . modelPosition ) {
79+ return ;
80+ }
81+
82+ let viewBlock = data . viewPosition . parent ;
83+ let modelParent = this . _viewToModelMapping . get ( viewBlock ) ;
84+
85+ while ( ! modelParent ) {
86+ viewBlock = viewBlock . parent ;
87+ modelParent = this . _viewToModelMapping . get ( viewBlock ) ;
88+ }
89+
90+ let modelOffset = this . _toModelOffset ( data . viewPosition . parent , data . viewPosition . offset , viewBlock ) ;
91+
92+ data . modelPosition = ModelPosition . createFromParentAndOffset ( modelParent , modelOffset ) ;
93+ } , { priority : 'low' } ) ;
6494 }
6595
6696 /**
@@ -159,7 +189,6 @@ export default class Mapper {
159189 toModelPosition ( viewPosition ) {
160190 const data = {
161191 viewPosition : viewPosition ,
162- modelPosition : this . _defaultToModelPosition ( viewPosition ) ,
163192 mapper : this
164193 } ;
165194
@@ -168,19 +197,6 @@ export default class Mapper {
168197 return data . modelPosition ;
169198 }
170199
171- /**
172- * Maps model position to view position using default mapper algorithm.
173- *
174- * @private
175- * @param {module:engine/model/position~Position } modelPosition
176- * @returns {module:engine/view/position~Position } View position mapped from model position.
177- */
178- _defaultToViewPosition ( modelPosition ) {
179- let viewContainer = this . _modelToViewMapping . get ( modelPosition . parent ) ;
180-
181- return this . _findPositionIn ( viewContainer , modelPosition . offset ) ;
182- }
183-
184200 /**
185201 * Gets the corresponding view position.
186202 *
@@ -190,7 +206,6 @@ export default class Mapper {
190206 */
191207 toViewPosition ( modelPosition ) {
192208 const data = {
193- viewPosition : this . _defaultToViewPosition ( modelPosition ) ,
194209 modelPosition : modelPosition ,
195210 mapper : this
196211 } ;
@@ -200,27 +215,6 @@ export default class Mapper {
200215 return data . viewPosition ;
201216 }
202217
203- /**
204- * Maps view position to model position using default mapper algorithm.
205- *
206- * @private
207- * @param {module:engine/view/position~Position } viewPosition
208- * @returns {module:engine/model/position~Position } Model position mapped from view position.
209- */
210- _defaultToModelPosition ( viewPosition ) {
211- let viewBlock = viewPosition . parent ;
212- let modelParent = this . _viewToModelMapping . get ( viewBlock ) ;
213-
214- while ( ! modelParent ) {
215- viewBlock = viewBlock . parent ;
216- modelParent = this . _viewToModelMapping . get ( viewBlock ) ;
217- }
218-
219- let modelOffset = this . _toModelOffset ( viewPosition . parent , viewPosition . offset , viewBlock ) ;
220-
221- return ModelPosition . createFromParentAndOffset ( modelParent , modelOffset ) ;
222- }
223-
224218 /**
225219 * Registers a callback that evaluates the length in the model of a view element with given name.
226220 *
@@ -442,65 +436,89 @@ export default class Mapper {
442436 // Otherwise, just return the given position.
443437 return viewPosition ;
444438 }
445- }
446439
447- mix ( Mapper , EmitterMixin ) ;
440+ /**
441+ * Fired for each model-to-view position mapping request. The purpose of this event is to enable custom model-to-view position
442+ * mapping. Callbacks added to this event take {@link module:engine/model/position~Position model position} and are expected to calculate
443+ * {@link module:engine/view/position~Position view position}. Calculated view position should be added as `viewPosition` value in
444+ * `data` object that is passed as one of parameters to the event callback.
445+ *
446+ * // Assume that "captionedImage" model element is converted to <img> and following <span> elements in view,
447+ * // and the model element is bound to <img> element. Force mapping model positions inside "captionedImage" to that <span> element.
448+ * mapper.on( 'modelToViewPosition', ( evt, data ) => {
449+ * const positionParent = modelPosition.parent;
450+ *
451+ * if ( positionParent.name == 'captionedImage' ) {
452+ * const viewImg = data.mapper.toViewElement( positionParent );
453+ * const viewCaption = viewImg.nextSibling; // The <span> element.
454+ *
455+ * data.viewPosition = new ViewPosition( viewCaption, modelPosition.offset );
456+ *
457+ * // Stop the event if other callbacks should not modify calculated value.
458+ * evt.stop();
459+ * }
460+ * } );
461+ *
462+ * **Note:** keep in mind that custom callback provided for this event should use provided `data.modelPosition` only to check
463+ * what is before the position (or position's parent). This is important when model-to-view position mapping is used in
464+ * remove change conversion. Model after the removed position (that is being mapped) does not correspond to view, so it cannot be used:
465+ *
466+ * // Incorrect:
467+ * const modelElement = data.modelPosition.nodeAfter;
468+ * const viewElement = data.mapper.toViewElement( modelElement );
469+ * // ... Do something with `viewElement` and set `data.viewPosition`.
470+ *
471+ * // Correct:
472+ * const prevModelElement = data.modelPosition.nodeBefore;
473+ * const prevViewElement = data.mapper.toViewElement( prevModelElement );
474+ * // ... Use `prevViewElement` to find correct `data.viewPosition`.
475+ *
476+ * **Note:** default mapping callback is provided with `low` priority setting and does not cancel the event, so it is possible to attach
477+ * a custom callback after default callback and also use `data.viewPosition` calculated by default callback (for example to fix it).
478+ *
479+ * **Note:** default mapping callback will not fire if `data.viewPosition` is already set.
480+ *
481+ * **Note:** these callbacks are called **very often**. For efficiency reasons, it is advised to use them only when position
482+ * mapping between given model and view elements is unsolvable using just elements mapping and default algorithm. Also,
483+ * the condition that checks if special case scenario happened should be as simple as possible.
484+ *
485+ * @event modelToViewPosition
486+ * @param {Object } data Data pipeline object that can store and pass data between callbacks. The callback should add
487+ * `viewPosition` value to that object with calculated {@link module:engine/view/position~Position view position}.
488+ * @param {module:engine/conversion/mapper~Mapper } data.mapper Mapper instance that fired the event.
489+ */
448490
449- /**
450- * Fired for each model -to-view position mapping request. The purpose of this event is to enable custom model-to-view position
451- * mapping. Callbacks added to this event take { @link module:engine/model/position~Position model position} and are expected to calculate
452- * { @link module:engine/view/position~Position view position}. Calculated view position should be added as `viewPosition` value in
453- * `data` object that is passed as one of parameters to the event callback .
454- *
455- * // Assume that "captionedImage" model element is converted to <img> and following <span> elements in view,
456- * // and the model element is bound to <img> element. Force mapping model positions inside "captionedImage" to that <span> element.
457- * mapper.on ( 'modelToViewPosition', ( evt, data ) => {
458- * const positionParent = modelPosition.parent ;
459- *
460- * if ( positionParent.name == 'captionedImage' ) {
461- * const viewImg = mapper.toViewElement( positionParent );
462- * const viewCaption = viewImg.nextSibling; // The <span> element.
463- *
464- * data.viewPosition = new ViewPosition( viewCaption, modelPosition.offset );
465- * evt.stop();
466- * }
467- * } );
468- *
469- * **Note:** these callbacks are called **very often**. For efficiency reasons, it is advised to use them only when position
470- * mapping between given model and view elements is unsolvable using just elements mapping and default algorithm. Also,
471- * the condition that checks if special case scenario happened should be as simple as possible.
472- *
473- * @event modelToViewPosition
474- * @param { Object } data Data pipeline object that can store and pass data between callbacks. The callback should add
475- * `viewPosition` value to that object with calculated { @link module:engine/view/position~Position view position}.
476- * @param {module:engine/model/position~Position } data.modelPosition Model position to be mapped.
477- * @param { module:engine/view /position~Position } data.viewPosition View position that is a result of mapping
478- * `modelPosition` using ` Mapper` default algorithm .
479- * @param { module:engine/conversion/mapper~Mapper } data.mapper Mapper instance that fired the event.
480- */
491+ /**
492+ * Fired for each view -to-model position mapping request. See { @link module:engine/conversion/mapper~Mapper# event:modelToViewPosition}.
493+ *
494+ * // See example in `modelToViewPosition` event description.
495+ * // This custom mapping will map positions from <span> element next to <img> to the "captionedImage" element .
496+ * mapper.on( 'viewToModelPosition', ( evt, data ) => {
497+ * const positionParent = viewPosition.parent;
498+ *
499+ * if ( positionParent.hasClass ( 'image-caption' ) ) {
500+ * const viewImg = positionParent.previousSibling ;
501+ * const modelImg = data.mapper.toModelElement( viewImg );
502+ *
503+ * data.modelPosition = new ModelPosition( modelImg, viewPosition.offset );
504+ * evt.stop();
505+ * }
506+ * } );
507+ *
508+ * **Note:** default mapping callback is provided with `low` priority setting and does not cancel the event, so it is possible to attach
509+ * a custom callback after default callback and also use `data.modelPosition` calculated by default callback (for example to fix it).
510+ *
511+ * **Note:** default mapping callback will not fire if `data.modelPosition` is already set.
512+ *
513+ * **Note:** these callbacks are called **very often**. For efficiency reasons, it is advised to use them only when position
514+ * mapping between given model and view elements is unsolvable using just elements mapping and default algorithm. Also,
515+ * the condition that checks if special case scenario happened should be as simple as possible.
516+ *
517+ * @event viewToModelPosition
518+ * @param {Object } data Data pipeline object that can store and pass data between callbacks. The callback should add
519+ * `modelPosition` value to that object with calculated { @link module:engine/model /position~Position model position}.
520+ * @param { module:engine/conversion/mapper~Mapper } data.mapper Mapper instance that fired the event .
521+ */
522+ }
481523
482- /**
483- * Fired for each view-to-model position mapping request. See {@link module:engine/conversion/mapper~Mapper#event:modelToViewPosition}.
484- *
485- * // See example in `modelToViewPosition` event description.
486- * // This custom mapping will map positions from <span> element next to <img> to the "captionedImage" element.
487- * mapper.on( 'viewToModelPosition', ( evt, data ) => {
488- * const positionParent = viewPosition.parent;
489- *
490- * if ( positionParent.hasClass( 'image-caption' ) ) {
491- * const viewImg = positionParent.previousSibling;
492- * const modelImg = mapper.toModelElement( viewImg );
493- *
494- * data.modelPosition = new ModelPosition( modelImg, viewPosition.offset );
495- * evt.stop();
496- * }
497- * } );
498- *
499- * @event viewToModelPosition
500- * @param {Object } data Data pipeline object that can store and pass data between callbacks. The callback should add
501- * `modelPosition` value to that object with calculated {@link module:engine/model/position~Position model position}.
502- * @param {module:engine/view/position~Position } data.viewPosition View position to be mapped.
503- * @param {module:engine/model/position~Position } data.modelPosition Model position that is a result of mapping
504- * `viewPosition` using `Mapper` default algorithm.
505- * @param {module:engine/conversion/mapper~Mapper } data.mapper Mapper instance that fired the event.
506- */
524+ mix ( Mapper , EmitterMixin ) ;
0 commit comments