Skip to content

Commit

Permalink
perf(textchecker-element): drop non-visible annotation
Browse files Browse the repository at this point in the history
  • Loading branch information
azu committed Aug 2, 2020
1 parent b49c608 commit 8590b90
Show file tree
Hide file tree
Showing 5 changed files with 667 additions and 8 deletions.
3 changes: 1 addition & 2 deletions packages/textchecker-element/public/index.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
import { attachToTextArea } from "../src/index";
import { attachToTextArea, LintEngineAPI } from "../src/index";
import type { TextlintFixResult, TextlintMessage, TextlintResult } from "@textlint/types";
import type {
TextlintWorkerCommandFix,
TextlintWorkerCommandLint,
TextlintWorkerCommandResponse
} from "@textlint/compiler";
import { LintEngineAPI } from "../src/attach-to-text-area";

const statusElement = document.querySelector("#js-status");
const updateStatus = (status: string) => {
Expand Down
32 changes: 26 additions & 6 deletions packages/textchecker-element/src/text-checker-element.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import textCaretPos from "text-caret-pos";
import { getCoordinates } from "./util/text-caret-pos";
import { html, render } from "lit-html";
import {
AnnotationItem,
Expand Down Expand Up @@ -135,21 +135,40 @@ export class TextCheckerElement extends HTMLElement {
};
this.annotationBox.scrollTop = target.scrollTop;
this.annotationBox.scrollLeft = target.scrollLeft;
const rectItems = annotationItems.flatMap((annotation, index) => {
// FIXME: more correct way
// AnnotationItems should be sorted by range.
// Because, drop non-visible rect items for performance.
// Example)
// ZYXWVUTSRQPONMLKJIHGFEDCBA
// ^^^^^^^--------------------
// visible ^^^^^
// non-visible = drop from `rectItems`
const annotationItemsByDescendingOrder = annotationItems.slice().reverse();
let stopSearchAboveIsNotVisible = false;
const rectItems = annotationItemsByDescendingOrder.flatMap((annotation, index) => {
// already the annotation is not visible, skip it
if (stopSearchAboveIsNotVisible) {
return [];
}
const start = annotation.start;
const end = annotation.end;
// 0 start
const startCoordinate = textCaretPos.getCoordinates(this.targetElement, start, {
const startCoordinate = getCoordinates(this.targetElement, start, {
reuse: true,
returnHeight: true,
returnDiv: true,
debug: false
debug: true
});
const endCoordinate = textCaretPos.getCoordinates(this.targetElement, end, {
// Stop to search if out of visible
if (startCoordinate.top + fontSize < visibleArea.top) {
stopSearchAboveIsNotVisible = true;
return [];
}
const endCoordinate = getCoordinates(this.targetElement, end, {
reuse: true,
returnHeight: true,
returnDiv: true,
debug: false
debug: true
});
const rectItems: TextCheckerElementRectItem[] =
startCoordinate.top === endCoordinate.top
Expand Down Expand Up @@ -211,6 +230,7 @@ export class TextCheckerElement extends HTMLElement {
];
return rectItems;
});
console.log("rectItems.length", rectItems.length);
this.store.update({
annotationItems,
rectItems
Expand Down
174 changes: 174 additions & 0 deletions packages/textchecker-element/src/util/text-caret-pos.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
/**
* optional settings for calculating the coordinates
*/
declare interface CaretOptions {
/**
* show shadow DIV that is used for calculating the caret coordinates; this will also include the created DIV in the coordinates-object in property `_div`
*
* (DEFAULT: false)
*/
debug?: boolean;
/**
* reuse shadow DIV that is used for calculating the caret coordinates
*
* (DEFAULT: false)
*/
reuse?: boolean;
/**
* if reuse was enabled, returns the shadow DIV in the coordinates-object in property `_div`
*
* (DEFAULT: false)
*/
returnDiv?: boolean;
/**
* returns the caret offset height instead of the computed `lineHeight` in the returned
* coordinates-object in property `height`
*
* (DEFAULT: false)
*/
returnHeight?: boolean;
/**
* the id attribute for the shadow DIV
*
* (DEFAULT: "input-textarea-caret-position-mirror-div")
*/
id?: string;
/**
* if TRUE, styling of the shadow DIV is not updated, if the current target element has the same type (Tag Name) as the previous one.
* If function: a callback for determining, if the shadow DIV's style should be updated (return TRUE, if it should get updated): `callback(shadowDiv) : boolean`
* NOTE this option is only relevant, if "reuse" is TRUE.
*
* (DEFAULT: false)
*/
guessIfUpdateStyle?: boolean | ((fauxDiv: HTMLElement) => boolean);
/**
* force updating the style of the shadow DIV; only relevant, if "reuse" is TRUE
*
* (DEFAULT: false)
*/
forceUpdateStyle?: boolean;
/**
* force faux span to use "cleared" style (e.g. in case SPAN is globally styled)
*
* (DEFAULT: false)
*/
forceClearFauxStyle?: boolean;
/**
* use ID for faux span (e.g. for styling faux span)
*
* (DEFAULT: undefined)
*/
fauxId?: string;
/**
* apply zoom factor to font-size.
* If `true` (boolean) the zoom factor will be calculated using `measureFontZoom()`, and the option-value
* (`true`) will be replaced with the measured zoom factor.
*
* (DEFAULT: undefined)
*/
fontZoom?: number | boolean;
/**
* if TRUE, allows text-wrapping for INPUT elements
*
* Note: the W3C specifically states that text in INPUT will not be wrapped, even if styles would "request" it,
* like "word-wrap: break-word" or "word-break: break-all | break-word" or similar
*
* (DEFAULT: false)
*/
allowInputWrap?: boolean;
/**
* transfers additional styles properties from the target element to the shadow DIV
*/
additionalStyles?: Array<string>;
/**
* transfers additional (node) attributes from the target element to the shadow DIV
*
* (DEFAULT: undefined)
*/
additionalAttributes?: Array<string>;
/**
* the text value that should be used for the calculation.
*
* If function: a callback which's return value is used as the text: `callback(element, options) : string`
*
* (DEFAULT: undefined)
*/
text?: string | ((element: HTMLElement, options: CaretOptions) => string);
}

export interface CaretPosition {
top: number;
left: number;
height: number;
_div?: HTMLElement;
}

/**
* create faux DIV
* @param [options] additional options
*/
export function createDiv(options?: CaretOptions): void;

/**
* calculate a "font zoom": zoom that is applied "by the environment" and not set by the CSS/HTML itself
* (e.g. some Android variants allow setting a font zoom in the system settings).
* If not "font zoom" is detected, returns `1` by default (i.e. no scaling).
* @return the font zoom
*/
export function measureFontZoom(): number;
/**
* apply styling of the target-element to the faux-DIV for accurately calculating the coordinates
* @param element the target element (textarea or input)
* @param position the character/text index, i.e. position for which the coordinates should be calculated
* @param div the faux DIV for calculating the coordinates
* @param [options] additional options
*/
export function styleDiv(
element: HTMLInputElement | HTMLTextAreaElement,
position: number,
div: HTMLElement,
options?: CaretOptions
): void;

/**
* reset styling for faux DIV, i.e. force re-styling for next calculation (only relevant if DIV is reused)
*/
export function resetStyleDiv(): void;

/**
* reset/remove faux DIV, if reuse or debug was enabled
*
* NOTE if faux DIV was created with custom `options.id`,
* then the options-argument must contain the same `id`
* (otherwise this call will have not effect)
* @param [options] additional options
*/
export function resetDiv(options?: CaretOptions): void;

/**
* recalulate the coordinates (e.g. due to changed text) without re-styling the faux DIV
*
* @param element the target element (textarea or input)
* @param position the character/text index, i.e. position for which the coordinates should be calculated
* @param div the faux DIV for calculating the coordinates
* @param [options] additional options
*/
export function updateCoordinates(
element: HTMLInputElement | HTMLTextAreaElement,
position: number,
div: HTMLElement,
options?: CaretOptions
): CaretPosition;

/**
* get coordinates in the target `element` for the text `position` (i.e. index in string)
*
* @param element the target element (textarea or input)
* @param position the character/text index, i.e. position for which the coordinates should be calculated
* @param [options] additional options
*/
export function getCoordinates(
element: HTMLInputElement | HTMLTextAreaElement,
position: number,
options?: CaretOptions
): CaretPosition;
Loading

0 comments on commit 8590b90

Please sign in to comment.