@defra/forms-engine-plugin 4.14.0 → 4.14.2-beta

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.
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Attaches {@link handleButtonClick} to every button that carries the
3
+ * `prevent-multiple-clicks` CSS class so that double-submissions are blocked
4
+ * across the page.
5
+ *
6
+ * Safe to call multiple times — adding the same listener twice on a given
7
+ * element has no effect (the browser deduplicates identical listener/options
8
+ * pairs).
9
+ */
10
+ export function initDebounceClick(): void;
@@ -0,0 +1,58 @@
1
+ /** How long (ms) a button stays locked after the first click. */
2
+ const DEBOUNCE_TIMEOUT_MS = 10_000;
3
+
4
+ /**
5
+ * Shared debounce logic used by both the click and keydown handlers.
6
+ * @param {Event} event
7
+ */
8
+ function handleActivation(event) {
9
+ const button = /** @type {HTMLButtonElement} */event.currentTarget;
10
+ if (button.dataset.debouncing === 'true') {
11
+ event.preventDefault();
12
+ event.stopImmediatePropagation();
13
+ return;
14
+ }
15
+ button.dataset.debouncing = 'true';
16
+ setTimeout(() => {
17
+ delete button.dataset.debouncing;
18
+ }, DEBOUNCE_TIMEOUT_MS);
19
+ }
20
+
21
+ /**
22
+ * Click handler that prevents a button from being activated more than once
23
+ * within {@link DEBOUNCE_TIMEOUT_MS}.
24
+ * @param {MouseEvent} event
25
+ */
26
+ function handleButtonClick(event) {
27
+ handleActivation(event);
28
+ }
29
+
30
+ /**
31
+ * Keydown handler that prevents a button from being activated more than once
32
+ * within {@link DEBOUNCE_TIMEOUT_MS} when submitted via Enter or Space.
33
+ * @param {KeyboardEvent} event
34
+ */
35
+ function handleButtonKeydown(event) {
36
+ if (event.key === 'Enter' || event.key === ' ') {
37
+ handleActivation(event);
38
+ }
39
+ }
40
+
41
+ /**
42
+ * Attaches {@link handleButtonClick} to every button that carries the
43
+ * `prevent-multiple-clicks` CSS class so that double-submissions are blocked
44
+ * across the page.
45
+ *
46
+ * Safe to call multiple times — adding the same listener twice on a given
47
+ * element has no effect (the browser deduplicates identical listener/options
48
+ * pairs).
49
+ */
50
+ export function initDebounceClick() {
51
+ const buttons = /** @type {NodeListOf<HTMLButtonElement>} */
52
+ document.querySelectorAll('.prevent-multiple-clicks');
53
+ for (const button of buttons) {
54
+ button.addEventListener('click', handleButtonClick);
55
+ button.addEventListener('keydown', handleButtonKeydown);
56
+ }
57
+ }
58
+ //# sourceMappingURL=debounce-click.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"debounce-click.js","names":["DEBOUNCE_TIMEOUT_MS","handleActivation","event","button","currentTarget","dataset","debouncing","preventDefault","stopImmediatePropagation","setTimeout","handleButtonClick","handleButtonKeydown","key","initDebounceClick","buttons","document","querySelectorAll","addEventListener"],"sources":["../../../src/client/javascripts/debounce-click.js"],"sourcesContent":["/** How long (ms) a button stays locked after the first click. */\nconst DEBOUNCE_TIMEOUT_MS = 10_000\n\n/**\n * Shared debounce logic used by both the click and keydown handlers.\n * @param {Event} event\n */\nfunction handleActivation(event) {\n const button = /** @type {HTMLButtonElement} */ (event.currentTarget)\n\n if (button.dataset.debouncing === 'true') {\n event.preventDefault()\n event.stopImmediatePropagation()\n return\n }\n\n button.dataset.debouncing = 'true'\n\n setTimeout(() => {\n delete button.dataset.debouncing\n }, DEBOUNCE_TIMEOUT_MS)\n}\n\n/**\n * Click handler that prevents a button from being activated more than once\n * within {@link DEBOUNCE_TIMEOUT_MS}.\n * @param {MouseEvent} event\n */\nfunction handleButtonClick(event) {\n handleActivation(event)\n}\n\n/**\n * Keydown handler that prevents a button from being activated more than once\n * within {@link DEBOUNCE_TIMEOUT_MS} when submitted via Enter or Space.\n * @param {KeyboardEvent} event\n */\nfunction handleButtonKeydown(event) {\n if (event.key === 'Enter' || event.key === ' ') {\n handleActivation(event)\n }\n}\n\n/**\n * Attaches {@link handleButtonClick} to every button that carries the\n * `prevent-multiple-clicks` CSS class so that double-submissions are blocked\n * across the page.\n *\n * Safe to call multiple times — adding the same listener twice on a given\n * element has no effect (the browser deduplicates identical listener/options\n * pairs).\n */\nexport function initDebounceClick() {\n const buttons = /** @type {NodeListOf<HTMLButtonElement>} */ (\n document.querySelectorAll('.prevent-multiple-clicks')\n )\n\n for (const button of buttons) {\n button.addEventListener('click', handleButtonClick)\n button.addEventListener('keydown', handleButtonKeydown)\n }\n}\n"],"mappings":"AAAA;AACA,MAAMA,mBAAmB,GAAG,MAAM;;AAElC;AACA;AACA;AACA;AACA,SAASC,gBAAgBA,CAACC,KAAK,EAAE;EAC/B,MAAMC,MAAM,GAAG,gCAAkCD,KAAK,CAACE,aAAc;EAErE,IAAID,MAAM,CAACE,OAAO,CAACC,UAAU,KAAK,MAAM,EAAE;IACxCJ,KAAK,CAACK,cAAc,CAAC,CAAC;IACtBL,KAAK,CAACM,wBAAwB,CAAC,CAAC;IAChC;EACF;EAEAL,MAAM,CAACE,OAAO,CAACC,UAAU,GAAG,MAAM;EAElCG,UAAU,CAAC,MAAM;IACf,OAAON,MAAM,CAACE,OAAO,CAACC,UAAU;EAClC,CAAC,EAAEN,mBAAmB,CAAC;AACzB;;AAEA;AACA;AACA;AACA;AACA;AACA,SAASU,iBAAiBA,CAACR,KAAK,EAAE;EAChCD,gBAAgB,CAACC,KAAK,CAAC;AACzB;;AAEA;AACA;AACA;AACA;AACA;AACA,SAASS,mBAAmBA,CAACT,KAAK,EAAE;EAClC,IAAIA,KAAK,CAACU,GAAG,KAAK,OAAO,IAAIV,KAAK,CAACU,GAAG,KAAK,GAAG,EAAE;IAC9CX,gBAAgB,CAACC,KAAK,CAAC;EACzB;AACF;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,OAAO,SAASW,iBAAiBA,CAAA,EAAG;EAClC,MAAMC,OAAO,GAAG;EACdC,QAAQ,CAACC,gBAAgB,CAAC,0BAA0B,CACrD;EAED,KAAK,MAAMb,MAAM,IAAIW,OAAO,EAAE;IAC5BX,MAAM,CAACc,gBAAgB,CAAC,OAAO,EAAEP,iBAAiB,CAAC;IACnDP,MAAM,CAACc,gBAAgB,CAAC,SAAS,EAAEN,mBAAmB,CAAC;EACzD;AACF","ignoreList":[]}
@@ -0,0 +1,191 @@
1
+ import { initDebounceClick } from "./debounce-click.js";
2
+ const DEBOUNCE_TIMEOUT_MS = 10_000;
3
+
4
+ /**
5
+ * @param {string} [extraClasses]
6
+ * @returns {HTMLButtonElement}
7
+ */
8
+ function makeButton(extraClasses = '') {
9
+ const button = document.createElement('button');
10
+ button.className = `prevent-multiple-clicks ${extraClasses}`.trim();
11
+ document.body.appendChild(button);
12
+ return button;
13
+ }
14
+
15
+ /**
16
+ * @param {HTMLButtonElement} button
17
+ * @returns {MouseEvent}
18
+ */
19
+ function click(button) {
20
+ const event = new MouseEvent('click', {
21
+ bubbles: true,
22
+ cancelable: true
23
+ });
24
+ button.dispatchEvent(event);
25
+ return event;
26
+ }
27
+
28
+ /**
29
+ * @param {HTMLButtonElement} button
30
+ * @param {string} key
31
+ * @returns {KeyboardEvent}
32
+ */
33
+ function keydown(button, key) {
34
+ const event = new KeyboardEvent('keydown', {
35
+ key,
36
+ bubbles: true,
37
+ cancelable: true
38
+ });
39
+ button.dispatchEvent(event);
40
+ return event;
41
+ }
42
+ afterEach(() => {
43
+ document.body.innerHTML = '';
44
+ });
45
+ describe('initDebounceClick', () => {
46
+ it('attaches a click listener to every .prevent-multiple-clicks button', () => {
47
+ const b1 = makeButton();
48
+ const b2 = makeButton();
49
+ const spy1 = jest.fn();
50
+ const spy2 = jest.fn();
51
+ b1.addEventListener('click', spy1);
52
+ b2.addEventListener('click', spy2);
53
+ initDebounceClick();
54
+ click(b1);
55
+ click(b2);
56
+ expect(spy1).toHaveBeenCalledTimes(1);
57
+ expect(spy2).toHaveBeenCalledTimes(1);
58
+ });
59
+ it('does not attach to buttons that lack the class', () => {
60
+ const plain = document.createElement('button');
61
+ document.body.appendChild(plain);
62
+ const spy = jest.fn();
63
+ plain.addEventListener('click', spy);
64
+ initDebounceClick();
65
+ click(plain);
66
+
67
+ // Listener still runs — debounce was never applied
68
+ expect(plain.dataset.debouncing).toBeUndefined();
69
+ });
70
+ });
71
+ describe('handleButtonClick (via initDebounceClick)', () => {
72
+ beforeEach(() => {
73
+ jest.useFakeTimers();
74
+ });
75
+ afterEach(() => {
76
+ jest.useRealTimers();
77
+ });
78
+ it('sets data-debouncing="true" on the first click', () => {
79
+ const button = makeButton();
80
+ initDebounceClick();
81
+ click(button);
82
+ expect(button.dataset.debouncing).toBe('true');
83
+ });
84
+ it('allows a second click after the debounce timeout expires', () => {
85
+ const button = makeButton();
86
+ const spy = jest.fn();
87
+ button.addEventListener('click', spy);
88
+ initDebounceClick();
89
+ click(button);
90
+ jest.advanceTimersByTime(DEBOUNCE_TIMEOUT_MS);
91
+ click(button);
92
+ expect(button.dataset.debouncing).toBe('true');
93
+ expect(spy).toHaveBeenCalledTimes(2);
94
+ });
95
+ it('removes data-debouncing after the timeout', () => {
96
+ const button = makeButton();
97
+ initDebounceClick();
98
+ click(button);
99
+ expect(button.dataset.debouncing).toBe('true');
100
+ jest.advanceTimersByTime(DEBOUNCE_TIMEOUT_MS);
101
+ expect(button.dataset.debouncing).toBeUndefined();
102
+ });
103
+ it('prevents the default action on a duplicate click', () => {
104
+ const button = makeButton();
105
+ initDebounceClick();
106
+ click(button);
107
+ const duplicate = new MouseEvent('click', {
108
+ bubbles: true,
109
+ cancelable: true
110
+ });
111
+ button.dispatchEvent(duplicate);
112
+ expect(duplicate.defaultPrevented).toBe(true);
113
+ });
114
+ it('stops immediate propagation on a duplicate click', () => {
115
+ const button = makeButton();
116
+ initDebounceClick();
117
+ click(button);
118
+ const subsequent = jest.fn();
119
+ button.addEventListener('click', subsequent);
120
+ click(button);
121
+ expect(subsequent).not.toHaveBeenCalled();
122
+ });
123
+ it('does not fire listeners registered after the handler for a click within the timeout window', () => {
124
+ const button = makeButton();
125
+ initDebounceClick();
126
+ // Registered after initDebounceClick so the debounce handler runs first and
127
+ // can call stopImmediatePropagation before this listener is reached.
128
+ const spy = jest.fn();
129
+ button.addEventListener('click', spy);
130
+ click(button);
131
+ jest.advanceTimersByTime(DEBOUNCE_TIMEOUT_MS - 1);
132
+ click(button);
133
+
134
+ // First click let through, second is still within the window — spy blocked
135
+ expect(spy).toHaveBeenCalledTimes(1);
136
+ });
137
+ });
138
+ describe('handleButtonKeydown (via initDebounceClick)', () => {
139
+ beforeEach(() => {
140
+ jest.useFakeTimers();
141
+ });
142
+ afterEach(() => {
143
+ jest.useRealTimers();
144
+ });
145
+ it('sets data-debouncing="true" on Enter', () => {
146
+ const button = makeButton();
147
+ initDebounceClick();
148
+ keydown(button, 'Enter');
149
+ expect(button.dataset.debouncing).toBe('true');
150
+ });
151
+ it('sets data-debouncing="true" on Space', () => {
152
+ const button = makeButton();
153
+ initDebounceClick();
154
+ keydown(button, ' ');
155
+ expect(button.dataset.debouncing).toBe('true');
156
+ });
157
+ it('ignores keys other than Enter and Space', () => {
158
+ const button = makeButton();
159
+ initDebounceClick();
160
+ keydown(button, 'Tab');
161
+ expect(button.dataset.debouncing).toBeUndefined();
162
+ });
163
+ it('prevents default on a duplicate Enter keydown', () => {
164
+ const button = makeButton();
165
+ initDebounceClick();
166
+ keydown(button, 'Enter');
167
+ const duplicate = keydown(button, 'Enter');
168
+ expect(duplicate.defaultPrevented).toBe(true);
169
+ });
170
+ it('stops immediate propagation on a duplicate keydown within the timeout window', () => {
171
+ const button = makeButton();
172
+ initDebounceClick();
173
+ keydown(button, 'Enter');
174
+ const spy = jest.fn();
175
+ button.addEventListener('keydown', spy);
176
+ keydown(button, 'Enter');
177
+ expect(spy).not.toHaveBeenCalled();
178
+ });
179
+ it('allows a second keydown after the debounce timeout expires', () => {
180
+ const button = makeButton();
181
+ const spy = jest.fn();
182
+ button.addEventListener('keydown', spy);
183
+ initDebounceClick();
184
+ keydown(button, 'Enter');
185
+ jest.advanceTimersByTime(DEBOUNCE_TIMEOUT_MS);
186
+ keydown(button, 'Enter');
187
+ expect(button.dataset.debouncing).toBe('true');
188
+ expect(spy).toHaveBeenCalledTimes(2);
189
+ });
190
+ });
191
+ //# sourceMappingURL=debounce-click.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"debounce-click.test.js","names":["initDebounceClick","DEBOUNCE_TIMEOUT_MS","makeButton","extraClasses","button","document","createElement","className","trim","body","appendChild","click","event","MouseEvent","bubbles","cancelable","dispatchEvent","keydown","key","KeyboardEvent","afterEach","innerHTML","describe","it","b1","b2","spy1","jest","fn","spy2","addEventListener","expect","toHaveBeenCalledTimes","plain","spy","dataset","debouncing","toBeUndefined","beforeEach","useFakeTimers","useRealTimers","toBe","advanceTimersByTime","duplicate","defaultPrevented","subsequent","not","toHaveBeenCalled"],"sources":["../../../src/client/javascripts/debounce-click.test.js"],"sourcesContent":["import { initDebounceClick } from '~/src/client/javascripts/debounce-click.js'\n\nconst DEBOUNCE_TIMEOUT_MS = 10_000\n\n/**\n * @param {string} [extraClasses]\n * @returns {HTMLButtonElement}\n */\nfunction makeButton(extraClasses = '') {\n const button = document.createElement('button')\n button.className = `prevent-multiple-clicks ${extraClasses}`.trim()\n document.body.appendChild(button)\n return button\n}\n\n/**\n * @param {HTMLButtonElement} button\n * @returns {MouseEvent}\n */\nfunction click(button) {\n const event = new MouseEvent('click', { bubbles: true, cancelable: true })\n button.dispatchEvent(event)\n return event\n}\n\n/**\n * @param {HTMLButtonElement} button\n * @param {string} key\n * @returns {KeyboardEvent}\n */\nfunction keydown(button, key) {\n const event = new KeyboardEvent('keydown', {\n key,\n bubbles: true,\n cancelable: true\n })\n button.dispatchEvent(event)\n return event\n}\n\nafterEach(() => {\n document.body.innerHTML = ''\n})\n\ndescribe('initDebounceClick', () => {\n it('attaches a click listener to every .prevent-multiple-clicks button', () => {\n const b1 = makeButton()\n const b2 = makeButton()\n const spy1 = jest.fn()\n const spy2 = jest.fn()\n b1.addEventListener('click', spy1)\n b2.addEventListener('click', spy2)\n\n initDebounceClick()\n\n click(b1)\n click(b2)\n\n expect(spy1).toHaveBeenCalledTimes(1)\n expect(spy2).toHaveBeenCalledTimes(1)\n })\n\n it('does not attach to buttons that lack the class', () => {\n const plain = document.createElement('button')\n document.body.appendChild(plain)\n const spy = jest.fn()\n plain.addEventListener('click', spy)\n\n initDebounceClick()\n click(plain)\n\n // Listener still runs — debounce was never applied\n expect(plain.dataset.debouncing).toBeUndefined()\n })\n})\n\ndescribe('handleButtonClick (via initDebounceClick)', () => {\n beforeEach(() => {\n jest.useFakeTimers()\n })\n\n afterEach(() => {\n jest.useRealTimers()\n })\n\n it('sets data-debouncing=\"true\" on the first click', () => {\n const button = makeButton()\n initDebounceClick()\n\n click(button)\n\n expect(button.dataset.debouncing).toBe('true')\n })\n\n it('allows a second click after the debounce timeout expires', () => {\n const button = makeButton()\n const spy = jest.fn()\n button.addEventListener('click', spy)\n initDebounceClick()\n\n click(button)\n jest.advanceTimersByTime(DEBOUNCE_TIMEOUT_MS)\n click(button)\n\n expect(button.dataset.debouncing).toBe('true')\n expect(spy).toHaveBeenCalledTimes(2)\n })\n\n it('removes data-debouncing after the timeout', () => {\n const button = makeButton()\n initDebounceClick()\n\n click(button)\n expect(button.dataset.debouncing).toBe('true')\n\n jest.advanceTimersByTime(DEBOUNCE_TIMEOUT_MS)\n expect(button.dataset.debouncing).toBeUndefined()\n })\n\n it('prevents the default action on a duplicate click', () => {\n const button = makeButton()\n initDebounceClick()\n click(button)\n\n const duplicate = new MouseEvent('click', {\n bubbles: true,\n cancelable: true\n })\n button.dispatchEvent(duplicate)\n\n expect(duplicate.defaultPrevented).toBe(true)\n })\n\n it('stops immediate propagation on a duplicate click', () => {\n const button = makeButton()\n initDebounceClick()\n click(button)\n\n const subsequent = jest.fn()\n button.addEventListener('click', subsequent)\n\n click(button)\n\n expect(subsequent).not.toHaveBeenCalled()\n })\n\n it('does not fire listeners registered after the handler for a click within the timeout window', () => {\n const button = makeButton()\n initDebounceClick()\n // Registered after initDebounceClick so the debounce handler runs first and\n // can call stopImmediatePropagation before this listener is reached.\n const spy = jest.fn()\n button.addEventListener('click', spy)\n\n click(button)\n jest.advanceTimersByTime(DEBOUNCE_TIMEOUT_MS - 1)\n click(button)\n\n // First click let through, second is still within the window — spy blocked\n expect(spy).toHaveBeenCalledTimes(1)\n })\n})\n\ndescribe('handleButtonKeydown (via initDebounceClick)', () => {\n beforeEach(() => {\n jest.useFakeTimers()\n })\n\n afterEach(() => {\n jest.useRealTimers()\n })\n\n it('sets data-debouncing=\"true\" on Enter', () => {\n const button = makeButton()\n initDebounceClick()\n\n keydown(button, 'Enter')\n\n expect(button.dataset.debouncing).toBe('true')\n })\n\n it('sets data-debouncing=\"true\" on Space', () => {\n const button = makeButton()\n initDebounceClick()\n\n keydown(button, ' ')\n\n expect(button.dataset.debouncing).toBe('true')\n })\n\n it('ignores keys other than Enter and Space', () => {\n const button = makeButton()\n initDebounceClick()\n\n keydown(button, 'Tab')\n\n expect(button.dataset.debouncing).toBeUndefined()\n })\n\n it('prevents default on a duplicate Enter keydown', () => {\n const button = makeButton()\n initDebounceClick()\n keydown(button, 'Enter')\n\n const duplicate = keydown(button, 'Enter')\n\n expect(duplicate.defaultPrevented).toBe(true)\n })\n\n it('stops immediate propagation on a duplicate keydown within the timeout window', () => {\n const button = makeButton()\n initDebounceClick()\n keydown(button, 'Enter')\n\n const spy = jest.fn()\n button.addEventListener('keydown', spy)\n keydown(button, 'Enter')\n\n expect(spy).not.toHaveBeenCalled()\n })\n\n it('allows a second keydown after the debounce timeout expires', () => {\n const button = makeButton()\n const spy = jest.fn()\n button.addEventListener('keydown', spy)\n initDebounceClick()\n\n keydown(button, 'Enter')\n jest.advanceTimersByTime(DEBOUNCE_TIMEOUT_MS)\n keydown(button, 'Enter')\n\n expect(button.dataset.debouncing).toBe('true')\n expect(spy).toHaveBeenCalledTimes(2)\n })\n})\n"],"mappings":"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","ignoreList":[]}
@@ -51,6 +51,23 @@ export function focusFeature(feature: Feature, mapProvider: MapLibreMap): void;
51
51
  * @param {boolean} [readonly] - render the list item in readonly mode
52
52
  */
53
53
  export function createFeatureHTML(feature: Feature, index: number, mapId: string, disabled?: boolean, readonly?: boolean): string;
54
+ /**
55
+ * Factory closure to manage the UI
56
+ * @param {GeoJSON} geojson - the features
57
+ * @param {InteractiveMap} map - the map
58
+ * @param {string} mapId - the ID of the map
59
+ * @param {HTMLDivElement} listEl - where to render the feature list
60
+ * @param {HTMLTextAreaElement} geospatialInput - the geospatial textarea
61
+ * @param { UIManagerOptions | undefined } options - extra options such as allowable geometry types
62
+ */
63
+ export function getUIManager(geojson: GeoJSON, map: InteractiveMap, mapId: string, listEl: HTMLDivElement, geospatialInput: HTMLTextAreaElement, options: UIManagerOptions | undefined): {
64
+ renderList: RenderList;
65
+ renderValue: RenderValue;
66
+ listEl: HTMLDivElement;
67
+ toggleActionButtons: (hidden: boolean) => void;
68
+ focusDescriptionInput: () => void;
69
+ getAllowableGeometryTypes: () => string[];
70
+ };
54
71
  export type GeoJSON = {
55
72
  /**
56
73
  * - the GeoJSON type string
@@ -210,4 +227,5 @@ import type { Feature } from '../../server/plugins/engine/types.js';
210
227
  import type { InteractiveMap } from '../../client/javascripts/map.js';
211
228
  import type { FeatureCollection } from '../../server/plugins/engine/types.js';
212
229
  import type { MapLibreMap } from '../../client/javascripts/map.js';
230
+ import type { UIManagerOptions } from '../../client/javascripts/map.js';
213
231
  import type { Geometry } from '../../server/plugins/engine/types.js';
@@ -186,7 +186,7 @@ export function processGeospatial(config, geospatial, index) {
186
186
  } = createMap(mapId, initConfig, config);
187
187
  const featuresManager = getFeaturesManager(geojson);
188
188
  const activeFeatureManager = getActiveFeatureManager();
189
- const geometryTypes = geospatial.dataset.geometrytypes ?? 'point,line,shape';
189
+ const geometryTypes = geospatial.dataset.geometrytypes;
190
190
  const options = {
191
191
  geometryTypes
192
192
  };
@@ -506,15 +506,15 @@ function getValueRenderer(geojson, geospatialInput) {
506
506
  * @param {string} mapId - the ID of the map
507
507
  * @param {HTMLDivElement} listEl - where to render the feature list
508
508
  * @param {HTMLTextAreaElement} geospatialInput - the geospatial textarea
509
- * @param {UIManagerOptions} options - extra options such as allowable geometry types
509
+ * @param { UIManagerOptions | undefined } options - extra options such as allowable geometry types
510
510
  */
511
- function getUIManager(geojson, map, mapId, listEl, geospatialInput, options) {
511
+ export function getUIManager(geojson, map, mapId, listEl, geospatialInput, options) {
512
512
  /**
513
513
  * Get a CSV list of geometry types the user can create
514
514
  * @returns {string[]}
515
515
  */
516
516
  function getAllowableGeometryTypes() {
517
- return options.geometryTypes ? options.geometryTypes.split(',') : [];
517
+ return options?.geometryTypes ? options.geometryTypes.split(',') : ['point', 'line', 'shape'];
518
518
  }
519
519
 
520
520
  /**