frappe-ui 0.1.138 → 0.1.140

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 CHANGED
@@ -1,8 +1,9 @@
1
1
  {
2
2
  "name": "frappe-ui",
3
- "version": "0.1.138",
3
+ "version": "0.1.140",
4
4
  "description": "A set of components and utilities for rapid UI development",
5
5
  "main": "./src/index.ts",
6
+ "type": "module",
6
7
  "scripts": {
7
8
  "test": "vitest",
8
9
  "prettier": "yarn prettier -w ./src",
@@ -85,7 +85,6 @@ const props = withDefaults(defineProps<ButtonProps>(), {
85
85
  })
86
86
 
87
87
  const slots = useSlots()
88
- const router = useRouter()
89
88
 
90
89
  const buttonClasses = computed(() => {
91
90
  let solidClasses = {
@@ -216,6 +215,7 @@ const isIconButton = computed(() => {
216
215
 
217
216
  const handleClick = () => {
218
217
  if (props.route) {
218
+ const router = useRouter()
219
219
  return router.push(props.route)
220
220
  } else if (props.link) {
221
221
  return window.open(props.link, '_blank')
@@ -46,7 +46,7 @@ interface ComboboxProps {
46
46
  const props = defineProps<ComboboxProps>()
47
47
  const emit = defineEmits(['update:modelValue', 'update:selectedOption'])
48
48
 
49
- const searchTerm = ref('')
49
+ const searchTerm = ref(getDisplayValue(props.modelValue))
50
50
  const internalModelValue = ref(props.modelValue)
51
51
  const isOpen = ref(false)
52
52
  const userHasTyped = ref(false)
@@ -71,23 +71,23 @@ const onUpdateModelValue = (value: string | null) => {
71
71
  emit('update:selectedOption', selectedOpt)
72
72
  }
73
73
 
74
- const isGroup = (option: ComboboxOption): option is GroupedOption => {
74
+ function isGroup(option: ComboboxOption): option is GroupedOption {
75
75
  return typeof option === 'object' && 'group' in option
76
76
  }
77
77
 
78
- const getLabel = (option: SimpleOption): string => {
78
+ function getLabel(option: SimpleOption): string {
79
79
  return typeof option === 'string' ? option : option.label
80
80
  }
81
81
 
82
- const getValue = (option: SimpleOption): string => {
82
+ function getValue(option: SimpleOption): string {
83
83
  return typeof option === 'string' ? option : option.value
84
84
  }
85
85
 
86
- const isDisabled = (option: SimpleOption): boolean => {
86
+ function isDisabled(option: SimpleOption): boolean {
87
87
  return typeof option === 'object' && !!option.disabled
88
88
  }
89
89
 
90
- const getIcon = (option: SimpleOption): string | Component | undefined => {
90
+ function getIcon(option: SimpleOption): string | Component | undefined {
91
91
  return typeof option === 'object' ? option.icon : undefined
92
92
  }
93
93
 
@@ -105,9 +105,10 @@ const allOptionsFlat = computed(() => {
105
105
 
106
106
  function getDisplayValue(selectedValue: string | null | undefined): string {
107
107
  if (!selectedValue) return ''
108
- const selectedOption = allOptionsFlat.value.find(
109
- (opt) => getValue(opt) === selectedValue,
108
+ const options = props.options.flatMap((opt) =>
109
+ isGroup(opt) ? opt.options : opt,
110
110
  )
111
+ const selectedOption = options.find((opt) => getValue(opt) === selectedValue)
111
112
  return selectedOption ? getLabel(selectedOption) : selectedValue || ''
112
113
  }
113
114
 
@@ -27,18 +27,16 @@
27
27
  </template>
28
28
 
29
29
  <script setup lang="ts">
30
- import { onMounted, ref, useTemplateRef } from 'vue'
30
+ import { onMounted, ref, useTemplateRef, nextTick } from 'vue'
31
31
  import Button from '../Button/Button.vue'
32
32
  import TextInput from '../TextInput.vue'
33
33
  import Tooltip from '../Tooltip/Tooltip.vue'
34
34
  import LucideCheck from '~icons/lucide/check'
35
35
  import LucideX from '~icons/lucide/x'
36
+ import { isValidUrl } from '../../utils/url-validation'
36
37
 
37
38
  const props = defineProps<{
38
- show: boolean
39
39
  href: string
40
- onClose: () => void
41
- onUpdateHref: (href: string) => void
42
40
  }>()
43
41
 
44
42
  const emit = defineEmits<{
@@ -49,28 +47,17 @@ const emit = defineEmits<{
49
47
  const _href = ref(props.href)
50
48
  const input = useTemplateRef('input')
51
49
 
52
- // Simple URL validation regex
53
- const isValidUrl = (url: string) => {
54
- if (!url) return true
55
- const regex =
56
- /^(https?:\/\/)?([\da-z\.-]+)\.([a-z\.]{2,6})([\/\w \.-]*)*\/?$/i
57
- return regex.test(url)
58
- }
59
-
60
50
  const submitLink = () => {
61
- if (isValidUrl(_href.value)) {
51
+ if (_href.value === '' || isValidUrl(_href.value)) {
62
52
  emit('updateHref', _href.value)
63
53
  }
64
54
  }
65
55
 
66
- onMounted(() => {
67
- if (props.show) {
68
- setTimeout(() => {
69
- if (input.value?.el) {
70
- input.value.el.focus()
71
- input.value.el.select()
72
- }
73
- }, 0)
56
+ onMounted(async () => {
57
+ await nextTick()
58
+ if (input.value?.el) {
59
+ input.value.el.focus()
60
+ input.value.el.select()
74
61
  }
75
62
  })
76
63
  </script>
@@ -1,8 +1,11 @@
1
- import Link from '@tiptap/extension-link'
2
1
  import { createApp, h } from 'vue'
2
+ import Link from '@tiptap/extension-link'
3
+ import tippy, { type Instance as TippyInstance } from 'tippy.js'
4
+ import { getMarkRange, Range, Editor } from '@tiptap/core'
5
+ import { MarkType, Mark as ProseMirrorMark } from 'prosemirror-model'
6
+ import { Plugin, PluginKey } from 'prosemirror-state'
3
7
  import EditLink from './EditLink.vue'
4
- import tippy from 'tippy.js'
5
- import { getMarkRange, Mark, Range, Editor } from '@tiptap/core'
8
+ import { linkPasteHandler } from './linkPasteHandler'
6
9
 
7
10
  declare module '@tiptap/core' {
8
11
  interface Commands<ReturnType> {
@@ -22,6 +25,7 @@ export const LinkExtension = Link.extend({
22
25
  openOnClick: false,
23
26
  autolink: true,
24
27
  defaultProtocol: 'https',
28
+ linkOnPaste: false,
25
29
  }
26
30
  },
27
31
 
@@ -36,7 +40,8 @@ export const LinkExtension = Link.extend({
36
40
  const { doc } = state
37
41
 
38
42
  let range: Range | undefined = undefined
39
- let mark: Mark | undefined = undefined
43
+ let mark: ProseMirrorMark | undefined = undefined
44
+ let shouldDelayPopover = false
40
45
 
41
46
  // Check if cursor is within a link or if there's a selection
42
47
  if (from === to) {
@@ -49,8 +54,15 @@ export const LinkExtension = Link.extend({
49
54
  .resolve(markRange.from)
50
55
  .marks()
51
56
  .find((m) => m.type === this.type)
57
+
58
+ // Select the link text
59
+ editor
60
+ .chain()
61
+ .setTextSelection({ from: markRange.from, to: markRange.to })
62
+ .run()
63
+ shouldDelayPopover = true
52
64
  } else {
53
- // No selection and not within a link
65
+ // No selection and not within a link, and cursor not in link
54
66
  return false
55
67
  }
56
68
  } else {
@@ -69,49 +81,58 @@ export const LinkExtension = Link.extend({
69
81
  const selectionFrom = range.from
70
82
  const selectionTo = range.to
71
83
 
72
- openLinkEditor(existingHref, editor.view.dom)
73
- .then((href) => {
74
- if (href === null) {
75
- return
76
- }
84
+ const showPopover = () => {
85
+ openLinkEditor(existingHref, editor.view.dom)
86
+ .then((href) => {
87
+ if (href === null) {
88
+ return
89
+ }
77
90
 
78
- let chain = editor.chain().focus(null, { scrollIntoView: false })
91
+ let chain = editor
92
+ .chain()
93
+ .focus(null, { scrollIntoView: false })
79
94
 
80
- if (href === '') {
81
- chain
95
+ if (href === '') {
96
+ chain
97
+ .setTextSelection({ from: selectionFrom, to: selectionTo })
98
+ .unsetLink()
99
+ .command(({ tr }) => {
100
+ tr.setStoredMarks([])
101
+ return true
102
+ })
103
+ .run()
104
+ return
105
+ }
106
+
107
+ chain = chain
82
108
  .setTextSelection({ from: selectionFrom, to: selectionTo })
83
- .unsetLink()
109
+ .setLink({ href })
110
+ .setTextSelection(selectionTo)
84
111
  .command(({ tr }) => {
85
112
  tr.setStoredMarks([])
86
113
  return true
87
114
  })
88
- .run()
89
- return
90
- }
91
-
92
- chain = chain
93
- .setTextSelection({ from: selectionFrom, to: selectionTo })
94
- .setLink({ href })
95
- .setTextSelection(selectionTo) // Move cursor to the end of the link
96
- .command(({ tr }) => {
97
- tr.setStoredMarks([]) // Clear stored marks to avoid applying link to next typed char
98
- return true
99
- })
100
-
101
- const posAfterLink = selectionTo
102
- const charAfter =
103
- posAfterLink < doc.content.size
104
- ? doc.textBetween(posAfterLink, posAfterLink + 1)
105
- : null
106
-
107
- // Insert a space after the link if needed
108
- if (charAfter === null || charAfter !== ' ') {
109
- chain = chain.insertContent(' ')
110
- }
111
-
112
- chain.run()
113
- })
114
- .catch(() => {}) // Ignore cancellation
115
+
116
+ const posAfterLink = selectionTo
117
+ const charAfter =
118
+ posAfterLink < doc.content.size
119
+ ? doc.textBetween(posAfterLink, posAfterLink + 1)
120
+ : null
121
+
122
+ if (charAfter === null || charAfter !== ' ') {
123
+ chain = chain.insertContent(' ')
124
+ }
125
+
126
+ chain.run()
127
+ })
128
+ .catch(() => {})
129
+ }
130
+
131
+ if (shouldDelayPopover) {
132
+ requestAnimationFrame(showPopover)
133
+ } else {
134
+ showPopover()
135
+ }
115
136
 
116
137
  return true
117
138
  },
@@ -123,6 +144,27 @@ export const LinkExtension = Link.extend({
123
144
  'Mod-k': () => this.editor.commands.openLinkEditor(),
124
145
  }
125
146
  },
147
+
148
+ addProseMirrorPlugins() {
149
+ let plugins = this.parent?.() || []
150
+
151
+ plugins.push(
152
+ linkPasteHandler({
153
+ editor: this.editor,
154
+ defaultProtocol: this.options.defaultProtocol,
155
+ type: this.type,
156
+ }),
157
+ )
158
+
159
+ plugins.push(
160
+ clearLinkOnBoundaryPlugin({
161
+ editor: this.editor,
162
+ type: this.type,
163
+ }),
164
+ )
165
+
166
+ return plugins
167
+ },
126
168
  })
127
169
 
128
170
  function openLinkEditor(href: string, anchor: HTMLElement): Promise<string> {
@@ -138,16 +180,17 @@ function openLinkEditor(href: string, anchor: HTMLElement): Promise<string> {
138
180
  if (selection && selection.rangeCount > 0) {
139
181
  const range = selection.getRangeAt(0)
140
182
  const rect = range.getBoundingClientRect()
183
+ const isCollapsed = range.collapsed
141
184
 
142
185
  virtualReference = {
143
186
  getBoundingClientRect: () => ({
144
187
  width: 0,
145
- height: 0,
188
+ height: rect.height,
146
189
  top: rect.top,
147
- right: rect.left + rect.width / 2,
148
- bottom: rect.top,
149
- left: rect.left + rect.width / 2,
150
- x: rect.left + rect.width / 2,
190
+ right: isCollapsed ? rect.left : rect.right,
191
+ bottom: rect.bottom,
192
+ left: rect.left,
193
+ x: rect.left,
151
194
  y: rect.top,
152
195
  toJSON: () => {},
153
196
  }),
@@ -158,18 +201,47 @@ function openLinkEditor(href: string, anchor: HTMLElement): Promise<string> {
158
201
  }
159
202
  }
160
203
 
161
- const app = createApp({
204
+ let app: ReturnType<typeof createApp> | null = null
205
+ let tippyInstance: TippyInstance | null = null
206
+ let isDestroyed = false
207
+ let promiseSettled = false
208
+
209
+ const settlePromise = (action: 'resolve' | 'reject', value?: any) => {
210
+ if (promiseSettled) return
211
+ promiseSettled = true
212
+ if (action === 'resolve') {
213
+ resolve(value)
214
+ } else {
215
+ reject(value)
216
+ }
217
+ }
218
+
219
+ const destroy = () => {
220
+ if (isDestroyed) return
221
+ isDestroyed = true
222
+
223
+ settlePromise('reject', 'Link editing cancelled or destroyed')
224
+
225
+ requestAnimationFrame(() => {
226
+ tippyInstance?.destroy()
227
+ app?.unmount()
228
+ container?.remove()
229
+ app = null
230
+ tippyInstance = null
231
+ })
232
+ }
233
+
234
+ app = createApp({
162
235
  render() {
163
236
  return h(EditLink, {
164
- show: true,
165
237
  href,
166
238
  onClose: () => {
239
+ settlePromise('reject', 'Link editing cancelled')
167
240
  destroy()
168
- reject('Link editing cancelled')
169
241
  },
170
242
  onUpdateHref: (newHref: string) => {
243
+ settlePromise('resolve', newHref)
171
244
  destroy()
172
- resolve(newHref)
173
245
  },
174
246
  })
175
247
  },
@@ -177,7 +249,7 @@ function openLinkEditor(href: string, anchor: HTMLElement): Promise<string> {
177
249
 
178
250
  app.mount(container)
179
251
 
180
- const tippyInstance = tippy(anchor, {
252
+ tippyInstance = tippy(anchor, {
181
253
  getReferenceClientRect: () => virtualReference.getBoundingClientRect(),
182
254
  content: container,
183
255
  trigger: 'manual',
@@ -189,20 +261,69 @@ function openLinkEditor(href: string, anchor: HTMLElement): Promise<string> {
189
261
  maxWidth: 'none',
190
262
  onHidden() {
191
263
  destroy()
192
- reject('Link editing cancelled')
193
264
  },
265
+ hideOnClick: true,
266
+ interactiveDebounce: 75,
194
267
  })
195
268
 
196
- function destroy() {
197
- setTimeout(() => {
198
- tippyInstance.destroy()
199
- app.unmount()
200
- container.remove()
201
- }, 0)
269
+ if (!tippyInstance) {
270
+ container.remove()
271
+ settlePromise('reject', 'Failed to initialize link editor tooltip')
272
+ return
202
273
  }
203
274
 
204
275
  tippyInstance.show()
205
276
  })
206
277
  }
207
278
 
279
+ function clearLinkOnBoundaryPlugin(options: {
280
+ editor: Editor
281
+ type: MarkType
282
+ }) {
283
+ return new Plugin({
284
+ key: new PluginKey('clearLinkMarkOnBoundary'),
285
+ appendTransaction: (transactions, oldState, newState) => {
286
+ if (!options.editor.isEditable) {
287
+ return null
288
+ }
289
+
290
+ const { tr, doc, selection, storedMarks } = newState
291
+ const { $from, empty } = selection
292
+
293
+ if (!empty || !storedMarks || storedMarks.length === 0) {
294
+ // Only apply for cursor selections and if there are stored marks
295
+ return null
296
+ }
297
+
298
+ const linkMarkType = options.type
299
+ const hasStoredLinkMark = storedMarks.some(
300
+ (mark) => mark.type === linkMarkType,
301
+ )
302
+
303
+ if (!hasStoredLinkMark) {
304
+ return null
305
+ }
306
+
307
+ // Check if the cursor position itself has an active link mark in the document
308
+ const marksAtCursor = $from.marks()
309
+ const activeLinkAtCursor = marksAtCursor.some(
310
+ (mark) => mark.type === linkMarkType,
311
+ )
312
+
313
+ if (activeLinkAtCursor) {
314
+ // If there's an actual link mark active in the document at the cursor,
315
+ // then it's correct for the stored mark to be there.
316
+ return null
317
+ }
318
+
319
+ // If we are here, it means:
320
+ // 1. Selection is a cursor (empty).
321
+ // 2. There's a stored link mark.
322
+ // 3. There's no active link mark in the document at the cursor position.
323
+ // This indicates the stored link mark should be cleared.
324
+ return tr.setStoredMarks([])
325
+ },
326
+ })
327
+ }
328
+
208
329
  export default LinkExtension
@@ -0,0 +1,51 @@
1
+ import { Editor } from '@tiptap/core'
2
+ import { MarkType } from '@tiptap/pm/model'
3
+ import { Plugin, PluginKey } from '@tiptap/pm/state'
4
+ import { isValidUrl } from '../../utils/url-validation'
5
+
6
+ type PasteHandlerOptions = {
7
+ editor: Editor
8
+ defaultProtocol: string
9
+ type: MarkType
10
+ }
11
+
12
+ export function linkPasteHandler(options: PasteHandlerOptions): Plugin {
13
+ return new Plugin({
14
+ key: new PluginKey('handlePasteLink'),
15
+ props: {
16
+ handlePaste: (view, event, slice) => {
17
+ const { state } = view
18
+ const { selection } = state
19
+ const { empty } = selection
20
+
21
+ if (empty) {
22
+ return false
23
+ }
24
+
25
+ let textContent = ''
26
+ slice.content.forEach((node) => {
27
+ textContent += node.textContent
28
+ })
29
+ if (!textContent) {
30
+ return false
31
+ }
32
+
33
+ let link = isValidUrl(textContent) ? textContent : null
34
+ if (!link) {
35
+ return false
36
+ }
37
+
38
+ return options.editor
39
+ .chain()
40
+ .setTextSelection({ from: selection.from, to: selection.to })
41
+ .setLink({ href: link })
42
+ .setTextSelection(selection.to)
43
+ .command(({ tr }) => {
44
+ tr.setStoredMarks([])
45
+ return true
46
+ })
47
+ .run()
48
+ },
49
+ },
50
+ })
51
+ }
@@ -1,5 +1,5 @@
1
- const tailwindColors = require('tailwindcss/colors')
2
- const colorsData = require('./colors.json')
1
+ import tailwindColors from 'tailwindcss/colors'
2
+ import colorsData from './colors.json' assert { type: 'json' }
3
3
 
4
4
  function generateColorPalette() {
5
5
  const colorPalette = {
@@ -108,8 +108,4 @@ function generateSemanticColors() {
108
108
  return output
109
109
  }
110
110
 
111
- module.exports = {
112
- generateColorPalette,
113
- generateCSSVariables,
114
- generateSemanticColors,
115
- }
111
+ export { generateColorPalette, generateCSSVariables, generateSemanticColors }
@@ -3,8 +3,8 @@
3
3
  * to colors JSON object that can be used in the Tailwind config.
4
4
  */
5
5
 
6
- const fs = require('fs')
7
- const path = require('path')
6
+ import fs from 'fs'
7
+ import path from 'path'
8
8
 
9
9
  function main() {
10
10
  const variables = getVariables()
@@ -144,7 +144,7 @@ function getVariables() {
144
144
  console.log('Please provide path to variables.json file')
145
145
  process.exit(1)
146
146
  }
147
- return require(variablesJSONPath)
147
+ return JSON.parse(fs.readFileSync(variablesJSONPath, 'utf-8'))
148
148
  }
149
149
 
150
150
  main()
@@ -1,9 +1,9 @@
1
- const plugin = require('tailwindcss/plugin')
2
- const {
1
+ import plugin from 'tailwindcss/plugin'
2
+ import {
3
3
  generateColorPalette,
4
4
  generateSemanticColors,
5
5
  generateCSSVariables,
6
- } = require('./colorPalette')
6
+ } from './colorPalette.js'
7
7
 
8
8
  let colorPalette = generateColorPalette()
9
9
  let semanticColors = generateSemanticColors()
@@ -49,7 +49,7 @@ let componentStyles = {
49
49
  },
50
50
  }
51
51
 
52
- module.exports = plugin(
52
+ export default plugin(
53
53
  function ({ addBase, addComponents, theme }) {
54
54
  addBase({ ...globalStyles(theme), ...cssVariables })
55
55
  addComponents(componentStyles)
@@ -342,7 +342,7 @@ module.exports = plugin(
342
342
  css: {
343
343
  fontSize: '14px',
344
344
  fontWeight: 420,
345
- lineHeight: 1.6,
345
+ lineHeight: 1.5,
346
346
  letterSpacing: '0.02em',
347
347
  h1: {
348
348
  fontSize: em(20, 14),
@@ -361,7 +361,7 @@ module.exports = plugin(
361
361
  },
362
362
  p: {
363
363
  marginTop: '0.5rem',
364
- marginBottom: '1rem',
364
+ marginBottom: '0.5rem',
365
365
  },
366
366
  'ul > li': {
367
367
  margin: '0.5rem 0',
@@ -1,10 +1,9 @@
1
- const themePlugin = require('./plugin')
1
+ import themePlugin from './plugin.js'
2
+ import forms from '@tailwindcss/forms'
3
+ import typography from '@tailwindcss/typography'
4
+
2
5
  /** @type {import('tailwindcss').Config} */
3
- module.exports = {
6
+ export default {
4
7
  darkMode: ['selector', '[data-theme="dark"]'],
5
- plugins: [
6
- require('@tailwindcss/forms'),
7
- require('@tailwindcss/typography'),
8
- themePlugin,
9
- ],
8
+ plugins: [forms, typography, themePlugin],
10
9
  }
@@ -1,6 +1,6 @@
1
- const fs = require('fs')
2
- const path = require('path')
3
- const colors = require('./colors.json')
1
+ import fs from 'fs'
2
+ import path from 'path'
3
+ import colors from './colors.json' assert { type: 'json' }
4
4
 
5
5
  function generateClassMap() {
6
6
  const classMap = {
@@ -1,6 +1,6 @@
1
- const preset = require('../tailwind/preset')
1
+ import preset from '../tailwind/preset.js'
2
2
  console.warn(
3
3
  '`frappe-ui/src/utils/tailwind.config.js` is deprecated. Use `frappe-ui/tailwind/preset.js` instead.',
4
4
  )
5
5
  // keep backwards compatible for now
6
- module.exports = preset
6
+ export default preset
@@ -0,0 +1,39 @@
1
+ /**
2
+ * Validates if the given string is a valid URL, including common protocols,
3
+ * relative paths, and anchor links. It also allows an empty string.
4
+ *
5
+ * @param url The URL string to validate.
6
+ * @returns True if the URL is considered valid, false otherwise.
7
+ */
8
+ export function isValidUrl(url: string): boolean {
9
+ if (url === '') {
10
+ return true // Allows empty string as per documentation
11
+ }
12
+
13
+ // Regex for absolute URLs with common schemes (http, https, mailto, tel, //)
14
+ // Allows for paths, query strings, and fragments.
15
+ if (/^(https?:\/\/|mailto:|tel:|\/\/)[^\s]+$/i.test(url)) {
16
+ return true
17
+ }
18
+
19
+ // Regex for:
20
+ // 1. Relative paths (starting with / or .)
21
+ // 2. Anchor links (starting with #)
22
+ // These allow for most characters except whitespace, as the initial characters
23
+ // prevent misinterpretation as a scheme like 'javascript:'.
24
+ if (/^([./#][^\s]*)$/i.test(url)) {
25
+ return true
26
+ }
27
+
28
+ // Regex for "Schemeless" paths (e.g., page.html, my-document, slug-name)
29
+ // Must start with a word character or hyphen.
30
+ // Subsequent characters are restricted to common URL path/query/fragment characters
31
+ // (alphanumeric, -, _, ., /, #, ?, =, &, %).
32
+ // This prevents colons (avoiding 'javascript:' like schemes) and other problematic
33
+ // characters like spaces, (, ), !, {, }.
34
+ if (/^[\w\-]([\w\-./#?=&%]*)$/i.test(url)) {
35
+ return true
36
+ }
37
+
38
+ return false
39
+ }
@@ -1,7 +1,7 @@
1
- const path = require('path')
2
- const fs = require('fs')
1
+ import path from 'path'
2
+ import fs from 'fs'
3
3
 
4
- function buildConfig(options = {}) {
4
+ export function buildConfig(options = {}) {
5
5
  let outDir = options.outDir || findOutputDir()
6
6
  if (!outDir) {
7
7
  console.error(
@@ -165,5 +165,3 @@ function findAppDir() {
165
165
 
166
166
  return null
167
167
  }
168
-
169
- module.exports = { buildConfig }
@@ -1,7 +1,7 @@
1
- const fs = require('fs').promises
2
- const path = require('path')
1
+ import fs from 'fs/promises'
2
+ import path from 'path'
3
3
 
4
- class DocTypeInterfaceGenerator {
4
+ export class DocTypeInterfaceGenerator {
5
5
  constructor(appsPath, appDoctypeMap, outputPath) {
6
6
  this.appsPath = appsPath
7
7
  this.appDoctypeMap = appDoctypeMap
@@ -244,5 +244,3 @@ class DocTypeInterfaceGenerator {
244
244
  `
245
245
  }
246
246
  }
247
-
248
- exports.DocTypeInterfaceGenerator = DocTypeInterfaceGenerator
@@ -1,6 +1,6 @@
1
- const { getCommonSiteConfig } = require('./utils')
1
+ import { getCommonSiteConfig } from './utils.js'
2
2
 
3
- function frappeProxy({
3
+ export function frappeProxy({
4
4
  port = 8080,
5
5
  source = '^/(app|login|api|assets|files|private)',
6
6
  } = {}) {
@@ -32,5 +32,3 @@ function frappeProxy({
32
32
  }),
33
33
  }
34
34
  }
35
-
36
- exports.frappeProxy = frappeProxy
@@ -1,7 +1,12 @@
1
- const path = require('path')
2
- const { spawn } = require('child_process')
1
+ import path from 'path'
2
+ import { spawn } from 'child_process'
3
+ import { fileURLToPath } from 'url'
3
4
 
4
- function frappeTypes(options = {}) {
5
+ // Get __dirname equivalent in ES modules
6
+ const __filename = fileURLToPath(import.meta.url)
7
+ const __dirname = path.dirname(__filename)
8
+
9
+ export function frappeTypes(options = {}) {
5
10
  let childProcess = null
6
11
 
7
12
  return {
@@ -63,5 +68,3 @@ function frappeTypes(options = {}) {
63
68
  },
64
69
  }
65
70
  }
66
-
67
- exports.frappeTypes = frappeTypes
@@ -1,6 +1,7 @@
1
- const path = require('path')
2
- const { findAppsFolder } = require('./utils')
3
- const { DocTypeInterfaceGenerator } = require('./doctypeInterfaceGenerator')
1
+ import path from 'path'
2
+ import { fileURLToPath } from 'url'
3
+ import { findAppsFolder } from './utils.js'
4
+ import { DocTypeInterfaceGenerator } from './doctypeInterfaceGenerator.js'
4
5
 
5
6
  // Handle termination signals to exit cleanly
6
7
  process.on('SIGINT', () => process.exit(0))
@@ -49,7 +50,8 @@ async function main() {
49
50
  }
50
51
 
51
52
  // Execute if run directly
52
- if (require.main === module) {
53
+ const currentFilePath = fileURLToPath(import.meta.url)
54
+ if (process.argv[1] === currentFilePath) {
53
55
  main()
54
56
  .then(() => process.exit(0))
55
57
  .catch((err) => {
package/vite/index.js CHANGED
@@ -1,8 +1,8 @@
1
- const { lucideIcons } = require('./lucideIcons')
2
- const { frappeProxy } = require('./frappeProxy')
3
- const { frappeTypes } = require('./frappeTypes')
4
- const { jinjaBootData } = require('./jinjaBootData')
5
- const { buildConfig } = require('./buildConfig')
1
+ import { lucideIcons } from './lucideIcons.js'
2
+ import { frappeProxy } from './frappeProxy.js'
3
+ import { frappeTypes } from './frappeTypes.js'
4
+ import { jinjaBootData } from './jinjaBootData.js'
5
+ import { buildConfig } from './buildConfig.js'
6
6
 
7
7
  function frappeuiPlugin(
8
8
  options = {
@@ -32,4 +32,4 @@ function frappeuiPlugin(
32
32
  return plugins
33
33
  }
34
34
 
35
- module.exports = frappeuiPlugin
35
+ export default frappeuiPlugin
@@ -1,4 +1,4 @@
1
- function jinjaBootData() {
1
+ export function jinjaBootData() {
2
2
  return {
3
3
  name: 'frappeui-jinja-boot-data-plugin',
4
4
  transformIndexHtml(html, context) {
@@ -21,5 +21,3 @@ function jinjaBootData() {
21
21
  },
22
22
  }
23
23
  }
24
-
25
- module.exports = { jinjaBootData }
@@ -1,19 +1,19 @@
1
- const LucideIcons = require('lucide-static')
2
- const Icons = require('unplugin-icons/vite')
3
- const Components = require('unplugin-vue-components/vite')
4
- const IconsResolver = require('unplugin-icons/resolver')
1
+ import * as LucideIcons from 'lucide-static'
2
+ import Icons from 'unplugin-icons/vite'
3
+ import Components from 'unplugin-vue-components/vite'
4
+ import IconsResolver from 'unplugin-icons/resolver'
5
5
 
6
- function lucideIconsPlugin() {
6
+ export function lucideIcons() {
7
7
  return [
8
- Components.default({
8
+ Components({
9
9
  resolvers: [
10
- IconsResolver.default({
10
+ IconsResolver({
11
11
  prefix: false,
12
12
  enabledCollections: ['lucide'],
13
13
  }),
14
14
  ],
15
15
  }),
16
- Icons.default({
16
+ Icons({
17
17
  customCollections: {
18
18
  lucide: getIcons(),
19
19
  },
@@ -63,5 +63,3 @@ function camelToDash(key) {
63
63
  }
64
64
  return [withNumber]
65
65
  }
66
-
67
- exports.lucideIcons = lucideIconsPlugin
package/vite/utils.js CHANGED
@@ -1,14 +1,14 @@
1
- const path = require('path')
2
- const fs = require('fs')
1
+ import path from 'path'
2
+ import fs from 'fs'
3
3
 
4
- function getConfig() {
4
+ export function getConfig() {
5
5
  let configPath = path.join(process.cwd(), 'frappeui.json')
6
6
  if (fs.existsSync(configPath)) {
7
7
  return JSON.parse(fs.readFileSync(configPath))
8
8
  }
9
9
  }
10
10
 
11
- function getCommonSiteConfig() {
11
+ export function getCommonSiteConfig() {
12
12
  let currentDir = path.resolve('.')
13
13
  // traverse up till we find frappe-bench with sites directory
14
14
  while (currentDir !== '/') {
@@ -27,7 +27,7 @@ function getCommonSiteConfig() {
27
27
  return null
28
28
  }
29
29
 
30
- function findAppsFolder() {
30
+ export function findAppsFolder() {
31
31
  let currentDir = process.cwd()
32
32
  while (currentDir !== '/') {
33
33
  if (
@@ -40,9 +40,3 @@ function findAppsFolder() {
40
40
  }
41
41
  return null
42
42
  }
43
-
44
- module.exports = {
45
- getConfig,
46
- getCommonSiteConfig,
47
- findAppsFolder,
48
- }