tyrell-react 1.0.0-TC24 → 1.0.0-TC26
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/components/TyCalendar.d.ts +6 -0
- package/dist/components/TyCalendar.d.ts.map +1 -1
- package/dist/components/TyCalendar.js +16 -2
- package/dist/components/TyCalendar.js.map +1 -1
- package/dist/components/TyCalendarMonth.d.ts +4 -0
- package/dist/components/TyCalendarMonth.d.ts.map +1 -1
- package/dist/components/TyCalendarMonth.js +5 -1
- package/dist/components/TyCalendarMonth.js.map +1 -1
- package/dist/components/TyCalendarNavigation.d.ts +4 -0
- package/dist/components/TyCalendarNavigation.d.ts.map +1 -1
- package/dist/components/TyCalendarNavigation.js +5 -1
- package/dist/components/TyCalendarNavigation.js.map +1 -1
- package/dist/components/TyCheckbox.d.ts +2 -0
- package/dist/components/TyCheckbox.d.ts.map +1 -1
- package/dist/components/TyCheckbox.js +4 -1
- package/dist/components/TyCheckbox.js.map +1 -1
- package/dist/components/TyCopy.d.ts +4 -0
- package/dist/components/TyCopy.d.ts.map +1 -1
- package/dist/components/TyCopy.js +19 -1
- package/dist/components/TyCopy.js.map +1 -1
- package/dist/components/TyDatePicker.d.ts +4 -0
- package/dist/components/TyDatePicker.d.ts.map +1 -1
- package/dist/components/TyDatePicker.js +7 -1
- package/dist/components/TyDatePicker.js.map +1 -1
- package/dist/components/TyDropdown.d.ts +2 -0
- package/dist/components/TyDropdown.d.ts.map +1 -1
- package/dist/components/TyDropdown.js +14 -2
- package/dist/components/TyDropdown.js.map +1 -1
- package/dist/components/TyMultiselect.d.ts +2 -0
- package/dist/components/TyMultiselect.d.ts.map +1 -1
- package/dist/components/TyMultiselect.js +14 -2
- package/dist/components/TyMultiselect.js.map +1 -1
- package/dist/components/TyOption.d.ts +8 -0
- package/dist/components/TyOption.d.ts.map +1 -1
- package/dist/components/TyOption.js +3 -1
- package/dist/components/TyOption.js.map +1 -1
- package/dist/components/TyScrollContainer.d.ts +26 -1
- package/dist/components/TyScrollContainer.d.ts.map +1 -1
- package/dist/components/TyScrollContainer.js +41 -24
- package/dist/components/TyScrollContainer.js.map +1 -1
- package/dist/components/TySelect.d.ts +75 -0
- package/dist/components/TySelect.d.ts.map +1 -0
- package/dist/components/TySelect.js +134 -0
- package/dist/components/TySelect.js.map +1 -0
- package/dist/components/index.d.ts +6 -0
- package/dist/components/index.d.ts.map +1 -1
- package/dist/components/index.js +5 -0
- package/dist/components/index.js.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +1 -1
- package/src/components/TyCalendar.tsx +30 -1
- package/src/components/TyCalendarMonth.tsx +11 -1
- package/src/components/TyCalendarNavigation.tsx +11 -1
- package/src/components/TyCheckbox.tsx +8 -2
- package/src/components/TyCopy.tsx +25 -1
- package/src/components/TyDatePicker.tsx +16 -0
- package/src/components/TyDropdown.tsx +14 -2
- package/src/components/TyMultiselect.tsx +12 -1
- package/src/components/TyOption.tsx +14 -1
- package/src/components/TyScrollContainer.tsx +74 -24
- package/src/components/TySelect.tsx +240 -0
- package/src/components/index.ts +9 -0
- package/src/version.ts +1 -1
- package/src/components/EventConventionTest.tsx +0 -155
|
@@ -22,7 +22,13 @@ export interface TyCalendarMonthProps extends React.HTMLAttributes<HTMLElement>
|
|
|
22
22
|
|
|
23
23
|
/** Maximum width */
|
|
24
24
|
maxWidth?: string;
|
|
25
|
-
|
|
25
|
+
|
|
26
|
+
/** Earliest enabled date (ISO "YYYY-MM-DD") — earlier days disabled */
|
|
27
|
+
min?: string;
|
|
28
|
+
|
|
29
|
+
/** Latest enabled date (ISO "YYYY-MM-DD") — later days disabled */
|
|
30
|
+
max?: string;
|
|
31
|
+
|
|
26
32
|
/** Day click event handler */
|
|
27
33
|
onDayClick?: (event: CustomEvent<DayClickDetail>) => void;
|
|
28
34
|
}
|
|
@@ -49,6 +55,8 @@ export const TyCalendarMonth = React.forwardRef<HTMLElement, TyCalendarMonthProp
|
|
|
49
55
|
width,
|
|
50
56
|
minWidth,
|
|
51
57
|
maxWidth,
|
|
58
|
+
min,
|
|
59
|
+
max,
|
|
52
60
|
onDayClick,
|
|
53
61
|
...props
|
|
54
62
|
}, ref) => {
|
|
@@ -95,6 +103,8 @@ export const TyCalendarMonth = React.forwardRef<HTMLElement, TyCalendarMonthProp
|
|
|
95
103
|
if (locale) webComponentProps.locale = locale;
|
|
96
104
|
if (size) webComponentProps.size = size;
|
|
97
105
|
if (width) webComponentProps.width = width;
|
|
106
|
+
if (min) webComponentProps.min = min;
|
|
107
|
+
if (max) webComponentProps.max = max;
|
|
98
108
|
if (minWidth) webComponentProps['min-width'] = minWidth;
|
|
99
109
|
if (maxWidth) webComponentProps['max-width'] = maxWidth;
|
|
100
110
|
|
|
@@ -16,7 +16,13 @@ export interface TyCalendarNavigationProps extends Omit<React.HTMLAttributes<HTM
|
|
|
16
16
|
|
|
17
17
|
/** Width of navigation */
|
|
18
18
|
width?: string;
|
|
19
|
-
|
|
19
|
+
|
|
20
|
+
/** Earliest reachable month (ISO "YYYY-MM-DD") — navigation clamps here */
|
|
21
|
+
min?: string;
|
|
22
|
+
|
|
23
|
+
/** Latest reachable month (ISO "YYYY-MM-DD") — navigation clamps here */
|
|
24
|
+
max?: string;
|
|
25
|
+
|
|
20
26
|
/** Navigation change event handler */
|
|
21
27
|
onChange?: (event: CustomEvent<NavigationChangeDetail>) => void;
|
|
22
28
|
}
|
|
@@ -34,6 +40,8 @@ export const TyCalendarNavigation = React.forwardRef<HTMLElement, TyCalendarNavi
|
|
|
34
40
|
locale,
|
|
35
41
|
size,
|
|
36
42
|
width,
|
|
43
|
+
min,
|
|
44
|
+
max,
|
|
37
45
|
onChange,
|
|
38
46
|
...props
|
|
39
47
|
}, ref) => {
|
|
@@ -80,6 +88,8 @@ export const TyCalendarNavigation = React.forwardRef<HTMLElement, TyCalendarNavi
|
|
|
80
88
|
if (locale) webComponentProps.locale = locale;
|
|
81
89
|
if (size) webComponentProps.size = size;
|
|
82
90
|
if (width) webComponentProps.width = width;
|
|
91
|
+
if (min) webComponentProps.min = min;
|
|
92
|
+
if (max) webComponentProps.max = max;
|
|
83
93
|
|
|
84
94
|
return React.createElement(
|
|
85
95
|
'ty-calendar-navigation',
|
|
@@ -5,7 +5,10 @@ import { useBooleanProperty } from '../utils/use-boolean-prop';
|
|
|
5
5
|
export interface TyCheckboxProps extends Omit<React.HTMLAttributes<HTMLElement>, 'onChange' | 'onInput'> {
|
|
6
6
|
/** Checked state */
|
|
7
7
|
checked?: boolean;
|
|
8
|
-
|
|
8
|
+
|
|
9
|
+
/** Mixed state (dash) — visual/ARIA only; clicking resolves to checked */
|
|
10
|
+
indeterminate?: boolean;
|
|
11
|
+
|
|
9
12
|
/** Form field value when checked */
|
|
10
13
|
value?: string;
|
|
11
14
|
|
|
@@ -53,8 +56,9 @@ export interface TyCheckboxEventDetail {
|
|
|
53
56
|
// React wrapper for ty-checkbox web component
|
|
54
57
|
export const TyCheckbox = React.forwardRef<HTMLElement, TyCheckboxProps>(
|
|
55
58
|
({
|
|
56
|
-
children,
|
|
59
|
+
children,
|
|
57
60
|
checked,
|
|
61
|
+
indeterminate,
|
|
58
62
|
value,
|
|
59
63
|
name,
|
|
60
64
|
disabled,
|
|
@@ -115,6 +119,7 @@ export const TyCheckbox = React.forwardRef<HTMLElement, TyCheckboxProps>(
|
|
|
115
119
|
// doesn't reliably remove them when the prop flips back to false on a
|
|
116
120
|
// custom element. React 19+ handles this natively.
|
|
117
121
|
const isChecked = useBooleanProperty(elementRef, 'checked', checked);
|
|
122
|
+
const isIndeterminate = useBooleanProperty(elementRef, 'indeterminate', indeterminate);
|
|
118
123
|
const isDisabled = useBooleanProperty(elementRef, 'disabled', disabled);
|
|
119
124
|
const isRequired = useBooleanProperty(elementRef, 'required', required);
|
|
120
125
|
|
|
@@ -126,6 +131,7 @@ export const TyCheckbox = React.forwardRef<HTMLElement, TyCheckboxProps>(
|
|
|
126
131
|
|
|
127
132
|
// Add boolean attributes
|
|
128
133
|
if (isChecked) webComponentProps.checked = '';
|
|
134
|
+
if (isIndeterminate) webComponentProps.indeterminate = '';
|
|
129
135
|
if (isDisabled) webComponentProps.disabled = '';
|
|
130
136
|
if (isRequired) webComponentProps.required = '';
|
|
131
137
|
|
|
@@ -26,6 +26,12 @@ export interface TyCopyProps extends Omit<React.HTMLAttributes<HTMLElement>, 'on
|
|
|
26
26
|
|
|
27
27
|
/** Required field */
|
|
28
28
|
required?: boolean;
|
|
29
|
+
|
|
30
|
+
/** Fired after the value is copied to the clipboard */
|
|
31
|
+
onCopySuccess?: (event: CustomEvent) => void;
|
|
32
|
+
|
|
33
|
+
/** Fired when copying to the clipboard fails */
|
|
34
|
+
onCopyError?: (event: CustomEvent) => void;
|
|
29
35
|
}
|
|
30
36
|
|
|
31
37
|
// React wrapper for ty-copy web component
|
|
@@ -39,7 +45,9 @@ export const TyCopy = React.forwardRef<HTMLElement, TyCopyProps>(
|
|
|
39
45
|
multiline,
|
|
40
46
|
disabled,
|
|
41
47
|
required,
|
|
42
|
-
|
|
48
|
+
onCopySuccess,
|
|
49
|
+
onCopyError,
|
|
50
|
+
...props
|
|
43
51
|
}, ref) => {
|
|
44
52
|
const elementRef = useRef<HTMLElement>(null);
|
|
45
53
|
|
|
@@ -54,6 +62,22 @@ export const TyCopy = React.forwardRef<HTMLElement, TyCopyProps>(
|
|
|
54
62
|
}
|
|
55
63
|
}, [ref]);
|
|
56
64
|
|
|
65
|
+
// Custom events → React callbacks
|
|
66
|
+
useEffect(() => {
|
|
67
|
+
const el = elementRef.current;
|
|
68
|
+
if (!el) return;
|
|
69
|
+
const bound: Array<[string, EventListener]> = [];
|
|
70
|
+
if (onCopySuccess) {
|
|
71
|
+
const h = (e: Event) => onCopySuccess(e as CustomEvent);
|
|
72
|
+
el.addEventListener('copy-success', h); bound.push(['copy-success', h]);
|
|
73
|
+
}
|
|
74
|
+
if (onCopyError) {
|
|
75
|
+
const h = (e: Event) => onCopyError(e as CustomEvent);
|
|
76
|
+
el.addEventListener('copy-error', h); bound.push(['copy-error', h]);
|
|
77
|
+
}
|
|
78
|
+
return () => bound.forEach(([n, h]) => el.removeEventListener(n, h));
|
|
79
|
+
}, [onCopySuccess, onCopyError]);
|
|
80
|
+
|
|
57
81
|
const isMultiline = useBooleanProperty(elementRef, 'multiline', multiline);
|
|
58
82
|
const isDisabled = useBooleanProperty(elementRef, 'disabled', disabled);
|
|
59
83
|
const isRequired = useBooleanProperty(elementRef, 'required', required);
|
|
@@ -50,6 +50,12 @@ export interface TyDatePickerProps extends Omit<React.HTMLAttributes<HTMLElement
|
|
|
50
50
|
|
|
51
51
|
/** Whether to include time selection */
|
|
52
52
|
withTime?: boolean;
|
|
53
|
+
|
|
54
|
+
/** Earliest selectable date (ISO "YYYY-MM-DD") */
|
|
55
|
+
min?: string;
|
|
56
|
+
|
|
57
|
+
/** Latest selectable date (ISO "YYYY-MM-DD") */
|
|
58
|
+
max?: string;
|
|
53
59
|
|
|
54
60
|
/** Callback when the date value changes */
|
|
55
61
|
onChange?: (event: CustomEvent<TyDatePickerEventDetail>) => void;
|
|
@@ -76,6 +82,8 @@ export const TyDatePicker = React.forwardRef<HTMLElement, TyDatePickerProps>(
|
|
|
76
82
|
format,
|
|
77
83
|
locale,
|
|
78
84
|
withTime,
|
|
85
|
+
min,
|
|
86
|
+
max,
|
|
79
87
|
onChange,
|
|
80
88
|
onOpen,
|
|
81
89
|
onClose,
|
|
@@ -208,6 +216,14 @@ export const TyDatePicker = React.forwardRef<HTMLElement, TyDatePickerProps>(
|
|
|
208
216
|
webComponentProps['with-time'] = ''; // Convert camelCase to kebab-case
|
|
209
217
|
}
|
|
210
218
|
|
|
219
|
+
if (min) {
|
|
220
|
+
webComponentProps.min = min;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
if (max) {
|
|
224
|
+
webComponentProps.max = max;
|
|
225
|
+
}
|
|
226
|
+
|
|
211
227
|
return React.createElement('ty-date-picker', webComponentProps);
|
|
212
228
|
}
|
|
213
229
|
);
|
|
@@ -80,7 +80,9 @@ export interface TyDropdownProps extends Omit<React.HTMLAttributes<HTMLElement>,
|
|
|
80
80
|
// React event handlers
|
|
81
81
|
onChange?: (event: CustomEvent<TyDropdownEventDetail>) => void;
|
|
82
82
|
onSearch?: (event: CustomEvent<{ query: string; element: HTMLElement }>) => void;
|
|
83
|
-
|
|
83
|
+
onOpen?: (event: CustomEvent) => void;
|
|
84
|
+
onClose?: (event: CustomEvent) => void;
|
|
85
|
+
|
|
84
86
|
// Children for slotted approach
|
|
85
87
|
children?: React.ReactNode;
|
|
86
88
|
}
|
|
@@ -92,6 +94,8 @@ export const TyDropdown = React.forwardRef<HTMLElement, TyDropdownProps>(
|
|
|
92
94
|
children,
|
|
93
95
|
onChange,
|
|
94
96
|
onSearch,
|
|
97
|
+
onOpen,
|
|
98
|
+
onClose,
|
|
95
99
|
disabled,
|
|
96
100
|
loading,
|
|
97
101
|
externalSearch,
|
|
@@ -118,6 +122,9 @@ export const TyDropdown = React.forwardRef<HTMLElement, TyDropdownProps>(
|
|
|
118
122
|
}
|
|
119
123
|
}, [onSearch]);
|
|
120
124
|
|
|
125
|
+
const handleOpen = useCallback((event: Event) => { if (onOpen) onOpen(event as CustomEvent); }, [onOpen]);
|
|
126
|
+
const handleClose = useCallback((event: Event) => { if (onClose) onClose(event as CustomEvent); }, [onClose]);
|
|
127
|
+
|
|
121
128
|
useEffect(() => {
|
|
122
129
|
const element = elementRef.current;
|
|
123
130
|
if (!element) return;
|
|
@@ -131,6 +138,9 @@ export const TyDropdown = React.forwardRef<HTMLElement, TyDropdownProps>(
|
|
|
131
138
|
element.addEventListener('search', handleSearch as EventListener);
|
|
132
139
|
}
|
|
133
140
|
|
|
141
|
+
if (onOpen) element.addEventListener('open', handleOpen);
|
|
142
|
+
if (onClose) element.addEventListener('close', handleClose);
|
|
143
|
+
|
|
134
144
|
return () => {
|
|
135
145
|
if (onChange) {
|
|
136
146
|
element.removeEventListener('change', handleChange as EventListener);
|
|
@@ -138,8 +148,10 @@ export const TyDropdown = React.forwardRef<HTMLElement, TyDropdownProps>(
|
|
|
138
148
|
if (onSearch) {
|
|
139
149
|
element.removeEventListener('search', handleSearch as EventListener);
|
|
140
150
|
}
|
|
151
|
+
if (onOpen) element.removeEventListener('open', handleOpen);
|
|
152
|
+
if (onClose) element.removeEventListener('close', handleClose);
|
|
141
153
|
};
|
|
142
|
-
}, [handleChange, handleSearch, onChange, onSearch]);
|
|
154
|
+
}, [handleChange, handleSearch, handleOpen, handleClose, onChange, onSearch, onOpen, onClose]);
|
|
143
155
|
|
|
144
156
|
// Imperatively sync `value` to the underlying element's property whenever
|
|
145
157
|
// it changes. React 18's prop-to-property bridging for custom elements is
|
|
@@ -65,6 +65,8 @@ export interface TyMultiselectProps extends Omit<React.HTMLAttributes<HTMLElemen
|
|
|
65
65
|
|
|
66
66
|
/** Callback fired on each search input change (debounced by `debounce`). Use for external/server-side filtering. */
|
|
67
67
|
onSearch?: (event: CustomEvent<{ query: string; element: HTMLElement }>) => void;
|
|
68
|
+
onOpen?: (event: CustomEvent) => void;
|
|
69
|
+
onClose?: (event: CustomEvent) => void;
|
|
68
70
|
|
|
69
71
|
/** Children should be TyTag components, not TyOption */
|
|
70
72
|
children?: React.ReactNode;
|
|
@@ -87,6 +89,8 @@ export const TyMultiselect = React.forwardRef<HTMLElement, TyMultiselectProps>(
|
|
|
87
89
|
name,
|
|
88
90
|
onChange,
|
|
89
91
|
onSearch,
|
|
92
|
+
onOpen,
|
|
93
|
+
onClose,
|
|
90
94
|
children,
|
|
91
95
|
...props
|
|
92
96
|
}, ref) => {
|
|
@@ -131,6 +135,9 @@ export const TyMultiselect = React.forwardRef<HTMLElement, TyMultiselectProps>(
|
|
|
131
135
|
}
|
|
132
136
|
}, [onSearch]);
|
|
133
137
|
|
|
138
|
+
const handleOpen = useCallback((event: Event) => { if (onOpen) onOpen(event as CustomEvent); }, [onOpen]);
|
|
139
|
+
const handleClose = useCallback((event: Event) => { if (onClose) onClose(event as CustomEvent); }, [onClose]);
|
|
140
|
+
|
|
134
141
|
// Set up event listeners
|
|
135
142
|
useEffect(() => {
|
|
136
143
|
const element = elementRef.current;
|
|
@@ -138,12 +145,16 @@ export const TyMultiselect = React.forwardRef<HTMLElement, TyMultiselectProps>(
|
|
|
138
145
|
|
|
139
146
|
if (onChange) element.addEventListener('change', handleChange);
|
|
140
147
|
if (onSearch) element.addEventListener('search', handleSearch);
|
|
148
|
+
if (onOpen) element.addEventListener('open', handleOpen);
|
|
149
|
+
if (onClose) element.addEventListener('close', handleClose);
|
|
141
150
|
|
|
142
151
|
return () => {
|
|
143
152
|
if (onChange) element.removeEventListener('change', handleChange);
|
|
144
153
|
if (onSearch) element.removeEventListener('search', handleSearch);
|
|
154
|
+
if (onOpen) element.removeEventListener('open', handleOpen);
|
|
155
|
+
if (onClose) element.removeEventListener('close', handleClose);
|
|
145
156
|
};
|
|
146
|
-
}, [handleChange, handleSearch, onChange, onSearch]);
|
|
157
|
+
}, [handleChange, handleSearch, handleOpen, handleClose, onChange, onSearch, onOpen, onClose]);
|
|
147
158
|
|
|
148
159
|
// Imperative property sync for boolean props (see use-boolean-prop.ts).
|
|
149
160
|
const isDisabled = useBooleanProperty(elementRef, 'disabled', disabled);
|
|
@@ -4,6 +4,17 @@ import { useBooleanProperty } from '../utils/use-boolean-prop';
|
|
|
4
4
|
// Type definitions for Ty Option component
|
|
5
5
|
export interface TyOptionProps extends React.HTMLAttributes<HTMLElement> {
|
|
6
6
|
value?: string;
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Clean display text (native <option label> semantics) — used by ty-select
|
|
10
|
+
* for field summaries and by ty-selected-tags chips when the option's
|
|
11
|
+
* children are rich HTML. data-* attributes feed chip templates.
|
|
12
|
+
*/
|
|
13
|
+
label?: string;
|
|
14
|
+
|
|
15
|
+
/** Semantic flavor — carried onto ty-selected-tags chips */
|
|
16
|
+
flavor?: 'primary' | 'secondary' | 'success' | 'danger' | 'warning' | 'neutral' | 'info';
|
|
17
|
+
|
|
7
18
|
disabled?: boolean;
|
|
8
19
|
selected?: boolean;
|
|
9
20
|
hidden?: boolean;
|
|
@@ -12,7 +23,7 @@ export interface TyOptionProps extends React.HTMLAttributes<HTMLElement> {
|
|
|
12
23
|
|
|
13
24
|
// React wrapper for ty-option web component
|
|
14
25
|
export const TyOption = React.forwardRef<HTMLElement, TyOptionProps>(
|
|
15
|
-
({ children, disabled, selected, hidden, ...props }, ref) => {
|
|
26
|
+
({ children, label, flavor, disabled, selected, hidden, ...props }, ref) => {
|
|
16
27
|
const elementRef = useRef<HTMLElement>(null);
|
|
17
28
|
|
|
18
29
|
// Handle ref forwarding
|
|
@@ -34,6 +45,8 @@ export const TyOption = React.forwardRef<HTMLElement, TyOptionProps>(
|
|
|
34
45
|
'ty-option',
|
|
35
46
|
{
|
|
36
47
|
...props,
|
|
48
|
+
...(label && { label }),
|
|
49
|
+
...(flavor && { flavor }),
|
|
37
50
|
...(isDisabled && { disabled: "" }),
|
|
38
51
|
...(isSelected && { selected: "" }),
|
|
39
52
|
...(isHidden && { hidden: "" }),
|
|
@@ -2,6 +2,14 @@ import React, { useEffect, useRef, useImperativeHandle } from 'react';
|
|
|
2
2
|
import { needsPropertyBridge } from '../utils/react-version';
|
|
3
3
|
import { useBooleanProperty, coerceBool } from '../utils/use-boolean-prop';
|
|
4
4
|
|
|
5
|
+
/** Detail payload for nearstart / nearend events. */
|
|
6
|
+
export interface TyScrollNearEdgeDetail {
|
|
7
|
+
distance: number;
|
|
8
|
+
scrollTop: number;
|
|
9
|
+
scrollHeight: number;
|
|
10
|
+
clientHeight: number;
|
|
11
|
+
}
|
|
12
|
+
|
|
5
13
|
// Type definitions for Ty ScrollContainer component
|
|
6
14
|
export interface TyScrollContainerProps extends React.HTMLAttributes<HTMLElement> {
|
|
7
15
|
/** Maximum height of the scroll container */
|
|
@@ -10,9 +18,27 @@ export interface TyScrollContainerProps extends React.HTMLAttributes<HTMLElement
|
|
|
10
18
|
/** Enable/disable scroll shadows (default: true) */
|
|
11
19
|
shadow?: boolean;
|
|
12
20
|
|
|
13
|
-
/** Hide native scrollbar */
|
|
21
|
+
/** Hide native scrollbar (no custom scrollbar) */
|
|
14
22
|
hideScrollbar?: boolean;
|
|
15
23
|
|
|
24
|
+
/** Use the styled custom scrollbar */
|
|
25
|
+
customScrollbar?: boolean;
|
|
26
|
+
|
|
27
|
+
/** Enable horizontal scrolling */
|
|
28
|
+
overflowX?: boolean;
|
|
29
|
+
|
|
30
|
+
/** Preserve visual position when content is prepended above the viewport */
|
|
31
|
+
scrollAnchoring?: boolean;
|
|
32
|
+
|
|
33
|
+
/** Distance (px) from an edge at which nearstart/nearend fire (default 100) */
|
|
34
|
+
nearEdgeThreshold?: number;
|
|
35
|
+
|
|
36
|
+
/** Fired once when scrolled within the threshold of the bottom */
|
|
37
|
+
onNearEnd?: (event: CustomEvent<TyScrollNearEdgeDetail>) => void;
|
|
38
|
+
|
|
39
|
+
/** Fired once when scrolled within the threshold of the top */
|
|
40
|
+
onNearStart?: (event: CustomEvent<TyScrollNearEdgeDetail>) => void;
|
|
41
|
+
|
|
16
42
|
/** Content to scroll */
|
|
17
43
|
children?: React.ReactNode;
|
|
18
44
|
}
|
|
@@ -25,6 +51,12 @@ export interface TyScrollContainerRef {
|
|
|
25
51
|
scrollToTop: (smooth?: boolean) => void;
|
|
26
52
|
/** Scroll to bottom */
|
|
27
53
|
scrollToBottom: (smooth?: boolean) => void;
|
|
54
|
+
/** Scroll to far left */
|
|
55
|
+
scrollToLeft: (smooth?: boolean) => void;
|
|
56
|
+
/** Scroll to far right */
|
|
57
|
+
scrollToRight: (smooth?: boolean) => void;
|
|
58
|
+
/** Scroll a descendant (element or CSS selector) into view */
|
|
59
|
+
scrollToElement: (target: Element | string, smooth?: boolean) => void;
|
|
28
60
|
/** Get the underlying scroll element */
|
|
29
61
|
scrollElement: HTMLElement | null;
|
|
30
62
|
/** Get the native element */
|
|
@@ -38,35 +70,29 @@ export const TyScrollContainer = React.forwardRef<TyScrollContainerRef, TyScroll
|
|
|
38
70
|
maxHeight,
|
|
39
71
|
shadow,
|
|
40
72
|
hideScrollbar,
|
|
73
|
+
customScrollbar,
|
|
74
|
+
overflowX,
|
|
75
|
+
scrollAnchoring,
|
|
76
|
+
nearEdgeThreshold,
|
|
77
|
+
onNearEnd,
|
|
78
|
+
onNearStart,
|
|
41
79
|
...props
|
|
42
80
|
}, ref) => {
|
|
43
81
|
const elementRef = useRef<HTMLElement>(null);
|
|
44
82
|
|
|
45
83
|
// Expose imperative methods via ref
|
|
46
84
|
useImperativeHandle(ref, () => ({
|
|
47
|
-
updateShadows: () => {
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
},
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
}
|
|
55
|
-
scrollToBottom: (smooth = true) => {
|
|
56
|
-
const el = elementRef.current as any;
|
|
57
|
-
el?.scrollToBottom?.(smooth);
|
|
58
|
-
},
|
|
59
|
-
get scrollElement() {
|
|
60
|
-
const el = elementRef.current as any;
|
|
61
|
-
return el?.scrollElement ?? null;
|
|
62
|
-
},
|
|
63
|
-
get element() {
|
|
64
|
-
return elementRef.current;
|
|
65
|
-
}
|
|
85
|
+
updateShadows: () => { (elementRef.current as any)?.updateShadows?.(); },
|
|
86
|
+
scrollToTop: (smooth = true) => { (elementRef.current as any)?.scrollToTop?.(smooth); },
|
|
87
|
+
scrollToBottom: (smooth = true) => { (elementRef.current as any)?.scrollToBottom?.(smooth); },
|
|
88
|
+
scrollToLeft: (smooth = true) => { (elementRef.current as any)?.scrollToLeft?.(smooth); },
|
|
89
|
+
scrollToRight: (smooth = true) => { (elementRef.current as any)?.scrollToRight?.(smooth); },
|
|
90
|
+
scrollToElement: (target, smooth = true) => { (elementRef.current as any)?.scrollToElement?.(target, smooth); },
|
|
91
|
+
get scrollElement() { return (elementRef.current as any)?.scrollElement ?? null; },
|
|
92
|
+
get element() { return elementRef.current; }
|
|
66
93
|
}), []);
|
|
67
94
|
|
|
68
|
-
// shadow defaults to true;
|
|
69
|
-
// attribute level. Bridge it imperatively so flipping back to true
|
|
95
|
+
// shadow defaults to true; bridge it imperatively so flipping back to true
|
|
70
96
|
// propagates on React 18.
|
|
71
97
|
useEffect(() => {
|
|
72
98
|
if (!needsPropertyBridge) return;
|
|
@@ -76,7 +102,27 @@ export const TyScrollContainer = React.forwardRef<TyScrollContainerRef, TyScroll
|
|
|
76
102
|
const next = coerceBool(shadow);
|
|
77
103
|
if (Boolean(el.shadow) !== next) el.shadow = next;
|
|
78
104
|
}, [shadow]);
|
|
105
|
+
|
|
79
106
|
const isHideScrollbar = useBooleanProperty(elementRef, 'hideScrollbar', hideScrollbar);
|
|
107
|
+
const isCustomScrollbar = useBooleanProperty(elementRef, 'customScrollbar', customScrollbar);
|
|
108
|
+
const isOverflowX = useBooleanProperty(elementRef, 'overflowX', overflowX);
|
|
109
|
+
const isScrollAnchoring = useBooleanProperty(elementRef, 'scrollAnchoring', scrollAnchoring);
|
|
110
|
+
|
|
111
|
+
// Custom events → React callbacks
|
|
112
|
+
useEffect(() => {
|
|
113
|
+
const el = elementRef.current;
|
|
114
|
+
if (!el) return;
|
|
115
|
+
const bound: Array<[string, EventListener]> = [];
|
|
116
|
+
if (onNearEnd) {
|
|
117
|
+
const h = (e: Event) => onNearEnd(e as CustomEvent<TyScrollNearEdgeDetail>);
|
|
118
|
+
el.addEventListener('nearend', h); bound.push(['nearend', h]);
|
|
119
|
+
}
|
|
120
|
+
if (onNearStart) {
|
|
121
|
+
const h = (e: Event) => onNearStart(e as CustomEvent<TyScrollNearEdgeDetail>);
|
|
122
|
+
el.addEventListener('nearstart', h); bound.push(['nearstart', h]);
|
|
123
|
+
}
|
|
124
|
+
return () => bound.forEach(([n, h]) => el.removeEventListener(n, h));
|
|
125
|
+
}, [onNearEnd, onNearStart]);
|
|
80
126
|
|
|
81
127
|
// Convert React props to web component attributes
|
|
82
128
|
const webComponentProps: Record<string, any> = {
|
|
@@ -84,12 +130,16 @@ export const TyScrollContainer = React.forwardRef<TyScrollContainerRef, TyScroll
|
|
|
84
130
|
ref: elementRef,
|
|
85
131
|
};
|
|
86
132
|
|
|
87
|
-
//
|
|
133
|
+
// String attributes
|
|
88
134
|
if (maxHeight) webComponentProps['max-height'] = maxHeight;
|
|
135
|
+
if (nearEdgeThreshold != null) webComponentProps['near-edge-threshold'] = String(nearEdgeThreshold);
|
|
89
136
|
|
|
90
|
-
//
|
|
137
|
+
// Boolean attributes
|
|
91
138
|
if (shadow !== undefined && !coerceBool(shadow)) webComponentProps.shadow = 'false';
|
|
92
139
|
if (isHideScrollbar) webComponentProps['hide-scrollbar'] = '';
|
|
140
|
+
if (isCustomScrollbar) webComponentProps['custom-scrollbar'] = '';
|
|
141
|
+
if (isOverflowX) webComponentProps['overflow-x'] = '';
|
|
142
|
+
if (isScrollAnchoring) webComponentProps['scroll-anchoring'] = '';
|
|
93
143
|
|
|
94
144
|
return React.createElement(
|
|
95
145
|
'ty-scroll-container',
|