@sentropic/design-system-svelte 0.22.0 → 0.22.1

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.
@@ -33,9 +33,12 @@
33
33
  */
34
34
  selectedKeys?: string[];
35
35
  /**
36
- * Called when a bar is clicked / activated (Enter / Space) with the bar's
37
- * key (its `label`). When provided the bars become focusable buttons; when
38
- * omitted the chart is purely presentational (no interactivity, unchanged).
36
+ * Called with the bar's key (its `label`) when the user selects it. When
37
+ * provided, an ACCESSIBLE row of filter chips (real <button>s) is rendered
38
+ * OUTSIDE the aria-hidden SVG — that is the keyboard + screen-reader surface.
39
+ * The SVG bars themselves stay decorative (aria-hidden) and only offer a
40
+ * mouse click shortcut for sighted pointer users. When omitted the chart is
41
+ * purely presentational (no interactivity, unchanged).
39
42
  */
40
43
  onSelect?: (key: string) => void;
41
44
  class?: string;
@@ -96,14 +99,6 @@
96
99
  const hasSelection = $derived(selectedSet.size > 0);
97
100
  const interactive = $derived(typeof onSelect === "function");
98
101
 
99
- function handleBarKeydown(key: string, e: KeyboardEvent) {
100
- if (e.key === "Enter" || e.key === " ") {
101
- // preventDefault on Space so it activates rather than scrolling the page.
102
- e.preventDefault();
103
- onSelect?.(key);
104
- }
105
- }
106
-
107
102
  const scales = $derived.by(() => {
108
103
  const values = data.map((d) => d.value);
109
104
  const minRaw = Math.min(0, ...values);
@@ -301,12 +296,18 @@
301
296
  {/each}
302
297
 
303
298
  <!-- bars -->
304
- <!-- A bar becomes an interactive button only when `onSelect` is provided;
305
- the role + tabindex are then dynamic, which the static a11y check cannot
306
- verify, hence the targeted ignore (mirrors SelectableRow). -->
299
+ <!-- The bars live inside an aria-hidden SVG, so they are NEVER an accessible
300
+ surface. When `onSelect` is provided they only carry a mouse click
301
+ shortcut (cursor:pointer) for sighted pointer users keyboard + screen
302
+ readers use the filter-chip buttons rendered below, outside this SVG. -->
307
303
  {#each bars as bar, i (bar.datum.label)}
308
304
  {@const isSelected = selectedSet.has(bar.datum.label)}
309
- <!-- svelte-ignore a11y_no_noninteractive_tabindex -->
305
+ <!-- The mouse click is a deliberate sighted-pointer-only shortcut on a
306
+ decorative element inside an aria-hidden SVG; the real keyboard + AT
307
+ path is the filter-chip <button>s below. No ARIA role/keyboard here
308
+ on purpose (it would be a lie under aria-hidden). -->
309
+ <!-- svelte-ignore a11y_no_static_element_interactions -->
310
+ <!-- svelte-ignore a11y_click_events_have_key_events -->
310
311
  <rect
311
312
  class="st-barChart__bar st-barChart__bar--{bar.tone}"
312
313
  class:st-barChart__bar--selected={isSelected}
@@ -318,17 +319,32 @@
318
319
  height={bar.height}
319
320
  rx="2"
320
321
  data-chart-index={i}
321
- role={interactive ? "button" : undefined}
322
- tabindex={interactive ? 0 : undefined}
323
- aria-pressed={interactive ? isSelected : undefined}
324
- aria-label={interactive ? `${bar.datum.label}: ${bar.datum.value}` : undefined}
325
322
  onclick={interactive ? () => onSelect?.(bar.datum.label) : undefined}
326
- onkeydown={interactive ? (e) => handleBarKeydown(bar.datum.label, e) : undefined}
327
323
  />
328
324
  {/each}
329
325
  </svg>
330
326
  </div>
331
327
 
328
+ {#if interactive}
329
+ <!-- Accessible selection surface — real <button>s OUTSIDE the aria-hidden
330
+ SVG. This is the keyboard + screen-reader path for filtering. -->
331
+ <div class="st-barChart__filters" role="group" aria-label={`Filtrer par ${label}`}>
332
+ {#each bars as bar (bar.datum.label)}
333
+ {@const isSelected = selectedSet.has(bar.datum.label)}
334
+ <button
335
+ type="button"
336
+ class="st-barChart__filterChip st-barChart__filterChip--{bar.tone}"
337
+ class:st-barChart__filterChip--selected={isSelected}
338
+ aria-pressed={isSelected}
339
+ onclick={() => onSelect?.(bar.datum.label)}
340
+ >
341
+ <span class="st-barChart__filterSwatch" aria-hidden="true"></span>
342
+ {bar.datum.label}: {bar.datum.value}
343
+ </button>
344
+ {/each}
345
+ </div>
346
+ {/if}
347
+
332
348
  <ChartDataList {label} items={dataValueItems} />
333
349
 
334
350
  {#if hoveredIndex !== null && bars[hoveredIndex]}
@@ -389,19 +405,16 @@
389
405
  opacity: 0.82;
390
406
  }
391
407
 
392
- /* Interactive (onSelect provided): the bar is a keyboard-activable button. */
393
- .st-barChart__bar--interactive:focus-visible {
394
- outline: 2px solid var(--st-semantic-border-interactive, var(--st-semantic-action-primary));
395
- outline-offset: 1px;
396
- }
397
-
398
- /* Non-selected bars are dimmed while a selection is active. */
408
+ /* Non-selected bars are dimmed while a selection is active. Floor kept high
409
+ (0.6) so the colour stays distinguishable — opacity is never the sole cue;
410
+ selection also adds a stroke (shape), and the values stay in the chips +
411
+ ChartDataList. */
399
412
  .st-barChart__bar--dim {
400
- opacity: 0.45;
413
+ opacity: 0.6;
401
414
  }
402
415
  /* Hover still lifts a dimmed bar so it stays explorable. */
403
416
  .st-barChart__bar--dim:hover {
404
- opacity: 0.7;
417
+ opacity: 0.8;
405
418
  }
406
419
 
407
420
  /* Selected bar: full opacity + a contrast-safe accent stroke (two signals,
@@ -423,6 +436,68 @@
423
436
  .st-barChart__bar--category7 { fill: var(--st-semantic-data-category7); }
424
437
  .st-barChart__bar--category8 { fill: var(--st-semantic-data-category8); }
425
438
 
439
+ /* Accessible filter chips — keyboard + screen-reader selection surface,
440
+ rendered outside the aria-hidden SVG. */
441
+ .st-barChart__filters {
442
+ display: flex;
443
+ flex-wrap: wrap;
444
+ gap: var(--st-spacing-2, 0.5rem);
445
+ margin-top: var(--st-spacing-2, 0.5rem);
446
+ }
447
+
448
+ .st-barChart__filterChip {
449
+ align-items: center;
450
+ background: var(--st-semantic-surface-subtle, #f8fafc);
451
+ border: 1px solid var(--st-semantic-border-interactive, #cbd5e1);
452
+ border-radius: var(--st-radius-pill, 999px);
453
+ color: var(--st-semantic-text-secondary, #475569);
454
+ cursor: var(--st-cursor-interactive, pointer);
455
+ display: inline-flex;
456
+ font: inherit;
457
+ font-size: 0.8125rem;
458
+ font-weight: 500;
459
+ gap: var(--st-spacing-1, 0.25rem);
460
+ line-height: 1;
461
+ padding: 0.3125rem var(--st-spacing-2, 0.5rem);
462
+ transition:
463
+ background-color var(--st-motion-fast, 120ms) var(--st-motion-easing, ease),
464
+ color var(--st-motion-fast, 120ms) var(--st-motion-easing, ease),
465
+ border-color var(--st-motion-fast, 120ms) var(--st-motion-easing, ease);
466
+ }
467
+
468
+ .st-barChart__filterChip:hover {
469
+ background: var(--st-semantic-surface-hover, #eef2f7);
470
+ }
471
+
472
+ .st-barChart__filterChip:focus-visible {
473
+ outline: 2px solid var(--st-semantic-border-interactive, var(--st-semantic-action-primary));
474
+ outline-offset: 2px;
475
+ }
476
+
477
+ /* Selected chip: solid accent fill + matching text — signalled by colour AND
478
+ by aria-pressed, never by opacity alone. */
479
+ .st-barChart__filterChip--selected {
480
+ background: var(--st-semantic-action-primary, #2563eb);
481
+ border-color: var(--st-semantic-action-primary, #2563eb);
482
+ color: var(--st-semantic-text-inverse, #fff);
483
+ }
484
+
485
+ /* Colour swatch echoing the bar tone, for quick visual mapping chip↔bar. */
486
+ .st-barChart__filterSwatch {
487
+ border-radius: var(--st-radius-sm, 0.25rem);
488
+ display: inline-block;
489
+ height: 0.625rem;
490
+ width: 0.625rem;
491
+ }
492
+ .st-barChart__filterChip--category1 .st-barChart__filterSwatch { background: var(--st-semantic-data-category1); }
493
+ .st-barChart__filterChip--category2 .st-barChart__filterSwatch { background: var(--st-semantic-data-category2); }
494
+ .st-barChart__filterChip--category3 .st-barChart__filterSwatch { background: var(--st-semantic-data-category3); }
495
+ .st-barChart__filterChip--category4 .st-barChart__filterSwatch { background: var(--st-semantic-data-category4); }
496
+ .st-barChart__filterChip--category5 .st-barChart__filterSwatch { background: var(--st-semantic-data-category5); }
497
+ .st-barChart__filterChip--category6 .st-barChart__filterSwatch { background: var(--st-semantic-data-category6); }
498
+ .st-barChart__filterChip--category7 .st-barChart__filterSwatch { background: var(--st-semantic-data-category7); }
499
+ .st-barChart__filterChip--category8 .st-barChart__filterSwatch { background: var(--st-semantic-data-category8); }
500
+
426
501
  .st-barChart__tooltip {
427
502
  background: var(--st-component-barChart-tooltipBackground, var(--st-semantic-surface-inverse));
428
503
  border-radius: var(--st-radius-sm, 0.25rem);
@@ -449,6 +524,7 @@
449
524
  }
450
525
 
451
526
  @media (prefers-reduced-motion: reduce) {
452
- .st-barChart__bar { transition: none; }
527
+ .st-barChart__bar,
528
+ .st-barChart__filterChip { transition: none; }
453
529
  }
454
530
  </style>
@@ -18,9 +18,12 @@ type BarChartProps = {
18
18
  */
19
19
  selectedKeys?: string[];
20
20
  /**
21
- * Called when a bar is clicked / activated (Enter / Space) with the bar's
22
- * key (its `label`). When provided the bars become focusable buttons; when
23
- * omitted the chart is purely presentational (no interactivity, unchanged).
21
+ * Called with the bar's key (its `label`) when the user selects it. When
22
+ * provided, an ACCESSIBLE row of filter chips (real <button>s) is rendered
23
+ * OUTSIDE the aria-hidden SVG — that is the keyboard + screen-reader surface.
24
+ * The SVG bars themselves stay decorative (aria-hidden) and only offer a
25
+ * mouse click shortcut for sighted pointer users. When omitted the chart is
26
+ * purely presentational (no interactivity, unchanged).
24
27
  */
25
28
  onSelect?: (key: string) => void;
26
29
  class?: string;
@@ -1 +1 @@
1
- {"version":3,"file":"BarChart.svelte.d.ts","sourceRoot":"","sources":["../src/lib/BarChart.svelte.ts"],"names":[],"mappings":"AAGE,MAAM,MAAM,YAAY,GACpB,WAAW,GACX,WAAW,GACX,WAAW,GACX,WAAW,GACX,WAAW,GACX,WAAW,GACX,WAAW,GACX,WAAW,CAAC;AAEhB,MAAM,MAAM,aAAa,GAAG;IAC1B,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,YAAY,CAAC;CACrB,CAAC;AAMF,KAAK,aAAa,GAAG;IACnB,IAAI,EAAE,aAAa,EAAE,CAAC;IACtB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,WAAW,CAAC,EAAE,UAAU,GAAG,YAAY,CAAC;IACxC,KAAK,EAAE,MAAM,CAAC;IACd;;;;;OAKG;IACH,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;IACxB;;;;OAIG;IACH,QAAQ,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;IACjC,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,CAAC;AAwOJ,QAAA,MAAM,QAAQ,mDAAwC,CAAC;AACvD,KAAK,QAAQ,GAAG,UAAU,CAAC,OAAO,QAAQ,CAAC,CAAC;AAC5C,eAAe,QAAQ,CAAC"}
1
+ {"version":3,"file":"BarChart.svelte.d.ts","sourceRoot":"","sources":["../src/lib/BarChart.svelte.ts"],"names":[],"mappings":"AAGE,MAAM,MAAM,YAAY,GACpB,WAAW,GACX,WAAW,GACX,WAAW,GACX,WAAW,GACX,WAAW,GACX,WAAW,GACX,WAAW,GACX,WAAW,CAAC;AAEhB,MAAM,MAAM,aAAa,GAAG;IAC1B,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,YAAY,CAAC;CACrB,CAAC;AAMF,KAAK,aAAa,GAAG;IACnB,IAAI,EAAE,aAAa,EAAE,CAAC;IACtB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,WAAW,CAAC,EAAE,UAAU,GAAG,YAAY,CAAC;IACxC,KAAK,EAAE,MAAM,CAAC;IACd;;;;;OAKG;IACH,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;IACxB;;;;;;;OAOG;IACH,QAAQ,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;IACjC,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,CAAC;AA+OJ,QAAA,MAAM,QAAQ,mDAAwC,CAAC;AACvD,KAAK,QAAQ,GAAG,UAAU,CAAC,OAAO,QAAQ,CAAC,CAAC;AAC5C,eAAe,QAAQ,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sentropic/design-system-svelte",
3
- "version": "0.22.0",
3
+ "version": "0.22.1",
4
4
  "type": "module",
5
5
  "publishConfig": {
6
6
  "access": "public"