@typlog/ui 0.14.0 → 0.15.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/charts.js ADDED
@@ -0,0 +1,382 @@
1
+ import { r as useForwardPropsWithout, t as buildPropsClass } from "./util-lkbchXvM.js";
2
+ import { Fragment, computed, createBlock, createCommentVNode, createElementBlock, createElementVNode, defineComponent, guardReactiveProps, h, mergeProps, normalizeClass, normalizeProps, normalizeStyle, onMounted, onUnmounted, openBlock, ref, render, renderList, renderSlot, toDisplayString, toRef, unref, useId, watch, withCtx } from "vue";
3
+ import { Primitive, createContext } from "reka-ui";
4
+ import { Icon, buildIcon, getIcon, loadIcons } from "@iconify/vue";
5
+ import { VisCrosshair, VisTooltip } from "@unovis/vue";
6
+ import { Donut, GroupedBar, StackedBar } from "@unovis/ts";
7
+ //#region src/charts/context.ts
8
+ var [injectChartContext, provideChartContext] = createContext("ui:Chart");
9
+ var defaultChartColors = [
10
+ "var(--accent-9)",
11
+ "var(--blue-9)",
12
+ "var(--ruby-9)",
13
+ "var(--green-9)",
14
+ "var(--amber-9)",
15
+ "var(--cyan-9)"
16
+ ];
17
+ //#endregion
18
+ //#region src/charts/util.ts
19
+ function getChartEntries(config) {
20
+ return Object.entries(config).filter((entry) => entry[1].role !== "x").map(([key, series], index) => ({
21
+ key,
22
+ series,
23
+ index
24
+ }));
25
+ }
26
+ function getChartColors(config) {
27
+ return getChartEntries(config).map(({ series, index }) => series.color ?? defaultChartColors[index % defaultChartColors.length]);
28
+ }
29
+ //#endregion
30
+ //#region src/charts/ChartRoot.vue
31
+ var ChartRoot_default = /* @__PURE__ */ defineComponent({
32
+ __name: "ChartRoot",
33
+ props: {
34
+ config: {},
35
+ asChild: { type: Boolean },
36
+ as: { default: "div" }
37
+ },
38
+ setup(__props) {
39
+ const props = __props;
40
+ const forwarded = useForwardPropsWithout(props, ["config"]);
41
+ const config = toRef(props, "config");
42
+ const mounted = ref(false);
43
+ onMounted(() => {
44
+ mounted.value = true;
45
+ });
46
+ const series = computed(() => getChartEntries(config.value));
47
+ const colors = computed(() => getChartColors(config.value));
48
+ const chartStyle = computed(() => {
49
+ const style = {};
50
+ defaultChartColors.forEach((color, index) => {
51
+ style[`--chart-${index + 1}`] = color;
52
+ });
53
+ colors.value.forEach((color, index) => {
54
+ style[`--chart-${index + 1}`] = color;
55
+ style[`--vis-color${index}`] = color;
56
+ });
57
+ return style;
58
+ });
59
+ provideChartContext({
60
+ config,
61
+ series,
62
+ colors,
63
+ mounted
64
+ });
65
+ return (_ctx, _cache) => {
66
+ return openBlock(), createBlock(unref(Primitive), mergeProps(unref(forwarded), {
67
+ class: "ui-ChartRoot",
68
+ style: chartStyle.value
69
+ }), {
70
+ default: withCtx(() => [renderSlot(_ctx.$slots, "default", { mounted: mounted.value })]),
71
+ _: 3
72
+ }, 16, ["style"]);
73
+ };
74
+ }
75
+ });
76
+ //#endregion
77
+ //#region src/charts/ChartTooltipContent.vue?vue&type=script&setup=true&lang.ts
78
+ var _hoisted_1$1 = { class: "ui-ChartTooltipContent" };
79
+ var _hoisted_2 = {
80
+ key: 0,
81
+ class: "ui-ChartTooltipLabel"
82
+ };
83
+ var _hoisted_3 = { class: "ui-ChartTooltipItems" };
84
+ var _hoisted_4 = ["innerHTML"];
85
+ var _hoisted_5 = { class: "ui-ChartTooltipSeries" };
86
+ var _hoisted_6 = { class: "ui-ChartTooltipSeriesLabel" };
87
+ var _hoisted_7 = { class: "ui-ChartTooltipValue" };
88
+ //#endregion
89
+ //#region src/charts/ChartTooltipContent.vue
90
+ var ChartTooltipContent_default = /* @__PURE__ */ defineComponent({
91
+ __name: "ChartTooltipContent",
92
+ props: {
93
+ payload: { default: () => ({}) },
94
+ config: { default: () => ({}) },
95
+ x: {},
96
+ indicator: { default: "dot" },
97
+ hideLabel: { type: Boolean },
98
+ hideIndicator: { type: Boolean },
99
+ labelKey: {},
100
+ labelFormatter: {},
101
+ valueFormatter: {}
102
+ },
103
+ setup(__props) {
104
+ const props = __props;
105
+ const items = computed(() => Object.entries(props.payload).map(([key, value]) => ({
106
+ key,
107
+ value,
108
+ series: props.config[key]
109
+ })).filter((item) => Boolean(item.series) && item.series.role !== "x").map((item, index) => ({
110
+ ...item,
111
+ index,
112
+ icon: buildSeriesIcon(item.series.icon)
113
+ })));
114
+ function buildSeriesIcon(name) {
115
+ const icon = name ? getIcon(name) : void 0;
116
+ return icon ? buildIcon(icon) : void 0;
117
+ }
118
+ const label = computed(() => {
119
+ if (props.hideLabel) return void 0;
120
+ const xKey = Object.entries(props.config).find(([_key, series]) => series.role === "x")?.[0];
121
+ const value = props.labelKey ? props.config[props.labelKey]?.label ?? props.payload[props.labelKey] ?? props.x : xKey ? props.payload[xKey] ?? props.x : props.x;
122
+ if (value === void 0 || value === null) return void 0;
123
+ const labelValue = typeof value === "string" || typeof value === "number" || value instanceof Date ? value : String(value);
124
+ return props.labelFormatter ? props.labelFormatter(labelValue) : String(labelValue);
125
+ });
126
+ function formatValue(value, key) {
127
+ if (props.valueFormatter) return props.valueFormatter(value, key);
128
+ if (typeof value === "number") return value.toLocaleString();
129
+ return String(value ?? "—");
130
+ }
131
+ return (_ctx, _cache) => {
132
+ return openBlock(), createElementBlock("div", _hoisted_1$1, [renderSlot(_ctx.$slots, "default", {
133
+ items: items.value,
134
+ label: label.value
135
+ }, () => [label.value ? (openBlock(), createElementBlock("div", _hoisted_2, toDisplayString(label.value), 1)) : createCommentVNode("", true), createElementVNode("div", _hoisted_3, [(openBlock(true), createElementBlock(Fragment, null, renderList(items.value, (item) => {
136
+ return openBlock(), createElementBlock("div", {
137
+ key: item.key,
138
+ class: "ui-ChartTooltipItem"
139
+ }, [
140
+ item.icon ? (openBlock(), createElementBlock("svg", mergeProps({
141
+ key: 0,
142
+ ref_for: true
143
+ }, item.icon.attributes, {
144
+ class: "ui-ChartTooltipIcon",
145
+ "aria-hidden": "true",
146
+ focusable: "false",
147
+ innerHTML: item.icon.body
148
+ }), null, 16, _hoisted_4)) : !props.hideIndicator && props.indicator !== "none" ? (openBlock(), createElementBlock("span", {
149
+ key: 1,
150
+ class: normalizeClass(["ui-ChartTooltipIndicator", `r-indicator-${props.indicator}`]),
151
+ style: normalizeStyle({ "--chart-tooltip-color": item.series.color ?? `var(--chart-${item.index + 1})` })
152
+ }, null, 6)) : createCommentVNode("", true),
153
+ createElementVNode("span", _hoisted_5, [createElementVNode("span", _hoisted_6, toDisplayString(item.series.label), 1)]),
154
+ createElementVNode("span", _hoisted_7, toDisplayString(formatValue(item.value, item.key)), 1)
155
+ ]);
156
+ }), 128))])])]);
157
+ };
158
+ }
159
+ });
160
+ //#endregion
161
+ //#region src/charts/tooltip.ts
162
+ /** Creates a synchronous Unovis template callback from a Vue tooltip component. */
163
+ function createChartTooltipTemplate(config, component, props = {}) {
164
+ return (rawPayload, x) => {
165
+ if (typeof document === "undefined") return "";
166
+ const payload = unwrapPayload(rawPayload, config);
167
+ const container = document.createElement("div");
168
+ const vnode = h(component, {
169
+ ...props,
170
+ payload,
171
+ config,
172
+ x
173
+ });
174
+ render(vnode, container);
175
+ const html = container.innerHTML;
176
+ render(null, container);
177
+ return html;
178
+ };
179
+ }
180
+ function unwrapPayload(payload, config) {
181
+ if (isRecord(payload) && isRecord(payload.data)) return unwrapPayload(payload.data, config);
182
+ if (!isRecord(payload)) return {};
183
+ const configured = Object.keys(config).filter((key) => key in payload);
184
+ if (configured.length) return Object.fromEntries(configured.map((key) => [key, payload[key]]));
185
+ return payload;
186
+ }
187
+ function isRecord(value) {
188
+ return typeof value === "object" && value !== null;
189
+ }
190
+ //#endregion
191
+ //#region src/charts/ChartCrosshair.vue
192
+ var ChartCrosshair_default = /* @__PURE__ */ defineComponent({
193
+ __name: "ChartCrosshair",
194
+ props: {
195
+ contentComponent: { default: ChartTooltipContent_default },
196
+ contentProps: { default: () => ({}) }
197
+ },
198
+ setup(__props) {
199
+ const props = __props;
200
+ const context = injectChartContext();
201
+ const forwarded = useForwardPropsWithout(props, [
202
+ "contentComponent",
203
+ "contentProps",
204
+ "template"
205
+ ]);
206
+ const icons = computed(() => context.series.value.flatMap((entry) => entry.series.icon ? [entry.series.icon] : []));
207
+ let cancelIconLoad;
208
+ watch(icons, (value) => {
209
+ cancelIconLoad?.();
210
+ cancelIconLoad = value.length ? loadIcons(value) : void 0;
211
+ }, { immediate: true });
212
+ onUnmounted(() => cancelIconLoad?.());
213
+ const color = computed(() => {
214
+ return context.series.value.map((d) => `var(--chart-${d.index + 1})`);
215
+ });
216
+ const template = computed(() => createChartTooltipTemplate(context.config.value, props.contentComponent, props.contentProps));
217
+ return (_ctx, _cache) => {
218
+ return openBlock(), createBlock(unref(VisCrosshair), normalizeProps(guardReactiveProps({
219
+ ...unref(forwarded),
220
+ color: color.value,
221
+ template: template.value
222
+ })), null, 16);
223
+ };
224
+ }
225
+ });
226
+ //#endregion
227
+ //#region src/charts/ChartTooltip.vue
228
+ var ChartTooltip_default = /* @__PURE__ */ defineComponent({
229
+ __name: "ChartTooltip",
230
+ props: {
231
+ targets: {},
232
+ contentComponent: { default: ChartTooltipContent_default },
233
+ contentProps: { default: () => ({}) }
234
+ },
235
+ setup(__props) {
236
+ const props = __props;
237
+ const context = injectChartContext();
238
+ const forwarded = useForwardPropsWithout(props, [
239
+ "targets",
240
+ "contentComponent",
241
+ "contentProps",
242
+ "triggers"
243
+ ]);
244
+ const tooltipClass = computed(() => ["ui-ChartTooltip", props.className].filter(Boolean).join(" "));
245
+ const template = computed(() => createChartTooltipTemplate(context.config.value, props.contentComponent, props.contentProps));
246
+ const generatedTriggers = computed(() => {
247
+ const triggers = {};
248
+ for (const target of props.targets ?? []) {
249
+ const { primitive, payload } = typeof target === "function" ? { primitive: target } : target;
250
+ if (primitive === Donut) triggers[Donut.selectors.segment] = (arc, index) => {
251
+ const mapped = payload ? payload(arc, index) : donutPayload(arc, index);
252
+ return template.value(mapped);
253
+ };
254
+ if (primitive === GroupedBar) triggers[GroupedBar.selectors.bar] = (datum, index) => {
255
+ const mapped = payload ? payload(datum, index) : datum;
256
+ return template.value(mapped);
257
+ };
258
+ if (primitive === StackedBar) triggers[StackedBar.selectors.bar] = (datum, index) => {
259
+ const mapped = payload ? payload(datum, index) : unwrapStackedDatum(datum);
260
+ return template.value(mapped);
261
+ };
262
+ }
263
+ return triggers;
264
+ });
265
+ const triggers = computed(() => ({
266
+ ...generatedTriggers.value,
267
+ ...props.triggers
268
+ }));
269
+ function donutPayload(value, callbackIndex) {
270
+ if (!isRecord(value)) return {};
271
+ const arcIndex = typeof value.index === "number" ? value.index : callbackIndex;
272
+ const entry = context.series.value[arcIndex];
273
+ if (!entry) return {};
274
+ return { [entry.key]: value.value };
275
+ }
276
+ function unwrapStackedDatum(value) {
277
+ if (!isRecord(value) || !isRecord(value.datum)) return value;
278
+ return {
279
+ ...value,
280
+ ...value.datum
281
+ };
282
+ }
283
+ function isRecord(value) {
284
+ return typeof value === "object" && value !== null;
285
+ }
286
+ return (_ctx, _cache) => {
287
+ return openBlock(), createBlock(unref(VisTooltip), mergeProps({
288
+ ...unref(forwarded),
289
+ triggers: triggers.value
290
+ }, { "class-name": tooltipClass.value }), null, 16, ["class-name"]);
291
+ };
292
+ }
293
+ });
294
+ //#endregion
295
+ //#region src/charts/ChartAreaGradient.vue
296
+ var ChartAreaGradient_default = /* @__PURE__ */ defineComponent({
297
+ __name: "ChartAreaGradient",
298
+ setup(__props) {
299
+ const context = injectChartContext();
300
+ const instanceId = useId();
301
+ const gradients = computed(() => {
302
+ return context.series.value.map((d) => {
303
+ return {
304
+ id: `ui-chart-area-${instanceId}-${d.index + 1}`,
305
+ color: `var(--chart-${d.index + 1})`
306
+ };
307
+ });
308
+ });
309
+ const svgDefs = computed(() => gradients.value.map((item) => `
310
+ <linearGradient id="${item.id}" x1="0" y1="0" x2="0" y2="1">
311
+ <stop offset="5%" stop-color="${item.color}" stop-opacity="0.8" />
312
+ <stop offset="95%" stop-color="${item.color}" stop-opacity="0.1" />
313
+ </linearGradient>
314
+ `).join(""));
315
+ const areaColor = (_data, index) => {
316
+ const gradient = gradients.value[index];
317
+ return gradient ? `url(#${gradient.id})` : "none";
318
+ };
319
+ return (_ctx, _cache) => {
320
+ return renderSlot(_ctx.$slots, "default", {
321
+ color: areaColor,
322
+ svgDefs: svgDefs.value
323
+ });
324
+ };
325
+ }
326
+ });
327
+ //#endregion
328
+ //#region src/charts/ChartLegend.vue?vue&type=script&setup=true&lang.ts
329
+ var _hoisted_1 = { class: "ui-ChartLegendLabel" };
330
+ //#endregion
331
+ //#region src/charts/ChartLegend.vue
332
+ var ChartLegend_default = /* @__PURE__ */ defineComponent({
333
+ __name: "ChartLegend",
334
+ props: {
335
+ orientation: { default: "horizontal" },
336
+ align: { default: "start" },
337
+ ariaLabel: { default: "Chart legend" },
338
+ asChild: { type: Boolean },
339
+ as: { default: "div" }
340
+ },
341
+ setup(__props) {
342
+ const props = __props;
343
+ const forwarded = useForwardPropsWithout(props, [
344
+ "orientation",
345
+ "align",
346
+ "ariaLabel"
347
+ ]);
348
+ const resetClass = buildPropsClass(props, ["orientation", "align"]);
349
+ const context = injectChartContext();
350
+ const entries = computed(() => context.series.value);
351
+ return (_ctx, _cache) => {
352
+ return openBlock(), createBlock(unref(Primitive), mergeProps(unref(forwarded), {
353
+ class: ["ui-ChartLegend", unref(resetClass)],
354
+ role: "list",
355
+ "aria-label": props.ariaLabel
356
+ }), {
357
+ default: withCtx(() => [(openBlock(true), createElementBlock(Fragment, null, renderList(entries.value, (entry) => {
358
+ return openBlock(), createElementBlock("div", {
359
+ key: entry.key,
360
+ class: "ui-ChartLegendItem",
361
+ role: "listitem"
362
+ }, [renderSlot(_ctx.$slots, "item", mergeProps({ ref_for: true }, entry), () => [
363
+ renderSlot(_ctx.$slots, "marker", mergeProps({ ref_for: true }, entry), () => [createElementVNode("span", {
364
+ class: "ui-ChartLegendMarker",
365
+ style: normalizeStyle({ backgroundColor: unref(context).colors.value[entry.index] })
366
+ }, null, 4)]),
367
+ entry.series.icon ? (openBlock(), createBlock(unref(Icon), {
368
+ key: 0,
369
+ icon: entry.series.icon,
370
+ class: "ui-ChartLegendIcon",
371
+ "aria-hidden": "true"
372
+ }, null, 8, ["icon"])) : createCommentVNode("", true),
373
+ createElementVNode("span", _hoisted_1, toDisplayString(entry.series.label), 1)
374
+ ])]);
375
+ }), 128))]),
376
+ _: 3
377
+ }, 16, ["class", "aria-label"]);
378
+ };
379
+ }
380
+ });
381
+ //#endregion
382
+ export { ChartAreaGradient_default as ChartAreaGradient, ChartCrosshair_default as ChartCrosshair, ChartLegend_default as ChartLegend, ChartRoot_default as ChartRoot, ChartTooltip_default as ChartTooltip, ChartTooltipContent_default as ChartTooltipContent, createChartTooltipTemplate };