<text>

<text> is a built-in element in Lynx used to display text content. It supports specifying text style, binding click event callbacks, and can nest <text>, <image>, and <view> elements to achieve relatively complex text and image content presentation.

Usage

The <text> element has its own layout context similar to the Web's inline formatting context. It does not support display properties like <view>. For advanced text styling with <text> element, see the Text and Typography guide.

Nested<text>

Nested <text> refers to <text> subelements within a <text> element.

Since the CSS inheritance is not enabled by default in Lynx, it's recommended to explicitly declare the required styles for <text>, as it will not inherit the text-related properties from its parent node.

// When CSS inheritance is not enabled, the font-size of the parent node will not be applied to the child <text> node.
<view style={{ fontSize: '20px' }}>
  <text>hello world</text>
</view>

However, nested <text> is special. Even when CSS inheritance is not enabled, it will still apply <color> and <font-family> properties of the parent <text>. To maintain consistency, it is recommended to explicitly override the properties of the parent <text> in the inline <text>.

<text style={{ color: 'red' }}>
  red
  <text>red</text>
  <text style={{ color: 'blue' }}>blue</text>
</text>

Nested<image>

Nested <image> refers to <image> subelements within a <text> element. It can be used for mixed text and image layout.

Nested<view>

A <view> written inside a text element will have inline characteristics and participate in text layout. It also supports all functionalities of the <view> tag, including adding borders, rounded corners, and any other element content inside it.

<inline-truncation>

<inline-truncation> tag is used to customize the content that needs to be displayed at the end of the text when truncation occurs.

RTL

The text element supports RTL (Right-To-Left) language layout display. By default, the text element will determine the text language based on the content and use the corresponding layout method. Developers can also specify the use of RTL layout by setting the direction style.

TIP

When direction is set to rtl or lynx-rtl, text-align:start will be converted to text-align:right, and similarly, text-align:end will be converted to text-align:left. When direction is set to lynx-rtl, text-align:left will be converted to text-align:right, and similarly, text-align:right will be converted to text-align:left.

Attributes

Attribute names and values are used to describe the behavior and appearance of elements.

text-maxline

// DefaultValue: '-1'
text-maxline?: number;

Limits the maximum number of lines displayed for the text content, overflow:hidden should be set simultaneously.

include-font-padding
Android only

// DefaultValue: false
include-font-padding?: boolean;

Add additional padding for Android text on top and bottom. Enabling this may cause inconsistencies between platforms.

tail-color-convert

// DefaultValue: false
tail-color-convert?: boolean;

By default, if the text is truncated, the inserted ... will be displayed with the color specified by the closest inline-text's style. If this attribute is enabled, the color of ... will be specified by the outermost text tag's style.

text-single-line-vertical-align

// DefaultValue: 'normal'
text-single-line-vertical-align?: 'normal' | 'top' | 'center' | 'bottom';

Used to set vertical alignment for single-line plain text. It can be changed by setting 'top' | 'center' | 'bottom'. It is recommended to use this only when the default font does not meet the center alignment requirements, as it increases text measurement time.

text-selection


// DefaultValue: false
text-selection?: boolean;

Sets whether to enable text selection. When enabled, flatten={false} should be set simultaneously.

custom-context-menu


// DefaultValue: false
custom-context-menu?: boolean;

Used to set whether to turn on the custom pop-up context menu after selection and copying. It takes effect after enabling text-selection.

custom-text-selection


// DefaultValue: false
custom-text-selection?: boolean;

Used to set whether to enable the custom text selection function. When it is enabled, the element will no longer handle the gesture logic related to selection and copying. Developers need to control it through APIs such as setTextSelection. It takes effect after enabling text-selection.

Events

Frontend can bind corresponding event callbacks to elements to listen to runtime behaviors. All basic events are supported.

layout

bindlayout = (e: layoutEvent) => {};

interface LineInfo {
  /**
   * The starting character offset of this line relative to the entire text
   */
  start: number;
  /**
   * The ending character offset of this line relative to the entire text
   */
  end: number;
  /**
   * The number of characters truncated in this line. A non-zero value indicates truncation occurred on this line.
   */
  ellipsisCount: number;
}

interface layoutEvent extends CustomEvent {
  detail: {
    /**
     * Number of visible text lines after layout.
     */
    lineCount: number;
    /**
     * Detailed information of each line after layout.
     */
    lines: LineInfo[];
    /**
     * Dimensions of the text element.
     */
    size: { width: number; height: number };
  };
}

The layout event returns the result information after text layout, including the number of lines of the current text, and the start and end positions of the text in each line relative to the entire text.

selectionchange

bindselectionchange = (e: selectionChangeEvent) => {};

interface selectionChangeEvent extends CustomEvent {
  detail: {
    /**
     * The start index of the selected text. Value is -1 when no text is selected.
     */
    start;
    /**
     * The end index of the selected text. Value is -1 when no text is selected.
     */
    end;
    /**
     * Selection direction: `forward` or `backward`
     */
    direction;
  };
}

This event is triggered whenever the selected text range changes.

Methods

You can invoke element methods from the frontend using the SelectorQuery API.

setTextSelection

<text id="test" text-selection={true} flatten={false}></text>

lynx.createSelectorQuery()
  .select('#test')
  .invoke({
    method: "setTextSelection",
    params: {
      startX,  // X-coordinate of the selection start relative to the element
      startY,  // Y-coordinate of the selection start
      endX,    // X-coordinate of the selection end
      endY,    // Y-coordinate of the selection end
      showStartHandle, // Whether to show the start selection handle
      showEndHandle,   // Whether to show the end selection handle
    },
    success: function (res) {
      console.log(res);
    },
    fail: function (error) {
      console.log(error);
    },
}).exec();

This method sets the selected text based on start and end positions and controls the visibility of selection handles. The response res contains:

interface Rect {
  left: number;    // Left boundary
  right: number;   // Right boundary
  top: number;     // Top boundary
  bottom: number;  // Bottom boundary
  width: number;   // Width
  height: number;  // Height
}

interface Handle {
  x: number;       // Center X of handle
  y: number;       // Center Y of handle
  radius: number;  // Touch radius of the handle
}

{
  /**
   * Bounding box of the selected text
   */
  boundingRect: Rect;
  /**
   * Bounding boxes for each line within the selection
   */
  boxes: Rect[];
  /**
   * Position and touch radius for each handle
   */
  handles: Handle[];
}

getTextBoundingRect

<text id="test"></text>

lynx.createSelectorQuery()
  .select('#test')
  .invoke({
    method: "getTextBoundingRect",
    params: {
      start,
      end,
    },
    success: function (res) {
      console.log(res);
    },
    fail: function (error) {
      console.log(error);
    },
}).exec();

This method retrieves the bounding box of a specific range of text. The response res includes:

{
  /**
   * Bounding box of the selected text
   */
  boundingRect: Rect;
  /**
   * Bounding boxes for each line in the selected range
   */
  boxes: Rect[];
}

getSelectedText

<text id="test" text-selection={true} flatten={false}></text>

lynx.createSelectorQuery()
  .select('#test')
  .invoke({
    method: "getSelectedText",
    params: {},
    success: function (res) {
      console.log(res);
    },
    fail: function (error) {
      console.log(error);
    },
}).exec();

This method retrieves the string content of the currently selected text.

Loading Custom Fonts

You can specify custom font resources using @font-face and utilize them with the font-family property. The client needs to implement the corresponding font resource loader using GenericResourceFetcher to download web font resources.

Implementing Android Resource Loader

public class ExampleGenericResourceFetcher extends LynxGenericResourceFetcher {
  @Override
  public void fetchResource(LynxResourceRequest request, LynxResourceCallback<byte[]> callback) {
    ...
      //download font file through http
      byte[] data = new byte[(int) file.length()];

      //notify the font data if success
      callback.onResponse(LynxResourceResponse.onSuccess(data));

    ...
  }
}

//Inject during `LynxView` construction.
LynxViewBuilder.setGenericResourceFetcher(new ExampleGenericResourceFetcher(context));

Implementing iOS Resource Loader

@interface ExampleGenericResourceFetcher : NSObject <LynxGenericResourceFetcher>

- (void)fetchResource:(LynxResourceRequest *)request
           onComplete:(LynxGenericResourceCompletionBlock)callback;

@end

@implementation ExampleGenericResourceFetcher

NSURL *url = [NSURL URLWithString:request.url];
NSURLRequest *urlRequest = [NSURLRequest requestWithURL:url
                                             cachePolicy:NSURLRequestReloadIgnoringCacheData
                                         timeoutInterval:5];

[NSURLConnection sendAsynchronousRequest:urlRequest
                                   queue:[NSOperationQueue mainQueue]
                       completionHandler:^(NSURLResponse * _Nullable response,
                                           NSData * _Nullable data,
                                           NSError * _Nullable connectionError) {
    if (!connectionError) {
        // Notify font data
        callback(data, nil);
    } else {
        callback(data, connectionError);
    }
}];
@end

//Inject during `LynxView` construction.
LynxViewBuilder.genericResourceFetcher = [[ExampleGenericResourceFetcher alloc] init];

Big Font

Clients can use font scaling related APIs to modify the font scaling ratio after users change the system or application font size. This will also trigger the onFontScaleChanged event.

Usage

  1. Client-side: Use LynxViewBuilder.setFontScale() to set the font scale when creating the LynxView.Update the font scale with LynxView.updateFontScale().

  2. Front-end: font-size and line-height will zoom in and out according to the font scale. The front-end can obtain the updated fontScale from the client by listening to the onFontScaleChanged event.

The front-end to listen to onFontScaleChanged:

const YourComponent = () => {
  const [touchCount, setTouchCount] = useState(0);

  useEffect(() => {
    const eventEmitter = getJSModule('GlobalEventEmitter');
    const listener = (msg) => {
      console.log('onFontScaleChanged testGlobalEvent:', msg);
      setTouchCount((prevCount) => prevCount + 1);
    };

    eventEmitter.addListener('onFontScaleChanged', listener);

    return () => {
      eventEmitter.removeListener('onFontScaleChanged', listener);
    };
  }, []);

  return (
    <view>
      <text>touch: {touchCount}</text>
    </view>
  );
};

More Features

<text> selection and copying

The <text> element supports text selection and copying. You can enable this feature by setting the text-selection attribute on the <text> element, and make sure to set flatten={false} as well:

<text text-selection={true} flatten={false}>
  hello world
</text>

Long-pressing the text will highlight the selected area and trigger the default context menu, which includes options like "Select All" and "Copy".

NOTE

Text selection is currently not supported for RTL (Right-to-Left) mode.

If you want to implement a custom context menu, you need to set the custom-context-menu attribute and bind the selectionchange event along with the getTextBoundingRect method to determine the position for rendering your custom menu.

cross<text> selection and copying

For more advanced scenarios, such as supporting cross-node selection across multiple <text> elements, you need to implement custom selection logic.

Step-by-Step: Implementing Cross-Text Selection

  1. Enable Custom Text Selection Mode

Set the custom-text-selection attribute on each <text> element. This disables the built-in selection and gesture handling, allowing you to define your own.

  1. Bind Gesture Events on the Wrapper <view>

Bind long-press, tap, and touch events on the outer <view> container. These will be used to control your custom selection and gesture logic.

<view
  id="container"
  style={{ width: '90vw' }}
  className="Container"
  bindlongpress={handleLongPress}
  bindtouchstart={handleTouchStart}
  bindtouchmove={handleTouchMove}
  bindtouchend={handleTouchEnd}
  bindtap={handleTap}
>
  <text
    id="0"
    text-selection={true}
    custom-text-selection={true}
    flatten={false}
    className="Title"
  >
    This is title
  </text>
  <view className="SplitLine" />
</view>
  1. Handle Selection and Copying Logic
  • On component mount, retrieve metadata for all selectable <text> nodes, including their ID, dimensions, and positions:
const CrossTextSelection = () => {
  useEffect(() => {
    getTextNodeRect();
}, []);


// Asynchronous function to get the bounding rectangles of text nodes
async function getTextNodeRect() {
  // 1.Use lynx.createSelectorQuery () to get the required text node.
  // 2.Call boundingClientRect method of the text node to get the rect of the node.
}
  • On Long Press: Start Selection handleLongPress() is triggered on long press. It initiates the selection state, records the starting point, and performs the first selection update:
// Handle long press event to start text selection
const handleLongPress = (e) => {
  isSelecting = true;
  startPosition.x = e.detail.x;
  startPosition.y = e.detail.y;
  setSelection(e.detail.x, e.detail.y, e.detail.x, e.detail.y);
};
  • On Drag: Update Selection Area in Real Time

As the user drags, handleTouchMove() is called. If a selection is in progress, it updates the highlighted area accordingly:

// Handle touch move event to update the selection area
const handleTouchMove = (e) => {
  if (isSelecting) {
    setSelection(startPosition.x, startPosition.y, e.detail.x, e.detail.y);
  }
};
  • On Finger Release: Finalize Selection

handleTouchEnd() is triggered on finger release. This finalizes the selected region and exits selection mode:

// Handle touch end event to finalize the selection
const handleTouchEnd = (e) => {
  if (isSelecting) {
    setSelection(startPosition.x, startPosition.y, e.detail.x, e.detail.y);
  }
  isSelecting = false;
};

On Tap Blank Area: Clear Selection

Tapping outside the text elements triggers a selection clear:

// Handle tap event to clear the selection
const handleTap = (e) => {
  if (handlers.length === 0) {
    return;
  }
  setSelection(-1, -1, -1, -1);
};
  • On Handle Drag: Adjust Selection Range

In handleTouchStart(), determine if the touch point is near one of the draggable handles. If so, allow real-time adjustment of the selection:

// Handle touch start event to check if the touch is on a handler
const handleTouchStart = (e) => {
  if (handlers.length === 0) {
    return;
  }
  const { x, y } = e.detail;
  for (const [index, handler] of handlers.entries()) {
    if (
      Math.pow(handler.x - x, 2) + Math.pow(handler.y - y, 2) <
      Math.pow(handler.radius, 2)
    ) {
      isSelecting = true;
      const another = handlers[(index + 1) % 2];
      startPosition = { x: another.startX, y: another.startY };
      break;
    }
  }
};

Compatibility

<text>

LCD tables only load in the browser

Nested<text>

LCD tables only load in the browser

nested<image>

LCD tables only load in the browser

<inline-truncation>

LCD tables only load in the browser

Except as otherwise noted, this work is licensed under a Creative Commons Attribution 4.0 International License, and code samples are licensed under the Apache License 2.0.