@getdom/studio 0.2.3 → 0.2.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,37 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.2.4 - 2026-10-08
4
+
5
+ Reliable chart hover values and optional sparkline tooltips.
6
+
7
+ ### Added
8
+
9
+ - Opt-in sparkline tooltips with `tooltip`, showing the inspected label and
10
+ formatted series values on hover or keyboard navigation. The floating panel
11
+ stays outside the compact plot and escapes scrolling-table clipping.
12
+ - Website privacy policy and terms pages, linked from checkout and the footer.
13
+
14
+ ### Improved
15
+
16
+ - Documentation explains how component source, runnable examples and reference
17
+ pages live together, and how that structure supports human and AI development.
18
+
19
+ ### Fixed
20
+
21
+ - Line and area chart tooltips update as the pointer moves between data points,
22
+ without requiring a click. Clicking preserves and selects the hovered record.
23
+ - Shared chart bindings forward Unovis configuration using its expected names,
24
+ including crosshair callbacks and stacked-series inspection.
25
+ - Chart values dismiss when the crosshair leaves the plot.
26
+ - Website licence checkout no longer depends on email delivery being configured.
27
+ Existing installations receive a repair migration for missing licence columns.
28
+
29
+ ### Upgrade notes
30
+
31
+ - Sparkline tooltips remain off by default. Add `tooltip` to `DomSparkline` to
32
+ enable them; existing value and label formatters also apply to the tooltip.
33
+ - Email-based licence claiming and recovery still require a configured sender.
34
+
3
35
  ## 0.2.3 - 2026-09-27
4
36
 
5
37
  Searchable Google Fonts previews, shared paginated option lists, and more reliable
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@getdom/studio",
3
- "version": "0.2.3",
3
+ "version": "0.2.4",
4
4
  "description": "A Vue 3 and Web Component interface system for application UI.",
5
5
  "type": "module",
6
6
  "license": "SEE LICENSE IN LICENSE.md",
@@ -0,0 +1,39 @@
1
+ <script setup>
2
+ import { onBeforeUnmount, onMounted, ref } from 'vue';
3
+ import { applyFloatingPosition, autoUpdateFloating } from '../../lib/headless/floating.js';
4
+ import ChartTooltip from './ChartTooltip.vue';
5
+
6
+ const props = defineProps({
7
+ anchor: { type: Object, required: true },
8
+ label: { type: String, default: '' },
9
+ items: { type: Array, default: () => [] },
10
+ });
11
+ const panel = ref(null);
12
+ let stopPositioning;
13
+
14
+ /** Positions the compact tooltip outside the plot and within the viewport. @returns {void} */
15
+ function position() {
16
+ applyFloatingPosition(props.anchor, panel.value, { placement: 'top', align: 'center', offset: 8, padding: 8 });
17
+ }
18
+
19
+ /** Opens in the top layer so table overflow cannot clip chart values. @returns {void} */
20
+ function open() {
21
+ panel.value.showPopover();
22
+ position();
23
+ stopPositioning = autoUpdateFloating(props.anchor, panel.value, position);
24
+ }
25
+
26
+ /** Releases scroll/resize observers when inspection ends or the chart unmounts. @returns {void} */
27
+ function cleanup() {
28
+ stopPositioning?.();
29
+ }
30
+
31
+ onMounted(open);
32
+ onBeforeUnmount(cleanup);
33
+ </script>
34
+
35
+ <template>
36
+ <div ref="panel" popover="manual" class="pointer-events-none m-0 w-max max-w-[calc(100vw-1rem)] overflow-visible border-0 bg-transparent p-0 text-canvas-fg">
37
+ <ChartTooltip :label="label" :items="items" />
38
+ </div>
39
+ </template>
@@ -5,6 +5,7 @@ import { chartPoints, chartSeries, formatChartValue, percentagePoints } from './
5
5
  import ChartContainer from './ChartContainer.vue';
6
6
  import ChartLegend from './ChartLegend.vue';
7
7
  import ChartTooltip from './ChartTooltip.vue';
8
+ import CompactChartTooltip from './CompactChartTooltip.vue';
8
9
 
9
10
  const props = defineProps({
10
11
  ...chartProps,
@@ -13,12 +14,14 @@ const props = defineProps({
13
14
  percentage: { type: Boolean, default: false },
14
15
  horizontal: { type: Boolean, default: false },
15
16
  compact: { type: Boolean, default: false },
17
+ tooltip: { type: Boolean, default: true },
16
18
  });
17
19
  const emit = defineEmits(['retry', 'select']);
18
20
  const renderer = shallowRef(null);
19
21
  const loadError = ref('');
20
22
  const hiddenKeys = ref([]);
21
23
  const inspected = ref(null);
24
+ const plot = ref(null);
22
25
  let disposed = false;
23
26
  const allSeries = computed(() => chartSeries(props.series));
24
27
  const visibleSeries = computed(() => allSeries.value.filter((item) => !hiddenKeys.value.includes(item.key)));
@@ -108,16 +111,18 @@ function scrub(event) { inspected.value = points.value[Number(event.target.value
108
111
  <template>
109
112
  <ChartContainer :title="title" :description="description" :height="safeHeight" :compact="compact" :loading="loading" :error="error || validationError || loadError" :empty="!hasData" :empty-label="emptyLabel" @retry="retry">
110
113
  <div
114
+ ref="plot"
111
115
  class="relative rounded-md focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
112
116
  :style="{ height: `${safeHeight}px` }" tabindex="0" role="group"
113
117
  :aria-label="`${title}. Use arrow keys to inspect values; Enter selects a row.`"
114
- @keydown="navigate" @focus="inspect(points[0])" @blur="inspect(null)" @mouseleave="inspect(null)" @click="selectPoint"
118
+ @keydown="navigate" @focus="inspect(inspected || points[0])" @blur="inspect(null)" @mouseleave="inspect(null)" @click="selectPoint"
115
119
  >
116
120
  <div aria-hidden="true">
117
121
  <component :is="renderer" v-if="renderer" :key="`${kind}-${horizontal}-${stacked}-${percentage}`" :points="renderPoints" :series="visibleSeries" :kind="kind" :height="safeHeight" :curve="curve" :stacked="stacked || percentage" :percentage="percentage" :horizontal="horizontal" :compact="compact" :grid="grid" :x-type="xType" :format-x="formatAxisLabel" :format-y="formatAxisValue" @inspect="inspect" />
118
122
  <div v-else class="h-full w-full" />
119
123
  </div>
120
- <ChartTooltip v-if="inspected && !compact" class="absolute right-2 top-2 z-10" :label="formatLabel(inspected.row[xKey])" :items="tooltipItems" />
124
+ <CompactChartTooltip v-if="inspected && tooltip && compact" :anchor="plot" :label="formatLabel(inspected.row[xKey])" :items="tooltipItems" />
125
+ <ChartTooltip v-else-if="inspected && tooltip" class="absolute right-2 top-2 z-10" :label="formatLabel(inspected.row[xKey])" :items="tooltipItems" />
121
126
  <span class="sr-only" aria-live="polite">{{ inspected ? `${formatLabel(inspected.row[xKey])}: ${tooltipItems.map(item => `${item.label} ${item.value}`).join(', ')}` : '' }}</span>
122
127
  </div>
123
128
  <template #legend>
@@ -56,35 +56,42 @@ function getX(point) { return point.x; }
56
56
  /** @param {unknown} _datum Unovis datum. @param {number} index Series index. @returns {string} Stable colour. */
57
57
  function getColor(_datum, index) { return props.series[index]?.color || 'var(--primary)'; }
58
58
 
59
- /** @param {number} _x Pointer coordinate. @param {object} point Nearest datum. @returns {void} */
60
- function inspectPoint(_x, point) { emit('inspect', point || null); }
59
+ /**
60
+ * Synchronizes inspection with the crosshair, including plot-boundary dismissal.
61
+ * Unovis can retain the nearest datum while clearing the pointer coordinate.
62
+ * @param {number|Date|undefined} x Pointer coordinate; absent when hidden.
63
+ * @param {object|undefined} point Nearest datum.
64
+ * @returns {void}
65
+ */
66
+ function inspectPoint(x, point) { emit('inspect', x == null ? null : point || null); }
61
67
 
62
68
  /** @param {object} datum Bar or stack datum. @returns {void} Inspects horizontal and touch-selected bars. */
63
69
  function inspectBar(datum) { emit('inspect', datum.datum || datum); }
64
70
  </script>
65
71
 
66
72
  <template>
73
+ <!-- Unovis 1.7 forwards undeclared attributes verbatim; config bindings must use camelCase. -->
67
74
  <VisXYContainer
68
75
  :data="points" :height="height" :duration="0" class="block w-full"
69
- :x-domain="horizontal ? valueDomain : undefined" :y-domain="horizontal ? undefined : valueDomain"
70
- :y-direction="horizontal ? 'south' : 'north'"
76
+ :xDomain="horizontal ? valueDomain : undefined" :yDomain="horizontal ? undefined : valueDomain"
77
+ :yDirection="horizontal ? 'south' : 'north'"
71
78
  :margin="compact ? { top: 4, bottom: 4, left: 4, right: 4 } : { top: 12, right: 12 }"
72
79
  >
73
80
  <template v-if="kind === 'line'">
74
- <VisLine v-for="(item, index) in series" :key="item.key" :x="getX" :y="accessors[index]" :color="item.color" :curve-type="curve" :line-width="2" :line-dash-array="item.dashed ? [5, 4] : undefined" :duration="0" />
81
+ <VisLine v-for="(item, index) in series" :key="item.key" :x="getX" :y="accessors[index]" :color="item.color" :curveType="curve" :lineWidth="2" :lineDashArray="item.dashed ? [5, 4] : undefined" :duration="0" />
75
82
  <VisScatter v-if="points.length === 1" :x="getX" :y="accessors" :color="getColor" :size="24" :duration="0" />
76
83
  </template>
77
84
  <template v-else-if="kind === 'area'">
78
- <VisArea v-if="stacked" :x="getX" :y="accessors" :color="getColor" :curve-type="curve" :opacity="0.45" :duration="0" />
79
- <VisArea v-for="(item, index) in stacked ? [] : series" :key="item.key" :x="getX" :y="accessors[index]" :color="item.color" :curve-type="curve" :opacity="0.15" :line="true" :line-color="item.color" :duration="0" />
85
+ <VisArea v-if="stacked" :x="getX" :y="accessors" :color="getColor" :curveType="curve" :opacity="0.45" :duration="0" />
86
+ <VisArea v-for="(item, index) in stacked ? [] : series" :key="item.key" :x="getX" :y="accessors[index]" :color="item.color" :curveType="curve" :opacity="0.15" :line="true" :lineColor="item.color" :duration="0" />
80
87
  </template>
81
88
  <component
82
89
  :is="stacked ? VisStackedBar : VisGroupedBar" v-else
83
90
  :x="getX" :y="accessors" :color="getColor" :orientation="horizontal ? 'horizontal' : 'vertical'"
84
- :bar-padding="stacked ? 0.3 : 0.1" :group-padding="0.3" :rounded-corners="3" :bar-min-height="0" :duration="0" :events="barEvents"
91
+ :barPadding="stacked ? 0.3 : 0.1" :groupPadding="0.3" :roundedCorners="3" :barMinHeight="0" :duration="0" :events="barEvents"
85
92
  />
86
- <VisAxis v-if="!compact" :key="`category-${horizontal}`" :type="horizontal ? 'y' : 'x'" :tick-values="categoryTicks" :tick-format="formatX" :grid-line="false" :domain-line="false" :tick-line="false" :tick-text-width="horizontal ? 90 : 72" :tick-text-hide-overlapping="true" :tick-text-adaptive-sets="true" :duration="0" />
87
- <VisAxis v-if="!compact" :key="`value-${horizontal}`" :type="horizontal ? 'x' : 'y'" :tick-format="formatY" :grid-line="grid" :domain-line="false" :tick-line="false" :tick-text-width="90" :tick-text-hide-overlapping="true" :num-ticks="4" :duration="0" />
88
- <VisCrosshair v-if="!horizontal" :x="getX" :y="stacked ? [] : accessors" :y-stacked="stacked ? accessors : undefined" :color="getColor" :hide-when-far-from-pointer="false" :on-crosshair-move="inspectPoint" :duration="0" />
93
+ <VisAxis v-if="!compact" :key="`category-${horizontal}`" :type="horizontal ? 'y' : 'x'" :tickValues="categoryTicks" :tickFormat="formatX" :gridLine="false" :domainLine="false" :tickLine="false" :tickTextWidth="horizontal ? 90 : 72" :tickTextHideOverlapping="true" :tickTextAdaptiveSets="true" :duration="0" />
94
+ <VisAxis v-if="!compact" :key="`value-${horizontal}`" :type="horizontal ? 'x' : 'y'" :tickFormat="formatY" :gridLine="grid" :domainLine="false" :tickLine="false" :tickTextWidth="90" :tickTextHideOverlapping="true" :numTicks="4" :duration="0" />
95
+ <VisCrosshair v-if="!horizontal" :x="getX" :y="stacked ? [] : accessors" :yStacked="stacked ? accessors : undefined" :color="getColor" :hideWhenFarFromPointer="false" :onCrosshairMove="inspectPoint" :duration="0" />
89
96
  </VisXYContainer>
90
97
  </template>
@@ -21,6 +21,7 @@ const props = defineProps({
21
21
  height: { type: Number, default: 48 },
22
22
  legend: { type: Boolean, default: false },
23
23
  showTable: { type: Boolean, default: false },
24
+ tooltip: { type: Boolean, default: false, _edit: { description: 'Show formatted values on hover or keyboard inspection in a floating tooltip outside the compact plot.' } },
24
25
  });
25
26
  defineEmits(['select', 'retry']);
26
27
  </script>
@@ -7811,6 +7811,10 @@ export interface DomSparklineProps {
7811
7811
  * Line interpolation. Linear avoids implying unobserved smooth changes.
7812
7812
  */
7813
7813
  curve?: "linear" | "monotoneX" | "step";
7814
+ /**
7815
+ * Show formatted values on hover or keyboard inspection in a floating tooltip outside the compact plot.
7816
+ */
7817
+ tooltip?: boolean;
7814
7818
  }
7815
7819
  export declare const DomSparkline: DomComponent<DomSparklineProps>;
7816
7820