kempo-ui 0.4.5 → 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 (170) 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/markdownEditorControls/Bold.js +11 -0
  8. package/dist/components/markdownEditorControls/BulletList.js +11 -0
  9. package/dist/components/markdownEditorControls/Code.js +11 -0
  10. package/dist/components/markdownEditorControls/FormatBlock.js +30 -0
  11. package/dist/components/markdownEditorControls/Heading.js +11 -0
  12. package/dist/components/markdownEditorControls/Image.js +93 -0
  13. package/dist/components/markdownEditorControls/Italic.js +11 -0
  14. package/dist/components/markdownEditorControls/Link.js +11 -0
  15. package/dist/components/markdownEditorControls/MarkdownEditorControl.js +39 -0
  16. package/dist/components/markdownEditorControls/Menu.js +30 -0
  17. package/dist/components/markdownEditorControls/NumberedList.js +11 -0
  18. package/dist/components/markdownEditorControls/Quote.js +11 -0
  19. package/dist/components/markdownEditorControls/SpeechToText.js +16 -0
  20. package/dist/components/markdownEditorControls/Strikethrough.js +11 -0
  21. package/dist/components/markdownEditorControls/Table.js +80 -0
  22. package/dist/utils/marked.esm.js +1 -0
  23. package/dist/utils/renderMarkdown.js +1 -0
  24. package/dist/utils/sanitizeHtml.js +1 -0
  25. package/docs/components/accordion.html +12 -0
  26. package/docs/components/aside.html +12 -0
  27. package/docs/components/calendar.html +12 -0
  28. package/docs/components/card.html +12 -0
  29. package/docs/components/chat.html +715 -0
  30. package/docs/components/code-editor.html +48 -0
  31. package/docs/components/color-picker.html +12 -0
  32. package/docs/components/combobox.html +12 -0
  33. package/docs/components/content-slider.html +12 -0
  34. package/docs/components/context.html +12 -0
  35. package/docs/components/dialog.html +12 -0
  36. package/docs/components/dropdown.html +12 -0
  37. package/docs/components/filter-list.html +12 -0
  38. package/docs/components/focus-capture.html +12 -0
  39. package/docs/components/html-editor.html +53 -0
  40. package/docs/components/hybrid-component.html +12 -0
  41. package/docs/components/icon.html +12 -0
  42. package/docs/components/import.html +12 -0
  43. package/docs/components/light-component.html +12 -0
  44. package/docs/components/markdown-editor.html +924 -0
  45. package/docs/components/nav-spacer.html +12 -0
  46. package/docs/components/nav.html +12 -0
  47. package/docs/components/photo-viewer.html +12 -0
  48. package/docs/components/progress.html +12 -0
  49. package/docs/components/resize.html +15 -0
  50. package/docs/components/rich-textarea.html +650 -0
  51. package/docs/components/shadow-component.html +12 -0
  52. package/docs/components/show-more.html +12 -0
  53. package/docs/components/slider.html +12 -0
  54. package/docs/components/sortable.html +12 -0
  55. package/docs/components/speech-to-text.html +12 -0
  56. package/docs/components/spinner.html +12 -0
  57. package/docs/components/split.html +12 -0
  58. package/docs/components/table.html +12 -0
  59. package/docs/components/tableControls.html +12 -0
  60. package/docs/components/tableCustomFields.html +12 -0
  61. package/docs/components/tableFetchRecords.html +12 -0
  62. package/docs/components/tableFieldSortHide.html +12 -0
  63. package/docs/components/tablePagination.html +12 -0
  64. package/docs/components/tablePlaceholder.html +12 -0
  65. package/docs/components/tableRecordEditing.html +12 -0
  66. package/docs/components/tableRecordFiltering.html +12 -0
  67. package/docs/components/tableRecordHiding.html +12 -0
  68. package/docs/components/tableRecordSearching.html +12 -0
  69. package/docs/components/tableRecordSelection.html +12 -0
  70. package/docs/components/tableRowControls.html +12 -0
  71. package/docs/components/tableServerSync.html +12 -0
  72. package/docs/components/tableSorting.html +12 -0
  73. package/docs/components/tabs.html +12 -0
  74. package/docs/components/tags.html +12 -0
  75. package/docs/components/text-to-speech.html +12 -0
  76. package/docs/components/theme-select.html +12 -0
  77. package/docs/components/theme-switcher.html +12 -0
  78. package/docs/components/time.html +12 -0
  79. package/docs/components/timestamp.html +12 -0
  80. package/docs/components/toast.html +12 -0
  81. package/docs/components/toggle.html +12 -0
  82. package/docs/components/tree.html +12 -0
  83. package/docs/components/voice-selector.html +12 -0
  84. package/docs/icons/done_all.svg +1 -0
  85. package/docs/icons/format_quote.svg +1 -0
  86. package/docs/icons/format_strikethrough.svg +1 -0
  87. package/docs/icons/send.svg +1 -0
  88. package/docs/icons/text_fields.svg +1 -0
  89. package/docs/index.html +30 -0
  90. package/docs/src/components/Chat.js +201 -0
  91. package/docs/src/components/CodeEditor.js +21 -2
  92. package/docs/src/components/Dropdown.js +1 -1
  93. package/docs/src/components/HtmlEditor.js +42 -3
  94. package/docs/src/components/MarkdownEditor.js +173 -0
  95. package/docs/src/components/Resize.js +12 -2
  96. package/docs/src/components/markdownEditorControls/Bold.js +11 -0
  97. package/docs/src/components/markdownEditorControls/BulletList.js +11 -0
  98. package/docs/src/components/markdownEditorControls/Code.js +11 -0
  99. package/docs/src/components/markdownEditorControls/FormatBlock.js +30 -0
  100. package/docs/src/components/markdownEditorControls/Heading.js +11 -0
  101. package/docs/src/components/markdownEditorControls/Image.js +93 -0
  102. package/docs/src/components/markdownEditorControls/Italic.js +11 -0
  103. package/docs/src/components/markdownEditorControls/Link.js +11 -0
  104. package/docs/src/components/markdownEditorControls/MarkdownEditorControl.js +39 -0
  105. package/docs/src/components/markdownEditorControls/Menu.js +30 -0
  106. package/docs/src/components/markdownEditorControls/NumberedList.js +11 -0
  107. package/docs/src/components/markdownEditorControls/Quote.js +11 -0
  108. package/docs/src/components/markdownEditorControls/SpeechToText.js +16 -0
  109. package/docs/src/components/markdownEditorControls/Strikethrough.js +11 -0
  110. package/docs/src/components/markdownEditorControls/Table.js +80 -0
  111. package/docs/src/utils/marked.esm.js +1 -0
  112. package/docs/src/utils/renderMarkdown.js +1 -0
  113. package/docs/src/utils/sanitizeHtml.js +1 -0
  114. package/docs/utils/context.html +12 -0
  115. package/docs/utils/cookie.html +12 -0
  116. package/docs/utils/debounce.html +12 -0
  117. package/docs/utils/drag.html +12 -0
  118. package/docs/utils/elevation.html +16 -10
  119. package/docs/utils/formatTimestamp.html +12 -0
  120. package/docs/utils/object.html +12 -0
  121. package/docs/utils/propConverters.html +12 -0
  122. package/docs/utils/sanitizeHtml.html +555 -0
  123. package/docs/utils/string.html +12 -0
  124. package/docs/utils/theme.html +12 -0
  125. package/docs/utils/toTitleCase.html +12 -0
  126. package/docs/utils/type.html +12 -0
  127. package/docs/utils/voice.html +12 -0
  128. package/docs/utils/wait.html +12 -0
  129. package/docs-src/components/chat.page.html +230 -0
  130. package/docs-src/components/code-editor.page.html +36 -0
  131. package/docs-src/components/html-editor.page.html +41 -0
  132. package/docs-src/components/markdown-editor.page.html +439 -0
  133. package/docs-src/components/resize.page.html +3 -0
  134. package/docs-src/index.page.html +18 -0
  135. package/docs-src/nav.fragment.html +12 -0
  136. package/docs-src/utils/elevation.page.html +4 -10
  137. package/docs-src/utils/sanitizeHtml.page.html +73 -0
  138. package/icons/done_all.svg +1 -0
  139. package/icons/format_quote.svg +1 -0
  140. package/icons/format_strikethrough.svg +1 -0
  141. package/icons/send.svg +1 -0
  142. package/icons/text_fields.svg +1 -0
  143. package/llms.txt +5 -2
  144. package/package.json +3 -2
  145. package/src/components/Chat.js +435 -0
  146. package/src/components/CodeEditor.js +58 -2
  147. package/src/components/Dropdown.js +1 -1
  148. package/src/components/HtmlEditor.js +84 -4
  149. package/src/components/MarkdownEditor.js +654 -0
  150. package/src/components/Resize.js +17 -1
  151. package/src/components/markdownEditorControls/Bold.js +30 -0
  152. package/src/components/markdownEditorControls/BulletList.js +30 -0
  153. package/src/components/markdownEditorControls/Code.js +39 -0
  154. package/src/components/markdownEditorControls/FormatBlock.js +102 -0
  155. package/src/components/markdownEditorControls/Heading.js +45 -0
  156. package/src/components/markdownEditorControls/Image.js +250 -0
  157. package/src/components/markdownEditorControls/Italic.js +30 -0
  158. package/src/components/markdownEditorControls/Link.js +74 -0
  159. package/src/components/markdownEditorControls/MarkdownEditorControl.js +158 -0
  160. package/src/components/markdownEditorControls/Menu.js +86 -0
  161. package/src/components/markdownEditorControls/NumberedList.js +69 -0
  162. package/src/components/markdownEditorControls/Quote.js +30 -0
  163. package/src/components/markdownEditorControls/SpeechToText.js +65 -0
  164. package/src/components/markdownEditorControls/Strikethrough.js +35 -0
  165. package/src/components/markdownEditorControls/Table.js +164 -0
  166. package/src/utils/marked.esm.js +77 -0
  167. package/src/utils/renderMarkdown.js +29 -0
  168. package/src/utils/sanitizeHtml.js +137 -0
  169. package/tests/components/Chat.browser-test.js +540 -0
  170. package/tests/utils/sanitizeHtml.browser-test.js +179 -0
@@ -0,0 +1,173 @@
1
+ import{html as t,css as e,nothing as i}from"../lit-all.min.js";import s from"./ShadowComponent.js";import l from"../utils/renderMarkdown.js";import o,{STRIP_COMPLETELY as a}from"../utils/sanitizeHtml.js";import"./Resize.js";import"./Tabs.js";export default class n extends s{static formAssociated=!0;static properties={value:{type:String},name:{type:String,reflect:!0},placeholder:{type:String,reflect:!0},disabled:{type:Boolean,reflect:!0},required:{type:Boolean,reflect:!0},readonly:{type:Boolean,reflect:!0},mode:{type:String,reflect:!0},allowedTags:{type:String,reflect:!0,attribute:"allowed-tags"},disallowedTags:{type:String,reflect:!0,attribute:"disallowed-tags"},scriptsEnabled:{type:Boolean,reflect:!0,attribute:"scripts-enabled"},controls:{type:String,reflect:!0}};#t="";constructor(){super(),this.internals=this.attachInternals(),this.value="",this.name="",this.placeholder="",this.disabled=!1,this.required=!1,this.readonly=!1,this.mode="write",this.allowedTags="",this.disallowedTags="",this.scriptsEnabled=!1,this.controls="",this.#e=!1}#e=!1;async loadControls(){if(this.#e)return;this.#e=!0;const t=new URL("./markdownEditorControls/",import.meta.url).href;await Promise.all([import(`${t}Bold.js`),import(`${t}Italic.js`),import(`${t}Strikethrough.js`),import(`${t}Heading.js`),import(`${t}Code.js`),import(`${t}Link.js`),import(`${t}Image.js`),import(`${t}Table.js`),import(`${t}BulletList.js`),import(`${t}NumberedList.js`),import(`${t}Quote.js`),import(`${t}Menu.js`),import(`${t}FormatBlock.js`),import(`${t}SpeechToText.js`)]),this.requestUpdate()}connectedCallback(){super.connectedCallback(),this.hasAttribute("value")&&(this.#t=this.getAttribute("value")),!this.value&&this.#t&&(this.value=this.#t)}updated(t){super.updated(t),t.has("value")&&this.internals.setFormValue(this.value),t.has("controls")&&this.controls&&"none"!==this.controls&&this.loadControls(),this.#i()}formResetCallback(){this.value=this.#t,this.mode="write"}formStateRestoreCallback(t){"string"==typeof t&&(this.value=t)}formDisabledCallback(t){this.disabled=t}focus(){"write"!==this.mode&&(this.mode="write"),this.updateComplete.then(()=>{this.shadowRoot?.querySelector("textarea")?.focus()})}blur(){this.shadowRoot?.querySelector("textarea")?.blur()}clear(){this.value=""}setMode(t){"write"!==t&&"preview"!==t||(this.mode=t)}togglePreview(){this.mode="write"===this.mode?"preview":"write"}get textarea(){return this.shadowRoot?.querySelector("textarea")||null}getSelection(){const t=this.textarea;return t?{start:t.selectionStart,end:t.selectionEnd,text:t.value.substring(t.selectionStart,t.selectionEnd)}:{start:0,end:0,text:""}}replaceSelection(t,{selectInserted:e=!0}={}){const i=this.textarea;i&&("write"!==this.mode&&(this.mode="write"),this.updateComplete.then(()=>{i.focus();const{selectionStart:s,selectionEnd:l}=i,o=i.value.substring(0,s),a=i.value.substring(l),n=o+t+a;i.value=n;const r=s+t.length;i.selectionStart=e?s:r,i.selectionEnd=r,this.value=n,i.dispatchEvent(new Event("input",{bubbles:!0}))}))}wrapSelection(t,e=t,i=""){const s=this.textarea;s?("write"!==this.mode&&(this.mode="write"),this.updateComplete.then(()=>{s.focus();const{selectionStart:l,selectionEnd:o,value:a}=s,n=a.substring(l,o);if(n.length>=t.length+e.length&&n.startsWith(t)&&n.endsWith(e)){const i=n.slice(t.length,n.length-e.length),r=a.substring(0,l)+i+a.substring(o);return s.value=r,s.selectionStart=l,s.selectionEnd=l+i.length,this.value=r,void s.dispatchEvent(new Event("input",{bubbles:!0}))}const r=a.substring(Math.max(0,l-t.length),l),d=a.substring(o,Math.min(a.length,o+e.length));if(r===t&&d===e){const i=a.substring(0,l-t.length)+n+a.substring(o+e.length);return s.value=i,s.selectionStart=l-t.length,s.selectionEnd=s.selectionStart+n.length,this.value=i,void s.dispatchEvent(new Event("input",{bubbles:!0}))}const m=n||i,c=a.substring(0,l)+t+m+e+a.substring(o);s.value=c;const h=l+t.length;s.selectionStart=h,s.selectionEnd=h+m.length,this.value=c,s.dispatchEvent(new Event("input",{bubbles:!0}))})):this.replaceSelection(t+i+e)}insertAtCursor(t){this.replaceSelection(t,{selectInserted:!1})}replaceInSelectedLines(t,e=""){const i=this.textarea;i&&("write"!==this.mode&&(this.mode="write"),this.updateComplete.then(()=>{i.focus();const{selectionStart:s,selectionEnd:l,value:o}=i,a=o.lastIndexOf("\n",s-1)+1,n=o.indexOf("\n",l),r=-1===n?o.length:n,d=o.substring(a,r),m=d.split("\n").map(i=>i.replace(t,e)).join("\n");if(m===d)return;const c=o.substring(0,a)+m+o.substring(r);i.value=c,i.selectionStart=a,i.selectionEnd=a+m.length,this.value=c,i.dispatchEvent(new Event("input",{bubbles:!0}))}))}insertLinePrefix(t,e=null){const i=this.textarea;i&&("write"!==this.mode&&(this.mode="write"),this.updateComplete.then(()=>{i.focus();const{selectionStart:s,selectionEnd:l,value:o}=i,a=o.lastIndexOf("\n",s-1)+1,n=o.indexOf("\n",l),r=-1===n?o.length:n,d=o.substring(a,r).split("\n"),m=d.filter(t=>t.length>0),c=m.length>0&&m.every(e=>e.startsWith(t)),h=d.map(i=>0===i.length?i:c?i.startsWith(t)?i.slice(t.length):i:i.startsWith(t)?i:e&&e.test(i)?i.replace(e,t):t+i).join("\n"),u=o.substring(0,a)+h+o.substring(r);i.value=u,i.selectionStart=a,i.selectionEnd=a+h.length,this.value=u,i.dispatchEvent(new Event("input",{bubbles:!0}))}))}get isEmpty(){return!(this.value||"").trim()}get renderedHtml(){const t={},e=this.#s;if(e&&(t.allowedTags=e),this.scriptsEnabled){const e=new Set(a);e.delete("SCRIPT"),t.stripCompletely=e}return o(l(this.value||""),t)}get#s(){const t=(this.allowedTags||"").trim(),e=(this.disallowedTags||"").trim();if(t&&e&&console.warn("[k-markdown-editor] `allowed-tags` and `disallowed-tags` are mutually exclusive; using `allowed-tags`."),t)return"*"===t?{has:()=>!0}:new Set(t.split(",").map(t=>t.trim().toUpperCase()).filter(Boolean));if(e){const t=new Set(e.split(",").map(t=>t.trim().toUpperCase()).filter(Boolean));return{has:e=>!t.has(e)}}return null}#i=()=>{const t=this.shadowRoot?.querySelector("textarea");this.required&&this.isEmpty?this.internals.setValidity({valueMissing:!0},"Please fill out this field.",t):this.internals.setValidity({})};handleInput=t=>{this.value=t.target.value,this.dispatchEvent(new CustomEvent("input",{detail:{value:this.value},bubbles:!0}))};handleChange=()=>{this.dispatchEvent(new CustomEvent("change",{detail:{value:this.value},bubbles:!0}))};handleTabChange=t=>{const e=t.detail?.tab;e&&e!==this.mode&&(this.mode=e,this.dispatchEvent(new CustomEvent("mode-change",{detail:{mode:e},bubbles:!0})))};render(){const e=this.constructor.controlSets[this.controls]??this.constructor.controlSets[""];return t`
2
+ <k-resize dimension="height" ?disabled=${this.disabled}>
3
+ <div class="frame">
4
+ <k-tabs fixed-height active=${this.mode} @tab=${this.handleTabChange}>
5
+ <k-tab slot="tabs" for="write">Write</k-tab>
6
+ <k-tab slot="tabs" for="preview">Preview</k-tab>
7
+ <k-tab-spacer slot="tabs"></k-tab-spacer>
8
+ <div slot="tabs" class="controls-top">
9
+ <slot name="controls-top">${e.top??i}</slot>
10
+ </div>
11
+ <k-tab-content name="write">
12
+ <textarea
13
+ class="editor"
14
+ .value=${this.value}
15
+ placeholder=${this.placeholder}
16
+ ?disabled=${this.disabled}
17
+ ?readonly=${this.readonly}
18
+ aria-label=${this.name||this.placeholder}
19
+ @input=${this.handleInput}
20
+ @blur=${this.handleChange}
21
+ ></textarea>
22
+ </k-tab-content>
23
+ <k-tab-content name="preview">
24
+ <div
25
+ class="preview"
26
+ role="article"
27
+ .innerHTML=${this.isEmpty?'<p class="preview-empty">Nothing to preview</p>':this.renderedHtml}
28
+ ></div>
29
+ </k-tab-content>
30
+ </k-tabs>
31
+ <div class="footer">
32
+ <slot name="controls-bottom">${e.bottom??i}</slot>
33
+ </div>
34
+ </div>
35
+ </k-resize>
36
+ `}static styles=e`
37
+ :host {
38
+ --padding: 0.5rem 0.75rem;
39
+ display: block;
40
+ }
41
+ :host([disabled]) {
42
+ opacity: 0.6;
43
+ }
44
+ :host([disabled]) k-tabs {
45
+ pointer-events: none;
46
+ }
47
+ /* readonly: textarea is read-only via the native attribute, but the
48
+ toolbar buttons mutate value programmatically, bypassing it. Mute
49
+ the controls-top wrapper and the footer so their buttons can't
50
+ fire. The Write/Preview tabs themselves stay interactive. */
51
+ :host([readonly]) .controls-top,
52
+ :host([readonly]) .footer {
53
+ pointer-events: none;
54
+ opacity: 0.5;
55
+ }
56
+ .controls-top {
57
+ display: inline-flex;
58
+ align-items: center;
59
+ flex-wrap: wrap;
60
+ }
61
+ k-resize {
62
+ width: 100%;
63
+ height: var(--height, 14rem);
64
+ background: var(--c_bg);
65
+ }
66
+ .frame {
67
+ display: flex;
68
+ flex-direction: column;
69
+ height: 100%;
70
+ width: 100%;
71
+ min-height: 0;
72
+ box-sizing: border-box;
73
+ }
74
+ k-tabs {
75
+ flex: 1 1 auto;
76
+ min-height: 0;
77
+ display: flex;
78
+ flex-direction: column;
79
+ }
80
+ k-tab-content {
81
+ --spacer: 0;
82
+ }
83
+ k-tab-content[active] {
84
+ display: flex;
85
+ flex-direction: column;
86
+ padding: var(--spacer_q);
87
+ }
88
+ k-tab-content:not([active]) {
89
+ display: none;
90
+ }
91
+ .editor,
92
+ .preview {
93
+ box-sizing: border-box;
94
+ flex: 1 1 0;
95
+ min-height: 0;
96
+ width: 100%;
97
+ padding: var(--padding);
98
+ background: transparent;
99
+ color: var(--tc);
100
+ font: inherit;
101
+ line-height: 1.5;
102
+ overflow-y: auto;
103
+ scrollbar-width: thin;
104
+ resize: none;
105
+ }
106
+ .preview > :first-child { margin-top: 0; }
107
+ .preview > :last-child { margin-bottom: 0; }
108
+ .preview-empty {
109
+ color: var(--tc_muted);
110
+ font-style: italic;
111
+ }
112
+ .footer {
113
+ display: flex;
114
+ align-items: center;
115
+ gap: 0.25rem;
116
+ padding: 0.25rem 0.5rem;
117
+ flex: 0 0 auto;
118
+ border-top: 1px solid var(--c_border);
119
+ box-sizing: border-box;
120
+ }
121
+ .footer:not(:has(::slotted(*))) {
122
+ display: none;
123
+ }
124
+ `;static controlSets={"":{top:null,bottom:null},none:{top:null,bottom:null},minimal:{top:t`
125
+ <k-md-menu label="Heading">
126
+ <k-icon slot="trigger" name="text_fields"></k-icon>
127
+ <k-md-format-block tag="h1"></k-md-format-block>
128
+ <k-md-format-block tag="h3"></k-md-format-block>
129
+ <k-md-format-block tag="h5"></k-md-format-block>
130
+ </k-md-menu>
131
+ <k-md-bold></k-md-bold>
132
+ <k-md-italic></k-md-italic>
133
+ <k-md-bullet-list></k-md-bullet-list>
134
+ <k-md-numbered-list></k-md-numbered-list>
135
+ `,bottom:null},normal:{top:t`
136
+ <k-md-menu label="Heading">
137
+ <k-icon slot="trigger" name="text_fields"></k-icon>
138
+ <k-md-format-block tag="h1"></k-md-format-block>
139
+ <k-md-format-block tag="h2"></k-md-format-block>
140
+ <k-md-format-block tag="h3"></k-md-format-block>
141
+ <k-md-format-block tag="h4"></k-md-format-block>
142
+ <k-md-format-block tag="h5"></k-md-format-block>
143
+ <k-md-format-block tag="h6"></k-md-format-block>
144
+ </k-md-menu>
145
+ <k-md-bold></k-md-bold>
146
+ <k-md-italic></k-md-italic>
147
+ <k-md-quote></k-md-quote>
148
+ <k-md-code></k-md-code>
149
+ <k-md-link></k-md-link>
150
+ <k-md-bullet-list></k-md-bullet-list>
151
+ <k-md-numbered-list></k-md-numbered-list>
152
+ `,bottom:null},full:{top:t`
153
+ <k-md-menu label="Heading">
154
+ <k-icon slot="trigger" name="text_fields"></k-icon>
155
+ <k-md-format-block tag="h1"></k-md-format-block>
156
+ <k-md-format-block tag="h2"></k-md-format-block>
157
+ <k-md-format-block tag="h3"></k-md-format-block>
158
+ <k-md-format-block tag="h4"></k-md-format-block>
159
+ <k-md-format-block tag="h5"></k-md-format-block>
160
+ <k-md-format-block tag="h6"></k-md-format-block>
161
+ </k-md-menu>
162
+ <k-md-bold></k-md-bold>
163
+ <k-md-italic></k-md-italic>
164
+ <k-md-strikethrough></k-md-strikethrough>
165
+ <k-md-quote></k-md-quote>
166
+ <k-md-code></k-md-code>
167
+ <k-md-link></k-md-link>
168
+ <k-md-image></k-md-image>
169
+ <k-md-table></k-md-table>
170
+ <k-md-bullet-list></k-md-bullet-list>
171
+ <k-md-numbered-list></k-md-numbered-list>
172
+ <k-md-speech-to-text></k-md-speech-to-text>
173
+ `,bottom:null}}}customElements.define("k-markdown-editor",n);
@@ -1,4 +1,4 @@
1
- import{LitElement as e,html as t,css as i}from"../lit-all.min.js";import r from"./ShadowComponent.js";import s from"./Icon.js";import n from"../utils/drag.js";export default class d extends r{static properties={resizing:{type:String,reflect:!0},dimention:{type:String,reflect:!0}};constructor(){super(),this.resizing="",this.dimension="",this.startSize={width:0,height:0},this.cleanupFuncs=[]}firstUpdated(){super.firstUpdated(),this.setupDragHandlers()}disconnectedCallback(){super.disconnectedCallback(),this.cleanupFuncs.forEach(e=>e())}dragStartHandler=({element:e})=>{const{width:t,height:i}=this.getBoundingClientRect();this.startSize.width=t,this.startSize.height=i,this.resizing=e.id};dragEndHandler=()=>{this.resizing=""};dragSideHandler=({x:e})=>{this.style.width=this.startSize.width+e+"px"};dragBottomHandler=({y:e})=>{this.style.height=this.startSize.height+e+"px"};dragCornerHandler=({x:e,y:t})=>{this.style.width=this.startSize.width+e+"px",this.style.height=this.startSize.height+t+"px"};setupDragHandlers(){const e=this.shadowRoot.getElementById("side"),t=this.shadowRoot.getElementById("bottom"),i=this.shadowRoot.getElementById("corner");e&&this.cleanupFuncs.push(n({element:e,startCallback:this.dragStartHandler,endCallback:this.dragEndHandler,callback:this.dragSideHandler,preventScroll:!0})),t&&this.cleanupFuncs.push(n({element:t,startCallback:this.dragStartHandler,endCallback:this.dragEndHandler,callback:this.dragBottomHandler,preventScroll:!0})),i&&this.cleanupFuncs.push(n({element:i,startCallback:this.dragStartHandler,endCallback:this.dragEndHandler,callback:this.dragCornerHandler,preventScroll:!0}))}static styles=i`
1
+ import{LitElement as e,html as t,css as i}from"../lit-all.min.js";import s from"./ShadowComponent.js";import r from"./Icon.js";import d from"../utils/drag.js";export default class n extends s{static properties={resizing:{type:String,reflect:!0},dimention:{type:String,reflect:!0},disabled:{type:Boolean,reflect:!0}};constructor(){super(),this.resizing="",this.dimension="",this.disabled=!1,this.startSize={width:0,height:0},this.cleanupFuncs=[]}firstUpdated(){super.firstUpdated(),this.setupDragHandlers()}disconnectedCallback(){super.disconnectedCallback(),this.cleanupFuncs.forEach(e=>e())}dragStartHandler=({element:e})=>{if(this.disabled)return;const{width:t,height:i}=this.getBoundingClientRect();this.startSize.width=t,this.startSize.height=i,this.resizing=e.id};dragEndHandler=()=>{this.resizing=""};dragSideHandler=({x:e})=>{this.disabled||(this.style.width=this.startSize.width+e+"px")};dragBottomHandler=({y:e})=>{this.disabled||(this.style.height=this.startSize.height+e+"px")};dragCornerHandler=({x:e,y:t})=>{this.disabled||(this.style.width=this.startSize.width+e+"px",this.style.height=this.startSize.height+t+"px")};setupDragHandlers(){const e=this.shadowRoot.getElementById("side"),t=this.shadowRoot.getElementById("bottom"),i=this.shadowRoot.getElementById("corner");e&&this.cleanupFuncs.push(d({element:e,startCallback:this.dragStartHandler,endCallback:this.dragEndHandler,callback:this.dragSideHandler,preventScroll:!0})),t&&this.cleanupFuncs.push(d({element:t,startCallback:this.dragStartHandler,endCallback:this.dragEndHandler,callback:this.dragBottomHandler,preventScroll:!0})),i&&this.cleanupFuncs.push(d({element:i,startCallback:this.dragStartHandler,endCallback:this.dragEndHandler,callback:this.dragCornerHandler,preventScroll:!0}))}static styles=i`
2
2
  :host {
3
3
  --handle_size: 1rem;
4
4
 
@@ -75,6 +75,16 @@ import{LitElement as e,html as t,css as i}from"../lit-all.min.js";import r from"
75
75
  :host([dimension="width"]) #corner {
76
76
  display: none;
77
77
  }
78
+ /* Disabled: handles can't capture pointer events, so a drag can't
79
+ start in the first place; visual cue: handles fade out and the
80
+ cursor reverts to default. */
81
+ :host([disabled]) #side,
82
+ :host([disabled]) #bottom,
83
+ :host([disabled]) #corner {
84
+ pointer-events: none;
85
+ cursor: default;
86
+ opacity: 0.4;
87
+ }
78
88
  `;render(){return t`
79
89
  <div id="main">
80
90
  <slot></slot>
@@ -94,4 +104,4 @@ import{LitElement as e,html as t,css as i}from"../lit-all.min.js";import r from"
94
104
  <path fill="currentColor" d="m12.735 3.0095e-4c-0.77288 0.011702-1.6098 0.36368-2.2773 1.0313l-9.4258 9.4258c-1.1868 1.1868-1.3772 2.908-0.42773 3.8574 0.94943 0.94943 2.6687 0.7571 3.8555-0.42969l9.4258-9.4258c1.1868-1.1868 1.3772-2.906 0.42774-3.8555-0.41537-0.41537-0.977-0.61262-1.5781-0.60352zm0 8.5684c-0.77288 0.011702-1.6098 0.36564-2.2773 1.0332l-0.85744 0.85546c-1.1868 1.1868-1.3772 2.908-0.42773 3.8574 0.94943 0.94943 2.6687 0.7571 3.8555-0.42969l0.85742-0.85742c1.1868-1.1868 1.3772-2.906 0.42773-3.8555-0.41537-0.41537-0.977-0.61262-1.5781-0.60352z" />
95
105
  </svg>
96
106
  </div>
97
- `}}customElements.define("k-resize",d);
107
+ `}}customElements.define("k-resize",n);
@@ -0,0 +1,11 @@
1
+ import t from"./MarkdownEditorControl.js";import{html as l}from"../../lit-all.min.js";import"../Icon.js";export default class o extends t{constructor(){super(),this.label="Bold (Cmd/Ctrl+B)"}command(){this.wrapSelection("**","**","bold text")}render(){return l`
2
+ <button
3
+ type="button"
4
+ class=${this.btnClass}
5
+ title=${this.label}
6
+ aria-label=${this.label}
7
+ @click=${this.handleClick}
8
+ >
9
+ <k-icon name="format_bold"></k-icon>
10
+ </button>
11
+ `}}customElements.define("k-md-bold",o);
@@ -0,0 +1,11 @@
1
+ import t from"./MarkdownEditorControl.js";import{html as l}from"../../lit-all.min.js";import"../Icon.js";export default class e extends t{constructor(){super(),this.label="Bulleted list"}command(){this.insertLinePrefix("- ")}render(){return l`
2
+ <button
3
+ type="button"
4
+ class=${this.btnClass}
5
+ title=${this.label}
6
+ aria-label=${this.label}
7
+ @click=${this.handleClick}
8
+ >
9
+ <k-icon name="format_list_bulleted"></k-icon>
10
+ </button>
11
+ `}}customElements.define("k-md-bullet-list",e);
@@ -0,0 +1,11 @@
1
+ import t from"./MarkdownEditorControl.js";import{html as e}from"../../lit-all.min.js";import"../Icon.js";export default class o extends t{constructor(){super(),this.label="Code"}command(){this.getSelection().text.includes("\n")?this.wrapSelection("```\n","\n```","code"):this.wrapSelection("`","`","code")}render(){return e`
2
+ <button
3
+ type="button"
4
+ class=${this.btnClass}
5
+ title=${this.label}
6
+ aria-label=${this.label}
7
+ @click=${this.handleClick}
8
+ >
9
+ <k-icon name="code"></k-icon>
10
+ </button>
11
+ `}}customElements.define("k-md-code",o);
@@ -0,0 +1,30 @@
1
+ import t from"./MarkdownEditorControl.js";import{html as e,css as s}from"../../lit-all.min.js";import"../Icon.js";const a={h1:"Heading 1",h2:"Heading 2",h3:"Heading 3",h4:"Heading 4",h5:"Heading 5",h6:"Heading 6"},i={h1:"format_h1",h2:"format_h2",h3:"format_h3",h4:"format_h4",h5:"format_h5",h6:"format_h6"};export default class o extends t{static properties={...t.properties,tag:{type:String,reflect:!0}};constructor(){super(),this.tag="h2",this.label=""}command(){const t=(this.tag||"h2").toLowerCase();if(/^h[1-6]$/.test(t)){const e=parseInt(t.slice(1),10);this.insertLinePrefix("#".repeat(e)+" ",/^#{1,6} /)}}get#t(){return this.label?this.label:a[(this.tag||"h2").toLowerCase()]||this.tag}get#e(){return i[(this.tag||"h2").toLowerCase()]||"format_h2"}render(){return e`
2
+ <button
3
+ type="button"
4
+ class=${this.btnClass}
5
+ title=${this.#t}
6
+ aria-label=${this.#t}
7
+ @click=${this.handleClick}
8
+ >
9
+ <k-icon name=${this.#e}></k-icon>
10
+ <span class="fb-label">${this.#t}</span>
11
+ </button>
12
+ `}static styles=[t.styles,s`
13
+ :host {
14
+ display: inline-flex;
15
+ }
16
+ .ctrl {
17
+ gap: 0.5rem;
18
+ justify-content: flex-start;
19
+ white-space: nowrap;
20
+ }
21
+ /* When this is slotted into something that stretches it (a menu),
22
+ the button fills the row instead of staying compact. */
23
+ :host([slot]) .ctrl,
24
+ :host(*) .ctrl {
25
+ width: 100%;
26
+ }
27
+ .fb-label {
28
+ font-size: 0.875rem;
29
+ }
30
+ `]}customElements.define("k-md-format-block",o);
@@ -0,0 +1,11 @@
1
+ import t from"./MarkdownEditorControl.js";import{html as e}from"../../lit-all.min.js";import"../Icon.js";export default class i extends t{static properties={...t.properties,level:{type:Number,reflect:!0}};constructor(){super(),this.level=2,this.label="Heading"}command(){const t=Math.min(6,Math.max(1,Number(this.level)||2));this.insertLinePrefix("#".repeat(t)+" ",/^#{1,6} /)}render(){const t=Math.min(6,Math.max(1,Number(this.level)||2));return e`
2
+ <button
3
+ type="button"
4
+ class=${this.btnClass}
5
+ title="${this.label} ${t}"
6
+ aria-label="${this.label} ${t}"
7
+ @click=${this.handleClick}
8
+ >
9
+ <k-icon name="format_h${t}"></k-icon>
10
+ </button>
11
+ `}}customElements.define("k-md-heading",i);
@@ -0,0 +1,93 @@
1
+ import e from"./MarkdownEditorControl.js";import{html as t,css as o}from"../../lit-all.min.js";import"../Icon.js";import"../Dropdown.js";import r from"../Dialog.js";export default class i extends e{static properties={...e.properties,popup:{type:String,reflect:!0}};constructor(){super(),this.label="Image",this.popup="dropdown"}command(){"dialog"===this.popup&&this.#e()}#t(e,t){return!!e&&(this.editor?.replaceSelection(`![${t}](${e})`,{selectInserted:!1}),!0)}handleDropdownOpened=()=>{requestAnimationFrame(()=>{this.#o?.focus();const e=this.getSelection();this.#r&&(this.#r.value=e.text||"")})};handleDropdownClosed=()=>{this.#o&&(this.#o.value=""),this.#r&&(this.#r.value="")};handleFormKeydown=e=>{"Enter"===e.key?(e.preventDefault(),this.#i()):"Escape"===e.key&&this.shadowRoot?.querySelector("k-dropdown")?.close()};handleInsertClick=()=>this.#i();handleCancelClick=()=>{this.shadowRoot?.querySelector("k-dropdown")?.close()};#i(){const e=this.#o?.value.trim()||"",t=this.#r?.value.trim()||"";e?(this.#t(e,t),this.shadowRoot?.querySelector("k-dropdown")?.close()):this.#o?.focus()}get#o(){return this.shadowRoot?.querySelector(".image-url")}get#r(){return this.shadowRoot?.querySelector(".image-alt")}#e(){if(!this.editor)return;const e=this.getSelection(),t=document.createElement("input");t.type="url",t.placeholder="https://example.com/image.png",t.required=!0,t.style.cssText="padding: 0.5rem; border: 1px solid var(--c_border); border-radius: var(--radius); font: inherit;";const o=document.createElement("input");o.type="text",o.placeholder="Description for screen readers",o.value=e.text||"",o.style.cssText="padding: 0.5rem; border: 1px solid var(--c_border); border-radius: var(--radius); font: inherit;";const i=document.createElement("div");i.className="p",i.style.cssText="display: flex; flex-direction: column; gap: 1rem;",i.innerHTML='\n <div style="display: flex; flex-direction: column; gap: 0.25rem;">\n <label style="font-weight: bold;">Image URL</label>\n </div>\n <div style="display: flex; flex-direction: column; gap: 0.25rem;">\n <label style="font-weight: bold;">Alt text</label>\n </div>\n ',i.children[0].appendChild(t),i.children[1].appendChild(o),r.create(i,{title:"Insert Image",cancelText:"Cancel",confirmText:"Insert",confirmClasses:"success ml",confirmAction:e=>{const r=t.value.trim();if(!r)return e.keepDialogOpen=!0,void t.focus();this.#t(r,o.value.trim())}}),queueMicrotask(()=>t.focus())}render(){return"dialog"===this.popup?t`
2
+ <button
3
+ type="button"
4
+ class=${this.btnClass}
5
+ title=${this.label}
6
+ aria-label=${this.label}
7
+ @click=${this.handleClick}
8
+ >
9
+ <k-icon name="image"></k-icon>
10
+ </button>
11
+ `:t`
12
+ <k-dropdown
13
+ @opened=${this.handleDropdownOpened}
14
+ @closed=${this.handleDropdownClosed}
15
+ >
16
+ <button
17
+ slot="trigger"
18
+ type="button"
19
+ class=${this.btnClass}
20
+ title=${this.label}
21
+ aria-label=${this.label}
22
+ >
23
+ <k-icon name="image"></k-icon>
24
+ </button>
25
+ <div class="image-form" @keydown=${this.handleFormKeydown}>
26
+ <label>Image URL</label>
27
+ <input
28
+ class="image-url"
29
+ type="url"
30
+ required
31
+ placeholder="https://example.com/image.png"
32
+ />
33
+ <label>Alt text</label>
34
+ <input
35
+ class="image-alt"
36
+ type="text"
37
+ placeholder="Description for screen readers"
38
+ />
39
+ <div class="image-actions">
40
+ <button type="button" class="no-btn" @click=${this.handleCancelClick}>Cancel</button>
41
+ <button type="button" class="no-btn primary" @click=${this.handleInsertClick}>Insert</button>
42
+ </div>
43
+ </div>
44
+ </k-dropdown>
45
+ `}static styles=[e.styles,o`
46
+ .image-form {
47
+ display: flex;
48
+ flex-direction: column;
49
+ gap: 0.5rem;
50
+ padding: 0.75rem;
51
+ min-width: 16rem;
52
+ }
53
+ .image-form label {
54
+ font-weight: bold;
55
+ font-size: 0.875rem;
56
+ }
57
+ .image-form input {
58
+ padding: 0.5rem;
59
+ border: 1px solid var(--c_border);
60
+ border-radius: var(--radius);
61
+ font: inherit;
62
+ background: var(--c_bg);
63
+ color: var(--tc);
64
+ }
65
+ .image-form input:focus-visible {
66
+ outline: none;
67
+ box-shadow: var(--focus_shadow);
68
+ }
69
+ .image-actions {
70
+ display: flex;
71
+ justify-content: flex-end;
72
+ gap: 0.5rem;
73
+ margin-top: 0.25rem;
74
+ }
75
+ .image-actions button {
76
+ padding: 0.4rem 0.8rem;
77
+ border: 1px solid var(--c_border);
78
+ border-radius: var(--radius);
79
+ background: var(--c_bg);
80
+ color: var(--tc);
81
+ cursor: pointer;
82
+ font: inherit;
83
+ }
84
+ .image-actions button.primary {
85
+ background: var(--c_primary);
86
+ color: white;
87
+ border-color: var(--c_primary);
88
+ }
89
+ .image-actions button:focus-visible {
90
+ outline: none;
91
+ box-shadow: var(--focus_shadow);
92
+ }
93
+ `]}customElements.define("k-md-image",i);
@@ -0,0 +1,11 @@
1
+ import t from"./MarkdownEditorControl.js";import{html as i}from"../../lit-all.min.js";import"../Icon.js";export default class l extends t{constructor(){super(),this.label="Italic (Cmd/Ctrl+I)"}command(){this.wrapSelection("_","_","italic text")}render(){return i`
2
+ <button
3
+ type="button"
4
+ class=${this.btnClass}
5
+ title=${this.label}
6
+ aria-label=${this.label}
7
+ @click=${this.handleClick}
8
+ >
9
+ <k-icon name="format_italic"></k-icon>
10
+ </button>
11
+ `}}customElements.define("k-md-italic",l);
@@ -0,0 +1,11 @@
1
+ import t from"./MarkdownEditorControl.js";import{html as e}from"../../lit-all.min.js";import"../Icon.js";export default class n extends t{constructor(){super(),this.label="Link"}command(){const t=this.getSelection(),e=this.editor;if(!e)return;const n=e.textarea;if(!n)return;const s=(t,s,l)=>{n.value=t,n.selectionStart=s,n.selectionEnd=l,n.focus(),e.value=t,n.dispatchEvent(new Event("input",{bubbles:!0}))},l=/\[([^\]]*)\]\([^)]*\)/g;let i;for(;null!==(i=l.exec(n.value));){const e=i.index,l=e+i[0].length;if(t.start>=e&&t.end<=l){const t=i[1];return void s(n.value.substring(0,e)+t+n.value.substring(l),e,e+t.length)}}const r=t.text||"link text",o=`[${r}](url)`,a=n.value.substring(0,t.start)+o+n.value.substring(t.end),u=t.start+1+r.length+2;s(a,u,u+3)}render(){return e`
2
+ <button
3
+ type="button"
4
+ class=${this.btnClass}
5
+ title=${this.label}
6
+ aria-label=${this.label}
7
+ @click=${this.handleClick}
8
+ >
9
+ <k-icon name="link"></k-icon>
10
+ </button>
11
+ `}}customElements.define("k-md-link",n);
@@ -0,0 +1,39 @@
1
+ import e from"../ShadowComponent.js";import{html as t,css as i}from"../../lit-all.min.js";export default class n extends e{static properties={btnClass:{type:String,attribute:"btn-class"},label:{type:String,reflect:!0},hidden:{type:Boolean,reflect:!0}};constructor(){super(),this.btnClass="no-btn ctrl",this.label="",this.hidesInPreviewMode=!0}connectedCallback(){super.connectedCallback();const e=this.editor;e&&(this.boundEditor=e,this.modeHandler=()=>{const e=this.hidesInPreviewMode&&"preview"===this.boundEditor.mode;this.hidden!==e&&(this.hidden=e)},this.modeHandler(),e.addEventListener("mode-change",this.modeHandler))}disconnectedCallback(){super.disconnectedCallback(),this.boundEditor&&this.modeHandler&&this.boundEditor.removeEventListener("mode-change",this.modeHandler),this.boundEditor=null,this.modeHandler=null}get editor(){const e=this.closest("k-markdown-editor");if(e)return e;const t=this.getRootNode();return t instanceof ShadowRoot&&"K-MARKDOWN-EDITOR"===t.host?.tagName?t.host:null}wrapSelection(e,t,i){this.editor?.wrapSelection(e,t,i)}insertAtCursor(e){this.editor?.insertAtCursor(e)}insertLinePrefix(e,t){this.editor?.insertLinePrefix(e,t)}replaceInSelectedLines(e,t){this.editor?.replaceInSelectedLines(e,t)}replaceSelection(e,t){this.editor?.replaceSelection(e,t)}getSelection(){return this.editor?.getSelection()||{start:0,end:0,text:""}}command(){}handleClick=e=>{e.preventDefault(),this.hidden||this.command()};render(){return t`
2
+ <button
3
+ type="button"
4
+ class=${this.btnClass}
5
+ title=${this.label}
6
+ aria-label=${this.label}
7
+ @click=${this.handleClick}
8
+ >
9
+ <slot>${this.label}</slot>
10
+ </button>
11
+ `}static styles=i`
12
+ :host {
13
+ display: inline-flex;
14
+ }
15
+ :host([hidden]) {
16
+ display: none;
17
+ }
18
+ .ctrl {
19
+ display: inline-flex;
20
+ align-items: center;
21
+ justify-content: center;
22
+ padding: var(--spacer_h);
23
+ border: 1px solid transparent;
24
+ border-radius: var(--radius);
25
+ background: transparent;
26
+ color: var(--tc_muted);
27
+ cursor: pointer;
28
+ font: inherit;
29
+ transition: background var(--animation_ms), color var(--animation_ms);
30
+ }
31
+ .ctrl:hover {
32
+ background: var(--c_bg__alt);
33
+ color: var(--tc);
34
+ }
35
+ .ctrl:focus-visible {
36
+ outline: none;
37
+ box-shadow: var(--focus_shadow);
38
+ }
39
+ `}customElements.define("k-md-control",n);
@@ -0,0 +1,30 @@
1
+ import t from"./MarkdownEditorControl.js";import{html as e,css as o}from"../../lit-all.min.js";import"../Icon.js";import"../Dropdown.js";export default class s extends t{constructor(){super(),this.label="Menu"}command(){}render(){return e`
2
+ <k-dropdown>
3
+ <button
4
+ slot="trigger"
5
+ type="button"
6
+ class=${this.btnClass}
7
+ title=${this.label}
8
+ aria-label=${this.label}
9
+ >
10
+ <slot name="trigger">
11
+ <k-icon name="menu"></k-icon>
12
+ </slot>
13
+ </button>
14
+ <slot></slot>
15
+ </k-dropdown>
16
+ `}static styles=[t.styles,o`
17
+ :host {
18
+ display: inline-flex;
19
+ }
20
+ k-dropdown {
21
+ display: inline-flex;
22
+ }
23
+ /* Stretch slotted menu items so each one fills the dropdown
24
+ horizontally — Dropdown's built-in styles only target raw
25
+ <a>/<button> children, so custom-element items need this themselves. */
26
+ ::slotted(:not([slot="trigger"])) {
27
+ display: block;
28
+ width: 100%;
29
+ }
30
+ `]}customElements.define("k-md-menu",s);
@@ -0,0 +1,11 @@
1
+ import t from"./MarkdownEditorControl.js";import{html as e}from"../../lit-all.min.js";import"../Icon.js";export default class n extends t{constructor(){super(),this.label="Numbered list"}command(){const t=this.editor;if(!t)return;const e=t.textarea;e&&("write"!==t.mode&&(t.mode="write"),t.updateComplete.then(()=>{e.focus();const{selectionStart:n,selectionEnd:l,value:s}=e,i=s.lastIndexOf("\n",n-1)+1,r=s.indexOf("\n",l),o=-1===r?s.length:r,a=s.substring(i,o).split("\n"),c=/^\d+\. /,u=a.filter(t=>t.length>0);let d;if(u.length>0&&u.every(t=>c.test(t)))d=a.map(t=>t.replace(c,""));else{let t=1;d=a.map(e=>{if(!e)return e;const n=e.replace(c,"");return`${t++}. ${n}`})}const m=d.join("\n"),b=s.substring(0,i)+m+s.substring(o);e.value=b,e.selectionStart=i,e.selectionEnd=i+m.length,t.value=b,e.dispatchEvent(new Event("input",{bubbles:!0}))}))}render(){return e`
2
+ <button
3
+ type="button"
4
+ class=${this.btnClass}
5
+ title=${this.label}
6
+ aria-label=${this.label}
7
+ @click=${this.handleClick}
8
+ >
9
+ <k-icon name="format_list_numbered"></k-icon>
10
+ </button>
11
+ `}}customElements.define("k-md-numbered-list",n);
@@ -0,0 +1,11 @@
1
+ import t from"./MarkdownEditorControl.js";import{html as e}from"../../lit-all.min.js";import"../Icon.js";export default class o extends t{constructor(){super(),this.label="Quote"}command(){this.insertLinePrefix("> ")}render(){return e`
2
+ <button
3
+ type="button"
4
+ class=${this.btnClass}
5
+ title=${this.label}
6
+ aria-label=${this.label}
7
+ @click=${this.handleClick}
8
+ >
9
+ <k-icon name="format_quote"></k-icon>
10
+ </button>
11
+ `}}customElements.define("k-md-quote",o);
@@ -0,0 +1,16 @@
1
+ import t from"./MarkdownEditorControl.js";import{html as e,css as o}from"../../lit-all.min.js";import"../SpeechToText.js";export default class s extends t{static properties={...t.properties,language:{type:String,reflect:!0},continuous:{type:Boolean,reflect:!0}};constructor(){super(),this.label="Speech to text",this.language="en-US",this.continuous=!1}command(){}handleEnd=t=>{const e=(t.detail?.text||"").trim();e&&this.editor?.replaceSelection(e+" ",{selectInserted:!1})};render(){return e`
2
+ <k-speech-to-text
3
+ language=${this.language}
4
+ ?continuous=${this.continuous}
5
+ @end=${this.handleEnd}
6
+ ></k-speech-to-text>
7
+ `}static styles=[t.styles,o`
8
+ :host {
9
+ margin: var(--spacer_q);
10
+ }
11
+ k-speech-to-text {
12
+ /* Shrink the default 2.5rem circular mic button so it sits
13
+ comfortably alongside other toolbar controls. */
14
+ --btn_size: 2rem;
15
+ }
16
+ `]}customElements.define("k-md-speech-to-text",s);
@@ -0,0 +1,11 @@
1
+ import t from"./MarkdownEditorControl.js";import{html as r}from"../../lit-all.min.js";import"../Icon.js";export default class e extends t{constructor(){super(),this.label="Strikethrough"}command(){this.wrapSelection("~~","~~","strikethrough")}render(){return r`
2
+ <button
3
+ type="button"
4
+ class=${this.btnClass}
5
+ title=${this.label}
6
+ aria-label=${this.label}
7
+ @click=${this.handleClick}
8
+ >
9
+ <k-icon name="format_strikethrough"></k-icon>
10
+ </button>
11
+ `}}customElements.define("k-md-strikethrough",e);
@@ -0,0 +1,80 @@
1
+ import t from"./MarkdownEditorControl.js";import{html as e,css as r}from"../../lit-all.min.js";import"../Icon.js";import"../Dropdown.js";export default class o extends t{constructor(){super(),this.label="Table"}command(){}handleDropdownOpened=()=>{requestAnimationFrame(()=>{const t=this.shadowRoot?.querySelector(".tbl-cols");t&&(t.value="3",t.select());const e=this.shadowRoot?.querySelector(".tbl-rows");e&&(e.value="2")})};handleFormKeydown=t=>{"Enter"===t.key?(t.preventDefault(),this.#t()):"Escape"===t.key&&this.shadowRoot?.querySelector("k-dropdown")?.close()};handleInsertClick=()=>this.#t();#t(){const t=this.#e(this.shadowRoot?.querySelector(".tbl-cols")?.value,1,20),e=this.#e(this.shadowRoot?.querySelector(".tbl-rows")?.value,1,50);if(!t||!e)return;const r=this.editor;r&&(r.replaceSelection(this.#r(t,e),{selectInserted:!1}),this.shadowRoot?.querySelector("k-dropdown")?.close())}#e(t,e,r){const o=parseInt(t,10);return Number.isFinite(o)?Math.max(e,Math.min(r,o)):0}#r(t,e){const r=Array.from({length:t},(t,e)=>` Header ${e+1} `).join("|"),o=Array.from({length:t},()=>" --- ").join("|"),n=Array.from({length:t},()=>" ").join("|");return`\n${[`|${r}|`,`|${o}|`,...Array.from({length:e},()=>`|${n}|`)].join("\n")}\n`}render(){return e`
2
+ <k-dropdown @opened=${this.handleDropdownOpened}>
3
+ <button
4
+ slot="trigger"
5
+ type="button"
6
+ class=${this.btnClass}
7
+ title=${this.label}
8
+ aria-label=${this.label}
9
+ >
10
+ <k-icon name="table"></k-icon>
11
+ </button>
12
+ <div class="tbl-form" @keydown=${this.handleFormKeydown}>
13
+ <input
14
+ class="tbl-cols"
15
+ type="number"
16
+ min="1"
17
+ max="20"
18
+ inputmode="numeric"
19
+ aria-label="Columns"
20
+ value="3"
21
+ />
22
+ <span class="tbl-x" aria-hidden="true">×</span>
23
+ <input
24
+ class="tbl-rows"
25
+ type="number"
26
+ min="1"
27
+ max="50"
28
+ inputmode="numeric"
29
+ aria-label="Rows"
30
+ value="2"
31
+ />
32
+ <button type="button" class="primary" @click=${this.handleInsertClick}>Insert</button>
33
+ </div>
34
+ </k-dropdown>
35
+ `}static styles=[t.styles,r`
36
+ .tbl-form {
37
+ display: flex;
38
+ align-items: center;
39
+ gap: 0.4rem;
40
+ padding: 0.5rem;
41
+ }
42
+ .tbl-form input {
43
+ width: 2.5rem;
44
+ padding: 0.35rem 0.4rem;
45
+ border: 1px solid var(--c_border);
46
+ border-radius: var(--radius);
47
+ font: inherit;
48
+ background: var(--c_bg);
49
+ color: var(--tc);
50
+ text-align: center;
51
+ /* Hide the up/down spinner buttons that take up extra width. */
52
+ -moz-appearance: textfield;
53
+ }
54
+ .tbl-form input::-webkit-inner-spin-button,
55
+ .tbl-form input::-webkit-outer-spin-button {
56
+ -webkit-appearance: none;
57
+ margin: 0;
58
+ }
59
+ .tbl-form input:focus-visible {
60
+ outline: none;
61
+ box-shadow: var(--focus_shadow);
62
+ }
63
+ .tbl-x {
64
+ color: var(--tc_muted);
65
+ font-weight: bold;
66
+ }
67
+ .tbl-form button.primary {
68
+ padding: 0.4rem 0.8rem;
69
+ border: 1px solid var(--c_primary);
70
+ border-radius: var(--radius);
71
+ background: var(--c_primary);
72
+ color: white;
73
+ cursor: pointer;
74
+ font: inherit;
75
+ }
76
+ .tbl-form button.primary:focus-visible {
77
+ outline: none;
78
+ box-shadow: var(--focus_shadow);
79
+ }
80
+ `]}customElements.define("k-md-table",o);