Skip to content

Commit 526e257

Browse files
committed
before bump
1 parent 9f0e69d commit 526e257

4 files changed

Lines changed: 171 additions & 560 deletions

File tree

dist-types/client/europlate.client.d.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,8 @@ export type EuroPlateUI = {
1616
status?: HTMLElement;
1717
};
1818
export type EuroPlateOptions = {
19-
input: HTMLInputElement;
19+
input?: HTMLInputElement;
20+
wrapper?: string | HTMLElement | false;
2021
ui?: EuroPlateUI;
2122
allowedCountries?: string[];
2223
mode?: "AUTO" | string;
@@ -35,6 +36,12 @@ export type EuroPlateOptions = {
3536
inputmask?: any;
3637
};
3738
debug?: boolean;
39+
autoLoadDeps?: {
40+
inputmask?: boolean;
41+
};
42+
cdn?: {
43+
inputmask?: string;
44+
};
3845
i18n?: I18nCode;
3946
};
4047
export type EuroPlateInstance = {

dist-types/client/europlate.client.d.ts.map

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/client/europlate.client.ts

Lines changed: 162 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,8 @@ export type EuroPlateUI = {
3131
};
3232

3333
export type EuroPlateOptions = {
34-
input: HTMLInputElement; // REQUIRED
34+
input?: HTMLInputElement; // era required → ora opzionale (se usi wrapper)
35+
wrapper?: string | HTMLElement | false; // 👈 selector, nodo o false (default)
3536
ui?: EuroPlateUI; // opzionale (UI pronta)
3637
allowedCountries?: string[]; // default: tutte
3738
mode?: "AUTO" | string; // default: "AUTO"
@@ -43,6 +44,14 @@ export type EuroPlateOptions = {
4344
logger?: Logger; // opzionale
4445
deps?: { inputmask?: any }; // opzionale (se non c’è su window)
4546
debug?: boolean; // default: false
47+
// 👉 Autoload delle dipendenze (true di default)
48+
autoLoadDeps?: {
49+
inputmask?: boolean; // default true
50+
};
51+
// 👉 CDN override (se vuoi cambiare l’URL)
52+
cdn?: {
53+
inputmask?: string; // default jsDelivr UMD
54+
};
4655
i18n?: I18nCode; // <— NEW (default: 'AUTO')
4756
};
4857

@@ -57,7 +66,6 @@ type DictScalarKey = "auto" | "placeholderAuto" | "valid" | "invalid" | "checked
5766

5867
// Nomi paese localizzati (facoltativi per ciascun cc)
5968
type CountryNameDict = Partial<Record<CountryKey, string>>;
60-
6169
const DICT: Record<
6270
Lang,
6371
{
@@ -75,17 +83,78 @@ const DICT: Record<
7583
valid: "Valida",
7684
invalid: "Non valida",
7785
checked: "Controllati",
78-
countries: { IT: "Italia", FR: "Francia", DE: "Germania", ES: "Spagna", NL: "Paesi Bassi" },
86+
countries: {
87+
IT: "Italia",
88+
UK: "Regno Unito",
89+
DE: "Germania",
90+
FR: "Francia",
91+
ES: "Spagna",
92+
PT: "Portogallo",
93+
NL: "Paesi Bassi",
94+
BE: "Belgio",
95+
CH: "Svizzera",
96+
AT: "Austria",
97+
IE: "Irlanda",
98+
LU: "Lussemburgo",
99+
DK: "Danimarca",
100+
SE: "Svezia",
101+
NO: "Norvegia",
102+
FI: "Finlandia",
103+
PL: "Polonia",
104+
CZ: "Cechia",
105+
SK: "Slovacchia",
106+
HU: "Ungheria",
107+
RO: "Romania",
108+
BG: "Bulgaria",
109+
SI: "Slovenia",
110+
HR: "Croazia",
111+
GR: "Grecia",
112+
LT: "Lituania",
113+
LV: "Lettonia",
114+
EE: "Estonia",
115+
UA: "Ucraina",
116+
},
79117
},
80118
en: {
81119
auto: "Auto (All)",
82120
placeholderAuto: "AA 999 AA / AA-999-AA / 9999 AAA",
83121
valid: "Valid",
84122
invalid: "Invalid",
85123
checked: "Checked",
86-
countries: { IT: "Italy", FR: "France", DE: "Germany", ES: "Spain", NL: "Netherlands" },
124+
countries: {
125+
IT: "Italy",
126+
UK: "United Kingdom",
127+
DE: "Germany",
128+
FR: "France",
129+
ES: "Spain",
130+
PT: "Portugal",
131+
NL: "Netherlands",
132+
BE: "Belgium",
133+
CH: "Switzerland",
134+
AT: "Austria",
135+
IE: "Ireland",
136+
LU: "Luxembourg",
137+
DK: "Denmark",
138+
SE: "Sweden",
139+
NO: "Norway",
140+
FI: "Finland",
141+
PL: "Poland",
142+
CZ: "Czechia",
143+
SK: "Slovakia",
144+
HU: "Hungary",
145+
RO: "Romania",
146+
BG: "Bulgaria",
147+
SI: "Slovenia",
148+
HR: "Croatia",
149+
GR: "Greece",
150+
LT: "Lithuania",
151+
LV: "Latvia",
152+
EE: "Estonia",
153+
UA: "Ukraine",
154+
},
87155
},
88156
};
157+
89158
function pickLang(code: I18nCode): Lang {
90159
if (code === "IT") return "it";
91160
if (code === "EN") return "en";
@@ -122,7 +191,8 @@ export type EuroPlateInstance = {
122191
export function createEuroPlate(EuroMod: any, opts: EuroPlateOptions): EuroPlateInstance {
123192
const {
124193
i18n = "AUTO",
125-
input,
194+
//input,
195+
wrapper = false, // 👈 AGGIUNTO: selector | HTMLElement | false
126196
ui = {},
127197
allowedCountries,
128198
mode = "AUTO",
@@ -141,18 +211,59 @@ export function createEuroPlate(EuroMod: any, opts: EuroPlateOptions): EuroPlate
141211
debug = false,
142212
} = opts || ({} as EuroPlateOptions);
143213
let lang: Lang = pickLang(opts?.i18n ?? "AUTO");
144-
// const lang: Lang = pickLang(opts?.i18n ?? "AUTO");
145-
const button = ui.button ?? undefined;
146-
const dropdown = ui.dropdown ?? undefined;
147-
const flagIcon = ui.flagIcon ?? undefined;
148-
const flagLabel = ui.flagLabel ?? undefined;
149-
const statusEl = ui.status ?? undefined;
150214

215+
// riferimenti UI locali (li riempiremo da wrapper oppure da opts.ui)
216+
let input!: HTMLInputElement; // ← verrà assegnato
217+
let button: HTMLElement | undefined = ui.button ?? undefined;
218+
let dropdown: HTMLElement | undefined = ui.dropdown ?? undefined;
219+
let flagIcon: HTMLElement | undefined = ui.flagIcon ?? undefined;
220+
let flagLabel: HTMLElement | undefined = ui.flagLabel ?? undefined;
221+
let statusEl: HTMLElement | undefined = ui.status ?? undefined;
222+
223+
// ----- AUTO-BUILD DOM SE wrapper È TRUTHY -----
224+
if (wrapper) {
225+
const root: HTMLElement | null =
226+
typeof wrapper === "string" ? document.querySelector(wrapper) : wrapper;
227+
228+
if (!root) throw new Error(`Wrapper non trovato: ${String(wrapper)}`);
229+
230+
root.classList.add("plate-iti-wrapper");
231+
root.innerHTML = `
232+
<div class="plate-iti">
233+
<button class="flag-btn" type="button" aria-haspopup="listbox" aria-expanded="false">
234+
<div class="iti__flag-box"><div class="iti__flag iti__auto-eu"></div></div>
235+
<span class="flag-label">${t(lang, "auto")}</span>
236+
<svg width="14" height="14" viewBox="0 0 20 20" fill="none" aria-hidden="true" style="margin-left:4px">
237+
<path d="M6 8l4 4 4-4" stroke="#6b7280" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" />
238+
</svg>
239+
</button>
240+
<input class="plate-input" placeholder="${t(lang, "placeholderAuto")}" autocomplete="off" />
241+
<div class="dropdown" role="listbox" aria-label="Select country"></div>
242+
</div>
243+
<div class="status"></div>
244+
`;
245+
246+
// bind elementi generati
247+
button = root.querySelector(".flag-btn") as HTMLElement;
248+
flagIcon = root.querySelector(".iti__flag") as HTMLElement;
249+
flagLabel = root.querySelector(".flag-label") as HTMLElement;
250+
dropdown = root.querySelector(".dropdown") as HTMLElement;
251+
input = root.querySelector(".plate-input") as HTMLInputElement;
252+
statusEl = root.querySelector(".status") as HTMLElement;
253+
254+
// aggiorna anche opts per retro-compat (se altrove li leggi da opts)
255+
(opts as any).ui = { button, flagIcon, flagLabel, dropdown, status: statusEl };
256+
(opts as any).input = input;
257+
} else {
258+
// nessun wrapper → usa ciò che arriva da fuori (comportamento attuale)
259+
input = (opts as any).input as HTMLInputElement;
260+
}
261+
262+
// guard finali
151263
if (!EuroMod?.validatePlate || !EuroMod?.getInputMask) {
152264
throw new Error("EuroMod mancante o incompleto");
153265
}
154-
if (!input) throw new Error("opts.input è richiesto");
155-
266+
if (!input) throw new Error("Devi passare `input` o `wrapper`.");
156267
// logger morbido
157268
let DBG = !!debug;
158269
const log: Logger = {
@@ -173,8 +284,40 @@ export function createEuroPlate(EuroMod: any, opts: EuroPlateOptions): EuroPlate
173284
},
174285
};
175286

176-
const IM = deps?.inputmask ?? (globalThis as any).Inputmask;
177-
if (!IM) log.warn?.("Inputmask non trovato: la maschera non sarà applicata");
287+
// ---- Inputmask fallback loader ---------------------------------------------
288+
let IM: any = deps?.inputmask ?? (globalThis as any).Inputmask;
289+
let imReady = !!IM;
290+
const wantAutoload = opts.autoLoadDeps?.inputmask !== false; // default: true
291+
292+
const imCdnUrl =
293+
opts.cdn?.inputmask ?? "https://cdn.jsdelivr.net/npm/inputmask@5.0.9/dist/inputmask.min.js";
294+
295+
// Carica UMD async se non presente e autoload attivo
296+
if (!IM && wantAutoload) {
297+
const script = document.createElement("script");
298+
script.src = imCdnUrl;
299+
script.async = true;
300+
script.crossOrigin = "anonymous";
301+
script.onload = () => {
302+
IM = (globalThis as any).Inputmask;
303+
imReady = !!IM;
304+
log.info?.("Inputmask caricato da CDN");
305+
// se siamo già su un paese fisso, prova a (ri)applicare la maschera
306+
if (IM && selected !== "AUTO") {
307+
try {
308+
applyMaskDebounced(input, selected);
309+
} catch {}
310+
}
311+
};
312+
script.onerror = () => {
313+
log.warn?.("Caricamento Inputmask fallito (CDN)");
314+
};
315+
document.head.appendChild(script);
316+
}
317+
318+
function hasIM(): boolean {
319+
return !!IM;
320+
}
178321

179322
const {
180323
supportedCountries,
@@ -249,6 +392,10 @@ export function createEuroPlate(EuroMod: any, opts: EuroPlateOptions): EuroPlate
249392
.replace(/9\{(\d+)\}/g, (_, n) => "9".repeat(+n));
250393

251394
const applyMaskDebounced = debounce((inputEl: HTMLInputElement, country: string) => {
395+
if (!hasIM()) {
396+
log.warn?.("Inputmask non disponibile: salto applyMask");
397+
return;
398+
}
252399
if (!IM) return;
253400
if (!inputEl || !country || country === "AUTO") {
254401
hardClearMaskDebounced(inputEl);

0 commit comments

Comments
 (0)