Dynamic Text Anchors
npm install dynamic-text-anchorsbash
npm i dynamic-text-anchors
`
Styles
For default styling of the custom elements, include the following CSS file in your project. This is optional; you can also create your own styles or easily override them.
`typescript
import 'dynamic-text-anchors/dist/lib/utils/_styles.css';
`
How to use
The library's core is the DTA class, which serves as the main entry point and orchestrator. You instantiate it with a root DOM element and then interact with its public methods to manage anchors and renderers.
Basic Usage Example
Here is a quick example of how to set up the library and create highlights from a user's selection.
`typescript
// Assume your project has an HTML file with an element with id="content"
import { DTA, InlineRenderer } from 'dynamic-text-anchors';
import 'dynamic-text-anchors/dist/lib/utils/_styles.css'; // Optional, for default styling
// 1. Get the root element you want to work with.
const contentElement = document.getElementById('content');
if (contentElement) {
// 2. Instantiate the DTA library
const dta = new DTA();
// 3. Create a renderer for how anchors will be displayed, linking it to the content element.
const inlineRenderer = new InlineRenderer(contentElement);
// 4. Add the renderer to the DTA instance.
dta.addRenderer(inlineRenderer);
// 5. Add a listener to the root element to create an anchor on selection.
contentElement.addEventListener('mouseup', () => {
dta.createAnchorFromSelection();
});
}
`
-----
DTA methods
The DTA class manages the lifecycle of all anchors and renderers.
* constructor()\
Instantiates the DTA library instance.
* addRenderer(renderer: RendererI): void\
Adds a new renderer to the DTA instance. This is how you tell DTA how to display anchors.
* removeRenderer(renderer: RendererI): void\
Removes a renderer and its rendered anchors from the DTA instance.
* createAnchorFromSelection(selection?: Selection | null): void\
Creates an anchor based on the current user selection. If no selection is provided, it uses the active one.
* createAnchorFromRange(range: Range): void\
Creates an anchor from a given Range object.
* removeAnchor(anchor: AnchorI): void\
Destroys a specific anchor and removes it from the DOM.
* canAnchorMerge(anchor: AnchorI, direction: MergeDirection): boolean\
Checks if an anchor can be merged with an adjacent anchor in a given direction ('left' or 'right').
* mergeAnchor(anchor: AnchorI, direction: MergeDirection): void\
Merges an anchor with an adjacent anchor in the specified direction.
* serialize(): SerializedDTA\
Returns a serializable JSON object of all active anchors. This can be used to save the anchors to a database.
* deserialize(data: SerializedDTA): void\
Loads a set of anchors from a serialized JSON object, rendering them in the DOM.
* clearAnchors(): void\
Destroys all anchors managed by the instance.
* clearRenderers(): void\
Destroys all renderers and their rendered content.
* destroy(): void\
Destroys the DTA instance, all anchors, and all renderers.
-----
Anchor methods
The Anchor class represents a single text highlight. You typically interact with it via the DTA instance, but you can also manipulate it directly.
* constructor(range: DTARange)\
Instantiates a new Anchor object from a DTARange object. A unique id is automatically generated.
* setColor(bg: ColorValue, fg?: ColorValue): void\
Sets the background and optional foreground colors of the anchor. The fg defaults to an inverted version of bg.
* setRange(range: DTARange): void\
Updates the anchor's underlying range.
* acceptChange(): void\
Marks the anchor’s change as accepted (e.g., removes the "changed" state).
* requestFocus(focus: boolean): void\
Emits a request to the Event Bus for the anchor's associated element to be focused or unfocused.
* requestMerge(direction: MergeDirection): void\
Emits a request to the Event Bus for the anchor to be merged with a neighboring anchor.
* serialize(): SerializedAnchor\
Returns a serializable JSON object of the anchor's data.
* static deserialize(data: SerializedAnchor): Anchor\
Creates a new Anchor instance from a serialized JSON object.
* destroy(): void\
Removes the anchor from the DTA instance and the DOM.
-----
AnchorElement methods
The AnchorElement is a custom DOM element that represents the rendered anchor.
* render(): void\
Renders the element into the DOM.
* requestFocus(focus: boolean): void\
Requests that the element gain or lose focus.
* requestHover(hover: boolean): void\
Requests that the element gain or lose hover state.
* requestMerge(direction: MergeDirection): void\
Requests a merge action for this anchor element.
* requestDestroy(): void\
Requests removal of this anchor element.
* toggleFocus(focus: boolean): void\
Applies or removes the focus state on the element.
* toggleHover(hover: boolean): void\
Applies or removes the hover state on the element.
* destroy(): void\
Removes the element from the DOM and performs cleanup.
-----
Renderers
Renderers are responsible for the visual representation of anchors. The library provides abstract base classes to help you create your own, and two built-in renderers to get you started.
$3
* InlineRenderer: Renders anchors as inline elements that wrap the text.
* ListRenderer: Renders anchors as list items in a separate container.
$3
You must add a renderer to a DTA instance to see any anchors rendered.
`typescript
const dta = new DTA();
const listRenderer = new ListRenderer(document.getElementById('list-container'));
dta.addRenderer(listRenderer);
`
$3
* renderAnchor(anchor: AnchorI): void\
Renders a specific anchor in the DOM.
* updateAnchor(anchor: AnchorI): void\
Updates the visual representation of a rendered anchor. The default implementation calls removeAnchor and then renderAnchor.
* removeAnchor(anchor: AnchorI): void\
Removes a rendered anchor from the DOM.
* focusAnchor(anchor: AnchorI, focus: boolean): void\
Toggles the focus state of the rendered anchor element.
* hoverAnchor(anchor: AnchorI, hover: boolean): void\
Toggles the hover state of the rendered anchor element.
* destroy(): void\
Destroys the renderer and removes all of its rendered content from the DOM.
-----
Event Bus
The library uses a global EventBus to handle communication between different components. All components have an event bus instance you can listen to.
$3
`typescript
import { EventBus } from "dynamic-text-anchors";
const eventBus = EventBus.getInstance();
eventBus.on("anchor:create", (event) => {
console.log("New anchor created:", event.payload.anchor.id);
}, this);
`
$3
* on\
Subscribes a function to a specific event type. The target is used for cleanup.
* off\
Unsubscribes a specific function from an event.
* offAll(target: any): void\
Removes all subscriptions for a given target object.
* emit\
Emits an event, triggering all subscribed functions.
-----
Utility Methods
A small set of utility functions are also exported for convenience.
$3
* getSelection(): Selection | null\
Returns the active Selection object or null.
* buildTextIndex(root: Node): TextIndex\
Creates an index of all text nodes within a root element, mapping character positions to DOM nodes.
* deserializeRange(range: DTARange, root: Node): Range | null\
Reconstructs a Range object from a serialized DTARange object within a given root node.
* getAllTextNodes(range: Range): Text[]\
Returns all text nodes within a given Range object.
$3
* isValidHexColor(color: string): boolean\
Checks if a string is a valid hex color string.
* invertHexColor(hex: string): ColorValue\
Inverts a hex color to be readable on the original color.
* adjustColorBrightness(hex: string, percent: number): ColorValue\
Adjusts the brightness of a hex color by a given percentage.
* generateRandomColor(): ColorValue\
Generates a random valid hex color.
$3
* normalizeString(str: string): string\
Returns a string in a normalized form by removing diacritics, punctuation, numbers, and collapsing whitespace.
* escapeRegExp(str: string): string\
Escapes a string for use in a regular expression.
* calculateStringSimilarity(str1: string, str2: string): number`\