@ons/design-system 73.3.0 → 73.4.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.
@@ -631,6 +631,7 @@ $button-shadow-size: 3px;
631
631
 
632
632
  &--menu {
633
633
  border-bottom: 4px solid rgb(0 0 0 / 0%);
634
+ height: 72px;
634
635
  .ons-icon {
635
636
  transform: rotate(90deg);
636
637
  }
@@ -644,12 +645,7 @@ $button-shadow-size: 3px;
644
645
  padding: 0;
645
646
  .ons-icon {
646
647
  fill: var(--ons-color-text-link);
647
- height: 1rem;
648
648
  margin-top: 0;
649
- width: 1rem;
650
- }
651
- .ons-btn__text {
652
- height: 24px;
653
649
  }
654
650
  }
655
651
  }
@@ -723,7 +719,8 @@ $button-shadow-size: 3px;
723
719
  &--close {
724
720
  align-items: center;
725
721
  display: flex;
726
- padding: 1.25rem 1rem 1rem;
722
+ height: 72px;
723
+ padding: 0 1.5rem;
727
724
  }
728
725
 
729
726
  &--search-icon &,
@@ -1,11 +1,20 @@
1
1
  import ChartConstants from './chart-constants';
2
2
 
3
3
  class AnnotationsOptions {
4
- constructor(annotations) {
4
+ constructor(annotations, xAxisCategories = []) {
5
5
  this.constants = ChartConstants.constants();
6
6
  this.annotations = annotations;
7
+ this.xAxisCategories = xAxisCategories;
7
8
  }
8
9
 
10
+ // Screen reader description that includes the annotated data point.
11
+ buildPointAccessibilityDescription = (annotation) => {
12
+ const x = annotation.point.x;
13
+ const y = annotation.point.y;
14
+ const xLabel = Number.isInteger(x) && this.xAxisCategories.length > x ? this.xAxisCategories[x] : x;
15
+ return `${annotation.text}. Related to ${xLabel}, ${y}`;
16
+ };
17
+
9
18
  getAnnotationsOptionsDesktop = () => {
10
19
  let annotations = [
11
20
  {
@@ -36,6 +45,9 @@ class AnnotationsOptions {
36
45
  text: annotation.text,
37
46
  x: annotation.labelOffsetX,
38
47
  y: annotation.labelOffsetY,
48
+ accessibility: {
49
+ description: this.buildPointAccessibilityDescription(annotation),
50
+ },
39
51
  });
40
52
  });
41
53
  return annotations;
@@ -66,9 +78,9 @@ class AnnotationsOptions {
66
78
  text: index + 1,
67
79
  x: 0,
68
80
  y: 0,
69
- // Ensures the full label is read out by screen readers
81
+ // Ensures the full label is read out by screen readers, including data point context
70
82
  accessibility: {
71
- description: annotation.text,
83
+ description: this.buildPointAccessibilityDescription(annotation),
72
84
  },
73
85
  });
74
86
  });
@@ -41,7 +41,7 @@ class HighchartsBaseChart {
41
41
  this.config = JSON.parse(this.node.querySelector(`[data-highcharts-config--${this.id}]`).textContent);
42
42
  if (this.node.querySelector(`[data-highcharts-annotations--${this.id}]`)) {
43
43
  this.annotations = JSON.parse(this.node.querySelector(`[data-highcharts-annotations--${this.id}]`).textContent);
44
- this.annotationsOptions = new AnnotationsOptions(this.annotations);
44
+ this.annotationsOptions = new AnnotationsOptions(this.annotations, this.config.xAxis?.categories ?? []);
45
45
  }
46
46
  if (this.node.querySelector(`[data-highcharts-range-annotations--${this.id}]`)) {
47
47
  this.rangeAnnotations = JSON.parse(this.node.querySelector(`[data-highcharts-range-annotations--${this.id}]`).textContent);
@@ -73,7 +73,6 @@ class HighchartsBaseChart {
73
73
  this.customReferenceLineValue = this.node.dataset.highchartsCustomReferenceLineValue
74
74
  ? parseFloat(this.node.dataset.highchartsCustomReferenceLineValue)
75
75
  : undefined;
76
-
77
76
  this.specificChartOptions = new SpecificChartOptions(
78
77
  this.theme,
79
78
  this.chartType,
@@ -98,6 +97,7 @@ class HighchartsBaseChart {
98
97
  }
99
98
  this.hideDataLabels = this.checkHideDataLabels();
100
99
  this.setSpecificChartOptions();
100
+ this.setAnnotationsAccessibilityDescription();
101
101
  this.setResponsiveOptions();
102
102
  this.setLoadEvent();
103
103
  this.setRenderEvent();
@@ -105,6 +105,25 @@ class HighchartsBaseChart {
105
105
  this.chart = Highcharts.chart(chartNode, this.config);
106
106
  }
107
107
 
108
+ // Making these annotation types accessible to screen readers. Point annotations are already
109
+ // handled by the Highcharts annotations module via {annotationsList}.
110
+ setAnnotationsAccessibilityDescription = () => {
111
+ const descriptions = [];
112
+ if (this.rangeAnnotationsOptions) {
113
+ descriptions.push(...this.rangeAnnotationsOptions.getAccessibilityDescriptions());
114
+ }
115
+ if (this.referenceLineAnnotationsOptions) {
116
+ descriptions.push(...this.referenceLineAnnotationsOptions.getAccessibilityDescriptions());
117
+ }
118
+ if (descriptions.length > 0) {
119
+ if (!this.config.accessibility) {
120
+ this.config.accessibility = {};
121
+ }
122
+ const listItems = descriptions.map((d) => `<li>${d}</li>`).join('');
123
+ this.config.accessibility.description = `<ul style="list-style-type: none">${listItems}</ul>`;
124
+ }
125
+ };
126
+
108
127
  // Check for the number of extra line series in the config
109
128
  checkForExtraLines = () => {
110
129
  return this.chartType === 'line' ? 0 : this.config.series.filter((series) => series.type === 'line').length;
@@ -319,6 +338,12 @@ class HighchartsBaseChart {
319
338
  }
320
339
  });
321
340
  }
341
+ // Clear the SVG <desc> element that Highcharts unconditionally
342
+ // injects with "Created with Highcharts x.x.x".
343
+ const svgDesc = currentChart.renderer.box.querySelector('desc');
344
+ if (svgDesc) {
345
+ svgDesc.textContent = '';
346
+ }
322
347
  currentChart.redraw(false);
323
348
  };
324
349
  };
@@ -55,6 +55,16 @@ class CommonChartOptions {
55
55
  // Remove Highcharts watermark
56
56
  enabled: false,
57
57
  },
58
+ lang: {
59
+ accessibility: {
60
+ // We use only the title so screen readers don't hear Highcharts interactive chart text.
61
+ chartContainerLabel: '{title}',
62
+ // The default "Interactive chart" creates a redundant landmark on
63
+ // the SVG element. The outer chart region already has the title
64
+ // label, so we clear this to avoid duplicate announcements.
65
+ svgContainerLabel: '',
66
+ },
67
+ },
58
68
  accessibility: {
59
69
  enabled: true,
60
70
  keyboardNavigation: {
@@ -6,6 +6,14 @@ class RangeAnnotationsOptions {
6
6
  this.rangeAnnotations = rangeAnnotations;
7
7
  }
8
8
 
9
+ buildRangeAccessibilityDescription = (rangeAnnotation) => {
10
+ const axisLabel = rangeAnnotation.axis === 'x' ? 'x-axis' : 'y-axis';
11
+ const fromValue = rangeAnnotation.range?.axisValue1;
12
+ const toValue = rangeAnnotation.range?.axisValue2;
13
+ const rangeText = fromValue !== undefined && toValue !== undefined ? `from ${fromValue} to ${toValue}` : 'for a range';
14
+ return `Range annotation on the ${axisLabel} ${rangeText}: ${rangeAnnotation.text}`;
15
+ };
16
+
9
17
  getRangeAnnotationsOptionsDesktop = (chartType) => {
10
18
  let xAxisPlotBands = [];
11
19
  let yAxisPlotBands = [];
@@ -25,6 +33,9 @@ class RangeAnnotationsOptions {
25
33
  ? `ons-chart__range-annotation-label ons-chart__range-annotation-label--${rangeAnnotation.axis}`
26
34
  : 'ons-chart__range-annotation-label--outside',
27
35
  allowOverlap: true,
36
+ accessibility: {
37
+ description: this.buildRangeAccessibilityDescription(rangeAnnotation),
38
+ },
28
39
  style: {
29
40
  color: this.constants.labelColor,
30
41
  fontSize: this.constants.defaultFontSize,
@@ -67,6 +78,9 @@ class RangeAnnotationsOptions {
67
78
  useHTML: true,
68
79
  className: 'ons-chart__annotations-footnotes-number',
69
80
  allowOverlap: true,
81
+ accessibility: {
82
+ description: this.buildRangeAccessibilityDescription(rangeAnnotation),
83
+ },
70
84
  style: {
71
85
  color: this.constants.labelColor,
72
86
  fontSize: this.constants.defaultFontSize,
@@ -204,9 +218,11 @@ class RangeAnnotationsOptions {
204
218
  });
205
219
  };
206
220
 
207
- // For bar and column charts, we want the range to
208
- // start and end flush with the edges of the columns / bars,
209
- // not halfway through as is the Highcharts default.
221
+ // Returns an array of plain-text descriptions for all range annotations.
222
+ getAccessibilityDescriptions = () => {
223
+ return this.rangeAnnotations.map((rangeAnnotation) => this.buildRangeAccessibilityDescription(rangeAnnotation));
224
+ };
225
+
210
226
  adjustRangeForCategoryAxis = (rangeAnnotation, chartType) => {
211
227
  let axisValue1 = rangeAnnotation.range.axisValue1;
212
228
  let axisValue2 = rangeAnnotation.range.axisValue2;
@@ -6,6 +6,19 @@ class ReferenceLineAnnotationsOptions {
6
6
  this.referenceLineAnnotations = referenceLineAnnotations;
7
7
  }
8
8
 
9
+ buildReferenceLineAccessibilityDescription = (referenceLineAnnotation) => {
10
+ const axisLabel = referenceLineAnnotation.axis === 'x' ? 'x-axis' : 'y-axis';
11
+ const valueText = referenceLineAnnotation.value !== undefined ? `at ${referenceLineAnnotation.value}` : 'at a value';
12
+ return `Reference line annotation on the ${axisLabel} ${valueText}: ${referenceLineAnnotation.text}`;
13
+ };
14
+
15
+ // Returns an array of plain-text descriptions for all reference line annotations
16
+ getAccessibilityDescriptions = () => {
17
+ return this.referenceLineAnnotations.map((referenceLineAnnotation) =>
18
+ this.buildReferenceLineAccessibilityDescription(referenceLineAnnotation),
19
+ );
20
+ };
21
+
9
22
  getReferenceLineAnnotationsOptionsDesktop = () => {
10
23
  let xAxisPlotLines = [];
11
24
  let yAxisPlotLines = [];
@@ -24,6 +37,9 @@ class ReferenceLineAnnotationsOptions {
24
37
  rotation: 0,
25
38
  align: 'left',
26
39
  textAlign: 'left',
40
+ accessibility: {
41
+ description: this.buildReferenceLineAccessibilityDescription(referenceLineAnnotation),
42
+ },
27
43
  },
28
44
  color: this.constants.zeroLineColor,
29
45
  // note this works to give a dashed line with 4px and a 4px gap, but
@@ -61,6 +77,9 @@ class ReferenceLineAnnotationsOptions {
61
77
  useHTML: true,
62
78
  className: 'ons-chart__annotations-footnotes-number',
63
79
  allowOverlap: true,
80
+ accessibility: {
81
+ description: this.buildReferenceLineAccessibilityDescription(referenceLineAnnotation),
82
+ },
64
83
  style: {
65
84
  color: this.constants.labelColor,
66
85
  fontSize: this.constants.defaultFontSize,
@@ -41,6 +41,32 @@
41
41
  }
42
42
 
43
43
  &--inline {
44
+ @include mq(600px, l, $to-operator: '<') {
45
+ .ons-description-list__item {
46
+ align-items: baseline;
47
+ column-gap: 0.25rem;
48
+ display: grid;
49
+ grid-template-columns: 10rem 1fr;
50
+ }
51
+
52
+ .ons-description-list__term,
53
+ .ons-description-list__value {
54
+ clear: none;
55
+ float: none;
56
+ max-width: none;
57
+ width: auto;
58
+ }
59
+
60
+ .ons-description-list__term {
61
+ grid-column: 1;
62
+ grid-row: 1;
63
+ }
64
+
65
+ .ons-description-list__value {
66
+ grid-column: 2;
67
+ }
68
+ }
69
+
44
70
  @include mq(l) {
45
71
  display: grid;
46
72
  grid-template-columns: repeat(3, 1fr);
@@ -246,6 +246,20 @@
246
246
  }
247
247
  }
248
248
 
249
+ &--basic {
250
+ .ons-header__links--language-section {
251
+ padding: 0 1.5rem !important;
252
+ }
253
+
254
+ .ons-header__language {
255
+ margin-bottom: 4px !important;
256
+ }
257
+
258
+ .ons-language-links__item {
259
+ margin: 0 !important;
260
+ }
261
+ }
262
+
249
263
  &--basic & {
250
264
  &__grid-top {
251
265
  padding: 0;
@@ -286,7 +300,8 @@
286
300
  position: relative;
287
301
  width: 100%;
288
302
 
289
- &__key-list {
303
+ &__key-list,
304
+ &__language {
290
305
  border-bottom: 1px solid var(--ons-color-ocean-blue);
291
306
  margin-bottom: 1.25rem;
292
307
  padding-bottom: 1rem;
@@ -300,6 +315,11 @@
300
315
  &__description {
301
316
  line-height: 1.714 !important;
302
317
  }
318
+ &__language {
319
+ .ons-language-links__item {
320
+ margin-left: 0 !important;
321
+ }
322
+ }
303
323
 
304
324
  &__groupItem-list:not(:last-of-type) {
305
325
  margin-bottom: 2rem !important;
@@ -314,6 +334,7 @@
314
334
  padding-bottom: 1rem;
315
335
  @include mq(m) {
316
336
  padding-bottom: 0;
337
+ padding-right: 2.5rem;
317
338
  }
318
339
  }
319
340
  }
@@ -327,6 +348,18 @@
327
348
  &-nav-search {
328
349
  background-color: var(--ons-color-branded-tint);
329
350
 
351
+ // Prevent flash of the non-JS, always-visible panels when JS is enabled.
352
+ // Panels are shown only when NavigationToggle sets aria-hidden="false".
353
+ body.ons-js-enabled .ons-header.ons-header--basic &.ons-js-nav-menu:not([aria-hidden='false']),
354
+ body.ons-js-enabled .ons-header.ons-header--basic &.ons-js-header-search:not([aria-hidden='false']) {
355
+ display: none !important;
356
+ }
357
+
358
+ body.ons-js-enabled .ons-header.ons-header--basic &.ons-js-nav-menu[aria-hidden='false'],
359
+ body.ons-js-enabled .ons-header.ons-header--basic &.ons-js-header-search[aria-hidden='false'] {
360
+ display: block !important;
361
+ }
362
+
330
363
  // updates styles when js is enabled
331
364
  .ons-js-enabled & {
332
365
  position: relative;
@@ -334,6 +367,16 @@
334
367
  width: 100vw;
335
368
  transform: translateX(-50%);
336
369
  border-top: 0;
370
+ padding-left: env(safe-area-inset-left);
371
+ padding-right: env(safe-area-inset-right);
372
+
373
+ // iOS Edge can render 100vw breakout panels under the Dynamic Island in landscape.
374
+ // Keep panels in normal flow on touch landscape viewports so body safe-area insets apply.
375
+ @media (orientation: landscape) and (hover: none) and (pointer: coarse) {
376
+ left: 0;
377
+ width: 100%;
378
+ transform: none;
379
+ }
337
380
  }
338
381
  }
339
382
 
@@ -110,7 +110,7 @@
110
110
  {{
111
111
  onsButton({
112
112
  "text": params.menuLinks.toggleMenuButton.text | default("Menu"),
113
- "classes": "ons-u-fs-s--b ons-js-toggle-nav-menu button-nav active disabled",
113
+ "classes": "ons-u-fs-r--b ons-js-toggle-nav-menu ons-u-db-no-js_disabled button-nav disabled",
114
114
  "type": "button",
115
115
  "variants": "menu",
116
116
  "iconType": "chevron",
@@ -118,7 +118,7 @@
118
118
  "attributes": {
119
119
  "aria-label": params.menuLinks.toggleMenuButton.ariaLabel | default("Toggle menu"),
120
120
  "aria-controls": params.menuLinks.id,
121
- "aria-expanded": "true",
121
+ "aria-expanded": "false",
122
122
  "aria-disabled": "true"
123
123
  }
124
124
  })
@@ -126,27 +126,18 @@
126
126
  </div>
127
127
  {% endif %}
128
128
 
129
- {% if params.language %}
130
- <div class="ons-grid__col ons-col-auto{{ ' ons-u-mr-s ons-u-d-no@2xs@xs' if params.serviceLinks }}">
131
- <div class="ons-header__language">
132
- {% from "components/language-selector/_macro.njk" import onsLanguageSelector %}
133
- {{ onsLanguageSelector(params.language) }}
134
- </div>
135
- </div>
136
- {% endif %}
137
-
138
129
  {% if params.search or params.searchLinks %}
139
- <div class="ons-header__links ons-grid__col ons-header__menu-link">
130
+ <div class="ons-header__links ons-grid__col ons-header__menu-links">
140
131
  {{
141
132
  onsButton({
142
- "classes": "ons-u-fs-s--b ons-js-toggle-header-search ons-btn--close ons-btn--search-icon active disabled",
133
+ "classes": "ons-u-fs-s--b ons-js-toggle-header-search ons-u-db-no-js_disabled ons-btn--search-icon disabled",
143
134
  "type": "button",
144
135
  "variants": "search",
145
136
  "iconType": "search",
146
137
  "attributes": {
147
138
  "aria-label": (params.search.toggleAriaLabel if params.search.toggleAriaLabel else params.searchLinks.searchButtonAriaLabel) | default("Toggle search"),
148
139
  "aria-controls": params.search.id if params.search.id else params.searchLinks.id,
149
- "aria-expanded": "true",
140
+ "aria-expanded": "false",
150
141
  "aria-disabled": "true"
151
142
  }
152
143
  })
@@ -154,8 +145,16 @@
154
145
  </div>
155
146
  {% endif %}
156
147
  </div>
148
+ {% if params.language %}
149
+ <div class="ons-header__links ons-grid__col ons-col-auto ons-header__links--language-section ons-u-d-no@2xs@m">
150
+ <div class="ons-header__language ons-u-fs-r--b">
151
+ {% from "components/language-selector/_macro.njk" import onsLanguageSelector %}
152
+ {{ onsLanguageSelector(params.language) }}
153
+ </div>
154
+ </div>
155
+ {% endif %}
157
156
  {% endif %}
158
- {% if params.language or params.serviceLinks %}
157
+ {% if params.variants!== 'basic' and (params.language or params.serviceLinks) %}
159
158
  <div
160
159
  class="ons-header__links ons-grid__col{{ ' ons-u-ml-auto' if not (params.searchLinks or params.search) and not params.menuLinks }}"
161
160
  >
@@ -278,11 +277,18 @@
278
277
  </div>
279
278
  {% if params.variants == "basic" and params.menuLinks %}
280
279
  <nav
281
- class="ons-js-nav-menu ons-header-nav-menu ons-u-pt-2xl ons-u-d-no"
280
+ class="ons-js-nav-menu ons-header-nav-menu ons-u-pt-2xl"
282
281
  id="{{ params.menuLinks.id }}"
283
282
  aria-label="{{ params.menuLinks.ariaLabel | default("Menu navigation") }}"
284
- aria-hidden="true"
285
283
  >
284
+ {% if params.language %}
285
+ <div class="ons-container ons-u-d-no@m ons-u-d-no@2xl">
286
+ <div class="ons-header-nav-menu__language ons-u-fs-s--b">
287
+ {% from "components/language-selector/_macro.njk" import onsLanguageSelector %}
288
+ {{ onsLanguageSelector(params.language) }}
289
+ </div>
290
+ </div>
291
+ {% endif %}
286
292
  {% if params.menuLinks.keyLinks %}
287
293
  <div class="ons-container">
288
294
  <ul class="ons-grid ons-header-nav-menu__key-list">
@@ -352,7 +358,6 @@
352
358
  class="ons-js-header-search ons-header-nav-search {{ params.searchLinks.classes if params.searchLinks and params.searchLinks.classes else '' }}{{ params.search.classes if params.search and params.search.classes else '' }}"
353
359
  id="{{ params.search.id if params.search else params.searchLinks.id }}"
354
360
  aria-label="{{ (params.search.navAriaLabel if params.search else params.searchLinks.searchNavigationAriaLabel) | default('Search navigation') }}"
355
- aria-hidden="false"
356
361
  >
357
362
  <div class="ons-container">
358
363
  <form class="ons-header-nav-search__input" method="get" action="{{ params.search.form.action | default('') }}">
@@ -378,9 +383,10 @@
378
383
  </div>
379
384
  {% if params.search or params.searchLinks %}
380
385
  <div class="ons-container">
381
- <h2 class="ons-u-fs-r--b ons-u-mb-s ons-header-nav-search__heading">
382
- {{ params.search.links.heading if params.search.links else params.searchLinks.heading }}
383
- </h2>
386
+ {% set searchHeading = params.search.links.heading if params.search.links else params.searchLinks.heading %}
387
+ {% if searchHeading %}
388
+ <h2 class="ons-u-fs-r--b ons-u-mb-s ons-header-nav-search__heading">{{ searchHeading }}</h2>
389
+ {% endif %}
384
390
  <ul class="ons-list ons-list--bare ons-list--inline">
385
391
  {% if params.searchLinks %}
386
392
  {% for item in params.searchLinks.itemsList %}