@multiplatform.one/frappe-ui 7.34.0 → 7.34.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/dist/cjs/fields/Barcode/index.cjs +2 -1
- package/dist/cjs/fields/Barcode/index.native.js +2 -1
- package/dist/cjs/fields/Barcode/index.native.js.map +1 -1
- package/dist/cjs/fields/Date/catalog.cjs +139 -0
- package/dist/cjs/fields/Date/catalog.native.js +165 -0
- package/dist/cjs/fields/Date/catalog.native.js.map +1 -0
- package/dist/cjs/fields/Date/dateTimePickerLoader.cjs +38 -0
- package/dist/cjs/fields/Date/dateTimePickerLoader.native.js +42 -0
- package/dist/cjs/fields/Date/dateTimePickerLoader.native.js.map +1 -0
- package/dist/cjs/fields/Date/index.cjs +2 -113
- package/dist/cjs/fields/Date/index.native.js +19 -15
- package/dist/cjs/fields/Date/index.native.js.map +1 -1
- package/dist/esm/fields/Barcode/index.mjs +2 -1
- package/dist/esm/fields/Barcode/index.mjs.map +1 -1
- package/dist/esm/fields/Barcode/index.native.js +2 -1
- package/dist/esm/fields/Barcode/index.native.js.map +1 -1
- package/dist/esm/fields/Date/catalog.mjs +114 -0
- package/dist/esm/fields/Date/catalog.mjs.map +1 -0
- package/dist/esm/fields/Date/catalog.native.js +137 -0
- package/dist/esm/fields/Date/catalog.native.js.map +1 -0
- package/dist/esm/fields/Date/dateTimePickerLoader.mjs +13 -0
- package/dist/esm/fields/Date/dateTimePickerLoader.mjs.map +1 -0
- package/dist/esm/fields/Date/dateTimePickerLoader.native.js +14 -0
- package/dist/esm/fields/Date/dateTimePickerLoader.native.js.map +1 -0
- package/dist/esm/fields/Date/index.mjs +1 -112
- package/dist/esm/fields/Date/index.mjs.map +1 -1
- package/dist/esm/fields/Date/index.native.js +17 -2
- package/dist/esm/fields/Date/index.native.js.map +1 -1
- package/dist/jsx/fields/Barcode/index.mjs +2 -1
- package/dist/jsx/fields/Barcode/index.mjs.map +1 -1
- package/dist/jsx/fields/Barcode/index.native.js +2 -1
- package/dist/jsx/fields/Barcode/index.native.js.map +1 -1
- package/dist/jsx/fields/Date/catalog.mjs +114 -0
- package/dist/jsx/fields/Date/catalog.mjs.map +1 -0
- package/dist/jsx/fields/Date/catalog.native.js +165 -0
- package/dist/jsx/fields/Date/catalog.native.js.map +1 -0
- package/dist/jsx/fields/Date/dateTimePickerLoader.mjs +13 -0
- package/dist/jsx/fields/Date/dateTimePickerLoader.mjs.map +1 -0
- package/dist/jsx/fields/Date/dateTimePickerLoader.native.js +42 -0
- package/dist/jsx/fields/Date/dateTimePickerLoader.native.js.map +1 -0
- package/dist/jsx/fields/Date/index.mjs +1 -112
- package/dist/jsx/fields/Date/index.mjs.map +1 -1
- package/dist/jsx/fields/Date/index.native.js +19 -15
- package/dist/jsx/fields/Date/index.native.js.map +1 -1
- package/package.json +27 -27
- package/src/FrappeTable.infiniteLoadError.spec.tsx +118 -0
- package/src/desk/DeskSidebar.screen.spec.tsx +131 -0
- package/src/fields/Barcode/Barcode.adapter.spec.tsx +77 -0
- package/src/fields/Barcode/index.tsx +2 -1
- package/src/fields/Date/catalog.tsx +190 -0
- package/src/fields/Date/dateTimePickerLoader.ts +25 -0
- package/src/fields/Date/index.native.tsx +24 -3
- package/src/fields/Date/index.tsx +1 -188
- package/src/fields/Date/nativeFallback.spec.tsx +23 -0
- package/src/fields/Date/nativePresentation.spec.tsx +2 -2
- package/src/fields/Date/typeParity.spec.tsx +2 -2
- package/types/fields/Barcode/index.d.ts.map +1 -1
- package/types/fields/Date/catalog.d.ts +25 -0
- package/types/fields/Date/catalog.d.ts.map +1 -0
- package/types/fields/Date/dateTimePickerLoader.d.ts +11 -0
- package/types/fields/Date/dateTimePickerLoader.d.ts.map +1 -0
- package/types/fields/Date/index.d.ts +1 -22
- package/types/fields/Date/index.d.ts.map +1 -1
- package/types/fields/Date/index.native.d.ts +2 -7
- package/types/fields/Date/index.native.d.ts.map +1 -1
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Web `Date` field for the three Frappe date fieldtypes, and the native one
|
|
3
|
+
* when the optional `@react-native-community/datetimepicker` peer is absent.
|
|
4
|
+
*
|
|
5
|
+
* The field used to render ONE date-only calendar for all three types, with
|
|
6
|
+
* only the placeholder varying — so a `Datetime` silently dropped its time
|
|
7
|
+
* component from the picker and a `Time` rendered EMPTY, because the calendar
|
|
8
|
+
* adopts its value through `coerceToDate`, for which a bare `HH:mm` is not a
|
|
9
|
+
* date (SB-M-617). Each type now gets the house primitive that matches it:
|
|
10
|
+
*
|
|
11
|
+
* date → forms `DatePicker` (month grid)
|
|
12
|
+
* datetime → forms `DatetimePicker` (Date/Time tabs — grid, then columns)
|
|
13
|
+
* time → the package's own `TimePicker`, which is also what the field
|
|
14
|
+
* registry routes the Frappe `Time` fieldtype to, so a
|
|
15
|
+
* `Date type="time"` and a `Time` field are one control
|
|
16
|
+
*
|
|
17
|
+
* This mirrors what the native half already does (calendar / wheel /
|
|
18
|
+
* calendar-then-wheel), and every branch serializes through the one shared
|
|
19
|
+
* `wire.ts` body, so a web pick and a native pick of the same moment persist
|
|
20
|
+
* byte-identical strings.
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
import {
|
|
24
|
+
DatePicker as FormsDatePicker,
|
|
25
|
+
DatetimePicker as FormsDatetimePicker,
|
|
26
|
+
} from "@multiplatform.one/forms";
|
|
27
|
+
import { useMemo } from "react";
|
|
28
|
+
import { useProps } from "tamagui";
|
|
29
|
+
import { TimePicker, type TimePickerProps } from "../TimePicker";
|
|
30
|
+
import type { DateProps } from "./types";
|
|
31
|
+
import { fromFrappeDateValue, toFrappeDateValue } from "./wire";
|
|
32
|
+
|
|
33
|
+
export type { DateProps } from "./types";
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Adopt a Frappe datetime wire string through the shared read body rather
|
|
37
|
+
* than handing the raw string to the picker, whose `coerceToDate` falls
|
|
38
|
+
* through to `new Date(string)` for anything that is not date-only —
|
|
39
|
+
* implementation-defined for the space-separated shape Frappe writes.
|
|
40
|
+
* Memoized by the caller so the Date identity is stable across renders: the
|
|
41
|
+
* picker re-seeds its display state from every new `value` identity, which
|
|
42
|
+
* would otherwise discard an in-progress edit on an unrelated re-render.
|
|
43
|
+
*/
|
|
44
|
+
function toPickerDate(value: string | undefined): Date | null {
|
|
45
|
+
if (!value) return null;
|
|
46
|
+
const date = fromFrappeDateValue(value, "datetime");
|
|
47
|
+
return Number.isNaN(date.getTime()) ? null : date;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* `TimePicker` speaks `HH:mm`; Frappe stores `HH:mm:ss`. Round-tripping
|
|
52
|
+
* through the shared wire body — rather than padding the string here — is
|
|
53
|
+
* what keeps the two platform halves byte-identical, since the native half
|
|
54
|
+
* serializes its wheel pick through exactly this call.
|
|
55
|
+
*/
|
|
56
|
+
function toTimeWireValue(picked: string): string {
|
|
57
|
+
if (!picked) return "";
|
|
58
|
+
const date = fromFrappeDateValue(picked, "time");
|
|
59
|
+
return Number.isNaN(date.getTime()) ? "" : toFrappeDateValue(date, "time");
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export function Date<
|
|
63
|
+
TData extends Record<string, unknown> = Record<string, unknown>,
|
|
64
|
+
TName extends string = string,
|
|
65
|
+
>(props: DateProps<TData, TName>) {
|
|
66
|
+
const {
|
|
67
|
+
defaultValue,
|
|
68
|
+
form,
|
|
69
|
+
inputProps: _inputProps,
|
|
70
|
+
mode,
|
|
71
|
+
name,
|
|
72
|
+
onBlur,
|
|
73
|
+
onChange,
|
|
74
|
+
preserveValue,
|
|
75
|
+
validators,
|
|
76
|
+
disabled,
|
|
77
|
+
value,
|
|
78
|
+
type = "date",
|
|
79
|
+
...fieldProps
|
|
80
|
+
} = useProps(props) as DateProps<TData, TName> & Record<string, unknown>;
|
|
81
|
+
|
|
82
|
+
const wireValue = value as string | undefined;
|
|
83
|
+
const emit = onChange as ((v: string) => void) | undefined;
|
|
84
|
+
const datetimeValue = useMemo(
|
|
85
|
+
() => (type === "datetime" ? toPickerDate(wireValue) : null),
|
|
86
|
+
[type, wireValue],
|
|
87
|
+
);
|
|
88
|
+
|
|
89
|
+
const placeholder =
|
|
90
|
+
(fieldProps.placeholder as string | undefined) ??
|
|
91
|
+
(type === "time" ? "Select time" : type === "datetime" ? "Select date & time" : "Select date");
|
|
92
|
+
|
|
93
|
+
if (type === "time") {
|
|
94
|
+
return (
|
|
95
|
+
<TimePicker
|
|
96
|
+
// `TimePicker` is not generic over the form's data shape, so the
|
|
97
|
+
// three form props are re-cast to its own non-generic surface.
|
|
98
|
+
name={name as TimePickerProps["name"]}
|
|
99
|
+
form={form as TimePickerProps["form"]}
|
|
100
|
+
validators={validators as TimePickerProps["validators"]}
|
|
101
|
+
// The wire shape is what this control already speaks: `HH:mm[:ss]`,
|
|
102
|
+
// 24h. It adopts the string directly, so nothing has to widen the
|
|
103
|
+
// shared date parser to let a Time field display its own value.
|
|
104
|
+
value={wireValue}
|
|
105
|
+
onChange={(picked) => emit?.(toTimeWireValue(picked))}
|
|
106
|
+
defaultValue={defaultValue as string | undefined}
|
|
107
|
+
label={fieldProps.label}
|
|
108
|
+
labelProps={fieldProps.labelProps}
|
|
109
|
+
error={fieldProps.error}
|
|
110
|
+
helperText={fieldProps.helperText}
|
|
111
|
+
readOnly={fieldProps.readOnly}
|
|
112
|
+
skeleton={fieldProps.skeleton}
|
|
113
|
+
compact={fieldProps.compact}
|
|
114
|
+
required={fieldProps.required}
|
|
115
|
+
id={fieldProps.id}
|
|
116
|
+
size={fieldProps.size}
|
|
117
|
+
disabled={disabled}
|
|
118
|
+
placeholder={placeholder}
|
|
119
|
+
onBlur={onBlur}
|
|
120
|
+
/>
|
|
121
|
+
);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
if (type === "datetime") {
|
|
125
|
+
return (
|
|
126
|
+
<FormsDatetimePicker
|
|
127
|
+
name={name}
|
|
128
|
+
form={form}
|
|
129
|
+
validators={validators}
|
|
130
|
+
value={datetimeValue}
|
|
131
|
+
// No `previousValue`: this picker owns both halves of the value and
|
|
132
|
+
// preserves an existing time across a day change itself, so carrying
|
|
133
|
+
// the old time part here would overwrite a time the user just set.
|
|
134
|
+
// The native half passes nothing for the same reason.
|
|
135
|
+
onChange={(date) => emit?.(date ? toFrappeDateValue(date, "datetime") : "")}
|
|
136
|
+
defaultValue={defaultValue as string}
|
|
137
|
+
label={fieldProps.label}
|
|
138
|
+
labelProps={fieldProps.labelProps}
|
|
139
|
+
error={fieldProps.error}
|
|
140
|
+
helperText={fieldProps.helperText}
|
|
141
|
+
readOnly={fieldProps.readOnly}
|
|
142
|
+
skeleton={fieldProps.skeleton}
|
|
143
|
+
compact={fieldProps.compact}
|
|
144
|
+
required={fieldProps.required}
|
|
145
|
+
id={fieldProps.id}
|
|
146
|
+
size={fieldProps.size}
|
|
147
|
+
disabled={disabled}
|
|
148
|
+
minDate={fieldProps.minDate}
|
|
149
|
+
maxDate={fieldProps.maxDate}
|
|
150
|
+
placeholder={placeholder}
|
|
151
|
+
onBlur={onBlur}
|
|
152
|
+
mode={mode}
|
|
153
|
+
preserveValue={preserveValue}
|
|
154
|
+
/>
|
|
155
|
+
);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
return (
|
|
159
|
+
<FormsDatePicker
|
|
160
|
+
name={name}
|
|
161
|
+
form={form}
|
|
162
|
+
validators={validators}
|
|
163
|
+
// Controlled pass-through: folding `value` into `defaultValue` was
|
|
164
|
+
// mount-only, so a value arriving after first paint (create-mode
|
|
165
|
+
// engine seeding) never displayed — and `onChange` was dropped, so
|
|
166
|
+
// picks never reached the consumer doc. `undefined` stays undefined so
|
|
167
|
+
// uncontrolled (defaultValue-only) use keeps its semantics.
|
|
168
|
+
value={wireValue}
|
|
169
|
+
onChange={(date) => emit?.(date ? toFrappeDateValue(date, "date") : "")}
|
|
170
|
+
defaultValue={defaultValue as string}
|
|
171
|
+
label={fieldProps.label}
|
|
172
|
+
labelProps={fieldProps.labelProps}
|
|
173
|
+
error={fieldProps.error}
|
|
174
|
+
helperText={fieldProps.helperText}
|
|
175
|
+
readOnly={fieldProps.readOnly}
|
|
176
|
+
skeleton={fieldProps.skeleton}
|
|
177
|
+
compact={fieldProps.compact}
|
|
178
|
+
required={fieldProps.required}
|
|
179
|
+
id={fieldProps.id}
|
|
180
|
+
size={fieldProps.size}
|
|
181
|
+
disabled={disabled}
|
|
182
|
+
minDate={fieldProps.minDate}
|
|
183
|
+
maxDate={fieldProps.maxDate}
|
|
184
|
+
placeholder={placeholder}
|
|
185
|
+
onBlur={onBlur}
|
|
186
|
+
mode={mode}
|
|
187
|
+
preserveValue={preserveValue}
|
|
188
|
+
/>
|
|
189
|
+
);
|
|
190
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export type DateTimePickerComponent =
|
|
2
|
+
typeof import("@react-native-community/datetimepicker").default;
|
|
3
|
+
export type { DateTimePickerEvent } from "@react-native-community/datetimepicker";
|
|
4
|
+
|
|
5
|
+
let cached: DateTimePickerComponent | null | undefined;
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Loads the optional `@react-native-community/datetimepicker` peer at runtime,
|
|
9
|
+
* the same contract as forms' `nativeDateTime/dateTimePickerLoader.ts`. A
|
|
10
|
+
* static import of an absent peer stays external in the native bundle and
|
|
11
|
+
* hermesc rejects the leftover `import` statement (FZ-7357); the guarded
|
|
12
|
+
* `require` returns null instead, and the field renders its catalog half.
|
|
13
|
+
*/
|
|
14
|
+
export function loadDateTimePicker(): DateTimePickerComponent | null {
|
|
15
|
+
if (cached !== undefined) return cached;
|
|
16
|
+
try {
|
|
17
|
+
const mod = require("@react-native-community/datetimepicker") as {
|
|
18
|
+
default?: DateTimePickerComponent;
|
|
19
|
+
};
|
|
20
|
+
cached = mod?.default ?? null;
|
|
21
|
+
} catch {
|
|
22
|
+
cached = null;
|
|
23
|
+
}
|
|
24
|
+
return cached;
|
|
25
|
+
}
|
|
@@ -25,9 +25,14 @@ import {
|
|
|
25
25
|
} from "react-native";
|
|
26
26
|
import { SafeAreaInsetsContext, initialWindowMetrics } from "react-native-safe-area-context";
|
|
27
27
|
import { platform } from "@multiplatform.one/platform";
|
|
28
|
-
import DateTimePicker, { type DateTimePickerEvent } from "@react-native-community/datetimepicker";
|
|
29
28
|
import { Text, View, XStack, YStack, useProps, useTheme, useThemeName } from "tamagui";
|
|
30
29
|
import { useKeyboardInset } from "../../shared/useKeyboardInset";
|
|
30
|
+
import { Date as CatalogDate } from "./catalog";
|
|
31
|
+
import {
|
|
32
|
+
type DateTimePickerComponent,
|
|
33
|
+
type DateTimePickerEvent,
|
|
34
|
+
loadDateTimePicker,
|
|
35
|
+
} from "./dateTimePickerLoader";
|
|
31
36
|
import { datePickerHeight, dateSheetBottomPadding, dateSheetMaxHeight } from "./sheetKeyboard";
|
|
32
37
|
import type { DateProps } from "./types";
|
|
33
38
|
import { fromFrappeDateValue, toFrappeDateValue } from "./wire";
|
|
@@ -104,10 +109,13 @@ function useNativePickerScheme(): "light" | "dark" {
|
|
|
104
109
|
* written as `function Date` so the global `Date` constructor stays reachable
|
|
105
110
|
* inside this module.
|
|
106
111
|
*/
|
|
107
|
-
function
|
|
112
|
+
function OsDateField<
|
|
108
113
|
TData extends Record<string, unknown> = Record<string, unknown>,
|
|
109
114
|
TName extends string = string,
|
|
110
|
-
>(
|
|
115
|
+
>({
|
|
116
|
+
DateTimePicker,
|
|
117
|
+
...props
|
|
118
|
+
}: DateProps<TData, TName> & { DateTimePicker: DateTimePickerComponent }) {
|
|
111
119
|
const {
|
|
112
120
|
compact,
|
|
113
121
|
defaultValue,
|
|
@@ -421,4 +429,17 @@ function DateField<
|
|
|
421
429
|
);
|
|
422
430
|
}
|
|
423
431
|
|
|
432
|
+
/**
|
|
433
|
+
* Without the optional `@react-native-community/datetimepicker` peer the
|
|
434
|
+
* field keeps working as the catalog picker the web half renders.
|
|
435
|
+
*/
|
|
436
|
+
function DateField<
|
|
437
|
+
TData extends Record<string, unknown> = Record<string, unknown>,
|
|
438
|
+
TName extends string = string,
|
|
439
|
+
>(props: DateProps<TData, TName>) {
|
|
440
|
+
const DateTimePicker = loadDateTimePicker();
|
|
441
|
+
if (!DateTimePicker) return <CatalogDate {...props} />;
|
|
442
|
+
return <OsDateField {...props} DateTimePicker={DateTimePicker} />;
|
|
443
|
+
}
|
|
444
|
+
|
|
424
445
|
export { DateField as Date };
|
|
@@ -1,189 +1,2 @@
|
|
|
1
|
-
|
|
2
|
-
* Web `Date` field for the three Frappe date fieldtypes.
|
|
3
|
-
*
|
|
4
|
-
* The field used to render ONE date-only calendar for all three types, with
|
|
5
|
-
* only the placeholder varying — so a `Datetime` silently dropped its time
|
|
6
|
-
* component from the picker and a `Time` rendered EMPTY, because the calendar
|
|
7
|
-
* adopts its value through `coerceToDate`, for which a bare `HH:mm` is not a
|
|
8
|
-
* date (SB-M-617). Each type now gets the house primitive that matches it:
|
|
9
|
-
*
|
|
10
|
-
* date → forms `DatePicker` (month grid)
|
|
11
|
-
* datetime → forms `DatetimePicker` (Date/Time tabs — grid, then columns)
|
|
12
|
-
* time → the package's own `TimePicker`, which is also what the field
|
|
13
|
-
* registry routes the Frappe `Time` fieldtype to, so a
|
|
14
|
-
* `Date type="time"` and a `Time` field are one control
|
|
15
|
-
*
|
|
16
|
-
* This mirrors what the native half already does (calendar / wheel /
|
|
17
|
-
* calendar-then-wheel), and every branch serializes through the one shared
|
|
18
|
-
* `wire.ts` body, so a web pick and a native pick of the same moment persist
|
|
19
|
-
* byte-identical strings.
|
|
20
|
-
*/
|
|
21
|
-
|
|
22
|
-
import {
|
|
23
|
-
DatePicker as FormsDatePicker,
|
|
24
|
-
DatetimePicker as FormsDatetimePicker,
|
|
25
|
-
} from "@multiplatform.one/forms";
|
|
26
|
-
import { useMemo } from "react";
|
|
27
|
-
import { useProps } from "tamagui";
|
|
28
|
-
import { TimePicker, type TimePickerProps } from "../TimePicker";
|
|
29
|
-
import type { DateProps } from "./types";
|
|
30
|
-
import { fromFrappeDateValue, toFrappeDateValue } from "./wire";
|
|
31
|
-
|
|
1
|
+
export { Date } from "./catalog";
|
|
32
2
|
export type { DateProps } from "./types";
|
|
33
|
-
|
|
34
|
-
/**
|
|
35
|
-
* Adopt a Frappe datetime wire string through the shared read body rather
|
|
36
|
-
* than handing the raw string to the picker, whose `coerceToDate` falls
|
|
37
|
-
* through to `new Date(string)` for anything that is not date-only —
|
|
38
|
-
* implementation-defined for the space-separated shape Frappe writes.
|
|
39
|
-
* Memoized by the caller so the Date identity is stable across renders: the
|
|
40
|
-
* picker re-seeds its display state from every new `value` identity, which
|
|
41
|
-
* would otherwise discard an in-progress edit on an unrelated re-render.
|
|
42
|
-
*/
|
|
43
|
-
function toPickerDate(value: string | undefined): Date | null {
|
|
44
|
-
if (!value) return null;
|
|
45
|
-
const date = fromFrappeDateValue(value, "datetime");
|
|
46
|
-
return Number.isNaN(date.getTime()) ? null : date;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
/**
|
|
50
|
-
* `TimePicker` speaks `HH:mm`; Frappe stores `HH:mm:ss`. Round-tripping
|
|
51
|
-
* through the shared wire body — rather than padding the string here — is
|
|
52
|
-
* what keeps the two platform halves byte-identical, since the native half
|
|
53
|
-
* serializes its wheel pick through exactly this call.
|
|
54
|
-
*/
|
|
55
|
-
function toTimeWireValue(picked: string): string {
|
|
56
|
-
if (!picked) return "";
|
|
57
|
-
const date = fromFrappeDateValue(picked, "time");
|
|
58
|
-
return Number.isNaN(date.getTime()) ? "" : toFrappeDateValue(date, "time");
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
export function Date<
|
|
62
|
-
TData extends Record<string, unknown> = Record<string, unknown>,
|
|
63
|
-
TName extends string = string,
|
|
64
|
-
>(props: DateProps<TData, TName>) {
|
|
65
|
-
const {
|
|
66
|
-
defaultValue,
|
|
67
|
-
form,
|
|
68
|
-
inputProps: _inputProps,
|
|
69
|
-
mode,
|
|
70
|
-
name,
|
|
71
|
-
onBlur,
|
|
72
|
-
onChange,
|
|
73
|
-
preserveValue,
|
|
74
|
-
validators,
|
|
75
|
-
disabled,
|
|
76
|
-
value,
|
|
77
|
-
type = "date",
|
|
78
|
-
...fieldProps
|
|
79
|
-
} = useProps(props) as DateProps<TData, TName> & Record<string, unknown>;
|
|
80
|
-
|
|
81
|
-
const wireValue = value as string | undefined;
|
|
82
|
-
const emit = onChange as ((v: string) => void) | undefined;
|
|
83
|
-
const datetimeValue = useMemo(
|
|
84
|
-
() => (type === "datetime" ? toPickerDate(wireValue) : null),
|
|
85
|
-
[type, wireValue],
|
|
86
|
-
);
|
|
87
|
-
|
|
88
|
-
const placeholder =
|
|
89
|
-
(fieldProps.placeholder as string | undefined) ??
|
|
90
|
-
(type === "time" ? "Select time" : type === "datetime" ? "Select date & time" : "Select date");
|
|
91
|
-
|
|
92
|
-
if (type === "time") {
|
|
93
|
-
return (
|
|
94
|
-
<TimePicker
|
|
95
|
-
// `TimePicker` is not generic over the form's data shape, so the
|
|
96
|
-
// three form props are re-cast to its own non-generic surface.
|
|
97
|
-
name={name as TimePickerProps["name"]}
|
|
98
|
-
form={form as TimePickerProps["form"]}
|
|
99
|
-
validators={validators as TimePickerProps["validators"]}
|
|
100
|
-
// The wire shape is what this control already speaks: `HH:mm[:ss]`,
|
|
101
|
-
// 24h. It adopts the string directly, so nothing has to widen the
|
|
102
|
-
// shared date parser to let a Time field display its own value.
|
|
103
|
-
value={wireValue}
|
|
104
|
-
onChange={(picked) => emit?.(toTimeWireValue(picked))}
|
|
105
|
-
defaultValue={defaultValue as string | undefined}
|
|
106
|
-
label={fieldProps.label}
|
|
107
|
-
labelProps={fieldProps.labelProps}
|
|
108
|
-
error={fieldProps.error}
|
|
109
|
-
helperText={fieldProps.helperText}
|
|
110
|
-
readOnly={fieldProps.readOnly}
|
|
111
|
-
skeleton={fieldProps.skeleton}
|
|
112
|
-
compact={fieldProps.compact}
|
|
113
|
-
required={fieldProps.required}
|
|
114
|
-
id={fieldProps.id}
|
|
115
|
-
size={fieldProps.size}
|
|
116
|
-
disabled={disabled}
|
|
117
|
-
placeholder={placeholder}
|
|
118
|
-
onBlur={onBlur}
|
|
119
|
-
/>
|
|
120
|
-
);
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
if (type === "datetime") {
|
|
124
|
-
return (
|
|
125
|
-
<FormsDatetimePicker
|
|
126
|
-
name={name}
|
|
127
|
-
form={form}
|
|
128
|
-
validators={validators}
|
|
129
|
-
value={datetimeValue}
|
|
130
|
-
// No `previousValue`: this picker owns both halves of the value and
|
|
131
|
-
// preserves an existing time across a day change itself, so carrying
|
|
132
|
-
// the old time part here would overwrite a time the user just set.
|
|
133
|
-
// The native half passes nothing for the same reason.
|
|
134
|
-
onChange={(date) => emit?.(date ? toFrappeDateValue(date, "datetime") : "")}
|
|
135
|
-
defaultValue={defaultValue as string}
|
|
136
|
-
label={fieldProps.label}
|
|
137
|
-
labelProps={fieldProps.labelProps}
|
|
138
|
-
error={fieldProps.error}
|
|
139
|
-
helperText={fieldProps.helperText}
|
|
140
|
-
readOnly={fieldProps.readOnly}
|
|
141
|
-
skeleton={fieldProps.skeleton}
|
|
142
|
-
compact={fieldProps.compact}
|
|
143
|
-
required={fieldProps.required}
|
|
144
|
-
id={fieldProps.id}
|
|
145
|
-
size={fieldProps.size}
|
|
146
|
-
disabled={disabled}
|
|
147
|
-
minDate={fieldProps.minDate}
|
|
148
|
-
maxDate={fieldProps.maxDate}
|
|
149
|
-
placeholder={placeholder}
|
|
150
|
-
onBlur={onBlur}
|
|
151
|
-
mode={mode}
|
|
152
|
-
preserveValue={preserveValue}
|
|
153
|
-
/>
|
|
154
|
-
);
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
return (
|
|
158
|
-
<FormsDatePicker
|
|
159
|
-
name={name}
|
|
160
|
-
form={form}
|
|
161
|
-
validators={validators}
|
|
162
|
-
// Controlled pass-through: folding `value` into `defaultValue` was
|
|
163
|
-
// mount-only, so a value arriving after first paint (create-mode
|
|
164
|
-
// engine seeding) never displayed — and `onChange` was dropped, so
|
|
165
|
-
// picks never reached the consumer doc. `undefined` stays undefined so
|
|
166
|
-
// uncontrolled (defaultValue-only) use keeps its semantics.
|
|
167
|
-
value={wireValue}
|
|
168
|
-
onChange={(date) => emit?.(date ? toFrappeDateValue(date, "date") : "")}
|
|
169
|
-
defaultValue={defaultValue as string}
|
|
170
|
-
label={fieldProps.label}
|
|
171
|
-
labelProps={fieldProps.labelProps}
|
|
172
|
-
error={fieldProps.error}
|
|
173
|
-
helperText={fieldProps.helperText}
|
|
174
|
-
readOnly={fieldProps.readOnly}
|
|
175
|
-
skeleton={fieldProps.skeleton}
|
|
176
|
-
compact={fieldProps.compact}
|
|
177
|
-
required={fieldProps.required}
|
|
178
|
-
id={fieldProps.id}
|
|
179
|
-
size={fieldProps.size}
|
|
180
|
-
disabled={disabled}
|
|
181
|
-
minDate={fieldProps.minDate}
|
|
182
|
-
maxDate={fieldProps.maxDate}
|
|
183
|
-
placeholder={placeholder}
|
|
184
|
-
onBlur={onBlur}
|
|
185
|
-
mode={mode}
|
|
186
|
-
preserveValue={preserveValue}
|
|
187
|
-
/>
|
|
188
|
-
);
|
|
189
|
-
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The native `Date` field without the optional
|
|
3
|
+
* `@react-native-community/datetimepicker` peer (FZ-7357): it renders the
|
|
4
|
+
* catalog half instead of an OS picker it cannot load.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { renderWithProviders } from "@multiplatform.one/test-utils";
|
|
8
|
+
import { describe, expect, it, vi } from "vitest";
|
|
9
|
+
|
|
10
|
+
vi.setConfig({ testTimeout: 240_000 });
|
|
11
|
+
|
|
12
|
+
vi.mock("./dateTimePickerLoader", () => ({ loadDateTimePicker: () => null }));
|
|
13
|
+
vi.mock("./catalog", () => ({
|
|
14
|
+
Date: ({ type }: { type?: string }) => <div data-testid="catalog-date" data-type={type} />,
|
|
15
|
+
}));
|
|
16
|
+
|
|
17
|
+
describe("native Date without the datetimepicker peer", () => {
|
|
18
|
+
it.each(["date", "datetime", "time"] as const)("renders the catalog %s field", async (type) => {
|
|
19
|
+
const { Date: DateField } = await import("./index.native");
|
|
20
|
+
const result = renderWithProviders(<DateField label="When" type={type} />);
|
|
21
|
+
expect(result.getByTestId("catalog-date").dataset.type).toBe(type);
|
|
22
|
+
});
|
|
23
|
+
});
|
|
@@ -36,8 +36,8 @@ vi.mock("../../shared/useKeyboardInset", () => ({
|
|
|
36
36
|
useKeyboardInset: () => mockState.keyboardInset,
|
|
37
37
|
}));
|
|
38
38
|
|
|
39
|
-
vi.mock("
|
|
40
|
-
|
|
39
|
+
vi.mock("./dateTimePickerLoader", () => ({
|
|
40
|
+
loadDateTimePicker: () => (props: Record<string, unknown>) => {
|
|
41
41
|
mockState.picks.push(props);
|
|
42
42
|
return null;
|
|
43
43
|
},
|
|
@@ -30,8 +30,8 @@ const mockState = vi.hoisted(() => ({
|
|
|
30
30
|
picks: [] as Record<string, unknown>[],
|
|
31
31
|
}));
|
|
32
32
|
|
|
33
|
-
vi.mock("
|
|
34
|
-
|
|
33
|
+
vi.mock("./dateTimePickerLoader", () => ({
|
|
34
|
+
loadDateTimePicker: () => (props: Record<string, unknown>) => {
|
|
35
35
|
mockState.picks.push(props);
|
|
36
36
|
return null;
|
|
37
37
|
},
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/fields/Barcode/index.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAC;AAGpF,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AAGpD,MAAM,MAAM,YAAY,CACtB,KAAK,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC/D,KAAK,SAAS,MAAM,GAAG,MAAM,IAC3B,IAAI,CACN,cAAc,CACZ,KAAK,EACL,KAAK,SAAS,QAAQ,CAAC,KAAK,CAAC,GAAG,KAAK,GAAG,QAAQ,CAAC,KAAK,CAAC,EACvD,OAAO,EACP,OAAO,EACP,MAAM,CACP,EACD,UAAU,GAAG,OAAO,CACrB,GACC,OAAO,CACL,IAAI,CACF,mBAAmB,CACjB,KAAK,EACL,KAAK,SAAS,QAAQ,CAAC,KAAK,CAAC,GAAG,KAAK,GAAG,QAAQ,CAAC,KAAK,CAAC,EACvD,OAAO,EACP,OAAO,EACP,MAAM,CACP,EACD,UAAU,CACX,CACF,GAAG;IACF,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACnC,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;CACpB,CAAC;AAEJ,wBAAgB,OAAO,CACrB,KAAK,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC/D,KAAK,SAAS,MAAM,GAAG,MAAM,EAC7B,KAAK,EAAE,YAAY,CAAC,KAAK,EAAE,KAAK,CAAC,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/fields/Barcode/index.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAC;AAGpF,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AAGpD,MAAM,MAAM,YAAY,CACtB,KAAK,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC/D,KAAK,SAAS,MAAM,GAAG,MAAM,IAC3B,IAAI,CACN,cAAc,CACZ,KAAK,EACL,KAAK,SAAS,QAAQ,CAAC,KAAK,CAAC,GAAG,KAAK,GAAG,QAAQ,CAAC,KAAK,CAAC,EACvD,OAAO,EACP,OAAO,EACP,MAAM,CACP,EACD,UAAU,GAAG,OAAO,CACrB,GACC,OAAO,CACL,IAAI,CACF,mBAAmB,CACjB,KAAK,EACL,KAAK,SAAS,QAAQ,CAAC,KAAK,CAAC,GAAG,KAAK,GAAG,QAAQ,CAAC,KAAK,CAAC,EACvD,OAAO,EACP,OAAO,EACP,MAAM,CACP,EACD,UAAU,CACX,CACF,GAAG;IACF,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACnC,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;CACpB,CAAC;AAEJ,wBAAgB,OAAO,CACrB,KAAK,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC/D,KAAK,SAAS,MAAM,GAAG,MAAM,EAC7B,KAAK,EAAE,YAAY,CAAC,KAAK,EAAE,KAAK,CAAC,2CA4ClC"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Web `Date` field for the three Frappe date fieldtypes, and the native one
|
|
3
|
+
* when the optional `@react-native-community/datetimepicker` peer is absent.
|
|
4
|
+
*
|
|
5
|
+
* The field used to render ONE date-only calendar for all three types, with
|
|
6
|
+
* only the placeholder varying — so a `Datetime` silently dropped its time
|
|
7
|
+
* component from the picker and a `Time` rendered EMPTY, because the calendar
|
|
8
|
+
* adopts its value through `coerceToDate`, for which a bare `HH:mm` is not a
|
|
9
|
+
* date (SB-M-617). Each type now gets the house primitive that matches it:
|
|
10
|
+
*
|
|
11
|
+
* date → forms `DatePicker` (month grid)
|
|
12
|
+
* datetime → forms `DatetimePicker` (Date/Time tabs — grid, then columns)
|
|
13
|
+
* time → the package's own `TimePicker`, which is also what the field
|
|
14
|
+
* registry routes the Frappe `Time` fieldtype to, so a
|
|
15
|
+
* `Date type="time"` and a `Time` field are one control
|
|
16
|
+
*
|
|
17
|
+
* This mirrors what the native half already does (calendar / wheel /
|
|
18
|
+
* calendar-then-wheel), and every branch serializes through the one shared
|
|
19
|
+
* `wire.ts` body, so a web pick and a native pick of the same moment persist
|
|
20
|
+
* byte-identical strings.
|
|
21
|
+
*/
|
|
22
|
+
import type { DateProps } from "./types";
|
|
23
|
+
export type { DateProps } from "./types";
|
|
24
|
+
export declare function Date<TData extends Record<string, unknown> = Record<string, unknown>, TName extends string = string>(props: DateProps<TData, TName>): import("react/jsx-runtime").JSX.Element;
|
|
25
|
+
//# sourceMappingURL=catalog.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"catalog.d.ts","sourceRoot":"","sources":["../../../src/fields/Date/catalog.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AASH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AAGzC,YAAY,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AA6BzC,wBAAgB,IAAI,CAClB,KAAK,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC/D,KAAK,SAAS,MAAM,GAAG,MAAM,EAC7B,KAAK,EAAE,SAAS,CAAC,KAAK,EAAE,KAAK,CAAC,2CA6H/B"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export type DateTimePickerComponent = typeof import("@react-native-community/datetimepicker").default;
|
|
2
|
+
export type { DateTimePickerEvent } from "@react-native-community/datetimepicker";
|
|
3
|
+
/**
|
|
4
|
+
* Loads the optional `@react-native-community/datetimepicker` peer at runtime,
|
|
5
|
+
* the same contract as forms' `nativeDateTime/dateTimePickerLoader.ts`. A
|
|
6
|
+
* static import of an absent peer stays external in the native bundle and
|
|
7
|
+
* hermesc rejects the leftover `import` statement (FZ-7357); the guarded
|
|
8
|
+
* `require` returns null instead, and the field renders its catalog half.
|
|
9
|
+
*/
|
|
10
|
+
export declare function loadDateTimePicker(): DateTimePickerComponent | null;
|
|
11
|
+
//# sourceMappingURL=dateTimePickerLoader.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dateTimePickerLoader.d.ts","sourceRoot":"","sources":["../../../src/fields/Date/dateTimePickerLoader.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,uBAAuB,GACjC,cAAc,wCAAwC,EAAE,OAAO,CAAC;AAClE,YAAY,EAAE,mBAAmB,EAAE,MAAM,wCAAwC,CAAC;AAIlF;;;;;;GAMG;AACH,wBAAgB,kBAAkB,IAAI,uBAAuB,GAAG,IAAI,CAWnE"}
|
|
@@ -1,24 +1,3 @@
|
|
|
1
|
-
|
|
2
|
-
* Web `Date` field for the three Frappe date fieldtypes.
|
|
3
|
-
*
|
|
4
|
-
* The field used to render ONE date-only calendar for all three types, with
|
|
5
|
-
* only the placeholder varying — so a `Datetime` silently dropped its time
|
|
6
|
-
* component from the picker and a `Time` rendered EMPTY, because the calendar
|
|
7
|
-
* adopts its value through `coerceToDate`, for which a bare `HH:mm` is not a
|
|
8
|
-
* date (SB-M-617). Each type now gets the house primitive that matches it:
|
|
9
|
-
*
|
|
10
|
-
* date → forms `DatePicker` (month grid)
|
|
11
|
-
* datetime → forms `DatetimePicker` (Date/Time tabs — grid, then columns)
|
|
12
|
-
* time → the package's own `TimePicker`, which is also what the field
|
|
13
|
-
* registry routes the Frappe `Time` fieldtype to, so a
|
|
14
|
-
* `Date type="time"` and a `Time` field are one control
|
|
15
|
-
*
|
|
16
|
-
* This mirrors what the native half already does (calendar / wheel /
|
|
17
|
-
* calendar-then-wheel), and every branch serializes through the one shared
|
|
18
|
-
* `wire.ts` body, so a web pick and a native pick of the same moment persist
|
|
19
|
-
* byte-identical strings.
|
|
20
|
-
*/
|
|
21
|
-
import type { DateProps } from "./types";
|
|
1
|
+
export { Date } from "./catalog";
|
|
22
2
|
export type { DateProps } from "./types";
|
|
23
|
-
export declare function Date<TData extends Record<string, unknown> = Record<string, unknown>, TName extends string = string>(props: DateProps<TData, TName>): import("react/jsx-runtime").JSX.Element;
|
|
24
3
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/fields/Date/index.tsx"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/fields/Date/index.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,YAAY,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC"}
|
|
@@ -6,13 +6,8 @@ import type { DateProps } from "./types";
|
|
|
6
6
|
*/
|
|
7
7
|
export type { DateProps } from "./types";
|
|
8
8
|
/**
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
* Resolution picks this file over `index.tsx` on native, so the field is the
|
|
12
|
-
* real OS picker: the iOS 14+ calendar for dates (`inline`), the wheel for
|
|
13
|
-
* times, and the Material dialog on Android. Declared as `Date` rather than
|
|
14
|
-
* written as `function Date` so the global `Date` constructor stays reachable
|
|
15
|
-
* inside this module.
|
|
9
|
+
* Without the optional `@react-native-community/datetimepicker` peer the
|
|
10
|
+
* field keeps working as the catalog picker the web half renders.
|
|
16
11
|
*/
|
|
17
12
|
declare function DateField<TData extends Record<string, unknown> = Record<string, unknown>, TName extends string = string>(props: DateProps<TData, TName>): import("react/jsx-runtime").JSX.Element;
|
|
18
13
|
export { DateField as Date };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.native.d.ts","sourceRoot":"","sources":["../../../src/fields/Date/index.native.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.native.d.ts","sourceRoot":"","sources":["../../../src/fields/Date/index.native.tsx"],"names":[],"mappings":"AAoCA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AAGzC;;;;GAIG;AAEH,YAAY,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AAkYzC;;;GAGG;AACH,iBAAS,SAAS,CAChB,KAAK,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC/D,KAAK,SAAS,MAAM,GAAG,MAAM,EAC7B,KAAK,EAAE,SAAS,CAAC,KAAK,EAAE,KAAK,CAAC,2CAI/B;AAED,OAAO,EAAE,SAAS,IAAI,IAAI,EAAE,CAAC"}
|