@tcn/ui 0.11.0 → 0.12.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (102) hide show
  1. package/dist/feedback/index.d.ts +1 -0
  2. package/dist/feedback/index.d.ts.map +1 -1
  3. package/dist/feedback/index.js +6 -4
  4. package/dist/feedback/index.js.map +1 -1
  5. package/dist/feedback/progress/progress.d.ts +7 -0
  6. package/dist/feedback/progress/progress.d.ts.map +1 -0
  7. package/dist/feedback/progress/progress.js +38 -0
  8. package/dist/feedback/progress/progress.js.map +1 -0
  9. package/dist/feedback/progress/progress_bar.d.ts +0 -1
  10. package/dist/feedback/progress/progress_bar.d.ts.map +1 -1
  11. package/dist/feedback/progress/progress_bar.js +6 -46
  12. package/dist/feedback/progress/progress_bar.js.map +1 -1
  13. package/dist/form/field/common/status_input/status_input.js +4 -3
  14. package/dist/form/field/common/status_input/status_input.js.map +1 -1
  15. package/dist/inputs/suggestions/suggestion_list.d.ts.map +1 -1
  16. package/dist/inputs/suggestions/suggestion_list.js +145 -127
  17. package/dist/inputs/suggestions/suggestion_list.js.map +1 -1
  18. package/dist/overlay/frame/frame.d.ts.map +1 -1
  19. package/dist/overlay/frame/frame.js +65 -65
  20. package/dist/overlay/frame/frame.js.map +1 -1
  21. package/dist/progress_bar-CPP0Jyv-.js +38 -0
  22. package/dist/progress_bar-CPP0Jyv-.js.map +1 -0
  23. package/dist/progress_bar.css +1 -1
  24. package/dist/stacks/box/bottom_resize_handle.d.ts +2 -8
  25. package/dist/stacks/box/bottom_resize_handle.d.ts.map +1 -1
  26. package/dist/stacks/box/bottom_resize_handle.js.map +1 -1
  27. package/dist/stacks/box/box.d.ts +2 -2
  28. package/dist/stacks/box/box.d.ts.map +1 -1
  29. package/dist/stacks/box/box.js.map +1 -1
  30. package/dist/stacks/box/end_resize_handle.d.ts +2 -8
  31. package/dist/stacks/box/end_resize_handle.d.ts.map +1 -1
  32. package/dist/stacks/box/end_resize_handle.js.map +1 -1
  33. package/dist/stacks/box/left_resize_handle.d.ts +2 -8
  34. package/dist/stacks/box/left_resize_handle.d.ts.map +1 -1
  35. package/dist/stacks/box/left_resize_handle.js.map +1 -1
  36. package/dist/stacks/box/resize_handlers.d.ts +3 -2
  37. package/dist/stacks/box/resize_handlers.d.ts.map +1 -1
  38. package/dist/stacks/box/resize_handlers.js +36 -32
  39. package/dist/stacks/box/resize_handlers.js.map +1 -1
  40. package/dist/stacks/box/right_resize_handle.d.ts +2 -8
  41. package/dist/stacks/box/right_resize_handle.d.ts.map +1 -1
  42. package/dist/stacks/box/right_resize_handle.js.map +1 -1
  43. package/dist/stacks/box/start_resize_handle.d.ts +2 -8
  44. package/dist/stacks/box/start_resize_handle.d.ts.map +1 -1
  45. package/dist/stacks/box/start_resize_handle.js.map +1 -1
  46. package/dist/stacks/box/top_resize_handle.d.ts +2 -8
  47. package/dist/stacks/box/top_resize_handle.d.ts.map +1 -1
  48. package/dist/stacks/box/top_resize_handle.js.map +1 -1
  49. package/dist/stacks/box/types.d.ts +18 -0
  50. package/dist/stacks/box/types.d.ts.map +1 -0
  51. package/dist/stacks/h_collapsible_box.js +25 -25
  52. package/dist/stacks/h_collapsible_box.js.map +1 -1
  53. package/dist/stacks/index.d.ts +1 -0
  54. package/dist/stacks/index.d.ts.map +1 -1
  55. package/dist/stacks/v_collapsible_box.js +25 -25
  56. package/dist/stacks/v_collapsible_box.js.map +1 -1
  57. package/dist/surfaces/modal/modal.d.ts +3 -4
  58. package/dist/surfaces/modal/modal.d.ts.map +1 -1
  59. package/dist/surfaces/modal/modal.js +10 -8
  60. package/dist/surfaces/modal/modal.js.map +1 -1
  61. package/dist/surfaces/pop_confirm/pop_confirm.d.ts.map +1 -1
  62. package/dist/surfaces/pop_confirm/pop_confirm.js +18 -12
  63. package/dist/surfaces/pop_confirm/pop_confirm.js.map +1 -1
  64. package/dist/surfaces/tooltip/tooltip.d.ts.map +1 -1
  65. package/dist/surfaces/tooltip/tooltip.js +22 -20
  66. package/dist/surfaces/tooltip/tooltip.js.map +1 -1
  67. package/dist/surfaces/window/window.d.ts +3 -4
  68. package/dist/surfaces/window/window.d.ts.map +1 -1
  69. package/dist/surfaces/window/window.js +20 -18
  70. package/dist/surfaces/window/window.js.map +1 -1
  71. package/dist/themes/themes/ergo/ergo_theme.css +1 -1
  72. package/dist/themes/themes/ergo/ergo_theme.js +3 -1
  73. package/dist/themes/themes/ergo/ergo_theme.js.map +1 -1
  74. package/package.json +1 -1
  75. package/src/feedback/index.ts +1 -0
  76. package/src/feedback/progress/progress.module.css +5 -0
  77. package/src/feedback/progress/progress.stories.tsx +48 -0
  78. package/src/feedback/progress/progress.tsx +39 -0
  79. package/src/feedback/progress/progress_bar.module.css +4 -28
  80. package/src/feedback/progress/progress_bar.stories.tsx +1 -1
  81. package/src/feedback/progress/progress_bar.tsx +14 -26
  82. package/src/inputs/select/select.stories.tsx +23 -2
  83. package/src/inputs/suggestions/suggestion_list.tsx +58 -39
  84. package/src/overlay/frame/frame.tsx +10 -12
  85. package/src/stacks/box/bottom_resize_handle.tsx +2 -13
  86. package/src/stacks/box/box.tsx +4 -2
  87. package/src/stacks/box/end_resize_handle.tsx +3 -13
  88. package/src/stacks/box/left_resize_handle.tsx +3 -13
  89. package/src/stacks/box/resize_handlers.ts +22 -18
  90. package/src/stacks/box/right_resize_handle.tsx +2 -13
  91. package/src/stacks/box/start_resize_handle.tsx +3 -13
  92. package/src/stacks/box/top_resize_handle.tsx +3 -12
  93. package/src/stacks/box/types.ts +44 -0
  94. package/src/stacks/h_collapsible_box.tsx +2 -2
  95. package/src/stacks/index.ts +1 -0
  96. package/src/stacks/v_collapsible_box.tsx +2 -2
  97. package/src/surfaces/modal/modal.tsx +6 -4
  98. package/src/surfaces/pop_confirm/pop_confirm.tsx +8 -2
  99. package/src/surfaces/tooltip/tooltip.tsx +2 -1
  100. package/src/surfaces/window/window.stories.tsx +9 -1
  101. package/src/surfaces/window/window.tsx +6 -4
  102. package/src/themes/themes/ergo/ergo_theme.css +3 -1
@@ -0,0 +1,39 @@
1
+ import React, { HTMLAttributes } from 'react';
2
+ import { BodyText } from '../../typography/index.js';
3
+ import { HStack } from '../../stacks/h_stack.js';
4
+ import { Spacer } from '../../stacks/spacer.js';
5
+ import { VStack } from '../../stacks/v_stack.js';
6
+ import clsx from 'clsx';
7
+
8
+ import styles from './progress_bar.module.css';
9
+ import { ProgressBar } from './progress_bar.js';
10
+
11
+ export interface ProgressProps extends HTMLAttributes<HTMLDivElement> {
12
+ message: string;
13
+ value: number;
14
+ }
15
+
16
+ export const Progress = React.forwardRef(function Progress(
17
+ { message, value, ...props }: ProgressProps,
18
+ ref: React.Ref<HTMLDivElement>
19
+ ) {
20
+ const progressPercent = `${(value * 100).toFixed(0)}%`;
21
+
22
+ return (
23
+ <VStack
24
+ ref={ref}
25
+ className={clsx(styles['progress-container'], 'tcn-progress-container')}
26
+ {...props}
27
+ >
28
+ <HStack height="auto" vAlign="end">
29
+ <BodyText>{message}</BodyText>
30
+ <Spacer />
31
+ <Spacer width="8px" />
32
+ <BodyText size="sm" style={{ flexShrink: 0 }}>
33
+ {progressPercent}
34
+ </BodyText>
35
+ </HStack>
36
+ <ProgressBar value={value} />
37
+ </VStack>
38
+ );
39
+ });
@@ -1,4 +1,4 @@
1
- @system {
1
+ @layer tcn-system {
2
2
  :root {
3
3
  --progress-bar-color: var(--accent-color); /* Default color for progress bar */
4
4
  --progress-bar-background: #dadada;
@@ -6,6 +6,9 @@
6
6
 
7
7
  :where(.progress-bar-container) {
8
8
  width: 100%;
9
+ height: 8px;
10
+ border-radius: 2px;
11
+ overflow: hidden;
9
12
  }
10
13
 
11
14
  :where(.progress-bar-background) {
@@ -21,7 +24,6 @@
21
24
  appearance: none;
22
25
  background: transparent;
23
26
  user-select: none;
24
- /* box-shadow: 0 1px 0 0 rgba(222, 222, 222, 0.7), inset 0 1px 0 0 rgba(0, 0, 0, 0.3); */
25
27
  overflow: hidden;
26
28
  }
27
29
 
@@ -32,30 +34,4 @@
32
34
  background-color: var(--progress-bar-color);
33
35
  transition: width 200ms ease-out;
34
36
  }
35
-
36
- :where(.progress-bar-fill::before) {
37
- content: "";
38
- position: absolute;
39
- top: 0px;
40
- left: -20px;
41
- height: 100%;
42
- width: calc(100% + 40px);
43
- background-size: 20px 20px;
44
- animation: moveStripes 500ms linear infinite;
45
- }
46
-
47
- :where(.progress-bar-fill[data-finished="true"]::before) {
48
- animation: none;
49
- background-image: none;
50
- }
51
-
52
- /* Animation for moving stripes */
53
- @keyframes moveStripes {
54
- 0% {
55
- transform: translateX(0);
56
- }
57
- 100% {
58
- transform: translateX(20px);
59
- }
60
- }
61
37
  }
@@ -19,7 +19,7 @@ function Base(props: Partial<ProgressBarProps>) {
19
19
  return () => window.clearTimeout(timer);
20
20
  }, [value]);
21
21
 
22
- return <Component message="Progress..." {...props} value={value} />;
22
+ return <Component {...props} value={value} />;
23
23
  }
24
24
 
25
25
  export function ProgressBar() {
@@ -1,48 +1,36 @@
1
1
  import React, { HTMLAttributes } from 'react';
2
- import { BodyText } from '../../typography/index.js';
3
- import { HStack } from '../../stacks/h_stack.js';
4
- import { Spacer } from '../../stacks/spacer.js';
5
- import { VStack } from '../../stacks/v_stack.js';
6
2
  import { ZStack } from '../../stacks/z_stack.js';
7
3
  import clsx from 'clsx';
8
4
 
9
5
  import styles from './progress_bar.module.css';
10
6
 
11
7
  export interface ProgressBarProps extends HTMLAttributes<HTMLDivElement> {
12
- message: string;
13
8
  value: number;
14
9
  }
15
10
 
16
11
  export const ProgressBar = React.forwardRef(function ProgressBar(
17
- { message, value, ...props }: ProgressBarProps,
12
+ { value, ...props }: ProgressBarProps,
18
13
  ref: React.Ref<HTMLDivElement>
19
14
  ) {
20
15
  const progressPercent = `${(value * 100).toFixed(0)}%`;
21
16
  const styleVariables: any = { '--progress-percentage': progressPercent };
22
17
 
23
18
  return (
24
- <VStack
19
+ <ZStack
25
20
  ref={ref}
26
- className={clsx(styles['progress-bar-container'], 'tcn-progress-bar')}
21
+ hAlign="start"
22
+ className={clsx(styles['progress-bar-container'], 'tcn-progress-bar-container')}
27
23
  {...props}
28
24
  >
29
- <HStack height="auto" vAlign="end">
30
- <BodyText>{message}</BodyText>
31
- <Spacer />
32
- <Spacer width="8px" />
33
- <BodyText size="sm" style={{ flexShrink: 0 }}>
34
- {progressPercent}
35
- </BodyText>
36
- </HStack>
37
- <ZStack height="8px" hAlign="start">
38
- <div className={styles['progress-bar-background']}></div>
39
- <div
40
- data-finished={progressPercent === '100%'}
41
- style={styleVariables}
42
- className={styles['progress-bar-fill']}
43
- />
44
- <div className={styles['progress-bar']}></div>
45
- </ZStack>
46
- </VStack>
25
+ <div
26
+ className={clsx(styles['progress-bar-background'], 'tcn-progress-bar-background')}
27
+ ></div>
28
+ <div
29
+ data-finished={progressPercent === '100%'}
30
+ style={styleVariables}
31
+ className={clsx(styles['progress-bar-fill'], 'tcn-progress-bar-fill')}
32
+ />
33
+ <div className={clsx(styles['progress-bar'], 'tcn-progress-bar')}></div>
34
+ </ZStack>
47
35
  );
48
36
  });
@@ -1,5 +1,5 @@
1
- import React, { useState } from 'react';
2
- import { Option } from '../options/option.js';
1
+ import React, { useEffect, useState } from 'react';
2
+ import { Option, type OptionProps } from '../options/option.js';
3
3
  import { Select as SelectComponent, SelectProps } from './select.js';
4
4
  import { HStack } from '../../stacks/h_stack.js';
5
5
  import { VStack } from '../../stacks/v_stack.js';
@@ -123,6 +123,27 @@ export function CustomWidth(_: Omit<SelectProps, 'children'>) {
123
123
  );
124
124
  }
125
125
 
126
+ export function DelayedOptions() {
127
+ const [value, setValue] = useState<string>('');
128
+ const [options, setOptions] = useState<React.ReactElement<OptionProps>[]>([]);
129
+
130
+ useEffect(() => {
131
+ window.setTimeout(() => {
132
+ setOptions([
133
+ <Option key={1} value="apple" label="Apple" keywords={['fruit', 'red', 'sweet']}>
134
+ Apple
135
+ </Option>,
136
+ ]);
137
+ }, 4000);
138
+ }, []);
139
+
140
+ return (
141
+ <SelectComponent value={value} onChange={setValue} width="100px">
142
+ {options}
143
+ </SelectComponent>
144
+ );
145
+ }
146
+
126
147
  export function CustomOptions() {
127
148
  const [value, setValue] = useState<string>('');
128
149
 
@@ -60,9 +60,13 @@ export function SuggestionList({
60
60
  ...props
61
61
  }: SuggestionListProps) {
62
62
  // Extract valid Option components from children
63
- const suggestions = Children.toArray(children).filter(
64
- (child): child is React.ReactElement<OptionProps> =>
65
- isValidElement(child) && child.type === Option
63
+ const suggestions = React.useMemo(
64
+ () =>
65
+ Children.toArray(children).filter(
66
+ (child): child is React.ReactElement<OptionProps> =>
67
+ isValidElement(child) && child.type === Option
68
+ ),
69
+ [children]
66
70
  );
67
71
 
68
72
  const [selectedIndex, setSelectedIndex] = useState(() => {
@@ -83,7 +87,7 @@ export function SuggestionList({
83
87
  const internalInputRef = useRef<HTMLInputElement | null>(null);
84
88
  const [totalMatchedLength, setTotalMatchedLength] = useState(suggestions.length);
85
89
  const [matchedOptions, setMatchedOptions] = useState<React.ReactElement<OptionProps>[]>(
86
- () => getMatchedOptions(value, MAX_RESULTS)
90
+ () => []
87
91
  );
88
92
  const [suggestionsWidth, setSuggestionsWidth] = useState<string | undefined>();
89
93
  const [suggestionsHeight, setSuggestionsHeight] = useState<string | undefined>();
@@ -278,42 +282,47 @@ export function SuggestionList({
278
282
  onKeyDown && onKeyDown(event);
279
283
  }
280
284
 
281
- function getMatchedOptions(value: string, maxResults: number) {
282
- const results = suggestions.filter(option => {
283
- const props = option.props;
284
- const label = String(props.label).toLocaleLowerCase();
285
- const keywords = props.keywords?.map(k => k.toLocaleLowerCase()) || [];
286
- const optionValue = String(props.value).toLocaleLowerCase();
287
- const searchValue = value.toLocaleLowerCase();
288
- const obfuscate = props.obfuscate ?? false;
289
-
290
- // Obfuscated options can only be searched by label or keywords, not by value
291
- if (obfuscate) {
292
- return label.includes(searchValue) || keywords.some(k => k.includes(searchValue));
293
- }
285
+ const getMatchedOptions = React.useCallback(
286
+ function getMatchedOptions(value: string, maxResults: number) {
287
+ const results = suggestions.filter(option => {
288
+ const props = option.props;
289
+ const label = String(props.label).toLocaleLowerCase();
290
+ const keywords = props.keywords?.map(k => k.toLocaleLowerCase()) || [];
291
+ const optionValue = String(props.value).toLocaleLowerCase();
292
+ const searchValue = value.toLocaleLowerCase();
293
+ const obfuscate = props.obfuscate ?? false;
294
+
295
+ // Obfuscated options can only be searched by label or keywords, not by value
296
+ if (obfuscate) {
297
+ return (
298
+ label.includes(searchValue) || keywords.some(k => k.includes(searchValue))
299
+ );
300
+ }
294
301
 
295
- return (
296
- label.includes(searchValue) ||
297
- keywords.some(k => k.includes(searchValue)) ||
298
- optionValue.includes(searchValue)
299
- );
300
- });
301
-
302
- if (
303
- haveValueAsOption &&
304
- value.trim().length > 0 &&
305
- !results.some(r => r.props.value === value)
306
- ) {
307
- results.unshift(
308
- <Option key="value" value={value} label={value} keywords={[value]}>
309
- {value}
310
- </Option>
311
- );
312
- }
302
+ return (
303
+ label.includes(searchValue) ||
304
+ keywords.some(k => k.includes(searchValue)) ||
305
+ optionValue.includes(searchValue)
306
+ );
307
+ });
308
+
309
+ if (
310
+ haveValueAsOption &&
311
+ value.trim().length > 0 &&
312
+ !results.some(r => r.props.value === value)
313
+ ) {
314
+ results.unshift(
315
+ <Option key="value" value={value} label={value} keywords={[value]}>
316
+ {value}
317
+ </Option>
318
+ );
319
+ }
313
320
 
314
- setTotalMatchedLength(results.length);
315
- return results.slice(0, maxResults);
316
- }
321
+ setTotalMatchedLength(results.length);
322
+ return results.slice(0, maxResults);
323
+ },
324
+ [suggestions, haveValueAsOption]
325
+ );
317
326
 
318
327
  function focusInput() {
319
328
  const input = internalInputRef.current;
@@ -393,6 +402,11 @@ export function SuggestionList({
393
402
  setInternalValue(searchValue ?? '');
394
403
  }, [searchValue]);
395
404
 
405
+ useLayoutEffect(() => {
406
+ const newMatches = getMatchedOptions(value, MAX_RESULTS);
407
+ setMatchedOptions(newMatches);
408
+ }, [value, getMatchedOptions]);
409
+
396
410
  return (
397
411
  <Popper
398
412
  open={open}
@@ -433,7 +447,12 @@ export function SuggestionList({
433
447
  ))}
434
448
  {totalMatchedLength > matchedOptions.length && (
435
449
  <>
436
- <Button marginBlock="8px" hierarchy="tertiary" onClick={handleShowMore}>
450
+ <Button
451
+ key="show-more"
452
+ marginBlock="8px"
453
+ hierarchy="tertiary"
454
+ onClick={handleShowMore}
455
+ >
437
456
  Show More
438
457
  </Button>
439
458
  </>
@@ -81,12 +81,11 @@ export const FrameDialog = React.forwardRef<HTMLElement, FrameDialogProps>(
81
81
  width: number,
82
82
  origin: 'left' | 'right',
83
83
  totalDelta: number,
84
- currentDelta: number
84
+ currentDelta: number,
85
+ atLimit: boolean
85
86
  ) => {
86
- if (!draggable) {
87
- return;
88
- }
89
- // TODO: add clamp logic
87
+ if (!draggable) return;
88
+ if (atLimit) return;
90
89
  if (origin === 'right') {
91
90
  flushSync(() => {
92
91
  drag.setPosition(prev => ({
@@ -104,7 +103,7 @@ export const FrameDialog = React.forwardRef<HTMLElement, FrameDialogProps>(
104
103
  });
105
104
  }
106
105
 
107
- onWidthResize?.(width, origin, totalDelta, currentDelta);
106
+ onWidthResize?.(width, origin, totalDelta, currentDelta, atLimit);
108
107
  },
109
108
  [onWidthResize, drag, draggable]
110
109
  );
@@ -114,12 +113,11 @@ export const FrameDialog = React.forwardRef<HTMLElement, FrameDialogProps>(
114
113
  height: number,
115
114
  origin: 'top' | 'bottom',
116
115
  totalDelta: number,
117
- currentDelta: number
116
+ currentDelta: number,
117
+ atLimit: boolean
118
118
  ) => {
119
- if (!draggable) {
120
- return;
121
- }
122
- // TODO: add clamp logic
119
+ if (!draggable) return;
120
+ if (atLimit) return;
123
121
  if (origin === 'bottom') {
124
122
  flushSync(() => {
125
123
  drag.setPosition(prev => ({
@@ -136,7 +134,7 @@ export const FrameDialog = React.forwardRef<HTMLElement, FrameDialogProps>(
136
134
  }));
137
135
  });
138
136
  }
139
- onHeightResize?.(height, origin, totalDelta, currentDelta);
137
+ onHeightResize?.(height, origin, totalDelta, currentDelta, atLimit);
140
138
  },
141
139
  [onHeightResize, drag, draggable]
142
140
  );
@@ -1,21 +1,10 @@
1
1
  import { clsx } from 'clsx';
2
2
  import React from 'react';
3
3
  import styles from './bottom_resize_handle.module.css';
4
- import type { HandleProps } from './handle_props.js';
5
4
  import { createVerticalResizeHandler } from './resize_handlers.js';
5
+ import type { VerticalResizeHandleProps } from './types.js';
6
6
 
7
- export interface BottomResizeHandleProps {
8
- targetRef: React.MutableRefObject<HTMLElement | null>;
9
- handleProps?: HandleProps;
10
- onHeightResize?: (
11
- height: number,
12
- origin: 'top' | 'bottom',
13
- totalDelta: number,
14
- currentDelta: number
15
- ) => void;
16
- onHeightResizeEnd?: (height: number, origin: 'top' | 'bottom') => void;
17
- }
18
-
7
+ export type BottomResizeHandleProps = VerticalResizeHandleProps;
19
8
  export function BottomResizeHandle({
20
9
  targetRef,
21
10
  handleProps,
@@ -49,13 +49,15 @@ export interface BoxProps<T extends HTMLElement = HTMLElement> extends HTMLAttri
49
49
  width: number,
50
50
  origin: 'left' | 'right',
51
51
  totalDelta: number,
52
- currentDelta: number
52
+ currentDelta: number,
53
+ atLimit: boolean
53
54
  ) => void;
54
55
  onHeightResize?: (
55
56
  height: number,
56
57
  origin: 'top' | 'bottom',
57
58
  totalDelta: number,
58
- currentDelta: number
59
+ currentDelta: number,
60
+ atLimit: boolean
59
61
  ) => void;
60
62
  onWidthResizeEnd?: (width: number, origin: 'left' | 'right') => void;
61
63
  onHeightResizeEnd?: (height: number, origin: 'top' | 'bottom') => void;
@@ -1,20 +1,10 @@
1
1
  import { clsx } from 'clsx';
2
- import React from 'react';
3
2
  import styles from './end_resize_handle.module.css';
4
- import { HandleProps } from './handle_props.js';
5
3
  import { createHorizontalResizeHandler } from './resize_handlers.js';
4
+ import type { HorizontalResizeHandleProps } from './types.js';
5
+
6
+ export type EndResizeHandleProps = HorizontalResizeHandleProps;
6
7
 
7
- export interface EndResizeHandleProps {
8
- targetRef: React.MutableRefObject<HTMLElement | null>;
9
- handleProps?: HandleProps;
10
- onWidthResize?: (
11
- width: number,
12
- origin: 'left' | 'right',
13
- totalDelta: number,
14
- currentDelta: number
15
- ) => void;
16
- onWidthResizeEnd?: (width: number, origin: 'left' | 'right') => void;
17
- }
18
8
  export function EndResizeHandle({
19
9
  targetRef,
20
10
  handleProps,
@@ -1,20 +1,10 @@
1
1
  import { clsx } from 'clsx';
2
- import React from 'react';
3
- import { HandleProps } from './handle_props.js';
4
2
  import styles from './left_resize_handle.module.css';
5
3
  import { createHorizontalResizeHandler } from './resize_handlers.js';
4
+ import type { HorizontalResizeHandleProps } from './types.js';
5
+
6
+ export type LeftResizeHandleProps = HorizontalResizeHandleProps;
6
7
 
7
- export interface LeftResizeHandleProps {
8
- targetRef: React.MutableRefObject<HTMLElement | null>;
9
- handleProps?: HandleProps;
10
- onWidthResize?: (
11
- width: number,
12
- origin: 'left' | 'right',
13
- totalDelta: number,
14
- currentDelta: number
15
- ) => void;
16
- onWidthResizeEnd?: (width: number, origin: 'left' | 'right') => void;
17
- }
18
8
  export function LeftResizeHandle({
19
9
  targetRef,
20
10
  handleProps,
@@ -1,3 +1,10 @@
1
+ import {
2
+ OnWidthResize,
3
+ type HeightResizeOrigin,
4
+ type OnHeightResize,
5
+ type WidthResizeOrigin,
6
+ } from './types';
7
+
1
8
  function createVeil() {
2
9
  const veil = window.document.createElement('div');
3
10
  veil.style.position = 'absolute';
@@ -9,14 +16,9 @@ function createVeil() {
9
16
 
10
17
  export function createHorizontalResizeHandler(
11
18
  targetRef: React.MutableRefObject<HTMLElement | null>,
12
- onWidthResize?: (
13
- width: number,
14
- origin: 'left' | 'right',
15
- totalDelta: number,
16
- currentDelta: number
17
- ) => void,
18
- onWidthResizeEnd?: (width: number, origin: 'left' | 'right') => void,
19
- origin: 'left' | 'right' = 'right',
19
+ onWidthResize?: OnWidthResize,
20
+ onWidthResizeEnd?: (width: number, origin: WidthResizeOrigin) => void,
21
+ origin: WidthResizeOrigin = 'right',
20
22
  invert = false,
21
23
  disableDirection = false
22
24
  ) {
@@ -40,6 +42,7 @@ export function createHorizontalResizeHandler(
40
42
  let width = startRect.width;
41
43
 
42
44
  const drag = (event: MouseEvent) => {
45
+ const beforeWidth = box.getBoundingClientRect().width;
43
46
  const totalDelta = direction * (event.clientX - startX);
44
47
  const newWidth = startRect.width + totalDelta;
45
48
  const currentDelta = newWidth - width;
@@ -47,7 +50,10 @@ export function createHorizontalResizeHandler(
47
50
  width = newWidth;
48
51
 
49
52
  box.style.width = `${newWidth}px`;
50
- onWidthResize?.(newWidth, origin, totalDelta, currentDelta);
53
+
54
+ const afterWidth = box.getBoundingClientRect().width;
55
+ const atLimit = afterWidth === beforeWidth;
56
+ onWidthResize?.(newWidth, origin, totalDelta, currentDelta, atLimit);
51
57
  event.stopPropagation();
52
58
  event.preventDefault();
53
59
  };
@@ -80,15 +86,10 @@ export function createHorizontalResizeHandler(
80
86
 
81
87
  export function createVerticalResizeHandler(
82
88
  targetRef: React.MutableRefObject<HTMLElement | null>,
83
- onHeightResize?: (
84
- height: number,
85
- origin: 'top' | 'bottom',
86
- totalDelta: number,
87
- currentDelta: number
88
- ) => void,
89
- onHeightResizeEnd?: (height: number, origin: 'top' | 'bottom') => void,
89
+ onHeightResize?: OnHeightResize,
90
+ onHeightResizeEnd?: (height: number, origin: HeightResizeOrigin) => void,
90
91
  invert = false,
91
- origin: 'top' | 'bottom' = 'bottom'
92
+ origin: HeightResizeOrigin = 'bottom'
92
93
  ) {
93
94
  const direction = invert ? -1 : 1;
94
95
  return function startVerticalResize(event: React.MouseEvent) {
@@ -106,12 +107,15 @@ export function createVerticalResizeHandler(
106
107
  let height = startRect.height;
107
108
 
108
109
  const drag = (event: MouseEvent) => {
110
+ const beforeHeight = box.getBoundingClientRect().height;
109
111
  const totalDelta = direction * (event.clientY - startY);
110
112
  const newHeight = startRect.height + totalDelta;
111
113
  const currentDelta = newHeight - height;
112
114
  height = newHeight;
113
115
  box.style.height = `${newHeight}px`;
114
- onHeightResize?.(newHeight, origin, totalDelta, currentDelta);
116
+ const afterHeight = box.getBoundingClientRect().height;
117
+ const atLimit = afterHeight === beforeHeight;
118
+ onHeightResize?.(newHeight, origin, totalDelta, currentDelta, atLimit);
115
119
  event.stopPropagation();
116
120
  event.preventDefault();
117
121
  };
@@ -1,20 +1,9 @@
1
1
  import { clsx } from 'clsx';
2
- import React from 'react';
3
- import { HandleProps } from './handle_props.js';
4
2
  import { createHorizontalResizeHandler } from './resize_handlers.js';
5
3
  import styles from './right_resize_handle.module.css';
4
+ import type { HorizontalResizeHandleProps } from './types.js';
6
5
 
7
- export interface RightResizeHandleProps {
8
- targetRef: React.MutableRefObject<HTMLElement | null>;
9
- handleProps?: HandleProps;
10
- onWidthResize?: (
11
- width: number,
12
- origin: 'left' | 'right',
13
- totalDelta: number,
14
- currentDelta: number
15
- ) => void;
16
- onWidthResizeEnd?: (width: number, origin: 'left' | 'right') => void;
17
- }
6
+ export type RightResizeHandleProps = HorizontalResizeHandleProps;
18
7
  export function RightResizeHandle({
19
8
  targetRef,
20
9
  handleProps,
@@ -1,20 +1,10 @@
1
1
  import { clsx } from 'clsx';
2
- import React from 'react';
3
- import { HandleProps } from './handle_props.js';
4
2
  import { createHorizontalResizeHandler } from './resize_handlers.js';
5
3
  import styles from './start_resize_handle.module.css';
4
+ import type { HorizontalResizeHandleProps } from './types.js';
5
+
6
+ export type StartResizeHandleProps = HorizontalResizeHandleProps;
6
7
 
7
- export interface StartResizeHandleProps {
8
- targetRef: React.MutableRefObject<HTMLElement | null>;
9
- handleProps?: HandleProps;
10
- onWidthResize?: (
11
- width: number,
12
- origin: 'left' | 'right',
13
- totalDelta: number,
14
- currentDelta: number
15
- ) => void;
16
- onWidthResizeEnd?: (width: number, origin: 'left' | 'right') => void;
17
- }
18
8
  export function StartResizeHandle({
19
9
  targetRef,
20
10
  handleProps,
@@ -1,20 +1,11 @@
1
1
  import { clsx } from 'clsx';
2
2
  import React from 'react';
3
- import { HandleProps } from './handle_props.js';
4
3
  import { createVerticalResizeHandler } from './resize_handlers.js';
5
4
  import styles from './top_resize_handle.module.css';
5
+ import type { VerticalResizeHandleProps } from './types.js';
6
+
7
+ export type TopResizeHandleProps = VerticalResizeHandleProps;
6
8
 
7
- export interface TopResizeHandleProps {
8
- targetRef: React.MutableRefObject<HTMLElement | null>;
9
- handleProps?: HandleProps;
10
- onHeightResize?: (
11
- height: number,
12
- origin: 'top' | 'bottom',
13
- totalDelta: number,
14
- currentDelta: number
15
- ) => void;
16
- onHeightResizeEnd?: (height: number, origin: 'top' | 'bottom') => void;
17
- }
18
9
  export function TopResizeHandle({
19
10
  targetRef,
20
11
  handleProps,