kelt-ui-kit-react 2.1.6 → 2.1.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +27 -0
- package/dist/addressSearch/AddressSearch.d.ts +6 -2
- package/dist/dataTable/dataTable.interface.d.ts +2 -0
- package/dist/form/form.interface.d.ts +4 -1
- package/dist/geocoding/CityGeocodingService.d.ts +23 -0
- package/dist/geocoding/geocoding.interface.d.ts +2 -0
- package/dist/index.d.ts +2 -1
- package/dist/index.js +1273 -1188
- package/dist/style.css +1 -1
- package/package.json +1 -1
- package/src/addressSearch/AddressSearch.tsx +27 -7
- package/src/dataTable/dataTable.interface.ts +2 -0
- package/src/dataTable/dataTableActions/DataTableAction.tsx +11 -9
- package/src/form/Form.tsx +4 -0
- package/src/form/form.interface.tsx +4 -0
- package/src/geocoding/CityGeocodingService.ts +144 -0
- package/src/geocoding/GeocodingService.ts +1 -0
- package/src/geocoding/geocoding.interface.ts +8 -0
- package/src/index.ts +5 -0
- package/src/sidebar/Sidebar.tsx +32 -0
- package/src/sidebar/sidebar.css +8 -0
- package/tests/dataTable.test.mjs +29 -0
- package/tsconfig.tsbuildinfo +1 -1
- package/vite.config.ts +7 -0
package/package.json
CHANGED
|
@@ -9,6 +9,7 @@ import { Icon } from "../icon/Icon";
|
|
|
9
9
|
import {
|
|
10
10
|
GeocodedAddress,
|
|
11
11
|
GeocodingProvider,
|
|
12
|
+
GeocodingSearchType,
|
|
12
13
|
} from "../geocoding/geocoding.interface";
|
|
13
14
|
import { geocodingService } from "../geocoding/GeocodingService";
|
|
14
15
|
import "./addressSearch.css";
|
|
@@ -24,6 +25,11 @@ export interface AddressSearchProps {
|
|
|
24
25
|
debounceTime?: number;
|
|
25
26
|
limit?: number;
|
|
26
27
|
provider?: GeocodingProvider;
|
|
28
|
+
searchType?: GeocodingSearchType;
|
|
29
|
+
// Valeur écrite dans le champ à la sélection (par défaut la ligne d'adresse)
|
|
30
|
+
formatValue?: (address: GeocodedAddress) => string;
|
|
31
|
+
emptyLabel?: string;
|
|
32
|
+
required?: boolean;
|
|
27
33
|
allowManualEntry?: boolean;
|
|
28
34
|
manualEntryLabel?: string;
|
|
29
35
|
onChange?: (value: string) => void;
|
|
@@ -42,6 +48,10 @@ export const AddressSearch = ({
|
|
|
42
48
|
debounceTime = 300,
|
|
43
49
|
limit = 5,
|
|
44
50
|
provider = geocodingService,
|
|
51
|
+
searchType,
|
|
52
|
+
formatValue = (address) => address.addressLine,
|
|
53
|
+
emptyLabel = "Aucune adresse trouvée.",
|
|
54
|
+
required = false,
|
|
45
55
|
allowManualEntry = true,
|
|
46
56
|
manualEntryLabel = "Je ne trouve pas mon adresse",
|
|
47
57
|
onChange,
|
|
@@ -59,10 +69,17 @@ export const AddressSearch = ({
|
|
|
59
69
|
const [activeIndex, setActiveIndex] = useState(-1);
|
|
60
70
|
const containerRef = useRef<HTMLDivElement>(null);
|
|
61
71
|
const requestRef = useRef<AbortController>();
|
|
62
|
-
|
|
72
|
+
// Pas de recherche sur la valeur initiale : la liste s'ouvrirait au chargement
|
|
73
|
+
const skipNextSearchRef = useRef(!!value?.trim());
|
|
74
|
+
const queryRef = useRef(query);
|
|
75
|
+
queryRef.current = query;
|
|
63
76
|
|
|
77
|
+
// Valeur changée de l'extérieur (ex. remplie par un autre champ) : on
|
|
78
|
+
// l'affiche sans lancer de recherche
|
|
64
79
|
useEffect(() => {
|
|
65
|
-
if (value
|
|
80
|
+
if (value === undefined || value === queryRef.current) return;
|
|
81
|
+
skipNextSearchRef.current = true;
|
|
82
|
+
setQuery(value);
|
|
66
83
|
}, [value]);
|
|
67
84
|
|
|
68
85
|
useEffect(() => {
|
|
@@ -103,6 +120,7 @@ export const AddressSearch = ({
|
|
|
103
120
|
const addresses = await provider.search(normalizedQuery, {
|
|
104
121
|
limit,
|
|
105
122
|
signal: controller.signal,
|
|
123
|
+
type: searchType,
|
|
106
124
|
});
|
|
107
125
|
setResults(addresses);
|
|
108
126
|
setActiveIndex(-1);
|
|
@@ -122,20 +140,21 @@ export const AddressSearch = ({
|
|
|
122
140
|
window.clearTimeout(timeout);
|
|
123
141
|
requestRef.current?.abort();
|
|
124
142
|
};
|
|
125
|
-
}, [debounceTime, disabled, limit, minimumQueryLength, provider, query]);
|
|
143
|
+
}, [debounceTime, disabled, limit, minimumQueryLength, provider, query, searchType]);
|
|
126
144
|
|
|
127
145
|
const selectAddress = useCallback(
|
|
128
146
|
(address: GeocodedAddress) => {
|
|
147
|
+
const nextValue = formatValue(address);
|
|
129
148
|
requestRef.current?.abort();
|
|
130
149
|
skipNextSearchRef.current = true;
|
|
131
|
-
setQuery(
|
|
150
|
+
setQuery(nextValue);
|
|
132
151
|
setResults([]);
|
|
133
152
|
setIsOpen(false);
|
|
134
153
|
setActiveIndex(-1);
|
|
135
|
-
onChange?.(
|
|
154
|
+
onChange?.(nextValue);
|
|
136
155
|
onSelect(address);
|
|
137
156
|
},
|
|
138
|
-
[onChange, onSelect],
|
|
157
|
+
[formatValue, onChange, onSelect],
|
|
139
158
|
);
|
|
140
159
|
|
|
141
160
|
const selectManualAddress = useCallback(() => {
|
|
@@ -186,6 +205,7 @@ export const AddressSearch = ({
|
|
|
186
205
|
}
|
|
187
206
|
autoComplete="off"
|
|
188
207
|
disabled={disabled}
|
|
208
|
+
required={required}
|
|
189
209
|
value={query}
|
|
190
210
|
placeholder={placeholder}
|
|
191
211
|
onChange={(event) => {
|
|
@@ -206,7 +226,7 @@ export const AddressSearch = ({
|
|
|
206
226
|
La recherche d'adresse est indisponible.
|
|
207
227
|
</div>
|
|
208
228
|
) : results.length === 0 && !loading ? (
|
|
209
|
-
<div className="address-search-status">
|
|
229
|
+
<div className="address-search-status">{emptyLabel}</div>
|
|
210
230
|
) : (
|
|
211
231
|
results.map((address, index) => (
|
|
212
232
|
<button
|
|
@@ -62,6 +62,8 @@ export interface dataTableActionsInterface<T> {
|
|
|
62
62
|
visible?: boolean;
|
|
63
63
|
width?: string | number;
|
|
64
64
|
minWidth?: string | number;
|
|
65
|
+
/** Aligns inline action content within its width; omitted preserves the action type's default. */
|
|
66
|
+
align?: "right" | "left" | "center";
|
|
65
67
|
focusNextRow?: boolean;
|
|
66
68
|
/** "inline" (défaut) : affichée dans la ligne ; "menu" : regroupée dans le menu « ⋮ » en fin de ligne */
|
|
67
69
|
display?: "inline" | "menu";
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { CSSProperties } from "react";
|
|
1
2
|
import { Button } from "../../button/Button";
|
|
2
3
|
import { Icon } from "../../icon/Icon";
|
|
3
4
|
import { dataTableActionsInterface } from "../dataTable.interface";
|
|
@@ -23,6 +24,13 @@ export const DataTableAction = ({
|
|
|
23
24
|
title = action.labelButton ? action.labelButton(row) : action.label;
|
|
24
25
|
}
|
|
25
26
|
|
|
27
|
+
const style: CSSProperties = {
|
|
28
|
+
minWidth: action.minWidth,
|
|
29
|
+
width: action.width,
|
|
30
|
+
justifyContent: action.align === "right" ? "flex-end" : action.align === "left" ? "flex-start" : action.align,
|
|
31
|
+
textAlign: action.align,
|
|
32
|
+
};
|
|
33
|
+
|
|
26
34
|
const handleClick = (e?: React.MouseEvent) => {
|
|
27
35
|
e?.stopPropagation();
|
|
28
36
|
if (action.disabled) return;
|
|
@@ -40,10 +48,7 @@ export const DataTableAction = ({
|
|
|
40
48
|
key={`button-action-${action.id}-${index}`}
|
|
41
49
|
onClick={handleClick}
|
|
42
50
|
disabled={action.disabled}
|
|
43
|
-
style={
|
|
44
|
-
minWidth: action.minWidth,
|
|
45
|
-
width: action.width,
|
|
46
|
-
}}
|
|
51
|
+
style={style}
|
|
47
52
|
className={`data-table-action button--tertiary mr-2 w-100 ${
|
|
48
53
|
action.classIcon ? action.classIcon : ""
|
|
49
54
|
}`}
|
|
@@ -65,16 +70,13 @@ export const DataTableAction = ({
|
|
|
65
70
|
}
|
|
66
71
|
}}
|
|
67
72
|
onClick={handleClick}
|
|
68
|
-
style={
|
|
69
|
-
minWidth: action.minWidth,
|
|
70
|
-
width: action.width,
|
|
71
|
-
}}
|
|
73
|
+
style={style}
|
|
72
74
|
>
|
|
73
75
|
<Icon classIcon={action.classIcon ?? ""} key={action.id} />
|
|
74
76
|
</div>
|
|
75
77
|
)}
|
|
76
78
|
{action.type === "link" && (
|
|
77
|
-
<button type="button" className="data-table-action-link" disabled={action.disabled} onClick={handleClick}>
|
|
79
|
+
<button type="button" className="data-table-action-link" style={style} disabled={action.disabled} onClick={handleClick}>
|
|
78
80
|
{title}
|
|
79
81
|
</button>
|
|
80
82
|
)}
|
package/src/form/Form.tsx
CHANGED
|
@@ -345,6 +345,10 @@ export const DynamicForm = forwardRef(
|
|
|
345
345
|
debounceTime={v.addressSearch?.debounceTime}
|
|
346
346
|
limit={v.addressSearch?.limit}
|
|
347
347
|
provider={v.addressSearch?.provider}
|
|
348
|
+
searchType={v.addressSearch?.searchType}
|
|
349
|
+
formatValue={v.addressSearch?.formatValue}
|
|
350
|
+
emptyLabel={v.addressSearch?.emptyLabel}
|
|
351
|
+
required={required}
|
|
348
352
|
allowManualEntry
|
|
349
353
|
manualEntryLabel={v.addressSearch?.manualEntryLabel}
|
|
350
354
|
onChange={(value) =>
|
|
@@ -3,6 +3,7 @@ import { GridBreakpoints } from "../grid/grid.interface";
|
|
|
3
3
|
import {
|
|
4
4
|
GeocodedAddress,
|
|
5
5
|
GeocodingProvider,
|
|
6
|
+
GeocodingSearchType,
|
|
6
7
|
} from "../geocoding/geocoding.interface";
|
|
7
8
|
|
|
8
9
|
export type FormMode = "read" | "edit";
|
|
@@ -46,6 +47,9 @@ export interface FormInterface {
|
|
|
46
47
|
debounceTime?: number;
|
|
47
48
|
limit?: number;
|
|
48
49
|
provider?: GeocodingProvider;
|
|
50
|
+
searchType?: GeocodingSearchType;
|
|
51
|
+
formatValue?: (address: GeocodedAddress) => string;
|
|
52
|
+
emptyLabel?: string;
|
|
49
53
|
manualEntryLabel?: string;
|
|
50
54
|
mapAddress?: (
|
|
51
55
|
address: GeocodedAddress,
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import {
|
|
2
|
+
GeocodedAddress,
|
|
3
|
+
GeocodingProvider,
|
|
4
|
+
GeocodingSearchOptions,
|
|
5
|
+
} from "./geocoding.interface";
|
|
6
|
+
|
|
7
|
+
const DEFAULT_CITY_URL = "https://geo.api.gouv.fr";
|
|
8
|
+
const FIELDS = "nom,codesPostaux,centre";
|
|
9
|
+
|
|
10
|
+
interface Commune {
|
|
11
|
+
code: string;
|
|
12
|
+
nom: string;
|
|
13
|
+
codesPostaux?: string[];
|
|
14
|
+
centre?: { coordinates?: [number, number] };
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface CityGeocodingServiceOptions {
|
|
18
|
+
baseUrl?: string;
|
|
19
|
+
fetcher?: typeof fetch;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Recherche de communes au fil de la saisie (API Découpage administratif) :
|
|
24
|
+
* - chiffres : début de code postal ("292" propose 29200 Brest),
|
|
25
|
+
* - texte : début de nom de commune ("bre" propose Brest, Brech...).
|
|
26
|
+
* Une commune à plusieurs codes postaux donne un résultat par code postal.
|
|
27
|
+
*/
|
|
28
|
+
export class CityGeocodingService implements GeocodingProvider {
|
|
29
|
+
private readonly baseUrl: string;
|
|
30
|
+
private readonly fetcher: typeof fetch;
|
|
31
|
+
// Communes par département, chargées une seule fois
|
|
32
|
+
private readonly departements = new Map<string, Promise<Commune[]>>();
|
|
33
|
+
|
|
34
|
+
constructor(options: CityGeocodingServiceOptions = {}) {
|
|
35
|
+
this.baseUrl = options.baseUrl ?? DEFAULT_CITY_URL;
|
|
36
|
+
this.fetcher = options.fetcher ?? globalThis.fetch.bind(globalThis);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
async search(
|
|
40
|
+
query: string,
|
|
41
|
+
options: GeocodingSearchOptions = {},
|
|
42
|
+
): Promise<GeocodedAddress[]> {
|
|
43
|
+
const normalizedQuery = query.trim();
|
|
44
|
+
if (!normalizedQuery) return [];
|
|
45
|
+
const limit = options.limit ?? 5;
|
|
46
|
+
|
|
47
|
+
if (/^\d+$/.test(normalizedQuery)) {
|
|
48
|
+
const communes = await this.communesDuCodePostal(normalizedQuery);
|
|
49
|
+
// Recherche dépassée par une saisie plus récente
|
|
50
|
+
if (options.signal?.aborted) {
|
|
51
|
+
throw new DOMException("Aborted", "AbortError");
|
|
52
|
+
}
|
|
53
|
+
return communes
|
|
54
|
+
.flatMap((commune) =>
|
|
55
|
+
(commune.codesPostaux ?? [])
|
|
56
|
+
.filter((codePostal) => codePostal.startsWith(normalizedQuery))
|
|
57
|
+
.map((codePostal) => this.toAddress(commune, codePostal)),
|
|
58
|
+
)
|
|
59
|
+
.sort(
|
|
60
|
+
(a, b) =>
|
|
61
|
+
a.postalCode.localeCompare(b.postalCode) ||
|
|
62
|
+
a.city.localeCompare(b.city, "fr"),
|
|
63
|
+
)
|
|
64
|
+
.slice(0, limit);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const params = new URLSearchParams({
|
|
68
|
+
nom: normalizedQuery,
|
|
69
|
+
fields: FIELDS,
|
|
70
|
+
boost: "population",
|
|
71
|
+
limit: String(limit),
|
|
72
|
+
});
|
|
73
|
+
const communes = await this.get(`/communes?${params}`, options.signal);
|
|
74
|
+
return communes
|
|
75
|
+
.flatMap((commune) =>
|
|
76
|
+
(commune.codesPostaux ?? []).map((codePostal) =>
|
|
77
|
+
this.toAddress(commune, codePostal),
|
|
78
|
+
),
|
|
79
|
+
)
|
|
80
|
+
.slice(0, limit);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Départements concernés par un début de code postal
|
|
84
|
+
private async communesDuCodePostal(codePostal: string): Promise<Commune[]> {
|
|
85
|
+
const prefixe = codePostal.slice(0, 2);
|
|
86
|
+
if (prefixe.length < 2 || prefixe === "98") return [];
|
|
87
|
+
// Corse : codes postaux 20xxx pour la Corse-du-Sud et la Haute-Corse
|
|
88
|
+
const departements =
|
|
89
|
+
prefixe === "20"
|
|
90
|
+
? ["2A", "2B"]
|
|
91
|
+
: prefixe === "97"
|
|
92
|
+
? codePostal.length >= 3
|
|
93
|
+
? [codePostal.slice(0, 3)]
|
|
94
|
+
: []
|
|
95
|
+
: [prefixe];
|
|
96
|
+
const listes = await Promise.all(
|
|
97
|
+
departements.map((departement) => this.communesDuDepartement(departement)),
|
|
98
|
+
);
|
|
99
|
+
return listes.flat();
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Sans signal : la requête est partagée par toutes les recherches du département
|
|
103
|
+
private communesDuDepartement(departement: string): Promise<Commune[]> {
|
|
104
|
+
let communes = this.departements.get(departement);
|
|
105
|
+
if (!communes) {
|
|
106
|
+
communes = this.get(
|
|
107
|
+
`/departements/${departement}/communes?fields=${FIELDS}`,
|
|
108
|
+
).catch((error) => {
|
|
109
|
+
this.departements.delete(departement);
|
|
110
|
+
throw error;
|
|
111
|
+
});
|
|
112
|
+
this.departements.set(departement, communes);
|
|
113
|
+
}
|
|
114
|
+
return communes;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
private async get(path: string, signal?: AbortSignal): Promise<Commune[]> {
|
|
118
|
+
const response = await this.fetcher(`${this.baseUrl}${path}`, { signal });
|
|
119
|
+
// Département inconnu (ex. 96) : aucune commune
|
|
120
|
+
if (response.status === 404) return [];
|
|
121
|
+
if (!response.ok) {
|
|
122
|
+
throw new Error(`City search request failed (${response.status})`);
|
|
123
|
+
}
|
|
124
|
+
return (await response.json()) as Commune[];
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
private toAddress(commune: Commune, codePostal: string): GeocodedAddress {
|
|
128
|
+
const [longitude, latitude] = commune.centre?.coordinates ?? [0, 0];
|
|
129
|
+
return {
|
|
130
|
+
id: `${commune.code}-${codePostal}`,
|
|
131
|
+
label: `${codePostal} ${commune.nom}`,
|
|
132
|
+
addressLine: commune.nom,
|
|
133
|
+
postalCode: codePostal,
|
|
134
|
+
city: commune.nom,
|
|
135
|
+
cityCode: commune.code,
|
|
136
|
+
countryCode: "FR",
|
|
137
|
+
longitude,
|
|
138
|
+
latitude,
|
|
139
|
+
type: "municipality",
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
export const cityGeocodingService = new CityGeocodingService();
|
|
@@ -53,6 +53,7 @@ export class GeocodingService implements GeocodingProvider {
|
|
|
53
53
|
autocomplete: "1",
|
|
54
54
|
index: "address",
|
|
55
55
|
});
|
|
56
|
+
if (options.type) params.set("type", options.type);
|
|
56
57
|
const response = await this.fetcher(`${this.baseUrl}?${params}`, {
|
|
57
58
|
signal: options.signal,
|
|
58
59
|
});
|
|
@@ -12,9 +12,17 @@ export interface GeocodedAddress {
|
|
|
12
12
|
type?: string;
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
+
// "municipality" : communes seulement (recherche par code postal ou ville)
|
|
16
|
+
export type GeocodingSearchType =
|
|
17
|
+
| "housenumber"
|
|
18
|
+
| "street"
|
|
19
|
+
| "locality"
|
|
20
|
+
| "municipality";
|
|
21
|
+
|
|
15
22
|
export interface GeocodingSearchOptions {
|
|
16
23
|
limit?: number;
|
|
17
24
|
signal?: AbortSignal;
|
|
25
|
+
type?: GeocodingSearchType;
|
|
18
26
|
}
|
|
19
27
|
|
|
20
28
|
export interface GeocodingProvider {
|
package/src/index.ts
CHANGED
|
@@ -52,10 +52,15 @@ export {
|
|
|
52
52
|
GeocodingService,
|
|
53
53
|
geocodingService,
|
|
54
54
|
} from "./geocoding/GeocodingService";
|
|
55
|
+
export {
|
|
56
|
+
CityGeocodingService,
|
|
57
|
+
cityGeocodingService,
|
|
58
|
+
} from "./geocoding/CityGeocodingService";
|
|
55
59
|
export type {
|
|
56
60
|
GeocodedAddress,
|
|
57
61
|
GeocodingProvider,
|
|
58
62
|
GeocodingSearchOptions,
|
|
63
|
+
GeocodingSearchType,
|
|
59
64
|
} from "./geocoding/geocoding.interface";
|
|
60
65
|
export { Header } from "./header/Header";
|
|
61
66
|
export { Icon } from "./icon/Icon";
|
package/src/sidebar/Sidebar.tsx
CHANGED
|
@@ -155,6 +155,38 @@ export const Sidebar = ({
|
|
|
155
155
|
[toggleSidebar],
|
|
156
156
|
);
|
|
157
157
|
|
|
158
|
+
// iOS : un glissé vertical sans rien à scroller dans la sidebar fait rebondir
|
|
159
|
+
// la page derrière (fond blanc, contenu visible en haut). Écouteur natif non
|
|
160
|
+
// passif : celui de React est passif, son preventDefault n'a pas d'effet.
|
|
161
|
+
useEffect(() => {
|
|
162
|
+
const sidebar = sidebarRef.current;
|
|
163
|
+
if (!isOpen || !sidebar) return;
|
|
164
|
+
|
|
165
|
+
const onTouchMove = (event: TouchEvent) => {
|
|
166
|
+
const deltaY = event.touches[0].clientY - touchStartY.current;
|
|
167
|
+
let element = event.target as HTMLElement | null;
|
|
168
|
+
while (element && element !== sidebar.parentElement) {
|
|
169
|
+
const { overflowY } = window.getComputedStyle(element);
|
|
170
|
+
const scrollable =
|
|
171
|
+
(overflowY === "auto" || overflowY === "scroll") &&
|
|
172
|
+
element.scrollHeight > element.clientHeight;
|
|
173
|
+
if (scrollable) {
|
|
174
|
+
const enHaut = element.scrollTop <= 0;
|
|
175
|
+
const enBas =
|
|
176
|
+
element.scrollTop + element.clientHeight >= element.scrollHeight;
|
|
177
|
+
// Laisse scroller tant qu'on n'est pas en butée dans le sens du geste
|
|
178
|
+
if ((deltaY > 0 && !enHaut) || (deltaY < 0 && !enBas)) return;
|
|
179
|
+
break;
|
|
180
|
+
}
|
|
181
|
+
element = element.parentElement;
|
|
182
|
+
}
|
|
183
|
+
if (event.cancelable) event.preventDefault();
|
|
184
|
+
};
|
|
185
|
+
|
|
186
|
+
sidebar.addEventListener("touchmove", onTouchMove, { passive: false });
|
|
187
|
+
return () => sidebar.removeEventListener("touchmove", onTouchMove);
|
|
188
|
+
}, [isOpen]);
|
|
189
|
+
|
|
158
190
|
useEffect(() => {
|
|
159
191
|
if (isOpen) {
|
|
160
192
|
document.addEventListener("mousedown", handleClickOutside);
|
package/src/sidebar/sidebar.css
CHANGED
|
@@ -2,10 +2,13 @@
|
|
|
2
2
|
touch-action: pan-y;
|
|
3
3
|
/* laisse le scroll vertical natif, gère le X nous-même */
|
|
4
4
|
will-change: transform;
|
|
5
|
+
/* le scroll ne se propage pas à la page derrière */
|
|
6
|
+
overscroll-behavior: contain;
|
|
5
7
|
}
|
|
6
8
|
|
|
7
9
|
.overlay {
|
|
8
10
|
touch-action: none;
|
|
11
|
+
overscroll-behavior: contain;
|
|
9
12
|
}
|
|
10
13
|
|
|
11
14
|
.toggle-btn {
|
|
@@ -15,7 +18,12 @@
|
|
|
15
18
|
|
|
16
19
|
.sidebar {
|
|
17
20
|
width: 200px;
|
|
21
|
+
/* iOS (Safari, WKWebView) : 100vh dépasse la zone visible et cache le bas
|
|
22
|
+
de la sidebar sous la barre d'outils ; 100dvh suit la hauteur visible */
|
|
18
23
|
height: 100vh;
|
|
24
|
+
height: 100dvh;
|
|
25
|
+
padding-bottom: env(safe-area-inset-bottom);
|
|
26
|
+
box-sizing: border-box;
|
|
19
27
|
background-color: var(--primary-color);
|
|
20
28
|
color: white;
|
|
21
29
|
transition: width 0.3s;
|
package/tests/dataTable.test.mjs
CHANGED
|
@@ -26,10 +26,12 @@ const server = await createServer({
|
|
|
26
26
|
server: { middlewareMode: true, watch: null, hmr: false },
|
|
27
27
|
});
|
|
28
28
|
let DataTableRow;
|
|
29
|
+
let DataTableAction;
|
|
29
30
|
let Tabs;
|
|
30
31
|
let Select;
|
|
31
32
|
try {
|
|
32
33
|
({ DataTableRow } = await server.ssrLoadModule("/src/dataTable/dataTableRow/DataTableRow.tsx"));
|
|
34
|
+
({ DataTableAction } = await server.ssrLoadModule("/src/dataTable/dataTableActions/DataTableAction.tsx"));
|
|
33
35
|
({ Tabs } = await server.ssrLoadModule("/src/tabs/Tabs.tsx"));
|
|
34
36
|
({ Select } = await server.ssrLoadModule("/src/select/Select.tsx"));
|
|
35
37
|
} finally {
|
|
@@ -50,6 +52,33 @@ test("action widths preserve Hub sums and Kelt explicit column widths", () => {
|
|
|
50
52
|
assert.equal(utils.getActionsWidth([]), 200);
|
|
51
53
|
});
|
|
52
54
|
|
|
55
|
+
test("inline action content aligns within its width for every action type", () => {
|
|
56
|
+
for (const type of ["button", "icon", "link"]) {
|
|
57
|
+
for (const [align, justifyContent] of [["left", "flex-start"], ["center", "center"], ["right", "flex-end"]]) {
|
|
58
|
+
const html = renderToStaticMarkup(createElement(DataTableAction, {
|
|
59
|
+
action: { id: "delete", label: "Delete", type, classIcon: "bi-trash", width: 50, minWidth: 40, align, onClick() {} },
|
|
60
|
+
row: {},
|
|
61
|
+
}));
|
|
62
|
+
assert.match(html, /width:50px/);
|
|
63
|
+
assert.match(html, /min-width:40px/);
|
|
64
|
+
assert.ok(html.includes(`justify-content:${justifyContent}`));
|
|
65
|
+
assert.ok(html.includes(`text-align:${align}`));
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
test("omitting action alignment preserves default styles and visibility", () => {
|
|
71
|
+
for (const type of ["button", "icon", "link"]) {
|
|
72
|
+
const action = { id: "delete", label: "Delete", type, width: "50px", onClick() {} };
|
|
73
|
+
const html = renderToStaticMarkup(createElement(DataTableAction, { action, row: {} }));
|
|
74
|
+
assert.doesNotMatch(html, /justify-content|text-align/);
|
|
75
|
+
assert.match(html, /width:50px/);
|
|
76
|
+
assert.equal(renderToStaticMarkup(createElement(DataTableAction, {
|
|
77
|
+
action: { ...action, visible: false }, row: {},
|
|
78
|
+
})), "");
|
|
79
|
+
}
|
|
80
|
+
});
|
|
81
|
+
|
|
53
82
|
test("menu actions are grouped behind a single three-dots toggle", () => {
|
|
54
83
|
assert.equal(utils.getActionsWidth([{ width: 48 }, { width: 100, display: "menu" }]), 48 + utils.ACTION_MENU_WIDTH);
|
|
55
84
|
assert.equal(utils.getActionsWidth([{ display: "menu" }, { display: "menu" }]), utils.ACTION_MENU_WIDTH);
|
package/tsconfig.tsbuildinfo
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"root":["./src/App.menu.tsx","./src/App.routes.tsx","./src/App.tsx","./src/index.ts","./src/main.tsx","./src/_core/hooks/HistoryProvider.tsx","./src/_core/hooks/ImageChecker.tsx","./src/_core/hooks/imageExist.tsx","./src/_core/hooks/isEAN13.tsx","./src/_core/hooks/objectToQueryParams.tsx","./src/_core/hooks/useCurrency.tsx","./src/_core/hooks/useDateutils.ts","./src/_core/hooks/useIsMobile.ts","./src/_core/hooks/useToken.tsx","./src/_core/hooks/useValidate.tsx","./src/_core/hooks/widgetSupport.tsx","./src/_core/type/generiqueType.tsx","./src/action/Action.view.tsx","./src/action/ButtonAction.tsx","./src/addressSearch/AddressSearch.tsx","./src/addressSearch/AddressSearch.view.tsx","./src/badge/Badge.tsx","./src/badge/Badge.view.tsx","./src/button/Button.tsx","./src/button/Button.view.tsx","./src/button/button.interface.tsx","./src/button/buttonActions/ButtonActions.tsx","./src/button/buttonFile/ButtonFile.tsx","./src/button/buttonFile/ButtonFile.view.tsx","./src/button/buttonFilters/ButtonFilters.tsx","./src/button/buttonImport/ButtonImport.tsx","./src/button/buttonImport/ButtonImport.view.tsx","./src/card/Card.tsx","./src/card/Card.view.tsx","./src/card/card.interface.tsx","./src/card/cardAction.interface.tsx","./src/card/cardAction/CardAction.tsx","./src/card/hook/useCardInteractions.tsx","./src/carousel/Carousel.tsx","./src/carousel/Carousel.view.tsx","./src/damier/Damier.tsx","./src/damier/Damier.view.tsx","./src/damier/damierCell/DamierCell.tsx","./src/damier/damierCell/damierCell.interface.tsx","./src/dataTable/DataTable.tsx","./src/dataTable/DataTable.view.tsx","./src/dataTable/dataTable.interface.ts","./src/dataTable/dataTable.utils.ts","./src/dataTable/useDataTableState.tsx","./src/dataTable/dataTableActions/DataTableAction.tsx","./src/dataTable/dataTableActions/DataTableActionMenu.tsx","./src/dataTable/dataTableColumn/DataTableColumn.tsx","./src/dataTable/dataTableColumn/dataTableColumnFilter/DataTableColumnFilter.tsx","./src/dataTable/dataTableColumn/dataTableColumnFilter/dataTableColumnFilterDate/DataTableColumnFilterDate.tsx","./src/dataTable/dataTableColumn/dataTableColumnFilter/dataTableColumnFilterDateRange/dataTableColumnFilterDateRange.tsx","./src/dataTable/dataTableColumn/dataTableColumnFilter/dataTableColumnFilterEnum/DataTableColumnFilterEnum.tsx","./src/dataTable/dataTableColumn/dataTableColumnFilter/dataTableColumnFilterString/DataTableColumnFilterString.tsx","./src/dataTable/dataTableColumn/dataTableColumnSort/DataTableColumnSort.tsx","./src/dataTable/dataTableFilters/DataTableFilters.tsx","./src/dataTable/dataTableLoading/DataTableLoading.tsx","./src/dataTable/dataTableRow/DataTableRow.tsx","./src/datePicker/DatePicker.tsx","./src/datePicker/DatePicker.view.tsx","./src/docs/ComponentDocs.tsx","./src/docs/catalog.ts","./src/docs/documentation.types.ts","./src/docs/virtual.d.ts","./src/expands/Expands.tsx","./src/expands/Expands.view.tsx","./src/expands/expand/expand.interface.tsx","./src/expands/expand/expand.tsx","./src/filAriane/FilAriane.tsx","./src/filAriane/FilAriane.view.tsx","./src/filAriane/filAriane.interface.tsx","./src/form/Form.tsx","./src/form/Form.view.tsx","./src/form/form.enum.ts","./src/form/form.interface.tsx","./src/form/input/Input.tsx","./src/form/inputNumber/InputNumber.tsx","./src/form/inputNumber/InputNumber.view.tsx","./src/form/inputPrice/InputPrice.tsx","./src/form/inputPrice/InputPrice.view.tsx","./src/form/textArea/TextArea.tsx","./src/form/textArea/TextArea.view.tsx","./src/geocoding/GeocodingService.ts","./src/geocoding/geocoding.interface.ts","./src/grid/Grid.tsx","./src/grid/Grid.view.tsx","./src/grid/grid.interface.tsx","./src/grid/col/Col.tsx","./src/grid/col/colStyled/ColStyled.tsx","./src/grid/container/Container.tsx","./src/grid/row/Row.tsx","./src/header/Header.tsx","./src/header/Header.view.tsx","./src/header/header.interface.tsx","./src/home/Home.tsx","./src/icon/Icon.tsx","./src/icon/Icons.view.tsx","./src/icon/icon.interface.tsx","./src/icon/iconSize.enum.ts","./src/loader/Loader.tsx","./src/loader/Loader.view.tsx","./src/menus/Menus.tsx","./src/menus/Menus.view.tsx","./src/menus/menu/Menu.tsx","./src/menus/menu/menu.interface.tsx","./src/message/Message.tsx","./src/message/Message.view.tsx","./src/modal/Modal.tsx","./src/modal/Modal.view.tsx","./src/notFound/NotFound.tsx","./src/overlayPanel/OverlayPanel.tsx","./src/overlayPanel/OverlayPanel.view.tsx","./src/overlayPanel/overlay.context.tsx","./src/overlayPanel/overlayPanelStyled/OverlayPanelStyled.tsx","./src/pdfViewer/PdfViewer.tsx","./src/pdfViewer/PdfViewer.view.tsx","./src/quantity/Quantity.tsx","./src/quantity/Quantity.view.tsx","./src/search/Search.tsx","./src/search/Search.view.tsx","./src/select/Select.tsx","./src/select/Select.view.tsx","./src/select/selectOption.interface.ts","./src/sidebar/Sidebar.tsx","./src/sidebar/Sidebar.view.tsx","./src/sidebarData/SidebarData.tsx","./src/sidebarData/SidebarData.view.tsx","./src/tabs/Tabs.tsx","./src/tabs/Tabs.view.tsx","./src/toaster/Toaster.tsx","./src/toaster/Toaster.view.tsx","./src/toaster/store/useToasterStore.tsx"],"version":"5.9.3"}
|
|
1
|
+
{"root":["./src/App.menu.tsx","./src/App.routes.tsx","./src/App.tsx","./src/index.ts","./src/main.tsx","./src/_core/hooks/HistoryProvider.tsx","./src/_core/hooks/ImageChecker.tsx","./src/_core/hooks/imageExist.tsx","./src/_core/hooks/isEAN13.tsx","./src/_core/hooks/objectToQueryParams.tsx","./src/_core/hooks/useCurrency.tsx","./src/_core/hooks/useDateutils.ts","./src/_core/hooks/useIsMobile.ts","./src/_core/hooks/useToken.tsx","./src/_core/hooks/useValidate.tsx","./src/_core/hooks/widgetSupport.tsx","./src/_core/type/generiqueType.tsx","./src/action/Action.view.tsx","./src/action/ButtonAction.tsx","./src/addressSearch/AddressSearch.tsx","./src/addressSearch/AddressSearch.view.tsx","./src/badge/Badge.tsx","./src/badge/Badge.view.tsx","./src/button/Button.tsx","./src/button/Button.view.tsx","./src/button/button.interface.tsx","./src/button/buttonActions/ButtonActions.tsx","./src/button/buttonFile/ButtonFile.tsx","./src/button/buttonFile/ButtonFile.view.tsx","./src/button/buttonFilters/ButtonFilters.tsx","./src/button/buttonImport/ButtonImport.tsx","./src/button/buttonImport/ButtonImport.view.tsx","./src/card/Card.tsx","./src/card/Card.view.tsx","./src/card/card.interface.tsx","./src/card/cardAction.interface.tsx","./src/card/cardAction/CardAction.tsx","./src/card/hook/useCardInteractions.tsx","./src/carousel/Carousel.tsx","./src/carousel/Carousel.view.tsx","./src/damier/Damier.tsx","./src/damier/Damier.view.tsx","./src/damier/damierCell/DamierCell.tsx","./src/damier/damierCell/damierCell.interface.tsx","./src/dataTable/DataTable.tsx","./src/dataTable/DataTable.view.tsx","./src/dataTable/dataTable.interface.ts","./src/dataTable/dataTable.utils.ts","./src/dataTable/useDataTableState.tsx","./src/dataTable/dataTableActions/DataTableAction.tsx","./src/dataTable/dataTableActions/DataTableActionMenu.tsx","./src/dataTable/dataTableColumn/DataTableColumn.tsx","./src/dataTable/dataTableColumn/dataTableColumnFilter/DataTableColumnFilter.tsx","./src/dataTable/dataTableColumn/dataTableColumnFilter/dataTableColumnFilterDate/DataTableColumnFilterDate.tsx","./src/dataTable/dataTableColumn/dataTableColumnFilter/dataTableColumnFilterDateRange/dataTableColumnFilterDateRange.tsx","./src/dataTable/dataTableColumn/dataTableColumnFilter/dataTableColumnFilterEnum/DataTableColumnFilterEnum.tsx","./src/dataTable/dataTableColumn/dataTableColumnFilter/dataTableColumnFilterString/DataTableColumnFilterString.tsx","./src/dataTable/dataTableColumn/dataTableColumnSort/DataTableColumnSort.tsx","./src/dataTable/dataTableFilters/DataTableFilters.tsx","./src/dataTable/dataTableLoading/DataTableLoading.tsx","./src/dataTable/dataTableRow/DataTableRow.tsx","./src/datePicker/DatePicker.tsx","./src/datePicker/DatePicker.view.tsx","./src/docs/ComponentDocs.tsx","./src/docs/catalog.ts","./src/docs/documentation.types.ts","./src/docs/virtual.d.ts","./src/expands/Expands.tsx","./src/expands/Expands.view.tsx","./src/expands/expand/expand.interface.tsx","./src/expands/expand/expand.tsx","./src/filAriane/FilAriane.tsx","./src/filAriane/FilAriane.view.tsx","./src/filAriane/filAriane.interface.tsx","./src/form/Form.tsx","./src/form/Form.view.tsx","./src/form/form.enum.ts","./src/form/form.interface.tsx","./src/form/input/Input.tsx","./src/form/inputNumber/InputNumber.tsx","./src/form/inputNumber/InputNumber.view.tsx","./src/form/inputPrice/InputPrice.tsx","./src/form/inputPrice/InputPrice.view.tsx","./src/form/textArea/TextArea.tsx","./src/form/textArea/TextArea.view.tsx","./src/geocoding/CityGeocodingService.ts","./src/geocoding/GeocodingService.ts","./src/geocoding/geocoding.interface.ts","./src/grid/Grid.tsx","./src/grid/Grid.view.tsx","./src/grid/grid.interface.tsx","./src/grid/col/Col.tsx","./src/grid/col/colStyled/ColStyled.tsx","./src/grid/container/Container.tsx","./src/grid/row/Row.tsx","./src/header/Header.tsx","./src/header/Header.view.tsx","./src/header/header.interface.tsx","./src/home/Home.tsx","./src/icon/Icon.tsx","./src/icon/Icons.view.tsx","./src/icon/icon.interface.tsx","./src/icon/iconSize.enum.ts","./src/loader/Loader.tsx","./src/loader/Loader.view.tsx","./src/menus/Menus.tsx","./src/menus/Menus.view.tsx","./src/menus/menu/Menu.tsx","./src/menus/menu/menu.interface.tsx","./src/message/Message.tsx","./src/message/Message.view.tsx","./src/modal/Modal.tsx","./src/modal/Modal.view.tsx","./src/notFound/NotFound.tsx","./src/overlayPanel/OverlayPanel.tsx","./src/overlayPanel/OverlayPanel.view.tsx","./src/overlayPanel/overlay.context.tsx","./src/overlayPanel/overlayPanelStyled/OverlayPanelStyled.tsx","./src/pdfViewer/PdfViewer.tsx","./src/pdfViewer/PdfViewer.view.tsx","./src/quantity/Quantity.tsx","./src/quantity/Quantity.view.tsx","./src/search/Search.tsx","./src/search/Search.view.tsx","./src/select/Select.tsx","./src/select/Select.view.tsx","./src/select/selectOption.interface.ts","./src/sidebar/Sidebar.tsx","./src/sidebar/Sidebar.view.tsx","./src/sidebarData/SidebarData.tsx","./src/sidebarData/SidebarData.view.tsx","./src/tabs/Tabs.tsx","./src/tabs/Tabs.view.tsx","./src/toaster/Toaster.tsx","./src/toaster/Toaster.view.tsx","./src/toaster/store/useToasterStore.tsx"],"version":"5.9.3"}
|
package/vite.config.ts
CHANGED
|
@@ -22,6 +22,13 @@ export default defineConfig(({ command, mode }) => ({
|
|
|
22
22
|
},
|
|
23
23
|
rollupOptions: {
|
|
24
24
|
external: [/^react(\/.*)?$/, /^react-dom(\/.*)?$/, "react-router", "react-router-dom"],
|
|
25
|
+
// Rapport de durée des plugins : bruit à chaque build
|
|
26
|
+
checks: { pluginTimings: false },
|
|
27
|
+
onwarn(warning, warn) {
|
|
28
|
+
// 'use client' de react-calendar : directive Next.js, sans effet ici
|
|
29
|
+
if (warning.code === "MODULE_LEVEL_DIRECTIVE") return;
|
|
30
|
+
warn(warning);
|
|
31
|
+
},
|
|
25
32
|
},
|
|
26
33
|
},
|
|
27
34
|
}));
|