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.
Files changed (38) hide show
  1. package/dist/input/InputBoolean.svelte +19 -4
  2. package/dist/input/InputBoolean.svelte.d.ts +3 -0
  3. package/dist/input/InputCheckboxes.svelte +8 -1
  4. package/dist/input/InputCheckboxes.svelte.d.ts +3 -0
  5. package/dist/input/InputDateTime.svelte +5 -0
  6. package/dist/input/InputDateTime.svelte.d.ts +3 -0
  7. package/dist/input/InputMultiSelect.svelte +60 -22
  8. package/dist/input/InputMultiSelect.svelte.d.ts +5 -0
  9. package/dist/input/InputNumber.svelte +8 -1
  10. package/dist/input/InputNumber.svelte.d.ts +3 -0
  11. package/dist/input/InputRadio.svelte +9 -2
  12. package/dist/input/InputRadio.svelte.d.ts +3 -0
  13. package/dist/input/InputRange.svelte +5 -0
  14. package/dist/input/InputRange.svelte.d.ts +3 -0
  15. package/dist/input/InputSelect.svelte +15 -3
  16. package/dist/input/InputSelect.svelte.d.ts +4 -0
  17. package/dist/input/InputSelectNative.svelte +8 -1
  18. package/dist/input/InputSelectNative.svelte.d.ts +3 -0
  19. package/dist/input/InputString.svelte +8 -1
  20. package/dist/input/InputString.svelte.d.ts +3 -0
  21. package/dist/input/InputTextarea.svelte +23 -5
  22. package/dist/input/InputTextarea.svelte.d.ts +10 -1
  23. package/dist/ui/button/ButtonDelete.svelte +5 -4
  24. package/dist/ui/button/ButtonDelete.svelte.d.ts +1 -1
  25. package/dist/ui/table/Table.svelte +9 -6
  26. package/dist/ui/table/TableFieldsEdition.svelte +1 -1
  27. package/dist/ui/table/head/OrderButtons.svelte +21 -20
  28. package/dist/ui/table/head/OrderButtons.svelte.d.ts +2 -0
  29. package/dist/ui/table/head/TableHeadDate.svelte +4 -2
  30. package/dist/ui/table/head/TableHeadNumber.svelte +3 -3
  31. package/dist/ui/table/head/TableHeadSelect.svelte +1 -1
  32. package/dist/ui/table/head/TableHeadString.svelte +1 -1
  33. package/dist/ui/table/index.d.ts +0 -1
  34. package/dist/ui/table/index.js +0 -1
  35. package/dist/validation/zod.js +2 -2
  36. package/package.json +1 -1
  37. package/dist/ui/table/TableViewSelect.svelte +0 -147
  38. 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 px-3 text-sm text-wrap">{label}</span>
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
- hotKey: (() => hotKey)()
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
- tabindex="0"
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
- 'not-disabled:cursor-pointer',
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
- <button
166
- type="button"
167
- class="btn btn-circle btn-ghost btn-xs"
168
- aria-label="Retirer"
169
- use:tip={{ content: 'Retirer' }}
170
- onclick={(event) => {
171
- // Sans quoi le clic remonte au combobox et rouvre le popover.
172
- event.stopPropagation()
173
- remove(item)
174
- }}
175
- >
176
- <XIcon size={12} />
177
- </button>
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}>{label}</label>
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}>{label}</label>
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 px-3 text-sm text-wrap">{label}</span>
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
- hotKey: (() => hotKey)()
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
- class={['input', field?.issues()?.length && 'input-error', klass]}
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}>{label}</label>
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}>{label}</label>
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}>{label}</label>
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
- variant?: 'floating' | 'block'
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
- class: 'textarea w-full',
38
- placeholder: variant === 'floating' ? label : '',
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}>{label}</label>
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
- variant?: 'floating' | 'block';
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
- } & HTMLButtonAttributes = $props()
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
- klass
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
- const initFields = () => (fields = syncFieldsWithParams(key, fields))
45
- initFields()
46
- afterNavigate(initFields)
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 {fields} {key} {onCreateField} />
54
+ <TableHead fields={fieldsSynced} {key} {onCreateField} />
52
55
  {#if !hideBody && items.length}
53
- <TableBody {fields} {items} {actions} {classRow} {onclick} />
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="bordered flex items-center gap-2 border-b pr-1 pb-1 pl-2">
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="p-1 pt-2">
26
- <span class="text-sm font-semibold opacity-70">Ordre:</span>
27
- <div class="grid grid-cols-2 gap-2 pt-2">
28
- <button
29
- class="btn ring-primary"
30
- class:ring-2={order === 'asc'}
31
- onclick={handleOrderClick('asc')}
32
- >
33
- <IconAsc />
34
- <span>Ascendant</span>
35
- </button>
36
- <button
37
- class="btn ring-primary"
38
- class:ring-2={order === 'desc'}
39
- onclick={handleOrderClick('desc')}
40
- >
41
- <IconDesc />
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={18} class="fill-primary" />
93
+ <CalendarArrowDownIcon size={15} class="text-primary" />
94
94
  {:else if order === 'desc'}
95
- <CalendarArrowUpIcon size={18} class="fill-primary" />
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="fill-primary" />
85
+ <ArrowDownNarrowWideIcon size={18} class="text-primary" />
86
86
  {:else if order === 'desc'}
87
- <ArrowDownWideNarrowIcon size={18} class="fill-primary" />
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 fill-white/80" />
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 fill-white/80" />
25
+ <SearchIcon size={10} class="-translate-x-1 text-white/80" />
26
26
  <span>{searchValue}</span>
27
27
  </span>
28
28
  {/if}
@@ -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';
@@ -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';
@@ -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 (e) {
19
- console.error(e);
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,6 +1,6 @@
1
1
  {
2
2
  "name": "fuma",
3
- "version": "2.2.7",
3
+ "version": "2.5.1",
4
4
  "description": "My fullstack material build with sveltekit, daisyui, zod, prisma, lucia",
5
5
  "author": {
6
6
  "name": "Jonas Voisard",
@@ -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;