@rh-support/components 2.5.231 → 2.6.10

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 (69) hide show
  1. package/lib/esm/CaseLabelLoadErrorModal/CaseLabelLoadErrorModal.d.ts +10 -0
  2. package/lib/esm/CaseLabelLoadErrorModal/CaseLabelLoadErrorModal.d.ts.map +1 -0
  3. package/lib/esm/CaseLabelLoadErrorModal/CaseLabelLoadErrorModal.js +17 -0
  4. package/lib/esm/CaseLabelLoadErrorModal/CaseLabelLoadErrorModal.scss +13 -0
  5. package/lib/esm/CaseLabelLoadErrorModal/index.d.ts +2 -0
  6. package/lib/esm/CaseLabelLoadErrorModal/index.d.ts.map +1 -0
  7. package/lib/esm/CaseLabelLoadErrorModal/index.js +1 -0
  8. package/lib/esm/ConfirmationModal/ConfirmationDialog.js +1 -1
  9. package/lib/esm/CustomDatePicker/CustomDatePicker.d.ts +18 -0
  10. package/lib/esm/CustomDatePicker/CustomDatePicker.d.ts.map +1 -0
  11. package/lib/esm/CustomDatePicker/CustomDatePicker.js +27 -0
  12. package/lib/esm/CustomDatePicker/customDatePicker.css +52 -0
  13. package/lib/esm/CustomDatePicker/index.d.ts +2 -0
  14. package/lib/esm/CustomDatePicker/index.d.ts.map +1 -0
  15. package/lib/esm/CustomDatePicker/index.js +1 -0
  16. package/lib/esm/Functional/CaseContactsSelectorExternal.d.ts +2 -0
  17. package/lib/esm/Functional/CaseContactsSelectorExternal.d.ts.map +1 -1
  18. package/lib/esm/Functional/CaseContactsSelectorExternal.js +176 -82
  19. package/lib/esm/InlineEdit/NewInlineEdit.d.ts +2 -0
  20. package/lib/esm/InlineEdit/NewInlineEdit.d.ts.map +1 -1
  21. package/lib/esm/InlineEdit/NewInlineEdit.js +7 -2
  22. package/lib/esm/OwnerTypeaheadDropdown/OwnerTypeaheadDropdown.d.ts +2 -0
  23. package/lib/esm/OwnerTypeaheadDropdown/OwnerTypeaheadDropdown.d.ts.map +1 -1
  24. package/lib/esm/OwnerTypeaheadDropdown/OwnerTypeaheadDropdown.js +8 -3
  25. package/lib/esm/PhoneInput/PhoneInput.d.ts.map +1 -1
  26. package/lib/esm/PhoneInput/PhoneInput.js +38 -1
  27. package/lib/esm/RichTextEditor/RichTextEditor.d.ts +45 -0
  28. package/lib/esm/RichTextEditor/RichTextEditor.d.ts.map +1 -0
  29. package/lib/esm/RichTextEditor/RichTextEditor.js +464 -0
  30. package/lib/esm/RichTextEditor/autoLinkModule.d.ts +18 -0
  31. package/lib/esm/RichTextEditor/autoLinkModule.d.ts.map +1 -0
  32. package/lib/esm/RichTextEditor/autoLinkModule.js +156 -0
  33. package/lib/esm/RichTextEditor/index.d.ts +4 -0
  34. package/lib/esm/RichTextEditor/index.d.ts.map +1 -0
  35. package/lib/esm/RichTextEditor/index.js +2 -0
  36. package/lib/esm/RichTextEditor/linkUrl.d.ts +7 -0
  37. package/lib/esm/RichTextEditor/linkUrl.d.ts.map +1 -0
  38. package/lib/esm/RichTextEditor/linkUrl.js +26 -0
  39. package/lib/esm/RichTextEditor/richTextEditor.css +182 -0
  40. package/lib/esm/RichTextEditor/useQuillEditor.d.ts +20 -0
  41. package/lib/esm/RichTextEditor/useQuillEditor.d.ts.map +1 -0
  42. package/lib/esm/RichTextEditor/useQuillEditor.js +180 -0
  43. package/lib/esm/SingleSelectDropdown/SingleSelectDropdown.d.ts +2 -0
  44. package/lib/esm/SingleSelectDropdown/SingleSelectDropdown.d.ts.map +1 -1
  45. package/lib/esm/SingleSelectDropdown/SingleSelectDropdown.js +7 -6
  46. package/lib/esm/Table/TablePagination.d.ts +1 -0
  47. package/lib/esm/Table/TablePagination.d.ts.map +1 -1
  48. package/lib/esm/Table/TablePagination.js +1 -1
  49. package/lib/esm/ToastNotification/ToastNotification.d.ts +5 -1
  50. package/lib/esm/ToastNotification/ToastNotification.d.ts.map +1 -1
  51. package/lib/esm/ToastNotification/ToastNotification.js +16 -3
  52. package/lib/esm/ToastNotification/toastNotification.css +7 -0
  53. package/lib/esm/helperFunctions/errorMessage.d.ts +2 -1
  54. package/lib/esm/helperFunctions/errorMessage.d.ts.map +1 -1
  55. package/lib/esm/helperFunctions/errorMessage.js +19 -5
  56. package/lib/esm/hooks/useCopyToClipboard.d.ts +1 -1
  57. package/lib/esm/hooks/useCopyToClipboard.d.ts.map +1 -1
  58. package/lib/esm/hooks/useCopyToClipboard.js +3 -1
  59. package/lib/esm/index.d.ts +3 -1
  60. package/lib/esm/index.d.ts.map +1 -1
  61. package/lib/esm/index.js +4 -1
  62. package/package.json +6 -3
  63. package/lib/esm/MarkdownEditor/MarkdownEditor.d.ts +0 -49
  64. package/lib/esm/MarkdownEditor/MarkdownEditor.d.ts.map +0 -1
  65. package/lib/esm/MarkdownEditor/MarkdownEditor.js +0 -601
  66. package/lib/esm/MarkdownEditor/index.d.ts +0 -2
  67. package/lib/esm/MarkdownEditor/index.d.ts.map +0 -1
  68. package/lib/esm/MarkdownEditor/index.js +0 -1
  69. package/lib/esm/MarkdownEditor/markdownEditor.css +0 -223
@@ -0,0 +1,18 @@
1
+ import type { default as Quill } from 'quill';
2
+ /**
3
+ * Registers auto-link behaviour on a Quill instance:
4
+ *
5
+ * 1. **Type** — after a delimiter character, checks the preceding token
6
+ * for URL / case-ID / BZ patterns.
7
+ * 2. **Paste** — when the insert is longer than one character, scans the
8
+ * full inserted range.
9
+ * 3. **Blur** — a full-document scan so trailing tokens without a
10
+ * delimiter still get linked before submit.
11
+ */
12
+ export declare function registerAutoLinkModule(quill: Quill): void;
13
+ /**
14
+ * Scans the entire document for unlinkified tokens.
15
+ * Call this on blur so trailing URLs / IDs without a delimiter are caught.
16
+ */
17
+ export declare function linkifyFullDocument(quill: Quill): void;
18
+ //# sourceMappingURL=autoLinkModule.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"autoLinkModule.d.ts","sourceRoot":"","sources":["../../../src/RichTextEditor/autoLinkModule.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,IAAI,KAAK,EAA8B,MAAM,OAAO,CAAC;AAyE1E;;;;;;;;;GASG;AACH,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,KAAK,GAAG,IAAI,CA0EzD;AAED;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,KAAK,GAAG,IAAI,CAItD"}
@@ -0,0 +1,156 @@
1
+ function getCaseIdHref(caseId) {
2
+ const host = typeof window !== 'undefined' ? window.location.host : 'access.redhat.com';
3
+ return `https://${host}/support/cases/#/case/${caseId}`;
4
+ }
5
+ function getBugzillaHref(bugId) {
6
+ return `https://bugzilla.redhat.com/show_bug.cgi?id=${bugId}`;
7
+ }
8
+ const DELIMITER_RE = /[\s.,;:!?)}\]>]/;
9
+ const CASE_ID_RE = /(?:^|[\s(])(\d{8})$/;
10
+ const BZ_RE = /(?:^|[\s(])(?:BZ[\s-]*|Bug[#\s]?)(\d{7,})$/i;
11
+ const URL_RE = /(?:^|[\s(])(https?:\/\/[-A-Z0-9+&@#/%?=~_|!:,.;]*[-A-Z0-9+&@#/%=~_|])/gi;
12
+ const URL_AT_END_RE = /(https?:\/\/[-A-Z0-9+&@#/%?=~_|!:,.;]*[-A-Z0-9+&@#/%=~_|])$/i;
13
+ const WORD_CASE_ID_RE = /^(\d{8})$/;
14
+ const WORD_BZ_RE = /^(?:BZ[\s-]*|Bug[#\s]?)(\d{7,})$/i;
15
+ /**
16
+ * Scans a text range inside the Quill document and applies link formatting
17
+ * to all recognised URL, case-ID, and BZ patterns that aren't already linked.
18
+ *
19
+ * Used by the blur scan and paste scan to cover text that was not preceded
20
+ * by a delimiter character.
21
+ */
22
+ function linkifyRange(quill, rangeStart, rangeLength) {
23
+ const text = quill.getText(rangeStart, rangeLength);
24
+ URL_RE.lastIndex = 0;
25
+ let urlMatch;
26
+ while ((urlMatch = URL_RE.exec(text)) !== null) {
27
+ const url = urlMatch[1];
28
+ const matchOffset = urlMatch.index + urlMatch[0].length - url.length;
29
+ const absStart = rangeStart + matchOffset;
30
+ const format = quill.getFormat(absStart, url.length);
31
+ if (!format.link) {
32
+ quill.formatText(absStart, url.length, 'link', url, 'api');
33
+ }
34
+ }
35
+ const lines = text.split('\n');
36
+ let offset = 0;
37
+ for (const line of lines) {
38
+ const words = line.split(/(\s+)/);
39
+ let wordOffset = offset;
40
+ for (const word of words) {
41
+ if (/\S/.test(word)) {
42
+ const caseMatch = WORD_CASE_ID_RE.exec(word);
43
+ if (caseMatch) {
44
+ const absStart = rangeStart + wordOffset;
45
+ const fmt = quill.getFormat(absStart, 8);
46
+ if (!fmt.link) {
47
+ quill.formatText(absStart, 8, 'link', getCaseIdHref(caseMatch[1]), 'api');
48
+ }
49
+ }
50
+ else {
51
+ const bzMatch = WORD_BZ_RE.exec(word);
52
+ if (bzMatch) {
53
+ const absStart = rangeStart + wordOffset;
54
+ const fmt = quill.getFormat(absStart, word.length);
55
+ if (!fmt.link) {
56
+ quill.formatText(absStart, word.length, 'link', getBugzillaHref(bzMatch[1]), 'api');
57
+ }
58
+ }
59
+ }
60
+ }
61
+ wordOffset += word.length;
62
+ }
63
+ offset += line.length + 1;
64
+ }
65
+ }
66
+ /**
67
+ * Registers auto-link behaviour on a Quill instance:
68
+ *
69
+ * 1. **Type** — after a delimiter character, checks the preceding token
70
+ * for URL / case-ID / BZ patterns.
71
+ * 2. **Paste** — when the insert is longer than one character, scans the
72
+ * full inserted range.
73
+ * 3. **Blur** — a full-document scan so trailing tokens without a
74
+ * delimiter still get linked before submit.
75
+ */
76
+ export function registerAutoLinkModule(quill) {
77
+ quill.on('text-change', (delta, _oldDelta, source) => {
78
+ if (source !== 'user')
79
+ return;
80
+ const ops = delta.ops;
81
+ if (!ops || ops.length === 0)
82
+ return;
83
+ const lastOp = ops.at(-1);
84
+ if (!lastOp || typeof lastOp.insert !== 'string')
85
+ return;
86
+ const insertedText = lastOp.insert;
87
+ // Paste: multi-char insert — scan the full inserted range
88
+ if (insertedText.length > 1) {
89
+ let insertOffset = 0;
90
+ for (const op of ops) {
91
+ if (!op.retain)
92
+ break;
93
+ insertOffset += op.retain;
94
+ }
95
+ setTimeout(() => linkifyRange(quill, insertOffset, insertedText.length), 0);
96
+ return;
97
+ }
98
+ // Type: single-char delimiter
99
+ if (!DELIMITER_RE.test(insertedText))
100
+ return;
101
+ const selection = quill.getSelection();
102
+ if (!selection)
103
+ return;
104
+ const cursorIndex = selection.index;
105
+ const text = quill.getText(0, cursorIndex);
106
+ let lineStart = text.lastIndexOf('\n', cursorIndex - 2);
107
+ if (lineStart === -1)
108
+ lineStart = 0;
109
+ else
110
+ lineStart += 1;
111
+ const lineText = text.slice(lineStart, cursorIndex - 1);
112
+ // URL — try first so 8-digit IDs inside URLs are not separately linked
113
+ const urlMatch = URL_AT_END_RE.exec(lineText);
114
+ if (urlMatch) {
115
+ const url = urlMatch[1];
116
+ const matchStart = lineStart + lineText.length - url.length;
117
+ const format = quill.getFormat(matchStart, url.length);
118
+ if (!format.link) {
119
+ quill.formatText(matchStart, url.length, 'link', url, 'api');
120
+ }
121
+ return;
122
+ }
123
+ // Case ID (8-digit number)
124
+ const caseMatch = CASE_ID_RE.exec(lineText);
125
+ if (caseMatch) {
126
+ const caseId = caseMatch[1];
127
+ const matchStart = lineStart + lineText.length - caseId.length;
128
+ const format = quill.getFormat(matchStart, caseId.length);
129
+ if (!format.link) {
130
+ quill.formatText(matchStart, caseId.length, 'link', getCaseIdHref(caseId), 'api');
131
+ }
132
+ return;
133
+ }
134
+ // Bugzilla
135
+ const bzMatch = BZ_RE.exec(lineText);
136
+ if (bzMatch) {
137
+ const bugId = bzMatch[1];
138
+ const fullMatch = bzMatch[0].trimStart();
139
+ const matchStart = lineStart + lineText.length - fullMatch.length;
140
+ const format = quill.getFormat(matchStart, fullMatch.length);
141
+ if (!format.link) {
142
+ quill.formatText(matchStart, fullMatch.length, 'link', getBugzillaHref(bugId), 'api');
143
+ }
144
+ }
145
+ });
146
+ }
147
+ /**
148
+ * Scans the entire document for unlinkified tokens.
149
+ * Call this on blur so trailing URLs / IDs without a delimiter are caught.
150
+ */
151
+ export function linkifyFullDocument(quill) {
152
+ const length = quill.getLength();
153
+ if (length <= 1)
154
+ return;
155
+ linkifyRange(quill, 0, length - 1);
156
+ }
@@ -0,0 +1,4 @@
1
+ export * from './RichTextEditor';
2
+ export { useQuillEditor } from './useQuillEditor';
3
+ export type { UseQuillEditorOptions } from './useQuillEditor';
4
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/RichTextEditor/index.ts"],"names":[],"mappings":"AAAA,cAAc,kBAAkB,CAAC;AACjC,OAAO,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAClD,YAAY,EAAE,qBAAqB,EAAE,MAAM,kBAAkB,CAAC"}
@@ -0,0 +1,2 @@
1
+ export * from './RichTextEditor';
2
+ export { useQuillEditor } from './useQuillEditor';
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Returns a normalized http(s)/mailto URL, or null if the value is not allowed.
3
+ * Protocol-less hosts (e.g. example.com) are treated as https.
4
+ */
5
+ export declare function normalizeLinkUrl(url: string): string | null;
6
+ export declare function isValidUrl(url: string): boolean;
7
+ //# sourceMappingURL=linkUrl.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"linkUrl.d.ts","sourceRoot":"","sources":["../../../src/RichTextEditor/linkUrl.ts"],"names":[],"mappings":"AAEA;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAa3D;AAED,wBAAgB,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAE/C"}
@@ -0,0 +1,26 @@
1
+ const ALLOWED_PROTOCOLS = new Set(['http:', 'https:', 'mailto:']);
2
+ /**
3
+ * Returns a normalized http(s)/mailto URL, or null if the value is not allowed.
4
+ * Protocol-less hosts (e.g. example.com) are treated as https.
5
+ */
6
+ export function normalizeLinkUrl(url) {
7
+ const trimmed = url.trim();
8
+ if (!trimmed)
9
+ return null;
10
+ try {
11
+ const hasAllowedPrefix = /^https?:\/\//i.test(trimmed) || /^mailto:/i.test(trimmed);
12
+ const candidate = hasAllowedPrefix ? trimmed : `https://${trimmed}`;
13
+ const parsed = new URL(candidate);
14
+ if (!ALLOWED_PROTOCOLS.has(parsed.protocol))
15
+ return null;
16
+ if (parsed.protocol !== 'mailto:' && !parsed.hostname.includes('.'))
17
+ return null;
18
+ return candidate;
19
+ }
20
+ catch (_a) {
21
+ return null;
22
+ }
23
+ }
24
+ export function isValidUrl(url) {
25
+ return normalizeLinkUrl(url) !== null;
26
+ }
@@ -0,0 +1,182 @@
1
+ .richtext-editor-container {
2
+ background: var(--pf-t--global--background--color--primary--default, #fff);
3
+ border: 1px solid var(--pf-t--global--border--color--default, #d2d2d2);
4
+ border-radius: var(--pf-t--global--border--radius--small, 3px);
5
+ display: block;
6
+ min-height: 100px;
7
+ }
8
+
9
+ .richtext-editor-container--disabled {
10
+ background: var(--pf-t--global--background--color--disabled--default, #f0f0f0);
11
+ pointer-events: none;
12
+ }
13
+
14
+ .richtext-editor-content {
15
+ resize: vertical;
16
+ overflow-y: auto;
17
+ overflow-x: hidden;
18
+ font-size: 15px;
19
+ min-width: 100%;
20
+ outline: none;
21
+ border: none;
22
+ min-height: 100px;
23
+ }
24
+
25
+ .richtext-editor-content.ql-container,
26
+ .richtext-editor-container .ql-container.ql-container {
27
+ border: none !important;
28
+ outline: none !important;
29
+ font-size: 15px;
30
+ }
31
+
32
+ .richtext-editor-content .ql-editor {
33
+ padding: 0.8rem;
34
+ outline: none;
35
+ border: none;
36
+ min-height: 100px;
37
+ overflow-wrap: break-word;
38
+ word-break: break-word;
39
+ }
40
+
41
+ .richtext-editor-content .ql-editor:focus {
42
+ outline: none;
43
+ border: none;
44
+ box-shadow: none;
45
+ }
46
+
47
+ .richtext-editor-content .ql-editor.ql-blank::before {
48
+ content: attr(data-placeholder);
49
+ color: var(--pf-t--global--text--color--placeholder, #6a6e73);
50
+ pointer-events: none;
51
+ position: absolute;
52
+ font-style: normal;
53
+ }
54
+
55
+ .richtext-editor-content ul,
56
+ .richtext-editor-content ol {
57
+ padding-left: 24px;
58
+ }
59
+
60
+ .richtext-editor-content li[data-list='bullet'] {
61
+ list-style-type: disc;
62
+ }
63
+
64
+ .richtext-editor-content li[data-list='bullet'].ql-indent-1 {
65
+ list-style-type: circle;
66
+ }
67
+
68
+ .richtext-editor-content li[data-list='bullet'].ql-indent-2 {
69
+ list-style-type: square;
70
+ }
71
+
72
+ .richtext-editor-content li[data-list='bullet'].ql-indent-3 {
73
+ list-style-type: disc;
74
+ }
75
+
76
+ .richtext-editor-content li[data-list='bullet'].ql-indent-4 {
77
+ list-style-type: circle;
78
+ }
79
+
80
+ .richtext-editor-content li[data-list='ordered'] {
81
+ list-style-type: decimal;
82
+ }
83
+
84
+ .richtext-editor-content li[data-list='ordered'].ql-indent-1 {
85
+ list-style-type: lower-alpha;
86
+ }
87
+
88
+ .richtext-editor-content li[data-list='ordered'].ql-indent-2 {
89
+ list-style-type: lower-roman;
90
+ }
91
+
92
+ .richtext-editor-content .ql-align-center {
93
+ text-align: center;
94
+ }
95
+
96
+ .richtext-editor-content .ql-align-right {
97
+ text-align: right;
98
+ }
99
+
100
+ .richtext-editor-content .ql-align-justify {
101
+ text-align: justify;
102
+ }
103
+
104
+ .richtext-editor-content .ql-indent-1 {
105
+ margin-left: 3em;
106
+ }
107
+
108
+ .richtext-editor-content .ql-indent-2 {
109
+ margin-left: 6em;
110
+ }
111
+
112
+ .richtext-editor-content .ql-indent-3 {
113
+ margin-left: 9em;
114
+ }
115
+
116
+ .richtext-editor-content .ql-indent-4 {
117
+ margin-left: 12em;
118
+ }
119
+
120
+ .richtext-editor-content .ql-indent-5 {
121
+ margin-left: 15em;
122
+ }
123
+
124
+ .richtext-editor-content a {
125
+ color: var(--pf-t--global--text--color--link--default, #0066cc);
126
+ text-decoration: underline;
127
+ }
128
+
129
+ .richtext-editor-content img {
130
+ max-width: 100%;
131
+ height: auto;
132
+ display: block;
133
+ margin: 8px 0;
134
+ }
135
+
136
+ .richtext-editor-container.is-private .richtext-editor-content {
137
+ background-color: #f5eeee;
138
+ }
139
+
140
+ .richtext-toolbar {
141
+ border-bottom: 1px solid var(--pf-t--global--border--color--default, #d2d2d2);
142
+ padding: 4px 8px;
143
+ }
144
+
145
+ .richtext-toolbar-left-section {
146
+ display: flex;
147
+ align-items: center;
148
+ flex-wrap: wrap;
149
+ gap: 2px;
150
+ }
151
+
152
+ .btn-group-separator {
153
+ display: inline-block;
154
+ width: 1px;
155
+ height: 24px;
156
+ margin: 0 8px;
157
+ background-color: var(--pf-t--global--border--color--default, #ccc);
158
+ }
159
+
160
+ .toggle-content {
161
+ display: inline-flex;
162
+ align-items: center;
163
+ gap: 4px;
164
+ font-size: 14px;
165
+ }
166
+
167
+ .dropdown-caret {
168
+ font-size: 12px;
169
+ }
170
+
171
+ .richtext-char-counter {
172
+ padding: 4px 8px;
173
+ font-size: 12px;
174
+ text-align: right;
175
+ color: var(--pf-t--global--text--color--subtle, #6a6e73);
176
+ border-top: 1px solid var(--pf-t--global--border--color--default, #d2d2d2);
177
+ }
178
+
179
+ .richtext-char-counter--exceeded {
180
+ color: var(--pf-t--global--color--status--danger--default, #c9190b);
181
+ font-weight: bold;
182
+ }
@@ -0,0 +1,20 @@
1
+ import Quill from 'quill';
2
+ export interface UseQuillEditorOptions {
3
+ placeholder?: string;
4
+ readOnly?: boolean;
5
+ onChange?: (html: string) => void;
6
+ onChangePlainText?: (text: string) => void;
7
+ onSelectionChange?: (format: Record<string, unknown>) => void;
8
+ onFocus?: () => void;
9
+ onBlur?: () => void;
10
+ }
11
+ export declare function useQuillEditor(options: UseQuillEditorOptions): {
12
+ editorRef: import("react").MutableRefObject<HTMLDivElement>;
13
+ quillRef: import("react").MutableRefObject<Quill>;
14
+ activeFormat: Record<string, unknown>;
15
+ setActiveFormat: import("react").Dispatch<import("react").SetStateAction<Record<string, unknown>>>;
16
+ setContents: (html: string) => void;
17
+ setReadOnly: (readOnly: boolean) => void;
18
+ getPlainText: () => string;
19
+ };
20
+ //# sourceMappingURL=useQuillEditor.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useQuillEditor.d.ts","sourceRoot":"","sources":["../../../src/RichTextEditor/useQuillEditor.ts"],"names":[],"mappings":"AACA,OAAO,KAAmC,MAAM,OAAO,CAAC;AAKxD,MAAM,WAAW,qBAAqB;IAClC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,QAAQ,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IAClC,iBAAiB,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IAC3C,iBAAiB,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,IAAI,CAAC;IAC9D,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB,MAAM,CAAC,EAAE,MAAM,IAAI,CAAC;CACvB;AAoFD,wBAAgB,cAAc,CAAC,OAAO,EAAE,qBAAqB;;;;;wBA+ElB,MAAM;4BAgBF,OAAO;;EASrD"}
@@ -0,0 +1,180 @@
1
+ import DOMPurify from 'dompurify';
2
+ import Quill from 'quill';
3
+ import { useCallback, useEffect, useRef, useState } from 'react';
4
+ import { linkifyFullDocument, registerAutoLinkModule } from './autoLinkModule';
5
+ const ALLOWED_FORMATS = ['header', 'bold', 'italic', 'underline', 'strike', 'list', 'indent', 'align', 'link', 'image'];
6
+ /**
7
+ * Quill 2 internally represents ALL lists as <ol> and distinguishes bullet vs.
8
+ * ordered via data-list attributes on <li>. root.innerHTML preserves text-entity
9
+ * encoding (important for literal angle-brackets), but outputs this internal
10
+ * format. This helper converts it to semantic <ul>/<ol> and nests indented
11
+ * items before sanitization.
12
+ */
13
+ function normalizeQuillListHtml(rawHtml) {
14
+ const tmp = document.createElement('div');
15
+ tmp.innerHTML = rawHtml;
16
+ tmp.querySelectorAll('.ql-ui').forEach((el) => el.remove());
17
+ tmp.querySelectorAll('ol').forEach((ol) => {
18
+ const items = Array.from(ol.querySelectorAll(':scope > li'));
19
+ const allBullet = items.length > 0 && items.every((li) => li instanceof HTMLElement && li.dataset.list === 'bullet');
20
+ if (allBullet) {
21
+ const ul = document.createElement('ul');
22
+ for (const attr of Array.from(ol.attributes)) {
23
+ ul.setAttribute(attr.name, attr.value);
24
+ }
25
+ while (ol.firstChild)
26
+ ul.appendChild(ol.firstChild);
27
+ ol.replaceWith(ul);
28
+ }
29
+ items.forEach((li) => {
30
+ if (li instanceof HTMLElement) {
31
+ delete li.dataset.list;
32
+ }
33
+ });
34
+ });
35
+ tmp.querySelectorAll('ul, ol').forEach((list) => {
36
+ if (list instanceof HTMLElement)
37
+ nestIndentedItems(list);
38
+ });
39
+ return tmp.innerHTML;
40
+ }
41
+ function tryNestIndentedItem(item, list) {
42
+ var _a;
43
+ const indentMatch = /ql-indent-(\d+)/.exec((_a = item.className) !== null && _a !== void 0 ? _a : '');
44
+ if (!indentMatch)
45
+ return false;
46
+ const level = Number.parseInt(indentMatch[1], 10);
47
+ item.classList.remove(`ql-indent-${level}`);
48
+ if (!item.className.trim())
49
+ item.removeAttribute('class');
50
+ let prev = item.previousElementSibling;
51
+ while (prev && prev.tagName !== 'LI')
52
+ prev = prev.previousElementSibling;
53
+ if (!prev)
54
+ return false;
55
+ let nested = prev.querySelector(':scope > ul, :scope > ol');
56
+ if (!(nested instanceof HTMLElement)) {
57
+ nested = document.createElement(list.tagName.toLowerCase());
58
+ prev.appendChild(nested);
59
+ }
60
+ nested.appendChild(item);
61
+ if (level > 1)
62
+ item.classList.add(`ql-indent-${level - 1}`);
63
+ return true;
64
+ }
65
+ function nestIndentedItems(list) {
66
+ let changed = true;
67
+ while (changed) {
68
+ changed = false;
69
+ const items = Array.from(list.querySelectorAll(':scope > li'));
70
+ for (const item of items) {
71
+ if (tryNestIndentedItem(item, list))
72
+ changed = true;
73
+ }
74
+ }
75
+ // Newly created nested lists are not in the original querySelectorAll snapshot.
76
+ // Recurse so indent 2+ items become true nested lists instead of leftover ql-indent-* siblings.
77
+ for (const nested of Array.from(list.querySelectorAll(':scope > li > ul, :scope > li > ol'))) {
78
+ if (nested instanceof HTMLElement)
79
+ nestIndentedItems(nested);
80
+ }
81
+ }
82
+ export function useQuillEditor(options) {
83
+ const editorRef = useRef(null);
84
+ const quillRef = useRef(null);
85
+ const [activeFormat, setActiveFormat] = useState({});
86
+ const optionsRef = useRef(options);
87
+ optionsRef.current = options;
88
+ // Track whether the last change was internal (from Quill) to avoid echo loops
89
+ const isInternalChange = useRef(false);
90
+ useEffect(() => {
91
+ if (!editorRef.current || quillRef.current)
92
+ return;
93
+ const quill = new Quill(editorRef.current, {
94
+ modules: {
95
+ toolbar: false,
96
+ keyboard: {
97
+ bindings: {
98
+ indent: {
99
+ key: 'Tab',
100
+ format: ['list'],
101
+ handler(_range, _context) {
102
+ this.quill.format('indent', '+1', 'user');
103
+ return false;
104
+ },
105
+ },
106
+ outdent: {
107
+ key: 'Tab',
108
+ shiftKey: true,
109
+ format: ['list'],
110
+ handler(_range, _context) {
111
+ this.quill.format('indent', '-1', 'user');
112
+ return false;
113
+ },
114
+ },
115
+ },
116
+ },
117
+ clipboard: { matchVisual: false },
118
+ },
119
+ formats: ALLOWED_FORMATS,
120
+ placeholder: optionsRef.current.placeholder,
121
+ readOnly: optionsRef.current.readOnly,
122
+ });
123
+ registerAutoLinkModule(quill);
124
+ quill.on('text-change', () => {
125
+ var _a, _b, _c, _d;
126
+ isInternalChange.current = true;
127
+ const html = DOMPurify.sanitize(normalizeQuillListHtml(quill.root.innerHTML));
128
+ (_b = (_a = optionsRef.current).onChange) === null || _b === void 0 ? void 0 : _b.call(_a, html);
129
+ (_d = (_c = optionsRef.current).onChangePlainText) === null || _d === void 0 ? void 0 : _d.call(_c, quill.getText().trim());
130
+ const sel = quill.getSelection();
131
+ if (sel) {
132
+ setActiveFormat(quill.getFormat(sel));
133
+ }
134
+ isInternalChange.current = false;
135
+ });
136
+ quill.on('selection-change', (range) => {
137
+ var _a, _b, _c, _d, _e, _f;
138
+ if (range) {
139
+ setActiveFormat(quill.getFormat(range));
140
+ (_b = (_a = optionsRef.current).onSelectionChange) === null || _b === void 0 ? void 0 : _b.call(_a, quill.getFormat(range));
141
+ if (range.length === 0) {
142
+ (_d = (_c = optionsRef.current).onFocus) === null || _d === void 0 ? void 0 : _d.call(_c);
143
+ }
144
+ }
145
+ else {
146
+ linkifyFullDocument(quill);
147
+ (_f = (_e = optionsRef.current).onBlur) === null || _f === void 0 ? void 0 : _f.call(_e);
148
+ }
149
+ });
150
+ quillRef.current = quill;
151
+ return () => {
152
+ quillRef.current = null;
153
+ };
154
+ // eslint-disable-next-line react-hooks/exhaustive-deps
155
+ }, []);
156
+ const setContents = useCallback((html) => {
157
+ const quill = quillRef.current;
158
+ if (!quill || isInternalChange.current)
159
+ return;
160
+ const currentHtml = DOMPurify.sanitize(normalizeQuillListHtml(quill.root.innerHTML));
161
+ if (currentHtml === html)
162
+ return;
163
+ const selection = quill.getSelection();
164
+ quill.clipboard.dangerouslyPasteHTML(html);
165
+ if (selection) {
166
+ const length = quill.getLength();
167
+ const safeIndex = Math.min(selection.index, length - 1);
168
+ quill.setSelection(safeIndex, 0, 'silent');
169
+ }
170
+ }, []);
171
+ const setReadOnly = useCallback((readOnly) => {
172
+ var _a;
173
+ (_a = quillRef.current) === null || _a === void 0 ? void 0 : _a.enable(!readOnly);
174
+ }, []);
175
+ const getPlainText = useCallback(() => {
176
+ var _a, _b;
177
+ return (_b = (_a = quillRef.current) === null || _a === void 0 ? void 0 : _a.getText().trim()) !== null && _b !== void 0 ? _b : '';
178
+ }, []);
179
+ return { editorRef, quillRef, activeFormat, setActiveFormat, setContents, setReadOnly, getPlainText };
180
+ }
@@ -27,6 +27,8 @@ export interface SingleSelectDropdownProps extends Omit<SelectProps, 'toggle'> {
27
27
  isScrollable?: boolean;
28
28
  zIndex?: number;
29
29
  isModalOpen?: boolean;
30
+ /** When set, toggle opens this handler instead of the select menu (e.g. case-details severity modal). */
31
+ onMenuToggle?: () => void;
30
32
  hideToggleIcons?: boolean;
31
33
  onClear?: () => void;
32
34
  onBlur?: (event: React.FocusEvent<HTMLDivElement>) => void;
@@ -1 +1 @@
1
- {"version":3,"file":"SingleSelectDropdown.d.ts","sourceRoot":"","sources":["../../../src/SingleSelectDropdown/SingleSelectDropdown.tsx"],"names":[],"mappings":"AAAA,OAAO,EAQH,WAAW,EAEd,MAAM,wBAAwB,CAAC;AAGhC,OAAO,KAAK,EAAE,EAAE,SAAS,EAA4B,MAAM,OAAO,CAAC;AAInE,MAAM,WAAW,2BAA2B;IACxC,KAAK,EAAE,GAAG,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,SAAS,CAAC;IACpB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,KAAK,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,yBAA0B,SAAQ,IAAI,CAAC,WAAW,EAAE,QAAQ,CAAC;IAC1E,QAAQ,EAAE,2BAA2B,CAAC;IACtC,OAAO,EAAE,2BAA2B,EAAE,CAAC;IACvC,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,WAAW,CAAC,EAAE,MAAM,GAAG,GAAG,CAAC,OAAO,CAAC;IACnC,KAAK,CAAC,EAAE,SAAS,CAAC;IAClB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B,mBAAmB,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,OAAO,CAAC;IACxC,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB,MAAM,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,CAAC,UAAU,CAAC,cAAc,CAAC,KAAK,IAAI,CAAC;IAC3D,QAAQ,CAAC,EAAE,CAAC,OAAO,KAAA,KAAK,IAAI,CAAC;CAChC;AAED,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,yBAAyB,qBAsIpE"}
1
+ {"version":3,"file":"SingleSelectDropdown.d.ts","sourceRoot":"","sources":["../../../src/SingleSelectDropdown/SingleSelectDropdown.tsx"],"names":[],"mappings":"AAAA,OAAO,EAQH,WAAW,EAEd,MAAM,wBAAwB,CAAC;AAGhC,OAAO,KAAK,EAAE,EAAE,SAAS,EAA4B,MAAM,OAAO,CAAC;AAInE,MAAM,WAAW,2BAA2B;IACxC,KAAK,EAAE,GAAG,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,SAAS,CAAC;IACpB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,KAAK,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,yBAA0B,SAAQ,IAAI,CAAC,WAAW,EAAE,QAAQ,CAAC;IAC1E,QAAQ,EAAE,2BAA2B,CAAC;IACtC,OAAO,EAAE,2BAA2B,EAAE,CAAC;IACvC,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,WAAW,CAAC,EAAE,MAAM,GAAG,GAAG,CAAC,OAAO,CAAC;IACnC,KAAK,CAAC,EAAE,SAAS,CAAC;IAClB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B,mBAAmB,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,OAAO,CAAC;IACxC,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,yGAAyG;IACzG,YAAY,CAAC,EAAE,MAAM,IAAI,CAAC;IAC1B,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB,MAAM,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,CAAC,UAAU,CAAC,cAAc,CAAC,KAAK,IAAI,CAAC;IAC3D,QAAQ,CAAC,EAAE,CAAC,OAAO,KAAA,KAAK,IAAI,CAAC;CAChC;AAED,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,yBAAyB,qBA0IpE"}
@@ -15,9 +15,13 @@ import isEqual from 'lodash/isEqual';
15
15
  import React, { useEffect, useState } from 'react';
16
16
  import { useSelectKeyboardNavigator } from '../hooks/useSelectKeyboardNavigator';
17
17
  export function SingleSelectDropdown(props) {
18
- const { badge, selected, options, isDisabled, isLoading, id, placeholder, isInvalid, className, toggleClassName, dataTrackingId, toggleDataTrackingId, isScrollable, ariaLabel, zIndex, ariaLabelledBy, isModalOpen, hideToggleIcons, onClear, onBlur, onSelect } = props, restProps = __rest(props, ["badge", "selected", "options", "isDisabled", "isLoading", "id", "placeholder", "isInvalid", "className", "toggleClassName", "dataTrackingId", "toggleDataTrackingId", "isScrollable", "ariaLabel", "zIndex", "ariaLabelledBy", "isModalOpen", "hideToggleIcons", "onClear", "onBlur", "onSelect"]);
18
+ const { badge, selected, options, isDisabled, isLoading, id, placeholder, isInvalid, className, toggleClassName, dataTrackingId, toggleDataTrackingId, isScrollable, ariaLabel, zIndex, ariaLabelledBy, isModalOpen, onMenuToggle, hideToggleIcons, onClear, onBlur, onSelect } = props, restProps = __rest(props, ["badge", "selected", "options", "isDisabled", "isLoading", "id", "placeholder", "isInvalid", "className", "toggleClassName", "dataTrackingId", "toggleDataTrackingId", "isScrollable", "ariaLabel", "zIndex", "ariaLabelledBy", "isModalOpen", "onMenuToggle", "hideToggleIcons", "onClear", "onBlur", "onSelect"]);
19
19
  const [isOpen, setIsOpen] = useState(false);
20
20
  const onToggleClick = () => {
21
+ if (onMenuToggle) {
22
+ onMenuToggle();
23
+ return;
24
+ }
21
25
  setIsOpen(!isOpen);
22
26
  };
23
27
  const handleSelect = (selectedOption) => {
@@ -36,8 +40,7 @@ export function SingleSelectDropdown(props) {
36
40
  if (isModalOpen) {
37
41
  setIsOpen(false);
38
42
  }
39
- // eslint-disable-next-line react-hooks/exhaustive-deps
40
- }, [isOpen]);
43
+ }, [isModalOpen]);
41
44
  const toggle = (toggleRef) => (React.createElement(MenuToggle, { className: toggleClassName || 'single-select-toggle-text', ref: toggleRef, onClick: onToggleClick, onKeyDown: onInputKeyDown, isExpanded: isOpen, isDisabled: isDisabled, isFullWidth: true, status: isInvalid ? 'danger' : undefined, "data-tracking-id": toggleDataTrackingId || '', badge: badge },
42
45
  hideToggleIcons && ((selected === null || selected === void 0 ? void 0 : selected.label) || placeholder),
43
46
  !hideToggleIcons && (React.createElement(Flex, { justifyContent: { default: 'justifyContentSpaceBetween' } },
@@ -58,8 +61,6 @@ export function SingleSelectDropdown(props) {
58
61
  return (React.createElement(Select, Object.assign({ shouldFocusToggleOnSelect: true, shouldPreventScrollOnItemFocus: true, id: id || '', className: className || '', "data-tracking-id": dataTrackingId, isOpen: isOpen, selected: selected, onSelect: onLocalSelect, onOpenChange: (isOpen) => setIsOpen(isOpen), toggle: toggle, popperProps: { direction: 'down', enableFlip: false }, isScrollable: isScrollable, onBlur: onLocalBlur, "aria-label": ariaLabel, zIndex: zIndex, "aria-labelledby": ariaLabelledBy, maxMenuHeight: "20rem" }, restProps),
59
62
  React.createElement(SelectList, null, options.map((option, index) => {
60
63
  var _a;
61
- return (React.createElement(SelectOption, { key: option.value, value: option, description: option.description || '',
62
- // Fix the comparison to compare the entire objects or at least their values
63
- isSelected: isEqual(selected === null || selected === void 0 ? void 0 : selected.value, option.value), isDisabled: option.disabled, isFocused: focusedItemIndex === index, title: (option === null || option === void 0 ? void 0 : option.title) || option.label }, (_a = option === null || option === void 0 ? void 0 : option.element) !== null && _a !== void 0 ? _a : option.label));
64
+ return (React.createElement(SelectOption, { key: option.value, value: option, description: option.description || '', isSelected: isEqual(selected === null || selected === void 0 ? void 0 : selected.value, option.value), isDisabled: option.disabled, isFocused: focusedItemIndex === index, title: (option === null || option === void 0 ? void 0 : option.title) || option.label, "aria-label": option.label }, (_a = option === null || option === void 0 ? void 0 : option.element) !== null && _a !== void 0 ? _a : option.label));
64
65
  }))));
65
66
  }