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.
- package/dist/_doc/data.d.ts +3 -1
- package/dist/_doc/data.js +6 -2
- package/dist/command/index.d.ts +1 -1
- package/dist/command/index.js +1 -1
- package/dist/command/{command.svelte.js → useCommand.svelte.js} +1 -1
- package/dist/input/InputBoolean.svelte +41 -8
- package/dist/input/InputBoolean.svelte.d.ts +7 -0
- package/dist/input/InputCheckboxes.svelte +123 -0
- package/dist/input/InputCheckboxes.svelte.d.ts +19 -0
- package/dist/input/InputDateTime.svelte +199 -0
- package/dist/input/InputDateTime.svelte.d.ts +26 -0
- package/dist/input/InputMultiSelect.svelte +324 -0
- package/dist/input/{InputRelation.svelte.d.ts → InputMultiSelect.svelte.d.ts} +32 -13
- package/dist/input/InputNumber.svelte +43 -6
- package/dist/input/InputNumber.svelte.d.ts +8 -0
- package/dist/input/InputRadio.svelte +62 -24
- package/dist/input/InputRadio.svelte.d.ts +3 -0
- package/dist/input/InputRange.svelte +24 -4
- package/dist/input/InputRange.svelte.d.ts +9 -0
- package/dist/input/InputSelect.svelte +216 -62
- package/dist/input/InputSelect.svelte.d.ts +91 -6
- package/dist/input/InputSelectNative.svelte +51 -19
- package/dist/input/InputSelectNative.svelte.d.ts +8 -0
- package/dist/input/InputString.svelte +21 -6
- package/dist/input/InputString.svelte.d.ts +7 -0
- package/dist/input/InputTextarea.svelte +58 -11
- package/dist/input/InputTextarea.svelte.d.ts +14 -0
- package/dist/input/index.d.ts +4 -1
- package/dist/input/index.js +4 -1
- package/dist/input/useItems.svelte.d.ts +38 -0
- package/dist/input/useItems.svelte.js +142 -0
- package/dist/loading/Loading.svelte +3 -2
- package/dist/loading/Loading.svelte.d.ts +3 -2
- package/dist/popover/Popover.svelte +1 -2
- package/dist/popover/Popover.svelte.d.ts +13 -0
- package/dist/popover/index.d.ts +1 -1
- package/dist/popover/index.js +1 -1
- package/dist/popover/{popover.svelte.d.ts → usePopover.svelte.d.ts} +2 -1
- package/dist/popover/usePopover.svelte.js +145 -0
- package/dist/remote/useForm.d.ts +4 -5
- package/dist/remote/useForm.js +9 -4
- package/dist/state/index.d.ts +1 -1
- package/dist/state/index.js +1 -1
- package/dist/ui/button/ButtonDelete.svelte +6 -3
- package/dist/ui/button/ButtonDelete.svelte.d.ts +1 -1
- package/dist/ui/dialog/Dialog.svelte +5 -5
- package/dist/ui/drawer/Drawer.svelte +74 -26
- package/dist/ui/drawer/Drawer.svelte.d.ts +10 -4
- package/dist/ui/drawer/index.d.ts +1 -0
- package/dist/ui/drawer/index.js +1 -0
- package/dist/ui/drawer/inertBackground.d.ts +13 -0
- package/dist/ui/drawer/inertBackground.js +59 -0
- package/dist/ui/drawer/useLayer.svelte.d.ts +1 -1
- package/dist/ui/drawer/useLayer.svelte.js +11 -7
- package/dist/ui/index.d.ts +1 -0
- package/dist/ui/index.js +1 -0
- package/dist/ui/menu/ContextMenu.svelte +30 -17
- package/dist/ui/menu/ContextMenu.svelte.d.ts +2 -3
- package/dist/ui/menu/DropDown.svelte +1 -1
- package/dist/ui/menu/DropDown.svelte.d.ts +1 -1
- package/dist/ui/menu/DropDownMenu.svelte +12 -10
- package/dist/ui/menu/DropDownMenu.svelte.d.ts +2 -3
- package/dist/ui/menu/index.d.ts +0 -1
- package/dist/ui/menu/index.js +0 -2
- package/dist/ui/range/RangePicker.svelte +25 -13
- package/dist/ui/range/RangePicker.svelte.d.ts +9 -25
- package/dist/ui/range/RangePickerButton.svelte +49 -32
- package/dist/ui/range/RangePickerButton.svelte.d.ts +1 -1
- package/dist/ui/table/Table.svelte +16 -16
- package/dist/ui/table/Table.svelte.d.ts +1 -1
- package/dist/ui/table/TableFieldsEdition.svelte +25 -23
- package/dist/ui/table/TableHead.svelte +5 -1
- package/dist/ui/table/cell/TableCellBoolean.svelte +2 -2
- package/dist/ui/table/head/OrderButtons.svelte +21 -20
- package/dist/ui/table/head/OrderButtons.svelte.d.ts +2 -0
- package/dist/ui/table/head/TableHeadDate.svelte +66 -57
- package/dist/ui/table/head/TableHeadNumber.svelte +58 -22
- package/dist/ui/table/head/TableHeadSelect.svelte +31 -28
- package/dist/ui/table/head/TableHeadString.svelte +12 -8
- package/dist/ui/table/index.d.ts +1 -1
- package/dist/ui/table/index.js +1 -1
- package/package.json +7 -4
- package/dist/input/InputRelation.svelte +0 -200
- package/dist/popover/popover.svelte.js +0 -106
- package/dist/ui/table/TableViewSelect.svelte +0 -142
- package/dist/ui/table/TableViewSelect.svelte.d.ts +0 -11
- /package/dist/command/{command.svelte.d.ts → useCommand.svelte.d.ts} +0 -0
- /package/dist/state/{param.svelte.d.ts → param.d.ts} +0 -0
- /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 {
|
|
6
|
-
import {
|
|
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
|
-
|
|
14
|
-
|
|
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
|
-
|
|
19
|
-
items: Item
|
|
20
|
-
|
|
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
|
-
|
|
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
|
-
|
|
84
|
+
const inputId = $props.id()
|
|
28
85
|
|
|
29
|
-
$
|
|
30
|
-
|
|
31
|
-
|
|
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({
|
|
35
|
-
|
|
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 =
|
|
116
|
+
const item = options[index]
|
|
117
|
+
onSelect?.(item, popover)
|
|
40
118
|
if (!item) return
|
|
41
|
-
|
|
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
|
-
{#
|
|
52
|
-
<
|
|
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
|
-
|
|
57
|
-
|
|
58
|
-
<
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
{
|
|
63
|
-
|
|
64
|
-
<
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
{
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
<
|
|
75
|
-
|
|
76
|
-
|
|
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
|
-
|
|
85
|
-
|
|
86
|
-
{
|
|
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={[
|
|
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
|
-
{
|
|
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
|
-
|
|
6
|
-
items: Item
|
|
7
|
-
|
|
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
|
-
|
|
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
|
-
|
|
26
|
-
<
|
|
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
|
-
|
|
29
|
-
|
|
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
|
-
|
|
45
|
-
|
|
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 = '
|
|
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:
|
|
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
|
-
|
|
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=
|
|
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=
|
|
60
|
-
<label class="label" for={inputId}>
|
|
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">;
|