@aiaiai-pt/design-system 0.30.0 → 0.31.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.
@@ -449,6 +449,12 @@
449
449
  "required" on the field — not just the disconnected visual hint below.
450
450
  Passed through the DS field components' `{...rest}` onto the input. -->
451
451
  {@const ariaRequired = parameter.required ? "true" : undefined}
452
+ <!-- #252 — a parameter with `editable: false` (visibility.editable === false)
453
+ renders READ-ONLY: its value (e.g. a logged-in citizen's prefilled
454
+ identity — name/email) is shown but not editable. The value still rides
455
+ the payload; the field just can't be changed. Default (undefined/true) is
456
+ editable, so this is purely additive. -->
457
+ {@const editable = parameter.editable !== false}
452
458
  {#if type === "enum" || type === "select" || enumOptions(parameter).length}
453
459
  <Select
454
460
  label={String(parameter.label ?? key)}
@@ -465,6 +471,7 @@
465
471
  name={key}
466
472
  type="number"
467
473
  value={String(values[key] ?? "")}
474
+ readonly={!editable}
468
475
  oninput={(event: Event) => {
469
476
  const value = (event.target as HTMLInputElement).value;
470
477
  setValue(key, value === "" ? "" : Number(value));
@@ -554,7 +561,8 @@
554
561
  <Input
555
562
  label={String(parameter.label ?? key)}
556
563
  name={key}
557
- value={String(values[key] ?? "")}
564
+ value={String(values[key] ?? initialValue(parameter) ?? "")}
565
+ readonly={!editable}
558
566
  oninput={(event: Event) => setValue(key, (event.target as HTMLInputElement).value)}
559
567
  aria-required={ariaRequired}
560
568
  />
@@ -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
- if (clustered?.length === 1) {
293
- const data = clustered[0].get('markerData');
294
- if (data?.label) {
295
- tooltipEl.textContent = data.label;
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`
@@ -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)) return /** @type {OverlayLayerStyle} */ (style);
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") flat.strokeColor = sym.outlineColor;
393
- if (typeof sym.outlineWidth === "number") flat.strokeWidth = sym.outlineWidth;
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({ color: flat.pointColor ?? flat.strokeColor ?? "#3366cc" }),
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
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aiaiai-pt/design-system",
3
- "version": "0.30.0",
3
+ "version": "0.31.1",
4
4
  "description": "Design system tokens and Svelte components for aiaiai products",
5
5
  "license": "MIT",
6
6
  "type": "module",