@12-apps/ui 6.82.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/components-catalog.md +7 -0
- 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-THD3N4BJ.js → chunk-6ONQTXFM.js} +24 -5
- 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-SMANO5OD.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-QCNUMOTL.js → chunk-YC2QYRNE.js} +9 -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 +2 -1
- package/dist/types/components/form/CepField/CepField.d.ts.map +1 -1
- package/dist/types/components/form/CepField/CepField.types.d.ts +24 -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 +11 -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 +29 -2
- package/src/components/form/CepField/CepField.tsx +44 -7
- package/src/components/form/CepField/CepField.types.ts +24 -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 +16 -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-MLGAGZJ3.js.map +0 -1
- package/dist/chunk-QCNUMOTL.js.map +0 -1
- package/dist/chunk-RDRXTH47.js.map +0 -1
- package/dist/chunk-SMANO5OD.js.map +0 -1
- package/dist/chunk-THD3N4BJ.js.map +0 -1
- package/dist/chunk-U5FOJBLG.js.map +0 -1
- package/dist/chunk-UETHX57J.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
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { useEffect, useRef, useState } from 'react';
|
|
4
|
+
|
|
5
|
+
import type { RangeValue } from './data-views-range-types';
|
|
6
|
+
|
|
7
|
+
/** One string per range CONTENT: `{}` is rebuilt on every render upstream. */
|
|
8
|
+
const rangeKeyOf = (range: RangeValue): string => `${range.min ?? ''}|${range.max ?? ''}`;
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Remounts the two bound fields when the range is moved from OUTSIDE them — a
|
|
12
|
+
* "Limpar", a preset, a saved view — and never for the fields' own writes.
|
|
13
|
+
*
|
|
14
|
+
* A field keeps a day it refuses (`31/02/2026`) on screen until the APPLIED
|
|
15
|
+
* bound moves. When a clear empties the other bound, or a bound that was open
|
|
16
|
+
* already, this field's bound does not move, so without a remount the refused
|
|
17
|
+
* day would sit red under a filter that had just been cleared (FUT-4144).
|
|
18
|
+
* Remounting only for outside writes keeps the caret where it is while typing.
|
|
19
|
+
*/
|
|
20
|
+
export function useOutsideResetKey(value: RangeValue): { key: number; emitted: (next: RangeValue) => RangeValue } {
|
|
21
|
+
const [key, setKey] = useState(0);
|
|
22
|
+
const lastWritten = useRef(rangeKeyOf(value));
|
|
23
|
+
const current = rangeKeyOf(value);
|
|
24
|
+
useEffect(() => {
|
|
25
|
+
if (current === lastWritten.current) return;
|
|
26
|
+
lastWritten.current = current;
|
|
27
|
+
setKey((n) => n + 1);
|
|
28
|
+
}, [current]);
|
|
29
|
+
const emitted = (next: RangeValue) => {
|
|
30
|
+
lastWritten.current = rangeKeyOf(next);
|
|
31
|
+
return next;
|
|
32
|
+
};
|
|
33
|
+
return { key, emitted };
|
|
34
|
+
}
|
|
@@ -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,
|
|
@@ -20,6 +20,7 @@ serves a supplier form, a store-location form or a delivery checkout.
|
|
|
20
20
|
- Race-safe: a slow response for a since-edited CEP can never overwrite a newer one
|
|
21
21
|
- Degrades to a plain masked input when the lookup fails, throws, or is absent
|
|
22
22
|
- Accessible status via `role="status"` / `aria-live="polite"` and `aria-busy`
|
|
23
|
+
- Takes `Input`'s `size`, and can float its label inside the box like `Input`
|
|
23
24
|
- Works uncontrolled-by-a-form (props) or bound to `total-form` (`Fields.CepField`)
|
|
24
25
|
|
|
25
26
|
## Props Documentation
|
|
@@ -42,6 +43,15 @@ serves a supplier form, a store-location form or a delivery checkout.
|
|
|
42
43
|
- `placeholder`: Placeholder text (default `00000-000`)
|
|
43
44
|
- `name`: Field name / input id (default `postalCode`)
|
|
44
45
|
- `dataTestId`: Test id for the input (default `cep-field-<name>`)
|
|
46
|
+
- `size`: A step of the `Input` size scale (`xs` | `sm` | `md` | `lg` | `xl`, default
|
|
47
|
+
`md`). Same values and same box heights as `Input`, so a `CepField` at `size="sm"`
|
|
48
|
+
stands level with `sm` `Input`s
|
|
49
|
+
- `labelPlacement`: `above` (default) stacks the label over the box; `inside` draws it
|
|
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`
|
|
45
55
|
|
|
46
56
|
### State Props
|
|
47
57
|
|
|
@@ -83,6 +93,20 @@ const [cep, setCep] = useState('');
|
|
|
83
93
|
/>
|
|
84
94
|
```
|
|
85
95
|
|
|
96
|
+
### Beside compact `Input`s
|
|
97
|
+
|
|
98
|
+
An address form made of `Input`s draws every label inside its box. Opt the CEP in to
|
|
99
|
+
the same treatment, and give it the form's `size`, so the CEP block is no taller than
|
|
100
|
+
its neighbours:
|
|
101
|
+
|
|
102
|
+
```tsx
|
|
103
|
+
<CepField size="sm" labelPlacement="inside" value={cep} onChange={setCep} copy={copy} />
|
|
104
|
+
<Input size="sm" label="Rua" />
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
`labelPlacement` defaults to `above` so a form that never asked keeps the stacked label
|
|
108
|
+
it has always had. `Fields.CepField` takes the same two props.
|
|
109
|
+
|
|
86
110
|
### Bound to `total-form`
|
|
87
111
|
|
|
88
112
|
`Fields.CepField` wires the field to the surrounding `FormContainer` and writes
|
|
@@ -134,5 +158,8 @@ the lookup. The pure helpers (`formatCep`, `isValidCep`) live in
|
|
|
134
158
|
"Buscando endereço…" / "Endereço preenchido pelo CEP" without stealing focus
|
|
135
159
|
- `aria-busy` reflects the in-flight lookup
|
|
136
160
|
- `aria-describedby` links the input to its status region
|
|
137
|
-
- `inputMode="numeric"`
|
|
138
|
-
|
|
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
|
|
163
|
+
- With `labelPlacement="inside"` the label is the `Input`'s own, associated to the input
|
|
164
|
+
by `for`/`id`, so `getByLabelText` and screen readers name the field the same way
|
|
165
|
+
- `autoComplete="postal-code"` (the default) lets the browser's own autofill participate
|
|
@@ -118,6 +118,35 @@ 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
|
+
|
|
127
|
+
/** The stacked label over the box (`labelPlacement="above"`), or nothing when there is no label. */
|
|
128
|
+
function AboveLabel({
|
|
129
|
+
name,
|
|
130
|
+
label,
|
|
131
|
+
error,
|
|
132
|
+
}: {
|
|
133
|
+
name: string;
|
|
134
|
+
label?: string;
|
|
135
|
+
error?: string;
|
|
136
|
+
}): React.ReactElement | null {
|
|
137
|
+
if (!label) return null;
|
|
138
|
+
return (
|
|
139
|
+
<FormLabel htmlFor={name} error={Boolean(error)}>
|
|
140
|
+
{label}
|
|
141
|
+
</FormLabel>
|
|
142
|
+
);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/** The label handed to the `Input`, which draws it in the box: only for `inside`. */
|
|
146
|
+
function insideLabel(label: string | undefined, placement: 'above' | 'inside'): string | undefined {
|
|
147
|
+
return placement === 'inside' ? label : undefined;
|
|
148
|
+
}
|
|
149
|
+
|
|
121
150
|
/**
|
|
122
151
|
* A CEP input that fills the rest of the address in.
|
|
123
152
|
*
|
|
@@ -156,6 +185,9 @@ export function CepField({
|
|
|
156
185
|
name = 'postalCode',
|
|
157
186
|
dataTestId,
|
|
158
187
|
copy,
|
|
188
|
+
size, // undefined reaches `Input`, whose own default is `md`
|
|
189
|
+
labelPlacement = 'above',
|
|
190
|
+
autoComplete,
|
|
159
191
|
}: CepFieldProps): React.ReactElement {
|
|
160
192
|
const testId = dataTestId ?? `cep-field-${name}`;
|
|
161
193
|
|
|
@@ -176,17 +208,22 @@ export function CepField({
|
|
|
176
208
|
|
|
177
209
|
return (
|
|
178
210
|
<FormControl fullWidth>
|
|
179
|
-
{
|
|
180
|
-
<FormLabel htmlFor={name} error={Boolean(error)}>
|
|
181
|
-
{label}
|
|
182
|
-
</FormLabel>
|
|
183
|
-
)}
|
|
211
|
+
{labelPlacement === 'above' && <AboveLabel name={name} label={label} error={error} />}
|
|
184
212
|
<Input
|
|
185
213
|
id={name}
|
|
186
214
|
name={name}
|
|
187
215
|
type="text"
|
|
188
|
-
inputMode
|
|
189
|
-
|
|
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)}
|
|
222
|
+
size={size}
|
|
223
|
+
// `inside`: the label is the Input's own, drawn in the box and floating
|
|
224
|
+
// up on focus or a value — what every other `Input` in the form does.
|
|
225
|
+
// `above` keeps the stacked `FormLabel`, for callers that never opted in.
|
|
226
|
+
label={insideLabel(label, labelPlacement)}
|
|
190
227
|
placeholder={placeholder}
|
|
191
228
|
value={value}
|
|
192
229
|
error={Boolean(error)}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { CepFieldCopy } from "../../../copy";
|
|
2
|
+
import type { InputSize } from "../Input/Input.base";
|
|
2
3
|
/**
|
|
3
4
|
* Types for {@link CepField} — the reusable CEP auto-completer.
|
|
4
5
|
*/
|
|
@@ -73,4 +74,27 @@ export interface CepFieldProps {
|
|
|
73
74
|
name?: string;
|
|
74
75
|
/** Test id for the input. Defaults to `cep-field-<name>`. */
|
|
75
76
|
dataTestId?: string;
|
|
77
|
+
/**
|
|
78
|
+
* A step of the house size scale — the same values, and the same box
|
|
79
|
+
* heights, as {@link Input}'s `size`. Defaults to `md`.
|
|
80
|
+
*/
|
|
81
|
+
size?: InputSize;
|
|
82
|
+
/**
|
|
83
|
+
* Where the label is drawn.
|
|
84
|
+
*
|
|
85
|
+
* - `above` (default): a stacked label over the box, as the field has always
|
|
86
|
+
* rendered. Every existing caller keeps it.
|
|
87
|
+
* - `inside`: the label sits in the box and floats up on focus or a value,
|
|
88
|
+
* exactly as `Input`'s label does. Use it beside `Input`s so the CEP block
|
|
89
|
+
* is the same height as its neighbours.
|
|
90
|
+
*/
|
|
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;
|
|
76
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
|
))}
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
import type { CepFieldCopy } from "../../../../copy";
|
|
4
4
|
import React, { useCallback, useMemo, useRef } from 'react';
|
|
5
5
|
|
|
6
|
+
import type { InputSize } from '../../Input/Input.base';
|
|
6
7
|
import { CepField as BaseCepField, type CepAddress } from '../../CepField';
|
|
7
8
|
import { useFormContext } from '../FormContext';
|
|
8
9
|
|
|
@@ -29,6 +30,15 @@ export interface CepFieldProps {
|
|
|
29
30
|
fills?: CepFills;
|
|
30
31
|
/** Milliseconds to wait after the last keystroke before looking up. */
|
|
31
32
|
debounceMs?: number;
|
|
33
|
+
/** The input's size — the same scale, and box heights, as `Input`. Defaults to `md`. */
|
|
34
|
+
size?: InputSize;
|
|
35
|
+
/** `above` (default) stacks the label over the box; `inside` floats it in the box like `Input`. */
|
|
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;
|
|
32
42
|
}
|
|
33
43
|
|
|
34
44
|
const PARTS: readonly CepPart[] = ['street', 'neighborhood', 'city', 'state'];
|
|
@@ -107,6 +117,9 @@ export function CepField({
|
|
|
107
117
|
fills,
|
|
108
118
|
debounceMs,
|
|
109
119
|
copy,
|
|
120
|
+
size,
|
|
121
|
+
labelPlacement,
|
|
122
|
+
autoComplete,
|
|
110
123
|
}: CepFieldProps): React.ReactElement {
|
|
111
124
|
const { values, errors, setFieldValue } = useFormContext();
|
|
112
125
|
const { write, clearOwned } = useOwnedAutofill();
|
|
@@ -142,6 +155,9 @@ export function CepField({
|
|
|
142
155
|
onResolved={handleResolved}
|
|
143
156
|
onNotFound={clearOwned}
|
|
144
157
|
debounceMs={debounceMs}
|
|
158
|
+
size={size}
|
|
159
|
+
labelPlacement={labelPlacement}
|
|
160
|
+
autoComplete={autoComplete}
|
|
145
161
|
dataTestId={`total-form-field-${name}`}
|
|
146
162
|
/>
|
|
147
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":";;;;;;;;;;;;;;;;;;;;;;;AAEA,OAAO,SAAS;AAChB,OAAO,UAAU;AAmDX,SAqCF,UArCE,KA8CE,YA9CF;AAnBN,SAAS,UAA6C;AAAA,EACpD;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAMsB;AACpB,QAAM,SAAS,GAAG,YAAY,WAAW,MAAM,EAAE;AAIjD,MAAI,MAAM,iBAAiB,MAAM,YAAY,iBAAiB,MAAM,YAAY,YAAY;AAC1F,WACE;AAAA,MAAC;AAAA;AAAA,QACC,SAAS,MAAM;AAAA,QACf,MAAM;AAAA,QACN;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA,QAAO;AAAA;AAAA,IACT;AAAA,EAEJ;AACA,SACE;AAAA,IAAC,YAAY;AAAA,IAAZ;AAAA,MACC,OAAO,MAAM;AAAA,MACb,SAAS,MAAM;AAAA,MACf,UAAU,IAAI,IAAI,QAAQ;AAAA,MAC1B,UAAU,CAAC,OAAO,YAAY,aAAa,MAAM,IAAI,OAAO,OAAO;AAAA,MACnE;AAAA;AAAA,EACF;AAEJ;AAGA,SAAS,eAAkD;AAAA,EACzD;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAAmE;AACjE,QAAM,OAAO,iBAAiB;AAC9B,SACE,iCACE;AAAA;AAAA,MAAC,YAAY;AAAA,MAAZ;AAAA,QACC,OAAO;AAAA,QACP,UAAU;AAAA,QACV,OAAO,KAAK,OAAO;AAAA,QACnB,aAAa,KAAK,OAAO;AAAA,QACzB,QAAQ,GAAG,YAAY;AAAA;AAAA,IACzB;AAAA,KACE,OAAO,SAAS,KAAK,YAAY,SAAS,MAC1C,qBAAC,YAAY,SAAZ,EAAoB,OAAO,KAAK,QAAQ,YACtC;AAAA,aAAO,IAAI,CAAC,UACX;AAAA,QAAC;AAAA;AAAA,UAEC;AAAA,UACA,UAAU,MAAM,MAAM,EAAE,KAAK,CAAC;AAAA,UAC9B;AAAA,UACA;AAAA,UACA;AAAA;AAAA,QALK,MAAM;AAAA,MAMb,CACD;AAAA,MACA,YAAY,IAAI,CAAC,UAChB;AAAA,QAAC;AAAA;AAAA,UAEC;AAAA,UACA,OAAO,OAAO,MAAM,EAAE,KAAK,CAAC;AAAA,UAC5B,UAAU,CAAC,UAAU,cAAc,MAAM,IAAI,KAAK;AAAA,UAClD,QAAQ,GAAG,YAAY,UAAU,MAAM,EAAE;AAAA;AAAA,QAJpC,MAAM;AAAA,MAKb,CACD;AAAA,OACH;AAAA,KAEJ;AAEJ;AAGO,SAAS,gBACd,OACmB;AACnB,QAAM,OAAO,iBAAiB;AAC9B,SACE;AAAA,IAAC,YAAY;AAAA,IAAZ;AAAA,MACC,YAAY,MAAM;AAAA,MAClB,aAAa,GAAG,MAAM,YAAY;AAAA,MAClC,WAAW,KAAK,QAAQ;AAAA,MAExB,8BAAC,kBAAgB,GAAG,OAAO;AAAA;AAAA,EAC7B;AAEJ;AAYO,SAAS,aAAgD;AAAA,EAC9D;AAAA,EACA;AAAA,EACA,GAAG;AACL,GAA4C;AAC1C,QAAM,OAAO,iBAAiB;AAC9B,SACE;AAAA,IAAC;AAAA;AAAA,MACC;AAAA,MACA;AAAA,MACA,iBAAiB,KAAK,QAAQ;AAAA,MAC9B,WAAW,KAAK,IAAI;AAAA,MACpB,SAAS,GAAG,MAAM,YAAY;AAAA,MAC9B,UAAS;AAAA,MACT,YAAY,GAAG,MAAM,YAAY;AAAA,MAEjC,8BAAC,uBACC,+BAAC,OAAI,IAAI,EAAE,SAAS,QAAQ,eAAe,UAAU,KAAK,IAAI,GAC5D;AAAA,4BAAC,OAAI,IAAI,EAAE,SAAS,QAAQ,gBAAgB,WAAW,GACrD;AAAA,UAAC;AAAA;AAAA,YACC,WAAU;AAAA,YACV,MAAK;AAAA,YACL,WAAU;AAAA,YACV,SAAS,MAAM;AAAA,YACf,eAAa,GAAG,MAAM,YAAY;AAAA,YAClC,IAAI,EAAE,UAAU,MAAM,EAAE,GAAG,OAAO,iBAAiB;AAAA,YAElD,eAAK,QAAQ;AAAA;AAAA,QAChB,GACF;AAAA,QACA,oBAAC,kBAAgB,GAAG,OAAO;AAAA,SAC7B,GACF;AAAA;AAAA,EACF;AAEJ;","names":[]}
|