@itwin/itwinui-react 3.0.10 → 3.1.0

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 (56) hide show
  1. package/CHANGELOG.md +21 -0
  2. package/README.md +1 -1
  3. package/cjs/core/ButtonGroup/ButtonGroup.d.ts +2 -0
  4. package/cjs/core/ButtonGroup/ButtonGroup.js +26 -21
  5. package/cjs/core/Buttons/IconButton.d.ts +6 -0
  6. package/cjs/core/Buttons/IconButton.js +4 -2
  7. package/cjs/core/Carousel/Carousel.d.ts +110 -2
  8. package/cjs/core/Carousel/CarouselNavigation.d.ts +110 -2
  9. package/cjs/core/ComboBox/ComboBoxInput.js +17 -5
  10. package/cjs/core/FileUpload/FileUpload.d.ts +3 -1
  11. package/cjs/core/FileUpload/FileUpload.js +5 -8
  12. package/cjs/core/InputWithDecorations/InputWithDecorations.d.ts +55 -1
  13. package/cjs/core/Popover/Popover.d.ts +22 -14
  14. package/cjs/core/Popover/Popover.js +14 -2
  15. package/cjs/core/Table/Table.js +28 -2
  16. package/cjs/core/Tabs/Tabs.js +23 -3
  17. package/cjs/core/ThemeProvider/ThemeProvider.js +44 -21
  18. package/cjs/core/Tile/Tile.d.ts +61 -1
  19. package/cjs/core/Tooltip/Tooltip.js +6 -2
  20. package/cjs/core/Tree/Tree.d.ts +6 -0
  21. package/cjs/core/Tree/Tree.js +4 -3
  22. package/cjs/core/Tree/TreeContext.d.ts +4 -0
  23. package/cjs/core/Tree/TreeNodeExpander.js +4 -1
  24. package/cjs/core/utils/icons/SvgChevronRightSmall.d.ts +2 -0
  25. package/cjs/core/utils/icons/SvgChevronRightSmall.js +15 -0
  26. package/cjs/core/utils/icons/index.d.ts +1 -0
  27. package/cjs/core/utils/icons/index.js +1 -0
  28. package/cjs/styles.js +2 -2
  29. package/esm/core/ButtonGroup/ButtonGroup.d.ts +2 -0
  30. package/esm/core/ButtonGroup/ButtonGroup.js +25 -20
  31. package/esm/core/Buttons/IconButton.d.ts +6 -0
  32. package/esm/core/Buttons/IconButton.js +4 -2
  33. package/esm/core/Carousel/Carousel.d.ts +110 -2
  34. package/esm/core/Carousel/CarouselNavigation.d.ts +110 -2
  35. package/esm/core/ComboBox/ComboBoxInput.js +17 -5
  36. package/esm/core/FileUpload/FileUpload.d.ts +3 -1
  37. package/esm/core/FileUpload/FileUpload.js +6 -9
  38. package/esm/core/InputWithDecorations/InputWithDecorations.d.ts +55 -1
  39. package/esm/core/Popover/Popover.d.ts +22 -14
  40. package/esm/core/Popover/Popover.js +15 -3
  41. package/esm/core/Table/Table.js +28 -2
  42. package/esm/core/Tabs/Tabs.js +23 -3
  43. package/esm/core/ThemeProvider/ThemeProvider.js +45 -22
  44. package/esm/core/Tile/Tile.d.ts +61 -1
  45. package/esm/core/Tooltip/Tooltip.js +6 -2
  46. package/esm/core/Tree/Tree.d.ts +6 -0
  47. package/esm/core/Tree/Tree.js +4 -3
  48. package/esm/core/Tree/TreeContext.d.ts +4 -0
  49. package/esm/core/Tree/TreeNodeExpander.js +5 -2
  50. package/esm/core/utils/icons/SvgChevronRightSmall.d.ts +2 -0
  51. package/esm/core/utils/icons/SvgChevronRightSmall.js +10 -0
  52. package/esm/core/utils/icons/index.d.ts +1 -0
  53. package/esm/core/utils/icons/index.js +1 -0
  54. package/esm/styles.js +2 -2
  55. package/package.json +4 -4
  56. package/styles.css +15 -15
@@ -42,18 +42,126 @@ export declare const Carousel: PolymorphicForwardRefComponent<"section", Carouse
42
42
  Navigation: PolymorphicForwardRefComponent<"div", {}> & {
43
43
  PreviousButton: PolymorphicForwardRefComponent<"button", Omit<Omit<React.DetailedHTMLProps<React.ButtonHTMLAttributes<HTMLButtonElement>, HTMLButtonElement>, "ref"> & {
44
44
  ref?: ((instance: HTMLButtonElement | null) => void) | React.RefObject<HTMLButtonElement> | null | undefined;
45
- }, "label" | "as" | "size" | "htmlDisabled" | "styleType" | "isActive" | "iconProps"> & {
45
+ }, "label" | "as" | "size" | "htmlDisabled" | "styleType" | "labelProps" | "isActive" | "iconProps"> & {
46
46
  isActive?: boolean | undefined;
47
47
  label?: React.ReactNode;
48
+ labelProps?: Omit<Omit<Omit<Omit<React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>, "ref"> & {
49
+ ref?: ((instance: HTMLDivElement | null) => void) | React.RefObject<HTMLDivElement> | null | undefined;
50
+ }, "as" | "children" | "content" | "portal" | keyof {
51
+ placement?: import("@floating-ui/utils").Placement | undefined;
52
+ visible?: boolean | undefined;
53
+ onVisibleChange?: ((visible: boolean) => void) | undefined;
54
+ autoUpdateOptions?: {
55
+ ancestorScroll?: boolean | undefined;
56
+ ancestorResize?: boolean | undefined;
57
+ elementResize?: boolean | undefined;
58
+ animationFrame?: boolean | undefined;
59
+ layoutShift?: boolean | undefined;
60
+ } | undefined;
61
+ middleware?: {
62
+ offset?: number | undefined;
63
+ flip?: boolean | undefined;
64
+ shift?: boolean | undefined;
65
+ size?: boolean | undefined;
66
+ autoPlacement?: boolean | undefined;
67
+ hide?: boolean | undefined;
68
+ inline?: boolean | undefined;
69
+ } | undefined;
70
+ reference?: HTMLElement | null | undefined;
71
+ ariaStrategy?: "label" | "description" | "none" | undefined;
72
+ id?: string | undefined;
73
+ }> & {
74
+ content: React.ReactNode;
75
+ children?: React.ReactNode;
76
+ } & import("../utils/index.js").PortalProps & {
77
+ placement?: import("@floating-ui/utils").Placement | undefined;
78
+ visible?: boolean | undefined;
79
+ onVisibleChange?: ((visible: boolean) => void) | undefined;
80
+ autoUpdateOptions?: {
81
+ ancestorScroll?: boolean | undefined;
82
+ ancestorResize?: boolean | undefined;
83
+ elementResize?: boolean | undefined;
84
+ animationFrame?: boolean | undefined;
85
+ layoutShift?: boolean | undefined;
86
+ } | undefined;
87
+ middleware?: {
88
+ offset?: number | undefined;
89
+ flip?: boolean | undefined;
90
+ shift?: boolean | undefined;
91
+ size?: boolean | undefined;
92
+ autoPlacement?: boolean | undefined;
93
+ hide?: boolean | undefined;
94
+ inline?: boolean | undefined;
95
+ } | undefined;
96
+ reference?: HTMLElement | null | undefined;
97
+ ariaStrategy?: "label" | "description" | "none" | undefined;
98
+ id?: string | undefined;
99
+ } & {
100
+ as?: "div" | undefined;
101
+ }, "ref">, "children" | "content" | "reference" | "ariaStrategy"> | undefined;
48
102
  iconProps?: React.DetailedHTMLProps<React.HTMLAttributes<HTMLSpanElement>, HTMLSpanElement> | undefined;
49
103
  } & Omit<import("../Buttons/Button.js").ButtonProps, "startIcon" | "endIcon" | "labelProps" | "startIconProps" | "endIconProps"> & {
50
104
  as?: "button" | undefined;
51
105
  }>;
52
106
  NextButton: PolymorphicForwardRefComponent<"button", Omit<Omit<React.DetailedHTMLProps<React.ButtonHTMLAttributes<HTMLButtonElement>, HTMLButtonElement>, "ref"> & {
53
107
  ref?: ((instance: HTMLButtonElement | null) => void) | React.RefObject<HTMLButtonElement> | null | undefined;
54
- }, "label" | "as" | "size" | "htmlDisabled" | "styleType" | "isActive" | "iconProps"> & {
108
+ }, "label" | "as" | "size" | "htmlDisabled" | "styleType" | "labelProps" | "isActive" | "iconProps"> & {
55
109
  isActive?: boolean | undefined;
56
110
  label?: React.ReactNode;
111
+ labelProps?: Omit<Omit<Omit<Omit<React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>, "ref"> & {
112
+ ref?: ((instance: HTMLDivElement | null) => void) | React.RefObject<HTMLDivElement> | null | undefined;
113
+ }, "as" | "children" | "content" | "portal" | keyof {
114
+ placement?: import("@floating-ui/utils").Placement | undefined;
115
+ visible?: boolean | undefined;
116
+ onVisibleChange?: ((visible: boolean) => void) | undefined;
117
+ autoUpdateOptions?: {
118
+ ancestorScroll?: boolean | undefined;
119
+ ancestorResize?: boolean | undefined;
120
+ elementResize?: boolean | undefined;
121
+ animationFrame?: boolean | undefined;
122
+ layoutShift?: boolean | undefined;
123
+ } | undefined;
124
+ middleware?: {
125
+ offset?: number | undefined;
126
+ flip?: boolean | undefined;
127
+ shift?: boolean | undefined;
128
+ size?: boolean | undefined;
129
+ autoPlacement?: boolean | undefined;
130
+ hide?: boolean | undefined;
131
+ inline?: boolean | undefined;
132
+ } | undefined;
133
+ reference?: HTMLElement | null | undefined;
134
+ ariaStrategy?: "label" | "description" | "none" | undefined;
135
+ id?: string | undefined;
136
+ }> & {
137
+ content: React.ReactNode;
138
+ children?: React.ReactNode;
139
+ } & import("../utils/index.js").PortalProps & {
140
+ placement?: import("@floating-ui/utils").Placement | undefined;
141
+ visible?: boolean | undefined;
142
+ onVisibleChange?: ((visible: boolean) => void) | undefined;
143
+ autoUpdateOptions?: {
144
+ ancestorScroll?: boolean | undefined;
145
+ ancestorResize?: boolean | undefined;
146
+ elementResize?: boolean | undefined;
147
+ animationFrame?: boolean | undefined;
148
+ layoutShift?: boolean | undefined;
149
+ } | undefined;
150
+ middleware?: {
151
+ offset?: number | undefined;
152
+ flip?: boolean | undefined;
153
+ shift?: boolean | undefined;
154
+ size?: boolean | undefined;
155
+ autoPlacement?: boolean | undefined;
156
+ hide?: boolean | undefined;
157
+ inline?: boolean | undefined;
158
+ } | undefined;
159
+ reference?: HTMLElement | null | undefined;
160
+ ariaStrategy?: "label" | "description" | "none" | undefined;
161
+ id?: string | undefined;
162
+ } & {
163
+ as?: "div" | undefined;
164
+ }, "ref">, "children" | "content" | "reference" | "ariaStrategy"> | undefined;
57
165
  iconProps?: React.DetailedHTMLProps<React.HTMLAttributes<HTMLSpanElement>, HTMLSpanElement> | undefined;
58
166
  } & Omit<import("../Buttons/Button.js").ButtonProps, "startIcon" | "endIcon" | "labelProps" | "startIconProps" | "endIconProps"> & {
59
167
  as?: "button" | undefined;
@@ -9,18 +9,126 @@ import type { PolymorphicForwardRefComponent } from '../utils/index.js';
9
9
  export declare const CarouselNavigation: PolymorphicForwardRefComponent<"div", {}> & {
10
10
  PreviousButton: PolymorphicForwardRefComponent<"button", Omit<Omit<React.DetailedHTMLProps<React.ButtonHTMLAttributes<HTMLButtonElement>, HTMLButtonElement>, "ref"> & {
11
11
  ref?: ((instance: HTMLButtonElement | null) => void) | React.RefObject<HTMLButtonElement> | null | undefined;
12
- }, "label" | "as" | "size" | "htmlDisabled" | "styleType" | "isActive" | "iconProps"> & {
12
+ }, "label" | "as" | "size" | "htmlDisabled" | "styleType" | "labelProps" | "isActive" | "iconProps"> & {
13
13
  isActive?: boolean | undefined;
14
14
  label?: React.ReactNode;
15
+ labelProps?: Omit<Omit<Omit<Omit<React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>, "ref"> & {
16
+ ref?: ((instance: HTMLDivElement | null) => void) | React.RefObject<HTMLDivElement> | null | undefined;
17
+ }, "as" | "children" | "content" | "portal" | keyof {
18
+ placement?: import("@floating-ui/utils").Placement | undefined;
19
+ visible?: boolean | undefined;
20
+ onVisibleChange?: ((visible: boolean) => void) | undefined;
21
+ autoUpdateOptions?: {
22
+ ancestorScroll?: boolean | undefined;
23
+ ancestorResize?: boolean | undefined;
24
+ elementResize?: boolean | undefined;
25
+ animationFrame?: boolean | undefined;
26
+ layoutShift?: boolean | undefined;
27
+ } | undefined;
28
+ middleware?: {
29
+ offset?: number | undefined;
30
+ flip?: boolean | undefined;
31
+ shift?: boolean | undefined;
32
+ size?: boolean | undefined;
33
+ autoPlacement?: boolean | undefined;
34
+ hide?: boolean | undefined;
35
+ inline?: boolean | undefined;
36
+ } | undefined;
37
+ reference?: HTMLElement | null | undefined;
38
+ ariaStrategy?: "label" | "description" | "none" | undefined;
39
+ id?: string | undefined;
40
+ }> & {
41
+ content: React.ReactNode;
42
+ children?: React.ReactNode;
43
+ } & import("../utils/index.js").PortalProps & {
44
+ placement?: import("@floating-ui/utils").Placement | undefined;
45
+ visible?: boolean | undefined;
46
+ onVisibleChange?: ((visible: boolean) => void) | undefined;
47
+ autoUpdateOptions?: {
48
+ ancestorScroll?: boolean | undefined;
49
+ ancestorResize?: boolean | undefined;
50
+ elementResize?: boolean | undefined;
51
+ animationFrame?: boolean | undefined;
52
+ layoutShift?: boolean | undefined;
53
+ } | undefined;
54
+ middleware?: {
55
+ offset?: number | undefined;
56
+ flip?: boolean | undefined;
57
+ shift?: boolean | undefined;
58
+ size?: boolean | undefined;
59
+ autoPlacement?: boolean | undefined;
60
+ hide?: boolean | undefined;
61
+ inline?: boolean | undefined;
62
+ } | undefined;
63
+ reference?: HTMLElement | null | undefined;
64
+ ariaStrategy?: "label" | "description" | "none" | undefined;
65
+ id?: string | undefined;
66
+ } & {
67
+ as?: "div" | undefined;
68
+ }, "ref">, "children" | "content" | "reference" | "ariaStrategy"> | undefined;
15
69
  iconProps?: React.DetailedHTMLProps<React.HTMLAttributes<HTMLSpanElement>, HTMLSpanElement> | undefined;
16
70
  } & Omit<import("../Buttons/Button.js").ButtonProps, "startIcon" | "endIcon" | "labelProps" | "startIconProps" | "endIconProps"> & {
17
71
  as?: "button" | undefined;
18
72
  }>;
19
73
  NextButton: PolymorphicForwardRefComponent<"button", Omit<Omit<React.DetailedHTMLProps<React.ButtonHTMLAttributes<HTMLButtonElement>, HTMLButtonElement>, "ref"> & {
20
74
  ref?: ((instance: HTMLButtonElement | null) => void) | React.RefObject<HTMLButtonElement> | null | undefined;
21
- }, "label" | "as" | "size" | "htmlDisabled" | "styleType" | "isActive" | "iconProps"> & {
75
+ }, "label" | "as" | "size" | "htmlDisabled" | "styleType" | "labelProps" | "isActive" | "iconProps"> & {
22
76
  isActive?: boolean | undefined;
23
77
  label?: React.ReactNode;
78
+ labelProps?: Omit<Omit<Omit<Omit<React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>, "ref"> & {
79
+ ref?: ((instance: HTMLDivElement | null) => void) | React.RefObject<HTMLDivElement> | null | undefined;
80
+ }, "as" | "children" | "content" | "portal" | keyof {
81
+ placement?: import("@floating-ui/utils").Placement | undefined;
82
+ visible?: boolean | undefined;
83
+ onVisibleChange?: ((visible: boolean) => void) | undefined;
84
+ autoUpdateOptions?: {
85
+ ancestorScroll?: boolean | undefined;
86
+ ancestorResize?: boolean | undefined;
87
+ elementResize?: boolean | undefined;
88
+ animationFrame?: boolean | undefined;
89
+ layoutShift?: boolean | undefined;
90
+ } | undefined;
91
+ middleware?: {
92
+ offset?: number | undefined;
93
+ flip?: boolean | undefined;
94
+ shift?: boolean | undefined;
95
+ size?: boolean | undefined;
96
+ autoPlacement?: boolean | undefined;
97
+ hide?: boolean | undefined;
98
+ inline?: boolean | undefined;
99
+ } | undefined;
100
+ reference?: HTMLElement | null | undefined;
101
+ ariaStrategy?: "label" | "description" | "none" | undefined;
102
+ id?: string | undefined;
103
+ }> & {
104
+ content: React.ReactNode;
105
+ children?: React.ReactNode;
106
+ } & import("../utils/index.js").PortalProps & {
107
+ placement?: import("@floating-ui/utils").Placement | undefined;
108
+ visible?: boolean | undefined;
109
+ onVisibleChange?: ((visible: boolean) => void) | undefined;
110
+ autoUpdateOptions?: {
111
+ ancestorScroll?: boolean | undefined;
112
+ ancestorResize?: boolean | undefined;
113
+ elementResize?: boolean | undefined;
114
+ animationFrame?: boolean | undefined;
115
+ layoutShift?: boolean | undefined;
116
+ } | undefined;
117
+ middleware?: {
118
+ offset?: number | undefined;
119
+ flip?: boolean | undefined;
120
+ shift?: boolean | undefined;
121
+ size?: boolean | undefined;
122
+ autoPlacement?: boolean | undefined;
123
+ hide?: boolean | undefined;
124
+ inline?: boolean | undefined;
125
+ } | undefined;
126
+ reference?: HTMLElement | null | undefined;
127
+ ariaStrategy?: "label" | "description" | "none" | undefined;
128
+ id?: string | undefined;
129
+ } & {
130
+ as?: "div" | undefined;
131
+ }, "ref">, "children" | "content" | "reference" | "ariaStrategy"> | undefined;
24
132
  iconProps?: React.DetailedHTMLProps<React.HTMLAttributes<HTMLSpanElement>, HTMLSpanElement> | undefined;
25
133
  } & Omit<import("../Buttons/Button.js").ButtonProps, "startIcon" | "endIcon" | "labelProps" | "startIconProps" | "endIconProps"> & {
26
134
  as?: "button" | undefined;
@@ -8,7 +8,7 @@ import { useSafeContext, useMergedRefs, useContainerWidth, mergeEventHandlers, }
8
8
  import { ComboBoxMultipleContainer } from './ComboBoxMultipleContainer.js';
9
9
  import { ComboBoxStateContext, ComboBoxActionContext, ComboBoxRefsContext, } from './helpers.js';
10
10
  export const ComboBoxInput = React.forwardRef((props, forwardedRef) => {
11
- const { onKeyDown: onKeyDownProp, onClick: onClickProp, selectTags, size, ...rest } = props;
11
+ const { selectTags, size, ...rest } = props;
12
12
  const { isOpen, id, focusedIndex, enableVirtualization, multiple, onClickHandler, popover, show, hide, } = useSafeContext(ComboBoxStateContext);
13
13
  const dispatch = useSafeContext(ComboBoxActionContext);
14
14
  const { inputRef, menuRef, optionsExtraInfoRef } = useSafeContext(ComboBoxRefsContext);
@@ -121,21 +121,33 @@ export const ComboBoxInput = React.forwardRef((props, forwardedRef) => {
121
121
  show,
122
122
  hide,
123
123
  ]);
124
+ /**
125
+ * This temporarily stores the state of `isOpen` before click event starts and resets it later.
126
+ * It is necessary because `isOpen` may have changed during the process of the click,
127
+ * e.g. because of focus, which could cause the menu to close immediately after opening.
128
+ */
129
+ const wasOpenBeforeClick = React.useRef(false);
130
+ const handlePointerDown = React.useCallback(() => {
131
+ wasOpenBeforeClick.current = isOpen;
132
+ }, [isOpen]);
124
133
  const handleClick = React.useCallback(() => {
125
- if (!isOpen) {
134
+ if (!wasOpenBeforeClick.current) {
126
135
  show();
127
136
  }
128
137
  else {
129
138
  hide();
130
139
  }
131
- }, [hide, isOpen, show]);
140
+ wasOpenBeforeClick.current = false;
141
+ }, [hide, show]);
132
142
  const [tagContainerWidthRef, tagContainerWidth] = useContainerWidth();
133
143
  return (React.createElement(React.Fragment, null,
134
- React.createElement(Input, { ref: refs, onClick: mergeEventHandlers(onClickProp, handleClick), "aria-expanded": isOpen, "aria-activedescendant": isOpen && focusedIndex != undefined && focusedIndex > -1
144
+ React.createElement(Input, { ref: refs, "aria-expanded": isOpen, "aria-activedescendant": isOpen && focusedIndex != undefined && focusedIndex > -1
135
145
  ? getIdFromIndex(focusedIndex)
136
146
  : undefined, role: 'combobox', "aria-controls": isOpen ? `${id}-list` : undefined, "aria-autocomplete": 'list', spellCheck: false, autoCapitalize: 'none', autoCorrect: 'off', style: multiple ? { paddingInlineStart: tagContainerWidth + 18 } : {}, "aria-describedby": multiple ? `${id}-selected-live` : undefined, size: size, ...popover.getReferenceProps({
137
- onKeyDown: mergeEventHandlers(onKeyDownProp, handleKeyDown),
138
147
  ...rest,
148
+ onPointerDown: mergeEventHandlers(props.onPointerDown, handlePointerDown),
149
+ onClick: mergeEventHandlers(props.onClick, handleClick),
150
+ onKeyDown: mergeEventHandlers(props.onKeyDown, handleKeyDown),
139
151
  }) }),
140
152
  multiple && selectTags ? (React.createElement(ComboBoxMultipleContainer, { ref: tagContainerWidthRef, selectedItems: selectTags, id: `${id}-selected-live` })) : null));
141
153
  });
@@ -8,8 +8,10 @@ type FileUploadProps = {
8
8
  dragContent?: React.ReactNode;
9
9
  /**
10
10
  * Callback fired when files are dropped onto the component.
11
+ *
12
+ * The first argument is the `files` list, and the second argument is the underlying "drop" event.
11
13
  */
12
- onFileDropped: (files: FileList) => void;
14
+ onFileDropped: (files: FileList, event: React.DragEvent) => void;
13
15
  /**
14
16
  * Component to wrap `FileUpload` around.
15
17
  * Either pass `FileUploadCard` (for default state) or a different component to wrap.
@@ -4,7 +4,7 @@
4
4
  *--------------------------------------------------------------------------------------------*/
5
5
  import * as React from 'react';
6
6
  import cx from 'classnames';
7
- import { Box, useMergedRefs } from '../utils/index.js';
7
+ import { Box, mergeEventHandlers } from '../utils/index.js';
8
8
  /**
9
9
  * File upload component to be wrapped around `FileUploadCard` or any arbitrary component.
10
10
  * Provides support for dragging and dropping multiple files.
@@ -12,11 +12,9 @@ import { Box, useMergedRefs } from '../utils/index.js';
12
12
  * <FileUpload onFileDropped={console.log}><FileUploadCard /></FileUpload>
13
13
  * <FileUpload dragContent='Drop file here' onFileDropped={console.log}><Textarea /></FileUpload>
14
14
  */
15
- export const FileUpload = React.forwardRef((props, ref) => {
16
- const { dragContent, children, onFileDropped, className, contentProps, ...rest } = props;
15
+ export const FileUpload = React.forwardRef((props, forwardedRef) => {
16
+ const { dragContent, children, onFileDropped, contentProps, ...rest } = props;
17
17
  const [isDragActive, setIsDragActive] = React.useState(false);
18
- const fileUploadRef = React.useRef(null);
19
- const refs = useMergedRefs(fileUploadRef, ref);
20
18
  const onDragOverHandler = (e) => {
21
19
  e.preventDefault();
22
20
  e.stopPropagation();
@@ -33,8 +31,7 @@ export const FileUpload = React.forwardRef((props, ref) => {
33
31
  e.preventDefault();
34
32
  e.stopPropagation();
35
33
  // only set inactive if secondary target is outside the component
36
- if (isDragActive &&
37
- !fileUploadRef.current?.contains(e.relatedTarget)) {
34
+ if (isDragActive && !e.currentTarget?.contains(e.relatedTarget)) {
38
35
  setIsDragActive(false);
39
36
  }
40
37
  };
@@ -43,10 +40,10 @@ export const FileUpload = React.forwardRef((props, ref) => {
43
40
  e.stopPropagation();
44
41
  if (isDragActive) {
45
42
  setIsDragActive(false);
46
- onFileDropped(e.dataTransfer?.files);
43
+ onFileDropped(e.dataTransfer?.files, e);
47
44
  }
48
45
  };
49
- return (React.createElement(Box, { className: cx('iui-file-upload', { 'iui-drag': isDragActive }, className), onDragEnter: onDragEnterHandler, onDragOver: onDragOverHandler, onDragLeave: onDragLeaveHandler, onDrop: onDropHandler, ref: refs, ...rest },
46
+ return (React.createElement(Box, { ...rest, className: cx('iui-file-upload', { 'iui-drag': isDragActive }, props?.className), ref: forwardedRef, onDragEnter: mergeEventHandlers(props.onDragEnter, onDragEnterHandler), onDragOver: mergeEventHandlers(props.onDragOver, onDragOverHandler), onDragLeave: mergeEventHandlers(props.onDragLeave, onDragLeaveHandler), onDrop: mergeEventHandlers(props.onDrop, onDropHandler) },
50
47
  dragContent ? (children) : (React.createElement(Box, { as: 'div', ...contentProps, className: cx('iui-content', contentProps?.className) }, children)),
51
48
  dragContent && (React.createElement(Box, { as: 'div', ...contentProps, className: cx('iui-content', contentProps?.className) }, dragContent))));
52
49
  });
@@ -29,9 +29,63 @@ export declare const InputWithDecorations: PolymorphicForwardRefComponent<"div",
29
29
  */
30
30
  Button: PolymorphicForwardRefComponent<"span", Omit<Omit<React.DetailedHTMLProps<React.ButtonHTMLAttributes<HTMLButtonElement>, HTMLButtonElement>, "ref"> & {
31
31
  ref?: ((instance: HTMLButtonElement | null) => void) | React.RefObject<HTMLButtonElement> | null | undefined;
32
- }, "label" | "as" | "size" | "htmlDisabled" | "styleType" | "isActive" | "iconProps"> & {
32
+ }, "label" | "as" | "size" | "htmlDisabled" | "styleType" | "labelProps" | "isActive" | "iconProps"> & {
33
33
  isActive?: boolean | undefined;
34
34
  label?: React.ReactNode;
35
+ labelProps?: Omit<Omit<Omit<Omit<React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>, "ref"> & {
36
+ ref?: ((instance: HTMLDivElement | null) => void) | React.RefObject<HTMLDivElement> | null | undefined;
37
+ }, "as" | "children" | "content" | "portal" | keyof {
38
+ placement?: import("@floating-ui/utils").Placement | undefined;
39
+ visible?: boolean | undefined;
40
+ onVisibleChange?: ((visible: boolean) => void) | undefined;
41
+ autoUpdateOptions?: {
42
+ ancestorScroll?: boolean | undefined;
43
+ ancestorResize?: boolean | undefined;
44
+ elementResize?: boolean | undefined;
45
+ animationFrame?: boolean | undefined;
46
+ layoutShift?: boolean | undefined;
47
+ } | undefined;
48
+ middleware?: {
49
+ offset?: number | undefined;
50
+ flip?: boolean | undefined;
51
+ shift?: boolean | undefined;
52
+ size?: boolean | undefined;
53
+ autoPlacement?: boolean | undefined;
54
+ hide?: boolean | undefined;
55
+ inline?: boolean | undefined;
56
+ } | undefined;
57
+ reference?: HTMLElement | null | undefined;
58
+ ariaStrategy?: "label" | "description" | "none" | undefined;
59
+ id?: string | undefined;
60
+ }> & {
61
+ content: React.ReactNode;
62
+ children?: React.ReactNode;
63
+ } & import("../utils/index.js").PortalProps & {
64
+ placement?: import("@floating-ui/utils").Placement | undefined;
65
+ visible?: boolean | undefined;
66
+ onVisibleChange?: ((visible: boolean) => void) | undefined;
67
+ autoUpdateOptions?: {
68
+ ancestorScroll?: boolean | undefined;
69
+ ancestorResize?: boolean | undefined;
70
+ elementResize?: boolean | undefined;
71
+ animationFrame?: boolean | undefined;
72
+ layoutShift?: boolean | undefined;
73
+ } | undefined;
74
+ middleware?: {
75
+ offset?: number | undefined;
76
+ flip?: boolean | undefined;
77
+ shift?: boolean | undefined;
78
+ size?: boolean | undefined;
79
+ autoPlacement?: boolean | undefined;
80
+ hide?: boolean | undefined;
81
+ inline?: boolean | undefined;
82
+ } | undefined;
83
+ reference?: HTMLElement | null | undefined;
84
+ ariaStrategy?: "label" | "description" | "none" | undefined;
85
+ id?: string | undefined;
86
+ } & {
87
+ as?: "div" | undefined;
88
+ }, "ref">, "children" | "content" | "reference" | "ariaStrategy"> | undefined;
35
89
  iconProps?: React.DetailedHTMLProps<React.HTMLAttributes<HTMLSpanElement>, HTMLSpanElement> | undefined;
36
90
  } & Omit<import("../Buttons/Button.js").ButtonProps, "startIcon" | "endIcon" | "labelProps" | "startIconProps" | "endIconProps"> & {
37
91
  as?: "button" | undefined;
@@ -23,6 +23,21 @@ type PopoverOptions = {
23
23
  * @default true
24
24
  */
25
25
  closeOnOutsideClick?: boolean;
26
+ /**
27
+ * Middleware options.
28
+ *
29
+ * By default, `flip` and `shift` are enabled.
30
+ *
31
+ * @see https://floating-ui.com/docs/middleware
32
+ */
33
+ middleware?: {
34
+ offset?: number;
35
+ flip?: boolean;
36
+ shift?: boolean;
37
+ autoPlacement?: boolean;
38
+ hide?: boolean;
39
+ inline?: boolean;
40
+ };
26
41
  };
27
42
  type PopoverInternalProps = {
28
43
  /**
@@ -43,19 +58,6 @@ type PopoverInternalProps = {
43
58
  animationFrame?: boolean;
44
59
  layoutShift?: boolean;
45
60
  };
46
- /**
47
- * Middleware options.
48
- *
49
- * @see https://floating-ui.com/docs/offset
50
- */
51
- middleware?: {
52
- offset?: number;
53
- flip?: boolean;
54
- shift?: boolean;
55
- autoPlacement?: boolean;
56
- hide?: boolean;
57
- inline?: boolean;
58
- };
59
61
  /**
60
62
  * By default, the popover will only open on click.
61
63
  * `hover` and `focus` can be manually specified as triggers.
@@ -96,7 +98,7 @@ export declare const usePopover: (options: PopoverOptions & PopoverInternalProps
96
98
  update: () => void;
97
99
  floatingStyles: React.CSSProperties;
98
100
  open: boolean;
99
- onOpenChange: (open: boolean, event?: Event | undefined) => void;
101
+ onOpenChange: (open: boolean, event?: Event | undefined, reason?: import("@floating-ui/react").OpenChangeReason | undefined) => void;
100
102
  events: import("@floating-ui/react").FloatingEvents;
101
103
  dataRef: React.MutableRefObject<import("@floating-ui/react").ContextData>;
102
104
  nodeId: string | undefined;
@@ -125,6 +127,12 @@ type PopoverPublicProps = {
125
127
  * @default false
126
128
  */
127
129
  applyBackground?: boolean;
130
+ /**
131
+ * This is used to position the popover relative to a different element than the trigger.
132
+ *
133
+ * Recommended to use state to store this element, rather than a ref.
134
+ */
135
+ positionReference?: HTMLElement;
128
136
  } & PortalProps & PopoverOptions;
129
137
  /**
130
138
  * A utility component to help with positioning of floating content relative to a trigger.
@@ -5,12 +5,13 @@
5
5
  import * as React from 'react';
6
6
  import cx from 'classnames';
7
7
  import { useFloating, useClick, useDismiss, useInteractions, size, autoUpdate, offset, flip, shift, autoPlacement, inline, hide, FloatingFocusManager, useHover, useFocus, safePolygon, useRole, FloatingPortal, } from '@floating-ui/react';
8
- import { Box, cloneElementWithRef, useControlledState, useId, useMergedRefs, } from '../utils/index.js';
8
+ import { Box, cloneElementWithRef, useControlledState, useId, useIsomorphicLayoutEffect, useMergedRefs, } from '../utils/index.js';
9
9
  import { Portal } from '../utils/components/Portal.js';
10
10
  import { ThemeProvider } from '../ThemeProvider/ThemeProvider.js';
11
11
  // ----------------------------------------------------------------------------
12
12
  export const usePopover = (options) => {
13
- const { placement = 'bottom-start', visible, onVisibleChange, closeOnOutsideClick, autoUpdateOptions, middleware = { flip: true, shift: true }, matchWidth, trigger = { click: true, hover: false, focus: false }, role, } = options;
13
+ const { placement = 'bottom-start', visible, onVisibleChange, closeOnOutsideClick, autoUpdateOptions, matchWidth, trigger = { click: true, hover: false, focus: false }, role, } = options;
14
+ const middleware = { flip: true, shift: true, ...options.middleware };
14
15
  const [open, onOpenChange] = useControlledState(false, visible, onVisibleChange);
15
16
  const floating = useFloating({
16
17
  placement,
@@ -81,7 +82,10 @@ export const Popover = React.forwardRef((props, forwardedRef) => {
81
82
  const { portal = true,
82
83
  //
83
84
  // popover options
84
- visible, placement = 'bottom-start', onVisibleChange, closeOnOutsideClick = true,
85
+ visible, placement = 'bottom-start', onVisibleChange, closeOnOutsideClick = true, middleware,
86
+ //
87
+ // extra props
88
+ positionReference,
85
89
  //
86
90
  // dom props
87
91
  className, children, content, applyBackground = false, ...rest } = props;
@@ -91,11 +95,19 @@ export const Popover = React.forwardRef((props, forwardedRef) => {
91
95
  onVisibleChange,
92
96
  closeOnOutsideClick,
93
97
  role: 'dialog',
98
+ middleware,
94
99
  });
95
100
  const [popoverElement, setPopoverElement] = React.useState();
96
101
  const popoverRef = useMergedRefs(popover.refs.setFloating, forwardedRef, setPopoverElement);
97
102
  const triggerId = `${useId()}-trigger`;
98
103
  const hasAriaLabel = !!props['aria-labelledby'] || !!props['aria-label'];
104
+ useIsomorphicLayoutEffect(() => {
105
+ if (!positionReference) {
106
+ return;
107
+ }
108
+ popover.refs.setPositionReference(positionReference);
109
+ return () => void popover.refs.setPositionReference(null);
110
+ }, [popover.refs, positionReference]);
99
111
  return (React.createElement(React.Fragment, null,
100
112
  cloneElementWithRef(children, (children) => ({
101
113
  id: children.props.id || triggerId,
@@ -3,6 +3,7 @@
3
3
  * See LICENSE.md in the project root for license terms and full copyright notice.
4
4
  *--------------------------------------------------------------------------------------------*/
5
5
  import * as React from 'react';
6
+ import * as ReactDOM from 'react-dom';
6
7
  import cx from 'classnames';
7
8
  import { actions as TableActions, useFlexLayout, useFilters, useRowSelect, useSortBy, useTable, useExpanded, usePagination, useColumnOrder, useGlobalFilter, } from 'react-table';
8
9
  import { ProgressRadial } from '../ProgressIndicators/ProgressRadial.js';
@@ -461,11 +462,19 @@ export const Table = (props) => {
461
462
  updateStickyState();
462
463
  }
463
464
  }, tabIndex: -1, "aria-multiselectable": (isSelectable && selectionMode === 'multi') || undefined },
465
+ React.createElement(ShadowTemplate, null,
466
+ React.createElement("slot", null),
467
+ React.createElement("div", { "aria-hidden": true, style: {
468
+ // This ensures that the table-body is always the same width as the table-header,
469
+ // even if the table has no rows. See https://github.com/iTwin/iTwinUI/pull/1725
470
+ width: headerRef.current?.scrollWidth,
471
+ height: 0.1,
472
+ } })),
464
473
  data.length !== 0 && (React.createElement(React.Fragment, null, enableVirtualization ? (React.createElement(VirtualScroll, { itemsLength: page.length, itemRenderer: virtualizedItemRenderer, scrollToIndex: scrollToIndex })) : (page.map((_, index) => getPreparedRow(index))))),
465
474
  isLoading && data.length === 0 && (React.createElement(Box, { as: 'div', ...emptyTableContentProps, className: cx('iui-table-empty', emptyTableContentProps?.className) },
466
475
  React.createElement(ProgressRadial, { indeterminate: true }))),
467
- isLoading && data.length !== 0 && (React.createElement(Box, { className: 'iui-table-row' },
468
- React.createElement(Box, { className: 'iui-table-cell', style: { justifyContent: 'center' } },
476
+ isLoading && data.length !== 0 && (React.createElement(Box, { className: 'iui-table-row', "data-iui-loading": 'true' },
477
+ React.createElement(Box, { className: 'iui-table-cell' },
469
478
  React.createElement(ProgressRadial, { indeterminate: true, size: 'small' })))),
470
479
  !isLoading && data.length === 0 && !areFiltersSet && (React.createElement(Box, { as: 'div', ...emptyTableContentProps, className: cx('iui-table-empty', emptyTableContentProps?.className) },
471
480
  React.createElement("div", null, emptyTableContent))),
@@ -476,3 +485,20 @@ export const Table = (props) => {
476
485
  paginatorRenderer?.(paginatorRendererProps))));
477
486
  };
478
487
  export default Table;
488
+ // ----------------------------------------------------------------------------
489
+ /**
490
+ * Wrapper around `<template>` element that attaches shadow root to its parent
491
+ * and moves its children into the shadow root.
492
+ */
493
+ const ShadowTemplate = ({ children }) => {
494
+ const [shadowRoot, setShadowRoot] = React.useState();
495
+ const attachShadowRef = React.useCallback((template) => {
496
+ const parent = template?.parentElement;
497
+ if (!template || !parent || parent.shadowRoot) {
498
+ return;
499
+ }
500
+ setShadowRoot(parent.attachShadow({ mode: 'open' }));
501
+ template.remove();
502
+ }, []);
503
+ return (React.createElement("template", { ref: attachShadowRef }, shadowRoot && ReactDOM.createPortal(children, shadowRoot)));
504
+ };
@@ -33,7 +33,7 @@ const TabList = React.forwardRef((props, ref) => {
33
33
  const isClient = useIsClient();
34
34
  const tablistRef = React.useRef(null);
35
35
  const [tablistSizeRef, tabsWidth] = useContainerWidth(type !== 'default');
36
- const refs = useMergedRefs(ref, tablistRef, tablistSizeRef);
36
+ const refs = useMergedRefs(ref, tablistRef, tablistSizeRef, useScrollbarGutter());
37
37
  return (React.createElement(Box, { className: cx('iui-tabs', `iui-${type}`, {
38
38
  'iui-green': color === 'green',
39
39
  'iui-animated': type !== 'default' && isClient,
@@ -134,7 +134,7 @@ const Tab = React.forwardRef((props, forwardedRef) => {
134
134
  setActiveValue(value);
135
135
  }
136
136
  }, [activeValue, setActiveValue, value]);
137
- return (React.createElement(ButtonBase, { className: cx('iui-tab', className), role: 'tab', tabIndex: isActive ? 0 : -1, "aria-selected": isActive, "aria-controls": `${idPrefix}-panel-${value}`, ref: useMergedRefs(tabRef, forwardedRef, setInitialActiveRef), ...rest, id: `${idPrefix}-tab-${value}`, onClick: mergeEventHandlers(props.onClick, () => setActiveValue(value)), onKeyDown: mergeEventHandlers(props.onKeyDown, onKeyDown), onFocus: mergeEventHandlers(props.onFocus, () => {
137
+ return (React.createElement(ButtonBase, { className: cx('iui-tab', className), role: 'tab', tabIndex: isActive ? 0 : -1, "aria-selected": isActive, "aria-controls": `${idPrefix}-panel-${value.replaceAll(' ', '-')}`, ref: useMergedRefs(tabRef, forwardedRef, setInitialActiveRef), ...rest, id: `${idPrefix}-tab-${value.replaceAll(' ', '-')}`, onClick: mergeEventHandlers(props.onClick, () => setActiveValue(value)), onKeyDown: mergeEventHandlers(props.onKeyDown, onKeyDown), onFocus: mergeEventHandlers(props.onFocus, () => {
138
138
  tabRef.current?.scrollIntoView({ block: 'nearest', inline: 'nearest' });
139
139
  if (focusActivationMode === 'auto' && !props.disabled) {
140
140
  setActiveValue(value);
@@ -174,7 +174,7 @@ TabsActions.displayName = 'Tabs.Actions';
174
174
  const TabsPanel = React.forwardRef((props, ref) => {
175
175
  const { value, className, children, ...rest } = props;
176
176
  const { activeValue, idPrefix } = useSafeContext(TabsContext);
177
- return (React.createElement(Box, { className: cx('iui-tabs-content', className), "aria-labelledby": `${idPrefix}-tab-${value}`, role: 'tabpanel', hidden: activeValue !== value ? true : undefined, ref: ref, ...rest, id: `${idPrefix}-panel-${value}` }, children));
177
+ return (React.createElement(Box, { className: cx('iui-tabs-content', className), "aria-labelledby": `${idPrefix}-tab-${value.replaceAll(' ', '-')}`, role: 'tabpanel', hidden: activeValue !== value ? true : undefined, ref: ref, ...rest, id: `${idPrefix}-panel-${value.replaceAll(' ', '-')}` }, children));
178
178
  });
179
179
  TabsPanel.displayName = 'Tabs.Panel';
180
180
  const LegacyTabsComponent = React.forwardRef((props, forwardedRef) => {
@@ -321,3 +321,23 @@ export const Tabs = Object.assign(LegacyTabsComponent, {
321
321
  const TabsContext = React.createContext(undefined);
322
322
  const TabListContext = React.createContext(undefined);
323
323
  export default Tabs;
324
+ // ----------------------------------------------------------------------------
325
+ /**
326
+ * This conditionally adds `scrollbar-gutter: stable` only if the content overflows.
327
+ * This is a workaround to prevent layout shift that happens because of scrollbar width.
328
+ *
329
+ * @see https://github.com/iTwin/iTwinUI/issues/1627
330
+ */
331
+ const useScrollbarGutter = () => {
332
+ return React.useCallback((element) => {
333
+ if (element) {
334
+ if (element.scrollHeight > element.clientHeight) {
335
+ element.style.scrollbarGutter = 'stable';
336
+ // Safari fallback
337
+ if (!CSS.supports('scrollbar-gutter: stable')) {
338
+ element.style.overflowY = 'scroll';
339
+ }
340
+ }
341
+ }
342
+ }, []);
343
+ };