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.
Files changed (65) hide show
  1. package/dist/components/TyCalendar.d.ts +6 -0
  2. package/dist/components/TyCalendar.d.ts.map +1 -1
  3. package/dist/components/TyCalendar.js +16 -2
  4. package/dist/components/TyCalendar.js.map +1 -1
  5. package/dist/components/TyCalendarMonth.d.ts +4 -0
  6. package/dist/components/TyCalendarMonth.d.ts.map +1 -1
  7. package/dist/components/TyCalendarMonth.js +5 -1
  8. package/dist/components/TyCalendarMonth.js.map +1 -1
  9. package/dist/components/TyCalendarNavigation.d.ts +4 -0
  10. package/dist/components/TyCalendarNavigation.d.ts.map +1 -1
  11. package/dist/components/TyCalendarNavigation.js +5 -1
  12. package/dist/components/TyCalendarNavigation.js.map +1 -1
  13. package/dist/components/TyCheckbox.d.ts +2 -0
  14. package/dist/components/TyCheckbox.d.ts.map +1 -1
  15. package/dist/components/TyCheckbox.js +4 -1
  16. package/dist/components/TyCheckbox.js.map +1 -1
  17. package/dist/components/TyCopy.d.ts +4 -0
  18. package/dist/components/TyCopy.d.ts.map +1 -1
  19. package/dist/components/TyCopy.js +19 -1
  20. package/dist/components/TyCopy.js.map +1 -1
  21. package/dist/components/TyDatePicker.d.ts +4 -0
  22. package/dist/components/TyDatePicker.d.ts.map +1 -1
  23. package/dist/components/TyDatePicker.js +7 -1
  24. package/dist/components/TyDatePicker.js.map +1 -1
  25. package/dist/components/TyDropdown.d.ts +2 -0
  26. package/dist/components/TyDropdown.d.ts.map +1 -1
  27. package/dist/components/TyDropdown.js +14 -2
  28. package/dist/components/TyDropdown.js.map +1 -1
  29. package/dist/components/TyMultiselect.d.ts +2 -0
  30. package/dist/components/TyMultiselect.d.ts.map +1 -1
  31. package/dist/components/TyMultiselect.js +14 -2
  32. package/dist/components/TyMultiselect.js.map +1 -1
  33. package/dist/components/TyOption.d.ts +8 -0
  34. package/dist/components/TyOption.d.ts.map +1 -1
  35. package/dist/components/TyOption.js +3 -1
  36. package/dist/components/TyOption.js.map +1 -1
  37. package/dist/components/TyScrollContainer.d.ts +26 -1
  38. package/dist/components/TyScrollContainer.d.ts.map +1 -1
  39. package/dist/components/TyScrollContainer.js +41 -24
  40. package/dist/components/TyScrollContainer.js.map +1 -1
  41. package/dist/components/TySelect.d.ts +75 -0
  42. package/dist/components/TySelect.d.ts.map +1 -0
  43. package/dist/components/TySelect.js +134 -0
  44. package/dist/components/TySelect.js.map +1 -0
  45. package/dist/components/index.d.ts +6 -0
  46. package/dist/components/index.d.ts.map +1 -1
  47. package/dist/components/index.js +5 -0
  48. package/dist/components/index.js.map +1 -1
  49. package/dist/version.d.ts +1 -1
  50. package/dist/version.js +1 -1
  51. package/package.json +1 -1
  52. package/src/components/TyCalendar.tsx +30 -1
  53. package/src/components/TyCalendarMonth.tsx +11 -1
  54. package/src/components/TyCalendarNavigation.tsx +11 -1
  55. package/src/components/TyCheckbox.tsx +8 -2
  56. package/src/components/TyCopy.tsx +25 -1
  57. package/src/components/TyDatePicker.tsx +16 -0
  58. package/src/components/TyDropdown.tsx +14 -2
  59. package/src/components/TyMultiselect.tsx +12 -1
  60. package/src/components/TyOption.tsx +14 -1
  61. package/src/components/TyScrollContainer.tsx +74 -24
  62. package/src/components/TySelect.tsx +240 -0
  63. package/src/components/index.ts +9 -0
  64. package/src/version.ts +1 -1
  65. 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
- ...props
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
- const el = elementRef.current as any;
49
- el?.updateShadows?.();
50
- },
51
- scrollToTop: (smooth = true) => {
52
- const el = elementRef.current as any;
53
- el?.scrollToTop?.(smooth);
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; only the explicit-false case matters at the
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
- // Add string attributes
133
+ // String attributes
88
134
  if (maxHeight) webComponentProps['max-height'] = maxHeight;
135
+ if (nearEdgeThreshold != null) webComponentProps['near-edge-threshold'] = String(nearEdgeThreshold);
89
136
 
90
- // Add boolean attributes
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',