@luomus/laji-form 15.1.50 → 15.1.52

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/dist/styles.css CHANGED
@@ -1900,6 +1900,24 @@ li.laji-map-layer-control-layer-item.active span {
1900
1900
  z-index: -1;
1901
1901
  }
1902
1902
 
1903
+ .laji-map .leaflet-control-locate-location circle {
1904
+ animation: laji-map-control-locate-throb 4s ease infinite;
1905
+ }
1906
+
1907
+ @keyframes laji-map-control-locate-throb {
1908
+ 0% {
1909
+ stroke-width: 1;
1910
+ }
1911
+
1912
+ 50% {
1913
+ stroke-width: 3;
1914
+ transform: scale(0.8, 0.8);
1915
+ }
1916
+
1917
+ 100% {
1918
+ stroke-width: 1;
1919
+ }
1920
+ }
1903
1921
 
1904
1922
 
1905
1923
  .leaflet-measure-path-measurement {
@@ -411,7 +411,7 @@ class LocationButton extends React.Component {
411
411
  else {
412
412
  const { translations } = that.props.formContext;
413
413
  const overlay = hasCoordinates ? (React.createElement(Popover, { id: `${id}-location-peeker`, title: `${translations.ChooseLocation} (${translations.below} ${translations.currentLocation})` },
414
- React.createElement(MapArrayField_1.Map, Object.assign({}, this.state.miniMap, { hidden: !this.state.miniMap || this.state.modalMap, style: { width: 200, height: 200 }, singleton: true, formContext: that.props.formContext, bodyAsDialogRoot: false, ref: this.setMiniMapRef })))) : (React.createElement(Tooltip, { id: `${id}-location-peeker` }, label || that.props.formContext.translations.ChooseLocation));
414
+ React.createElement(MapArrayField_1.Map, Object.assign({}, this.state.miniMap, { hidden: !this.state.miniMap || this.state.modalMap, style: { width: 200, height: 200 }, singleton: true, formContext: that.props.formContext, bodyAsDialogRoot: false, syncZoomToDataOnDataChangeOnSingleton: true, ref: this.setMiniMapRef })))) : (React.createElement(Tooltip, { id: `${id}-location-peeker` }, label || that.props.formContext.translations.ChooseLocation));
415
415
  return (React.createElement(components_1.OverlayTrigger, { key: `${id}-set-coordinates-${glyph}`, overlay: overlay, placement: "left", hoverable: true, formContext: that.props.formContext, onEntered: hasCoordinates ? this.onEntered : undefined }, button));
416
416
  }
417
417
  };
@@ -1688,7 +1688,9 @@ class Map extends React.Component {
1688
1688
  default:
1689
1689
  if (!equals(mapOptions[key], prevMapOptions[key])) {
1690
1690
  this.map.setOption(key, mapOptions[key]);
1691
- if (this.props.singleton && mapOptions.zoomToData && key === "data") {
1691
+ if (this.props.syncZoomToDataOnDataChangeOnSingleton
1692
+ && this.props.singleton
1693
+ && mapOptions.zoomToData && key === "data") {
1692
1694
  this.map.zoomToData(mapOptions.zoomToData);
1693
1695
  }
1694
1696
  }
@@ -31,17 +31,14 @@ export default class MapField extends React.Component<any, any, any> {
31
31
  map: any;
32
32
  showMobileEditorMap: () => void;
33
33
  getDrawOptions: (props: any) => any;
34
- getEditWithModalFeatureStyle: () => {
35
- fillOpacity: number;
36
- color: string;
37
- weight: string;
38
- };
39
34
  getGeometry: (props: any) => any;
35
+ getMobileGeometry: () => any;
40
36
  onOptionsChanged: (options: any) => void;
41
37
  onChange: (events: any) => void;
42
38
  onMobileEditorChange: (point: any) => void;
43
39
  onHideMobileEditorMap: (options: any) => void;
44
- onLocate: (latlng: any, radius: any, forceShow: any) => void;
40
+ onLocate: (latlng: any, forceShow: any) => void;
41
+ onLocateError: () => void;
45
42
  renderBlocker(): JSX.Element | undefined;
46
43
  }
47
44
  import * as React from "react";
@@ -108,22 +108,29 @@ class MapField extends React.Component {
108
108
  this.getDrawOptions = (props) => {
109
109
  const { uiSchema, disabled, readonly } = props;
110
110
  const options = utils_1.getUiOptions(uiSchema);
111
- const { mapOptions = {}, mobileEditor } = options;
112
- const drawOptions = Object.assign(Object.assign({}, (mapOptions.draw || {})), { geoData: this.getGeometry(props), onChange: this.onChange, editable: !disabled && !readonly });
113
- if (mobileEditor) {
114
- drawOptions.getFeatureStyle = this.getEditWithModalFeatureStyle;
115
- }
111
+ const { mapOptions = {} } = options;
112
+ const drawOptions = Object.assign(Object.assign({}, (mapOptions.draw || {})), { geoData: this.getGeometry(props), onChange: this.onChange, editable: !disabled && !readonly && !options.mobileEditor });
116
113
  return drawOptions;
117
114
  };
118
- this.getEditWithModalFeatureStyle = () => ({
119
- fillOpacity: 0,
120
- color: "black",
121
- weight: "2",
122
- });
123
115
  this.getGeometry = (props) => {
124
116
  const { formData } = props;
125
117
  return formData && Object.keys(formData).length ? formData : undefined;
126
118
  };
119
+ this.getMobileGeometry = () => {
120
+ var _a;
121
+ if (this.props.formData) {
122
+ return this.props.formData;
123
+ }
124
+ else if ((_a = this.map) === null || _a === void 0 ? void 0 : _a.userLocation) {
125
+ return {
126
+ type: "Point",
127
+ coordinates: [this.map.userLocation.latlng.lng, this.map.userLocation.latlng.lat]
128
+ };
129
+ }
130
+ else {
131
+ return undefined;
132
+ }
133
+ };
127
134
  this.onOptionsChanged = (options) => {
128
135
  this.setState({ mapOptions: Object.assign(Object.assign({}, this.state.mapOptions), options) });
129
136
  };
@@ -170,7 +177,7 @@ class MapField extends React.Component {
170
177
  this.onHideMobileEditorMap = (options) => {
171
178
  this.setState({ mobileEditor: { visible: false, options } });
172
179
  };
173
- this.onLocate = (latlng, radius, forceShow) => {
180
+ this.onLocate = (latlng, forceShow) => {
174
181
  const { geometryCollection = true, mobileEditor, createOnLocate } = utils_1.getUiOptions(this.props.uiSchema);
175
182
  const isEmpty = !this.getGeometry(this.props);
176
183
  if (!latlng || !isEmpty) {
@@ -182,8 +189,6 @@ class MapField extends React.Component {
182
189
  this.setState({
183
190
  mobileEditor: {
184
191
  visible: true,
185
- center: latlng,
186
- radius,
187
192
  options: (this.state.mobileEditor || {}).options || {}
188
193
  },
189
194
  located: true
@@ -197,6 +202,14 @@ class MapField extends React.Component {
197
202
  this.props.onChange(geometryCollection ? { type: "GeometryCollection", geometries: [geometry] } : geometry);
198
203
  }
199
204
  };
205
+ this.onLocateError = () => {
206
+ this.setState({
207
+ mobileEditor: {
208
+ visible: true,
209
+ options: (this.state.mobileEditor || {}).options || {}
210
+ },
211
+ });
212
+ };
200
213
  this.state = { located: false };
201
214
  this.props.formContext.services.settings.bind(this, props);
202
215
  }
@@ -231,7 +244,8 @@ class MapField extends React.Component {
231
244
  const isEmpty = !formData || !formData.geometries || !formData.geometries.length;
232
245
  const _mapOptions = Object.assign(Object.assign(Object.assign({ controls: true, clickBeforeZoomAndPan: true }, mapOptions), (this.state.mapOptions || {})), { locate: {
233
246
  on: this.state.locateOn || false,
234
- onLocationFound: this.onLocate
247
+ onLocationFound: this.onLocate,
248
+ onLocationError: this.onLocateError
235
249
  } });
236
250
  const isSingleton = _mapOptions.singleton;
237
251
  let singletonHasLocate = false;
@@ -258,14 +272,6 @@ class MapField extends React.Component {
258
272
  const { lang, topOffset, bottomOffset } = this.props.formContext;
259
273
  const { mobileEditor } = this.state;
260
274
  let mobileEditorOptions = utils_1.isObject(mobileEditor) ? mobileEditor : {};
261
- const geometry = this.getGeometry(this.props);
262
- if (geometry) {
263
- const { center, radius } = getCenterAndRadiusFromGeometry(geometry);
264
- mobileEditorOptions = {
265
- center,
266
- radius
267
- };
268
- }
269
275
  if (this.map && this.map.userLocation) {
270
276
  mobileEditorOptions.userLocation = this.map.userLocation;
271
277
  }
@@ -277,7 +283,7 @@ class MapField extends React.Component {
277
283
  React.createElement(MapArrayField_1.MapComponent, Object.assign({}, _mapOptions, { ref: this.setMapRef, draw: this.getDrawOptions(this.props), data: extraData, lang: lang, zoomToData: { paddingInMeters: 200 }, panel: emptyHelp && isEmpty ? { panelTextContent: emptyHelp } : undefined, formContext: this.props.formContext, onOptionsChanged: this.onOptionsChanged })),
278
284
  this.map && this.map.container && react_dom_1.createPortal(this.renderBlocker(), this.map.container))),
279
285
  this.state.mapRendered && mobileEditor && mobileEditor.visible &&
280
- React.createElement(MobileEditorMap, Object.assign({}, mobileEditorOptions, { options: mobileEditor.options, onChange: this.onMobileEditorChange, onClose: this.onHideMobileEditorMap, map: this.map, formContext: this.props.formContext }))));
286
+ React.createElement(MobileEditorMap, Object.assign({}, mobileEditorOptions, { options: mobileEditor.options, onChange: this.onMobileEditorChange, onClose: this.onHideMobileEditorMap, map: this.map, formContext: this.props.formContext, geometry: this.getMobileGeometry() }))));
281
287
  }
282
288
  renderBlocker() {
283
289
  const { blockBeforeLocation } = utils_1.getUiOptions(this.props.uiSchema);
@@ -310,7 +316,6 @@ MapField.propTypes = {
310
316
  };
311
317
  class MobileEditorMap extends React.Component {
312
318
  constructor(props) {
313
- var _a, _b;
314
319
  super(props);
315
320
  this.DEFAULT_RADIUS_PIXELS = 100;
316
321
  this.setMobileEditorMapRef = (mapComponent) => {
@@ -322,56 +327,31 @@ class MobileEditorMap extends React.Component {
322
327
  this.setOkButtonRef = (elem) => {
323
328
  this.okButtonElem = react_dom_1.findDOMNode(elem);
324
329
  };
325
- this.updateDimensions = () => {
326
- var _a, _b;
327
- this.setState({ width: ((_a = window.visualViewport) === null || _a === void 0 ? void 0 : _a.width) || window.innerWidth, height: ((_b = window.visualViewport) === null || _b === void 0 ? void 0 : _b.height) || window.innerHeight });
328
- };
329
330
  this.onChange = () => {
330
- const { map } = this.map;
331
- const centerLatLng = map.getCenter();
332
- const centerPoint = map.latLngToContainerPoint(centerLatLng);
333
- const leftEdgeAsLatLng = map.containerPointToLatLng({ x: centerPoint.x - this.DEFAULT_RADIUS_PIXELS, y: centerPoint.y });
334
- const radius = map.getCenter().distanceTo(leftEdgeAsLatLng);
335
- this.props.onChange({
336
- type: "Point",
337
- coordinates: [centerLatLng.lng, centerLatLng.lat],
338
- radius
339
- });
331
+ if (this.marker) {
332
+ const { lat, lng } = this.marker.getLatLng();
333
+ this.map.map.setView({ lng, lat }, 12);
334
+ const markerGeometry = {
335
+ type: "Point",
336
+ coordinates: [lng, lat]
337
+ };
338
+ this.props.onChange(markerGeometry);
339
+ }
340
340
  this.onClose();
341
341
  };
342
- this.computePadding = () => {
343
- var _a, _b;
344
- // If the rendered element wasn't full screen, we couldn't use these as height/width.
345
- const height = ((_a = window.visualViewport) === null || _a === void 0 ? void 0 : _a.height) || window.innerHeight;
346
- const width = ((_b = window.visualViewport) === null || _b === void 0 ? void 0 : _b.width) || window.innerWidth;
347
- const topToCircleEdgePixels = parseInt(height / 2 - this.DEFAULT_RADIUS_PIXELS);
348
- const leftToCircleEdgePixels = parseInt(width / 2 - this.DEFAULT_RADIUS_PIXELS);
349
- const padding = [
350
- leftToCircleEdgePixels,
351
- topToCircleEdgePixels
352
- ];
353
- return padding;
342
+ this.handleMapClick = (e) => {
343
+ if (!this.state.moved) {
344
+ this.setState({ moved: true });
345
+ }
346
+ this.setMarkerLatLng(e.latlng);
354
347
  };
355
- this.setViewFromCenterAndRadius = (center, radius) => {
356
- if (center) {
357
- if (radius) {
358
- const centerLatLng = L.latLng(center);
359
- const data = {
360
- geoData: {
361
- type: "Point",
362
- coordinates: [centerLatLng.lng, centerLatLng.lat],
363
- radius
364
- },
365
- getFeatureStyle: this.invisibleStyle
366
- };
367
- const zoomToData = {
368
- padding: this.computePadding()
369
- };
370
- return { data, zoomToData };
371
- }
372
- return { center };
348
+ this.setMarkerLatLng = (latlng) => {
349
+ if (this.marker) {
350
+ this.marker.setLatLng(latlng);
351
+ }
352
+ else {
353
+ this.marker = L.marker(latlng, { icon: this.map._createIcon(), draggable: true }).addTo(this.map.map);
373
354
  }
374
- return {};
375
355
  };
376
356
  this.invisibleStyle = () => {
377
357
  return {
@@ -384,65 +364,51 @@ class MobileEditorMap extends React.Component {
384
364
  this.onClose();
385
365
  }
386
366
  };
387
- this.onLocate = (latlng, accuracy) => {
388
- if (this.props.center || !this.mounted)
389
- return;
390
- const options = this.setViewFromCenterAndRadius(latlng, accuracy);
391
- if (options.data && options.zoomToData) {
392
- this.setState({ mapOptions: { data: options.data } }, () => {
393
- this.map.zoomToData(options.zoomToData);
394
- });
395
- }
396
- else if (options.center) {
397
- this.map.setCenter(options.center);
398
- }
399
- };
400
367
  this.onClose = () => {
401
368
  this.props.onClose(this.map.getOptions());
402
369
  };
403
- const { center, radius } = this.props;
370
+ const { geometry } = this.props;
404
371
  this.state = {
405
- mapOptions: this.setViewFromCenterAndRadius(center, radius),
406
- width: ((_a = window.visualViewport) === null || _a === void 0 ? void 0 : _a.width) || window.innerWidth,
407
- height: ((_b = window.visualViewport) === null || _b === void 0 ? void 0 : _b.height) || window.innerHeight,
372
+ geometry: [{ geoData: geometry }],
373
+ moved: false
408
374
  };
409
375
  }
410
376
  componentDidMount() {
411
377
  this.mounted = true;
412
378
  this.okButtonElem.focus();
413
- window.addEventListener("resize", this.updateDimensions);
379
+ if (this.props.geometry) {
380
+ const [lng, lat] = this.props.geometry.coordinates;
381
+ this.setMarkerLatLng({ lng, lat });
382
+ this.map.map.setView({ lng, lat }, 12);
383
+ this.marker.on("dragend", () => {
384
+ if (!this.state.moved) {
385
+ this.setState({ moved: true });
386
+ }
387
+ });
388
+ }
389
+ this.map.map.on("click", this.handleMapClick);
414
390
  }
415
- componenWillUnmount() {
391
+ componentWillUnmount() {
416
392
  this.mounted = false;
417
- window.removeEventListener("resize", this.updateDimensions);
418
- }
419
- getCircle(radiusPixels) {
420
- return (React.createElement("svg", { width: this.state.width, height: this.state.height, style: { position: "absolute", zIndex: 1000, top: 0, left: 0, pointerEvents: "none" } },
421
- React.createElement("defs", null,
422
- React.createElement("mask", { id: "mask", x: "0", y: "0", width: this.state.width, height: this.state.height },
423
- React.createElement("rect", { x: "0", y: "0", width: this.state.width, height: this.state.height, fill: "#fff" }),
424
- React.createElement("circle", { cx: this.state.width / 2, cy: this.state.height / 2, r: radiusPixels }))),
425
- React.createElement("rect", { x: "0", y: "0", width: this.state.width, height: this.state.height, mask: "url(#mask)", fillOpacity: "0.2" }),
426
- React.createElement("circle", { cx: this.state.width / 2, cy: this.state.height / 2, r: radiusPixels, stroke: "black", strokeWidth: "2", fillOpacity: "0" }),
427
- React.createElement("line", { x1: this.state.width / 2, y1: this.state.height / 2 - radiusPixels, x2: this.state.width / 2, y2: this.state.height / 2 + radiusPixels, stroke: "black", strokeWidth: "2" }),
428
- React.createElement("line", { x1: this.state.width / 2 - radiusPixels, y1: this.state.height / 2, x2: this.state.width / 2 + radiusPixels, y2: this.state.height / 2, stroke: "black", strokeWidth: "2" })));
393
+ if (this.marker) {
394
+ this.marker.remove();
395
+ }
396
+ this.map.map.off("click", this.handleMapClick);
429
397
  }
430
398
  render() {
431
- let _a = this.props.map.getOptions(), { rootElem, customControls, draw, data, zoomToData, zoom, center, locate } = _a, options = __rest(_a, ["rootElem", "customControls", "draw", "data", "zoomToData", "zoom", "center", "locate"]); // eslint-disable-line @typescript-eslint/no-unused-vars
399
+ let _a = this.props.map.getOptions(), { rootElem, customControls, draw, data, zoomToData, zoom, locate } = _a, options = __rest(_a, ["rootElem", "customControls", "draw", "data", "zoomToData", "zoom", "locate"]); // eslint-disable-line @typescript-eslint/no-unused-vars
432
400
  const { userLocation } = this.props;
433
- options = Object.assign(Object.assign(Object.assign({}, options), (this.props.options || {})), this.state.mapOptions);
434
- options.locate = {
435
- on: true,
436
- userLocation,
437
- onLocationFound: this.onLocate,
438
- panOnFound: false
439
- };
440
401
  const { translations } = this.props.formContext;
402
+ const mapComponentProps = Object.assign(Object.assign(Object.assign({}, options), (this.props.options || {})), { locate: {
403
+ on: true,
404
+ userLocation,
405
+ panOnFound: false,
406
+ }, singleton: true, clickBeforeZoomAndPan: false, viewLocked: false, controls: { draw: false }, ref: this.setMobileEditorMapRef, formContext: this.props.formContext, panel: {
407
+ panelTextContent: this.props.formContext.translations.MobileMapInstructions
408
+ } });
441
409
  return (React.createElement(components_2.Fullscreen, { onKeyDown: this.onKeyDown, tabIndex: -1, ref: this.setContainerRef, formContext: this.props.formContext },
442
- React.createElement(MapArrayField_1.MapComponent, Object.assign({}, options, { singleton: true, clickBeforeZoomAndPan: false, viewLocked: false, controls: { draw: false }, ref: this.setMobileEditorMapRef, formContext: this.props.formContext })),
443
- this.state.mapRendered && react_dom_1.createPortal(this.getCircle(this.DEFAULT_RADIUS_PIXELS), this.map.container),
410
+ React.createElement(MapArrayField_1.MapComponent, Object.assign({}, mapComponentProps)),
444
411
  React.createElement("div", { className: "floating-buttons-container" },
445
- React.createElement(components_2.Button, { block: true, onClick: this.onChange, ref: this.setOkButtonRef }, translations.ChooseThisLocation),
446
- React.createElement(components_2.Button, { block: true, onClick: this.onClose }, translations.Cancel))));
412
+ React.createElement(components_2.Button, { block: true, onClick: this.onChange, ref: this.setOkButtonRef, disabled: !this.state.moved }, translations.ChooseThisLocation))));
447
413
  }
448
414
  }
@@ -62,7 +62,7 @@ function SearchableDrowndown(props) {
62
62
  const enumOptions = react_1.useMemo(() =>
63
63
  // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
64
64
  getEnumOptions(options.enumOptions, uiSchema, includeEmpty), [options.enumOptions, uiSchema, includeEmpty]);
65
- const getLabelFromValue = react_1.useCallback((value) => value !== undefined
65
+ const getLabelFromValue = react_1.useCallback((value) => value !== undefined && value !== ""
66
66
  // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
67
67
  ? enumOptions.find(item => item.value === value).label
68
68
  : "", [enumOptions]);
@@ -149,10 +149,6 @@ function SearchableDrowndown(props) {
149
149
  }
150
150
  function SearchableMultiDrowndown(props) {
151
151
  const { id, disabled, readonly, value, uiSchema, options, onChange, getEnumOptionsAsync } = props;
152
- // const enumOptions = React.useMemo(() =>
153
- // // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
154
- // getEnumOptions(options.enumOptions!, uiSchema, false),
155
- // [options.enumOptions, uiSchema]);
156
152
  const [enumOptions, setEnumOptions] = react_1.useState(getEnumOptionsAsync
157
153
  ? undefined
158
154
  // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
@@ -524,6 +524,11 @@
524
524
  "en": "Exit fullscreen mode",
525
525
  "sv": "Avsluta helskärmsläge"
526
526
  },
527
+ "mobileMapInstructions": {
528
+ "fi": "Valitse sijainti napauttamalla karttaa tai raahaamalla olemassa olevaa pistettä.",
529
+ "en": "Select a location by tapping the map or dragging an existing point.",
530
+ "sv": "Välj en plats genom att trycka på kartan eller dra en befintlig punkt."
531
+ },
527
532
  "openShorthand": {
528
533
  "fi": "Avaa pikasyöttötila",
529
534
  "en": "Open shorthand mode",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@luomus/laji-form",
3
- "version": "15.1.50",
3
+ "version": "15.1.52",
4
4
  "description": "React module capable of building dynamic forms from Laji form json schemas",
5
5
  "main": "lib/index.js",
6
6
  "types": "lib/index.d.ts",
@@ -39,7 +39,7 @@
39
39
  "repository": "git+https://github.com/luomus/laji-form.git",
40
40
  "license": "MIT",
41
41
  "dependencies": {
42
- "@luomus/laji-map": "^5.1.13",
42
+ "@luomus/laji-map": "^5.1.16",
43
43
  "@luomus/laji-validate": "^0.0.122",
44
44
  "@rjsf/core": "~5.1.0",
45
45
  "@rjsf/utils": "~5.1.0",