frappe-ui 1.0.0-beta.67 → 1.0.0-beta.69
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/Calendar/Calendar.vue +2 -2
- package/experimental/Calendar/stories/CustomHeader.vue +2 -2
- package/package.json +10 -1
- package/scripts/migrate-base-props-v1.js +379 -0
- package/scripts/migrate-list-v1.js +436 -0
- package/scripts/migrate-navigation-v1.js +300 -0
- package/scripts/migrate-overlays-v1.js +519 -0
- package/src/components/Badge/Badge.api.md +1 -1
- package/src/components/Badge/types.ts +1 -5
- package/src/components/Combobox/Combobox.cy.ts +2 -6
- package/src/components/Combobox/Combobox.md +4 -0
- package/src/components/Combobox/Combobox.vue +6 -1
- package/src/components/Combobox/types.ts +2 -0
- package/src/components/ContextMenu/ContextMenu.api.md +36 -0
- package/src/components/ContextMenu/ContextMenu.cy.ts +141 -29
- package/src/components/ContextMenu/ContextMenu.md +5 -0
- package/src/components/ContextMenu/ContextMenu.vue +81 -8
- package/src/components/ContextMenu/types.ts +19 -7
- package/src/components/DatePicker/DatePicker.api.md +0 -7
- package/src/components/DatePicker/DatePicker.cy.ts +11 -15
- package/src/components/DatePicker/DatePicker.md +4 -0
- package/src/components/DatePicker/DatePicker.vue +17 -6
- package/src/components/DatePicker/DateRangePicker.cy.ts +22 -8
- package/src/components/DatePicker/DateRangePicker.vue +4 -1
- package/src/components/DatePicker/DateTimePicker.cy.ts +4 -4
- package/src/components/DatePicker/DateTimePicker.vue +5 -3
- package/src/components/DatePicker/stories/Examples.vue +2 -2
- package/src/components/DatePicker/stories/Range.vue +3 -3
- package/src/components/DatePicker/types.ts +15 -5
- package/src/components/DesktopShell/stories/Default.vue +1 -1
- package/src/components/Divider/Divider.api.md +1 -1
- package/src/components/Divider/Divider.cy.ts +23 -0
- package/src/components/Divider/Divider.md +3 -0
- package/src/components/Divider/Divider.playground.vue +4 -4
- package/src/components/Divider/Divider.vue +11 -7
- package/src/components/Divider/stories/Action.vue +1 -1
- package/src/components/Divider/types.ts +4 -11
- package/src/components/Dropdown/Dropdown.api.md +30 -0
- package/src/components/Dropdown/Dropdown.cy.ts +23 -3
- package/src/components/Dropdown/Dropdown.md +3 -0
- package/src/components/Dropdown/Dropdown.vue +11 -4
- package/src/components/Dropdown/types.ts +20 -9
- package/src/components/HoverCard/HoverCard.api.md +5 -5
- package/src/components/HoverCard/HoverCard.cy.ts +88 -1
- package/src/components/HoverCard/HoverCard.md +2 -2
- package/src/components/HoverCard/HoverCard.vue +65 -18
- package/src/components/HoverCard/index.ts +1 -0
- package/src/components/HoverCard/stories/Delays.vue +1 -1
- package/src/components/HoverCard/types.ts +11 -4
- package/src/components/Icon/Icon.api.md +7 -1
- package/src/components/Icon/Icon.cy.ts +43 -11
- package/src/components/Icon/Icon.md +11 -0
- package/src/components/Icon/Icon.vue +15 -8
- package/src/components/Icon/stories/ComponentIcon.vue +1 -1
- package/src/components/Icon/stories/Lucide.vue +4 -4
- package/src/components/Icon/types.ts +6 -0
- package/src/components/Menu/Menu.api.md +1 -1
- package/src/components/Menu/Menu.vue +3 -3
- package/src/components/Menu/MenuItemContent.vue +2 -2
- package/src/components/Menu/types.test.ts +49 -0
- package/src/components/Menu/types.ts +9 -10
- package/src/components/MobileNav/MobileNavItem.vue +5 -20
- package/src/components/MultiSelect/MultiSelect.cy.ts +4 -1
- package/src/components/MultiSelect/MultiSelect.md +2 -2
- package/src/components/MultiSelect/MultiSelect.vue +6 -1
- package/src/components/MultiSelect/types.ts +2 -0
- package/src/components/Popover/Popover.api.md +5 -5
- package/src/components/Popover/Popover.cy.ts +9 -6
- package/src/components/Popover/Popover.md +3 -3
- package/src/components/Popover/Popover.vue +4 -9
- package/src/components/Popover/types.ts +2 -2
- package/src/components/Progress/Progress.api.md +2 -10
- package/src/components/Progress/Progress.cy.ts +16 -6
- package/src/components/Progress/Progress.md +2 -2
- package/src/components/Progress/Progress.playground.vue +4 -9
- package/src/components/Progress/Progress.vue +3 -5
- package/src/components/Progress/stories/MultiStepForm.vue +1 -6
- package/src/components/Progress/types.ts +2 -5
- package/src/components/Select/Select.cy.ts +2 -6
- package/src/components/Select/Select.md +2 -2
- package/src/components/Select/Select.vue +6 -0
- package/src/components/Select/types.ts +2 -0
- package/src/components/SettingsDialog/SettingsDialog.api.md +4 -4
- package/src/components/SettingsDialog/SettingsDialog.cy.ts +84 -1
- package/src/components/SettingsDialog/SettingsDialog.vue +15 -15
- package/src/components/SettingsDialog/index.ts +1 -0
- package/src/components/SettingsDialog/types.ts +3 -2
- package/src/components/Sidebar/Sidebar.api.md +5 -4
- package/src/components/Sidebar/Sidebar.cy.ts +22 -8
- package/src/components/Sidebar/Sidebar.md +2 -2
- package/src/components/Sidebar/Sidebar.vue +2 -2
- package/src/components/Sidebar/SidebarItem.vue +2 -2
- package/src/components/Sidebar/stories/Default.vue +1 -1
- package/src/components/Sidebar/stories/Section.vue +1 -1
- package/src/components/Sidebar/types.ts +4 -7
- package/src/components/SidebarRail/SidebarRail.api.md +3 -3
- package/src/components/SidebarRail/SidebarRail.cy.ts +88 -14
- package/src/components/SidebarRail/SidebarRail.md +4 -2
- package/src/components/SidebarRail/SidebarRailItem.vue +46 -41
- package/src/components/SidebarRail/types.ts +2 -2
- package/src/components/TabButtons/TabButtons.api.md +2 -2
- package/src/components/TabButtons/TabButtons.cy.ts +6 -6
- package/src/components/TabButtons/TabButtons.md +1 -1
- package/src/components/TabButtons/TabButtons.vue +4 -4
- package/src/components/TabButtons/types.ts +2 -2
- package/src/components/Tabs/TabTrigger.vue +2 -4
- package/src/components/Tabs/Tabs.cy.ts +3 -3
- package/src/components/Tabs/Tabs.md +1 -1
- package/src/components/Tabs/types.ts +1 -1
- package/src/components/TimePicker/TimePicker.api.md +1 -1
- package/src/components/TimePicker/TimePicker.cy.ts +48 -62
- package/src/components/TimePicker/TimePicker.md +4 -1
- package/src/components/TimePicker/TimePicker.vue +27 -15
- package/src/components/Tooltip/Tooltip.api.md +6 -6
- package/src/components/Tooltip/Tooltip.cy.ts +5 -5
- package/src/components/Tooltip/Tooltip.playground.vue +2 -2
- package/src/components/Tooltip/Tooltip.vue +6 -4
- package/src/components/Tooltip/TooltipProvider.vue +5 -5
- package/src/components/Tooltip/stories/Group.vue +1 -1
- package/src/components/Tooltip/types.ts +1 -1
- package/src/components/shared/action.ts +5 -5
- package/src/components/shared/picker/PickerShell.vue +11 -14
- package/src/components/shared/picker/types.ts +7 -3
- package/src/components/shared/tabs/Pill.vue +2 -2
- package/src/composables/useKeyboardShortcut.spec.ts +40 -1
- package/src/composables/useKeyboardShortcut.ts +19 -10
- package/src/molecules/list/List.cy.ts +146 -11
- package/src/molecules/list/ListGroup.vue +5 -5
- package/src/molecules/list/ListHeaderCellSort.vue +11 -9
- package/src/molecules/list/ListRowBase.vue +2 -2
- package/src/molecules/list/ListRows.vue +41 -18
- package/src/molecules/list/index.ts +0 -4
- package/src/molecules/list/list.api.md +14 -8
- package/src/molecules/list/list.md +17 -10
- package/src/molecules/list/stories/Columns.vue +3 -3
- package/src/molecules/list/stories/Feed.vue +5 -2
- package/src/molecules/list/style.css +2 -2
- package/src/molecules/list/types.ts +0 -8
- package/src/molecules/list/useVirtualRows.ts +46 -27
|
@@ -27,13 +27,13 @@
|
|
|
27
27
|
@update:modelValue="(val) => onMonthYearChange(val)"
|
|
28
28
|
:clearable="false"
|
|
29
29
|
>
|
|
30
|
-
<template #trigger="{
|
|
30
|
+
<template #trigger="{ open, setOpen }">
|
|
31
31
|
<Button
|
|
32
32
|
variant="ghost"
|
|
33
33
|
class="text-lg-medium text-ink-gray-7"
|
|
34
34
|
:label="currentMonthYear"
|
|
35
35
|
iconRight="lucide-chevron-down"
|
|
36
|
-
@click="
|
|
36
|
+
@click="setOpen(!open)"
|
|
37
37
|
/>
|
|
38
38
|
</template>
|
|
39
39
|
</DatePicker>
|
|
@@ -110,13 +110,13 @@ const events = ref([
|
|
|
110
110
|
@update:modelValue="(val) => headerProps.onMonthYearChange(val)"
|
|
111
111
|
:clearable="false"
|
|
112
112
|
>
|
|
113
|
-
<template #trigger="{
|
|
113
|
+
<template #trigger="{ setOpen, open }">
|
|
114
114
|
<Button
|
|
115
115
|
variant="ghost"
|
|
116
116
|
class="text-lg-medium text-ink-gray-7"
|
|
117
117
|
:label="headerProps.currentMonthYear"
|
|
118
118
|
iconRight="lucide-chevron-down"
|
|
119
|
-
@click="
|
|
119
|
+
@click="setOpen(!open)"
|
|
120
120
|
/>
|
|
121
121
|
</template>
|
|
122
122
|
</DatePicker>
|
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.69",
|
|
4
4
|
"description": "A set of components and utilities for rapid UI development",
|
|
5
5
|
"engines": {
|
|
6
6
|
"node": ">=20.19.0"
|
|
@@ -19,6 +19,10 @@
|
|
|
19
19
|
"#utils/*": "./src/utils/*"
|
|
20
20
|
},
|
|
21
21
|
"bin": {
|
|
22
|
+
"base-props-v1": "./scripts/migrate-base-props-v1.js",
|
|
23
|
+
"list-v1": "./scripts/migrate-list-v1.js",
|
|
24
|
+
"navigation-v1": "./scripts/migrate-navigation-v1.js",
|
|
25
|
+
"overlays-v1": "./scripts/migrate-overlays-v1.js",
|
|
22
26
|
"tokens-v2": "./tailwind/migrate-tokens-v2.js",
|
|
23
27
|
"shortcuts-v1": "./scripts/migrate-shortcuts-v1.js",
|
|
24
28
|
"editor-v1": "./scripts/migrate-editor-v1.js",
|
|
@@ -104,7 +108,11 @@
|
|
|
104
108
|
"tailwind",
|
|
105
109
|
"vitepress",
|
|
106
110
|
"scripts/migrate-shortcuts-v1.js",
|
|
111
|
+
"scripts/migrate-base-props-v1.js",
|
|
107
112
|
"scripts/migrate-editor-v1.js",
|
|
113
|
+
"scripts/migrate-list-v1.js",
|
|
114
|
+
"scripts/migrate-navigation-v1.js",
|
|
115
|
+
"scripts/migrate-overlays-v1.js",
|
|
108
116
|
"experimental",
|
|
109
117
|
"experimental.ts",
|
|
110
118
|
"tsconfig.base.json"
|
|
@@ -116,6 +124,7 @@
|
|
|
116
124
|
"author": "Frappe Technologies Pvt. Ltd.",
|
|
117
125
|
"license": "MIT",
|
|
118
126
|
"dependencies": {
|
|
127
|
+
"@vue/compiler-sfc": "^3.5.13",
|
|
119
128
|
"@codemirror/lang-css": "^6.3.0",
|
|
120
129
|
"@codemirror/lang-html": "^6.4.9",
|
|
121
130
|
"@codemirror/lang-javascript": "^6.2.2",
|
|
@@ -0,0 +1,379 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import fs from 'node:fs'
|
|
4
|
+
import path from 'node:path'
|
|
5
|
+
import { fileURLToPath } from 'node:url'
|
|
6
|
+
import { parse as parseSfc } from '@vue/compiler-sfc'
|
|
7
|
+
|
|
8
|
+
const NodeTypes = {
|
|
9
|
+
ELEMENT: 1,
|
|
10
|
+
SIMPLE_EXPRESSION: 4,
|
|
11
|
+
ATTRIBUTE: 6,
|
|
12
|
+
DIRECTIVE: 7,
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const USAGE = `Usage: base-props-v1 [--dry-run] <dir-or-file...>
|
|
16
|
+
|
|
17
|
+
Renames the v1 base-component props on statically named Vue component tags:
|
|
18
|
+
Icon name -> icon (optional canonical spelling)
|
|
19
|
+
Divider position -> align
|
|
20
|
+
Progress intervals + intervalCount -> numeric intervals`
|
|
21
|
+
|
|
22
|
+
const COMPONENTS = new Set(['Icon', 'Divider', 'Progress'])
|
|
23
|
+
|
|
24
|
+
function walk(target, visited = new Set()) {
|
|
25
|
+
if (fs.lstatSync(target).isSymbolicLink()) return []
|
|
26
|
+
const resolved = fs.realpathSync(target)
|
|
27
|
+
if (visited.has(resolved)) return []
|
|
28
|
+
visited.add(resolved)
|
|
29
|
+
const stat = fs.statSync(resolved)
|
|
30
|
+
if (stat.isFile()) return path.extname(target) === '.vue' ? [target] : []
|
|
31
|
+
return fs.readdirSync(resolved, { withFileTypes: true }).flatMap((entry) => {
|
|
32
|
+
if (entry.name === 'node_modules' || entry.name === '.git') return []
|
|
33
|
+
return walk(path.join(resolved, entry.name), visited)
|
|
34
|
+
})
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function kebabCase(name) {
|
|
38
|
+
return name.replace(/([a-z0-9])([A-Z])/g, '$1-$2').toLowerCase()
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function setAlias(aliases, binding, component) {
|
|
42
|
+
aliases.set(binding, component)
|
|
43
|
+
aliases.set(kebabCase(binding), component)
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function importedComponentAliases(source) {
|
|
47
|
+
const aliases = new Map()
|
|
48
|
+
for (const name of COMPONENTS) setAlias(aliases, name, name)
|
|
49
|
+
const importPattern = /import\s*\{([\s\S]*?)\}\s*from\s*['"]([^'"]+)['"]/g
|
|
50
|
+
for (const match of source.matchAll(importPattern)) {
|
|
51
|
+
for (const part of match[1].split(',')) {
|
|
52
|
+
const binding = part
|
|
53
|
+
.trim()
|
|
54
|
+
.match(/^(Icon|Divider|Progress)(?:\s+as\s+([A-Za-z_$][\w$]*))?$/)
|
|
55
|
+
if (!binding) continue
|
|
56
|
+
const name = binding[2] || binding[1]
|
|
57
|
+
if (match[2] === 'frappe-ui') setAlias(aliases, name, binding[1])
|
|
58
|
+
else {
|
|
59
|
+
aliases.delete(name)
|
|
60
|
+
aliases.delete(kebabCase(name))
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
for (const match of source.matchAll(
|
|
65
|
+
/import\s+([A-Za-z_$][\w$]*)\s+from\s*['"]([^'"]+)['"]/g,
|
|
66
|
+
)) {
|
|
67
|
+
const [, binding, specifier] = match
|
|
68
|
+
const component = [...COMPONENTS].find((name) =>
|
|
69
|
+
specifier.endsWith(`/${name}/${name}.vue`),
|
|
70
|
+
)
|
|
71
|
+
if (component) setAlias(aliases, binding, component)
|
|
72
|
+
else {
|
|
73
|
+
aliases.delete(binding)
|
|
74
|
+
aliases.delete(kebabCase(binding))
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
return aliases
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function propName(prop) {
|
|
81
|
+
if (prop.type === NodeTypes.ATTRIBUTE) return prop.name
|
|
82
|
+
if (
|
|
83
|
+
prop.type === NodeTypes.DIRECTIVE &&
|
|
84
|
+
prop.name === 'bind' &&
|
|
85
|
+
prop.arg?.type === NodeTypes.SIMPLE_EXPRESSION &&
|
|
86
|
+
prop.arg.isStatic
|
|
87
|
+
) {
|
|
88
|
+
return prop.arg.content
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function renamePropEdit(prop, from, to, offset) {
|
|
93
|
+
if (
|
|
94
|
+
prop.type === NodeTypes.DIRECTIVE &&
|
|
95
|
+
prop.name === 'bind' &&
|
|
96
|
+
prop.exp === undefined
|
|
97
|
+
) {
|
|
98
|
+
const renamed = prop.loc.source.replace(from, to)
|
|
99
|
+
return {
|
|
100
|
+
start: offset + prop.loc.start.offset,
|
|
101
|
+
end: offset + prop.loc.end.offset,
|
|
102
|
+
text: `${renamed}="${from}"`,
|
|
103
|
+
description: `${from} -> ${to}`,
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
const relative = prop.loc.source.indexOf(from)
|
|
107
|
+
return {
|
|
108
|
+
start: offset + prop.loc.start.offset + relative,
|
|
109
|
+
end: offset + prop.loc.start.offset + relative + from.length,
|
|
110
|
+
text: to,
|
|
111
|
+
description: `${from} -> ${to}`,
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function boundExpression(prop) {
|
|
116
|
+
if (prop?.type !== NodeTypes.DIRECTIVE || prop.name !== 'bind')
|
|
117
|
+
return undefined
|
|
118
|
+
const match = prop.loc.source.match(/^(?::|v-bind:)[^=]+=(['"])([\s\S]*)\1$/)
|
|
119
|
+
return match?.[2]
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function countExpression(prop) {
|
|
123
|
+
if (prop.type === NodeTypes.ATTRIBUTE) return prop.value?.content
|
|
124
|
+
return boundExpression(prop)
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function looksNumericExpression(expression) {
|
|
128
|
+
return (
|
|
129
|
+
/^\d+(?:\.\d+)?$/.test(expression) ||
|
|
130
|
+
/(?:^|\.)(?:length|size)$/.test(expression) ||
|
|
131
|
+
/^(?:Number|parseInt|parseFloat)\s*\(/.test(expression) ||
|
|
132
|
+
/[+*/%]|\?[^:]+:\s*(?:undefined|\d)/.test(expression)
|
|
133
|
+
)
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function progressEdit(element, offset, refusals) {
|
|
137
|
+
const intervals = element.props.find((prop) => propName(prop) === 'intervals')
|
|
138
|
+
const count = element.props.find((prop) =>
|
|
139
|
+
['intervalCount', 'interval-count'].includes(propName(prop)),
|
|
140
|
+
)
|
|
141
|
+
if (!count && !intervals) return []
|
|
142
|
+
|
|
143
|
+
if (count && !intervals) {
|
|
144
|
+
refusals.push({
|
|
145
|
+
line: count.loc.start.line,
|
|
146
|
+
message:
|
|
147
|
+
'Progress has intervalCount without intervals; choose continuous or interval mode',
|
|
148
|
+
})
|
|
149
|
+
return []
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
const condition = boundExpression(intervals)
|
|
153
|
+
const staticValue =
|
|
154
|
+
intervals.type === NodeTypes.ATTRIBUTE
|
|
155
|
+
? intervals.value?.content
|
|
156
|
+
: undefined
|
|
157
|
+
const bareBoolean =
|
|
158
|
+
intervals.type === NodeTypes.ATTRIBUTE && intervals.value === undefined
|
|
159
|
+
const numericStatic =
|
|
160
|
+
staticValue !== undefined && /^\d+(?:\.\d+)?$/.test(staticValue)
|
|
161
|
+
const shorthandBinding =
|
|
162
|
+
intervals.type === NodeTypes.DIRECTIVE && condition === undefined
|
|
163
|
+
// A dynamic `:intervals` without `intervalCount` may already be the v1
|
|
164
|
+
// numeric API. Leaving it alone makes repeat runs idempotent.
|
|
165
|
+
if (!count && shorthandBinding) {
|
|
166
|
+
refusals.push({
|
|
167
|
+
line: intervals.loc.start.line,
|
|
168
|
+
message:
|
|
169
|
+
'Progress shorthand :intervals may still be the v0 boolean mode; replace it with a segment count',
|
|
170
|
+
})
|
|
171
|
+
return []
|
|
172
|
+
}
|
|
173
|
+
if (
|
|
174
|
+
!count &&
|
|
175
|
+
!bareBoolean &&
|
|
176
|
+
staticValue !== 'true' &&
|
|
177
|
+
!numericStatic &&
|
|
178
|
+
condition !== 'true' &&
|
|
179
|
+
condition !== 'false'
|
|
180
|
+
) {
|
|
181
|
+
if (!looksNumericExpression(condition)) {
|
|
182
|
+
refusals.push({
|
|
183
|
+
line: intervals.loc.start.line,
|
|
184
|
+
message: `Progress ${intervals.loc.source} may still be the v0 boolean mode; replace it with a segment count`,
|
|
185
|
+
})
|
|
186
|
+
}
|
|
187
|
+
return []
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
if (condition === 'false') {
|
|
191
|
+
const removeProp = (prop, description) => {
|
|
192
|
+
let start = prop.loc.start.offset
|
|
193
|
+
while (
|
|
194
|
+
start > element.loc.start.offset &&
|
|
195
|
+
/\s/.test(
|
|
196
|
+
element.loc.source[start - element.loc.start.offset - 1] || '',
|
|
197
|
+
)
|
|
198
|
+
)
|
|
199
|
+
start--
|
|
200
|
+
return {
|
|
201
|
+
start: offset + start,
|
|
202
|
+
end: offset + prop.loc.end.offset,
|
|
203
|
+
text: '',
|
|
204
|
+
description,
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
return [
|
|
208
|
+
removeProp(intervals, 'remove disabled Progress interval mode'),
|
|
209
|
+
...(count
|
|
210
|
+
? [removeProp(count, 'remove unused Progress intervalCount')]
|
|
211
|
+
: []),
|
|
212
|
+
]
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
let replacement
|
|
216
|
+
if (!count && numericStatic) {
|
|
217
|
+
replacement = `:intervals="${staticValue}"`
|
|
218
|
+
} else {
|
|
219
|
+
const countValue = count ? countExpression(count) : '6'
|
|
220
|
+
if (!countValue) {
|
|
221
|
+
refusals.push({
|
|
222
|
+
line: count.loc.start.line,
|
|
223
|
+
message: 'Progress intervalCount has no static value to migrate',
|
|
224
|
+
})
|
|
225
|
+
return []
|
|
226
|
+
}
|
|
227
|
+
if (condition && condition !== 'true') {
|
|
228
|
+
replacement = `:intervals="(${condition}) ? (${countValue}) : undefined"`
|
|
229
|
+
} else {
|
|
230
|
+
replacement = `:intervals="${countValue}"`
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
const edits = [
|
|
235
|
+
{
|
|
236
|
+
start: offset + intervals.loc.start.offset,
|
|
237
|
+
end: offset + intervals.loc.end.offset,
|
|
238
|
+
text: replacement,
|
|
239
|
+
description: 'Progress intervals + intervalCount -> numeric intervals',
|
|
240
|
+
},
|
|
241
|
+
]
|
|
242
|
+
if (count) {
|
|
243
|
+
let countStart = count.loc.start.offset
|
|
244
|
+
let countEnd = count.loc.end.offset
|
|
245
|
+
if (countStart > intervals.loc.end.offset) {
|
|
246
|
+
while (
|
|
247
|
+
countStart > intervals.loc.end.offset &&
|
|
248
|
+
/\s/.test(
|
|
249
|
+
element.loc.source[countStart - element.loc.start.offset - 1] || '',
|
|
250
|
+
)
|
|
251
|
+
)
|
|
252
|
+
countStart--
|
|
253
|
+
} else {
|
|
254
|
+
while (
|
|
255
|
+
countEnd < intervals.loc.start.offset &&
|
|
256
|
+
/\s/.test(element.loc.source[countEnd - element.loc.start.offset] || '')
|
|
257
|
+
)
|
|
258
|
+
countEnd++
|
|
259
|
+
}
|
|
260
|
+
edits.push({
|
|
261
|
+
start: offset + countStart,
|
|
262
|
+
end: offset + countEnd,
|
|
263
|
+
text: '',
|
|
264
|
+
description: 'remove Progress intervalCount',
|
|
265
|
+
})
|
|
266
|
+
}
|
|
267
|
+
return edits
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
export function migrateBaseProps(source) {
|
|
271
|
+
const parsed = parseSfc(source, { filename: 'component.vue' })
|
|
272
|
+
if (parsed.errors.length) {
|
|
273
|
+
return {
|
|
274
|
+
migrated: source,
|
|
275
|
+
changes: [],
|
|
276
|
+
refusals: [
|
|
277
|
+
{ line: 1, message: `Vue parse error: ${String(parsed.errors[0])}` },
|
|
278
|
+
],
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
const block = parsed.descriptor.template
|
|
282
|
+
if (!block) return { migrated: source, changes: [], refusals: [] }
|
|
283
|
+
|
|
284
|
+
const aliases = importedComponentAliases(source)
|
|
285
|
+
const ast = block.ast
|
|
286
|
+
const edits = []
|
|
287
|
+
const refusals = []
|
|
288
|
+
|
|
289
|
+
function visit(node) {
|
|
290
|
+
if (node.type === NodeTypes.ELEMENT) {
|
|
291
|
+
const component = aliases.get(node.tag)
|
|
292
|
+
if (component === 'Icon') {
|
|
293
|
+
const name = node.props.find((prop) => propName(prop) === 'name')
|
|
294
|
+
const icon = node.props.find((prop) => propName(prop) === 'icon')
|
|
295
|
+
// Both props are a supported contract. Leave the lower-precedence name
|
|
296
|
+
// intact instead of creating a duplicate icon binding.
|
|
297
|
+
if (name && !icon) {
|
|
298
|
+
edits.push(renamePropEdit(name, 'name', 'icon', 0))
|
|
299
|
+
}
|
|
300
|
+
} else if (component === 'Divider') {
|
|
301
|
+
for (const prop of node.props) {
|
|
302
|
+
if (propName(prop) === 'position')
|
|
303
|
+
edits.push(renamePropEdit(prop, 'position', 'align', 0))
|
|
304
|
+
}
|
|
305
|
+
} else if (component === 'Progress') {
|
|
306
|
+
edits.push(...progressEdit(node, 0, refusals))
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
for (const child of node.children || []) visit(child)
|
|
310
|
+
}
|
|
311
|
+
visit(ast)
|
|
312
|
+
|
|
313
|
+
if (refusals.length) return { migrated: source, changes: [], refusals }
|
|
314
|
+
const migrated = [...edits]
|
|
315
|
+
.sort((a, b) => b.start - a.start)
|
|
316
|
+
.reduce(
|
|
317
|
+
(text, edit) =>
|
|
318
|
+
text.slice(0, edit.start) + edit.text + text.slice(edit.end),
|
|
319
|
+
source,
|
|
320
|
+
)
|
|
321
|
+
return { migrated, changes: edits, refusals }
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
function main() {
|
|
325
|
+
const args = process.argv.slice(2)
|
|
326
|
+
if (args.includes('--help') || args.includes('-h')) {
|
|
327
|
+
console.log(USAGE)
|
|
328
|
+
return
|
|
329
|
+
}
|
|
330
|
+
const unknown = args.filter(
|
|
331
|
+
(arg) => arg.startsWith('-') && arg !== '--dry-run',
|
|
332
|
+
)
|
|
333
|
+
const targets = args.filter((arg) => !arg.startsWith('-'))
|
|
334
|
+
if (unknown.length || !targets.length) {
|
|
335
|
+
if (unknown.length) console.error(`Unknown option: ${unknown.join(' ')}`)
|
|
336
|
+
console.error(USAGE)
|
|
337
|
+
process.exit(1)
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
for (const target of targets) {
|
|
341
|
+
if (!fs.existsSync(target)) {
|
|
342
|
+
console.error(`Invalid path: ${target}`)
|
|
343
|
+
process.exit(1)
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
const dryRun = args.includes('--dry-run')
|
|
348
|
+
const files = [...new Set(targets.flatMap((target) => walk(target)))]
|
|
349
|
+
let changed = 0
|
|
350
|
+
const refusals = []
|
|
351
|
+
for (const file of files) {
|
|
352
|
+
const source = fs.readFileSync(file, 'utf8')
|
|
353
|
+
const result = migrateBaseProps(source)
|
|
354
|
+
if (result.refusals.length) {
|
|
355
|
+
for (const refusal of result.refusals) refusals.push({ file, ...refusal })
|
|
356
|
+
continue
|
|
357
|
+
}
|
|
358
|
+
if (!result.changes.length) continue
|
|
359
|
+
changed++
|
|
360
|
+
if (!dryRun) fs.writeFileSync(file, result.migrated)
|
|
361
|
+
console.log(`${file} (${result.changes.length})`)
|
|
362
|
+
}
|
|
363
|
+
console.log(
|
|
364
|
+
`\n${dryRun ? '[dry-run] would update' : 'Updated'} ${changed} files`,
|
|
365
|
+
)
|
|
366
|
+
if (refusals.length) {
|
|
367
|
+
console.error(
|
|
368
|
+
`\nNot converted — ${refusals.length} sites need a decision; affected files were left unchanged:`,
|
|
369
|
+
)
|
|
370
|
+
for (const refusal of refusals)
|
|
371
|
+
console.error(` ${refusal.file}:L${refusal.line} ${refusal.message}`)
|
|
372
|
+
process.exit(1)
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
const scriptPath = fileURLToPath(import.meta.url)
|
|
377
|
+
const invokedPath = process.argv[1]
|
|
378
|
+
if (invokedPath && fs.realpathSync(invokedPath) === fs.realpathSync(scriptPath))
|
|
379
|
+
main()
|