kempo-ui 0.4.4 → 0.4.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (185) hide show
  1. package/dist/components/Chat.js +201 -0
  2. package/dist/components/CodeEditor.js +21 -2
  3. package/dist/components/Dropdown.js +1 -1
  4. package/dist/components/HtmlEditor.js +42 -3
  5. package/dist/components/MarkdownEditor.js +173 -0
  6. package/dist/components/Resize.js +12 -2
  7. package/dist/components/TextToSpeech.js +4 -4
  8. package/dist/components/VoiceSelector.js +49 -0
  9. package/dist/components/markdownEditorControls/Bold.js +11 -0
  10. package/dist/components/markdownEditorControls/BulletList.js +11 -0
  11. package/dist/components/markdownEditorControls/Code.js +11 -0
  12. package/dist/components/markdownEditorControls/FormatBlock.js +30 -0
  13. package/dist/components/markdownEditorControls/Heading.js +11 -0
  14. package/dist/components/markdownEditorControls/Image.js +93 -0
  15. package/dist/components/markdownEditorControls/Italic.js +11 -0
  16. package/dist/components/markdownEditorControls/Link.js +11 -0
  17. package/dist/components/markdownEditorControls/MarkdownEditorControl.js +39 -0
  18. package/dist/components/markdownEditorControls/Menu.js +30 -0
  19. package/dist/components/markdownEditorControls/NumberedList.js +11 -0
  20. package/dist/components/markdownEditorControls/Quote.js +11 -0
  21. package/dist/components/markdownEditorControls/SpeechToText.js +16 -0
  22. package/dist/components/markdownEditorControls/Strikethrough.js +11 -0
  23. package/dist/components/markdownEditorControls/Table.js +80 -0
  24. package/dist/utils/marked.esm.js +1 -0
  25. package/dist/utils/renderMarkdown.js +1 -0
  26. package/dist/utils/sanitizeHtml.js +1 -0
  27. package/dist/utils/voice.js +1 -0
  28. package/docs/components/accordion.html +19 -0
  29. package/docs/components/aside.html +19 -0
  30. package/docs/components/calendar.html +19 -0
  31. package/docs/components/card.html +19 -0
  32. package/docs/components/chat.html +715 -0
  33. package/docs/components/code-editor.html +55 -0
  34. package/docs/components/color-picker.html +19 -0
  35. package/docs/components/combobox.html +19 -0
  36. package/docs/components/content-slider.html +19 -0
  37. package/docs/components/context.html +19 -0
  38. package/docs/components/dialog.html +19 -0
  39. package/docs/components/dropdown.html +19 -0
  40. package/docs/components/filter-list.html +19 -0
  41. package/docs/components/focus-capture.html +19 -0
  42. package/docs/components/html-editor.html +60 -0
  43. package/docs/components/hybrid-component.html +19 -0
  44. package/docs/components/icon.html +19 -0
  45. package/docs/components/import.html +19 -0
  46. package/docs/components/light-component.html +19 -0
  47. package/docs/components/markdown-editor.html +924 -0
  48. package/docs/components/nav-spacer.html +19 -0
  49. package/docs/components/nav.html +19 -0
  50. package/docs/components/photo-viewer.html +19 -0
  51. package/docs/components/progress.html +19 -0
  52. package/docs/components/resize.html +22 -0
  53. package/docs/components/rich-textarea.html +650 -0
  54. package/docs/components/shadow-component.html +19 -0
  55. package/docs/components/show-more.html +19 -0
  56. package/docs/components/slider.html +19 -0
  57. package/docs/components/sortable.html +19 -0
  58. package/docs/components/speech-to-text.html +19 -0
  59. package/docs/components/spinner.html +19 -0
  60. package/docs/components/split.html +19 -0
  61. package/docs/components/table.html +19 -0
  62. package/docs/components/tableControls.html +19 -0
  63. package/docs/components/tableCustomFields.html +19 -0
  64. package/docs/components/tableFetchRecords.html +19 -0
  65. package/docs/components/tableFieldSortHide.html +19 -0
  66. package/docs/components/tablePagination.html +19 -0
  67. package/docs/components/tablePlaceholder.html +19 -0
  68. package/docs/components/tableRecordEditing.html +19 -0
  69. package/docs/components/tableRecordFiltering.html +19 -0
  70. package/docs/components/tableRecordHiding.html +19 -0
  71. package/docs/components/tableRecordSearching.html +19 -0
  72. package/docs/components/tableRecordSelection.html +19 -0
  73. package/docs/components/tableRowControls.html +19 -0
  74. package/docs/components/tableServerSync.html +19 -0
  75. package/docs/components/tableSorting.html +19 -0
  76. package/docs/components/tabs.html +19 -0
  77. package/docs/components/tags.html +19 -0
  78. package/docs/components/text-to-speech.html +63 -1
  79. package/docs/components/theme-select.html +19 -0
  80. package/docs/components/theme-switcher.html +19 -0
  81. package/docs/components/time.html +19 -0
  82. package/docs/components/timestamp.html +19 -0
  83. package/docs/components/toast.html +19 -0
  84. package/docs/components/toggle.html +19 -0
  85. package/docs/components/tree.html +19 -0
  86. package/docs/components/voice-selector.html +610 -0
  87. package/docs/icons/done_all.svg +1 -0
  88. package/docs/icons/format_quote.svg +1 -0
  89. package/docs/icons/format_strikethrough.svg +1 -0
  90. package/docs/icons/send.svg +1 -0
  91. package/docs/icons/text_fields.svg +1 -0
  92. package/docs/index.html +49 -0
  93. package/docs/src/components/Chat.js +201 -0
  94. package/docs/src/components/CodeEditor.js +21 -2
  95. package/docs/src/components/Dropdown.js +1 -1
  96. package/docs/src/components/HtmlEditor.js +42 -3
  97. package/docs/src/components/MarkdownEditor.js +173 -0
  98. package/docs/src/components/Resize.js +12 -2
  99. package/docs/src/components/TextToSpeech.js +4 -4
  100. package/docs/src/components/VoiceSelector.js +49 -0
  101. package/docs/src/components/markdownEditorControls/Bold.js +11 -0
  102. package/docs/src/components/markdownEditorControls/BulletList.js +11 -0
  103. package/docs/src/components/markdownEditorControls/Code.js +11 -0
  104. package/docs/src/components/markdownEditorControls/FormatBlock.js +30 -0
  105. package/docs/src/components/markdownEditorControls/Heading.js +11 -0
  106. package/docs/src/components/markdownEditorControls/Image.js +93 -0
  107. package/docs/src/components/markdownEditorControls/Italic.js +11 -0
  108. package/docs/src/components/markdownEditorControls/Link.js +11 -0
  109. package/docs/src/components/markdownEditorControls/MarkdownEditorControl.js +39 -0
  110. package/docs/src/components/markdownEditorControls/Menu.js +30 -0
  111. package/docs/src/components/markdownEditorControls/NumberedList.js +11 -0
  112. package/docs/src/components/markdownEditorControls/Quote.js +11 -0
  113. package/docs/src/components/markdownEditorControls/SpeechToText.js +16 -0
  114. package/docs/src/components/markdownEditorControls/Strikethrough.js +11 -0
  115. package/docs/src/components/markdownEditorControls/Table.js +80 -0
  116. package/docs/src/utils/marked.esm.js +1 -0
  117. package/docs/src/utils/renderMarkdown.js +1 -0
  118. package/docs/src/utils/sanitizeHtml.js +1 -0
  119. package/docs/src/utils/voice.js +1 -0
  120. package/docs/utils/context.html +19 -0
  121. package/docs/utils/cookie.html +19 -0
  122. package/docs/utils/debounce.html +19 -0
  123. package/docs/utils/drag.html +19 -0
  124. package/docs/utils/elevation.html +23 -10
  125. package/docs/utils/formatTimestamp.html +19 -0
  126. package/docs/utils/object.html +19 -0
  127. package/docs/utils/propConverters.html +19 -0
  128. package/docs/utils/sanitizeHtml.html +555 -0
  129. package/docs/utils/string.html +19 -0
  130. package/docs/utils/theme.html +19 -0
  131. package/docs/utils/toTitleCase.html +19 -0
  132. package/docs/utils/type.html +19 -0
  133. package/docs/utils/voice.html +548 -0
  134. package/docs/utils/wait.html +19 -0
  135. package/docs-src/components/chat.page.html +230 -0
  136. package/docs-src/components/code-editor.page.html +36 -0
  137. package/docs-src/components/html-editor.page.html +41 -0
  138. package/docs-src/components/markdown-editor.page.html +439 -0
  139. package/docs-src/components/resize.page.html +3 -0
  140. package/docs-src/components/text-to-speech.page.html +44 -1
  141. package/docs-src/components/voice-selector.page.html +125 -0
  142. package/docs-src/index.page.html +30 -0
  143. package/docs-src/nav.fragment.html +19 -0
  144. package/docs-src/utils/elevation.page.html +4 -10
  145. package/docs-src/utils/sanitizeHtml.page.html +73 -0
  146. package/docs-src/utils/voice.page.html +66 -0
  147. package/icons/done_all.svg +1 -0
  148. package/icons/format_quote.svg +1 -0
  149. package/icons/format_strikethrough.svg +1 -0
  150. package/icons/send.svg +1 -0
  151. package/icons/text_fields.svg +1 -0
  152. package/llms.txt +7 -2
  153. package/package.json +3 -2
  154. package/src/components/Chat.js +435 -0
  155. package/src/components/CodeEditor.js +58 -2
  156. package/src/components/Dropdown.js +1 -1
  157. package/src/components/HtmlEditor.js +84 -4
  158. package/src/components/MarkdownEditor.js +654 -0
  159. package/src/components/Resize.js +17 -1
  160. package/src/components/TextToSpeech.js +13 -2
  161. package/src/components/VoiceSelector.js +271 -0
  162. package/src/components/markdownEditorControls/Bold.js +30 -0
  163. package/src/components/markdownEditorControls/BulletList.js +30 -0
  164. package/src/components/markdownEditorControls/Code.js +39 -0
  165. package/src/components/markdownEditorControls/FormatBlock.js +102 -0
  166. package/src/components/markdownEditorControls/Heading.js +45 -0
  167. package/src/components/markdownEditorControls/Image.js +250 -0
  168. package/src/components/markdownEditorControls/Italic.js +30 -0
  169. package/src/components/markdownEditorControls/Link.js +74 -0
  170. package/src/components/markdownEditorControls/MarkdownEditorControl.js +158 -0
  171. package/src/components/markdownEditorControls/Menu.js +86 -0
  172. package/src/components/markdownEditorControls/NumberedList.js +69 -0
  173. package/src/components/markdownEditorControls/Quote.js +30 -0
  174. package/src/components/markdownEditorControls/SpeechToText.js +65 -0
  175. package/src/components/markdownEditorControls/Strikethrough.js +35 -0
  176. package/src/components/markdownEditorControls/Table.js +164 -0
  177. package/src/utils/marked.esm.js +77 -0
  178. package/src/utils/renderMarkdown.js +29 -0
  179. package/src/utils/sanitizeHtml.js +137 -0
  180. package/src/utils/voice.js +103 -0
  181. package/tests/components/Chat.browser-test.js +540 -0
  182. package/tests/components/TextToSpeech.browser-test.js +150 -1
  183. package/tests/components/VoiceSelector.browser-test.js +374 -0
  184. package/tests/utils/sanitizeHtml.browser-test.js +179 -0
  185. package/tests/utils/voice.browser-test.js +128 -0
@@ -0,0 +1,439 @@
1
+ <page pageName="Markdown Editor" title="Markdown Editor - Components - Kempo Docs - A Web Components Solution">
2
+ <content>
3
+ <k-accordion persistent-id="toc" class="b r mb">
4
+ <k-accordion-header for-panel="toc-panel">Table of Contents</k-accordion-header>
5
+ <k-accordion-panel name="toc-panel">
6
+ <div class="m pl">
7
+ <h6>Examples</h6>
8
+ <a href="#basicUsage">Basic Usage</a><br />
9
+ <a href="#withControls">With Controls</a><br />
10
+ <a href="#preConfigured">Pre-Configured Controls</a><br />
11
+ <a href="#bottomControls">Bottom Controls</a><br />
12
+ <a href="#defaultValue">Default Value</a><br />
13
+ <a href="#formUsage">Form Usage</a><br />
14
+ <a href="#disabled">Disabled / Read-only</a><br />
15
+ <a href="#tagFiltering">Tag Filtering</a><br />
16
+ <a href="#customControls">Custom Controls</a><br />
17
+
18
+ <h6 class="mt"><a href="#jsRef" class="no-link">JavaScript Reference</a></h6>
19
+ <a href="#constructor">Constructor</a><br />
20
+ <a href="#requirements">Requirements</a><br />
21
+ <a href="#properties">Properties</a><br />
22
+ <a href="#methods">Methods</a><br />
23
+ <a href="#cssVariables">CSS Variables</a><br />
24
+ <a href="#events">Events</a><br />
25
+ <a href="#controls">Built-in Controls</a><br />
26
+ </div>
27
+ </k-accordion-panel>
28
+ </k-accordion>
29
+
30
+ <p>A markdown editor with GitHub-style <strong>Write</strong> and <strong>Preview</strong> tabs. The user types markdown in the textarea and can flip to a live preview at any time. Markdown is parsed by <a href="https://marked.js.org" target="_blank">marked</a> &mdash; a full CommonMark + GFM parser &mdash; so tables, task lists, fenced code blocks, autolinks, and inline HTML all work out of the box. The rendered output runs through <a href="../utils/sanitizeHtml.html"><code>sanitizeHtml</code></a> using <code>allowed-tags</code> (an allowlist) <em>or</em> <code>disallowed-tags</code> (a denylist) &mdash; the two are mutually exclusive. <code>&lt;script&gt;</code> tags are <strong>always</strong> stripped unless you opt in via the <code>scripts-enabled</code> attribute, and <code>&lt;iframe&gt;</code> / <code>&lt;style&gt;</code> / similar dangerous tags are always stripped regardless of any setting.</p>
31
+ <p>Toolbar buttons are slotted via <code>controls-top</code> and <code>controls-bottom</code> &mdash; mix and match the built-in controls listed below or write your own by extending <a href="#customControls"><code>MarkdownEditorControl</code></a>.</p>
32
+
33
+ <h3 id="basicUsage"><a href="#basicUsage" class="no-link">Basic Usage</a></h3>
34
+ <p>Drop in the tag with a <code>placeholder</code> and you get a markdown textarea with a Write/Preview toggle. No toolbars, no controls &mdash; just the bare editor.</p>
35
+ <div class="row -mx">
36
+ <div class="col m-span-12 px">
37
+ <k-card label="HTML">
38
+ <pre><code class="hljs xml"><span class="hljs-tag">&lt;<span class="hljs-name">k-markdown-editor</span> <span class="hljs-attr">placeholder</span>=<span class="hljs-string">&quot;Type some **markdown**...&quot;</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">k-markdown-editor</span>&gt;</span></code></pre>
39
+ </k-card>
40
+ </div>
41
+ <div class="col m-span-12 px">
42
+ <k-card label="Output">
43
+ <k-markdown-editor placeholder="Type some **markdown**..."></k-markdown-editor>
44
+ </k-card>
45
+ </div>
46
+ </div>
47
+
48
+ <h3 id="withControls"><a href="#withControls" class="no-link">With Controls</a></h3>
49
+ <p>Wrap your toolbar buttons in a single <code>&lt;div slot="controls-top"&gt;</code> &mdash; that wrapper is the only element placed in the editor's <code>controls-top</code> slot, and you can lay it out however you like (the example below uses kempo-css's <code>d-f</code> for flex). Each control is its own custom element &mdash; import only the ones you need.</p>
50
+ <div class="row -mx">
51
+ <div class="col m-span-12 px">
52
+ <k-card label="HTML">
53
+ <pre><code class="hljs xml"><span class="hljs-tag">&lt;<span class="hljs-name">k-markdown-editor</span> <span class="hljs-attr">placeholder</span>=<span class="hljs-string">&quot;Write a comment...&quot;</span>&gt;</span><br> <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">slot</span>=<span class="hljs-string">&quot;controls-top&quot;</span> <span class="hljs-attr">class</span>=<span class="hljs-string">&quot;d-f&quot;</span>&gt;</span><br> <span class="hljs-tag">&lt;<span class="hljs-name">k-md-menu</span> <span class="hljs-attr">label</span>=<span class="hljs-string">&quot;Heading&quot;</span>&gt;</span><br> <span class="hljs-tag">&lt;<span class="hljs-name">k-icon</span> <span class="hljs-attr">slot</span>=<span class="hljs-string">&quot;trigger&quot;</span> <span class="hljs-attr">name</span>=<span class="hljs-string">&quot;text_fields&quot;</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">k-icon</span>&gt;</span><br> <span class="hljs-tag">&lt;<span class="hljs-name">k-md-format-block</span> <span class="hljs-attr">tag</span>=<span class="hljs-string">&quot;h1&quot;</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">k-md-format-block</span>&gt;</span><br> <span class="hljs-tag">&lt;<span class="hljs-name">k-md-format-block</span> <span class="hljs-attr">tag</span>=<span class="hljs-string">&quot;h2&quot;</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">k-md-format-block</span>&gt;</span><br> <span class="hljs-tag">&lt;<span class="hljs-name">k-md-format-block</span> <span class="hljs-attr">tag</span>=<span class="hljs-string">&quot;h3&quot;</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">k-md-format-block</span>&gt;</span><br> <span class="hljs-tag">&lt;<span class="hljs-name">k-md-format-block</span> <span class="hljs-attr">tag</span>=<span class="hljs-string">&quot;h4&quot;</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">k-md-format-block</span>&gt;</span><br> <span class="hljs-tag">&lt;<span class="hljs-name">k-md-format-block</span> <span class="hljs-attr">tag</span>=<span class="hljs-string">&quot;h5&quot;</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">k-md-format-block</span>&gt;</span><br> <span class="hljs-tag">&lt;<span class="hljs-name">k-md-format-block</span> <span class="hljs-attr">tag</span>=<span class="hljs-string">&quot;h6&quot;</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">k-md-format-block</span>&gt;</span><br> <span class="hljs-tag">&lt;/<span class="hljs-name">k-md-menu</span>&gt;</span><br> <span class="hljs-tag">&lt;<span class="hljs-name">k-md-bold</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">k-md-bold</span>&gt;</span><br> <span class="hljs-tag">&lt;<span class="hljs-name">k-md-italic</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">k-md-italic</span>&gt;</span><br> <span class="hljs-tag">&lt;<span class="hljs-name">k-md-quote</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">k-md-quote</span>&gt;</span><br> <span class="hljs-tag">&lt;<span class="hljs-name">k-md-code</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">k-md-code</span>&gt;</span><br> <span class="hljs-tag">&lt;<span class="hljs-name">k-md-link</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">k-md-link</span>&gt;</span><br> <span class="hljs-tag">&lt;<span class="hljs-name">k-md-image</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">k-md-image</span>&gt;</span><br> <span class="hljs-tag">&lt;<span class="hljs-name">k-md-table</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">k-md-table</span>&gt;</span><br> <span class="hljs-tag">&lt;<span class="hljs-name">k-md-bullet-list</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">k-md-bullet-list</span>&gt;</span><br> <span class="hljs-tag">&lt;<span class="hljs-name">k-md-numbered-list</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">k-md-numbered-list</span>&gt;</span><br> <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span><br><span class="hljs-tag">&lt;/<span class="hljs-name">k-markdown-editor</span>&gt;</span></code></pre>
54
+ </k-card>
55
+ </div>
56
+ <div class="col m-span-12 px">
57
+ <k-card label="Output">
58
+ <k-markdown-editor placeholder="Write a comment...">
59
+ <div slot="controls-top" class="d-f">
60
+ <k-md-menu label="Heading">
61
+ <k-icon slot="trigger" name="text_fields"></k-icon>
62
+ <k-md-format-block tag="h1"></k-md-format-block>
63
+ <k-md-format-block tag="h2"></k-md-format-block>
64
+ <k-md-format-block tag="h3"></k-md-format-block>
65
+ <k-md-format-block tag="h4"></k-md-format-block>
66
+ <k-md-format-block tag="h5"></k-md-format-block>
67
+ <k-md-format-block tag="h6"></k-md-format-block>
68
+ </k-md-menu>
69
+ <k-md-bold></k-md-bold>
70
+ <k-md-italic></k-md-italic>
71
+ <k-md-quote></k-md-quote>
72
+ <k-md-code></k-md-code>
73
+ <k-md-link></k-md-link>
74
+ <k-md-image></k-md-image>
75
+ <k-md-table></k-md-table>
76
+ <k-md-bullet-list></k-md-bullet-list>
77
+ <k-md-numbered-list></k-md-numbered-list>
78
+ </div>
79
+ </k-markdown-editor>
80
+ </k-card>
81
+ </div>
82
+ </div>
83
+
84
+ <h3 id="preConfigured"><a href="#preConfigured" class="no-link">Pre-Configured Controls</a></h3>
85
+ <p>The <code>controls</code> attribute lets you opt into a built-in toolbar without writing any slot markup. Set it directly on <code>&lt;k-markdown-editor&gt;</code> to one of the three levels below; the matching control modules are imported automatically.</p>
86
+
87
+ <h4>controls="full"</h4>
88
+ <p>Every available control: heading menu (H1&ndash;H6), bold, italic, strikethrough, quote, code, link, image (with dialog/dropdown), table builder, bulleted and numbered lists, speech-to-text.</p>
89
+ <div class="row -mx">
90
+ <div class="col m-span-12 px">
91
+ <k-card label="HTML">
92
+ <pre><code class="hljs xml"><span class="hljs-tag">&lt;<span class="hljs-name">k-markdown-editor</span> <span class="hljs-attr">controls</span>=<span class="hljs-string">&quot;full&quot;</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">k-markdown-editor</span>&gt;</span></code></pre>
93
+ </k-card>
94
+ </div>
95
+ <div class="col m-span-12 px">
96
+ <k-card label="Output">
97
+ <k-markdown-editor controls="full" placeholder="Type some **markdown**..."></k-markdown-editor>
98
+ </k-card>
99
+ </div>
100
+ </div>
101
+
102
+ <h4>controls="normal"</h4>
103
+ <p>A balanced everyday toolbar: heading menu, bold, italic, quote, code, link, and lists.</p>
104
+ <div class="row -mx">
105
+ <div class="col m-span-12 px">
106
+ <k-card label="HTML">
107
+ <pre><code class="hljs xml"><span class="hljs-tag">&lt;<span class="hljs-name">k-markdown-editor</span> <span class="hljs-attr">controls</span>=<span class="hljs-string">&quot;normal&quot;</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">k-markdown-editor</span>&gt;</span></code></pre>
108
+ </k-card>
109
+ </div>
110
+ <div class="col m-span-12 px">
111
+ <k-card label="Output">
112
+ <k-markdown-editor controls="normal" placeholder="Type some **markdown**..."></k-markdown-editor>
113
+ </k-card>
114
+ </div>
115
+ </div>
116
+
117
+ <h4>controls="minimal"</h4>
118
+ <p>Essential controls only: heading menu (H1, H3, H5), bold, italic, and lists. Ideal for simple comment forms.</p>
119
+ <div class="row -mx">
120
+ <div class="col m-span-12 px">
121
+ <k-card label="HTML">
122
+ <pre><code class="hljs xml"><span class="hljs-tag">&lt;<span class="hljs-name">k-markdown-editor</span> <span class="hljs-attr">controls</span>=<span class="hljs-string">&quot;minimal&quot;</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">k-markdown-editor</span>&gt;</span></code></pre>
123
+ </k-card>
124
+ </div>
125
+ <div class="col m-span-12 px">
126
+ <k-card label="Output">
127
+ <k-markdown-editor controls="minimal" placeholder="Type some **markdown**..."></k-markdown-editor>
128
+ </k-card>
129
+ </div>
130
+ </div>
131
+
132
+ <p>If you provide your own children with <code>slot="controls-top"</code> (or <code>controls-bottom</code>), they replace the corresponding fallback content from the preset &mdash; so you can layer custom controls on top of any level.</p>
133
+
134
+ <h3 id="bottomControls"><a href="#bottomControls" class="no-link">Bottom Controls</a></h3>
135
+ <p>Same pattern for the bottom: wrap your action row in a single <code>&lt;div slot="controls-bottom"&gt;</code>. The footer is hidden until something is slotted in. Mix the built-in markdown controls with your own action buttons to build a Slack/GitHub-style composer.</p>
136
+ <div class="row -mx">
137
+ <div class="col m-span-12 px">
138
+ <k-card label="HTML">
139
+ <pre><code class="hljs xml"><span class="hljs-tag">&lt;<span class="hljs-name">k-markdown-editor</span> <span class="hljs-attr">placeholder</span>=<span class="hljs-string">&quot;Write a comment...&quot;</span>&gt;</span><br> <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">slot</span>=<span class="hljs-string">&quot;controls-top&quot;</span> <span class="hljs-attr">class</span>=<span class="hljs-string">&quot;d-f&quot;</span>&gt;</span><br> <span class="hljs-tag">&lt;<span class="hljs-name">k-md-bold</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">k-md-bold</span>&gt;</span><br> <span class="hljs-tag">&lt;<span class="hljs-name">k-md-italic</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">k-md-italic</span>&gt;</span><br> <span class="hljs-tag">&lt;<span class="hljs-name">k-md-link</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">k-md-link</span>&gt;</span><br> <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span><br> <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">slot</span>=<span class="hljs-string">&quot;controls-bottom&quot;</span> <span class="hljs-attr">class</span>=<span class="hljs-string">&quot;d-f&quot;</span> <span class="hljs-attr">style</span>=<span class="hljs-string">&quot;align-items:center; width:100%;&quot;</span>&gt;</span><br> <span class="hljs-tag">&lt;<span class="hljs-name">small</span> <span class="hljs-attr">class</span>=<span class="hljs-string">&quot;tc-muted&quot;</span>&gt;</span>Markdown supported<span class="hljs-tag">&lt;/<span class="hljs-name">small</span>&gt;</span><br> <span class="hljs-tag">&lt;<span class="hljs-name">button</span> <span class="hljs-attr">style</span>=<span class="hljs-string">&quot;margin-left:auto&quot;</span>&gt;</span>Comment<span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span><br> <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span><br><span class="hljs-tag">&lt;/<span class="hljs-name">k-markdown-editor</span>&gt;</span></code></pre>
140
+ </k-card>
141
+ </div>
142
+ <div class="col m-span-12 px">
143
+ <k-card label="Output">
144
+ <k-markdown-editor placeholder="Write a comment...">
145
+ <div slot="controls-top" class="d-f">
146
+ <k-md-bold></k-md-bold>
147
+ <k-md-italic></k-md-italic>
148
+ <k-md-link></k-md-link>
149
+ </div>
150
+ <div slot="controls-bottom" class="d-f" style="align-items:center; width:100%;">
151
+ <small class="tc-muted">Markdown supported</small>
152
+ <button style="margin-left:auto">Comment</button>
153
+ </div>
154
+ </k-markdown-editor>
155
+ </k-card>
156
+ </div>
157
+ </div>
158
+
159
+ <h3 id="defaultValue"><a href="#defaultValue" class="no-link">Default Value</a></h3>
160
+ <p>Set initial markdown via the <code>value</code> attribute. Children are reserved for slotted controls (and their own content like icons or labels), so they are <strong>not</strong> read as the editor's value &mdash; otherwise icon labels, separators, etc. would leak into what the user sees.</p>
161
+ <div class="row -mx">
162
+ <div class="col m-span-12 px">
163
+ <k-card label="HTML">
164
+ <pre><code class="hljs xml"><span class="hljs-tag">&lt;<span class="hljs-name">k-markdown-editor</span> <span class="hljs-attr">value</span>=<span class="hljs-string">&quot;# Hello&amp;#10;&amp;#10;Seeded from the value attribute. Try the Preview tab.&quot;</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">k-markdown-editor</span>&gt;</span></code></pre>
165
+ </k-card>
166
+ </div>
167
+ <div class="col m-span-12 px">
168
+ <k-card label="Output">
169
+ <k-markdown-editor value="# Hello&#10;&#10;Seeded from the value attribute. Try the Preview tab."></k-markdown-editor>
170
+ </k-card>
171
+ </div>
172
+ </div>
173
+
174
+ <h3 id="formUsage"><a href="#formUsage" class="no-link">Form Usage</a></h3>
175
+ <p>The component is form-associated. Set a <code>name</code> and the raw markdown is included in the form's <code>FormData</code>. <code>required</code> participates in the browser's standard validation when the editor is empty.</p>
176
+ <div class="row -mx">
177
+ <div class="col m-span-12 px">
178
+ <k-card label="HTML">
179
+ <pre><code class="hljs xml"><span class="hljs-tag">&lt;<span class="hljs-name">form</span> <span class="hljs-attr">id</span>=<span class="hljs-string">&quot;mdForm&quot;</span>&gt;</span><br> <span class="hljs-tag">&lt;<span class="hljs-name">label</span>&gt;</span>Comment:<span class="hljs-tag">&lt;/<span class="hljs-name">label</span>&gt;</span><br> <span class="hljs-tag">&lt;<span class="hljs-name">k-markdown-editor</span> <span class="hljs-attr">name</span>=<span class="hljs-string">&quot;comment&quot;</span> <span class="hljs-attr">required</span> <span class="hljs-attr">placeholder</span>=<span class="hljs-string">&quot;Markdown supported&quot;</span>&gt;</span><br> <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">slot</span>=<span class="hljs-string">&quot;controls-top&quot;</span> <span class="hljs-attr">class</span>=<span class="hljs-string">&quot;d-f&quot;</span>&gt;</span><br> <span class="hljs-tag">&lt;<span class="hljs-name">k-md-bold</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">k-md-bold</span>&gt;</span><br> <span class="hljs-tag">&lt;<span class="hljs-name">k-md-italic</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">k-md-italic</span>&gt;</span><br> <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span><br> <span class="hljs-tag">&lt;/<span class="hljs-name">k-markdown-editor</span>&gt;</span><br> <span class="hljs-tag">&lt;<span class="hljs-name">button</span> <span class="hljs-attr">type</span>=<span class="hljs-string">&quot;submit&quot;</span>&gt;</span>Submit<span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span><br><span class="hljs-tag">&lt;/<span class="hljs-name">form</span>&gt;</span></code></pre>
180
+ </k-card>
181
+ </div>
182
+ <div class="col m-span-12 px">
183
+ <k-card label="Output">
184
+ <form id="mdForm">
185
+ <label class="d-b mb">Comment:</label>
186
+ <k-markdown-editor name="comment" required placeholder="Markdown supported">
187
+ <div slot="controls-top" class="d-f">
188
+ <k-md-bold></k-md-bold>
189
+ <k-md-italic></k-md-italic>
190
+ </div>
191
+ </k-markdown-editor>
192
+ <div class="mt">
193
+ <button type="submit">Submit</button>
194
+ </div>
195
+ </form>
196
+ <p class="mt">Form data: <span id="mdOut">(submit to see)</span></p>
197
+ <script type="module">
198
+ document.getElementById('mdForm').addEventListener('submit', (e) => {
199
+ e.preventDefault();
200
+ const fd = new FormData(e.target);
201
+ document.getElementById('mdOut').textContent = JSON.stringify([...fd.entries()]);
202
+ });
203
+ </script>
204
+ </k-card>
205
+ </div>
206
+ </div>
207
+
208
+ <h3 id="disabled"><a href="#disabled" class="no-link">Disabled / Read-only</a></h3>
209
+ <p>The <code>disabled</code> attribute prevents user interaction (including switching tabs) and excludes the field from form submission. The <code>readonly</code> attribute keeps the editor visible and lets the user switch to Preview, but the textarea cannot be edited; the value is still submitted with the form.</p>
210
+ <div class="row -mx">
211
+ <div class="col m-span-12 px">
212
+ <k-card label="Output">
213
+ <k-markdown-editor disabled value="This is **disabled**." class="mb"></k-markdown-editor>
214
+ <k-markdown-editor readonly value="This is _read-only_ &mdash; check the Preview tab."></k-markdown-editor>
215
+ </k-card>
216
+ </div>
217
+ </div>
218
+
219
+ <h3 id="tagFiltering"><a href="#tagFiltering" class="no-link">Tag Filtering</a></h3>
220
+ <p>Inline HTML the user (or a custom control) types is filtered by either an allowlist or a denylist. The two attributes are mutually exclusive &mdash; if both are set, <code>allowed-tags</code> wins and a console warning fires.</p>
221
+
222
+ <h4>allowed-tags</h4>
223
+ <p>Permits only the tags you list. Anything else is unwrapped (its text survives, the wrapper is dropped). Type the markdown below and flip to Preview &mdash; only <code>strong</code>, <code>em</code>, <code>p</code>, and <code>br</code> get through; the <code>&lt;img&gt;</code>, <code>&lt;table&gt;</code>, and <code>&lt;script&gt;</code> are scrubbed out.</p>
224
+ <div class="row -mx">
225
+ <div class="col m-span-12 px">
226
+ <k-card label="HTML">
227
+ <pre><code class="hljs xml"><span class="hljs-tag">&lt;<span class="hljs-name">k-markdown-editor</span> <span class="hljs-attr">allowed-tags</span>=<span class="hljs-string">&quot;strong,em,p,br&quot;</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">k-markdown-editor</span>&gt;</span></code></pre>
228
+ </k-card>
229
+ </div>
230
+ <div class="col m-span-12 px">
231
+ <k-card label="Output">
232
+ <k-markdown-editor
233
+ allowed-tags="strong,em,p,br"
234
+ value="**Bold** and _italic_ pass through.&#10;&#10;An image is stripped: ![alt](https://example.com/x.png)&#10;&#10;A table is stripped:&#10;&#10;| a | b |&#10;| --- | --- |&#10;| 1 | 2 |&#10;&#10;A &lt;script&gt;alert('xss')&lt;/script&gt; is always nuked."
235
+ ></k-markdown-editor>
236
+ </k-card>
237
+ </div>
238
+ </div>
239
+
240
+ <h4>disallowed-tags</h4>
241
+ <p>Allows everything <em>except</em> the tags you list &mdash; useful when you only want to ban a couple of things rather than enumerate the whole set. Below, images are blocked but everything else (tables, code, etc.) renders normally.</p>
242
+ <div class="row -mx">
243
+ <div class="col m-span-12 px">
244
+ <k-card label="HTML">
245
+ <pre><code class="hljs xml"><span class="hljs-tag">&lt;<span class="hljs-name">k-markdown-editor</span> <span class="hljs-attr">disallowed-tags</span>=<span class="hljs-string">&quot;img&quot;</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">k-markdown-editor</span>&gt;</span></code></pre>
246
+ </k-card>
247
+ </div>
248
+ <div class="col m-span-12 px">
249
+ <k-card label="Output">
250
+ <k-markdown-editor
251
+ disallowed-tags="img"
252
+ value="Tables still work:&#10;&#10;| a | b |&#10;| --- | --- |&#10;| 1 | 2 |&#10;&#10;Images do not: ![alt](https://example.com/x.png)"
253
+ ></k-markdown-editor>
254
+ </k-card>
255
+ </div>
256
+ </div>
257
+
258
+ <h3 id="customControls"><a href="#customControls" class="no-link">Custom Controls</a></h3>
259
+ <p>To write your own toolbar button, extend <code>MarkdownEditorControl</code>. Override <code>command()</code> and use the helpers (<code>wrapSelection</code>, <code>insertLinePrefix</code>, <code>replaceSelection</code>, <code>insertAtCursor</code>) to manipulate the textarea. The base class hides the control automatically when the editor flips to Preview mode.</p>
260
+ <p>Below is a tiny control that drops a horizontal rule (<code>---</code> on its own line) at the cursor &mdash; not common enough to ship as a built-in, but a clean example of using <code>insertAtCursor</code>.</p>
261
+ <k-card label="JavaScript">
262
+ <pre><code class="hljs javascript"><span class="hljs-keyword">import</span> <span class="hljs-title class_">MarkdownEditorControl</span> <span class="hljs-keyword">from</span> <span class="hljs-string">'./markdownEditorControls/MarkdownEditorControl.js'</span>;
263
+ <span class="hljs-keyword">import</span> { html } <span class="hljs-keyword">from</span> <span class="hljs-string">'./lit-all.min.js'</span>;
264
+
265
+ <span class="hljs-comment">// Inserts a horizontal rule (`---`) on its own line at the cursor.</span>
266
+ <span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">MarkdownHr</span> <span class="hljs-keyword">extends</span> <span class="hljs-title class_ inherited__">MarkdownEditorControl</span> {
267
+ <span class="hljs-title function_">constructor</span>(<span class="hljs-params"></span>) {
268
+ <span class="hljs-variable language_">super</span>();
269
+ <span class="hljs-variable language_">this</span>.<span class="hljs-property">label</span> = <span class="hljs-string">'Horizontal rule'</span>;
270
+ }
271
+ <span class="hljs-title function_">command</span>(<span class="hljs-params"></span>) {
272
+ <span class="hljs-variable language_">this</span>.<span class="hljs-title function_">insertAtCursor</span>(<span class="hljs-string">'\n\n---\n\n'</span>);
273
+ }
274
+ <span class="hljs-title function_">render</span>(<span class="hljs-params"></span>) {
275
+ <span class="hljs-keyword">return</span> html<span class="hljs-string">`
276
+ &lt;button type=&quot;button&quot; class=<span class="hljs-subst">${<span class="hljs-variable language_">this</span>.<span class="hljs-property">btnClass</span>}</span> @click=<span class="hljs-subst">${<span class="hljs-variable language_">this</span>.<span class="hljs-property">handleClick</span>}</span>&gt;&mdash;&lt;/button&gt;
277
+ `</span>;
278
+ }
279
+ }
280
+ customElements.<span class="hljs-title function_">define</span>(<span class="hljs-string">'k-md-hr'</span>, <span class="hljs-title class_">MarkdownHr</span>);</code></pre>
281
+ </k-card>
282
+
283
+ <h2 id="jsRef"><a href="#jsRef" class="no-link">JavaScript Reference</a></h2>
284
+
285
+ <h3 id="constructor"><a href="#constructor" class="no-link">Constructor</a></h3>
286
+ <h6>Extends <a href="./shadow-component.html">ShadowComponent</a></h6>
287
+ <h5><code>new MarkdownEditor()</code></h5>
288
+
289
+ <h3 id="requirements"><a href="#requirements" class="no-link">Requirements</a></h3>
290
+ <ul>
291
+ <li><a href="./shadow-component.html">ShadowComponent</a></li>
292
+ <li><code>src/utils/renderMarkdown.js</code> (thin wrapper around marked)</li>
293
+ <li><code>src/utils/marked.esm.js</code> (vendored marked, ~42KB)</li>
294
+ <li><a href="../utils/sanitizeHtml.html"><code>sanitizeHtml</code></a> utility</li>
295
+ </ul>
296
+
297
+ <h3 id="properties"><a href="#properties" class="no-link">Properties</a></h3>
298
+ <h5><code>value<i>: String</i></code></h5>
299
+ <p>The current markdown source. Reading returns whatever the user has typed; writing replaces the editor content. Initial value comes from the <code>value</code> attribute. (Children are reserved for slotted controls and are <em>not</em> read as the editor value &mdash; otherwise control content like icons / labels would leak in.)</p>
300
+
301
+ <h5><code>name<i>: String</i></code></h5>
302
+ <p>The form field name. When set inside a <code>&lt;form&gt;</code>, the component participates in form submission. The submitted value is the raw markdown string. Syncs to <code>name</code> attribute.</p>
303
+
304
+ <h5><code>placeholder<i>: String</i></code></h5>
305
+ <p>Placeholder text shown in the textarea when the editor is empty. Syncs to <code>placeholder</code> attribute.</p>
306
+
307
+ <h5><code>disabled<i>: Boolean</i></code></h5>
308
+ <p>When <code>true</code>, the editor is non-interactive (textarea is disabled, mode tabs are disabled) and excluded from form submission. Syncs to <code>disabled</code> attribute.</p>
309
+
310
+ <h5><code>required<i>: Boolean</i></code></h5>
311
+ <p>When <code>true</code> and the editor is empty, the element reports a <code>valueMissing</code> validity error. Syncs to <code>required</code> attribute.</p>
312
+
313
+ <h5><code>readonly<i>: Boolean</i></code></h5>
314
+ <p>When <code>true</code>, the user can switch to Preview but cannot edit the textarea. Form submission still includes the value. Syncs to <code>readonly</code> attribute.</p>
315
+
316
+ <h5><code>mode<i>: String</i></code></h5>
317
+ <p>Either <code>'write'</code> (default) or <code>'preview'</code>. Syncs to <code>mode</code> attribute.</p>
318
+
319
+ <h5><code>allowedTags<i>: String</i></code></h5>
320
+ <p>Comma-separated <em>allowlist</em> for the rendered preview. Tags outside the list are unwrapped (their text survives, the wrapper is dropped). Pass <code>"*"</code> to allow every tag. Empty / unset uses sanitizeHtml's default (headings, paragraphs, lists, links, code, images, tables, etc.). Mutually exclusive with <code>disallowed-tags</code> &mdash; setting both warns and <code>allowed-tags</code> wins. Syncs to <code>allowed-tags</code> attribute.</p>
321
+
322
+ <h5><code>disallowedTags<i>: String</i></code></h5>
323
+ <p>Comma-separated <em>denylist</em>. Allows everything except the listed tags &mdash; useful when you want to block a couple of tags rather than enumerate the whole allowed set. Mutually exclusive with <code>allowed-tags</code>. Syncs to <code>disallowed-tags</code> attribute.</p>
324
+
325
+ <h5><code>scriptsEnabled<i>: Boolean</i></code></h5>
326
+ <p>When this attribute is present, <code>&lt;script&gt;</code> tags survive sanitization (provided they also pass the allow/deny check). When absent (the default), <code>&lt;script&gt;</code> &mdash; including its content &mdash; is always stripped to prevent XSS, regardless of any other configuration. Other dangerous tags (<code>&lt;iframe&gt;</code>, <code>&lt;style&gt;</code>, etc.) are always stripped. Only enable this for trusted input. Syncs to <code>scripts-enabled</code> attribute.</p>
327
+
328
+ <h5><code>controls<i>: String</i></code></h5>
329
+ <p>Activates a built-in toolbar. Accepted values:</p>
330
+ <ul>
331
+ <li><code>'none'</code> / <code>''</code> (default) &mdash; no built-in toolbar; use named slots to build your own.</li>
332
+ <li><code>'minimal'</code> &mdash; heading menu (H1, H3, H5), bold, italic, link, bulleted list, numbered list.</li>
333
+ <li><code>'normal'</code> &mdash; full heading menu (H1&ndash;H6), bold, italic, quote, code, link, lists.</li>
334
+ <li><code>'full'</code> &mdash; everything from <code>normal</code> plus strikethrough, image, table builder, and speech-to-text.</li>
335
+ </ul>
336
+ <p>The chosen set renders as fallback content inside the <code>controls-top</code> slot. Providing your own slot content replaces it. Setting a non-empty value also dynamically imports every built-in control module so consumers don't have to remember which imports each preset needs.</p>
337
+
338
+ <h5><code>renderedHtml<i>: String</i></code> (getter)</h5>
339
+ <p>The current value rendered to HTML by <code>renderMarkdown</code> (marked, with GFM enabled) and then run through <code>sanitizeHtml</code> using <code>allowedTags</code>.</p>
340
+
341
+ <h5><code>isEmpty<i>: Boolean</i></code> (getter)</h5>
342
+ <p><code>true</code> when <code>value.trim()</code> is empty.</p>
343
+
344
+ <h3 id="methods"><a href="#methods" class="no-link">Methods</a></h3>
345
+ <h5><code>focus()<i>: void</i></code></h5>
346
+ <p>Switches to write mode (if necessary) and focuses the textarea.</p>
347
+
348
+ <h5><code>blur()<i>: void</i></code></h5>
349
+ <p>Removes focus from the textarea.</p>
350
+
351
+ <h5><code>clear()<i>: void</i></code></h5>
352
+ <p>Empties the editor (equivalent to <code>el.value = ''</code>).</p>
353
+
354
+ <h5><code>setMode(mode)<i>: void</i></code></h5>
355
+ <p>Sets the mode to <code>'write'</code> or <code>'preview'</code>. Other values are ignored.</p>
356
+
357
+ <h5><code>togglePreview()<i>: void</i></code></h5>
358
+ <p>Flips between <code>'write'</code> and <code>'preview'</code>.</p>
359
+
360
+ <h5><code>getSelection()<i>: { start, end, text }</i></code></h5>
361
+ <p>Returns the current textarea selection. Used by control subclasses; in preview mode returns a zero-length selection at index 0.</p>
362
+
363
+ <h5><code>replaceSelection(text, options)<i>: void</i></code></h5>
364
+ <p>Replaces the current selection with <code>text</code>. <code>options.selectInserted</code> (default <code>true</code>) controls whether the inserted text ends up selected or whether the cursor moves to the end of it.</p>
365
+
366
+ <h5><code>wrapSelection(prefix, suffix, placeholder)<i>: void</i></code></h5>
367
+ <p>Wraps the current selection with <code>prefix</code>/<code>suffix</code>. If nothing is selected, inserts <code>placeholder</code> between them and selects it so the user can immediately overtype. <code>suffix</code> defaults to <code>prefix</code>.</p>
368
+
369
+ <h5><code>insertAtCursor(text)<i>: void</i></code></h5>
370
+ <p>Inserts <code>text</code> at the cursor position; selection is collapsed to the end of the inserted text.</p>
371
+
372
+ <h5><code>insertLinePrefix(prefix)<i>: void</i></code></h5>
373
+ <p>Prefixes every selected line (or the current line if no selection) with <code>prefix</code>. Used for headings, blockquotes, list items, etc.</p>
374
+
375
+ <h3 id="cssVariables"><a href="#cssVariables" class="no-link">CSS Variables</a></h3>
376
+ <table class="b">
377
+ <thead><tr><th>Variable</th><th>Default</th><th>Description</th></tr></thead>
378
+ <tbody>
379
+ <tr><td><code>--min_height</code></td><td><code>6rem</code></td><td>Minimum height of the textarea / preview area</td></tr>
380
+ <tr><td><code>--max_height</code></td><td><code>24rem</code></td><td>Maximum height before the editor scrolls internally</td></tr>
381
+ <tr><td><code>--padding</code></td><td><code>0.5rem 0.75rem</code></td><td>Padding inside the textarea / preview</td></tr>
382
+ <tr><td><code>--border</code></td><td><code>1px solid var(--c_border)</code></td><td>Border around the editor</td></tr>
383
+ </tbody>
384
+ </table>
385
+ <p class="mt">The component also uses kempo-css's global <code>--c_border</code>, <code>--c_bg</code>, <code>--tc</code>, <code>--tc_muted</code>, <code>--radius</code>, <code>--animation_ms</code>, and <code>--focus_shadow</code>; override those globally for site-wide tuning.</p>
386
+
387
+ <h3 id="events"><a href="#events" class="no-link">Events</a></h3>
388
+ <h5><code>input</code></h5>
389
+ <p>Fires on every keystroke (or programmatic change via the selection-manipulation API). <code>detail.value</code> is the current markdown.</p>
390
+
391
+ <h5><code>change</code></h5>
392
+ <p>Fires when the textarea blurs. <code>detail.value</code> is the markdown at the moment focus left.</p>
393
+
394
+ <h5><code>mode-change</code></h5>
395
+ <p>Fires when the user switches between Write and Preview tabs. <code>detail.mode</code> is the new mode.</p>
396
+
397
+ <h3 id="controls"><a href="#controls" class="no-link">Built-in Controls</a></h3>
398
+ <p>Each control is a separate custom element under <code>src/components/markdownEditorControls/</code>. Import the ones you want and slot them into <code>controls-top</code> or <code>controls-bottom</code>.</p>
399
+ <table class="b">
400
+ <thead><tr><th>Tag</th><th>File</th><th>Inserts</th></tr></thead>
401
+ <tbody>
402
+ <tr><td><code>&lt;k-md-bold&gt;</code></td><td><code>Bold.js</code></td><td><code>**bold**</code></td></tr>
403
+ <tr><td><code>&lt;k-md-italic&gt;</code></td><td><code>Italic.js</code></td><td><code>_italic_</code></td></tr>
404
+ <tr><td><code>&lt;k-md-strikethrough&gt;</code></td><td><code>Strikethrough.js</code></td><td><code>~~strike~~</code> &mdash; GFM strikethrough; renders to <code>&lt;del&gt;</code>.</td></tr>
405
+ <tr><td><code>&lt;k-md-heading&gt;</code></td><td><code>Heading.js</code></td><td><code>## </code> on the current line; <code>level</code> attribute (1&ndash;6) selects heading depth</td></tr>
406
+ <tr><td><code>&lt;k-md-code&gt;</code></td><td><code>Code.js</code></td><td>Inline backticks for a single line, fenced <code>```</code> blocks for multi-line selections</td></tr>
407
+ <tr><td><code>&lt;k-md-link&gt;</code></td><td><code>Link.js</code></td><td><code>[text](url)</code> with the URL portion pre-selected</td></tr>
408
+ <tr><td><code>&lt;k-md-image&gt;</code></td><td><code>Image.js</code></td><td>Opens a popover (URL + alt text) and inserts <code>![alt](url)</code> at the cursor. The popover type is configurable via <code>popup="dropdown"</code> (default, anchored next to the button) or <code>popup="dialog"</code> (modal Dialog).</td></tr>
409
+ <tr><td><code>&lt;k-md-table&gt;</code></td><td><code>Table.js</code></td><td>Opens a small dropdown with <em>cols × rows</em> inputs and a Create button. Inserts a GFM markdown table (with <code>Header 1</code>, <code>Header 2</code>&hellip; placeholders and empty body cells) at the cursor.</td></tr>
410
+ <tr><td><code>&lt;k-md-menu&gt;</code></td><td><code>Menu.js</code></td><td>Renders a single toolbar button that opens a Dropdown containing whatever child controls you slot into the default slot. The trigger button content is fully customizable via the <code>trigger</code> slot (defaults to a <code>menu</code> icon); <code>label</code> sets the accessible name.</td></tr>
411
+ <tr><td><code>&lt;k-md-format-block&gt;</code></td><td><code>FormatBlock.js</code></td><td>Heading-block picker designed to go inside a <code>&lt;k-md-menu&gt;</code>. <code>tag="h1"</code>&hellip;<code>"h6"</code> sets the heading level on the current line(s), swapping out any existing heading.</td></tr>
412
+ <tr><td><code>&lt;k-md-speech-to-text&gt;</code></td><td><code>SpeechToText.js</code></td><td>Reuses the standalone <a href="./speech-to-text.html"><code>&lt;k-speech-to-text&gt;</code></a> component &mdash; click the mic to start recording, click again to stop. The final transcript is inserted at the cursor when the user finishes speaking. Forwards <code>language</code> and <code>continuous</code> attributes through.</td></tr>
413
+ <tr><td><code>&lt;k-md-bullet-list&gt;</code></td><td><code>BulletList.js</code></td><td><code>- </code> prefix on each selected line</td></tr>
414
+ <tr><td><code>&lt;k-md-numbered-list&gt;</code></td><td><code>NumberedList.js</code></td><td><code>1. </code>, <code>2. </code>, &hellip; numbering each non-empty selected line</td></tr>
415
+ <tr><td><code>&lt;k-md-quote&gt;</code></td><td><code>Quote.js</code></td><td><code>&gt; </code> prefix on each selected line</td></tr>
416
+ </tbody>
417
+ </table>
418
+ <p class="mt">All controls extend <code>MarkdownEditorControl</code> &mdash; see <a href="#customControls">Custom Controls</a> to write your own.</p>
419
+ </content>
420
+ <content location="scripts">
421
+ <script type="module" src="{{pathToRoot}}src/components/MarkdownEditor.js"></script>
422
+ <script type="module" src="{{pathToRoot}}src/components/markdownEditorControls/Bold.js"></script>
423
+ <script type="module" src="{{pathToRoot}}src/components/markdownEditorControls/Italic.js"></script>
424
+ <script type="module" src="{{pathToRoot}}src/components/markdownEditorControls/Strikethrough.js"></script>
425
+ <script type="module" src="{{pathToRoot}}src/components/markdownEditorControls/SpeechToText.js"></script>
426
+ <script type="module" src="{{pathToRoot}}src/components/markdownEditorControls/Heading.js"></script>
427
+ <script type="module" src="{{pathToRoot}}src/components/markdownEditorControls/Code.js"></script>
428
+ <script type="module" src="{{pathToRoot}}src/components/markdownEditorControls/Link.js"></script>
429
+ <script type="module" src="{{pathToRoot}}src/components/markdownEditorControls/Image.js"></script>
430
+ <script type="module" src="{{pathToRoot}}src/components/markdownEditorControls/Table.js"></script>
431
+ <script type="module" src="{{pathToRoot}}src/components/markdownEditorControls/Menu.js"></script>
432
+ <script type="module" src="{{pathToRoot}}src/components/markdownEditorControls/FormatBlock.js"></script>
433
+ <script type="module" src="{{pathToRoot}}src/components/markdownEditorControls/BulletList.js"></script>
434
+ <script type="module" src="{{pathToRoot}}src/components/markdownEditorControls/NumberedList.js"></script>
435
+ <script type="module" src="{{pathToRoot}}src/components/markdownEditorControls/Quote.js"></script>
436
+ <script type="module" src="{{pathToRoot}}src/components/Accordion.js"></script>
437
+ <script type="module" src="{{pathToRoot}}src/components/Card.js"></script>
438
+ </content>
439
+ </page>
@@ -121,6 +121,9 @@
121
121
  <h5><code>dimension<i>: string</i></code></h5>
122
122
  <p>Restricts resizing to a specific dimension. Possible values are <code>"width"</code> (horizontal only) or <code>"height"</code> (vertical only). If not specified, both dimensions can be resized. Syncs with the <code>dimension</code> attribute.</p>
123
123
 
124
+ <h5><code>disabled<i>: Boolean</i></code></h5>
125
+ <p>When <code>true</code>, the resize handles can't capture pointer events &mdash; a drag can't start, and any in-progress drag handlers no-op. Handles fade to <code>opacity: 0.4</code> as a visual cue. Syncs with the <code>disabled</code> attribute.</p>
126
+
124
127
  <h3 id="slots"><a href="#slots" class="no-link">Slots</a></h3>
125
128
  <h5><code>default</code></h5>
126
129
  <p>The main content area that will be resizable. Content automatically gets scroll behavior when it overflows the container.</p>
@@ -9,6 +9,7 @@
9
9
  <a href="#slotted">Slotted Text</a><br />
10
10
  <a href="#rate">Rate &amp; Pitch</a><br />
11
11
  <a href="#language">Language</a><br />
12
+ <a href="#voice">Voice Selection</a><br />
12
13
  <a href="#dynamic">Dynamic Text</a><br />
13
14
  <a href="#disabled">Disabled</a><br />
14
15
 
@@ -86,6 +87,48 @@
86
87
  </div>
87
88
  </div>
88
89
 
90
+ <h3 id="voice"><a href="#voice" class="no-link">Voice Selection</a></h3>
91
+ <p>Set <code>voice</code> to a specific voice name. The component looks it up in <code>speechSynthesis.getVoices()</code>; if the name isn't installed on the user's device the component silently falls back to the language default. Voice availability varies by browser and OS &mdash; here's a quick reference:</p>
92
+ <table class="b">
93
+ <thead><tr><th>Platform</th><th>Available voices</th></tr></thead>
94
+ <tbody>
95
+ <tr><td>Chrome (any OS)</td><td><code>Google US English</code>, <code>Google UK English Female</code>, <code>Google UK English Male</code>, plus localized <code>Google fran&ccedil;ais</code>, <code>Google espa&ntilde;ol</code>, etc. Most reliable cross-platform names.</td></tr>
96
+ <tr><td>macOS / iOS Safari</td><td><code>Samantha</code>, <code>Alex</code>, <code>Karen</code>, <code>Daniel</code>, <code>Moira</code>, plus any "premium" voices the user has downloaded.</td></tr>
97
+ <tr><td>Windows (Edge / non-Chrome)</td><td><code>Microsoft David</code>, <code>Microsoft Zira</code>, <code>Microsoft Mark</code>, plus regional packs.</td></tr>
98
+ <tr><td>Linux / eSpeak</td><td>A single generic voice; quality is rough.</td></tr>
99
+ <tr><td>Android Chrome</td><td>Whatever TTS engine the user has installed; defaults to Google TTS.</td></tr>
100
+ </tbody>
101
+ </table>
102
+ <p class="mt">There is no voice name that is identical across every platform. For portable code, prefer <code>language</code> over <code>voice</code> &mdash; the browser will pick the best installed voice for that language tag. Only set <code>voice</code> when you specifically want one and accept the device-dependent fallback.</p>
103
+ <div class="row -mx">
104
+ <div class="col m-span-12 px">
105
+ <k-card label="HTML">
106
+ <pre><code class="hljs xml"><span class="hljs-tag">&lt;<span class="hljs-name">k-text-to-speech</span> <span class="hljs-attr">text</span>=<span class="hljs-string">&quot;Hello, this is Google US English&quot;</span> <span class="hljs-attr">voice</span>=<span class="hljs-string">&quot;Google US English&quot;</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">k-text-to-speech</span>&gt;</span><br><span class="hljs-tag">&lt;<span class="hljs-name">k-text-to-speech</span> <span class="hljs-attr">text</span>=<span class="hljs-string">&quot;Hello in French (browser picks a voice)&quot;</span> <span class="hljs-attr">language</span>=<span class="hljs-string">&quot;fr-FR&quot;</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">k-text-to-speech</span>&gt;</span></code></pre>
107
+ </k-card>
108
+ </div>
109
+ <div class="col m-span-12 px">
110
+ <k-card label="Output">
111
+ <k-text-to-speech text="Hello, this is Google US English" voice="Google US English" class="mr"></k-text-to-speech>
112
+ <k-text-to-speech text="Hello in French (browser picks a voice)" language="fr-FR"></k-text-to-speech>
113
+ </k-card>
114
+ </div>
115
+ </div>
116
+
117
+ <h4>Voice Fallback Chain</h4>
118
+ <p>The <code>voice</code> attribute accepts a comma-separated list (like CSS <code>font-family</code>). The component tries each name in order and uses the first one installed; if none match it falls back to the language default.</p>
119
+ <div class="row -mx">
120
+ <div class="col m-span-12 px">
121
+ <k-card label="HTML">
122
+ <pre><code class="hljs xml"><span class="hljs-tag">&lt;<span class="hljs-name">k-text-to-speech</span> <span class="hljs-attr">text</span>=<span class="hljs-string">&quot;Trying Samantha first, then a Google fallback&quot;</span> <span class="hljs-attr">voice</span>=<span class="hljs-string">&quot;Samantha, Google US English, Microsoft Zira&quot;</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">k-text-to-speech</span>&gt;</span></code></pre>
123
+ </k-card>
124
+ </div>
125
+ <div class="col m-span-12 px">
126
+ <k-card label="Output">
127
+ <k-text-to-speech text="Trying Samantha first, then a Google fallback" voice="Samantha, Google US English, Microsoft Zira"></k-text-to-speech>
128
+ </k-card>
129
+ </div>
130
+ </div>
131
+
89
132
  <h3 id="dynamic"><a href="#dynamic" class="no-link">Dynamic Text</a></h3>
90
133
  <p>Set the <code>text</code> property at runtime to read whatever is in an input or other DOM source.</p>
91
134
  <div class="row -mx">
@@ -142,7 +185,7 @@
142
185
  <p>The text to speak. When empty, the element's slotted text content is used. Syncs to <code>text</code> attribute.</p>
143
186
 
144
187
  <h5><code>voice<i>: String</i></code></h5>
145
- <p>Optional voice name (matched against <code>speechSynthesis.getVoices()</code>). Falls back to the default voice for the language. Syncs to <code>voice</code> attribute.</p>
188
+ <p>Optional voice name, or a comma-separated fallback chain (e.g. <code>"Samantha, Google US English, Microsoft Zira"</code>). Each name is matched against <code>speechSynthesis.getVoices()</code> in order; the first installed match wins. If none match, the component falls back to the default voice for the configured <code>language</code>. Syncs to <code>voice</code> attribute.</p>
146
189
 
147
190
  <h5><code>language<i>: String</i></code></h5>
148
191
  <p>BCP&#x2011;47 language tag (e.g. <code>"en-US"</code>, <code>"fr-FR"</code>). Default is empty (browser default). Syncs to <code>language</code> attribute.</p>