@nnkogift/dhis2-form-utils-dhis2-ui 0.1.0-alpha.0 → 0.1.0-alpha.2

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/index.js CHANGED
@@ -1,6 +1,12 @@
1
- import { useFieldControl, useSectionState, useFormFeedback, resolveFieldValidation, parseMultiTextValue, joinMultiTextValue, computeAgeFromDob } from '@nnkogift/dhis2-form-utils-hooks';
2
- import { InputField, CalendarInput, MultiSelectField, MultiSelectOption, Radio, SingleSelectField, SingleSelectOption, CheckboxField, TextAreaField, NoticeBox } from '@dhis2/ui';
3
- import { jsx, Fragment, jsxs } from 'react/jsx-runtime';
1
+ import { resolveFieldValidation, computeAgeFromDob, parseMultiTextValue, joinMultiTextValue, useOrgUnitPickerContext, useOrganisationUnitsQuery, useFieldControl, useSectionState, useFormFeedback, useFileResourceUpload } from '@nnkogift/dhis2-form-utils-hooks';
2
+ import { InputField, TextAreaField, CalendarInput, Field, Radio, CheckboxField, SingleSelectField, SingleSelectOption, MultiSelectField, MultiSelectOption, Button, OrganisationUnitTree, FileInputField, NoticeBox } from '@dhis2/ui';
3
+ import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
4
+ import { useMemo, useState, useEffect, useRef } from 'react';
5
+ import { useConfig } from '@dhis2/app-runtime';
6
+ import { Map, NavigationControl, Marker } from 'maplibre-gl';
7
+ import { TerraDraw, TerraDrawPolygonMode, TerraDrawLineStringMode, TerraDrawPointMode } from 'terra-draw';
8
+ import { TerraDrawMapLibreGLAdapter } from 'terra-draw-maplibre-gl-adapter';
9
+ import 'maplibre-gl/dist/maplibre-gl.css';
4
10
 
5
11
  // src/fields/D2Field.tsx
6
12
  function D2TextField({ control }) {
@@ -234,36 +240,54 @@ var BOOLEAN_OPTIONS = [
234
240
  ];
235
241
  var RADIO_RENDER_HINTS = /* @__PURE__ */ new Set(["RADIO", "VERTICAL_RADIOBUTTONS", "HORIZONTAL_RADIOBUTTONS"]);
236
242
  function D2BooleanField({ control }) {
237
- const { fieldConfig, field, isMandatory, isDisabled } = control;
238
- const { validationText, hasError, hasWarning } = resolveFieldValidation(control);
239
- const options = isMandatory ? [...BOOLEAN_OPTIONS] : [...BOOLEAN_OPTIONS, { label: "\u2014", value: "" }];
240
- return /* @__PURE__ */ jsxs("fieldset", { children: [
241
- /* @__PURE__ */ jsxs("legend", { children: [
242
- fieldConfig.label,
243
- isMandatory ? " *" : ""
244
- ] }),
245
- fieldConfig.description ? /* @__PURE__ */ jsx("p", { children: fieldConfig.description }) : null,
246
- options.map((option) => /* @__PURE__ */ jsx(
247
- Radio,
248
- {
249
- name: field.name,
250
- label: option.label,
251
- value: option.value,
252
- checked: field.value === option.value,
253
- disabled: isDisabled,
254
- onChange: ({ value }) => {
255
- field.onChange(value ?? "");
256
- },
257
- onBlur: field.onBlur
258
- },
259
- option.label
260
- )),
261
- validationText ? /* @__PURE__ */ jsx("p", { "data-error": hasError || void 0, "data-warning": hasWarning || void 0, children: validationText }) : null
262
- ] });
243
+ const { fieldConfig, field, isMandatory, isDisabled } = useMemo(() => control, [control]);
244
+ const { validationText, hasError, hasWarning } = useMemo(
245
+ () => resolveFieldValidation(control),
246
+ [control]
247
+ );
248
+ return /* @__PURE__ */ jsx(
249
+ Field,
250
+ {
251
+ label: fieldConfig.label,
252
+ helpText: fieldConfig.description,
253
+ warning: hasWarning,
254
+ error: hasError,
255
+ required: isMandatory,
256
+ disabled: isDisabled,
257
+ validationText,
258
+ children: /* @__PURE__ */ jsx(
259
+ "div",
260
+ {
261
+ style: {
262
+ display: "flex",
263
+ gap: 16
264
+ },
265
+ children: BOOLEAN_OPTIONS.map((option) => /* @__PURE__ */ jsx(
266
+ Radio,
267
+ {
268
+ name: field.name,
269
+ label: option.label,
270
+ value: option.value,
271
+ checked: field.value === option.value,
272
+ disabled: isDisabled,
273
+ onChange: ({ value }) => {
274
+ field.onChange(value ?? "");
275
+ },
276
+ onBlur: field.onBlur
277
+ },
278
+ option.label
279
+ ))
280
+ }
281
+ )
282
+ }
283
+ );
263
284
  }
264
285
  function D2TrueOnlyField({ control }) {
265
- const { fieldConfig, field, isMandatory, isDisabled } = control;
266
- const { validationText, hasError, hasWarning } = resolveFieldValidation(control);
286
+ const { fieldConfig, field, isMandatory, isDisabled } = useMemo(() => control, [control]);
287
+ const { validationText, hasError, hasWarning } = useMemo(
288
+ () => resolveFieldValidation(control),
289
+ [control]
290
+ );
267
291
  return /* @__PURE__ */ jsx(
268
292
  CheckboxField,
269
293
  {
@@ -333,11 +357,12 @@ function D2SelectField({ control }) {
333
357
  function D2MultiSelectField({ control }) {
334
358
  const { fieldConfig, field, isMandatory, isDisabled } = control;
335
359
  const { validationText, hasError, hasWarning } = resolveFieldValidation(control);
336
- const options = (control.visibleOptions ?? fieldConfig.optionSet?.options ?? []).map(
337
- (option) => ({
360
+ const options = useMemo(
361
+ () => (control.visibleOptions ?? fieldConfig.optionSet?.options ?? []).map((option) => ({
338
362
  label: option.label,
339
363
  value: option.code
340
- })
364
+ })),
365
+ [control.visibleOptions, fieldConfig.optionSet?.options]
341
366
  );
342
367
  const selected = parseMultiTextValue(field.value);
343
368
  return /* @__PURE__ */ jsx(
@@ -359,6 +384,482 @@ function D2MultiSelectField({ control }) {
359
384
  }
360
385
  );
361
386
  }
387
+ function D2OrgUnitField({ control }) {
388
+ const { fieldConfig, field, isMandatory, isDisabled } = control;
389
+ const { validationText, hasError, hasWarning } = resolveFieldValidation(control);
390
+ const pickerContext = useOrgUnitPickerContext();
391
+ const { organisationUnits, roots, loading, error } = useOrganisationUnitsQuery(
392
+ pickerContext?.roots
393
+ );
394
+ const value = field.value;
395
+ const selectedUnit = organisationUnits.find((ou) => ou.id === value);
396
+ const selectedLabel = selectedUnit?.displayName ?? value;
397
+ return /* @__PURE__ */ jsxs("fieldset", { children: [
398
+ /* @__PURE__ */ jsxs("legend", { children: [
399
+ fieldConfig.label,
400
+ isMandatory ? " *" : ""
401
+ ] }),
402
+ fieldConfig.description ? /* @__PURE__ */ jsx("p", { children: fieldConfig.description }) : null,
403
+ selectedLabel ? /* @__PURE__ */ jsxs("p", { children: [
404
+ "Selected: ",
405
+ selectedLabel,
406
+ " ",
407
+ /* @__PURE__ */ jsx(
408
+ Button,
409
+ {
410
+ small: true,
411
+ disabled: isDisabled,
412
+ onClick: () => {
413
+ field.onChange("");
414
+ },
415
+ children: "Clear"
416
+ }
417
+ )
418
+ ] }) : null,
419
+ roots.length > 0 ? /* @__PURE__ */ jsx(
420
+ OrganisationUnitTree,
421
+ {
422
+ roots,
423
+ singleSelection: true,
424
+ disableSelection: isDisabled,
425
+ initiallyExpanded: roots.map((id) => `/${id}`),
426
+ onChange: ({ id }) => {
427
+ field.onChange(id);
428
+ }
429
+ }
430
+ ) : loading ? /* @__PURE__ */ jsx("p", { children: "Loading organisation units\u2026" }) : error ? /* @__PURE__ */ jsx("p", { children: "Failed to load organisation units." }) : null,
431
+ validationText ? /* @__PURE__ */ jsx("p", { "data-error": hasError || void 0, "data-warning": hasWarning || void 0, children: validationText }) : null
432
+ ] });
433
+ }
434
+ function useFileFieldUpload(onChange) {
435
+ const { upload, uploading, error } = useFileResourceUpload();
436
+ const [uploadError, setUploadError] = useState(void 0);
437
+ const handleFiles = (files) => {
438
+ const file = files.item(0);
439
+ if (!file) return;
440
+ setUploadError(void 0);
441
+ upload(file).then((result) => {
442
+ onChange(result.id);
443
+ }).catch((uploadFailure) => {
444
+ setUploadError(
445
+ uploadFailure instanceof Error ? uploadFailure.message : "Upload failed"
446
+ );
447
+ });
448
+ };
449
+ return { handleFiles, uploading, uploadError: uploadError ?? error?.message };
450
+ }
451
+ function D2FileField({ control }) {
452
+ const { fieldConfig, field, isMandatory, isDisabled } = control;
453
+ const { validationText, hasError, hasWarning } = resolveFieldValidation(control);
454
+ const { handleFiles, uploading, uploadError } = useFileFieldUpload(field.onChange);
455
+ return /* @__PURE__ */ jsx(
456
+ FileInputField,
457
+ {
458
+ name: field.name,
459
+ label: fieldConfig.label,
460
+ helpText: uploadError ?? fieldConfig.description ?? (uploading ? "Uploading\u2026" : "Max size depends on server configuration."),
461
+ required: isMandatory,
462
+ disabled: isDisabled || uploading,
463
+ warning: hasWarning,
464
+ error: hasError || Boolean(uploadError),
465
+ validationText,
466
+ buttonLabel: field.value ? "Replace file" : "Select file",
467
+ onChange: ({ files }) => {
468
+ handleFiles(files);
469
+ },
470
+ onBlur: field.onBlur
471
+ }
472
+ );
473
+ }
474
+ function D2ImageField({ control }) {
475
+ const { fieldConfig, field, isMandatory, isDisabled } = control;
476
+ const { validationText, hasError, hasWarning } = resolveFieldValidation(control);
477
+ const { handleFiles, uploading, uploadError } = useFileFieldUpload(field.onChange);
478
+ const { baseUrl } = useConfig();
479
+ const value = field.value;
480
+ return /* @__PURE__ */ jsxs("div", { children: [
481
+ /* @__PURE__ */ jsx(
482
+ FileInputField,
483
+ {
484
+ name: field.name,
485
+ label: fieldConfig.label,
486
+ accept: "image/*",
487
+ helpText: uploadError ?? fieldConfig.description ?? (uploading ? "Uploading\u2026" : "Max size depends on server configuration."),
488
+ required: isMandatory,
489
+ disabled: isDisabled || uploading,
490
+ warning: hasWarning,
491
+ error: hasError || Boolean(uploadError),
492
+ validationText,
493
+ buttonLabel: value ? "Replace image" : "Select image",
494
+ onChange: ({ files }) => {
495
+ handleFiles(files);
496
+ },
497
+ onBlur: field.onBlur
498
+ }
499
+ ),
500
+ value ? /* @__PURE__ */ jsx(
501
+ "img",
502
+ {
503
+ src: `${baseUrl}/api/fileResources/${value}/data`,
504
+ alt: fieldConfig.label,
505
+ style: { maxWidth: "200px", maxHeight: "200px", marginTop: "8px" }
506
+ }
507
+ ) : null
508
+ ] });
509
+ }
510
+
511
+ // ../../packages/map/src/coordinateValue.ts
512
+ var LNG_MIN = -180;
513
+ var LNG_MAX = 180;
514
+ var LAT_MIN = -90;
515
+ var LAT_MAX = 90;
516
+ function parseCoordinateValue(value) {
517
+ if (!value) return null;
518
+ let parsed;
519
+ try {
520
+ parsed = JSON.parse(value);
521
+ } catch {
522
+ return null;
523
+ }
524
+ if (!Array.isArray(parsed) || parsed.length !== 2) return null;
525
+ const lng = parsed[0];
526
+ const lat = parsed[1];
527
+ if (typeof lng !== "number" || typeof lat !== "number") return null;
528
+ if (!Number.isFinite(lng) || !Number.isFinite(lat)) return null;
529
+ if (lng < LNG_MIN || lng > LNG_MAX || lat < LAT_MIN || lat > LAT_MAX) return null;
530
+ return { lng, lat };
531
+ }
532
+ function joinCoordinateValue(lng, lat) {
533
+ return JSON.stringify([lng, lat]);
534
+ }
535
+
536
+ // ../../packages/map/src/geojsonValue.ts
537
+ var GEOMETRY_TYPES = /* @__PURE__ */ new Set([
538
+ "Point",
539
+ "MultiPoint",
540
+ "LineString",
541
+ "MultiLineString",
542
+ "Polygon",
543
+ "MultiPolygon",
544
+ "GeometryCollection"
545
+ ]);
546
+ function isGeometryShape(value) {
547
+ if (typeof value !== "object" || value === null) return false;
548
+ const candidate = value;
549
+ if (typeof candidate.type !== "string" || !GEOMETRY_TYPES.has(candidate.type)) return false;
550
+ if (candidate.type === "GeometryCollection") return Array.isArray(candidate.geometries);
551
+ return Array.isArray(candidate.coordinates);
552
+ }
553
+ function parseGeojsonGeometry(value) {
554
+ if (!value) return null;
555
+ let parsed;
556
+ try {
557
+ parsed = JSON.parse(value);
558
+ } catch {
559
+ return null;
560
+ }
561
+ return isGeometryShape(parsed) ? parsed : null;
562
+ }
563
+ function stringifyGeojsonGeometry(geometry) {
564
+ return JSON.stringify(geometry);
565
+ }
566
+
567
+ // ../../packages/map/src/defaultMapStyle.ts
568
+ var defaultMapStyle = {
569
+ version: 8,
570
+ sources: {
571
+ osm: {
572
+ type: "raster",
573
+ tiles: ["https://tile.openstreetmap.org/{z}/{x}/{y}.png"],
574
+ tileSize: 256,
575
+ attribution: "&copy; OpenStreetMap contributors"
576
+ }
577
+ },
578
+ layers: [
579
+ {
580
+ id: "osm",
581
+ type: "raster",
582
+ source: "osm"
583
+ }
584
+ ]
585
+ };
586
+ function useMapLibreMap({ style }) {
587
+ const containerRef = useRef(null);
588
+ const [map, setMap] = useState(null);
589
+ useEffect(() => {
590
+ if (!containerRef.current) return;
591
+ const instance = new Map({
592
+ container: containerRef.current,
593
+ style,
594
+ center: [0, 0],
595
+ zoom: 1
596
+ });
597
+ instance.addControl(new NavigationControl(), "top-right");
598
+ instance.once("load", () => {
599
+ setMap(instance);
600
+ });
601
+ return () => {
602
+ instance.remove();
603
+ setMap(null);
604
+ };
605
+ }, []);
606
+ return { containerRef, map };
607
+ }
608
+ function CoordinateMapPicker({
609
+ value,
610
+ onChange,
611
+ mapStyle,
612
+ disabled,
613
+ className
614
+ }) {
615
+ const { containerRef, map } = useMapLibreMap({ style: mapStyle ?? defaultMapStyle });
616
+ const markerRef = useRef(null);
617
+ const onChangeRef = useRef(onChange);
618
+ onChangeRef.current = onChange;
619
+ useEffect(() => {
620
+ if (!map) return;
621
+ const ensureMarker = (lng, lat) => {
622
+ if (markerRef.current) {
623
+ markerRef.current.setLngLat([lng, lat]);
624
+ return markerRef.current;
625
+ }
626
+ const marker = new Marker({ draggable: !disabled }).setLngLat([lng, lat]).addTo(map);
627
+ marker.on("dragend", () => {
628
+ const lngLat = marker.getLngLat();
629
+ onChangeRef.current(joinCoordinateValue(lngLat.lng, lngLat.lat));
630
+ });
631
+ markerRef.current = marker;
632
+ return marker;
633
+ };
634
+ const handleClick = (event) => {
635
+ if (disabled) return;
636
+ ensureMarker(event.lngLat.lng, event.lngLat.lat);
637
+ onChangeRef.current(joinCoordinateValue(event.lngLat.lng, event.lngLat.lat));
638
+ };
639
+ map.on("click", handleClick);
640
+ const parsed = parseCoordinateValue(value);
641
+ if (parsed) {
642
+ ensureMarker(parsed.lng, parsed.lat);
643
+ map.setCenter([parsed.lng, parsed.lat]);
644
+ }
645
+ return () => {
646
+ map.off("click", handleClick);
647
+ };
648
+ }, [map, disabled]);
649
+ useEffect(() => {
650
+ if (!markerRef.current) return;
651
+ const parsed = parseCoordinateValue(value);
652
+ if (parsed) {
653
+ markerRef.current.setLngLat([parsed.lng, parsed.lat]);
654
+ }
655
+ }, [value]);
656
+ useEffect(() => {
657
+ markerRef.current?.setDraggable(!disabled);
658
+ }, [disabled]);
659
+ return /* @__PURE__ */ jsx(
660
+ "div",
661
+ {
662
+ ref: containerRef,
663
+ className,
664
+ style: { width: "100%", height: "300px" },
665
+ "aria-hidden": "true"
666
+ }
667
+ );
668
+ }
669
+ var DEFAULT_MODES = ["point", "linestring", "polygon"];
670
+ function createDrawMode(mode) {
671
+ switch (mode) {
672
+ case "point":
673
+ return new TerraDrawPointMode();
674
+ case "linestring":
675
+ return new TerraDrawLineStringMode();
676
+ case "polygon":
677
+ return new TerraDrawPolygonMode();
678
+ }
679
+ }
680
+ function GeoJsonMapEditor({
681
+ value,
682
+ onChange,
683
+ mapStyle,
684
+ disabled,
685
+ className,
686
+ modes = DEFAULT_MODES
687
+ }) {
688
+ const { containerRef, map } = useMapLibreMap({ style: mapStyle ?? defaultMapStyle });
689
+ const drawRef = useRef(null);
690
+ const onChangeRef = useRef(onChange);
691
+ onChangeRef.current = onChange;
692
+ useEffect(() => {
693
+ if (!map) return;
694
+ const draw = new TerraDraw({
695
+ adapter: new TerraDrawMapLibreGLAdapter({ map }),
696
+ modes: modes.map(createDrawMode)
697
+ });
698
+ drawRef.current = draw;
699
+ draw.start();
700
+ const initial = parseGeojsonGeometry(value);
701
+ if (initial) {
702
+ draw.addFeatures([
703
+ { type: "Feature", geometry: initial, properties: {} }
704
+ ]);
705
+ }
706
+ const handleFinish = (id) => {
707
+ const feature = draw.getSnapshotFeature(id);
708
+ if (feature) {
709
+ onChangeRef.current(stringifyGeojsonGeometry(feature.geometry));
710
+ }
711
+ };
712
+ draw.on("finish", handleFinish);
713
+ return () => {
714
+ draw.off("finish", handleFinish);
715
+ try {
716
+ draw.stop();
717
+ } catch {
718
+ }
719
+ drawRef.current = null;
720
+ };
721
+ }, [map]);
722
+ const startDrawing = (mode) => {
723
+ const draw = drawRef.current;
724
+ if (!draw || disabled) return;
725
+ draw.clear();
726
+ draw.setMode(mode);
727
+ };
728
+ return /* @__PURE__ */ jsxs("div", { className, children: [
729
+ /* @__PURE__ */ jsx("div", { ref: containerRef, style: { width: "100%", height: "300px" }, "aria-hidden": "true" }),
730
+ !disabled ? /* @__PURE__ */ jsx("div", { role: "group", "aria-label": "Draw geometry", children: modes.map((mode) => /* @__PURE__ */ jsxs(
731
+ "button",
732
+ {
733
+ type: "button",
734
+ onClick: () => {
735
+ startDrawing(mode);
736
+ },
737
+ children: [
738
+ "Draw ",
739
+ mode
740
+ ]
741
+ },
742
+ mode
743
+ )) }) : null
744
+ ] });
745
+ }
746
+ function D2CoordinateField({ control }) {
747
+ const { fieldConfig, field, isMandatory, isDisabled } = control;
748
+ const { validationText, hasError, hasWarning } = resolveFieldValidation(control);
749
+ const value = field.value;
750
+ const [lngText, setLngText] = useState(() => parseCoordinateValue(value)?.lng.toString() ?? "");
751
+ const [latText, setLatText] = useState(() => parseCoordinateValue(value)?.lat.toString() ?? "");
752
+ useEffect(() => {
753
+ const parsed = parseCoordinateValue(value);
754
+ setLngText(parsed ? parsed.lng.toString() : "");
755
+ setLatText(parsed ? parsed.lat.toString() : "");
756
+ }, [value]);
757
+ const commit = (lngRaw, latRaw) => {
758
+ const lng = parseFloat(lngRaw);
759
+ const lat = parseFloat(latRaw);
760
+ if (Number.isFinite(lng) && Number.isFinite(lat)) {
761
+ field.onChange(joinCoordinateValue(lng, lat));
762
+ }
763
+ };
764
+ return /* @__PURE__ */ jsxs("fieldset", { children: [
765
+ /* @__PURE__ */ jsxs("legend", { children: [
766
+ fieldConfig.label,
767
+ isMandatory ? " *" : ""
768
+ ] }),
769
+ fieldConfig.description ? /* @__PURE__ */ jsx("p", { children: fieldConfig.description }) : null,
770
+ /* @__PURE__ */ jsx(CoordinateMapPicker, { value, onChange: field.onChange, disabled: isDisabled }),
771
+ /* @__PURE__ */ jsx(
772
+ InputField,
773
+ {
774
+ name: `${field.name}-lng`,
775
+ type: "number",
776
+ label: "Longitude",
777
+ value: lngText,
778
+ disabled: isDisabled,
779
+ onChange: ({ value: next }) => {
780
+ const text = next ?? "";
781
+ setLngText(text);
782
+ commit(text, latText);
783
+ },
784
+ onBlur: field.onBlur
785
+ }
786
+ ),
787
+ /* @__PURE__ */ jsx(
788
+ InputField,
789
+ {
790
+ name: `${field.name}-lat`,
791
+ type: "number",
792
+ label: "Latitude",
793
+ value: latText,
794
+ disabled: isDisabled,
795
+ onChange: ({ value: next }) => {
796
+ const text = next ?? "";
797
+ setLatText(text);
798
+ commit(lngText, text);
799
+ },
800
+ onBlur: field.onBlur
801
+ }
802
+ ),
803
+ validationText ? /* @__PURE__ */ jsx("p", { "data-error": hasError || void 0, "data-warning": hasWarning || void 0, children: validationText }) : null
804
+ ] });
805
+ }
806
+ function D2GeoJsonField({ control }) {
807
+ const { fieldConfig, field, isMandatory, isDisabled } = control;
808
+ const { validationText, hasError, hasWarning } = resolveFieldValidation(control);
809
+ const value = field.value;
810
+ const [textValue, setTextValue] = useState(value);
811
+ const [textError, setTextError] = useState(void 0);
812
+ return /* @__PURE__ */ jsxs("fieldset", { children: [
813
+ /* @__PURE__ */ jsxs("legend", { children: [
814
+ fieldConfig.label,
815
+ isMandatory ? " *" : ""
816
+ ] }),
817
+ fieldConfig.description ? /* @__PURE__ */ jsx("p", { children: fieldConfig.description }) : null,
818
+ /* @__PURE__ */ jsx(
819
+ GeoJsonMapEditor,
820
+ {
821
+ value,
822
+ onChange: (next) => {
823
+ field.onChange(next);
824
+ const geometry = parseGeojsonGeometry(next);
825
+ setTextValue(geometry ? JSON.stringify(geometry, null, 2) : next);
826
+ setTextError(void 0);
827
+ },
828
+ disabled: isDisabled
829
+ }
830
+ ),
831
+ /* @__PURE__ */ jsx(
832
+ TextAreaField,
833
+ {
834
+ name: `${field.name}-geometry`,
835
+ label: "Geometry (JSON)",
836
+ value: textValue,
837
+ disabled: isDisabled,
838
+ warning: Boolean(textError),
839
+ validationText: textError,
840
+ onChange: ({ value: next }) => {
841
+ setTextValue(next ?? "");
842
+ },
843
+ onBlur: () => {
844
+ if (!textValue) {
845
+ field.onChange("");
846
+ setTextError(void 0);
847
+ return;
848
+ }
849
+ const geometry = parseGeojsonGeometry(textValue);
850
+ if (geometry) {
851
+ field.onChange(stringifyGeojsonGeometry(geometry));
852
+ setTextError(void 0);
853
+ } else {
854
+ setTextError("Not a valid GeoJSON geometry");
855
+ }
856
+ field.onBlur();
857
+ }
858
+ }
859
+ ),
860
+ validationText ? /* @__PURE__ */ jsx("p", { "data-error": hasError || void 0, "data-warning": hasWarning || void 0, children: validationText }) : null
861
+ ] });
862
+ }
362
863
  function D2UnsupportedField({ control }) {
363
864
  const { fieldConfig, field, widgetKind, isMandatory } = control;
364
865
  const { validationText, hasError, hasWarning } = resolveFieldValidation(control);
@@ -397,10 +898,11 @@ var WIDGET_BY_KIND = {
397
898
  select: D2SelectField,
398
899
  multiSelect: D2MultiSelectField,
399
900
  datetime: D2DateTimeField,
400
- coordinate: D2UnsupportedField,
401
- orgUnit: D2UnsupportedField,
402
- file: D2UnsupportedField,
403
- image: D2UnsupportedField,
901
+ coordinate: D2CoordinateField,
902
+ geojson: D2GeoJsonField,
903
+ orgUnit: D2OrgUnitField,
904
+ file: D2FileField,
905
+ image: D2ImageField,
404
906
  unsupported: D2UnsupportedField
405
907
  };
406
908
  function D2FieldWidget({ control }) {
@@ -446,6 +948,6 @@ function FormFeedback() {
446
948
  ] });
447
949
  }
448
950
 
449
- export { D2Field, D2FieldWidget, FormFeedback, FormSection };
951
+ export { D2AgeField, D2BooleanField, D2CoordinateField, D2DateField, D2DateTimeField, D2EmailField, D2Field, D2FieldWidget, D2FileField, D2GeoJsonField, D2ImageField, D2IntegerField, D2LongTextField, D2MultiSelectField, D2NumberField, D2OrgUnitField, D2PercentageField, D2PhoneField, D2SelectField, D2TextField, D2TimeField, D2TrueOnlyField, D2UnsupportedField, FormFeedback, FormSection };
450
952
  //# sourceMappingURL=index.js.map
451
953
  //# sourceMappingURL=index.js.map