@jetbrains/ring-ui 8.0.0-beta.1 → 8.0.0-beta.11

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 (211) hide show
  1. package/babel.config.js +1 -9
  2. package/components/alert/alert-actions.d.ts +4 -0
  3. package/components/alert/alert-actions.js +5 -0
  4. package/components/alert/alert-heading.d.ts +4 -0
  5. package/components/alert/alert-heading.js +6 -0
  6. package/components/alert/alert.css +63 -22
  7. package/components/alert/alert.d.ts +14 -1
  8. package/components/alert/alert.js +34 -19
  9. package/components/alert/container.css +1 -2
  10. package/components/alert-service/alert-service.d.ts +2 -1
  11. package/components/alert-service/alert-service.js +8 -4
  12. package/components/auth/auth-core.d.ts +5 -9
  13. package/components/auth/auth-core.js +56 -15
  14. package/components/auth-dialog-service/auth-dialog-service.js +5 -2
  15. package/components/avatar/avatar-info.js +0 -2
  16. package/components/avatar/avatar-size.d.ts +0 -4
  17. package/components/avatar/avatar-size.js +0 -4
  18. package/components/avatar/avatar.js +0 -6
  19. package/components/avatar/fallback-avatar.js +0 -28
  20. package/components/avatar-stack/avatar-stack.js +1 -1
  21. package/components/banner/banner.css +3 -3
  22. package/components/button/button.css +4 -4
  23. package/components/button/button.d.ts +0 -4
  24. package/components/button/button.js +2 -7
  25. package/components/button-group/button-group.css +5 -5
  26. package/components/checkbox/checkbox.d.ts +1 -1
  27. package/components/collapse/collapse-content.d.ts +7 -0
  28. package/components/collapse/collapse-content.js +44 -11
  29. package/components/collapse/collapse-context.d.ts +2 -0
  30. package/components/collapse/collapse-context.js +2 -0
  31. package/components/collapse/collapse-control.js +2 -2
  32. package/components/collapse/collapse.css +8 -2
  33. package/components/collapse/collapse.d.ts +9 -0
  34. package/components/collapse/collapse.js +12 -3
  35. package/components/{expand → collapsible-group}/collapsible-group.css +7 -1
  36. package/components/collapsible-group/collapsible-group.d.ts +33 -0
  37. package/components/{expand → collapsible-group}/collapsible-group.js +13 -21
  38. package/components/confirm/confirm.js +12 -8
  39. package/components/confirm-service/confirm-service.js +5 -2
  40. package/components/data-list/data-list.d.ts +1 -1
  41. package/components/data-list/data-list.mock.d.ts +1 -1
  42. package/components/data-list/item.d.ts +1 -1
  43. package/components/data-list/selection.d.ts +1 -1
  44. package/components/data-list/selection.js +1 -1
  45. package/components/date-picker/consts.d.ts +1 -0
  46. package/components/date-picker/date-input.js +6 -2
  47. package/components/date-picker/months.js +2 -2
  48. package/components/date-picker/use-scroll-behavior.js +5 -6
  49. package/components/date-picker/years.js +2 -2
  50. package/components/dialog/dialog-controls.d.ts +13 -0
  51. package/components/dialog/dialog-controls.js +17 -0
  52. package/components/dialog/dialog-geometry.d.ts +31 -0
  53. package/components/dialog/dialog-geometry.js +48 -0
  54. package/components/dialog/dialog.css +166 -27
  55. package/components/dialog/dialog.d.ts +15 -7
  56. package/components/dialog/dialog.js +110 -29
  57. package/components/dropdown-menu/dropdown-menu.d.ts +4 -4
  58. package/components/dropdown-menu/dropdown-menu.js +4 -4
  59. package/components/editable-heading/editable-heading.d.ts +1 -2
  60. package/components/editable-heading/editable-heading.js +18 -19
  61. package/components/global/compose-refs.d.ts +2 -1
  62. package/components/global/compose-refs.js +24 -8
  63. package/components/global/create-stateful-context.js +5 -5
  64. package/components/global/focus-with-temporary-tabindex.d.ts +11 -0
  65. package/components/global/focus-with-temporary-tabindex.js +21 -0
  66. package/components/global/get-viewport-size.d.ts +5 -0
  67. package/components/global/get-viewport-size.js +5 -0
  68. package/components/global/intersection-observer-context.d.ts +31 -11
  69. package/components/global/intersection-observer-context.js +47 -28
  70. package/components/global/is-within-interactive-element.d.ts +6 -0
  71. package/components/global/is-within-interactive-element.js +26 -0
  72. package/components/global/is-within-navigable-element.d.ts +6 -0
  73. package/components/global/is-within-navigable-element.js +27 -0
  74. package/components/global/parse-css-duration.d.ts +5 -0
  75. package/components/global/parse-css-duration.js +13 -0
  76. package/components/global/rerender-hoc.d.ts +4 -2
  77. package/components/global/rerender-hoc.js +4 -4
  78. package/components/global/schedule-with-cleanup.d.ts +12 -0
  79. package/components/global/schedule-with-cleanup.js +34 -0
  80. package/components/{legacy-table/selection.d.ts → global/table-selection.d.ts} +14 -14
  81. package/components/{legacy-table/selection.js → global/table-selection.js} +2 -2
  82. package/components/global/theme.d.ts +4 -3
  83. package/components/global/theme.js +8 -8
  84. package/components/global/variables.css +18 -28
  85. package/components/global/variables.interface.d.ts +0 -4
  86. package/components/global/variables_dark.css +18 -25
  87. package/components/header/header.css +3 -3
  88. package/components/http/http.d.ts +2 -2
  89. package/components/http/http.js +6 -17
  90. package/components/i18n/i18n-context.js +1 -1
  91. package/components/i18n/i18n.d.ts +1 -0
  92. package/components/i18n/messages.json +1 -0
  93. package/components/icon/icon.css +0 -27
  94. package/components/icon/icon.d.ts +0 -1
  95. package/components/icon/icon.js +2 -4
  96. package/components/input/input.d.ts +1 -1
  97. package/components/island/adaptive-island-hoc.js +4 -4
  98. package/components/island/content.d.ts +7 -2
  99. package/components/island/content.js +5 -5
  100. package/components/island/header.js +1 -1
  101. package/components/legacy-table/cell.js +1 -1
  102. package/components/legacy-table/header-cell.js +1 -1
  103. package/components/legacy-table/header.js +1 -1
  104. package/components/legacy-table/multitable.d.ts +1 -1
  105. package/components/legacy-table/row.d.ts +1 -1
  106. package/components/legacy-table/row.js +1 -1
  107. package/components/legacy-table/selection-adapter.d.ts +3 -3
  108. package/components/legacy-table/selection-shortcuts-hoc.d.ts +5 -5
  109. package/components/legacy-table/simple-table.d.ts +2 -2
  110. package/components/legacy-table/simple-table.js +3 -3
  111. package/components/legacy-table/smart-table.d.ts +5 -5
  112. package/components/legacy-table/smart-table.js +3 -3
  113. package/components/legacy-table/table.js +1 -1
  114. package/components/list/consts.d.ts +0 -1
  115. package/components/list/consts.js +0 -1
  116. package/components/list/list.d.ts +0 -4
  117. package/components/login-dialog/service.js +5 -2
  118. package/components/panel/panel.css +1 -1
  119. package/components/popup/popup.d.ts +2 -0
  120. package/components/popup/popup.js +2 -2
  121. package/components/popup/popup.target.d.ts +3 -2
  122. package/components/popup/popup.target.js +4 -6
  123. package/components/query-assist/query-assist.d.ts +3 -1
  124. package/components/query-assist/query-assist.js +2 -2
  125. package/components/radio/radio-item.d.ts +3 -3
  126. package/components/radio/radio-item.js +3 -4
  127. package/components/radio/radio.d.ts +2 -2
  128. package/components/radio/radio.js +1 -1
  129. package/components/select/select-popup.d.ts +3 -2
  130. package/components/select/select-popup.js +3 -3
  131. package/components/select/select.d.ts +6 -2
  132. package/components/select/select.js +2 -4
  133. package/components/selection-toolbar/selection-toolbar.css +86 -0
  134. package/components/selection-toolbar/selection-toolbar.d.ts +11 -0
  135. package/components/selection-toolbar/selection-toolbar.js +23 -0
  136. package/components/slider/slider.js +4 -5
  137. package/components/tab-trap/tab-trap.d.ts +3 -3
  138. package/components/tab-trap/tab-trap.js +3 -5
  139. package/components/table/default-item-renderer.d.ts +41 -11
  140. package/components/table/default-item-renderer.js +60 -47
  141. package/components/table/internal/reorder-animation-context.d.ts +21 -0
  142. package/components/table/internal/reorder-animation-context.js +60 -0
  143. package/components/table/internal/reorder-handle.d.ts +9 -0
  144. package/components/table/internal/reorder-handle.js +334 -0
  145. package/components/table/internal/reorder-layout-context.d.ts +16 -0
  146. package/components/table/internal/reorder-layout-context.js +69 -0
  147. package/components/table/internal/table-header.d.ts +1 -0
  148. package/components/table/internal/table-header.js +143 -0
  149. package/components/table/internal/virtualization.d.ts +41 -0
  150. package/components/table/{table-virtualize.js → internal/virtualization.js} +82 -46
  151. package/components/table/item-virtualization.d.ts +37 -0
  152. package/components/table/item-virtualization.js +26 -0
  153. package/components/table/reorder-animation.d.ts +37 -0
  154. package/components/table/reorder-animation.js +11 -0
  155. package/components/table/reorder-item-layout.d.ts +32 -0
  156. package/components/table/reorder-item-layout.js +23 -0
  157. package/components/table/table-const.d.ts +8 -6
  158. package/components/table/table-const.js +7 -5
  159. package/components/table/table-primitives.d.ts +64 -0
  160. package/components/table/table-primitives.js +34 -0
  161. package/components/table/table-props.d.ts +304 -0
  162. package/components/table/table-props.js +1 -0
  163. package/components/table/table.css +179 -67
  164. package/components/table/table.d.ts +231 -224
  165. package/components/table/table.js +327 -2
  166. package/components/tag/tag.css +3 -3
  167. package/components/tags-input/tags-input.d.ts +3 -1
  168. package/components/tooltip/tooltip.d.ts +2 -1
  169. package/components/tooltip/tooltip.js +6 -5
  170. package/components/upload/upload.d.ts +4 -3
  171. package/components/upload/upload.js +3 -7
  172. package/components/user-agreement/service.js +5 -2
  173. package/components/user-agreement/user-agreement.css +2 -8
  174. package/components/user-agreement/user-agreement.js +8 -10
  175. package/components/user-card/smart-user-card-tooltip.d.ts +1 -1
  176. package/components/util-stories.d.ts +26 -0
  177. package/components/util-stories.js +61 -0
  178. package/package.json +47 -50
  179. package/components/content-layout/content-layout.css +0 -109
  180. package/components/content-layout/content-layout.d.ts +0 -20
  181. package/components/content-layout/content-layout.js +0 -40
  182. package/components/content-layout/sidebar.d.ts +0 -26
  183. package/components/content-layout/sidebar.js +0 -71
  184. package/components/date-picker/use-intersection-observer.d.ts +0 -6
  185. package/components/date-picker/use-intersection-observer.js +0 -48
  186. package/components/expand/collapsible-group.d.ts +0 -16
  187. package/components/global/composeRefs.d.ts +0 -6
  188. package/components/global/composeRefs.js +0 -7
  189. package/components/global/use-event-callback.d.ts +0 -1
  190. package/components/global/use-event-callback.js +0 -15
  191. package/components/grid/col.d.ts +0 -16
  192. package/components/grid/col.js +0 -35
  193. package/components/grid/grid.css +0 -920
  194. package/components/grid/grid.d.ts +0 -13
  195. package/components/grid/grid.js +0 -19
  196. package/components/grid/row.d.ts +0 -21
  197. package/components/grid/row.js +0 -41
  198. package/components/old-browsers-message/__mocks__/old-browsers-message.js +0 -1
  199. package/components/old-browsers-message/old-browsers-message-stop.d.ts +0 -1
  200. package/components/old-browsers-message/old-browsers-message-stop.js +0 -2
  201. package/components/old-browsers-message/old-browsers-message.css +0 -26
  202. package/components/old-browsers-message/old-browsers-message.d.ts +0 -5
  203. package/components/old-browsers-message/old-browsers-message.js +0 -86
  204. package/components/old-browsers-message/white-list.d.ts +0 -2
  205. package/components/old-browsers-message/white-list.js +0 -24
  206. package/components/table/table-base.d.ts +0 -24
  207. package/components/table/table-base.js +0 -79
  208. package/components/table/table-component.d.ts +0 -53
  209. package/components/table/table-component.js +0 -101
  210. package/components/table/table-virtualize.d.ts +0 -32
  211. /package/components/legacy-table/{table.css → legacy-table.css} +0 -0
@@ -2,20 +2,19 @@ import { createRef, PureComponent } from 'react';
2
2
  import * as React from 'react';
3
3
  import { createPortal } from 'react-dom';
4
4
  import classNames from 'classnames';
5
- import closeIcon from '@jetbrains/icons/close';
6
5
  import { AdaptiveIsland } from '../island/island';
7
6
  import getUID from '../global/get-uid';
8
7
  import dataTests from '../global/data-tests';
9
8
  import Shortcuts from '../shortcuts/shortcuts';
10
9
  import TabTrap from '../tab-trap/tab-trap';
11
- import Button from '../button/button';
12
10
  import { normalizePopupTarget, PopupTarget, PopupTargetContext } from '../popup/popup.target';
13
11
  import { getPopupContainer } from '../popup/popup';
12
+ import getViewportSize from '../global/get-viewport-size';
14
13
  import { preventerFactory as scrollPreventerFactory } from './dialog-body-scroll-preventer';
14
+ import DialogControls from './dialog-controls';
15
+ import { fitGeometry, getNativeTabIndex, getResizeMinimum, getTrapDisabled, moveGeometry, resizeGeometry, } from './dialog-geometry';
15
16
  import styles from './dialog.css';
16
- /**
17
- * @name Dialog
18
- */
17
+ /** @name Dialog */
19
18
  function noop() { }
20
19
  export default class Dialog extends PureComponent {
21
20
  static defaultProps = {
@@ -25,21 +24,22 @@ export default class Dialog extends PureComponent {
25
24
  onCloseClick: noop,
26
25
  onCloseAttempt: noop,
27
26
  showCloseButton: false,
28
- closeButtonInside: false,
27
+ movable: true,
28
+ resizable: true,
29
29
  shortcutOptions: { modal: false },
30
30
  trapFocus: false,
31
31
  autoFocusFirst: true,
32
+ native: true,
32
33
  modal: true,
33
34
  preventBodyScroll: true,
34
35
  };
35
- state = {
36
- shortcutsScope: getUID('ring-dialog-'),
37
- };
36
+ state = { shortcutsScope: getUID('ring-dialog-'), geometry: null, resized: false };
38
37
  componentDidMount() {
39
38
  const { show, native } = this.props;
40
39
  if (native && show) {
41
40
  this.toggleNativeDialog();
42
41
  }
42
+ window.addEventListener('resize', this.fitGeometryToViewport);
43
43
  this.toggleScrollPreventer();
44
44
  }
45
45
  componentDidUpdate(prevProps) {
@@ -49,15 +49,23 @@ export default class Dialog extends PureComponent {
49
49
  }
50
50
  if (prevProps.show !== this.props.show) {
51
51
  this.toggleScrollPreventer();
52
+ if (show) {
53
+ this.fitGeometryToViewport();
54
+ }
55
+ else {
56
+ this.interaction = null;
57
+ }
52
58
  }
53
59
  }
54
60
  componentWillUnmount() {
61
+ window.removeEventListener('resize', this.fitGeometryToViewport);
62
+ this.interaction = null;
55
63
  this.scrollPreventer.reset();
56
64
  }
57
65
  scrollPreventer = scrollPreventerFactory(getUID('preventer-'));
58
66
  uid = getUID('dialog-');
59
67
  toggleNativeDialog() {
60
- const { show, modal } = this.props;
68
+ const { show, modal, autoFocusFirst } = this.props;
61
69
  if (this.nativeDialog.current) {
62
70
  if (show) {
63
71
  this.nativeDialog.current.removeAttribute('open');
@@ -67,6 +75,9 @@ export default class Dialog extends PureComponent {
67
75
  else {
68
76
  this.nativeDialog.current.show();
69
77
  }
78
+ if (!autoFocusFirst && !this.nativeDialog.current.querySelector('[autofocus]')) {
79
+ this.nativeDialog.current.focus();
80
+ }
70
81
  }
71
82
  else {
72
83
  this.nativeDialog.current.close();
@@ -99,42 +110,112 @@ export default class Dialog extends PureComponent {
99
110
  this.props.onCloseAttempt(event);
100
111
  }
101
112
  };
102
- return {
103
- esc: onEscape,
104
- };
113
+ return { esc: onEscape };
105
114
  };
106
115
  dialog;
107
- dialogRef = (tabTrap) => {
108
- this.dialog = tabTrap && tabTrap.node;
116
+ dialogRef = (tabTrapObj) => {
117
+ this.dialog = tabTrapObj && tabTrapObj.node;
109
118
  };
110
119
  nativeDialog = createRef();
120
+ innerContainer = createRef();
121
+ interaction = null;
122
+ resizeMinimum = null;
123
+ fitGeometryToViewport = () => {
124
+ this.setState(({ geometry }) => {
125
+ if (!geometry) {
126
+ return null;
127
+ }
128
+ const { width, height } = getViewportSize();
129
+ return { geometry: fitGeometry(geometry, width, height) };
130
+ });
131
+ };
132
+ startInteraction = (event, direction) => {
133
+ if ((event.button ?? 0) !== 0 || !this.innerContainer.current) {
134
+ return;
135
+ }
136
+ const { left, top, width, height } = this.innerContainer.current.getBoundingClientRect();
137
+ const resizeMinimum = this.resizeMinimum ?? getResizeMinimum(this.innerContainer.current.firstElementChild);
138
+ if (direction !== 'move') {
139
+ this.resizeMinimum = resizeMinimum;
140
+ }
141
+ this.interaction = {
142
+ pointerId: event.pointerId,
143
+ direction,
144
+ startX: event.clientX,
145
+ startY: event.clientY,
146
+ ...resizeMinimum,
147
+ geometry: { left, top, width, height },
148
+ };
149
+ this.setState(({ resized }) => ({ geometry: { left, top, width, height }, resized: resized || direction !== 'move' }));
150
+ event.currentTarget.setPointerCapture?.(event.pointerId);
151
+ event.preventDefault();
152
+ };
153
+ continueInteraction = (event) => {
154
+ const interaction = this.interaction;
155
+ if (!interaction || interaction.pointerId !== event.pointerId) {
156
+ return;
157
+ }
158
+ const dx = event.clientX - interaction.startX;
159
+ const dy = event.clientY - interaction.startY;
160
+ const { width: viewportWidth, height: viewportHeight } = getViewportSize();
161
+ const start = interaction.geometry;
162
+ if (interaction.direction === 'move') {
163
+ this.setState({ geometry: moveGeometry(start, dx, dy, viewportWidth, viewportHeight) });
164
+ return;
165
+ }
166
+ this.setState({
167
+ geometry: resizeGeometry(interaction, dx, dy, viewportWidth, viewportHeight),
168
+ resized: true,
169
+ });
170
+ };
171
+ stopInteraction = (event) => {
172
+ if (this.interaction?.pointerId !== event.pointerId) {
173
+ return;
174
+ }
175
+ this.interaction = null;
176
+ if (event.currentTarget.hasPointerCapture?.(event.pointerId)) {
177
+ event.currentTarget.releasePointerCapture?.(event.pointerId);
178
+ }
179
+ };
180
+ startMove = (event) => {
181
+ const target = event.target;
182
+ if (!this.props.movable || !(target instanceof Element)) {
183
+ return;
184
+ }
185
+ const handle = target.closest('[data-ring-dialog-move-handle], [data-ring-island-header]');
186
+ if (handle?.parentElement === event.currentTarget) {
187
+ this.startInteraction(event, 'move');
188
+ }
189
+ };
111
190
  render() {
112
- const { show, showCloseButton, onOverlayClick, onCloseAttempt, onEscPress, onCloseClick, children, className, contentClassName, trapFocus, 'data-test': dataTest, closeButtonInside, portalTarget, label, closeButtonTitle, dense, shortcutOptions, native, modal, preventBodyScroll, ...restProps } = this.props;
191
+ const { show, showCloseButton, onOverlayClick, onCloseAttempt, onEscPress, onCloseClick, children, className, contentClassName, trapFocus, 'data-test': dataTest, portalTarget, label, closeButtonTitle, dense, shortcutOptions, native, modal, preventBodyScroll, movable, resizable, autoFocusFirst, focusBackOnClose, focusBackOnExit, trapDisabled, ...restProps } = this.props;
113
192
  const classes = classNames(styles.container, className);
114
193
  const shortcutsMap = this.getShortcutsMap();
115
194
  const content = (<>
116
- <Shortcuts map={shortcutsMap} scope={this.state.shortcutsScope} options={this.props.shortcutOptions}/>
195
+ <Shortcuts disabled={!show} map={shortcutsMap} scope={this.state.shortcutsScope} options={shortcutOptions}/>
117
196
  {(onOverlayClick !== noop || onCloseAttempt !== noop) && (<div
118
197
  // click handler is duplicated in close button
119
198
  role='presentation' className={styles.clickableOverlay} onClick={this.handleClick} data-test='ring-dialog-overlay'/>)}
120
- <div className={styles.innerContainer}>
199
+ <div ref={this.innerContainer} data-test='ring-dialog-inner-container' className={classNames(styles.innerContainer, { [styles.resized]: this.state.resized })} style={this.state.geometry
200
+ ? {
201
+ position: 'fixed',
202
+ left: this.state.geometry.left,
203
+ top: this.state.geometry.top,
204
+ width: this.state.resized ? this.state.geometry.width : undefined,
205
+ height: this.state.resized ? this.state.geometry.height : undefined,
206
+ }
207
+ : undefined}>
121
208
  <AdaptiveIsland className={classNames(styles.content, contentClassName, {
122
209
  [styles.dense]: dense,
123
- [styles.contentCloseButtonOutside]: !closeButtonInside,
124
- [styles.contentCloseButtonInside]: closeButtonInside,
125
- })} data-test='ring-dialog' role='dialog' aria-label={label}>
210
+ [styles.movable]: movable,
211
+ })} data-test='ring-dialog' role={native ? undefined : 'dialog'} aria-label={native ? undefined : label} onPointerDown={this.startMove} onPointerMove={this.continueInteraction} onPointerUp={this.stopInteraction} onPointerCancel={this.stopInteraction} onLostPointerCapture={this.stopInteraction}>
126
212
  {children}
127
- {showCloseButton && (<Button icon={closeIcon} data-test='ring-dialog-close-button' className={classNames(styles.closeButton, {
128
- [styles.closeButtonOutside]: !closeButtonInside,
129
- [styles.closeButtonInside]: closeButtonInside,
130
- })} iconClassName={classNames(styles.closeIcon, {
131
- [styles.closeIconOutside]: !closeButtonInside,
132
- })} onClick={this.onCloseClick} title={closeButtonTitle} aria-label={closeButtonTitle || 'close dialog'}/>)}
213
+ <DialogControls movable={movable} resizable={resizable} showCloseButton={showCloseButton} closeButtonTitle={closeButtonTitle} onCloseClick={this.onCloseClick} onStartInteraction={this.startInteraction} onStopInteraction={this.stopInteraction}/>
133
214
  </AdaptiveIsland>
134
215
  </div>
135
216
  </>);
136
217
  if (native) {
137
- return (<dialog className={classNames(styles.nativeDialog, className)} ref={this.nativeDialog} data-rg-modal-dialog-container={modal ? '' : undefined}>
218
+ return (<dialog {...restProps} aria-label={label} className={classNames(styles.nativeDialog, className)} data-test={dataTests('ring-dialog-container', dataTest)} ref={this.nativeDialog} tabIndex={getNativeTabIndex(autoFocusFirst)} onCancel={event => event.preventDefault()} data-rg-modal-dialog-container={modal ? '' : undefined}>
138
219
  <PopupTarget id={this.uid} className={styles.popupTarget}>
139
220
  {target => (<>
140
221
  {content}
@@ -157,7 +238,7 @@ export default class Dialog extends PureComponent {
157
238
  }
158
239
  }
159
240
  return createPortal(<PopupTarget id={this.uid} className={styles.popupTarget}>
160
- {target => (<TabTrap trapDisabled={!trapFocus} data-test={dataTests('ring-dialog-container', dataTest)} data-rg-modal-dialog-container='' ref={this.dialogRef} className={classes} role='presentation' {...restProps}>
241
+ {target => (<TabTrap trapDisabled={getTrapDisabled(trapFocus, trapDisabled)} autoFocusFirst={autoFocusFirst} focusBackOnClose={focusBackOnClose} focusBackOnExit={focusBackOnExit} data-test={dataTests('ring-dialog-container', dataTest)} data-rg-modal-dialog-container='' ref={this.dialogRef} className={classes} role='presentation' {...restProps}>
161
242
  {content}
162
243
  {target}
163
244
  </TabTrap>)}
@@ -1,4 +1,4 @@
1
- import { type ReactElement, type HTMLAttributes, type SyntheticEvent, type Ref, type ReactNode } from 'react';
1
+ import { type ReactElement, type HTMLAttributes, type SyntheticEvent, type ReactNode } from 'react';
2
2
  import { type SelectHandlerParams } from '../list/list';
3
3
  import { type AnchorProps, type DropdownAttrs, type DropdownChildrenFunction } from '../dropdown/dropdown';
4
4
  import PopupMenu, { type PopupMenuAttrs } from '../popup-menu/popup-menu';
@@ -10,6 +10,7 @@ export interface DropdownAnchorWrapperProps extends AnchorProps {
10
10
  }
11
11
  type OnSelectHandler<T> = ((item: ListDataItem<T>, event: Event | SyntheticEvent, params?: SelectHandlerParams) => void) | undefined;
12
12
  export interface DropdownMenuProps<T = unknown> extends Omit<DropdownAttrs, 'anchor' | 'onSelect' | 'children'> {
13
+ ref?: React.Ref<PopupMenu<T> | null>;
13
14
  anchor: ReactElement | ReactNode[] | string | ((props: AnchorProps, ariaProps: HTMLAttributes<HTMLElement>) => ReactElement | null);
14
15
  data?: readonly ListDataItem<T>[] | undefined;
15
16
  ariaLabel?: string | null | undefined;
@@ -17,9 +18,8 @@ export interface DropdownMenuProps<T = unknown> extends Omit<DropdownAttrs, 'anc
17
18
  menuProps?: PopupMenuAttrs<T> | null | undefined;
18
19
  children?: DropdownChildrenFunction;
19
20
  }
20
- declare const _default: (<T = unknown>(props: DropdownMenuProps<T> & {
21
- ref?: Ref<PopupMenu>;
22
- }) => ReactElement | null) & {
21
+ declare function DropdownMenu<T = unknown>({ ref, id, anchor, ariaLabel, data, onSelect, menuProps, children, ...restDropdownProps }: DropdownMenuProps<T>): import("react").JSX.Element;
22
+ declare const _default: typeof DropdownMenu & {
23
23
  ListProps: {
24
24
  Type: typeof import("../list/consts").Type;
25
25
  Dimension: {
@@ -1,4 +1,4 @@
1
- import { forwardRef, cloneElement, useState, } from 'react';
1
+ import { cloneElement, useState, } from 'react';
2
2
  import List, { ActiveItemContext } from '../list/list';
3
3
  import Dropdown from '../dropdown/dropdown';
4
4
  import PopupMenu from '../popup-menu/popup-menu';
@@ -31,11 +31,11 @@ function renderDropdownMenuChildren({ children, popupMenuProps }) {
31
31
  }
32
32
  return (popupProps) => children({ ...popupProps, ...popupMenuProps });
33
33
  }
34
- const DropdownMenu = forwardRef(function DropdownMenu({ id, anchor, ariaLabel, data, onSelect, menuProps, children, ...restDropdownProps }, forwardedRef) {
34
+ function DropdownMenu({ ref, id, anchor, ariaLabel, data, onSelect, menuProps, children, ...restDropdownProps }) {
35
35
  const [uid] = useState(() => getUID('dropdown-menu-list'));
36
36
  const listId = id || uid;
37
37
  const popupMenuProps = {
38
- ref: forwardedRef,
38
+ ref,
39
39
  id: listId,
40
40
  ariaLabel: ariaLabel || defaultAriaLabel,
41
41
  closeOnSelect: true,
@@ -51,5 +51,5 @@ const DropdownMenu = forwardRef(function DropdownMenu({ id, anchor, ariaLabel, d
51
51
  {renderDropdownMenuChildren({ children, popupMenuProps })}
52
52
  </Dropdown>
53
53
  </ActiveItemContext.Provider>);
54
- });
54
+ }
55
55
  export default Object.assign(DropdownMenu, { ListProps: List.ListProps });
@@ -29,5 +29,4 @@ export type EditableHeadingProps = Omit<InputHTMLAttributes<HTMLInputElement | H
29
29
  translations?: EditableHeadingTranslations;
30
30
  };
31
31
  export declare const EditableHeading: (props: EditableHeadingProps) => React.JSX.Element;
32
- declare const _default: React.MemoExoticComponent<(props: EditableHeadingProps) => React.JSX.Element>;
33
- export default _default;
32
+ export default EditableHeading;
@@ -1,4 +1,4 @@
1
- import { useEffect } from 'react';
1
+ import { useEffect, useEffectEvent, useLayoutEffect } from 'react';
2
2
  import * as React from 'react';
3
3
  import classNames from 'classnames';
4
4
  import Heading, { Levels } from '../heading/heading';
@@ -6,7 +6,6 @@ import Button from '../button/button';
6
6
  import { Size } from '../input/input';
7
7
  import getUID from '../global/get-uid';
8
8
  import Shortcuts from '../shortcuts/shortcuts';
9
- import useEventCallback from '../global/use-event-callback';
10
9
  import inputStyles from '../input/input.css';
11
10
  import styles from './editable-heading.css';
12
11
  export { Levels };
@@ -46,36 +45,30 @@ export const EditableHeading = (props) => {
46
45
  });
47
46
  const headingClasses = classNames(styles.heading, headingClassName, size !== Size.AUTO && styles[`size${size}`]);
48
47
  const inputClasses = classNames('ring-js-shortcuts', styles.input, styles.textarea, { [styles.textareaNotOverflow]: !isOverflow }, size !== Size.AUTO && inputStyles[`size${size}`], level !== Levels.H4 && styles[`level${level}`], inputClassName);
49
- const stretch = (el) => {
50
- if (!el || !el.style) {
51
- return;
52
- }
53
- el.style.height = '0';
54
- const { paddingTop, paddingBottom } = window.getComputedStyle(el);
55
- el.style.height = `${el.scrollHeight - parseFloat(paddingTop) - parseFloat(paddingBottom)}px`;
56
- };
57
- const checkValue = (el) => {
48
+ const checkValue = React.useCallback((el) => {
58
49
  if (multiline && el && el.scrollHeight >= el.clientHeight) {
59
- stretch(el);
50
+ el.style.height = '0';
51
+ const { paddingTop, paddingBottom } = window.getComputedStyle(el);
52
+ el.style.height = `${el.scrollHeight - parseFloat(paddingTop) - parseFloat(paddingBottom)}px`;
60
53
  }
61
- };
62
- const checkOverflow = (el) => {
54
+ }, [multiline]);
55
+ const checkOverflow = React.useCallback((el) => {
63
56
  const scrollHeight = el.scrollHeight || 0;
64
57
  const clientHeight = el.clientHeight || 0;
65
58
  const scrollTop = el.scrollTop || 0;
66
59
  setIsScrolledToBottom(scrollHeight - clientHeight <= scrollTop);
67
60
  setIsOverflow(scrollHeight > clientHeight);
68
- };
61
+ }, []);
69
62
  const onHeadingMouseDown = () => {
70
63
  setIsMouseDown(true);
71
64
  };
72
- const onMouseMove = useEventCallback(() => {
65
+ const onMouseMove = useEffectEvent(() => {
73
66
  if (!isMouseDown) {
74
67
  return;
75
68
  }
76
69
  setIsInSelectionMode(true);
77
70
  });
78
- const onMouseUp = useEventCallback(() => {
71
+ const onMouseUp = useEffectEvent(() => {
79
72
  if (isMouseDown && !isInSelectionMode && !disabled) {
80
73
  onEdit();
81
74
  }
@@ -101,6 +94,12 @@ export const EditableHeading = (props) => {
101
94
  setIsInFocus(false);
102
95
  onBlur?.(e);
103
96
  };
97
+ useLayoutEffect(() => {
98
+ if (multiline && isEditing && textAreaRef.current) {
99
+ checkValue(textAreaRef.current);
100
+ checkOverflow(textAreaRef.current);
101
+ }
102
+ }, [checkOverflow, checkValue, isEditing, multiline]);
104
103
  useEffect(() => {
105
104
  window.addEventListener('mousemove', onMouseMove);
106
105
  window.addEventListener('mouseup', onMouseUp);
@@ -108,7 +107,7 @@ export const EditableHeading = (props) => {
108
107
  window.removeEventListener('mousemove', onMouseMove);
109
108
  window.removeEventListener('mouseup', onMouseUp);
110
109
  };
111
- }, [onMouseMove, onMouseUp]);
110
+ }, []);
112
111
  return (<>
113
112
  <div className={classes}>
114
113
  {!disabled && isEditing ? (<>
@@ -140,4 +139,4 @@ export const EditableHeading = (props) => {
140
139
  {isEditing && error && (<div className={classNames(styles.errorText, size !== Size.AUTO && inputStyles[`size${size}`])}>{error}</div>)}
141
140
  </>);
142
141
  };
143
- export default React.memo(EditableHeading);
142
+ export default EditableHeading;
@@ -1,2 +1,3 @@
1
1
  import { type Ref } from 'react';
2
- export declare function createComposedRef<T>(): import("memoize-one").MemoizedFn<(...refs: (Ref<T> | undefined)[]) => (value: T | null) => void>;
2
+ export declare function createComposedRef<T>(): import("memoize-one").MemoizedFn<(...refs: (Ref<T> | undefined)[]) => (value: T | null) => () => void>;
3
+ export declare function useComposedRef<T>(...refs: (Ref<T> | undefined)[]): Ref<T>;
@@ -1,14 +1,30 @@
1
+ import { useMemo } from 'react';
1
2
  import memoizeOne from 'memoize-one';
2
3
  function composeRefs(...refs) {
3
- return (value) => refs.forEach(ref => {
4
- if (typeof ref === 'function') {
5
- ref(value);
6
- }
7
- else if (ref) {
8
- ref.current = value;
9
- }
10
- });
4
+ return (value) => {
5
+ const cleanups = [];
6
+ refs.forEach(ref => {
7
+ if (typeof ref === 'function') {
8
+ const cleanup = ref(value);
9
+ if (typeof cleanup === 'function') {
10
+ cleanups.push(cleanup);
11
+ }
12
+ }
13
+ else if (ref) {
14
+ ref.current = value;
15
+ }
16
+ });
17
+ return () => cleanups.forEach(cleanup => cleanup());
18
+ };
11
19
  }
12
20
  export function createComposedRef() {
13
21
  return memoizeOne((composeRefs));
14
22
  }
23
+ export function useComposedRef(...refs) {
24
+ /**
25
+ * The React Compiler doesn't allow non-literal arrays in useMemo
26
+ * dependency lists, so we still use memoizeOne under the hood.
27
+ */
28
+ const composer = useMemo(() => createComposedRef(), []);
29
+ return composer(...refs);
30
+ }
@@ -1,16 +1,16 @@
1
- import { createContext, memo, useContext, useEffect, useState } from 'react';
1
+ import { createContext, memo, use, useEffect, useState } from 'react';
2
2
  export default function createStatefulContext(initialValue, name = '') {
3
3
  const ValueContext = createContext(initialValue);
4
4
  const UpdateContext = createContext(() => { });
5
5
  function Provider({ children }) {
6
6
  const [value, update] = useState(initialValue);
7
- return (<ValueContext.Provider value={value}>
8
- <UpdateContext.Provider value={update}>{children}</UpdateContext.Provider>
9
- </ValueContext.Provider>);
7
+ return (<ValueContext value={value}>
8
+ <UpdateContext value={update}>{children}</UpdateContext>
9
+ </ValueContext>);
10
10
  }
11
11
  Provider.displayName = `${name}Provider`;
12
12
  function useUpdate(value, skipUpdate) {
13
- const update = useContext(UpdateContext);
13
+ const update = use(UpdateContext);
14
14
  useEffect(() => {
15
15
  if (!skipUpdate) {
16
16
  update(value);
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Focuses an element, temporarily adding `tabindex="0"` if necessary.
3
+ *
4
+ * If the element does not already have a `tabindex` attribute, one is
5
+ * added before focusing and automatically removed when the element loses
6
+ * focus.
7
+ *
8
+ * This is useful when implementing roving tabindex patterns on elements
9
+ * that are not normally focusable.
10
+ */
11
+ export declare function focusWithTemporaryTabIndex(element: HTMLElement): void;
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Focuses an element, temporarily adding `tabindex="0"` if necessary.
3
+ *
4
+ * If the element does not already have a `tabindex` attribute, one is
5
+ * added before focusing and automatically removed when the element loses
6
+ * focus.
7
+ *
8
+ * This is useful when implementing roving tabindex patterns on elements
9
+ * that are not normally focusable.
10
+ */
11
+ export function focusWithTemporaryTabIndex(element) {
12
+ if (!element.hasAttribute('tabindex')) {
13
+ element.tabIndex = 0;
14
+ function onBlur() {
15
+ element.removeAttribute('tabindex');
16
+ element.removeEventListener('blur', onBlur);
17
+ }
18
+ element.addEventListener('blur', onBlur);
19
+ }
20
+ element.focus();
21
+ }
@@ -0,0 +1,5 @@
1
+ declare const getViewportSize: () => {
2
+ width: number;
3
+ height: number;
4
+ };
5
+ export default getViewportSize;
@@ -0,0 +1,5 @@
1
+ const getViewportSize = () => ({
2
+ width: document.documentElement.clientWidth || window.innerWidth,
3
+ height: document.documentElement.clientHeight || window.innerHeight,
4
+ });
5
+ export default getViewportSize;
@@ -1,26 +1,46 @@
1
1
  import { type RefObject } from 'react';
2
2
  /**
3
+ * Provides access to a shared `IntersectionObserver` instance
4
+ * via the {@link IntersectionObserverContext} context.
5
+ *
6
+ * @see IntersectionObserverContext
7
+ */
8
+ export interface IntersectionObserverHandle {
9
+ /**
10
+ * Starts observing an element.
11
+ *
12
+ * Returns a cleanup function that stops observing it.
13
+ */
14
+ observe(element: Element, isIntersecting: (isIntersecting: boolean) => void): () => void;
15
+ }
16
+ /**
17
+ * Multiple components can share a single `IntersectionObserver` instance through this context.
18
+ *
3
19
  * Usage:
4
20
  *
5
21
  * ```tsx
6
- * <IntersectionObserverContext.Provider value={useIntersectionObserverHandle()}>
22
+ * <IntersectionObserverContext value={useIntersectionObserverHandle()}>
7
23
  * <YourComponent />
8
- * </IntersectionObserverContext.Provider>
24
+ * </IntersectionObserverContext>
9
25
  *
10
26
  * function YourComponent() {
11
27
  * // Contains the current isIntersecting value
12
28
  * const isIntersecting = useIsIntersecting(elementRef);
13
- * // Or, to get updates instead:
14
- * useIsIntersectingListener(elementRef, newIsIntersecting => {
15
- * // ...
29
+ *
30
+ * // Or, to manually work with the IntersectionObserverHandle:
31
+ * const handle = use(IntersectionObserverContext);
32
+ * useEffect(() => {
33
+ * return handle.observe(elementRef.current, isIntersecting => { ... })
16
34
  * })
17
35
  * }
18
36
  * ```
19
37
  */
20
- export declare const IntersectionObserverContext: import("react").Context<IntersectionObserverHandle | null>;
21
- export declare function useIntersectionObserverHandle(rootRef?: RefObject<HTMLElement | null>, rootMargin?: number, scrollMargin?: number): IntersectionObserverHandle | null;
22
- export interface IntersectionObserverHandle {
23
- observe(element: Element, setIsIntersecting: (isIntersecting: boolean) => void): () => void;
24
- }
38
+ export declare const IntersectionObserverContext: import("react").Context<IntersectionObserverHandle>;
39
+ /**
40
+ * Creates an IntersectionObserverHandle suitable for {@link IntersectionObserverContext}.
41
+ */
42
+ export declare function useIntersectionObserverHandle(rootRef?: RefObject<HTMLElement | null>, rootMargin?: number, scrollMargin?: number): IntersectionObserverHandle;
43
+ /**
44
+ * Returns whether the referenced element is currently intersecting.
45
+ */
25
46
  export declare function useIsIntersecting(elementRef: RefObject<Element | null>): boolean;
26
- export declare function useIsIntersectingListener(elementRef: RefObject<Element | null>, onChange: (isIntersecting: boolean) => void): void;