@celestia-island/hikari 0.55.334 → 0.55.335

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.334",
3
+ "version": "0.55.335",
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",
@@ -42,11 +42,32 @@ export type HkDockBarSurface = "glass" | "solid";
42
42
  *
43
43
  * Relationship to HkScrollContainer: that component's dockTop /
44
44
  * dockBottom named slots are the contract for docks that live INSIDE a
45
- * scrolling region — pinned to the scroll viewport, excluded from the
46
- * scroll flow. HkDockBar is the carrier for docks that FLOAT over a
47
- * canvas or page instead. Pick by host: scrolling region →
48
- * HkScrollContainer dock slots; canvas/page overlay → HkDockBar. Do
49
- * not nest an HkDockBar inside a scroll viewport expecting pinning.
45
+ * scrolling region. Those slots place a dock two ways (2026-11-10
46
+ * addition — `dockMode`):
47
+ * - `ride` (the pinned-to-the-visible-bottom-edge form): the dock
48
+ * mounts inside the scroll viewport after the content, on a spacer
49
+ * exactly one measured dock height tall, `position: sticky; bottom:
50
+ * 0`. The air between the content end and the dock is therefore
51
+ * exactly one dock height; short content lets the dock follow the
52
+ * content end (the leftover window height falls below it), long
53
+ * content pins it to the visible bottom edge. Use it for a dock that
54
+ * belongs to the CONTENT (a cruise canvas's bottom bar under its
55
+ * last row). The container's ride wrapper is `pointer-events: none`
56
+ * (it covers content while pinned), so this carrier's own box
57
+ * contract is what keeps the dock interactive: the anchor box catches
58
+ * nothing, the surface catches everything.
59
+ * - `pin` (the default, older form): the dock is a non-scrolling flex
60
+ * SIBLING of the viewport, so it sits on the container's edge and the
61
+ * viewport shrinks by its height — correct for chrome that owns the
62
+ * region's frame rather than trailing its content.
63
+ * HkDockBar is the carrier for docks that FLOAT over a canvas or page
64
+ * instead. Pick by host: scrolling region → HkScrollContainer dock slots
65
+ * (`ride` to follow the content, `pin` to own the edge); canvas/page
66
+ * overlay → HkDockBar. Do not nest an HkDockBar inside a scroll viewport
67
+ * expecting the container to place it — the container's slots do the
68
+ * placing, and a dock it mounted inside the viewport (ride) makes
69
+ * `closest(".hk-scroll-dock")` true, which is how a carrier knows to stop
70
+ * publishing shell-level clearance for it.
50
71
  *
51
72
  * Surfaces:
52
73
  * - `glass` (default): translucent surface + backdrop blur.
@@ -17,9 +17,32 @@
17
17
  display: flex;
18
18
  align-items: stretch;
19
19
  gap: var(--space-12);
20
+ /* A short board fills the strip: the lanes box is at least the viewport
21
+ height, so lane chrome reaches the strip's floor. */
20
22
  min-height: 100%;
21
23
  }
22
24
 
25
+ /* ── Ride boards: the lanes box must follow its CONTENT ────────────────
26
+ 2026-11-10 review S0, measured on a real board (chest cruise canvas):
27
+ in a column-flex strip viewport `min-height: 100%` makes the lanes BOX
28
+ exactly as tall as the viewport, so the ride spacer + sticky dock —
29
+ which are IN FLOW after that box — always sit below the fold. The dock
30
+ can then only hug the visible bottom edge, the spacer clears nothing,
31
+ and the air goes back to being the window's leftover (measured 275.8px
32
+ static / 563.8px at the scroll end against a 139px dock, i.e. no
33
+ improvement over the pin board and worse when scrolled). Releasing the
34
+ min-height lets the box end at the real content end, which is what the
35
+ ride placement needs: the spacer becomes the visible air and the dock
36
+ trails the content.
37
+
38
+ Ride only, by the root hook HkKanban emits for a ride host: a pin board
39
+ must keep the full-height lanes box it always had, so this rule never
40
+ reaches an existing board. Specificity (0,3,0) beats the base rule
41
+ above; a host that wants its own rule can override it the same way. */
42
+ .hk-kanban[data-dock-mode="ride"] .hk-kanban-lanes {
43
+ min-height: 0;
44
+ }
45
+
23
46
  .hk-kanban[data-axis="vertical"] .hk-kanban-lanes {
24
47
  flex-direction: column;
25
48
  }
@@ -105,6 +105,26 @@ export default defineComponent({
105
105
  * contract generates no spacer and the shrink pin is inert in the
106
106
  * block viewport — a horizontal board gets a no-op by design. */
107
107
  dockTailClearance: { type: Boolean, default: false },
108
+ /** Where a `dockBottom` dock lives, forwarded to the strip's
109
+ * HkScrollContainer (see its `dockMode`): "pin" (default) keeps the
110
+ * dock on the strip's bottom edge, "ride" moves it into the strip's
111
+ * content flow so the air above it is the measured dock occupancy and
112
+ * the dock follows the content. Only effective on axis="vertical"/
113
+ * "both" boards — a horizontal strip carries its tail on the cross
114
+ * axis, where the container's ride placement is gated off by design.
115
+ *
116
+ * A ride board ALSO carries `data-dock-mode="ride"` on its root — only
117
+ * while a `dockBottom` slot is actually mounted, since that hook
118
+ * releases this component's full-height lanes box
119
+ * (see HkKanban.scss): `.hk-kanban-lanes { min-height: 100% }` keeps
120
+ * the lanes box as tall as the viewport, which would push the in-flow
121
+ * spacer + dock below the fold and turn the air back into the
122
+ * window's leftover (measured on a real board: 275.8px static vs the
123
+ * 139px dock, i.e. zero improvement over the pin board). The
124
+ * attribute is emitted only while a ride dock is genuinely mounted (not
125
+ * for pin, not for a horizontal board, not for an empty slot), so every
126
+ * existing board is untouched. */
127
+ dockMode: { type: String as PropType<"pin" | "ride">, default: "pin" },
108
128
  },
109
129
  emits: ["move"],
110
130
  setup(props, { slots, emit, expose }) {
@@ -185,90 +205,117 @@ export default defineComponent({
185
205
 
186
206
  expose({ scrollHost, dragging });
187
207
 
188
- return () => (
189
- <div
190
- class="hk-kanban"
191
- data-axis={props.axis}
192
- data-dragging={dragging.value ? "" : undefined}
193
- aria-label={props.ariaLabel}
194
- >
195
- {props.lanes.length === 0
196
- ? (slots.empty?.() ?? null)
197
- : (
198
- <HScrollContainer
199
- ref={scrollHost}
200
- class="hk-kanban-strip"
201
- axis={stripAxis.value}
202
- overscanScreens={1}
203
- dockTailClearance={props.dockTailClearance}
204
- >
205
- <div
206
- class="hk-kanban-lanes"
207
- // One variable; the stylesheet decides whether it is the
208
- // lane's width (horizontal boards) or its height (vertical).
209
- style={
210
- props.laneSize ? { "--hk-kanban-lane-size": props.laneSize } : undefined
211
- }
208
+ return () => {
209
+ // Docks attach to the strip's VERTICAL tail: `axis="horizontal"`
210
+ // boards keep the pre-2026-11-10 shape (no forwarded dock slot), the
211
+ // same gate the container itself applies to the ride placement —
212
+ // there the tail is the cross axis, where an in-flow block child
213
+ // leaks onto the hidden vertical axis.
214
+ const docksOnTail = stripAxis.value !== "horizontal";
215
+ // Styling hook AND behaviour switch for the CONTENT HEIGHT of the
216
+ // board (see the dockMode prop doc): a ride dock only follows the
217
+ // content when the lanes box is allowed to end above the viewport
218
+ // bottom. Ride only — a pin board keeps the full-height lanes box it
219
+ // always had, so no existing board changes. Gated on the dock actually
220
+ // being mounted too (S2-2): a requested-but-empty slot is legal, and
221
+ // releasing the lanes box for it would only shrink lane chrome on a
222
+ // short board while no dock is there to justify it.
223
+ const rideBoard = docksOnTail && props.dockMode === "ride" && !!slots.dockBottom;
224
+ return (
225
+ <div
226
+ class="hk-kanban"
227
+ data-axis={props.axis}
228
+ data-dock-mode={rideBoard ? "ride" : undefined}
229
+ data-dragging={dragging.value ? "" : undefined}
230
+ aria-label={props.ariaLabel}
231
+ >
232
+ {props.lanes.length === 0
233
+ ? (slots.empty?.() ?? null)
234
+ : (
235
+ <HScrollContainer
236
+ ref={scrollHost}
237
+ class="hk-kanban-strip"
238
+ axis={stripAxis.value}
239
+ overscanScreens={1}
240
+ dockTailClearance={props.dockTailClearance}
241
+ dockMode={props.dockMode}
212
242
  >
213
- {props.lanes.map((lane, laneIndex) => {
214
- const laneKey = props.laneKeyOf(lane, laneIndex);
215
- const cards = cardsFor(lane, laneIndex);
216
- return (
217
- <section
218
- key={laneKey}
219
- class="hk-kanban-lane"
220
- data-lane={laneKey}
221
- data-drop-target={dropTarget.value === laneKey ? "" : undefined}
222
- onDragover={(event: DragEvent) => onDragOver(laneKey, event)}
223
- onDragleave={(event: DragEvent) => onDragLeave(laneKey, event)}
224
- onDrop={(event: DragEvent) => onDrop(laneKey, cards.length, event)}
243
+ {{
244
+ default: () => (
245
+ <div
246
+ class="hk-kanban-lanes"
247
+ // One variable; the stylesheet decides whether it is the
248
+ // lane's width (horizontal boards) or its height (vertical).
249
+ style={
250
+ props.laneSize ? { "--hk-kanban-lane-size": props.laneSize } : undefined
251
+ }
225
252
  >
226
- <header class="hk-kanban-lane-header">
227
- {slots.laneHeader?.({
228
- lane,
229
- laneIndex,
230
- laneKey,
231
- cards,
232
- })}
233
- </header>
234
- <div class="hk-kanban-cards">
235
- {cards.map((card, cardIndex) => {
236
- const cardKey =
237
- props.cardKeyOf?.(card, cardIndex) ?? `${laneKey}#${cardIndex}`;
238
- return (
239
- <div
240
- key={cardKey}
241
- class="hk-kanban-card"
242
- data-card={cardKey}
243
- data-card-index={cardIndex}
244
- draggable={props.draggable || undefined}
245
- onDragstart={(event: DragEvent) =>
246
- onDragStart(card, laneKey, cardIndex, event)
247
- }
248
- onDragend={onDragEnd}
249
- >
250
- {slots.card?.({
251
- card,
252
- cardIndex,
253
+ {props.lanes.map((lane, laneIndex) => {
254
+ const laneKey = props.laneKeyOf(lane, laneIndex);
255
+ const cards = cardsFor(lane, laneIndex);
256
+ return (
257
+ <section
258
+ key={laneKey}
259
+ class="hk-kanban-lane"
260
+ data-lane={laneKey}
261
+ data-drop-target={dropTarget.value === laneKey ? "" : undefined}
262
+ onDragover={(event: DragEvent) => onDragOver(laneKey, event)}
263
+ onDragleave={(event: DragEvent) => onDragLeave(laneKey, event)}
264
+ onDrop={(event: DragEvent) => onDrop(laneKey, cards.length, event)}
265
+ >
266
+ <header class="hk-kanban-lane-header">
267
+ {slots.laneHeader?.({
253
268
  lane,
254
269
  laneIndex,
255
270
  laneKey,
271
+ cards,
256
272
  })}
273
+ </header>
274
+ <div class="hk-kanban-cards">
275
+ {cards.map((card, cardIndex) => {
276
+ const cardKey =
277
+ props.cardKeyOf?.(card, cardIndex) ?? `${laneKey}#${cardIndex}`;
278
+ return (
279
+ <div
280
+ key={cardKey}
281
+ class="hk-kanban-card"
282
+ data-card={cardKey}
283
+ data-card-index={cardIndex}
284
+ draggable={props.draggable || undefined}
285
+ onDragstart={(event: DragEvent) =>
286
+ onDragStart(card, laneKey, cardIndex, event)
287
+ }
288
+ onDragend={onDragEnd}
289
+ >
290
+ {slots.card?.({
291
+ card,
292
+ cardIndex,
293
+ lane,
294
+ laneIndex,
295
+ laneKey,
296
+ })}
297
+ </div>
298
+ );
299
+ })}
300
+ {/* The tail keeps the lane's empty space hittable
301
+ (the lane itself owns the drop). */}
302
+ <div class="hk-kanban-lane-tail" />
257
303
  </div>
258
- );
259
- })}
260
- {/* The tail keeps the lane's empty space hittable
261
- (the lane itself owns the drop). */}
262
- <div class="hk-kanban-lane-tail" />
263
- </div>
264
- {slots.laneFooter?.({ lane, laneIndex, laneKey, cards })}
265
- </section>
266
- );
267
- })}
268
- </div>
269
- </HScrollContainer>
270
- )}
271
- </div>
272
- );
304
+ {slots.laneFooter?.({ lane, laneIndex, laneKey, cards })}
305
+ </section>
306
+ );
307
+ })}
308
+ </div>
309
+ ),
310
+ // Forwarded so a board's own chrome (a cruise canvas's dock
311
+ // under the lanes) keeps the container's dock contract —
312
+ // including the --hk-scroll-dock-* heights it publishes.
313
+ dockBottom: docksOnTail ? slots.dockBottom : undefined,
314
+ }}
315
+ </HScrollContainer>
316
+ )}
317
+ </div>
318
+ );
319
+ };
273
320
  },
274
321
  });