kempo-ui 0.0.43 → 0.0.63

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 (194) hide show
  1. package/.github/copilot-instructions.md +11 -0
  2. package/.github/workflows/publish.yml +66 -0
  3. package/CONTRIBUTING.md +70 -0
  4. package/dist/components/Dialog.js +9 -8
  5. package/dist/components/HtmlEditor.js +91 -0
  6. package/dist/components/Icon.js +2 -2
  7. package/dist/components/Main.js +5 -9
  8. package/dist/components/ShadowComponent.js +1 -1
  9. package/dist/components/SideMenu.js +1 -1
  10. package/dist/components/SidePanel.js +46 -35
  11. package/dist/components/htmlEditorControls/AlignCenter.js +12 -0
  12. package/dist/components/htmlEditorControls/AlignJustify.js +12 -0
  13. package/dist/components/htmlEditorControls/AlignLeft.js +12 -0
  14. package/dist/components/htmlEditorControls/AlignRight.js +12 -0
  15. package/dist/components/htmlEditorControls/Bold.js +12 -0
  16. package/dist/components/htmlEditorControls/BulletList.js +12 -0
  17. package/dist/components/htmlEditorControls/CharacterCount.js +18 -0
  18. package/dist/components/htmlEditorControls/ClearFormatting.js +12 -0
  19. package/dist/components/htmlEditorControls/CodeBlock.js +12 -0
  20. package/dist/components/htmlEditorControls/ControlGroup.js +14 -0
  21. package/dist/components/htmlEditorControls/ControlSpacer.js +6 -0
  22. package/dist/components/htmlEditorControls/CreateLink.js +15 -0
  23. package/dist/components/htmlEditorControls/DropdownControl.js +57 -0
  24. package/dist/components/htmlEditorControls/FormatBlock.js +12 -0
  25. package/dist/components/htmlEditorControls/HtmlEditorControl.js +44 -0
  26. package/dist/components/htmlEditorControls/InlineCode.js +12 -0
  27. package/dist/components/htmlEditorControls/InsertTable.js +15 -0
  28. package/dist/components/htmlEditorControls/Italic.js +12 -0
  29. package/dist/components/htmlEditorControls/Mode.js +9 -0
  30. package/dist/components/htmlEditorControls/NumberList.js +12 -0
  31. package/dist/components/htmlEditorControls/Strikethrough.js +12 -0
  32. package/dist/components/htmlEditorControls/TextBackgroundColor.js +97 -0
  33. package/dist/components/htmlEditorControls/TextColor.js +97 -0
  34. package/dist/components/htmlEditorControls/Underline.js +12 -0
  35. package/dist/components/htmlEditorControls/WordCount.js +18 -0
  36. package/dist/utils/formatCode.js +1 -0
  37. package/docs/components/dropdown.html +2 -2
  38. package/docs/components/html-editor.html +442 -0
  39. package/docs/components/icon.html +2 -7
  40. package/docs/components/photo-viewer.html +14 -14
  41. package/docs/components/side-menu.html +32 -0
  42. package/docs/components/side-panel-dual.html +70 -0
  43. package/docs/components/side-panel-scroll.html +176 -0
  44. package/docs/components/side-panel.html +41 -1
  45. package/docs/icons/arrow_drop_down.svg +1 -0
  46. package/docs/icons/circle.svg +1 -0
  47. package/docs/icons/code.svg +1 -0
  48. package/docs/icons/code_blocks.svg +1 -0
  49. package/docs/icons/copy.svg +1 -0
  50. package/docs/icons/dot.svg +1 -0
  51. package/docs/icons/format_align_center.svg +1 -0
  52. package/docs/icons/format_align_justify.svg +1 -0
  53. package/docs/icons/format_align_left.svg +1 -0
  54. package/docs/icons/format_align_right.svg +1 -0
  55. package/docs/icons/format_bold.svg +1 -0
  56. package/docs/icons/format_clear.svg +1 -0
  57. package/docs/icons/format_color_fill.svg +1 -0
  58. package/docs/icons/format_color_text.svg +1 -0
  59. package/docs/icons/format_h1.svg +1 -0
  60. package/docs/icons/format_h2.svg +1 -0
  61. package/docs/icons/format_h3.svg +1 -0
  62. package/docs/icons/format_h4.svg +1 -0
  63. package/docs/icons/format_h5.svg +1 -0
  64. package/docs/icons/format_h6.svg +1 -0
  65. package/docs/icons/format_italic.svg +1 -0
  66. package/docs/icons/format_list_bulleted.svg +1 -0
  67. package/docs/icons/format_list_numbered.svg +1 -0
  68. package/docs/icons/format_paragraph.svg +1 -0
  69. package/docs/icons/format_underlined.svg +1 -0
  70. package/docs/icons/help.svg +1 -0
  71. package/docs/icons/link.svg +1 -0
  72. package/docs/icons/more_vert.svg +1 -0
  73. package/docs/icons/strikethrough_s.svg +1 -0
  74. package/docs/icons/table.svg +1 -0
  75. package/docs/icons/wysiwyg.svg +1 -0
  76. package/docs/index.html +6 -0
  77. package/docs/nav-1.inc.html +1 -0
  78. package/docs/nav.inc.html +1 -0
  79. package/docs/src/components/Dialog.js +9 -8
  80. package/docs/src/components/HtmlEditor.js +91 -0
  81. package/docs/src/components/Icon.js +2 -2
  82. package/docs/src/components/Main.js +5 -9
  83. package/docs/src/components/ShadowComponent.js +1 -1
  84. package/docs/src/components/SideMenu.js +1 -1
  85. package/docs/src/components/SidePanel.js +46 -35
  86. package/docs/src/components/htmlEditorControls/AlignCenter.js +12 -0
  87. package/docs/src/components/htmlEditorControls/AlignJustify.js +12 -0
  88. package/docs/src/components/htmlEditorControls/AlignLeft.js +12 -0
  89. package/docs/src/components/htmlEditorControls/AlignRight.js +12 -0
  90. package/docs/src/components/htmlEditorControls/Bold.js +12 -0
  91. package/docs/src/components/htmlEditorControls/BulletList.js +12 -0
  92. package/docs/src/components/htmlEditorControls/CharacterCount.js +18 -0
  93. package/docs/src/components/htmlEditorControls/ClearFormatting.js +12 -0
  94. package/docs/src/components/htmlEditorControls/CodeBlock.js +12 -0
  95. package/docs/src/components/htmlEditorControls/ControlGroup.js +14 -0
  96. package/docs/src/components/htmlEditorControls/ControlSpacer.js +6 -0
  97. package/docs/src/components/htmlEditorControls/CreateLink.js +15 -0
  98. package/docs/src/components/htmlEditorControls/DropdownControl.js +57 -0
  99. package/docs/src/components/htmlEditorControls/FormatBlock.js +12 -0
  100. package/docs/src/components/htmlEditorControls/HtmlEditorControl.js +44 -0
  101. package/docs/src/components/htmlEditorControls/InlineCode.js +12 -0
  102. package/docs/src/components/htmlEditorControls/InsertTable.js +15 -0
  103. package/docs/src/components/htmlEditorControls/Italic.js +12 -0
  104. package/docs/src/components/htmlEditorControls/Mode.js +9 -0
  105. package/docs/src/components/htmlEditorControls/NumberList.js +12 -0
  106. package/docs/src/components/htmlEditorControls/Strikethrough.js +12 -0
  107. package/docs/src/components/htmlEditorControls/TextBackgroundColor.js +97 -0
  108. package/docs/src/components/htmlEditorControls/TextColor.js +97 -0
  109. package/docs/src/components/htmlEditorControls/Underline.js +12 -0
  110. package/docs/src/components/htmlEditorControls/WordCount.js +18 -0
  111. package/docs/src/utils/formatCode.js +1 -0
  112. package/icons/arrow_drop_down.svg +1 -0
  113. package/icons/circle.svg +1 -0
  114. package/icons/code.svg +1 -0
  115. package/icons/code_blocks.svg +1 -0
  116. package/icons/copy.svg +1 -0
  117. package/icons/dot.svg +1 -0
  118. package/icons/format_align_center.svg +1 -0
  119. package/icons/format_align_justify.svg +1 -0
  120. package/icons/format_align_left.svg +1 -0
  121. package/icons/format_align_right.svg +1 -0
  122. package/icons/format_bold.svg +1 -0
  123. package/icons/format_clear.svg +1 -0
  124. package/icons/format_color_fill.svg +1 -0
  125. package/icons/format_color_text.svg +1 -0
  126. package/icons/format_h1.svg +1 -0
  127. package/icons/format_h2.svg +1 -0
  128. package/icons/format_h3.svg +1 -0
  129. package/icons/format_h4.svg +1 -0
  130. package/icons/format_h5.svg +1 -0
  131. package/icons/format_h6.svg +1 -0
  132. package/icons/format_italic.svg +1 -0
  133. package/icons/format_list_bulleted.svg +1 -0
  134. package/icons/format_list_numbered.svg +1 -0
  135. package/icons/format_paragraph.svg +1 -0
  136. package/icons/format_underlined.svg +1 -0
  137. package/icons/help.svg +1 -0
  138. package/icons/link.svg +1 -0
  139. package/icons/more_vert.svg +1 -0
  140. package/icons/strikethrough_s.svg +1 -0
  141. package/icons/table.svg +1 -0
  142. package/icons/wysiwyg.svg +1 -0
  143. package/package.json +6 -5
  144. package/scripts/getIcon.js +52 -0
  145. package/src/components/Dialog.js +25 -10
  146. package/src/components/HtmlEditor.js +1548 -0
  147. package/src/components/Icon.js +6 -3
  148. package/src/components/Main.js +22 -23
  149. package/src/components/ShadowComponent.js +0 -1
  150. package/src/components/SideMenu.js +10 -0
  151. package/src/components/SidePanel.js +78 -37
  152. package/src/components/htmlEditorControls/AlignCenter.js +65 -0
  153. package/src/components/htmlEditorControls/AlignJustify.js +65 -0
  154. package/src/components/htmlEditorControls/AlignLeft.js +65 -0
  155. package/src/components/htmlEditorControls/AlignRight.js +65 -0
  156. package/src/components/htmlEditorControls/Bold.js +65 -0
  157. package/src/components/htmlEditorControls/BulletList.js +65 -0
  158. package/src/components/htmlEditorControls/CharacterCount.js +75 -0
  159. package/src/components/htmlEditorControls/ClearFormatting.js +65 -0
  160. package/src/components/htmlEditorControls/CodeBlock.js +72 -0
  161. package/src/components/htmlEditorControls/ControlGroup.js +95 -0
  162. package/src/components/htmlEditorControls/ControlSpacer.js +23 -0
  163. package/src/components/htmlEditorControls/CreateLink.js +121 -0
  164. package/src/components/htmlEditorControls/DropdownControl.js +149 -0
  165. package/src/components/htmlEditorControls/FormatBlock.js +108 -0
  166. package/src/components/htmlEditorControls/HtmlEditorControl.js +126 -0
  167. package/src/components/htmlEditorControls/InlineCode.js +77 -0
  168. package/src/components/htmlEditorControls/InsertTable.js +171 -0
  169. package/src/components/htmlEditorControls/Italic.js +65 -0
  170. package/src/components/htmlEditorControls/Mode.js +66 -0
  171. package/src/components/htmlEditorControls/NumberList.js +65 -0
  172. package/src/components/htmlEditorControls/Strikethrough.js +65 -0
  173. package/src/components/htmlEditorControls/TextBackgroundColor.js +195 -0
  174. package/src/components/htmlEditorControls/TextColor.js +195 -0
  175. package/src/components/htmlEditorControls/Underline.js +65 -0
  176. package/src/components/htmlEditorControls/WordCount.js +76 -0
  177. package/src/utils/formatCode.js +87 -0
  178. package/tests/components/Main.browser-test.js +166 -105
  179. package/tests/components/SidePanel.browser-test.js +62 -8
  180. package/.github/workflows/publish-major.yml +0 -39
  181. package/.github/workflows/publish-minor.yml +0 -39
  182. package/.github/workflows/publish-patch.yml +0 -45
  183. package/docs/icons/arrow-back.svg +0 -1
  184. package/docs/icons/arrow-down-double.svg +0 -1
  185. package/docs/icons/arrow-down.svg +0 -1
  186. package/docs/icons/arrow-forward.svg +0 -1
  187. package/docs/icons/arrow-menu-close.svg +0 -1
  188. package/docs/icons/arrow-menu-open.svg +0 -1
  189. package/docs/icons/arrow-right.svg +0 -1
  190. package/docs/icons/arrow-up-double.svg +0 -1
  191. package/docs/icons/arrow-up.svg +0 -1
  192. package/docs/icons/chevron-left.svg +0 -1
  193. package/docs/icons/chevron-right.svg +0 -1
  194. package/docs/icons/chevron-up.svg +0 -1
@@ -150,6 +150,17 @@ export default class MyComponent extends HybridComponent {
150
150
 
151
151
  ## Development Workflow
152
152
 
153
+ ### Icons
154
+ - All icons are stored in the `icons/` directory as SVG files
155
+ - Icons should have no `width` or `height` attributes, and use `fill="currentColor"` for theming
156
+ - To download new Material Icons from Google's CDN:
157
+ ```bash
158
+ npm run geticon icon_name
159
+ ```
160
+ Example: `npm run geticon format_bold`
161
+ - The script automatically formats the icon with proper attributes
162
+ - Browse available icons at [Google Material Symbols](https://fonts.google.com/icons)
163
+
153
164
  ### Local Development Server
154
165
  - **DO NOT** start a development server - one is already running
155
166
  - Default port: **8083**
@@ -0,0 +1,66 @@
1
+ name: Publish to npmjs
2
+
3
+ on:
4
+ push:
5
+ branches:
6
+ - main
7
+ workflow_dispatch:
8
+ inputs:
9
+ version-type:
10
+ description: 'Version bump type'
11
+ required: true
12
+ type: choice
13
+ options:
14
+ - patch
15
+ - minor
16
+ - major
17
+ default: patch
18
+
19
+ jobs:
20
+ publish:
21
+ runs-on: ubuntu-latest
22
+ permissions:
23
+ contents: write
24
+ id-token: write
25
+ steps:
26
+ - uses: actions/checkout@v4
27
+ with:
28
+ token: ${{ secrets.GITHUB_TOKEN }}
29
+
30
+ - uses: actions/setup-node@v4
31
+ with:
32
+ node-version: '20.x'
33
+
34
+ - run: npm ci
35
+
36
+ - name: Set version type
37
+ id: version
38
+ run: |
39
+ if [ "${{ github.event_name }}" == "workflow_dispatch" ]; then
40
+ echo "type=${{ inputs.version-type }}" >> $GITHUB_OUTPUT
41
+ else
42
+ echo "type=patch" >> $GITHUB_OUTPUT
43
+ fi
44
+
45
+ - name: Bump ${{ steps.version.outputs.type }} version
46
+ run: |
47
+ git config --global user.name "github-actions"
48
+ git config --global user.email "github-actions@github.com"
49
+ npm version ${{ steps.version.outputs.type }} --no-git-tag-version
50
+ git add package.json package-lock.json || true
51
+ git commit -m "ci: bump ${{ steps.version.outputs.type }} version [skip ci]" || echo "No changes to commit"
52
+ git push origin HEAD:main || echo "No changes to push"
53
+ env:
54
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
55
+
56
+ - name: Setup .npmrc for OIDC
57
+ run: |
58
+ cat << EOF > ~/.npmrc
59
+ //registry.npmjs.org/:_authToken=\${NPM_TOKEN}
60
+ registry=https://registry.npmjs.org/
61
+ always-auth=true
62
+ EOF
63
+
64
+ - run: npm publish --access public
65
+ env:
66
+ NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
package/CONTRIBUTING.md CHANGED
@@ -147,3 +147,73 @@ export default class MyComponent extends HybridComponent {
147
147
  - Wrap dependent components inside a parent `ktf-test-framework` element. Children find it via `closest('ktf-test-framework')` and call its methods. The framework can query its subtree to orchestrate children.
148
148
 
149
149
  - Avoid `window` globals and global custom events for coordination. If broadcast is needed, scope events to the framework element; reserve window events for global, non-visual concerns (e.g., settings changes).
150
+
151
+ ## Development Workflow
152
+
153
+ ### NPM Scripts
154
+
155
+ #### Build
156
+ ```bash
157
+ npm run build
158
+ ```
159
+ Compiles and minifies source files from `src/` and copies all necessary files to the `docs/` directory for GitHub Pages deployment.
160
+
161
+ #### Documentation Server
162
+ ```bash
163
+ npm run docs
164
+ ```
165
+ Starts a local development server and opens the documentation site. Automatically runs the build script first.
166
+
167
+ **Variants:**
168
+ - `npm run docs:src` - Serves from `src/` instead of `docs/` for development
169
+ - `npm run docs:no-build` - Skips the build step and serves existing `docs/`
170
+
171
+ #### Testing
172
+ ```bash
173
+ npm test # Run all tests
174
+ npm run test:gui # Run tests with GUI interface
175
+ npm run test:browser # Run browser-only tests
176
+ npm run test:node # Run Node.js-only tests
177
+ ```
178
+
179
+ #### Update Kempo CSS
180
+ ```bash
181
+ npm run update-kempo-css
182
+ ```
183
+ Downloads the latest version of Kempo CSS from the CDN and updates local files.
184
+
185
+ ### Adding Icons
186
+
187
+ Use the `geticon` script to download icons from [Google Material Symbols](https://fonts.google.com/icons). The script automatically formats icons and triggers a build.
188
+
189
+ #### Basic Usage
190
+ ```bash
191
+ npm run geticon <icon_name>
192
+ ```
193
+
194
+ Example:
195
+ ```bash
196
+ npm run geticon format_bold
197
+ ```
198
+
199
+ This downloads the icon and saves it as `format_bold.svg` in the `icons/` directory.
200
+
201
+ #### Custom Naming
202
+
203
+ Rename icons when downloading by providing a second argument:
204
+ ```bash
205
+ npm run geticon <icon_name> <custom_name>
206
+ ```
207
+
208
+ Example - download `content_copy` but save as `copy.svg`:
209
+ ```bash
210
+ npm run geticon content_copy copy
211
+ ```
212
+
213
+ #### What the Script Does
214
+
215
+ - Downloads the specified icon from Google's Material Symbols CDN
216
+ - Removes `width` and `height` attributes
217
+ - Adds `fill="currentColor"` to all `<path>` elements
218
+ - Saves the formatted SVG to the `icons/` directory
219
+ - Automatically runs the build script to copy the icon to `docs/`
@@ -1,4 +1,4 @@
1
- import ShadowComponent from"./ShadowComponent.js";import{html,css}from"../lit-all.min.js";import{boolExists}from"../utils/propConverters.js";import"./Icon.js";import"./FocusCapture.js";const firstFocusable=e=>e.querySelectorAll('button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])')[0];export default class Dialog extends ShadowComponent{static properties={opened:{type:Boolean,reflect:!0,converter:boolExists},closeBtn:{type:Boolean,reflect:!0,attribute:"close-btn",converter:boolExists},overlayClose:{type:Boolean,reflect:!0,attribute:"overlay-close",converter:boolExists},disableKeyboardClose:{type:Boolean,reflect:!0,attribute:"disable-keyboard-close",converter:boolExists},confirmText:{type:String,reflect:!0,attribute:"confirm-text"},confirmClasses:{type:String,reflect:!0,attribute:"confirm-classes"},cancelText:{type:String,reflect:!0,attribute:"cancel-text"},cancelClasses:{type:String,reflect:!0,attribute:"cancel-classes"}};constructor(){super(),this.opened=!1,this.closeBtn=!0,this.overlayClose=!0,this.disableKeyboardClose=!1,this.confirmText="",this.confirmClasses="success ml",this.cancelText="",this.cancelClasses="",this.confirmAction=()=>{},this.cancelAction=()=>{},this.closeCallback=()=>{},this.previousFocus=null}handleClick=e=>{const{target:t}=e,o=t.id||t.closest("[id]")?.id;"overlay"===o&&this.overlayClose||"close"===o?this.close():"cancel"===o?(this.cancelAction(e),e.defaultPrevented||this.close()):"confirm"===o&&(this.confirmAction(e),e.defaultPrevented||this.close())};handleKeydown=e=>{this.disableKeyboardClose||27!==e.keyCode||this.close()};connectedCallback(){super.connectedCallback()}disconnectedCallback(){super.disconnectedCallback(),window.removeEventListener("keydown",this.handleKeydown)}updated(e){super.updated(e),e.has("opened")&&this.dispatchEvent(new CustomEvent(this.opened?"opened":"close"))}open(){this.opened=!0,window.addEventListener("keydown",this.handleKeydown),document.body.classList.add("no-scroll");const e=this.updateComplete||this.requestUpdate();e&&e.then?e.then(()=>{const e=this.shadowRoot.querySelector("[autofocus]")||firstFocusable(this.shadowRoot);e&&e.focus()}):setTimeout(()=>{const e=this.shadowRoot.querySelector("[autofocus]")||firstFocusable(this.shadowRoot);e&&e.focus()},0)}close(){this.opened=!1,this.blur(),this.closeCallback(),window.removeEventListener("keydown",this.handleKeydown),document.body.classList.remove("no-scroll")}toggle(){this.opened?this.close():this.open()}focus(){const e=firstFocusable(this.shadowRoot);e&&(this.previousFocus=document.activeElement,e.focus())}blur(){this.previousFocus&&this.previousFocus.focus()}hasTitle(){return!!this.querySelector('[slot="title"]')}static styles=css`
1
+ import ShadowComponent from"./ShadowComponent.js";import{html,css}from"../lit-all.min.js";import{boolExists}from"../utils/propConverters.js";import"./Icon.js";import"./FocusCapture.js";const firstFocusable=e=>e.querySelectorAll('button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])')[0];export default class Dialog extends ShadowComponent{static properties={opened:{type:Boolean,reflect:!0,converter:boolExists},closeBtn:{type:Boolean,reflect:!0,attribute:"close-btn",converter:boolExists},overlayClose:{type:Boolean,reflect:!0,attribute:"overlay-close",converter:boolExists},disableKeyboardClose:{type:Boolean,reflect:!0,attribute:"disable-keyboard-close",converter:boolExists},confirmText:{type:String,reflect:!0,attribute:"confirm-text"},confirmClasses:{type:String,reflect:!0,attribute:"confirm-classes"},cancelText:{type:String,reflect:!0,attribute:"cancel-text"},cancelClasses:{type:String,reflect:!0,attribute:"cancel-classes"}};constructor(){super(),this.opened=!1,this.closeBtn=!0,this.overlayClose=!0,this.disableKeyboardClose=!1,this.confirmText="",this.confirmClasses="success ml",this.cancelText="",this.cancelClasses="",this.confirmAction=()=>{},this.cancelAction=()=>{},this.closeCallback=()=>{},this.previousFocus=null}handleClick=e=>{const{target:t}=e,o=t.id||t.closest("[id]")?.id;"overlay"===o&&this.overlayClose||"close"===o?this.close():"cancel"===o&&(this.cancelAction(e),e.defaultPrevented||this.close())};handleSubmit=e=>{e.preventDefault(),this.confirmAction(e),e.keepDialogOpen||this.close()};handleSlotKeydown=e=>{if("Enter"===e.key&&this.confirmText){const t=e.target;if("INPUT"===t.tagName||"TEXTAREA"===t.tagName){e.preventDefault();const t=this.shadowRoot.querySelector("form");t&&t.requestSubmit()}}};handleKeydown=e=>{this.disableKeyboardClose||27!==e.keyCode||this.close()};connectedCallback(){super.connectedCallback()}disconnectedCallback(){super.disconnectedCallback(),window.removeEventListener("keydown",this.handleKeydown)}updated(e){super.updated(e),e.has("opened")&&this.dispatchEvent(new CustomEvent(this.opened?"opened":"close"))}open(){this.opened=!0,window.addEventListener("keydown",this.handleKeydown),document.body.classList.add("no-scroll");const e=this.updateComplete||this.requestUpdate();e&&e.then?e.then(()=>{const e=this.shadowRoot.querySelector("[autofocus]")||firstFocusable(this.shadowRoot);e&&e.focus()}):setTimeout(()=>{const e=this.shadowRoot.querySelector("[autofocus]")||firstFocusable(this.shadowRoot);e&&e.focus()},0)}close(){this.opened=!1,this.blur(),this.closeCallback(),window.removeEventListener("keydown",this.handleKeydown),document.body.classList.remove("no-scroll")}toggle(){this.opened?this.close():this.open()}focus(){const e=firstFocusable(this.shadowRoot);e&&(this.previousFocus=document.activeElement,e.focus())}blur(){this.previousFocus&&this.previousFocus.focus()}hasTitle(){return!!this.querySelector('[slot="title"]')}static styles=css`
2
2
  :host {
3
3
  position: fixed;
4
4
  top: 0;
@@ -75,14 +75,15 @@ import ShadowComponent from"./ShadowComponent.js";import{html,css}from"../lit-al
75
75
  padding: var(--spacer_h);
76
76
  }
77
77
  `;render(){return html`
78
- <button id="overlay" aria-label="Close the Dialog" @click=${this.handleClick}></button>
78
+ <button id="overlay" type="button" aria-label="Close the Dialog" @click=${this.handleClick}></button>
79
79
  <div id="wrapper">
80
80
  <k-focus-capture>
81
- <div
81
+ <form
82
82
  id="dialog"
83
83
  role="dialog"
84
84
  aria-modal="true"
85
85
  aria-labelledby="title"
86
+ @submit=${this.handleSubmit}
86
87
  >
87
88
  <div
88
89
  id="header"
@@ -92,29 +93,29 @@ import ShadowComponent from"./ShadowComponent.js";import{html,css}from"../lit-al
92
93
  <slot name="title"></slot>
93
94
  </div>
94
95
  ${this.closeBtn?html`
95
- <button id="close" @click=${this.handleClick}>
96
+ <button id="close" type="button" @click=${this.handleClick}>
96
97
  <k-icon name="close"></k-icon>
97
98
  </button>
98
99
  `:""}
99
100
  </div>
100
101
  <div id="body">
101
- <slot></slot>
102
+ <slot @keydown=${this.handleSlotKeydown}></slot>
102
103
  </div>
103
104
  ${this.cancelText||this.confirmText?html`
104
105
  <div id="footer">
105
106
  ${this.cancelText?html`
106
- <button id="cancel" class="${this.cancelClasses}" @click=${this.handleClick}>
107
+ <button id="cancel" type="button" class="${this.cancelClasses}" @click=${this.handleClick}>
107
108
  ${this.cancelText}
108
109
  </button>
109
110
  `:""}
110
111
  ${this.confirmText?html`
111
- <button id="confirm" class="${this.confirmClasses}" @click=${this.handleClick}>
112
+ <button id="confirm" type="submit" class="${this.confirmClasses}">
112
113
  ${this.confirmText}
113
114
  </button>
114
115
  `:""}
115
116
  </div>
116
117
  `:""}
117
- </div>
118
+ </form>
118
119
  </k-focus-capture>
119
120
  </div>
120
121
  `}static create(e="",t={}){!1!==t.closeExisting&&document.querySelectorAll("k-dialog").forEach(e=>e.close());const{removeOnClose:o=!0,closeCallback:s=()=>{},title:i="",titleClasses:l="pyh px m0"}=t,a=new Dialog;if(Object.assign(a,{opened:!0,...t,closeCallback:(...e)=>{o&&a.remove(),s(...e)}}),i){const e=document.createElement("h5");e.slot="title",e.className=l,i instanceof HTMLElement?e.appendChild(i):e.innerHTML=i,a.appendChild(e)}if(e instanceof HTMLElement||e instanceof DocumentFragment)a.appendChild(e);else if(e){if(/<[^>]+>/.test(e))a.innerHTML+=e;else{const t=document.createElement("p");t.className="p",t.textContent=e,a.appendChild(t)}}return t.width&&a.style.setProperty("--width",t.width),t.minWidth&&a.style.setProperty("--min_width",t.minWidth),t.maxWidth&&a.style.setProperty("--max_width",t.maxWidth),t.height&&a.style.setProperty("--height",t.height),t.minHeight&&a.style.setProperty("--min_height",t.minHeight),t.maxHeight&&a.style.setProperty("--max_height",t.maxHeight),document.body.appendChild(a),a.open(),a}static confirm(e,t,o={}){return Dialog.create(e,{title:o.title||"Confirm",closeBtn:!1,overlayClose:!1,disableKeyboardClose:!0,confirmText:"Yes",confirmClasses:"success ml",confirmAction:()=>t(!0),cancelText:"No",cancelClasses:"danger",cancelAction:()=>t(!1),...o})}static alert(e,t=()=>{},o={}){return Dialog.create(e,{title:o.title||"Alert",closeCallback:t,cancelText:"Ok",...o})}static error(e,t=()=>{},o={}){return Dialog.create(e,{title:o.title||"Error",titleClasses:"pyh px m0 tc-danger",closeCallback:t,cancelText:"Ok",...o})}static success(e,t=()=>{},o={}){return Dialog.create(e,{title:o.title||"Success",titleClasses:"pyh px m0 tc-success",closeCallback:t,cancelText:"Ok",...o})}}customElements.define("k-dialog",Dialog);
@@ -0,0 +1,91 @@
1
+ import{html,css}from"../lit-all.min.js";import ShadowComponent from"./ShadowComponent.js";import formatCode from"../utils/formatCode.js";import debounce from"../utils/debounce.js";export default class HtmlEditor extends ShadowComponent{static formAssociated=!0;static properties={name:{type:String,reflect:!0},value:{type:String,reflect:!0},selection:{type:Object,state:!0},mode:{type:String,reflect:!0},hasTopToolbar:{type:Boolean,state:!0},hasBottomToolbar:{type:Boolean,state:!0}};constructor(){super(),this.internals=this.attachInternals(),this.name="",this.value="",this.selection=null,this.cursor=null,this.mode="visual",this.hasTopToolbar=!1,this.hasBottomToolbar=!1,this.skipSync=!1,this.savedSelection=null,this.debouncedDispatchChange=debounce(()=>{this.value=this.editorEl.innerHTML},300)}connectedCallback(){super.connectedCallback(),this.hasAttribute("value")&&(this.value=this.getAttribute("value")),this.slotObserver=new MutationObserver(()=>{this.updateToolbarVisibility()}),this.slotObserver.observe(this,{childList:!0,subtree:!0}),this.updateToolbarVisibility()}disconnectedCallback(){super.disconnectedCallback(),this.slotObserver?.disconnect(),this.boundUpdateSelection&&(this.shadowRoot.removeEventListener("selectionchange",this.boundUpdateSelection),this.editorEl&&(this.editorEl.removeEventListener("mouseup",this.boundUpdateSelection),this.editorEl.removeEventListener("keyup",this.boundUpdateSelection),this.editorEl.removeEventListener("focus",this.boundUpdateSelection)))}updateToolbarVisibility(){const e=Array.from(this.children).filter(e=>"toolbar-top"===e.getAttribute("slot")),t=Array.from(this.children).filter(e=>"toolbar-bottom"===e.getAttribute("slot"));this.hasTopToolbar=e.length>0,this.hasBottomToolbar=t.length>0}updated(e){super.updated(e),e.has("value")&&(this.syncContent(),this.updateFormValue(),this.dispatchEvent(new CustomEvent("change",{detail:{value:this.value},bubbles:!0}))),e.has("mode")&&(this.syncContent(),this.dispatchEvent(new CustomEvent("mode-changed",{detail:{mode:this.mode},bubbles:!0})))}firstUpdated(){this.editorEl=this.shadowRoot.querySelector(".editor"),this.textareaEl=this.shadowRoot.querySelector("textarea"),this.value&&this.syncContent(),this.boundUpdateSelection=this.updateSelection.bind(this),this.shadowRoot.addEventListener("selectionchange",this.boundUpdateSelection),this.editorEl.addEventListener("mouseup",this.boundUpdateSelection),this.editorEl.addEventListener("keyup",this.boundUpdateSelection),this.editorEl.addEventListener("focus",this.boundUpdateSelection),this.editorEl.addEventListener("keydown",this.handleTableNavigation),this.dispatchEvent(new CustomEvent("ready",{detail:{value:this.value},bubbles:!0}))}updateFormValue(){this.internals.setFormValue(this.getValue())}formResetCallback(){this.value=""}formStateRestoreCallback(e){this.value=e}handleTableNavigation=e=>{if("Tab"!==e.key&&"Enter"!==e.key)return;if("visual"!==this.mode)return;const t=this.shadowRoot.getSelection()||window.getSelection();if(!t.rangeCount)return;let n=t.getRangeAt(0).startContainer;n.nodeType===Node.TEXT_NODE&&(n=n.parentElement);const o=n.closest("td, th");if(o)if(e.preventDefault(),"Tab"===e.key){const n=o.parentElement,i=Array.from(n.cells),r=i.indexOf(o);if(e.shiftKey){let e=i[r-1];if(!e){const t=n.previousElementSibling;t&&(e=t.cells[t.cells.length-1])}if(e){const n=document.createRange();n.selectNodeContents(e),n.collapse(!1),t.removeAllRanges(),t.addRange(n),e.focus()}}else{let e=i[r+1];if(!e){const t=n.nextElementSibling;t&&(e=t.cells[0])}if(e){const n=document.createRange();n.selectNodeContents(e),n.collapse(!1),t.removeAllRanges(),t.addRange(n),e.focus()}}}else if("Enter"===e.key){const n=o.parentElement,i=Array.from(n.cells).indexOf(o);if(e.shiftKey){const e=n.previousElementSibling;if(e&&e.cells[i]){const n=e.cells[i],o=document.createRange();o.selectNodeContents(n),o.collapse(!1),t.removeAllRanges(),t.addRange(o),n.focus()}}else{const e=n.nextElementSibling;if(e&&e.cells[i]){const n=e.cells[i],o=document.createRange();o.selectNodeContents(n),o.collapse(!1),t.removeAllRanges(),t.addRange(o),n.focus()}else{const e=o.closest("table");if(e){const n=document.createElement("p");n.innerHTML="<br>",e.nextSibling?e.parentNode.insertBefore(n,e.nextSibling):e.parentNode.appendChild(n);const o=document.createRange();o.setStart(n,0),o.setEnd(n,0),t.removeAllRanges(),t.addRange(o),n.focus()}}}}};updateSelection=()=>{if("visual"!==this.mode)return void(this.selection=null);const e=this.shadowRoot.getSelection()||window.getSelection();if(!e.rangeCount)return void(this.selection=null);const t=e.getRangeAt(0),n=this.editorEl.contains(t.startContainer)||this.editorEl===t.startContainer,o=this.editorEl.contains(t.endContainer)||this.editorEl===t.endContainer;if(!n||!o)return void(this.selection=null);const i=t.toString();t.collapsed?(this.selection=null,this.cursor={container:t.startContainer,offset:t.startOffset}):(this.cursor=null,this.selection={startContainer:t.startContainer,startOffset:t.startOffset,endContainer:t.endContainer,endOffset:t.endOffset,text:i,collapsed:!1})};handleEditorInput=()=>{this.debouncedDispatchChange(),this.dispatchEvent(new CustomEvent("input",{detail:{value:this.editorEl.innerHTML}}))};handleTextareaInput=e=>{"code"===this.mode&&(this.value=e.target.value)};handleEditorPaste=e=>{e.preventDefault();const t=e.clipboardData.getData("text/plain");document.execCommand("insertText",!1,t)};handleEditorBlur=()=>{if("visual"===this.mode){const e=this.shadowRoot.getSelection()||window.getSelection();if(e.rangeCount>0){const t=e.getRangeAt(0);this.editorEl.contains(t.commonAncestorContainer)&&(this.savedSelection={startContainer:t.startContainer,startOffset:t.startOffset,endContainer:t.endContainer,endOffset:t.endOffset,collapsed:t.collapsed})}const t=this.cleanupHtml(this.editorEl.innerHTML);t!==this.value&&(this.value=t,this.syncContent())}};setMode(e){return["visual","code"].includes(e)&&("visual"===this.mode&&"code"===e&&(this.value=this.cleanupHtml(this.editorEl.innerHTML)),this.mode=e),this}toggleMode(){const e="visual"===this.mode?"code":"visual";return this.setMode(e),this}getValue(){if("visual"===this.mode){return(this.editorEl?this.editorEl.innerHTML:this.value).replace(/\u200B/g,"")}return this.value}setValue(e){return this.value=e,this.syncContent(),this}clear(){return this.value="",this.syncContent(),this}cleanupHtml(e){const t=document.createElement("div");t.innerHTML=e;const n=(e,t)=>{e.querySelectorAll(t).forEach(n=>{let o=n.parentElement;for(;o&&o!==e;){if(o.tagName.toLowerCase()===t){for(;n.firstChild;)n.parentNode.insertBefore(n.firstChild,n);n.parentNode.removeChild(n);break}o=o.parentElement}})};return["code","a","span","strong","em","b","i","u","s"].forEach(e=>n(t,e)),["pre","div","p","h1","h2","h3","h4","h5","h6","blockquote"].forEach(e=>n(t,e)),t.querySelectorAll("*").forEach(e=>{if(!e.hasAttribute("style"))return;const t=e.style,n=["inherit","rgba(0, 0, 0, 0)","transparent","initial","unset"];["font-size","font-family","background-color","line-height","font-weight","font-style"].forEach(e=>{const o=t.getPropertyValue(e);o&&n.some(e=>o.includes(e))&&t.removeProperty(e)}),0===t.length&&e.removeAttribute("style")}),(e=>{e.querySelectorAll("span").forEach(e=>{if(!e.className&&!e.id&&!e.hasAttribute("style")){for(;e.firstChild;)e.parentNode.insertBefore(e.firstChild,e);e.parentNode.removeChild(e)}})})(t),(e=>{const t=document.createTreeWalker(e,NodeFilter.SHOW_TEXT,null),n=[];for(;t.nextNode();)n.push(t.currentNode);n.forEach(e=>{e.textContent=e.textContent.replace(/\u00A0/g," ")})})(t),t.innerHTML}bold(){return this.execCommand("bold"),this}italic(){return this.execCommand("italic"),this}underline(){return this.execCommand("underline"),this}strikethrough(){return this.execCommand("strikeThrough"),this}orderedList(){return this.execCommand("insertOrderedList"),this}unorderedList(){return this.execCommand("insertUnorderedList"),this}alignLeft(){return this.execCommand("justifyLeft"),this}alignCenter(){return this.execCommand("justifyCenter"),this}alignRight(){return this.execCommand("justifyRight"),this}alignJustify(){return this.execCommand("justifyFull"),this}setTextColor(e){return this.execCommand("foreColor",e),this}removeTextColor(){return this.execCommand("removeFormat"),this}setTextBackgroundColor(e){return this.execCommand("backColor",e),this}removeTextBackgroundColor(){const e=this.shadowRoot.getSelection();if(!e||0===e.rangeCount)return this;const t=e.getRangeAt(0);if(t.collapsed)return this;const n=t.extractContents(),o=document.createTreeWalker(n,NodeFilter.SHOW_ELEMENT,null),i=e=>{e.style&&(e.style.backgroundColor="",e.style.cssText||e.removeAttribute("style"))};let r;for(;r=o.nextNode();)i(r);return i(n.firstChild),t.insertNode(n),this.editorEl.dispatchEvent(new Event("input",{bubbles:!0})),this}removeFormat(){if("visual"!==this.mode)return this;if(this.selection)return this.execCommand("removeFormat"),this;const e=this.shadowRoot.getSelection()||window.getSelection();if(!e.rangeCount)return this;let t=e.getRangeAt(0).startContainer;t.nodeType===Node.TEXT_NODE&&(t=t.parentElement);const n=t.closest(["B","I","U","S","STRONG","EM","CODE","MARK","SUB","SUP","SPAN"].map(e=>e.toLowerCase()).join(","));if(n&&this.editorEl.contains(n)){const t=n.textContent,o=document.createTextNode(t);n.replaceWith(o);const i=document.createRange();i.setStart(o,t.length),i.setEnd(o,t.length),e.removeAllRanges(),e.addRange(i),this.value=this.editorEl.innerHTML}return this}formatBlock(e){return this.execCommand("formatBlock",e),this}insertHTML(e){return this.execCommand("insertHTML",e),this}insertTable(e,t,n=!1,o=null){if("visual"!==this.mode)return this;const i=this.getValueWithSelectionMarkers();let r=i.html;const s=document.createElement("table");if(s.style.cssText="border-collapse: collapse; width: 100%;",n){const e=document.createElement("thead"),n=document.createElement("tr");for(let e=0;e<t;e++){const t=document.createElement("th");t.style.cssText="border: 1px solid var(--border-color, #ccc); padding: 0.5rem; text-align: left; background-color: var(--background-secondary, #f5f5f5);",o&&o[0]&&void 0!==o[0][e]?t.innerHTML=o[0][e]:t.innerHTML=`Header ${e+1}`,n.appendChild(t)}e.appendChild(n),s.appendChild(e)}const l=document.createElement("tbody"),a=n?1:0;for(let n=0;n<e;n++){const e=document.createElement("tr");for(let i=0;i<t;i++){const t=document.createElement("td");t.style.cssText="border: 1px solid var(--border-color, #ccc); padding: 0.5rem;",o&&o[n+a]&&void 0!==o[n+a][i]?t.innerHTML=o[n+a][i]:t.innerHTML="​",e.appendChild(t)}l.appendChild(e)}s.appendChild(l);const d=document.createElement("div");d.appendChild(s);const c=d.innerHTML,h=document.createElement("p");h.innerHTML="<br>";const u=c+h.outerHTML;i.hasCursor?r=r.replace("￰",u+"￰"):i.hasSelection?(r=r.replace(i.selectionStart,"").replace(i.selectionEnd,""),r+=u):r+=u;const m={hasCursor:i.hasCursor,hasSelection:!1,cursorMarker:"￰"};return this.setValueFromSelectionMarkers(r,m),this}insertElementAtCursor(e,t=!0){if("visual"!==this.mode)return this;const n=this.getValueWithSelectionMarkers();let o=n.html;t&&(e.textContent="￱"+(e.textContent||"")+"￲");const i=e.outerHTML;if(n.hasCursor)o=o.replace("￰",i);else{const t=["code","a","span","strong","em","b","i","u","s","mark","sub","sup"],n=e.tagName.toLowerCase();t.includes(n)?o=o+"<p>"+i+"</p>":o+=i}const r={hasCursor:!1,hasSelection:t,selectionStart:"￱",selectionEnd:"￲",selectedText:e.textContent||""};return this.setValueFromSelectionMarkers(o,r),this}replaceSelectionWithElement(e,t=!0){if("visual"!==this.mode)return this;const n=this.getValueWithSelectionMarkers();if(!n.hasSelection)return this;let o=n.html;t&&(e.textContent="￱"+e.textContent+"￲");const i=e.outerHTML,r="￱"+n.selectedText+"￲";o=o.replace(r,i);const s={hasCursor:!1,hasSelection:t,selectionStart:"￱",selectionEnd:"￲",selectedText:n.selectedText};return this.setValueFromSelectionMarkers(o,s),this}getSelection(){if("visual"!==this.mode)return null;this.editorEl.focus();const e=window.getSelection();if(!e.rangeCount)return null;const t=e.getRangeAt(0);return this.editorEl.contains(t.commonAncestorContainer)?{text:e.toString(),html:t.cloneContents().textContent?(new XMLSerializer).serializeToString(t.cloneContents()):"",range:t,selection:e}:null}getSelectedText(){const e=this.getSelection();return e?e.text:""}getSelectedHTML(){const e=this.getSelection();return e?e.html:""}setSelection(e,t,n,o){if("visual"!==this.mode)return this;this.editorEl.focus();const i=window.getSelection(),r=document.createRange();return r.setStart(e,t),r.setEnd(n,o),i.removeAllRanges(),i.addRange(r),this}selectAll(){return"visual"===this.mode?this.execCommand("selectAll"):this.textareaEl.select(),this}replaceSelection(e){if("visual"!==this.mode)return this;const t=this.getSelection();if(!t)return this;t.range.deleteContents();const n=t.range.createContextualFragment(e);return t.range.insertNode(n),this.value=this.editorEl.innerHTML,this}deleteSelection(){if("visual"===this.mode)this.execCommand("delete");else{const e=this.textareaEl.selectionStart,t=this.textareaEl.selectionEnd;this.textareaEl.value=this.textareaEl.value.substring(0,e)+this.textareaEl.value.substring(t),this.value=this.textareaEl.value}return this}getTextPosition(e,t){const n=document.createTreeWalker(e,NodeFilter.SHOW_TEXT,null);let o=0;for(;n.nextNode();){const e=n.currentNode;if(t.contains(e))return o;o+=e.textContent.length}return o}wrapSelection(e,t,n=null){if("visual"!==this.mode)return this;let o;if(n)o=n;else{const e=window.getSelection();if(!e.rangeCount||e.isCollapsed)return this;const t=e.getRangeAt(0);if(!this.editorEl.contains(t.commonAncestorContainer))return this;o=e.toString()}if(!o)return this;this.editorEl.focus();const i=e.match(/<([a-zA-Z]+)/);if(!i)return this;const r=i[1],s=document.createElement("div");s.innerHTML=this.getValue();const l=s.textContent.indexOf(o);if(-1===l)return this;const a=document.createTreeWalker(s,NodeFilter.SHOW_TEXT|NodeFilter.SHOW_ELEMENT,null);let d=0,c=null,h=0,u=null,m=0;const f=[];for(;a.nextNode();){const e=a.currentNode;if(e.nodeType===Node.ELEMENT_NODE){if(e.tagName.toLowerCase()===r){const t=this.getTextPosition(s,e),n=t+e.textContent.length,i=l+o.length;!(n<=l||t>=i)&&f.push(e)}continue}const t=e.textContent.length,n=d,i=d+t;if(!c&&i>l&&(c=e,h=l-n),i>=l+o.length){u=e,m=l+o.length-n;break}d+=t}if(f.length>0)f.forEach(e=>{const t=e.parentNode,n="pre"===e.tagName.toLowerCase(),o=[],i=e=>{o.push(e),Array.from(e.children).forEach(e=>i(e))};if(i(e),n){const n=document.createElement("p");for(;e.firstChild;)n.appendChild(e.firstChild);t.replaceChild(n,e)}else o.reverse().forEach(e=>{if(e.parentNode){for(;e.firstChild;)e.parentNode.insertBefore(e.firstChild,e);e.parentNode.removeChild(e)}})});else if(c&&u){const n=document.createRange();n.setStart(c,h),n.setEnd(u,m);const o=n.extractContents(),i=document.createElement("div");i.innerHTML=e+t;const l=i.firstChild;let a=l;for(;a.firstChild&&a.firstChild.nodeType===Node.ELEMENT_NODE;)a=a.firstChild;if(a.appendChild(o),n.insertNode(l),"pre"===r){let e=l.parentNode;for(;e&&e!==s;){if(e.tagName&&"p"===e.tagName.toLowerCase()){const t=[];let n=l.nextSibling;for(;n;){const e=n.nextSibling;t.push(n),n=e}if(e.parentNode.insertBefore(l,e.nextSibling),t.length>0&&t.some(e=>e.textContent.trim())){const e=document.createElement("p");t.forEach(t=>e.appendChild(t)),l.parentNode.insertBefore(e,l.nextSibling)}break}e=e.parentNode}}}return this.setValue(s.innerHTML),this.editorEl.focus(),setTimeout(()=>{const e=this.editorEl.textContent.indexOf(o);if(-1===e)return;const t=document.createTreeWalker(this.editorEl,NodeFilter.SHOW_TEXT,null);let n=0,i=null,r=0,s=null,l=0;for(;t.nextNode();){const a=t.currentNode,d=a.textContent.length,c=n,h=n+d;if(null===i&&h>e&&(i=a,r=e-c),h>=e+o.length){s=a,l=e+o.length-c;break}n+=d}if(i&&s){const e=document.createRange();e.setStart(i,r),e.setEnd(s,l);const t=window.getSelection();t.removeAllRanges(),t.addRange(e)}},0),this}insertAtCursor(e){return this.insertHTML(e),this}createLink(e){return this.execCommand("createLink",e),this}unlink(){return this.execCommand("unlink"),this}insertImage(e){return this.execCommand("insertImage",e),this}undo(){return this.execCommand("undo"),this}redo(){return this.execCommand("redo"),this}execCommand(e,t=null){"visual"===this.mode&&(this.editorEl.focus(),document.execCommand(e,!1,t),this.value=this.editorEl.innerHTML)}captureSelection(){if("visual"!==this.mode)return null;const e=window.getSelection();if(!e.rangeCount)return null;const t=e.getRangeAt(0);if(!this.editorEl.contains(t.commonAncestorContainer))return null;const n=t.toString();return t.collapsed?null:{startContainer:t.startContainer,startOffset:t.startOffset,endContainer:t.endContainer,endOffset:t.endOffset,text:n,collapsed:t.collapsed}}restoreSavedSelection(){if(!this.savedSelection)return!1;const e=this.shadowRoot.getSelection()||window.getSelection(),t=document.createRange();try{return t.setStart(this.savedSelection.startContainer,this.savedSelection.startOffset),t.setEnd(this.savedSelection.endContainer,this.savedSelection.endOffset),e.removeAllRanges(),e.addRange(t),!0}catch(e){return this.savedSelection=null,!1}}clearSavedSelection(){this.savedSelection=null}getValueWithSelectionMarkers(){if("visual"!==this.mode)return{html:this.value,hasCursor:!1,hasSelection:!1};const e=this.shadowRoot.getSelection()||window.getSelection();if(!e.rangeCount&&!this.restoreSavedSelection())return{html:this.editorEl.innerHTML,hasCursor:!1,hasSelection:!1};if(!e.rangeCount)return{html:this.editorEl.innerHTML,hasCursor:!1,hasSelection:!1};const t=e.getRangeAt(0);if(!this.editorEl.contains(t.commonAncestorContainer))return{html:this.editorEl.innerHTML,hasCursor:!1,hasSelection:!1};const n=t.toString();if(t.collapsed){const e=document.createTextNode("￰"),n=document.createRange();n.setStart(t.startContainer,t.startOffset),n.insertNode(e);const o=this.editorEl.innerHTML;return e.remove(),{html:o,hasCursor:!0,hasSelection:!1,cursorMarker:"￰",selectedText:""}}{const e=document.createTextNode("￱"),o=document.createTextNode("￲"),i=document.createRange();i.setStart(t.endContainer,t.endOffset),i.insertNode(o),i.setStart(t.startContainer,t.startOffset),i.insertNode(e);const r=this.editorEl.innerHTML;return e.remove(),o.remove(),{html:r,hasCursor:!1,hasSelection:!0,selectionStart:"￱",selectionEnd:"￲",selectedText:n}}}setValueFromSelectionMarkers(e,t){if("visual"!==this.mode)return this;const n=t.cursorMarker||"￰",o=t.selectionStart||"￱",i=t.selectionEnd||"￲";if(this.editorEl.innerHTML=e,t.hasCursor){const e=document.createTreeWalker(this.editorEl,NodeFilter.SHOW_TEXT,null);for(;e.nextNode();){const t=e.currentNode,o=t.textContent.indexOf(n);if(-1!==o){t.textContent=t.textContent.replace(n,"");const e=document.createRange();e.setStart(t,o),e.setEnd(t,o);const i=this.shadowRoot.getSelection()||window.getSelection();i.removeAllRanges(),i.addRange(e);break}}}else if(t.hasSelection){const e=document.createTreeWalker(this.editorEl,NodeFilter.SHOW_TEXT,null);let t=null,n=0,r=null,s=0;for(;e.nextNode();){const l=e.currentNode,a=l.textContent.indexOf(o);-1!==a&&(t=l,n=a,l.textContent=l.textContent.replace(o,""));const d=l.textContent.indexOf(i);if(-1!==d&&(r=l,s=d,l.textContent=l.textContent.replace(i,"")),t&&r)break}if(t&&r){const e=document.createRange();e.setStart(t,n),e.setEnd(r,s);const o=this.shadowRoot.getSelection()||window.getSelection();o.removeAllRanges(),o.addRange(e)}}return this.editorEl.focus(),this}syncContent(){if(this.editorEl&&this.textareaEl&&!this.skipSync)if("visual"===this.mode)this.editorEl.innerHTML!==this.value&&(this.editorEl.innerHTML=this.value);else{const e=formatCode(this.value);this.textareaEl.value!==e&&(this.textareaEl.value=e)}}static styles=css`
2
+ :host {
3
+ display: flex;
4
+ flex-direction: column;
5
+ gap: 0;
6
+ height: 300px;
7
+ }
8
+
9
+ .toolbar-top,
10
+ .toolbar-bottom {
11
+ display: flex;
12
+ flex-wrap: wrap;
13
+ align-items: center;
14
+ gap: 0;
15
+ background: var(--bg-secondary);
16
+ min-height: 40px;
17
+ }
18
+
19
+ .editor-container {
20
+ position: relative;
21
+ flex: 1;
22
+ min-height: 0;
23
+ overflow: hidden;
24
+ }
25
+
26
+ .editor,
27
+ textarea {
28
+ width: 100%;
29
+ height: 100%;
30
+ padding: 1rem;
31
+ border: 1px solid var(--border-color);
32
+ background: var(--bg-primary);
33
+ color: var(--text-primary);
34
+ font-family: inherit;
35
+ font-size: inherit;
36
+ line-height: 1.5;
37
+ overflow: auto;
38
+ box-sizing: border-box;
39
+ }
40
+
41
+ .editor {
42
+ outline: none;
43
+ }
44
+
45
+ .editor:focus {
46
+ border-color: var(--primary-color);
47
+ }
48
+
49
+ textarea {
50
+ resize: none;
51
+ font-family: monospace;
52
+ }
53
+
54
+ textarea:focus {
55
+ outline: none;
56
+ border-color: var(--primary-color);
57
+ }
58
+
59
+ .editor[hidden],
60
+ textarea[hidden] {
61
+ display: none;
62
+ }
63
+ `;render(){return html`
64
+ ${this.hasTopToolbar?html`
65
+ <div class="toolbar-top bb">
66
+ <slot name="toolbar-top"></slot>
67
+ </div>
68
+ `:""}
69
+
70
+ <div class="editor-container">
71
+ <div
72
+ class="editor"
73
+ contenteditable="true"
74
+ ?hidden=${"visual"!==this.mode}
75
+ @input=${this.handleEditorInput}
76
+ @paste=${this.handleEditorPaste}
77
+ @blur=${this.handleEditorBlur}
78
+ ></div>
79
+
80
+ <textarea
81
+ ?hidden=${"code"!==this.mode}
82
+ @input=${this.handleTextareaInput}
83
+ ></textarea>
84
+ </div>
85
+
86
+ ${this.hasBottomToolbar?html`
87
+ <div class="toolbar-bottom bt">
88
+ <slot name="toolbar-bottom"></slot>
89
+ </div>
90
+ `:""}
91
+ `}}customElements.define("k-html-editor",HtmlEditor);
@@ -1,4 +1,4 @@
1
- import ShadowComponent from"./ShadowComponent.js";import{html,css,unsafeHTML}from"../lit-all.min.js";const cache={},getIconByPath=async t=>(cache[t]||(cache[t]=new Promise(async(n,e)=>{const i=(new AbortController).signal;try{const e=await fetch(t,{signal:i});200===e.status?n(await e.text()):404===e.status&&n(null)}catch(t){"AbortError"!==t.name&&e(t)}}).catch(()=>null)),await cache[t]),getIconByName=t=>{const n=async n=>{const e=n.endsWith("/")?n.slice(0,-1):n;return getIconByPath(`${e}/${t}.svg`)};return new Promise(async(t,e)=>{let i;const o=window.kempo?.pathsToIcons||["/icons","../../icons","https://cdn.jsdelivr.net/npm/kempo-ui@0.0.32/icons/"];for(let t=0;t<o.length&&!i;t++)try{i=await n(o[t])}catch(t){}t(i||null)})};export default class Icon extends ShadowComponent{static properties={src:{type:String,reflect:!0},name:{type:String,reflect:!0},rotation:{type:String,reflect:!0},direction:{type:String,reflect:!0},animation:{type:String,reflect:!0},iconContent:{type:String}};constructor(t=""){super(),this.src="",this.name=t,this.rotation="",this.direction="",this.animation="",this.iconContent=""}updated(t){super.updated(),(t.has("src")||t.has("name"))&&this.loadIcon()}async loadIcon(){let t;if(this.src?t=await getIconByPath(this.src):this.name&&(t=await getIconByName(this.name)),t)this.iconContent=this.fixSVG(t);else{const t=this.innerHTML.trim();this.iconContent=t?this.fixSVG(t):window.kempo?.fallbackIcon||'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 -960 960 960"><path fill="currentColor" d="M480-79q-16 0-30.5-6T423-102L102-423q-11-12-17-26.5T79-480q0-16 6-31t17-26l321-321q12-12 26.5-17.5T480-881q16 0 31 5.5t26 17.5l321 321q12 11 17.5 26t5.5 31q0 16-5.5 30.5T858-423L537-102q-11 11-26 17t-31 6Zm0-80 321-321-321-321-321 321 321 321Zm-40-281h80v-240h-80v240Zm40 120q17 0 28.5-11.5T520-360q0-17-11.5-28.5T480-400q-17 0-28.5 11.5T440-360q0 17 11.5 28.5T480-320Zm0-160Z"/></svg>'}}fixSVG(t){if(!t)return t;const n=(new DOMParser).parseFromString(t,"image/svg+xml").querySelector("svg");return n?(n.removeAttribute("width"),n.removeAttribute("height"),n.querySelectorAll("path, rect, circle").forEach(t=>{t.setAttribute("fill","currentColor")}),(new XMLSerializer).serializeToString(n)):t}getRotationDegrees(){if(this.rotation)return this.rotation;return{down:"90",left:"180",up:"270"}[this.direction]||"0"}static styles=css`
1
+ import ShadowComponent from"./ShadowComponent.js";import{html,css,unsafeHTML}from"../lit-all.min.js";const cache={},getIconByPath=async t=>(cache[t]||(cache[t]=new Promise(async(e,n)=>{const i=(new AbortController).signal;try{const n=await fetch(t,{signal:i});200===n.status?e(await n.text()):e(null)}catch(t){"AbortError"!==t.name&&n(t)}}).catch(()=>null)),await cache[t]),getIconByName=t=>{const e=async e=>{const n=e.endsWith("/")?e.slice(0,-1):e;return getIconByPath(`${n}/${t}.svg`)};return new Promise(async(t,n)=>{let i;const o=window.kempo?.pathsToIcons||["./icons","/icons","../icons","../../icons"];for(let t=0;t<o.length&&!i;t++)try{i=await e(o[t])}catch(t){}t(i||null)})};export default class Icon extends ShadowComponent{static properties={src:{type:String,reflect:!0},name:{type:String,reflect:!0},rotation:{type:String,reflect:!0},direction:{type:String,reflect:!0},animation:{type:String,reflect:!0},iconContent:{type:String}};constructor(t=""){super(),this.src="",this.name=t,this.rotation="",this.direction="",this.animation="",this.iconContent=""}updated(t){super.updated(),(t.has("src")||t.has("name"))&&this.loadIcon()}async loadIcon(){let t;if(this.src?t=await getIconByPath(this.src):this.name&&(t=await getIconByName(this.name)),t)this.iconContent=this.fixSVG(t),this.requestUpdate();else{const t=this.innerHTML.trim();t?(this.iconContent=this.fixSVG(t),this.requestUpdate()):(this.iconContent=window.kempo?.fallbackIcon||'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 -960 960 960"><path fill="currentColor" d="M480-79q-16 0-30.5-6T423-102L102-423q-11-12-17-26.5T79-480q0-16 6-31t17-26l321-321q12-12 26.5-17.5T480-881q16 0 31 5.5t26 17.5l321 321q12 11 17.5 26t5.5 31q0 16-5.5 30.5T858-423L537-102q-11 11-26 17t-31 6Zm0-80 321-321-321-321-321 321 321 321Zm-40-281h80v-240h-80v240Zm40 120q17 0 28.5-11.5T520-360q0-17-11.5-28.5T480-400q-17 0-28.5 11.5T440-360q0 17 11.5 28.5T480-320Zm0-160Z"/></svg>',this.requestUpdate())}}fixSVG(t){if(!t)return t;const e=(new DOMParser).parseFromString(t,"image/svg+xml").querySelector("svg");return e?(e.removeAttribute("width"),e.removeAttribute("height"),e.querySelectorAll("path, rect, circle").forEach(t=>{t.setAttribute("fill","currentColor")}),(new XMLSerializer).serializeToString(e)):t}getRotationDegrees(){if(this.rotation)return this.rotation;return{down:"90",left:"180",up:"270"}[this.direction]||"0"}static styles=css`
2
2
  :host {
3
3
  display: inline-block;
4
4
  vertical-align: bottom;
@@ -39,4 +39,4 @@ import ShadowComponent from"./ShadowComponent.js";import{html,css,unsafeHTML}fro
39
39
  0%, 100% { transform: scale(1); }
40
40
  50% { transform: scale(1.2); }
41
41
  }
42
- `;render(){const t=this.getRotationDegrees(),n="0"!==t?`--icon-rotation: ${t}deg;`:"";return this.iconContent?html`<div style="${n}">${unsafeHTML(this.iconContent)}</div>`:html`<div style="${n}"><slot></slot></div>`}}window.customElements.define("k-icon",Icon);
42
+ `;render(){const t=this.getRotationDegrees(),e="0"!==t?`--icon-rotation: ${t}deg;`:"";return this.iconContent?html`<div style="${e}">${unsafeHTML(this.iconContent)}</div>`:html`<div style="${e}"><slot></slot></div>`}}window.customElements.define("k-icon",Icon);
@@ -1,17 +1,13 @@
1
- import ShadowComponent from"./ShadowComponent.js";import{html,css}from"../lit-all.min.js";export default class Main extends ShadowComponent{static properties={panelWidth:{type:String,state:!0},panelSide:{type:String,state:!0}};constructor(){super(),this.panelWidth="0px",this.panelSide="left",this.handlePanelChange=this.handlePanelChange.bind(this)}connectedCallback(){super.connectedCallback(),window.addEventListener("side-panel-change",this.handlePanelChange);const e=document.querySelector("k-side-panel");e&&(this.panelWidth=e.collapsed?"3.5rem":"16rem",this.panelSide=e.side||"left")}disconnectedCallback(){super.disconnectedCallback(),window.removeEventListener("side-panel-change",this.handlePanelChange)}handlePanelChange(e){this.panelWidth=e.detail.width,this.panelSide=e.detail.side}render(){return html`
1
+ import ShadowComponent from"./ShadowComponent.js";import{html,css}from"../lit-all.min.js";export default class Main extends ShadowComponent{static properties={leftPanelWidth:{type:String,state:!0},rightPanelWidth:{type:String,state:!0}};constructor(){super(),this.leftPanelWidth="0px",this.rightPanelWidth="0px",this.handlePanelChange=this.handlePanelChange.bind(this)}connectedCallback(){super.connectedCallback(),window.addEventListener("side-panel-change",this.handlePanelChange);const t=document.querySelector('k-side-panel:not([side="right"])');t&&(this.leftPanelWidth=t.collapsed?"3.5rem":"16rem");const e=document.querySelector('k-side-panel[side="right"]');e&&(this.rightPanelWidth=e.collapsed?"3.5rem":"16rem")}disconnectedCallback(){super.disconnectedCallback(),window.removeEventListener("side-panel-change",this.handlePanelChange)}handlePanelChange(t){"right"===t.detail.side?this.rightPanelWidth=t.detail.width:this.leftPanelWidth=t.detail.width}render(){return html`
2
2
  <main>
3
3
  <slot></slot>
4
4
  </main>
5
5
  `}static styles=css`
6
6
  :host {
7
7
  display: block;
8
- margin-left: var(--panel-width, 0px);
9
- transition: margin-left var(--animation_ms, 256ms);
10
- }
11
- :host([panel-side="right"]) {
12
- margin-left: 0;
13
- margin-right: var(--panel-width, 0px);
14
- transition: margin-right var(--animation_ms, 256ms);
8
+ margin-left: var(--left-panel-width, 0px);
9
+ margin-right: var(--right-panel-width, 0px);
10
+ transition: margin-left var(--animation_ms, 256ms), margin-right var(--animation_ms, 256ms);
15
11
  }
16
12
  main {
17
13
  max-width: var(--container_width, 90rem);
@@ -21,4 +17,4 @@ import ShadowComponent from"./ShadowComponent.js";import{html,css}from"../lit-al
21
17
  padding-left: var(--spacer);
22
18
  padding-right: var(--spacer);
23
19
  }
24
- `;updated(){super.updated(),this.style.setProperty("--panel-width",this.panelWidth),"right"===this.panelSide?this.setAttribute("panel-side","right"):this.removeAttribute("panel-side")}}window.customElements.define("k-main",Main);
20
+ `;updated(){super.updated(),this.style.setProperty("--left-panel-width",this.leftPanelWidth),this.style.setProperty("--right-panel-width",this.rightPanelWidth)}}window.customElements.define("k-main",Main);
@@ -1 +1 @@
1
- import{LitElement}from"../lit-all.min.js";export default class ShadowComponent extends LitElement{#e;connectedCallback(){super.connectedCallback(),this.#e=new MutationObserver(()=>{console.log("MutationObserver fired for",this.constructor.name),this.childrenUpdated(),this.requestUpdate()}),this.#e.observe(this,{childList:!0})}disconnectedCallback(){super.disconnectedCallback(),this.#e?.disconnect()}childrenUpdated(){}createRenderRoot(){const e=this.attachShadow({mode:"open"}),t=document.createElement("link");t.rel="stylesheet",t.href=window.kempo?.pathToStylesheet||"https://cdn.jsdelivr.net/npm/kempo-css@1.3.2/dist/kempo.min.css",e.appendChild(t);const n=this.constructor.styles;if(n){const t=document.createElement("style");Array.isArray(n)?t.textContent=n.map(e=>e.cssText||e).join("\n"):t.textContent=n.cssText||n,e.appendChild(t)}const s=document.createElement("div");return s.style.display="contents",e.appendChild(s),s}}
1
+ import{LitElement}from"../lit-all.min.js";export default class ShadowComponent extends LitElement{#e;connectedCallback(){super.connectedCallback(),this.#e=new MutationObserver(()=>{this.childrenUpdated(),this.requestUpdate()}),this.#e.observe(this,{childList:!0})}disconnectedCallback(){super.disconnectedCallback(),this.#e?.disconnect()}childrenUpdated(){}createRenderRoot(){const e=this.attachShadow({mode:"open"}),t=document.createElement("link");t.rel="stylesheet",t.href=window.kempo?.pathToStylesheet||"https://cdn.jsdelivr.net/npm/kempo-css@1.3.2/dist/kempo.min.css",e.appendChild(t);const n=this.constructor.styles;if(n){const t=document.createElement("style");Array.isArray(n)?t.textContent=n.map(e=>e.cssText||e).join("\n"):t.textContent=n.cssText||n,e.appendChild(t)}const s=document.createElement("div");return s.style.display="contents",e.appendChild(s),s}}
@@ -1,4 +1,4 @@
1
- import ShadowComponent from"./ShadowComponent.js";import{html,css}from"../lit-all.min.js";import{boolTrueFalse}from"../utils/propConverters.js";import"./FocusCapture.js";import"./Icon.js";export default class SideMenu extends ShadowComponent{static properties={opened:{type:Boolean,reflect:!0},overlayClose:{type:Boolean,reflect:!0,attribute:"overlay-close",converter:boolTrueFalse},side:{type:String,reflect:!0}};constructor(){super(),this.opened=!1,this.overlayClose=!0,this.side="left"}overlayClick=()=>{this.overlayClose&&this.close()};updated(e){super.updated(e),e.has("opened")&&(this.opened?(document.body.classList.add("no-scroll"),this.dispatchEvent(new CustomEvent("change",{detail:"open"})),this.dispatchEvent(new CustomEvent("open"))):(document.body.classList.remove("no-scroll"),this.dispatchEvent(new CustomEvent("change",{detail:"close"})),this.dispatchEvent(new CustomEvent("close"))))}open(){this.opened=!0}close(){this.opened=!1}toggle(){this.opened?this.close():this.open(),this.dispatchEvent(new CustomEvent("toggle"))}render(){return html`
1
+ import ShadowComponent from"./ShadowComponent.js";import{html,css}from"../lit-all.min.js";import{boolTrueFalse}from"../utils/propConverters.js";import"./FocusCapture.js";import"./Icon.js";export default class SideMenu extends ShadowComponent{static properties={opened:{type:Boolean,reflect:!0},overlayClose:{type:Boolean,reflect:!0,attribute:"overlay-close",converter:boolTrueFalse},escClose:{type:Boolean,reflect:!0,attribute:"esc-close",converter:boolTrueFalse},side:{type:String,reflect:!0}};constructor(){super(),this.opened=!1,this.overlayClose=!0,this.escClose=!0,this.side="left"}overlayClick=()=>{this.overlayClose&&this.close()};handleKeyDown=e=>{this.escClose&&"Escape"===e.key&&this.close()};updated(e){super.updated(e),e.has("opened")&&(this.opened?(document.body.classList.add("no-scroll"),document.addEventListener("keydown",this.handleKeyDown),this.dispatchEvent(new CustomEvent("change",{detail:"open"})),this.dispatchEvent(new CustomEvent("open"))):(document.body.classList.remove("no-scroll"),document.removeEventListener("keydown",this.handleKeyDown),this.dispatchEvent(new CustomEvent("change",{detail:"close"})),this.dispatchEvent(new CustomEvent("close"))))}open(){this.opened=!0}close(){this.opened=!1}toggle(){this.opened?this.close():this.open(),this.dispatchEvent(new CustomEvent("toggle"))}render(){return html`
2
2
  <k-focus-capture>
3
3
  <div id="container">
4
4
  <button id="overlay" @click=${this.overlayClick}>