@konce-pt/react 0.8.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 (245) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +58 -0
  3. package/dist/accordion/accordion.d.ts +45 -0
  4. package/dist/accordion/accordion.js +69 -0
  5. package/dist/alert/alert.d.ts +25 -0
  6. package/dist/alert/alert.js +31 -0
  7. package/dist/app-shell/app-shell.d.ts +94 -0
  8. package/dist/app-shell/app-shell.js +105 -0
  9. package/dist/auth/auth-types.d.ts +44 -0
  10. package/dist/auth/auth-types.js +2 -0
  11. package/dist/auth/auth.d.ts +76 -0
  12. package/dist/auth/auth.js +184 -0
  13. package/dist/autocomplete/autocomplete.d.ts +18 -0
  14. package/dist/autocomplete/autocomplete.js +72 -0
  15. package/dist/avatar/avatar.d.ts +33 -0
  16. package/dist/avatar/avatar.js +30 -0
  17. package/dist/avatar-group/avatar-group.d.ts +21 -0
  18. package/dist/avatar-group/avatar-group.js +15 -0
  19. package/dist/badge/badge.d.ts +25 -0
  20. package/dist/badge/badge.js +14 -0
  21. package/dist/bottom-sheet/bottom-sheet.d.ts +21 -0
  22. package/dist/bottom-sheet/bottom-sheet.js +22 -0
  23. package/dist/breadcrumb/breadcrumb.d.ts +19 -0
  24. package/dist/breadcrumb/breadcrumb.js +20 -0
  25. package/dist/button/button.d.ts +27 -0
  26. package/dist/button/button.js +49 -0
  27. package/dist/button-group/button-group.d.ts +25 -0
  28. package/dist/button-group/button-group.js +30 -0
  29. package/dist/card/card.d.ts +66 -0
  30. package/dist/card/card.js +96 -0
  31. package/dist/carousel/carousel.d.ts +20 -0
  32. package/dist/carousel/carousel.js +24 -0
  33. package/dist/charts/chart-legend.d.ts +23 -0
  34. package/dist/charts/chart-legend.js +9 -0
  35. package/dist/charts/chart-tooltip.d.ts +21 -0
  36. package/dist/charts/chart-tooltip.js +13 -0
  37. package/dist/charts/chart.d.ts +18 -0
  38. package/dist/charts/chart.js +231 -0
  39. package/dist/charts-entry/public-api.d.ts +4 -0
  40. package/dist/charts-entry/public-api.js +7 -0
  41. package/dist/checkbox/checkbox.d.ts +33 -0
  42. package/dist/checkbox/checkbox.js +29 -0
  43. package/dist/chip/chip.d.ts +22 -0
  44. package/dist/chip/chip.js +16 -0
  45. package/dist/chips-input/chips-input.d.ts +25 -0
  46. package/dist/chips-input/chips-input.js +54 -0
  47. package/dist/clock/clock.d.ts +17 -0
  48. package/dist/clock/clock.js +57 -0
  49. package/dist/color-picker/color-picker.d.ts +23 -0
  50. package/dist/color-picker/color-picker.js +28 -0
  51. package/dist/confirm/confirm-container.d.ts +13 -0
  52. package/dist/confirm/confirm-container.js +26 -0
  53. package/dist/confirm/confirm-store.d.ts +29 -0
  54. package/dist/confirm/confirm-store.js +52 -0
  55. package/dist/context-menu/context-menu.d.ts +21 -0
  56. package/dist/context-menu/context-menu.js +36 -0
  57. package/dist/data-table/data-table-types.d.ts +67 -0
  58. package/dist/data-table/data-table-types.js +1 -0
  59. package/dist/data-table/data-table.d.ts +62 -0
  60. package/dist/data-table/data-table.js +453 -0
  61. package/dist/data-table/export-csv.d.ts +7 -0
  62. package/dist/data-table/export-csv.js +19 -0
  63. package/dist/data-view/data-view.d.ts +21 -0
  64. package/dist/data-view/data-view.js +25 -0
  65. package/dist/date-range/date-range.d.ts +30 -0
  66. package/dist/date-range/date-range.js +155 -0
  67. package/dist/datepicker/datepicker.d.ts +39 -0
  68. package/dist/datepicker/datepicker.js +202 -0
  69. package/dist/dialog/dialog.d.ts +35 -0
  70. package/dist/dialog/dialog.js +59 -0
  71. package/dist/divider/divider.d.ts +16 -0
  72. package/dist/divider/divider.js +13 -0
  73. package/dist/drawer/drawer.d.ts +25 -0
  74. package/dist/drawer/drawer.js +25 -0
  75. package/dist/empty/empty.d.ts +18 -0
  76. package/dist/empty/empty.js +14 -0
  77. package/dist/fab/fab.d.ts +22 -0
  78. package/dist/fab/fab.js +17 -0
  79. package/dist/fieldset/fieldset.d.ts +22 -0
  80. package/dist/fieldset/fieldset.js +24 -0
  81. package/dist/file-upload/file-upload.d.ts +22 -0
  82. package/dist/file-upload/file-upload.js +50 -0
  83. package/dist/form-field/form-field-context.d.ts +23 -0
  84. package/dist/form-field/form-field-context.js +17 -0
  85. package/dist/form-field/form-field.d.ts +39 -0
  86. package/dist/form-field/form-field.js +41 -0
  87. package/dist/galleria/galleria.d.ts +22 -0
  88. package/dist/galleria/galleria.js +21 -0
  89. package/dist/grid/col.d.ts +39 -0
  90. package/dist/grid/col.js +32 -0
  91. package/dist/grid/flex.d.ts +45 -0
  92. package/dist/grid/flex.js +35 -0
  93. package/dist/grid/grid.d.ts +47 -0
  94. package/dist/grid/grid.js +38 -0
  95. package/dist/grid/layout.d.ts +18 -0
  96. package/dist/grid/layout.js +32 -0
  97. package/dist/grid-entry/public-api.d.ts +6 -0
  98. package/dist/grid-entry/public-api.js +8 -0
  99. package/dist/i18n/i18n-context.d.ts +45 -0
  100. package/dist/i18n/i18n-context.js +65 -0
  101. package/dist/icon/icon-registry.d.ts +16 -0
  102. package/dist/icon/icon-registry.js +43 -0
  103. package/dist/icon/icon.d.ts +17 -0
  104. package/dist/icon/icon.js +23 -0
  105. package/dist/icon-button/icon-button.d.ts +23 -0
  106. package/dist/icon-button/icon-button.js +14 -0
  107. package/dist/icons-entry/public-api.d.ts +12 -0
  108. package/dist/icons-entry/public-api.js +20 -0
  109. package/dist/image/image.d.ts +17 -0
  110. package/dist/image/image.js +16 -0
  111. package/dist/input/input.d.ts +41 -0
  112. package/dist/input/input.js +36 -0
  113. package/dist/input-mask/input-mask.d.ts +19 -0
  114. package/dist/input-mask/input-mask.js +65 -0
  115. package/dist/input-number/input-number.d.ts +28 -0
  116. package/dist/input-number/input-number.js +52 -0
  117. package/dist/input-otp/input-otp.d.ts +21 -0
  118. package/dist/input-otp/input-otp.js +59 -0
  119. package/dist/internal/cn.d.ts +9 -0
  120. package/dist/internal/cn.js +19 -0
  121. package/dist/internal/compose-refs.d.ts +6 -0
  122. package/dist/internal/compose-refs.js +16 -0
  123. package/dist/internal/overlay-container.d.ts +3 -0
  124. package/dist/internal/overlay-container.js +19 -0
  125. package/dist/internal/overlay.d.ts +26 -0
  126. package/dist/internal/overlay.js +64 -0
  127. package/dist/internal/slot.d.ts +23 -0
  128. package/dist/internal/slot.js +49 -0
  129. package/dist/internal/types.d.ts +25 -0
  130. package/dist/internal/types.js +1 -0
  131. package/dist/internal/use-breakpoint.d.ts +18 -0
  132. package/dist/internal/use-breakpoint.js +52 -0
  133. package/dist/internal/use-controllable-state.d.ts +11 -0
  134. package/dist/internal/use-controllable-state.js +18 -0
  135. package/dist/internal/use-id.d.ts +9 -0
  136. package/dist/internal/use-id.js +13 -0
  137. package/dist/knob/knob.d.ts +27 -0
  138. package/dist/knob/knob.js +86 -0
  139. package/dist/listbox/listbox.d.ts +28 -0
  140. package/dist/listbox/listbox.js +43 -0
  141. package/dist/map/map-create-dialog.d.ts +21 -0
  142. package/dist/map/map-create-dialog.js +196 -0
  143. package/dist/map/map-hours-field.d.ts +22 -0
  144. package/dist/map/map-hours-field.js +90 -0
  145. package/dist/map/map-image-field.d.ts +24 -0
  146. package/dist/map/map-image-field.js +61 -0
  147. package/dist/map/map-legend.d.ts +29 -0
  148. package/dist/map/map-legend.js +41 -0
  149. package/dist/map/map-poi-card.d.ts +42 -0
  150. package/dist/map/map-poi-card.js +152 -0
  151. package/dist/map/map-search.d.ts +37 -0
  152. package/dist/map/map-search.js +201 -0
  153. package/dist/map/map.d.ts +113 -0
  154. package/dist/map/map.js +425 -0
  155. package/dist/map/ports.d.ts +76 -0
  156. package/dist/map/ports.js +174 -0
  157. package/dist/map-entry/public-api.d.ts +20 -0
  158. package/dist/map-entry/public-api.js +19 -0
  159. package/dist/megamenu/megamenu.d.ts +17 -0
  160. package/dist/megamenu/megamenu.js +28 -0
  161. package/dist/menu/menu-item.d.ts +33 -0
  162. package/dist/menu/menu-item.js +1 -0
  163. package/dist/menu/menu-rows.d.ts +9 -0
  164. package/dist/menu/menu-rows.js +15 -0
  165. package/dist/menu/menu.d.ts +22 -0
  166. package/dist/menu/menu.js +31 -0
  167. package/dist/menubar/menubar.d.ts +16 -0
  168. package/dist/menubar/menubar.js +39 -0
  169. package/dist/meter-group/meter-group.d.ts +20 -0
  170. package/dist/meter-group/meter-group.js +24 -0
  171. package/dist/order-list/order-list.d.ts +18 -0
  172. package/dist/order-list/order-list.js +37 -0
  173. package/dist/paginator/paginator.d.ts +29 -0
  174. package/dist/paginator/paginator.js +45 -0
  175. package/dist/panel/panel.d.ts +31 -0
  176. package/dist/panel/panel.js +36 -0
  177. package/dist/password/password.d.ts +28 -0
  178. package/dist/password/password.js +37 -0
  179. package/dist/pick-list/pick-list.d.ts +28 -0
  180. package/dist/pick-list/pick-list.js +65 -0
  181. package/dist/popover/popover.d.ts +23 -0
  182. package/dist/popover/popover.js +27 -0
  183. package/dist/progress/progress.d.ts +19 -0
  184. package/dist/progress/progress.js +21 -0
  185. package/dist/public-api.d.ts +181 -0
  186. package/dist/public-api.js +105 -0
  187. package/dist/radio-group/radio-group.d.ts +24 -0
  188. package/dist/radio-group/radio-group.js +23 -0
  189. package/dist/rating/rating.d.ts +22 -0
  190. package/dist/rating/rating.js +30 -0
  191. package/dist/rich-text/lists.d.ts +17 -0
  192. package/dist/rich-text/lists.js +144 -0
  193. package/dist/rich-text/rich-text.d.ts +28 -0
  194. package/dist/rich-text/rich-text.js +654 -0
  195. package/dist/rich-text/sanitize.d.ts +10 -0
  196. package/dist/rich-text/sanitize.js +85 -0
  197. package/dist/rich-text/tables.d.ts +44 -0
  198. package/dist/rich-text/tables.js +268 -0
  199. package/dist/roadmap/roadmap.d.ts +37 -0
  200. package/dist/roadmap/roadmap.js +271 -0
  201. package/dist/roadmap-entry/public-api.d.ts +4 -0
  202. package/dist/roadmap-entry/public-api.js +7 -0
  203. package/dist/scroll-top/scroll-top.d.ts +16 -0
  204. package/dist/scroll-top/scroll-top.js +38 -0
  205. package/dist/select/select.d.ts +37 -0
  206. package/dist/select/select.js +117 -0
  207. package/dist/shared/types.d.ts +8 -0
  208. package/dist/shared/types.js +1 -0
  209. package/dist/sidenav/sidenav.d.ts +26 -0
  210. package/dist/sidenav/sidenav.js +41 -0
  211. package/dist/skeleton/skeleton.d.ts +20 -0
  212. package/dist/skeleton/skeleton.js +15 -0
  213. package/dist/slider/slider.d.ts +28 -0
  214. package/dist/slider/slider.js +30 -0
  215. package/dist/speed-dial/speed-dial.d.ts +20 -0
  216. package/dist/speed-dial/speed-dial.js +23 -0
  217. package/dist/spinner/spinner.d.ts +15 -0
  218. package/dist/spinner/spinner.js +14 -0
  219. package/dist/split-button/split-button.d.ts +28 -0
  220. package/dist/split-button/split-button.js +34 -0
  221. package/dist/splitter/splitter.d.ts +39 -0
  222. package/dist/splitter/splitter.js +80 -0
  223. package/dist/stepper/stepper.d.ts +51 -0
  224. package/dist/stepper/stepper.js +71 -0
  225. package/dist/switch/switch.d.ts +39 -0
  226. package/dist/switch/switch.js +30 -0
  227. package/dist/switch-group/switch-group.d.ts +29 -0
  228. package/dist/switch-group/switch-group.js +47 -0
  229. package/dist/tabs/tabs.d.ts +38 -0
  230. package/dist/tabs/tabs.js +74 -0
  231. package/dist/textarea/textarea.d.ts +28 -0
  232. package/dist/textarea/textarea.js +44 -0
  233. package/dist/timeline/timeline.d.ts +19 -0
  234. package/dist/timeline/timeline.js +12 -0
  235. package/dist/toast/toast-container.d.ts +12 -0
  236. package/dist/toast/toast-container.js +23 -0
  237. package/dist/toast/toast-store.d.ts +26 -0
  238. package/dist/toast/toast-store.js +51 -0
  239. package/dist/toolbar/toolbar.d.ts +29 -0
  240. package/dist/toolbar/toolbar.js +24 -0
  241. package/dist/tooltip/tooltip.d.ts +26 -0
  242. package/dist/tooltip/tooltip.js +113 -0
  243. package/dist/tree/tree.d.ts +31 -0
  244. package/dist/tree/tree.js +62 -0
  245. package/package.json +91 -0
@@ -0,0 +1,654 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { useCallback, useEffect, useRef, useState, } from 'react';
3
+ import { createPortal } from 'react-dom';
4
+ import { cn } from "../internal/cn.js";
5
+ import { useControllableState } from "../internal/use-controllable-state.js";
6
+ import { useKptOverlayContainer } from "../internal/overlay-container.js";
7
+ import { KptOverlay } from "../internal/overlay.js";
8
+ import { useKptMessages } from "../i18n/i18n-context.js";
9
+ import { KptIcon } from "../icon/icon.js";
10
+ import { sanitizeHtml, sanitizeUrl } from "./sanitize.js";
11
+ import { canIndentItem, canOutdentItem, selectedItems, shiftItems } from "./lists.js";
12
+ import { CELL_SELECTED, deleteColumn, deleteRow, insertColumn, insertRow, mergeCells, regionBetween, splitCell, tableHtml, toggleHeaderColumn, toggleHeaderRow, } from "./tables.js";
13
+ const ICON = {
14
+ copy: 'copy',
15
+ paste: 'clipboard',
16
+ pasteText: 'clipboard-text',
17
+ heading: 'code', // nieużywane — „heading" renderuje select
18
+ bold: 'bold',
19
+ italic: 'italic',
20
+ underline: 'underline',
21
+ strike: 'strikethrough',
22
+ ul: 'list',
23
+ ol: 'list-numbers',
24
+ indent: 'indent-increase',
25
+ outdent: 'indent-decrease',
26
+ foreColor: 'palette',
27
+ backColor: 'highlight',
28
+ alignLeft: 'align-left',
29
+ alignCenter: 'align-center',
30
+ alignRight: 'align-right',
31
+ alignJustify: 'align-justified',
32
+ link: 'link',
33
+ table: 'table',
34
+ clear: 'clear-formatting',
35
+ html: 'code',
36
+ };
37
+ /** Narzędzia zwykłe → komenda `execCommand` (zarazem nazwa dla `queryCommandState`). */
38
+ const COMMAND = {
39
+ bold: 'bold',
40
+ italic: 'italic',
41
+ underline: 'underline',
42
+ strike: 'strikeThrough',
43
+ ul: 'insertUnorderedList',
44
+ ol: 'insertOrderedList',
45
+ alignLeft: 'justifyLeft',
46
+ alignCenter: 'justifyCenter',
47
+ alignRight: 'justifyRight',
48
+ alignJustify: 'justifyFull',
49
+ clear: 'removeFormat',
50
+ };
51
+ const SWATCHES = [
52
+ '#000000', '#434343', '#666666', '#999999', '#b7b7b7', '#cccccc', '#ffffff',
53
+ '#e11d48', '#ea580c', '#ca8a04', '#16a34a', '#0891b2', '#2563eb', '#7c3aed',
54
+ ];
55
+ const DEFAULT_TOOLBAR = [
56
+ ['copy', 'paste', 'pasteText'],
57
+ ['heading', 'bold', 'italic', 'underline', 'strike', 'ul', 'ol', 'indent', 'outdent'],
58
+ ['foreColor', 'backColor'],
59
+ ['alignLeft', 'alignCenter', 'alignRight', 'alignJustify'],
60
+ ['link', 'table', 'clear', 'html'],
61
+ ];
62
+ const GRID_ROWS = Array.from({ length: 8 }, (_, i) => i + 1);
63
+ const GRID_COLS = Array.from({ length: 8 }, (_, i) => i + 1);
64
+ /**
65
+ * Jedyne wywołania `document.execCommand` w komponencie. API jest formalnie wycofane, ale nie ma
66
+ * zamiennika: to jedyny sposób formatowania trafiający do natywnego stosu cofania przeglądarki
67
+ * (Ctrl+Z). Własne komendy wymagałyby własnej historii, czyli przepisania edytora.
68
+ */
69
+ function legacyExec(cmd, value) {
70
+ return document.execCommand(cmd, false, value); // NOSONAR: brak zamiennika, patrz JSDoc
71
+ }
72
+ function legacyState(cmd) {
73
+ try {
74
+ return document.queryCommandState(cmd); // NOSONAR: brak zamiennika
75
+ }
76
+ catch {
77
+ return false;
78
+ }
79
+ }
80
+ /**
81
+ * Lekki edytor tekstu sformatowanego na `contenteditable`, z konfigurowalnym paskiem sekcji
82
+ * i edycją tabel. Wartością jest HTML.
83
+ *
84
+ * @example
85
+ * <KptRichText value={html} onValueChange={setHtml} />
86
+ * <KptRichText value={html} onValueChange={setHtml} toolbar={[['bold', 'italic'], ['link']]} />
87
+ */
88
+ export function KptRichText({ value, defaultValue = '', onValueChange, toolbar = DEFAULT_TOOLBAR, placeholder = '', maxListLevel = 6, disabled = false, invalid = false, touched, onTouch, locale, dictionary, className, ...rest }) {
89
+ const { t } = useKptMessages({ locale, dictionary });
90
+ const container = useKptOverlayContainer();
91
+ const editorRef = useRef(null);
92
+ const savedRange = useRef(null);
93
+ const [current, setCurrent] = useControllableState({ value, defaultValue, onChange: onValueChange });
94
+ const showInvalid = invalid && (touched ?? true);
95
+ // Kotwice narzędzi z panelem — `KptOverlay` przypina panel do konkretnego przycisku.
96
+ const [styleAnchor, setStyleAnchor] = useState(null);
97
+ const [foreAnchor, setForeAnchor] = useState(null);
98
+ const [backAnchor, setBackAnchor] = useState(null);
99
+ const [tableAnchor, setTableAnchor] = useState(null);
100
+ const [styleOpen, setStyleOpen] = useState(false);
101
+ const [colorOpen, setColorOpen] = useState(false);
102
+ const [colorKind, setColorKind] = useState('foreColor');
103
+ const [tableOpen, setTableOpen] = useState(false);
104
+ const [grid, setGrid] = useState({ r: 0, c: 0 });
105
+ const [sourceView, setSourceView] = useState(false);
106
+ const [menuOpen, setMenuOpen] = useState(false);
107
+ const [menuPoint, setMenuPoint] = useState({ x: 0, y: 0 });
108
+ const [borderOpen, setBorderOpen] = useState(false);
109
+ const [borderTarget, setBorderTarget] = useState('table');
110
+ const [activeCmds, setActiveCmds] = useState(new Set());
111
+ const [canIndent, setCanIndent] = useState(false);
112
+ const [canOutdent, setCanOutdent] = useState(false);
113
+ const [currentBlock, setCurrentBlock] = useState('p');
114
+ const [selCount, setSelCount] = useState(0);
115
+ const ctxCell = useRef(null);
116
+ const canSplitCtx = useRef(false);
117
+ const selCells = useRef(new Set());
118
+ const selAnchor = useRef(null);
119
+ const selecting = useRef(false);
120
+ const resize = useRef(null);
121
+ const sections = toolbar.length === 0
122
+ ? []
123
+ : Array.isArray(toolbar[0])
124
+ ? toolbar
125
+ : [toolbar];
126
+ const pushValue = useCallback(() => {
127
+ const el = editorRef.current;
128
+ if (el)
129
+ setCurrent(el.innerHTML);
130
+ }, [setCurrent]);
131
+ /*
132
+ * Zapis z zewnątrz wstawiamy ręcznie, bo `contenteditable` nie może być renderowany przez
133
+ * Reacta — przerysowanie kasowałoby zaznaczenie i pozycję kursora przy każdym znaku.
134
+ * Porównanie z surowym `innerHTML` sprawia, że pisanie w edytorze nie wchodzi w tę gałąź,
135
+ * więc sanityzacja włącza się wyłącznie przy zapisie z aplikacji albo z trybu HTML.
136
+ */
137
+ useEffect(() => {
138
+ const el = editorRef.current;
139
+ if (el && el.innerHTML !== current)
140
+ el.innerHTML = sanitizeHtml(current);
141
+ }, [current, sourceView]);
142
+ const inEditor = useCallback(() => {
143
+ const el = editorRef.current;
144
+ const selection = window.getSelection();
145
+ return !!el && !!selection && selection.rangeCount > 0 && el.contains(selection.anchorNode);
146
+ }, []);
147
+ /** Tag bloku, w którym stoi kursor — dla selecta stylu; domyślnie paragraf. */
148
+ const blockTag = useCallback(() => {
149
+ let node = window.getSelection()?.anchorNode;
150
+ while (node && node !== editorRef.current) {
151
+ const tag = node.tagName?.toLowerCase();
152
+ if (tag && /^(h[1-6]|p|blockquote)$/.test(tag))
153
+ return tag;
154
+ node = node.parentElement;
155
+ }
156
+ return 'p';
157
+ }, []);
158
+ const refreshState = useCallback(() => {
159
+ if (disabled || !inEditor())
160
+ return;
161
+ const active = new Set();
162
+ for (const cmd of Object.values(COMMAND)) {
163
+ if (cmd && cmd !== 'removeFormat' && legacyState(cmd))
164
+ active.add(cmd);
165
+ }
166
+ setActiveCmds(active);
167
+ const items = selectedItems(editorRef.current);
168
+ setCanIndent(items.some((li) => canIndentItem(editorRef.current, li, maxListLevel)));
169
+ setCanOutdent(items.some((li) => canOutdentItem(li)));
170
+ setCurrentBlock(blockTag());
171
+ }, [blockTag, disabled, inEditor, maxListLevel]);
172
+ const clearCellSelection = useCallback(() => {
173
+ for (const cell of selCells.current)
174
+ cell.classList.remove(CELL_SELECTED);
175
+ selCells.current = new Set();
176
+ setSelCount(0);
177
+ }, []);
178
+ // Stan narzędzi zależy od zaznaczenia, a to zmienia się także poza naszymi zdarzeniami
179
+ // (myszą, klawiaturą, przez inne okno), więc nasłuchujemy `selectionchange` na dokumencie.
180
+ useEffect(() => {
181
+ const onSelectionChange = () => refreshState();
182
+ document.addEventListener('selectionchange', onSelectionChange);
183
+ return () => document.removeEventListener('selectionchange', onSelectionChange);
184
+ }, [refreshState]);
185
+ // Przeciąganie krawędzi kolumny i zaznaczanie komórek biegną na całym dokumencie: kursor
186
+ // wyjeżdża poza tabelę praktycznie zawsze.
187
+ useEffect(() => {
188
+ const onMove = (event) => {
189
+ if (resize.current) {
190
+ const width = Math.max(32, resize.current.startW + (event.clientX - resize.current.startX));
191
+ resize.current.col.style.width = `${width}px`;
192
+ return;
193
+ }
194
+ if (!selAnchor.current || !(event.buttons & 1))
195
+ return;
196
+ const target = event.target;
197
+ if (!(target instanceof Element))
198
+ return;
199
+ const focus = target.closest('td, th');
200
+ if (!focus || focus.closest('table') !== selAnchor.current.closest('table'))
201
+ return;
202
+ if (focus === selAnchor.current && !selecting.current)
203
+ return;
204
+ selecting.current = true;
205
+ event.preventDefault();
206
+ window.getSelection()?.removeAllRanges();
207
+ const region = regionBetween(selAnchor.current, focus);
208
+ clearCellSelection();
209
+ for (const cell of region)
210
+ cell.classList.add(CELL_SELECTED);
211
+ selCells.current = region;
212
+ setSelCount(region.size);
213
+ };
214
+ const onUp = () => {
215
+ if (resize.current) {
216
+ resize.current = null;
217
+ pushValue();
218
+ }
219
+ if (selAnchor.current && !selecting.current)
220
+ clearCellSelection(); // zwykły klik
221
+ selAnchor.current = null;
222
+ selecting.current = false;
223
+ };
224
+ document.addEventListener('mousemove', onMove);
225
+ document.addEventListener('mouseup', onUp);
226
+ return () => {
227
+ document.removeEventListener('mousemove', onMove);
228
+ document.removeEventListener('mouseup', onUp);
229
+ };
230
+ }, [clearCellSelection, pushValue]);
231
+ const focusEditor = () => editorRef.current?.focus();
232
+ const saveSelection = () => {
233
+ const selection = window.getSelection();
234
+ savedRange.current = selection && selection.rangeCount > 0 ? selection.getRangeAt(0).cloneRange() : null;
235
+ };
236
+ const restoreSelection = () => {
237
+ const selection = window.getSelection();
238
+ if (savedRange.current && selection) {
239
+ selection.removeAllRanges();
240
+ selection.addRange(savedRange.current);
241
+ }
242
+ };
243
+ // ── Schowek ──
244
+ const copySelection = async () => {
245
+ const selection = window.getSelection();
246
+ if (!selection || selection.rangeCount === 0 || selection.isCollapsed)
247
+ return;
248
+ const holder = document.createElement('div');
249
+ holder.append(selection.getRangeAt(0).cloneContents());
250
+ try {
251
+ await navigator.clipboard.write([
252
+ new ClipboardItem({
253
+ 'text/html': new Blob([holder.innerHTML], { type: 'text/html' }),
254
+ 'text/plain': new Blob([selection.toString()], { type: 'text/plain' }),
255
+ }),
256
+ ]);
257
+ }
258
+ catch {
259
+ legacyExec('copy'); // brak uprawnień albo starsza przeglądarka
260
+ }
261
+ };
262
+ const pasteRich = async () => {
263
+ try {
264
+ const items = await navigator.clipboard.read();
265
+ for (const item of items) {
266
+ if (item.types.includes('text/html')) {
267
+ const html = sanitizeHtml(await (await item.getType('text/html')).text());
268
+ focusEditor();
269
+ legacyExec('insertHTML', html);
270
+ pushValue();
271
+ return;
272
+ }
273
+ }
274
+ const text = await navigator.clipboard.readText();
275
+ focusEditor();
276
+ legacyExec('insertText', text);
277
+ pushValue();
278
+ }
279
+ catch {
280
+ /* brak uprawnień albo API — ignorujemy */
281
+ }
282
+ };
283
+ const pastePlain = async () => {
284
+ try {
285
+ const text = await navigator.clipboard.readText();
286
+ focusEditor();
287
+ legacyExec('insertText', text);
288
+ pushValue();
289
+ }
290
+ catch {
291
+ /* brak uprawnień albo API — ignorujemy */
292
+ }
293
+ };
294
+ // ── Narzędzia ──
295
+ const exec = (item) => {
296
+ if (disabled)
297
+ return;
298
+ if (item === 'html') {
299
+ setSourceView((v) => !v);
300
+ return;
301
+ }
302
+ if (item === 'table') {
303
+ saveSelection();
304
+ setTableOpen(true);
305
+ return;
306
+ }
307
+ focusEditor();
308
+ switch (item) {
309
+ case 'link': {
310
+ const url = window.prompt(t('richText.linkPrompt'));
311
+ if (url) {
312
+ const safe = sanitizeUrl(url);
313
+ if (safe)
314
+ legacyExec('createLink', safe);
315
+ }
316
+ break;
317
+ }
318
+ case 'copy':
319
+ void copySelection();
320
+ return;
321
+ case 'paste':
322
+ void pasteRich();
323
+ return;
324
+ case 'pasteText':
325
+ void pastePlain();
326
+ return;
327
+ case 'indent':
328
+ case 'outdent':
329
+ if (shiftItems(editorRef.current, item, maxListLevel)) {
330
+ pushValue();
331
+ refreshState();
332
+ }
333
+ return;
334
+ default: {
335
+ const cmd = COMMAND[item];
336
+ if (cmd)
337
+ legacyExec(cmd);
338
+ }
339
+ }
340
+ pushValue();
341
+ refreshState();
342
+ };
343
+ /**
344
+ * Tab: wcięcie w liście, przeskok komórki w tabeli. Poza nimi i na krawędziach zostawiamy Tab
345
+ * natywny — inaczej edytor byłby pułapką klawiaturową (WCAG 2.1.2).
346
+ */
347
+ const onKeyDown = (event) => {
348
+ if (disabled || event.key !== 'Tab' || event.ctrlKey || event.altKey || event.metaKey)
349
+ return;
350
+ if (shiftItems(editorRef.current, event.shiftKey ? 'outdent' : 'indent', maxListLevel)) {
351
+ event.preventDefault();
352
+ pushValue();
353
+ refreshState();
354
+ return;
355
+ }
356
+ const node = window.getSelection()?.anchorNode ?? null;
357
+ const el = node instanceof Element ? node : (node?.parentElement ?? null);
358
+ const cell = el?.closest('td, th');
359
+ if (!cell || !editorRef.current?.contains(cell))
360
+ return;
361
+ const table = cell.closest('table');
362
+ if (!table)
363
+ return;
364
+ const cells = [...table.querySelectorAll('td, th')];
365
+ const next = cells[cells.indexOf(cell) + (event.shiftKey ? -1 : 1)];
366
+ if (!next)
367
+ return; // koniec tabeli — Tab zostaje natywny
368
+ event.preventDefault();
369
+ const range = document.createRange();
370
+ range.selectNodeContents(next);
371
+ const selection = window.getSelection();
372
+ selection?.removeAllRanges();
373
+ selection?.addRange(range);
374
+ clearCellSelection();
375
+ refreshState();
376
+ };
377
+ /** Natywne wklejanie — czyścimy treść, żeby nie wnosić śmieci z edytorów tekstu i stron. */
378
+ const onPaste = (event) => {
379
+ if (disabled)
380
+ return;
381
+ const html = event.clipboardData?.getData('text/html');
382
+ const text = event.clipboardData?.getData('text/plain') ?? '';
383
+ event.preventDefault();
384
+ if (html)
385
+ legacyExec('insertHTML', sanitizeHtml(html));
386
+ else
387
+ legacyExec('insertText', text);
388
+ pushValue();
389
+ };
390
+ // ── Kolory ──
391
+ const activeCells = () => {
392
+ if (selCells.current.size)
393
+ return [...selCells.current];
394
+ return ctxCell.current ? [ctxCell.current] : [];
395
+ };
396
+ const applyColor = (color) => {
397
+ setColorOpen(false);
398
+ if (colorKind === 'cellBg') {
399
+ for (const cell of activeCells())
400
+ cell.style.backgroundColor = color;
401
+ pushValue();
402
+ return;
403
+ }
404
+ focusEditor();
405
+ restoreSelection();
406
+ if (colorKind === 'foreColor')
407
+ legacyExec('foreColor', color);
408
+ else if (!legacyExec('hiliteColor', color))
409
+ legacyExec('backColor', color);
410
+ pushValue();
411
+ };
412
+ // ── Tabele ──
413
+ const insertTableAt = (rows, cols) => {
414
+ setTableOpen(false);
415
+ setGrid({ r: 0, c: 0 });
416
+ focusEditor();
417
+ restoreSelection();
418
+ legacyExec('insertHTML', `${tableHtml(rows, cols)}<p><br></p>`);
419
+ pushValue();
420
+ };
421
+ const afterTableOp = () => {
422
+ setMenuOpen(false);
423
+ pushValue();
424
+ };
425
+ const onContextMenu = (event) => {
426
+ if (disabled)
427
+ return;
428
+ const cell = event.target.closest('td, th');
429
+ if (!cell || !editorRef.current?.contains(cell))
430
+ return; // poza tabelą zostaje natywne menu
431
+ event.preventDefault();
432
+ ctxCell.current = cell;
433
+ canSplitCtx.current = (cell.colSpan || 1) > 1 || (cell.rowSpan || 1) > 1;
434
+ setMenuPoint({ x: event.clientX, y: event.clientY });
435
+ setMenuOpen(true);
436
+ };
437
+ const onEditorMouseDown = (event) => {
438
+ if (disabled || event.button !== 0)
439
+ return;
440
+ const cell = event.target.closest('td, th');
441
+ if (!cell) {
442
+ clearCellSelection();
443
+ return;
444
+ }
445
+ const rect = cell.getBoundingClientRect();
446
+ if (event.clientX >= rect.right - 6) {
447
+ // Strefa przy prawej krawędzi komórki = zmiana szerokości kolumny.
448
+ const col = cell.closest('table')?.querySelectorAll('col')[cell.cellIndex];
449
+ if (col) {
450
+ event.preventDefault();
451
+ resize.current = { col, startX: event.clientX, startW: col.getBoundingClientRect().width };
452
+ return;
453
+ }
454
+ }
455
+ clearCellSelection();
456
+ selAnchor.current = cell;
457
+ selecting.current = false;
458
+ };
459
+ const onEditorMouseMove = (event) => {
460
+ const cell = event.target.closest('td, th');
461
+ const editor = editorRef.current;
462
+ if (!editor)
463
+ return;
464
+ const near = !!cell && event.clientX >= cell.getBoundingClientRect().right - 6;
465
+ editor.classList.toggle('is-col-resize', near || !!resize.current);
466
+ };
467
+ const borderCells = () => {
468
+ if (borderTarget === 'cell')
469
+ return activeCells();
470
+ const table = ctxCell.current?.closest('table');
471
+ return table ? Array.from(table.querySelectorAll('td, th')) : [];
472
+ };
473
+ const setBorderWidth = (width) => {
474
+ const px = { thin: '1px', medium: '2px', thick: '3px' };
475
+ for (const cell of borderCells()) {
476
+ if (width === 'none') {
477
+ cell.style.borderStyle = 'none';
478
+ cell.style.borderWidth = '';
479
+ }
480
+ else {
481
+ cell.style.borderStyle = 'solid';
482
+ cell.style.borderWidth = px[width];
483
+ }
484
+ }
485
+ pushValue();
486
+ };
487
+ const setBorderColor = (color) => {
488
+ for (const cell of borderCells()) {
489
+ cell.style.borderColor = color;
490
+ if (!cell.style.borderStyle || cell.style.borderStyle === 'none')
491
+ cell.style.borderStyle = 'solid';
492
+ if (!cell.style.borderWidth)
493
+ cell.style.borderWidth = '1px';
494
+ }
495
+ pushValue();
496
+ };
497
+ // ── Prezentacja narzędzi ──
498
+ const styleOptions = [
499
+ { tag: 'p', label: t('richText.styleNormal') },
500
+ ...[1, 2, 3, 4, 5, 6].map((level) => ({
501
+ tag: `h${level}`,
502
+ label: t('richText.styleHeading', { level }),
503
+ })),
504
+ ];
505
+ const styleLabel = styleOptions.find((o) => o.tag === currentBlock)?.label ?? t('richText.styleNormal');
506
+ const isActive = (item) => {
507
+ if (item === 'html')
508
+ return sourceView;
509
+ const cmd = COMMAND[item];
510
+ return !!cmd && activeCmds.has(cmd);
511
+ };
512
+ /** `aria-pressed` tylko dla przełączników — wcięcia są akcjami, nie stanem. */
513
+ const pressed = (item) => item === 'indent' || item === 'outdent' ? undefined : isActive(item);
514
+ const enabled = (item) => {
515
+ if (item === 'indent')
516
+ return canIndent;
517
+ if (item === 'outdent')
518
+ return canOutdent;
519
+ return true;
520
+ };
521
+ const palette = (onPick, customLabel) => (_jsxs("div", { className: "kpt-rich-text__palette", onMouseDown: (event) => event.preventDefault(), children: [_jsx("div", { className: "kpt-rich-text__swatches", children: SWATCHES.map((color) => (_jsx("button", { type: "button", className: "kpt-rich-text__swatch", style: { background: color }, "aria-label": color, onClick: () => onPick(color) }, color))) }), _jsxs("label", { className: "kpt-rich-text__picker", children: [_jsx("span", { children: customLabel }), _jsx("input", { type: "color", onInput: (event) => onPick(event.target.value) })] })] }));
522
+ /**
523
+ * Panel otwarty przy dolnej albo prawej krawędzi okna wychodziłby poza ekran — CDK robił to
524
+ * w Angularze sam, tutaj przesuwamy go po zamontowaniu, gdy już znać jego rozmiar.
525
+ */
526
+ const clampToViewport = (el) => {
527
+ if (!el)
528
+ return;
529
+ const rect = el.getBoundingClientRect();
530
+ const left = Math.max(8, Math.min(menuPoint.x, window.innerWidth - rect.width - 8));
531
+ const top = Math.max(8, Math.min(menuPoint.y, window.innerHeight - rect.height - 8));
532
+ el.style.left = `${left}px`;
533
+ el.style.top = `${top}px`;
534
+ };
535
+ /**
536
+ * Panel przypięty do punktu kursora — menu tabeli i jego popovery nie mają wyzwalacza, więc
537
+ * nie przechodzą przez `KptOverlay`; pozycję niosą współrzędne kliknięcia, tak jak
538
+ * w `KptContextMenu`.
539
+ */
540
+ const pointPanel = (content, onClose) => {
541
+ if (!container)
542
+ return null; // render na serwerze — nie ma czego portalować
543
+ const layer = { zIndex: 'var(--kpt-z-overlay, 1100)' };
544
+ return createPortal(_jsxs(_Fragment, { children: [_jsx("div", { style: { position: 'fixed', inset: 0, ...layer }, onClick: onClose, onContextMenu: (event) => {
545
+ event.preventDefault();
546
+ onClose();
547
+ } }), _jsx("div", { ref: clampToViewport, style: { position: 'fixed', left: menuPoint.x, top: menuPoint.y, ...layer }, children: content })] }), container);
548
+ };
549
+ const toolButton = (item) => (_jsx("button", { type: "button", className: cn('kpt-rich-text__tool', isActive(item) && 'is-active'), "aria-label": t(`richText.${item}`), "aria-pressed": pressed(item), title: t(`richText.${item}`), disabled: disabled || !enabled(item), onMouseDown: (event) => event.preventDefault(), onClick: () => exec(item), children: _jsx(KptIcon, { name: ICON[item] }) }, item));
550
+ const menuItem = (icon, label, onClick, extra) => (_jsxs("button", { type: "button", className: cn('kpt-rt-menu__item', extra), onClick: onClick, children: [_jsx(KptIcon, { name: icon }), " ", label] }));
551
+ return (_jsxs("div", { className: cn('kpt-rich-text-host', className), ...rest, children: [_jsxs("div", { className: cn('kpt-rich-text', showInvalid && 'kpt-rich-text--invalid', disabled && 'kpt-rich-text--disabled'), children: [_jsx("div", { className: "kpt-rich-text__toolbar", role: "toolbar", children: sections.map((section, index) => (_jsxs("span", { style: { display: 'contents' }, children: [index > 0 ? _jsx("span", { className: "kpt-rich-text__divider", "aria-hidden": "true" }) : null, section.map((item) => {
552
+ if (item === 'heading') {
553
+ return (_jsxs("button", { ref: setStyleAnchor, type: "button", className: cn('kpt-rich-text__style', styleOpen && 'is-open'), disabled: disabled, "aria-label": t('richText.heading'), "aria-expanded": styleOpen, title: t('richText.heading'), onMouseDown: (event) => event.preventDefault(), onClick: () => {
554
+ if (disabled)
555
+ return;
556
+ saveSelection();
557
+ setStyleOpen(true);
558
+ }, children: [_jsx("span", { children: styleLabel }), _jsx(KptIcon, { name: "chevron-down", className: "kpt-rich-text__style-caret" })] }, item));
559
+ }
560
+ if (item === 'foreColor' || item === 'backColor') {
561
+ return (_jsx("button", { ref: item === 'foreColor' ? setForeAnchor : setBackAnchor, type: "button", className: "kpt-rich-text__tool kpt-rich-text__tool--color", "aria-label": t(`richText.${item}`), title: t(`richText.${item}`), disabled: disabled, onMouseDown: (event) => event.preventDefault(), onClick: () => {
562
+ if (disabled)
563
+ return;
564
+ saveSelection();
565
+ setColorKind(item);
566
+ setColorOpen(true);
567
+ }, children: _jsx(KptIcon, { name: ICON[item] }) }, item));
568
+ }
569
+ if (item === 'table') {
570
+ return (_jsx("button", { ref: setTableAnchor, type: "button", className: cn('kpt-rich-text__tool', tableOpen && 'is-active'), "aria-label": t('richText.table'), title: t('richText.table'), disabled: disabled, onMouseDown: (event) => event.preventDefault(), onClick: () => exec(item), children: _jsx(KptIcon, { name: ICON[item] }) }, item));
571
+ }
572
+ return toolButton(item);
573
+ })] }, index))) }), sourceView ? (_jsx("textarea", { className: "kpt-rich-text__source", value: current, disabled: disabled, spellCheck: false, "aria-label": t('richText.htmlSource'), onChange: (event) => setCurrent(event.target.value), onBlur: () => onTouch?.() })) : (_jsx("div", { ref: editorRef, className: "kpt-rich-text__editor", contentEditable: !disabled, suppressContentEditableWarning: true, "data-placeholder": placeholder, "aria-invalid": showInvalid || undefined, role: "textbox", "aria-multiline": "true", onInput: pushValue, onKeyDown: onKeyDown, onPaste: onPaste, onContextMenu: onContextMenu, onMouseDown: onEditorMouseDown, onMouseMove: onEditorMouseMove, onBlur: () => onTouch?.() }))] }), _jsx(KptOverlay, { open: styleOpen, anchor: styleAnchor, onClose: () => setStyleOpen(false), offset: 4, children: _jsx("ul", { className: "kpt-rich-text__style-panel", role: "listbox", children: styleOptions.map((option) => (_jsx("li", { className: cn(`kpt-rich-text__style-opt kpt-rich-text__style-opt--${option.tag}`, currentBlock === option.tag && 'is-selected'), role: "option", "aria-selected": currentBlock === option.tag, onClick: () => {
574
+ setStyleOpen(false);
575
+ focusEditor();
576
+ restoreSelection();
577
+ legacyExec('formatBlock', `<${option.tag}>`);
578
+ pushValue();
579
+ refreshState();
580
+ }, children: option.label }, option.tag))) }) }), _jsx(KptOverlay, { open: colorOpen && colorKind === 'foreColor', anchor: foreAnchor, onClose: () => setColorOpen(false), offset: 4, children: palette(applyColor, t('richText.paletteCustom')) }), _jsx(KptOverlay, { open: colorOpen && colorKind === 'backColor', anchor: backAnchor, onClose: () => setColorOpen(false), offset: 4, children: palette(applyColor, t('richText.paletteCustom')) }), _jsx(KptOverlay, { open: tableOpen, anchor: tableAnchor, onClose: () => {
581
+ setTableOpen(false);
582
+ setGrid({ r: 0, c: 0 });
583
+ }, offset: 4, children: _jsxs("div", { className: "kpt-rt-grid-pop", onMouseDown: (event) => event.preventDefault(), children: [_jsx("div", { className: "kpt-rt-grid", onMouseLeave: () => setGrid({ r: 0, c: 0 }), children: GRID_ROWS.map((r) => (_jsx("div", { className: "kpt-rt-grid__row", children: GRID_COLS.map((c) => (_jsx("span", { className: cn('kpt-rt-grid__cell', r <= grid.r && c <= grid.c && 'is-on'), onMouseEnter: () => setGrid({ r, c }), onClick: () => insertTableAt(r, c) }, c))) }, r))) }), _jsxs("div", { className: "kpt-rt-grid__label", children: [grid.r, " \u00D7 ", grid.c] })] }) }), menuOpen
584
+ ? pointPanel(_jsxs("div", { className: "kpt-rt-menu", onContextMenu: (event) => event.preventDefault(), children: [_jsx("div", { className: "kpt-rt-menu__group", children: t('richText.menu.column') }), menuItem('plus', t('richText.menu.insertLeft'), () => {
585
+ if (ctxCell.current)
586
+ insertColumn(ctxCell.current, 'left');
587
+ afterTableOp();
588
+ }), menuItem('plus', t('richText.menu.insertRight'), () => {
589
+ if (ctxCell.current)
590
+ insertColumn(ctxCell.current, 'right');
591
+ afterTableOp();
592
+ }), menuItem('table', t('richText.menu.headerColumn'), () => {
593
+ if (ctxCell.current)
594
+ toggleHeaderColumn(ctxCell.current);
595
+ ctxCell.current = null; // komórki zostały podmienione
596
+ afterTableOp();
597
+ }), menuItem('trash', t('richText.menu.deleteColumn'), () => {
598
+ if (ctxCell.current)
599
+ deleteColumn(ctxCell.current);
600
+ afterTableOp();
601
+ }), _jsx("div", { className: "kpt-rt-menu__sep" }), _jsx("div", { className: "kpt-rt-menu__group", children: t('richText.menu.row') }), menuItem('plus', t('richText.menu.insertAbove'), () => {
602
+ if (ctxCell.current)
603
+ insertRow(ctxCell.current, 'above');
604
+ afterTableOp();
605
+ }), menuItem('plus', t('richText.menu.insertBelow'), () => {
606
+ if (ctxCell.current)
607
+ insertRow(ctxCell.current, 'below');
608
+ afterTableOp();
609
+ }), menuItem('table', t('richText.menu.headerRow'), () => {
610
+ if (ctxCell.current)
611
+ toggleHeaderRow(ctxCell.current);
612
+ ctxCell.current = null;
613
+ afterTableOp();
614
+ }), menuItem('trash', t('richText.menu.deleteRow'), () => {
615
+ if (ctxCell.current)
616
+ deleteRow(ctxCell.current);
617
+ afterTableOp();
618
+ }), _jsx("div", { className: "kpt-rt-menu__sep" }), _jsx("div", { className: "kpt-rt-menu__group", children: t('richText.menu.cell') }), menuItem('palette', t('richText.menu.cellBackground'), () => {
619
+ setMenuOpen(false);
620
+ setColorKind('cellBg');
621
+ setColorOpen(true);
622
+ }), menuItem('align-left', t('richText.menu.alignLeft'), () => {
623
+ for (const cell of activeCells())
624
+ cell.style.textAlign = 'left';
625
+ afterTableOp();
626
+ }), menuItem('align-center', t('richText.menu.alignCenter'), () => {
627
+ for (const cell of activeCells())
628
+ cell.style.textAlign = 'center';
629
+ afterTableOp();
630
+ }), menuItem('align-right', t('richText.menu.alignRight'), () => {
631
+ for (const cell of activeCells())
632
+ cell.style.textAlign = 'right';
633
+ afterTableOp();
634
+ }), _jsx("div", { className: "kpt-rt-menu__sep" }), menuItem('table', t('richText.menu.mergeCells'), () => {
635
+ mergeCells([...selCells.current]);
636
+ clearCellSelection();
637
+ afterTableOp();
638
+ }, selCount < 2 ? 'is-disabled' : undefined), menuItem('grip-vertical', t('richText.menu.splitCell'), () => {
639
+ if (ctxCell.current)
640
+ splitCell(ctxCell.current);
641
+ afterTableOp();
642
+ }, canSplitCtx.current ? undefined : 'is-disabled'), _jsx("div", { className: "kpt-rt-menu__sep" }), menuItem('table', t('richText.menu.border'), () => {
643
+ setMenuOpen(false);
644
+ setBorderOpen(true);
645
+ }), _jsx("div", { className: "kpt-rt-menu__sep" }), menuItem('trash', t('richText.menu.deleteTable'), () => {
646
+ ctxCell.current?.closest('table')?.remove();
647
+ afterTableOp();
648
+ }, 'kpt-rt-menu__item--danger')] }), () => setMenuOpen(false))
649
+ : null, borderOpen
650
+ ? pointPanel(_jsxs("div", { className: "kpt-rt-border", onMouseDown: (event) => event.preventDefault(), children: [_jsxs("div", { className: "kpt-rt-border__seg", children: [_jsx("button", { type: "button", className: cn(borderTarget === 'table' && 'is-active'), onClick: () => setBorderTarget('table'), children: t('richText.border.table') }), _jsx("button", { type: "button", className: cn(borderTarget === 'cell' && 'is-active'), onClick: () => setBorderTarget('cell'), children: t('richText.border.cell') })] }), _jsxs("div", { className: "kpt-rt-border__widths", children: [_jsx("button", { type: "button", onClick: () => setBorderWidth('none'), children: t('richText.border.none') }), _jsx("button", { type: "button", onClick: () => setBorderWidth('thin'), children: t('richText.border.thin') }), _jsx("button", { type: "button", onClick: () => setBorderWidth('medium'), children: t('richText.border.medium') }), _jsx("button", { type: "button", onClick: () => setBorderWidth('thick'), children: t('richText.border.thick') })] }), _jsx("div", { className: "kpt-rich-text__swatches", children: SWATCHES.map((color) => (_jsx("button", { type: "button", className: "kpt-rich-text__swatch", style: { background: color }, "aria-label": color, onClick: () => setBorderColor(color) }, color))) }), _jsxs("label", { className: "kpt-rich-text__picker", children: [_jsx("span", { children: t('richText.border.customColor') }), _jsx("input", { type: "color", onInput: (event) => setBorderColor(event.target.value) })] })] }), () => setBorderOpen(false))
651
+ : null, colorOpen && colorKind === 'cellBg'
652
+ ? pointPanel(palette(applyColor, t('richText.paletteCustom')), () => setColorOpen(false))
653
+ : null] }));
654
+ }
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Czyści HTML wchodzący do edytora. Dotyczy **każdego** zapisu do DOM edytora, nie tylko
3
+ * wklejania: `value` z aplikacji też bywa niezaufane (baza, CMS, inny użytkownik).
4
+ */
5
+ export declare function sanitizeHtml(html: string): string;
6
+ /**
7
+ * Waliduje URL z narzędzia „link". Brak schematu → dopisujemy `https://`; `#` i `/` przechodzą
8
+ * bez zmian; schemat spoza allowlisty → `null`, czyli link nie zostaje wstawiony.
9
+ */
10
+ export declare function sanitizeUrl(url: string): string | null;