@multiplatform.one/forms 6.4.3 → 6.6.0
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/Button/index.cjs +23 -8
- package/dist/cjs/Button/index.native.js +23 -8
- package/dist/cjs/Button/index.native.js.map +1 -1
- package/dist/cjs/Skeleton/index.cjs +1 -0
- package/dist/cjs/Skeleton/index.native.js +4 -0
- package/dist/cjs/Skeleton/index.native.js.map +1 -1
- package/dist/cjs/fields/Checkbox/Checkbox.cjs +22 -15
- package/dist/cjs/fields/Checkbox/Checkbox.native.js +11 -6
- package/dist/cjs/fields/Checkbox/Checkbox.native.js.map +1 -1
- package/dist/cjs/fields/Checkbox/index.cjs +9 -3
- package/dist/cjs/fields/Checkbox/index.native.js +9 -3
- package/dist/cjs/fields/Checkbox/index.native.js.map +1 -1
- package/dist/cjs/fields/Combobox/index.cjs +9 -4
- package/dist/cjs/fields/Combobox/index.native.js +9 -4
- package/dist/cjs/fields/Combobox/index.native.js.map +1 -1
- package/dist/cjs/fields/DatePicker/DateRangePicker.cjs +30 -20
- package/dist/cjs/fields/DatePicker/DateRangePicker.native.js +36 -21
- package/dist/cjs/fields/DatePicker/DateRangePicker.native.js.map +1 -1
- package/dist/cjs/fields/DatePicker/DatetimePicker.cjs +1 -1
- package/dist/cjs/fields/DatePicker/DatetimePicker.native.js +1 -1
- package/dist/cjs/fields/DatePicker/DatetimePicker.native.js.map +1 -1
- package/dist/cjs/fields/DatePicker/MonthPicker.cjs +24 -14
- package/dist/cjs/fields/DatePicker/MonthPicker.native.js +36 -15
- package/dist/cjs/fields/DatePicker/MonthPicker.native.js.map +1 -1
- package/dist/cjs/fields/DatePicker/MultiDatePicker.cjs +27 -18
- package/dist/cjs/fields/DatePicker/MultiDatePicker.native.js +32 -19
- package/dist/cjs/fields/DatePicker/MultiDatePicker.native.js.map +1 -1
- package/dist/cjs/fields/DatePicker/fieldValueSync.cjs +11 -1
- package/dist/cjs/fields/DatePicker/fieldValueSync.native.js +12 -1
- package/dist/cjs/fields/DatePicker/fieldValueSync.native.js.map +1 -1
- package/dist/cjs/fields/Geolocation/GeolocationMap.native.js +2 -2
- package/dist/cjs/fields/RadioGroup/index.cjs +2 -2
- package/dist/cjs/fields/RadioGroup/index.native.js +2 -2
- package/dist/cjs/fields/RadioGroup/index.native.js.map +1 -1
- package/dist/cjs/fields/Rating/index.cjs +17 -2
- package/dist/cjs/fields/Rating/index.native.js +25 -2
- package/dist/cjs/fields/Rating/index.native.js.map +1 -1
- package/dist/cjs/fields/SearchInput/index.cjs +5 -1
- package/dist/cjs/fields/SearchInput/index.native.js +5 -1
- package/dist/cjs/fields/SearchInput/index.native.js.map +1 -1
- package/dist/cjs/fields/Select/index.cjs +6 -10
- package/dist/cjs/fields/Select/index.native.js +12 -10
- package/dist/cjs/fields/Select/index.native.js.map +1 -1
- package/dist/cjs/fields/Signature/index.cjs +1 -1
- package/dist/cjs/fields/Signature/index.native.js +1 -1
- package/dist/cjs/fields/Signature/index.native.js.map +1 -1
- package/dist/cjs/fields/Slider/index.cjs +1 -0
- package/dist/cjs/fields/Slider/index.native.js +4 -0
- package/dist/cjs/fields/Slider/index.native.js.map +1 -1
- package/dist/cjs/fields/TextArea/index.cjs +3 -2
- package/dist/cjs/fields/TextArea/index.native.js +5 -2
- package/dist/cjs/fields/TextArea/index.native.js.map +1 -1
- package/dist/cjs/fields/TimePicker/index.cjs +1 -1
- package/dist/cjs/fields/TimePicker/index.native.js +1 -1
- package/dist/cjs/fields/TimePicker/index.native.js.map +1 -1
- package/dist/cjs/fields/ToggleGroup/index.cjs +6 -22
- package/dist/cjs/fields/ToggleGroup/index.native.js +11 -28
- package/dist/cjs/fields/ToggleGroup/index.native.js.map +1 -1
- package/dist/cjs/shared/TimeColumn.cjs +1 -1
- package/dist/cjs/shared/TimeColumn.native.js +1 -1
- package/dist/cjs/shared/TimeColumn.native.js.map +1 -1
- package/dist/cjs/shared/focusManagement.cjs +7 -1
- package/dist/cjs/shared/focusManagement.native.js +5 -1
- package/dist/cjs/shared/focusManagement.native.js.map +1 -1
- package/dist/cjs/shared/t.cjs +5 -0
- package/dist/cjs/shared/t.native.js +5 -0
- package/dist/cjs/shared/t.native.js.map +1 -1
- package/dist/esm/Button/index.mjs +24 -9
- package/dist/esm/Button/index.mjs.map +1 -1
- package/dist/esm/Button/index.native.js +24 -9
- package/dist/esm/Button/index.native.js.map +1 -1
- package/dist/esm/Skeleton/index.mjs +2 -1
- package/dist/esm/Skeleton/index.mjs.map +1 -1
- package/dist/esm/Skeleton/index.native.js +5 -1
- package/dist/esm/Skeleton/index.native.js.map +1 -1
- package/dist/esm/fields/Checkbox/Checkbox.mjs +24 -17
- package/dist/esm/fields/Checkbox/Checkbox.mjs.map +1 -1
- package/dist/esm/fields/Checkbox/Checkbox.native.js +13 -8
- package/dist/esm/fields/Checkbox/Checkbox.native.js.map +1 -1
- package/dist/esm/fields/Checkbox/index.mjs +11 -5
- package/dist/esm/fields/Checkbox/index.mjs.map +1 -1
- package/dist/esm/fields/Checkbox/index.native.js +11 -5
- package/dist/esm/fields/Checkbox/index.native.js.map +1 -1
- package/dist/esm/fields/Combobox/index.mjs +9 -4
- package/dist/esm/fields/Combobox/index.mjs.map +1 -1
- package/dist/esm/fields/Combobox/index.native.js +9 -4
- package/dist/esm/fields/Combobox/index.native.js.map +1 -1
- package/dist/esm/fields/DatePicker/DateRangePicker.mjs +31 -21
- package/dist/esm/fields/DatePicker/DateRangePicker.mjs.map +1 -1
- package/dist/esm/fields/DatePicker/DateRangePicker.native.js +37 -22
- package/dist/esm/fields/DatePicker/DateRangePicker.native.js.map +1 -1
- package/dist/esm/fields/DatePicker/DatetimePicker.mjs +1 -1
- package/dist/esm/fields/DatePicker/DatetimePicker.mjs.map +1 -1
- package/dist/esm/fields/DatePicker/DatetimePicker.native.js +1 -1
- package/dist/esm/fields/DatePicker/DatetimePicker.native.js.map +1 -1
- package/dist/esm/fields/DatePicker/MonthPicker.mjs +25 -15
- package/dist/esm/fields/DatePicker/MonthPicker.mjs.map +1 -1
- package/dist/esm/fields/DatePicker/MonthPicker.native.js +37 -16
- package/dist/esm/fields/DatePicker/MonthPicker.native.js.map +1 -1
- package/dist/esm/fields/DatePicker/MultiDatePicker.mjs +28 -19
- package/dist/esm/fields/DatePicker/MultiDatePicker.mjs.map +1 -1
- package/dist/esm/fields/DatePicker/MultiDatePicker.native.js +33 -20
- package/dist/esm/fields/DatePicker/MultiDatePicker.native.js.map +1 -1
- package/dist/esm/fields/DatePicker/fieldValueSync.mjs +10 -1
- package/dist/esm/fields/DatePicker/fieldValueSync.mjs.map +1 -1
- package/dist/esm/fields/DatePicker/fieldValueSync.native.js +11 -1
- package/dist/esm/fields/DatePicker/fieldValueSync.native.js.map +1 -1
- package/dist/esm/fields/Geolocation/GeolocationMap.native.js +2 -2
- package/dist/esm/fields/RadioGroup/index.mjs +2 -2
- package/dist/esm/fields/RadioGroup/index.mjs.map +1 -1
- package/dist/esm/fields/RadioGroup/index.native.js +2 -2
- package/dist/esm/fields/RadioGroup/index.native.js.map +1 -1
- package/dist/esm/fields/Rating/index.mjs +17 -2
- package/dist/esm/fields/Rating/index.mjs.map +1 -1
- package/dist/esm/fields/Rating/index.native.js +25 -2
- package/dist/esm/fields/Rating/index.native.js.map +1 -1
- package/dist/esm/fields/SearchInput/index.mjs +6 -2
- package/dist/esm/fields/SearchInput/index.mjs.map +1 -1
- package/dist/esm/fields/SearchInput/index.native.js +6 -2
- package/dist/esm/fields/SearchInput/index.native.js.map +1 -1
- package/dist/esm/fields/Select/index.mjs +6 -10
- package/dist/esm/fields/Select/index.mjs.map +1 -1
- package/dist/esm/fields/Select/index.native.js +12 -10
- package/dist/esm/fields/Select/index.native.js.map +1 -1
- package/dist/esm/fields/Signature/index.mjs +1 -1
- package/dist/esm/fields/Signature/index.mjs.map +1 -1
- package/dist/esm/fields/Signature/index.native.js +1 -1
- package/dist/esm/fields/Signature/index.native.js.map +1 -1
- package/dist/esm/fields/Slider/index.mjs +1 -0
- package/dist/esm/fields/Slider/index.mjs.map +1 -1
- package/dist/esm/fields/Slider/index.native.js +4 -0
- package/dist/esm/fields/Slider/index.native.js.map +1 -1
- package/dist/esm/fields/TextArea/index.mjs +3 -2
- package/dist/esm/fields/TextArea/index.mjs.map +1 -1
- package/dist/esm/fields/TextArea/index.native.js +5 -2
- package/dist/esm/fields/TextArea/index.native.js.map +1 -1
- package/dist/esm/fields/TimePicker/index.mjs +1 -1
- package/dist/esm/fields/TimePicker/index.mjs.map +1 -1
- package/dist/esm/fields/TimePicker/index.native.js +1 -1
- package/dist/esm/fields/TimePicker/index.native.js.map +1 -1
- package/dist/esm/fields/ToggleGroup/index.mjs +7 -23
- package/dist/esm/fields/ToggleGroup/index.mjs.map +1 -1
- package/dist/esm/fields/ToggleGroup/index.native.js +12 -29
- package/dist/esm/fields/ToggleGroup/index.native.js.map +1 -1
- package/dist/esm/shared/TimeColumn.mjs +1 -1
- package/dist/esm/shared/TimeColumn.mjs.map +1 -1
- package/dist/esm/shared/TimeColumn.native.js +1 -1
- package/dist/esm/shared/TimeColumn.native.js.map +1 -1
- package/dist/esm/shared/focusManagement.mjs +7 -1
- package/dist/esm/shared/focusManagement.mjs.map +1 -1
- package/dist/esm/shared/focusManagement.native.js +5 -1
- package/dist/esm/shared/focusManagement.native.js.map +1 -1
- package/dist/esm/shared/t.mjs +5 -1
- package/dist/esm/shared/t.mjs.map +1 -1
- package/dist/esm/shared/t.native.js +5 -1
- package/dist/esm/shared/t.native.js.map +1 -1
- package/dist/jsx/Button/index.mjs +24 -9
- package/dist/jsx/Button/index.mjs.map +1 -1
- package/dist/jsx/Button/index.native.js +23 -8
- package/dist/jsx/Button/index.native.js.map +1 -1
- package/dist/jsx/Skeleton/index.mjs +2 -1
- package/dist/jsx/Skeleton/index.mjs.map +1 -1
- package/dist/jsx/Skeleton/index.native.js +4 -0
- package/dist/jsx/Skeleton/index.native.js.map +1 -1
- package/dist/jsx/fields/Checkbox/Checkbox.mjs +24 -17
- package/dist/jsx/fields/Checkbox/Checkbox.mjs.map +1 -1
- package/dist/jsx/fields/Checkbox/Checkbox.native.js +11 -6
- package/dist/jsx/fields/Checkbox/Checkbox.native.js.map +1 -1
- package/dist/jsx/fields/Checkbox/index.mjs +11 -5
- package/dist/jsx/fields/Checkbox/index.mjs.map +1 -1
- package/dist/jsx/fields/Checkbox/index.native.js +9 -3
- package/dist/jsx/fields/Checkbox/index.native.js.map +1 -1
- package/dist/jsx/fields/Combobox/index.mjs +9 -4
- package/dist/jsx/fields/Combobox/index.mjs.map +1 -1
- package/dist/jsx/fields/Combobox/index.native.js +9 -4
- package/dist/jsx/fields/Combobox/index.native.js.map +1 -1
- package/dist/jsx/fields/DatePicker/DateRangePicker.mjs +31 -21
- package/dist/jsx/fields/DatePicker/DateRangePicker.mjs.map +1 -1
- package/dist/jsx/fields/DatePicker/DateRangePicker.native.js +36 -21
- package/dist/jsx/fields/DatePicker/DateRangePicker.native.js.map +1 -1
- package/dist/jsx/fields/DatePicker/DatetimePicker.mjs +1 -1
- package/dist/jsx/fields/DatePicker/DatetimePicker.mjs.map +1 -1
- package/dist/jsx/fields/DatePicker/DatetimePicker.native.js +1 -1
- package/dist/jsx/fields/DatePicker/DatetimePicker.native.js.map +1 -1
- package/dist/jsx/fields/DatePicker/MonthPicker.mjs +25 -15
- package/dist/jsx/fields/DatePicker/MonthPicker.mjs.map +1 -1
- package/dist/jsx/fields/DatePicker/MonthPicker.native.js +36 -15
- package/dist/jsx/fields/DatePicker/MonthPicker.native.js.map +1 -1
- package/dist/jsx/fields/DatePicker/MultiDatePicker.mjs +28 -19
- package/dist/jsx/fields/DatePicker/MultiDatePicker.mjs.map +1 -1
- package/dist/jsx/fields/DatePicker/MultiDatePicker.native.js +32 -19
- package/dist/jsx/fields/DatePicker/MultiDatePicker.native.js.map +1 -1
- package/dist/jsx/fields/DatePicker/fieldValueSync.mjs +10 -1
- package/dist/jsx/fields/DatePicker/fieldValueSync.mjs.map +1 -1
- package/dist/jsx/fields/DatePicker/fieldValueSync.native.js +12 -1
- package/dist/jsx/fields/DatePicker/fieldValueSync.native.js.map +1 -1
- package/dist/jsx/fields/Geolocation/GeolocationMap.native.js +2 -2
- package/dist/jsx/fields/RadioGroup/index.mjs +2 -2
- package/dist/jsx/fields/RadioGroup/index.mjs.map +1 -1
- package/dist/jsx/fields/RadioGroup/index.native.js +2 -2
- package/dist/jsx/fields/RadioGroup/index.native.js.map +1 -1
- package/dist/jsx/fields/Rating/index.mjs +17 -2
- package/dist/jsx/fields/Rating/index.mjs.map +1 -1
- package/dist/jsx/fields/Rating/index.native.js +25 -2
- package/dist/jsx/fields/Rating/index.native.js.map +1 -1
- package/dist/jsx/fields/SearchInput/index.mjs +6 -2
- package/dist/jsx/fields/SearchInput/index.mjs.map +1 -1
- package/dist/jsx/fields/SearchInput/index.native.js +5 -1
- package/dist/jsx/fields/SearchInput/index.native.js.map +1 -1
- package/dist/jsx/fields/Select/index.mjs +6 -10
- package/dist/jsx/fields/Select/index.mjs.map +1 -1
- package/dist/jsx/fields/Select/index.native.js +12 -10
- package/dist/jsx/fields/Select/index.native.js.map +1 -1
- package/dist/jsx/fields/Signature/index.mjs +1 -1
- package/dist/jsx/fields/Signature/index.mjs.map +1 -1
- package/dist/jsx/fields/Signature/index.native.js +1 -1
- package/dist/jsx/fields/Signature/index.native.js.map +1 -1
- package/dist/jsx/fields/Slider/index.mjs +1 -0
- package/dist/jsx/fields/Slider/index.mjs.map +1 -1
- package/dist/jsx/fields/Slider/index.native.js +4 -0
- package/dist/jsx/fields/Slider/index.native.js.map +1 -1
- package/dist/jsx/fields/TextArea/index.mjs +3 -2
- package/dist/jsx/fields/TextArea/index.mjs.map +1 -1
- package/dist/jsx/fields/TextArea/index.native.js +5 -2
- package/dist/jsx/fields/TextArea/index.native.js.map +1 -1
- package/dist/jsx/fields/TimePicker/index.mjs +1 -1
- package/dist/jsx/fields/TimePicker/index.mjs.map +1 -1
- package/dist/jsx/fields/TimePicker/index.native.js +1 -1
- package/dist/jsx/fields/TimePicker/index.native.js.map +1 -1
- package/dist/jsx/fields/ToggleGroup/index.mjs +7 -23
- package/dist/jsx/fields/ToggleGroup/index.mjs.map +1 -1
- package/dist/jsx/fields/ToggleGroup/index.native.js +11 -28
- package/dist/jsx/fields/ToggleGroup/index.native.js.map +1 -1
- package/dist/jsx/shared/TimeColumn.mjs +1 -1
- package/dist/jsx/shared/TimeColumn.mjs.map +1 -1
- package/dist/jsx/shared/TimeColumn.native.js +1 -1
- package/dist/jsx/shared/TimeColumn.native.js.map +1 -1
- package/dist/jsx/shared/focusManagement.mjs +7 -1
- package/dist/jsx/shared/focusManagement.mjs.map +1 -1
- package/dist/jsx/shared/focusManagement.native.js +5 -1
- package/dist/jsx/shared/focusManagement.native.js.map +1 -1
- package/dist/jsx/shared/t.mjs +5 -1
- package/dist/jsx/shared/t.mjs.map +1 -1
- package/dist/jsx/shared/t.native.js +5 -0
- package/dist/jsx/shared/t.native.js.map +1 -1
- package/package.json +8 -7
- package/src/Button/Button.stories.tsx +19 -0
- package/src/Button/index.tsx +45 -12
- package/src/ControlGroup/ControlGroup.stories.tsx +12 -0
- package/src/FieldLayout.stories.tsx +3 -2
- package/src/Form/Form.stories.tsx +13 -0
- package/src/Skeleton/index.tsx +5 -1
- package/src/fields/Checkbox/Checkbox.spec.tsx +50 -1
- package/src/fields/Checkbox/Checkbox.tsx +47 -19
- package/src/fields/Checkbox/index.tsx +23 -5
- package/src/fields/ColorPicker/ColorPicker.stories.tsx +2 -1
- package/src/fields/Combobox/index.tsx +8 -3
- package/src/fields/DatePicker/DatePicker.spec.tsx +93 -0
- package/src/fields/DatePicker/DatePicker.stories.tsx +2 -1
- package/src/fields/DatePicker/DateRangePicker.spec.tsx +72 -0
- package/src/fields/DatePicker/DateRangePicker.tsx +44 -30
- package/src/fields/DatePicker/DatetimePicker.spec.tsx +39 -0
- package/src/fields/DatePicker/DatetimePicker.tsx +1 -1
- package/src/fields/DatePicker/MonthPicker.spec.tsx +61 -0
- package/src/fields/DatePicker/MonthPicker.tsx +47 -21
- package/src/fields/DatePicker/MultiDatePicker.spec.tsx +34 -0
- package/src/fields/DatePicker/MultiDatePicker.tsx +39 -27
- package/src/fields/DatePicker/fieldValueSync.tsx +23 -0
- package/src/fields/FileUpload/FileUpload.stories.tsx +11 -1
- package/src/fields/Geolocation/Geolocation.stories.tsx +9 -0
- package/src/fields/Geolocation/GeolocationMap.native.tsx +2 -2
- package/src/fields/ImageField/ImageField.stories.tsx +2 -1
- package/src/fields/Input/Input.stories.tsx +19 -0
- package/src/fields/Progress/Progress.stories.tsx +19 -0
- package/src/fields/RadioGroup/RadioGroup.spec.tsx +139 -2
- package/src/fields/RadioGroup/index.tsx +2 -2
- package/src/fields/Rating/Rating.spec.tsx +43 -1
- package/src/fields/Rating/index.tsx +43 -4
- package/src/fields/SearchInput/SearchInput.spec.tsx +11 -0
- package/src/fields/SearchInput/index.tsx +6 -2
- package/src/fields/Select/Select.stories.tsx +19 -0
- package/src/fields/Select/index.tsx +12 -5
- package/src/fields/Signature/index.tsx +1 -1
- package/src/fields/Slider/Slider.spec.tsx +89 -4
- package/src/fields/Slider/Slider.stories.tsx +19 -0
- package/src/fields/Slider/index.tsx +4 -0
- package/src/fields/Stepper/Stepper.stories.tsx +12 -0
- package/src/fields/Switch/Switch.spec.tsx +50 -2
- package/src/fields/TextArea/TextArea.spec.tsx +57 -0
- package/src/fields/TextArea/TextArea.stories.tsx +19 -0
- package/src/fields/TextArea/index.tsx +10 -2
- package/src/fields/TimePicker/index.tsx +1 -1
- package/src/fields/ToggleGroup/ToggleGroup.spec.tsx +64 -1
- package/src/fields/ToggleGroup/ToggleGroup.stories.tsx +19 -0
- package/src/fields/ToggleGroup/index.tsx +31 -29
- package/src/shared/FloatingListScrollArrows.stories.tsx +4 -2
- package/src/shared/TimeColumn.tsx +1 -1
- package/src/shared/focusManagement.ts +12 -1
- package/src/shared/t.spec.ts +39 -0
- package/src/shared/t.ts +20 -0
- package/types/Button/index.d.ts +9 -1
- package/types/Button/index.d.ts.map +1 -1
- package/types/Skeleton/index.d.ts.map +1 -1
- package/types/fields/Checkbox/Checkbox.d.ts.map +1 -1
- package/types/fields/Checkbox/index.d.ts.map +1 -1
- package/types/fields/Combobox/index.d.ts.map +1 -1
- package/types/fields/DatePicker/DateRangePicker.d.ts.map +1 -1
- package/types/fields/DatePicker/MonthPicker.d.ts.map +1 -1
- package/types/fields/DatePicker/MultiDatePicker.d.ts.map +1 -1
- package/types/fields/DatePicker/fieldValueSync.d.ts +14 -0
- package/types/fields/DatePicker/fieldValueSync.d.ts.map +1 -1
- package/types/fields/Rating/index.d.ts.map +1 -1
- package/types/fields/SearchInput/index.d.ts.map +1 -1
- package/types/fields/Select/index.d.ts.map +1 -1
- package/types/fields/Slider/index.d.ts.map +1 -1
- package/types/fields/TextArea/index.d.ts.map +1 -1
- package/types/fields/ToggleGroup/index.d.ts.map +1 -1
- package/types/shared/focusManagement.d.ts.map +1 -1
- package/types/shared/t.d.ts +16 -0
- package/types/shared/t.d.ts.map +1 -1
|
@@ -32,6 +32,7 @@ import {
|
|
|
32
32
|
useDateNavigation,
|
|
33
33
|
yearPageSize,
|
|
34
34
|
} from "./parts";
|
|
35
|
+
import { FieldValueSync } from "./fieldValueSync";
|
|
35
36
|
|
|
36
37
|
import type { ViewMode } from "./parts";
|
|
37
38
|
|
|
@@ -414,19 +415,25 @@ export function MultiDatePicker<
|
|
|
414
415
|
setOpen(true);
|
|
415
416
|
}, [setDisplayDate]);
|
|
416
417
|
|
|
417
|
-
//
|
|
418
|
-
//
|
|
419
|
-
//
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
const next = Array.isArray(
|
|
418
|
+
// Adopt an externally-applied value (controlled `value` prop or the live
|
|
419
|
+
// TanStack field value) into the display state without emitting onChange
|
|
420
|
+
// (LC-14). Skip setState when the calendar days are unchanged so a new
|
|
421
|
+
// array identity from the parent cannot force a render storm.
|
|
422
|
+
const adoptExternalValue = useCallback((raw: unknown) => {
|
|
423
|
+
const next = Array.isArray(raw) ? (raw as Date[]) : [];
|
|
423
424
|
const prev = selectedDatesRef.current;
|
|
424
425
|
const same =
|
|
425
426
|
prev.length === next.length && prev.every((d, i) => next[i] != null && isSameDay(d, next[i]));
|
|
426
427
|
if (same) return;
|
|
427
428
|
selectedDatesRef.current = next;
|
|
428
429
|
setSelectedDates(next);
|
|
429
|
-
}, [
|
|
430
|
+
}, []);
|
|
431
|
+
|
|
432
|
+
// Keep standalone/controlled state in sync when the parent value changes.
|
|
433
|
+
useEffect(() => {
|
|
434
|
+
if (valueProp === undefined) return;
|
|
435
|
+
adoptExternalValue(valueProp);
|
|
436
|
+
}, [valueProp, adoptExternalValue]);
|
|
430
437
|
|
|
431
438
|
const handleToggleDate = useCallback(
|
|
432
439
|
(date: Date, fieldChange?: (value: any) => void) => {
|
|
@@ -560,26 +567,31 @@ export function MultiDatePicker<
|
|
|
560
567
|
const resolvedError = getFieldError(field, errorProp);
|
|
561
568
|
|
|
562
569
|
return (
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
570
|
+
<>
|
|
571
|
+
{/* Form branch displays from `selectedDates` — keep it synced to
|
|
572
|
+
the live field value (late seeds / programmatic updates). */}
|
|
573
|
+
<FieldValueSync value={field.state.value} onSync={adoptExternalValue} />
|
|
574
|
+
<FieldLayout
|
|
575
|
+
id={id}
|
|
576
|
+
label={label}
|
|
577
|
+
labelProps={labelProps}
|
|
578
|
+
error={resolvedError}
|
|
579
|
+
helperText={helperText}
|
|
580
|
+
required={required}
|
|
581
|
+
size={size}
|
|
582
|
+
knobProps={knobProps}
|
|
583
|
+
// LC-40: see standalone branch — FieldLayout carries dimWhole.
|
|
584
|
+
disabled={disabled}
|
|
585
|
+
>
|
|
586
|
+
<InputParts size={size || knobProps.sizeToken}>
|
|
587
|
+
{renderPopover(
|
|
588
|
+
!!resolvedError,
|
|
589
|
+
mergeFieldHandler(field, "handleBlur", _onBlur),
|
|
590
|
+
mergeFieldHandler(field, "handleChange"),
|
|
591
|
+
)}
|
|
592
|
+
</InputParts>
|
|
593
|
+
</FieldLayout>
|
|
594
|
+
</>
|
|
583
595
|
);
|
|
584
596
|
}}
|
|
585
597
|
</Field>
|
|
@@ -32,3 +32,26 @@ export function DatePickerFieldValueSync({
|
|
|
32
32
|
}, [value, onSync]);
|
|
33
33
|
return null;
|
|
34
34
|
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Shape-agnostic variant for pickers whose display state is not a single
|
|
38
|
+
* Date (MonthPicker's {month,year}, MultiDatePicker's Date[],
|
|
39
|
+
* DateRangePicker's {start,end}): mirrors the live TanStack field value into
|
|
40
|
+
* the picker's adoption callback whenever it changes (including mount, so a
|
|
41
|
+
* Field-level defaultValue displays too). Each picker's `onSync` owns its
|
|
42
|
+
* coercion + identity-preserving equality guard, and must never emit
|
|
43
|
+
* onChange from this path (LC-14 — a programmatic value application is not
|
|
44
|
+
* a user edit).
|
|
45
|
+
*/
|
|
46
|
+
export function FieldValueSync({
|
|
47
|
+
value,
|
|
48
|
+
onSync,
|
|
49
|
+
}: {
|
|
50
|
+
value: unknown;
|
|
51
|
+
onSync: (value: unknown) => void;
|
|
52
|
+
}) {
|
|
53
|
+
useEffect(() => {
|
|
54
|
+
onSync(value);
|
|
55
|
+
}, [value, onSync]);
|
|
56
|
+
return null;
|
|
57
|
+
}
|
|
@@ -107,6 +107,15 @@ export const Basic: Story = {
|
|
|
107
107
|
};
|
|
108
108
|
|
|
109
109
|
export const Disabled: Story = {
|
|
110
|
+
parameters: {
|
|
111
|
+
// LC-57 allowlist: disabled-dial demo; FileUpload's internal upload Button
|
|
112
|
+
// has no disabledReason plumbing yet (component-contract follow-up).
|
|
113
|
+
guardrailSpecimen: {
|
|
114
|
+
warns: ["bare-disabled"],
|
|
115
|
+
reason:
|
|
116
|
+
"Disabled-dial specimen: FileUpload's internal upload Button has no disabledReason plumbing yet.",
|
|
117
|
+
},
|
|
118
|
+
},
|
|
110
119
|
render: (args) => <FileUpload {...args} />,
|
|
111
120
|
args: {
|
|
112
121
|
label: "Disabled Upload",
|
|
@@ -119,7 +128,8 @@ export const WithError: Story = {
|
|
|
119
128
|
render: (args) => <FileUpload {...args} />,
|
|
120
129
|
args: {
|
|
121
130
|
label: "Required File",
|
|
122
|
-
|
|
131
|
+
// DG-COPY-02: error copy states the rule broken — never "please"/"invalid"/"error".
|
|
132
|
+
error: "A file is required",
|
|
123
133
|
required: true,
|
|
124
134
|
},
|
|
125
135
|
};
|
|
@@ -95,6 +95,15 @@ export const Basic: Story = {
|
|
|
95
95
|
};
|
|
96
96
|
|
|
97
97
|
export const Disabled: Story = {
|
|
98
|
+
parameters: {
|
|
99
|
+
// LC-57 allowlist: disabled-dial demo; Geolocation's internal action
|
|
100
|
+
// Buttons have no disabledReason plumbing yet (component-contract follow-up).
|
|
101
|
+
guardrailSpecimen: {
|
|
102
|
+
warns: ["bare-disabled"],
|
|
103
|
+
reason:
|
|
104
|
+
"Disabled-dial specimen: Geolocation's internal action Buttons have no disabledReason plumbing yet.",
|
|
105
|
+
},
|
|
106
|
+
},
|
|
98
107
|
args: {
|
|
99
108
|
label: "Location",
|
|
100
109
|
helperText: "This field is disabled",
|
|
@@ -36,10 +36,10 @@ function MapFallback({ lat, lng, minHeight }: { lat: number; lng: number; minHei
|
|
|
36
36
|
backgroundColor="$backgroundHover"
|
|
37
37
|
gap="$1"
|
|
38
38
|
>
|
|
39
|
-
<Text color="$
|
|
39
|
+
<Text color="$color11" fontSize="$2">
|
|
40
40
|
{t("Map preview unavailable — requires expo-maps")}
|
|
41
41
|
</Text>
|
|
42
|
-
<Text color="$
|
|
42
|
+
<Text color="$color11" fontSize="$1">
|
|
43
43
|
{`${lat.toFixed(4)}, ${lng.toFixed(4)}`}
|
|
44
44
|
</Text>
|
|
45
45
|
</YStack>
|
|
@@ -135,7 +135,8 @@ export const ReadOnly: Story = {
|
|
|
135
135
|
export const WithError: Story = {
|
|
136
136
|
args: {
|
|
137
137
|
label: "Required Image",
|
|
138
|
-
|
|
138
|
+
// DG-COPY-02: error copy states the rule broken — never "please"/"invalid"/"error".
|
|
139
|
+
error: "An image is required",
|
|
139
140
|
required: true,
|
|
140
141
|
imageWidth: 64,
|
|
141
142
|
imageHeight: 64,
|
|
@@ -115,7 +115,26 @@ export const SkeletonState: Story = {
|
|
|
115
115
|
args: { label: "Full name", skeleton: true },
|
|
116
116
|
};
|
|
117
117
|
|
|
118
|
+
/**
|
|
119
|
+
* FOREIGN CONTRAST SPECIMEN (LC-56 STORY-HONESTY): the right column renders the
|
|
120
|
+
* RAW tamagui Input beside the house Input strictly so the difference stays
|
|
121
|
+
* visible. Raw primitives here are by design and labeled — knob/contrast probes
|
|
122
|
+
* skip this story via `parameters.foreignContrastSpecimen`.
|
|
123
|
+
*/
|
|
118
124
|
export const Comparison: StoryObj = {
|
|
125
|
+
name: "Foreign contrast specimen (raw tamagui)",
|
|
126
|
+
parameters: {
|
|
127
|
+
// Machine-readable exemption for knob/contrast probes and the LC-56 linter arm.
|
|
128
|
+
foreignContrastSpecimen: true,
|
|
129
|
+
docs: {
|
|
130
|
+
description: {
|
|
131
|
+
story:
|
|
132
|
+
"FOREIGN CONTRAST SPECIMEN (LC-56 STORY-HONESTY). The right column is the raw " +
|
|
133
|
+
"tamagui Input, rendered for contrast only: it ignores house knobs and field " +
|
|
134
|
+
"recipes by design. Probes asserting house rendering must skip this story.",
|
|
135
|
+
},
|
|
136
|
+
},
|
|
137
|
+
},
|
|
119
138
|
render: () => {
|
|
120
139
|
const ComparisonExample = () => {
|
|
121
140
|
const [customVal, setCustomVal] = useState("");
|
|
@@ -169,7 +169,26 @@ export const Interactive: Story = {
|
|
|
169
169
|
},
|
|
170
170
|
};
|
|
171
171
|
|
|
172
|
+
/**
|
|
173
|
+
* FOREIGN CONTRAST SPECIMEN (LC-56 STORY-HONESTY): the right column renders the
|
|
174
|
+
* RAW tamagui Progress beside the house Progress strictly so the difference
|
|
175
|
+
* stays visible. Raw primitives here are by design and labeled — knob/contrast
|
|
176
|
+
* probes skip this story via `parameters.foreignContrastSpecimen`.
|
|
177
|
+
*/
|
|
172
178
|
export const Comparison: Story = {
|
|
179
|
+
name: "Foreign contrast specimen (raw tamagui)",
|
|
180
|
+
parameters: {
|
|
181
|
+
// Machine-readable exemption for knob/contrast probes and the LC-56 linter arm.
|
|
182
|
+
foreignContrastSpecimen: true,
|
|
183
|
+
docs: {
|
|
184
|
+
description: {
|
|
185
|
+
story:
|
|
186
|
+
"FOREIGN CONTRAST SPECIMEN (LC-56 STORY-HONESTY). The right column is the raw " +
|
|
187
|
+
"tamagui Progress, rendered for contrast only: it ignores house knobs and " +
|
|
188
|
+
"recipes by design. Probes asserting house rendering must skip this story.",
|
|
189
|
+
},
|
|
190
|
+
},
|
|
191
|
+
},
|
|
173
192
|
render: () => (
|
|
174
193
|
<XStack gap="$6" alignItems="flex-start" padding="$4">
|
|
175
194
|
<YStack gap="$3" width={300}>
|
|
@@ -1,8 +1,16 @@
|
|
|
1
1
|
import { useForm } from "@tanstack/react-form";
|
|
2
|
-
import { act, fireEvent, waitFor } from "@testing-library/react";
|
|
3
|
-
import { YStack } from "tamagui";
|
|
2
|
+
import { act, fireEvent, render, waitFor } from "@testing-library/react";
|
|
3
|
+
import { TamaguiProvider, YStack, createTamagui } from "tamagui";
|
|
4
4
|
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
5
5
|
import { renderWithProviders } from "@multiplatform.one/test-utils";
|
|
6
|
+
import {
|
|
7
|
+
createThemesBuilder,
|
|
8
|
+
defaultAccentTheme,
|
|
9
|
+
defaultBaseTheme,
|
|
10
|
+
defaultBuilderOptions,
|
|
11
|
+
} from "@multiplatform.one/theme";
|
|
12
|
+
import { configWithoutAnimations } from "@tamagui/config";
|
|
13
|
+
import { animationsCSS } from "@tamagui/config/v5-css";
|
|
6
14
|
import { Button } from "../../Button";
|
|
7
15
|
import { Form } from "../../Form";
|
|
8
16
|
import { RadioGroup } from "./index";
|
|
@@ -13,6 +21,21 @@ const mockOptions = [
|
|
|
13
21
|
{ label: "Option 3", value: "option3" },
|
|
14
22
|
];
|
|
15
23
|
|
|
24
|
+
// House builder themes for LC-05 mark assertions — created at MODULE scope:
|
|
25
|
+
// tamagui registers a config's theme variables globally at createTamagui
|
|
26
|
+
// time, and a config created after the first render (inside a test body)
|
|
27
|
+
// never resolves its tokens (Progress.spec pattern).
|
|
28
|
+
const houseThemes = createThemesBuilder(
|
|
29
|
+
defaultBaseTheme,
|
|
30
|
+
defaultAccentTheme,
|
|
31
|
+
defaultBuilderOptions,
|
|
32
|
+
).themes() as Record<string, Record<string, string>>;
|
|
33
|
+
const houseConfig = createTamagui({
|
|
34
|
+
...configWithoutAnimations,
|
|
35
|
+
animations: animationsCSS,
|
|
36
|
+
themes: houseThemes as any,
|
|
37
|
+
});
|
|
38
|
+
|
|
16
39
|
let consoleErrorSpy: ReturnType<typeof vi.spyOn> | null = null;
|
|
17
40
|
beforeEach(() => {
|
|
18
41
|
consoleErrorSpy = vi.spyOn(console, "error").mockImplementation((...args: unknown[]) => {
|
|
@@ -266,6 +289,95 @@ describe("RadioGroup Components", () => {
|
|
|
266
289
|
});
|
|
267
290
|
});
|
|
268
291
|
|
|
292
|
+
describe("late-arriving value display (create-mode seeding)", () => {
|
|
293
|
+
// The non-card variant fed the current value into tamagui as mount-only
|
|
294
|
+
// `defaultValue`, so a value applied AFTER first paint (create-mode
|
|
295
|
+
// engine seeding lands once meta loads) never displayed. The group must
|
|
296
|
+
// render controlled — and adopting a programmatic value must not emit
|
|
297
|
+
// onChange (LC-14: a seeded value is not a user edit).
|
|
298
|
+
const getRadioByValue = (container: ParentNode, value: string) =>
|
|
299
|
+
[...container.querySelectorAll("[role='radio']")].find((radio) =>
|
|
300
|
+
radio.id.endsWith(`-${value}`),
|
|
301
|
+
);
|
|
302
|
+
|
|
303
|
+
it("standalone controlled: a value arriving after mount renders checked, without onChange", async () => {
|
|
304
|
+
const onChange = vi.fn();
|
|
305
|
+
const onValueChange = vi.fn();
|
|
306
|
+
const result = renderWithProviders(
|
|
307
|
+
<RadioGroup
|
|
308
|
+
label="Status"
|
|
309
|
+
value=""
|
|
310
|
+
options={mockOptions}
|
|
311
|
+
onChange={onChange}
|
|
312
|
+
onValueChange={onValueChange}
|
|
313
|
+
/>,
|
|
314
|
+
);
|
|
315
|
+
for (const radio of result.container.querySelectorAll("[role='radio']")) {
|
|
316
|
+
expect(radio.getAttribute("aria-checked")).not.toBe("true");
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
// Late seed: the upstream doc value arrives after first paint.
|
|
320
|
+
result.rerender(
|
|
321
|
+
<RadioGroup
|
|
322
|
+
label="Status"
|
|
323
|
+
value="option2"
|
|
324
|
+
options={mockOptions}
|
|
325
|
+
onChange={onChange}
|
|
326
|
+
onValueChange={onValueChange}
|
|
327
|
+
/>,
|
|
328
|
+
);
|
|
329
|
+
await waitFor(() => {
|
|
330
|
+
expect(getRadioByValue(result.container, "option2")?.getAttribute("aria-checked")).toBe(
|
|
331
|
+
"true",
|
|
332
|
+
);
|
|
333
|
+
});
|
|
334
|
+
expect(onChange).not.toHaveBeenCalled();
|
|
335
|
+
expect(onValueChange).not.toHaveBeenCalled();
|
|
336
|
+
});
|
|
337
|
+
|
|
338
|
+
it("form-integrated: a field value set after mount renders checked, without onChange", async () => {
|
|
339
|
+
const onChange = vi.fn();
|
|
340
|
+
let formApi: { setFieldValue: (name: "option", value: string) => void } | undefined;
|
|
341
|
+
const TestComponent = () => {
|
|
342
|
+
const form = useForm({ defaultValues: { option: "" } });
|
|
343
|
+
formApi = form as unknown as typeof formApi;
|
|
344
|
+
return (
|
|
345
|
+
<Form form={form}>
|
|
346
|
+
<RadioGroup label="Status" name="option" options={mockOptions} onChange={onChange} />
|
|
347
|
+
</Form>
|
|
348
|
+
);
|
|
349
|
+
};
|
|
350
|
+
const result = renderWithProviders(<TestComponent />);
|
|
351
|
+
|
|
352
|
+
await act(async () => {
|
|
353
|
+
formApi?.setFieldValue("option", "option3");
|
|
354
|
+
});
|
|
355
|
+
await waitFor(() => {
|
|
356
|
+
expect(getRadioByValue(result.container, "option3")?.getAttribute("aria-checked")).toBe(
|
|
357
|
+
"true",
|
|
358
|
+
);
|
|
359
|
+
});
|
|
360
|
+
expect(onChange).not.toHaveBeenCalled();
|
|
361
|
+
});
|
|
362
|
+
|
|
363
|
+
it("still emits exactly once for a real click after a late seed", async () => {
|
|
364
|
+
const onChange = vi.fn();
|
|
365
|
+
const result = renderWithProviders(
|
|
366
|
+
<RadioGroup label="Status" value="" options={mockOptions} onChange={onChange} />,
|
|
367
|
+
);
|
|
368
|
+
result.rerender(
|
|
369
|
+
<RadioGroup label="Status" value="option1" options={mockOptions} onChange={onChange} />,
|
|
370
|
+
);
|
|
371
|
+
const radio = getRadioByValue(result.container, "option2");
|
|
372
|
+
expect(radio).toBeTruthy();
|
|
373
|
+
await act(async () => {
|
|
374
|
+
if (radio) fireEvent.click(radio);
|
|
375
|
+
});
|
|
376
|
+
expect(onChange).toHaveBeenCalledTimes(1);
|
|
377
|
+
expect(onChange).toHaveBeenCalledWith("option2");
|
|
378
|
+
});
|
|
379
|
+
});
|
|
380
|
+
|
|
269
381
|
describe("card mode (LC-22 SELECTION-CARD)", () => {
|
|
270
382
|
const cardOptions = [
|
|
271
383
|
{ label: "Card A", value: "a", description: "Description for A" },
|
|
@@ -326,5 +438,30 @@ describe("RadioGroup Components", () => {
|
|
|
326
438
|
expect(new Set(ids).size).toBe(2);
|
|
327
439
|
});
|
|
328
440
|
});
|
|
441
|
+
|
|
442
|
+
describe("selected mark (LC-05 ONE-EMPHASIS / D-01)", () => {
|
|
443
|
+
it("paints the checked radio's indicator dot with the accent mark, never ink", () => {
|
|
444
|
+
const { container } = render(
|
|
445
|
+
<TamaguiProvider config={houseConfig} defaultTheme="light" disableInjectCSS>
|
|
446
|
+
<RadioGroup label="Choose" value="option2" options={mockOptions} />
|
|
447
|
+
</TamaguiProvider>,
|
|
448
|
+
);
|
|
449
|
+
const checked = container.querySelector('[role="radio"][aria-checked="true"]');
|
|
450
|
+
expect(checked).toBeTruthy();
|
|
451
|
+
// The indicator dot is the selection mark: it fills with the shared
|
|
452
|
+
// accent mark token ($accentBackground), not the ink/label tier.
|
|
453
|
+
const marked = Array.from(checked!.querySelectorAll("*")).filter((el) =>
|
|
454
|
+
(el as HTMLElement).className?.toString().includes("_bg-accentBackg"),
|
|
455
|
+
);
|
|
456
|
+
expect(marked.length).toBeGreaterThan(0);
|
|
457
|
+
// Unchecked radios render no accent mark.
|
|
458
|
+
const unchecked = container.querySelector('[role="radio"][aria-checked="false"]');
|
|
459
|
+
expect(unchecked).toBeTruthy();
|
|
460
|
+
const uncheckedMarked = Array.from(unchecked!.querySelectorAll("*")).filter((el) =>
|
|
461
|
+
(el as HTMLElement).className?.toString().includes("_bg-accentBackg"),
|
|
462
|
+
);
|
|
463
|
+
expect(uncheckedMarked.length).toBe(0);
|
|
464
|
+
});
|
|
465
|
+
});
|
|
329
466
|
});
|
|
330
467
|
});
|
|
@@ -280,7 +280,7 @@ export function RadioGroup({
|
|
|
280
280
|
borderWidth={radioRingWidth(knobProps.borderRadius.borderWidth)}
|
|
281
281
|
borderRadius={itemRadius}
|
|
282
282
|
outlined={knobProps.outlined}
|
|
283
|
-
{...(knobProps.transition ? {
|
|
283
|
+
{...(knobProps.transition ? { transition: knobProps.transition } : undefined)}
|
|
284
284
|
>
|
|
285
285
|
<RadioIndicatorFrame
|
|
286
286
|
borderRadius={indicatorRadius}
|
|
@@ -288,7 +288,7 @@ export function RadioGroup({
|
|
|
288
288
|
// accent — `textAccentColor` is the LABEL ink tier ($color/$color11)
|
|
289
289
|
// and made the checked radio read as ink, not as chosen (D-01).
|
|
290
290
|
backgroundColor={formSelectedColors.mark}
|
|
291
|
-
{...(knobProps.transition ? {
|
|
291
|
+
{...(knobProps.transition ? { transition: knobProps.transition } : undefined)}
|
|
292
292
|
/>
|
|
293
293
|
</RadioGlyph>
|
|
294
294
|
);
|
|
@@ -1,6 +1,15 @@
|
|
|
1
1
|
import { describe, expect, it, vi } from "vitest";
|
|
2
|
-
import { fireEvent } from "@testing-library/react";
|
|
2
|
+
import { fireEvent, render } from "@testing-library/react";
|
|
3
3
|
import { renderWithProviders } from "@multiplatform.one/test-utils";
|
|
4
|
+
import {
|
|
5
|
+
createThemesBuilder,
|
|
6
|
+
defaultAccentTheme,
|
|
7
|
+
defaultBaseTheme,
|
|
8
|
+
defaultBuilderOptions,
|
|
9
|
+
} from "@multiplatform.one/theme";
|
|
10
|
+
import { configWithoutAnimations } from "@tamagui/config";
|
|
11
|
+
import { animationsCSS } from "@tamagui/config/v5-css";
|
|
12
|
+
import { TamaguiProvider, createTamagui } from "tamagui";
|
|
4
13
|
import { Form } from "../../Form";
|
|
5
14
|
import { Rating } from "./index";
|
|
6
15
|
|
|
@@ -8,6 +17,21 @@ const MockStarIcon = ({ size, weight, color }: { size: number; weight: string; c
|
|
|
8
17
|
<span data-testid="star" data-size={size} data-weight={weight} data-color={color} />
|
|
9
18
|
);
|
|
10
19
|
|
|
20
|
+
// House builder themes for the LC-05 mark assertion — created at MODULE
|
|
21
|
+
// scope: tamagui registers a config's theme variables globally at
|
|
22
|
+
// createTamagui time, and a config created after the first render (inside a
|
|
23
|
+
// test body) never resolves its tokens (Progress.spec pattern).
|
|
24
|
+
const houseThemes = createThemesBuilder(
|
|
25
|
+
defaultBaseTheme,
|
|
26
|
+
defaultAccentTheme,
|
|
27
|
+
defaultBuilderOptions,
|
|
28
|
+
).themes() as Record<string, Record<string, string>>;
|
|
29
|
+
const houseConfig = createTamagui({
|
|
30
|
+
...configWithoutAnimations,
|
|
31
|
+
animations: animationsCSS,
|
|
32
|
+
themes: houseThemes as any,
|
|
33
|
+
});
|
|
34
|
+
|
|
11
35
|
describe("Rating", () => {
|
|
12
36
|
describe("without form context", () => {
|
|
13
37
|
it("renders with label", () => {
|
|
@@ -97,6 +121,24 @@ describe("Rating", () => {
|
|
|
97
121
|
expect(star.getAttribute("aria-disabled")).toBe("true");
|
|
98
122
|
}
|
|
99
123
|
});
|
|
124
|
+
|
|
125
|
+
// LC-05 ONE-EMPHASIS / D-01: the FILLED star is the selection mark, so it
|
|
126
|
+
// resolves the shared accent mark token; the empty star stays neutral
|
|
127
|
+
// chrome. Stock test themes carry no $accentBackground, so mount the
|
|
128
|
+
// house builder themes for real resolution.
|
|
129
|
+
it("fills selected stars with the accent mark and empty stars with neutral chrome", () => {
|
|
130
|
+
const { container } = render(
|
|
131
|
+
<TamaguiProvider config={houseConfig} defaultTheme="light" disableInjectCSS>
|
|
132
|
+
<Rating label="Rate" value={0.6} starIcon={MockStarIcon} />
|
|
133
|
+
</TamaguiProvider>,
|
|
134
|
+
);
|
|
135
|
+
const filled = container.querySelector("[data-rating-star='filled'] [data-testid='star']");
|
|
136
|
+
const empty = container.querySelector("[data-rating-star='empty'] [data-testid='star']");
|
|
137
|
+
expect(filled).toBeTruthy();
|
|
138
|
+
expect(empty).toBeTruthy();
|
|
139
|
+
expect(filled!.getAttribute("data-color")).toContain("accentBackground");
|
|
140
|
+
expect(empty!.getAttribute("data-color")).not.toContain("accentBackground");
|
|
141
|
+
});
|
|
100
142
|
});
|
|
101
143
|
|
|
102
144
|
describe("with form context", () => {
|
|
@@ -49,7 +49,10 @@ function PointyStar({
|
|
|
49
49
|
<Svg width={size} height={size} viewBox={`0 0 ${size} ${size}`}>
|
|
50
50
|
<Path
|
|
51
51
|
d={d}
|
|
52
|
-
|
|
52
|
+
// `transparent` rather than `none`: an empty->filled star has to
|
|
53
|
+
// interpolate its fill (LC-25), and `none` is not a colour so it can
|
|
54
|
+
// only jump. Paints identically.
|
|
55
|
+
fill={weight === "fill" ? color : "transparent"}
|
|
53
56
|
stroke={color}
|
|
54
57
|
strokeWidth={1.5}
|
|
55
58
|
strokeLinejoin="miter"
|
|
@@ -80,6 +83,32 @@ function getTransitionStyle(transition: TransitionProp | undefined, live: boolea
|
|
|
80
83
|
};
|
|
81
84
|
}
|
|
82
85
|
|
|
86
|
+
// LC-25, star ink: the visible change is the SVG `fill`/`stroke` of the glyph
|
|
87
|
+
// CHILD, and `transition` does not inherit — a declaration on the wrapper can
|
|
88
|
+
// never reach it, while the swappable `starIcon` contract ({ size, weight,
|
|
89
|
+
// color }) has no style passthrough to declare it on. A one-shot descendant
|
|
90
|
+
// stylesheet (Spinner/Wheel pattern) arms the tween on whatever glyph the
|
|
91
|
+
// contract renders, so the contract itself stays untouched.
|
|
92
|
+
const INK_STYLE_ID = "mp1-rating-ink";
|
|
93
|
+
const inkClassName = (token: string) => `mp1-rating-ink-${token}`;
|
|
94
|
+
function ensureRatingInkStyles() {
|
|
95
|
+
if (!isWeb || typeof document === "undefined") return;
|
|
96
|
+
if (document.getElementById(INK_STYLE_ID)) return;
|
|
97
|
+
const style = document.createElement("style");
|
|
98
|
+
style.id = INK_STYLE_ID;
|
|
99
|
+
// Shape elements only, never the <svg> root: a Phosphor-shaped glyph paints
|
|
100
|
+
// the root's `fill` and the shape INHERITS it, so arming both would leave
|
|
101
|
+
// the shape chasing an already-animating value and stretch the tween well
|
|
102
|
+
// past the token.
|
|
103
|
+
style.textContent = Object.entries(cssTimings)
|
|
104
|
+
.map(
|
|
105
|
+
([token, timing]) =>
|
|
106
|
+
`.${inkClassName(token)} svg * { transition: fill ${timing}, stroke ${timing}; }`,
|
|
107
|
+
)
|
|
108
|
+
.join("\n");
|
|
109
|
+
document.head.appendChild(style);
|
|
110
|
+
}
|
|
111
|
+
|
|
83
112
|
const StarWrapper = styled(View, {
|
|
84
113
|
variants: {
|
|
85
114
|
filled: {
|
|
@@ -124,10 +153,19 @@ function StarRow({
|
|
|
124
153
|
const [scrubFilled, setScrubFilled] = useState<number | null>(null);
|
|
125
154
|
const scrubbing = scrubFilled != null;
|
|
126
155
|
const displayFilled = scrubFilled ?? filledStars;
|
|
127
|
-
// LC-25: discrete star jumps tween via knob transition
|
|
128
|
-
//
|
|
129
|
-
|
|
156
|
+
// LC-25: discrete star jumps tween via knob transition. During pointer
|
|
157
|
+
// scrub, omit transition so fill tracks the pointer 1:1. `transition` is
|
|
158
|
+
// undefined exactly at animation="none" (which prefers-reduced-motion maps
|
|
159
|
+
// onto), and Axiom 3 says that position is NO motion — not a default one.
|
|
160
|
+
const motionTransition = knobProps.transition as TransitionProp | undefined;
|
|
130
161
|
const live = scrubbing;
|
|
162
|
+
ensureRatingInkStyles();
|
|
163
|
+
const inkToken =
|
|
164
|
+
isWeb && !live && motionTransition
|
|
165
|
+
? (motionTransition as string) in cssTimings
|
|
166
|
+
? (motionTransition as string)
|
|
167
|
+
: "quick"
|
|
168
|
+
: undefined;
|
|
131
169
|
|
|
132
170
|
// DG-A11Y keyboard operability: one roving tab stop (ToggleGroup pattern —
|
|
133
171
|
// a group of N buttons is one Tab entry, arrows move within). The stop
|
|
@@ -227,6 +265,7 @@ function StarRow({
|
|
|
227
265
|
data-rating-star={isFilled ? "filled" : "empty"}
|
|
228
266
|
data-rating-index={starNumber}
|
|
229
267
|
{...(!isWeb && !live ? { transition: motionTransition } : {})}
|
|
268
|
+
{...(inkToken ? { className: inkClassName(inkToken) } : undefined)}
|
|
230
269
|
style={getTransitionStyle(live ? undefined : motionTransition, live)}
|
|
231
270
|
>
|
|
232
271
|
<StarIcon
|
|
@@ -21,6 +21,17 @@ describe("SearchInput", () => {
|
|
|
21
21
|
expect(result.container.querySelector("svg")).toBeTruthy();
|
|
22
22
|
});
|
|
23
23
|
|
|
24
|
+
it("bidi-isolates the default placeholder (LC-58) and passes explicit ones through", () => {
|
|
25
|
+
// Default: FSI…PDI framing so the trailing "..." cannot reorder to
|
|
26
|
+
// "…Search" under RTL; the visible run stays byte-identical inside.
|
|
27
|
+
const result = renderWithProviders(<SearchInput />);
|
|
28
|
+
const input = result.container.querySelector("input");
|
|
29
|
+
expect(input?.getAttribute("placeholder")).toBe("\u2068Search...\u2069");
|
|
30
|
+
// Explicit placeholders are the consumer's interpolation site — the
|
|
31
|
+
// component must not double-wrap or mutate them (asserted above too).
|
|
32
|
+
expect(input?.getAttribute("placeholder")?.slice(1, -1)).toBe("Search...");
|
|
33
|
+
});
|
|
34
|
+
|
|
24
35
|
it("fires onChange per keystroke and onSearch after debounce", () => {
|
|
25
36
|
const onChange = vi.fn();
|
|
26
37
|
const onSearch = vi.fn();
|
|
@@ -12,7 +12,7 @@ import type { ReactNode } from "react";
|
|
|
12
12
|
import { useCallback, useEffect, useRef, useState } from "react";
|
|
13
13
|
import type { SizeTokens } from "tamagui";
|
|
14
14
|
import { View } from "tamagui";
|
|
15
|
-
import { t } from "../../shared/t";
|
|
15
|
+
import { bidiIsolate, t } from "../../shared/t";
|
|
16
16
|
import { Input } from "../Input";
|
|
17
17
|
import type { InputProps } from "../Input";
|
|
18
18
|
|
|
@@ -60,7 +60,11 @@ export function SearchInput({
|
|
|
60
60
|
onSearch,
|
|
61
61
|
onClear,
|
|
62
62
|
debounceMs = 300,
|
|
63
|
-
|
|
63
|
+
// Bidi-isolated at the interpolation site (LC-58): under RTL the trailing
|
|
64
|
+
// weak "..." otherwise reorders to the start ("…Search"). First-strong
|
|
65
|
+
// isolation keeps a translated RTL placeholder rendering RTL. Consumer
|
|
66
|
+
// placeholders are the consumer's interpolation site — passed through.
|
|
67
|
+
placeholder = bidiIsolate(t("Search...")),
|
|
64
68
|
clearable = true,
|
|
65
69
|
disabled,
|
|
66
70
|
size,
|
|
@@ -303,7 +303,26 @@ export const DynamicOptions: StoryObj<typeof Select> = {
|
|
|
303
303
|
},
|
|
304
304
|
};
|
|
305
305
|
|
|
306
|
+
/**
|
|
307
|
+
* FOREIGN CONTRAST SPECIMEN (LC-56 STORY-HONESTY): the right column renders the
|
|
308
|
+
* RAW tamagui Select beside the house Select strictly so the difference stays
|
|
309
|
+
* visible. Raw primitives here are by design and labeled — knob/contrast probes
|
|
310
|
+
* skip this story via `parameters.foreignContrastSpecimen`.
|
|
311
|
+
*/
|
|
306
312
|
export const Comparison: StoryObj<typeof Select> = {
|
|
313
|
+
name: "Foreign contrast specimen (raw tamagui)",
|
|
314
|
+
parameters: {
|
|
315
|
+
// Machine-readable exemption for knob/contrast probes and the LC-56 linter arm.
|
|
316
|
+
foreignContrastSpecimen: true,
|
|
317
|
+
docs: {
|
|
318
|
+
description: {
|
|
319
|
+
story:
|
|
320
|
+
"FOREIGN CONTRAST SPECIMEN (LC-56 STORY-HONESTY). The right column is the raw " +
|
|
321
|
+
"tamagui Select, rendered for contrast only: it ignores house knobs and control " +
|
|
322
|
+
"recipes by design. Probes asserting house rendering must skip this story.",
|
|
323
|
+
},
|
|
324
|
+
},
|
|
325
|
+
},
|
|
307
326
|
render: () => {
|
|
308
327
|
const ComparisonExample = () => {
|
|
309
328
|
const [customValue, setCustomValue] = useState<string>();
|