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