frappe-ui 1.0.0-beta.65 → 1.0.0-beta.67
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/package.json +5 -2
- package/scripts/migrate-destinations-v1.js +229 -0
- package/scripts/migrate-editor-v1.js +477 -0
- 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/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/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/molecules/editor/EditorFixedMenu.vue +2 -2
- package/src/molecules/editor/MenuItems.vue +2 -2
- package/src/molecules/editor/SuggestionExtension.ts +10 -3
- package/src/molecules/editor/extensions/emoji/emoji-extension.ts +1 -1
- package/src/molecules/editor/extensions/mention/mention-extension.test.ts +17 -1
- package/src/molecules/editor/extensions/mention/mention-extension.ts +11 -14
- package/src/molecules/editor/extensions/shared/suggestion-open.test.ts +1 -1
- package/src/molecules/editor/extensions/slash-commands/slash-commands-extension.ts +1 -1
- package/src/molecules/editor/extensions/suggestion/createSuggestionExtension.ts +2 -2
- package/src/molecules/editor/extensions/tag/tag-extension.ts +1 -1
- package/src/molecules/editor/kits.ts +1 -1
- package/src/molecules/editor/menu.test.ts +11 -0
- package/src/molecules/editor/useEditor.test.ts +30 -2
- 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/utils/warnDeprecated.ts +21 -4
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.67",
|
|
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,9 @@
|
|
|
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
|
+
"editor-v1": "./scripts/migrate-editor-v1.js",
|
|
25
|
+
"destinations-v1": "./scripts/migrate-destinations-v1.js"
|
|
24
26
|
},
|
|
25
27
|
"scripts": {
|
|
26
28
|
"test": "vitest --run",
|
|
@@ -102,6 +104,7 @@
|
|
|
102
104
|
"tailwind",
|
|
103
105
|
"vitepress",
|
|
104
106
|
"scripts/migrate-shortcuts-v1.js",
|
|
107
|
+
"scripts/migrate-editor-v1.js",
|
|
105
108
|
"experimental",
|
|
106
109
|
"experimental.ts",
|
|
107
110
|
"tsconfig.base.json"
|
|
@@ -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))
|