Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
68 changes: 68 additions & 0 deletions docs/api/config/trigger-template.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
---
sidebar_label: triggerTemplate
title: triggerTemplate Config
description: You can learn about the triggerTemplate config in the documentation of the DHTMLX JavaScript RichText library. Browse developer guides and API reference, try out code examples and live demos, and download a free 30-day evaluation version of DHTMLX RichText.
---

# triggerTemplate

### Description

@short: Optional. Customizes how RichText renders items in the suggestion dropdown opened by a [`triggers`](api/config/triggers.md) entry

By default, the dropdown shows each item's `label` as plain text. Use `triggerTemplate` to render richer rows — for example, an avatar plus a name and an email.

### Usage

~~~jsx {}
function triggerTemplate({ data, trigger }) {
return "HTML template of the suggestion item";
};
~~~

### Parameters

The callback function takes an object with the following parameters:

- `data` - the current suggestion item (`{ id, label, url }`, plus any custom fields you add to the trigger's `data` source)
- `trigger` - the trigger character that opened the dropdown (`"@"`, `"#"`, etc.)

:::tip
The dropdown default width is `160px`. If you need more space for your template, add the `.wx-editor` parent in front of the selector:

~~~css {}
.wx-editor .wx-suggest-anchor {
width: 220px;
}
~~~
:::

### Example

The following code snippet configures two triggers: `@` for mentions and `#` for tags. Use `triggerTemplate` to expand the `trigger` value to render each dropdown differently. For the `@` dropdown the template returns a custom HTML row with an avatar (`data.image`), a nickname (`data.label`), and a full name (`data.name`). For the `#` trigger the template use the `label`:

~~~jsx {5-6,8-15}
const { template, Richtext } = richtext;

new Richtext("#root", {
triggers: [
{ trigger: "@", data: people },
{ trigger: "#", data: tags }
],
triggerTemplate: template(obj => {
if (obj.trigger === "@") {
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe add one more trigger, because otherwise there is no point in checking for equality to a "@"

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done. Added a second # trigger so the obj.trigger === "@" branch is actually meaningful, with the # dropdown falling back to the plain label.

return `<div class="user">
<img class="user-avatar" src="${obj.data.image}">
<div class="user-nickname">${obj.data.label}</div>
<div class="user-name">${obj.data.name}</div>
</div>`;
}
// other triggers (for example, "#") use the plain label
return obj.data.label;
})
});
~~~

**Change log:** The property was added in v2.1

**Related articles:** [Mentions and tags](guides/mentions_and_tags.md)
241 changes: 241 additions & 0 deletions docs/api/config/triggers.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,241 @@
---
sidebar_label: triggers
title: triggers Config
description: You can learn about the triggers config in the documentation of the DHTMLX JavaScript RichText library. Browse developer guides and API reference, try out code examples and live demos, and download a free 30-day evaluation version of DHTMLX RichText.
---

# triggers

### Description

@short: Optional. Defines dropdown triggers for inserting mentions, tags, and other tokens

When a user types a configured character (for example, `@` or `#`), RichText opens a dropdown with predefined items. When the user selects an item, RichText inserts it into the document as a non-editable token (`<a data-token="..." data-token-id="...">`).

### Usage

~~~jsx {}
triggers?: Array<{
trigger: string,
data: Array<{ id?: string; label?: string; url?: string }>
| ((query: string) =>
Array<{ id?: string; label?: string; url?: string }>
| Promise<Array<{ id?: string; label?: string; url?: string }>>),
showTrigger?: boolean,
action?: (item) => void
}>;
~~~

### Parameters

Each entry of the `triggers` array accepts the following fields:

- `trigger` - (required) the character that opens the suggestion dropdown (for example, `"@"`, `"#"`, `"/"`, `"$"`)
- `data` - (required) the data source for the dropdown; can be an array, a sync function, or an async function. See [Data source forms](#data-source-forms)
- `showTrigger` - (optional) when `true` (default), RichText keeps the trigger character in the inserted token (for example, `@Alice`); when `false`, RichText inserts only `label` (for example, `Alice`)
- `action` - (optional) a custom callback called when a user selects an item. When set, RichText removes the typed trigger text (the trigger character plus the query) and calls `action(item)` **instead of** inserting a token. The callback receives the picked item and can insert any content instead of selected one. The `action` parameter takes priority over `showTrigger`, which has no effect when `action` is set. See [Custom action](#custom-action)

### Data source forms

* **Static array** — RichText filters the array automatically by matching the query against `label` (case-insensitive, `startsWith`):

~~~jsx {3-7}
new richtext.Richtext("#root", {
triggers: [{
trigger: "@",
data: [
{ id: "alice", label: "Alice" },
{ id: "bob", label: "Bob" }
]
}]
});
~~~

* **Sync function** — RichText calls your function with the current `query` string; you do the filtering and return the matching array:

~~~jsx {3-6}
new richtext.Richtext("#root", {
triggers: [{
trigger: "#",
data: query => tags.filter(t =>
t.label.toLowerCase().startsWith(query.toLowerCase())
)
}]
});
~~~

* **Async function** — RichText calls your function with the current `query` string; return a `Promise` that resolves to the matching array. Useful for server-side search:

~~~jsx {3-8}
new richtext.Richtext("#root", {
triggers: [{
trigger: "+",
data: async query => {
const res = await fetch(`/api/users?q=${encodeURIComponent(query)}`);
const users = await res.json();
return users.map(u => ({ id: String(u.id), label: u.name, url: u.website }));
}
}]
});
~~~

### Suggestion item fields

Each item in `data` (or each item returned by a function) has the following fields:

- `id` - (optional) unique identifier saved on the inserted token. If omitted, RichText generates an ID automatically
- `label` - (optional) the text shown in the dropdown and inserted into the document. Required only for the default rendering; with a custom [`triggerTemplate`](api/config/trigger-template.md) you can render items from other fields (for example, `template(({ data }) => data.id)`) and omit `label`
- `url` - (optional) URL associated with the item. RichText stores the URL as the inserted token's `href` attribute. `Ctrl+Click` on the token opens the link

An item may also include any number of custom fields beyond `id`, `label`, and `url` (for example, `code` for an emoji, or `image` and `name` for an avatar). These extra fields are passed through to the [`triggerTemplate`](api/config/trigger-template.md) callback and to the `action` callback.

### Rendered token

When a user selects an item in the dropdown, RichText inserts a non-editable token element into the document:

~~~html {}
<a data-token="@" data-token-id="alice" href="mailto:alice@example.com">@Alice</a>
Copy link
Copy Markdown

@DmitriySlabodchikov DmitriySlabodchikov Jun 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Where:

  • @ is trigger
  • alice is id
  • mailto:alice@example.com is url
  • @Alice - combination of trigger and label, in case of showTrigger: false it would be just Alice

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done. Added a Where: breakdown under the token: @trigger (data-token), aliceid (data-token-id), mailto:alice@example.comurl (href), and @Alicetrigger + label (just Alice when showTrigger: false).

~~~

- `@` (in `data-token="@"`) - the item's `trigger`
- `alice` (in `data-token-id="alice"`) - the item's `id`
- `mailto:alice@example.com` (in `href="mailto:alice@example.com"`) - the item's `url`
- `@Alice` - the combination of `trigger` and `label`; with `showTrigger: false` it would be just `Alice`

Use the `data-token` and `data-token-id` attributes to target tokens with CSS, for example, to highlight all mentions of a user:

~~~css {}
.wx-editor-content a[data-token="@"][data-token-id="alice"] {
background: #fb8500;
color: #fff;
}
~~~

### Custom action

By default, when a user picks an item, RichText inserts the item into the document as a token. Set the `action` parameter to run your code instead: RichText removes the typed trigger string (the trigger character and the query) and calls the `action(item)` callback with the picked item. No token is inserted, so you can decide what to add to the document (or run your custom code). The `action` parameter takes priority over `showTrigger`. When `action` is set, `showTrigger` is ignored.

#### Add emoji

A common use case is inserting an emoji from a `:` trigger, where each item contains a custom `code` field. Pair `action` with [`triggerTemplate`](api/config/trigger-template.md) so the dropdown shows the emoji itself instead of just its label:
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe it's better to use this snippet code with categories (it is also a good example of custom data filtering): https://snippet.dhtmlx.com/r16rmt4m

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done. Thanks for the snippet — added a new "Group emoji by category" section based on it: category headers (items with no code), custom data filtering that keeps only categories with matches, a triggerTemplate that renders headers in bold, and an api.intercept("insert-token", ...) guard so headers cannot be inserted. Kept the simpler "Add emoji" example above it as an intro.


~~~jsx {8,12}
const { template, Richtext } = richtext;

const editor = new Richtext("#root", {
triggers: [
{
trigger: ":",
data: emoji, // [{ id: "apple", label: "apple", code: "1F34E" }, ...]
action: item => editor.insertValue(`<span>${emojiFromCode(item.code)} </span>`)
}
],
// render the emoji itself (not just its label) in the dropdown
triggerTemplate: template(({ data }) => `${emojiFromCode(data.code)} ${data.label}`)
});

function emojiFromCode(code) {
return String.fromCodePoint(parseInt(code, 16));
}
~~~

#### Group emoji by categories

When the `data` parameter is a function, you are not limited to the built-in `label` matching. You can run your own filtering and keep category headers in the dropdown. Add header items that include a `label` field and do not include `code`. The `data` function first finds the emoji that match the query, then returns emoji together with the headers of the categories that still have matches:

~~~jsx {18-26,31-33,41}
const { template, Richtext } = richtext;

// header items carry no `code` field; emoji items include one
const emoji = [
{ id: "$smileys", label: "Smileys", category: 1 }, // category
{ id: "grinning", label: "grinning", code: "1F600", category: 1 },
{ id: "smile", label: "smile", code: "1F604", category: 1 },
{ id: "$animals", label: "Animals", category: 2 }, // category
{ id: "dog", label: "dog", code: "1F436", category: 2 },
{ id: "cat", label: "cat", code: "1F431", category: 2 }
];

const editor = new Richtext("#root", {
triggers: [
{
trigger: ":",
data: query => {
const matched = emoji.filter(item =>
item.code &&
item.label.toLowerCase().startsWith(query.toLowerCase().trim())
);
const categories = new Set(matched.map(item => item.category));
// keep matching emoji plus the headers of categories that still match
return emoji.filter(item =>
item.code ? matched.includes(item) : categories.has(item.category)
);
},
action: item => editor.insertValue(`<span>${emojiFromCode(item.code)} </span>`)
}
],
// render emoji rows normally and category headers in bold
triggerTemplate: template(({ data }) =>
data.code ? `${emojiFromCode(data.code)} ${data.label}` : `<b>${data.label}</b>`
)
});

function emojiFromCode(code) {
return String.fromCodePoint(parseInt(code, 16));
}

// headers have no `code` — ignore picks on them so they are never inserted
editor.api.intercept("insert-token", ({ data }) => !!data.code);
~~~

#### Add slash-style command menu

You can use `action` to build a slash-style command menu (like `/` in Notion or Slack). Store a command name in each item's `id`, its options in a custom `config` field, and let the callback run it with [`api.exec`](api/internal/exec.md):

~~~jsx {13}
// each item stores an api.exec action name in `id` and its parameters in `config`
const commands = [
{ id: "set-text-style", label: "Heading 1", config: { tag: "h1" } },
{ id: "insert-list", label: "Bulleted list", config: { type: "bulleted" } },
{ id: "insert-line", label: "Divider" } // no config → `|| {}` applies
];

const editor = new richtext.Richtext("#root", {
triggers: [
{
trigger: "/",
data: commands,
action: item => editor.api.exec(item.id, item.config || {})
}
]
});
~~~

### Example

The following example sets up two triggers: `@` for mentions (each item carries a `url` that becomes the token's `href`) and `#` for tags (label only):

~~~jsx {4,11}
new richtext.Richtext("#root", {
triggers: [
{
trigger: "@",
data: [
{ id: "alice", label: "Alice", url: "mailto:alice@example.com" },
Copy link
Copy Markdown

@DmitriySlabodchikov DmitriySlabodchikov Jun 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

url: "mailto:alice@example.com"

so ctrl-click automatically launches your default email client (like Gmail, Outlook, or Apple Mail) and prefills a new email draft with the recipient's address

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done. Added a note under the example: because the @ items use a mailto: value, Ctrl+Click on the token launches the default email client (Gmail, Outlook, Apple Mail, …) and opens a new message pre-filled with the recipient address.

{ id: "bob", label: "Bob", url: "mailto:bob@example.com" }
]
},
{
trigger: "#",
data: [
{ id: "css", label: "CSS" },
{ id: "html", label: "HTML" }
]
}
]
});
~~~

**Change log:** The property was added in v2.1

**Related articles:** [Mentions and tags](guides/mentions_and_tags.md)
47 changes: 47 additions & 0 deletions docs/api/events/hide-suggest.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
---
sidebar_label: hide-suggest
title: hide-suggest Event
description: You can learn about the hide-suggest event in the documentation of the DHTMLX JavaScript RichText library. Browse developer guides and API reference, try out code examples and live demos, and download a free 30-day evaluation version of DHTMLX RichText.
---

# hide-suggest

### Description

@short: Fires when the suggestion dropdown closes

The event fires when any of these happen:

- the user picks an item from the dropdown
- the user presses `Escape`
- the cursor leaves the trigger context (for example, on `Backspace` past the trigger character)
- the current query returns no matches

### Usage

~~~jsx {}
"hide-suggest": () => boolean | void;
~~~

### Parameters

The `hide-suggest` event callback does not receive any parameters.

:::info
To handle internal events, use [**Event Bus methods**](api/overview/event_bus_methods_overview.md).
:::

### Example

~~~jsx {5-7}
// initialize RichText
const editor = new richtext.Richtext("#root", {
// configuration properties
});
// subscribe to the "hide-suggest" event
editor.api.on("hide-suggest", () => {
console.log("Suggestion dropdown closed");
});
~~~

**Change log:** The event was added in v2.1
Loading