@tcn/ui 0.12.1 → 0.12.3

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 (99) hide show
  1. package/dist/inputs/phone_number_input/phone_number_input_adapter.d.ts.map +1 -1
  2. package/dist/inputs/phone_number_input/phone_number_input_adapter.js +39 -34
  3. package/dist/inputs/phone_number_input/phone_number_input_adapter.js.map +1 -1
  4. package/dist/inputs/phone_number_input/sip_input.d.ts.map +1 -1
  5. package/dist/inputs/phone_number_input/sip_input.js +57 -52
  6. package/dist/inputs/phone_number_input/sip_input.js.map +1 -1
  7. package/dist/inputs/suggestions/suggestion_list.d.ts +8 -1
  8. package/dist/inputs/suggestions/suggestion_list.d.ts.map +1 -1
  9. package/dist/inputs/suggestions/suggestion_list.js +121 -111
  10. package/dist/inputs/suggestions/suggestion_list.js.map +1 -1
  11. package/dist/overlay/popper/base/dismissal_decorator.js +6 -6
  12. package/dist/overlay/popper/base/dismissal_decorator.js.map +1 -1
  13. package/dist/overlay/popper/context_popper.d.ts.map +1 -1
  14. package/dist/overlay/popper/context_popper.js +34 -26
  15. package/dist/overlay/popper/context_popper.js.map +1 -1
  16. package/dist/overlay/popper/element_popper.d.ts.map +1 -1
  17. package/dist/overlay/popper/element_popper.js +43 -25
  18. package/dist/overlay/popper/element_popper.js.map +1 -1
  19. package/dist/overlay/tethered/hooks/use_ref_dimensions.d.ts +3 -0
  20. package/dist/overlay/tethered/hooks/use_ref_dimensions.d.ts.map +1 -0
  21. package/dist/overlay/tethered/hooks/use_ref_dimensions.js +26 -0
  22. package/dist/overlay/tethered/hooks/use_ref_dimensions.js.map +1 -0
  23. package/dist/overlay/tethered/hooks/{useTether.d.ts → use_tether.d.ts} +7 -4
  24. package/dist/overlay/tethered/hooks/use_tether.d.ts.map +1 -0
  25. package/dist/overlay/tethered/hooks/{useTether.js → use_tether.js} +19 -15
  26. package/dist/overlay/tethered/hooks/use_tether.js.map +1 -0
  27. package/dist/overlay/tethered/hooks/use_tether_origin.d.ts +10 -0
  28. package/dist/overlay/tethered/hooks/use_tether_origin.d.ts.map +1 -0
  29. package/dist/overlay/tethered/hooks/use_tether_origin.js +22 -0
  30. package/dist/overlay/tethered/hooks/use_tether_origin.js.map +1 -0
  31. package/dist/overlay/tethered/hooks/{calculate_origin.d.ts → utils/calculate_origin.d.ts} +4 -10
  32. package/dist/overlay/tethered/hooks/utils/calculate_origin.d.ts.map +1 -0
  33. package/dist/overlay/tethered/hooks/utils/calculate_origin.js +41 -0
  34. package/dist/overlay/tethered/hooks/utils/calculate_origin.js.map +1 -0
  35. package/dist/overlay/tethered/hooks/{calculate_position.d.ts → utils/calculate_position.d.ts} +2 -2
  36. package/dist/overlay/tethered/hooks/utils/calculate_position.d.ts.map +1 -0
  37. package/dist/overlay/tethered/hooks/utils/calculate_position.js.map +1 -0
  38. package/dist/overlay/tethered/tethered.d.ts.map +1 -1
  39. package/dist/overlay/tethered/tethered.js +63 -62
  40. package/dist/overlay/tethered/tethered.js.map +1 -1
  41. package/dist/surfaces/pop_confirm/pop_confirm.js +7 -7
  42. package/dist/surfaces/pop_confirm/pop_confirm.js.map +1 -1
  43. package/dist/surfaces/tooltip/tooltip.d.ts.map +1 -1
  44. package/dist/surfaces/tooltip/tooltip.js +27 -28
  45. package/dist/surfaces/tooltip/tooltip.js.map +1 -1
  46. package/dist/themes/themes/ergo/ergo_theme.css +1 -1
  47. package/dist/themes/themes/ergo/ergo_theme.js +98 -8
  48. package/dist/themes/themes/ergo/ergo_theme.js.map +1 -1
  49. package/package.json +1 -1
  50. package/src/inputs/phone_number_input/phone_number_input.stories.tsx +7 -0
  51. package/src/inputs/phone_number_input/phone_number_input_adapter.tsx +5 -0
  52. package/src/inputs/phone_number_input/sip_input.tsx +5 -0
  53. package/src/inputs/select/select.stories.tsx +34 -11
  54. package/src/inputs/suggestions/suggestion_list.tsx +23 -6
  55. package/src/overlay/popper/base/dismissal_decorator.tsx +1 -1
  56. package/src/overlay/popper/context_popper.tsx +7 -3
  57. package/src/overlay/popper/element_popper.tsx +14 -4
  58. package/src/overlay/tethered/__stories__/shared/base_story_config.ts +1 -1
  59. package/src/overlay/tethered/hooks/use_ref_dimensions.ts +32 -0
  60. package/src/overlay/tethered/hooks/{useTether.ts → use_tether.ts} +7 -2
  61. package/src/overlay/tethered/hooks/use_tether_origin.ts +46 -0
  62. package/src/overlay/tethered/hooks/{calculate_origin.ts → utils/calculate_origin.ts} +12 -12
  63. package/src/overlay/tethered/hooks/{calculate_position.ts → utils/calculate_position.ts} +3 -3
  64. package/src/overlay/tethered/tethered.tsx +22 -26
  65. package/src/surfaces/pop_confirm/pop_confirm.stories.tsx +206 -6
  66. package/src/surfaces/pop_confirm/pop_confirm.tsx +1 -1
  67. package/src/surfaces/tooltip/__stories__/tooltip.stories.tsx +136 -0
  68. package/src/surfaces/tooltip/__stories__/tooltip_stories.module.css +14 -0
  69. package/src/surfaces/tooltip/tooltip.tsx +6 -2
  70. package/src/themes/themes/ergo/ergo_theme.css +98 -8
  71. package/dist/overlay/tethered/hooks/calculate_origin.d.ts.map +0 -1
  72. package/dist/overlay/tethered/hooks/calculate_origin.js +0 -41
  73. package/dist/overlay/tethered/hooks/calculate_origin.js.map +0 -1
  74. package/dist/overlay/tethered/hooks/calculate_position.d.ts.map +0 -1
  75. package/dist/overlay/tethered/hooks/calculate_position.js.map +0 -1
  76. package/dist/overlay/tethered/hooks/useCaretRefDimensions.d.ts +0 -9
  77. package/dist/overlay/tethered/hooks/useCaretRefDimensions.d.ts.map +0 -1
  78. package/dist/overlay/tethered/hooks/useCaretRefDimensions.js +0 -14
  79. package/dist/overlay/tethered/hooks/useCaretRefDimensions.js.map +0 -1
  80. package/dist/overlay/tethered/hooks/useTether.d.ts.map +0 -1
  81. package/dist/overlay/tethered/hooks/useTether.js.map +0 -1
  82. package/dist/overlay/tethered/hooks/useTetherContentRect.d.ts +0 -3
  83. package/dist/overlay/tethered/hooks/useTetherContentRect.d.ts.map +0 -1
  84. package/dist/overlay/tethered/hooks/useTetherContentRect.js +0 -36
  85. package/dist/overlay/tethered/hooks/useTetherContentRect.js.map +0 -1
  86. package/dist/overlay/tethered/hooks/useTetherOrigin.d.ts +0 -14
  87. package/dist/overlay/tethered/hooks/useTetherOrigin.d.ts.map +0 -1
  88. package/dist/overlay/tethered/hooks/useTetherOrigin.js +0 -24
  89. package/dist/overlay/tethered/hooks/useTetherOrigin.js.map +0 -1
  90. package/dist/surfaces/popconfirm/pop_confirm.d.ts +0 -5
  91. package/dist/surfaces/popconfirm/pop_confirm.d.ts.map +0 -1
  92. package/dist/surfaces/popconfirm/pop_confirm.js +0 -13
  93. package/dist/surfaces/popconfirm/pop_confirm.js.map +0 -1
  94. package/src/overlay/tethered/hooks/useCaretRefDimensions.ts +0 -22
  95. package/src/overlay/tethered/hooks/useTetherContentRect.ts +0 -49
  96. package/src/overlay/tethered/hooks/useTetherOrigin.ts +0 -49
  97. package/src/surfaces/popconfirm/pop_confirm.tsx +0 -18
  98. package/src/surfaces/tooltip/tooltip.stories.tsx +0 -54
  99. /package/dist/overlay/tethered/hooks/{calculate_position.js → utils/calculate_position.js} +0 -0
@@ -139,6 +139,13 @@ export const WithPhoneBook = () => {
139
139
  <Option value="+14355865955" label="Bob Johnson" keywords={['bob', 'johnson']}>
140
140
  Bob Johnson - +1 (435) 586-5955
141
141
  </Option>
142
+ <Option
143
+ value="+14355865950"
144
+ label="A Really Really Really Really Really Long Label For Jack"
145
+ keywords={['jack', 'johnson']}
146
+ >
147
+ A Really Really Really Really Reall Long Label - +1 (435) 586-5955
148
+ </Option>
142
149
  <Option
143
150
  value="sip:foo@bar.com"
144
151
  label="Foo Sip"
@@ -112,6 +112,11 @@ export const PhoneNumberInputAdapter = forwardRef<
112
112
  <SuggestionList
113
113
  open={isPhoneBookOpen}
114
114
  anchorElement={phoneBookElement}
115
+ horizontalAnchor="end"
116
+ horizontalOrigin="end"
117
+ verticalAnchor="top"
118
+ verticalOrigin="top"
119
+ width="300px"
115
120
  onOptionSelect={handlePhoneBookOptionSelect}
116
121
  onClose={closePhoneBook}
117
122
  noSuggestionMessage="No phone numbers found"
@@ -138,7 +138,12 @@ export function SipInput({
138
138
  anchorElement={phoneBookElement}
139
139
  onOptionSelect={handlePhoneBookOptionSelect}
140
140
  onClose={closePhoneBook}
141
+ width="300px"
141
142
  noSuggestionMessage="No phone numbers found"
143
+ horizontalAnchor="end"
144
+ horizontalOrigin="end"
145
+ verticalAnchor="top"
146
+ verticalOrigin="top"
142
147
  >
143
148
  {phoneBookOptions}
144
149
  </SuggestionList>
@@ -1,10 +1,11 @@
1
- import React, { useEffect, useState } from 'react';
1
+ import React, { useEffect, useRef, useState } from 'react';
2
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';
6
6
  import { Headline } from '../../typography/headline/headline.js';
7
7
  import { BodyText } from '../../typography/body_text/body_text.js';
8
+ import { Button } from '../../actions/button/button/button.js';
8
9
 
9
10
  export default {
10
11
  title: 'Inputs/Select',
@@ -124,23 +125,45 @@ export function CustomWidth(_: Omit<SelectProps, 'children'>) {
124
125
  }
125
126
 
126
127
  export function DelayedOptions() {
128
+ const timeoutRef = useRef<number | null>(null);
127
129
  const [value, setValue] = useState<string>('');
128
130
  const [options, setOptions] = useState<React.ReactElement<OptionProps>[]>([]);
129
131
 
130
132
  useEffect(() => {
131
- window.setTimeout(() => {
132
- setOptions([
133
- <Option key={1} value="apple" label="Apple" keywords={['fruit', 'red', 'sweet']}>
134
- Apple
133
+ timeoutRef.current = window.setTimeout(() => {
134
+ const length = options.length;
135
+ const newOptions = [
136
+ ...options,
137
+ <Option
138
+ key={length}
139
+ value={String(length)}
140
+ label={String(length)}
141
+ keywords={[String(length)]}
142
+ >
143
+ {String(length)}
135
144
  </Option>,
136
- ]);
137
- }, 4000);
138
- }, []);
145
+ ];
146
+ setOptions(newOptions);
147
+ }, 2000);
148
+
149
+ return () => {
150
+ window.clearTimeout(timeoutRef?.current || 0);
151
+ };
152
+ }, [options]);
139
153
 
140
154
  return (
141
- <SelectComponent value={value} onChange={setValue} width="100px">
142
- {options}
143
- </SelectComponent>
155
+ <>
156
+ <SelectComponent value={value} onChange={setValue} width="100px">
157
+ {options}
158
+ </SelectComponent>
159
+ <Button
160
+ onClick={() => {
161
+ window.clearInterval(timeoutRef.current ?? 0);
162
+ }}
163
+ >
164
+ Stop
165
+ </Button>
166
+ </>
144
167
  );
145
168
  }
146
169
 
@@ -24,6 +24,7 @@ export interface SuggestionListProps
24
24
  open?: boolean;
25
25
  children?: React.ReactNode;
26
26
  onChange?: (value: string) => void;
27
+ width?: string;
27
28
  onOptionSelect?: (
28
29
  value: string,
29
30
  label: string | undefined,
@@ -39,6 +40,12 @@ export interface SuggestionListProps
39
40
  cursorStartPosition: number | null,
40
41
  cursorEndPosition: number | null
41
42
  ) => void;
43
+ verticalAnchor?: 'top' | 'center' | 'bottom';
44
+ verticalOrigin?: 'top' | 'center' | 'bottom';
45
+ verticalOffset?: number;
46
+ horizontalAnchor?: 'start' | 'center' | 'end';
47
+ horizontalOrigin?: 'start' | 'center' | 'end';
48
+ horizontalOffset?: number;
42
49
  }
43
50
 
44
51
  export function SuggestionList({
@@ -47,6 +54,7 @@ export function SuggestionList({
47
54
  scrollToValue,
48
55
  anchorElement,
49
56
  open = false,
57
+ width = 'auto',
50
58
  children,
51
59
  onOptionSelect,
52
60
  noSuggestionMessage = '-- No Matches --',
@@ -57,6 +65,12 @@ export function SuggestionList({
57
65
  trimCustomInput = false,
58
66
  haveValueAsOption = false,
59
67
  restoreFocus = true,
68
+ verticalAnchor = 'top',
69
+ verticalOrigin = 'top',
70
+ verticalOffset = -4,
71
+ horizontalAnchor = 'start',
72
+ horizontalOrigin = 'start',
73
+ horizontalOffset = 0,
60
74
  ...props
61
75
  }: SuggestionListProps) {
62
76
  // Extract valid Option components from children
@@ -403,25 +417,28 @@ export function SuggestionList({
403
417
  }, [searchValue]);
404
418
 
405
419
  useLayoutEffect(() => {
406
- const newMatches = getMatchedOptions(value, MAX_RESULTS);
420
+ const newMatches = getMatchedOptions(internalValue, MAX_RESULTS);
407
421
  setMatchedOptions(newMatches);
408
- }, [value, getMatchedOptions]);
422
+ }, [getMatchedOptions, internalValue]);
409
423
 
410
424
  return (
411
425
  <Popper
412
426
  open={open}
413
427
  anchorElement={anchorElement}
414
428
  onClose={handleUseClose}
415
- verticalAnchor="top"
416
- verticalOrigin="top"
417
- verticalOffset={-4}
429
+ verticalAnchor={verticalAnchor}
430
+ verticalOrigin={verticalOrigin}
431
+ verticalOffset={verticalOffset}
432
+ horizontalAnchor={horizontalAnchor}
433
+ horizontalOrigin={horizontalOrigin}
434
+ horizontalOffset={horizontalOffset}
418
435
  restoreFocus={restoreFocus}
419
436
  >
420
437
  <VStack
421
438
  minHeight={`calc(${suggestionsHeight}, 8px)`}
422
439
  maxHeight="300px"
423
440
  minWidth={suggestionsWidth}
424
- width="auto"
441
+ width={width}
425
442
  hAlign="start"
426
443
  className={clsx(styles['suggestion-list'], 'tcn-suggestion-list')}
427
444
  >
@@ -35,7 +35,7 @@ export const PopperDismissalDecorator = forwardRef<
35
35
  };
36
36
 
37
37
  function buildExceptionHandler(dismissal: PopperDismissal) {
38
- if (dismissal in dismissals) {
38
+ if (dismissals.includes(dismissal)) {
39
39
  return (target: HTMLElement) => isException?.(dismissal, target) ?? false;
40
40
  }
41
41
  return () => false;
@@ -1,8 +1,9 @@
1
- import { forwardRef, type PropsWithChildren } from 'react';
1
+ import { forwardRef, useRef, type PropsWithChildren } from 'react';
2
2
  import { BasePopper, type BasePopperProps } from './base/base_popper.js';
3
3
  import { Tethered, type TetheredProps } from '../tethered/tethered.js';
4
4
  import { useContextTrigger } from './hooks/use_context_trigger.js';
5
5
  import { PopperDismissal } from './base/dismissal_decorator.js';
6
+ import { useForkRef } from '../../utils/index.js';
6
7
 
7
8
  export type ContextPopperProps = Omit<BasePopperProps, 'open' | 'onDismissal'> &
8
9
  Omit<TetheredProps, 'anchor'> & {
@@ -25,6 +26,9 @@ export const ContextPopper = forwardRef<
25
26
  ref
26
27
  ) {
27
28
  const { isOpen, close, rectangle } = useContextTrigger(anchorElement);
29
+ const popperRef = useRef<HTMLDivElement>(null);
30
+ const mergedRef = useForkRef(ref, popperRef);
31
+ const effectiveAcceptedRefs = [popperRef, anchorElement, ...acceptedRefs];
28
32
 
29
33
  return (
30
34
  <BasePopper
@@ -32,10 +36,10 @@ export const ContextPopper = forwardRef<
32
36
  onDismissal={close}
33
37
  restoreFocus={restoreFocus}
34
38
  dismissals={dismissals}
35
- acceptedRefs={acceptedRefs}
39
+ acceptedRefs={effectiveAcceptedRefs}
36
40
  isException={isException}
37
41
  >
38
- <Tethered ref={ref} anchor={rectangle} {...tetheredProps}>
42
+ <Tethered ref={mergedRef} anchor={rectangle} {...tetheredProps}>
39
43
  {children}
40
44
  </Tethered>
41
45
  </BasePopper>
@@ -1,9 +1,10 @@
1
- import { forwardRef, type PropsWithChildren } from 'react';
1
+ import { forwardRef, useRef, type PropsWithChildren } from 'react';
2
2
  import { BasePopper, type BasePopperProps } from './base/base_popper.js';
3
3
  import {
4
4
  ElementTethered,
5
5
  type ElementTetheredProps,
6
6
  } from '../tethered/element_tethered.js';
7
+ import { useForkRef } from '../../utils/index.js';
7
8
 
8
9
  export type ElementPopperProps = BasePopperProps & ElementTetheredProps;
9
10
 
@@ -16,14 +17,19 @@ export const ElementPopper = forwardRef<
16
17
  open,
17
18
  onDismissal,
18
19
  isException,
19
- acceptedRefs,
20
+ acceptedRefs = [],
20
21
  veil,
21
22
  dismissals,
22
23
  children,
24
+ anchorElement,
23
25
  ...elementTetheredProps
24
26
  },
25
27
  ref
26
28
  ) {
29
+ const popperRef = useRef<HTMLDivElement>(null);
30
+ const mergedRef = useForkRef(ref, popperRef);
31
+ const effectiveAcceptedRefs = [popperRef, anchorElement, ...acceptedRefs];
32
+
27
33
  return (
28
34
  <BasePopper
29
35
  restoreFocus={restoreFocus}
@@ -31,10 +37,14 @@ export const ElementPopper = forwardRef<
31
37
  onDismissal={onDismissal}
32
38
  isException={isException}
33
39
  dismissals={dismissals}
34
- acceptedRefs={acceptedRefs}
40
+ acceptedRefs={effectiveAcceptedRefs}
35
41
  veil={veil}
36
42
  >
37
- <ElementTethered ref={ref} {...elementTetheredProps}>
43
+ <ElementTethered
44
+ ref={mergedRef}
45
+ anchorElement={anchorElement}
46
+ {...elementTetheredProps}
47
+ >
38
48
  {children}
39
49
  </ElementTethered>
40
50
  </BasePopper>
@@ -56,5 +56,5 @@ export const tetheredArgs: TetheredStoryArgs = {
56
56
  horizontalOrigin: 'center',
57
57
  verticalOffset: 0,
58
58
  horizontalOffset: 0,
59
- precision: 'low',
59
+ precision: undefined,
60
60
  };
@@ -0,0 +1,32 @@
1
+ import { useLayoutEffect, useState } from 'react';
2
+ import type { Dimensions } from '../../../utils/index.js';
3
+
4
+ function getDimensions(element: HTMLElement | null): Dimensions {
5
+ if (!element) return { width: 0, height: 0 };
6
+ const rect = element.getBoundingClientRect();
7
+ return { width: rect.width, height: rect.height };
8
+ }
9
+
10
+ export function useRefDimensions(ref: React.RefObject<HTMLElement>): Dimensions {
11
+ const [dimensions, setDimensions] = useState<Dimensions>(() =>
12
+ getDimensions(ref.current)
13
+ );
14
+
15
+ useLayoutEffect(() => {
16
+ const update = () => {
17
+ setDimensions(getDimensions(ref.current));
18
+ };
19
+
20
+ update();
21
+
22
+ const resizeObserver = new ResizeObserver(update);
23
+ if (!ref.current) return;
24
+ resizeObserver.observe(ref.current);
25
+
26
+ return () => {
27
+ resizeObserver.disconnect();
28
+ };
29
+ }, [ref]);
30
+
31
+ return dimensions;
32
+ }
@@ -1,7 +1,8 @@
1
1
  import { useCallback, useLayoutEffect, useRef, useState } from 'react';
2
2
  import { type Rectangle } from '../../../utils/index.js';
3
3
  import type { HorizontalTether, VerticalTether } from '../types.js';
4
- import { calculateTetheredPosition } from './calculate_position.js';
4
+ import { calculateTetheredPosition } from './utils/calculate_position.js';
5
+ import { useRefDimensions } from './use_ref_dimensions.js';
5
6
 
6
7
  export interface UseTetherParams {
7
8
  anchor: Rectangle | null;
@@ -24,6 +25,7 @@ export function useTether({
24
25
  }: UseTetherParams) {
25
26
  const [position, setPosition] = useState({ top: 0, left: 0 });
26
27
  const tetherRef = useRef<HTMLDivElement>(null);
28
+ const dimensions = useRefDimensions(tetherRef);
27
29
 
28
30
  const getPosition = useCallback(() => {
29
31
  if (!anchor || !tetherRef.current) return;
@@ -82,5 +84,8 @@ export function useTether({
82
84
  };
83
85
  });
84
86
 
85
- return { position, tetherRef };
87
+ return {
88
+ rectangle: { dimensions, position: { x: position.left, y: position.top } },
89
+ tetherRef,
90
+ };
86
91
  }
@@ -0,0 +1,46 @@
1
+ import { useRef } from 'react';
2
+ import type { Position, Rectangle } from '../../../utils/index.js';
3
+ import type { HorizontalTether, VerticalTether } from '../types.js';
4
+ import {
5
+ getOriginDirection,
6
+ getOriginOffset,
7
+ getOriginPosition,
8
+ type CaretDirection,
9
+ } from './utils/calculate_origin.js';
10
+ import { useRefDimensions } from './use_ref_dimensions.js';
11
+
12
+ export interface TetherOrigin {
13
+ rectangle: Rectangle;
14
+ offset: Position;
15
+ direction: CaretDirection;
16
+ }
17
+
18
+ const initialOriginResult: TetherOrigin = {
19
+ rectangle: { dimensions: { width: 0, height: 0 }, position: { x: 0, y: 0 } },
20
+ offset: { x: 0, y: 0 },
21
+ direction: 'none',
22
+ };
23
+
24
+ export function useTetherOrigin(
25
+ tetherRectangle: Rectangle,
26
+ hOrigin: HorizontalTether,
27
+ vOrigin: VerticalTether,
28
+ precision: 'high' | 'low'
29
+ ): [TetherOrigin, React.RefObject<HTMLElement>] {
30
+ const ref = useRef<HTMLElement | null>(null);
31
+ const dimensions = useRefDimensions(ref);
32
+
33
+ if (precision === 'low') return [initialOriginResult, ref];
34
+
35
+ // Calculate origin values for high precision
36
+ const originOffset = getOriginOffset(tetherRectangle.dimensions, hOrigin, vOrigin);
37
+ const originPosition = getOriginPosition(tetherRectangle.position, originOffset);
38
+ const originDirection = getOriginDirection(vOrigin, hOrigin);
39
+
40
+ const origin: TetherOrigin = {
41
+ rectangle: { dimensions, position: originPosition },
42
+ offset: originOffset,
43
+ direction: originDirection,
44
+ };
45
+ return [origin, ref];
46
+ }
@@ -1,5 +1,5 @@
1
- import type { Position, Rectangle } from '../../../utils/index.js';
2
- import type { HorizontalTether, VerticalTether } from '../types.js';
1
+ import type { Dimensions, Position } from '../../../../utils/index.js';
2
+ import type { HorizontalTether, VerticalTether } from '../../types.js';
3
3
 
4
4
  export type CaretDirection = 'top' | 'bottom' | 'start' | 'end' | 'none';
5
5
 
@@ -8,10 +8,10 @@ export type CaretDirection = 'top' | 'bottom' | 'start' | 'end' | 'none';
8
8
  * based on tether dimensions and origin alignment.
9
9
  */
10
10
  export function getOriginOffset(
11
- tether: Rectangle,
11
+ tetherDimensions: Dimensions,
12
12
  hOrigin: HorizontalTether,
13
13
  vOrigin: VerticalTether
14
- ): { yOffset: number; xOffset: number } {
14
+ ): Position {
15
15
  let yOffset = 0;
16
16
  let xOffset = 0;
17
17
 
@@ -21,10 +21,10 @@ export function getOriginOffset(
21
21
  yOffset = 0;
22
22
  break;
23
23
  case 'center':
24
- yOffset = tether.dimensions.height / 2;
24
+ yOffset = tetherDimensions.height / 2;
25
25
  break;
26
26
  case 'bottom':
27
- yOffset = tether.dimensions.height;
27
+ yOffset = tetherDimensions.height;
28
28
  break;
29
29
  }
30
30
 
@@ -34,14 +34,14 @@ export function getOriginOffset(
34
34
  xOffset = 0;
35
35
  break;
36
36
  case 'center':
37
- xOffset = tether.dimensions.width / 2;
37
+ xOffset = tetherDimensions.width / 2;
38
38
  break;
39
39
  case 'end':
40
- xOffset = tether.dimensions.width;
40
+ xOffset = tetherDimensions.width;
41
41
  break;
42
42
  }
43
43
 
44
- return { yOffset, xOffset };
44
+ return { y: yOffset, x: xOffset };
45
45
  }
46
46
 
47
47
  /**
@@ -49,11 +49,11 @@ export function getOriginOffset(
49
49
  */
50
50
  export function getOriginPosition(
51
51
  baselinePosition: Position,
52
- offset: { yOffset: number; xOffset: number }
52
+ offset: Position
53
53
  ): Position {
54
54
  return {
55
- x: baselinePosition.x + offset.xOffset,
56
- y: baselinePosition.y + offset.yOffset,
55
+ x: baselinePosition.x + offset.x,
56
+ y: baselinePosition.y + offset.y,
57
57
  };
58
58
  }
59
59
 
@@ -1,6 +1,6 @@
1
- import type { HorizontalTether, VerticalTether } from '../types.js';
2
- import type { Rectangle } from '../../../utils/index.js';
3
- import type { Dimensions } from '../../../utils/index.js';
1
+ import type { HorizontalTether, VerticalTether } from '../../types.js';
2
+ import type { Rectangle } from '../../../../utils/index.js';
3
+ import type { Dimensions } from '../../../../utils/index.js';
4
4
 
5
5
  export interface CalculateTetheredPositionParams {
6
6
  anchor: Rectangle;
@@ -4,9 +4,8 @@ import { ZStack, type ZStackProps } from '../../stacks/index.js';
4
4
  import { useForkRef, type Rectangle } from '../../utils/index.js';
5
5
  import { Caret } from '../caret/caret.js';
6
6
  import { Portal } from '../portal/portal.js';
7
- import { useCaretRefDimensions } from './hooks/useCaretRefDimensions.js';
8
- import { useTether } from './hooks/useTether.js';
9
- import { useTetherOrigin } from './hooks/useTetherOrigin.js';
7
+ import { useTether } from './hooks/use_tether.js';
8
+ import { useTetherOrigin } from './hooks/use_tether_origin.js';
10
9
  import type { HorizontalTether, VerticalTether } from './types.js';
11
10
 
12
11
  // Styles
@@ -25,6 +24,7 @@ export interface TetheredOwnProp extends BaseTetheredOwnProps {
25
24
  anchor: Rectangle | null;
26
25
  precision?: 'high' | 'low';
27
26
  }
27
+
28
28
  export interface TetheredProps extends TetheredOwnProp, ZStackProps {}
29
29
 
30
30
  export const Tethered = forwardRef<HTMLElement, PropsWithChildren<TetheredProps>>(
@@ -45,7 +45,7 @@ export const Tethered = forwardRef<HTMLElement, PropsWithChildren<TetheredProps>
45
45
  },
46
46
  ref
47
47
  ) {
48
- const { position, tetherRef } = useTether({
48
+ const { rectangle, tetherRef } = useTether({
49
49
  anchor,
50
50
  verticalAnchor,
51
51
  verticalOrigin,
@@ -55,28 +55,24 @@ export const Tethered = forwardRef<HTMLElement, PropsWithChildren<TetheredProps>
55
55
  horizontalOffset,
56
56
  });
57
57
 
58
- // Convert position from { top, left } to Position type { x, y }
59
- const tetherPosition = { x: position.left, y: position.top };
60
-
61
- const { originPosition, originOffset, containerRef, originDirection } =
62
- useTetherOrigin(tetherPosition, horizontalOrigin, verticalOrigin, precision);
63
-
64
- const { caretElementRef: setCaretElement, caretSize } = useCaretRefDimensions(
65
- precision,
66
- originDirection
58
+ const [origin, originRef] = useTetherOrigin(
59
+ rectangle,
60
+ horizontalOrigin,
61
+ verticalOrigin,
62
+ precision
67
63
  );
68
64
 
69
- const forkedRef = useForkRef(ref, tetherRef, containerRef);
65
+ const forkedRef = useForkRef(ref, tetherRef);
70
66
 
71
67
  const cssVariables = {
72
- '--tethered-top': `${position.top}px`,
73
- '--tethered-left': `${position.left}px`,
74
- '--tethered-origin-y': `${originPosition.y}px`,
75
- '--tethered-origin-x': `${originPosition.x}px`,
76
- '--tethered-origin-delta-y': `${originOffset.yOffset}px`,
77
- '--tethered-origin-delta-x': `${originOffset.xOffset}px`,
78
- '--origin-indicator-dimensions-width': `${caretSize.width}px`,
79
- '--origin-indicator-dimensions-height': `${caretSize.height}px`,
68
+ '--tethered-top': `${rectangle.position.y}px`,
69
+ '--tethered-left': `${rectangle.position.x}px`,
70
+ '--tethered-origin-y': `${origin.rectangle.position.y}px`,
71
+ '--tethered-origin-x': `${origin.rectangle.position.x}px`,
72
+ '--tethered-origin-delta-y': `${origin.offset.y}px`,
73
+ '--tethered-origin-delta-x': `${origin.offset.x}px`,
74
+ '--origin-indicator-dimensions-width': `${origin.rectangle.dimensions.width}px`,
75
+ '--origin-indicator-dimensions-height': `${origin.rectangle.dimensions.height}px`,
80
76
  };
81
77
 
82
78
  return (
@@ -89,14 +85,14 @@ export const Tethered = forwardRef<HTMLElement, PropsWithChildren<TetheredProps>
89
85
  data-h-anchor={horizontalAnchor}
90
86
  data-v-origin={verticalOrigin}
91
87
  data-h-origin={horizontalOrigin}
92
- data-anchor-direction={originDirection}
88
+ data-anchor-direction={origin.direction}
93
89
  {...rest}
94
90
  >
95
91
  {children}
96
- {precision === 'high' && originDirection !== 'none' && (
92
+ {precision === 'high' && origin.direction !== 'none' && (
97
93
  <Caret
98
- ref={setCaretElement}
99
- direction={originDirection}
94
+ ref={originRef}
95
+ direction={origin.direction}
100
96
  className={clsx(
101
97
  styles['origin-indicator'],
102
98
  'tcn-tethered-origin-indicator'