@konce-pt/react 0.11.2 → 0.14.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.
- package/README.md +128 -64
- package/dist/accordion/accordion.d.ts +14 -6
- package/dist/accordion/accordion.js +25 -17
- package/dist/accordion/llms.txt +22 -10
- package/dist/alert/alert.d.ts +2 -2
- package/dist/alert/alert.js +3 -2
- package/dist/alert/llms.txt +10 -9
- package/dist/app-shell/app-shell.d.ts +14 -14
- package/dist/app-shell/app-shell.js +22 -15
- package/dist/app-shell/llms.txt +16 -15
- package/dist/auth/auth-types.d.ts +6 -6
- package/dist/auth/auth.d.ts +2 -2
- package/dist/auth/auth.js +24 -15
- package/dist/auth/llms.txt +12 -11
- package/dist/autocomplete/autocomplete.d.ts +19 -6
- package/dist/autocomplete/autocomplete.js +32 -11
- package/dist/autocomplete/llms.txt +19 -5
- package/dist/avatar/avatar.d.ts +6 -6
- package/dist/avatar/avatar.js +6 -5
- package/dist/avatar/llms.txt +10 -10
- package/dist/avatar-group/avatar-group.d.ts +4 -4
- package/dist/avatar-group/avatar-group.js +4 -3
- package/dist/avatar-group/llms.txt +8 -8
- package/dist/backdrop/backdrop.d.ts +7 -26
- package/dist/backdrop/backdrop.js +32 -27
- package/dist/backdrop/llms.txt +27 -27
- package/dist/badge/badge.d.ts +3 -3
- package/dist/badge/badge.js +1 -1
- package/dist/badge/llms.txt +11 -11
- package/dist/bottom-sheet/bottom-sheet.d.ts +8 -3
- package/dist/bottom-sheet/bottom-sheet.js +5 -4
- package/dist/bottom-sheet/llms.txt +11 -8
- package/dist/breadcrumb/breadcrumb.d.ts +8 -2
- package/dist/breadcrumb/breadcrumb.js +11 -3
- package/dist/breadcrumb/llms.txt +10 -2
- package/dist/button/button.d.ts +3 -3
- package/dist/button/button.js +10 -9
- package/dist/button/llms.txt +27 -19
- package/dist/button-group/button-group.d.ts +18 -5
- package/dist/button-group/button-group.js +54 -6
- package/dist/button-group/llms.txt +27 -18
- package/dist/card/card.d.ts +8 -8
- package/dist/card/card.js +10 -9
- package/dist/card/llms.txt +20 -20
- package/dist/carousel/carousel.d.ts +6 -6
- package/dist/carousel/carousel.js +8 -5
- package/dist/carousel/llms.txt +10 -10
- package/dist/charts/chart-legend.d.ts +3 -3
- package/dist/charts/chart-legend.js +2 -1
- package/dist/charts/chart-tooltip.d.ts +3 -3
- package/dist/charts/chart-tooltip.js +1 -1
- package/dist/charts/chart.d.ts +4 -3
- package/dist/charts/chart.js +18 -10
- package/dist/charts/llms.txt +10 -7
- package/dist/charts-entry/public-api.js +2 -2
- package/dist/chat/chat.d.ts +17 -10
- package/dist/chat/chat.js +77 -23
- package/dist/chat/llms.txt +39 -15
- package/dist/chat-composer/chat-composer.d.ts +20 -7
- package/dist/chat-composer/chat-composer.js +29 -7
- package/dist/chat-composer/llms.txt +20 -11
- package/dist/chat-dock/chat-dock.d.ts +11 -4
- package/dist/chat-dock/chat-dock.js +10 -6
- package/dist/chat-dock/llms.txt +15 -14
- package/dist/chat-entry/public-api.js +1 -1
- package/dist/chat-message/chat-message.d.ts +12 -5
- package/dist/chat-message/chat-message.js +19 -8
- package/dist/chat-message/llms.txt +16 -15
- package/dist/chat-typing/chat-typing.d.ts +2 -2
- package/dist/chat-typing/chat-typing.js +4 -3
- package/dist/chat-typing/llms.txt +6 -6
- package/dist/checkbox/checkbox.d.ts +13 -6
- package/dist/checkbox/checkbox.js +31 -5
- package/dist/checkbox/llms.txt +26 -11
- package/dist/chip/chip.d.ts +4 -4
- package/dist/chip/chip.js +3 -2
- package/dist/chip/llms.txt +11 -11
- package/dist/chips-input/chips-input.d.ts +6 -4
- package/dist/chips-input/chips-input.js +47 -12
- package/dist/chips-input/llms.txt +18 -8
- package/dist/clock/clock.d.ts +11 -6
- package/dist/clock/clock.js +38 -8
- package/dist/clock/llms.txt +12 -8
- package/dist/color-picker/color-picker.d.ts +9 -5
- package/dist/color-picker/color-picker.js +37 -18
- package/dist/color-picker/llms.txt +8 -5
- package/dist/confirm/confirm-container.d.ts +3 -3
- package/dist/confirm/confirm-container.js +6 -5
- package/dist/confirm/confirm-store.d.ts +4 -4
- package/dist/confirm/confirm-store.js +11 -5
- package/dist/confirm/llms.txt +8 -7
- package/dist/context-menu/context-menu.d.ts +1 -1
- package/dist/context-menu/context-menu.js +37 -14
- package/dist/context-menu/llms.txt +10 -7
- package/dist/data-table/data-table-types.d.ts +5 -5
- package/dist/data-table/data-table.d.ts +37 -5
- package/dist/data-table/data-table.js +426 -119
- package/dist/data-table/export-csv.d.ts +3 -3
- package/dist/data-table/export-csv.js +8 -10
- package/dist/data-table/llms.txt +127 -79
- package/dist/data-view/data-view.d.ts +13 -5
- package/dist/data-view/data-view.js +68 -6
- package/dist/data-view/llms.txt +29 -10
- package/dist/date-range/date-range.d.ts +20 -8
- package/dist/date-range/date-range.js +86 -63
- package/dist/date-range/llms.txt +32 -18
- package/dist/datepicker/datepicker.d.ts +22 -8
- package/dist/datepicker/datepicker.js +103 -71
- package/dist/datepicker/llms.txt +36 -22
- package/dist/dialog/dialog.d.ts +4 -4
- package/dist/dialog/dialog.js +32 -11
- package/dist/dialog/llms.txt +13 -10
- package/dist/divider/divider.d.ts +7 -1
- package/dist/divider/divider.js +3 -2
- package/dist/divider/llms.txt +4 -3
- package/dist/drawer/drawer.d.ts +9 -4
- package/dist/drawer/drawer.js +5 -4
- package/dist/drawer/llms.txt +13 -10
- package/dist/empty/empty.d.ts +2 -2
- package/dist/empty/empty.js +1 -1
- package/dist/empty/llms.txt +9 -9
- package/dist/fab/fab.d.ts +1 -1
- package/dist/fab/fab.js +1 -1
- package/dist/fab/llms.txt +10 -6
- package/dist/fieldset/fieldset.d.ts +18 -4
- package/dist/fieldset/fieldset.js +12 -6
- package/dist/fieldset/llms.txt +26 -9
- package/dist/file-upload/file-upload.d.ts +8 -2
- package/dist/file-upload/file-upload.js +22 -11
- package/dist/file-upload/llms.txt +12 -9
- package/dist/form-field/form-field-context.d.ts +4 -4
- package/dist/form-field/form-field.d.ts +12 -4
- package/dist/form-field/form-field.js +27 -6
- package/dist/form-field/llms.txt +25 -11
- package/dist/galleria/galleria.d.ts +3 -3
- package/dist/galleria/galleria.js +9 -6
- package/dist/galleria/llms.txt +7 -7
- package/dist/grid/col.d.ts +4 -4
- package/dist/grid/col.js +2 -2
- package/dist/grid/flex.d.ts +3 -3
- package/dist/grid/flex.js +1 -1
- package/dist/grid/grid.d.ts +7 -7
- package/dist/grid/grid.js +3 -3
- package/dist/grid/layout.d.ts +2 -2
- package/dist/grid/layout.js +2 -2
- package/dist/grid/llms.txt +15 -15
- package/dist/grid-entry/public-api.js +1 -1
- package/dist/i18n/i18n-context.d.ts +6 -6
- package/dist/i18n/i18n-context.js +27 -5
- package/dist/icon/icon-registry.d.ts +3 -3
- package/dist/icon/icon-registry.js +3 -3
- package/dist/icon/icon.d.ts +4 -4
- package/dist/icon/icon.js +10 -7
- package/dist/icon/llms.txt +13 -13
- package/dist/icon-button/icon-button.d.ts +2 -2
- package/dist/icon-button/icon-button.js +1 -1
- package/dist/icon-button/llms.txt +9 -5
- package/dist/icons-entry/public-api.d.ts +2 -2
- package/dist/icons-entry/public-api.js +3 -3
- package/dist/image/image.js +5 -5
- package/dist/image/llms.txt +13 -13
- package/dist/input/input.d.ts +18 -5
- package/dist/input/input.js +22 -6
- package/dist/input/llms.txt +25 -11
- package/dist/input-mask/input-mask.d.ts +7 -2
- package/dist/input-mask/input-mask.js +17 -43
- package/dist/input-mask/llms.txt +8 -5
- package/dist/input-number/input-number.d.ts +11 -9
- package/dist/input-number/input-number.js +25 -21
- package/dist/input-number/llms.txt +17 -14
- package/dist/input-otp/input-otp.d.ts +18 -5
- package/dist/input-otp/input-otp.js +48 -24
- package/dist/input-otp/llms.txt +18 -7
- package/dist/internal/calendar-focus.d.ts +1 -1
- package/dist/internal/calendar-focus.js +3 -3
- package/dist/internal/cn.d.ts +1 -1
- package/dist/internal/mask.d.ts +14 -0
- package/dist/internal/mask.js +62 -0
- package/dist/internal/number-group.d.ts +22 -0
- package/dist/internal/number-group.js +53 -0
- package/dist/internal/option-key.d.ts +6 -0
- package/dist/internal/option-key.js +8 -0
- package/dist/internal/option-nav.d.ts +4 -4
- package/dist/internal/option-nav.js +5 -5
- package/dist/internal/overlay-container.js +1 -1
- package/dist/internal/overlay.d.ts +3 -3
- package/dist/internal/overlay.js +40 -10
- package/dist/internal/password-strength.d.ts +6 -0
- package/dist/internal/password-strength.js +74 -0
- package/dist/internal/roving.d.ts +2 -2
- package/dist/internal/roving.js +3 -3
- package/dist/internal/scroll-lock.d.ts +6 -0
- package/dist/internal/scroll-lock.js +35 -0
- package/dist/internal/slot-children.d.ts +13 -0
- package/dist/internal/slot-children.js +31 -0
- package/dist/internal/slot.d.ts +3 -3
- package/dist/internal/slot.js +2 -2
- package/dist/internal/step.d.ts +12 -0
- package/dist/internal/step.js +31 -0
- package/dist/internal/tween.d.ts +5 -5
- package/dist/internal/tween.js +7 -8
- package/dist/internal/types.d.ts +2 -2
- package/dist/internal/use-breakpoint.d.ts +2 -2
- package/dist/internal/use-breakpoint.js +4 -4
- package/dist/internal/use-controllable-state.d.ts +5 -0
- package/dist/internal/use-controllable-state.js +25 -4
- package/dist/internal/use-id.d.ts +2 -2
- package/dist/internal/use-id.js +2 -2
- package/dist/internal/use-modal-focus.d.ts +2 -1
- package/dist/internal/use-modal-focus.js +14 -3
- package/dist/knob/knob.d.ts +7 -4
- package/dist/knob/knob.js +27 -22
- package/dist/knob/llms.txt +18 -11
- package/dist/listbox/listbox.d.ts +8 -6
- package/dist/listbox/listbox.js +37 -17
- package/dist/listbox/llms.txt +12 -9
- package/dist/loader/llms.txt +56 -0
- package/dist/loader/loader.d.ts +49 -0
- package/dist/loader/loader.js +68 -0
- package/dist/map/llms.txt +30 -23
- package/dist/map/map-create-dialog.d.ts +4 -4
- package/dist/map/map-create-dialog.js +36 -28
- package/dist/map/map-hours-field.d.ts +4 -4
- package/dist/map/map-hours-field.js +8 -7
- package/dist/map/map-image-field.d.ts +2 -2
- package/dist/map/map-image-field.js +6 -5
- package/dist/map/map-legend.d.ts +4 -4
- package/dist/map/map-legend.js +3 -2
- package/dist/map/map-poi-card.d.ts +3 -3
- package/dist/map/map-poi-card.js +10 -9
- package/dist/map/map-search.d.ts +17 -6
- package/dist/map/map-search.js +70 -43
- package/dist/map/map.d.ts +25 -13
- package/dist/map/map.js +86 -57
- package/dist/map/ports.d.ts +9 -9
- package/dist/map/ports.js +6 -6
- package/dist/map-entry/public-api.js +2 -2
- package/dist/megamenu/llms.txt +18 -7
- package/dist/megamenu/megamenu.d.ts +12 -6
- package/dist/megamenu/megamenu.js +23 -14
- package/dist/mention/llms.txt +15 -15
- package/dist/mention/mention.d.ts +5 -5
- package/dist/mention/mention.js +2 -2
- package/dist/mention/mentions.d.ts +1 -1
- package/dist/mention/mentions.js +1 -1
- package/dist/menu/llms.txt +12 -12
- package/dist/menu/menu-item.d.ts +4 -4
- package/dist/menu/menu-keys.d.ts +2 -2
- package/dist/menu/menu-keys.js +5 -5
- package/dist/menu/menu-rows.js +1 -0
- package/dist/menu/menu.d.ts +2 -2
- package/dist/menu/menu.js +15 -8
- package/dist/menubar/llms.txt +19 -8
- package/dist/menubar/menubar.d.ts +11 -5
- package/dist/menubar/menubar.js +23 -11
- package/dist/meter-group/llms.txt +12 -12
- package/dist/meter-group/meter-group.d.ts +2 -2
- package/dist/meter-group/meter-group.js +2 -1
- package/dist/motion/motion-context.d.ts +22 -0
- package/dist/motion/motion-context.js +28 -0
- package/dist/order-list/llms.txt +24 -9
- package/dist/order-list/order-list.d.ts +12 -4
- package/dist/order-list/order-list.js +123 -25
- package/dist/paginator/llms.txt +8 -8
- package/dist/paginator/paginator.js +7 -4
- package/dist/panel/llms.txt +25 -13
- package/dist/panel/panel.d.ts +11 -3
- package/dist/panel/panel.js +12 -7
- package/dist/password/llms.txt +19 -7
- package/dist/password/password.d.ts +2 -2
- package/dist/password/password.js +27 -16
- package/dist/peek/llms.txt +11 -11
- package/dist/peek/peek.d.ts +6 -6
- package/dist/peek/peek.js +41 -19
- package/dist/pick-list/llms.txt +35 -12
- package/dist/pick-list/pick-list.d.ts +12 -4
- package/dist/pick-list/pick-list.js +128 -24
- package/dist/popover/llms.txt +13 -10
- package/dist/popover/popover.d.ts +6 -4
- package/dist/popover/popover.js +14 -7
- package/dist/progress/llms.txt +17 -17
- package/dist/progress/progress.d.ts +7 -7
- package/dist/progress/progress.js +7 -6
- package/dist/public-api.d.ts +16 -4
- package/dist/public-api.js +13 -7
- package/dist/radio-group/llms.txt +16 -6
- package/dist/radio-group/radio-group.d.ts +12 -3
- package/dist/radio-group/radio-group.js +23 -4
- package/dist/rating/llms.txt +20 -13
- package/dist/rating/rating-pointer.d.ts +16 -0
- package/dist/rating/rating-pointer.js +26 -0
- package/dist/rating/rating.d.ts +6 -6
- package/dist/rating/rating.js +77 -12
- package/dist/rich-text/lists.d.ts +2 -2
- package/dist/rich-text/lists.js +7 -7
- package/dist/rich-text/llms.txt +35 -12
- package/dist/rich-text/rich-text.d.ts +13 -14
- package/dist/rich-text/rich-text.js +311 -142
- package/dist/rich-text/sanitize.d.ts +11 -2
- package/dist/rich-text/sanitize.js +80 -32
- package/dist/rich-text/tables.d.ts +3 -3
- package/dist/rich-text/tables.js +3 -3
- package/dist/roadmap/llms.txt +23 -17
- package/dist/roadmap/roadmap.d.ts +6 -6
- package/dist/roadmap/roadmap.js +102 -25
- package/dist/roadmap-entry/public-api.js +2 -2
- package/dist/scroll-top/llms.txt +6 -6
- package/dist/scroll-top/scroll-top.d.ts +1 -1
- package/dist/scroll-top/scroll-top.js +2 -1
- package/dist/select/llms.txt +22 -12
- package/dist/select/select.d.ts +10 -8
- package/dist/select/select.js +87 -47
- package/dist/shared/types.d.ts +22 -1
- package/dist/shared/types.js +9 -1
- package/dist/sidenav/llms.txt +9 -9
- package/dist/sidenav/sidenav.d.ts +2 -3
- package/dist/sidenav/sidenav.js +5 -4
- package/dist/skeleton/llms.txt +3 -3
- package/dist/skeleton/skeleton.d.ts +1 -1
- package/dist/skeleton/skeleton.js +1 -1
- package/dist/slider/llms.txt +34 -12
- package/dist/slider/slider-drag.d.ts +26 -0
- package/dist/slider/slider-drag.js +79 -0
- package/dist/slider/slider.d.ts +8 -5
- package/dist/slider/slider.js +38 -8
- package/dist/speech/llms.txt +97 -0
- package/dist/speech/speech-button.d.ts +44 -0
- package/dist/speech/speech-button.js +86 -0
- package/dist/speech/speech-wave.d.ts +19 -0
- package/dist/speech/speech-wave.js +16 -0
- package/dist/speech/use-speech-recognition.d.ts +81 -0
- package/dist/speech/use-speech-recognition.js +179 -0
- package/dist/speed-dial/llms.txt +14 -8
- package/dist/speed-dial/speed-dial.d.ts +1 -1
- package/dist/speed-dial/speed-dial.js +3 -2
- package/dist/spinner/llms.txt +7 -7
- package/dist/spinner/spinner.d.ts +1 -1
- package/dist/spinner/spinner.js +2 -1
- package/dist/split-button/llms.txt +21 -15
- package/dist/split-button/split-button.d.ts +2 -2
- package/dist/split-button/split-button.js +13 -6
- package/dist/splitter/llms.txt +11 -11
- package/dist/splitter/splitter.d.ts +3 -3
- package/dist/splitter/splitter.js +25 -10
- package/dist/stepper/llms.txt +35 -13
- package/dist/stepper/stepper.d.ts +6 -6
- package/dist/stepper/stepper.js +60 -13
- package/dist/switch/llms.txt +15 -5
- package/dist/switch/switch.d.ts +10 -3
- package/dist/switch/switch.js +18 -4
- package/dist/switch-group/llms.txt +11 -8
- package/dist/switch-group/switch-group.d.ts +6 -4
- package/dist/switch-group/switch-group.js +9 -7
- package/dist/tabs/llms.txt +12 -9
- package/dist/tabs/tabs.d.ts +5 -5
- package/dist/tabs/tabs.js +36 -20
- package/dist/textarea/llms.txt +23 -12
- package/dist/textarea/textarea.d.ts +19 -6
- package/dist/textarea/textarea.js +22 -7
- package/dist/timeline/llms.txt +4 -4
- package/dist/timeline/timeline.d.ts +1 -1
- package/dist/timeline/timeline.js +1 -1
- package/dist/toast/llms.txt +50 -17
- package/dist/toast/toast-container.d.ts +9 -3
- package/dist/toast/toast-container.js +18 -9
- package/dist/toast/toast-store.d.ts +42 -2
- package/dist/toast/toast-store.js +36 -1
- package/dist/toolbar/llms.txt +7 -7
- package/dist/toolbar/toolbar.d.ts +3 -3
- package/dist/toolbar/toolbar.js +2 -2
- package/dist/tooltip/llms.txt +14 -12
- package/dist/tooltip/tooltip.d.ts +5 -4
- package/dist/tooltip/tooltip.js +100 -41
- package/dist/tree/llms.txt +25 -11
- package/dist/tree/tree-keys.d.ts +5 -5
- package/dist/tree/tree-keys.js +7 -7
- package/dist/tree/tree.d.ts +17 -5
- package/dist/tree/tree.js +62 -20
- package/dist/update/llms.txt +54 -0
- package/dist/update/update.d.ts +49 -0
- package/dist/update/update.js +82 -0
- package/package.json +18 -15
|
@@ -1,5 +1,6 @@
|
|
|
1
|
+
'use client';
|
|
1
2
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
-
import { useCallback, useEffect, useRef, useState, } from 'react';
|
|
3
|
+
import { useCallback, useEffect, useEffectEvent, useRef, useState, } from 'react';
|
|
3
4
|
import { createPortal } from 'react-dom';
|
|
4
5
|
import { cn } from "../internal/cn.js";
|
|
5
6
|
import { useControllableState } from "../internal/use-controllable-state.js";
|
|
@@ -8,17 +9,20 @@ import { useKptOverlayContainer } from "../internal/overlay-container.js";
|
|
|
8
9
|
import { KptOverlay } from "../internal/overlay.js";
|
|
9
10
|
import { useKptMessages } from "../i18n/i18n-context.js";
|
|
10
11
|
import { KptIcon } from "../icon/icon.js";
|
|
11
|
-
import {
|
|
12
|
+
import { KptSpeechButton } from "../speech/speech-button.js";
|
|
13
|
+
import { useKptSpeechRecognition } from "../speech/use-speech-recognition.js";
|
|
14
|
+
import { sanitizeHtml, sanitizeHtmlReport, sanitizeUrl } from "./sanitize.js";
|
|
12
15
|
import { canIndentItem, canOutdentItem, selectedItems, shiftItems } from "./lists.js";
|
|
13
16
|
import { CELL_SELECTED, deleteColumn, deleteRow, insertColumn, insertRow, mergeCells, regionBetween, splitCell, tableHtml, toggleHeaderColumn, toggleHeaderRow, } from "./tables.js";
|
|
14
17
|
import { useKptFieldA11y } from "../form-field/form-field-context.js";
|
|
15
18
|
import { nextRovingIndex } from "../internal/roving.js";
|
|
19
|
+
import { trapTab } from "../internal/calendar-focus.js";
|
|
16
20
|
import { focusMenuEdge, handleMenuKeydown } from "../menu/menu-keys.js";
|
|
17
21
|
const ICON = {
|
|
18
22
|
copy: 'copy',
|
|
19
23
|
paste: 'clipboard',
|
|
20
24
|
pasteText: 'clipboard-text',
|
|
21
|
-
heading: 'code', // nieużywane
|
|
25
|
+
heading: 'code', // nieużywane - "heading" renderuje select
|
|
22
26
|
bold: 'bold',
|
|
23
27
|
italic: 'italic',
|
|
24
28
|
underline: 'underline',
|
|
@@ -37,8 +41,9 @@ const ICON = {
|
|
|
37
41
|
table: 'table',
|
|
38
42
|
clear: 'clear-formatting',
|
|
39
43
|
html: 'code',
|
|
44
|
+
dictate: 'microphone', // nieużywane - "dictate" renderuje KptSpeechButton
|
|
40
45
|
};
|
|
41
|
-
/** Narzędzia zwykłe
|
|
46
|
+
/** Narzędzia zwykłe -> komenda `execCommand` (zarazem nazwa dla `queryCommandState`). */
|
|
42
47
|
const COMMAND = {
|
|
43
48
|
bold: 'bold',
|
|
44
49
|
italic: 'italic',
|
|
@@ -52,7 +57,7 @@ const COMMAND = {
|
|
|
52
57
|
alignJustify: 'justifyFull',
|
|
53
58
|
clear: 'removeFormat',
|
|
54
59
|
};
|
|
55
|
-
/** Paleta kolorów z nazwami (`richText.swatches.*`)
|
|
60
|
+
/** Paleta kolorów z nazwami (`richText.swatches.*`) - sam kod szesnastkowy nic czytnikowi nie mówi. */
|
|
56
61
|
const SWATCHES = [
|
|
57
62
|
{ color: '#000000', name: 'black' },
|
|
58
63
|
{ color: '#434343', name: 'darkGray' },
|
|
@@ -84,26 +89,22 @@ const GRID_COLS = Array.from({ length: 8 }, (_, i) => i + 1);
|
|
|
84
89
|
* (Ctrl+Z). Własne komendy wymagałyby własnej historii, czyli przepisania edytora.
|
|
85
90
|
*/
|
|
86
91
|
function legacyExec(cmd, value) {
|
|
92
|
+
// eslint-disable-next-line @typescript-eslint/no-deprecated -- brak zamiennika, patrz JSDoc
|
|
87
93
|
return document.execCommand(cmd, false, value); // NOSONAR: brak zamiennika, patrz JSDoc
|
|
88
94
|
}
|
|
89
95
|
function legacyState(cmd) {
|
|
90
96
|
try {
|
|
97
|
+
// eslint-disable-next-line @typescript-eslint/no-deprecated -- brak zamiennika, patrz `legacyExec`
|
|
91
98
|
return document.queryCommandState(cmd); // NOSONAR: brak zamiennika
|
|
92
99
|
}
|
|
93
100
|
catch {
|
|
94
101
|
return false;
|
|
95
102
|
}
|
|
96
103
|
}
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
* @example
|
|
102
|
-
* <KptRichText value={html} onValueChange={setHtml} />
|
|
103
|
-
* <KptRichText value={html} onValueChange={setHtml} toolbar={[['bold', 'italic'], ['link']]} />
|
|
104
|
-
*/
|
|
105
|
-
export function KptRichText({ value, defaultValue = '', onValueChange, toolbar = DEFAULT_TOOLBAR, placeholder = '', maxListLevel = 6, id, ariaLabel = null, required = false, disabled = false, invalid = false, touched, onTouch, mentions = [], mentionLimit = KPT_MENTION_LIMIT, onMention, locale, dictionary, className, ...rest }) {
|
|
106
|
-
/** `contenteditable` nie dostaje nazwy z `label[for]` — stąd `aria-labelledby` na etykietę wrappera. */
|
|
104
|
+
export function KptRichText({ value, defaultValue = '', onValueChange, toolbar = DEFAULT_TOOLBAR, placeholder = '', maxListLevel = 6, id, ariaLabel = null, required = false, disabled: disabledProp = false, readOnly = false, invalid = false, touched, onTouch, mentions = [], mentionLimit = KPT_MENTION_LIMIT, onMention, speechLang, speechColor, locale, dictionary, className, ...rest }) {
|
|
105
|
+
/** Edycja zablokowana: wyłączone pole albo tylko do odczytu - narzędzia i skróty nie działają. */
|
|
106
|
+
const disabled = disabledProp || readOnly;
|
|
107
|
+
/** `contenteditable` nie dostaje nazwy z `label[for]` - stąd `aria-labelledby` na etykietę wrappera. */
|
|
107
108
|
const field = useKptFieldA11y(id, 'kpt-rich-text');
|
|
108
109
|
const { t } = useKptMessages({ locale, dictionary });
|
|
109
110
|
const container = useKptOverlayContainer();
|
|
@@ -111,7 +112,7 @@ export function KptRichText({ value, defaultValue = '', onValueChange, toolbar =
|
|
|
111
112
|
const savedRange = useRef(null);
|
|
112
113
|
const [current, setCurrent] = useControllableState({ value, defaultValue, onChange: onValueChange });
|
|
113
114
|
const showInvalid = invalid && (touched ?? true);
|
|
114
|
-
// Kotwice narzędzi z panelem
|
|
115
|
+
// Kotwice narzędzi z panelem - `KptOverlay` przypina panel do konkretnego przycisku.
|
|
115
116
|
const [styleAnchor, setStyleAnchor] = useState(null);
|
|
116
117
|
const [foreAnchor, setForeAnchor] = useState(null);
|
|
117
118
|
const [backAnchor, setBackAnchor] = useState(null);
|
|
@@ -120,25 +121,27 @@ export function KptRichText({ value, defaultValue = '', onValueChange, toolbar =
|
|
|
120
121
|
/** Okienko adresu linku (zamiast `window.prompt()`) i wpisywany adres. */
|
|
121
122
|
const [linkOpen, setLinkOpen] = useState(false);
|
|
122
123
|
const [linkDraft, setLinkDraft] = useState('');
|
|
123
|
-
/** Menu tabeli otwarte z klawiatury
|
|
124
|
+
/** Menu tabeli otwarte z klawiatury - po zamontowaniu fokus idzie na pierwszą pozycję. */
|
|
124
125
|
const menuFromKeyboard = useRef(false);
|
|
125
126
|
const [styleOpen, setStyleOpen] = useState(false);
|
|
126
|
-
/** Narzędzie, na którym ostatnio stał fokus paska
|
|
127
|
+
/** Narzędzie, na którym ostatnio stał fokus paska - przystanek Tab przy powrocie. */
|
|
127
128
|
const [lastTool, setLastTool] = useState(null);
|
|
128
129
|
const [colorOpen, setColorOpen] = useState(false);
|
|
129
130
|
const [colorKind, setColorKind] = useState('foreColor');
|
|
130
131
|
const [tableOpen, setTableOpen] = useState(false);
|
|
132
|
+
/** Aktywna opcja listy stylów (aria-activedescendant). */
|
|
133
|
+
const [styleActive, setStyleActive] = useState(0);
|
|
131
134
|
const [grid, setGrid] = useState({ r: 0, c: 0 });
|
|
132
135
|
const [sourceView, setSourceView] = useState(false);
|
|
133
136
|
const [menuOpen, setMenuOpen] = useState(false);
|
|
134
137
|
const [menuPoint, setMenuPoint] = useState({ x: 0, y: 0 });
|
|
135
|
-
//
|
|
138
|
+
// -- Wzmianki (`@`) --
|
|
136
139
|
const [mentionOpen, setMentionOpen] = useState(false);
|
|
137
140
|
const [mentionMatches, setMentionMatches] = useState([]);
|
|
138
141
|
const [mentionActive, setMentionActive] = useState(0);
|
|
139
|
-
/** Zaczepienie listy w miejscu kursora
|
|
142
|
+
/** Zaczepienie listy w miejscu kursora - punkt, nie element, bo `@` nie ma własnego elementu. */
|
|
140
143
|
const [mentionPoint, setMentionPoint] = useState({ x: 0, y: 0 });
|
|
141
|
-
/** Węzeł tekstowy i zakres zapytania
|
|
144
|
+
/** Węzeł tekstowy i zakres zapytania `@...`, który podmieni wybrana pozycja. */
|
|
142
145
|
const mentionRange = useRef(null);
|
|
143
146
|
const [borderOpen, setBorderOpen] = useState(false);
|
|
144
147
|
const [borderTarget, setBorderTarget] = useState('table');
|
|
@@ -148,7 +151,7 @@ export function KptRichText({ value, defaultValue = '', onValueChange, toolbar =
|
|
|
148
151
|
const [currentBlock, setCurrentBlock] = useState('p');
|
|
149
152
|
const [selCount, setSelCount] = useState(0);
|
|
150
153
|
const ctxCell = useRef(null);
|
|
151
|
-
const canSplitCtx =
|
|
154
|
+
const [canSplitCtx, setCanSplitCtx] = useState(false);
|
|
152
155
|
const selCells = useRef(new Set());
|
|
153
156
|
const selAnchor = useRef(null);
|
|
154
157
|
const selecting = useRef(false);
|
|
@@ -158,6 +161,18 @@ export function KptRichText({ value, defaultValue = '', onValueChange, toolbar =
|
|
|
158
161
|
: Array.isArray(toolbar[0])
|
|
159
162
|
? toolbar
|
|
160
163
|
: [toolbar];
|
|
164
|
+
// -- Dyktowanie (narzędzie `dictate`) --
|
|
165
|
+
// Cel to sam obszar edycji: tekst zatwierdzony wchodzi przez `insertText` w miejsce kursora
|
|
166
|
+
// (natywne undo, zdarzenie `input` -> wartość), tymczasowy jest nakładką przy kursorze i nie trafia
|
|
167
|
+
// do HTML-a. Bez narzędzia w pasku kontroler nie tworzy sesji.
|
|
168
|
+
const hasDictate = sections.some((section) => section.includes('dictate'));
|
|
169
|
+
const mic = useKptSpeechRecognition({ enabled: hasDictate, target: editorRef, lang: speechLang, disabled });
|
|
170
|
+
// Widok źródła zdejmuje obszar edycji, a blokada zabrania pisania - dyktowanie przerywamy.
|
|
171
|
+
const stopDictation = useEffectEvent(() => mic.abort());
|
|
172
|
+
useEffect(() => {
|
|
173
|
+
if (sourceView || disabled)
|
|
174
|
+
stopDictation();
|
|
175
|
+
}, [sourceView, disabled]);
|
|
161
176
|
const pushValue = useCallback(() => {
|
|
162
177
|
const el = editorRef.current;
|
|
163
178
|
if (el)
|
|
@@ -165,21 +180,27 @@ export function KptRichText({ value, defaultValue = '', onValueChange, toolbar =
|
|
|
165
180
|
}, [setCurrent]);
|
|
166
181
|
/*
|
|
167
182
|
* Zapis z zewnątrz wstawiamy ręcznie, bo `contenteditable` nie może być renderowany przez
|
|
168
|
-
* Reacta
|
|
183
|
+
* Reacta - przerysowanie kasowałoby zaznaczenie i pozycję kursora przy każdym znaku.
|
|
169
184
|
* Porównanie z surowym `innerHTML` sprawia, że pisanie w edytorze nie wchodzi w tę gałąź,
|
|
170
|
-
* więc sanityzacja włącza się wyłącznie przy zapisie z aplikacji albo z trybu HTML.
|
|
185
|
+
* więc sanityzacja włącza się wyłącznie przy zapisie z aplikacji albo z trybu HTML. Gdy
|
|
186
|
+
* sanityzator coś usunął, oczyszczona treść wraca też do wartości - inaczej aplikacja dostawałaby
|
|
187
|
+
* z powrotem niebezpieczny HTML. Sama różnica zapisu (cudzysłowy) wartości nie rusza.
|
|
171
188
|
*/
|
|
172
189
|
useEffect(() => {
|
|
173
190
|
const el = editorRef.current;
|
|
174
|
-
if (el
|
|
175
|
-
|
|
176
|
-
|
|
191
|
+
if (!el || el.innerHTML === current)
|
|
192
|
+
return;
|
|
193
|
+
const clean = sanitizeHtmlReport(current);
|
|
194
|
+
el.innerHTML = clean.html;
|
|
195
|
+
if (clean.changed)
|
|
196
|
+
setCurrent(clean.html);
|
|
197
|
+
}, [current, setCurrent, sourceView]);
|
|
177
198
|
const inEditor = useCallback(() => {
|
|
178
199
|
const el = editorRef.current;
|
|
179
200
|
const selection = window.getSelection();
|
|
180
201
|
return !!el && !!selection && selection.rangeCount > 0 && el.contains(selection.anchorNode);
|
|
181
202
|
}, []);
|
|
182
|
-
/** Tag bloku, w którym stoi kursor
|
|
203
|
+
/** Tag bloku, w którym stoi kursor - dla selecta stylu; domyślnie paragraf. */
|
|
183
204
|
const blockTag = useCallback(() => {
|
|
184
205
|
let node = window.getSelection()?.anchorNode;
|
|
185
206
|
while (node && node !== editorRef.current) {
|
|
@@ -210,19 +231,19 @@ export function KptRichText({ value, defaultValue = '', onValueChange, toolbar =
|
|
|
210
231
|
selCells.current = new Set();
|
|
211
232
|
setSelCount(0);
|
|
212
233
|
}, []);
|
|
213
|
-
/** Zamknięcie podpowiedzi
|
|
234
|
+
/** Zamknięcie podpowiedzi - czyści też zapamiętany zakres zapytania. */
|
|
214
235
|
const closeMention = useCallback(() => {
|
|
215
236
|
mentionRange.current = null;
|
|
216
237
|
setMentionMatches([]);
|
|
217
238
|
setMentionOpen(false);
|
|
218
239
|
}, []);
|
|
219
|
-
/** Punkt pod kursorem
|
|
240
|
+
/** Punkt pod kursorem - lista podpowiedzi zaczepia się o niego, bo `@` nie ma swojego elementu. */
|
|
220
241
|
const caretPoint = useCallback((node, offset) => {
|
|
221
242
|
const range = document.createRange();
|
|
222
243
|
range.setStart(node, Math.min(offset, node.length));
|
|
223
244
|
range.collapse(true);
|
|
224
245
|
const rect = range.getBoundingClientRect();
|
|
225
|
-
// Zwinięty zakres na początku wiersza potrafi dać zerowy prostokąt
|
|
246
|
+
// Zwinięty zakres na początku wiersza potrafi dać zerowy prostokąt - wtedy bierzemy
|
|
226
247
|
// krawędź samego edytora, żeby lista nie wylądowała w rogu ekranu.
|
|
227
248
|
if (rect.width === 0 && rect.height === 0) {
|
|
228
249
|
const fallback = editorRef.current?.getBoundingClientRect();
|
|
@@ -231,11 +252,11 @@ export function KptRichText({ value, defaultValue = '', onValueChange, toolbar =
|
|
|
231
252
|
return { x: rect.left, y: rect.bottom };
|
|
232
253
|
}, []);
|
|
233
254
|
/**
|
|
234
|
-
* Sprawdza, czy kursor stoi w zapytaniu
|
|
255
|
+
* Sprawdza, czy kursor stoi w zapytaniu `@...`, i ustawia listę podpowiedzi.
|
|
235
256
|
*
|
|
236
257
|
* Zapytanie liczymy z **węzła tekstowego**, nie z całego HTML-a: w treści siedzą tagi, a `@`
|
|
237
258
|
* ma widzieć tylko to, co użytkownik naprawdę napisał w tym miejscu. Rozpoznanie zostawiamy
|
|
238
|
-
* `mentionQuery()` z `@konce-pt/mention`
|
|
259
|
+
* `mentionQuery()` z `@konce-pt/mention` - te same reguły działają w obu portach.
|
|
239
260
|
*/
|
|
240
261
|
const syncMention = useCallback(() => {
|
|
241
262
|
if (disabled || !mentions.length) {
|
|
@@ -265,10 +286,10 @@ export function KptRichText({ value, defaultValue = '', onValueChange, toolbar =
|
|
|
265
286
|
setMentionOpen(true);
|
|
266
287
|
}, [caretPoint, closeMention, disabled, mentionLimit, mentions]);
|
|
267
288
|
/**
|
|
268
|
-
* Wstawia wzmiankę w miejsce zapytania
|
|
289
|
+
* Wstawia wzmiankę w miejsce zapytania `@...`.
|
|
269
290
|
*
|
|
270
291
|
* Wzmianka jest elementem, nie samym tekstem: `data-mention` niesie identyfikator, który
|
|
271
|
-
* przeżyje zmianę nazwy, a `contenteditable="false"` trzyma ją w jednym kawałku
|
|
292
|
+
* przeżyje zmianę nazwy, a `contenteditable="false"` trzyma ją w jednym kawałku - bez tego
|
|
272
293
|
* kursor wchodziłby w środek nazwiska i psuł zapis. Spacja po wzmiance daje gdzie postawić
|
|
273
294
|
* kursor: bez niej nie da się wyjść poza element, który nie jest edytowalny.
|
|
274
295
|
*/
|
|
@@ -303,7 +324,7 @@ export function KptRichText({ value, defaultValue = '', onValueChange, toolbar =
|
|
|
303
324
|
useEffect(() => {
|
|
304
325
|
const onSelectionChange = () => {
|
|
305
326
|
refreshState();
|
|
306
|
-
// Kursor może odjechać od `@` także bez pisania (klik, strzałki)
|
|
327
|
+
// Kursor może odjechać od `@` także bez pisania (klik, strzałki) - wtedy podpowiedzi
|
|
307
328
|
// muszą zniknąć, inaczej Enter wstawiłby wzmiankę w zupełnie innym miejscu.
|
|
308
329
|
syncMention();
|
|
309
330
|
};
|
|
@@ -325,7 +346,7 @@ export function KptRichText({ value, defaultValue = '', onValueChange, toolbar =
|
|
|
325
346
|
if (!(target instanceof Element))
|
|
326
347
|
return;
|
|
327
348
|
const focus = target.closest('td, th');
|
|
328
|
-
if (
|
|
349
|
+
if (focus?.closest('table') !== selAnchor.current.closest('table'))
|
|
329
350
|
return;
|
|
330
351
|
if (focus === selAnchor.current && !selecting.current)
|
|
331
352
|
return;
|
|
@@ -368,7 +389,7 @@ export function KptRichText({ value, defaultValue = '', onValueChange, toolbar =
|
|
|
368
389
|
selection.addRange(savedRange.current);
|
|
369
390
|
}
|
|
370
391
|
};
|
|
371
|
-
//
|
|
392
|
+
// -- Schowek --
|
|
372
393
|
const copySelection = async () => {
|
|
373
394
|
const selection = window.getSelection();
|
|
374
395
|
if (!selection || selection.rangeCount === 0 || selection.isCollapsed)
|
|
@@ -405,7 +426,7 @@ export function KptRichText({ value, defaultValue = '', onValueChange, toolbar =
|
|
|
405
426
|
pushValue();
|
|
406
427
|
}
|
|
407
428
|
catch {
|
|
408
|
-
/* brak uprawnień albo API
|
|
429
|
+
/* brak uprawnień albo API - ignorujemy */
|
|
409
430
|
}
|
|
410
431
|
};
|
|
411
432
|
const pastePlain = async () => {
|
|
@@ -416,10 +437,10 @@ export function KptRichText({ value, defaultValue = '', onValueChange, toolbar =
|
|
|
416
437
|
pushValue();
|
|
417
438
|
}
|
|
418
439
|
catch {
|
|
419
|
-
/* brak uprawnień albo API
|
|
440
|
+
/* brak uprawnień albo API - ignorujemy */
|
|
420
441
|
}
|
|
421
442
|
};
|
|
422
|
-
//
|
|
443
|
+
// -- Narzędzia --
|
|
423
444
|
const exec = (item) => {
|
|
424
445
|
if (disabled)
|
|
425
446
|
return;
|
|
@@ -464,7 +485,7 @@ export function KptRichText({ value, defaultValue = '', onValueChange, toolbar =
|
|
|
464
485
|
};
|
|
465
486
|
/**
|
|
466
487
|
* Tab: wcięcie w liście, przeskok komórki w tabeli. Poza nimi i na krawędziach zostawiamy Tab
|
|
467
|
-
* natywny
|
|
488
|
+
* natywny - inaczej edytor byłby pułapką klawiaturową (WCAG 2.1.2).
|
|
468
489
|
*/
|
|
469
490
|
/** Klawiatura listy podpowiedzi; `true` = zdarzenie obsłużone i edytor go już nie zobaczy. */
|
|
470
491
|
const onMentionKeyDown = (event) => {
|
|
@@ -525,7 +546,7 @@ export function KptRichText({ value, defaultValue = '', onValueChange, toolbar =
|
|
|
525
546
|
const cells = [...table.querySelectorAll('td, th')];
|
|
526
547
|
const next = cells[cells.indexOf(cell) + (event.shiftKey ? -1 : 1)];
|
|
527
548
|
if (!next)
|
|
528
|
-
return; // koniec tabeli
|
|
549
|
+
return; // koniec tabeli - Tab zostaje natywny
|
|
529
550
|
event.preventDefault();
|
|
530
551
|
const range = document.createRange();
|
|
531
552
|
range.selectNodeContents(next);
|
|
@@ -535,7 +556,7 @@ export function KptRichText({ value, defaultValue = '', onValueChange, toolbar =
|
|
|
535
556
|
clearCellSelection();
|
|
536
557
|
refreshState();
|
|
537
558
|
};
|
|
538
|
-
/** Natywne wklejanie
|
|
559
|
+
/** Natywne wklejanie - czyścimy treść, żeby nie wnosić śmieci z edytorów tekstu i stron. */
|
|
539
560
|
const onPaste = (event) => {
|
|
540
561
|
if (disabled)
|
|
541
562
|
return;
|
|
@@ -548,7 +569,7 @@ export function KptRichText({ value, defaultValue = '', onValueChange, toolbar =
|
|
|
548
569
|
legacyExec('insertText', text);
|
|
549
570
|
pushValue();
|
|
550
571
|
};
|
|
551
|
-
//
|
|
572
|
+
// -- Kolory --
|
|
552
573
|
const activeCells = () => {
|
|
553
574
|
if (selCells.current.size)
|
|
554
575
|
return [...selCells.current];
|
|
@@ -570,7 +591,40 @@ export function KptRichText({ value, defaultValue = '', onValueChange, toolbar =
|
|
|
570
591
|
legacyExec('backColor', color);
|
|
571
592
|
pushValue();
|
|
572
593
|
};
|
|
573
|
-
//
|
|
594
|
+
// -- Tabele --
|
|
595
|
+
const pickStyle = (tag) => {
|
|
596
|
+
setStyleOpen(false);
|
|
597
|
+
focusEditor();
|
|
598
|
+
restoreSelection();
|
|
599
|
+
legacyExec('formatBlock', `<${tag}>`);
|
|
600
|
+
pushValue();
|
|
601
|
+
refreshState();
|
|
602
|
+
};
|
|
603
|
+
/** Siatka tabeli z klawiatury: strzałki zmieniają rozmiar (1-8), Enter wstawia, Escape wraca. */
|
|
604
|
+
const onGridKeyDown = (event) => {
|
|
605
|
+
const clamp = (n) => Math.min(GRID_ROWS.length, Math.max(1, n));
|
|
606
|
+
const r = grid.r || 1;
|
|
607
|
+
const c = grid.c || 1;
|
|
608
|
+
if (event.key === 'ArrowDown')
|
|
609
|
+
setGrid({ r: clamp(r + 1), c });
|
|
610
|
+
else if (event.key === 'ArrowUp')
|
|
611
|
+
setGrid({ r: clamp(r - 1), c });
|
|
612
|
+
else if (event.key === 'ArrowRight')
|
|
613
|
+
setGrid({ r, c: clamp(c + 1) });
|
|
614
|
+
else if (event.key === 'ArrowLeft')
|
|
615
|
+
setGrid({ r, c: clamp(c - 1) });
|
|
616
|
+
else if (event.key === 'Enter' || event.key === ' ')
|
|
617
|
+
insertTableAt(r, c);
|
|
618
|
+
else if (event.key === 'Escape') {
|
|
619
|
+
setTableOpen(false);
|
|
620
|
+
setGrid({ r: 0, c: 0 });
|
|
621
|
+
tableAnchor?.focus();
|
|
622
|
+
}
|
|
623
|
+
else
|
|
624
|
+
return;
|
|
625
|
+
event.preventDefault();
|
|
626
|
+
event.stopPropagation();
|
|
627
|
+
};
|
|
574
628
|
const insertTableAt = (rows, cols) => {
|
|
575
629
|
setTableOpen(false);
|
|
576
630
|
setGrid({ r: 0, c: 0 });
|
|
@@ -582,7 +636,7 @@ export function KptRichText({ value, defaultValue = '', onValueChange, toolbar =
|
|
|
582
636
|
const afterTableOp = () => {
|
|
583
637
|
const fromMenu = !!document.activeElement?.closest('.kpt-rt-menu');
|
|
584
638
|
setMenuOpen(false);
|
|
585
|
-
// Pozycja wybrana z klawiatury znika razem z menu
|
|
639
|
+
// Pozycja wybrana z klawiatury znika razem z menu - fokus wraca do edytora, nie na <body>.
|
|
586
640
|
if (fromMenu)
|
|
587
641
|
focusEditor();
|
|
588
642
|
pushValue();
|
|
@@ -598,7 +652,7 @@ export function KptRichText({ value, defaultValue = '', onValueChange, toolbar =
|
|
|
598
652
|
if (handleMenuKeydown(event.currentTarget, event.nativeEvent) === 'close')
|
|
599
653
|
closeMenuToEditor();
|
|
600
654
|
};
|
|
601
|
-
/** Menu tabeli z klawiatury: klawisz menu albo `Shift+F10` w komórce
|
|
655
|
+
/** Menu tabeli z klawiatury: klawisz menu albo `Shift+F10` w komórce - jak natywne menu kontekstowe. */
|
|
602
656
|
const openMenuFromKeyboard = () => {
|
|
603
657
|
const node = window.getSelection()?.anchorNode ?? null;
|
|
604
658
|
const el = node instanceof Element ? node : (node?.parentElement ?? null);
|
|
@@ -608,13 +662,13 @@ export function KptRichText({ value, defaultValue = '', onValueChange, toolbar =
|
|
|
608
662
|
const rect = cell.getBoundingClientRect();
|
|
609
663
|
saveSelection();
|
|
610
664
|
ctxCell.current = cell;
|
|
611
|
-
|
|
665
|
+
setCanSplitCtx((cell.colSpan || 1) > 1 || (cell.rowSpan || 1) > 1);
|
|
612
666
|
setMenuPoint({ x: rect.left, y: rect.bottom });
|
|
613
667
|
menuFromKeyboard.current = true;
|
|
614
668
|
setMenuOpen(true);
|
|
615
669
|
return true;
|
|
616
670
|
};
|
|
617
|
-
/** Okienko adresu zamiast `window.prompt()`
|
|
671
|
+
/** Okienko adresu zamiast `window.prompt()` - nazwane pole, `Enter` wstawia, `Escape` wraca do tekstu. */
|
|
618
672
|
const openLink = () => {
|
|
619
673
|
if (disabled)
|
|
620
674
|
return;
|
|
@@ -650,7 +704,7 @@ export function KptRichText({ value, defaultValue = '', onValueChange, toolbar =
|
|
|
650
704
|
event.preventDefault();
|
|
651
705
|
saveSelection();
|
|
652
706
|
ctxCell.current = cell;
|
|
653
|
-
|
|
707
|
+
setCanSplitCtx((cell.colSpan || 1) > 1 || (cell.rowSpan || 1) > 1);
|
|
654
708
|
setMenuPoint({ x: event.clientX, y: event.clientY });
|
|
655
709
|
setMenuOpen(true);
|
|
656
710
|
};
|
|
@@ -714,7 +768,7 @@ export function KptRichText({ value, defaultValue = '', onValueChange, toolbar =
|
|
|
714
768
|
}
|
|
715
769
|
pushValue();
|
|
716
770
|
};
|
|
717
|
-
//
|
|
771
|
+
// -- Prezentacja narzędzi --
|
|
718
772
|
const styleOptions = [
|
|
719
773
|
{ tag: 'p', label: t('richText.styleNormal') },
|
|
720
774
|
...[1, 2, 3, 4, 5, 6].map((level) => ({
|
|
@@ -729,18 +783,67 @@ export function KptRichText({ value, defaultValue = '', onValueChange, toolbar =
|
|
|
729
783
|
const cmd = COMMAND[item];
|
|
730
784
|
return !!cmd && activeCmds.has(cmd);
|
|
731
785
|
};
|
|
732
|
-
/** `aria-pressed` tylko dla przełączników
|
|
786
|
+
/** `aria-pressed` tylko dla przełączników - wcięcia są akcjami, nie stanem. */
|
|
733
787
|
const pressed = (item) => item === 'indent' || item === 'outdent' ? undefined : isActive(item);
|
|
734
788
|
const enabled = (item) => {
|
|
735
789
|
if (item === 'indent')
|
|
736
790
|
return canIndent;
|
|
737
791
|
if (item === 'outdent')
|
|
738
792
|
return canOutdent;
|
|
793
|
+
if (item === 'dictate')
|
|
794
|
+
return mic.available && !sourceView;
|
|
739
795
|
return true;
|
|
740
796
|
};
|
|
741
|
-
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((swatch) => (_jsx("button", { type: "button", className: "kpt-rich-text__swatch", style: { background: swatch.color }, "aria-label": t(`richText.swatches.${swatch.name}`), onClick: () => onPick(swatch.color) }, swatch.color))) }), _jsxs("label", { className: "kpt-rich-text__picker", children: [_jsx("span", { children: customLabel }), _jsx("input", { type: "color", onInput: (event) => onPick(event.target.value) })] })] }));
|
|
742
797
|
/**
|
|
743
|
-
*
|
|
798
|
+
* Panele narzędzi żyją w nakładce na końcu `<body>` - po zamontowaniu fokus wchodzi do środka,
|
|
799
|
+
* inaczej Tab musiałby przejść przez całą stronę, żeby do nich dojść (WCAG 2.1.1).
|
|
800
|
+
*/
|
|
801
|
+
// Refy stabilne (`useCallback`): nowa funkcja przy każdym renderze byłaby wołana od nowa
|
|
802
|
+
// i przy każdej zmianie stanu edytora zabierałaby fokus z powrotem na początek panelu.
|
|
803
|
+
const focusPanelOnMount = useCallback((el) => {
|
|
804
|
+
if (el)
|
|
805
|
+
requestAnimationFrame(() => el.focus());
|
|
806
|
+
}, []);
|
|
807
|
+
const focusSwatchOnMount = useCallback((el) => {
|
|
808
|
+
if (el)
|
|
809
|
+
requestAnimationFrame(() => el.querySelector('.kpt-rich-text__swatch')?.focus());
|
|
810
|
+
}, []);
|
|
811
|
+
const focusGridOnMount = useCallback((el) => {
|
|
812
|
+
if (!el)
|
|
813
|
+
return;
|
|
814
|
+
setGrid((g) => (g.r === 0 ? { r: 1, c: 1 } : g));
|
|
815
|
+
requestAnimationFrame(() => el.focus());
|
|
816
|
+
}, []);
|
|
817
|
+
/** Paleta: strzałki po próbkach, Tab krąży w panelu, Escape zamyka i oddaje fokus narzędziu. */
|
|
818
|
+
const onPaletteKeyDown = (event) => {
|
|
819
|
+
const panel = event.currentTarget;
|
|
820
|
+
if (event.key === 'Escape') {
|
|
821
|
+
event.preventDefault();
|
|
822
|
+
event.stopPropagation();
|
|
823
|
+
setColorOpen(false);
|
|
824
|
+
if (colorKind === 'cellBg')
|
|
825
|
+
focusEditor();
|
|
826
|
+
else
|
|
827
|
+
(colorKind === 'foreColor' ? foreAnchor : backAnchor)?.focus();
|
|
828
|
+
return;
|
|
829
|
+
}
|
|
830
|
+
if (event.key === 'Tab') {
|
|
831
|
+
trapTab(panel, event.nativeEvent);
|
|
832
|
+
return;
|
|
833
|
+
}
|
|
834
|
+
const swatches = [...panel.querySelectorAll('.kpt-rich-text__swatch')];
|
|
835
|
+
const at = swatches.indexOf(document.activeElement);
|
|
836
|
+
if (at === -1)
|
|
837
|
+
return;
|
|
838
|
+
const next = nextRovingIndex(swatches.map(() => false), at, event.key);
|
|
839
|
+
if (next === null || next === at)
|
|
840
|
+
return;
|
|
841
|
+
event.preventDefault();
|
|
842
|
+
swatches[next].focus();
|
|
843
|
+
};
|
|
844
|
+
const paletteContent = (_jsxs("div", { ref: focusSwatchOnMount, className: "kpt-rich-text__palette", role: "group", "aria-label": t(colorKind === 'backColor' ? 'richText.backColor' : 'richText.foreColor'), onMouseDown: (event) => event.preventDefault(), onKeyDown: onPaletteKeyDown, children: [_jsx("div", { className: "kpt-rich-text__swatches", children: SWATCHES.map((swatch) => (_jsx("button", { type: "button", className: "kpt-rich-text__swatch", style: { background: swatch.color }, "aria-label": t(`richText.swatches.${swatch.name}`), onClick: () => applyColor(swatch.color) }, swatch.color))) }), _jsxs("label", { className: "kpt-rich-text__picker", children: [_jsx("span", { children: t('richText.paletteCustom') }), _jsx("input", { type: "color", onInput: (event) => applyColor(event.target.value) })] })] }));
|
|
845
|
+
/**
|
|
846
|
+
* Panel otwarty przy dolnej albo prawej krawędzi okna wychodziłby poza ekran - CDK robił to
|
|
744
847
|
* w Angularze sam, tutaj przesuwamy go po zamontowaniu, gdy już znać jego rozmiar.
|
|
745
848
|
*/
|
|
746
849
|
const clampToViewport = (el) => {
|
|
@@ -753,13 +856,13 @@ export function KptRichText({ value, defaultValue = '', onValueChange, toolbar =
|
|
|
753
856
|
el.style.top = `${top}px`;
|
|
754
857
|
};
|
|
755
858
|
/**
|
|
756
|
-
* Panel przypięty do punktu kursora
|
|
859
|
+
* Panel przypięty do punktu kursora - menu tabeli i jego popovery nie mają wyzwalacza, więc
|
|
757
860
|
* nie przechodzą przez `KptOverlay`; pozycję niosą współrzędne kliknięcia, tak jak
|
|
758
861
|
* w `KptContextMenu`.
|
|
759
862
|
*/
|
|
760
863
|
const pointPanel = (content, onClose) => {
|
|
761
864
|
if (!container)
|
|
762
|
-
return null; // render na serwerze
|
|
865
|
+
return null; // render na serwerze - nie ma czego portalować
|
|
763
866
|
const layer = { zIndex: 'var(--kpt-z-overlay, 1100)' };
|
|
764
867
|
return createPortal(_jsxs(_Fragment, { children: [_jsx("div", { style: { position: 'fixed', inset: 0, ...layer }, onClick: onClose, onContextMenu: (event) => {
|
|
765
868
|
event.preventDefault();
|
|
@@ -767,37 +870,144 @@ export function KptRichText({ value, defaultValue = '', onValueChange, toolbar =
|
|
|
767
870
|
} }), _jsx("div", { ref: clampToViewport, style: { position: 'fixed', left: menuPoint.x, top: menuPoint.y, ...layer }, children: content })] }), container);
|
|
768
871
|
};
|
|
769
872
|
/**
|
|
770
|
-
* Przystanek Tab paska: ostatnio użyte narzędzie, a gdy jest wyłączone albo go nie ma
|
|
873
|
+
* Przystanek Tab paska: ostatnio użyte narzędzie, a gdy jest wyłączone albo go nie ma -
|
|
771
874
|
* pierwsze dostępne. Dzięki temu `Tab` z paska idzie od razu do edytora, a nie przez kilkanaście
|
|
772
875
|
* przycisków.
|
|
773
876
|
*/
|
|
774
877
|
const usableTools = sections.flat().filter((item) => !disabled && enabled(item));
|
|
775
878
|
const toolStop = lastTool && usableTools.includes(lastTool) ? lastTool : (usableTools[0] ?? null);
|
|
776
879
|
const rovingProps = (item) => ({ 'data-tool': item, tabIndex: item === toolStop ? 0 : -1 });
|
|
777
|
-
/** Strzałki po pasku (w RTL odwrócone), `Home`/`End`
|
|
880
|
+
/** Strzałki po pasku (w RTL odwrócone), `Home`/`End` - skrajne; wyłączone narzędzia są pomijane. */
|
|
778
881
|
const onToolbarKeyDown = (event) => {
|
|
779
882
|
const bar = event.currentTarget;
|
|
780
|
-
const
|
|
883
|
+
const tools = [...bar.querySelectorAll('[data-tool]')];
|
|
884
|
+
// Narzędzie może być komponentem (`dictate` to KptSpeechButton) - fokus dostaje jego przycisk.
|
|
885
|
+
const buttons = tools.map((tool) => (tool instanceof HTMLButtonElement ? tool : tool.querySelector('button')));
|
|
781
886
|
const current = buttons.indexOf(event.target);
|
|
782
887
|
if (current < 0)
|
|
783
888
|
return;
|
|
784
889
|
const rtl = getComputedStyle(bar).direction === 'rtl';
|
|
785
890
|
const key = rtl && event.key === 'ArrowLeft' ? 'ArrowRight' : rtl && event.key === 'ArrowRight' ? 'ArrowLeft' : event.key;
|
|
786
|
-
const next = nextRovingIndex(buttons.map((button) => button.disabled), current, key);
|
|
891
|
+
const next = nextRovingIndex(buttons.map((button) => !button || button.disabled), current, key);
|
|
787
892
|
if (next === null)
|
|
788
893
|
return;
|
|
789
894
|
event.preventDefault();
|
|
790
|
-
setLastTool(
|
|
791
|
-
buttons[next]
|
|
895
|
+
setLastTool(tools[next].dataset['tool']);
|
|
896
|
+
buttons[next]?.focus();
|
|
792
897
|
};
|
|
793
898
|
const toolButton = (item) => (_jsx("button", { ...rovingProps(item), ref: item === 'link' ? setLinkAnchor : undefined, "aria-haspopup": item === 'link' ? 'dialog' : undefined, "aria-expanded": item === 'link' ? linkOpen : undefined, 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));
|
|
794
|
-
|
|
795
|
-
|
|
899
|
+
/**
|
|
900
|
+
* Akcje menu tabeli po identyfikatorze. Handlery żyją tutaj, a nie jako domknięcia przekazywane
|
|
901
|
+
* do `menuItem()` - funkcja wołana w renderze nie dostaje wtedy niczego, co czyta refy.
|
|
902
|
+
*/
|
|
903
|
+
const runTableAction = (action) => {
|
|
904
|
+
switch (action) {
|
|
905
|
+
case 'insertLeft': {
|
|
906
|
+
if (ctxCell.current)
|
|
907
|
+
insertColumn(ctxCell.current, 'left');
|
|
908
|
+
afterTableOp();
|
|
909
|
+
break;
|
|
910
|
+
}
|
|
911
|
+
case 'insertRight': {
|
|
912
|
+
if (ctxCell.current)
|
|
913
|
+
insertColumn(ctxCell.current, 'right');
|
|
914
|
+
afterTableOp();
|
|
915
|
+
break;
|
|
916
|
+
}
|
|
917
|
+
case 'headerColumn': {
|
|
918
|
+
if (ctxCell.current)
|
|
919
|
+
toggleHeaderColumn(ctxCell.current);
|
|
920
|
+
ctxCell.current = null; // komórki zostały podmienione
|
|
921
|
+
afterTableOp();
|
|
922
|
+
break;
|
|
923
|
+
}
|
|
924
|
+
case 'deleteColumn': {
|
|
925
|
+
if (ctxCell.current)
|
|
926
|
+
deleteColumn(ctxCell.current);
|
|
927
|
+
afterTableOp();
|
|
928
|
+
break;
|
|
929
|
+
}
|
|
930
|
+
case 'insertAbove': {
|
|
931
|
+
if (ctxCell.current)
|
|
932
|
+
insertRow(ctxCell.current, 'above');
|
|
933
|
+
afterTableOp();
|
|
934
|
+
break;
|
|
935
|
+
}
|
|
936
|
+
case 'insertBelow': {
|
|
937
|
+
if (ctxCell.current)
|
|
938
|
+
insertRow(ctxCell.current, 'below');
|
|
939
|
+
afterTableOp();
|
|
940
|
+
break;
|
|
941
|
+
}
|
|
942
|
+
case 'headerRow': {
|
|
943
|
+
if (ctxCell.current)
|
|
944
|
+
toggleHeaderRow(ctxCell.current);
|
|
945
|
+
ctxCell.current = null;
|
|
946
|
+
afterTableOp();
|
|
947
|
+
break;
|
|
948
|
+
}
|
|
949
|
+
case 'deleteRow': {
|
|
950
|
+
if (ctxCell.current)
|
|
951
|
+
deleteRow(ctxCell.current);
|
|
952
|
+
afterTableOp();
|
|
953
|
+
break;
|
|
954
|
+
}
|
|
955
|
+
case 'cellBackground': {
|
|
956
|
+
setMenuOpen(false);
|
|
957
|
+
setColorKind('cellBg');
|
|
958
|
+
setColorOpen(true);
|
|
959
|
+
break;
|
|
960
|
+
}
|
|
961
|
+
case 'alignLeft': {
|
|
962
|
+
for (const cell of activeCells())
|
|
963
|
+
cell.style.textAlign = 'left';
|
|
964
|
+
afterTableOp();
|
|
965
|
+
break;
|
|
966
|
+
}
|
|
967
|
+
case 'alignCenter': {
|
|
968
|
+
for (const cell of activeCells())
|
|
969
|
+
cell.style.textAlign = 'center';
|
|
970
|
+
afterTableOp();
|
|
971
|
+
break;
|
|
972
|
+
}
|
|
973
|
+
case 'alignRight': {
|
|
974
|
+
for (const cell of activeCells())
|
|
975
|
+
cell.style.textAlign = 'right';
|
|
976
|
+
afterTableOp();
|
|
977
|
+
break;
|
|
978
|
+
}
|
|
979
|
+
case 'mergeCells': {
|
|
980
|
+
mergeCells([...selCells.current]);
|
|
981
|
+
clearCellSelection();
|
|
982
|
+
afterTableOp();
|
|
983
|
+
break;
|
|
984
|
+
}
|
|
985
|
+
case 'splitCell': {
|
|
986
|
+
if (ctxCell.current)
|
|
987
|
+
splitCell(ctxCell.current);
|
|
988
|
+
afterTableOp();
|
|
989
|
+
break;
|
|
990
|
+
}
|
|
991
|
+
case 'border': {
|
|
992
|
+
setMenuOpen(false);
|
|
993
|
+
setBorderOpen(true);
|
|
994
|
+
break;
|
|
995
|
+
}
|
|
996
|
+
case 'deleteTable': {
|
|
997
|
+
ctxCell.current?.closest('table')?.remove();
|
|
998
|
+
afterTableOp();
|
|
999
|
+
break;
|
|
1000
|
+
}
|
|
1001
|
+
}
|
|
1002
|
+
};
|
|
1003
|
+
const menuItem = (icon, label, action, extra) => (_jsxs("button", { type: "button", role: "menuitem", tabIndex: -1, className: cn('kpt-rt-menu__item', extra), "aria-disabled": extra === 'is-disabled' || undefined, onClick: () => runTableAction(action), children: [_jsx(KptIcon, { name: icon }), " ", label] }));
|
|
1004
|
+
return (_jsxs("div", { className: cn('kpt-rich-text-host', className), ...rest, children: [_jsxs("div", { className: cn('kpt-rich-text', showInvalid && 'kpt-rich-text--invalid', disabledProp && 'kpt-rich-text--disabled'), children: [_jsx("div", { className: "kpt-rich-text__toolbar", role: "toolbar", "aria-label": t('richText.toolbar'), "aria-controls": field.id, onKeyDown: onToolbarKeyDown, 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) => {
|
|
796
1005
|
if (item === 'heading') {
|
|
797
1006
|
return (_jsxs("button", { ref: setStyleAnchor, ...rovingProps(item), 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: () => {
|
|
798
1007
|
if (disabled)
|
|
799
1008
|
return;
|
|
800
1009
|
saveSelection();
|
|
1010
|
+
setStyleActive(Math.max(0, styleOptions.findIndex((o) => o.tag === currentBlock)));
|
|
801
1011
|
setStyleOpen(true);
|
|
802
1012
|
}, children: [_jsx("span", { children: styleLabel }), _jsx(KptIcon, { name: "chevron-down", className: "kpt-rich-text__style-caret" })] }, item));
|
|
803
1013
|
}
|
|
@@ -813,8 +1023,15 @@ export function KptRichText({ value, defaultValue = '', onValueChange, toolbar =
|
|
|
813
1023
|
if (item === 'table') {
|
|
814
1024
|
return (_jsx("button", { ref: setTableAnchor, ...rovingProps(item), 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));
|
|
815
1025
|
}
|
|
1026
|
+
if (item === 'dictate') {
|
|
1027
|
+
// `data-tool` na hoście - roving znajduje narzędzie, a fokus dostaje jego przycisk.
|
|
1028
|
+
const host = { className: 'kpt-rich-text__dictate', 'data-tool': item };
|
|
1029
|
+
return (_jsx(KptSpeechButton, { listeningColor: speechColor, speech: mic, size: "sm", unsupported: "hide", wavePosition: "end", tabIndex: item === toolStop ? 0 : -1, disabled: disabled || sourceView, hostProps: host }, item));
|
|
1030
|
+
}
|
|
816
1031
|
return toolButton(item);
|
|
817
|
-
})] }, 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,
|
|
1032
|
+
})] }, 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,
|
|
1033
|
+
// Wyłączony edytor (contentEditable=false) wypada z kolejności Tab jak wyłączone pole.
|
|
1034
|
+
tabIndex: disabled ? -1 : 0, "aria-readonly": readOnly || undefined, suppressContentEditableWarning: true, "data-placeholder": placeholder, "aria-invalid": showInvalid || undefined, role: "textbox", "aria-multiline": "true", id: field.id, "aria-required": required || undefined, "aria-label": ariaLabel ?? undefined, "aria-labelledby": ariaLabel ? undefined : field.labelledBy, "aria-describedby": field.describedBy, onInput: () => {
|
|
818
1035
|
pushValue();
|
|
819
1036
|
syncMention();
|
|
820
1037
|
}, onKeyDown: onKeyDown, onPaste: onPaste, onContextMenu: onContextMenu, onMouseDown: onEditorMouseDown, onMouseMove: onEditorMouseMove, onBlur: () => onTouch?.() }))] }), mentionOpen && container
|
|
@@ -830,7 +1047,7 @@ export function KptRichText({ value, defaultValue = '', onValueChange, toolbar =
|
|
|
830
1047
|
insertMention(item);
|
|
831
1048
|
}, onMouseEnter: () => setMentionActive(index), children: [_jsx("span", { className: "kpt-rt-mentions__label", children: item.label }), item.kind ? _jsx("span", { className: "kpt-rt-mentions__kind", children: item.kind }) : null] }, item.id))) }), container)
|
|
832
1049
|
: null, _jsx(KptOverlay, { open: linkOpen, anchor: linkAnchor, onClose: () => closeLink(false), offset: 4, children: _jsxs("form", { className: "kpt-rt-link", role: "dialog",
|
|
833
|
-
// Bez walidacji przeglądarki:
|
|
1050
|
+
// Bez walidacji przeglądarki: "example.com" jest poprawnym wpisem - `sanitizeUrl` dopisze https://.
|
|
834
1051
|
noValidate: true, "aria-label": t('richText.link'), onSubmit: (event) => {
|
|
835
1052
|
event.preventDefault();
|
|
836
1053
|
applyLink();
|
|
@@ -841,93 +1058,45 @@ export function KptRichText({ value, defaultValue = '', onValueChange, toolbar =
|
|
|
841
1058
|
event.stopPropagation();
|
|
842
1059
|
closeLink(true);
|
|
843
1060
|
}, children: [_jsx("label", { className: "kpt-rt-link__label", htmlFor: `${field.id}-link`, children: t('richText.linkPrompt') }), _jsx("input", { ref: (el) => {
|
|
844
|
-
// Panel jest niewidoczny do pierwszego pomiaru
|
|
1061
|
+
// Panel jest niewidoczny do pierwszego pomiaru - fokus po klatce.
|
|
845
1062
|
if (el)
|
|
846
1063
|
requestAnimationFrame(() => el.focus());
|
|
847
|
-
}, className: "kpt-rt-link__input", id: `${field.id}-link`, type: "url", inputMode: "url", placeholder: "https://", value: linkDraft, onChange: (event) => setLinkDraft(event.target.value) }), _jsx("button", { type: "submit", className: "kpt-rt-link__apply", children: t('richText.linkApply') })] }) }), _jsx(KptOverlay, { open: styleOpen, anchor: styleAnchor, onClose: () => setStyleOpen(false), offset: 4, children: _jsx("ul", { className: "kpt-rich-text__style-panel", role: "listbox",
|
|
1064
|
+
}, className: "kpt-rt-link__input", id: `${field.id}-link`, type: "url", inputMode: "url", placeholder: "https://", value: linkDraft, onChange: (event) => setLinkDraft(event.target.value) }), _jsx("button", { type: "submit", className: "kpt-rt-link__apply", children: t('richText.linkApply') })] }) }), _jsx(KptOverlay, { open: styleOpen, anchor: styleAnchor, onClose: () => setStyleOpen(false), offset: 4, children: _jsx("ul", { ref: focusPanelOnMount, className: "kpt-rich-text__style-panel", role: "listbox", tabIndex: -1, "aria-label": t('richText.heading'), "aria-activedescendant": `${field.id}-style-${styleActive}`, onKeyDown: (event) => {
|
|
1065
|
+
const count = styleOptions.length;
|
|
1066
|
+
if (event.key === 'ArrowDown')
|
|
1067
|
+
setStyleActive((i) => (i + 1) % count);
|
|
1068
|
+
else if (event.key === 'ArrowUp')
|
|
1069
|
+
setStyleActive((i) => (i - 1 + count) % count);
|
|
1070
|
+
else if (event.key === 'Home')
|
|
1071
|
+
setStyleActive(0);
|
|
1072
|
+
else if (event.key === 'End')
|
|
1073
|
+
setStyleActive(count - 1);
|
|
1074
|
+
else if (event.key === 'Enter' || event.key === ' ')
|
|
1075
|
+
pickStyle(styleOptions[styleActive].tag);
|
|
1076
|
+
else if (event.key === 'Escape' || event.key === 'Tab') {
|
|
848
1077
|
setStyleOpen(false);
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
1078
|
+
styleAnchor?.focus();
|
|
1079
|
+
}
|
|
1080
|
+
else
|
|
1081
|
+
return;
|
|
1082
|
+
event.preventDefault();
|
|
1083
|
+
event.stopPropagation();
|
|
1084
|
+
}, children: styleOptions.map((option, index) => (_jsx("li", { id: `${field.id}-style-${index}`, className: cn(`kpt-rich-text__style-opt kpt-rich-text__style-opt--${option.tag}`, currentBlock === option.tag && 'is-selected', styleActive === index && 'is-active'), role: "option", "aria-selected": currentBlock === option.tag, onClick: () => pickStyle(option.tag), children: option.label }, option.tag))) }) }), _jsx(KptOverlay, { open: colorOpen && colorKind === 'foreColor', anchor: foreAnchor, onClose: () => setColorOpen(false), offset: 4, children: paletteContent }), _jsx(KptOverlay, { open: colorOpen && colorKind === 'backColor', anchor: backAnchor, onClose: () => setColorOpen(false), offset: 4, children: paletteContent }), _jsx(KptOverlay, { open: tableOpen, anchor: tableAnchor, onClose: () => {
|
|
855
1085
|
setTableOpen(false);
|
|
856
1086
|
setGrid({ r: 0, c: 0 });
|
|
857
|
-
}, offset: 4, children: _jsxs("div", { className: "kpt-rt-grid-pop", onMouseDown: (event) => event.preventDefault(), children: [_jsx("div", {
|
|
1087
|
+
}, offset: 4, children: _jsxs("div", { className: "kpt-rt-grid-pop", onMouseDown: (event) => event.preventDefault(), children: [_jsx("div", { ref: focusGridOnMount, className: "kpt-rt-grid", role: "group",
|
|
1088
|
+
// Jeden przystanek ze strzałkami (rozmiar tabeli) - grupa z własną obsługą klawiatury.
|
|
1089
|
+
// eslint-disable-next-line jsx-a11y/no-noninteractive-tabindex -- patrz wyżej
|
|
1090
|
+
tabIndex: 0, "aria-label": t('richText.table'), "aria-describedby": `${field.id}-grid-size`, "aria-keyshortcuts": "ArrowUp ArrowDown ArrowLeft ArrowRight Enter Escape", onKeyDown: onGridKeyDown, 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", id: `${field.id}-grid-size`, "aria-live": "polite", children: [grid.r, " x ", grid.c] })] }) }), menuOpen
|
|
858
1091
|
? pointPanel(_jsxs("div", { ref: (el) => {
|
|
859
1092
|
if (el && menuFromKeyboard.current) {
|
|
860
1093
|
menuFromKeyboard.current = false;
|
|
861
1094
|
requestAnimationFrame(() => focusMenuEdge(el, 'first'));
|
|
862
1095
|
}
|
|
863
|
-
}, className: "kpt-rt-menu", role: "menu", "aria-label": t('richText.table'), onContextMenu: (event) => event.preventDefault(), onKeyDown: onMenuKeyDown, children: [_jsx("div", { className: "kpt-rt-menu__group", "aria-hidden": "true", children: t('richText.menu.column') }), menuItem('plus', t('richText.menu.insertLeft'), () =>
|
|
864
|
-
if (ctxCell.current)
|
|
865
|
-
insertColumn(ctxCell.current, 'left');
|
|
866
|
-
afterTableOp();
|
|
867
|
-
}), menuItem('plus', t('richText.menu.insertRight'), () => {
|
|
868
|
-
if (ctxCell.current)
|
|
869
|
-
insertColumn(ctxCell.current, 'right');
|
|
870
|
-
afterTableOp();
|
|
871
|
-
}), menuItem('table', t('richText.menu.headerColumn'), () => {
|
|
872
|
-
if (ctxCell.current)
|
|
873
|
-
toggleHeaderColumn(ctxCell.current);
|
|
874
|
-
ctxCell.current = null; // komórki zostały podmienione
|
|
875
|
-
afterTableOp();
|
|
876
|
-
}), menuItem('trash', t('richText.menu.deleteColumn'), () => {
|
|
877
|
-
if (ctxCell.current)
|
|
878
|
-
deleteColumn(ctxCell.current);
|
|
879
|
-
afterTableOp();
|
|
880
|
-
}), _jsx("div", { className: "kpt-rt-menu__sep", role: "separator" }), _jsx("div", { className: "kpt-rt-menu__group", "aria-hidden": "true", children: t('richText.menu.row') }), menuItem('plus', t('richText.menu.insertAbove'), () => {
|
|
881
|
-
if (ctxCell.current)
|
|
882
|
-
insertRow(ctxCell.current, 'above');
|
|
883
|
-
afterTableOp();
|
|
884
|
-
}), menuItem('plus', t('richText.menu.insertBelow'), () => {
|
|
885
|
-
if (ctxCell.current)
|
|
886
|
-
insertRow(ctxCell.current, 'below');
|
|
887
|
-
afterTableOp();
|
|
888
|
-
}), menuItem('table', t('richText.menu.headerRow'), () => {
|
|
889
|
-
if (ctxCell.current)
|
|
890
|
-
toggleHeaderRow(ctxCell.current);
|
|
891
|
-
ctxCell.current = null;
|
|
892
|
-
afterTableOp();
|
|
893
|
-
}), menuItem('trash', t('richText.menu.deleteRow'), () => {
|
|
894
|
-
if (ctxCell.current)
|
|
895
|
-
deleteRow(ctxCell.current);
|
|
896
|
-
afterTableOp();
|
|
897
|
-
}), _jsx("div", { className: "kpt-rt-menu__sep", role: "separator" }), _jsx("div", { className: "kpt-rt-menu__group", "aria-hidden": "true", children: t('richText.menu.cell') }), menuItem('palette', t('richText.menu.cellBackground'), () => {
|
|
898
|
-
setMenuOpen(false);
|
|
899
|
-
setColorKind('cellBg');
|
|
900
|
-
setColorOpen(true);
|
|
901
|
-
}), menuItem('align-left', t('richText.menu.alignLeft'), () => {
|
|
902
|
-
for (const cell of activeCells())
|
|
903
|
-
cell.style.textAlign = 'left';
|
|
904
|
-
afterTableOp();
|
|
905
|
-
}), menuItem('align-center', t('richText.menu.alignCenter'), () => {
|
|
906
|
-
for (const cell of activeCells())
|
|
907
|
-
cell.style.textAlign = 'center';
|
|
908
|
-
afterTableOp();
|
|
909
|
-
}), menuItem('align-right', t('richText.menu.alignRight'), () => {
|
|
910
|
-
for (const cell of activeCells())
|
|
911
|
-
cell.style.textAlign = 'right';
|
|
912
|
-
afterTableOp();
|
|
913
|
-
}), _jsx("div", { className: "kpt-rt-menu__sep", role: "separator" }), menuItem('table', t('richText.menu.mergeCells'), () => {
|
|
914
|
-
mergeCells([...selCells.current]);
|
|
915
|
-
clearCellSelection();
|
|
916
|
-
afterTableOp();
|
|
917
|
-
}, selCount < 2 ? 'is-disabled' : undefined), menuItem('grip-vertical', t('richText.menu.splitCell'), () => {
|
|
918
|
-
if (ctxCell.current)
|
|
919
|
-
splitCell(ctxCell.current);
|
|
920
|
-
afterTableOp();
|
|
921
|
-
}, canSplitCtx.current ? undefined : 'is-disabled'), _jsx("div", { className: "kpt-rt-menu__sep", role: "separator" }), menuItem('table', t('richText.menu.border'), () => {
|
|
922
|
-
setMenuOpen(false);
|
|
923
|
-
setBorderOpen(true);
|
|
924
|
-
}), _jsx("div", { className: "kpt-rt-menu__sep", role: "separator" }), menuItem('trash', t('richText.menu.deleteTable'), () => {
|
|
925
|
-
ctxCell.current?.closest('table')?.remove();
|
|
926
|
-
afterTableOp();
|
|
927
|
-
}, 'kpt-rt-menu__item--danger')] }), () => setMenuOpen(false))
|
|
1096
|
+
}, className: "kpt-rt-menu", role: "menu", tabIndex: -1, "aria-label": t('richText.table'), onContextMenu: (event) => event.preventDefault(), onKeyDown: onMenuKeyDown, children: [_jsx("div", { className: "kpt-rt-menu__group", "aria-hidden": "true", children: t('richText.menu.column') }), menuItem('plus', t('richText.menu.insertLeft'), 'insertLeft'), menuItem('plus', t('richText.menu.insertRight'), 'insertRight'), menuItem('table', t('richText.menu.headerColumn'), 'headerColumn'), menuItem('trash', t('richText.menu.deleteColumn'), 'deleteColumn'), _jsx("div", { className: "kpt-rt-menu__sep", role: "separator" }), _jsx("div", { className: "kpt-rt-menu__group", "aria-hidden": "true", children: t('richText.menu.row') }), menuItem('plus', t('richText.menu.insertAbove'), 'insertAbove'), menuItem('plus', t('richText.menu.insertBelow'), 'insertBelow'), menuItem('table', t('richText.menu.headerRow'), 'headerRow'), menuItem('trash', t('richText.menu.deleteRow'), 'deleteRow'), _jsx("div", { className: "kpt-rt-menu__sep", role: "separator" }), _jsx("div", { className: "kpt-rt-menu__group", "aria-hidden": "true", children: t('richText.menu.cell') }), menuItem('palette', t('richText.menu.cellBackground'), 'cellBackground'), menuItem('align-left', t('richText.menu.alignLeft'), 'alignLeft'), menuItem('align-center', t('richText.menu.alignCenter'), 'alignCenter'), menuItem('align-right', t('richText.menu.alignRight'), 'alignRight'), _jsx("div", { className: "kpt-rt-menu__sep", role: "separator" }), menuItem('table', t('richText.menu.mergeCells'), 'mergeCells', selCount < 2 ? 'is-disabled' : undefined), menuItem('grip-vertical', t('richText.menu.splitCell'), 'splitCell', canSplitCtx ? undefined : 'is-disabled'), _jsx("div", { className: "kpt-rt-menu__sep", role: "separator" }), menuItem('table', t('richText.menu.border'), 'border'), _jsx("div", { className: "kpt-rt-menu__sep", role: "separator" }), menuItem('trash', t('richText.menu.deleteTable'), 'deleteTable', 'kpt-rt-menu__item--danger')] }), () => setMenuOpen(false))
|
|
928
1097
|
: null, borderOpen
|
|
929
1098
|
? 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((swatch) => (_jsx("button", { type: "button", className: "kpt-rich-text__swatch", style: { background: swatch.color }, "aria-label": t(`richText.swatches.${swatch.name}`), onClick: () => setBorderColor(swatch.color) }, swatch.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))
|
|
930
1099
|
: null, colorOpen && colorKind === 'cellBg'
|
|
931
|
-
? pointPanel(
|
|
1100
|
+
? pointPanel(paletteContent, () => setColorOpen(false))
|
|
932
1101
|
: null] }));
|
|
933
1102
|
}
|