@12-apps/ui 6.83.0 → 6.84.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/_internal/components/data-display/Avatar/Avatar.js +3 -3
- package/dist/_internal/components/data-display/Avatar/Avatar.view.js +2 -2
- package/dist/_internal/components/data-display/Carousel/Carousel.chrome.js +3 -3
- package/dist/_internal/components/data-display/Carousel/Carousel.js +5 -5
- package/dist/_internal/components/data-display/Carousel/Carousel.parts.js +2 -2
- package/dist/_internal/components/data-display/Chart/Chart.js +3 -3
- package/dist/_internal/components/data-display/Chart/SpecChart.js +6 -6
- package/dist/_internal/components/data-display/Chart/chart-renderers.js +2 -2
- package/dist/_internal/components/data-display/Chart/chart-spec.js +2 -2
- package/dist/_internal/components/data-display/DataGrid/DataGrid.js +2 -2
- package/dist/_internal/components/data-display/DataViews/DataViewsGrid.js +20 -19
- package/dist/_internal/components/data-display/DataViews/DataViewsTableBase.js +21 -20
- package/dist/_internal/components/data-display/DataViews/base-list-card.js +2 -2
- package/dist/_internal/components/data-display/DataViews/data-views-day-input.js +7 -3
- package/dist/_internal/components/data-display/DataViews/data-views-display-panel.js +2 -2
- package/dist/_internal/components/data-display/DataViews/data-views-empty.js +1 -1
- package/dist/_internal/components/data-display/DataViews/data-views-filter-panel.js +5 -4
- package/dist/_internal/components/data-display/DataViews/data-views-grid-bodies.js +3 -3
- package/dist/_internal/components/data-display/DataViews/data-views-grid-parts.js +17 -16
- package/dist/_internal/components/data-display/DataViews/data-views-inline-bar.js +7 -6
- package/dist/_internal/components/data-display/DataViews/data-views-more-fields.js +5 -4
- package/dist/_internal/components/data-display/DataViews/data-views-more-filters.js +6 -5
- package/dist/_internal/components/data-display/DataViews/data-views-range-pill.js +4 -3
- package/dist/_internal/components/data-display/DataViews/data-views-range-reset.js +9 -0
- package/dist/_internal/components/data-display/DataViews/data-views-range-reset.js.map +1 -0
- package/dist/_internal/components/data-display/DataViews/data-views-shell-toolbar.js +4 -4
- package/dist/_internal/components/data-display/DataViews/data-views-toolbar.js +3 -3
- package/dist/_internal/components/data-display/DataViews/data-views-writers.js +3 -1
- package/dist/_internal/components/data-display/DataViews/use-data-views-state.js +2 -2
- package/dist/_internal/components/data-display/FleetMap/FleetBody.js +8 -8
- package/dist/_internal/components/data-display/FleetMap/FleetCanvas.js +7 -7
- package/dist/_internal/components/data-display/FleetMap/FleetMap.fixtures.js +1 -1
- package/dist/_internal/components/data-display/FleetMap/FleetMap.js +9 -9
- package/dist/_internal/components/data-display/MapPreview/MapControlBar.js +3 -3
- package/dist/_internal/components/data-display/MapPreview/MapLayers.js +4 -4
- package/dist/_internal/components/data-display/MapPreview/MapMarkers.js +3 -3
- package/dist/_internal/components/data-display/MapPreview/MapPreview.helpers.js +2 -2
- package/dist/_internal/components/data-display/MapPreview/MapPreview.js +6 -6
- package/dist/_internal/components/data-display/index.js +29 -29
- package/dist/_internal/components/form/CepField/CepField.js +1 -1
- package/dist/_internal/components/form/DateRangePicker/DateRangePicker.hooks.js +3 -3
- package/dist/_internal/components/form/DateRangePicker/DateRangePicker.js +5 -5
- package/dist/_internal/components/form/DateRangePicker/DateRangePicker.range.js +2 -2
- package/dist/_internal/components/form/DateRangePicker/DateRangePickerFields.js +3 -3
- package/dist/_internal/components/form/InputOTP/InputOTP.hooks.js +1 -1
- package/dist/_internal/components/form/InputOTP/InputOTP.js +2 -2
- package/dist/_internal/components/form/index.js +8 -8
- package/dist/_internal/components/form/total-form/Fields/CepField.js +2 -2
- package/dist/_internal/components/form/total-form/Fields/TextField.js +1 -1
- package/dist/_internal/components/form/total-form/Fields/index.js +4 -4
- package/dist/_internal/components/index.js +37 -37
- package/dist/_internal/en-US.data-display.js +1 -1
- package/dist/_internal/pt-BR.data-display.js +1 -1
- package/dist/_internal/user-avatar/user-avatar.js +4 -4
- package/dist/charts.js +11 -11
- package/dist/{chunk-HF2WLLGH.js → chunk-2XI4R6UO.js} +2 -2
- package/dist/{chunk-HF2WLLGH.js.map → chunk-2XI4R6UO.js.map} +1 -1
- package/dist/{chunk-GYFKIPR2.js → chunk-4PUCT3RX.js} +2 -2
- package/dist/{chunk-JQSF3CUR.js → chunk-6HMDUUVJ.js} +2 -1
- package/dist/{chunk-JQSF3CUR.js.map → chunk-6HMDUUVJ.js.map} +1 -1
- package/dist/{chunk-EBEMNKTK.js → chunk-6ONQTXFM.js} +7 -4
- package/dist/chunk-6ONQTXFM.js.map +1 -0
- package/dist/{chunk-C2I7NQC4.js → chunk-74KCGLOU.js} +2 -2
- package/dist/{chunk-4J62V5OE.js → chunk-7YJYIMWP.js} +2 -2
- package/dist/{chunk-MT74NRLY.js → chunk-C2OX3C7L.js} +4 -4
- package/dist/{chunk-62XVHRTX.js → chunk-D7SOXI2K.js} +70 -21
- package/dist/chunk-D7SOXI2K.js.map +1 -0
- package/dist/{chunk-BO6IMK5P.js → chunk-DPHSJLMN.js} +2 -2
- package/dist/{chunk-7XSCEHGA.js → chunk-EOQ6EKTS.js} +7 -7
- package/dist/{chunk-4OTJ66ID.js → chunk-EXTES7LP.js} +15 -4
- package/dist/chunk-EXTES7LP.js.map +1 -0
- package/dist/{chunk-4IVEBBF6.js → chunk-FYJDI5BW.js} +4 -3
- package/dist/{chunk-4IVEBBF6.js.map → chunk-FYJDI5BW.js.map} +1 -1
- package/dist/{chunk-A5U7HEBI.js → chunk-GNDIVKWF.js} +4 -2
- package/dist/chunk-GNDIVKWF.js.map +1 -0
- package/dist/{chunk-XKPV52CG.js → chunk-GPTNZ57P.js} +2 -2
- package/dist/{chunk-ZGAOA7LN.js → chunk-H56CRMCM.js} +6 -6
- package/dist/{chunk-AMLHEKSV.js → chunk-HI7JPDD6.js} +5 -5
- package/dist/{chunk-JK3QB7MO.js → chunk-I54LHHZS.js} +2 -2
- package/dist/{chunk-GLAAX5JJ.js → chunk-I5NTADBO.js} +2 -1
- package/dist/{chunk-GLAAX5JJ.js.map → chunk-I5NTADBO.js.map} +1 -1
- package/dist/{chunk-LCGK22QQ.js → chunk-IKXCAYFH.js} +5 -5
- package/dist/{chunk-MLGAGZJ3.js → chunk-ILNAPS44.js} +8 -7
- package/dist/chunk-ILNAPS44.js.map +1 -0
- package/dist/{chunk-D22FGMGU.js → chunk-JIAIPOEV.js} +2 -2
- package/dist/{chunk-HQCZT6TO.js → chunk-JVOEVTCZ.js} +5 -5
- package/dist/{chunk-76IHYINM.js → chunk-KEBYZ2MO.js} +16 -9
- package/dist/chunk-KEBYZ2MO.js.map +1 -0
- package/dist/{chunk-JJNY5MZL.js → chunk-KG7OTJ26.js} +2 -2
- package/dist/{chunk-2ZMR3TZU.js → chunk-L6UTYEUS.js} +3 -3
- package/dist/{chunk-WWB43LWU.js → chunk-LOGSJH2Q.js} +2 -2
- package/dist/{chunk-YJPBNIEM.js → chunk-MGFTHOPG.js} +2 -2
- package/dist/{chunk-RB3R6BJL.js → chunk-O4G7QKDU.js} +2 -2
- package/dist/{chunk-DYHOBKPU.js → chunk-OA6T2ME3.js} +5 -5
- package/dist/{chunk-U5FOJBLG.js → chunk-OOLES6RR.js} +14 -2
- package/dist/chunk-OOLES6RR.js.map +1 -0
- package/dist/{chunk-YHKHHSZH.js → chunk-OQ554425.js} +3 -3
- package/dist/{chunk-54OJAOXC.js → chunk-PDSZN26U.js} +2 -2
- package/dist/{chunk-MNNIOQTQ.js → chunk-PGVGCDPD.js} +3 -3
- package/dist/{chunk-RDRXTH47.js → chunk-PWXDMP7G.js} +10 -6
- package/dist/chunk-PWXDMP7G.js.map +1 -0
- package/dist/{chunk-6ATXOJVQ.js → chunk-QADTI63U.js} +4 -4
- package/dist/{chunk-UETHX57J.js → chunk-R6L376KR.js} +21 -7
- package/dist/chunk-R6L376KR.js.map +1 -0
- package/dist/{chunk-SEUJNFOD.js → chunk-RLDTV5I2.js} +2 -2
- package/dist/{chunk-4UCXJYOO.js → chunk-RUQNKOSP.js} +4 -4
- package/dist/chunk-RUQNKOSP.js.map +1 -0
- package/dist/{chunk-7RG37FVT.js → chunk-RXKFPXVH.js} +29 -2
- package/dist/chunk-RXKFPXVH.js.map +1 -0
- package/dist/{chunk-KSJBFT4G.js → chunk-SGAHME6W.js} +3 -3
- package/dist/{chunk-SB2SEGR6.js → chunk-TIM4PXWN.js} +3 -3
- package/dist/{chunk-D4LMRSY7.js → chunk-UIWVSZ6I.js} +2 -2
- package/dist/{chunk-GJOU26UW.js → chunk-V2UJEICC.js} +3 -3
- package/dist/chunk-V2UJEICC.js.map +1 -0
- package/dist/{chunk-XATRYJ4Q.js → chunk-VQBY73QH.js} +4 -4
- package/dist/{chunk-SYERREN5.js → chunk-W2JYXZC2.js} +4 -4
- package/dist/{chunk-XNLJ2Z7T.js → chunk-YC2QYRNE.js} +5 -3
- package/dist/chunk-YC2QYRNE.js.map +1 -0
- package/dist/chunk-YOPMXGHY.js +23 -0
- package/dist/chunk-YOPMXGHY.js.map +1 -0
- package/dist/{chunk-QK3XXPBA.js → chunk-YWJCMGTE.js} +2 -2
- package/dist/{chunk-UQ6SAIX7.js → chunk-Z47SYKRX.js} +2 -2
- package/dist/data-display/Avatar.js +3 -3
- package/dist/data-display/CardKit.js +3 -3
- package/dist/data-display/Carousel.js +5 -5
- package/dist/data-display/Chart.js +11 -11
- package/dist/data-display/DataGrid.js +2 -2
- package/dist/data-display/DataViews.js +25 -24
- package/dist/data-display/FleetMap.js +9 -9
- package/dist/data-display/MapPreview.js +6 -6
- package/dist/en-US.js +1 -1
- package/dist/form/CepField.js +1 -1
- package/dist/form/DateRangePicker.js +5 -5
- package/dist/form/InputOTP.js +2 -2
- package/dist/form/total-form.js +4 -4
- package/dist/locales.js +2 -2
- package/dist/pt-BR.js +1 -1
- package/dist/types/components/data-display/DataViews/data-views-copy.d.ts +6 -0
- package/dist/types/components/data-display/DataViews/data-views-copy.d.ts.map +1 -1
- package/dist/types/components/data-display/DataViews/data-views-day-input.d.ts +32 -5
- package/dist/types/components/data-display/DataViews/data-views-day-input.d.ts.map +1 -1
- package/dist/types/components/data-display/DataViews/data-views-filter-panel.d.ts +6 -0
- package/dist/types/components/data-display/DataViews/data-views-filter-panel.d.ts.map +1 -1
- package/dist/types/components/data-display/DataViews/data-views-grid-parts.d.ts.map +1 -1
- package/dist/types/components/data-display/DataViews/data-views-inline-bar.d.ts +3 -1
- package/dist/types/components/data-display/DataViews/data-views-inline-bar.d.ts.map +1 -1
- package/dist/types/components/data-display/DataViews/data-views-more-filters.d.ts +3 -1
- package/dist/types/components/data-display/DataViews/data-views-more-filters.d.ts.map +1 -1
- package/dist/types/components/data-display/DataViews/data-views-range-pill.d.ts.map +1 -1
- package/dist/types/components/data-display/DataViews/data-views-range-reset.d.ts +16 -0
- package/dist/types/components/data-display/DataViews/data-views-range-reset.d.ts.map +1 -0
- package/dist/types/components/data-display/DataViews/data-views-writers.d.ts +13 -0
- package/dist/types/components/data-display/DataViews/data-views-writers.d.ts.map +1 -1
- package/dist/types/components/data-display/DataViews/use-data-views-state.d.ts +4 -0
- package/dist/types/components/data-display/DataViews/use-data-views-state.d.ts.map +1 -1
- package/dist/types/components/form/CepField/CepField.d.ts +1 -1
- package/dist/types/components/form/CepField/CepField.d.ts.map +1 -1
- package/dist/types/components/form/CepField/CepField.types.d.ts +8 -0
- package/dist/types/components/form/CepField/CepField.types.d.ts.map +1 -1
- package/dist/types/components/form/InputOTP/InputOTP.d.ts.map +1 -1
- package/dist/types/components/form/InputOTP/InputOTP.hooks.d.ts.map +1 -1
- package/dist/types/components/form/total-form/Fields/CepField.d.ts +6 -1
- package/dist/types/components/form/total-form/Fields/CepField.d.ts.map +1 -1
- package/dist/types/components/form/total-form/Fields/TextField.d.ts +22 -1
- package/dist/types/components/form/total-form/Fields/TextField.d.ts.map +1 -1
- package/dist/types/components/form/total-form/Fields/index.d.ts +1 -1
- package/dist/types/components/form/total-form/Fields/index.d.ts.map +1 -1
- package/dist/types/components/form/total-form/index.d.ts +1 -1
- package/dist/types/components/form/total-form/index.d.ts.map +1 -1
- package/dist/types/en-US.data-display.d.ts.map +1 -1
- package/dist/types/pt-BR.data-display.d.ts.map +1 -1
- package/dist/user-avatar.js +4 -4
- package/package.json +1 -1
- package/src/components/data-display/DataViews/data-views-copy.ts +6 -0
- package/src/components/data-display/DataViews/data-views-day-input.tsx +134 -38
- package/src/components/data-display/DataViews/data-views-empty.tsx +1 -1
- package/src/components/data-display/DataViews/data-views-filter-panel.tsx +8 -2
- package/src/components/data-display/DataViews/data-views-grid-parts.tsx +3 -1
- package/src/components/data-display/DataViews/data-views-inline-bar.tsx +7 -0
- package/src/components/data-display/DataViews/data-views-more-filters.tsx +4 -1
- package/src/components/data-display/DataViews/data-views-range-pill.tsx +11 -3
- package/src/components/data-display/DataViews/data-views-range-reset.ts +34 -0
- package/src/components/data-display/DataViews/data-views-writers.ts +24 -1
- package/src/components/data-display/DataViews/use-data-views-state.ts +6 -1
- package/src/components/form/CepField/CepField.md +7 -2
- package/src/components/form/CepField/CepField.tsx +13 -2
- package/src/components/form/CepField/CepField.types.ts +8 -0
- package/src/components/form/InputOTP/InputOTP.hooks.ts +48 -9
- package/src/components/form/InputOTP/InputOTP.md +6 -1
- package/src/components/form/InputOTP/InputOTP.tsx +40 -1
- package/src/components/form/total-form/Fields/CepField.tsx +7 -0
- package/src/components/form/total-form/Fields/TextField.tsx +64 -1
- package/src/components/form/total-form/Fields/index.ts +1 -1
- package/src/components/form/total-form/index.ts +1 -1
- package/src/en-US.data-display.ts +1 -0
- package/src/pt-BR.data-display.ts +1 -0
- package/dist/chunk-4OTJ66ID.js.map +0 -1
- package/dist/chunk-4UCXJYOO.js.map +0 -1
- package/dist/chunk-62XVHRTX.js.map +0 -1
- package/dist/chunk-76IHYINM.js.map +0 -1
- package/dist/chunk-7RG37FVT.js.map +0 -1
- package/dist/chunk-A5U7HEBI.js.map +0 -1
- package/dist/chunk-EBEMNKTK.js.map +0 -1
- package/dist/chunk-GJOU26UW.js.map +0 -1
- package/dist/chunk-MLGAGZJ3.js.map +0 -1
- package/dist/chunk-RDRXTH47.js.map +0 -1
- package/dist/chunk-U5FOJBLG.js.map +0 -1
- package/dist/chunk-UETHX57J.js.map +0 -1
- package/dist/chunk-XNLJ2Z7T.js.map +0 -1
- /package/dist/{chunk-GYFKIPR2.js.map → chunk-4PUCT3RX.js.map} +0 -0
- /package/dist/{chunk-C2I7NQC4.js.map → chunk-74KCGLOU.js.map} +0 -0
- /package/dist/{chunk-4J62V5OE.js.map → chunk-7YJYIMWP.js.map} +0 -0
- /package/dist/{chunk-MT74NRLY.js.map → chunk-C2OX3C7L.js.map} +0 -0
- /package/dist/{chunk-BO6IMK5P.js.map → chunk-DPHSJLMN.js.map} +0 -0
- /package/dist/{chunk-7XSCEHGA.js.map → chunk-EOQ6EKTS.js.map} +0 -0
- /package/dist/{chunk-XKPV52CG.js.map → chunk-GPTNZ57P.js.map} +0 -0
- /package/dist/{chunk-ZGAOA7LN.js.map → chunk-H56CRMCM.js.map} +0 -0
- /package/dist/{chunk-AMLHEKSV.js.map → chunk-HI7JPDD6.js.map} +0 -0
- /package/dist/{chunk-JK3QB7MO.js.map → chunk-I54LHHZS.js.map} +0 -0
- /package/dist/{chunk-LCGK22QQ.js.map → chunk-IKXCAYFH.js.map} +0 -0
- /package/dist/{chunk-D22FGMGU.js.map → chunk-JIAIPOEV.js.map} +0 -0
- /package/dist/{chunk-HQCZT6TO.js.map → chunk-JVOEVTCZ.js.map} +0 -0
- /package/dist/{chunk-JJNY5MZL.js.map → chunk-KG7OTJ26.js.map} +0 -0
- /package/dist/{chunk-2ZMR3TZU.js.map → chunk-L6UTYEUS.js.map} +0 -0
- /package/dist/{chunk-WWB43LWU.js.map → chunk-LOGSJH2Q.js.map} +0 -0
- /package/dist/{chunk-YJPBNIEM.js.map → chunk-MGFTHOPG.js.map} +0 -0
- /package/dist/{chunk-RB3R6BJL.js.map → chunk-O4G7QKDU.js.map} +0 -0
- /package/dist/{chunk-DYHOBKPU.js.map → chunk-OA6T2ME3.js.map} +0 -0
- /package/dist/{chunk-YHKHHSZH.js.map → chunk-OQ554425.js.map} +0 -0
- /package/dist/{chunk-54OJAOXC.js.map → chunk-PDSZN26U.js.map} +0 -0
- /package/dist/{chunk-MNNIOQTQ.js.map → chunk-PGVGCDPD.js.map} +0 -0
- /package/dist/{chunk-6ATXOJVQ.js.map → chunk-QADTI63U.js.map} +0 -0
- /package/dist/{chunk-SEUJNFOD.js.map → chunk-RLDTV5I2.js.map} +0 -0
- /package/dist/{chunk-KSJBFT4G.js.map → chunk-SGAHME6W.js.map} +0 -0
- /package/dist/{chunk-SB2SEGR6.js.map → chunk-TIM4PXWN.js.map} +0 -0
- /package/dist/{chunk-D4LMRSY7.js.map → chunk-UIWVSZ6I.js.map} +0 -0
- /package/dist/{chunk-XATRYJ4Q.js.map → chunk-VQBY73QH.js.map} +0 -0
- /package/dist/{chunk-SYERREN5.js.map → chunk-W2JYXZC2.js.map} +0 -0
- /package/dist/{chunk-QK3XXPBA.js.map → chunk-YWJCMGTE.js.map} +0 -0
- /package/dist/{chunk-UQ6SAIX7.js.map → chunk-Z47SYKRX.js.map} +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
|
-
import { type Dispatch, type SetStateAction } from "react";
|
|
3
|
+
import { type Dispatch, type SetStateAction, useState } from "react";
|
|
4
4
|
|
|
5
5
|
import { type DataViewState, type DataViewStatePatch } from "./data-views-types";
|
|
6
6
|
|
|
@@ -43,3 +43,26 @@ export function useStateWriters(setState: Dispatch<SetStateAction<DataViewState>
|
|
|
43
43
|
})),
|
|
44
44
|
};
|
|
45
45
|
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* "Limpar filtros", from ANY surface — the bar, the slide-in panel, the modal,
|
|
49
|
+
* the empty state — plus a key that changes with every click of it.
|
|
50
|
+
*
|
|
51
|
+
* The filter controls are keyed on `clearKey`, so a clear remounts them. That
|
|
52
|
+
* is what empties a field holding text the filter never applied: a refused day
|
|
53
|
+
* (`31/02/2026`) under a range that was open already. Clearing `{}` to `{}`
|
|
54
|
+
* changes no state, so nothing else would reach that field (FUT-4144).
|
|
55
|
+
*/
|
|
56
|
+
export function useClearFilters(patch: (next: DataViewStatePatch) => void): {
|
|
57
|
+
clearKey: number;
|
|
58
|
+
clearFilters: () => void;
|
|
59
|
+
} {
|
|
60
|
+
const [clearKey, setClearKey] = useState(0);
|
|
61
|
+
return {
|
|
62
|
+
clearKey,
|
|
63
|
+
clearFilters: () => {
|
|
64
|
+
patch({ search: "", pills: {}, ranges: {} });
|
|
65
|
+
setClearKey((n) => n + 1);
|
|
66
|
+
},
|
|
67
|
+
};
|
|
68
|
+
}
|
|
@@ -16,7 +16,7 @@ import { computeActiveFilterCount, defaultSortFields } from "./data-views-grid-h
|
|
|
16
16
|
import { resolveScope, useScopeConfigChecks, type ScopeConfig } from "./data-views-scopes";
|
|
17
17
|
import { serverDerived, useServerQuery } from "./data-views-server-query";
|
|
18
18
|
import { useSelection } from "./data-views-selection";
|
|
19
|
-
import { useStateWriters } from "./data-views-writers";
|
|
19
|
+
import { useClearFilters, useStateWriters } from "./data-views-writers";
|
|
20
20
|
import {
|
|
21
21
|
emptyViewState,
|
|
22
22
|
type DataViewColumn,
|
|
@@ -85,6 +85,10 @@ export interface DataViewsController<T extends Record<string, unknown>> {
|
|
|
85
85
|
* its patch from the same render-scoped snapshot and the last write would win.
|
|
86
86
|
*/
|
|
87
87
|
patch: (next: DataViewStatePatch) => void;
|
|
88
|
+
/** Drop every filter — search, pills, ranges — from any surface that offers it. */
|
|
89
|
+
clearFilters: () => void;
|
|
90
|
+
/** Changes on every {@link clearFilters}; the filter controls are keyed on it. */
|
|
91
|
+
clearKey: number;
|
|
88
92
|
matched: T[];
|
|
89
93
|
gridColumns: GridColumn<T>[];
|
|
90
94
|
/** Columns eligible for the show/hide menu (hideable, with a text header). */
|
|
@@ -355,6 +359,7 @@ export function useDataViewsState<T extends Record<string, unknown>>({
|
|
|
355
359
|
state,
|
|
356
360
|
ranges: state.ranges ?? {},
|
|
357
361
|
patch,
|
|
362
|
+
...useClearFilters(patch),
|
|
358
363
|
matched,
|
|
359
364
|
gridColumns,
|
|
360
365
|
hideableColumns,
|
|
@@ -48,6 +48,10 @@ serves a supplier form, a store-location form or a delivery checkout.
|
|
|
48
48
|
stands level with `sm` `Input`s
|
|
49
49
|
- `labelPlacement`: `above` (default) stacks the label over the box; `inside` draws it
|
|
50
50
|
in the box and floats it up on focus or a value, exactly as `Input`'s label does
|
|
51
|
+
- `autoComplete`: the `<input>`'s `autocomplete` (default `postal-code`). Pass `off` on a
|
|
52
|
+
form for somebody else's address (a supplier), or the browser offers the signed-in
|
|
53
|
+
person's own CEP and the lookup then fills their street (FUT-4140). Same prop on
|
|
54
|
+
`Fields.CepField`
|
|
51
55
|
|
|
52
56
|
### State Props
|
|
53
57
|
|
|
@@ -154,7 +158,8 @@ the lookup. The pure helpers (`formatCep`, `isValidCep`) live in
|
|
|
154
158
|
"Buscando endereço…" / "Endereço preenchido pelo CEP" without stealing focus
|
|
155
159
|
- `aria-busy` reflects the in-flight lookup
|
|
156
160
|
- `aria-describedby` links the input to its status region
|
|
157
|
-
- `inputMode="numeric"`
|
|
161
|
+
- `inputMode="numeric"` sits on the `<input>` (through `inputProps`), so it brings up the
|
|
162
|
+
numeric keypad on mobile; at the top level it landed on the wrapper `div` and did nothing
|
|
158
163
|
- With `labelPlacement="inside"` the label is the `Input`'s own, associated to the input
|
|
159
164
|
by `for`/`id`, so `getByLabelText` and screen readers name the field the same way
|
|
160
|
-
- `autoComplete="postal-code"` lets the browser's own autofill participate
|
|
165
|
+
- `autoComplete="postal-code"` (the default) lets the browser's own autofill participate
|
|
@@ -118,6 +118,12 @@ function CepStatus({
|
|
|
118
118
|
);
|
|
119
119
|
}
|
|
120
120
|
|
|
121
|
+
/** What only the `<input>` can act on. Hoisted: one object for every render. */
|
|
122
|
+
const CEP_INPUT_PROPS = { inputMode: 'numeric' } as const;
|
|
123
|
+
|
|
124
|
+
/** `postal-code` unless the caller said otherwise (`off` for somebody else's address). */
|
|
125
|
+
const autoCompleteOf = (autoComplete: string | undefined): string => autoComplete ?? 'postal-code';
|
|
126
|
+
|
|
121
127
|
/** The stacked label over the box (`labelPlacement="above"`), or nothing when there is no label. */
|
|
122
128
|
function AboveLabel({
|
|
123
129
|
name,
|
|
@@ -181,6 +187,7 @@ export function CepField({
|
|
|
181
187
|
copy,
|
|
182
188
|
size, // undefined reaches `Input`, whose own default is `md`
|
|
183
189
|
labelPlacement = 'above',
|
|
190
|
+
autoComplete,
|
|
184
191
|
}: CepFieldProps): React.ReactElement {
|
|
185
192
|
const testId = dataTestId ?? `cep-field-${name}`;
|
|
186
193
|
|
|
@@ -206,8 +213,12 @@ export function CepField({
|
|
|
206
213
|
id={name}
|
|
207
214
|
name={name}
|
|
208
215
|
type="text"
|
|
209
|
-
inputMode
|
|
210
|
-
|
|
216
|
+
// On the `<input>`, through `inputProps`: a top-level `inputMode` is a
|
|
217
|
+
// prop `Input` does not recognise, so it landed on the wrapper `div`
|
|
218
|
+
// and the phone kept opening the letter keyboard for eight digits
|
|
219
|
+
// (FUT-1440's CepField row).
|
|
220
|
+
inputProps={CEP_INPUT_PROPS}
|
|
221
|
+
autoComplete={autoCompleteOf(autoComplete)}
|
|
211
222
|
size={size}
|
|
212
223
|
// `inside`: the label is the Input's own, drawn in the box and floating
|
|
213
224
|
// up on focus or a value — what every other `Input` in the form does.
|
|
@@ -89,4 +89,12 @@ export interface CepFieldProps {
|
|
|
89
89
|
* is the same height as its neighbours.
|
|
90
90
|
*/
|
|
91
91
|
labelPlacement?: 'above' | 'inside';
|
|
92
|
+
/**
|
|
93
|
+
* The `<input>`'s `autocomplete`. Defaults to `postal-code`, which lets the
|
|
94
|
+
* browser offer the signed-in person's own CEP — right on their own address
|
|
95
|
+
* form, wrong on a form for somebody else's (a supplier): pass `off` there,
|
|
96
|
+
* or the owner's CEP goes in and the lookup fills the owner's street
|
|
97
|
+
* (FUT-4140).
|
|
98
|
+
*/
|
|
99
|
+
autoComplete?: string;
|
|
92
100
|
}
|
|
@@ -17,6 +17,36 @@ const isAllowed = (variant: string, text: string) => (PATTERNS[variant] ?? /.*/)
|
|
|
17
17
|
const padTo = (digits: string[], length: number) =>
|
|
18
18
|
Array.from({ length }, (_, i) => digits[i] ?? '');
|
|
19
19
|
|
|
20
|
+
/**
|
|
21
|
+
* What a change to the numeric FIRST slot is, now that its `maxLength` is the
|
|
22
|
+
* code's length rather than 1 (FUT-4144).
|
|
23
|
+
*
|
|
24
|
+
* That slot carries `one-time-code`, and the phone's SMS fill (or a password
|
|
25
|
+
* manager) writes the WHOLE code into it as one change, not a paste — kept to
|
|
26
|
+
* one character, the code lost every digit but the last. So:
|
|
27
|
+
*
|
|
28
|
+
* - `overtype`: a keystroke into the already-filled slot (its old character
|
|
29
|
+
* plus the new one). Ignored, exactly as `maxLength={1}` refused it before.
|
|
30
|
+
* - `code`: anything else longer than one character, written in one go.
|
|
31
|
+
* Spread across the slots like a paste.
|
|
32
|
+
*
|
|
33
|
+
* Every other slot and variant keeps `maxLength={1}` and never gets here.
|
|
34
|
+
*/
|
|
35
|
+
function firstSlotFill(variant: string, index: number, inputValue: string, current: string) {
|
|
36
|
+
if (variant !== 'numeric' || index !== 0 || inputValue.length < 2) return null;
|
|
37
|
+
const overtype = inputValue.length === 2 && current !== '' && inputValue.includes(current);
|
|
38
|
+
return overtype ? 'overtype' : 'code';
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* The code inside a first-slot fill. A phone that INSERTS the SMS code at the
|
|
43
|
+
* caret rather than replacing the slot leaves the digit already there in
|
|
44
|
+
* front: `4` + `482913` arrives as `4482913`, which spread as-is would give
|
|
45
|
+
* `448291`. Longer than the code and led by that digit: the digit is dropped.
|
|
46
|
+
*/
|
|
47
|
+
const codeOfFill = (inputValue: string, current: string, length: number): string =>
|
|
48
|
+
inputValue.length > length && current !== '' && inputValue.startsWith(current) ? inputValue.slice(1) : inputValue;
|
|
49
|
+
|
|
20
50
|
interface OtpInput {
|
|
21
51
|
variant: string;
|
|
22
52
|
length: number;
|
|
@@ -55,8 +85,23 @@ export const useOtpDigits = ({ variant, length, value, onChange, onComplete }: O
|
|
|
55
85
|
[length, onChange, onComplete],
|
|
56
86
|
);
|
|
57
87
|
|
|
88
|
+
/** Lays a whole code across the slots from the first: a paste, or an OS/SMS fill. */
|
|
89
|
+
const spread = useCallback(
|
|
90
|
+
(text: string) => {
|
|
91
|
+
const code = text.slice(0, length);
|
|
92
|
+
// An empty code is not a rejection, but it is nothing to apply either.
|
|
93
|
+
if (!code || !isAllowed(variant, code)) return;
|
|
94
|
+
|
|
95
|
+
setDigits(padTo(code.split(''), length));
|
|
96
|
+
publish(code);
|
|
97
|
+
},
|
|
98
|
+
[variant, length, publish],
|
|
99
|
+
);
|
|
100
|
+
|
|
58
101
|
const handleInputChange = useCallback(
|
|
59
102
|
(index: number, inputValue: string) => {
|
|
103
|
+
const fill = firstSlotFill(variant, index, inputValue, digits[0] ?? '');
|
|
104
|
+
if (fill) return fill === 'code' ? spread(codeOfFill(inputValue, digits[0] ?? '', length)) : undefined;
|
|
60
105
|
if (!isAllowed(variant, inputValue)) return;
|
|
61
106
|
|
|
62
107
|
const next = [...digits];
|
|
@@ -68,7 +113,7 @@ export const useOtpDigits = ({ variant, length, value, onChange, onComplete }: O
|
|
|
68
113
|
focusSlot(index + 1);
|
|
69
114
|
}
|
|
70
115
|
},
|
|
71
|
-
[variant, digits, length, publish, focusSlot],
|
|
116
|
+
[variant, digits, length, publish, focusSlot, spread],
|
|
72
117
|
);
|
|
73
118
|
|
|
74
119
|
const handleKeyDown = useCallback(
|
|
@@ -89,15 +134,9 @@ export const useOtpDigits = ({ variant, length, value, onChange, onComplete }: O
|
|
|
89
134
|
const handlePaste = useCallback(
|
|
90
135
|
(e: React.ClipboardEvent) => {
|
|
91
136
|
e.preventDefault();
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
// An empty paste is not a rejection, but it is nothing to apply either.
|
|
95
|
-
if (!pasted || !isAllowed(variant, pasted)) return;
|
|
96
|
-
|
|
97
|
-
setDigits(padTo(pasted.split(''), length));
|
|
98
|
-
publish(pasted);
|
|
137
|
+
spread(e.clipboardData.getData('text'));
|
|
99
138
|
},
|
|
100
|
-
[
|
|
139
|
+
[spread],
|
|
101
140
|
);
|
|
102
141
|
|
|
103
142
|
return { digits, inputRefs, handleInputChange, handleKeyDown, handlePaste };
|
|
@@ -161,7 +161,12 @@ function CodeVerification() {
|
|
|
161
161
|
|
|
162
162
|
The InputOTP component is designed with accessibility in mind:
|
|
163
163
|
|
|
164
|
-
- **ARIA Labels**:
|
|
164
|
+
- **ARIA Labels**: The container is a `role="group"`, so the `aria-label` you pass names
|
|
165
|
+
the slots together
|
|
166
|
+
- **Phone keypad** (`variant="numeric"`, FUT-4144): every slot `<input>` carries
|
|
167
|
+
`inputmode="numeric"` and `pattern="[0-9]*"`, and the first slot
|
|
168
|
+
`autocomplete="one-time-code"` so the phone can offer a code from an SMS. The
|
|
169
|
+
`alphanumeric` and `masked` variants carry none of the three
|
|
165
170
|
- **Keyboard Navigation**: Full keyboard support for all interactions
|
|
166
171
|
- **Focus Management**: Clear focus indicators and logical tab order
|
|
167
172
|
- **Screen Reader Support**: Announces field positions and values
|
|
@@ -15,6 +15,41 @@ const StyledOTPInput = styled(TextField, {
|
|
|
15
15
|
!['customColor', 'customSize', 'glass', 'gradient'].includes(prop as string),
|
|
16
16
|
})<OtpSlotFlags>(({ theme, ...flags }) => ({ ...otpSlotSx(theme, flags) }));
|
|
17
17
|
|
|
18
|
+
/**
|
|
19
|
+
* What tells a phone that a `numeric` code is digits (FUT-4144).
|
|
20
|
+
*
|
|
21
|
+
* The hook already refused letters, but nothing reached the `<input>` to say
|
|
22
|
+
* so, and the courier's six-digit join code opened the letter keyboard.
|
|
23
|
+
* `inputMode` picks the keypad; `pattern` is what iOS reads for the same; and
|
|
24
|
+
* `one-time-code` on the FIRST slot lets the phone offer a code from an SMS —
|
|
25
|
+
* the first slot, because it is the one that takes a paste and spreads it.
|
|
26
|
+
* `alphanumeric` and `masked` are left exactly as they were.
|
|
27
|
+
*/
|
|
28
|
+
function keypadProps(variant: InputOTPProps['variant'], index: number): Record<string, string> {
|
|
29
|
+
if (variant !== 'numeric') return {};
|
|
30
|
+
return {
|
|
31
|
+
inputMode: 'numeric',
|
|
32
|
+
pattern: '[0-9]*',
|
|
33
|
+
...(index === 0 ? { autoComplete: 'one-time-code' } : {}),
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* One character per slot — except the numeric FIRST slot, which carries
|
|
39
|
+
* `one-time-code`: the phone's SMS fill writes the whole code there in one
|
|
40
|
+
* change, and a `maxLength` of 1 would cut it to one digit before the hook
|
|
41
|
+
* could spread it.
|
|
42
|
+
*
|
|
43
|
+
* The code's length PLUS ONE: a phone that INSERTS the code at the caret keeps
|
|
44
|
+
* the digit already there in front (`4` + `482913`), and the browser caps that
|
|
45
|
+
* insertion at `maxlength` BEFORE the hook sees it. At the bare length it
|
|
46
|
+
* arrived as `448291` — a wrong code that looks whole. One more character lets
|
|
47
|
+
* the hook see the leading digit and drop it. A keystroke into the slot once
|
|
48
|
+
* filled is still refused, by the hook, as `maxLength={1}` refused it.
|
|
49
|
+
*/
|
|
50
|
+
const slotMaxLength = (variant: InputOTPProps['variant'], index: number, length: number): number =>
|
|
51
|
+
variant === 'numeric' && index === 0 ? length + 1 : 1;
|
|
52
|
+
|
|
18
53
|
const DEFAULTS = {
|
|
19
54
|
variant: 'numeric',
|
|
20
55
|
color: 'primary',
|
|
@@ -49,6 +84,9 @@ export const InputOTP = forwardRef<HTMLDivElement, InputOTPProps>((props, ref) =
|
|
|
49
84
|
<Box
|
|
50
85
|
ref={ref}
|
|
51
86
|
data-testid={dataTestId}
|
|
87
|
+
// A group, so the `aria-label` a caller gives the code names the six
|
|
88
|
+
// slots together: on a plain `div` it named nothing anyone could reach.
|
|
89
|
+
role="group"
|
|
52
90
|
sx={{ display: 'flex', gap: 1, justifyContent: 'center', alignItems: 'center' }}
|
|
53
91
|
{...rest}
|
|
54
92
|
>
|
|
@@ -72,9 +110,10 @@ export const InputOTP = forwardRef<HTMLDivElement, InputOTPProps>((props, ref) =
|
|
|
72
110
|
error={error}
|
|
73
111
|
disabled={disabled}
|
|
74
112
|
inputProps={{
|
|
75
|
-
maxLength:
|
|
113
|
+
maxLength: slotMaxLength(variant, index, length),
|
|
76
114
|
style: { textAlign: 'center' },
|
|
77
115
|
'data-testid': dataTestId ? `${dataTestId}-slot-${index}` : undefined,
|
|
116
|
+
...keypadProps(variant, index),
|
|
78
117
|
}}
|
|
79
118
|
/>
|
|
80
119
|
))}
|
|
@@ -34,6 +34,11 @@ export interface CepFieldProps {
|
|
|
34
34
|
size?: InputSize;
|
|
35
35
|
/** `above` (default) stacks the label over the box; `inside` floats it in the box like `Input`. */
|
|
36
36
|
labelPlacement?: 'above' | 'inside';
|
|
37
|
+
/**
|
|
38
|
+
* The `<input>`'s `autocomplete`. Defaults to `postal-code`; pass `off` when
|
|
39
|
+
* the form holds somebody else's address (FUT-4140).
|
|
40
|
+
*/
|
|
41
|
+
autoComplete?: string;
|
|
37
42
|
}
|
|
38
43
|
|
|
39
44
|
const PARTS: readonly CepPart[] = ['street', 'neighborhood', 'city', 'state'];
|
|
@@ -114,6 +119,7 @@ export function CepField({
|
|
|
114
119
|
copy,
|
|
115
120
|
size,
|
|
116
121
|
labelPlacement,
|
|
122
|
+
autoComplete,
|
|
117
123
|
}: CepFieldProps): React.ReactElement {
|
|
118
124
|
const { values, errors, setFieldValue } = useFormContext();
|
|
119
125
|
const { write, clearOwned } = useOwnedAutofill();
|
|
@@ -151,6 +157,7 @@ export function CepField({
|
|
|
151
157
|
debounceMs={debounceMs}
|
|
152
158
|
size={size}
|
|
153
159
|
labelPlacement={labelPlacement}
|
|
160
|
+
autoComplete={autoComplete}
|
|
154
161
|
dataTestId={`total-form-field-${name}`}
|
|
155
162
|
/>
|
|
156
163
|
);
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
|
+
import type { InputHTMLAttributes } from 'react';
|
|
3
4
|
import React from 'react';
|
|
4
5
|
|
|
5
6
|
import { FormControl, FormLabel, FormMessage } from '../../Form';
|
|
@@ -16,6 +17,59 @@ export interface TextFieldProps {
|
|
|
16
17
|
type?: string;
|
|
17
18
|
/** Placeholder shown when the field is empty. */
|
|
18
19
|
placeholder?: string;
|
|
20
|
+
/**
|
|
21
|
+
* The `autocomplete` attribute of the `<input>`. Omitted, the browser decides
|
|
22
|
+
* from the `name` — which is how a supplier's `name`/`phone`/`email` gets
|
|
23
|
+
* offered the signed-in owner's own (FUT-4140). Pass `off` for somebody
|
|
24
|
+
* else's data, `new-password` for a secret the password manager must not
|
|
25
|
+
* fill. An `inputProps.autoComplete` wins over this one.
|
|
26
|
+
*/
|
|
27
|
+
autoComplete?: string;
|
|
28
|
+
/**
|
|
29
|
+
* Attributes for the `<input>` element itself — `inputMode`, `autoCapitalize`,
|
|
30
|
+
* `autoCorrect`, `spellCheck`, `maxLength`, `pattern`, `enterKeyHint`…
|
|
31
|
+
*
|
|
32
|
+
* They reach the `<input>`, never the wrapper `div`, where a keypad hint does
|
|
33
|
+
* nothing. What the field owns — `id`, `name`, `type`, `value`, `onChange`,
|
|
34
|
+
* `placeholder` and the `total-form-field-<name>` test id — is not
|
|
35
|
+
* overridable here.
|
|
36
|
+
*/
|
|
37
|
+
inputProps?: TextFieldInputProps;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** The `<input>` attributes {@link TextField} lets a caller set (see `inputProps`). */
|
|
41
|
+
export type TextFieldInputProps = Omit<
|
|
42
|
+
InputHTMLAttributes<HTMLInputElement>,
|
|
43
|
+
'id' | 'name' | 'type' | 'value' | 'defaultValue' | 'onChange' | 'placeholder' | 'size' | 'color'
|
|
44
|
+
>;
|
|
45
|
+
|
|
46
|
+
/** The keys {@link TextFieldInputProps} leaves out, also dropped at runtime. */
|
|
47
|
+
const FIELD_OWNED = [
|
|
48
|
+
'id',
|
|
49
|
+
'name',
|
|
50
|
+
'type',
|
|
51
|
+
'value',
|
|
52
|
+
'defaultValue',
|
|
53
|
+
'onChange',
|
|
54
|
+
'placeholder',
|
|
55
|
+
'size',
|
|
56
|
+
'color',
|
|
57
|
+
'data-testid',
|
|
58
|
+
];
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* The caller's `<input>` attributes, minus what the field owns.
|
|
62
|
+
*
|
|
63
|
+
* The type already refuses those keys; plain JS or a cast does not, and a
|
|
64
|
+
* renamed id, name or test id would silently unbind the label, the form value
|
|
65
|
+
* or every spec that finds the field by it. So they are dropped here too, and
|
|
66
|
+
* the field's own test id is written last.
|
|
67
|
+
*/
|
|
68
|
+
function callerInputProps(inputProps: TextFieldInputProps | undefined, testId: string): Record<string, unknown> {
|
|
69
|
+
const kept = Object.fromEntries(
|
|
70
|
+
Object.entries(inputProps ?? {}).filter(([key]) => !FIELD_OWNED.includes(key)),
|
|
71
|
+
);
|
|
72
|
+
return { ...kept, 'data-testid': testId };
|
|
19
73
|
}
|
|
20
74
|
|
|
21
75
|
/**
|
|
@@ -26,7 +80,14 @@ export interface TextFieldProps {
|
|
|
26
80
|
* Renders its error via `total-form-field-<name>-message` and the input via
|
|
27
81
|
* `total-form-field-<name>`.
|
|
28
82
|
*/
|
|
29
|
-
export function TextField({
|
|
83
|
+
export function TextField({
|
|
84
|
+
name,
|
|
85
|
+
label,
|
|
86
|
+
type = 'text',
|
|
87
|
+
placeholder,
|
|
88
|
+
autoComplete,
|
|
89
|
+
inputProps,
|
|
90
|
+
}: TextFieldProps): React.ReactElement {
|
|
30
91
|
const { values, errors, setFieldValue } = useFormContext();
|
|
31
92
|
const error = errors[name];
|
|
32
93
|
const testId = `total-form-field-${name}`;
|
|
@@ -45,6 +106,8 @@ export function TextField({ name, label, type = 'text', placeholder }: TextField
|
|
|
45
106
|
placeholder={placeholder}
|
|
46
107
|
value={values[name] ?? ''}
|
|
47
108
|
error={Boolean(error)}
|
|
109
|
+
autoComplete={autoComplete}
|
|
110
|
+
inputProps={callerInputProps(inputProps, testId)}
|
|
48
111
|
data-testid={testId}
|
|
49
112
|
onChange={(event) => setFieldValue(name, event.target.value)}
|
|
50
113
|
/>
|
|
@@ -3,7 +3,7 @@ export type { CategoryFieldProps } from './CategoryField';
|
|
|
3
3
|
export { CepField } from './CepField';
|
|
4
4
|
export type { CepFieldProps, CepFills } from './CepField';
|
|
5
5
|
export { TextField } from './TextField';
|
|
6
|
-
export type { TextFieldProps } from './TextField';
|
|
6
|
+
export type { TextFieldInputProps, TextFieldProps } from './TextField';
|
|
7
7
|
export { SelectField } from './SelectField';
|
|
8
8
|
export type { SelectFieldProps } from './SelectField';
|
|
9
9
|
export { ToggleField } from './ToggleField';
|
|
@@ -9,4 +9,4 @@ export type { SubmitButtonProps } from './SubmitButton';
|
|
|
9
9
|
|
|
10
10
|
export * as Fields from './Fields';
|
|
11
11
|
export { CategoryField, TextField, SelectField } from './Fields';
|
|
12
|
-
export type { CategoryFieldProps, TextFieldProps, SelectFieldProps } from './Fields';
|
|
12
|
+
export type { CategoryFieldProps, TextFieldInputProps, TextFieldProps, SelectFieldProps } from './Fields';
|
|
@@ -219,6 +219,7 @@ export const EN_US_DATA_VIEWS_COPY: DataViewsCopy = {
|
|
|
219
219
|
// rather than being carried over. A translated label above an unchanged
|
|
220
220
|
// mask is the shape that gets a date entered backwards.
|
|
221
221
|
dayMask: "mm/dd/yyyy",
|
|
222
|
+
dayInvalid: "That date does not exist.",
|
|
222
223
|
clearAllFilters: "Clear all filters",
|
|
223
224
|
// The KEYS are the package's own preset ids and stay as they are — the
|
|
224
225
|
// surface matches on them. Only the labels beside them are words.
|
|
@@ -213,6 +213,7 @@ export const PT_BR_DATA_VIEWS_COPY: DataViewsCopy = {
|
|
|
213
213
|
rangeInclusiveNote: "Ambas as datas entram no resultado.",
|
|
214
214
|
rangeStart: "De",
|
|
215
215
|
dayMask: "dd/mm/aaaa",
|
|
216
|
+
dayInvalid: "Essa data não existe.",
|
|
216
217
|
clearAllFilters: "Limpar todos os filtros",
|
|
217
218
|
rangePresets: {
|
|
218
219
|
hoje: "Hoje",
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/components/form/InputOTP/InputOTP.tsx"],"sourcesContent":["import Box from '@mui/material/Box/index.js';\nimport TextField from '@mui/material/TextField/index.js';\nimport { styled } from '@mui/material/styles/index.js';\nimport React, { forwardRef } from 'react';\n\nimport { withDefaults } from '../../../utils/withDefaults';\n\nimport { useOtpDigits } from './InputOTP.hooks';\nimport { otpSlotSx } from './InputOTP.styles';\nimport type { OtpSlotFlags } from './InputOTP.styles';\nimport type { InputOTPProps } from './InputOTP.types';\n\nconst StyledOTPInput = styled(TextField, {\n shouldForwardProp: (prop) =>\n !['customColor', 'customSize', 'glass', 'gradient'].includes(prop as string),\n})<OtpSlotFlags>(({ theme, ...flags }) => ({ ...otpSlotSx(theme, flags) }));\n\nconst DEFAULTS = {\n variant: 'numeric',\n color: 'primary',\n size: 'md',\n length: 6,\n value: '',\n glass: false,\n gradient: false,\n autoFocus: false,\n error: false,\n disabled: false,\n} satisfies Partial<InputOTPProps>;\n\ntype ResolvedProps = InputOTPProps & Required<Pick<InputOTPProps, keyof typeof DEFAULTS>>;\n\nexport const InputOTP = forwardRef<HTMLDivElement, InputOTPProps>((props, ref) => {\n const {\n variant, color, size, length, value, onChange, onComplete,\n glass, gradient, autoFocus, error, disabled, dataTestId,\n ...rest\n } = withDefaults(props, DEFAULTS) as ResolvedProps;\n\n const { digits, inputRefs, handleInputChange, handleKeyDown, handlePaste } = useOtpDigits({\n variant,\n length,\n value,\n onChange,\n onComplete,\n });\n\n return (\n <Box\n ref={ref}\n data-testid={dataTestId}\n sx={{ display: 'flex', gap: 1, justifyContent: 'center', alignItems: 'center' }}\n {...rest}\n >\n {digits.map((digit, index) => (\n <StyledOTPInput\n key={index}\n ref={(el: HTMLDivElement | null) => {\n inputRefs.current[index] = el?.querySelector('input') || null;\n }}\n customColor={color}\n customSize={size}\n glass={glass}\n gradient={gradient}\n // `masked` keeps the digit in state and shows a bullet in its place.\n value={variant === 'masked' ? (digit ? '•' : '') : digit}\n onChange={(e) => handleInputChange(index, e.target.value)}\n onKeyDown={(e) => handleKeyDown(index, e)}\n // Only the first slot takes a paste; it fills the rest itself.\n onPaste={index === 0 ? handlePaste : undefined}\n autoFocus={autoFocus && index === 0}\n error={error}\n disabled={disabled}\n inputProps={{\n maxLength: 1,\n style: { textAlign: 'center' },\n 'data-testid': dataTestId ? `${dataTestId}-slot-${index}` : undefined,\n }}\n />\n ))}\n </Box>\n );\n});\n\nInputOTP.displayName = 'InputOTP';\n"],"mappings":";;;;;;;;;;;AAAA,OAAO,SAAS;AAChB,OAAO,eAAe;AACtB,SAAS,cAAc;AACvB,SAAgB,kBAAkB;AAoD1B;AA3CR,IAAM,iBAAiB,OAAO,WAAW;AAAA,EACvC,mBAAmB,CAAC,SAClB,CAAC,CAAC,eAAe,cAAc,SAAS,UAAU,EAAE,SAAS,IAAc;AAC/E,CAAC,EAAgB,CAAC,EAAE,OAAO,GAAG,MAAM,OAAO,EAAE,GAAG,UAAU,OAAO,KAAK,EAAE,EAAE;AAE1E,IAAM,WAAW;AAAA,EACf,SAAS;AAAA,EACT,OAAO;AAAA,EACP,MAAM;AAAA,EACN,QAAQ;AAAA,EACR,OAAO;AAAA,EACP,OAAO;AAAA,EACP,UAAU;AAAA,EACV,WAAW;AAAA,EACX,OAAO;AAAA,EACP,UAAU;AACZ;AAIO,IAAM,WAAW,WAA0C,CAAC,OAAO,QAAQ;AAChF,QAAM;AAAA,IACJ;AAAA,IAAS;AAAA,IAAO;AAAA,IAAM;AAAA,IAAQ;AAAA,IAAO;AAAA,IAAU;AAAA,IAC/C;AAAA,IAAO;AAAA,IAAU;AAAA,IAAW;AAAA,IAAO;AAAA,IAAU;AAAA,IAC7C,GAAG;AAAA,EACL,IAAI,aAAa,OAAO,QAAQ;AAEhC,QAAM,EAAE,QAAQ,WAAW,mBAAmB,eAAe,YAAY,IAAI,aAAa;AAAA,IACxF;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF,CAAC;AAED,SACE;AAAA,IAAC;AAAA;AAAA,MACC;AAAA,MACA,eAAa;AAAA,MACb,IAAI,EAAE,SAAS,QAAQ,KAAK,GAAG,gBAAgB,UAAU,YAAY,SAAS;AAAA,MAC7E,GAAG;AAAA,MAEH,iBAAO,IAAI,CAAC,OAAO,UAClB;AAAA,QAAC;AAAA;AAAA,UAEC,KAAK,CAAC,OAA8B;AAClC,sBAAU,QAAQ,KAAK,IAAI,IAAI,cAAc,OAAO,KAAK;AAAA,UAC3D;AAAA,UACA,aAAa;AAAA,UACb,YAAY;AAAA,UACZ;AAAA,UACA;AAAA,UAEA,OAAO,YAAY,WAAY,QAAQ,WAAM,KAAM;AAAA,UACnD,UAAU,CAAC,MAAM,kBAAkB,OAAO,EAAE,OAAO,KAAK;AAAA,UACxD,WAAW,CAAC,MAAM,cAAc,OAAO,CAAC;AAAA,UAExC,SAAS,UAAU,IAAI,cAAc;AAAA,UACrC,WAAW,aAAa,UAAU;AAAA,UAClC;AAAA,UACA;AAAA,UACA,YAAY;AAAA,YACV,WAAW;AAAA,YACX,OAAO,EAAE,WAAW,SAAS;AAAA,YAC7B,eAAe,aAAa,GAAG,UAAU,SAAS,KAAK,KAAK;AAAA,UAC9D;AAAA;AAAA,QArBK;AAAA,MAsBP,CACD;AAAA;AAAA,EACH;AAEJ,CAAC;AAED,SAAS,cAAc;","names":[]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/components/data-display/DataViews/data-views-filter-panel.tsx"],"sourcesContent":["\"use client\";\n\nimport Box from \"@mui/material/Box/index.js\";\nimport Link from \"@mui/material/Link/index.js\";\n\nimport { StackedModal, StackedModalContent } from \"../../feedback/StackedModal\";\nimport { TableFilter } from \"../../layout/TableFilter\";\n\nimport { PillControl } from \"./data-views-category-pill\";\nimport { PanelRangeField } from \"./data-views-range-pill\";\nimport { useDataViewsCopy } from \"./data-views-copy-context\";\nimport type {\n FilterFieldConfig,\n RangeFieldConfig,\n RangeValue,\n} from \"./data-views-types\";\nimport { sxRem } from \"../../../tokens/relative\";\n\n/* ── Filter panel ────────────────────────────────────────────────────────── */\n\nexport interface GridFilterPanelProps<T extends Record<string, unknown>> {\n testIdPrefix: string;\n search: string;\n fields: FilterFieldConfig<T>[];\n rangeFields: RangeFieldConfig<T>[];\n pills: Record<string, string[]>;\n ranges: Record<string, RangeValue>;\n onSearchChange: (value: string) => void;\n onTogglePill: (fieldId: string, value: string, checked: boolean) => void;\n onChangeRange: (fieldId: string, range: RangeValue) => void;\n onClearField: (fieldId: string) => void;\n onClearAll: () => void;\n}\n\n/** One pill field: a compact multi-select for long lists, else a checkbox list. */\nfunction PillField<T extends Record<string, unknown>>({\n field,\n selected,\n testIdPrefix,\n onTogglePill,\n onClearField,\n}: {\n field: FilterFieldConfig<T>;\n selected: string[];\n testIdPrefix: string;\n onTogglePill: (fieldId: string, value: string, checked: boolean) => void;\n onClearField: (fieldId: string) => void;\n}): React.JSX.Element {\n const testId = `${testIdPrefix}-filter-${field.id}`;\n // `searchEnabled` forces the searchable multi-select; a plain `multiselect`\n // keeps the auto \"search only when the list is long\" rule. `category` is the\n // hierarchical tree — see PillControl.\n if (field.searchEnabled || field.control === \"multiselect\" || field.control === \"category\") {\n return (\n <PillControl\n fieldId={field.id}\n pill={field}\n selected={selected}\n onTogglePill={onTogglePill}\n onClearField={onClearField}\n testIdPrefix={testIdPrefix}\n layout=\"stacked\"\n />\n );\n }\n return (\n <TableFilter.CheckboxField\n label={field.label}\n options={field.options}\n selected={new Set(selected)}\n onToggle={(value, checked) => onTogglePill(field.id, value, checked)}\n testId={testId}\n />\n );\n}\n\n/** The filter controls (keyword + pill facets + numeric ranges), surface-agnostic. */\nfunction FilterControls<T extends Record<string, unknown>>({\n testIdPrefix,\n search,\n fields,\n rangeFields,\n pills,\n ranges,\n onSearchChange,\n onTogglePill,\n onChangeRange,\n onClearField,\n}: Omit<GridFilterPanelProps<T>, \"onClearAll\">): React.JSX.Element {\n const copy = useDataViewsCopy();\n return (\n <>\n <TableFilter.Keyword\n value={search}\n onChange={onSearchChange}\n label={copy.search.allColumnsLabel}\n placeholder={copy.search.keywordPlaceholder}\n testId={`${testIdPrefix}-search-all`}\n />\n {(fields.length > 0 || rangeFields.length > 0) && (\n <TableFilter.Section title={copy.filters.panelTitle}>\n {fields.map((field) => (\n <PillField\n key={field.id}\n field={field}\n selected={pills[field.id] ?? []}\n testIdPrefix={testIdPrefix}\n onTogglePill={onTogglePill}\n onClearField={onClearField}\n />\n ))}\n {rangeFields.map((field) => (\n <PanelRangeField\n key={field.id}\n field={field}\n value={ranges[field.id] ?? {}}\n onChange={(range) => onChangeRange(field.id, range)}\n testId={`${testIdPrefix}-range-${field.id}`}\n />\n ))}\n </TableFilter.Section>\n )}\n </>\n );\n}\n\n/** The slide-in filter panel: keyword + checkbox facets + numeric ranges. */\nexport function GridFilterPanel<T extends Record<string, unknown>>(\n props: GridFilterPanelProps<T>,\n): React.JSX.Element {\n const copy = useDataViewsCopy();\n return (\n <TableFilter.Panel\n onClearAll={props.onClearAll}\n clearTestId={`${props.testIdPrefix}-clear-filters`}\n ariaLabel={copy.filters.panelTitle}\n >\n <FilterControls {...props} />\n </TableFilter.Panel>\n );\n}\n\n/** Props for the modal filter surface used below the inline breakpoint. */\ninterface FilterDialogProps<T extends Record<string, unknown>> extends GridFilterPanelProps<T> {\n open: boolean;\n onClose: () => void;\n}\n\n/**\n * The same filter controls in a centered modal — used on smaller screens where the\n * slide-in panel would overlap the table. The \"Filtros\" toolbar button opens it.\n */\nexport function FilterDialog<T extends Record<string, unknown>>({\n open,\n onClose,\n ...props\n}: FilterDialogProps<T>): React.JSX.Element {\n const copy = useDataViewsCopy();\n return (\n <StackedModal\n open={open}\n onClose={onClose}\n navigationTitle={copy.filters.panelTitle}\n backLabel={copy.nav.goBack}\n modalId={`${props.testIdPrefix}-filters`}\n maxWidth=\"xs\"\n dataTestId={`${props.testIdPrefix}-filter-dialog`}\n >\n <StackedModalContent>\n <Box sx={{ display: \"flex\", flexDirection: \"column\", gap: 2.5 }}>\n <Box sx={{ display: \"flex\", justifyContent: \"flex-end\" }}>\n <Link\n component=\"button\"\n type=\"button\"\n underline=\"hover\"\n onClick={props.onClearAll}\n data-testid={`${props.testIdPrefix}-clear-filters`}\n sx={{ fontSize: sxRem(12), color: \"text.secondary\" }}\n >\n {copy.filters.clearAllFilters}\n </Link>\n </Box>\n <FilterControls {...props} />\n </Box>\n </StackedModalContent>\n </StackedModal>\n );\n}\n"],"mappings":"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","names":[]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/components/data-display/DataViews/data-views-day-input.tsx"],"sourcesContent":["'use client';\n\n/**\n * A day bound as a MASKED `dd/mm/aaaa` text field, replacing `<input type=\"date\">`.\n *\n * The native control is three separate segments wearing one box. Typing a date\n * means filling a segment, being jumped to the next, and starting over — and\n * which segment comes first is the BROWSER's locale, not ours, so the same\n * field reads `dd/mm/aaaa` for one merchant and `mm/dd/yyyy` for the next with\n * nothing in our code saying which. There is no way to type a date as one\n * continuous run of digits, which is how people actually type dates.\n *\n * A mask gives that back: eight digits straight through, separators inserted as\n * you go, backspace walking back through them.\n *\n * Measured, driving both controls in Chromium with the same eight keystrokes\n * (`01072026`, one `keydown` at a time):\n *\n * native → field ends EMPTY, no filter applied, 0 requests\n * masked → field reads `01/07/2026`, filter applied, 1 request\n *\n * The native result is the whole complaint: you type a date and nothing lands.\n *\n * The second half of the fix is WHEN the bound is written. A partially-typed\n * year is itself a valid date — `2026` passes through `0002`, `0020`, `0202` on\n * the way — so committing on every keystroke would send the grid off to fetch\n * years nobody meant. A bound is written only once the text is a WHOLE, real\n * date, which is what keeps the run above at one request rather than four.\n */\nimport TextField from '@mui/material/TextField/index.js';\nimport React, { useEffect, useState } from 'react';\nimport { fieldTextFieldStyles } from '../../../tokens/field-height';\nimport { sxRem } from '../../../tokens/relative';\n\n\n/**\n * THE MASK IS THE ORDER, and both halves of this file read it.\n *\n * The placeholder a host supplies (`copy.filters.dayMask` — `dd/mm/aaaa` for\n * pt-BR, `mm/dd/yyyy` for en-US) is not decoration: it tells the reader which\n * segment comes first. Everything below therefore derives the segment order\n * FROM that same string, so the shape on screen and the shape the parser\n * expects cannot disagree.\n *\n * They did disagree, and the failure was silent in the worst direction. The\n * mask/parse pair was hardcoded day-first (the `Br` in the old names was\n * accurate) while the placeholder followed the copy pack — so under `en-US` the\n * field ASKED for `mm/dd/yyyy`, and a reader who typed exactly that got a day\n * of 12 and a month of 31, which is not a date, which commits nothing. No\n * error, no filter, a full list that reads like an answer. The en-US pack's own\n * comment already said the mask \"is the shape that gets a date entered\n * backwards\"; nothing enforced it.\n */\n\n/** What one segment of a mask is, and how many digits it takes. */\ntype DaySegment = 'year' | 'month' | 'day';\nconst SEGMENT_WIDTH: Readonly<Record<DaySegment, number>> = { year: 4, month: 2, day: 2 };\n\n/**\n * The segments a mask asks for, in its own order.\n *\n * Keyed on each token's FIRST letter so one rule covers every spelling a host\n * might use — `aaaa`/`yyyy` for the year, `mm` for the month, `dd`/`tt` for the\n * day. An unreadable mask falls back to day-first rather than throwing: this\n * runs on every keystroke, and a field that renders is recoverable where one\n * that crashes the grid is not.\n */\nfunction segmentsOf(mask: string): readonly DaySegment[] {\n const tokens = mask.split(/[^A-Za-z]+/).filter(Boolean);\n if (tokens.length !== 3) return ['day', 'month', 'year'];\n const segments = tokens.map((token): DaySegment => {\n const head = token.slice(0, 1).toLowerCase();\n if (head === 'y' || head === 'a') return 'year';\n return head === 'm' ? 'month' : 'day';\n });\n // A mask naming the same segment twice describes no date. Fall back rather\n // than build a parser that can never succeed.\n return new Set(segments).size === 3 ? segments : ['day', 'month', 'year'];\n}\n\n/** The separator the mask uses, defaulting to `/`. */\nconst separatorOf = (mask: string): string => mask.replace(/[A-Za-z]/g, '').slice(0, 1) || '/';\n\n/** `AAAA-MM-DD` (the wire) → the mask's own order (what the reader sees). */\nexport function isoToMasked(iso: string, mask: string): string {\n const parts = /^(\\d{4})-(\\d{2})-(\\d{2})$/.exec(iso);\n if (!parts) return '';\n const value: Record<DaySegment, string> = {\n year: parts[1] as string,\n month: parts[2] as string,\n day: parts[3] as string,\n };\n return segmentsOf(mask)\n .map((segment) => value[segment])\n .join(separatorOf(mask));\n}\n\n/**\n * The mask's order → `AAAA-MM-DD`, but ONLY for a day that exists.\n *\n * `31/02/2026` parses fine as three numbers and is not a date, and `Date` would\n * silently roll it into March rather than reject it — so the round trip through\n * UTC is the check: if the month or day comes back changed, the input was not a\n * day.\n */\nexport function maskedToIso(text: string, mask: string): string {\n const segments = segmentsOf(mask);\n const digits = text.replace(/\\D/g, '');\n if (digits.length !== 8) return '';\n const value = {} as Record<DaySegment, string>;\n let cursor = 0;\n for (const segment of segments) {\n value[segment] = digits.slice(cursor, cursor + SEGMENT_WIDTH[segment]);\n cursor += SEGMENT_WIDTH[segment];\n }\n const year = Number(value.year);\n const month = Number(value.month);\n const day = Number(value.day);\n if (year < 1000) return '';\n const probe = new Date(Date.UTC(year, month - 1, day));\n if (probe.getUTCMonth() !== month - 1 || probe.getUTCDate() !== day) return '';\n return `${value.year}-${value.month}-${value.day}`;\n}\n\n/**\n * Digits only, capped at the mask's width, with the separators put back as you\n * type.\n *\n * Deriving the whole string from the digits — rather than editing the text in\n * place — is what makes backspace behave. Four digits of `dd/mm/aaaa` render as\n * `06/08`, not `06/08/`, so deleting the first digit of the year removes the\n * trailing separator with it instead of leaving a dead keystroke that changes\n * nothing.\n */\nexport function maskDate(raw: string, mask: string): string {\n const segments = segmentsOf(mask);\n const separator = separatorOf(mask);\n const digits = raw.replace(/\\D/g, '').slice(0, 8);\n const out: string[] = [];\n let cursor = 0;\n for (const segment of segments) {\n if (cursor >= digits.length) break;\n out.push(digits.slice(cursor, cursor + SEGMENT_WIDTH[segment]));\n cursor += SEGMENT_WIDTH[segment];\n }\n return out.join(separator);\n}\n\n/**\n * What has to land on the `<input>` itself rather than on the `TextField`.\n *\n * Anything `TextField` does not recognise is spread onto its root `FormControl`\n * div. `inputMode` went up there once: the attribute sat on a non-editable\n * wrapper, meant nothing, and the phone went on offering the letter keyboard\n * for a field that only takes digits (the `dd/mm/aaaa` hint under a QWERTY\n * layout). Only the `<input>` this reaches decides which keypad opens — and the\n * same is true of `aria-describedby`, which on the wrapper points at a\n * description no screen reader will read out for the field.\n *\n * `numeric` and not `tel`: both raise a keypad, but the phone one is a dialler\n * with `+ * #` on it — keys that cannot occur in a date. A paste carrying\n * separators still lands, because the MASK is what filters the text; the keypad\n * only narrows what a thumb can reach for.\n *\n * Autofill off for the same reason: the browser's saved-value strip covers the\n * keypad to suggest names and addresses, none of which are a date, and none of\n * which the mask would keep.\n */\nfunction dayInputProps(testId: string, describedBy: string | undefined) {\n return {\n 'data-testid': testId,\n maxLength: 10,\n inputMode: 'numeric' as const,\n autoComplete: 'off',\n ...(describedBy ? { 'aria-describedby': describedBy } : {}),\n };\n}\n\n/**\n * One end of a day window. The BOUND stays `AAAA-MM-DD` — the form the backend's\n * inclusive day comparison already speaks, and the form the chip label reads —\n * so only the typing surface changes.\n */\nexport function DayBoundInput({\n label,\n value,\n onChange,\n testId,\n error = false,\n describedBy,\n mask,\n}: {\n label: string;\n /** The applied bound, `AAAA-MM-DD`, or undefined when this end is open. */\n value: string | undefined;\n onChange: (bound: string | undefined) => void;\n testId: string;\n /**\n * Paint the field as rejected. It only ever writes a real day by itself, so\n * this is for a caller judging the PAIR: a window whose end precedes its\n * start is two valid days and one impossible period, and the field it was\n * typed into is the only place to say which half is being complained about.\n */\n error?: boolean;\n /** Id of the element carrying that complaint, so it is read WITH the field. */\n describedBy?: string;\n /**\n * The day mask, which IS the placeholder — so the expected ORDER is on\n * screen before the first keystroke rather than discovered by getting it\n * wrong. `dd/mm/aaaa` is one locale's order and one locale's letters.\n *\n * A PROP rather than a context read: this field has two callers, and only\n * one of them is inside DataViews. `DateRangePicker` mounts it standalone\n * with its own message table, so a context read here throws for every host\n * of that picker — which is exactly what it did.\n */\n mask: string;\n}): React.JSX.Element {\n const applied = value ?? '';\n const [text, setText] = useState(() => isoToMasked(applied, mask));\n\n // The bound is the APPLIED filter; `text` is what is being typed. Re-sync only\n // when the applied value moved somewhere the text does not already mean — a\n // \"Limpar\", a saved view, the other pill resetting the pair. Without that\n // guard the field's own write bounces straight back mid-edit and resets the\n // caret, which is the native control's worst habit reproduced by hand.\n useEffect(() => {\n setText((current) =>\n maskedToIso(current, mask) === applied ? current : isoToMasked(applied, mask),\n );\n }, [applied, mask]);\n\n return (\n <TextField\n size=\"small\"\n type=\"text\"\n label={label}\n error={error}\n placeholder={mask}\n value={text}\n onChange={(event) => {\n const next = maskDate(event.target.value, mask);\n setText(next);\n // Emptying clears the bound. A COMPLETE day applies it. A partial one\n // does NEITHER — it leaves whatever is applied alone, so typing is never\n // fought and a half-typed year never reaches the backend.\n if (next === '') return onChange(undefined);\n const iso = maskedToIso(next, mask);\n if (iso) onChange(iso);\n }}\n // Leaving a half-typed (or impossible) date on screen would show a filter\n // the list is not actually using. On blur the field snaps back to what IS\n // applied; a complete date already equals it, so this only ever tidies.\n onBlur={() => setText(isoToMasked(applied, mask))}\n // The placeholder occupies the space an un-shrunk label would render into.\n InputLabelProps={{ shrink: true }}\n inputProps={dayInputProps(testId, describedBy)}\n sx={[{ width: sxRem(165) }, fieldTextFieldStyles]}\n />\n );\n}\n"],"mappings":"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","names":[]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/components/data-display/DataViews/data-views-range-pill.tsx"],"sourcesContent":["'use client';\n\n/**\n * A range filter as a PILL in the inline filter bar — the same affordance the\n * multi-select facets use, so \"Data\" and \"Valor\" sit in the filter row beside\n * \"Pagamento\"/\"Situação\"/\"Método\" instead of floating above the grid.\n *\n * Before this the inline bar rendered pills only, and `rangeFields` had a\n * surface on the slide-in panel alone — which `inlineFilters` REPLACES on a wide\n * screen. So a range filter was invisible on exactly the screens that use it,\n * and a page wanting one had to hang its own inputs outside the grid, outside\n * `DataViewState`, and outside the grid's \"Limpar filtros\" (FUT-668).\n */\nimport CloseIcon from '@mui/icons-material/Close';\nimport ChevronDownIcon from '@mui/icons-material/KeyboardArrowDown';\nimport Box from '@mui/material/Box/index.js';\nimport Button from '@mui/material/Button/index.js';\nimport Popover from '@mui/material/Popover/index.js';\nimport Typography from '@mui/material/Typography/index.js';\nimport React, { useState } from 'react';\n\nimport { useDataViewsCopy } from \"./data-views-copy-context\";\nimport { TableFilter } from '../../layout/TableFilter';\nimport { DayBoundInput } from './data-views-day-input';\nimport { NumberBoundInput } from './data-views-number-input';\nimport { RangePresets } from './data-views-preset-chips';\nimport { presetsFor } from './data-views-range-presets';\nimport {\n isRangeInverted,\n isRangeSet,\n numericRange,\n rangeChipLabel,\n} from './data-views-range-values';\nimport type { RangeFieldConfig, RangeValue } from './data-views-types';\nimport { fieldRadiusPx } from '../../../tokens/field-radius';\nimport { fieldHeight } from '../../../tokens/field-height';\nimport { fieldEdge } from '../../../tokens/field-edge';\nimport { sxRem } from '../../../tokens/relative';\n\n\n\n/**\n * One end of the range. A day keeps its `AAAA-MM-DD` string verbatim (the form\n * both the field and the backend speak) and is typed through the masked\n * `dd/mm/aaaa` field rather than a native date input — see\n * {@link DayBoundInput} for why. A number parses, and an unparseable/blank\n * entry CLEARS the bound rather than writing `NaN`.\n */\nfunction BoundField<T extends Record<string, unknown>>({\n field,\n label,\n value,\n onChange,\n testId,\n}: {\n field: RangeFieldConfig<T>;\n label: string;\n value: number | string | undefined;\n onChange: (bound: number | string | undefined) => void;\n testId: string;\n}): React.JSX.Element {\n const copy = useDataViewsCopy();\n if (field.kind === 'day') {\n return (\n <DayBoundInput\n mask={copy.filters.dayMask}\n label={label}\n value={value == null ? undefined : String(value)}\n onChange={onChange}\n testId={testId}\n />\n );\n }\n return (\n <NumberBoundInput\n label={label}\n value={value}\n unit={field.unit}\n onChange={onChange}\n testId={testId}\n />\n );\n}\n\n\n/**\n * The `De`/`Até` pair for a range — shared by the pill's popover and the\n * slide-in panel's day fields, so the two surfaces cannot drift on what a bound\n * accepts or how it clears.\n */\nexport function RangeBounds<T extends Record<string, unknown>>({\n field,\n value,\n onChange,\n testId,\n}: {\n field: RangeFieldConfig<T>;\n value: RangeValue;\n onChange: (range: RangeValue) => void;\n testId: string;\n}): React.JSX.Element {\n const copy = useDataViewsCopy();\n const inverted = isRangeInverted(value);\n return (\n <Box data-testid={`${testId}-panel`}>\n {/* Above the inputs, not below: the presets are the answer for most\n merchants, and the two date pickers are the escape hatch. */}\n <Box sx={{ '&:not(:empty)': { mb: 1.5 } }}>\n <RangePresets presets={presetsFor(field, copy)} value={value} onChange={onChange} testId={testId} />\n </Box>\n <Box sx={{ display: 'flex', alignItems: 'center', gap: 1.5 }}>\n <BoundField\n field={field}\n label={copy.filters.rangeStart}\n value={value.min}\n onChange={(min) => onChange({ ...value, min })}\n testId={`${testId}-min`}\n />\n <BoundField\n field={field}\n label={copy.filters.rangeEnd}\n value={value.max}\n onChange={(max) => onChange({ ...value, max })}\n testId={`${testId}-max`}\n />\n </Box>\n {inverted && (\n <Typography\n component=\"p\"\n data-testid={`${testId}-inverted`}\n sx={{ mt: 0.75, fontSize: sxRem(12), color: 'error.main' }}\n >\n {copy.filters.rangeInvalid}\n </Typography>\n )}\n {/* BOTH days are in the result. Saying so is FUT-668's other half: the\n bounds compare as ISO strings precisely so `até` keeps its whole day,\n and nothing on screen would otherwise tell the merchant that. */}\n {field.kind === 'day' && !inverted && (\n <Typography\n component=\"p\"\n data-testid={`${testId}-inclusive`}\n sx={{ mt: 0.75, fontSize: sxRem(12), color: 'text.disabled' }}\n >\n {copy.filters.rangeInclusiveNote}\n </Typography>\n )}\n </Box>\n );\n}\n\n/**\n * The pill + its popover. Bounds commit on change (the list is server-driven, so\n * each edit is a query) and the popover stays open, which is what lets a\n * merchant set \"de\" and then \"até\" without re-opening it.\n */\n/** An applied range clears from the pill itself — re-opening to empty two fields is three gestures for one intent. */\nfunction ClearAffordance({\n label,\n onClear,\n testId,\n}: {\n label: string;\n onClear: () => void;\n testId: string;\n}): React.JSX.Element {\n const copy = useDataViewsCopy();\n const clear = (event: React.SyntheticEvent): void => {\n event.stopPropagation();\n onClear();\n };\n return (\n <Box\n component=\"span\"\n role=\"button\"\n tabIndex={0}\n aria-label={copy.filters.clearRange(label)}\n data-testid={testId}\n onClick={clear}\n onKeyDown={(event: React.KeyboardEvent) => {\n if (event.key !== 'Enter' && event.key !== ' ') return;\n event.preventDefault();\n clear(event);\n }}\n sx={{ display: 'inline-flex', borderRadius: 999, p: 0.125, '&:hover': { bgcolor: 'action.selected' } }}\n >\n <CloseIcon sx={{ fontSize: sxRem(14) }} />\n </Box>\n );\n}\n\n/** The pill button: its label carries the applied window, and its tone the state. */\nfunction RangeTrigger<T extends Record<string, unknown>>({\n field,\n value,\n onChange,\n onOpen,\n open,\n testId,\n}: {\n field: RangeFieldConfig<T>;\n value: RangeValue;\n onChange: (range: RangeValue) => void;\n onOpen: (anchor: HTMLElement) => void;\n open: boolean;\n testId: string;\n}): React.JSX.Element {\n const active = isRangeSet(value);\n const inverted = isRangeInverted(value);\n // An INVERTED window gets its own tone rather than the ordinary \"applied\"\n // one: it is applied, and it matches nothing, and those are different states.\n // At rest the pill draws the one field border (`fieldEdge`).\n const tone = inverted ? 'error.main' : active ? 'primary.main' : null;\n return (\n <Button\n variant=\"outlined\"\n color=\"inherit\"\n size=\"small\"\n data-testid={testId}\n aria-haspopup=\"dialog\"\n aria-expanded={open}\n onClick={(event) => onOpen(event.currentTarget)}\n // ✕ FIRST, chevron LAST — the same shape as every other pill. It used to\n // replace the chevron at the end, so a range pill and a multi-select pill\n // put the clear in different places, and an applied range lost the\n // affordance that says it opens.\n startIcon={\n active ? (\n <ClearAffordance label={field.label} onClear={() => onChange({})} testId={`${testId}-clear-inline`} />\n ) : undefined\n }\n endIcon={<ChevronDownIcon sx={{ fontSize: sxRem(16) }} />}\n sx={{\n borderRadius: fieldRadiusPx,\n height: fieldHeight,\n px: 1.5,\n maxWidth: sxRem(280),\n color: inverted ? 'error.main' : 'text.primary',\n fontWeight: 600,\n fontSize: sxRem(13),\n textTransform: 'none',\n whiteSpace: 'nowrap',\n borderColor: (theme) => tone ?? fieldEdge(theme),\n bgcolor: active ? 'action.selected' : 'background.paper',\n '&:hover': {\n borderColor: active ? tone : 'text.primary',\n bgcolor: active ? 'action.selected' : 'action.hover',\n },\n }}\n >\n {/* The applied bounds, in the label — a pill reading only \"Valor\" hides\n the very thing that is narrowing the list. */}\n <Box component=\"span\" sx={{ overflow: 'hidden', textOverflow: 'ellipsis' }}>\n {active ? rangeChipLabel(field, value) : field.label}\n </Box>\n </Button>\n );\n}\n\nexport function RangePill<T extends Record<string, unknown>>({\n field,\n value,\n onChange,\n onOpenChange,\n testIdPrefix,\n}: {\n field: RangeFieldConfig<T>;\n value: RangeValue;\n onChange: (range: RangeValue) => void;\n /** So a measured toolbar can hold still while this is open. */\n onOpenChange?: (open: boolean) => void;\n testIdPrefix: string;\n}): React.JSX.Element {\n const copy = useDataViewsCopy();\n const [anchor, setAnchor] = useState<HTMLElement | null>(null);\n const active = isRangeSet(value);\n const testId = `${testIdPrefix}-range-${field.id}`;\n return (\n <>\n <RangeTrigger\n field={field}\n value={value}\n onChange={onChange}\n onOpen={(element) => {\n setAnchor(element);\n onOpenChange?.(true);\n }}\n open={Boolean(anchor)}\n testId={testId}\n />\n <Popover\n open={Boolean(anchor)}\n anchorEl={anchor}\n onClose={() => {\n setAnchor(null);\n onOpenChange?.(false);\n }}\n anchorOrigin={{ vertical: 'bottom', horizontal: 'left' }}\n slotProps={{ paper: { sx: { p: 2, mt: 0.5 } } }}\n >\n <RangeBounds field={field} value={value} onChange={onChange} testId={testId} />\n {/* An inverted window is not an error the merchant must fix before the\n list responds — it simply matches nothing, and saying so beats an\n empty grid with no explanation (FUT-668). */}\n {active && (\n <Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 2, mt: 1.5 }}>\n <Typography component=\"span\" sx={{ fontSize: sxRem(12), color: 'text.secondary' }}>\n {rangeChipLabel(field, value)}\n </Typography>\n <Button\n variant=\"text\"\n size=\"small\"\n onClick={() => onChange({})}\n data-testid={`${testId}-clear`}\n sx={{ textTransform: 'none' }}\n >\n {copy.filters.clear}\n </Button>\n </Box>\n )}\n </Popover>\n </>\n );\n}\n\n\n/**\n * One range on the SLIDE-IN panel (and the narrow-screen modal): the numeric\n * control the panel already had, or — for a day range, which that control cannot\n * express — the same `De`/`Até` pair the pill's popover uses.\n */\nexport function PanelRangeField<T extends Record<string, unknown>>({\n field,\n value,\n onChange,\n testId,\n}: {\n field: RangeFieldConfig<T>;\n value: RangeValue;\n onChange: (range: RangeValue) => void;\n testId: string;\n}): React.JSX.Element {\n const copy = useDataViewsCopy();\n if (field.kind === \"day\") {\n return (\n <Box sx={{ display: \"flex\", flexDirection: \"column\", gap: 1 }}>\n <Typography component=\"span\" sx={{ fontSize: sxRem(12), fontWeight: 500, color: \"text.secondary\" }}>\n {field.label}\n </Typography>\n <RangeBounds field={field} value={value} onChange={onChange} testId={testId} />\n </Box>\n );\n }\n return (\n <Box sx={{ display: \"flex\", flexDirection: \"column\", gap: 1 }}>\n <TableFilter.RangeField\n label={field.label}\n unit={field.unit}\n value={numericRange(value)}\n onChange={(range) => onChange(range)}\n testId={testId}\n />\n {/* Below the numeric control here, unlike the pill's popover: this one\n renders its own label, and chips above it would separate the label\n from the field it names. */}\n <RangePresets presets={presetsFor(field, copy)} value={value} onChange={onChange} testId={testId} />\n </Box>\n );\n}\n"],"mappings":"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","names":[]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/components/form/total-form/Fields/TextField.tsx"],"sourcesContent":["'use client';\n\nimport React from 'react';\n\nimport { FormControl, FormLabel, FormMessage } from '../../Form';\nimport { Input } from '../../Input';\nimport { useFormContext } from '../FormContext';\n\n/** Props for the `total-form` {@link TextField}. */\nexport interface TextFieldProps {\n /** Field name; also the key into the form's values/errors. */\n name: string;\n /** Optional visible label rendered above the input. */\n label?: string;\n /** Native input type (e.g. `email`, `password`). Defaults to `text`. */\n type?: string;\n /** Placeholder shown when the field is empty. */\n placeholder?: string;\n}\n\n/**\n * Text input field bound to {@link useFormContext}. Composes the existing\n * `@12-apps/ui` `Form*` presentational primitives and the `Input` control,\n * reading its value/error from context and writing changes back through it.\n *\n * Renders its error via `total-form-field-<name>-message` and the input via\n * `total-form-field-<name>`.\n */\nexport function TextField({ name, label, type = 'text', placeholder }: TextFieldProps): React.ReactElement {\n const { values, errors, setFieldValue } = useFormContext();\n const error = errors[name];\n const testId = `total-form-field-${name}`;\n\n return (\n <FormControl fullWidth>\n {label && (\n <FormLabel htmlFor={name} error={Boolean(error)}>\n {label}\n </FormLabel>\n )}\n <Input\n id={name}\n name={name}\n type={type}\n placeholder={placeholder}\n value={values[name] ?? ''}\n error={Boolean(error)}\n data-testid={testId}\n onChange={(event) => setFieldValue(name, event.target.value)}\n />\n {error && (\n <FormMessage error dataTestId={`${testId}-message`}>\n {error}\n </FormMessage>\n )}\n </FormControl>\n );\n}\n"],"mappings":";;;;;;;;;;;;;AAkCI,SAEI,KAFJ;AANG,SAAS,UAAU,EAAE,MAAM,OAAO,OAAO,QAAQ,YAAY,GAAuC;AACzG,QAAM,EAAE,QAAQ,QAAQ,cAAc,IAAI,eAAe;AACzD,QAAM,QAAQ,OAAO,IAAI;AACzB,QAAM,SAAS,oBAAoB,IAAI;AAEvC,SACE,qBAAC,eAAY,WAAS,MACnB;AAAA,aACC,oBAAC,aAAU,SAAS,MAAM,OAAO,QAAQ,KAAK,GAC3C,iBACH;AAAA,IAEF;AAAA,MAAC;AAAA;AAAA,QACC,IAAI;AAAA,QACJ;AAAA,QACA;AAAA,QACA;AAAA,QACA,OAAO,OAAO,IAAI,KAAK;AAAA,QACvB,OAAO,QAAQ,KAAK;AAAA,QACpB,eAAa;AAAA,QACb,UAAU,CAAC,UAAU,cAAc,MAAM,MAAM,OAAO,KAAK;AAAA;AAAA,IAC7D;AAAA,IACC,SACC,oBAAC,eAAY,OAAK,MAAC,YAAY,GAAG,MAAM,YACrC,iBACH;AAAA,KAEJ;AAEJ;","names":[]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/components/data-display/DataViews/use-data-views-state.ts"],"sourcesContent":["\"use client\";\n\nimport { useEffect, useMemo, useRef, useState } from \"react\";\n\nimport { type GridColumn } from \"../DataGrid\";\nimport { type SortFieldDefinition } from \"../../layout/ContentToolbar\";\n\nimport { useDataViewsCopy } from \"./data-views-copy-context\";\nimport { filterRows } from \"./data-views-filter\";\nimport {\n useColumnsWithActions,\n useRenderedColumns,\n type HideableColumn,\n} from \"./data-views-columns\";\nimport { computeActiveFilterCount, defaultSortFields } from \"./data-views-grid-helpers\";\nimport { resolveScope, useScopeConfigChecks, type ScopeConfig } from \"./data-views-scopes\";\nimport { serverDerived, useServerQuery } from \"./data-views-server-query\";\nimport { useSelection } from \"./data-views-selection\";\nimport { useStateWriters } from \"./data-views-writers\";\nimport {\n emptyViewState,\n type DataViewColumn,\n type DataViewServer,\n type DataViewState,\n type DataViewStatePatch,\n type DataViewSyncState,\n type FilterFieldConfig,\n type RangeFieldConfig,\n type DataViewQuery,\n type RangeValue,\n type RowAction,\n} from \"./data-views-types\";\n\nexport type { HideableColumn };\n\n/** Inputs the hook needs to own the view state + derived data. */\ninterface UseDataViewsStateArgs<T extends Record<string, unknown>> {\n rows: T[];\n columns: DataViewColumn<T>[];\n fields: FilterFieldConfig<T>[];\n rangeFields: RangeFieldConfig<T>[];\n getRowId: (row: T) => string | number;\n onRowClick?: (row: T) => void;\n appliedState?: DataViewState;\n /**\n * URL-driven controls (search/pills/ranges/sort) that re-apply on reference\n * change while preserving column visibility — see {@link DataViewSyncState}.\n */\n syncState?: DataViewSyncState;\n onStateChange?: (state: DataViewState) => void;\n testIdPrefix: string;\n sortFields?: SortFieldDefinition[];\n rowActions?: RowAction<T>[];\n rowActionsLeading?: (row: T) => React.ReactNode;\n /** Bespoke per-row menu rendered in the actions column instead of the auto kebab. */\n renderRowMenu?: (row: T) => React.ReactNode;\n /**\n * When present, the table is BACKEND-driven: `rows` is already the current\n * page (no in-browser filter/sort/paginate), and query changes re-fetch via\n * `server.onQueryChange`. Omit for the legacy client-side behaviour.\n */\n server?: DataViewServer;\n /**\n * The page-level partition rendered as tabs under the toolbar. Requires server\n * mode: a scope is applied at the BACKEND, never in the browser.\n */\n scopes?: ScopeConfig[];\n /**\n * The row field the scopes partition by, when the host knows it — used only to\n * reject the pill/scope clash at setup. See `assertNoScopePillOverlap`.\n */\n scopeFieldId?: string;\n}\n\n/** Everything the DataViewsGrid render needs; keeps the component itself thin. */\nexport interface DataViewsController<T extends Record<string, unknown>> {\n state: DataViewState;\n ranges: Record<string, RangeValue>;\n /**\n * Merge a partial into the view state.\n *\n * The UPDATER form is the one to reach for when the patch is derived from\n * state you are also changing — successive calls within one tick each see the\n * previous call's result, where the object form would have every caller build\n * its patch from the same render-scoped snapshot and the last write would win.\n */\n patch: (next: DataViewStatePatch) => void;\n matched: T[];\n gridColumns: GridColumn<T>[];\n /** Columns eligible for the show/hide menu (hideable, with a text header). */\n hideableColumns: HideableColumn[];\n /** Every column id in reading order — what the Colunas tab reorders. */\n columnOrder: string[];\n /** Toggle one column's visibility in the current view state. */\n toggleColumn: (id: string, visible: boolean) => void;\n selectedIds: Set<string | number>;\n setSelectedIds: (ids: Set<string | number>) => void;\n /**\n * Add/remove ONE id from the selection. The card and board bodies both need\n * it, so it lives here rather than being written out twice at the call sites —\n * one selection model, one toggle, identical in every layout.\n */\n toggleId: (id: string | number) => void;\n selectedRows: T[];\n clearSelection: () => void;\n selectAll: () => void;\n filterOpen: boolean;\n setFilterOpen: (open: boolean) => void;\n resolvedSortFields: SortFieldDefinition[];\n activeSortField: string;\n activeSortOrder: \"asc\" | \"desc\";\n activeFilterCount: number;\n /** True when the table is backend-driven (server mode). */\n serverMode: boolean;\n /** Server-mode pagination: 1-based current page (1 when client mode). */\n serverPage: number;\n /** Server-mode pagination: total pages (1 when client mode). */\n serverPageCount: number;\n /** Server-mode: total matched rows across all pages (drives the counter). */\n serverTotalCount: number;\n /** Server-mode: request a specific 1-based page (re-fetches via onQueryChange). */\n changePage: (page: number) => void;\n /**\n * The active scope id, RESOLVED against the declared scopes (see\n * `resolveScope`). `undefined` when the table declares none — which is what\n * keeps `scope` out of the emitted query for every table that never opted in.\n */\n scope: string | undefined;\n /** Select a scope: re-fetches from page 1 and drops the selection. */\n setScope: (id: string) => void;\n /**\n * The query the grid is currently showing. Read by Export, which hands it\n * back to the host UNPAGINATED — so an export follows the filters rather\n * than the 25 rows that happen to be loaded.\n */\n currentQuery: DataViewQuery;\n /**\n * Server-supplied per-scope totals, passed through verbatim, or `undefined`\n * when the server omits them. NEVER falls back to counting `matched`: a count\n * off the loaded page is wrong under pagination, which is the bug scopes fix.\n */\n scopeCounts?: Record<string, number>;\n}\n\n/**\n * The rows to render: in server mode `rows` is already the current page (the\n * backend filtered/sorted/paginated), so it passes through untouched; otherwise\n * the client search/pill/range filter narrows it.\n */\nfunction useMatchedRows<T extends Record<string, unknown>>(\n server: DataViewServer | undefined,\n rows: T[],\n columns: DataViewColumn<T>[],\n fields: FilterFieldConfig<T>[],\n rangeFields: RangeFieldConfig<T>[],\n state: DataViewState,\n): T[] {\n return useMemo(\n () =>\n server\n ? rows\n : filterRows(\n rows,\n columns,\n fields,\n { search: state.search, pills: state.pills, ranges: state.ranges },\n rangeFields,\n ),\n [server, rows, columns, fields, rangeFields, state.search, state.pills, state.ranges],\n );\n}\n\n/**\n * Reconcile external state changes and surface every change to the caller:\n * - `appliedState` (a saved view) fully REPLACES the state — it is columns-\n * authoritative, so applying a view restores its filters, sort AND columns.\n * - `syncState` (URL-driven controls) MERGES over the current state, leaving\n * `visibleColumns` untouched — so browser back/forward or a same-route\n * deep-link re-applies search/pills/sort without wiping a user's hidden columns.\n * Each channel only fires when its own reference changes (after the first run).\n */\nfunction useStateSyncEffects(\n appliedState: DataViewState | undefined,\n syncState: DataViewSyncState | undefined,\n state: DataViewState,\n onStateChange: ((state: DataViewState) => void) | undefined,\n setState: React.Dispatch<React.SetStateAction<DataViewState>>,\n): void {\n const firstApplied = useRef(true);\n useEffect(() => {\n if (firstApplied.current) {\n firstApplied.current = false;\n return;\n }\n if (appliedState) setState(appliedState);\n // `setState` is a stable useState setter (passed in), included to satisfy\n // exhaustive-deps; the effect still only re-applies when `appliedState` changes.\n }, [appliedState, setState]);\n const firstSync = useRef(true);\n useEffect(() => {\n if (firstSync.current) {\n firstSync.current = false;\n return;\n }\n // Merge — keep the current `visibleColumns` (never URL-carried); only the\n // URL-driven controls change.\n if (syncState) setState((prev) => ({ ...prev, ...syncState }));\n }, [syncState, setState]);\n useEffect(() => {\n onStateChange?.(state);\n }, [state, onStateChange]);\n}\n\n/**\n * The seed view state: a saved view (`appliedState`) or a pristine all-columns\n * one, with the URL-driven controls (`syncState`) merged over it so a deep-linked\n * filter or scope is reflected on the FIRST render. Column visibility is never\n * URL-carried, so the merge cannot wipe it.\n */\nfunction seedState(\n appliedState: DataViewState | undefined,\n syncState: DataViewSyncState | undefined,\n allColumnIds: string[],\n): DataViewState {\n const seed = appliedState ?? emptyViewState(allColumnIds);\n return syncState ? { ...seed, ...syncState } : seed;\n}\n\n/**\n * Selecting a scope: record it, then DROP the selection.\n *\n * A scope change renders a different result set. A selection that survived it\n * would let a bulk action run against rows the user can no longer see — the\n * same reason changing any other part of the query clears it.\n */\nfunction makeSetScope(\n patch: (next: DataViewStatePatch) => void,\n clearSelection: () => void,\n): (id: string) => void {\n return (id) => {\n patch({ scope: id });\n clearSelection();\n };\n}\n\n/**\n * The declared column ids in the stored reading order — the same tolerance\n * `applyOrder` uses, so the Colunas tab lists exactly what the grid renders.\n */\nfunction orderedColumnIds(allColumnIds: string[], order: string[] | undefined): string[] {\n if (!order || order.length === 0) return allColumnIds;\n const declared = new Set(allColumnIds);\n const kept = order.filter((id) => declared.has(id));\n const seen = new Set(kept);\n return [...kept, ...allColumnIds.filter((id) => !seen.has(id))];\n}\n\n/**\n * The query the grid is currently SHOWING — the same shape `onQueryChange`\n * emits, kept as a value so a host control (Exportar) can re-run it against the\n * backend rather than reading the loaded rows.\n */\nfunction liveQuery(\n state: DataViewState,\n scope: string | undefined,\n server: DataViewServer | undefined,\n matchedCount: number,\n): DataViewQuery {\n return {\n search: state.search,\n pills: state.pills,\n ranges: state.ranges ?? {},\n sortBy: state.sortBy,\n // Absent, not undefined, when the table declares no scopes.\n ...(scope !== undefined ? { scope } : {}),\n page: server?.page ?? 1,\n // Client mode has one page, and it is everything that matched.\n pageSize: server?.pageSize ?? matchedCount,\n };\n}\n\n/** The toolbar's active sort, read off the state's FIRST sort entry. */\nfunction activeSortOf(\n state: DataViewState,\n resolvedSortFields: SortFieldDefinition[],\n): { activeSortField: string; activeSortOrder: \"asc\" | \"desc\" } {\n const active = state.sortBy?.[0];\n return {\n activeSortField: active?.id ?? resolvedSortFields[0]?.value ?? \"\",\n activeSortOrder: active?.dir === \"desc\" ? \"desc\" : \"asc\",\n };\n}\n\n/**\n * Owns the DataViews view state (search/pills/ranges/sort/visibleColumns),\n * multi-select, the filter-panel toggle, and every derived value (filtered rows,\n * grid columns, active sort/filter counts). Extracted from the component so the\n * render stays within the size/complexity budget.\n */\nexport function useDataViewsState<T extends Record<string, unknown>>({\n rows,\n columns,\n fields,\n rangeFields,\n getRowId,\n onRowClick,\n appliedState,\n syncState,\n onStateChange,\n testIdPrefix,\n sortFields,\n rowActions,\n rowActionsLeading,\n renderRowMenu,\n server,\n scopes = [],\n scopeFieldId,\n}: UseDataViewsStateArgs<T>): DataViewsController<T> {\n useScopeConfigChecks(scopes, fields.map((field) => field.id), scopeFieldId, server !== undefined);\n const copy = useDataViewsCopy();\n const { columnsWithActions, allColumnIds } = useColumnsWithActions(columns, {\n rowActions,\n rowActionsLeading,\n renderRowMenu,\n testIdPrefix,\n getRowId,\n });\n // Seed once — see {@link seedState}.\n const [state, setState] = useState<DataViewState>(() =>\n seedState(appliedState, syncState, allColumnIds),\n );\n const [filterOpen, setFilterOpen] = useState(false);\n\n useStateSyncEffects(appliedState, syncState, state, onStateChange, setState);\n\n const { patch, toggleColumn } = useStateWriters(setState);\n const matched = useMatchedRows(server, rows, columns, fields, rangeFields, state);\n // Resolved at READ time, every render: a stale deep link or a saved view naming\n // a scope that has since been removed falls back to the first declared scope\n // instead of putting an id the backend rejects on the wire.\n const scope = resolveScope(scopes, state.scope);\n // Server mode: re-fetch on query change (page resets to 1) + drive pagination.\n const { changePage } = useServerQuery(server, state, scope);\n const { gridColumns, hideableColumns } = useRenderedColumns(\n columnsWithActions,\n state.visibleColumns,\n onRowClick,\n state.order,\n );\n // Server mode is where `matched` is a PAGE — see `useSelection` (FUT-942).\n const selection = useSelection(matched, getRowId, { rememberOffPage: Boolean(server) });\n const resolvedSortFields = sortFields ?? defaultSortFields(columns, copy.grid);\n\n return {\n state,\n ranges: state.ranges ?? {},\n patch,\n matched,\n gridColumns,\n hideableColumns,\n columnOrder: orderedColumnIds(allColumnIds, state.order),\n toggleColumn,\n ...selection,\n filterOpen,\n setFilterOpen,\n resolvedSortFields,\n ...activeSortOf(state, resolvedSortFields),\n activeFilterCount: computeActiveFilterCount(state, fields, rangeFields),\n ...serverDerived(server, matched.length),\n changePage,\n scope,\n setScope: makeSetScope(patch, selection.clearSelection),\n currentQuery: liveQuery(state, scope, server, matched.length),\n // Verbatim, or absent. See DataViewServer.scopeCounts for why there is no\n // client-side fallback.\n scopeCounts: server?.scopeCounts,\n };\n}\n"],"mappings":"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","names":[]}
|