rn-grid-date-time-picker 1.2.0 → 1.2.2
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/CHANGELOG.md +25 -0
- package/lib/commonjs/components/DateTimePicker.js +5 -0
- package/lib/commonjs/components/DateTimePicker.js.map +1 -1
- package/lib/commonjs/components/field/KeyboardAccessoryBar.js +38 -0
- package/lib/commonjs/components/field/KeyboardAccessoryBar.js.map +1 -0
- package/lib/commonjs/components/field/SegmentedInput.js +11 -6
- package/lib/commonjs/components/field/SegmentedInput.js.map +1 -1
- package/lib/commonjs/components/theme/defaultStrings.js.map +1 -1
- package/lib/commonjs/components/theme/styleKeys.js +3 -1
- package/lib/commonjs/components/theme/styleKeys.js.map +1 -1
- package/lib/commonjs/components/time/TimeRow.js +42 -4
- package/lib/commonjs/components/time/TimeRow.js.map +1 -1
- package/lib/commonjs/components/time/TimeRows.js +27 -1
- package/lib/commonjs/components/time/TimeRows.js.map +1 -1
- package/lib/commonjs/components/time/TypedTime.js +14 -8
- package/lib/commonjs/components/time/TypedTime.js.map +1 -1
- package/lib/commonjs/components/time/timeStyles.js +15 -1
- package/lib/commonjs/components/time/timeStyles.js.map +1 -1
- package/lib/commonjs/components/types.js +2 -0
- package/lib/commonjs/components/types.js.map +1 -1
- package/lib/commonjs/core/zoned.js +18 -10
- package/lib/commonjs/core/zoned.js.map +1 -1
- package/lib/module/components/DateTimePicker.js +5 -0
- package/lib/module/components/DateTimePicker.js.map +1 -1
- package/lib/module/components/field/KeyboardAccessoryBar.js +33 -0
- package/lib/module/components/field/KeyboardAccessoryBar.js.map +1 -0
- package/lib/module/components/field/SegmentedInput.js +11 -7
- package/lib/module/components/field/SegmentedInput.js.map +1 -1
- package/lib/module/components/theme/defaultStrings.js.map +1 -1
- package/lib/module/components/theme/styleKeys.js +3 -1
- package/lib/module/components/theme/styleKeys.js.map +1 -1
- package/lib/module/components/time/TimeRow.js +43 -5
- package/lib/module/components/time/TimeRow.js.map +1 -1
- package/lib/module/components/time/TimeRows.js +27 -1
- package/lib/module/components/time/TimeRows.js.map +1 -1
- package/lib/module/components/time/TypedTime.js +12 -8
- package/lib/module/components/time/TypedTime.js.map +1 -1
- package/lib/module/components/time/timeStyles.js +15 -1
- package/lib/module/components/time/timeStyles.js.map +1 -1
- package/lib/module/components/types.js +2 -0
- package/lib/module/components/types.js.map +1 -1
- package/lib/module/core/zoned.js +18 -10
- package/lib/module/core/zoned.js.map +1 -1
- package/lib/typescript/commonjs/src/components/DateTimePicker.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/components/field/KeyboardAccessoryBar.d.ts +15 -0
- package/lib/typescript/commonjs/src/components/field/KeyboardAccessoryBar.d.ts.map +1 -0
- package/lib/typescript/commonjs/src/components/field/SegmentedInput.d.ts +2 -2
- package/lib/typescript/commonjs/src/components/field/SegmentedInput.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/components/theme/defaultStrings.d.ts +1 -0
- package/lib/typescript/commonjs/src/components/theme/defaultStrings.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/components/theme/styleKeys.d.ts +1 -1
- package/lib/typescript/commonjs/src/components/theme/styleKeys.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/components/time/TimeRow.d.ts +4 -0
- package/lib/typescript/commonjs/src/components/time/TimeRow.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/components/time/TimeRows.d.ts +7 -0
- package/lib/typescript/commonjs/src/components/time/TimeRows.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/components/time/TypedTime.d.ts +1 -1
- package/lib/typescript/commonjs/src/components/time/TypedTime.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/components/time/timeStyles.d.ts +24 -1
- package/lib/typescript/commonjs/src/components/time/timeStyles.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/components/types.d.ts +21 -0
- package/lib/typescript/commonjs/src/components/types.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/core/zoned.d.ts +5 -1
- package/lib/typescript/commonjs/src/core/zoned.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/index.d.ts +1 -1
- package/lib/typescript/commonjs/src/index.d.ts.map +1 -1
- package/lib/typescript/module/src/components/DateTimePicker.d.ts.map +1 -1
- package/lib/typescript/module/src/components/field/KeyboardAccessoryBar.d.ts +15 -0
- package/lib/typescript/module/src/components/field/KeyboardAccessoryBar.d.ts.map +1 -0
- package/lib/typescript/module/src/components/field/SegmentedInput.d.ts +2 -2
- package/lib/typescript/module/src/components/field/SegmentedInput.d.ts.map +1 -1
- package/lib/typescript/module/src/components/theme/defaultStrings.d.ts +1 -0
- package/lib/typescript/module/src/components/theme/defaultStrings.d.ts.map +1 -1
- package/lib/typescript/module/src/components/theme/styleKeys.d.ts +1 -1
- package/lib/typescript/module/src/components/theme/styleKeys.d.ts.map +1 -1
- package/lib/typescript/module/src/components/time/TimeRow.d.ts +4 -0
- package/lib/typescript/module/src/components/time/TimeRow.d.ts.map +1 -1
- package/lib/typescript/module/src/components/time/TimeRows.d.ts +7 -0
- package/lib/typescript/module/src/components/time/TimeRows.d.ts.map +1 -1
- package/lib/typescript/module/src/components/time/TypedTime.d.ts +1 -1
- package/lib/typescript/module/src/components/time/TypedTime.d.ts.map +1 -1
- package/lib/typescript/module/src/components/time/timeStyles.d.ts +24 -1
- package/lib/typescript/module/src/components/time/timeStyles.d.ts.map +1 -1
- package/lib/typescript/module/src/components/types.d.ts +21 -0
- package/lib/typescript/module/src/components/types.d.ts.map +1 -1
- package/lib/typescript/module/src/core/zoned.d.ts +5 -1
- package/lib/typescript/module/src/core/zoned.d.ts.map +1 -1
- package/lib/typescript/module/src/index.d.ts +1 -1
- package/lib/typescript/module/src/index.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/components/DateTimePicker.tsx +5 -0
- package/src/components/field/KeyboardAccessoryBar.tsx +38 -0
- package/src/components/field/SegmentedInput.tsx +12 -7
- package/src/components/theme/defaultStrings.ts +1 -0
- package/src/components/theme/styleKeys.ts +4 -0
- package/src/components/time/TimeRow.tsx +49 -2
- package/src/components/time/TimeRows.tsx +38 -2
- package/src/components/time/TypedTime.tsx +7 -5
- package/src/components/time/timeStyles.ts +10 -1
- package/src/components/types.ts +22 -0
- package/src/core/zoned.ts +15 -3
- package/src/index.tsx +1 -0
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React, { useEffect, useMemo, useRef } from 'react';
|
|
1
|
+
import React, { useEffect, useId, useMemo, useRef } from 'react';
|
|
2
2
|
import {
|
|
3
3
|
Pressable,
|
|
4
4
|
StyleSheet,
|
|
@@ -20,6 +20,7 @@ import { MAX_FONT_SCALE } from '../theme/a11y';
|
|
|
20
20
|
import { useNumerals } from '../theme/numerals';
|
|
21
21
|
import type { ThemeTokens } from '../theme/tokens';
|
|
22
22
|
import { buildFieldStyles } from './fieldStyles';
|
|
23
|
+
import KeyboardAccessoryBar from './KeyboardAccessoryBar';
|
|
23
24
|
|
|
24
25
|
interface Props {
|
|
25
26
|
state: SegmentEditorState;
|
|
@@ -93,8 +94,8 @@ export const changeToActions = (
|
|
|
93
94
|
|
|
94
95
|
/**
|
|
95
96
|
* The typed field: one box per segment in locale order, plus an AM/PM switch for
|
|
96
|
-
* 12-hour date-times. Number pad, with
|
|
97
|
-
*
|
|
97
|
+
* 12-hour date-times. Number pad, with an empty solid bar above it on iOS (so
|
|
98
|
+
* React Native adds no see-through Done bar).
|
|
98
99
|
*/
|
|
99
100
|
const SegmentedInput: React.FC<Props> = ({
|
|
100
101
|
state,
|
|
@@ -108,6 +109,7 @@ const SegmentedInput: React.FC<Props> = ({
|
|
|
108
109
|
testID,
|
|
109
110
|
}) => {
|
|
110
111
|
const digits = useNumerals();
|
|
112
|
+
const barId = useId();
|
|
111
113
|
// Each box's text as last rendered, and as it last reported. Fast input (a
|
|
112
114
|
// hardware keyboard, autofill) sends the next character before React has
|
|
113
115
|
// re-rendered, and the native box may ignore React's reset while it's
|
|
@@ -197,10 +199,9 @@ const SegmentedInput: React.FC<Props> = ({
|
|
|
197
199
|
keyboardType="number-pad"
|
|
198
200
|
editable={!disabled}
|
|
199
201
|
contextMenuHidden
|
|
200
|
-
// iOS
|
|
201
|
-
//
|
|
202
|
-
|
|
203
|
-
inputAccessoryViewButtonLabel={strings.done}
|
|
202
|
+
// iOS: this box's own solid bar (below), so React Native adds no
|
|
203
|
+
// see-through Done bar.
|
|
204
|
+
inputAccessoryViewID={`${barId}-${spec.key}`}
|
|
204
205
|
onFocus={() => dispatch({ type: 'focus', key: spec.key })}
|
|
205
206
|
onBlur={() => {
|
|
206
207
|
if ((getState?.() ?? state).focused === spec.key)
|
|
@@ -228,6 +229,10 @@ const SegmentedInput: React.FC<Props> = ({
|
|
|
228
229
|
isFocused && [s.segmentFocused, styles?.fieldSegmentFocused],
|
|
229
230
|
]}
|
|
230
231
|
/>
|
|
232
|
+
<KeyboardAccessoryBar
|
|
233
|
+
nativeID={`${barId}-${spec.key}`}
|
|
234
|
+
tokens={tokens}
|
|
235
|
+
/>
|
|
231
236
|
</React.Fragment>
|
|
232
237
|
);
|
|
233
238
|
};
|
|
@@ -110,6 +110,10 @@ export const STYLE_KEYS = [
|
|
|
110
110
|
// time
|
|
111
111
|
'timeRow',
|
|
112
112
|
'timeLabel',
|
|
113
|
+
/** Holds the label and its date (`timeRowDate`) or a `renderTimeLabel` label. */
|
|
114
|
+
'timeLabelWrap',
|
|
115
|
+
/** The picked day under a time row's label (`timeRowDate`). */
|
|
116
|
+
'timeLabelDate',
|
|
113
117
|
'timeBox',
|
|
114
118
|
'timeBoxFocused',
|
|
115
119
|
'timeBoxError',
|
|
@@ -57,6 +57,10 @@ export interface TimeRowProps {
|
|
|
57
57
|
/** The day's limits (`Constraints.timeBounds`). */
|
|
58
58
|
bounds?: TimeBounds | null;
|
|
59
59
|
label: string;
|
|
60
|
+
/** The row's day, shown under the label (`timeRowDate`); null shows none. */
|
|
61
|
+
dateText?: string | null;
|
|
62
|
+
/** Replaces the label and its date (`renderTimeLabel`). */
|
|
63
|
+
renderLabel?: () => React.ReactNode;
|
|
60
64
|
disabled?: boolean;
|
|
61
65
|
tokens: ThemeTokens;
|
|
62
66
|
styles?: PickerStyles;
|
|
@@ -77,6 +81,37 @@ const issueText = (
|
|
|
77
81
|
return strings.errorTimeNotAllowed;
|
|
78
82
|
};
|
|
79
83
|
|
|
84
|
+
/** The row's label: plain, with the day under it, or the app's own. */
|
|
85
|
+
const TimeLabel: React.FC<
|
|
86
|
+
Pick<
|
|
87
|
+
TimeRowProps,
|
|
88
|
+
'label' | 'dateText' | 'renderLabel' | 'tokens' | 'styles' | 'testID'
|
|
89
|
+
>
|
|
90
|
+
> = ({ label, dateText, renderLabel, tokens, styles, testID }) => {
|
|
91
|
+
const s = buildTimeStyles(tokens);
|
|
92
|
+
if (!renderLabel && !dateText)
|
|
93
|
+
return <Text style={[s.label, styles?.timeLabel]}>{label}</Text>;
|
|
94
|
+
return (
|
|
95
|
+
<View style={[s.labelWrap, styles?.timeLabelWrap]}>
|
|
96
|
+
{renderLabel ? (
|
|
97
|
+
renderLabel()
|
|
98
|
+
) : (
|
|
99
|
+
<>
|
|
100
|
+
<Text style={[s.label, s.labelInWrap, styles?.timeLabel]}>
|
|
101
|
+
{label}
|
|
102
|
+
</Text>
|
|
103
|
+
<Text
|
|
104
|
+
style={[s.labelDate, styles?.timeLabelDate]}
|
|
105
|
+
testID={testID ? `${testID}-date` : undefined}
|
|
106
|
+
>
|
|
107
|
+
{dateText}
|
|
108
|
+
</Text>
|
|
109
|
+
</>
|
|
110
|
+
)}
|
|
111
|
+
</View>
|
|
112
|
+
);
|
|
113
|
+
};
|
|
114
|
+
|
|
80
115
|
/**
|
|
81
116
|
* Label, the time boxes and the error below them. The boxes are
|
|
82
117
|
* normal inputs (free cursor, digits only, two at most) with three helpers:
|
|
@@ -97,6 +132,8 @@ const TimeRow: React.FC<TimeRowProps> = ({
|
|
|
97
132
|
minuteStep = 1,
|
|
98
133
|
bounds,
|
|
99
134
|
label,
|
|
135
|
+
dateText,
|
|
136
|
+
renderLabel,
|
|
100
137
|
disabled,
|
|
101
138
|
tokens,
|
|
102
139
|
styles,
|
|
@@ -205,8 +242,11 @@ const TimeRow: React.FC<TimeRowProps> = ({
|
|
|
205
242
|
let issue: TimeEntryIssue | null = effective.issue;
|
|
206
243
|
if (effective.isEmpty) issue = time ? 'INCOMPLETE' : null;
|
|
207
244
|
if (effective.time) issue = checkTime(effective.time, { minuteStep, bounds });
|
|
245
|
+
// A time set elsewhere reaches the boxes one render later (the effect
|
|
246
|
+
// above); until then the boxes are stale, so check that time instead.
|
|
247
|
+
const boxesStale = !focused && timeKey(time) !== lastKey.current;
|
|
208
248
|
// The wheel always holds a complete time: only the limits and the step apply.
|
|
209
|
-
if (input === 'wheel')
|
|
249
|
+
if (input === 'wheel' || boxesStale)
|
|
210
250
|
issue = time ? checkTime(time, { minuteStep, bounds }) : null;
|
|
211
251
|
|
|
212
252
|
const pending =
|
|
@@ -268,7 +308,14 @@ const TimeRow: React.FC<TimeRowProps> = ({
|
|
|
268
308
|
return (
|
|
269
309
|
<View style={s.wrapper} testID={testID}>
|
|
270
310
|
<View style={[s.row, styles?.timeRow]}>
|
|
271
|
-
<
|
|
311
|
+
<TimeLabel
|
|
312
|
+
label={label}
|
|
313
|
+
dateText={dateText}
|
|
314
|
+
renderLabel={renderLabel}
|
|
315
|
+
tokens={tokens}
|
|
316
|
+
styles={styles}
|
|
317
|
+
testID={testID}
|
|
318
|
+
/>
|
|
272
319
|
{input === 'wheel' ? (
|
|
273
320
|
<TimeWheel
|
|
274
321
|
time={time}
|
|
@@ -5,10 +5,11 @@ import type { Draft, DraftTarget } from '../../core/draft';
|
|
|
5
5
|
import type { TimeEntryIssue } from '../../core/timeEntry';
|
|
6
6
|
import type { CalendarDate, Time, TimeFormat } from '../../core/types';
|
|
7
7
|
import type { RangeEnd } from '../field/RangeFields';
|
|
8
|
-
import { formatDateText } from '../field/fieldText';
|
|
8
|
+
import { formatDateText, type DateTextFormatter } from '../field/fieldText';
|
|
9
9
|
import type { PickerStrings } from '../theme/defaultStrings';
|
|
10
10
|
import type { PickerStyles } from '../theme/styleKeys';
|
|
11
11
|
import type { ThemeTokens } from '../theme/tokens';
|
|
12
|
+
import type { TimeLabelRenderProps } from '../types';
|
|
12
13
|
import TimeRow from './TimeRow';
|
|
13
14
|
|
|
14
15
|
/** Times typed before their day is picked, per end ('shared' lives in the draft). */
|
|
@@ -37,6 +38,11 @@ interface Props {
|
|
|
37
38
|
onSpinChange: (spinning: boolean) => void;
|
|
38
39
|
/** A row's box gained or lost focus. */
|
|
39
40
|
onFocusChange?: (rowKey: string, focused: boolean) => void;
|
|
41
|
+
/** Shows each row's day under its label. */
|
|
42
|
+
timeRowDate?: boolean;
|
|
43
|
+
renderTimeLabel?: (p: TimeLabelRenderProps) => React.ReactNode;
|
|
44
|
+
/** A day as the field writes it (the Hijri text for a Hijri pick); default `dateFormat`. */
|
|
45
|
+
dateText?: DateTextFormatter;
|
|
40
46
|
/** A row's time that is typed but not saved yet (null once saved or gone). */
|
|
41
47
|
onPendingChange?: (
|
|
42
48
|
rowKey: string,
|
|
@@ -51,6 +57,7 @@ interface Props {
|
|
|
51
57
|
|
|
52
58
|
interface RowSpec {
|
|
53
59
|
key: string;
|
|
60
|
+
kind: TimeLabelRenderProps['row'];
|
|
54
61
|
label: string;
|
|
55
62
|
time: Time | null;
|
|
56
63
|
day: CalendarDate | null;
|
|
@@ -85,6 +92,9 @@ const TimeRows: React.FC<Props> = (props) => {
|
|
|
85
92
|
onSpinChange,
|
|
86
93
|
onFocusChange,
|
|
87
94
|
onPendingChange,
|
|
95
|
+
timeRowDate = false,
|
|
96
|
+
renderTimeLabel,
|
|
97
|
+
dateText,
|
|
88
98
|
tokens,
|
|
89
99
|
styles,
|
|
90
100
|
strings,
|
|
@@ -98,6 +108,7 @@ const TimeRows: React.FC<Props> = (props) => {
|
|
|
98
108
|
): RowSpec => {
|
|
99
109
|
return {
|
|
100
110
|
key,
|
|
111
|
+
kind: key,
|
|
101
112
|
label,
|
|
102
113
|
time: value?.time ?? pending[key] ?? null,
|
|
103
114
|
day: value?.date ?? null,
|
|
@@ -123,6 +134,7 @@ const TimeRows: React.FC<Props> = (props) => {
|
|
|
123
134
|
rows = [
|
|
124
135
|
{
|
|
125
136
|
key: 'shared',
|
|
137
|
+
kind: 'shared',
|
|
126
138
|
label: strings.time,
|
|
127
139
|
time: draft.sharedTime,
|
|
128
140
|
day: null,
|
|
@@ -142,7 +154,11 @@ const TimeRows: React.FC<Props> = (props) => {
|
|
|
142
154
|
rows = [
|
|
143
155
|
{
|
|
144
156
|
key: `day-${slot.date.year}-${slot.date.month}-${slot.date.day}`,
|
|
145
|
-
|
|
157
|
+
kind: 'day',
|
|
158
|
+
// With timeRowDate the day goes under the label instead.
|
|
159
|
+
label: timeRowDate
|
|
160
|
+
? strings.time
|
|
161
|
+
: `${strings.time} · ${formatDateText(slot, dateFormat)}`,
|
|
146
162
|
time: slot.time,
|
|
147
163
|
day: slot.date,
|
|
148
164
|
target: { day: slot.date },
|
|
@@ -200,6 +216,24 @@ const TimeRows: React.FC<Props> = (props) => {
|
|
|
200
216
|
// eslint-disable-next-line react-hooks/exhaustive-deps -- reports changes only; the callback may be a new function on every render
|
|
201
217
|
useEffect(() => onSpinChange(anySpinning), [anySpinning]);
|
|
202
218
|
|
|
219
|
+
/** A row's day as the field writes it, or null before it is picked. */
|
|
220
|
+
const dayText = (row: RowSpec): string | null => {
|
|
221
|
+
if (!row.day) return null;
|
|
222
|
+
const value = { date: row.day, time: row.time };
|
|
223
|
+
return dateText ? dateText(value) : formatDateText(value, dateFormat);
|
|
224
|
+
};
|
|
225
|
+
const labelRenderer = (row: RowSpec) =>
|
|
226
|
+
renderTimeLabel
|
|
227
|
+
? () =>
|
|
228
|
+
renderTimeLabel({
|
|
229
|
+
label: row.label,
|
|
230
|
+
row: row.kind,
|
|
231
|
+
date: row.day,
|
|
232
|
+
dateText: dayText(row),
|
|
233
|
+
time: row.time,
|
|
234
|
+
})
|
|
235
|
+
: undefined;
|
|
236
|
+
|
|
203
237
|
const single = rows.length === 1;
|
|
204
238
|
return (
|
|
205
239
|
<>
|
|
@@ -218,6 +252,8 @@ const TimeRows: React.FC<Props> = (props) => {
|
|
|
218
252
|
minuteStep={minuteStep}
|
|
219
253
|
bounds={row.day ? constraints.timeBounds(row.day) : null}
|
|
220
254
|
label={row.label}
|
|
255
|
+
dateText={timeRowDate ? dayText(row) : null}
|
|
256
|
+
renderLabel={labelRenderer(row)}
|
|
221
257
|
disabled={disabled}
|
|
222
258
|
tokens={tokens}
|
|
223
259
|
styles={styles}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import React from 'react';
|
|
1
|
+
import React, { useId } from 'react';
|
|
2
2
|
import { Pressable, Text, TextInput, View } from 'react-native';
|
|
3
|
+
import KeyboardAccessoryBar from '../field/KeyboardAccessoryBar';
|
|
3
4
|
import type { TextInputRef } from '../nativeTypes';
|
|
4
5
|
import type { TimeBounds } from '../../core/constraints';
|
|
5
6
|
import type { Period } from '../../core/segments';
|
|
@@ -39,7 +40,7 @@ const PERIODS: Period[] = ['AM', 'PM'];
|
|
|
39
40
|
|
|
40
41
|
/**
|
|
41
42
|
* `[hh] : [mm] [AM | PM]`, or `[HH] : [mm]` in 24-hour. The boxes are plain
|
|
42
|
-
* TextInputs; the row (TimeRow) owns their values. Number pad, with a
|
|
43
|
+
* TextInputs; the row (TimeRow) owns their values. Number pad, with a solid bar
|
|
43
44
|
* on iOS. The AM/PM side that can't hold an allowed time is disabled.
|
|
44
45
|
*/
|
|
45
46
|
const TypedTime: React.FC<Props> = ({
|
|
@@ -61,6 +62,7 @@ const TypedTime: React.FC<Props> = ({
|
|
|
61
62
|
testID,
|
|
62
63
|
}) => {
|
|
63
64
|
const digits = useNumerals();
|
|
65
|
+
const barId = useId();
|
|
64
66
|
const s = buildTimeStyles(tokens);
|
|
65
67
|
|
|
66
68
|
const box = (key: TimeKey) => (
|
|
@@ -99,9 +101,8 @@ const TypedTime: React.FC<Props> = ({
|
|
|
99
101
|
contextMenuHidden
|
|
100
102
|
editable={!disabled}
|
|
101
103
|
underlineColorAndroid="transparent"
|
|
102
|
-
// iOS
|
|
103
|
-
|
|
104
|
-
inputAccessoryViewButtonLabel={strings.done}
|
|
104
|
+
// iOS: this box's own solid bar (below).
|
|
105
|
+
inputAccessoryViewID={`${barId}-${key}`}
|
|
105
106
|
onChangeText={(text) => onChangeBox(key, text)}
|
|
106
107
|
onFocus={() => onFocusBox(key)}
|
|
107
108
|
onBlur={() => onBlurBox(key)}
|
|
@@ -110,6 +111,7 @@ const TypedTime: React.FC<Props> = ({
|
|
|
110
111
|
}}
|
|
111
112
|
style={[s.boxText, styles?.timeBoxText, s.boxInput]}
|
|
112
113
|
/>
|
|
114
|
+
<KeyboardAccessoryBar nativeID={`${barId}-${key}`} tokens={tokens} />
|
|
113
115
|
</View>
|
|
114
116
|
</View>
|
|
115
117
|
</View>
|
|
@@ -23,6 +23,15 @@ const create = (t: ThemeTokens) =>
|
|
|
23
23
|
fontSize: t.fontSizeLarge,
|
|
24
24
|
color: t.heading,
|
|
25
25
|
},
|
|
26
|
+
// The label with the day under it (timeRowDate), or a renderTimeLabel label.
|
|
27
|
+
labelWrap: { flexShrink: 1, marginEnd: 12 },
|
|
28
|
+
labelInWrap: { marginEnd: 0 },
|
|
29
|
+
labelDate: {
|
|
30
|
+
marginTop: 2,
|
|
31
|
+
...fontStyle(t, 'regular'),
|
|
32
|
+
fontSize: t.fontSizeSmall,
|
|
33
|
+
color: t.mutedText,
|
|
34
|
+
},
|
|
26
35
|
boxes: {
|
|
27
36
|
flexDirection: 'row',
|
|
28
37
|
alignItems: 'center',
|
|
@@ -62,9 +71,9 @@ const create = (t: ThemeTokens) =>
|
|
|
62
71
|
},
|
|
63
72
|
boxFocused: { borderColor: t.borderFocused },
|
|
64
73
|
boxError: { borderColor: t.error },
|
|
74
|
+
// No height of its own, so `timeBox` sets the row's height.
|
|
65
75
|
colonCell: {
|
|
66
76
|
width: 24,
|
|
67
|
-
height: 46,
|
|
68
77
|
alignItems: 'center',
|
|
69
78
|
justifyContent: 'center',
|
|
70
79
|
},
|
package/src/components/types.ts
CHANGED
|
@@ -165,6 +165,20 @@ export interface LabelRenderProps {
|
|
|
165
165
|
fieldTestID?: string;
|
|
166
166
|
}
|
|
167
167
|
|
|
168
|
+
/** What `renderTimeLabel` receives, for one time row. */
|
|
169
|
+
export interface TimeLabelRenderProps {
|
|
170
|
+
/** The row's label from `strings`: 'Time', 'Start time' or 'End time'. */
|
|
171
|
+
label: string;
|
|
172
|
+
/** A single value's row, a range end, the multiple-mode shared row, or a day's own row (`timeMode: 'perDate'`). */
|
|
173
|
+
row: 'single' | 'from' | 'to' | 'shared' | 'day';
|
|
174
|
+
/** The row's picked day; null before it is picked, and for the shared row. */
|
|
175
|
+
date: CalendarDate | null;
|
|
176
|
+
/** That day as the field writes it; null when there is no day. Set whether or not `timeRowDate` is on. */
|
|
177
|
+
dateText: string | null;
|
|
178
|
+
/** The row's time, or null. */
|
|
179
|
+
time: Time | null;
|
|
180
|
+
}
|
|
181
|
+
|
|
168
182
|
/** What `renderInstruction` receives. */
|
|
169
183
|
export interface InstructionRenderProps {
|
|
170
184
|
instruction: string;
|
|
@@ -368,6 +382,14 @@ export interface DateTimePickerProps {
|
|
|
368
382
|
minuteStep?: number;
|
|
369
383
|
/** A newly picked day always gets this time (over a time typed first and over the current time). */
|
|
370
384
|
newDayTime?: Time;
|
|
385
|
+
/**
|
|
386
|
+
* Shows each time row's picked day under its label ('Start time',
|
|
387
|
+
* 10-03-2026), as the field writes it. Nothing shows until the day is
|
|
388
|
+
* picked. Style it with `timeLabelDate`. Default false.
|
|
389
|
+
*/
|
|
390
|
+
timeRowDate?: boolean;
|
|
391
|
+
/** Replaces a time row's label (and its date). The boxes and errors stay built in. */
|
|
392
|
+
renderTimeLabel?: (p: TimeLabelRenderProps) => React.ReactNode;
|
|
371
393
|
|
|
372
394
|
// limits → greyed days and errors
|
|
373
395
|
showHolidays?: boolean;
|
package/src/core/zoned.ts
CHANGED
|
@@ -1,9 +1,16 @@
|
|
|
1
1
|
import { DateTime } from 'luxon';
|
|
2
2
|
import type { DateTimeValue } from './types';
|
|
3
3
|
|
|
4
|
-
/**
|
|
5
|
-
|
|
6
|
-
|
|
4
|
+
/**
|
|
5
|
+
* The Luxon DateTime for a date-time value in `zone` (seconds and ms zeroed).
|
|
6
|
+
* A time that happens twice resolves to its first occurrence: Luxon alone picks
|
|
7
|
+
* by the zone's offset today, so the result would change with the season.
|
|
8
|
+
*/
|
|
9
|
+
export const toZonedDateTime = (
|
|
10
|
+
value: DateTimeValue,
|
|
11
|
+
zone: string
|
|
12
|
+
): DateTime => {
|
|
13
|
+
const dt = DateTime.fromObject(
|
|
7
14
|
{
|
|
8
15
|
...value.date,
|
|
9
16
|
hour: value.time?.hour ?? 0,
|
|
@@ -13,6 +20,11 @@ export const toZonedDateTime = (value: DateTimeValue, zone: string): DateTime =>
|
|
|
13
20
|
},
|
|
14
21
|
{ zone }
|
|
15
22
|
);
|
|
23
|
+
if (!dt.isValid) return dt;
|
|
24
|
+
return dt
|
|
25
|
+
.getPossibleOffsets()
|
|
26
|
+
.reduce((first, d) => (d.toMillis() < first.toMillis() ? d : first), dt);
|
|
27
|
+
};
|
|
16
28
|
|
|
17
29
|
export type DstStatus = 'ok' | 'gap' | 'overlap';
|
|
18
30
|
|
package/src/index.tsx
CHANGED