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
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
import React, { useEffect, useRef, useCallback } from 'react';
|
|
2
|
+
import { needsPropertyBridge } from '../utils/react-version';
|
|
3
|
+
import { useBooleanProperty } from '../utils/use-boolean-prop';
|
|
4
|
+
|
|
5
|
+
// Type definitions for Ty Select component
|
|
6
|
+
export interface TySelectItem {
|
|
7
|
+
value: string;
|
|
8
|
+
label: string;
|
|
9
|
+
flavor: string | null;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface TySelectEventDetail {
|
|
13
|
+
/** Scalar for single select, array when `multiple` */
|
|
14
|
+
value: string | string[] | null;
|
|
15
|
+
/** Always the array form of the selection */
|
|
16
|
+
values: string[];
|
|
17
|
+
/** Rich info per selected value — enough to render chips out-of-band */
|
|
18
|
+
items: TySelectItem[];
|
|
19
|
+
/** Action that triggered the change */
|
|
20
|
+
action: 'add' | 'remove' | 'clear' | 'set';
|
|
21
|
+
/** The specific value that changed */
|
|
22
|
+
item: string | null;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface TySelectProps extends Omit<React.HTMLAttributes<HTMLElement>, 'onChange' | 'style'> {
|
|
26
|
+
style?: import('./TyInput').TyInputCSSProperties;
|
|
27
|
+
|
|
28
|
+
/** Selected value(s) — string, comma-separated string, or array */
|
|
29
|
+
value?: string | string[];
|
|
30
|
+
|
|
31
|
+
/** Multi select (native <select multiple> semantics). Default: single. */
|
|
32
|
+
multiple?: boolean;
|
|
33
|
+
|
|
34
|
+
/** Compact content-hugging trigger (toolbars, filter bars) instead of the full-width field look */
|
|
35
|
+
compact?: boolean;
|
|
36
|
+
|
|
37
|
+
/** Placeholder shown while nothing is selected */
|
|
38
|
+
placeholder?: string;
|
|
39
|
+
|
|
40
|
+
/** Label text above the field */
|
|
41
|
+
label?: string;
|
|
42
|
+
|
|
43
|
+
/** Form field name — single submits one entry, multiple submits repeated entries */
|
|
44
|
+
name?: string;
|
|
45
|
+
|
|
46
|
+
/** Disable the select */
|
|
47
|
+
disabled?: boolean;
|
|
48
|
+
|
|
49
|
+
/** Read-only */
|
|
50
|
+
readonly?: boolean;
|
|
51
|
+
|
|
52
|
+
/** Mark the field as required */
|
|
53
|
+
required?: boolean;
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Search row visibility: 'auto' (default) shows it only for 8+ options,
|
|
57
|
+
* 'always' / 'never' force it. external-search always shows it.
|
|
58
|
+
*/
|
|
59
|
+
searchable?: 'auto' | 'always' | 'never' | boolean;
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* External (remote) search mode — the component stops filtering and emits
|
|
63
|
+
* debounced `search` events; replace the option children in response.
|
|
64
|
+
*/
|
|
65
|
+
externalSearch?: boolean;
|
|
66
|
+
|
|
67
|
+
/** Debounce for the search event in ms (0-5000) */
|
|
68
|
+
debounce?: number;
|
|
69
|
+
|
|
70
|
+
/** Loading state — shows a spinner in the options area (external search in flight) */
|
|
71
|
+
loading?: boolean;
|
|
72
|
+
|
|
73
|
+
/** Size variant */
|
|
74
|
+
size?: 'sm' | 'md' | 'lg';
|
|
75
|
+
|
|
76
|
+
/** Callback when selection changes */
|
|
77
|
+
onChange?: (event: CustomEvent<TySelectEventDetail>) => void;
|
|
78
|
+
|
|
79
|
+
/** Callback fired on each search input change (debounced). Use for external/server-side filtering. */
|
|
80
|
+
onSearch?: (event: CustomEvent<{ query: string; element: HTMLElement }>) => void;
|
|
81
|
+
onOpen?: (event: CustomEvent) => void;
|
|
82
|
+
onClose?: (event: CustomEvent) => void;
|
|
83
|
+
|
|
84
|
+
/** TyOption children (plus optional slot="trigger" / slot="start" / slot="end" elements) */
|
|
85
|
+
children?: React.ReactNode;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// React wrapper for ty-select web component
|
|
89
|
+
export const TySelect = React.forwardRef<HTMLElement, TySelectProps>(
|
|
90
|
+
({
|
|
91
|
+
value,
|
|
92
|
+
multiple,
|
|
93
|
+
compact,
|
|
94
|
+
placeholder,
|
|
95
|
+
label,
|
|
96
|
+
name,
|
|
97
|
+
disabled,
|
|
98
|
+
readonly,
|
|
99
|
+
required,
|
|
100
|
+
searchable,
|
|
101
|
+
externalSearch,
|
|
102
|
+
debounce,
|
|
103
|
+
loading,
|
|
104
|
+
size,
|
|
105
|
+
onChange,
|
|
106
|
+
onSearch,
|
|
107
|
+
onOpen,
|
|
108
|
+
onClose,
|
|
109
|
+
children,
|
|
110
|
+
...props
|
|
111
|
+
}, ref) => {
|
|
112
|
+
const elementRef = useRef<HTMLElement>(null);
|
|
113
|
+
|
|
114
|
+
// Handle ref forwarding
|
|
115
|
+
useEffect(() => {
|
|
116
|
+
if (ref && elementRef.current) {
|
|
117
|
+
if (typeof ref === 'function') {
|
|
118
|
+
ref(elementRef.current);
|
|
119
|
+
} else {
|
|
120
|
+
ref.current = elementRef.current;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}, [ref]);
|
|
124
|
+
|
|
125
|
+
// Imperatively sync `value` so resets ('' or null) reliably clear the
|
|
126
|
+
// visible selection. React 18 workaround; React 19+ handles this natively.
|
|
127
|
+
useEffect(() => {
|
|
128
|
+
if (!needsPropertyBridge) return;
|
|
129
|
+
const element = elementRef.current as any;
|
|
130
|
+
if (!element) return;
|
|
131
|
+
const next = Array.isArray(value) ? value.join(',') : (value ?? '');
|
|
132
|
+
if (element.value !== next) {
|
|
133
|
+
element.value = next;
|
|
134
|
+
}
|
|
135
|
+
}, [value]);
|
|
136
|
+
|
|
137
|
+
// Handle events
|
|
138
|
+
const handleChange = useCallback((event: Event) => {
|
|
139
|
+
if (onChange) onChange(event as CustomEvent<TySelectEventDetail>);
|
|
140
|
+
}, [onChange]);
|
|
141
|
+
|
|
142
|
+
const handleSearch = useCallback((event: Event) => {
|
|
143
|
+
if (onSearch) onSearch(event as CustomEvent<{ query: string; element: HTMLElement }>);
|
|
144
|
+
}, [onSearch]);
|
|
145
|
+
|
|
146
|
+
const handleOpen = useCallback((event: Event) => { if (onOpen) onOpen(event as CustomEvent); }, [onOpen]);
|
|
147
|
+
const handleClose = useCallback((event: Event) => { if (onClose) onClose(event as CustomEvent); }, [onClose]);
|
|
148
|
+
|
|
149
|
+
// Set up event listeners
|
|
150
|
+
useEffect(() => {
|
|
151
|
+
const element = elementRef.current;
|
|
152
|
+
if (!element) return;
|
|
153
|
+
|
|
154
|
+
if (onChange) element.addEventListener('change', handleChange);
|
|
155
|
+
if (onSearch) element.addEventListener('search', handleSearch);
|
|
156
|
+
if (onOpen) element.addEventListener('open', handleOpen);
|
|
157
|
+
if (onClose) element.addEventListener('close', handleClose);
|
|
158
|
+
|
|
159
|
+
return () => {
|
|
160
|
+
if (onChange) element.removeEventListener('change', handleChange);
|
|
161
|
+
if (onSearch) element.removeEventListener('search', handleSearch);
|
|
162
|
+
if (onOpen) element.removeEventListener('open', handleOpen);
|
|
163
|
+
if (onClose) element.removeEventListener('close', handleClose);
|
|
164
|
+
};
|
|
165
|
+
}, [handleChange, handleSearch, handleOpen, handleClose, onChange, onSearch, onOpen, onClose]);
|
|
166
|
+
|
|
167
|
+
// Imperative property sync for boolean props (see use-boolean-prop.ts).
|
|
168
|
+
const isMultiple = useBooleanProperty(elementRef, 'multiple', multiple);
|
|
169
|
+
const isCompact = useBooleanProperty(elementRef, 'compact', compact);
|
|
170
|
+
const isDisabled = useBooleanProperty(elementRef, 'disabled', disabled);
|
|
171
|
+
const isReadonly = useBooleanProperty(elementRef, 'readonly', readonly);
|
|
172
|
+
const isRequired = useBooleanProperty(elementRef, 'required', required);
|
|
173
|
+
const isLoading = useBooleanProperty(elementRef, 'loading', loading);
|
|
174
|
+
const isExternalSearch = useBooleanProperty(elementRef, 'externalSearch', externalSearch);
|
|
175
|
+
|
|
176
|
+
// Convert React props to web component attributes
|
|
177
|
+
const webComponentProps: Record<string, any> = {
|
|
178
|
+
...props,
|
|
179
|
+
ref: elementRef,
|
|
180
|
+
};
|
|
181
|
+
|
|
182
|
+
// Handle value conversion (array to comma-separated string)
|
|
183
|
+
if (value !== undefined) {
|
|
184
|
+
webComponentProps.value = Array.isArray(value) ? value.join(',') : value;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
if (isMultiple) webComponentProps.multiple = '';
|
|
188
|
+
if (isCompact) webComponentProps.compact = '';
|
|
189
|
+
if (isDisabled) webComponentProps.disabled = '';
|
|
190
|
+
if (isReadonly) webComponentProps.readonly = '';
|
|
191
|
+
if (isRequired) webComponentProps.required = '';
|
|
192
|
+
if (isLoading) webComponentProps.loading = '';
|
|
193
|
+
if (isExternalSearch) webComponentProps['external-search'] = '';
|
|
194
|
+
|
|
195
|
+
if (placeholder) webComponentProps.placeholder = placeholder;
|
|
196
|
+
if (label) webComponentProps.label = label;
|
|
197
|
+
if (name) webComponentProps.name = name;
|
|
198
|
+
if (size) webComponentProps.size = size;
|
|
199
|
+
if (debounce !== undefined) webComponentProps.debounce = debounce.toString();
|
|
200
|
+
if (searchable === 'always' || searchable === true) webComponentProps.searchable = 'true';
|
|
201
|
+
else if (searchable === 'never' || searchable === false) webComponentProps.searchable = 'false';
|
|
202
|
+
// 'auto' / undefined → omit (component default)
|
|
203
|
+
|
|
204
|
+
return React.createElement('ty-select', webComponentProps, children);
|
|
205
|
+
}
|
|
206
|
+
);
|
|
207
|
+
|
|
208
|
+
TySelect.displayName = 'TySelect';
|
|
209
|
+
|
|
210
|
+
// ============================================================================
|
|
211
|
+
// TySelectedTags — out-of-band chip display for a TySelect
|
|
212
|
+
// ============================================================================
|
|
213
|
+
|
|
214
|
+
export interface TySelectedTagsProps extends React.HTMLAttributes<HTMLElement> {
|
|
215
|
+
/** id of the ty-select to display. Falls back to the previous element sibling. */
|
|
216
|
+
htmlFor?: string;
|
|
217
|
+
/** Optional <template> child as chip blueprint ({value}/{label}/{flavor}/{data-*} placeholders) */
|
|
218
|
+
children?: React.ReactNode;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// React wrapper for ty-selected-tags web component
|
|
222
|
+
export const TySelectedTags = React.forwardRef<HTMLElement, TySelectedTagsProps>(
|
|
223
|
+
({ htmlFor, children, ...props }, ref) => {
|
|
224
|
+
const elementRef = useRef<HTMLElement>(null);
|
|
225
|
+
|
|
226
|
+
useEffect(() => {
|
|
227
|
+
if (ref && elementRef.current) {
|
|
228
|
+
if (typeof ref === 'function') ref(elementRef.current);
|
|
229
|
+
else ref.current = elementRef.current;
|
|
230
|
+
}
|
|
231
|
+
}, [ref]);
|
|
232
|
+
|
|
233
|
+
const webComponentProps: Record<string, any> = { ...props, ref: elementRef };
|
|
234
|
+
if (htmlFor) webComponentProps.for = htmlFor;
|
|
235
|
+
|
|
236
|
+
return React.createElement('ty-selected-tags', webComponentProps, children);
|
|
237
|
+
}
|
|
238
|
+
);
|
|
239
|
+
|
|
240
|
+
TySelectedTags.displayName = 'TySelectedTags';
|
package/src/components/index.ts
CHANGED
|
@@ -55,6 +55,9 @@ export type { TyIconProps } from './TyIcon';
|
|
|
55
55
|
|
|
56
56
|
export { TyModal } from './TyModal';
|
|
57
57
|
export type { TyModalProps, TyModalEventDetail, TyModalRef } from './TyModal';
|
|
58
|
+
// Platform/ARIA name alias (the element is also registered as ty-dialog)
|
|
59
|
+
export { TyModal as TyDialog } from './TyModal';
|
|
60
|
+
export type { TyModalProps as TyDialogProps, TyModalEventDetail as TyDialogEventDetail, TyModalRef as TyDialogRef } from './TyModal';
|
|
58
61
|
|
|
59
62
|
export { TyTooltip } from './TyTooltip';
|
|
60
63
|
export type { TyTooltipProps } from './TyTooltip';
|
|
@@ -62,6 +65,9 @@ export type { TyTooltipProps } from './TyTooltip';
|
|
|
62
65
|
export { TyMultiselect } from './TyMultiselect';
|
|
63
66
|
export type { TyMultiselectProps, TyMultiselectEventDetail } from './TyMultiselect';
|
|
64
67
|
|
|
68
|
+
export { TySelect, TySelectedTags } from './TySelect';
|
|
69
|
+
export type { TySelectProps, TySelectEventDetail, TySelectItem, TySelectedTagsProps } from './TySelect';
|
|
70
|
+
|
|
65
71
|
export { TyCalendar } from './TyCalendar';
|
|
66
72
|
export type { TyCalendarProps, TyCalendarChangeEventDetail, TyCalendarNavigateEventDetail } from './TyCalendar';
|
|
67
73
|
|
|
@@ -85,6 +91,9 @@ export type { TyRadioGroupProps, TyRadioGroupEventDetail } from './TyRadioGroup'
|
|
|
85
91
|
|
|
86
92
|
export { TyCopy } from './TyCopy';
|
|
87
93
|
export type { TyCopyProps } from './TyCopy';
|
|
94
|
+
// Descriptive name alias (the element is also registered as ty-copy-field)
|
|
95
|
+
export { TyCopy as TyCopyField } from './TyCopy';
|
|
96
|
+
export type { TyCopyProps as TyCopyFieldProps } from './TyCopy';
|
|
88
97
|
|
|
89
98
|
export { TyFileUpload } from './TyFileUpload';
|
|
90
99
|
export type { TyFileUploadProps, TyFileUploadEventDetail } from './TyFileUpload';
|
package/src/version.ts
CHANGED
|
@@ -1,155 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Test Example: React Event Convention
|
|
3
|
-
*
|
|
4
|
-
* This file demonstrates the new event handling convention
|
|
5
|
-
* for tyrell-react components.
|
|
6
|
-
*
|
|
7
|
-
* To test:
|
|
8
|
-
* 1. Create a new React project or use existing
|
|
9
|
-
* 2. npm install tyrell-react
|
|
10
|
-
* 3. Add this component to your app
|
|
11
|
-
* 4. Observe console logs and state updates
|
|
12
|
-
*/
|
|
13
|
-
|
|
14
|
-
import React, { useState } from 'react';
|
|
15
|
-
import { TyInput } from './TyInput';
|
|
16
|
-
import { TyTextarea } from './TyTextarea';
|
|
17
|
-
import { TyCheckbox } from './TyCheckbox';
|
|
18
|
-
import type { TyInputEventDetail } from './TyInput';
|
|
19
|
-
import type { TyTextareaEventDetail } from './TyTextarea';
|
|
20
|
-
import type { TyCheckboxEventDetail } from './TyCheckbox';
|
|
21
|
-
|
|
22
|
-
export function EventConventionTest() {
|
|
23
|
-
const [inputValue, setInputValue] = useState('');
|
|
24
|
-
const [textareaValue, setTextareaValue] = useState('');
|
|
25
|
-
const [checked, setChecked] = useState(false);
|
|
26
|
-
const [logs, setLogs] = useState<string[]>([]);
|
|
27
|
-
|
|
28
|
-
const addLog = (message: string) => {
|
|
29
|
-
setLogs(prev => [...prev, `${new Date().toLocaleTimeString()}: ${message}`]);
|
|
30
|
-
};
|
|
31
|
-
|
|
32
|
-
return (
|
|
33
|
-
<div className="p-8 space-y-6 max-w-2xl">
|
|
34
|
-
<h1 className="text-2xl font-bold">React Event Convention Test</h1>
|
|
35
|
-
|
|
36
|
-
{/* Input Test */}
|
|
37
|
-
<div className="space-y-2">
|
|
38
|
-
<h2 className="text-lg font-semibold">TyInput Test</h2>
|
|
39
|
-
<TyInput
|
|
40
|
-
label="Email"
|
|
41
|
-
placeholder="Type to test onChange..."
|
|
42
|
-
value={inputValue}
|
|
43
|
-
onChange={(e: CustomEvent<TyInputEventDetail>) => {
|
|
44
|
-
setInputValue(e.detail.value);
|
|
45
|
-
addLog(`onChange: "${e.detail.value}" (fires on keystroke)`);
|
|
46
|
-
}}
|
|
47
|
-
onChangeCommit={(e: CustomEvent<TyInputEventDetail>) => {
|
|
48
|
-
addLog(`onChangeCommit: "${e.detail.value}" (fires on blur)`);
|
|
49
|
-
}}
|
|
50
|
-
onFocus={() => addLog('onFocus')}
|
|
51
|
-
onBlur={() => addLog('onBlur')}
|
|
52
|
-
/>
|
|
53
|
-
<p className="text-sm text-gray-600">
|
|
54
|
-
Current value: <strong>{inputValue}</strong>
|
|
55
|
-
</p>
|
|
56
|
-
</div>
|
|
57
|
-
|
|
58
|
-
{/* Textarea Test */}
|
|
59
|
-
<div className="space-y-2">
|
|
60
|
-
<h2 className="text-lg font-semibold">TyTextarea Test</h2>
|
|
61
|
-
<TyTextarea
|
|
62
|
-
label="Comments"
|
|
63
|
-
placeholder="Type to test onChange..."
|
|
64
|
-
value={textareaValue}
|
|
65
|
-
rows={3}
|
|
66
|
-
onChange={(e: CustomEvent<TyTextareaEventDetail>) => {
|
|
67
|
-
setTextareaValue(e.detail.value);
|
|
68
|
-
addLog(`Textarea onChange: "${e.detail.value}"`);
|
|
69
|
-
}}
|
|
70
|
-
onChangeCommit={(e: CustomEvent<TyTextareaEventDetail>) => {
|
|
71
|
-
addLog(`Textarea onChangeCommit: "${e.detail.value}"`);
|
|
72
|
-
}}
|
|
73
|
-
/>
|
|
74
|
-
<p className="text-sm text-gray-600">
|
|
75
|
-
Current value: <strong>{textareaValue}</strong>
|
|
76
|
-
</p>
|
|
77
|
-
</div>
|
|
78
|
-
|
|
79
|
-
{/* Checkbox Test */}
|
|
80
|
-
<div className="space-y-2">
|
|
81
|
-
<h2 className="text-lg font-semibold">TyCheckbox Test</h2>
|
|
82
|
-
<TyCheckbox
|
|
83
|
-
checked={checked}
|
|
84
|
-
onChange={(e: CustomEvent<TyCheckboxEventDetail>) => {
|
|
85
|
-
setChecked(e.detail.checked);
|
|
86
|
-
addLog(`Checkbox onChange: ${e.detail.checked} (fires immediately)`);
|
|
87
|
-
}}
|
|
88
|
-
onChangeCommit={(e: CustomEvent<TyCheckboxEventDetail>) => {
|
|
89
|
-
addLog(`Checkbox onChangeCommit: ${e.detail.checked} (fires on blur)`);
|
|
90
|
-
}}
|
|
91
|
-
>
|
|
92
|
-
Subscribe to newsletter
|
|
93
|
-
</TyCheckbox>
|
|
94
|
-
<p className="text-sm text-gray-600">
|
|
95
|
-
Current state: <strong>{checked ? 'Checked' : 'Unchecked'}</strong>
|
|
96
|
-
</p>
|
|
97
|
-
</div>
|
|
98
|
-
|
|
99
|
-
{/* Event Log */}
|
|
100
|
-
<div className="space-y-2">
|
|
101
|
-
<h2 className="text-lg font-semibold">Event Log</h2>
|
|
102
|
-
<div className="bg-gray-100 p-4 rounded max-h-64 overflow-y-auto font-mono text-xs">
|
|
103
|
-
{logs.length === 0 ? (
|
|
104
|
-
<p className="text-gray-500">No events yet. Start typing or checking boxes!</p>
|
|
105
|
-
) : (
|
|
106
|
-
logs.map((log, i) => (
|
|
107
|
-
<div key={i} className="text-gray-800">
|
|
108
|
-
{log}
|
|
109
|
-
</div>
|
|
110
|
-
))
|
|
111
|
-
)}
|
|
112
|
-
</div>
|
|
113
|
-
<button
|
|
114
|
-
onClick={() => setLogs([])}
|
|
115
|
-
className="px-4 py-2 bg-gray-200 rounded hover:bg-gray-300"
|
|
116
|
-
>
|
|
117
|
-
Clear Log
|
|
118
|
-
</button>
|
|
119
|
-
</div>
|
|
120
|
-
|
|
121
|
-
{/* Expected Behavior */}
|
|
122
|
-
<div className="bg-blue-50 p-4 rounded">
|
|
123
|
-
<h3 className="font-semibold mb-2">Expected Behavior:</h3>
|
|
124
|
-
<ul className="list-disc list-inside space-y-1 text-sm">
|
|
125
|
-
<li>
|
|
126
|
-
<strong>onChange</strong> - Fires on every keystroke/state change (React convention)
|
|
127
|
-
</li>
|
|
128
|
-
<li>
|
|
129
|
-
<strong>onChangeCommit</strong> - Fires on blur if value changed (optional)
|
|
130
|
-
</li>
|
|
131
|
-
<li>
|
|
132
|
-
<strong>onFocus</strong> - Fires when element gains focus
|
|
133
|
-
</li>
|
|
134
|
-
<li>
|
|
135
|
-
<strong>onBlur</strong> - Fires when element loses focus
|
|
136
|
-
</li>
|
|
137
|
-
</ul>
|
|
138
|
-
</div>
|
|
139
|
-
|
|
140
|
-
{/* Verification */}
|
|
141
|
-
<div className="bg-green-50 p-4 rounded">
|
|
142
|
-
<h3 className="font-semibold mb-2">Verification Checklist:</h3>
|
|
143
|
-
<ul className="list-disc list-inside space-y-1 text-sm">
|
|
144
|
-
<li>✅ onChange fires on EVERY keystroke (not just on blur)</li>
|
|
145
|
-
<li>✅ State updates in real-time as you type</li>
|
|
146
|
-
<li>✅ onChangeCommit fires ONLY on blur (if value changed)</li>
|
|
147
|
-
<li>✅ Event order: onChange → onBlur → onChangeCommit</li>
|
|
148
|
-
<li>✅ Checkbox onChange fires immediately on click</li>
|
|
149
|
-
</ul>
|
|
150
|
-
</div>
|
|
151
|
-
</div>
|
|
152
|
-
);
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
export default EventConventionTest;
|