frappe-ui 1.0.0-beta.64 → 1.0.0-beta.66
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.ts +23 -0
- package/package.json +3 -2
- package/scripts/migrate-destinations-v1.js +229 -0
- package/src/charts/AreaChart.cy.ts +2 -4
- package/src/charts/AreaChart.vue +7 -8
- package/src/charts/BarChart.cy.ts +17 -9
- package/src/charts/BarChart.vue +9 -10
- package/src/charts/DonutChart.cy.ts +68 -6
- package/src/charts/DonutChart.vue +32 -25
- package/src/charts/FunnelChart.cy.ts +38 -10
- package/src/charts/FunnelChart.vue +39 -26
- package/src/charts/HeatmapChart.cy.ts +16 -2
- package/src/charts/HeatmapChart.vue +7 -3
- package/src/charts/LineChart.cy.ts +3 -5
- package/src/charts/LineChart.vue +7 -8
- package/src/charts/NumberCard.cy.ts +21 -5
- package/src/charts/NumberCard.vue +11 -7
- package/src/charts/SankeyChart.cy.ts +20 -1
- package/src/charts/SankeyChart.vue +29 -18
- package/src/charts/ScatterChart.vue +10 -6
- package/src/charts/areaChartOptions.test.ts +11 -16
- package/src/charts/axisChartCommon.ts +25 -17
- package/src/charts/axisChartOptions.ts +9 -23
- package/src/charts/axisFormat.test.ts +1 -1
- package/src/charts/barChartOptions.test.ts +3 -3
- package/src/charts/categoryAxisFit.test.ts +1 -1
- package/src/charts/comboChartOptions.test.ts +9 -22
- package/src/charts/components/ChartContainer.cy.ts +44 -4
- package/src/charts/components/ChartContainer.vue +22 -17
- package/src/charts/components/ChartTooltip.cy.ts +13 -2
- package/src/charts/components/ChartTooltip.vue +9 -11
- package/src/charts/core/useAxisChart.test.ts +127 -0
- package/src/charts/core/useAxisChart.ts +64 -37
- package/src/charts/core/usePlotKeyboard.test.ts +7 -3
- package/src/charts/docs/AreaChart.api.md +15 -15
- package/src/charts/docs/AreaChart.md +2 -2
- package/src/charts/docs/BarChart.api.md +15 -15
- package/src/charts/docs/BarChart.md +4 -2
- package/src/charts/docs/ChartCard.md +2 -2
- package/src/charts/docs/ChartContainer.api.md +5 -5
- package/src/charts/docs/ChartContainer.md +7 -5
- package/src/charts/docs/ChartTooltip.api.md +5 -5
- package/src/charts/docs/ChartTooltip.md +6 -2
- package/src/charts/docs/DonutChart.api.md +20 -8
- package/src/charts/docs/DonutChart.md +7 -1
- package/src/charts/docs/FunnelChart.api.md +4 -11
- package/src/charts/docs/FunnelChart.md +9 -5
- package/src/charts/docs/HeatmapChart.api.md +4 -4
- package/src/charts/docs/HeatmapChart.md +1 -1
- package/src/charts/docs/LineChart.api.md +15 -15
- package/src/charts/docs/LineChart.md +7 -7
- package/src/charts/docs/NumberCard.api.md +7 -7
- package/src/charts/docs/NumberCard.md +6 -5
- package/src/charts/docs/Overview.md +69 -10
- package/src/charts/docs/SankeyChart.api.md +6 -6
- package/src/charts/docs/SankeyChart.md +1 -1
- package/src/charts/docs/ScatterChart.api.md +5 -5
- package/src/charts/docs/States.md +3 -1
- package/src/charts/donutChartOptions.test.ts +4 -4
- package/src/charts/donutChartOptions.ts +11 -9
- package/src/charts/format.test.ts +3 -0
- package/src/charts/format.ts +3 -2
- package/src/charts/funnelGeometry.test.ts +33 -2
- package/src/charts/funnelGeometry.ts +32 -11
- package/src/charts/heatmapOptions.test.ts +7 -5
- package/src/charts/heatmapOptions.ts +12 -10
- package/src/charts/index.ts +6 -9
- package/src/charts/lineChartOptions.test.ts +15 -14
- package/src/charts/normalizedStackOptions.test.ts +6 -6
- package/src/charts/numberCardFormat.test.ts +21 -9
- package/src/charts/numberCardFormat.ts +8 -8
- package/src/charts/paletteColors.test.ts +123 -0
- package/src/charts/referenceLines.test.ts +25 -13
- package/src/charts/sankeyOptions.test.ts +2 -2
- package/src/charts/sankeyOptions.ts +8 -6
- package/src/charts/scatterOptions.test.ts +21 -5
- package/src/charts/scatterOptions.ts +7 -5
- package/src/charts/seriesData.test.ts +20 -1
- package/src/charts/seriesData.ts +1 -0
- package/src/charts/stories/AreaLatency.vue +0 -1
- package/src/charts/stories/BarSelect.vue +1 -1
- package/src/charts/stories/CustomRadar.vue +2 -2
- package/src/charts/stories/Dashboard.vue +10 -4
- package/src/charts/stories/DonutHalf.vue +1 -1
- package/src/charts/stories/DonutOthers.vue +1 -1
- package/src/charts/stories/FunnelDeals.vue +1 -2
- package/src/charts/stories/HeatmapSignups.vue +1 -1
- package/src/charts/stories/HeatmapTickets.vue +1 -1
- package/src/charts/stories/LineDualAxis.vue +1 -1
- package/src/charts/stories/LineFilledSeries.vue +1 -1
- package/src/charts/stories/LineTrend.vue +1 -1
- package/src/charts/stories/NumberKpis.vue +8 -2
- package/src/charts/stories/NumberSparklines.vue +7 -1
- package/src/charts/stories/SankeySpend.vue +1 -1
- package/src/charts/stories/TooltipCustom.vue +5 -0
- package/src/charts/tokens.ts +37 -76
- package/src/charts/tooltipItems.test.ts +3 -3
- package/src/charts/tooltipItems.ts +4 -4
- package/src/charts/types.ts +167 -115
- package/src/charts/utils.test.ts +50 -0
- package/src/charts/utils.ts +52 -1
- package/src/charts/valueAxisOptions.test.ts +36 -2
- package/src/components/Button/Button.api.md +2 -2
- package/src/components/Button/Button.cy.ts +4 -4
- package/src/components/Button/Button.vue +2 -2
- package/src/components/Button/types.ts +2 -2
- package/src/components/DatePicker/CalendarPanel.vue +7 -55
- package/src/components/DatePicker/DateCalendar.cy.ts +149 -0
- package/src/components/DatePicker/DateCalendar.vue +114 -0
- package/src/components/DatePicker/DatePicker.cy.ts +42 -6
- package/src/components/DatePicker/DatePicker.md +3 -0
- package/src/components/DatePicker/DatePicker.vue +14 -104
- package/src/components/DatePicker/DateRangeCalendar.cy.ts +186 -0
- package/src/components/DatePicker/DateRangeCalendar.vue +211 -0
- package/src/components/DatePicker/DateRangePicker.vue +50 -265
- package/src/components/DatePicker/DateTimePicker.cy.ts +42 -4
- package/src/components/DatePicker/DateTimePicker.vue +29 -96
- package/src/components/DatePicker/calendarTypes.ts +143 -0
- package/src/components/DatePicker/composables.ts +64 -2
- package/src/components/DatePicker/stories/DateCalendar.vue +18 -0
- package/src/components/DatePicker/stories/DateRangeCalendar.vue +27 -0
- package/src/components/DatePicker/utils.ts +72 -1
- package/src/components/MobileNav/MobileNav.api.md +8 -2
- package/src/components/MobileNav/MobileNav.cy.ts +18 -5
- package/src/components/MobileNav/MobileNav.md +6 -5
- package/src/components/MobileNav/MobileNavItem.vue +10 -6
- package/src/components/MobileNav/types.ts +5 -2
- package/src/components/PageHeader/PageHeader.api.md +1 -1
- package/src/components/PageHeader/PageHeader.cy.ts +24 -0
- package/src/components/PageHeader/PageHeader.md +3 -2
- package/src/components/PageHeader/PageHeaderBackButton.vue +1 -1
- package/src/components/PageHeader/types.ts +1 -1
- package/src/components/PageHeader/useHeaderBack.ts +8 -8
- package/src/components/Popover/Popover.api.md +25 -5
- package/src/components/Popover/Popover.cy.ts +116 -2
- package/src/components/Popover/Popover.md +22 -3
- package/src/components/Popover/Popover.vue +43 -7
- package/src/components/Popover/stories/Typeahead.vue +66 -0
- package/src/components/Popover/types.ts +25 -1
- package/src/components/Sidebar/Sidebar.api.md +8 -2
- package/src/components/Sidebar/Sidebar.cy.ts +13 -4
- package/src/components/Sidebar/Sidebar.md +5 -3
- package/src/components/Sidebar/SidebarItem.vue +19 -8
- package/src/components/Sidebar/types.ts +5 -2
- package/src/components/SidebarRail/SidebarRail.api.md +7 -1
- package/src/components/SidebarRail/SidebarRail.cy.ts +14 -3
- package/src/components/SidebarRail/SidebarRail.md +2 -1
- package/src/components/SidebarRail/SidebarRailItem.vue +9 -9
- package/src/components/SidebarRail/types.ts +4 -1
- package/src/components/TabButtons/TabButtons.md +4 -5
- package/src/components/TabButtons/TabButtons.vue +3 -3
- package/src/components/TabButtons/types.ts +11 -4
- package/src/components/shared/picker/PickerShell.vue +38 -109
- package/src/components/shared/picker/types.ts +81 -0
- package/src/molecules/list/List.cy.ts +6 -4
- package/src/molecules/list/ListRow.vue +4 -1
- package/src/molecules/list/list.api.md +8 -2
- package/src/molecules/list/list.md +4 -3
- package/src/molecules/list/types.ts +5 -2
- package/src/charts/chartColors.test.ts +0 -207
package/experimental.ts
CHANGED
|
@@ -35,12 +35,35 @@ export * from './experimental/Charts'
|
|
|
35
35
|
// rebuilt here as seven composable parts. Unstable: the parts stay here until
|
|
36
36
|
// gameplan, helpdesk and this repo's docs site all run on them.
|
|
37
37
|
export * from './experimental/CommandPalette'
|
|
38
|
+
// Date calendars. `DateCalendar` and `DateRangeCalendar` are the calendars
|
|
39
|
+
// inside the date pickers, as standalone components. Incubating while their
|
|
40
|
+
// props and events settle.
|
|
41
|
+
export { default as DateCalendar } from './src/components/DatePicker/DateCalendar.vue'
|
|
42
|
+
export { default as DateRangeCalendar } from './src/components/DatePicker/DateRangeCalendar.vue'
|
|
43
|
+
export type {
|
|
44
|
+
DateCalendarProps,
|
|
45
|
+
DateCalendarEmits,
|
|
46
|
+
DateCalendarExposed,
|
|
47
|
+
DateRangeCalendarProps,
|
|
48
|
+
DateRangeCalendarEmits,
|
|
49
|
+
DateRangeCalendarExposed,
|
|
50
|
+
} from './src/components/DatePicker/calendarTypes'
|
|
38
51
|
// ListView family. Moved out of root (#985) — `frappe-ui/list` is
|
|
39
52
|
// composition-based by design (P3) and doesn't replicate ListView's
|
|
40
53
|
// config-driven columns (resizable widths, per-column getLabel/prefix
|
|
41
54
|
// functions, tooltips, disabled-row exclusion, the select banner), so this
|
|
42
55
|
// stays here, unstable, until `frappe-ui/list` reaches parity.
|
|
43
56
|
export * from './experimental/ListView'
|
|
57
|
+
// PickerShell. The input half of the date pickers: a `TextInput`, a manual
|
|
58
|
+
// `Popover` and the focus wiring between them. Incubating while its props and
|
|
59
|
+
// slots settle.
|
|
60
|
+
export { default as PickerShell } from './src/components/shared/picker/PickerShell.vue'
|
|
61
|
+
export type {
|
|
62
|
+
PickerShellProps,
|
|
63
|
+
PickerShellTriggerSlotProps,
|
|
64
|
+
PickerShellSlots,
|
|
65
|
+
PickerShellExposed,
|
|
66
|
+
} from './src/components/shared/picker/types'
|
|
44
67
|
// v0 TextEditor family. Moved out of root (#974) — `frappe-ui/editor` is the
|
|
45
68
|
// composition-based replacement. Parked here (#1007), unstable, as an interim
|
|
46
69
|
// import path while apps migrate. Deletion stays human-gated (spec/editor.md
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "frappe-ui",
|
|
3
|
-
"version": "1.0.0-beta.
|
|
3
|
+
"version": "1.0.0-beta.66",
|
|
4
4
|
"description": "A set of components and utilities for rapid UI development",
|
|
5
5
|
"engines": {
|
|
6
6
|
"node": ">=20.19.0"
|
|
@@ -20,7 +20,8 @@
|
|
|
20
20
|
},
|
|
21
21
|
"bin": {
|
|
22
22
|
"tokens-v2": "./tailwind/migrate-tokens-v2.js",
|
|
23
|
-
"shortcuts-v1": "./scripts/migrate-shortcuts-v1.js"
|
|
23
|
+
"shortcuts-v1": "./scripts/migrate-shortcuts-v1.js",
|
|
24
|
+
"destinations-v1": "./scripts/migrate-destinations-v1.js"
|
|
24
25
|
},
|
|
25
26
|
"scripts": {
|
|
26
27
|
"test": "vitest --run",
|
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/** Rename pre-v1 navigation props to the v1 destination vocabulary.
|
|
3
|
+
* Usage: destinations-v1 [--dry-run] <dir-or-file...>
|
|
4
|
+
*/
|
|
5
|
+
import fs from 'node:fs'
|
|
6
|
+
import path from 'node:path'
|
|
7
|
+
import { fileURLToPath } from 'node:url'
|
|
8
|
+
|
|
9
|
+
const USAGE = 'Usage: destinations-v1 [--dry-run] <dir-or-file...>'
|
|
10
|
+
const EXTENSIONS = new Set(['.vue', '.md'])
|
|
11
|
+
const PROP_RENAMES = {
|
|
12
|
+
Button: { link: 'href' },
|
|
13
|
+
PageHeaderBackButton: { to: 'fallback-route' },
|
|
14
|
+
ListRow: { to: 'route' },
|
|
15
|
+
SidebarItem: { to: 'route' },
|
|
16
|
+
SidebarRailItem: { to: 'route' },
|
|
17
|
+
MobileNavItem: { to: 'route' },
|
|
18
|
+
}
|
|
19
|
+
const kebab = (name) =>
|
|
20
|
+
name.replace(/([a-z0-9])([A-Z])/g, '$1-$2').toLowerCase()
|
|
21
|
+
|
|
22
|
+
function componentAliases(source) {
|
|
23
|
+
const aliases = new Map()
|
|
24
|
+
const imports =
|
|
25
|
+
/import\s*\{([^}]+)\}\s*from\s*['"]frappe-ui(?:\/[^'"]*)?['"]/g
|
|
26
|
+
for (const match of source.matchAll(imports)) {
|
|
27
|
+
for (const binding of match[1].split(',')) {
|
|
28
|
+
const [imported, local] = binding
|
|
29
|
+
.trim()
|
|
30
|
+
.replace(/^type\s+/, '')
|
|
31
|
+
.split(/\s+as\s+/)
|
|
32
|
+
if (PROP_RENAMES[imported]) {
|
|
33
|
+
const binding = local || imported
|
|
34
|
+
aliases.set(binding, imported)
|
|
35
|
+
aliases.set(kebab(binding), imported)
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
// Local template bindings shadow imports with the same local name.
|
|
40
|
+
const localImports =
|
|
41
|
+
/import\s+([^'";]+?)\s+from\s*['"](?!frappe-ui(?:\/|['"]))[^'"]+['"]/g
|
|
42
|
+
for (const match of source.matchAll(localImports)) {
|
|
43
|
+
const clause = match[1].trim()
|
|
44
|
+
const bindings = []
|
|
45
|
+
if (!clause.startsWith('{') && !clause.startsWith('*')) {
|
|
46
|
+
bindings.push(clause.split(',')[0].trim())
|
|
47
|
+
}
|
|
48
|
+
const named = clause.match(/\{([^}]+)\}/)?.[1]
|
|
49
|
+
if (named) {
|
|
50
|
+
for (const binding of named.split(',')) {
|
|
51
|
+
bindings.push(
|
|
52
|
+
binding
|
|
53
|
+
.trim()
|
|
54
|
+
.split(/\s+as\s+/)
|
|
55
|
+
.at(-1),
|
|
56
|
+
)
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
for (const name of bindings) {
|
|
60
|
+
if (!PROP_RENAMES[name]) continue
|
|
61
|
+
aliases.delete(name)
|
|
62
|
+
aliases.delete(kebab(name))
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return aliases
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function renameAttributes(tag, renames) {
|
|
69
|
+
let migrated = ''
|
|
70
|
+
let cursor = 0
|
|
71
|
+
let quote
|
|
72
|
+
while (cursor < tag.length) {
|
|
73
|
+
const character = tag[cursor]
|
|
74
|
+
if (quote) {
|
|
75
|
+
migrated += character
|
|
76
|
+
if (character === quote && tag[cursor - 1] !== '\\') quote = undefined
|
|
77
|
+
cursor += 1
|
|
78
|
+
continue
|
|
79
|
+
}
|
|
80
|
+
if (character === '"' || character === "'") {
|
|
81
|
+
quote = character
|
|
82
|
+
migrated += character
|
|
83
|
+
cursor += 1
|
|
84
|
+
continue
|
|
85
|
+
}
|
|
86
|
+
if (!/\s/.test(character)) {
|
|
87
|
+
migrated += character
|
|
88
|
+
cursor += 1
|
|
89
|
+
continue
|
|
90
|
+
}
|
|
91
|
+
migrated += character
|
|
92
|
+
cursor += 1
|
|
93
|
+
const match = tag
|
|
94
|
+
.slice(cursor)
|
|
95
|
+
.match(
|
|
96
|
+
/^(v-bind:|[:.]?)([A-Za-z][\w-]*)((?:\.[A-Za-z][\w-]*)*)(?=[\s=/>])/,
|
|
97
|
+
)
|
|
98
|
+
if (!match) continue
|
|
99
|
+
const replacement = renames[match[2]]
|
|
100
|
+
if (replacement) {
|
|
101
|
+
const isShorthandBinding =
|
|
102
|
+
match[1] && tag[cursor + match[0].length] !== '='
|
|
103
|
+
migrated += `${match[1]}${replacement}${match[3]}`
|
|
104
|
+
if (isShorthandBinding) migrated += `="${match[2]}"`
|
|
105
|
+
} else {
|
|
106
|
+
migrated += match[0]
|
|
107
|
+
}
|
|
108
|
+
cursor += match[0].length
|
|
109
|
+
}
|
|
110
|
+
return migrated
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function migrateMarkup(source, aliases) {
|
|
114
|
+
let migrated = ''
|
|
115
|
+
let cursor = 0
|
|
116
|
+
while (cursor < source.length) {
|
|
117
|
+
const start = source.indexOf('<', cursor)
|
|
118
|
+
if (start === -1) return migrated + source.slice(cursor)
|
|
119
|
+
migrated += source.slice(cursor, start)
|
|
120
|
+
if (source.startsWith('<!--', start)) {
|
|
121
|
+
const commentEnd = source.indexOf('-->', start + 4)
|
|
122
|
+
const next = commentEnd === -1 ? source.length : commentEnd + 3
|
|
123
|
+
migrated += source.slice(start, next)
|
|
124
|
+
cursor = next
|
|
125
|
+
continue
|
|
126
|
+
}
|
|
127
|
+
const nameMatch = source.slice(start + 1).match(/^([A-Za-z][\w.-]*)/)
|
|
128
|
+
if (!nameMatch) {
|
|
129
|
+
migrated += '<'
|
|
130
|
+
cursor = start + 1
|
|
131
|
+
continue
|
|
132
|
+
}
|
|
133
|
+
let end = start + 1 + nameMatch[1].length
|
|
134
|
+
let quote
|
|
135
|
+
for (; end < source.length; end += 1) {
|
|
136
|
+
const character = source[end]
|
|
137
|
+
if (quote) {
|
|
138
|
+
if (character === quote && source[end - 1] !== '\\') quote = undefined
|
|
139
|
+
} else if (character === '"' || character === "'") {
|
|
140
|
+
quote = character
|
|
141
|
+
} else if (character === '>') {
|
|
142
|
+
break
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
if (end === source.length) return migrated + source.slice(start)
|
|
146
|
+
const tag = source.slice(start, end + 1)
|
|
147
|
+
const component = aliases.get(nameMatch[1])
|
|
148
|
+
if (!component) {
|
|
149
|
+
migrated += tag
|
|
150
|
+
cursor = end + 1
|
|
151
|
+
continue
|
|
152
|
+
}
|
|
153
|
+
migrated += renameAttributes(tag, PROP_RENAMES[component])
|
|
154
|
+
cursor = end + 1
|
|
155
|
+
}
|
|
156
|
+
return migrated
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Rename destination attributes on template components explicitly imported
|
|
161
|
+
* from frappe-ui, leaving scripts and ambiguous global components unchanged.
|
|
162
|
+
*/
|
|
163
|
+
export function migrateDestinations(source) {
|
|
164
|
+
const aliases = componentAliases(source)
|
|
165
|
+
const templateStart = source.indexOf('<template')
|
|
166
|
+
const templateEnd = source.lastIndexOf('</template>')
|
|
167
|
+
if (templateStart === -1 || templateEnd < templateStart)
|
|
168
|
+
return migrateMarkup(source, aliases)
|
|
169
|
+
const end = templateEnd + '</template>'.length
|
|
170
|
+
return (
|
|
171
|
+
source.slice(0, templateStart) +
|
|
172
|
+
migrateMarkup(source.slice(templateStart, end), aliases) +
|
|
173
|
+
source.slice(end)
|
|
174
|
+
)
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
function filesIn(target, seen = new Set()) {
|
|
178
|
+
const stat = fs.lstatSync(target)
|
|
179
|
+
if (stat.isSymbolicLink()) return []
|
|
180
|
+
if (stat.isFile()) return EXTENSIONS.has(path.extname(target)) ? [target] : []
|
|
181
|
+
const real = fs.realpathSync(target)
|
|
182
|
+
if (seen.has(real)) return []
|
|
183
|
+
seen.add(real)
|
|
184
|
+
return fs
|
|
185
|
+
.readdirSync(target, { withFileTypes: true })
|
|
186
|
+
.sort((a, b) => a.name.localeCompare(b.name))
|
|
187
|
+
.flatMap((entry) => {
|
|
188
|
+
if (['node_modules', '.git', 'dist', 'build'].includes(entry.name))
|
|
189
|
+
return []
|
|
190
|
+
return filesIn(path.join(target, entry.name), seen)
|
|
191
|
+
})
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/** Run the destinations-v1 CLI and return its process exit code. */
|
|
195
|
+
export function run(argv) {
|
|
196
|
+
const dryRun = argv.includes('--dry-run')
|
|
197
|
+
const unknown = argv.find((arg) => arg.startsWith('-') && arg !== '--dry-run')
|
|
198
|
+
if (unknown) {
|
|
199
|
+
console.error(`Unknown option: ${unknown}\n${USAGE}`)
|
|
200
|
+
return 1
|
|
201
|
+
}
|
|
202
|
+
const targets = argv.filter((arg) => arg !== '--dry-run')
|
|
203
|
+
if (!targets.length) {
|
|
204
|
+
console.error(USAGE)
|
|
205
|
+
return 1
|
|
206
|
+
}
|
|
207
|
+
let changed = 0
|
|
208
|
+
const files = [
|
|
209
|
+
...new Set(targets.flatMap((target) => filesIn(path.resolve(target)))),
|
|
210
|
+
]
|
|
211
|
+
for (const file of files) {
|
|
212
|
+
const source = fs.readFileSync(file, 'utf8')
|
|
213
|
+
const migrated = migrateDestinations(source)
|
|
214
|
+
if (migrated === source) continue
|
|
215
|
+
changed += 1
|
|
216
|
+
if (!dryRun) fs.writeFileSync(file, migrated)
|
|
217
|
+
console.log(`${dryRun ? 'Would migrate' : 'Migrated'} ${file}`)
|
|
218
|
+
}
|
|
219
|
+
console.log(
|
|
220
|
+
`${dryRun ? 'Would migrate' : 'Migrated'} ${changed} file${changed === 1 ? '' : 's'}.`,
|
|
221
|
+
)
|
|
222
|
+
return 0
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
const script = fileURLToPath(import.meta.url)
|
|
226
|
+
const isMain =
|
|
227
|
+
process.argv[1] &&
|
|
228
|
+
fs.realpathSync(process.argv[1]) === fs.realpathSync(script)
|
|
229
|
+
if (isMain) process.exitCode = run(process.argv.slice(2))
|
|
@@ -210,8 +210,7 @@ describe('AreaChart', () => {
|
|
|
210
210
|
reading().should('contain.text', 'Refunds')
|
|
211
211
|
plot().type('{enter}')
|
|
212
212
|
cy.get('@onSelect').should('have.been.calledWithMatch', {
|
|
213
|
-
|
|
214
|
-
dataIndex: 0,
|
|
213
|
+
name: 'refunds',
|
|
215
214
|
value: 4,
|
|
216
215
|
row: { month: 'Jan', refunds: 4 },
|
|
217
216
|
})
|
|
@@ -224,8 +223,7 @@ describe('AreaChart', () => {
|
|
|
224
223
|
plot().type('{rightarrow}')
|
|
225
224
|
plot().type('{enter}')
|
|
226
225
|
cy.get('@onSelect').should('have.been.calledWithMatch', {
|
|
227
|
-
|
|
228
|
-
dataIndex: 1,
|
|
226
|
+
name: 'sales',
|
|
229
227
|
value: 20,
|
|
230
228
|
row: { month: 'Feb', sales: 20 },
|
|
231
229
|
})
|
package/src/charts/AreaChart.vue
CHANGED
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
<ChartContainer
|
|
3
3
|
:title="title"
|
|
4
4
|
:subtitle="subtitle"
|
|
5
|
-
:
|
|
6
|
-
:
|
|
5
|
+
:y-axis-title="yAxisTitle"
|
|
6
|
+
:y2-axis-title="y2AxisTitle"
|
|
7
7
|
:loading="loading"
|
|
8
8
|
:error="error || renderError"
|
|
9
9
|
:empty="isEmpty"
|
|
@@ -40,7 +40,7 @@
|
|
|
40
40
|
:y="tooltip.y"
|
|
41
41
|
:label="tooltip.label"
|
|
42
42
|
:items="tooltip.items"
|
|
43
|
-
:
|
|
43
|
+
:rows="tooltip.rows"
|
|
44
44
|
:dir="dir"
|
|
45
45
|
>
|
|
46
46
|
<template v-if="$slots.tooltip" #default="slotProps">
|
|
@@ -79,7 +79,7 @@ import type {
|
|
|
79
79
|
AreaChartProps,
|
|
80
80
|
AreaChartSlots,
|
|
81
81
|
AxisChartConfig,
|
|
82
|
-
|
|
82
|
+
ChartExposedRefs,
|
|
83
83
|
} from './types'
|
|
84
84
|
|
|
85
85
|
registerChartModules([
|
|
@@ -107,7 +107,6 @@ const config = computed<AxisChartConfig>(() => ({
|
|
|
107
107
|
type: 'area',
|
|
108
108
|
stacked: props.stacked,
|
|
109
109
|
connectNulls: props.connectNulls,
|
|
110
|
-
fillOpacity: props.fillOpacity,
|
|
111
110
|
}))
|
|
112
111
|
|
|
113
112
|
const {
|
|
@@ -115,8 +114,8 @@ const {
|
|
|
115
114
|
chart,
|
|
116
115
|
dir,
|
|
117
116
|
isEmpty,
|
|
118
|
-
|
|
119
|
-
|
|
117
|
+
yAxisTitle,
|
|
118
|
+
y2AxisTitle,
|
|
120
119
|
renderError,
|
|
121
120
|
tooltip,
|
|
122
121
|
legendItems,
|
|
@@ -133,5 +132,5 @@ const {
|
|
|
133
132
|
onSelect: (event) => emit('select', event),
|
|
134
133
|
})
|
|
135
134
|
|
|
136
|
-
defineExpose<
|
|
135
|
+
defineExpose<ChartExposedRefs>({ chart: computed(() => chart.value) })
|
|
137
136
|
</script>
|
|
@@ -81,8 +81,7 @@ describe('BarChart', () => {
|
|
|
81
81
|
mountChart({ onSelect: cy.spy().as('onSelect') })
|
|
82
82
|
bars().first().click()
|
|
83
83
|
cy.get('@onSelect').should('have.been.calledWithMatch', {
|
|
84
|
-
|
|
85
|
-
dataIndex: 0,
|
|
84
|
+
name: 'sales',
|
|
86
85
|
value: 10,
|
|
87
86
|
row: { month: 'Jan', sales: 10 },
|
|
88
87
|
})
|
|
@@ -476,6 +475,18 @@ describe('BarChart', () => {
|
|
|
476
475
|
container().should('have.attr', 'data-state', 'empty')
|
|
477
476
|
})
|
|
478
477
|
|
|
478
|
+
it('says so when no row carries a number for a series', () => {
|
|
479
|
+
mountChart({ y: ['typo'] })
|
|
480
|
+
cy.contains('No data to show').should('be.visible')
|
|
481
|
+
container().should('have.attr', 'data-state', 'empty')
|
|
482
|
+
})
|
|
483
|
+
|
|
484
|
+
it('says so once the legend has switched every series off', () => {
|
|
485
|
+
mountChart({ hiddenSeries: ['sales', 'refunds'] })
|
|
486
|
+
cy.contains('No data to show').should('be.visible')
|
|
487
|
+
container().should('have.attr', 'data-state', 'empty')
|
|
488
|
+
})
|
|
489
|
+
|
|
479
490
|
it('reads as ready once the bars are drawn', () => {
|
|
480
491
|
mountChart()
|
|
481
492
|
bars().should('have.length', data.length * 2)
|
|
@@ -621,7 +632,7 @@ describe('BarChart', () => {
|
|
|
621
632
|
// The row carries every column, `tooltipColumns` or not: a tooltip the
|
|
622
633
|
// app draws itself needs no prop to reach one.
|
|
623
634
|
mountChart({ data: data.map((row) => ({ ...row, orders: 1000 })) }, {
|
|
624
|
-
tooltip: ({
|
|
635
|
+
tooltip: ({ rows }: any) => h('span', `${rows[0].orders} orders`),
|
|
625
636
|
} as any)
|
|
626
637
|
bars().should('have.length', data.length * 2)
|
|
627
638
|
plot().focus()
|
|
@@ -658,8 +669,7 @@ describe('BarChart', () => {
|
|
|
658
669
|
plot().focus()
|
|
659
670
|
plot().type('{rightarrow}{enter}')
|
|
660
671
|
cy.get('@onSelect').should('have.been.calledWithMatch', {
|
|
661
|
-
|
|
662
|
-
dataIndex: 1,
|
|
672
|
+
name: 'sales',
|
|
663
673
|
value: 20,
|
|
664
674
|
row: { month: 'Feb', sales: 20 },
|
|
665
675
|
})
|
|
@@ -673,8 +683,7 @@ describe('BarChart', () => {
|
|
|
673
683
|
plot().focus()
|
|
674
684
|
plot().type('{downarrow}{enter}')
|
|
675
685
|
cy.get('@onSelect').should('have.been.calledWithMatch', {
|
|
676
|
-
|
|
677
|
-
dataIndex: 0,
|
|
686
|
+
name: 'refunds',
|
|
678
687
|
value: 4,
|
|
679
688
|
})
|
|
680
689
|
})
|
|
@@ -685,8 +694,7 @@ describe('BarChart', () => {
|
|
|
685
694
|
plot().focus()
|
|
686
695
|
plot().type('{downarrow}{uparrow}{enter}')
|
|
687
696
|
cy.get('@onSelect').should('have.been.calledWithMatch', {
|
|
688
|
-
|
|
689
|
-
dataIndex: 0,
|
|
697
|
+
name: 'sales',
|
|
690
698
|
value: 10,
|
|
691
699
|
})
|
|
692
700
|
})
|
package/src/charts/BarChart.vue
CHANGED
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
<ChartContainer
|
|
3
3
|
:title="title"
|
|
4
4
|
:subtitle="subtitle"
|
|
5
|
-
:
|
|
6
|
-
:
|
|
7
|
-
:
|
|
5
|
+
:y-axis-title="yAxisTitle"
|
|
6
|
+
:y2-axis-title="y2AxisTitle"
|
|
7
|
+
:axis-title-placement="axisTitlePlacement"
|
|
8
8
|
:loading="loading"
|
|
9
9
|
:error="error || renderError"
|
|
10
10
|
:empty="isEmpty"
|
|
@@ -41,7 +41,7 @@
|
|
|
41
41
|
:y="tooltip.y"
|
|
42
42
|
:label="tooltip.label"
|
|
43
43
|
:items="tooltip.items"
|
|
44
|
-
:
|
|
44
|
+
:rows="tooltip.rows"
|
|
45
45
|
:dir="dir"
|
|
46
46
|
>
|
|
47
47
|
<template v-if="$slots.tooltip" #default="slotProps">
|
|
@@ -80,7 +80,7 @@ import type {
|
|
|
80
80
|
BarChartEmits,
|
|
81
81
|
BarChartProps,
|
|
82
82
|
BarChartSlots,
|
|
83
|
-
|
|
83
|
+
ChartExposedRefs,
|
|
84
84
|
} from './types'
|
|
85
85
|
|
|
86
86
|
registerChartModules([
|
|
@@ -109,7 +109,6 @@ const config = computed<AxisChartConfig>(() => ({
|
|
|
109
109
|
stacked: props.stacked,
|
|
110
110
|
horizontal: props.horizontal,
|
|
111
111
|
connectNulls: props.connectNulls,
|
|
112
|
-
fillOpacity: props.fillOpacity,
|
|
113
112
|
}))
|
|
114
113
|
|
|
115
114
|
const {
|
|
@@ -117,9 +116,9 @@ const {
|
|
|
117
116
|
chart,
|
|
118
117
|
dir,
|
|
119
118
|
isEmpty,
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
119
|
+
yAxisTitle,
|
|
120
|
+
y2AxisTitle,
|
|
121
|
+
axisTitlePlacement,
|
|
123
122
|
renderError,
|
|
124
123
|
tooltip,
|
|
125
124
|
legendItems,
|
|
@@ -137,5 +136,5 @@ const {
|
|
|
137
136
|
onSelect: (event) => emit('select', event),
|
|
138
137
|
})
|
|
139
138
|
|
|
140
|
-
defineExpose<
|
|
139
|
+
defineExpose<ChartExposedRefs>({ chart: computed(() => chart.value) })
|
|
141
140
|
</script>
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { defineComponent, h } from 'vue'
|
|
1
|
+
import { defineComponent, h, ref } from 'vue'
|
|
2
2
|
import DonutChart from './DonutChart.vue'
|
|
3
|
+
import { OTHERS_KEY } from './utils'
|
|
3
4
|
import './style.css'
|
|
4
5
|
|
|
5
6
|
const data = [
|
|
@@ -73,6 +74,7 @@ describe('DonutChart', () => {
|
|
|
73
74
|
cy.get('@onSelect')
|
|
74
75
|
.should('have.been.calledWithMatch', {
|
|
75
76
|
name: 'Search',
|
|
77
|
+
label: 'Search',
|
|
76
78
|
value: 50,
|
|
77
79
|
percent: 50,
|
|
78
80
|
})
|
|
@@ -96,8 +98,47 @@ describe('DonutChart', () => {
|
|
|
96
98
|
cy.get('[data-slot="chart-legend"]').should('contain.text', '60%')
|
|
97
99
|
})
|
|
98
100
|
|
|
101
|
+
it('round-trips v-model:hiddenSeries', () => {
|
|
102
|
+
const hidden = ref<string[]>([])
|
|
103
|
+
cy.mount(
|
|
104
|
+
defineComponent({
|
|
105
|
+
setup() {
|
|
106
|
+
return () =>
|
|
107
|
+
h('div', { style: 'width: 480px; height: 300px' }, [
|
|
108
|
+
h(DonutChart, {
|
|
109
|
+
data,
|
|
110
|
+
category: 'source',
|
|
111
|
+
value: 'visits',
|
|
112
|
+
echartOptions: { animation: false },
|
|
113
|
+
hiddenSeries: hidden.value,
|
|
114
|
+
'onUpdate:hiddenSeries': (v: string[]) => (hidden.value = v),
|
|
115
|
+
}),
|
|
116
|
+
])
|
|
117
|
+
},
|
|
118
|
+
}),
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
cy.get('[aria-label="Hide Search"]')
|
|
122
|
+
.click()
|
|
123
|
+
.then(() => expect(hidden.value).to.deep.equal(['Search']))
|
|
124
|
+
slices().should('have.length', 2)
|
|
125
|
+
})
|
|
126
|
+
|
|
127
|
+
// An empty ring reads as a bug, so the last entry left showing does nothing.
|
|
128
|
+
it('refuses to hide the last slice left on the ring', () => {
|
|
129
|
+
mountChart()
|
|
130
|
+
cy.get('[aria-label="Hide Search"]').click()
|
|
131
|
+
cy.get('[aria-label="Hide Direct"]').click()
|
|
132
|
+
cy.get('[aria-label="Hide Email"]').click()
|
|
133
|
+
cy.get('[aria-label="Hide Email"]').should(
|
|
134
|
+
'have.attr',
|
|
135
|
+
'aria-pressed',
|
|
136
|
+
'true',
|
|
137
|
+
)
|
|
138
|
+
})
|
|
139
|
+
|
|
99
140
|
it('gives up the readout when the labels move onto the ring', () => {
|
|
100
|
-
mountChart({
|
|
141
|
+
mountChart({ showDataLabels: true })
|
|
101
142
|
cy.get('[data-slot="chart-plot"]').should('not.contain.text', 'Visits')
|
|
102
143
|
cy.get('[data-slot="chart-plot"] svg text').should('contain.text', 'Search')
|
|
103
144
|
})
|
|
@@ -114,6 +155,25 @@ describe('DonutChart', () => {
|
|
|
114
155
|
cy.get('[data-slot="chart-legend"]').should('contain.text', 'Others')
|
|
115
156
|
})
|
|
116
157
|
|
|
158
|
+
// The tail is identified by the reserved key, not by what it prints, so a
|
|
159
|
+
// caller can tell it apart from a category that happens to read "Others".
|
|
160
|
+
it('names the tail slice by the reserved key', () => {
|
|
161
|
+
mountChart({ maxSlices: 2, onSelect: cy.spy().as('onSelect') })
|
|
162
|
+
slices().should('have.length', 2)
|
|
163
|
+
cy.get('[data-slot="chart-plot"] [role="img"]')
|
|
164
|
+
.focus()
|
|
165
|
+
.type('{rightarrow}{enter}')
|
|
166
|
+
cy.get('@onSelect').should('have.been.calledWithMatch', {
|
|
167
|
+
name: OTHERS_KEY,
|
|
168
|
+
label: 'Others',
|
|
169
|
+
value: 50,
|
|
170
|
+
rows: [
|
|
171
|
+
{ source: 'Direct', visits: 30 },
|
|
172
|
+
{ source: 'Email', visits: 20 },
|
|
173
|
+
],
|
|
174
|
+
})
|
|
175
|
+
})
|
|
176
|
+
|
|
117
177
|
describe('states', () => {
|
|
118
178
|
/** The state an app styles the card from, without tracking it itself. */
|
|
119
179
|
const container = () => cy.get('[data-slot="chart-container"]')
|
|
@@ -172,7 +232,8 @@ describe('DonutChart', () => {
|
|
|
172
232
|
mountChart(
|
|
173
233
|
{},
|
|
174
234
|
{
|
|
175
|
-
center: ({
|
|
235
|
+
center: ({ formattedValue, label }: any) =>
|
|
236
|
+
h('span', `${formattedValue} of ${label}`),
|
|
176
237
|
},
|
|
177
238
|
)
|
|
178
239
|
cy.get('[data-slot="chart-plot"]').should('contain.text', '100 of Visits')
|
|
@@ -182,8 +243,8 @@ describe('DonutChart', () => {
|
|
|
182
243
|
mountChart(
|
|
183
244
|
{},
|
|
184
245
|
{
|
|
185
|
-
center: ({ label, percent }: any) =>
|
|
186
|
-
h('span', `${label} ${percent
|
|
246
|
+
center: ({ label, value, percent }: any) =>
|
|
247
|
+
h('span', `${label} ${value} ${Math.round(percent)}%`),
|
|
187
248
|
},
|
|
188
249
|
)
|
|
189
250
|
slices().should('have.length', data.length)
|
|
@@ -194,7 +255,7 @@ describe('DonutChart', () => {
|
|
|
194
255
|
350,
|
|
195
256
|
135,
|
|
196
257
|
)
|
|
197
|
-
cy.get('[data-slot="chart-plot"]').should('contain.text', 'Search 50%')
|
|
258
|
+
cy.get('[data-slot="chart-plot"]').should('contain.text', 'Search 50 50%')
|
|
198
259
|
})
|
|
199
260
|
|
|
200
261
|
it('takes an app’s own tooltip body', () => {
|
|
@@ -244,6 +305,7 @@ describe('DonutChart', () => {
|
|
|
244
305
|
plot().type('{enter}')
|
|
245
306
|
cy.get('@onSelect').should('have.been.calledWithMatch', {
|
|
246
307
|
name: 'Direct',
|
|
308
|
+
label: 'Direct',
|
|
247
309
|
value: 30,
|
|
248
310
|
percent: 30,
|
|
249
311
|
rows: [{ source: 'Direct', visits: 30 }],
|