{"id":47005044,"url":"https://github.com/densermeerkat/hyphen","last_synced_at":"2026-05-30T19:00:39.020Z","repository":{"id":343046179,"uuid":"1169410864","full_name":"DenserMeerkat/hyphen","owner":"DenserMeerkat","description":"⌨️ WYSIWYG markdown editor library for Compose Multiplatform. Live formatting, keyboard shortcuts, clipboard, and undo/redo history.","archived":false,"fork":false,"pushed_at":"2026-05-20T17:14:13.000Z","size":12767,"stargazers_count":53,"open_issues_count":2,"forks_count":1,"subscribers_count":4,"default_branch":"main","last_synced_at":"2026-05-24T09:06:04.663Z","etag":null,"topics":["android","compose-multiplatform","desktop","jetpack-compose","kmp","kotlin","kotlin-multiplatform","markdown","markdown-editor","rich-text-editor","wasmjs","wysiwyg"],"latest_commit_sha":null,"homepage":"https://densermeerkat.github.io/hyphen/","language":"Kotlin","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/DenserMeerkat.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-02-28T16:43:49.000Z","updated_at":"2026-05-17T14:11:13.000Z","dependencies_parsed_at":null,"dependency_job_id":"4dd653f4-598c-4301-9f1b-c5475ecebbcd","html_url":"https://github.com/DenserMeerkat/hyphen","commit_stats":null,"previous_names":["densermeerkat/hyphen"],"tags_count":4,"template":false,"template_full_name":null,"purl":"pkg:github/DenserMeerkat/hyphen","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/DenserMeerkat%2Fhyphen","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/DenserMeerkat%2Fhyphen/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/DenserMeerkat%2Fhyphen/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/DenserMeerkat%2Fhyphen/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/DenserMeerkat","download_url":"https://codeload.github.com/DenserMeerkat/hyphen/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/DenserMeerkat%2Fhyphen/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":33705207,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-05-30T02:00:06.278Z","response_time":92,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["android","compose-multiplatform","desktop","jetpack-compose","kmp","kotlin","kotlin-multiplatform","markdown","markdown-editor","rich-text-editor","wasmjs","wysiwyg"],"created_at":"2026-03-11T20:09:00.288Z","updated_at":"2026-05-30T19:00:39.008Z","avatar_url":"https://github.com/DenserMeerkat.png","language":"Kotlin","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Hyphen\n\n\u003cpicture\u003e\n  \u003csource srcset=\"assets/images/banner.jpg\"\u003e\n  \u003cimg alt=\"Hyphen — WYSIWYG Markdown editor for Compose Multiplatform\" src=\"docs/images/banner.jpg\"\u003e\n\u003c/picture\u003e\n\n\u003cbr\u003e\n\n\u003cp align=\"center\"\u003e\n  A \u003cstrong\u003eWYSIWYG Markdown editor\u003c/strong\u003e for Compose Multiplatform.\u003cbr\u003e\n  Type in Markdown, see formatting live. Copy as Markdown. Works on Android, Desktop, and Web.\n\u003c/p\u003e\n\n\u003cp align=\"center\"\u003e\n  \u003ca href=\"https://github.com/densermeerkat/hyphen/releases\"\u003e\u003cimg alt=\"Maven Central\" src=\"https://img.shields.io/maven-central/v/io.github.densermeerkat/hyphen?color=4CAF50\u0026label=Maven%20Central\"\u003e\u003c/a\u003e\n  \u003ca href=\"https://kotlinlang.org\"\u003e\u003cimg alt=\"Kotlin\" src=\"https://img.shields.io/badge/Kotlin-2.2.21-7F52FF?logo=kotlin\u0026logoColor=white\"\u003e\u003c/a\u003e\n  \u003ca href=\"https://www.jetbrains.com/compose-multiplatform/\"\u003e\u003cimg alt=\"Compose Multiplatform\" src=\"https://img.shields.io/badge/Compose%20Multiplatform-1.10.1-4285F4?logo=jetpackcompose\u0026logoColor=white\"\u003e\u003c/a\u003e\n  \u003cimg alt=\"Platforms\" src=\"https://img.shields.io/badge/Platforms-Android%20%7C%20Desktop%20%7C%20Web-orange\"\u003e\n\u003c/p\u003e\n\n\u003cp align=\"center\"\u003e\n  \u003ca href=\"https://densermeerkat.github.io/hyphen/\"\u003e\u003cstrong\u003e→ Try the live web demo\u003c/strong\u003e\u003c/a\u003e\n\u003c/p\u003e\n\n\u003cpicture\u003e\n  \u003csource media=\"(prefers-color-scheme: dark)\" srcset=\"assets/images/demo_dark.png\"\u003e\n  \u003csource media=\"(prefers-color-scheme: light)\" srcset=\"assets/images/demo_light.png\"\u003e\n  \u003cimg alt=\"Hyphen editor Demo screenshot\" src=\"assets/images/demo_light.png\" width=\"100%\"\u003e\n\u003c/picture\u003e\n\n## Features\n\n### ✍️ Live Markdown Input\n\nType Markdown syntax directly and watch it convert as you write — no mode switching, no preview pane required.\n\n| Syntax                  | Style                |\n|-------------------------|----------------------|\n| `**text**`              | **Bold**             |\n| `*text*`                | _Italic_             |\n| `__text__`              | Underline            |\n| `[text](url)`           | [Link](url)          |\n| `` `text` ``            | `Inline code`        |\n| `~~text~~`              | ~~Strikethrough~~    |\n| `==text==`              | Highlight            |\n| `# ` at line start      | Heading 1            |\n| `## ` at line start     | Heading 2            |\n| `### ` at line start    | Heading 3            |\n| `#### ` at line start   | Heading 4            |\n| `##### ` at line start  | Heading 5            |\n| `###### ` at line start | Heading 6            |\n| `- ` at line start      | Bullet list          |\n| `1. ` at line start     | Ordered list         |\n| `\u003e ` at line start      | Blockquote           |\n| `- [ ] ` at line start  | Checkbox (unchecked) |\n| `- [x] ` at line start  | Checkbox (checked)   |\n| `[text](scheme:id)`    | Mention              |\n\n### 📋 Markdown Clipboard\n\nCut, copy, and paste all work across Android, Desktop, and Web. Copying a selection serializes it to Markdown automatically, paste into any Markdown-aware editor and all formatting travels with it.\n\nWhen you need to write something else to the clipboard from inside the editor (e.g. a raw URL from a link context menu), use [`LocalHyphenRawClipboard`](#bypassing-markdown-clipboard-serialization) to reach the underlying system clipboard directly.\n\n### ⌨️ Keyboard Shortcuts\n\nFull hardware keyboard support on Desktop and Web:\n\n| Shortcut                 | Action                          |\n|--------------------------|---------------------------------|\n| `Ctrl / Cmd + B`         | Toggle bold                     |\n| `Ctrl / Cmd + I`         | Toggle italic                   |\n| `Ctrl / Cmd + U`         | Toggle underline                |\n| `Ctrl / Cmd + Shift + S` | Toggle strikethrough            |\n| `Ctrl / Cmd + Shift + X` | Toggle strikethrough            |\n| `Ctrl / Cmd + Alt + X`   | Toggle strikethrough            |\n| `Ctrl / Cmd + Shift + H` | Toggle highlight                |\n| `Ctrl / Cmd + Space`     | Clear all styles on selection   |\n| `Ctrl / Cmd + 1`         | Toggle Heading 1                |\n| `Ctrl / Cmd + 2`         | Toggle Heading 2                |\n| `Ctrl / Cmd + 3`         | Toggle Heading 3                |\n| `Ctrl / Cmd + 4`         | Toggle Heading 4                |\n| `Ctrl / Cmd + 5`         | Toggle Heading 5                |\n| `Ctrl / Cmd + 6`         | Toggle Heading 6                |\n| `Ctrl / Cmd + Enter`     | Toggle checkbox on current line |\n| `Ctrl / Cmd + K`         | Toggle link on selection        |\n| `Ctrl / Cmd + Z`         | Undo                            |\n| `Ctrl / Cmd + Y`         | Redo                            |\n| `Ctrl / Cmd + Shift + Z` | Redo                            |\n\n### ↩️ Undo / Redo History\n\nGranular history with snapshots saved at word boundaries, pastes, and Markdown conversions. The redo stack is maintained correctly across all operations, including toolbar toggles and programmatic edits.\n\n### 🌍 Compose Multiplatform\n\nSingle shared implementation targeting Android, Desktop (JVM), and Web (WasmJS / JS).\n\n### 🏷️ Mentions \u0026 Autocomplete\n\nPowerful trigger-based autocomplete and interaction system. Define triggers like `@` or `#`, show custom suggestion menus, and handle mention-specific hover cards or context menus.\n\n---\n\n## Installation\n\n### Using `libs.versions.toml` (recommended)\n\nAdd the version and library entry to your version catalog:\n\n**`gradle/libs.versions.toml`**\n\n```toml\n[versions]\nhyphen = \"0.5.0-alpha04\"\n\n[libraries]\nhyphen = { group = \"io.github.densermeerkat\", name = \"hyphen\", version.ref = \"hyphen\" }\n```\n\nThen reference it in your shared module:\n\n**`shared/build.gradle.kts`**\n\n```kotlin\nkotlin {\n    sourceSets {\n        commonMain.dependencies {\n            implementation(libs.hyphen)\n        }\n    }\n}\n```\n\n\u003e `commonMain` is the source set that compiles for every target at once — Android, Desktop, and Web. Declaring Hyphen there means you write the dependency once and all platforms pick it up automatically.\n\n### Using string notation\n\n```kotlin\n// shared/build.gradle.kts\nkotlin {\n    sourceSets {\n        commonMain.dependencies {\n            implementation(\"io.github.densermeerkat:hyphen:0.5.0-alpha04\")\n        }\n    }\n}\n```\n\n## Quick Start\n\n```kotlin\nval state = rememberHyphenTextState(\n    initialText = \"**Hello**, *Hyphen*!\"\n)\n\nHyphenTextField(\n    state = state,\n    label = { Text(\"Notes\") },\n)\n\n// Read the result at any time\nval markdown = state.toMarkdown()\n```\n\n---\n\n## Choosing an Editor Component\n\nHyphen ships two editor composables. Use whichever fits your design:\n\n### `HyphenBasicTextEditor`\n\nA thin wrapper around `BasicTextField` with no decoration. Use this when you control the layout yourself or want full design freedom.\n\n```kotlin\nHyphenBasicTextEditor(\n    state = state,\n    modifier = Modifier\n        .fillMaxWidth()\n        .padding(16.dp),\n    textStyle = TextStyle(\n        fontSize = 16.sp,\n        color = MaterialTheme.colorScheme.onSurface,\n    ),\n    cursorBrush = SolidColor(MaterialTheme.colorScheme.primary),\n    onMarkdownChange = { markdown -\u003e /* sync to ViewModel */ },\n)\n```\n\n### `HyphenTextField` _(Material 3)_\n\nWraps `HyphenBasicTextEditor` inside a standard Material3 filled text field decorator — labels, placeholder, leading/trailing icons, supporting text, and error state all work out of the box.\n\n```kotlin\nHyphenTextField(\n    state = state,\n    label = { Text(\"Notes\") },\n    placeholder = { Text(\"Start typing…\") },\n    trailingIcon = { Icon(Icons.Default.Edit, contentDescription = null) },\n    supportingText = { Text(\"Markdown supported\") },\n    modifier = Modifier.fillMaxWidth(),\n)\n```\n\nBoth composables accept the same `styleConfig`, `mentionConfig`, `triggerPopup`, `onTextChange`, `onMarkdownChange`, and `clipboardLabel` parameters. The Material3 variant additionally accepts `colors`, `shape`, `labelPosition`, `contentPadding`, and all standard decoration slots.\n\n---\n\n## Usage\n\n### Toolbar buttons — keeping focus on Desktop \u0026 Web\n\nOn Desktop and Web, clicking a button moves keyboard focus away from the editor. This causes the text selection to be lost before the style toggle runs. Fix this by adding `focusProperties { canFocus = false }` to every toolbar button so focus never leaves the editor when a button is tapped:\n\n```kotlin\nIconToggleButton(\n    checked = state.hasStyle(MarkupStyle.Bold),\n    onCheckedChange = { state.toggleStyle(MarkupStyle.Bold) },\n    modifier = Modifier.focusProperties { canFocus = false }, // ← required on Desktop \u0026 Web\n) {\n    Icon(Icons.Default.FormatBold, contentDescription = \"Bold\")\n}\n```\n\nThis applies to any clickable element in your toolbar — `IconButton`, `Button`, `IconToggleButton`, etc.\n\n### Custom style config\n\n```kotlin\nHyphenBasicTextEditor(\n    state = state,\n    styleConfig = HyphenStyleConfig(\n        boldStyle = SpanStyle(\n            fontWeight = FontWeight.ExtraBold,\n            color = Color(0xFF1A73E8),\n        ),\n        highlightStyle = SpanStyle(\n            background = Color(0xFFFFF176),\n        ),\n        inlineCodeStyle = SpanStyle(\n            background = Color(0xFFF1F3F4),\n            fontFamily = FontFamily.Monospace,\n            color = Color(0xFFD93025),\n        ),\n    ),\n)\n```\n\n### Programmatic control\n\n```kotlin\n// Load new Markdown content (resets undo history)\nstate.setMarkdown(\"# New content\\n\\nHello!\")\n\n// Toggle formatting from a custom button\nButton(onClick = { state.toggleStyle(MarkupStyle.Bold) }) { Text(\"B\") }\n\n// Remove all inline formatting from the current selection\nButton(onClick = { state.clearAllStyles() }) { Text(\"Clear\") }\n\n// Undo / redo\nstate.undo()\nstate.redo()\n```\n\n### Mentions \u0026 Autocomplete\n\nHyphen provides a powerful trigger-based autocomplete and interaction framework for mentions (such as @users), hashtags (such as #features), or custom template variables.\n\n#### 1. Define Triggers\n\nConfigure the triggers and the Markdown schemes they map to. Triggers are completely optional and can be defined at initialization:\n\n```kotlin\nval triggers = listOf(\n    TriggerConfig(trigger = \"@\", scheme = \"user\"),\n    TriggerConfig(trigger = \"#\", scheme = \"tag\")\n)\n\n// Triggers can be optionally passed directly to rememberHyphenTextState\nval state = rememberHyphenTextState(triggerConfigs = triggers)\n```\n\n#### 2. Configure Interactions\n\nUse `mentionConfig` (`HyphenMentionConfig`) to manage interactions with completed mentions, such as handling taps, presenting hover cards, or showing custom context Dropdown menus:\n\n```kotlin\nHyphenTextField(\n    state = state,\n    mentionConfig = HyphenMentionConfig(\n        onMentionClick = { mention -\u003e \n            println(\"Clicked mention: ${mention.display} with ID: ${mention.id}\") \n        },\n        hoverCardContent = { mention -\u003e\n            Surface(tonalElevation = 8.dp, shape = MaterialTheme.shapes.medium) {\n                Text(\"Viewing details for ${mention.display}\", Modifier.padding(8.dp))\n            }\n        },\n        dropdownContent = { span, offset, onDismiss -\u003e\n            // Display a custom context menu on right-click / long-press\n            val mention = span.style as? MarkupStyle.Mention\n            if (mention != null) {\n                DropdownMenu(expanded = true, onDismissRequest = onDismiss, offset = offset) {\n                    DropdownMenuItem(\n                        text = { Text(\"Profile\") },\n                        onClick = {\n                            println(\"Opening profile for ${mention.id}\")\n                            onDismiss()\n                        }\n                    )\n                }\n            }\n        }\n    ),\n    triggerPopup = { triggerState -\u003e\n        // Render your custom autocomplete suggestions menu\n        MySuggestionMenu(\n            query = triggerState.query,\n            onSelected = { item -\u003e \n                state.completeMention(id = item.id, display = item.display, trigger = triggerState) \n            }\n        )\n    }\n)\n```\n\n#### 3. Keyboard Navigation \u0026 Auto-Completion\n\nWhen an autocomplete trigger is active, the editor automatically intercepts and routes arrow keys (`Up`/`Down`) and the `Enter` key to coordinate suggestion selection.\n* Watch `state.suggestionSelectedIndex`, `state.suggestionCount`, and `state.suggestionSelectionRequested` to control your suggestions popup.\n* Call `state.completeMention(id, display)` to insert a completed mention, which replaces the active query and registers a `MarkupStyle.Mention` style range.\n* Use `state.insertMention(display, scheme, id)` to programmatically insert a formatted mention at the current cursor index.\n\n\u003e [!TIP]\n\u003e Hyphen includes a built-in `TriggerSuggestions` composable that handles standard Material3 list styling and keyboard selection synchronization for you out-of-the-box. Check the sample project for a full demonstration.\n\n### Reactive observation\n\n```kotlin\n// Callback — fires on every text or formatting change\nHyphenBasicTextEditor(\n    state = state,\n    onMarkdownChange = { markdown -\u003e viewModel.onContentChanged(markdown) },\n)\n\n// Flow — collect anywhere, debounce freely\nviewModelScope.launch {\n    state.markdownFlow\n        .debounce(500)\n        .collect { markdown -\u003e repository.save(markdown) }\n}\n```\n\n### Bypassing Markdown clipboard serialization\n\nHyphen intercepts every `Clipboard.setClipEntry()` call made from within the editor. When there is an active selection it replaces whatever you wrote with the Markdown-serialized form of that selection. This is the right behaviour for a user pressing **Ctrl+C**, but not for a programmatic action like \"Copy link URL\" that is triggered from a context menu while the link text happens to be selected.\n\nUse `LocalHyphenRawClipboard` to access the original, unintercepted system clipboard:\n\n```kotlin\nimport com.denser.hyphen.ui.LocalHyphenRawClipboard\n\n@Composable\nfun LinkContextMenu(url: String, onDismiss: () -\u003e Unit) {\n    // Always falls through to the real system clipboard, ignoring any editor selection\n    val rawClipboard = LocalHyphenRawClipboard.current\n    val scope = rememberCoroutineScope()\n\n    DropdownMenuItem(\n        text = { Text(\"Copy URL\") },\n        onClick = {\n            scope.launch {\n                rawClipboard?.setClipEntry(\n                    ClipEntry(ClipData.newPlainText(\"URL\", url))\n                )\n            }\n            onDismiss()\n        }\n    )\n}\n```\n\n`LocalHyphenRawClipboard.current` is `null` when the composable is rendered outside a `HyphenBasicTextEditor`. Fall back to `LocalClipboard.current` if you need to handle both cases:\n\n```kotlin\nval clipboard = LocalHyphenRawClipboard.current ?: LocalClipboard.current\n```\n\n\u003e [!IMPORTANT]\n\u003e On **Android**, `LocalHyphenRawClipboard` is **essential** for any custom clipboard write originating from inside the editor. Android has no alternative clipboard path — all `setClipEntry()` calls go through `LocalClipboard`, which Hyphen intercepts. If the editor has an active selection at the time (e.g. because a long-press on a link both selects it and opens a context menu), your custom value will be silently replaced with the Markdown-serialized selection unless you use `LocalHyphenRawClipboard`.\n\u003e\n\u003e On Desktop and Web the interception follows the same pattern but there are additional native clipboard APIs available as fallbacks.\n\n---\n\n## API Reference\n\n### `HyphenBasicTextEditor`\n\n| Parameter           | Type                        | Default                       | Description                                                                                     |\n|:--------------------|:----------------------------|:------------------------------|:------------------------------------------------------------------------------------------------|\n| `state`             | `HyphenTextState`           | **Required**                  | Holds text content, spans, selection, and undo/redo history.                                    |\n| `modifier`          | `Modifier`                  | `Modifier`                    | Applied to the underlying `BasicTextField`.                                                     |\n| `enabled`           | `Boolean`                   | `true`                        | When `false`, the field is neither editable nor focusable.                                      |\n| `readOnly`          | `Boolean`                   | `false`                       | When `true`, the field cannot be modified but can be focused and copied.                        |\n| `textStyle`         | `TextStyle`                 | `TextStyle(fontSize = 16.sp)` | Typographic style applied to the visible text.                                                  |\n| `styleConfig`       | `HyphenStyleConfig`         | `HyphenStyleConfig()`         | Visual appearance of each `MarkupStyle`.                                                        |\n| `linkConfig`        | `HyphenLinkConfig`          | `HyphenLinkConfig()`          | Interaction configuration for link spans (menus, dialogs, opening URLs).                        |\n| `mentionConfig`     | `HyphenMentionConfig`       | `HyphenMentionConfig()`       | Interaction configuration for mention spans (hover cards, dropdowns, click handlers).          |\n| `triggerPopup`      | `@Composable (TriggerState) -\u003e Unit` | `{}`                 | Composable content shown in a popup when a trigger is active.                                   |\n| `keyboardOptions`   | `KeyboardOptions`           | Sentences, no autocorrect     | Software keyboard options.                                                                      |\n| `lineLimits`        | `TextFieldLineLimits`       | `TextFieldLineLimits.Default` | Single-line or multi-line behaviour.                                                            |\n| `scrollState`       | `ScrollState`               | `rememberScrollState()`       | Controls vertical or horizontal scroll of the field content.                                    |\n| `interactionSource` | `MutableInteractionSource?` | `null`                        | Hoist to observe focus, hover, and press interactions externally.                               |\n| `cursorBrush`       | `Brush`                     | `SolidColor(Color.Black)`     | Brush used to paint the cursor.                                                                 |\n| `decorator`         | `TextFieldDecorator?`       | `null`                        | Optional decorator for external visual styling.                                                 |\n| `onTextLayout`      | `(Density.(...) -\u003e Unit)?`  | `null`                        | Callback invoked on every text layout recalculation.                                            |\n| `clipboardLabel`    | `String`                    | `\"Markdown Text\"`             | Label attached to clipboard entries on copy/cut.                                                |\n| `onTextChange`      | `((String) -\u003e Unit)?`       | `null`                        | Callback invoked whenever the plain text changes.                                               |\n| `onMarkdownChange`  | `((String) -\u003e Unit)?`       | `null`                        | Callback invoked whenever text or formatting changes, providing the serialized Markdown string. |\n\n### `HyphenTextState`\n\n| Property / Method             | Type / Return               | Description                                                                                |\n|:------------------------------|:----------------------------|:-------------------------------------------------------------------------------------------|\n| `textFieldState`              | `TextFieldState`            | The underlying Compose foundation state driving the editor.                                |\n| `text`                        | `String`                    | Plain text with all Markdown syntax stripped.                                              |\n| `selection`                   | `TextRange`                 | Current cursor position or selected range.                                                 |\n| `spans`                       | `List\u003cMarkupStyleRange\u003e`    | Snapshot-observable list of active formatting spans.                                       |\n| `pendingOverrides`            | `Map\u003cMarkupStyle, Boolean\u003e` | Transient style intent applied to the next typed characters.                               |\n| `canUndo` / `canRedo`         | `Boolean`                   | `true` if undo/redo actions are available in the history stack.                            |\n| `activeLinkForEditing`        | `MarkupStyleRange?`         | The link span currently being edited via the built-in dialog.                              |\n| `isFocused`                   | `Boolean`                   | Whether the text field currently has input focus.                                          |\n| `triggerConfigs`              | `List\u003cTriggerConfig\u003e`       | Active autocomplete trigger configurations (e.g. `@` or `#`).                              |\n| `activeTrigger`               | `TriggerState?`             | The current active trigger being typed by the user, if any.                                |\n| `suggestionSelectedIndex`     | `Int`                       | The index of the currently highlighted suggestion in the autocomplete menu.                |\n| `suggestionCount`             | `Int`                       | Total number of suggestions currently available (must be set by the custom menu).          |\n| `suggestionSelectionRequested`| `Boolean`                   | Set to `true` when Enter is pressed on an active trigger to request selection completion.  |\n| `toggleStyle(style)`          | `Unit`                      | Toggles an inline or block style on the current selection.                                 |\n| `toggleCheckbox(index?)`      | `Unit`                      | Toggles the checked/unchecked state of checkboxes in the selection or at a specific index. |\n| `clearAllStyles()`            | `Unit`                      | Removes all inline formatting from the selection; suppresses at cursor.                    |\n| `toggleLink()`                | `Unit`                      | Wraps selection in a link, or opens an existing link at the cursor for editing.            |\n| `updateLink(span, text, url)` | `Unit`                      | Updates an existing link's display text and URL.                                           |\n| `completeMention(id, display, triggerEnd?, trigger?)` | `Unit`       | Completes the active trigger, replacing the query with a formatted mention span.           |\n| `dismissActiveTrigger()`      | `Unit`                      | Clears the currently active trigger state, dismissing suggestions popups.                  |\n| `activateTrigger(config, query)` | `Unit`                   | Programmatically activates an autocomplete trigger at the current cursor position.          |\n| `insertText(value)`           | `Unit`                      | Programmatically inserts text at the current cursor, running full Markdown/trigger checks.  |\n| `insertMention(display, scheme, id)` | `Unit`              | Programmatically inserts a formatted mention at the current cursor position.               |\n| `hasStyle(style)`             | `Boolean`                   | `true` if the style is active at the current selection or cursor.                          |\n| `isStyleAt(index, style)`     | `Boolean`                   | Point query against the span list (ignores selection / overrides).                         |\n| `clearPendingOverrides()`     | `Unit`                      | Resets transient typing intent.                                                            |\n| `undo()` / `redo()`           | `Unit`                      | Navigates the undo / redo history stack.                                                   |\n| `toMarkdown(start?, end?)`    | `String`                    | Serializes content (or a substring range) to a Markdown formatted string.                  |\n| `setMarkdown(markdown)`       | `Unit`                      | Replaces all content programmatically, parses it, and resets history.                      |\n| `markdownFlow`                | `Flow\u003cString\u003e`              | Emits the serialized Markdown string on every text or formatting change.                   |\n\n### `HyphenStyleConfig`\n\n| Property                 | Default Value                                                                                      |\n|--------------------------|----------------------------------------------------------------------------------------------------|\n| `boldStyle`              | `SpanStyle(fontWeight = FontWeight.Bold)`                                                          |\n| `italicStyle`            | `SpanStyle(fontStyle = FontStyle.Italic)`                                                          |\n| `underlineStyle`         | `SpanStyle(textDecoration = TextDecoration.Underline)`                                             |\n| `strikethroughStyle`     | `SpanStyle(textDecoration = TextDecoration.LineThrough)`                                           |\n| `highlightStyle`         | `SpanStyle(background = Color(0xFFFFEB3B).copy(alpha = 0.4f))`                                     |\n| `inlineCodeStyle`        | `SpanStyle(background = Color.Gray.copy(alpha = 0.15f), fontFamily = FontFamily.Monospace)`        |\n| `blockquoteSpanStyle`    | `SpanStyle(fontStyle = FontStyle.Italic, color = Color.Gray, background = Color.Gray.copy(0.05f))` |\n| `bulletListStyle`        | `ListItemStyle()`                                                                                  |\n| `orderedListStyle`       | `ListItemStyle()`                                                                                  |\n| `checkboxCheckedStyle`   | `SpanStyle(textDecoration = TextDecoration.LineThrough)`                                           |\n| `checkboxUncheckedStyle` | `null`                                                                                             |\n| `h1Style`                | `SpanStyle(fontSize = 24.sp, fontWeight = FontWeight.Bold)`                                        |\n| `h2Style`                | `SpanStyle(fontSize = 22.sp, fontWeight = FontWeight.Bold)`                                        |\n| `h3Style`                | `SpanStyle(fontSize = 20.sp, fontWeight = FontWeight.Bold)`                                        |\n| `h4Style`                | `SpanStyle(fontSize = 18.sp, fontWeight = FontWeight.Bold)`                                        |\n| `h5Style`                | `SpanStyle(fontSize = 17.sp, fontWeight = FontWeight.Bold)`                                        |\n| `h6Style`                | `SpanStyle(fontSize = 16.sp, fontWeight = FontWeight.Bold)`                                        |\n| `linkStyle`              | `SpanStyle(color = Color.Blue, textDecoration = TextDecoration.Underline)`                         |\n| `mentionStyle`           | `SpanStyle(color = Color(0xFF1976D2), fontWeight = FontWeight.Medium)`                             |\n| `mentionStyles`          | `emptyMap\u003cString, SpanStyle\u003e()`                                                                    |\n\n### `HyphenMentionConfig`\n\nInteraction configuration for mention spans, controlling what happens when they are clicked, hovered, or right-clicked/long-pressed.\n\n| Property | Type | Default | Description |\n|:---|:---|:---|:---|\n| `onMentionClick` | `(MarkupStyle.Mention) -\u003e Unit` | `{}` | Callback invoked when a mention span is clicked or tapped. |\n| `hoverCardContent` | `@Composable (MarkupStyle.Mention) -\u003e Unit` | `{}` | Composable content displayed in a popup hover card when hovered. |\n| `dropdownContent` | `@Composable (span: MarkupStyleRange, menuOffset: Offset, onDismiss: () -\u003e Unit) -\u003e Unit` | `null` | Optional context DropdownMenu composable displayed on right-click or long-press. |\n\n### `TriggerSuggestions`\n\nBuilt-in Material 3 helper popup for displaying autocomplete items and managing selection index synchronization.\n\n```kotlin\nTriggerSuggestions(\n    state = state,\n    trigger = triggerState,\n    items = listOf(\n        SuggestionItem(id = \"alice\", display = \"Alice\"),\n        SuggestionItem(id = \"bob\", display = \"Bob\")\n    ),\n    onSelect = { item -\u003e\n        state.completeMention(id = item.id, display = item.display, trigger = triggerState)\n    }\n)\n```\n\n#### `SuggestionItem`\n\n| Property | Type | Default | Description |\n|:---|:---|:---|:---|\n| `id` | `String` | **Required** | The unique identifier of the target entity. |\n| `display` | `String` | **Required** | The text displayed in the suggestion list and inserted into the editor. |\n| `subtitle` | `String?` | `null` | Optional descriptive label displayed below the main text. |\n| `icon` | `@Composable (() -\u003e Unit)?` | `null` | Optional leading composable slot (e.g. user avatar or icon). |\n\n### `MarkupStyle`\n\n```kotlin\n// Inline styles\nMarkupStyle.Bold\nMarkupStyle.Italic\nMarkupStyle.Underline\nMarkupStyle.Strikethrough\nMarkupStyle.InlineCode\nMarkupStyle.Highlight\n\n// Heading styles\nMarkupStyle.H1\nMarkupStyle.H2\nMarkupStyle.H3\nMarkupStyle.H4\nMarkupStyle.H5\nMarkupStyle.H6\n\n// Block styles\nMarkupStyle.BulletList\nMarkupStyle.OrderedList\nMarkupStyle.Blockquote\nMarkupStyle.CheckboxUnchecked\nMarkupStyle.CheckboxChecked\n\n// Mention style\nMarkupStyle.Mention(display, scheme, id)\n```\n\n### `ListItemStyle`\n\nControls the prefix marker and content text of a list item independently. Used by `bulletListStyle`\nand `orderedListStyle` on `HyphenStyleConfig`.\n\n| Property       | Type         | Default | Description                                          |\n|----------------|--------------|---------|------------------------------------------------------|\n| `prefixStyle`  | `SpanStyle?` | `null`  | Applied to the marker (`-`, `1.`, `- [ ]`, `- [x]`). |\n| `contentStyle` | `SpanStyle?` | `null`  | Applied to the text after the marker.                |\n\n### Checklist Styling\n\nCheckboxes in Hyphen are rendered as native Material3 widgets overlaid on the editor. This ensures\nthey always match your theme and remain perfectly aligned regardless of font size. Use\n`checkboxCheckedStyle` and `checkboxUncheckedStyle` to style the **label text** of the checklist\nitems:\n\n```kotlin\nHyphenBasicTextEditor(\n    state = state,\n    styleConfig = HyphenStyleConfig(\n        checkboxCheckedStyle = SpanStyle(\n            textDecoration = TextDecoration.LineThrough,\n            color = Color.Gray,\n        ),\n    ),\n)\n```\n\n---\n\n## Supported Platforms\n\n| Platform      | Status     |\n|---------------|------------|\n| Android       | ✅          |\n| Desktop (JVM) | ✅          |\n| Web (WasmJS)  | ✅          |\n| Web (JS / IR) | ✅          |\n| iOS           | 🚧 Planned |\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdensermeerkat%2Fhyphen","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdensermeerkat%2Fhyphen","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdensermeerkat%2Fhyphen/lists"}