@12-apps/ui 6.92.1 → 6.93.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.
Files changed (152) hide show
  1. package/components-catalog.md +1 -1
  2. package/dist/_internal/components/data-display/Alert/Alert.js +2 -2
  3. package/dist/_internal/components/data-display/Avatar/Avatar.js +3 -3
  4. package/dist/_internal/components/data-display/Avatar/Avatar.view.js +2 -2
  5. package/dist/_internal/components/data-display/CardKit/card-actions-context.js +3 -3
  6. package/dist/_internal/components/data-display/CardKit/use-remove-confirm.js +5 -5
  7. package/dist/_internal/components/data-display/CardKit/use-row-confirm.js +1 -1
  8. package/dist/_internal/components/data-display/Chart/SpecChart.js +3 -3
  9. package/dist/_internal/components/data-display/Chart/chart-spec.js +2 -2
  10. package/dist/_internal/components/data-display/DataViews/DataViewsGrid.js +9 -9
  11. package/dist/_internal/components/data-display/DataViews/DataViewsTableBase.js +16 -16
  12. package/dist/_internal/components/data-display/DataViews/ManageViewsDialog.js +1 -1
  13. package/dist/_internal/components/data-display/DataViews/base-list-card-geometry.js +1 -1
  14. package/dist/_internal/components/data-display/DataViews/base-list-card-layout.js +1 -1
  15. package/dist/_internal/components/data-display/DataViews/base-list-card.js +1 -1
  16. package/dist/_internal/components/data-display/DataViews/data-views-grid-parts.js +6 -6
  17. package/dist/_internal/components/data-display/DataViews/data-views-inline-bar.js +5 -5
  18. package/dist/_internal/components/data-display/DataViews/data-views-more-fields.js +10 -4
  19. package/dist/_internal/components/data-display/DataViews/data-views-more-filters.js +3 -3
  20. package/dist/_internal/components/data-display/DataViews/data-views-table-parts.js +4 -4
  21. package/dist/_internal/components/data-display/DataViews/use-data-views-state.js +2 -2
  22. package/dist/_internal/components/data-display/DayRuler/DayRuler.js +6 -6
  23. package/dist/_internal/components/data-display/DayRuler/DayRuler.keyboard.js +2 -2
  24. package/dist/_internal/components/data-display/DayRuler/DayRuler.parts.js +2 -2
  25. package/dist/_internal/components/data-display/DayRuler/useRulerDrag.js +2 -2
  26. package/dist/_internal/components/data-display/HoverCard/HoverCard.js +1 -1
  27. package/dist/_internal/components/data-display/Lightbox/Lightbox.js +5 -5
  28. package/dist/_internal/components/data-display/Lightbox/LightboxControls.js +2 -2
  29. package/dist/_internal/components/data-display/Lightbox/LightboxOverlay.js +3 -3
  30. package/dist/_internal/components/data-display/Lightbox/LightboxThumbnails.js +2 -2
  31. package/dist/_internal/components/data-display/RegionMap/RegionMap.js +2 -2
  32. package/dist/_internal/components/data-display/ScheduleGrid/ScheduleGrid.js +1 -1
  33. package/dist/_internal/components/data-display/Sheet/Sheet.hooks.js +2 -2
  34. package/dist/_internal/components/data-display/Sheet/Sheet.js +3 -3
  35. package/dist/_internal/components/data-display/Table/Table.js +3 -3
  36. package/dist/_internal/components/data-display/index.js +35 -35
  37. package/dist/_internal/components/feedback/NotificationStatusNotice/NotificationStatusNotice.js +3 -3
  38. package/dist/_internal/components/feedback/SequenceResolver/SequenceResolver.frame.js +48 -0
  39. package/dist/_internal/components/feedback/SequenceResolver/SequenceResolver.frame.js.map +1 -0
  40. package/dist/_internal/components/feedback/SequenceResolver/SequenceResolver.js +6 -5
  41. package/dist/_internal/components/feedback/SequenceResolver/SequenceResolver.overview.js +4 -4
  42. package/dist/_internal/components/feedback/SequenceResolver/SequenceResolver.parts.js +3 -3
  43. package/dist/_internal/components/feedback/index.js +10 -9
  44. package/dist/_internal/components/index.js +44 -43
  45. package/dist/_internal/components/navigation/AppHeader/AppHeader.details.js +4 -4
  46. package/dist/_internal/components/navigation/index.js +4 -4
  47. package/dist/_internal/user-avatar/user-avatar.js +4 -4
  48. package/dist/charts.js +6 -6
  49. package/dist/{chunk-DBPFKGXX.js → chunk-3YFDGFEK.js} +2 -2
  50. package/dist/{chunk-Y3F2F36I.js → chunk-4QGKR65P.js} +4 -4
  51. package/dist/{chunk-YE7M7URT.js → chunk-6D5AMANO.js} +2 -2
  52. package/dist/{chunk-UDBOGSFO.js → chunk-6HPVCDVU.js} +4 -4
  53. package/dist/{chunk-EOQ6EKTS.js → chunk-7XSCEHGA.js} +7 -7
  54. package/dist/{chunk-B5STMM3R.js → chunk-ALMGYCHY.js} +2 -2
  55. package/dist/{chunk-64UNVW6D.js → chunk-BP3ZKIPE.js} +4 -4
  56. package/dist/{chunk-OCT5VCLP.js → chunk-BQBEN44R.js} +2 -2
  57. package/dist/chunk-CPQJFHNZ.js +63 -0
  58. package/dist/chunk-CPQJFHNZ.js.map +1 -0
  59. package/dist/{chunk-Q3SILZTQ.js → chunk-E2UOEI7N.js} +2 -2
  60. package/dist/{chunk-RZWU2KYP.js → chunk-F5H3WDG7.js} +4 -3
  61. package/dist/chunk-F5H3WDG7.js.map +1 -0
  62. package/dist/{chunk-JVOEVTCZ.js → chunk-HQCZT6TO.js} +5 -5
  63. package/dist/{chunk-ZVHHFLYY.js → chunk-HSVSU7I2.js} +6 -6
  64. package/dist/{chunk-4QR7LM5F.js → chunk-JIEVKHOH.js} +2 -2
  65. package/dist/{chunk-FWTVIRZZ.js → chunk-JVQ56E3U.js} +6 -6
  66. package/dist/{chunk-YOLLISEY.js → chunk-JXK53XJW.js} +9 -9
  67. package/dist/{chunk-PCQ5JWLF.js → chunk-KPUUHAK6.js} +7 -7
  68. package/dist/{chunk-56JF5PZK.js → chunk-LYVTJLCF.js} +4 -4
  69. package/dist/{chunk-NXQ6PXLJ.js → chunk-LZ5PGB7U.js} +2 -2
  70. package/dist/{chunk-EKSJES5E.js → chunk-MAYAJXO6.js} +4 -4
  71. package/dist/{chunk-OCGPOACB.js → chunk-MFX4ASX5.js} +3 -3
  72. package/dist/{chunk-5I75WRXT.js → chunk-N3GLX64O.js} +4 -4
  73. package/dist/{chunk-PEAUGQKU.js → chunk-NQCTANF3.js} +7 -7
  74. package/dist/{chunk-IEUVP6UV.js → chunk-OM6ZT64U.js} +5 -5
  75. package/dist/{chunk-W676GY42.js → chunk-P772HCHI.js} +4 -4
  76. package/dist/{chunk-VWRXSBT2.js → chunk-R52N5UCS.js} +2 -2
  77. package/dist/{chunk-FQNDJITD.js → chunk-RCTHCXS6.js} +2 -2
  78. package/dist/{chunk-WLLUARTC.js → chunk-T2G4LDU5.js} +2 -2
  79. package/dist/{chunk-VCJ72ODU.js → chunk-VJIJCLUX.js} +68 -18
  80. package/dist/chunk-VJIJCLUX.js.map +1 -0
  81. package/dist/{chunk-66VKX6JE.js → chunk-WJMSND5Q.js} +2 -2
  82. package/dist/{chunk-ZCNR4DN5.js → chunk-XBWW3QSI.js} +4 -4
  83. package/dist/{chunk-BXZMV6QC.js → chunk-XFZDO2KB.js} +2 -2
  84. package/dist/{chunk-BOADHYTA.js → chunk-YNZUYZ2O.js} +2 -2
  85. package/dist/{chunk-XRV4QFEQ.js → chunk-ZBUMGMWP.js} +83 -65
  86. package/dist/chunk-ZBUMGMWP.js.map +1 -0
  87. package/dist/data-display/Alert.js +2 -2
  88. package/dist/data-display/Avatar.js +3 -3
  89. package/dist/data-display/CardKit.js +10 -10
  90. package/dist/data-display/Chart.js +6 -6
  91. package/dist/data-display/DataViews.js +21 -21
  92. package/dist/data-display/DayRuler.js +6 -6
  93. package/dist/data-display/HoverCard.js +1 -1
  94. package/dist/data-display/Lightbox.js +5 -5
  95. package/dist/data-display/RegionMap.js +2 -2
  96. package/dist/data-display/ScheduleGrid.js +5 -5
  97. package/dist/data-display/Sheet.js +3 -3
  98. package/dist/data-display/Table.js +3 -3
  99. package/dist/feedback/ConfirmAction.js +3 -3
  100. package/dist/feedback/NotificationStatusNotice.js +3 -3
  101. package/dist/feedback/SequenceResolver.js +6 -5
  102. package/dist/navigation/AppHeader.js +4 -4
  103. package/dist/types/components/data-display/DataViews/data-views-more-fields.d.ts +80 -0
  104. package/dist/types/components/data-display/DataViews/data-views-more-fields.d.ts.map +1 -1
  105. package/dist/types/components/data-display/DataViews/data-views-more-filters.d.ts.map +1 -1
  106. package/dist/types/components/feedback/SequenceResolver/SequenceResolver.d.ts +55 -2
  107. package/dist/types/components/feedback/SequenceResolver/SequenceResolver.d.ts.map +1 -1
  108. package/dist/types/components/feedback/SequenceResolver/SequenceResolver.frame.d.ts +16 -0
  109. package/dist/types/components/feedback/SequenceResolver/SequenceResolver.frame.d.ts.map +1 -0
  110. package/dist/types/components/feedback/SequenceResolver/SequenceResolver.types.d.ts +10 -4
  111. package/dist/types/components/feedback/SequenceResolver/SequenceResolver.types.d.ts.map +1 -1
  112. package/dist/user-avatar.js +4 -4
  113. package/package.json +1 -1
  114. package/src/components/data-display/DataViews/data-views-more-fields.tsx +90 -14
  115. package/src/components/data-display/DataViews/data-views-more-filters.tsx +2 -2
  116. package/src/components/feedback/SequenceResolver/SequenceResolver.frame.tsx +71 -0
  117. package/src/components/feedback/SequenceResolver/SequenceResolver.md +2 -2
  118. package/src/components/feedback/SequenceResolver/SequenceResolver.tsx +122 -61
  119. package/src/components/feedback/SequenceResolver/SequenceResolver.types.ts +10 -4
  120. package/dist/chunk-RZWU2KYP.js.map +0 -1
  121. package/dist/chunk-VCJ72ODU.js.map +0 -1
  122. package/dist/chunk-XRV4QFEQ.js.map +0 -1
  123. /package/dist/{chunk-DBPFKGXX.js.map → chunk-3YFDGFEK.js.map} +0 -0
  124. /package/dist/{chunk-Y3F2F36I.js.map → chunk-4QGKR65P.js.map} +0 -0
  125. /package/dist/{chunk-YE7M7URT.js.map → chunk-6D5AMANO.js.map} +0 -0
  126. /package/dist/{chunk-UDBOGSFO.js.map → chunk-6HPVCDVU.js.map} +0 -0
  127. /package/dist/{chunk-EOQ6EKTS.js.map → chunk-7XSCEHGA.js.map} +0 -0
  128. /package/dist/{chunk-B5STMM3R.js.map → chunk-ALMGYCHY.js.map} +0 -0
  129. /package/dist/{chunk-64UNVW6D.js.map → chunk-BP3ZKIPE.js.map} +0 -0
  130. /package/dist/{chunk-OCT5VCLP.js.map → chunk-BQBEN44R.js.map} +0 -0
  131. /package/dist/{chunk-Q3SILZTQ.js.map → chunk-E2UOEI7N.js.map} +0 -0
  132. /package/dist/{chunk-JVOEVTCZ.js.map → chunk-HQCZT6TO.js.map} +0 -0
  133. /package/dist/{chunk-ZVHHFLYY.js.map → chunk-HSVSU7I2.js.map} +0 -0
  134. /package/dist/{chunk-4QR7LM5F.js.map → chunk-JIEVKHOH.js.map} +0 -0
  135. /package/dist/{chunk-FWTVIRZZ.js.map → chunk-JVQ56E3U.js.map} +0 -0
  136. /package/dist/{chunk-YOLLISEY.js.map → chunk-JXK53XJW.js.map} +0 -0
  137. /package/dist/{chunk-PCQ5JWLF.js.map → chunk-KPUUHAK6.js.map} +0 -0
  138. /package/dist/{chunk-56JF5PZK.js.map → chunk-LYVTJLCF.js.map} +0 -0
  139. /package/dist/{chunk-NXQ6PXLJ.js.map → chunk-LZ5PGB7U.js.map} +0 -0
  140. /package/dist/{chunk-EKSJES5E.js.map → chunk-MAYAJXO6.js.map} +0 -0
  141. /package/dist/{chunk-OCGPOACB.js.map → chunk-MFX4ASX5.js.map} +0 -0
  142. /package/dist/{chunk-5I75WRXT.js.map → chunk-N3GLX64O.js.map} +0 -0
  143. /package/dist/{chunk-PEAUGQKU.js.map → chunk-NQCTANF3.js.map} +0 -0
  144. /package/dist/{chunk-IEUVP6UV.js.map → chunk-OM6ZT64U.js.map} +0 -0
  145. /package/dist/{chunk-W676GY42.js.map → chunk-P772HCHI.js.map} +0 -0
  146. /package/dist/{chunk-VWRXSBT2.js.map → chunk-R52N5UCS.js.map} +0 -0
  147. /package/dist/{chunk-FQNDJITD.js.map → chunk-RCTHCXS6.js.map} +0 -0
  148. /package/dist/{chunk-WLLUARTC.js.map → chunk-T2G4LDU5.js.map} +0 -0
  149. /package/dist/{chunk-66VKX6JE.js.map → chunk-WJMSND5Q.js.map} +0 -0
  150. /package/dist/{chunk-ZCNR4DN5.js.map → chunk-XBWW3QSI.js.map} +0 -0
  151. /package/dist/{chunk-BXZMV6QC.js.map → chunk-XFZDO2KB.js.map} +0 -0
  152. /package/dist/{chunk-BOADHYTA.js.map → chunk-YNZUYZ2O.js.map} +0 -0
@@ -1,21 +1,18 @@
1
1
  'use client';
2
2
 
3
- import Box from '@mui/material/Box/index.js';
4
3
  import { useTheme } from '@mui/material/styles/index.js';
5
4
  import useMediaQuery from '@mui/material/useMediaQuery/index.js';
6
- import React, { useCallback, useId, useRef, useState } from 'react';
5
+ import React, { useCallback, useEffect, useId, useRef, useState } from 'react';
7
6
 
8
- import { Dialog } from '../Dialog';
9
7
  import { canReturn, currentOf, jumpTargetOf, positionOf, progressOf, summaryOf, type RunState } from './sequence-model';
10
8
  import {
11
9
  SequenceDoneBody,
12
10
  SequenceIntroBody,
13
11
  SequenceStepBody,
14
12
  } from './SequenceResolver.parts';
15
- import { SequenceHeader } from './SequenceResolver.header';
16
13
  import { SequenceOverviewBody } from './SequenceResolver.overview';
14
+ import { ResolverFrame } from './SequenceResolver.frame';
17
15
  import { SEQUENCE_METRICS } from './SequenceResolver.metrics';
18
- import { chromeSx, columnSx, paperSx, scrollBodySx } from './SequenceResolver.styles';
19
16
  import type {
20
17
  SequenceItem,
21
18
  SequenceOverviewCategory,
@@ -124,19 +121,6 @@ function headerOf<I extends SequenceItem, R>(inputs: HeaderInputs<I, R>) {
124
121
  return { ...base, back: backOf(props, state), seeAll: overview.button };
125
122
  }
126
123
 
127
- /**
128
- * Esc closes. `persistent` turns MUI's own Esc off together with the veil's
129
- * click (the prototype's veil does nothing), so Esc is put back here; a popup
130
- * inside a step that handles its own Esc stops it before it gets here.
131
- */
132
- function escapeCloses(close: () => void) {
133
- return (event: React.KeyboardEvent): void => {
134
- if (event.key !== 'Escape' || event.defaultPrevented) return;
135
- event.stopPropagation();
136
- close();
137
- };
138
- }
139
-
140
124
  interface BodyProps<I extends SequenceItem, R> {
141
125
  props: SequenceResolverProps<I, R>;
142
126
  state: SequenceRun<I, R>;
@@ -193,6 +177,8 @@ function SequenceBody<I extends SequenceItem, R>(body: BodyProps<I, R>): React.J
193
177
  );
194
178
  }
195
179
  if (stepBody) return <>{stepBody}</>;
180
+ // `done: null`: no end screen — the resolver is closing over its last step (see `useExitScreen`).
181
+ if (props.done === null) return <></>;
196
182
  const summary = summaryOf(state.run);
197
183
  return (
198
184
  <SequenceDoneBody
@@ -207,10 +193,94 @@ function SequenceBody<I extends SequenceItem, R>(body: BodyProps<I, R>): React.J
207
193
  );
208
194
  }
209
195
 
196
+ /**
197
+ * With `done: null`, the run's end closes the resolver: `onClose(summary)`
198
+ * once, when the queue runs out while it is open and past the intro. Returns
199
+ * whether the run has ended that way, so the dialog starts closing in the same
200
+ * render (and keeps its last screen for the exit, see `useExitScreen`).
201
+ *
202
+ * `fired` makes it once per ending: StrictMode replays a mount's effects, and
203
+ * a resolver mounted open over an empty queue would otherwise close twice. It
204
+ * resets when the run is live again (a reopening, a queue that grew back).
205
+ */
206
+ function useCloseAtEnd(ended: boolean, close: () => void): boolean {
207
+ const closeRef = useRef(close);
208
+ const fired = useRef(false);
209
+ useEffect(() => {
210
+ closeRef.current = close;
211
+ });
212
+ useEffect(() => {
213
+ if (!ended) {
214
+ fired.current = false;
215
+ return;
216
+ }
217
+ if (fired.current) return;
218
+ fired.current = true;
219
+ closeRef.current();
220
+ }, [ended]);
221
+ return ended;
222
+ }
223
+
224
+ export interface Screen {
225
+ header: ReturnType<typeof headerOf>;
226
+ body: React.JSX.Element;
227
+ }
228
+
229
+ /**
230
+ * What the dialog shows. While a step is on screen, that screen — and it is
231
+ * kept: when the run ends with `done: null` the dialog closes in that same
232
+ * render, and its exit transition (a fade, the sheet sliding down) plays over
233
+ * the LAST step as it was — its label, Back, progress and body — rather than
234
+ * an empty, collapsed "done" paper — also once the host turns `open` off from
235
+ * `onClose`. Back is inert during the exit.
236
+ */
237
+ function useExitScreen(live: Screen, input: { open: boolean; hasStep: boolean; closesAtEnd: boolean }): Screen {
238
+ const { open, hasStep, closesAtEnd } = input;
239
+ const last = useRef<Screen | null>(null);
240
+ const wasOpen = useRef(open);
241
+ // A new opening is a new run: the last one's screen is not kept into it.
242
+ if (open && !wasOpen.current) last.current = null;
243
+ wasOpen.current = open;
244
+ if (!closesAtEnd) return live;
245
+ if (open && hasStep) {
246
+ last.current = live;
247
+ return live;
248
+ }
249
+ // Ended (or closed by the host after `onClose`): the exit plays over the last step.
250
+ const kept = last.current;
251
+ if (!kept) return live;
252
+ const { header } = kept;
253
+ if (!('back' in header) || !header.back) return kept;
254
+ return { ...kept, header: { ...header, back: { ...header.back, onClick: () => undefined } } };
255
+ }
256
+
257
+ interface RunEnding<I extends SequenceItem> {
258
+ current: I | undefined;
259
+ close: () => void;
260
+ /** The run ended with `done: null`: the dialog is closing. */
261
+ ended: boolean;
262
+ /** The kept last step is playing its exit. */
263
+ exiting: boolean;
264
+ }
265
+
266
+ function useRunEnding<I extends SequenceItem, R>(
267
+ props: SequenceResolverProps<I, R>,
268
+ state: SequenceRun<I, R>,
269
+ ): RunEnding<I> {
270
+ const { open, onClose, intro } = props;
271
+ const { run } = state;
272
+ const current = intro ? undefined : currentOf(run);
273
+ const close = (): void => onClose(summaryOf(run));
274
+ const closesAtEnd = props.done === null && !intro;
275
+ const ended = useCloseAtEnd(open && closesAtEnd && !current, close);
276
+ return { current, close, ended, exiting: ended || (!open && closesAtEnd) };
277
+ }
278
+
210
279
  /**
211
280
  * Resolve a queue of problems one at a time, in ONE dialog whose body is
212
281
  * replaced step by step: a header with "‹ Back", the step's label and close,
213
- * a progress bar, the host's step, and an end screen (FUT-3671).
282
+ * a progress bar, the host's step, and an end screen (FUT-3671) — or none,
283
+ * with `done: null`: the run's end closes it (FUT-3833).
214
284
  *
215
285
  * A centred `Dialog` from the `sm` breakpoint up; the `Dialog` inset
216
286
  * bottom-sheet variant below it, never taller than 95% of the viewport. Either
@@ -223,7 +293,7 @@ function SequenceBody<I extends SequenceItem, R>(body: BodyProps<I, R>): React.J
223
293
  export function SequenceResolver<I extends SequenceItem, R = unknown>(
224
294
  props: SequenceResolverProps<I, R>,
225
295
  ): React.JSX.Element {
226
- const { open, onClose, intro, labels } = props;
296
+ const { open, intro, labels } = props;
227
297
  const testId = props.dataTestId ?? 'sequence-resolver';
228
298
  const theme = useTheme();
229
299
  const touch = useMediaQuery(theme.breakpoints.down(SEQUENCE_METRICS.sheetBreakpoint));
@@ -244,53 +314,44 @@ export function SequenceResolver<I extends SequenceItem, R = unknown>(
244
314
  failed: labels.failed,
245
315
  stepAllowsBack: () => stepAllowsBack.current,
246
316
  });
247
- const { run } = state;
248
- const current = intro ? undefined : currentOf(run);
249
- const close = (): void => onClose(summaryOf(run));
317
+ const ending = useRunEnding(props, state);
318
+ const { current, close } = ending;
250
319
  const overview = useOverview(props, state, current, open);
251
320
  // Under the overview the step is kept but hidden, and the overview's title names the dialog.
252
321
  const titleProps = { id: overview.shown ? `${titleId}-kept` : titleId, tabIndex: -1, ref: titleRef } as const;
253
322
  const step = current ? props.renderStep(current, stepApiOf(state, titleProps)) : undefined;
254
323
  stepAllowsBack.current = step?.canGoBack !== false;
324
+ const screen = useExitScreen(
325
+ {
326
+ header: headerOf({ props, state, current, overview, titleId }),
327
+ body: (
328
+ <SequenceBody
329
+ props={props}
330
+ state={state}
331
+ current={current}
332
+ step={step}
333
+ overview={overview}
334
+ titleId={titleId}
335
+ stepTitleRef={stepTitleRef}
336
+ touch={touch}
337
+ testId={testId}
338
+ close={close}
339
+ />
340
+ ),
341
+ },
342
+ { open, hasStep: current !== undefined && step !== undefined, closesAtEnd: props.done === null && !intro },
343
+ );
255
344
 
256
345
  return (
257
- <Dialog
258
- open={open}
259
- onClose={close}
260
- persistent
261
- variant={touch ? 'bottom-sheet' : 'default'}
262
- size="sm"
263
- aria-labelledby={titleId}
264
- onKeyDown={escapeCloses(close)}
265
- slotProps={{ paper: { sx: paperSx(touch) } }}
266
- dataTestId={testId}
267
- >
268
- <Box sx={columnSx}>
269
- <Box sx={chromeSx}>
270
- <SequenceHeader
271
- touch={touch}
272
- {...headerOf({ props, state, current, overview, titleId })}
273
- closeLabel={labels.close}
274
- onClose={close}
275
- progressLabel={labels.progress}
276
- testId={testId}
277
- />
278
- </Box>
279
- <Box sx={scrollBodySx} data-testid={`${testId}-body`}>
280
- <SequenceBody
281
- props={props}
282
- state={state}
283
- current={current}
284
- step={step}
285
- overview={overview}
286
- titleId={titleId}
287
- stepTitleRef={stepTitleRef}
288
- touch={touch}
289
- testId={testId}
290
- close={close}
291
- />
292
- </Box>
293
- </Box>
294
- </Dialog>
346
+ <ResolverFrame
347
+ shown={open && !ending.ended}
348
+ exiting={ending.exiting}
349
+ touch={touch}
350
+ titleId={titleId}
351
+ testId={testId}
352
+ labels={labels}
353
+ screen={screen}
354
+ close={close}
355
+ />
295
356
  );
296
357
  }
@@ -196,8 +196,9 @@ export interface SequenceResolverLabels {
196
196
  export interface SequenceResolverProps<I extends SequenceItem = SequenceItem, R = unknown> {
197
197
  open: boolean;
198
198
  /**
199
- * The close button, Esc, and the end screen's button. Gets where the run
200
- * stood, for the host's "N resolved · Undo" toast.
199
+ * The close button, Esc, and the end screen's button — or, with `done:
200
+ * null`, the queue running out. Gets where the run stood, for the host's
201
+ * "N resolved · Undo" toast.
201
202
  */
202
203
  onClose: (summary: SequenceSummary) => void;
203
204
  /**
@@ -228,8 +229,13 @@ export interface SequenceResolverProps<I extends SequenceItem = SequenceItem, R
228
229
  overview?: SequenceOverview;
229
230
  /** Open the run at this problem (a click on its block); the ones before it follow it. */
230
231
  initialKey?: string;
231
- /** The end screen's words, from the run's summary. */
232
- done: SequenceDone | ((summary: SequenceSummary) => SequenceDone);
232
+ /**
233
+ * The end screen's words, from the run's summary. `null` drops the end
234
+ * screen: when the queue runs out — the last step answered or passed over —
235
+ * the resolver calls `onClose(summary)` at once, and the host's toast is the
236
+ * run's last word (FUT-3833).
237
+ */
238
+ done: SequenceDone | ((summary: SequenceSummary) => SequenceDone) | null;
233
239
  labels: SequenceResolverLabels;
234
240
  /**
235
241
  * Base test id (default `sequence-resolver`). Parts get `${id}-back`,
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/components/data-display/DataViews/data-views-more-filters.tsx"],"sourcesContent":["\"use client\";\n\nimport KeyboardArrowDownRoundedIcon from \"@mui/icons-material/KeyboardArrowDownRounded\";\n// A FUNNEL, not the tune sliders this used to share with \"Exibir\". The two\n// sit a few px apart on the same bar and do unrelated things — one narrows\n// WHICH rows you see, the other changes HOW they are shown — and with one\n// glyph between them the bar offered the operator no way to tell which was\n// which. On a phone, where both are icon-only, they were indistinguishable.\nimport FilterAltRoundedIcon from \"@mui/icons-material/FilterAltRounded\";\nimport Popover from \"@mui/material/Popover/index.js\";\nimport { useTheme, type Theme } from \"@mui/material/styles/index.js\";\nimport useMediaQuery from \"@mui/material/useMediaQuery/index.js\";\n\n\nimport { useDataViewsCopy } from \"./data-views-copy-context\";\nimport { Button } from \"../../form/Button\";\nimport { Box } from \"../../../mui/Box\";\nimport { Text } from \"../../typography/Text\";\n\nimport { fieldClearing, MoreGroup, MoreQuick, type MoreFieldProps } from \"./data-views-more-fields\";\nimport { useStillAnchor } from \"./data-views-still-anchor\";\nimport type { OverflowField } from \"./data-views-overflow\";\nimport { rem, remPx, sxRem } from \"../../../tokens/relative\";\n\n/**\n * \"MAIS\" — the filter overflow: its trigger, its panel and its footer. What\n * goes INSIDE it is `data-views-more-fields`.\n *\n * It holds the fields that had no room on the toolbar. An applied filter is\n * ranked ahead of an idle one for the visible slots but is NOT exempt (see\n * `useFilterOverflow`), so an applied filter can be in here — on a phone, most\n * of them are. That is why the trigger changes tone and counts the applied\n * ones: a filter you cannot see is a filter you forget you set, and the badge\n * is what stops that.\n */\n\ninterface MoreFiltersProps<T extends Record<string, unknown>> extends MoreFieldProps {\n fields: OverflowField<T>[];\n}\n\n/**\n * The trigger, badged with how many fields had no room on the bar — and told\n * apart when some of them are APPLIED.\n *\n * An applied filter can land in here now (it is ranked first, not exempt), so\n * this button is the only thing on screen still saying it exists. A neutral\n * badge reading \"3\" would make \"three filters you have not used\" and \"three\n * filters narrowing this list\" look identical, which is the whole failure the\n * old exemption was written to avoid. So an overflow holding applied filters\n * takes the applied tone and counts them in its own badge.\n */\nfunction MoreTrigger({\n count,\n appliedCount,\n compact,\n onOpen,\n testIdPrefix,\n}: {\n count: number;\n appliedCount: number;\n /** Icon only — the same rung that strips Exibir/Exportar. */\n compact: boolean;\n onOpen: (anchor: HTMLElement) => void;\n testIdPrefix: string;\n}): React.JSX.Element {\n const copy = useDataViewsCopy();\n const applied = appliedCount > 0;\n return (\n <Button\n variant=\"outline\"\n size=\"sm\"\n color=\"neutral\"\n onClick={(event) => onOpen(event.currentTarget as HTMLElement)}\n dataTestId={`${testIdPrefix}-more-filters`}\n aria-label={\n applied\n ? copy.filters.moreFiltersApplied(count, appliedCount)\n : copy.filters.moreFilters(count)\n }\n >\n <Box\n component=\"span\"\n sx={{\n display: \"inline-flex\",\n alignItems: \"center\",\n gap: 0.5,\n color: applied ? \"primary.main\" : undefined,\n }}\n >\n <FilterAltRoundedIcon fontSize=\"small\" />\n {!compact && (\n <Box component=\"span\" sx={{ display: { xs: \"none\", sm: \"inline\" } }}>\n {copy.filters.moreTriggerLabel}\n </Box>\n )}\n <Box\n component=\"span\"\n data-testid={`${testIdPrefix}-more-badge`}\n sx={{\n px: 0.75,\n borderRadius: 5,\n bgcolor: applied ? \"primary.main\" : \"action.selected\",\n color: applied ? \"primary.contrastText\" : undefined,\n fontSize: sxRem(11),\n fontWeight: applied ? 700 : undefined,\n }}\n >\n {applied ? appliedCount : count}\n </Box>\n {!compact && <KeyboardArrowDownRoundedIcon fontSize=\"small\" />}\n </Box>\n </Button>\n );\n}\n\n/** The panel's heading: what these are, and why they are in here. */\nfunction MoreHeading(): React.JSX.Element {\n const copy = useDataViewsCopy();\n return (\n <Box\n sx={{\n display: \"flex\",\n alignItems: \"center\",\n gap: 1,\n px: 1.5,\n py: 1,\n borderBottom: 1,\n borderColor: \"divider\",\n }}\n >\n <Text variant=\"caption\" as=\"span\">\n <Box component=\"span\" sx={{ textTransform: \"uppercase\", letterSpacing: sxRem(0.5), color: \"text.disabled\" }}>\n {copy.filters.moreHeading}\n </Box>\n </Text>\n <Text variant=\"caption\" as=\"span\">\n <Box component=\"span\" sx={{ ml: \"auto\", color: \"text.disabled\" }}>\n {copy.filters.overflowNote}\n </Box>\n </Text>\n </Box>\n );\n}\n\n/**\n * The panel's own way back to the unfiltered list.\n *\n * The bar's \"Limpar\" is the primary one, but it is a control like any other\n * and the ladder can take its label — and on the narrowest rungs this panel is\n * where most of the fields ended up anyway. Clearing everything from inside it\n * saves closing it to reach a button three pixels wide.\n */\nfunction MoreFooter({\n onClearAll,\n testIdPrefix,\n reserved = false,\n}: {\n onClearAll: () => void;\n testIdPrefix: string;\n /**\n * Hold the footer's height with nothing in it. A panel holding a day range\n * keeps the row while nothing is applied, so the first tap on its calendar\n * does not grow the panel by a row and move its top under the finger\n * (FUT-4144 UI review).\n */\n reserved?: boolean;\n}): React.JSX.Element {\n const copy = useDataViewsCopy();\n return (\n <Box\n aria-hidden={reserved || undefined}\n // Held, the row is blank — no rule over it either, or it reads as an\n // empty bordered row.\n sx={{ px: 1.5, py: 1, borderTop: 1, borderColor: reserved ? \"transparent\" : \"divider\", visibility: reserved ? \"hidden\" : undefined }}\n >\n <Box\n component=\"button\"\n type=\"button\"\n onClick={reserved ? undefined : onClearAll}\n tabIndex={reserved ? -1 : undefined}\n data-testid={reserved ? undefined : `${testIdPrefix}-more-clear-all`}\n sx={{\n width: \"100%\",\n border: 0,\n p: 0,\n bgcolor: \"transparent\",\n cursor: \"pointer\",\n font: \"inherit\",\n fontSize: sxRem(13),\n textAlign: \"left\",\n color: \"primary.main\",\n \"&:hover\": { textDecoration: \"underline\" },\n }}\n >\n {copy.filters.clearAll}\n </Box>\n </Box>\n );\n}\n\n/** The panel's body: a shed quick chip as a toggle row, anything else as a labelled group. */\nfunction MoreFields<T extends Record<string, unknown>>({\n fields,\n ...rest\n}: MoreFiltersProps<T>): React.JSX.Element {\n return (\n <>\n {fields.map((field) =>\n field.quick ? (\n <MoreQuick key={field.id} quick={field.quick} {...rest} />\n ) : (\n <MoreGroup key={field.id} field={field} {...rest} />\n ),\n )}\n </>\n );\n}\n\n/**\n * The panel is a column: heading, the fields' scroller, footer. Its height is\n * the screen's, not a fixed 320px — at 390×844 that cap scrolled a day range's\n * month caption and weekday row out of view after the first tap, and a typed\n * day's refusal out of sight of its box (FUT-4144 UI review). MUI moves the\n * panel up when it is taller than the room under its trigger.\n */\nconst COLUMN_SX = { display: \"flex\", flexDirection: \"column\" } as const;\n\n/**\n * 320, not 300: inside 12px of padding a day range's month then has the 280px\n * its seven 40px columns need and its month/year caption one row (FUT-4144).\n */\nconst PAPER_SX = (theme: Theme) => ({\n ...COLUMN_SX,\n width: rem(theme, 320),\n maxWidth: `calc(100vw - ${rem(theme, 32)})`,\n});\n\n/**\n * Below `sm` the panel takes the screen's width less 8px each side, so a day\n * range's calendar gets the 280px its seven 40px columns need (it had 264 at\n * 320 and 276 at 390, so every day was a 34-36px target).\n */\nconst PHONE_PAPER_SX = (theme: Theme) => ({\n ...COLUMN_SX,\n width: `calc(100vw - ${rem(theme, 16)})`,\n maxWidth: `calc(100vw - ${rem(theme, 16)})`,\n maxHeight: `calc(100% - ${rem(theme, 16)})`,\n});\n\n/** Props of {@link MoreFilters}. */\ninterface MoreFiltersPanelProps<T extends Record<string, unknown>> extends MoreFiltersProps<T> {\n /** Changes on every clear-all; the fields here are keyed on it (FUT-4144). */\n clearKey?: number;\n /** Icon only — the same rung that strips Exibir/Exportar. */\n compact?: boolean;\n /** Clears every filter, not just the ones in here. */\n onClearAll: () => void;\n /** Whether anything is applied anywhere — the footer is pointless otherwise. */\n anyApplied: boolean;\n /**\n * So the shell can FREEZE the measured split while this panel is open.\n *\n * Applying a filter in here makes its field active, and an active field\n * takes an inline slot first — so the field being edited was promoted OUT of\n * this panel mid-keystroke, taking the focused input with it. Typing \"12\"\n * got as far as \"1\" and then had nowhere to go.\n */\n onOpenChange?: (open: boolean) => void;\n}\n\n/** The overflow trigger + panel. Renders nothing when everything fits. */\nexport function MoreFilters<T extends Record<string, unknown>>({\n fields,\n onOpenChange,\n onClearAll,\n anyApplied,\n compact = false,\n clearKey,\n ...rest\n}: MoreFiltersPanelProps<T>): React.JSX.Element | null {\n const still = useStillAnchor();\n const theme = useTheme();\n const phone = useMediaQuery(theme.breakpoints.down(\"sm\"));\n if (fields.length === 0) return null;\n const holdsDayRange = fields.some((field) => field.range?.kind === \"day\");\n return (\n <>\n <MoreTrigger\n count={fields.length}\n appliedCount={\n fields.filter(\n (field) =>\n fieldClearing({\n field,\n pills: rest.pills,\n ranges: rest.ranges,\n onTogglePill: rest.onTogglePill,\n onChangeRange: rest.onChangeRange,\n }).applied,\n ).length\n }\n compact={compact}\n onOpen={(element) => {\n still.open(element);\n onOpenChange?.(true);\n }}\n testIdPrefix={rest.testIdPrefix}\n />\n <Popover\n {...still.popoverProps}\n onClose={() => {\n still.close();\n onOpenChange?.(false);\n }}\n // Below `sm`, 8px from the screen's edges instead of 16.\n marginThreshold={phone ? remPx(theme, 8) : undefined}\n slotProps={{ paper: { ref: still.paperRef, sx: phone ? PHONE_PAPER_SX : PAPER_SX } }}\n >\n <Box data-testid={`${rest.testIdPrefix}-more-panel`} sx={{ ...COLUMN_SX, flex: \"1 1 auto\", minHeight: 0 }}>\n <MoreHeading />\n {/* overflowX hidden, not auto: this panel is a fixed width and its\n contents are told to shrink into it. A horizontal scrollbar here\n means something refused to, which is a bug to fix rather than a\n scrollbar to live with. */}\n <Box sx={{ flex: \"1 1 auto\", minHeight: 0, overflowY: \"auto\", overflowX: \"hidden\", p: { xs: 1, sm: 1.5 }, minWidth: 0 }}>\n <MoreFields key={clearKey} fields={fields} {...rest} />\n </Box>\n {(anyApplied || holdsDayRange) && (\n <MoreFooter onClearAll={onClearAll} testIdPrefix={rest.testIdPrefix} reserved={!anyApplied} />\n )}\n </Box>\n </Popover>\n </>\n );\n}\n"],"mappings":"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","names":["default"]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/components/data-display/DataViews/data-views-more-fields.tsx"],"sourcesContent":["\"use client\";\n\n/**\n * ONE FIELD, RENDERED INSIDE THE \"MAIS\" PANEL.\n *\n * Split from `data-views-more-filters` at the file-size gate, along the seam\n * that was already there: that module is the trigger, the panel and its\n * footer — the chrome — and this is what goes in it.\n *\n * A field with one or two options renders FLAT: inside a panel there is no\n * space to win back, and two labelled checkboxes are faster than a dropdown\n * needing a second click. Past that — or whenever the field asks for a tree or\n * a search — it is the bar's own {@link PillControl}; see `sharesBarControl`.\n */\nimport Checkbox from \"@mui/material/Checkbox/index.js\";\n\nimport { Box } from \"../../../mui/Box\";\nimport { Text } from \"../../typography/Text\";\n\nimport { PillControl } from \"./data-views-category-pill\";\nimport type { OverflowField } from \"./data-views-overflow\";\nimport { RangeBounds } from \"./data-views-range-pill\";\nimport { isRangeSet } from \"./data-views-range-values\";\nimport type { QuickFilterConfig, RangeValue } from \"./data-views-types\";\nimport { QuickRow } from \"./data-views-quick-chip\";\nimport { isQuickActive } from \"./data-views-overflow-fields\";\nimport { useDataViewsCopy } from \"./data-views-copy-context\";\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/** What each field's control needs, minus the panel's own chrome. */\nexport interface MoreFieldProps {\n pills: Record<string, string[]>;\n ranges: Record<string, RangeValue>;\n onTogglePill: (fieldId: string, value: string, checked: boolean) => void;\n onChangeRange: (fieldId: string, range: RangeValue) => void;\n /** Presses or releases a quick chip shed into the panel. */\n onToggleQuick?: (quick: QuickFilterConfig) => void;\n testIdPrefix: string;\n}\n\n/**\n * Above this many options a field is a DROPDOWN rather than a flat row of\n * checkboxes. Two options cost two lines and read faster laid out; a \"Cliente\"\n * with a dozen turns the panel into a scrolling wall and buries every field\n * under it, which is exactly what the overflow was meant to avoid.\n */\nconst INLINE_OPTION_LIMIT = 2;\n\n/**\n * Whether an overflowed pill keeps the SAME control it has on the bar.\n *\n * The panel used to build its own flat dropdown for every field past two\n * options, so a field's config only held while it fitted: `control:\n * \"category\"` lost its tree (subcategories flattened into one list, no\n * \"Marcar tudo\", no count footer) and `searchEnabled` lost its search, on\n * exactly the phone widths where most filters live in here. Anything the bar\n * would render as a dropdown now renders as the bar's own {@link PillControl};\n * only a plain two-option field — one that did not ask for a dropdown, a tree\n * or a search — stays a flat checkbox row, which is a panel layout rather than\n * a different control (the filter panel draws the same line).\n */\nfunction sharesBarControl<T extends Record<string, unknown>>(field: OverflowField<T>): boolean {\n const pill = field.pill;\n if (!pill) return false;\n return (\n pill.control === \"category\" ||\n pill.control === \"multiselect\" ||\n pill.searchEnabled === true ||\n pill.options.length > INLINE_OPTION_LIMIT\n );\n}\n\n/** One overflowed pill: the bar's control, or its options as checkboxes. */\nfunction OverflowPill<T extends Record<string, unknown>>({\n field,\n values,\n onTogglePill,\n onClear,\n testIdPrefix,\n}: {\n field: OverflowField<T>;\n values: string[];\n onTogglePill: (fieldId: string, value: string, checked: boolean) => void;\n onClear: () => void;\n testIdPrefix: string;\n}): React.JSX.Element {\n const testId = `${testIdPrefix}-more-${field.id}`;\n if (field.pill && sharesBarControl(field)) {\n return (\n <PillControl\n fieldId={field.id}\n pill={field.pill}\n selected={values}\n onTogglePill={onTogglePill}\n onClearField={onClear}\n testIdPrefix={testIdPrefix}\n testId={testId}\n />\n );\n }\n const onToggle = (value: string, checked: boolean) => onTogglePill(field.id, value, checked);\n return (\n <Box sx={{ display: \"flex\", flexWrap: \"wrap\", gap: 0.75 }}>\n {(field.pill?.options ?? []).map((option) => {\n const checked = values.includes(option.value);\n return (\n <Box\n key={option.value}\n component=\"label\"\n data-testid={`${testId}-${option.value}`}\n sx={{\n display: \"inline-flex\",\n alignItems: \"center\",\n gap: 0.25,\n pr: 1,\n border: 1,\n borderStyle: \"solid\",\n minHeight: fieldHeight,\n borderColor: (theme) => (checked ? theme.palette.primary.main : fieldEdge(theme)),\n bgcolor: checked ? \"action.selected\" : \"transparent\",\n borderRadius: fieldRadiusPx,\n cursor: \"pointer\",\n fontSize: sxRem(13),\n color: checked ? \"primary.main\" : \"text.secondary\",\n }}\n >\n <Checkbox\n size=\"small\"\n checked={checked}\n onChange={(event) => onToggle(option.value, event.target.checked)}\n inputProps={{ \"aria-label\": `${field.label}: ${option.label}` }}\n />\n {option.label}\n </Box>\n );\n })}\n </Box>\n );\n}\n\n\n/**\n * One overflowed range: its presets and its two bounds, as the same controls\n * the pill uses.\n *\n * The whole control DELEGATES to {@link RangeBounds} rather than being built\n * here, which is the point — this panel used to render a raw\n * `<input type=\"date\">`, so the masked `dd/mm/aaaa` field existed only while\n * the filter FITTED on the bar. The moment \"Data\" overflowed it reverted to the\n * native control the mask replaced, and a merchant on a narrow screen never saw\n * the fix at all (FUT-744). `Valor` had the twin bug: a bare `type=\"number\"`\n * plus `Number(raw)` dropped the decimal comma this panel is meant to accept.\n *\n * That also retires the intrinsic-width workaround this pair used to need — a\n * native date input reports a wide minimum (its own mask plus the picker\n * glyph), which is what made two of them refuse to shrink and scrolled the\n * panel sideways. A text input has no such minimum.\n *\n * `RangeBounds` renders the preset chips ITSELF, above the inputs. This panel\n * must NOT render its own row as well: when the delegation above landed, the\n * chips it used to draw by hand stayed behind, and \"Data\" showed Hoje/Ontem/\n * Esta semana/Este mês/Este ano twice — two rows, duplicated test ids, and half\n * the panel's height spent saying the same thing (FUT-751).\n */\nfunction OverflowRange<T extends Record<string, unknown>>({\n field,\n value,\n onChange,\n testIdPrefix,\n}: {\n field: OverflowField<T>;\n value: RangeValue;\n onChange: (range: RangeValue) => void;\n testIdPrefix: string;\n}): React.JSX.Element | null {\n // `group === \"range\"` is what selects this component, so `range` is always\n // set; the guard is for the type, not for a state the caller can reach.\n if (!field.range) return null;\n return (\n <RangeBounds\n field={field.range}\n value={value}\n onChange={onChange}\n testId={`${testIdPrefix}-more-${field.id}`}\n fillRow\n />\n );\n}\n\n/**\n * Whether a field has anything applied, and how to unapply it — the two shapes\n * (a pill's selected values, a range's bounds) answered in one place so the\n * group that renders them stays a renderer.\n */\nexport function fieldClearing<T extends Record<string, unknown>>({\n field,\n pills,\n ranges,\n onTogglePill,\n onChangeRange,\n}: {\n field: OverflowField<T>;\n} & Pick<MoreFieldProps, \"pills\" | \"ranges\" | \"onTogglePill\" | \"onChangeRange\">): {\n applied: boolean;\n clear: () => void;\n} {\n if (field.group === \"quick\" && field.quick) {\n const quick = field.quick;\n // A `default` chip lit by an empty field has nothing to clear.\n const applied = isQuickActive(quick, pills) && (pills[quick.fieldId]?.length ?? 0) > 0;\n return { applied, clear: () => onTogglePill(quick.fieldId, quick.value, false) };\n }\n if (field.group === \"range\") {\n return {\n applied: isRangeSet(ranges[field.id] ?? {}),\n clear: () => onChangeRange(field.id, {}),\n };\n }\n const values = pills[field.id] ?? [];\n return {\n applied: values.length > 0,\n clear: () => values.forEach((value) => onTogglePill(field.id, value, false)),\n };\n}\n\n\n/**\n * A quick chip shed into the panel: its own line, because label and count are\n * already the whole control — no heading above it, no \"Limpar\" beside it.\n */\nexport function MoreQuick({\n quick,\n pills,\n onToggleQuick,\n testIdPrefix,\n}: {\n quick: QuickFilterConfig;\n} & Pick<MoreFieldProps, \"pills\" | \"onToggleQuick\" | \"testIdPrefix\">): React.JSX.Element {\n return (\n <Box sx={{ mb: 1, \"&:last-of-type\": { mb: 0 } }}>\n <QuickRow\n quick={quick}\n active={isQuickActive(quick, pills)}\n onToggle={() => onToggleQuick?.(quick)}\n testIdPrefix={testIdPrefix}\n />\n </Box>\n );\n}\n\n/** One labelled group in the panel: the field's name, then its control. */\nexport function MoreGroup<T extends Record<string, unknown>>({\n field,\n pills,\n ranges,\n onTogglePill,\n onChangeRange,\n testIdPrefix,\n}: {\n field: OverflowField<T>;\n} & MoreFieldProps): React.JSX.Element {\n const copy = useDataViewsCopy();\n const { applied, clear } = fieldClearing({ field, pills, ranges, onTogglePill, onChangeRange });\n return (\n <Box sx={{ mb: 1.5, \"&:last-of-type\": { mb: 0 } }}>\n <Box sx={{ display: \"flex\", alignItems: \"center\", gap: 1, mb: 0.5 }}>\n <Text variant=\"caption\" as=\"span\">\n <Box component=\"span\" sx={{ color: \"text.secondary\" }}>\n {field.label}\n {field.group === \"range\" && isRangeSet(ranges[field.id] ?? {}) ? \" •\" : \"\"}\n </Box>\n </Text>\n {/* The panel's equivalent of the pill's ✕. Without it a field applied\n in here could only be cleared by finding it again on the bar —\n which is where it goes the moment it becomes active, so the\n operator has to close this panel to undo what they just did. */}\n {applied && (\n <Box\n component=\"button\"\n type=\"button\"\n onClick={clear}\n data-testid={`${testIdPrefix}-more-${field.id}-clear`}\n sx={{\n ml: \"auto\",\n border: 0,\n p: 0,\n bgcolor: \"transparent\",\n cursor: \"pointer\",\n font: \"inherit\",\n fontSize: sxRem(12),\n color: \"primary.main\",\n \"&:hover\": { textDecoration: \"underline\" },\n }}\n >\n {copy.filters.clear}\n </Box>\n )}\n </Box>\n {field.group === \"pill\" ? (\n <OverflowPill\n field={field}\n values={pills[field.id] ?? []}\n onTogglePill={onTogglePill}\n onClear={() => (pills[field.id] ?? []).forEach((v) => onTogglePill(field.id, v, false))}\n testIdPrefix={testIdPrefix}\n />\n ) : (\n <OverflowRange\n field={field}\n value={ranges[field.id] ?? {}}\n onChange={(range) => onChangeRange(field.id, range)}\n testIdPrefix={testIdPrefix}\n />\n )}\n </Box>\n );\n}\n\n"],"mappings":"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","names":["default"]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/components/feedback/SequenceResolver/SequenceResolver.tsx"],"sourcesContent":["'use client';\n\nimport Box from '@mui/material/Box/index.js';\nimport { useTheme } from '@mui/material/styles/index.js';\nimport useMediaQuery from '@mui/material/useMediaQuery/index.js';\nimport React, { useCallback, useId, useRef, useState } from 'react';\n\nimport { Dialog } from '../Dialog';\nimport { canReturn, currentOf, jumpTargetOf, positionOf, progressOf, summaryOf, type RunState } from './sequence-model';\nimport {\n SequenceDoneBody,\n SequenceIntroBody,\n SequenceStepBody,\n} from './SequenceResolver.parts';\nimport { SequenceHeader } from './SequenceResolver.header';\nimport { SequenceOverviewBody } from './SequenceResolver.overview';\nimport { SEQUENCE_METRICS } from './SequenceResolver.metrics';\nimport { chromeSx, columnSx, paperSx, scrollBodySx } from './SequenceResolver.styles';\nimport type {\n SequenceItem,\n SequenceOverviewCategory,\n SequenceResolverProps,\n SequenceStep,\n SequenceStepApi,\n} from './SequenceResolver.types';\nimport { useSequenceRun, type SequenceRun } from './useSequenceRun';\n\n/** What the header's label says on the screen showing. */\nfunction headerLabelOf<I extends SequenceItem, R>(\n props: SequenceResolverProps<I, R>,\n run: RunState<I, R>,\n current: I | undefined,\n): string {\n const { labels } = props;\n if (props.intro) return labels.introStep;\n if (!current) return labels.doneStep;\n return labels.step(positionOf(run), summaryOf(run).total, current);\n}\n\n/** The header's back button, once a previous step exists and the host allows it. */\nfunction backOf<I extends SequenceItem, R>(props: SequenceResolverProps<I, R>, state: SequenceRun<I, R>) {\n const { intro, labels, canGoBack = true } = props;\n if (intro || !canGoBack || !canReturn(state.run)) return undefined;\n return { label: labels.back, accessible: labels.backAccessible, onClick: state.back, disabled: state.busy };\n}\n\ninterface OverviewState {\n /** The overview is on screen instead of the step. */\n shown: boolean;\n /** The header's \"Ver todos (N)\", while there is a step and more than one problem. */\n button?: { label: string; onClick: () => void; disabled: boolean };\n canPick: (category: SequenceOverviewCategory) => boolean;\n pick: (category: SequenceOverviewCategory) => void;\n hide: () => void;\n /** The overview's title (in the header row): focus goes to it when it opens. */\n titleRef: React.RefObject<HTMLHeadingElement | null>;\n}\n\n/**\n * \"Ver todos\" inside the run: whether it shows, its header button, and its\n * picks. Opening it changes nothing in the run; a pick jumps; \"continue\"\n * returns to the step it left. Every opening of the resolver starts closed.\n */\nfunction useOverview<I extends SequenceItem, R>(\n props: SequenceResolverProps<I, R>,\n state: SequenceRun<I, R>,\n current: I | undefined,\n open: boolean,\n): OverviewState {\n const [shown, setShown] = useState(false);\n const [wasOpen, setWasOpen] = useState(open);\n const titleRef = useRef<HTMLHeadingElement | null>(null);\n if (wasOpen !== open) {\n setWasOpen(open);\n if (shown) setShown(false);\n }\n const { overview } = props;\n const total = summaryOf(state.run).total;\n const available = Boolean(overview && current && total > 1);\n const hide = (): void => setShown(false);\n return {\n shown: shown && available,\n button:\n available && overview\n ? { label: overview.label(total), onClick: () => setShown(true), disabled: state.busy }\n : undefined,\n canPick: (category) => jumpTargetOf(state.run, category.keys) !== undefined,\n pick: (category) => {\n const target = jumpTargetOf(state.run, category.keys);\n if (target === undefined) return;\n state.jumpTo(target);\n setShown(false);\n category.onPick?.();\n },\n hide,\n titleRef,\n };\n}\n\n/** What the host's `renderStep` gets: the run's verbs, `busy`, and the props for its title. */\nfunction stepApiOf<I extends SequenceItem, R>(\n state: SequenceRun<I, R>,\n titleProps: SequenceStepApi<R>['titleProps'],\n): SequenceStepApi<R> {\n return { resolve: state.resolve, skip: state.skip, back: state.back, jumpTo: state.jumpTo, busy: state.busy, titleProps };\n}\n\ninterface HeaderInputs<I extends SequenceItem, R> {\n props: SequenceResolverProps<I, R>;\n state: SequenceRun<I, R>;\n current: I | undefined;\n overview: OverviewState;\n titleId: string;\n}\n\n/** The header's back, \"Ver todos\" and label — or, over the overview, its title and close alone. */\nfunction headerOf<I extends SequenceItem, R>(inputs: HeaderInputs<I, R>) {\n const { props, state, current, overview, titleId } = inputs;\n const run = state.run;\n const base = { label: headerLabelOf(props, run, current), progress: props.intro ? 0 : progressOf(run) };\n if (overview.shown && props.overview) {\n return { ...base, title: { id: titleId, text: props.overview.title, ref: overview.titleRef } };\n }\n return { ...base, back: backOf(props, state), seeAll: overview.button };\n}\n\n/**\n * Esc closes. `persistent` turns MUI's own Esc off together with the veil's\n * click (the prototype's veil does nothing), so Esc is put back here; a popup\n * inside a step that handles its own Esc stops it before it gets here.\n */\nfunction escapeCloses(close: () => void) {\n return (event: React.KeyboardEvent): void => {\n if (event.key !== 'Escape' || event.defaultPrevented) return;\n event.stopPropagation();\n close();\n };\n}\n\ninterface BodyProps<I extends SequenceItem, R> {\n props: SequenceResolverProps<I, R>;\n state: SequenceRun<I, R>;\n current: I | undefined;\n /** The step on screen, rendered once by the resolver (it also reads `canGoBack` from it). */\n step: SequenceStep | undefined;\n overview: OverviewState;\n titleId: string;\n stepTitleRef: React.RefObject<HTMLElement | null>;\n touch: boolean;\n testId: string;\n close: () => void;\n}\n\n/** The intro, the step on screen, or the end screen. */\nfunction SequenceBody<I extends SequenceItem, R>(body: BodyProps<I, R>): React.JSX.Element {\n const { props, state, current, step, overview, titleId, stepTitleRef, touch, testId, close } = body;\n if (props.intro) {\n return <SequenceIntroBody intro={props.intro} titleId={titleId} testId={testId} screenKey=\"intro\" />;\n }\n const stepBody =\n current && step ? (\n <SequenceStepBody\n key={`step:${current.key}`}\n step={step}\n skip={{ label: props.labels.skip, onClick: state.skip }}\n busy={state.busy}\n error={state.error}\n touch={touch}\n testId={testId}\n screenKey={`step:${current.key}`}\n titleRef={stepTitleRef}\n hidden={overview.shown}\n />\n ) : null;\n if (overview.shown && props.overview) {\n // The step stays mounted under the overview, so a half-filled answer survives \"continue\".\n return (\n <>\n <SequenceOverviewBody\n key=\"overview\"\n categories={props.overview.categories}\n canPick={overview.canPick}\n onPick={overview.pick}\n pickLabel={props.overview.pickLabel}\n continueLabel={props.overview.continueLabel}\n onContinue={overview.hide}\n titleRef={overview.titleRef}\n touch={touch}\n testId={testId}\n />\n {stepBody}\n </>\n );\n }\n if (stepBody) return <>{stepBody}</>;\n const summary = summaryOf(state.run);\n return (\n <SequenceDoneBody\n done={typeof props.done === 'function' ? props.done(summary) : props.done}\n titleId={titleId}\n error={state.error}\n touch={touch}\n onConfirm={close}\n testId={testId}\n screenKey=\"done\"\n />\n );\n}\n\n/**\n * Resolve a queue of problems one at a time, in ONE dialog whose body is\n * replaced step by step: a header with \"‹ Back\", the step's label and close,\n * a progress bar, the host's step, and an end screen (FUT-3671).\n *\n * A centred `Dialog` from the `sm` breakpoint up; the `Dialog` inset\n * bottom-sheet variant below it, never taller than 95% of the viewport. Either\n * way the paper scrolls inside the viewport, so a long step is never cut off.\n *\n * The component owns the run — which step is on screen, what was answered or\n * skipped, where Back returns to — and nothing about the domain: every step\n * body, every word and every server call is the host's.\n */\nexport function SequenceResolver<I extends SequenceItem, R = unknown>(\n props: SequenceResolverProps<I, R>,\n): React.JSX.Element {\n const { open, onClose, intro, labels } = props;\n const testId = props.dataTestId ?? 'sequence-resolver';\n const theme = useTheme();\n const touch = useMediaQuery(theme.breakpoints.down(SEQUENCE_METRICS.sheetBreakpoint));\n const titleId = `${useId()}-title`;\n const stepTitleRef = useRef<HTMLElement | null>(null);\n const titleRef = useCallback((node: HTMLElement | null) => {\n stepTitleRef.current = node;\n }, []);\n\n // Read when an answer is recorded: the step on screen's own `canGoBack`.\n const stepAllowsBack = useRef(true);\n const state = useSequenceRun<I, R>({\n open,\n active: open && !intro,\n queue: props.queue,\n initialKey: props.initialKey,\n onBack: props.onBack,\n failed: labels.failed,\n stepAllowsBack: () => stepAllowsBack.current,\n });\n const { run } = state;\n const current = intro ? undefined : currentOf(run);\n const close = (): void => onClose(summaryOf(run));\n const overview = useOverview(props, state, current, open);\n // Under the overview the step is kept but hidden, and the overview's title names the dialog.\n const titleProps = { id: overview.shown ? `${titleId}-kept` : titleId, tabIndex: -1, ref: titleRef } as const;\n const step = current ? props.renderStep(current, stepApiOf(state, titleProps)) : undefined;\n stepAllowsBack.current = step?.canGoBack !== false;\n\n return (\n <Dialog\n open={open}\n onClose={close}\n persistent\n variant={touch ? 'bottom-sheet' : 'default'}\n size=\"sm\"\n aria-labelledby={titleId}\n onKeyDown={escapeCloses(close)}\n slotProps={{ paper: { sx: paperSx(touch) } }}\n dataTestId={testId}\n >\n <Box sx={columnSx}>\n <Box sx={chromeSx}>\n <SequenceHeader\n touch={touch}\n {...headerOf({ props, state, current, overview, titleId })}\n closeLabel={labels.close}\n onClose={close}\n progressLabel={labels.progress}\n testId={testId}\n />\n </Box>\n <Box sx={scrollBodySx} data-testid={`${testId}-body`}>\n <SequenceBody\n props={props}\n state={state}\n current={current}\n step={step}\n overview={overview}\n titleId={titleId}\n stepTitleRef={stepTitleRef}\n touch={touch}\n testId={testId}\n close={close}\n />\n </Box>\n </Box>\n </Dialog>\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAEA,OAAO,SAAS;AAChB,SAAS,gBAAgB;AACzB,OAAO,mBAAmB;AAC1B,SAAgB,aAAa,OAAO,QAAQ,gBAAgB;AAwJjD,SAoBL,UApBK,KAoBL,YApBK;AAjIX,SAAS,cACP,OACA,KACA,SACQ;AACR,QAAM,EAAE,OAAO,IAAI;AACnB,MAAI,MAAM,MAAO,QAAO,OAAO;AAC/B,MAAI,CAAC,QAAS,QAAO,OAAO;AAC5B,SAAO,OAAO,KAAK,WAAW,GAAG,GAAG,UAAU,GAAG,EAAE,OAAO,OAAO;AACnE;AAGA,SAAS,OAAkC,OAAoC,OAA0B;AACvG,QAAM,EAAE,OAAO,QAAQ,YAAY,KAAK,IAAI;AAC5C,MAAI,SAAS,CAAC,aAAa,CAAC,UAAU,MAAM,GAAG,EAAG,QAAO;AACzD,SAAO,EAAE,OAAO,OAAO,MAAM,YAAY,OAAO,gBAAgB,SAAS,MAAM,MAAM,UAAU,MAAM,KAAK;AAC5G;AAmBA,SAAS,YACP,OACA,OACA,SACA,MACe;AACf,QAAM,CAAC,OAAO,QAAQ,IAAI,SAAS,KAAK;AACxC,QAAM,CAAC,SAAS,UAAU,IAAI,SAAS,IAAI;AAC3C,QAAM,WAAW,OAAkC,IAAI;AACvD,MAAI,YAAY,MAAM;AACpB,eAAW,IAAI;AACf,QAAI,MAAO,UAAS,KAAK;AAAA,EAC3B;AACA,QAAM,EAAE,SAAS,IAAI;AACrB,QAAM,QAAQ,UAAU,MAAM,GAAG,EAAE;AACnC,QAAM,YAAY,QAAQ,YAAY,WAAW,QAAQ,CAAC;AAC1D,QAAM,OAAO,MAAY,SAAS,KAAK;AACvC,SAAO;AAAA,IACL,OAAO,SAAS;AAAA,IAChB,QACE,aAAa,WACT,EAAE,OAAO,SAAS,MAAM,KAAK,GAAG,SAAS,MAAM,SAAS,IAAI,GAAG,UAAU,MAAM,KAAK,IACpF;AAAA,IACN,SAAS,CAAC,aAAa,aAAa,MAAM,KAAK,SAAS,IAAI,MAAM;AAAA,IAClE,MAAM,CAAC,aAAa;AAClB,YAAM,SAAS,aAAa,MAAM,KAAK,SAAS,IAAI;AACpD,UAAI,WAAW,OAAW;AAC1B,YAAM,OAAO,MAAM;AACnB,eAAS,KAAK;AACd,eAAS,SAAS;AAAA,IACpB;AAAA,IACA;AAAA,IACA;AAAA,EACF;AACF;AAGA,SAAS,UACP,OACA,YACoB;AACpB,SAAO,EAAE,SAAS,MAAM,SAAS,MAAM,MAAM,MAAM,MAAM,MAAM,MAAM,QAAQ,MAAM,QAAQ,MAAM,MAAM,MAAM,WAAW;AAC1H;AAWA,SAAS,SAAoC,QAA4B;AACvE,QAAM,EAAE,OAAO,OAAO,SAAS,UAAU,QAAQ,IAAI;AACrD,QAAM,MAAM,MAAM;AAClB,QAAM,OAAO,EAAE,OAAO,cAAc,OAAO,KAAK,OAAO,GAAG,UAAU,MAAM,QAAQ,IAAI,WAAW,GAAG,EAAE;AACtG,MAAI,SAAS,SAAS,MAAM,UAAU;AACpC,WAAO,EAAE,GAAG,MAAM,OAAO,EAAE,IAAI,SAAS,MAAM,MAAM,SAAS,OAAO,KAAK,SAAS,SAAS,EAAE;AAAA,EAC/F;AACA,SAAO,EAAE,GAAG,MAAM,MAAM,OAAO,OAAO,KAAK,GAAG,QAAQ,SAAS,OAAO;AACxE;AAOA,SAAS,aAAa,OAAmB;AACvC,SAAO,CAAC,UAAqC;AAC3C,QAAI,MAAM,QAAQ,YAAY,MAAM,iBAAkB;AACtD,UAAM,gBAAgB;AACtB,UAAM;AAAA,EACR;AACF;AAiBA,SAAS,aAAwC,MAA0C;AACzF,QAAM,EAAE,OAAO,OAAO,SAAS,MAAM,UAAU,SAAS,cAAc,OAAO,QAAQ,MAAM,IAAI;AAC/F,MAAI,MAAM,OAAO;AACf,WAAO,oBAAC,qBAAkB,OAAO,MAAM,OAAO,SAAkB,QAAgB,WAAU,SAAQ;AAAA,EACpG;AACA,QAAM,WACJ,WAAW,OACT;AAAA,IAAC;AAAA;AAAA,MAEC;AAAA,MACA,MAAM,EAAE,OAAO,MAAM,OAAO,MAAM,SAAS,MAAM,KAAK;AAAA,MACtD,MAAM,MAAM;AAAA,MACZ,OAAO,MAAM;AAAA,MACb;AAAA,MACA;AAAA,MACA,WAAW,QAAQ,QAAQ,GAAG;AAAA,MAC9B,UAAU;AAAA,MACV,QAAQ,SAAS;AAAA;AAAA,IATZ,QAAQ,QAAQ,GAAG;AAAA,EAU1B,IACE;AACN,MAAI,SAAS,SAAS,MAAM,UAAU;AAEpC,WACE,iCACE;AAAA;AAAA,QAAC;AAAA;AAAA,UAEC,YAAY,MAAM,SAAS;AAAA,UAC3B,SAAS,SAAS;AAAA,UAClB,QAAQ,SAAS;AAAA,UACjB,WAAW,MAAM,SAAS;AAAA,UAC1B,eAAe,MAAM,SAAS;AAAA,UAC9B,YAAY,SAAS;AAAA,UACrB,UAAU,SAAS;AAAA,UACnB;AAAA,UACA;AAAA;AAAA,QATI;AAAA,MAUN;AAAA,MACC;AAAA,OACH;AAAA,EAEJ;AACA,MAAI,SAAU,QAAO,gCAAG,oBAAS;AACjC,QAAM,UAAU,UAAU,MAAM,GAAG;AACnC,SACE;AAAA,IAAC;AAAA;AAAA,MACC,MAAM,OAAO,MAAM,SAAS,aAAa,MAAM,KAAK,OAAO,IAAI,MAAM;AAAA,MACrE;AAAA,MACA,OAAO,MAAM;AAAA,MACb;AAAA,MACA,WAAW;AAAA,MACX;AAAA,MACA,WAAU;AAAA;AAAA,EACZ;AAEJ;AAeO,SAAS,iBACd,OACmB;AACnB,QAAM,EAAE,MAAM,SAAS,OAAO,OAAO,IAAI;AACzC,QAAM,SAAS,MAAM,cAAc;AACnC,QAAM,QAAQ,SAAS;AACvB,QAAM,QAAQ,cAAc,MAAM,YAAY,KAAK,iBAAiB,eAAe,CAAC;AACpF,QAAM,UAAU,GAAG,MAAM,CAAC;AAC1B,QAAM,eAAe,OAA2B,IAAI;AACpD,QAAM,WAAW,YAAY,CAAC,SAA6B;AACzD,iBAAa,UAAU;AAAA,EACzB,GAAG,CAAC,CAAC;AAGL,QAAM,iBAAiB,OAAO,IAAI;AAClC,QAAM,QAAQ,eAAqB;AAAA,IACjC;AAAA,IACA,QAAQ,QAAQ,CAAC;AAAA,IACjB,OAAO,MAAM;AAAA,IACb,YAAY,MAAM;AAAA,IAClB,QAAQ,MAAM;AAAA,IACd,QAAQ,OAAO;AAAA,IACf,gBAAgB,MAAM,eAAe;AAAA,EACvC,CAAC;AACD,QAAM,EAAE,IAAI,IAAI;AAChB,QAAM,UAAU,QAAQ,SAAY,UAAU,GAAG;AACjD,QAAM,QAAQ,MAAY,QAAQ,UAAU,GAAG,CAAC;AAChD,QAAM,WAAW,YAAY,OAAO,OAAO,SAAS,IAAI;AAExD,QAAM,aAAa,EAAE,IAAI,SAAS,QAAQ,GAAG,OAAO,UAAU,SAAS,UAAU,IAAI,KAAK,SAAS;AACnG,QAAM,OAAO,UAAU,MAAM,WAAW,SAAS,UAAU,OAAO,UAAU,CAAC,IAAI;AACjF,iBAAe,UAAU,MAAM,cAAc;AAE7C,SACE;AAAA,IAAC;AAAA;AAAA,MACC;AAAA,MACA,SAAS;AAAA,MACT,YAAU;AAAA,MACV,SAAS,QAAQ,iBAAiB;AAAA,MAClC,MAAK;AAAA,MACL,mBAAiB;AAAA,MACjB,WAAW,aAAa,KAAK;AAAA,MAC7B,WAAW,EAAE,OAAO,EAAE,IAAI,QAAQ,KAAK,EAAE,EAAE;AAAA,MAC3C,YAAY;AAAA,MAEZ,+BAAC,OAAI,IAAI,UACP;AAAA,4BAAC,OAAI,IAAI,UACP;AAAA,UAAC;AAAA;AAAA,YACC;AAAA,YACC,GAAG,SAAS,EAAE,OAAO,OAAO,SAAS,UAAU,QAAQ,CAAC;AAAA,YACzD,YAAY,OAAO;AAAA,YACnB,SAAS;AAAA,YACT,eAAe,OAAO;AAAA,YACtB;AAAA;AAAA,QACF,GACF;AAAA,QACA,oBAAC,OAAI,IAAI,cAAc,eAAa,GAAG,MAAM,SAC3C;AAAA,UAAC;AAAA;AAAA,YACC;AAAA,YACA;AAAA,YACA;AAAA,YACA;AAAA,YACA;AAAA,YACA;AAAA,YACA;AAAA,YACA;AAAA,YACA;AAAA,YACA;AAAA;AAAA,QACF,GACF;AAAA,SACF;AAAA;AAAA,EACF;AAEJ;","names":[]}