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

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 (199) 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 +2 -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-service/confirm-service.js +2 -2
  39. package/components/data-list/data-list.d.ts +1 -1
  40. package/components/data-list/data-list.mock.d.ts +1 -1
  41. package/components/data-list/item.d.ts +1 -1
  42. package/components/data-list/selection.d.ts +1 -1
  43. package/components/data-list/selection.js +1 -1
  44. package/components/date-picker/consts.d.ts +1 -0
  45. package/components/date-picker/date-input.js +6 -2
  46. package/components/date-picker/months.js +2 -2
  47. package/components/date-picker/use-scroll-behavior.js +5 -6
  48. package/components/date-picker/years.js +2 -2
  49. package/components/dialog/dialog.d.ts +2 -2
  50. package/components/dialog/dialog.js +2 -2
  51. package/components/dropdown-menu/dropdown-menu.d.ts +4 -4
  52. package/components/dropdown-menu/dropdown-menu.js +4 -4
  53. package/components/editable-heading/editable-heading.d.ts +1 -2
  54. package/components/editable-heading/editable-heading.js +18 -19
  55. package/components/global/compose-refs.d.ts +2 -1
  56. package/components/global/compose-refs.js +24 -8
  57. package/components/global/create-stateful-context.js +5 -5
  58. package/components/global/focus-with-temporary-tabindex.d.ts +11 -0
  59. package/components/global/focus-with-temporary-tabindex.js +21 -0
  60. package/components/global/intersection-observer-context.d.ts +31 -11
  61. package/components/global/intersection-observer-context.js +47 -28
  62. package/components/global/is-within-interactive-element.d.ts +6 -0
  63. package/components/global/is-within-interactive-element.js +26 -0
  64. package/components/global/is-within-navigable-element.d.ts +6 -0
  65. package/components/global/is-within-navigable-element.js +27 -0
  66. package/components/global/parse-css-duration.d.ts +5 -0
  67. package/components/global/parse-css-duration.js +13 -0
  68. package/components/global/rerender-hoc.d.ts +4 -2
  69. package/components/global/rerender-hoc.js +4 -4
  70. package/components/global/schedule-with-cleanup.d.ts +12 -0
  71. package/components/global/schedule-with-cleanup.js +34 -0
  72. package/components/{legacy-table/selection.d.ts → global/table-selection.d.ts} +14 -14
  73. package/components/{legacy-table/selection.js → global/table-selection.js} +2 -2
  74. package/components/global/theme.d.ts +4 -3
  75. package/components/global/theme.js +8 -8
  76. package/components/global/variables.css +18 -28
  77. package/components/global/variables.interface.d.ts +0 -4
  78. package/components/global/variables_dark.css +18 -25
  79. package/components/header/header.css +3 -3
  80. package/components/http/http.d.ts +2 -2
  81. package/components/http/http.js +2 -2
  82. package/components/i18n/i18n-context.js +1 -1
  83. package/components/i18n/i18n.d.ts +1 -0
  84. package/components/i18n/messages.json +1 -0
  85. package/components/icon/icon.css +0 -27
  86. package/components/icon/icon.d.ts +0 -1
  87. package/components/icon/icon.js +2 -4
  88. package/components/input/input.d.ts +1 -1
  89. package/components/island/adaptive-island-hoc.js +4 -4
  90. package/components/island/content.d.ts +7 -2
  91. package/components/island/content.js +5 -5
  92. package/components/legacy-table/cell.js +1 -1
  93. package/components/legacy-table/header-cell.js +1 -1
  94. package/components/legacy-table/header.js +1 -1
  95. package/components/legacy-table/multitable.d.ts +1 -1
  96. package/components/legacy-table/row.d.ts +1 -1
  97. package/components/legacy-table/row.js +1 -1
  98. package/components/legacy-table/selection-adapter.d.ts +3 -3
  99. package/components/legacy-table/selection-shortcuts-hoc.d.ts +5 -5
  100. package/components/legacy-table/simple-table.d.ts +2 -2
  101. package/components/legacy-table/simple-table.js +3 -3
  102. package/components/legacy-table/smart-table.d.ts +5 -5
  103. package/components/legacy-table/smart-table.js +3 -3
  104. package/components/legacy-table/table.js +1 -1
  105. package/components/list/consts.d.ts +0 -1
  106. package/components/list/consts.js +0 -1
  107. package/components/list/list.d.ts +0 -4
  108. package/components/login-dialog/service.js +2 -2
  109. package/components/popup/popup.d.ts +2 -0
  110. package/components/popup/popup.js +2 -2
  111. package/components/popup/popup.target.d.ts +3 -2
  112. package/components/popup/popup.target.js +4 -6
  113. package/components/query-assist/query-assist.d.ts +3 -1
  114. package/components/query-assist/query-assist.js +2 -2
  115. package/components/radio/radio-item.d.ts +3 -3
  116. package/components/radio/radio-item.js +3 -4
  117. package/components/radio/radio.d.ts +2 -2
  118. package/components/radio/radio.js +1 -1
  119. package/components/select/select-popup.d.ts +3 -2
  120. package/components/select/select-popup.js +3 -3
  121. package/components/select/select.d.ts +6 -2
  122. package/components/select/select.js +2 -4
  123. package/components/selection-toolbar/selection-toolbar.css +86 -0
  124. package/components/selection-toolbar/selection-toolbar.d.ts +11 -0
  125. package/components/selection-toolbar/selection-toolbar.js +23 -0
  126. package/components/slider/slider.js +4 -5
  127. package/components/tab-trap/tab-trap.d.ts +3 -3
  128. package/components/tab-trap/tab-trap.js +3 -5
  129. package/components/table/default-item-renderer.d.ts +41 -11
  130. package/components/table/default-item-renderer.js +60 -47
  131. package/components/table/internal/reorder-animation-context.d.ts +21 -0
  132. package/components/table/internal/reorder-animation-context.js +60 -0
  133. package/components/table/internal/reorder-handle.d.ts +9 -0
  134. package/components/table/internal/reorder-handle.js +334 -0
  135. package/components/table/internal/reorder-layout-context.d.ts +16 -0
  136. package/components/table/internal/reorder-layout-context.js +69 -0
  137. package/components/table/internal/table-header.d.ts +1 -0
  138. package/components/table/internal/table-header.js +143 -0
  139. package/components/table/internal/virtualization.d.ts +41 -0
  140. package/components/table/{table-virtualize.js → internal/virtualization.js} +82 -46
  141. package/components/table/item-virtualization.d.ts +37 -0
  142. package/components/table/item-virtualization.js +26 -0
  143. package/components/table/reorder-animation.d.ts +37 -0
  144. package/components/table/reorder-animation.js +11 -0
  145. package/components/table/reorder-item-layout.d.ts +32 -0
  146. package/components/table/reorder-item-layout.js +23 -0
  147. package/components/table/table-const.d.ts +8 -6
  148. package/components/table/table-const.js +7 -5
  149. package/components/table/table-primitives.d.ts +64 -0
  150. package/components/table/table-primitives.js +34 -0
  151. package/components/table/table-props.d.ts +304 -0
  152. package/components/table/table-props.js +1 -0
  153. package/components/table/table.css +179 -67
  154. package/components/table/table.d.ts +231 -224
  155. package/components/table/table.js +327 -2
  156. package/components/tag/tag.css +3 -3
  157. package/components/tags-input/tags-input.d.ts +3 -1
  158. package/components/tooltip/tooltip.d.ts +2 -1
  159. package/components/tooltip/tooltip.js +6 -5
  160. package/components/upload/upload.d.ts +4 -3
  161. package/components/upload/upload.js +3 -7
  162. package/components/user-agreement/service.js +2 -2
  163. package/components/user-card/smart-user-card-tooltip.d.ts +1 -1
  164. package/components/util-stories.d.ts +26 -0
  165. package/components/util-stories.js +61 -0
  166. package/package.json +44 -47
  167. package/components/content-layout/content-layout.css +0 -109
  168. package/components/content-layout/content-layout.d.ts +0 -20
  169. package/components/content-layout/content-layout.js +0 -40
  170. package/components/content-layout/sidebar.d.ts +0 -26
  171. package/components/content-layout/sidebar.js +0 -71
  172. package/components/date-picker/use-intersection-observer.d.ts +0 -6
  173. package/components/date-picker/use-intersection-observer.js +0 -48
  174. package/components/expand/collapsible-group.d.ts +0 -16
  175. package/components/global/composeRefs.d.ts +0 -6
  176. package/components/global/composeRefs.js +0 -7
  177. package/components/global/use-event-callback.d.ts +0 -1
  178. package/components/global/use-event-callback.js +0 -15
  179. package/components/grid/col.d.ts +0 -16
  180. package/components/grid/col.js +0 -35
  181. package/components/grid/grid.css +0 -920
  182. package/components/grid/grid.d.ts +0 -13
  183. package/components/grid/grid.js +0 -19
  184. package/components/grid/row.d.ts +0 -21
  185. package/components/grid/row.js +0 -41
  186. package/components/old-browsers-message/__mocks__/old-browsers-message.js +0 -1
  187. package/components/old-browsers-message/old-browsers-message-stop.d.ts +0 -1
  188. package/components/old-browsers-message/old-browsers-message-stop.js +0 -2
  189. package/components/old-browsers-message/old-browsers-message.css +0 -26
  190. package/components/old-browsers-message/old-browsers-message.d.ts +0 -5
  191. package/components/old-browsers-message/old-browsers-message.js +0 -86
  192. package/components/old-browsers-message/white-list.d.ts +0 -2
  193. package/components/old-browsers-message/white-list.js +0 -24
  194. package/components/table/table-base.d.ts +0 -24
  195. package/components/table/table-base.js +0 -79
  196. package/components/table/table-component.d.ts +0 -53
  197. package/components/table/table-component.js +0 -101
  198. package/components/table/table-virtualize.d.ts +0 -32
  199. /package/components/legacy-table/{table.css → legacy-table.css} +0 -0
@@ -34,6 +34,7 @@ export interface DateInputTranslations {
34
34
  addSecondDate?: string;
35
35
  addTime?: string;
36
36
  selectName?: string;
37
+ selectDate?: string;
37
38
  }
38
39
  export interface DateSpecificPopupProps {
39
40
  withTime?: false | undefined;
@@ -47,7 +47,7 @@ export default class DateInput extends React.PureComponent {
47
47
  };
48
48
  render() {
49
49
  const { active, divider, text, time, name, hoverDate, date, displayFormat, translations, onActivate, onClear, fromPlaceholder, toPlaceholder, timePlaceholder, locale, } = this.props;
50
- const { translate } = this.context;
50
+ const { messages, translate } = this.context;
51
51
  let displayText = '';
52
52
  if (active && hoverDate) {
53
53
  displayText = displayFormat(hoverDate, locale);
@@ -70,7 +70,11 @@ export default class DateInput extends React.PureComponent {
70
70
  case 'time':
71
71
  return timePlaceholder || (translations?.addTime ?? translate('addTime'));
72
72
  default:
73
- return (translations?.selectName ?? translate('selectName'))
73
+ return (translations?.selectDate ??
74
+ translations?.selectName ??
75
+ messages.selectDate ??
76
+ messages.selectName ??
77
+ translate('selectDate'))
74
78
  .replace('%name%', name)
75
79
  .replace('{{name}}', name);
76
80
  }
@@ -29,9 +29,9 @@ const intersectionObserverRootMargin = units.cellSize * 2;
29
29
  export default function Months(props) {
30
30
  const { scrollDate, setScrollDate, locale } = props;
31
31
  const { containerRef, items } = useScrollBehavior(scrollDate, setScrollDate, locale, 'monthsScroll', scrollArith, scheduleScroll);
32
- return (<IntersectionObserverContext.Provider value={useIntersectionObserverHandle(containerRef, intersectionObserverRootMargin)}>
32
+ return (<IntersectionObserverContext value={useIntersectionObserverHandle(containerRef, intersectionObserverRootMargin)}>
33
33
  <div className={styles.months} ref={containerRef} data-test='ring-date-popup--months'>
34
34
  {items.map(month => (<Month {...props} month={month} key={+month}/>))}
35
35
  </div>
36
- </IntersectionObserverContext.Provider>);
36
+ </IntersectionObserverContext>);
37
37
  }
@@ -1,11 +1,10 @@
1
- import { useEffect, useLayoutEffect, useRef, useState } from 'react';
1
+ import { useEffect, useEffectEvent, useLayoutEffect, useRef, useState } from 'react';
2
2
  import units, { isSafariOnIPhone, scrollerReRenderDelayIPhone } from './consts';
3
- import useEventCallback from '../global/use-event-callback';
4
3
  export function useScrollBehavior(scrollDate, onContainerScroll, locale, selfScrollDateSource, arith, scheduleScroll) {
5
4
  const [items, setItems] = useState(() => arith.getItems(scrollDate.date));
6
5
  const [scrollTop, setScrollTop] = useState(() => arith.getScrollTop(items, scrollDate.date, locale));
7
6
  const containerRef = useRef(null);
8
- const syncSelfState = useEventCallback((newScrollDate) => {
7
+ const syncSelfState = useEffectEvent((newScrollDate) => {
9
8
  const newScrollTopOnExistingItems = arith.getScrollTop(items, newScrollDate, locale);
10
9
  if (isNearEdge(newScrollTopOnExistingItems, containerRef.current)) {
11
10
  const { newItems, newScrollTop } = arith.getItemsAndScrollTop(newScrollDate, locale);
@@ -28,7 +27,7 @@ export function useScrollBehavior(scrollDate, onContainerScroll, locale, selfScr
28
27
  if (scrollDate.source === selfScrollDateSource)
29
28
  return;
30
29
  syncSelfState(scrollDate.date);
31
- }, [scrollDate, selfScrollDateSource, syncSelfState]);
30
+ }, [scrollDate, selfScrollDateSource]);
32
31
  const ignoreNextScrollEventRef = useRef(true);
33
32
  useLayoutEffect(function setContainerScrollFromState() {
34
33
  const container = containerRef.current;
@@ -38,7 +37,7 @@ export function useScrollBehavior(scrollDate, onContainerScroll, locale, selfScr
38
37
  container.scrollTop = scrollTop;
39
38
  }, [items, scrollTop]);
40
39
  const updateStateTimerRef = useRef(null);
41
- const handleScroll = useEventCallback(() => {
40
+ const handleScroll = useEffectEvent(() => {
42
41
  scheduleScroll(() => {
43
42
  if (updateStateTimerRef.current != null) {
44
43
  window.clearTimeout(updateStateTimerRef.current);
@@ -83,7 +82,7 @@ export function useScrollBehavior(scrollDate, onContainerScroll, locale, selfScr
83
82
  updateStateTimerRef.current = null;
84
83
  }
85
84
  };
86
- }, [handleScroll]);
85
+ }, []);
87
86
  return { containerRef, items };
88
87
  }
89
88
  function isNearEdge(scrollTop, container) {
@@ -87,11 +87,11 @@ export default function Years({ scrollDate, setScrollDate }) {
87
87
  }, yearsAnimationDuration);
88
88
  }, [localScrollDate.date, setScrollDate]);
89
89
  const { containerRef, items } = useScrollBehavior(localScrollDate, syncCalendarScrollDate, undefined, 'yearsScroll', scrollArith, scheduleScroll);
90
- return (<IntersectionObserverContext.Provider value={useIntersectionObserverHandle(containerRef, intersectionObserverRootMargin)}>
90
+ return (<IntersectionObserverContext value={useIntersectionObserverHandle(containerRef, intersectionObserverRootMargin)}>
91
91
  <div className={styles.years} ref={containerRef} data-test='ring-date-popup--years'>
92
92
  {items.map(year => (<Year year={year} scrollDate={localScrollDate.date} handleYearClick={handleYearClick} key={+year}/>))}
93
93
  </div>
94
- </IntersectionObserverContext.Provider>);
94
+ </IntersectionObserverContext>);
95
95
  }
96
96
  function Year({ year, scrollDate, handleYearClick, }) {
97
97
  const buttonRef = useRef(null);
@@ -1,6 +1,6 @@
1
1
  import { PureComponent } from 'react';
2
2
  import * as React from 'react';
3
- import TabTrap, { type TabTrapProps } from '../tab-trap/tab-trap';
3
+ import { type TabTrapObject, type TabTrapProps } from '../tab-trap/tab-trap';
4
4
  import type { ShortcutsScopeOptions } from '../shortcuts/core';
5
5
  export interface DialogProps extends Partial<TabTrapProps> {
6
6
  show: boolean;
@@ -43,7 +43,7 @@ export default class Dialog extends PureComponent<DialogProps> {
43
43
  esc: (event: KeyboardEvent) => void;
44
44
  };
45
45
  dialog?: HTMLElement | null;
46
- dialogRef: (tabTrap: TabTrap | null) => void;
46
+ dialogRef: (tabTrapObj: TabTrapObject | null) => void;
47
47
  nativeDialog: React.RefObject<HTMLDialogElement | null>;
48
48
  render(): false | React.JSX.Element;
49
49
  }
@@ -104,8 +104,8 @@ export default class Dialog extends PureComponent {
104
104
  };
105
105
  };
106
106
  dialog;
107
- dialogRef = (tabTrap) => {
108
- this.dialog = tabTrap && tabTrap.node;
107
+ dialogRef = (tabTrapObj) => {
108
+ this.dialog = tabTrapObj && tabTrapObj.node;
109
109
  };
110
110
  nativeDialog = createRef();
111
111
  render() {
@@ -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
+ }
@@ -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;
@@ -1,32 +1,45 @@
1
- import { createContext, useContext, useEffect, useState } from 'react';
1
+ import { createContext, use, useEffect, useState } from 'react';
2
2
  /**
3
+ * @internal
4
+ */
5
+ const noopIntersectionObserverHandle = {
6
+ observe: () => () => { },
7
+ };
8
+ /**
9
+ * Multiple components can share a single `IntersectionObserver` instance through this context.
10
+ *
3
11
  * Usage:
4
12
  *
5
13
  * ```tsx
6
- * <IntersectionObserverContext.Provider value={useIntersectionObserverHandle()}>
14
+ * <IntersectionObserverContext value={useIntersectionObserverHandle()}>
7
15
  * <YourComponent />
8
- * </IntersectionObserverContext.Provider>
16
+ * </IntersectionObserverContext>
9
17
  *
10
18
  * function YourComponent() {
11
19
  * // Contains the current isIntersecting value
12
20
  * const isIntersecting = useIsIntersecting(elementRef);
13
- * // Or, to get updates instead:
14
- * useIsIntersectingListener(elementRef, newIsIntersecting => {
15
- * // ...
21
+ *
22
+ * // Or, to manually work with the IntersectionObserverHandle:
23
+ * const handle = use(IntersectionObserverContext);
24
+ * useEffect(() => {
25
+ * return handle.observe(elementRef.current, isIntersecting => { ... })
16
26
  * })
17
27
  * }
18
28
  * ```
19
29
  */
20
- export const IntersectionObserverContext = createContext(null);
30
+ export const IntersectionObserverContext = createContext(noopIntersectionObserverHandle);
31
+ /**
32
+ * Creates an IntersectionObserverHandle suitable for {@link IntersectionObserverContext}.
33
+ */
21
34
  export function useIntersectionObserverHandle(rootRef, rootMargin, scrollMargin) {
22
- const [handle, setHandle] = useState(null);
35
+ const [handle, setHandle] = useState(noopIntersectionObserverHandle);
23
36
  useEffect(() => {
24
37
  const root = rootRef?.current;
25
- const elementToOnChange = new Map();
38
+ const callbacksByElement = new Map();
26
39
  const observer = new IntersectionObserver(entries => {
27
40
  for (const entry of entries) {
28
- const onChange = elementToOnChange.get(entry.target);
29
- onChange?.(entry.isIntersecting);
41
+ const callbacks = callbacksByElement.get(entry.target);
42
+ callbacks?.forEach(cb => cb(entry.isIntersecting));
30
43
  }
31
44
  }, {
32
45
  root,
@@ -35,38 +48,44 @@ export function useIntersectionObserverHandle(rootRef, rootMargin, scrollMargin)
35
48
  });
36
49
  setHandle({
37
50
  observe(element, onChange) {
38
- elementToOnChange.set(element, onChange);
39
- observer.observe(element);
51
+ if (!callbacksByElement.has(element)) {
52
+ callbacksByElement.set(element, []);
53
+ observer.observe(element);
54
+ }
55
+ callbacksByElement.get(element).push(onChange);
40
56
  return () => {
41
- elementToOnChange.delete(element);
42
- observer.unobserve(element);
57
+ const callbacks = callbacksByElement.get(element);
58
+ if (!callbacks)
59
+ return;
60
+ const index = callbacks.indexOf(onChange);
61
+ if (index !== -1) {
62
+ callbacks.splice(index, 1);
63
+ }
64
+ if (!callbacks.length) {
65
+ callbacksByElement.delete(element);
66
+ observer.unobserve(element);
67
+ }
43
68
  };
44
69
  },
45
70
  });
46
71
  return () => {
47
72
  observer.disconnect();
48
- setHandle(null);
73
+ setHandle(noopIntersectionObserverHandle);
49
74
  };
50
75
  }, [rootRef, rootMargin, scrollMargin]);
51
76
  return handle;
52
77
  }
78
+ /**
79
+ * Returns whether the referenced element is currently intersecting.
80
+ */
53
81
  export function useIsIntersecting(elementRef) {
54
- const handle = useContext(IntersectionObserverContext);
82
+ const handle = use(IntersectionObserverContext);
55
83
  const [isIntersecting, setIsIntersecting] = useState(false);
56
84
  useEffect(() => {
57
85
  const element = elementRef.current;
58
- if (!element || !handle)
86
+ if (!element)
59
87
  return;
60
88
  return handle.observe(element, setIsIntersecting);
61
- }, [handle, elementRef, setIsIntersecting]);
89
+ }, [handle, elementRef]);
62
90
  return isIntersecting;
63
91
  }
64
- export function useIsIntersectingListener(elementRef, onChange) {
65
- const handle = useContext(IntersectionObserverContext);
66
- useEffect(() => {
67
- const element = elementRef.current;
68
- if (!element || !handle)
69
- return;
70
- return handle.observe(element, onChange);
71
- }, [handle, elementRef, onChange]);
72
- }
@@ -0,0 +1,6 @@
1
+ /**
2
+ * If this function returns `false`, the event may be interpreted as a click
3
+ * or tap on a "empty space" rather than on an interactive element such as
4
+ * a button or a link.
5
+ */
6
+ export declare function isWithinInteractiveElement(target: EventTarget | null): boolean;
@@ -0,0 +1,26 @@
1
+ const interactiveSelector = [
2
+ 'a',
3
+ 'button',
4
+ 'details',
5
+ 'input',
6
+ 'label',
7
+ 'option',
8
+ 'select',
9
+ 'summary',
10
+ 'textarea',
11
+ '[contenteditable]',
12
+ '[role="button"]',
13
+ '[role="checkbox"]',
14
+ '[role="link"]',
15
+ '[role="radio"]',
16
+ '[role="switch"]',
17
+ '[role="tab"]',
18
+ ].join();
19
+ /**
20
+ * If this function returns `false`, the event may be interpreted as a click
21
+ * or tap on a "empty space" rather than on an interactive element such as
22
+ * a button or a link.
23
+ */
24
+ export function isWithinInteractiveElement(target) {
25
+ return target instanceof Element && target.closest(interactiveSelector) != null;
26
+ }
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Returns whether the event target is within an element that is expected to
3
+ * handle keyboard navigation itself, so container-level keyboard shortcuts
4
+ * should generally be ignored.
5
+ */
6
+ export declare function isWithinNavigableElement(target: EventTarget | null): boolean;
@@ -0,0 +1,27 @@
1
+ const navigableSelector = [
2
+ 'input:not([type="button"]):not([type="checkbox"]):not([type="color"]):not([type="file"]):not([type="hidden"]):not([type="image"]):not([type="reset"]):not([type="submit"])',
3
+ 'textarea',
4
+ 'select',
5
+ '[contenteditable]',
6
+ '[role="combobox"]',
7
+ '[role="grid"]',
8
+ '[role="listbox"]',
9
+ '[role="menu"]',
10
+ '[role="menubar"]',
11
+ '[role="radiogroup"]',
12
+ '[role="searchbox"]',
13
+ '[role="slider"]',
14
+ '[role="spinbutton"]',
15
+ '[role="tablist"]',
16
+ '[role="textbox"]',
17
+ '[role="tree"]',
18
+ '[role="treegrid"]',
19
+ ].join();
20
+ /**
21
+ * Returns whether the event target is within an element that is expected to
22
+ * handle keyboard navigation itself, so container-level keyboard shortcuts
23
+ * should generally be ignored.
24
+ */
25
+ export function isWithinNavigableElement(target) {
26
+ return target instanceof Element && target.closest(navigableSelector) != null;
27
+ }
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Parses a CSS duration string (e.g., "500ms", "2s") and returns the duration in milliseconds.
3
+ * Returns the `defaultVal` if the input string is not a valid CSS duration.
4
+ */
5
+ export declare function parseCssDuration(duration: string, defaultVal?: number): number;