@celestia-island/hikari 0.55.245 → 0.55.246
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
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
// AI-generated: developed with the DeepSeek Harness agent (model DeepSeek V4.1 Flash); human-directed.
|
|
2
|
+
|
|
3
|
+
/* HkDateTag — the locale-aware stacked date capsule (see the component
|
|
4
|
+
* docblock for the contract). Rows are calendar segments; the year and
|
|
5
|
+
* the optional time row read as context (muted, a touch smaller), the
|
|
6
|
+
* month/day figures carry the full text color. */
|
|
7
|
+
|
|
8
|
+
.hk-date-tag {
|
|
9
|
+
display: inline-flex;
|
|
10
|
+
flex-direction: column;
|
|
11
|
+
align-items: center;
|
|
12
|
+
padding: var(--space-2, 2px) var(--space-10, 0.625rem);
|
|
13
|
+
border-radius: var(--radius-full, 999px);
|
|
14
|
+
background: rgb(var(--color-text) / 6%);
|
|
15
|
+
white-space: nowrap;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
.hk-date-tag__row {
|
|
19
|
+
display: block;
|
|
20
|
+
text-align: center;
|
|
21
|
+
line-height: 1.25;
|
|
22
|
+
font-size: var(--text-2xs, 0.625rem);
|
|
23
|
+
font-variant-numeric: tabular-nums;
|
|
24
|
+
color: rgb(var(--color-text));
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/* Context rows: the year (wherever the locale puts it) and the optional
|
|
28
|
+
* time row — muted and a touch smaller than the month/day figures. */
|
|
29
|
+
.hk-date-tag__row--year,
|
|
30
|
+
.hk-date-tag__row--time {
|
|
31
|
+
color: rgb(var(--color-text) / 55%);
|
|
32
|
+
font-size: calc(var(--text-2xs, 0.625rem) - 1px);
|
|
33
|
+
}
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
// AI-generated: developed with the DeepSeek Harness agent (model DeepSeek V4.1 Flash); human-directed.
|
|
2
|
+
import { afterEach, describe, expect, it } from "vitest";
|
|
3
|
+
import { createApp, h, nextTick } from "vue";
|
|
4
|
+
|
|
5
|
+
import HkDateTagDefault, { HkDateTag } from "./HkDateTag";
|
|
6
|
+
import { setLocale } from "../i18n/context";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* HkDateTag — the locale-aware date capsule. These cases pin the
|
|
10
|
+
* COMPONENT contract: the row order follows the ACTIVE hikari locale
|
|
11
|
+
* (year on top for zh-Hans, year LAST for en), the order re-derives
|
|
12
|
+
* live when the host flips the locale, `time` appends the muted
|
|
13
|
+
* fourth row, and unparseable input renders the empty capsule shell.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
const mounts: Array<{ app: ReturnType<typeof createApp>; container: HTMLElement }> = [];
|
|
17
|
+
|
|
18
|
+
// Built in LOCAL time so the calendar fields (2024-10-06 14:30) survive
|
|
19
|
+
// every CI time zone; the capsule renders local calendar segments.
|
|
20
|
+
const SHIP_DATE = new Date(2024, 9, 6, 14, 30);
|
|
21
|
+
|
|
22
|
+
function mountTag(props: { value: string | number | Date; time?: boolean }): HTMLElement {
|
|
23
|
+
const container = document.createElement("div");
|
|
24
|
+
document.body.appendChild(container);
|
|
25
|
+
const app = createApp({ render: () => h(HkDateTag, props) });
|
|
26
|
+
app.mount(container);
|
|
27
|
+
mounts.push({ app, container });
|
|
28
|
+
return container;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function rowsOf(container: HTMLElement): HTMLElement[] {
|
|
32
|
+
return [...container.querySelectorAll<HTMLElement>(".hk-date-tag__row")];
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function capsuleOf(container: HTMLElement): HTMLElement {
|
|
36
|
+
const el = container.querySelector<HTMLElement>(".hk-date-tag");
|
|
37
|
+
expect(el, "capsule renders").toBeTruthy();
|
|
38
|
+
return el!;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// setLocale mutates the module-global i18n state — restore the default
|
|
42
|
+
// after every case (the HkSolarLocationSetting suite's idiom).
|
|
43
|
+
afterEach(async () => {
|
|
44
|
+
for (const { app, container } of mounts.splice(0)) {
|
|
45
|
+
app.unmount();
|
|
46
|
+
container.remove();
|
|
47
|
+
}
|
|
48
|
+
await setLocale("en");
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
describe("HkDateTag locale ordering", () => {
|
|
52
|
+
it("renders year/month/day rows top to bottom for zh-Hans", async () => {
|
|
53
|
+
await setLocale("zh-Hans");
|
|
54
|
+
const container = mountTag({ value: SHIP_DATE });
|
|
55
|
+
|
|
56
|
+
const capsule = capsuleOf(container);
|
|
57
|
+
expect(capsule.classList.contains("hk-date-tag")).toBe(true);
|
|
58
|
+
const rows = rowsOf(container);
|
|
59
|
+
expect(rows.map((r) => r.textContent)).toEqual(["2024", "10", "06"]);
|
|
60
|
+
// The year row carries its modifier wherever it lands.
|
|
61
|
+
expect(rows[0].classList.contains("hk-date-tag__row--year")).toBe(true);
|
|
62
|
+
expect(rows[1].classList.contains("hk-date-tag__row--year")).toBe(false);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it("orders month/day/year for en — the year goes LAST", async () => {
|
|
66
|
+
await setLocale("en");
|
|
67
|
+
const container = mountTag({ value: SHIP_DATE });
|
|
68
|
+
|
|
69
|
+
const rows = rowsOf(container);
|
|
70
|
+
expect(rows.map((r) => r.textContent)).toEqual(["10", "06", "2024"]);
|
|
71
|
+
expect(rows[2].classList.contains("hk-date-tag__row--year")).toBe(true);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it("re-orders the rows live when the locale flips under a mounted capsule", async () => {
|
|
75
|
+
await setLocale("zh-Hans");
|
|
76
|
+
const container = mountTag({ value: SHIP_DATE });
|
|
77
|
+
expect(rowsOf(container).map((r) => r.textContent)).toEqual(["2024", "10", "06"]);
|
|
78
|
+
|
|
79
|
+
await setLocale("en");
|
|
80
|
+
await nextTick();
|
|
81
|
+
expect(rowsOf(container).map((r) => r.textContent)).toEqual(["10", "06", "2024"]);
|
|
82
|
+
|
|
83
|
+
await setLocale("zh-Hans");
|
|
84
|
+
await nextTick();
|
|
85
|
+
expect(rowsOf(container).map((r) => r.textContent)).toEqual(["2024", "10", "06"]);
|
|
86
|
+
});
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
describe("HkDateTag time row", () => {
|
|
90
|
+
it("appends the muted time row only when asked", async () => {
|
|
91
|
+
await setLocale("zh-Hans");
|
|
92
|
+
const withTime = rowsOf(mountTag({ value: SHIP_DATE, time: true }));
|
|
93
|
+
expect(withTime).toHaveLength(4);
|
|
94
|
+
expect(withTime[3].classList.contains("hk-date-tag__row--time")).toBe(true);
|
|
95
|
+
expect(withTime[3].textContent).toBe("14:30");
|
|
96
|
+
|
|
97
|
+
const withoutTime = rowsOf(mountTag({ value: SHIP_DATE }));
|
|
98
|
+
expect(withoutTime).toHaveLength(3);
|
|
99
|
+
expect(withoutTime.some((r) => r.classList.contains("hk-date-tag__row--time"))).toBe(false);
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
it("formats the time row for the active locale", async () => {
|
|
103
|
+
await setLocale("en");
|
|
104
|
+
const rows = rowsOf(mountTag({ value: SHIP_DATE, time: true }));
|
|
105
|
+
expect(rows).toHaveLength(4);
|
|
106
|
+
expect(rows[3].textContent).toBe("02:30 PM");
|
|
107
|
+
});
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
describe("HkDateTag value normalization", () => {
|
|
111
|
+
it("accepts epoch milliseconds and date strings like the Date form", async () => {
|
|
112
|
+
await setLocale("en");
|
|
113
|
+
const expected = ["10", "06", "2024"];
|
|
114
|
+
expect(rowsOf(mountTag({ value: SHIP_DATE })).map((r) => r.textContent)).toEqual(expected);
|
|
115
|
+
// A number is epoch MILLISECONDS.
|
|
116
|
+
expect(rowsOf(mountTag({ value: SHIP_DATE.getTime() })).map((r) => r.textContent)).toEqual(expected);
|
|
117
|
+
expect(rowsOf(mountTag({ value: "2024-10-06T14:30:00" })).map((r) => r.textContent)).toEqual(expected);
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
it("renders the empty capsule shell for unparseable input", async () => {
|
|
121
|
+
await setLocale("zh-Hans");
|
|
122
|
+
const container = mountTag({ value: "not-a-date" });
|
|
123
|
+
|
|
124
|
+
const capsule = capsuleOf(container);
|
|
125
|
+
expect(capsule.children).toHaveLength(0);
|
|
126
|
+
expect(rowsOf(container)).toHaveLength(0);
|
|
127
|
+
});
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
describe("HkDateTag module surface", () => {
|
|
131
|
+
it("exports the same component as the default and named binding", () => {
|
|
132
|
+
expect(HkDateTagDefault).toBe(HkDateTag);
|
|
133
|
+
expect((HkDateTag as unknown as { name: string }).name).toBe("HkDateTag");
|
|
134
|
+
});
|
|
135
|
+
});
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
// AI-generated: developed with the DeepSeek Harness agent (model DeepSeek V4.1 Flash); human-directed.
|
|
2
|
+
import { computed, defineComponent, type PropType } from "vue";
|
|
3
|
+
|
|
4
|
+
import { activeLocale } from "../i18n/context";
|
|
5
|
+
import "./HkDateTag.scss";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* HkDateTag — a locale-aware date capsule: a small pill whose calendar
|
|
9
|
+
* segments are stacked vertically, ONE PER ROW, each centered, ordered
|
|
10
|
+
* the way the ACTIVE hikari locale orders dates (zh: year → month → day
|
|
11
|
+
* top to bottom; en: month → day → year, the year LAST; de: day first …).
|
|
12
|
+
* With the `time` flag a fourth, muted row carries the locale time
|
|
13
|
+
* (hour + minute, 2-digit each).
|
|
14
|
+
*
|
|
15
|
+
* Generic business part (peer of HkTemplateText / HkMarkdownRenderer,
|
|
16
|
+
* NOT of the HkInput family): hosts render compact per-row dates in data
|
|
17
|
+
* tables and cards — wowsp's water-meter ship tables are the first
|
|
18
|
+
* consumer.
|
|
19
|
+
*
|
|
20
|
+
* Contract:
|
|
21
|
+
* - `value` accepts a Date, an epoch-MILLISECONDS number or a
|
|
22
|
+
* date-ish string, normalized with `new Date(value)` (so a number is
|
|
23
|
+
* NEVER seconds — pass what `Date.now()` returns). Unparseable input
|
|
24
|
+
* renders NOTHING but the empty capsule shell; the caller owns the
|
|
25
|
+
* empty state.
|
|
26
|
+
* - The row order is derived from the locale itself, never a hardcoded
|
|
27
|
+
* locale→order map: `Intl.DateTimeFormat(activeLocale(), { year,
|
|
28
|
+
* month, day }).formatToParts(date)` supplies the year/month/day
|
|
29
|
+
* segments in the order that locale writes them. The component is a
|
|
30
|
+
* computed over the reactive i18n state, so a host `setLocale`
|
|
31
|
+
* re-orders the rows live.
|
|
32
|
+
* - `time` (default false) appends the muted time row
|
|
33
|
+
* (`hk-date-tag__row--time`) under the date rows.
|
|
34
|
+
*/
|
|
35
|
+
export const HkDateTag = defineComponent({
|
|
36
|
+
name: "HkDateTag",
|
|
37
|
+
props: {
|
|
38
|
+
/** The date to render — Date, epoch milliseconds or a parseable
|
|
39
|
+
* string. Invalid input renders the empty capsule. */
|
|
40
|
+
value: {
|
|
41
|
+
type: [String, Number, Date] as PropType<string | number | Date>,
|
|
42
|
+
required: true,
|
|
43
|
+
},
|
|
44
|
+
/** Append the muted locale time row (hour + minute, 2-digit). */
|
|
45
|
+
time: { type: Boolean, default: false },
|
|
46
|
+
},
|
|
47
|
+
setup(props) {
|
|
48
|
+
const date = computed<Date | null>(() => {
|
|
49
|
+
const d = new Date(props.value);
|
|
50
|
+
return Number.isNaN(d.getTime()) ? null : d;
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
// The locale is read through the reactive i18n state (activeLocale
|
|
54
|
+
// reads it on every evaluation), so this computed re-runs — and the
|
|
55
|
+
// rows re-order — whenever the host calls setLocale. One formatter
|
|
56
|
+
// per shape per locale, rebuilt only on a locale/value change (the
|
|
57
|
+
// HkCalendarHeatmap idiom).
|
|
58
|
+
const locale = computed(() => activeLocale());
|
|
59
|
+
|
|
60
|
+
/** The year/month/day segments in the locale's own order. */
|
|
61
|
+
const rows = computed<Intl.DateTimeFormatPart[]>(() => {
|
|
62
|
+
const d = date.value;
|
|
63
|
+
if (d === null) return [];
|
|
64
|
+
return new Intl.DateTimeFormat(locale.value, {
|
|
65
|
+
year: "numeric",
|
|
66
|
+
month: "2-digit",
|
|
67
|
+
day: "2-digit",
|
|
68
|
+
})
|
|
69
|
+
.formatToParts(d)
|
|
70
|
+
.filter((part) => part.type === "year" || part.type === "month" || part.type === "day");
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
/** The locale time (hour + minute, 2-digit) when the capsule opts
|
|
74
|
+
* in — null without `time` or for an invalid date. */
|
|
75
|
+
const timeText = computed<string | null>(() => {
|
|
76
|
+
const d = date.value;
|
|
77
|
+
if (!props.time || d === null) return null;
|
|
78
|
+
return new Intl.DateTimeFormat(locale.value, { hour: "2-digit", minute: "2-digit" }).format(d);
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
return () => (
|
|
82
|
+
<span class="hk-date-tag">
|
|
83
|
+
{rows.value.map((part) => (
|
|
84
|
+
<span
|
|
85
|
+
key={part.type}
|
|
86
|
+
class={part.type === "year" ? "hk-date-tag__row hk-date-tag__row--year" : "hk-date-tag__row"}
|
|
87
|
+
>
|
|
88
|
+
{part.value}
|
|
89
|
+
</span>
|
|
90
|
+
))}
|
|
91
|
+
{timeText.value !== null && (
|
|
92
|
+
<span key="time" class="hk-date-tag__row hk-date-tag__row--time">{timeText.value}</span>
|
|
93
|
+
)}
|
|
94
|
+
</span>
|
|
95
|
+
);
|
|
96
|
+
},
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
export default HkDateTag;
|
package/src/index.ts
CHANGED
|
@@ -555,6 +555,7 @@ export { HkNavSidebar as HNavSidebar } from "./components/HkNavSidebar";
|
|
|
555
555
|
export { HkThemeToggle as HThemeToggle, type ThemeItemScope } from "./components/HkThemeToggle";
|
|
556
556
|
export { HkThemePanelContent as HThemePanelContent } from "./components/HkThemePanelContent";
|
|
557
557
|
export { HkSolarLocationSetting as HSolarLocationSetting } from "./components/HkSolarLocationSetting";
|
|
558
|
+
export { HkDateTag as HDateTag } from "./components/HkDateTag";
|
|
558
559
|
export { HkAuthCard as HAuthCard } from "./components/HkAuthCard";
|
|
559
560
|
export { default as HAuthMethodList } from "./components/HkAuthMethodList";
|
|
560
561
|
export { HkSignInCard as HSignInCard } from "./components/HkSignInCard";
|
|
@@ -836,6 +837,7 @@ export { HkNavSidebar as HkNavSidebar } from "./components/HkNavSidebar";
|
|
|
836
837
|
export { HkThemeToggle as HkThemeToggle } from "./components/HkThemeToggle";
|
|
837
838
|
export { HkThemePanelContent as HkThemePanelContent } from "./components/HkThemePanelContent";
|
|
838
839
|
export { HkSolarLocationSetting as HkSolarLocationSetting } from "./components/HkSolarLocationSetting";
|
|
840
|
+
export { HkDateTag as HkDateTag } from "./components/HkDateTag";
|
|
839
841
|
export { HkAuthCard as HkAuthCard } from "./components/HkAuthCard";
|
|
840
842
|
export { HkSignInCard as HkSignInCard } from "./components/HkSignInCard";
|
|
841
843
|
export { HkThirdPartySignInCard as HkThirdPartySignInCard } from "./components/HkThirdPartySignInCard";
|