@celestia-island/hikari 0.49.2 → 0.49.4

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.49.2",
3
+ "version": "0.49.4",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "description": "Hikari Vue 3 component library — production-grade UI components based on shittim-chest design system",
@@ -393,7 +393,7 @@ describe("HkDatePicker", () => {
393
393
  await nextTick();
394
394
  panel()?.querySelector<HTMLButtonElement>(".hk-dp-title-btn")?.click();
395
395
  await settle();
396
- expect(panel()?.querySelector<HTMLButtonElement>(".hk-dp-title-btn")?.textContent).toBe("2026");
396
+ await waitForTitle("2026");
397
397
  const navs = () => panel()?.querySelectorAll<HTMLButtonElement>(".hk-dp-nav");
398
398
  navs()?.[1].click();
399
399
  await nextTick();
@@ -413,23 +413,36 @@ describe("HkDatePicker", () => {
413
413
  const stage = panel()?.querySelector<HTMLElement>(".hk-dp-stage");
414
414
  expect(stage).not.toBeNull();
415
415
  expect(stage?.children.length).toBe(1); // the single days pane
416
+ const pickCount = () =>
417
+ stage?.querySelectorAll<HTMLButtonElement>(".hk-dp-cell[data-variant='pick']").length ?? 0;
418
+ // Destination state = the entering pane mounted AND the leaving pane
419
+ // gone; mid-transition both conditions half-hold.
420
+ const settledDrill = (days: boolean) => (): boolean =>
421
+ days
422
+ ? stage?.children.length === 1 &&
423
+ stage?.querySelectorAll<HTMLButtonElement>(".hk-dp-cell:not([data-variant])").length === 42
424
+ : pickCount() === 12 && stage?.children.length === 1;
416
425
  panel()?.querySelector<HTMLButtonElement>(".hk-dp-title-btn")?.click();
417
426
  await settle();
427
+ await waitForView("the months grid settled to one pane", settledDrill(false));
418
428
  expect(panel()?.querySelector<HTMLElement>(".hk-dp-stage")).toBe(stage);
419
429
  expect(stage?.getAttribute("data-dir")).toBe("fwd");
420
430
  expect(stage?.children.length).toBe(1); // one pane at a time after settle
421
- expect(stage?.querySelectorAll<HTMLButtonElement>(".hk-dp-cell[data-variant='pick']").length).toBe(12);
431
+ expect(pickCount()).toBe(12);
422
432
  panel()?.querySelector<HTMLButtonElement>(".hk-dp-title-btn")?.click();
423
433
  await settle();
434
+ await waitForView("the years grid settled to one pane", settledDrill(false));
424
435
  expect(stage?.getAttribute("data-dir")).toBe("fwd");
425
- expect(stage?.querySelectorAll<HTMLButtonElement>(".hk-dp-cell[data-variant='pick']").length).toBe(12);
436
+ expect(pickCount()).toBe(12);
426
437
  // back steps down the stack one level at a time: years → months → days.
427
438
  panel()?.querySelector<HTMLButtonElement>(".hk-dp-back")?.click();
428
439
  await settle();
440
+ await waitForView("the months grid again, one pane", settledDrill(false));
429
441
  expect(stage?.getAttribute("data-dir")).toBe("back");
430
- expect(stage?.querySelectorAll<HTMLButtonElement>(".hk-dp-cell[data-variant='pick']").length).toBe(12);
442
+ expect(pickCount()).toBe(12);
431
443
  panel()?.querySelector<HTMLButtonElement>(".hk-dp-back")?.click();
432
444
  await settle();
445
+ await waitForView("the days grid settled to one pane", settledDrill(true));
433
446
  expect(stage?.getAttribute("data-dir")).toBe("back");
434
447
  expect(stage?.querySelectorAll<HTMLButtonElement>(".hk-dp-cell:not([data-variant])").length).toBe(42);
435
448
  });
@@ -83,6 +83,20 @@ async function waitForTitle(expected: string): Promise<void> {
83
83
  }
84
84
  }
85
85
 
86
+ /** The pick cells of the drilled months view (exactly 12 once mounted). */
87
+ function pickCells(): HTMLButtonElement[] {
88
+ return Array.from(picker()?.querySelectorAll<HTMLButtonElement>(".hk-dtp-cell[data-variant='pick']") ?? []);
89
+ }
90
+
91
+ /** Generic state poll over the same drill race: wait until `probe` holds. */
92
+ async function waitForView(desc: string, probe: () => boolean): Promise<void> {
93
+ const deadline = Date.now() + 2000;
94
+ while (!probe()) {
95
+ if (Date.now() > deadline) throw new Error(`view never reached: ${desc}`);
96
+ await new Promise((r) => setTimeout(r, 10));
97
+ }
98
+ }
99
+
86
100
  function picker(): HTMLElement | null {
87
101
  return document.querySelector<HTMLElement>(".hk-dtp");
88
102
  }
@@ -178,6 +192,10 @@ describe("HkDateTimePicker", () => {
178
192
  const monthBtn = picker()?.querySelectorAll<HTMLButtonElement>(".hk-dtp-title-btn")[0];
179
193
  monthBtn?.click();
180
194
  await settle();
195
+ // Destination state = the months pane mounted AND the leaving days
196
+ // pane gone — mid-transition both conditions half-hold.
197
+ await waitForView("the months grid settled to one pane", () =>
198
+ pickCells().length === 12 && picker()?.querySelectorAll(".hk-dtp-cell").length === 12);
181
199
  const cells = Array.from(picker()?.querySelectorAll<HTMLElement>(".hk-dtp-cell") ?? [])
182
200
  .map((c) => c.textContent ?? "");
183
201
  const expected = Array.from({ length: 12 }, (_, i) =>
@@ -196,6 +214,7 @@ describe("HkDateTimePicker", () => {
196
214
  const monthBtn = picker()?.querySelectorAll<HTMLButtonElement>(".hk-dtp-title-btn")[0];
197
215
  monthBtn?.click();
198
216
  await settle();
217
+ await waitForView("the months grid", () => pickCells().length === 12);
199
218
  expect(stage?.getAttribute("data-dir")).toBe("fwd");
200
219
  expect(picker()?.querySelector<HTMLElement>(".hk-dtp-stage")).toBe(stage);
201
220
  expect(stage?.children.length).toBe(1); // one pane at a time after settle
@@ -205,8 +224,9 @@ describe("HkDateTimePicker", () => {
205
224
  expect(picker()?.querySelectorAll(".hk-dtp-step").length).toBe(2);
206
225
  picker()?.querySelector<HTMLButtonElement>(".hk-dtp-back")?.click();
207
226
  await settle();
227
+ await waitForView("the days grid settled to one pane", () =>
228
+ dayCells().length === 42 && stage?.children.length === 1);
208
229
  expect(stage?.getAttribute("data-dir")).toBe("back");
209
- expect(dayCells().length).toBe(42);
210
230
  });
211
231
 
212
232
  it("shifts the year with the chevrons inside the month view", async () => {
@@ -229,7 +249,8 @@ describe("HkDateTimePicker", () => {
229
249
  const monthBtn = picker()?.querySelectorAll<HTMLButtonElement>(".hk-dtp-title-btn")[0];
230
250
  monthBtn?.click();
231
251
  await settle();
232
- expect(picker()?.querySelectorAll<HTMLButtonElement>(".hk-dtp-cell[data-variant='pick']").length).toBe(12);
252
+ await waitForView("the months grid", () => pickCells().length === 12);
253
+ expect(pickCells().length).toBe(12);
233
254
  // The first step button is "Hour +"; bumping it changes only the time
234
255
  // part of the model, which must not yank the view back to days.
235
256
  const upBtn = picker()?.querySelector<HTMLButtonElement>(".hk-dtp-step-btn");
@@ -86,48 +86,64 @@
86
86
  }
87
87
 
88
88
  // ------
89
- // Content enter/leave transitions
89
+ // Content enter/leave transitions (desktop ≥768px)
90
90
  //
91
- // Compositor-driven pop: the frame fades and scales 0.94 → 1 — transform
92
- // and opacity ONLY, no layout properties. The previous desktop reveal
93
- // animated `height` (calc-size keywords): height is a layout property, so
94
- // every animation frame re-laid-out the entire modal subtree — for a
95
- // content-heavy modal (a full graph stage, long report bodies) that is
96
- // visible frame drops through the whole open/close, made worse by the
97
- // frame's own backdrop-filter whose sampling region then also changed per
98
- // frame. transform/opacity run on the compositor: zero layout, zero
99
- // paint, and the blurred surface keeps a constant region. Mirrors the
100
- // mobile sheet's translateY reveal, which has always been transform-based.
91
+ // Height-style unveil WITHOUT layout: the visible box starts at 90% of
92
+ // its height and grows to 100% while the frame settles from a +5% offset
93
+ // back to rest (user spec 2026-09-14). Numbers line up so the character
94
+ // of the old height-grow reveal is preserved exactly:
95
+ // t=0 box [center−0.45h, center+0.45h] shifted +0.05h, bottom 10%
96
+ // clipped → visible [center−0.40h, center+0.40h] (the old 80%
97
+ // centered start);
98
+ // t=1 clip 0, offset 0 → visible [center−0.50h, center+0.50h];
99
+ // the bottom edge is pinned at its final line the whole time and the
100
+ // top edge sweeps up 0.1h — content rides rigidly (never squashed).
101
+ // Implemented with transform (translate) + clip-path (inset) + opacity:
102
+ // all compositor/paint-level, zero layout per frame — the previous
103
+ // height-keyword reveal re-laid-out the entire modal subtree every frame
104
+ // and re-filtered the frame's own backdrop blur per frame (visible frame
105
+ // drops on content-heavy modals, chest history topic detail). Scoped to
106
+ // ≥768px; ≤767px keeps the sheet's translateY choreography below.
101
107
  // ------
102
108
 
103
- .hk-modal-content-enter-active {
104
- transition:
105
- opacity var(--hk-modal-duration, 0.3s)
106
- var(--hk-modal-ease-out, cubic-bezier(0.16, 1, 0.3, 1)),
107
- transform var(--hk-modal-duration, 0.3s)
108
- var(--hk-modal-ease-out, cubic-bezier(0.16, 1, 0.3, 1));
109
- will-change: transform, opacity;
110
- }
109
+ @media (min-width: 768px) {
110
+ .hk-modal-content-enter-active {
111
+ transition:
112
+ opacity var(--hk-modal-duration, 0.3s)
113
+ var(--hk-modal-ease-out, cubic-bezier(0.16, 1, 0.3, 1)),
114
+ transform var(--hk-modal-duration, 0.3s)
115
+ var(--hk-modal-ease-out, cubic-bezier(0.16, 1, 0.3, 1)),
116
+ clip-path var(--hk-modal-duration, 0.3s)
117
+ var(--hk-modal-ease-out, cubic-bezier(0.16, 1, 0.3, 1));
118
+ will-change: transform, opacity, clip-path;
119
+ }
111
120
 
112
- .hk-modal-content-leave-active {
113
- transition:
114
- opacity var(--hk-modal-duration, 0.25s)
115
- var(--hk-modal-ease-in, cubic-bezier(0.4, 0, 1, 1)),
116
- transform var(--hk-modal-duration, 0.25s)
117
- var(--hk-modal-ease-in, cubic-bezier(0.4, 0, 1, 1));
118
- will-change: transform, opacity;
119
- }
121
+ .hk-modal-content-leave-active {
122
+ transition:
123
+ opacity var(--hk-modal-duration, 0.25s)
124
+ var(--hk-modal-ease-in, cubic-bezier(0.4, 0, 1, 1)),
125
+ transform var(--hk-modal-duration, 0.25s)
126
+ var(--hk-modal-ease-in, cubic-bezier(0.4, 0, 1, 1)),
127
+ clip-path var(--hk-modal-duration, 0.25s)
128
+ var(--hk-modal-ease-in, cubic-bezier(0.4, 0, 1, 1));
129
+ will-change: transform, opacity, clip-path;
130
+ }
120
131
 
121
- .hk-modal-content-enter-from,
122
- .hk-modal-content-leave-to {
123
- opacity: 0;
124
- transform: translate(-50%, -50%) scale(0.94);
125
- }
132
+ .hk-modal-content-enter-from,
133
+ .hk-modal-content-leave-to {
134
+ opacity: 0;
135
+ transform: translate(-50%, -50%) translateY(5%);
136
+ clip-path: inset(0 0 10% 0 round 0 0 var(--hk-modal-radius, var(--radius-lg, 12px))
137
+ var(--hk-modal-radius, var(--radius-lg, 12px)));
138
+ }
126
139
 
127
- .hk-modal-content-enter-to,
128
- .hk-modal-content-leave-from {
129
- opacity: 1;
130
- transform: translate(-50%, -50%) scale(1);
140
+ .hk-modal-content-enter-to,
141
+ .hk-modal-content-leave-from {
142
+ opacity: 1;
143
+ transform: translate(-50%, -50%);
144
+ clip-path: inset(0 0 0 0 round 0 0 var(--hk-modal-radius, var(--radius-lg, 12px))
145
+ var(--hk-modal-radius, var(--radius-lg, 12px)));
146
+ }
131
147
  }
132
148
 
133
149
  // ------