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,125 @@
1
+ <page pageName="Voice Selector" title="Voice Selector - 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="#languageFilter">Language Filter</a><br />
10
+ <a href="#withTextToSpeech">With TextToSpeech</a><br />
11
+ <a href="#disabled">Disabled</a><br />
12
+
13
+ <h6 class="mt"><a href="#jsRef" class="no-link">JavaScript Reference</a></h6>
14
+ <a href="#constructor">Constructor</a><br />
15
+ <a href="#requirements">Requirements</a><br />
16
+ <a href="#properties">Properties</a><br />
17
+ <a href="#methods">Methods</a><br />
18
+ <a href="#events">Events</a><br />
19
+ </div>
20
+ </k-accordion-panel>
21
+ </k-accordion>
22
+
23
+ <p>Two cascading <code>&lt;select&gt;</code>s &mdash; the first picks a language, the second lists voices for that language. The chosen voice is persisted via the <a href="../utils/voice.html">voice utility</a>, so every <a href="./text-to-speech.html"><code>&lt;k-text-to-speech&gt;</code></a> on the page (and across reloads) uses it as a default when no <code>voice</code> attribute is set.</p>
24
+ <p>The language dropdown defaults to the browser's primary language (<code>navigator.language</code>) so a user almost always sees a relevant voice list immediately.</p>
25
+
26
+ <h3 id="basicUsage"><a href="#basicUsage" class="no-link">Basic Usage</a></h3>
27
+ <p>Just drop the element into a settings page or somewhere visible. Voices load asynchronously, so the dropdowns may briefly be empty before the rest populate.</p>
28
+ <div class="row -mx">
29
+ <div class="col m-span-12 px">
30
+ <k-card label="HTML">
31
+ <pre><code class="hljs xml"><span class="hljs-tag">&lt;<span class="hljs-name">k-voice-selector</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">k-voice-selector</span>&gt;</span></code></pre>
32
+ </k-card>
33
+ </div>
34
+ <div class="col m-span-12 px">
35
+ <k-card label="Output">
36
+ <k-voice-selector></k-voice-selector>
37
+ </k-card>
38
+ </div>
39
+ </div>
40
+
41
+ <h3 id="languageFilter"><a href="#languageFilter" class="no-link">Initial Language</a></h3>
42
+ <p>Set <code>language</code> to override the default browser language. Accepts a primary language code (e.g. <code>"fr"</code>, <code>"es"</code>) or a full BCP&#x2011;47 tag (the primary part is used). The user can still switch via the dropdown afterward.</p>
43
+ <div class="row -mx">
44
+ <div class="col m-span-12 px">
45
+ <k-card label="HTML">
46
+ <pre><code class="hljs xml"><span class="hljs-tag">&lt;<span class="hljs-name">k-voice-selector</span> <span class="hljs-attr">language</span>=<span class="hljs-string">&quot;fr&quot;</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">k-voice-selector</span>&gt;</span></code></pre>
47
+ </k-card>
48
+ </div>
49
+ <div class="col m-span-12 px">
50
+ <k-card label="Output">
51
+ <k-voice-selector language="fr"></k-voice-selector>
52
+ </k-card>
53
+ </div>
54
+ </div>
55
+
56
+ <h3 id="withTextToSpeech"><a href="#withTextToSpeech" class="no-link">With TextToSpeech</a></h3>
57
+ <p>Pick a voice in the dropdown, then click any <code>&lt;k-text-to-speech&gt;</code> on the page that doesn't have its own <code>voice</code> attribute &mdash; it'll use your saved preference. The choice persists across page reloads.</p>
58
+ <div class="row -mx">
59
+ <div class="col m-span-12 px">
60
+ <k-card label="HTML">
61
+ <pre><code class="hljs xml"><span class="hljs-tag">&lt;<span class="hljs-name">k-voice-selector</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">k-voice-selector</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;This will use the voice you picked above&quot;</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">k-text-to-speech</span>&gt;</span></code></pre>
62
+ </k-card>
63
+ </div>
64
+ <div class="col m-span-12 px">
65
+ <k-card label="Output">
66
+ <k-voice-selector class="mr"></k-voice-selector>
67
+ <k-text-to-speech text="This will use the voice you picked above"></k-text-to-speech>
68
+ </k-card>
69
+ </div>
70
+ </div>
71
+
72
+ <h3 id="disabled"><a href="#disabled" class="no-link">Disabled</a></h3>
73
+ <p>The <code>disabled</code> attribute prevents user interaction. The dropdown also auto-disables when the browser doesn't support the SpeechSynthesis API.</p>
74
+ <div class="row -mx">
75
+ <div class="col m-span-12 px">
76
+ <k-card label="HTML">
77
+ <pre><code class="hljs xml"><span class="hljs-tag">&lt;<span class="hljs-name">k-voice-selector</span> <span class="hljs-attr">disabled</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">k-voice-selector</span>&gt;</span></code></pre>
78
+ </k-card>
79
+ </div>
80
+ <div class="col m-span-12 px">
81
+ <k-card label="Output">
82
+ <k-voice-selector disabled></k-voice-selector>
83
+ </k-card>
84
+ </div>
85
+ </div>
86
+
87
+ <h2 id="jsRef"><a href="#jsRef" class="no-link">JavaScript Reference</a></h2>
88
+
89
+ <h3 id="constructor"><a href="#constructor" class="no-link">Constructor</a></h3>
90
+ <h6>Extends <a href="./shadow-component.html">ShadowComponent</a></h6>
91
+ <h5><code>new VoiceSelector()</code></h5>
92
+
93
+ <h3 id="requirements"><a href="#requirements" class="no-link">Requirements</a></h3>
94
+ <ul>
95
+ <li><a href="./shadow-component.html">ShadowComponent</a></li>
96
+ <li>The <a href="../utils/voice.html">voice utility</a></li>
97
+ <li>A browser with <code>window.speechSynthesis</code></li>
98
+ </ul>
99
+
100
+ <h3 id="properties"><a href="#properties" class="no-link">Properties</a></h3>
101
+ <h5><code>language<i>: String</i></code></h5>
102
+ <p>Optional initial language. If set, overrides the default <code>navigator.language</code> on first render. Accepts a primary language code (<code>"en"</code>, <code>"es"</code>) or a full BCP&#x2011;47 tag (the primary part is used). The user can change the language via the first dropdown afterward. Syncs to <code>language</code> attribute.</p>
103
+
104
+ <h5><code>placeholder<i>: String</i></code></h5>
105
+ <p>Label for the empty-selection option. Default is <code>"Browser default"</code>. Syncs to <code>placeholder</code> attribute.</p>
106
+
107
+ <h5><code>disabled<i>: Boolean</i></code></h5>
108
+ <p>When <code>true</code>, the dropdown cannot be changed. Syncs to <code>disabled</code> attribute.</p>
109
+
110
+ <h3 id="methods"><a href="#methods" class="no-link">Methods</a></h3>
111
+ <h5><code>refresh()<i>: void</i></code></h5>
112
+ <p>Force a re-read of <code>speechSynthesis.getVoices()</code>. Rarely needed since the component subscribes to the <code>voiceschanged</code> event, but useful if you populate voices dynamically.</p>
113
+
114
+ <h3 id="events"><a href="#events" class="no-link">Events</a></h3>
115
+ <h5><code>change</code></h5>
116
+ <p>Fires when the user picks a new voice. <code>detail</code> contains <code>{ voice: String }</code> &mdash; the voice name, or empty string for "browser default". The voice utility is updated automatically; this event is for callers that want to react in addition.</p>
117
+ </content>
118
+ <content location="scripts">
119
+ <script type="module" src="{{pathToRoot}}src/components/VoiceSelector.js"></script>
120
+ <script type="module" src="{{pathToRoot}}src/components/TextToSpeech.js"></script>
121
+ <script type="module" src="{{pathToRoot}}src/components/Icon.js"></script>
122
+ <script type="module" src="{{pathToRoot}}src/components/Accordion.js"></script>
123
+ <script type="module" src="{{pathToRoot}}src/components/Card.js"></script>
124
+ </content>
125
+ </page>
@@ -59,6 +59,12 @@
59
59
  <p class="tc-muted">Container component with optional labels and border styling.</p>
60
60
  </a>
61
61
  </div>
62
+ <div class="span-12 t-span-6 d-span-4 px">
63
+ <a href="{{pathToRoot}}components/chat.html" class="card mb no-link d-b">
64
+ <h3 class="tc-primary">Chat</h3>
65
+ <p class="tc-muted">Chat-room widget with rich-text input, send-on-enter option, and per-message state tracking.</p>
66
+ </a>
67
+ </div>
62
68
  <div class="span-12 t-span-6 d-span-4 px">
63
69
  <a href="{{pathToRoot}}components/code-editor.html" class="card mb no-link d-b">
64
70
  <h3 class="tc-primary">Code Editor</h3>
@@ -119,6 +125,12 @@
119
125
  <p class="tc-muted">WYSIWYG editor with formatting tools and dual editing modes.</p>
120
126
  </a>
121
127
  </div>
128
+ <div class="span-12 t-span-6 d-span-4 px">
129
+ <a href="{{pathToRoot}}components/markdown-editor.html" class="card mb no-link d-b">
130
+ <h3 class="tc-primary">Markdown Editor</h3>
131
+ <p class="tc-muted">Markdown editor with Write/Preview tabs, slot-based toolbars, and a tiny safe parser (no inline HTML).</p>
132
+ </a>
133
+ </div>
122
134
  <div class="span-12 t-span-6 d-span-4 px">
123
135
  <a href="{{pathToRoot}}components/icon.html" class="card mb no-link d-b">
124
136
  <h3 class="tc-primary">Icon</h3>
@@ -263,6 +275,12 @@
263
275
  <p class="tc-muted">Hierarchical data visualization with collapsible branches and custom leaf types.</p>
264
276
  </a>
265
277
  </div>
278
+ <div class="span-12 t-span-6 d-span-4 px">
279
+ <a href="{{pathToRoot}}components/voice-selector.html" class="card mb no-link d-b">
280
+ <h3 class="tc-primary">Voice Selector</h3>
281
+ <p class="tc-muted">Dropdown of available speech-synthesis voices, persisted via the voice utility for site-wide use.</p>
282
+ </a>
283
+ </div>
266
284
  </div>
267
285
 
268
286
  <h2>Base Components</h2>
@@ -325,6 +343,12 @@
325
343
  <p class="tc-muted">Type converters for Lit component properties and attributes.</p>
326
344
  </a>
327
345
  </div>
346
+ <div class="span-12 t-span-6 d-span-4 px">
347
+ <a href="{{pathToRoot}}utils/sanitizeHtml.html" class="card mb no-link d-b">
348
+ <h3 class="tc-primary">sanitizeHtml</h3>
349
+ <p class="tc-muted">Whitelist HTML sanitizer for user-submitted rich text. Strips unsafe tags, attributes, and URL schemes.</p>
350
+ </a>
351
+ </div>
328
352
  <div class="span-12 t-span-6 d-span-4 px">
329
353
  <a href="{{pathToRoot}}utils/string.html" class="card mb no-link d-b">
330
354
  <h3 class="tc-primary">string</h3>
@@ -349,6 +373,12 @@
349
373
  <p class="tc-muted">Functions for manipulating and analyzing JavaScript objects.</p>
350
374
  </a>
351
375
  </div>
376
+ <div class="span-12 t-span-6 d-span-4 px">
377
+ <a href="{{pathToRoot}}utils/voice.html" class="card mb no-link d-b">
378
+ <h3 class="tc-primary">voice</h3>
379
+ <p class="tc-muted">Persisted speech-synthesis voice preference with pub/sub, mirroring the theme utility.</p>
380
+ </a>
381
+ </div>
352
382
  <div class="span-12 t-span-6 d-span-4 px">
353
383
  <a href="{{pathToRoot}}utils/wait.html" class="card mb no-link d-b">
354
384
  <h3 class="tc-primary">wait</h3>
@@ -51,6 +51,9 @@
51
51
  >Calendar<br><small>Component</small></a></k-filter-item>
52
52
  <k-filter-item filter-keywords="card components"><a
53
53
  href="{{pathToRoot}}components/card.html">Card<br><small>Component</small></a></k-filter-item>
54
+ <k-filter-item filter-keywords="chat message conversation messaging components"><a
55
+ href="{{pathToRoot}}components/chat.html"
56
+ >Chat<br><small>Component</small></a></k-filter-item>
54
57
  <k-filter-item filter-keywords="code editor codeeditor monaco components"><a
55
58
  href="{{pathToRoot}}components/code-editor.html"
56
59
  >Code Editor<br><small>Component</small></a></k-filter-item>
@@ -79,6 +82,9 @@
79
82
  <k-filter-item filter-keywords="html editor components"><a
80
83
  href="{{pathToRoot}}components/html-editor.html">HTML
81
84
  Editor<br><small>Component</small></a></k-filter-item>
85
+ <k-filter-item filter-keywords="markdown editor markdowneditor write preview marked gfm tables components"><a
86
+ href="{{pathToRoot}}components/markdown-editor.html"
87
+ >Markdown Editor<br><small>Component</small></a></k-filter-item>
82
88
  <k-filter-item filter-keywords="icon components"><a
83
89
  href="{{pathToRoot}}components/icon.html">Icon<br><small>Component</small></a></k-filter-item>
84
90
  <k-filter-item filter-keywords="import components"><a
@@ -182,6 +188,9 @@
182
188
  >Toggle<br><small>Component</small></a></k-filter-item>
183
189
  <k-filter-item filter-keywords="tree treeview components"><a
184
190
  href="{{pathToRoot}}components/tree.html">Tree<br><small>Component</small></a></k-filter-item>
191
+ <k-filter-item filter-keywords="voice selector dropdown speech synthesis preference components"><a
192
+ href="{{pathToRoot}}components/voice-selector.html"
193
+ >Voice Selector<br><small>Component</small></a></k-filter-item>
185
194
  <k-filter-item filter-keywords="shadow component base"><a
186
195
  href="{{pathToRoot}}components/shadow-component.html"
187
196
  >Shadow Component<br><small>Base Component</small></a></k-filter-item>
@@ -209,12 +218,17 @@
209
218
  <k-filter-item filter-keywords="propconverters prop converters utils utility"><a
210
219
  href="{{pathToRoot}}utils/propConverters.html"
211
220
  >propConverters<br><small>Utility</small></a></k-filter-item>
221
+ <k-filter-item filter-keywords="sanitizehtml sanitize html xss security utils utility"><a
222
+ href="{{pathToRoot}}utils/sanitizeHtml.html"
223
+ >sanitizeHtml<br><small>Utility</small></a></k-filter-item>
212
224
  <k-filter-item filter-keywords="string utils utility"><a
213
225
  href="{{pathToRoot}}utils/string.html">string<br><small>Utility</small></a></k-filter-item>
214
226
  <k-filter-item filter-keywords="theme utils utility"><a
215
227
  href="{{pathToRoot}}utils/theme.html">theme<br><small>Utility</small></a></k-filter-item>
216
228
  <k-filter-item filter-keywords="type utils utility"><a
217
229
  href="{{pathToRoot}}utils/type.html">type<br><small>Utility</small></a></k-filter-item>
230
+ <k-filter-item filter-keywords="voice speech synthesis preference utils utility"><a
231
+ href="{{pathToRoot}}utils/voice.html">voice<br><small>Utility</small></a></k-filter-item>
218
232
  <k-filter-item filter-keywords="wait async utils utility"><a
219
233
  href="{{pathToRoot}}utils/wait.html">wait<br><small>Utility</small></a></k-filter-item>
220
234
  </k-filter-list>
@@ -267,6 +281,7 @@
267
281
  <a href="{{pathToRoot}}components/aside.html">Aside</a>
268
282
  <a href="{{pathToRoot}}components/calendar.html">Calendar</a>
269
283
  <a href="{{pathToRoot}}components/card.html">Card</a>
284
+ <a href="{{pathToRoot}}components/chat.html">Chat</a>
270
285
  <a href="{{pathToRoot}}components/code-editor.html">Code Editor</a>
271
286
  <a href="{{pathToRoot}}components/combobox.html">Combobox</a>
272
287
  <a href="{{pathToRoot}}components/color-picker.html">ColorPicker</a>
@@ -277,6 +292,7 @@
277
292
  <a href="{{pathToRoot}}components/filter-list.html">Filter List</a>
278
293
  <a href="{{pathToRoot}}components/focus-capture.html">FocusCapture</a>
279
294
  <a href="{{pathToRoot}}components/html-editor.html">HTML Editor</a>
295
+ <a href="{{pathToRoot}}components/markdown-editor.html">Markdown Editor</a>
280
296
  <a href="{{pathToRoot}}components/icon.html">Icon</a>
281
297
  <a href="{{pathToRoot}}components/import.html">Import</a>
282
298
  <a href="{{pathToRoot}}components/nav.html">Nav</a>
@@ -301,6 +317,7 @@
301
317
  <a href="{{pathToRoot}}components/toast.html">Toast</a>
302
318
  <a href="{{pathToRoot}}components/toggle.html">Toggle</a>
303
319
  <a href="{{pathToRoot}}components/tree.html">Tree</a>
320
+ <a href="{{pathToRoot}}components/voice-selector.html">Voice Selector</a>
304
321
  </div>
305
322
 
306
323
  <h5 class="mb0">Base Components</h5>
@@ -320,9 +337,11 @@
320
337
  <a href="{{pathToRoot}}utils/formatTimestamp.html">formatTimestamp</a>
321
338
  <a href="{{pathToRoot}}utils/object.html">object</a>
322
339
  <a href="{{pathToRoot}}utils/propConverters.html">propConverters</a>
340
+ <a href="{{pathToRoot}}utils/sanitizeHtml.html">sanitizeHtml</a>
323
341
  <a href="{{pathToRoot}}utils/string.html">string</a>
324
342
  <a href="{{pathToRoot}}utils/theme.html">theme</a>
325
343
  <a href="{{pathToRoot}}utils/type.html">type</a>
344
+ <a href="{{pathToRoot}}utils/voice.html">voice</a>
326
345
  <a href="{{pathToRoot}}utils/wait.html">wait</a>
327
346
  </div>
328
347
  </menu>
@@ -50,11 +50,11 @@
50
50
  <tr><td>0</td><td>0</td><td>Behind the page — sunken or background elements</td></tr>
51
51
  <tr><td>1</td><td>10</td><td><em>Buffer zone — reserved for customization</em></td></tr>
52
52
  <tr><td>2</td><td>20</td><td>Page default — normal document flow</td></tr>
53
- <tr><td>3</td><td>30</td><td>Fixed panels that sit below a navbar (push drawers)</td></tr>
53
+ <tr><td>3</td><td>30</td><td>Fixed panels that sit below a navbar (push drawers, dropdowns)</td></tr>
54
54
  <tr><td>4</td><td>40</td><td><em>Buffer zone — reserved for customization</em></td></tr>
55
55
  <tr><td>5</td><td>50</td><td>Fixed navbar (user-defined)</td></tr>
56
56
  <tr><td>6</td><td>60</td><td>Overlay drawers — above a fixed navbar</td></tr>
57
- <tr><td>7</td><td>70</td><td>Floating menus (dropdowns, popovers)</td></tr>
57
+ <tr><td>7</td><td>70</td><td><em>Buffer zone reserved for customization</em></td></tr>
58
58
  <tr><td>8</td><td>80</td><td>Full-screen modals and lightboxes</td></tr>
59
59
  <tr><td>9</td><td>90</td><td>Notification toasts</td></tr>
60
60
  <tr><td>10</td><td>100</td><td><em>Reserved — use only when absolutely necessary</em></td></tr>
@@ -79,7 +79,8 @@
79
79
  <td>30</td>
80
80
  <td>
81
81
  <a href="{{pathToRoot}}components/aside.html">Aside</a> (<code>main="push"</code>),
82
- <a href="{{pathToRoot}}components/side-panel.html">Side Panel</a>
82
+ <a href="{{pathToRoot}}components/side-panel.html">Side Panel</a>,
83
+ <a href="{{pathToRoot}}components/dropdown.html">Dropdown</a>
83
84
  </td>
84
85
  </tr>
85
86
  <tr>
@@ -89,13 +90,6 @@
89
90
  <a href="{{pathToRoot}}components/aside.html">Aside</a> (<code>main="overlay"</code>)
90
91
  </td>
91
92
  </tr>
92
- <tr>
93
- <td>7</td>
94
- <td>70</td>
95
- <td>
96
- <a href="{{pathToRoot}}components/dropdown.html">Dropdown</a>
97
- </td>
98
- </tr>
99
93
  <tr>
100
94
  <td>8</td>
101
95
  <td>80</td>
@@ -0,0 +1,73 @@
1
+ <page pageName="sanitizeHtml Utility" title="sanitizeHtml Utility - Kempo UI">
2
+ <content location="header"></content>
3
+ <content>
4
+ <h1>sanitizeHtml Utility</h1>
5
+
6
+ <k-accordion persistent-id="toc" class="b r mb">
7
+ <k-accordion-header for-panel="toc-panel">Table of Contents</k-accordion-header>
8
+ <k-accordion-panel name="toc-panel">
9
+ <div class="m">
10
+ <h6>Usage</h6>
11
+ <a href="#description">Description</a><br />
12
+ <a href="#basicUsage">Basic Usage</a><br />
13
+ <a href="#api">API</a><br />
14
+ <a href="#defaults">Defaults</a><br />
15
+ <a href="#customizing">Customizing the Whitelist</a><br />
16
+ <a href="#serverNote">Server-Side Caveat</a><br />
17
+ </div>
18
+ </k-accordion-panel>
19
+ </k-accordion>
20
+
21
+ <h3 id="description"><a href="#description" class="no-link">Description</a></h3>
22
+ <p>A small whitelist-based HTML sanitizer for user-submitted rich text. Strips <code>&lt;script&gt;</code>, <code>&lt;style&gt;</code>, <code>&lt;iframe&gt;</code>, event-handler attributes, and unsafe URL schemes (<code>javascript:</code>, <code>data:</code>, etc.). Used by <a href="../components/chat.html"><code>&lt;k-chat&gt;</code></a> on every message it accepts, but works as a standalone util for any place you accept HTML from a contenteditable or rich-text editor.</p>
23
+
24
+ <div class="callout warning b r p mb">
25
+ <strong>This is a frontend defense, not a substitute for server sanitization.</strong>
26
+ Anything you persist or rebroadcast must be sanitized again on the server &mdash; an attacker can submit raw HTTP without ever loading your client code.
27
+ </div>
28
+
29
+ <h3 id="basicUsage"><a href="#basicUsage" class="no-link">Basic Usage</a></h3>
30
+ <pre><code class="hljs javascript"><span class="hljs-keyword">import</span> sanitizeHtml <span class="hljs-keyword">from</span> <span class="hljs-string">&#x27;./utils/sanitizeHtml.js&#x27;</span>;<br><span class="hljs-keyword">const</span> dirty = <span class="hljs-string">&#x27;&lt;p&gt;Hello &lt;script&gt;alert(&quot;xss&quot;)&lt;/script&gt;&lt;b&gt;world&lt;/b&gt;&lt;/p&gt;&#x27;</span>;<br><span class="hljs-keyword">const</span> clean = <span class="hljs-title function_">sanitizeHtml</span>(dirty);<br><span class="hljs-comment">// → &#x27;&lt;p&gt;Hello &lt;b&gt;world&lt;/b&gt;&lt;/p&gt;&#x27;</span></code></pre>
31
+
32
+ <h3 id="api"><a href="#api" class="no-link">API</a></h3>
33
+
34
+ <h4><code>sanitizeHtml(html, options?)</code></h4>
35
+ <h5><code>html<i>: string</i></code></h5>
36
+ <p>Untrusted HTML to sanitize. <code>null</code>, <code>undefined</code>, and non-strings are coerced to <code>""</code>.</p>
37
+ <h5><code>options<i>: Object</i></code> (optional)</h5>
38
+ <ul>
39
+ <li><code>allowedTags</code> &mdash; <code>Set&lt;string&gt;</code> of UPPERCASE tag names (e.g. <code>"B"</code>, <code>"P"</code>). Overrides the default whitelist.</li>
40
+ <li><code>allowedAttrs</code> &mdash; <code>Set&lt;string&gt;</code> of attribute names allowed on every tag.</li>
41
+ <li><code>allowedAttrsPerTag</code> &mdash; <code>{ TAGNAME: Set&lt;string&gt; }</code> map of attributes allowed per specific tag.</li>
42
+ </ul>
43
+ <h5>Returns: <code>string</code></h5>
44
+ <p>The sanitized HTML. Disallowed elements are replaced by their (sanitized) children so user text is preserved; <code>&lt;script&gt;</code>, <code>&lt;style&gt;</code>, and similar are removed entirely (children too).</p>
45
+
46
+ <h3 id="defaults"><a href="#defaults" class="no-link">Defaults</a></h3>
47
+
48
+ <h5>Allowed tags (default)</h5>
49
+ <p><code>A</code>, <code>B</code>, <code>STRONG</code>, <code>I</code>, <code>EM</code>, <code>U</code>, <code>S</code>, <code>STRIKE</code>, <code>BR</code>, <code>P</code>, <code>DIV</code>, <code>SPAN</code>, <code>UL</code>, <code>OL</code>, <code>LI</code>, <code>BLOCKQUOTE</code>, <code>CODE</code>, <code>PRE</code>, <code>H1</code>&ndash;<code>H6</code></p>
50
+
51
+ <h5>Tags removed completely (children too)</h5>
52
+ <p><code>SCRIPT</code>, <code>STYLE</code>, <code>IFRAME</code>, <code>OBJECT</code>, <code>EMBED</code>, <code>LINK</code>, <code>META</code>, <code>BASE</code>, <code>FRAME</code>, <code>FRAMESET</code>, <code>NOSCRIPT</code>, <code>SVG</code>, <code>MATH</code></p>
53
+
54
+ <h5>Allowed attributes (default)</h5>
55
+ <ul>
56
+ <li>Globally: <code>class</code>, plus any <code>data-*</code></li>
57
+ <li>On <code>&lt;a&gt;</code>: <code>href</code>, <code>title</code>, <code>target</code>, <code>rel</code></li>
58
+ </ul>
59
+
60
+ <h5>URL handling</h5>
61
+ <p><code>href</code> and <code>src</code> values are checked against a safe-scheme regex; anything that doesn't match <code>http(s):</code>, <code>mailto:</code>, <code>tel:</code>, a relative path, or a fragment is removed. Links with <code>target="_blank"</code> automatically get <code>rel="noopener noreferrer"</code> to prevent reverse-tabnabbing.</p>
62
+
63
+ <h3 id="customizing"><a href="#customizing" class="no-link">Customizing the Whitelist</a></h3>
64
+ <p>Pass an options object to tighten or loosen the defaults:</p>
65
+ <pre><code class="hljs javascript"><span class="hljs-keyword">const</span> html = <span class="hljs-title function_">sanitizeHtml</span>(userInput, {<br> <span class="hljs-attr">allowedTags</span>: <span class="hljs-keyword">new</span> <span class="hljs-title class_">Set</span>([<span class="hljs-string">&#x27;B&#x27;</span>, <span class="hljs-string">&#x27;I&#x27;</span>, <span class="hljs-string">&#x27;EM&#x27;</span>, <span class="hljs-string">&#x27;STRONG&#x27;</span>, <span class="hljs-string">&#x27;A&#x27;</span>, <span class="hljs-string">&#x27;BR&#x27;</span>, <span class="hljs-string">&#x27;P&#x27;</span>]),<br> <span class="hljs-attr">allowedAttrsPerTag</span>: {<br> <span class="hljs-attr">A</span>: <span class="hljs-keyword">new</span> <span class="hljs-title class_">Set</span>([<span class="hljs-string">&#x27;href&#x27;</span>])<br> }<br>});</code></pre>
66
+
67
+ <h3 id="serverNote"><a href="#serverNote" class="no-link">Server-Side Caveat</a></h3>
68
+ <p>This util only protects content that flows through your client code. If your app accepts user-submitted HTML and stores or echoes it to other users, the server <strong>must</strong> independently sanitize before storing and again before sending it back. Battle-tested server libraries: <a href="https://github.com/cure53/DOMPurify" target="_blank" rel="noopener noreferrer">DOMPurify</a> (Node), <a href="https://github.com/apostrophecms/sanitize-html" target="_blank" rel="noopener noreferrer">sanitize-html</a> (Node), <a href="https://github.com/mozilla/bleach" target="_blank" rel="noopener noreferrer">Bleach</a> (Python), <a href="https://github.com/rgrove/sanitize" target="_blank" rel="noopener noreferrer">sanitize</a> (Ruby).</p>
69
+ </content>
70
+ <content location="scripts">
71
+ <script type="module" src="{{pathToRoot}}src/components/Accordion.js"></script>
72
+ </content>
73
+ </page>
@@ -0,0 +1,66 @@
1
+ <page pageName="Voice Utility" title="Voice Utility - Kempo UI">
2
+ <content location="header"></content>
3
+ <content>
4
+ <h1>Voice Utility</h1>
5
+
6
+ <k-accordion persistent-id="toc" class="b r mb">
7
+ <k-accordion-header for-panel="toc-panel">Table of Contents</k-accordion-header>
8
+ <k-accordion-panel name="toc-panel">
9
+ <div class="m">
10
+ <h6>Usage</h6>
11
+ <a href="#basicUsage">Basic Usage</a><br />
12
+ <a href="#api">API Methods</a><br />
13
+ <a href="#examples">Examples</a><br />
14
+ </div>
15
+ </k-accordion-panel>
16
+ </k-accordion>
17
+
18
+ <h3>Description</h3>
19
+ <p>The <code>voice</code> utility manages the user's preferred speech-synthesis voice across the site. It mirrors the <a href="./theme.html">theme</a> utility &mdash; persisting to <code>localStorage</code>, syncing across tabs via the <code>storage</code> event, and exposing a pub/sub API. Pair it with <a href="../components/voice-selector.html"><code>&lt;k-voice-selector&gt;</code></a> for a settings UI; <a href="../components/text-to-speech.html"><code>&lt;k-text-to-speech&gt;</code></a> automatically uses the saved preference when no <code>voice</code> attribute is set.</p>
20
+
21
+ <h3 id="basicUsage"><a href="#basicUsage" class="no-link">Basic Usage</a></h3>
22
+ <pre><code class="hljs javascript"><span class="hljs-keyword">import</span> voice <span class="hljs-keyword">from</span> <span class="hljs-string">&#x27;./utils/voice.js&#x27;</span>;<br><span class="hljs-comment">// Get current preferred voice (empty string = use browser default)</span><br><span class="hljs-keyword">const</span> current = voice.<span class="hljs-title function_">get</span>();<br><span class="hljs-comment">// Set a preferred voice</span><br>voice.<span class="hljs-title function_">set</span>(<span class="hljs-string">&#x27;Google US English&#x27;</span>);<br><span class="hljs-comment">// Subscribe to changes</span><br><span class="hljs-keyword">const</span> unsubscribe = voice.<span class="hljs-title function_">subscribe</span>(<span class="hljs-function">(<span class="hljs-params">name</span>) =&gt;</span> {<br> <span class="hljs-variable language_">console</span>.<span class="hljs-title function_">log</span>(<span class="hljs-string">&#x27;Voice preference changed to:&#x27;</span>, name);<br>});<br><span class="hljs-comment">// Enumerate available voices (may need to wait — the list loads async)</span><br><span class="hljs-keyword">const</span> voices = <span class="hljs-keyword">await</span> voice.<span class="hljs-title function_">waitForVoices</span>();</code></pre>
23
+
24
+ <h3 id="api"><a href="#api" class="no-link">API Methods</a></h3>
25
+
26
+ <h4><code>voice.get()</code></h4>
27
+ <p>Returns the user's saved voice name. An empty string means "no preference &mdash; use the browser default for the language".</p>
28
+ <h5>Returns: <code>string</code></h5>
29
+
30
+ <h4><code>voice.set(name)</code></h4>
31
+ <p>Sets the saved voice and persists it to <code>localStorage</code>. Pass an empty string (or a falsy value) to clear the preference.</p>
32
+ <h5><code>name<i>: string</i></code></h5>
33
+ <p>The voice <code>name</code> as reported by <code>speechSynthesis.getVoices()</code>, or a comma-separated fallback chain (<code>"Samantha, Google US English"</code>).</p>
34
+
35
+ <h4><code>voice.subscribe(callback)</code></h4>
36
+ <p>Subscribes to preference changes. Fires once immediately with the current value, then on every change (including changes from other tabs).</p>
37
+ <h5>Returns: <code>Function</code></h5>
38
+ <p>An unsubscribe function.</p>
39
+
40
+ <h4><code>voice.getAvailable()</code></h4>
41
+ <p>Synchronous shortcut for <code>window.speechSynthesis.getVoices()</code>. May return <code>[]</code> if the browser hasn't populated the list yet.</p>
42
+ <h5>Returns: <code>SpeechSynthesisVoice[]</code></h5>
43
+
44
+ <h4><code>voice.waitForVoices(timeoutMs?)</code></h4>
45
+ <p>Returns a promise that resolves with the list of available voices, waiting for the browser's <code>voiceschanged</code> event if the list is initially empty.</p>
46
+ <h5><code>timeoutMs<i>: number</i></code> &mdash; default <code>2000</code></h5>
47
+ <p>How long to wait before giving up and resolving with whatever the browser has.</p>
48
+ <h5>Returns: <code>Promise&lt;SpeechSynthesisVoice[]&gt;</code></h5>
49
+
50
+ <h4><code>voice.subscribeAvailable(callback)</code></h4>
51
+ <p>Subscribes to changes in the available voice list (fires on the <code>voiceschanged</code> event). Useful when populating a settings UI.</p>
52
+ <h5>Returns: <code>Function</code></h5>
53
+ <p>An unsubscribe function.</p>
54
+
55
+ <h3 id="examples"><a href="#examples" class="no-link">Examples</a></h3>
56
+
57
+ <h4>Building a Voice Picker by Hand</h4>
58
+ <pre><code class="hljs javascript"><span class="hljs-keyword">import</span> voice <span class="hljs-keyword">from</span> <span class="hljs-string">&#x27;./utils/voice.js&#x27;</span>;<br><br><span class="hljs-keyword">const</span> $select = <span class="hljs-variable language_">document</span>.<span class="hljs-title function_">getElementById</span>(<span class="hljs-string">&#x27;voice-picker&#x27;</span>);<br><br>voice.<span class="hljs-title function_">subscribeAvailable</span>(<span class="hljs-function">(<span class="hljs-params">voices</span>) =&gt;</span> {<br> $select.<span class="hljs-property">innerHTML</span> = <span class="hljs-string">&#x27;&lt;option value=&quot;&quot;&gt;Browser default&lt;/option&gt;&#x27;</span>;<br> <span class="hljs-keyword">for</span>(<span class="hljs-keyword">const</span> v <span class="hljs-keyword">of</span> voices){<br> <span class="hljs-keyword">const</span> opt = <span class="hljs-variable language_">document</span>.<span class="hljs-title function_">createElement</span>(<span class="hljs-string">&#x27;option&#x27;</span>);<br> opt.<span class="hljs-property">value</span> = v.<span class="hljs-property">name</span>;<br> opt.<span class="hljs-property">textContent</span> = <span class="hljs-string">`<span class="hljs-subst">${v.name}</span> (<span class="hljs-subst">${v.lang}</span>)`</span>;<br> $select.<span class="hljs-title function_">appendChild</span>(opt);<br> }<br> $select.<span class="hljs-property">value</span> = voice.<span class="hljs-title function_">get</span>();<br>});<br><br>$select.<span class="hljs-title function_">addEventListener</span>(<span class="hljs-string">&#x27;change&#x27;</span>, <span class="hljs-function">() =&gt;</span> voice.<span class="hljs-title function_">set</span>($select.<span class="hljs-property">value</span>));</code></pre>
59
+
60
+ <h4>Reading the Preference Without a Component</h4>
61
+ <pre><code class="hljs javascript"><span class="hljs-keyword">import</span> voice <span class="hljs-keyword">from</span> <span class="hljs-string">&#x27;./utils/voice.js&#x27;</span>;<br><br><span class="hljs-keyword">const</span> preferred = voice.<span class="hljs-title function_">get</span>();<br><span class="hljs-keyword">if</span>(preferred){<br> <span class="hljs-built_in">console</span>.<span class="hljs-title function_">log</span>(<span class="hljs-string">`User picked: <span class="hljs-subst">${preferred}</span>`</span>);<br>} <span class="hljs-keyword">else</span> {<br> <span class="hljs-built_in">console</span>.<span class="hljs-title function_">log</span>(<span class="hljs-string">&#x27;No preference set — using browser default&#x27;</span>);<br>}</code></pre>
62
+ </content>
63
+ <content location="scripts">
64
+ <script type="module" src="{{pathToRoot}}src/components/Accordion.js"></script>
65
+ </content>
66
+ </page>
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 -960 960 960"><path fill="currentColor" d="M268-240 42-466l57-56 170 170 56 56-57 56Zm226 0L268-466l56-57 170 170 368-368 56 57-424 424Zm0-226-57-56 198-198 57 56-198 198Z"/></svg>
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 -960 960 960"><path fill="currentColor" d="m228-240 92-160q-66 0-113-47t-47-113q0-66 47-113t113-47q66 0 113 47t47 113q0 23-5.5 42.5T458-480L320-240h-92Zm360 0 92-160q-66 0-113-47t-47-113q0-66 47-113t113-47q66 0 113 47t47 113q0 23-5.5 42.5T818-480L680-240h-92ZM362.5-517.5Q380-535 380-560t-17.5-42.5Q345-620 320-620t-42.5 17.5Q260-585 260-560t17.5 42.5Q295-500 320-500t42.5-17.5Zm360 0Q740-535 740-560t-17.5-42.5Q705-620 680-620t-42.5 17.5Q620-585 620-560t17.5 42.5Q655-500 680-500t42.5-17.5ZM680-560Zm-360 0Z"/></svg>
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 -960 960 960"><path fill="currentColor" d="M80-400v-80h800v80H80Zm340-160v-120H200v-120h560v120H540v120H420Zm0 400v-160h120v160H420Z"/></svg>
package/icons/send.svg ADDED
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 -960 960 960"><path fill="currentColor" d="M120-160v-640l760 320-760 320Zm80-120 474-200-474-200v140l240 60-240 60v140Zm0 0v-400 400Z"/></svg>
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 -960 960 960"><path fill="currentColor" d="M280-160v-520H80v-120h520v120H400v520H280Zm360 0v-320H520v-120h360v120H760v320H640Z"/></svg>
package/llms.txt CHANGED
@@ -56,7 +56,8 @@ import { html, css, LitElement } from 'kempo-ui/src/lit-all.min.js';
56
56
  | `<k-aside>` `<k-aside-item>` `<k-aside-label>` `<k-aside-menu>` `<k-aside-spacer>` `<k-aside-toggle>` | `Aside.js` | Sidebar/drawer; `main="push"` or `main="overlay"`, `side="left|right"` | [aside.html](https://dustinpoissant.github.io/kempo-ui/components/aside.html) |
57
57
  | `<k-calendar>` | `Calendar.js` | Inline month-grid date picker widget; `mode="single"` (default) or `mode="range"`; `value` is ISO `YYYY-MM-DD` (single) or comma-separated pair (range); `min`/`max` bounds; form-associated; fires `change` | [calendar.html](https://dustinpoissant.github.io/kempo-ui/components/calendar.html) |
58
58
  | `<k-card>` | `Card.js` | Bordered container; optional `label` attribute | [card.html](https://dustinpoissant.github.io/kempo-ui/components/card.html) |
59
- | `<k-code-editor>` | `CodeEditor.js` | Monaco-powered code editor; `value`, `language`, `controls` attributes; form-associated; theme-aware | [code-editor.html](https://dustinpoissant.github.io/kempo-ui/components/code-editor.html) |
59
+ | `<k-chat>` | `Chat.js` | Chat-room widget; markdown input via embedded `<k-markdown-editor>` (rendered through `marked` with `breaks:true` so single newlines become `<br>`, then run through `sanitizeHtml` so any inline HTML is filtered against the allow-list); default Enter sends / Shift+Enter newline (Slack-style), add `enter-newline` to invert; `show-status` (or `show-status="text"` / `show-status="icons"`) renders iMessage-style `sending`/`delivered`/`read`/`failed` only on the most recent outgoing message (sending/failed always show); methods `addMessage(msg)`, `updateMessage(id, updates)`, `removeMessage(id)`, `clear()`, `send()`; fires `send` with `{id, html, markdown}` | [chat.html](https://dustinpoissant.github.io/kempo-ui/components/chat.html) |
60
+ | `<k-code-editor>` | `CodeEditor.js` | Monaco-powered code editor; `value`, `language`, `controls`, `disabled`/`readonly`/`required` attributes; form-associated (full validity + reset/restore/disabled callbacks); theme-aware | [code-editor.html](https://dustinpoissant.github.io/kempo-ui/components/code-editor.html) |
60
61
  | `<k-color-picker>` | `ColorPicker.js` | Full color picker (hex, rgb, hsl, hsv, alpha); `value` property | [color-picker.html](https://dustinpoissant.github.io/kempo-ui/components/color-picker.html) |
61
62
  | `<k-combobox>` | `Combobox.js` | Searchable dropdown with debounced `search` event, dynamic `setOptions()`, `searching` indicator, keyboard nav; options are strings or `{label, value}` objects | [combobox.html](https://dustinpoissant.github.io/kempo-ui/components/combobox.html) |
62
63
  | `<k-content-slider>` | `ContentSlider.js` | Swipeable carousel | [content-slider.html](https://dustinpoissant.github.io/kempo-ui/components/content-slider.html) |
@@ -65,7 +66,8 @@ import { html, css, LitElement } from 'kempo-ui/src/lit-all.min.js';
65
66
  | `<k-dropdown>` | `Dropdown.js` | Action menu with keyboard navigation | [dropdown.html](https://dustinpoissant.github.io/kempo-ui/components/dropdown.html) |
66
67
  | `<k-filter-list>` `<k-filter-item>` | `FilterList.js` `FilterItem.js` | Filter/search list; items have `filter` attribute with keywords | [filter-list.html](https://dustinpoissant.github.io/kempo-ui/components/filter-list.html) |
67
68
  | `<k-focus-capture>` | `FocusCapture.js` | Traps keyboard focus within children | [focus-capture.html](https://dustinpoissant.github.io/kempo-ui/components/focus-capture.html) |
68
- | `<k-html-editor>` | `HtmlEditor.js` | WYSIWYG rich text editor; `value` property; `controls` attribute (`full`/`normal`/`minimal`/`none`) for built-in toolbars | [html-editor.html](https://dustinpoissant.github.io/kempo-ui/components/html-editor.html) |
69
+ | `<k-html-editor>` | `HtmlEditor.js` | WYSIWYG rich text editor; `value` property; `controls` attribute (`full`/`normal`/`minimal`/`none`) for built-in toolbars; `disabled`/`readonly`/`required` (Lexical's `setEditable()` + Monaco's `readOnly`); form-associated | [html-editor.html](https://dustinpoissant.github.io/kempo-ui/components/html-editor.html) |
70
+ | `<k-markdown-editor>` | `MarkdownEditor.js` | Markdown editor with Write/Preview tabs; raw markdown is the form value; rendered with vendored `marked` (full CommonMark + GFM — tables, task lists, fenced code, autolinks, inline HTML) via `renderMarkdown.js`; then sanitized via `sanitizeHtml` using either `allowed-tags` (comma-separated allowlist; pass `*` for everything) OR `disallowed-tags` (denylist) — mutually exclusive, allowlist wins if both set; defaults cover standard markdown output incl. img/hr/table family; `<script>` is always stripped unless `scripts-enabled` attribute is set; iframe/style/etc. are always stripped; `value`, `name`, `placeholder`, `disabled`/`readonly`/`required`, `mode` (`write`/`preview`), `allowed-tags`, `controls` (`'minimal'`/`'normal'`/`'full'`/`'none'` — pre-built toolbar; auto-imports the relevant control modules; user-slotted children override the preset); methods `focus()`, `blur()`, `clear()`, `setMode()`, `togglePreview()`, plus selection helpers `getSelection()`, `replaceSelection()`, `wrapSelection()`, `insertAtCursor()`, `insertLinePrefix()`; `controls-top`/`controls-bottom` slots for toolbar buttons; built-in controls under `markdownEditorControls/` (`k-md-bold`, `k-md-italic`, `k-md-strikethrough` (`~~...~~`), `k-md-heading` w/ `level`, `k-md-code`, `k-md-link`, `k-md-image` w/ `popup` (opens a Dropdown by default — `popup="dialog"` to switch to modal Dialog — for URL + alt text), `k-md-table` (small Dropdown with cols × rows inputs + Create button, inserts a GFM markdown table), `k-md-menu` w/ `label` (toolbar button that opens a Dropdown of slotted child controls; `trigger` slot fully customizes the button content — defaults to a `menu` icon), `k-md-format-block` w/ `tag` (heading picker designed to live inside `k-md-menu` — `h1`-`h6`), `k-md-speech-to-text` w/ `language`/`continuous` (wraps `<k-speech-to-text>`; final transcript is inserted at the cursor when the user stops speaking), `k-md-bullet-list`, `k-md-numbered-list`, `k-md-quote`); extend `MarkdownEditorControl` for custom buttons (incl. ones that insert raw HTML — adjust `allowed-tags` accordingly); fires `input`, `change`, `mode-change` | [markdown-editor.html](https://dustinpoissant.github.io/kempo-ui/components/markdown-editor.html) |
69
71
  | `<k-icon>` | `Icon.js` | SVG icon; `name` (looked up in `pathsToIcons`) or `src` (direct URL); `rotation`, `direction`, `animation` attributes | [icon.html](https://dustinpoissant.github.io/kempo-ui/components/icon.html) |
70
72
  | `<k-import>` | `Import.js` | Includes external HTML fragments; `src` attribute | [import.html](https://dustinpoissant.github.io/kempo-ui/components/import.html) |
71
73
  | `<k-main>` | `Main.js` | Layout main; automatically adjusts margins when a `k-aside[main="push"]` opens | — |
@@ -90,6 +92,7 @@ import { html, css, LitElement } from 'kempo-ui/src/lit-all.min.js';
90
92
  | `<k-toast>` `<k-toast-container>` | `Toast.js` | Toast notifications; use `<k-toast-container>` once in page, call `.addToast(options)` | [toast.html](https://dustinpoissant.github.io/kempo-ui/components/toast.html) |
91
93
  | `<k-toggle>` | `Toggle.js` | On/off toggle; `value` (Boolean, reflects); methods: `on()`, `off()`, `toggle()`; fires `change`, `on`, `off` | [toggle.html](https://dustinpoissant.github.io/kempo-ui/components/toggle.html) |
92
94
  | `<k-tree>` `<k-tree-node>` | `Tree.js` | Expandable tree; nodes have `label`, `icon`, `href`, `expanded` | [tree.html](https://dustinpoissant.github.io/kempo-ui/components/tree.html) |
95
+ | `<k-voice-selector>` | `VoiceSelector.js` | `<select>` populated with `speechSynthesis.getVoices()`; persists choice via the `voice` utility so any `<k-text-to-speech>` without an explicit `voice` attr uses the saved preference; `language` (BCP-47 prefix) attr filters the list; fires `change` | [voice-selector.html](https://dustinpoissant.github.io/kempo-ui/components/voice-selector.html) |
93
96
 
94
97
  ---
95
98
 
@@ -245,7 +248,9 @@ Located in `src/utils/`. Import individually.
245
248
  | `debounce.js` | `default` | `debounce(fn, ms)` — delay fn until calls stop | [debounce.html](https://dustinpoissant.github.io/kempo-ui/utils/debounce.html) |
246
249
  | `wait.js` | `wait`, `waitFrames` | `wait(ms)` → Promise; `waitFrames(n)` → Promise after n animation frames | [wait.html](https://dustinpoissant.github.io/kempo-ui/utils/wait.html) |
247
250
  | `theme.js` | `setTheme`, `getTheme`, `subscribeToTheme`, `initTheme`, `getCalculatedTheme` | Manage light/dark/auto theme; persists to localStorage | [theme.html](https://dustinpoissant.github.io/kempo-ui/utils/theme.html) |
251
+ | `voice.js` | `getVoice`, `setVoice`, `subscribeToVoice`, `getAvailableVoices`, `waitForVoices`, `subscribeToAvailableVoices` | Persisted speech-synthesis voice preference (mirrors `theme`); used by `<k-voice-selector>` and consumed by `<k-text-to-speech>` when no explicit `voice` attr is set | [voice.html](https://dustinpoissant.github.io/kempo-ui/utils/voice.html) |
248
252
  | `propConverters.js` | `boolExists`, `boolTrueFalse` | Lit property converters for boolean attributes | [propConverters.html](https://dustinpoissant.github.io/kempo-ui/utils/propConverters.html) |
253
+ | `sanitizeHtml.js` | `default`, `sanitizeHtml`, `DEFAULT_TAGS`, `DEFAULT_ATTRS_GLOBAL`, `DEFAULT_ATTRS_PER_TAG` | Whitelist HTML sanitizer; strips `<script>`, `<style>`, `<iframe>`, event-handler attrs, unsafe URL schemes; defaults sized for rich-text input. **Frontend defense only — server must also sanitize.** | [sanitizeHtml.html](https://dustinpoissant.github.io/kempo-ui/utils/sanitizeHtml.html) |
249
254
  | `context.js` | `default` | `createContext(key, initialValue)` → `{ get, set, subscribe }` pub/sub state | [context.html](https://dustinpoissant.github.io/kempo-ui/utils/context.html) |
250
255
  | `cookie.js` | `saveCookie`, `getCookie`, `deleteCookie` | Get/set/delete browser cookies | [cookie.html](https://dustinpoissant.github.io/kempo-ui/utils/cookie.html) |
251
256
  | `drag.js` | `default` | `drag(options)` — attach mouse/touch drag listeners to an element; options: `element`, `callback`, `startCallback`, `moveCallback`, `endCallback`, `preventScroll` | [drag.html](https://dustinpoissant.github.io/kempo-ui/utils/drag.html) |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kempo-ui",
3
- "version": "0.4.4",
3
+ "version": "0.4.6",
4
4
  "type": "module",
5
5
  "description": "A Lit based web-component library",
6
6
  "main": "index.js",
@@ -42,6 +42,7 @@
42
42
  },
43
43
  "dependencies": {
44
44
  "@inquirer/prompts": "^8.3.0",
45
- "kempo-css": "^2.2.1"
45
+ "kempo-css": "^2.2.1",
46
+ "marked": "^18.0.2"
46
47
  }
47
48
  }