@vunset/vue 1.0.0-rc.2 → 1.0.0-rc.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.
@@ -12,6 +12,8 @@ type __VLS_Props = {
12
12
  numTicks?: number;
13
13
  /** Cut long row labels rather than dropping the ones that would collide. */
14
14
  trimLabels?: boolean;
15
+ /** A title along the axis, such as its unit. */
16
+ label?: string;
15
17
  };
16
18
  declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
17
19
  declare const _default: typeof __VLS_export;
@@ -10,7 +10,8 @@ var ChartAxis_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ define
10
10
  side: {},
11
11
  format: { type: Function },
12
12
  numTicks: {},
13
- trimLabels: { type: Boolean }
13
+ trimLabels: { type: Boolean },
14
+ label: {}
14
15
  },
15
16
  setup(__props) {
16
17
  const props = __props;
@@ -1,5 +1,5 @@
1
1
  import { useChartSeries } from "./use-chart-series.js";
2
- import { chartName } from "./chart-scales.js";
2
+ import { SVG_ANIMATION, chartName } from "./chart-scales.js";
3
3
  import ChartFrame_default from "./ChartFrame.js";
4
4
  import { computed, createBlock, createCommentVNode, createElementBlock, createElementVNode, createSlots, defineComponent, guardReactiveProps, mergeProps, normalizeProps, normalizeStyle, openBlock, renderSlot, toDisplayString, withCtx } from "vue";
5
5
  import { formatNumber } from "@vunset/utils";
@@ -83,6 +83,7 @@ var ChartDonut_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ defin
83
83
  const width = props.arcWidth;
84
84
  const cornerRadius = props.cornerRadius;
85
85
  return defineChart({
86
+ svgAnimation: SVG_ANIMATION,
86
87
  marks: [polar({
87
88
  marks: [radialArc(pie(shown.value, {
88
89
  value: "size",
@@ -52,9 +52,10 @@ declare const __VLS_export: __VLS_WithSlots<import("vue").DefineComponent<{
52
52
  }>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>, {
53
53
  /** Content before the drawing, such as a `ChartLegend`. */
54
54
  default(): unknown;
55
- /** The tooltip's content, for the point in focus. */
55
+ /** The tooltip's content, for the point in focus; `pinned` once a click holds it. */
56
56
  tooltip(props: {
57
57
  point: ChartPoint<any>;
58
+ pinned: boolean;
58
59
  }): unknown;
59
60
  /** Content laid over the plot, centered on it. */
60
61
  center?(): unknown;
@@ -49,7 +49,13 @@ var ChartFrame_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ defin
49
49
  emit("render", context);
50
50
  }
51
51
  const height = computed(() => typeof props.height === "number" ? `${props.height}px` : props.height);
52
- const tooltipBody = ({ primaryPoint }) => primaryPoint ? h("div", mergeProps({ "data-part": "chart-tooltip" }, props.tooltipAttrs?.() ?? {}), [h("div", { "data-part": "tooltip" }, slots.tooltip({ point: primaryPoint }))]) : null;
52
+ const tooltipBody = ({ primaryPoint, pinned }) => primaryPoint ? h("div", mergeProps({
53
+ "data-part": "chart-tooltip",
54
+ "data-pinned": pinned ? "" : void 0
55
+ }, props.tooltipAttrs?.() ?? {}), [h("div", { "data-part": "tooltip" }, slots.tooltip({
56
+ point: primaryPoint,
57
+ pinned
58
+ }))]) : null;
53
59
  const Drawing = defineComponent({
54
60
  name: "ChartDrawing",
55
61
  setup: () => () => h(Chart, {
@@ -22,6 +22,7 @@ export interface ChartAxisPart {
22
22
  format?: (value: any) => string;
23
23
  numTicks?: number;
24
24
  trimLabels?: boolean;
25
+ label?: string;
25
26
  }
26
27
  export interface ChartGridPart {
27
28
  type: 'x' | 'y';
@@ -42,12 +43,17 @@ export interface ChartTooltipSlotProps<T = Record<string, any>> {
42
43
  row: T;
43
44
  /** Its `x-key` value, through the x axis' formatter. */
44
45
  heading: string;
45
- /** The series showing, in order. */
46
+ /** The series showing, in order; in `point` mode, only the point's. */
46
47
  series: ChartTooltipSeries[];
48
+ /** Pinned in place by a click, Enter or Space. */
49
+ pinned: boolean;
47
50
  }
48
51
  export interface ChartTooltipPart {
49
52
  attrs: Record<string, unknown>;
50
53
  slot?: (props: ChartTooltipSlotProps) => unknown;
54
+ mode: 'row' | 'point';
55
+ sticky: boolean;
56
+ headingFormat?: (value: any) => string;
51
57
  }
52
58
  interface Parts {
53
59
  series: ChartSeriesPart;
@@ -83,6 +89,13 @@ declare const __VLS_export: <T extends Record<string, any>>(__VLS_props: NonNull
83
89
  data: T[];
84
90
  /** The key naming each row, along the x axis. */
85
91
  xKey: keyof T & string;
92
+ /**
93
+ * Where the rows sit along the x axis. `category` spaces them evenly, in
94
+ * order; `linear` places them by their `x-key` number; or pass a scale
95
+ * factory, such as d3-scale's `scaleUtc` for dates. Bars need `category`.
96
+ * @default 'category'
97
+ */
98
+ xScale?: "category" | "linear" | (() => unknown);
86
99
  /** A number means pixels. @default '20rem' */
87
100
  height?: number | string;
88
101
  /** Which way the bars grow, and so which way the rows run. @default 'vertical' */
@@ -1,6 +1,6 @@
1
1
  import { createContext } from "../../shared/context.js";
2
2
  import { useChartSeries } from "./use-chart-series.js";
3
- import { curveOf, plotted, rowFocus, rowLabel, rowTicks, tickLabels, valueLabel } from "./chart-scales.js";
3
+ import { SVG_ANIMATION, curveOf, plotted, pointFocus, rowFocus, rowLabel, rowTicks, tickLabels, valueLabel } from "./chart-scales.js";
4
4
  import ChartFrame_default from "./ChartFrame.js";
5
5
  import { Fragment, computed, createBlock, createCommentVNode, createElementBlock, createElementVNode, defineComponent, getCurrentInstance, mergeProps, normalizeStyle, onBeforeUnmount, onMounted, openBlock, renderList, renderSlot, shallowRef, toDisplayString, unref, withCtx } from "vue";
6
6
  import { chartColors, formatNumber } from "@vunset/utils";
@@ -53,6 +53,10 @@ var ChartRoot_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ define
53
53
  props: {
54
54
  data: {},
55
55
  xKey: {},
56
+ xScale: {
57
+ type: [String, Function],
58
+ default: "category"
59
+ },
56
60
  height: { default: "20rem" },
57
61
  orientation: { default: "vertical" },
58
62
  stacked: { type: Boolean },
@@ -97,6 +101,7 @@ var ChartRoot_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ define
97
101
  onHiddenChange: (next) => emit("update:hidden", next)
98
102
  });
99
103
  const horizontal = computed(() => props.orientation === "horizontal");
104
+ const continuous = computed(() => props.xScale !== "category");
100
105
  const shownKeys = computed(() => visible.value.map((entry) => entry.category));
101
106
  const shownColors = computed(() => visible.value.map((entry) => entry.color));
102
107
  const shown = computed(() => series.value.filter((entry) => shownKeys.value.includes(entry.yKey)));
@@ -120,9 +125,12 @@ var ChartRoot_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ define
120
125
  });
121
126
  const formatOf = (entry) => entry?.format ?? axisOf(entry?.scale ?? "y")?.format;
122
127
  const xText = (row) => rowLabel(row, props.xKey, xAxis.value?.format);
123
- /** The row's series for the tooltip, one per key showing. */
124
- function tooltipSeries(row) {
125
- return shownKeys.value.map((key, index) => {
128
+ const headingText = (row) => rowLabel(row, props.xKey, tooltips.value[0]?.headingFormat ?? xAxis.value?.format);
129
+ const tooltip$1 = computed(() => tooltips.value[0]);
130
+ const pointMode = computed(() => tooltip$1.value?.mode === "point");
131
+ /** The row's series for the tooltip, one per key showing; in `point` mode, only `key`'s. */
132
+ function tooltipSeries(row, only) {
133
+ const list = shownKeys.value.map((key, index) => {
126
134
  const value = row[key];
127
135
  const format = formatOf(series.value.find((entry) => entry.yKey === key));
128
136
  return {
@@ -133,21 +141,25 @@ var ChartRoot_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ define
133
141
  text: format ? format(value) : formatNumber(Number(value) || 0)
134
142
  };
135
143
  });
144
+ return pointMode.value && only !== void 0 ? list.filter((entry) => entry.key === only) : list;
136
145
  }
137
- const tooltipProps = (row) => ({
138
- row,
139
- heading: xText(row),
140
- series: tooltipSeries(row)
146
+ const tooltipProps = (point, pinned) => ({
147
+ row: point.datum.row,
148
+ heading: headingText(point.datum.row),
149
+ series: tooltipSeries(point.datum.row, point.datum.category),
150
+ pinned
141
151
  });
142
152
  const band = shallowRef();
143
153
  const tickSize = shallowRef();
144
- const fits = computed(() => xAxis.value?.trimLabels && !horizontal.value && band.value !== void 0 ? Math.max(1, Math.floor(band.value / ((tickSize.value ?? 11) * .6))) : void 0);
154
+ const fits = computed(() => xAxis.value?.trimLabels && !horizontal.value && !continuous.value && band.value !== void 0 ? Math.max(1, Math.floor(band.value / ((tickSize.value ?? 11) * .6))) : void 0);
145
155
  const trim = (label, fits) => fits !== void 0 && label.length > fits ? `${label.slice(0, Math.max(1, fits - 1))}…` : label;
146
156
  const areaGradient = (key) => `area-${colorIndex(key)}`;
147
157
  const barGradient = (key) => `bar-${colorIndex(key)}`;
158
+ const at = (row, index) => continuous.value ? row[props.xKey] : index;
148
159
  const pointsOf = (key, fill) => props.data.map((row, index) => ({
149
160
  row,
150
161
  index,
162
+ at: at(row, index),
151
163
  category: key,
152
164
  value: plotted(row[key]),
153
165
  fill
@@ -159,6 +171,7 @@ var ChartRoot_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ define
159
171
  const data = props.data.flatMap((row, index) => bars.map((entry) => ({
160
172
  row,
161
173
  index,
174
+ at: index,
162
175
  category: entry.yKey,
163
176
  value: plotted(row[entry.yKey]),
164
177
  fill: entry.gradient ? `url(#${barGradient(entry.yKey)})` : void 0
@@ -176,11 +189,11 @@ var ChartRoot_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ define
176
189
  return horizontal.value ? barX(data, {
177
190
  ...options,
178
191
  x: "value",
179
- y: "index",
192
+ y: "at",
180
193
  xScale: valueId(first.scale)
181
194
  }) : barY(data, {
182
195
  ...options,
183
- x: "index",
196
+ x: "at",
184
197
  y: "value",
185
198
  yScale: valueId(first.scale)
186
199
  });
@@ -190,10 +203,10 @@ var ChartRoot_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ define
190
203
  const channels = {
191
204
  ...horizontal.value ? {
192
205
  x: "value",
193
- y: "index",
206
+ y: "at",
194
207
  xScale: valueId(entry.scale)
195
208
  } : {
196
- x: "index",
209
+ x: "at",
197
210
  y: "value",
198
211
  yScale: valueId(entry.scale)
199
212
  },
@@ -264,7 +277,10 @@ var ChartRoot_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ define
264
277
  const marks = [];
265
278
  let bars = false;
266
279
  for (const entry of shown.value) if (entry.kind !== "bar") marks.push(...seriesMarks(entry));
267
- else if (!bars) {
280
+ else if (continuous.value) {
281
+ if (process.env.NODE_ENV !== "production" && !bars) console.warn("[vunset] ChartBar needs `x-scale=\"category\"`: bars are left out on a continuous x axis.");
282
+ bars = true;
283
+ } else if (!bars) {
268
284
  marks.push(barMark());
269
285
  bars = true;
270
286
  }
@@ -278,15 +294,29 @@ var ChartRoot_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ define
278
294
  const budget = fits.value;
279
295
  const x = xAxis.value;
280
296
  const firstBar = series.value.find((entry) => entry.kind === "bar");
281
- const rowScale = {
297
+ const titled = (axis) => axis?.label ? { label: axis.label } : {};
298
+ const rowScale = continuous.value ? {
299
+ scale: props.xScale === "linear" ? scaleLinear : props.xScale,
300
+ grid: grids.value.some((entry) => entry.type === "x"),
301
+ axis: x ? {
302
+ line: false,
303
+ ...tickLabels(tickSize.value),
304
+ ...titled(x),
305
+ ticks: {
306
+ ...x.format && { format: x.format },
307
+ ...x.numTicks !== void 0 && { count: x.numTicks }
308
+ }
309
+ } : false
310
+ } : {
282
311
  scale: firstBar ? () => scaleBand().padding(firstBar.padding ?? .1) : () => scalePoint().padding(0),
283
312
  grid: grids.value.some((entry) => entry.type === "x"),
284
313
  axis: x ? {
285
314
  line: false,
286
315
  ...tickLabels(tickSize.value),
316
+ ...titled(x),
287
317
  ticks: {
288
318
  format: (index) => trim(xText(props.data[index]), budget),
289
- values: rowTicks(props.data.length, x.numTicks)
319
+ values: shown.value.length ? rowTicks(props.data.length, x.numTicks) : void 0
290
320
  }
291
321
  } : false
292
322
  };
@@ -303,6 +333,7 @@ var ChartRoot_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ define
303
333
  axis: axis && axisShown(scale) ? {
304
334
  line: false,
305
335
  ...tickLabels(tickSize.value),
336
+ ...titled(axis),
306
337
  ticks: {
307
338
  format: (raw) => valueLabel(raw, axis.format),
308
339
  ...axis.numTicks !== void 0 && { count: axis.numTicks }
@@ -311,6 +342,7 @@ var ChartRoot_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ define
311
342
  }];
312
343
  }));
313
344
  return defineChart({
345
+ svgAnimation: SVG_ANIMATION,
314
346
  marks,
315
347
  scales: {
316
348
  [rowId.value]: rowScale,
@@ -318,15 +350,16 @@ var ChartRoot_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ define
318
350
  },
319
351
  color: { scale: scaleOrdinal().domain(shownKeys.value).range(shownColors.value) },
320
352
  gradients: gradients(),
321
- ...rowFocus(horizontal.value),
322
- tooltip: tooltips.value.length > 0 && {
353
+ ...pointMode.value ? pointFocus : rowFocus(horizontal.value),
354
+ tooltip: tooltip$1.value && {
323
355
  use: tooltip,
356
+ sticky: tooltip$1.value.sticky,
324
357
  content: (points) => {
325
358
  const row = points[0]?.datum.row;
326
359
  if (!row) return { rows: [] };
327
360
  return {
328
- title: row[props.xKey] !== void 0 ? xText(row) : void 0,
329
- rows: tooltipSeries(row).map((entry) => ({
361
+ title: row[props.xKey] !== void 0 ? headingText(row) : void 0,
362
+ rows: tooltipSeries(row, points[0].datum.category).map((entry) => ({
330
363
  label: entry.label,
331
364
  value: entry.text,
332
365
  color: entry.color
@@ -348,7 +381,6 @@ var ChartRoot_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ define
348
381
  for (const label of svg.querySelectorAll(`text[data-ts-key^="${valueId(scale)}-tick-label"]`)) label.setAttribute("fill", color);
349
382
  }
350
383
  }
351
- const tooltip$1 = computed(() => tooltips.value[0]);
352
384
  const Render = (own) => own.render();
353
385
  return (_ctx, _cache) => {
354
386
  return openBlock(), createBlock(ChartFrame_default, mergeProps({
@@ -361,10 +393,10 @@ var ChartRoot_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ define
361
393
  "tick-size": tickSize.value,
362
394
  "onUpdate:tickSize": _cache[0] || (_cache[0] = ($event) => tickSize.value = $event)
363
395
  }, _ctx.$attrs, { onRender }), {
364
- tooltip: withCtx(({ point }) => [tooltip$1.value?.slot ? (openBlock(), createBlock(Render, {
396
+ tooltip: withCtx(({ point, pinned }) => [tooltip$1.value?.slot ? (openBlock(), createBlock(Render, {
365
397
  key: 0,
366
- render: () => tooltip$1.value.slot(tooltipProps(point.datum.row))
367
- }, null, 8, ["render"])) : (openBlock(), createElementBlock(Fragment, { key: 1 }, [point.datum.row[props.xKey] !== void 0 ? (openBlock(), createElementBlock("div", _hoisted_1, toDisplayString(xText(point.datum.row)), 1)) : createCommentVNode("", true), createElementVNode("div", _hoisted_2, [(openBlock(true), createElementBlock(Fragment, null, renderList(tooltipSeries(point.datum.row), (entry) => {
398
+ render: () => tooltip$1.value.slot(tooltipProps(point, pinned))
399
+ }, null, 8, ["render"])) : (openBlock(), createElementBlock(Fragment, { key: 1 }, [point.datum.row[props.xKey] !== void 0 ? (openBlock(), createElementBlock("div", _hoisted_1, toDisplayString(headingText(point.datum.row)), 1)) : createCommentVNode("", true), createElementVNode("div", _hoisted_2, [(openBlock(true), createElementBlock(Fragment, null, renderList(tooltipSeries(point.datum.row, point.datum.category), (entry) => {
368
400
  return openBlock(), createElementBlock("div", {
369
401
  key: entry.key,
370
402
  "data-part": "tooltip-row"
@@ -1,9 +1,20 @@
1
1
  import { type ChartTooltipSlotProps } from './ChartRoot.vue';
2
+ type __VLS_Props = {
3
+ /** `row`: every series at the row under the pointer. `point`: the one point nearest it. @default 'row' */
4
+ mode?: 'row' | 'point';
5
+ /** A click, Enter or Space pins it in place; another, or Escape, lets it go. @default true */
6
+ sticky?: boolean;
7
+ /** Formats the heading from the row's `x-key` value, apart from the x axis' ticks. Defaults to the x axis' `format`. */
8
+ headingFormat?: (value: any) => string;
9
+ };
2
10
  type __VLS_Slots = {
3
11
  /** Replaces the content: a heading, then a row per series. */
4
12
  default?(props: ChartTooltipSlotProps): unknown;
5
13
  };
6
- declare const __VLS_base: import("vue").DefineComponent<{}, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
14
+ declare const __VLS_base: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {
15
+ sticky: boolean;
16
+ mode: "row" | "point";
17
+ }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
7
18
  declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
8
19
  declare const _default: typeof __VLS_export;
9
20
  export default _default;
@@ -5,12 +5,24 @@ var ChartTooltip_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ def
5
5
  name: "ChartTooltip",
6
6
  inheritAttrs: false,
7
7
  __name: "ChartTooltip",
8
+ props: {
9
+ mode: { default: "row" },
10
+ sticky: {
11
+ type: Boolean,
12
+ default: true
13
+ },
14
+ headingFormat: {}
15
+ },
8
16
  setup(__props) {
17
+ const props = __props;
9
18
  const slots = useSlots();
10
19
  const attrs = useAttrs();
11
20
  useChartPart("tooltip", () => ({
12
21
  attrs,
13
- slot: slots.default
22
+ slot: slots.default,
23
+ mode: props.mode,
24
+ sticky: props.sticky,
25
+ headingFormat: props.headingFormat
14
26
  }));
15
27
  return (_ctx, _cache) => {
16
28
  return null;
@@ -6,6 +6,11 @@ import type { ChartCurve } from '@tanstack/charts';
6
6
  export declare function rowTicks(length: number, count: number | undefined): number[] | undefined;
7
7
  /** TanStack's own tick label size, in px, until `--chart-tick-font-size` sets one. */
8
8
  export declare const TICK_FONT_SIZE = 11;
9
+ /** Tweens a redraw for new data or a toggled series; off under reduced motion, never on resize. */
10
+ export declare const SVG_ANIMATION: {
11
+ readonly duration: 240;
12
+ readonly easing: "ease-out";
13
+ };
9
14
  /** An axis' tick labels at `size` px; TanStack lays the axis out for it. */
10
15
  export declare const tickLabels: (size: number | undefined) => {
11
16
  tickLabels?: undefined;
@@ -28,6 +33,11 @@ export declare const rowFocus: (horizontal?: boolean) => {
28
33
  focus: "group-y" | "group-x";
29
34
  maxFocusDistance: number;
30
35
  };
36
+ /** The one point nearest the pointer, however far. */
37
+ export declare const pointFocus: {
38
+ focus: "nearest";
39
+ maxFocusDistance: number;
40
+ };
31
41
  /** A chart's accessible name: what it plots, in the reader's words. */
32
42
  export declare const chartName: (categories: string[], labelFormat: ((category: string) => string) | undefined, fallback: string) => string;
33
43
  /** A line's run between its points: bending through them, or straight. TanStack draws straight by default. */
@@ -10,12 +10,19 @@ function rowTicks(length, count) {
10
10
  const step = Math.max(1, Math.ceil(length / Math.max(1, count)));
11
11
  return Array.from({ length }, (_, index) => index).filter((index) => index % step === 0);
12
12
  }
13
+ /** Tweens a redraw for new data or a toggled series; off under reduced motion, never on resize. */
14
+ var SVG_ANIMATION = {
15
+ duration: 240,
16
+ easing: "ease-out"
17
+ };
13
18
  /** An axis' tick labels at `size` px; TanStack lays the axis out for it. */
14
19
  var tickLabels = (size) => size === void 0 ? {} : { tickLabels: { fontSize: size } };
15
20
  /** A row's `xKey` value as its axis label. */
16
21
  function rowLabel(row, xKey, format) {
17
22
  const raw = row?.[xKey];
18
- return raw === void 0 ? "" : format ? format(raw) : String(raw);
23
+ if (raw === void 0) return "";
24
+ if (format) return format(raw);
25
+ return raw instanceof Date ? raw.toLocaleString() : String(raw);
19
26
  }
20
27
  /** A value axis label: the caller's formatter, or a grouped number. */
21
28
  var valueLabel = (raw, format) => format ? format(raw) : formatNumber(raw);
@@ -29,6 +36,11 @@ var rowFocus = (horizontal = false) => ({
29
36
  focus: horizontal ? "group-y" : "group-x",
30
37
  maxFocusDistance: Number.POSITIVE_INFINITY
31
38
  });
39
+ /** The one point nearest the pointer, however far. */
40
+ var pointFocus = {
41
+ focus: "nearest",
42
+ maxFocusDistance: Number.POSITIVE_INFINITY
43
+ };
32
44
  /** A chart's accessible name: what it plots, in the reader's words. */
33
45
  var chartName = (categories, labelFormat, fallback) => categories.map((category) => labelFormat ? labelFormat(category) : category).join(", ") || fallback;
34
46
  var SMOOTH = {
@@ -38,4 +50,4 @@ var SMOOTH = {
38
50
  /** A line's run between its points: bending through them, or straight. TanStack draws straight by default. */
39
51
  var curveOf = (curve) => curve === "smooth" ? SMOOTH : void 0;
40
52
  //#endregion
41
- export { chartName, curveOf, plotted, rowFocus, rowLabel, rowTicks, tickLabels, valueLabel };
53
+ export { SVG_ANIMATION, chartName, curveOf, plotted, pointFocus, rowFocus, rowLabel, rowTicks, tickLabels, valueLabel };
@@ -20,12 +20,19 @@ function useInput(options = {}) {
20
20
  });
21
21
  const invalid = () => (toValue(options.invalid) ?? false) || (field?.state.value.invalid ?? false) || outsideLimits();
22
22
  const normalize = (raw) => raw == null ? void 0 : String(raw);
23
+ const typed = ref();
23
24
  const value = useControlled({
24
- value: () => normalize(toValue(options.value)),
25
+ value: () => {
26
+ const raw = toValue(options.value);
27
+ return typeof raw === "number" && typed.value !== void 0 && parseFloat(typed.value) === raw ? typed.value : normalize(raw);
28
+ },
25
29
  defaultValue: () => normalize(toValue(options.defaultValue)) ?? "",
26
30
  prop: "modelValue",
27
31
  empty: "",
28
- onChange: options.onChange,
32
+ onChange: (next) => {
33
+ typed.value = next;
34
+ options.onChange?.(next);
35
+ },
29
36
  name: "useInput",
30
37
  label: "value"
31
38
  });
@@ -108,7 +115,15 @@ function useInput(options = {}) {
108
115
  const element = event.target;
109
116
  const inserted = insertedText(event);
110
117
  if (inserted === null) return;
111
- if (!accepts(prospectiveValue(element, inserted) ?? element.value + inserted)) event.preventDefault();
118
+ if (accepts(prospectiveValue(element, inserted) ?? element.value + inserted)) return;
119
+ event.preventDefault();
120
+ if (!numeric() || !/^insertFrom(Paste|Drop)/.test(event.inputType)) return;
121
+ const cleaned = inserted.replace(/[,\s]/g, "");
122
+ const start = element.selectionStart ?? element.value.length;
123
+ const end = element.selectionEnd ?? element.value.length;
124
+ if (!accepts(element.value.slice(0, start) + cleaned + element.value.slice(end))) return;
125
+ element.setRangeText(cleaned, start, end, "end");
126
+ handleInput({ target: element });
112
127
  },
113
128
  onInput(event) {
114
129
  if (composing) return;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vunset/vue",
3
- "version": "1.0.0-rc.2",
3
+ "version": "1.0.0-rc.4",
4
4
  "description": "Headless, unstyled Vue 3 components: keyboard, focus and ARIA handled, the look left to you.",
5
5
  "keywords": [
6
6
  "vue",
@@ -47,7 +47,7 @@
47
47
  },
48
48
  "dependencies": {
49
49
  "@floating-ui/vue": "^2.0.1",
50
- "@vunset/utils": "1.0.0-rc.2"
50
+ "@vunset/utils": "1.0.0-rc.4"
51
51
  },
52
52
  "peerDependencies": {
53
53
  "@tanstack/charts": "^1.1.0",