@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 @@
|
|
|
1
|
+
{"version":3,"file":"textarea-mode.js","sourceRoot":"","sources":["../../../../src/prompt-input/components/textarea-mode.tsx"],"names":[],"mappings":"AAAA,qEAAqE;AACrE,sCAAsC;AAEtC,OAAO,KAAK,MAAM,OAAO,CAAC;AAE1B,OAAO,oBAAoB,MAAM,6CAA6C,CAAC;AAU/E,MAAM,CAAC,OAAO,UAAU,YAAY,CAAC,EACnC,WAAW,EACX,SAAS,EACT,kBAAkB,EAClB,wBAAwB,GACN;IAClB,OAAO,CACL,oBAAC,oBAAoB,OACf,kBAAkB,EACtB,GAAG,EAAC,UAAU,EACd,aAAa,EAAC,aAAa,EAC3B,gBAAgB,EAAE,wBAAwB,EAC1C,GAAG,EAAE,WAAW,EAChB,EAAE,EAAE,SAAS,GACb,CACH,CAAC;AACJ,CAAC","sourcesContent":["// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n// SPDX-License-Identifier: Apache-2.0\n\nimport React from 'react';\n\nimport WithNativeAttributes from '../../internal/utils/with-native-attributes';\n\n/** Props for when using standard PromptInput. */\ninterface TextareaModeProps {\n textareaRef: React.RefObject<HTMLTextAreaElement>;\n controlId?: string;\n textareaAttributes: React.TextareaHTMLAttributes<HTMLTextAreaElement>;\n nativeTextareaAttributes?: Record<string, any>;\n}\n\nexport default function TextareaMode({\n textareaRef,\n controlId,\n textareaAttributes,\n nativeTextareaAttributes,\n}: TextareaModeProps) {\n return (\n <WithNativeAttributes\n {...textareaAttributes}\n tag=\"textarea\"\n componentName=\"PromptInput\"\n nativeAttributes={nativeTextareaAttributes}\n ref={textareaRef}\n id={controlId}\n />\n );\n}\n"]}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { DropdownStatusResult } from '../../internal/components/dropdown-status';
|
|
3
|
+
import { MenuItemsHandlers, MenuItemsState } from '../core/menu-state';
|
|
4
|
+
import { PromptInputProps } from '../interfaces';
|
|
5
|
+
/** Props for the token-mode contentEditable input and its associated menu dropdown. */
|
|
6
|
+
interface TokenModeProps {
|
|
7
|
+
/** Ref to the contentEditable div */
|
|
8
|
+
editableElementRef: React.RefObject<HTMLDivElement>;
|
|
9
|
+
/** Ref to the active trigger element, used to anchor the dropdown */
|
|
10
|
+
triggerWrapperRef: React.MutableRefObject<HTMLElement | null>;
|
|
11
|
+
controlId?: string;
|
|
12
|
+
menuListId: string;
|
|
13
|
+
menuFooterControlId: string;
|
|
14
|
+
highlightedMenuOptionId?: string;
|
|
15
|
+
/** When set, renders a hidden input for native form submission */
|
|
16
|
+
name?: string;
|
|
17
|
+
/** Plain text representation of the current tokens */
|
|
18
|
+
plainTextValue: string;
|
|
19
|
+
menuIsOpen: boolean;
|
|
20
|
+
/** True once the trigger element is mounted and ready for dropdown positioning */
|
|
21
|
+
triggerWrapperReady: boolean;
|
|
22
|
+
shouldRenderMenuDropdown: boolean;
|
|
23
|
+
activeMenu: PromptInputProps.MenuDefinition | null;
|
|
24
|
+
activeTriggerToken: PromptInputProps.TriggerToken | null;
|
|
25
|
+
menuFilterText: string;
|
|
26
|
+
menuItemsState: MenuItemsState | null;
|
|
27
|
+
menuItemsHandlers: MenuItemsHandlers | null;
|
|
28
|
+
menuDropdownStatus: DropdownStatusResult | null;
|
|
29
|
+
handleInput: () => void;
|
|
30
|
+
handleLoadMore: () => void;
|
|
31
|
+
/** Spread onto the contentEditable div — includes aria attrs, className, and event handlers */
|
|
32
|
+
editableElementAttributes: React.HTMLAttributes<HTMLDivElement> & {
|
|
33
|
+
'data-placeholder'?: string;
|
|
34
|
+
};
|
|
35
|
+
i18nStrings?: PromptInputProps['i18nStrings'];
|
|
36
|
+
maxMenuHeight?: number;
|
|
37
|
+
}
|
|
38
|
+
export default function TokenMode({
|
|
39
|
+
editableElementRef,
|
|
40
|
+
triggerWrapperRef,
|
|
41
|
+
controlId,
|
|
42
|
+
menuListId,
|
|
43
|
+
menuFooterControlId,
|
|
44
|
+
highlightedMenuOptionId,
|
|
45
|
+
name,
|
|
46
|
+
plainTextValue,
|
|
47
|
+
menuIsOpen,
|
|
48
|
+
triggerWrapperReady,
|
|
49
|
+
shouldRenderMenuDropdown,
|
|
50
|
+
activeMenu,
|
|
51
|
+
activeTriggerToken,
|
|
52
|
+
menuFilterText,
|
|
53
|
+
menuItemsState,
|
|
54
|
+
menuItemsHandlers,
|
|
55
|
+
menuDropdownStatus,
|
|
56
|
+
maxMenuHeight,
|
|
57
|
+
handleInput,
|
|
58
|
+
handleLoadMore,
|
|
59
|
+
editableElementAttributes
|
|
60
|
+
}: TokenModeProps): JSX.Element;
|
|
61
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"token-mode.d.ts","sourceRoot":"","sources":["../../../../src/prompt-input/components/token-mode.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAK,MAAM,OAAO,CAAC;AAK1B,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AACjF,OAAO,EAAE,iBAAiB,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AACvE,OAAO,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AAMjD,uFAAuF;AACvF,UAAU,cAAc;IACtB,qCAAqC;IACrC,kBAAkB,EAAE,KAAK,CAAC,SAAS,CAAC,cAAc,CAAC,CAAC;IACpD,qEAAqE;IACrE,iBAAiB,EAAE,KAAK,CAAC,gBAAgB,CAAC,WAAW,GAAG,IAAI,CAAC,CAAC;IAE9D,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,mBAAmB,EAAE,MAAM,CAAC;IAC5B,uBAAuB,CAAC,EAAE,MAAM,CAAC;IAEjC,kEAAkE;IAClE,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,sDAAsD;IACtD,cAAc,EAAE,MAAM,CAAC;IACvB,UAAU,EAAE,OAAO,CAAC;IACpB,kFAAkF;IAClF,mBAAmB,EAAE,OAAO,CAAC;IAC7B,wBAAwB,EAAE,OAAO,CAAC;IAElC,UAAU,EAAE,gBAAgB,CAAC,cAAc,GAAG,IAAI,CAAC;IACnD,kBAAkB,EAAE,gBAAgB,CAAC,YAAY,GAAG,IAAI,CAAC;IACzD,cAAc,EAAE,MAAM,CAAC;IACvB,cAAc,EAAE,cAAc,GAAG,IAAI,CAAC;IACtC,iBAAiB,EAAE,iBAAiB,GAAG,IAAI,CAAC;IAC5C,kBAAkB,EAAE,oBAAoB,GAAG,IAAI,CAAC;IAEhD,WAAW,EAAE,MAAM,IAAI,CAAC;IACxB,cAAc,EAAE,MAAM,IAAI,CAAC;IAE3B,+FAA+F;IAC/F,yBAAyB,EAAE,KAAK,CAAC,cAAc,CAAC,cAAc,CAAC,GAAG;QAChE,kBAAkB,CAAC,EAAE,MAAM,CAAC;KAC7B,CAAC;IAEF,WAAW,CAAC,EAAE,gBAAgB,CAAC,aAAa,CAAC,CAAC;IAE9C,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAID,MAAM,CAAC,OAAO,UAAU,SAAS,CAAC,EAChC,kBAAkB,EAClB,iBAAiB,EACjB,SAAS,EACT,UAAU,EACV,mBAAmB,EACnB,uBAAuB,EACvB,IAAI,EACJ,cAAc,EACd,UAAU,EACV,mBAAmB,EACnB,wBAAwB,EACxB,UAAU,EACV,kBAAkB,EAClB,cAAc,EACd,cAAc,EACd,iBAAiB,EACjB,kBAAkB,EAClB,aAAa,EACb,WAAW,EACX,cAAc,EACd,yBAAyB,GAC1B,EAAE,cAAc,eAkGhB"}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
import React from 'react';
|
|
4
|
+
import clsx from 'clsx';
|
|
5
|
+
import Dropdown from '../../dropdown/internal';
|
|
6
|
+
import DropdownFooter from '../../internal/components/dropdown-footer';
|
|
7
|
+
import MenuDropdown from './menu-dropdown';
|
|
8
|
+
import styles from '../styles.css.js';
|
|
9
|
+
import testutilStyles from '../test-classes/styles.css.js';
|
|
10
|
+
const MENU_MIN_WIDTH = 300;
|
|
11
|
+
export default function TokenMode({ editableElementRef, triggerWrapperRef, controlId, menuListId, menuFooterControlId, highlightedMenuOptionId, name, plainTextValue, menuIsOpen, triggerWrapperReady, shouldRenderMenuDropdown, activeMenu, activeTriggerToken, menuFilterText, menuItemsState, menuItemsHandlers, menuDropdownStatus, maxMenuHeight, handleInput, handleLoadMore, editableElementAttributes, }) {
|
|
12
|
+
var _a;
|
|
13
|
+
return (React.createElement(React.Fragment, null,
|
|
14
|
+
name && React.createElement("input", { type: "hidden", name: name, value: plainTextValue }),
|
|
15
|
+
React.createElement("div", { className: styles['editable-wrapper'] },
|
|
16
|
+
React.createElement("div", { id: controlId, ref: editableElementRef, role: "textbox", "aria-multiline": "true", "aria-haspopup": "listbox", "aria-expanded": menuIsOpen && shouldRenderMenuDropdown, contentEditable: !editableElementAttributes['aria-disabled'] && !editableElementAttributes['aria-readonly']
|
|
17
|
+
? 'true'
|
|
18
|
+
: 'false',
|
|
19
|
+
// React warns when children of a contentEditable element are managed by React.
|
|
20
|
+
// We suppress this because we intentionally manage the DOM directly via token-renderer
|
|
21
|
+
// to avoid React's reconciliation conflicting with browser-native editing behavior.
|
|
22
|
+
suppressContentEditableWarning: true, "aria-controls": menuIsOpen ? menuListId : undefined, "aria-owns": menuIsOpen ? menuListId : undefined, "aria-activedescendant": highlightedMenuOptionId, onInput: handleInput, ...editableElementAttributes, className: clsx(editableElementAttributes.className, testutilStyles['content-editable'], styles['editable-element']) }),
|
|
23
|
+
React.createElement(Dropdown, { minWidth: MENU_MIN_WIDTH, maxHeight: maxMenuHeight, expandToViewport: true, open: !!(shouldRenderMenuDropdown &&
|
|
24
|
+
triggerWrapperReady &&
|
|
25
|
+
menuIsOpen &&
|
|
26
|
+
menuItemsState &&
|
|
27
|
+
(menuItemsState.items.length > 0 || (menuDropdownStatus === null || menuDropdownStatus === void 0 ? void 0 : menuDropdownStatus.content))), trigger: null, triggerRef: triggerWrapperRef, triggerId: activeTriggerToken === null || activeTriggerToken === void 0 ? void 0 : activeTriggerToken.id, contentKey: triggerWrapperReady
|
|
28
|
+
? `trigger-${activeTriggerToken === null || activeTriggerToken === void 0 ? void 0 : activeTriggerToken.id}-${activeTriggerToken === null || activeTriggerToken === void 0 ? void 0 : activeTriggerToken.triggerChar}-${menuItemsState ? menuItemsState.items.length > 0 : false}`
|
|
29
|
+
: undefined,
|
|
30
|
+
/* istanbul ignore next -- integ test: src/prompt-input/__integ__/prompt-input-token-mode.test.ts > "clicking a menu option inserts reference and retains focus" */
|
|
31
|
+
onMouseDown: event => {
|
|
32
|
+
// Prevent default to stop the dropdown from stealing focus from the contentEditable.
|
|
33
|
+
// Without this, clicking a menu option would blur the input before the selection handler fires.
|
|
34
|
+
event.preventDefault();
|
|
35
|
+
}, footer: (menuDropdownStatus === null || menuDropdownStatus === void 0 ? void 0 : menuDropdownStatus.isSticky) && menuDropdownStatus.content ? (React.createElement(DropdownFooter, { id: menuFooterControlId, content: menuDropdownStatus.content, hasItems: menuItemsState ? menuItemsState.items.length >= 1 : false })) : null, content: React.createElement(React.Fragment, null, shouldRenderMenuDropdown && menuItemsState && menuItemsHandlers && activeMenu && (React.createElement(MenuDropdown, { menu: activeMenu, statusType: (_a = activeMenu.statusType) !== null && _a !== void 0 ? _a : 'finished', menuItemsState: menuItemsState, menuItemsHandlers: menuItemsHandlers, highlightedOptionId: highlightedMenuOptionId, highlightText: menuFilterText, listId: menuListId, controlId: controlId !== null && controlId !== void 0 ? controlId : '', handleLoadMore: handleLoadMore, hasDropdownStatus: (menuDropdownStatus === null || menuDropdownStatus === void 0 ? void 0 : menuDropdownStatus.content) !== null, listBottom: !(menuDropdownStatus === null || menuDropdownStatus === void 0 ? void 0 : menuDropdownStatus.isSticky) && (menuDropdownStatus === null || menuDropdownStatus === void 0 ? void 0 : menuDropdownStatus.content) ? (React.createElement(DropdownFooter, { content: menuDropdownStatus.content, id: menuFooterControlId })) : null, ariaDescribedby: (menuDropdownStatus === null || menuDropdownStatus === void 0 ? void 0 : menuDropdownStatus.content) ? menuFooterControlId : undefined }))) }))));
|
|
36
|
+
}
|
|
37
|
+
//# sourceMappingURL=token-mode.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"token-mode.js","sourceRoot":"","sources":["../../../../src/prompt-input/components/token-mode.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\nimport React from 'react';\nimport clsx from 'clsx';\n\nimport Dropdown from '../../dropdown/internal';\nimport DropdownFooter from '../../internal/components/dropdown-footer';\nimport { DropdownStatusResult } from '../../internal/components/dropdown-status';\nimport { MenuItemsHandlers, MenuItemsState } from '../core/menu-state';\nimport { PromptInputProps } from '../interfaces';\nimport MenuDropdown from './menu-dropdown';\n\nimport styles from '../styles.css.js';\nimport testutilStyles from '../test-classes/styles.css.js';\n\n/** Props for the token-mode contentEditable input and its associated menu dropdown. */\ninterface TokenModeProps {\n /** Ref to the contentEditable div */\n editableElementRef: React.RefObject<HTMLDivElement>;\n /** Ref to the active trigger element, used to anchor the dropdown */\n triggerWrapperRef: React.MutableRefObject<HTMLElement | null>;\n\n controlId?: string;\n menuListId: string;\n menuFooterControlId: string;\n highlightedMenuOptionId?: string;\n\n /** When set, renders a hidden input for native form submission */\n name?: string;\n /** Plain text representation of the current tokens */\n plainTextValue: string;\n menuIsOpen: boolean;\n /** True once the trigger element is mounted and ready for dropdown positioning */\n triggerWrapperReady: boolean;\n shouldRenderMenuDropdown: boolean;\n\n activeMenu: PromptInputProps.MenuDefinition | null;\n activeTriggerToken: PromptInputProps.TriggerToken | null;\n menuFilterText: string;\n menuItemsState: MenuItemsState | null;\n menuItemsHandlers: MenuItemsHandlers | null;\n menuDropdownStatus: DropdownStatusResult | null;\n\n handleInput: () => void;\n handleLoadMore: () => void;\n\n /** Spread onto the contentEditable div — includes aria attrs, className, and event handlers */\n editableElementAttributes: React.HTMLAttributes<HTMLDivElement> & {\n 'data-placeholder'?: string;\n };\n\n i18nStrings?: PromptInputProps['i18nStrings'];\n\n maxMenuHeight?: number;\n}\n\nconst MENU_MIN_WIDTH = 300;\n\nexport default function TokenMode({\n editableElementRef,\n triggerWrapperRef,\n controlId,\n menuListId,\n menuFooterControlId,\n highlightedMenuOptionId,\n name,\n plainTextValue,\n menuIsOpen,\n triggerWrapperReady,\n shouldRenderMenuDropdown,\n activeMenu,\n activeTriggerToken,\n menuFilterText,\n menuItemsState,\n menuItemsHandlers,\n menuDropdownStatus,\n maxMenuHeight,\n handleInput,\n handleLoadMore,\n editableElementAttributes,\n}: TokenModeProps) {\n return (\n <>\n {/* Hidden input enables native form submission with the plain text value when a name is provided */}\n {name && <input type=\"hidden\" name={name} value={plainTextValue} />}\n <div className={styles['editable-wrapper']}>\n <div\n id={controlId}\n ref={editableElementRef}\n role=\"textbox\"\n aria-multiline=\"true\"\n aria-haspopup=\"listbox\"\n aria-expanded={menuIsOpen && shouldRenderMenuDropdown}\n contentEditable={\n !editableElementAttributes['aria-disabled'] && !editableElementAttributes['aria-readonly']\n ? 'true'\n : 'false'\n }\n // React warns when children of a contentEditable element are managed by React.\n // We suppress this because we intentionally manage the DOM directly via token-renderer\n // to avoid React's reconciliation conflicting with browser-native editing behavior.\n suppressContentEditableWarning={true}\n aria-controls={menuIsOpen ? menuListId : undefined}\n // aria-owns needed for Safari+VoiceOver to announce activedescendant content\n aria-owns={menuIsOpen ? menuListId : undefined}\n aria-activedescendant={highlightedMenuOptionId}\n onInput={handleInput}\n {...editableElementAttributes}\n className={clsx(\n editableElementAttributes.className,\n testutilStyles['content-editable'],\n styles['editable-element']\n )}\n />\n <Dropdown\n minWidth={MENU_MIN_WIDTH}\n maxHeight={maxMenuHeight}\n expandToViewport={true}\n open={\n !!(\n shouldRenderMenuDropdown &&\n triggerWrapperReady &&\n menuIsOpen &&\n menuItemsState &&\n (menuItemsState.items.length > 0 || menuDropdownStatus?.content)\n )\n }\n trigger={null}\n triggerRef={triggerWrapperRef}\n triggerId={activeTriggerToken?.id}\n contentKey={\n triggerWrapperReady\n ? `trigger-${activeTriggerToken?.id}-${activeTriggerToken?.triggerChar}-${menuItemsState ? menuItemsState.items.length > 0 : false}`\n : undefined\n }\n /* istanbul ignore next -- integ test: src/prompt-input/__integ__/prompt-input-token-mode.test.ts > \"clicking a menu option inserts reference and retains focus\" */\n onMouseDown={event => {\n // Prevent default to stop the dropdown from stealing focus from the contentEditable.\n // Without this, clicking a menu option would blur the input before the selection handler fires.\n event.preventDefault();\n }}\n footer={\n menuDropdownStatus?.isSticky && menuDropdownStatus.content ? (\n <DropdownFooter\n id={menuFooterControlId}\n content={menuDropdownStatus.content}\n hasItems={menuItemsState ? menuItemsState.items.length >= 1 : false}\n />\n ) : null\n }\n content={\n <>\n {shouldRenderMenuDropdown && menuItemsState && menuItemsHandlers && activeMenu && (\n <MenuDropdown\n menu={activeMenu}\n statusType={activeMenu.statusType ?? 'finished'}\n menuItemsState={menuItemsState}\n menuItemsHandlers={menuItemsHandlers}\n highlightedOptionId={highlightedMenuOptionId}\n highlightText={menuFilterText}\n listId={menuListId}\n controlId={controlId ?? ''}\n handleLoadMore={handleLoadMore}\n hasDropdownStatus={menuDropdownStatus?.content !== null}\n listBottom={\n !menuDropdownStatus?.isSticky && menuDropdownStatus?.content ? (\n <DropdownFooter content={menuDropdownStatus.content} id={menuFooterControlId} />\n ) : null\n }\n ariaDescribedby={menuDropdownStatus?.content ? menuFooterControlId : undefined}\n />\n )}\n </>\n }\n />\n </div>\n </>\n );\n}\n"]}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { PromptInputProps } from '../interfaces';
|
|
2
|
+
/** Returns the Selection from the element's owning window, supporting iframe contexts. */
|
|
3
|
+
export declare function getOwnerSelection(element: Node): Selection | null;
|
|
4
|
+
/** Logical lengths for each token type, used for cursor position calculations. */
|
|
5
|
+
export declare const TOKEN_LENGTHS: {
|
|
6
|
+
readonly REFERENCE: 1;
|
|
7
|
+
readonly LINE_BREAK: 1;
|
|
8
|
+
readonly trigger: (filterText: string) => number;
|
|
9
|
+
readonly text: (content: string) => number;
|
|
10
|
+
};
|
|
11
|
+
/** Calculates the logical cursor position after a given token index. */
|
|
12
|
+
export declare function calculateTokenPosition(tokens: readonly PromptInputProps.InputToken[], upToIndex: number): number;
|
|
13
|
+
/** Calculates the total logical length of all tokens. */
|
|
14
|
+
export declare function calculateTotalTokenLength(tokens: readonly PromptInputProps.InputToken[]): number;
|
|
15
|
+
/** A resolved position within the DOM: a node and an offset into it. */
|
|
16
|
+
interface DOMLocation {
|
|
17
|
+
node: Node;
|
|
18
|
+
offset: number;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Manages caret positioning within a contentEditable element.
|
|
22
|
+
* Translates between logical token positions and DOM Range/Selection API.
|
|
23
|
+
*/
|
|
24
|
+
export declare class CaretController {
|
|
25
|
+
private element;
|
|
26
|
+
private state;
|
|
27
|
+
constructor(element: HTMLElement);
|
|
28
|
+
private get ownerDoc();
|
|
29
|
+
/**
|
|
30
|
+
* Creates a DOM Range from resolved start/end locations and applies it to the given selection.
|
|
31
|
+
* Returns the created Range for further use (e.g. scroll-into-view checks).
|
|
32
|
+
*/
|
|
33
|
+
applyRange(ownerDocument: Document, selection: Selection, startLocation: DOMLocation, endLocation?: DOMLocation): Range;
|
|
34
|
+
/** Returns the logical length of a DOM node based on its token type. */
|
|
35
|
+
private getNodeLength;
|
|
36
|
+
/** Returns the current logical caret position from the DOM selection. */
|
|
37
|
+
getPosition(): number;
|
|
38
|
+
/** Finds the trigger element at the current caret position, if any. */
|
|
39
|
+
findActiveTrigger(): HTMLElement | null;
|
|
40
|
+
/**
|
|
41
|
+
* Sets the caret to a logical position, or creates a selection range if end is provided.
|
|
42
|
+
* Handles smart positioning around atomic reference tokens and scrolls into view.
|
|
43
|
+
* @param start logical start position
|
|
44
|
+
* @param end optional logical end position for range selection
|
|
45
|
+
*/
|
|
46
|
+
setPosition(start: number, end?: number): void;
|
|
47
|
+
/** Captures the current caret/selection state for later restoration. */
|
|
48
|
+
capture(): void;
|
|
49
|
+
/** Returns the captured caret start position, or null if no valid capture exists. */
|
|
50
|
+
getSavedPosition(): number | null;
|
|
51
|
+
/** Restores the caret to the previously captured state. */
|
|
52
|
+
restore(offset?: number): void;
|
|
53
|
+
/** Overrides the captured state so the next restore() positions to a calculated location.
|
|
54
|
+
* Currently used only in tests — consider removing if no production use case emerges.
|
|
55
|
+
*/
|
|
56
|
+
setCapturedPosition(start: number, end?: number): void;
|
|
57
|
+
/** Selects all content in the element. */
|
|
58
|
+
selectAll(): void;
|
|
59
|
+
/** Positions the caret at the end of a text node. */
|
|
60
|
+
positionAfterText(textNode: Text): void;
|
|
61
|
+
/** Moves the caret forward by a logical offset. */
|
|
62
|
+
moveForward(offset: number): void;
|
|
63
|
+
/** Moves the caret backward by a logical offset, clamped to 0. */
|
|
64
|
+
moveBackward(offset: number): void;
|
|
65
|
+
private calculatePositionFromRange;
|
|
66
|
+
private findDOMLocation;
|
|
67
|
+
/** Resolves a DOM location for a specific child node at the given offset within a paragraph. */
|
|
68
|
+
private resolveChildLocation;
|
|
69
|
+
private findLocationInParagraph;
|
|
70
|
+
private countParagraphContent;
|
|
71
|
+
private countUpToCursor;
|
|
72
|
+
}
|
|
73
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"caret-controller.d.ts","sourceRoot":"","sources":["../../../../src/prompt-input/core/caret-controller.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AAYjD,0FAA0F;AAC1F,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,IAAI,GAAG,SAAS,GAAG,IAAI,CAEjE;AAED,kFAAkF;AAClF,eAAO,MAAM,aAAa;;;mCAGF,MAAM;6BACZ,MAAM;CACd,CAAC;AAEX,wEAAwE;AACxE,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,SAAS,gBAAgB,CAAC,UAAU,EAAE,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,CAehH;AAED,yDAAyD;AACzD,wBAAgB,yBAAyB,CAAC,MAAM,EAAE,SAAS,gBAAgB,CAAC,UAAU,EAAE,GAAG,MAAM,CAEhG;AAQD,wEAAwE;AACxE,UAAU,WAAW;IACnB,IAAI,EAAE,IAAI,CAAC;IACX,MAAM,EAAE,MAAM,CAAC;CAChB;AAED;;;GAGG;AACH,qBAAa,eAAe;IAC1B,OAAO,CAAC,OAAO,CAAc;IAC7B,OAAO,CAAC,KAAK,CAAa;gBAEd,OAAO,EAAE,WAAW;IAKhC,OAAO,KAAK,QAAQ,GAEnB;IAED;;;OAGG;IACH,UAAU,CACR,aAAa,EAAE,QAAQ,EACvB,SAAS,EAAE,SAAS,EACpB,aAAa,EAAE,WAAW,EAC1B,WAAW,CAAC,EAAE,WAAW,GACxB,KAAK;IAeR,wEAAwE;IACxE,OAAO,CAAC,aAAa;IAYrB,yEAAyE;IACzE,WAAW,IAAI,MAAM;IAcrB,uEAAuE;IACvE,iBAAiB,IAAI,WAAW,GAAG,IAAI;IAuCvC;;;;;OAKG;IACH,WAAW,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,MAAM,GAAG,IAAI;IA0B9C,wEAAwE;IACxE,OAAO,IAAI,IAAI;IAmBf,qFAAqF;IACrF,gBAAgB,IAAI,MAAM,GAAG,IAAI;IAIjC,2DAA2D;IAC3D,OAAO,CAAC,MAAM,SAAI,GAAG,IAAI;IAQzB;;OAEG;IACH,mBAAmB,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,MAAM,GAAG,IAAI;IAItD,0CAA0C;IAC1C,SAAS,IAAI,IAAI;IA6BjB,qDAAqD;IACrD,iBAAiB,CAAC,QAAQ,EAAE,IAAI,GAAG,IAAI;IAYvC,mDAAmD;IACnD,WAAW,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI;IAKjC,kEAAkE;IAClE,YAAY,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI;IAKlC,OAAO,CAAC,0BAA0B;IA4BlC,OAAO,CAAC,eAAe;IA8BvB,gGAAgG;IAChG,OAAO,CAAC,oBAAoB;IAqC5B,OAAO,CAAC,uBAAuB;IAmB/B,OAAO,CAAC,qBAAqB;IAQ7B,OAAO,CAAC,eAAe;CA+DxB"}
|
|
@@ -0,0 +1,396 @@
|
|
|
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 } from './constants';
|
|
5
|
+
import { findAllParagraphs, findElement, getTokenType, isEmptyState, isReferenceElementType, stripZeroWidthCharacters, } from './dom-utils';
|
|
6
|
+
import { isBreakTextToken, isTextNode, isTextToken, isTriggerToken } from './type-guards';
|
|
7
|
+
/** Returns the Selection from the element's owning window, supporting iframe contexts. */
|
|
8
|
+
export function getOwnerSelection(element) {
|
|
9
|
+
var _a, _b;
|
|
10
|
+
return ((_b = (_a = element.ownerDocument) === null || _a === void 0 ? void 0 : _a.defaultView) !== null && _b !== void 0 ? _b : window).getSelection();
|
|
11
|
+
}
|
|
12
|
+
/** Logical lengths for each token type, used for cursor position calculations. */
|
|
13
|
+
export const TOKEN_LENGTHS = {
|
|
14
|
+
REFERENCE: 1,
|
|
15
|
+
LINE_BREAK: 1,
|
|
16
|
+
trigger: (filterText) => 1 + filterText.length,
|
|
17
|
+
text: (content) => content.length,
|
|
18
|
+
};
|
|
19
|
+
/** Calculates the logical cursor position after a given token index. */
|
|
20
|
+
export function calculateTokenPosition(tokens, upToIndex) {
|
|
21
|
+
let pos = 0;
|
|
22
|
+
for (let i = 0; i <= upToIndex && i < tokens.length; i++) {
|
|
23
|
+
const token = tokens[i];
|
|
24
|
+
if (isTextToken(token)) {
|
|
25
|
+
pos += TOKEN_LENGTHS.text(token.value);
|
|
26
|
+
}
|
|
27
|
+
else if (isBreakTextToken(token)) {
|
|
28
|
+
pos += TOKEN_LENGTHS.LINE_BREAK;
|
|
29
|
+
}
|
|
30
|
+
else if (isTriggerToken(token)) {
|
|
31
|
+
pos += TOKEN_LENGTHS.trigger(token.value);
|
|
32
|
+
}
|
|
33
|
+
else {
|
|
34
|
+
pos += TOKEN_LENGTHS.REFERENCE;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
return pos;
|
|
38
|
+
}
|
|
39
|
+
/** Calculates the total logical length of all tokens. */
|
|
40
|
+
export function calculateTotalTokenLength(tokens) {
|
|
41
|
+
return tokens.length === 0 ? 0 : calculateTokenPosition(tokens, tokens.length - 1);
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Manages caret positioning within a contentEditable element.
|
|
45
|
+
* Translates between logical token positions and DOM Range/Selection API.
|
|
46
|
+
*/
|
|
47
|
+
export class CaretController {
|
|
48
|
+
constructor(element) {
|
|
49
|
+
this.element = element;
|
|
50
|
+
this.state = { start: 0, end: undefined, isValid: false };
|
|
51
|
+
}
|
|
52
|
+
get ownerDoc() {
|
|
53
|
+
var _a;
|
|
54
|
+
return (_a = this.element.ownerDocument) !== null && _a !== void 0 ? _a : document;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Creates a DOM Range from resolved start/end locations and applies it to the given selection.
|
|
58
|
+
* Returns the created Range for further use (e.g. scroll-into-view checks).
|
|
59
|
+
*/
|
|
60
|
+
applyRange(ownerDocument, selection, startLocation, endLocation) {
|
|
61
|
+
const range = ownerDocument.createRange();
|
|
62
|
+
range.setStart(startLocation.node, startLocation.offset);
|
|
63
|
+
if (endLocation) {
|
|
64
|
+
range.setEnd(endLocation.node, endLocation.offset);
|
|
65
|
+
}
|
|
66
|
+
else {
|
|
67
|
+
range.collapse(true);
|
|
68
|
+
}
|
|
69
|
+
selection.removeAllRanges();
|
|
70
|
+
selection.addRange(range);
|
|
71
|
+
return range;
|
|
72
|
+
}
|
|
73
|
+
/** Returns the logical length of a DOM node based on its token type. */
|
|
74
|
+
getNodeLength(node) {
|
|
75
|
+
const tokenType = isHTMLElement(node) ? getTokenType(node) : null;
|
|
76
|
+
if (isTextNode(node) || tokenType === ElementType.Trigger) {
|
|
77
|
+
return TOKEN_LENGTHS.text(node.textContent || '');
|
|
78
|
+
}
|
|
79
|
+
else if (tokenType && isReferenceElementType(tokenType)) {
|
|
80
|
+
return TOKEN_LENGTHS.REFERENCE;
|
|
81
|
+
}
|
|
82
|
+
return 0;
|
|
83
|
+
}
|
|
84
|
+
/** Returns the current logical caret position from the DOM selection. */
|
|
85
|
+
getPosition() {
|
|
86
|
+
const selection = getOwnerSelection(this.element);
|
|
87
|
+
if (!(selection === null || selection === void 0 ? void 0 : selection.rangeCount)) {
|
|
88
|
+
return 0;
|
|
89
|
+
}
|
|
90
|
+
const range = selection.getRangeAt(0);
|
|
91
|
+
if (!this.element.contains(range.startContainer)) {
|
|
92
|
+
return 0;
|
|
93
|
+
}
|
|
94
|
+
return this.calculatePositionFromRange(range, false);
|
|
95
|
+
}
|
|
96
|
+
/** Finds the trigger element at the current caret position, if any. */
|
|
97
|
+
findActiveTrigger() {
|
|
98
|
+
const selection = getOwnerSelection(this.element);
|
|
99
|
+
if (!(selection === null || selection === void 0 ? void 0 : selection.rangeCount)) {
|
|
100
|
+
return null;
|
|
101
|
+
}
|
|
102
|
+
const range = selection.getRangeAt(0);
|
|
103
|
+
if (!range.collapsed) {
|
|
104
|
+
return null;
|
|
105
|
+
}
|
|
106
|
+
let node = range.startContainer;
|
|
107
|
+
// Walk up from cursor to find a trigger ancestor
|
|
108
|
+
while (node && node !== this.element) {
|
|
109
|
+
if (isHTMLElement(node) && getTokenType(node) === ElementType.Trigger) {
|
|
110
|
+
if (isTextNode(range.startContainer) && range.startContainer.parentElement === node) {
|
|
111
|
+
// Caret must be after the trigger character (offset > 0) to be considered "in" the trigger
|
|
112
|
+
if (range.startOffset > 0) {
|
|
113
|
+
return node;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
else {
|
|
117
|
+
return node;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
node = node.parentNode;
|
|
121
|
+
}
|
|
122
|
+
// Also check: cursor at offset 0 of a text node right after a trigger
|
|
123
|
+
if (isTextNode(range.startContainer) && range.startOffset === 0) {
|
|
124
|
+
const prevSibling = range.startContainer.previousSibling;
|
|
125
|
+
if (isHTMLElement(prevSibling) && getTokenType(prevSibling) === ElementType.Trigger) {
|
|
126
|
+
return prevSibling;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
return null;
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Sets the caret to a logical position, or creates a selection range if end is provided.
|
|
133
|
+
* Handles smart positioning around atomic reference tokens and scrolls into view.
|
|
134
|
+
* @param start logical start position
|
|
135
|
+
* @param end optional logical end position for range selection
|
|
136
|
+
*/
|
|
137
|
+
setPosition(start, end) {
|
|
138
|
+
var _a;
|
|
139
|
+
const ownerDocument = this.ownerDoc;
|
|
140
|
+
if (ownerDocument.activeElement !== this.element) {
|
|
141
|
+
this.element.focus();
|
|
142
|
+
}
|
|
143
|
+
const startLocation = this.findDOMLocation(start);
|
|
144
|
+
if (!startLocation) {
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
const selection = getOwnerSelection(this.element);
|
|
148
|
+
if (!selection) {
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
const endLocation = end !== undefined && end !== start ? ((_a = this.findDOMLocation(end)) !== null && _a !== void 0 ? _a : undefined) : undefined;
|
|
152
|
+
this.applyRange(ownerDocument, selection, startLocation, endLocation);
|
|
153
|
+
this.state = { start, end, isValid: true };
|
|
154
|
+
ownerDocument.dispatchEvent(new Event('selectionchange'));
|
|
155
|
+
}
|
|
156
|
+
/** Captures the current caret/selection state for later restoration. */
|
|
157
|
+
capture() {
|
|
158
|
+
const selection = getOwnerSelection(this.element);
|
|
159
|
+
if (!(selection === null || selection === void 0 ? void 0 : selection.rangeCount)) {
|
|
160
|
+
this.state = { start: 0, end: undefined, isValid: false };
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
const range = selection.getRangeAt(0);
|
|
164
|
+
if (!this.element.contains(range.startContainer)) {
|
|
165
|
+
this.state = { start: 0, end: undefined, isValid: false };
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
const start = this.calculatePositionFromRange(range, false);
|
|
169
|
+
const end = range.collapsed ? undefined : this.calculatePositionFromRange(range, true);
|
|
170
|
+
this.state = { start, end, isValid: true };
|
|
171
|
+
}
|
|
172
|
+
/** Returns the captured caret start position, or null if no valid capture exists. */
|
|
173
|
+
getSavedPosition() {
|
|
174
|
+
return this.state.isValid ? this.state.start : null;
|
|
175
|
+
}
|
|
176
|
+
/** Restores the caret to the previously captured state. */
|
|
177
|
+
restore(offset = 0) {
|
|
178
|
+
if (!this.state.isValid || this.ownerDoc.activeElement !== this.element) {
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
this.setPosition(this.state.start + offset, this.state.end !== undefined ? this.state.end + offset : undefined);
|
|
182
|
+
}
|
|
183
|
+
/** Overrides the captured state so the next restore() positions to a calculated location.
|
|
184
|
+
* Currently used only in tests — consider removing if no production use case emerges.
|
|
185
|
+
*/
|
|
186
|
+
setCapturedPosition(start, end) {
|
|
187
|
+
this.state = { start, end, isValid: true };
|
|
188
|
+
}
|
|
189
|
+
/** Selects all content in the element. */
|
|
190
|
+
selectAll() {
|
|
191
|
+
const selection = getOwnerSelection(this.element);
|
|
192
|
+
if (!selection) {
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
if (isEmptyState(this.element)) {
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
if (this.ownerDoc.activeElement !== this.element) {
|
|
199
|
+
this.element.focus();
|
|
200
|
+
}
|
|
201
|
+
const paragraphs = findAllParagraphs(this.element);
|
|
202
|
+
if (paragraphs.length === 0) {
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
205
|
+
const firstP = paragraphs[0];
|
|
206
|
+
const lastP = paragraphs[paragraphs.length - 1];
|
|
207
|
+
const range = this.ownerDoc.createRange();
|
|
208
|
+
range.setStart(firstP, 0);
|
|
209
|
+
range.setEnd(lastP, lastP.childNodes.length);
|
|
210
|
+
selection.removeAllRanges();
|
|
211
|
+
selection.addRange(range);
|
|
212
|
+
}
|
|
213
|
+
/** Positions the caret at the end of a text node. */
|
|
214
|
+
positionAfterText(textNode) {
|
|
215
|
+
var _a;
|
|
216
|
+
const range = this.ownerDoc.createRange();
|
|
217
|
+
range.setStart(textNode, ((_a = textNode.textContent) === null || _a === void 0 ? void 0 : _a.length) || 0);
|
|
218
|
+
range.collapse(true);
|
|
219
|
+
const selection = getOwnerSelection(this.element);
|
|
220
|
+
if (selection) {
|
|
221
|
+
selection.removeAllRanges();
|
|
222
|
+
selection.addRange(range);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
/** Moves the caret forward by a logical offset. */
|
|
226
|
+
moveForward(offset) {
|
|
227
|
+
const currentPos = this.getPosition();
|
|
228
|
+
this.setPosition(currentPos + offset);
|
|
229
|
+
}
|
|
230
|
+
/** Moves the caret backward by a logical offset, clamped to 0. */
|
|
231
|
+
moveBackward(offset) {
|
|
232
|
+
const currentPos = this.getPosition();
|
|
233
|
+
this.setPosition(Math.max(0, currentPos - offset));
|
|
234
|
+
}
|
|
235
|
+
calculatePositionFromRange(range, useEnd) {
|
|
236
|
+
const paragraphs = findAllParagraphs(this.element);
|
|
237
|
+
if (paragraphs.length === 0) {
|
|
238
|
+
return 0;
|
|
239
|
+
}
|
|
240
|
+
const container = useEnd ? range.endContainer : range.startContainer;
|
|
241
|
+
const offset = useEnd ? range.endOffset : range.startOffset;
|
|
242
|
+
let position = 0;
|
|
243
|
+
for (let pIndex = 0; pIndex < paragraphs.length; pIndex++) {
|
|
244
|
+
const p = paragraphs[pIndex];
|
|
245
|
+
if (pIndex > 0) {
|
|
246
|
+
position += TOKEN_LENGTHS.LINE_BREAK;
|
|
247
|
+
}
|
|
248
|
+
if (!p.contains(container)) {
|
|
249
|
+
position += this.countParagraphContent(p);
|
|
250
|
+
}
|
|
251
|
+
else {
|
|
252
|
+
position += this.countUpToCursor(p, container, offset);
|
|
253
|
+
break;
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
return position;
|
|
257
|
+
}
|
|
258
|
+
findDOMLocation(position) {
|
|
259
|
+
var _a;
|
|
260
|
+
const paragraphs = findAllParagraphs(this.element);
|
|
261
|
+
let caretPos = 0;
|
|
262
|
+
for (let pIndex = 0; pIndex < paragraphs.length; pIndex++) {
|
|
263
|
+
const p = paragraphs[pIndex];
|
|
264
|
+
if (pIndex > 0) {
|
|
265
|
+
caretPos += TOKEN_LENGTHS.LINE_BREAK;
|
|
266
|
+
if (caretPos >= position) {
|
|
267
|
+
return { node: p, offset: 0 };
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
const paragraphLength = this.countParagraphContent(p);
|
|
271
|
+
if (caretPos + paragraphLength >= position) {
|
|
272
|
+
return this.findLocationInParagraph(p, position - caretPos);
|
|
273
|
+
}
|
|
274
|
+
caretPos += paragraphLength;
|
|
275
|
+
}
|
|
276
|
+
const lastP = paragraphs[paragraphs.length - 1];
|
|
277
|
+
if ((lastP === null || lastP === void 0 ? void 0 : lastP.lastChild) && isTextNode(lastP.lastChild)) {
|
|
278
|
+
return { node: lastP.lastChild, offset: ((_a = lastP.lastChild.textContent) === null || _a === void 0 ? void 0 : _a.length) || 0 };
|
|
279
|
+
}
|
|
280
|
+
return lastP ? { node: lastP, offset: lastP.childNodes.length } : null;
|
|
281
|
+
}
|
|
282
|
+
/** Resolves a DOM location for a specific child node at the given offset within a paragraph. */
|
|
283
|
+
resolveChildLocation(p, child, offsetInChild) {
|
|
284
|
+
if (isTextNode(child)) {
|
|
285
|
+
return { node: child, offset: offsetInChild };
|
|
286
|
+
}
|
|
287
|
+
if (!isHTMLElement(child)) {
|
|
288
|
+
return { node: p, offset: Array.from(p.childNodes).indexOf(child) };
|
|
289
|
+
}
|
|
290
|
+
const tokenType = getTokenType(child);
|
|
291
|
+
const childIndex = Array.from(p.childNodes).indexOf(child);
|
|
292
|
+
if (tokenType === ElementType.Trigger) {
|
|
293
|
+
if (offsetInChild === 0) {
|
|
294
|
+
return { node: p, offset: childIndex };
|
|
295
|
+
}
|
|
296
|
+
const triggerTextNode = child.childNodes[0];
|
|
297
|
+
if (triggerTextNode && isTextNode(triggerTextNode)) {
|
|
298
|
+
return { node: triggerTextNode, offset: offsetInChild };
|
|
299
|
+
}
|
|
300
|
+
return { node: p, offset: childIndex };
|
|
301
|
+
}
|
|
302
|
+
if (isReferenceElementType(tokenType)) {
|
|
303
|
+
if (offsetInChild === 0) {
|
|
304
|
+
return { node: p, offset: childIndex };
|
|
305
|
+
}
|
|
306
|
+
const nextSibling = child.nextSibling;
|
|
307
|
+
if (nextSibling) {
|
|
308
|
+
return isTextNode(nextSibling) ? { node: nextSibling, offset: 0 } : { node: p, offset: childIndex + 1 };
|
|
309
|
+
}
|
|
310
|
+
return { node: p, offset: p.childNodes.length };
|
|
311
|
+
}
|
|
312
|
+
return { node: p, offset: childIndex };
|
|
313
|
+
}
|
|
314
|
+
findLocationInParagraph(p, targetOffset) {
|
|
315
|
+
var _a;
|
|
316
|
+
let offsetInParagraph = 0;
|
|
317
|
+
for (const child of Array.from(p.childNodes)) {
|
|
318
|
+
const childLength = this.getNodeLength(child);
|
|
319
|
+
if (offsetInParagraph + childLength >= targetOffset) {
|
|
320
|
+
return this.resolveChildLocation(p, child, targetOffset - offsetInParagraph);
|
|
321
|
+
}
|
|
322
|
+
offsetInParagraph += childLength;
|
|
323
|
+
}
|
|
324
|
+
if (p.lastChild && isTextNode(p.lastChild)) {
|
|
325
|
+
return { node: p.lastChild, offset: ((_a = p.lastChild.textContent) === null || _a === void 0 ? void 0 : _a.length) || 0 };
|
|
326
|
+
}
|
|
327
|
+
return { node: p, offset: p.childNodes.length };
|
|
328
|
+
}
|
|
329
|
+
countParagraphContent(p) {
|
|
330
|
+
let count = 0;
|
|
331
|
+
for (const child of Array.from(p.childNodes)) {
|
|
332
|
+
count += this.getNodeLength(child);
|
|
333
|
+
}
|
|
334
|
+
return count;
|
|
335
|
+
}
|
|
336
|
+
countUpToCursor(p, container, offset) {
|
|
337
|
+
if (container === p) {
|
|
338
|
+
let count = 0;
|
|
339
|
+
for (let i = 0; i < offset && i < p.childNodes.length; i++) {
|
|
340
|
+
count += this.getNodeLength(p.childNodes[i]);
|
|
341
|
+
}
|
|
342
|
+
return count;
|
|
343
|
+
}
|
|
344
|
+
let count = 0;
|
|
345
|
+
for (const child of Array.from(p.childNodes)) {
|
|
346
|
+
if (child === container || child.contains(container)) {
|
|
347
|
+
if (isTextNode(child)) {
|
|
348
|
+
return count + offset;
|
|
349
|
+
}
|
|
350
|
+
if (isHTMLElement(child)) {
|
|
351
|
+
const tokenType = getTokenType(child);
|
|
352
|
+
if (tokenType === ElementType.Trigger) {
|
|
353
|
+
const triggerTextNode = child.childNodes[0];
|
|
354
|
+
if (triggerTextNode && isTextNode(triggerTextNode) && triggerTextNode === container) {
|
|
355
|
+
return count + offset;
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
else if (isReferenceElementType(tokenType)) {
|
|
359
|
+
const caretSpotBefore = findElement(child, { tokenType: ElementType.CaretSpotBefore });
|
|
360
|
+
const caretSpotAfter = findElement(child, { tokenType: ElementType.CaretSpotAfter });
|
|
361
|
+
const caretInBefore = caretSpotBefore && (caretSpotBefore === container || caretSpotBefore.contains(container));
|
|
362
|
+
const caretInAfter = caretSpotAfter && (caretSpotAfter === container || caretSpotAfter.contains(container));
|
|
363
|
+
if (caretInBefore) {
|
|
364
|
+
// Caret is in the before-spot: any typed text counts from the start of the reference
|
|
365
|
+
const beforeContent = stripZeroWidthCharacters(caretSpotBefore.textContent || '');
|
|
366
|
+
if (beforeContent && isTextNode(container)) {
|
|
367
|
+
return count + offset;
|
|
368
|
+
}
|
|
369
|
+
// No real content — caret is logically before the reference
|
|
370
|
+
return count;
|
|
371
|
+
}
|
|
372
|
+
else if (caretInAfter) {
|
|
373
|
+
// Caret is in the after-spot: position is after the reference (count it first)
|
|
374
|
+
count += TOKEN_LENGTHS.REFERENCE;
|
|
375
|
+
const afterContent = stripZeroWidthCharacters(caretSpotAfter.textContent || '');
|
|
376
|
+
if (afterContent && isTextNode(container)) {
|
|
377
|
+
// offset - 1 because the zero-width character occupies position 0
|
|
378
|
+
const contentOffset = Math.max(0, offset - 1);
|
|
379
|
+
return count + contentOffset;
|
|
380
|
+
}
|
|
381
|
+
// No real content — caret is logically after the reference
|
|
382
|
+
return count;
|
|
383
|
+
}
|
|
384
|
+
else {
|
|
385
|
+
return count + TOKEN_LENGTHS.REFERENCE;
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
return count + this.getNodeLength(child);
|
|
390
|
+
}
|
|
391
|
+
count += this.getNodeLength(child);
|
|
392
|
+
}
|
|
393
|
+
return count;
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
//# sourceMappingURL=caret-controller.js.map
|