fuma 2.1.3 → 2.1.5

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.
Files changed (60) hide show
  1. package/dist/_doc/DocExample.svelte +1 -1
  2. package/dist/command/command.svelte.d.ts +4 -2
  3. package/dist/command/command.svelte.js +29 -2
  4. package/dist/command/index.d.ts +1 -1
  5. package/dist/command/index.js +1 -1
  6. package/dist/index.d.ts +11 -10
  7. package/dist/index.js +11 -10
  8. package/dist/input/InputBoolean.svelte +1 -1
  9. package/dist/input/InputBoolean.svelte.d.ts +1 -1
  10. package/dist/input/InputNumber.svelte +1 -1
  11. package/dist/input/InputNumber.svelte.d.ts +1 -1
  12. package/dist/input/InputRadio.svelte +1 -1
  13. package/dist/input/InputRadio.svelte.d.ts +1 -1
  14. package/dist/input/InputRange.svelte +1 -1
  15. package/dist/input/InputRange.svelte.d.ts +1 -1
  16. package/dist/input/InputRelation.svelte +133 -52
  17. package/dist/input/InputRelation.svelte.d.ts +60 -4
  18. package/dist/input/InputSelect.svelte +2 -2
  19. package/dist/input/InputSelectNative.svelte +2 -2
  20. package/dist/input/InputSelectNative.svelte.d.ts +2 -2
  21. package/dist/input/InputString.svelte +1 -1
  22. package/dist/input/InputString.svelte.d.ts +1 -1
  23. package/dist/input/InputTextarea.svelte +1 -1
  24. package/dist/input/InputTextarea.svelte.d.ts +1 -1
  25. package/dist/input/index.d.ts +1 -1
  26. package/dist/loading/Loading.svelte +43 -0
  27. package/dist/loading/Loading.svelte.d.ts +11 -0
  28. package/dist/loading/index.d.ts +1 -0
  29. package/dist/loading/index.js +1 -0
  30. package/dist/popover/Popover.svelte +1 -1
  31. package/dist/popover/index.d.ts +1 -1
  32. package/dist/popover/index.js +1 -1
  33. package/dist/popover/popover.svelte.d.ts +4 -1
  34. package/dist/popover/popover.svelte.js +14 -1
  35. package/dist/remote/index.d.ts +1 -1
  36. package/dist/remote/index.js +1 -1
  37. package/dist/remote/useForm.d.ts +10 -0
  38. package/dist/remote/useForm.js +19 -2
  39. package/dist/search/Spans.svelte +1 -1
  40. package/dist/search/Spans.svelte.d.ts +1 -1
  41. package/dist/search/index.d.ts +1 -1
  42. package/dist/search/index.js +1 -1
  43. package/dist/search/search.d.ts +38 -0
  44. package/dist/search/search.js +15 -2
  45. package/dist/ui/copy/ButtonCopy.svelte +1 -1
  46. package/dist/ui/copy/ButtonCopy.svelte.d.ts +1 -1
  47. package/dist/ui/copy/index.d.ts +1 -1
  48. package/dist/ui/copy/index.js +1 -1
  49. package/dist/ui/drawer/Drawer.svelte +1 -1
  50. package/dist/ui/drawer/index.d.ts +1 -1
  51. package/dist/ui/drawer/index.js +1 -1
  52. package/dist/ui/pagination/Pagination.svelte +1 -1
  53. package/dist/ui/range/RangePickerButton.svelte +1 -1
  54. package/dist/ui/table/TableFieldsEdition.svelte +1 -1
  55. package/dist/ui/table/head/TableHeadDate.svelte +1 -1
  56. package/dist/ui/table/head/TableHeadNumber.svelte +1 -1
  57. package/dist/ui/table/head/TableHeadSelect.svelte +1 -1
  58. package/dist/utils/options.d.ts +1 -1
  59. package/dist/utils/options.js +1 -1
  60. package/package.json +3 -3
@@ -2,7 +2,7 @@
2
2
  import { type Snippet } from 'svelte'
3
3
  import { ButtonCopy } from '../ui/copy/index.js'
4
4
  import { useMode } from '../ui/mode/useMode.svelte.js'
5
- import { highlight, transformPackageImports } from './highlight.ts'
5
+ import { highlight, transformPackageImports } from './highlight.js'
6
6
 
7
7
  let {
8
8
  code,
@@ -2,13 +2,15 @@ type CommandOptions = {
2
2
  isEnable?: () => boolean;
3
3
  onSelect?: (index: number) => unknown;
4
4
  onFocus?: (index: number) => unknown;
5
+ hotKey?: string;
5
6
  };
6
- export declare function useCommand({ onSelect, onFocus, isEnable }?: CommandOptions): {
7
+ export declare function useCommand({ onSelect, onFocus, isEnable, hotKey }?: CommandOptions): {
7
8
  readonly focusIndex: number;
8
- readonly selectedIndex: number;
9
+ selectedIndex: number;
9
10
  trigger: {
10
11
  [x: symbol]: (node: HTMLElement) => () => void;
11
12
  };
13
+ focusTrigger: () => void;
12
14
  item: (index: number, scrollMargin?: string) => {
13
15
  [x: symbol]: (node: HTMLElement) => () => void;
14
16
  };
@@ -1,8 +1,9 @@
1
1
  import { createAttachmentKey } from 'svelte/attachments';
2
2
  import { on } from 'svelte/events';
3
- export function useCommand({ onSelect, onFocus, isEnable = () => true } = {}) {
3
+ export function useCommand({ onSelect, onFocus, isEnable = () => true, hotKey } = {}) {
4
4
  let focusIndex = $state(0);
5
5
  let selectedIndex = $state(-1);
6
+ let triggerElement = $state();
6
7
  const items = [];
7
8
  function onKeydown(event) {
8
9
  if (!isEnable())
@@ -35,6 +36,20 @@ export function useCommand({ onSelect, onFocus, isEnable = () => true } = {}) {
35
36
  function scrollFocusIntoView() {
36
37
  items.at(focusIndex)?.scrollIntoView({ block: 'nearest', behavior: 'smooth' });
37
38
  }
39
+ async function handleHotKey(event) {
40
+ const { metaKey, ctrlKey, key } = event;
41
+ if ((metaKey || ctrlKey) && key === hotKey) {
42
+ event.preventDefault();
43
+ focusTrigger();
44
+ return;
45
+ }
46
+ }
47
+ function focusTrigger() {
48
+ if (triggerElement instanceof HTMLInputElement ||
49
+ triggerElement instanceof HTMLTextAreaElement) {
50
+ triggerElement.select();
51
+ }
52
+ }
38
53
  return {
39
54
  get focusIndex() {
40
55
  return focusIndex;
@@ -42,11 +57,23 @@ export function useCommand({ onSelect, onFocus, isEnable = () => true } = {}) {
42
57
  get selectedIndex() {
43
58
  return selectedIndex;
44
59
  },
60
+ set selectedIndex(index) {
61
+ selectedIndex = index;
62
+ },
45
63
  trigger: {
46
64
  [createAttachmentKey()]: (node) => {
47
- return on(node, 'keydown', onKeydown);
65
+ triggerElement = node;
66
+ const cleanups = [];
67
+ cleanups.push(on(node, 'keydown', onKeydown));
68
+ if (hotKey)
69
+ cleanups.push(on(window, 'keydown', handleHotKey));
70
+ return () => {
71
+ for (const cleanup of cleanups)
72
+ cleanup();
73
+ };
48
74
  }
49
75
  },
76
+ focusTrigger,
50
77
  item: (index, scrollMargin = '8px') => ({
51
78
  [createAttachmentKey()]: (node) => {
52
79
  node.style.scrollMargin = scrollMargin;
@@ -1 +1 @@
1
- export * from './command.svelte.ts';
1
+ export * from './command.svelte.js';
@@ -1 +1 @@
1
- export * from './command.svelte.ts';
1
+ export * from './command.svelte.js';
package/dist/index.d.ts CHANGED
@@ -1,10 +1,11 @@
1
- export * from './action/index.ts';
2
- export * from './command/index.ts';
3
- export * from './input/index.ts';
4
- export * from './popover/index.ts';
5
- export * from './remote/index.ts';
6
- export * from './search/index.ts';
7
- export * from './state/index.ts';
8
- export * from './ui/index.ts';
9
- export * from './utils/index.ts';
10
- export * from './validation/index.ts';
1
+ export * from './action/index.js';
2
+ export * from './command/index.js';
3
+ export * from './input/index.js';
4
+ export * from './loading/index.js';
5
+ export * from './popover/index.js';
6
+ export * from './remote/index.js';
7
+ export * from './search/index.js';
8
+ export * from './state/index.js';
9
+ export * from './ui/index.js';
10
+ export * from './utils/index.js';
11
+ export * from './validation/index.js';
package/dist/index.js CHANGED
@@ -1,10 +1,11 @@
1
- export * from './action/index.ts';
2
- export * from './command/index.ts';
3
- export * from './input/index.ts';
4
- export * from './popover/index.ts';
5
- export * from './remote/index.ts';
6
- export * from './search/index.ts';
7
- export * from './state/index.ts';
8
- export * from './ui/index.ts';
9
- export * from './utils/index.ts';
10
- export * from './validation/index.ts';
1
+ export * from './action/index.js';
2
+ export * from './command/index.js';
3
+ export * from './input/index.js';
4
+ export * from './loading/index.js';
5
+ export * from './popover/index.js';
6
+ export * from './remote/index.js';
7
+ export * from './search/index.js';
8
+ export * from './state/index.js';
9
+ export * from './ui/index.js';
10
+ export * from './utils/index.js';
11
+ export * from './validation/index.js';
@@ -2,7 +2,7 @@
2
2
  import { CheckIcon } from '@lucide/svelte'
3
3
  import type { RemoteFormField } from '@sveltejs/kit'
4
4
  import Issues from './Issues.svelte'
5
- import type { InputProps } from './type.ts'
5
+ import type { InputProps } from './type.js'
6
6
 
7
7
  let {
8
8
  field,
@@ -1,5 +1,5 @@
1
1
  import type { RemoteFormField } from '@sveltejs/kit';
2
- import type { InputProps } from './type.ts';
2
+ import type { InputProps } from './type.js';
3
3
  type $$ComponentProps = {
4
4
  label: string;
5
5
  field?: RemoteFormField<boolean>;
@@ -1,7 +1,7 @@
1
1
  <script lang="ts">
2
2
  import type { RemoteFormField } from '@sveltejs/kit'
3
3
  import Issues from './Issues.svelte'
4
- import type { InputProps } from './type.ts'
4
+ import type { InputProps } from './type.js'
5
5
 
6
6
  let {
7
7
  label,
@@ -1,5 +1,5 @@
1
1
  import type { RemoteFormField } from '@sveltejs/kit';
2
- import type { InputProps } from './type.ts';
2
+ import type { InputProps } from './type.js';
3
3
  type $$ComponentProps = {
4
4
  label: string;
5
5
  field?: RemoteFormField<number>;
@@ -1,6 +1,6 @@
1
1
  <script lang="ts">
2
2
  import type { RemoteFormField } from '@sveltejs/kit'
3
- import { type Options, parseOptions } from '../utils/index.ts'
3
+ import { type Options, parseOptions } from '../utils/index.js'
4
4
  import Issues from './Issues.svelte'
5
5
 
6
6
  let {
@@ -1,5 +1,5 @@
1
1
  import type { RemoteFormField } from '@sveltejs/kit';
2
- import { type Options } from '../utils/index.ts';
2
+ import { type Options } from '../utils/index.js';
3
3
  type $$ComponentProps = {
4
4
  field?: RemoteFormField<string>;
5
5
  label: string;
@@ -1,7 +1,7 @@
1
1
  <script lang="ts">
2
2
  import type { RemoteFormField } from '@sveltejs/kit'
3
3
  import Issues from './Issues.svelte'
4
- import type { InputProps } from './type.ts'
4
+ import type { InputProps } from './type.js'
5
5
 
6
6
  let {
7
7
  label,
@@ -1,5 +1,5 @@
1
1
  import type { RemoteFormField } from '@sveltejs/kit';
2
- import type { InputProps } from './type.ts';
2
+ import type { InputProps } from './type.js';
3
3
  type $$ComponentProps = {
4
4
  label: string;
5
5
  field?: RemoteFormField<number>;
@@ -1,81 +1,139 @@
1
1
  <script lang="ts" generics="Item">
2
- import { ChevronsUpDownIcon } from '@lucide/svelte'
2
+ import { ChevronsUpDownIcon, SearchIcon, XIcon } from '@lucide/svelte'
3
3
  import type { RemoteFormField, RemoteQueryFunction } from '@sveltejs/kit'
4
4
  import type { Snippet } from 'svelte'
5
- import { useCommand } from '../command/command.svelte.ts'
6
- import { usePopover } from '../popover/popover.svelte.ts'
5
+ import type { ClassValue } from 'svelte/elements'
6
+ import { tip } from '../action/tip.js'
7
+ import { useCommand } from '../command/command.svelte.js'
8
+ import Loading from '../loading/Loading.svelte'
9
+ import { type PopoverType, usePopover } from '../popover/popover.svelte.js'
7
10
  import Issues from './Issues.svelte'
8
11
 
9
12
  let {
10
13
  label,
11
14
  searchItems,
12
- getValue,
13
- selected,
14
- proposal,
15
+ getValue = defaultGetValue,
16
+ selected = defaultSnippet,
17
+ proposal = defaultSnippet,
15
18
  field,
16
- value = $bindable()
19
+ value = $bindable(),
20
+ placeholder = 'Selectionner une valeur',
21
+ class: klass,
22
+ nullable,
23
+ hint,
24
+ onSelect,
25
+ hotKey
17
26
  }: {
18
- label: string
19
27
  searchItems: RemoteQueryFunction<{ search: string }, Item[]>
20
- getValue: (item: Item) => string
28
+ label?: string
29
+ getValue?: (item: NoInfer<Item>) => string
21
30
  selected?: Snippet<[Item]>
22
31
  proposal?: Snippet<[Item, { isSelected: boolean; isFocus: boolean }]>
23
32
  field?: RemoteFormField<string>
24
33
  value?: string
34
+ placeholder?: string
35
+ class?: ClassValue
36
+ nullable?: boolean
37
+ hint?: Snippet<[Item | undefined]>
38
+ onSelect?: (item: NoInfer<Item> | undefined, popover: PopoverType) => void
39
+ hotKey?: string
25
40
  } = $props()
26
41
 
27
42
  let search = $state('')
28
- const items = $derived.by(() => searchItems({ search }))
43
+ const query = $derived.by(() => searchItems({ search }))
44
+ const items = $derived(query.current || [])
29
45
 
30
46
  let selectedItem = $state<Item | undefined>(undefined)
31
-
32
47
  $effect(() => {
33
- const targetValue = field?.value() ?? value
34
- selectedItem = items.current?.find((item) => getValue(item) === targetValue)
48
+ const targetValue = (field?.value() ?? value)?.toString()
49
+ if (!targetValue) {
50
+ selectedItem = undefined
51
+ command.selectedIndex = -1
52
+ return
53
+ }
54
+ // TODO: Quelle est la meilleur facon de récupérer l'item initial ?
55
+ command.selectedIndex = items.findIndex((item) => getValue(item) === targetValue)
35
56
  })
36
57
 
37
- const popover = usePopover({ listenFocus: false })
38
- const command = useCommand({
58
+ export const popover = usePopover({
59
+ listenFocus: false,
60
+ onShow: () => command.focusTrigger(),
61
+ hotKey: (() => hotKey)()
62
+ })
63
+ export const command = useCommand({
39
64
  isEnable: () => popover.isOpen,
40
65
  onSelect: (index) => {
41
66
  popover.hide()
42
- const item = items.current?.[index]
67
+ const item = items[index]
68
+ onSelect?.(item, popover)
43
69
  if (!item) return
44
70
  selectedItem = item
45
- if (field) {
46
- field.set(getValue(item))
47
- } else {
48
- value = getValue(item)
49
- }
71
+ setValue(item)
50
72
  }
51
73
  })
74
+
75
+ function setValue(item: Item | undefined) {
76
+ const newValue = item && getValue(item)
77
+ if (field) {
78
+ field.set(newValue)
79
+ } else {
80
+ value = newValue
81
+ }
82
+ }
83
+
84
+ function defaultGetValue(item: Item): string {
85
+ if (item && typeof item === 'object') {
86
+ if ('value' in item && typeof item.value === 'string') return item.value
87
+ if ('rowid' in item && typeof item.rowid === 'string') return item.rowid
88
+ }
89
+ return JSON.stringify(item)
90
+ }
91
+
92
+ function getDefaultLabel(item: Item) {
93
+ if (item && typeof item === 'object' && 'label' in item) {
94
+ if (typeof item.label === 'string') return item.label
95
+ }
96
+ return getValue(item)
97
+ }
98
+
99
+ const isButtonSetNullVisible = $derived(nullable && (field?.value() || value))
52
100
  </script>
53
101
 
54
- {#if field?.value() !== undefined}
55
- <input {...field.as('hidden', field.value() as string)} />
102
+ {#snippet defaultSnippet(item: Item)}
103
+ <span>{getDefaultLabel(item)}</span>
104
+ {/snippet}
105
+
106
+ {#snippet triggerButton()}
107
+ <button
108
+ type="button"
109
+ class={['input', field?.issues.length && 'input-error', klass]}
110
+ {...popover.trigger}
111
+ >
112
+ <div class="grow text-left">
113
+ {#if !selectedItem}
114
+ <span class="opacity-60">{placeholder}</span>
115
+ {:else}
116
+ <!-- item.icon not rerender if not wrapped in #key -->
117
+ {#key selectedItem}{@render selected(selectedItem)}{/key}
118
+ {/if}
119
+ </div>
120
+ <ChevronsUpDownIcon size={14} opacity={0.7} />
121
+ </button>
122
+ {/snippet}
123
+
124
+ {#if field}
125
+ <input {...field.as('hidden', field.value() || '')} />
56
126
  {/if}
57
127
 
58
128
  <div>
59
- <label class="floating-label">
60
- <span>{label}</span>
61
- <button
62
- class={['input', field?.issues.length && 'input-error']}
63
- {...popover.trigger}
64
- type="button"
65
- >
66
- <div class="grow text-left">
67
- {#if !selectedItem}
68
- <span class="opacity-60">Select a value</span>
69
- {:else if selected}
70
- <!-- item.icon not rerender if not wrapped in #key -->
71
- {#key selectedItem}{@render selected(selectedItem)}{/key}
72
- {:else}
73
- {getValue(selectedItem)}
74
- {/if}
75
- </div>
76
- <ChevronsUpDownIcon size={14} />
77
- </button>
78
- </label>
129
+ {#if label}
130
+ <label class="floating-label">
131
+ <span>{label}</span>
132
+ {@render triggerButton()}
133
+ </label>
134
+ {:else}
135
+ {@render triggerButton()}
136
+ {/if}
79
137
 
80
138
  <div
81
139
  {...popover.content}
@@ -83,33 +141,56 @@
83
141
  style="min-width: anchor-size(width);"
84
142
  tabindex="-1"
85
143
  >
86
- <div class="sticky top-0 z-10 bg-base-100 p-2">
87
- <input type="search" {...command.trigger} class="input input-sm" bind:value={search} />
144
+ <div class="sticky top-0 z-10 flex gap-2 bg-base-100/10 p-2 backdrop-blur-md">
145
+ <label class="input input-sm grow input-ghost">
146
+ <SearchIcon opacity={0.6} size={20} />
147
+ <input
148
+ type="search"
149
+ placeholder="Recherche"
150
+ autocomplete="off"
151
+ bind:value={search}
152
+ {...command.trigger}
153
+ />
154
+ </label>
155
+ {#if isButtonSetNullVisible}
156
+ <button
157
+ class="btn btn-square btn-soft btn-sm"
158
+ type="button"
159
+ onclick={() => {
160
+ selectedItem = undefined
161
+ setValue(undefined)
162
+ onSelect?.(undefined, popover)
163
+ popover.hide()
164
+ }}
165
+ use:tip={{ content: 'Pas de sélection' }}
166
+ >
167
+ <XIcon />
168
+ </button>
169
+ {/if}
88
170
  </div>
89
171
 
90
172
  <ul class="menu max-h-80 w-full flex-nowrap pt-0">
91
- {#each items.current as item, index (item)}
173
+ {#each items as item, index (item)}
92
174
  {@const isSelected = index === command.selectedIndex}
93
175
  {@const isFocus = index === command.focusIndex}
94
176
  <li>
95
177
  <button
96
178
  {...command.item(index, '')}
97
- class={[isFocus && 'menu-focus', 'scroll-mt-13 scroll-mb-2']}
179
+ class={[isSelected && 'border', isFocus && 'menu-focus', 'scroll-mt-13 scroll-mb-2']}
98
180
  type="button"
99
181
  tabindex="-1"
100
182
  role="option"
101
183
  >
102
- {#if proposal}
103
- {@render proposal(item, { isSelected, isFocus })}
104
- {:else}
105
- {getValue(item)}
106
- {/if}
184
+ {@render proposal(item, { isSelected, isFocus })}
107
185
  </button>
108
186
  </li>
109
187
  {/each}
110
188
  </ul>
189
+
190
+ <Loading {query} />
111
191
  </div>
112
192
  <Issues {field} />
193
+ {@render hint?.(selectedItem)}
113
194
  </div>
114
195
 
115
196
  <style>
@@ -1,12 +1,14 @@
1
1
  import type { RemoteFormField, RemoteQueryFunction } from '@sveltejs/kit';
2
2
  import type { Snippet } from 'svelte';
3
+ import type { ClassValue } from 'svelte/elements';
4
+ import { type PopoverType } from '../popover/popover.svelte.js';
3
5
  declare function $$render<Item>(): {
4
6
  props: {
5
- label: string;
6
7
  searchItems: RemoteQueryFunction<{
7
8
  search: string;
8
9
  }, Item[]>;
9
- getValue: (item: Item) => string;
10
+ label?: string;
11
+ getValue?: (item: NoInfer<Item>) => string;
10
12
  selected?: Snippet<[Item]>;
11
13
  proposal?: Snippet<[Item, {
12
14
  isSelected: boolean;
@@ -14,8 +16,38 @@ declare function $$render<Item>(): {
14
16
  }]>;
15
17
  field?: RemoteFormField<string>;
16
18
  value?: string;
19
+ placeholder?: string;
20
+ class?: ClassValue;
21
+ nullable?: boolean;
22
+ hint?: Snippet<[Item | undefined]>;
23
+ onSelect?: (item: NoInfer<Item> | undefined, popover: PopoverType) => void;
24
+ hotKey?: string;
25
+ };
26
+ exports: {
27
+ popover: {
28
+ show: () => void;
29
+ hide: () => void;
30
+ readonly isOpen: boolean;
31
+ content: {
32
+ [x: symbol]: (node: HTMLElement) => () => void;
33
+ popover: "auto" | "hint" | "manual";
34
+ };
35
+ trigger: {
36
+ [x: symbol]: (node: HTMLElement) => () => void;
37
+ };
38
+ };
39
+ command: {
40
+ readonly focusIndex: number;
41
+ selectedIndex: number;
42
+ trigger: {
43
+ [x: symbol]: (node: HTMLElement) => () => void;
44
+ };
45
+ focusTrigger: () => void;
46
+ item: (index: number, scrollMargin?: string) => {
47
+ [x: symbol]: (node: HTMLElement) => () => void;
48
+ };
49
+ };
17
50
  };
18
- exports: {};
19
51
  bindings: "value";
20
52
  slots: {};
21
53
  events: {};
@@ -25,7 +57,31 @@ declare class __sveltets_Render<Item> {
25
57
  events(): ReturnType<typeof $$render<Item>>['events'];
26
58
  slots(): ReturnType<typeof $$render<Item>>['slots'];
27
59
  bindings(): "value";
28
- exports(): {};
60
+ exports(): {
61
+ popover: {
62
+ show: () => void;
63
+ hide: () => void;
64
+ readonly isOpen: boolean;
65
+ content: {
66
+ [x: symbol]: (node: HTMLElement) => () => void;
67
+ popover: "auto" | "hint" | "manual";
68
+ };
69
+ trigger: {
70
+ [x: symbol]: (node: HTMLElement) => () => void;
71
+ };
72
+ };
73
+ command: {
74
+ readonly focusIndex: number;
75
+ selectedIndex: number;
76
+ trigger: {
77
+ [x: symbol]: (node: HTMLElement) => () => void;
78
+ };
79
+ focusTrigger: () => void;
80
+ item: (index: number, scrollMargin?: string) => {
81
+ [x: symbol]: (node: HTMLElement) => () => void;
82
+ };
83
+ };
84
+ };
29
85
  }
30
86
  interface $$IsomorphicComponent {
31
87
  new <Item>(options: import('svelte').ComponentConstructorOptions<ReturnType<__sveltets_Render<Item>['props']>>): import('svelte').SvelteComponent<ReturnType<__sveltets_Render<Item>['props']>, ReturnType<__sveltets_Render<Item>['events']>, ReturnType<__sveltets_Render<Item>['slots']>> & {
@@ -2,8 +2,8 @@
2
2
  import { ChevronsUpDownIcon } from '@lucide/svelte'
3
3
  import type { RemoteFormField } from '@sveltejs/kit'
4
4
  import type { Snippet } from 'svelte'
5
- import { useCommand } from '../command/command.svelte.ts'
6
- import { usePopover } from '../popover/popover.svelte.ts'
5
+ import { useCommand } from '../command/command.svelte.js'
6
+ import { usePopover } from '../popover/popover.svelte.js'
7
7
  import Issues from './Issues.svelte'
8
8
 
9
9
  let {
@@ -1,8 +1,8 @@
1
1
  <script lang="ts">
2
2
  import type { RemoteFormField } from '@sveltejs/kit'
3
- import { type Options, parseOptions } from '../index.ts'
3
+ import { type Options, parseOptions } from '../index.js'
4
4
  import Issues from './Issues.svelte'
5
- import type { SelectProps } from './type.ts'
5
+ import type { SelectProps } from './type.js'
6
6
 
7
7
  let {
8
8
  label,
@@ -1,6 +1,6 @@
1
1
  import type { RemoteFormField } from '@sveltejs/kit';
2
- import { type Options } from '../index.ts';
3
- import type { SelectProps } from './type.ts';
2
+ import { type Options } from '../index.js';
3
+ import type { SelectProps } from './type.js';
4
4
  type $$ComponentProps = {
5
5
  label: string;
6
6
  options: Options;
@@ -1,7 +1,7 @@
1
1
  <script lang="ts">
2
2
  import type { RemoteFormField } from '@sveltejs/kit'
3
3
  import Issues from './Issues.svelte'
4
- import type { InputProps } from './type.ts'
4
+ import type { InputProps } from './type.js'
5
5
 
6
6
  let {
7
7
  label,
@@ -1,5 +1,5 @@
1
1
  import type { RemoteFormField } from '@sveltejs/kit';
2
- import type { InputProps } from './type.ts';
2
+ import type { InputProps } from './type.js';
3
3
  type $$ComponentProps = {
4
4
  label: string;
5
5
  field?: RemoteFormField<string>;
@@ -2,7 +2,7 @@
2
2
  import type { RemoteFormField } from '@sveltejs/kit'
3
3
  import { TextareaAutosize } from 'runed'
4
4
  import Issues from './Issues.svelte'
5
- import type { TextareaProps } from './type.ts'
5
+ import type { TextareaProps } from './type.js'
6
6
 
7
7
  let {
8
8
  label,
@@ -1,5 +1,5 @@
1
1
  import type { RemoteFormField } from '@sveltejs/kit';
2
- import type { TextareaProps } from './type.ts';
2
+ import type { TextareaProps } from './type.js';
3
3
  type $$ComponentProps = {
4
4
  label: string;
5
5
  field?: RemoteFormField<string>;
@@ -8,4 +8,4 @@ export { default as InputSelectNative } from './InputSelectNative.svelte';
8
8
  export { default as InputString } from './InputString.svelte';
9
9
  export { default as InputTextarea } from './InputTextarea.svelte';
10
10
  export { default as Issues } from './Issues.svelte';
11
- export type * from './type.ts';
11
+ export type * from './type.js';
@@ -0,0 +1,43 @@
1
+ <script lang="ts">
2
+ import { GhostIcon, LoaderCircleIcon, TriangleAlertIcon } from '@lucide/svelte'
3
+ import type { RemoteQuery } from '@sveltejs/kit'
4
+ import type { ClassValue } from 'svelte/elements'
5
+ import { fade } from 'svelte/transition'
6
+
7
+ let {
8
+ query,
9
+ empty = 'Aucun résultat',
10
+ error = 'Une erreur est survenue',
11
+ class: klass
12
+ }: {
13
+ query: RemoteQuery<unknown>
14
+ empty?: string
15
+ error?: string
16
+ class?: ClassValue
17
+ } = $props()
18
+
19
+ const isEmpty = $derived.by(() => {
20
+ if (!query.ready) return true
21
+ if (!query.current) return true
22
+ if (Array.isArray(query.current) && !query.current.length) return true
23
+ return false
24
+ })
25
+
26
+ const box = 'm-2 flex flex-col items-center gap-1 rounded border border-dashed p-2 text-sm'
27
+ </script>
28
+
29
+ {#if query.loading}
30
+ <div class={['grid h-12 place-content-center', klass]} in:fade={{ duration: 200 }}>
31
+ <LoaderCircleIcon class="animate-spin" opacity={0.7} />
32
+ </div>
33
+ {:else if query.error}
34
+ <div class={[box, 'border-error/50 text-error', klass]} in:fade={{ duration: 200 }}>
35
+ <TriangleAlertIcon size={18} />
36
+ <span>{error}</span>
37
+ </div>
38
+ {:else if isEmpty}
39
+ <div class={[box, 'border-base-content/50 opacity-70', klass]} in:fade={{ duration: 200 }}>
40
+ <GhostIcon size={18} />
41
+ <span>{empty}</span>
42
+ </div>
43
+ {/if}
@@ -0,0 +1,11 @@
1
+ import type { RemoteQuery } from '@sveltejs/kit';
2
+ import type { ClassValue } from 'svelte/elements';
3
+ type $$ComponentProps = {
4
+ query: RemoteQuery<unknown>;
5
+ empty?: string;
6
+ error?: string;
7
+ class?: ClassValue;
8
+ };
9
+ declare const Loading: import("svelte").Component<$$ComponentProps, {}, "">;
10
+ type Loading = ReturnType<typeof Loading>;
11
+ export default Loading;
@@ -0,0 +1 @@
1
+ export { default as Loading } from './Loading.svelte';
@@ -0,0 +1 @@
1
+ export { default as Loading } from './Loading.svelte';
@@ -6,7 +6,7 @@
6
6
  type PopoverOptions,
7
7
  type PopoverType,
8
8
  usePopover
9
- } from './popover.svelte.ts'
9
+ } from './popover.svelte.js'
10
10
 
11
11
  let {
12
12
  children,
@@ -1,2 +1,2 @@
1
1
  export { default as Popover } from './Popover.svelte';
2
- export * from './popover.svelte.ts';
2
+ export * from './popover.svelte.js';
@@ -1,2 +1,2 @@
1
1
  export { default as Popover } from './Popover.svelte';
2
- export * from './popover.svelte.ts';
2
+ export * from './popover.svelte.js';
@@ -10,8 +10,11 @@ export type PopoverOptions = {
10
10
  listenHover?: boolean;
11
11
  /** Only when listenHover={true} */
12
12
  hideDelay?: number;
13
+ onShow?: () => void;
14
+ onHide?: () => void;
15
+ hotKey?: string;
13
16
  };
14
- export declare function usePopover({ mode, placement, listenClick, listenFocus, listenHover, hideDelay }?: PopoverOptions): {
17
+ export declare function usePopover({ mode, placement, listenClick, listenFocus, listenHover, hideDelay, onShow, onHide, hotKey }?: PopoverOptions): {
15
18
  show: () => void;
16
19
  hide: () => void;
17
20
  readonly isOpen: boolean;
@@ -16,7 +16,7 @@ const placements = {
16
16
  'right-start': 'right span-bottom'
17
17
  };
18
18
  let popoverInstanceCount = 0;
19
- export function usePopover({ mode = 'auto', placement = 'bottom-start', listenClick = true, listenFocus = true, listenHover = false, hideDelay = 400 } = {}) {
19
+ export function usePopover({ mode = 'auto', placement = 'bottom-start', listenClick = true, listenFocus = true, listenHover = false, hideDelay = 400, onShow, onHide, hotKey } = {}) {
20
20
  const uid = popoverInstanceCount++;
21
21
  const anchorName = `--anchor-${uid}`;
22
22
  let popover = $state();
@@ -24,9 +24,11 @@ export function usePopover({ mode = 'auto', placement = 'bottom-start', listenCl
24
24
  const hideDebounced = debounce(hide, hideDelay);
25
25
  function show() {
26
26
  popover?.showPopover();
27
+ onShow?.();
27
28
  }
28
29
  function hide() {
29
30
  popover?.hidePopover();
31
+ onHide?.();
30
32
  }
31
33
  function onMouseEnter() {
32
34
  hideDebounced.clear();
@@ -48,6 +50,9 @@ export function usePopover({ mode = 'auto', placement = 'bottom-start', listenCl
48
50
  cleanups.push(on(activator, 'mouseenter', onMouseEnter));
49
51
  cleanups.push(on(activator, 'mouseleave', hideDebounced));
50
52
  }
53
+ if (hotKey) {
54
+ cleanups.push(on(window, 'keydown', handleHotKey));
55
+ }
51
56
  return () => {
52
57
  for (const cleanup of cleanups)
53
58
  cleanup();
@@ -65,6 +70,14 @@ export function usePopover({ mode = 'auto', placement = 'bottom-start', listenCl
65
70
  cleanup();
66
71
  };
67
72
  }
73
+ async function handleHotKey(event) {
74
+ const { metaKey, ctrlKey, key } = event;
75
+ if ((metaKey || ctrlKey) && key === hotKey) {
76
+ event.preventDefault();
77
+ show();
78
+ return;
79
+ }
80
+ }
68
81
  return {
69
82
  show,
70
83
  hide,
@@ -1 +1 @@
1
- export * from './useForm.ts';
1
+ export * from './useForm.js';
@@ -1 +1 @@
1
- export * from './useForm.ts';
1
+ export * from './useForm.js';
@@ -1,6 +1,16 @@
1
1
  import type { RemoteForm, RemoteFormInput } from '@sveltejs/kit';
2
2
  import type { StandardSchemaV1 } from 'runed/kit';
3
+ import { type Attachment } from 'svelte/attachments';
3
4
  /**
4
5
  * Enable preflight and run validation on input
6
+ * @deprecated
5
7
  */
6
8
  export declare function useForm<Input extends RemoteFormInput | undefined, Output>(remoteForm: RemoteForm<Input, Output>, schema: StandardSchemaV1<Input>, debounceMs?: number): RemoteForm<Input, Output>;
9
+ export declare function validationOnInput<Input extends RemoteFormInput | undefined, Output>(remoteForm: RemoteForm<Input, Output>, debounceMs?: number): Attachment<HTMLFormElement>;
10
+ type UseEnhanceCallbackOptions<Input extends RemoteFormInput | void> = {
11
+ onSuccess?: (data: Input, form: HTMLFormElement) => void;
12
+ onError?: (err: unknown) => void;
13
+ };
14
+ type EnhanceCallback<Input extends RemoteFormInput | void> = Parameters<RemoteForm<Input, unknown>['enhance']>[0];
15
+ export declare function useEnhanceCallback<Input extends RemoteFormInput | void>({ onSuccess, onError }: UseEnhanceCallbackOptions<Input>): EnhanceCallback<Input>;
16
+ export {};
@@ -3,8 +3,14 @@ import { createAttachmentKey } from 'svelte/attachments';
3
3
  import { on } from 'svelte/events';
4
4
  /**
5
5
  * Enable preflight and run validation on input
6
+ * @deprecated
6
7
  */
7
8
  export function useForm(remoteForm, schema, debounceMs = 1000) {
9
+ const validation = validationOnInput(remoteForm, debounceMs);
10
+ Object.assign(remoteForm, { [createAttachmentKey()]: validation });
11
+ return remoteForm.preflight(schema);
12
+ }
13
+ export function validationOnInput(remoteForm, debounceMs = 1000) {
8
14
  const validation = (formElement) => {
9
15
  const debouncedValidate = debounce(() => remoteForm.validate(), debounceMs);
10
16
  return on(formElement, 'input', ({ target }) => {
@@ -20,6 +26,17 @@ export function useForm(remoteForm, schema, debounceMs = 1000) {
20
26
  }
21
27
  });
22
28
  };
23
- Object.assign(remoteForm, { [createAttachmentKey()]: validation });
24
- return remoteForm.preflight(schema);
29
+ return validation;
30
+ }
31
+ export function useEnhanceCallback({ onSuccess, onError }) {
32
+ return ({ submit, data, form }) => {
33
+ submit()
34
+ .then(() => {
35
+ onSuccess?.(data, form);
36
+ })
37
+ .catch((err) => {
38
+ console.error(err);
39
+ onError?.(err);
40
+ });
41
+ };
25
42
  }
@@ -1,5 +1,5 @@
1
1
  <script lang="ts">
2
- import type { SearchQueryResultSpan } from './search.ts'
2
+ import type { SearchQueryResultSpan } from './search.js'
3
3
 
4
4
  let { spans }: { spans: SearchQueryResultSpan[] } = $props()
5
5
  </script>
@@ -1,4 +1,4 @@
1
- import type { SearchQueryResultSpan } from './search.ts';
1
+ import type { SearchQueryResultSpan } from './search.js';
2
2
  type $$ComponentProps = {
3
3
  spans: SearchQueryResultSpan[];
4
4
  };
@@ -1,2 +1,2 @@
1
1
  export { default as Spans } from './Spans.svelte';
2
- export * from './search.ts';
2
+ export * from './search.js';
@@ -1,2 +1,2 @@
1
1
  export { default as Spans } from './Spans.svelte';
2
- export * from './search.ts';
2
+ export * from './search.js';
@@ -0,0 +1,38 @@
1
+ import type { FuseResultMatch, IFuseOptions } from 'fuse.js';
2
+ import Fuse from 'fuse.js';
3
+ export type CombinedFuseResult<T> = {
4
+ item: T;
5
+ score: number;
6
+ matches: FuseResultMatch[];
7
+ };
8
+ type Keys<Item> = Partial<Record<keyof Item, Partial<{
9
+ weight: number;
10
+ getFn: (item: Item) => string;
11
+ }>>>;
12
+ export type SearchOptions<Item, K extends Keys<Item> = Keys<Item>> = {
13
+ items?: Item[];
14
+ keys: K;
15
+ } & Omit<IFuseOptions<Item>, 'includeMatches' | 'keys'>;
16
+ export type SearchQueryOptions = {
17
+ tokensSeparator?: string;
18
+ tokensMaxCount?: number;
19
+ };
20
+ export type SearchQueryResult<Item, K extends Keys<Item> = Keys<Item>> = {
21
+ item: Item;
22
+ spans: Record<keyof K, SearchQueryResultSpan[]>;
23
+ score: number;
24
+ };
25
+ export type SearchQueryResultSpan = {
26
+ value: string;
27
+ isMatch: boolean;
28
+ };
29
+ export declare function useSearch<Item extends object, K extends Keys<Item>>({ items, keys, ...fuseOptions }: SearchOptions<Item, K>): {
30
+ fuse: Fuse<Item>;
31
+ query: (value: string, { tokensSeparator, tokensMaxCount }?: SearchQueryOptions) => SearchQueryResult<Item, K>[];
32
+ prismaWhereTokens: (value: string, { tokensSeparator, tokensMaxCount }?: SearchQueryOptions) => {
33
+ OR: { [P in keyof K]?: {
34
+ contains: string;
35
+ } | undefined; }[];
36
+ }[];
37
+ };
38
+ export {};
@@ -3,6 +3,8 @@ import { mergeRanges } from 'perod';
3
3
  export function useSearch({ items = [], keys, ...fuseOptions }) {
4
4
  const fuse = new Fuse(items, {
5
5
  includeMatches: true,
6
+ ignoreLocation: true,
7
+ ignoreDiacritics: true,
6
8
  keys: Object.entries(keys).map(([name, param]) => ({
7
9
  name,
8
10
  ...(param || {})
@@ -28,9 +30,20 @@ export function useSearch({ items = [], keys, ...fuseOptions }) {
28
30
  }
29
31
  return queryResults;
30
32
  }
33
+ function prismaWhereTokens(value, { tokensSeparator = ' ', tokensMaxCount = 4 } = {}) {
34
+ const tokens = value.split(tokensSeparator).slice(0, tokensMaxCount);
35
+ const whereTokens = [];
36
+ for (const token of tokens) {
37
+ whereTokens.push({
38
+ OR: Object.keys(keys).map((k) => ({ [k]: { contains: token } }))
39
+ });
40
+ }
41
+ return whereTokens;
42
+ }
31
43
  return {
32
- ...fuse,
33
- query
44
+ fuse,
45
+ query,
46
+ prismaWhereTokens
34
47
  };
35
48
  }
36
49
  function getSpans(key, matches) {
@@ -3,7 +3,7 @@
3
3
  import type { Component } from 'svelte'
4
4
  import type { ClassValue } from 'svelte/elements'
5
5
  import { tip } from '../../action/tip.js'
6
- import { type UseCopyOptions, useCopy } from './useCopy.svelte.ts'
6
+ import { type UseCopyOptions, useCopy } from './useCopy.svelte.js'
7
7
 
8
8
  let {
9
9
  title = '',
@@ -1,7 +1,7 @@
1
1
  import { type IconProps } from '@lucide/svelte';
2
2
  import type { Component } from 'svelte';
3
3
  import type { ClassValue } from 'svelte/elements';
4
- import { type UseCopyOptions } from './useCopy.svelte.ts';
4
+ import { type UseCopyOptions } from './useCopy.svelte.js';
5
5
  type $$ComponentProps = {
6
6
  /** Tooltip text */
7
7
  title?: string;
@@ -1,2 +1,2 @@
1
1
  export { default as ButtonCopy } from './ButtonCopy.svelte';
2
- export * from './useCopy.svelte.ts';
2
+ export * from './useCopy.svelte.js';
@@ -1,2 +1,2 @@
1
1
  export { default as ButtonCopy } from './ButtonCopy.svelte';
2
- export * from './useCopy.svelte.ts';
2
+ export * from './useCopy.svelte.js';
@@ -8,7 +8,7 @@
8
8
  import { XIcon } from '@lucide/svelte'
9
9
 
10
10
  import { goto } from '$app/navigation'
11
- import { urlParam } from '../../state/param.svelte.ts'
11
+ import { urlParam } from '../../state/param.svelte.js'
12
12
  import { drawerFly } from './drawerFly.js'
13
13
  import { writable } from 'svelte/store'
14
14
  import { useLayer } from './useLayer.svelte.js'
@@ -1,2 +1,2 @@
1
- export { default as Drawer } from './Drawer.svelte';
1
+ export { default as Drawer, transitionX } from './Drawer.svelte';
2
2
  export * from './drawerFly.js';
@@ -1,2 +1,2 @@
1
- export { default as Drawer } from './Drawer.svelte';
1
+ export { default as Drawer, transitionX } from './Drawer.svelte';
2
2
  export * from './drawerFly.js';
@@ -1,7 +1,7 @@
1
1
  <script lang="ts">
2
2
  import { ChevronLeftIcon, ChevronRightIcon } from '@lucide/svelte'
3
3
  import { tip } from '../../action/tip.js'
4
- import { param, urlParam } from '../../state/index.ts'
4
+ import { param, urlParam } from '../../state/index.js'
5
5
 
6
6
  let { take = 20 }: { take?: number } = $props()
7
7
 
@@ -2,7 +2,7 @@
2
2
  import { CalendarRangeIcon } from '@lucide/svelte'
3
3
  import { slide } from 'svelte/transition'
4
4
  import { goto } from '$app/navigation'
5
- import { param, urlParam } from '../../state/param.svelte.ts'
5
+ import { param, urlParam } from '../../state/param.svelte.js'
6
6
  import { DropDown } from '../menu/index.js'
7
7
  import { formatRangeShort } from './format.js'
8
8
  import { type RangeAsDate, RangePicker } from './index.js'
@@ -11,7 +11,7 @@
11
11
  import { page } from '$app/state'
12
12
  import { listEditable } from '../../action/list/index.js'
13
13
  import { tip } from '../../action/tip.js'
14
- import { param, urlParam } from '../../state/param.svelte.ts'
14
+ import { param, urlParam } from '../../state/param.svelte.js'
15
15
  import { DropDown } from '../menu/index.js'
16
16
  import { context } from './context.js'
17
17
  import type { ItemBase, TableField } from './index.js'
@@ -2,7 +2,7 @@
2
2
  import { CalendarArrowDownIcon, CalendarArrowUpIcon, CalendarSearchIcon } from '@lucide/svelte'
3
3
  import { goto } from '$app/navigation'
4
4
  import { page } from '$app/state'
5
- import { urlParam } from '../../../state/param.svelte.ts'
5
+ import { urlParam } from '../../../state/param.svelte.js'
6
6
  import { DropDown } from '../../menu/index.js'
7
7
  import { formatRange } from '../../range/format.js'
8
8
  import { type RangeAsDate, RangePicker } from '../../range/index.js'
@@ -4,7 +4,7 @@
4
4
  import z from 'zod'
5
5
  import { goto } from '$app/navigation'
6
6
  import { page } from '$app/state'
7
- import { urlParam } from '../../../state/param.svelte.ts'
7
+ import { urlParam } from '../../../state/param.svelte.js'
8
8
  import { DropDown } from '../../menu/index.js'
9
9
  import { zodCoerceJsonRecord } from '../../../validation/zod.js'
10
10
  import type { ItemBase, TableField } from '../field.js'
@@ -1,7 +1,7 @@
1
1
  <script lang="ts" generics="Item extends ItemBase">
2
2
  import { ListFilterIcon } from '@lucide/svelte'
3
3
  import { page } from '$app/state'
4
- import { param, urlParam } from '../../../state/param.svelte.ts'
4
+ import { param, urlParam } from '../../../state/param.svelte.js'
5
5
  import { DropDown } from '../../menu/index.js'
6
6
  import type { ItemBase, TableField } from '../index.js'
7
7
  import { jsonParse } from '../../../utils/jsonParse.js'
@@ -1,6 +1,6 @@
1
1
  import type { IconProps } from '@lucide/svelte';
2
2
  import type { Component } from 'svelte';
3
- import type { InputProps } from '../input/type.ts';
3
+ import type { InputProps } from '../input/type.js';
4
4
  export type Option = {
5
5
  value: string;
6
6
  label: string;
@@ -1,5 +1,5 @@
1
1
  import z from 'zod';
2
- import { zodCoerceJsonValue } from '../validation/zod.ts';
2
+ import { zodCoerceJsonValue } from '../validation/zod.js';
3
3
  export function parseOptions(options) {
4
4
  if (typeof options === 'string') {
5
5
  options = zodCoerceJsonValue.pipe(z.array(z.string())).default([]).parse(options);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fuma",
3
- "version": "2.1.3",
3
+ "version": "2.1.5",
4
4
  "description": "My fullstack material build with sveltekit, daisyui, zod, prisma, lucia",
5
5
  "author": {
6
6
  "name": "Jonas Voisard",
@@ -8,10 +8,10 @@
8
8
  },
9
9
  "scripts": {
10
10
  "dev": "vite dev --host",
11
- "build": "vite build && npm run package",
11
+ "build": "vite build && bun run package",
12
12
  "preview": "vite preview --host",
13
13
  "package": "svelte-kit sync && svelte-package && publint",
14
- "prepublishOnly": "npm run package",
14
+ "prepublishOnly": "bun run package",
15
15
  "sync": "svelte-kit sync",
16
16
  "svelte-check": "svelte-check --tsconfig ./tsconfig.json",
17
17
  "check": "run-p sync svelte-check lint",