@verbb/formie-browser 1.0.3 → 1.0.5
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/CHANGELOG.md +65 -17
- package/dist/chunks/address-country-Rx6GQTND.js +68 -0
- package/dist/chunks/address-finder-yXU5yMjG.js +30 -0
- package/dist/chunks/address-state-BQ0xGV9n.js +344 -0
- package/dist/chunks/api-CmwLRq_n.js +780 -0
- package/dist/chunks/api-DMK8NSUI.js +407 -0
- package/dist/chunks/api-sPqGbOww.js +125 -0
- package/dist/chunks/async-nPFRNQ06.js +25 -0
- package/dist/chunks/bpoint-DSjqRv63.js +24 -0
- package/dist/chunks/calculations-CgVJcQ2P.js +299 -0
- package/dist/chunks/captcha-eu-CWJb64_j.js +31 -0
- package/dist/chunks/checkbox-radio-DQ0H67Tj.js +125 -0
- package/dist/chunks/chunk-3b4jIN3o.js +13 -0
- package/dist/chunks/combobox-D-2b42wn.js +1954 -0
- package/dist/chunks/conditions-CZ3FU5QO.js +412 -0
- package/dist/chunks/constants-DVcJAvc5.js +38 -0
- package/dist/chunks/country-from-ip-BwxKaBuJ.js +26 -0
- package/dist/chunks/custom-google-maps-BsaSirEZ.js +194 -0
- package/dist/chunks/custom-link-CJ1-FjEM.js +46 -0
- package/dist/chunks/custom-maps-DYW_pBid.js +161 -0
- package/dist/chunks/date-picker-5CA_-zGb.js +5311 -0
- package/dist/chunks/debug-BV0DvdHx.js +41 -0
- package/dist/chunks/dist-BF5GU3_9.js +2674 -0
- package/dist/chunks/event-names-BCI2FLD8.js +51 -0
- package/dist/chunks/eway-D3ctTbEk.js +70 -0
- package/dist/chunks/field-references.keys-58ZSTrCW.js +21 -0
- package/dist/chunks/field-references.resolver-CHwn0G0L.js +145 -0
- package/dist/chunks/file-upload-D49m8-DR.js +287 -0
- package/dist/chunks/friendly-captcha-v1-ByXzENYZ.js +31 -0
- package/dist/chunks/friendly-captcha-v2-BPdlmhQu.js +36 -0
- package/dist/chunks/friendly-challenge-8uPatWqk.js +832 -0
- package/dist/chunks/go-cardless-BwASSYSK.js +23 -0
- package/dist/chunks/google-address-OhKilPWE.js +161 -0
- package/dist/chunks/hcaptcha-CdCd9oEi.js +54 -0
- package/dist/chunks/hidden-D7_Ch-QN.js +26 -0
- package/dist/chunks/http-D-JExro7.js +27 -0
- package/dist/chunks/i18n-BY1ds1BL.js +121 -0
- package/dist/chunks/leaflet-src-j-lHNmhR.js +4585 -0
- package/dist/chunks/loqate-Cp2zvwpE.js +76 -0
- package/dist/chunks/mollie-COG-ko2D.js +23 -0
- package/dist/chunks/moneris-CMH8PlRc.js +117 -0
- package/dist/chunks/opayo-D2jsR5vh.js +113 -0
- package/dist/chunks/paddle-DxF88RfC.js +55 -0
- package/dist/chunks/paypal-b5pnwrA2.js +95 -0
- package/dist/chunks/payway-BJYu9DIR.js +55 -0
- package/dist/chunks/phone-country-D3ayr60L.js +2645 -0
- package/dist/chunks/place-kit-BGHklIZ-.js +48 -0
- package/dist/chunks/placekit-autocomplete.esm-DWVabQlq.js +1236 -0
- package/dist/chunks/recaptcha-enterprise-BSwi8IVX.js +65 -0
- package/dist/chunks/recaptcha-shared-B4zAescW.js +15 -0
- package/dist/chunks/recaptcha-v2-checkbox-CnDQmsKa.js +40 -0
- package/dist/chunks/recaptcha-v2-invisible-Cz5uIlp-.js +53 -0
- package/dist/chunks/recaptcha-v3-BtbbP111.js +27 -0
- package/dist/chunks/repeater-B5leVxZU.js +102 -0
- package/dist/chunks/rich-text-Cv8ADyk-.js +349 -0
- package/dist/chunks/scripts-6CYSUGKQ.js +24 -0
- package/dist/chunks/sdk-BhHAYgga.js +1397 -0
- package/dist/chunks/shared-Bx9s0i0P.js +100 -0
- package/dist/chunks/signature-CpgqHCEx.js +463 -0
- package/dist/chunks/snaptcha-vz1RnZ15.js +8 -0
- package/dist/chunks/square-BP8RWAKB.js +40 -0
- package/dist/chunks/stripe-MihN63uC.js +184 -0
- package/dist/chunks/styles-BfoIZwJp.js +19 -0
- package/dist/chunks/summary-D3AjxpYN.js +114 -0
- package/dist/chunks/table-C-lkQN6I.js +78 -0
- package/dist/chunks/text-limit-B09elPDB.js +125 -0
- package/dist/chunks/theme-classes-Tv7q7ToE.js +44 -0
- package/dist/chunks/turnstile-D_Pi1CKM.js +58 -0
- package/dist/chunks/utils-Vqp8i_RM.js +49401 -0
- package/dist/css/formie-theme.css +2 -0
- package/dist/css/theme/fields/_address-state.css +94 -0
- package/dist/css/theme/fields/_combobox.css +246 -0
- package/dist/css/theme/fields/_select.css +13 -0
- package/dist/index.js +2447 -39
- package/dist/js/core/create-formie-client.d.ts.map +1 -1
- package/dist/js/core/field-error-container.d.ts +4 -0
- package/dist/js/core/field-error-container.d.ts.map +1 -0
- package/dist/js/core/submit-result-state.d.ts.map +1 -1
- package/dist/js/core/submit-result-ui.d.ts.map +1 -1
- package/dist/js/core/validation-focus.d.ts +4 -0
- package/dist/js/core/validation-focus.d.ts.map +1 -0
- package/dist/js/modules/address/api.d.ts +1 -0
- package/dist/js/modules/address/api.d.ts.map +1 -1
- package/dist/js/modules/address/constants.d.ts +22 -1
- package/dist/js/modules/address/constants.d.ts.map +1 -1
- package/dist/js/modules/address/host.d.ts +2 -2
- package/dist/js/modules/address/host.d.ts.map +1 -1
- package/dist/js/modules/address/loqate.d.ts.map +1 -1
- package/dist/js/modules/fields/address-country.d.ts +3 -0
- package/dist/js/modules/fields/address-country.d.ts.map +1 -0
- package/dist/js/modules/fields/address-state.d.ts +3 -0
- package/dist/js/modules/fields/address-state.d.ts.map +1 -0
- package/dist/js/modules/fields/calculations.d.ts.map +1 -1
- package/dist/js/modules/fields/checkbox-radio.d.ts.map +1 -1
- package/dist/js/modules/fields/combobox.d.ts +17 -0
- package/dist/js/modules/fields/combobox.d.ts.map +1 -0
- package/dist/js/modules/fields/custom-google-maps.d.ts +64 -0
- package/dist/js/modules/fields/custom-google-maps.d.ts.map +1 -0
- package/dist/js/modules/fields/custom-link.d.ts +3 -0
- package/dist/js/modules/fields/custom-link.d.ts.map +1 -0
- package/dist/js/modules/fields/custom-maps.d.ts +3 -0
- package/dist/js/modules/fields/custom-maps.d.ts.map +1 -0
- package/dist/js/modules/fields/date-picker.d.ts.map +1 -1
- package/dist/js/modules/fields/file-upload.d.ts.map +1 -1
- package/dist/js/modules/fields/index.d.ts.map +1 -1
- package/dist/js/modules/fields/phone-country.d.ts.map +1 -1
- package/dist/js/modules/fields/text-limit.d.ts.map +1 -1
- package/dist/js/submit/pipeline.d.ts.map +1 -1
- package/dist/js/utils/country-from-ip.d.ts +7 -0
- package/dist/js/utils/country-from-ip.d.ts.map +1 -0
- package/dist/js/utils/field-references.d.ts +1 -0
- package/dist/js/utils/field-references.d.ts.map +1 -1
- package/dist/js/utils/field-references.row-scope.d.ts +11 -0
- package/dist/js/utils/field-references.row-scope.d.ts.map +1 -0
- package/dist/js/utils/i18n.d.ts.map +1 -1
- package/dist/js/validation/rules/email.d.ts.map +1 -1
- package/dist/js/validation/rules/number.d.ts.map +1 -1
- package/dist/js/validation/validator.d.ts.map +1 -1
- package/package.json +6 -3
- package/src/css/formie-theme.css +2 -0
- package/src/css/theme/fields/_address-state.css +94 -0
- package/src/css/theme/fields/_combobox.css +246 -0
- package/src/css/theme/fields/_select.css +13 -0
- package/src/js/core/create-formie-client.ts +27 -0
- package/src/js/core/field-error-container.ts +39 -0
- package/src/js/core/submit-result-state.ts +12 -2
- package/src/js/core/submit-result-ui.ts +7 -13
- package/src/js/core/validation-focus.ts +117 -0
- package/src/js/modules/address/api.ts +7 -0
- package/src/js/modules/address/constants.ts +82 -2
- package/src/js/modules/address/host.ts +16 -6
- package/src/js/modules/address/loqate.ts +3 -17
- package/src/js/modules/fields/address-country.ts +156 -0
- package/src/js/modules/fields/address-state.ts +946 -0
- package/src/js/modules/fields/calculations.ts +32 -0
- package/src/js/modules/fields/checkbox-radio.ts +21 -15
- package/src/js/modules/fields/combobox.ts +188 -0
- package/src/js/modules/fields/custom-google-maps.ts +416 -0
- package/src/js/modules/fields/custom-link.ts +95 -0
- package/src/js/modules/fields/custom-maps.ts +339 -0
- package/src/js/modules/fields/date-picker.ts +96 -3
- package/src/js/modules/fields/file-upload.ts +12 -9
- package/src/js/modules/fields/index.ts +6 -0
- package/src/js/modules/fields/phone-country.ts +6 -0
- package/src/js/modules/fields/text-limit.ts +87 -22
- package/src/js/submit/pipeline.ts +11 -1
- package/src/js/utils/country-from-ip.ts +54 -0
- package/src/js/utils/field-references.row-scope.ts +303 -0
- package/src/js/utils/field-references.ts +1 -0
- package/src/js/utils/i18n.ts +184 -5
- package/src/js/validation/rules/email.ts +3 -2
- package/src/js/validation/rules/match.ts +2 -2
- package/src/js/validation/rules/minmax.ts +4 -4
- package/src/js/validation/rules/number.ts +9 -5
- package/src/js/validation/rules/pattern.ts +4 -4
- package/src/js/validation/rules/required.ts +1 -1
- package/src/js/validation/rules/url.ts +1 -1
- package/src/js/validation/validator.ts +7 -7
- package/dist/chunks/address-finder-BjMuEiwF.js +0 -37
- package/dist/chunks/bpoint-KudsBCo2.js +0 -26
- package/dist/chunks/calculations-DlGSkQ3W.js +0 -147
- package/dist/chunks/captcha-eu-CterYFok.js +0 -34
- package/dist/chunks/checkbox-radio-CBvgk3hR.js +0 -136
- package/dist/chunks/conditions-DYy1-Hwt.js +0 -459
- package/dist/chunks/date-picker-BlD--zfe.js +0 -4994
- package/dist/chunks/eway-qwc-B5Us.js +0 -74
- package/dist/chunks/file-upload-CIgHrXfp.js +0 -316
- package/dist/chunks/friendly-captcha-v1-EDFac33c.js +0 -33
- package/dist/chunks/friendly-captcha-v2-BDpSvFGR.js +0 -38
- package/dist/chunks/go-cardless-DuDMVsnz.js +0 -24
- package/dist/chunks/google-address-Bmjp01pH.js +0 -181
- package/dist/chunks/hcaptcha-BTQ6An6g.js +0 -56
- package/dist/chunks/hidden-DbPdOxKb.js +0 -31
- package/dist/chunks/index-BEsfee-D.js +0 -1839
- package/dist/chunks/index-BP7Unm5Y.js +0 -863
- package/dist/chunks/index-BcqwpPNE.js +0 -4314
- package/dist/chunks/loqate-BdNGv93o.js +0 -55
- package/dist/chunks/mollie-DFOjTuGX.js +0 -24
- package/dist/chunks/moneris-BbFfl-EL.js +0 -101
- package/dist/chunks/opayo-CmgYa9ya.js +0 -122
- package/dist/chunks/paddle-BUi7w1_o.js +0 -60
- package/dist/chunks/paypal-A-ePfh6k.js +0 -100
- package/dist/chunks/payway-BqFCbhyo.js +0 -56
- package/dist/chunks/phone-country-DPMiqk4L.js +0 -3765
- package/dist/chunks/place-kit-CX7tamAg.js +0 -42
- package/dist/chunks/placekit-autocomplete.esm-LAwfbPZl.js +0 -1299
- package/dist/chunks/recaptcha-enterprise-DxF6cW95.js +0 -71
- package/dist/chunks/recaptcha-shared-DhGcbiaq.js +0 -15
- package/dist/chunks/recaptcha-v2-checkbox-R9EfbZ4k.js +0 -41
- package/dist/chunks/recaptcha-v2-invisible-DV9hswAp.js +0 -59
- package/dist/chunks/recaptcha-v3-V0xc-Edh.js +0 -28
- package/dist/chunks/repeater-DKUT6TGI.js +0 -124
- package/dist/chunks/rich-text-SH7KiaKH.js +0 -304
- package/dist/chunks/scripts-D9N2d5rN.js +0 -36
- package/dist/chunks/sdk-3SicTWby.js +0 -1559
- package/dist/chunks/shared-BmntPmnW.js +0 -80
- package/dist/chunks/signature-BA3tQsGT.js +0 -550
- package/dist/chunks/snaptcha-DV7p6J9k.js +0 -8
- package/dist/chunks/square-Bs-ndqYK.js +0 -46
- package/dist/chunks/stripe-DkZAHxtn.js +0 -202
- package/dist/chunks/styles-C3aqgtek.js +0 -21
- package/dist/chunks/summary-Dq2i9TQk.js +0 -126
- package/dist/chunks/table-Cu4WfVcw.js +0 -95
- package/dist/chunks/text-limit-2ycmB-Ae.js +0 -135
- package/dist/chunks/turnstile-CId90tRr.js +0 -59
- package/dist/chunks/utils-BntI3W4S.js +0 -6149
|
@@ -0,0 +1,339 @@
|
|
|
1
|
+
import type { FormieModuleDefinition } from '#contracts/modules';
|
|
2
|
+
import type * as Leaflet from 'leaflet';
|
|
3
|
+
import leafletCss from 'leaflet/dist/leaflet.css?inline';
|
|
4
|
+
import { getModuleFieldTarget, observeMatchingElements } from '#modules/fields/shared';
|
|
5
|
+
import { ensureModuleStyles } from '#modules/styles';
|
|
6
|
+
|
|
7
|
+
const MODULE_ID = 'custom-maps';
|
|
8
|
+
const MAPS_SELECTOR = '[data-formie-custom-maps]';
|
|
9
|
+
|
|
10
|
+
ensureModuleStyles(MODULE_ID, [leafletCss]);
|
|
11
|
+
|
|
12
|
+
type LatLng = {
|
|
13
|
+
lat: number;
|
|
14
|
+
lng: number;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
type MapsSettings = {
|
|
18
|
+
mapTiles?: string;
|
|
19
|
+
mapToken?: string | Record<string, string>;
|
|
20
|
+
geoService?: string;
|
|
21
|
+
geoToken?: string | Record<string, string>;
|
|
22
|
+
defaultLat?: number | null;
|
|
23
|
+
defaultLng?: number | null;
|
|
24
|
+
defaultZoom?: number | null;
|
|
25
|
+
minZoom?: number | null;
|
|
26
|
+
maxZoom?: number | null;
|
|
27
|
+
country?: string | null;
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
type MapsElements = {
|
|
31
|
+
root: HTMLElement;
|
|
32
|
+
canvas: HTMLElement | null;
|
|
33
|
+
searchInput: HTMLInputElement | null;
|
|
34
|
+
addressInput: HTMLInputElement | null;
|
|
35
|
+
latInput: HTMLInputElement | null;
|
|
36
|
+
lngInput: HTMLInputElement | null;
|
|
37
|
+
zoomInput: HTMLInputElement | null;
|
|
38
|
+
partsInput: HTMLInputElement | null;
|
|
39
|
+
currentLocationButton: HTMLButtonElement | null;
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
type NominatimResult = {
|
|
43
|
+
display_name?: string;
|
|
44
|
+
lat?: string;
|
|
45
|
+
lon?: string;
|
|
46
|
+
address?: Record<string, string | undefined>;
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
let leafletPromise: Promise<typeof Leaflet> | null = null;
|
|
50
|
+
|
|
51
|
+
function isCustomMaps(element: Element): element is HTMLElement {
|
|
52
|
+
return element instanceof HTMLElement && element.matches(MAPS_SELECTOR);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function parseSettings(root: HTMLElement): MapsSettings {
|
|
56
|
+
const raw = root.getAttribute('data-formie-custom-maps-settings');
|
|
57
|
+
|
|
58
|
+
if (!raw) {
|
|
59
|
+
return {};
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
try {
|
|
63
|
+
return JSON.parse(raw) as MapsSettings;
|
|
64
|
+
} catch {
|
|
65
|
+
return {};
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function findElements(root: HTMLElement): MapsElements {
|
|
70
|
+
return {
|
|
71
|
+
root,
|
|
72
|
+
canvas: root.querySelector<HTMLElement>('[data-formie-custom-maps-canvas]'),
|
|
73
|
+
searchInput: root.querySelector<HTMLInputElement>('[data-formie-custom-maps-search]'),
|
|
74
|
+
addressInput: root.querySelector<HTMLInputElement>('[data-formie-custom-map-address]'),
|
|
75
|
+
latInput: root.querySelector<HTMLInputElement>('[data-formie-custom-map-lat]'),
|
|
76
|
+
lngInput: root.querySelector<HTMLInputElement>('[data-formie-custom-map-lng]'),
|
|
77
|
+
zoomInput: root.querySelector<HTMLInputElement>('[data-formie-custom-map-zoom]'),
|
|
78
|
+
partsInput: root.querySelector<HTMLInputElement>('[data-formie-custom-maps-parts]'),
|
|
79
|
+
currentLocationButton: root.querySelector<HTMLButtonElement>('[data-formie-custom-maps-current-location]'),
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function toNumber(value: string | number | null | undefined): number | null {
|
|
84
|
+
if (value === null || value === undefined || value === '') {
|
|
85
|
+
return null;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const number = Number(value);
|
|
89
|
+
|
|
90
|
+
return Number.isFinite(number) ? number : null;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function getCurrentLatLng(elements: MapsElements, settings: MapsSettings): LatLng {
|
|
94
|
+
return {
|
|
95
|
+
lat: toNumber(elements.latInput?.value) ?? settings.defaultLat ?? 51.272154,
|
|
96
|
+
lng: toNumber(elements.lngInput?.value) ?? settings.defaultLng ?? 0.514951,
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function getCurrentZoom(elements: MapsElements, settings: MapsSettings): number {
|
|
101
|
+
return toNumber(elements.zoomInput?.value) ?? settings.defaultZoom ?? 15;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function setValue(input: HTMLInputElement | null, value: string | number | null | undefined): void {
|
|
105
|
+
if (!input) {
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
input.value = value === null || value === undefined ? '' : String(value);
|
|
110
|
+
input.dispatchEvent(new Event('input', { bubbles: true }));
|
|
111
|
+
input.dispatchEvent(new Event('change', { bubbles: true }));
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function syncCoordinates(elements: MapsElements, latLng: LatLng, zoom?: number): void {
|
|
115
|
+
setValue(elements.latInput, latLng.lat);
|
|
116
|
+
setValue(elements.lngInput, latLng.lng);
|
|
117
|
+
|
|
118
|
+
if (zoom !== undefined) {
|
|
119
|
+
setValue(elements.zoomInput, zoom);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function loadLeaflet(): Promise<typeof Leaflet> {
|
|
124
|
+
if (!leafletPromise) {
|
|
125
|
+
leafletPromise = import('leaflet');
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
return leafletPromise;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function getRasterTileUrl(settings: MapsSettings): string {
|
|
132
|
+
switch (settings.mapTiles) {
|
|
133
|
+
case 'openstreetmap':
|
|
134
|
+
case 'wikimedia':
|
|
135
|
+
return 'https://a.tile.openstreetmap.org/{z}/{x}/{y}.png';
|
|
136
|
+
case 'carto.light_all':
|
|
137
|
+
return 'https://a.basemaps.cartocdn.com/light_all/{z}/{x}/{y}.png';
|
|
138
|
+
case 'carto.dark_all':
|
|
139
|
+
return 'https://a.basemaps.cartocdn.com/dark_all/{z}/{x}/{y}.png';
|
|
140
|
+
case 'carto.rastertiles/voyager':
|
|
141
|
+
default:
|
|
142
|
+
return 'https://a.basemaps.cartocdn.com/rastertiles/voyager/{z}/{x}/{y}.png';
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function mapNominatimParts(address: Record<string, string | undefined> | undefined): Record<string, string> {
|
|
147
|
+
if (!address) {
|
|
148
|
+
return {};
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
return {
|
|
152
|
+
number: address.house_number || '',
|
|
153
|
+
address: address.road || address.pedestrian || address.footway || address.path || address.neighbourhood || '',
|
|
154
|
+
city: address.city || address.town || address.village || address.suburb || '',
|
|
155
|
+
postcode: address.postcode || '',
|
|
156
|
+
county: address.county || '',
|
|
157
|
+
state: address.state || address.state_district || '',
|
|
158
|
+
country: address.country || '',
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
async function searchNominatim(query: string, settings: MapsSettings): Promise<{ latLng: LatLng; address: string; parts: Record<string, string> } | null> {
|
|
163
|
+
const params = new URLSearchParams({
|
|
164
|
+
format: 'jsonv2',
|
|
165
|
+
limit: '1',
|
|
166
|
+
addressdetails: '1',
|
|
167
|
+
q: query,
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
if (settings.country) {
|
|
171
|
+
params.set('countrycodes', settings.country.toLowerCase());
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
const response = await fetch(`https://nominatim.openstreetmap.org/search?${params.toString()}`);
|
|
175
|
+
|
|
176
|
+
if (!response.ok) {
|
|
177
|
+
return null;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
const results = await response.json() as NominatimResult[];
|
|
181
|
+
const result = results[0];
|
|
182
|
+
const lat = toNumber(result?.lat);
|
|
183
|
+
const lng = toNumber(result?.lon);
|
|
184
|
+
|
|
185
|
+
if (!result || lat === null || lng === null) {
|
|
186
|
+
return null;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
return {
|
|
190
|
+
latLng: { lat, lng },
|
|
191
|
+
address: result.display_name || query,
|
|
192
|
+
parts: mapNominatimParts(result.address),
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
function applySearchResult(elements: MapsElements, result: { latLng: LatLng; address: string; parts: Record<string, string> }, zoom: number): void {
|
|
197
|
+
setValue(elements.addressInput, result.address);
|
|
198
|
+
setValue(elements.searchInput, result.address);
|
|
199
|
+
setValue(elements.partsInput, JSON.stringify(result.parts));
|
|
200
|
+
syncCoordinates(elements, result.latLng, zoom);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
async function initLeafletMap(elements: MapsElements, settings: MapsSettings): Promise<() => void> {
|
|
204
|
+
if (!elements.canvas) {
|
|
205
|
+
return () => {};
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
const L = await loadLeaflet();
|
|
209
|
+
const initialLatLng = getCurrentLatLng(elements, settings);
|
|
210
|
+
const initialZoom = getCurrentZoom(elements, settings);
|
|
211
|
+
const map = L.map(elements.canvas, {
|
|
212
|
+
minZoom: settings.minZoom ?? 3,
|
|
213
|
+
maxZoom: settings.maxZoom ?? 18,
|
|
214
|
+
}).setView([initialLatLng.lat, initialLatLng.lng], initialZoom);
|
|
215
|
+
const marker = L.marker([initialLatLng.lat, initialLatLng.lng], {
|
|
216
|
+
draggable: true,
|
|
217
|
+
}).addTo(map);
|
|
218
|
+
|
|
219
|
+
L.tileLayer(getRasterTileUrl(settings), {
|
|
220
|
+
maxZoom: settings.maxZoom ?? 18,
|
|
221
|
+
attribution: '© OpenStreetMap contributors, © CARTO',
|
|
222
|
+
}).addTo(map);
|
|
223
|
+
|
|
224
|
+
map.on('click', (event) => {
|
|
225
|
+
marker.setLatLng([event.latlng.lat, event.latlng.lng]);
|
|
226
|
+
syncCoordinates(elements, event.latlng, map.getZoom());
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
marker.on('dragend', (event) => {
|
|
230
|
+
const latLng = event.target.getLatLng();
|
|
231
|
+
syncCoordinates(elements, latLng, map.getZoom());
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
const submitSearch = async(): Promise<void> => {
|
|
235
|
+
const query = elements.searchInput?.value.trim();
|
|
236
|
+
|
|
237
|
+
if (!query) {
|
|
238
|
+
return;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
const result = await searchNominatim(query, settings);
|
|
242
|
+
|
|
243
|
+
if (!result) {
|
|
244
|
+
return;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
const zoom = getCurrentZoom(elements, settings);
|
|
248
|
+
applySearchResult(elements, result, zoom);
|
|
249
|
+
map.setView([result.latLng.lat, result.latLng.lng], zoom);
|
|
250
|
+
marker.setLatLng([result.latLng.lat, result.latLng.lng]);
|
|
251
|
+
};
|
|
252
|
+
|
|
253
|
+
const onSearchKeydown = (event: KeyboardEvent): void => {
|
|
254
|
+
if (event.key !== 'Enter') {
|
|
255
|
+
return;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
event.preventDefault();
|
|
259
|
+
void submitSearch();
|
|
260
|
+
};
|
|
261
|
+
|
|
262
|
+
const onSearchBlur = (): void => {
|
|
263
|
+
void submitSearch();
|
|
264
|
+
};
|
|
265
|
+
|
|
266
|
+
elements.searchInput?.addEventListener('keydown', onSearchKeydown);
|
|
267
|
+
elements.searchInput?.addEventListener('blur', onSearchBlur);
|
|
268
|
+
|
|
269
|
+
const onCurrentLocationClick = (): void => {
|
|
270
|
+
navigator.geolocation?.getCurrentPosition((position) => {
|
|
271
|
+
const latLng = {
|
|
272
|
+
lat: position.coords.latitude,
|
|
273
|
+
lng: position.coords.longitude,
|
|
274
|
+
};
|
|
275
|
+
const zoom = getCurrentZoom(elements, settings);
|
|
276
|
+
|
|
277
|
+
syncCoordinates(elements, latLng, zoom);
|
|
278
|
+
map.setView([latLng.lat, latLng.lng], zoom);
|
|
279
|
+
marker.setLatLng([latLng.lat, latLng.lng]);
|
|
280
|
+
});
|
|
281
|
+
};
|
|
282
|
+
|
|
283
|
+
elements.currentLocationButton?.addEventListener('click', onCurrentLocationClick);
|
|
284
|
+
|
|
285
|
+
return () => {
|
|
286
|
+
elements.searchInput?.removeEventListener('keydown', onSearchKeydown);
|
|
287
|
+
elements.searchInput?.removeEventListener('blur', onSearchBlur);
|
|
288
|
+
elements.currentLocationButton?.removeEventListener('click', onCurrentLocationClick);
|
|
289
|
+
map.remove();
|
|
290
|
+
};
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
function initCustomMaps(root: HTMLElement): () => void {
|
|
294
|
+
const elements = findElements(root);
|
|
295
|
+
const settings = parseSettings(root);
|
|
296
|
+
let cleanup: (() => void) | null = null;
|
|
297
|
+
let destroyed = false;
|
|
298
|
+
|
|
299
|
+
if (root.getAttribute('data-formie-custom-map-hide-map') !== '1') {
|
|
300
|
+
void initLeafletMap(elements, settings).then((destroy) => {
|
|
301
|
+
if (destroyed) {
|
|
302
|
+
destroy();
|
|
303
|
+
return;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
cleanup = destroy;
|
|
307
|
+
});
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
return () => {
|
|
311
|
+
destroyed = true;
|
|
312
|
+
cleanup?.();
|
|
313
|
+
};
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
export const customMapsModule: FormieModuleDefinition = {
|
|
317
|
+
id: MODULE_ID,
|
|
318
|
+
kind: 'field',
|
|
319
|
+
match: ({ target }) => {
|
|
320
|
+
return target instanceof Element && (
|
|
321
|
+
target.matches(MAPS_SELECTOR) ||
|
|
322
|
+
!!target.querySelector(MAPS_SELECTOR)
|
|
323
|
+
);
|
|
324
|
+
},
|
|
325
|
+
setup: async(ctx) => {
|
|
326
|
+
const field = getModuleFieldTarget(ctx);
|
|
327
|
+
const root = field || ctx.target;
|
|
328
|
+
|
|
329
|
+
if (!(root instanceof Element)) {
|
|
330
|
+
return;
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
const cleanup = observeMatchingElements(root, MAPS_SELECTOR, isCustomMaps, initCustomMaps);
|
|
334
|
+
|
|
335
|
+
return {
|
|
336
|
+
destroy: cleanup,
|
|
337
|
+
};
|
|
338
|
+
},
|
|
339
|
+
};
|
|
@@ -8,18 +8,20 @@ import { ensureModuleStyles } from '#modules/styles';
|
|
|
8
8
|
import { createDebug } from '#utils/debug';
|
|
9
9
|
|
|
10
10
|
const INPUT_SELECTOR = 'input[data-formie-date-datepicker-input]';
|
|
11
|
+
const RANGE_START_SELECTOR = 'input[data-formie-date-range-start-input]';
|
|
12
|
+
const RANGE_END_SELECTOR = 'input[data-formie-date-range-end-input]';
|
|
11
13
|
const MODULE_ID = 'date-picker';
|
|
12
14
|
const debug = createDebug('fields', 'date-picker');
|
|
13
15
|
|
|
14
|
-
ensureModuleStyles(MODULE_ID, [flatpickrCss]);
|
|
15
|
-
|
|
16
16
|
type DatePickerOptions = {
|
|
17
|
+
includeFlatpickrCss?: boolean;
|
|
17
18
|
datePickerOptions?: Array<{ label?: string; value?: unknown }>;
|
|
18
19
|
dateFormat?: string;
|
|
19
20
|
timeFormat?: string;
|
|
20
21
|
getIsDate?: boolean;
|
|
21
22
|
getIsTime?: boolean;
|
|
22
23
|
getIsDateTime?: boolean;
|
|
24
|
+
collectMode?: string;
|
|
23
25
|
locale?: string;
|
|
24
26
|
minDate?: string | null;
|
|
25
27
|
maxDate?: string | null;
|
|
@@ -34,6 +36,11 @@ type FlatpickrInput = HTMLInputElement & {
|
|
|
34
36
|
_formieFlatpickr?: FlatpickrInstanceLike;
|
|
35
37
|
};
|
|
36
38
|
|
|
39
|
+
type FlatpickrChangeInstance = {
|
|
40
|
+
input: HTMLInputElement;
|
|
41
|
+
altInput?: HTMLInputElement;
|
|
42
|
+
};
|
|
43
|
+
|
|
37
44
|
function attributesPlugin() {
|
|
38
45
|
return (instance: { input: HTMLInputElement; altInput?: HTMLInputElement | undefined; loadedPlugins: string[] }) => {
|
|
39
46
|
return {
|
|
@@ -168,9 +175,61 @@ function getCustomOptions(options: DatePickerOptions): Record<string, unknown> {
|
|
|
168
175
|
return result;
|
|
169
176
|
}
|
|
170
177
|
|
|
178
|
+
function pad(value: number): string {
|
|
179
|
+
return String(value).padStart(2, '0');
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
function formatSubmissionDate(date: Date, options: DatePickerOptions): string {
|
|
183
|
+
const year = date.getFullYear();
|
|
184
|
+
const month = pad(date.getMonth() + 1);
|
|
185
|
+
const day = pad(date.getDate());
|
|
186
|
+
|
|
187
|
+
if (!options.getIsTime && !options.getIsDateTime) {
|
|
188
|
+
return `${year}-${month}-${day}`;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
const hours = pad(date.getHours());
|
|
192
|
+
const minutes = pad(date.getMinutes());
|
|
193
|
+
const seconds = pad(date.getSeconds());
|
|
194
|
+
|
|
195
|
+
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
function parseSubmissionDate(value: string): Date | null {
|
|
199
|
+
const parsed = new Date(value);
|
|
200
|
+
|
|
201
|
+
if (Number.isNaN(parsed.getTime())) {
|
|
202
|
+
return null;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
return parsed;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
function syncRangeHiddenInputs(
|
|
209
|
+
selectedDates: Date[],
|
|
210
|
+
startInput: HTMLInputElement | null,
|
|
211
|
+
endInput: HTMLInputElement | null,
|
|
212
|
+
options: DatePickerOptions,
|
|
213
|
+
): void {
|
|
214
|
+
if (startInput) {
|
|
215
|
+
startInput.value = selectedDates[0] ? formatSubmissionDate(selectedDates[0], options) : '';
|
|
216
|
+
startInput.dispatchEvent(new Event('input', { bubbles: true }));
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
if (endInput) {
|
|
220
|
+
endInput.value = selectedDates[1] ? formatSubmissionDate(selectedDates[1], options) : '';
|
|
221
|
+
endInput.dispatchEvent(new Event('input', { bubbles: true }));
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
171
225
|
function initDatePicker(input: FlatpickrInput, options: DatePickerOptions): () => void {
|
|
172
226
|
input._formieFlatpickr?.destroy();
|
|
173
227
|
|
|
228
|
+
const fieldContainer = input.closest('[data-formie-field-handle]');
|
|
229
|
+
const isRange = options.collectMode === 'range' || input.hasAttribute('data-formie-date-range-input');
|
|
230
|
+
const startInput = fieldContainer?.querySelector(RANGE_START_SELECTOR);
|
|
231
|
+
const endInput = fieldContainer?.querySelector(RANGE_END_SELECTOR);
|
|
232
|
+
|
|
174
233
|
const defaultOptions: Record<string, unknown> = {
|
|
175
234
|
disableMobile: true,
|
|
176
235
|
allowInput: true,
|
|
@@ -183,7 +242,16 @@ function initDatePicker(input: FlatpickrInput, options: DatePickerOptions): () =
|
|
|
183
242
|
maxDate: normalizeOffsetDate(options.maxDate, 'max'),
|
|
184
243
|
plugins: [attributesPlugin()],
|
|
185
244
|
locale: getLocale(options.locale),
|
|
186
|
-
onChange: (
|
|
245
|
+
onChange: (selectedDates: Date[], _dateStr: string, instance: FlatpickrChangeInstance) => {
|
|
246
|
+
if (isRange) {
|
|
247
|
+
syncRangeHiddenInputs(
|
|
248
|
+
selectedDates,
|
|
249
|
+
startInput instanceof HTMLInputElement ? startInput : null,
|
|
250
|
+
endInput instanceof HTMLInputElement ? endInput : null,
|
|
251
|
+
options,
|
|
252
|
+
);
|
|
253
|
+
}
|
|
254
|
+
|
|
187
255
|
// Mirror changes back through bubbling input events so validation,
|
|
188
256
|
// conditions, and calculations respond to picker-driven updates.
|
|
189
257
|
instance.input.dispatchEvent(new Event('input', { bubbles: true }));
|
|
@@ -204,11 +272,30 @@ function initDatePicker(input: FlatpickrInput, options: DatePickerOptions): () =
|
|
|
204
272
|
defaultOptions.noCalendar = true;
|
|
205
273
|
}
|
|
206
274
|
|
|
275
|
+
if (isRange) {
|
|
276
|
+
defaultOptions.mode = 'range';
|
|
277
|
+
|
|
278
|
+
const startDate = startInput instanceof HTMLInputElement && startInput.value
|
|
279
|
+
? parseSubmissionDate(startInput.value)
|
|
280
|
+
: null;
|
|
281
|
+
const endDate = endInput instanceof HTMLInputElement && endInput.value
|
|
282
|
+
? parseSubmissionDate(endInput.value)
|
|
283
|
+
: null;
|
|
284
|
+
|
|
285
|
+
if (startDate) {
|
|
286
|
+
defaultOptions.defaultDate = endDate ? [startDate, endDate] : [startDate];
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
|
|
207
290
|
const mergedOptions = {
|
|
208
291
|
...defaultOptions,
|
|
209
292
|
...getCustomOptions(options),
|
|
210
293
|
};
|
|
211
294
|
|
|
295
|
+
if (isRange) {
|
|
296
|
+
mergedOptions.mode = 'range';
|
|
297
|
+
}
|
|
298
|
+
|
|
212
299
|
dispatchFieldEvent(input, MODULE_ID, 'before-init', {
|
|
213
300
|
datepicker: input,
|
|
214
301
|
options: mergedOptions,
|
|
@@ -218,6 +305,7 @@ function initDatePicker(input: FlatpickrInput, options: DatePickerOptions): () =
|
|
|
218
305
|
input._formieFlatpickr = instance;
|
|
219
306
|
debug.log('Initialized.', {
|
|
220
307
|
inputName: input.name,
|
|
308
|
+
isRange,
|
|
221
309
|
});
|
|
222
310
|
|
|
223
311
|
dispatchFieldEvent(input, MODULE_ID, 'after-init', {
|
|
@@ -242,6 +330,11 @@ export const datePickerModule: FormieModuleDefinition = {
|
|
|
242
330
|
},
|
|
243
331
|
setup: async (ctx) => {
|
|
244
332
|
const options = (ctx.options || {}) as DatePickerOptions;
|
|
333
|
+
|
|
334
|
+
if (options.includeFlatpickrCss !== false) {
|
|
335
|
+
ensureModuleStyles(MODULE_ID, [flatpickrCss]);
|
|
336
|
+
}
|
|
337
|
+
|
|
245
338
|
const fields = getModuleFieldContainers(ctx);
|
|
246
339
|
const cleanups = fields.map((field) => {
|
|
247
340
|
const input = field.querySelector(INPUT_SELECTOR);
|
|
@@ -239,9 +239,10 @@ function registerValidators(form: HTMLFormElement | null): void {
|
|
|
239
239
|
|
|
240
240
|
return input.files.length <= limit;
|
|
241
241
|
}, ({ input, t }) => {
|
|
242
|
-
return
|
|
243
|
-
|
|
244
|
-
|
|
242
|
+
return input.getAttribute('data-formie-validation-max-files-message')
|
|
243
|
+
?? t('Choose up to {files} files.', {
|
|
244
|
+
files: input.getAttribute('data-formie-file-limit') || '',
|
|
245
|
+
});
|
|
245
246
|
});
|
|
246
247
|
|
|
247
248
|
validator.addValidator('fileSizeMinLimit', ({ input }) => {
|
|
@@ -256,9 +257,10 @@ function registerValidators(form: HTMLFormElement | null): void {
|
|
|
256
257
|
return file.size >= sizeBytes;
|
|
257
258
|
});
|
|
258
259
|
}, ({ input, t }) => {
|
|
259
|
-
return
|
|
260
|
-
|
|
261
|
-
|
|
260
|
+
return input.getAttribute('data-formie-validation-min-file-size-message')
|
|
261
|
+
?? t('File must be larger than {filesize} MB.', {
|
|
262
|
+
filesize: input.getAttribute('data-formie-size-min-limit') || '',
|
|
263
|
+
});
|
|
262
264
|
});
|
|
263
265
|
|
|
264
266
|
validator.addValidator('fileSizeMaxLimit', ({ input }) => {
|
|
@@ -273,9 +275,10 @@ function registerValidators(form: HTMLFormElement | null): void {
|
|
|
273
275
|
return file.size <= sizeBytes;
|
|
274
276
|
});
|
|
275
277
|
}, ({ input, t }) => {
|
|
276
|
-
return
|
|
277
|
-
|
|
278
|
-
|
|
278
|
+
return input.getAttribute('data-formie-validation-max-file-size-message')
|
|
279
|
+
?? t('File must be smaller than {filesize} MB.', {
|
|
280
|
+
filesize: input.getAttribute('data-formie-size-max-limit') || '',
|
|
281
|
+
});
|
|
279
282
|
});
|
|
280
283
|
});
|
|
281
284
|
}
|
|
@@ -5,11 +5,17 @@ export const builtinFieldModuleLoaders: Record<string, () => Promise<FormieModul
|
|
|
5
5
|
// truth for lazy-loading first-party field enhancements.
|
|
6
6
|
'calculations': () => import('#modules/fields/calculations').then((module) => module.calculationsModule),
|
|
7
7
|
'checkbox-radio': () => import('#modules/fields/checkbox-radio').then((module) => module.checkboxRadioModule),
|
|
8
|
+
'combobox': () => import('#modules/fields/combobox').then((module) => module.comboboxModule),
|
|
8
9
|
'conditions': () => import('#modules/fields/conditions').then((module) => module.conditionsModule),
|
|
10
|
+
'custom-google-maps': () => import('#modules/fields/custom-google-maps').then((module) => module.customGoogleMapsModule),
|
|
11
|
+
'custom-link': () => import('#modules/fields/custom-link').then((module) => module.customLinkModule),
|
|
12
|
+
'custom-maps': () => import('#modules/fields/custom-maps').then((module) => module.customMapsModule),
|
|
9
13
|
'date-picker': () => import('#modules/fields/date-picker').then((module) => module.datePickerModule),
|
|
10
14
|
'file-upload': () => import('#modules/fields/file-upload').then((module) => module.fileUploadModule),
|
|
11
15
|
'hidden': () => import('#modules/fields/hidden').then((module) => module.hiddenModule),
|
|
12
16
|
'phone-country': () => import('#modules/fields/phone-country').then((module) => module.phoneCountryModule),
|
|
17
|
+
'address-country': () => import('#modules/fields/address-country').then((module) => module.addressCountryModule),
|
|
18
|
+
'address-state': () => import('#modules/fields/address-state').then((module) => module.addressStateModule),
|
|
13
19
|
'repeater': () => import('#modules/fields/repeater').then((module) => module.repeaterModule),
|
|
14
20
|
'rich-text': () => import('#modules/fields/rich-text').then((module) => module.richTextModule),
|
|
15
21
|
'signature': () => import('#modules/fields/signature').then((module) => module.signatureModule),
|
|
@@ -4,6 +4,7 @@ import intlTelInputCss from 'intl-tel-input/styles?inline';
|
|
|
4
4
|
import type { FormieModuleDefinition } from '#contracts/modules';
|
|
5
5
|
import { dispatchFieldEvent, getModuleFieldContainers, releaseFormValidators, retainFormValidators } from '#modules/fields/shared';
|
|
6
6
|
import { ensureModuleStyles } from '#modules/styles';
|
|
7
|
+
import { createGeoIpLookup } from '#utils/country-from-ip';
|
|
7
8
|
import { createDebug } from '#utils/debug';
|
|
8
9
|
|
|
9
10
|
const PHONE_SELECTOR = 'input[type="tel"][data-formie-phone-input]';
|
|
@@ -18,6 +19,8 @@ ensureModuleStyles(MODULE_ID, [intlTelInputCss]);
|
|
|
18
19
|
type PhoneCountryOptions = {
|
|
19
20
|
countryDefaultValue?: string;
|
|
20
21
|
countryAllowed?: string[];
|
|
22
|
+
countryPreselectFromIp?: boolean;
|
|
23
|
+
countryFromIpAction?: string;
|
|
21
24
|
language?: string;
|
|
22
25
|
};
|
|
23
26
|
|
|
@@ -111,6 +114,9 @@ function buildOptions(options: PhoneCountryOptions): Record<string, unknown> {
|
|
|
111
114
|
|
|
112
115
|
if (options.countryDefaultValue) {
|
|
113
116
|
intlOptions.initialCountry = options.countryDefaultValue.toLowerCase();
|
|
117
|
+
} else if (options.countryPreselectFromIp) {
|
|
118
|
+
intlOptions.initialCountry = 'auto';
|
|
119
|
+
intlOptions.geoIpLookup = createGeoIpLookup(options.countryFromIpAction);
|
|
114
120
|
}
|
|
115
121
|
|
|
116
122
|
if (Array.isArray(intlOptions.onlyCountries) && typeof intlOptions.initialCountry === 'string') {
|