tyrell-react 1.0.0-TC12 → 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.map +1 -1
- package/dist/components/TyButton.js +11 -5
- package/dist/components/TyButton.js.map +1 -1
- package/dist/components/TyCalendar.d.ts.map +1 -1
- package/dist/components/TyCalendar.js +7 -6
- package/dist/components/TyCalendar.js.map +1 -1
- package/dist/components/TyCheckbox.d.ts.map +1 -1
- package/dist/components/TyCheckbox.js +11 -18
- 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 +10 -9
- package/dist/components/TyDatePicker.js.map +1 -1
- package/dist/components/TyDropdown.d.ts.map +1 -1
- package/dist/components/TyDropdown.js +27 -15
- package/dist/components/TyDropdown.js.map +1 -1
- package/dist/components/TyFileUpload.d.ts.map +1 -1
- package/dist/components/TyFileUpload.js +7 -3
- package/dist/components/TyFileUpload.js.map +1 -1
- 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 +10 -3
- 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.map +1 -1
- package/dist/components/TyMultiselect.js +16 -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 -17
- 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 -18
- 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 +5 -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/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 +12 -5
- package/src/components/TyCalendar.tsx +5 -6
- package/src/components/TyCheckbox.tsx +11 -16
- package/src/components/TyCopy.tsx +9 -4
- package/src/components/TyDatePicker.tsx +7 -10
- package/src/components/TyDropdown.tsx +26 -20
- package/src/components/TyFileUpload.tsx +8 -3
- package/src/components/TyIcon.tsx +6 -7
- package/src/components/TyInput.tsx +10 -3
- package/src/components/TyModal.tsx +31 -13
- package/src/components/TyMultiselect.tsx +13 -17
- package/src/components/TyOption.tsx +8 -3
- package/src/components/TyPopup.tsx +5 -6
- package/src/components/TyRadio.tsx +6 -15
- package/src/components/TyRadioGroup.tsx +6 -2
- package/src/components/TyScrollContainer.tsx +17 -2
- package/src/components/TySwitch.tsx +8 -16
- package/src/components/TyTab.tsx +3 -1
- package/src/components/TyTag.tsx +12 -5
- package/src/components/TyTextarea.tsx +6 -2
- package/src/components/TyTooltip.tsx +3 -3
- package/src/utils/use-boolean-prop.ts +62 -0
|
@@ -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,5 +1,6 @@
|
|
|
1
1
|
import React, { useEffect, useRef, useCallback } from 'react';
|
|
2
2
|
import { needsPropertyBridge } from '../utils/react-version';
|
|
3
|
+
import { useBooleanProperty } from '../utils/use-boolean-prop';
|
|
3
4
|
|
|
4
5
|
// Type definitions for Ty Multiselect component
|
|
5
6
|
export interface TyMultiselectEventDetail {
|
|
@@ -144,6 +145,13 @@ export const TyMultiselect = React.forwardRef<HTMLElement, TyMultiselectProps>(
|
|
|
144
145
|
};
|
|
145
146
|
}, [handleChange, handleSearch, onChange, onSearch]);
|
|
146
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
|
+
|
|
147
155
|
// Convert React props to web component attributes
|
|
148
156
|
const webComponentProps: Record<string, any> = {
|
|
149
157
|
...props,
|
|
@@ -161,14 +169,11 @@ export const TyMultiselect = React.forwardRef<HTMLElement, TyMultiselectProps>(
|
|
|
161
169
|
webComponentProps.placeholder = placeholder;
|
|
162
170
|
}
|
|
163
171
|
|
|
164
|
-
if (
|
|
165
|
-
if (
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
if (readonly) {
|
|
170
|
-
webComponentProps.readonly = ''; // Boolean attributes as empty string
|
|
171
|
-
}
|
|
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'] = '';
|
|
172
177
|
|
|
173
178
|
if (flavor) {
|
|
174
179
|
webComponentProps.flavor = flavor;
|
|
@@ -178,18 +183,9 @@ export const TyMultiselect = React.forwardRef<HTMLElement, TyMultiselectProps>(
|
|
|
178
183
|
webComponentProps.label = label;
|
|
179
184
|
}
|
|
180
185
|
|
|
181
|
-
if (required) {
|
|
182
|
-
webComponentProps.required = ''; // Boolean attributes as empty string
|
|
183
|
-
}
|
|
184
|
-
|
|
185
186
|
if (name) {
|
|
186
187
|
webComponentProps.name = name;
|
|
187
188
|
}
|
|
188
|
-
|
|
189
|
-
// External (remote) search mode: parent owns filtering, multiselect dispatches search events
|
|
190
|
-
if (externalSearch) {
|
|
191
|
-
webComponentProps['external-search'] = '';
|
|
192
|
-
}
|
|
193
189
|
|
|
194
190
|
// Add debounce attribute
|
|
195
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
|
|
@@ -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 Popup component
|
|
4
5
|
export interface TyPopupProps extends Omit<React.HTMLAttributes<HTMLElement>, 'onClose'> {
|
|
@@ -92,13 +93,11 @@ export const TyPopup = React.forwardRef<TyPopupElement, TyPopupProps>(
|
|
|
92
93
|
webComponentProps.offset = offset.toString();
|
|
93
94
|
}
|
|
94
95
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
}
|
|
96
|
+
const isManual = useBooleanProperty(elementRef, 'manual', manual);
|
|
97
|
+
const isDisableClose = useBooleanProperty(elementRef, 'disableClose', disableClose);
|
|
98
98
|
|
|
99
|
-
if (
|
|
100
|
-
|
|
101
|
-
}
|
|
99
|
+
if (isManual) webComponentProps.manual = '';
|
|
100
|
+
if (isDisableClose) webComponentProps['disable-close'] = '';
|
|
102
101
|
|
|
103
102
|
return React.createElement(
|
|
104
103
|
'ty-popup',
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import React, { useEffect, useRef } from 'react';
|
|
2
|
-
import {
|
|
2
|
+
import { useBooleanProperty } from '../utils/use-boolean-prop';
|
|
3
3
|
|
|
4
4
|
export interface TyRadioProps extends React.HTMLAttributes<HTMLElement> {
|
|
5
5
|
/** Form field value (selected by parent ty-radio-group when matches its `value`) */
|
|
@@ -38,26 +38,17 @@ export const TyRadio = React.forwardRef<HTMLElement, TyRadioProps>(
|
|
|
38
38
|
}
|
|
39
39
|
}, [ref]);
|
|
40
40
|
|
|
41
|
-
//
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
// React 19+ handles boolean prop-to-property bridging natively.
|
|
45
|
-
useEffect(() => {
|
|
46
|
-
if (!needsPropertyBridge) return;
|
|
47
|
-
const element = elementRef.current as any;
|
|
48
|
-
if (!element) return;
|
|
49
|
-
if (Boolean(element.checked) !== Boolean(checked)) {
|
|
50
|
-
element.checked = Boolean(checked);
|
|
51
|
-
}
|
|
52
|
-
}, [checked]);
|
|
41
|
+
// Imperative property sync for boolean props (see use-boolean-prop.ts).
|
|
42
|
+
const isChecked = useBooleanProperty(elementRef, 'checked', checked);
|
|
43
|
+
const isDisabled = useBooleanProperty(elementRef, 'disabled', disabled);
|
|
53
44
|
|
|
54
45
|
const webComponentProps: Record<string, any> = {
|
|
55
46
|
...props,
|
|
56
47
|
ref: elementRef,
|
|
57
48
|
};
|
|
58
49
|
|
|
59
|
-
if (
|
|
60
|
-
if (
|
|
50
|
+
if (isChecked) webComponentProps.checked = '';
|
|
51
|
+
if (isDisabled) webComponentProps.disabled = '';
|
|
61
52
|
|
|
62
53
|
if (value !== undefined) webComponentProps.value = value;
|
|
63
54
|
if (size) webComponentProps.size = size;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import React, { useEffect, useRef } from 'react';
|
|
2
|
+
import { useBooleanProperty } from '../utils/use-boolean-prop';
|
|
2
3
|
|
|
3
4
|
export interface TyRadioGroupProps extends Omit<React.HTMLAttributes<HTMLElement>, 'onChange' | 'onInput'> {
|
|
4
5
|
/** Currently selected value (matches one child `<TyRadio value="...">`) */
|
|
@@ -98,13 +99,16 @@ export const TyRadioGroup = React.forwardRef<HTMLElement, TyRadioGroupProps>(
|
|
|
98
99
|
}
|
|
99
100
|
}, [ref]);
|
|
100
101
|
|
|
102
|
+
const isDisabled = useBooleanProperty(elementRef, 'disabled', disabled);
|
|
103
|
+
const isRequired = useBooleanProperty(elementRef, 'required', required);
|
|
104
|
+
|
|
101
105
|
const webComponentProps: Record<string, any> = {
|
|
102
106
|
...props,
|
|
103
107
|
ref: elementRef,
|
|
104
108
|
};
|
|
105
109
|
|
|
106
|
-
if (
|
|
107
|
-
if (
|
|
110
|
+
if (isDisabled) webComponentProps.disabled = '';
|
|
111
|
+
if (isRequired) webComponentProps.required = '';
|
|
108
112
|
|
|
109
113
|
if (value !== undefined) webComponentProps.value = value;
|
|
110
114
|
if (name) webComponentProps.name = name;
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import React, { useEffect, useRef, useImperativeHandle } from 'react';
|
|
2
|
+
import { needsPropertyBridge } from '../utils/react-version';
|
|
3
|
+
import { useBooleanProperty, coerceBool } from '../utils/use-boolean-prop';
|
|
2
4
|
|
|
3
5
|
// Type definitions for Ty ScrollContainer component
|
|
4
6
|
export interface TyScrollContainerProps extends React.HTMLAttributes<HTMLElement> {
|
|
@@ -63,6 +65,19 @@ export const TyScrollContainer = React.forwardRef<TyScrollContainerRef, TyScroll
|
|
|
63
65
|
}
|
|
64
66
|
}), []);
|
|
65
67
|
|
|
68
|
+
// shadow defaults to true; only the explicit-false case matters at the
|
|
69
|
+
// attribute level. Bridge it imperatively so flipping back to true
|
|
70
|
+
// propagates on React 18.
|
|
71
|
+
useEffect(() => {
|
|
72
|
+
if (!needsPropertyBridge) return;
|
|
73
|
+
if (shadow === undefined) return;
|
|
74
|
+
const el = elementRef.current as any;
|
|
75
|
+
if (!el) return;
|
|
76
|
+
const next = coerceBool(shadow);
|
|
77
|
+
if (Boolean(el.shadow) !== next) el.shadow = next;
|
|
78
|
+
}, [shadow]);
|
|
79
|
+
const isHideScrollbar = useBooleanProperty(elementRef, 'hideScrollbar', hideScrollbar);
|
|
80
|
+
|
|
66
81
|
// Convert React props to web component attributes
|
|
67
82
|
const webComponentProps: Record<string, any> = {
|
|
68
83
|
...props,
|
|
@@ -73,8 +88,8 @@ export const TyScrollContainer = React.forwardRef<TyScrollContainerRef, TyScroll
|
|
|
73
88
|
if (maxHeight) webComponentProps['max-height'] = maxHeight;
|
|
74
89
|
|
|
75
90
|
// Add boolean attributes
|
|
76
|
-
if (shadow
|
|
77
|
-
if (
|
|
91
|
+
if (shadow !== undefined && !coerceBool(shadow)) webComponentProps.shadow = 'false';
|
|
92
|
+
if (isHideScrollbar) webComponentProps['hide-scrollbar'] = '';
|
|
78
93
|
|
|
79
94
|
return React.createElement(
|
|
80
95
|
'ty-scroll-container',
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import React, { useEffect, useRef } from 'react';
|
|
2
|
-
import {
|
|
2
|
+
import { useBooleanProperty } from '../utils/use-boolean-prop';
|
|
3
3
|
|
|
4
4
|
export interface TySwitchProps extends Omit<React.HTMLAttributes<HTMLElement>, 'onChange' | 'onInput'> {
|
|
5
5
|
/** Checked (on) state */
|
|
@@ -88,27 +88,19 @@ export const TySwitch = React.forwardRef<HTMLElement, TySwitchProps>(
|
|
|
88
88
|
}
|
|
89
89
|
}, [ref]);
|
|
90
90
|
|
|
91
|
-
//
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
useEffect(() => {
|
|
96
|
-
if (!needsPropertyBridge) return;
|
|
97
|
-
const element = elementRef.current as any;
|
|
98
|
-
if (!element) return;
|
|
99
|
-
if (Boolean(element.checked) !== Boolean(checked)) {
|
|
100
|
-
element.checked = Boolean(checked);
|
|
101
|
-
}
|
|
102
|
-
}, [checked]);
|
|
91
|
+
// Imperative property sync for boolean props (see use-boolean-prop.ts).
|
|
92
|
+
const isChecked = useBooleanProperty(elementRef, 'checked', checked);
|
|
93
|
+
const isDisabled = useBooleanProperty(elementRef, 'disabled', disabled);
|
|
94
|
+
const isRequired = useBooleanProperty(elementRef, 'required', required);
|
|
103
95
|
|
|
104
96
|
const webComponentProps: Record<string, any> = {
|
|
105
97
|
...props,
|
|
106
98
|
ref: elementRef,
|
|
107
99
|
};
|
|
108
100
|
|
|
109
|
-
if (
|
|
110
|
-
if (
|
|
111
|
-
if (
|
|
101
|
+
if (isChecked) webComponentProps.checked = '';
|
|
102
|
+
if (isDisabled) webComponentProps.disabled = '';
|
|
103
|
+
if (isRequired) webComponentProps.required = '';
|
|
112
104
|
|
|
113
105
|
if (value) webComponentProps.value = value;
|
|
114
106
|
if (name) webComponentProps.name = name;
|
package/src/components/TyTab.tsx
CHANGED
|
@@ -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 Tab component
|
|
4
5
|
export interface TyTabProps extends React.HTMLAttributes<HTMLElement> {
|
|
@@ -47,7 +48,8 @@ export const TyTab = React.forwardRef<HTMLElement, TyTabProps>(
|
|
|
47
48
|
webComponentProps.id = id;
|
|
48
49
|
|
|
49
50
|
// Add boolean attributes
|
|
50
|
-
|
|
51
|
+
const isDisabled = useBooleanProperty(elementRef, 'disabled', disabled);
|
|
52
|
+
if (isDisabled) webComponentProps.disabled = '';
|
|
51
53
|
|
|
52
54
|
// Add string attributes
|
|
53
55
|
if (label) webComponentProps.label = label;
|
package/src/components/TyTag.tsx
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import React, { useEffect, useRef, useCallback } from 'react';
|
|
2
|
+
import { useBooleanProperty } from '../utils/use-boolean-prop';
|
|
2
3
|
|
|
3
4
|
// CSS custom properties that cascade into the shadow DOM for full color control
|
|
4
5
|
export interface TyTagCSSProperties extends React.CSSProperties {
|
|
@@ -57,6 +58,12 @@ export const TyTag = React.forwardRef<HTMLElement, TyTagProps>(
|
|
|
57
58
|
}
|
|
58
59
|
}, [ref]);
|
|
59
60
|
|
|
61
|
+
const isNotPill = useBooleanProperty(elementRef, 'notPill', notPill);
|
|
62
|
+
const isClickable = useBooleanProperty(elementRef, 'clickable', clickable);
|
|
63
|
+
const isDismissible = useBooleanProperty(elementRef, 'dismissible', dismissible);
|
|
64
|
+
const isDisabled = useBooleanProperty(elementRef, 'disabled', disabled);
|
|
65
|
+
const isSelected = useBooleanProperty(elementRef, 'selected', selected);
|
|
66
|
+
|
|
60
67
|
return React.createElement(
|
|
61
68
|
'ty-tag',
|
|
62
69
|
{
|
|
@@ -64,11 +71,11 @@ export const TyTag = React.forwardRef<HTMLElement, TyTagProps>(
|
|
|
64
71
|
// click is dispatched as composed CustomEvent by the web component — React's
|
|
65
72
|
// synthetic onClick already catches it, so we just pass it through as onClick
|
|
66
73
|
...(onClick && { onClick }),
|
|
67
|
-
...(
|
|
68
|
-
...(
|
|
69
|
-
...(
|
|
70
|
-
...(
|
|
71
|
-
...(
|
|
74
|
+
...(isNotPill && { 'not-pill': "" }),
|
|
75
|
+
...(isClickable && { clickable: "" }),
|
|
76
|
+
...(isDismissible && { dismissible: "" }),
|
|
77
|
+
...(isDisabled && { disabled: "" }),
|
|
78
|
+
...(isSelected && { selected: "" }),
|
|
72
79
|
ref: elementRef,
|
|
73
80
|
},
|
|
74
81
|
children
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import React, { useEffect, useRef, useCallback } from 'react';
|
|
2
2
|
import { needsPropertyBridge } from '../utils/react-version';
|
|
3
|
+
import { useBooleanProperty } from '../utils/use-boolean-prop';
|
|
3
4
|
|
|
4
5
|
// Event detail structure for ty-textarea events
|
|
5
6
|
export interface TyTextareaEventDetail {
|
|
@@ -160,12 +161,15 @@ export const TyTextarea = React.forwardRef<HTMLElement, TyTextareaProps>(
|
|
|
160
161
|
}
|
|
161
162
|
}, [(props as any).value]);
|
|
162
163
|
|
|
164
|
+
const isDisabled = useBooleanProperty(elementRef, 'disabled', disabled);
|
|
165
|
+
const isRequired = useBooleanProperty(elementRef, 'required', required);
|
|
166
|
+
|
|
163
167
|
return React.createElement(
|
|
164
168
|
'ty-textarea',
|
|
165
169
|
{
|
|
166
170
|
...props,
|
|
167
|
-
...(
|
|
168
|
-
...(
|
|
171
|
+
...(isDisabled && { disabled: "" }),
|
|
172
|
+
...(isRequired && { required: "" }),
|
|
169
173
|
...(minHeight && { 'min-height': minHeight }), // Convert camelCase to kebab-case
|
|
170
174
|
...(maxHeight && { 'max-height': maxHeight }), // Convert camelCase to kebab-case
|
|
171
175
|
ref: elementRef,
|
|
@@ -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 Tooltip component
|
|
4
5
|
export interface TyTooltipProps extends React.HTMLAttributes<HTMLElement> {
|
|
@@ -64,9 +65,8 @@ export const TyTooltip = React.forwardRef<HTMLElement, TyTooltipProps>(
|
|
|
64
65
|
webComponentProps.delay = delay.toString();
|
|
65
66
|
}
|
|
66
67
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
}
|
|
68
|
+
const isDisabled = useBooleanProperty(elementRef, 'disabled', disabled);
|
|
69
|
+
if (isDisabled) webComponentProps.disabled = '';
|
|
70
70
|
|
|
71
71
|
if (flavor) {
|
|
72
72
|
webComponentProps.flavor = flavor;
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { useEffect, RefObject } from 'react';
|
|
2
|
+
import { needsPropertyBridge } from './react-version';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Coerce a value to a boolean using the same rules as the core property
|
|
6
|
+
* manager (packages/core/src/utils/property-manager.ts:142-152). This matters
|
|
7
|
+
* because consumers sometimes pass the string "false" through untyped call
|
|
8
|
+
* sites (JSON config, query params, server-rendered props) — and the JS
|
|
9
|
+
* `Boolean("false")` is surprisingly `true`.
|
|
10
|
+
*
|
|
11
|
+
* undefined | null | false | "false" | "0" → false
|
|
12
|
+
* "" → true (HTML boolean-attribute convention)
|
|
13
|
+
* any other truthy → true
|
|
14
|
+
*/
|
|
15
|
+
export function coerceBool(value: unknown): boolean {
|
|
16
|
+
if (value === undefined || value === null || value === false) return false;
|
|
17
|
+
if (typeof value === 'string') {
|
|
18
|
+
if (value === '') return true;
|
|
19
|
+
const norm = value.toLowerCase().trim();
|
|
20
|
+
if (norm === 'false' || norm === '0') return false;
|
|
21
|
+
return true;
|
|
22
|
+
}
|
|
23
|
+
return Boolean(value);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Imperatively keep a boolean property on the underlying custom element in
|
|
28
|
+
* sync with its React prop.
|
|
29
|
+
*
|
|
30
|
+
* Why this exists: React 18 sets boolean attributes as empty strings on the
|
|
31
|
+
* first render to a custom element but does not reliably *remove* them when
|
|
32
|
+
* the prop flips back to `false`. Without this bridge, `<TyModal open>` →
|
|
33
|
+
* `<TyModal open={false}>` leaves the `open` attribute on the element and
|
|
34
|
+
* the modal stays open. Same class of bug affects `disabled`, `required`,
|
|
35
|
+
* `clearable`, `loading`, `readonly`, `protected`, etc.
|
|
36
|
+
*
|
|
37
|
+
* React 19+ bridges this natively — the effect short-circuits via
|
|
38
|
+
* `needsPropertyBridge`, so this is dead code on modern React.
|
|
39
|
+
*
|
|
40
|
+
* Pass the *camelCase JS property name* on the element (e.g. `externalSearch`
|
|
41
|
+
* for the `external-search` attribute). The core base class handles the
|
|
42
|
+
* attribute-side sync once the property changes.
|
|
43
|
+
*
|
|
44
|
+
* Returns the coerced boolean so the caller can also drive its conditional
|
|
45
|
+
* JSX attribute emission with a value that correctly handles "false".
|
|
46
|
+
*/
|
|
47
|
+
export function useBooleanProperty(
|
|
48
|
+
ref: RefObject<HTMLElement | null>,
|
|
49
|
+
propName: string,
|
|
50
|
+
value: unknown
|
|
51
|
+
): boolean {
|
|
52
|
+
const coerced = coerceBool(value);
|
|
53
|
+
useEffect(() => {
|
|
54
|
+
if (!needsPropertyBridge) return;
|
|
55
|
+
const el = ref.current as any;
|
|
56
|
+
if (!el) return;
|
|
57
|
+
if (Boolean(el[propName]) !== coerced) {
|
|
58
|
+
el[propName] = coerced;
|
|
59
|
+
}
|
|
60
|
+
}, [coerced, propName, ref]);
|
|
61
|
+
return coerced;
|
|
62
|
+
}
|