@milaboratories/uikit 2.2.45 → 2.2.46

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.
Files changed (41) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/dist/pl-uikit.js +7708 -5293
  3. package/dist/pl-uikit.umd.cjs +12 -8
  4. package/dist/src/components/PlChartHistogram/PlChartHistogram.vue.d.ts +6 -0
  5. package/dist/src/components/PlChartHistogram/createGridlines.d.ts +3 -0
  6. package/dist/src/components/PlChartHistogram/createLabels.d.ts +3 -0
  7. package/dist/src/components/PlChartHistogram/createSvgContainer.d.ts +3 -0
  8. package/dist/src/components/PlChartHistogram/drawBins.d.ts +2 -0
  9. package/dist/src/components/PlChartHistogram/drawThreshold.d.ts +2 -0
  10. package/dist/src/components/PlChartHistogram/histogram.d.ts +4 -0
  11. package/dist/src/components/PlChartHistogram/index.d.ts +1 -0
  12. package/dist/src/components/PlChartHistogram/logspace.d.ts +1 -0
  13. package/dist/src/components/PlChartHistogram/normalizeBins.d.ts +2 -0
  14. package/dist/src/components/PlChartHistogram/scales.spec.d.ts +1 -0
  15. package/dist/src/components/PlChartHistogram/types.d.ts +58 -0
  16. package/dist/src/composition/useComponentProp.d.ts +33 -0
  17. package/dist/src/index.d.ts +2 -0
  18. package/dist/src/types.d.ts +2 -1
  19. package/dist/style.css +1 -1
  20. package/dist/tsconfig.lib.tsbuildinfo +1 -1
  21. package/package.json +6 -4
  22. package/src/colors/__tests__/colors.spec.ts +0 -2
  23. package/src/components/PlChartHistogram/PlChartHistogram.vue +90 -0
  24. package/src/components/PlChartHistogram/createGridlines.ts +42 -0
  25. package/src/components/PlChartHistogram/createLabels.ts +32 -0
  26. package/src/components/PlChartHistogram/createSvgContainer.ts +23 -0
  27. package/src/components/PlChartHistogram/drawBins.ts +55 -0
  28. package/src/components/PlChartHistogram/drawThreshold.ts +19 -0
  29. package/src/components/PlChartHistogram/histogram.ts +136 -0
  30. package/src/components/PlChartHistogram/index.ts +1 -0
  31. package/src/components/PlChartHistogram/logspace.ts +13 -0
  32. package/src/components/PlChartHistogram/normalizeBins.ts +19 -0
  33. package/src/components/PlChartHistogram/scales.spec.ts +10 -0
  34. package/src/components/PlChartHistogram/types.ts +66 -0
  35. package/src/components/PlDropdownMulti/PlDropdownMulti.vue +0 -2
  36. package/src/components/PlDropdownMultiRef/__tests__/PlDropdownMultiRef.spec.ts +1 -3
  37. package/src/components/PlProgressCell/PlProgressCell.vue +0 -2
  38. package/src/composition/useComponentProp.ts +36 -0
  39. package/src/helpers/index.ts +1 -1
  40. package/src/index.ts +2 -0
  41. package/src/types.ts +3 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@milaboratories/uikit",
3
- "version": "2.2.45",
3
+ "version": "2.2.46",
4
4
  "type": "module",
5
5
  "main": "dist/pl-uikit.umd.js",
6
6
  "module": "dist/pl-uikit.js",
@@ -18,7 +18,8 @@
18
18
  "./*": "./*"
19
19
  },
20
20
  "dependencies": {
21
- "vue": "^3.5.13"
21
+ "vue": "^3.5.13",
22
+ "d3": "^7.9.0"
22
23
  },
23
24
  "devDependencies": {
24
25
  "@vue/test-utils": "^2.4.6",
@@ -32,9 +33,10 @@
32
33
  "vue-tsc": "^2.1.10",
33
34
  "yarpm": "^1.2.0",
34
35
  "svgo": "^3.3.2",
35
- "@milaboratories/helpers": "^1.6.11",
36
+ "@types/d3": "^7.4.3",
36
37
  "@milaboratories/eslint-config": "^1.0.1",
37
- "@platforma-sdk/model": "^1.21.20"
38
+ "@platforma-sdk/model": "^1.21.20",
39
+ "@milaboratories/helpers": "^1.6.11"
38
40
  },
39
41
  "scripts": {
40
42
  "dev": "vite",
@@ -14,8 +14,6 @@ describe('Colors', () => {
14
14
 
15
15
  const viridis15 = Gradient(viridis).split(15);
16
16
 
17
- console.log('viridis15', JSON.stringify(viridis15));
18
-
19
17
  expect(viridis.map((it) => it + 'FF').join(',')).toEqual(viridis15.map((it) => it.hex.toUpperCase()).join(','));
20
18
  });
21
19
 
@@ -0,0 +1,90 @@
1
+ <script lang="ts" setup>
2
+ import { computed, onMounted, ref, watch } from 'vue';
3
+ import { createHistogramFromBins, createHistogramLinear, createHistogramLog } from './histogram';
4
+ import type { ChartOptions, PlChartHistogramSettings } from './types';
5
+
6
+ const props = defineProps<{
7
+ settings: PlChartHistogramSettings;
8
+ }>();
9
+
10
+ const chart = ref<HTMLElement>();
11
+
12
+ const options = computed<ChartOptions>(() => {
13
+ const { xAxisLabel, yAxisLabel, threshold, compact, totalWidth = 674, totalHeight = 252 } = props.settings;
14
+
15
+ const margin = compact ? { top: 0, right: 0, bottom: 0, left: 0 } : { top: 0, right: 30, bottom: 40, left: 85 };
16
+ const width = totalWidth - margin.left - margin.right;
17
+ const height = totalHeight - margin.top - margin.bottom;
18
+
19
+ return {
20
+ width,
21
+ height,
22
+ margin,
23
+ compact,
24
+ nBins: 'nBins' in props.settings ? props.settings.nBins : 10,
25
+ xAxisLabel: xAxisLabel,
26
+ yAxisLabel: yAxisLabel,
27
+ threshold: threshold,
28
+ };
29
+ });
30
+
31
+ const createHistogram = () => {
32
+ const settings = props.settings;
33
+
34
+ if (settings.type === 'log-bins') {
35
+ createHistogramFromBins(chart.value!, options.value, settings.bins);
36
+ return;
37
+ }
38
+
39
+ if (settings.log) {
40
+ createHistogramLog(chart.value!, options.value, settings.numbers);
41
+ } else {
42
+ createHistogramLinear(chart.value!, options.value, settings.numbers);
43
+ }
44
+ };
45
+
46
+ watch(props, createHistogram);
47
+
48
+ onMounted(createHistogram);
49
+ </script>
50
+
51
+ <template>
52
+ <div :class="$style.component">
53
+ <div v-if="settings.title && !settings.compact" :class="$style.title">{{ settings.title }}</div>
54
+ <div ref="chart" />
55
+ </div>
56
+ </template>
57
+
58
+ <style module>
59
+ .component {
60
+ display: flex;
61
+ flex-direction: column;
62
+ gap: 24px;
63
+ svg {
64
+ font-family: var(--font-family-base);
65
+ }
66
+ }
67
+
68
+ .title {
69
+ font-size: 20px;
70
+ font-weight: 500;
71
+ line-height: 24px; /* 120% */
72
+ letter-spacing: -0.2px;
73
+ }
74
+
75
+ :global(.svg-tooltip) {
76
+ font-family: var(--font-family-base);
77
+ background: rgba(69,77,93,.9);
78
+ border-radius: .1rem;
79
+ color: #fff;
80
+ display: block;
81
+ font-size: 14px;
82
+ max-width: 320px;
83
+ padding: .2rem .4rem;
84
+ position: absolute;
85
+ text-overflow: ellipsis;
86
+ white-space: pre;
87
+ z-index: 300;
88
+ visibility: hidden;
89
+ }
90
+ </style>
@@ -0,0 +1,42 @@
1
+ import * as d3 from 'd3';
2
+ import type { ChartOptions, Scales } from './types';
3
+
4
+ export function createGridlines(
5
+ svg: d3.Selection<SVGGElement, unknown, null, undefined>,
6
+ options: ChartOptions,
7
+ scales: Scales,
8
+ xTicks: (d: d3.Axis<d3.NumberValue>) => d3.Axis<d3.NumberValue>,
9
+ ) {
10
+ const { width, height } = options;
11
+
12
+ function makeYGridlines() {
13
+ return d3.axisLeft(scales.y) // Use the y-scale for horizontal gridlines
14
+ .ticks(6); // Adjust the number of gridlines
15
+ }
16
+
17
+ function makeXGridlines() {
18
+ return xTicks(d3.axisBottom(scales.x));
19
+ }
20
+ // Append horizontal gridlines
21
+ svg.append('g')
22
+ .attr('class', 'grid') // Add a class for styling
23
+ .attr('font-family', '\'Manrope\', sans-serif') // Doesn't work
24
+ .call(makeYGridlines()
25
+ .tickSize(-width) // Extend gridlines across the chart width
26
+ .tickFormat(() => '')); // Remove tick labels
27
+
28
+ // Append vertical gridlines
29
+ svg.append('g')
30
+ .attr('class', 'grid') // Add a class for styling
31
+ .attr('font-family', '\'Manrope\', sans-serif')
32
+ .attr('transform', `translate(0,${height})`) // Position at the bottom of the chart
33
+ .call(makeXGridlines()
34
+ .tickSize(-height) // Extend gridlines across the chart height
35
+ .tickFormat(() => '')); // Remove tick labels
36
+
37
+ // Style the gridlines using CSS (or inline styles)
38
+ d3.selectAll('.grid line')
39
+ .style('stroke', '#E1E3EB') // Light gray gridlines
40
+ // .style('stroke-dasharray', '2,2') // Dashed gridlines
41
+ .style('opacity', 0.7); // Slightly transparent
42
+ }
@@ -0,0 +1,32 @@
1
+ import type * as d3 from 'd3';
2
+ import type { ChartOptions } from './types';
3
+
4
+ export function createLabels(
5
+ svg: d3.Selection<SVGGElement, unknown, null, undefined>,
6
+ options: ChartOptions,
7
+ ) {
8
+ const { height, width, margin, xAxisLabel, yAxisLabel, compact } = options;
9
+
10
+ if (compact) {
11
+ return;
12
+ }
13
+
14
+ // X-axis label
15
+ svg.append('text')
16
+ .attr('class', 'x-axis-label')
17
+ .attr('font-weight', 500)
18
+ .attr('text-anchor', 'middle') // Center the text
19
+ .attr('x', width / 2) // Center horizontally
20
+ .attr('y', height + margin.bottom - 5) // Position below the X-axis
21
+ .text(xAxisLabel ?? 'Value Range'); // Set your custom label text
22
+
23
+ // Y-axis label
24
+ svg.append('text')
25
+ .attr('class', 'y-axis-label')
26
+ .attr('font-weight', 500)
27
+ .attr('text-anchor', 'middle') // Center the text
28
+ .attr('x', -height / 2) // Center vertically (rotated axis)
29
+ .attr('y', -margin.left + 15) // Position to the left of the Y-axis
30
+ .attr('transform', 'rotate(-90)') // Rotate text 90 degrees counter-clockwise
31
+ .text(yAxisLabel ?? 'Frequency'); // Set your custom label text
32
+ }
@@ -0,0 +1,23 @@
1
+ import * as d3 from 'd3';
2
+ import type { ChartOptions } from './types';
3
+
4
+ export function createSvgContainer(el: HTMLElement, options: ChartOptions) {
5
+ const { width, height, margin, compact } = options;
6
+
7
+ el.replaceChildren();
8
+
9
+ if (compact) {
10
+ el.style.height = height + 'px';
11
+ el.style.lineHeight = height + 'px';
12
+ }
13
+
14
+ const svg = d3
15
+ .select(el) // Append the SVG element to the body
16
+ .append('svg')
17
+ .attr('width', width + margin.left + margin.right) // Set the total width
18
+ .attr('height', height + margin.top + margin.bottom) // Set the total height
19
+ .append('g') // Append a group to handle margins
20
+ .attr('transform', `translate(${margin.left},${margin.top})`);
21
+
22
+ return svg;
23
+ }
@@ -0,0 +1,55 @@
1
+ import type { BinLike, ChartOptions, Scales, SVG } from './types';
2
+ import * as d3 from 'd3';
3
+
4
+ export function drawBins(
5
+ svg: SVG,
6
+ bins: BinLike[],
7
+ dimension: ChartOptions,
8
+ scales: Scales,
9
+ ) {
10
+ const { height } = dimension;
11
+
12
+ const { x, y } = scales;
13
+
14
+ const tooltip = d3
15
+ .select('body')
16
+ .append('div')
17
+ .attr('class', 'svg-tooltip')
18
+ .style('position', 'absolute')
19
+ .style('visibility', 'hidden');
20
+
21
+ // Three function that change the tooltip when user hover / move / leave a cell
22
+ const mouseover = function (_event: MouseEvent, d: BinLike) {
23
+ tooltip
24
+ .style('visibility', 'visible')
25
+ .text(`count: ${d.length}\nx0: ${d.x0}\nx1: ${d.x1}`);
26
+ };
27
+
28
+ const mousemove = function (event: MouseEvent) {
29
+ tooltip
30
+ .style('top', event.pageY - 10 + 'px')
31
+ .style('left', event.pageX + 10 + 'px');
32
+ };
33
+
34
+ const mouseout = function () {
35
+ tooltip.style('visibility', 'hidden');
36
+ };
37
+
38
+ // Add rectangles for the histogram bars
39
+ svg.selectAll('rect')
40
+ .data(bins)
41
+ .enter()
42
+ .append('rect')
43
+ .attr('x', (d) => x(d.x0!)) // Position the bar based on the bin start
44
+ .attr('y', (d) => y(d.length)) // Height based on bin count
45
+ .attr('width', (d) => x(d.x1!) - x(d.x0!)) // Bar width based on logarithmic intervals
46
+ .attr('height', (d) => height - y(d.length)) // Invert height to fit SVG coordinate system
47
+ .style('fill', '#929BAD')
48
+ .attr('stroke', '#fff') // Border color
49
+ .attr('stroke-opacity', 0.2)
50
+ .attr('stroke-width', 0.5)
51
+ .on('mouseover', mouseover)
52
+ .on('mousemove', mousemove)
53
+ .on('mouseout', mouseout)
54
+ .append('title').text((d) => `[${d.x0}, ${d.x1}]\n` + d.length + '\n'); // Set bar color
55
+ }
@@ -0,0 +1,19 @@
1
+ import type { ChartOptions, Scales, SVG } from './types';
2
+
3
+ export function drawThreshold(svg: SVG, scales: Scales, options: ChartOptions) {
4
+ const { threshold } = options;
5
+
6
+ if (!threshold) {
7
+ return;
8
+ }
9
+
10
+ svg
11
+ .append('line')
12
+ .attr('x1', scales.x(threshold))
13
+ .attr('x2', scales.x(threshold))
14
+ .attr('y1', 0)
15
+ .attr('y2', options.height)
16
+ .style('stroke', '#F05670')
17
+ .style('stroke-width', '1')
18
+ .style('stroke-dasharray', '7.4 3.2');
19
+ }
@@ -0,0 +1,136 @@
1
+ import * as d3 from 'd3';
2
+ import { createSvgContainer } from './createSvgContainer';
3
+ import { drawBins } from './drawBins';
4
+ import { createGridlines } from './createGridlines';
5
+ import { logspace } from './logspace';
6
+ import { createLabels } from './createLabels';
7
+ import type { AnyBin, BinLike, ChartOptions } from './types';
8
+ import { drawThreshold } from './drawThreshold';
9
+ import { normalizeBins } from './normalizeBins';
10
+
11
+ const gx = (svg: d3.Selection<SVGGElement, unknown, null, undefined>, height: number) => {
12
+ return svg.append('g')
13
+ .style('font-size', '14px')
14
+ .style('font-weight', '500')
15
+ .attr('transform', `translate(0,${height})`);
16
+ };
17
+
18
+ const gy = (svg: d3.Selection<SVGGElement, unknown, null, undefined>) => {
19
+ return svg.append('g')
20
+ .style('font-size', '14px')
21
+ .style('font-weight', '500');
22
+ };
23
+
24
+ const createYScale = (bins: BinLike[], height: number) => {
25
+ return d3.scaleLinear()
26
+ .domain([0, d3.max(bins, (d) => d.length)!]) // Max bin count for the domain
27
+ .range([height, 0]); // Map to chart height (invert to match SVG coordinates)
28
+ };
29
+
30
+ export function createHistogramLinear(el: HTMLElement, options: ChartOptions, data: number[]) {
31
+ const { width, height, nBins = 10 } = options;
32
+
33
+ const svg = createSvgContainer(el, options);
34
+
35
+ const min = d3.min(data) as number;
36
+ const max = d3.max(data) as number;
37
+
38
+ const x = d3.scaleLinear()
39
+ .domain([min, max])
40
+ .range([0, width]);
41
+
42
+ const bins: BinLike[] = normalizeBins(d3.bin()
43
+ .domain(x.domain() as [number, number]) // Set the input domain to match the x-scale
44
+ .thresholds(x.ticks(nBins))(data)); // Apply the data to create bins
45
+
46
+ const y = createYScale(bins, height);
47
+
48
+ createGridlines(svg, options, { x, y }, (x) => x.ticks(6));
49
+
50
+ drawBins(svg, bins, options, { x, y });
51
+
52
+ drawThreshold(svg, { x, y }, options);
53
+
54
+ gx(svg, height).call(d3.axisBottom(x).tickSize(0));
55
+
56
+ gy(svg).call(d3.axisLeft(y).tickSize(0));
57
+
58
+ createLabels(svg, options);
59
+ }
60
+
61
+ export function createHistogramLog(el: HTMLElement, options: ChartOptions, data: number[]) {
62
+ const { width, height, nBins = 10 } = options;
63
+
64
+ const svg = createSvgContainer(el, options);
65
+
66
+ const min = d3.min(data) as number;
67
+ const max = d3.max(data) as number;
68
+
69
+ const x = d3.scaleSymlog()
70
+ .domain([min, max]) // Input range (min and max values of the data)
71
+ .range([0, width])
72
+ .nice() // Output range (width of the chart)
73
+ ;
74
+
75
+ const createThresholds = (n: number) => {
76
+ const res = [];
77
+
78
+ for (let i = 0; i <= n; i++) {
79
+ res.push(Number(x.invert(width * (i / n)).toFixed(2)));
80
+ }
81
+
82
+ return res;
83
+ };
84
+
85
+ const bins = normalizeBins(d3.bin()
86
+ .domain(x.domain() as [number, number]) // Set the input domain to match the x-scale
87
+ .thresholds(createThresholds(nBins))(data)); // Apply the data to create bins
88
+
89
+ const y = createYScale(bins, height);
90
+
91
+ const tickValues = logspace(0, Math.ceil(Math.log10(max)), 6);
92
+
93
+ createGridlines(svg, options, { x, y }, (x) => x.tickValues(tickValues));
94
+
95
+ drawBins(svg, bins, options, { x, y });
96
+
97
+ drawThreshold(svg, { x, y }, options);
98
+
99
+ gx(svg, height).call(d3.axisBottom(x).tickValues(tickValues).tickSize(0));
100
+
101
+ gy(svg).call(d3.axisLeft(y).tickSize(0));
102
+
103
+ createLabels(svg, options);
104
+ }
105
+
106
+ export function createHistogramFromBins(el: HTMLElement, options: ChartOptions, _bins: AnyBin[]) {
107
+ const { width, height } = options;
108
+
109
+ const svg = createSvgContainer(el, options);
110
+
111
+ const bins = normalizeBins(_bins);
112
+
113
+ const min = d3.min(bins, (b) => b.x0) as number;
114
+ const max = d3.max(bins, (b) => b.x1) as number;
115
+
116
+ const x = d3.scaleSymlog()
117
+ .domain([min, max])
118
+ .range([0, width])
119
+ .nice();
120
+
121
+ const y = createYScale(bins, height);
122
+
123
+ const tickValues = logspace(0, Math.ceil(Math.log10(max)), 6);
124
+
125
+ createGridlines(svg, options, { x, y }, (x) => x.tickValues(tickValues));
126
+
127
+ drawBins(svg, bins, options, { x, y });
128
+
129
+ drawThreshold(svg, { x, y }, options);
130
+
131
+ gx(svg, height).call(d3.axisBottom(x).tickValues(tickValues).tickSize(0));
132
+
133
+ gy(svg).call(d3.axisLeft(y).tickSize(0));
134
+
135
+ createLabels(svg, options);
136
+ }
@@ -0,0 +1 @@
1
+ export { default as PlChartHistogram } from './PlChartHistogram.vue';
@@ -0,0 +1,13 @@
1
+ export function logspace(startExp: number, stopExp: number, num = 50, base = 10) {
2
+ if (num <= 0) return [];
3
+
4
+ const step = (stopExp - startExp) / (num - 1);
5
+ const result = [];
6
+
7
+ for (let i = 0; i < num; i++) {
8
+ const exponent = startExp + i * step;
9
+ result.push(Math.pow(base, exponent));
10
+ }
11
+
12
+ return result;
13
+ }
@@ -0,0 +1,19 @@
1
+ import type { AnyBin, BinLike } from './types';
2
+
3
+ export function normalizeBins(bins: (d3.Bin<number, number> | AnyBin)[]): BinLike[] {
4
+ return bins.map((it) => {
5
+ if ('from' in it) {
6
+ return {
7
+ x0: it.from,
8
+ x1: it.to,
9
+ length: it.weight,
10
+ };
11
+ }
12
+
13
+ return {
14
+ x0: it.x0!,
15
+ x1: it.x1!,
16
+ length: it.length,
17
+ };
18
+ });
19
+ }
@@ -0,0 +1,10 @@
1
+ import { describe, it } from 'vitest';
2
+ import { logspace } from './logspace';
3
+
4
+ describe('Scales', () => {
5
+ it('logspace', async () => {
6
+ const res = logspace(0, 4, 10);
7
+
8
+ console.log('res', res);
9
+ });
10
+ });
@@ -0,0 +1,66 @@
1
+ export type Margin = { top: number; right: number; bottom: number; left: number };
2
+
3
+ export type ChartOptions = {
4
+ width: number;
5
+ height: number;
6
+ margin: Margin;
7
+ nBins?: number;
8
+ yAxisLabel?: string;
9
+ xAxisLabel?: string;
10
+ threshold?: number;
11
+ compact?: boolean;
12
+ };
13
+
14
+ export type Scales = {
15
+ x: d3.ScaleSymLog<number, number, never> | d3.ScaleLinear<number, number>;
16
+ y: d3.ScaleLinear<number, number, never>;
17
+ };
18
+
19
+ export type SVG = d3.Selection<SVGGElement, unknown, null, undefined>;
20
+
21
+ export type CustomBin = {
22
+ from: number;
23
+ to: number;
24
+ weight: number;
25
+ };
26
+
27
+ export type BinLike = {
28
+ x0: number;
29
+ x1: number;
30
+ length: number;
31
+ };
32
+
33
+ export type AnyBin = CustomBin | BinLike;
34
+
35
+ /**
36
+ * Common case: array of numbers
37
+ */
38
+ export type PlChartHistogramBasicSettings = {
39
+ type: 'basic';
40
+ threshold?: number;
41
+ numbers: number[];
42
+ log?: boolean;
43
+ nBins?: number;
44
+ };
45
+
46
+ /**
47
+ * For precalculated bins on log x scale
48
+ */
49
+ export type PlChartHistogramLogBinsSettings = {
50
+ type: 'log-bins';
51
+ threshold?: number;
52
+ bins: AnyBin[];
53
+ };
54
+
55
+ export type PlChartHistogramSettings = (
56
+ PlChartHistogramBasicSettings |
57
+ PlChartHistogramLogBinsSettings
58
+ ) & {
59
+ title?: string;
60
+ yAxisLabel?: string;
61
+ xAxisLabel?: string;
62
+ // with margins
63
+ totalWidth?: number;
64
+ totalHeight?: number;
65
+ compact?: boolean;
66
+ };
@@ -166,8 +166,6 @@ const toggleModel = () => (data.open = !data.open);
166
166
  const onFocusOut = (event: FocusEvent) => {
167
167
  const relatedTarget = event.relatedTarget as Node | null;
168
168
 
169
- console.log('>>>> overlay.value?.$el', overlay.value?.$el);
170
-
171
169
  if (!rootRef.value?.contains(relatedTarget) && !overlay.value?.listRef?.contains(relatedTarget)) {
172
170
  data.search = '';
173
171
  data.open = false;
@@ -43,11 +43,9 @@ describe('PlDropdownMultiRef', () => {
43
43
 
44
44
  const options = getOptions();
45
45
 
46
- console.log('options', options);
47
-
48
46
  expect(options.length).toBe(2);
49
47
 
50
- console.log(wrapper.props('modelValue'), 'mv');
48
+ // console.log(wrapper.props('modelValue'), 'mv');
51
49
  options[0].click();
52
50
 
53
51
  await delay(20);
@@ -17,8 +17,6 @@ const canShowWhiteBg = computed(() => props.stage !== 'not_started');
17
17
  const currentProgress = computed(() => props.stage === 'done' ? 100 : Math.min(100, props.progress || 0));
18
18
 
19
19
  const canShowInfinityLoader = computed(() => props.progress === undefined && props.stage !== 'done' && props.stage !== 'not_started' && !props.error);
20
-
21
- console.log(props);
22
20
  </script>
23
21
 
24
22
  <template>
@@ -0,0 +1,36 @@
1
+ import type { InferComponentProps } from '@/types';
2
+ import { computed, type Component, type ComputedRef } from 'vue';
3
+
4
+ /**
5
+ * A utility function that creates a reactive computed property
6
+ * based on a specific prop of a Vue component.
7
+ *
8
+ *
9
+ * @template C - The Vue component type.
10
+ * @template P - The prop name.
11
+ *
12
+ * @param cb - A factory function that returns the value of the prop from the inferred component props.
13
+ *
14
+ * @returns A `ComputedRef` of the specified prop
15
+ *
16
+ * @example
17
+ * ```ts
18
+ * import { ref, defineComponent } from 'vue';
19
+ * import { useComponentProp } from '@platforma-sdk/ui-vue';
20
+ *
21
+ * const MyComponent = defineComponent({
22
+ * props: {
23
+ * myProp: {
24
+ * type: String,
25
+ * required: true
26
+ * }
27
+ * }
28
+ * });
29
+ *
30
+ * const propValue = useComponentProp<typeof MyComponent, 'myProp'>(() => 'example');
31
+ * console.log(propValue.value); // Outputs: 'example'
32
+ * ```
33
+ */
34
+ export function useComponentProp<C extends Component, P extends keyof InferComponentProps<C>>(cb: () => InferComponentProps<C>[P]): ComputedRef<InferComponentProps<C>[P]> {
35
+ return computed(cb);
36
+ }
@@ -9,7 +9,7 @@ export function requestTick<P>(cb: (...args: P[]) => void) {
9
9
  });
10
10
  tick = true;
11
11
  } else {
12
- console.log('handle pressure');
12
+ // console.log('handle pressure');
13
13
  }
14
14
  };
15
15
  }
package/src/index.ts CHANGED
@@ -63,6 +63,7 @@ export * from './components/PlIcon16';
63
63
  export * from './components/PlIcon24';
64
64
 
65
65
  export * from './components/PlChartStackedBar';
66
+ export * from './components/PlChartHistogram';
66
67
 
67
68
  export * from './colors';
68
69
 
@@ -93,6 +94,7 @@ export { useInterval } from './composition/useInterval';
93
94
  export { useFormState } from './composition/useFormState';
94
95
  export { useQuery } from './composition/useQuery.ts';
95
96
  export { useDraggable } from './composition/useDraggable';
97
+ export { useComponentProp } from './composition/useComponentProp';
96
98
 
97
99
  /**
98
100
  * Utils/Partials
package/src/types.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import type { ImportFileHandle, Platforma, StorageHandle, PlRef as ModelRef } from '@platforma-sdk/model';
2
- import type { Ref, ComputedRef } from 'vue';
2
+ import type { Ref, ComputedRef, Component } from 'vue';
3
3
  import { maskIcons16 } from './generated/icons-16';
4
4
  import { maskIcons24 } from './generated/icons-24';
5
5
 
@@ -78,6 +78,8 @@ export type ImportedFiles = {
78
78
  files: ImportFileHandle[];
79
79
  };
80
80
 
81
+ export type InferComponentProps<C extends Component> = C extends Component<infer P> ? P : never;
82
+
81
83
  declare global {
82
84
  const platforma: Platforma | undefined;
83
85
  interface Window {