@fluentui-copilot/chat-input-plugins 0.1.2 → 0.1.4

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/CHANGELOG.json CHANGED
@@ -2,7 +2,37 @@
2
2
  "name": "@fluentui-copilot/chat-input-plugins",
3
3
  "entries": [
4
4
  {
5
- "date": "Thu, 01 Aug 2024 22:27:53 GMT",
5
+ "date": "Wed, 14 Aug 2024 23:11:18 GMT",
6
+ "tag": "@fluentui-copilot/chat-input-plugins_v0.1.4",
7
+ "version": "0.1.4",
8
+ "comments": {
9
+ "patch": [
10
+ {
11
+ "author": "owcampbe@microsoft.com",
12
+ "package": "@fluentui-copilot/chat-input-plugins",
13
+ "commit": "1b37d749959789efdf29b6b0bdac82666bc0f49b",
14
+ "comment": "chore: Lower command priorities to allow overriding."
15
+ }
16
+ ]
17
+ }
18
+ },
19
+ {
20
+ "date": "Mon, 05 Aug 2024 19:36:40 GMT",
21
+ "tag": "@fluentui-copilot/chat-input-plugins_v0.1.3",
22
+ "version": "0.1.3",
23
+ "comments": {
24
+ "patch": [
25
+ {
26
+ "author": "owcampbe@microsoft.com",
27
+ "package": "@fluentui-copilot/chat-input-plugins",
28
+ "commit": "5aece4b9922c8591ad1c573ea4356062b268da39",
29
+ "comment": "chore: Refactor SentinelNode handlers and add tests."
30
+ }
31
+ ]
32
+ }
33
+ },
34
+ {
35
+ "date": "Thu, 01 Aug 2024 22:28:24 GMT",
6
36
  "tag": "@fluentui-copilot/chat-input-plugins_v0.1.2",
7
37
  "version": "0.1.2",
8
38
  "comments": {
package/CHANGELOG.md CHANGED
@@ -1,12 +1,30 @@
1
1
  # Change Log - @fluentui-copilot/chat-input-plugins
2
2
 
3
- This log was last generated on Thu, 01 Aug 2024 22:27:53 GMT and should not be manually modified.
3
+ This log was last generated on Wed, 14 Aug 2024 23:11:18 GMT and should not be manually modified.
4
4
 
5
5
  <!-- Start content -->
6
6
 
7
+ ## [0.1.4](https://github.com/microsoft/fluentai/tree/@fluentui-copilot/chat-input-plugins_v0.1.4)
8
+
9
+ Wed, 14 Aug 2024 23:11:18 GMT
10
+ [Compare changes](https://github.com/microsoft/fluentai/compare/@fluentui-copilot/chat-input-plugins_v0.1.3..@fluentui-copilot/chat-input-plugins_v0.1.4)
11
+
12
+ ### Patches
13
+
14
+ - chore: Lower command priorities to allow overriding. ([PR #2042](https://github.com/microsoft/fluentai/pull/2042) by owcampbe@microsoft.com)
15
+
16
+ ## [0.1.3](https://github.com/microsoft/fluentai/tree/@fluentui-copilot/chat-input-plugins_v0.1.3)
17
+
18
+ Mon, 05 Aug 2024 19:36:40 GMT
19
+ [Compare changes](https://github.com/microsoft/fluentai/compare/@fluentui-copilot/chat-input-plugins_v0.1.2..@fluentui-copilot/chat-input-plugins_v0.1.3)
20
+
21
+ ### Patches
22
+
23
+ - chore: Refactor SentinelNode handlers and add tests. ([PR #1956](https://github.com/microsoft/fluentai/pull/1956) by owcampbe@microsoft.com)
24
+
7
25
  ## [0.1.2](https://github.com/microsoft/fluentai/tree/@fluentui-copilot/chat-input-plugins_v0.1.2)
8
26
 
9
- Thu, 01 Aug 2024 22:27:53 GMT
27
+ Thu, 01 Aug 2024 22:28:24 GMT
10
28
  [Compare changes](https://github.com/microsoft/fluentai/compare/@fluentui-copilot/chat-input-plugins_v0.1.1..@fluentui-copilot/chat-input-plugins_v0.1.2)
11
29
 
12
30
  ### Patches
package/dist/index.d.ts CHANGED
@@ -4,6 +4,7 @@ import { Klass } from 'lexical';
4
4
  import { LexicalEditor } from 'lexical';
5
5
  import { LexicalNode } from 'lexical';
6
6
  import { NodeKey } from 'lexical';
7
+ import { SerializedTextNode } from 'lexical';
7
8
  import { TextNode } from 'lexical';
8
9
 
9
10
  export declare function $createSentinelNode(key?: NodeKey): SentinelNode;
@@ -185,6 +186,7 @@ export declare class SentinelNode extends TextNode {
185
186
  static clone(node: SentinelNode): SentinelNode;
186
187
  createDOM(config: EditorConfig): HTMLElement;
187
188
  isToken(): boolean;
189
+ exportJSON(): SerializedTextNode;
188
190
  }
189
191
 
190
192
  export { }
@@ -1,6 +1,8 @@
1
1
  import { _ as _define_property } from "@swc/helpers/_/_define_property";
2
- import { $createTextNode, $getLeafNodes, $getNodeByKey, $getRoot, $getSelection, $insertNodes, $isRangeSelection, $normalizeSelection__EXPERIMENTAL, $setSelection, COMMAND_PRIORITY_CRITICAL, INSERT_PARAGRAPH_COMMAND, KEY_ENTER_COMMAND, PASTE_COMMAND, mergeRegister } from '@fluentui-copilot/text-editor';
3
- import { $createSentinelNode, $isSentinelNode, SENTINEL_VALUE, SentinelNode } from './SentinelNode';
2
+ import { COMMAND_PRIORITY_HIGH } from '@fluentui-copilot/text-editor';
3
+ import { $createTextNode, $getRoot, $getSelection, $insertNodes, $isRangeSelection, INSERT_PARAGRAPH_COMMAND, KEY_ENTER_COMMAND, PASTE_COMMAND, mergeRegister } from '@fluentui-copilot/text-editor';
4
+ import { SENTINEL_VALUE } from './SentinelNode';
5
+ import { registerSentinelNodeHandlers } from './SentinelNodeHandlers';
4
6
  function isClipboardEvent(event) {
5
7
  return event.type === 'paste';
6
8
  }
@@ -51,7 +53,7 @@ export class BasicFunctionalityBase {
51
53
  return true;
52
54
  }
53
55
  return false;
54
- }, COMMAND_PRIORITY_CRITICAL);
56
+ }, COMMAND_PRIORITY_HIGH);
55
57
  }
56
58
  deactivatePasteCallback() {
57
59
  var _this___pasteHandlerCleanup, _this;
@@ -97,85 +99,7 @@ export class BasicFunctionalityBase {
97
99
  _define_property(this, "__baseHandlersCleanup", void 0);
98
100
  _define_property(this, "__pasteHandlerCleanup", void 0);
99
101
  this.__editor = editor;
100
- this.__baseHandlersCleanup = mergeRegister(this.__editor.registerCommand(KEY_ENTER_COMMAND, this.__enterHandler.bind(this), COMMAND_PRIORITY_CRITICAL),
101
- // Add a sentinel node at the end of the input when there is content.
102
- // This sentinel node fixes a number of issues.
103
- // In Safari, Lexical's behaviour of adding <br /> tags to the end of the input when it ends
104
- // in a decorator node causes cursor location issues: https://github.com/facebook/lexical/issues/4487
105
- // Otherwise, when a decorator node is the last node in the input, the cursor can't move past it.
106
- // Adding an invisible text node that doesn't contribute to the content and can't be selected to the end of the input
107
- // mitigates these issues.
108
- this.__editor.registerUpdateListener(({
109
- editorState
110
- }) => {
111
- editorState.read(() => {
112
- const leaves = $getLeafNodes($getRoot());
113
- if (leaves.length === 0) {
114
- return;
115
- }
116
- const lastNode = leaves[leaves.length - 1];
117
- const lastNodeKey = lastNode.getKey();
118
- // If the last node isn't a sentinel, add one
119
- if (!$isSentinelNode(lastNode)) {
120
- this.__editor.update(() => {
121
- var
122
- // We find the node by its key again in case the node was removed before this update runs
123
- _$getNodeByKey;
124
- (_$getNodeByKey = $getNodeByKey(lastNodeKey)) === null || _$getNodeByKey === void 0 ? void 0 : _$getNodeByKey.insertAfter($createSentinelNode());
125
- },
126
- // historic tag prevents every sentinel node addition from being added to LexicalHistoryPlugin undo stack
127
- {
128
- discrete: true,
129
- tag: 'historic'
130
- });
131
- return;
132
- }
133
- // If the sentinel node is not selected, we're done
134
- const previous = lastNode.getPreviousSibling();
135
- if (!previous || !lastNode.isSelected()) {
136
- return;
137
- }
138
- const selection = $getSelection();
139
- if (!$isRangeSelection(selection)) {
140
- return;
141
- }
142
- // If the cursor is inside the sentinel node, move it out (next to the beginning)
143
- // We allow selection on the boundary of the sentinel in case the adjacent node is a decorator node
144
- // where selection is ill-defined.
145
- if (selection.isCollapsed() && selection.anchor.offset > 0) {
146
- this.__editor.update(() => {
147
- var _$getNodeByKey;
148
- (_$getNodeByKey = $getNodeByKey(lastNodeKey)) === null || _$getNodeByKey === void 0 ? void 0 : _$getNodeByKey.selectStart();
149
- });
150
- return;
151
- }
152
- // If the selection is a range which includes the sentinel, modify the range to exclude it
153
- if (!selection.isCollapsed()) {
154
- let selectionChanged = false;
155
- const newSelection = selection.clone();
156
- if (newSelection.anchor.getNode() === lastNode && newSelection.anchor.offset > 0) {
157
- newSelection.anchor.set(lastNodeKey, 0, 'text');
158
- selectionChanged = true;
159
- }
160
- if (newSelection.focus.getNode() === lastNode && newSelection.focus.offset > 0) {
161
- newSelection.focus.set(lastNodeKey, 0, 'text');
162
- selectionChanged = true;
163
- }
164
- if (selectionChanged) {
165
- this.__editor.update(() => {
166
- if ($getNodeByKey(lastNodeKey) !== null) {
167
- $setSelection($normalizeSelection__EXPERIMENTAL(newSelection));
168
- }
169
- });
170
- }
171
- }
172
- });
173
- }), this.__editor.registerNodeTransform(SentinelNode, node => {
174
- if (!node.getPreviousSibling() || node.getNextSibling()) {
175
- node.remove();
176
- return;
177
- }
178
- }));
102
+ this.__baseHandlersCleanup = mergeRegister(this.__editor.registerCommand(KEY_ENTER_COMMAND, this.__enterHandler.bind(this), COMMAND_PRIORITY_HIGH), registerSentinelNodeHandlers(editor));
179
103
  }
180
104
  }
181
105
  //# sourceMappingURL=BasicFunctionality.base.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["BasicFunctionality.base.ts"],"sourcesContent":["import type { LexicalEditor } from '@fluentui-copilot/text-editor';\nimport {\n $createTextNode,\n $getLeafNodes,\n $getNodeByKey,\n $getRoot,\n $getSelection,\n $insertNodes,\n $isRangeSelection,\n $normalizeSelection__EXPERIMENTAL,\n $setSelection,\n COMMAND_PRIORITY_CRITICAL,\n INSERT_PARAGRAPH_COMMAND,\n KEY_ENTER_COMMAND,\n PASTE_COMMAND,\n mergeRegister,\n} from '@fluentui-copilot/text-editor';\nimport { $createSentinelNode, $isSentinelNode, SENTINEL_VALUE, SentinelNode } from './SentinelNode';\n\nfunction isClipboardEvent(event: ClipboardEvent | KeyboardEvent | InputEvent): event is ClipboardEvent {\n return event.type === 'paste';\n}\n\nexport interface IBasicFunctionalityBase {\n insertDefaultValue: (defaultValue: string) => void;\n setIsDisabled: (isDisabled: boolean) => void;\n activateContentCallbacks(onContentChange?: (value: string) => void, onCountChanged?: (count: number) => void): void;\n deactivateContentCallbacks(): void;\n activateTrimWhitespace(): void;\n deactivateTrimWhitespace(): void;\n activatePasteCallback(onPaste: (event: ClipboardEvent) => void): void;\n deactivatePasteCallback(): void;\n cleanup(): void;\n}\n\nexport class BasicFunctionalityBase implements IBasicFunctionalityBase {\n private __editor: LexicalEditor;\n private __contentChangeCleanup?: () => void;\n private __trimWhitespaceCleanup?: () => void;\n private __baseHandlersCleanup?: () => void;\n private __pasteHandlerCleanup?: () => void;\n\n private __enterHandler(event: KeyboardEvent) {\n const selection = $getSelection();\n if (!$isRangeSelection(selection)) {\n return false;\n }\n\n if (event === null) {\n return false;\n }\n\n event.preventDefault();\n\n if (event.shiftKey) {\n return this.__editor.dispatchCommand(INSERT_PARAGRAPH_COMMAND, undefined);\n }\n\n // Mark event handled to override default behavior\n return true;\n }\n\n constructor(editor: LexicalEditor) {\n this.__editor = editor;\n\n this.__baseHandlersCleanup = mergeRegister(\n this.__editor.registerCommand(KEY_ENTER_COMMAND, this.__enterHandler.bind(this), COMMAND_PRIORITY_CRITICAL),\n\n // Add a sentinel node at the end of the input when there is content.\n // This sentinel node fixes a number of issues.\n // In Safari, Lexical's behaviour of adding <br /> tags to the end of the input when it ends\n // in a decorator node causes cursor location issues: https://github.com/facebook/lexical/issues/4487\n // Otherwise, when a decorator node is the last node in the input, the cursor can't move past it.\n // Adding an invisible text node that doesn't contribute to the content and can't be selected to the end of the input\n // mitigates these issues.\n this.__editor.registerUpdateListener(({ editorState }) => {\n editorState.read(() => {\n const leaves = $getLeafNodes($getRoot());\n if (leaves.length === 0) {\n return;\n }\n\n const lastNode = leaves[leaves.length - 1];\n const lastNodeKey = lastNode.getKey();\n\n // If the last node isn't a sentinel, add one\n if (!$isSentinelNode(lastNode)) {\n this.__editor.update(\n () => {\n // We find the node by its key again in case the node was removed before this update runs\n $getNodeByKey(lastNodeKey)?.insertAfter($createSentinelNode());\n },\n // historic tag prevents every sentinel node addition from being added to LexicalHistoryPlugin undo stack\n { discrete: true, tag: 'historic' },\n );\n return;\n }\n\n // If the sentinel node is not selected, we're done\n const previous = lastNode.getPreviousSibling();\n if (!previous || !lastNode.isSelected()) {\n return;\n }\n\n const selection = $getSelection();\n if (!$isRangeSelection(selection)) {\n return;\n }\n\n // If the cursor is inside the sentinel node, move it out (next to the beginning)\n // We allow selection on the boundary of the sentinel in case the adjacent node is a decorator node\n // where selection is ill-defined.\n if (selection.isCollapsed() && selection.anchor.offset > 0) {\n this.__editor.update(() => {\n $getNodeByKey(lastNodeKey)?.selectStart();\n });\n return;\n }\n\n // If the selection is a range which includes the sentinel, modify the range to exclude it\n if (!selection.isCollapsed()) {\n let selectionChanged = false;\n const newSelection = selection.clone();\n\n if (newSelection.anchor.getNode() === lastNode && newSelection.anchor.offset > 0) {\n newSelection.anchor.set(lastNodeKey, 0, 'text');\n selectionChanged = true;\n }\n if (newSelection.focus.getNode() === lastNode && newSelection.focus.offset > 0) {\n newSelection.focus.set(lastNodeKey, 0, 'text');\n selectionChanged = true;\n }\n\n if (selectionChanged) {\n this.__editor.update(() => {\n if ($getNodeByKey(lastNodeKey) !== null) {\n $setSelection($normalizeSelection__EXPERIMENTAL(newSelection));\n }\n });\n }\n }\n });\n }),\n this.__editor.registerNodeTransform(SentinelNode, node => {\n if (!node.getPreviousSibling() || node.getNextSibling()) {\n node.remove();\n return;\n }\n }),\n );\n }\n\n insertDefaultValue(defaultValue: string) {\n if (defaultValue) {\n this.__editor.update(() => {\n $insertNodes([$createTextNode(defaultValue)]);\n });\n }\n }\n\n activateContentCallbacks(\n onContentChange?: ((value: string) => void) | undefined,\n onCountChanged?: ((count: number) => void) | undefined,\n ) {\n this.deactivateContentCallbacks();\n this.__contentChangeCleanup = this.__editor.registerTextContentListener(text => {\n // Remove the sentinel node\n const processed = text.replace(SENTINEL_VALUE, '');\n onContentChange?.(processed);\n onCountChanged?.(processed.length);\n });\n }\n\n deactivateContentCallbacks() {\n this.__contentChangeCleanup?.();\n this.__contentChangeCleanup = undefined;\n }\n\n activatePasteCallback(onPaste: (event: ClipboardEvent) => void) {\n this.__pasteHandlerCleanup = this.__editor.registerCommand(\n PASTE_COMMAND,\n (event: ClipboardEvent | KeyboardEvent | InputEvent) => {\n if (!isClipboardEvent(event)) {\n return false;\n }\n\n onPaste(event);\n\n if (event.defaultPrevented) {\n return true;\n }\n\n return false;\n },\n COMMAND_PRIORITY_CRITICAL,\n );\n }\n\n deactivatePasteCallback() {\n this.__pasteHandlerCleanup?.();\n this.__pasteHandlerCleanup = undefined;\n }\n\n activateTrimWhitespace() {\n this.deactivateTrimWhitespace();\n this.__trimWhitespaceCleanup = this.__editor.registerTextContentListener(text => {\n if (text.trim() === '') {\n this.__editor.update(\n () => {\n $getRoot()\n .getAllTextNodes()\n .forEach(node => {\n node.remove();\n });\n },\n // Don't allow undoing this action\n { tag: 'historic' },\n );\n }\n });\n }\n\n deactivateTrimWhitespace() {\n this.__trimWhitespaceCleanup?.();\n this.__trimWhitespaceCleanup = undefined;\n }\n\n setIsDisabled(isDisabled: boolean) {\n this.__editor.setEditable(!isDisabled);\n }\n\n cleanup() {\n this.deactivateContentCallbacks();\n this.deactivateTrimWhitespace();\n this.deactivatePasteCallback();\n this.__baseHandlersCleanup?.();\n this.__baseHandlersCleanup = undefined;\n }\n}\n"],"names":["$createTextNode","$getLeafNodes","$getNodeByKey","$getRoot","$getSelection","$insertNodes","$isRangeSelection","$normalizeSelection__EXPERIMENTAL","$setSelection","COMMAND_PRIORITY_CRITICAL","INSERT_PARAGRAPH_COMMAND","KEY_ENTER_COMMAND","PASTE_COMMAND","mergeRegister","$createSentinelNode","$isSentinelNode","SENTINEL_VALUE","SentinelNode","isClipboardEvent","event","type","BasicFunctionalityBase","__enterHandler","selection","preventDefault","shiftKey","__editor","dispatchCommand","undefined","insertDefaultValue","defaultValue","update","activateContentCallbacks","onContentChange","onCountChanged","deactivateContentCallbacks","__contentChangeCleanup","registerTextContentListener","text","processed","replace","length","activatePasteCallback","onPaste","__pasteHandlerCleanup","registerCommand","defaultPrevented","deactivatePasteCallback","activateTrimWhitespace","deactivateTrimWhitespace","__trimWhitespaceCleanup","trim","getAllTextNodes","forEach","node","remove","tag","setIsDisabled","isDisabled","setEditable","cleanup","__baseHandlersCleanup","constructor","editor","bind","registerUpdateListener","editorState","read","leaves","lastNode","lastNodeKey","getKey","insertAfter","discrete","previous","getPreviousSibling","isSelected","isCollapsed","anchor","offset","selectStart","selectionChanged","newSelection","clone","getNode","set","focus","registerNodeTransform","getNextSibling"],"rangeMappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;","mappings":"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"}
1
+ {"version":3,"sources":["BasicFunctionality.base.ts"],"sourcesContent":["import type { LexicalEditor } from '@fluentui-copilot/text-editor';\nimport { COMMAND_PRIORITY_HIGH } from '@fluentui-copilot/text-editor';\nimport {\n $createTextNode,\n $getRoot,\n $getSelection,\n $insertNodes,\n $isRangeSelection,\n INSERT_PARAGRAPH_COMMAND,\n KEY_ENTER_COMMAND,\n PASTE_COMMAND,\n mergeRegister,\n} from '@fluentui-copilot/text-editor';\nimport { SENTINEL_VALUE } from './SentinelNode';\nimport { registerSentinelNodeHandlers } from './SentinelNodeHandlers';\n\nfunction isClipboardEvent(event: ClipboardEvent | KeyboardEvent | InputEvent): event is ClipboardEvent {\n return event.type === 'paste';\n}\n\nexport interface IBasicFunctionalityBase {\n insertDefaultValue: (defaultValue: string) => void;\n setIsDisabled: (isDisabled: boolean) => void;\n activateContentCallbacks(onContentChange?: (value: string) => void, onCountChanged?: (count: number) => void): void;\n deactivateContentCallbacks(): void;\n activateTrimWhitespace(): void;\n deactivateTrimWhitespace(): void;\n activatePasteCallback(onPaste: (event: ClipboardEvent) => void): void;\n deactivatePasteCallback(): void;\n cleanup(): void;\n}\n\nexport class BasicFunctionalityBase implements IBasicFunctionalityBase {\n private __editor: LexicalEditor;\n private __contentChangeCleanup?: () => void;\n private __trimWhitespaceCleanup?: () => void;\n private __baseHandlersCleanup?: () => void;\n private __pasteHandlerCleanup?: () => void;\n\n private __enterHandler(event: KeyboardEvent) {\n const selection = $getSelection();\n if (!$isRangeSelection(selection)) {\n return false;\n }\n\n if (event === null) {\n return false;\n }\n\n event.preventDefault();\n\n if (event.shiftKey) {\n return this.__editor.dispatchCommand(INSERT_PARAGRAPH_COMMAND, undefined);\n }\n\n // Mark event handled to override default behavior\n return true;\n }\n\n constructor(editor: LexicalEditor) {\n this.__editor = editor;\n\n this.__baseHandlersCleanup = mergeRegister(\n this.__editor.registerCommand(KEY_ENTER_COMMAND, this.__enterHandler.bind(this), COMMAND_PRIORITY_HIGH),\n registerSentinelNodeHandlers(editor),\n );\n }\n\n insertDefaultValue(defaultValue: string) {\n if (defaultValue) {\n this.__editor.update(() => {\n $insertNodes([$createTextNode(defaultValue)]);\n });\n }\n }\n\n activateContentCallbacks(\n onContentChange?: ((value: string) => void) | undefined,\n onCountChanged?: ((count: number) => void) | undefined,\n ) {\n this.deactivateContentCallbacks();\n this.__contentChangeCleanup = this.__editor.registerTextContentListener(text => {\n // Remove the sentinel node\n const processed = text.replace(SENTINEL_VALUE, '');\n onContentChange?.(processed);\n onCountChanged?.(processed.length);\n });\n }\n\n deactivateContentCallbacks() {\n this.__contentChangeCleanup?.();\n this.__contentChangeCleanup = undefined;\n }\n\n activatePasteCallback(onPaste: (event: ClipboardEvent) => void) {\n this.__pasteHandlerCleanup = this.__editor.registerCommand(\n PASTE_COMMAND,\n (event: ClipboardEvent | KeyboardEvent | InputEvent) => {\n if (!isClipboardEvent(event)) {\n return false;\n }\n\n onPaste(event);\n\n if (event.defaultPrevented) {\n return true;\n }\n\n return false;\n },\n COMMAND_PRIORITY_HIGH,\n );\n }\n\n deactivatePasteCallback() {\n this.__pasteHandlerCleanup?.();\n this.__pasteHandlerCleanup = undefined;\n }\n\n activateTrimWhitespace() {\n this.deactivateTrimWhitespace();\n this.__trimWhitespaceCleanup = this.__editor.registerTextContentListener(text => {\n if (text.trim() === '') {\n this.__editor.update(\n () => {\n $getRoot()\n .getAllTextNodes()\n .forEach(node => {\n node.remove();\n });\n },\n // Don't allow undoing this action\n { tag: 'historic' },\n );\n }\n });\n }\n\n deactivateTrimWhitespace() {\n this.__trimWhitespaceCleanup?.();\n this.__trimWhitespaceCleanup = undefined;\n }\n\n setIsDisabled(isDisabled: boolean) {\n this.__editor.setEditable(!isDisabled);\n }\n\n cleanup() {\n this.deactivateContentCallbacks();\n this.deactivateTrimWhitespace();\n this.deactivatePasteCallback();\n this.__baseHandlersCleanup?.();\n this.__baseHandlersCleanup = undefined;\n }\n}\n"],"names":["COMMAND_PRIORITY_HIGH","$createTextNode","$getRoot","$getSelection","$insertNodes","$isRangeSelection","INSERT_PARAGRAPH_COMMAND","KEY_ENTER_COMMAND","PASTE_COMMAND","mergeRegister","SENTINEL_VALUE","registerSentinelNodeHandlers","isClipboardEvent","event","type","BasicFunctionalityBase","__enterHandler","selection","preventDefault","shiftKey","__editor","dispatchCommand","undefined","insertDefaultValue","defaultValue","update","activateContentCallbacks","onContentChange","onCountChanged","deactivateContentCallbacks","__contentChangeCleanup","registerTextContentListener","text","processed","replace","length","activatePasteCallback","onPaste","__pasteHandlerCleanup","registerCommand","defaultPrevented","deactivatePasteCallback","activateTrimWhitespace","deactivateTrimWhitespace","__trimWhitespaceCleanup","trim","getAllTextNodes","forEach","node","remove","tag","setIsDisabled","isDisabled","setEditable","cleanup","__baseHandlersCleanup","constructor","editor","bind"],"rangeMappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;","mappings":"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"}
@@ -1,4 +1,8 @@
1
1
  import { TextNode } from '@fluentui-copilot/text-editor';
2
+ // 2 zero width characters will not be visible in the editor.
3
+ // \u200b is the zero width space character which can allow line wrapping in a word
4
+ // \u200c is the zero width non-joiner character which prevents ligatures from connecting the two surrounding characters
5
+ // These two characters together are a no-op and should not be present together in any legitimate user input.
2
6
  export const SENTINEL_VALUE = '\u200b\u200c';
3
7
  export class SentinelNode extends TextNode {
4
8
  static getType() {
@@ -15,9 +19,14 @@ export class SentinelNode extends TextNode {
15
19
  isToken() {
16
20
  return true;
17
21
  }
22
+ exportJSON() {
23
+ return {
24
+ ...super.exportJSON(),
25
+ type: 'sentinel',
26
+ version: 1
27
+ };
28
+ }
18
29
  constructor(key) {
19
- // 2 zero width characters will not be visible in the editor.
20
- // These also happen to be markers for CIQ to ignore the following content.
21
30
  super(SENTINEL_VALUE, key);
22
31
  }
23
32
  }
@@ -1 +1 @@
1
- {"version":3,"sources":["SentinelNode.ts"],"sourcesContent":["import type { EditorConfig, LexicalNode, NodeKey } from '@fluentui-copilot/text-editor';\nimport { TextNode } from '@fluentui-copilot/text-editor';\n\nexport const SENTINEL_VALUE = '\\u200b\\u200c';\n\nexport class SentinelNode extends TextNode {\n constructor(key?: NodeKey) {\n // 2 zero width characters will not be visible in the editor.\n // These also happen to be markers for CIQ to ignore the following content.\n super(SENTINEL_VALUE, key);\n }\n\n static getType() {\n return 'sentinel';\n }\n static clone(node: SentinelNode) {\n return new SentinelNode(node.__key);\n }\n\n createDOM(config: EditorConfig): HTMLElement {\n const element = super.createDOM(config);\n element.ariaHidden = 'true';\n return element;\n }\n\n isToken() {\n return true;\n }\n}\n\nexport function $createSentinelNode(key?: NodeKey) {\n return new SentinelNode(key);\n}\n\nexport function $isSentinelNode(node: LexicalNode | null): node is SentinelNode {\n return node instanceof SentinelNode;\n}\n"],"names":["TextNode","SENTINEL_VALUE","SentinelNode","getType","clone","node","__key","createDOM","config","element","ariaHidden","isToken","constructor","key","$createSentinelNode","$isSentinelNode"],"rangeMappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;","mappings":"AACA,SAASA,QAAQ,QAAQ,gCAAgC;AAEzD,OAAO,MAAMC,iBAAiB,eAAe;AAE7C,OAAO,MAAMC,qBAAqBF;IAOhC,OAAOG,UAAU;QACf,OAAO;IACT;IACA,OAAOC,MAAMC,IAAkB,EAAE;QAC/B,OAAO,IAAIH,aAAaG,KAAKC,KAAK;IACpC;IAEAC,UAAUC,MAAoB,EAAe;QAC3C,MAAMC,UAAU,KAAK,CAACF,UAAUC;QAChCC,QAAQC,UAAU,GAAG;QACrB,OAAOD;IACT;IAEAE,UAAU;QACR,OAAO;IACT;IArBAC,YAAYC,GAAa,CAAE;QACzB,6DAA6D;QAC7D,2EAA2E;QAC3E,KAAK,CAACZ,gBAAgBY;IACxB;AAkBF;AAEA,OAAO,SAASC,oBAAoBD,GAAa;IAC/C,OAAO,IAAIX,aAAaW;AAC1B;AAEA,OAAO,SAASE,gBAAgBV,IAAwB;IACtD,OAAOA,gBAAgBH;AACzB"}
1
+ {"version":3,"sources":["SentinelNode.ts"],"sourcesContent":["import type { EditorConfig, LexicalNode, NodeKey, SerializedTextNode } from '@fluentui-copilot/text-editor';\nimport { TextNode } from '@fluentui-copilot/text-editor';\n\n// 2 zero width characters will not be visible in the editor.\n// \\u200b is the zero width space character which can allow line wrapping in a word\n// \\u200c is the zero width non-joiner character which prevents ligatures from connecting the two surrounding characters\n// These two characters together are a no-op and should not be present together in any legitimate user input.\nexport const SENTINEL_VALUE = '\\u200b\\u200c';\n\nexport class SentinelNode extends TextNode {\n constructor(key?: NodeKey) {\n super(SENTINEL_VALUE, key);\n }\n\n static getType() {\n return 'sentinel';\n }\n static clone(node: SentinelNode) {\n return new SentinelNode(node.__key);\n }\n\n createDOM(config: EditorConfig): HTMLElement {\n const element = super.createDOM(config);\n element.ariaHidden = 'true';\n return element;\n }\n\n isToken() {\n return true;\n }\n\n exportJSON(): SerializedTextNode {\n return {\n ...super.exportJSON(),\n type: 'sentinel',\n version: 1,\n };\n }\n}\n\nexport function $createSentinelNode(key?: NodeKey) {\n return new SentinelNode(key);\n}\n\nexport function $isSentinelNode(node: LexicalNode | null): node is SentinelNode {\n return node instanceof SentinelNode;\n}\n"],"names":["TextNode","SENTINEL_VALUE","SentinelNode","getType","clone","node","__key","createDOM","config","element","ariaHidden","isToken","exportJSON","type","version","constructor","key","$createSentinelNode","$isSentinelNode"],"rangeMappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;","mappings":"AACA,SAASA,QAAQ,QAAQ,gCAAgC;AAEzD,6DAA6D;AAC7D,mFAAmF;AACnF,wHAAwH;AACxH,6GAA6G;AAC7G,OAAO,MAAMC,iBAAiB,eAAe;AAE7C,OAAO,MAAMC,qBAAqBF;IAKhC,OAAOG,UAAU;QACf,OAAO;IACT;IACA,OAAOC,MAAMC,IAAkB,EAAE;QAC/B,OAAO,IAAIH,aAAaG,KAAKC,KAAK;IACpC;IAEAC,UAAUC,MAAoB,EAAe;QAC3C,MAAMC,UAAU,KAAK,CAACF,UAAUC;QAChCC,QAAQC,UAAU,GAAG;QACrB,OAAOD;IACT;IAEAE,UAAU;QACR,OAAO;IACT;IAEAC,aAAiC;QAC/B,OAAO;YACL,GAAG,KAAK,CAACA,YAAY;YACrBC,MAAM;YACNC,SAAS;QACX;IACF;IA3BAC,YAAYC,GAAa,CAAE;QACzB,KAAK,CAACf,gBAAgBe;IACxB;AA0BF;AAEA,OAAO,SAASC,oBAAoBD,GAAa;IAC/C,OAAO,IAAId,aAAac;AAC1B;AAEA,OAAO,SAASE,gBAAgBb,IAAwB;IACtD,OAAOA,gBAAgBH;AACzB"}
@@ -0,0 +1,84 @@
1
+ import { $getLeafNodes, $getNodeByKey, $getRoot, $getSelection, $isRangeSelection, $normalizeSelection__EXPERIMENTAL, $setSelection, mergeRegister } from '@fluentui-copilot/text-editor';
2
+ import { $isSentinelNode, $createSentinelNode, SentinelNode } from './SentinelNode';
3
+ export function registerSentinelNodeHandlers(editor) {
4
+ // Add a sentinel node at the end of the input when there is content.
5
+ // This sentinel node fixes a number of issues.
6
+ // In Safari, Lexical's behaviour of adding <br /> tags to the end of the input when it ends
7
+ // in a decorator node causes cursor location issues: https://github.com/facebook/lexical/issues/4487
8
+ // Otherwise, when a decorator node is the last node in the input, the cursor can't move past it.
9
+ // Adding an invisible text node that doesn't contribute to the content and can't be selected to the end of the input
10
+ // mitigates these issues.
11
+ const sentinelNodeUpdateHandler = ({
12
+ editorState
13
+ }) => {
14
+ editorState.read(() => {
15
+ const leaves = $getLeafNodes($getRoot());
16
+ if (leaves.length === 0) {
17
+ return;
18
+ }
19
+ const lastNode = leaves[leaves.length - 1];
20
+ const lastNodeKey = lastNode.getKey();
21
+ // If the last node isn't a sentinel, add one
22
+ if (!$isSentinelNode(lastNode)) {
23
+ editor.update(() => {
24
+ var
25
+ // We find the node by its key again in case the node was removed before this update runs
26
+ _$getNodeByKey;
27
+ (_$getNodeByKey = $getNodeByKey(lastNodeKey)) === null || _$getNodeByKey === void 0 ? void 0 : _$getNodeByKey.insertAfter($createSentinelNode());
28
+ },
29
+ // historic tag prevents every sentinel node addition from being added to LexicalHistoryPlugin undo stack
30
+ {
31
+ discrete: true,
32
+ tag: 'historic'
33
+ });
34
+ return;
35
+ }
36
+ // If the sentinel node is not selected, we're done
37
+ const previous = lastNode.getPreviousSibling();
38
+ if (!previous || !lastNode.isSelected()) {
39
+ return;
40
+ }
41
+ const selection = $getSelection();
42
+ if (!$isRangeSelection(selection)) {
43
+ return;
44
+ }
45
+ // If the cursor is inside the sentinel node, move it out (next to the beginning)
46
+ // We allow selection on the boundary of the sentinel in case the adjacent node is a decorator node
47
+ // where selection is ill-defined.
48
+ if (selection.isCollapsed() && selection.anchor.offset > 0) {
49
+ editor.update(() => {
50
+ var _$getNodeByKey;
51
+ (_$getNodeByKey = $getNodeByKey(lastNodeKey)) === null || _$getNodeByKey === void 0 ? void 0 : _$getNodeByKey.selectStart();
52
+ });
53
+ return;
54
+ }
55
+ // If the selection is a range which includes the sentinel, modify the range to exclude it
56
+ if (!selection.isCollapsed()) {
57
+ let selectionChanged = false;
58
+ const newSelection = selection.clone();
59
+ if (newSelection.anchor.getNode() === lastNode && newSelection.anchor.offset > 0) {
60
+ newSelection.anchor.set(lastNodeKey, 0, 'text');
61
+ selectionChanged = true;
62
+ }
63
+ if (newSelection.focus.getNode() === lastNode && newSelection.focus.offset > 0) {
64
+ newSelection.focus.set(lastNodeKey, 0, 'text');
65
+ selectionChanged = true;
66
+ }
67
+ if (selectionChanged) {
68
+ editor.update(() => {
69
+ if ($getNodeByKey(lastNodeKey) !== null) {
70
+ $setSelection($normalizeSelection__EXPERIMENTAL(newSelection));
71
+ }
72
+ });
73
+ }
74
+ }
75
+ });
76
+ };
77
+ return mergeRegister(editor.registerUpdateListener(sentinelNodeUpdateHandler), editor.registerNodeTransform(SentinelNode, node => {
78
+ if (!node.getPreviousSibling() || node.getNextSibling()) {
79
+ node.remove();
80
+ return;
81
+ }
82
+ }));
83
+ }
84
+ //# sourceMappingURL=SentinelNodeHandlers.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["SentinelNodeHandlers.ts"],"sourcesContent":["import {\n $getLeafNodes,\n $getNodeByKey,\n $getRoot,\n $getSelection,\n $isRangeSelection,\n $normalizeSelection__EXPERIMENTAL,\n $setSelection,\n LexicalEditor,\n mergeRegister,\n UpdateListener,\n} from '@fluentui-copilot/text-editor';\nimport { $isSentinelNode, $createSentinelNode, SentinelNode } from './SentinelNode';\n\nexport function registerSentinelNodeHandlers(editor: LexicalEditor) {\n // Add a sentinel node at the end of the input when there is content.\n // This sentinel node fixes a number of issues.\n // In Safari, Lexical's behaviour of adding <br /> tags to the end of the input when it ends\n // in a decorator node causes cursor location issues: https://github.com/facebook/lexical/issues/4487\n // Otherwise, when a decorator node is the last node in the input, the cursor can't move past it.\n // Adding an invisible text node that doesn't contribute to the content and can't be selected to the end of the input\n // mitigates these issues.\n const sentinelNodeUpdateHandler: UpdateListener = ({ editorState }) => {\n editorState.read(() => {\n const leaves = $getLeafNodes($getRoot());\n if (leaves.length === 0) {\n return;\n }\n\n const lastNode = leaves[leaves.length - 1];\n const lastNodeKey = lastNode.getKey();\n\n // If the last node isn't a sentinel, add one\n if (!$isSentinelNode(lastNode)) {\n editor.update(\n () => {\n // We find the node by its key again in case the node was removed before this update runs\n $getNodeByKey(lastNodeKey)?.insertAfter($createSentinelNode());\n },\n // historic tag prevents every sentinel node addition from being added to LexicalHistoryPlugin undo stack\n { discrete: true, tag: 'historic' },\n );\n return;\n }\n\n // If the sentinel node is not selected, we're done\n const previous = lastNode.getPreviousSibling();\n if (!previous || !lastNode.isSelected()) {\n return;\n }\n\n const selection = $getSelection();\n if (!$isRangeSelection(selection)) {\n return;\n }\n\n // If the cursor is inside the sentinel node, move it out (next to the beginning)\n // We allow selection on the boundary of the sentinel in case the adjacent node is a decorator node\n // where selection is ill-defined.\n if (selection.isCollapsed() && selection.anchor.offset > 0) {\n editor.update(() => {\n $getNodeByKey(lastNodeKey)?.selectStart();\n });\n return;\n }\n\n // If the selection is a range which includes the sentinel, modify the range to exclude it\n if (!selection.isCollapsed()) {\n let selectionChanged = false;\n const newSelection = selection.clone();\n\n if (newSelection.anchor.getNode() === lastNode && newSelection.anchor.offset > 0) {\n newSelection.anchor.set(lastNodeKey, 0, 'text');\n selectionChanged = true;\n }\n if (newSelection.focus.getNode() === lastNode && newSelection.focus.offset > 0) {\n newSelection.focus.set(lastNodeKey, 0, 'text');\n selectionChanged = true;\n }\n\n if (selectionChanged) {\n editor.update(() => {\n if ($getNodeByKey(lastNodeKey) !== null) {\n $setSelection($normalizeSelection__EXPERIMENTAL(newSelection));\n }\n });\n }\n }\n });\n };\n\n return mergeRegister(\n editor.registerUpdateListener(sentinelNodeUpdateHandler),\n editor.registerNodeTransform(SentinelNode, node => {\n if (!node.getPreviousSibling() || node.getNextSibling()) {\n node.remove();\n return;\n }\n }),\n );\n}\n"],"names":["$getLeafNodes","$getNodeByKey","$getRoot","$getSelection","$isRangeSelection","$normalizeSelection__EXPERIMENTAL","$setSelection","mergeRegister","$isSentinelNode","$createSentinelNode","SentinelNode","registerSentinelNodeHandlers","editor","sentinelNodeUpdateHandler","editorState","read","leaves","length","lastNode","lastNodeKey","getKey","update","insertAfter","discrete","tag","previous","getPreviousSibling","isSelected","selection","isCollapsed","anchor","offset","selectStart","selectionChanged","newSelection","clone","getNode","set","focus","registerUpdateListener","registerNodeTransform","node","getNextSibling","remove"],"rangeMappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;","mappings":"AAAA,SACEA,aAAa,EACbC,aAAa,EACbC,QAAQ,EACRC,aAAa,EACbC,iBAAiB,EACjBC,iCAAiC,EACjCC,aAAa,EAEbC,aAAa,QAER,gCAAgC;AACvC,SAASC,eAAe,EAAEC,mBAAmB,EAAEC,YAAY,QAAQ,iBAAiB;AAEpF,OAAO,SAASC,6BAA6BC,MAAqB;IAChE,qEAAqE;IACrE,+CAA+C;IAC/C,4FAA4F;IAC5F,qGAAqG;IACrG,iGAAiG;IACjG,qHAAqH;IACrH,0BAA0B;IAC1B,MAAMC,4BAA4C,CAAC,EAAEC,WAAW,EAAE;QAChEA,YAAYC,IAAI,CAAC;YACf,MAAMC,SAAShB,cAAcE;YAC7B,IAAIc,OAAOC,MAAM,KAAK,GAAG;gBACvB;YACF;YAEA,MAAMC,WAAWF,MAAM,CAACA,OAAOC,MAAM,GAAG,EAAE;YAC1C,MAAME,cAAcD,SAASE,MAAM;YAEnC,6CAA6C;YAC7C,IAAI,CAACZ,gBAAgBU,WAAW;gBAC9BN,OAAOS,MAAM,CACX;wBACE,yFAAyF;oBACzFpB;qBAAAA,iBAAAA,cAAckB,0BAAdlB,qCAAAA,eAA4BqB,WAAW,CAACb;gBAC1C,GACA,yGAAyG;gBACzG;oBAAEc,UAAU;oBAAMC,KAAK;gBAAW;gBAEpC;YACF;YAEA,mDAAmD;YACnD,MAAMC,WAAWP,SAASQ,kBAAkB;YAC5C,IAAI,CAACD,YAAY,CAACP,SAASS,UAAU,IAAI;gBACvC;YACF;YAEA,MAAMC,YAAYzB;YAClB,IAAI,CAACC,kBAAkBwB,YAAY;gBACjC;YACF;YAEA,iFAAiF;YACjF,mGAAmG;YACnG,kCAAkC;YAClC,IAAIA,UAAUC,WAAW,MAAMD,UAAUE,MAAM,CAACC,MAAM,GAAG,GAAG;gBAC1DnB,OAAOS,MAAM,CAAC;wBACZpB;qBAAAA,iBAAAA,cAAckB,0BAAdlB,qCAAAA,eAA4B+B,WAAW;gBACzC;gBACA;YACF;YAEA,0FAA0F;YAC1F,IAAI,CAACJ,UAAUC,WAAW,IAAI;gBAC5B,IAAII,mBAAmB;gBACvB,MAAMC,eAAeN,UAAUO,KAAK;gBAEpC,IAAID,aAAaJ,MAAM,CAACM,OAAO,OAAOlB,YAAYgB,aAAaJ,MAAM,CAACC,MAAM,GAAG,GAAG;oBAChFG,aAAaJ,MAAM,CAACO,GAAG,CAAClB,aAAa,GAAG;oBACxCc,mBAAmB;gBACrB;gBACA,IAAIC,aAAaI,KAAK,CAACF,OAAO,OAAOlB,YAAYgB,aAAaI,KAAK,CAACP,MAAM,GAAG,GAAG;oBAC9EG,aAAaI,KAAK,CAACD,GAAG,CAAClB,aAAa,GAAG;oBACvCc,mBAAmB;gBACrB;gBAEA,IAAIA,kBAAkB;oBACpBrB,OAAOS,MAAM,CAAC;wBACZ,IAAIpB,cAAckB,iBAAiB,MAAM;4BACvCb,cAAcD,kCAAkC6B;wBAClD;oBACF;gBACF;YACF;QACF;IACF;IAEA,OAAO3B,cACLK,OAAO2B,sBAAsB,CAAC1B,4BAC9BD,OAAO4B,qBAAqB,CAAC9B,cAAc+B,CAAAA;QACzC,IAAI,CAACA,KAAKf,kBAAkB,MAAMe,KAAKC,cAAc,IAAI;YACvDD,KAAKE,MAAM;YACX;QACF;IACF;AAEJ"}
@@ -11,6 +11,7 @@ Object.defineProperty(exports, "BasicFunctionalityBase", {
11
11
  const _define_property = require("@swc/helpers/_/_define_property");
12
12
  const _texteditor = require("@fluentui-copilot/text-editor");
13
13
  const _SentinelNode = require("./SentinelNode");
14
+ const _SentinelNodeHandlers = require("./SentinelNodeHandlers");
14
15
  function isClipboardEvent(event) {
15
16
  return event.type === 'paste';
16
17
  }
@@ -63,7 +64,7 @@ class BasicFunctionalityBase {
63
64
  return true;
64
65
  }
65
66
  return false;
66
- }, _texteditor.COMMAND_PRIORITY_CRITICAL);
67
+ }, _texteditor.COMMAND_PRIORITY_HIGH);
67
68
  }
68
69
  deactivatePasteCallback() {
69
70
  var _this___pasteHandlerCleanup, _this;
@@ -108,79 +109,6 @@ class BasicFunctionalityBase {
108
109
  (0, _define_property._)(this, "__baseHandlersCleanup", void 0);
109
110
  (0, _define_property._)(this, "__pasteHandlerCleanup", void 0);
110
111
  this.__editor = editor;
111
- this.__baseHandlersCleanup = (0, _texteditor.mergeRegister)(this.__editor.registerCommand(_texteditor.KEY_ENTER_COMMAND, this.__enterHandler.bind(this), _texteditor.COMMAND_PRIORITY_CRITICAL), // Add a sentinel node at the end of the input when there is content.
112
- // This sentinel node fixes a number of issues.
113
- // In Safari, Lexical's behaviour of adding <br /> tags to the end of the input when it ends
114
- // in a decorator node causes cursor location issues: https://github.com/facebook/lexical/issues/4487
115
- // Otherwise, when a decorator node is the last node in the input, the cursor can't move past it.
116
- // Adding an invisible text node that doesn't contribute to the content and can't be selected to the end of the input
117
- // mitigates these issues.
118
- this.__editor.registerUpdateListener(({ editorState })=>{
119
- editorState.read(()=>{
120
- const leaves = (0, _texteditor.$getLeafNodes)((0, _texteditor.$getRoot)());
121
- if (leaves.length === 0) {
122
- return;
123
- }
124
- const lastNode = leaves[leaves.length - 1];
125
- const lastNodeKey = lastNode.getKey();
126
- // If the last node isn't a sentinel, add one
127
- if (!(0, _SentinelNode.$isSentinelNode)(lastNode)) {
128
- this.__editor.update(()=>{
129
- var // We find the node by its key again in case the node was removed before this update runs
130
- _$getNodeByKey;
131
- (_$getNodeByKey = (0, _texteditor.$getNodeByKey)(lastNodeKey)) === null || _$getNodeByKey === void 0 ? void 0 : _$getNodeByKey.insertAfter((0, _SentinelNode.$createSentinelNode)());
132
- }, // historic tag prevents every sentinel node addition from being added to LexicalHistoryPlugin undo stack
133
- {
134
- discrete: true,
135
- tag: 'historic'
136
- });
137
- return;
138
- }
139
- // If the sentinel node is not selected, we're done
140
- const previous = lastNode.getPreviousSibling();
141
- if (!previous || !lastNode.isSelected()) {
142
- return;
143
- }
144
- const selection = (0, _texteditor.$getSelection)();
145
- if (!(0, _texteditor.$isRangeSelection)(selection)) {
146
- return;
147
- }
148
- // If the cursor is inside the sentinel node, move it out (next to the beginning)
149
- // We allow selection on the boundary of the sentinel in case the adjacent node is a decorator node
150
- // where selection is ill-defined.
151
- if (selection.isCollapsed() && selection.anchor.offset > 0) {
152
- this.__editor.update(()=>{
153
- var _$getNodeByKey;
154
- (_$getNodeByKey = (0, _texteditor.$getNodeByKey)(lastNodeKey)) === null || _$getNodeByKey === void 0 ? void 0 : _$getNodeByKey.selectStart();
155
- });
156
- return;
157
- }
158
- // If the selection is a range which includes the sentinel, modify the range to exclude it
159
- if (!selection.isCollapsed()) {
160
- let selectionChanged = false;
161
- const newSelection = selection.clone();
162
- if (newSelection.anchor.getNode() === lastNode && newSelection.anchor.offset > 0) {
163
- newSelection.anchor.set(lastNodeKey, 0, 'text');
164
- selectionChanged = true;
165
- }
166
- if (newSelection.focus.getNode() === lastNode && newSelection.focus.offset > 0) {
167
- newSelection.focus.set(lastNodeKey, 0, 'text');
168
- selectionChanged = true;
169
- }
170
- if (selectionChanged) {
171
- this.__editor.update(()=>{
172
- if ((0, _texteditor.$getNodeByKey)(lastNodeKey) !== null) {
173
- (0, _texteditor.$setSelection)((0, _texteditor.$normalizeSelection__EXPERIMENTAL)(newSelection));
174
- }
175
- });
176
- }
177
- }
178
- });
179
- }), this.__editor.registerNodeTransform(_SentinelNode.SentinelNode, (node)=>{
180
- if (!node.getPreviousSibling() || node.getNextSibling()) {
181
- node.remove();
182
- return;
183
- }
184
- }));
112
+ this.__baseHandlersCleanup = (0, _texteditor.mergeRegister)(this.__editor.registerCommand(_texteditor.KEY_ENTER_COMMAND, this.__enterHandler.bind(this), _texteditor.COMMAND_PRIORITY_HIGH), (0, _SentinelNodeHandlers.registerSentinelNodeHandlers)(editor));
185
113
  }
186
114
  } //# sourceMappingURL=BasicFunctionality.base.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["BasicFunctionality.base.ts"],"sourcesContent":["import type { LexicalEditor } from '@fluentui-copilot/text-editor';\nimport {\n $createTextNode,\n $getLeafNodes,\n $getNodeByKey,\n $getRoot,\n $getSelection,\n $insertNodes,\n $isRangeSelection,\n $normalizeSelection__EXPERIMENTAL,\n $setSelection,\n COMMAND_PRIORITY_CRITICAL,\n INSERT_PARAGRAPH_COMMAND,\n KEY_ENTER_COMMAND,\n PASTE_COMMAND,\n mergeRegister,\n} from '@fluentui-copilot/text-editor';\nimport { $createSentinelNode, $isSentinelNode, SENTINEL_VALUE, SentinelNode } from './SentinelNode';\n\nfunction isClipboardEvent(event: ClipboardEvent | KeyboardEvent | InputEvent): event is ClipboardEvent {\n return event.type === 'paste';\n}\n\nexport interface IBasicFunctionalityBase {\n insertDefaultValue: (defaultValue: string) => void;\n setIsDisabled: (isDisabled: boolean) => void;\n activateContentCallbacks(onContentChange?: (value: string) => void, onCountChanged?: (count: number) => void): void;\n deactivateContentCallbacks(): void;\n activateTrimWhitespace(): void;\n deactivateTrimWhitespace(): void;\n activatePasteCallback(onPaste: (event: ClipboardEvent) => void): void;\n deactivatePasteCallback(): void;\n cleanup(): void;\n}\n\nexport class BasicFunctionalityBase implements IBasicFunctionalityBase {\n private __editor: LexicalEditor;\n private __contentChangeCleanup?: () => void;\n private __trimWhitespaceCleanup?: () => void;\n private __baseHandlersCleanup?: () => void;\n private __pasteHandlerCleanup?: () => void;\n\n private __enterHandler(event: KeyboardEvent) {\n const selection = $getSelection();\n if (!$isRangeSelection(selection)) {\n return false;\n }\n\n if (event === null) {\n return false;\n }\n\n event.preventDefault();\n\n if (event.shiftKey) {\n return this.__editor.dispatchCommand(INSERT_PARAGRAPH_COMMAND, undefined);\n }\n\n // Mark event handled to override default behavior\n return true;\n }\n\n constructor(editor: LexicalEditor) {\n this.__editor = editor;\n\n this.__baseHandlersCleanup = mergeRegister(\n this.__editor.registerCommand(KEY_ENTER_COMMAND, this.__enterHandler.bind(this), COMMAND_PRIORITY_CRITICAL),\n\n // Add a sentinel node at the end of the input when there is content.\n // This sentinel node fixes a number of issues.\n // In Safari, Lexical's behaviour of adding <br /> tags to the end of the input when it ends\n // in a decorator node causes cursor location issues: https://github.com/facebook/lexical/issues/4487\n // Otherwise, when a decorator node is the last node in the input, the cursor can't move past it.\n // Adding an invisible text node that doesn't contribute to the content and can't be selected to the end of the input\n // mitigates these issues.\n this.__editor.registerUpdateListener(({ editorState }) => {\n editorState.read(() => {\n const leaves = $getLeafNodes($getRoot());\n if (leaves.length === 0) {\n return;\n }\n\n const lastNode = leaves[leaves.length - 1];\n const lastNodeKey = lastNode.getKey();\n\n // If the last node isn't a sentinel, add one\n if (!$isSentinelNode(lastNode)) {\n this.__editor.update(\n () => {\n // We find the node by its key again in case the node was removed before this update runs\n $getNodeByKey(lastNodeKey)?.insertAfter($createSentinelNode());\n },\n // historic tag prevents every sentinel node addition from being added to LexicalHistoryPlugin undo stack\n { discrete: true, tag: 'historic' },\n );\n return;\n }\n\n // If the sentinel node is not selected, we're done\n const previous = lastNode.getPreviousSibling();\n if (!previous || !lastNode.isSelected()) {\n return;\n }\n\n const selection = $getSelection();\n if (!$isRangeSelection(selection)) {\n return;\n }\n\n // If the cursor is inside the sentinel node, move it out (next to the beginning)\n // We allow selection on the boundary of the sentinel in case the adjacent node is a decorator node\n // where selection is ill-defined.\n if (selection.isCollapsed() && selection.anchor.offset > 0) {\n this.__editor.update(() => {\n $getNodeByKey(lastNodeKey)?.selectStart();\n });\n return;\n }\n\n // If the selection is a range which includes the sentinel, modify the range to exclude it\n if (!selection.isCollapsed()) {\n let selectionChanged = false;\n const newSelection = selection.clone();\n\n if (newSelection.anchor.getNode() === lastNode && newSelection.anchor.offset > 0) {\n newSelection.anchor.set(lastNodeKey, 0, 'text');\n selectionChanged = true;\n }\n if (newSelection.focus.getNode() === lastNode && newSelection.focus.offset > 0) {\n newSelection.focus.set(lastNodeKey, 0, 'text');\n selectionChanged = true;\n }\n\n if (selectionChanged) {\n this.__editor.update(() => {\n if ($getNodeByKey(lastNodeKey) !== null) {\n $setSelection($normalizeSelection__EXPERIMENTAL(newSelection));\n }\n });\n }\n }\n });\n }),\n this.__editor.registerNodeTransform(SentinelNode, node => {\n if (!node.getPreviousSibling() || node.getNextSibling()) {\n node.remove();\n return;\n }\n }),\n );\n }\n\n insertDefaultValue(defaultValue: string) {\n if (defaultValue) {\n this.__editor.update(() => {\n $insertNodes([$createTextNode(defaultValue)]);\n });\n }\n }\n\n activateContentCallbacks(\n onContentChange?: ((value: string) => void) | undefined,\n onCountChanged?: ((count: number) => void) | undefined,\n ) {\n this.deactivateContentCallbacks();\n this.__contentChangeCleanup = this.__editor.registerTextContentListener(text => {\n // Remove the sentinel node\n const processed = text.replace(SENTINEL_VALUE, '');\n onContentChange?.(processed);\n onCountChanged?.(processed.length);\n });\n }\n\n deactivateContentCallbacks() {\n this.__contentChangeCleanup?.();\n this.__contentChangeCleanup = undefined;\n }\n\n activatePasteCallback(onPaste: (event: ClipboardEvent) => void) {\n this.__pasteHandlerCleanup = this.__editor.registerCommand(\n PASTE_COMMAND,\n (event: ClipboardEvent | KeyboardEvent | InputEvent) => {\n if (!isClipboardEvent(event)) {\n return false;\n }\n\n onPaste(event);\n\n if (event.defaultPrevented) {\n return true;\n }\n\n return false;\n },\n COMMAND_PRIORITY_CRITICAL,\n );\n }\n\n deactivatePasteCallback() {\n this.__pasteHandlerCleanup?.();\n this.__pasteHandlerCleanup = undefined;\n }\n\n activateTrimWhitespace() {\n this.deactivateTrimWhitespace();\n this.__trimWhitespaceCleanup = this.__editor.registerTextContentListener(text => {\n if (text.trim() === '') {\n this.__editor.update(\n () => {\n $getRoot()\n .getAllTextNodes()\n .forEach(node => {\n node.remove();\n });\n },\n // Don't allow undoing this action\n { tag: 'historic' },\n );\n }\n });\n }\n\n deactivateTrimWhitespace() {\n this.__trimWhitespaceCleanup?.();\n this.__trimWhitespaceCleanup = undefined;\n }\n\n setIsDisabled(isDisabled: boolean) {\n this.__editor.setEditable(!isDisabled);\n }\n\n cleanup() {\n this.deactivateContentCallbacks();\n this.deactivateTrimWhitespace();\n this.deactivatePasteCallback();\n this.__baseHandlersCleanup?.();\n this.__baseHandlersCleanup = undefined;\n }\n}\n"],"names":["BasicFunctionalityBase","isClipboardEvent","event","type","__enterHandler","selection","$getSelection","$isRangeSelection","preventDefault","shiftKey","__editor","dispatchCommand","INSERT_PARAGRAPH_COMMAND","insertDefaultValue","defaultValue","update","$insertNodes","onContentChange","onCountChanged","deactivateContentCallbacks","activateContentCallbacks","text","processed","__contentChangeCleanup","SENTINEL_VALUE","registerTextContentListener","length","_this___contentChangeCleanup","_this","call","undefined","__pasteHandlerCleanup","registerCommand","PASTE_COMMAND","activatePasteCallback","onPaste","defaultPrevented","COMMAND_PRIORITY_CRITICAL","_this___pasteHandlerCleanup","deactivatePasteCallback","deactivateTrimWhitespace","activateTrimWhitespace","__trimWhitespaceCleanup","trim","forEach","node","remove","_this___trimWhitespaceCleanup","isDisabled","setIsDisabled","cleanup","__baseHandlersCleanup","_this___baseHandlersCleanup","editor","constructor","registerUpdateListener","editorState","read","leaves","$getLeafNodes","$getRoot","$isSentinelNode","lastNode","$getNodeByKey","discrete","lastNodeKey","_$getNodeByKey","insertAfter","$createSentinelNode","isCollapsed","anchor","offset","selectStart","newSelection","selectionChanged","clone","focus","getNode","$setSelection","$normalizeSelection__EXPERIMENTAL","registerNodeTransform","SentinelNode","getPreviousSibling","getNextSibling"],"rangeMappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;","mappings":"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"}
1
+ {"version":3,"sources":["BasicFunctionality.base.ts"],"sourcesContent":["import type { LexicalEditor } from '@fluentui-copilot/text-editor';\nimport { COMMAND_PRIORITY_HIGH } from '@fluentui-copilot/text-editor';\nimport {\n $createTextNode,\n $getRoot,\n $getSelection,\n $insertNodes,\n $isRangeSelection,\n INSERT_PARAGRAPH_COMMAND,\n KEY_ENTER_COMMAND,\n PASTE_COMMAND,\n mergeRegister,\n} from '@fluentui-copilot/text-editor';\nimport { SENTINEL_VALUE } from './SentinelNode';\nimport { registerSentinelNodeHandlers } from './SentinelNodeHandlers';\n\nfunction isClipboardEvent(event: ClipboardEvent | KeyboardEvent | InputEvent): event is ClipboardEvent {\n return event.type === 'paste';\n}\n\nexport interface IBasicFunctionalityBase {\n insertDefaultValue: (defaultValue: string) => void;\n setIsDisabled: (isDisabled: boolean) => void;\n activateContentCallbacks(onContentChange?: (value: string) => void, onCountChanged?: (count: number) => void): void;\n deactivateContentCallbacks(): void;\n activateTrimWhitespace(): void;\n deactivateTrimWhitespace(): void;\n activatePasteCallback(onPaste: (event: ClipboardEvent) => void): void;\n deactivatePasteCallback(): void;\n cleanup(): void;\n}\n\nexport class BasicFunctionalityBase implements IBasicFunctionalityBase {\n private __editor: LexicalEditor;\n private __contentChangeCleanup?: () => void;\n private __trimWhitespaceCleanup?: () => void;\n private __baseHandlersCleanup?: () => void;\n private __pasteHandlerCleanup?: () => void;\n\n private __enterHandler(event: KeyboardEvent) {\n const selection = $getSelection();\n if (!$isRangeSelection(selection)) {\n return false;\n }\n\n if (event === null) {\n return false;\n }\n\n event.preventDefault();\n\n if (event.shiftKey) {\n return this.__editor.dispatchCommand(INSERT_PARAGRAPH_COMMAND, undefined);\n }\n\n // Mark event handled to override default behavior\n return true;\n }\n\n constructor(editor: LexicalEditor) {\n this.__editor = editor;\n\n this.__baseHandlersCleanup = mergeRegister(\n this.__editor.registerCommand(KEY_ENTER_COMMAND, this.__enterHandler.bind(this), COMMAND_PRIORITY_HIGH),\n registerSentinelNodeHandlers(editor),\n );\n }\n\n insertDefaultValue(defaultValue: string) {\n if (defaultValue) {\n this.__editor.update(() => {\n $insertNodes([$createTextNode(defaultValue)]);\n });\n }\n }\n\n activateContentCallbacks(\n onContentChange?: ((value: string) => void) | undefined,\n onCountChanged?: ((count: number) => void) | undefined,\n ) {\n this.deactivateContentCallbacks();\n this.__contentChangeCleanup = this.__editor.registerTextContentListener(text => {\n // Remove the sentinel node\n const processed = text.replace(SENTINEL_VALUE, '');\n onContentChange?.(processed);\n onCountChanged?.(processed.length);\n });\n }\n\n deactivateContentCallbacks() {\n this.__contentChangeCleanup?.();\n this.__contentChangeCleanup = undefined;\n }\n\n activatePasteCallback(onPaste: (event: ClipboardEvent) => void) {\n this.__pasteHandlerCleanup = this.__editor.registerCommand(\n PASTE_COMMAND,\n (event: ClipboardEvent | KeyboardEvent | InputEvent) => {\n if (!isClipboardEvent(event)) {\n return false;\n }\n\n onPaste(event);\n\n if (event.defaultPrevented) {\n return true;\n }\n\n return false;\n },\n COMMAND_PRIORITY_HIGH,\n );\n }\n\n deactivatePasteCallback() {\n this.__pasteHandlerCleanup?.();\n this.__pasteHandlerCleanup = undefined;\n }\n\n activateTrimWhitespace() {\n this.deactivateTrimWhitespace();\n this.__trimWhitespaceCleanup = this.__editor.registerTextContentListener(text => {\n if (text.trim() === '') {\n this.__editor.update(\n () => {\n $getRoot()\n .getAllTextNodes()\n .forEach(node => {\n node.remove();\n });\n },\n // Don't allow undoing this action\n { tag: 'historic' },\n );\n }\n });\n }\n\n deactivateTrimWhitespace() {\n this.__trimWhitespaceCleanup?.();\n this.__trimWhitespaceCleanup = undefined;\n }\n\n setIsDisabled(isDisabled: boolean) {\n this.__editor.setEditable(!isDisabled);\n }\n\n cleanup() {\n this.deactivateContentCallbacks();\n this.deactivateTrimWhitespace();\n this.deactivatePasteCallback();\n this.__baseHandlersCleanup?.();\n this.__baseHandlersCleanup = undefined;\n }\n}\n"],"names":["BasicFunctionalityBase","isClipboardEvent","event","type","__enterHandler","selection","$getSelection","$isRangeSelection","preventDefault","shiftKey","__editor","dispatchCommand","INSERT_PARAGRAPH_COMMAND","insertDefaultValue","defaultValue","update","$insertNodes","onContentChange","onCountChanged","deactivateContentCallbacks","activateContentCallbacks","text","processed","__contentChangeCleanup","SENTINEL_VALUE","registerTextContentListener","length","_this___contentChangeCleanup","_this","call","undefined","__pasteHandlerCleanup","registerCommand","PASTE_COMMAND","activatePasteCallback","onPaste","defaultPrevented","COMMAND_PRIORITY_HIGH","_this___pasteHandlerCleanup","deactivatePasteCallback","deactivateTrimWhitespace","activateTrimWhitespace","__trimWhitespaceCleanup","trim","forEach","node","remove","_this___trimWhitespaceCleanup","isDisabled","setIsDisabled","cleanup","__baseHandlersCleanup","_this___baseHandlersCleanup","editor","constructor"],"rangeMappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;","mappings":"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"}
@@ -39,9 +39,14 @@ class SentinelNode extends _texteditor.TextNode {
39
39
  isToken() {
40
40
  return true;
41
41
  }
42
+ exportJSON() {
43
+ return {
44
+ ...super.exportJSON(),
45
+ type: 'sentinel',
46
+ version: 1
47
+ };
48
+ }
42
49
  constructor(key){
43
- // 2 zero width characters will not be visible in the editor.
44
- // These also happen to be markers for CIQ to ignore the following content.
45
50
  super(SENTINEL_VALUE, key);
46
51
  }
47
52
  }
@@ -1 +1 @@
1
- {"version":3,"sources":["SentinelNode.ts"],"sourcesContent":["import type { EditorConfig, LexicalNode, NodeKey } from '@fluentui-copilot/text-editor';\nimport { TextNode } from '@fluentui-copilot/text-editor';\n\nexport const SENTINEL_VALUE = '\\u200b\\u200c';\n\nexport class SentinelNode extends TextNode {\n constructor(key?: NodeKey) {\n // 2 zero width characters will not be visible in the editor.\n // These also happen to be markers for CIQ to ignore the following content.\n super(SENTINEL_VALUE, key);\n }\n\n static getType() {\n return 'sentinel';\n }\n static clone(node: SentinelNode) {\n return new SentinelNode(node.__key);\n }\n\n createDOM(config: EditorConfig): HTMLElement {\n const element = super.createDOM(config);\n element.ariaHidden = 'true';\n return element;\n }\n\n isToken() {\n return true;\n }\n}\n\nexport function $createSentinelNode(key?: NodeKey) {\n return new SentinelNode(key);\n}\n\nexport function $isSentinelNode(node: LexicalNode | null): node is SentinelNode {\n return node instanceof SentinelNode;\n}\n"],"names":["$createSentinelNode","$isSentinelNode","SENTINEL_VALUE","SentinelNode","TextNode","getType","clone","node","__key","createDOM","config","element","ariaHidden","isToken","constructor","key"],"rangeMappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;","mappings":";;;;;;;;;;;IA8BgBA,mBAAAA;eAAAA;;IAIAC,eAAAA;eAAAA;;IA/BHC,cAAAA;eAAAA;;IAEAC,YAAAA;eAAAA;;;4BAJY;AAElB,MAAMD,iBAAiB;AAEvB,MAAMC,qBAAqBC,oBAAAA;WAOhCC,UAAOA;eACL;;WAEFC,MAAOA,IAAMC,EAAkB;eAC7B,IAAOJ,aAAIA,KAAaI,KAAKC;;cAG/BC,MAAUC,EAAoB;cAC5BC,UAAMA,KAAU,CAAAF,UAAMA;gBACtBE,UAAQC,GAAAA;eACRD;;cAGFE;eACE;;gBApBFC,GAAYC,CAAa;qEACvB;mFACA;aACA,CAAAb,gBAAMA;;AAmBV;AAEO,SAASF,oBAAoBe,GAAa;WAC/C,IAAOZ,aAAIA;AACb;AAEO,SAASF,gBAAgBM,IAAwB;WACtDA,gBAAOA;AACT"}
1
+ {"version":3,"sources":["SentinelNode.ts"],"sourcesContent":["import type { EditorConfig, LexicalNode, NodeKey, SerializedTextNode } from '@fluentui-copilot/text-editor';\nimport { TextNode } from '@fluentui-copilot/text-editor';\n\n// 2 zero width characters will not be visible in the editor.\n// \\u200b is the zero width space character which can allow line wrapping in a word\n// \\u200c is the zero width non-joiner character which prevents ligatures from connecting the two surrounding characters\n// These two characters together are a no-op and should not be present together in any legitimate user input.\nexport const SENTINEL_VALUE = '\\u200b\\u200c';\n\nexport class SentinelNode extends TextNode {\n constructor(key?: NodeKey) {\n super(SENTINEL_VALUE, key);\n }\n\n static getType() {\n return 'sentinel';\n }\n static clone(node: SentinelNode) {\n return new SentinelNode(node.__key);\n }\n\n createDOM(config: EditorConfig): HTMLElement {\n const element = super.createDOM(config);\n element.ariaHidden = 'true';\n return element;\n }\n\n isToken() {\n return true;\n }\n\n exportJSON(): SerializedTextNode {\n return {\n ...super.exportJSON(),\n type: 'sentinel',\n version: 1,\n };\n }\n}\n\nexport function $createSentinelNode(key?: NodeKey) {\n return new SentinelNode(key);\n}\n\nexport function $isSentinelNode(node: LexicalNode | null): node is SentinelNode {\n return node instanceof SentinelNode;\n}\n"],"names":["$createSentinelNode","$isSentinelNode","SENTINEL_VALUE","SentinelNode","TextNode","getType","clone","node","__key","createDOM","config","element","ariaHidden","isToken","exportJSON","type","version","constructor","key"],"rangeMappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;","mappings":";;;;;;;;;;;IAwCgBA,mBAAAA;eAAAA;;IAIAC,eAAAA;eAAAA;;IArCHC,cAAAA;eAAAA;;IAEAC,YAAAA;eAAAA;;;4BARY;AAMlB,MAAMD,iBAAiB;AAEvB,MAAMC,qBAAqBC,oBAAAA;WAKhCC,UAAOA;eACL;;WAEFC,MAAOA,IAAMC,EAAkB;eAC7B,IAAOJ,aAAIA,KAAaI,KAAKC;;cAG/BC,MAAUC,EAAoB;cAC5BC,UAAMA,KAAU,CAAAF,UAAMA;gBACtBE,UAAQC,GAAAA;eACRD;;cAGFE;eACE;;iBAGFC;eACE;oBACE,CAAGA,YAAMA;kBACTC;qBACAC;;;gBAzBJC,GAAYC,CAAa;aACvB,CAAAhB,gBAAMA;;AA2BV;AAEO,SAASF,oBAAoBkB,GAAa;WAC/C,IAAOf,aAAIA;AACb;AAEO,SAASF,gBAAgBM,IAAwB;WACtDA,gBAAOA;AACT"}
@@ -0,0 +1,89 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", {
3
+ value: true
4
+ });
5
+ Object.defineProperty(exports, "registerSentinelNodeHandlers", {
6
+ enumerable: true,
7
+ get: function() {
8
+ return registerSentinelNodeHandlers;
9
+ }
10
+ });
11
+ const _texteditor = require("@fluentui-copilot/text-editor");
12
+ const _SentinelNode = require("./SentinelNode");
13
+ function registerSentinelNodeHandlers(editor) {
14
+ // Add a sentinel node at the end of the input when there is content.
15
+ // This sentinel node fixes a number of issues.
16
+ // In Safari, Lexical's behaviour of adding <br /> tags to the end of the input when it ends
17
+ // in a decorator node causes cursor location issues: https://github.com/facebook/lexical/issues/4487
18
+ // Otherwise, when a decorator node is the last node in the input, the cursor can't move past it.
19
+ // Adding an invisible text node that doesn't contribute to the content and can't be selected to the end of the input
20
+ // mitigates these issues.
21
+ const sentinelNodeUpdateHandler = ({ editorState })=>{
22
+ editorState.read(()=>{
23
+ const leaves = (0, _texteditor.$getLeafNodes)((0, _texteditor.$getRoot)());
24
+ if (leaves.length === 0) {
25
+ return;
26
+ }
27
+ const lastNode = leaves[leaves.length - 1];
28
+ const lastNodeKey = lastNode.getKey();
29
+ // If the last node isn't a sentinel, add one
30
+ if (!(0, _SentinelNode.$isSentinelNode)(lastNode)) {
31
+ editor.update(()=>{
32
+ var // We find the node by its key again in case the node was removed before this update runs
33
+ _$getNodeByKey;
34
+ (_$getNodeByKey = (0, _texteditor.$getNodeByKey)(lastNodeKey)) === null || _$getNodeByKey === void 0 ? void 0 : _$getNodeByKey.insertAfter((0, _SentinelNode.$createSentinelNode)());
35
+ }, // historic tag prevents every sentinel node addition from being added to LexicalHistoryPlugin undo stack
36
+ {
37
+ discrete: true,
38
+ tag: 'historic'
39
+ });
40
+ return;
41
+ }
42
+ // If the sentinel node is not selected, we're done
43
+ const previous = lastNode.getPreviousSibling();
44
+ if (!previous || !lastNode.isSelected()) {
45
+ return;
46
+ }
47
+ const selection = (0, _texteditor.$getSelection)();
48
+ if (!(0, _texteditor.$isRangeSelection)(selection)) {
49
+ return;
50
+ }
51
+ // If the cursor is inside the sentinel node, move it out (next to the beginning)
52
+ // We allow selection on the boundary of the sentinel in case the adjacent node is a decorator node
53
+ // where selection is ill-defined.
54
+ if (selection.isCollapsed() && selection.anchor.offset > 0) {
55
+ editor.update(()=>{
56
+ var _$getNodeByKey;
57
+ (_$getNodeByKey = (0, _texteditor.$getNodeByKey)(lastNodeKey)) === null || _$getNodeByKey === void 0 ? void 0 : _$getNodeByKey.selectStart();
58
+ });
59
+ return;
60
+ }
61
+ // If the selection is a range which includes the sentinel, modify the range to exclude it
62
+ if (!selection.isCollapsed()) {
63
+ let selectionChanged = false;
64
+ const newSelection = selection.clone();
65
+ if (newSelection.anchor.getNode() === lastNode && newSelection.anchor.offset > 0) {
66
+ newSelection.anchor.set(lastNodeKey, 0, 'text');
67
+ selectionChanged = true;
68
+ }
69
+ if (newSelection.focus.getNode() === lastNode && newSelection.focus.offset > 0) {
70
+ newSelection.focus.set(lastNodeKey, 0, 'text');
71
+ selectionChanged = true;
72
+ }
73
+ if (selectionChanged) {
74
+ editor.update(()=>{
75
+ if ((0, _texteditor.$getNodeByKey)(lastNodeKey) !== null) {
76
+ (0, _texteditor.$setSelection)((0, _texteditor.$normalizeSelection__EXPERIMENTAL)(newSelection));
77
+ }
78
+ });
79
+ }
80
+ }
81
+ });
82
+ };
83
+ return (0, _texteditor.mergeRegister)(editor.registerUpdateListener(sentinelNodeUpdateHandler), editor.registerNodeTransform(_SentinelNode.SentinelNode, (node)=>{
84
+ if (!node.getPreviousSibling() || node.getNextSibling()) {
85
+ node.remove();
86
+ return;
87
+ }
88
+ }));
89
+ } //# sourceMappingURL=SentinelNodeHandlers.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["SentinelNodeHandlers.ts"],"sourcesContent":["import {\n $getLeafNodes,\n $getNodeByKey,\n $getRoot,\n $getSelection,\n $isRangeSelection,\n $normalizeSelection__EXPERIMENTAL,\n $setSelection,\n LexicalEditor,\n mergeRegister,\n UpdateListener,\n} from '@fluentui-copilot/text-editor';\nimport { $isSentinelNode, $createSentinelNode, SentinelNode } from './SentinelNode';\n\nexport function registerSentinelNodeHandlers(editor: LexicalEditor) {\n // Add a sentinel node at the end of the input when there is content.\n // This sentinel node fixes a number of issues.\n // In Safari, Lexical's behaviour of adding <br /> tags to the end of the input when it ends\n // in a decorator node causes cursor location issues: https://github.com/facebook/lexical/issues/4487\n // Otherwise, when a decorator node is the last node in the input, the cursor can't move past it.\n // Adding an invisible text node that doesn't contribute to the content and can't be selected to the end of the input\n // mitigates these issues.\n const sentinelNodeUpdateHandler: UpdateListener = ({ editorState }) => {\n editorState.read(() => {\n const leaves = $getLeafNodes($getRoot());\n if (leaves.length === 0) {\n return;\n }\n\n const lastNode = leaves[leaves.length - 1];\n const lastNodeKey = lastNode.getKey();\n\n // If the last node isn't a sentinel, add one\n if (!$isSentinelNode(lastNode)) {\n editor.update(\n () => {\n // We find the node by its key again in case the node was removed before this update runs\n $getNodeByKey(lastNodeKey)?.insertAfter($createSentinelNode());\n },\n // historic tag prevents every sentinel node addition from being added to LexicalHistoryPlugin undo stack\n { discrete: true, tag: 'historic' },\n );\n return;\n }\n\n // If the sentinel node is not selected, we're done\n const previous = lastNode.getPreviousSibling();\n if (!previous || !lastNode.isSelected()) {\n return;\n }\n\n const selection = $getSelection();\n if (!$isRangeSelection(selection)) {\n return;\n }\n\n // If the cursor is inside the sentinel node, move it out (next to the beginning)\n // We allow selection on the boundary of the sentinel in case the adjacent node is a decorator node\n // where selection is ill-defined.\n if (selection.isCollapsed() && selection.anchor.offset > 0) {\n editor.update(() => {\n $getNodeByKey(lastNodeKey)?.selectStart();\n });\n return;\n }\n\n // If the selection is a range which includes the sentinel, modify the range to exclude it\n if (!selection.isCollapsed()) {\n let selectionChanged = false;\n const newSelection = selection.clone();\n\n if (newSelection.anchor.getNode() === lastNode && newSelection.anchor.offset > 0) {\n newSelection.anchor.set(lastNodeKey, 0, 'text');\n selectionChanged = true;\n }\n if (newSelection.focus.getNode() === lastNode && newSelection.focus.offset > 0) {\n newSelection.focus.set(lastNodeKey, 0, 'text');\n selectionChanged = true;\n }\n\n if (selectionChanged) {\n editor.update(() => {\n if ($getNodeByKey(lastNodeKey) !== null) {\n $setSelection($normalizeSelection__EXPERIMENTAL(newSelection));\n }\n });\n }\n }\n });\n };\n\n return mergeRegister(\n editor.registerUpdateListener(sentinelNodeUpdateHandler),\n editor.registerNodeTransform(SentinelNode, node => {\n if (!node.getPreviousSibling() || node.getNextSibling()) {\n node.remove();\n return;\n }\n }),\n );\n}\n"],"names":["registerSentinelNodeHandlers","editor","sentinelNodeUpdateHandler","editorState","leaves","$getLeafNodes","$getRoot","length","lastNode","lastNodeKey","$isSentinelNode","$getNodeByKey","discrete","_$getNodeByKey","insertAfter","$createSentinelNode","previous","selection","$getSelection","$isRangeSelection","isCollapsed","anchor","offset","selectStart","newSelection","selectionChanged","clone","getNode","focus","update","$setSelection","$normalizeSelection__EXPERIMENTAL","mergeRegister","registerUpdateListener","registerNodeTransform","SentinelNode","node","getPreviousSibling","getNextSibling","remove"],"rangeMappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;","mappings":";;;;+BAcgBA;;;eAAAA;;;4BAHT;8BAC4D;AAE5D,SAASA,6BAA6BC,MAAqB;yEAChE;mDACA;gGACA;yGACA;qGACA;yHACA;8BACA;UACAC,4BAAMA,CAAAA,aACJC;oBAEMC,IAAAA,CAAAA;2BACFC,IAAAA,yBAAA,EAAAC,IAAAA,oBAAA;uBACFC,MAAA,KAAA,GAAA;;;kBAKAC,WAAAJ,MAAA,CAAAA,OAAAG,MAAA,GAAA,EAAA;kBACAE,cAAKC,SAAgBF,MAAAA;yDAEjB;kDACE,EAAAA,WAAA;6BACAG,CAAAA;iHAEF;;sCACEC,IAAAA,yBAAU,EAAAH,YAAA,MAAA,QAAAI,mBAAA,KAAA,IAAA,KAAA,IAAAA,eAAAC,WAAA,CAAAC,IAAAA,iCAAA;4HAAsB;;8BAGtC;yBAEA;;;;+DAIA;kBAEAC,WAAMC,SAAYC,kBAAAA;6BACbC,CAAAA,SAAAA,UAAkBF,IAAY;;;kBAInCA,YAAAC,IAAAA,yBAAA;kDACA,EAAAD,YAAA;;;6FAGgB;+GACZN;8CAAAA;0BACFS,WAAA,MAAAH,UAAAI,MAAA,CAAAC,MAAA,GAAA,GAAA;6BACA,CAAA;wBACFT;sCAEAF,IAAAA,yBAAA,EAAAF,YAAA,MAAA,QAAAI,mBAAA,KAAA,IAAA,KAAA,IAA0FA,eAAAU,WAAA;;;;sGAKT;2BAC7EC,WAAAA,IAAaH;uCACbI;qCACFR,UAAAS,KAAA;iCACIF,MAAAA,CAAAA,OAAmBG,OAAOnB,YAAOA,aAAYgB,MAAAA,CAAaI,MAAMN,GAAAA,GAAM;iCACxEE,MAAAA,CAAAA,GAAaI,CAAAA,aAAUnB,GAAAA;uCACvBgB;;iCAGEA,KAAAA,CAAAA,OAAkB,OAAAjB,YAAAgB,aAAAI,KAAA,CAAAN,MAAA,GAAA,GAAA;iCACpBrB,KAAO4B,CAAAA,GAAAA,CAAMpB,aAAC,GAAA;uCACRE;;sCAEJ;iCACF,CAAA;4BACFA,IAAAA,yBAAA,EAAAF,iBAAA,MAAA;4BACFqB,IAAAA,yBAAA,EAAAC,IAAAA,6CAAA,EAAAP;wBACF;oBACF;gBAEA;;;;WAMIQ,IAAAA,yBAAA,EAAA/B,OAAAgC,sBAAA,CAAA/B,4BAAAD,OAAAiC,qBAAA,CAAAC,0BAAA,EAAAC,CAAAA;QACF,IAAA,CAAAA,KAAAC,kBAAA,MAAAD,KAAAE,cAAA,IAAA;YAEJF,KAAAG,MAAA"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fluentui-copilot/chat-input-plugins",
3
- "version": "0.1.2",
3
+ "version": "0.1.4",
4
4
  "description": "A Fluent AI package for non-react specific chat input plugins.",
5
5
  "main": "lib-commonjs/index.js",
6
6
  "module": "lib/index.js",
@@ -12,7 +12,7 @@
12
12
  },
13
13
  "license": "MIT",
14
14
  "dependencies": {
15
- "@fluentui-copilot/text-editor": "^0.0.5",
15
+ "@fluentui-copilot/text-editor": "^0.0.6",
16
16
  "@swc/helpers": "^0.5.1"
17
17
  },
18
18
  "beachball": {