@luomus/laji-form 15.1.51 → 15.1.53

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 {
@@ -401,8 +401,8 @@ function Label({ label, children, id, required, registry = {}, uiSchema = {} })
401
401
  }, []);
402
402
  const LabelComponent = labelComponent || "label";
403
403
  const labelElem = (React.createElement(LabelComponent, { htmlFor: id, "aria-describedby": `${id}--help` },
404
- React.createElement("div", null,
405
- React.createElement("strong", { dangerouslySetInnerHTML: { __html: label + requiredHtml } }),
404
+ React.createElement("div", { style: { whiteSpace: "normal" } },
405
+ React.createElement("span", { dangerouslySetInnerHTML: { __html: label + requiredHtml } }),
406
406
  showHelp ? React.createElement(Help, { focusable: true, onFocus: onHelpFocus, onBlur: onHelpBlur, onClick: onHelpClick, id: id }) : null),
407
407
  children));
408
408
  return help ? React.createElement(React.Fragment, null,
@@ -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,15 @@ 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;
36
+ setMoved: (moved: any) => void;
40
37
  onOptionsChanged: (options: any) => void;
41
38
  onChange: (events: any) => void;
42
39
  onMobileEditorChange: (point: any) => void;
43
40
  onHideMobileEditorMap: (options: any) => void;
44
- onLocate: (latlng: any, radius: any, forceShow: any) => void;
41
+ onLocate: (latlng: any, forceShow: any) => void;
42
+ onLocateError: () => void;
45
43
  renderBlocker(): JSX.Element | undefined;
46
44
  }
47
45
  import * as React from "react";
@@ -108,22 +108,35 @@ 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 {
132
+ type: "Point",
133
+ coordinates: [24.94782264266911, 60.17522413438655]
134
+ };
135
+ }
136
+ };
137
+ this.setMoved = (moved) => {
138
+ this.setState({ moved });
139
+ };
127
140
  this.onOptionsChanged = (options) => {
128
141
  this.setState({ mapOptions: Object.assign(Object.assign({}, this.state.mapOptions), options) });
129
142
  };
@@ -170,7 +183,7 @@ class MapField extends React.Component {
170
183
  this.onHideMobileEditorMap = (options) => {
171
184
  this.setState({ mobileEditor: { visible: false, options } });
172
185
  };
173
- this.onLocate = (latlng, radius, forceShow) => {
186
+ this.onLocate = (latlng, forceShow) => {
174
187
  const { geometryCollection = true, mobileEditor, createOnLocate } = utils_1.getUiOptions(this.props.uiSchema);
175
188
  const isEmpty = !this.getGeometry(this.props);
176
189
  if (!latlng || !isEmpty) {
@@ -182,8 +195,6 @@ class MapField extends React.Component {
182
195
  this.setState({
183
196
  mobileEditor: {
184
197
  visible: true,
185
- center: latlng,
186
- radius,
187
198
  options: (this.state.mobileEditor || {}).options || {}
188
199
  },
189
200
  located: true
@@ -197,7 +208,18 @@ class MapField extends React.Component {
197
208
  this.props.onChange(geometryCollection ? { type: "GeometryCollection", geometries: [geometry] } : geometry);
198
209
  }
199
210
  };
200
- this.state = { located: false };
211
+ this.onLocateError = () => {
212
+ this.setState({
213
+ mobileEditor: {
214
+ visible: true,
215
+ options: (this.state.mobileEditor || {}).options || {}
216
+ },
217
+ });
218
+ };
219
+ this.state = {
220
+ located: false,
221
+ moved: false
222
+ };
201
223
  this.props.formContext.services.settings.bind(this, props);
202
224
  }
203
225
  componentDidMount() {
@@ -225,13 +247,15 @@ class MapField extends React.Component {
225
247
  }
226
248
  }
227
249
  render() {
250
+ var _a;
228
251
  const TitleFieldTemplate = utils_3.getTemplate("TitleFieldTemplate", this.props.registry, utils_1.getUiOptions(this.props.uiSchema));
229
252
  const { uiSchema, formData } = this.props;
230
253
  const { height = 400, emptyHelp, mapOptions = {}, mobileEditor: _mobileEditor, data } = utils_1.getUiOptions(uiSchema);
231
254
  const isEmpty = !formData || !formData.geometries || !formData.geometries.length;
232
255
  const _mapOptions = Object.assign(Object.assign(Object.assign({ controls: true, clickBeforeZoomAndPan: true }, mapOptions), (this.state.mapOptions || {})), { locate: {
233
256
  on: this.state.locateOn || false,
234
- onLocationFound: this.onLocate
257
+ onLocationFound: this.onLocate,
258
+ onLocationError: this.onLocateError
235
259
  } });
236
260
  const isSingleton = _mapOptions.singleton;
237
261
  let singletonHasLocate = false;
@@ -258,14 +282,6 @@ class MapField extends React.Component {
258
282
  const { lang, topOffset, bottomOffset } = this.props.formContext;
259
283
  const { mobileEditor } = this.state;
260
284
  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
285
  if (this.map && this.map.userLocation) {
270
286
  mobileEditorOptions.userLocation = this.map.userLocation;
271
287
  }
@@ -277,7 +293,7 @@ class MapField extends React.Component {
277
293
  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
294
  this.map && this.map.container && react_dom_1.createPortal(this.renderBlocker(), this.map.container))),
279
295
  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 }))));
296
+ 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(), moved: this.state.moved, setMoved: this.setMoved, defaultLocation: !this.props.formData && !((_a = this.map) === null || _a === void 0 ? void 0 : _a.userLocation) }))));
281
297
  }
282
298
  renderBlocker() {
283
299
  const { blockBeforeLocation } = utils_1.getUiOptions(this.props.uiSchema);
@@ -310,7 +326,6 @@ MapField.propTypes = {
310
326
  };
311
327
  class MobileEditorMap extends React.Component {
312
328
  constructor(props) {
313
- var _a, _b;
314
329
  super(props);
315
330
  this.DEFAULT_RADIUS_PIXELS = 100;
316
331
  this.setMobileEditorMapRef = (mapComponent) => {
@@ -322,56 +337,31 @@ class MobileEditorMap extends React.Component {
322
337
  this.setOkButtonRef = (elem) => {
323
338
  this.okButtonElem = react_dom_1.findDOMNode(elem);
324
339
  };
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
340
  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
- });
341
+ if (this.marker) {
342
+ const { lat, lng } = this.marker.getLatLng();
343
+ this.map.map.setView({ lng, lat }, 12);
344
+ const markerGeometry = {
345
+ type: "Point",
346
+ coordinates: [lng, lat]
347
+ };
348
+ this.props.onChange(markerGeometry);
349
+ }
340
350
  this.onClose();
341
351
  };
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;
352
+ this.handleMapClick = (e) => {
353
+ if (!this.props.moved) {
354
+ this.props.setMoved(true);
355
+ }
356
+ this.setMarkerLatLng(e.latlng);
354
357
  };
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 };
358
+ this.setMarkerLatLng = (latlng) => {
359
+ if (this.marker) {
360
+ this.marker.setLatLng(latlng);
361
+ }
362
+ else {
363
+ this.marker = L.marker(latlng, { icon: this.map._createIcon(), draggable: true }).addTo(this.map.map);
373
364
  }
374
- return {};
375
365
  };
376
366
  this.invisibleStyle = () => {
377
367
  return {
@@ -384,65 +374,56 @@ class MobileEditorMap extends React.Component {
384
374
  this.onClose();
385
375
  }
386
376
  };
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
377
  this.onClose = () => {
401
378
  this.props.onClose(this.map.getOptions());
402
379
  };
403
- const { center, radius } = this.props;
380
+ const { geometry } = this.props;
404
381
  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,
382
+ geometry: [{ geoData: geometry }]
408
383
  };
409
384
  }
410
385
  componentDidMount() {
411
386
  this.mounted = true;
412
387
  this.okButtonElem.focus();
413
- window.addEventListener("resize", this.updateDimensions);
388
+ if (this.props.geometry) {
389
+ const [lng, lat] = this.props.geometry.coordinates;
390
+ this.setMarkerLatLng({ lng, lat });
391
+ if (this.props.defaultLocation) {
392
+ this.map.map.setView({ lng, lat }, 4);
393
+ }
394
+ else {
395
+ this.map.map.setView({ lng, lat }, 12);
396
+ this.props.setMoved(true);
397
+ }
398
+ this.marker.on("dragend", () => {
399
+ if (!this.props.moved) {
400
+ this.props.setMoved(true);
401
+ }
402
+ });
403
+ }
404
+ this.map.map.on("click", this.handleMapClick);
414
405
  }
415
- componenWillUnmount() {
406
+ componentWillUnmount() {
416
407
  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" })));
408
+ if (this.marker) {
409
+ this.marker.remove();
410
+ }
411
+ this.map.map.off("click", this.handleMapClick);
429
412
  }
430
413
  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
414
+ 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
415
  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
416
  const { translations } = this.props.formContext;
417
+ const mapComponentProps = Object.assign(Object.assign(Object.assign({}, options), (this.props.options || {})), { locate: {
418
+ on: true,
419
+ userLocation,
420
+ panOnFound: false,
421
+ }, singleton: true, clickBeforeZoomAndPan: false, viewLocked: false, controls: { draw: false }, ref: this.setMobileEditorMapRef, formContext: this.props.formContext, panel: {
422
+ panelTextContent: this.props.formContext.translations.MobileMapInstructions
423
+ } });
441
424
  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),
425
+ React.createElement(MapArrayField_1.MapComponent, Object.assign({}, mapComponentProps)),
444
426
  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))));
427
+ React.createElement(components_2.Button, { block: true, onClick: this.onChange, variant: "primary", ref: this.setOkButtonRef, disabled: !this.props.moved }, translations.ChooseThisLocation))));
447
428
  }
448
429
  }
@@ -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.51",
3
+ "version": "15.1.53",
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",