tyrell-react 1.0.0-TC25 → 1.0.0-TC27

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 (54) hide show
  1. package/dist/components/TyCalendar.d.ts +4 -0
  2. package/dist/components/TyCalendar.d.ts.map +1 -1
  3. package/dist/components/TyCalendar.js +7 -1
  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/TyDatePicker.d.ts +4 -0
  18. package/dist/components/TyDatePicker.d.ts.map +1 -1
  19. package/dist/components/TyDatePicker.js +7 -1
  20. package/dist/components/TyDatePicker.js.map +1 -1
  21. package/dist/components/TyOption.d.ts +8 -0
  22. package/dist/components/TyOption.d.ts.map +1 -1
  23. package/dist/components/TyOption.js +3 -1
  24. package/dist/components/TyOption.js.map +1 -1
  25. package/dist/components/TySelect.d.ts +75 -0
  26. package/dist/components/TySelect.d.ts.map +1 -0
  27. package/dist/components/{TyMultiselect.js → TySelect.js} +54 -43
  28. package/dist/components/TySelect.js.map +1 -0
  29. package/dist/components/index.d.ts +6 -8
  30. package/dist/components/index.d.ts.map +1 -1
  31. package/dist/components/index.js +5 -4
  32. package/dist/components/index.js.map +1 -1
  33. package/dist/version.d.ts +1 -1
  34. package/dist/version.js +1 -1
  35. package/package.json +1 -1
  36. package/src/components/TyCalendar.tsx +16 -0
  37. package/src/components/TyCalendarMonth.tsx +11 -1
  38. package/src/components/TyCalendarNavigation.tsx +11 -1
  39. package/src/components/TyCheckbox.tsx +8 -2
  40. package/src/components/TyDatePicker.tsx +16 -0
  41. package/src/components/TyOption.tsx +14 -1
  42. package/src/components/TySelect.tsx +240 -0
  43. package/src/components/index.ts +8 -13
  44. package/src/version.ts +1 -1
  45. package/dist/components/TyDropdown.d.ts +0 -64
  46. package/dist/components/TyDropdown.d.ts.map +0 -1
  47. package/dist/components/TyDropdown.js +0 -136
  48. package/dist/components/TyDropdown.js.map +0 -1
  49. package/dist/components/TyMultiselect.d.ts +0 -59
  50. package/dist/components/TyMultiselect.d.ts.map +0 -1
  51. package/dist/components/TyMultiselect.js.map +0 -1
  52. package/src/components/EventConventionTest.tsx +0 -155
  53. package/src/components/TyDropdown.tsx +0 -252
  54. package/src/components/TyMultiselect.tsx +0 -219
@@ -1,252 +0,0 @@
1
- import React, { useEffect, useRef, useCallback } from 'react';
2
- import { needsPropertyBridge } from '../utils/react-version';
3
- import { useBooleanProperty, coerceBool } from '../utils/use-boolean-prop';
4
-
5
- // Option data structure for data-driven approach
6
- export interface OptionData {
7
- value: string;
8
- text: string;
9
- disabled?: boolean;
10
- }
11
-
12
- // Event detail structure for dropdown events
13
- export interface TyDropdownEventDetail {
14
- option: HTMLElement;
15
- }
16
-
17
- // Type definitions for Ty Dropdown component
18
- export interface TyDropdownProps extends Omit<React.HTMLAttributes<HTMLElement>, 'onChange' | 'style'> {
19
- style?: import('./TyInput').TyInputCSSProperties;
20
- /** Semantic styling variant */
21
- flavor?: 'primary' | 'secondary' | 'success' | 'danger' | 'warning' | 'neutral';
22
-
23
- /** Dropdown size */
24
- size?: 'xs' | 'sm' | 'md' | 'lg' | 'xl';
25
-
26
- /** Selected value */
27
- value?: string;
28
-
29
- /** Placeholder text */
30
- placeholder?: string;
31
-
32
- /** Dropdown label */
33
- label?: string;
34
-
35
- /** Disable the dropdown */
36
- disabled?: boolean;
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
-
45
- /** Make dropdown readonly */
46
- readonly?: boolean;
47
-
48
- /** Required field */
49
- required?: boolean;
50
-
51
- /** Show clear button */
52
- clearable?: boolean;
53
-
54
- /** Disable clear button (alias for clearable={false}) */
55
- notClearable?: boolean;
56
-
57
- /** Debounce in milliseconds (0-5000) */
58
- debounce?: number;
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
- */
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;
73
-
74
- /** Form field name for form submission */
75
- name?: string;
76
-
77
- // Data-driven approach
78
- options?: OptionData[];
79
-
80
- // React event handlers
81
- onChange?: (event: CustomEvent<TyDropdownEventDetail>) => void;
82
- onSearch?: (event: CustomEvent<{ query: string; element: HTMLElement }>) => void;
83
- onOpen?: (event: CustomEvent) => void;
84
- onClose?: (event: CustomEvent) => void;
85
-
86
- // Children for slotted approach
87
- children?: React.ReactNode;
88
- }
89
-
90
- // React wrapper for ty-dropdown web component
91
- export const TyDropdown = React.forwardRef<HTMLElement, TyDropdownProps>(
92
- ({
93
- options,
94
- children,
95
- onChange,
96
- onSearch,
97
- onOpen,
98
- onClose,
99
- disabled,
100
- loading,
101
- externalSearch,
102
- notSearchable,
103
- searchable,
104
- clearable,
105
- notClearable,
106
- debounce,
107
- name,
108
- value,
109
- ...props
110
- }, ref) => {
111
- const elementRef = useRef<HTMLElement>(null);
112
-
113
- const handleChange = useCallback((event: CustomEvent<TyDropdownEventDetail>) => {
114
- if (onChange) {
115
- onChange(event);
116
- }
117
- }, [onChange]);
118
-
119
- const handleSearch = useCallback((event: CustomEvent<{ query: string; element: HTMLElement }>) => {
120
- if (onSearch) {
121
- onSearch(event);
122
- }
123
- }, [onSearch]);
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
-
128
- useEffect(() => {
129
- const element = elementRef.current;
130
- if (!element) return;
131
-
132
- // Listen for custom events from ty-dropdown
133
- if (onChange) {
134
- element.addEventListener('change', handleChange as EventListener);
135
- }
136
-
137
- if (onSearch) {
138
- element.addEventListener('search', handleSearch as EventListener);
139
- }
140
-
141
- if (onOpen) element.addEventListener('open', handleOpen);
142
- if (onClose) element.addEventListener('close', handleClose);
143
-
144
- return () => {
145
- if (onChange) {
146
- element.removeEventListener('change', handleChange as EventListener);
147
- }
148
- if (onSearch) {
149
- element.removeEventListener('search', handleSearch as EventListener);
150
- }
151
- if (onOpen) element.removeEventListener('open', handleOpen);
152
- if (onClose) element.removeEventListener('close', handleClose);
153
- };
154
- }, [handleChange, handleSearch, handleOpen, handleClose, onChange, onSearch, onOpen, onClose]);
155
-
156
- // Imperatively sync `value` to the underlying element's property whenever
157
- // it changes. React 18's prop-to-property bridging for custom elements is
158
- // unreliable for empty strings (programmatic resets), so we set the
159
- // property directly to guarantee the dropdown clears on `value=""`.
160
- // React 19+ handles this natively, so the effect short-circuits there.
161
- useEffect(() => {
162
- if (!needsPropertyBridge) return;
163
- const element = elementRef.current as any;
164
- if (!element) return;
165
- if (element.value !== value) {
166
- element.value = value ?? '';
167
- }
168
- }, [value]);
169
-
170
- // Handle ref forwarding
171
- useEffect(() => {
172
- if (ref && elementRef.current) {
173
- if (typeof ref === 'function') {
174
- ref(elementRef.current);
175
- } else {
176
- ref.current = elementRef.current;
177
- }
178
- }
179
- }, [ref]);
180
-
181
- // Render options from data if provided, otherwise use children
182
- const renderContent = () => {
183
- if (options && options.length > 0) {
184
- // Data-driven approach - create ty-option elements
185
- return options.map((option, index) =>
186
- React.createElement(
187
- 'ty-option',
188
- {
189
- key: option.value || index,
190
- value: option.value,
191
- ...(option.disabled && { disabled: "" }),
192
- },
193
- option.text
194
- )
195
- );
196
- }
197
-
198
- // Slotted approach - use provided children
199
- return children;
200
- };
201
-
202
- // Imperative property sync for boolean props (see use-boolean-prop.ts).
203
- const isDisabled = useBooleanProperty(elementRef, 'disabled', disabled);
204
- const isLoading = useBooleanProperty(elementRef, 'loading', loading);
205
- const isExternalSearch = coerceBool(externalSearch) || coerceBool(notSearchable) || searchable === false;
206
- useBooleanProperty(elementRef, 'externalSearch', isExternalSearch);
207
- // clearable: explicit boolean OR the `notClearable` alias inverts it
208
- const isClearable = clearable !== undefined
209
- ? coerceBool(clearable)
210
- : (coerceBool(notClearable) ? false : undefined);
211
- useEffect(() => {
212
- if (!needsPropertyBridge) return;
213
- if (isClearable === undefined) return;
214
- const el = elementRef.current as any;
215
- if (!el) return;
216
- if (Boolean(el.clearable) !== isClearable) el.clearable = isClearable;
217
- }, [isClearable]);
218
-
219
- // Convert React props to web component attributes
220
- const webComponentProps: Record<string, any> = {
221
- ...props,
222
- ref: elementRef,
223
- };
224
-
225
- if (isDisabled) webComponentProps.disabled = '';
226
- if (isLoading) webComponentProps.loading = '';
227
- if (isExternalSearch) webComponentProps['external-search'] = '';
228
-
229
- // Handle clearable functionality
230
- if (isClearable === true) {
231
- webComponentProps.clearable = '';
232
- } else if (isClearable === false) {
233
- webComponentProps['not-clearable'] = '';
234
- }
235
-
236
- // Add debounce attribute
237
- if (debounce !== undefined) {
238
- webComponentProps.debounce = debounce;
239
- }
240
-
241
- // Add string attributes
242
- if (name) webComponentProps.name = name;
243
-
244
- return React.createElement(
245
- 'ty-dropdown',
246
- webComponentProps,
247
- renderContent()
248
- );
249
- }
250
- );
251
-
252
- TyDropdown.displayName = 'TyDropdown';
@@ -1,219 +0,0 @@
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 Multiselect component
6
- export interface TyMultiselectEventDetail {
7
- /** Array of currently selected values */
8
- values: string[];
9
- /** Action that triggered the change: "add" | "remove" */
10
- action: 'add' | 'remove';
11
- /** The specific item that was added or removed */
12
- item: string;
13
- }
14
-
15
- export interface TyMultiselectProps extends Omit<React.HTMLAttributes<HTMLElement>, 'onChange' | 'style'> {
16
- style?: import('./TyInput').TyInputCSSProperties;
17
- /** Current selected values as comma-separated string or array */
18
- value?: string | string[];
19
-
20
- /** Placeholder text when no items are selected */
21
- placeholder?: string;
22
-
23
- /** Disable the multiselect component */
24
- disabled?: boolean;
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
-
33
- /** Make the multiselect read-only */
34
- readonly?: boolean;
35
-
36
- /** Semantic styling variant */
37
- flavor?: 'primary' | 'secondary' | 'success' | 'danger' | 'warning' | 'neutral';
38
-
39
- /** Label text for the multiselect */
40
- label?: string;
41
-
42
- /** Mark the field as required */
43
- required?: boolean;
44
-
45
- /**
46
- * Switch to external (remote) search mode. Default is `false` — the
47
- * multiselect filters its own children client-side. Set to `true` when you
48
- * want to handle filtering yourself (e.g. server-side): the component will
49
- * dispatch `search` events on each keystroke, and you must update the children
50
- * in response. The `onSearch` prop receives those events.
51
- */
52
- externalSearch?: boolean;
53
-
54
- /** Debounce in milliseconds (0-5000) */
55
- debounce?: number;
56
-
57
- /** Mobile section label for selected items */
58
- selectedLabel?: string;
59
-
60
- /** Form field name for form submission */
61
- name?: string;
62
-
63
- /** Callback when selection changes */
64
- onChange?: (event: CustomEvent<TyMultiselectEventDetail>) => void;
65
-
66
- /** Callback fired on each search input change (debounced by `debounce`). Use for external/server-side filtering. */
67
- onSearch?: (event: CustomEvent<{ query: string; element: HTMLElement }>) => void;
68
- onOpen?: (event: CustomEvent) => void;
69
- onClose?: (event: CustomEvent) => void;
70
-
71
- /** Children should be TyTag components, not TyOption */
72
- children?: React.ReactNode;
73
- }
74
-
75
- // React wrapper for ty-multiselect web component
76
- export const TyMultiselect = React.forwardRef<HTMLElement, TyMultiselectProps>(
77
- ({
78
- value,
79
- placeholder,
80
- disabled,
81
- loading,
82
- readonly,
83
- flavor,
84
- label,
85
- required,
86
- externalSearch,
87
- debounce,
88
- selectedLabel,
89
- name,
90
- onChange,
91
- onSearch,
92
- onOpen,
93
- onClose,
94
- children,
95
- ...props
96
- }, ref) => {
97
- const elementRef = useRef<HTMLElement>(null);
98
-
99
- // Handle ref forwarding
100
- useEffect(() => {
101
- if (ref && elementRef.current) {
102
- if (typeof ref === 'function') {
103
- ref(elementRef.current);
104
- } else {
105
- ref.current = elementRef.current;
106
- }
107
- }
108
- }, [ref]);
109
-
110
- // Imperatively sync `value` to the underlying property so resets
111
- // (`value=""` or null) reliably clear the visible selection.
112
- // React 18 workaround; React 19+ handles this natively.
113
- useEffect(() => {
114
- if (!needsPropertyBridge) return;
115
- const element = elementRef.current as any;
116
- if (!element) return;
117
- const next = (props as any).value ?? '';
118
- if (element.value !== next) {
119
- element.value = next;
120
- }
121
- }, [(props as any).value]);
122
-
123
- // Handle change events
124
- const handleChange = useCallback((event: Event) => {
125
- const customEvent = event as CustomEvent<TyMultiselectEventDetail>;
126
- if (onChange) {
127
- onChange(customEvent);
128
- }
129
- }, [onChange]);
130
-
131
- const handleSearch = useCallback((event: Event) => {
132
- const customEvent = event as CustomEvent<{ query: string; element: HTMLElement }>;
133
- if (onSearch) {
134
- onSearch(customEvent);
135
- }
136
- }, [onSearch]);
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
-
141
- // Set up event listeners
142
- useEffect(() => {
143
- const element = elementRef.current;
144
- if (!element) return;
145
-
146
- if (onChange) element.addEventListener('change', handleChange);
147
- if (onSearch) element.addEventListener('search', handleSearch);
148
- if (onOpen) element.addEventListener('open', handleOpen);
149
- if (onClose) element.addEventListener('close', handleClose);
150
-
151
- return () => {
152
- if (onChange) element.removeEventListener('change', handleChange);
153
- if (onSearch) element.removeEventListener('search', handleSearch);
154
- if (onOpen) element.removeEventListener('open', handleOpen);
155
- if (onClose) element.removeEventListener('close', handleClose);
156
- };
157
- }, [handleChange, handleSearch, handleOpen, handleClose, onChange, onSearch, onOpen, onClose]);
158
-
159
- // Imperative property sync for boolean props (see use-boolean-prop.ts).
160
- const isDisabled = useBooleanProperty(elementRef, 'disabled', disabled);
161
- const isLoading = useBooleanProperty(elementRef, 'loading', loading);
162
- const isReadonly = useBooleanProperty(elementRef, 'readonly', readonly);
163
- const isRequired = useBooleanProperty(elementRef, 'required', required);
164
- const isExternalSearch = useBooleanProperty(elementRef, 'externalSearch', externalSearch);
165
-
166
- // Convert React props to web component attributes
167
- const webComponentProps: Record<string, any> = {
168
- ...props,
169
- ref: elementRef,
170
- };
171
-
172
- // Handle value conversion (array to comma-separated string)
173
- if (value !== undefined) {
174
- const valueString = Array.isArray(value) ? value.join(',') : value;
175
- webComponentProps.value = valueString;
176
- }
177
-
178
- // Add optional attributes only if they have values
179
- if (placeholder) {
180
- webComponentProps.placeholder = placeholder;
181
- }
182
-
183
- if (isLoading) webComponentProps.loading = '';
184
- if (isDisabled) webComponentProps.disabled = '';
185
- if (isReadonly) webComponentProps.readonly = '';
186
- if (isRequired) webComponentProps.required = '';
187
- if (isExternalSearch) webComponentProps['external-search'] = '';
188
-
189
- if (flavor) {
190
- webComponentProps.flavor = flavor;
191
- }
192
-
193
- if (label) {
194
- webComponentProps.label = label;
195
- }
196
-
197
- if (name) {
198
- webComponentProps.name = name;
199
- }
200
-
201
- // Add debounce attribute
202
- if (debounce !== undefined) {
203
- webComponentProps.debounce = debounce;
204
- }
205
-
206
- // Add selectedLabel attribute
207
- if (selectedLabel) {
208
- webComponentProps['selected-label'] = selectedLabel;
209
- }
210
-
211
- return React.createElement(
212
- 'ty-multiselect',
213
- webComponentProps,
214
- children
215
- );
216
- }
217
- );
218
-
219
- TyMultiselect.displayName = 'TyMultiselect';