snice 3.4.0 → 3.5.0

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 (177) hide show
  1. package/bin/templates/base/package.json +2 -2
  2. package/bin/templates/social/package.json +2 -2
  3. package/bin/templates/social/src/styles/global.css +56 -47
  4. package/dist/components/avatar/snice-avatar.d.ts +2 -2
  5. package/dist/components/avatar/snice-avatar.js +20 -21
  6. package/dist/components/avatar/snice-avatar.js.map +1 -1
  7. package/dist/components/calendar/snice-calendar.d.ts +8 -2
  8. package/dist/components/calendar/snice-calendar.js +160 -82
  9. package/dist/components/calendar/snice-calendar.js.map +1 -1
  10. package/dist/components/chart/snice-chart.js +50 -18
  11. package/dist/components/chart/snice-chart.js.map +1 -1
  12. package/dist/components/checkbox/snice-checkbox.d.ts +4 -1
  13. package/dist/components/checkbox/snice-checkbox.js +46 -17
  14. package/dist/components/checkbox/snice-checkbox.js.map +1 -1
  15. package/dist/components/code-block/highlighter.d.ts +5 -0
  16. package/dist/components/code-block/highlighter.js +137 -0
  17. package/dist/components/code-block/highlighter.js.map +1 -0
  18. package/dist/components/code-block/highlighters/highlight.d.ts +64 -0
  19. package/dist/components/code-block/highlighters/highlight.js +108 -0
  20. package/dist/components/code-block/highlighters/highlight.js.map +1 -0
  21. package/dist/components/code-block/highlighters/prism.d.ts +41 -0
  22. package/dist/components/code-block/highlighters/prism.js +73 -0
  23. package/dist/components/code-block/highlighters/prism.js.map +1 -0
  24. package/dist/components/code-block/snice-code-block.d.ts +19 -1
  25. package/dist/components/code-block/snice-code-block.js +128 -29
  26. package/dist/components/code-block/snice-code-block.js.map +1 -1
  27. package/dist/components/code-block/snice-code-block.types.d.ts +15 -1
  28. package/dist/components/color-picker/snice-color-picker.d.ts +1 -0
  29. package/dist/components/color-picker/snice-color-picker.js +17 -6
  30. package/dist/components/color-picker/snice-color-picker.js.map +1 -1
  31. package/dist/components/date-picker/snice-date-picker.d.ts +1 -0
  32. package/dist/components/date-picker/snice-date-picker.js +16 -5
  33. package/dist/components/date-picker/snice-date-picker.js.map +1 -1
  34. package/dist/components/doc/snice-doc.d.ts +27 -73
  35. package/dist/components/doc/snice-doc.js +385 -534
  36. package/dist/components/doc/snice-doc.js.map +1 -1
  37. package/dist/components/draw/snice-draw.d.ts +4 -0
  38. package/dist/components/draw/snice-draw.js +134 -14
  39. package/dist/components/draw/snice-draw.js.map +1 -1
  40. package/dist/components/draw/snice-draw.types.d.ts +5 -0
  41. package/dist/components/file-upload/snice-file-upload.js +1 -1
  42. package/dist/components/input/snice-input.d.ts +2 -0
  43. package/dist/components/input/snice-input.js +34 -9
  44. package/dist/components/input/snice-input.js.map +1 -1
  45. package/dist/components/kanban/snice-kanban.d.ts +13 -1
  46. package/dist/components/kanban/snice-kanban.js +191 -36
  47. package/dist/components/kanban/snice-kanban.js.map +1 -1
  48. package/dist/components/kanban/snice-kanban.types.d.ts +11 -1
  49. package/dist/components/kpi/snice-kpi.js +5 -1
  50. package/dist/components/kpi/snice-kpi.js.map +1 -1
  51. package/dist/components/layout/snice-layout-sidebar.js +1 -1
  52. package/dist/components/layout/snice-layout-sidebar.js.map +1 -1
  53. package/dist/components/layout/snice-layout.js +1 -1
  54. package/dist/components/layout/snice-layout.js.map +1 -1
  55. package/dist/components/location/snice-location.js +1 -1
  56. package/dist/components/location/snice-location.js.map +1 -1
  57. package/dist/components/radio/snice-radio.d.ts +1 -0
  58. package/dist/components/radio/snice-radio.js +17 -6
  59. package/dist/components/radio/snice-radio.js.map +1 -1
  60. package/dist/components/select/snice-select.d.ts +2 -0
  61. package/dist/components/select/snice-select.js +48 -19
  62. package/dist/components/select/snice-select.js.map +1 -1
  63. package/dist/components/slider/snice-slider.d.ts +2 -0
  64. package/dist/components/slider/snice-slider.js +34 -14
  65. package/dist/components/slider/snice-slider.js.map +1 -1
  66. package/dist/components/snice-cell-HZ2iIBIC.js +4 -0
  67. package/dist/components/snice-cell-HZ2iIBIC.js.map +1 -0
  68. package/dist/components/split-pane/snice-split-pane.js +1 -1
  69. package/dist/components/split-pane/snice-split-pane.js.map +1 -1
  70. package/dist/components/switch/snice-switch.d.ts +1 -0
  71. package/dist/components/switch/snice-switch.js +16 -6
  72. package/dist/components/switch/snice-switch.js.map +1 -1
  73. package/dist/components/table/snice-cell-actions.js +1 -1
  74. package/dist/components/table/snice-cell-actions.js.map +1 -1
  75. package/dist/components/table/snice-cell-boolean.js +1 -1
  76. package/dist/components/table/snice-cell-color.js +1 -1
  77. package/dist/components/table/snice-cell-color.js.map +1 -1
  78. package/dist/components/table/snice-cell-currency.js +1 -1
  79. package/dist/components/table/snice-cell-date.js +1 -1
  80. package/dist/components/table/snice-cell-duration.js +1 -1
  81. package/dist/components/table/snice-cell-email.js +1 -1
  82. package/dist/components/table/snice-cell-email.js.map +1 -1
  83. package/dist/components/table/snice-cell-filesize.js +1 -1
  84. package/dist/components/table/snice-cell-image.js +1 -1
  85. package/dist/components/table/snice-cell-image.js.map +1 -1
  86. package/dist/components/table/snice-cell-json.js +1 -1
  87. package/dist/components/table/snice-cell-json.js.map +1 -1
  88. package/dist/components/table/snice-cell-link.js +1 -1
  89. package/dist/components/table/snice-cell-link.js.map +1 -1
  90. package/dist/components/table/snice-cell-location.js +1 -1
  91. package/dist/components/table/snice-cell-location.js.map +1 -1
  92. package/dist/components/table/snice-cell-number.js +1 -1
  93. package/dist/components/table/snice-cell-percentage.js +1 -1
  94. package/dist/components/table/snice-cell-percentage.js.map +1 -1
  95. package/dist/components/table/snice-cell-phone.js +1 -1
  96. package/dist/components/table/snice-cell-phone.js.map +1 -1
  97. package/dist/components/table/snice-cell-progress.js +3 -3
  98. package/dist/components/table/snice-cell-progress.js.map +1 -1
  99. package/dist/components/table/snice-cell-rating.js +2 -2
  100. package/dist/components/table/snice-cell-rating.js.map +1 -1
  101. package/dist/components/table/snice-cell-sparkline.js +2 -2
  102. package/dist/components/table/snice-cell-sparkline.js.map +1 -1
  103. package/dist/components/table/snice-cell-status.js +1 -1
  104. package/dist/components/table/snice-cell-status.js.map +1 -1
  105. package/dist/components/table/snice-cell-tag.js +1 -1
  106. package/dist/components/table/snice-cell-tag.js.map +1 -1
  107. package/dist/components/table/snice-cell-text.js +1 -1
  108. package/dist/components/table/snice-cell.js +15 -10
  109. package/dist/components/table/snice-cell.js.map +1 -1
  110. package/dist/components/table/snice-header.js +1 -1
  111. package/dist/components/table/snice-header.js.map +1 -1
  112. package/dist/components/table/snice-row.js +2 -2
  113. package/dist/components/table/snice-row.js.map +1 -1
  114. package/dist/components/table/snice-table.d.ts +1 -0
  115. package/dist/components/table/snice-table.js +24 -4
  116. package/dist/components/table/snice-table.js.map +1 -1
  117. package/dist/components/terminal/snice-terminal.d.ts +40 -0
  118. package/dist/components/terminal/snice-terminal.js +371 -0
  119. package/dist/components/terminal/snice-terminal.js.map +1 -0
  120. package/dist/components/terminal/snice-terminal.types.d.ts +20 -24
  121. package/dist/components/textarea/snice-textarea.d.ts +2 -0
  122. package/dist/components/textarea/snice-textarea.js +25 -6
  123. package/dist/components/textarea/snice-textarea.js.map +1 -1
  124. package/dist/components/theme/theme.css +16 -0
  125. package/dist/components/tree/snice-tree-item.d.ts +18 -4
  126. package/dist/components/tree/snice-tree-item.js +271 -88
  127. package/dist/components/tree/snice-tree-item.js.map +1 -1
  128. package/dist/components/tree/snice-tree-item.types.d.ts +3 -0
  129. package/dist/components/tree/snice-tree.d.ts +18 -2
  130. package/dist/components/tree/snice-tree.js +422 -56
  131. package/dist/components/tree/snice-tree.js.map +1 -1
  132. package/dist/components/tree/snice-tree.types.d.ts +1 -0
  133. package/dist/components/virtual-scroller/snice-virtual-scroller.js +4 -2
  134. package/dist/components/virtual-scroller/snice-virtual-scroller.js.map +1 -1
  135. package/dist/index.cjs +42 -23
  136. package/dist/index.cjs.map +1 -1
  137. package/dist/index.esm.js +42 -23
  138. package/dist/index.esm.js.map +1 -1
  139. package/dist/index.iife.js +42 -23
  140. package/dist/index.iife.js.map +1 -1
  141. package/dist/render-tracker.d.ts +1 -0
  142. package/dist/symbols.cjs +13 -14
  143. package/dist/symbols.cjs.map +1 -1
  144. package/dist/symbols.esm.js +13 -14
  145. package/dist/symbols.esm.js.map +1 -1
  146. package/dist/template.d.ts +1 -0
  147. package/dist/transitions.cjs +1 -1
  148. package/dist/transitions.esm.js +1 -1
  149. package/docs/ai/api.md +37 -4
  150. package/docs/ai/components/doc.md +41 -106
  151. package/docs/ai/components/kanban.md +31 -9
  152. package/docs/ai/components/kpi.md +15 -0
  153. package/docs/components/doc.md +96 -212
  154. package/docs/components/kanban.md +119 -4
  155. package/docs/components/kpi.md +27 -0
  156. package/package.json +4 -1
  157. package/dist/components/actions/snice-actions.d.ts +0 -28
  158. package/dist/components/actions/snice-actions.js +0 -220
  159. package/dist/components/actions/snice-actions.js.map +0 -1
  160. package/dist/components/actions/snice-actions.types.d.ts +0 -27
  161. package/dist/components/doc/snice-doc.types.d.ts +0 -118
  162. package/dist/components/gantt/snice-gantt.d.ts +0 -29
  163. package/dist/components/gantt/snice-gantt.js +0 -268
  164. package/dist/components/gantt/snice-gantt.js.map +0 -1
  165. package/dist/components/gantt/snice-gantt.types.d.ts +0 -23
  166. package/dist/components/snice-cell-C0slgOpe.js +0 -4
  167. package/dist/components/snice-cell-C0slgOpe.js.map +0 -1
  168. package/dist/components/stat/snice-stat.d.ts +0 -14
  169. package/dist/components/stat/snice-stat.js +0 -140
  170. package/dist/components/stat/snice-stat.js.map +0 -1
  171. package/dist/components/stat/snice-stat.types.d.ts +0 -12
  172. package/docs/ai/components/actions.md +0 -81
  173. package/docs/ai/components/gantt.md +0 -95
  174. package/docs/ai/components/stat.md +0 -29
  175. package/docs/components/actions.md +0 -317
  176. package/docs/components/gantt.md +0 -347
  177. package/docs/components/stat.md +0 -45
@@ -1,89 +1,10 @@
1
1
  import { __runInitializers, __esDecorate } from 'tslib';
2
- import { element, property, query, styles, ready, dispose, watch, dispatch, render, css, html } from 'snice';
2
+ import { element, property, styles, ready, dispose, css } from 'snice';
3
3
 
4
- var cssContent = ":host{display:block;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI','Noto Sans',Helvetica,Arial,sans-serif,'Apple Color Emoji','Segoe UI Emoji';font-size:16px;line-height:1.5;color:var(--snice-doc-text-color,#333);background:var(--snice-doc-background,#fff);padding:var(--snice-doc-padding,40px 20px);min-height:var(--snice-doc-min-height,400px)}.doc-container{max-width:var(--snice-doc-max-width,900px);margin:0 auto}.doc-block{position:relative;margin:4px 0;min-height:1.5em}.doc-block:hover .block-handle{opacity:1}.block-handle{position:absolute;left:-30px;top:4px;width:20px;height:20px;opacity:0;cursor:grab;display:flex;align-items:center;justify-content:center;color:var(--snice-doc-muted-color,#999);transition:opacity .2s}.block-handle:active{cursor:grabbing}.block-handle svg{width:16px;height:16px}.block-content{outline:0;word-wrap:break-word;white-space:pre-wrap}.block-content:empty:before{content:attr(data-placeholder);color:var(--snice-doc-placeholder-color,#999);pointer-events:none}.block-paragraph{margin:2px 0}.block-heading-1 .block-content{font-size:2em;font-weight:700;line-height:1.2;margin:.67em 0}.block-heading-2 .block-content{font-size:1.5em;font-weight:700;line-height:1.3;margin:.75em 0}.block-heading-3 .block-content{font-size:1.25em;font-weight:700;line-height:1.4;margin:.83em 0}.block-bulleted-list{display:flex;align-items:flex-start}.block-bulleted-list:before{content:'•';margin-right:8px;margin-top:2px;color:var(--snice-doc-text-color,#333)}.block-numbered-list{display:flex;align-items:flex-start}.block-numbered-list:before{content:attr(data-number) '.';margin-right:8px;margin-top:2px;min-width:24px;color:var(--snice-doc-text-color,#333)}.block-todo{display:flex;align-items:flex-start}.todo-checkbox{margin-right:8px;margin-top:4px;cursor:pointer;flex-shrink:0}.block-todo.checked .block-content{text-decoration:line-through;opacity:.6}.block-code .block-content{font-family:Monaco,Menlo,'Ubuntu Mono',Consolas,source-code-pro,monospace;font-size:.9em;background:var(--snice-doc-code-background,#f6f8fa);border:1px solid var(--snice-doc-code-border,#e1e4e8);border-radius:6px;padding:16px;overflow-x:auto;white-space:pre}.block-quote .block-content{border-left:3px solid var(--snice-doc-quote-border,#e1e4e8);padding-left:16px;color:var(--snice-doc-muted-color,#666);font-style:italic}.block-divider{border:none;border-top:1px solid var(--snice-doc-divider-color,#e1e4e8);margin:16px 0;height:1px}.format-bold{font-weight:700}.format-italic{font-style:italic}.format-underline{text-decoration:underline}.format-strikethrough{text-decoration:line-through}.format-code{font-family:Monaco,Menlo,'Ubuntu Mono',Consolas,source-code-pro,monospace;font-size:.9em;background:var(--snice-doc-inline-code-background,rgba(175,184,193,.2));padding:2px 6px;border-radius:3px}.format-link{color:var(--snice-doc-link-color,#0969da);text-decoration:underline;cursor:pointer}.format-link:hover{opacity:.8}.block-menu{position:absolute;left:0;top:100%;background:var(--snice-doc-menu-background,#fff);border:1px solid var(--snice-doc-menu-border,#e1e4e8);border-radius:6px;box-shadow:0 8px 24px rgba(0,0,0,.12);max-height:300px;overflow-y:auto;z-index:1000;min-width:240px}.block-menu-item{display:flex;align-items:center;gap:12px;padding:8px 12px;cursor:pointer;transition:background .2s}.block-menu-item.selected,.block-menu-item:hover{background:var(--snice-doc-menu-hover,#f6f8fa)}.block-menu-icon{width:20px;height:20px;flex-shrink:0;color:var(--snice-doc-muted-color,#666)}.block-menu-label{font-size:14px;color:var(--snice-doc-text-color,#333)}.format-toolbar{position:absolute;left:50%;transform:translateX(-50%);background:var(--snice-doc-toolbar-background,#1f2328);border-radius:6px;box-shadow:0 8px 24px rgba(0,0,0,.12);padding:4px;display:flex;gap:2px;z-index:1000}.format-button{width:32px;height:32px;border:none;background:0 0;color:var(--snice-doc-toolbar-text,#fff);cursor:pointer;border-radius:4px;display:flex;align-items:center;justify-content:center;transition:background .2s}.format-button:hover{background:var(--snice-doc-toolbar-hover,rgba(255,255,255,.1))}.format-button.active{background:var(--snice-doc-toolbar-active,rgba(88,166,255,.3))}.format-button svg{width:16px;height:16px}.doc-block.dragging{opacity:.5}.doc-block.drag-over{border-top:2px solid var(--snice-doc-primary-color,#0969da)}:host([readonly]) .block-handle,:host([readonly]) .block-menu,:host([readonly]) .format-toolbar{display:none}:host([readonly]) .block-content{cursor:default}:host([readonly]) .todo-checkbox{pointer-events:none}";
4
+ var cssContent = ":host{display:block;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI','Noto Sans',Helvetica,Arial,sans-serif;font-size:16px;line-height:1.6;color:var(--snice-doc-text-color,#333);background:var(--snice-doc-background,#fff);height:100%;min-height:600px}.doc-wrapper{display:flex;flex-direction:column;height:100%}.toolbar{display:flex;gap:4px;padding:8px 12px;background:var(--snice-doc-sidebar-background,#f6f8fa);border-bottom:1px solid var(--snice-doc-border,#e1e4e8);flex-wrap:wrap}.toolbar-btn{padding:6px 12px;border:1px solid var(--snice-doc-border,#e1e4e8);background:var(--snice-doc-background,#fff);border-radius:4px;cursor:pointer;font-weight:600;font-size:14px;color:var(--snice-doc-text-color,#333);transition:.2s;min-width:32px;text-align:center}.toolbar-btn:hover{background:var(--snice-doc-menu-hover,#e8eaed);border-color:var(--snice-doc-primary-color,#0969da)}.toolbar-btn:active{transform:translateY(1px)}.toolbar-divider{width:1px;background:var(--snice-doc-border,#e1e4e8);margin:0 4px}.doc-main{display:flex;flex:1;overflow:hidden}.doc-sidebar{width:200px;background:var(--snice-doc-sidebar-background,#f6f8fa);border-right:1px solid var(--snice-doc-border,#e1e4e8);padding:16px 0;flex-shrink:0;transition:width .3s;position:relative}.doc-sidebar.collapsed{width:50px}.sidebar-toggle{position:absolute;top:10px;right:10px;background:var(--snice-doc-background,#fff);border:1px solid var(--snice-doc-border,#e1e4e8);border-radius:4px;width:28px;height:28px;cursor:pointer;display:flex;align-items:center;justify-content:center;font-size:14px;color:var(--snice-doc-text-color,#333);transition:background .2s}.sidebar-toggle:hover{background:var(--snice-doc-menu-hover,#e1e4e8)}.sidebar-header{font-weight:600;font-size:12px;color:var(--snice-doc-muted-color,#666);text-transform:uppercase;padding:0 16px 8px;margin-bottom:8px;border-bottom:1px solid var(--snice-doc-border,#e1e4e8)}.sidebar-item{display:flex;align-items:center;gap:12px;padding:10px 16px;border:none;background:0 0;cursor:pointer;width:100%;text-align:left;font-size:14px;color:var(--snice-doc-text-color,#333);transition:background .2s}.sidebar-item:hover{background:var(--snice-doc-background,#fff)}.sidebar-icon{font-size:18px;width:24px;text-align:center}.doc-editor{flex:1;padding:60px 100px;overflow-y:auto;outline:0;max-width:850px;margin:0 auto;width:100%}.doc-editor:empty:before{content:attr(data-placeholder);color:var(--snice-doc-placeholder-color,#999);pointer-events:none}.doc-editor p{margin:0 0 12px;min-height:1.6em}.doc-editor h1{font-size:2em;font-weight:700;line-height:1.2;margin:24px 0 16px}.doc-editor h2{font-size:1.5em;font-weight:700;line-height:1.3;margin:20px 0 12px}.doc-editor h3{font-size:1.25em;font-weight:700;line-height:1.4;margin:16px 0 8px}.doc-editor ol,.doc-editor ul{margin:0 0 12px;padding-left:30px}.doc-editor li{margin:4px 0}.doc-editor code{font-family:Monaco,Menlo,'Ubuntu Mono',Consolas,monospace;font-size:.9em;background:var(--snice-doc-inline-code-background,rgba(175,184,193,.2));padding:2px 6px;border-radius:3px}.doc-editor a{color:var(--snice-doc-link-color,#0969da);text-decoration:underline}.doc-editor img{max-width:100%;height:auto;display:block;margin:16px 0;border-radius:4px}.doc-editor table{border-collapse:collapse;width:100%;margin:16px 0}.doc-editor table td,.doc-editor table th{border:1px solid var(--snice-doc-border,#e1e4e8);padding:8px 12px}.doc-editor table th{background:var(--snice-doc-sidebar-background,#f6f8fa);font-weight:600}.doc-editor hr{margin:20px 0;border:none;border-top:1px solid var(--snice-doc-border,#e1e4e8)}:host([readonly]) .doc-sidebar,:host([readonly]) .toolbar{display:none}:host([readonly]) .doc-editor{cursor:default}";
5
5
 
6
6
  /**
7
- * Generate unique ID
8
- */
9
- function generateId() {
10
- return Math.random().toString(36).substring(2, 15);
11
- }
12
- /**
13
- * Block menu items
14
- */
15
- const BLOCK_MENU_ITEMS = [
16
- {
17
- type: 'paragraph',
18
- label: 'Paragraph',
19
- icon: '¶',
20
- keywords: ['text', 'paragraph', 'p'],
21
- },
22
- {
23
- type: 'heading-1',
24
- label: 'Heading 1',
25
- icon: 'H1',
26
- keywords: ['h1', 'heading', 'title'],
27
- },
28
- {
29
- type: 'heading-2',
30
- label: 'Heading 2',
31
- icon: 'H2',
32
- keywords: ['h2', 'heading', 'subtitle'],
33
- },
34
- {
35
- type: 'heading-3',
36
- label: 'Heading 3',
37
- icon: 'H3',
38
- keywords: ['h3', 'heading'],
39
- },
40
- {
41
- type: 'bulleted-list',
42
- label: 'Bulleted List',
43
- icon: '•',
44
- keywords: ['ul', 'list', 'bullet'],
45
- },
46
- {
47
- type: 'numbered-list',
48
- label: 'Numbered List',
49
- icon: '1.',
50
- keywords: ['ol', 'list', 'number'],
51
- },
52
- {
53
- type: 'todo',
54
- label: 'To-do List',
55
- icon: '☐',
56
- keywords: ['todo', 'checkbox', 'task'],
57
- },
58
- {
59
- type: 'code',
60
- label: 'Code Block',
61
- icon: '</>',
62
- keywords: ['code', 'snippet'],
63
- },
64
- {
65
- type: 'quote',
66
- label: 'Quote',
67
- icon: '"',
68
- keywords: ['quote', 'blockquote'],
69
- },
70
- {
71
- type: 'divider',
72
- label: 'Divider',
73
- icon: '―',
74
- keywords: ['divider', 'separator', 'hr'],
75
- },
76
- ];
77
- /**
78
- * snice-doc - Document editor component
79
- *
80
- * A Notion-like document editor with block-based editing
81
- *
82
- * @element snice-doc
83
- *
84
- * @fires {CustomEvent<{ blocks: DocBlock[] }>} doc-change - Fires when document changes
85
- * @fires {CustomEvent<{ blockId: string }>} doc-focus - Fires when a block receives focus
86
- * @fires {CustomEvent<{ blockId: string }>} doc-blur - Fires when a block loses focus
7
+ * snice-doc - Simple document editor
87
8
  */
88
9
  let SniceDoc = (() => {
89
10
  let _classDecorators = [element('snice-doc')];
@@ -92,143 +13,98 @@ let SniceDoc = (() => {
92
13
  let _classThis;
93
14
  let _classSuper = HTMLElement;
94
15
  let _instanceExtraInitializers = [];
95
- let _blocks_decorators;
96
- let _blocks_initializers = [];
97
- let _blocks_extraInitializers = [];
98
16
  let _placeholder_decorators;
99
17
  let _placeholder_initializers = [];
100
18
  let _placeholder_extraInitializers = [];
101
19
  let _readonly_decorators;
102
20
  let _readonly_initializers = [];
103
21
  let _readonly_extraInitializers = [];
104
- let _container_decorators;
105
- let _container_initializers = [];
106
- let _container_extraInitializers = [];
107
- let _firstBlockElement_decorators;
108
- let _firstBlockElement_initializers = [];
109
- let _firstBlockElement_extraInitializers = [];
110
22
  let _styles_decorators;
111
23
  let _init_decorators;
112
24
  let _cleanup_decorators;
113
- let _blocksChanged_decorators;
114
- let _emitChange_decorators;
115
- let _emitFocus_decorators;
116
- let _emitBlur_decorators;
117
- let _render_decorators;
118
25
  (class extends _classSuper {
119
26
  static { _classThis = this; }
120
27
  constructor() {
121
28
  super(...arguments);
122
- this.blocks = (__runInitializers(this, _instanceExtraInitializers), __runInitializers(this, _blocks_initializers, [{ id: generateId(), type: 'paragraph', content: '', formats: [] }]));
123
- this.placeholder = (__runInitializers(this, _blocks_extraInitializers), __runInitializers(this, _placeholder_initializers, "Type '/' for commands..."));
29
+ this.placeholder = (__runInitializers(this, _instanceExtraInitializers), __runInitializers(this, _placeholder_initializers, 'Start typing...'));
124
30
  this.readonly = (__runInitializers(this, _placeholder_extraInitializers), __runInitializers(this, _readonly_initializers, false));
125
- this.container = (__runInitializers(this, _readonly_extraInitializers), __runInitializers(this, _container_initializers, void 0));
126
- this.firstBlockElement = (__runInitializers(this, _container_extraInitializers), __runInitializers(this, _firstBlockElement_initializers, void 0));
127
- this.selection = (__runInitializers(this, _firstBlockElement_extraInitializers), null);
128
- this.draggedBlock = null;
129
- this.showBlockMenu = false;
130
- this.blockMenuFilter = '';
131
- this.blockMenuSelectedIndex = 0;
132
- this.blockMenuBlockId = '';
133
- this.handleKeyDown = (e) => {
31
+ this.editor = __runInitializers(this, _readonly_extraInitializers);
32
+ this.showFormatToolbar = false;
33
+ this.formatToolbarPosition = null;
34
+ this.savedSelection = null;
35
+ this.handlePaste = (e) => {
134
36
  if (this.readonly)
135
37
  return;
136
- const target = e.target;
137
- if (!target.classList.contains('block-content'))
138
- return;
139
- const blockId = target.dataset.blockId;
140
- const block = this.blocks.find((b) => b.id === blockId);
141
- if (!block)
142
- return;
143
- // Handle block menu navigation
144
- if (this.showBlockMenu) {
145
- if (e.key === 'ArrowDown') {
146
- e.preventDefault();
147
- this.blockMenuSelectedIndex = Math.min(this.blockMenuSelectedIndex + 1, this.getFilteredBlockMenuItems().length - 1);
148
- return;
149
- }
150
- if (e.key === 'ArrowUp') {
151
- e.preventDefault();
152
- this.blockMenuSelectedIndex = Math.max(this.blockMenuSelectedIndex - 1, 0);
153
- return;
154
- }
155
- if (e.key === 'Enter') {
156
- e.preventDefault();
157
- const items = this.getFilteredBlockMenuItems();
158
- if (items[this.blockMenuSelectedIndex]) {
159
- this.selectBlockType(this.blockMenuBlockId, items[this.blockMenuSelectedIndex].type);
38
+ const items = e.clipboardData?.items;
39
+ if (items) {
40
+ for (let i = 0; i < items.length; i++) {
41
+ if (items[i].type.indexOf('image') !== -1) {
42
+ e.preventDefault();
43
+ const blob = items[i].getAsFile();
44
+ if (blob) {
45
+ const reader = new FileReader();
46
+ reader.onload = (event) => {
47
+ const dataUrl = event.target?.result || null;
48
+ if (!dataUrl)
49
+ return;
50
+ const img = document.createElement('img');
51
+ img.src = dataUrl;
52
+ img.style.maxWidth = '100%';
53
+ img.style.height = 'auto';
54
+ const selection = window.getSelection();
55
+ if (selection && selection.rangeCount > 0) {
56
+ const range = selection.getRangeAt(0);
57
+ range.insertNode(img);
58
+ range.setStartAfter(img);
59
+ range.collapse(true);
60
+ selection.removeAllRanges();
61
+ selection.addRange(range);
62
+ }
63
+ };
64
+ reader.readAsDataURL(blob);
65
+ }
66
+ return;
160
67
  }
161
- return;
162
- }
163
- if (e.key === 'Escape') {
164
- e.preventDefault();
165
- this.hideBlockMenu();
166
- return;
167
68
  }
168
69
  }
169
- // Enter - create new block
170
- if (e.key === 'Enter' && !e.shiftKey) {
171
- e.preventDefault();
172
- this.createNewBlock(blockId);
173
- return;
70
+ };
71
+ this.saveCurrentSelection = () => {
72
+ const selection = window.getSelection();
73
+ if (selection && selection.rangeCount > 0) {
74
+ const range = selection.getRangeAt(0);
75
+ // Just save it - we'll validate when restoring
76
+ this.savedSelection = range.cloneRange();
174
77
  }
175
- // Backspace on empty block - remove block
176
- if (e.key === 'Backspace' && block.content === '' && this.blocks.length > 1) {
177
- e.preventDefault();
178
- this.removeBlock(blockId);
78
+ };
79
+ this.saveSelectionBeforeAction = (e) => {
80
+ // Save selection before clicking toolbar/sidebar buttons
81
+ this.saveCurrentSelection();
82
+ };
83
+ this.handleSelectionChange = () => {
84
+ const selection = window.getSelection();
85
+ if (!selection || selection.isCollapsed || selection.rangeCount === 0) {
179
86
  return;
180
87
  }
181
- // Keyboard shortcuts
182
- if (e.ctrlKey || e.metaKey) {
183
- if (e.key === 'b') {
184
- e.preventDefault();
185
- this.toggleFormat('bold');
186
- }
187
- else if (e.key === 'i') {
188
- e.preventDefault();
189
- this.toggleFormat('italic');
190
- }
191
- else if (e.key === 'u') {
192
- e.preventDefault();
193
- this.toggleFormat('underline');
194
- }
195
- }
196
- };
197
- this.handlePaste = (e) => {
198
- if (this.readonly)
88
+ // Check if selection is within our editor
89
+ const range = selection.getRangeAt(0);
90
+ if (!this.editor.contains(range.commonAncestorContainer)) {
199
91
  return;
200
- // Allow default paste behavior for now
201
- // Could add special handling for HTML/markdown paste
92
+ }
93
+ // Could add floating toolbar here if desired
202
94
  };
203
95
  }
204
96
  static {
205
97
  const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(_classSuper[Symbol.metadata] ?? null) : void 0;
206
- _blocks_decorators = [property({ type: Array, attribute: false })];
207
98
  _placeholder_decorators = [property({ type: String })];
208
99
  _readonly_decorators = [property({ type: Boolean })];
209
- _container_decorators = [query('.doc-container')];
210
- _firstBlockElement_decorators = [query('.block-content')];
211
100
  _styles_decorators = [styles()];
212
101
  _init_decorators = [ready()];
213
102
  _cleanup_decorators = [dispose()];
214
- _blocksChanged_decorators = [watch('blocks')];
215
- _emitChange_decorators = [dispatch('doc-change')];
216
- _emitFocus_decorators = [dispatch('doc-focus')];
217
- _emitBlur_decorators = [dispatch('doc-blur')];
218
- _render_decorators = [render()];
219
103
  __esDecorate(this, null, _styles_decorators, { kind: "method", name: "styles", static: false, private: false, access: { has: obj => "styles" in obj, get: obj => obj.styles }, metadata: _metadata }, null, _instanceExtraInitializers);
220
104
  __esDecorate(this, null, _init_decorators, { kind: "method", name: "init", static: false, private: false, access: { has: obj => "init" in obj, get: obj => obj.init }, metadata: _metadata }, null, _instanceExtraInitializers);
221
105
  __esDecorate(this, null, _cleanup_decorators, { kind: "method", name: "cleanup", static: false, private: false, access: { has: obj => "cleanup" in obj, get: obj => obj.cleanup }, metadata: _metadata }, null, _instanceExtraInitializers);
222
- __esDecorate(this, null, _blocksChanged_decorators, { kind: "method", name: "blocksChanged", static: false, private: false, access: { has: obj => "blocksChanged" in obj, get: obj => obj.blocksChanged }, metadata: _metadata }, null, _instanceExtraInitializers);
223
- __esDecorate(this, null, _emitChange_decorators, { kind: "method", name: "emitChange", static: false, private: false, access: { has: obj => "emitChange" in obj, get: obj => obj.emitChange }, metadata: _metadata }, null, _instanceExtraInitializers);
224
- __esDecorate(this, null, _emitFocus_decorators, { kind: "method", name: "emitFocus", static: false, private: false, access: { has: obj => "emitFocus" in obj, get: obj => obj.emitFocus }, metadata: _metadata }, null, _instanceExtraInitializers);
225
- __esDecorate(this, null, _emitBlur_decorators, { kind: "method", name: "emitBlur", static: false, private: false, access: { has: obj => "emitBlur" in obj, get: obj => obj.emitBlur }, metadata: _metadata }, null, _instanceExtraInitializers);
226
- __esDecorate(this, null, _render_decorators, { kind: "method", name: "render", static: false, private: false, access: { has: obj => "render" in obj, get: obj => obj.render }, metadata: _metadata }, null, _instanceExtraInitializers);
227
- __esDecorate(null, null, _blocks_decorators, { kind: "field", name: "blocks", static: false, private: false, access: { has: obj => "blocks" in obj, get: obj => obj.blocks, set: (obj, value) => { obj.blocks = value; } }, metadata: _metadata }, _blocks_initializers, _blocks_extraInitializers);
228
106
  __esDecorate(null, null, _placeholder_decorators, { kind: "field", name: "placeholder", static: false, private: false, access: { has: obj => "placeholder" in obj, get: obj => obj.placeholder, set: (obj, value) => { obj.placeholder = value; } }, metadata: _metadata }, _placeholder_initializers, _placeholder_extraInitializers);
229
107
  __esDecorate(null, null, _readonly_decorators, { kind: "field", name: "readonly", static: false, private: false, access: { has: obj => "readonly" in obj, get: obj => obj.readonly, set: (obj, value) => { obj.readonly = value; } }, metadata: _metadata }, _readonly_initializers, _readonly_extraInitializers);
230
- __esDecorate(null, null, _container_decorators, { kind: "field", name: "container", static: false, private: false, access: { has: obj => "container" in obj, get: obj => obj.container, set: (obj, value) => { obj.container = value; } }, metadata: _metadata }, _container_initializers, _container_extraInitializers);
231
- __esDecorate(null, null, _firstBlockElement_decorators, { kind: "field", name: "firstBlockElement", static: false, private: false, access: { has: obj => "firstBlockElement" in obj, get: obj => obj.firstBlockElement, set: (obj, value) => { obj.firstBlockElement = value; } }, metadata: _metadata }, _firstBlockElement_initializers, _firstBlockElement_extraInitializers);
232
108
  __esDecorate(null, _classDescriptor = { value: _classThis }, _classDecorators, { kind: "class", name: _classThis.name, metadata: _metadata }, null, _classExtraInitializers);
233
109
  _classThis = _classDescriptor.value;
234
110
  if (_metadata) Object.defineProperty(_classThis, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata });
@@ -238,385 +114,360 @@ let SniceDoc = (() => {
238
114
  return css /*css*/ `${cssContent}`;
239
115
  }
240
116
  init() {
241
- this.addEventListener('keydown', this.handleKeyDown);
242
- this.addEventListener('paste', this.handlePaste);
117
+ this.initializeDOM();
118
+ document.addEventListener('selectionchange', this.handleSelectionChange);
119
+ // Save selection whenever editor loses focus
120
+ this.editor.addEventListener('blur', this.saveCurrentSelection);
243
121
  }
244
122
  cleanup() {
245
- this.removeEventListener('keydown', this.handleKeyDown);
246
- this.removeEventListener('paste', this.handlePaste);
247
- }
248
- blocksChanged() {
249
- this.emitChange();
123
+ document.removeEventListener('selectionchange', this.handleSelectionChange);
124
+ this.editor.removeEventListener('blur', this.saveCurrentSelection);
250
125
  }
251
- emitChange() {
252
- return new CustomEvent('doc-change', {
253
- detail: { blocks: this.blocks },
254
- bubbles: true,
255
- composed: true,
126
+ initializeDOM() {
127
+ if (!this.shadowRoot)
128
+ return;
129
+ const wrapper = document.createElement('div');
130
+ wrapper.className = 'doc-wrapper';
131
+ // Create toolbar
132
+ const toolbar = this.createToolbar();
133
+ wrapper.appendChild(toolbar);
134
+ // Create editor
135
+ this.editor = document.createElement('div');
136
+ this.editor.className = 'doc-editor';
137
+ this.editor.contentEditable = String(!this.readonly);
138
+ this.editor.setAttribute('data-placeholder', this.placeholder);
139
+ // Add default paragraph
140
+ const p = document.createElement('p');
141
+ p.innerHTML = '<br>'; // Placeholder for empty paragraph
142
+ this.editor.appendChild(p);
143
+ this.editor.addEventListener('paste', this.handlePaste);
144
+ this.editor.addEventListener('keyup', this.saveCurrentSelection);
145
+ this.editor.addEventListener('mouseup', this.saveCurrentSelection);
146
+ this.editor.addEventListener('click', this.saveCurrentSelection);
147
+ wrapper.appendChild(this.editor);
148
+ this.shadowRoot.appendChild(wrapper);
149
+ }
150
+ createToolbar() {
151
+ const toolbar = document.createElement('div');
152
+ toolbar.className = 'toolbar';
153
+ const tools = [
154
+ { cmd: 'bold', label: 'B', title: 'Bold (Ctrl+B)' },
155
+ { cmd: 'italic', label: 'I', title: 'Italic (Ctrl+I)' },
156
+ { cmd: 'underline', label: 'U', title: 'Underline (Ctrl+U)' },
157
+ { cmd: 'strikeThrough', label: 'S', title: 'Strikethrough' },
158
+ { cmd: 'divider' },
159
+ { cmd: 'formatBlock', value: 'h1', label: 'H1', title: 'Heading 1' },
160
+ { cmd: 'formatBlock', value: 'h2', label: 'H2', title: 'Heading 2' },
161
+ { cmd: 'formatBlock', value: 'h3', label: 'H3', title: 'Heading 3' },
162
+ { cmd: 'formatBlock', value: 'p', label: 'P', title: 'Paragraph' },
163
+ { cmd: 'divider' },
164
+ { cmd: 'insertUnorderedList', label: '•', title: 'Bullet List' },
165
+ { cmd: 'insertOrderedList', label: '1.', title: 'Numbered List' },
166
+ { cmd: 'divider' },
167
+ { cmd: 'createLink', label: '🔗', title: 'Insert Link' },
168
+ { cmd: 'insertImage', label: '🖼', title: 'Insert Image' },
169
+ { cmd: 'insertTable', label: '📊', title: 'Insert Table' },
170
+ { cmd: 'insertDivider', label: '―', title: 'Insert Divider' },
171
+ ];
172
+ tools.forEach(tool => {
173
+ if (tool.cmd === 'divider') {
174
+ const divider = document.createElement('div');
175
+ divider.className = 'toolbar-divider';
176
+ toolbar.appendChild(divider);
177
+ }
178
+ else {
179
+ const btn = document.createElement('button');
180
+ btn.className = 'toolbar-btn';
181
+ btn.textContent = tool.label || '';
182
+ btn.title = tool.title || '';
183
+ btn.addEventListener('click', () => this.execCommand(tool.cmd, tool.value));
184
+ toolbar.appendChild(btn);
185
+ }
256
186
  });
187
+ toolbar.addEventListener('mousedown', this.saveSelectionBeforeAction);
188
+ return toolbar;
189
+ }
190
+ execCommand(cmd, value) {
191
+ this.editor.focus();
192
+ switch (cmd) {
193
+ case 'createLink':
194
+ this.showLinkDialog();
195
+ break;
196
+ case 'insertImage':
197
+ this.insertImage();
198
+ break;
199
+ case 'insertTable':
200
+ this.insertTable();
201
+ break;
202
+ case 'insertDivider':
203
+ this.insertDivider();
204
+ break;
205
+ default:
206
+ document.execCommand(cmd, false, value);
207
+ break;
208
+ }
257
209
  }
258
- emitFocus(blockId) {
259
- return new CustomEvent('doc-focus', {
260
- detail: { blockId },
261
- bubbles: true,
262
- composed: true,
210
+ showLinkDialog() {
211
+ const dialog = document.createElement('snice-modal');
212
+ dialog.setAttribute('title', 'Insert Link');
213
+ const content = document.createElement('div');
214
+ content.style.padding = '20px';
215
+ const input = document.createElement('snice-input');
216
+ input.setAttribute('label', 'URL');
217
+ input.setAttribute('placeholder', 'https://example.com');
218
+ input.style.width = '100%';
219
+ content.appendChild(input);
220
+ const actions = document.createElement('div');
221
+ actions.style.display = 'flex';
222
+ actions.style.gap = '10px';
223
+ actions.style.justifyContent = 'flex-end';
224
+ actions.style.marginTop = '20px';
225
+ const cancelBtn = document.createElement('snice-button');
226
+ cancelBtn.textContent = 'Cancel';
227
+ cancelBtn.setAttribute('variant', 'secondary');
228
+ cancelBtn.addEventListener('click', () => {
229
+ dialog.remove();
230
+ });
231
+ actions.appendChild(cancelBtn);
232
+ const insertBtn = document.createElement('snice-button');
233
+ insertBtn.textContent = 'Insert';
234
+ insertBtn.addEventListener('click', () => {
235
+ const url = input.value;
236
+ if (url) {
237
+ this.restoreSelection();
238
+ document.execCommand('createLink', false, url);
239
+ }
240
+ dialog.remove();
241
+ });
242
+ actions.appendChild(insertBtn);
243
+ content.appendChild(actions);
244
+ dialog.appendChild(content);
245
+ document.body.appendChild(dialog);
246
+ // Wait for components to be ready, then show modal
247
+ customElements.whenDefined('snice-modal').then(() => {
248
+ requestAnimationFrame(() => {
249
+ dialog.show();
250
+ setTimeout(() => input.focus(), 100);
251
+ });
263
252
  });
264
253
  }
265
- emitBlur(blockId) {
266
- return new CustomEvent('doc-blur', {
267
- detail: { blockId },
268
- bubbles: true,
269
- composed: true,
254
+ insertImage() {
255
+ this.showImageDialog();
256
+ }
257
+ showImageDialog() {
258
+ const dialog = document.createElement('snice-modal');
259
+ dialog.setAttribute('title', 'Insert Image');
260
+ const content = document.createElement('div');
261
+ content.style.padding = '20px';
262
+ const input = document.createElement('snice-input');
263
+ input.setAttribute('label', 'Image URL');
264
+ input.setAttribute('placeholder', 'https://example.com/image.jpg');
265
+ input.style.width = '100%';
266
+ content.appendChild(input);
267
+ const actions = document.createElement('div');
268
+ actions.style.display = 'flex';
269
+ actions.style.gap = '10px';
270
+ actions.style.justifyContent = 'flex-end';
271
+ actions.style.marginTop = '20px';
272
+ const cancelBtn = document.createElement('snice-button');
273
+ cancelBtn.textContent = 'Cancel';
274
+ cancelBtn.setAttribute('variant', 'secondary');
275
+ cancelBtn.addEventListener('click', () => {
276
+ dialog.remove();
277
+ });
278
+ actions.appendChild(cancelBtn);
279
+ const insertBtn = document.createElement('snice-button');
280
+ insertBtn.textContent = 'Insert';
281
+ insertBtn.addEventListener('click', () => {
282
+ const url = input.value;
283
+ if (url) {
284
+ this.doInsertImage(url);
285
+ }
286
+ dialog.remove();
287
+ });
288
+ actions.appendChild(insertBtn);
289
+ content.appendChild(actions);
290
+ dialog.appendChild(content);
291
+ document.body.appendChild(dialog);
292
+ // Wait for components to be ready, then show modal
293
+ customElements.whenDefined('snice-modal').then(() => {
294
+ requestAnimationFrame(() => {
295
+ dialog.show();
296
+ setTimeout(() => input.focus(), 100);
297
+ });
270
298
  });
271
299
  }
272
- /**
273
- * Get current blocks
274
- */
275
- getBlocks() {
276
- return [...this.blocks];
300
+ doInsertImage(url) {
301
+ this.restoreSelection();
302
+ this.editor.focus();
303
+ const img = document.createElement('img');
304
+ img.src = url;
305
+ img.style.maxWidth = '100%';
306
+ img.style.height = 'auto';
307
+ img.style.display = 'block';
308
+ img.style.margin = '10px 0';
309
+ const selection = window.getSelection();
310
+ if (selection && selection.rangeCount > 0) {
311
+ const range = selection.getRangeAt(0);
312
+ if (!this.editor.contains(range.commonAncestorContainer)) {
313
+ this.editor.appendChild(img);
314
+ }
315
+ else {
316
+ range.insertNode(img);
317
+ range.setStartAfter(img);
318
+ range.collapse(true);
319
+ selection.removeAllRanges();
320
+ selection.addRange(range);
321
+ }
322
+ }
323
+ else {
324
+ this.editor.appendChild(img);
325
+ }
277
326
  }
278
- /**
279
- * Set blocks
280
- */
281
- setBlocks(blocks) {
282
- this.blocks = blocks.map((b) => ({ ...b }));
327
+ insertTable() {
328
+ this.showTableDialog();
329
+ }
330
+ showTableDialog() {
331
+ const dialog = document.createElement('snice-modal');
332
+ dialog.setAttribute('title', 'Insert Table');
333
+ const content = document.createElement('div');
334
+ content.style.padding = '20px';
335
+ const rowsInput = document.createElement('snice-input');
336
+ rowsInput.setAttribute('label', 'Rows');
337
+ rowsInput.setAttribute('type', 'number');
338
+ rowsInput.setAttribute('value', '3');
339
+ rowsInput.style.width = '100%';
340
+ rowsInput.style.marginBottom = '15px';
341
+ content.appendChild(rowsInput);
342
+ const colsInput = document.createElement('snice-input');
343
+ colsInput.setAttribute('label', 'Columns');
344
+ colsInput.setAttribute('type', 'number');
345
+ colsInput.setAttribute('value', '3');
346
+ colsInput.style.width = '100%';
347
+ content.appendChild(colsInput);
348
+ const actions = document.createElement('div');
349
+ actions.style.display = 'flex';
350
+ actions.style.gap = '10px';
351
+ actions.style.justifyContent = 'flex-end';
352
+ actions.style.marginTop = '20px';
353
+ const cancelBtn = document.createElement('snice-button');
354
+ cancelBtn.textContent = 'Cancel';
355
+ cancelBtn.setAttribute('variant', 'secondary');
356
+ cancelBtn.addEventListener('click', () => {
357
+ dialog.remove();
358
+ });
359
+ actions.appendChild(cancelBtn);
360
+ const insertBtn = document.createElement('snice-button');
361
+ insertBtn.textContent = 'Insert';
362
+ insertBtn.addEventListener('click', () => {
363
+ const rows = parseInt(rowsInput.value) || 3;
364
+ const cols = parseInt(colsInput.value) || 3;
365
+ this.doInsertTable(rows, cols);
366
+ dialog.remove();
367
+ });
368
+ actions.appendChild(insertBtn);
369
+ content.appendChild(actions);
370
+ dialog.appendChild(content);
371
+ document.body.appendChild(dialog);
372
+ // Wait for components to be ready, then show modal
373
+ customElements.whenDefined('snice-modal').then(() => {
374
+ // Use connectedCallback to ensure the component is fully initialized
375
+ requestAnimationFrame(() => {
376
+ requestAnimationFrame(() => {
377
+ dialog.open = true;
378
+ setTimeout(() => rowsInput.focus(), 100);
379
+ });
380
+ });
381
+ });
283
382
  }
284
- /**
285
- * Export as JSON
286
- */
287
- toJSON() {
288
- return JSON.stringify(this.blocks, null, 2);
383
+ doInsertTable(rows, cols) {
384
+ this.restoreSelection();
385
+ this.editor.focus();
386
+ const table = document.createElement('table');
387
+ table.style.borderCollapse = 'collapse';
388
+ table.style.width = '100%';
389
+ table.style.margin = '10px 0';
390
+ for (let i = 0; i < rows; i++) {
391
+ const tr = document.createElement('tr');
392
+ for (let j = 0; j < cols; j++) {
393
+ const td = document.createElement('td');
394
+ td.style.border = '1px solid #ddd';
395
+ td.style.padding = '8px';
396
+ td.innerHTML = '<br>';
397
+ tr.appendChild(td);
398
+ }
399
+ table.appendChild(tr);
400
+ }
401
+ const selection = window.getSelection();
402
+ if (selection && selection.rangeCount > 0) {
403
+ const range = selection.getRangeAt(0);
404
+ if (!this.editor.contains(range.commonAncestorContainer)) {
405
+ this.editor.appendChild(table);
406
+ }
407
+ else {
408
+ range.insertNode(table);
409
+ range.setStartAfter(table);
410
+ range.collapse(true);
411
+ selection.removeAllRanges();
412
+ selection.addRange(range);
413
+ }
414
+ }
415
+ else {
416
+ this.editor.appendChild(table);
417
+ }
289
418
  }
290
- /**
291
- * Import from JSON
292
- */
293
- fromJSON(json) {
294
- try {
295
- const blocks = JSON.parse(json);
296
- if (Array.isArray(blocks)) {
297
- this.blocks = blocks;
419
+ insertDivider() {
420
+ this.restoreSelection();
421
+ this.editor.focus();
422
+ const hr = document.createElement('hr');
423
+ hr.style.margin = '20px 0';
424
+ hr.style.border = 'none';
425
+ hr.style.borderTop = '1px solid #e1e4e8';
426
+ const selection = window.getSelection();
427
+ if (selection && selection.rangeCount > 0) {
428
+ const range = selection.getRangeAt(0);
429
+ // Check if the range is within the editor
430
+ if (!this.editor.contains(range.commonAncestorContainer)) {
431
+ this.editor.appendChild(hr);
432
+ }
433
+ else {
434
+ range.insertNode(hr);
435
+ range.setStartAfter(hr);
436
+ range.collapse(true);
437
+ selection.removeAllRanges();
438
+ selection.addRange(range);
298
439
  }
299
440
  }
300
- catch (error) {
301
- console.error('Failed to parse JSON:', error);
441
+ else {
442
+ this.editor.appendChild(hr);
302
443
  }
303
444
  }
304
- /**
305
- * Export as Markdown
306
- */
307
- toMarkdown() {
308
- return this.blocks
309
- .map((block) => {
310
- switch (block.type) {
311
- case 'heading-1':
312
- return `# ${block.content}`;
313
- case 'heading-2':
314
- return `## ${block.content}`;
315
- case 'heading-3':
316
- return `### ${block.content}`;
317
- case 'bulleted-list':
318
- return `- ${block.content}`;
319
- case 'numbered-list':
320
- return `1. ${block.content}`;
321
- case 'todo':
322
- return `- [${block.checked ? 'x' : ' '}] ${block.content}`;
323
- case 'code':
324
- return `\`\`\`\n${block.content}\n\`\`\``;
325
- case 'quote':
326
- return `> ${block.content}`;
327
- case 'divider':
328
- return '---';
329
- default:
330
- return block.content;
445
+ restoreSelection() {
446
+ if (this.savedSelection) {
447
+ const selection = window.getSelection();
448
+ if (selection) {
449
+ selection.removeAllRanges();
450
+ selection.addRange(this.savedSelection);
331
451
  }
332
- })
333
- .join('\n\n');
452
+ }
334
453
  }
335
454
  /**
336
- * Export as HTML
455
+ * Get document HTML
337
456
  */
338
- toHTML() {
339
- return this.blocks
340
- .map((block) => {
341
- const content = this.escapeHtml(block.content);
342
- switch (block.type) {
343
- case 'heading-1':
344
- return `<h1>${content}</h1>`;
345
- case 'heading-2':
346
- return `<h2>${content}</h2>`;
347
- case 'heading-3':
348
- return `<h3>${content}</h3>`;
349
- case 'bulleted-list':
350
- return `<ul><li>${content}</li></ul>`;
351
- case 'numbered-list':
352
- return `<ol><li>${content}</li></ol>`;
353
- case 'todo':
354
- return `<input type="checkbox" ${block.checked ? 'checked' : ''}>${content}`;
355
- case 'code':
356
- return `<pre><code>${content}</code></pre>`;
357
- case 'quote':
358
- return `<blockquote>${content}</blockquote>`;
359
- case 'divider':
360
- return '<hr>';
361
- default:
362
- return `<p>${content}</p>`;
363
- }
364
- })
365
- .join('\n');
457
+ getHTML() {
458
+ return this.editor.innerHTML;
366
459
  }
367
460
  /**
368
- * Focus the editor
461
+ * Set document HTML
369
462
  */
370
- focus() {
371
- this.firstBlockElement?.focus();
463
+ setHTML(html) {
464
+ this.editor.innerHTML = html;
372
465
  }
373
466
  /**
374
- * Clear all content
467
+ * Clear document
375
468
  */
376
469
  clear() {
377
- this.blocks = [{ id: generateId(), type: 'paragraph', content: '', formats: [] }];
378
- }
379
- escapeHtml(text) {
380
- return text
381
- .replace(/&/g, '&amp;')
382
- .replace(/</g, '&lt;')
383
- .replace(/>/g, '&gt;')
384
- .replace(/"/g, '&quot;')
385
- .replace(/'/g, '&#039;');
386
- }
387
- handleInput(blockId, e) {
388
- if (this.readonly)
389
- return;
390
- const target = e.target;
391
- const content = target.textContent || '';
392
- const blockIndex = this.blocks.findIndex((b) => b.id === blockId);
393
- if (blockIndex === -1)
394
- return;
395
- // Check for block menu trigger
396
- if (content.startsWith('/')) {
397
- this.showBlockMenuFor(blockId, content.slice(1));
398
- }
399
- else {
400
- this.hideBlockMenu();
401
- }
402
- // Update block content
403
- this.blocks = this.blocks.map((b) => b.id === blockId ? { ...b, content: content } : b);
404
- }
405
- handleFocus(blockId) {
406
- this.emitFocus(blockId);
407
- }
408
- handleBlur(blockId) {
409
- this.emitBlur(blockId);
410
- }
411
- createNewBlock(afterBlockId) {
412
- const index = this.blocks.findIndex((b) => b.id === afterBlockId);
413
- if (index === -1)
414
- return;
415
- const newBlock = {
416
- id: generateId(),
417
- type: 'paragraph',
418
- content: '',
419
- formats: [],
420
- };
421
- this.blocks = [
422
- ...this.blocks.slice(0, index + 1),
423
- newBlock,
424
- ...this.blocks.slice(index + 1),
425
- ];
426
- // Focus new block
427
- setTimeout(() => {
428
- const el = this.shadowRoot?.querySelector(`[data-block-id="${newBlock.id}"]`);
429
- el?.focus();
430
- });
431
- }
432
- removeBlock(blockId) {
433
- const index = this.blocks.findIndex((b) => b.id === blockId);
434
- if (index === -1)
435
- return;
436
- this.blocks = this.blocks.filter((b) => b.id !== blockId);
437
- // Focus previous block
438
- if (index > 0) {
439
- setTimeout(() => {
440
- const prevBlock = this.blocks[index - 1];
441
- const el = this.shadowRoot?.querySelector(`[data-block-id="${prevBlock.id}"]`);
442
- el?.focus();
443
- });
444
- }
445
- }
446
- toggleFormat(format) {
447
- // TODO: Implement inline formatting
448
- console.log('Toggle format:', format);
449
- }
450
- showBlockMenuFor(blockId, filter) {
451
- this.showBlockMenu = true;
452
- this.blockMenuBlockId = blockId;
453
- this.blockMenuFilter = filter;
454
- this.blockMenuSelectedIndex = 0;
455
- }
456
- hideBlockMenu() {
457
- this.showBlockMenu = false;
458
- this.blockMenuFilter = '';
459
- this.blockMenuSelectedIndex = 0;
460
- }
461
- getFilteredBlockMenuItems() {
462
- if (!this.blockMenuFilter)
463
- return BLOCK_MENU_ITEMS;
464
- const filter = this.blockMenuFilter.toLowerCase();
465
- return BLOCK_MENU_ITEMS.filter((item) => item.label.toLowerCase().includes(filter) ||
466
- item.keywords.some((k) => k.includes(filter)));
467
- }
468
- selectBlockType(blockId, type) {
469
- this.blocks = this.blocks.map((b) => b.id === blockId ? { ...b, type, content: '' } : b);
470
- this.hideBlockMenu();
471
- // Focus the block
472
- setTimeout(() => {
473
- const el = this.shadowRoot?.querySelector(`[data-block-id="${blockId}"]`);
474
- el?.focus();
475
- });
476
- }
477
- handleBlockMenuItemClick(blockId, type) {
478
- this.selectBlockType(blockId, type);
479
- }
480
- handleTodoToggle(blockId) {
481
- if (this.readonly)
482
- return;
483
- this.blocks = this.blocks.map((b) => b.id === blockId ? { ...b, checked: !b.checked } : b);
484
- }
485
- handleDragStart(blockId, e) {
486
- if (this.readonly)
487
- return;
488
- this.draggedBlock = blockId;
489
- e.dataTransfer.effectAllowed = 'move';
490
- }
491
- handleDragOver(blockId, e) {
492
- if (this.readonly)
493
- return;
494
- if (!this.draggedBlock || this.draggedBlock === blockId)
495
- return;
496
- e.preventDefault();
497
- e.dataTransfer.dropEffect = 'move';
498
- }
499
- handleDrop(blockId, e) {
500
- if (this.readonly)
501
- return;
502
- e.preventDefault();
503
- if (!this.draggedBlock || this.draggedBlock === blockId)
504
- return;
505
- const draggedIndex = this.blocks.findIndex((b) => b.id === this.draggedBlock);
506
- const targetIndex = this.blocks.findIndex((b) => b.id === blockId);
507
- if (draggedIndex === -1 || targetIndex === -1)
508
- return;
509
- const newBlocks = [...this.blocks];
510
- const [draggedBlock] = newBlocks.splice(draggedIndex, 1);
511
- newBlocks.splice(targetIndex, 0, draggedBlock);
512
- this.blocks = newBlocks;
513
- this.draggedBlock = null;
514
- }
515
- handleDragEnd() {
516
- this.draggedBlock = null;
517
- }
518
- render() {
519
- return html /*html*/ `
520
- <div class="doc-container">
521
- ${this.blocks.map((block, index) => this.renderBlock(block, index))}
522
- ${this.showBlockMenu ? this.renderBlockMenu() : ''}
523
- </div>
524
- `;
525
- }
526
- renderBlock(block, index) {
527
- const placeholder = index === 0 && !block.content ? this.placeholder : "Type '/' for commands";
528
- const isDivider = block.type === 'divider';
529
- if (isDivider) {
530
- return html /*html*/ `
531
- <div class="doc-block block-divider" data-block-id="${block.id}">
532
- <div
533
- class="block-handle"
534
- draggable="${!this.readonly}"
535
- @dragstart="${(e) => this.handleDragStart(block.id, e)}"
536
- @dragend="${() => this.handleDragEnd()}"
537
- >
538
- <svg viewBox="0 0 16 16" fill="currentColor">
539
- <circle cx="3" cy="3" r="1.5" />
540
- <circle cx="8" cy="3" r="1.5" />
541
- <circle cx="3" cy="8" r="1.5" />
542
- <circle cx="8" cy="8" r="1.5" />
543
- <circle cx="3" cy="13" r="1.5" />
544
- <circle cx="8" cy="13" r="1.5" />
545
- </svg>
546
- </div>
547
- </div>
548
- `;
549
- }
550
- const isTodo = block.type === 'todo';
551
- const isNumberedList = block.type === 'numbered-list';
552
- return html /*html*/ `
553
- <div
554
- class="doc-block block-${block.type} ${block.checked ? 'checked' : ''} ${this
555
- .draggedBlock === block.id
556
- ? 'dragging'
557
- : ''}"
558
- data-block-id="${block.id}"
559
- @dragover="${(e) => this.handleDragOver(block.id, e)}"
560
- @drop="${(e) => this.handleDrop(block.id, e)}"
561
- data-number="${isNumberedList ? index + 1 : ''}"
562
- >
563
- <div
564
- class="block-handle"
565
- draggable="${!this.readonly}"
566
- @dragstart="${(e) => this.handleDragStart(block.id, e)}"
567
- @dragend="${() => this.handleDragEnd()}"
568
- >
569
- <svg viewBox="0 0 16 16" fill="currentColor">
570
- <circle cx="3" cy="3" r="1.5" />
571
- <circle cx="8" cy="3" r="1.5" />
572
- <circle cx="3" cy="8" r="1.5" />
573
- <circle cx="8" cy="8" r="1.5" />
574
- <circle cx="3" cy="13" r="1.5" />
575
- <circle cx="8" cy="13" r="1.5" />
576
- </svg>
577
- </div>
578
- ${isTodo
579
- ? html `<input
580
- type="checkbox"
581
- class="todo-checkbox"
582
- ?checked="${block.checked}"
583
- @change="${() => this.handleTodoToggle(block.id)}"
584
- />`
585
- : ''}
586
- <div
587
- class="block-content"
588
- contenteditable="${!this.readonly}"
589
- data-block-id="${block.id}"
590
- data-placeholder="${placeholder}"
591
- @input="${(e) => this.handleInput(block.id, e)}"
592
- @focus="${() => this.handleFocus(block.id)}"
593
- @blur="${() => this.handleBlur(block.id)}"
594
- >
595
- ${block.content}
596
- </div>
597
- </div>
598
- `;
599
- }
600
- renderBlockMenu() {
601
- const items = this.getFilteredBlockMenuItems();
602
- return html /*html*/ `
603
- <div class="block-menu">
604
- ${items.map((item, index) => html `
605
- <div
606
- class="block-menu-item ${index === this.blockMenuSelectedIndex ? 'selected' : ''}"
607
- @click="${() => this.handleBlockMenuItemClick(this.blockMenuBlockId, item.type)}"
608
- >
609
- <div class="block-menu-icon">${item.icon}</div>
610
- <div class="block-menu-label">${item.label}</div>
611
- </div>
612
- `)}
613
- ${items.length === 0
614
- ? html `<div class="block-menu-item">
615
- <div class="block-menu-label">No results</div>
616
- </div>`
617
- : ''}
618
- </div>
619
- `;
470
+ this.editor.innerHTML = '<p><br></p>';
620
471
  }
621
472
  });
622
473
  return _classThis;