From 42aa2f8df6d0767c5a7fdeb9fbd63cf70ca37b93 Mon Sep 17 00:00:00 2001 From: azu Date: Fri, 24 Jul 2020 14:17:46 +0900 Subject: [PATCH] feat: support popup position --- packages/textchecker-element/package.json | 1 + .../textchecker-element/public/index.html | 10 ++- packages/textchecker-element/public/index.ts | 72 ++++++++++--------- .../src/text-checker-popup-element.ts | 14 ++-- .../src/textchecker-element.ts | 71 +++++++++++++----- .../src/textchecker-store.ts | 24 ++++++- yarn.lock | 12 ++++ 7 files changed, 142 insertions(+), 62 deletions(-) diff --git a/packages/textchecker-element/package.json b/packages/textchecker-element/package.json index f159658..35845df 100644 --- a/packages/textchecker-element/package.json +++ b/packages/textchecker-element/package.json @@ -62,6 +62,7 @@ "eventmit": "^1.0.2", "lit-html": "^1.2.1", "text-caret-pos": "^1.0.1", + "to-px": "^1.1.0", "@textlint/kernel": "^3.2.1", "@textlint/textlint-plugin-markdown": "^5.1.12", "textlint-rule-preset-ja-technical-writing": "^4.0.0" diff --git a/packages/textchecker-element/public/index.html b/packages/textchecker-element/public/index.html index 28e1c7a..a99332e 100644 --- a/packages/textchecker-element/public/index.html +++ b/packages/textchecker-element/public/index.html @@ -8,11 +8,14 @@ width: 800px; margin: auto; position: relative; + + display: flex; + align-items: center; } textarea { - width: 12em; - height: 5em; + width: 100%; + height: 10em; font-size: 24px; } @@ -25,7 +28,8 @@
- +
diff --git a/packages/textchecker-element/public/index.ts b/packages/textchecker-element/public/index.ts index 00da81f..3fd854a 100644 --- a/packages/textchecker-element/public/index.ts +++ b/packages/textchecker-element/public/index.ts @@ -1,10 +1,12 @@ import { TextCheckerElement } from "../src/textchecker-element"; import { TextCheckerPopupElement } from "../src/text-checker-popup-element"; import type { TextlintResult } from "@textlint/types"; +import { RectItem } from "../src/textchecker-store"; const targetElement = document.querySelector("#js-target") as HTMLTextAreaElement; const textChecker = new TextCheckerElement({ - targetElement: targetElement + targetElement: targetElement, + hoverPadding: 4 }); const textCheckerPopup = new TextCheckerPopupElement(); targetElement.before(textChecker); @@ -23,37 +25,37 @@ function debounce(fn: () => void, delay: number) { } const linter = new (window as any).Textlint(); -targetElement.addEventListener( - "input", - debounce(async () => { - const result: TextlintResult = await linter.lintText(targetElement.value); - const messages = result.messages; - console.log(result); - const annotations = messages.map((message) => { - const card = { - id: message.ruleId + "::" + message.index, - message: message.message - }; - return { - start: message.index, - end: message.index + 1, - onMouseEnter: ({ rectItem }) => { - console.log("enteR", count); - const boundingClientRect = targetElement.getBoundingClientRect(); - textCheckerPopup.updateCard(card, { - index: rectItem.index, - top: boundingClientRect.y + rectItem.top + rectItem.height, - left: boundingClientRect.x + rectItem.left, - width: rectItem.width, - height: rectItem.height - }); - }, - onMouseLeave() { - console.log("leave", count); - textCheckerPopup.dismissCard(card); - } - }; - }); - textChecker.updateAnnotations(annotations); - }, 500) -); +const update = debounce(async () => { + const result: TextlintResult = await linter.lintText(targetElement.value); + const messages = result.messages; + console.log(result); + const annotations = messages.map((message) => { + const card = { + id: message.ruleId + "::" + message.index, + message: message.message + }; + return { + start: message.index, + end: message.index + 1, + onMouseEnter: ({ rectItem }: { rectItem: RectItem }) => { + textCheckerPopup.updateCard(card, { + top: + rectItem.boxBorderWidth + + rectItem.boxMarginTop + + rectItem.boxPaddingTop + + rectItem.boxAbsoluteY + + rectItem.top + + rectItem.height, + left: rectItem.boxAbsoluteX + rectItem.left, + width: rectItem.width + }); + }, + onMouseLeave() { + textCheckerPopup.dismissCard(card); + } + }; + }); + textChecker.updateAnnotations(annotations); +}, 500); +targetElement.addEventListener("input", update); +update(); diff --git a/packages/textchecker-element/src/text-checker-popup-element.ts b/packages/textchecker-element/src/text-checker-popup-element.ts index f482520..8882022 100644 --- a/packages/textchecker-element/src/text-checker-popup-element.ts +++ b/packages/textchecker-element/src/text-checker-popup-element.ts @@ -1,17 +1,23 @@ import { html, render } from "lit-html"; import { eventmit } from "eventmit"; -import { RectItem } from "./textchecker-store"; export type TextCheckerElementAttributes = { target?: HTMLElement; }; -type TextCheckerCard = { +export type TextCheckerCard = { id: string; message: string; }; +export type TextCheckerCardRect = { + left: number; + top: number; + width: number; + height?: number; +}; + type TextCheckerPopupState = { card?: TextCheckerCard; - targetRect?: RectItem; + targetRect?: TextCheckerCardRect; }; const createTextCheckerPopupState = (state?: Partial) => { let currentState: TextCheckerPopupState = { @@ -78,7 +84,7 @@ export class TextCheckerPopupElement extends HTMLElement { }); } - public updateCard(card: TextCheckerCard, rect: RectItem) { + public updateCard(card: TextCheckerCard, rect: TextCheckerCardRect) { this.store.update({ card, targetRect: rect diff --git a/packages/textchecker-element/src/textchecker-element.ts b/packages/textchecker-element/src/textchecker-element.ts index 6680969..e2887e0 100644 --- a/packages/textchecker-element/src/textchecker-element.ts +++ b/packages/textchecker-element/src/textchecker-element.ts @@ -2,8 +2,10 @@ import textCaretPos from "text-caret-pos"; import { html, render } from "lit-html"; import { AnnotationItem, createTextCheckerStore, RectItem, TextCheckerState } from "./textchecker-store"; +const toPX = require("to-px"); export type TextCheckerElementAttributes = { targetElement: HTMLTextAreaElement; + hoverPadding: number; }; const Marker = (rect: RectItem, isHighLight: boolean = false) => { if (isHighLight) { @@ -21,10 +23,12 @@ export class TextCheckerElement extends HTMLElement { private annotationBox!: HTMLDivElement; private targetElement!: HTMLTextAreaElement; private store: ReturnType; + private hoverPadding: number; constructor(args: TextCheckerElementAttributes) { super(); this.targetElement = args.targetElement; + this.hoverPadding = args.hoverPadding; this.store = createTextCheckerStore(); } @@ -72,7 +76,18 @@ export class TextCheckerElement extends HTMLElement { }) .join(""); this.annotationBox.setAttribute("style", copyStyle + "pointer-events: none;"); - // + // box + const fontSize: number = toPX(targetStyle.getPropertyValue("font-size")) ?? 16.123; + const boxMarginTop: number = toPX(targetStyle.getPropertyValue("margin-top")) ?? 0; + const boxMarginBottom: number = toPX(targetStyle.getPropertyValue("margin-bottom")) ?? 0; + const boxBorderWidth: number = toPX(targetStyle.getPropertyValue("border-width")) ?? 0; + const boxPaddingTop: number = toPX(targetStyle.getPropertyValue("padding-top")) ?? 0; + const boxPaddingBottom: number = toPX(targetStyle.getPropertyValue("padding-bottom")) ?? 0; + const boundingClientRect = target.getBoundingClientRect(); + const boxAbsoluteX: number = boundingClientRect.x; + const boxAbsoluteY: number = boundingClientRect.y; + const boxWidth: number = boundingClientRect.width; + const boxHeight: number = boundingClientRect.height; const rectItems = annotationItems.flatMap((annotation, index) => { const start = annotation.start; const end = annotation.end; @@ -89,7 +104,6 @@ export class TextCheckerElement extends HTMLElement { returnDiv: true, debug: false }); - const fontSize = Number(targetStyle.getPropertyValue("font-size").replace("px", "")); const rectItems: RectItem[] = startCoordinate.top === endCoordinate.top ? [ @@ -98,7 +112,16 @@ export class TextCheckerElement extends HTMLElement { left: target.offsetLeft - target.scrollLeft + startCoordinate.left, top: target.offsetTop - target.scrollTop + startCoordinate.top, height: fontSize, //startCoordinate.height, - width: endCoordinate.left - startCoordinate.left + width: endCoordinate.left - startCoordinate.left, + boxMarginTop, + boxMarginBottom, + boxBorderWidth, + boxAbsoluteX, + boxAbsoluteY, + boxWidth, + boxHeight, + boxPaddingTop, + boxPaddingBottom } ] : // two line @@ -108,14 +131,31 @@ export class TextCheckerElement extends HTMLElement { left: target.offsetLeft - target.scrollLeft + startCoordinate.left, top: target.offsetTop - target.scrollTop + startCoordinate.top, height: fontSize, //startCoordinate.height, - width: (startCoordinate?._div?.getBoundingClientRect()?.width ?? 0) - startCoordinate.left + width: + (startCoordinate?._div?.getBoundingClientRect()?.width ?? 0) - startCoordinate.left, + boxMarginTop, + boxMarginBottom, + boxBorderWidth, + boxAbsoluteX, + boxAbsoluteY, + boxWidth, + boxHeight }, { index, left: target.offsetLeft - target.scrollLeft, top: target.offsetTop - target.scrollTop + endCoordinate.top, height: fontSize, - width: (startCoordinate?._div?.getBoundingClientRect()?.left ?? 0) + endCoordinate.left + width: (startCoordinate?._div?.getBoundingClientRect()?.left ?? 0) + endCoordinate.left, + boxMarginTop, + boxMarginBottom, + boxBorderWidth, + boxAbsoluteX, + boxAbsoluteY, + boxWidth, + boxHeight, + boxPaddingTop, + boxPaddingBottom } ]; return rectItems; @@ -132,22 +172,19 @@ export class TextCheckerElement extends HTMLElement { }; onMouseUpdate = (event: MouseEvent) => { - const clientRect = (event.currentTarget as HTMLTextAreaElement)?.getBoundingClientRect() ?? { - left: 0, - top: 0 - }; - const point = { - x: event.clientX - clientRect.left, - y: event.clientY - clientRect.top - }; const state = this.store.get(); + const hoverPadding = this.hoverPadding; const isIncludedIndexes = state.rectItems .filter((rect) => { + const point = { + x: event.clientX - rect.boxAbsoluteX, + y: event.clientY - rect.boxAbsoluteY + }; return ( - rect.left <= point.x && - point.x <= rect.left + rect.width && - rect.top <= point.y && - point.y <= rect.top + rect.height + rect.left - hoverPadding <= point.x && + point.x <= rect.left + hoverPadding + rect.width && + rect.top - hoverPadding <= point.y && + point.y <= rect.top + rect.height + hoverPadding ); }) .map((item) => item.index); diff --git a/packages/textchecker-element/src/textchecker-store.ts b/packages/textchecker-element/src/textchecker-store.ts index a304959..4745c71 100644 --- a/packages/textchecker-element/src/textchecker-store.ts +++ b/packages/textchecker-element/src/textchecker-store.ts @@ -6,8 +6,26 @@ export type AnnotationItem = { onMouseEnter: ({ rectItem }: { rectItem: RectItem }) => void; onMouseLeave: ({ rectItem }: { rectItem: RectItem }) => void; }; - -export type RectItem = { index: number; left: number; top: number; height: number; width: number }; +/** + * RectItem is pixel based + */ +export type RectItem = { + index: number; + left: number; + top: number; + height: number; + width: number; + // box + boxPaddingTop: number; + boxPaddingBottom: number; + boxMarginTop: number; + boxMarginBottom: number; + boxBorderWidth: number; + boxAbsoluteX: number; + boxAbsoluteY: number; + boxWidth: number; + boxHeight: number; +}; export type TextCheckerState = { rectItems: RectItem[]; annotationItems: AnnotationItem[]; @@ -37,7 +55,7 @@ export const createTextCheckerStore = (initialState?: Partial) highlightRectIndexes(indexes: RectItem["index"][]) { textCheckerState = { ...textCheckerState, - highlightRectIdSet: new Set([...textCheckerState.highlightRectIdSet, ...indexes]) + highlightRectIdSet: new Set(indexes) }; changeEvent.emit(); }, diff --git a/yarn.lock b/yarn.lock index 2c1bc60..6c693b1 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5018,6 +5018,11 @@ parse-json@^5.0.0: json-parse-better-errors "^1.0.1" lines-and-columns "^1.1.6" +parse-unit@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/parse-unit/-/parse-unit-1.0.1.tgz#7e1bb6d5bef3874c28e392526a2541170291eecf" + integrity sha1-fhu21b7zh0wo45JSaiVBFwKR7s8= + parse5@5.1.0: version "5.1.0" resolved "https://registry.yarnpkg.com/parse5/-/parse5-5.1.0.tgz#c59341c9723f414c452975564c7c00a68d58acd2" @@ -7098,6 +7103,13 @@ to-object-path@^0.3.0: dependencies: kind-of "^3.0.2" +to-px@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/to-px/-/to-px-1.1.0.tgz#b6b269ed5db0cc9aefc15272a4c8bcb2ca1e99ca" + integrity sha512-bfg3GLYrGoEzrGoE05TAL/Uw+H/qrf2ptr9V3W7U0lkjjyYnIfgxmVLUfhQ1hZpIQwin81uxhDjvUkDYsC0xWw== + dependencies: + parse-unit "^1.0.1" + to-regex-range@^2.1.0: version "2.1.1" resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-2.1.1.tgz#7c80c17b9dfebe599e27367e0d4dd5590141db38"