kempo-ui 0.4.6 → 0.4.8

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 (91) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/dist/components/Markdown.js +1 -0
  3. package/dist/components/Timestamp.js +1 -1
  4. package/dist/utils/formatTimestamp.js +1 -1
  5. package/docs/components/accordion.html +4 -0
  6. package/docs/components/aside.html +4 -0
  7. package/docs/components/calendar.html +4 -0
  8. package/docs/components/card.html +4 -0
  9. package/docs/components/chat.html +4 -0
  10. package/docs/components/code-editor.html +4 -0
  11. package/docs/components/color-picker.html +4 -0
  12. package/docs/components/combobox.html +4 -0
  13. package/docs/components/content-slider.html +4 -0
  14. package/docs/components/context.html +4 -0
  15. package/docs/components/dialog.html +4 -0
  16. package/docs/components/dropdown.html +4 -0
  17. package/docs/components/filter-list.html +4 -0
  18. package/docs/components/focus-capture.html +4 -0
  19. package/docs/components/html-editor.html +4 -0
  20. package/docs/components/hybrid-component.html +4 -0
  21. package/docs/components/icon.html +4 -0
  22. package/docs/components/import.html +4 -0
  23. package/docs/components/light-component.html +4 -0
  24. package/docs/components/markdown-editor.html +4 -0
  25. package/docs/components/markdown.html +650 -0
  26. package/docs/components/nav-spacer.html +4 -0
  27. package/docs/components/nav.html +4 -0
  28. package/docs/components/photo-viewer.html +4 -0
  29. package/docs/components/progress.html +4 -0
  30. package/docs/components/resize.html +4 -0
  31. package/docs/components/shadow-component.html +4 -0
  32. package/docs/components/show-more.html +4 -0
  33. package/docs/components/slider.html +4 -0
  34. package/docs/components/sortable.html +4 -0
  35. package/docs/components/speech-to-text.html +4 -0
  36. package/docs/components/spinner.html +4 -0
  37. package/docs/components/split.html +4 -0
  38. package/docs/components/table.html +4 -0
  39. package/docs/components/tableControls.html +4 -0
  40. package/docs/components/tableCustomFields.html +4 -0
  41. package/docs/components/tableFetchRecords.html +4 -0
  42. package/docs/components/tableFieldSortHide.html +4 -0
  43. package/docs/components/tablePagination.html +4 -0
  44. package/docs/components/tablePlaceholder.html +4 -0
  45. package/docs/components/tableRecordEditing.html +4 -0
  46. package/docs/components/tableRecordFiltering.html +4 -0
  47. package/docs/components/tableRecordHiding.html +4 -0
  48. package/docs/components/tableRecordSearching.html +4 -0
  49. package/docs/components/tableRecordSelection.html +4 -0
  50. package/docs/components/tableRowControls.html +4 -0
  51. package/docs/components/tableServerSync.html +4 -0
  52. package/docs/components/tableSorting.html +4 -0
  53. package/docs/components/tabs.html +4 -0
  54. package/docs/components/tags.html +4 -0
  55. package/docs/components/text-to-speech.html +4 -0
  56. package/docs/components/theme-select.html +4 -0
  57. package/docs/components/theme-switcher.html +4 -0
  58. package/docs/components/time.html +4 -0
  59. package/docs/components/timestamp.html +4 -0
  60. package/docs/components/toast.html +4 -0
  61. package/docs/components/toggle.html +4 -0
  62. package/docs/components/tree.html +4 -0
  63. package/docs/components/voice-selector.html +4 -0
  64. package/docs/index.html +10 -0
  65. package/docs/src/components/Markdown.js +1 -0
  66. package/docs/src/components/Timestamp.js +1 -1
  67. package/docs/src/utils/formatTimestamp.js +1 -1
  68. package/docs/utils/context.html +4 -0
  69. package/docs/utils/cookie.html +4 -0
  70. package/docs/utils/debounce.html +4 -0
  71. package/docs/utils/drag.html +4 -0
  72. package/docs/utils/elevation.html +4 -0
  73. package/docs/utils/formatTimestamp.html +4 -0
  74. package/docs/utils/object.html +4 -0
  75. package/docs/utils/propConverters.html +4 -0
  76. package/docs/utils/sanitizeHtml.html +4 -0
  77. package/docs/utils/string.html +4 -0
  78. package/docs/utils/theme.html +4 -0
  79. package/docs/utils/toTitleCase.html +4 -0
  80. package/docs/utils/type.html +4 -0
  81. package/docs/utils/voice.html +4 -0
  82. package/docs/utils/wait.html +4 -0
  83. package/docs-src/components/markdown.page.html +161 -0
  84. package/docs-src/index.page.html +6 -0
  85. package/docs-src/nav.fragment.html +4 -0
  86. package/llms.txt +1 -0
  87. package/package.json +1 -1
  88. package/src/components/Markdown.js +125 -0
  89. package/src/components/Timestamp.js +3 -3
  90. package/src/utils/formatTimestamp.js +2 -1
  91. package/tests/components/Markdown.browser-test.js +292 -0
@@ -0,0 +1,292 @@
1
+ import Markdown from '../../src/components/Markdown.js';
2
+ import LightComponent from '../../src/components/LightComponent.js';
3
+
4
+ const createMarkdown = async (innerHTML = '', attrs = {}) => {
5
+ const container = document.createElement('div');
6
+ const el = document.createElement('k-markdown');
7
+ for(const [key, value] of Object.entries(attrs)){
8
+ el.setAttribute(key, value);
9
+ }
10
+ if(innerHTML) el.innerHTML = innerHTML;
11
+ container.appendChild(el);
12
+ document.body.appendChild(container);
13
+ await el.updateComplete;
14
+ return { container, el };
15
+ };
16
+
17
+ const cleanup = (container) => {
18
+ if(container && container.parentNode){
19
+ container.parentNode.removeChild(container);
20
+ }
21
+ };
22
+
23
+ export default {
24
+ /*
25
+ Element Creation
26
+ */
27
+ 'should create k-markdown element': async ({pass, fail}) => {
28
+ const { container, el } = await createMarkdown();
29
+ if(!(el instanceof Markdown)){
30
+ cleanup(container);
31
+ return fail('Element should be instance of Markdown');
32
+ }
33
+ cleanup(container);
34
+ pass('k-markdown element created correctly');
35
+ },
36
+
37
+ 'should extend LightComponent': async ({pass, fail}) => {
38
+ const { container, el } = await createMarkdown();
39
+ if(!(el instanceof LightComponent)){
40
+ cleanup(container);
41
+ return fail('Markdown should extend LightComponent');
42
+ }
43
+ cleanup(container);
44
+ pass('Markdown extends LightComponent');
45
+ },
46
+
47
+ /*
48
+ Default Properties
49
+ */
50
+ 'should have empty default value': async ({pass, fail}) => {
51
+ const { container, el } = await createMarkdown();
52
+ if(el.value !== ''){
53
+ cleanup(container);
54
+ return fail(`Expected value to be '', got '${el.value}'`);
55
+ }
56
+ cleanup(container);
57
+ pass('Default value is empty string');
58
+ },
59
+
60
+ 'should have breaks default false': async ({pass, fail}) => {
61
+ const { container, el } = await createMarkdown();
62
+ if(el.breaks !== false){
63
+ cleanup(container);
64
+ return fail(`Expected breaks to be false, got ${el.breaks}`);
65
+ }
66
+ cleanup(container);
67
+ pass('Default breaks is false');
68
+ },
69
+
70
+ /*
71
+ Children → value capture
72
+ */
73
+ 'should capture markdown from child text content': async ({pass, fail}) => {
74
+ const { container, el } = await createMarkdown('# Hello');
75
+ if(el.value !== '# Hello'){
76
+ cleanup(container);
77
+ return fail(`Expected value '# Hello', got '${el.value}'`);
78
+ }
79
+ cleanup(container);
80
+ pass('Captures markdown from children');
81
+ },
82
+
83
+ 'should dedent indented child markdown': async ({pass, fail}) => {
84
+ const container = document.createElement('div');
85
+ container.innerHTML = `<k-markdown>
86
+ # Title
87
+
88
+ Some text
89
+ </k-markdown>`;
90
+ document.body.appendChild(container);
91
+ const el = container.querySelector('k-markdown');
92
+ await el.updateComplete;
93
+ const expected = '# Title\n\nSome text';
94
+ if(el.value !== expected){
95
+ cleanup(container);
96
+ return fail(`Expected dedented value '${expected}', got '${el.value}'`);
97
+ }
98
+ cleanup(container);
99
+ pass('Indented child markdown is dedented');
100
+ },
101
+
102
+ 'value attribute takes precedence over children': async ({pass, fail}) => {
103
+ const container = document.createElement('div');
104
+ container.innerHTML = `<k-markdown value="from attr"># From children</k-markdown>`;
105
+ document.body.appendChild(container);
106
+ const el = container.querySelector('k-markdown');
107
+ await el.updateComplete;
108
+ if(el.value !== 'from attr'){
109
+ cleanup(container);
110
+ return fail(`Expected value 'from attr', got '${el.value}'`);
111
+ }
112
+ cleanup(container);
113
+ pass('value attribute beats children');
114
+ },
115
+
116
+ /*
117
+ Rendering
118
+ */
119
+ 'should render markdown to HTML in light DOM': async ({pass, fail}) => {
120
+ const { container, el } = await createMarkdown('# Heading');
121
+ const heading = el.querySelector('h1');
122
+ if(!heading){
123
+ cleanup(container);
124
+ return fail(`Expected h1 in rendered output, got: ${el.innerHTML}`);
125
+ }
126
+ if(heading.textContent !== 'Heading'){
127
+ cleanup(container);
128
+ return fail(`Expected h1 text 'Heading', got '${heading.textContent}'`);
129
+ }
130
+ cleanup(container);
131
+ pass('Renders markdown heading as h1 in light DOM');
132
+ },
133
+
134
+ 'should render bold and italic': async ({pass, fail}) => {
135
+ const { container, el } = await createMarkdown('This is **bold** and *italic*.');
136
+ const strong = el.querySelector('strong');
137
+ const em = el.querySelector('em');
138
+ if(!strong || strong.textContent !== 'bold'){
139
+ cleanup(container);
140
+ return fail(`Expected <strong>bold</strong>, got: ${el.innerHTML}`);
141
+ }
142
+ if(!em || em.textContent !== 'italic'){
143
+ cleanup(container);
144
+ return fail(`Expected <em>italic</em>, got: ${el.innerHTML}`);
145
+ }
146
+ cleanup(container);
147
+ pass('Bold and italic render correctly');
148
+ },
149
+
150
+ 'should render lists': async ({pass, fail}) => {
151
+ const container = document.createElement('div');
152
+ container.innerHTML = `<k-markdown>
153
+ - one
154
+ - two
155
+ - three
156
+ </k-markdown>`;
157
+ document.body.appendChild(container);
158
+ const el = container.querySelector('k-markdown');
159
+ await el.updateComplete;
160
+ const items = el.querySelectorAll('ul > li');
161
+ if(items.length !== 3){
162
+ cleanup(container);
163
+ return fail(`Expected 3 li elements, got ${items.length}: ${el.innerHTML}`);
164
+ }
165
+ cleanup(container);
166
+ pass('Lists render with three items');
167
+ },
168
+
169
+ 'should clear original child markdown source': async ({pass, fail}) => {
170
+ const { container, el } = await createMarkdown('# Hello');
171
+ if(el.textContent.includes('# Hello')){
172
+ cleanup(container);
173
+ return fail(`Original markdown source should be removed, got textContent: '${el.textContent}'`);
174
+ }
175
+ cleanup(container);
176
+ pass('Original child markdown is cleared');
177
+ },
178
+
179
+ /*
180
+ Re-rendering on value change
181
+ */
182
+ 'should re-render when value property changes': async ({pass, fail}) => {
183
+ const { container, el } = await createMarkdown('first');
184
+ el.value = '## Updated';
185
+ await el.updateComplete;
186
+ const h2 = el.querySelector('h2');
187
+ if(!h2 || h2.textContent !== 'Updated'){
188
+ cleanup(container);
189
+ return fail(`Expected h2 'Updated' after value change, got: ${el.innerHTML}`);
190
+ }
191
+ cleanup(container);
192
+ pass('Re-renders on value change');
193
+ },
194
+
195
+ /*
196
+ breaks attribute
197
+ */
198
+ 'breaks=false joins single newlines (CommonMark)': async ({pass, fail}) => {
199
+ const { container, el } = await createMarkdown();
200
+ el.value = 'one\ntwo';
201
+ await el.updateComplete;
202
+ const br = el.querySelector('br');
203
+ if(br){
204
+ cleanup(container);
205
+ return fail(`Expected no <br> when breaks=false, got: ${el.innerHTML}`);
206
+ }
207
+ cleanup(container);
208
+ pass('breaks=false joins single newlines');
209
+ },
210
+
211
+ 'breaks=true converts single newlines to <br>': async ({pass, fail}) => {
212
+ const { container, el } = await createMarkdown('', { breaks: '' });
213
+ el.value = 'one\ntwo';
214
+ await el.updateComplete;
215
+ const br = el.querySelector('br');
216
+ if(!br){
217
+ cleanup(container);
218
+ return fail(`Expected <br> when breaks=true, got: ${el.innerHTML}`);
219
+ }
220
+ cleanup(container);
221
+ pass('breaks=true converts single newlines to <br>');
222
+ },
223
+
224
+ /*
225
+ Sanitization
226
+ */
227
+ 'should strip <script> tags by default': async ({pass, fail}) => {
228
+ const { container, el } = await createMarkdown();
229
+ el.value = 'safe <script>alert(1)</script> text';
230
+ await el.updateComplete;
231
+ if(el.querySelector('script')){
232
+ cleanup(container);
233
+ return fail(`Expected no <script>, got: ${el.innerHTML}`);
234
+ }
235
+ if(el.textContent.includes('alert(1)')){
236
+ cleanup(container);
237
+ return fail(`Script content should be stripped completely, got textContent: '${el.textContent}'`);
238
+ }
239
+ cleanup(container);
240
+ pass('Script tags stripped by default');
241
+ },
242
+
243
+ 'allowed-tags="*" allows everything': async ({pass, fail}) => {
244
+ const { container, el } = await createMarkdown('', { 'allowed-tags': '*' });
245
+ el.value = '<details><summary>open</summary>hi</details>';
246
+ await el.updateComplete;
247
+ if(!el.querySelector('details') || !el.querySelector('summary')){
248
+ cleanup(container);
249
+ return fail(`Expected details+summary preserved, got: ${el.innerHTML}`);
250
+ }
251
+ cleanup(container);
252
+ pass('allowed-tags="*" allows custom tags');
253
+ },
254
+
255
+ 'disallowed-tags drops listed tags but keeps text': async ({pass, fail}) => {
256
+ const { container, el } = await createMarkdown('', { 'disallowed-tags': 'h1' });
257
+ el.value = '# Heading\n\nA paragraph.';
258
+ await el.updateComplete;
259
+ if(el.querySelector('h1')){
260
+ cleanup(container);
261
+ return fail(`Expected no <h1>, got: ${el.innerHTML}`);
262
+ }
263
+ if(!el.textContent.includes('Heading')){
264
+ cleanup(container);
265
+ return fail(`Expected heading text preserved, got textContent: '${el.textContent}'`);
266
+ }
267
+ if(!el.querySelector('p')){
268
+ cleanup(container);
269
+ return fail(`Expected paragraph preserved, got: ${el.innerHTML}`);
270
+ }
271
+ cleanup(container);
272
+ pass('disallowed-tags drops listed tags but keeps text');
273
+ },
274
+
275
+ /*
276
+ renderedHtml getter
277
+ */
278
+ 'renderedHtml getter returns sanitized HTML string': async ({pass, fail}) => {
279
+ const { container, el } = await createMarkdown('# Title');
280
+ const out = el.renderedHtml;
281
+ if(typeof out !== 'string'){
282
+ cleanup(container);
283
+ return fail(`Expected string, got ${typeof out}`);
284
+ }
285
+ if(!out.includes('<h1') || !out.includes('Title')){
286
+ cleanup(container);
287
+ return fail(`Expected '<h1...>Title' in renderedHtml, got '${out}'`);
288
+ }
289
+ cleanup(container);
290
+ pass('renderedHtml returns expected HTML string');
291
+ }
292
+ };