@stubber/form-fields 2.1.0 → 2.1.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.
@@ -5,10 +5,24 @@ export const map_field_param_spec = {
5
5
  spec: {
6
6
  fields: {
7
7
  hide_geolocate_button: {
8
- fieldtype: "checkbox"
8
+ fieldtype: "checkbox",
9
+ without_value_details: true
9
10
  },
10
11
  auto_set_location: {
11
- fieldtype: "checkbox"
12
+ fieldtype: "checkbox",
13
+ without_value_details: true
14
+ },
15
+ address_mode: {
16
+ fieldtype: "select",
17
+ without_value_details: true,
18
+ params: {
19
+ options: [
20
+ { label: "None", value: "none" },
21
+ { label: "Hidden", value: "hidden" },
22
+ { label: "Readonly", value: "readonly" },
23
+ { label: "Editable", value: "editable" }
24
+ ]
25
+ }
12
26
  }
13
27
  }
14
28
  }
@@ -17,10 +31,14 @@ export const map_field_param_spec = {
17
31
 
18
32
  <script>import { controls, Geocoder, Map, Marker } from "@beyonk/svelte-mapbox";
19
33
  import FieldLabel from "../FieldLabel.svelte";
34
+ import { mapValues, omit } from "lodash-es";
35
+ import { Input } from "@stubber/ui/input";
36
+ import { Label } from "@stubber/ui/label";
20
37
  const { GeolocateControl } = controls;
21
38
  export let fieldStore;
22
39
  const initial_value = $fieldStore.value;
23
40
  let params = $fieldStore.params;
41
+ let reverse_geocode_enabled = params?.address_mode && params?.address_mode !== "none";
24
42
  let hide_geolocate_button = params?.hide_geolocate_button || false;
25
43
  let auto_set_location = params?.auto_set_location || false;
26
44
  let dependencies = $fieldStore?.formDependencies;
@@ -58,6 +76,39 @@ function setValueCoords(lat, lng, force = false) {
58
76
  lng: lng ?? null,
59
77
  latlng: `${lat ?? null},${lng ?? null}`
60
78
  };
79
+ reverse_geocode(lat, lng);
80
+ }
81
+ }
82
+ async function reverse_geocode(lat, lng) {
83
+ if (!mapboxAccessToken || !reverse_geocode_enabled) return;
84
+ try {
85
+ const response = await fetch(
86
+ `https://api.mapbox.com/search/geocode/v6/reverse?longitude=${lng}&latitude=${lat}&access_token=${mapboxAccessToken}`
87
+ );
88
+ const data = await response.json();
89
+ if (data?.features?.length > 0) {
90
+ const feature = data?.features[0];
91
+ if (feature?.properties?.feature_type === "address") {
92
+ const full_address = feature?.properties?.full_address;
93
+ const cleaned_context = mapValues(
94
+ feature?.properties?.context,
95
+ (v) => omit(v, ["mapbox_id", "wikidata_id", "alternate"])
96
+ );
97
+ const address = {};
98
+ if (full_address) {
99
+ address.full_address = full_address;
100
+ }
101
+ if (cleaned_context) {
102
+ address["context"] = cleaned_context;
103
+ }
104
+ $fieldStore.value = {
105
+ ...$fieldStore.value,
106
+ address
107
+ };
108
+ }
109
+ }
110
+ } catch (error) {
111
+ console.error("reverse geocode error", error);
61
112
  }
62
113
  }
63
114
  function resetValues() {
@@ -79,22 +130,34 @@ function handle_geolocate(o) {
79
130
  const coords = o?.detail?.coords || {};
80
131
  setValueCoords(coords.latitude, coords.longitude);
81
132
  }
133
+ function handle_address_change(e) {
134
+ const new_address = e?.target?.value || "";
135
+ if (new_address && $fieldStore.value.address.full_address !== new_address) {
136
+ $fieldStore.value = {
137
+ ...$fieldStore.value,
138
+ address: {
139
+ ...$fieldStore.value.address,
140
+ full_address: new_address
141
+ }
142
+ };
143
+ }
144
+ }
82
145
  </script>
83
146
 
84
147
  <FieldLabel {fieldStore} />
85
148
  {#if mapboxAccessToken}
86
- <div class="flex flex-col w-full">
149
+ <div class="flex w-full flex-col">
87
150
  <div>
88
151
  <button
89
152
  type="button"
90
153
  on:click={resetValues}
91
- class="text-sm text-surface-300 hover:underline hover:text-surface-500"
154
+ class="text-sm text-surface-300 hover:text-surface-500 hover:underline"
92
155
  >
93
156
  <i class="fa-regular fa-undo" /> Reset
94
157
  </button>
95
158
  </div>
96
159
  <div class="relative mt-2 rounded-md">
97
- <div class="w-full h-72">
160
+ <div class="h-72 w-full">
98
161
  <Map
99
162
  bind:center
100
163
  zoom={14}
@@ -114,31 +177,60 @@ function handle_geolocate(o) {
114
177
  <GeolocateControl position="top-right" on:geolocate={handle_geolocate} />
115
178
  {/if}
116
179
  {#if browserLocation}
117
- <Marker lat={browserLocation.lat} lng={browserLocation.lng}>
118
- <div class="relative w-0">
119
- <div
120
- class="bg-surface-0 p-4 rounded-xl rounded-bl-none shadow-2xl border absolute bottom-0 whitespace-nowrap"
121
- >
122
- <i class="fa-solid fa-user text-4xl" />
123
- </div>
180
+ <Marker
181
+ lat={browserLocation.lat}
182
+ lng={browserLocation.lng}
183
+ markerOptions={{ anchor: "center" }}
184
+ >
185
+ <div class="relative flex flex-col items-center">
186
+ <span class="fa-stack fa-2x">
187
+ <i
188
+ class="fa-solid fa-circle fa-stack-2x text-[1.3em] leading-[inherit] text-white"
189
+ />
190
+ <i class="fa-solid fa-user fa-stack-1x text-blue-600" />
191
+ </span>
124
192
  </div>
125
193
  </Marker>
126
194
  {/if}
127
195
  {#if $fieldStore.value?.lat && $fieldStore.value?.lng}
128
- <Marker lat={$fieldStore.value.lat} lng={$fieldStore.value.lng} popup={false}>
129
- <div class="relative w-0">
196
+ <Marker
197
+ lat={$fieldStore.value.lat}
198
+ lng={$fieldStore.value.lng}
199
+ popup={false}
200
+ markerOptions={{ anchor: "bottom" }}
201
+ >
202
+ <div class="relative flex flex-col items-center">
130
203
  <div
131
- class="bg-surface-0 p-4 rounded-xl rounded-bl-none shadow-2xl border absolute bottom-0 whitespace-nowrap"
204
+ class="rounded-xl bg-white px-2 py-1 text-sm font-medium text-slate-800 shadow-lg ring-1 ring-black/20"
132
205
  >
133
- lat: {$fieldStore.value.lat?.toString()?.substring(0, 6)} <br />
134
- lng: {$fieldStore.value.lng?.toString()?.substring(0, 6)}
206
+ <div>lat: {$fieldStore.value.lat?.toString()?.substring(0, 7)}</div>
207
+ <div>lng: {$fieldStore.value.lng?.toString()?.substring(0, 7)}</div>
135
208
  </div>
209
+
210
+ <span class="fa-stack fa-2x h-[1.5em]">
211
+ <i
212
+ class="fa-solid fa-location-pin fa-stack-2x text-[1.2em] leading-[inherit] text-white"
213
+ />
214
+ <i class="fa-solid fa-location-dot fa-stack-1x text-green-600" />
215
+ </span>
136
216
  </div>
137
217
  </Marker>
138
218
  {/if}
139
219
  </Map>
140
220
  </div>
141
221
  </div>
222
+ {#if params?.address_mode === "editable"}
223
+ <Input
224
+ type="text"
225
+ class="mt-1"
226
+ value={$fieldStore.value?.address?.full_address || ".."}
227
+ on:change={handle_address_change}
228
+ />
229
+ {:else if params?.address_mode === "readonly"}
230
+ <Label class="mt-1 block text-sm font-medium text-surface-700"
231
+ >{$fieldStore.value?.address?.full_address || ".."}</Label
232
+ >
233
+ {/if}
142
234
  </div>
143
235
  {/if}
144
236
  <FieldMessage {fieldStore} />
@@ -4,6 +4,7 @@ import { type IBaseField, type IBuiltField, type IInitialForm } from "../interfa
4
4
  export interface IMapFieldParams {
5
5
  hide_geolocate_button?: boolean;
6
6
  auto_set_location?: boolean;
7
+ address_mode?: "none" | "hidden" | "readonly" | "editable";
7
8
  }
8
9
  export interface IMapField extends IBaseField<IMapFieldParams> {
9
10
  fieldtype: "map";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stubber/form-fields",
3
- "version": "2.1.0",
3
+ "version": "2.1.1",
4
4
  "description": "An automatic form builder based on field specifications",
5
5
  "keywords": [
6
6
  "components",