kempo-ui 0.3.6 → 0.3.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 (271) hide show
  1. package/dist/components/Accordion.js +5 -5
  2. package/dist/components/Aside.js +16 -16
  3. package/dist/components/Card.js +3 -3
  4. package/dist/components/ColorPicker.js +4 -4
  5. package/dist/components/ContentSlider.js +3 -3
  6. package/dist/components/Context.js +1 -1
  7. package/dist/components/Dialog.js +7 -7
  8. package/dist/components/Dropdown.js +7 -4
  9. package/dist/components/FilterItem.js +2 -2
  10. package/dist/components/FilterList.js +2 -2
  11. package/dist/components/FocusCapture.js +2 -2
  12. package/dist/components/HtmlEditor.js +62 -39
  13. package/dist/components/HybridComponent.js +1 -1
  14. package/dist/components/Icon.js +2 -2
  15. package/dist/components/Import.js +1 -1
  16. package/dist/components/LightComponent.js +1 -1
  17. package/dist/components/Main.js +3 -3
  18. package/dist/components/Nav.js +2 -2
  19. package/dist/components/NavSpacer.js +6 -0
  20. package/dist/components/PhotoViewer.js +3 -3
  21. package/dist/components/Resize.js +3 -3
  22. package/dist/components/ShadowComponent.js +1 -1
  23. package/dist/components/ShowMore.js +3 -3
  24. package/dist/components/Sortable.js +3 -3
  25. package/dist/components/Spinner.js +2 -2
  26. package/dist/components/Split.js +3 -3
  27. package/dist/components/Table.js +27 -27
  28. package/dist/components/Tabs.js +7 -7
  29. package/dist/components/Tags.js +4 -4
  30. package/dist/components/ThemeSelect.js +4 -4
  31. package/dist/components/ThemeSwitcher.js +5 -5
  32. package/dist/components/Timestamp.js +1 -1
  33. package/dist/components/Toast.js +3 -3
  34. package/dist/components/Toggle.js +3 -3
  35. package/dist/components/Tree.js +10 -10
  36. package/dist/components/htmlEditorControls/AlignCenter.js +4 -4
  37. package/dist/components/htmlEditorControls/AlignJustify.js +4 -4
  38. package/dist/components/htmlEditorControls/AlignLeft.js +4 -4
  39. package/dist/components/htmlEditorControls/AlignRight.js +4 -4
  40. package/dist/components/htmlEditorControls/Bold.js +4 -4
  41. package/dist/components/htmlEditorControls/BulletList.js +4 -4
  42. package/dist/components/htmlEditorControls/CharacterCount.js +3 -3
  43. package/dist/components/htmlEditorControls/ClearFormatting.js +4 -4
  44. package/dist/components/htmlEditorControls/CodeBlock.js +3 -3
  45. package/dist/components/htmlEditorControls/ControlGroup.js +2 -2
  46. package/dist/components/htmlEditorControls/ControlSpacer.js +2 -2
  47. package/dist/components/htmlEditorControls/CreateLink.js +3 -3
  48. package/dist/components/htmlEditorControls/DropdownControl.js +3 -3
  49. package/dist/components/htmlEditorControls/FormatBlock.js +4 -4
  50. package/dist/components/htmlEditorControls/HtmlEditorControl.js +2 -2
  51. package/dist/components/htmlEditorControls/InlineCode.js +3 -3
  52. package/dist/components/htmlEditorControls/InsertTable.js +3 -3
  53. package/dist/components/htmlEditorControls/Italic.js +4 -4
  54. package/dist/components/htmlEditorControls/Mode.js +3 -3
  55. package/dist/components/htmlEditorControls/NumberList.js +4 -4
  56. package/dist/components/htmlEditorControls/Strikethrough.js +4 -4
  57. package/dist/components/htmlEditorControls/TextBackgroundColor.js +6 -6
  58. package/dist/components/htmlEditorControls/TextColor.js +7 -7
  59. package/dist/components/htmlEditorControls/Underline.js +4 -4
  60. package/dist/components/htmlEditorControls/WordCount.js +3 -3
  61. package/dist/components/htmlEditorNodes/HtmlComment.js +24 -0
  62. package/dist/components/tableControls/DeleteRecord.js +2 -2
  63. package/dist/components/tableControls/DeleteSelected.js +2 -2
  64. package/dist/components/tableControls/Edit.js +4 -4
  65. package/dist/components/tableControls/ExportCSV.js +2 -2
  66. package/dist/components/tableControls/ExportJson.js +2 -2
  67. package/dist/components/tableControls/FieldSortHide.js +4 -4
  68. package/dist/components/tableControls/Filters.js +17 -17
  69. package/dist/components/tableControls/FirstPage.js +2 -2
  70. package/dist/components/tableControls/HiddenCount.js +2 -2
  71. package/dist/components/tableControls/Hide.js +2 -2
  72. package/dist/components/tableControls/LastPage.js +2 -2
  73. package/dist/components/tableControls/NextPage.js +2 -2
  74. package/dist/components/tableControls/PageSelect.js +4 -4
  75. package/dist/components/tableControls/PageSize.js +3 -3
  76. package/dist/components/tableControls/PrevPage.js +2 -2
  77. package/dist/components/tableControls/Search.js +2 -2
  78. package/dist/components/tableControls/ShowAll.js +2 -2
  79. package/dist/components/tableControls/TableControl.js +2 -2
  80. package/dist/utils/context.js +1 -1
  81. package/dist/utils/debounce.js +1 -1
  82. package/dist/utils/object.js +1 -1
  83. package/dist/utils/theme.js +1 -1
  84. package/dist/utils/wait.js +0 -1
  85. package/docs/components/accordion.html +2 -0
  86. package/docs/components/aside.html +55 -111
  87. package/docs/components/card.html +2 -0
  88. package/docs/components/color-picker.html +2 -0
  89. package/docs/components/content-slider.html +2 -0
  90. package/docs/components/context.html +2 -0
  91. package/docs/components/dialog.html +2 -0
  92. package/docs/components/dropdown.html +2 -0
  93. package/docs/components/filter-list.html +2 -0
  94. package/docs/components/focus-capture.html +2 -0
  95. package/docs/components/html-editor.html +54 -6
  96. package/docs/components/hybrid-component.html +2 -0
  97. package/docs/components/icon.html +2 -0
  98. package/docs/components/import.html +2 -0
  99. package/docs/components/light-component.html +2 -0
  100. package/docs/components/nav-spacer.html +315 -0
  101. package/docs/components/nav.html +2 -0
  102. package/docs/components/photo-viewer.html +2 -0
  103. package/docs/components/resize.html +2 -0
  104. package/docs/components/shadow-component.html +2 -0
  105. package/docs/components/show-more.html +2 -0
  106. package/docs/components/sortable.html +2 -0
  107. package/docs/components/spinner.html +2 -0
  108. package/docs/components/split.html +2 -0
  109. package/docs/components/table.html +2 -0
  110. package/docs/components/tableControls.html +2 -0
  111. package/docs/components/tableCustomFields.html +2 -0
  112. package/docs/components/tableFetchRecords.html +2 -0
  113. package/docs/components/tableFieldSortHide.html +2 -0
  114. package/docs/components/tablePagination.html +2 -0
  115. package/docs/components/tablePlaceholder.html +2 -0
  116. package/docs/components/tableRecordEditing.html +2 -0
  117. package/docs/components/tableRecordFiltering.html +2 -0
  118. package/docs/components/tableRecordHiding.html +2 -0
  119. package/docs/components/tableRecordSearching.html +2 -0
  120. package/docs/components/tableRecordSelection.html +2 -0
  121. package/docs/components/tableRowControls.html +2 -0
  122. package/docs/components/tableServerSync.html +2 -0
  123. package/docs/components/tableSorting.html +2 -0
  124. package/docs/components/tabs.html +2 -0
  125. package/docs/components/tags.html +2 -0
  126. package/docs/components/theme-select.html +2 -0
  127. package/docs/components/theme-switcher.html +2 -0
  128. package/docs/components/timestamp.html +2 -0
  129. package/docs/components/toast.html +2 -0
  130. package/docs/components/toggle.html +2 -0
  131. package/docs/components/tree.html +2 -0
  132. package/docs/icons/comment.svg +1 -0
  133. package/docs/index.html +8 -0
  134. package/docs/src/components/Accordion.js +5 -5
  135. package/docs/src/components/Aside.js +16 -16
  136. package/docs/src/components/Card.js +3 -3
  137. package/docs/src/components/ColorPicker.js +4 -4
  138. package/docs/src/components/ContentSlider.js +3 -3
  139. package/docs/src/components/Context.js +1 -1
  140. package/docs/src/components/Dialog.js +7 -7
  141. package/docs/src/components/Dropdown.js +7 -4
  142. package/docs/src/components/FilterItem.js +2 -2
  143. package/docs/src/components/FilterList.js +2 -2
  144. package/docs/src/components/FocusCapture.js +2 -2
  145. package/docs/src/components/HtmlEditor.js +62 -39
  146. package/docs/src/components/HybridComponent.js +1 -1
  147. package/docs/src/components/Icon.js +2 -2
  148. package/docs/src/components/Import.js +1 -1
  149. package/docs/src/components/LightComponent.js +1 -1
  150. package/docs/src/components/Main.js +3 -3
  151. package/docs/src/components/Nav.js +2 -2
  152. package/docs/src/components/NavSpacer.js +6 -0
  153. package/docs/src/components/PhotoViewer.js +3 -3
  154. package/docs/src/components/Resize.js +3 -3
  155. package/docs/src/components/ShadowComponent.js +1 -1
  156. package/docs/src/components/ShowMore.js +3 -3
  157. package/docs/src/components/Sortable.js +3 -3
  158. package/docs/src/components/Spinner.js +2 -2
  159. package/docs/src/components/Split.js +3 -3
  160. package/docs/src/components/Table.js +27 -27
  161. package/docs/src/components/Tabs.js +7 -7
  162. package/docs/src/components/Tags.js +4 -4
  163. package/docs/src/components/ThemeSelect.js +4 -4
  164. package/docs/src/components/ThemeSwitcher.js +5 -5
  165. package/docs/src/components/Timestamp.js +1 -1
  166. package/docs/src/components/Toast.js +3 -3
  167. package/docs/src/components/Toggle.js +3 -3
  168. package/docs/src/components/Tree.js +10 -10
  169. package/docs/src/components/htmlEditorControls/AlignCenter.js +4 -4
  170. package/docs/src/components/htmlEditorControls/AlignJustify.js +4 -4
  171. package/docs/src/components/htmlEditorControls/AlignLeft.js +4 -4
  172. package/docs/src/components/htmlEditorControls/AlignRight.js +4 -4
  173. package/docs/src/components/htmlEditorControls/Bold.js +4 -4
  174. package/docs/src/components/htmlEditorControls/BulletList.js +4 -4
  175. package/docs/src/components/htmlEditorControls/CharacterCount.js +3 -3
  176. package/docs/src/components/htmlEditorControls/ClearFormatting.js +4 -4
  177. package/docs/src/components/htmlEditorControls/CodeBlock.js +3 -3
  178. package/docs/src/components/htmlEditorControls/ControlGroup.js +2 -2
  179. package/docs/src/components/htmlEditorControls/ControlSpacer.js +2 -2
  180. package/docs/src/components/htmlEditorControls/CreateLink.js +3 -3
  181. package/docs/src/components/htmlEditorControls/DropdownControl.js +3 -3
  182. package/docs/src/components/htmlEditorControls/FormatBlock.js +4 -4
  183. package/docs/src/components/htmlEditorControls/HtmlEditorControl.js +2 -2
  184. package/docs/src/components/htmlEditorControls/InlineCode.js +3 -3
  185. package/docs/src/components/htmlEditorControls/InsertTable.js +3 -3
  186. package/docs/src/components/htmlEditorControls/Italic.js +4 -4
  187. package/docs/src/components/htmlEditorControls/Mode.js +3 -3
  188. package/docs/src/components/htmlEditorControls/NumberList.js +4 -4
  189. package/docs/src/components/htmlEditorControls/Strikethrough.js +4 -4
  190. package/docs/src/components/htmlEditorControls/TextBackgroundColor.js +6 -6
  191. package/docs/src/components/htmlEditorControls/TextColor.js +7 -7
  192. package/docs/src/components/htmlEditorControls/Underline.js +4 -4
  193. package/docs/src/components/htmlEditorControls/WordCount.js +3 -3
  194. package/docs/src/components/htmlEditorNodes/HtmlComment.js +24 -0
  195. package/docs/src/components/tableControls/DeleteRecord.js +2 -2
  196. package/docs/src/components/tableControls/DeleteSelected.js +2 -2
  197. package/docs/src/components/tableControls/Edit.js +4 -4
  198. package/docs/src/components/tableControls/ExportCSV.js +2 -2
  199. package/docs/src/components/tableControls/ExportJson.js +2 -2
  200. package/docs/src/components/tableControls/FieldSortHide.js +4 -4
  201. package/docs/src/components/tableControls/Filters.js +17 -17
  202. package/docs/src/components/tableControls/FirstPage.js +2 -2
  203. package/docs/src/components/tableControls/HiddenCount.js +2 -2
  204. package/docs/src/components/tableControls/Hide.js +2 -2
  205. package/docs/src/components/tableControls/LastPage.js +2 -2
  206. package/docs/src/components/tableControls/NextPage.js +2 -2
  207. package/docs/src/components/tableControls/PageSelect.js +4 -4
  208. package/docs/src/components/tableControls/PageSize.js +3 -3
  209. package/docs/src/components/tableControls/PrevPage.js +2 -2
  210. package/docs/src/components/tableControls/Search.js +2 -2
  211. package/docs/src/components/tableControls/ShowAll.js +2 -2
  212. package/docs/src/components/tableControls/TableControl.js +2 -2
  213. package/docs/src/utils/context.js +1 -1
  214. package/docs/src/utils/debounce.js +1 -1
  215. package/docs/src/utils/object.js +1 -1
  216. package/docs/src/utils/theme.js +1 -1
  217. package/docs/src/utils/wait.js +0 -1
  218. package/docs/utils/context.html +2 -0
  219. package/docs/utils/cookie.html +2 -0
  220. package/docs/utils/debounce.html +2 -0
  221. package/docs/utils/drag.html +2 -0
  222. package/docs/utils/elevation.html +2 -0
  223. package/docs/utils/formatTimestamp.html +8 -6
  224. package/docs/utils/object.html +2 -0
  225. package/docs/utils/propConverters.html +2 -0
  226. package/docs/utils/string.html +2 -0
  227. package/docs/utils/theme.html +2 -0
  228. package/docs/utils/toTitleCase.html +8 -6
  229. package/docs/utils/type.html +2 -0
  230. package/docs/utils/wait.html +2 -0
  231. package/docs-src/components/aside.page.html +53 -111
  232. package/docs-src/components/html-editor.page.html +52 -6
  233. package/docs-src/components/nav-spacer.page.html +65 -0
  234. package/docs-src/index.page.html +6 -0
  235. package/docs-src/nav.fragment.html +2 -0
  236. package/docs-src/utils/formatTimestamp.page.html +6 -6
  237. package/docs-src/utils/toTitleCase.page.html +6 -6
  238. package/icons/comment.svg +1 -0
  239. package/llm.txt +1 -0
  240. package/package.json +3 -3
  241. package/scripts/build.js +1 -1
  242. package/src/components/Accordion.js +1 -1
  243. package/src/components/Aside.js +12 -1
  244. package/src/components/Dropdown.js +7 -3
  245. package/src/components/HtmlEditor.js +699 -1206
  246. package/src/components/Main.js +1 -1
  247. package/src/components/NavSpacer.js +55 -0
  248. package/src/components/htmlEditorControls/AlignCenter.js +5 -5
  249. package/src/components/htmlEditorControls/AlignJustify.js +5 -5
  250. package/src/components/htmlEditorControls/AlignLeft.js +5 -5
  251. package/src/components/htmlEditorControls/AlignRight.js +5 -5
  252. package/src/components/htmlEditorControls/Bold.js +5 -5
  253. package/src/components/htmlEditorControls/BulletList.js +5 -5
  254. package/src/components/htmlEditorControls/ClearFormatting.js +5 -5
  255. package/src/components/htmlEditorControls/CodeBlock.js +16 -8
  256. package/src/components/htmlEditorControls/CreateLink.js +3 -7
  257. package/src/components/htmlEditorControls/FormatBlock.js +5 -5
  258. package/src/components/htmlEditorControls/InlineCode.js +1 -13
  259. package/src/components/htmlEditorControls/InsertTable.js +43 -44
  260. package/src/components/htmlEditorControls/Italic.js +5 -5
  261. package/src/components/htmlEditorControls/Mode.js +1 -1
  262. package/src/components/htmlEditorControls/NumberList.js +5 -5
  263. package/src/components/htmlEditorControls/Strikethrough.js +5 -5
  264. package/src/components/htmlEditorControls/Underline.js +5 -5
  265. package/src/components/htmlEditorNodes/HtmlComment.js +118 -0
  266. package/tests/components/Accordion.browser-test.js +154 -0
  267. package/tests/components/Aside.browser-test.js +75 -0
  268. package/tests/components/Dropdown.browser-test.js +85 -0
  269. package/tests/components/HtmlComment.browser-test.js +115 -0
  270. package/tests/components/HtmlEditor.browser-test.js +379 -0
  271. package/tests/components/NavSpacer.browser-test.js +81 -0
@@ -466,5 +466,159 @@ export default {
466
466
 
467
467
  cleanup(container);
468
468
  pass('AccordionPanel active attribute reflects state');
469
+ },
470
+
471
+ /*
472
+ Persistent ID Tests
473
+ */
474
+ 'persistent-id: should save open panel state to localStorage': async ({pass, fail}) => {
475
+ const id = 'test-save-' + Date.now();
476
+ const key = `accordion-persistent-id-${id}`;
477
+ window.localStorage.removeItem(key);
478
+
479
+ const { container, accordion } = await createAccordion({ persistentId: id });
480
+
481
+ accordion.openPanel('panel1');
482
+ await accordion.updateComplete;
483
+ await new Promise(r => setTimeout(r, 300));
484
+
485
+ const stored = window.localStorage.getItem(key);
486
+ window.localStorage.removeItem(key);
487
+ cleanup(container);
488
+
489
+ if(stored !== 'panel1'){
490
+ fail(`Expected localStorage to contain "panel1", got "${stored}"`);
491
+ return;
492
+ }
493
+
494
+ pass('Open panel state saved to localStorage');
495
+ },
496
+
497
+ 'persistent-id: should restore open panel state from localStorage': async ({pass, fail}) => {
498
+ const id = 'test-restore-' + Date.now();
499
+ const key = `accordion-persistent-id-${id}`;
500
+ window.localStorage.setItem(key, 'panel2');
501
+
502
+ const { container, accordion } = await createAccordion({ persistentId: id });
503
+ await new Promise(r => setTimeout(r, 50));
504
+
505
+ const panel1 = accordion.getPanel('panel1');
506
+ const panel2 = accordion.getPanel('panel2');
507
+ const panel3 = accordion.getPanel('panel3');
508
+
509
+ window.localStorage.removeItem(key);
510
+ cleanup(container);
511
+
512
+ if(panel1.active){
513
+ fail('panel1 should not be active');
514
+ return;
515
+ }
516
+ if(!panel2.active){
517
+ fail('panel2 should be active (restored from localStorage)');
518
+ return;
519
+ }
520
+ if(panel3.active){
521
+ fail('panel3 should not be active');
522
+ return;
523
+ }
524
+
525
+ pass('Open panel state restored from localStorage');
526
+ },
527
+
528
+ 'persistent-id: should restore all-panels-closed state from localStorage': async ({pass, fail}) => {
529
+ const id = 'test-allclosed-' + Date.now();
530
+ const key = `accordion-persistent-id-${id}`;
531
+ window.localStorage.setItem(key, '');
532
+
533
+ const container = document.createElement('div');
534
+ container.innerHTML = `
535
+ <k-accordion persistent-id="${id}">
536
+ <k-accordion-header for-panel="p1">H1</k-accordion-header>
537
+ <k-accordion-panel name="p1" active>Content 1</k-accordion-panel>
538
+ <k-accordion-header for-panel="p2">H2</k-accordion-header>
539
+ <k-accordion-panel name="p2">Content 2</k-accordion-panel>
540
+ </k-accordion>
541
+ `;
542
+ document.body.appendChild(container);
543
+
544
+ const accordion = container.querySelector('k-accordion');
545
+ await accordion.updateComplete;
546
+ await new Promise(r => setTimeout(r, 50));
547
+
548
+ const p1 = accordion.getPanel('p1');
549
+ const p2 = accordion.getPanel('p2');
550
+
551
+ window.localStorage.removeItem(key);
552
+ cleanup(container);
553
+
554
+ if(p1.active){
555
+ fail('panel p1 should be closed (all-closed state restored), but it is active');
556
+ return;
557
+ }
558
+ if(p2.active){
559
+ fail('panel p2 should be closed');
560
+ return;
561
+ }
562
+
563
+ pass('All-panels-closed state correctly restored from localStorage');
564
+ },
565
+
566
+ 'persistent-id: should not restore when no localStorage entry exists': async ({pass, fail}) => {
567
+ const id = 'test-noentry-' + Date.now();
568
+ const key = `accordion-persistent-id-${id}`;
569
+ window.localStorage.removeItem(key);
570
+
571
+ const container = document.createElement('div');
572
+ container.innerHTML = `
573
+ <k-accordion persistent-id="${id}">
574
+ <k-accordion-header for-panel="p1">H1</k-accordion-header>
575
+ <k-accordion-panel name="p1" active>Content 1</k-accordion-panel>
576
+ <k-accordion-header for-panel="p2">H2</k-accordion-header>
577
+ <k-accordion-panel name="p2">Content 2</k-accordion-panel>
578
+ </k-accordion>
579
+ `;
580
+ document.body.appendChild(container);
581
+
582
+ const accordion = container.querySelector('k-accordion');
583
+ await accordion.updateComplete;
584
+ await new Promise(r => setTimeout(r, 50));
585
+
586
+ const p1 = accordion.getPanel('p1');
587
+
588
+ cleanup(container);
589
+
590
+ if(!p1.active){
591
+ fail('panel p1 should remain active when no localStorage entry exists');
592
+ return;
593
+ }
594
+
595
+ pass('HTML active attributes preserved when no localStorage entry exists');
596
+ },
597
+
598
+ 'persistent-id: should save all-closed state to localStorage': async ({pass, fail}) => {
599
+ const id = 'test-saveclosed-' + Date.now();
600
+ const key = `accordion-persistent-id-${id}`;
601
+ window.localStorage.removeItem(key);
602
+
603
+ const { container, accordion } = await createAccordion({ persistentId: id });
604
+
605
+ accordion.openPanel('panel1');
606
+ await accordion.updateComplete;
607
+ await new Promise(r => setTimeout(r, 300));
608
+
609
+ accordion.closePanel('panel1');
610
+ await accordion.updateComplete;
611
+ await new Promise(r => setTimeout(r, 300));
612
+
613
+ const stored = window.localStorage.getItem(key);
614
+ window.localStorage.removeItem(key);
615
+ cleanup(container);
616
+
617
+ if(stored !== ''){
618
+ fail(`Expected localStorage to contain "" (empty string), got ${JSON.stringify(stored)}`);
619
+ return;
620
+ }
621
+
622
+ pass('All-closed state saved as empty string in localStorage');
469
623
  }
470
624
  };
@@ -452,6 +452,81 @@ export default {
452
452
  pass('Dispatches offscreen event on disconnect');
453
453
  },
454
454
 
455
+ /*
456
+ Persistent ID
457
+ */
458
+ 'persistentId: should default to null': async ({pass, fail}) => {
459
+ const aside = await createAside();
460
+ if(aside.persistentId !== null) {
461
+ cleanup(aside);
462
+ return fail(`Expected persistentId null, got ${aside.persistentId}`);
463
+ }
464
+ cleanup(aside);
465
+ pass('persistentId defaults to null');
466
+ },
467
+
468
+ 'persistentId: should save state to localStorage on state change': async ({pass, fail}) => {
469
+ const id = `test-aside-save-${Date.now()}`;
470
+ const key = `aside-persistent-id-${id}`;
471
+ localStorage.removeItem(key);
472
+ const aside = await createAside({ main: 'push', state: 'offscreen' });
473
+ aside.persistentId = id;
474
+ await aside.updateComplete;
475
+ aside.expand();
476
+ await aside.updateComplete;
477
+ const saved = localStorage.getItem(key);
478
+ localStorage.removeItem(key);
479
+ cleanup(aside);
480
+ if(saved !== 'expanded') return fail(`Expected "expanded" in localStorage, got "${saved}"`);
481
+ pass('persistentId saves state to localStorage');
482
+ },
483
+
484
+ 'persistentId: should restore state from localStorage on connect': async ({pass, fail}) => {
485
+ const id = `test-aside-restore-${Date.now()}`;
486
+ const key = `aside-persistent-id-${id}`;
487
+ localStorage.setItem(key, 'expanded');
488
+ const aside = document.createElement('k-aside');
489
+ aside.setAttribute('main', 'push');
490
+ aside.setAttribute('state', 'offscreen');
491
+ aside.setAttribute('persistent-id', id);
492
+ document.body.appendChild(aside);
493
+ await aside.updateComplete;
494
+ // Wait for the property change to apply
495
+ await new Promise(r => setTimeout(r, 50));
496
+ const state = aside.state;
497
+ localStorage.removeItem(key);
498
+ cleanup(aside);
499
+ if(state !== 'expanded') return fail(`Expected restored state "expanded", got "${state}"`);
500
+ pass('persistentId restores state from localStorage');
501
+ },
502
+
503
+ 'persistentId: should save collapsed state': async ({pass, fail}) => {
504
+ const id = `test-aside-collapsed-${Date.now()}`;
505
+ const key = `aside-persistent-id-${id}`;
506
+ localStorage.removeItem(key);
507
+ const aside = await createAside({ main: 'push', state: 'offscreen' });
508
+ aside.persistentId = id;
509
+ await aside.updateComplete;
510
+ aside.collapse();
511
+ await aside.updateComplete;
512
+ const saved = localStorage.getItem(key);
513
+ localStorage.removeItem(key);
514
+ cleanup(aside);
515
+ if(saved !== 'collapsed') return fail(`Expected "collapsed" in localStorage, got "${saved}"`);
516
+ pass('persistentId saves collapsed state');
517
+ },
518
+
519
+ 'persistentId: should not save state when not set': async ({pass, fail}) => {
520
+ const aside = await createAside({ main: 'push', state: 'offscreen' });
521
+ const keysBefore = Object.keys(localStorage).filter(k => k.startsWith('aside-persistent-id-'));
522
+ aside.expand();
523
+ await aside.updateComplete;
524
+ const keysAfter = Object.keys(localStorage).filter(k => k.startsWith('aside-persistent-id-'));
525
+ cleanup(aside);
526
+ if(keysAfter.length > keysBefore.length) return fail('Should not save to localStorage when persistentId is null');
527
+ pass('Does not save to localStorage without persistentId');
528
+ },
529
+
455
530
  /*
456
531
  AsideItem
457
532
  */
@@ -576,5 +576,90 @@ export default {
576
576
  }
577
577
  cleanup(container);
578
578
  pass('Disabled items skipped in navigation');
579
+ },
580
+
581
+ /*
582
+ Shadow DOM Compatibility (composedPath)
583
+ */
584
+ 'should close when clicking outside via composedPath': async ({pass, fail}) => {
585
+ const { container, dropdown } = await createDropdown();
586
+ dropdown.open();
587
+ await dropdown.updateComplete;
588
+ const outside = document.createElement('div');
589
+ outside.textContent = 'outside';
590
+ document.body.appendChild(outside);
591
+ outside.click();
592
+ await dropdown.updateComplete;
593
+ if(dropdown.opened) {
594
+ cleanup(container);
595
+ outside.remove();
596
+ return fail('Dropdown should close on outside click');
597
+ }
598
+ cleanup(container);
599
+ outside.remove();
600
+ pass('Dropdown closes on outside click via composedPath');
601
+ },
602
+
603
+ 'should not close when clicking inside menu with close-on-select false': async ({pass, fail}) => {
604
+ const { container, dropdown } = await createDropdown({ closeOnSelect: false });
605
+ dropdown.open();
606
+ await dropdown.updateComplete;
607
+ const item = dropdown.querySelector('[data-value="item1"]');
608
+ item.click();
609
+ await dropdown.updateComplete;
610
+ if(!dropdown.opened) {
611
+ cleanup(container);
612
+ return fail('Dropdown should stay open with close-on-select="false"');
613
+ }
614
+ cleanup(container);
615
+ pass('Dropdown stays open with close-on-select="false"');
616
+ },
617
+
618
+ /*
619
+ Host Styles
620
+ */
621
+ 'host should have white-space normal': async ({pass, fail}) => {
622
+ const { container, dropdown } = await createDropdown();
623
+ const style = getComputedStyle(dropdown);
624
+ if(style.whiteSpace !== 'normal') {
625
+ cleanup(container);
626
+ return fail(`Expected white-space: normal, got ${style.whiteSpace}`);
627
+ }
628
+ cleanup(container);
629
+ pass('Host has white-space: normal');
630
+ },
631
+
632
+ 'trigger should have inline-flex display': async ({pass, fail}) => {
633
+ const { container, dropdown } = await createDropdown();
634
+ await dropdown.updateComplete;
635
+ const trigger = dropdown.shadowRoot.querySelector('#trigger');
636
+ const style = getComputedStyle(trigger);
637
+ if(style.display !== 'inline-flex') {
638
+ cleanup(container);
639
+ return fail(`Expected trigger display: inline-flex, got ${style.display}`);
640
+ }
641
+ cleanup(container);
642
+ pass('Trigger has inline-flex display');
643
+ },
644
+
645
+ 'dropdown should not inflate height in pre-wrap context': async ({pass, fail}) => {
646
+ const container = document.createElement('div');
647
+ container.style.whiteSpace = 'pre-wrap';
648
+ container.innerHTML = `
649
+ <k-dropdown>
650
+ <button slot="trigger">T</button>
651
+ <button>Item</button>
652
+ </k-dropdown>
653
+ `;
654
+ document.body.appendChild(container);
655
+ const dropdown = container.querySelector('k-dropdown');
656
+ await dropdown.updateComplete;
657
+ const height = dropdown.getBoundingClientRect().height;
658
+ if(height > 60) {
659
+ cleanup(container);
660
+ return fail(`Dropdown height should not inflate in pre-wrap context, got ${height}px`);
661
+ }
662
+ cleanup(container);
663
+ pass('Dropdown does not inflate in pre-wrap context');
579
664
  }
580
665
  };
@@ -0,0 +1,115 @@
1
+ import '../../../src/components/htmlEditorNodes/HtmlComment.js';
2
+
3
+ const HenHtmlComment = customElements.get('k-hen-html-comment');
4
+
5
+ export default {
6
+ /*
7
+ Custom Element Registration
8
+ */
9
+ 'should register k-hen-html-comment element': async ({pass, fail}) => {
10
+ if(!HenHtmlComment) return fail('k-hen-html-comment should be registered');
11
+ pass('k-hen-html-comment is registered');
12
+ },
13
+
14
+ 'should have static lexicalNode property': async ({pass, fail}) => {
15
+ if(!HenHtmlComment.lexicalNode) return fail('HenHtmlComment should have static lexicalNode');
16
+ if(HenHtmlComment.lexicalNode.getType() !== 'html-comment') return fail(`Expected type 'html-comment', got '${HenHtmlComment.lexicalNode.getType()}'`);
17
+ pass('lexicalNode property exists with correct type');
18
+ },
19
+
20
+ /*
21
+ isVisualCompatible
22
+ */
23
+ 'isVisualCompatible should return true for comment nodes': async ({pass, fail}) => {
24
+ const commentNode = document.createComment('test');
25
+ if(!HenHtmlComment.lexicalNode.isVisualCompatible(commentNode)) return fail('Should return true for comment nodes');
26
+ pass('Returns true for comment nodes');
27
+ },
28
+
29
+ 'isVisualCompatible should return false for element nodes': async ({pass, fail}) => {
30
+ const el = document.createElement('div');
31
+ if(HenHtmlComment.lexicalNode.isVisualCompatible(el)) return fail('Should return false for element nodes');
32
+ pass('Returns false for element nodes');
33
+ },
34
+
35
+ 'isVisualCompatible should return false for text nodes': async ({pass, fail}) => {
36
+ const textNode = document.createTextNode('hello');
37
+ if(HenHtmlComment.lexicalNode.isVisualCompatible(textNode)) return fail('Should return false for text nodes');
38
+ pass('Returns false for text nodes');
39
+ },
40
+
41
+ /*
42
+ preprocessHtml
43
+ */
44
+ 'preprocessHtml should convert comments to spans': async ({pass, fail}) => {
45
+ const result = HenHtmlComment.lexicalNode.preprocessHtml('<p>text <!-- hello --> more</p>');
46
+ if(result.includes('<!--')) return fail('Comment should be replaced');
47
+ if(!result.includes('data-html-comment=')) return fail(`Expected data-html-comment attribute, got '${result}'`);
48
+ if(!result.includes('<span')) return fail('Should convert to span element');
49
+ pass('Converts comments to spans');
50
+ },
51
+
52
+ 'preprocessHtml should handle multiple comments': async ({pass, fail}) => {
53
+ const result = HenHtmlComment.lexicalNode.preprocessHtml('<!-- a --><!-- b -->');
54
+ const count = (result.match(/data-html-comment/g) || []).length;
55
+ if(count !== 2) return fail(`Expected 2 spans, got ${count}`);
56
+ pass('Handles multiple comments');
57
+ },
58
+
59
+ 'preprocessHtml should handle multiline comments': async ({pass, fail}) => {
60
+ const result = HenHtmlComment.lexicalNode.preprocessHtml('<!-- line1\nline2 -->');
61
+ if(result.includes('<!--')) return fail('Multiline comment should be replaced');
62
+ if(!result.includes('data-html-comment')) return fail('Should produce data-html-comment attribute');
63
+ pass('Handles multiline comments');
64
+ },
65
+
66
+ 'preprocessHtml should leave non-comment HTML untouched': async ({pass, fail}) => {
67
+ const html = '<p>No comments here</p>';
68
+ const result = HenHtmlComment.lexicalNode.preprocessHtml(html);
69
+ if(result !== html) return fail(`Should not modify non-comment HTML, got '${result}'`);
70
+ pass('Leaves non-comment HTML untouched');
71
+ },
72
+
73
+ 'preprocessHtml should escape quotes in comment text': async ({pass, fail}) => {
74
+ const result = HenHtmlComment.lexicalNode.preprocessHtml('<!-- say "hello" -->');
75
+ if(result.includes('<!--')) return fail('Comment should be replaced');
76
+ if(!result.includes('&quot;')) return fail('Quotes should be escaped in attribute');
77
+ pass('Escapes quotes in comment text');
78
+ },
79
+
80
+ /*
81
+ importDOM
82
+ */
83
+ 'importDOM should return span handler': async ({pass, fail}) => {
84
+ const domConfig = HenHtmlComment.lexicalNode.importDOM();
85
+ if(!domConfig || !domConfig.span) return fail('importDOM should return config with span key');
86
+ pass('importDOM returns span handler');
87
+ },
88
+
89
+ 'importDOM span handler should return null for non-comment spans': async ({pass, fail}) => {
90
+ const domConfig = HenHtmlComment.lexicalNode.importDOM();
91
+ const span = document.createElement('span');
92
+ const result = domConfig.span(span);
93
+ if(result !== null) return fail('Should return null for spans without data-html-comment');
94
+ pass('Returns null for non-comment spans');
95
+ },
96
+
97
+ 'importDOM span handler should return conversion for comment spans': async ({pass, fail}) => {
98
+ const domConfig = HenHtmlComment.lexicalNode.importDOM();
99
+ const span = document.createElement('span');
100
+ span.setAttribute('data-html-comment', 'test comment');
101
+ const result = domConfig.span(span);
102
+ if(!result) return fail('Should return conversion for comment spans');
103
+ if(!result.conversion) return fail('Result should have conversion function');
104
+ if(result.priority !== 2) return fail(`Expected priority 2, got ${result.priority}`);
105
+ pass('Returns conversion for comment spans');
106
+ },
107
+
108
+ /*
109
+ Lexical Node (static only - instance methods require active Lexical editor)
110
+ */
111
+ 'lexical node should have correct type': async ({pass, fail}) => {
112
+ if(HenHtmlComment.lexicalNode.getType() !== 'html-comment') return fail('Type should be html-comment');
113
+ pass('Type is html-comment');
114
+ }
115
+ };