@coinbase/cds-web 9.13.0 → 9.15.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 (46) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/dts/accordion/Accordion.d.ts +27 -4
  3. package/dts/accordion/Accordion.d.ts.map +1 -1
  4. package/dts/accordion/AccordionHeader.d.ts +36 -4
  5. package/dts/accordion/AccordionHeader.d.ts.map +1 -1
  6. package/dts/accordion/AccordionItem.d.ts +26 -15
  7. package/dts/accordion/AccordionItem.d.ts.map +1 -1
  8. package/dts/accordion/AccordionPanel.d.ts +7 -1
  9. package/dts/accordion/AccordionPanel.d.ts.map +1 -1
  10. package/dts/collapsible/Collapsible.d.ts +7 -1
  11. package/dts/collapsible/Collapsible.d.ts.map +1 -1
  12. package/dts/controls/Switch.d.ts +14 -0
  13. package/dts/controls/Switch.d.ts.map +1 -1
  14. package/dts/core/componentConfig.d.ts +2 -0
  15. package/dts/core/componentConfig.d.ts.map +1 -1
  16. package/dts/tabs/SegmentedTabs.d.ts +11 -1
  17. package/dts/tabs/SegmentedTabs.d.ts.map +1 -1
  18. package/dts/visualizations/chart/PeriodSelector.d.ts +3 -2
  19. package/dts/visualizations/chart/PeriodSelector.d.ts.map +1 -1
  20. package/dts/visualizations/chart/axis/Axis.d.ts +11 -0
  21. package/dts/visualizations/chart/axis/Axis.d.ts.map +1 -1
  22. package/dts/visualizations/chart/axis/AxisTickLabelOverflowMask.d.ts +15 -0
  23. package/dts/visualizations/chart/axis/AxisTickLabelOverflowMask.d.ts.map +1 -0
  24. package/dts/visualizations/chart/axis/XAxis.d.ts.map +1 -1
  25. package/dts/visualizations/chart/axis/YAxis.d.ts.map +1 -1
  26. package/dts/visualizations/chart/axis/index.d.ts +1 -0
  27. package/dts/visualizations/chart/axis/index.d.ts.map +1 -1
  28. package/dts/visualizations/chart/line/ReferenceLine.d.ts +1 -0
  29. package/dts/visualizations/chart/line/ReferenceLine.d.ts.map +1 -1
  30. package/dts/visualizations/chart/text/ChartText.d.ts +19 -4
  31. package/dts/visualizations/chart/text/ChartText.d.ts.map +1 -1
  32. package/esm/accordion/Accordion.js +28 -6
  33. package/esm/accordion/AccordionHeader.js +51 -14
  34. package/esm/accordion/AccordionItem.js +64 -6
  35. package/esm/accordion/AccordionPanel.js +5 -1
  36. package/esm/collapsible/Collapsible.js +7 -3
  37. package/esm/controls/Switch.js +14 -2
  38. package/esm/tabs/SegmentedTabs.css +2 -0
  39. package/esm/tabs/SegmentedTabs.js +21 -6
  40. package/esm/visualizations/chart/axis/AxisTickLabelOverflowMask.js +99 -0
  41. package/esm/visualizations/chart/axis/XAxis.js +20 -7
  42. package/esm/visualizations/chart/axis/YAxis.js +20 -7
  43. package/esm/visualizations/chart/axis/index.js +1 -0
  44. package/esm/visualizations/chart/scrubber/DefaultScrubberBeaconLabel.js +1 -1
  45. package/esm/visualizations/chart/text/ChartText.js +26 -21
  46. package/package.json +2 -2
@@ -1,4 +1,4 @@
1
- const _excluded = ["axisId", "position", "showGrid", "requestedTickCount", "ticks", "tickLabelFormatter", "TickLabelComponent", "style", "className", "styles", "classNames", "GridLineComponent", "LineComponent", "TickMarkLineComponent", "tickMarkLabelGap", "minTickLabelGap", "showTickMarks", "showLine", "tickMarkSize", "tickInterval", "tickMinStep", "tickMaxStep", "label", "labelGap", "height", "testID", "bandGridLinePlacement", "bandTickMarkPlacement"];
1
+ const _excluded = ["axisId", "position", "showGrid", "requestedTickCount", "ticks", "tickLabelFormatter", "TickLabelComponent", "style", "className", "styles", "classNames", "GridLineComponent", "LineComponent", "TickMarkLineComponent", "tickMarkLabelGap", "minTickLabelGap", "showTickMarks", "showLine", "tickMarkSize", "tickInterval", "tickMinStep", "tickMaxStep", "label", "labelGap", "height", "testID", "bandGridLinePlacement", "bandTickMarkPlacement", "tickLabelOverflow"];
2
2
  function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
3
3
  function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
4
4
  function _defineProperty(e, r, t) { return (r = _toPropertyKey(r)) in e ? Object.defineProperty(e, r, { value: t, enumerable: !0, configurable: !0, writable: !0 }) : e[r] = t, e; }
@@ -19,6 +19,7 @@ import { lineToPath } from '../utils/path';
19
19
  import { getPointOnScale } from '../utils/point';
20
20
  import { isCategoricalScale } from '../utils/scale';
21
21
  import { axisLineStyles, axisTickMarkStyles, axisUpdateAnimationTransition } from './Axis';
22
+ import { AxisTickLabelOverflowMask } from './AxisTickLabelOverflowMask';
22
23
  import { DefaultAxisTickLabel } from './DefaultAxisTickLabel';
23
24
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
24
25
  const AXIS_HEIGHT = 32;
@@ -54,7 +55,8 @@ export const XAxis = /*#__PURE__*/memo(_ref => {
54
55
  height = label ? AXIS_HEIGHT + LABEL_SIZE : AXIS_HEIGHT,
55
56
  testID = 'x-axis',
56
57
  bandGridLinePlacement = 'edges',
57
- bandTickMarkPlacement = 'middle'
58
+ bandTickMarkPlacement = 'middle',
59
+ tickLabelOverflow = 'reposition'
58
60
  } = _ref,
59
61
  props = _objectWithoutProperties(_ref, _excluded);
60
62
  const registrationId = useId();
@@ -68,6 +70,7 @@ export const XAxis = /*#__PURE__*/memo(_ref => {
68
70
  getAxisBounds,
69
71
  drawingArea
70
72
  } = useCartesianChartContext();
73
+ const fadeTickLabels = tickLabelOverflow === 'fade';
71
74
  const xScale = getXScale(axisId);
72
75
  const xAxis = getXAxis(axisId);
73
76
  const axisBounds = getAxisBounds(registrationId);
@@ -201,11 +204,12 @@ export const XAxis = /*#__PURE__*/memo(_ref => {
201
204
  color: 'var(--color-fgMuted)',
202
205
  verticalAlignment: 'middle',
203
206
  style: styles === null || styles === void 0 ? void 0 : styles.tickLabel,
204
- horizontalAlignment: 'center'
207
+ horizontalAlignment: 'center',
208
+ repositionAxes: tickLabelOverflow === 'visible' ? 'none' : fadeTickLabels ? 'y' : 'both'
205
209
  }
206
210
  };
207
211
  });
208
- }, [axisBounds, ticksData, tickMarkLabelGap, showTickMarks, tickMarkSize, position, formatTick, classNames === null || classNames === void 0 ? void 0 : classNames.tickLabel, styles === null || styles === void 0 ? void 0 : styles.tickLabel]);
212
+ }, [axisBounds, ticksData, tickMarkLabelGap, showTickMarks, tickMarkSize, position, formatTick, classNames === null || classNames === void 0 ? void 0 : classNames.tickLabel, styles === null || styles === void 0 ? void 0 : styles.tickLabel, fadeTickLabels, tickLabelOverflow]);
209
213
  if (!xScale || !axisBounds || !drawingArea) return;
210
214
  const labelX = axisBounds.x + axisBounds.width / 2;
211
215
  const labelY = position === 'bottom' ? axisBounds.y + axisBounds.height - LABEL_SIZE / 2 : axisBounds.y + LABEL_SIZE / 2;
@@ -251,12 +255,21 @@ export const XAxis = /*#__PURE__*/memo(_ref => {
251
255
  style: styles === null || styles === void 0 ? void 0 : styles.gridLine
252
256
  }, key);
253
257
  })
254
- }), chartTextData && /*#__PURE__*/_jsx(ChartTextGroup, {
258
+ }), chartTextData && (fadeTickLabels ? /*#__PURE__*/_jsx(AxisTickLabelOverflowMask, {
259
+ axis: "x",
260
+ testID: "".concat(testID, "-tick-label-overflow"),
261
+ children: /*#__PURE__*/_jsx(ChartTextGroup, {
262
+ prioritizeEndLabels: true,
263
+ LabelComponent: TickLabelComponent,
264
+ labels: chartTextData,
265
+ minGap: minTickLabelGap
266
+ })
267
+ }) : /*#__PURE__*/_jsx(ChartTextGroup, {
255
268
  prioritizeEndLabels: true,
256
269
  LabelComponent: TickLabelComponent,
257
270
  labels: chartTextData,
258
271
  minGap: minTickLabelGap
259
- }), axisBounds && showTickMarks && /*#__PURE__*/_jsx("g", {
272
+ })), axisBounds && showTickMarks && /*#__PURE__*/_jsx("g", {
260
273
  "data-testid": "".concat(testID, "-tick-marks"),
261
274
  children: tickMarkPositions.map(_ref3 => {
262
275
  let {
@@ -303,9 +316,9 @@ export const XAxis = /*#__PURE__*/memo(_ref => {
303
316
  strokeWidth: 1,
304
317
  style: styles === null || styles === void 0 ? void 0 : styles.line
305
318
  }), label && /*#__PURE__*/_jsx(ChartText, {
306
- disableRepositioning: true,
307
319
  className: classNames === null || classNames === void 0 ? void 0 : classNames.label,
308
320
  horizontalAlignment: "center",
321
+ repositionAxes: "none",
309
322
  style: styles === null || styles === void 0 ? void 0 : styles.label,
310
323
  testID: "".concat(testID, "-label"),
311
324
  verticalAlignment: "middle",
@@ -1,4 +1,4 @@
1
- const _excluded = ["axisId", "position", "showGrid", "requestedTickCount", "ticks", "tickLabelFormatter", "TickLabelComponent", "style", "className", "styles", "classNames", "GridLineComponent", "LineComponent", "TickMarkLineComponent", "tickMarkLabelGap", "minTickLabelGap", "showTickMarks", "showLine", "tickMarkSize", "tickInterval", "label", "labelGap", "width", "testID", "bandGridLinePlacement", "bandTickMarkPlacement"];
1
+ const _excluded = ["axisId", "position", "showGrid", "requestedTickCount", "ticks", "tickLabelFormatter", "TickLabelComponent", "style", "className", "styles", "classNames", "GridLineComponent", "LineComponent", "TickMarkLineComponent", "tickMarkLabelGap", "minTickLabelGap", "showTickMarks", "showLine", "tickMarkSize", "tickInterval", "label", "labelGap", "width", "testID", "bandGridLinePlacement", "bandTickMarkPlacement", "tickLabelOverflow"];
2
2
  function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
3
3
  function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
4
4
  function _defineProperty(e, r, t) { return (r = _toPropertyKey(r)) in e ? Object.defineProperty(e, r, { value: t, enumerable: !0, configurable: !0, writable: !0 }) : e[r] = t, e; }
@@ -19,6 +19,7 @@ import { lineToPath } from '../utils/path';
19
19
  import { getPointOnScale } from '../utils/point';
20
20
  import { isCategoricalScale } from '../utils/scale';
21
21
  import { axisLineStyles, axisTickMarkStyles, axisUpdateAnimationTransition } from './Axis';
22
+ import { AxisTickLabelOverflowMask } from './AxisTickLabelOverflowMask';
22
23
  import { DefaultAxisTickLabel } from './DefaultAxisTickLabel';
23
24
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
24
25
  const AXIS_WIDTH = 44;
@@ -52,7 +53,8 @@ export const YAxis = /*#__PURE__*/memo(_ref => {
52
53
  width = label ? AXIS_WIDTH + LABEL_SIZE : AXIS_WIDTH,
53
54
  testID = 'y-axis',
54
55
  bandGridLinePlacement = 'edges',
55
- bandTickMarkPlacement = 'middle'
56
+ bandTickMarkPlacement = 'middle',
57
+ tickLabelOverflow = 'reposition'
56
58
  } = _ref,
57
59
  props = _objectWithoutProperties(_ref, _excluded);
58
60
  const registrationId = useId();
@@ -66,6 +68,7 @@ export const YAxis = /*#__PURE__*/memo(_ref => {
66
68
  getAxisBounds,
67
69
  drawingArea
68
70
  } = useCartesianChartContext();
71
+ const fadeTickLabels = tickLabelOverflow === 'fade';
69
72
  const yScale = getYScale(axisId);
70
73
  const yAxis = getYAxis(axisId);
71
74
  const axisBounds = getAxisBounds(registrationId);
@@ -195,11 +198,12 @@ export const YAxis = /*#__PURE__*/memo(_ref => {
195
198
  color: 'var(--color-fgMuted)',
196
199
  verticalAlignment: 'middle',
197
200
  style: styles === null || styles === void 0 ? void 0 : styles.tickLabel,
198
- horizontalAlignment: position === 'left' ? 'right' : 'left'
201
+ horizontalAlignment: position === 'left' ? 'right' : 'left',
202
+ repositionAxes: tickLabelOverflow === 'visible' ? 'none' : fadeTickLabels ? 'x' : 'both'
199
203
  }
200
204
  };
201
205
  });
202
- }, [axisBounds, ticksData, tickMarkLabelGap, showTickMarks, tickMarkSize, position, formatTick, classNames === null || classNames === void 0 ? void 0 : classNames.tickLabel, styles === null || styles === void 0 ? void 0 : styles.tickLabel]);
206
+ }, [axisBounds, ticksData, tickMarkLabelGap, showTickMarks, tickMarkSize, position, formatTick, classNames === null || classNames === void 0 ? void 0 : classNames.tickLabel, styles === null || styles === void 0 ? void 0 : styles.tickLabel, fadeTickLabels, tickLabelOverflow]);
203
207
  if (!yScale || !axisBounds || !drawingArea) return;
204
208
  const labelX = position === 'left' ? axisBounds.x + LABEL_SIZE / 2 : axisBounds.x + axisBounds.width - LABEL_SIZE / 2;
205
209
  const labelY = axisBounds.y + axisBounds.height / 2;
@@ -245,12 +249,21 @@ export const YAxis = /*#__PURE__*/memo(_ref => {
245
249
  style: styles === null || styles === void 0 ? void 0 : styles.gridLine
246
250
  }, key);
247
251
  })
248
- }), chartTextData && /*#__PURE__*/_jsx(ChartTextGroup, {
252
+ }), chartTextData && (fadeTickLabels ? /*#__PURE__*/_jsx(AxisTickLabelOverflowMask, {
253
+ axis: "y",
254
+ testID: "".concat(testID, "-tick-label-overflow"),
255
+ children: /*#__PURE__*/_jsx(ChartTextGroup, {
256
+ prioritizeEndLabels: true,
257
+ LabelComponent: TickLabelComponent,
258
+ labels: chartTextData,
259
+ minGap: minTickLabelGap
260
+ })
261
+ }) : /*#__PURE__*/_jsx(ChartTextGroup, {
249
262
  prioritizeEndLabels: true,
250
263
  LabelComponent: TickLabelComponent,
251
264
  labels: chartTextData,
252
265
  minGap: minTickLabelGap
253
- }), showTickMarks && /*#__PURE__*/_jsx("g", {
266
+ })), showTickMarks && /*#__PURE__*/_jsx("g", {
254
267
  "data-testid": "".concat(testID, "-tick-marks"),
255
268
  children: tickMarkPositions.map(_ref3 => {
256
269
  let {
@@ -303,9 +316,9 @@ export const YAxis = /*#__PURE__*/memo(_ref => {
303
316
  transform: "rotate(".concat(position === 'left' ? -90 : 90, "deg)")
304
317
  },
305
318
  children: /*#__PURE__*/_jsx(ChartText, {
306
- disableRepositioning: true,
307
319
  className: classNames === null || classNames === void 0 ? void 0 : classNames.label,
308
320
  horizontalAlignment: "center",
321
+ repositionAxes: "none",
309
322
  style: styles === null || styles === void 0 ? void 0 : styles.label,
310
323
  testID: "".concat(testID, "-label-text"),
311
324
  verticalAlignment: "middle",
@@ -1,5 +1,6 @@
1
1
  // codegen:start {preset: barrel, include: ./*.tsx, exclude: ./__stories__/*.tsx}
2
2
  export * from './Axis';
3
+ export * from './AxisTickLabelOverflowMask';
3
4
  export * from './DefaultAxisTickLabel';
4
5
  export * from './XAxis';
5
6
  export * from './YAxis';
@@ -41,13 +41,13 @@ export const DefaultScrubberBeaconLabel = /*#__PURE__*/memo(_ref => {
41
41
  initial: false,
42
42
  transition: transition,
43
43
  children: /*#__PURE__*/_jsx(ChartText, _objectSpread(_objectSpread({
44
- disableRepositioning: true,
45
44
  background: background,
46
45
  borderRadius: borderRadius,
47
46
  color: color,
48
47
  elevated: elevated,
49
48
  font: font,
50
49
  inset: inset,
50
+ repositionAxes: "none",
51
51
  verticalAlignment: verticalAlignment,
52
52
  y: transition ? 0 : y
53
53
  }, chartTextProps), {}, {
@@ -23,6 +23,10 @@ import { accessoryFadeTransitionDuration } from '../utils/transition';
23
23
  /**
24
24
  * Vertical alignment options for chart text.
25
25
  */
26
+
27
+ /**
28
+ * Which axes ChartText may nudge labels along to stay in bounds.
29
+ */
26
30
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
27
31
  /**
28
32
  * Get text anchor based on horizontal alignment.
@@ -60,7 +64,8 @@ export const ChartText = /*#__PURE__*/memo(_ref => {
60
64
  verticalAlignment = 'middle',
61
65
  dx,
62
66
  dy,
63
- disableRepositioning,
67
+ disableRepositioning = false,
68
+ repositionAxes = disableRepositioning ? 'none' : 'both',
64
69
  bounds,
65
70
  opacity,
66
71
  testID,
@@ -92,7 +97,7 @@ export const ChartText = /*#__PURE__*/memo(_ref => {
92
97
  }), [chartWidth, chartHeight]);
93
98
  const textRef = useRef(null);
94
99
  const [textBBox, setTextBBox] = useState(null);
95
- const isDimensionsReady = disableRepositioning || textRef.current !== null;
100
+ const isDimensionsReady = repositionAxes === 'none' || textRef.current !== null;
96
101
  const backgroundRectDimensions = useMemo(() => {
97
102
  if (!textBBox) {
98
103
  return null;
@@ -106,12 +111,8 @@ export const ChartText = /*#__PURE__*/memo(_ref => {
106
111
  };
107
112
  }, [textBBox, insetInput]);
108
113
  const overflowAmount = useMemo(() => {
109
- if (disableRepositioning) {
110
- return {
111
- x: 0,
112
- y: 0
113
- };
114
- }
114
+ const repositionX = repositionAxes === 'both' || repositionAxes === 'x';
115
+ const repositionY = repositionAxes === 'both' || repositionAxes === 'y';
115
116
  const parentBounds = bounds !== null && bounds !== void 0 ? bounds : fullChartBounds;
116
117
  if (!backgroundRectDimensions || !parentBounds || parentBounds.width <= 0 || parentBounds.height <= 0) {
117
118
  return {
@@ -119,23 +120,27 @@ export const ChartText = /*#__PURE__*/memo(_ref => {
119
120
  y: 0
120
121
  };
121
122
  }
122
- let x = 0;
123
- let y = 0;
124
- if (backgroundRectDimensions.x < parentBounds.x) {
125
- x = parentBounds.x - backgroundRectDimensions.x; // positive = shift right
126
- } else if (backgroundRectDimensions.x + backgroundRectDimensions.width > parentBounds.x + parentBounds.width) {
127
- x = parentBounds.x + parentBounds.width - (backgroundRectDimensions.x + backgroundRectDimensions.width); // negative = shift left
123
+ let nextX = 0;
124
+ let nextY = 0;
125
+ if (repositionX) {
126
+ if (backgroundRectDimensions.x < parentBounds.x) {
127
+ nextX = parentBounds.x - backgroundRectDimensions.x;
128
+ } else if (backgroundRectDimensions.x + backgroundRectDimensions.width > parentBounds.x + parentBounds.width) {
129
+ nextX = parentBounds.x + parentBounds.width - (backgroundRectDimensions.x + backgroundRectDimensions.width);
130
+ }
128
131
  }
129
- if (backgroundRectDimensions.y < parentBounds.y) {
130
- y = parentBounds.y - backgroundRectDimensions.y; // positive = shift down
131
- } else if (backgroundRectDimensions.y + backgroundRectDimensions.height > parentBounds.y + parentBounds.height) {
132
- y = parentBounds.y + parentBounds.height - (backgroundRectDimensions.y + backgroundRectDimensions.height); // negative = shift up
132
+ if (repositionY) {
133
+ if (backgroundRectDimensions.y < parentBounds.y) {
134
+ nextY = parentBounds.y - backgroundRectDimensions.y;
135
+ } else if (backgroundRectDimensions.y + backgroundRectDimensions.height > parentBounds.y + parentBounds.height) {
136
+ nextY = parentBounds.y + parentBounds.height - (backgroundRectDimensions.y + backgroundRectDimensions.height);
137
+ }
133
138
  }
134
139
  return {
135
- x,
136
- y
140
+ x: nextX,
141
+ y: nextY
137
142
  };
138
- }, [backgroundRectDimensions, fullChartBounds, bounds, disableRepositioning]);
143
+ }, [backgroundRectDimensions, fullChartBounds, bounds, repositionAxes]);
139
144
 
140
145
  // Compose the final reported rect including any overflow translation applied
141
146
  const reportedRect = useMemo(() => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@coinbase/cds-web",
3
- "version": "9.13.0",
3
+ "version": "9.15.0",
4
4
  "description": "Coinbase Design System - Web",
5
5
  "repository": {
6
6
  "type": "git",
@@ -227,7 +227,7 @@
227
227
  "react-dom": "^18.0.0 || ~19.1.2"
228
228
  },
229
229
  "dependencies": {
230
- "@coinbase/cds-common": "^9.13.0",
230
+ "@coinbase/cds-common": "^9.15.0",
231
231
  "@coinbase/cds-icons": "^5.21.0",
232
232
  "@coinbase/cds-illustrations": "^4.47.0",
233
233
  "@coinbase/cds-lottie-files": "^3.3.4",