fuma 2.1.6 → 2.5.0

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 (89) hide show
  1. package/dist/_doc/data.d.ts +3 -1
  2. package/dist/_doc/data.js +6 -2
  3. package/dist/command/index.d.ts +1 -1
  4. package/dist/command/index.js +1 -1
  5. package/dist/command/{command.svelte.js → useCommand.svelte.js} +1 -1
  6. package/dist/input/InputBoolean.svelte +41 -8
  7. package/dist/input/InputBoolean.svelte.d.ts +7 -0
  8. package/dist/input/InputCheckboxes.svelte +123 -0
  9. package/dist/input/InputCheckboxes.svelte.d.ts +19 -0
  10. package/dist/input/InputDateTime.svelte +199 -0
  11. package/dist/input/InputDateTime.svelte.d.ts +26 -0
  12. package/dist/input/InputMultiSelect.svelte +324 -0
  13. package/dist/input/{InputRelation.svelte.d.ts → InputMultiSelect.svelte.d.ts} +32 -13
  14. package/dist/input/InputNumber.svelte +43 -6
  15. package/dist/input/InputNumber.svelte.d.ts +8 -0
  16. package/dist/input/InputRadio.svelte +62 -24
  17. package/dist/input/InputRadio.svelte.d.ts +3 -0
  18. package/dist/input/InputRange.svelte +24 -4
  19. package/dist/input/InputRange.svelte.d.ts +9 -0
  20. package/dist/input/InputSelect.svelte +216 -62
  21. package/dist/input/InputSelect.svelte.d.ts +91 -6
  22. package/dist/input/InputSelectNative.svelte +51 -19
  23. package/dist/input/InputSelectNative.svelte.d.ts +8 -0
  24. package/dist/input/InputString.svelte +21 -6
  25. package/dist/input/InputString.svelte.d.ts +7 -0
  26. package/dist/input/InputTextarea.svelte +58 -11
  27. package/dist/input/InputTextarea.svelte.d.ts +14 -0
  28. package/dist/input/index.d.ts +4 -1
  29. package/dist/input/index.js +4 -1
  30. package/dist/input/useItems.svelte.d.ts +38 -0
  31. package/dist/input/useItems.svelte.js +142 -0
  32. package/dist/loading/Loading.svelte +3 -2
  33. package/dist/loading/Loading.svelte.d.ts +3 -2
  34. package/dist/popover/Popover.svelte +1 -2
  35. package/dist/popover/Popover.svelte.d.ts +13 -0
  36. package/dist/popover/index.d.ts +1 -1
  37. package/dist/popover/index.js +1 -1
  38. package/dist/popover/{popover.svelte.d.ts → usePopover.svelte.d.ts} +2 -1
  39. package/dist/popover/usePopover.svelte.js +145 -0
  40. package/dist/remote/useForm.d.ts +4 -5
  41. package/dist/remote/useForm.js +9 -4
  42. package/dist/state/index.d.ts +1 -1
  43. package/dist/state/index.js +1 -1
  44. package/dist/ui/button/ButtonDelete.svelte +6 -3
  45. package/dist/ui/button/ButtonDelete.svelte.d.ts +1 -1
  46. package/dist/ui/dialog/Dialog.svelte +5 -5
  47. package/dist/ui/drawer/Drawer.svelte +74 -26
  48. package/dist/ui/drawer/Drawer.svelte.d.ts +10 -4
  49. package/dist/ui/drawer/index.d.ts +1 -0
  50. package/dist/ui/drawer/index.js +1 -0
  51. package/dist/ui/drawer/inertBackground.d.ts +13 -0
  52. package/dist/ui/drawer/inertBackground.js +59 -0
  53. package/dist/ui/drawer/useLayer.svelte.d.ts +1 -1
  54. package/dist/ui/drawer/useLayer.svelte.js +11 -7
  55. package/dist/ui/index.d.ts +1 -0
  56. package/dist/ui/index.js +1 -0
  57. package/dist/ui/menu/ContextMenu.svelte +30 -17
  58. package/dist/ui/menu/ContextMenu.svelte.d.ts +2 -3
  59. package/dist/ui/menu/DropDown.svelte +1 -1
  60. package/dist/ui/menu/DropDown.svelte.d.ts +1 -1
  61. package/dist/ui/menu/DropDownMenu.svelte +12 -10
  62. package/dist/ui/menu/DropDownMenu.svelte.d.ts +2 -3
  63. package/dist/ui/menu/index.d.ts +0 -1
  64. package/dist/ui/menu/index.js +0 -2
  65. package/dist/ui/range/RangePicker.svelte +25 -13
  66. package/dist/ui/range/RangePicker.svelte.d.ts +9 -25
  67. package/dist/ui/range/RangePickerButton.svelte +49 -32
  68. package/dist/ui/range/RangePickerButton.svelte.d.ts +1 -1
  69. package/dist/ui/table/Table.svelte +16 -16
  70. package/dist/ui/table/Table.svelte.d.ts +1 -1
  71. package/dist/ui/table/TableFieldsEdition.svelte +25 -23
  72. package/dist/ui/table/TableHead.svelte +5 -1
  73. package/dist/ui/table/cell/TableCellBoolean.svelte +2 -2
  74. package/dist/ui/table/head/OrderButtons.svelte +21 -20
  75. package/dist/ui/table/head/OrderButtons.svelte.d.ts +2 -0
  76. package/dist/ui/table/head/TableHeadDate.svelte +66 -57
  77. package/dist/ui/table/head/TableHeadNumber.svelte +58 -22
  78. package/dist/ui/table/head/TableHeadSelect.svelte +31 -28
  79. package/dist/ui/table/head/TableHeadString.svelte +12 -8
  80. package/dist/ui/table/index.d.ts +1 -1
  81. package/dist/ui/table/index.js +1 -1
  82. package/package.json +7 -4
  83. package/dist/input/InputRelation.svelte +0 -200
  84. package/dist/popover/popover.svelte.js +0 -106
  85. package/dist/ui/table/TableViewSelect.svelte +0 -142
  86. package/dist/ui/table/TableViewSelect.svelte.d.ts +0 -11
  87. /package/dist/command/{command.svelte.d.ts → useCommand.svelte.d.ts} +0 -0
  88. /package/dist/state/{param.svelte.d.ts → param.d.ts} +0 -0
  89. /package/dist/state/{param.svelte.js → param.js} +0 -0
@@ -1,9 +1,18 @@
1
1
  import type { RemoteFormField } from '@sveltejs/kit';
2
2
  import type { InputProps } from './type.js';
3
+ import type { Snippet } from 'svelte';
3
4
  type $$ComponentProps = {
4
5
  label: string;
5
6
  field?: RemoteFormField<number>;
7
+ /**
8
+ * Sans `field`, la valeur liée. Avec, la valeur initiale du champ — celui-ci prend
9
+ * ensuite le relais et devient la source de vérité.
10
+ */
6
11
  value?: number;
12
+ /** Sans `field`, ce qui nomme la valeur soumise. Avec, `field.as()` s'en charge. */
13
+ name?: string;
14
+ /** Rendu à droite du libellé, avant la valeur courante. */
15
+ labelAppend?: Snippet;
7
16
  } & InputProps;
8
17
  declare const InputRange: import("svelte").Component<$$ComponentProps, {}, "value">;
9
18
  type InputRange = ReturnType<typeof InputRange>;
@@ -1,109 +1,263 @@
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 } from '@sveltejs/kit'
4
4
  import type { Snippet } from 'svelte'
5
- import { useCommand } from '../command/command.svelte.js'
6
- import { usePopover } from '../popover/popover.svelte.js'
5
+ import type { ClassValue, HTMLLiAttributes } from 'svelte/elements'
6
+ import { tip } from '../action/tip.js'
7
+ import { useCommand } from '../command/useCommand.svelte.js'
8
+ import Loading from '../loading/Loading.svelte'
9
+ import { type PopoverType, usePopover } from '../popover/usePopover.svelte.js'
7
10
  import Issues from './Issues.svelte'
11
+ import {
12
+ defaultGetLabel,
13
+ defaultGetValue,
14
+ type ItemsSource,
15
+ useItems
16
+ } from './useItems.svelte.js'
8
17
 
9
18
  let {
10
19
  label,
11
20
  items,
12
- getValue,
13
- selected,
14
- proposal,
21
+ getValue = defaultGetValue,
22
+ getLabel = (item) => defaultGetLabel(item, getValue),
23
+ filter,
24
+ searchable,
25
+ debounceMs,
26
+ selected = defaultSnippet,
27
+ proposal = defaultSnippet,
15
28
  field,
16
- value = $bindable()
29
+ value = $bindable(),
30
+ placeholder = 'Selectionner une valeur',
31
+ class: klass,
32
+ nullable,
33
+ disabled = false,
34
+ hint,
35
+ append,
36
+ labelAppend,
37
+ header,
38
+ proposalAppend,
39
+ onSelect,
40
+ hotKey,
41
+ variant = 'block',
42
+ propsLi
17
43
  }: {
18
- label: string
19
- items: Item[]
20
- getValue: (item: Item) => string
44
+ /** Un tableau déjà chargé, une fonction (synchrone ou non), ou une remote query. */
45
+ items: ItemsSource<Item>
46
+ label?: string
47
+ getValue?: (item: NoInfer<Item>) => string
48
+ /** Libellé affiché à défaut de snippet, et base du filtre d'une source tableau. */
49
+ getLabel?: (item: NoInfer<Item>) => string
50
+ /** Ne s'applique qu'à une source tableau: une fonction porte son propre filtre. */
51
+ filter?: (item: NoInfer<Item>, search: string) => boolean
52
+ /** Par défaut: toujours pour une fonction, au-delà de 7 entrées pour un tableau. */
53
+ searchable?: boolean
54
+ debounceMs?: number
21
55
  selected?: Snippet<[Item]>
22
- proposal?: Snippet<[Item, { isSelected: boolean; isFocus: boolean }]>
56
+ proposal?: Snippet<[Item, { popover: PopoverType; isSelected: boolean; isFocus: boolean }]>
57
+ proposalAppend?: Snippet<
58
+ [Item, { popover: PopoverType; isSelected: boolean; isFocus: boolean }]
59
+ >
23
60
  field?: RemoteFormField<string>
24
- value?: string
61
+ /**
62
+ * L'item, pas sa valeur soumise. Un parent qui le repilote après coup — remise à
63
+ * zéro, changement de jeu d'options — doit le lier par `bind:`: passé en simple
64
+ * prop, un `$bindable` cesse de suivre le parent dès que le composant y a écrit.
65
+ */
66
+ value?: NoInfer<Item>
67
+ placeholder?: string
68
+ class?: ClassValue
69
+ nullable?: boolean
70
+ /** La valeur reste soumise, mais la sélection ne peut plus changer. */
71
+ disabled?: boolean
72
+ hint?: Snippet<[Item | undefined]>
73
+ /** Rendu à droite du champ de recherche: une action « créer », typiquement. */
74
+ append?: Snippet<[PopoverType]>
75
+ /** Rendu à droite du libellé. Sans effet sur la variante `floating`. */
76
+ labelAppend?: Snippet
77
+ header?: Snippet<[PopoverType]>
78
+ onSelect?: (item: NoInfer<Item> | undefined, popover: PopoverType) => void
79
+ hotKey?: string
80
+ variant?: 'floating' | 'block'
81
+ propsLi?: HTMLLiAttributes
25
82
  } = $props()
26
83
 
27
- let selectedItem = $state<Item | undefined>(undefined)
84
+ const inputId = $props.id()
28
85
 
29
- $effect(() => {
30
- const targetValue = field?.value() ?? value
31
- selectedItem = items.find((item) => getValue(item) === targetValue)
86
+ let search = $state('')
87
+ // Une énumération courte n'a que faire d'un champ de recherche; une fonction, elle, ne
88
+ // peut pas s'en passer, puisque le composant ne sait pas la filtrer.
89
+ const isSearchable = $derived(searchable ?? (Array.isArray(items) ? items.length > 7 : true))
90
+ const query = useItems({
91
+ source: () => items,
92
+ search: () => search,
93
+ getLabel: (item) => getLabel(item),
94
+ filter: () => filter,
95
+ debounceMs: (() => debounceMs)()
32
96
  })
97
+ const options = $derived(query.current ?? [])
98
+
99
+ // `value` porte l'item et non sa valeur soumise: celle-ci s'en dérive par `getValue`,
100
+ // alors que l'inverse demanderait une requête. C'est aussi ce qui rend l'état initial
101
+ // gratuit — pas de `defaultValue` à réconcilier avec la valeur du champ.
102
+ const submittedValue = $derived(value ? getValue(value) : undefined)
33
103
 
34
- const popover = usePopover({ listenFocus: false })
35
- const command = useCommand({
104
+ export const popover = usePopover({
105
+ listenFocus: false,
106
+ listenFocusout: true,
107
+ onShow: () => command.focusTrigger(),
108
+ // Un bouton désactivé n'émet plus ni clic ni focus; le raccourci, lui, écoute la
109
+ // fenêtre et ouvrirait un popover qu'aucun geste ne peut plus atteindre.
110
+ hotKey: (() => (disabled ? undefined : hotKey))()
111
+ })
112
+ export const command = useCommand({
36
113
  isEnable: () => popover.isOpen,
37
114
  onSelect: (index) => {
38
115
  popover.hide()
39
- const item = items[index]
116
+ const item = options[index]
117
+ onSelect?.(item, popover)
40
118
  if (!item) return
41
- selectedItem = item
42
- if (field) {
43
- field.set(getValue(item))
44
- } else {
45
- value = getValue(item)
46
- }
119
+ setValue(item)
47
120
  }
48
121
  })
122
+
123
+ function setValue(item: Item | undefined) {
124
+ value = item
125
+ // L'input caché suffit à soumettre; `set` garde la validation du formulaire à jour.
126
+ field?.set(item && getValue(item))
127
+ }
128
+
129
+ const isButtonSetNullVisible = $derived(nullable && value)
49
130
  </script>
50
131
 
51
- {#if field?.value() !== undefined}
52
- <input {...field.as('hidden', field.value() as string)} />
132
+ {#snippet defaultSnippet(item: Item)}
133
+ <span>{getLabel(item)}</span>
134
+ {/snippet}
135
+
136
+ {#snippet triggerButton()}
137
+ <button
138
+ id={inputId}
139
+ type="button"
140
+ {disabled}
141
+ class={['input w-full', field?.issues()?.length && 'input-error', klass]}
142
+ {...popover.trigger}
143
+ {...isSearchable ? {} : command.trigger}
144
+ >
145
+ <div class="grow text-left">
146
+ {#if !value}
147
+ <span class="opacity-60">{placeholder}</span>
148
+ {:else}
149
+ <!-- item.icon not rerender if not wrapped in #key -->
150
+ {#key value}{@render selected(value)}{/key}
151
+ {/if}
152
+ </div>
153
+ <ChevronsUpDownIcon size={14} opacity={0.7} />
154
+ </button>
155
+ {/snippet}
156
+
157
+ <!-- Rien à soumettre tant qu'aucune valeur n'est choisie: `as('hidden')` refuse une
158
+ valeur vide. -->
159
+ {#if field && submittedValue}
160
+ <input {...field.as('hidden', submittedValue)} />
53
161
  {/if}
54
162
 
55
- <div>
56
- <label class="floating-label">
57
- <span>{label}</span>
58
- <button
59
- type="button"
60
- class={['input', field?.issues.length && 'input-error']}
61
- {...popover.trigger}
62
- {...command.trigger}
63
- >
64
- <div class="grow text-left">
65
- {#if !selectedItem}
66
- <span class="opacity-60">Select a value</span>
67
- {:else if selected}
68
- <!-- item.icon not rerender if not wrapped in #key -->
69
- {#key selectedItem}{@render selected(selectedItem)}{/key}
70
- {:else}
71
- {getValue(selectedItem)}
72
- {/if}
73
- </div>
74
- <ChevronsUpDownIcon size={14} />
75
- </button>
76
- </label>
163
+ <div class="contents">
164
+ {#if !label}
165
+ {@render triggerButton()}
166
+ <Issues {field} />
167
+ {@render hint?.(value)}
168
+ {:else if variant === 'floating'}
169
+ <label class="floating-label">
170
+ <span>{label}</span>
171
+ {@render triggerButton()}
172
+ <Issues {field} />
173
+ {@render hint?.(value)}
174
+ </label>
175
+ {:else}
176
+ <fieldset class="fieldset">
177
+ <label class="label text-wrap" for={inputId}>
178
+ <span>{label}</span>
179
+ {@render labelAppend?.()}
180
+ </label>
181
+ {@render triggerButton()}
182
+ <Issues {field} />
183
+ {@render hint?.(value)}
184
+ </fieldset>
185
+ {/if}
77
186
 
78
187
  <div
79
188
  {...popover.content}
80
189
  class={['my-2 rounded-box border bg-base-100 shadow-xl']}
81
- style="width: anchor-size(width);"
190
+ style="min-width: anchor-size(width);"
82
191
  tabindex="-1"
83
192
  >
84
- <ul class="menu max-h-80 w-full flex-nowrap">
85
- {#each items as item, index (item)}
86
- {@const isSelected = index === command.selectedIndex}
193
+ {#if isSearchable || isButtonSetNullVisible || append || header}
194
+ <div class="sticky top-0 z-10 flex gap-2 bg-base-100/10 p-1 backdrop-blur-md">
195
+ {#if header}
196
+ {@render header(popover)}
197
+ {:else}
198
+ {#if isSearchable}
199
+ <label class="input grow input-ghost input-sm">
200
+ <SearchIcon opacity={0.6} size={20} />
201
+ <input
202
+ type="search"
203
+ placeholder="Recherche"
204
+ autocomplete="off"
205
+ bind:value={search}
206
+ {...command.trigger}
207
+ />
208
+ </label>
209
+ {:else}
210
+ <div class="grow"></div>
211
+ {/if}
212
+ {#if isButtonSetNullVisible}
213
+ <button
214
+ class="btn btn-square btn-soft btn-sm"
215
+ type="button"
216
+ onclick={() => {
217
+ setValue(undefined)
218
+ onSelect?.(undefined, popover)
219
+ popover.hide()
220
+ }}
221
+ use:tip={{ content: 'Pas de sélection' }}
222
+ >
223
+ <XIcon />
224
+ </button>
225
+ {/if}
226
+ {@render append?.(popover)}
227
+ {/if}
228
+ </div>
229
+ {/if}
230
+
231
+ <ul
232
+ class={[
233
+ 'menu max-h-80 w-full flex-nowrap gap-1 p-1',
234
+ (isSearchable || append || header) && 'pt-0'
235
+ ]}
236
+ >
237
+ {#each options as item, index (item)}
238
+ {@const isSelected = submittedValue === getValue(item)}
87
239
  {@const isFocus = index === command.focusIndex}
88
- <li>
240
+ <li {...propsLi}>
89
241
  <button
90
- {...command.item(index)}
91
- class={[isFocus && 'menu-focus']}
242
+ {...command.item(index, '')}
243
+ class={[
244
+ isSelected && 'border-hard border',
245
+ isFocus && 'menu-focus',
246
+ 'grow scroll-mt-13 scroll-mb-2'
247
+ ]}
92
248
  type="button"
93
249
  tabindex="-1"
94
250
  role="option"
95
251
  >
96
- {#if proposal}
97
- {@render proposal(item, { isSelected, isFocus })}
98
- {:else}
99
- {getValue(item)}
100
- {/if}
252
+ {@render proposal(item, { popover, isSelected, isFocus })}
101
253
  </button>
254
+ {@render proposalAppend?.(item, { popover, isSelected, isFocus })}
102
255
  </li>
103
256
  {/each}
104
257
  </ul>
258
+
259
+ <Loading {query} />
105
260
  </div>
106
- <Issues {field} />
107
261
  </div>
108
262
 
109
263
  <style>
@@ -1,19 +1,80 @@
1
1
  import type { RemoteFormField } from '@sveltejs/kit';
2
2
  import type { Snippet } from 'svelte';
3
+ import type { ClassValue, HTMLLiAttributes } from 'svelte/elements';
4
+ import { type PopoverType } from '../popover/usePopover.svelte.js';
5
+ import { type ItemsSource } from './useItems.svelte.js';
3
6
  declare function $$render<Item>(): {
4
7
  props: {
5
- label: string;
6
- items: Item[];
7
- getValue: (item: Item) => string;
8
+ /** Un tableau déjà chargé, une fonction (synchrone ou non), ou une remote query. */
9
+ items: ItemsSource<Item>;
10
+ label?: string;
11
+ getValue?: (item: NoInfer<Item>) => string;
12
+ /** Libellé affiché à défaut de snippet, et base du filtre d'une source tableau. */
13
+ getLabel?: (item: NoInfer<Item>) => string;
14
+ /** Ne s'applique qu'à une source tableau: une fonction porte son propre filtre. */
15
+ filter?: (item: NoInfer<Item>, search: string) => boolean;
16
+ /** Par défaut: toujours pour une fonction, au-delà de 7 entrées pour un tableau. */
17
+ searchable?: boolean;
18
+ debounceMs?: number;
8
19
  selected?: Snippet<[Item]>;
9
20
  proposal?: Snippet<[Item, {
21
+ popover: PopoverType;
22
+ isSelected: boolean;
23
+ isFocus: boolean;
24
+ }]>;
25
+ proposalAppend?: Snippet<[Item, {
26
+ popover: PopoverType;
10
27
  isSelected: boolean;
11
28
  isFocus: boolean;
12
29
  }]>;
13
30
  field?: RemoteFormField<string>;
14
- value?: string;
31
+ /**
32
+ * L'item, pas sa valeur soumise. Un parent qui le repilote après coup — remise à
33
+ * zéro, changement de jeu d'options — doit le lier par `bind:`: passé en simple
34
+ * prop, un `$bindable` cesse de suivre le parent dès que le composant y a écrit.
35
+ */
36
+ value?: NoInfer<Item>;
37
+ placeholder?: string;
38
+ class?: ClassValue;
39
+ nullable?: boolean;
40
+ /** La valeur reste soumise, mais la sélection ne peut plus changer. */
41
+ disabled?: boolean;
42
+ hint?: Snippet<[Item | undefined]>;
43
+ /** Rendu à droite du champ de recherche: une action « créer », typiquement. */
44
+ append?: Snippet<[PopoverType]>;
45
+ /** Rendu à droite du libellé. Sans effet sur la variante `floating`. */
46
+ labelAppend?: Snippet;
47
+ header?: Snippet<[PopoverType]>;
48
+ onSelect?: (item: NoInfer<Item> | undefined, popover: PopoverType) => void;
49
+ hotKey?: string;
50
+ variant?: "floating" | "block";
51
+ propsLi?: HTMLLiAttributes;
52
+ };
53
+ exports: {
54
+ popover: {
55
+ show: () => void;
56
+ hide: () => void;
57
+ readonly isOpen: boolean;
58
+ content: {
59
+ [x: symbol]: (node: HTMLElement) => () => void;
60
+ popover: "auto" | "hint" | "manual";
61
+ };
62
+ trigger: {
63
+ [x: symbol]: (node: HTMLElement) => () => void;
64
+ };
65
+ };
66
+ command: {
67
+ readonly focusIndex: number;
68
+ selectedIndex: number;
69
+ trigger: {
70
+ [x: symbol]: (node: HTMLElement) => () => void;
71
+ };
72
+ focusTrigger: () => void;
73
+ item: (index: number, scrollMargin?: string) => {
74
+ [x: symbol]: (node: HTMLElement) => () => void;
75
+ };
76
+ };
15
77
  };
16
- exports: {};
17
78
  bindings: "value";
18
79
  slots: {};
19
80
  events: {};
@@ -23,7 +84,31 @@ declare class __sveltets_Render<Item> {
23
84
  events(): ReturnType<typeof $$render<Item>>['events'];
24
85
  slots(): ReturnType<typeof $$render<Item>>['slots'];
25
86
  bindings(): "value";
26
- exports(): {};
87
+ exports(): {
88
+ popover: {
89
+ show: () => void;
90
+ hide: () => void;
91
+ readonly isOpen: boolean;
92
+ content: {
93
+ [x: symbol]: (node: HTMLElement) => () => void;
94
+ popover: "auto" | "hint" | "manual";
95
+ };
96
+ trigger: {
97
+ [x: symbol]: (node: HTMLElement) => () => void;
98
+ };
99
+ };
100
+ command: {
101
+ readonly focusIndex: number;
102
+ selectedIndex: number;
103
+ trigger: {
104
+ [x: symbol]: (node: HTMLElement) => () => void;
105
+ };
106
+ focusTrigger: () => void;
107
+ item: (index: number, scrollMargin?: string) => {
108
+ [x: symbol]: (node: HTMLElement) => () => void;
109
+ };
110
+ };
111
+ };
27
112
  }
28
113
  interface $$IsomorphicComponent {
29
114
  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']>> & {
@@ -3,6 +3,7 @@
3
3
  import { type Options, parseOptions } from '../index.js'
4
4
  import Issues from './Issues.svelte'
5
5
  import type { SelectProps } from './type.js'
6
+ import type { Snippet } from 'svelte'
6
7
 
7
8
  let {
8
9
  label,
@@ -11,38 +12,69 @@
11
12
  options: optionsProp,
12
13
  class: klass,
13
14
  multiple,
15
+ variant = 'block',
16
+ labelAppend,
14
17
  ...props
15
18
  }: {
16
19
  label: string
17
20
  options: Options
18
21
  field?: RemoteFormField<string | string[]>
22
+ /**
23
+ * Sans `field`, la valeur liée. Avec, la valeur initiale du champ — celui-ci prend
24
+ * ensuite le relais et devient la source de vérité.
25
+ */
19
26
  value?: string | string[]
27
+ variant?: 'floating' | 'block'
28
+ /** Rendu à droite du libellé. Sans effet sur la variante `floating`. */
29
+ labelAppend?: Snippet
20
30
  } & SelectProps = $props()
21
31
 
22
32
  const options = $derived(parseOptions(optionsProp))
33
+ const inputId = $props.id()
34
+
35
+ // Sans valeur initiale, `as()` rend `value: undefined` tant que le champ n'a rien reçu: le
36
+ // moindre re-rendu du spread — l'`aria-invalid` d'une soumission refusée, typiquement —
37
+ // désélectionne alors tout. La liaison, elle, ne rattrape rien: sa source n'a pas bougé.
38
+ const fieldProps = $derived.by(() => {
39
+ if (!field) return undefined
40
+ if (multiple === true) {
41
+ const initial = Array.isArray(value) ? value : undefined
42
+ return initial ? field.as('select multiple', initial) : field.as('select multiple')
43
+ }
44
+ const initial = typeof value === 'string' ? value : undefined
45
+ return initial === undefined ? field.as('select') : field.as('select', initial)
46
+ })
23
47
  </script>
24
48
 
25
- <div>
26
- <label class="floating-label">
49
+ {#snippet snippetSelect()}
50
+ <select id={inputId} class="select w-full" {...fieldProps} bind:value {...props}>
51
+ {#each options as option (option.value)}
52
+ <option value={option.value} class={['', option.class]} disabled={option.disabled}>
53
+ {#if option.icon}
54
+ <option.icon size={18} opacity={0.8} />
55
+ {/if}
56
+ <span class="grow">{option.label}</span>
57
+ </option>
58
+ {/each}
59
+ </select>
60
+ {/snippet}
61
+
62
+ {#if variant === 'floating'}
63
+ <label class={['floating-label', klass]}>
27
64
  <span>{label}</span>
28
- <select
29
- class={['select', klass]}
30
- {...field?.as(multiple !== true ? 'select' : 'select multiple')}
31
- bind:value
32
- {...props}
33
- >
34
- {#each options as option (option.value)}
35
- <option value={option.value} class={['', option.class]} disabled={option.disabled}>
36
- {#if option.icon}
37
- <option.icon size={18} opacity={0.8} />
38
- {/if}
39
- <span class="grow">{option.label}</span>
40
- </option>
41
- {/each}
42
- </select>
65
+ {@render snippetSelect()}
66
+ <Issues {field} />
43
67
  </label>
44
- <Issues {field} />
45
- </div>
68
+ {:else}
69
+ <fieldset class={['fieldset', klass]}>
70
+ <label class="label text-wrap" for={inputId}>
71
+ <span>{label}</span>
72
+ {@render labelAppend?.()}
73
+ </label>
74
+ {@render snippetSelect()}
75
+ <Issues {field} />
76
+ </fieldset>
77
+ {/if}
46
78
 
47
79
  <style>
48
80
  option::checkmark {
@@ -1,11 +1,19 @@
1
1
  import type { RemoteFormField } from '@sveltejs/kit';
2
2
  import { type Options } from '../index.js';
3
3
  import type { SelectProps } from './type.js';
4
+ import type { Snippet } from 'svelte';
4
5
  type $$ComponentProps = {
5
6
  label: string;
6
7
  options: Options;
7
8
  field?: RemoteFormField<string | string[]>;
9
+ /**
10
+ * Sans `field`, la valeur liée. Avec, la valeur initiale du champ — celui-ci prend
11
+ * ensuite le relais et devient la source de vérité.
12
+ */
8
13
  value?: string | string[];
14
+ variant?: 'floating' | 'block';
15
+ /** Rendu à droite du libellé. Sans effet sur la variante `floating`. */
16
+ labelAppend?: Snippet;
9
17
  } & SelectProps;
10
18
  declare const InputSelectNative: import("svelte").Component<$$ComponentProps, {}, "value">;
11
19
  type InputSelectNative = ReturnType<typeof InputSelectNative>;
@@ -2,6 +2,7 @@
2
2
  import type { RemoteFormField } from '@sveltejs/kit'
3
3
  import Issues from './Issues.svelte'
4
4
  import type { InputProps } from './type.js'
5
+ import type { Snippet } from 'svelte'
5
6
 
6
7
  let {
7
8
  label,
@@ -9,13 +10,20 @@
9
10
  value = $bindable(),
10
11
  type = 'text',
11
12
  class: klass,
12
- variant = 'floating',
13
+ variant = 'block',
14
+ labelAppend,
13
15
  ...props
14
16
  }: {
15
17
  label: string
16
18
  field?: RemoteFormField<string>
19
+ /**
20
+ * Sans `field`, la valeur liée. Avec, la valeur initiale du champ — celui-ci prend
21
+ * ensuite le relais et devient la source de vérité.
22
+ */
17
23
  value?: string
18
24
  variant?: 'floating' | 'block'
25
+ /** Rendu à droite du libellé. Sans effet sur la variante `floating`. */
26
+ labelAppend?: Snippet
19
27
  type?:
20
28
  | 'text'
21
29
  | 'email'
@@ -35,7 +43,7 @@
35
43
  const inputId = $props.id()
36
44
  const inputProps = $derived({
37
45
  id: inputId,
38
- class: ['input', klass],
46
+ class: 'input w-full',
39
47
  placeholder: variant === 'floating' ? label : '',
40
48
  ...props
41
49
  })
@@ -43,21 +51,28 @@
43
51
 
44
52
  {#snippet snippetInput()}
45
53
  {#if field}
46
- <input {...inputProps} {...field.as(type)} />
54
+ {#if value === undefined}
55
+ <input {...inputProps} {...field.as(type)} />
56
+ {:else}
57
+ <input {...inputProps} {...field.as(type, value)} />
58
+ {/if}
47
59
  {:else}
48
60
  <input {...inputProps} {type} bind:value />
49
61
  {/if}
50
62
  {/snippet}
51
63
 
52
64
  {#if variant === 'floating'}
53
- <label class="floating-label">
65
+ <label class={['floating-label', klass]}>
54
66
  <span>{label}</span>
55
67
  {@render snippetInput()}
56
68
  <Issues {field} />
57
69
  </label>
58
70
  {:else}
59
- <fieldset class="fieldset">
60
- <label class="label" for={inputId}>{label}</label>
71
+ <fieldset class={['fieldset', klass]}>
72
+ <label class="label text-wrap" for={inputId}>
73
+ <span>{label}</span>
74
+ {@render labelAppend?.()}
75
+ </label>
61
76
  {@render snippetInput()}
62
77
  <Issues {field} />
63
78
  </fieldset>
@@ -1,10 +1,17 @@
1
1
  import type { RemoteFormField } from '@sveltejs/kit';
2
2
  import type { InputProps } from './type.js';
3
+ import type { Snippet } from 'svelte';
3
4
  type $$ComponentProps = {
4
5
  label: string;
5
6
  field?: RemoteFormField<string>;
7
+ /**
8
+ * Sans `field`, la valeur liée. Avec, la valeur initiale du champ — celui-ci prend
9
+ * ensuite le relais et devient la source de vérité.
10
+ */
6
11
  value?: string;
7
12
  variant?: 'floating' | 'block';
13
+ /** Rendu à droite du libellé. Sans effet sur la variante `floating`. */
14
+ labelAppend?: Snippet;
8
15
  type?: 'text' | 'email' | 'password' | 'url' | 'tel' | 'search' | 'date' | 'datetime-local' | 'time' | 'month' | 'week' | 'color' | 'select';
9
16
  } & InputProps;
10
17
  declare const InputString: import("svelte").Component<$$ComponentProps, {}, "value">;