@aiaiai-pt/design-system 0.31.0 → 0.32.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.
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
<script>
|
|
16
16
|
import { fromLonLat } from 'ol/proj.js';
|
|
17
17
|
import { boundingExtent } from 'ol/extent.js';
|
|
18
|
-
import { createTileLayer, createMapStyles, createOverlayLayers, watchTheme, renderMapError } from './map-utils.js';
|
|
18
|
+
import { createTileLayer, createMapStyles, createOverlayLayers, clusterTooltipLabel, watchTheme, renderMapError } from './map-utils.js';
|
|
19
19
|
|
|
20
20
|
let {
|
|
21
21
|
/** @type {{ id: string, lon: number, lat: number, label?: string, [key: string]: any }[]} */
|
|
@@ -286,21 +286,21 @@
|
|
|
286
286
|
return;
|
|
287
287
|
}
|
|
288
288
|
|
|
289
|
-
const clustered = feature.get('features');
|
|
290
289
|
if (container) container.style.cursor = 'pointer';
|
|
291
290
|
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
tooltipEl.style.display = 'block';
|
|
297
|
-
tooltipOverlay.setPosition(feature.getGeometry()?.getCoordinates());
|
|
298
|
-
}
|
|
299
|
-
} else {
|
|
300
|
-
tooltipEl.textContent = `${clustered?.length ?? 0} items`;
|
|
301
|
-
tooltipEl.style.display = 'block';
|
|
302
|
-
tooltipOverlay.setPosition(feature.getGeometry()?.getCoordinates());
|
|
291
|
+
const label = clusterTooltipLabel(feature);
|
|
292
|
+
if (label == null) {
|
|
293
|
+
tooltipEl.style.display = 'none';
|
|
294
|
+
return;
|
|
303
295
|
}
|
|
296
|
+
tooltipEl.textContent = label;
|
|
297
|
+
tooltipEl.style.display = 'block';
|
|
298
|
+
// Cluster features carry a Point geometry; raw overlay (file/WFS)
|
|
299
|
+
// features can be polygons/lines, so anchor those at the pointer.
|
|
300
|
+
const coords = feature.get('features') === undefined
|
|
301
|
+
? evt.coordinate
|
|
302
|
+
: feature.getGeometry()?.getCoordinates();
|
|
303
|
+
tooltipOverlay.setPosition(coords);
|
|
304
304
|
});
|
|
305
305
|
|
|
306
306
|
// `popup` opens the anchored ficha-resumo overlay; otherwise `onclick`
|
|
@@ -61,27 +61,32 @@
|
|
|
61
61
|
data-testid="text-size-adjuster"
|
|
62
62
|
{...rest}
|
|
63
63
|
>
|
|
64
|
+
<!--
|
|
65
|
+
WCAG 2.5.3 (Label in Name): the accessible name MUST contain the visible
|
|
66
|
+
text. The visible label is the glyph ("A−" / "A" / "A+"), so the
|
|
67
|
+
glyph is part of the name (NOT aria-hidden, NOT replaced by aria-label) and
|
|
68
|
+
the descriptive i18n label rides along as visually-hidden text. Result, e.g.
|
|
69
|
+
name = "A− Decrease text size", whose visible substring "A−"
|
|
70
|
+
satisfies the rule while screen readers still hear the full description.
|
|
71
|
+
-->
|
|
64
72
|
<button
|
|
65
73
|
type="button"
|
|
66
74
|
class="ts-btn ts-decrease"
|
|
67
|
-
aria-label={decreaseLabel}
|
|
68
75
|
disabled={atMin}
|
|
69
76
|
onclick={() => emit(decreaseTextSize(current))}
|
|
70
|
-
>A
|
|
77
|
+
>A−<span class="sr-only"> {decreaseLabel}</span></button>
|
|
71
78
|
<button
|
|
72
79
|
type="button"
|
|
73
80
|
class="ts-btn ts-reset"
|
|
74
|
-
aria-label={resetLabel}
|
|
75
81
|
disabled={atDefault}
|
|
76
82
|
onclick={() => emit(DEFAULT_TEXT_SIZE)}
|
|
77
|
-
>A</button>
|
|
83
|
+
>A<span class="sr-only"> {resetLabel}</span></button>
|
|
78
84
|
<button
|
|
79
85
|
type="button"
|
|
80
86
|
class="ts-btn ts-increase"
|
|
81
|
-
aria-label={increaseLabel}
|
|
82
87
|
disabled={atMax}
|
|
83
88
|
onclick={() => emit(increaseTextSize(current))}
|
|
84
|
-
>A
|
|
89
|
+
>A+<span class="sr-only"> {increaseLabel}</span></button>
|
|
85
90
|
<span class="sr-only" aria-live="polite" data-testid="text-size-readout"
|
|
86
91
|
>{current}%</span
|
|
87
92
|
>
|
package/components/Toggle.svelte
CHANGED
|
@@ -10,10 +10,6 @@
|
|
|
10
10
|
@example Disabled
|
|
11
11
|
<Toggle label="Notifications" checked disabled />
|
|
12
12
|
-->
|
|
13
|
-
<script module>
|
|
14
|
-
let _toggleUid = 0;
|
|
15
|
-
</script>
|
|
16
|
-
|
|
17
13
|
<script>
|
|
18
14
|
let {
|
|
19
15
|
/** @type {boolean} */
|
|
@@ -31,9 +27,11 @@
|
|
|
31
27
|
...rest
|
|
32
28
|
} = $props();
|
|
33
29
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
30
|
+
// No auto-generated id: the switch self-names via aria-label, so it needs no
|
|
31
|
+
// id/labelledby plumbing. A module-counter fallback id (`toggle-N`) diverged
|
|
32
|
+
// between SSR and client (server counter ≠ fresh client counter), an id that
|
|
33
|
+
// could surface as a duplicate/parse error; an id is now emitted ONLY when a
|
|
34
|
+
// consumer passes one.
|
|
37
35
|
function handleClick() {
|
|
38
36
|
if (!disabled) {
|
|
39
37
|
checked = !checked;
|
|
@@ -50,7 +48,7 @@
|
|
|
50
48
|
override (e.g. type="submit" if they really want submit semantics).
|
|
51
49
|
-->
|
|
52
50
|
<button
|
|
53
|
-
id
|
|
51
|
+
{id}
|
|
54
52
|
class="toggle"
|
|
55
53
|
class:toggle-on={checked}
|
|
56
54
|
class:toggle-disabled={disabled}
|
|
@@ -58,15 +56,23 @@
|
|
|
58
56
|
type="button"
|
|
59
57
|
role="switch"
|
|
60
58
|
aria-checked={checked}
|
|
61
|
-
aria-
|
|
59
|
+
aria-label={label}
|
|
62
60
|
{...rest}
|
|
63
61
|
onclick={handleClick}
|
|
64
62
|
>
|
|
65
63
|
<span class="toggle-knob"></span>
|
|
66
64
|
</button>
|
|
67
65
|
{#if label}
|
|
66
|
+
<!--
|
|
67
|
+
The switch now names itself directly via aria-label (was aria-labelledby →
|
|
68
|
+
this span). Stricter ACT accessible-name engines (QualWeb/AccessMonitor)
|
|
69
|
+
did not credit a name referenced through a role="none" target and read the
|
|
70
|
+
switch as nameless; a direct aria-label is unambiguous. This span is the
|
|
71
|
+
VISIBLE label only — role="none" keeps it out of the a11y tree so the name
|
|
72
|
+
isn't announced twice. Click still toggles (sighted affordance); the button
|
|
73
|
+
stays the keyboard/AT target.
|
|
74
|
+
-->
|
|
68
75
|
<span
|
|
69
|
-
id="{toggleId}-label"
|
|
70
76
|
class="toggle-label"
|
|
71
77
|
class:toggle-label-disabled={disabled}
|
|
72
78
|
onclick={handleClick}
|
package/components/map-utils.js
CHANGED
|
@@ -374,7 +374,8 @@ export function getHeatmapGradient(el) {
|
|
|
374
374
|
*/
|
|
375
375
|
export function geoStylerToFlat(style) {
|
|
376
376
|
if (!style || typeof style !== "object") return {};
|
|
377
|
-
if (!Array.isArray(style.rules))
|
|
377
|
+
if (!Array.isArray(style.rules))
|
|
378
|
+
return /** @type {OverlayLayerStyle} */ (style);
|
|
378
379
|
const sym = style.rules?.[0]?.symbolizers?.[0];
|
|
379
380
|
if (!sym || typeof sym !== "object") return {};
|
|
380
381
|
/** @type {OverlayLayerStyle} */
|
|
@@ -389,8 +390,10 @@ export function geoStylerToFlat(style) {
|
|
|
389
390
|
if (typeof sym.width === "number") flat.strokeWidth = sym.width;
|
|
390
391
|
} else if (sym.kind === "Fill") {
|
|
391
392
|
if (typeof sym.color === "string") flat.fillColor = sym.color;
|
|
392
|
-
if (typeof sym.outlineColor === "string")
|
|
393
|
-
|
|
393
|
+
if (typeof sym.outlineColor === "string")
|
|
394
|
+
flat.strokeColor = sym.outlineColor;
|
|
395
|
+
if (typeof sym.outlineWidth === "number")
|
|
396
|
+
flat.strokeWidth = sym.outlineWidth;
|
|
394
397
|
}
|
|
395
398
|
return flat;
|
|
396
399
|
}
|
|
@@ -449,7 +452,9 @@ export async function createOverlayLayers(defs, styles) {
|
|
|
449
452
|
const point = new Style({
|
|
450
453
|
image: new CircleStyle({
|
|
451
454
|
radius: flat.pointRadius ?? 6,
|
|
452
|
-
fill: new Fill({
|
|
455
|
+
fill: new Fill({
|
|
456
|
+
color: flat.pointColor ?? flat.strokeColor ?? "#3366cc",
|
|
457
|
+
}),
|
|
453
458
|
stroke,
|
|
454
459
|
}),
|
|
455
460
|
});
|
|
@@ -484,6 +489,39 @@ export async function createOverlayLayers(defs, styles) {
|
|
|
484
489
|
});
|
|
485
490
|
}
|
|
486
491
|
|
|
492
|
+
// ─── Hover tooltip label ─────────────────────────────────────────
|
|
493
|
+
|
|
494
|
+
/**
|
|
495
|
+
* Decide the hover-tooltip text for a feature under the pointer.
|
|
496
|
+
*
|
|
497
|
+
* Markers passed via the `markers` prop go through an OL Cluster source, so
|
|
498
|
+
* each rendered feature carries a `features` collection: one entry → that
|
|
499
|
+
* marker's label; many → "N items". Features served from an overlay `layers`
|
|
500
|
+
* def (a file/WFS source) are added to a plain VectorSource and have NO
|
|
501
|
+
* `features` collection (`undefined`) — for those, fall back to the feature's
|
|
502
|
+
* own descriptive props rather than rendering the misleading "0 items".
|
|
503
|
+
*
|
|
504
|
+
* @param {{ get: (key: string) => any }} feature — an OL Feature (or any
|
|
505
|
+
* object exposing `.get(key)`).
|
|
506
|
+
* @returns {string | null} the label to show, or null to hide the tooltip.
|
|
507
|
+
*/
|
|
508
|
+
export function clusterTooltipLabel(feature) {
|
|
509
|
+
const clustered = feature.get("features");
|
|
510
|
+
if (clustered === undefined) {
|
|
511
|
+
const label =
|
|
512
|
+
feature.get("name") ??
|
|
513
|
+
feature.get("title") ??
|
|
514
|
+
feature.get("label") ??
|
|
515
|
+
feature.get("markerData")?.label;
|
|
516
|
+
return label != null && String(label) !== "" ? String(label) : null;
|
|
517
|
+
}
|
|
518
|
+
if (clustered.length === 1) {
|
|
519
|
+
const data = clustered[0].get("markerData");
|
|
520
|
+
return data?.label ?? null;
|
|
521
|
+
}
|
|
522
|
+
return `${clustered.length} items`;
|
|
523
|
+
}
|
|
524
|
+
|
|
487
525
|
// ─── Boundary (#187 D-e prep) ────────────────────────────────────
|
|
488
526
|
|
|
489
527
|
/**
|