frappe-ui 1.0.0-beta.61 → 1.0.0-beta.63
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/experimental/CodeEditor/CodeEditor.api.md +1 -1
- package/experimental/CodeEditor/CodeEditor.md +1 -1
- package/experimental/CodeEditor/CodeEditor.playground.vue +1 -1
- package/experimental/CodeEditor/CodeEditor.vue +2 -2
- package/experimental/CodeEditor/stories/Sizes.vue +1 -1
- package/experimental/CodeEditor/types.ts +1 -1
- package/experimental/MultiEmailInput/MultiEmailInput.cy.ts +27 -0
- package/experimental/MultiEmailInput/MultiEmailInput.vue +9 -1
- package/package.json +1 -1
- package/src/charts/AreaChart.vue +3 -4
- package/src/charts/BarChart.cy.ts +138 -0
- package/src/charts/BarChart.vue +3 -4
- package/src/charts/DonutChart.vue +9 -0
- package/src/charts/FunnelChart.vue +12 -3
- package/src/charts/HeatmapChart.vue +13 -2
- package/src/charts/LineChart.vue +3 -4
- package/src/charts/SankeyChart.vue +9 -0
- package/src/charts/ScatterChart.vue +9 -0
- package/src/charts/areaChartOptions.test.ts +1 -1
- package/src/charts/axisChartCommon.ts +242 -30
- package/src/charts/axisChartOptions.ts +3 -44
- package/src/charts/axisFormat.test.ts +1 -1
- package/src/charts/barChartOptions.test.ts +83 -2
- package/src/charts/categoryAxisFit.test.ts +1 -1
- package/src/charts/chartColors.test.ts +1 -1
- package/src/charts/comboChartOptions.test.ts +75 -7
- package/src/charts/components/ChartTooltip.vue +33 -3
- package/src/charts/core/useAxisChart.ts +43 -26
- package/src/charts/core/useChart.test.ts +32 -0
- package/src/charts/core/useChart.ts +28 -1
- package/src/charts/core/useTooltipDismiss.ts +48 -0
- package/src/charts/docs/AreaChart.api.md +13 -7
- package/src/charts/docs/AreaChart.md +8 -2
- package/src/charts/docs/BarChart.api.md +13 -7
- package/src/charts/docs/BarChart.md +18 -0
- package/src/charts/docs/ChartTooltip.api.md +7 -1
- package/src/charts/docs/LineChart.api.md +13 -7
- package/src/charts/docs/LineChart.md +46 -11
- package/src/charts/docs/Overview.md +1 -1
- package/src/charts/docs/ScatterChart.md +3 -0
- package/src/charts/donutChartOptions.test.ts +1 -1
- package/src/charts/heatmapOptions.test.ts +3 -3
- package/src/charts/heatmapOptions.ts +2 -2
- package/src/charts/index.ts +2 -0
- package/src/charts/lineChartOptions.test.ts +1 -1
- package/src/charts/normalizedStackOptions.test.ts +1 -1
- package/src/charts/referenceLines.test.ts +62 -14
- package/src/charts/referenceLines.ts +72 -24
- package/src/charts/sankeyOptions.test.ts +1 -1
- package/src/charts/scatterOptions.test.ts +7 -7
- package/src/charts/seriesData.test.ts +2 -2
- package/src/charts/seriesData.ts +52 -1
- package/src/charts/stories/BarCombo.vue +0 -1
- package/src/charts/stories/LineTooltipColumns.vue +44 -0
- package/src/charts/style.css +16 -12
- package/src/charts/tokens.test.ts +56 -0
- package/src/charts/tokens.ts +56 -12
- package/src/charts/tooltipItems.test.ts +261 -0
- package/src/charts/tooltipItems.ts +70 -0
- package/src/charts/types.ts +63 -7
- package/src/charts/valueAxisOptions.test.ts +1 -1
- package/src/components/Checkbox/Checkbox.cy.ts +3 -3
- package/src/components/Combobox/Combobox.api.md +2 -2
- package/src/components/Combobox/Combobox.playground.vue +1 -1
- package/src/components/Combobox/Combobox.vue +5 -0
- package/src/components/Combobox/stories/Clearable.vue +5 -5
- package/src/components/Combobox/types.ts +9 -8
- package/src/components/ContextMenu/index.ts +1 -0
- package/src/components/ContextMenu/types.ts +16 -0
- package/src/components/DesktopShell/DesktopShell.api.md +1 -1
- package/src/components/DesktopShell/DesktopShell.md +6 -5
- package/src/components/DesktopShell/DesktopShell.vue +1 -1
- package/src/components/DesktopShell/stories/Default.vue +8 -8
- package/src/components/Divider/Divider.api.md +4 -4
- package/src/components/Divider/index.ts +1 -1
- package/src/components/Divider/types.ts +24 -1
- package/src/components/Duration/Duration.playground.vue +1 -1
- package/src/components/Duration/stories/Sizes.vue +1 -1
- package/src/components/FormControl/FormControl.api.md +2 -2
- package/src/components/FormControl/FormControl.cy.ts +40 -0
- package/src/components/FormControl/FormControl.playground.vue +2 -0
- package/src/components/FormControl/FormControl.vue +6 -1
- package/src/components/FormControl/types.ts +7 -2
- package/src/components/FormLabel/FormLabel.api.md +0 -7
- package/src/components/FormLabel/FormLabel.cy.ts +24 -5
- package/src/components/FormLabel/FormLabel.playground.vue +1 -11
- package/src/components/FormLabel/FormLabel.vue +7 -12
- package/src/components/FormLabel/types.ts +0 -3
- package/src/components/HoverCard/HoverCard.api.md +1 -1
- package/src/components/HoverCard/types.ts +3 -0
- package/src/components/InputLabeling/InputDescription.vue +1 -1
- package/src/components/InputLabeling/InputLabel.vue +9 -4
- package/src/components/ItemListRow/ItemListRow.api.md +1 -1
- package/src/components/ItemListRow/ItemListRow.vue +13 -7
- package/src/components/ItemListRow/types.ts +7 -1
- package/src/components/KeyboardShortcutsDialog/KeyboardShortcutsDialog.api.md +1 -1
- package/src/components/KeyboardShortcutsDialog/types.ts +3 -0
- package/src/components/MultiSelect/MultiSelect.api.md +3 -3
- package/src/components/MultiSelect/MultiSelect.playground.vue +1 -1
- package/src/components/MultiSelect/MultiSelect.vue +5 -2
- package/src/components/MultiSelect/types.ts +4 -8
- package/src/components/Password/Password.playground.vue +1 -1
- package/src/components/Password/stories/Sizes.vue +1 -1
- package/src/components/Rating/Rating.cy.ts +1 -1
- package/src/components/Rating/Rating.playground.vue +1 -1
- package/src/components/Rating/Rating.vue +14 -8
- package/src/components/Rating/stories/Sizes.vue +1 -1
- package/src/components/Select/Select.api.md +2 -2
- package/src/components/Select/Select.cy.ts +19 -1
- package/src/components/Select/Select.playground.vue +1 -1
- package/src/components/Select/types.ts +3 -2
- package/src/components/Select/utils.ts +12 -6
- package/src/components/{Rail/Rail.api.md → SidebarRail/SidebarRail.api.md} +11 -11
- package/src/components/{Rail/Rail.cy.ts → SidebarRail/SidebarRail.cy.ts} +31 -31
- package/src/components/SidebarRail/SidebarRail.md +36 -0
- package/src/components/{Rail/Rail.vue → SidebarRail/SidebarRail.vue} +2 -2
- package/src/components/{Rail/RailItem.vue → SidebarRail/SidebarRailItem.vue} +12 -12
- package/src/components/{Rail/RailItemBadge.vue → SidebarRail/SidebarRailItemBadge.vue} +1 -1
- package/src/components/SidebarRail/index.ts +4 -0
- package/src/components/{Rail → SidebarRail}/stories/Default.vue +10 -10
- package/src/components/{Rail → SidebarRail}/types.ts +1 -1
- package/src/components/Switch/Switch.cy.ts +2 -2
- package/src/components/TabButtons/TabButtons.api.md +4 -4
- package/src/components/TabButtons/TabButtons.vue +3 -0
- package/src/components/TabButtons/index.ts +1 -0
- package/src/components/TabButtons/types.ts +7 -0
- package/src/components/TextInput/TextInput.cy.ts +108 -4
- package/src/components/TextInput/TextInput.playground.vue +1 -1
- package/src/components/TextInput/TextInput.vue +60 -26
- package/src/components/TextInput/stories/Sizes.vue +1 -1
- package/src/components/Textarea/Textarea.api.md +1 -1
- package/src/components/Textarea/Textarea.cy.ts +65 -34
- package/src/components/Textarea/Textarea.playground.vue +1 -1
- package/src/components/Textarea/Textarea.vue +39 -14
- package/src/components/Textarea/stories/Sizes.vue +1 -1
- package/src/components/Textarea/types.ts +5 -1
- package/src/components/TimePicker/stories/SizesAndVariants.vue +1 -1
- package/src/components/Tree/Tree.vue +2 -6
- package/src/components/Tree/types.ts +8 -0
- package/src/components/shared/picker/PickerShell.vue +34 -37
- package/src/components/shared/selection/utils.test.ts +54 -2
- package/src/components/shared/selection/utils.ts +64 -21
- package/src/components/shared/tabs/styles.ts +1 -1
- package/src/composables/inputTypes.ts +14 -2
- package/src/composables/usePortalTarget.spec.ts +6 -5
- package/src/index.ts +12 -1
- package/src/molecules/list/List.cy.ts +426 -0
- package/src/molecules/list/List.ssr.test.ts +59 -0
- package/src/molecules/list/List.vue +92 -7
- package/src/molecules/list/index.ts +2 -0
- package/src/molecules/list/list-context.ts +2 -1
- package/src/molecules/list/list.api.md +3 -3
- package/src/molecules/list/list.md +152 -33
- package/src/molecules/list/stories/Columns.vue +15 -9
- package/src/molecules/list/stories/Responsive.vue +56 -0
- package/src/molecules/list/stories/RowActions.vue +123 -0
- package/src/molecules/list/style.css +67 -26
- package/src/molecules/list/types.ts +51 -9
- package/tailwind/listColumns.js +191 -0
- package/tailwind/listColumns.test.js +358 -0
- package/tailwind/plugin.js +5 -6
- package/src/components/Rail/Rail.md +0 -30
- package/src/components/Rail/index.ts +0 -2
|
@@ -41,7 +41,7 @@
|
|
|
41
41
|
},
|
|
42
42
|
{
|
|
43
43
|
name: 'size',
|
|
44
|
-
description: 'Size token, mirroring frappe-ui inputs. Scales the editor\'s font size and\nminimum height (`sm | md | lg
|
|
44
|
+
description: 'Size token, mirroring frappe-ui inputs. Scales the editor\'s font size and\nminimum height (`xs | sm | md | lg`). Defaults to `md`.',
|
|
45
45
|
required: false,
|
|
46
46
|
type: 'InputSize',
|
|
47
47
|
default: '"md"'
|
|
@@ -40,7 +40,7 @@ and loses the affordance that it's an editable field.
|
|
|
40
40
|
|
|
41
41
|
## Sizes
|
|
42
42
|
|
|
43
|
-
`size` mirrors the frappe-ui input sizes (`sm | md | lg
|
|
43
|
+
`size` mirrors the frappe-ui input sizes (`xs | sm | md | lg`, default `md`),
|
|
44
44
|
scaling the editor's font size and minimum height.
|
|
45
45
|
|
|
46
46
|
<ComponentPreview name="CodeEditor-Sizes" />
|
|
@@ -43,10 +43,10 @@ const knobs: Knob[] = [
|
|
|
43
43
|
type: 'tabs',
|
|
44
44
|
default: 'md',
|
|
45
45
|
options: [
|
|
46
|
+
{ label: 'xs', value: 'xs' },
|
|
46
47
|
{ label: 'sm', value: 'sm' },
|
|
47
48
|
{ label: 'md', value: 'md' },
|
|
48
49
|
{ label: 'lg', value: 'lg' },
|
|
49
|
-
{ label: 'xl', value: 'xl' },
|
|
50
50
|
],
|
|
51
51
|
},
|
|
52
52
|
{ name: 'placeholder', type: 'text', default: '', width: '12rem' },
|
|
@@ -288,17 +288,17 @@ function buildVariant(v?: string, disabled?: boolean): Extension {
|
|
|
288
288
|
return cmView.EditorView.theme(fragments[v ?? 'subtle'] ?? fragments.subtle)
|
|
289
289
|
}
|
|
290
290
|
|
|
291
|
-
// Per-size font scale + content min-height, mirroring frappe-ui's `sm|md|lg
|
|
291
|
+
// Per-size font scale + content min-height, mirroring frappe-ui's `xs|sm|md|lg`
|
|
292
292
|
// input sizes. This compartment is the SOLE source of `&` font size and
|
|
293
293
|
// `.cm-content` min-height — the base `theme` deliberately omits both so there's
|
|
294
294
|
// no equal-specificity fight whose winner depends on StyleModule order.
|
|
295
295
|
function buildSize(s?: string): Extension {
|
|
296
296
|
if (!cmView) return []
|
|
297
297
|
const sizes: Record<string, { fontSize: string; minHeight: string }> = {
|
|
298
|
+
xs: { fontSize: '11px', minHeight: '2.5rem' },
|
|
298
299
|
sm: { fontSize: '12px', minHeight: '3.5rem' },
|
|
299
300
|
md: { fontSize: '13px', minHeight: '4.5rem' },
|
|
300
301
|
lg: { fontSize: '14px', minHeight: '6rem' },
|
|
301
|
-
xl: { fontSize: '16px', minHeight: '7.5rem' },
|
|
302
302
|
}
|
|
303
303
|
const cfg = sizes[s ?? 'md'] ?? sizes.md
|
|
304
304
|
return cmView.EditorView.theme({
|
|
@@ -7,9 +7,9 @@ const code = ref(`const sum = (a, b) => a + b`)
|
|
|
7
7
|
|
|
8
8
|
<template>
|
|
9
9
|
<div class="w-full max-w-xl space-y-4">
|
|
10
|
+
<CodeEditor v-model="code" language="javascript" size="xs" />
|
|
10
11
|
<CodeEditor v-model="code" language="javascript" size="sm" />
|
|
11
12
|
<CodeEditor v-model="code" language="javascript" size="md" />
|
|
12
13
|
<CodeEditor v-model="code" language="javascript" size="lg" />
|
|
13
|
-
<CodeEditor v-model="code" language="javascript" size="xl" />
|
|
14
14
|
</div>
|
|
15
15
|
</template>
|
|
@@ -42,7 +42,7 @@ export interface CodeEditorProps extends InputLabelingProps {
|
|
|
42
42
|
variant?: Exclude<InputVariant, 'ghost'>
|
|
43
43
|
/**
|
|
44
44
|
* Size token, mirroring frappe-ui inputs. Scales the editor's font size and
|
|
45
|
-
* minimum height (`sm | md | lg
|
|
45
|
+
* minimum height (`xs | sm | md | lg`). Defaults to `md`.
|
|
46
46
|
*/
|
|
47
47
|
size?: InputSize
|
|
48
48
|
}
|
|
@@ -17,6 +17,7 @@ function host(
|
|
|
17
17
|
required?: boolean
|
|
18
18
|
error?: string
|
|
19
19
|
disabled?: boolean
|
|
20
|
+
size?: 'xs' | 'sm' | 'md' | 'lg'
|
|
20
21
|
} = {},
|
|
21
22
|
) {
|
|
22
23
|
return defineComponent({
|
|
@@ -46,6 +47,7 @@ function host(
|
|
|
46
47
|
required: options.required,
|
|
47
48
|
error: options.error,
|
|
48
49
|
disabled: options.disabled,
|
|
50
|
+
size: options.size,
|
|
49
51
|
placeholder: 'Add email…',
|
|
50
52
|
}),
|
|
51
53
|
]
|
|
@@ -152,4 +154,29 @@ describe('MultiEmailInput', () => {
|
|
|
152
154
|
cy.mount(host({ disabled: true }))
|
|
153
155
|
cy.get('[data-slot="input"]').should('have.attr', 'disabled')
|
|
154
156
|
})
|
|
157
|
+
|
|
158
|
+
// The option-row avatar has to shrink with the row. `md` (24px) is the whole
|
|
159
|
+
// height of an `xs` row before its padding, so a size-invariant avatar makes
|
|
160
|
+
// the smallest control draw the biggest picture.
|
|
161
|
+
const rowAvatarHeights: Array<[size: 'xs' | 'sm' | 'md' | 'lg', px: number]> =
|
|
162
|
+
[
|
|
163
|
+
['xs', 16],
|
|
164
|
+
['sm', 20],
|
|
165
|
+
['md', 24],
|
|
166
|
+
['lg', 24],
|
|
167
|
+
]
|
|
168
|
+
|
|
169
|
+
for (const [size, px] of rowAvatarHeights) {
|
|
170
|
+
it(`draws a ${px}px option-row avatar at size="${size}"`, () => {
|
|
171
|
+
cy.mount(host({ size }))
|
|
172
|
+
cy.get('[data-slot="input"]').click()
|
|
173
|
+
cy.get('[data-slot="item"]')
|
|
174
|
+
.first()
|
|
175
|
+
.find('[data-slot="item-prefix"] > div')
|
|
176
|
+
.first()
|
|
177
|
+
.then(($avatar) => {
|
|
178
|
+
expect($avatar[0].getBoundingClientRect().height).to.equal(px)
|
|
179
|
+
})
|
|
180
|
+
})
|
|
181
|
+
}
|
|
155
182
|
})
|
|
@@ -166,7 +166,15 @@ const showEmpty = computed(
|
|
|
166
166
|
() => !props.loading && !suggestions.value.length && !showCreateOption.value,
|
|
167
167
|
)
|
|
168
168
|
|
|
169
|
-
|
|
169
|
+
// Avatar has its own scale (`xs` 16px, `sm` 20px, `md` 24px), and the option
|
|
170
|
+
// row is `min-h-6`/`min-h-7`/`min-h-8`/`min-h-10` plus `ItemListRow`'s own
|
|
171
|
+
// padding. Step the avatar down with the row for the two small sizes rather
|
|
172
|
+
// than forwarding a size the row cannot draw — a `md` avatar is 24px, the
|
|
173
|
+
// whole height of an `xs` row before its `py-1`. `md` and `lg` rows both fit
|
|
174
|
+
// `md`; a 28px `lg` avatar would leave no room in a 32px `md` row.
|
|
175
|
+
const rowAvatarSize = computed(() =>
|
|
176
|
+
props.size === 'xs' ? 'xs' : props.size === 'sm' ? 'sm' : 'md',
|
|
177
|
+
)
|
|
170
178
|
|
|
171
179
|
const boxClasses = computed(() => [
|
|
172
180
|
'flex flex-wrap items-center gap-1.5 rounded-4 bg-surface-gray-2 px-1.5 py-1 transition-colors focus-within:ring-2 focus-within:ring-outline-gray-3',
|
package/package.json
CHANGED
package/src/charts/AreaChart.vue
CHANGED
|
@@ -40,6 +40,7 @@
|
|
|
40
40
|
:y="tooltip.y"
|
|
41
41
|
:label="tooltip.label"
|
|
42
42
|
:items="tooltip.items"
|
|
43
|
+
:row="tooltip.row"
|
|
43
44
|
:dir="dir"
|
|
44
45
|
>
|
|
45
46
|
<template v-if="$slots.tooltip" #default="slotProps">
|
|
@@ -49,10 +50,7 @@
|
|
|
49
50
|
</template>
|
|
50
51
|
|
|
51
52
|
<template v-if="legendItems.length > 1" #legend>
|
|
52
|
-
<ChartLegend
|
|
53
|
-
:items="legendItems"
|
|
54
|
-
@change="toggleSeries"
|
|
55
|
-
/>
|
|
53
|
+
<ChartLegend :items="legendItems" @change="toggleSeries" />
|
|
56
54
|
</template>
|
|
57
55
|
</ChartContainer>
|
|
58
56
|
</template>
|
|
@@ -130,6 +128,7 @@ const {
|
|
|
130
128
|
format: () => normalized.value.format,
|
|
131
129
|
buildOption: buildAxisChartOption,
|
|
132
130
|
stackShares: () => buildStackShares(config.value, hiddenSeries.value),
|
|
131
|
+
tooltipColumns: () => normalized.value.tooltipColumns,
|
|
133
132
|
hiddenSeries,
|
|
134
133
|
onSelect: (event) => emit('select', event),
|
|
135
134
|
})
|
|
@@ -98,6 +98,56 @@ describe('BarChart', () => {
|
|
|
98
98
|
.and('contain.text', 'Sales')
|
|
99
99
|
.and('contain.text', '10')
|
|
100
100
|
})
|
|
101
|
+
|
|
102
|
+
// `tooltipColumns` is the context in another unit — the count behind a
|
|
103
|
+
// rate. It reaches the tooltip and nothing else.
|
|
104
|
+
describe('a tooltip-only column', () => {
|
|
105
|
+
const withOrders = data.map((row, index) => ({
|
|
106
|
+
...row,
|
|
107
|
+
orders: (index + 1) * 1000,
|
|
108
|
+
}))
|
|
109
|
+
|
|
110
|
+
const mountWithExtra = (props: Record<string, any> = {}) =>
|
|
111
|
+
mountChart({
|
|
112
|
+
data: withOrders,
|
|
113
|
+
tooltipColumns: [{ name: 'orders', label: 'Orders' }],
|
|
114
|
+
...props,
|
|
115
|
+
})
|
|
116
|
+
|
|
117
|
+
it('prints in the tooltip', () => {
|
|
118
|
+
mountWithExtra()
|
|
119
|
+
cy.get('[data-slot="chart-plot"]').trigger('mousemove', 100, 150)
|
|
120
|
+
cy.get('[data-slot="chart-tooltip"]')
|
|
121
|
+
.should('contain.text', 'Orders')
|
|
122
|
+
.and('contain.text', '1,000')
|
|
123
|
+
})
|
|
124
|
+
|
|
125
|
+
it('carries no swatch, which would claim a mark on the plot', () => {
|
|
126
|
+
mountWithExtra()
|
|
127
|
+
cy.get('[data-slot="chart-plot"]').trigger('mousemove', 100, 150)
|
|
128
|
+
// Two series, and the column adds no third.
|
|
129
|
+
cy.get('[data-slot="chart-tooltip"] .size-2').should('have.length', 2)
|
|
130
|
+
})
|
|
131
|
+
|
|
132
|
+
it('draws no mark and takes no legend entry', () => {
|
|
133
|
+
mountWithExtra()
|
|
134
|
+
bars().should('have.length', data.length * 2)
|
|
135
|
+
cy.get('[data-slot="chart-legend"] button').should('have.length', 2)
|
|
136
|
+
cy.get('[data-slot="chart-legend"]').should(
|
|
137
|
+
'not.contain.text',
|
|
138
|
+
'Orders',
|
|
139
|
+
)
|
|
140
|
+
})
|
|
141
|
+
|
|
142
|
+
it('stays in the tooltip when the legend hides a series', () => {
|
|
143
|
+
mountWithExtra()
|
|
144
|
+
cy.get('[aria-label="Hide Sales"]').click()
|
|
145
|
+
cy.get('[data-slot="chart-plot"]').trigger('mousemove', 100, 150)
|
|
146
|
+
cy.get('[data-slot="chart-tooltip"]')
|
|
147
|
+
.should('not.contain.text', 'Sales')
|
|
148
|
+
.and('contain.text', 'Orders')
|
|
149
|
+
})
|
|
150
|
+
})
|
|
101
151
|
})
|
|
102
152
|
|
|
103
153
|
describe('legend', () => {
|
|
@@ -492,6 +542,94 @@ describe('BarChart', () => {
|
|
|
492
542
|
.should('contain.text', 'at Jan: 2')
|
|
493
543
|
.and('not.contain.text', 'Sales')
|
|
494
544
|
})
|
|
545
|
+
|
|
546
|
+
describe('dismissal', () => {
|
|
547
|
+
function openTooltip() {
|
|
548
|
+
mountChart()
|
|
549
|
+
bars().should('have.length', data.length * 2)
|
|
550
|
+
plot().trigger('mousemove', 100, 150)
|
|
551
|
+
cy.get('[data-slot="chart-tooltip"]').should('exist')
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
it('closes when the page scrolls', () => {
|
|
555
|
+
openTooltip()
|
|
556
|
+
// Dispatched on the plot, not the window: a scroll event does not
|
|
557
|
+
// bubble, so this reaches the listener only through the capture phase.
|
|
558
|
+
// That is how a scroll inside a nested container arrives.
|
|
559
|
+
plot().then(($el) => $el[0].dispatchEvent(new Event('scroll')))
|
|
560
|
+
cy.get('[data-slot="chart-tooltip"]').should('not.exist')
|
|
561
|
+
})
|
|
562
|
+
|
|
563
|
+
it('closes when the window resizes', () => {
|
|
564
|
+
openTooltip()
|
|
565
|
+
cy.window().then((win) => win.dispatchEvent(new Event('resize')))
|
|
566
|
+
cy.get('[data-slot="chart-tooltip"]').should('not.exist')
|
|
567
|
+
})
|
|
568
|
+
|
|
569
|
+
it('closes when the pointer leaves the plot', () => {
|
|
570
|
+
openTooltip()
|
|
571
|
+
plot().trigger('pointerleave')
|
|
572
|
+
cy.get('[data-slot="chart-tooltip"]').should('not.exist')
|
|
573
|
+
})
|
|
574
|
+
|
|
575
|
+
it('closes when the window goes away', () => {
|
|
576
|
+
openTooltip()
|
|
577
|
+
cy.window().then((win) => win.dispatchEvent(new Event('blur')))
|
|
578
|
+
cy.get('[data-slot="chart-tooltip"]').should('not.exist')
|
|
579
|
+
})
|
|
580
|
+
|
|
581
|
+
it('closes when the tab is hidden', () => {
|
|
582
|
+
openTooltip()
|
|
583
|
+
cy.document().then((doc) => {
|
|
584
|
+
Object.defineProperty(doc, 'hidden', {
|
|
585
|
+
value: true,
|
|
586
|
+
configurable: true,
|
|
587
|
+
})
|
|
588
|
+
doc.dispatchEvent(new Event('visibilitychange'))
|
|
589
|
+
})
|
|
590
|
+
cy.get('[data-slot="chart-tooltip"]').should('not.exist')
|
|
591
|
+
})
|
|
592
|
+
|
|
593
|
+
it('closes when the rows behind it change', () => {
|
|
594
|
+
const rows = ref(data)
|
|
595
|
+
cy.mount(
|
|
596
|
+
defineComponent({
|
|
597
|
+
setup() {
|
|
598
|
+
return () =>
|
|
599
|
+
h('div', { style: 'width: 480px; height: 300px' }, [
|
|
600
|
+
h(BarChart, {
|
|
601
|
+
data: rows.value,
|
|
602
|
+
x: 'month',
|
|
603
|
+
y: ['sales', 'refunds'],
|
|
604
|
+
echartOptions: { animation: false },
|
|
605
|
+
}),
|
|
606
|
+
])
|
|
607
|
+
},
|
|
608
|
+
}),
|
|
609
|
+
)
|
|
610
|
+
bars().should('have.length', data.length * 2)
|
|
611
|
+
plot().trigger('mousemove', 100, 150)
|
|
612
|
+
cy.get('[data-slot="chart-tooltip"]').should('exist')
|
|
613
|
+
cy.then(() => {
|
|
614
|
+
rows.value = data.map((row) => ({ ...row, sales: row.sales * 2 }))
|
|
615
|
+
})
|
|
616
|
+
cy.get('[data-slot="chart-tooltip"]').should('not.exist')
|
|
617
|
+
})
|
|
618
|
+
})
|
|
619
|
+
|
|
620
|
+
it('hands the plotted row to the tooltip slot', () => {
|
|
621
|
+
// The row carries every column, `tooltipColumns` or not: a tooltip the
|
|
622
|
+
// app draws itself needs no prop to reach one.
|
|
623
|
+
mountChart({ data: data.map((row) => ({ ...row, orders: 1000 })) }, {
|
|
624
|
+
tooltip: ({ row }: any) => h('span', `${row.orders} orders`),
|
|
625
|
+
} as any)
|
|
626
|
+
bars().should('have.length', data.length * 2)
|
|
627
|
+
plot().focus()
|
|
628
|
+
cy.get('[data-slot="chart-tooltip"]').should(
|
|
629
|
+
'contain.text',
|
|
630
|
+
'1000 orders',
|
|
631
|
+
)
|
|
632
|
+
})
|
|
495
633
|
})
|
|
496
634
|
|
|
497
635
|
// echarts draws into one element, so the plot takes a single tab stop and the
|
package/src/charts/BarChart.vue
CHANGED
|
@@ -41,6 +41,7 @@
|
|
|
41
41
|
:y="tooltip.y"
|
|
42
42
|
:label="tooltip.label"
|
|
43
43
|
:items="tooltip.items"
|
|
44
|
+
:row="tooltip.row"
|
|
44
45
|
:dir="dir"
|
|
45
46
|
>
|
|
46
47
|
<template v-if="$slots.tooltip" #default="slotProps">
|
|
@@ -50,10 +51,7 @@
|
|
|
50
51
|
</template>
|
|
51
52
|
|
|
52
53
|
<template v-if="legendItems.length > 1" #legend>
|
|
53
|
-
<ChartLegend
|
|
54
|
-
:items="legendItems"
|
|
55
|
-
@change="toggleSeries"
|
|
56
|
-
/>
|
|
54
|
+
<ChartLegend :items="legendItems" @change="toggleSeries" />
|
|
57
55
|
</template>
|
|
58
56
|
</ChartContainer>
|
|
59
57
|
</template>
|
|
@@ -133,6 +131,7 @@ const {
|
|
|
133
131
|
format: () => normalized.value.format,
|
|
134
132
|
buildOption: buildAxisChartOption,
|
|
135
133
|
stackShares: () => buildStackShares(config.value, hiddenSeries.value),
|
|
134
|
+
tooltipColumns: () => normalized.value.tooltipColumns,
|
|
136
135
|
horizontal: () => Boolean(props.horizontal),
|
|
137
136
|
hiddenSeries,
|
|
138
137
|
onSelect: (event) => emit('select', event),
|
|
@@ -105,6 +105,7 @@
|
|
|
105
105
|
import { computed, reactive, ref, watch } from 'vue'
|
|
106
106
|
import { PieChart as PieSeries } from 'echarts/charts'
|
|
107
107
|
import { registerChartModules, useChart } from './core/useChart'
|
|
108
|
+
import { useTooltipDismiss } from './core/useTooltipDismiss'
|
|
108
109
|
import { usePlotKeyboard } from './core/usePlotKeyboard'
|
|
109
110
|
import {
|
|
110
111
|
buildDonutChartOption,
|
|
@@ -227,6 +228,14 @@ const tooltip = reactive({
|
|
|
227
228
|
items: [] as ChartTooltipItem[],
|
|
228
229
|
})
|
|
229
230
|
|
|
231
|
+
useTooltipDismiss({
|
|
232
|
+
plot: plotEl,
|
|
233
|
+
data: () => visibleSlices.value,
|
|
234
|
+
close: () => {
|
|
235
|
+
tooltip.open = false
|
|
236
|
+
},
|
|
237
|
+
})
|
|
238
|
+
|
|
230
239
|
/** Slice under the pointer, or under the hovered legend entry. */
|
|
231
240
|
const hovered = ref<string | null>(null)
|
|
232
241
|
|
|
@@ -38,14 +38,14 @@
|
|
|
38
38
|
/>
|
|
39
39
|
</div>
|
|
40
40
|
|
|
41
|
-
<div class="relative mb-
|
|
41
|
+
<div class="relative mb-6 grid" :style="gridStyle">
|
|
42
42
|
<div
|
|
43
43
|
v-for="stage in stages"
|
|
44
44
|
:key="stage.index"
|
|
45
|
-
class="min-w-0"
|
|
45
|
+
class="flex min-w-0 flex-col gap-1"
|
|
46
46
|
:class="stage.index === 0 ? 'pe-3' : 'px-3'"
|
|
47
47
|
>
|
|
48
|
-
<div class="truncate text-
|
|
48
|
+
<div class="truncate text-sm text-ink-gray-5">
|
|
49
49
|
{{ stage.label }}
|
|
50
50
|
</div>
|
|
51
51
|
<div
|
|
@@ -164,6 +164,7 @@
|
|
|
164
164
|
import { computed, reactive, ref } from 'vue'
|
|
165
165
|
import { formatLabel, formatPercent, formatValue } from './format'
|
|
166
166
|
import { buildFunnelStages, funnelShapes } from './funnelGeometry'
|
|
167
|
+
import { useTooltipDismiss } from './core/useTooltipDismiss'
|
|
167
168
|
import { chartColors, useChartTokens } from './tokens'
|
|
168
169
|
import { documentDir } from './utils'
|
|
169
170
|
import ChartContainer from './components/ChartContainer.vue'
|
|
@@ -248,6 +249,14 @@ const tooltip = reactive({
|
|
|
248
249
|
rates: [] as { label: string; value: string }[],
|
|
249
250
|
})
|
|
250
251
|
|
|
252
|
+
useTooltipDismiss({
|
|
253
|
+
plot: root,
|
|
254
|
+
data: () => stages.value,
|
|
255
|
+
close: () => {
|
|
256
|
+
tooltip.open = false
|
|
257
|
+
},
|
|
258
|
+
})
|
|
259
|
+
|
|
251
260
|
function hover(stage: FunnelStage, event: MouseEvent) {
|
|
252
261
|
readStage(stage)
|
|
253
262
|
track(event)
|
|
@@ -93,6 +93,7 @@ import { GridComponent, VisualMapContinuousComponent } from 'echarts/components'
|
|
|
93
93
|
import { LabelLayout } from 'echarts/features'
|
|
94
94
|
import { registerChartModules, useChart } from './core/useChart'
|
|
95
95
|
import { usePlotKeyboard } from './core/usePlotKeyboard'
|
|
96
|
+
import { useTooltipDismiss } from './core/useTooltipDismiss'
|
|
96
97
|
import { AXIS_LABEL_MARGIN } from './axisChartCommon'
|
|
97
98
|
import {
|
|
98
99
|
buildHeatmapMatrix,
|
|
@@ -188,6 +189,14 @@ const tooltip = reactive({
|
|
|
188
189
|
items: [] as ChartTooltipItem[],
|
|
189
190
|
})
|
|
190
191
|
|
|
192
|
+
useTooltipDismiss({
|
|
193
|
+
plot: plotEl,
|
|
194
|
+
data: () => matrix.value.cells,
|
|
195
|
+
close: () => {
|
|
196
|
+
tooltip.open = false
|
|
197
|
+
},
|
|
198
|
+
})
|
|
199
|
+
|
|
191
200
|
/**
|
|
192
201
|
* How much of the plot box sits below the axis line: the row echarts reserved
|
|
193
202
|
* for the x-axis labels, in px.
|
|
@@ -335,8 +344,10 @@ function downplayCell(index: number | null) {
|
|
|
335
344
|
const keyboard = usePlotKeyboard({
|
|
336
345
|
marks: () => matrix.value.cells,
|
|
337
346
|
// The pair of categories names the cell, whatever order the grid ends up in
|
|
338
|
-
// and whichever refetch built the rows.
|
|
339
|
-
|
|
347
|
+
// and whichever refetch built the rows. NUL joins them because no category
|
|
348
|
+
// can hold one, so no pair of values can collide on the seam. Written as an
|
|
349
|
+
// escape: a literal NUL makes every text tool read this file as binary.
|
|
350
|
+
key: (cell) => `${cell.y}\u0000${cell.x}`,
|
|
340
351
|
move: (index, previous) => {
|
|
341
352
|
downplayCell(previous)
|
|
342
353
|
readCell(index)
|
package/src/charts/LineChart.vue
CHANGED
|
@@ -40,6 +40,7 @@
|
|
|
40
40
|
:y="tooltip.y"
|
|
41
41
|
:label="tooltip.label"
|
|
42
42
|
:items="tooltip.items"
|
|
43
|
+
:row="tooltip.row"
|
|
43
44
|
:dir="dir"
|
|
44
45
|
>
|
|
45
46
|
<template v-if="$slots.tooltip" #default="slotProps">
|
|
@@ -49,10 +50,7 @@
|
|
|
49
50
|
</template>
|
|
50
51
|
|
|
51
52
|
<template v-if="legendItems.length > 1" #legend>
|
|
52
|
-
<ChartLegend
|
|
53
|
-
:items="legendItems"
|
|
54
|
-
@change="toggleSeries"
|
|
55
|
-
/>
|
|
53
|
+
<ChartLegend :items="legendItems" @change="toggleSeries" />
|
|
56
54
|
</template>
|
|
57
55
|
</ChartContainer>
|
|
58
56
|
</template>
|
|
@@ -130,6 +128,7 @@ const {
|
|
|
130
128
|
format: () => normalized.value.format,
|
|
131
129
|
buildOption: buildAxisChartOption,
|
|
132
130
|
stackShares: () => buildStackShares(config.value, hiddenSeries.value),
|
|
131
|
+
tooltipColumns: () => normalized.value.tooltipColumns,
|
|
133
132
|
hiddenSeries,
|
|
134
133
|
onSelect: (event) => emit('select', event),
|
|
135
134
|
})
|
|
@@ -53,6 +53,7 @@ import { computed, reactive, ref } from 'vue'
|
|
|
53
53
|
import { SankeyChart as SankeySeries } from 'echarts/charts'
|
|
54
54
|
import { registerChartModules, useChart } from './core/useChart'
|
|
55
55
|
import { usePlotKeyboard } from './core/usePlotKeyboard'
|
|
56
|
+
import { useTooltipDismiss } from './core/useTooltipDismiss'
|
|
56
57
|
import { buildSankeyGraph, buildSankeyOption } from './sankeyOptions'
|
|
57
58
|
import { formatLabel, formatValue } from './format'
|
|
58
59
|
import { useChartTokens } from './tokens'
|
|
@@ -143,6 +144,14 @@ const tooltip = reactive({
|
|
|
143
144
|
items: [] as ChartTooltipItem[],
|
|
144
145
|
})
|
|
145
146
|
|
|
147
|
+
useTooltipDismiss({
|
|
148
|
+
plot: plotEl,
|
|
149
|
+
data: () => graph.value.links,
|
|
150
|
+
close: () => {
|
|
151
|
+
tooltip.open = false
|
|
152
|
+
},
|
|
153
|
+
})
|
|
154
|
+
|
|
146
155
|
const { chart, dispatch } = useChart({
|
|
147
156
|
container: plotEl,
|
|
148
157
|
option: () => built.value.option,
|
|
@@ -64,6 +64,7 @@ import { GridComponent, MarkLineComponent } from 'echarts/components'
|
|
|
64
64
|
import { LabelLayout } from 'echarts/features'
|
|
65
65
|
import { registerChartModules, useChart } from './core/useChart'
|
|
66
66
|
import { usePlotKeyboard } from './core/usePlotKeyboard'
|
|
67
|
+
import { useTooltipDismiss } from './core/useTooltipDismiss'
|
|
67
68
|
import { buildScatterOption, buildScatterSeries } from './scatterOptions'
|
|
68
69
|
import { formatLabel, formatValue } from './format'
|
|
69
70
|
import { pruneHiddenSeries, toggleHiddenSeries } from './hiddenSeries'
|
|
@@ -382,6 +383,14 @@ function downplayPoint(index: number | null) {
|
|
|
382
383
|
})
|
|
383
384
|
}
|
|
384
385
|
|
|
386
|
+
useTooltipDismiss({
|
|
387
|
+
plot: plotEl,
|
|
388
|
+
data: () => walk.value,
|
|
389
|
+
close: () => {
|
|
390
|
+
tooltip.open = false
|
|
391
|
+
},
|
|
392
|
+
})
|
|
393
|
+
|
|
385
394
|
const keyboard = usePlotKeyboard({
|
|
386
395
|
marks: () => walk.value,
|
|
387
396
|
// A point is its group and its place on the axes. Two points that sit on top
|