fuma 2.2.7 → 2.5.1
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/input/InputBoolean.svelte +19 -4
- package/dist/input/InputBoolean.svelte.d.ts +3 -0
- package/dist/input/InputCheckboxes.svelte +8 -1
- package/dist/input/InputCheckboxes.svelte.d.ts +3 -0
- package/dist/input/InputDateTime.svelte +5 -0
- package/dist/input/InputDateTime.svelte.d.ts +3 -0
- package/dist/input/InputMultiSelect.svelte +60 -22
- package/dist/input/InputMultiSelect.svelte.d.ts +5 -0
- package/dist/input/InputNumber.svelte +8 -1
- package/dist/input/InputNumber.svelte.d.ts +3 -0
- package/dist/input/InputRadio.svelte +9 -2
- package/dist/input/InputRadio.svelte.d.ts +3 -0
- package/dist/input/InputRange.svelte +5 -0
- package/dist/input/InputRange.svelte.d.ts +3 -0
- package/dist/input/InputSelect.svelte +15 -3
- package/dist/input/InputSelect.svelte.d.ts +4 -0
- package/dist/input/InputSelectNative.svelte +8 -1
- package/dist/input/InputSelectNative.svelte.d.ts +3 -0
- package/dist/input/InputString.svelte +8 -1
- package/dist/input/InputString.svelte.d.ts +3 -0
- package/dist/input/InputTextarea.svelte +23 -5
- package/dist/input/InputTextarea.svelte.d.ts +10 -1
- package/dist/ui/button/ButtonDelete.svelte +5 -4
- package/dist/ui/button/ButtonDelete.svelte.d.ts +1 -1
- package/dist/ui/table/Table.svelte +9 -6
- package/dist/ui/table/TableFieldsEdition.svelte +1 -1
- 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 +4 -2
- package/dist/ui/table/head/TableHeadNumber.svelte +3 -3
- package/dist/ui/table/head/TableHeadSelect.svelte +1 -1
- package/dist/ui/table/head/TableHeadString.svelte +1 -1
- package/dist/ui/table/index.d.ts +0 -1
- package/dist/ui/table/index.js +0 -1
- package/dist/validation/zod.js +2 -2
- package/package.json +1 -1
- package/dist/ui/table/TableViewSelect.svelte +0 -147
- package/dist/ui/table/TableViewSelect.svelte.d.ts +0 -11
|
@@ -3,13 +3,17 @@
|
|
|
3
3
|
import type { RemoteFormField } from '@sveltejs/kit'
|
|
4
4
|
import Issues from './Issues.svelte'
|
|
5
5
|
import type { InputProps } from './type.js'
|
|
6
|
+
import type { Snippet } from 'svelte'
|
|
6
7
|
|
|
7
8
|
let {
|
|
8
9
|
field,
|
|
9
10
|
label,
|
|
10
11
|
checked = $bindable(),
|
|
11
12
|
hint,
|
|
13
|
+
labelAppend,
|
|
12
14
|
variant = 'checkbox',
|
|
15
|
+
class: klass,
|
|
16
|
+
disabled,
|
|
13
17
|
...props
|
|
14
18
|
}: {
|
|
15
19
|
label: string
|
|
@@ -20,6 +24,8 @@
|
|
|
20
24
|
*/
|
|
21
25
|
checked?: boolean
|
|
22
26
|
hint?: string
|
|
27
|
+
/** Rendu à droite du libellé, dans le `label`: un clic dessus bascule la case. */
|
|
28
|
+
labelAppend?: Snippet
|
|
23
29
|
variant?: 'checkbox' | 'switch'
|
|
24
30
|
} & InputProps = $props()
|
|
25
31
|
|
|
@@ -30,7 +36,8 @@
|
|
|
30
36
|
<label
|
|
31
37
|
class={[
|
|
32
38
|
'input block not-disabled:cursor-pointer not-disabled:hover:bg-base-200',
|
|
33
|
-
'group h-auto min-h-10 w-full'
|
|
39
|
+
'group h-auto min-h-10 w-full',
|
|
40
|
+
klass
|
|
34
41
|
]}
|
|
35
42
|
>
|
|
36
43
|
<!-- `.input` de daisyUI pose `white-space: nowrap`: sans `text-wrap`, un libellé un peu
|
|
@@ -38,17 +45,18 @@
|
|
|
38
45
|
contenu, `--size` n'étant plus qu'un plancher. -->
|
|
39
46
|
<div class="flex min-h-(--size) items-center gap-2 py-1.5">
|
|
40
47
|
<div class="grow text-wrap">{label}</div>
|
|
48
|
+
{@render labelAppend?.()}
|
|
41
49
|
{#if field}
|
|
42
50
|
<!-- `as('checkbox')` sans valeur pose `checked: undefined`: la case d'un champ
|
|
43
51
|
déjà à `true` partirait décochée, alors que la coche dessinée, elle, suit
|
|
44
52
|
`checked`. Les deux ne se contredisent que si l'état initial n'est pas transmis. -->
|
|
45
53
|
{#if checked === undefined}
|
|
46
|
-
<input {...field.as('checkbox')} class={['peer w-0']} {...props} />
|
|
54
|
+
<input {...field.as('checkbox')} class={['peer w-0']} {disabled} {...props} />
|
|
47
55
|
{:else}
|
|
48
|
-
<input {...field.as('checkbox', checked)} class={['peer w-0']} {...props} />
|
|
56
|
+
<input {...field.as('checkbox', checked)} class={['peer w-0']} {disabled} {...props} />
|
|
49
57
|
{/if}
|
|
50
58
|
{:else}
|
|
51
|
-
<input type="checkbox" class={['peer w-0']} bind:checked {...props} />
|
|
59
|
+
<input type="checkbox" class={['peer w-0']} bind:checked {disabled} {...props} />
|
|
52
60
|
{/if}
|
|
53
61
|
{#if variant === 'checkbox'}
|
|
54
62
|
{@render variantCheckbox()}
|
|
@@ -67,6 +75,13 @@
|
|
|
67
75
|
</div>
|
|
68
76
|
{/if}
|
|
69
77
|
</label>
|
|
78
|
+
{#if field && !isChecked && !disabled}
|
|
79
|
+
<!-- Une case décochée ne transmet rien: sans ce relais, le serveur ne distinguerait pas
|
|
80
|
+
« décochée » de « champ non rendu », et une valeur ne pourrait jamais repasser à faux.
|
|
81
|
+
Il porte la clé de la case, d'où l'exclusion mutuelle — SvelteKit refuse un formulaire
|
|
82
|
+
où une clé apparaît deux fois. Un contrôle désactivé, lui, ne transmet toujours rien. -->
|
|
83
|
+
<input {...field.as('hidden', false)} />
|
|
84
|
+
{/if}
|
|
70
85
|
<Issues {field} />
|
|
71
86
|
</div>
|
|
72
87
|
|
|
@@ -1,5 +1,6 @@
|
|
|
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<boolean>;
|
|
@@ -9,6 +10,8 @@ type $$ComponentProps = {
|
|
|
9
10
|
*/
|
|
10
11
|
checked?: boolean;
|
|
11
12
|
hint?: string;
|
|
13
|
+
/** Rendu à droite du libellé, dans le `label`: un clic dessus bascule la case. */
|
|
14
|
+
labelAppend?: Snippet;
|
|
12
15
|
variant?: 'checkbox' | 'switch';
|
|
13
16
|
} & InputProps;
|
|
14
17
|
declare const InputBoolean: import("svelte").Component<$$ComponentProps, {}, "checked">;
|
|
@@ -4,12 +4,14 @@
|
|
|
4
4
|
import { type Options, parseOptions } from '../utils/index.js'
|
|
5
5
|
import Issues from './Issues.svelte'
|
|
6
6
|
import type { InputProps } from './type.js'
|
|
7
|
+
import type { Snippet } from 'svelte'
|
|
7
8
|
|
|
8
9
|
let {
|
|
9
10
|
field,
|
|
10
11
|
label,
|
|
11
12
|
value = $bindable([]),
|
|
12
13
|
options: optionsProp,
|
|
14
|
+
labelAppend,
|
|
13
15
|
...props
|
|
14
16
|
}: {
|
|
15
17
|
field?: RemoteFormField<string[]>
|
|
@@ -20,6 +22,8 @@
|
|
|
20
22
|
*/
|
|
21
23
|
value?: string[]
|
|
22
24
|
options: Options
|
|
25
|
+
/** Rendu à droite du libellé. */
|
|
26
|
+
labelAppend?: Snippet
|
|
23
27
|
} & InputProps = $props()
|
|
24
28
|
|
|
25
29
|
const options = $derived(parseOptions(optionsProp))
|
|
@@ -32,7 +36,10 @@
|
|
|
32
36
|
</script>
|
|
33
37
|
|
|
34
38
|
<div class="flex flex-col gap-1">
|
|
35
|
-
<span class="label
|
|
39
|
+
<span class="label text-xs text-wrap">
|
|
40
|
+
<span>{label}</span>
|
|
41
|
+
{@render labelAppend?.()}
|
|
42
|
+
</span>
|
|
36
43
|
<div class="join join-vertical">
|
|
37
44
|
{#each options as option (option.value)}
|
|
38
45
|
{@const {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { RemoteFormField } from '@sveltejs/kit';
|
|
2
2
|
import { type Options } from '../utils/index.js';
|
|
3
3
|
import type { InputProps } from './type.js';
|
|
4
|
+
import type { Snippet } from 'svelte';
|
|
4
5
|
type $$ComponentProps = {
|
|
5
6
|
field?: RemoteFormField<string[]>;
|
|
6
7
|
label: string;
|
|
@@ -10,6 +11,8 @@ type $$ComponentProps = {
|
|
|
10
11
|
*/
|
|
11
12
|
value?: string[];
|
|
12
13
|
options: Options;
|
|
14
|
+
/** Rendu à droite du libellé. */
|
|
15
|
+
labelAppend?: Snippet;
|
|
13
16
|
} & InputProps;
|
|
14
17
|
declare const InputCheckboxes: import("svelte").Component<$$ComponentProps, {}, "value">;
|
|
15
18
|
type InputCheckboxes = ReturnType<typeof InputCheckboxes>;
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
import utcPlugin from 'dayjs/plugin/utc.js'
|
|
7
7
|
import Issues from './Issues.svelte'
|
|
8
8
|
import type { InputProps } from './type.js'
|
|
9
|
+
import type { Snippet } from 'svelte'
|
|
9
10
|
|
|
10
11
|
dayjs.extend(utcPlugin)
|
|
11
12
|
dayjs.extend(timezonePlugin)
|
|
@@ -19,6 +20,7 @@
|
|
|
19
20
|
layout = 'split',
|
|
20
21
|
variant = 'block',
|
|
21
22
|
hint,
|
|
23
|
+
labelAppend,
|
|
22
24
|
transform = (v) => v,
|
|
23
25
|
onchange,
|
|
24
26
|
class: klass,
|
|
@@ -37,6 +39,8 @@
|
|
|
37
39
|
/** Sans effet sur `split`, dont la ligne de label porte le `hint`. */
|
|
38
40
|
variant?: 'floating' | 'block'
|
|
39
41
|
hint?: string
|
|
42
|
+
/** Rendu à droite du libellé, après le `hint`. */
|
|
43
|
+
labelAppend?: Snippet
|
|
40
44
|
/** Ajuste la valeur avant qu'elle ne soit posée — borne, arrondi, décalage. */
|
|
41
45
|
transform?: (value: Date) => Date
|
|
42
46
|
onchange?: (value: Date) => void
|
|
@@ -177,6 +181,7 @@
|
|
|
177
181
|
{#if hint}
|
|
178
182
|
<span class="ml-auto">{hint}</span>
|
|
179
183
|
{/if}
|
|
184
|
+
{@render labelAppend?.()}
|
|
180
185
|
</label>
|
|
181
186
|
{#if layout === 'datetime'}
|
|
182
187
|
{@render inputDateTime()}
|
|
@@ -1,5 +1,6 @@
|
|
|
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
|
/** La date part en ISO dans un champ caché: au schéma de la reconstruire. */
|
|
@@ -14,6 +15,8 @@ type $$ComponentProps = {
|
|
|
14
15
|
/** Sans effet sur `split`, dont la ligne de label porte le `hint`. */
|
|
15
16
|
variant?: 'floating' | 'block';
|
|
16
17
|
hint?: string;
|
|
18
|
+
/** Rendu à droite du libellé, après le `hint`. */
|
|
19
|
+
labelAppend?: Snippet;
|
|
17
20
|
/** Ajuste la valeur avant qu'elle ne soit posée — borne, arrondi, décalage. */
|
|
18
21
|
transform?: (value: Date) => Date;
|
|
19
22
|
onchange?: (value: Date) => void;
|
|
@@ -29,9 +29,12 @@
|
|
|
29
29
|
field,
|
|
30
30
|
value = $bindable([]),
|
|
31
31
|
placeholder = 'Selectionner des valeurs',
|
|
32
|
+
canReset = false,
|
|
32
33
|
class: klass,
|
|
34
|
+
disabled = false,
|
|
33
35
|
hint,
|
|
34
36
|
append,
|
|
37
|
+
labelAppend,
|
|
35
38
|
onSelect,
|
|
36
39
|
hotKey,
|
|
37
40
|
variant = 'block'
|
|
@@ -57,10 +60,15 @@
|
|
|
57
60
|
*/
|
|
58
61
|
value?: NoInfer<Item>[]
|
|
59
62
|
placeholder?: string
|
|
63
|
+
canReset?: boolean
|
|
60
64
|
class?: ClassValue
|
|
65
|
+
/** Les valeurs restent soumises, mais la sélection ne peut plus changer. */
|
|
66
|
+
disabled?: boolean
|
|
61
67
|
hint?: Snippet<[Item[]]>
|
|
62
68
|
/** Rendu à droite du champ de recherche: une action « créer », typiquement. */
|
|
63
69
|
append?: Snippet<[PopoverType]>
|
|
70
|
+
/** Rendu à droite du libellé. Sans effet sur la variante `floating`. */
|
|
71
|
+
labelAppend?: Snippet
|
|
64
72
|
onSelect?: (items: NoInfer<Item>[], popover: PopoverType) => void
|
|
65
73
|
hotKey?: string
|
|
66
74
|
variant?: 'floating' | 'block'
|
|
@@ -68,6 +76,10 @@
|
|
|
68
76
|
|
|
69
77
|
const inputId = $props.id()
|
|
70
78
|
const listId = `${inputId}-list`
|
|
79
|
+
// `<label for>` ne nomme que les éléments étiquetables, ce qu'un `div` n'est pas: sans
|
|
80
|
+
// cette référence explicite, le combobox reste sans nom accessible.
|
|
81
|
+
const labelId = `${inputId}-label`
|
|
82
|
+
let trigger = $state<HTMLDivElement>()
|
|
71
83
|
|
|
72
84
|
let search = $state('')
|
|
73
85
|
// Une énumération courte n'a que faire d'un champ de recherche; une fonction, elle, ne
|
|
@@ -91,7 +103,9 @@
|
|
|
91
103
|
listenFocus: false,
|
|
92
104
|
listenFocusout: true,
|
|
93
105
|
onShow: () => command.focusTrigger(),
|
|
94
|
-
|
|
106
|
+
// Le déclencheur est un `div`: `disabled` ne le neutralise pas comme un bouton, et
|
|
107
|
+
// le raccourci ouvrirait un popover qu'aucun geste ne peut plus atteindre.
|
|
108
|
+
hotKey: (() => (disabled ? undefined : hotKey))()
|
|
95
109
|
})
|
|
96
110
|
// Un multi-select ne se referme pas à chaque choix: `popover.hide()` n'est appelé
|
|
97
111
|
// que par le bouton de fermeture.
|
|
@@ -120,6 +134,11 @@
|
|
|
120
134
|
function commit() {
|
|
121
135
|
// Les cases cachées suffisent à soumettre; `set` garde la validation à jour.
|
|
122
136
|
field?.set(selectedValues)
|
|
137
|
+
// Un choix n'émet aucun évènement de formulaire: les cases sont écrites par le code,
|
|
138
|
+
// et le popover reste ouvert — rien ne rend donc la main au déclencheur, contrairement
|
|
139
|
+
// au select simple. Sans cette annonce, ce qui surveille le formulaire (une barre de
|
|
140
|
+
// sauvegarde) ne voit la sélection qu'au choix suivant.
|
|
141
|
+
trigger?.dispatchEvent(new Event('change', { bubbles: true }))
|
|
123
142
|
onSelect?.(value, popover)
|
|
124
143
|
}
|
|
125
144
|
</script>
|
|
@@ -132,25 +151,29 @@
|
|
|
132
151
|
<!-- Un `div` et non un `button`: les puces portent leur propre bouton de retrait, et
|
|
133
152
|
un bouton ne peut pas en contenir un autre. -->
|
|
134
153
|
<div
|
|
154
|
+
bind:this={trigger}
|
|
135
155
|
id={inputId}
|
|
136
156
|
role="combobox"
|
|
137
157
|
aria-controls={listId}
|
|
158
|
+
aria-labelledby={label ? labelId : undefined}
|
|
138
159
|
aria-expanded={popover.isOpen}
|
|
139
160
|
aria-haspopup="listbox"
|
|
140
|
-
|
|
161
|
+
aria-disabled={disabled}
|
|
162
|
+
tabindex={disabled ? -1 : 0}
|
|
141
163
|
class={[
|
|
142
164
|
'input h-auto min-h-(--size) w-full flex-wrap items-center gap-1 py-1',
|
|
143
|
-
'
|
|
165
|
+
!disabled && 'cursor-pointer',
|
|
144
166
|
field?.issues()?.length && 'input-error',
|
|
145
167
|
klass
|
|
146
168
|
]}
|
|
147
169
|
onkeydown={(event) => {
|
|
170
|
+
if (disabled) return
|
|
148
171
|
if (event.key !== 'Enter' && event.key !== ' ') return
|
|
149
172
|
event.preventDefault()
|
|
150
173
|
popover.show()
|
|
151
174
|
}}
|
|
152
|
-
{...popover.trigger}
|
|
153
|
-
{...isSearchable ? {} : command.trigger}
|
|
175
|
+
{...disabled ? {} : popover.trigger}
|
|
176
|
+
{...isSearchable || disabled ? {} : command.trigger}
|
|
154
177
|
>
|
|
155
178
|
{#if !value.length}
|
|
156
179
|
<span class="opacity-60">{placeholder}</span>
|
|
@@ -162,19 +185,21 @@
|
|
|
162
185
|
>
|
|
163
186
|
<!-- item.icon not rerender if not wrapped in #key -->
|
|
164
187
|
{#key item}{@render selected(item)}{/key}
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
188
|
+
{#if !disabled}
|
|
189
|
+
<button
|
|
190
|
+
type="button"
|
|
191
|
+
class="btn btn-circle btn-ghost btn-xs"
|
|
192
|
+
aria-label="Retirer"
|
|
193
|
+
use:tip={{ content: 'Retirer' }}
|
|
194
|
+
onclick={(event) => {
|
|
195
|
+
// Sans quoi le clic remonte au combobox et rouvre le popover.
|
|
196
|
+
event.stopPropagation()
|
|
197
|
+
remove(item)
|
|
198
|
+
}}
|
|
199
|
+
>
|
|
200
|
+
<XIcon size={12} />
|
|
201
|
+
</button>
|
|
202
|
+
{/if}
|
|
178
203
|
</span>
|
|
179
204
|
{/each}
|
|
180
205
|
{/if}
|
|
@@ -196,12 +221,15 @@
|
|
|
196
221
|
{@render triggerCombobox()}
|
|
197
222
|
{:else if variant === 'floating'}
|
|
198
223
|
<label class="floating-label">
|
|
199
|
-
<span>{label}</span>
|
|
224
|
+
<span id={labelId}>{label}</span>
|
|
200
225
|
{@render triggerCombobox()}
|
|
201
226
|
</label>
|
|
202
227
|
{:else}
|
|
203
228
|
<fieldset class="fieldset">
|
|
204
|
-
<label class="label text-wrap" for={inputId}>
|
|
229
|
+
<label id={labelId} class="label text-wrap" for={inputId}>
|
|
230
|
+
<span>{label}</span>
|
|
231
|
+
{@render labelAppend?.()}
|
|
232
|
+
</label>
|
|
205
233
|
{@render triggerCombobox()}
|
|
206
234
|
</fieldset>
|
|
207
235
|
{/if}
|
|
@@ -212,7 +240,7 @@
|
|
|
212
240
|
style="min-width: anchor-size(width);"
|
|
213
241
|
tabindex="-1"
|
|
214
242
|
>
|
|
215
|
-
{#if isSearchable || value.length || append}
|
|
243
|
+
{#if isSearchable || (canReset && value.length) || append}
|
|
216
244
|
<div class="sticky top-0 z-10 flex gap-2 bg-base-100/10 p-2 backdrop-blur-md">
|
|
217
245
|
{#if isSearchable}
|
|
218
246
|
<label class="input grow input-ghost input-sm">
|
|
@@ -228,7 +256,7 @@
|
|
|
228
256
|
{:else}
|
|
229
257
|
<div class="grow"></div>
|
|
230
258
|
{/if}
|
|
231
|
-
{#if value.length}
|
|
259
|
+
{#if canReset && value.length}
|
|
232
260
|
<button
|
|
233
261
|
class="btn btn-square btn-soft btn-sm"
|
|
234
262
|
type="button"
|
|
@@ -283,4 +311,14 @@
|
|
|
283
311
|
div[popover] {
|
|
284
312
|
border-color: color-mix(in oklab, var(--color-base-content) 20%, #0000);
|
|
285
313
|
}
|
|
314
|
+
|
|
315
|
+
/* Le déclencheur est un `div`: ni `:disabled` ni `[disabled]` ne s'y appliquent, et le
|
|
316
|
+
style désactivé de daisyUI passe à côté. Reproduit ici sur `aria-disabled`, le seul
|
|
317
|
+
marqueur valide sur un `role="combobox"`. */
|
|
318
|
+
div[aria-disabled='true'] {
|
|
319
|
+
cursor: not-allowed;
|
|
320
|
+
background-color: var(--color-base-200);
|
|
321
|
+
border-color: var(--color-base-200);
|
|
322
|
+
box-shadow: none;
|
|
323
|
+
}
|
|
286
324
|
</style>
|
|
@@ -29,10 +29,15 @@ declare function $$render<Item>(): {
|
|
|
29
29
|
*/
|
|
30
30
|
value?: NoInfer<Item>[];
|
|
31
31
|
placeholder?: string;
|
|
32
|
+
canReset?: boolean;
|
|
32
33
|
class?: ClassValue;
|
|
34
|
+
/** Les valeurs restent soumises, mais la sélection ne peut plus changer. */
|
|
35
|
+
disabled?: boolean;
|
|
33
36
|
hint?: Snippet<[Item[]]>;
|
|
34
37
|
/** Rendu à droite du champ de recherche: une action « créer », typiquement. */
|
|
35
38
|
append?: Snippet<[PopoverType]>;
|
|
39
|
+
/** Rendu à droite du libellé. Sans effet sur la variante `floating`. */
|
|
40
|
+
labelAppend?: Snippet;
|
|
36
41
|
onSelect?: (items: NoInfer<Item>[], popover: PopoverType) => void;
|
|
37
42
|
hotKey?: string;
|
|
38
43
|
variant?: "floating" | "block";
|
|
@@ -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,6 +10,7 @@
|
|
|
9
10
|
value = $bindable(),
|
|
10
11
|
class: klass,
|
|
11
12
|
variant = 'block',
|
|
13
|
+
labelAppend,
|
|
12
14
|
...props
|
|
13
15
|
}: {
|
|
14
16
|
label: string
|
|
@@ -19,6 +21,8 @@
|
|
|
19
21
|
*/
|
|
20
22
|
value?: number
|
|
21
23
|
variant?: 'floating' | 'block'
|
|
24
|
+
/** Rendu à droite du libellé. Sans effet sur la variante `floating`. */
|
|
25
|
+
labelAppend?: Snippet
|
|
22
26
|
} & InputProps = $props()
|
|
23
27
|
|
|
24
28
|
const inputId = $props.id()
|
|
@@ -50,7 +54,10 @@
|
|
|
50
54
|
</label>
|
|
51
55
|
{:else}
|
|
52
56
|
<fieldset class={['fieldset', klass]}>
|
|
53
|
-
<label class="label text-wrap" for={inputId}>
|
|
57
|
+
<label class="label text-wrap" for={inputId}>
|
|
58
|
+
<span>{label}</span>
|
|
59
|
+
{@render labelAppend?.()}
|
|
60
|
+
</label>
|
|
54
61
|
{@render snippetInput()}
|
|
55
62
|
<Issues {field} />
|
|
56
63
|
</fieldset>
|
|
@@ -1,5 +1,6 @@
|
|
|
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>;
|
|
@@ -9,6 +10,8 @@ type $$ComponentProps = {
|
|
|
9
10
|
*/
|
|
10
11
|
value?: number;
|
|
11
12
|
variant?: 'floating' | 'block';
|
|
13
|
+
/** Rendu à droite du libellé. Sans effet sur la variante `floating`. */
|
|
14
|
+
labelAppend?: Snippet;
|
|
12
15
|
} & InputProps;
|
|
13
16
|
declare const InputNumber: import("svelte").Component<$$ComponentProps, {}, "value">;
|
|
14
17
|
type InputNumber = ReturnType<typeof InputNumber>;
|
|
@@ -2,24 +2,31 @@
|
|
|
2
2
|
import type { RemoteFormField } from '@sveltejs/kit'
|
|
3
3
|
import { type Options, parseOptions } from '../utils/index.js'
|
|
4
4
|
import Issues from './Issues.svelte'
|
|
5
|
+
import type { Snippet } from 'svelte'
|
|
5
6
|
|
|
6
7
|
let {
|
|
7
8
|
field,
|
|
8
9
|
label,
|
|
9
10
|
value = $bindable(),
|
|
10
|
-
options: optionsProp
|
|
11
|
+
options: optionsProp,
|
|
12
|
+
labelAppend
|
|
11
13
|
}: {
|
|
12
14
|
field?: RemoteFormField<string>
|
|
13
15
|
label: string
|
|
14
16
|
value?: string
|
|
15
17
|
options: Options
|
|
18
|
+
/** Rendu à droite du libellé. */
|
|
19
|
+
labelAppend?: Snippet
|
|
16
20
|
} = $props()
|
|
17
21
|
|
|
18
22
|
const options = $derived(parseOptions(optionsProp))
|
|
19
23
|
</script>
|
|
20
24
|
|
|
21
25
|
<div class="flex flex-col gap-1">
|
|
22
|
-
<span class="label
|
|
26
|
+
<span class="label text-xs text-wrap">
|
|
27
|
+
<span>{label}</span>
|
|
28
|
+
{@render labelAppend?.()}
|
|
29
|
+
</span>
|
|
23
30
|
<div class="join join-vertical">
|
|
24
31
|
{#each options as option (option.value)}
|
|
25
32
|
{@const { class: klass, ...props } = option}
|
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
import type { RemoteFormField } from '@sveltejs/kit';
|
|
2
2
|
import { type Options } from '../utils/index.js';
|
|
3
|
+
import type { Snippet } from 'svelte';
|
|
3
4
|
type $$ComponentProps = {
|
|
4
5
|
field?: RemoteFormField<string>;
|
|
5
6
|
label: string;
|
|
6
7
|
value?: string;
|
|
7
8
|
options: Options;
|
|
9
|
+
/** Rendu à droite du libellé. */
|
|
10
|
+
labelAppend?: Snippet;
|
|
8
11
|
};
|
|
9
12
|
declare const InputRadio: import("svelte").Component<$$ComponentProps, {}, "value">;
|
|
10
13
|
type InputRadio = ReturnType<typeof InputRadio>;
|
|
@@ -2,12 +2,14 @@
|
|
|
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,
|
|
8
9
|
field,
|
|
9
10
|
value = $bindable(),
|
|
10
11
|
name,
|
|
12
|
+
labelAppend,
|
|
11
13
|
class: klass,
|
|
12
14
|
...props
|
|
13
15
|
}: {
|
|
@@ -20,12 +22,15 @@
|
|
|
20
22
|
value?: number
|
|
21
23
|
/** Sans `field`, ce qui nomme la valeur soumise. Avec, `field.as()` s'en charge. */
|
|
22
24
|
name?: string
|
|
25
|
+
/** Rendu à droite du libellé, avant la valeur courante. */
|
|
26
|
+
labelAppend?: Snippet
|
|
23
27
|
} & InputProps = $props()
|
|
24
28
|
</script>
|
|
25
29
|
|
|
26
30
|
<label>
|
|
27
31
|
<div class="flex gap-2 px-3 text-sm">
|
|
28
32
|
<span class="label text-wrap">{label}</span>
|
|
33
|
+
{@render labelAppend?.()}
|
|
29
34
|
<span class="label ml-auto">{field?.value() ?? value}</span>
|
|
30
35
|
</div>
|
|
31
36
|
{#if field}
|
|
@@ -1,5 +1,6 @@
|
|
|
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>;
|
|
@@ -10,6 +11,8 @@ type $$ComponentProps = {
|
|
|
10
11
|
value?: number;
|
|
11
12
|
/** Sans `field`, ce qui nomme la valeur soumise. Avec, `field.as()` s'en charge. */
|
|
12
13
|
name?: string;
|
|
14
|
+
/** Rendu à droite du libellé, avant la valeur courante. */
|
|
15
|
+
labelAppend?: Snippet;
|
|
13
16
|
} & InputProps;
|
|
14
17
|
declare const InputRange: import("svelte").Component<$$ComponentProps, {}, "value">;
|
|
15
18
|
type InputRange = ReturnType<typeof InputRange>;
|
|
@@ -30,8 +30,10 @@
|
|
|
30
30
|
placeholder = 'Selectionner une valeur',
|
|
31
31
|
class: klass,
|
|
32
32
|
nullable,
|
|
33
|
+
disabled = false,
|
|
33
34
|
hint,
|
|
34
35
|
append,
|
|
36
|
+
labelAppend,
|
|
35
37
|
header,
|
|
36
38
|
proposalAppend,
|
|
37
39
|
onSelect,
|
|
@@ -65,9 +67,13 @@
|
|
|
65
67
|
placeholder?: string
|
|
66
68
|
class?: ClassValue
|
|
67
69
|
nullable?: boolean
|
|
70
|
+
/** La valeur reste soumise, mais la sélection ne peut plus changer. */
|
|
71
|
+
disabled?: boolean
|
|
68
72
|
hint?: Snippet<[Item | undefined]>
|
|
69
73
|
/** Rendu à droite du champ de recherche: une action « créer », typiquement. */
|
|
70
74
|
append?: Snippet<[PopoverType]>
|
|
75
|
+
/** Rendu à droite du libellé. Sans effet sur la variante `floating`. */
|
|
76
|
+
labelAppend?: Snippet
|
|
71
77
|
header?: Snippet<[PopoverType]>
|
|
72
78
|
onSelect?: (item: NoInfer<Item> | undefined, popover: PopoverType) => void
|
|
73
79
|
hotKey?: string
|
|
@@ -99,7 +105,9 @@
|
|
|
99
105
|
listenFocus: false,
|
|
100
106
|
listenFocusout: true,
|
|
101
107
|
onShow: () => command.focusTrigger(),
|
|
102
|
-
|
|
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))()
|
|
103
111
|
})
|
|
104
112
|
export const command = useCommand({
|
|
105
113
|
isEnable: () => popover.isOpen,
|
|
@@ -129,7 +137,8 @@
|
|
|
129
137
|
<button
|
|
130
138
|
id={inputId}
|
|
131
139
|
type="button"
|
|
132
|
-
|
|
140
|
+
{disabled}
|
|
141
|
+
class={['input w-full', field?.issues()?.length && 'input-error', klass]}
|
|
133
142
|
{...popover.trigger}
|
|
134
143
|
{...isSearchable ? {} : command.trigger}
|
|
135
144
|
>
|
|
@@ -165,7 +174,10 @@
|
|
|
165
174
|
</label>
|
|
166
175
|
{:else}
|
|
167
176
|
<fieldset class="fieldset">
|
|
168
|
-
<label class="label text-wrap" for={inputId}>
|
|
177
|
+
<label class="label text-wrap" for={inputId}>
|
|
178
|
+
<span>{label}</span>
|
|
179
|
+
{@render labelAppend?.()}
|
|
180
|
+
</label>
|
|
169
181
|
{@render triggerButton()}
|
|
170
182
|
<Issues {field} />
|
|
171
183
|
{@render hint?.(value)}
|
|
@@ -37,9 +37,13 @@ declare function $$render<Item>(): {
|
|
|
37
37
|
placeholder?: string;
|
|
38
38
|
class?: ClassValue;
|
|
39
39
|
nullable?: boolean;
|
|
40
|
+
/** La valeur reste soumise, mais la sélection ne peut plus changer. */
|
|
41
|
+
disabled?: boolean;
|
|
40
42
|
hint?: Snippet<[Item | undefined]>;
|
|
41
43
|
/** Rendu à droite du champ de recherche: une action « créer », typiquement. */
|
|
42
44
|
append?: Snippet<[PopoverType]>;
|
|
45
|
+
/** Rendu à droite du libellé. Sans effet sur la variante `floating`. */
|
|
46
|
+
labelAppend?: Snippet;
|
|
43
47
|
header?: Snippet<[PopoverType]>;
|
|
44
48
|
onSelect?: (item: NoInfer<Item> | undefined, popover: PopoverType) => void;
|
|
45
49
|
hotKey?: string;
|
|
@@ -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,
|
|
@@ -12,6 +13,7 @@
|
|
|
12
13
|
class: klass,
|
|
13
14
|
multiple,
|
|
14
15
|
variant = 'block',
|
|
16
|
+
labelAppend,
|
|
15
17
|
...props
|
|
16
18
|
}: {
|
|
17
19
|
label: string
|
|
@@ -23,6 +25,8 @@
|
|
|
23
25
|
*/
|
|
24
26
|
value?: string | string[]
|
|
25
27
|
variant?: 'floating' | 'block'
|
|
28
|
+
/** Rendu à droite du libellé. Sans effet sur la variante `floating`. */
|
|
29
|
+
labelAppend?: Snippet
|
|
26
30
|
} & SelectProps = $props()
|
|
27
31
|
|
|
28
32
|
const options = $derived(parseOptions(optionsProp))
|
|
@@ -63,7 +67,10 @@
|
|
|
63
67
|
</label>
|
|
64
68
|
{:else}
|
|
65
69
|
<fieldset class={['fieldset', klass]}>
|
|
66
|
-
<label class="label text-wrap" for={inputId}>
|
|
70
|
+
<label class="label text-wrap" for={inputId}>
|
|
71
|
+
<span>{label}</span>
|
|
72
|
+
{@render labelAppend?.()}
|
|
73
|
+
</label>
|
|
67
74
|
{@render snippetSelect()}
|
|
68
75
|
<Issues {field} />
|
|
69
76
|
</fieldset>
|
|
@@ -1,6 +1,7 @@
|
|
|
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;
|
|
@@ -11,6 +12,8 @@ type $$ComponentProps = {
|
|
|
11
12
|
*/
|
|
12
13
|
value?: string | string[];
|
|
13
14
|
variant?: 'floating' | 'block';
|
|
15
|
+
/** Rendu à droite du libellé. Sans effet sur la variante `floating`. */
|
|
16
|
+
labelAppend?: Snippet;
|
|
14
17
|
} & SelectProps;
|
|
15
18
|
declare const InputSelectNative: import("svelte").Component<$$ComponentProps, {}, "value">;
|
|
16
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,
|
|
@@ -10,6 +11,7 @@
|
|
|
10
11
|
type = 'text',
|
|
11
12
|
class: klass,
|
|
12
13
|
variant = 'block',
|
|
14
|
+
labelAppend,
|
|
13
15
|
...props
|
|
14
16
|
}: {
|
|
15
17
|
label: string
|
|
@@ -20,6 +22,8 @@
|
|
|
20
22
|
*/
|
|
21
23
|
value?: string
|
|
22
24
|
variant?: 'floating' | 'block'
|
|
25
|
+
/** Rendu à droite du libellé. Sans effet sur la variante `floating`. */
|
|
26
|
+
labelAppend?: Snippet
|
|
23
27
|
type?:
|
|
24
28
|
| 'text'
|
|
25
29
|
| 'email'
|
|
@@ -65,7 +69,10 @@
|
|
|
65
69
|
</label>
|
|
66
70
|
{:else}
|
|
67
71
|
<fieldset class={['fieldset', klass]}>
|
|
68
|
-
<label class="label text-wrap" for={inputId}>
|
|
72
|
+
<label class="label text-wrap" for={inputId}>
|
|
73
|
+
<span>{label}</span>
|
|
74
|
+
{@render labelAppend?.()}
|
|
75
|
+
</label>
|
|
69
76
|
{@render snippetInput()}
|
|
70
77
|
<Issues {field} />
|
|
71
78
|
</fieldset>
|
|
@@ -1,5 +1,6 @@
|
|
|
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>;
|
|
@@ -9,6 +10,8 @@ type $$ComponentProps = {
|
|
|
9
10
|
*/
|
|
10
11
|
value?: string;
|
|
11
12
|
variant?: 'floating' | 'block';
|
|
13
|
+
/** Rendu à droite du libellé. Sans effet sur la variante `floating`. */
|
|
14
|
+
labelAppend?: Snippet;
|
|
12
15
|
type?: 'text' | 'email' | 'password' | 'url' | 'tel' | 'search' | 'date' | 'datetime-local' | 'time' | 'month' | 'week' | 'color' | 'select';
|
|
13
16
|
} & InputProps;
|
|
14
17
|
declare const InputString: import("svelte").Component<$$ComponentProps, {}, "value">;
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
import { TextareaAutosize } from 'runed'
|
|
4
4
|
import Issues from './Issues.svelte'
|
|
5
5
|
import type { TextareaProps } from './type.js'
|
|
6
|
+
import type { Snippet } from 'svelte'
|
|
6
7
|
|
|
7
8
|
let {
|
|
8
9
|
label,
|
|
@@ -11,6 +12,7 @@
|
|
|
11
12
|
class: klass,
|
|
12
13
|
maxHeight = 200,
|
|
13
14
|
variant = 'block',
|
|
15
|
+
labelAppend,
|
|
14
16
|
...props
|
|
15
17
|
}: {
|
|
16
18
|
label: string
|
|
@@ -21,7 +23,15 @@
|
|
|
21
23
|
*/
|
|
22
24
|
value?: string
|
|
23
25
|
maxHeight?: number
|
|
24
|
-
|
|
26
|
+
/**
|
|
27
|
+
* `bare` ne rend que le champ, sans enveloppe ni libellé visible: `label` devient le
|
|
28
|
+
* placeholder et le nom accessible, et `class` s'applique au `<textarea>`. C'est la
|
|
29
|
+
* variante d'un champ posé dans une barre de saisie, où l'appelant place lui-même ce qui
|
|
30
|
+
* l'accompagne.
|
|
31
|
+
*/
|
|
32
|
+
variant?: 'floating' | 'block' | 'bare'
|
|
33
|
+
/** Rendu à droite du libellé. Sans effet sur les variantes `floating` et `bare`. */
|
|
34
|
+
labelAppend?: Snippet
|
|
25
35
|
} & TextareaProps = $props()
|
|
26
36
|
|
|
27
37
|
let textarea = $state<HTMLTextAreaElement>()
|
|
@@ -34,10 +44,11 @@
|
|
|
34
44
|
const inputId = $props.id()
|
|
35
45
|
const textareaProps = $derived({
|
|
36
46
|
id: inputId,
|
|
37
|
-
|
|
38
|
-
|
|
47
|
+
placeholder: variant === 'block' ? '' : label,
|
|
48
|
+
'aria-label': variant === 'bare' ? label : undefined,
|
|
39
49
|
...props
|
|
40
50
|
})
|
|
51
|
+
const textareaClass = $derived(['textarea w-full', variant === 'bare' && klass])
|
|
41
52
|
</script>
|
|
42
53
|
|
|
43
54
|
{#snippet snippetInput()}
|
|
@@ -47,10 +58,11 @@
|
|
|
47
58
|
<textarea
|
|
48
59
|
bind:this={textarea}
|
|
49
60
|
{...textareaProps}
|
|
61
|
+
class={textareaClass}
|
|
50
62
|
{...value === undefined ? field.as('text') : field.as('text', value)}
|
|
51
63
|
></textarea>
|
|
52
64
|
{:else}
|
|
53
|
-
<textarea bind:this={textarea} {...textareaProps} bind:value></textarea>
|
|
65
|
+
<textarea bind:this={textarea} {...textareaProps} class={textareaClass} bind:value></textarea>
|
|
54
66
|
{/if}
|
|
55
67
|
{/snippet}
|
|
56
68
|
|
|
@@ -60,9 +72,15 @@
|
|
|
60
72
|
{@render snippetInput()}
|
|
61
73
|
<Issues {field} />
|
|
62
74
|
</label>
|
|
75
|
+
{:else if variant === 'bare'}
|
|
76
|
+
{@render snippetInput()}
|
|
77
|
+
<Issues {field} />
|
|
63
78
|
{:else}
|
|
64
79
|
<fieldset class={['fieldset', klass]}>
|
|
65
|
-
<label class="label text-wrap" for={inputId}>
|
|
80
|
+
<label class="label text-wrap" for={inputId}>
|
|
81
|
+
<span>{label}</span>
|
|
82
|
+
{@render labelAppend?.()}
|
|
83
|
+
</label>
|
|
66
84
|
{@render snippetInput()}
|
|
67
85
|
<Issues {field} />
|
|
68
86
|
</fieldset>
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { RemoteFormField } from '@sveltejs/kit';
|
|
2
2
|
import type { TextareaProps } from './type.js';
|
|
3
|
+
import type { Snippet } from 'svelte';
|
|
3
4
|
type $$ComponentProps = {
|
|
4
5
|
label: string;
|
|
5
6
|
field?: RemoteFormField<string>;
|
|
@@ -9,7 +10,15 @@ type $$ComponentProps = {
|
|
|
9
10
|
*/
|
|
10
11
|
value?: string;
|
|
11
12
|
maxHeight?: number;
|
|
12
|
-
|
|
13
|
+
/**
|
|
14
|
+
* `bare` ne rend que le champ, sans enveloppe ni libellé visible: `label` devient le
|
|
15
|
+
* placeholder et le nom accessible, et `class` s'applique au `<textarea>`. C'est la
|
|
16
|
+
* variante d'un champ posé dans une barre de saisie, où l'appelant place lui-même ce qui
|
|
17
|
+
* l'accompagne.
|
|
18
|
+
*/
|
|
19
|
+
variant?: 'floating' | 'block' | 'bare';
|
|
20
|
+
/** Rendu à droite du libellé. Sans effet sur les variantes `floating` et `bare`. */
|
|
21
|
+
labelAppend?: Snippet;
|
|
13
22
|
} & TextareaProps;
|
|
14
23
|
declare const InputTextarea: import("svelte").Component<$$ComponentProps, {}, "value">;
|
|
15
24
|
type InputTextarea = ReturnType<typeof InputTextarea>;
|
|
@@ -17,7 +17,8 @@
|
|
|
17
17
|
btn?: boolean
|
|
18
18
|
/** Custom button content */
|
|
19
19
|
children?: Snippet<[{ waitConfirmation: boolean }]>
|
|
20
|
-
|
|
20
|
+
// `children` de `HTMLButtonAttributes` est un `Snippet<[]>`: l'intersection interdirait le paramètre.
|
|
21
|
+
} & Omit<HTMLButtonAttributes, 'children'> = $props()
|
|
21
22
|
|
|
22
23
|
let button: HTMLButtonElement
|
|
23
24
|
let waitConfirmation = $state(false)
|
|
@@ -49,9 +50,9 @@
|
|
|
49
50
|
btn && [
|
|
50
51
|
'btn',
|
|
51
52
|
waitConfirmation ? 'btn-outline btn-error' : 'btn-ghost text-error',
|
|
52
|
-
props.disabled && 'btn-disabled'
|
|
53
|
-
|
|
54
|
-
|
|
53
|
+
props.disabled && 'btn-disabled'
|
|
54
|
+
],
|
|
55
|
+
klass
|
|
55
56
|
]}
|
|
56
57
|
formaction={waitConfirmation ? formaction : undefined}
|
|
57
58
|
{onclick}
|
|
@@ -9,7 +9,7 @@ type $$ComponentProps = {
|
|
|
9
9
|
children?: Snippet<[{
|
|
10
10
|
waitConfirmation: boolean;
|
|
11
11
|
}]>;
|
|
12
|
-
} & HTMLButtonAttributes
|
|
12
|
+
} & Omit<HTMLButtonAttributes, 'children'>;
|
|
13
13
|
declare const ButtonDelete: import("svelte").Component<$$ComponentProps, {}, "">;
|
|
14
14
|
type ButtonDelete = ReturnType<typeof ButtonDelete>;
|
|
15
15
|
export default ButtonDelete;
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
<script lang="ts" generics="Item extends ItemBase">
|
|
2
2
|
import { type Snippet, untrack } from 'svelte'
|
|
3
|
-
import { afterNavigate } from '$app/navigation'
|
|
4
3
|
import {
|
|
5
4
|
context,
|
|
6
5
|
createKeys,
|
|
@@ -41,16 +40,20 @@
|
|
|
41
40
|
const tableKey = untrack(() => key)
|
|
42
41
|
context.set(tableKey, createKeys(tableKey))
|
|
43
42
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
43
|
+
// Dérivé, et non écrit dans la prop `fields`: Svelte annule l'écriture locale d'une prop
|
|
44
|
+
// non liée dès que le parent en recalcule la valeur. Un parent qui déclare ses colonnes
|
|
45
|
+
// dans un `$derived` les reproduit à chaque rafraîchissement de `load` — celui que
|
|
46
|
+
// déclenche une remote function, sans navigation — et la table repartait alors sans
|
|
47
|
+
// `_visible`, donc sans une seule colonne. `syncFieldsWithParams` lit `page.url`: le
|
|
48
|
+
// dérivé suit aussi les changements de paramètres, ce dont `afterNavigate` se chargeait.
|
|
49
|
+
const fieldsSynced = $derived(syncFieldsWithParams(tableKey, fields))
|
|
47
50
|
</script>
|
|
48
51
|
|
|
49
52
|
<div class="{klass} overflow-x-auto rounded-box border bg-base-100" class:min-h-[320px]={!hideBody}>
|
|
50
53
|
<table class="table relative">
|
|
51
|
-
<TableHead {
|
|
54
|
+
<TableHead fields={fieldsSynced} {key} {onCreateField} />
|
|
52
55
|
{#if !hideBody && items.length}
|
|
53
|
-
<TableBody {
|
|
56
|
+
<TableBody fields={fieldsSynced} {items} {actions} {classRow} {onclick} />
|
|
54
57
|
{/if}
|
|
55
58
|
</table>
|
|
56
59
|
|
|
@@ -80,7 +80,7 @@
|
|
|
80
80
|
|
|
81
81
|
{#snippet children(popover)}
|
|
82
82
|
{#if onCreateField}
|
|
83
|
-
<div class="
|
|
83
|
+
<div class="flex items-center gap-2 border-b pr-1 pb-1 pl-2">
|
|
84
84
|
<span class=" font-semibold opacity-70">Champs</span>
|
|
85
85
|
<button
|
|
86
86
|
type="button"
|
|
@@ -7,11 +7,15 @@
|
|
|
7
7
|
order = $bindable(),
|
|
8
8
|
IconAsc = ArrowDownNarrowWideIcon,
|
|
9
9
|
IconDesc = ArrowDownWideNarrowIcon,
|
|
10
|
+
labelAsc = 'Ascendant',
|
|
11
|
+
labelDesc = 'Descendant',
|
|
10
12
|
onChange
|
|
11
13
|
}: {
|
|
12
14
|
order: Order
|
|
13
15
|
IconAsc?: Component<IconProps>
|
|
14
16
|
IconDesc?: Component<IconProps>
|
|
17
|
+
labelAsc?: string
|
|
18
|
+
labelDesc?: string
|
|
15
19
|
onChange?: (order: Order) => void
|
|
16
20
|
} = $props()
|
|
17
21
|
|
|
@@ -22,24 +26,21 @@
|
|
|
22
26
|
}
|
|
23
27
|
</script>
|
|
24
28
|
|
|
25
|
-
<div class="
|
|
26
|
-
<
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
>
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
>
|
|
41
|
-
|
|
42
|
-
<span>Descendant</span>
|
|
43
|
-
</button>
|
|
44
|
-
</div>
|
|
29
|
+
<div class="m-2 grid grid-cols-2 gap-1">
|
|
30
|
+
<button
|
|
31
|
+
class="btn ring-primary btn-sm"
|
|
32
|
+
class:ring-2={order === 'asc'}
|
|
33
|
+
onclick={handleOrderClick('asc')}
|
|
34
|
+
>
|
|
35
|
+
<IconAsc size={16} />
|
|
36
|
+
<span>{labelAsc}</span>
|
|
37
|
+
</button>
|
|
38
|
+
<button
|
|
39
|
+
class="btn ring-primary btn-sm"
|
|
40
|
+
class:ring-2={order === 'desc'}
|
|
41
|
+
onclick={handleOrderClick('desc')}
|
|
42
|
+
>
|
|
43
|
+
<IconDesc size={16} />
|
|
44
|
+
<span>{labelDesc}</span>
|
|
45
|
+
</button>
|
|
45
46
|
</div>
|
|
@@ -5,6 +5,8 @@ type $$ComponentProps = {
|
|
|
5
5
|
order: Order;
|
|
6
6
|
IconAsc?: Component<IconProps>;
|
|
7
7
|
IconDesc?: Component<IconProps>;
|
|
8
|
+
labelAsc?: string;
|
|
9
|
+
labelDesc?: string;
|
|
8
10
|
onChange?: (order: Order) => void;
|
|
9
11
|
};
|
|
10
12
|
declare const OrderButtons: Component<$$ComponentProps, {}, "order">;
|
|
@@ -90,9 +90,9 @@
|
|
|
90
90
|
</span>
|
|
91
91
|
{/if}
|
|
92
92
|
{#if order === 'asc'}
|
|
93
|
-
<CalendarArrowDownIcon size={
|
|
93
|
+
<CalendarArrowDownIcon size={15} class="text-primary" />
|
|
94
94
|
{:else if order === 'desc'}
|
|
95
|
-
<CalendarArrowUpIcon size={
|
|
95
|
+
<CalendarArrowUpIcon size={15} class="text-primary" />
|
|
96
96
|
{/if}
|
|
97
97
|
</button>
|
|
98
98
|
{/snippet}
|
|
@@ -107,6 +107,8 @@
|
|
|
107
107
|
}}
|
|
108
108
|
IconAsc={CalendarArrowDownIcon}
|
|
109
109
|
IconDesc={CalendarArrowUpIcon}
|
|
110
|
+
labelAsc="Ancien d'abord"
|
|
111
|
+
labelDesc="Récent d'abord"
|
|
110
112
|
/>
|
|
111
113
|
{/if}
|
|
112
114
|
|
|
@@ -63,7 +63,7 @@
|
|
|
63
63
|
<Popover class="p-1">
|
|
64
64
|
{#snippet trigger(popover)}
|
|
65
65
|
<button class="menu-item min-h-8 w-full flex-wrap gap-y-1" {...popover.trigger}>
|
|
66
|
-
<div class="flex gap-2">
|
|
66
|
+
<div class="flex items-center gap-2">
|
|
67
67
|
<span>{field.label}</span>
|
|
68
68
|
{#if !isSet(min) && !isSet(max)}
|
|
69
69
|
<FunnelIcon size={15} class="opacity-50" />
|
|
@@ -82,9 +82,9 @@
|
|
|
82
82
|
</span>
|
|
83
83
|
{/if}
|
|
84
84
|
{#if order === 'asc'}
|
|
85
|
-
<ArrowDownNarrowWideIcon size={18} class="
|
|
85
|
+
<ArrowDownNarrowWideIcon size={18} class="text-primary" />
|
|
86
86
|
{:else if order === 'desc'}
|
|
87
|
-
<ArrowDownWideNarrowIcon size={18} class="
|
|
87
|
+
<ArrowDownWideNarrowIcon size={18} class="text-primary" />
|
|
88
88
|
{/if}
|
|
89
89
|
</button>
|
|
90
90
|
{/snippet}
|
|
@@ -64,7 +64,7 @@
|
|
|
64
64
|
{#each optionsActive as option (option.value)}
|
|
65
65
|
<span class="badge badge-xs text-[0.7rem] font-normal text-white badge-primary">
|
|
66
66
|
{#if option.icon}
|
|
67
|
-
<option.icon size={10} class="-translate-x-1
|
|
67
|
+
<option.icon size={10} class="-translate-x-1 text-white/80" />
|
|
68
68
|
{/if}
|
|
69
69
|
{option.label}
|
|
70
70
|
</span>
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
|
|
23
23
|
{#if searchValue}
|
|
24
24
|
<span class="badge badge-xs text-[0.7rem] font-normal text-white badge-primary">
|
|
25
|
-
<SearchIcon size={10} class="-translate-x-1
|
|
25
|
+
<SearchIcon size={10} class="-translate-x-1 text-white/80" />
|
|
26
26
|
<span>{searchValue}</span>
|
|
27
27
|
</span>
|
|
28
28
|
{/if}
|
package/dist/ui/table/index.d.ts
CHANGED
|
@@ -8,4 +8,3 @@ export { default as TableBody } from './TableBody.svelte';
|
|
|
8
8
|
export { default as TableCell } from './TableCell.svelte';
|
|
9
9
|
export { default as TableFieldsEdition } from './TableFieldsEdition.svelte';
|
|
10
10
|
export { default as TableHead } from './TableHead.svelte';
|
|
11
|
-
export { default as TableViewSelect } from './TableViewSelect.svelte';
|
package/dist/ui/table/index.js
CHANGED
|
@@ -7,4 +7,3 @@ export { default as TableBody } from './TableBody.svelte';
|
|
|
7
7
|
export { default as TableCell } from './TableCell.svelte';
|
|
8
8
|
export { default as TableFieldsEdition } from './TableFieldsEdition.svelte';
|
|
9
9
|
export { default as TableHead } from './TableHead.svelte';
|
|
10
|
-
export { default as TableViewSelect } from './TableViewSelect.svelte';
|
package/dist/validation/zod.js
CHANGED
|
@@ -15,8 +15,8 @@ export const zodCoerceJson = z.string().transform((str, ctx) => {
|
|
|
15
15
|
try {
|
|
16
16
|
return JSON.parse(str);
|
|
17
17
|
}
|
|
18
|
-
catch (
|
|
19
|
-
|
|
18
|
+
catch (err) {
|
|
19
|
+
void err;
|
|
20
20
|
ctx.addIssue({ code: 'custom', message: 'Invalid JSON' });
|
|
21
21
|
return z.NEVER;
|
|
22
22
|
}
|
package/package.json
CHANGED
|
@@ -1,147 +0,0 @@
|
|
|
1
|
-
<script lang="ts">
|
|
2
|
-
import { ChevronDownIcon, PlusIcon, SaveIcon } from '@lucide/svelte'
|
|
3
|
-
import { page } from '$app/state'
|
|
4
|
-
|
|
5
|
-
import { Popover } from '../../popover/index.js'
|
|
6
|
-
import { Dialog } from '../dialog/index.js'
|
|
7
|
-
import { SvelteURLSearchParams } from 'svelte/reactivity'
|
|
8
|
-
|
|
9
|
-
// import { InputText } from '../input/index.js';
|
|
10
|
-
// import { useForm } from '../../validation/form.jss';
|
|
11
|
-
|
|
12
|
-
type View = {
|
|
13
|
-
id: string
|
|
14
|
-
name: string
|
|
15
|
-
query: string
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
let {
|
|
19
|
-
views
|
|
20
|
-
}: {
|
|
21
|
-
views: View[]
|
|
22
|
-
} = $props()
|
|
23
|
-
|
|
24
|
-
let dialog = $state<HTMLDialogElement>()
|
|
25
|
-
// const form = useForm({
|
|
26
|
-
// onSuccess() {
|
|
27
|
-
// dialog?.close();
|
|
28
|
-
// }
|
|
29
|
-
// });
|
|
30
|
-
|
|
31
|
-
let query = $derived.by(() => {
|
|
32
|
-
const searchParam = new SvelteURLSearchParams(page.url.searchParams)
|
|
33
|
-
searchParam.delete('skip')
|
|
34
|
-
searchParam.delete('take')
|
|
35
|
-
return searchParam.toString()
|
|
36
|
-
})
|
|
37
|
-
|
|
38
|
-
let selectedView = $derived(views.find((v) => v.query === query))
|
|
39
|
-
let isNewView = $derived(!!query && !selectedView)
|
|
40
|
-
</script>
|
|
41
|
-
|
|
42
|
-
/* eslint-disable @typescript-eslint/no-unused-vars */ /* eslint-disable
|
|
43
|
-
@typescript-eslint/no-unused-vars */
|
|
44
|
-
<Popover class="p-1">
|
|
45
|
-
{#snippet trigger(popover)}
|
|
46
|
-
<button
|
|
47
|
-
type="button"
|
|
48
|
-
class="menu-item bordered gap-1 rounded-lg border font-semibold opacity-90 btn-sm"
|
|
49
|
-
{...popover.trigger}
|
|
50
|
-
>
|
|
51
|
-
<span>{isNewView ? 'Nouvelle vue' : selectedView?.name || 'Vue simple'}</span>
|
|
52
|
-
<ChevronDownIcon size={20} class="translate-x-1 translate-y-px opacity-90" />
|
|
53
|
-
</button>
|
|
54
|
-
{/snippet}
|
|
55
|
-
|
|
56
|
-
{#snippet children(popover)}
|
|
57
|
-
<ul class="flex flex-col gap-1">
|
|
58
|
-
{#if isNewView}
|
|
59
|
-
<li>
|
|
60
|
-
<button
|
|
61
|
-
type="button"
|
|
62
|
-
class="menu-item w-full pr-1.5"
|
|
63
|
-
onclick={() => {
|
|
64
|
-
selectedView = undefined
|
|
65
|
-
dialog?.showModal()
|
|
66
|
-
popover.hide()
|
|
67
|
-
}}
|
|
68
|
-
>
|
|
69
|
-
<span>Ajouter la nouvelle vue</span>
|
|
70
|
-
<PlusIcon class="ml-auto opacity-80" size={21} />
|
|
71
|
-
</button>
|
|
72
|
-
<hr class="my-1" />
|
|
73
|
-
</li>
|
|
74
|
-
{/if}
|
|
75
|
-
|
|
76
|
-
<li>
|
|
77
|
-
<a href={page.url.pathname} class="menu-item" class:active={!query} onclick={popover.hide}>
|
|
78
|
-
<span class="grow">Vue simple</span>
|
|
79
|
-
</a>
|
|
80
|
-
</li>
|
|
81
|
-
|
|
82
|
-
{#each views as view (view.id)}
|
|
83
|
-
<li>
|
|
84
|
-
<a
|
|
85
|
-
href="{page.url.pathname}?{view.query}"
|
|
86
|
-
class="menu-item group pr-1"
|
|
87
|
-
class:active={view.id === selectedView?.id}
|
|
88
|
-
onclick={popover.hide}
|
|
89
|
-
>
|
|
90
|
-
<span class="grow">{view.name}</span>
|
|
91
|
-
<button
|
|
92
|
-
type="button"
|
|
93
|
-
class="btn btn-square rounded btn-ghost btn-xs"
|
|
94
|
-
onclick={(e) => {
|
|
95
|
-
e.preventDefault()
|
|
96
|
-
selectedView = view
|
|
97
|
-
dialog?.showModal()
|
|
98
|
-
}}
|
|
99
|
-
>
|
|
100
|
-
<SaveIcon
|
|
101
|
-
class="opacity-50 group-hover:opacity-80"
|
|
102
|
-
size={18}
|
|
103
|
-
title="Modifier la vue '{view.name}'"
|
|
104
|
-
/>
|
|
105
|
-
</button>
|
|
106
|
-
</a>
|
|
107
|
-
</li>
|
|
108
|
-
{/each}
|
|
109
|
-
</ul>
|
|
110
|
-
{/snippet}
|
|
111
|
-
</Popover>
|
|
112
|
-
|
|
113
|
-
<Dialog bind:dialog>
|
|
114
|
-
{#snippet header()}
|
|
115
|
-
<h2 class="title">
|
|
116
|
-
{#if selectedView}
|
|
117
|
-
Modifier la vue
|
|
118
|
-
{:else}
|
|
119
|
-
Ajouter la nouvelle vue
|
|
120
|
-
{/if}
|
|
121
|
-
</h2>
|
|
122
|
-
{/snippet}
|
|
123
|
-
|
|
124
|
-
<span>TODO: how to build basic form (create update) with new system ?</span>
|
|
125
|
-
<!--
|
|
126
|
-
<form
|
|
127
|
-
action="{action}{selectedView ? actionUpdate : actionCreate}"
|
|
128
|
-
method="post"
|
|
129
|
-
use:enhance={form.submit}
|
|
130
|
-
>
|
|
131
|
-
{#if selectedView}
|
|
132
|
-
<input type="hidden" name="id" value={selectedView.id} />
|
|
133
|
-
{/if}
|
|
134
|
-
<input type="hidden" name="key" value={key} />
|
|
135
|
-
<input type="hidden" name="query" value={query} />
|
|
136
|
-
|
|
137
|
-
<Input key="name" input={{ placeholder: 'Nom de la vue' }} value={selectedView?.name || ''} />
|
|
138
|
-
|
|
139
|
-
<div class="mt-2 flex flex-row-reverse gap-2">
|
|
140
|
-
<button class="btn"> Valider </button>
|
|
141
|
-
|
|
142
|
-
<button formaction="{action}{actionDelete}" class="btn text-error btn-ghost">
|
|
143
|
-
Supprimer
|
|
144
|
-
</button>
|
|
145
|
-
</div>
|
|
146
|
-
</form> -->
|
|
147
|
-
</Dialog>
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
type View = {
|
|
2
|
-
id: string;
|
|
3
|
-
name: string;
|
|
4
|
-
query: string;
|
|
5
|
-
};
|
|
6
|
-
type $$ComponentProps = {
|
|
7
|
-
views: View[];
|
|
8
|
-
};
|
|
9
|
-
declare const TableViewSelect: import("svelte").Component<$$ComponentProps, {}, "">;
|
|
10
|
-
type TableViewSelect = ReturnType<typeof TableViewSelect>;
|
|
11
|
-
export default TableViewSelect;
|