@12-apps/ui 6.41.1 → 6.42.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 (176) hide show
  1. package/dist/_internal/components/data-display/DataGrid/DataGrid.body.js +2 -2
  2. package/dist/_internal/components/data-display/DataGrid/DataGrid.cells.js +1 -1
  3. package/dist/_internal/components/data-display/DataGrid/DataGrid.js +3 -3
  4. package/dist/_internal/components/data-display/DataViews/DataViewsGrid.js +6 -6
  5. package/dist/_internal/components/data-display/DataViews/DataViewsTableBase.js +7 -7
  6. package/dist/_internal/components/data-display/DataViews/data-views-grid-bodies.js +4 -4
  7. package/dist/_internal/components/data-display/DataViews/data-views-grid-parts.js +5 -5
  8. package/dist/_internal/components/data-display/FleetMap/FleetMap.fixtures.js +1 -1
  9. package/dist/_internal/components/data-display/Lightbox/Lightbox.js +7 -7
  10. package/dist/_internal/components/data-display/Lightbox/LightboxControls.js +2 -2
  11. package/dist/_internal/components/data-display/Lightbox/LightboxCurrentMedia.js +3 -2
  12. package/dist/_internal/components/data-display/Lightbox/LightboxMedia.js +2 -1
  13. package/dist/_internal/components/data-display/Lightbox/LightboxOverlay.js +3 -3
  14. package/dist/_internal/components/data-display/Lightbox/LightboxThumbnails.js +2 -1
  15. package/dist/_internal/components/data-display/TimingDiagram/TimingDiagram.js +2 -2
  16. package/dist/_internal/components/data-display/TimingDiagram/TimingDiagram.views.js +1 -1
  17. package/dist/_internal/components/data-display/index.js +12 -12
  18. package/dist/_internal/components/feedback/TutorialOverlay/TutorialOverlay.js +2 -2
  19. package/dist/_internal/components/feedback/TutorialOverlay/TutorialStepBody.js +1 -1
  20. package/dist/_internal/components/feedback/index.js +2 -2
  21. package/dist/_internal/components/index.js +16 -16
  22. package/dist/_internal/components/layout/Dashboard/Dashboard.js +2 -2
  23. package/dist/_internal/components/layout/Dashboard/DashboardParts.js +1 -1
  24. package/dist/_internal/components/layout/SettingsLayout/SettingsLayout.js +4 -4
  25. package/dist/_internal/components/layout/SettingsLayout/SettingsPanel.js +3 -3
  26. package/dist/_internal/components/layout/SettingsLayout/SettingsSectionChips.js +2 -2
  27. package/dist/_internal/components/layout/SettingsLayout/SettingsSectionChips.scroll.js +7 -1
  28. package/dist/_internal/components/layout/index.js +2 -2
  29. package/dist/_internal/en-US.data-display.js +3 -1
  30. package/dist/_internal/en-US.feedback.js +1 -1
  31. package/dist/_internal/pt-BR.data-display.js +3 -1
  32. package/dist/_internal/pt-BR.feedback.js +1 -1
  33. package/dist/{chunk-K4L42FF6.js → chunk-24JOJJ2J.js} +3 -3
  34. package/dist/chunk-24JOJJ2J.js.map +1 -0
  35. package/dist/{chunk-LODMNSLR.js → chunk-3GYPZ5MI.js} +4 -3
  36. package/dist/chunk-3GYPZ5MI.js.map +1 -0
  37. package/dist/{chunk-HFVYWHJT.js → chunk-AP4LSXRK.js} +2 -2
  38. package/dist/{chunk-MEBQKYH4.js → chunk-BX7QWHKC.js} +2 -2
  39. package/dist/{chunk-UFNIS7KC.js → chunk-GGBFDHXE.js} +11 -8
  40. package/dist/chunk-GGBFDHXE.js.map +1 -0
  41. package/dist/chunk-GT2RCOHM.js +184 -0
  42. package/dist/chunk-GT2RCOHM.js.map +1 -0
  43. package/dist/{chunk-2XHIF3M3.js → chunk-GXQDWHPN.js} +25 -3
  44. package/dist/chunk-GXQDWHPN.js.map +1 -0
  45. package/dist/{chunk-F7JTIHV5.js → chunk-ICDAYOJ6.js} +6 -5
  46. package/dist/chunk-ICDAYOJ6.js.map +1 -0
  47. package/dist/{chunk-ASCUWG55.js → chunk-IOCY5G43.js} +8 -3
  48. package/dist/chunk-IOCY5G43.js.map +1 -0
  49. package/dist/{chunk-RZ2VZ7SL.js → chunk-JMEPGXJG.js} +2 -2
  50. package/dist/{chunk-3MEIGSDK.js → chunk-LTTYUSJ2.js} +5 -2
  51. package/dist/chunk-LTTYUSJ2.js.map +1 -0
  52. package/dist/{chunk-3AQD2EA6.js → chunk-M42ZOCO7.js} +10 -9
  53. package/dist/chunk-M42ZOCO7.js.map +1 -0
  54. package/dist/{chunk-GJFAI3J5.js → chunk-OOB3YWPX.js} +17 -10
  55. package/dist/chunk-OOB3YWPX.js.map +1 -0
  56. package/dist/{chunk-TZASFX6G.js → chunk-P53KRCKW.js} +4 -4
  57. package/dist/{chunk-2DWBJJK6.js → chunk-RBGUS7WO.js} +2 -2
  58. package/dist/{chunk-ZEYUSS3W.js → chunk-SQNQQTP7.js} +4 -3
  59. package/dist/chunk-SQNQQTP7.js.map +1 -0
  60. package/dist/chunk-SV4FYSDL.js +92 -0
  61. package/dist/chunk-SV4FYSDL.js.map +1 -0
  62. package/dist/{chunk-CKHFYTIX.js → chunk-TBMGYQJF.js} +15 -5
  63. package/dist/chunk-TBMGYQJF.js.map +1 -0
  64. package/dist/{chunk-TCNTIGMY.js → chunk-TDXEO2UI.js} +2 -2
  65. package/dist/{chunk-ASHXIAJO.js → chunk-UGH2DDST.js} +5 -2
  66. package/dist/chunk-UGH2DDST.js.map +1 -0
  67. package/dist/{chunk-WFAJTO2B.js → chunk-UVFZQCC7.js} +5 -9
  68. package/dist/chunk-UVFZQCC7.js.map +1 -0
  69. package/dist/{chunk-U5SH3VRJ.js → chunk-V5ZLUJV7.js} +25 -3
  70. package/dist/chunk-V5ZLUJV7.js.map +1 -0
  71. package/dist/{chunk-VCBV5JM7.js → chunk-VNOXUWK7.js} +2 -2
  72. package/dist/{chunk-IVJKILIX.js → chunk-VZ4VAQX3.js} +2 -2
  73. package/dist/{chunk-5QAJ3Y5F.js → chunk-XQWUVQ2F.js} +2 -2
  74. package/dist/{chunk-H5ZTIELC.js → chunk-ZFXTRPQO.js} +5 -4
  75. package/dist/chunk-ZFXTRPQO.js.map +1 -0
  76. package/dist/{chunk-UT73NGRS.js → chunk-ZSTUKSTJ.js} +2 -2
  77. package/dist/data-display/DataGrid.js +3 -3
  78. package/dist/data-display/DataViews.js +7 -7
  79. package/dist/data-display/Lightbox.js +7 -7
  80. package/dist/data-display/TimingDiagram.js +2 -2
  81. package/dist/en-US.js +4 -2
  82. package/dist/feedback/TutorialOverlay.js +2 -2
  83. package/dist/layout/Dashboard.js +2 -2
  84. package/dist/layout/SettingsLayout.js +4 -4
  85. package/dist/locales.js +8 -4
  86. package/dist/locales.js.map +1 -1
  87. package/dist/pt-BR.js +4 -2
  88. package/dist/types/components/data-display/DataGrid/DataGrid.body.d.ts.map +1 -1
  89. package/dist/types/components/data-display/DataGrid/DataGrid.cells.d.ts +3 -1
  90. package/dist/types/components/data-display/DataGrid/DataGrid.cells.d.ts.map +1 -1
  91. package/dist/types/components/data-display/DataGrid/DataGrid.d.ts +1 -1
  92. package/dist/types/components/data-display/DataGrid/DataGrid.d.ts.map +1 -1
  93. package/dist/types/components/data-display/DataGrid/DataGrid.types.d.ts +12 -2
  94. package/dist/types/components/data-display/DataGrid/DataGrid.types.d.ts.map +1 -1
  95. package/dist/types/components/data-display/DataViews/data-views-copy.d.ts +8 -2
  96. package/dist/types/components/data-display/DataViews/data-views-copy.d.ts.map +1 -1
  97. package/dist/types/components/data-display/DataViews/data-views-grid-bodies.d.ts.map +1 -1
  98. package/dist/types/components/data-display/Lightbox/Lightbox.d.ts.map +1 -1
  99. package/dist/types/components/data-display/Lightbox/LightboxMedia.d.ts.map +1 -1
  100. package/dist/types/components/data-display/Lightbox/LightboxThumbnails.d.ts.map +1 -1
  101. package/dist/types/components/data-display/TimingDiagram/TimingDiagram.d.ts.map +1 -1
  102. package/dist/types/components/data-display/TimingDiagram/TimingDiagram.views.d.ts +2 -0
  103. package/dist/types/components/data-display/TimingDiagram/TimingDiagram.views.d.ts.map +1 -1
  104. package/dist/types/components/layout/Dashboard/Dashboard.d.ts +1 -1
  105. package/dist/types/components/layout/Dashboard/Dashboard.types.d.ts +22 -12
  106. package/dist/types/components/layout/Dashboard/Dashboard.types.d.ts.map +1 -1
  107. package/dist/types/components/layout/Dashboard/DashboardParts.d.ts +1 -1
  108. package/dist/types/components/layout/Dashboard/DashboardParts.d.ts.map +1 -1
  109. package/dist/types/components/layout/SettingsLayout/SettingsSectionChips.d.ts.map +1 -1
  110. package/dist/types/components/layout/SettingsLayout/SettingsSectionChips.scroll.d.ts +20 -2
  111. package/dist/types/components/layout/SettingsLayout/SettingsSectionChips.scroll.d.ts.map +1 -1
  112. package/dist/types/copy/data-display.d.ts +40 -1
  113. package/dist/types/copy/data-display.d.ts.map +1 -1
  114. package/dist/types/copy/feedback.d.ts +7 -1
  115. package/dist/types/copy/feedback.d.ts.map +1 -1
  116. package/dist/types/en-US.data-display.d.ts +2 -1
  117. package/dist/types/en-US.data-display.d.ts.map +1 -1
  118. package/dist/types/en-US.feedback.d.ts.map +1 -1
  119. package/dist/types/locales.d.ts +2 -1
  120. package/dist/types/locales.d.ts.map +1 -1
  121. package/dist/types/pt-BR.data-display.d.ts +2 -1
  122. package/dist/types/pt-BR.data-display.d.ts.map +1 -1
  123. package/dist/types/pt-BR.feedback.d.ts.map +1 -1
  124. package/package.json +1 -1
  125. package/src/components/data-display/DataGrid/DataGrid.body.tsx +2 -1
  126. package/src/components/data-display/DataGrid/DataGrid.cells.tsx +7 -4
  127. package/src/components/data-display/DataGrid/DataGrid.tsx +2 -1
  128. package/src/components/data-display/DataGrid/DataGrid.types.ts +13 -2
  129. package/src/components/data-display/DataViews/data-views-copy.ts +8 -2
  130. package/src/components/data-display/DataViews/data-views-grid-bodies.tsx +1 -0
  131. package/src/components/data-display/Lightbox/Lightbox.tsx +8 -2
  132. package/src/components/data-display/Lightbox/LightboxMedia.tsx +5 -2
  133. package/src/components/data-display/Lightbox/LightboxThumbnails.tsx +7 -3
  134. package/src/components/data-display/TimingDiagram/TimingDiagram.tsx +10 -7
  135. package/src/components/data-display/TimingDiagram/TimingDiagram.views.tsx +6 -2
  136. package/src/components/feedback/TutorialOverlay/TutorialStepBody.tsx +2 -2
  137. package/src/components/layout/Dashboard/Dashboard.types.ts +22 -12
  138. package/src/components/layout/Dashboard/DashboardParts.tsx +10 -7
  139. package/src/components/layout/SettingsLayout/SettingsSectionChips.scroll.ts +212 -28
  140. package/src/components/layout/SettingsLayout/SettingsSectionChips.tsx +24 -4
  141. package/src/copy/data-display.ts +41 -1
  142. package/src/copy/feedback.ts +7 -1
  143. package/src/en-US.data-display.ts +23 -0
  144. package/src/en-US.feedback.ts +3 -0
  145. package/src/locales.ts +2 -0
  146. package/src/pt-BR.data-display.ts +23 -0
  147. package/src/pt-BR.feedback.ts +3 -0
  148. package/dist/chunk-2XHIF3M3.js.map +0 -1
  149. package/dist/chunk-3AQD2EA6.js.map +0 -1
  150. package/dist/chunk-3MEIGSDK.js.map +0 -1
  151. package/dist/chunk-ASCUWG55.js.map +0 -1
  152. package/dist/chunk-ASHXIAJO.js.map +0 -1
  153. package/dist/chunk-BAFDSAWY.js +0 -86
  154. package/dist/chunk-BAFDSAWY.js.map +0 -1
  155. package/dist/chunk-CKHFYTIX.js.map +0 -1
  156. package/dist/chunk-F7JTIHV5.js.map +0 -1
  157. package/dist/chunk-GJFAI3J5.js.map +0 -1
  158. package/dist/chunk-H5ZTIELC.js.map +0 -1
  159. package/dist/chunk-K4L42FF6.js.map +0 -1
  160. package/dist/chunk-LODMNSLR.js.map +0 -1
  161. package/dist/chunk-SLJAQA2X.js +0 -82
  162. package/dist/chunk-SLJAQA2X.js.map +0 -1
  163. package/dist/chunk-U5SH3VRJ.js.map +0 -1
  164. package/dist/chunk-UFNIS7KC.js.map +0 -1
  165. package/dist/chunk-WFAJTO2B.js.map +0 -1
  166. package/dist/chunk-ZEYUSS3W.js.map +0 -1
  167. /package/dist/{chunk-HFVYWHJT.js.map → chunk-AP4LSXRK.js.map} +0 -0
  168. /package/dist/{chunk-MEBQKYH4.js.map → chunk-BX7QWHKC.js.map} +0 -0
  169. /package/dist/{chunk-RZ2VZ7SL.js.map → chunk-JMEPGXJG.js.map} +0 -0
  170. /package/dist/{chunk-TZASFX6G.js.map → chunk-P53KRCKW.js.map} +0 -0
  171. /package/dist/{chunk-2DWBJJK6.js.map → chunk-RBGUS7WO.js.map} +0 -0
  172. /package/dist/{chunk-TCNTIGMY.js.map → chunk-TDXEO2UI.js.map} +0 -0
  173. /package/dist/{chunk-VCBV5JM7.js.map → chunk-VNOXUWK7.js.map} +0 -0
  174. /package/dist/{chunk-IVJKILIX.js.map → chunk-VZ4VAQX3.js.map} +0 -0
  175. /package/dist/{chunk-5QAJ3Y5F.js.map → chunk-XQWUVQ2F.js.map} +0 -0
  176. /package/dist/{chunk-UT73NGRS.js.map → chunk-ZSTUKSTJ.js.map} +0 -0
@@ -21,6 +21,24 @@ const LANDED_SLACK = 1;
21
21
  */
22
22
  const QUIET_BEFORE_SETTLED_MS = 250;
23
23
 
24
+ /**
25
+ * How much `scrollLeft` has to move, tick to tick, before a `scroll` event is
26
+ * even a CANDIDATE for arming (FUT-2775). Below this, a browser's own rounding
27
+ * or a repeated event carrying no real motion cannot arm anything by itself.
28
+ *
29
+ * This alone does not stop the false latch the ticket describes: the strip's
30
+ * own `scrollTo` produces real per-tick deltas well past this line, same as a
31
+ * drag would. It only rules out a scroll event that moved nothing.
32
+ */
33
+ const SCROLL_DELTA_EPSILON = 2;
34
+
35
+ /**
36
+ * How far a pointer or touch has to travel from where it went down before it
37
+ * counts as having MOVED, rather than merely resting (FUT-2775). Below this,
38
+ * an held-still finger's sub-pixel tremor does not count as a drag.
39
+ */
40
+ const POINTER_MOVE_EPSILON = 2;
41
+
24
42
  interface VisitorScrollWatch {
25
43
  /** True once the visitor has moved the strip themselves. */
26
44
  hasScrolled: () => boolean;
@@ -29,10 +47,14 @@ interface VisitorScrollWatch {
29
47
  * strip must not scroll itself under it: that would yank a drag in progress.
30
48
  */
31
49
  isUndecided: () => boolean;
32
- /** Where the strip last aimed itself, in its own pixels; `NaN` before it has. */
50
+ /**
51
+ * Where the strip last aimed itself, PHYSICAL pixels (see {@link physicalScrollLeft}) —
52
+ * direction-blind, like every other geometry read in this file. `NaN` before it has.
53
+ */
33
54
  aimedAt: () => number;
34
55
  /**
35
- * The strip is about to scroll itself to `left`. Records the aim and NOTHING
56
+ * The strip is about to scroll itself to `left` (physical pixels). Records the
57
+ * aim and NOTHING
36
58
  * else: it used to disarm too, and a re-centre landing between a visitor's key
37
59
  * press (or wheel, or pan) and the first tick of the scroll it causes wiped
38
60
  * the arming — their scroll went unclaimed, read as the strip's own. Left
@@ -62,13 +84,172 @@ interface WatchState {
62
84
  aimed: number;
63
85
  quiet: ReturnType<typeof setTimeout> | undefined;
64
86
  settled: () => void;
87
+ /** `scrollLeft` as of the last `scroll` tick, updated every tick regardless of arming. */
88
+ lastScrollLeft: number;
89
+ /** Where a `pointerdown` on the strip landed, in viewport `clientX`. `NaN` while up. */
90
+ pointerDownAt: number;
91
+ /** True once that pointer has travelled past {@link POINTER_MOVE_EPSILON} from `pointerDownAt`. */
92
+ pointerMoved: boolean;
93
+ /** Same pair, for `touchstart`/`touchmove` — a distinct event stream on some engines. */
94
+ touchDownAt: number;
95
+ touchMoved: boolean;
96
+ }
97
+
98
+ type RtlScrollType = 'default' | 'negative' | 'reverse';
99
+
100
+ let cachedRtlScrollType: RtlScrollType | undefined;
101
+
102
+ /**
103
+ * Which of the three incompatible conventions this engine reports an RTL
104
+ * scroller's `scrollLeft` in, probed once and cached (FUT-2775).
105
+ *
106
+ * Per the CSSOM View spec, an RTL scroller's `scrollLeft` is `0` at its
107
+ * (right-hand) start and goes NEGATIVE toward the end — `'negative'` below,
108
+ * what every current evergreen engine implements. Older WebKit mirrored that
109
+ * (`'reverse'`: `0` at the start, growing toward `+max`), and older
110
+ * Chromium/Safari ignored `dir` altogether and kept the plain left-to-right
111
+ * numbering (`'default'`). A hidden probe, not a version sniff, tells them
112
+ * apart — the same technique `@mui/utils`'s `detectScrollType` uses for
113
+ * `Tabs`; reimplemented here rather than taking a new dependency for one
114
+ * probe.
115
+ */
116
+ function detectRtlScrollType(doc: Document): RtlScrollType {
117
+ if (cachedRtlScrollType) return cachedRtlScrollType;
118
+ // The exact pixel values below are the probe's mechanism, not a design size:
119
+ // what matters is only that the INNER box is wider than the OUTER one, so the
120
+ // outer one has something to scroll — same technique `@mui/utils` uses,
121
+ // detached and off-screen either way.
122
+ const probe = doc.createElement('div');
123
+ const inner = doc.createElement('div');
124
+ inner.style.width = '10px';
125
+ probe.appendChild(inner);
126
+ probe.dir = 'rtl';
127
+ probe.style.width = '4px';
128
+ probe.style.position = 'absolute';
129
+ probe.style.top = '-1000px';
130
+ probe.style.overflow = 'scroll';
131
+ doc.body.appendChild(probe);
132
+ let type: RtlScrollType = 'reverse';
133
+ if (probe.scrollLeft > 0) {
134
+ type = 'default';
135
+ } else {
136
+ probe.scrollLeft = 1;
137
+ if (probe.scrollLeft === 0) type = 'negative';
138
+ }
139
+ doc.body.removeChild(probe);
140
+ cachedRtlScrollType = type;
141
+ return type;
142
+ }
143
+
144
+ /** True when the strip's own computed direction is right-to-left. */
145
+ export function isRtlStrip(strip: HTMLElement): boolean {
146
+ const view = strip.ownerDocument.defaultView;
147
+ const getComputedStyle = view?.getComputedStyle ?? globalThis.getComputedStyle;
148
+ return getComputedStyle(strip).direction === 'rtl';
149
+ }
150
+
151
+ /**
152
+ * `strip.scrollLeft`, renumbered so `0` is always the strip's physical LEFT
153
+ * edge and larger is always further physically right — whatever `direction`
154
+ * and whatever convention this engine uses to report it raw (FUT-2775).
155
+ *
156
+ * Every geometry read in this file (`getBoundingClientRect`) is already in
157
+ * that same physical, direction-blind coordinate space, so this is what makes
158
+ * `scrollLeft` combinable with it at all.
159
+ */
160
+ export function physicalScrollLeft(strip: HTMLElement): number {
161
+ const raw = strip.scrollLeft;
162
+ if (!isRtlStrip(strip)) return raw;
163
+ const max = strip.scrollWidth - strip.clientWidth;
164
+ const type = detectRtlScrollType(strip.ownerDocument);
165
+ if (type === 'negative') return raw + max;
166
+ if (type === 'reverse') return max - raw;
167
+ return raw;
168
+ }
169
+
170
+ /** The inverse of {@link physicalScrollLeft}: a physical offset back to this engine's raw `scrollLeft`. */
171
+ export function toRawScrollLeft(strip: HTMLElement, physical: number): number {
172
+ if (!isRtlStrip(strip)) return physical;
173
+ const max = strip.scrollWidth - strip.clientWidth;
174
+ const type = detectRtlScrollType(strip.ownerDocument);
175
+ if (type === 'negative') return physical - max;
176
+ if (type === 'reverse') return max - physical;
177
+ return physical;
65
178
  }
66
179
 
67
180
  /** Whether the strip came to rest where it last aimed itself — clamped as the browser would. */
68
181
  function restsOnAim(strip: HTMLElement, aimed: number): boolean {
69
182
  const reachable = Math.min(aimed, strip.scrollWidth - strip.clientWidth);
70
183
  // Written so that `NaN` — the strip never aimed — reads as "not on aim".
71
- return Math.abs(strip.scrollLeft - reachable) <= LANDED_SLACK;
184
+ return Math.abs(physicalScrollLeft(strip) - reachable) <= LANDED_SLACK;
185
+ }
186
+
187
+ /** The pointer/touch/keydown/wheel listeners that arm `state`, split out of `watchVisitorScroll` (line budget). */
188
+ function createGestureListeners(state: WatchState): { onStrip: Listeners; onView: Listeners } {
189
+ // `MouseEvent`, not `PointerEvent`: a real `PointerEvent` IS one (it extends
190
+ // `MouseEvent`), so this reads `clientX` off it just the same in a browser —
191
+ // but jsdom (this file's own test environment) has never implemented
192
+ // `PointerEvent` at all, and the bare identifier throws a `ReferenceError`
193
+ // the moment anything evaluates `instanceof PointerEvent`, pointer event or
194
+ // not. Same reasoning as feature-checking `scrollTo` elsewhere in this file:
195
+ // nothing in here is worth a crash over which constructor a probe prefers.
196
+ const onPointerDown = (event: Event): void => {
197
+ state.pointer = true;
198
+ state.pointerMoved = false;
199
+ state.pointerDownAt = event instanceof MouseEvent ? event.clientX : Number.NaN;
200
+ };
201
+ const onPointerMove = (event: Event): void => {
202
+ if (!(event instanceof MouseEvent) || Number.isNaN(state.pointerDownAt)) return;
203
+ if (Math.abs(event.clientX - state.pointerDownAt) > POINTER_MOVE_EPSILON) state.pointerMoved = true;
204
+ };
205
+ const onPointerUp = (): void => {
206
+ state.pointer = false;
207
+ state.pointerDownAt = Number.NaN;
208
+ };
209
+ const onTouchStart = (event: Event): void => {
210
+ state.touch = true;
211
+ state.touchMoved = false;
212
+ state.touchDownAt = event instanceof TouchEvent ? (event.touches[0]?.clientX ?? Number.NaN) : Number.NaN;
213
+ };
214
+ const onTouchMove = (event: Event): void => {
215
+ if (!(event instanceof TouchEvent) || Number.isNaN(state.touchDownAt)) return;
216
+ const clientX = event.touches[0]?.clientX;
217
+ if (clientX !== undefined && Math.abs(clientX - state.touchDownAt) > POINTER_MOVE_EPSILON) {
218
+ state.touchMoved = true;
219
+ }
220
+ };
221
+ const onTouchEnd = (): void => {
222
+ state.touch = false;
223
+ state.touchDownAt = Number.NaN;
224
+ };
225
+ const onPointerCancel = (): void => {
226
+ state.nudged = true;
227
+ onPointerUp();
228
+ };
229
+ const onKeydown = (): void => {
230
+ state.nudged = true;
231
+ };
232
+ const onWheel = (event: Event): void => {
233
+ if (horizontalDelta(event) !== 0) state.nudged = true;
234
+ };
235
+ return {
236
+ onStrip: [
237
+ ['pointerdown', onPointerDown],
238
+ ['pointermove', onPointerMove],
239
+ ['touchstart', onTouchStart],
240
+ ['touchmove', onTouchMove],
241
+ ['pointercancel', onPointerCancel],
242
+ ['keydown', onKeydown],
243
+ ['wheel', onWheel],
244
+ ],
245
+ // A gesture can END off the strip, so its end is heard on the window.
246
+ onView: [
247
+ ['pointerup', onPointerUp],
248
+ ['pointercancel', onPointerUp],
249
+ ['touchend', onTouchEnd],
250
+ ['touchcancel', onTouchEnd],
251
+ ],
252
+ };
72
253
  }
73
254
 
74
255
  /**
@@ -115,12 +296,12 @@ export function watchVisitorScroll(strip: HTMLElement): VisitorScrollWatch {
115
296
  aimed: Number.NaN,
116
297
  quiet: undefined,
117
298
  settled: () => undefined,
299
+ lastScrollLeft: physicalScrollLeft(strip),
300
+ pointerDownAt: Number.NaN,
301
+ pointerMoved: false,
302
+ touchDownAt: Number.NaN,
303
+ touchMoved: false,
118
304
  };
119
- const set =
120
- (key: 'pointer' | 'touch' | 'nudged', value: boolean) =>
121
- (): void => {
122
- state[key] = value;
123
- };
124
305
  const settle = (): void => {
125
306
  // Only a scroll that was UNDER JUDGEMENT disarms. Chromium can end the
126
307
  // strip's own smooth scroll with a `scrollend` when another scroll cuts it
@@ -132,33 +313,36 @@ export function watchVisitorScroll(strip: HTMLElement): VisitorScrollWatch {
132
313
  if (!restsOnAim(strip, state.aimed)) state.scrolled = true;
133
314
  state.settled();
134
315
  };
135
- const onWheel = (event: Event): void => {
136
- if (horizontalDelta(event) !== 0) state.nudged = true;
137
- };
138
316
  const onScroll = (): void => {
139
- if (state.pointer || state.touch || state.nudged) state.undecided = true;
317
+ const now = physicalScrollLeft(strip);
318
+ const movedFarEnough = Math.abs(now - state.lastScrollLeft) > SCROLL_DELTA_EPSILON;
319
+ state.lastScrollLeft = now;
320
+ // Pointer/touch need BOTH signals (FUT-2775): the pointer/finger must have
321
+ // itself MOVED since going down, AND this tick's delta must be real. Either
322
+ // alone still latches on the other's shape — a pointer merely resting on
323
+ // the strip while its OWN `scrollTo` produces real per-tick deltas would
324
+ // arm under a delta-only check (`pointerMoved`/`touchMoved` rules that out);
325
+ // a pointer that drifts a sub-epsilon amount while resting would arm under
326
+ // a moved-only check with no delta floor (`movedFarEnough` rules that out).
327
+ const pointerDrag = state.pointer && state.pointerMoved && movedFarEnough;
328
+ const touchDrag = state.touch && state.touchMoved && movedFarEnough;
329
+ // `nudged` (a keypress, a sideways wheel, the browser taking the pointer
330
+ // over to pan) is already a discrete, one-shot gesture with no "resting
331
+ // pointer coincides with an unrelated animation" shape to rule out — unlike
332
+ // pointer/touch, requiring a delta on the SAME tick that arms it would only
333
+ // race the strip's own scroll ending before a qualifying tick arrives (seen:
334
+ // it broke the existing "keeps a wheel/drag scroll" stories against a real
335
+ // browser's `scrollend` timing). So it arms as it always has.
336
+ if (pointerDrag || touchDrag || state.nudged) state.undecided = true;
140
337
  if (!state.undecided || hasScrollEnd) return;
141
338
  // Every tick while undecided, armed or not: a quiet spell that began while
142
339
  // the strip was still moving would judge it mid-flight, away from its aim.
143
340
  clearTimeout(state.quiet);
144
341
  state.quiet = setTimeout(settle, QUIET_BEFORE_SETTLED_MS);
145
342
  };
146
- const onStrip: Listeners = [
147
- ['pointerdown', set('pointer', true)],
148
- ['touchstart', set('touch', true)],
149
- ['pointercancel', set('nudged', true)],
150
- ['keydown', set('nudged', true)],
151
- ['wheel', onWheel],
152
- ['scroll', onScroll],
153
- ['scrollend', settle],
154
- ];
155
- // A gesture can END off the strip, so its end is heard on the window.
156
- const onView: Listeners = [
157
- ['pointerup', set('pointer', false)],
158
- ['pointercancel', set('pointer', false)],
159
- ['touchend', set('touch', false)],
160
- ['touchcancel', set('touch', false)],
161
- ];
343
+ const gestures = createGestureListeners(state);
344
+ const onStrip: Listeners = [...gestures.onStrip, ['scroll', onScroll], ['scrollend', settle]];
345
+ const onView = gestures.onView;
162
346
  for (const [type, listener] of onStrip) strip.addEventListener(type, listener, { passive: true });
163
347
  for (const [type, listener] of onView) view?.addEventListener(type, listener, { passive: true });
164
348
  return {
@@ -4,7 +4,7 @@ import Box from '@mui/material/Box/index.js';
4
4
  import { alpha, useTheme, type Theme } from '@mui/material/styles/index.js';
5
5
  import React, { useEffect, useRef, type RefObject } from 'react';
6
6
 
7
- import { watchVisitorScroll } from './SettingsSectionChips.scroll';
7
+ import { physicalScrollLeft, toRawScrollLeft, watchVisitorScroll } from './SettingsSectionChips.scroll';
8
8
  import { SettingsStatusMarker } from './SettingsStatusMarker';
9
9
  import { TOUCH_TARGET } from './SettingsLayout.styles';
10
10
  import { rem, sxRem } from '../../../tokens/relative';
@@ -48,11 +48,20 @@ function prefersReducedMotion(): boolean {
48
48
  * it. The clamp hid that for the last chip; a middle one was simply off-centre.
49
49
  * The boxes are both in viewport coordinates, so their difference is the strip's
50
50
  * own, whatever the host wraps it in.
51
+ *
52
+ * Read and returned in PHYSICAL pixels (`physicalScrollLeft`, FUT-2775): the
53
+ * bounding-rect arithmetic above is already direction-blind, and an RTL
54
+ * scroller's raw `scrollLeft` is not — the CSSOM View spec has it `0` at the
55
+ * strip's right-hand start and NEGATIVE past it, not `0`-to-`scrollWidth` like
56
+ * an LTR one. `Math.max(0, …)` alone, against that raw value, would clamp every
57
+ * legitimate RTL target straight back to the start. Physical throughout keeps
58
+ * this comparable to {@link VisitorScrollWatch.aimedAt}; the caller converts to
59
+ * this engine's raw `scrollLeft` (`toRawScrollLeft`) only at the `scrollTo` call.
51
60
  */
52
61
  function centredScrollLeft(strip: HTMLElement, chip: HTMLElement): number {
53
62
  const chipBox = chip.getBoundingClientRect();
54
63
  const visibleStart = strip.getBoundingClientRect().left + strip.clientLeft;
55
- const chipCentre = chipBox.left - visibleStart + strip.scrollLeft + chipBox.width / 2;
64
+ const chipCentre = chipBox.left - visibleStart + physicalScrollLeft(strip) + chipBox.width / 2;
56
65
  const target = chipCentre - strip.clientWidth / 2;
57
66
  return Math.max(0, Math.min(target, strip.scrollWidth - strip.clientWidth));
58
67
  }
@@ -122,13 +131,16 @@ function useCentreActiveChip(
122
131
  // Not while a scroll that may be the visitor's is still moving: a resize
123
132
  // mid-drag must not yank the strip. It is re-tried once that scroll rests.
124
133
  if (!chip || visitor.hasScrolled() || visitor.isUndecided()) return;
134
+ // Physical pixels throughout (FUT-2775) — comparable to `aimedAt()` and to
135
+ // what `beforeOwnScroll` records; converted to this engine's raw
136
+ // `scrollLeft` only for the actual `scrollTo` call below.
125
137
  const left = centredScrollLeft(strip, chip);
126
138
  // Only when the target MOVED: a resize that leaves it where it was must not
127
139
  // restart a smooth scroll that is already on its way there. The aim is a
128
140
  // runtime-computed scroll offset, like the exempt `left` below — not a size.
129
141
  if (Math.abs(left - visitor.aimedAt()) < 0.5) return;
130
142
  visitor.beforeOwnScroll(left);
131
- strip.scrollTo({ left, behavior: prefersReducedMotion() ? 'auto' : 'smooth' });
143
+ strip.scrollTo({ left: toRawScrollLeft(strip, left), behavior: prefersReducedMotion() ? 'auto' : 'smooth' });
132
144
  };
133
145
  visitor.onSettled(centre);
134
146
  centre();
@@ -250,7 +262,15 @@ export function SettingsSectionChips({
250
262
  display: 'flex',
251
263
  alignItems: 'center',
252
264
  gap: 0.75,
253
- px: 0.25,
265
+ // The start side keeps its old, symmetric 0.25 — nothing sits flush
266
+ // against it once scrolled. The END side is where a scrolled-to-end
267
+ // chip's own border used to sit almost against the clip boundary
268
+ // (FUT-2775): sized like the gap BETWEEN chips (`gap` above) rather
269
+ // than invented fresh, so the last chip clears the edge by the same
270
+ // margin any two chips clear each other. Logical, not `pr`: the side
271
+ // that gets clipped is the strip's END, whichever edge that is.
272
+ paddingInlineStart: 0.25,
273
+ paddingInlineEnd: 0.75,
254
274
  py: 0.25,
255
275
  overflowX: 'auto',
256
276
  overflowY: 'hidden',
@@ -24,6 +24,16 @@ export interface LightboxCopy {
24
24
  /** The slideshow toggle, in its two states. */
25
25
  play: string;
26
26
  pause: string;
27
+ /** The dialog's own accessible name, read out before its hidden title. */
28
+ dialogLabel: string;
29
+ /** The hidden title's item count, when the current item has no `alt`. */
30
+ itemPosition: (index: number, total: number) => string;
31
+ /** A `<video>`'s accessible name, when the item has no `alt`. */
32
+ videoFallback: (index: number, total: number) => string;
33
+ /** An `<img>`'s `alt`, when the item has no `alt`. */
34
+ imageFallback: (index: number, total: number) => string;
35
+ /** A filmstrip thumbnail's `alt`, when the item has no `alt`. */
36
+ thumbnailFallback: (index: number) => string;
27
37
  }
28
38
 
29
39
  /** The map preview's control bar — same shape, same reason, as the lightbox. */
@@ -42,10 +52,40 @@ export interface CarouselCopy {
42
52
  next: string;
43
53
  }
44
54
 
45
- /** The request-timing diagram's own two strings. */
55
+ /** The request-timing diagram's own words: its region, its phases, its totals. */
46
56
  export interface TimingDiagramCopy {
47
57
  regionLabel: string;
48
58
  heading: string;
59
+ /** The five phases a waterfall/stacked/horizontal bar can show. */
60
+ dns: string;
61
+ connect: string;
62
+ ssl: string;
63
+ request: string;
64
+ response: string;
65
+ /** The stacked view's own total sentence, e.g. "Total: 1.20s". */
66
+ total: (formatted: string) => string;
67
+ /** The horizontal view's own total sentence, e.g. "Total Time: 1.20s". */
68
+ totalTime: (formatted: string) => string;
69
+ }
70
+
71
+ /**
72
+ * The DataGrid's own words — every one a fallback the caller can still
73
+ * override: `column.ariaLabel`, `expansion.expandLabel`/`collapseLabel` and
74
+ * the grid's own `ariaLabel` all win when given. REQUIRED: the fallbacks were
75
+ * English literals with no way to localise them at all.
76
+ */
77
+ export interface DataGridCopy {
78
+ /** A sortable header button's accessible name, unless the column's own `ariaLabel` wins. */
79
+ sortBy: (header: string) => string;
80
+ /** A row's selection checkbox, unless `selection` is off. */
81
+ selectRow: (rowNumber: number) => string;
82
+ /** The header's select-all checkbox. */
83
+ selectAllRows: string;
84
+ /** The expand chevron's two states, unless `expansion.expandLabel`/`collapseLabel` win. */
85
+ expandRow: string;
86
+ collapseRow: string;
87
+ /** The grid's own accessible name, unless the caller's `ariaLabel` wins. */
88
+ gridLabel: string;
49
89
  }
50
90
 
51
91
  export interface DataStateCopy {
@@ -15,12 +15,18 @@
15
15
  * table, the grid, the async container, the hover card and the infinite
16
16
  * scroller each rendered their own copy of.
17
17
  */
18
- /** The guided tour's four controls, and the toast/modal chrome around it. */
18
+ /** The guided tour's controls, and the toast/modal chrome around it. */
19
19
  export interface TutorialCopy {
20
20
  skip: string;
21
21
  previous: string;
22
22
  next: string;
23
23
  restart: string;
24
+ /** The last step's own next button, when it is the tour's only step. */
25
+ complete: string;
26
+ /** The last step's own next button, in a tour of more than one step. */
27
+ finish: string;
28
+ /** The next/finish button's title while `requiresActionBeforeNext` blocks it. */
29
+ requiresAction: string;
24
30
  }
25
31
 
26
32
  /** Chrome that carries a glyph and no text, across several small surfaces. */
@@ -5,6 +5,7 @@
5
5
  import type { DataViewsCopy } from "./components/data-display/DataViews/data-views-copy";
6
6
  import type {
7
7
  CarouselCopy,
8
+ DataGridCopy,
8
9
  DataStateCopy,
9
10
  LightboxCopy,
10
11
  MapPreviewCopy,
@@ -26,6 +27,20 @@ export const EN_US_LIGHTBOX_COPY: LightboxCopy = {
26
27
  resetZoom: "Reset zoom",
27
28
  play: "Start the slideshow",
28
29
  pause: "Pause the slideshow",
30
+ dialogLabel: "Lightbox",
31
+ itemPosition: (index, total) => `Item ${index} of ${total}`,
32
+ videoFallback: (index, total) => `Video ${index} of ${total}`,
33
+ imageFallback: (index, total) => `Image ${index} of ${total}`,
34
+ thumbnailFallback: (index) => `Thumbnail ${index}`,
35
+ };
36
+
37
+ export const EN_US_DATA_GRID_COPY: DataGridCopy = {
38
+ sortBy: (header) => `Sort by ${header}`,
39
+ selectRow: (rowNumber) => `Select row ${rowNumber}`,
40
+ selectAllRows: "Select all rows",
41
+ expandRow: "Expand row",
42
+ collapseRow: "Collapse row",
43
+ gridLabel: "Data grid",
29
44
  };
30
45
 
31
46
  export const EN_US_MAP_PREVIEW_COPY: MapPreviewCopy = {
@@ -45,6 +60,13 @@ export const EN_US_CAROUSEL_COPY: CarouselCopy = {
45
60
  export const EN_US_TIMING_DIAGRAM_COPY: TimingDiagramCopy = {
46
61
  regionLabel: "Timing diagram",
47
62
  heading: "Request timing",
63
+ dns: "DNS Lookup",
64
+ connect: "Connection",
65
+ ssl: "SSL/TLS",
66
+ request: "Request",
67
+ response: "Response",
68
+ total: (formatted) => `Total: ${formatted}`,
69
+ totalTime: (formatted) => `Total Time: ${formatted}`,
48
70
  };
49
71
 
50
72
  export const EN_US_DATA_STATE_COPY: DataStateCopy = {
@@ -161,6 +183,7 @@ export const EN_US_DATA_VIEWS_COPY: DataViewsCopy = {
161
183
  },
162
184
  tableFilter: EN_US_TABLE_FILTER_COPY,
163
185
  grid: {
186
+ ...EN_US_DATA_GRID_COPY,
164
187
  rowActions: "Actions",
165
188
  bulkActions: (selectedCount) => `Actions (${selectedCount}) ▾`,
166
189
  emptyFilteredTitle: "No results for these filters",
@@ -13,6 +13,9 @@ export const EN_US_TUTORIAL_COPY: TutorialCopy = {
13
13
  previous: "Back",
14
14
  next: "Next",
15
15
  restart: "Start over",
16
+ complete: "Complete",
17
+ finish: "Finish",
18
+ requiresAction: "Complete the required action first",
16
19
  };
17
20
 
18
21
  export const EN_US_CHROME_COPY: ChromeCopy = {
package/src/locales.ts CHANGED
@@ -30,6 +30,7 @@ import type {
30
30
  CodeEditorCopy,
31
31
  CommandPaletteCopy,
32
32
  ConfirmActionCopy,
33
+ DataGridCopy,
33
34
  DataStateCopy,
34
35
  InstallPromptCopy,
35
36
  LightboxCopy,
@@ -69,6 +70,7 @@ export const DATA_STATE_COPY = pack<DataStateCopy>(
69
70
  pt.PT_BR_DATA_STATE_COPY,
70
71
  en.EN_US_DATA_STATE_COPY,
71
72
  );
73
+ export const DATA_GRID_COPY = pack<DataGridCopy>(pt.PT_BR_DATA_GRID_COPY, en.EN_US_DATA_GRID_COPY);
72
74
  export const DATA_VIEWS_COPY = pack<DataViewsCopy>(
73
75
  pt.PT_BR_DATA_VIEWS_COPY,
74
76
  en.EN_US_DATA_VIEWS_COPY,
@@ -5,6 +5,7 @@
5
5
  import type { DataViewsCopy } from "./components/data-display/DataViews/data-views-copy";
6
6
  import type {
7
7
  CarouselCopy,
8
+ DataGridCopy,
8
9
  DataStateCopy,
9
10
  LightboxCopy,
10
11
  MapPreviewCopy,
@@ -26,6 +27,20 @@ export const PT_BR_LIGHTBOX_COPY: LightboxCopy = {
26
27
  resetZoom: "Restaurar o zoom",
27
28
  play: "Iniciar a apresentação",
28
29
  pause: "Pausar a apresentação",
30
+ dialogLabel: "Galeria de imagens",
31
+ itemPosition: (index, total) => `Item ${index} de ${total}`,
32
+ videoFallback: (index, total) => `Vídeo ${index} de ${total}`,
33
+ imageFallback: (index, total) => `Imagem ${index} de ${total}`,
34
+ thumbnailFallback: (index) => `Miniatura ${index}`,
35
+ };
36
+
37
+ export const PT_BR_DATA_GRID_COPY: DataGridCopy = {
38
+ sortBy: (header) => `Ordenar por ${header}`,
39
+ selectRow: (rowNumber) => `Selecionar linha ${rowNumber}`,
40
+ selectAllRows: "Selecionar todas as linhas",
41
+ expandRow: "Expandir linha",
42
+ collapseRow: "Recolher linha",
43
+ gridLabel: "Grade de dados",
29
44
  };
30
45
 
31
46
  export const PT_BR_MAP_PREVIEW_COPY: MapPreviewCopy = {
@@ -45,6 +60,13 @@ export const PT_BR_CAROUSEL_COPY: CarouselCopy = {
45
60
  export const PT_BR_TIMING_DIAGRAM_COPY: TimingDiagramCopy = {
46
61
  regionLabel: "Diagrama de tempos",
47
62
  heading: "Tempo da requisição",
63
+ dns: "Busca de DNS",
64
+ connect: "Conexão",
65
+ ssl: "SSL/TLS",
66
+ request: "Requisição",
67
+ response: "Resposta",
68
+ total: (formatted) => `Total: ${formatted}`,
69
+ totalTime: (formatted) => `Tempo total: ${formatted}`,
48
70
  };
49
71
 
50
72
  export const PT_BR_DATA_STATE_COPY: DataStateCopy = {
@@ -158,6 +180,7 @@ export const PT_BR_DATA_VIEWS_COPY: DataViewsCopy = {
158
180
  },
159
181
  tableFilter: PT_BR_TABLE_FILTER_COPY,
160
182
  grid: {
183
+ ...PT_BR_DATA_GRID_COPY,
161
184
  rowActions: "Ações",
162
185
  bulkActions: (selectedCount) => `Ações (${selectedCount}) ▾`,
163
186
  emptyFilteredTitle: "Nenhum resultado para esses filtros",
@@ -13,6 +13,9 @@ export const PT_BR_TUTORIAL_COPY: TutorialCopy = {
13
13
  previous: "Anterior",
14
14
  next: "Próximo",
15
15
  restart: "Recomeçar",
16
+ complete: "Concluir",
17
+ finish: "Finalizar",
18
+ requiresAction: "Conclua a ação obrigatória primeiro",
16
19
  };
17
20
 
18
21
  export const PT_BR_CHROME_COPY: ChromeCopy = {
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/en-US.data-display.ts"],"sourcesContent":["/**\n * The en-US pack for the data-display family. Split the same way\n * `pt-BR.data-display.ts` is — see `en-US.ts` for why.\n */\nimport type { DataViewsCopy } from \"./components/data-display/DataViews/data-views-copy\";\nimport type {\n CarouselCopy,\n DataStateCopy,\n LightboxCopy,\n MapPreviewCopy,\n TimingDiagramCopy,\n} from './copy';\nimport { EN_US_TABLE_FILTER_COPY } from './en-US.layout';\nimport {\n EN_US_CATEGORY_SELECT_COPY,\n EN_US_CONFIRM_ACTION_COPY,\n EN_US_SAVED_VIEWS_LABELS,\n} from './en-US.shared';\n\nexport const EN_US_LIGHTBOX_COPY: LightboxCopy = {\n close: \"Close\",\n previous: \"Previous\",\n next: \"Next\",\n zoomIn: \"Zoom in\",\n zoomOut: \"Zoom out\",\n resetZoom: \"Reset zoom\",\n play: \"Start the slideshow\",\n pause: \"Pause the slideshow\",\n};\n\nexport const EN_US_MAP_PREVIEW_COPY: MapPreviewCopy = {\n zoomIn: \"Zoom in\",\n zoomOut: \"Zoom out\",\n center: \"Centre the map\",\n mapType: \"Change the map type\",\n fullscreen: \"Full screen\",\n search: \"Search for an address\",\n};\n\nexport const EN_US_CAROUSEL_COPY: CarouselCopy = {\n previous: \"Previous\",\n next: \"Next\",\n};\n\nexport const EN_US_TIMING_DIAGRAM_COPY: TimingDiagramCopy = {\n regionLabel: \"Timing diagram\",\n heading: \"Request timing\",\n};\n\nexport const EN_US_DATA_STATE_COPY: DataStateCopy = {\n empty: \"Nothing here\",\n loading: \"Loading…\",\n endOfList: \"No more items to load\",\n dismissAlert: \"Dismiss\",\n dismissBanner: \"Dismiss this banner\",\n};\n\nexport const EN_US_DATA_VIEWS_COPY: DataViewsCopy = {\n savedViewsMenu: EN_US_SAVED_VIEWS_LABELS,\n confirmAction: EN_US_CONFIRM_ACTION_COPY,\n categorySelect: EN_US_CATEGORY_SELECT_COPY,\n board: {\n countOnPage: (label, count) => `${label}: ${count} on this page`,\n onThisPage: (count) => `${count} on this page`,\n pageSum: (sum) => `This page totals ${sum}`,\n emptyColumn: \"Nothing at this stage\",\n pageScopeNote: \"The counts and totals below cover only the items on this page.\",\n },\n deleteView: {\n title: \"Delete view\",\n target: (viewName) => `Delete \"${viewName}\"?`,\n sharedWarning: \"This view is shared — the team will lose access to it.\",\n // `entityLabel` is the HOST's word for what the rows are, so the sentence is\n // built around it rather than naming a noun of its own.\n rowsUnaffected: (entityLabel) =>\n `${entityLabel} are not affected; only the saved slice is removed.`,\n confirm: \"Delete view\",\n cancel: \"Cancel\",\n },\n manageViews: {\n title: \"Manage views\",\n empty: \"No saved views.\",\n edit: \"Edit\",\n defaultTag: \"Default\",\n otherUserTag: \"Another user's\",\n deleteTitle: \"Delete this saved view?\",\n deleteBody: \"The filters and columns it holds are lost. This cannot be undone.\",\n deleteConfirm: \"Delete\",\n },\n saveView: {\n sharedDescription: \"Anyone in the store will be able to open and use this view.\",\n setDefaultTitle: \"Set as default\",\n setDefaultDescription: \"This screen opens on this view instead of the Main view.\",\n previewHeading: \"What this view holds\",\n previewUnchanged: \"Nothing has changed — this view will match the Main view.\",\n sortRowLabel: \"Sorting\",\n saveFailed: \"Could not save the view.\",\n titleEditing: \"Edit view\",\n titleCreating: \"Save view\",\n descriptionLabel: \"Description — optional\",\n descriptionPlaceholder: \"What this view is for\",\n submitEditing: \"Save changes\",\n submitCreating: \"Save view\",\n nameLabel: \"Name\",\n cancel: \"Cancel\",\n pinnedTitle: \"Pin to the sidebar\",\n pinnedDescription: \"Appears as a shortcut in the menu, under this screen.\",\n sharedTitle: \"Share with the team\",\n namePlaceholder: \"e.g. Declined card payments this week\",\n previewFilters: \"Filters\",\n previewHiddenColumns: \"Hidden columns\",\n },\n columns: {\n visibleCount: (visible, total) => `${visible} of ${total} visible`,\n reset: \"Default\",\n showAll: \"Show all\",\n dragHint: \"Drag to reorder the columns.\",\n moveUp: (columnLabel) => `Move ${columnLabel} up`,\n moveDown: (columnLabel) => `Move ${columnLabel} down`,\n },\n display: {\n sortTab: \"Sort\",\n columnsTab: \"Columns\",\n panelTab: \"Display\",\n direction: \"Direction\",\n layoutHints: {\n table: \"Comparable columns, good for scanning numbers.\",\n list: \"One row per item, with the key fields.\",\n cards: \"Larger blocks, good for a handful of items.\",\n board: \"A column per stage, for tracking what is where.\",\n },\n densityHeadings: {\n table: \"Row height\",\n list: \"Row height\",\n cards: \"Cards per row\",\n board: \"Column width\",\n },\n densityLabels: {\n table: { compact: \"Low\", cozy: \"Medium\", comfortable: \"High\" },\n list: { compact: \"Low\", cozy: \"Medium\", comfortable: \"High\" },\n cards: { compact: \"Many\", cozy: \"Some\", comfortable: \"Few\" },\n board: { compact: \"Narrow\", cozy: \"Medium\", comfortable: \"Wide\" },\n },\n boardUnavailable: \"This screen declares no stages, so it offers no board.\",\n densityUnavailableNarrow:\n \"One card per row at this width — density returns on larger screens.\",\n trigger: \"Display\",\n fieldHeading: \"Field\",\n formatHeading: \"Format\",\n cardZoom: \"Card size\",\n },\n export: {\n formats: {\n // The extension is the file's, not a word: it stays as it is written on\n // disk in every language.\n json: { label: \"JSON (.json)\", hint: \"For integrations\" },\n },\n visibleColumns: (columnCount) => `${columnCount} visible columns, in their current order`,\n trigger: \"Export\",\n triggerLabel: \"Export\",\n },\n tableFilter: EN_US_TABLE_FILTER_COPY,\n grid: {\n rowActions: \"Actions\",\n bulkActions: (selectedCount) => `Actions (${selectedCount}) ▾`,\n emptyFilteredTitle: \"No results for these filters\",\n emptyFilteredHint: \"Adjust or remove the filters.\",\n emptyFilteredAction: \"Clear filters\",\n },\n filters: {\n moreFilters: (count) => `More filters: ${count} did not fit in the bar`,\n moreFiltersApplied: (count, appliedCount) =>\n `More filters: ${count} did not fit in the bar, ${appliedCount} applied`,\n moreTriggerLabel: \"More\",\n moreHeading: \"More filters\",\n overflowNote: \"did not fit in the bar\",\n rangeEnd: \"To\",\n rangeInvalid: \"The start must be earlier than the end.\",\n rangeInclusiveNote: \"Both dates are included in the result.\",\n rangeStart: \"From\",\n // The mask is what the reader TYPES, so it follows their own date order\n // rather than being carried over. A translated label above an unchanged\n // mask is the shape that gets a date entered backwards.\n dayMask: \"mm/dd/yyyy\",\n clearAllFilters: \"Clear all filters\",\n // The KEYS are the package's own preset ids and stay as they are — the\n // surface matches on them. Only the labels beside them are words.\n rangePresets: {\n hoje: \"Today\",\n ontem: \"Yesterday\",\n semana: \"This week\",\n mes: \"This month\",\n ano: \"This year\",\n },\n scopesLabel: \"Status\",\n panelTitle: \"Filters\",\n clear: \"Clear\",\n clearAll: \"Clear filters\",\n clearRange: (label) => `Clear ${label}`,\n allOption: \"All\",\n optionSearchPlaceholder: \"Search…\",\n optionsEmpty: \"No results\",\n optionsHeading: \"Options\",\n },\n selection: {\n selectAllOnPage: \"Select all on this page\",\n clearSelection: \"Clear selection\",\n selectAll: \"Select all\",\n onThisPage: (count) => `${count} on this page`,\n selectRow: \"Select\",\n expandRow: \"Expand details\",\n collapseRow: \"Collapse details\",\n },\n nav: {\n mainView: \"Main view\",\n defaultTag: \"Default\",\n viewOptions: (viewName) => `${viewName} options`,\n emptyHint: \"No saved views. Adjust filters, columns or sorting and save below.\",\n setDefault: \"Set as default\",\n deleteView: \"Delete view\",\n unsavedChanges: \"Unsaved changes\",\n label: \"View\",\n update: \"Update view\",\n save: \"Save view\",\n saveAs: \"Save as new\",\n reset: \"Reset\",\n updateFailed: \"Could not update the view\",\n dismissError: \"Dismiss\",\n goBack: \"Back\",\n pageSize: \"Show:\",\n saveFailed: \"Could not save the change to the view.\",\n editView: \"Rename and edit\",\n pinToSidebar: \"Pin to the sidebar\",\n shareWithTeam: \"Share with the team\",\n },\n search: {\n placeholder: \"Search…\",\n allColumnsLabel: \"Search every column\",\n keywordPlaceholder: \"Press Enter to filter\",\n open: \"Search\",\n close: \"Close search\",\n clear: \"Clear search\",\n },\n};\n"],"mappings":"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","names":[]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/components/data-display/TimingDiagram/TimingDiagram.tsx"],"sourcesContent":["import Box from '@mui/material/Box/index.js';\nimport Paper from '@mui/material/Paper/index.js';\nimport Typography from '@mui/material/Typography/index.js';\nimport { alpha, styled } from '@mui/material/styles/index.js';\nimport type { FC } from 'react';\nimport React, { useMemo } from 'react';\n\nimport type { TimingData, TimingDiagramProps } from './TimingDiagram.types';\nimport type { Phase, PhaseKey, TimingViewProps } from './TimingDiagram.views';\nimport {\n HorizontalView,\n phaseColors,\n StackedView,\n WaterfallView,\n} from './TimingDiagram.views';\nimport { rem } from '../../../tokens/relative';\n\n// Styled components\nconst DiagramContainer = styled(Paper)(({ theme }) => ({\n padding: theme.spacing(3),\n background: `linear-gradient(135deg, ${alpha(theme.palette.background.paper, 0.9)} 0%, ${alpha(theme.palette.background.paper, 0.8)} 100%)`,\n backdropFilter: `blur(${rem(theme, 10)})`,\n WebkitBackdropFilter: `blur(${rem(theme, 10)})`,\n border: `1px solid ${alpha(theme.palette.divider, 0.18)}`,\n borderRadius: theme.shape.borderRadius * 2,\n}));\n\nconst Legend = styled(Box)(({ theme }) => ({\n display: 'flex',\n flexWrap: 'wrap',\n gap: theme.spacing(2),\n marginTop: theme.spacing(2),\n paddingTop: theme.spacing(2),\n borderTop: `1px solid ${alpha(theme.palette.divider, 0.2)}`,\n}));\n\nconst LegendItem = styled(Box)(({ theme }) => ({\n display: 'flex',\n alignItems: 'center',\n gap: theme.spacing(1),\n '& .color': {\n width: rem(theme, 12),\n height: rem(theme, 12),\n borderRadius: rem(theme, 2),\n },\n '& .label': {\n fontSize: rem(theme, 12.8),\n color: theme.palette.text.secondary,\n },\n}));\n\n// Helper functions\nconst calculatePercentages = (data: TimingData): Record<string, number> => {\n const total = data.total || 1;\n return {\n dns: ((data.dns || 0) / total) * 100,\n connect: ((data.connect || 0) / total) * 100,\n ssl: ((data.ssl || 0) / total) * 100,\n request: ((data.request || 0) / total) * 100,\n response: ((data.response || 0) / total) * 100,\n };\n};\n\n// A phase with no recorded time never happened — an HTTP request has no SSL\n// handshake, for instance — so it gets no bar and no legend entry.\nconst phasesOf = (data: TimingData): Phase[] =>\n [\n { key: 'dns', label: 'DNS Lookup', value: data.dns },\n { key: 'connect', label: 'Connection', value: data.connect },\n { key: 'ssl', label: 'SSL/TLS', value: data.ssl },\n { key: 'request', label: 'Request', value: data.request },\n { key: 'response', label: 'Response', value: data.response },\n ].filter((phase) => phase.value !== undefined && phase.value > 0);\n\nconst VIEWS: Record<string, FC<TimingViewProps>> = {\n waterfall: WaterfallView,\n stacked: StackedView,\n horizontal: HorizontalView,\n};\n\n// Main component\nexport const TimingDiagram: FC<TimingDiagramProps> = ({\n copy,\n data,\n showLabels = true,\n height,\n animated = true,\n showTooltips = true,\n variant = 'waterfall',\n}) => {\n const percentages = useMemo(() => calculatePercentages(data), [data]);\n const phases = phasesOf(data);\n const View = VIEWS[variant];\n\n return (\n <DiagramContainer elevation={2} role=\"region\" aria-label={copy.regionLabel} tabIndex={0}>\n <Typography variant=\"h6\" fontWeight=\"bold\" gutterBottom>\n {copy.heading}\n </Typography>\n\n {View && (\n <View\n phases={phases}\n percentages={percentages}\n data={data}\n animated={animated}\n showLabels={showLabels}\n showTooltips={showTooltips}\n // The waterfall's plot height, 40 design px unless the caller says otherwise.\n plotHeightPx={height ?? 40}\n />\n )}\n\n {showLabels && (\n <Legend>\n {phases.map((phase) => (\n <LegendItem key={phase.key}>\n <Box\n className=\"color\"\n sx={(theme) => ({ backgroundColor: phaseColors(theme)[phase.key as PhaseKey] })}\n />\n <Typography className=\"label\">{phase.label}</Typography>\n </LegendItem>\n ))}\n </Legend>\n )}\n </DiagramContainer>\n );\n};\n\n// Export default\nexport default TimingDiagram;\n"],"mappings":"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","names":[]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/pt-BR.feedback.ts"],"sourcesContent":["/**\n * The pt-BR pack for the feedback family. Split out of `pt-BR.ts`, which is a\n * barrel over this folder — see that file for why.\n */\nimport type {\n ChromeCopy,\n NotificationUnblockCopy,\n TutorialCopy,\n} from './copy';\n\nexport const PT_BR_TUTORIAL_COPY: TutorialCopy = {\n skip: \"Pular\",\n previous: \"Anterior\",\n next: \"Próximo\",\n restart: \"Recomeçar\",\n};\n\nexport const PT_BR_CHROME_COPY: ChromeCopy = {\n dismissToast: \"Fechar o aviso\",\n goBack: \"Voltar\",\n closePanel: \"Fechar\",\n closeTab: \"Fechar a aba\",\n scrollRegion: \"Conteúdo rolável\",\n scrollToTop: \"Voltar ao topo\",\n share: \"Compartilhar\",\n};\n\nconst PT_BR_CHROMIUM_DESKTOP = [\n \"Clique no ícone à esquerda do endereço do site.\",\n \"Abra \\\"Configurações do site\\\".\",\n \"Em \\\"Notificações\\\", escolha \\\"Permitir\\\".\",\n \"Volte a esta página e recarregue.\",\n] as const;\n\nconst PT_BR_CHROMIUM_ANDROID = [\n \"Toque no ícone à esquerda do endereço do site.\",\n \"Toque em \\\"Permissões\\\".\",\n \"Toque em \\\"Notificações\\\" e escolha \\\"Permitir\\\".\",\n \"Volte a esta página e recarregue.\",\n] as const;\n\nexport const PT_BR_NOTIFICATION_UNBLOCK_COPY: NotificationUnblockCopy = {\n ios: [\n \"Abra os Ajustes do iPhone.\",\n \"Toque em \\\"Notificações\\\".\",\n \"Encontre este app na lista e ative \\\"Permitir Notificações\\\".\",\n ],\n chrome: { desktop: PT_BR_CHROMIUM_DESKTOP, android: PT_BR_CHROMIUM_ANDROID },\n edge: {\n desktop: [\n \"Clique no cadeado à esquerda do endereço do site.\",\n \"Abra \\\"Permissões para este site\\\".\",\n \"Em \\\"Notificações\\\", escolha \\\"Permitir\\\".\",\n \"Volte a esta página e recarregue.\",\n ],\n android: PT_BR_CHROMIUM_ANDROID,\n },\n firefox: {\n desktop: [\n \"Clique no ícone de permissões à esquerda do endereço do site.\",\n \"Ao lado de \\\"Enviar notificações\\\", clique no X para tirar o bloqueio.\",\n \"Recarregue a página e ative as notificações de novo.\",\n ],\n android: [\n \"Abra o menu do navegador e toque em \\\"Configurações\\\".\",\n \"Toque em \\\"Permissões de sites\\\" e depois em \\\"Notificações\\\".\",\n \"Encontre este site e escolha \\\"Permitir\\\".\",\n \"Volte a esta página e recarregue.\",\n ],\n },\n opera: { desktop: PT_BR_CHROMIUM_DESKTOP, android: PT_BR_CHROMIUM_ANDROID },\n safari: {\n desktop: [\n \"No menu Safari, abra \\\"Ajustes\\\".\",\n \"Vá na aba \\\"Sites\\\" e, na lateral, em \\\"Notificações\\\".\",\n \"Encontre este site e escolha \\\"Permitir\\\".\",\n \"Volte a esta página e recarregue.\",\n ],\n },\n samsung: {\n android: [\n \"Abra o menu do navegador e toque em \\\"Configurações\\\".\",\n \"Toque em \\\"Sites e downloads\\\" e depois em \\\"Notificações\\\".\",\n \"Encontre este site e permita as notificações.\",\n \"Volte a esta página e recarregue.\",\n ],\n },\n other: [\n \"Abra as configurações do navegador.\",\n \"Procure as permissões dos sites e depois \\\"Notificações\\\".\",\n \"Permita as notificações para este site.\",\n \"Volte a esta página e recarregue.\",\n ],\n};\n"],"mappings":";AAUO,IAAM,sBAAoC;AAAA,EAC/C,MAAM;AAAA,EACN,UAAU;AAAA,EACV,MAAM;AAAA,EACN,SAAS;AACX;AAEO,IAAM,oBAAgC;AAAA,EAC3C,cAAc;AAAA,EACd,QAAQ;AAAA,EACR,YAAY;AAAA,EACZ,UAAU;AAAA,EACV,cAAc;AAAA,EACd,aAAa;AAAA,EACb,OAAO;AACT;AAEA,IAAM,yBAAyB;AAAA,EAC7B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAEA,IAAM,yBAAyB;AAAA,EAC7B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAEO,IAAM,kCAA2D;AAAA,EACtE,KAAK;AAAA,IACH;AAAA,IACA;AAAA,IACA;AAAA,EACF;AAAA,EACA,QAAQ,EAAE,SAAS,wBAAwB,SAAS,uBAAuB;AAAA,EAC3E,MAAM;AAAA,IACJ,SAAS;AAAA,MACP;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,IACA,SAAS;AAAA,EACX;AAAA,EACA,SAAS;AAAA,IACP,SAAS;AAAA,MACP;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,IACA,SAAS;AAAA,MACP;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,EACF;AAAA,EACA,OAAO,EAAE,SAAS,wBAAwB,SAAS,uBAAuB;AAAA,EAC1E,QAAQ;AAAA,IACN,SAAS;AAAA,MACP;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,EACF;AAAA,EACA,SAAS;AAAA,IACP,SAAS;AAAA,MACP;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,EACF;AAAA,EACA,OAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AACF;","names":[]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/components/data-display/Lightbox/LightboxMedia.tsx"],"sourcesContent":["import type { FC } from 'react';\nimport React from 'react';\n\nimport type { LightboxItem } from './Lightbox.types';\n\nexport interface LightboxMediaProps {\n item: LightboxItem;\n index: number;\n total: number;\n zoomLevel: number;\n panOffset: { x: number; y: number };\n zoomable: boolean;\n isDragging: boolean;\n testId: (suffix: string) => string;\n onLoadStart: () => void;\n onLoaded: () => void;\n onWheel: (event: React.WheelEvent) => void;\n onDoubleClick: () => void;\n}\n\nconst cursorFor = (zoomLevel: number, zoomable: boolean): string => {\n if (zoomLevel > 1) return 'move';\n return zoomable ? 'zoom-in' : 'default';\n};\n\nexport const LightboxMedia: FC<LightboxMediaProps> = ({\n item,\n index,\n total,\n zoomLevel,\n panOffset,\n zoomable,\n isDragging,\n testId,\n onLoadStart,\n onLoaded,\n onWheel,\n onDoubleClick,\n}) => {\n if (item.type === 'video') {\n return (\n <video\n src={item.src}\n controls\n style={{ maxWidth: '90vw', maxHeight: '80vh', objectFit: 'contain' }}\n onLoadStart={onLoadStart}\n onLoadedData={onLoaded}\n aria-label={item.alt || `Video ${index + 1} of ${total}`}\n data-testid={testId('video')}\n />\n );\n }\n\n return (\n <img\n src={item.src}\n alt={item.alt || `Image ${index + 1} of ${total}`}\n style={{\n // At 1x the image is bounded by the viewport; once zoomed it is allowed\n // to overflow so there is something to pan around.\n maxWidth: zoomLevel === 1 ? '90vw' : 'none',\n maxHeight: zoomLevel === 1 ? '80vh' : 'none',\n transform: `scale(${zoomLevel}) translate(${panOffset.x}px, ${panOffset.y}px)`,\n transformOrigin: 'center',\n cursor: cursorFor(zoomLevel, zoomable),\n transition: isDragging ? 'none' : 'transform 0.2s ease',\n objectFit: 'contain',\n }}\n onLoad={onLoaded}\n onWheel={onWheel}\n onDoubleClick={onDoubleClick}\n draggable={false}\n data-testid={testId('image')}\n />\n );\n};\n"],"mappings":";AAyCM;AArBN,IAAM,YAAY,CAAC,WAAmB,aAA8B;AAClE,MAAI,YAAY,EAAG,QAAO;AAC1B,SAAO,WAAW,YAAY;AAChC;AAEO,IAAM,gBAAwC,CAAC;AAAA,EACpD;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,MAAM;AACJ,MAAI,KAAK,SAAS,SAAS;AACzB,WACE;AAAA,MAAC;AAAA;AAAA,QACC,KAAK,KAAK;AAAA,QACV,UAAQ;AAAA,QACR,OAAO,EAAE,UAAU,QAAQ,WAAW,QAAQ,WAAW,UAAU;AAAA,QACnE;AAAA,QACA,cAAc;AAAA,QACd,cAAY,KAAK,OAAO,SAAS,QAAQ,CAAC,OAAO,KAAK;AAAA,QACtD,eAAa,OAAO,OAAO;AAAA;AAAA,IAC7B;AAAA,EAEJ;AAEA,SACE;AAAA,IAAC;AAAA;AAAA,MACC,KAAK,KAAK;AAAA,MACV,KAAK,KAAK,OAAO,SAAS,QAAQ,CAAC,OAAO,KAAK;AAAA,MAC/C,OAAO;AAAA;AAAA;AAAA,QAGL,UAAU,cAAc,IAAI,SAAS;AAAA,QACrC,WAAW,cAAc,IAAI,SAAS;AAAA,QACtC,WAAW,SAAS,SAAS,eAAe,UAAU,CAAC,OAAO,UAAU,CAAC;AAAA,QACzE,iBAAiB;AAAA,QACjB,QAAQ,UAAU,WAAW,QAAQ;AAAA,QACrC,YAAY,aAAa,SAAS;AAAA,QAClC,WAAW;AAAA,MACb;AAAA,MACA,QAAQ;AAAA,MACR;AAAA,MACA;AAAA,MACA,WAAW;AAAA,MACX,eAAa,OAAO,OAAO;AAAA;AAAA,EAC7B;AAEJ;","names":[]}