@cloudscape-design/components 3.0.1270 → 3.0.1272
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/app-layout/visual-refresh-toolbar/toolbar/index.js +1 -1
- package/app-layout/visual-refresh-toolbar/toolbar/index.js.map +1 -1
- package/app-layout/visual-refresh-toolbar/toolbar/trigger-button/index.d.ts +1 -1
- package/app-layout/visual-refresh-toolbar/toolbar/trigger-button/index.d.ts.map +1 -1
- package/app-layout/visual-refresh-toolbar/toolbar/trigger-button/index.js +1 -2
- package/app-layout/visual-refresh-toolbar/toolbar/trigger-button/index.js.map +1 -1
- package/app-layout/visual-refresh-toolbar/toolbar/trigger-button/styles.css.js +6 -7
- package/app-layout/visual-refresh-toolbar/toolbar/trigger-button/styles.scoped.css +20 -26
- package/app-layout/visual-refresh-toolbar/toolbar/trigger-button/styles.selectors.js +6 -7
- package/button/styles.css.js +22 -22
- package/button/styles.scoped.css +78 -78
- package/button/styles.selectors.js +22 -22
- package/help-panel/styles.css.js +6 -6
- package/help-panel/styles.scoped.css +70 -70
- package/help-panel/styles.selectors.js +6 -6
- package/i18n/messages/all.all.js +1 -1
- package/i18n/messages/all.all.json +1 -1
- package/i18n/messages/all.ar.js +1 -1
- package/i18n/messages/all.ar.json +1 -1
- package/i18n/messages/all.de.js +1 -1
- package/i18n/messages/all.de.json +1 -1
- package/i18n/messages/all.en-GB.js +1 -1
- package/i18n/messages/all.en-GB.json +1 -1
- package/i18n/messages/all.en.js +1 -1
- package/i18n/messages/all.en.json +1 -1
- package/i18n/messages/all.es.js +1 -1
- package/i18n/messages/all.es.json +1 -1
- package/i18n/messages/all.fr.js +1 -1
- package/i18n/messages/all.fr.json +1 -1
- package/i18n/messages/all.id.js +1 -1
- package/i18n/messages/all.id.json +1 -1
- package/i18n/messages/all.it.js +1 -1
- package/i18n/messages/all.it.json +1 -1
- package/i18n/messages/all.ja.js +1 -1
- package/i18n/messages/all.ja.json +1 -1
- package/i18n/messages/all.ko.js +1 -1
- package/i18n/messages/all.ko.json +1 -1
- package/i18n/messages/all.pt-BR.js +1 -1
- package/i18n/messages/all.pt-BR.json +1 -1
- package/i18n/messages/all.tr.js +1 -1
- package/i18n/messages/all.tr.json +1 -1
- package/i18n/messages/all.zh-CN.js +1 -1
- package/i18n/messages/all.zh-CN.json +1 -1
- package/i18n/messages/all.zh-TW.js +1 -1
- package/i18n/messages/all.zh-TW.json +1 -1
- package/i18n/messages-types.d.ts +18 -0
- package/i18n/messages-types.d.ts.map +1 -1
- package/i18n/messages-types.js.map +1 -1
- package/internal/base-component/styles.scoped.css +63 -4
- package/internal/components/token-list/styles.css.js +10 -10
- package/internal/components/token-list/styles.scoped.css +25 -25
- package/internal/components/token-list/styles.selectors.js +10 -10
- package/internal/environment.js +2 -2
- package/internal/environment.json +2 -2
- package/internal/generated/styles/tokens.d.ts +17 -3
- package/internal/generated/styles/tokens.js +17 -3
- package/internal/generated/theming/index.cjs +417 -9
- package/internal/generated/theming/index.cjs.d.ts +189 -0
- package/internal/generated/theming/index.d.ts +189 -0
- package/internal/generated/theming/index.js +417 -9
- package/internal/keycode.d.ts +3 -1
- package/internal/keycode.d.ts.map +1 -1
- package/internal/keycode.js +5 -0
- package/internal/keycode.js.map +1 -1
- package/internal/manifest.json +1 -1
- package/internal/utils/handle-key.d.ts +22 -1
- package/internal/utils/handle-key.d.ts.map +1 -1
- package/internal/utils/handle-key.js +62 -4
- package/internal/utils/handle-key.js.map +1 -1
- package/item-card/styles.css.js +14 -14
- package/item-card/styles.scoped.css +52 -52
- package/item-card/styles.selectors.js +14 -14
- package/link/styles.css.js +21 -21
- package/link/styles.scoped.css +81 -81
- package/link/styles.selectors.js +21 -21
- package/package.json +1 -1
- package/prompt-input/components/menu-dropdown.d.ts +33 -0
- package/prompt-input/components/menu-dropdown.d.ts.map +1 -0
- package/prompt-input/components/menu-dropdown.js +48 -0
- package/prompt-input/components/menu-dropdown.js.map +1 -0
- package/prompt-input/components/textarea-mode.d.ts +15 -0
- package/prompt-input/components/textarea-mode.d.ts.map +1 -0
- package/prompt-input/components/textarea-mode.js +8 -0
- package/prompt-input/components/textarea-mode.js.map +1 -0
- package/prompt-input/components/token-mode.d.ts +61 -0
- package/prompt-input/components/token-mode.d.ts.map +1 -0
- package/prompt-input/components/token-mode.js +37 -0
- package/prompt-input/components/token-mode.js.map +1 -0
- package/prompt-input/core/caret-controller.d.ts +73 -0
- package/prompt-input/core/caret-controller.d.ts.map +1 -0
- package/prompt-input/core/caret-controller.js +396 -0
- package/prompt-input/core/caret-controller.js.map +1 -0
- package/prompt-input/core/caret-spot-utils.d.ts +6 -0
- package/prompt-input/core/caret-spot-utils.d.ts.map +1 -0
- package/prompt-input/core/caret-spot-utils.js +52 -0
- package/prompt-input/core/caret-spot-utils.js.map +1 -0
- package/prompt-input/core/caret-utils.d.ts +25 -0
- package/prompt-input/core/caret-utils.d.ts.map +1 -0
- package/prompt-input/core/caret-utils.js +183 -0
- package/prompt-input/core/caret-utils.js.map +1 -0
- package/prompt-input/core/constants.d.ts +14 -0
- package/prompt-input/core/constants.d.ts.map +1 -0
- package/prompt-input/core/constants.js +18 -0
- package/prompt-input/core/constants.js.map +1 -0
- package/prompt-input/core/dom-utils.d.ts +60 -0
- package/prompt-input/core/dom-utils.d.ts.map +1 -0
- package/prompt-input/core/dom-utils.js +252 -0
- package/prompt-input/core/dom-utils.js.map +1 -0
- package/prompt-input/core/event-handlers.d.ts +68 -0
- package/prompt-input/core/event-handlers.d.ts.map +1 -0
- package/prompt-input/core/event-handlers.js +678 -0
- package/prompt-input/core/event-handlers.js.map +1 -0
- package/prompt-input/core/menu-state.d.ts +62 -0
- package/prompt-input/core/menu-state.d.ts.map +1 -0
- package/prompt-input/core/menu-state.js +168 -0
- package/prompt-input/core/menu-state.js.map +1 -0
- package/prompt-input/core/token-operations.d.ts +21 -0
- package/prompt-input/core/token-operations.d.ts.map +1 -0
- package/prompt-input/core/token-operations.js +273 -0
- package/prompt-input/core/token-operations.js.map +1 -0
- package/prompt-input/core/token-renderer.d.ts +26 -0
- package/prompt-input/core/token-renderer.d.ts.map +1 -0
- package/prompt-input/core/token-renderer.js +230 -0
- package/prompt-input/core/token-renderer.js.map +1 -0
- package/prompt-input/core/token-utils.d.ts +22 -0
- package/prompt-input/core/token-utils.d.ts.map +1 -0
- package/prompt-input/core/token-utils.js +262 -0
- package/prompt-input/core/token-utils.js.map +1 -0
- package/prompt-input/core/trigger-utils.d.ts +18 -0
- package/prompt-input/core/trigger-utils.d.ts.map +1 -0
- package/prompt-input/core/trigger-utils.js +174 -0
- package/prompt-input/core/trigger-utils.js.map +1 -0
- package/prompt-input/core/type-guards.d.ts +13 -0
- package/prompt-input/core/type-guards.d.ts.map +1 -0
- package/prompt-input/core/type-guards.js +36 -0
- package/prompt-input/core/type-guards.js.map +1 -0
- package/prompt-input/index.d.ts +1 -1
- package/prompt-input/index.d.ts.map +1 -1
- package/prompt-input/index.js.map +1 -1
- package/prompt-input/interfaces.d.ts +356 -7
- package/prompt-input/interfaces.d.ts.map +1 -1
- package/prompt-input/interfaces.js.map +1 -1
- package/prompt-input/internal.d.ts +1 -1
- package/prompt-input/internal.d.ts.map +1 -1
- package/prompt-input/internal.js +195 -61
- package/prompt-input/internal.js.map +1 -1
- package/prompt-input/styles.css.js +26 -17
- package/prompt-input/styles.scoped.css +152 -39
- package/prompt-input/styles.selectors.js +26 -17
- package/prompt-input/test-classes/styles.css.js +7 -6
- package/prompt-input/test-classes/styles.scoped.css +10 -6
- package/prompt-input/test-classes/styles.selectors.js +7 -6
- package/prompt-input/tokens/use-shortcuts.d.ts +37 -0
- package/prompt-input/tokens/use-shortcuts.d.ts.map +1 -0
- package/prompt-input/tokens/use-shortcuts.js +89 -0
- package/prompt-input/tokens/use-shortcuts.js.map +1 -0
- package/prompt-input/tokens/use-token-mode.d.ts +78 -0
- package/prompt-input/tokens/use-token-mode.d.ts.map +1 -0
- package/prompt-input/tokens/use-token-mode.js +817 -0
- package/prompt-input/tokens/use-token-mode.js.map +1 -0
- package/prompt-input/utils/insert-text-content-editable.d.ts +10 -0
- package/prompt-input/utils/insert-text-content-editable.d.ts.map +1 -0
- package/prompt-input/utils/insert-text-content-editable.js +133 -0
- package/prompt-input/utils/insert-text-content-editable.js.map +1 -0
- package/tabs/styles.css.js +30 -30
- package/tabs/styles.scoped.css +55 -55
- package/tabs/styles.selectors.js +30 -30
- package/test-utils/dom/hotspot/index.js +1 -2
- package/test-utils/dom/hotspot/index.js.map +1 -1
- package/test-utils/dom/prompt-input/index.d.ts +53 -1
- package/test-utils/dom/prompt-input/index.js +121 -13
- package/test-utils/dom/prompt-input/index.js.map +1 -1
- package/test-utils/dom/s3-resource-selector/index.js +2 -3
- package/test-utils/dom/s3-resource-selector/index.js.map +1 -1
- package/test-utils/selectors/hotspot/index.js +1 -2
- package/test-utils/selectors/hotspot/index.js.map +1 -1
- package/test-utils/selectors/prompt-input/index.d.ts +30 -0
- package/test-utils/selectors/prompt-input/index.js +53 -7
- package/test-utils/selectors/prompt-input/index.js.map +1 -1
- package/test-utils/selectors/s3-resource-selector/index.js +2 -3
- package/test-utils/selectors/s3-resource-selector/index.js.map +1 -1
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
import { isHTMLElement } from '../../internal/utils/dom';
|
|
4
|
+
import { ElementType, SPECIAL_CHARS } from './constants';
|
|
5
|
+
import { findAllParagraphs, findElement, generateTokenId, getTokenType, hasOnlyTrailingBR, isReferenceElementType, stripZeroWidthCharacters, } from './dom-utils';
|
|
6
|
+
import { detectTriggersInText, mergeConsecutiveTextTokens } from './token-utils';
|
|
7
|
+
import { isBreakTextToken, isPinnedReferenceToken, isReferenceToken, isTextNode, isTextToken, isTriggerToken, } from './type-guards';
|
|
8
|
+
export function extractTokensFromDOM(element, menus, portalContainers) {
|
|
9
|
+
const paragraphs = findAllParagraphs(element);
|
|
10
|
+
if (paragraphs.length === 0) {
|
|
11
|
+
return [];
|
|
12
|
+
}
|
|
13
|
+
// Special case: single empty paragraph = empty input
|
|
14
|
+
if (paragraphs.length === 1) {
|
|
15
|
+
if (hasOnlyTrailingBR(paragraphs[0])) {
|
|
16
|
+
return [];
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
const allTokens = [];
|
|
20
|
+
paragraphs.forEach((p, pIndex) => {
|
|
21
|
+
const paragraphTokens = extractTokensFromParagraph(p, menus, portalContainers);
|
|
22
|
+
if (pIndex > 0) {
|
|
23
|
+
allTokens.push({ type: 'break', value: SPECIAL_CHARS.NEWLINE });
|
|
24
|
+
}
|
|
25
|
+
allTokens.push(...paragraphTokens);
|
|
26
|
+
});
|
|
27
|
+
return allTokens;
|
|
28
|
+
}
|
|
29
|
+
/** Extracts tokens from a single paragraph element by processing each child node. */
|
|
30
|
+
function extractTokensFromParagraph(p, menus, portalContainers) {
|
|
31
|
+
const tokens = Array.from(p.childNodes).flatMap(node => extractTokensFromNode(node, menus, portalContainers));
|
|
32
|
+
return mergeConsecutiveTextTokens(tokens);
|
|
33
|
+
}
|
|
34
|
+
/** Converts a single DOM node into zero or more tokens. */
|
|
35
|
+
function extractTokensFromNode(node, menus, portalContainers) {
|
|
36
|
+
if (isTextNode(node)) {
|
|
37
|
+
const text = stripZeroWidthCharacters(node.textContent || '');
|
|
38
|
+
return text ? [{ type: 'text', value: text }] : [];
|
|
39
|
+
}
|
|
40
|
+
if (!isHTMLElement(node)) {
|
|
41
|
+
return [];
|
|
42
|
+
}
|
|
43
|
+
if (node.tagName === 'BR') {
|
|
44
|
+
return [];
|
|
45
|
+
}
|
|
46
|
+
const tokenType = getTokenType(node);
|
|
47
|
+
if (tokenType === ElementType.Trigger) {
|
|
48
|
+
return extractTriggerTokens(node, menus);
|
|
49
|
+
}
|
|
50
|
+
if (isReferenceElementType(tokenType)) {
|
|
51
|
+
return extractReferenceToken(node, tokenType, portalContainers);
|
|
52
|
+
}
|
|
53
|
+
// Unknown element — recurse into children
|
|
54
|
+
return Array.from(node.childNodes).flatMap(child => extractTokensFromNode(child, menus, portalContainers));
|
|
55
|
+
}
|
|
56
|
+
/** Extracts trigger tokens from a trigger DOM element, handling nested triggers. */
|
|
57
|
+
function extractTriggerTokens(node, menus = []) {
|
|
58
|
+
const tokens = [];
|
|
59
|
+
const id = node.id;
|
|
60
|
+
const fullText = node.textContent || '';
|
|
61
|
+
// Find the earliest trigger character in the text content
|
|
62
|
+
let triggerCharIndex = -1;
|
|
63
|
+
let triggerChar = '';
|
|
64
|
+
for (const menu of menus) {
|
|
65
|
+
const index = fullText.indexOf(menu.trigger);
|
|
66
|
+
if (index >= 0 && (triggerCharIndex === -1 || index < triggerCharIndex)) {
|
|
67
|
+
triggerCharIndex = index;
|
|
68
|
+
triggerChar = menu.trigger;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
// Text before trigger character (corruption case)
|
|
72
|
+
if (triggerCharIndex > 0) {
|
|
73
|
+
tokens.push({ type: 'text', value: fullText.substring(0, triggerCharIndex) });
|
|
74
|
+
}
|
|
75
|
+
if (triggerCharIndex >= 0) {
|
|
76
|
+
const value = fullText.substring(triggerCharIndex + 1);
|
|
77
|
+
// Check for a nested trigger character in the filter text
|
|
78
|
+
let nestedTriggerIndex = -1;
|
|
79
|
+
let nestedTriggerChar = '';
|
|
80
|
+
for (const menu of menus) {
|
|
81
|
+
if (menu.useAtStart) {
|
|
82
|
+
continue;
|
|
83
|
+
}
|
|
84
|
+
const index = value.indexOf(menu.trigger);
|
|
85
|
+
if (index >= 0 && (nestedTriggerIndex === -1 || index < nestedTriggerIndex)) {
|
|
86
|
+
nestedTriggerIndex = index;
|
|
87
|
+
nestedTriggerChar = menu.trigger;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
if (nestedTriggerIndex === 0) {
|
|
91
|
+
// Adjacent trigger characters — first trigger has empty filter, second is a new trigger
|
|
92
|
+
tokens.push({ type: 'trigger', value: '', triggerChar, id });
|
|
93
|
+
tokens.push({
|
|
94
|
+
type: 'trigger',
|
|
95
|
+
value: value.substring(1),
|
|
96
|
+
triggerChar: nestedTriggerChar,
|
|
97
|
+
id: generateTokenId(),
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
else if (nestedTriggerIndex > 0 && value[nestedTriggerIndex - 1].trim() === '') {
|
|
101
|
+
// Split into first trigger, whitespace, and second trigger
|
|
102
|
+
const firstValue = value.substring(0, nestedTriggerIndex).trim();
|
|
103
|
+
const spaceBefore = value.substring(firstValue.length, nestedTriggerIndex);
|
|
104
|
+
const secondValue = value.substring(nestedTriggerIndex + 1);
|
|
105
|
+
tokens.push({ type: 'trigger', value: firstValue, triggerChar, id });
|
|
106
|
+
if (spaceBefore) {
|
|
107
|
+
tokens.push({ type: 'text', value: spaceBefore });
|
|
108
|
+
}
|
|
109
|
+
tokens.push({ type: 'trigger', value: secondValue, triggerChar: nestedTriggerChar, id: generateTokenId() });
|
|
110
|
+
}
|
|
111
|
+
else {
|
|
112
|
+
tokens.push({ type: 'trigger', value, triggerChar, id });
|
|
113
|
+
}
|
|
114
|
+
// No trigger character found — treat as text
|
|
115
|
+
}
|
|
116
|
+
else if (fullText) {
|
|
117
|
+
tokens.push({ type: 'text', value: fullText });
|
|
118
|
+
}
|
|
119
|
+
return tokens;
|
|
120
|
+
}
|
|
121
|
+
/** Extracts reference and surrounding text tokens from a reference DOM element. */
|
|
122
|
+
function extractReferenceToken(node, tokenType, portalContainers) {
|
|
123
|
+
const tokens = [];
|
|
124
|
+
// Text from cursor-spot-before
|
|
125
|
+
const cursorSpotBefore = findElement(node, { tokenType: ElementType.CaretSpotBefore });
|
|
126
|
+
if (cursorSpotBefore) {
|
|
127
|
+
const beforeText = stripZeroWidthCharacters(cursorSpotBefore.textContent || '');
|
|
128
|
+
if (beforeText) {
|
|
129
|
+
tokens.push({ type: 'text', value: beforeText });
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
const instanceId = node.id || '';
|
|
133
|
+
// Skip orphaned reference wrappers — deleteContents can remove internal
|
|
134
|
+
// elements while leaving the wrapper shell. A reference is orphaned if
|
|
135
|
+
// it has no portal container (source of truth for label/value).
|
|
136
|
+
const portalContainer = portalContainers === null || portalContainers === void 0 ? void 0 : portalContainers.get(instanceId);
|
|
137
|
+
if (!portalContainer) {
|
|
138
|
+
// Salvage any text from remaining caret-spots
|
|
139
|
+
const cursorSpotAfter = findElement(node, { tokenType: ElementType.CaretSpotAfter });
|
|
140
|
+
if (cursorSpotAfter) {
|
|
141
|
+
const afterText = stripZeroWidthCharacters(cursorSpotAfter.textContent || '');
|
|
142
|
+
if (afterText) {
|
|
143
|
+
tokens.push({ type: 'text', value: afterText });
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
return tokens;
|
|
147
|
+
}
|
|
148
|
+
const { value, label, menuId } = portalContainer;
|
|
149
|
+
const token = {
|
|
150
|
+
type: 'reference',
|
|
151
|
+
id: instanceId,
|
|
152
|
+
value,
|
|
153
|
+
label,
|
|
154
|
+
menuId,
|
|
155
|
+
};
|
|
156
|
+
if (tokenType === ElementType.Pinned) {
|
|
157
|
+
token.pinned = true;
|
|
158
|
+
}
|
|
159
|
+
tokens.push(token);
|
|
160
|
+
// Text from cursor-spot-after
|
|
161
|
+
const cursorSpotAfter = findElement(node, { tokenType: ElementType.CaretSpotAfter });
|
|
162
|
+
if (cursorSpotAfter) {
|
|
163
|
+
const afterText = stripZeroWidthCharacters(cursorSpotAfter.textContent || '');
|
|
164
|
+
if (afterText) {
|
|
165
|
+
tokens.push({ type: 'text', value: afterText });
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
return tokens;
|
|
169
|
+
}
|
|
170
|
+
/** Default plain text serialization for tokens. */
|
|
171
|
+
export function getPromptText(tokens) {
|
|
172
|
+
let result = '';
|
|
173
|
+
let prevToken = null;
|
|
174
|
+
for (const token of tokens) {
|
|
175
|
+
if (isBreakTextToken(token)) {
|
|
176
|
+
result += '\n';
|
|
177
|
+
prevToken = token;
|
|
178
|
+
continue;
|
|
179
|
+
}
|
|
180
|
+
let segment;
|
|
181
|
+
if (isTriggerToken(token)) {
|
|
182
|
+
segment = token.triggerChar + token.value;
|
|
183
|
+
}
|
|
184
|
+
else if (isReferenceToken(token)) {
|
|
185
|
+
segment = token.label || token.value;
|
|
186
|
+
}
|
|
187
|
+
else {
|
|
188
|
+
segment = token.value;
|
|
189
|
+
}
|
|
190
|
+
if (segment.length === 0) {
|
|
191
|
+
continue;
|
|
192
|
+
}
|
|
193
|
+
// Insert a space between a reference and its neighbor when neither side has whitespace
|
|
194
|
+
const needsSpace = result.length > 0 &&
|
|
195
|
+
!result.endsWith(' ') &&
|
|
196
|
+
!result.endsWith('\n') &&
|
|
197
|
+
!segment.startsWith(' ') &&
|
|
198
|
+
(isReferenceToken(token) || (prevToken && isReferenceToken(prevToken)));
|
|
199
|
+
if (needsSpace) {
|
|
200
|
+
result += ' ';
|
|
201
|
+
}
|
|
202
|
+
result += segment;
|
|
203
|
+
prevToken = token;
|
|
204
|
+
}
|
|
205
|
+
return result;
|
|
206
|
+
}
|
|
207
|
+
export function findLastPinnedTokenIndex(tokens) {
|
|
208
|
+
for (let i = tokens.length - 1; i >= 0; i--) {
|
|
209
|
+
if (isPinnedReferenceToken(tokens[i])) {
|
|
210
|
+
return i;
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
return -1;
|
|
214
|
+
}
|
|
215
|
+
/** Scans text tokens for trigger characters and converts them to trigger tokens. */
|
|
216
|
+
export function detectTriggersInTokens(tokens, menus, onTriggerDetected, cancelledIds) {
|
|
217
|
+
const result = [];
|
|
218
|
+
for (let i = 0; i < tokens.length; i++) {
|
|
219
|
+
const token = tokens[i];
|
|
220
|
+
// Skip cancelled triggers — don't re-parse their adjacent text
|
|
221
|
+
if (isTriggerToken(token) && (cancelledIds === null || cancelledIds === void 0 ? void 0 : cancelledIds.has(token.id))) {
|
|
222
|
+
result.push(token);
|
|
223
|
+
continue;
|
|
224
|
+
}
|
|
225
|
+
// Collapse empty trigger + adjacent text back into a text token for re-parsing.
|
|
226
|
+
// Don't fire onTriggerDetected — the trigger already exists.
|
|
227
|
+
if (isTriggerToken(token) && token.value === '' && i !== tokens.length - 1) {
|
|
228
|
+
const next = tokens[i + 1];
|
|
229
|
+
if (isTextToken(next) && next.value.length > 0 && !next.value.startsWith(' ')) {
|
|
230
|
+
const detected = detectTriggersInText(token.triggerChar + next.value, menus, result);
|
|
231
|
+
const reusedTrigger = detected.find(isTriggerToken);
|
|
232
|
+
if (reusedTrigger && token.id) {
|
|
233
|
+
reusedTrigger.id = token.id;
|
|
234
|
+
}
|
|
235
|
+
result.push(...detected);
|
|
236
|
+
i++;
|
|
237
|
+
continue;
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
// Merge non-empty trigger + adjacent text when the separator was removed.
|
|
241
|
+
// Don't fire onTriggerDetected — the trigger already exists.
|
|
242
|
+
if (isTriggerToken(token) && token.value.length > 0 && i !== tokens.length - 1) {
|
|
243
|
+
const next = tokens[i + 1];
|
|
244
|
+
if (isTextToken(next) && next.value.length > 0 && !next.value.startsWith(' ')) {
|
|
245
|
+
const combined = token.triggerChar + token.value + next.value;
|
|
246
|
+
const detected = detectTriggersInText(combined, menus, result);
|
|
247
|
+
const reusedTrigger = detected.find(isTriggerToken);
|
|
248
|
+
if (reusedTrigger && token.id) {
|
|
249
|
+
reusedTrigger.id = token.id;
|
|
250
|
+
}
|
|
251
|
+
result.push(...detected);
|
|
252
|
+
i++;
|
|
253
|
+
continue;
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
if (isTextToken(token)) {
|
|
257
|
+
result.push(...detectTriggersInText(token.value, menus, result, onTriggerDetected, cancelledIds));
|
|
258
|
+
}
|
|
259
|
+
else {
|
|
260
|
+
result.push(token);
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
return result;
|
|
264
|
+
}
|
|
265
|
+
export function processTokens(tokens, config, options, onTriggerDetected, existingCancelledIds) {
|
|
266
|
+
let result = [...tokens];
|
|
267
|
+
const cancelledIds = new Set(existingCancelledIds);
|
|
268
|
+
if (options.detectTriggers && config.menus) {
|
|
269
|
+
result = detectTriggersInTokens(result, config.menus, onTriggerDetected, cancelledIds);
|
|
270
|
+
}
|
|
271
|
+
return { tokens: result, cancelledIds };
|
|
272
|
+
}
|
|
273
|
+
//# sourceMappingURL=token-operations.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"token-operations.js","sourceRoot":"","sources":["../../../../src/prompt-input/core/token-operations.ts"],"names":[],"mappings":"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","sourcesContent":["// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n// SPDX-License-Identifier: Apache-2.0\n\nimport { isHTMLElement } from '../../internal/utils/dom';\nimport type { PromptInputProps } from '../interfaces';\nimport { ElementType, SPECIAL_CHARS } from './constants';\nimport {\n findAllParagraphs,\n findElement,\n generateTokenId,\n getTokenType,\n hasOnlyTrailingBR,\n isReferenceElementType,\n stripZeroWidthCharacters,\n} from './dom-utils';\nimport { PortalContainer } from './token-renderer';\nimport { detectTriggersInText, mergeConsecutiveTextTokens } from './token-utils';\nimport {\n isBreakTextToken,\n isPinnedReferenceToken,\n isReferenceToken,\n isTextNode,\n isTextToken,\n isTriggerToken,\n} from './type-guards';\n\nexport type UpdateSource = 'user-input' | 'external' | 'menu-selection' | 'internal';\n\nexport interface ShortcutsConfig {\n menus?: readonly PromptInputProps.MenuDefinition[];\n tokensToText?: (tokens: readonly PromptInputProps.InputToken[]) => string;\n}\n\nexport function extractTokensFromDOM(\n element: HTMLElement,\n menus?: readonly PromptInputProps.MenuDefinition[],\n portalContainers?: Map<string, PortalContainer>\n): PromptInputProps.InputToken[] {\n const paragraphs = findAllParagraphs(element);\n\n if (paragraphs.length === 0) {\n return [];\n }\n\n // Special case: single empty paragraph = empty input\n if (paragraphs.length === 1) {\n if (hasOnlyTrailingBR(paragraphs[0])) {\n return [];\n }\n }\n\n const allTokens: PromptInputProps.InputToken[] = [];\n\n paragraphs.forEach((p, pIndex) => {\n const paragraphTokens = extractTokensFromParagraph(p, menus, portalContainers);\n\n if (pIndex > 0) {\n allTokens.push({ type: 'break', value: SPECIAL_CHARS.NEWLINE });\n }\n\n allTokens.push(...paragraphTokens);\n });\n\n return allTokens;\n}\n\n/** Extracts tokens from a single paragraph element by processing each child node. */\nfunction extractTokensFromParagraph(\n p: HTMLElement,\n menus?: readonly PromptInputProps.MenuDefinition[],\n portalContainers?: Map<string, PortalContainer>\n): PromptInputProps.InputToken[] {\n const tokens = Array.from(p.childNodes).flatMap(node => extractTokensFromNode(node, menus, portalContainers));\n return mergeConsecutiveTextTokens(tokens);\n}\n\n/** Converts a single DOM node into zero or more tokens. */\nfunction extractTokensFromNode(\n node: Node,\n menus?: readonly PromptInputProps.MenuDefinition[],\n portalContainers?: Map<string, PortalContainer>\n): PromptInputProps.InputToken[] {\n if (isTextNode(node)) {\n const text = stripZeroWidthCharacters(node.textContent || '');\n return text ? [{ type: 'text', value: text }] : [];\n }\n\n if (!isHTMLElement(node)) {\n return [];\n }\n\n if (node.tagName === 'BR') {\n return [];\n }\n\n const tokenType = getTokenType(node);\n\n if (tokenType === ElementType.Trigger) {\n return extractTriggerTokens(node, menus);\n }\n\n if (isReferenceElementType(tokenType)) {\n return extractReferenceToken(node, tokenType, portalContainers);\n }\n\n // Unknown element — recurse into children\n return Array.from(node.childNodes).flatMap(child => extractTokensFromNode(child, menus, portalContainers));\n}\n\n/** Extracts trigger tokens from a trigger DOM element, handling nested triggers. */\nfunction extractTriggerTokens(\n node: HTMLElement,\n menus: readonly PromptInputProps.MenuDefinition[] = []\n): PromptInputProps.InputToken[] {\n const tokens: PromptInputProps.InputToken[] = [];\n const id = node.id;\n const fullText = node.textContent || '';\n\n // Find the earliest trigger character in the text content\n let triggerCharIndex = -1;\n let triggerChar = '';\n\n for (const menu of menus) {\n const index = fullText.indexOf(menu.trigger);\n if (index >= 0 && (triggerCharIndex === -1 || index < triggerCharIndex)) {\n triggerCharIndex = index;\n triggerChar = menu.trigger;\n }\n }\n\n // Text before trigger character (corruption case)\n if (triggerCharIndex > 0) {\n tokens.push({ type: 'text', value: fullText.substring(0, triggerCharIndex) });\n }\n\n if (triggerCharIndex >= 0) {\n const value = fullText.substring(triggerCharIndex + 1);\n\n // Check for a nested trigger character in the filter text\n let nestedTriggerIndex = -1;\n let nestedTriggerChar = '';\n\n for (const menu of menus) {\n if (menu.useAtStart) {\n continue;\n }\n const index = value.indexOf(menu.trigger);\n if (index >= 0 && (nestedTriggerIndex === -1 || index < nestedTriggerIndex)) {\n nestedTriggerIndex = index;\n nestedTriggerChar = menu.trigger;\n }\n }\n\n if (nestedTriggerIndex === 0) {\n // Adjacent trigger characters — first trigger has empty filter, second is a new trigger\n tokens.push({ type: 'trigger', value: '', triggerChar, id });\n tokens.push({\n type: 'trigger',\n value: value.substring(1),\n triggerChar: nestedTriggerChar,\n id: generateTokenId(),\n });\n } else if (nestedTriggerIndex > 0 && value[nestedTriggerIndex - 1].trim() === '') {\n // Split into first trigger, whitespace, and second trigger\n const firstValue = value.substring(0, nestedTriggerIndex).trim();\n const spaceBefore = value.substring(firstValue.length, nestedTriggerIndex);\n const secondValue = value.substring(nestedTriggerIndex + 1);\n\n tokens.push({ type: 'trigger', value: firstValue, triggerChar, id });\n if (spaceBefore) {\n tokens.push({ type: 'text', value: spaceBefore });\n }\n tokens.push({ type: 'trigger', value: secondValue, triggerChar: nestedTriggerChar, id: generateTokenId() });\n } else {\n tokens.push({ type: 'trigger', value, triggerChar, id });\n }\n // No trigger character found — treat as text\n } else if (fullText) {\n tokens.push({ type: 'text', value: fullText });\n }\n\n return tokens;\n}\n\n/** Extracts reference and surrounding text tokens from a reference DOM element. */\nfunction extractReferenceToken(\n node: HTMLElement,\n tokenType: string | null,\n portalContainers?: Map<string, PortalContainer>\n): PromptInputProps.InputToken[] {\n const tokens: PromptInputProps.InputToken[] = [];\n\n // Text from cursor-spot-before\n const cursorSpotBefore = findElement(node, { tokenType: ElementType.CaretSpotBefore });\n if (cursorSpotBefore) {\n const beforeText = stripZeroWidthCharacters(cursorSpotBefore.textContent || '');\n if (beforeText) {\n tokens.push({ type: 'text', value: beforeText });\n }\n }\n\n const instanceId = node.id || '';\n\n // Skip orphaned reference wrappers — deleteContents can remove internal\n // elements while leaving the wrapper shell. A reference is orphaned if\n // it has no portal container (source of truth for label/value).\n const portalContainer = portalContainers?.get(instanceId);\n if (!portalContainer) {\n // Salvage any text from remaining caret-spots\n const cursorSpotAfter = findElement(node, { tokenType: ElementType.CaretSpotAfter });\n if (cursorSpotAfter) {\n const afterText = stripZeroWidthCharacters(cursorSpotAfter.textContent || '');\n if (afterText) {\n tokens.push({ type: 'text', value: afterText });\n }\n }\n return tokens;\n }\n\n const { value, label, menuId } = portalContainer;\n\n const token: PromptInputProps.ReferenceToken = {\n type: 'reference',\n id: instanceId,\n value,\n label,\n menuId,\n };\n if (tokenType === ElementType.Pinned) {\n token.pinned = true;\n }\n\n tokens.push(token);\n\n // Text from cursor-spot-after\n const cursorSpotAfter = findElement(node, { tokenType: ElementType.CaretSpotAfter });\n if (cursorSpotAfter) {\n const afterText = stripZeroWidthCharacters(cursorSpotAfter.textContent || '');\n if (afterText) {\n tokens.push({ type: 'text', value: afterText });\n }\n }\n\n return tokens;\n}\n\n/** Default plain text serialization for tokens. */\nexport function getPromptText(tokens: readonly PromptInputProps.InputToken[]): string {\n let result = '';\n let prevToken: PromptInputProps.InputToken | null = null;\n\n for (const token of tokens) {\n if (isBreakTextToken(token)) {\n result += '\\n';\n prevToken = token;\n continue;\n }\n\n let segment: string;\n if (isTriggerToken(token)) {\n segment = token.triggerChar + token.value;\n } else if (isReferenceToken(token)) {\n segment = token.label || token.value;\n } else {\n segment = (token as PromptInputProps.TextToken).value;\n }\n\n if (segment.length === 0) {\n continue;\n }\n\n // Insert a space between a reference and its neighbor when neither side has whitespace\n const needsSpace =\n result.length > 0 &&\n !result.endsWith(' ') &&\n !result.endsWith('\\n') &&\n !segment.startsWith(' ') &&\n (isReferenceToken(token) || (prevToken && isReferenceToken(prevToken)));\n\n if (needsSpace) {\n result += ' ';\n }\n\n result += segment;\n prevToken = token;\n }\n\n return result;\n}\n\nexport function findLastPinnedTokenIndex(tokens: readonly PromptInputProps.InputToken[]): number {\n for (let i = tokens.length - 1; i >= 0; i--) {\n if (isPinnedReferenceToken(tokens[i])) {\n return i;\n }\n }\n return -1;\n}\n\n/** Scans text tokens for trigger characters and converts them to trigger tokens. */\nexport function detectTriggersInTokens(\n tokens: readonly PromptInputProps.InputToken[],\n menus: readonly PromptInputProps.MenuDefinition[],\n onTriggerDetected?: (detail: PromptInputProps.TriggerDetectedDetail) => boolean,\n cancelledIds?: Set<string>\n): PromptInputProps.InputToken[] {\n const result: PromptInputProps.InputToken[] = [];\n\n for (let i = 0; i < tokens.length; i++) {\n const token = tokens[i];\n\n // Skip cancelled triggers — don't re-parse their adjacent text\n if (isTriggerToken(token) && cancelledIds?.has(token.id)) {\n result.push(token);\n continue;\n }\n\n // Collapse empty trigger + adjacent text back into a text token for re-parsing.\n // Don't fire onTriggerDetected — the trigger already exists.\n if (isTriggerToken(token) && token.value === '' && i !== tokens.length - 1) {\n const next = tokens[i + 1];\n if (isTextToken(next) && next.value.length > 0 && !next.value.startsWith(' ')) {\n const detected = detectTriggersInText(token.triggerChar + next.value, menus, result);\n const reusedTrigger = detected.find(isTriggerToken);\n if (reusedTrigger && token.id) {\n reusedTrigger.id = token.id;\n }\n result.push(...detected);\n i++;\n continue;\n }\n }\n\n // Merge non-empty trigger + adjacent text when the separator was removed.\n // Don't fire onTriggerDetected — the trigger already exists.\n if (isTriggerToken(token) && token.value.length > 0 && i !== tokens.length - 1) {\n const next = tokens[i + 1];\n if (isTextToken(next) && next.value.length > 0 && !next.value.startsWith(' ')) {\n const combined = token.triggerChar + token.value + next.value;\n const detected = detectTriggersInText(combined, menus, result);\n const reusedTrigger = detected.find(isTriggerToken);\n if (reusedTrigger && token.id) {\n reusedTrigger.id = token.id;\n }\n result.push(...detected);\n i++;\n continue;\n }\n }\n\n if (isTextToken(token)) {\n result.push(...detectTriggersInText(token.value, menus, result, onTriggerDetected, cancelledIds));\n } else {\n result.push(token);\n }\n }\n\n return result;\n}\n\nexport interface ProcessTokensResult {\n tokens: PromptInputProps.InputToken[];\n cancelledIds: Set<string>;\n}\n\nexport function processTokens(\n tokens: readonly PromptInputProps.InputToken[],\n config: ShortcutsConfig,\n options: {\n source: UpdateSource;\n detectTriggers?: boolean;\n },\n onTriggerDetected?: (detail: PromptInputProps.TriggerDetectedDetail) => boolean,\n existingCancelledIds?: Set<string>\n): ProcessTokensResult {\n let result = [...tokens];\n const cancelledIds = new Set<string>(existingCancelledIds);\n\n if (options.detectTriggers && config.menus) {\n result = detectTriggersInTokens(result, config.menus, onTriggerDetected, cancelledIds);\n }\n\n return { tokens: result, cancelledIds };\n}\n"]}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { PromptInputProps } from '../interfaces';
|
|
2
|
+
/** Whether the current React version supports token mode (React 18+). */
|
|
3
|
+
export declare const supportsTokenMode: boolean;
|
|
4
|
+
/** A portal target — the DOM element where a reference token's React content is rendered via createPortal. */
|
|
5
|
+
export interface PortalContainer {
|
|
6
|
+
/** Unique ID matching the token */
|
|
7
|
+
id: string;
|
|
8
|
+
/** The DOM element to render the portal into */
|
|
9
|
+
element: HTMLElement;
|
|
10
|
+
/** Label for the token */
|
|
11
|
+
label: string;
|
|
12
|
+
/** Value for the token */
|
|
13
|
+
value: string;
|
|
14
|
+
/** Menu ID the token was selected from */
|
|
15
|
+
menuId?: string;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Renders tokens into a contentEditable element using direct DOM manipulation.
|
|
19
|
+
* Reference tokens are NOT rendered here — instead, their DOM containers are registered
|
|
20
|
+
* in portalContainers for the parent component to render via ReactDOM.createPortal.
|
|
21
|
+
*/
|
|
22
|
+
export declare function renderTokensToDOM(tokens: readonly PromptInputProps.InputToken[], targetElement: HTMLElement, portalContainers: Map<string, PortalContainer>, existingTriggers?: Map<string, HTMLElement>, cancelledTriggerIds?: Set<string>): {
|
|
23
|
+
newTriggerElement: HTMLElement | null;
|
|
24
|
+
lastReferenceWithCaretSpots: HTMLElement | null;
|
|
25
|
+
triggerElements: Map<string, HTMLElement>;
|
|
26
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"token-renderer.d.ts","sourceRoot":"","sources":["../../../../src/prompt-input/core/token-renderer.tsx"],"names":[],"mappings":"AA+BA,OAAO,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AAajD,yEAAyE;AACzE,eAAO,MAAM,iBAAiB,SAA+B,CAAC;AAE9D,8GAA8G;AAC9G,MAAM,WAAW,eAAe;IAC9B,mCAAmC;IACnC,EAAE,EAAE,MAAM,CAAC;IACX,gDAAgD;IAChD,OAAO,EAAE,WAAW,CAAC;IACrB,0BAA0B;IAC1B,KAAK,EAAE,MAAM,CAAC;IACd,0BAA0B;IAC1B,KAAK,EAAE,MAAM,CAAC;IACd,0CAA0C;IAC1C,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAgFD;;;;GAIG;AACH,wBAAgB,iBAAiB,CAC/B,MAAM,EAAE,SAAS,gBAAgB,CAAC,UAAU,EAAE,EAC9C,aAAa,EAAE,WAAW,EAC1B,gBAAgB,EAAE,GAAG,CAAC,MAAM,EAAE,eAAe,CAAC,EAC9C,gBAAgB,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,WAAW,CAAC,EAC3C,mBAAmB,CAAC,EAAE,GAAG,CAAC,MAAM,CAAC,GAChC;IACD,iBAAiB,EAAE,WAAW,GAAG,IAAI,CAAC;IACtC,2BAA2B,EAAE,WAAW,GAAG,IAAI,CAAC;IAChD,eAAe,EAAE,GAAG,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC;CAC3C,CAoJA"}
|
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
//
|
|
4
|
+
// Token Renderer — Direct DOM manipulation for contentEditable
|
|
5
|
+
//
|
|
6
|
+
// This module renders tokens (text, triggers, references) into a contentEditable element
|
|
7
|
+
// using direct DOM operations instead of React's declarative rendering. This approach is
|
|
8
|
+
// necessary because:
|
|
9
|
+
//
|
|
10
|
+
// 1. React's reconciliation conflicts with contentEditable. When the user types, the browser
|
|
11
|
+
// mutates the DOM directly. React expects to own the DOM and would overwrite user input
|
|
12
|
+
// on the next render, causing cursor jumps and lost keystrokes.
|
|
13
|
+
//
|
|
14
|
+
// 2. Reference tokens are atomic inline elements (rendered via React portals into <span>
|
|
15
|
+
// containers) surrounded by caret spots (zero-width characters). This structure requires
|
|
16
|
+
// precise DOM control that React's diffing algorithm cannot provide — it would merge
|
|
17
|
+
// adjacent text nodes, remove "empty" spans, or reorder elements unpredictably.
|
|
18
|
+
//
|
|
19
|
+
// 3. Cursor positioning depends on exact DOM node identity. React may replace a text node
|
|
20
|
+
// with an equivalent one during reconciliation, which resets the browser's caret position.
|
|
21
|
+
// By managing DOM nodes directly, we preserve node identity across renders.
|
|
22
|
+
//
|
|
23
|
+
// Reference tokens are rendered via React portals (ReactDOM.createPortal) from the parent
|
|
24
|
+
// component, keeping them in the same React tree for shared context and lifecycle. The
|
|
25
|
+
// token-renderer creates the DOM containers; the parent renders content into them.
|
|
26
|
+
//
|
|
27
|
+
import clsx from 'clsx';
|
|
28
|
+
import { getReactMajorVersion } from '../../internal/utils/react-version';
|
|
29
|
+
import { ElementType, SPECIAL_CHARS } from './constants';
|
|
30
|
+
import { createParagraph, createTrailingBreak, findAllParagraphs, getTokenType, isReferenceElementType, } from './dom-utils';
|
|
31
|
+
import { isBreakTextToken, isReferenceToken, isTextToken, isTriggerToken } from './type-guards';
|
|
32
|
+
import styles from '../styles.css.js';
|
|
33
|
+
/** Whether the current React version supports token mode (React 18+). */
|
|
34
|
+
export const supportsTokenMode = getReactMajorVersion() >= 18;
|
|
35
|
+
function groupTokensIntoParagraphs(tokens) {
|
|
36
|
+
if (tokens.length === 0) {
|
|
37
|
+
return [{ tokens: [] }];
|
|
38
|
+
}
|
|
39
|
+
const paragraphs = [];
|
|
40
|
+
let currentParagraph = [];
|
|
41
|
+
for (let i = 0; i < tokens.length; i++) {
|
|
42
|
+
const token = tokens[i];
|
|
43
|
+
if (isBreakTextToken(token)) {
|
|
44
|
+
const isLeadingBreak = currentParagraph.length === 0;
|
|
45
|
+
if (isLeadingBreak) {
|
|
46
|
+
paragraphs.push({ tokens: [] });
|
|
47
|
+
}
|
|
48
|
+
else {
|
|
49
|
+
paragraphs.push({ tokens: currentParagraph });
|
|
50
|
+
currentParagraph = [];
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
else {
|
|
54
|
+
currentParagraph.push(token);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
paragraphs.push({ tokens: currentParagraph });
|
|
58
|
+
return paragraphs;
|
|
59
|
+
}
|
|
60
|
+
/** Creates an invisible span with a zero-width character to provide a valid caret position next to reference tokens. */
|
|
61
|
+
function createCaretSpot(type, ownerDoc) {
|
|
62
|
+
const caretSpot = ownerDoc.createElement('span');
|
|
63
|
+
caretSpot.setAttribute('data-type', type);
|
|
64
|
+
caretSpot.setAttribute('contenteditable', 'true');
|
|
65
|
+
caretSpot.setAttribute('aria-hidden', 'true');
|
|
66
|
+
caretSpot.appendChild(ownerDoc.createTextNode(SPECIAL_CHARS.ZERO_WIDTH_CHARACTER));
|
|
67
|
+
return caretSpot;
|
|
68
|
+
}
|
|
69
|
+
function createReferenceWithCaretSpots(token, portalContainers, ownerDoc) {
|
|
70
|
+
const wrapper = ownerDoc.createElement('span');
|
|
71
|
+
wrapper.className = styles['reference-wrapper'];
|
|
72
|
+
wrapper.setAttribute('data-type', token.pinned ? ElementType.Pinned : ElementType.Reference);
|
|
73
|
+
const instanceId = token.id;
|
|
74
|
+
wrapper.id = instanceId;
|
|
75
|
+
const caretSpotBefore = createCaretSpot(ElementType.CaretSpotBefore, ownerDoc);
|
|
76
|
+
const element = ownerDoc.createElement('span');
|
|
77
|
+
element.className = styles['token-container'];
|
|
78
|
+
element.setAttribute('contenteditable', 'false');
|
|
79
|
+
// Register the container for portal rendering by the parent component.
|
|
80
|
+
portalContainers.set(instanceId, {
|
|
81
|
+
id: instanceId,
|
|
82
|
+
element,
|
|
83
|
+
label: token.label,
|
|
84
|
+
value: token.value,
|
|
85
|
+
menuId: token.menuId,
|
|
86
|
+
});
|
|
87
|
+
const caretSpotAfter = createCaretSpot(ElementType.CaretSpotAfter, ownerDoc);
|
|
88
|
+
wrapper.appendChild(caretSpotBefore);
|
|
89
|
+
wrapper.appendChild(element);
|
|
90
|
+
wrapper.appendChild(caretSpotAfter);
|
|
91
|
+
return wrapper;
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Renders tokens into a contentEditable element using direct DOM manipulation.
|
|
95
|
+
* Reference tokens are NOT rendered here — instead, their DOM containers are registered
|
|
96
|
+
* in portalContainers for the parent component to render via ReactDOM.createPortal.
|
|
97
|
+
*/
|
|
98
|
+
export function renderTokensToDOM(tokens, targetElement, portalContainers, existingTriggers, cancelledTriggerIds) {
|
|
99
|
+
var _a, _b;
|
|
100
|
+
// Preserve existing portal containers that are still in the DOM.
|
|
101
|
+
const existingContainers = new Map();
|
|
102
|
+
portalContainers.forEach((container, instanceId) => {
|
|
103
|
+
if (container.element.isConnected && targetElement.contains(container.element)) {
|
|
104
|
+
existingContainers.set(instanceId, container);
|
|
105
|
+
}
|
|
106
|
+
});
|
|
107
|
+
portalContainers.clear();
|
|
108
|
+
// Use the provided trigger map or start empty for the initial render.
|
|
109
|
+
const reusableTriggers = new Map(existingTriggers !== null && existingTriggers !== void 0 ? existingTriggers : []);
|
|
110
|
+
const existingParagraphs = findAllParagraphs(targetElement);
|
|
111
|
+
const paragraphGroups = groupTokensIntoParagraphs(tokens);
|
|
112
|
+
const ownerDoc = (_a = targetElement.ownerDocument) !== null && _a !== void 0 ? _a : document;
|
|
113
|
+
let newTriggerElement = null;
|
|
114
|
+
let lastReferenceWithCaretSpots = null;
|
|
115
|
+
const triggerElements = new Map();
|
|
116
|
+
for (let pIndex = 0; pIndex < paragraphGroups.length; pIndex++) {
|
|
117
|
+
const paragraphGroup = paragraphGroups[pIndex];
|
|
118
|
+
let p;
|
|
119
|
+
if (pIndex < existingParagraphs.length) {
|
|
120
|
+
p = existingParagraphs[pIndex];
|
|
121
|
+
}
|
|
122
|
+
else {
|
|
123
|
+
p = createParagraph();
|
|
124
|
+
targetElement.appendChild(p);
|
|
125
|
+
}
|
|
126
|
+
const newNodes = [];
|
|
127
|
+
for (let i = 0; i < paragraphGroup.tokens.length; i++) {
|
|
128
|
+
const token = paragraphGroup.tokens[i];
|
|
129
|
+
if (isTextToken(token)) {
|
|
130
|
+
if (token.value) {
|
|
131
|
+
newNodes.push(ownerDoc.createTextNode(token.value));
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
else if (isTriggerToken(token)) {
|
|
135
|
+
let span;
|
|
136
|
+
const triggerId = token.id;
|
|
137
|
+
const isNewTrigger = !reusableTriggers.has(triggerId);
|
|
138
|
+
const hasFilterText = token.value.length > 0;
|
|
139
|
+
const isCancelled = (_b = cancelledTriggerIds === null || cancelledTriggerIds === void 0 ? void 0 : cancelledTriggerIds.has(triggerId)) !== null && _b !== void 0 ? _b : false;
|
|
140
|
+
if (reusableTriggers.has(triggerId)) {
|
|
141
|
+
span = reusableTriggers.get(triggerId);
|
|
142
|
+
reusableTriggers.delete(triggerId);
|
|
143
|
+
}
|
|
144
|
+
else {
|
|
145
|
+
span = ownerDoc.createElement('span');
|
|
146
|
+
span.setAttribute('data-type', ElementType.Trigger);
|
|
147
|
+
span.id = triggerId;
|
|
148
|
+
span.setAttribute('data-id', triggerId);
|
|
149
|
+
}
|
|
150
|
+
const classes = clsx(styles['trigger-base'], hasFilterText && styles['trigger-token']);
|
|
151
|
+
span.className = classes;
|
|
152
|
+
span.textContent = token.triggerChar + token.value;
|
|
153
|
+
newNodes.push(span);
|
|
154
|
+
triggerElements.set(triggerId, span);
|
|
155
|
+
if (isNewTrigger && !isCancelled) {
|
|
156
|
+
newTriggerElement = span;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
else if (isReferenceToken(token)) {
|
|
160
|
+
// Check if we can reuse an existing portal container.
|
|
161
|
+
const existingContainer = token.id ? existingContainers.get(token.id) : undefined;
|
|
162
|
+
if (existingContainer) {
|
|
163
|
+
const existingWrapper = existingContainer.element.parentElement;
|
|
164
|
+
if (existingWrapper) {
|
|
165
|
+
const tokenType = getTokenType(existingWrapper);
|
|
166
|
+
if (isReferenceElementType(tokenType)) {
|
|
167
|
+
// Reuse existing container — update props in case they changed.
|
|
168
|
+
existingContainer.label = token.label;
|
|
169
|
+
existingContainer.value = token.value;
|
|
170
|
+
existingContainer.menuId = token.menuId;
|
|
171
|
+
portalContainers.set(token.id, existingContainer);
|
|
172
|
+
newNodes.push(existingWrapper);
|
|
173
|
+
existingContainers.delete(token.id);
|
|
174
|
+
lastReferenceWithCaretSpots = existingWrapper;
|
|
175
|
+
continue;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
const wrapper = createReferenceWithCaretSpots(token, portalContainers, ownerDoc);
|
|
180
|
+
newNodes.push(wrapper);
|
|
181
|
+
lastReferenceWithCaretSpots = wrapper;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
if (newNodes.length === 0) {
|
|
185
|
+
newNodes.push(createTrailingBreak());
|
|
186
|
+
}
|
|
187
|
+
const existingNodes = Array.from(p.childNodes);
|
|
188
|
+
let nodesMatch = existingNodes.length === newNodes.length;
|
|
189
|
+
if (nodesMatch) {
|
|
190
|
+
for (let i = 0; i < newNodes.length; i++) {
|
|
191
|
+
if (existingNodes[i] !== newNodes[i]) {
|
|
192
|
+
nodesMatch = false;
|
|
193
|
+
break;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
if (nodesMatch) {
|
|
198
|
+
continue;
|
|
199
|
+
}
|
|
200
|
+
for (let i = newNodes.length; i < existingNodes.length; i++) {
|
|
201
|
+
existingNodes[i].remove();
|
|
202
|
+
}
|
|
203
|
+
for (let i = 0; i < newNodes.length; i++) {
|
|
204
|
+
const newNode = newNodes[i];
|
|
205
|
+
const existingNode = existingNodes[i];
|
|
206
|
+
if (existingNode === newNode) {
|
|
207
|
+
continue;
|
|
208
|
+
}
|
|
209
|
+
if (existingNode && newNodes.includes(existingNode)) {
|
|
210
|
+
if (i < p.childNodes.length) {
|
|
211
|
+
p.insertBefore(newNode, p.childNodes[i]);
|
|
212
|
+
}
|
|
213
|
+
else {
|
|
214
|
+
p.appendChild(newNode);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
else if (existingNode) {
|
|
218
|
+
p.replaceChild(newNode, existingNode);
|
|
219
|
+
}
|
|
220
|
+
else {
|
|
221
|
+
p.appendChild(newNode);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
while (targetElement.children.length > paragraphGroups.length) {
|
|
226
|
+
targetElement.removeChild(targetElement.lastChild);
|
|
227
|
+
}
|
|
228
|
+
return { newTriggerElement, lastReferenceWithCaretSpots, triggerElements };
|
|
229
|
+
}
|
|
230
|
+
//# sourceMappingURL=token-renderer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"token-renderer.js","sourceRoot":"","sources":["../../../../src/prompt-input/core/token-renderer.tsx"],"names":[],"mappings":"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","sourcesContent":["// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n// SPDX-License-Identifier: Apache-2.0\n\n//\n// Token Renderer — Direct DOM manipulation for contentEditable\n//\n// This module renders tokens (text, triggers, references) into a contentEditable element\n// using direct DOM operations instead of React's declarative rendering. This approach is\n// necessary because:\n//\n// 1. React's reconciliation conflicts with contentEditable. When the user types, the browser\n// mutates the DOM directly. React expects to own the DOM and would overwrite user input\n// on the next render, causing cursor jumps and lost keystrokes.\n//\n// 2. Reference tokens are atomic inline elements (rendered via React portals into <span>\n// containers) surrounded by caret spots (zero-width characters). This structure requires\n// precise DOM control that React's diffing algorithm cannot provide — it would merge\n// adjacent text nodes, remove \"empty\" spans, or reorder elements unpredictably.\n//\n// 3. Cursor positioning depends on exact DOM node identity. React may replace a text node\n// with an equivalent one during reconciliation, which resets the browser's caret position.\n// By managing DOM nodes directly, we preserve node identity across renders.\n//\n// Reference tokens are rendered via React portals (ReactDOM.createPortal) from the parent\n// component, keeping them in the same React tree for shared context and lifecycle. The\n// token-renderer creates the DOM containers; the parent renders content into them.\n//\n\nimport clsx from 'clsx';\n\nimport { getReactMajorVersion } from '../../internal/utils/react-version';\nimport { PromptInputProps } from '../interfaces';\nimport { ElementType, SPECIAL_CHARS } from './constants';\nimport {\n createParagraph,\n createTrailingBreak,\n findAllParagraphs,\n getTokenType,\n isReferenceElementType,\n} from './dom-utils';\nimport { isBreakTextToken, isReferenceToken, isTextToken, isTriggerToken } from './type-guards';\n\nimport styles from '../styles.css.js';\n\n/** Whether the current React version supports token mode (React 18+). */\nexport const supportsTokenMode = getReactMajorVersion() >= 18;\n\n/** A portal target — the DOM element where a reference token's React content is rendered via createPortal. */\nexport interface PortalContainer {\n /** Unique ID matching the token */\n id: string;\n /** The DOM element to render the portal into */\n element: HTMLElement;\n /** Label for the token */\n label: string;\n /** Value for the token */\n value: string;\n /** Menu ID the token was selected from */\n menuId?: string;\n}\n\ninterface ParagraphGroup {\n tokens: PromptInputProps.InputToken[];\n}\n\nfunction groupTokensIntoParagraphs(tokens: readonly PromptInputProps.InputToken[]): ParagraphGroup[] {\n if (tokens.length === 0) {\n return [{ tokens: [] }];\n }\n\n const paragraphs: ParagraphGroup[] = [];\n let currentParagraph: PromptInputProps.InputToken[] = [];\n\n for (let i = 0; i < tokens.length; i++) {\n const token = tokens[i];\n\n if (isBreakTextToken(token)) {\n const isLeadingBreak = currentParagraph.length === 0;\n\n if (isLeadingBreak) {\n paragraphs.push({ tokens: [] });\n } else {\n paragraphs.push({ tokens: currentParagraph });\n currentParagraph = [];\n }\n } else {\n currentParagraph.push(token);\n }\n }\n\n paragraphs.push({ tokens: currentParagraph });\n\n return paragraphs;\n}\n\n/** Creates an invisible span with a zero-width character to provide a valid caret position next to reference tokens. */\nfunction createCaretSpot(type: string, ownerDoc: Document): HTMLSpanElement {\n const caretSpot = ownerDoc.createElement('span');\n caretSpot.setAttribute('data-type', type);\n caretSpot.setAttribute('contenteditable', 'true');\n caretSpot.setAttribute('aria-hidden', 'true');\n caretSpot.appendChild(ownerDoc.createTextNode(SPECIAL_CHARS.ZERO_WIDTH_CHARACTER));\n return caretSpot;\n}\n\nfunction createReferenceWithCaretSpots(\n token: PromptInputProps.ReferenceToken,\n portalContainers: Map<string, PortalContainer>,\n ownerDoc: Document\n): HTMLSpanElement {\n const wrapper = ownerDoc.createElement('span');\n wrapper.className = styles['reference-wrapper'];\n wrapper.setAttribute('data-type', token.pinned ? ElementType.Pinned : ElementType.Reference);\n const instanceId = token.id;\n wrapper.id = instanceId;\n\n const caretSpotBefore = createCaretSpot(ElementType.CaretSpotBefore, ownerDoc);\n const element = ownerDoc.createElement('span');\n element.className = styles['token-container'];\n element.setAttribute('contenteditable', 'false');\n\n // Register the container for portal rendering by the parent component.\n portalContainers.set(instanceId, {\n id: instanceId,\n element,\n label: token.label,\n value: token.value,\n menuId: token.menuId,\n });\n\n const caretSpotAfter = createCaretSpot(ElementType.CaretSpotAfter, ownerDoc);\n\n wrapper.appendChild(caretSpotBefore);\n wrapper.appendChild(element);\n wrapper.appendChild(caretSpotAfter);\n\n return wrapper;\n}\n\n/**\n * Renders tokens into a contentEditable element using direct DOM manipulation.\n * Reference tokens are NOT rendered here — instead, their DOM containers are registered\n * in portalContainers for the parent component to render via ReactDOM.createPortal.\n */\nexport function renderTokensToDOM(\n tokens: readonly PromptInputProps.InputToken[],\n targetElement: HTMLElement,\n portalContainers: Map<string, PortalContainer>,\n existingTriggers?: Map<string, HTMLElement>,\n cancelledTriggerIds?: Set<string>\n): {\n newTriggerElement: HTMLElement | null;\n lastReferenceWithCaretSpots: HTMLElement | null;\n triggerElements: Map<string, HTMLElement>;\n} {\n // Preserve existing portal containers that are still in the DOM.\n const existingContainers = new Map<string, PortalContainer>();\n portalContainers.forEach((container, instanceId) => {\n if (container.element.isConnected && targetElement.contains(container.element)) {\n existingContainers.set(instanceId, container);\n }\n });\n portalContainers.clear();\n\n // Use the provided trigger map or start empty for the initial render.\n const reusableTriggers = new Map(existingTriggers ?? []);\n\n const existingParagraphs = findAllParagraphs(targetElement);\n const paragraphGroups = groupTokensIntoParagraphs(tokens);\n const ownerDoc = targetElement.ownerDocument ?? document;\n\n let newTriggerElement: HTMLElement | null = null;\n let lastReferenceWithCaretSpots: HTMLElement | null = null;\n const triggerElements = new Map<string, HTMLElement>();\n\n for (let pIndex = 0; pIndex < paragraphGroups.length; pIndex++) {\n const paragraphGroup = paragraphGroups[pIndex];\n let p: HTMLParagraphElement;\n\n if (pIndex < existingParagraphs.length) {\n p = existingParagraphs[pIndex];\n } else {\n p = createParagraph();\n targetElement.appendChild(p);\n }\n\n const newNodes: Node[] = [];\n\n for (let i = 0; i < paragraphGroup.tokens.length; i++) {\n const token = paragraphGroup.tokens[i];\n\n if (isTextToken(token)) {\n if (token.value) {\n newNodes.push(ownerDoc.createTextNode(token.value));\n }\n } else if (isTriggerToken(token)) {\n let span: HTMLElement;\n const triggerId = token.id;\n const isNewTrigger = !reusableTriggers.has(triggerId);\n const hasFilterText = token.value.length > 0;\n const isCancelled = cancelledTriggerIds?.has(triggerId) ?? false;\n\n if (reusableTriggers.has(triggerId)) {\n span = reusableTriggers.get(triggerId)!;\n reusableTriggers.delete(triggerId);\n } else {\n span = ownerDoc.createElement('span');\n span.setAttribute('data-type', ElementType.Trigger);\n span.id = triggerId;\n span.setAttribute('data-id', triggerId);\n }\n\n const classes = clsx(styles['trigger-base'], hasFilterText && styles['trigger-token']);\n\n span.className = classes;\n span.textContent = token.triggerChar + token.value;\n\n newNodes.push(span);\n triggerElements.set(triggerId, span);\n\n if (isNewTrigger && !isCancelled) {\n newTriggerElement = span;\n }\n } else if (isReferenceToken(token)) {\n // Check if we can reuse an existing portal container.\n const existingContainer = token.id ? existingContainers.get(token.id) : undefined;\n if (existingContainer) {\n const existingWrapper = existingContainer.element.parentElement;\n if (existingWrapper) {\n const tokenType = getTokenType(existingWrapper);\n if (isReferenceElementType(tokenType)) {\n // Reuse existing container — update props in case they changed.\n existingContainer.label = token.label;\n existingContainer.value = token.value;\n existingContainer.menuId = token.menuId;\n portalContainers.set(token.id!, existingContainer);\n\n newNodes.push(existingWrapper);\n existingContainers.delete(token.id!);\n lastReferenceWithCaretSpots = existingWrapper;\n continue;\n }\n }\n }\n\n const wrapper = createReferenceWithCaretSpots(token, portalContainers, ownerDoc);\n newNodes.push(wrapper);\n lastReferenceWithCaretSpots = wrapper;\n }\n }\n\n if (newNodes.length === 0) {\n newNodes.push(createTrailingBreak());\n }\n\n const existingNodes = Array.from(p.childNodes);\n\n let nodesMatch = existingNodes.length === newNodes.length;\n if (nodesMatch) {\n for (let i = 0; i < newNodes.length; i++) {\n if (existingNodes[i] !== newNodes[i]) {\n nodesMatch = false;\n break;\n }\n }\n }\n\n if (nodesMatch) {\n continue;\n }\n\n for (let i = newNodes.length; i < existingNodes.length; i++) {\n existingNodes[i].remove();\n }\n\n for (let i = 0; i < newNodes.length; i++) {\n const newNode = newNodes[i];\n const existingNode = existingNodes[i];\n\n if (existingNode === newNode) {\n continue;\n }\n\n if (existingNode && newNodes.includes(existingNode)) {\n if (i < p.childNodes.length) {\n p.insertBefore(newNode, p.childNodes[i]);\n } else {\n p.appendChild(newNode);\n }\n } else if (existingNode) {\n p.replaceChild(newNode, existingNode);\n } else {\n p.appendChild(newNode);\n }\n }\n }\n\n while (targetElement.children.length > paragraphGroups.length) {\n targetElement.removeChild(targetElement.lastChild!);\n }\n\n return { newTriggerElement, lastReferenceWithCaretSpots, triggerElements };\n}\n"]}
|