tyrell-react 1.0.0-TC11 → 1.0.0-TC17
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/README.md +8 -8
- package/dist/components/TyButton.d.ts +2 -0
- package/dist/components/TyButton.d.ts.map +1 -1
- package/dist/components/TyButton.js +13 -5
- package/dist/components/TyButton.js.map +1 -1
- package/dist/components/TyCalendar.d.ts.map +1 -1
- package/dist/components/TyCalendar.js +13 -7
- package/dist/components/TyCalendar.js.map +1 -1
- package/dist/components/TyCheckbox.d.ts.map +1 -1
- package/dist/components/TyCheckbox.js +11 -14
- package/dist/components/TyCheckbox.js.map +1 -1
- package/dist/components/TyCopy.d.ts.map +1 -1
- package/dist/components/TyCopy.js +7 -3
- package/dist/components/TyCopy.js.map +1 -1
- package/dist/components/TyDatePicker.d.ts.map +1 -1
- package/dist/components/TyDatePicker.js +16 -10
- package/dist/components/TyDatePicker.js.map +1 -1
- package/dist/components/TyDropdown.d.ts +16 -5
- package/dist/components/TyDropdown.d.ts.map +1 -1
- package/dist/components/TyDropdown.js +34 -19
- package/dist/components/TyDropdown.js.map +1 -1
- package/dist/components/TyFileUpload.d.ts +31 -0
- package/dist/components/TyFileUpload.d.ts.map +1 -0
- package/dist/components/TyFileUpload.js +56 -0
- package/dist/components/TyFileUpload.js.map +1 -0
- package/dist/components/TyIcon.d.ts.map +1 -1
- package/dist/components/TyIcon.js +7 -6
- package/dist/components/TyIcon.js.map +1 -1
- package/dist/components/TyInput.d.ts.map +1 -1
- package/dist/components/TyInput.js +15 -4
- package/dist/components/TyInput.js.map +1 -1
- package/dist/components/TyModal.d.ts.map +1 -1
- package/dist/components/TyModal.js +35 -10
- package/dist/components/TyModal.js.map +1 -1
- package/dist/components/TyMultiselect.d.ts +6 -0
- package/dist/components/TyMultiselect.d.ts.map +1 -1
- package/dist/components/TyMultiselect.js +22 -14
- package/dist/components/TyMultiselect.js.map +1 -1
- package/dist/components/TyOption.d.ts.map +1 -1
- package/dist/components/TyOption.js +7 -3
- package/dist/components/TyOption.js.map +1 -1
- package/dist/components/TyPopup.d.ts.map +1 -1
- package/dist/components/TyPopup.js +7 -6
- package/dist/components/TyPopup.js.map +1 -1
- package/dist/components/TyRadio.d.ts.map +1 -1
- package/dist/components/TyRadio.js +6 -2
- package/dist/components/TyRadio.js.map +1 -1
- package/dist/components/TyRadioGroup.d.ts.map +1 -1
- package/dist/components/TyRadioGroup.js +5 -2
- package/dist/components/TyRadioGroup.js.map +1 -1
- package/dist/components/TyScrollContainer.d.ts.map +1 -1
- package/dist/components/TyScrollContainer.js +22 -4
- package/dist/components/TyScrollContainer.js.map +1 -1
- package/dist/components/TySwitch.d.ts.map +1 -1
- package/dist/components/TySwitch.js +8 -3
- package/dist/components/TySwitch.js.map +1 -1
- package/dist/components/TyTab.d.ts.map +1 -1
- package/dist/components/TyTab.js +3 -1
- package/dist/components/TyTab.js.map +1 -1
- package/dist/components/TyTag.d.ts.map +1 -1
- package/dist/components/TyTag.js +11 -5
- package/dist/components/TyTag.js.map +1 -1
- package/dist/components/TyTextarea.d.ts.map +1 -1
- package/dist/components/TyTextarea.js +10 -2
- package/dist/components/TyTextarea.js.map +1 -1
- package/dist/components/TyTooltip.d.ts.map +1 -1
- package/dist/components/TyTooltip.js +4 -3
- package/dist/components/TyTooltip.js.map +1 -1
- package/dist/components/index.d.ts +4 -0
- package/dist/components/index.d.ts.map +1 -1
- package/dist/components/index.js +2 -0
- package/dist/components/index.js.map +1 -1
- package/dist/utils/react-version.d.ts +2 -0
- package/dist/utils/react-version.d.ts.map +1 -0
- package/dist/utils/react-version.js +8 -0
- package/dist/utils/react-version.js.map +1 -0
- package/dist/utils/use-boolean-prop.d.ts +36 -0
- package/dist/utils/use-boolean-prop.d.ts.map +1 -0
- package/dist/utils/use-boolean-prop.js +62 -0
- package/dist/utils/use-boolean-prop.js.map +1 -0
- package/package.json +1 -1
- package/src/components/TyButton.tsx +17 -5
- package/src/components/TyCalendar.tsx +10 -7
- package/src/components/TyCheckbox.tsx +11 -13
- package/src/components/TyCopy.tsx +9 -4
- package/src/components/TyDatePicker.tsx +12 -11
- package/src/components/TyDropdown.tsx +56 -34
- package/src/components/TyFileUpload.tsx +108 -0
- package/src/components/TyIcon.tsx +6 -7
- package/src/components/TyInput.tsx +14 -4
- package/src/components/TyModal.tsx +31 -13
- package/src/components/TyMultiselect.tsx +25 -17
- package/src/components/TyOption.tsx +8 -3
- package/src/components/TyPopup.tsx +5 -6
- package/src/components/TyRadio.tsx +7 -2
- package/src/components/TyRadioGroup.tsx +6 -2
- package/src/components/TyScrollContainer.tsx +17 -2
- package/src/components/TySwitch.tsx +9 -3
- package/src/components/TyTab.tsx +3 -1
- package/src/components/TyTag.tsx +12 -5
- package/src/components/TyTextarea.tsx +10 -2
- package/src/components/TyTooltip.tsx +3 -3
- package/src/components/index.ts +7 -0
- package/src/utils/react-version.ts +8 -0
- package/src/utils/use-boolean-prop.ts +62 -0
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import React, { useEffect, useRef } from 'react';
|
|
2
|
+
import { useBooleanProperty } from '../utils/use-boolean-prop';
|
|
2
3
|
|
|
3
4
|
// Type definitions for Ty Checkbox component
|
|
4
5
|
export interface TyCheckboxProps extends Omit<React.HTMLAttributes<HTMLElement>, 'onChange' | 'onInput'> {
|
|
@@ -109,16 +110,13 @@ export const TyCheckbox = React.forwardRef<HTMLElement, TyCheckboxProps>(
|
|
|
109
110
|
}
|
|
110
111
|
}, [ref]);
|
|
111
112
|
|
|
112
|
-
//
|
|
113
|
-
// boolean attributes as empty strings on first render but
|
|
114
|
-
// remove them when the prop flips back to false on a
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
element.checked = Boolean(checked);
|
|
120
|
-
}
|
|
121
|
-
}, [checked]);
|
|
113
|
+
// Imperative property sync for boolean props (see use-boolean-prop.ts).
|
|
114
|
+
// React 18 sets boolean attributes as empty strings on first render but
|
|
115
|
+
// doesn't reliably remove them when the prop flips back to false on a
|
|
116
|
+
// custom element. React 19+ handles this natively.
|
|
117
|
+
const isChecked = useBooleanProperty(elementRef, 'checked', checked);
|
|
118
|
+
const isDisabled = useBooleanProperty(elementRef, 'disabled', disabled);
|
|
119
|
+
const isRequired = useBooleanProperty(elementRef, 'required', required);
|
|
122
120
|
|
|
123
121
|
// Convert React props to web component attributes
|
|
124
122
|
const webComponentProps: Record<string, any> = {
|
|
@@ -127,9 +125,9 @@ export const TyCheckbox = React.forwardRef<HTMLElement, TyCheckboxProps>(
|
|
|
127
125
|
};
|
|
128
126
|
|
|
129
127
|
// Add boolean attributes
|
|
130
|
-
if (
|
|
131
|
-
if (
|
|
132
|
-
if (
|
|
128
|
+
if (isChecked) webComponentProps.checked = '';
|
|
129
|
+
if (isDisabled) webComponentProps.disabled = '';
|
|
130
|
+
if (isRequired) webComponentProps.required = '';
|
|
133
131
|
|
|
134
132
|
// Add string attributes
|
|
135
133
|
if (value) webComponentProps.value = value;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import React, { useEffect, useRef } from 'react';
|
|
2
|
+
import { useBooleanProperty } from '../utils/use-boolean-prop';
|
|
2
3
|
|
|
3
4
|
// Type definitions for Ty Copy component
|
|
4
5
|
export interface TyCopyProps extends Omit<React.HTMLAttributes<HTMLElement>, 'onChange'> {
|
|
@@ -53,6 +54,10 @@ export const TyCopy = React.forwardRef<HTMLElement, TyCopyProps>(
|
|
|
53
54
|
}
|
|
54
55
|
}, [ref]);
|
|
55
56
|
|
|
57
|
+
const isMultiline = useBooleanProperty(elementRef, 'multiline', multiline);
|
|
58
|
+
const isDisabled = useBooleanProperty(elementRef, 'disabled', disabled);
|
|
59
|
+
const isRequired = useBooleanProperty(elementRef, 'required', required);
|
|
60
|
+
|
|
56
61
|
// Convert React props to web component attributes
|
|
57
62
|
const webComponentProps: Record<string, any> = {
|
|
58
63
|
...props,
|
|
@@ -65,11 +70,11 @@ export const TyCopy = React.forwardRef<HTMLElement, TyCopyProps>(
|
|
|
65
70
|
if (size) webComponentProps.size = size;
|
|
66
71
|
if (flavor) webComponentProps.flavor = flavor;
|
|
67
72
|
if (format) webComponentProps.format = format;
|
|
68
|
-
|
|
73
|
+
|
|
69
74
|
// Add boolean attributes
|
|
70
|
-
if (
|
|
71
|
-
if (
|
|
72
|
-
if (
|
|
75
|
+
if (isMultiline) webComponentProps.multiline = '';
|
|
76
|
+
if (isDisabled) webComponentProps.disabled = '';
|
|
77
|
+
if (isRequired) webComponentProps.required = '';
|
|
73
78
|
|
|
74
79
|
return React.createElement('ty-copy', webComponentProps);
|
|
75
80
|
}
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import React, { useEffect, useRef, useCallback } from 'react';
|
|
2
|
+
import { needsPropertyBridge } from '../utils/react-version';
|
|
3
|
+
import { useBooleanProperty } from '../utils/use-boolean-prop';
|
|
2
4
|
|
|
3
5
|
// Type definitions for Ty DatePicker component
|
|
4
6
|
export interface TyDatePickerEventDetail {
|
|
@@ -92,8 +94,11 @@ export const TyDatePicker = React.forwardRef<HTMLElement, TyDatePickerProps>(
|
|
|
92
94
|
}
|
|
93
95
|
}, [ref]);
|
|
94
96
|
|
|
95
|
-
// Sync value property with the web component
|
|
97
|
+
// Sync value property with the web component.
|
|
98
|
+
// React 18 workaround: prop-to-property bridging is unreliable for empty
|
|
99
|
+
// strings on custom elements. React 19+ handles this natively.
|
|
96
100
|
useEffect(() => {
|
|
101
|
+
if (!needsPropertyBridge) return;
|
|
97
102
|
const element = elementRef.current;
|
|
98
103
|
if (element && value !== undefined) {
|
|
99
104
|
// Set the value property directly on the element
|
|
@@ -181,22 +186,18 @@ export const TyDatePicker = React.forwardRef<HTMLElement, TyDatePickerProps>(
|
|
|
181
186
|
webComponentProps.placeholder = placeholder;
|
|
182
187
|
}
|
|
183
188
|
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
189
|
+
const isRequired = useBooleanProperty(elementRef, 'required', required);
|
|
190
|
+
const isDisabled = useBooleanProperty(elementRef, 'disabled', disabled);
|
|
191
|
+
const isClearable = useBooleanProperty(elementRef, 'clearable', clearable);
|
|
187
192
|
|
|
188
|
-
if (
|
|
189
|
-
|
|
190
|
-
|
|
193
|
+
if (isRequired) webComponentProps.required = '';
|
|
194
|
+
if (isDisabled) webComponentProps.disabled = '';
|
|
195
|
+
if (isClearable) webComponentProps.clearable = '';
|
|
191
196
|
|
|
192
197
|
if (name) {
|
|
193
198
|
webComponentProps.name = name;
|
|
194
199
|
}
|
|
195
200
|
|
|
196
|
-
if (clearable) {
|
|
197
|
-
webComponentProps.clearable = ''; // Boolean attributes as empty string
|
|
198
|
-
}
|
|
199
|
-
|
|
200
201
|
if (format) {
|
|
201
202
|
webComponentProps.format = format;
|
|
202
203
|
}
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import React, { useEffect, useRef, useCallback } from 'react';
|
|
2
|
+
import { needsPropertyBridge } from '../utils/react-version';
|
|
3
|
+
import { useBooleanProperty, coerceBool } from '../utils/use-boolean-prop';
|
|
2
4
|
|
|
3
5
|
// Option data structure for data-driven approach
|
|
4
6
|
export interface OptionData {
|
|
@@ -32,30 +34,42 @@ export interface TyDropdownProps extends Omit<React.HTMLAttributes<HTMLElement>,
|
|
|
32
34
|
|
|
33
35
|
/** Disable the dropdown */
|
|
34
36
|
disabled?: boolean;
|
|
35
|
-
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Loading state — replaces the open popup options list with a centered
|
|
40
|
+
* spinner. Search input stays usable. Pair with `externalSearch` while
|
|
41
|
+
* fetching results from a parent-owned data source.
|
|
42
|
+
*/
|
|
43
|
+
loading?: boolean;
|
|
44
|
+
|
|
36
45
|
/** Make dropdown readonly */
|
|
37
46
|
readonly?: boolean;
|
|
38
47
|
|
|
39
48
|
/** Required field */
|
|
40
49
|
required?: boolean;
|
|
41
50
|
|
|
42
|
-
/** Enable search functionality */
|
|
43
|
-
searchable?: boolean;
|
|
44
|
-
|
|
45
51
|
/** Show clear button */
|
|
46
52
|
clearable?: boolean;
|
|
47
|
-
|
|
53
|
+
|
|
48
54
|
/** Disable clear button (alias for clearable={false}) */
|
|
49
55
|
notClearable?: boolean;
|
|
50
|
-
|
|
56
|
+
|
|
51
57
|
/** Debounce in milliseconds (0-5000) */
|
|
52
58
|
debounce?: number;
|
|
53
|
-
|
|
54
|
-
/**
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Switch to external (remote) search mode. Default is `false` — the dropdown
|
|
62
|
+
* filters its options locally. When `true`, the dropdown stops filtering and
|
|
63
|
+
* dispatches `search` events on each keystroke; the parent owns filtering
|
|
64
|
+
* and updates the children.
|
|
65
|
+
*/
|
|
58
66
|
externalSearch?: boolean;
|
|
67
|
+
|
|
68
|
+
/** @deprecated Use `externalSearch` instead. */
|
|
69
|
+
notSearchable?: boolean;
|
|
70
|
+
|
|
71
|
+
/** @deprecated Use `externalSearch` instead. Pass `searchable={false}` was equivalent to `externalSearch={true}`. */
|
|
72
|
+
searchable?: boolean;
|
|
59
73
|
|
|
60
74
|
/** Form field name for form submission */
|
|
61
75
|
name?: string;
|
|
@@ -79,8 +93,10 @@ export const TyDropdown = React.forwardRef<HTMLElement, TyDropdownProps>(
|
|
|
79
93
|
onChange,
|
|
80
94
|
onSearch,
|
|
81
95
|
disabled,
|
|
82
|
-
|
|
96
|
+
loading,
|
|
83
97
|
externalSearch,
|
|
98
|
+
notSearchable,
|
|
99
|
+
searchable,
|
|
84
100
|
clearable,
|
|
85
101
|
notClearable,
|
|
86
102
|
debounce,
|
|
@@ -129,7 +145,9 @@ export const TyDropdown = React.forwardRef<HTMLElement, TyDropdownProps>(
|
|
|
129
145
|
// it changes. React 18's prop-to-property bridging for custom elements is
|
|
130
146
|
// unreliable for empty strings (programmatic resets), so we set the
|
|
131
147
|
// property directly to guarantee the dropdown clears on `value=""`.
|
|
148
|
+
// React 19+ handles this natively, so the effect short-circuits there.
|
|
132
149
|
useEffect(() => {
|
|
150
|
+
if (!needsPropertyBridge) return;
|
|
133
151
|
const element = elementRef.current as any;
|
|
134
152
|
if (!element) return;
|
|
135
153
|
if (element.value !== value) {
|
|
@@ -169,41 +187,45 @@ export const TyDropdown = React.forwardRef<HTMLElement, TyDropdownProps>(
|
|
|
169
187
|
return children;
|
|
170
188
|
};
|
|
171
189
|
|
|
190
|
+
// Imperative property sync for boolean props (see use-boolean-prop.ts).
|
|
191
|
+
const isDisabled = useBooleanProperty(elementRef, 'disabled', disabled);
|
|
192
|
+
const isLoading = useBooleanProperty(elementRef, 'loading', loading);
|
|
193
|
+
const isExternalSearch = coerceBool(externalSearch) || coerceBool(notSearchable) || searchable === false;
|
|
194
|
+
useBooleanProperty(elementRef, 'externalSearch', isExternalSearch);
|
|
195
|
+
// clearable: explicit boolean OR the `notClearable` alias inverts it
|
|
196
|
+
const isClearable = clearable !== undefined
|
|
197
|
+
? coerceBool(clearable)
|
|
198
|
+
: (coerceBool(notClearable) ? false : undefined);
|
|
199
|
+
useEffect(() => {
|
|
200
|
+
if (!needsPropertyBridge) return;
|
|
201
|
+
if (isClearable === undefined) return;
|
|
202
|
+
const el = elementRef.current as any;
|
|
203
|
+
if (!el) return;
|
|
204
|
+
if (Boolean(el.clearable) !== isClearable) el.clearable = isClearable;
|
|
205
|
+
}, [isClearable]);
|
|
206
|
+
|
|
172
207
|
// Convert React props to web component attributes
|
|
173
208
|
const webComponentProps: Record<string, any> = {
|
|
174
209
|
...props,
|
|
175
210
|
ref: elementRef,
|
|
176
211
|
};
|
|
177
212
|
|
|
178
|
-
|
|
179
|
-
if (
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
if (notSearchable) {
|
|
183
|
-
webComponentProps['not-searchable'] = '';
|
|
184
|
-
} else if (externalSearch) {
|
|
185
|
-
// Support deprecated externalSearch for backward compatibility
|
|
186
|
-
webComponentProps['not-searchable'] = '';
|
|
187
|
-
}
|
|
188
|
-
|
|
213
|
+
if (isDisabled) webComponentProps.disabled = '';
|
|
214
|
+
if (isLoading) webComponentProps.loading = '';
|
|
215
|
+
if (isExternalSearch) webComponentProps['external-search'] = '';
|
|
216
|
+
|
|
189
217
|
// Handle clearable functionality
|
|
190
|
-
if (
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
} else {
|
|
194
|
-
webComponentProps['not-clearable'] = '';
|
|
195
|
-
}
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
if (notClearable) {
|
|
218
|
+
if (isClearable === true) {
|
|
219
|
+
webComponentProps.clearable = '';
|
|
220
|
+
} else if (isClearable === false) {
|
|
199
221
|
webComponentProps['not-clearable'] = '';
|
|
200
222
|
}
|
|
201
|
-
|
|
223
|
+
|
|
202
224
|
// Add debounce attribute
|
|
203
225
|
if (debounce !== undefined) {
|
|
204
226
|
webComponentProps.debounce = debounce;
|
|
205
227
|
}
|
|
206
|
-
|
|
228
|
+
|
|
207
229
|
// Add string attributes
|
|
208
230
|
if (name) webComponentProps.name = name;
|
|
209
231
|
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import React, { useEffect, useRef } from 'react';
|
|
2
|
+
import { useBooleanProperty } from '../utils/use-boolean-prop';
|
|
3
|
+
|
|
4
|
+
// Type definitions for Ty File Upload component
|
|
5
|
+
export interface TyFileUploadProps extends Omit<React.HTMLAttributes<HTMLElement>, 'onChange'> {
|
|
6
|
+
/** Form field name — used as the FormData key */
|
|
7
|
+
name?: string;
|
|
8
|
+
|
|
9
|
+
/** Allow selecting multiple files */
|
|
10
|
+
multiple?: boolean;
|
|
11
|
+
|
|
12
|
+
/** File type filter passed to the underlying input (e.g. "image/*", ".pdf,.docx") */
|
|
13
|
+
accept?: string;
|
|
14
|
+
|
|
15
|
+
/** Field label rendered above the drop zone */
|
|
16
|
+
label?: string;
|
|
17
|
+
|
|
18
|
+
/** Hint text shown inside the drop zone when no files are selected */
|
|
19
|
+
placeholder?: string;
|
|
20
|
+
|
|
21
|
+
/** Disable interaction */
|
|
22
|
+
disabled?: boolean;
|
|
23
|
+
|
|
24
|
+
/** Mark the field as required (shows asterisk) */
|
|
25
|
+
required?: boolean;
|
|
26
|
+
|
|
27
|
+
/** Validation error message — also applies danger border styling */
|
|
28
|
+
error?: string;
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Fires when the selection changes — browse, drag-drop, or remove.
|
|
32
|
+
* Maps to the native 'change' event from ty-file-upload.
|
|
33
|
+
*/
|
|
34
|
+
onChange?: (event: CustomEvent<TyFileUploadEventDetail>) => void;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export interface TyFileUploadEventDetail {
|
|
38
|
+
value: File[];
|
|
39
|
+
files: File[];
|
|
40
|
+
names: string[];
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// React wrapper for ty-file-upload web component
|
|
44
|
+
export const TyFileUpload = React.forwardRef<HTMLElement, TyFileUploadProps>(
|
|
45
|
+
({
|
|
46
|
+
name,
|
|
47
|
+
multiple,
|
|
48
|
+
accept,
|
|
49
|
+
label,
|
|
50
|
+
placeholder,
|
|
51
|
+
disabled,
|
|
52
|
+
required,
|
|
53
|
+
error,
|
|
54
|
+
onChange,
|
|
55
|
+
...props
|
|
56
|
+
}, ref) => {
|
|
57
|
+
const elementRef = useRef<HTMLElement>(null);
|
|
58
|
+
|
|
59
|
+
useEffect(() => {
|
|
60
|
+
const element = elementRef.current;
|
|
61
|
+
if (!element) return;
|
|
62
|
+
|
|
63
|
+
const handleChange = (event: Event) => {
|
|
64
|
+
if (onChange) {
|
|
65
|
+
onChange(event as CustomEvent<TyFileUploadEventDetail>);
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
element.addEventListener('change', handleChange);
|
|
70
|
+
return () => {
|
|
71
|
+
element.removeEventListener('change', handleChange);
|
|
72
|
+
};
|
|
73
|
+
}, [onChange]);
|
|
74
|
+
|
|
75
|
+
useEffect(() => {
|
|
76
|
+
if (ref && elementRef.current) {
|
|
77
|
+
if (typeof ref === 'function') {
|
|
78
|
+
ref(elementRef.current);
|
|
79
|
+
} else {
|
|
80
|
+
ref.current = elementRef.current;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}, [ref]);
|
|
84
|
+
|
|
85
|
+
const isMultiple = useBooleanProperty(elementRef, 'multiple', multiple);
|
|
86
|
+
const isDisabled = useBooleanProperty(elementRef, 'disabled', disabled);
|
|
87
|
+
const isRequired = useBooleanProperty(elementRef, 'required', required);
|
|
88
|
+
|
|
89
|
+
const webComponentProps: Record<string, any> = {
|
|
90
|
+
...props,
|
|
91
|
+
ref: elementRef,
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
if (isMultiple) webComponentProps.multiple = '';
|
|
95
|
+
if (isDisabled) webComponentProps.disabled = '';
|
|
96
|
+
if (isRequired) webComponentProps.required = '';
|
|
97
|
+
|
|
98
|
+
if (name) webComponentProps.name = name;
|
|
99
|
+
if (accept) webComponentProps.accept = accept;
|
|
100
|
+
if (label) webComponentProps.label = label;
|
|
101
|
+
if (placeholder) webComponentProps.placeholder = placeholder;
|
|
102
|
+
if (error) webComponentProps.error = error;
|
|
103
|
+
|
|
104
|
+
return React.createElement('ty-file-upload', webComponentProps);
|
|
105
|
+
}
|
|
106
|
+
);
|
|
107
|
+
|
|
108
|
+
TyFileUpload.displayName = 'TyFileUpload';
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import React, { useEffect, useRef } from 'react';
|
|
2
|
+
import { useBooleanProperty } from '../utils/use-boolean-prop';
|
|
2
3
|
|
|
3
4
|
// Type definitions for Ty Icon component
|
|
4
5
|
export interface TyIconProps extends React.HTMLAttributes<HTMLElement> {
|
|
@@ -37,6 +38,9 @@ export const TyIcon = React.forwardRef<HTMLElement, TyIconProps>(
|
|
|
37
38
|
}
|
|
38
39
|
}, [ref]);
|
|
39
40
|
|
|
41
|
+
const isSpin = useBooleanProperty(elementRef, 'spin', spin);
|
|
42
|
+
const isPulse = useBooleanProperty(elementRef, 'pulse', pulse);
|
|
43
|
+
|
|
40
44
|
// Convert React props to web component attributes
|
|
41
45
|
const webComponentProps: Record<string, any> = {
|
|
42
46
|
...props,
|
|
@@ -49,13 +53,8 @@ export const TyIcon = React.forwardRef<HTMLElement, TyIconProps>(
|
|
|
49
53
|
webComponentProps.size = size;
|
|
50
54
|
}
|
|
51
55
|
|
|
52
|
-
if (
|
|
53
|
-
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
if (pulse) {
|
|
57
|
-
webComponentProps.pulse = ''; // Boolean attributes as empty string
|
|
58
|
-
}
|
|
56
|
+
if (isSpin) webComponentProps.spin = '';
|
|
57
|
+
if (isPulse) webComponentProps.pulse = '';
|
|
59
58
|
|
|
60
59
|
if (tempo) {
|
|
61
60
|
webComponentProps.tempo = tempo;
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import React, { useEffect, useRef, useCallback } from 'react';
|
|
2
|
+
import { needsPropertyBridge } from '../utils/react-version';
|
|
3
|
+
import { useBooleanProperty } from '../utils/use-boolean-prop';
|
|
2
4
|
|
|
3
5
|
// Event detail structure for ty-input events
|
|
4
6
|
export interface TyInputEventDetail {
|
|
@@ -91,7 +93,7 @@ let _warnedOnInputProp = false;
|
|
|
91
93
|
|
|
92
94
|
// React wrapper for ty-input web component
|
|
93
95
|
export const TyInput = React.forwardRef<HTMLElement, TyInputProps>(
|
|
94
|
-
({ onChange, onChangeCommit, onFocus, onBlur, disabled, name, checked, debounce, ...props }, ref) => {
|
|
96
|
+
({ onChange, onChangeCommit, onFocus, onBlur, disabled, required, name, checked, debounce, ...props }, ref) => {
|
|
95
97
|
const elementRef = useRef<HTMLElement>(null);
|
|
96
98
|
|
|
97
99
|
// Catch the most common mistake: passing `onInput` (React's prop) instead
|
|
@@ -193,8 +195,10 @@ export const TyInput = React.forwardRef<HTMLElement, TyInputProps>(
|
|
|
193
195
|
// Imperatively sync `value` to the underlying element's property whenever
|
|
194
196
|
// the React prop changes. React 18's prop-to-property bridging for custom
|
|
195
197
|
// elements is unreliable for empty strings, so we set the property directly
|
|
196
|
-
// to guarantee resets (`value=""`) clear the visible content.
|
|
198
|
+
// to guarantee resets (`value=""`) clear the visible content. React 19+
|
|
199
|
+
// handles this natively, so the effect short-circuits there.
|
|
197
200
|
useEffect(() => {
|
|
201
|
+
if (!needsPropertyBridge) return;
|
|
198
202
|
const element = elementRef.current as any;
|
|
199
203
|
if (!element) return;
|
|
200
204
|
const next = (props as any).value ?? '';
|
|
@@ -203,6 +207,11 @@ export const TyInput = React.forwardRef<HTMLElement, TyInputProps>(
|
|
|
203
207
|
}
|
|
204
208
|
}, [(props as any).value]);
|
|
205
209
|
|
|
210
|
+
// Imperative property sync for boolean props (see use-boolean-prop.ts).
|
|
211
|
+
const isDisabled = useBooleanProperty(elementRef, 'disabled', disabled);
|
|
212
|
+
const isRequired = useBooleanProperty(elementRef, 'required', required);
|
|
213
|
+
const isChecked = useBooleanProperty(elementRef, 'checked', checked);
|
|
214
|
+
|
|
206
215
|
// Convert React props to web component attributes
|
|
207
216
|
const webComponentProps: Record<string, any> = {
|
|
208
217
|
...props,
|
|
@@ -210,8 +219,9 @@ export const TyInput = React.forwardRef<HTMLElement, TyInputProps>(
|
|
|
210
219
|
};
|
|
211
220
|
|
|
212
221
|
// Add conditional attributes
|
|
213
|
-
if (
|
|
214
|
-
if (
|
|
222
|
+
if (isDisabled) webComponentProps.disabled = '';
|
|
223
|
+
if (isRequired) webComponentProps.required = '';
|
|
224
|
+
if (isChecked) webComponentProps.checked = '';
|
|
215
225
|
|
|
216
226
|
// Add string attributes
|
|
217
227
|
if (name) webComponentProps.name = name;
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import React, { useEffect, useRef, useImperativeHandle } from 'react';
|
|
2
|
+
import { useBooleanProperty, coerceBool } from '../utils/use-boolean-prop';
|
|
3
|
+
import { needsPropertyBridge } from '../utils/react-version';
|
|
2
4
|
|
|
3
5
|
// Event detail structure for modal events
|
|
4
6
|
export interface TyModalEventDetail {
|
|
@@ -104,33 +106,49 @@ export const TyModal = React.forwardRef<TyModalRef, TyModalProps>(
|
|
|
104
106
|
};
|
|
105
107
|
}, [onOpen, onClose]);
|
|
106
108
|
|
|
109
|
+
// Imperative property sync for boolean props (see use-boolean-prop.ts).
|
|
110
|
+
// Without this, flipping `open` from `true` to `false` on React 18 leaves
|
|
111
|
+
// the `open` attribute on the element and the modal stays open.
|
|
112
|
+
const isOpen = useBooleanProperty(elementRef, 'open', open);
|
|
113
|
+
const isProt = useBooleanProperty(elementRef, 'protected', isProtected);
|
|
114
|
+
|
|
115
|
+
// For default-true booleans (backdrop, closeOn*), only the explicit-false
|
|
116
|
+
// case is interesting — bridge it imperatively too so it propagates.
|
|
117
|
+
useEffect(() => {
|
|
118
|
+
if (!needsPropertyBridge) return;
|
|
119
|
+
const el = elementRef.current as any;
|
|
120
|
+
if (!el) return;
|
|
121
|
+
const setIf = (prop: string, raw: unknown) => {
|
|
122
|
+
if (raw === undefined) return;
|
|
123
|
+
const next = coerceBool(raw);
|
|
124
|
+
if (Boolean(el[prop]) !== next) el[prop] = next;
|
|
125
|
+
};
|
|
126
|
+
setIf('backdrop', backdrop);
|
|
127
|
+
setIf('closeOnOutsideClick', closeOnOutsideClick);
|
|
128
|
+
setIf('closeOnEscape', closeOnEscape);
|
|
129
|
+
}, [backdrop, closeOnOutsideClick, closeOnEscape]);
|
|
130
|
+
|
|
107
131
|
// Convert React props to web component attributes
|
|
108
132
|
const webComponentProps: Record<string, any> = {
|
|
109
133
|
...props,
|
|
110
134
|
ref: elementRef,
|
|
111
135
|
};
|
|
112
136
|
|
|
113
|
-
|
|
114
|
-
if (
|
|
115
|
-
webComponentProps.open = ''; // Boolean attributes as empty string
|
|
116
|
-
}
|
|
137
|
+
if (isOpen) webComponentProps.open = '';
|
|
138
|
+
if (isProt) webComponentProps.protected = '';
|
|
117
139
|
|
|
118
|
-
|
|
140
|
+
// Default-true booleans use "false" string on the attribute side; the
|
|
141
|
+
// core's parseBoolAttr handles it correctly.
|
|
142
|
+
if (backdrop !== undefined && !coerceBool(backdrop)) {
|
|
119
143
|
webComponentProps.backdrop = 'false';
|
|
120
144
|
}
|
|
121
|
-
|
|
122
|
-
if (closeOnOutsideClick === false) { // Only set if explicitly false (default is true)
|
|
145
|
+
if (closeOnOutsideClick !== undefined && !coerceBool(closeOnOutsideClick)) {
|
|
123
146
|
webComponentProps['close-on-outside-click'] = 'false';
|
|
124
147
|
}
|
|
125
|
-
|
|
126
|
-
if (closeOnEscape === false) { // Only set if explicitly false (default is true)
|
|
148
|
+
if (closeOnEscape !== undefined && !coerceBool(closeOnEscape)) {
|
|
127
149
|
webComponentProps['close-on-escape'] = 'false';
|
|
128
150
|
}
|
|
129
151
|
|
|
130
|
-
if (isProtected) {
|
|
131
|
-
webComponentProps.protected = ''; // Boolean attributes as empty string
|
|
132
|
-
}
|
|
133
|
-
|
|
134
152
|
return React.createElement(
|
|
135
153
|
'ty-modal',
|
|
136
154
|
webComponentProps,
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import React, { useEffect, useRef, useCallback } from 'react';
|
|
2
|
+
import { needsPropertyBridge } from '../utils/react-version';
|
|
3
|
+
import { useBooleanProperty } from '../utils/use-boolean-prop';
|
|
2
4
|
|
|
3
5
|
// Type definitions for Ty Multiselect component
|
|
4
6
|
export interface TyMultiselectEventDetail {
|
|
@@ -20,7 +22,14 @@ export interface TyMultiselectProps extends Omit<React.HTMLAttributes<HTMLElemen
|
|
|
20
22
|
|
|
21
23
|
/** Disable the multiselect component */
|
|
22
24
|
disabled?: boolean;
|
|
23
|
-
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Loading state — replaces the available-options area with a centered
|
|
28
|
+
* spinner. Search input stays usable. Pair with `externalSearch` while
|
|
29
|
+
* fetching results from a parent-owned data source.
|
|
30
|
+
*/
|
|
31
|
+
loading?: boolean;
|
|
32
|
+
|
|
24
33
|
/** Make the multiselect read-only */
|
|
25
34
|
readonly?: boolean;
|
|
26
35
|
|
|
@@ -67,6 +76,7 @@ export const TyMultiselect = React.forwardRef<HTMLElement, TyMultiselectProps>(
|
|
|
67
76
|
value,
|
|
68
77
|
placeholder,
|
|
69
78
|
disabled,
|
|
79
|
+
loading,
|
|
70
80
|
readonly,
|
|
71
81
|
flavor,
|
|
72
82
|
label,
|
|
@@ -95,7 +105,9 @@ export const TyMultiselect = React.forwardRef<HTMLElement, TyMultiselectProps>(
|
|
|
95
105
|
|
|
96
106
|
// Imperatively sync `value` to the underlying property so resets
|
|
97
107
|
// (`value=""` or null) reliably clear the visible selection.
|
|
108
|
+
// React 18 workaround; React 19+ handles this natively.
|
|
98
109
|
useEffect(() => {
|
|
110
|
+
if (!needsPropertyBridge) return;
|
|
99
111
|
const element = elementRef.current as any;
|
|
100
112
|
if (!element) return;
|
|
101
113
|
const next = (props as any).value ?? '';
|
|
@@ -133,6 +145,13 @@ export const TyMultiselect = React.forwardRef<HTMLElement, TyMultiselectProps>(
|
|
|
133
145
|
};
|
|
134
146
|
}, [handleChange, handleSearch, onChange, onSearch]);
|
|
135
147
|
|
|
148
|
+
// Imperative property sync for boolean props (see use-boolean-prop.ts).
|
|
149
|
+
const isDisabled = useBooleanProperty(elementRef, 'disabled', disabled);
|
|
150
|
+
const isLoading = useBooleanProperty(elementRef, 'loading', loading);
|
|
151
|
+
const isReadonly = useBooleanProperty(elementRef, 'readonly', readonly);
|
|
152
|
+
const isRequired = useBooleanProperty(elementRef, 'required', required);
|
|
153
|
+
const isExternalSearch = useBooleanProperty(elementRef, 'externalSearch', externalSearch);
|
|
154
|
+
|
|
136
155
|
// Convert React props to web component attributes
|
|
137
156
|
const webComponentProps: Record<string, any> = {
|
|
138
157
|
...props,
|
|
@@ -150,13 +169,11 @@ export const TyMultiselect = React.forwardRef<HTMLElement, TyMultiselectProps>(
|
|
|
150
169
|
webComponentProps.placeholder = placeholder;
|
|
151
170
|
}
|
|
152
171
|
|
|
153
|
-
if (
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
if (
|
|
158
|
-
webComponentProps.readonly = ''; // Boolean attributes as empty string
|
|
159
|
-
}
|
|
172
|
+
if (isLoading) webComponentProps.loading = '';
|
|
173
|
+
if (isDisabled) webComponentProps.disabled = '';
|
|
174
|
+
if (isReadonly) webComponentProps.readonly = '';
|
|
175
|
+
if (isRequired) webComponentProps.required = '';
|
|
176
|
+
if (isExternalSearch) webComponentProps['external-search'] = '';
|
|
160
177
|
|
|
161
178
|
if (flavor) {
|
|
162
179
|
webComponentProps.flavor = flavor;
|
|
@@ -166,18 +183,9 @@ export const TyMultiselect = React.forwardRef<HTMLElement, TyMultiselectProps>(
|
|
|
166
183
|
webComponentProps.label = label;
|
|
167
184
|
}
|
|
168
185
|
|
|
169
|
-
if (required) {
|
|
170
|
-
webComponentProps.required = ''; // Boolean attributes as empty string
|
|
171
|
-
}
|
|
172
|
-
|
|
173
186
|
if (name) {
|
|
174
187
|
webComponentProps.name = name;
|
|
175
188
|
}
|
|
176
|
-
|
|
177
|
-
// External (remote) search mode: parent owns filtering, multiselect dispatches search events
|
|
178
|
-
if (externalSearch) {
|
|
179
|
-
webComponentProps['external-search'] = '';
|
|
180
|
-
}
|
|
181
189
|
|
|
182
190
|
// Add debounce attribute
|
|
183
191
|
if (debounce !== undefined) {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import React, { useEffect, useRef } from 'react';
|
|
2
|
+
import { useBooleanProperty } from '../utils/use-boolean-prop';
|
|
2
3
|
|
|
3
4
|
// Type definitions for Ty Option component
|
|
4
5
|
export interface TyOptionProps extends React.HTMLAttributes<HTMLElement> {
|
|
@@ -25,13 +26,17 @@ export const TyOption = React.forwardRef<HTMLElement, TyOptionProps>(
|
|
|
25
26
|
}
|
|
26
27
|
}, [ref]);
|
|
27
28
|
|
|
29
|
+
const isDisabled = useBooleanProperty(elementRef, 'disabled', disabled);
|
|
30
|
+
const isSelected = useBooleanProperty(elementRef, 'selected', selected);
|
|
31
|
+
const isHidden = useBooleanProperty(elementRef, 'hidden', hidden);
|
|
32
|
+
|
|
28
33
|
return React.createElement(
|
|
29
34
|
'ty-option',
|
|
30
35
|
{
|
|
31
36
|
...props,
|
|
32
|
-
...(
|
|
33
|
-
...(
|
|
34
|
-
...(
|
|
37
|
+
...(isDisabled && { disabled: "" }),
|
|
38
|
+
...(isSelected && { selected: "" }),
|
|
39
|
+
...(isHidden && { hidden: "" }),
|
|
35
40
|
ref: elementRef,
|
|
36
41
|
},
|
|
37
42
|
children
|