@fluentui-copilot/react-prompt-listbox 0.1.0 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (28) hide show
  1. package/CHANGELOG.json +16 -1
  2. package/CHANGELOG.md +11 -2
  3. package/dist/index.d.ts +17 -0
  4. package/lib/components/utils/PromptListboxFunctionality.types.js.map +1 -1
  5. package/lib/components/utils/dropdownKeyActions.js +21 -5
  6. package/lib/components/utils/dropdownKeyActions.js.map +1 -1
  7. package/lib/components/utils/{useComboboxPositioning.js → useListboxPositioning.js} +2 -2
  8. package/lib/components/utils/useListboxPositioning.js.map +1 -0
  9. package/lib/components/utils/usePromptListboxFunctionality.js +23 -33
  10. package/lib/components/utils/usePromptListboxFunctionality.js.map +1 -1
  11. package/lib/components/utils/useTriggerKeyDown.js +30 -10
  12. package/lib/components/utils/useTriggerKeyDown.js.map +1 -1
  13. package/lib/plugins/CursorPositionPlugin.js +5 -4
  14. package/lib/plugins/CursorPositionPlugin.js.map +1 -1
  15. package/lib-commonjs/components/utils/PromptListboxFunctionality.types.js.map +1 -1
  16. package/lib-commonjs/components/utils/dropdownKeyActions.js +19 -5
  17. package/lib-commonjs/components/utils/dropdownKeyActions.js.map +1 -1
  18. package/lib-commonjs/components/utils/{useComboboxPositioning.js → useListboxPositioning.js} +4 -4
  19. package/lib-commonjs/components/utils/useListboxPositioning.js.map +1 -0
  20. package/lib-commonjs/components/utils/usePromptListboxFunctionality.js +25 -31
  21. package/lib-commonjs/components/utils/usePromptListboxFunctionality.js.map +1 -1
  22. package/lib-commonjs/components/utils/useTriggerKeyDown.js +27 -10
  23. package/lib-commonjs/components/utils/useTriggerKeyDown.js.map +1 -1
  24. package/lib-commonjs/plugins/CursorPositionPlugin.js +5 -4
  25. package/lib-commonjs/plugins/CursorPositionPlugin.js.map +1 -1
  26. package/package.json +8 -8
  27. package/lib/components/utils/useComboboxPositioning.js.map +0 -1
  28. package/lib-commonjs/components/utils/useComboboxPositioning.js.map +0 -1
package/CHANGELOG.json CHANGED
@@ -2,7 +2,22 @@
2
2
  "name": "@fluentui-copilot/react-prompt-listbox",
3
3
  "entries": [
4
4
  {
5
- "date": "Wed, 21 Aug 2024 00:24:51 GMT",
5
+ "date": "Wed, 04 Sep 2024 18:11:50 GMT",
6
+ "tag": "@fluentui-copilot/react-prompt-listbox_v0.2.0",
7
+ "version": "0.2.0",
8
+ "comments": {
9
+ "patch": [
10
+ {
11
+ "author": "estebanmu@microsoft.com",
12
+ "package": "@fluentui-copilot/react-prompt-listbox",
13
+ "commit": "46ebd259e0e1cd0bdeef96b5400553b0dcc318b1",
14
+ "comment": "feat: Add arrow up navigation, consolidate usage of getDropdownKeyDown, and update cursor plugin to track both start and end positions."
15
+ }
16
+ ]
17
+ }
18
+ },
19
+ {
20
+ "date": "Wed, 21 Aug 2024 00:26:06 GMT",
6
21
  "tag": "@fluentui-copilot/react-prompt-listbox_v0.1.0",
7
22
  "version": "0.1.0",
8
23
  "comments": {
package/CHANGELOG.md CHANGED
@@ -1,12 +1,21 @@
1
1
  # Change Log - @fluentui-copilot/react-prompt-listbox
2
2
 
3
- This log was last generated on Wed, 21 Aug 2024 00:24:51 GMT and should not be manually modified.
3
+ This log was last generated on Wed, 04 Sep 2024 18:11:50 GMT and should not be manually modified.
4
4
 
5
5
  <!-- Start content -->
6
6
 
7
+ ## [0.2.0](https://github.com/microsoft/fluentai/tree/@fluentui-copilot/react-prompt-listbox_v0.2.0)
8
+
9
+ Wed, 04 Sep 2024 18:11:50 GMT
10
+ [Compare changes](https://github.com/microsoft/fluentai/compare/@fluentui-copilot/react-prompt-listbox_v0.1.0..@fluentui-copilot/react-prompt-listbox_v0.2.0)
11
+
12
+ ### Patches
13
+
14
+ - feat: Add arrow up navigation, consolidate usage of getDropdownKeyDown, and update cursor plugin to track both start and end positions. ([PR #2095](https://github.com/microsoft/fluentai/pull/2095) by estebanmu@microsoft.com)
15
+
7
16
  ## [0.1.0](https://github.com/microsoft/fluentai/tree/@fluentui-copilot/react-prompt-listbox_v0.1.0)
8
17
 
9
- Wed, 21 Aug 2024 00:24:51 GMT
18
+ Wed, 21 Aug 2024 00:26:06 GMT
10
19
  [Compare changes](https://github.com/microsoft/fluentai/compare/@fluentui-copilot/react-prompt-listbox_v0.0.4..@fluentui-copilot/react-prompt-listbox_v0.1.0)
11
20
 
12
21
  ### Minor changes
package/dist/index.d.ts CHANGED
@@ -212,6 +212,23 @@ export declare type UsePromptListboxFunctionalityParams = {
212
212
  * @default false
213
213
  */
214
214
  fluid?: boolean;
215
+ /**
216
+ * Whether to allow reaching the listbox options by arrowing up at the start of the input.
217
+ * Note, this prop is meant to be used with the following positioning props:
218
+ * ```ts
219
+ * usePromptListboxFunctionality({
220
+ * positioning: {
221
+ * position: 'above',
222
+ * fallbackPositions: ['above']
223
+ * }
224
+ * });
225
+ * ```
226
+ * This is useful when using PromptListbox with other components such as ChatInput since
227
+ * the input will always stay at the bottom therefore the listbox would always get cut.
228
+ *
229
+ * @default false
230
+ */
231
+ allowArrowUpNavigation?: boolean;
215
232
  };
216
233
 
217
234
  /**
@@ -1 +1 @@
1
- {"version":3,"sources":["PromptListboxFunctionality.types.ts"],"sourcesContent":["import type React from 'react';\nimport type { PromptListboxProps } from '../../components/PromptListbox';\nimport type { PositioningShorthand } from '@fluentui/react-components';\nimport type { EventData, EventHandler } from '@fluentui/react-utilities';\nimport type { EditorInputProps } from '@fluentui-copilot/react-editor-input';\n\n// Note: While we are removing multiselect, we are keeping the logic and disabling it\n// in case it's needed in the future.\nexport type ProcessedPromptListboxProps = Partial<\n Omit<PromptListboxProps, 'activeDescendantController' | 'multiselect'>\n> & {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n ref?: React.MutableRefObject<any>;\n};\n\nexport type UsePromptListboxFunctionality = {\n /**\n * Component to be rendered in the Input component. This should be passed to the listbox prop.\n */\n promptListbox: JSX.Element;\n /**\n * Props to be spread in the PromptInput, these props are needed for the keyboard behavior to\n * work correctly.\n */\n triggerProps: {\n ref: React.RefObject<HTMLSpanElement>;\n /**\n * Whether the listbox is being used to go through options or the user is currently typing.\n */\n isInSelectionMode: boolean;\n } & Required<Pick<EditorInputProps, 'onBlur' | 'onFocus' | 'onKeyDown'>>;\n /**\n * Ref used to point which element the listbox should be anchored to. Most use cases\n * will provide this prop to the PromptInput's EditorInput (since this is the root slot,\n * this is provided directly to the component and not the slot).\n *\n * Note: If the containerRef is the same as the trigger, the ref provided in triggerProps needs\n * to be merged with this one using `useMergedRefs(containerRef, triggerProps.ref);`\n */\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n containerRef: React.MutableRefObject<any>;\n /**\n * Plugin used to tell where the cursor is in the EditorInput, this is important for the\n * keyboard behavior. This should be passed as children in the PromptInput.\n */\n cursorPositionPlugin: JSX.Element;\n};\n\nexport type UsePromptListboxFunctionalityParams = {\n open?: boolean;\n defaultOpen?: boolean;\n onOpenChange?: EventHandler<OnOpenChangeData>;\n positioning?: PositioningShorthand;\n\n /**\n * Callback to call when the selection mode (selecting an action vs typing) changes.\n */\n onSelectionModeChange?: (isInSelectionMode: boolean) => void;\n\n /**\n * Props to be passed to the ListboxComponent\n */\n listboxProps?: ProcessedPromptListboxProps;\n\n /**\n * Whether the listbox's width should take all the available space or only\n * the required space.\n *\n * @default false\n */\n fluid?: boolean;\n};\n\nexport type OnOpenChangeData = (\n | EventData<'click', React.MouseEvent<HTMLSpanElement>>\n | EventData<'focus', React.FocusEvent<HTMLSpanElement>>\n | EventData<'keyboard', React.KeyboardEvent<HTMLSpanElement>>\n) & {\n open: boolean;\n};\n"],"names":[],"rangeMappings":"","mappings":"AAyEA,WAME"}
1
+ {"version":3,"sources":["PromptListboxFunctionality.types.ts"],"sourcesContent":["import type React from 'react';\nimport type { PromptListboxProps } from '../../components/PromptListbox';\nimport type { PositioningShorthand } from '@fluentui/react-components';\nimport type { EventData, EventHandler } from '@fluentui/react-utilities';\nimport type { EditorInputProps } from '@fluentui-copilot/react-editor-input';\n\n// Note: While we are removing multiselect, we are keeping the logic and disabling it\n// in case it's needed in the future.\nexport type ProcessedPromptListboxProps = Partial<\n Omit<PromptListboxProps, 'activeDescendantController' | 'multiselect'>\n> & {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n ref?: React.MutableRefObject<any>;\n};\n\nexport type UsePromptListboxFunctionality = {\n /**\n * Component to be rendered in the Input component. This should be passed to the listbox prop.\n */\n promptListbox: JSX.Element;\n /**\n * Props to be spread in the PromptInput, these props are needed for the keyboard behavior to\n * work correctly.\n */\n triggerProps: {\n ref: React.RefObject<HTMLSpanElement>;\n /**\n * Whether the listbox is being used to go through options or the user is currently typing.\n */\n isInSelectionMode: boolean;\n } & Required<Pick<EditorInputProps, 'onBlur' | 'onFocus' | 'onKeyDown'>>;\n /**\n * Ref used to point which element the listbox should be anchored to. Most use cases\n * will provide this prop to the PromptInput's EditorInput (since this is the root slot,\n * this is provided directly to the component and not the slot).\n *\n * Note: If the containerRef is the same as the trigger, the ref provided in triggerProps needs\n * to be merged with this one using `useMergedRefs(containerRef, triggerProps.ref);`\n */\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n containerRef: React.MutableRefObject<any>;\n /**\n * Plugin used to tell where the cursor is in the EditorInput, this is important for the\n * keyboard behavior. This should be passed as children in the PromptInput.\n */\n cursorPositionPlugin: JSX.Element;\n};\n\nexport type UsePromptListboxFunctionalityParams = {\n open?: boolean;\n defaultOpen?: boolean;\n onOpenChange?: EventHandler<OnOpenChangeData>;\n positioning?: PositioningShorthand;\n\n /**\n * Callback to call when the selection mode (selecting an action vs typing) changes.\n */\n onSelectionModeChange?: (isInSelectionMode: boolean) => void;\n\n /**\n * Props to be passed to the ListboxComponent\n */\n listboxProps?: ProcessedPromptListboxProps;\n\n /**\n * Whether the listbox's width should take all the available space or only\n * the required space.\n *\n * @default false\n */\n fluid?: boolean;\n\n /**\n * Whether to allow reaching the listbox options by arrowing up at the start of the input.\n * Note, this prop is meant to be used with the following positioning props:\n * ```ts\n * usePromptListboxFunctionality({\n * positioning: {\n * position: 'above',\n * fallbackPositions: ['above']\n * }\n * });\n * ```\n * This is useful when using PromptListbox with other components such as ChatInput since\n * the input will always stay at the bottom therefore the listbox would always get cut.\n *\n * @default false\n */\n allowArrowUpNavigation?: boolean;\n};\n\nexport type OnOpenChangeData = (\n | EventData<'click', React.MouseEvent<HTMLSpanElement>>\n | EventData<'focus', React.FocusEvent<HTMLSpanElement>>\n | EventData<'keyboard', React.KeyboardEvent<HTMLSpanElement>>\n) & {\n open: boolean;\n};\n"],"names":[],"rangeMappings":"","mappings":"AA2FA,WAME"}
@@ -7,7 +7,9 @@
7
7
  */
8
8
  export function getDropdownActionFromKey(e, options) {
9
9
  const {
10
- cursorPosition
10
+ cursorPosition,
11
+ allowArrowUpNavigation,
12
+ isInSelectionMode
11
13
  } = options;
12
14
  const code = e.key;
13
15
  const {
@@ -25,17 +27,31 @@ export function getDropdownActionFromKey(e, options) {
25
27
  return 'CloseSelect';
26
28
  }
27
29
  // navigation interactions
30
+ const atStart = allowArrowUpNavigation && (cursorPosition === 'start' || cursorPosition === 'start-end');
31
+ const atEnd = cursorPosition === 'end' || cursorPosition === 'start-end';
28
32
  if (code === keys.ArrowDown) {
29
- return cursorPosition === 'end' ? 'Next' : 'Type';
33
+ if (atEnd) {
34
+ return 'Next';
35
+ } else if (atStart && isInSelectionMode) {
36
+ return 'Next';
37
+ }
38
+ return 'Type';
30
39
  }
31
40
  if (code === keys.ArrowUp) {
32
- return cursorPosition === 'end' ? 'Previous' : 'Type';
41
+ if (atEnd && isInSelectionMode) {
42
+ return 'Previous';
43
+ } else if (atStart && !isInSelectionMode) {
44
+ return 'Next';
45
+ } else if (atStart && isInSelectionMode) {
46
+ return 'Previous';
47
+ }
48
+ return 'Type';
33
49
  }
34
50
  if (code === keys.Home) {
35
- return cursorPosition === 'end' ? 'First' : 'Type';
51
+ return atEnd || atStart ? 'First' : 'Type';
36
52
  }
37
53
  if (code === keys.End) {
38
- return cursorPosition === 'end' ? 'Last' : 'Type';
54
+ return atEnd || atStart ? 'Last' : 'Type';
39
55
  }
40
56
  if (code === keys.PageUp) {
41
57
  return 'PageUp';
@@ -1 +1 @@
1
- {"version":3,"sources":["dropdownKeyActions.ts"],"sourcesContent":["/**\n * Note, this is mainly brought from Fluent UI, only removed the closing and\n * opening logic since that's not needed for this use case.\n */\n\nimport * as keys from '@fluentui/keyboard-keys';\nimport type * as React from 'react';\nimport type { CursorPosition } from '../../plugins/CursorPositionPlugin';\n\n/**\n * enum of actions available in any type of managed dropdown control\n * e.g. combobox, select, datepicker, menu\n */\nexport type DropdownActions =\n | 'CloseSelect'\n | 'First'\n | 'Last'\n | 'Next'\n | 'None'\n | 'PageDown'\n | 'PageUp'\n | 'Previous'\n | 'Select'\n | 'Tab'\n | 'Type';\n\nexport interface DropdownActionOptions {\n open?: boolean;\n multiselect?: boolean;\n cursorPosition: CursorPosition;\n}\n\n/**\n * Converts a keyboard interaction into a defined action\n */\nexport function getDropdownActionFromKey(\n e: KeyboardEvent | React.KeyboardEvent,\n options: DropdownActionOptions,\n): DropdownActions {\n const { cursorPosition } = options;\n const code = e.key;\n const { altKey, ctrlKey, key, metaKey } = e;\n\n // typing action occurs whether open or closed\n if (key.length === 1 && code !== keys.Space && !altKey && !ctrlKey && !metaKey) {\n return 'Type';\n }\n\n // select or close actions\n if ((code === keys.ArrowUp && altKey) || code === keys.Enter) {\n return 'CloseSelect';\n }\n\n // navigation interactions\n if (code === keys.ArrowDown) {\n return cursorPosition === 'end' ? 'Next' : 'Type';\n }\n if (code === keys.ArrowUp) {\n return cursorPosition === 'end' ? 'Previous' : 'Type';\n }\n if (code === keys.Home) {\n return cursorPosition === 'end' ? 'First' : 'Type';\n }\n if (code === keys.End) {\n return cursorPosition === 'end' ? 'Last' : 'Type';\n }\n if (code === keys.PageUp) {\n return 'PageUp';\n }\n if (code === keys.PageDown) {\n return 'PageDown';\n }\n if (code === keys.Tab) {\n return 'Tab';\n }\n\n // if nothing matched, return none\n return 'None';\n}\n"],"names":["keys","getDropdownActionFromKey","e","options","cursorPosition","code","key","altKey","ctrlKey","metaKey","length","Space","ArrowUp","Enter","ArrowDown","Home","End","PageUp","PageDown","Tab"],"rangeMappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;","mappings":"AAAA;;;CAGC,GAED,YAAYA,UAAU,0BAA0B;AA2BhD;;CAEC,GACD,OAAO,SAASC,yBACdC,CAAsC,EACtCC,OAA8B;IAE9B,MAAM,EAAEC,cAAc,EAAE,GAAGD;IAC3B,MAAME,OAAOH,EAAEI,GAAG;IAClB,MAAM,EAAEC,MAAM,EAAEC,OAAO,EAAEF,GAAG,EAAEG,OAAO,EAAE,GAAGP;IAE1C,8CAA8C;IAC9C,IAAII,IAAII,MAAM,KAAK,KAAKL,SAASL,KAAKW,KAAK,IAAI,CAACJ,UAAU,CAACC,WAAW,CAACC,SAAS;QAC9E,OAAO;IACT;IAEA,0BAA0B;IAC1B,IAAI,AAACJ,SAASL,KAAKY,OAAO,IAAIL,UAAWF,SAASL,KAAKa,KAAK,EAAE;QAC5D,OAAO;IACT;IAEA,0BAA0B;IAC1B,IAAIR,SAASL,KAAKc,SAAS,EAAE;QAC3B,OAAOV,mBAAmB,QAAQ,SAAS;IAC7C;IACA,IAAIC,SAASL,KAAKY,OAAO,EAAE;QACzB,OAAOR,mBAAmB,QAAQ,aAAa;IACjD;IACA,IAAIC,SAASL,KAAKe,IAAI,EAAE;QACtB,OAAOX,mBAAmB,QAAQ,UAAU;IAC9C;IACA,IAAIC,SAASL,KAAKgB,GAAG,EAAE;QACrB,OAAOZ,mBAAmB,QAAQ,SAAS;IAC7C;IACA,IAAIC,SAASL,KAAKiB,MAAM,EAAE;QACxB,OAAO;IACT;IACA,IAAIZ,SAASL,KAAKkB,QAAQ,EAAE;QAC1B,OAAO;IACT;IACA,IAAIb,SAASL,KAAKmB,GAAG,EAAE;QACrB,OAAO;IACT;IAEA,kCAAkC;IAClC,OAAO;AACT"}
1
+ {"version":3,"sources":["dropdownKeyActions.ts"],"sourcesContent":["/**\n * Note, this is mainly brought from Fluent UI, only removed the closing and\n * opening logic since that's not needed for this use case.\n */\n\nimport * as keys from '@fluentui/keyboard-keys';\nimport type * as React from 'react';\nimport type { CursorPosition } from '../../plugins/CursorPositionPlugin';\n\n/**\n * enum of actions available in any type of managed dropdown control\n * e.g. combobox, select, datepicker, menu\n */\nexport type DropdownActions =\n | 'CloseSelect'\n | 'First'\n | 'Last'\n | 'Next'\n | 'None'\n | 'PageDown'\n | 'PageUp'\n | 'Previous'\n | 'Select'\n | 'Tab'\n | 'Type';\n\nexport interface DropdownActionOptions {\n open?: boolean;\n multiselect?: boolean;\n cursorPosition: CursorPosition;\n allowArrowUpNavigation: boolean;\n isInSelectionMode: boolean;\n}\n\n/**\n * Converts a keyboard interaction into a defined action\n */\nexport function getDropdownActionFromKey(\n e: KeyboardEvent | React.KeyboardEvent,\n options: DropdownActionOptions,\n): DropdownActions {\n const { cursorPosition, allowArrowUpNavigation, isInSelectionMode } = options;\n const code = e.key;\n const { altKey, ctrlKey, key, metaKey } = e;\n\n // typing action occurs whether open or closed\n if (key.length === 1 && code !== keys.Space && !altKey && !ctrlKey && !metaKey) {\n return 'Type';\n }\n\n // select or close actions\n if ((code === keys.ArrowUp && altKey) || code === keys.Enter) {\n return 'CloseSelect';\n }\n\n // navigation interactions\n const atStart = allowArrowUpNavigation && (cursorPosition === 'start' || cursorPosition === 'start-end');\n const atEnd = cursorPosition === 'end' || cursorPosition === 'start-end';\n if (code === keys.ArrowDown) {\n if (atEnd) {\n return 'Next';\n } else if (atStart && isInSelectionMode) {\n return 'Next';\n }\n return 'Type';\n }\n if (code === keys.ArrowUp) {\n if (atEnd && isInSelectionMode) {\n return 'Previous';\n } else if (atStart && !isInSelectionMode) {\n return 'Next';\n } else if (atStart && isInSelectionMode) {\n return 'Previous';\n }\n return 'Type';\n }\n if (code === keys.Home) {\n return atEnd || atStart ? 'First' : 'Type';\n }\n if (code === keys.End) {\n return atEnd || atStart ? 'Last' : 'Type';\n }\n if (code === keys.PageUp) {\n return 'PageUp';\n }\n if (code === keys.PageDown) {\n return 'PageDown';\n }\n if (code === keys.Tab) {\n return 'Tab';\n }\n\n // if nothing matched, return none\n return 'None';\n}\n"],"names":["keys","getDropdownActionFromKey","e","options","cursorPosition","allowArrowUpNavigation","isInSelectionMode","code","key","altKey","ctrlKey","metaKey","length","Space","ArrowUp","Enter","atStart","atEnd","ArrowDown","Home","End","PageUp","PageDown","Tab"],"rangeMappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;","mappings":"AAAA;;;CAGC,GAED,YAAYA,UAAU,0BAA0B;AA6BhD;;CAEC,GACD,OAAO,SAASC,yBACdC,CAAsC,EACtCC,OAA8B;IAE9B,MAAM,EAAEC,cAAc,EAAEC,sBAAsB,EAAEC,iBAAiB,EAAE,GAAGH;IACtE,MAAMI,OAAOL,EAAEM,GAAG;IAClB,MAAM,EAAEC,MAAM,EAAEC,OAAO,EAAEF,GAAG,EAAEG,OAAO,EAAE,GAAGT;IAE1C,8CAA8C;IAC9C,IAAIM,IAAII,MAAM,KAAK,KAAKL,SAASP,KAAKa,KAAK,IAAI,CAACJ,UAAU,CAACC,WAAW,CAACC,SAAS;QAC9E,OAAO;IACT;IAEA,0BAA0B;IAC1B,IAAI,AAACJ,SAASP,KAAKc,OAAO,IAAIL,UAAWF,SAASP,KAAKe,KAAK,EAAE;QAC5D,OAAO;IACT;IAEA,0BAA0B;IAC1B,MAAMC,UAAUX,0BAA2BD,CAAAA,mBAAmB,WAAWA,mBAAmB,WAAU;IACtG,MAAMa,QAAQb,mBAAmB,SAASA,mBAAmB;IAC7D,IAAIG,SAASP,KAAKkB,SAAS,EAAE;QAC3B,IAAID,OAAO;YACT,OAAO;QACT,OAAO,IAAID,WAAWV,mBAAmB;YACvC,OAAO;QACT;QACA,OAAO;IACT;IACA,IAAIC,SAASP,KAAKc,OAAO,EAAE;QACzB,IAAIG,SAASX,mBAAmB;YAC9B,OAAO;QACT,OAAO,IAAIU,WAAW,CAACV,mBAAmB;YACxC,OAAO;QACT,OAAO,IAAIU,WAAWV,mBAAmB;YACvC,OAAO;QACT;QACA,OAAO;IACT;IACA,IAAIC,SAASP,KAAKmB,IAAI,EAAE;QACtB,OAAOF,SAASD,UAAU,UAAU;IACtC;IACA,IAAIT,SAASP,KAAKoB,GAAG,EAAE;QACrB,OAAOH,SAASD,UAAU,SAAS;IACrC;IACA,IAAIT,SAASP,KAAKqB,MAAM,EAAE;QACxB,OAAO;IACT;IACA,IAAId,SAASP,KAAKsB,QAAQ,EAAE;QAC1B,OAAO;IACT;IACA,IAAIf,SAASP,KAAKuB,GAAG,EAAE;QACrB,OAAO;IACT;IAEA,kCAAkC;IAClC,OAAO;AACT"}
@@ -1,6 +1,6 @@
1
1
  // Brought from Fluent UI
2
2
  import { resolvePositioningShorthand, usePositioning } from '@fluentui/react-positioning';
3
- export function useComboboxPositioning(props) {
3
+ export function useListboxPositioning(props) {
4
4
  const {
5
5
  positioning,
6
6
  fluid
@@ -25,4 +25,4 @@ export function useComboboxPositioning(props) {
25
25
  } = usePositioning(popperOptions);
26
26
  return [containerRef, targetRef];
27
27
  }
28
- //# sourceMappingURL=useComboboxPositioning.js.map
28
+ //# sourceMappingURL=useListboxPositioning.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["useListboxPositioning.ts"],"sourcesContent":["// Brought from Fluent UI\n\nimport { resolvePositioningShorthand, usePositioning } from '@fluentui/react-positioning';\nimport type * as React from 'react';\nimport type { ComboboxBaseProps } from '@fluentui/react-combobox';\nimport type { UsePromptListboxFunctionalityParams } from './PromptListboxFunctionality.types';\nimport type { PositioningShorthandValue } from '@fluentui/react-positioning';\n\nexport function useListboxPositioning(\n props: ComboboxBaseProps & Required<Pick<UsePromptListboxFunctionalityParams, 'fluid'>>,\n): [\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n listboxRef: React.MutableRefObject<any>,\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n triggerRef: React.MutableRefObject<any>,\n] {\n const { positioning, fluid } = props;\n\n const fallbackPositions: PositioningShorthandValue[] = ['below'];\n\n // popper options\n const popperOptions = {\n position: 'below' as const,\n align: 'start' as const,\n offset: { crossAxis: 0, mainAxis: 2 },\n fallbackPositions: fallbackPositions,\n matchTargetSize: fluid ? ('width' as const) : undefined,\n autoSize: true,\n ...resolvePositioningShorthand(positioning),\n };\n\n const { targetRef, containerRef } = usePositioning(popperOptions);\n\n return [containerRef, targetRef];\n}\n"],"names":["resolvePositioningShorthand","usePositioning","useListboxPositioning","props","positioning","fluid","fallbackPositions","popperOptions","position","align","offset","crossAxis","mainAxis","matchTargetSize","undefined","autoSize","targetRef","containerRef"],"rangeMappings":";;;;;;;;;;;;;;;;;;;;;;;;;","mappings":"AAAA,yBAAyB;AAEzB,SAASA,2BAA2B,EAAEC,cAAc,QAAQ,8BAA8B;AAM1F,OAAO,SAASC,sBACdC,KAAuF;IAOvF,MAAM,EAAEC,WAAW,EAAEC,KAAK,EAAE,GAAGF;IAE/B,MAAMG,oBAAiD;QAAC;KAAQ;IAEhE,iBAAiB;IACjB,MAAMC,gBAAgB;QACpBC,UAAU;QACVC,OAAO;QACPC,QAAQ;YAAEC,WAAW;YAAGC,UAAU;QAAE;QACpCN,mBAAmBA;QACnBO,iBAAiBR,QAAS,UAAoBS;QAC9CC,UAAU;QACV,GAAGf,4BAA4BI,YAAY;IAC7C;IAEA,MAAM,EAAEY,SAAS,EAAEC,YAAY,EAAE,GAAGhB,eAAeM;IAEnD,OAAO;QAACU;QAAcD;KAAU;AAClC"}
@@ -1,12 +1,10 @@
1
1
  import * as React from 'react';
2
2
  import { useActiveDescendant } from '@fluentui/react-aria';
3
- import { mergeCallbacks, useControllableState, useEventCallback, useMergedRefs } from '@fluentui/react-utilities';
4
- import { getDropdownActionFromKey } from './dropdownKeyActions';
3
+ import { useControllableState, useMergedRefs } from '@fluentui/react-utilities';
5
4
  import { CursorPositionPlugin } from '../../plugins/CursorPositionPlugin';
6
5
  import { useOptionCollection } from './useOptionCollection';
7
6
  import { useSelection } from './useSelection';
8
- import { ArrowLeft, ArrowRight } from '@fluentui/keyboard-keys';
9
- import { useComboboxPositioning } from './useComboboxPositioning';
7
+ import { useListboxPositioning } from './useListboxPositioning';
10
8
  import { useTriggerKeydown } from './useTriggerKeyDown';
11
9
  import { PromptListbox } from '../PromptListbox';
12
10
  import { promptOptionClassNames } from '../PromptOption';
@@ -16,7 +14,8 @@ export function usePromptListboxFunctionality(params) {
16
14
  onOpenChange,
17
15
  onSelectionModeChange,
18
16
  listboxProps,
19
- fluid = false
17
+ fluid = false,
18
+ allowArrowUpNavigation = false
20
19
  } = params;
21
20
  const {
22
21
  listboxRef: activeDescendantListboxRef,
@@ -42,6 +41,16 @@ export function usePromptListboxFunctionality(params) {
42
41
  defaultState: params.defaultOpen,
43
42
  initialState: false
44
43
  });
44
+ const setSelectionMode = React.useCallback(selectionMode => {
45
+ if (selectionMode === false) {
46
+ activeDescendantController.blur();
47
+ setHideActiveDescendant(true);
48
+ } else {
49
+ setHideActiveDescendant(false);
50
+ }
51
+ setIsInSelectionMode(selectionMode);
52
+ onSelectionModeChange === null || onSelectionModeChange === void 0 ? void 0 : onSelectionModeChange(selectionMode);
53
+ }, [activeDescendantController, onSelectionModeChange]);
45
54
  const onBlur = event => {
46
55
  setOpen(false);
47
56
  onOpenChange === null || onOpenChange === void 0 ? void 0 : onOpenChange(event, {
@@ -49,8 +58,7 @@ export function usePromptListboxFunctionality(params) {
49
58
  type: 'focus',
50
59
  open: false
51
60
  });
52
- activeDescendantController.blur();
53
- setHideActiveDescendant(true);
61
+ setSelectionMode(false);
54
62
  };
55
63
  const onFocus = event => {
56
64
  if (event.target === event.currentTarget) {
@@ -66,45 +74,27 @@ export function usePromptListboxFunctionality(params) {
66
74
  setCursorPosition: setCursorPosition
67
75
  });
68
76
  const onListboxBlur = React.useCallback(() => {
69
- setIsInSelectionMode(false);
77
+ setSelectionMode(false);
70
78
  onSelectionModeChange === null || onSelectionModeChange === void 0 ? void 0 : onSelectionModeChange(false);
71
- }, [onSelectionModeChange]);
79
+ }, [onSelectionModeChange, setSelectionMode]);
72
80
  // handle combobox keyboard interaction
73
81
  const onKeyDown = useTriggerKeydown({
74
82
  ...optionCollection,
83
+ allowArrowUpNavigation,
75
84
  activeDescendantController,
76
85
  getOptionById,
77
86
  onBlur: onListboxBlur,
78
87
  selectOption,
79
88
  cursorPosition,
80
89
  open,
81
- multiselect: false
90
+ multiselect: false,
91
+ isInSelectionMode,
92
+ setSelectionMode
82
93
  });
83
94
  // NVDA and JAWS have bugs that suppress reading the input value text when aria-activedescendant is set
84
95
  // To prevent this, we clear the HTML attribute (but save the state) when a user presses left/right arrows
85
96
  // ref: https://github.com/microsoft/fluentui/issues/26359#issuecomment-1397759888
86
97
  const [hideActiveDescendant, setHideActiveDescendant] = React.useState(false);
87
- /**
88
- * Freeform combobox should not select
89
- */
90
- const onInputTriggerKeyDown = useEventCallback(event => {
91
- // update typing state to true if the user is typing
92
- const action = getDropdownActionFromKey(event, {
93
- open,
94
- multiselect: false,
95
- cursorPosition
96
- });
97
- if (event.key === ArrowLeft || event.key === ArrowRight || action === 'Type') {
98
- activeDescendantController.blur();
99
- setHideActiveDescendant(true);
100
- setIsInSelectionMode(false);
101
- onSelectionModeChange === null || onSelectionModeChange === void 0 ? void 0 : onSelectionModeChange(false);
102
- } else if (action === 'Next' || action === 'Previous' || action === 'First' || action === 'Last' || action === 'PageUp' || action === 'PageDown') {
103
- setHideActiveDescendant(false);
104
- setIsInSelectionMode(true);
105
- onSelectionModeChange === null || onSelectionModeChange === void 0 ? void 0 : onSelectionModeChange(true);
106
- }
107
- });
108
98
  React.useEffect(() => {
109
99
  if (hideActiveDescendant) {
110
100
  var _triggerRef_current;
@@ -116,7 +106,7 @@ export function usePromptListboxFunctionality(params) {
116
106
  // eslint-disable-next-line react-compiler/react-compiler
117
107
  // eslint-disable-next-line react-hooks/exhaustive-deps
118
108
  }, [hideActiveDescendant]);
119
- const [comboboxPopupRef, comboboxTargetRef] = useComboboxPositioning({
109
+ const [comboboxPopupRef, comboboxTargetRef] = useListboxPositioning({
120
110
  positioning,
121
111
  fluid
122
112
  });
@@ -137,7 +127,7 @@ export function usePromptListboxFunctionality(params) {
137
127
  ref: triggerRef,
138
128
  onBlur,
139
129
  onFocus,
140
- onKeyDown: useEventCallback(mergeCallbacks(onKeyDown, onInputTriggerKeyDown)),
130
+ onKeyDown,
141
131
  isInSelectionMode
142
132
  },
143
133
  containerRef: comboboxTargetRef,
@@ -1 +1 @@
1
- {"version":3,"sources":["usePromptListboxFunctionality.tsx"],"sourcesContent":["import * as React from 'react';\nimport { useActiveDescendant } from '@fluentui/react-aria';\nimport { mergeCallbacks, useControllableState, useEventCallback, useMergedRefs } from '@fluentui/react-utilities';\nimport { getDropdownActionFromKey } from './dropdownKeyActions';\nimport { CursorPositionPlugin } from '../../plugins/CursorPositionPlugin';\nimport { useOptionCollection } from './useOptionCollection';\nimport { useSelection } from './useSelection';\nimport { ArrowLeft, ArrowRight } from '@fluentui/keyboard-keys';\nimport { useComboboxPositioning } from './useComboboxPositioning';\nimport { useTriggerKeydown } from './useTriggerKeyDown';\nimport { PromptListbox } from '../PromptListbox';\nimport { promptOptionClassNames } from '../PromptOption';\nimport type { CursorPosition } from '../../plugins/CursorPositionPlugin';\nimport type { EditorInputProps } from '@fluentui-copilot/react-editor-input';\nimport type {\n UsePromptListboxFunctionalityParams,\n UsePromptListboxFunctionality,\n} from './PromptListboxFunctionality.types';\n\nexport function usePromptListboxFunctionality(\n params: UsePromptListboxFunctionalityParams,\n): UsePromptListboxFunctionality {\n const { positioning, onOpenChange, onSelectionModeChange, listboxProps, fluid = false } = params;\n const {\n listboxRef: activeDescendantListboxRef,\n activeParentRef,\n controller: activeDescendantController,\n } = useActiveDescendant<HTMLSpanElement, HTMLDivElement>({\n matchOption: el => el.classList.contains(promptOptionClassNames.root),\n });\n // useMergedRefs to normalize the ref into a React.RefObject type\n const triggerRef = useMergedRefs(activeParentRef);\n const selectionState = useSelection(listboxProps ?? {});\n const { selectOption } = selectionState;\n const optionCollection = useOptionCollection();\n const { getOptionById } = optionCollection;\n const [cursorPosition, setCursorPosition] = React.useState<CursorPosition>('end');\n const [isInSelectionMode, setIsInSelectionMode] = React.useState(false);\n const [open, setOpen] = useControllableState({\n state: params.open,\n defaultState: params.defaultOpen,\n initialState: false,\n });\n\n const onBlur = (event: React.FocusEvent<HTMLSpanElement>) => {\n setOpen(false);\n onOpenChange?.(event, { event, type: 'focus', open: false });\n activeDescendantController.blur();\n setHideActiveDescendant(true);\n };\n\n const onFocus = (event: React.FocusEvent<HTMLSpanElement>) => {\n if (event.target === event.currentTarget) {\n setOpen(true);\n onOpenChange?.(event, { event, type: 'focus', open: true });\n }\n };\n\n const cursorPositionPlugin = <CursorPositionPlugin setCursorPosition={setCursorPosition} />;\n\n const onListboxBlur = React.useCallback(() => {\n setIsInSelectionMode(false);\n onSelectionModeChange?.(false);\n }, [onSelectionModeChange]);\n\n // handle combobox keyboard interaction\n const onKeyDown = useTriggerKeydown({\n ...optionCollection,\n activeDescendantController,\n getOptionById,\n onBlur: onListboxBlur,\n selectOption,\n cursorPosition,\n open,\n multiselect: false,\n });\n\n // NVDA and JAWS have bugs that suppress reading the input value text when aria-activedescendant is set\n // To prevent this, we clear the HTML attribute (but save the state) when a user presses left/right arrows\n // ref: https://github.com/microsoft/fluentui/issues/26359#issuecomment-1397759888\n const [hideActiveDescendant, setHideActiveDescendant] = React.useState(false);\n\n /**\n * Freeform combobox should not select\n */\n const onInputTriggerKeyDown: EditorInputProps['onKeyDown'] = useEventCallback(event => {\n // update typing state to true if the user is typing\n const action = getDropdownActionFromKey(event, { open, multiselect: false, cursorPosition });\n if (event.key === ArrowLeft || event.key === ArrowRight || action === 'Type') {\n activeDescendantController.blur();\n setHideActiveDescendant(true);\n setIsInSelectionMode(false);\n onSelectionModeChange?.(false);\n } else if (\n action === 'Next' ||\n action === 'Previous' ||\n action === 'First' ||\n action === 'Last' ||\n action === 'PageUp' ||\n action === 'PageDown'\n ) {\n setHideActiveDescendant(false);\n setIsInSelectionMode(true);\n onSelectionModeChange?.(true);\n }\n });\n\n React.useEffect(() => {\n if (hideActiveDescendant) {\n triggerRef.current?.removeAttribute('aria-activedescendant');\n }\n // We only want to run this when the hideActiveDescendant changes, if the triggerRef\n // is undefined, there's no need to remove theAttribute and we shouldn't be adding\n // refs as dependencies since it can blow up the number of runs.\n // eslint-disable-next-line react-compiler/react-compiler\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [hideActiveDescendant]);\n\n const [comboboxPopupRef, comboboxTargetRef] = useComboboxPositioning({ positioning, fluid });\n\n const listboxMergedRef = useMergedRefs(comboboxPopupRef, activeDescendantListboxRef, listboxProps?.ref);\n const listbox = React.useMemo(() => {\n return (\n <PromptListbox\n open={open}\n {...listboxProps}\n {...optionCollection}\n {...selectionState}\n ref={listboxMergedRef}\n activeDescendantController={activeDescendantController}\n />\n );\n }, [activeDescendantController, listboxMergedRef, listboxProps, open, optionCollection, selectionState]);\n\n return {\n promptListbox: listbox,\n triggerProps: {\n ref: triggerRef,\n onBlur,\n onFocus,\n onKeyDown: useEventCallback(mergeCallbacks(onKeyDown, onInputTriggerKeyDown)),\n isInSelectionMode,\n },\n containerRef: comboboxTargetRef,\n cursorPositionPlugin,\n };\n}\n"],"names":["React","useActiveDescendant","mergeCallbacks","useControllableState","useEventCallback","useMergedRefs","getDropdownActionFromKey","CursorPositionPlugin","useOptionCollection","useSelection","ArrowLeft","ArrowRight","useComboboxPositioning","useTriggerKeydown","PromptListbox","promptOptionClassNames","usePromptListboxFunctionality","params","positioning","onOpenChange","onSelectionModeChange","listboxProps","fluid","listboxRef","activeDescendantListboxRef","activeParentRef","controller","activeDescendantController","matchOption","el","classList","contains","root","triggerRef","selectionState","selectOption","optionCollection","getOptionById","cursorPosition","setCursorPosition","useState","isInSelectionMode","setIsInSelectionMode","open","setOpen","state","defaultState","defaultOpen","initialState","onBlur","event","type","blur","setHideActiveDescendant","onFocus","target","currentTarget","cursorPositionPlugin","onListboxBlur","useCallback","onKeyDown","multiselect","hideActiveDescendant","onInputTriggerKeyDown","action","key","useEffect","current","removeAttribute","comboboxPopupRef","comboboxTargetRef","listboxMergedRef","ref","listbox","useMemo","promptListbox","triggerProps","containerRef"],"rangeMappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;","mappings":"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"}
1
+ {"version":3,"sources":["usePromptListboxFunctionality.tsx"],"sourcesContent":["import * as React from 'react';\nimport { useActiveDescendant } from '@fluentui/react-aria';\nimport { useControllableState, useMergedRefs } from '@fluentui/react-utilities';\nimport { CursorPositionPlugin } from '../../plugins/CursorPositionPlugin';\nimport { useOptionCollection } from './useOptionCollection';\nimport { useSelection } from './useSelection';\nimport { useListboxPositioning } from './useListboxPositioning';\nimport { useTriggerKeydown } from './useTriggerKeyDown';\nimport { PromptListbox } from '../PromptListbox';\nimport { promptOptionClassNames } from '../PromptOption';\nimport type { CursorPosition } from '../../plugins/CursorPositionPlugin';\nimport type {\n UsePromptListboxFunctionalityParams,\n UsePromptListboxFunctionality,\n} from './PromptListboxFunctionality.types';\n\nexport function usePromptListboxFunctionality(\n params: UsePromptListboxFunctionalityParams,\n): UsePromptListboxFunctionality {\n const {\n positioning,\n onOpenChange,\n onSelectionModeChange,\n listboxProps,\n fluid = false,\n allowArrowUpNavigation = false,\n } = params;\n const {\n listboxRef: activeDescendantListboxRef,\n activeParentRef,\n controller: activeDescendantController,\n } = useActiveDescendant<HTMLSpanElement, HTMLDivElement>({\n matchOption: el => el.classList.contains(promptOptionClassNames.root),\n });\n // useMergedRefs to normalize the ref into a React.RefObject type\n const triggerRef = useMergedRefs(activeParentRef);\n const selectionState = useSelection(listboxProps ?? {});\n const { selectOption } = selectionState;\n const optionCollection = useOptionCollection();\n const { getOptionById } = optionCollection;\n const [cursorPosition, setCursorPosition] = React.useState<CursorPosition>('end');\n const [isInSelectionMode, setIsInSelectionMode] = React.useState(false);\n const [open, setOpen] = useControllableState({\n state: params.open,\n defaultState: params.defaultOpen,\n initialState: false,\n });\n\n const setSelectionMode = React.useCallback(\n (selectionMode: boolean) => {\n if (selectionMode === false) {\n activeDescendantController.blur();\n setHideActiveDescendant(true);\n } else {\n setHideActiveDescendant(false);\n }\n\n setIsInSelectionMode(selectionMode);\n onSelectionModeChange?.(selectionMode);\n },\n [activeDescendantController, onSelectionModeChange],\n );\n\n const onBlur = (event: React.FocusEvent<HTMLSpanElement>) => {\n setOpen(false);\n onOpenChange?.(event, { event, type: 'focus', open: false });\n setSelectionMode(false);\n };\n\n const onFocus = (event: React.FocusEvent<HTMLSpanElement>) => {\n if (event.target === event.currentTarget) {\n setOpen(true);\n onOpenChange?.(event, { event, type: 'focus', open: true });\n }\n };\n\n const cursorPositionPlugin = <CursorPositionPlugin setCursorPosition={setCursorPosition} />;\n\n const onListboxBlur = React.useCallback(() => {\n setSelectionMode(false);\n onSelectionModeChange?.(false);\n }, [onSelectionModeChange, setSelectionMode]);\n\n // handle combobox keyboard interaction\n const onKeyDown = useTriggerKeydown({\n ...optionCollection,\n allowArrowUpNavigation,\n activeDescendantController,\n getOptionById,\n onBlur: onListboxBlur,\n selectOption,\n cursorPosition,\n open,\n multiselect: false,\n isInSelectionMode,\n setSelectionMode,\n });\n\n // NVDA and JAWS have bugs that suppress reading the input value text when aria-activedescendant is set\n // To prevent this, we clear the HTML attribute (but save the state) when a user presses left/right arrows\n // ref: https://github.com/microsoft/fluentui/issues/26359#issuecomment-1397759888\n const [hideActiveDescendant, setHideActiveDescendant] = React.useState(false);\n\n React.useEffect(() => {\n if (hideActiveDescendant) {\n triggerRef.current?.removeAttribute('aria-activedescendant');\n }\n // We only want to run this when the hideActiveDescendant changes, if the triggerRef\n // is undefined, there's no need to remove theAttribute and we shouldn't be adding\n // refs as dependencies since it can blow up the number of runs.\n // eslint-disable-next-line react-compiler/react-compiler\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [hideActiveDescendant]);\n\n const [comboboxPopupRef, comboboxTargetRef] = useListboxPositioning({ positioning, fluid });\n\n const listboxMergedRef = useMergedRefs(comboboxPopupRef, activeDescendantListboxRef, listboxProps?.ref);\n const listbox = React.useMemo(() => {\n return (\n <PromptListbox\n open={open}\n {...listboxProps}\n {...optionCollection}\n {...selectionState}\n ref={listboxMergedRef}\n activeDescendantController={activeDescendantController}\n />\n );\n }, [activeDescendantController, listboxMergedRef, listboxProps, open, optionCollection, selectionState]);\n\n return {\n promptListbox: listbox,\n triggerProps: {\n ref: triggerRef,\n onBlur,\n onFocus,\n onKeyDown,\n isInSelectionMode,\n },\n containerRef: comboboxTargetRef,\n cursorPositionPlugin,\n };\n}\n"],"names":["React","useActiveDescendant","useControllableState","useMergedRefs","CursorPositionPlugin","useOptionCollection","useSelection","useListboxPositioning","useTriggerKeydown","PromptListbox","promptOptionClassNames","usePromptListboxFunctionality","params","positioning","onOpenChange","onSelectionModeChange","listboxProps","fluid","allowArrowUpNavigation","listboxRef","activeDescendantListboxRef","activeParentRef","controller","activeDescendantController","matchOption","el","classList","contains","root","triggerRef","selectionState","selectOption","optionCollection","getOptionById","cursorPosition","setCursorPosition","useState","isInSelectionMode","setIsInSelectionMode","open","setOpen","state","defaultState","defaultOpen","initialState","setSelectionMode","useCallback","selectionMode","blur","setHideActiveDescendant","onBlur","event","type","onFocus","target","currentTarget","cursorPositionPlugin","onListboxBlur","onKeyDown","multiselect","hideActiveDescendant","useEffect","current","removeAttribute","comboboxPopupRef","comboboxTargetRef","listboxMergedRef","ref","listbox","useMemo","promptListbox","triggerProps","containerRef"],"rangeMappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;","mappings":"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"}
@@ -6,6 +6,7 @@
6
6
  import { useSetKeyboardNavigation } from '@fluentui/react-tabster';
7
7
  import { useEventCallback } from '@fluentui/react-utilities';
8
8
  import { getDropdownActionFromKey } from './dropdownKeyActions';
9
+ import { ArrowLeft, ArrowRight } from '@fluentui/keyboard-keys';
9
10
  export function useTriggerKeydown(options) {
10
11
  const {
11
12
  activeDescendantController,
@@ -14,7 +15,10 @@ export function useTriggerKeydown(options) {
14
15
  multiselect,
15
16
  open,
16
17
  cursorPosition,
17
- onBlur
18
+ onBlur,
19
+ allowArrowUpNavigation,
20
+ isInSelectionMode,
21
+ setSelectionMode
18
22
  } = options;
19
23
  const getActiveOption = React.useCallback(() => {
20
24
  const activeOptionId = activeDescendantController.active();
@@ -59,12 +63,19 @@ export function useTriggerKeydown(options) {
59
63
  const action = getDropdownActionFromKey(e, {
60
64
  open,
61
65
  multiselect,
62
- cursorPosition
66
+ cursorPosition,
67
+ allowArrowUpNavigation,
68
+ isInSelectionMode
63
69
  });
64
70
  const activeOption = getActiveOption();
65
71
  const firstOption = activeDescendantController.first({
66
72
  passive: true
67
73
  });
74
+ if (e.key === ArrowLeft || e.key === ArrowRight || action === 'Type') {
75
+ setSelectionMode(false);
76
+ } else if (action === 'Next' || action === 'Previous' || action === 'First' || action === 'Last' || action === 'PageUp' || action === 'PageDown') {
77
+ setSelectionMode(true);
78
+ }
68
79
  switch (action) {
69
80
  case 'Last':
70
81
  case 'First':
@@ -75,14 +86,7 @@ export function useTriggerKeydown(options) {
75
86
  e.preventDefault();
76
87
  break;
77
88
  case 'Previous':
78
- // when active option is the first option and the action was "Previous",
79
- // this means we were in the first option and we are "leaving" the listbox
80
- if ((activeOption === null || activeOption === void 0 ? void 0 : activeOption.id) === firstOption) {
81
- blur();
82
- e.preventDefault();
83
- } else if (activeOption) {
84
- e.preventDefault();
85
- }
89
+ e.preventDefault();
86
90
  break;
87
91
  case 'Next':
88
92
  e.preventDefault();
@@ -92,18 +96,33 @@ export function useTriggerKeydown(options) {
92
96
  switch (action) {
93
97
  case 'First':
94
98
  first();
99
+ if (!isInSelectionMode) {
100
+ setSelectionMode(true);
101
+ }
95
102
  break;
96
103
  case 'Last':
97
104
  last();
105
+ if (!isInSelectionMode) {
106
+ setSelectionMode(true);
107
+ }
98
108
  break;
99
109
  case 'Next':
100
110
  next(activeOption);
111
+ if (!isInSelectionMode) {
112
+ setSelectionMode(true);
113
+ }
101
114
  break;
102
115
  case 'Previous':
116
+ // when active option is the first option and the action was "Previous",
117
+ // this means we were in the first option and we are "leaving" the listbox
103
118
  if (activeOption && activeOption.id !== firstOption) {
104
119
  previous(activeOption);
120
+ if (!isInSelectionMode) {
121
+ setSelectionMode(true);
122
+ }
105
123
  } else {
106
124
  blur();
125
+ setSelectionMode(false);
107
126
  }
108
127
  break;
109
128
  case 'PageDown':
@@ -115,6 +134,7 @@ export function useTriggerKeydown(options) {
115
134
  case 'CloseSelect':
116
135
  if (!multiselect && !(activeOption === null || activeOption === void 0 ? void 0 : activeOption.disabled)) {
117
136
  blur();
137
+ setSelectionMode(false);
118
138
  }
119
139
  // fallthrough
120
140
  case 'Select':
@@ -1 +1 @@
1
- {"version":3,"sources":["useTriggerKeyDown.ts"],"sourcesContent":["/**\n * Note, this is mainly brought from Fluent UI, only removed the closing and\n * opening logic since that's not needed for this use case and added the bluring\n * functionality.\n */\n\nimport * as React from 'react';\nimport { useSetKeyboardNavigation } from '@fluentui/react-tabster';\nimport { useEventCallback } from '@fluentui/react-utilities';\nimport { getDropdownActionFromKey } from './dropdownKeyActions';\nimport type { ActiveDescendantImperativeRef } from '@fluentui/react-aria';\nimport type { OptionCollectionState, OptionValue } from './OptionCollection.types';\nimport type { SelectionProps, SelectionState } from './Selection.types';\nimport type { CursorPosition } from '../../plugins/CursorPositionPlugin';\n\nexport function useTriggerKeydown(\n options: {\n activeDescendantController: ActiveDescendantImperativeRef;\n cursorPosition: CursorPosition;\n open: boolean;\n onBlur: () => void;\n } & OptionCollectionState &\n Pick<SelectionProps, 'multiselect'> &\n Pick<SelectionState, 'selectOption'>,\n) {\n const { activeDescendantController, getOptionById, selectOption, multiselect, open, cursorPosition, onBlur } =\n options;\n\n const getActiveOption = React.useCallback(() => {\n const activeOptionId = activeDescendantController.active();\n return activeOptionId ? getOptionById(activeOptionId) : undefined;\n }, [activeDescendantController, getOptionById]);\n\n const first = () => {\n activeDescendantController.first();\n };\n\n const last = () => {\n activeDescendantController.last();\n };\n\n const blur = () => {\n activeDescendantController.blur();\n onBlur();\n };\n\n const next = (activeOption: OptionValue | undefined) => {\n if (activeOption) {\n activeDescendantController.next();\n } else {\n activeDescendantController.first();\n }\n };\n\n const previous = (activeOption: OptionValue | undefined) => {\n if (activeOption) {\n activeDescendantController.prev();\n } else {\n activeDescendantController.first();\n }\n };\n\n const pageUp = () => {\n for (let i = 0; i < 10; i++) {\n activeDescendantController.prev();\n }\n };\n\n const pageDown = () => {\n for (let i = 0; i < 10; i++) {\n activeDescendantController.next();\n }\n };\n\n const setKeyboardNavigation = useSetKeyboardNavigation();\n return useEventCallback((e: React.KeyboardEvent<HTMLSpanElement>) => {\n const action = getDropdownActionFromKey(e, { open, multiselect, cursorPosition });\n const activeOption = getActiveOption();\n const firstOption = activeDescendantController.first({ passive: true });\n\n switch (action) {\n case 'Last':\n case 'First':\n case 'PageDown':\n case 'PageUp':\n case 'CloseSelect':\n case 'Select':\n e.preventDefault();\n break;\n case 'Previous':\n // when active option is the first option and the action was \"Previous\",\n // this means we were in the first option and we are \"leaving\" the listbox\n if (activeOption?.id === firstOption) {\n blur();\n e.preventDefault();\n } else if (activeOption) {\n e.preventDefault();\n }\n break;\n case 'Next':\n e.preventDefault();\n break;\n }\n\n setKeyboardNavigation(true);\n\n switch (action) {\n case 'First':\n first();\n break;\n case 'Last':\n last();\n break;\n case 'Next':\n next(activeOption);\n break;\n case 'Previous':\n if (activeOption && activeOption.id !== firstOption) {\n previous(activeOption);\n } else {\n blur();\n }\n break;\n case 'PageDown':\n pageDown();\n break;\n case 'PageUp':\n pageUp();\n break;\n case 'CloseSelect':\n if (!multiselect && !activeOption?.disabled) {\n blur();\n }\n // fallthrough\n case 'Select':\n activeOption && selectOption(e, activeOption);\n break;\n case 'Tab':\n !multiselect && activeOption && selectOption(e, activeOption);\n break;\n }\n });\n}\n"],"names":["React","useSetKeyboardNavigation","useEventCallback","getDropdownActionFromKey","useTriggerKeydown","options","activeDescendantController","getOptionById","selectOption","multiselect","open","cursorPosition","onBlur","getActiveOption","useCallback","activeOptionId","active","undefined","first","last","blur","next","activeOption","previous","prev","pageUp","i","pageDown","setKeyboardNavigation","e","action","firstOption","passive","preventDefault","id","disabled"],"rangeMappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;","mappings":"AAAA;;;;CAIC,GAED,YAAYA,WAAW,QAAQ;AAC/B,SAASC,wBAAwB,QAAQ,0BAA0B;AACnE,SAASC,gBAAgB,QAAQ,4BAA4B;AAC7D,SAASC,wBAAwB,QAAQ,uBAAuB;AAMhE,OAAO,SAASC,kBACdC,OAOsC;IAEtC,MAAM,EAAEC,0BAA0B,EAAEC,aAAa,EAAEC,YAAY,EAAEC,WAAW,EAAEC,IAAI,EAAEC,cAAc,EAAEC,MAAM,EAAE,GAC1GP;IAEF,MAAMQ,kBAAkBb,MAAMc,WAAW,CAAC;QACxC,MAAMC,iBAAiBT,2BAA2BU,MAAM;QACxD,OAAOD,iBAAiBR,cAAcQ,kBAAkBE;IAC1D,GAAG;QAACX;QAA4BC;KAAc;IAE9C,MAAMW,QAAQ;QACZZ,2BAA2BY,KAAK;IAClC;IAEA,MAAMC,OAAO;QACXb,2BAA2Ba,IAAI;IACjC;IAEA,MAAMC,OAAO;QACXd,2BAA2Bc,IAAI;QAC/BR;IACF;IAEA,MAAMS,OAAO,CAACC;QACZ,IAAIA,cAAc;YAChBhB,2BAA2Be,IAAI;QACjC,OAAO;YACLf,2BAA2BY,KAAK;QAClC;IACF;IAEA,MAAMK,WAAW,CAACD;QAChB,IAAIA,cAAc;YAChBhB,2BAA2BkB,IAAI;QACjC,OAAO;YACLlB,2BAA2BY,KAAK;QAClC;IACF;IAEA,MAAMO,SAAS;QACb,IAAK,IAAIC,IAAI,GAAGA,IAAI,IAAIA,IAAK;YAC3BpB,2BAA2BkB,IAAI;QACjC;IACF;IAEA,MAAMG,WAAW;QACf,IAAK,IAAID,IAAI,GAAGA,IAAI,IAAIA,IAAK;YAC3BpB,2BAA2Be,IAAI;QACjC;IACF;IAEA,MAAMO,wBAAwB3B;IAC9B,OAAOC,iBAAiB,CAAC2B;QACvB,MAAMC,SAAS3B,yBAAyB0B,GAAG;YAAEnB;YAAMD;YAAaE;QAAe;QAC/E,MAAMW,eAAeT;QACrB,MAAMkB,cAAczB,2BAA2BY,KAAK,CAAC;YAAEc,SAAS;QAAK;QAErE,OAAQF;YACN,KAAK;YACL,KAAK;YACL,KAAK;YACL,KAAK;YACL,KAAK;YACL,KAAK;gBACHD,EAAEI,cAAc;gBAChB;YACF,KAAK;gBACH,wEAAwE;gBACxE,0EAA0E;gBAC1E,IAAIX,CAAAA,yBAAAA,mCAAAA,aAAcY,EAAE,MAAKH,aAAa;oBACpCX;oBACAS,EAAEI,cAAc;gBAClB,OAAO,IAAIX,cAAc;oBACvBO,EAAEI,cAAc;gBAClB;gBACA;YACF,KAAK;gBACHJ,EAAEI,cAAc;gBAChB;QACJ;QAEAL,sBAAsB;QAEtB,OAAQE;YACN,KAAK;gBACHZ;gBACA;YACF,KAAK;gBACHC;gBACA;YACF,KAAK;gBACHE,KAAKC;gBACL;YACF,KAAK;gBACH,IAAIA,gBAAgBA,aAAaY,EAAE,KAAKH,aAAa;oBACnDR,SAASD;gBACX,OAAO;oBACLF;gBACF;gBACA;YACF,KAAK;gBACHO;gBACA;YACF,KAAK;gBACHF;gBACA;YACF,KAAK;gBACH,IAAI,CAAChB,eAAe,EAACa,yBAAAA,mCAAAA,aAAca,QAAQ,GAAE;oBAC3Cf;gBACF;YACF,cAAc;YACd,KAAK;gBACHE,gBAAgBd,aAAaqB,GAAGP;gBAChC;YACF,KAAK;gBACH,CAACb,eAAea,gBAAgBd,aAAaqB,GAAGP;gBAChD;QACJ;IACF;AACF"}
1
+ {"version":3,"sources":["useTriggerKeyDown.ts"],"sourcesContent":["/**\n * Note, this is mainly brought from Fluent UI, only removed the closing and\n * opening logic since that's not needed for this use case and added the bluring\n * functionality.\n */\n\nimport * as React from 'react';\nimport { useSetKeyboardNavigation } from '@fluentui/react-tabster';\nimport { useEventCallback } from '@fluentui/react-utilities';\nimport { getDropdownActionFromKey } from './dropdownKeyActions';\nimport { ArrowLeft, ArrowRight } from '@fluentui/keyboard-keys';\nimport type { ActiveDescendantImperativeRef } from '@fluentui/react-aria';\nimport type { OptionCollectionState, OptionValue } from './OptionCollection.types';\nimport type { SelectionProps, SelectionState } from './Selection.types';\nimport type { CursorPosition } from '../../plugins/CursorPositionPlugin';\n\nexport function useTriggerKeydown(\n options: {\n activeDescendantController: ActiveDescendantImperativeRef;\n cursorPosition: CursorPosition;\n open: boolean;\n onBlur: () => void;\n allowArrowUpNavigation: boolean;\n isInSelectionMode: boolean;\n setSelectionMode: (selectionMode: boolean) => void;\n } & OptionCollectionState &\n Pick<SelectionProps, 'multiselect'> &\n Pick<SelectionState, 'selectOption'>,\n) {\n const {\n activeDescendantController,\n getOptionById,\n selectOption,\n multiselect,\n open,\n cursorPosition,\n onBlur,\n allowArrowUpNavigation,\n isInSelectionMode,\n setSelectionMode,\n } = options;\n\n const getActiveOption = React.useCallback(() => {\n const activeOptionId = activeDescendantController.active();\n return activeOptionId ? getOptionById(activeOptionId) : undefined;\n }, [activeDescendantController, getOptionById]);\n\n const first = () => {\n activeDescendantController.first();\n };\n\n const last = () => {\n activeDescendantController.last();\n };\n\n const blur = () => {\n activeDescendantController.blur();\n onBlur();\n };\n\n const next = (activeOption: OptionValue | undefined) => {\n if (activeOption) {\n activeDescendantController.next();\n } else {\n activeDescendantController.first();\n }\n };\n\n const previous = (activeOption: OptionValue | undefined) => {\n if (activeOption) {\n activeDescendantController.prev();\n } else {\n activeDescendantController.first();\n }\n };\n\n const pageUp = () => {\n for (let i = 0; i < 10; i++) {\n activeDescendantController.prev();\n }\n };\n\n const pageDown = () => {\n for (let i = 0; i < 10; i++) {\n activeDescendantController.next();\n }\n };\n\n const setKeyboardNavigation = useSetKeyboardNavigation();\n return useEventCallback((e: React.KeyboardEvent<HTMLSpanElement>) => {\n const action = getDropdownActionFromKey(e, {\n open,\n multiselect,\n cursorPosition,\n allowArrowUpNavigation,\n isInSelectionMode,\n });\n const activeOption = getActiveOption();\n const firstOption = activeDescendantController.first({ passive: true });\n\n if (e.key === ArrowLeft || e.key === ArrowRight || action === 'Type') {\n setSelectionMode(false);\n } else if (\n action === 'Next' ||\n action === 'Previous' ||\n action === 'First' ||\n action === 'Last' ||\n action === 'PageUp' ||\n action === 'PageDown'\n ) {\n setSelectionMode(true);\n }\n\n switch (action) {\n case 'Last':\n case 'First':\n case 'PageDown':\n case 'PageUp':\n case 'CloseSelect':\n case 'Select':\n e.preventDefault();\n break;\n case 'Previous':\n e.preventDefault();\n break;\n case 'Next':\n e.preventDefault();\n break;\n }\n\n setKeyboardNavigation(true);\n\n switch (action) {\n case 'First':\n first();\n if (!isInSelectionMode) {\n setSelectionMode(true);\n }\n break;\n case 'Last':\n last();\n if (!isInSelectionMode) {\n setSelectionMode(true);\n }\n break;\n case 'Next':\n next(activeOption);\n if (!isInSelectionMode) {\n setSelectionMode(true);\n }\n break;\n case 'Previous':\n // when active option is the first option and the action was \"Previous\",\n // this means we were in the first option and we are \"leaving\" the listbox\n if (activeOption && activeOption.id !== firstOption) {\n previous(activeOption);\n if (!isInSelectionMode) {\n setSelectionMode(true);\n }\n } else {\n blur();\n setSelectionMode(false);\n }\n break;\n case 'PageDown':\n pageDown();\n break;\n case 'PageUp':\n pageUp();\n break;\n case 'CloseSelect':\n if (!multiselect && !activeOption?.disabled) {\n blur();\n setSelectionMode(false);\n }\n // fallthrough\n case 'Select':\n activeOption && selectOption(e, activeOption);\n break;\n case 'Tab':\n !multiselect && activeOption && selectOption(e, activeOption);\n break;\n }\n });\n}\n"],"names":["React","useSetKeyboardNavigation","useEventCallback","getDropdownActionFromKey","ArrowLeft","ArrowRight","useTriggerKeydown","options","activeDescendantController","getOptionById","selectOption","multiselect","open","cursorPosition","onBlur","allowArrowUpNavigation","isInSelectionMode","setSelectionMode","getActiveOption","useCallback","activeOptionId","active","undefined","first","last","blur","next","activeOption","previous","prev","pageUp","i","pageDown","setKeyboardNavigation","e","action","firstOption","passive","key","preventDefault","id","disabled"],"rangeMappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;","mappings":"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"}
@@ -21,16 +21,17 @@ export const CursorPositionPlugin = ({
21
21
  }
22
22
  // If there are no leaf nodes, the paragraph node will be selected
23
23
  if ($isElementNode(selectedNode)) {
24
- setCursorPosition('end');
24
+ setCursorPosition('start-end');
25
25
  return false;
26
- }
27
- // if the selection node is a sentinel and it matches the sentinel at the end
28
- if ($isSentinelNode(selectedNode) && !selectedNode.getNextSibling()) {
26
+ } else if ($isSentinelNode(selectedNode) && !selectedNode.getNextSibling()) {
29
27
  setCursorPosition('end');
30
28
  return false;
31
29
  } else if ($isSentinelNode(selectedNode.getNextSibling()) && selection.focus.offset === selectedNode.getTextContentSize()) {
32
30
  setCursorPosition('end');
33
31
  return false;
32
+ } else if (selectedNode.getPreviousSibling() === null && selection.focus.offset === 0) {
33
+ setCursorPosition('start');
34
+ return false;
34
35
  }
35
36
  setCursorPosition('between-text');
36
37
  return false;
@@ -1 +1 @@
1
- {"version":3,"sources":["CursorPositionPlugin.ts"],"sourcesContent":["import { $isSentinelNode } from '@fluentui-copilot/chat-input-plugins';\nimport {\n SELECTION_CHANGE_COMMAND,\n $getSelection,\n useLexicalComposerContext,\n $isRangeSelection,\n $isElementNode,\n COMMAND_PRIORITY_HIGH,\n} from '@fluentui-copilot/react-text-editor';\nimport * as React from 'react';\n\n/**\n * Position the cursor is in based on it's content. The goal\n * is to track whether the cursor is at the end of the input\n * or between text.\n */\nexport type CursorPosition = 'end' | 'between-text';\n\nexport type CursorPositionPluginProps = {\n setCursorPosition: (position: CursorPosition) => void;\n};\n\nexport const CursorPositionPlugin: React.FunctionComponent<CursorPositionPluginProps> = ({ setCursorPosition }) => {\n const [editor] = useLexicalComposerContext();\n\n React.useEffect(() => {\n const $selectionChangeHandler = () => {\n const selection = $getSelection();\n // If selection is null, the cursor is not active in the editor and we should just noop\n if (selection === null || !$isRangeSelection(selection) || !selection.isCollapsed()) {\n setCursorPosition('between-text');\n return false;\n }\n\n // Should only be one node in the selection because the selection is collapsed\n const selectedNode = selection.getNodes().at(0);\n // If there's no selected node, focus isn't in the editor\n if (!selectedNode) {\n return false;\n }\n\n // If there are no leaf nodes, the paragraph node will be selected\n if ($isElementNode(selectedNode)) {\n setCursorPosition('end');\n return false;\n }\n\n // if the selection node is a sentinel and it matches the sentinel at the end\n if ($isSentinelNode(selectedNode) && !selectedNode.getNextSibling()) {\n setCursorPosition('end');\n return false;\n }\n\n // else if the selection node is not a sentinel, check that the next sibling node is a sentinel\n // and check if the focus offset is in the last position of the node.\n else if (\n $isSentinelNode(selectedNode.getNextSibling()) &&\n selection.focus.offset === selectedNode.getTextContentSize()\n ) {\n setCursorPosition('end');\n return false;\n }\n\n setCursorPosition('between-text');\n return false;\n };\n\n return editor.registerCommand(SELECTION_CHANGE_COMMAND, $selectionChangeHandler, COMMAND_PRIORITY_HIGH);\n }, [editor, setCursorPosition]);\n\n return null;\n};\n"],"names":["$isSentinelNode","SELECTION_CHANGE_COMMAND","$getSelection","useLexicalComposerContext","$isRangeSelection","$isElementNode","COMMAND_PRIORITY_HIGH","React","CursorPositionPlugin","setCursorPosition","editor","useEffect","$selectionChangeHandler","selection","isCollapsed","selectedNode","getNodes","at","getNextSibling","focus","offset","getTextContentSize","registerCommand"],"rangeMappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;","mappings":"AAAA,SAASA,eAAe,QAAQ,uCAAuC;AACvE,SACEC,wBAAwB,EACxBC,aAAa,EACbC,yBAAyB,EACzBC,iBAAiB,EACjBC,cAAc,EACdC,qBAAqB,QAChB,sCAAsC;AAC7C,YAAYC,WAAW,QAAQ;AAa/B,OAAO,MAAMC,uBAA2E,CAAC,EAAEC,iBAAiB,EAAE;IAC5G,MAAM,CAACC,OAAO,GAAGP;IAEjBI,MAAMI,SAAS,CAAC;QACd,MAAMC,0BAA0B;YAC9B,MAAMC,YAAYX;YAClB,uFAAuF;YACvF,IAAIW,cAAc,QAAQ,CAACT,kBAAkBS,cAAc,CAACA,UAAUC,WAAW,IAAI;gBACnFL,kBAAkB;gBAClB,OAAO;YACT;YAEA,8EAA8E;YAC9E,MAAMM,eAAeF,UAAUG,QAAQ,GAAGC,EAAE,CAAC;YAC7C,yDAAyD;YACzD,IAAI,CAACF,cAAc;gBACjB,OAAO;YACT;YAEA,kEAAkE;YAClE,IAAIV,eAAeU,eAAe;gBAChCN,kBAAkB;gBAClB,OAAO;YACT;YAEA,6EAA6E;YAC7E,IAAIT,gBAAgBe,iBAAiB,CAACA,aAAaG,cAAc,IAAI;gBACnET,kBAAkB;gBAClB,OAAO;YACT,OAIK,IACHT,gBAAgBe,aAAaG,cAAc,OAC3CL,UAAUM,KAAK,CAACC,MAAM,KAAKL,aAAaM,kBAAkB,IAC1D;gBACAZ,kBAAkB;gBAClB,OAAO;YACT;YAEAA,kBAAkB;YAClB,OAAO;QACT;QAEA,OAAOC,OAAOY,eAAe,CAACrB,0BAA0BW,yBAAyBN;IACnF,GAAG;QAACI;QAAQD;KAAkB;IAE9B,OAAO;AACT,EAAE"}
1
+ {"version":3,"sources":["CursorPositionPlugin.ts"],"sourcesContent":["import { $isSentinelNode } from '@fluentui-copilot/chat-input-plugins';\nimport {\n SELECTION_CHANGE_COMMAND,\n $getSelection,\n useLexicalComposerContext,\n $isRangeSelection,\n $isElementNode,\n COMMAND_PRIORITY_HIGH,\n} from '@fluentui-copilot/react-text-editor';\nimport * as React from 'react';\n\n/**\n * Position the cursor is in based on it's content. The goal\n * is to track whether the cursor is at the end of the input\n * or between text.\n */\nexport type CursorPosition = 'start' | 'between-text' | 'end' | 'start-end';\n\nexport type CursorPositionPluginProps = {\n setCursorPosition: (position: CursorPosition) => void;\n};\n\nexport const CursorPositionPlugin: React.FunctionComponent<CursorPositionPluginProps> = ({ setCursorPosition }) => {\n const [editor] = useLexicalComposerContext();\n\n React.useEffect(() => {\n const $selectionChangeHandler = () => {\n const selection = $getSelection();\n // If selection is null, the cursor is not active in the editor and we should just noop\n if (selection === null || !$isRangeSelection(selection) || !selection.isCollapsed()) {\n setCursorPosition('between-text');\n return false;\n }\n\n // Should only be one node in the selection because the selection is collapsed\n const selectedNode = selection.getNodes().at(0);\n // If there's no selected node, focus isn't in the editor\n if (!selectedNode) {\n return false;\n }\n\n // If there are no leaf nodes, the paragraph node will be selected\n if ($isElementNode(selectedNode)) {\n setCursorPosition('start-end');\n return false;\n }\n\n // if the selection node is a sentinel and it matches the sentinel at the end\n else if ($isSentinelNode(selectedNode) && !selectedNode.getNextSibling()) {\n setCursorPosition('end');\n return false;\n }\n\n // else if the selection node is not a sentinel, check that the next sibling node is a sentinel\n // and check if the focus offset is in the last position of the node.\n else if (\n $isSentinelNode(selectedNode.getNextSibling()) &&\n selection.focus.offset === selectedNode.getTextContentSize()\n ) {\n setCursorPosition('end');\n return false;\n }\n\n // else if the selection node is not at the end and is not a sentinel, check if there's a previous\n // sibling and that we are at the start. If that's the case we are at the start.\n else if (selectedNode.getPreviousSibling() === null && selection.focus.offset === 0) {\n setCursorPosition('start');\n return false;\n }\n\n setCursorPosition('between-text');\n return false;\n };\n\n return editor.registerCommand(SELECTION_CHANGE_COMMAND, $selectionChangeHandler, COMMAND_PRIORITY_HIGH);\n }, [editor, setCursorPosition]);\n\n return null;\n};\n"],"names":["$isSentinelNode","SELECTION_CHANGE_COMMAND","$getSelection","useLexicalComposerContext","$isRangeSelection","$isElementNode","COMMAND_PRIORITY_HIGH","React","CursorPositionPlugin","setCursorPosition","editor","useEffect","$selectionChangeHandler","selection","isCollapsed","selectedNode","getNodes","at","getNextSibling","focus","offset","getTextContentSize","getPreviousSibling","registerCommand"],"rangeMappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;","mappings":"AAAA,SAASA,eAAe,QAAQ,uCAAuC;AACvE,SACEC,wBAAwB,EACxBC,aAAa,EACbC,yBAAyB,EACzBC,iBAAiB,EACjBC,cAAc,EACdC,qBAAqB,QAChB,sCAAsC;AAC7C,YAAYC,WAAW,QAAQ;AAa/B,OAAO,MAAMC,uBAA2E,CAAC,EAAEC,iBAAiB,EAAE;IAC5G,MAAM,CAACC,OAAO,GAAGP;IAEjBI,MAAMI,SAAS,CAAC;QACd,MAAMC,0BAA0B;YAC9B,MAAMC,YAAYX;YAClB,uFAAuF;YACvF,IAAIW,cAAc,QAAQ,CAACT,kBAAkBS,cAAc,CAACA,UAAUC,WAAW,IAAI;gBACnFL,kBAAkB;gBAClB,OAAO;YACT;YAEA,8EAA8E;YAC9E,MAAMM,eAAeF,UAAUG,QAAQ,GAAGC,EAAE,CAAC;YAC7C,yDAAyD;YACzD,IAAI,CAACF,cAAc;gBACjB,OAAO;YACT;YAEA,kEAAkE;YAClE,IAAIV,eAAeU,eAAe;gBAChCN,kBAAkB;gBAClB,OAAO;YACT,OAGK,IAAIT,gBAAgBe,iBAAiB,CAACA,aAAaG,cAAc,IAAI;gBACxET,kBAAkB;gBAClB,OAAO;YACT,OAIK,IACHT,gBAAgBe,aAAaG,cAAc,OAC3CL,UAAUM,KAAK,CAACC,MAAM,KAAKL,aAAaM,kBAAkB,IAC1D;gBACAZ,kBAAkB;gBAClB,OAAO;YACT,OAIK,IAAIM,aAAaO,kBAAkB,OAAO,QAAQT,UAAUM,KAAK,CAACC,MAAM,KAAK,GAAG;gBACnFX,kBAAkB;gBAClB,OAAO;YACT;YAEAA,kBAAkB;YAClB,OAAO;QACT;QAEA,OAAOC,OAAOa,eAAe,CAACtB,0BAA0BW,yBAAyBN;IACnF,GAAG;QAACI;QAAQD;KAAkB;IAE9B,OAAO;AACT,EAAE"}
@@ -1 +1 @@
1
- {"version":3,"sources":["PromptListboxFunctionality.types.ts"],"sourcesContent":["import type React from 'react';\nimport type { PromptListboxProps } from '../../components/PromptListbox';\nimport type { PositioningShorthand } from '@fluentui/react-components';\nimport type { EventData, EventHandler } from '@fluentui/react-utilities';\nimport type { EditorInputProps } from '@fluentui-copilot/react-editor-input';\n\n// Note: While we are removing multiselect, we are keeping the logic and disabling it\n// in case it's needed in the future.\nexport type ProcessedPromptListboxProps = Partial<\n Omit<PromptListboxProps, 'activeDescendantController' | 'multiselect'>\n> & {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n ref?: React.MutableRefObject<any>;\n};\n\nexport type UsePromptListboxFunctionality = {\n /**\n * Component to be rendered in the Input component. This should be passed to the listbox prop.\n */\n promptListbox: JSX.Element;\n /**\n * Props to be spread in the PromptInput, these props are needed for the keyboard behavior to\n * work correctly.\n */\n triggerProps: {\n ref: React.RefObject<HTMLSpanElement>;\n /**\n * Whether the listbox is being used to go through options or the user is currently typing.\n */\n isInSelectionMode: boolean;\n } & Required<Pick<EditorInputProps, 'onBlur' | 'onFocus' | 'onKeyDown'>>;\n /**\n * Ref used to point which element the listbox should be anchored to. Most use cases\n * will provide this prop to the PromptInput's EditorInput (since this is the root slot,\n * this is provided directly to the component and not the slot).\n *\n * Note: If the containerRef is the same as the trigger, the ref provided in triggerProps needs\n * to be merged with this one using `useMergedRefs(containerRef, triggerProps.ref);`\n */\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n containerRef: React.MutableRefObject<any>;\n /**\n * Plugin used to tell where the cursor is in the EditorInput, this is important for the\n * keyboard behavior. This should be passed as children in the PromptInput.\n */\n cursorPositionPlugin: JSX.Element;\n};\n\nexport type UsePromptListboxFunctionalityParams = {\n open?: boolean;\n defaultOpen?: boolean;\n onOpenChange?: EventHandler<OnOpenChangeData>;\n positioning?: PositioningShorthand;\n\n /**\n * Callback to call when the selection mode (selecting an action vs typing) changes.\n */\n onSelectionModeChange?: (isInSelectionMode: boolean) => void;\n\n /**\n * Props to be passed to the ListboxComponent\n */\n listboxProps?: ProcessedPromptListboxProps;\n\n /**\n * Whether the listbox's width should take all the available space or only\n * the required space.\n *\n * @default false\n */\n fluid?: boolean;\n};\n\nexport type OnOpenChangeData = (\n | EventData<'click', React.MouseEvent<HTMLSpanElement>>\n | EventData<'focus', React.FocusEvent<HTMLSpanElement>>\n | EventData<'keyboard', React.KeyboardEvent<HTMLSpanElement>>\n) & {\n open: boolean;\n};\n"],"names":[],"rangeMappings":"","mappings":""}
1
+ {"version":3,"sources":["PromptListboxFunctionality.types.ts"],"sourcesContent":["import type React from 'react';\nimport type { PromptListboxProps } from '../../components/PromptListbox';\nimport type { PositioningShorthand } from '@fluentui/react-components';\nimport type { EventData, EventHandler } from '@fluentui/react-utilities';\nimport type { EditorInputProps } from '@fluentui-copilot/react-editor-input';\n\n// Note: While we are removing multiselect, we are keeping the logic and disabling it\n// in case it's needed in the future.\nexport type ProcessedPromptListboxProps = Partial<\n Omit<PromptListboxProps, 'activeDescendantController' | 'multiselect'>\n> & {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n ref?: React.MutableRefObject<any>;\n};\n\nexport type UsePromptListboxFunctionality = {\n /**\n * Component to be rendered in the Input component. This should be passed to the listbox prop.\n */\n promptListbox: JSX.Element;\n /**\n * Props to be spread in the PromptInput, these props are needed for the keyboard behavior to\n * work correctly.\n */\n triggerProps: {\n ref: React.RefObject<HTMLSpanElement>;\n /**\n * Whether the listbox is being used to go through options or the user is currently typing.\n */\n isInSelectionMode: boolean;\n } & Required<Pick<EditorInputProps, 'onBlur' | 'onFocus' | 'onKeyDown'>>;\n /**\n * Ref used to point which element the listbox should be anchored to. Most use cases\n * will provide this prop to the PromptInput's EditorInput (since this is the root slot,\n * this is provided directly to the component and not the slot).\n *\n * Note: If the containerRef is the same as the trigger, the ref provided in triggerProps needs\n * to be merged with this one using `useMergedRefs(containerRef, triggerProps.ref);`\n */\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n containerRef: React.MutableRefObject<any>;\n /**\n * Plugin used to tell where the cursor is in the EditorInput, this is important for the\n * keyboard behavior. This should be passed as children in the PromptInput.\n */\n cursorPositionPlugin: JSX.Element;\n};\n\nexport type UsePromptListboxFunctionalityParams = {\n open?: boolean;\n defaultOpen?: boolean;\n onOpenChange?: EventHandler<OnOpenChangeData>;\n positioning?: PositioningShorthand;\n\n /**\n * Callback to call when the selection mode (selecting an action vs typing) changes.\n */\n onSelectionModeChange?: (isInSelectionMode: boolean) => void;\n\n /**\n * Props to be passed to the ListboxComponent\n */\n listboxProps?: ProcessedPromptListboxProps;\n\n /**\n * Whether the listbox's width should take all the available space or only\n * the required space.\n *\n * @default false\n */\n fluid?: boolean;\n\n /**\n * Whether to allow reaching the listbox options by arrowing up at the start of the input.\n * Note, this prop is meant to be used with the following positioning props:\n * ```ts\n * usePromptListboxFunctionality({\n * positioning: {\n * position: 'above',\n * fallbackPositions: ['above']\n * }\n * });\n * ```\n * This is useful when using PromptListbox with other components such as ChatInput since\n * the input will always stay at the bottom therefore the listbox would always get cut.\n *\n * @default false\n */\n allowArrowUpNavigation?: boolean;\n};\n\nexport type OnOpenChangeData = (\n | EventData<'click', React.MouseEvent<HTMLSpanElement>>\n | EventData<'focus', React.FocusEvent<HTMLSpanElement>>\n | EventData<'keyboard', React.KeyboardEvent<HTMLSpanElement>>\n) & {\n open: boolean;\n};\n"],"names":[],"rangeMappings":"","mappings":""}
@@ -14,7 +14,7 @@ Object.defineProperty(exports, "getDropdownActionFromKey", {
14
14
  const _interop_require_wildcard = require("@swc/helpers/_/_interop_require_wildcard");
15
15
  const _keyboardkeys = /*#__PURE__*/ _interop_require_wildcard._(require("@fluentui/keyboard-keys"));
16
16
  function getDropdownActionFromKey(e, options) {
17
- const { cursorPosition } = options;
17
+ const { cursorPosition, allowArrowUpNavigation, isInSelectionMode } = options;
18
18
  const code = e.key;
19
19
  const { altKey, ctrlKey, key, metaKey } = e;
20
20
  // typing action occurs whether open or closed
@@ -26,17 +26,31 @@ function getDropdownActionFromKey(e, options) {
26
26
  return 'CloseSelect';
27
27
  }
28
28
  // navigation interactions
29
+ const atStart = allowArrowUpNavigation && (cursorPosition === 'start' || cursorPosition === 'start-end');
30
+ const atEnd = cursorPosition === 'end' || cursorPosition === 'start-end';
29
31
  if (code === _keyboardkeys.ArrowDown) {
30
- return cursorPosition === 'end' ? 'Next' : 'Type';
32
+ if (atEnd) {
33
+ return 'Next';
34
+ } else if (atStart && isInSelectionMode) {
35
+ return 'Next';
36
+ }
37
+ return 'Type';
31
38
  }
32
39
  if (code === _keyboardkeys.ArrowUp) {
33
- return cursorPosition === 'end' ? 'Previous' : 'Type';
40
+ if (atEnd && isInSelectionMode) {
41
+ return 'Previous';
42
+ } else if (atStart && !isInSelectionMode) {
43
+ return 'Next';
44
+ } else if (atStart && isInSelectionMode) {
45
+ return 'Previous';
46
+ }
47
+ return 'Type';
34
48
  }
35
49
  if (code === _keyboardkeys.Home) {
36
- return cursorPosition === 'end' ? 'First' : 'Type';
50
+ return atEnd || atStart ? 'First' : 'Type';
37
51
  }
38
52
  if (code === _keyboardkeys.End) {
39
- return cursorPosition === 'end' ? 'Last' : 'Type';
53
+ return atEnd || atStart ? 'Last' : 'Type';
40
54
  }
41
55
  if (code === _keyboardkeys.PageUp) {
42
56
  return 'PageUp';
@@ -1 +1 @@
1
- {"version":3,"sources":["dropdownKeyActions.ts"],"sourcesContent":["/**\n * Note, this is mainly brought from Fluent UI, only removed the closing and\n * opening logic since that's not needed for this use case.\n */\n\nimport * as keys from '@fluentui/keyboard-keys';\nimport type * as React from 'react';\nimport type { CursorPosition } from '../../plugins/CursorPositionPlugin';\n\n/**\n * enum of actions available in any type of managed dropdown control\n * e.g. combobox, select, datepicker, menu\n */\nexport type DropdownActions =\n | 'CloseSelect'\n | 'First'\n | 'Last'\n | 'Next'\n | 'None'\n | 'PageDown'\n | 'PageUp'\n | 'Previous'\n | 'Select'\n | 'Tab'\n | 'Type';\n\nexport interface DropdownActionOptions {\n open?: boolean;\n multiselect?: boolean;\n cursorPosition: CursorPosition;\n}\n\n/**\n * Converts a keyboard interaction into a defined action\n */\nexport function getDropdownActionFromKey(\n e: KeyboardEvent | React.KeyboardEvent,\n options: DropdownActionOptions,\n): DropdownActions {\n const { cursorPosition } = options;\n const code = e.key;\n const { altKey, ctrlKey, key, metaKey } = e;\n\n // typing action occurs whether open or closed\n if (key.length === 1 && code !== keys.Space && !altKey && !ctrlKey && !metaKey) {\n return 'Type';\n }\n\n // select or close actions\n if ((code === keys.ArrowUp && altKey) || code === keys.Enter) {\n return 'CloseSelect';\n }\n\n // navigation interactions\n if (code === keys.ArrowDown) {\n return cursorPosition === 'end' ? 'Next' : 'Type';\n }\n if (code === keys.ArrowUp) {\n return cursorPosition === 'end' ? 'Previous' : 'Type';\n }\n if (code === keys.Home) {\n return cursorPosition === 'end' ? 'First' : 'Type';\n }\n if (code === keys.End) {\n return cursorPosition === 'end' ? 'Last' : 'Type';\n }\n if (code === keys.PageUp) {\n return 'PageUp';\n }\n if (code === keys.PageDown) {\n return 'PageDown';\n }\n if (code === keys.Tab) {\n return 'Tab';\n }\n\n // if nothing matched, return none\n return 'None';\n}\n"],"names":["cursorPosition","keys","options","key","length","altKey","ctrlKey","e","code","ArrowDown","Space","metaKey","ArrowUp","Enter","Home","End","PageUp","PageDown"],"rangeMappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;","mappings":"AAAA;;;CAGC;;;;+BAoCSA;;;eAAAA;;;;wEAlCEC;AAkCV,SAAQD,yBAAmBE,CAAAA,EAAAA,OAAAA;UAC3B,EACAF,cAAc,KAEdE;UACIC,OAAIC,EAAAA,GAAM;UACZ,EACFC,MAAA,EAEAC,OAAA,EACAH,GAAA,SACE,KACFI;kDAE0B;QAC1BJ,IAAIK,MAAAA,KAASP,KAAKQ,SAAWR,cAAAS,KAAA,IAAA,CAAAL,UAAA,CAAAC,WAAA,CAAAK,SAAA;eAC3B;;8BAEgBC;iBACTZ,cAAAA,OAAAA,IAAAA,UAAmBQ,SAAQP,cAAaY,KAAA,EAAA;QACjD,OAAA;;8BAESb;QACTQ,SAAAP,cAAAQ,SAAA,EAAA;QACA,OAAID,mBAAmB,QAAA,SAAA;;QAEvBA,SAAAP,cAAAW,OAAA,EAAA;QACA,OAAIJ,mBAAsB,QAAA,aAAA;;QAE1BA,SAAAP,cAAAa,IAAA,EAAA;QACA,OAAIN,mBAAsB,QAAE,UAAA;;QAE5BA,SAAAP,cAAAc,GAAA,EAAA;QACA,OAAIP,mBAAmB,QAAA,SAAA;;QAEvBA,SAAAP,cAAAe,MAAA,EAAA;QAEA,OAAA;;IAEF,IAAAR,SAAAP,cAAAgB,QAAA,EAAA"}
1
+ {"version":3,"sources":["dropdownKeyActions.ts"],"sourcesContent":["/**\n * Note, this is mainly brought from Fluent UI, only removed the closing and\n * opening logic since that's not needed for this use case.\n */\n\nimport * as keys from '@fluentui/keyboard-keys';\nimport type * as React from 'react';\nimport type { CursorPosition } from '../../plugins/CursorPositionPlugin';\n\n/**\n * enum of actions available in any type of managed dropdown control\n * e.g. combobox, select, datepicker, menu\n */\nexport type DropdownActions =\n | 'CloseSelect'\n | 'First'\n | 'Last'\n | 'Next'\n | 'None'\n | 'PageDown'\n | 'PageUp'\n | 'Previous'\n | 'Select'\n | 'Tab'\n | 'Type';\n\nexport interface DropdownActionOptions {\n open?: boolean;\n multiselect?: boolean;\n cursorPosition: CursorPosition;\n allowArrowUpNavigation: boolean;\n isInSelectionMode: boolean;\n}\n\n/**\n * Converts a keyboard interaction into a defined action\n */\nexport function getDropdownActionFromKey(\n e: KeyboardEvent | React.KeyboardEvent,\n options: DropdownActionOptions,\n): DropdownActions {\n const { cursorPosition, allowArrowUpNavigation, isInSelectionMode } = options;\n const code = e.key;\n const { altKey, ctrlKey, key, metaKey } = e;\n\n // typing action occurs whether open or closed\n if (key.length === 1 && code !== keys.Space && !altKey && !ctrlKey && !metaKey) {\n return 'Type';\n }\n\n // select or close actions\n if ((code === keys.ArrowUp && altKey) || code === keys.Enter) {\n return 'CloseSelect';\n }\n\n // navigation interactions\n const atStart = allowArrowUpNavigation && (cursorPosition === 'start' || cursorPosition === 'start-end');\n const atEnd = cursorPosition === 'end' || cursorPosition === 'start-end';\n if (code === keys.ArrowDown) {\n if (atEnd) {\n return 'Next';\n } else if (atStart && isInSelectionMode) {\n return 'Next';\n }\n return 'Type';\n }\n if (code === keys.ArrowUp) {\n if (atEnd && isInSelectionMode) {\n return 'Previous';\n } else if (atStart && !isInSelectionMode) {\n return 'Next';\n } else if (atStart && isInSelectionMode) {\n return 'Previous';\n }\n return 'Type';\n }\n if (code === keys.Home) {\n return atEnd || atStart ? 'First' : 'Type';\n }\n if (code === keys.End) {\n return atEnd || atStart ? 'Last' : 'Type';\n }\n if (code === keys.PageUp) {\n return 'PageUp';\n }\n if (code === keys.PageDown) {\n return 'PageDown';\n }\n if (code === keys.Tab) {\n return 'Tab';\n }\n\n // if nothing matched, return none\n return 'None';\n}\n"],"names":["cursorPosition","keys","allowArrowUpNavigation","isInSelectionMode","code","e","key","altKey","metaKey","ArrowDown","Space","ctrlKey","atStart","ArrowUp","Enter","atEnd","Home","End","PageUp"],"rangeMappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;","mappings":"AAAA;;;CAGC;;;;+BAsCSA;;;eAAAA;;;;wEApCEC;AAoCV,SAAQD,yBAAgBE,CAAAA,EAAAA,OAAsB;UAC9C,EACAF,cAAc,EAEdE,sBAAA,EACAC,iBAAc;UAEdC,OAAAC,EAAAC,GAAA;UAEA,EACAC,MAAKH,SACH,EACFE,GAAA,EAEAE,OAAA,KACAH;kDAC0CL;QAC1CM,IAAIF,MAAAA,KAASH,KAAKQ,SAAWR,cAAAS,KAAA,IAAA,CAAAH,UAAA,CAAAI,WAAA,CAAAH,SAAA;eAC3B;;8BAEWI;iBACTX,cAAOY,OAAA,IAAAN,UAAAH,SAAAH,cAAAa,KAAA,EAAA;eACT;;8BAEF;UACIV,UAASH,0BAAcD,CAAAA,mBAAA,WAAAA,mBAAA,WAAA;UACzBe,QAAIA,mBAASZ,SAAmBH,mBAAA;iBAC9BC,cAAOQ,SAAA,EAAA;YACTM,OAAO;mBACL;eACF,IAAOH,WAAIA,mBAAWT;mBACpB;;eAEF;;QAEFC,SAAIA,cAASH,OAAS,EAAE;YACtBc,SAAOA,mBAAmB;YAC5B,OAAA;QACA,OAAIX,IAAAA,WAAiB,CAAED,mBAAA;mBACrB;QACF,OAAA,IAAAS,WAAAT,mBAAA;YACA,OAAIC;;QAEJ,OAAA;;iBAESH,cAAAe,IAAA,EAAA;QACT,OAAAD,SAAAH,UAAA,UAAA;;iBAESX,cAAAgB,GAAA,EAAA;QACT,OAAAF,SAAAH,UAAA,SAAA;;QAGAR,SAAOH,cAAAiB,MAAA,EAAA;QACT,OAAA"}
@@ -3,14 +3,14 @@
3
3
  Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
- Object.defineProperty(exports, "useComboboxPositioning", {
6
+ Object.defineProperty(exports, "useListboxPositioning", {
7
7
  enumerable: true,
8
8
  get: function() {
9
- return useComboboxPositioning;
9
+ return useListboxPositioning;
10
10
  }
11
11
  });
12
12
  const _reactpositioning = require("@fluentui/react-positioning");
13
- function useComboboxPositioning(props) {
13
+ function useListboxPositioning(props) {
14
14
  const { positioning, fluid } = props;
15
15
  const fallbackPositions = [
16
16
  'below'
@@ -33,4 +33,4 @@ function useComboboxPositioning(props) {
33
33
  containerRef,
34
34
  targetRef
35
35
  ];
36
- } //# sourceMappingURL=useComboboxPositioning.js.map
36
+ } //# sourceMappingURL=useListboxPositioning.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["useListboxPositioning.ts"],"sourcesContent":["// Brought from Fluent UI\n\nimport { resolvePositioningShorthand, usePositioning } from '@fluentui/react-positioning';\nimport type * as React from 'react';\nimport type { ComboboxBaseProps } from '@fluentui/react-combobox';\nimport type { UsePromptListboxFunctionalityParams } from './PromptListboxFunctionality.types';\nimport type { PositioningShorthandValue } from '@fluentui/react-positioning';\n\nexport function useListboxPositioning(\n props: ComboboxBaseProps & Required<Pick<UsePromptListboxFunctionalityParams, 'fluid'>>,\n): [\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n listboxRef: React.MutableRefObject<any>,\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n triggerRef: React.MutableRefObject<any>,\n] {\n const { positioning, fluid } = props;\n\n const fallbackPositions: PositioningShorthandValue[] = ['below'];\n\n // popper options\n const popperOptions = {\n position: 'below' as const,\n align: 'start' as const,\n offset: { crossAxis: 0, mainAxis: 2 },\n fallbackPositions: fallbackPositions,\n matchTargetSize: fluid ? ('width' as const) : undefined,\n autoSize: true,\n ...resolvePositioningShorthand(positioning),\n };\n\n const { targetRef, containerRef } = usePositioning(popperOptions);\n\n return [containerRef, targetRef];\n}\n"],"names":["useListboxPositioning","props","positioning","fallbackPositions","popperOptions","position","align","offset","crossAxis","mainAxis","matchTargetSize","fluid","undefined","resolvePositioningShorthand","containerRef","targetRef","usePositioning"],"rangeMappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;","mappings":"AAAA,yBAAyB;;;;;+BAQTA;;;eAAAA;;;kCAN4C;AAMrD,SAASA,sBACdC,KAAuF;UAOvF,EAEAC,WAAMC,OAAkD,KAAQF;UAEhEE,oBAAiB;QAAA;KAAA;qBACXC;UACJC,gBAAU;kBACVC;eACAC;gBAAUC;uBAAcC;sBAAY;;2BAEpCC;yBACUC,QAAA,UAAAC;kBACPC;QACL,GAAAA,IAAAA,6CAAA,EAAAX,YAAA;;UAIA,WAAQY,cAAcC,KAAUC,IAAAA,gCAAA,EAAAZ;IAClC,OAAA;QAAAU;QAAAC;KAAA"}
@@ -12,17 +12,15 @@ const _interop_require_wildcard = require("@swc/helpers/_/_interop_require_wildc
12
12
  const _react = /*#__PURE__*/ _interop_require_wildcard._(require("react"));
13
13
  const _reactaria = require("@fluentui/react-aria");
14
14
  const _reactutilities = require("@fluentui/react-utilities");
15
- const _dropdownKeyActions = require("./dropdownKeyActions");
16
15
  const _CursorPositionPlugin = require("../../plugins/CursorPositionPlugin");
17
16
  const _useOptionCollection = require("./useOptionCollection");
18
17
  const _useSelection = require("./useSelection");
19
- const _keyboardkeys = require("@fluentui/keyboard-keys");
20
- const _useComboboxPositioning = require("./useComboboxPositioning");
18
+ const _useListboxPositioning = require("./useListboxPositioning");
21
19
  const _useTriggerKeyDown = require("./useTriggerKeyDown");
22
20
  const _PromptListbox = require("../PromptListbox");
23
21
  const _PromptOption = require("../PromptOption");
24
22
  function usePromptListboxFunctionality(params) {
25
- const { positioning, onOpenChange, onSelectionModeChange, listboxProps, fluid = false } = params;
23
+ const { positioning, onOpenChange, onSelectionModeChange, listboxProps, fluid = false, allowArrowUpNavigation = false } = params;
26
24
  const { listboxRef: activeDescendantListboxRef, activeParentRef, controller: activeDescendantController } = (0, _reactaria.useActiveDescendant)({
27
25
  matchOption: (el)=>el.classList.contains(_PromptOption.promptOptionClassNames.root)
28
26
  });
@@ -39,6 +37,19 @@ function usePromptListboxFunctionality(params) {
39
37
  defaultState: params.defaultOpen,
40
38
  initialState: false
41
39
  });
40
+ const setSelectionMode = _react.useCallback((selectionMode)=>{
41
+ if (selectionMode === false) {
42
+ activeDescendantController.blur();
43
+ setHideActiveDescendant(true);
44
+ } else {
45
+ setHideActiveDescendant(false);
46
+ }
47
+ setIsInSelectionMode(selectionMode);
48
+ onSelectionModeChange === null || onSelectionModeChange === void 0 ? void 0 : onSelectionModeChange(selectionMode);
49
+ }, [
50
+ activeDescendantController,
51
+ onSelectionModeChange
52
+ ]);
42
53
  const onBlur = (event)=>{
43
54
  setOpen(false);
44
55
  onOpenChange === null || onOpenChange === void 0 ? void 0 : onOpenChange(event, {
@@ -46,8 +57,7 @@ function usePromptListboxFunctionality(params) {
46
57
  type: 'focus',
47
58
  open: false
48
59
  });
49
- activeDescendantController.blur();
50
- setHideActiveDescendant(true);
60
+ setSelectionMode(false);
51
61
  };
52
62
  const onFocus = (event)=>{
53
63
  if (event.target === event.currentTarget) {
@@ -63,46 +73,30 @@ function usePromptListboxFunctionality(params) {
63
73
  setCursorPosition: setCursorPosition
64
74
  });
65
75
  const onListboxBlur = _react.useCallback(()=>{
66
- setIsInSelectionMode(false);
76
+ setSelectionMode(false);
67
77
  onSelectionModeChange === null || onSelectionModeChange === void 0 ? void 0 : onSelectionModeChange(false);
68
78
  }, [
69
- onSelectionModeChange
79
+ onSelectionModeChange,
80
+ setSelectionMode
70
81
  ]);
71
82
  // handle combobox keyboard interaction
72
83
  const onKeyDown = (0, _useTriggerKeyDown.useTriggerKeydown)({
73
84
  ...optionCollection,
85
+ allowArrowUpNavigation,
74
86
  activeDescendantController,
75
87
  getOptionById,
76
88
  onBlur: onListboxBlur,
77
89
  selectOption,
78
90
  cursorPosition,
79
91
  open,
80
- multiselect: false
92
+ multiselect: false,
93
+ isInSelectionMode,
94
+ setSelectionMode
81
95
  });
82
96
  // NVDA and JAWS have bugs that suppress reading the input value text when aria-activedescendant is set
83
97
  // To prevent this, we clear the HTML attribute (but save the state) when a user presses left/right arrows
84
98
  // ref: https://github.com/microsoft/fluentui/issues/26359#issuecomment-1397759888
85
99
  const [hideActiveDescendant, setHideActiveDescendant] = _react.useState(false);
86
- /**
87
- * Freeform combobox should not select
88
- */ const onInputTriggerKeyDown = (0, _reactutilities.useEventCallback)((event)=>{
89
- // update typing state to true if the user is typing
90
- const action = (0, _dropdownKeyActions.getDropdownActionFromKey)(event, {
91
- open,
92
- multiselect: false,
93
- cursorPosition
94
- });
95
- if (event.key === _keyboardkeys.ArrowLeft || event.key === _keyboardkeys.ArrowRight || action === 'Type') {
96
- activeDescendantController.blur();
97
- setHideActiveDescendant(true);
98
- setIsInSelectionMode(false);
99
- onSelectionModeChange === null || onSelectionModeChange === void 0 ? void 0 : onSelectionModeChange(false);
100
- } else if (action === 'Next' || action === 'Previous' || action === 'First' || action === 'Last' || action === 'PageUp' || action === 'PageDown') {
101
- setHideActiveDescendant(false);
102
- setIsInSelectionMode(true);
103
- onSelectionModeChange === null || onSelectionModeChange === void 0 ? void 0 : onSelectionModeChange(true);
104
- }
105
- });
106
100
  _react.useEffect(()=>{
107
101
  if (hideActiveDescendant) {
108
102
  var _triggerRef_current;
@@ -116,7 +110,7 @@ function usePromptListboxFunctionality(params) {
116
110
  }, [
117
111
  hideActiveDescendant
118
112
  ]);
119
- const [comboboxPopupRef, comboboxTargetRef] = (0, _useComboboxPositioning.useComboboxPositioning)({
113
+ const [comboboxPopupRef, comboboxTargetRef] = (0, _useListboxPositioning.useListboxPositioning)({
120
114
  positioning,
121
115
  fluid
122
116
  });
@@ -144,7 +138,7 @@ function usePromptListboxFunctionality(params) {
144
138
  ref: triggerRef,
145
139
  onBlur,
146
140
  onFocus,
147
- onKeyDown: (0, _reactutilities.useEventCallback)((0, _reactutilities.mergeCallbacks)(onKeyDown, onInputTriggerKeyDown)),
141
+ onKeyDown,
148
142
  isInSelectionMode
149
143
  },
150
144
  containerRef: comboboxTargetRef,
@@ -1 +1 @@
1
- {"version":3,"sources":["usePromptListboxFunctionality.tsx"],"sourcesContent":["import * as React from 'react';\nimport { useActiveDescendant } from '@fluentui/react-aria';\nimport { mergeCallbacks, useControllableState, useEventCallback, useMergedRefs } from '@fluentui/react-utilities';\nimport { getDropdownActionFromKey } from './dropdownKeyActions';\nimport { CursorPositionPlugin } from '../../plugins/CursorPositionPlugin';\nimport { useOptionCollection } from './useOptionCollection';\nimport { useSelection } from './useSelection';\nimport { ArrowLeft, ArrowRight } from '@fluentui/keyboard-keys';\nimport { useComboboxPositioning } from './useComboboxPositioning';\nimport { useTriggerKeydown } from './useTriggerKeyDown';\nimport { PromptListbox } from '../PromptListbox';\nimport { promptOptionClassNames } from '../PromptOption';\nimport type { CursorPosition } from '../../plugins/CursorPositionPlugin';\nimport type { EditorInputProps } from '@fluentui-copilot/react-editor-input';\nimport type {\n UsePromptListboxFunctionalityParams,\n UsePromptListboxFunctionality,\n} from './PromptListboxFunctionality.types';\n\nexport function usePromptListboxFunctionality(\n params: UsePromptListboxFunctionalityParams,\n): UsePromptListboxFunctionality {\n const { positioning, onOpenChange, onSelectionModeChange, listboxProps, fluid = false } = params;\n const {\n listboxRef: activeDescendantListboxRef,\n activeParentRef,\n controller: activeDescendantController,\n } = useActiveDescendant<HTMLSpanElement, HTMLDivElement>({\n matchOption: el => el.classList.contains(promptOptionClassNames.root),\n });\n // useMergedRefs to normalize the ref into a React.RefObject type\n const triggerRef = useMergedRefs(activeParentRef);\n const selectionState = useSelection(listboxProps ?? {});\n const { selectOption } = selectionState;\n const optionCollection = useOptionCollection();\n const { getOptionById } = optionCollection;\n const [cursorPosition, setCursorPosition] = React.useState<CursorPosition>('end');\n const [isInSelectionMode, setIsInSelectionMode] = React.useState(false);\n const [open, setOpen] = useControllableState({\n state: params.open,\n defaultState: params.defaultOpen,\n initialState: false,\n });\n\n const onBlur = (event: React.FocusEvent<HTMLSpanElement>) => {\n setOpen(false);\n onOpenChange?.(event, { event, type: 'focus', open: false });\n activeDescendantController.blur();\n setHideActiveDescendant(true);\n };\n\n const onFocus = (event: React.FocusEvent<HTMLSpanElement>) => {\n if (event.target === event.currentTarget) {\n setOpen(true);\n onOpenChange?.(event, { event, type: 'focus', open: true });\n }\n };\n\n const cursorPositionPlugin = <CursorPositionPlugin setCursorPosition={setCursorPosition} />;\n\n const onListboxBlur = React.useCallback(() => {\n setIsInSelectionMode(false);\n onSelectionModeChange?.(false);\n }, [onSelectionModeChange]);\n\n // handle combobox keyboard interaction\n const onKeyDown = useTriggerKeydown({\n ...optionCollection,\n activeDescendantController,\n getOptionById,\n onBlur: onListboxBlur,\n selectOption,\n cursorPosition,\n open,\n multiselect: false,\n });\n\n // NVDA and JAWS have bugs that suppress reading the input value text when aria-activedescendant is set\n // To prevent this, we clear the HTML attribute (but save the state) when a user presses left/right arrows\n // ref: https://github.com/microsoft/fluentui/issues/26359#issuecomment-1397759888\n const [hideActiveDescendant, setHideActiveDescendant] = React.useState(false);\n\n /**\n * Freeform combobox should not select\n */\n const onInputTriggerKeyDown: EditorInputProps['onKeyDown'] = useEventCallback(event => {\n // update typing state to true if the user is typing\n const action = getDropdownActionFromKey(event, { open, multiselect: false, cursorPosition });\n if (event.key === ArrowLeft || event.key === ArrowRight || action === 'Type') {\n activeDescendantController.blur();\n setHideActiveDescendant(true);\n setIsInSelectionMode(false);\n onSelectionModeChange?.(false);\n } else if (\n action === 'Next' ||\n action === 'Previous' ||\n action === 'First' ||\n action === 'Last' ||\n action === 'PageUp' ||\n action === 'PageDown'\n ) {\n setHideActiveDescendant(false);\n setIsInSelectionMode(true);\n onSelectionModeChange?.(true);\n }\n });\n\n React.useEffect(() => {\n if (hideActiveDescendant) {\n triggerRef.current?.removeAttribute('aria-activedescendant');\n }\n // We only want to run this when the hideActiveDescendant changes, if the triggerRef\n // is undefined, there's no need to remove theAttribute and we shouldn't be adding\n // refs as dependencies since it can blow up the number of runs.\n // eslint-disable-next-line react-compiler/react-compiler\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [hideActiveDescendant]);\n\n const [comboboxPopupRef, comboboxTargetRef] = useComboboxPositioning({ positioning, fluid });\n\n const listboxMergedRef = useMergedRefs(comboboxPopupRef, activeDescendantListboxRef, listboxProps?.ref);\n const listbox = React.useMemo(() => {\n return (\n <PromptListbox\n open={open}\n {...listboxProps}\n {...optionCollection}\n {...selectionState}\n ref={listboxMergedRef}\n activeDescendantController={activeDescendantController}\n />\n );\n }, [activeDescendantController, listboxMergedRef, listboxProps, open, optionCollection, selectionState]);\n\n return {\n promptListbox: listbox,\n triggerProps: {\n ref: triggerRef,\n onBlur,\n onFocus,\n onKeyDown: useEventCallback(mergeCallbacks(onKeyDown, onInputTriggerKeyDown)),\n isInSelectionMode,\n },\n containerRef: comboboxTargetRef,\n cursorPositionPlugin,\n };\n}\n"],"names":["usePromptListboxFunctionality","params","positioning","listboxRef","matchOption","onSelectionModeChange","listboxProps","fluid","triggerRef","optionCollection","useOptionCollection","activeParentRef","getOptionById","controller","cursorPosition","setCursorPosition","useActiveDescendant","isInSelectionMode","setOpen","classList","useControllableState","root","initialState","selectionState","useSelection","event","open","activeDescendantController","React","useState","setHideActiveDescendant","state","onFocus","target","defaultOpen","currentTarget","onOpenChange","setIsInSelectionMode","type","onKeyDown","onBlur","onListboxBlur","selectOption","useTriggerKeydown","hideActiveDescendant","onInputTriggerKeyDown","multiselect","action","useEffect","comboboxPopupRef","_triggerRef_current","current","removeAttribute","PromptListbox","listboxMergedRef","ref","listbox","cursorPositionPlugin"],"rangeMappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;","mappings":"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"}
1
+ {"version":3,"sources":["usePromptListboxFunctionality.tsx"],"sourcesContent":["import * as React from 'react';\nimport { useActiveDescendant } from '@fluentui/react-aria';\nimport { useControllableState, useMergedRefs } from '@fluentui/react-utilities';\nimport { CursorPositionPlugin } from '../../plugins/CursorPositionPlugin';\nimport { useOptionCollection } from './useOptionCollection';\nimport { useSelection } from './useSelection';\nimport { useListboxPositioning } from './useListboxPositioning';\nimport { useTriggerKeydown } from './useTriggerKeyDown';\nimport { PromptListbox } from '../PromptListbox';\nimport { promptOptionClassNames } from '../PromptOption';\nimport type { CursorPosition } from '../../plugins/CursorPositionPlugin';\nimport type {\n UsePromptListboxFunctionalityParams,\n UsePromptListboxFunctionality,\n} from './PromptListboxFunctionality.types';\n\nexport function usePromptListboxFunctionality(\n params: UsePromptListboxFunctionalityParams,\n): UsePromptListboxFunctionality {\n const {\n positioning,\n onOpenChange,\n onSelectionModeChange,\n listboxProps,\n fluid = false,\n allowArrowUpNavigation = false,\n } = params;\n const {\n listboxRef: activeDescendantListboxRef,\n activeParentRef,\n controller: activeDescendantController,\n } = useActiveDescendant<HTMLSpanElement, HTMLDivElement>({\n matchOption: el => el.classList.contains(promptOptionClassNames.root),\n });\n // useMergedRefs to normalize the ref into a React.RefObject type\n const triggerRef = useMergedRefs(activeParentRef);\n const selectionState = useSelection(listboxProps ?? {});\n const { selectOption } = selectionState;\n const optionCollection = useOptionCollection();\n const { getOptionById } = optionCollection;\n const [cursorPosition, setCursorPosition] = React.useState<CursorPosition>('end');\n const [isInSelectionMode, setIsInSelectionMode] = React.useState(false);\n const [open, setOpen] = useControllableState({\n state: params.open,\n defaultState: params.defaultOpen,\n initialState: false,\n });\n\n const setSelectionMode = React.useCallback(\n (selectionMode: boolean) => {\n if (selectionMode === false) {\n activeDescendantController.blur();\n setHideActiveDescendant(true);\n } else {\n setHideActiveDescendant(false);\n }\n\n setIsInSelectionMode(selectionMode);\n onSelectionModeChange?.(selectionMode);\n },\n [activeDescendantController, onSelectionModeChange],\n );\n\n const onBlur = (event: React.FocusEvent<HTMLSpanElement>) => {\n setOpen(false);\n onOpenChange?.(event, { event, type: 'focus', open: false });\n setSelectionMode(false);\n };\n\n const onFocus = (event: React.FocusEvent<HTMLSpanElement>) => {\n if (event.target === event.currentTarget) {\n setOpen(true);\n onOpenChange?.(event, { event, type: 'focus', open: true });\n }\n };\n\n const cursorPositionPlugin = <CursorPositionPlugin setCursorPosition={setCursorPosition} />;\n\n const onListboxBlur = React.useCallback(() => {\n setSelectionMode(false);\n onSelectionModeChange?.(false);\n }, [onSelectionModeChange, setSelectionMode]);\n\n // handle combobox keyboard interaction\n const onKeyDown = useTriggerKeydown({\n ...optionCollection,\n allowArrowUpNavigation,\n activeDescendantController,\n getOptionById,\n onBlur: onListboxBlur,\n selectOption,\n cursorPosition,\n open,\n multiselect: false,\n isInSelectionMode,\n setSelectionMode,\n });\n\n // NVDA and JAWS have bugs that suppress reading the input value text when aria-activedescendant is set\n // To prevent this, we clear the HTML attribute (but save the state) when a user presses left/right arrows\n // ref: https://github.com/microsoft/fluentui/issues/26359#issuecomment-1397759888\n const [hideActiveDescendant, setHideActiveDescendant] = React.useState(false);\n\n React.useEffect(() => {\n if (hideActiveDescendant) {\n triggerRef.current?.removeAttribute('aria-activedescendant');\n }\n // We only want to run this when the hideActiveDescendant changes, if the triggerRef\n // is undefined, there's no need to remove theAttribute and we shouldn't be adding\n // refs as dependencies since it can blow up the number of runs.\n // eslint-disable-next-line react-compiler/react-compiler\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [hideActiveDescendant]);\n\n const [comboboxPopupRef, comboboxTargetRef] = useListboxPositioning({ positioning, fluid });\n\n const listboxMergedRef = useMergedRefs(comboboxPopupRef, activeDescendantListboxRef, listboxProps?.ref);\n const listbox = React.useMemo(() => {\n return (\n <PromptListbox\n open={open}\n {...listboxProps}\n {...optionCollection}\n {...selectionState}\n ref={listboxMergedRef}\n activeDescendantController={activeDescendantController}\n />\n );\n }, [activeDescendantController, listboxMergedRef, listboxProps, open, optionCollection, selectionState]);\n\n return {\n promptListbox: listbox,\n triggerProps: {\n ref: triggerRef,\n onBlur,\n onFocus,\n onKeyDown,\n isInSelectionMode,\n },\n containerRef: comboboxTargetRef,\n cursorPositionPlugin,\n };\n}\n"],"names":["usePromptListboxFunctionality","params","positioning","listboxRef","matchOption","onSelectionModeChange","listboxProps","fluid","triggerRef","allowArrowUpNavigation","useSelection","getOptionById","optionCollection","activeParentRef","cursorPosition","controller","isInSelectionMode","setIsInSelectionMode","useActiveDescendant","open","classList","contains","promptOptionClassNames","root","useMergedRefs","selectionState","setSelectionMode","useCallback","selectionMode","activeDescendantController","useOptionCollection","React","useState","defaultOpen","initialState","onBlur","onOpenChange","event","blur","currentTarget","type","cursorPositionPlugin","onListboxBlur","createElement","CursorPositionPlugin","selectOption","multiselect","hideActiveDescendant","setHideActiveDescendant","useEffect","_triggerRef_current","current","removeAttribute","activeDescendantListboxRef","listboxMergedRef","promptListbox","triggerProps","containerRef"],"rangeMappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;","mappings":"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"}
@@ -17,8 +17,9 @@ const _react = /*#__PURE__*/ _interop_require_wildcard._(require("react"));
17
17
  const _reacttabster = require("@fluentui/react-tabster");
18
18
  const _reactutilities = require("@fluentui/react-utilities");
19
19
  const _dropdownKeyActions = require("./dropdownKeyActions");
20
+ const _keyboardkeys = require("@fluentui/keyboard-keys");
20
21
  function useTriggerKeydown(options) {
21
- const { activeDescendantController, getOptionById, selectOption, multiselect, open, cursorPosition, onBlur } = options;
22
+ const { activeDescendantController, getOptionById, selectOption, multiselect, open, cursorPosition, onBlur, allowArrowUpNavigation, isInSelectionMode, setSelectionMode } = options;
22
23
  const getActiveOption = _react.useCallback(()=>{
23
24
  const activeOptionId = activeDescendantController.active();
24
25
  return activeOptionId ? getOptionById(activeOptionId) : undefined;
@@ -65,12 +66,19 @@ function useTriggerKeydown(options) {
65
66
  const action = (0, _dropdownKeyActions.getDropdownActionFromKey)(e, {
66
67
  open,
67
68
  multiselect,
68
- cursorPosition
69
+ cursorPosition,
70
+ allowArrowUpNavigation,
71
+ isInSelectionMode
69
72
  });
70
73
  const activeOption = getActiveOption();
71
74
  const firstOption = activeDescendantController.first({
72
75
  passive: true
73
76
  });
77
+ if (e.key === _keyboardkeys.ArrowLeft || e.key === _keyboardkeys.ArrowRight || action === 'Type') {
78
+ setSelectionMode(false);
79
+ } else if (action === 'Next' || action === 'Previous' || action === 'First' || action === 'Last' || action === 'PageUp' || action === 'PageDown') {
80
+ setSelectionMode(true);
81
+ }
74
82
  switch(action){
75
83
  case 'Last':
76
84
  case 'First':
@@ -81,14 +89,7 @@ function useTriggerKeydown(options) {
81
89
  e.preventDefault();
82
90
  break;
83
91
  case 'Previous':
84
- // when active option is the first option and the action was "Previous",
85
- // this means we were in the first option and we are "leaving" the listbox
86
- if ((activeOption === null || activeOption === void 0 ? void 0 : activeOption.id) === firstOption) {
87
- blur();
88
- e.preventDefault();
89
- } else if (activeOption) {
90
- e.preventDefault();
91
- }
92
+ e.preventDefault();
92
93
  break;
93
94
  case 'Next':
94
95
  e.preventDefault();
@@ -98,18 +99,33 @@ function useTriggerKeydown(options) {
98
99
  switch(action){
99
100
  case 'First':
100
101
  first();
102
+ if (!isInSelectionMode) {
103
+ setSelectionMode(true);
104
+ }
101
105
  break;
102
106
  case 'Last':
103
107
  last();
108
+ if (!isInSelectionMode) {
109
+ setSelectionMode(true);
110
+ }
104
111
  break;
105
112
  case 'Next':
106
113
  next(activeOption);
114
+ if (!isInSelectionMode) {
115
+ setSelectionMode(true);
116
+ }
107
117
  break;
108
118
  case 'Previous':
119
+ // when active option is the first option and the action was "Previous",
120
+ // this means we were in the first option and we are "leaving" the listbox
109
121
  if (activeOption && activeOption.id !== firstOption) {
110
122
  previous(activeOption);
123
+ if (!isInSelectionMode) {
124
+ setSelectionMode(true);
125
+ }
111
126
  } else {
112
127
  blur();
128
+ setSelectionMode(false);
113
129
  }
114
130
  break;
115
131
  case 'PageDown':
@@ -121,6 +137,7 @@ function useTriggerKeydown(options) {
121
137
  case 'CloseSelect':
122
138
  if (!multiselect && !(activeOption === null || activeOption === void 0 ? void 0 : activeOption.disabled)) {
123
139
  blur();
140
+ setSelectionMode(false);
124
141
  }
125
142
  // fallthrough
126
143
  case 'Select':
@@ -1 +1 @@
1
- {"version":3,"sources":["useTriggerKeyDown.ts"],"sourcesContent":["/**\n * Note, this is mainly brought from Fluent UI, only removed the closing and\n * opening logic since that's not needed for this use case and added the bluring\n * functionality.\n */\n\nimport * as React from 'react';\nimport { useSetKeyboardNavigation } from '@fluentui/react-tabster';\nimport { useEventCallback } from '@fluentui/react-utilities';\nimport { getDropdownActionFromKey } from './dropdownKeyActions';\nimport type { ActiveDescendantImperativeRef } from '@fluentui/react-aria';\nimport type { OptionCollectionState, OptionValue } from './OptionCollection.types';\nimport type { SelectionProps, SelectionState } from './Selection.types';\nimport type { CursorPosition } from '../../plugins/CursorPositionPlugin';\n\nexport function useTriggerKeydown(\n options: {\n activeDescendantController: ActiveDescendantImperativeRef;\n cursorPosition: CursorPosition;\n open: boolean;\n onBlur: () => void;\n } & OptionCollectionState &\n Pick<SelectionProps, 'multiselect'> &\n Pick<SelectionState, 'selectOption'>,\n) {\n const { activeDescendantController, getOptionById, selectOption, multiselect, open, cursorPosition, onBlur } =\n options;\n\n const getActiveOption = React.useCallback(() => {\n const activeOptionId = activeDescendantController.active();\n return activeOptionId ? getOptionById(activeOptionId) : undefined;\n }, [activeDescendantController, getOptionById]);\n\n const first = () => {\n activeDescendantController.first();\n };\n\n const last = () => {\n activeDescendantController.last();\n };\n\n const blur = () => {\n activeDescendantController.blur();\n onBlur();\n };\n\n const next = (activeOption: OptionValue | undefined) => {\n if (activeOption) {\n activeDescendantController.next();\n } else {\n activeDescendantController.first();\n }\n };\n\n const previous = (activeOption: OptionValue | undefined) => {\n if (activeOption) {\n activeDescendantController.prev();\n } else {\n activeDescendantController.first();\n }\n };\n\n const pageUp = () => {\n for (let i = 0; i < 10; i++) {\n activeDescendantController.prev();\n }\n };\n\n const pageDown = () => {\n for (let i = 0; i < 10; i++) {\n activeDescendantController.next();\n }\n };\n\n const setKeyboardNavigation = useSetKeyboardNavigation();\n return useEventCallback((e: React.KeyboardEvent<HTMLSpanElement>) => {\n const action = getDropdownActionFromKey(e, { open, multiselect, cursorPosition });\n const activeOption = getActiveOption();\n const firstOption = activeDescendantController.first({ passive: true });\n\n switch (action) {\n case 'Last':\n case 'First':\n case 'PageDown':\n case 'PageUp':\n case 'CloseSelect':\n case 'Select':\n e.preventDefault();\n break;\n case 'Previous':\n // when active option is the first option and the action was \"Previous\",\n // this means we were in the first option and we are \"leaving\" the listbox\n if (activeOption?.id === firstOption) {\n blur();\n e.preventDefault();\n } else if (activeOption) {\n e.preventDefault();\n }\n break;\n case 'Next':\n e.preventDefault();\n break;\n }\n\n setKeyboardNavigation(true);\n\n switch (action) {\n case 'First':\n first();\n break;\n case 'Last':\n last();\n break;\n case 'Next':\n next(activeOption);\n break;\n case 'Previous':\n if (activeOption && activeOption.id !== firstOption) {\n previous(activeOption);\n } else {\n blur();\n }\n break;\n case 'PageDown':\n pageDown();\n break;\n case 'PageUp':\n pageUp();\n break;\n case 'CloseSelect':\n if (!multiselect && !activeOption?.disabled) {\n blur();\n }\n // fallthrough\n case 'Select':\n activeOption && selectOption(e, activeOption);\n break;\n case 'Tab':\n !multiselect && activeOption && selectOption(e, activeOption);\n break;\n }\n });\n}\n"],"names":["useTriggerKeydown","React","options","activeDescendantController","activeOptionId","multiselect","getOptionById","active","last","undefined","first","onBlur","blur","activeOption","next","previous","prev","pageUp","pageDown","i","setKeyboardNavigation","useEventCallback","e","getDropdownActionFromKey","cursorPosition","action","getActiveOption","id","firstOption","preventDefault","selectOption"],"rangeMappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;","mappings":"AAAA;;;;CAIC;;;;+BAWeA;;;eAAAA;;;;iEATJC;8BAC6B;gCACR;oCACQ;AAMlC,SAASD,kBACdE,OAOsC;UAEtC,EAGAC,0BAAwBF,eAChBG,cACCA,EACTC,WAAG,MAACF,gBAA4BG,QAAc,KAE9CJ;UACEC,kBAAAA,OAAAA,WAAgC,CAAA;QAClC,MAAAC,iBAAAD,2BAAAI,MAAA;QAEA,OAAMC,iBAAOF,cAAAF,kBAAAK;;;QACXN;KAA+B;UACjCO,QAAA;QAEAP,2BAAaO,KAAA;;UAEXC,OAAAA;QACFR,2BAAAK,IAAA;;UAGEI,OAAIC;mCACFV,IAAAA;;;UAGFW,OAAAD,CAAAA;QACF,IAAAA,cAAA;YAEAV,2BAAkBU,IAAAA;eAChB;uCACEV,KAAAA;;;UAGFY,WAAAF,CAAAA;QACF,IAAAA,cAAA;YAEAV,2BAAea,IAAA;eACb;uCACEb,KAAAA;;;UAIJc,SAAMC;YACJ,IAAKC,IAAIA,GAAIA,IAAGA,IAAIA,IAAIA;uCACtBhB,IAAAA;;;UAIJe,WAAME;QACN,IAAA,IAAOC,IAAAA,GAAAA,IAAAA,IAAiBF,IAACG;uCACRC,IAAAA;;;kCAAiDC,IAAAA,sCAAAA;WAAeH,IAAAA,gCAAA,EAAAC,CAAAA;cAC/EG,SAAMZ,IAAAA,4CAAea,EAAAA,GAAAA;;;;;6BAIdA;4BACAvB,2BAAAO,KAAA,CAAA;qBACL;;eAEAe;;;;;;;gCAOMZ;;;wFAGqB;0FACP;sCAClB,QAAAA,iBAAA,KAAA,IAAA,KAAA,IAAAA,aAAAc,EAAA,MAAAC,aAAA;;sBAEFC,cAAK;2BACDA,cAAc;oCAChB;gBACJ;gBAEAT;iBAEA;gCACO;;;8BAGA;;;;;;;;;;;;oCAYHP,aAAAc,EAAA,KAAAC,aAAA;6BACGf;;;;;;;;;;;;oCAYHA,CAAAA,CAAAA,iBAA6BS,QAAGT,iBAAAA,KAAAA,IAAAA,KAAAA,IAAAA,aAAAA,QAAAA,GAAAA;;;0BAG/BR;;gBAELQ,gBAAAiB,aAAAR,GAAAT;gBACF;YACF,KAAA"}
1
+ {"version":3,"sources":["useTriggerKeyDown.ts"],"sourcesContent":["/**\n * Note, this is mainly brought from Fluent UI, only removed the closing and\n * opening logic since that's not needed for this use case and added the bluring\n * functionality.\n */\n\nimport * as React from 'react';\nimport { useSetKeyboardNavigation } from '@fluentui/react-tabster';\nimport { useEventCallback } from '@fluentui/react-utilities';\nimport { getDropdownActionFromKey } from './dropdownKeyActions';\nimport { ArrowLeft, ArrowRight } from '@fluentui/keyboard-keys';\nimport type { ActiveDescendantImperativeRef } from '@fluentui/react-aria';\nimport type { OptionCollectionState, OptionValue } from './OptionCollection.types';\nimport type { SelectionProps, SelectionState } from './Selection.types';\nimport type { CursorPosition } from '../../plugins/CursorPositionPlugin';\n\nexport function useTriggerKeydown(\n options: {\n activeDescendantController: ActiveDescendantImperativeRef;\n cursorPosition: CursorPosition;\n open: boolean;\n onBlur: () => void;\n allowArrowUpNavigation: boolean;\n isInSelectionMode: boolean;\n setSelectionMode: (selectionMode: boolean) => void;\n } & OptionCollectionState &\n Pick<SelectionProps, 'multiselect'> &\n Pick<SelectionState, 'selectOption'>,\n) {\n const {\n activeDescendantController,\n getOptionById,\n selectOption,\n multiselect,\n open,\n cursorPosition,\n onBlur,\n allowArrowUpNavigation,\n isInSelectionMode,\n setSelectionMode,\n } = options;\n\n const getActiveOption = React.useCallback(() => {\n const activeOptionId = activeDescendantController.active();\n return activeOptionId ? getOptionById(activeOptionId) : undefined;\n }, [activeDescendantController, getOptionById]);\n\n const first = () => {\n activeDescendantController.first();\n };\n\n const last = () => {\n activeDescendantController.last();\n };\n\n const blur = () => {\n activeDescendantController.blur();\n onBlur();\n };\n\n const next = (activeOption: OptionValue | undefined) => {\n if (activeOption) {\n activeDescendantController.next();\n } else {\n activeDescendantController.first();\n }\n };\n\n const previous = (activeOption: OptionValue | undefined) => {\n if (activeOption) {\n activeDescendantController.prev();\n } else {\n activeDescendantController.first();\n }\n };\n\n const pageUp = () => {\n for (let i = 0; i < 10; i++) {\n activeDescendantController.prev();\n }\n };\n\n const pageDown = () => {\n for (let i = 0; i < 10; i++) {\n activeDescendantController.next();\n }\n };\n\n const setKeyboardNavigation = useSetKeyboardNavigation();\n return useEventCallback((e: React.KeyboardEvent<HTMLSpanElement>) => {\n const action = getDropdownActionFromKey(e, {\n open,\n multiselect,\n cursorPosition,\n allowArrowUpNavigation,\n isInSelectionMode,\n });\n const activeOption = getActiveOption();\n const firstOption = activeDescendantController.first({ passive: true });\n\n if (e.key === ArrowLeft || e.key === ArrowRight || action === 'Type') {\n setSelectionMode(false);\n } else if (\n action === 'Next' ||\n action === 'Previous' ||\n action === 'First' ||\n action === 'Last' ||\n action === 'PageUp' ||\n action === 'PageDown'\n ) {\n setSelectionMode(true);\n }\n\n switch (action) {\n case 'Last':\n case 'First':\n case 'PageDown':\n case 'PageUp':\n case 'CloseSelect':\n case 'Select':\n e.preventDefault();\n break;\n case 'Previous':\n e.preventDefault();\n break;\n case 'Next':\n e.preventDefault();\n break;\n }\n\n setKeyboardNavigation(true);\n\n switch (action) {\n case 'First':\n first();\n if (!isInSelectionMode) {\n setSelectionMode(true);\n }\n break;\n case 'Last':\n last();\n if (!isInSelectionMode) {\n setSelectionMode(true);\n }\n break;\n case 'Next':\n next(activeOption);\n if (!isInSelectionMode) {\n setSelectionMode(true);\n }\n break;\n case 'Previous':\n // when active option is the first option and the action was \"Previous\",\n // this means we were in the first option and we are \"leaving\" the listbox\n if (activeOption && activeOption.id !== firstOption) {\n previous(activeOption);\n if (!isInSelectionMode) {\n setSelectionMode(true);\n }\n } else {\n blur();\n setSelectionMode(false);\n }\n break;\n case 'PageDown':\n pageDown();\n break;\n case 'PageUp':\n pageUp();\n break;\n case 'CloseSelect':\n if (!multiselect && !activeOption?.disabled) {\n blur();\n setSelectionMode(false);\n }\n // fallthrough\n case 'Select':\n activeOption && selectOption(e, activeOption);\n break;\n case 'Tab':\n !multiselect && activeOption && selectOption(e, activeOption);\n break;\n }\n });\n}\n"],"names":["useTriggerKeydown","React","options","activeDescendantController","activeOptionId","multiselect","getOptionById","allowArrowUpNavigation","setSelectionMode","active","blur","undefined","onBlur","first","last","activeOption","next","previous","i","prev","pageUp","setKeyboardNavigation","useSetKeyboardNavigation","open","cursorPosition","useEventCallback","action","firstOption","getActiveOption","ArrowLeft","e","key","ArrowRight","preventDefault","isInSelectionMode","id","pageDown","disabled"],"rangeMappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;","mappings":"AAAA;;;;CAIC;;;;+BAYeA;;;eAAAA;;;;iEAVJC;8BAC6B;gCACR;oCACQ;8BACH;AAM/B,SAASD,kBACdE,OAUsC;UAEtC,EAaAC,0BAAwBF,eAChBG,cACCA,EACTC,WAAG,MAACF,gBAA4BG,QAAc,EAE9CC,sBAAc,mBACZJ,EACFK,gBAAA,KAEAN;UACEC,kBAAAA,OAAAA,WAA+B,CAAA;QACjC,MAAAC,iBAAAD,2BAAAM,MAAA;QAEA,OAAMC,iBAAOJ,cAAAF,kBAAAO;;;QACXR;KAA+B;UAC/BS,QAAAA;QACFT,2BAAAU,KAAA;;UAGEC,OAAIC;mCACFZ,IAAAA;;iBAEAA;mCACFO,IAAA;QACFE;;UAGEI,OAAID,CAAAA;0BACFZ;uCACKa,IAAA;;uCAEPH,KAAA;QACF;;UAGEI,WAASC,CAAAA;0BACPf;uCACFgB,IAAA;QACF,OAAA;YAEAhB,2BAAiBU,KAAA;;;UAGfO,SAAA;QACF,IAAA,IAAAF,IAAA,GAAAA,IAAA,IAAAA,IAAA;YAEAf,2BAAMkB,IAAwBC;QAC9B;;qBAEIC;gBACAlB,IAAAA,GAAAA,IAAAA,IAAAA,IAAAA;uCACAmB,IAAAA;;;UAGFH,wBAAAC,IAAAA,sCAAA;WACAG,IAAAA,gCAAMV,EAAAA,CAAAA;cACNW,SAAMC,IAAAA,4CAAcxB,EAAAA,GAAAA;;;;;;;cAapBY,eAAAa;cAEAD,cAAQD,2BAAAA,KAAAA,CAAAA;qBACN;;iBAEA,KAAKG,uBAAA,IAAAC,EAAAC,GAAA,KAAAC,wBAAA,IAAAN,WAAA,QAAA;6BACA;mBACLA,WAAK,UAAAA,WAAA,cAAAA,WAAA,WAAAA,WAAA,UAAAA,WAAA,YAAAA,WAAA,YAAA;6BACA;;;;;;;;;gBASPI,EAAAG,cAAA;gBAEAZ;iBAEA;gCACO;;;gCAGDb;;;8BAGC;;;;wCAIH;qCACA;;;;;wCAKA;qCACA;;;;;wCAKWO;qCACJmB;;;;wFAILxB;0FACiB;oCACnBK,aAAAoB,EAAA,KAAAR,aAAA;6BACAZ;wBACF,CAAAmB,mBAAK;yCACHE;;uBAEF;;qCAEE;;;;;;;;;;oBAUF,CAAA/B,eAAK,CAAAU,CAAAA,iBAAA,QAAAA,iBAAA,KAAA,IAAA,KAAA,IAAAA,aAAAsB,QAAA,GAAA;;qCAEH;gBACJ;YACF,cAAA;YACF,KAAA"}
@@ -30,16 +30,17 @@ const CursorPositionPlugin = ({ setCursorPosition })=>{
30
30
  }
31
31
  // If there are no leaf nodes, the paragraph node will be selected
32
32
  if ((0, _reacttexteditor.$isElementNode)(selectedNode)) {
33
- setCursorPosition('end');
33
+ setCursorPosition('start-end');
34
34
  return false;
35
- }
36
- // if the selection node is a sentinel and it matches the sentinel at the end
37
- if ((0, _chatinputplugins.$isSentinelNode)(selectedNode) && !selectedNode.getNextSibling()) {
35
+ } else if ((0, _chatinputplugins.$isSentinelNode)(selectedNode) && !selectedNode.getNextSibling()) {
38
36
  setCursorPosition('end');
39
37
  return false;
40
38
  } else if ((0, _chatinputplugins.$isSentinelNode)(selectedNode.getNextSibling()) && selection.focus.offset === selectedNode.getTextContentSize()) {
41
39
  setCursorPosition('end');
42
40
  return false;
41
+ } else if (selectedNode.getPreviousSibling() === null && selection.focus.offset === 0) {
42
+ setCursorPosition('start');
43
+ return false;
43
44
  }
44
45
  setCursorPosition('between-text');
45
46
  return false;
@@ -1 +1 @@
1
- {"version":3,"sources":["CursorPositionPlugin.ts"],"sourcesContent":["import { $isSentinelNode } from '@fluentui-copilot/chat-input-plugins';\nimport {\n SELECTION_CHANGE_COMMAND,\n $getSelection,\n useLexicalComposerContext,\n $isRangeSelection,\n $isElementNode,\n COMMAND_PRIORITY_HIGH,\n} from '@fluentui-copilot/react-text-editor';\nimport * as React from 'react';\n\n/**\n * Position the cursor is in based on it's content. The goal\n * is to track whether the cursor is at the end of the input\n * or between text.\n */\nexport type CursorPosition = 'end' | 'between-text';\n\nexport type CursorPositionPluginProps = {\n setCursorPosition: (position: CursorPosition) => void;\n};\n\nexport const CursorPositionPlugin: React.FunctionComponent<CursorPositionPluginProps> = ({ setCursorPosition }) => {\n const [editor] = useLexicalComposerContext();\n\n React.useEffect(() => {\n const $selectionChangeHandler = () => {\n const selection = $getSelection();\n // If selection is null, the cursor is not active in the editor and we should just noop\n if (selection === null || !$isRangeSelection(selection) || !selection.isCollapsed()) {\n setCursorPosition('between-text');\n return false;\n }\n\n // Should only be one node in the selection because the selection is collapsed\n const selectedNode = selection.getNodes().at(0);\n // If there's no selected node, focus isn't in the editor\n if (!selectedNode) {\n return false;\n }\n\n // If there are no leaf nodes, the paragraph node will be selected\n if ($isElementNode(selectedNode)) {\n setCursorPosition('end');\n return false;\n }\n\n // if the selection node is a sentinel and it matches the sentinel at the end\n if ($isSentinelNode(selectedNode) && !selectedNode.getNextSibling()) {\n setCursorPosition('end');\n return false;\n }\n\n // else if the selection node is not a sentinel, check that the next sibling node is a sentinel\n // and check if the focus offset is in the last position of the node.\n else if (\n $isSentinelNode(selectedNode.getNextSibling()) &&\n selection.focus.offset === selectedNode.getTextContentSize()\n ) {\n setCursorPosition('end');\n return false;\n }\n\n setCursorPosition('between-text');\n return false;\n };\n\n return editor.registerCommand(SELECTION_CHANGE_COMMAND, $selectionChangeHandler, COMMAND_PRIORITY_HIGH);\n }, [editor, setCursorPosition]);\n\n return null;\n};\n"],"names":["CursorPositionPlugin","editor","$selectionChangeHandler","selection","$isRangeSelection","isCollapsed","selectedNode","getNodes","at","getNextSibling","$isSentinelNode","focus","offset","getTextContentSize","setCursorPosition"],"rangeMappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;","mappings":";;;;+BAsBaA;;;eAAAA;;;;kCAtBmB;iCAQzB;iEACgB;AAahB,MAAMA,uBAA2E,CAAC,mBACzE;UAGZ,CAAAC,OAAMC,GAAAA,IAAAA,0CAA0B;oBAC9B,CAAMC;wCACN;kBACAA,YAAIA,IAAAA,8BAAc;mGACE;8BACX,QAAA,CAAAC,IAAAA,kCAAA,EAAAD,cAAA,CAAAA,UAAAE,WAAA,IAAA;kCACT;uBAEA;;0FAEyD;kBACzDC,eAAKA,UAAcC,QAAA,GAAAC,EAAA,CAAA;qEACV;+BACT;uBAEA;;8EAEoB;mDACX,EAAAF,eAAA;kCACT;uBAEA;;yFAEoB;qDACX,EAAAA,iBAAA,CAAAA,aAAAG,cAAA,IAAA;kCAMPC;;uBAIAA,IAAAA,iCAAO,EAAAJ,aAAAG,cAAA,OAAAN,UAAAQ,KAAA,CAAAC,MAAA,KAAAN,aAAAO,kBAAA,IAAA;kCACT;uBAEAC;;8BAEF;mBAEA;QACF;eAAIb,OAAAA,eAAAA,CAAAA,yCAAAA,EAAAA,yBAAAA,sCAAAA;;;QAAQa;KAAAA;WAAkB;GAGhC,gDAAE"}
1
+ {"version":3,"sources":["CursorPositionPlugin.ts"],"sourcesContent":["import { $isSentinelNode } from '@fluentui-copilot/chat-input-plugins';\nimport {\n SELECTION_CHANGE_COMMAND,\n $getSelection,\n useLexicalComposerContext,\n $isRangeSelection,\n $isElementNode,\n COMMAND_PRIORITY_HIGH,\n} from '@fluentui-copilot/react-text-editor';\nimport * as React from 'react';\n\n/**\n * Position the cursor is in based on it's content. The goal\n * is to track whether the cursor is at the end of the input\n * or between text.\n */\nexport type CursorPosition = 'start' | 'between-text' | 'end' | 'start-end';\n\nexport type CursorPositionPluginProps = {\n setCursorPosition: (position: CursorPosition) => void;\n};\n\nexport const CursorPositionPlugin: React.FunctionComponent<CursorPositionPluginProps> = ({ setCursorPosition }) => {\n const [editor] = useLexicalComposerContext();\n\n React.useEffect(() => {\n const $selectionChangeHandler = () => {\n const selection = $getSelection();\n // If selection is null, the cursor is not active in the editor and we should just noop\n if (selection === null || !$isRangeSelection(selection) || !selection.isCollapsed()) {\n setCursorPosition('between-text');\n return false;\n }\n\n // Should only be one node in the selection because the selection is collapsed\n const selectedNode = selection.getNodes().at(0);\n // If there's no selected node, focus isn't in the editor\n if (!selectedNode) {\n return false;\n }\n\n // If there are no leaf nodes, the paragraph node will be selected\n if ($isElementNode(selectedNode)) {\n setCursorPosition('start-end');\n return false;\n }\n\n // if the selection node is a sentinel and it matches the sentinel at the end\n else if ($isSentinelNode(selectedNode) && !selectedNode.getNextSibling()) {\n setCursorPosition('end');\n return false;\n }\n\n // else if the selection node is not a sentinel, check that the next sibling node is a sentinel\n // and check if the focus offset is in the last position of the node.\n else if (\n $isSentinelNode(selectedNode.getNextSibling()) &&\n selection.focus.offset === selectedNode.getTextContentSize()\n ) {\n setCursorPosition('end');\n return false;\n }\n\n // else if the selection node is not at the end and is not a sentinel, check if there's a previous\n // sibling and that we are at the start. If that's the case we are at the start.\n else if (selectedNode.getPreviousSibling() === null && selection.focus.offset === 0) {\n setCursorPosition('start');\n return false;\n }\n\n setCursorPosition('between-text');\n return false;\n };\n\n return editor.registerCommand(SELECTION_CHANGE_COMMAND, $selectionChangeHandler, COMMAND_PRIORITY_HIGH);\n }, [editor, setCursorPosition]);\n\n return null;\n};\n"],"names":["CursorPositionPlugin","editor","$selectionChangeHandler","selection","$isRangeSelection","isCollapsed","selectedNode","getNodes","at","$isSentinelNode","getNextSibling","focus","offset","getTextContentSize","getPreviousSibling","setCursorPosition"],"rangeMappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;","mappings":";;;;+BAsBaA;;;eAAAA;;;;kCAtBmB;iCAQzB;iEACgB;AAahB,MAAMA,uBAA2E,CAAC,mBACzE;UAGZ,CAAAC,OAAMC,GAAAA,IAAAA,0CAA0B;oBAC9B,CAAMC;wCACN;kBACAA,YAAIA,IAAAA,8BAAc;mGACE;8BACX,QAAA,CAAAC,IAAAA,kCAAA,EAAAD,cAAA,CAAAA,UAAAE,WAAA,IAAA;kCACT;uBAEA;;0FAEyD;kBACzDC,eAAKA,UAAcC,QAAA,GAAAC,EAAA,CAAA;qEACV;+BACT;uBAEA;;8EAEoB;mDACX,EAAAF,eAAA;kCAIAG;;uBAEPA,IAAAA,iCAAO,EAAAH,iBAAA,CAAAA,aAAAI,cAAA,IAAA;kCAMPD;;uBAIAA,IAAAA,iCAAO,EAAAH,aAAAI,cAAA,OAAAP,UAAAQ,KAAA,CAAAC,MAAA,KAAAN,aAAAO,kBAAA,IAAA;kCAKAP;;uBAEPA,aAAOQ,kBAAA,OAAA,QAAAX,UAAAQ,KAAA,CAAAC,MAAA,KAAA,GAAA;kCACT;uBAEAG;;8BAEF;mBAEA;QACF;eAAId,OAAAA,eAAAA,CAAAA,yCAAAA,EAAAA,yBAAAA,sCAAAA;;;QAAQc;KAAAA;WAAkB;GAGhC,gDAAE"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fluentui-copilot/react-prompt-listbox",
3
- "version": "0.1.0",
3
+ "version": "0.2.1",
4
4
  "description": "PromptListbox for input components using EditorInput.",
5
5
  "main": "lib-commonjs/index.js",
6
6
  "module": "lib/index.js",
@@ -12,13 +12,13 @@
12
12
  },
13
13
  "license": "MIT",
14
14
  "dependencies": {
15
- "@fluentui-copilot/chat-input-plugins": "^0.1.4",
16
- "@fluentui-copilot/react-chat-input-plugins": "^0.1.5",
17
- "@fluentui-copilot/react-editor-input": "^0.1.5",
18
- "@fluentui-copilot/react-prompt-input": "^0.2.0",
19
- "@fluentui-copilot/react-provider": "^0.9.0",
20
- "@fluentui-copilot/react-text-editor": "^0.1.9",
21
- "@fluentui-copilot/text-editor": "^0.0.6",
15
+ "@fluentui-copilot/chat-input-plugins": "^0.2.0",
16
+ "@fluentui-copilot/react-chat-input-plugins": "^0.2.0",
17
+ "@fluentui-copilot/react-editor-input": "^0.2.0",
18
+ "@fluentui-copilot/react-prompt-input": "^0.3.1",
19
+ "@fluentui-copilot/react-provider": "^0.9.1",
20
+ "@fluentui-copilot/react-text-editor": "^0.2.0",
21
+ "@fluentui-copilot/text-editor": "^0.1.0",
22
22
  "@swc/helpers": "^0.5.1"
23
23
  },
24
24
  "peerDependencies": {
@@ -1 +0,0 @@
1
- {"version":3,"sources":["useComboboxPositioning.ts"],"sourcesContent":["// Brought from Fluent UI\n\nimport { resolvePositioningShorthand, usePositioning } from '@fluentui/react-positioning';\nimport type * as React from 'react';\nimport type { ComboboxBaseProps } from '@fluentui/react-combobox';\nimport type { UsePromptListboxFunctionalityParams } from './PromptListboxFunctionality.types';\nimport type { PositioningShorthandValue } from '@fluentui/react-positioning';\n\nexport function useComboboxPositioning(\n props: ComboboxBaseProps & Required<Pick<UsePromptListboxFunctionalityParams, 'fluid'>>,\n): [\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n listboxRef: React.MutableRefObject<any>,\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n triggerRef: React.MutableRefObject<any>,\n] {\n const { positioning, fluid } = props;\n\n const fallbackPositions: PositioningShorthandValue[] = ['below'];\n\n // popper options\n const popperOptions = {\n position: 'below' as const,\n align: 'start' as const,\n offset: { crossAxis: 0, mainAxis: 2 },\n fallbackPositions: fallbackPositions,\n matchTargetSize: fluid ? ('width' as const) : undefined,\n autoSize: true,\n ...resolvePositioningShorthand(positioning),\n };\n\n const { targetRef, containerRef } = usePositioning(popperOptions);\n\n return [containerRef, targetRef];\n}\n"],"names":["resolvePositioningShorthand","usePositioning","useComboboxPositioning","props","positioning","fluid","fallbackPositions","popperOptions","position","align","offset","crossAxis","mainAxis","matchTargetSize","undefined","autoSize","targetRef","containerRef"],"rangeMappings":";;;;;;;;;;;;;;;;;;;;;;;;;","mappings":"AAAA,yBAAyB;AAEzB,SAASA,2BAA2B,EAAEC,cAAc,QAAQ,8BAA8B;AAM1F,OAAO,SAASC,uBACdC,KAAuF;IAOvF,MAAM,EAAEC,WAAW,EAAEC,KAAK,EAAE,GAAGF;IAE/B,MAAMG,oBAAiD;QAAC;KAAQ;IAEhE,iBAAiB;IACjB,MAAMC,gBAAgB;QACpBC,UAAU;QACVC,OAAO;QACPC,QAAQ;YAAEC,WAAW;YAAGC,UAAU;QAAE;QACpCN,mBAAmBA;QACnBO,iBAAiBR,QAAS,UAAoBS;QAC9CC,UAAU;QACV,GAAGf,4BAA4BI,YAAY;IAC7C;IAEA,MAAM,EAAEY,SAAS,EAAEC,YAAY,EAAE,GAAGhB,eAAeM;IAEnD,OAAO;QAACU;QAAcD;KAAU;AAClC"}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["useComboboxPositioning.ts"],"sourcesContent":["// Brought from Fluent UI\n\nimport { resolvePositioningShorthand, usePositioning } from '@fluentui/react-positioning';\nimport type * as React from 'react';\nimport type { ComboboxBaseProps } from '@fluentui/react-combobox';\nimport type { UsePromptListboxFunctionalityParams } from './PromptListboxFunctionality.types';\nimport type { PositioningShorthandValue } from '@fluentui/react-positioning';\n\nexport function useComboboxPositioning(\n props: ComboboxBaseProps & Required<Pick<UsePromptListboxFunctionalityParams, 'fluid'>>,\n): [\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n listboxRef: React.MutableRefObject<any>,\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n triggerRef: React.MutableRefObject<any>,\n] {\n const { positioning, fluid } = props;\n\n const fallbackPositions: PositioningShorthandValue[] = ['below'];\n\n // popper options\n const popperOptions = {\n position: 'below' as const,\n align: 'start' as const,\n offset: { crossAxis: 0, mainAxis: 2 },\n fallbackPositions: fallbackPositions,\n matchTargetSize: fluid ? ('width' as const) : undefined,\n autoSize: true,\n ...resolvePositioningShorthand(positioning),\n };\n\n const { targetRef, containerRef } = usePositioning(popperOptions);\n\n return [containerRef, targetRef];\n}\n"],"names":["useComboboxPositioning","props","positioning","fallbackPositions","popperOptions","position","align","offset","crossAxis","mainAxis","matchTargetSize","fluid","undefined","resolvePositioningShorthand","containerRef","targetRef","usePositioning"],"rangeMappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;","mappings":"AAAA,yBAAyB;;;;;+BAQTA;;;eAAAA;;;kCAN4C;AAMrD,SAASA,uBACdC,KAAuF;UAOvF,EAEAC,WAAMC,OAAkD,KAAQF;UAEhEE,oBAAiB;QAAA;KAAA;qBACXC;UACJC,gBAAU;kBACVC;eACAC;gBAAUC;uBAAcC;sBAAY;;2BAEpCC;yBACUC,QAAA,UAAAC;kBACPC;QACL,GAAAA,IAAAA,6CAAA,EAAAX,YAAA;;UAIA,WAAQY,cAAcC,KAAUC,IAAAA,gCAAA,EAAAZ;IAClC,OAAA;QAAAU;QAAAC;KAAA"}