@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.
- package/dist/inputs/phone_number_input/phone_number_input_adapter.d.ts.map +1 -1
- package/dist/inputs/phone_number_input/phone_number_input_adapter.js +39 -34
- package/dist/inputs/phone_number_input/phone_number_input_adapter.js.map +1 -1
- package/dist/inputs/phone_number_input/sip_input.d.ts.map +1 -1
- package/dist/inputs/phone_number_input/sip_input.js +57 -52
- package/dist/inputs/phone_number_input/sip_input.js.map +1 -1
- package/dist/inputs/suggestions/suggestion_list.d.ts +8 -1
- package/dist/inputs/suggestions/suggestion_list.d.ts.map +1 -1
- package/dist/inputs/suggestions/suggestion_list.js +121 -111
- package/dist/inputs/suggestions/suggestion_list.js.map +1 -1
- package/dist/overlay/popper/base/dismissal_decorator.js +6 -6
- package/dist/overlay/popper/base/dismissal_decorator.js.map +1 -1
- package/dist/overlay/popper/context_popper.d.ts.map +1 -1
- package/dist/overlay/popper/context_popper.js +34 -26
- package/dist/overlay/popper/context_popper.js.map +1 -1
- package/dist/overlay/popper/element_popper.d.ts.map +1 -1
- package/dist/overlay/popper/element_popper.js +43 -25
- package/dist/overlay/popper/element_popper.js.map +1 -1
- package/dist/overlay/tethered/hooks/use_ref_dimensions.d.ts +3 -0
- package/dist/overlay/tethered/hooks/use_ref_dimensions.d.ts.map +1 -0
- package/dist/overlay/tethered/hooks/use_ref_dimensions.js +26 -0
- package/dist/overlay/tethered/hooks/use_ref_dimensions.js.map +1 -0
- package/dist/overlay/tethered/hooks/{useTether.d.ts → use_tether.d.ts} +7 -4
- package/dist/overlay/tethered/hooks/use_tether.d.ts.map +1 -0
- package/dist/overlay/tethered/hooks/{useTether.js → use_tether.js} +19 -15
- package/dist/overlay/tethered/hooks/use_tether.js.map +1 -0
- package/dist/overlay/tethered/hooks/use_tether_origin.d.ts +10 -0
- package/dist/overlay/tethered/hooks/use_tether_origin.d.ts.map +1 -0
- package/dist/overlay/tethered/hooks/use_tether_origin.js +22 -0
- package/dist/overlay/tethered/hooks/use_tether_origin.js.map +1 -0
- package/dist/overlay/tethered/hooks/{calculate_origin.d.ts → utils/calculate_origin.d.ts} +4 -10
- package/dist/overlay/tethered/hooks/utils/calculate_origin.d.ts.map +1 -0
- package/dist/overlay/tethered/hooks/utils/calculate_origin.js +41 -0
- package/dist/overlay/tethered/hooks/utils/calculate_origin.js.map +1 -0
- package/dist/overlay/tethered/hooks/{calculate_position.d.ts → utils/calculate_position.d.ts} +2 -2
- package/dist/overlay/tethered/hooks/utils/calculate_position.d.ts.map +1 -0
- package/dist/overlay/tethered/hooks/utils/calculate_position.js.map +1 -0
- package/dist/overlay/tethered/tethered.d.ts.map +1 -1
- package/dist/overlay/tethered/tethered.js +63 -62
- package/dist/overlay/tethered/tethered.js.map +1 -1
- package/dist/surfaces/pop_confirm/pop_confirm.js +7 -7
- package/dist/surfaces/pop_confirm/pop_confirm.js.map +1 -1
- package/dist/surfaces/tooltip/tooltip.d.ts.map +1 -1
- package/dist/surfaces/tooltip/tooltip.js +27 -28
- package/dist/surfaces/tooltip/tooltip.js.map +1 -1
- package/dist/themes/themes/ergo/ergo_theme.css +1 -1
- package/dist/themes/themes/ergo/ergo_theme.js +98 -8
- package/dist/themes/themes/ergo/ergo_theme.js.map +1 -1
- package/package.json +1 -1
- package/src/inputs/phone_number_input/phone_number_input.stories.tsx +7 -0
- package/src/inputs/phone_number_input/phone_number_input_adapter.tsx +5 -0
- package/src/inputs/phone_number_input/sip_input.tsx +5 -0
- package/src/inputs/select/select.stories.tsx +34 -11
- package/src/inputs/suggestions/suggestion_list.tsx +23 -6
- package/src/overlay/popper/base/dismissal_decorator.tsx +1 -1
- package/src/overlay/popper/context_popper.tsx +7 -3
- package/src/overlay/popper/element_popper.tsx +14 -4
- package/src/overlay/tethered/__stories__/shared/base_story_config.ts +1 -1
- package/src/overlay/tethered/hooks/use_ref_dimensions.ts +32 -0
- package/src/overlay/tethered/hooks/{useTether.ts → use_tether.ts} +7 -2
- package/src/overlay/tethered/hooks/use_tether_origin.ts +46 -0
- package/src/overlay/tethered/hooks/{calculate_origin.ts → utils/calculate_origin.ts} +12 -12
- package/src/overlay/tethered/hooks/{calculate_position.ts → utils/calculate_position.ts} +3 -3
- package/src/overlay/tethered/tethered.tsx +22 -26
- package/src/surfaces/pop_confirm/pop_confirm.stories.tsx +206 -6
- package/src/surfaces/pop_confirm/pop_confirm.tsx +1 -1
- package/src/surfaces/tooltip/__stories__/tooltip.stories.tsx +136 -0
- package/src/surfaces/tooltip/__stories__/tooltip_stories.module.css +14 -0
- package/src/surfaces/tooltip/tooltip.tsx +6 -2
- package/src/themes/themes/ergo/ergo_theme.css +98 -8
- package/dist/overlay/tethered/hooks/calculate_origin.d.ts.map +0 -1
- package/dist/overlay/tethered/hooks/calculate_origin.js +0 -41
- package/dist/overlay/tethered/hooks/calculate_origin.js.map +0 -1
- package/dist/overlay/tethered/hooks/calculate_position.d.ts.map +0 -1
- package/dist/overlay/tethered/hooks/calculate_position.js.map +0 -1
- package/dist/overlay/tethered/hooks/useCaretRefDimensions.d.ts +0 -9
- package/dist/overlay/tethered/hooks/useCaretRefDimensions.d.ts.map +0 -1
- package/dist/overlay/tethered/hooks/useCaretRefDimensions.js +0 -14
- package/dist/overlay/tethered/hooks/useCaretRefDimensions.js.map +0 -1
- package/dist/overlay/tethered/hooks/useTether.d.ts.map +0 -1
- package/dist/overlay/tethered/hooks/useTether.js.map +0 -1
- package/dist/overlay/tethered/hooks/useTetherContentRect.d.ts +0 -3
- package/dist/overlay/tethered/hooks/useTetherContentRect.d.ts.map +0 -1
- package/dist/overlay/tethered/hooks/useTetherContentRect.js +0 -36
- package/dist/overlay/tethered/hooks/useTetherContentRect.js.map +0 -1
- package/dist/overlay/tethered/hooks/useTetherOrigin.d.ts +0 -14
- package/dist/overlay/tethered/hooks/useTetherOrigin.d.ts.map +0 -1
- package/dist/overlay/tethered/hooks/useTetherOrigin.js +0 -24
- package/dist/overlay/tethered/hooks/useTetherOrigin.js.map +0 -1
- package/dist/surfaces/popconfirm/pop_confirm.d.ts +0 -5
- package/dist/surfaces/popconfirm/pop_confirm.d.ts.map +0 -1
- package/dist/surfaces/popconfirm/pop_confirm.js +0 -13
- package/dist/surfaces/popconfirm/pop_confirm.js.map +0 -1
- package/src/overlay/tethered/hooks/useCaretRefDimensions.ts +0 -22
- package/src/overlay/tethered/hooks/useTetherContentRect.ts +0 -49
- package/src/overlay/tethered/hooks/useTetherOrigin.ts +0 -49
- package/src/surfaces/popconfirm/pop_confirm.tsx +0 -18
- package/src/surfaces/tooltip/tooltip.stories.tsx +0 -54
- /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
|
-
|
|
133
|
-
|
|
134
|
-
|
|
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
|
-
|
|
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
|
-
|
|
142
|
-
{
|
|
143
|
-
|
|
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(
|
|
420
|
+
const newMatches = getMatchedOptions(internalValue, MAX_RESULTS);
|
|
407
421
|
setMatchedOptions(newMatches);
|
|
408
|
-
}, [
|
|
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=
|
|
416
|
-
verticalOrigin=
|
|
417
|
-
verticalOffset={
|
|
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=
|
|
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
|
|
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={
|
|
39
|
+
acceptedRefs={effectiveAcceptedRefs}
|
|
36
40
|
isException={isException}
|
|
37
41
|
>
|
|
38
|
-
<Tethered ref={
|
|
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={
|
|
40
|
+
acceptedRefs={effectiveAcceptedRefs}
|
|
35
41
|
veil={veil}
|
|
36
42
|
>
|
|
37
|
-
<ElementTethered
|
|
43
|
+
<ElementTethered
|
|
44
|
+
ref={mergedRef}
|
|
45
|
+
anchorElement={anchorElement}
|
|
46
|
+
{...elementTetheredProps}
|
|
47
|
+
>
|
|
38
48
|
{children}
|
|
39
49
|
</ElementTethered>
|
|
40
50
|
</BasePopper>
|
|
@@ -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 {
|
|
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 {
|
|
2
|
-
import type { HorizontalTether, VerticalTether } from '
|
|
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
|
-
|
|
11
|
+
tetherDimensions: Dimensions,
|
|
12
12
|
hOrigin: HorizontalTether,
|
|
13
13
|
vOrigin: VerticalTether
|
|
14
|
-
):
|
|
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 =
|
|
24
|
+
yOffset = tetherDimensions.height / 2;
|
|
25
25
|
break;
|
|
26
26
|
case 'bottom':
|
|
27
|
-
yOffset =
|
|
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 =
|
|
37
|
+
xOffset = tetherDimensions.width / 2;
|
|
38
38
|
break;
|
|
39
39
|
case 'end':
|
|
40
|
-
xOffset =
|
|
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:
|
|
52
|
+
offset: Position
|
|
53
53
|
): Position {
|
|
54
54
|
return {
|
|
55
|
-
x: baselinePosition.x + offset.
|
|
56
|
-
y: baselinePosition.y + offset.
|
|
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 '
|
|
2
|
-
import type { Rectangle } from '
|
|
3
|
-
import type { Dimensions } from '
|
|
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 {
|
|
8
|
-
import {
|
|
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 {
|
|
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
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
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
|
|
65
|
+
const forkedRef = useForkRef(ref, tetherRef);
|
|
70
66
|
|
|
71
67
|
const cssVariables = {
|
|
72
|
-
'--tethered-top': `${position.
|
|
73
|
-
'--tethered-left': `${position.
|
|
74
|
-
'--tethered-origin-y': `${
|
|
75
|
-
'--tethered-origin-x': `${
|
|
76
|
-
'--tethered-origin-delta-y': `${
|
|
77
|
-
'--tethered-origin-delta-x': `${
|
|
78
|
-
'--origin-indicator-dimensions-width': `${
|
|
79
|
-
'--origin-indicator-dimensions-height': `${
|
|
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={
|
|
88
|
+
data-anchor-direction={origin.direction}
|
|
93
89
|
{...rest}
|
|
94
90
|
>
|
|
95
91
|
{children}
|
|
96
|
-
{precision === 'high' &&
|
|
92
|
+
{precision === 'high' && origin.direction !== 'none' && (
|
|
97
93
|
<Caret
|
|
98
|
-
ref={
|
|
99
|
-
direction={
|
|
94
|
+
ref={originRef}
|
|
95
|
+
direction={origin.direction}
|
|
100
96
|
className={clsx(
|
|
101
97
|
styles['origin-indicator'],
|
|
102
98
|
'tcn-tethered-origin-indicator'
|