@celestia-island/hikari 0.55.257 → 0.55.259
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.
package/package.json
CHANGED
|
@@ -1,33 +1,43 @@
|
|
|
1
1
|
// AI-generated: developed with the DeepSeek Harness agent (model DeepSeek V4.1 Flash); human-directed.
|
|
2
2
|
|
|
3
|
-
/* HkDateTag — the locale-aware
|
|
4
|
-
* docblock for the contract).
|
|
5
|
-
*
|
|
6
|
-
*
|
|
3
|
+
/* HkDateTag — the locale-aware one-line date pill (see the component
|
|
4
|
+
* docblock for the contract). Segments are the calendar figures laid
|
|
5
|
+
* out on ONE horizontal line in the active locale's order; the year
|
|
6
|
+
* and the optional time segment read as context (muted ink). */
|
|
7
7
|
|
|
8
8
|
.hk-date-tag {
|
|
9
9
|
display: inline-flex;
|
|
10
|
-
flex-direction:
|
|
11
|
-
align-items:
|
|
10
|
+
flex-direction: row;
|
|
11
|
+
align-items: baseline;
|
|
12
12
|
padding: var(--space-2, 2px) var(--space-10, 0.625rem);
|
|
13
13
|
border-radius: var(--radius-full, 999px);
|
|
14
14
|
background: rgb(var(--color-text) / 6%);
|
|
15
15
|
white-space: nowrap;
|
|
16
|
+
/* One size for the whole pill: segments AND separators inherit it,
|
|
17
|
+
* so a large ambient host font cannot inflate the separators. */
|
|
18
|
+
font-size: var(--text-2xs, 0.625rem);
|
|
19
|
+
line-height: 1.4;
|
|
16
20
|
}
|
|
17
21
|
|
|
18
|
-
.hk-date-
|
|
19
|
-
display: block;
|
|
20
|
-
text-align: center;
|
|
21
|
-
line-height: 1.25;
|
|
22
|
-
font-size: var(--text-2xs, 0.625rem);
|
|
22
|
+
.hk-date-tag__seg {
|
|
23
23
|
font-variant-numeric: tabular-nums;
|
|
24
24
|
color: rgb(var(--color-text));
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
-
/* Context
|
|
28
|
-
* time
|
|
29
|
-
.
|
|
30
|
-
.hk-date-
|
|
27
|
+
/* Context segments: the year (wherever the locale puts it) and the
|
|
28
|
+
* optional time segment — muted, same size so the one-line baseline
|
|
29
|
+
* stays even. */
|
|
30
|
+
.hk-date-tag__seg--year,
|
|
31
|
+
.hk-date-tag__seg--time {
|
|
32
|
+
color: rgb(var(--color-text) / 55%);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/* The locale's own separator literals (and the blank one ahead of the
|
|
36
|
+
* time segment) — muted like the context ink. `pre` keeps that blank
|
|
37
|
+
* separator's single space alive: a space-only flex item would
|
|
38
|
+
* otherwise trim it at line end under the pill's nowrap and glue the
|
|
39
|
+
* time segment to the date. */
|
|
40
|
+
.hk-date-tag__sep {
|
|
31
41
|
color: rgb(var(--color-text) / 55%);
|
|
32
|
-
|
|
42
|
+
white-space: pre;
|
|
33
43
|
}
|
|
@@ -6,17 +6,18 @@ import HkDateTagDefault, { HkDateTag } from "./HkDateTag";
|
|
|
6
6
|
import { setLocale } from "../i18n/context";
|
|
7
7
|
|
|
8
8
|
/**
|
|
9
|
-
* HkDateTag — the locale-aware date
|
|
10
|
-
* COMPONENT contract: the
|
|
11
|
-
* (year
|
|
12
|
-
* live when the host flips the
|
|
13
|
-
*
|
|
9
|
+
* HkDateTag — the locale-aware one-line date pill. These cases pin the
|
|
10
|
+
* COMPONENT contract: the segment order follows the ACTIVE hikari
|
|
11
|
+
* locale (year first for zh-Hans, year LAST for en) with that locale's
|
|
12
|
+
* own separators, the order re-derives live when the host flips the
|
|
13
|
+
* locale, `time` appends the muted trailing segment, and unparseable
|
|
14
|
+
* input renders the empty pill shell.
|
|
14
15
|
*/
|
|
15
16
|
|
|
16
17
|
const mounts: Array<{ app: ReturnType<typeof createApp>; container: HTMLElement }> = [];
|
|
17
18
|
|
|
18
19
|
// Built in LOCAL time so the calendar fields (2024-10-06 14:30) survive
|
|
19
|
-
// every CI time zone; the
|
|
20
|
+
// every CI time zone; the pill renders local calendar segments.
|
|
20
21
|
const SHIP_DATE = new Date(2024, 9, 6, 14, 30);
|
|
21
22
|
|
|
22
23
|
function mountTag(props: { value: string | number | Date; time?: boolean }): HTMLElement {
|
|
@@ -28,16 +29,20 @@ function mountTag(props: { value: string | number | Date; time?: boolean }): HTM
|
|
|
28
29
|
return container;
|
|
29
30
|
}
|
|
30
31
|
|
|
31
|
-
function
|
|
32
|
-
return [...container.querySelectorAll<HTMLElement>(".hk-date-
|
|
32
|
+
function segsOf(container: HTMLElement): HTMLElement[] {
|
|
33
|
+
return [...container.querySelectorAll<HTMLElement>(".hk-date-tag__seg")];
|
|
33
34
|
}
|
|
34
35
|
|
|
35
|
-
function
|
|
36
|
+
function pillOf(container: HTMLElement): HTMLElement {
|
|
36
37
|
const el = container.querySelector<HTMLElement>(".hk-date-tag");
|
|
37
|
-
expect(el, "
|
|
38
|
+
expect(el, "pill renders").toBeTruthy();
|
|
38
39
|
return el!;
|
|
39
40
|
}
|
|
40
41
|
|
|
42
|
+
function textOf(container: HTMLElement): string {
|
|
43
|
+
return pillOf(container).textContent ?? "";
|
|
44
|
+
}
|
|
45
|
+
|
|
41
46
|
// setLocale mutates the module-global i18n state — restore the default
|
|
42
47
|
// after every case (the HkSolarLocationSetting suite's idiom).
|
|
43
48
|
afterEach(async () => {
|
|
@@ -49,81 +54,91 @@ afterEach(async () => {
|
|
|
49
54
|
});
|
|
50
55
|
|
|
51
56
|
describe("HkDateTag locale ordering", () => {
|
|
52
|
-
it("
|
|
57
|
+
it("lays year/month/day segments out on one line for zh-Hans", async () => {
|
|
53
58
|
await setLocale("zh-Hans");
|
|
54
59
|
const container = mountTag({ value: SHIP_DATE });
|
|
55
60
|
|
|
56
|
-
const
|
|
57
|
-
expect(
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
expect(
|
|
62
|
-
expect(
|
|
61
|
+
const pill = pillOf(container);
|
|
62
|
+
expect(pill.classList.contains("hk-date-tag")).toBe(true);
|
|
63
|
+
// One horizontal run of segments, in the locale's order, joined by
|
|
64
|
+
// the locale's own separators.
|
|
65
|
+
const segs = segsOf(container);
|
|
66
|
+
expect(segs.map((s) => s.textContent)).toEqual(["2024", "10", "06"]);
|
|
67
|
+
expect(textOf(container)).toBe("2024/10/06");
|
|
68
|
+
// The year segment carries its modifier wherever it lands.
|
|
69
|
+
expect(segs[0].classList.contains("hk-date-tag__seg--year")).toBe(true);
|
|
70
|
+
expect(segs[1].classList.contains("hk-date-tag__seg--year")).toBe(false);
|
|
63
71
|
});
|
|
64
72
|
|
|
65
73
|
it("orders month/day/year for en — the year goes LAST", async () => {
|
|
66
74
|
await setLocale("en");
|
|
67
75
|
const container = mountTag({ value: SHIP_DATE });
|
|
68
76
|
|
|
69
|
-
const
|
|
70
|
-
expect(
|
|
71
|
-
expect(
|
|
77
|
+
const segs = segsOf(container);
|
|
78
|
+
expect(segs.map((s) => s.textContent)).toEqual(["10", "06", "2024"]);
|
|
79
|
+
expect(textOf(container)).toBe("10/06/2024");
|
|
80
|
+
expect(segs[2].classList.contains("hk-date-tag__seg--year")).toBe(true);
|
|
72
81
|
});
|
|
73
82
|
|
|
74
|
-
it("re-orders the
|
|
83
|
+
it("re-orders the line live when the locale flips under a mounted pill", async () => {
|
|
75
84
|
await setLocale("zh-Hans");
|
|
76
85
|
const container = mountTag({ value: SHIP_DATE });
|
|
77
|
-
expect(
|
|
86
|
+
expect(segsOf(container).map((s) => s.textContent)).toEqual(["2024", "10", "06"]);
|
|
78
87
|
|
|
79
88
|
await setLocale("en");
|
|
80
89
|
await nextTick();
|
|
81
|
-
expect(
|
|
90
|
+
expect(segsOf(container).map((s) => s.textContent)).toEqual(["10", "06", "2024"]);
|
|
82
91
|
|
|
83
92
|
await setLocale("zh-Hans");
|
|
84
93
|
await nextTick();
|
|
85
|
-
expect(
|
|
94
|
+
expect(segsOf(container).map((s) => s.textContent)).toEqual(["2024", "10", "06"]);
|
|
86
95
|
});
|
|
87
96
|
});
|
|
88
97
|
|
|
89
|
-
describe("HkDateTag time
|
|
90
|
-
it("appends the muted time
|
|
98
|
+
describe("HkDateTag time segment", () => {
|
|
99
|
+
it("appends the muted time segment only when asked", async () => {
|
|
91
100
|
await setLocale("zh-Hans");
|
|
92
|
-
const
|
|
101
|
+
const withTimeContainer = mountTag({ value: SHIP_DATE, time: true });
|
|
102
|
+
const withTime = segsOf(withTimeContainer);
|
|
93
103
|
expect(withTime).toHaveLength(4);
|
|
94
|
-
expect(withTime[3].classList.contains("hk-date-
|
|
104
|
+
expect(withTime[3].classList.contains("hk-date-tag__seg--time")).toBe(true);
|
|
95
105
|
expect(withTime[3].textContent).toBe("14:30");
|
|
106
|
+
// The blank separator between the date and the time reads as one
|
|
107
|
+
// space in the pill's text.
|
|
108
|
+
expect(textOf(withTimeContainer)).toBe("2024/10/06 14:30");
|
|
96
109
|
|
|
97
|
-
const
|
|
110
|
+
const withoutTimeContainer = mountTag({ value: SHIP_DATE });
|
|
111
|
+
const withoutTime = segsOf(withoutTimeContainer);
|
|
98
112
|
expect(withoutTime).toHaveLength(3);
|
|
99
|
-
expect(withoutTime.some((
|
|
113
|
+
expect(withoutTime.some((s) => s.classList.contains("hk-date-tag__seg--time"))).toBe(false);
|
|
100
114
|
});
|
|
101
115
|
|
|
102
|
-
it("formats the time
|
|
116
|
+
it("formats the time segment for the active locale", async () => {
|
|
103
117
|
await setLocale("en");
|
|
104
|
-
const
|
|
105
|
-
|
|
106
|
-
expect(
|
|
118
|
+
const container = mountTag({ value: SHIP_DATE, time: true });
|
|
119
|
+
const segs = segsOf(container);
|
|
120
|
+
expect(segs).toHaveLength(4);
|
|
121
|
+
expect(segs[3].textContent).toBe("02:30 PM");
|
|
122
|
+
expect(textOf(container)).toBe("10/06/2024 02:30 PM");
|
|
107
123
|
});
|
|
108
124
|
});
|
|
109
125
|
|
|
110
126
|
describe("HkDateTag value normalization", () => {
|
|
111
127
|
it("accepts epoch milliseconds and date strings like the Date form", async () => {
|
|
112
128
|
await setLocale("en");
|
|
113
|
-
|
|
114
|
-
expect(rowsOf(mountTag({ value: SHIP_DATE })).map((r) => r.textContent)).toEqual(expected);
|
|
129
|
+
expect(textOf(mountTag({ value: SHIP_DATE }))).toBe("10/06/2024");
|
|
115
130
|
// A number is epoch MILLISECONDS.
|
|
116
|
-
expect(
|
|
117
|
-
expect(
|
|
131
|
+
expect(textOf(mountTag({ value: SHIP_DATE.getTime() }))).toBe("10/06/2024");
|
|
132
|
+
expect(textOf(mountTag({ value: "2024-10-06T14:30:00" }))).toBe("10/06/2024");
|
|
118
133
|
});
|
|
119
134
|
|
|
120
|
-
it("renders the empty
|
|
135
|
+
it("renders the empty pill shell for unparseable input", async () => {
|
|
121
136
|
await setLocale("zh-Hans");
|
|
122
137
|
const container = mountTag({ value: "not-a-date" });
|
|
123
138
|
|
|
124
|
-
const
|
|
125
|
-
expect(
|
|
126
|
-
expect(
|
|
139
|
+
const pill = pillOf(container);
|
|
140
|
+
expect(pill.children).toHaveLength(0);
|
|
141
|
+
expect(segsOf(container)).toHaveLength(0);
|
|
127
142
|
});
|
|
128
143
|
});
|
|
129
144
|
|
|
@@ -5,43 +5,44 @@ import { activeLocale } from "../i18n/context";
|
|
|
5
5
|
import "./HkDateTag.scss";
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
|
-
* HkDateTag — a locale-aware date
|
|
9
|
-
* segments
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
* With the `time` flag a fourth, muted
|
|
13
|
-
* (hour + minute, 2-digit
|
|
8
|
+
* HkDateTag — a locale-aware date pill: ONE horizontal line whose
|
|
9
|
+
* calendar segments read in the order the ACTIVE hikari locale writes
|
|
10
|
+
* dates (zh: 2024/10/06 — year first; en: 10/06/2024 — the year LAST;
|
|
11
|
+
* de: 06.10.2024 — day first), joined by that locale's own literal
|
|
12
|
+
* separators. With the `time` flag a fourth, muted segment carries the
|
|
13
|
+
* locale time (hour + minute, 2-digit) after a blank separator.
|
|
14
14
|
*
|
|
15
15
|
* Generic business part (peer of HkTemplateText / HkMarkdownRenderer,
|
|
16
|
-
* NOT of the HkInput family): hosts render compact per-row dates in
|
|
17
|
-
* tables and cards — wowsp's water-meter ship tables are the
|
|
18
|
-
* consumer.
|
|
16
|
+
* NOT of the HkInput family): hosts render compact per-row dates in
|
|
17
|
+
* data tables and cards — wowsp's water-meter ship tables are the
|
|
18
|
+
* first consumer.
|
|
19
19
|
*
|
|
20
20
|
* Contract:
|
|
21
21
|
* - `value` accepts a Date, an epoch-MILLISECONDS number or a
|
|
22
22
|
* date-ish string, normalized with `new Date(value)` (so a number is
|
|
23
23
|
* NEVER seconds — pass what `Date.now()` returns). Unparseable input
|
|
24
|
-
* renders NOTHING but the empty
|
|
24
|
+
* renders NOTHING but the empty pill shell; the caller owns the
|
|
25
25
|
* empty state.
|
|
26
|
-
* - The
|
|
27
|
-
* locale→
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
26
|
+
* - The segment order AND the separators come from the locale itself,
|
|
27
|
+
* never a hardcoded locale→layout map:
|
|
28
|
+
* `Intl.DateTimeFormat(activeLocale(), { year, month, day })
|
|
29
|
+
* .formatToParts(date)` supplies the year/month/day segments and
|
|
30
|
+
* the literal separators in the order that locale writes them. The
|
|
31
|
+
* component is a computed over the reactive i18n state, so a host
|
|
32
|
+
* `setLocale` re-orders the line live.
|
|
33
|
+
* - `time` (default false) appends the muted time segment
|
|
34
|
+
* (`hk-date-tag__seg--time`) after a blank separator.
|
|
34
35
|
*/
|
|
35
36
|
export const HkDateTag = defineComponent({
|
|
36
37
|
name: "HkDateTag",
|
|
37
38
|
props: {
|
|
38
39
|
/** The date to render — Date, epoch milliseconds or a parseable
|
|
39
|
-
* string. Invalid input renders the empty
|
|
40
|
+
* string. Invalid input renders the empty pill. */
|
|
40
41
|
value: {
|
|
41
42
|
type: [String, Number, Date] as PropType<string | number | Date>,
|
|
42
43
|
required: true,
|
|
43
44
|
},
|
|
44
|
-
/** Append the muted locale time
|
|
45
|
+
/** Append the muted locale time segment (hour + minute, 2-digit). */
|
|
45
46
|
time: { type: Boolean, default: false },
|
|
46
47
|
},
|
|
47
48
|
setup(props) {
|
|
@@ -52,25 +53,24 @@ export const HkDateTag = defineComponent({
|
|
|
52
53
|
|
|
53
54
|
// The locale is read through the reactive i18n state (activeLocale
|
|
54
55
|
// reads it on every evaluation), so this computed re-runs — and the
|
|
55
|
-
//
|
|
56
|
+
// line re-orders — whenever the host calls setLocale. One formatter
|
|
56
57
|
// per shape per locale, rebuilt only on a locale/value change (the
|
|
57
58
|
// HkCalendarHeatmap idiom).
|
|
58
59
|
const locale = computed(() => activeLocale());
|
|
59
60
|
|
|
60
|
-
/**
|
|
61
|
-
|
|
61
|
+
/** Every format part — the year/month/day segments interleaved
|
|
62
|
+
* with the locale's own literal separators. */
|
|
63
|
+
const parts = computed<Intl.DateTimeFormatPart[]>(() => {
|
|
62
64
|
const d = date.value;
|
|
63
65
|
if (d === null) return [];
|
|
64
66
|
return new Intl.DateTimeFormat(locale.value, {
|
|
65
67
|
year: "numeric",
|
|
66
68
|
month: "2-digit",
|
|
67
69
|
day: "2-digit",
|
|
68
|
-
})
|
|
69
|
-
.formatToParts(d)
|
|
70
|
-
.filter((part) => part.type === "year" || part.type === "month" || part.type === "day");
|
|
70
|
+
}).formatToParts(d);
|
|
71
71
|
});
|
|
72
72
|
|
|
73
|
-
/** The locale time (hour + minute, 2-digit) when the
|
|
73
|
+
/** The locale time (hour + minute, 2-digit) when the pill opts
|
|
74
74
|
* in — null without `time` or for an invalid date. */
|
|
75
75
|
const timeText = computed<string | null>(() => {
|
|
76
76
|
const d = date.value;
|
|
@@ -80,16 +80,31 @@ export const HkDateTag = defineComponent({
|
|
|
80
80
|
|
|
81
81
|
return () => (
|
|
82
82
|
<span class="hk-date-tag">
|
|
83
|
-
{
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
83
|
+
{parts.value.map((part, index) =>
|
|
84
|
+
part.type === "literal" ? (
|
|
85
|
+
part.value ? (
|
|
86
|
+
<span key={`sep-${index}`} class="hk-date-tag__sep">
|
|
87
|
+
{part.value}
|
|
88
|
+
</span>
|
|
89
|
+
) : null
|
|
90
|
+
) : (
|
|
91
|
+
<span
|
|
92
|
+
key={part.type}
|
|
93
|
+
class={part.type === "year" ? "hk-date-tag__seg hk-date-tag__seg--year" : "hk-date-tag__seg"}
|
|
94
|
+
>
|
|
95
|
+
{part.value}
|
|
96
|
+
</span>
|
|
97
|
+
),
|
|
98
|
+
)}
|
|
99
|
+
{timeText.value !== null && (
|
|
100
|
+
<span key="time-sep" class="hk-date-tag__sep">
|
|
101
|
+
{" "}
|
|
89
102
|
</span>
|
|
90
|
-
)
|
|
103
|
+
)}
|
|
91
104
|
{timeText.value !== null && (
|
|
92
|
-
<span key="time" class="hk-date-
|
|
105
|
+
<span key="time" class="hk-date-tag__seg hk-date-tag__seg--time">
|
|
106
|
+
{timeText.value}
|
|
107
|
+
</span>
|
|
93
108
|
)}
|
|
94
109
|
</span>
|
|
95
110
|
);
|
|
@@ -80,6 +80,25 @@ describe("HkDonutChart", () => {
|
|
|
80
80
|
expect(empty.querySelector(".hk-donut--empty")).toBeTruthy();
|
|
81
81
|
});
|
|
82
82
|
|
|
83
|
+
it("renders the legendIcon slot's glyph in place of the text label", () => {
|
|
84
|
+
const el = mount(
|
|
85
|
+
<HkDonutChart
|
|
86
|
+
entries={ENTRIES}
|
|
87
|
+
v-slots={{
|
|
88
|
+
legendIcon: (slice: { key: string }) => <b data-flag={slice.key}>flag</b>,
|
|
89
|
+
}}
|
|
90
|
+
/>,
|
|
91
|
+
);
|
|
92
|
+
const rows = [...el.querySelectorAll(".hk-donut__legend-row")];
|
|
93
|
+
expect(rows.map((r) => r.querySelector("[data-flag]")?.getAttribute("data-flag"))).toEqual([
|
|
94
|
+
"dd",
|
|
95
|
+
"ca",
|
|
96
|
+
"bb",
|
|
97
|
+
]);
|
|
98
|
+
// the plain-text labels are gone; the color dots stay
|
|
99
|
+
expect(rows.every((r) => r.querySelector(".hk-donut__legend-dot"))).toBe(true);
|
|
100
|
+
});
|
|
101
|
+
|
|
83
102
|
it("carries the chart-level aria label", () => {
|
|
84
103
|
const el = mount(<HkDonutChart entries={ENTRIES} ariaLabel="ship mix" />);
|
|
85
104
|
expect(el.querySelector(".hk-donut")?.getAttribute("aria-label")).toBe("ship mix");
|
|
@@ -29,7 +29,9 @@ function defaultFill(i: number): string {
|
|
|
29
29
|
* o'clock start, clockwise), legend rows that stack in fixed-height
|
|
30
30
|
* columns with percents shown inline, and paired hover focus —
|
|
31
31
|
* hovering a slice or its legend row lifts the pair and dims the
|
|
32
|
-
* block's siblings without rescaling any shape.
|
|
32
|
+
* block's siblings without rescaling any shape. A scoped `legendIcon`
|
|
33
|
+
* slot (slice => node) swaps a legend row's text label for a glyph —
|
|
34
|
+
* nation flags, tier marks — keeping the color dot and the row hint.
|
|
33
35
|
*
|
|
34
36
|
* Entries may carry an explicit `color`; absent entries ride the
|
|
35
37
|
* default primary ladder above. Hints ride the consumer's tooltip
|
|
@@ -63,7 +65,7 @@ export default defineComponent({
|
|
|
63
65
|
emits: {
|
|
64
66
|
select: (key: string) => typeof key === "string",
|
|
65
67
|
},
|
|
66
|
-
setup(props, { emit }) {
|
|
68
|
+
setup(props, { emit, slots }) {
|
|
67
69
|
const hovered = ref<string | null>(null);
|
|
68
70
|
|
|
69
71
|
const slices = computed<HkDonutSlice[]>(() =>
|
|
@@ -129,7 +131,14 @@ export default defineComponent({
|
|
|
129
131
|
onClick={() => emit("select", s.key)}
|
|
130
132
|
>
|
|
131
133
|
<span class="hk-donut__legend-dot" style={{ background: s.fill }} />
|
|
132
|
-
<span class="hk-donut__legend-label">
|
|
134
|
+
<span class="hk-donut__legend-label">
|
|
135
|
+
{/* legendIcon slot: an optional per-slice glyph
|
|
136
|
+
* (flags, tier marks) rendered IN PLACE of the
|
|
137
|
+
* text label — the color dot stays, and the row's
|
|
138
|
+
* data-hint carries the full slice hint either
|
|
139
|
+
* way (wowsp's NationFlag contract). */}
|
|
140
|
+
{slots.legendIcon?.(s) ?? s.label}
|
|
141
|
+
</span>
|
|
133
142
|
<span class="hk-donut__legend-pct">{s.percentInt}%</span>
|
|
134
143
|
</div>
|
|
135
144
|
))}
|