@celestia-island/hikari 0.55.334 → 0.55.336
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 +1 -1
- package/src/components/HkDockBar.tsx +26 -5
- package/src/components/HkKanban.scss +23 -0
- package/src/components/HkKanban.tsx +125 -78
- package/src/components/HkNumberInput.test.tsx +78 -0
- package/src/components/HkNumberInput.tsx +17 -2
- package/src/components/HkScrollContainer.dockRide.test.tsx +762 -0
- package/src/components/HkScrollContainer.fade.contract.test.ts +14 -4
- package/src/components/HkScrollContainer.scss +127 -0
- package/src/components/HkScrollContainer.tsx +248 -27
- package/src/components/HkTreemap.scss +6 -1
- package/src/components/HkTreemap.test.tsx +100 -6
- package/src/components/HkTreemap.tsx +79 -11
- package/src/components/HkTreemapStage.scss +5 -1
- package/src/components/HkTreemapStage.test.tsx +52 -1
- package/src/components/HkTreemapStage.tsx +18 -3
- package/src/overlay/overlayRoot.styles.test.ts +27 -0
- package/src/overlay/overlayRoot.ts +10 -1
- package/src/utils/dom.test.ts +94 -1
- package/src/utils/dom.ts +13 -1
package/package.json
CHANGED
|
@@ -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
|
|
46
|
-
*
|
|
47
|
-
*
|
|
48
|
-
*
|
|
49
|
-
*
|
|
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
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
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
|
-
{
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
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
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
<
|
|
240
|
-
|
|
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
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
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
|
});
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { afterEach, describe, expect, it } from "vitest";
|
|
2
|
+
import { createApp, h } from "vue";
|
|
3
|
+
|
|
4
|
+
import HkNumberInput from "./HkNumberInput";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* HkNumberInput's label-association contract (#1206): a bare `label` prop
|
|
8
|
+
* used to render a <label> with NO programmatic association to the field
|
|
9
|
+
* (no `for`, no wrapping), so the number field announced as "spin button"
|
|
10
|
+
* to screen readers. Mirrors HkInput's contract — `for` points at the
|
|
11
|
+
* field id, which is `props.id` when given and a useId() generation
|
|
12
|
+
* otherwise — and follows the repo test convention (raw createApp +
|
|
13
|
+
* document queries, no @vue/test-utils).
|
|
14
|
+
*/
|
|
15
|
+
const mounts: Array<{ app: ReturnType<typeof createApp>; container: HTMLElement }> = [];
|
|
16
|
+
|
|
17
|
+
function mountInput(props: Record<string, unknown>): HTMLElement {
|
|
18
|
+
const container = document.createElement("div");
|
|
19
|
+
document.body.appendChild(container);
|
|
20
|
+
const app = createApp({ render: () => h(HkNumberInput, props) });
|
|
21
|
+
app.mount(container);
|
|
22
|
+
mounts.push({ app, container });
|
|
23
|
+
return container;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
afterEach(() => {
|
|
27
|
+
for (const { app, container } of mounts.splice(0)) {
|
|
28
|
+
app.unmount();
|
|
29
|
+
container.remove();
|
|
30
|
+
}
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
describe("HkNumberInput label association (#1206)", () => {
|
|
34
|
+
it("associates a bare label with the field via for/id", () => {
|
|
35
|
+
const container = mountInput({ label: "Per-phone cap", modelValue: 10 });
|
|
36
|
+
console.log("HTML:", container.innerHTML.slice(0, 300));
|
|
37
|
+
const label = container.querySelector("label.hk-input-label");
|
|
38
|
+
const field = container.querySelector<HTMLInputElement>("input.hk-number-input-field");
|
|
39
|
+
expect(label).not.toBeNull();
|
|
40
|
+
expect(field).not.toBeNull();
|
|
41
|
+
expect(label!.getAttribute("for")).toBe(field!.id);
|
|
42
|
+
expect(field!.id).not.toBe("");
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it("an explicit id prop wins over the generated one", () => {
|
|
46
|
+
const container = mountInput({ label: "Cap", modelValue: 1, id: "cap-field" });
|
|
47
|
+
const label = container.querySelector("label.hk-input-label");
|
|
48
|
+
const field = container.querySelector<HTMLInputElement>("input.hk-number-input-field");
|
|
49
|
+
expect(field!.id).toBe("cap-field");
|
|
50
|
+
expect(label!.getAttribute("for")).toBe("cap-field");
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it("keeps field ids unique among instances of ONE app", () => {
|
|
54
|
+
// useId's counter is PER APP: two separate createApp mounts both start
|
|
55
|
+
// at v-0 (uniqueness is a within-app contract — a real host has one
|
|
56
|
+
// app). Two siblings in the same app must never share an id, or the
|
|
57
|
+
// second label's `for` would point at the FIRST field.
|
|
58
|
+
const container = document.createElement("div");
|
|
59
|
+
document.body.appendChild(container);
|
|
60
|
+
const app = createApp({
|
|
61
|
+
render: () =>
|
|
62
|
+
h("div", [
|
|
63
|
+
h(HkNumberInput, { modelValue: 1, label: "First" }),
|
|
64
|
+
h(HkNumberInput, { modelValue: 2, label: "Second" }),
|
|
65
|
+
]),
|
|
66
|
+
});
|
|
67
|
+
app.mount(container);
|
|
68
|
+
mounts.push({ app, container });
|
|
69
|
+
const fields = [...container.querySelectorAll<HTMLInputElement>("input.hk-number-input-field")];
|
|
70
|
+
expect(fields).toHaveLength(2);
|
|
71
|
+
const ids = fields.map((f) => f.id);
|
|
72
|
+
expect(new Set(ids).size).toBe(2);
|
|
73
|
+
// Each label's `for` points at ITS OWN field, in document order.
|
|
74
|
+
const labels = [...container.querySelectorAll("label.hk-input-label")];
|
|
75
|
+
expect(labels[0]!.getAttribute("for")).toBe(ids[0]);
|
|
76
|
+
expect(labels[1]!.getAttribute("for")).toBe(ids[1]);
|
|
77
|
+
});
|
|
78
|
+
});
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { computed, defineComponent, ref, type PropType } from "vue";
|
|
1
|
+
import { computed, defineComponent, ref, useId, type PropType } from "vue";
|
|
2
2
|
import HkNumberSteppers from "./HkNumberSteppers";
|
|
3
3
|
import "./HkNumberInput.scss";
|
|
4
4
|
|
|
@@ -12,6 +12,13 @@ export default defineComponent({
|
|
|
12
12
|
disabled: { type: Boolean, default: false },
|
|
13
13
|
placeholder: { type: String, default: undefined },
|
|
14
14
|
label: { type: String, default: undefined },
|
|
15
|
+
/* `id` for the field element; the rendered label's `for` points here.
|
|
16
|
+
Generated via Vue's useId when omitted, so a bare `label` prop is
|
|
17
|
+
fully associated for screen readers and label clicks with zero
|
|
18
|
+
caller effort (HkInput's contract, same shape). Without this the
|
|
19
|
+
bare <label> had NO programmatic association at all — the field
|
|
20
|
+
announced as "spin button" (2026-11-10, #1206). */
|
|
21
|
+
id: { type: String, default: undefined },
|
|
15
22
|
/* Affix text rendered INSIDE the field (before/after the input,
|
|
16
23
|
flex-centered by the inner row). Slots of the same name take
|
|
17
24
|
precedence; empty/undefined means no affix. Consumers should use
|
|
@@ -60,6 +67,13 @@ export default defineComponent({
|
|
|
60
67
|
emitValue(props.modelValue - props.step);
|
|
61
68
|
}
|
|
62
69
|
|
|
70
|
+
// Field identity: the rendered label points at this id, so a bare
|
|
71
|
+
// `label` prop gives a fully associated field (screen readers, label
|
|
72
|
+
// clicks). An explicit `id` prop wins. useId() must run synchronously
|
|
73
|
+
// in setup; it is SSR-safe (HkInput's same shape).
|
|
74
|
+
const generatedId = useId();
|
|
75
|
+
const fieldId = computed(() => props.id ?? generatedId);
|
|
76
|
+
|
|
63
77
|
const wrapperClass = computed(() => [
|
|
64
78
|
"hk-number-input",
|
|
65
79
|
`hk-number-input-${props.size}`,
|
|
@@ -68,7 +82,7 @@ export default defineComponent({
|
|
|
68
82
|
|
|
69
83
|
return () => (
|
|
70
84
|
<div class={wrapperClass.value}>
|
|
71
|
-
{props.label && <label class="hk-input-label">{props.label}</label>}
|
|
85
|
+
{props.label && <label class="hk-input-label" for={fieldId.value}>{props.label}</label>}
|
|
72
86
|
<div class="hk-number-input-inner">
|
|
73
87
|
{(slots.prefix || props.prefix) ? (
|
|
74
88
|
<span class="hk-number-input-prefix">
|
|
@@ -77,6 +91,7 @@ export default defineComponent({
|
|
|
77
91
|
) : null}
|
|
78
92
|
<input
|
|
79
93
|
ref={inputRef}
|
|
94
|
+
id={fieldId.value}
|
|
80
95
|
type="number"
|
|
81
96
|
class="hk-number-input-field"
|
|
82
97
|
value={props.modelValue}
|