@celestia-island/hikari 0.55.226 → 0.55.227

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,6 +1,6 @@
1
1
  {
2
2
  "name": "@celestia-island/hikari",
3
- "version": "0.55.226",
3
+ "version": "0.55.227",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "description": "Hikari UI library, Vue 3 runtime — one design system for Vue 3 and Rust/WASM applications",
@@ -31,6 +31,10 @@ afterEach(() => {
31
31
 
32
32
  const OPTIONS = [{ value: "a1", label: "Agent One" }];
33
33
 
34
+ const DESCRIBED_OPTIONS = [
35
+ { value: "a1", label: "Agent One", description: "Personal · /srv/one" },
36
+ ];
37
+
34
38
  describe("HkPopupSelect (mobile sheet)", () => {
35
39
  it("hideLabel keeps the sheet's breadcrumb title on the field's name", async () => {
36
40
  // hideLabel only suppresses the INLINE caption; the sheet header
@@ -47,4 +51,18 @@ describe("HkPopupSelect (mobile sheet)", () => {
47
51
  expect(title, "the sheet renders a title band").toBeTruthy();
48
52
  expect(title!.textContent).toBe("Agent");
49
53
  });
54
+
55
+ it("described options keep their second line inside the sheet", async () => {
56
+ const c = mount(
57
+ h(HkPopupSelect, { modelValue: "", options: DESCRIBED_OPTIONS }),
58
+ );
59
+ const trigger = c.querySelector<HTMLButtonElement>(".hk-popup-select-trigger")!;
60
+ trigger.dispatchEvent(new MouseEvent("click", { bubbles: true }));
61
+ await Promise.resolve();
62
+ await Promise.resolve();
63
+ const desc = document.body.querySelector(
64
+ ".hk-popover-panel .hk-popup-select-item-desc",
65
+ );
66
+ expect(desc?.textContent).toBe("Personal · /srv/one");
67
+ });
50
68
  });
@@ -149,6 +149,38 @@
149
149
  @include mi.item;
150
150
  }
151
151
 
152
+ /* Two-line option rows, rendered ONLY for options that carry a
153
+ * description — a stock row keeps the bare label span (wrap allowed, no
154
+ * optical shift) so existing consumers see byte-identical geometry (R2).
155
+ * The body owns the row's flex growth so both lines ellipsis against the
156
+ * same edge. */
157
+ .hk-popup-select-item-body {
158
+ flex: 1;
159
+ min-width: 0;
160
+ display: flex;
161
+ flex-direction: column;
162
+ gap: var(--space-2);
163
+ }
164
+
165
+ .hk-popup-select-item-label {
166
+ @include mi.label;
167
+ }
168
+
169
+ /* The secondary line inherits the row color (hover/checked tints flow
170
+ * through) and steps down via opacity + the caption size, so it stays
171
+ * clearly meta against both the plain and the primary checked face. */
172
+ .hk-popup-select-item-desc {
173
+ overflow: hidden;
174
+ text-overflow: ellipsis;
175
+ white-space: nowrap;
176
+ font-size: var(--text-xs);
177
+ font-weight: 400;
178
+ color: inherit;
179
+ opacity: 0.62;
180
+ /* Same italic-overhang clipping room the label mixin reserves. */
181
+ padding-inline-end: var(--hk-menu-item-label-cushion);
182
+ }
183
+
152
184
  /* Keyboard highlight face yields to checked/active; the hover face comes
153
185
  * from mi.item's own guarded :hover (identical values, higher specificity). */
154
186
  .hk-popup-select-item[data-highlighted]:not([data-checked]):not([data-active]) {
@@ -81,4 +81,51 @@ describe("HkPopupSelect", () => {
81
81
  const c = mount(h(HkPopupSelect, { modelValue: "a1", label: "Agent", options: OPTIONS }));
82
82
  expect(c.querySelector(".hk-popup-select-trigger")!.getAttribute("aria-label")).toBeNull();
83
83
  });
84
+
85
+ it("an option description renders its own muted line under the label", async () => {
86
+ const c = mount(
87
+ h(HkPopupSelect, {
88
+ modelValue: "",
89
+ options: [{ value: "a1", label: "Agent One", description: "Personal · /srv/one" }],
90
+ }),
91
+ );
92
+ // Open the popout — option rows live in the anchored panel, which
93
+ // mounts across a microtask (the mobile sheet tests await the same).
94
+ c.querySelector<HTMLButtonElement>(".hk-popup-select-trigger")!
95
+ .dispatchEvent(new MouseEvent("click", { bubbles: true }));
96
+ await Promise.resolve();
97
+ await Promise.resolve();
98
+ const item = document.body.querySelector(".hk-popup-select-item")!;
99
+ expect(item.textContent).toContain("Agent One");
100
+ const desc = item.querySelector(".hk-popup-select-item-desc");
101
+ expect(desc?.textContent).toBe("Personal · /srv/one");
102
+ // The label sits in its own span so the two lines never merge.
103
+ expect(item.querySelector(".hk-popup-select-item-label")?.textContent).toBe("Agent One");
104
+ });
105
+
106
+ it("no description → the exact stock DOM: bare label span, no body wrapper", async () => {
107
+ const c = mount(h(HkPopupSelect, { modelValue: "", options: OPTIONS }));
108
+ c.querySelector<HTMLButtonElement>(".hk-popup-select-trigger")!
109
+ .dispatchEvent(new MouseEvent("click", { bubbles: true }));
110
+ await Promise.resolve();
111
+ await Promise.resolve();
112
+ const item = document.body.querySelector(".hk-popup-select-item")!;
113
+ // R2 contract: description-less rows render the pre-description DOM —
114
+ // no two-line anatomy, so no mi.label ellipsis/optical spec applies.
115
+ expect(item.querySelector(".hk-popup-select-item-body")).toBeNull();
116
+ expect(item.querySelector(".hk-popup-select-item-label")).toBeNull();
117
+ expect(item.querySelector("span")?.textContent).toBe("Agent One");
118
+ });
119
+
120
+ it("the collapsed trigger shows the label only, never the description", () => {
121
+ const c = mount(
122
+ h(HkPopupSelect, {
123
+ modelValue: "a1",
124
+ options: [{ value: "a1", label: "Agent One", description: "secret-meta" }],
125
+ }),
126
+ );
127
+ const value = c.querySelector(".hk-popup-select-value")!;
128
+ expect(value.textContent).toBe("Agent One");
129
+ expect(value.textContent).not.toContain("secret-meta");
130
+ });
84
131
  });
@@ -9,6 +9,15 @@ import "./HkPopupSelect.scss";
9
9
  export interface HkPopupSelectOption {
10
10
  value: string;
11
11
  label: string;
12
+ /** Secondary line under the label, inside the same option row — a
13
+ * muted, single-line ellipsis strip for meta text (a row's owner,
14
+ * address, type) that must stay visible without leaking into the
15
+ * collapsed trigger, which keeps showing `label` alone. Absent → the
16
+ * row renders the EXACT stock DOM (bare label span, wrap allowed, no
17
+ * optical shift): the two-line anatomy exists only when there is a
18
+ * second line to show (R2: the mi.label ellipsis + 0.5px spec is a
19
+ * behavior change stock rows must not inherit silently). */
20
+ description?: string;
12
21
  }
13
22
 
14
23
  const activePopupId = ref<string | null>(null);
@@ -232,7 +241,16 @@ export default defineComponent({
232
241
  onClick={() => select(opt.value)}
233
242
  onPointerenter={() => { highlightedIndex.value = i; }}
234
243
  >
235
- <span>{opt.label}</span>
244
+ {opt.description ? (
245
+ <span class="hk-popup-select-item-body">
246
+ <span class="hk-popup-select-item-label">{opt.label}</span>
247
+ <span class="hk-popup-select-item-desc">{opt.description}</span>
248
+ </span>
249
+ ) : (
250
+ // Stock rows keep the exact pre-description DOM: no
251
+ // body wrapper, no mi.label spec — the R2 contract.
252
+ <span>{opt.label}</span>
253
+ )}
236
254
  </div>
237
255
  ))}
238
256
  </HkScrollContainer>