angular-three-tweakpane 4.0.0-next.115 → 4.0.0-next.118

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,21 +1,120 @@
1
1
  import * as i0 from '@angular/core';
2
- import { input, booleanAttribute, inject, Injector, untracked, effect, Directive, numberAttribute, model, linkedSignal, computed, DestroyRef, InjectionToken, signal, output, DOCUMENT, afterNextRender, isSignal } from '@angular/core';
2
+ import { inject, ViewContainerRef, signal, Directive, Component, input, booleanAttribute, Injector, untracked, effect, numberAttribute, model, linkedSignal, computed, DestroyRef, InjectionToken, output, DOCUMENT, afterNextRender, isSignal, inputBinding, twoWayBinding } from '@angular/core';
3
3
  import { fromEventPattern, debounceTime } from 'rxjs';
4
4
  import { ClassName } from '@tweakpane/core';
5
5
  import { Pane } from 'tweakpane';
6
+ import { assertInjector } from 'ngxtension/assert-injector';
6
7
 
8
+ /**
9
+ * Directive that provides an anchor point for the `tweaks()` function to dynamically create Tweakpane controls.
10
+ *
11
+ * Add this directive to your `ngt-canvas` element to enable the `tweaks()` API:
12
+ *
13
+ * ```html
14
+ * <ngt-canvas tweakpaneAnchor>
15
+ * <ng-template #sceneGraph>
16
+ * <!-- your scene -->
17
+ * </ng-template>
18
+ * </ngt-canvas>
19
+ * ```
20
+ *
21
+ * Then use `tweaks()` in any component within the canvas:
22
+ *
23
+ * ```typescript
24
+ * const controls = tweaks('Physics', {
25
+ * gravity: { value: 9.8, min: 0, max: 20 },
26
+ * debug: this.debugMode, // two-way binding with existing signal
27
+ * });
28
+ * ```
29
+ */
30
+ class TweakpaneAnchor {
31
+ constructor() {
32
+ /**
33
+ * The ViewContainerRef where dynamic components will be created.
34
+ * Injected from the host element.
35
+ */
36
+ this.vcr = inject(ViewContainerRef);
37
+ /**
38
+ * Reference to the pane's TweakpaneFolder, set by TweakpanePane when it initializes.
39
+ * This is used as the parent folder for dynamically created folders.
40
+ */
41
+ this.paneFolder = signal(null, ...(ngDevMode ? [{ debugName: "paneFolder" }] : []));
42
+ /**
43
+ * Registry of folder ComponentRefs by folder name.
44
+ * Used to reuse existing folders when multiple `tweaks()` calls use the same folder name.
45
+ */
46
+ this.folders = {};
47
+ }
48
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: TweakpaneAnchor, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
49
+ static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.0.6", type: TweakpaneAnchor, isStandalone: true, selector: "[tweakpaneAnchor]", ngImport: i0 }); }
50
+ }
51
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: TweakpaneAnchor, decorators: [{
52
+ type: Directive,
53
+ args: [{ selector: '[tweakpaneAnchor]' }]
54
+ }] });
55
+ /**
56
+ * A minimal host component used for dynamically creating Tweakpane controls.
57
+ * This component serves as a host for directives like TweakpaneFolder, TweakpaneNumber, etc.
58
+ * when using the `tweaks()` function.
59
+ *
60
+ * @internal
61
+ */
62
+ class TweakpaneAnchorHost {
63
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: TweakpaneAnchorHost, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
64
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.0.6", type: TweakpaneAnchorHost, isStandalone: true, selector: "tweakpane-anchor-host", ngImport: i0, template: '', isInline: true }); }
65
+ }
66
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: TweakpaneAnchorHost, decorators: [{
67
+ type: Component,
68
+ args: [{
69
+ selector: 'tweakpane-anchor-host',
70
+ template: '',
71
+ }]
72
+ }] });
73
+
74
+ /**
75
+ * Directive that provides hidden and disabled state management for Tweakpane blades.
76
+ *
77
+ * This is a base directive used by other Tweakpane components to control
78
+ * visibility and interactivity of controls.
79
+ *
80
+ * @example
81
+ * ```html
82
+ * <tweakpane-number [hidden]="isHidden" [disabled]="isDisabled" [(value)]="speed" />
83
+ * ```
84
+ */
7
85
  class TweakpaneBlade {
8
86
  constructor() {
9
- this.hidden = input(false, ...(ngDevMode ? [{ debugName: "hidden", transform: booleanAttribute }] : [{ transform: booleanAttribute }]));
10
- this.disabled = input(false, ...(ngDevMode ? [{ debugName: "disabled", transform: booleanAttribute }] : [{ transform: booleanAttribute }]));
87
+ /**
88
+ * Whether the blade is hidden.
89
+ * @default false
90
+ */
91
+ this.hidden = input(false, { ...(ngDevMode ? { debugName: "hidden" } : {}), transform: booleanAttribute });
92
+ /**
93
+ * Whether the blade is disabled (non-interactive).
94
+ * @default false
95
+ */
96
+ this.disabled = input(false, { ...(ngDevMode ? { debugName: "disabled" } : {}), transform: booleanAttribute });
11
97
  this.injector = inject(Injector);
12
98
  }
99
+ /**
100
+ * Gets the current hidden and disabled values without tracking signal dependencies.
101
+ * Useful for initial configuration when creating Tweakpane components.
102
+ * @returns An object containing the current hidden and disabled states
103
+ */
13
104
  get snapshot() {
14
105
  return {
15
106
  hidden: untracked(this.hidden),
16
107
  disabled: untracked(this.disabled),
17
108
  };
18
109
  }
110
+ /**
111
+ * Synchronizes the hidden and disabled properties with a Tweakpane BladeApi.
112
+ * Creates a reactive effect that updates the API's state whenever
113
+ * the input values change.
114
+ *
115
+ * @param api - A function that returns the Tweakpane BladeApi (or null if not ready)
116
+ * @returns The created effect reference
117
+ */
19
118
  sync(api) {
20
119
  return effect(() => {
21
120
  const _api = api();
@@ -25,19 +124,44 @@ class TweakpaneBlade {
25
124
  _api.disabled = this.disabled();
26
125
  }, { injector: this.injector });
27
126
  }
28
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.7", ngImport: i0, type: TweakpaneBlade, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
29
- static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "20.1.7", type: TweakpaneBlade, isStandalone: true, selector: "tweakpane-blade", inputs: { hidden: { classPropertyName: "hidden", publicName: "hidden", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0 }); }
127
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: TweakpaneBlade, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
128
+ static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.0.6", type: TweakpaneBlade, isStandalone: true, selector: "tweakpane-blade", inputs: { hidden: { classPropertyName: "hidden", publicName: "hidden", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0 }); }
30
129
  }
31
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.7", ngImport: i0, type: TweakpaneBlade, decorators: [{
130
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: TweakpaneBlade, decorators: [{
32
131
  type: Directive,
33
132
  args: [{ selector: 'tweakpane-blade' }]
34
- }] });
133
+ }], propDecorators: { hidden: [{ type: i0.Input, args: [{ isSignal: true, alias: "hidden", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }] } });
35
134
 
135
+ /**
136
+ * Directive that provides debounced change event handling for Tweakpane controls.
137
+ *
138
+ * This is a base directive used by binding-based controls to prevent
139
+ * excessive updates when values change rapidly (e.g., during slider dragging).
140
+ *
141
+ * @example
142
+ * ```html
143
+ * <!-- Debounce value updates by 300ms -->
144
+ * <tweakpane-number [debounce]="300" [(value)]="speed" />
145
+ * ```
146
+ */
36
147
  class TweakpaneDebounce {
37
148
  constructor() {
38
- this.debounce = input(150, ...(ngDevMode ? [{ debugName: "debounce", transform: numberAttribute }] : [{ transform: numberAttribute }]));
149
+ /**
150
+ * The debounce delay in milliseconds before emitting value changes.
151
+ * @default 150
152
+ */
153
+ this.debounce = input(150, { ...(ngDevMode ? { debugName: "debounce" } : {}), transform: numberAttribute });
39
154
  this.injector = inject(Injector);
40
155
  }
156
+ /**
157
+ * Synchronizes debounced change events from a Tweakpane API with a callback.
158
+ * Creates a reactive effect that subscribes to change events and debounces them.
159
+ *
160
+ * @typeParam T - The type of value being tracked
161
+ * @param api - A function that returns the Tweakpane API object with on/off methods (or null if not ready)
162
+ * @param cb - Callback function invoked with the change event after debounce delay
163
+ * @returns The created effect reference
164
+ */
41
165
  sync(api, cb) {
42
166
  return effect((onCleanup) => {
43
167
  const _api = api();
@@ -53,21 +177,51 @@ class TweakpaneDebounce {
53
177
  });
54
178
  }, { injector: this.injector });
55
179
  }
56
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.7", ngImport: i0, type: TweakpaneDebounce, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
57
- static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "20.1.7", type: TweakpaneDebounce, isStandalone: true, inputs: { debounce: { classPropertyName: "debounce", publicName: "debounce", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0 }); }
180
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: TweakpaneDebounce, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
181
+ static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.0.6", type: TweakpaneDebounce, isStandalone: true, inputs: { debounce: { classPropertyName: "debounce", publicName: "debounce", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0 }); }
58
182
  }
59
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.7", ngImport: i0, type: TweakpaneDebounce, decorators: [{
183
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: TweakpaneDebounce, decorators: [{
60
184
  type: Directive
61
- }] });
185
+ }], propDecorators: { debounce: [{ type: i0.Input, args: [{ isSignal: true, alias: "debounce", required: false }] }] } });
62
186
 
187
+ /**
188
+ * Directive that provides title functionality for Tweakpane components.
189
+ *
190
+ * This is a base directive used by other Tweakpane components (like `TweakpaneFolder`,
191
+ * `TweakpanePane`, `TweakpaneButton`) to manage their titles reactively.
192
+ *
193
+ * @example
194
+ * ```html
195
+ * <tweakpane-folder title="Settings">
196
+ * <!-- folder contents -->
197
+ * </tweakpane-folder>
198
+ * ```
199
+ */
63
200
  class TweakpaneTitle {
64
201
  constructor() {
202
+ /**
203
+ * The title text to display.
204
+ * @default 'TweakPane Title'
205
+ */
65
206
  this.title = input('TweakPane Title', ...(ngDevMode ? [{ debugName: "title" }] : []));
66
207
  this.injector = inject(Injector);
67
208
  }
209
+ /**
210
+ * Gets the current title value without tracking signal dependencies.
211
+ * Useful for initial configuration when creating Tweakpane components.
212
+ * @returns The current title string value
213
+ */
68
214
  get snapshot() {
69
215
  return untracked(this.title);
70
216
  }
217
+ /**
218
+ * Synchronizes the title property with a Tweakpane API object.
219
+ * Creates a reactive effect that updates the API's title whenever
220
+ * the input title changes.
221
+ *
222
+ * @param api - A function that returns the Tweakpane API object (or null if not ready)
223
+ * @returns The created effect reference
224
+ */
71
225
  sync(api) {
72
226
  return effect(() => {
73
227
  const _api = api();
@@ -76,20 +230,53 @@ class TweakpaneTitle {
76
230
  _api.title = this.title();
77
231
  }, { injector: this.injector });
78
232
  }
79
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.7", ngImport: i0, type: TweakpaneTitle, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
80
- static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "20.1.7", type: TweakpaneTitle, isStandalone: true, inputs: { title: { classPropertyName: "title", publicName: "title", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0 }); }
233
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: TweakpaneTitle, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
234
+ static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.0.6", type: TweakpaneTitle, isStandalone: true, inputs: { title: { classPropertyName: "title", publicName: "title", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0 }); }
81
235
  }
82
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.7", ngImport: i0, type: TweakpaneTitle, decorators: [{
236
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: TweakpaneTitle, decorators: [{
83
237
  type: Directive
84
- }] });
238
+ }], propDecorators: { title: [{ type: i0.Input, args: [{ isSignal: true, alias: "title", required: false }] }] } });
85
239
 
240
+ /**
241
+ * Directive for creating collapsible folders within a Tweakpane.
242
+ *
243
+ * Folders are used to organize controls into logical groups. They can be
244
+ * nested within other folders or directly within a pane.
245
+ *
246
+ * @example
247
+ * ```html
248
+ * <tweakpane-pane>
249
+ * <tweakpane-folder title="Physics" [expanded]="true">
250
+ * <tweakpane-number label="Gravity" [(value)]="gravity" />
251
+ * <tweakpane-number label="Friction" [(value)]="friction" />
252
+ * </tweakpane-folder>
253
+ *
254
+ * <tweakpane-folder title="Appearance">
255
+ * <tweakpane-color label="Color" [(value)]="color" />
256
+ * </tweakpane-folder>
257
+ * </tweakpane-pane>
258
+ * ```
259
+ */
86
260
  class TweakpaneFolder {
87
261
  constructor() {
262
+ /**
263
+ * Whether the folder is expanded (open) or collapsed.
264
+ * Supports two-way binding with `[(expanded)]`.
265
+ * @default false
266
+ */
88
267
  this.expanded = model(false, ...(ngDevMode ? [{ debugName: "expanded" }] : []));
89
268
  this.title = inject(TweakpaneTitle);
90
269
  this.blade = inject(TweakpaneBlade);
91
270
  this.parent = inject(TweakpaneFolder, { skipSelf: true, optional: true });
92
- this.parentFolder = linkedSignal(() => this.parent?.folder());
271
+ /**
272
+ * Signal containing the parent folder API.
273
+ * Automatically links to the parent folder in the component hierarchy.
274
+ */
275
+ this.parentFolder = linkedSignal(() => this.parent?.folder(), ...(ngDevMode ? [{ debugName: "parentFolder" }] : []));
276
+ /**
277
+ * Computed signal containing the Tweakpane FolderApi for this folder.
278
+ * Returns null if the parent folder is not yet available.
279
+ */
93
280
  this.folder = computed(() => {
94
281
  const parent = this.parentFolder();
95
282
  if (!parent)
@@ -103,6 +290,11 @@ class TweakpaneFolder {
103
290
  hidden: this.blade.snapshot.hidden,
104
291
  });
105
292
  }, ...(ngDevMode ? [{ debugName: "folder" }] : []));
293
+ /**
294
+ * Internal flag indicating whether this directive creates its own folder
295
+ * or reuses the parent folder. Set to `false` by `TweakpanePane`.
296
+ * @internal
297
+ */
106
298
  this.isSelf = true;
107
299
  this.title.sync(this.folder);
108
300
  this.blade.sync(this.folder);
@@ -122,10 +314,10 @@ class TweakpaneFolder {
122
314
  this.folder()?.dispose();
123
315
  });
124
316
  }
125
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.7", ngImport: i0, type: TweakpaneFolder, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
126
- static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "20.1.7", type: TweakpaneFolder, isStandalone: true, selector: "tweakpane-folder", inputs: { expanded: { classPropertyName: "expanded", publicName: "expanded", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { expanded: "expandedChange" }, hostDirectives: [{ directive: TweakpaneTitle, inputs: ["title", "title"] }, { directive: TweakpaneBlade, inputs: ["hidden", "hidden", "disabled", "disabled"] }], ngImport: i0 }); }
317
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: TweakpaneFolder, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
318
+ static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.0.6", type: TweakpaneFolder, isStandalone: true, selector: "tweakpane-folder", inputs: { expanded: { classPropertyName: "expanded", publicName: "expanded", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { expanded: "expandedChange" }, hostDirectives: [{ directive: TweakpaneTitle, inputs: ["title", "title"] }, { directive: TweakpaneBlade, inputs: ["hidden", "hidden", "disabled", "disabled"] }], ngImport: i0 }); }
127
319
  }
128
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.7", ngImport: i0, type: TweakpaneFolder, decorators: [{
320
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: TweakpaneFolder, decorators: [{
129
321
  type: Directive,
130
322
  args: [{
131
323
  selector: 'tweakpane-folder',
@@ -134,17 +326,49 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.7", ngImpor
134
326
  { directive: TweakpaneBlade, inputs: ['hidden', 'disabled'] },
135
327
  ],
136
328
  }]
137
- }], ctorParameters: () => [] });
329
+ }], ctorParameters: () => [], propDecorators: { expanded: [{ type: i0.Input, args: [{ isSignal: true, alias: "expanded", required: false }] }, { type: i0.Output, args: ["expandedChange"] }] } });
138
330
 
331
+ /**
332
+ * Directive that provides label and tag functionality for Tweakpane controls.
333
+ *
334
+ * This is a base directive used by binding-based controls (like `TweakpaneNumber`,
335
+ * `TweakpaneText`, `TweakpaneCheckbox`) to manage their labels and optional tags.
336
+ *
337
+ * @example
338
+ * ```html
339
+ * <tweakpane-number label="Speed" tag="m/s" [(value)]="speed" />
340
+ * ```
341
+ */
139
342
  class TweakpaneLabel {
140
343
  constructor() {
344
+ /**
345
+ * The label text displayed next to the control.
346
+ * @default ''
347
+ */
141
348
  this.label = input('', ...(ngDevMode ? [{ debugName: "label" }] : []));
349
+ /**
350
+ * An optional tag displayed alongside the label (e.g., units like "px", "m/s").
351
+ * @default ''
352
+ */
142
353
  this.tag = input('', ...(ngDevMode ? [{ debugName: "tag" }] : []));
143
354
  this.injector = inject(Injector);
144
355
  }
356
+ /**
357
+ * Gets the current label and tag values without tracking signal dependencies.
358
+ * Useful for initial configuration when creating Tweakpane controls.
359
+ * @returns An object containing the current label and tag values
360
+ */
145
361
  get snapshot() {
146
362
  return { label: untracked(this.label), tag: untracked(this.tag) };
147
363
  }
364
+ /**
365
+ * Synchronizes the label and tag properties with a Tweakpane API object.
366
+ * Creates a reactive effect that updates the API's label and tag
367
+ * whenever the input values change.
368
+ *
369
+ * @param api - A function that returns the Tweakpane API object (or null if not ready)
370
+ * @returns The created effect reference
371
+ */
148
372
  sync(api) {
149
373
  return effect(() => {
150
374
  const _api = api();
@@ -156,18 +380,77 @@ class TweakpaneLabel {
156
380
  }
157
381
  }, { injector: this.injector });
158
382
  }
159
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.7", ngImport: i0, type: TweakpaneLabel, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
160
- static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "20.1.7", type: TweakpaneLabel, isStandalone: true, inputs: { label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, tag: { classPropertyName: "tag", publicName: "tag", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0 }); }
383
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: TweakpaneLabel, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
384
+ static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.0.6", type: TweakpaneLabel, isStandalone: true, inputs: { label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, tag: { classPropertyName: "tag", publicName: "tag", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0 }); }
161
385
  }
162
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.7", ngImport: i0, type: TweakpaneLabel, decorators: [{
386
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: TweakpaneLabel, decorators: [{
163
387
  type: Directive
164
- }] });
388
+ }], propDecorators: { label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], tag: [{ type: i0.Input, args: [{ isSignal: true, alias: "tag", required: false }] }] } });
165
389
 
390
+ /**
391
+ * Injection token used to configure value transformation when TweakpaneBinding
392
+ * is used as a host directive.
393
+ *
394
+ * When set to `true`, values pass through unchanged.
395
+ * When set to an object with `in` and `out` functions, values are transformed:
396
+ * - `in`: Transforms the input value before binding to Tweakpane
397
+ * - `out`: Transforms the Tweakpane value before emitting to the model
398
+ *
399
+ * @internal
400
+ */
166
401
  const NGT_TWEAK_BINDING_AS_HOST = new InjectionToken('hostDirective NgtTweakBinding', { factory: () => null });
402
+ /**
403
+ * Provides configuration for TweakpaneBinding when used as a host directive.
404
+ *
405
+ * @typeParam TIn - The input value type (from the component)
406
+ * @typeParam TOut - The output value type (for Tweakpane)
407
+ * @param inOut - Optional object containing `in` and `out` transformation functions
408
+ * @returns A provider configuration object
409
+ *
410
+ * @example
411
+ * ```typescript
412
+ * // Simple passthrough (no transformation)
413
+ * providers: [provideTweakBindingAsHost()]
414
+ *
415
+ * // With value transformation (e.g., array to object)
416
+ * providers: [provideTweakBindingAsHost({
417
+ * in: (value) => ({ x: value[0], y: value[1] }),
418
+ * out: (value) => [value.x, value.y]
419
+ * })]
420
+ * ```
421
+ */
167
422
  function provideTweakBindingAsHost(inOut) {
168
423
  return { provide: NGT_TWEAK_BINDING_AS_HOST, useValue: inOut ?? true };
169
424
  }
425
+ /**
426
+ * Base directive for creating Tweakpane bindings (two-way data binding controls).
427
+ *
428
+ * This directive provides the core functionality for binding Angular values to
429
+ * Tweakpane controls. It handles:
430
+ * - Creating the binding on the parent folder
431
+ * - Syncing label, blade, and debounce settings
432
+ * - Value transformation when used as a host directive
433
+ * - Automatic cleanup on destroy
434
+ *
435
+ * Most commonly used as a host directive by specific control types like
436
+ * `TweakpaneNumber`, `TweakpaneText`, `TweakpaneCheckbox`, etc.
437
+ *
438
+ * @typeParam TValue - The type of value being bound
439
+ *
440
+ * @example
441
+ * ```html
442
+ * <!-- Direct usage (rarely needed) -->
443
+ * <tweakpane-binding [(value)]="myValue" label="My Value" />
444
+ *
445
+ * <!-- More commonly used via specific controls -->
446
+ * <tweakpane-number [(value)]="speed" label="Speed" />
447
+ * ```
448
+ */
170
449
  class TweakpaneBinding {
450
+ /**
451
+ * Gets the bindable object with optional value transformation.
452
+ * @returns An object with a `value` property suitable for Tweakpane binding
453
+ */
171
454
  get bindableObject() {
172
455
  let value = untracked(this.value);
173
456
  if (this.asHostDirective && typeof this.asHostDirective === 'object') {
@@ -176,6 +459,9 @@ class TweakpaneBinding {
176
459
  return { value };
177
460
  }
178
461
  constructor() {
462
+ /**
463
+ * The bound value. Supports two-way binding with `[(value)]`.
464
+ */
179
465
  this.value = model.required(...(ngDevMode ? [{ debugName: "value" }] : []));
180
466
  this.debounce = inject(TweakpaneDebounce);
181
467
  this.label = inject(TweakpaneLabel);
@@ -219,15 +505,23 @@ class TweakpaneBinding {
219
505
  this.bindingApi()?.dispose();
220
506
  });
221
507
  }
508
+ /**
509
+ * Synchronizes additional binding parameters with the Tweakpane binding.
510
+ * Called by specific control directives to add control-specific parameters
511
+ * (e.g., min/max for numbers, options for lists).
512
+ *
513
+ * @param params - A function that returns the binding parameters
514
+ * @returns The created effect reference
515
+ */
222
516
  syncBindingParams(params) {
223
517
  return effect(() => {
224
518
  this.bindingParams.set(params());
225
519
  }, { injector: this.injector });
226
520
  }
227
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.7", ngImport: i0, type: TweakpaneBinding, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
228
- static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "20.1.7", type: TweakpaneBinding, isStandalone: true, selector: "tweakpane-binding", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: true, transformFunction: null } }, outputs: { value: "valueChange" }, hostDirectives: [{ directive: TweakpaneBlade, inputs: ["disabled", "disabled", "hidden", "hidden"] }, { directive: TweakpaneDebounce, inputs: ["debounce", "debounce"] }, { directive: TweakpaneLabel, inputs: ["label", "label", "tag", "tag"] }], ngImport: i0 }); }
521
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: TweakpaneBinding, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
522
+ static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.0.6", type: TweakpaneBinding, isStandalone: true, selector: "tweakpane-binding", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: true, transformFunction: null } }, outputs: { value: "valueChange" }, hostDirectives: [{ directive: TweakpaneBlade, inputs: ["disabled", "disabled", "hidden", "hidden"] }, { directive: TweakpaneDebounce, inputs: ["debounce", "debounce"] }, { directive: TweakpaneLabel, inputs: ["label", "label", "tag", "tag"] }], ngImport: i0 }); }
229
523
  }
230
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.7", ngImport: i0, type: TweakpaneBinding, decorators: [{
524
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: TweakpaneBinding, decorators: [{
231
525
  type: Directive,
232
526
  args: [{
233
527
  selector: 'tweakpane-binding',
@@ -237,10 +531,29 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.7", ngImpor
237
531
  { directive: TweakpaneLabel, inputs: ['label', 'tag'] },
238
532
  ],
239
533
  }]
240
- }], ctorParameters: () => [] });
534
+ }], ctorParameters: () => [], propDecorators: { value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: true }] }, { type: i0.Output, args: ["valueChange"] }] } });
241
535
 
536
+ /**
537
+ * Directive for creating a clickable button in Tweakpane.
538
+ *
539
+ * Buttons are used to trigger actions (like reset, randomize, etc.)
540
+ * and emit click events when pressed.
541
+ *
542
+ * @example
543
+ * ```html
544
+ * <tweakpane-pane>
545
+ * <tweakpane-button title="Reset" (click)="onReset()" />
546
+ * <tweakpane-button title="Randomize" label="Action" (click)="onRandomize()" />
547
+ * <tweakpane-button title="Save" [disabled]="!canSave" (click)="onSave()" />
548
+ * </tweakpane-pane>
549
+ * ```
550
+ */
242
551
  class TweakpaneButton {
243
552
  constructor() {
553
+ /**
554
+ * Event emitted when the button is clicked.
555
+ * Provides the Tweakpane mouse event with the ButtonApi.
556
+ */
244
557
  this.click = output();
245
558
  this.title = inject(TweakpaneTitle);
246
559
  this.label = inject(TweakpaneLabel);
@@ -274,10 +587,10 @@ class TweakpaneButton {
274
587
  this.buttonApi()?.dispose();
275
588
  });
276
589
  }
277
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.7", ngImport: i0, type: TweakpaneButton, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
278
- static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "20.1.7", type: TweakpaneButton, isStandalone: true, selector: "tweakpane-button", outputs: { click: "click" }, hostDirectives: [{ directive: TweakpaneTitle, inputs: ["title", "title"] }, { directive: TweakpaneLabel, inputs: ["label", "label"] }, { directive: TweakpaneBlade, inputs: ["hidden", "hidden", "disabled", "disabled"] }], ngImport: i0 }); }
590
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: TweakpaneButton, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
591
+ static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.0.6", type: TweakpaneButton, isStandalone: true, selector: "tweakpane-button", outputs: { click: "click" }, hostDirectives: [{ directive: TweakpaneTitle, inputs: ["title", "title"] }, { directive: TweakpaneLabel, inputs: ["label", "label"] }, { directive: TweakpaneBlade, inputs: ["hidden", "hidden", "disabled", "disabled"] }], ngImport: i0 }); }
279
592
  }
280
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.7", ngImport: i0, type: TweakpaneButton, decorators: [{
593
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: TweakpaneButton, decorators: [{
281
594
  type: Directive,
282
595
  args: [{
283
596
  selector: 'tweakpane-button',
@@ -287,47 +600,118 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.7", ngImpor
287
600
  { directive: TweakpaneBlade, inputs: ['hidden', 'disabled'] },
288
601
  ],
289
602
  }]
290
- }], ctorParameters: () => [] });
603
+ }], ctorParameters: () => [], propDecorators: { click: [{ type: i0.Output, args: ["click"] }] } });
291
604
 
605
+ /**
606
+ * Directive for creating a boolean checkbox control in Tweakpane.
607
+ *
608
+ * Provides two-way binding for boolean values with a checkbox UI.
609
+ *
610
+ * @example
611
+ * ```html
612
+ * <tweakpane-pane>
613
+ * <tweakpane-checkbox label="Debug Mode" [(value)]="debugMode" />
614
+ * <tweakpane-checkbox label="Wireframe" [(value)]="showWireframe" [disabled]="!debugMode()" />
615
+ * </tweakpane-pane>
616
+ * ```
617
+ */
292
618
  class TweakpaneCheckbox {
293
619
  constructor() {
620
+ /**
621
+ * Additional Tweakpane boolean input parameters.
622
+ * @default {}
623
+ */
294
624
  this.params = input({}, ...(ngDevMode ? [{ debugName: "params" }] : []));
295
625
  this.binding = inject(TweakpaneBinding);
296
626
  this.binding.syncBindingParams(this.params);
297
627
  }
298
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.7", ngImport: i0, type: TweakpaneCheckbox, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
299
- static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "20.1.7", type: TweakpaneCheckbox, isStandalone: true, selector: "tweakpane-checkbox", inputs: { params: { classPropertyName: "params", publicName: "params", isSignal: true, isRequired: false, transformFunction: null } }, providers: [provideTweakBindingAsHost()], hostDirectives: [{ directive: TweakpaneBinding, inputs: ["value", "value"], outputs: ["valueChange", "valueChange"] }], ngImport: i0 }); }
628
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: TweakpaneCheckbox, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
629
+ static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.0.6", type: TweakpaneCheckbox, isStandalone: true, selector: "tweakpane-checkbox", inputs: { params: { classPropertyName: "params", publicName: "params", isSignal: true, isRequired: false, transformFunction: null } }, providers: [provideTweakBindingAsHost()], hostDirectives: [{ directive: TweakpaneBinding, inputs: ["value", "value"], outputs: ["valueChange", "valueChange"] }], ngImport: i0 }); }
300
630
  }
301
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.7", ngImport: i0, type: TweakpaneCheckbox, decorators: [{
631
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: TweakpaneCheckbox, decorators: [{
302
632
  type: Directive,
303
633
  args: [{
304
634
  selector: 'tweakpane-checkbox',
305
635
  hostDirectives: [{ directive: TweakpaneBinding, inputs: ['value'], outputs: ['valueChange'] }],
306
636
  providers: [provideTweakBindingAsHost()],
307
637
  }]
308
- }], ctorParameters: () => [] });
638
+ }], ctorParameters: () => [], propDecorators: { params: [{ type: i0.Input, args: [{ isSignal: true, alias: "params", required: false }] }] } });
309
639
 
640
+ /**
641
+ * Directive for creating a color picker control in Tweakpane.
642
+ *
643
+ * Provides two-way binding for color values (as hex strings) with a
644
+ * color picker UI that supports RGB, HSL, and alpha.
645
+ *
646
+ * @example
647
+ * ```html
648
+ * <tweakpane-pane>
649
+ * <tweakpane-color label="Background" [(value)]="backgroundColor" />
650
+ * <tweakpane-color label="Accent" [(value)]="accentColor" [params]="{ alpha: true }" />
651
+ * </tweakpane-pane>
652
+ * ```
653
+ */
310
654
  class TweakpaneColor {
311
655
  constructor() {
656
+ /**
657
+ * Additional Tweakpane color input parameters.
658
+ * Can include options like `alpha`, `color.type`, etc.
659
+ * @default {}
660
+ */
312
661
  this.params = input({}, ...(ngDevMode ? [{ debugName: "params" }] : []));
313
662
  this.binding = inject(TweakpaneBinding);
314
663
  this.binding.syncBindingParams(this.params);
315
664
  }
316
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.7", ngImport: i0, type: TweakpaneColor, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
317
- static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "20.1.7", type: TweakpaneColor, isStandalone: true, selector: "tweakpane-color", inputs: { params: { classPropertyName: "params", publicName: "params", isSignal: true, isRequired: false, transformFunction: null } }, providers: [provideTweakBindingAsHost()], hostDirectives: [{ directive: TweakpaneBinding, inputs: ["value", "value"], outputs: ["valueChange", "valueChange"] }], ngImport: i0 }); }
665
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: TweakpaneColor, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
666
+ static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.0.6", type: TweakpaneColor, isStandalone: true, selector: "tweakpane-color", inputs: { params: { classPropertyName: "params", publicName: "params", isSignal: true, isRequired: false, transformFunction: null } }, providers: [provideTweakBindingAsHost()], hostDirectives: [{ directive: TweakpaneBinding, inputs: ["value", "value"], outputs: ["valueChange", "valueChange"] }], ngImport: i0 }); }
318
667
  }
319
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.7", ngImport: i0, type: TweakpaneColor, decorators: [{
668
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: TweakpaneColor, decorators: [{
320
669
  type: Directive,
321
670
  args: [{
322
671
  selector: 'tweakpane-color',
323
672
  hostDirectives: [{ directive: TweakpaneBinding, inputs: ['value'], outputs: ['valueChange'] }],
324
673
  providers: [provideTweakBindingAsHost()],
325
674
  }]
326
- }], ctorParameters: () => [] });
675
+ }], ctorParameters: () => [], propDecorators: { params: [{ type: i0.Input, args: [{ isSignal: true, alias: "params", required: false }] }] } });
327
676
 
677
+ /**
678
+ * Directive for creating a dropdown list/select control in Tweakpane.
679
+ *
680
+ * Provides two-way binding for selecting from a list of options.
681
+ * Options can be provided as an array or as a key-value object.
682
+ *
683
+ * @typeParam TOptionValue - The type of values in the list
684
+ *
685
+ * @example
686
+ * ```html
687
+ * <tweakpane-pane>
688
+ * <!-- Options as array -->
689
+ * <tweakpane-list
690
+ * label="Mode"
691
+ * [(value)]="mode"
692
+ * [options]="['normal', 'debug', 'performance']"
693
+ * />
694
+ *
695
+ * <!-- Options as object (label: value mapping) -->
696
+ * <tweakpane-list
697
+ * label="Quality"
698
+ * [(value)]="quality"
699
+ * [options]="{ 'Low': 1, 'Medium': 2, 'High': 3, 'Ultra': 4 }"
700
+ * />
701
+ * </tweakpane-pane>
702
+ * ```
703
+ */
328
704
  class TweakpaneList {
329
705
  constructor() {
706
+ /**
707
+ * The currently selected value. Supports two-way binding with `[(value)]`.
708
+ */
330
709
  this.value = model.required(...(ngDevMode ? [{ debugName: "value" }] : []));
710
+ /**
711
+ * The list options. Can be:
712
+ * - An array of values (labels will be stringified values)
713
+ * - An object mapping display labels to values
714
+ */
331
715
  this.options = input.required(...(ngDevMode ? [{ debugName: "options" }] : []));
332
716
  this.blade = inject(TweakpaneBlade);
333
717
  this.debounce = inject(TweakpaneDebounce);
@@ -369,10 +753,10 @@ class TweakpaneList {
369
753
  this.listApi()?.dispose();
370
754
  });
371
755
  }
372
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.7", ngImport: i0, type: TweakpaneList, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
373
- static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "20.1.7", type: TweakpaneList, isStandalone: true, selector: "tweakpane-list", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: true, transformFunction: null }, options: { classPropertyName: "options", publicName: "options", isSignal: true, isRequired: true, transformFunction: null } }, outputs: { value: "valueChange" }, hostDirectives: [{ directive: TweakpaneBlade, inputs: ["hidden", "hidden", "disabled", "disabled"] }, { directive: TweakpaneDebounce, inputs: ["debounce", "debounce"] }, { directive: TweakpaneLabel, inputs: ["label", "label"] }], ngImport: i0 }); }
756
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: TweakpaneList, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
757
+ static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.0.6", type: TweakpaneList, isStandalone: true, selector: "tweakpane-list", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: true, transformFunction: null }, options: { classPropertyName: "options", publicName: "options", isSignal: true, isRequired: true, transformFunction: null } }, outputs: { value: "valueChange" }, hostDirectives: [{ directive: TweakpaneBlade, inputs: ["hidden", "hidden", "disabled", "disabled"] }, { directive: TweakpaneDebounce, inputs: ["debounce", "debounce"] }, { directive: TweakpaneLabel, inputs: ["label", "label"] }], ngImport: i0 }); }
374
758
  }
375
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.7", ngImport: i0, type: TweakpaneList, decorators: [{
759
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: TweakpaneList, decorators: [{
376
760
  type: Directive,
377
761
  args: [{
378
762
  selector: 'tweakpane-list',
@@ -382,37 +766,126 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.7", ngImpor
382
766
  { directive: TweakpaneLabel, inputs: ['label'] },
383
767
  ],
384
768
  }]
385
- }], ctorParameters: () => [] });
769
+ }], ctorParameters: () => [], propDecorators: { value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: true }] }, { type: i0.Output, args: ["valueChange"] }], options: [{ type: i0.Input, args: [{ isSignal: true, alias: "options", required: true }] }] } });
386
770
 
771
+ /**
772
+ * Directive for creating a numeric input control in Tweakpane.
773
+ *
774
+ * Provides two-way binding for number values with optional min/max/step
775
+ * constraints. Displays as a text input with increment buttons, or as
776
+ * a slider if min and max are specified.
777
+ *
778
+ * @example
779
+ * ```html
780
+ * <tweakpane-pane>
781
+ * <!-- Simple number input -->
782
+ * <tweakpane-number label="Count" [(value)]="count" />
783
+ *
784
+ * <!-- With slider (min/max defined) -->
785
+ * <tweakpane-number
786
+ * label="Speed"
787
+ * [(value)]="speed"
788
+ * [params]="{ min: 0, max: 100, step: 0.1 }"
789
+ * />
790
+ *
791
+ * <!-- With custom format -->
792
+ * <tweakpane-number
793
+ * label="Angle"
794
+ * [(value)]="angle"
795
+ * [params]="{ min: 0, max: 360, format: (v) => v.toFixed(0) + '°' }"
796
+ * />
797
+ * </tweakpane-pane>
798
+ * ```
799
+ */
387
800
  class TweakpaneNumber {
388
801
  constructor() {
802
+ /**
803
+ * Additional Tweakpane number input parameters.
804
+ * Can include `min`, `max`, `step`, `format`, etc.
805
+ * @default {}
806
+ */
389
807
  this.params = input({}, ...(ngDevMode ? [{ debugName: "params" }] : []));
390
808
  this.binding = inject(TweakpaneBinding);
391
809
  this.binding.syncBindingParams(this.params);
392
810
  }
393
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.7", ngImport: i0, type: TweakpaneNumber, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
394
- static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "20.1.7", type: TweakpaneNumber, isStandalone: true, selector: "tweakpane-number", inputs: { params: { classPropertyName: "params", publicName: "params", isSignal: true, isRequired: false, transformFunction: null } }, providers: [provideTweakBindingAsHost()], hostDirectives: [{ directive: TweakpaneBinding, inputs: ["value", "value"], outputs: ["valueChange", "valueChange"] }], ngImport: i0 }); }
811
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: TweakpaneNumber, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
812
+ static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.0.6", type: TweakpaneNumber, isStandalone: true, selector: "tweakpane-number", inputs: { params: { classPropertyName: "params", publicName: "params", isSignal: true, isRequired: false, transformFunction: null } }, providers: [provideTweakBindingAsHost()], hostDirectives: [{ directive: TweakpaneBinding, inputs: ["value", "value"], outputs: ["valueChange", "valueChange"] }], ngImport: i0 }); }
395
813
  }
396
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.7", ngImport: i0, type: TweakpaneNumber, decorators: [{
814
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: TweakpaneNumber, decorators: [{
397
815
  type: Directive,
398
816
  args: [{
399
817
  selector: 'tweakpane-number',
400
818
  hostDirectives: [{ directive: TweakpaneBinding, inputs: ['value'], outputs: ['valueChange'] }],
401
819
  providers: [provideTweakBindingAsHost()],
402
820
  }]
403
- }], ctorParameters: () => [] });
821
+ }], ctorParameters: () => [], propDecorators: { params: [{ type: i0.Input, args: [{ isSignal: true, alias: "params", required: false }] }] } });
404
822
 
823
+ /**
824
+ * Main Tweakpane container directive that creates the root pane element.
825
+ *
826
+ * This directive creates a floating Tweakpane panel that can be positioned
827
+ * anywhere on the screen. It serves as the root container for all
828
+ * Tweakpane controls (folders, bindings, buttons, etc.).
829
+ *
830
+ * @example
831
+ * ```html
832
+ * <!-- Basic usage with default positioning (top-right) -->
833
+ * <tweakpane-pane title="Settings">
834
+ * <tweakpane-number label="Speed" [(value)]="speed" />
835
+ * <tweakpane-checkbox label="Debug" [(value)]="debug" />
836
+ * </tweakpane-pane>
837
+ *
838
+ * <!-- Custom positioning -->
839
+ * <tweakpane-pane title="Controls" left="8px" bottom="8px" [right]="undefined">
840
+ * <tweakpane-folder title="Physics">
841
+ * <tweakpane-number label="Gravity" [(value)]="gravity" />
842
+ * </tweakpane-folder>
843
+ * </tweakpane-pane>
844
+ *
845
+ * <!-- Embedded in a container element -->
846
+ * <div #container></div>
847
+ * <tweakpane-pane [container]="container">
848
+ * <!-- controls -->
849
+ * </tweakpane-pane>
850
+ * ```
851
+ */
405
852
  class TweakpanePane {
406
853
  constructor() {
854
+ /**
855
+ * CSS top position of the pane.
856
+ * @default '8px'
857
+ */
407
858
  this.top = input('8px', ...(ngDevMode ? [{ debugName: "top" }] : []));
859
+ /**
860
+ * CSS right position of the pane.
861
+ * @default '8px'
862
+ */
408
863
  this.right = input('8px', ...(ngDevMode ? [{ debugName: "right" }] : []));
864
+ /**
865
+ * CSS left position of the pane.
866
+ * @default undefined
867
+ */
409
868
  this.left = input(...(ngDevMode ? [undefined, { debugName: "left" }] : []));
869
+ /**
870
+ * CSS bottom position of the pane.
871
+ * @default undefined
872
+ */
410
873
  this.bottom = input(...(ngDevMode ? [undefined, { debugName: "bottom" }] : []));
874
+ /**
875
+ * CSS width of the pane.
876
+ * @default '256px'
877
+ */
411
878
  this.width = input('256px', ...(ngDevMode ? [{ debugName: "width" }] : []));
879
+ /**
880
+ * Optional container element to embed the pane into.
881
+ * If not provided, the pane floats freely in the document.
882
+ * Can be an HTMLElement or an Angular ElementRef.
883
+ */
412
884
  this.container = input(...(ngDevMode ? [undefined, { debugName: "container" }] : []));
413
885
  this.document = inject(DOCUMENT);
414
886
  this.title = inject(TweakpaneTitle, { host: true });
415
887
  this.folder = inject(TweakpaneFolder, { host: true });
888
+ this.tweakpaneAnchor = inject(TweakpaneAnchor, { optional: true });
416
889
  this.pane = signal(null, ...(ngDevMode ? [{ debugName: "pane" }] : []));
417
890
  this.folder.isSelf = false;
418
891
  afterNextRender(() => {
@@ -433,6 +906,10 @@ class TweakpanePane {
433
906
  const pane = new Pane(paneOptions);
434
907
  this.pane.set(pane);
435
908
  this.folder.parentFolder.set(pane);
909
+ // Set the pane's folder for tweaks() to use
910
+ if (this.tweakpaneAnchor) {
911
+ this.tweakpaneAnchor.paneFolder.set(this.folder);
912
+ }
436
913
  });
437
914
  inject(DestroyRef).onDestroy(() => {
438
915
  const pane = this.pane();
@@ -459,6 +936,10 @@ class TweakpanePane {
459
936
  this.updateStyleEffect('width');
460
937
  });
461
938
  }
939
+ /**
940
+ * Updates a CSS style property on the pane's parent element.
941
+ * @param propertyName - The name of the style property to update
942
+ */
462
943
  updateStyleEffect(propertyName) {
463
944
  const pane = this.pane();
464
945
  if (!pane)
@@ -475,25 +956,61 @@ class TweakpanePane {
475
956
  parentElement.style.setProperty(propertyName, typeof value === 'number' ? value + 'px' : value);
476
957
  pane.refresh();
477
958
  }
478
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.7", ngImport: i0, type: TweakpanePane, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
479
- static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "20.1.7", type: TweakpanePane, isStandalone: true, selector: "tweakpane-pane", inputs: { top: { classPropertyName: "top", publicName: "top", isSignal: true, isRequired: false, transformFunction: null }, right: { classPropertyName: "right", publicName: "right", isSignal: true, isRequired: false, transformFunction: null }, left: { classPropertyName: "left", publicName: "left", isSignal: true, isRequired: false, transformFunction: null }, bottom: { classPropertyName: "bottom", publicName: "bottom", isSignal: true, isRequired: false, transformFunction: null }, width: { classPropertyName: "width", publicName: "width", isSignal: true, isRequired: false, transformFunction: null }, container: { classPropertyName: "container", publicName: "container", isSignal: true, isRequired: false, transformFunction: null } }, hostDirectives: [{ directive: TweakpaneFolder, inputs: ["expanded", "expanded"], outputs: ["expandedChange", "expandedChange"] }], ngImport: i0 }); }
959
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: TweakpanePane, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
960
+ static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.0.6", type: TweakpanePane, isStandalone: true, selector: "tweakpane-pane", inputs: { top: { classPropertyName: "top", publicName: "top", isSignal: true, isRequired: false, transformFunction: null }, right: { classPropertyName: "right", publicName: "right", isSignal: true, isRequired: false, transformFunction: null }, left: { classPropertyName: "left", publicName: "left", isSignal: true, isRequired: false, transformFunction: null }, bottom: { classPropertyName: "bottom", publicName: "bottom", isSignal: true, isRequired: false, transformFunction: null }, width: { classPropertyName: "width", publicName: "width", isSignal: true, isRequired: false, transformFunction: null }, container: { classPropertyName: "container", publicName: "container", isSignal: true, isRequired: false, transformFunction: null } }, hostDirectives: [{ directive: TweakpaneFolder, inputs: ["expanded", "expanded"], outputs: ["expandedChange", "expandedChange"] }], ngImport: i0 }); }
480
961
  }
481
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.7", ngImport: i0, type: TweakpanePane, decorators: [{
962
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: TweakpanePane, decorators: [{
482
963
  type: Directive,
483
964
  args: [{
484
965
  selector: 'tweakpane-pane',
485
966
  hostDirectives: [{ directive: TweakpaneFolder, inputs: ['expanded'], outputs: ['expandedChange'] }],
486
967
  }]
487
- }], ctorParameters: () => [] });
968
+ }], ctorParameters: () => [], propDecorators: { top: [{ type: i0.Input, args: [{ isSignal: true, alias: "top", required: false }] }], right: [{ type: i0.Input, args: [{ isSignal: true, alias: "right", required: false }] }], left: [{ type: i0.Input, args: [{ isSignal: true, alias: "left", required: false }] }], bottom: [{ type: i0.Input, args: [{ isSignal: true, alias: "bottom", required: false }] }], width: [{ type: i0.Input, args: [{ isSignal: true, alias: "width", required: false }] }], container: [{ type: i0.Input, args: [{ isSignal: true, alias: "container", required: false }] }] } });
488
969
 
970
+ /**
971
+ * Directive for creating a 2D/3D/4D point input control in Tweakpane.
972
+ *
973
+ * Provides two-way binding for point values (as tuple arrays or objects).
974
+ * The control displays input fields for each dimension and optionally
975
+ * shows a 2D picker for x/y values.
976
+ *
977
+ * Values are accepted as arrays `[x, y, z?, w?]` and emitted in the same format.
978
+ *
979
+ * @example
980
+ * ```html
981
+ * <tweakpane-pane>
982
+ * <!-- 2D point -->
983
+ * <tweakpane-point label="Position" [(value)]="position2D" />
984
+ *
985
+ * <!-- 3D point with custom ranges -->
986
+ * <tweakpane-point
987
+ * label="Position"
988
+ * [(value)]="position3D"
989
+ * [params]="{
990
+ * x: { min: -10, max: 10 },
991
+ * y: { min: 0, max: 100 },
992
+ * z: { min: -10, max: 10 }
993
+ * }"
994
+ * />
995
+ *
996
+ * <!-- 4D point (e.g., quaternion) -->
997
+ * <tweakpane-point label="Rotation" [(value)]="quaternion" />
998
+ * </tweakpane-pane>
999
+ * ```
1000
+ */
489
1001
  class TweakpanePoint {
490
1002
  constructor() {
1003
+ /**
1004
+ * Additional Tweakpane point input parameters.
1005
+ * Can include per-axis configuration like `{ x: { min, max }, y: { min, max } }`.
1006
+ * @default {}
1007
+ */
491
1008
  this.params = input({}, ...(ngDevMode ? [{ debugName: "params" }] : []));
492
1009
  this.binding = inject(TweakpaneBinding);
493
1010
  this.binding.syncBindingParams(this.params);
494
1011
  }
495
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.7", ngImport: i0, type: TweakpanePoint, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
496
- static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "20.1.7", type: TweakpanePoint, isStandalone: true, selector: "tweakpane-point", inputs: { params: { classPropertyName: "params", publicName: "params", isSignal: true, isRequired: false, transformFunction: null } }, providers: [
1012
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: TweakpanePoint, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
1013
+ static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.0.6", type: TweakpanePoint, isStandalone: true, selector: "tweakpane-point", inputs: { params: { classPropertyName: "params", publicName: "params", isSignal: true, isRequired: false, transformFunction: null } }, providers: [
497
1014
  provideTweakBindingAsHost({
498
1015
  in: (value) => {
499
1016
  if (Array.isArray(value)) {
@@ -509,7 +1026,7 @@ class TweakpanePoint {
509
1026
  }),
510
1027
  ], hostDirectives: [{ directive: TweakpaneBinding, inputs: ["value", "value"], outputs: ["valueChange", "valueChange"] }], ngImport: i0 }); }
511
1028
  }
512
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.7", ngImport: i0, type: TweakpanePoint, decorators: [{
1029
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: TweakpanePoint, decorators: [{
513
1030
  type: Directive,
514
1031
  args: [{
515
1032
  selector: 'tweakpane-point',
@@ -530,29 +1047,618 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.7", ngImpor
530
1047
  }),
531
1048
  ],
532
1049
  }]
533
- }], ctorParameters: () => [] });
1050
+ }], ctorParameters: () => [], propDecorators: { params: [{ type: i0.Input, args: [{ isSignal: true, alias: "params", required: false }] }] } });
534
1051
 
1052
+ /**
1053
+ * Directive for creating a text input control in Tweakpane.
1054
+ *
1055
+ * Provides two-way binding for string values with a text input UI.
1056
+ *
1057
+ * @example
1058
+ * ```html
1059
+ * <tweakpane-pane>
1060
+ * <tweakpane-text label="Name" [(value)]="objectName" />
1061
+ * <tweakpane-text label="Description" [(value)]="description" />
1062
+ * </tweakpane-pane>
1063
+ * ```
1064
+ */
535
1065
  class TweakpaneText {
536
1066
  constructor() {
1067
+ /**
1068
+ * Additional Tweakpane string input parameters.
1069
+ * @default {}
1070
+ */
537
1071
  this.params = input({}, ...(ngDevMode ? [{ debugName: "params" }] : []));
538
1072
  this.binding = inject(TweakpaneBinding);
539
1073
  this.binding.syncBindingParams(this.params);
540
1074
  }
541
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.7", ngImport: i0, type: TweakpaneText, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
542
- static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "20.1.7", type: TweakpaneText, isStandalone: true, selector: "tweakpane-text", inputs: { params: { classPropertyName: "params", publicName: "params", isSignal: true, isRequired: false, transformFunction: null } }, providers: [provideTweakBindingAsHost()], hostDirectives: [{ directive: TweakpaneBinding, inputs: ["value", "value"], outputs: ["valueChange", "valueChange"] }], ngImport: i0 }); }
1075
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: TweakpaneText, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
1076
+ static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.0.6", type: TweakpaneText, isStandalone: true, selector: "tweakpane-text", inputs: { params: { classPropertyName: "params", publicName: "params", isSignal: true, isRequired: false, transformFunction: null } }, providers: [provideTweakBindingAsHost()], hostDirectives: [{ directive: TweakpaneBinding, inputs: ["value", "value"], outputs: ["valueChange", "valueChange"] }], ngImport: i0 }); }
543
1077
  }
544
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.7", ngImport: i0, type: TweakpaneText, decorators: [{
1078
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: TweakpaneText, decorators: [{
545
1079
  type: Directive,
546
1080
  args: [{
547
1081
  selector: 'tweakpane-text',
548
1082
  hostDirectives: [{ directive: TweakpaneBinding, inputs: ['value'], outputs: ['valueChange'] }],
549
1083
  providers: [provideTweakBindingAsHost()],
550
1084
  }]
551
- }], ctorParameters: () => [] });
1085
+ }], ctorParameters: () => [], propDecorators: { params: [{ type: i0.Input, args: [{ isSignal: true, alias: "params", required: false }] }] } });
1086
+
1087
+ // ============================================================================
1088
+ // Type Guards
1089
+ // ============================================================================
1090
+ /**
1091
+ * Checks if a config value is a TweakNumberConfig.
1092
+ * @internal
1093
+ */
1094
+ function isNumberConfig(config) {
1095
+ if (typeof config !== 'object' || config === null || !('value' in config))
1096
+ return false;
1097
+ if ('color' in config || 'options' in config || 'action' in config || '__folder' in config)
1098
+ return false;
1099
+ if ('x' in config || 'y' in config)
1100
+ return false;
1101
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1102
+ const value = isSignal(config.value) ? untracked(config.value) : config.value;
1103
+ return typeof value === 'number';
1104
+ }
1105
+ /**
1106
+ * Checks if a config value is a TweakTextConfig.
1107
+ * @internal
1108
+ */
1109
+ function isTextConfig(config) {
1110
+ if (typeof config !== 'object' || config === null || !('value' in config))
1111
+ return false;
1112
+ if ('color' in config || 'options' in config || 'action' in config || '__folder' in config)
1113
+ return false;
1114
+ if ('min' in config || 'max' in config || 'step' in config)
1115
+ return false;
1116
+ if ('x' in config || 'y' in config)
1117
+ return false;
1118
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1119
+ const value = isSignal(config.value) ? untracked(config.value) : config.value;
1120
+ return typeof value === 'string';
1121
+ }
1122
+ /**
1123
+ * Checks if a config value is a TweakColorConfig.
1124
+ * @internal
1125
+ */
1126
+ function isColorConfig(config) {
1127
+ return typeof config === 'object' && config !== null && 'color' in config && config.color === true;
1128
+ }
1129
+ /**
1130
+ * Checks if a config value is a TweakCheckboxConfig.
1131
+ * @internal
1132
+ */
1133
+ function isCheckboxConfig(config) {
1134
+ if (typeof config !== 'object' || config === null || !('value' in config))
1135
+ return false;
1136
+ if ('color' in config || 'options' in config || 'action' in config || '__folder' in config)
1137
+ return false;
1138
+ if ('min' in config || 'max' in config || 'step' in config)
1139
+ return false;
1140
+ if ('x' in config || 'y' in config)
1141
+ return false;
1142
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1143
+ const value = isSignal(config.value) ? untracked(config.value) : config.value;
1144
+ return typeof value === 'boolean';
1145
+ }
1146
+ /**
1147
+ * Checks if a config value is a TweakListConfig.
1148
+ * @internal
1149
+ */
1150
+ function isListConfig(config) {
1151
+ return typeof config === 'object' && config !== null && 'options' in config;
1152
+ }
1153
+ /**
1154
+ * Checks if a config value is a TweakPointConfig.
1155
+ * @internal
1156
+ */
1157
+ function isPointConfig(config) {
1158
+ return (typeof config === 'object' &&
1159
+ config !== null &&
1160
+ 'value' in config &&
1161
+ (('x' in config && 'y' in config) ||
1162
+ (Array.isArray(config.value) && config.value.length >= 2) ||
1163
+ (isSignal(config.value) && Array.isArray(untracked(config.value)))));
1164
+ }
1165
+ /**
1166
+ * Checks if a config value is a TweakButtonConfig.
1167
+ * @internal
1168
+ */
1169
+ function isButtonConfig(config) {
1170
+ return typeof config === 'object' && config !== null && 'action' in config && typeof config.action === 'function';
1171
+ }
1172
+ /**
1173
+ * Checks if a config value is a TweakFolderConfig.
1174
+ * @internal
1175
+ */
1176
+ function isFolderConfig(config) {
1177
+ return typeof config === 'object' && config !== null && '__folder' in config && config.__folder === true;
1178
+ }
1179
+ // ============================================================================
1180
+ // Helper to create folder configs
1181
+ // ============================================================================
1182
+ /**
1183
+ * Creates a nested folder configuration for use with `tweaks()`.
1184
+ *
1185
+ * This helper function creates a folder configuration object that can be
1186
+ * used as a value in the `tweaks()` config to organize controls into
1187
+ * collapsible groups.
1188
+ *
1189
+ * @typeParam T - The config type for the folder's contents
1190
+ * @param name - The display name/title of the folder
1191
+ * @param config - The configuration object for controls within the folder
1192
+ * @param options - Optional folder settings
1193
+ * @param options.expanded - Whether the folder starts expanded
1194
+ * @returns A folder configuration object
1195
+ *
1196
+ * @example
1197
+ * ```typescript
1198
+ * const controls = tweaks('Settings', {
1199
+ * basic: 42,
1200
+ * advanced: tweaks.folder('Advanced', {
1201
+ * iterations: { value: 4, min: 1, max: 10 },
1202
+ * tolerance: { value: 0.001, min: 0, max: 1, step: 0.001 },
1203
+ * }),
1204
+ * });
1205
+ *
1206
+ * // Access nested values
1207
+ * controls.advanced.iterations(); // Signal<number>
1208
+ * ```
1209
+ */
1210
+ function folder(name, config, options) {
1211
+ return {
1212
+ __folder: true,
1213
+ name,
1214
+ config,
1215
+ expanded: options?.expanded,
1216
+ };
1217
+ }
1218
+ // ============================================================================
1219
+ // Main tweaks() function
1220
+ // ============================================================================
1221
+ /**
1222
+ * Creates Tweakpane controls declaratively from any component within an `ngt-canvas`.
1223
+ *
1224
+ * **Prerequisites:**
1225
+ * 1. Add `tweakpaneAnchor` directive to your `ngt-canvas`:
1226
+ * ```html
1227
+ * <ngt-canvas tweakpaneAnchor>
1228
+ * ```
1229
+ * 2. Add `<tweakpane-pane>` somewhere in your scene
1230
+ *
1231
+ * @param folderName - The name of the folder to create/use
1232
+ * @param config - Configuration object defining the controls
1233
+ * @param options - Optional folder options (expanded, etc.)
1234
+ * @returns An object with signals for each control value
1235
+ *
1236
+ * @example
1237
+ * ```typescript
1238
+ * // Basic usage with primitives (creates new signals)
1239
+ * const controls = tweaks('Physics', {
1240
+ * gravity: 9.8,
1241
+ * debug: false,
1242
+ * name: 'World',
1243
+ * });
1244
+ *
1245
+ * // With config objects for more control
1246
+ * const controls = tweaks('Physics', {
1247
+ * gravity: { value: 9.8, min: 0, max: 20, step: 0.1 },
1248
+ * color: { value: '#ff0000', color: true },
1249
+ * mode: { value: 'normal', options: ['normal', 'debug', 'verbose'] },
1250
+ * });
1251
+ *
1252
+ * // Two-way binding with existing signals
1253
+ * filteringEnabled = signal(true);
1254
+ * const controls = tweaks('Settings', {
1255
+ * filteringEnabled: this.filteringEnabled, // two-way binding
1256
+ * });
1257
+ *
1258
+ * // Buttons (actions)
1259
+ * const controls = tweaks('Actions', {
1260
+ * reset: { action: () => this.reset() },
1261
+ * });
1262
+ *
1263
+ * // Nested folders
1264
+ * const controls = tweaks('Settings', {
1265
+ * basic: 42,
1266
+ * advanced: tweaks.folder('Advanced', {
1267
+ * iterations: { value: 4, min: 1, max: 10 },
1268
+ * }),
1269
+ * });
1270
+ * ```
1271
+ */
1272
+ function tweaks(folderName, config, { injector, ...options } = {}) {
1273
+ return assertInjector(tweaks, injector, () => {
1274
+ const maybeAnchor = inject(TweakpaneAnchor, { optional: true });
1275
+ const assertedInjector = inject(Injector);
1276
+ const destroyRef = inject(DestroyRef);
1277
+ if (!maybeAnchor) {
1278
+ throw new Error('[NGT Tweakpane] tweaks() requires TweakpaneAnchor directive. Add `tweakpaneAnchor` to your ngt-canvas element.');
1279
+ }
1280
+ const anchor = maybeAnchor;
1281
+ const result = {};
1282
+ const createdComponents = [];
1283
+ // Process config and create controls when pane folder is ready
1284
+ effect((onCleanup) => {
1285
+ const paneFolder = anchor.paneFolder();
1286
+ if (!paneFolder)
1287
+ return;
1288
+ // Get or create folder for this tweaks() call
1289
+ let folderRef = anchor.folders[folderName];
1290
+ let folderInstance;
1291
+ if (!folderRef) {
1292
+ // Create folder using the directives approach
1293
+ folderRef = anchor.vcr.createComponent(TweakpaneAnchorHost, {
1294
+ injector: Injector.create({
1295
+ providers: [{ provide: TweakpaneFolder, useValue: paneFolder }],
1296
+ parent: assertedInjector,
1297
+ }),
1298
+ directives: [
1299
+ {
1300
+ type: TweakpaneFolder,
1301
+ bindings: [
1302
+ inputBinding('title', () => folderName),
1303
+ inputBinding('expanded', () => options?.expanded ?? false),
1304
+ ],
1305
+ },
1306
+ ],
1307
+ });
1308
+ folderInstance = folderRef.injector.get(TweakpaneFolder);
1309
+ anchor.folders[folderName] = folderRef;
1310
+ createdComponents.push(folderRef);
1311
+ }
1312
+ else {
1313
+ folderInstance = folderRef.injector.get(TweakpaneFolder);
1314
+ }
1315
+ // Process each config entry
1316
+ processConfig(config, result, folderInstance, anchor.vcr, createdComponents, anchor, assertedInjector);
1317
+ onCleanup(() => {
1318
+ // Destroy created components
1319
+ for (const ref of createdComponents) {
1320
+ ref.destroy();
1321
+ }
1322
+ createdComponents.length = 0;
1323
+ });
1324
+ }, { injector: assertedInjector });
1325
+ // Cleanup on destroy
1326
+ destroyRef.onDestroy(() => {
1327
+ for (const ref of createdComponents) {
1328
+ ref.destroy();
1329
+ }
1330
+ });
1331
+ return result;
1332
+ });
1333
+ }
1334
+ /**
1335
+ * Attach the folder helper to tweaks for convenient access.
1336
+ * Allows usage like `tweaks.folder('Name', config)`.
1337
+ */
1338
+ tweaks.folder = folder;
1339
+ // ============================================================================
1340
+ // Internal: Process config and create components
1341
+ // ============================================================================
1342
+ /**
1343
+ * Processes a configuration object and creates the corresponding Tweakpane controls.
1344
+ * This is an internal function called by `tweaks()` to set up the UI.
1345
+ *
1346
+ * @param config - The configuration object to process
1347
+ * @param result - The result object to populate with signals
1348
+ * @param parentFolder - The parent folder to add controls to
1349
+ * @param vcr - The ViewContainerRef for creating components
1350
+ * @param createdComponents - Array to track created component refs for cleanup
1351
+ * @param anchor - The TweakpaneAnchor for folder management
1352
+ * @param parentInjector - The parent injector for dependency injection
1353
+ *
1354
+ * @internal
1355
+ */
1356
+ function processConfig(config, result, parentFolder, vcr, createdComponents, anchor, parentInjector) {
1357
+ const keys = Object.keys(config);
1358
+ // Create injector with parent folder provided
1359
+ const folderInjector = Injector.create({
1360
+ providers: [{ provide: TweakpaneFolder, useValue: parentFolder }],
1361
+ parent: parentInjector,
1362
+ });
1363
+ for (const key of keys) {
1364
+ const configValue = config[key];
1365
+ // Handle nested folders
1366
+ if (isFolderConfig(configValue)) {
1367
+ const nestedResult = {};
1368
+ // Get or create nested folder
1369
+ const nestedFolderName = configValue.name;
1370
+ let nestedFolderRef = anchor.folders[nestedFolderName];
1371
+ let nestedFolderInstance;
1372
+ if (!nestedFolderRef) {
1373
+ nestedFolderRef = vcr.createComponent(TweakpaneAnchorHost, {
1374
+ injector: folderInjector,
1375
+ directives: [
1376
+ {
1377
+ type: TweakpaneFolder,
1378
+ bindings: [
1379
+ inputBinding('title', () => nestedFolderName),
1380
+ inputBinding('expanded', () => configValue.expanded ?? false),
1381
+ ],
1382
+ },
1383
+ ],
1384
+ });
1385
+ nestedFolderInstance = nestedFolderRef.injector.get(TweakpaneFolder);
1386
+ anchor.folders[nestedFolderName] = nestedFolderRef;
1387
+ createdComponents.push(nestedFolderRef);
1388
+ }
1389
+ else {
1390
+ nestedFolderInstance = nestedFolderRef.injector.get(TweakpaneFolder);
1391
+ }
1392
+ processConfig(configValue.config, nestedResult, nestedFolderInstance, vcr, createdComponents, anchor, folderInjector);
1393
+ result[key] = nestedResult;
1394
+ continue;
1395
+ }
1396
+ // Handle buttons (no result signal)
1397
+ if (isButtonConfig(configValue)) {
1398
+ const buttonRef = vcr.createComponent(TweakpaneAnchorHost, {
1399
+ injector: folderInjector,
1400
+ directives: [
1401
+ {
1402
+ type: TweakpaneButton,
1403
+ bindings: [
1404
+ inputBinding('title', () => key),
1405
+ ...(configValue.label ? [inputBinding('label', () => configValue.label)] : []),
1406
+ ],
1407
+ },
1408
+ ],
1409
+ });
1410
+ // Subscribe to click events
1411
+ const buttonInstance = buttonRef.injector.get(TweakpaneButton);
1412
+ buttonInstance.click.subscribe(() => {
1413
+ configValue.action();
1414
+ });
1415
+ createdComponents.push(buttonRef);
1416
+ continue;
1417
+ }
1418
+ // Handle direct WritableSignal (two-way binding)
1419
+ if (isSignal(configValue)) {
1420
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1421
+ const untrackedValue = untracked(configValue);
1422
+ const valueType = typeof untrackedValue;
1423
+ if (valueType === 'number') {
1424
+ const ref = vcr.createComponent(TweakpaneAnchorHost, {
1425
+ injector: folderInjector,
1426
+ directives: [
1427
+ {
1428
+ type: TweakpaneNumber,
1429
+ bindings: [
1430
+ inputBinding('label', () => key),
1431
+ twoWayBinding('value', configValue),
1432
+ ],
1433
+ },
1434
+ ],
1435
+ });
1436
+ createdComponents.push(ref);
1437
+ result[key] = configValue.asReadonly();
1438
+ }
1439
+ else if (valueType === 'string') {
1440
+ const ref = vcr.createComponent(TweakpaneAnchorHost, {
1441
+ injector: folderInjector,
1442
+ directives: [
1443
+ {
1444
+ type: TweakpaneText,
1445
+ bindings: [
1446
+ inputBinding('label', () => key),
1447
+ twoWayBinding('value', configValue),
1448
+ ],
1449
+ },
1450
+ ],
1451
+ });
1452
+ createdComponents.push(ref);
1453
+ result[key] = configValue.asReadonly();
1454
+ }
1455
+ else if (valueType === 'boolean') {
1456
+ const ref = vcr.createComponent(TweakpaneAnchorHost, {
1457
+ injector: folderInjector,
1458
+ directives: [
1459
+ {
1460
+ type: TweakpaneCheckbox,
1461
+ bindings: [
1462
+ inputBinding('label', () => key),
1463
+ twoWayBinding('value', configValue),
1464
+ ],
1465
+ },
1466
+ ],
1467
+ });
1468
+ createdComponents.push(ref);
1469
+ result[key] = configValue.asReadonly();
1470
+ }
1471
+ continue;
1472
+ }
1473
+ // Handle primitive values (create new signal)
1474
+ if (typeof configValue === 'number') {
1475
+ const valueSignal = signal(configValue, ...(ngDevMode ? [{ debugName: "valueSignal" }] : []));
1476
+ const ref = vcr.createComponent(TweakpaneAnchorHost, {
1477
+ injector: folderInjector,
1478
+ directives: [
1479
+ {
1480
+ type: TweakpaneNumber,
1481
+ bindings: [inputBinding('label', () => key), twoWayBinding('value', valueSignal)],
1482
+ },
1483
+ ],
1484
+ });
1485
+ createdComponents.push(ref);
1486
+ result[key] = valueSignal.asReadonly();
1487
+ continue;
1488
+ }
1489
+ if (typeof configValue === 'string') {
1490
+ const valueSignal = signal(configValue, ...(ngDevMode ? [{ debugName: "valueSignal" }] : []));
1491
+ const ref = vcr.createComponent(TweakpaneAnchorHost, {
1492
+ injector: folderInjector,
1493
+ directives: [
1494
+ {
1495
+ type: TweakpaneText,
1496
+ bindings: [inputBinding('label', () => key), twoWayBinding('value', valueSignal)],
1497
+ },
1498
+ ],
1499
+ });
1500
+ createdComponents.push(ref);
1501
+ result[key] = valueSignal.asReadonly();
1502
+ continue;
1503
+ }
1504
+ if (typeof configValue === 'boolean') {
1505
+ const valueSignal = signal(configValue, ...(ngDevMode ? [{ debugName: "valueSignal" }] : []));
1506
+ const ref = vcr.createComponent(TweakpaneAnchorHost, {
1507
+ injector: folderInjector,
1508
+ directives: [
1509
+ {
1510
+ type: TweakpaneCheckbox,
1511
+ bindings: [inputBinding('label', () => key), twoWayBinding('value', valueSignal)],
1512
+ },
1513
+ ],
1514
+ });
1515
+ createdComponents.push(ref);
1516
+ result[key] = valueSignal.asReadonly();
1517
+ continue;
1518
+ }
1519
+ // Handle config objects
1520
+ if (isColorConfig(configValue)) {
1521
+ const initialValue = isSignal(configValue.value)
1522
+ ? untracked(configValue.value)
1523
+ : configValue.value;
1524
+ const valueSignal = isSignal(configValue.value) ? configValue.value : signal(initialValue);
1525
+ const ref = vcr.createComponent(TweakpaneAnchorHost, {
1526
+ injector: folderInjector,
1527
+ directives: [
1528
+ {
1529
+ type: TweakpaneColor,
1530
+ bindings: [inputBinding('label', () => key), twoWayBinding('value', valueSignal)],
1531
+ },
1532
+ ],
1533
+ });
1534
+ createdComponents.push(ref);
1535
+ result[key] = valueSignal.asReadonly();
1536
+ continue;
1537
+ }
1538
+ if (isListConfig(configValue)) {
1539
+ const initialValue = isSignal(configValue.value) ? untracked(configValue.value) : configValue.value;
1540
+ const valueSignal = isSignal(configValue.value)
1541
+ ? configValue.value
1542
+ : signal(initialValue);
1543
+ const ref = vcr.createComponent(TweakpaneAnchorHost, {
1544
+ injector: folderInjector,
1545
+ directives: [
1546
+ {
1547
+ type: TweakpaneList,
1548
+ bindings: [
1549
+ inputBinding('label', () => key),
1550
+ inputBinding('options', () => configValue.options),
1551
+ twoWayBinding('value', valueSignal),
1552
+ ],
1553
+ },
1554
+ ],
1555
+ });
1556
+ createdComponents.push(ref);
1557
+ result[key] = valueSignal.asReadonly();
1558
+ continue;
1559
+ }
1560
+ if (isPointConfig(configValue)) {
1561
+ const initialValue = isSignal(configValue.value) ? untracked(configValue.value) : configValue.value;
1562
+ const valueSignal = isSignal(configValue.value) ? configValue.value : signal(initialValue);
1563
+ // Build params from x, y, z, w configs
1564
+ const params = {};
1565
+ if (configValue.x)
1566
+ params.x = configValue.x;
1567
+ if (configValue.y)
1568
+ params.y = configValue.y;
1569
+ if (configValue.z)
1570
+ params.z = configValue.z;
1571
+ if (configValue.w)
1572
+ params.w = configValue.w;
1573
+ const ref = vcr.createComponent(TweakpaneAnchorHost, {
1574
+ injector: folderInjector,
1575
+ directives: [
1576
+ {
1577
+ type: TweakpanePoint,
1578
+ bindings: [
1579
+ inputBinding('label', () => key),
1580
+ inputBinding('params', () => params),
1581
+ twoWayBinding('value', valueSignal),
1582
+ ],
1583
+ },
1584
+ ],
1585
+ });
1586
+ createdComponents.push(ref);
1587
+ result[key] = valueSignal.asReadonly();
1588
+ continue;
1589
+ }
1590
+ if (isNumberConfig(configValue)) {
1591
+ const initialValue = isSignal(configValue.value)
1592
+ ? untracked(configValue.value)
1593
+ : configValue.value;
1594
+ const valueSignal = isSignal(configValue.value) ? configValue.value : signal(initialValue);
1595
+ // Build params
1596
+ const params = {};
1597
+ if (configValue.min !== undefined)
1598
+ params.min = configValue.min;
1599
+ if (configValue.max !== undefined)
1600
+ params.max = configValue.max;
1601
+ if (configValue.step !== undefined)
1602
+ params.step = configValue.step;
1603
+ const ref = vcr.createComponent(TweakpaneAnchorHost, {
1604
+ injector: folderInjector,
1605
+ directives: [
1606
+ {
1607
+ type: TweakpaneNumber,
1608
+ bindings: [
1609
+ inputBinding('label', () => key),
1610
+ inputBinding('params', () => params),
1611
+ twoWayBinding('value', valueSignal),
1612
+ ],
1613
+ },
1614
+ ],
1615
+ });
1616
+ createdComponents.push(ref);
1617
+ result[key] = valueSignal.asReadonly();
1618
+ continue;
1619
+ }
1620
+ if (isCheckboxConfig(configValue)) {
1621
+ const initialValue = isSignal(configValue.value)
1622
+ ? untracked(configValue.value)
1623
+ : configValue.value;
1624
+ const valueSignal = isSignal(configValue.value) ? configValue.value : signal(initialValue);
1625
+ const ref = vcr.createComponent(TweakpaneAnchorHost, {
1626
+ injector: folderInjector,
1627
+ directives: [
1628
+ {
1629
+ type: TweakpaneCheckbox,
1630
+ bindings: [inputBinding('label', () => key), twoWayBinding('value', valueSignal)],
1631
+ },
1632
+ ],
1633
+ });
1634
+ createdComponents.push(ref);
1635
+ result[key] = valueSignal.asReadonly();
1636
+ continue;
1637
+ }
1638
+ if (isTextConfig(configValue)) {
1639
+ const initialValue = isSignal(configValue.value)
1640
+ ? untracked(configValue.value)
1641
+ : configValue.value;
1642
+ const valueSignal = isSignal(configValue.value) ? configValue.value : signal(initialValue);
1643
+ const ref = vcr.createComponent(TweakpaneAnchorHost, {
1644
+ injector: folderInjector,
1645
+ directives: [
1646
+ {
1647
+ type: TweakpaneText,
1648
+ bindings: [inputBinding('label', () => key), twoWayBinding('value', valueSignal)],
1649
+ },
1650
+ ],
1651
+ });
1652
+ createdComponents.push(ref);
1653
+ result[key] = valueSignal.asReadonly();
1654
+ continue;
1655
+ }
1656
+ }
1657
+ }
552
1658
 
553
1659
  /**
554
1660
  * Generated bundle index. Do not edit.
555
1661
  */
556
1662
 
557
- export { NGT_TWEAK_BINDING_AS_HOST, TweakpaneBinding, TweakpaneBlade, TweakpaneButton, TweakpaneCheckbox, TweakpaneColor, TweakpaneDebounce, TweakpaneFolder, TweakpaneLabel, TweakpaneList, TweakpaneNumber, TweakpanePane, TweakpanePoint, TweakpaneText, TweakpaneTitle, provideTweakBindingAsHost };
1663
+ export { NGT_TWEAK_BINDING_AS_HOST, TweakpaneAnchor, TweakpaneAnchorHost, TweakpaneBinding, TweakpaneBlade, TweakpaneButton, TweakpaneCheckbox, TweakpaneColor, TweakpaneDebounce, TweakpaneFolder, TweakpaneLabel, TweakpaneList, TweakpaneNumber, TweakpanePane, TweakpanePoint, TweakpaneText, TweakpaneTitle, provideTweakBindingAsHost, tweaks };
558
1664
  //# sourceMappingURL=angular-three-tweakpane.mjs.map