vectis-ui 0.16.0 → 0.17.0
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/dist/components/VColorPicker/color.js +2 -2
- package/dist/components/VColorPicker/color.js.map +1 -1
- package/dist/components/VCombobox/VCombobox.d.ts +10 -0
- package/dist/components/VCombobox/VCombobox.js.map +1 -1
- package/dist/components/VCombobox/VCombobox.vue_vue_type_script_setup_true_lang.js +177 -159
- package/dist/components/VCombobox/VCombobox.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VDataTable/VDataTable.css +1 -1
- package/dist/components/VDataTable/VDataTable.js.map +1 -1
- package/dist/components/VDataTable/VDataTable.vue_vue_type_script_setup_true_lang.js +1 -1
- package/dist/components/VDataTable/VDataTable.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VDatePicker/VDatePicker.css +1 -1
- package/dist/components/VDatePicker/VDatePicker.js.map +1 -1
- package/dist/components/VDatePicker/VDatePicker.vue_vue_type_script_setup_true_lang.js +3 -2
- package/dist/components/VDatePicker/VDatePicker.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VDialog/VDialog.css +1 -1
- package/dist/components/VDialog/VDialog.js.map +1 -1
- package/dist/components/VDialog/VDialog.vue_vue_type_script_setup_true_lang.js +4 -2
- package/dist/components/VDialog/VDialog.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VDrawer/VDrawer.css +1 -1
- package/dist/components/VDrawer/VDrawer.js.map +1 -1
- package/dist/components/VDrawer/VDrawer.vue_vue_type_script_setup_true_lang.js +4 -2
- package/dist/components/VDrawer/VDrawer.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VMeter/VMeter.css +1 -1
- package/dist/components/VMeter/VMeter.d.ts +24 -8
- package/dist/components/VMeter/VMeter.js.map +1 -1
- package/dist/components/VMeter/VMeter.vue_vue_type_script_setup_true_lang.js +65 -48
- package/dist/components/VMeter/VMeter.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VMeterGroup/VMeterGroup.css +2 -0
- package/dist/components/VMeterGroup/VMeterGroup.d.ts +101 -0
- package/dist/components/VMeterGroup/VMeterGroup.js +9 -0
- package/dist/components/VMeterGroup/VMeterGroup.js.map +1 -0
- package/dist/components/VMeterGroup/VMeterGroup.vue_vue_type_script_setup_true_lang.js +117 -0
- package/dist/components/VMeterGroup/VMeterGroup.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/components/VNumberInput/number.js +2 -2
- package/dist/components/VNumberInput/number.js.map +1 -1
- package/dist/components/VProgressCircular/VProgressCircular.d.ts +1 -1
- package/dist/components/VProgressLinear/VProgressLinear.css +1 -1
- package/dist/components/VProgressLinear/VProgressLinear.d.ts +12 -13
- package/dist/components/VProgressLinear/VProgressLinear.js.map +1 -1
- package/dist/components/VProgressLinear/VProgressLinear.vue_vue_type_script_setup_true_lang.js +65 -48
- package/dist/components/VProgressLinear/VProgressLinear.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VSeparator/VSeparator.css +1 -1
- package/dist/components/VSeparator/VSeparator.d.ts +42 -2
- package/dist/components/VSeparator/VSeparator.js.map +1 -1
- package/dist/components/VSeparator/VSeparator.vue_vue_type_script_setup_true_lang.js +50 -9
- package/dist/components/VSeparator/VSeparator.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VSideNavigation/VSideNavigation.d.ts +6 -0
- package/dist/components/VSideNavigation/VSideNavigation.js.map +1 -1
- package/dist/components/VSideNavigation/VSideNavigation.vue_vue_type_script_setup_true_lang.js +11 -2
- package/dist/components/VSideNavigation/VSideNavigation.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VSideNavigation/VSideNavigationItem.css +1 -1
- package/dist/components/VSideNavigation/VSideNavigationItem.js.map +1 -1
- package/dist/components/VSideNavigation/VSideNavigationItem.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VSnackbar/state.js +2 -3
- package/dist/components/VSnackbar/state.js.map +1 -1
- package/dist/components/VToast/state.js +2 -3
- package/dist/components/VToast/state.js.map +1 -1
- package/dist/composables/useAriaLabel.d.ts +5 -0
- package/dist/composables/useAriaLabel.js +5 -1
- package/dist/composables/useAriaLabel.js.map +1 -1
- package/dist/composables/useListbox.d.ts +2 -0
- package/dist/composables/useListbox.js +71 -68
- package/dist/composables/useListbox.js.map +1 -1
- package/dist/i18n/en.js +3 -1
- package/dist/i18n/en.js.map +1 -1
- package/dist/i18n/fr.js +3 -1
- package/dist/i18n/fr.js.map +1 -1
- package/dist/i18n/types.d.ts +6 -0
- package/dist/index.d.ts +5 -3
- package/dist/index.js +21 -20
- package/dist/styles.css +1 -1
- package/dist/tokens/index.d.ts +10 -5
- package/dist/tokens/semantic.d.ts +10 -5
- package/dist/tokens/semantic.js +10 -5
- package/dist/tokens/semantic.js.map +1 -1
- package/dist/tokens.json +39 -14
- package/package.json +3 -3
|
@@ -1,30 +1,39 @@
|
|
|
1
1
|
import { useLocale as e, useMessages as t } from "../../i18n/state.js";
|
|
2
2
|
import { customColorStyle as n } from "../../utils/css.js";
|
|
3
3
|
import { clamp as r } from "../../utils/number.js";
|
|
4
|
-
import { useAriaLabel as i } from "../../composables/useAriaLabel.js";
|
|
5
|
-
import { useRootAttrs as
|
|
6
|
-
import { Fragment as
|
|
4
|
+
import { useAriaLabel as i, useLabelledBy as a } from "../../composables/useAriaLabel.js";
|
|
5
|
+
import { useRootAttrs as o } from "../../composables/useRootAttrs.js";
|
|
6
|
+
import { Fragment as s, computed as c, createCommentVNode as l, createElementBlock as u, createElementVNode as d, defineComponent as f, mergeProps as p, normalizeClass as m, normalizeStyle as h, openBlock as g, renderList as _, toDisplayString as v, unref as y, useId as b } from "vue";
|
|
7
7
|
//#region src/components/VMeter/VMeter.vue?vue&type=script&setup=true&lang.ts
|
|
8
|
-
var
|
|
8
|
+
var x = [
|
|
9
9
|
"data-tone",
|
|
10
10
|
"data-custom",
|
|
11
11
|
"data-level",
|
|
12
|
-
"data-size"
|
|
13
|
-
|
|
12
|
+
"data-size",
|
|
13
|
+
"data-value-position",
|
|
14
|
+
"data-orientation"
|
|
15
|
+
], S = {
|
|
14
16
|
key: 0,
|
|
15
17
|
class: "v-meter-header"
|
|
16
|
-
},
|
|
18
|
+
}, C = ["id"], w = {
|
|
17
19
|
key: 1,
|
|
18
20
|
class: "v-meter-value",
|
|
19
21
|
"aria-hidden": "true"
|
|
20
|
-
},
|
|
22
|
+
}, T = ["id"], E = [
|
|
21
23
|
"aria-valuenow",
|
|
22
24
|
"aria-valuemin",
|
|
23
25
|
"aria-valuemax",
|
|
24
26
|
"aria-valuetext",
|
|
25
27
|
"aria-labelledby",
|
|
26
28
|
"aria-label"
|
|
27
|
-
],
|
|
29
|
+
], D = {
|
|
30
|
+
class: "v-meter-text",
|
|
31
|
+
"aria-hidden": "true"
|
|
32
|
+
}, O = {
|
|
33
|
+
class: "v-meter-text",
|
|
34
|
+
"data-on-fill": "",
|
|
35
|
+
"aria-hidden": "true"
|
|
36
|
+
}, k = /*@__PURE__*/ f({
|
|
28
37
|
inheritAttrs: !1,
|
|
29
38
|
__name: "VMeter",
|
|
30
39
|
props: {
|
|
@@ -41,73 +50,81 @@ var b = [
|
|
|
41
50
|
},
|
|
42
51
|
valueText: { default: void 0 },
|
|
43
52
|
formatOptions: { default: void 0 },
|
|
44
|
-
|
|
53
|
+
showValue: {
|
|
45
54
|
type: Boolean,
|
|
46
55
|
default: !1
|
|
47
56
|
},
|
|
57
|
+
valuePosition: { default: "header" },
|
|
48
58
|
tone: { default: void 0 },
|
|
49
59
|
color: { default: void 0 },
|
|
50
60
|
segments: { default: 1 },
|
|
51
|
-
size: { default: "md" }
|
|
61
|
+
size: { default: "md" },
|
|
62
|
+
orientation: { default: "horizontal" }
|
|
52
63
|
},
|
|
53
|
-
setup(
|
|
54
|
-
let
|
|
55
|
-
let e = Number.isFinite(
|
|
64
|
+
setup(f) {
|
|
65
|
+
let k = f, { rootClass: A, rootStyle: j, forwardedAttrs: M } = o(), N = t(), P = e(), F = b(), I = c(() => {
|
|
66
|
+
let e = Number.isFinite(k.min) ? k.min : 0, t = Number.isFinite(k.max) ? Math.max(k.max, e) : Math.max(100, e), n = (e, n, i) => e === void 0 || Number.isNaN(e) ? i : r(e, n, t), i = n(k.low, e, e);
|
|
56
67
|
return {
|
|
57
68
|
min: e,
|
|
58
69
|
max: t,
|
|
59
|
-
value: n(
|
|
70
|
+
value: n(k.value, e, e),
|
|
60
71
|
low: i,
|
|
61
|
-
high: n(
|
|
62
|
-
optimum: n(
|
|
72
|
+
high: n(k.high, i, t),
|
|
73
|
+
optimum: n(k.optimum, e, (e + t) / 2)
|
|
63
74
|
};
|
|
64
|
-
}),
|
|
65
|
-
let { min: e, max: t, value: n } =
|
|
75
|
+
}), L = c(() => {
|
|
76
|
+
let { min: e, max: t, value: n } = I.value;
|
|
66
77
|
return t > e ? (n - e) / (t - e) : 0;
|
|
67
|
-
}),
|
|
68
|
-
let { value: e, low: t, high: n, optimum: r } =
|
|
78
|
+
}), R = c(() => {
|
|
79
|
+
let { value: e, low: t, high: n, optimum: r } = I.value;
|
|
69
80
|
return r >= t && r <= n ? e >= t && e <= n ? "optimum" : "suboptimal" : r < t ? e < t ? "optimum" : e <= n ? "suboptimal" : "poor" : e > n ? "optimum" : e >= t ? "suboptimal" : "poor";
|
|
70
|
-
}),
|
|
81
|
+
}), z = {
|
|
71
82
|
optimum: "success",
|
|
72
83
|
suboptimal: "warning",
|
|
73
84
|
poor: "danger"
|
|
74
|
-
},
|
|
75
|
-
return (e, t) => (
|
|
76
|
-
class:
|
|
77
|
-
style:
|
|
78
|
-
"data-tone":
|
|
79
|
-
"data-custom":
|
|
80
|
-
"data-level":
|
|
81
|
-
"data-size":
|
|
85
|
+
}, B = c(() => k.tone ? k.tone : k.low !== void 0 || k.high !== void 0 || k.optimum !== void 0 ? z[R.value] : "accent"), V = c(() => k.valueText === void 0 ? k.formatOptions ? new Intl.NumberFormat(P.value, k.formatOptions).format(I.value.value) : N.value.progress.percent(Math.round(L.value * 100)) : k.valueText), H = c(() => Number.isFinite(k.segments) ? Math.max(1, Math.floor(k.segments)) : 1), U = (e) => r(L.value * H.value - e, 0, 1), W = c(() => k.showValue && k.valuePosition === "header"), G = c(() => k.showValue && k.valuePosition !== "header"), K = i(() => k.label ? void 0 : N.value.meter.label), q = a(F, () => k.label);
|
|
86
|
+
return (e, t) => (g(), u("div", {
|
|
87
|
+
class: m(["v-meter v-tone", y(A)]),
|
|
88
|
+
style: h([y(n)(f.color), y(j)]),
|
|
89
|
+
"data-tone": B.value,
|
|
90
|
+
"data-custom": f.color === void 0 ? void 0 : "",
|
|
91
|
+
"data-level": R.value,
|
|
92
|
+
"data-size": f.size,
|
|
93
|
+
"data-value-position": f.valuePosition,
|
|
94
|
+
"data-orientation": f.orientation
|
|
82
95
|
}, [
|
|
83
|
-
|
|
96
|
+
f.label && !f.hideLabel || W.value ? (g(), u("div", S, [f.label && !f.hideLabel ? (g(), u("span", {
|
|
84
97
|
key: 0,
|
|
85
|
-
id:
|
|
98
|
+
id: y(F),
|
|
86
99
|
class: "v-meter-label"
|
|
87
|
-
},
|
|
88
|
-
|
|
100
|
+
}, v(f.label), 9, C)) : l("", !0), W.value ? (g(), u("span", w, v(V.value), 1)) : l("", !0)])) : l("", !0),
|
|
101
|
+
f.label && f.hideLabel ? (g(), u("span", {
|
|
89
102
|
key: 1,
|
|
90
|
-
id:
|
|
103
|
+
id: y(F),
|
|
91
104
|
class: "v-visually-hidden"
|
|
92
|
-
},
|
|
93
|
-
|
|
105
|
+
}, v(f.label), 9, T)) : l("", !0),
|
|
106
|
+
d("div", p({
|
|
94
107
|
class: "v-meter-track",
|
|
95
108
|
role: "meter",
|
|
96
|
-
"aria-valuenow":
|
|
97
|
-
"aria-valuemin":
|
|
98
|
-
"aria-valuemax":
|
|
99
|
-
"aria-valuetext":
|
|
100
|
-
"aria-labelledby":
|
|
101
|
-
"aria-label":
|
|
102
|
-
},
|
|
109
|
+
"aria-valuenow": I.value.value,
|
|
110
|
+
"aria-valuemin": I.value.min,
|
|
111
|
+
"aria-valuemax": I.value.max,
|
|
112
|
+
"aria-valuetext": V.value,
|
|
113
|
+
"aria-labelledby": y(q),
|
|
114
|
+
"aria-label": y(K)
|
|
115
|
+
}, y(M), { style: {
|
|
116
|
+
"--meter-fraction": String(L.value),
|
|
117
|
+
"--meter-segments": String(H.value),
|
|
118
|
+
"--meter-gaps-filled": String(Math.floor(L.value * H.value))
|
|
119
|
+
} }), [(g(!0), u(s, null, _(H.value, (e) => (g(), u("span", {
|
|
103
120
|
key: e,
|
|
104
121
|
class: "v-meter-segment",
|
|
105
|
-
style:
|
|
106
|
-
}, [...t[0] ||= [
|
|
107
|
-
], 14,
|
|
122
|
+
style: h({ "--meter-segment-fill": String(U(e - 1)) })
|
|
123
|
+
}, [...t[0] ||= [d("span", { class: "v-meter-fill" }, null, -1)]], 4))), 128)), G.value ? (g(), u(s, { key: 0 }, [d("span", D, v(V.value), 1), d("span", O, v(V.value), 1)], 64)) : l("", !0)], 16, E)
|
|
124
|
+
], 14, x));
|
|
108
125
|
}
|
|
109
126
|
});
|
|
110
127
|
//#endregion
|
|
111
|
-
export {
|
|
128
|
+
export { k as default };
|
|
112
129
|
|
|
113
130
|
//# sourceMappingURL=VMeter.vue_vue_type_script_setup_true_lang.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"VMeter.vue_vue_type_script_setup_true_lang.js","names":["_openBlock","_createElementBlock","_normalizeClass","_unref","_normalizeStyle","_toDisplayString","_createCommentVNode","_createElementVNode","_mergeProps","_Fragment","_renderList","_hoisted_1","_hoisted_2","_hoisted_3","_hoisted_4","_hoisted_5","_hoisted_6","VMeter_vue_vue_type_script_setup_true_lang_default","props","__props","useRootAttrs","m","useMessages","locale","useLocale","labelId","useId","range","computed","min","max","fit","point","fallback","clamp","from","low","fraction","value","level","optimum","high","LEVEL_TONES","resolvedTone","text","segmentCount","segmentFill","index","ariaLabel","useAriaLabel","labelledBy","attrs","openBlock","createElementBlock","normalizeClass","unref","rootClass","normalizeStyle","customColorStyle","rootStyle","toDisplayString","createCommentVNode","createElementVNode","mergeProps","forwardedAttrs","Fragment","renderList","segment","_cache"],"sources":["../../../src/components/VMeter/VMeter.vue"],"sourcesContent":["<script setup lang=\"ts\">\n// @a11y\n/**\n * A measurement within a known range, drawn in CSS under the `meter` role. The native `<meter>`\n * is styled only through vendor pseudo-elements, which need one rule set per engine, does not\n * transition its fill in Firefox and cannot be split into segments; the role carries the same\n * semantics. As with the native element, `low`, `high` and `optimum` place the value in a\n * region, which picks the tone.\n */\nimport { computed, useId } from 'vue'\n\nimport { useAriaLabel } from '../../composables/useAriaLabel'\nimport { useRootAttrs } from '../../composables/useRootAttrs'\nimport { useLocale, useMessages } from '../../i18n/state'\nimport { customColorStyle } from '../../utils/css'\nimport { clamp } from '../../utils/number'\n\n/** The colour of the fill, which replaces the one chosen from `low`, `high` and `optimum`. */\nexport type MeterTone = 'accent' | 'neutral' | 'success' | 'warning' | 'danger'\n\n/** The thickness of the bar, 4, 8 or 12 pixels, and the size of the text above it. */\nexport type MeterSize = 'sm' | 'md' | 'lg'\n\ninterface MeterProps {\n /** The measurement. Anything outside the range is brought back into it. */\n value?: number\n /** The lower bound of the range. */\n min?: number\n /** The upper bound of the range. A bound below `min` is raised to it. */\n max?: number\n /**\n * The upper end of the low part of the range. With `high` and `optimum`, it decides whether\n * the value is good, average or poor, which colours the bar.\n */\n low?: number\n /** The lower end of the high part of the range. */\n high?: number\n /**\n * The best value. In the low part, low values are good; in the high part, high values are;\n * between `low` and `high`, the middle is good and both ends are average.\n */\n optimum?: number\n /**\n * What is measured, shown above the bar and naming it for screen readers. Without it, the bar\n * is named from the design system dictionary; an `aria-label` or `aria-labelledby` of your own\n * takes precedence.\n */\n label?: string\n /** Hides the label visually. It still names the bar for screen readers. */\n hideLabel?: boolean\n /**\n * The value as written above the bar and read by screen readers, such as \"7.2 GB of 10 GB\" or\n * \"Strong\". It replaces the formatted value.\n */\n valueText?: string\n /**\n * Formats the value for the current locale, such as a unit or a currency. Without it, the\n * value is written as a percentage of the range.\n */\n formatOptions?: Intl.NumberFormatOptions\n /** Hides the value visually. Screen readers still read it from the bar. */\n hideValue?: boolean\n /**\n * The colour of the fill. Without it, the region of the value picks one when `low`, `high` or\n * `optimum` is set: success, warning or danger; otherwise the accent.\n */\n tone?: MeterTone\n /**\n * A colour of your own (hex, CSS name or `oklch()`), which replaces the tone. The track is\n * derived from it against the theme.\n */\n color?: string\n /** Splits the bar into this many equal parts, for a strength or a battery level. */\n segments?: number\n /** The thickness of the bar and the size of the text above it. */\n size?: MeterSize\n}\n\n// The root only lays out the text and the bar: the consumer's ARIA and other attributes belong\n// on the element carrying the role.\ndefineOptions({ inheritAttrs: false })\n\nconst props = withDefaults(defineProps<MeterProps>(), {\n value: 0,\n min: 0,\n max: 100,\n low: undefined,\n high: undefined,\n optimum: undefined,\n label: undefined,\n hideLabel: false,\n valueText: undefined,\n formatOptions: undefined,\n hideValue: false,\n // Deliberately undefined: the region of the value then picks the tone.\n tone: undefined,\n color: undefined,\n segments: 1,\n size: 'md',\n})\n\nconst { attrs, rootClass, rootStyle, forwardedAttrs } = useRootAttrs()\nconst m = useMessages()\nconst locale = useLocale()\nconst labelId = useId()\n\n/* The normalization of the HTML `<meter>`: the bounds first, then each point brought into\n them, `high` never below `low`. A value that is not a number reads as the minimum. */\nconst range = computed(() => {\n const min = Number.isFinite(props.min) ? props.min : 0\n const max = Number.isFinite(props.max) ? Math.max(props.max, min) : Math.max(100, min)\n const fit = (point: number | undefined, from: number, fallback: number) =>\n point === undefined || Number.isNaN(point) ? fallback : clamp(point, from, max)\n const low = fit(props.low, min, min)\n return {\n min,\n max,\n value: fit(props.value, min, min),\n low,\n high: fit(props.high, low, max),\n optimum: fit(props.optimum, min, (min + max) / 2),\n }\n})\n\nconst fraction = computed(() => {\n const { min, max, value } = range.value\n return max > min ? (value - min) / (max - min) : 0\n})\n\n// @core\n// The regions of the HTML `<meter>`, with the boundaries Chromium draws: the part holding the\n// optimum is good, the part next to it average, and the far part, when the optimum sits beyond\n// `low` or `high`, poor.\nconst level = computed<'optimum' | 'suboptimal' | 'poor'>(() => {\n const { value, low, high, optimum } = range.value\n if (optimum >= low && optimum <= high)\n return value >= low && value <= high ? 'optimum' : 'suboptimal'\n if (optimum < low) return value < low ? 'optimum' : value <= high ? 'suboptimal' : 'poor'\n return value > high ? 'optimum' : value >= low ? 'suboptimal' : 'poor'\n})\n\nconst LEVEL_TONES = { optimum: 'success', suboptimal: 'warning', poor: 'danger' } as const\n\nconst resolvedTone = computed<MeterTone>(() => {\n if (props.tone) return props.tone\n const graded = props.low !== undefined || props.high !== undefined || props.optimum !== undefined\n return graded ? LEVEL_TONES[level.value] : 'accent'\n})\n\nconst text = computed(() => {\n if (props.valueText !== undefined) return props.valueText\n if (props.formatOptions)\n return new Intl.NumberFormat(locale.value, props.formatOptions).format(range.value.value)\n return m.value.progress.percent(Math.round(fraction.value * 100))\n})\n\nconst segmentCount = computed(() =>\n Number.isFinite(props.segments) ? Math.max(1, Math.floor(props.segments)) : 1,\n)\n\n/** How much of segment `index` is filled, from 0 to 1. */\nconst segmentFill = (index: number) => clamp(fraction.value * segmentCount.value - index, 0, 1)\n\n// @a11y\n// A visible label names the bar by reference, unless the consumer named it themselves; without\n// one, the dictionary does.\nconst ariaLabel = useAriaLabel(() => (props.label ? undefined : m.value.meter.label))\nconst labelledBy = computed(() =>\n props.label && attrs['aria-label'] == null && attrs['aria-labelledby'] == null\n ? labelId\n : undefined,\n)\n</script>\n\n<template>\n <div\n class=\"v-meter v-tone\"\n :class=\"rootClass\"\n :style=\"[customColorStyle(color), rootStyle]\"\n :data-tone=\"resolvedTone\"\n :data-custom=\"color !== undefined ? '' : undefined\"\n :data-level=\"level\"\n :data-size=\"size\"\n >\n <div v-if=\"(label && !hideLabel) || !hideValue\" class=\"v-meter-header\">\n <span v-if=\"label && !hideLabel\" :id=\"labelId\" class=\"v-meter-label\">{{ label }}</span>\n <!-- Read from the bar's `aria-valuetext`, so hidden here to avoid reading it twice. -->\n <span v-if=\"!hideValue\" class=\"v-meter-value\" aria-hidden=\"true\">{{ text }}</span>\n </div>\n <span v-if=\"label && hideLabel\" :id=\"labelId\" class=\"v-visually-hidden\">{{ label }}</span>\n <div\n class=\"v-meter-track\"\n role=\"meter\"\n :aria-valuenow=\"range.value\"\n :aria-valuemin=\"range.min\"\n :aria-valuemax=\"range.max\"\n :aria-valuetext=\"text\"\n :aria-labelledby=\"labelledBy\"\n :aria-label=\"ariaLabel\"\n v-bind=\"forwardedAttrs\"\n >\n <span\n v-for=\"segment in segmentCount\"\n :key=\"segment\"\n class=\"v-meter-segment\"\n :style=\"{ '--meter-segment-fill': String(segmentFill(segment - 1)) }\"\n >\n <span class=\"v-meter-fill\" />\n </span>\n </div>\n </div>\n</template>\n\n<style>\n@layer vectis.components {\n .v-meter {\n --meter-thickness: var(--vectis-control-size-meter-thickness-md);\n /*\n * Fill and track come from the shared tone table. The track takes the soft border rather than\n * the soft background: the empty part must stay visible, since the reading is a proportion.\n */\n --meter-fill: var(--tone-bg-solid);\n --meter-track: var(--tone-border-soft);\n display: flex;\n flex-direction: column;\n gap: var(--vectis-space-1);\n min-inline-size: 0;\n font-family: var(--vectis-text-family);\n }\n\n /* The custom-colour row of the tone table defines no soft border; the tone's would show. */\n .v-meter[data-custom] {\n --meter-track: color-mix(in oklab, var(--custom-color), var(--vectis-color-surface) 70%);\n }\n\n .v-meter[data-size='sm'] {\n --meter-thickness: var(--vectis-control-size-meter-thickness-sm);\n }\n\n .v-meter[data-size='lg'] {\n --meter-thickness: var(--vectis-control-size-meter-thickness-lg);\n }\n\n .v-meter-header {\n display: flex;\n flex-wrap: wrap;\n align-items: baseline;\n column-gap: var(--vectis-space-2);\n }\n\n .v-meter-label {\n color: var(--vectis-color-text);\n font-size: var(--vectis-text-label-size);\n font-weight: var(--vectis-text-label-weight);\n line-height: var(--vectis-text-label-leading);\n }\n\n /* Pushed to the end even without a label before it. */\n .v-meter-value {\n margin-inline-start: auto;\n color: var(--vectis-color-text-muted);\n font-size: var(--vectis-text-body-md-size);\n font-weight: var(--vectis-text-body-md-weight);\n line-height: var(--vectis-text-body-md-leading);\n font-variant-numeric: tabular-nums;\n }\n\n .v-meter[data-size='sm'] .v-meter-label,\n .v-meter[data-size='sm'] .v-meter-value {\n font-size: var(--vectis-text-caption-size);\n font-weight: var(--vectis-text-caption-weight);\n line-height: var(--vectis-text-caption-leading);\n }\n\n .v-meter-track {\n display: flex;\n gap: var(--vectis-control-size-meter-segment-gap);\n block-size: var(--meter-thickness);\n }\n\n .v-meter-segment {\n position: relative;\n flex: 1 1 0;\n border-radius: var(--vectis-radius-pill);\n background: var(--meter-track);\n }\n\n .v-meter-fill {\n position: absolute;\n inset-block: 0;\n inset-inline-start: 0;\n inline-size: calc(100% * var(--meter-segment-fill));\n border-radius: inherit;\n background: var(--meter-fill);\n /* A logical length rather than a scale, which would have to be undone in right to left. */\n transition:\n inline-size var(--vectis-duration-base) var(--vectis-ease-default),\n background-color var(--vectis-duration-base) var(--vectis-ease-default);\n }\n\n @media (prefers-reduced-motion: reduce) {\n .v-meter-fill {\n transition: none;\n }\n }\n\n @media (forced-colors: active) {\n .v-meter-segment {\n forced-color-adjust: none;\n background: Canvas;\n box-shadow: inset 0 0 0 var(--vectis-control-border-width) CanvasText;\n }\n\n .v-meter-fill {\n background: Highlight;\n }\n }\n}\n</style>\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"VMeter.vue_vue_type_script_setup_true_lang.js","names":["_openBlock","_createElementBlock","_normalizeClass","_unref","_normalizeStyle","_toDisplayString","_createCommentVNode","_createElementVNode","_mergeProps","_Fragment","_renderList","_hoisted_1","_hoisted_2","_hoisted_3","_hoisted_4","_hoisted_5","_hoisted_6","_hoisted_7","_hoisted_8","VMeter_vue_vue_type_script_setup_true_lang_default","props","__props","useRootAttrs","m","useMessages","locale","useLocale","labelId","useId","range","computed","min","max","fit","point","fallback","clamp","from","low","fraction","value","level","optimum","high","LEVEL_TONES","resolvedTone","text","segmentCount","segmentFill","index","valueInHeader","valueInBar","ariaLabel","useAriaLabel","labelledBy","useLabelledBy","openBlock","createElementBlock","normalizeClass","unref","rootClass","normalizeStyle","customColorStyle","rootStyle","toDisplayString","createCommentVNode","createElementVNode","mergeProps","forwardedAttrs","Fragment","renderList","segment","_cache"],"sources":["../../../src/components/VMeter/VMeter.vue"],"sourcesContent":["<script setup lang=\"ts\">\n// @a11y\n/**\n * A measurement within a known range, drawn in CSS under the `meter` role. The native `<meter>`\n * is styled only through vendor pseudo-elements, which need one rule set per engine, does not\n * transition its fill in Firefox and cannot be split into segments; the role carries the same\n * semantics. As with the native element, `low`, `high` and `optimum` place the value in a\n * region, which picks the tone.\n */\nimport { computed, useId } from 'vue'\n\nimport { useAriaLabel, useLabelledBy } from '../../composables/useAriaLabel'\nimport { useRootAttrs } from '../../composables/useRootAttrs'\nimport { useLocale, useMessages } from '../../i18n/state'\nimport { customColorStyle } from '../../utils/css'\nimport { clamp } from '../../utils/number'\n\n/** The colour of the fill, which replaces the one chosen from `low`, `high` and `optimum`. */\nexport type MeterTone = 'accent' | 'neutral' | 'success' | 'warning' | 'danger'\n\n/** The thickness of the bar: 2, 4, 8, 12 or 20 pixels. */\nexport type MeterSize = 'xs' | 'sm' | 'md' | 'lg' | 'xl'\n\n/** Where the value is written: after the label, or inside the bar at its start, centre or end. */\nexport type MeterValuePosition = 'header' | 'start' | 'center' | 'end'\n\n/** Which way the bar fills. */\nexport type MeterOrientation = 'horizontal' | 'vertical'\n\ninterface MeterProps {\n /** The measurement. Anything outside the range is brought back into it. */\n value?: number\n /** The lower bound of the range. */\n min?: number\n /** The upper bound of the range. A bound below `min` is raised to it. */\n max?: number\n /**\n * The upper end of the low part of the range. With `high` and `optimum`, it decides whether\n * the value is good, average or poor, which colours the bar.\n */\n low?: number\n /** The lower end of the high part of the range. */\n high?: number\n /**\n * The best value. In the low part, low values are good; in the high part, high values are;\n * between `low` and `high`, the middle is good and both ends are average.\n */\n optimum?: number\n /**\n * What is measured, shown above the bar and naming it for screen readers. Without it, the bar\n * is named from the design system dictionary; an `aria-label` or `aria-labelledby` of your own\n * takes precedence.\n */\n label?: string\n /** Hides the label visually. It still names the bar for screen readers. */\n hideLabel?: boolean\n /**\n * The value as written and read by screen readers, such as \"7.2 GB of 10 GB\" or \"Strong\". It\n * replaces the formatted value.\n */\n valueText?: string\n /**\n * Formats the value for the current locale, such as a unit or a currency. Without it, the\n * value is written as a percentage of the range.\n */\n formatOptions?: Intl.NumberFormatOptions\n /** Writes the value. Screen readers read it from the bar either way. */\n showValue?: boolean\n /**\n * Where that text is written: at the end of the label's line, or inside the bar. On a vertical\n * bar the start is the minimum end, hence the bottom.\n */\n valuePosition?: MeterValuePosition\n /**\n * The colour of the fill. Without it, the region of the value picks one when `low`, `high` or\n * `optimum` is set: success, warning or danger; otherwise the accent.\n */\n tone?: MeterTone\n /**\n * A colour of your own (hex, CSS name or `oklch()`), which replaces the tone. The track is\n * derived from it against the theme.\n */\n color?: string\n /** Splits the bar into this many equal parts, for a strength or a battery level. */\n segments?: number\n /** The thickness of the bar. Only `xl` is thick enough to hold the value written inside it. */\n size?: MeterSize\n /**\n * Turns the bar upright, filling from the bottom up. Its length is then a height set on the\n * component.\n */\n orientation?: MeterOrientation\n}\n\n// The root only lays out the text and the bar: the consumer's ARIA and other attributes belong\n// on the element carrying the role.\ndefineOptions({ inheritAttrs: false })\n\nconst props = withDefaults(defineProps<MeterProps>(), {\n value: 0,\n min: 0,\n max: 100,\n low: undefined,\n high: undefined,\n optimum: undefined,\n label: undefined,\n hideLabel: false,\n valueText: undefined,\n formatOptions: undefined,\n showValue: false,\n valuePosition: 'header',\n // Deliberately undefined: the region of the value then picks the tone.\n tone: undefined,\n color: undefined,\n segments: 1,\n size: 'md',\n orientation: 'horizontal',\n})\n\nconst { rootClass, rootStyle, forwardedAttrs } = useRootAttrs()\nconst m = useMessages()\nconst locale = useLocale()\nconst labelId = useId()\n\n/* The normalization of the HTML `<meter>`: the bounds first, then each point brought into\n them, `high` never below `low`. A value that is not a number reads as the minimum. */\nconst range = computed(() => {\n const min = Number.isFinite(props.min) ? props.min : 0\n const max = Number.isFinite(props.max) ? Math.max(props.max, min) : Math.max(100, min)\n const fit = (point: number | undefined, from: number, fallback: number) =>\n point === undefined || Number.isNaN(point) ? fallback : clamp(point, from, max)\n const low = fit(props.low, min, min)\n return {\n min,\n max,\n value: fit(props.value, min, min),\n low,\n high: fit(props.high, low, max),\n optimum: fit(props.optimum, min, (min + max) / 2),\n }\n})\n\nconst fraction = computed(() => {\n const { min, max, value } = range.value\n return max > min ? (value - min) / (max - min) : 0\n})\n\n// @core\n// The regions of the HTML `<meter>`, with the boundaries Chromium draws: the part holding the\n// optimum is good, the part next to it average, and the far part, when the optimum sits beyond\n// `low` or `high`, poor.\nconst level = computed<'optimum' | 'suboptimal' | 'poor'>(() => {\n const { value, low, high, optimum } = range.value\n if (optimum >= low && optimum <= high)\n return value >= low && value <= high ? 'optimum' : 'suboptimal'\n if (optimum < low) return value < low ? 'optimum' : value <= high ? 'suboptimal' : 'poor'\n return value > high ? 'optimum' : value >= low ? 'suboptimal' : 'poor'\n})\n\nconst LEVEL_TONES = { optimum: 'success', suboptimal: 'warning', poor: 'danger' } as const\n\nconst resolvedTone = computed<MeterTone>(() => {\n if (props.tone) return props.tone\n const graded = props.low !== undefined || props.high !== undefined || props.optimum !== undefined\n return graded ? LEVEL_TONES[level.value] : 'accent'\n})\n\nconst text = computed(() => {\n if (props.valueText !== undefined) return props.valueText\n if (props.formatOptions)\n return new Intl.NumberFormat(locale.value, props.formatOptions).format(range.value.value)\n return m.value.progress.percent(Math.round(fraction.value * 100))\n})\n\nconst segmentCount = computed(() =>\n Number.isFinite(props.segments) ? Math.max(1, Math.floor(props.segments)) : 1,\n)\n\n/** How much of segment `index` is filled, from 0 to 1. */\nconst segmentFill = (index: number) => clamp(fraction.value * segmentCount.value - index, 0, 1)\n\nconst valueInHeader = computed(() => props.showValue && props.valuePosition === 'header')\nconst valueInBar = computed(() => props.showValue && props.valuePosition !== 'header')\n\n// @a11y\n// A visible label names the bar by reference, unless the consumer named it themselves; without\n// one, the dictionary does.\nconst ariaLabel = useAriaLabel(() => (props.label ? undefined : m.value.meter.label))\nconst labelledBy = useLabelledBy(labelId, () => props.label)\n</script>\n\n<template>\n <div\n class=\"v-meter v-tone\"\n :class=\"rootClass\"\n :style=\"[customColorStyle(color), rootStyle]\"\n :data-tone=\"resolvedTone\"\n :data-custom=\"color !== undefined ? '' : undefined\"\n :data-level=\"level\"\n :data-size=\"size\"\n :data-value-position=\"valuePosition\"\n :data-orientation=\"orientation\"\n >\n <div v-if=\"(label && !hideLabel) || valueInHeader\" class=\"v-meter-header\">\n <span v-if=\"label && !hideLabel\" :id=\"labelId\" class=\"v-meter-label\">{{ label }}</span>\n <!-- Read from the bar's `aria-valuetext`, so hidden here to avoid reading it twice. -->\n <span v-if=\"valueInHeader\" class=\"v-meter-value\" aria-hidden=\"true\">{{ text }}</span>\n </div>\n <span v-if=\"label && hideLabel\" :id=\"labelId\" class=\"v-visually-hidden\">{{ label }}</span>\n <div\n class=\"v-meter-track\"\n role=\"meter\"\n :aria-valuenow=\"range.value\"\n :aria-valuemin=\"range.min\"\n :aria-valuemax=\"range.max\"\n :aria-valuetext=\"text\"\n :aria-labelledby=\"labelledBy\"\n :aria-label=\"ariaLabel\"\n v-bind=\"forwardedAttrs\"\n :style=\"{\n '--meter-fraction': String(fraction),\n '--meter-segments': String(segmentCount),\n '--meter-gaps-filled': String(Math.floor(fraction * segmentCount)),\n }\"\n >\n <span\n v-for=\"segment in segmentCount\"\n :key=\"segment\"\n class=\"v-meter-segment\"\n :style=\"{ '--meter-segment-fill': String(segmentFill(segment - 1)) }\"\n >\n <span class=\"v-meter-fill\" />\n </span>\n <!--\n The same text twice, as in VProgressLinear: each copy is cut at the fill's edge, the\n second coloured to contrast with the fill. Both are hidden, `aria-valuetext` carrying\n the text.\n -->\n <template v-if=\"valueInBar\">\n <span class=\"v-meter-text\" aria-hidden=\"true\">{{ text }}</span>\n <span class=\"v-meter-text\" data-on-fill aria-hidden=\"true\">{{ text }}</span>\n </template>\n </div>\n </div>\n</template>\n\n<style>\n@layer vectis.components {\n .v-meter {\n --meter-thickness: var(--vectis-control-size-bar-thickness-md);\n /* Fill, track and the text over the fill come from the shared tone table, as in\n VProgressLinear. */\n --meter-fill: var(--tone-bg-solid);\n --meter-track: var(--tone-bg-soft);\n --meter-text-fallback: var(--tone-text-solid);\n display: flex;\n flex-direction: column;\n gap: var(--vectis-space-1);\n min-inline-size: 0;\n font-family: var(--vectis-text-family);\n }\n\n .v-meter[data-size='xs'] {\n --meter-thickness: var(--vectis-control-size-bar-thickness-xs);\n }\n\n .v-meter[data-size='sm'] {\n --meter-thickness: var(--vectis-control-size-bar-thickness-sm);\n }\n\n .v-meter[data-size='lg'] {\n --meter-thickness: var(--vectis-control-size-bar-thickness-lg);\n }\n\n .v-meter[data-size='xl'] {\n --meter-thickness: var(--vectis-control-size-bar-thickness-xl);\n }\n\n .v-meter-header {\n display: flex;\n flex-wrap: wrap;\n align-items: baseline;\n column-gap: var(--vectis-space-2);\n }\n\n .v-meter-label {\n color: var(--vectis-color-text);\n font-size: var(--vectis-text-label-size);\n font-weight: var(--vectis-text-label-weight);\n line-height: var(--vectis-text-label-leading);\n }\n\n /* Pushed to the end even without a label before it. */\n .v-meter-value {\n margin-inline-start: auto;\n color: var(--vectis-color-text-muted);\n font-size: var(--vectis-text-body-md-size);\n font-weight: var(--vectis-text-body-md-weight);\n line-height: var(--vectis-text-body-md-leading);\n font-variant-numeric: tabular-nums;\n }\n\n /*\n * Upright, the root carries the length, so a consumer's height on it sets the bar's: the track\n * takes whatever the header leaves.\n */\n .v-meter[data-orientation='vertical'] {\n align-items: flex-start;\n block-size: var(--vectis-control-size-bar-length);\n }\n\n .v-meter-track {\n /*\n * Where the fill ends along the whole track, gaps included: the filled fraction of the\n * segments' total length, plus every gap the fill has crossed.\n */\n --meter-edge: calc(\n (100% - (var(--meter-segments) - 1) * var(--vectis-control-size-meter-segment-gap)) *\n var(--meter-fraction) + var(--meter-gaps-filled) *\n var(--vectis-control-size-meter-segment-gap)\n );\n position: relative;\n display: flex;\n gap: var(--vectis-control-size-meter-segment-gap);\n block-size: var(--meter-thickness);\n }\n\n .v-meter-segment {\n position: relative;\n flex: 1 1 0;\n border-radius: var(--vectis-radius-pill);\n background: var(--meter-track);\n }\n\n .v-meter-fill {\n position: absolute;\n inset-block: 0;\n inset-inline-start: 0;\n inline-size: calc(100% * var(--meter-segment-fill));\n border-radius: inherit;\n background: var(--meter-fill);\n /* A logical length rather than a scale, which would have to be undone in right to left. */\n transition:\n inline-size var(--vectis-duration-base) var(--vectis-ease-default),\n background-color var(--vectis-duration-base) var(--vectis-ease-default);\n }\n\n /*\n * The two copies are complementary, each cut to its own side of the fill's edge, so every\n * letter is painted once. The large negative insets keep text taller than the bar uncropped.\n */\n .v-meter-text {\n --meter-clip-start: var(--meter-edge);\n --meter-clip-end: -100vmax;\n position: absolute;\n inset: 0;\n display: flex;\n align-items: center;\n justify-content: center;\n padding-inline: var(--vectis-space-2);\n color: var(--vectis-color-text);\n font-size: var(--vectis-text-caption-size);\n font-weight: var(--vectis-text-control-weight);\n line-height: var(--vectis-text-control-leading);\n font-variant-numeric: tabular-nums;\n clip-path: inset(-100vmax var(--meter-clip-end) -100vmax var(--meter-clip-start));\n transition: clip-path var(--vectis-duration-base) var(--vectis-ease-default);\n white-space: nowrap;\n pointer-events: none;\n }\n\n .v-meter[data-value-position='start'] .v-meter-text {\n justify-content: start;\n }\n\n .v-meter[data-value-position='end'] .v-meter-text {\n justify-content: end;\n }\n\n /* The fallback stands alone for the reason given in VProgressLinear: a var() in a second\n declaration would win the cascade and then turn invalid. */\n .v-meter-text[data-on-fill] {\n --meter-clip-start: -100vmax;\n --meter-clip-end: calc(100% - var(--meter-edge));\n color: var(--meter-text-fallback);\n }\n\n @supports (color: contrast-color(red)) {\n .v-meter-text[data-on-fill] {\n color: contrast-color(var(--meter-fill));\n }\n }\n\n .v-meter-track:dir(rtl) .v-meter-text {\n clip-path: inset(-100vmax var(--meter-clip-start) -100vmax var(--meter-clip-end));\n }\n\n /*\n * As in VProgressLinear, the track turns on its side through its writing mode, so every inline\n * length and inset above follows. Zero goes to the bottom by anchoring the first segment and\n * each fill at the far end of the axis, never by reversing the direction, which would reorder\n * the text's characters.\n */\n .v-meter[data-orientation='vertical'] .v-meter-track {\n flex: 1 1 0;\n flex-direction: row-reverse;\n writing-mode: vertical-lr;\n }\n\n .v-meter[data-orientation='vertical'] .v-meter-fill {\n inset-inline-start: auto;\n inset-inline-end: 0;\n }\n\n /* A right-to-left page already runs a vertical inline axis from the bottom up. */\n .v-meter[data-orientation='vertical'] .v-meter-track:dir(rtl) {\n flex-direction: row;\n }\n\n .v-meter[data-orientation='vertical'] .v-meter-track:dir(rtl) .v-meter-fill {\n inset-inline-start: 0;\n inset-inline-end: auto;\n }\n\n .v-meter[data-orientation='vertical'] .v-meter-text {\n flex-direction: column-reverse;\n padding-inline: 0;\n padding-block: var(--vectis-space-2);\n /* The text stays upright on a bar turned on its side. */\n writing-mode: horizontal-tb;\n }\n\n /* The second selector outranks the right-to-left rule above instead of tying with it. */\n .v-meter[data-orientation='vertical'] .v-meter-text,\n .v-meter[data-orientation='vertical'] .v-meter-track:dir(rtl) .v-meter-text {\n clip-path: inset(var(--meter-clip-end) -100vmax var(--meter-clip-start) -100vmax);\n }\n\n @media (prefers-reduced-motion: reduce) {\n .v-meter-fill,\n .v-meter-text {\n transition: none;\n }\n }\n\n @media (forced-colors: active) {\n .v-meter-segment {\n forced-color-adjust: none;\n background: Canvas;\n box-shadow: inset 0 0 0 var(--vectis-control-border-width) CanvasText;\n }\n\n .v-meter-fill {\n background: Highlight;\n }\n\n .v-meter-text {\n forced-color-adjust: none;\n color: CanvasText;\n }\n\n .v-meter-text[data-on-fill] {\n color: HighlightText;\n }\n }\n}\n</style>\n"],"mappings":"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"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
@layer vectis.reset, vectis.tokens, vectis.components, vectis.utilities;
|
|
2
|
+
@layer vectis.components{.v-meter-group{--meter-group-thickness:var(--vectis-control-size-bar-thickness-md);gap:var(--vectis-space-1);min-inline-size:0;font-family:var(--vectis-text-family);flex-direction:column;display:flex}.v-meter-group[data-size=xs]{--meter-group-thickness:var(--vectis-control-size-bar-thickness-xs)}.v-meter-group[data-size=sm]{--meter-group-thickness:var(--vectis-control-size-bar-thickness-sm)}.v-meter-group[data-size=lg]{--meter-group-thickness:var(--vectis-control-size-bar-thickness-lg)}.v-meter-group[data-size=xl]{--meter-group-thickness:var(--vectis-control-size-bar-thickness-xl)}.v-meter-group-header{align-items:baseline;column-gap:var(--vectis-space-2);flex-wrap:wrap;display:flex}.v-meter-group-label{color:var(--vectis-color-text);font-size:var(--vectis-text-label-size);font-weight:var(--vectis-text-label-weight);line-height:var(--vectis-text-label-leading)}.v-meter-group-value{color:var(--vectis-color-text-muted);font-size:var(--vectis-text-body-md-size);font-weight:var(--vectis-text-body-md-weight);line-height:var(--vectis-text-body-md-leading);font-variant-numeric:tabular-nums;margin-inline-start:auto}.v-meter-group-body{gap:var(--vectis-space-2);flex-direction:column;display:flex}.v-meter-group-track{block-size:var(--meter-group-thickness);border-radius:var(--vectis-radius-pill);background:var(--tone-bg-soft);display:flex;overflow:hidden}.v-meter-group-part,.v-meter-group-rest{flex:var(--meter-group-share) 1 0;min-inline-size:0;transition:flex-grow var(--vectis-duration-base) var(--vectis-ease-default)}.v-meter-group-part,.v-meter-group-leading{--meter-group-colour:var(--tone-bg-solid)}.v-meter-group-part[data-derived],.v-meter-group-leading[data-derived]{--meter-group-colour:oklch(from var(--tone-bg-solid) l c calc(h + var(--meter-group-index) * 137.508))}.v-meter-group-part{background:var(--meter-group-colour)}.v-meter-group-legend{column-gap:var(--vectis-space-4);row-gap:var(--vectis-space-1);flex-wrap:wrap;margin:0;padding:0;list-style:none;display:flex}.v-meter-group-item{align-items:center;gap:var(--vectis-space-2);font-size:var(--vectis-text-body-sm-size);font-weight:var(--vectis-text-body-sm-weight);line-height:var(--vectis-text-body-sm-leading);display:inline-flex}.v-meter-group-leading{color:var(--meter-group-colour);display:inline-flex}.v-meter-group-swatch{inline-size:var(--vectis-control-size-meter-group-swatch);block-size:var(--vectis-control-size-meter-group-swatch);border-radius:var(--vectis-radius-pill);background:currentColor}.v-meter-group-item-label{color:var(--vectis-color-text)}.v-meter-group-item-value{color:var(--vectis-color-text-muted);font-variant-numeric:tabular-nums}.v-meter-group[data-orientation=vertical]{block-size:var(--vectis-control-size-bar-length)}.v-meter-group[data-orientation=vertical] .v-meter-group-body{gap:var(--vectis-space-3);flex-direction:row;flex:1 1 0;min-block-size:0}.v-meter-group[data-orientation=vertical] .v-meter-group-track{inline-size:var(--meter-group-thickness);flex-direction:column-reverse;flex-shrink:0;block-size:auto}.v-meter-group[data-orientation=vertical] .v-meter-group-part,.v-meter-group[data-orientation=vertical] .v-meter-group-rest{min-block-size:0;min-inline-size:auto}.v-meter-group[data-orientation=vertical] .v-meter-group-legend{flex-flow:column}@media (prefers-reduced-motion:reduce){.v-meter-group-part,.v-meter-group-rest{transition:none}}@media (forced-colors:active){.v-meter-group-track{gap:var(--vectis-control-size-meter-segment-gap);box-shadow:inset 0 0 0 var(--vectis-control-border-width) CanvasText}.v-meter-group-part{forced-color-adjust:none;background:highlight}}}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { IconSource } from '../VIcon/types.js';
|
|
2
|
+
/** The colour of a part. */
|
|
3
|
+
export type MeterGroupTone = 'accent' | 'neutral' | 'success' | 'warning' | 'danger';
|
|
4
|
+
/** The thickness of the bar: 2, 4, 8, 12 or 20 pixels. */
|
|
5
|
+
export type MeterGroupSize = 'xs' | 'sm' | 'md' | 'lg' | 'xl';
|
|
6
|
+
/** Which way the bar fills. */
|
|
7
|
+
export type MeterGroupOrientation = 'horizontal' | 'vertical';
|
|
8
|
+
/** One part of the bar and its row in the legend. */
|
|
9
|
+
export interface MeterGroupItem {
|
|
10
|
+
/** What the part is, written in the legend. */
|
|
11
|
+
label: string;
|
|
12
|
+
/** Its amount, on the scale of `max`. A negative amount or one that is not a number counts as zero. */
|
|
13
|
+
value: number;
|
|
14
|
+
/** Its colour. Without `tone` or `color`, a hue is derived from the part's position. */
|
|
15
|
+
tone?: MeterGroupTone;
|
|
16
|
+
/** A colour of your own (hex, CSS name or `oklch()`), which replaces the tone. */
|
|
17
|
+
color?: string;
|
|
18
|
+
/** An icon in the legend, in place of the colour swatch. */
|
|
19
|
+
icon?: IconSource;
|
|
20
|
+
}
|
|
21
|
+
/** What the item slots receive. */
|
|
22
|
+
export interface MeterGroupItemSlotProps {
|
|
23
|
+
item: MeterGroupItem;
|
|
24
|
+
index: number;
|
|
25
|
+
/** The part's amount as a percentage of `max`, unrounded. */
|
|
26
|
+
percent: number;
|
|
27
|
+
}
|
|
28
|
+
/** What the value slot receives. */
|
|
29
|
+
export interface MeterGroupValueSlotProps {
|
|
30
|
+
/** The sum of the amounts. */
|
|
31
|
+
value: number;
|
|
32
|
+
/** The bound. */
|
|
33
|
+
max: number;
|
|
34
|
+
/** The sum as a percentage of `max`, at most 100 and unrounded. */
|
|
35
|
+
percent: number;
|
|
36
|
+
}
|
|
37
|
+
interface MeterGroupProps {
|
|
38
|
+
/** The parts, in the order they fill the bar. */
|
|
39
|
+
items?: MeterGroupItem[];
|
|
40
|
+
/**
|
|
41
|
+
* The total the amounts are measured against. When they add up to more, the parts share the
|
|
42
|
+
* whole bar in proportion.
|
|
43
|
+
*/
|
|
44
|
+
max?: number;
|
|
45
|
+
/**
|
|
46
|
+
* What is measured, shown above the bar and naming the group for screen readers. Without it,
|
|
47
|
+
* the group is named from the design system dictionary; an `aria-label` or `aria-labelledby` of
|
|
48
|
+
* your own takes precedence.
|
|
49
|
+
*/
|
|
50
|
+
label?: string;
|
|
51
|
+
/** Hides the label visually. It still names the group for screen readers. */
|
|
52
|
+
hideLabel?: boolean;
|
|
53
|
+
/** Writes the sum of the amounts after the label. */
|
|
54
|
+
showValue?: boolean;
|
|
55
|
+
/** The sum as written, such as "96 GB of 128 GB". It replaces the formatted sum. */
|
|
56
|
+
valueText?: string;
|
|
57
|
+
/**
|
|
58
|
+
* Formats the sum and every amount for the current locale, such as a unit or a currency.
|
|
59
|
+
* Without it, they are written as percentages of `max`.
|
|
60
|
+
*/
|
|
61
|
+
formatOptions?: Intl.NumberFormatOptions;
|
|
62
|
+
/** Hides the legend visually. Screen readers still read it, the bar itself being silent. */
|
|
63
|
+
hideLegend?: boolean;
|
|
64
|
+
/** The thickness of the bar. */
|
|
65
|
+
size?: MeterGroupSize;
|
|
66
|
+
/**
|
|
67
|
+
* Turns the bar upright, filling from the bottom up, with the legend beside it. Its length is
|
|
68
|
+
* then a height set on the component.
|
|
69
|
+
*/
|
|
70
|
+
orientation?: MeterGroupOrientation;
|
|
71
|
+
}
|
|
72
|
+
type __VLS_Slots = {
|
|
73
|
+
/** What to write after the label instead of the formatted sum. */
|
|
74
|
+
value?(props: MeterGroupValueSlotProps): unknown;
|
|
75
|
+
/** What to put before a legend row's label instead of its swatch or icon. It is decorative and hidden from screen readers. */
|
|
76
|
+
'item-leading'?(props: MeterGroupItemSlotProps): unknown;
|
|
77
|
+
/** What to write instead of a legend row's label. */
|
|
78
|
+
'item-label'?(props: MeterGroupItemSlotProps): unknown;
|
|
79
|
+
/** What to write instead of a legend row's formatted amount. */
|
|
80
|
+
'item-value'?(props: MeterGroupItemSlotProps): unknown;
|
|
81
|
+
};
|
|
82
|
+
declare const __VLS_base: import('vue').DefineComponent<MeterGroupProps, {}, {}, {}, {}, import('vue').ComponentOptionsMixin, import('vue').ComponentOptionsMixin, {}, string, import('vue').PublicProps, Readonly<MeterGroupProps> & Readonly<{}>, {
|
|
83
|
+
label: string;
|
|
84
|
+
size: MeterGroupSize;
|
|
85
|
+
orientation: MeterGroupOrientation;
|
|
86
|
+
items: MeterGroupItem[];
|
|
87
|
+
hideLabel: boolean;
|
|
88
|
+
max: number;
|
|
89
|
+
formatOptions: Intl.NumberFormatOptions;
|
|
90
|
+
hideLegend: boolean;
|
|
91
|
+
valueText: string;
|
|
92
|
+
showValue: boolean;
|
|
93
|
+
}, {}, {}, {}, string, import('vue').ComponentProvideOptions, false, {}, any>;
|
|
94
|
+
declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
|
|
95
|
+
declare const _default: typeof __VLS_export;
|
|
96
|
+
export default _default;
|
|
97
|
+
type __VLS_WithSlots<T, S> = T & {
|
|
98
|
+
new (): {
|
|
99
|
+
$slots: S;
|
|
100
|
+
};
|
|
101
|
+
};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import './VMeterGroup.css';
|
|
2
|
+
import e from "./VMeterGroup.vue_vue_type_script_setup_true_lang.js";
|
|
3
|
+
/* empty css */
|
|
4
|
+
//#region src/components/VMeterGroup/VMeterGroup.vue
|
|
5
|
+
var t = e;
|
|
6
|
+
//#endregion
|
|
7
|
+
export { t as default };
|
|
8
|
+
|
|
9
|
+
//# sourceMappingURL=VMeterGroup.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"VMeterGroup.js","names":["_sfc_main","VMeterGroup_vue_vue_type_script_setup_true_lang_default","VMeterGroup_default"],"sources":["../../../src/components/VMeterGroup/VMeterGroup.vue"],"sourcesContent":["<script setup lang=\"ts\">\n// @a11y\n/**\n * Several measurements sharing one range, drawn as the parts of a single bar. ARIA has no meter\n * holding several values, so the bar is decorative and the legend, a list of labels and values in\n * text, carries the reading; the root is a group named by the label.\n */\nimport { computed, useId } from 'vue'\n\nimport { useAriaLabel, useLabelledBy } from '../../composables/useAriaLabel'\nimport { useLocale, useMessages } from '../../i18n/state'\nimport VIcon from '../VIcon/VIcon.vue'\nimport { iconProps } from '../VIcon/iconProps'\nimport type { IconSource } from '../VIcon/types'\n\n/** The colour of a part. */\nexport type MeterGroupTone = 'accent' | 'neutral' | 'success' | 'warning' | 'danger'\n\n/** The thickness of the bar: 2, 4, 8, 12 or 20 pixels. */\nexport type MeterGroupSize = 'xs' | 'sm' | 'md' | 'lg' | 'xl'\n\n/** Which way the bar fills. */\nexport type MeterGroupOrientation = 'horizontal' | 'vertical'\n\n/** One part of the bar and its row in the legend. */\nexport interface MeterGroupItem {\n /** What the part is, written in the legend. */\n label: string\n /** Its amount, on the scale of `max`. A negative amount or one that is not a number counts as zero. */\n value: number\n /** Its colour. Without `tone` or `color`, a hue is derived from the part's position. */\n tone?: MeterGroupTone\n /** A colour of your own (hex, CSS name or `oklch()`), which replaces the tone. */\n color?: string\n /** An icon in the legend, in place of the colour swatch. */\n icon?: IconSource\n}\n\n/** What the item slots receive. */\nexport interface MeterGroupItemSlotProps {\n item: MeterGroupItem\n index: number\n /** The part's amount as a percentage of `max`, unrounded. */\n percent: number\n}\n\n/** What the value slot receives. */\nexport interface MeterGroupValueSlotProps {\n /** The sum of the amounts. */\n value: number\n /** The bound. */\n max: number\n /** The sum as a percentage of `max`, at most 100 and unrounded. */\n percent: number\n}\n\ninterface MeterGroupProps {\n /** The parts, in the order they fill the bar. */\n items?: MeterGroupItem[]\n /**\n * The total the amounts are measured against. When they add up to more, the parts share the\n * whole bar in proportion.\n */\n max?: number\n /**\n * What is measured, shown above the bar and naming the group for screen readers. Without it,\n * the group is named from the design system dictionary; an `aria-label` or `aria-labelledby` of\n * your own takes precedence.\n */\n label?: string\n /** Hides the label visually. It still names the group for screen readers. */\n hideLabel?: boolean\n /** Writes the sum of the amounts after the label. */\n showValue?: boolean\n /** The sum as written, such as \"96 GB of 128 GB\". It replaces the formatted sum. */\n valueText?: string\n /**\n * Formats the sum and every amount for the current locale, such as a unit or a currency.\n * Without it, they are written as percentages of `max`.\n */\n formatOptions?: Intl.NumberFormatOptions\n /** Hides the legend visually. Screen readers still read it, the bar itself being silent. */\n hideLegend?: boolean\n /** The thickness of the bar. */\n size?: MeterGroupSize\n /**\n * Turns the bar upright, filling from the bottom up, with the legend beside it. Its length is\n * then a height set on the component.\n */\n orientation?: MeterGroupOrientation\n}\n\nconst props = withDefaults(defineProps<MeterGroupProps>(), {\n items: () => [],\n max: 100,\n label: undefined,\n hideLabel: false,\n showValue: false,\n valueText: undefined,\n formatOptions: undefined,\n hideLegend: false,\n size: 'md',\n orientation: 'horizontal',\n})\n\ndefineSlots<{\n /** What to write after the label instead of the formatted sum. */\n value?(props: MeterGroupValueSlotProps): unknown\n /** What to put before a legend row's label instead of its swatch or icon. It is decorative and hidden from screen readers. */\n 'item-leading'?(props: MeterGroupItemSlotProps): unknown\n /** What to write instead of a legend row's label. */\n 'item-label'?(props: MeterGroupItemSlotProps): unknown\n /** What to write instead of a legend row's formatted amount. */\n 'item-value'?(props: MeterGroupItemSlotProps): unknown\n}>()\n\nconst m = useMessages()\nconst locale = useLocale()\nconst labelId = useId()\n\nconst amounts = computed(() =>\n props.items.map(({ value }) => (Number.isFinite(value) ? Math.max(value, 0) : 0)),\n)\nconst bound = computed(() => (Number.isFinite(props.max) ? Math.max(props.max, 0) : 100))\nconst sum = computed(() => amounts.value.reduce((total, amount) => total + amount, 0))\n// The parts share the larger of the bound and the sum, so an overflow shrinks them all alike.\nconst scale = computed(() => Math.max(sum.value, bound.value) || 1)\n\nconst percentOf = (amount: number) => (bound.value > 0 ? (amount / bound.value) * 100 : 0)\nconst totalPercent = computed(() => Math.min(percentOf(sum.value), 100))\n\nconst format = (amount: number, percent: number) =>\n props.formatOptions\n ? new Intl.NumberFormat(locale.value, props.formatOptions).format(amount)\n : m.value.progress.percent(Math.round(percent))\n\nconst totalText = computed(() => props.valueText ?? format(sum.value, totalPercent.value))\nconst remainder = computed(() => 1 - sum.value / scale.value)\n\n/** The tone and colour attributes a part shares with its legend swatch. */\nconst colourOf = (item: MeterGroupItem, index: number) => ({\n 'data-tone': item.tone ?? 'accent',\n 'data-custom': item.color !== undefined ? '' : undefined,\n 'data-derived': item.tone === undefined && item.color === undefined ? '' : undefined,\n style: { '--custom-color': item.color, '--meter-group-index': String(index) },\n})\n\nconst ariaLabel = useAriaLabel(() => (props.label ? undefined : m.value.meterGroup.label))\nconst labelledBy = useLabelledBy(labelId, () => props.label)\n</script>\n\n<template>\n <div\n class=\"v-meter-group\"\n role=\"group\"\n :aria-labelledby=\"labelledBy\"\n :aria-label=\"ariaLabel\"\n :data-size=\"size\"\n :data-orientation=\"orientation\"\n >\n <div v-if=\"(label && !hideLabel) || showValue\" class=\"v-meter-group-header\">\n <span v-if=\"label && !hideLabel\" :id=\"labelId\" class=\"v-meter-group-label\">{{ label }}</span>\n <span v-if=\"showValue\" class=\"v-meter-group-value\">\n <slot name=\"value\" :value=\"sum\" :max=\"bound\" :percent=\"totalPercent\">{{ totalText }}</slot>\n </span>\n </div>\n <span v-if=\"label && hideLabel\" :id=\"labelId\" class=\"v-visually-hidden\">{{ label }}</span>\n <div class=\"v-meter-group-body\">\n <div class=\"v-meter-group-track v-tone\" data-tone=\"neutral\" aria-hidden=\"true\">\n <template v-for=\"(item, index) in items\" :key=\"index\">\n <span\n v-if=\"amounts[index]! > 0\"\n class=\"v-meter-group-part v-tone\"\n v-bind=\"colourOf(item, index)\"\n :style=\"{ '--meter-group-share': String(amounts[index]! / scale) }\"\n />\n </template>\n <span\n v-if=\"remainder > 0\"\n class=\"v-meter-group-rest\"\n :style=\"{ '--meter-group-share': String(remainder) }\"\n />\n </div>\n <ul class=\"v-meter-group-legend\" :class=\"{ 'v-visually-hidden': hideLegend }\">\n <li v-for=\"(item, index) in items\" :key=\"index\" class=\"v-meter-group-item\">\n <span\n class=\"v-meter-group-leading v-tone\"\n v-bind=\"colourOf(item, index)\"\n aria-hidden=\"true\"\n >\n <slot\n name=\"item-leading\"\n :item=\"item\"\n :index=\"index\"\n :percent=\"percentOf(amounts[index]!)\"\n >\n <VIcon v-if=\"item.icon\" v-bind=\"iconProps(item.icon)\" />\n <span v-else class=\"v-meter-group-swatch\" />\n </slot>\n </span>\n <span class=\"v-meter-group-item-label\">\n <slot\n name=\"item-label\"\n :item=\"item\"\n :index=\"index\"\n :percent=\"percentOf(amounts[index]!)\"\n >\n {{ item.label }}\n </slot>\n </span>\n <!-- Separates the label from the amount in what screen readers read. -->\n {{ ' ' }}\n <span class=\"v-meter-group-item-value\">\n <slot\n name=\"item-value\"\n :item=\"item\"\n :index=\"index\"\n :percent=\"percentOf(amounts[index]!)\"\n >\n {{ format(amounts[index]!, percentOf(amounts[index]!)) }}\n </slot>\n </span>\n </li>\n </ul>\n </div>\n </div>\n</template>\n\n<style>\n@layer vectis.components {\n .v-meter-group {\n --meter-group-thickness: var(--vectis-control-size-bar-thickness-md);\n display: flex;\n flex-direction: column;\n gap: var(--vectis-space-1);\n min-inline-size: 0;\n font-family: var(--vectis-text-family);\n }\n\n .v-meter-group[data-size='xs'] {\n --meter-group-thickness: var(--vectis-control-size-bar-thickness-xs);\n }\n\n .v-meter-group[data-size='sm'] {\n --meter-group-thickness: var(--vectis-control-size-bar-thickness-sm);\n }\n\n .v-meter-group[data-size='lg'] {\n --meter-group-thickness: var(--vectis-control-size-bar-thickness-lg);\n }\n\n .v-meter-group[data-size='xl'] {\n --meter-group-thickness: var(--vectis-control-size-bar-thickness-xl);\n }\n\n .v-meter-group-header {\n display: flex;\n flex-wrap: wrap;\n align-items: baseline;\n column-gap: var(--vectis-space-2);\n }\n\n .v-meter-group-label {\n color: var(--vectis-color-text);\n font-size: var(--vectis-text-label-size);\n font-weight: var(--vectis-text-label-weight);\n line-height: var(--vectis-text-label-leading);\n }\n\n /* Pushed to the end even without a label before it. */\n .v-meter-group-value {\n margin-inline-start: auto;\n color: var(--vectis-color-text-muted);\n font-size: var(--vectis-text-body-md-size);\n font-weight: var(--vectis-text-body-md-weight);\n line-height: var(--vectis-text-body-md-leading);\n font-variant-numeric: tabular-nums;\n }\n\n .v-meter-group-body {\n display: flex;\n flex-direction: column;\n gap: var(--vectis-space-2);\n }\n\n /* The track is the neutral tone's soft background; the parts paint their own tone over it. */\n .v-meter-group-track {\n display: flex;\n block-size: var(--meter-group-thickness);\n overflow: hidden;\n border-radius: var(--vectis-radius-pill);\n background: var(--tone-bg-soft);\n }\n\n /*\n * Grown from a zero basis in proportion to their share, the parts and the empty rest divide the\n * track, and whatever gaps forced colours add, so the parts stay in exact proportion.\n */\n .v-meter-group-part,\n .v-meter-group-rest {\n flex: var(--meter-group-share) 1 0;\n min-inline-size: 0;\n transition: flex-grow var(--vectis-duration-base) var(--vectis-ease-default);\n }\n\n /*\n * A part with no tone or colour of its own turns the accent's hue by the golden angle for each\n * position, which keeps neighbouring hues apart for any number of parts; the first is the accent\n * itself. Lightness and chroma stay the accent's, so the parts follow the theme.\n */\n .v-meter-group-part,\n .v-meter-group-leading {\n --meter-group-colour: var(--tone-bg-solid);\n }\n\n .v-meter-group-part[data-derived],\n .v-meter-group-leading[data-derived] {\n --meter-group-colour: oklch(\n from var(--tone-bg-solid) l c calc(h + var(--meter-group-index) * 137.508)\n );\n }\n\n .v-meter-group-part {\n background: var(--meter-group-colour);\n }\n\n .v-meter-group-legend {\n display: flex;\n flex-wrap: wrap;\n column-gap: var(--vectis-space-4);\n row-gap: var(--vectis-space-1);\n margin: 0;\n padding: 0;\n list-style: none;\n }\n\n .v-meter-group-item {\n display: inline-flex;\n align-items: center;\n gap: var(--vectis-space-2);\n font-size: var(--vectis-text-body-sm-size);\n font-weight: var(--vectis-text-body-sm-weight);\n line-height: var(--vectis-text-body-sm-leading);\n }\n\n .v-meter-group-leading {\n display: inline-flex;\n color: var(--meter-group-colour);\n }\n\n .v-meter-group-swatch {\n inline-size: var(--vectis-control-size-meter-group-swatch);\n block-size: var(--vectis-control-size-meter-group-swatch);\n border-radius: var(--vectis-radius-pill);\n background: currentcolor;\n }\n\n .v-meter-group-item-label {\n color: var(--vectis-color-text);\n }\n\n .v-meter-group-item-value {\n color: var(--vectis-color-text-muted);\n font-variant-numeric: tabular-nums;\n }\n\n /*\n * Upright, the root carries the length and the legend stands beside the bar. A column filled\n * from its end puts the first part at the bottom, in either reading direction.\n */\n .v-meter-group[data-orientation='vertical'] {\n block-size: var(--vectis-control-size-bar-length);\n }\n\n .v-meter-group[data-orientation='vertical'] .v-meter-group-body {\n flex: 1 1 0;\n flex-direction: row;\n gap: var(--vectis-space-3);\n min-block-size: 0;\n }\n\n .v-meter-group[data-orientation='vertical'] .v-meter-group-track {\n flex-direction: column-reverse;\n flex-shrink: 0;\n inline-size: var(--meter-group-thickness);\n block-size: auto;\n }\n\n .v-meter-group[data-orientation='vertical'] .v-meter-group-part,\n .v-meter-group[data-orientation='vertical'] .v-meter-group-rest {\n min-inline-size: auto;\n min-block-size: 0;\n }\n\n .v-meter-group[data-orientation='vertical'] .v-meter-group-legend {\n flex-direction: column;\n flex-wrap: nowrap;\n }\n\n @media (prefers-reduced-motion: reduce) {\n .v-meter-group-part,\n .v-meter-group-rest {\n transition: none;\n }\n }\n\n /* Forced colours flatten every part to one system colour: only gaps keep them apart there. */\n @media (forced-colors: active) {\n .v-meter-group-track {\n gap: var(--vectis-control-size-meter-segment-gap);\n box-shadow: inset 0 0 0 var(--vectis-control-border-width) CanvasText;\n }\n\n .v-meter-group-part {\n forced-color-adjust: none;\n background: Highlight;\n }\n }\n}\n</style>\n"],"mappings":""}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import { useLocale as e, useMessages as t } from "../../i18n/state.js";
|
|
2
|
+
import { iconProps as n } from "../VIcon/iconProps.js";
|
|
3
|
+
import r from "../VIcon/VIcon.js";
|
|
4
|
+
import { useAriaLabel as i, useLabelledBy as a } from "../../composables/useAriaLabel.js";
|
|
5
|
+
import { Fragment as o, computed as s, createBlock as c, createCommentVNode as l, createElementBlock as u, createElementVNode as d, createTextVNode as f, defineComponent as p, mergeProps as m, normalizeClass as h, normalizeStyle as g, openBlock as _, renderList as v, renderSlot as y, toDisplayString as b, unref as x, useId as S } from "vue";
|
|
6
|
+
//#region src/components/VMeterGroup/VMeterGroup.vue?vue&type=script&setup=true&lang.ts
|
|
7
|
+
var C = [
|
|
8
|
+
"aria-labelledby",
|
|
9
|
+
"aria-label",
|
|
10
|
+
"data-size",
|
|
11
|
+
"data-orientation"
|
|
12
|
+
], w = {
|
|
13
|
+
key: 0,
|
|
14
|
+
class: "v-meter-group-header"
|
|
15
|
+
}, T = ["id"], E = {
|
|
16
|
+
key: 1,
|
|
17
|
+
class: "v-meter-group-value"
|
|
18
|
+
}, D = ["id"], O = { class: "v-meter-group-body" }, k = {
|
|
19
|
+
class: "v-meter-group-track v-tone",
|
|
20
|
+
"data-tone": "neutral",
|
|
21
|
+
"aria-hidden": "true"
|
|
22
|
+
}, A = {
|
|
23
|
+
key: 1,
|
|
24
|
+
class: "v-meter-group-swatch"
|
|
25
|
+
}, j = { class: "v-meter-group-item-label" }, M = { class: "v-meter-group-item-value" }, N = /*@__PURE__*/ p({
|
|
26
|
+
__name: "VMeterGroup",
|
|
27
|
+
props: {
|
|
28
|
+
items: { default: () => [] },
|
|
29
|
+
max: { default: 100 },
|
|
30
|
+
label: { default: void 0 },
|
|
31
|
+
hideLabel: {
|
|
32
|
+
type: Boolean,
|
|
33
|
+
default: !1
|
|
34
|
+
},
|
|
35
|
+
showValue: {
|
|
36
|
+
type: Boolean,
|
|
37
|
+
default: !1
|
|
38
|
+
},
|
|
39
|
+
valueText: { default: void 0 },
|
|
40
|
+
formatOptions: { default: void 0 },
|
|
41
|
+
hideLegend: {
|
|
42
|
+
type: Boolean,
|
|
43
|
+
default: !1
|
|
44
|
+
},
|
|
45
|
+
size: { default: "md" },
|
|
46
|
+
orientation: { default: "horizontal" }
|
|
47
|
+
},
|
|
48
|
+
setup(p) {
|
|
49
|
+
let N = p, P = t(), F = e(), I = S(), L = s(() => N.items.map(({ value: e }) => Number.isFinite(e) ? Math.max(e, 0) : 0)), R = s(() => Number.isFinite(N.max) ? Math.max(N.max, 0) : 100), z = s(() => L.value.reduce((e, t) => e + t, 0)), B = s(() => Math.max(z.value, R.value) || 1), V = (e) => R.value > 0 ? e / R.value * 100 : 0, H = s(() => Math.min(V(z.value), 100)), U = (e, t) => N.formatOptions ? new Intl.NumberFormat(F.value, N.formatOptions).format(e) : P.value.progress.percent(Math.round(t)), W = s(() => N.valueText ?? U(z.value, H.value)), G = s(() => 1 - z.value / B.value), K = (e, t) => ({
|
|
50
|
+
"data-tone": e.tone ?? "accent",
|
|
51
|
+
"data-custom": e.color === void 0 ? void 0 : "",
|
|
52
|
+
"data-derived": e.tone === void 0 && e.color === void 0 ? "" : void 0,
|
|
53
|
+
style: {
|
|
54
|
+
"--custom-color": e.color,
|
|
55
|
+
"--meter-group-index": String(t)
|
|
56
|
+
}
|
|
57
|
+
}), q = i(() => N.label ? void 0 : P.value.meterGroup.label), J = a(I, () => N.label);
|
|
58
|
+
return (e, t) => (_(), u("div", {
|
|
59
|
+
class: "v-meter-group",
|
|
60
|
+
role: "group",
|
|
61
|
+
"aria-labelledby": x(J),
|
|
62
|
+
"aria-label": x(q),
|
|
63
|
+
"data-size": p.size,
|
|
64
|
+
"data-orientation": p.orientation
|
|
65
|
+
}, [
|
|
66
|
+
p.label && !p.hideLabel || p.showValue ? (_(), u("div", w, [p.label && !p.hideLabel ? (_(), u("span", {
|
|
67
|
+
key: 0,
|
|
68
|
+
id: x(I),
|
|
69
|
+
class: "v-meter-group-label"
|
|
70
|
+
}, b(p.label), 9, T)) : l("", !0), p.showValue ? (_(), u("span", E, [y(e.$slots, "value", {
|
|
71
|
+
value: z.value,
|
|
72
|
+
max: R.value,
|
|
73
|
+
percent: H.value
|
|
74
|
+
}, () => [f(b(W.value), 1)])])) : l("", !0)])) : l("", !0),
|
|
75
|
+
p.label && p.hideLabel ? (_(), u("span", {
|
|
76
|
+
key: 1,
|
|
77
|
+
id: x(I),
|
|
78
|
+
class: "v-visually-hidden"
|
|
79
|
+
}, b(p.label), 9, D)) : l("", !0),
|
|
80
|
+
d("div", O, [d("div", k, [(_(!0), u(o, null, v(p.items, (e, t) => (_(), u(o, { key: t }, [L.value[t] > 0 ? (_(), u("span", m({
|
|
81
|
+
key: 0,
|
|
82
|
+
class: "v-meter-group-part v-tone"
|
|
83
|
+
}, { ref_for: !0 }, K(e, t), { style: { "--meter-group-share": String(L.value[t] / B.value) } }), null, 16)) : l("", !0)], 64))), 128)), G.value > 0 ? (_(), u("span", {
|
|
84
|
+
key: 0,
|
|
85
|
+
class: "v-meter-group-rest",
|
|
86
|
+
style: g({ "--meter-group-share": String(G.value) })
|
|
87
|
+
}, null, 4)) : l("", !0)]), d("ul", { class: h(["v-meter-group-legend", { "v-visually-hidden": p.hideLegend }]) }, [(_(!0), u(o, null, v(p.items, (i, a) => (_(), u("li", {
|
|
88
|
+
key: a,
|
|
89
|
+
class: "v-meter-group-item"
|
|
90
|
+
}, [
|
|
91
|
+
d("span", m({ class: "v-meter-group-leading v-tone" }, { ref_for: !0 }, K(i, a), { "aria-hidden": "true" }), [y(e.$slots, "item-leading", {
|
|
92
|
+
item: i,
|
|
93
|
+
index: a,
|
|
94
|
+
percent: V(L.value[a])
|
|
95
|
+
}, () => [i.icon ? (_(), c(r, m({
|
|
96
|
+
key: 0,
|
|
97
|
+
ref_for: !0
|
|
98
|
+
}, x(n)(i.icon)), null, 16)) : (_(), u("span", A))])], 16),
|
|
99
|
+
d("span", j, [y(e.$slots, "item-label", {
|
|
100
|
+
item: i,
|
|
101
|
+
index: a,
|
|
102
|
+
percent: V(L.value[a])
|
|
103
|
+
}, () => [f(b(i.label), 1)])]),
|
|
104
|
+
t[0] ||= f(" " + b(" ") + " ", -1),
|
|
105
|
+
d("span", M, [y(e.$slots, "item-value", {
|
|
106
|
+
item: i,
|
|
107
|
+
index: a,
|
|
108
|
+
percent: V(L.value[a])
|
|
109
|
+
}, () => [f(b(U(L.value[a], V(L.value[a]))), 1)])])
|
|
110
|
+
]))), 128))], 2)])
|
|
111
|
+
], 8, C));
|
|
112
|
+
}
|
|
113
|
+
});
|
|
114
|
+
//#endregion
|
|
115
|
+
export { N as default };
|
|
116
|
+
|
|
117
|
+
//# sourceMappingURL=VMeterGroup.vue_vue_type_script_setup_true_lang.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"VMeterGroup.vue_vue_type_script_setup_true_lang.js","names":["_openBlock","_createElementBlock","_unref","_toDisplayString","_createCommentVNode","_renderSlot","_createTextVNode","_createElementVNode","_Fragment","_renderList","_mergeProps","_normalizeStyle","_normalizeClass","_createBlock","VIcon","VIcon_default","_hoisted_1","_hoisted_2","_hoisted_3","_hoisted_4","_hoisted_5","_hoisted_6","_hoisted_7","_hoisted_8","_hoisted_9","_hoisted_10","VMeterGroup_vue_vue_type_script_setup_true_lang_default","props","__props","m","useMessages","locale","useLocale","labelId","useId","amounts","computed","value","bound","sum","total","amount","scale","percentOf","totalPercent","format","percent","totalText","remainder","colourOf","item","index","ariaLabel","useAriaLabel","labelledBy","useLabelledBy","openBlock","createElementBlock","unref","toDisplayString","createCommentVNode","renderSlot","_ctx","createTextVNode","createElementVNode","Fragment","renderList","mergeProps","normalizeStyle","normalizeClass","createBlock","iconProps","_cache"],"sources":["../../../src/components/VMeterGroup/VMeterGroup.vue"],"sourcesContent":["<script setup lang=\"ts\">\n// @a11y\n/**\n * Several measurements sharing one range, drawn as the parts of a single bar. ARIA has no meter\n * holding several values, so the bar is decorative and the legend, a list of labels and values in\n * text, carries the reading; the root is a group named by the label.\n */\nimport { computed, useId } from 'vue'\n\nimport { useAriaLabel, useLabelledBy } from '../../composables/useAriaLabel'\nimport { useLocale, useMessages } from '../../i18n/state'\nimport VIcon from '../VIcon/VIcon.vue'\nimport { iconProps } from '../VIcon/iconProps'\nimport type { IconSource } from '../VIcon/types'\n\n/** The colour of a part. */\nexport type MeterGroupTone = 'accent' | 'neutral' | 'success' | 'warning' | 'danger'\n\n/** The thickness of the bar: 2, 4, 8, 12 or 20 pixels. */\nexport type MeterGroupSize = 'xs' | 'sm' | 'md' | 'lg' | 'xl'\n\n/** Which way the bar fills. */\nexport type MeterGroupOrientation = 'horizontal' | 'vertical'\n\n/** One part of the bar and its row in the legend. */\nexport interface MeterGroupItem {\n /** What the part is, written in the legend. */\n label: string\n /** Its amount, on the scale of `max`. A negative amount or one that is not a number counts as zero. */\n value: number\n /** Its colour. Without `tone` or `color`, a hue is derived from the part's position. */\n tone?: MeterGroupTone\n /** A colour of your own (hex, CSS name or `oklch()`), which replaces the tone. */\n color?: string\n /** An icon in the legend, in place of the colour swatch. */\n icon?: IconSource\n}\n\n/** What the item slots receive. */\nexport interface MeterGroupItemSlotProps {\n item: MeterGroupItem\n index: number\n /** The part's amount as a percentage of `max`, unrounded. */\n percent: number\n}\n\n/** What the value slot receives. */\nexport interface MeterGroupValueSlotProps {\n /** The sum of the amounts. */\n value: number\n /** The bound. */\n max: number\n /** The sum as a percentage of `max`, at most 100 and unrounded. */\n percent: number\n}\n\ninterface MeterGroupProps {\n /** The parts, in the order they fill the bar. */\n items?: MeterGroupItem[]\n /**\n * The total the amounts are measured against. When they add up to more, the parts share the\n * whole bar in proportion.\n */\n max?: number\n /**\n * What is measured, shown above the bar and naming the group for screen readers. Without it,\n * the group is named from the design system dictionary; an `aria-label` or `aria-labelledby` of\n * your own takes precedence.\n */\n label?: string\n /** Hides the label visually. It still names the group for screen readers. */\n hideLabel?: boolean\n /** Writes the sum of the amounts after the label. */\n showValue?: boolean\n /** The sum as written, such as \"96 GB of 128 GB\". It replaces the formatted sum. */\n valueText?: string\n /**\n * Formats the sum and every amount for the current locale, such as a unit or a currency.\n * Without it, they are written as percentages of `max`.\n */\n formatOptions?: Intl.NumberFormatOptions\n /** Hides the legend visually. Screen readers still read it, the bar itself being silent. */\n hideLegend?: boolean\n /** The thickness of the bar. */\n size?: MeterGroupSize\n /**\n * Turns the bar upright, filling from the bottom up, with the legend beside it. Its length is\n * then a height set on the component.\n */\n orientation?: MeterGroupOrientation\n}\n\nconst props = withDefaults(defineProps<MeterGroupProps>(), {\n items: () => [],\n max: 100,\n label: undefined,\n hideLabel: false,\n showValue: false,\n valueText: undefined,\n formatOptions: undefined,\n hideLegend: false,\n size: 'md',\n orientation: 'horizontal',\n})\n\ndefineSlots<{\n /** What to write after the label instead of the formatted sum. */\n value?(props: MeterGroupValueSlotProps): unknown\n /** What to put before a legend row's label instead of its swatch or icon. It is decorative and hidden from screen readers. */\n 'item-leading'?(props: MeterGroupItemSlotProps): unknown\n /** What to write instead of a legend row's label. */\n 'item-label'?(props: MeterGroupItemSlotProps): unknown\n /** What to write instead of a legend row's formatted amount. */\n 'item-value'?(props: MeterGroupItemSlotProps): unknown\n}>()\n\nconst m = useMessages()\nconst locale = useLocale()\nconst labelId = useId()\n\nconst amounts = computed(() =>\n props.items.map(({ value }) => (Number.isFinite(value) ? Math.max(value, 0) : 0)),\n)\nconst bound = computed(() => (Number.isFinite(props.max) ? Math.max(props.max, 0) : 100))\nconst sum = computed(() => amounts.value.reduce((total, amount) => total + amount, 0))\n// The parts share the larger of the bound and the sum, so an overflow shrinks them all alike.\nconst scale = computed(() => Math.max(sum.value, bound.value) || 1)\n\nconst percentOf = (amount: number) => (bound.value > 0 ? (amount / bound.value) * 100 : 0)\nconst totalPercent = computed(() => Math.min(percentOf(sum.value), 100))\n\nconst format = (amount: number, percent: number) =>\n props.formatOptions\n ? new Intl.NumberFormat(locale.value, props.formatOptions).format(amount)\n : m.value.progress.percent(Math.round(percent))\n\nconst totalText = computed(() => props.valueText ?? format(sum.value, totalPercent.value))\nconst remainder = computed(() => 1 - sum.value / scale.value)\n\n/** The tone and colour attributes a part shares with its legend swatch. */\nconst colourOf = (item: MeterGroupItem, index: number) => ({\n 'data-tone': item.tone ?? 'accent',\n 'data-custom': item.color !== undefined ? '' : undefined,\n 'data-derived': item.tone === undefined && item.color === undefined ? '' : undefined,\n style: { '--custom-color': item.color, '--meter-group-index': String(index) },\n})\n\nconst ariaLabel = useAriaLabel(() => (props.label ? undefined : m.value.meterGroup.label))\nconst labelledBy = useLabelledBy(labelId, () => props.label)\n</script>\n\n<template>\n <div\n class=\"v-meter-group\"\n role=\"group\"\n :aria-labelledby=\"labelledBy\"\n :aria-label=\"ariaLabel\"\n :data-size=\"size\"\n :data-orientation=\"orientation\"\n >\n <div v-if=\"(label && !hideLabel) || showValue\" class=\"v-meter-group-header\">\n <span v-if=\"label && !hideLabel\" :id=\"labelId\" class=\"v-meter-group-label\">{{ label }}</span>\n <span v-if=\"showValue\" class=\"v-meter-group-value\">\n <slot name=\"value\" :value=\"sum\" :max=\"bound\" :percent=\"totalPercent\">{{ totalText }}</slot>\n </span>\n </div>\n <span v-if=\"label && hideLabel\" :id=\"labelId\" class=\"v-visually-hidden\">{{ label }}</span>\n <div class=\"v-meter-group-body\">\n <div class=\"v-meter-group-track v-tone\" data-tone=\"neutral\" aria-hidden=\"true\">\n <template v-for=\"(item, index) in items\" :key=\"index\">\n <span\n v-if=\"amounts[index]! > 0\"\n class=\"v-meter-group-part v-tone\"\n v-bind=\"colourOf(item, index)\"\n :style=\"{ '--meter-group-share': String(amounts[index]! / scale) }\"\n />\n </template>\n <span\n v-if=\"remainder > 0\"\n class=\"v-meter-group-rest\"\n :style=\"{ '--meter-group-share': String(remainder) }\"\n />\n </div>\n <ul class=\"v-meter-group-legend\" :class=\"{ 'v-visually-hidden': hideLegend }\">\n <li v-for=\"(item, index) in items\" :key=\"index\" class=\"v-meter-group-item\">\n <span\n class=\"v-meter-group-leading v-tone\"\n v-bind=\"colourOf(item, index)\"\n aria-hidden=\"true\"\n >\n <slot\n name=\"item-leading\"\n :item=\"item\"\n :index=\"index\"\n :percent=\"percentOf(amounts[index]!)\"\n >\n <VIcon v-if=\"item.icon\" v-bind=\"iconProps(item.icon)\" />\n <span v-else class=\"v-meter-group-swatch\" />\n </slot>\n </span>\n <span class=\"v-meter-group-item-label\">\n <slot\n name=\"item-label\"\n :item=\"item\"\n :index=\"index\"\n :percent=\"percentOf(amounts[index]!)\"\n >\n {{ item.label }}\n </slot>\n </span>\n <!-- Separates the label from the amount in what screen readers read. -->\n {{ ' ' }}\n <span class=\"v-meter-group-item-value\">\n <slot\n name=\"item-value\"\n :item=\"item\"\n :index=\"index\"\n :percent=\"percentOf(amounts[index]!)\"\n >\n {{ format(amounts[index]!, percentOf(amounts[index]!)) }}\n </slot>\n </span>\n </li>\n </ul>\n </div>\n </div>\n</template>\n\n<style>\n@layer vectis.components {\n .v-meter-group {\n --meter-group-thickness: var(--vectis-control-size-bar-thickness-md);\n display: flex;\n flex-direction: column;\n gap: var(--vectis-space-1);\n min-inline-size: 0;\n font-family: var(--vectis-text-family);\n }\n\n .v-meter-group[data-size='xs'] {\n --meter-group-thickness: var(--vectis-control-size-bar-thickness-xs);\n }\n\n .v-meter-group[data-size='sm'] {\n --meter-group-thickness: var(--vectis-control-size-bar-thickness-sm);\n }\n\n .v-meter-group[data-size='lg'] {\n --meter-group-thickness: var(--vectis-control-size-bar-thickness-lg);\n }\n\n .v-meter-group[data-size='xl'] {\n --meter-group-thickness: var(--vectis-control-size-bar-thickness-xl);\n }\n\n .v-meter-group-header {\n display: flex;\n flex-wrap: wrap;\n align-items: baseline;\n column-gap: var(--vectis-space-2);\n }\n\n .v-meter-group-label {\n color: var(--vectis-color-text);\n font-size: var(--vectis-text-label-size);\n font-weight: var(--vectis-text-label-weight);\n line-height: var(--vectis-text-label-leading);\n }\n\n /* Pushed to the end even without a label before it. */\n .v-meter-group-value {\n margin-inline-start: auto;\n color: var(--vectis-color-text-muted);\n font-size: var(--vectis-text-body-md-size);\n font-weight: var(--vectis-text-body-md-weight);\n line-height: var(--vectis-text-body-md-leading);\n font-variant-numeric: tabular-nums;\n }\n\n .v-meter-group-body {\n display: flex;\n flex-direction: column;\n gap: var(--vectis-space-2);\n }\n\n /* The track is the neutral tone's soft background; the parts paint their own tone over it. */\n .v-meter-group-track {\n display: flex;\n block-size: var(--meter-group-thickness);\n overflow: hidden;\n border-radius: var(--vectis-radius-pill);\n background: var(--tone-bg-soft);\n }\n\n /*\n * Grown from a zero basis in proportion to their share, the parts and the empty rest divide the\n * track, and whatever gaps forced colours add, so the parts stay in exact proportion.\n */\n .v-meter-group-part,\n .v-meter-group-rest {\n flex: var(--meter-group-share) 1 0;\n min-inline-size: 0;\n transition: flex-grow var(--vectis-duration-base) var(--vectis-ease-default);\n }\n\n /*\n * A part with no tone or colour of its own turns the accent's hue by the golden angle for each\n * position, which keeps neighbouring hues apart for any number of parts; the first is the accent\n * itself. Lightness and chroma stay the accent's, so the parts follow the theme.\n */\n .v-meter-group-part,\n .v-meter-group-leading {\n --meter-group-colour: var(--tone-bg-solid);\n }\n\n .v-meter-group-part[data-derived],\n .v-meter-group-leading[data-derived] {\n --meter-group-colour: oklch(\n from var(--tone-bg-solid) l c calc(h + var(--meter-group-index) * 137.508)\n );\n }\n\n .v-meter-group-part {\n background: var(--meter-group-colour);\n }\n\n .v-meter-group-legend {\n display: flex;\n flex-wrap: wrap;\n column-gap: var(--vectis-space-4);\n row-gap: var(--vectis-space-1);\n margin: 0;\n padding: 0;\n list-style: none;\n }\n\n .v-meter-group-item {\n display: inline-flex;\n align-items: center;\n gap: var(--vectis-space-2);\n font-size: var(--vectis-text-body-sm-size);\n font-weight: var(--vectis-text-body-sm-weight);\n line-height: var(--vectis-text-body-sm-leading);\n }\n\n .v-meter-group-leading {\n display: inline-flex;\n color: var(--meter-group-colour);\n }\n\n .v-meter-group-swatch {\n inline-size: var(--vectis-control-size-meter-group-swatch);\n block-size: var(--vectis-control-size-meter-group-swatch);\n border-radius: var(--vectis-radius-pill);\n background: currentcolor;\n }\n\n .v-meter-group-item-label {\n color: var(--vectis-color-text);\n }\n\n .v-meter-group-item-value {\n color: var(--vectis-color-text-muted);\n font-variant-numeric: tabular-nums;\n }\n\n /*\n * Upright, the root carries the length and the legend stands beside the bar. A column filled\n * from its end puts the first part at the bottom, in either reading direction.\n */\n .v-meter-group[data-orientation='vertical'] {\n block-size: var(--vectis-control-size-bar-length);\n }\n\n .v-meter-group[data-orientation='vertical'] .v-meter-group-body {\n flex: 1 1 0;\n flex-direction: row;\n gap: var(--vectis-space-3);\n min-block-size: 0;\n }\n\n .v-meter-group[data-orientation='vertical'] .v-meter-group-track {\n flex-direction: column-reverse;\n flex-shrink: 0;\n inline-size: var(--meter-group-thickness);\n block-size: auto;\n }\n\n .v-meter-group[data-orientation='vertical'] .v-meter-group-part,\n .v-meter-group[data-orientation='vertical'] .v-meter-group-rest {\n min-inline-size: auto;\n min-block-size: 0;\n }\n\n .v-meter-group[data-orientation='vertical'] .v-meter-group-legend {\n flex-direction: column;\n flex-wrap: nowrap;\n }\n\n @media (prefers-reduced-motion: reduce) {\n .v-meter-group-part,\n .v-meter-group-rest {\n transition: none;\n }\n }\n\n /* Forced colours flatten every part to one system colour: only gaps keep them apart there. */\n @media (forced-colors: active) {\n .v-meter-group-track {\n gap: var(--vectis-control-size-meter-segment-gap);\n box-shadow: inset 0 0 0 var(--vectis-control-border-width) CanvasText;\n }\n\n .v-meter-group-part {\n forced-color-adjust: none;\n background: Highlight;\n }\n }\n}\n</style>\n"],"mappings":"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"}
|
|
@@ -6,12 +6,12 @@ function e(e) {
|
|
|
6
6
|
decimal: t.find((e) => e.type === "decimal")?.value ?? "."
|
|
7
7
|
};
|
|
8
8
|
}
|
|
9
|
-
var t = /[−‒–﹣-]/g, n = /^-?(\d
|
|
9
|
+
var t = /[−‒–﹣-]/g, n = /^-?(\d+(?:\.\d*)?|\.\d+)$/;
|
|
10
10
|
function r(e, r) {
|
|
11
11
|
let i = e.trim();
|
|
12
12
|
if (!i) return null;
|
|
13
13
|
let a = i.replace(t, "-");
|
|
14
|
-
return r.group && (a = a.split(r.group).join("")), a = a.replace(/\s/g, ""), r.decimal !== "." && (a = a.split(r.decimal).join(".")), a = a.replace(/^(-?)[^\d.-]+/, "$1").replace(/[^\d.]+$/, ""), n.test(a) ? Number(a) : NaN;
|
|
14
|
+
return r.group && (a = a.split(r.group).join("")), a = a.replace(/\s/g, ""), r.decimal !== "." && (a = a.split(r.decimal).join(".")), a = a.replace(/^(-?)[^\d.-]+/, "$1").replace(/(?<=^|[\d.])[^\d.]+$/, ""), n.test(a) ? Number(a) : NaN;
|
|
15
15
|
}
|
|
16
16
|
function i(e) {
|
|
17
17
|
let t = String(e), n = t.indexOf("e-");
|