@idealyst/datepicker 1.0.0 → 1.0.41

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 (36) hide show
  1. package/package.json +6 -2
  2. package/src/DatePicker/Calendar.native.tsx +180 -78
  3. package/src/DatePicker/Calendar.styles.tsx +73 -70
  4. package/src/DatePicker/DatePicker.native.tsx +24 -6
  5. package/src/DatePicker/DatePicker.styles.tsx +18 -11
  6. package/src/DatePicker/DatePicker.web.tsx +1 -1
  7. package/src/DatePicker/index.ts +1 -1
  8. package/src/DateRangePicker/RangeCalendar.native.tsx +143 -55
  9. package/src/DateRangePicker/RangeCalendar.styles.tsx +65 -39
  10. package/src/DateRangePicker/RangeCalendar.web.tsx +169 -60
  11. package/src/DateRangePicker/types.ts +9 -0
  12. package/src/DateTimePicker/DateTimePicker.native.tsx +11 -69
  13. package/src/DateTimePicker/DateTimePicker.tsx +12 -70
  14. package/src/DateTimePicker/DateTimePickerBase.tsx +241 -0
  15. package/src/DateTimePicker/TimePicker.native.tsx +9 -196
  16. package/src/DateTimePicker/TimePicker.styles.tsx +4 -2
  17. package/src/DateTimePicker/TimePicker.tsx +9 -401
  18. package/src/DateTimePicker/TimePickerBase.tsx +232 -0
  19. package/src/DateTimePicker/primitives/ClockFace.native.tsx +195 -0
  20. package/src/DateTimePicker/primitives/ClockFace.web.tsx +168 -0
  21. package/src/DateTimePicker/primitives/TimeInput.native.tsx +53 -0
  22. package/src/DateTimePicker/primitives/TimeInput.web.tsx +66 -0
  23. package/src/DateTimePicker/primitives/index.native.ts +2 -0
  24. package/src/DateTimePicker/primitives/index.ts +2 -0
  25. package/src/DateTimePicker/primitives/index.web.ts +2 -0
  26. package/src/DateTimePicker/types.ts +0 -4
  27. package/src/DateTimePicker/utils/dimensions.native.ts +9 -0
  28. package/src/DateTimePicker/utils/dimensions.ts +9 -0
  29. package/src/DateTimePicker/utils/dimensions.web.ts +33 -0
  30. package/src/DateTimeRangePicker/DateTimeRangePicker.native.tsx +10 -199
  31. package/src/DateTimeRangePicker/DateTimeRangePicker.styles.tsx +3 -0
  32. package/src/DateTimeRangePicker/DateTimeRangePicker.web.tsx +11 -131
  33. package/src/DateTimeRangePicker/DateTimeRangePickerBase.tsx +391 -0
  34. package/src/DateTimeRangePicker/types.ts +0 -2
  35. package/src/examples/DatePickerExamples.tsx +42 -118
  36. /package/src/DatePicker/{Calendar.tsx → Calendar.web.tsx} +0 -0
@@ -0,0 +1,195 @@
1
+ import React from 'react';
2
+ import { View, Text } from '@idealyst/components';
3
+ import { TouchableOpacity } from 'react-native';
4
+ import Svg, { Circle, Line } from 'react-native-svg';
5
+ import { timePickerStyles } from '../TimePicker.styles';
6
+
7
+ interface ClockFaceProps {
8
+ activeSelection: 'hour' | 'minute';
9
+ hours: number;
10
+ minutes: number;
11
+ displayHours: number;
12
+ mode: '12h' | '24h';
13
+ disabled: boolean;
14
+ onHourClick: (hour: number) => void;
15
+ onMinuteClick: (minute: number) => void;
16
+ }
17
+
18
+ export const ClockFace: React.FC<ClockFaceProps> = ({
19
+ activeSelection,
20
+ hours,
21
+ minutes,
22
+ displayHours,
23
+ mode,
24
+ disabled,
25
+ onHourClick,
26
+ onMinuteClick,
27
+ }) => {
28
+ // Clock configuration
29
+ const CLOCK_SIZE = 180;
30
+ const CENTER = CLOCK_SIZE / 2;
31
+ const CLOCK_RADIUS = CENTER - 5;
32
+ const NUMBER_RADIUS = CENTER - 24;
33
+ const HOUR_HAND_LENGTH = CENTER - 44;
34
+ const MINUTE_HAND_LENGTH = CENTER - 36;
35
+ const CIRCLE_RADIUS = 15;
36
+
37
+ if (activeSelection === 'hour') {
38
+ return (
39
+ <View style={[timePickerStyles.clockContainer, { position: 'relative' }]}>
40
+ <Svg width={CLOCK_SIZE} height={CLOCK_SIZE} style={timePickerStyles.clockSvg}>
41
+ {/* Clock face */}
42
+ <Circle
43
+ cx={CENTER}
44
+ cy={CENTER}
45
+ r={CLOCK_RADIUS}
46
+ fill="#f9fafb"
47
+ stroke="#e5e7eb"
48
+ strokeWidth="2"
49
+ />
50
+
51
+ {/* Hour hand pointing to selected hour */}
52
+ {(() => {
53
+ const selectedHour = mode === '12h' ? displayHours : hours;
54
+ const hourFor12Clock = selectedHour === 12 ? 0 : selectedHour;
55
+ const hourAngle = (hourFor12Clock * 30) - 90;
56
+ const handX = CENTER + HOUR_HAND_LENGTH * Math.cos(hourAngle * Math.PI / 180);
57
+ const handY = CENTER + HOUR_HAND_LENGTH * Math.sin(hourAngle * Math.PI / 180);
58
+
59
+ return (
60
+ <Line
61
+ x1={CENTER}
62
+ y1={CENTER}
63
+ x2={handX}
64
+ y2={handY}
65
+ stroke="#3b82f6"
66
+ strokeWidth="3"
67
+ strokeLinecap="round"
68
+ />
69
+ );
70
+ })()}
71
+
72
+ {/* Center dot */}
73
+ <Circle cx={CENTER} cy={CENTER} r="4" fill="#3b82f6"/>
74
+ </Svg>
75
+
76
+ {/* TouchableOpacity buttons for hour numbers */}
77
+ {[...Array(12)].map((_, i) => {
78
+ const hour = i === 0 ? 12 : i;
79
+ const angle = (i * 30) - 90;
80
+ const x = CENTER + NUMBER_RADIUS * Math.cos(angle * Math.PI / 180);
81
+ const y = CENTER + NUMBER_RADIUS * Math.sin(angle * Math.PI / 180);
82
+ const isSelected = (mode === '12h' ? displayHours : hours) === (hour === 12 ? (mode === '12h' ? 12 : 0) : hour);
83
+
84
+ return (
85
+ <TouchableOpacity
86
+ key={`touch-${i}`}
87
+ onPressIn={() => !disabled && onHourClick(hour)}
88
+ disabled={disabled}
89
+ style={{
90
+ position: 'absolute',
91
+ left: x - CIRCLE_RADIUS,
92
+ top: y - CIRCLE_RADIUS,
93
+ width: CIRCLE_RADIUS * 2,
94
+ height: CIRCLE_RADIUS * 2,
95
+ borderRadius: CIRCLE_RADIUS,
96
+ backgroundColor: isSelected ? '#3b82f6' : 'transparent',
97
+ borderWidth: 1,
98
+ borderColor: isSelected ? '#3b82f6' : '#e5e7eb',
99
+ justifyContent: 'center',
100
+ alignItems: 'center',
101
+ }}
102
+ >
103
+ <Text style={{
104
+ fontSize: 14,
105
+ fontWeight: '500',
106
+ color: isSelected ? '#ffffff' : '#374151',
107
+ textAlign: 'center',
108
+ }}>
109
+ {hour}
110
+ </Text>
111
+ </TouchableOpacity>
112
+ );
113
+ })}
114
+ </View>
115
+ );
116
+ } else {
117
+ // Minute selection clock
118
+ return (
119
+ <View style={[timePickerStyles.clockContainer, { position: 'relative' }]}>
120
+ <Svg width={CLOCK_SIZE} height={CLOCK_SIZE} style={timePickerStyles.clockSvg}>
121
+ {/* Clock face */}
122
+ <Circle
123
+ cx={CENTER}
124
+ cy={CENTER}
125
+ r={CLOCK_RADIUS}
126
+ fill="#f9fafb"
127
+ stroke="#e5e7eb"
128
+ strokeWidth="2"
129
+ />
130
+
131
+ {/* Minute hand */}
132
+ {(() => {
133
+ const minuteAngle = (minutes * 6) - 90;
134
+ const handX = CENTER + MINUTE_HAND_LENGTH * Math.cos(minuteAngle * Math.PI / 180);
135
+ const handY = CENTER + MINUTE_HAND_LENGTH * Math.sin(minuteAngle * Math.PI / 180);
136
+
137
+ return (
138
+ <Line
139
+ x1={CENTER}
140
+ y1={CENTER}
141
+ x2={handX}
142
+ y2={handY}
143
+ stroke="#3b82f6"
144
+ strokeWidth="3"
145
+ strokeLinecap="round"
146
+ />
147
+ );
148
+ })()}
149
+
150
+ {/* Center dot */}
151
+ <Circle cx={CENTER} cy={CENTER} r="4" fill="#3b82f6"/>
152
+ </Svg>
153
+
154
+ {/* TouchableOpacity buttons for minute numbers */}
155
+ {[...Array(12)].map((_, i) => {
156
+ const minute = i * 5;
157
+ const angle = (i * 30) - 90;
158
+ const x = CENTER + NUMBER_RADIUS * Math.cos(angle * Math.PI / 180);
159
+ const y = CENTER + NUMBER_RADIUS * Math.sin(angle * Math.PI / 180);
160
+ const isSelected = Math.floor(minutes / 5) * 5 === minute;
161
+
162
+ return (
163
+ <TouchableOpacity
164
+ key={`touch-${i}`}
165
+ onPressIn={() => !disabled && onMinuteClick(minute)}
166
+ disabled={disabled}
167
+ style={{
168
+ position: 'absolute',
169
+ left: x - CIRCLE_RADIUS,
170
+ top: y - CIRCLE_RADIUS,
171
+ width: CIRCLE_RADIUS * 2,
172
+ height: CIRCLE_RADIUS * 2,
173
+ borderRadius: CIRCLE_RADIUS,
174
+ backgroundColor: isSelected ? '#3b82f6' : 'transparent',
175
+ borderWidth: 1,
176
+ borderColor: isSelected ? '#3b82f6' : '#e5e7eb',
177
+ justifyContent: 'center',
178
+ alignItems: 'center',
179
+ }}
180
+ >
181
+ <Text style={{
182
+ fontSize: 14,
183
+ fontWeight: '500',
184
+ color: isSelected ? '#ffffff' : '#374151',
185
+ textAlign: 'center',
186
+ }}>
187
+ {String(minute).padStart(2, '0')}
188
+ </Text>
189
+ </TouchableOpacity>
190
+ );
191
+ })}
192
+ </View>
193
+ );
194
+ }
195
+ };
@@ -0,0 +1,168 @@
1
+ import React from 'react';
2
+ import { View } from '@idealyst/components';
3
+ import { timePickerStyles } from '../TimePicker.styles';
4
+
5
+ interface ClockFaceProps {
6
+ activeSelection: 'hour' | 'minute';
7
+ hours: number;
8
+ minutes: number;
9
+ displayHours: number;
10
+ mode: '12h' | '24h';
11
+ disabled: boolean;
12
+ onHourClick: (hour: number) => void;
13
+ onMinuteClick: (minute: number) => void;
14
+ }
15
+
16
+ export const ClockFace: React.FC<ClockFaceProps> = ({
17
+ activeSelection,
18
+ hours,
19
+ minutes,
20
+ displayHours,
21
+ mode,
22
+ disabled,
23
+ onHourClick,
24
+ onMinuteClick,
25
+ }) => {
26
+ // Clock configuration
27
+ const CLOCK_SIZE = 180;
28
+ const CENTER = CLOCK_SIZE / 2;
29
+ const CLOCK_RADIUS = CENTER - 5;
30
+ const NUMBER_RADIUS = CENTER - 24;
31
+ const HOUR_HAND_LENGTH = CENTER - 44;
32
+ const MINUTE_HAND_LENGTH = CENTER - 36;
33
+ const CIRCLE_RADIUS = 15;
34
+
35
+ if (activeSelection === 'hour') {
36
+ return (
37
+ <View style={timePickerStyles.clockContainer}>
38
+ <svg width={CLOCK_SIZE} height={CLOCK_SIZE} style={timePickerStyles.clockSvg}>
39
+ {/* Clock face */}
40
+ <circle cx={CENTER} cy={CENTER} r={CLOCK_RADIUS} fill="#f9fafb" stroke="#e5e7eb" strokeWidth="2"/>
41
+
42
+ {/* Hour numbers - clickable */}
43
+ {[...Array(12)].map((_, i) => {
44
+ const hour = i === 0 ? 12 : i;
45
+ const angle = (i * 30) - 90;
46
+ const x = CENTER + NUMBER_RADIUS * Math.cos(angle * Math.PI / 180);
47
+ const y = CENTER + NUMBER_RADIUS * Math.sin(angle * Math.PI / 180);
48
+ const isSelected = (mode === '12h' ? displayHours : hours) === (hour === 12 ? (mode === '12h' ? 12 : 0) : hour);
49
+
50
+ return (
51
+ <g key={i} onClick={() => !disabled && onHourClick(hour)}>
52
+ <circle
53
+ cx={x}
54
+ cy={y}
55
+ r={CIRCLE_RADIUS}
56
+ fill={isSelected ? '#3b82f6' : 'transparent'}
57
+ stroke={isSelected ? '#3b82f6' : '#e5e7eb'}
58
+ strokeWidth="1"
59
+ style={{ cursor: disabled ? 'default' : 'pointer' }}
60
+ />
61
+ <text
62
+ x={x}
63
+ y={y + 4}
64
+ textAnchor="middle"
65
+ fontSize="14"
66
+ fill={isSelected ? '#ffffff' : '#374151'}
67
+ fontWeight="500"
68
+ style={{ cursor: disabled ? 'default' : 'pointer', userSelect: 'none', pointerEvents: 'none' }}
69
+ >
70
+ {hour}
71
+ </text>
72
+ </g>
73
+ );
74
+ })}
75
+
76
+ {/* Hour hand pointing to selected hour */}
77
+ {(() => {
78
+ const selectedHour = mode === '12h' ? displayHours : hours;
79
+ const hourFor12Clock = selectedHour === 12 ? 0 : selectedHour;
80
+ const hourAngle = (hourFor12Clock * 30) - 90;
81
+ const handX = CENTER + HOUR_HAND_LENGTH * Math.cos(hourAngle * Math.PI / 180);
82
+ const handY = CENTER + HOUR_HAND_LENGTH * Math.sin(hourAngle * Math.PI / 180);
83
+
84
+ return (
85
+ <line
86
+ x1={CENTER}
87
+ y1={CENTER}
88
+ x2={handX}
89
+ y2={handY}
90
+ stroke="#3b82f6"
91
+ strokeWidth="3"
92
+ strokeLinecap="round"
93
+ />
94
+ );
95
+ })()}
96
+
97
+ {/* Center dot */}
98
+ <circle cx={CENTER} cy={CENTER} r="4" fill="#3b82f6"/>
99
+ </svg>
100
+ </View>
101
+ );
102
+ } else {
103
+ return (
104
+ <View style={timePickerStyles.clockContainer}>
105
+ <svg width={CLOCK_SIZE} height={CLOCK_SIZE} style={timePickerStyles.clockSvg}>
106
+ {/* Clock face */}
107
+ <circle cx={CENTER} cy={CENTER} r={CLOCK_RADIUS} fill="#f9fafb" stroke="#e5e7eb" strokeWidth="2"/>
108
+
109
+ {/* Minute markers - every 5 minutes */}
110
+ {[...Array(12)].map((_, i) => {
111
+ const minute = i * 5;
112
+ const angle = (i * 30) - 90;
113
+ const x = CENTER + NUMBER_RADIUS * Math.cos(angle * Math.PI / 180);
114
+ const y = CENTER + NUMBER_RADIUS * Math.sin(angle * Math.PI / 180);
115
+ const isSelected = Math.floor(minutes / 5) * 5 === minute;
116
+
117
+ return (
118
+ <g key={i} onClick={() => !disabled && onMinuteClick(minute)}>
119
+ <circle
120
+ cx={x}
121
+ cy={y}
122
+ r={CIRCLE_RADIUS}
123
+ fill={isSelected ? '#3b82f6' : 'transparent'}
124
+ stroke={isSelected ? '#3b82f6' : '#e5e7eb'}
125
+ strokeWidth="1"
126
+ style={{ cursor: disabled ? 'default' : 'pointer' }}
127
+ />
128
+ <text
129
+ x={x}
130
+ y={y + 4}
131
+ textAnchor="middle"
132
+ fontSize="12"
133
+ fill={isSelected ? '#ffffff' : '#374151'}
134
+ fontWeight="500"
135
+ style={{ cursor: disabled ? 'default' : 'pointer', userSelect: 'none', pointerEvents: 'none' }}
136
+ >
137
+ {minute.toString().padStart(2, '0')}
138
+ </text>
139
+ </g>
140
+ );
141
+ })}
142
+
143
+ {/* Minute hand pointing to selected minute */}
144
+ {(() => {
145
+ const minuteAngle = (minutes * 6) - 90;
146
+ const handX = CENTER + MINUTE_HAND_LENGTH * Math.cos(minuteAngle * Math.PI / 180);
147
+ const handY = CENTER + MINUTE_HAND_LENGTH * Math.sin(minuteAngle * Math.PI / 180);
148
+
149
+ return (
150
+ <line
151
+ x1={CENTER}
152
+ y1={CENTER}
153
+ x2={handX}
154
+ y2={handY}
155
+ stroke="#3b82f6"
156
+ strokeWidth="2"
157
+ strokeLinecap="round"
158
+ />
159
+ );
160
+ })()}
161
+
162
+ {/* Center dot */}
163
+ <circle cx={CENTER} cy={CENTER} r="4" fill="#3b82f6"/>
164
+ </svg>
165
+ </View>
166
+ );
167
+ }
168
+ };
@@ -0,0 +1,53 @@
1
+ import React from 'react';
2
+ import { TextInput } from 'react-native';
3
+ import { timePickerStyles } from '../TimePicker.styles';
4
+
5
+ interface TimeInputProps {
6
+ type: 'hour' | 'minute';
7
+ value: string;
8
+ onChangeText: (value: string) => void;
9
+ onFocus: () => void;
10
+ onBlur: () => void;
11
+ isActive: boolean;
12
+ disabled: boolean;
13
+ inputRef: React.RefObject<TextInput>;
14
+ }
15
+
16
+ export const TimeInput: React.FC<TimeInputProps> = ({
17
+ type,
18
+ value,
19
+ onChangeText,
20
+ onFocus,
21
+ onBlur,
22
+ isActive,
23
+ disabled,
24
+ inputRef,
25
+ }) => {
26
+ const maxLength = type === 'hour' ? 2 : 2;
27
+
28
+ return (
29
+ <TextInput
30
+ ref={inputRef}
31
+ value={value}
32
+ onChangeText={onChangeText}
33
+ onFocus={onFocus}
34
+ onBlur={onBlur}
35
+ keyboardType="numeric"
36
+ maxLength={maxLength}
37
+ selectTextOnFocus
38
+ editable={!disabled}
39
+ style={[
40
+ timePickerStyles.timeInput,
41
+ isActive && timePickerStyles.activeInput,
42
+ {
43
+ fontSize: 16,
44
+ fontWeight: '600',
45
+ color: isActive ? '#3b82f6' : '#111827',
46
+ textAlign: 'center',
47
+ paddingHorizontal: 2,
48
+ paddingVertical: 2,
49
+ }
50
+ ]}
51
+ />
52
+ );
53
+ };
@@ -0,0 +1,66 @@
1
+ import React from 'react';
2
+ import { Input } from '@idealyst/components';
3
+ import { timePickerStyles } from '../TimePicker.styles';
4
+
5
+ interface TimeInputProps {
6
+ type: 'hour' | 'minute';
7
+ value: string;
8
+ onChangeText: (value: string) => void;
9
+ onFocus: () => void;
10
+ onBlur: () => void;
11
+ isActive: boolean;
12
+ disabled: boolean;
13
+ inputRef: React.RefObject<HTMLInputElement>;
14
+ }
15
+
16
+ export const TimeInput: React.FC<TimeInputProps> = ({
17
+ type,
18
+ value,
19
+ onChangeText,
20
+ onFocus,
21
+ onBlur,
22
+ isActive,
23
+ disabled,
24
+ inputRef,
25
+ }) => {
26
+ const handleChange = (inputValue: string) => {
27
+ onChangeText(inputValue);
28
+
29
+ // Smart focus switching for web
30
+ if (type === 'hour') {
31
+ const num = parseInt(inputValue);
32
+ if (!isNaN(num) && num >= 2) {
33
+ // Wait a moment then focus minutes
34
+ setTimeout(() => {
35
+ const minuteInput = document.querySelector('input[data-time-input="minute"]') as HTMLInputElement;
36
+ minuteInput?.focus();
37
+ }, 100);
38
+ }
39
+ }
40
+ };
41
+
42
+ const handleFocus = () => {
43
+ onFocus();
44
+ // For web, sync the displayed value on focus
45
+ if (type === 'minute' && value.length === 1) {
46
+ onChangeText(value.padStart(2, '0'));
47
+ }
48
+ };
49
+
50
+ return (
51
+ <Input
52
+ ref={inputRef}
53
+ variant="bare"
54
+ value={value}
55
+ onChangeText={handleChange}
56
+ onFocus={handleFocus}
57
+ onBlur={onBlur}
58
+ style={[
59
+ timePickerStyles.timeInput,
60
+ isActive ? timePickerStyles.activeInput : {}
61
+ ]}
62
+ disabled={disabled}
63
+ data-time-input={type}
64
+ />
65
+ );
66
+ };
@@ -0,0 +1,2 @@
1
+ export { ClockFace } from './ClockFace';
2
+ export { TimeInput } from './TimeInput';
@@ -0,0 +1,2 @@
1
+ export { ClockFace } from './ClockFace';
2
+ export { TimeInput } from './TimeInput';
@@ -0,0 +1,2 @@
1
+ export { ClockFace } from './ClockFace';
2
+ export { TimeInput } from './TimeInput';
@@ -44,8 +44,6 @@ export interface DateTimePickerProps {
44
44
  /** Time picker mode */
45
45
  timeMode?: '12h' | '24h';
46
46
 
47
- /** Show seconds in time picker */
48
- showSeconds?: boolean;
49
47
 
50
48
  /** Time step in minutes */
51
49
  timeStep?: number;
@@ -70,8 +68,6 @@ export interface TimePickerProps {
70
68
  /** Time picker mode */
71
69
  mode?: '12h' | '24h';
72
70
 
73
- /** Show seconds */
74
- showSeconds?: boolean;
75
71
 
76
72
  /** Time step in minutes */
77
73
  step?: number;
@@ -0,0 +1,9 @@
1
+ import { Dimensions } from 'react-native';
2
+
3
+ export const getDimensions = () => Dimensions.get('window');
4
+
5
+ export const addEventListener = (callback: (data: { window: { width: number; height: number } }) => void) => {
6
+ return Dimensions.addEventListener('change', ({ window }) => {
7
+ callback({ window });
8
+ });
9
+ };
@@ -0,0 +1,9 @@
1
+ import { Dimensions } from 'react-native';
2
+
3
+ export const getDimensions = () => Dimensions.get('window');
4
+
5
+ export const addEventListener = (callback: (data: { window: { width: number; height: number } }) => void) => {
6
+ return Dimensions.addEventListener('change', ({ window }) => {
7
+ callback({ window });
8
+ });
9
+ };
@@ -0,0 +1,33 @@
1
+ export const getDimensions = () => {
2
+ if (typeof window !== 'undefined') {
3
+ return {
4
+ width: window.innerWidth,
5
+ height: window.innerHeight,
6
+ };
7
+ }
8
+
9
+ return { width: 1024, height: 768 }; // Default desktop size
10
+ };
11
+
12
+ export const addEventListener = (callback: (data: { window: { width: number; height: number } }) => void) => {
13
+ if (typeof window !== 'undefined') {
14
+ const handleResize = () => {
15
+ callback({
16
+ window: {
17
+ width: window.innerWidth,
18
+ height: window.innerHeight,
19
+ },
20
+ });
21
+ };
22
+
23
+ window.addEventListener('resize', handleResize);
24
+
25
+ return {
26
+ remove: () => window.removeEventListener('resize', handleResize),
27
+ };
28
+ }
29
+
30
+ return {
31
+ remove: () => {},
32
+ };
33
+ };