frappe-ui 0.1.209 → 0.1.211

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.
@@ -0,0 +1,101 @@
1
+ <template>
2
+ <div class="flex flex-col gap-1.5" :class="attrs.class" :style="attrs.style">
3
+ <FormLabel v-if="label" :label="label" size="sm" :required="required" />
4
+ <Combobox
5
+ v-model="model"
6
+ :placeholder="placeholder || `Select ${doctype}`"
7
+ :options="linkOptions"
8
+ @input="handleInputChange"
9
+ @focus="() => loadOptions('')"
10
+ :open-on-focus="true"
11
+ v-bind="attrsWithoutClassStyle"
12
+ >
13
+ <template #create-new="{ searchTerm }">
14
+ <LucidePlus class="size-4 mr-2" />
15
+ <span class="font-medium"> Create new {{ doctype }}</span>
16
+ </template>
17
+ </Combobox>
18
+ </div>
19
+ </template>
20
+
21
+ <script setup lang="ts">
22
+ import { watch, useAttrs, computed } from 'vue'
23
+ import { Combobox, type ComboboxOption } from '../../src/components/Combobox'
24
+ import FormLabel from '../../src/components/FormLabel.vue'
25
+ import debounce from '../../src/utils/debounce'
26
+ // @ts-ignore - Vue SFC without explicit types
27
+ import { createResource } from '../../src/resources'
28
+ import { frappeRequest } from '../../src/utils/frappeRequest'
29
+ import type { LinkProps, SelectOption } from './types'
30
+ import LucidePlus from '~icons/lucide/plus'
31
+
32
+ const props = withDefaults(defineProps<LinkProps>(), {
33
+ label: '',
34
+ filters: () => ({}),
35
+ })
36
+ const model = defineModel<string | null>({ default: '' })
37
+ const emit = defineEmits<{
38
+ (e: 'create', searchTerm: string): void
39
+ }>()
40
+ defineOptions({ inheritAttrs: false })
41
+
42
+ const attrs = useAttrs() as Record<string, any>
43
+ const attrsWithoutClassStyle = computed(() => {
44
+ return Object.fromEntries(
45
+ Object.entries(attrs).filter(([key]) => key !== 'class' && key !== 'style'),
46
+ )
47
+ })
48
+
49
+ const options = createResource({
50
+ url: 'frappe.desk.search.search_link',
51
+ params: {
52
+ doctype: props.doctype,
53
+ txt: '',
54
+ filters: props.filters,
55
+ },
56
+ method: 'POST',
57
+ resourceFetcher: frappeRequest,
58
+ transform: (data: SelectOption[]) => {
59
+ return data.map((doc) => ({
60
+ label: doc.label || doc.value,
61
+ value: doc.value,
62
+ }))
63
+ },
64
+ })
65
+
66
+ const createNewOption = {
67
+ type: 'custom' as const,
68
+ key: 'create_new',
69
+ label: 'Create New',
70
+ slotName: 'create-new',
71
+ condition: () => true,
72
+ onClick: ({ searchTerm }) => emit('create', searchTerm),
73
+ } as ComboboxOption
74
+
75
+ const linkOptions = computed(() => {
76
+ if (props.allowCreate) {
77
+ return [...(options.data || []), createNewOption]
78
+ }
79
+ return options.data
80
+ })
81
+
82
+ const loadOptions = (txt: string = '') => {
83
+ options.update({
84
+ params: {
85
+ txt,
86
+ doctype: props.doctype,
87
+ filters: props.filters,
88
+ },
89
+ })
90
+ options.reload()
91
+ }
92
+
93
+ const handleInputChange = debounce((inputString: string) => {
94
+ loadOptions(inputString || '')
95
+ }, 300)
96
+
97
+ watch([() => props.doctype, () => props.filters], () => loadOptions(''), {
98
+ immediate: true,
99
+ deep: true,
100
+ })
101
+ </script>
@@ -0,0 +1,2 @@
1
+ export { default as Link } from './Link.vue'
2
+ export type { LinkProps } from './types'
@@ -0,0 +1,10 @@
1
+ export interface LinkProps {
2
+ doctype: string
3
+ label?: string
4
+ placeholder?: string
5
+ filters?: Record<string, string | [string, string]>
6
+ required?: boolean
7
+ allowCreate?: boolean
8
+ }
9
+
10
+ export type SelectOption = { value: string; label: string }
package/frappe/index.js CHANGED
@@ -1,3 +1,6 @@
1
+ // components dependent on frappe backend
2
+ export * from './Link'
3
+
1
4
  // help components
2
5
  export { default as HelpModal } from './Help/HelpModal.vue'
3
6
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "frappe-ui",
3
- "version": "0.1.209",
3
+ "version": "0.1.211",
4
4
  "description": "A set of components and utilities for rapid UI development",
5
5
  "main": "./src/index.ts",
6
6
  "type": "module",
@@ -309,11 +309,8 @@ const selectedValue = computed({
309
309
  set(val) {
310
310
  query.value = ''
311
311
  if (val && !props.multiple) showOptions.value = false
312
- if (!props.multiple) {
313
- emit('update:modelValue', val)
314
- return
315
- }
316
312
  emit('update:modelValue', val)
313
+ emit('change', val)
317
314
  },
318
315
  })
319
316
 
@@ -38,6 +38,7 @@ const emit = defineEmits([
38
38
  'update:selectedOption',
39
39
  'focus',
40
40
  'blur',
41
+ 'input',
41
42
  ])
42
43
 
43
44
  const searchTerm = ref(getDisplayValue(props.modelValue))
@@ -238,6 +239,7 @@ const handleInputChange = (event: Event) => {
238
239
  internalModelValue.value = null
239
240
  emit('update:modelValue', null)
240
241
  }
242
+ emit('input', searchTerm.value)
241
243
  }
242
244
 
243
245
  const handleOpenChange = (open: boolean) => {
@@ -56,6 +56,7 @@ import TaskItem from '@tiptap/extension-task-item'
56
56
  import TaskList from '@tiptap/extension-task-list'
57
57
  import NamedColorExtension from './extensions/color'
58
58
  import NamedHighlightExtension from './extensions/highlight'
59
+ import improvedList from './extensions/list-extension'
59
60
 
60
61
  import { MentionExtension } from './extensions/mention'
61
62
  import TextEditorFixedMenu from './TextEditorFixedMenu.vue'
@@ -165,16 +166,7 @@ onMounted(() => {
165
166
  }).extend({
166
167
  addKeyboardShortcuts() {
167
168
  return {
168
- Backspace: () => {
169
- const selection = this.editor.view.state.selection
170
- const { $from } = selection
171
- if (
172
- !this.editor.can().liftListItem('listItem') ||
173
- $from.parentOffset > 0 || !selection.empty
174
- )
175
- return false
176
- return this.editor.commands.liftListItem('listItem')
177
- },
169
+ Backspace: () => improvedList(this.editor),
178
170
  }
179
171
  },
180
172
  }),
@@ -0,0 +1,32 @@
1
+ import { Selection } from '@tiptap/pm/state'
2
+ import { Editor } from '@tiptap/core'
3
+
4
+ export default function improvedList(editor: Editor) {
5
+ const selection = editor.view.state.selection
6
+ const { $from } = selection
7
+ if ($from.parentOffset > 0 || !selection.empty) return false
8
+
9
+ let nodeBefore = editor.view.state.doc.resolve($from.pos - 1).nodeBefore
10
+ if (!nodeBefore)
11
+ nodeBefore = editor.state.doc.resolve($from.before() - 1).nodeBefore
12
+ console.log(nodeBefore)
13
+ if (nodeBefore && ['bulletList', 'orderedList', 'listItem'].includes(nodeBefore.type.name)) {
14
+ // Join item with the last bullet item
15
+ editor
16
+ .chain()
17
+ .command(({ tr }) => {
18
+ const paragraphContent = $from.parent.content
19
+ const inList = nodeBefore.type.name === 'listItem'
20
+ console.log(inList)
21
+ tr.delete($from.before() - (inList ? 1 : 0), $from.after())
22
+ const insertPos = $from.pos - 4
23
+ tr.insert(insertPos, paragraphContent)
24
+ tr.setSelection(Selection.near(tr.doc.resolve(insertPos)))
25
+ })
26
+ .run()
27
+ return true
28
+ }
29
+
30
+ if (editor.can().liftListItem('listItem'))
31
+ return editor.commands.liftListItem('listItem')
32
+ }
package/src/index.ts CHANGED
@@ -22,7 +22,6 @@ export * from './components/FormControl'
22
22
  export { default as FormLabel } from './components/FormLabel.vue'
23
23
  export { default as GreenCheckIcon } from './components/GreenCheckIcon.vue'
24
24
  export { default as Input } from './components/Input.vue'
25
- export { default as Link } from './components/Link.vue'
26
25
  export { default as ListItem } from './components/ListItem.vue'
27
26
  export { default as LoadingIndicator } from './components/LoadingIndicator.vue'
28
27
  export { default as LoadingText } from './components/LoadingText.vue'
@@ -1,28 +0,0 @@
1
- <template>
2
- <component
3
- :is="isExternal ? 'a' : 'router-link'"
4
- v-bind="attributes"
5
- class="cursor-pointer text-blue-500 hover:text-blue-600"
6
- >
7
- <slot></slot>
8
- </component>
9
- </template>
10
-
11
- <script>
12
- export default {
13
- props: ['to'],
14
- computed: {
15
- attributes() {
16
- return {
17
- ...this.$attrs,
18
- target: this.isExternal ? '_blank' : null,
19
- to: !this.isExternal ? this.to : undefined,
20
- href: this.isExternal ? this.to : undefined,
21
- }
22
- },
23
- isExternal() {
24
- return this.to.startsWith('http')
25
- },
26
- },
27
- }
28
- </script>