perfect-gui 5.1.3 → 6.0.0

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.
@@ -1,62 +1,159 @@
1
- import Button from './components/Button.js';
2
- import Slider from './components/Slider.js';
3
- import Image from './components/Image.js';
4
- import Toggle from './components/Toggle.js';
5
- import List from './components/List.js';
1
+ import Button from './components/Button';
2
+ import Slider from './components/Slider';
3
+ import Image from './components/Image';
4
+ import Toggle from './components/Toggle';
5
+ import List from './components/List';
6
6
  import Color from './components/Color.js';
7
- import Vector2 from './components/Vector2.js';
7
+ import Vector2 from './components/Vector2';
8
8
  import styles from './styles/styles.js';
9
9
 
10
- export default class GUI {
11
- constructor(options = {}) {
12
- this.firstParent = this;
10
+ import type { Options as SliderOptions } from './components/Slider';
11
+ import type { Options as ButtonOptions } from './components/Button';
12
+ import type { Options as ImageOptions } from './components/Image';
13
+ import type { Options as ToggleOptions } from './components/Toggle';
14
+ import type { Options as ListOptions, Values as ListValues } from './components/List';
15
+ import type { Options as ColorOptions } from './components/Color';
16
+ import type { Options as Vector2Options } from './components/Vector2';
17
+
18
+ declare global {
19
+ interface Window {
20
+ perfectGUI?: {
21
+ instanceCounter?: number;
22
+ };
23
+ }
24
+ }
13
25
 
14
- if (options.container) {
15
- this.container =
16
- typeof options.container == 'string'
17
- ? document.querySelector(options.container)
18
- : options.container;
19
- this.position_type = 'absolute';
20
- } else {
21
- this.container = document.body;
22
- this.position_type = 'fixed';
23
- }
26
+ type FolderOptions = {
27
+ container: HTMLElement;
28
+ wrapper: HTMLElement;
29
+ parent: GUI;
30
+ firstParent: GUI;
31
+ closed: boolean;
32
+ label: string;
33
+ color: string;
34
+ maxHeight: number;
35
+ }
24
36
 
25
- this.propReferences = [];
37
+ type Options = {
38
+ label?: string;
39
+ container?: HTMLElement | string;
40
+ isFolder?: boolean;
41
+ folderOptions?: FolderOptions;
42
+ onUpdate?: () => void;
43
+ color?: string;
44
+ opacity?: number;
45
+ position?: string;
46
+ maxHeight?: number;
47
+ width?: number;
48
+ closed?: boolean;
49
+ draggable?: boolean;
50
+ autoRepositioning?: boolean;
51
+ }
52
+
53
+ type ScreenCorner = {
54
+ x: 'left' | 'right';
55
+ y: 'top' | 'bottom';
56
+ };
57
+
58
+ export default class GUI {
59
+ public firstParent: GUI;
60
+ private container: HTMLElement = document.body;
61
+ public wrapper!: HTMLElement;
62
+ public folders: GUI[];
63
+ private tabsArray: any[];
64
+ private label: string = '';
65
+ private backgroundColor: string | null = null;
66
+ private opacity: number = 1;
67
+ private maxHeight: number = window.innerHeight;
68
+ private initMaxHeight: number | null = null;
69
+ private screenCorner!: ScreenCorner;
70
+ private instanceId: number = 0;
71
+ private wrapperWidth: number = 290;
72
+ private stylesheet: HTMLStyleElement | null = null;
73
+ private closed: boolean = false;
74
+ public domElement: HTMLElement | null = null;
75
+ private hasBeenDragged: boolean = false;
76
+ private xOffset: number = 0;
77
+ private yOffset: number = 0;
78
+ private position = {
79
+ initX: 0,
80
+ initY: 0,
81
+ prevX: 0,
82
+ prevY: 0,
83
+ x: 0,
84
+ y: 0,
85
+ };
86
+ // folder properties
87
+ public isFolder: boolean = false;
88
+ public parent: GUI | null = null;
89
+ public imageContainer: HTMLElement | null = null;
90
+ public header!: HTMLElement;
91
+ public previousInnerScroll: number = 0;
92
+
93
+ // tab-related properties added dynamically
94
+ public getTab?: (index: number) => GUI | null;
95
+ public getTabElement?: (index: number) => HTMLElement | null;
96
+ public setActiveTab?: (index: number) => void;
97
+ public getActiveTab?: () => number;
98
+ public element?: HTMLElement;
99
+
100
+ public propReferences: any[];
101
+ public onUpdate: (() => void) | null = null;
102
+
103
+ constructor(options: Options = {}, isFolder = false) {
104
+ this.firstParent = this;
26
105
  this.folders = [];
27
106
  this.tabsArray = [];
107
+ this.propReferences = [];
28
108
 
29
109
  if (options.isFolder) {
30
110
  this._folderConstructor(options.folderOptions);
31
111
  return;
32
112
  }
33
113
 
34
- if (typeof options.onUpdate == 'function') {
35
- this.onUpdate = options.onUpdate;
114
+ if (isFolder) {
115
+ return;
116
+ }
117
+
118
+ let positionType: 'absolute' | 'fixed' = 'fixed';
119
+
120
+ if (options.container) {
121
+ const container =
122
+ typeof options.container == 'string'
123
+ ? document.querySelector(options.container)
124
+ : options.container;
125
+ if (container instanceof HTMLElement) {
126
+ this.container = container;
127
+ positionType = 'absolute';
128
+ }
36
129
  }
130
+
131
+ this.screenCorner = this._parseScreenCorner(options.position);
37
132
 
38
- this.label =
39
- options != undefined && typeof options.label == 'string'
40
- ? options.label
41
- : '';
133
+ if (options.width) {
134
+ this.wrapperWidth = options.width;
135
+ }
136
+
137
+ if (typeof options.onUpdate === 'function') {
138
+ this.onUpdate = options.onUpdate;
139
+ }
140
+
141
+ this.label = typeof options.label == 'string' ? options.label : '';
42
142
  this.backgroundColor = options.color || null;
43
143
  this.opacity = options.opacity || 1;
44
-
45
- if (this.container == document.body) {
46
- this.maxHeight = window.innerHeight;
47
- } else {
144
+
145
+ if (this.container && this.container !== document.body) {
48
146
  this.maxHeight = Math.min(
49
147
  this.container.clientHeight,
50
148
  window.innerHeight,
51
149
  );
52
150
  }
151
+
53
152
  if (options.maxHeight) {
54
153
  this.initMaxHeight = options.maxHeight;
55
154
  this.maxHeight = Math.min(this.initMaxHeight, this.maxHeight);
56
155
  }
57
156
 
58
- this.screenCorner = this._parseScreenCorner(options.position);
59
-
60
157
  if (!window.perfectGUI) {
61
158
  window.perfectGUI = {};
62
159
  }
@@ -67,7 +164,6 @@ export default class GUI {
67
164
  }
68
165
  this.instanceId = window.perfectGUI.instanceCounter;
69
166
 
70
- this.wrapperWidth = options.width || 290;
71
167
  this.stylesheet = document.createElement('style');
72
168
  this.stylesheet.setAttribute('type', 'text/css');
73
169
  this.stylesheet.setAttribute('id', 'lm-gui-stylesheet');
@@ -75,14 +171,17 @@ export default class GUI {
75
171
 
76
172
  // Common styles
77
173
  if (this.instanceId == 0) {
78
- this._addStyles(`${styles(this.position_type)}`);
174
+ this._addStyles(`${styles(positionType)}`);
79
175
  }
80
176
 
81
177
  // Instance specific styles
82
178
  this._styleInstance();
83
179
 
84
180
  this.closed = !!options.closed;
85
- this._addWrapper();
181
+
182
+ const [domElement, wrapper] = this._addWrapper();
183
+ this.domElement = domElement;
184
+ this.wrapper = wrapper;
86
185
  this.domElement.setAttribute('data-corner-x', this.screenCorner.x);
87
186
  this.domElement.setAttribute('data-corner-y', this.screenCorner.y);
88
187
 
@@ -91,8 +190,9 @@ export default class GUI {
91
190
  }
92
191
  this._handleResize();
93
192
 
94
- this.hasBeenDragged = false;
95
- if (options.draggable == true) this._makeDraggable();
193
+ if (options.draggable == true) {
194
+ this._makeDraggable();
195
+ }
96
196
  }
97
197
 
98
198
  _styleInstance() {
@@ -108,7 +208,8 @@ export default class GUI {
108
208
 
109
209
  if (this.instanceId > 0) {
110
210
  let existingDomInstances =
111
- this.container.querySelectorAll('.p-gui');
211
+ this.container.querySelectorAll<HTMLElement>('.p-gui');
212
+
112
213
  for (let i = 0; i < existingDomInstances.length; i++) {
113
214
  if (
114
215
  this.screenCorner.y ==
@@ -130,6 +231,8 @@ export default class GUI {
130
231
  }
131
232
  this.yOffset = 0;
132
233
  this.position = {
234
+ initX: this.xOffset,
235
+ initY: this.yOffset,
133
236
  prevX: this.xOffset,
134
237
  prevY: this.yOffset,
135
238
  x: this.xOffset,
@@ -146,18 +249,24 @@ export default class GUI {
146
249
  }`);
147
250
  }
148
251
 
149
- _folderConstructor(folderOptions) {
150
- this.domElement = folderOptions.wrapper;
252
+ _folderConstructor(folderOptions: FolderOptions | undefined) {
253
+ if (!folderOptions) {
254
+ throw Error('[perfect-gui] folderOptions is undefined');
255
+ }
256
+
257
+ this.domElement = folderOptions.container;
151
258
  this.isFolder = true;
152
259
  this.parent = folderOptions.parent;
153
260
  this.firstParent = folderOptions.firstParent;
154
- this.wrapper = folderOptions.inner;
261
+ this.wrapper = folderOptions.wrapper;
155
262
  }
156
263
 
157
- _parseScreenCorner(position) {
158
- let parsedPosition = { x: 'right', y: 'top' };
264
+ _parseScreenCorner(position: string | undefined): ScreenCorner {
265
+ let parsedPosition: ScreenCorner = { x: 'right', y: 'top' };
159
266
 
160
- if (position == undefined) return parsedPosition;
267
+ if (position == undefined) {
268
+ return parsedPosition;
269
+ }
161
270
  else if (typeof position != 'string')
162
271
  console.error('[perfect-gui] Position must be a string.');
163
272
 
@@ -167,7 +276,7 @@ export default class GUI {
167
276
  return parsedPosition;
168
277
  }
169
278
 
170
- _getScrollbarWidth(element) {
279
+ _getScrollbarWidth(element: HTMLElement) {
171
280
  if (element === document.body) {
172
281
  return window.innerWidth - document.documentElement.clientWidth;
173
282
  } else {
@@ -176,6 +285,9 @@ export default class GUI {
176
285
  }
177
286
 
178
287
  _handleResize() {
288
+ if (!this.domElement) {
289
+ return;
290
+ }
179
291
  if (this.container == document.body) {
180
292
  this.maxHeight = window.innerHeight;
181
293
  } else {
@@ -201,7 +313,7 @@ export default class GUI {
201
313
  this.wrapperWidth -
202
314
  scrollbar_width;
203
315
  if (this.instanceId > 0) {
204
- let existingDomInstances = this.container.querySelectorAll(
316
+ let existingDomInstances = this.container.querySelectorAll<HTMLElement>(
205
317
  `.p-gui:not(#${this.domElement.id}):not([data-dragged])`,
206
318
  );
207
319
  for (let i = 0; i < existingDomInstances.length; i++) {
@@ -228,6 +340,8 @@ export default class GUI {
228
340
  }
229
341
  }
230
342
  this.position = {
343
+ initX: this.xOffset,
344
+ initY: this.yOffset,
231
345
  prevX: this.xOffset,
232
346
  prevY: this.yOffset,
233
347
  x: this.xOffset,
@@ -236,22 +350,25 @@ export default class GUI {
236
350
  this.domElement.style.transform = `translate3d(${this.position.x}px, ${this.position.y}px, 0)`;
237
351
  }
238
352
 
239
- _addStyles(styles) {
240
- this.stylesheet.innerHTML += styles;
353
+ _addStyles(styles: string) {
354
+ if (this.stylesheet) {
355
+ this.stylesheet.innerHTML += styles;
356
+ }
241
357
  }
242
358
 
243
- _addWrapper() {
244
- this.domElement = document.createElement('div');
245
- this.domElement.id = 'p-gui-' + this.instanceId;
246
- this.domElement.className = 'p-gui' + (this.closed ? ' p-gui--collapsed' : '');
247
- this.domElement.setAttribute('data-lenis-prevent', '');
248
- this.container.append(this.domElement);
359
+ _addWrapper(): [HTMLDivElement, HTMLDivElement] {
360
+ const domElement = document.createElement('div');
361
+ domElement.id = 'p-gui-' + this.instanceId;
362
+ domElement.className =
363
+ 'p-gui' + (this.closed ? ' p-gui--collapsed' : '');
364
+ domElement.setAttribute('data-lenis-prevent', '');
365
+ this.container.append(domElement);
249
366
 
250
367
  this.header = document.createElement('div');
251
368
  this.header.className = 'p-gui__header';
252
369
  this.header.textContent = this.label;
253
370
  this.header.style = `${this.backgroundColor ? 'border-color: ' + this.backgroundColor + ';' : ''}`;
254
- this.domElement.append(this.header);
371
+ domElement.append(this.header);
255
372
 
256
373
  const close_btn = document.createElement('div');
257
374
  close_btn.className = 'p-gui__header-close';
@@ -260,60 +377,61 @@ export default class GUI {
260
377
 
261
378
  const content = document.createElement('div');
262
379
  content.className = 'p-gui__content';
263
- this.domElement.append(content);
380
+ domElement.append(content);
381
+
382
+ const wrapper = document.createElement('div');
383
+ wrapper.className = 'p-gui__inner';
384
+ content.append(wrapper);
264
385
 
265
- this.wrapper = document.createElement('div');
266
- this.wrapper.className = 'p-gui__inner';
267
- content.append(this.wrapper);
386
+ return [domElement, wrapper];
268
387
  }
269
388
 
270
- button(params = {}) {
389
+ button(options: ButtonOptions = {}) {
271
390
  this.imageContainer = null;
272
- const instance = new Button(this, params);
391
+ const instance = new Button(this, options);
273
392
  return instance;
274
393
  }
275
394
 
276
- image(params = {}) {
395
+ image(path: string, options: ImageOptions = {}) {
277
396
  if (!this.imageContainer) {
278
397
  this.imageContainer = document.createElement('div');
279
398
  this.imageContainer.className = 'p-gui__image-container';
280
399
  this.wrapper.append(this.imageContainer);
281
400
  }
282
- const instance = new Image(this, params);
283
- return instance;
401
+ return new Image(this, path, options);
284
402
  }
285
403
 
286
- slider(arg1, arg2, arg3) {
404
+ slider(obj: any, prop: string, options: SliderOptions) {
287
405
  this.imageContainer = null;
288
- const instance = new Slider(this, arg1, arg2, arg3);
406
+ const instance = new Slider(this, obj, prop, options);
289
407
  return instance;
290
408
  }
291
409
 
292
- toggle(arg1, arg2, arg3) {
410
+ toggle(obj: any, prop: string, options: ToggleOptions) {
293
411
  this.imageContainer = null;
294
- const instance = new Toggle(this, arg1, arg2, arg3);
412
+ const instance = new Toggle(this, obj, prop, options);
295
413
  return instance;
296
414
  }
297
415
 
298
- list(arg1, arg2, arg3) {
416
+ list(obj: any, prop: string, values: ListValues, options: ListOptions) {
299
417
  this.imageContainer = null;
300
- const instance = new List(this, arg1, arg2, arg3);
418
+ const instance = new List(this, obj, prop, values, options);
301
419
  return instance;
302
420
  }
303
421
 
304
- color(arg1, arg2, arg3) {
422
+ color(obj: any, prop: string, options: ColorOptions) {
305
423
  this.imageContainer = null;
306
- const instance = new Color(this, arg1, arg2, arg3);
424
+ const instance = new Color(this, obj, prop, options);
307
425
  return instance;
308
426
  }
309
427
 
310
- vector2(arg1, arg2, arg3, arg4) {
428
+ vector2(obj: any, propX: string, propY: string, options: Vector2Options) {
311
429
  this.imageContainer = null;
312
- const instance = new Vector2(this, arg1, arg2, arg3, arg4);
430
+ const instance = new Vector2(this, obj, propX, propY, options);
313
431
  return instance;
314
432
  }
315
433
 
316
- folder(options = {}) {
434
+ folder(options: FolderOptions) {
317
435
  let closed =
318
436
  typeof options.closed == 'boolean' ? options.closed : false;
319
437
  let label = options.label || '';
@@ -333,7 +451,9 @@ export default class GUI {
333
451
  }
334
452
 
335
453
  let container_style = color ? `background-color: ${color};` : '';
336
- container_style += maxHeight ? `max-height: ${maxHeight}px; overflow-y: auto;` : '';
454
+ container_style += maxHeight
455
+ ? `max-height: ${maxHeight}px; overflow-y: auto;`
456
+ : '';
337
457
 
338
458
  const container = document.createElement('div');
339
459
  container.className = className;
@@ -357,20 +477,17 @@ export default class GUI {
357
477
  container.classList.toggle('p-gui__folder--closed');
358
478
  });
359
479
 
360
- let folder = new GUI({
361
- isFolder: true,
362
- folderOptions: {
363
- wrapper: container,
364
- inner: folderInner,
365
- parent: this,
366
- firstParent: this.firstParent,
367
- },
480
+ let folder = new Folder({
481
+ container,
482
+ wrapper: folderInner,
483
+ parent: this,
484
+ firstParent: this.firstParent,
368
485
  });
369
486
  this.folders.push(folder);
370
487
  return folder;
371
488
  }
372
489
 
373
- tabs(options = {}) {
490
+ tabs(options: { tabs?: string[]; active?: number; color?: string; maxHeight?: number} = {}) {
374
491
  const tabs = Array.isArray(options.tabs) ? options.tabs : [];
375
492
  const activeTab = options.active || 0;
376
493
  const color = options.color || null;
@@ -384,7 +501,9 @@ export default class GUI {
384
501
  }
385
502
 
386
503
  let container_style = color ? `background-color: ${color};` : '';
387
- container_style += maxHeight ? `max-height: ${maxHeight}px; overflow-y: auto;` : '';
504
+ container_style += maxHeight
505
+ ? `max-height: ${maxHeight}px; overflow-y: auto;`
506
+ : '';
388
507
 
389
508
  const container = document.createElement('div');
390
509
  container.className = className;
@@ -402,11 +521,14 @@ export default class GUI {
402
521
  container.append(tabsContent);
403
522
 
404
523
  // Store tab instances for later access
405
- const tabInstances = [];
524
+ const tabInstances: { gui: GUI; button: HTMLButtonElement; pane: HTMLElement }[] = [];
406
525
 
407
526
  tabs.forEach((tabConfig, index) => {
408
- const tabLabel = typeof tabConfig === 'string' ? tabConfig : tabConfig.label || `Tab ${index + 1}`;
409
-
527
+ const tabLabel =
528
+ typeof tabConfig === 'string'
529
+ ? tabConfig
530
+ : tabConfig.label || `Tab ${index + 1}`;
531
+
410
532
  // Create tab button
411
533
  const tabButton = document.createElement('button');
412
534
  tabButton.className = 'p-gui__tab-button';
@@ -425,20 +547,17 @@ export default class GUI {
425
547
  tabsContent.append(tabPane);
426
548
 
427
549
  // Create GUI instance for this tab
428
- const tabGUI = new GUI({
429
- isFolder: true,
430
- folderOptions: {
431
- wrapper: container,
432
- inner: tabPane,
433
- parent: this,
434
- firstParent: this.firstParent,
435
- },
550
+ const tabGUI = new Folder({
551
+ container,
552
+ wrapper: tabPane,
553
+ parent: this,
554
+ firstParent: this.firstParent,
436
555
  });
437
556
 
438
557
  tabInstances.push({
439
558
  gui: tabGUI,
440
559
  button: tabButton,
441
- pane: tabPane
560
+ pane: tabPane,
442
561
  });
443
562
 
444
563
  // Add click handler
@@ -456,27 +575,27 @@ export default class GUI {
456
575
  });
457
576
 
458
577
  // Create main tabs instance to return
459
- const tabsInstance = new GUI({
460
- isFolder: true,
461
- folderOptions: {
462
- wrapper: container,
463
- inner: tabInstances[activeTab]?.pane || document.createElement('div'),
464
- parent: this,
465
- firstParent: this.firstParent,
466
- },
578
+ const tabsInstance = new Folder({
579
+ container,
580
+ wrapper:
581
+ tabInstances[activeTab]?.pane ||
582
+ document.createElement('div'),
583
+ parent: this,
584
+ firstParent: this.firstParent,
467
585
  });
468
586
 
469
587
  // Add methods to access individual tabs
470
588
  tabsInstance.getTab = (index) => tabInstances[index]?.gui || null;
471
- tabsInstance.getTabElement = (index) => tabInstances[index]?.button || null;
589
+ tabsInstance.getTabElement = (index) =>
590
+ tabInstances[index]?.button || null;
472
591
  tabsInstance.setActiveTab = (index) => {
473
592
  if (index >= 0 && index < tabInstances.length) {
474
593
  tabInstances[index].button.click();
475
594
  }
476
595
  };
477
596
  tabsInstance.getActiveTab = () => {
478
- return tabInstances.findIndex(tab =>
479
- tab.button.classList.contains('p-gui__tab-button--active')
597
+ return tabInstances.findIndex((tab) =>
598
+ tab.button.classList.contains('p-gui__tab-button--active'),
480
599
  );
481
600
  };
482
601
 
@@ -487,49 +606,52 @@ export default class GUI {
487
606
  return tabsInstance;
488
607
  }
489
608
 
490
- _makeDraggable() {
491
- var that = this;
492
- this.header.addEventListener('pointerdown', dragMouseDown);
493
- this.header.addEventListener('pointerup', dragMouseUp);
609
+ private _onPointerDown = (evt: PointerEvent) => {
610
+ evt.preventDefault();
494
611
 
495
- function dragMouseDown(ev) {
496
- ev.preventDefault();
612
+ this.position.initX = this.position.x;
613
+ this.position.initY = this.position.y;
497
614
 
498
- that.position.initX = that.position.x;
499
- that.position.initY = that.position.y;
615
+ this.position.prevX = evt.clientX;
616
+ this.position.prevY = evt.clientY;
500
617
 
501
- that.position.prevX = ev.clientX;
502
- that.position.prevY = ev.clientY;
618
+ this.container.addEventListener('pointermove', this._onPointerMove);
619
+ document.addEventListener('pointerup', this._onPointerUp);
620
+ };
503
621
 
504
- document.addEventListener('pointermove', dragElement);
505
- }
622
+ private _onPointerMove = (evt: PointerEvent) => {
623
+ evt.preventDefault();
506
624
 
507
- function dragElement(ev) {
508
- ev.preventDefault();
509
- if (!that.hasBeenDragged) {
510
- that.hasBeenDragged = true;
511
- that.domElement.setAttribute('data-dragged', 'true');
512
- }
625
+ if (!this.hasBeenDragged) {
626
+ this.hasBeenDragged = true;
627
+ this.domElement?.setAttribute('data-dragged', 'true');
628
+ }
513
629
 
514
- that.position.x =
515
- that.position.initX + ev.clientX - that.position.prevX;
516
- that.position.y =
517
- that.position.initY + ev.clientY - that.position.prevY;
630
+ this.position.x = this.position.initX + evt.clientX - this.position.prevX;
631
+ this.position.y = this.position.initY + evt.clientY - this.position.prevY;
518
632
 
519
- that.domElement.style.transform =
520
- 'translate3d(' +
521
- that.position.x +
522
- 'px,' +
523
- that.position.y +
524
- 'px,0)';
633
+ if (this.domElement) {
634
+ this.domElement.style.transform = `translate3d(${this.position.x}px, ${this.position.y}px, 0)`;
525
635
  }
636
+ };
526
637
 
527
- function dragMouseUp(ev) {
528
- document.removeEventListener('pointermove', dragElement);
638
+ private _onPointerUp = () => {
639
+ this.container.removeEventListener('pointermove', this._onPointerMove);
640
+ document.removeEventListener('pointerup', this._onPointerUp);
641
+ };
642
+
643
+ _makeDraggable() {
644
+ if (!this.domElement || !this.header) {
645
+ return;
529
646
  }
647
+
648
+ this.header.addEventListener('pointerdown', this._onPointerDown);
530
649
  }
531
650
 
532
651
  toggleClose() {
652
+ if (!this.domElement) {
653
+ return;
654
+ }
533
655
  this.closed = !this.closed;
534
656
 
535
657
  if (this.closed) {
@@ -543,14 +665,16 @@ export default class GUI {
543
665
  }
544
666
 
545
667
  kill() {
546
- this.domElement.remove();
668
+ if (this.domElement) {
669
+ this.domElement.remove();
670
+ }
547
671
  }
548
672
 
549
- _mapLinear(x, a1, a2, b1, b2) {
673
+ _mapLinear(x: number, a1: number, a2: number, b1: number, b2: number): number {
550
674
  return b1 + ((x - a1) * (b2 - b1)) / (a2 - a1);
551
675
  }
552
676
 
553
- _countDecimals(num) {
677
+ _countDecimals(num: number) {
554
678
  // Convert the number to a string
555
679
  const numStr = num.toString();
556
680
 
@@ -574,3 +698,24 @@ export default class GUI {
574
698
  }
575
699
  }
576
700
  }
701
+
702
+ type FolderParams = {
703
+ container: HTMLElement,
704
+ wrapper: HTMLElement,
705
+ parent: GUI,
706
+ firstParent: GUI,
707
+ }
708
+
709
+ export class Folder extends GUI {
710
+ public parent: GUI;
711
+ public firstParent: GUI;
712
+
713
+ constructor(folderOptions: FolderParams) {
714
+ super({}, true);
715
+ this.isFolder = true;
716
+ this.domElement = folderOptions.container;
717
+ this.wrapper = folderOptions.wrapper;
718
+ this.parent = folderOptions.parent;
719
+ this.firstParent = folderOptions.firstParent;
720
+ }
721
+ }