angular-three 2.0.0-beta.6 → 2.0.0-beta.8

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.
Files changed (125) hide show
  1. package/README.md +4 -147
  2. package/esm2022/angular-three.mjs +1 -1
  3. package/esm2022/index.mjs +10 -9
  4. package/esm2022/lib/before-render.mjs +13 -0
  5. package/esm2022/lib/canvas.mjs +129 -161
  6. package/esm2022/lib/directives/args.mjs +13 -11
  7. package/esm2022/lib/directives/common.mjs +28 -27
  8. package/esm2022/lib/directives/key.mjs +29 -0
  9. package/esm2022/lib/directives/parent.mjs +13 -11
  10. package/esm2022/lib/directives/repeat.mjs +5 -6
  11. package/esm2022/lib/dom/events.mjs +6 -1
  12. package/esm2022/lib/events.mjs +75 -58
  13. package/esm2022/lib/instance.mjs +65 -0
  14. package/esm2022/lib/loader.mjs +17 -20
  15. package/esm2022/lib/loop.mjs +6 -3
  16. package/esm2022/lib/portal.mjs +103 -112
  17. package/esm2022/lib/ref.mjs +48 -0
  18. package/esm2022/lib/renderer/catalogue.mjs +7 -0
  19. package/esm2022/lib/renderer/constants.mjs +21 -0
  20. package/esm2022/lib/renderer/index.mjs +409 -0
  21. package/esm2022/lib/renderer/store.mjs +61 -38
  22. package/esm2022/lib/renderer/utils.mjs +20 -43
  23. package/esm2022/lib/roots.mjs +249 -0
  24. package/esm2022/lib/routed-scene.mjs +6 -7
  25. package/esm2022/lib/store.mjs +207 -0
  26. package/esm2022/lib/three-types.mjs +1 -1
  27. package/esm2022/lib/types.mjs +1 -1
  28. package/esm2022/lib/utils/apply-props.mjs +12 -7
  29. package/esm2022/lib/utils/assert-injection-context.mjs +14 -0
  30. package/esm2022/lib/utils/attach.mjs +2 -2
  31. package/esm2022/lib/utils/create-injection-token.mjs +47 -0
  32. package/esm2022/lib/utils/is.mjs +1 -1
  33. package/esm2022/lib/utils/make.mjs +1 -1
  34. package/esm2022/lib/utils/safe-detect-changes.mjs +15 -13
  35. package/esm2022/lib/utils/signal-store.mjs +91 -0
  36. package/esm2022/lib/utils/update.mjs +1 -1
  37. package/fesm2022/angular-three.mjs +1569 -1425
  38. package/fesm2022/angular-three.mjs.map +1 -1
  39. package/index.d.ts +10 -8
  40. package/lib/{di/before-render.d.ts → before-render.d.ts} +1 -1
  41. package/lib/canvas.d.ts +81 -11
  42. package/lib/directives/args.d.ts +2 -2
  43. package/lib/directives/common.d.ts +5 -1
  44. package/lib/directives/key.d.ts +10 -0
  45. package/lib/directives/parent.d.ts +5 -5
  46. package/lib/dom/events.d.ts +3 -2
  47. package/lib/events.d.ts +78 -2
  48. package/lib/instance.d.ts +36 -0
  49. package/lib/loader.d.ts +13 -2
  50. package/lib/loop.d.ts +64 -6
  51. package/lib/portal.d.ts +21 -12
  52. package/lib/{di/ref.d.ts → ref.d.ts} +3 -2
  53. package/lib/renderer/catalogue.d.ts +9 -0
  54. package/lib/renderer/constants.d.ts +20 -0
  55. package/lib/renderer/index.d.ts +5 -0
  56. package/lib/renderer/store.d.ts +21 -15
  57. package/lib/renderer/utils.d.ts +29 -19
  58. package/lib/roots.d.ts +11 -0
  59. package/lib/routed-scene.d.ts +1 -1
  60. package/lib/store.d.ts +143 -0
  61. package/lib/three-types.d.ts +5 -5
  62. package/lib/types.d.ts +1 -309
  63. package/lib/utils/apply-props.d.ts +4 -2
  64. package/lib/utils/attach.d.ts +4 -2
  65. package/lib/utils/create-injection-token.d.ts +27 -0
  66. package/lib/utils/is.d.ts +3 -2
  67. package/lib/utils/make.d.ts +12 -1
  68. package/lib/utils/safe-detect-changes.d.ts +2 -2
  69. package/lib/utils/signal-store.d.ts +17 -0
  70. package/lib/utils/update.d.ts +1 -1
  71. package/metadata.json +1 -0
  72. package/package.json +3 -3
  73. package/plugin/README.md +11 -0
  74. package/plugin/generators.json +39 -0
  75. package/plugin/package.json +6 -0
  76. package/plugin/src/generators/init/compat.d.ts +2 -0
  77. package/plugin/src/generators/init/compat.js +6 -0
  78. package/plugin/src/generators/init/compat.js.map +1 -0
  79. package/plugin/src/generators/init/generator.d.ts +2 -0
  80. package/plugin/src/generators/init/generator.js +48 -0
  81. package/plugin/src/generators/init/generator.js.map +1 -0
  82. package/plugin/src/generators/init/schema.json +6 -0
  83. package/plugin/src/generators/init-postprocessing/compat.d.ts +2 -0
  84. package/plugin/src/generators/init-postprocessing/compat.js +6 -0
  85. package/plugin/src/generators/init-postprocessing/compat.js.map +1 -0
  86. package/plugin/src/generators/init-postprocessing/generator.d.ts +2 -0
  87. package/plugin/src/generators/init-postprocessing/generator.js +20 -0
  88. package/plugin/src/generators/init-postprocessing/generator.js.map +1 -0
  89. package/plugin/src/generators/init-postprocessing/schema.json +6 -0
  90. package/plugin/src/generators/init-soba/compat.d.ts +2 -0
  91. package/plugin/src/generators/init-soba/compat.js +6 -0
  92. package/plugin/src/generators/init-soba/compat.js.map +1 -0
  93. package/plugin/src/generators/init-soba/generator.d.ts +2 -0
  94. package/plugin/src/generators/init-soba/generator.js +24 -0
  95. package/plugin/src/generators/init-soba/generator.js.map +1 -0
  96. package/plugin/src/generators/init-soba/schema.json +6 -0
  97. package/plugin/src/generators/versions.d.ts +9 -0
  98. package/plugin/src/generators/versions.js +13 -0
  99. package/plugin/src/generators/versions.js.map +1 -0
  100. package/plugin/src/index.d.ts +3 -0
  101. package/plugin/src/index.js +10 -0
  102. package/plugin/src/index.js.map +1 -0
  103. package/web-types.json +1 -0
  104. package/esm2022/lib/di/before-render.mjs +0 -13
  105. package/esm2022/lib/di/catalogue.mjs +0 -7
  106. package/esm2022/lib/di/ref.mjs +0 -54
  107. package/esm2022/lib/renderer/di.mjs +0 -3
  108. package/esm2022/lib/renderer/enums.mjs +0 -2
  109. package/esm2022/lib/renderer/provider.mjs +0 -18
  110. package/esm2022/lib/renderer/renderer.mjs +0 -367
  111. package/esm2022/lib/stores/signal.store.mjs +0 -78
  112. package/esm2022/lib/stores/store.mjs +0 -420
  113. package/esm2022/lib/utils/assert-in-injection-context.mjs +0 -14
  114. package/esm2022/lib/utils/instance.mjs +0 -66
  115. package/esm2022/lib/utils/timing.mjs +0 -9
  116. package/lib/di/catalogue.d.ts +0 -3
  117. package/lib/renderer/di.d.ts +0 -2
  118. package/lib/renderer/enums.d.ts +0 -26
  119. package/lib/renderer/provider.d.ts +0 -8
  120. package/lib/renderer/renderer.d.ts +0 -49
  121. package/lib/stores/signal.store.d.ts +0 -21
  122. package/lib/stores/store.d.ts +0 -13
  123. package/lib/utils/instance.d.ts +0 -4
  124. package/lib/utils/timing.d.ts +0 -2
  125. /package/lib/utils/{assert-in-injection-context.d.ts → assert-injection-context.d.ts} +0 -0
@@ -1,82 +1,97 @@
1
1
  import * as i0 from '@angular/core';
2
- import { signal, computed, untracked, Injectable, Optional, Inject, ElementRef, inject, Injector, assertInInjectionContext, runInInjectionContext, ChangeDetectorRef, effect, InjectionToken, EventEmitter, ViewContainerRef, NgZone, TemplateRef, Directive, Input, getDebugNode, RendererFactory2, makeEnvironmentProviders, provideZoneChangeDetection, EnvironmentInjector, DestroyRef, createEnvironmentInjector, Component, ChangeDetectionStrategy, Output, ViewChild, SkipSelf, ContentChild } from '@angular/core';
3
- import { provideNgxResizeOptions, NgxResize } from 'ngx-resize';
4
- import * as THREE from 'three';
2
+ import { untracked, computed, signal, ElementRef, inject, Injector, assertInInjectionContext, runInInjectionContext, DestroyRef, effect, InjectionToken, ChangeDetectorRef, Optional, SkipSelf, ViewContainerRef, NgZone, TemplateRef, Directive, Input, EventEmitter, getDebugNode, RendererFactory2, Injectable, makeEnvironmentProviders, provideZoneChangeDetection, EnvironmentInjector, createEnvironmentInjector, Component, ChangeDetectionStrategy, Output, ViewChild, ContentChild } from '@angular/core';
5
3
  import { DOCUMENT, NgForOf, NgIf } from '@angular/common';
4
+ import { Subject, filter } from 'rxjs';
5
+ import * as THREE from 'three';
6
+ import { provideNgxResizeOptions, NgxResize } from 'ngx-resize';
6
7
  import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
7
8
  import * as i1 from '@angular/router';
8
9
  import { ActivationEnd, RouterOutlet } from '@angular/router';
9
- import { filter } from 'rxjs';
10
10
 
11
- const STORE_COMPUTED_KEY = '__ngt_store_computed__';
12
- class NgtSignalStore {
13
- #state;
14
- #computedCache = new Map();
15
- constructor(initialState = {}) {
16
- initialState ??= {};
17
- this.#state = signal(Object.assign(initialState, { __ngt_dummy_state__: Date.now() }));
18
- this.state = this.#state.asReadonly();
19
- }
20
- select(...keysAndOptions) {
21
- if (keysAndOptions.length === 0)
22
- return this.state;
23
- if (keysAndOptions.length === 1 && typeof keysAndOptions[0] === 'object') {
24
- if (!this.#computedCache.has(STORE_COMPUTED_KEY)) {
25
- this.#computedCache.set(STORE_COMPUTED_KEY, computed(this.state, keysAndOptions));
26
- return this.#computedCache.get(STORE_COMPUTED_KEY);
27
- }
28
- }
29
- const [keys, options] = parseOptions(keysAndOptions);
30
- const joinedKeys = keys.join('-');
31
- if (!this.#computedCache.has(joinedKeys)) {
32
- this.#computedCache.set(joinedKeys, computed(() => keys.reduce((value, key) => value[key], this.state()), options));
33
- }
34
- return this.#computedCache.get(joinedKeys);
35
- }
36
- get(...keys) {
37
- const state = untracked(this.state);
38
- if (keys.length === 0)
39
- return state;
40
- return keys.reduce((value, key) => value[key], state);
41
- }
42
- set(state) {
43
- const updater = (previous) => {
44
- const partial = typeof state === 'function' ? state(previous) : state;
45
- Object.keys(partial).forEach((key) => {
46
- const partialKey = key;
47
- if (partial[partialKey] === undefined && previous[partialKey] != null) {
48
- partial[partialKey] = previous[partialKey];
49
- }
50
- });
51
- return partial;
52
- };
53
- this.#state.update((previous) => ({ ...previous, ...updater(previous) }));
54
- // this.#state.update(previous => ({...previous, ...(typeof state === 'function' ? state(previous) : state)}))
55
- }
56
- patch(state) {
57
- const updater = (previous) => {
58
- Object.keys(state).forEach((key) => {
59
- const partialKey = key;
60
- if (state[partialKey] === undefined && previous[partialKey] != null) {
61
- state[partialKey] = previous[partialKey];
62
- }
63
- });
64
- return state;
65
- };
66
- this.#state.update((previous) => ({ ...updater(previous), ...previous }));
11
+ const STORE_COMPUTED_KEY = '__ngt_signal_store_computed__';
12
+ const setter = (_source) => (state) => {
13
+ const updater = (previous) => {
14
+ const partial = typeof state === 'function' ? state(previous) : state;
15
+ Object.keys(partial).forEach((key) => {
16
+ const typedKey = key;
17
+ if (partial[typedKey] === undefined && previous[typedKey] != null) {
18
+ partial[typedKey] = previous[typedKey];
19
+ }
20
+ });
21
+ return partial;
22
+ };
23
+ untracked(() => {
24
+ _source.update((previous) => ({ ...previous, ...updater(previous) }));
25
+ });
26
+ };
27
+ const patcher = (_source) => (state) => {
28
+ const updater = (previous) => {
29
+ Object.keys(state).forEach((key) => {
30
+ const typedKey = key;
31
+ if (state[typedKey] === undefined && previous[typedKey] != null) {
32
+ state[typedKey] = previous[typedKey];
33
+ }
34
+ });
35
+ return state;
36
+ };
37
+ untracked(() => {
38
+ _source.update((previous) => ({ ...updater(previous), ...previous }));
39
+ });
40
+ };
41
+ const getter = (_source) => (...keys) => {
42
+ const root = untracked(_source);
43
+ if (keys.length === 0)
44
+ return root;
45
+ return keys.reduce((value, key) => value[key], root);
46
+ };
47
+ const selector = (_state, computedCache) => (...keysAndOptions) => {
48
+ if (keysAndOptions.length === 0)
49
+ return _state;
50
+ if (keysAndOptions.length === 1 && typeof keysAndOptions[0] === 'object') {
51
+ const cachedKey = STORE_COMPUTED_KEY.concat(JSON.stringify(keysAndOptions[0]));
52
+ if (!computedCache.has(cachedKey)) {
53
+ computedCache.set(cachedKey, computed(_state, keysAndOptions));
54
+ }
55
+ return computedCache.get(cachedKey);
56
+ }
57
+ const [keys, options] = parseStoreOptions(keysAndOptions);
58
+ const joinedKeys = keys.join('-');
59
+ const cachedKeys = joinedKeys.concat(options ? JSON.stringify(options) : '');
60
+ if (!computedCache.has(cachedKeys)) {
61
+ computedCache.set(cachedKeys, computed(() => keys.reduce((value, key) => value[key], _state()), options));
62
+ }
63
+ return computedCache.get(cachedKeys);
64
+ };
65
+ function signalStore(initialState = {}, options) {
66
+ let source;
67
+ let set;
68
+ let get;
69
+ let patch;
70
+ if (typeof initialState === 'function') {
71
+ source = signal({}, options);
72
+ get = getter(source);
73
+ set = setter(source);
74
+ patch = patcher(source);
75
+ source.set(initialState({ set, get, patch }));
67
76
  }
68
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.0.3", ngImport: i0, type: NgtSignalStore, deps: [{ token: 'INITIAL_STATE', optional: true }], target: i0.ɵɵFactoryTarget.Injectable }); }
69
- static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "16.0.3", ngImport: i0, type: NgtSignalStore }); }
77
+ else {
78
+ source = signal(initialState, options);
79
+ get = getter(source);
80
+ set = setter(source);
81
+ patch = patcher(source);
82
+ }
83
+ const state = source.asReadonly();
84
+ const computedCache = new Map();
85
+ const store = { select: selector(state, computedCache), get, set, patch, state };
86
+ // NOTE: internal _snapshot to debug current state
87
+ Object.defineProperty(store, '_snapshot', {
88
+ get: state,
89
+ configurable: false,
90
+ enumerable: false,
91
+ });
92
+ return store;
70
93
  }
71
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.0.3", ngImport: i0, type: NgtSignalStore, decorators: [{
72
- type: Injectable
73
- }], ctorParameters: function () { return [{ type: undefined, decorators: [{
74
- type: Optional
75
- }, {
76
- type: Inject,
77
- args: ['INITIAL_STATE']
78
- }] }]; } });
79
- function parseOptions(keysAndOptions) {
94
+ function parseStoreOptions(keysAndOptions) {
80
95
  if (typeof keysAndOptions.at(-1) === 'object') {
81
96
  return [keysAndOptions.slice(0, -1), keysAndOptions.at(-1)];
82
97
  }
@@ -188,27 +203,26 @@ function prepare(object, localState) {
188
203
  handlers: {},
189
204
  objects,
190
205
  nonObjects,
191
- nativeProps: new NgtSignalStore({}),
206
+ nativeProps: signalStore(),
192
207
  add: (object, type) => {
193
- queueMicrotask(() => {
194
- const current = untracked(instance.__ngt__[type]);
208
+ untracked(() => {
209
+ const current = instance.__ngt__[type]();
195
210
  const foundIndex = current.indexOf((obj) => obj === object);
196
211
  if (foundIndex > -1) {
197
212
  // if we add an object with the same reference, then we switch it out
198
- // and update the BehaviorSubject
199
213
  current.splice(foundIndex, 1, object);
200
214
  instance.__ngt__[type].set(current);
201
215
  }
202
216
  else {
203
217
  instance.__ngt__[type].update((prev) => [...prev, object]);
204
218
  }
205
- notifyAncestors(untracked(instance.__ngt__.parent));
219
+ notifyAncestors(instance.__ngt__.parent());
206
220
  });
207
221
  },
208
222
  remove: (object, type) => {
209
- queueMicrotask(() => {
223
+ untracked(() => {
210
224
  instance.__ngt__[type].update((prev) => prev.filter((o) => o !== object));
211
- notifyAncestors(untracked(instance.__ngt__.parent));
225
+ notifyAncestors(instance.__ngt__.parent());
212
226
  });
213
227
  },
214
228
  ...rest,
@@ -224,7 +238,142 @@ function notifyAncestors(instance) {
224
238
  localState.objects.update((prev) => prev);
225
239
  if (localState.nonObjects)
226
240
  localState.nonObjects.update((prev) => prev);
227
- notifyAncestors(untracked(localState.parent));
241
+ notifyAncestors(localState.parent());
242
+ }
243
+
244
+ // This function prepares a set of changes to be applied to the instance
245
+ function diffProps(instance, props) {
246
+ const propsEntries = Object.entries(props);
247
+ const changes = [];
248
+ for (const [propKey, propValue] of propsEntries) {
249
+ let key = propKey;
250
+ if (is.colorSpaceExist(instance)) {
251
+ if (propKey === 'encoding') {
252
+ key = 'colorSpace';
253
+ }
254
+ else if (propKey === 'outputEncoding') {
255
+ key = 'outputColorSpace';
256
+ }
257
+ }
258
+ if (is.equ(propValue, instance[key]))
259
+ continue;
260
+ changes.push([propKey, propValue]);
261
+ }
262
+ return changes;
263
+ }
264
+ // This function applies a set of changes to the instance
265
+ function applyProps(instance, props) {
266
+ // if props is empty
267
+ if (!Object.keys(props).length)
268
+ return instance;
269
+ // Filter equals, events and reserved props
270
+ // filter equals, events , and reserved props
271
+ const localState = getLocalState(instance);
272
+ const rootState = localState.store?.get();
273
+ const changes = diffProps(instance, props);
274
+ for (let i = 0; i < changes.length; i++) {
275
+ let [key, value] = changes[i];
276
+ // Alias (output)encoding => (output)colorSpace (since r152)
277
+ // https://github.com/pmndrs/react-three-fiber/pull/2829
278
+ if (is.colorSpaceExist(instance)) {
279
+ const sRGBEncoding = 3001;
280
+ const SRGBColorSpace = 'srgb';
281
+ const LinearSRGBColorSpace = 'srgb-linear';
282
+ if (key === 'encoding') {
283
+ key = 'colorSpace';
284
+ value = value === sRGBEncoding ? SRGBColorSpace : LinearSRGBColorSpace;
285
+ }
286
+ else if (key === 'outputEncoding') {
287
+ key = 'outputColorSpace';
288
+ value = value === sRGBEncoding ? SRGBColorSpace : LinearSRGBColorSpace;
289
+ }
290
+ }
291
+ const currentInstance = instance;
292
+ const targetProp = currentInstance[key];
293
+ // special treatmen for objects with support for set/copy, and layers
294
+ if (targetProp && targetProp['set'] && (targetProp['copy'] || targetProp instanceof THREE.Layers)) {
295
+ const isColor = targetProp instanceof THREE.Color;
296
+ // if value is an array
297
+ if (Array.isArray(value)) {
298
+ if (targetProp['fromArray'])
299
+ targetProp['fromArray'](value);
300
+ else
301
+ targetProp['set'](...value);
302
+ }
303
+ // test again target.copy
304
+ else if (targetProp['copy'] &&
305
+ value &&
306
+ value.constructor &&
307
+ targetProp.constructor.name === value.constructor.name) {
308
+ targetProp['copy'](value);
309
+ if (!THREE.ColorManagement && !rootState.linear && isColor)
310
+ targetProp['convertSRGBToLinear']();
311
+ }
312
+ // if nothing else fits, just set the single value, ignore undefined
313
+ else if (value !== undefined) {
314
+ const isColor = targetProp instanceof THREE.Color;
315
+ // allow setting array scalars
316
+ if (!isColor && targetProp['setScalar'])
317
+ targetProp['setScalar'](value);
318
+ // layers have no copy function, copy the mask
319
+ else if (targetProp instanceof THREE.Layers && value instanceof THREE.Layers)
320
+ targetProp.mask = value.mask;
321
+ // otherwise just set ...
322
+ else
323
+ targetProp['set'](value);
324
+ // auto-convert srgb
325
+ if (!THREE.ColorManagement && !rootState?.linear && isColor)
326
+ targetProp.convertSRGBToLinear();
327
+ }
328
+ }
329
+ // else just overwrite the value
330
+ else {
331
+ currentInstance[key] = value;
332
+ // auto-convert srgb textures
333
+ if (currentInstance[key] instanceof THREE.Texture &&
334
+ currentInstance[key].format === THREE.RGBAFormat &&
335
+ currentInstance[key].type === THREE.UnsignedByteType) {
336
+ const texture = currentInstance[key];
337
+ if (rootState?.gl) {
338
+ if (is.colorSpaceExist(texture) && is.colorSpaceExist(rootState.gl))
339
+ texture.colorSpace = rootState.gl.outputColorSpace;
340
+ else
341
+ texture.encoding = rootState.gl.outputEncoding;
342
+ }
343
+ }
344
+ }
345
+ checkUpdate(currentInstance[key]);
346
+ checkUpdate(targetProp);
347
+ invalidateInstance(instance);
348
+ }
349
+ const instanceHandlers = localState.eventCount;
350
+ const parent = localState.parent ? untracked(localState.parent) : null;
351
+ if (parent && rootState.internal && instance['raycast'] && instanceHandlers !== localState.eventCount) {
352
+ // Pre-emptively remove the instance from the interaction manager
353
+ const index = rootState.internal.interaction.indexOf(instance);
354
+ if (index > -1)
355
+ rootState.internal.interaction.splice(index, 1);
356
+ // Add the instance to the interaction manager only when it has handlers
357
+ if (localState.eventCount)
358
+ rootState.internal.interaction.push(instance);
359
+ }
360
+ if (parent && localState.afterUpdate && localState.afterUpdate.observed && changes.length) {
361
+ localState.afterUpdate.emit(instance);
362
+ }
363
+ return instance;
364
+ }
365
+
366
+ function assertInjectionContext(fn, injector) {
367
+ try {
368
+ if (!injector) {
369
+ return inject(Injector);
370
+ }
371
+ return injector;
372
+ }
373
+ catch {
374
+ !injector && assertInInjectionContext(fn);
375
+ return null;
376
+ }
228
377
  }
229
378
 
230
379
  const idCache = {};
@@ -276,97 +425,742 @@ function makeObjectGraph(object) {
276
425
  return data;
277
426
  }
278
427
 
279
- function releaseInternalPointerCapture(capturedMap, obj, captures, pointerId) {
280
- const captureData = captures.get(obj);
281
- if (captureData) {
282
- captures.delete(obj);
283
- // if this was the last captured object for this pointer
284
- if (captures.size === 0) {
285
- capturedMap.delete(pointerId);
286
- captureData.target.releasePointerCapture(pointerId);
287
- }
288
- }
289
- }
290
- function removeInteractivity(store, object) {
291
- const internal = store.get('internal');
292
- // removes every trace of an object from data store
293
- internal.interaction = internal.interaction.filter((o) => o !== object);
294
- internal.initialHits = internal.initialHits.filter((o) => o !== object);
295
- internal.hovered.forEach((value, key) => {
296
- if (value.eventObject === object || value.object === object) {
297
- // clear out intersects, they are outdated by now
298
- internal.hovered.delete(key);
299
- }
300
- });
301
- internal.capturedMap.forEach((captures, pointerId) => {
302
- releaseInternalPointerCapture(internal.capturedMap, object, captures, pointerId);
428
+ const shallowLoose = { objects: 'shallow', strict: false };
429
+ const roots = new Map();
430
+ function injectCanvasRootInitializer(injector) {
431
+ injector = assertInjectionContext(injectCanvasRootInitializer, injector);
432
+ return runInInjectionContext(injector, () => {
433
+ const injectedStore = injectNgtStore();
434
+ const loop = injectNgtLoop();
435
+ const destroyRef = inject(DestroyRef);
436
+ return (canvas) => {
437
+ const exist = roots.has(canvas);
438
+ let store = roots.get(canvas);
439
+ if (store) {
440
+ console.warn('[NGT] Same canvas root is being created twice');
441
+ }
442
+ store ||= injectedStore;
443
+ if (!store) {
444
+ throw new Error('[NGT] No store initialized');
445
+ }
446
+ if (!exist) {
447
+ roots.set(canvas, store);
448
+ }
449
+ let isConfigured = false;
450
+ let invalidateRef;
451
+ destroyRef.onDestroy(() => invalidateRef?.destroy());
452
+ return {
453
+ isConfigured,
454
+ destroy: (timeout = 500) => {
455
+ const root = roots.get(canvas);
456
+ if (root) {
457
+ root.set((state) => ({ internal: { ...state.internal, active: false } }));
458
+ setTimeout(() => {
459
+ try {
460
+ const state = root.get();
461
+ state.events.disconnect?.();
462
+ state.gl?.renderLists?.dispose?.();
463
+ state.gl?.forceContextLoss?.();
464
+ if (state.gl?.xr)
465
+ state.xr.disconnect();
466
+ dispose(state);
467
+ roots.delete(canvas);
468
+ }
469
+ catch (e) {
470
+ console.error('[NGT] Unexpected error while destroying Canvas Root', e);
471
+ }
472
+ }, timeout);
473
+ }
474
+ },
475
+ configure: (inputs) => {
476
+ const { gl: glOptions, size: sizeOptions, camera: cameraOptions, raycaster: raycasterOptions, scene: sceneOptions, events, orthographic, lookAt, shadows, linear, legacy, flat, dpr, frameloop, performance, } = inputs;
477
+ const state = store.get();
478
+ const stateToUpdate = {};
479
+ // setup renderer
480
+ let gl = state.gl;
481
+ if (!state.gl)
482
+ stateToUpdate.gl = gl = makeDefaultRenderer(glOptions, canvas);
483
+ // setup raycaster
484
+ let raycaster = state.raycaster;
485
+ if (!raycaster)
486
+ stateToUpdate.raycaster = raycaster = new THREE.Raycaster();
487
+ // set raycaster options
488
+ const { params, ...options } = raycasterOptions || {};
489
+ if (!is.equ(options, raycaster, shallowLoose))
490
+ applyProps(raycaster, { ...options });
491
+ if (!is.equ(params, raycaster.params, shallowLoose)) {
492
+ applyProps(raycaster, { params: { ...raycaster.params, ...(params || {}) } });
493
+ }
494
+ // create default camera
495
+ if (!state.camera) {
496
+ const isCamera = is.camera(cameraOptions);
497
+ let camera = isCamera ? cameraOptions : makeDefaultCamera(orthographic || false, state.size);
498
+ if (!isCamera) {
499
+ if (cameraOptions)
500
+ applyProps(camera, cameraOptions);
501
+ // set position.z
502
+ if (!cameraOptions?.position)
503
+ camera.position.z = 5;
504
+ // always look at center or passed-in lookAt by default
505
+ if (!cameraOptions?.rotation && !cameraOptions?.quaternion) {
506
+ if (Array.isArray(lookAt))
507
+ camera.lookAt(lookAt[0], lookAt[1], lookAt[2]);
508
+ else if (lookAt instanceof THREE.Vector3)
509
+ camera.lookAt(lookAt);
510
+ else
511
+ camera.lookAt(0, 0, 0);
512
+ }
513
+ // update projection matrix after applyprops
514
+ camera.updateProjectionMatrix?.();
515
+ }
516
+ if (!is.instance(camera))
517
+ camera = prepare(camera, { store });
518
+ stateToUpdate.camera = camera;
519
+ }
520
+ // Set up scene (one time only!)
521
+ if (!state.scene) {
522
+ let scene;
523
+ if (sceneOptions instanceof THREE.Scene) {
524
+ scene = prepare(sceneOptions, { store });
525
+ }
526
+ else {
527
+ scene = prepare(new THREE.Scene(), { store });
528
+ if (sceneOptions)
529
+ applyProps(scene, sceneOptions);
530
+ }
531
+ stateToUpdate.scene = scene;
532
+ }
533
+ // Set up XR (one time only!)
534
+ if (!state.xr) {
535
+ // Handle frame behavior in WebXR
536
+ const handleXRFrame = (timestamp, frame) => {
537
+ const state = store.get();
538
+ if (state.frameloop === 'never')
539
+ return;
540
+ loop.advance(timestamp, true, store, frame);
541
+ };
542
+ // Toggle render switching on session
543
+ const handleSessionChange = () => {
544
+ const state = store.get();
545
+ state.gl.xr.enabled = state.gl.xr.isPresenting;
546
+ state.gl.xr.setAnimationLoop(state.gl.xr.isPresenting ? handleXRFrame : null);
547
+ if (!state.gl.xr.isPresenting)
548
+ loop.invalidate(store);
549
+ };
550
+ // WebXR session manager
551
+ const xr = {
552
+ connect: () => {
553
+ gl.xr.addEventListener('sessionstart', handleSessionChange);
554
+ gl.xr.addEventListener('sessionend', handleSessionChange);
555
+ },
556
+ disconnect: () => {
557
+ gl.xr.removeEventListener('sessionstart', handleSessionChange);
558
+ gl.xr.removeEventListener('sessionend', handleSessionChange);
559
+ },
560
+ };
561
+ // Subscribe to WebXR session events
562
+ if (gl.xr && typeof gl.xr.addEventListener === 'function')
563
+ xr.connect();
564
+ stateToUpdate.xr = xr;
565
+ }
566
+ // Set shadowmap
567
+ if (gl.shadowMap) {
568
+ const oldEnabled = gl.shadowMap.enabled;
569
+ const oldType = gl.shadowMap.type;
570
+ gl.shadowMap.enabled = !!shadows;
571
+ if (typeof shadows === 'boolean') {
572
+ gl.shadowMap.type = THREE.PCFSoftShadowMap;
573
+ }
574
+ else if (typeof shadows === 'string') {
575
+ const types = {
576
+ basic: THREE.BasicShadowMap,
577
+ percentage: THREE.PCFShadowMap,
578
+ soft: THREE.PCFSoftShadowMap,
579
+ variance: THREE.VSMShadowMap,
580
+ };
581
+ gl.shadowMap.type = types[shadows] ?? THREE.PCFSoftShadowMap;
582
+ }
583
+ else if (is.obj(shadows)) {
584
+ Object.assign(gl.shadowMap, shadows);
585
+ }
586
+ if (oldEnabled !== gl.shadowMap.enabled || oldType !== gl.shadowMap.type)
587
+ checkNeedsUpdate(gl.shadowMap);
588
+ }
589
+ // Safely set color management if available.
590
+ // Avoid accessing THREE.ColorManagement to play nice with older versions
591
+ if (THREE.ColorManagement) {
592
+ const ColorManagement = THREE.ColorManagement;
593
+ if ('enabled' in ColorManagement)
594
+ ColorManagement['enabled'] = !legacy ?? false;
595
+ else if ('legacyMode' in ColorManagement)
596
+ ColorManagement['legacyMode'] = legacy ?? true;
597
+ }
598
+ // set color space and tonemapping preferences
599
+ const LinearEncoding = 3000;
600
+ const sRGBEncoding = 3001;
601
+ applyProps(gl, {
602
+ outputEncoding: linear ? LinearEncoding : sRGBEncoding,
603
+ toneMapping: flat ? THREE.NoToneMapping : THREE.ACESFilmicToneMapping,
604
+ });
605
+ // Update color management state
606
+ if (state.legacy !== legacy)
607
+ stateToUpdate.legacy = legacy;
608
+ if (state.linear !== linear)
609
+ stateToUpdate.linear = linear;
610
+ if (state.flat !== flat)
611
+ stateToUpdate.flat = flat;
612
+ // Set gl props
613
+ gl.setClearAlpha(0);
614
+ gl.setPixelRatio(makeDpr(state.viewport.dpr));
615
+ gl.setSize(state.size.width, state.size.height);
616
+ if (is.obj(glOptions) &&
617
+ !(typeof glOptions === 'function') &&
618
+ !is.renderer(glOptions) &&
619
+ !is.equ(glOptions, gl, shallowLoose)) {
620
+ applyProps(gl, glOptions);
621
+ }
622
+ // Store events internally
623
+ if (events && !state.events.handlers)
624
+ stateToUpdate.events = events(store);
625
+ // Check performance
626
+ if (performance && !is.equ(performance, state.performance, shallowLoose)) {
627
+ stateToUpdate.performance = { ...state.performance, ...performance };
628
+ }
629
+ store.set(stateToUpdate);
630
+ // Check size, allow it to take on container bounds initially
631
+ const size = computeInitialSize(canvas, sizeOptions);
632
+ if (!is.equ(size, state.size, shallowLoose)) {
633
+ state.setSize(size.width, size.height, size.top, size.left);
634
+ }
635
+ // Check pixelratio
636
+ if (dpr && state.viewport.dpr !== makeDpr(dpr))
637
+ state.setDpr(dpr);
638
+ // Check frameloop
639
+ if (state.frameloop !== frameloop)
640
+ state.setFrameloop(frameloop);
641
+ isConfigured = true;
642
+ invalidateRef?.destroy();
643
+ invalidateRef = effect(() => void store.state().invalidate(), { manualCleanup: true, injector });
644
+ },
645
+ };
646
+ };
303
647
  });
304
- if (store.get('previousStore')) {
305
- removeInteractivity(store.get('previousStore'), object);
306
- }
307
648
  }
308
- function createEvents(store) {
309
- /** calculates delta **/
310
- function calculateDistance(event) {
311
- const internal = store.get('internal');
312
- const dx = event.offsetX - internal.initialClick[0];
313
- const dy = event.offsetY - internal.initialClick[1];
314
- return Math.round(Math.sqrt(dx * dx + dy * dy));
649
+ function computeInitialSize(canvas, defaultSize) {
650
+ if (defaultSize)
651
+ return defaultSize;
652
+ if (canvas instanceof HTMLCanvasElement && canvas.parentElement) {
653
+ return canvas.parentElement.getBoundingClientRect();
315
654
  }
316
- /** returns true if an instance has a valid pointer-event registered, this excludes scroll, clicks etc... **/
317
- function filterPointerEvents(objects) {
318
- return objects.filter((obj) => ['move', 'over', 'enter', 'out', 'leave'].some((name) => {
319
- const eventName = `pointer${name}`;
320
- return getLocalState(obj).handlers?.[eventName];
321
- }));
655
+ return { width: 0, height: 0, top: 0, left: 0 };
656
+ }
657
+ // Disposes an object and all its properties
658
+ function dispose(obj) {
659
+ if (obj.dispose && obj.type !== 'Scene')
660
+ obj.dispose();
661
+ for (const p in obj) {
662
+ p.dispose?.();
663
+ delete obj[p];
322
664
  }
323
- function intersect(event, filter) {
324
- const state = store.get();
325
- const duplicates = new Set();
326
- const intersections = [];
327
- // allow callers to eliminate event objects
328
- const eventObjects = filter ? filter(state.internal.interaction) : state.internal.interaction;
329
- // reset all raycaster cameras to undefined
330
- for (let i = 0; i < eventObjects.length; i++) {
331
- const instanceState = getLocalState(eventObjects[i]).store?.get();
332
- if (instanceState) {
333
- instanceState.raycaster.camera = undefined;
334
- }
335
- }
336
- if (!state.previousStore) {
337
- // make sure root-level pointer and ray are setup
338
- state.events.compute?.(event, store);
665
+ }
666
+
667
+ function createInjectFn(token) {
668
+ return (injectOptions) => inject(token, injectOptions);
669
+ }
670
+ function createProvideFn(token, factory, deps, extraProviders) {
671
+ return (value) => {
672
+ let provider;
673
+ if (value) {
674
+ provider = { provide: token, useValue: value };
339
675
  }
340
- function handleRaycast(obj) {
341
- const objLocalState = getLocalState(obj);
342
- const objStore = objLocalState.store;
343
- const objState = objStore?.get();
344
- // skip event handling when noEvents is set, or when raycaster camera is null
345
- if (!objState || !objState.events.enabled || objState.raycaster.camera === null)
346
- return [];
347
- // when the camera is undefined, we have to call the events layers to update function
348
- if (objState.raycaster.camera === undefined) {
349
- objState.events.compute?.(event, objStore, objState.previousStore);
350
- // if the camera is still undefined, we have to skip this layer entirely
351
- if (objState.raycaster.camera === undefined)
352
- objState.raycaster.camera = null;
353
- }
354
- // intersect object by object
355
- return objState.raycaster.camera ? objState.raycaster.intersectObject(obj, true) : [];
676
+ else {
677
+ provider = { provide: token, useFactory: factory, deps: (deps ?? []) };
356
678
  }
357
- // collect events
358
- let hits = eventObjects
359
- // intersect objects
360
- .flatMap(handleRaycast)
361
- // sort by event priority
362
- .sort((a, b) => {
363
- const aState = getLocalState(a.object).store.get();
364
- const bState = getLocalState(b.object).store.get();
365
- if (!aState || !bState)
366
- return a.distance - b.distance;
367
- return bState.events.priority - aState.events.priority || a.distance - b.distance;
679
+ return extraProviders ? [extraProviders, provider] : provider;
680
+ };
681
+ }
682
+ function createInjectionToken(factory, options) {
683
+ const opts = options ?? { isRoot: true };
684
+ opts.isRoot ??= true;
685
+ if (opts.isRoot) {
686
+ if (opts.token) {
687
+ throw new Error(`\
688
+ createInjectionToken is creating a root InjectionToken but an external token is passed in.
689
+ `);
690
+ }
691
+ const token = new InjectionToken(`Token for ${factory.name}`, {
692
+ factory: () => {
693
+ if (opts.deps && Array.isArray(opts.deps)) {
694
+ return factory(...opts.deps.map((dep) => inject(dep)));
695
+ }
696
+ return factory();
697
+ },
698
+ });
699
+ return [
700
+ createInjectFn(token),
701
+ createProvideFn(token, factory, opts.deps),
702
+ token,
703
+ ];
704
+ }
705
+ const token = opts.token || new InjectionToken(`Token for ${factory.name}`);
706
+ return [
707
+ createInjectFn(token),
708
+ createProvideFn(token, factory, opts.deps, opts.extraProviders),
709
+ token,
710
+ ];
711
+ }
712
+
713
+ function createSubs(callback, subs) {
714
+ const sub = { callback };
715
+ subs.add(sub);
716
+ return () => void subs.delete(sub);
717
+ }
718
+ const globalEffects = new Set();
719
+ const globalAfterEffects = new Set();
720
+ const globalTailEffects = new Set();
721
+ /**
722
+ * Adds a global render callback which is called each frame.
723
+ * @see https://docs.pmnd.rs/react-three-fiber/api/additional-exports#addEffect
724
+ */
725
+ const addEffect = (callback) => createSubs(callback, globalEffects);
726
+ /**
727
+ * Adds a global after-render callback which is called each frame.
728
+ * @see https://docs.pmnd.rs/react-three-fiber/api/additional-exports#addAfterEffect
729
+ */
730
+ const addAfterEffect = (callback) => createSubs(callback, globalAfterEffects);
731
+ /**
732
+ * Adds a global callback which is called when rendering stops.
733
+ * @see https://docs.pmnd.rs/react-three-fiber/api/additional-exports#addTail
734
+ */
735
+ const addTail = (callback) => createSubs(callback, globalTailEffects);
736
+ function run(effects, timestamp) {
737
+ if (!effects.size)
738
+ return;
739
+ for (const { callback } of effects.values()) {
740
+ callback(timestamp);
741
+ }
742
+ }
743
+ function flushGlobalEffects(type, timestamp) {
744
+ switch (type) {
745
+ case 'before':
746
+ return run(globalEffects, timestamp);
747
+ case 'after':
748
+ return run(globalAfterEffects, timestamp);
749
+ case 'tail':
750
+ return run(globalTailEffects, timestamp);
751
+ }
752
+ }
753
+ function render(timestamp, store, frame) {
754
+ const state = store.get();
755
+ // Run local effects
756
+ let delta = state.clock.getDelta();
757
+ // In frameloop='never' mode, clock times are updated using the provided timestamp
758
+ if (state.frameloop === 'never' && typeof timestamp === 'number') {
759
+ delta = timestamp - state.clock.elapsedTime;
760
+ state.clock.oldTime = state.clock.elapsedTime;
761
+ state.clock.elapsedTime = timestamp;
762
+ }
763
+ // Call subscribers (useFrame)
764
+ const subscribers = state.internal.subscribers;
765
+ for (let i = 0; i < subscribers.length; i++) {
766
+ const subscription = subscribers[i];
767
+ subscription.callback({ ...subscription.store.get(), delta, frame });
768
+ }
769
+ // Render content
770
+ if (!state.internal.priority && state.gl.render)
771
+ state.gl.render(state.scene, state.camera);
772
+ // Decrease frame count
773
+ state.internal.frames = Math.max(0, state.internal.frames - 1);
774
+ return state.frameloop === 'always' ? 1 : state.internal.frames;
775
+ }
776
+ function createLoop(roots) {
777
+ let running = false;
778
+ let repeat;
779
+ let frame;
780
+ function loop(timestamp) {
781
+ frame = requestAnimationFrame(loop);
782
+ running = true;
783
+ repeat = 0;
784
+ // Run effects
785
+ flushGlobalEffects('before', timestamp);
786
+ // Render all roots
787
+ for (const root of roots.values()) {
788
+ const state = root.get();
789
+ // If the frameloop is invalidated, do not run another frame
790
+ if (state.internal.active &&
791
+ (state.frameloop === 'always' || state.internal.frames > 0) &&
792
+ !state.gl.xr?.isPresenting) {
793
+ repeat += render(timestamp, root);
794
+ }
795
+ }
796
+ // Run after-effects
797
+ flushGlobalEffects('after', timestamp);
798
+ // Stop the loop if nothing invalidates it
799
+ if (repeat === 0) {
800
+ // Tail call effects, they are called when rendering stops
801
+ flushGlobalEffects('tail', timestamp);
802
+ // Flag end of operation
803
+ running = false;
804
+ return cancelAnimationFrame(frame);
805
+ }
806
+ }
807
+ function invalidate(store, frames = 1) {
808
+ const state = store?.get();
809
+ if (!state)
810
+ return roots.forEach((root) => invalidate(root, frames));
811
+ if (state.gl.xr?.isPresenting || !state.internal.active || state.frameloop === 'never')
812
+ return;
813
+ // Increase frames, do not go higher than 60
814
+ state.internal.frames = Math.min(60, state.internal.frames + frames);
815
+ // If the render-loop isn't active, start it
816
+ if (!running) {
817
+ running = true;
818
+ requestAnimationFrame(loop);
819
+ }
820
+ }
821
+ function advance(timestamp, runGlobalEffects = true, store, frame) {
822
+ if (runGlobalEffects)
823
+ flushGlobalEffects('before', timestamp);
824
+ const state = store?.get();
825
+ if (!state)
826
+ for (const root of roots.values())
827
+ render(timestamp, root);
828
+ else
829
+ render(timestamp, store, frame);
830
+ if (runGlobalEffects)
831
+ flushGlobalEffects('after', timestamp);
832
+ }
833
+ return {
834
+ loop,
835
+ /**
836
+ * Invalidates the view, requesting a frame to be rendered. Will globally invalidate unless passed a root's state.
837
+ * @see https://docs.pmnd.rs/react-three-fiber/api/additional-exports#invalidate
838
+ */
839
+ invalidate,
840
+ /**
841
+ * Advances the frameloop and runs render effects, useful for when manually rendering via `frameloop="never"`.
842
+ * @see https://docs.pmnd.rs/react-three-fiber/api/additional-exports#advance
843
+ */
844
+ advance,
845
+ };
846
+ }
847
+ const [injectNgtLoop, , NGT_LOOP] = createInjectionToken(() => createLoop(roots));
848
+
849
+ function safeDetectChanges(...cdrs) {
850
+ cdrs.forEach((cdr) => {
851
+ if (!cdr)
852
+ return;
853
+ try {
854
+ // dynamic created component with ViewContainerRef#createComponent does not have Context
855
+ // but it has _attachedToViewContainer
856
+ if (cdr['_attachedToViewContainer'] || !!cdr['context']) {
857
+ cdr.detectChanges();
858
+ }
859
+ }
860
+ catch (e) {
861
+ cdr.markForCheck();
862
+ }
863
+ });
864
+ }
865
+
866
+ function storeFactory(loop, document, injector, parent) {
867
+ return runInInjectionContext(injector, () => {
868
+ const window = document.defaultView;
869
+ if (!window) {
870
+ // TODO: revisit this when we need to support multiple platforms
871
+ throw new Error(`[NGT] Window is not available.`);
872
+ }
873
+ const cdr = inject(ChangeDetectorRef);
874
+ // NOTE: using Subject because we do not care about late-subscribers
875
+ const pointerMissed$ = new Subject();
876
+ const store = signalStore(({ get, set }) => {
877
+ const { invalidate, advance } = loop;
878
+ const position = new THREE.Vector3();
879
+ const defaultTarget = new THREE.Vector3();
880
+ const tempTarget = new THREE.Vector3();
881
+ function getCurrentViewport(camera = get('camera'), target = defaultTarget, size = get('size')) {
882
+ const { width, height, top, left } = size;
883
+ const aspect = width / height;
884
+ if (target instanceof THREE.Vector3)
885
+ tempTarget.copy(target);
886
+ else
887
+ tempTarget.set(...target);
888
+ const distance = camera.getWorldPosition(position).distanceTo(tempTarget);
889
+ if (is.orthographicCamera(camera)) {
890
+ return {
891
+ width: width / camera.zoom,
892
+ height: height / camera.zoom,
893
+ top,
894
+ left,
895
+ factor: 1,
896
+ distance,
897
+ aspect,
898
+ };
899
+ }
900
+ else {
901
+ const fov = (camera.fov * Math.PI) / 180; // convert vertical fov to radians
902
+ const h = 2 * Math.tan(fov / 2) * distance; // visible height
903
+ const w = h * (width / height);
904
+ return { width: w, height: h, top, left, factor: width / w, distance, aspect };
905
+ }
906
+ }
907
+ let performanceTimeout = undefined;
908
+ const setPerformanceCurrent = (current) => set((state) => ({ performance: { ...state.performance, current } }));
909
+ const pointer = new THREE.Vector2();
910
+ return {
911
+ pointerMissed$: pointerMissed$.asObservable(),
912
+ events: { priority: 1, enabled: true, connected: false },
913
+ invalidate: (frames = 1) => invalidate(store, frames),
914
+ advance: (timestamp, runGlobalEffects) => advance(timestamp, runGlobalEffects, store),
915
+ legacy: false,
916
+ linear: false,
917
+ flat: false,
918
+ controls: null,
919
+ clock: new THREE.Clock(),
920
+ pointer,
921
+ frameloop: 'always',
922
+ performance: {
923
+ current: 1,
924
+ min: 0.5,
925
+ max: 1,
926
+ debounce: 200,
927
+ regress: () => {
928
+ const state = get();
929
+ // Clear timeout
930
+ if (performanceTimeout)
931
+ clearTimeout(performanceTimeout);
932
+ // Set lower bound performance
933
+ if (state.performance.current !== state.performance.min)
934
+ setPerformanceCurrent(state.performance.min);
935
+ // Go back to upper bound performance after a while unless something regresses meanwhile
936
+ performanceTimeout = setTimeout(() => {
937
+ setPerformanceCurrent(get('performance', 'max'));
938
+ safeDetectChanges(cdr);
939
+ }, state.performance.debounce);
940
+ },
941
+ },
942
+ size: { width: 0, height: 0, top: 0, left: 0, updateStyle: false },
943
+ viewport: {
944
+ initialDpr: 0,
945
+ dpr: 0,
946
+ width: 0,
947
+ height: 0,
948
+ top: 0,
949
+ left: 0,
950
+ aspect: 0,
951
+ distance: 0,
952
+ factor: 0,
953
+ getCurrentViewport,
954
+ },
955
+ setEvents: (events) => set((state) => ({ ...state, events: { ...state.events, ...events } })),
956
+ setSize: (width, height, top, left) => {
957
+ const camera = get('camera');
958
+ const size = { width, height, top: top || 0, left: left || 0 };
959
+ set((state) => ({
960
+ size,
961
+ viewport: { ...state.viewport, ...getCurrentViewport(camera, defaultTarget, size) },
962
+ }));
963
+ },
964
+ setDpr: (dpr) => set((state) => {
965
+ const resolved = makeDpr(dpr, window);
966
+ return {
967
+ viewport: {
968
+ ...state.viewport,
969
+ dpr: resolved,
970
+ initialDpr: state.viewport.initialDpr || resolved,
971
+ },
972
+ };
973
+ }),
974
+ setFrameloop: (frameloop = 'always') => {
975
+ const clock = get('clock');
976
+ // if frameloop === "never" clock.elapsedTime is updated using advance(timestamp)
977
+ clock.stop();
978
+ clock.elapsedTime = 0;
979
+ if (frameloop !== 'never') {
980
+ clock.start();
981
+ clock.elapsedTime = 0;
982
+ }
983
+ set(() => ({ frameloop }));
984
+ },
985
+ previousRoot: parent,
986
+ internal: {
987
+ active: false,
988
+ priority: 0,
989
+ frames: 0,
990
+ lastEvent: new ElementRef(null),
991
+ interaction: [],
992
+ hovered: new Map(),
993
+ subscribers: [],
994
+ initialClick: [0, 0],
995
+ initialHits: [],
996
+ capturedMap: new Map(),
997
+ subscribe: (callback, priority = 0, _store = store) => {
998
+ const internal = get('internal');
999
+ // If this subscription was given a priority, it takes rendering into its own hands
1000
+ // For that reason we switch off automatic rendering and increase the manual flag
1001
+ // As long as this flag is positive there can be no internal rendering at all
1002
+ // because there could be multiple render subscriptions
1003
+ internal.priority = internal.priority + (priority > 0 ? 1 : 0);
1004
+ internal.subscribers.push({ callback, priority, store });
1005
+ // Register subscriber and sort layers from lowest to highest, meaning,
1006
+ // highest priority renders last (on top of the other frames)
1007
+ internal.subscribers = internal.subscribers.sort((a, b) => (a.priority || 0) - (b.priority || 0));
1008
+ return () => {
1009
+ const internal = get('internal');
1010
+ if (internal?.subscribers) {
1011
+ // Decrease manual flag if this subscription had a priority
1012
+ internal.priority = internal.priority - (priority > 0 ? 1 : 0);
1013
+ // Remove subscriber from list
1014
+ internal.subscribers = internal.subscribers.filter((s) => s.callback !== callback);
1015
+ }
1016
+ };
1017
+ },
1018
+ },
1019
+ };
1020
+ });
1021
+ // NOTE: assign pointerMissed$ so we can use it in events
1022
+ Object.defineProperty(store, 'pointerMissed$', { get: () => pointerMissed$ });
1023
+ const state = store.get();
1024
+ let oldSize = state.size;
1025
+ let oldDpr = state.viewport.dpr;
1026
+ let oldCamera = state.camera;
1027
+ const _camera = store.select('camera');
1028
+ const _size = store.select('size');
1029
+ const _viewport = store.select('viewport');
1030
+ effect(() => {
1031
+ const [camera, size, viewport, gl] = [_camera(), _size(), _viewport(), store.get('gl')];
1032
+ // Resize camera and renderer on changes to size and pixelratio
1033
+ if (size !== oldSize || viewport.dpr !== oldDpr) {
1034
+ oldSize = size;
1035
+ oldDpr = viewport.dpr;
1036
+ // Update camera & renderer
1037
+ updateCamera(camera, size);
1038
+ gl.setPixelRatio(viewport.dpr);
1039
+ const updateStyle = typeof HTMLCanvasElement !== 'undefined' && gl.domElement instanceof HTMLCanvasElement;
1040
+ gl.setSize(size.width, size.height, updateStyle);
1041
+ }
1042
+ // Update viewport once the camera changes
1043
+ if (camera !== oldCamera) {
1044
+ oldCamera = camera;
1045
+ updateCamera(camera, size);
1046
+ // Update viewport
1047
+ store.set((state) => ({
1048
+ viewport: { ...state.viewport, ...state.viewport.getCurrentViewport(camera) },
1049
+ }));
1050
+ }
1051
+ });
1052
+ return store;
1053
+ });
1054
+ }
1055
+ const NGT_STORE = new InjectionToken('NgtStore token');
1056
+ const [injectNgtStore, provideNgtStore] = createInjectionToken(storeFactory, {
1057
+ isRoot: false,
1058
+ deps: [NGT_LOOP, DOCUMENT, Injector, [new Optional(), new SkipSelf(), NGT_STORE]],
1059
+ token: NGT_STORE,
1060
+ });
1061
+
1062
+ function injectBeforeRender(cb, { priority = 0, injector } = {}) {
1063
+ injector = assertInjectionContext(injectBeforeRender, injector);
1064
+ return runInInjectionContext(injector, () => {
1065
+ const store = injectNgtStore();
1066
+ const sub = store.get('internal').subscribe(cb, priority, store);
1067
+ inject(DestroyRef).onDestroy(() => void sub());
1068
+ return sub;
1069
+ });
1070
+ }
1071
+
1072
+ /**
1073
+ * Release pointer captures.
1074
+ * This is called by releasePointerCapture in the API, and when an object is removed.
1075
+ */
1076
+ function releaseInternalPointerCapture(capturedMap, obj, captures, pointerId) {
1077
+ const captureData = captures.get(obj);
1078
+ if (captureData) {
1079
+ captures.delete(obj);
1080
+ // If this was the last capturing object for this pointer
1081
+ if (captures.size === 0) {
1082
+ capturedMap.delete(pointerId);
1083
+ captureData.target.releasePointerCapture(pointerId);
1084
+ }
1085
+ }
1086
+ }
1087
+ function removeInteractivity(store, object) {
1088
+ const { internal } = store.get();
1089
+ // Removes every trace of an object from the data store
1090
+ internal.interaction = internal.interaction.filter((o) => o !== object);
1091
+ internal.initialHits = internal.initialHits.filter((o) => o !== object);
1092
+ internal.hovered.forEach((value, key) => {
1093
+ if (value.eventObject === object || value.object === object) {
1094
+ // Clear out intersects, they are outdated by now
1095
+ internal.hovered.delete(key);
1096
+ }
1097
+ });
1098
+ internal.capturedMap.forEach((captures, pointerId) => {
1099
+ releaseInternalPointerCapture(internal.capturedMap, object, captures, pointerId);
1100
+ });
1101
+ }
1102
+ function createEvents(store) {
1103
+ /** Calculates delta */
1104
+ function calculateDistance(event) {
1105
+ const internal = store.get('internal');
1106
+ const dx = event.offsetX - internal.initialClick[0];
1107
+ const dy = event.offsetY - internal.initialClick[1];
1108
+ return Math.round(Math.sqrt(dx * dx + dy * dy));
1109
+ }
1110
+ /** Returns true if an instance has a valid pointer-event registered, this excludes scroll, clicks etc */
1111
+ function filterPointerEvents(objects) {
1112
+ return objects.filter((obj) => ['move', 'over', 'enter', 'out', 'leave'].some((name) => {
1113
+ const eventName = `pointer${name}`;
1114
+ return getLocalState(obj).handlers?.[eventName];
1115
+ }));
1116
+ }
1117
+ function intersect(event, filter) {
1118
+ const state = store.get();
1119
+ const duplicates = new Set();
1120
+ const intersections = [];
1121
+ // Allow callers to eliminate event objects
1122
+ const eventsObjects = filter ? filter(state.internal.interaction) : state.internal.interaction;
1123
+ // Reset all raycaster cameras to undefined
1124
+ for (let i = 0; i < eventsObjects.length; i++) {
1125
+ const state = getLocalState(eventsObjects[i]).store.get();
1126
+ if (state) {
1127
+ state.raycaster.camera = undefined;
1128
+ }
1129
+ }
1130
+ if (!state.previousRoot) {
1131
+ // Make sure root-level pointer and ray are set up
1132
+ state.events.compute?.(event, store, null);
1133
+ }
1134
+ function handleRaycast(obj) {
1135
+ const objLocalState = getLocalState(obj);
1136
+ const objStore = objLocalState.store;
1137
+ const objState = objStore?.get();
1138
+ // Skip event handling when noEvents is set, or when the raycasters camera is null
1139
+ if (!objState || !objState.events.enabled || objState.raycaster.camera === null)
1140
+ return [];
1141
+ // When the camera is undefined we have to call the event layers update function
1142
+ if (objState.raycaster.camera === undefined) {
1143
+ objState.events.compute?.(event, objStore, objState.previousRoot);
1144
+ // If the camera is still undefined we have to skip this layer entirely
1145
+ if (objState.raycaster.camera === undefined)
1146
+ objState.raycaster.camera = null;
1147
+ }
1148
+ // Intersect object by object
1149
+ return objState.raycaster.camera ? objState.raycaster.intersectObject(obj, true) : [];
1150
+ }
1151
+ // Collect events
1152
+ let hits = eventsObjects
1153
+ // Intersect objects
1154
+ .flatMap(handleRaycast)
1155
+ // Sort by event priority and distance
1156
+ .sort((a, b) => {
1157
+ const aState = getLocalState(a.object).store.get();
1158
+ const bState = getLocalState(b.object).store.get();
1159
+ if (!aState || !bState)
1160
+ return a.distance - b.distance;
1161
+ return bState.events.priority - aState.events.priority || a.distance - b.distance;
368
1162
  })
369
- // filter out duplicates
1163
+ // Filter out duplicates
370
1164
  .filter((item) => {
371
1165
  const id = makeId(item);
372
1166
  if (duplicates.has(id))
@@ -374,10 +1168,11 @@ function createEvents(store) {
374
1168
  duplicates.add(id);
375
1169
  return true;
376
1170
  });
377
- // allow custom userland intersect sort order, this likely only makes sense on the root
1171
+ // https://github.com/mrdoob/three.js/issues/16031
1172
+ // Allow custom userland intersect sort order, this likely only makes sense on the root filter
378
1173
  if (state.events.filter)
379
1174
  hits = state.events.filter(hits, store);
380
- // bubble up the events, find the event source
1175
+ // Bubble up the events, find the event source (eventObject)
381
1176
  for (const hit of hits) {
382
1177
  let eventObject = hit.object;
383
1178
  // bubble event up
@@ -388,20 +1183,19 @@ function createEvents(store) {
388
1183
  eventObject = eventObject.parent;
389
1184
  }
390
1185
  }
391
- // if the interaction is captured, make all capturing targets part of the intersects
1186
+ // If the interaction is captured, make all capturing targets part of the intersect.
392
1187
  if ('pointerId' in event && state.internal.capturedMap.has(event.pointerId)) {
393
- for (const capturedData of state.internal.capturedMap.get(event.pointerId).values()) {
394
- if (!duplicates.has(makeId(capturedData.intersection))) {
395
- intersections.push(capturedData.intersection);
396
- }
1188
+ for (let captureData of state.internal.capturedMap.get(event.pointerId).values()) {
1189
+ if (!duplicates.has(makeId(captureData.intersection)))
1190
+ intersections.push(captureData.intersection);
397
1191
  }
398
1192
  }
399
1193
  return intersections;
400
1194
  }
401
- /** handle intersections by forwarding them to handlers */
1195
+ /** Handles intersections by forwarding them to handlers */
402
1196
  function handleIntersects(intersections, event, delta, callback) {
403
1197
  const rootState = store.get();
404
- // if anything has been found, forward it to the event listeners
1198
+ // If anything has been found, forward it to the event listeners
405
1199
  if (intersections.length) {
406
1200
  const localState = { stopped: false };
407
1201
  for (const hit of intersections) {
@@ -412,14 +1206,17 @@ function createEvents(store) {
412
1206
  const setPointerCapture = (id) => {
413
1207
  const captureData = { intersection: hit, target: event.target };
414
1208
  if (internal.capturedMap.has(id)) {
415
- // if the pointerId was previously captured, we add the hit to the event capturedMap
1209
+ // if the pointerId was previously captured, we add the hit to the
1210
+ // event capturedMap.
416
1211
  internal.capturedMap.get(id).set(hit.eventObject, captureData);
417
1212
  }
418
1213
  else {
419
- // if the pointerId was not previously captured, we create a Map containing the hitObject, and the hit. hitObject is used for faster access
1214
+ // if the pointerId was not previously captured, we create a map
1215
+ // containing the hitObject, and the hit. hitObject is used for
1216
+ // faster access.
420
1217
  internal.capturedMap.set(id, new Map([[hit.eventObject, captureData]]));
421
1218
  }
422
- // call the original event now
1219
+ // Call the original event now
423
1220
  event.target.setPointerCapture(id);
424
1221
  };
425
1222
  const releasePointerCapture = (id) => {
@@ -428,15 +1225,15 @@ function createEvents(store) {
428
1225
  releaseInternalPointerCapture(internal.capturedMap, hit.eventObject, captures, id);
429
1226
  }
430
1227
  };
431
- // add native event props
1228
+ // Add native event props
432
1229
  const extractEventProps = {};
433
1230
  // This iterates over the event's properties including the inherited ones. Native PointerEvents have most of their props as getters which are inherited, but polyfilled PointerEvents have them all as their own properties (i.e. not inherited). We can't use Object.keys() or Object.entries() as they only return "own" properties; nor Object.getPrototypeOf(event) as that *doesn't* return "own" properties, only inherited ones.
434
- for (const prop in event) {
435
- const property = event[prop];
436
- // only copy over atomics, leave functions alone as these should be called as event.nativeEvent.fn()
437
- if (typeof property !== 'function') {
1231
+ for (let prop in event) {
1232
+ let property = event[prop];
1233
+ // Only copy over atomics, leave functions alone as these should be
1234
+ // called as event.nativeEvent.fn()
1235
+ if (typeof property !== 'function')
438
1236
  extractEventProps[prop] = property;
439
- }
440
1237
  }
441
1238
  const raycastEvent = {
442
1239
  ...hit,
@@ -447,7 +1244,7 @@ function createEvents(store) {
447
1244
  delta,
448
1245
  unprojectedPoint,
449
1246
  ray: raycaster.ray,
450
- camera: camera,
1247
+ camera,
451
1248
  // Hijack stopPropagation, which just sets a flag
452
1249
  stopPropagation() {
453
1250
  // https://github.com/pmndrs/react-three-fiber/issues/596
@@ -475,9 +1272,9 @@ function createEvents(store) {
475
1272
  currentTarget: { hasPointerCapture, setPointerCapture, releasePointerCapture },
476
1273
  nativeEvent: event,
477
1274
  };
478
- // call subscribers
1275
+ // Call subscribers
479
1276
  callback(raycastEvent);
480
- // event bubbling may be interupted by stopPropagation
1277
+ // Event bubbling may be interrupted by stopPropagation
481
1278
  if (localState.stopped === true)
482
1279
  break;
483
1280
  }
@@ -485,9 +1282,9 @@ function createEvents(store) {
485
1282
  return intersections;
486
1283
  }
487
1284
  function cancelPointer(intersections) {
488
- const { internal } = store.get();
1285
+ const internal = store.get('internal');
489
1286
  for (const hoveredObj of internal.hovered.values()) {
490
- // When no objects were hit or the hovered object wasn't found underneath the cursor
1287
+ // When no objects were hit or the the hovered object wasn't found underneath the cursor
491
1288
  // we call onPointerOut and delete the object from the hovered-elements map
492
1289
  if (!intersections.length ||
493
1290
  !intersections.find((hit) => hit.object === hoveredObj.object &&
@@ -531,14 +1328,14 @@ function createEvents(store) {
531
1328
  }
532
1329
  // Any other pointer goes here ...
533
1330
  return function handleEvent(event) {
534
- const { onPointerMissed, internal } = store.get();
1331
+ const pointerMissed$ = store['pointerMissed$'];
1332
+ const internal = store.get('internal');
535
1333
  // prepareRay(event)
536
1334
  internal.lastEvent.nativeElement = event;
537
1335
  // Get fresh intersects
538
1336
  const isPointerMove = name === 'pointermove';
539
1337
  const isClickEvent = name === 'click' || name === 'contextmenu' || name === 'dblclick';
540
1338
  const filter = isPointerMove ? filterPointerEvents : undefined;
541
- // const hits = patchIntersects(intersect(filter), event)
542
1339
  const hits = intersect(event, filter);
543
1340
  const delta = isClickEvent ? calculateDistance(event) : 0;
544
1341
  // Save initial coordinates on pointer-down
@@ -551,8 +1348,7 @@ function createEvents(store) {
551
1348
  if (isClickEvent && !hits.length) {
552
1349
  if (delta <= 2) {
553
1350
  pointerMissed(event, internal.interaction);
554
- if (onPointerMissed)
555
- onPointerMissed(event);
1351
+ pointerMissed$.next(event);
556
1352
  }
557
1353
  }
558
1354
  // Take care of unhover
@@ -565,6 +1361,20 @@ function createEvents(store) {
565
1361
  // Check presence of handlers
566
1362
  if (!instance?.eventCount)
567
1363
  return;
1364
+ /*
1365
+ MAYBE TODO, DELETE IF NOT:
1366
+ Check if the object is captured, captured events should not have intersects running in parallel
1367
+ But wouldn't it be better to just replace capturedMap with a single entry?
1368
+ Also, are we OK with straight up making picking up multiple objects impossible?
1369
+
1370
+ const pointerId = (data as ThreeEvent<PointerEvent>).pointerId
1371
+ if (pointerId !== undefined) {
1372
+ const capturedMeshSet = internal.capturedMap.get(pointerId)
1373
+ if (capturedMeshSet) {
1374
+ const captured = capturedMeshSet.get(eventObject)
1375
+ if (captured && captured.localState.stopped) return
1376
+ }
1377
+ }*/
568
1378
  if (isPointerMove) {
569
1379
  // Move event ...
570
1380
  if (handlers?.pointerover ||
@@ -659,6 +1469,11 @@ function createPointerEvents(store) {
659
1469
  handlers[supportedEventName] = handlePointer(supportedEventName);
660
1470
  return handlers;
661
1471
  }, {}),
1472
+ update: () => {
1473
+ const { events, internal } = store.get();
1474
+ if (internal.lastEvent?.nativeElement && events.handlers)
1475
+ events.handlers.pointermove(internal.lastEvent.nativeElement);
1476
+ },
662
1477
  connect: (target) => {
663
1478
  const state = store.get();
664
1479
  state.events.disconnect?.();
@@ -682,41 +1497,6 @@ function createPointerEvents(store) {
682
1497
  };
683
1498
  }
684
1499
 
685
- function assertInjectionContext(fn, injector) {
686
- try {
687
- if (!injector) {
688
- return inject(Injector);
689
- }
690
- return injector;
691
- }
692
- catch {
693
- !injector && assertInInjectionContext(fn);
694
- return null;
695
- }
696
- }
697
-
698
- function safeDetectChanges(cdr) {
699
- if (!cdr)
700
- return;
701
- try {
702
- // dynamic created component with ViewContainerRef#createComponent does not have Context
703
- // but it has _attachedToViewContainer
704
- if (cdr['_attachedToViewContainer'] || !!cdr['context']) {
705
- cdr.detectChanges();
706
- }
707
- }
708
- catch (e) {
709
- cdr.markForCheck();
710
- }
711
- }
712
-
713
- function requestAnimationFrameInInjectionContext(cb, injector) {
714
- injector = assertInjectionContext(requestAnimationFrameInInjectionContext, injector);
715
- return requestAnimationFrame(() => {
716
- return runInInjectionContext(injector, cb);
717
- });
718
- }
719
-
720
1500
  const cached = new Map();
721
1501
  function normalizeInputs(input) {
722
1502
  if (Array.isArray(input))
@@ -752,702 +1532,160 @@ function injectNgtLoader(loaderConstructorFactory, inputs, { extensions, onProgr
752
1532
  return runInInjectionContext(injector, () => {
753
1533
  const cdr = inject(ChangeDetectorRef);
754
1534
  const effector = load(loaderConstructorFactory, inputs, { extensions, onProgress });
755
- requestAnimationFrameInInjectionContext(() => {
756
- effect(() => {
757
- const originalUrls = inputs();
758
- Promise.all(effector()).then((results) => {
759
- response.update(() => {
760
- if (Array.isArray(originalUrls))
761
- return results;
762
- if (typeof originalUrls === 'string')
763
- return results[0];
764
- const keys = Object.keys(originalUrls);
765
- return keys.reduce((result, key) => {
766
- result[key] = results[keys.indexOf(key)];
767
- return result;
768
- }, {});
769
- });
770
- safeDetectChanges(cdr);
1535
+ effect(() => {
1536
+ const originalUrls = inputs();
1537
+ Promise.all(effector()).then((results) => {
1538
+ response.update(() => {
1539
+ if (Array.isArray(originalUrls))
1540
+ return results;
1541
+ if (typeof originalUrls === 'string')
1542
+ return results[0];
1543
+ const keys = Object.keys(originalUrls);
1544
+ return keys.reduce((result, key) => {
1545
+ result[key] = results[keys.indexOf(key)];
1546
+ return result;
1547
+ }, {});
771
1548
  });
772
- }, { allowSignalWrites: true });
1549
+ safeDetectChanges(cdr);
1550
+ });
773
1551
  });
774
1552
  return response.asReadonly();
775
1553
  });
776
1554
  }
777
1555
  injectNgtLoader['preload'] = (loaderConstructorFactory, inputs, extensions) => {
778
1556
  Promise.all(load(loaderConstructorFactory, inputs, { extensions })());
779
- };
780
- injectNgtLoader['destroy'] = () => {
781
- cached.clear();
782
- };
783
-
784
- function createSubs(callback, subs) {
785
- const sub = { callback };
786
- subs.add(sub);
787
- return () => void subs.delete(sub);
788
- }
789
- const globalEffects = new Set();
790
- const globalAfterEffects = new Set();
791
- const globalTailEffects = new Set();
792
- /**
793
- * Adds a global render callback which is called each frame.
794
- * @see https://docs.pmnd.rs/react-three-fiber/api/additional-exports#addEffect
795
- */
796
- const addEffect = (callback) => createSubs(callback, globalEffects);
797
- /**
798
- * Adds a global after-render callback which is called each frame.
799
- * @see https://docs.pmnd.rs/react-three-fiber/api/additional-exports#addAfterEffect
800
- */
801
- const addAfterEffect = (callback) => createSubs(callback, globalAfterEffects);
802
- /**
803
- * Adds a global callback which is called when rendering stops.
804
- * @see https://docs.pmnd.rs/react-three-fiber/api/additional-exports#addTail
805
- */
806
- const addTail = (callback) => createSubs(callback, globalTailEffects);
807
- function run(effects, timestamp) {
808
- if (!effects.size)
809
- return;
810
- for (const { callback } of effects.values()) {
811
- callback(timestamp);
812
- }
813
- }
814
- function flushGlobalEffects(type, timestamp) {
815
- switch (type) {
816
- case 'before':
817
- return run(globalEffects, timestamp);
818
- case 'after':
819
- return run(globalAfterEffects, timestamp);
820
- case 'tail':
821
- return run(globalTailEffects, timestamp);
822
- }
823
- }
824
- function render(timestamp, store, frame) {
825
- const state = store.get();
826
- // Run local effects
827
- let delta = state.clock.getDelta();
828
- // In frameloop='never' mode, clock times are updated using the provided timestamp
829
- if (state.frameloop === 'never' && typeof timestamp === 'number') {
830
- delta = timestamp - state.clock.elapsedTime;
831
- state.clock.oldTime = state.clock.elapsedTime;
832
- state.clock.elapsedTime = timestamp;
833
- }
834
- // Call subscribers (useFrame)
835
- const subscribers = state.internal.subscribers;
836
- for (let i = 0; i < subscribers.length; i++) {
837
- const subscription = subscribers[i];
838
- subscription.callback({ ...subscription.store.get(), delta, frame });
839
- }
840
- // Render content
841
- if (!state.internal.priority && state.gl.render)
842
- state.gl.render(state.scene, state.camera);
843
- // Decrease frame count
844
- state.internal.frames = Math.max(0, state.internal.frames - 1);
845
- return state.frameloop === 'always' ? 1 : state.internal.frames;
846
- }
847
- function createLoop(roots) {
848
- let running = false;
849
- let repeat;
850
- let frame;
851
- function loop(timestamp) {
852
- frame = requestAnimationFrame(loop);
853
- running = true;
854
- repeat = 0;
855
- // Run effects
856
- flushGlobalEffects('before', timestamp);
857
- // Render all roots
858
- for (const root of roots.values()) {
859
- const state = root.get();
860
- // If the frameloop is invalidated, do not run another frame
861
- if (state.internal.active &&
862
- (state.frameloop === 'always' || state.internal.frames > 0) &&
863
- !state.gl.xr?.isPresenting) {
864
- repeat += render(timestamp, root);
865
- }
866
- }
867
- // Run after-effects
868
- flushGlobalEffects('after', timestamp);
869
- // Stop the loop if nothing invalidates it
870
- if (repeat === 0) {
871
- // Tail call effects, they are called when rendering stops
872
- flushGlobalEffects('tail', timestamp);
873
- // Flag end of operation
874
- running = false;
875
- return cancelAnimationFrame(frame);
876
- }
877
- }
878
- function invalidate(store, frames = 1) {
879
- const state = store?.get();
880
- if (!state)
881
- return roots.forEach((root) => invalidate(root, frames));
882
- if (state.gl.xr?.isPresenting || !state.internal.active || state.frameloop === 'never')
883
- return;
884
- // Increase frames, do not go higher than 60
885
- state.internal.frames = Math.min(60, state.internal.frames + frames);
886
- // If the render-loop isn't active, start it
887
- if (!running) {
888
- running = true;
889
- requestAnimationFrame(loop);
890
- }
891
- }
892
- function advance(timestamp, runGlobalEffects = true, store, frame) {
893
- const state = store?.get();
894
- if (runGlobalEffects)
895
- flushGlobalEffects('before', timestamp);
896
- if (!state)
897
- for (const root of roots.values())
898
- render(timestamp, root);
899
- else
900
- render(timestamp, store, frame);
901
- if (runGlobalEffects)
902
- flushGlobalEffects('after', timestamp);
903
- }
904
- return {
905
- loop,
906
- /**
907
- * Invalidates the view, requesting a frame to be rendered. Will globally invalidate unless passed a root's state.
908
- * @see https://docs.pmnd.rs/react-three-fiber/api/additional-exports#invalidate
909
- */
910
- invalidate,
911
- /**
912
- * Advances the frameloop and runs render effects, useful for when manually rendering via `frameloop="never"`.
913
- * @see https://docs.pmnd.rs/react-three-fiber/api/additional-exports#advance
914
- */
915
- advance,
916
- };
917
- }
918
-
919
- function diffProps(instance, props) {
920
- const propsEntries = Object.entries(props);
921
- const changes = [];
922
- for (const [propKey, propValue] of propsEntries) {
923
- let key = propKey;
924
- if (is.colorSpaceExist(instance)) {
925
- if (propKey === 'encoding') {
926
- key = 'colorSpace';
927
- }
928
- else if (propKey === 'outputEncoding') {
929
- key = 'outputColorSpace';
930
- }
931
- }
932
- if (is.equ(propValue, instance[key]))
933
- continue;
934
- changes.push([propKey, propValue]);
935
- }
936
- return changes;
937
- }
938
- function applyProps(instance, props) {
939
- // if props is empty
940
- if (!Object.keys(props).length)
941
- return instance;
942
- // filter equals, events , and reserved props
943
- const localState = getLocalState(instance);
944
- const rootState = localState.store?.get();
945
- const changes = diffProps(instance, props);
946
- for (let i = 0; i < changes.length; i++) {
947
- const currentInstance = instance;
948
- let key = changes[i][0];
949
- let value = changes[i][1];
950
- if (is.colorSpaceExist(currentInstance)) {
951
- const sRGBEncoding = 3001;
952
- const SRGBColorSpace = 'srgb';
953
- const LinearSRGBColorSpace = 'srgb-linear';
954
- if (key === 'encoding') {
955
- key = 'colorSpace';
956
- value = value === sRGBEncoding ? SRGBColorSpace : LinearSRGBColorSpace;
957
- }
958
- else if (key === 'outputEncoding') {
959
- key = 'outputColorSpace';
960
- value = value === sRGBEncoding ? SRGBColorSpace : LinearSRGBColorSpace;
961
- }
962
- }
963
- const targetProp = currentInstance[key];
964
- // special treatmen for objects with support for set/copy, and layers
965
- if (targetProp && targetProp['set'] && (targetProp['copy'] || targetProp instanceof THREE.Layers)) {
966
- const isColor = targetProp instanceof THREE.Color;
967
- // if value is an array
968
- if (Array.isArray(value)) {
969
- if (targetProp['fromArray'])
970
- targetProp['fromArray'](value);
971
- else
972
- targetProp['set'](...value);
973
- }
974
- // test again target.copy
975
- else if (targetProp['copy'] &&
976
- value &&
977
- value.constructor &&
978
- targetProp.constructor.name === value.constructor.name) {
979
- targetProp['copy'](value);
980
- if (!THREE.ColorManagement && !rootState.linear && isColor)
981
- targetProp['convertSRGBToLinear']();
982
- }
983
- // if nothing else fits, just set the single value, ignore undefined
984
- else if (value !== undefined) {
985
- const isColor = targetProp instanceof THREE.Color;
986
- // allow setting array scalars
987
- if (!isColor && targetProp['setScalar'])
988
- targetProp['setScalar'](value);
989
- // layers have no copy function, copy the mask
990
- else if (targetProp instanceof THREE.Layers && value instanceof THREE.Layers)
991
- targetProp.mask = value.mask;
992
- // otherwise just set ...
993
- else
994
- targetProp['set'](value);
995
- // auto-convert srgb
996
- if (!THREE.ColorManagement && !rootState?.linear && isColor)
997
- targetProp.convertSRGBToLinear();
1557
+ };
1558
+ injectNgtLoader['destroy'] = () => {
1559
+ cached.clear();
1560
+ };
1561
+
1562
+ const catalogue = {};
1563
+ function extend(objects) {
1564
+ Object.assign(catalogue, objects);
1565
+ }
1566
+ const [injectNgtCatalogue] = createInjectionToken(() => catalogue);
1567
+
1568
+ const ROUTED_SCENE = '__ngt_renderer_is_routed_scene__';
1569
+ const HTML = '__ngt_renderer_is_html';
1570
+ const SPECIAL_INTERNAL_ADD_COMMENT = '__ngt_renderer_add_comment__';
1571
+ const SPECIAL_DOM_TAG = {
1572
+ NGT_PORTAL: 'ngt-portal',
1573
+ NGT_PRIMITIVE: 'ngt-primitive',
1574
+ NGT_VALUE: 'ngt-value',
1575
+ };
1576
+ const SPECIAL_PROPERTIES = {
1577
+ COMPOUND: 'ngtCompound',
1578
+ RENDER_PRIORITY: 'priority',
1579
+ ATTACH: 'attach',
1580
+ VALUE: 'rawValue',
1581
+ REF: 'ref',
1582
+ };
1583
+ const SPECIAL_EVENTS = {
1584
+ BEFORE_RENDER: 'beforeRender',
1585
+ AFTER_UPDATE: 'afterUpdate',
1586
+ AFTER_ATTACH: 'afterAttach',
1587
+ };
1588
+
1589
+ class NgtCommonDirective {
1590
+ static { this.processComment = true; }
1591
+ constructor() {
1592
+ this.vcr = inject(ViewContainerRef);
1593
+ this.zone = inject(NgZone);
1594
+ this.template = inject(TemplateRef);
1595
+ this.injected = false;
1596
+ this.shouldCreateView = true;
1597
+ if (NgtCommonDirective.processComment) {
1598
+ const commentNode = this.vcr.element.nativeElement;
1599
+ if (commentNode[SPECIAL_INTERNAL_ADD_COMMENT]) {
1600
+ commentNode[SPECIAL_INTERNAL_ADD_COMMENT]();
1601
+ delete commentNode[SPECIAL_INTERNAL_ADD_COMMENT];
998
1602
  }
999
1603
  }
1000
- // else just overwrite the value
1001
- else {
1002
- currentInstance[key] = value;
1003
- // auto-convert srgb textures
1004
- if (currentInstance[key] instanceof THREE.Texture &&
1005
- currentInstance[key].format === THREE.RGBAFormat &&
1006
- currentInstance[key].type === THREE.UnsignedByteType) {
1007
- const texture = currentInstance[key];
1008
- if (rootState?.gl) {
1009
- if (is.colorSpaceExist(texture) && is.colorSpaceExist(rootState.gl))
1010
- texture.colorSpace = rootState.gl.outputColorSpace;
1011
- else
1012
- texture.encoding = rootState.gl.outputEncoding;
1604
+ inject(DestroyRef).onDestroy(() => {
1605
+ this.view?.destroy();
1606
+ });
1607
+ }
1608
+ createView() {
1609
+ this.zone.runOutsideAngular(() => {
1610
+ if (this.shouldCreateView) {
1611
+ if (this.view && !this.view.destroyed) {
1612
+ this.view.destroy();
1013
1613
  }
1614
+ this.view = this.vcr.createEmbeddedView(this.template);
1615
+ safeDetectChanges(this.view);
1014
1616
  }
1015
- }
1016
- checkUpdate(targetProp);
1017
- invalidateInstance(instance);
1018
- }
1019
- const instanceHandlers = localState.eventCount;
1020
- const parent = localState.parent ? untracked(localState.parent) : null;
1021
- if (parent && rootState.internal && instance['raycast'] && instanceHandlers !== localState.eventCount) {
1022
- // Pre-emptively remove the instance from the interaction manager
1023
- const index = rootState.internal.interaction.indexOf(instance);
1024
- if (index > -1)
1025
- rootState.internal.interaction.splice(index, 1);
1026
- // Add the instance to the interaction manager only when it has handlers
1027
- if (localState.eventCount)
1028
- rootState.internal.interaction.push(instance);
1029
- }
1030
- if (parent && localState.afterUpdate && localState.afterUpdate.observed && changes.length) {
1031
- localState.afterUpdate.emit(instance);
1617
+ });
1032
1618
  }
1033
- return instance;
1619
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.1.7", ngImport: i0, type: NgtCommonDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
1620
+ static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "16.1.7", type: NgtCommonDirective, ngImport: i0 }); }
1034
1621
  }
1622
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.1.7", ngImport: i0, type: NgtCommonDirective, decorators: [{
1623
+ type: Directive
1624
+ }], ctorParameters: function () { return []; } });
1035
1625
 
1036
- const rootStateMap = new Map();
1037
- const { invalidate, advance } = createLoop(rootStateMap);
1038
- const shallowLoose = { objects: 'shallow', strict: false };
1039
- class NgtStore extends NgtSignalStore {
1626
+ class NgtArgs extends NgtCommonDirective {
1040
1627
  constructor() {
1041
1628
  super(...arguments);
1042
- this.#parentStore = inject(NgtStore, { optional: true, skipSelf: true });
1043
- this.#window = inject(DOCUMENT).defaultView;
1044
- this.#injector = inject(Injector);
1045
- this.isInit = false;
1046
- }
1047
- #parentStore;
1048
- #window;
1049
- #injector;
1050
- init() {
1051
- if (!this.isInit) {
1052
- const position = new THREE.Vector3();
1053
- const defaultTarget = new THREE.Vector3();
1054
- const tempTarget = new THREE.Vector3();
1055
- const getCurrentViewport = (camera = this.get('camera'), target = defaultTarget, size = this.get('size')) => {
1056
- const { width, height, top, left } = size;
1057
- const aspect = width / height;
1058
- if (target instanceof THREE.Vector3)
1059
- tempTarget.copy(target);
1060
- else
1061
- tempTarget.set(...target);
1062
- const distance = camera.getWorldPosition(position).distanceTo(tempTarget);
1063
- if (is.orthographicCamera(camera)) {
1064
- return {
1065
- width: width / camera.zoom,
1066
- height: height / camera.zoom,
1067
- top,
1068
- left,
1069
- factor: 1,
1070
- distance,
1071
- aspect,
1072
- };
1073
- }
1074
- const fov = (camera.fov * Math.PI) / 180; // convert vertical fov to radians
1075
- const h = 2 * Math.tan(fov / 2) * distance; // visible height
1076
- const w = h * aspect;
1077
- return { width: w, height: h, top, left, factor: width / w, distance, aspect };
1078
- };
1079
- let performanceTimeout;
1080
- const setPerformanceCurrent = (current) => {
1081
- this.set((state) => ({ performance: { ...state.performance, current } }));
1082
- };
1083
- this.set({
1084
- get: this.get.bind(this),
1085
- set: this.set.bind(this),
1086
- ready: false,
1087
- events: { priority: 1, enabled: true, connected: false },
1088
- invalidate: (frames = 1) => invalidate(this, frames),
1089
- advance: (timestamp, runGlobalEffects) => advance(timestamp, runGlobalEffects, this),
1090
- legacy: false,
1091
- linear: false,
1092
- flat: false,
1093
- controls: null,
1094
- clock: new THREE.Clock(),
1095
- pointer: new THREE.Vector2(),
1096
- frameloop: 'always',
1097
- performance: {
1098
- current: 1,
1099
- min: 0.5,
1100
- max: 1,
1101
- debounce: 200,
1102
- regress: () => {
1103
- const state = this.get();
1104
- // clear timeout
1105
- if (performanceTimeout)
1106
- clearTimeout(performanceTimeout);
1107
- // set lower bound
1108
- if (state.performance.current !== state.performance.min)
1109
- setPerformanceCurrent(state.performance.min);
1110
- // go back to upper bound
1111
- performanceTimeout = setTimeout(() => {
1112
- setPerformanceCurrent(this.get('performance', 'max') || 1);
1113
- }, state.performance.debounce);
1114
- },
1115
- },
1116
- size: { width: 0, height: 0, top: 0, left: 0 },
1117
- viewport: {
1118
- initialDpr: 0,
1119
- dpr: 0,
1120
- width: 0,
1121
- height: 0,
1122
- top: 0,
1123
- left: 0,
1124
- aspect: 0,
1125
- distance: 0,
1126
- factor: 0,
1127
- getCurrentViewport,
1128
- },
1129
- previousStore: this.#parentStore,
1130
- internal: {
1131
- active: false,
1132
- priority: 0,
1133
- frames: 0,
1134
- lastEvent: new ElementRef(null),
1135
- interaction: [],
1136
- hovered: new Map(),
1137
- subscribers: [],
1138
- initialClick: [0, 0],
1139
- initialHits: [],
1140
- capturedMap: new Map(),
1141
- subscribe: (callback, priority = 0, store = this) => {
1142
- const internal = this.get('internal');
1143
- // If this subscription was given a priority, it takes rendering into its own hands
1144
- // For that reason we switch off automatic rendering and increase the manual flag
1145
- // As long as this flag is positive there can be no internal rendering at all
1146
- // because there could be multiple render subscriptions
1147
- internal.priority = internal.priority + (priority > 0 ? 1 : 0);
1148
- internal.subscribers.push({ priority, store, callback });
1149
- // Register subscriber and sort layers from lowest to highest, meaning,
1150
- // highest priority renders last (on top of the other frames)
1151
- internal.subscribers.sort((a, b) => (a.priority || 0) - (b.priority || 0));
1152
- return () => {
1153
- const internal = this.get('internal');
1154
- if (internal?.subscribers) {
1155
- // Decrease manual flag if this subscription had a priority
1156
- internal.priority = internal.priority - (priority > 0 ? 1 : 0);
1157
- // Remove subscriber from list
1158
- internal.subscribers = internal.subscribers.filter((s) => s.callback !== callback);
1159
- }
1160
- };
1161
- },
1162
- },
1163
- setEvents: (events) => {
1164
- this.set((state) => ({ events: { ...state.events, ...events } }));
1165
- },
1166
- setSize: (width, height, top, left) => {
1167
- const camera = this.get('camera');
1168
- const size = { width, height, top: top || 0, left: left || 0 };
1169
- this.set((state) => ({
1170
- size,
1171
- viewport: { ...state.viewport, ...getCurrentViewport(camera, defaultTarget, size) },
1172
- }));
1173
- },
1174
- setDpr: (dpr) => {
1175
- const resolved = makeDpr(dpr, this.#window);
1176
- this.set((state) => ({
1177
- viewport: {
1178
- ...state.viewport,
1179
- dpr: resolved,
1180
- initialDpr: state.viewport.initialDpr || resolved,
1181
- },
1182
- }));
1183
- },
1184
- setFrameloop: (frameloop = 'always') => {
1185
- const clock = this.get('clock');
1186
- clock.stop();
1187
- clock.elapsedTime = 0;
1188
- if (frameloop !== 'never') {
1189
- clock.start();
1190
- clock.elapsedTime = 0;
1191
- }
1192
- this.set({ frameloop });
1193
- },
1194
- });
1195
- this.isInit = true;
1196
- this.#resize();
1197
- }
1198
- }
1199
- configure(inputs, canvasElement) {
1200
- const { gl: glOptions, size: sizeOptions, camera: cameraOptions, raycaster: raycasterOptions, scene: sceneOptions, events, orthographic, lookAt, shadows, linear, legacy, flat, dpr, frameloop, performance, } = inputs;
1201
- const state = this.get();
1202
- const stateToUpdate = {};
1203
- // setup renderer
1204
- let gl = state.gl;
1205
- if (!state.gl)
1206
- stateToUpdate.gl = gl = makeDefaultRenderer(glOptions, canvasElement);
1207
- // setup raycaster
1208
- let raycaster = state.raycaster;
1209
- if (!raycaster)
1210
- stateToUpdate.raycaster = raycaster = new THREE.Raycaster();
1211
- // set raycaster options
1212
- const { params, ...options } = raycasterOptions || {};
1213
- if (!is.equ(options, raycaster, shallowLoose))
1214
- applyProps(raycaster, { ...options });
1215
- if (!is.equ(params, raycaster.params, shallowLoose)) {
1216
- applyProps(raycaster, { params: { ...raycaster.params, ...(params || {}) } });
1217
- }
1218
- // create default camera
1219
- if (!state.camera) {
1220
- const isCamera = is.camera(cameraOptions);
1221
- let camera = isCamera ? cameraOptions : makeDefaultCamera(orthographic || false, state.size);
1222
- if (!isCamera) {
1223
- if (cameraOptions)
1224
- applyProps(camera, cameraOptions);
1225
- // set position.z
1226
- if (!cameraOptions?.position)
1227
- camera.position.z = 5;
1228
- // always look at center or passed-in lookAt by default
1229
- if (!cameraOptions?.rotation && !cameraOptions?.quaternion) {
1230
- if (Array.isArray(lookAt))
1231
- camera.lookAt(lookAt[0], lookAt[1], lookAt[2]);
1232
- else if (lookAt instanceof THREE.Vector3)
1233
- camera.lookAt(lookAt);
1234
- else
1235
- camera.lookAt(0, 0, 0);
1236
- }
1237
- // update projection matrix after applyprops
1238
- camera.updateProjectionMatrix?.();
1239
- }
1240
- if (!is.instance(camera))
1241
- camera = prepare(camera, { store: this });
1242
- stateToUpdate.camera = camera;
1243
- }
1244
- // Set up scene (one time only!)
1245
- if (!state.scene) {
1246
- let scene;
1247
- if (sceneOptions instanceof THREE.Scene) {
1248
- scene = prepare(sceneOptions, { store: this });
1249
- }
1250
- else {
1251
- scene = prepare(new THREE.Scene(), { store: this });
1252
- if (sceneOptions)
1253
- applyProps(scene, sceneOptions);
1254
- }
1255
- stateToUpdate.scene = scene;
1256
- }
1257
- // Set up XR (one time only!)
1258
- if (!state.xr) {
1259
- // Handle frame behavior in WebXR
1260
- const handleXRFrame = (timestamp, frame) => {
1261
- const state = this.get();
1262
- if (state.frameloop === 'never')
1263
- return;
1264
- advance(timestamp, true, this, frame);
1265
- };
1266
- // Toggle render switching on session
1267
- const handleSessionChange = () => {
1268
- const state = this.get();
1269
- state.gl.xr.enabled = state.gl.xr.isPresenting;
1270
- state.gl.xr.setAnimationLoop(state.gl.xr.isPresenting ? handleXRFrame : null);
1271
- if (!state.gl.xr.isPresenting)
1272
- state.invalidate();
1273
- };
1274
- // WebXR session manager
1275
- const xr = {
1276
- connect: () => {
1277
- gl.xr.addEventListener('sessionstart', handleSessionChange);
1278
- gl.xr.addEventListener('sessionend', handleSessionChange);
1279
- },
1280
- disconnect: () => {
1281
- gl.xr.removeEventListener('sessionstart', handleSessionChange);
1282
- gl.xr.removeEventListener('sessionend', handleSessionChange);
1283
- },
1284
- };
1285
- // Subscribe to WebXR session events
1286
- if (gl.xr)
1287
- xr.connect();
1288
- stateToUpdate.xr = xr;
1289
- }
1290
- // Set shadowmap
1291
- if (gl.shadowMap) {
1292
- const oldEnabled = gl.shadowMap.enabled;
1293
- const oldType = gl.shadowMap.type;
1294
- gl.shadowMap.enabled = !!shadows;
1295
- if (typeof shadows === 'boolean') {
1296
- gl.shadowMap.type = THREE.PCFSoftShadowMap;
1297
- }
1298
- else if (typeof shadows === 'string') {
1299
- const types = {
1300
- basic: THREE.BasicShadowMap,
1301
- percentage: THREE.PCFShadowMap,
1302
- soft: THREE.PCFSoftShadowMap,
1303
- variance: THREE.VSMShadowMap,
1304
- };
1305
- gl.shadowMap.type = types[shadows] ?? THREE.PCFSoftShadowMap;
1306
- }
1307
- else if (is.obj(shadows)) {
1308
- Object.assign(gl.shadowMap, shadows);
1309
- }
1310
- if (oldEnabled !== gl.shadowMap.enabled || oldType !== gl.shadowMap.type)
1311
- checkNeedsUpdate(gl.shadowMap);
1312
- }
1313
- // Safely set color management if available.
1314
- // Avoid accessing THREE.ColorManagement to play nice with older versions
1315
- if (THREE.ColorManagement) {
1316
- const ColorManagement = THREE.ColorManagement;
1317
- if ('enabled' in ColorManagement)
1318
- ColorManagement['enabled'] = !legacy ?? false;
1319
- else if ('legacyMode' in ColorManagement)
1320
- ColorManagement['legacyMode'] = legacy ?? true;
1321
- }
1322
- // set color space and tonemapping preferences
1323
- const LinearEncoding = 3000;
1324
- const sRGBEncoding = 3001;
1325
- applyProps(gl, {
1326
- outputEncoding: linear ? LinearEncoding : sRGBEncoding,
1327
- toneMapping: flat ? THREE.NoToneMapping : THREE.ACESFilmicToneMapping,
1328
- });
1329
- // Update color management state
1330
- if (state.legacy !== legacy)
1331
- stateToUpdate.legacy = legacy;
1332
- if (state.linear !== linear)
1333
- stateToUpdate.linear = linear;
1334
- if (state.flat !== flat)
1335
- stateToUpdate.flat = flat;
1336
- // Set gl props
1337
- gl.setClearAlpha(0);
1338
- gl.setPixelRatio(makeDpr(state.viewport.dpr));
1339
- gl.setSize(state.size.width, state.size.height);
1340
- if (is.obj(glOptions) &&
1341
- !(typeof glOptions === 'function') &&
1342
- !is.renderer(glOptions) &&
1343
- !is.equ(glOptions, gl, shallowLoose)) {
1344
- applyProps(gl, glOptions);
1345
- }
1346
- // Store events internally
1347
- if (events && !state.events.handlers)
1348
- stateToUpdate.events = events(this);
1349
- // Check performance
1350
- if (performance && !is.equ(performance, state.performance, shallowLoose)) {
1351
- stateToUpdate.performance = { ...state.performance, ...performance };
1352
- }
1353
- this.set(stateToUpdate);
1354
- // Check pixelratio
1355
- if (dpr && state.viewport.dpr !== makeDpr(dpr))
1356
- state.setDpr(dpr);
1357
- // Check size, allow it to take on container bounds initially
1358
- const size = computeInitialSize(canvasElement, sizeOptions);
1359
- if (!is.equ(size, state.size, shallowLoose))
1360
- state.setSize(size.width, size.height, size.top, size.left);
1361
- // Check frameloop
1362
- if (state.frameloop !== frameloop)
1363
- state.setFrameloop(frameloop);
1364
- if (!this.get('ready'))
1365
- this.set({ ready: true });
1366
- this.#invalidate();
1367
- }
1368
- destroy(canvas) {
1369
- this.set((state) => ({ internal: { ...state.internal, active: false } }));
1370
- setTimeout(() => {
1371
- const { gl, xr, events } = this.get();
1372
- if (gl) {
1373
- if (events.disconnect) {
1374
- events.disconnect();
1375
- }
1376
- gl.renderLists.dispose();
1377
- gl.forceContextLoss();
1378
- if (gl.xr && gl.xr.enabled) {
1379
- gl.xr.setAnimationLoop(null);
1380
- xr.disconnect();
1381
- }
1382
- dispose(this.get());
1383
- rootStateMap.delete(canvas);
1384
- }
1385
- }, 500);
1629
+ this.injectedArgs = [];
1386
1630
  }
1387
- #resize() {
1388
- const state = this.get();
1389
- let oldSize = state.size;
1390
- let oldDpr = state.viewport.dpr;
1391
- let oldCamera = state.camera;
1392
- const camera = this.select('camera');
1393
- const size = this.select('size');
1394
- const viewport = this.select('viewport');
1395
- const triggers = computed(() => ({ camera: camera(), size: size(), viewport: viewport(), gl: this.get('gl') }));
1396
- effect(() => {
1397
- const { camera, size, viewport, gl } = triggers();
1398
- // resize camera and renderer on changes to size and dpr
1399
- if (size !== oldSize || viewport.dpr !== oldDpr) {
1400
- oldSize = size;
1401
- oldDpr = viewport.dpr;
1402
- // update camera
1403
- updateCamera(camera, size);
1404
- gl.setPixelRatio(viewport.dpr);
1405
- gl.setSize(size.width, size.height);
1406
- }
1407
- // update viewport when camera changes
1408
- if (camera !== oldCamera) {
1409
- oldCamera = camera;
1410
- updateCamera(camera, size);
1411
- this.set((state) => ({
1412
- viewport: { ...state.viewport, ...state.viewport.getCurrentViewport(camera) },
1413
- }));
1414
- }
1415
- }, { injector: this.#injector, allowSignalWrites: true });
1631
+ set args(args) {
1632
+ if (args == null || !Array.isArray(args) || (args.length === 1 && args[0] === null))
1633
+ return;
1634
+ this.injected = false;
1635
+ this.injectedArgs = args;
1636
+ this.createView();
1637
+ }
1638
+ get args() {
1639
+ if (this.validate()) {
1640
+ this.injected = true;
1641
+ return this.injectedArgs;
1642
+ }
1643
+ return null;
1416
1644
  }
1417
- #invalidate() {
1418
- effect(() => void this.state().invalidate(), { injector: this.#injector });
1645
+ validate() {
1646
+ return !this.injected && !!this.injectedArgs.length;
1419
1647
  }
1420
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.0.3", ngImport: i0, type: NgtStore, deps: null, target: i0.ɵɵFactoryTarget.Injectable }); }
1421
- static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "16.0.3", ngImport: i0, type: NgtStore }); }
1648
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.1.7", ngImport: i0, type: NgtArgs, deps: null, target: i0.ɵɵFactoryTarget.Directive }); }
1649
+ static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "16.1.7", type: NgtArgs, isStandalone: true, selector: "ng-template[args]", inputs: { args: "args" }, usesInheritance: true, ngImport: i0 }); }
1422
1650
  }
1423
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.0.3", ngImport: i0, type: NgtStore, decorators: [{
1424
- type: Injectable
1425
- }] });
1426
- function computeInitialSize(canvas, defaultSize) {
1427
- if (defaultSize)
1428
- return defaultSize;
1429
- if (canvas instanceof HTMLCanvasElement && canvas.parentElement) {
1430
- return canvas.parentElement.getBoundingClientRect();
1651
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.1.7", ngImport: i0, type: NgtArgs, decorators: [{
1652
+ type: Directive,
1653
+ args: [{ selector: 'ng-template[args]', standalone: true }]
1654
+ }], propDecorators: { args: [{
1655
+ type: Input
1656
+ }] } });
1657
+
1658
+ class NgtParent extends NgtCommonDirective {
1659
+ constructor() {
1660
+ super(...arguments);
1661
+ this.injectedParent = null;
1431
1662
  }
1432
- return { width: 0, height: 0, top: 0, left: 0 };
1433
- }
1434
- // Disposes an object and all its properties
1435
- function dispose(obj) {
1436
- if (obj.dispose && !is.scene(obj))
1437
- obj.dispose();
1438
- for (const p in obj) {
1439
- p.dispose?.();
1440
- delete obj[p];
1663
+ set parent(parent) {
1664
+ if (!parent)
1665
+ return;
1666
+ this.injected = false;
1667
+ this.injectedParent = parent;
1668
+ this.createView();
1669
+ }
1670
+ get parent() {
1671
+ if (this.validate()) {
1672
+ this.injected = true;
1673
+ return this.injectedParent;
1674
+ }
1675
+ return null;
1441
1676
  }
1677
+ validate() {
1678
+ return !this.injected && !!this.injectedParent;
1679
+ }
1680
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.1.7", ngImport: i0, type: NgtParent, deps: null, target: i0.ɵɵFactoryTarget.Directive }); }
1681
+ static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "16.1.7", type: NgtParent, isStandalone: true, selector: "ng-template[parent]", inputs: { parent: "parent" }, usesInheritance: true, ngImport: i0 }); }
1442
1682
  }
1443
-
1444
- const NGT_COMPOUND_PREFIXES = new InjectionToken('NgtCompoundPrefixes');
1445
-
1446
- const catalogue = {};
1447
- function extend(objects) {
1448
- Object.assign(catalogue, objects);
1449
- }
1450
- const NGT_CATALOGUE = new InjectionToken('THREE Constructors Catalogue', { factory: () => catalogue });
1683
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.1.7", ngImport: i0, type: NgtParent, decorators: [{
1684
+ type: Directive,
1685
+ args: [{ selector: 'ng-template[parent]', standalone: true }]
1686
+ }], propDecorators: { parent: [{
1687
+ type: Input
1688
+ }] } });
1451
1689
 
1452
1690
  function attach(object, value, paths = []) {
1453
1691
  const [base, ...remaining] = paths;
@@ -1479,25 +1717,6 @@ function createAttachFunction(cb) {
1479
1717
  return (parent, child, store) => cb({ parent, child, store });
1480
1718
  }
1481
1719
 
1482
- const ROUTED_SCENE = '__ngt_renderer_is_routed_scene__';
1483
- const SPECIAL_INTERNAL_ADD_COMMENT = '__ngt_renderer_add_comment__';
1484
- const SPECIAL_DOM_TAG = {
1485
- NGT_PORTAL: 'ngt-portal',
1486
- NGT_PRIMITIVE: 'ngt-primitive',
1487
- NGT_VALUE: 'ngt-value',
1488
- };
1489
- const SPECIAL_PROPERTIES = {
1490
- COMPOUND: 'ngtCompound',
1491
- RENDER_PRIORITY: 'priority',
1492
- ATTACH: 'attach',
1493
- VALUE: 'rawValue',
1494
- REF: 'ref',
1495
- };
1496
- const SPECIAL_EVENTS = {
1497
- BEFORE_RENDER: 'beforeRender',
1498
- AFTER_UPDATE: 'afterUpdate',
1499
- AFTER_ATTACH: 'afterAttach',
1500
- };
1501
1720
  function attachThreeChild(parent, child) {
1502
1721
  const pLS = getLocalState(parent);
1503
1722
  const cLS = getLocalState(child);
@@ -1508,7 +1727,7 @@ function attachThreeChild(parent, child) {
1508
1727
  let added = false;
1509
1728
  // assign store on child if not already exist
1510
1729
  // or child store is the parent of parent store
1511
- if (!cLS.store || cLS.store === pLS.store.get('previousStore')) {
1730
+ if (!cLS.store || cLS.store === pLS.store.get('previousRoot')) {
1512
1731
  cLS.store = pLS.store;
1513
1732
  }
1514
1733
  if (cLS.attach) {
@@ -1534,8 +1753,8 @@ function attachThreeChild(parent, child) {
1534
1753
  }
1535
1754
  // attach
1536
1755
  if (cLS.isRaw) {
1537
- if (cLS.parent) {
1538
- queueMicrotask(() => {
1756
+ if (cLS.parent && cLS.parent() !== parent) {
1757
+ untracked(() => {
1539
1758
  cLS.parent.set(parent);
1540
1759
  });
1541
1760
  }
@@ -1556,8 +1775,8 @@ function attachThreeChild(parent, child) {
1556
1775
  added = true;
1557
1776
  }
1558
1777
  pLS.add(child, added ? 'objects' : 'nonObjects');
1559
- if (cLS.parent) {
1560
- queueMicrotask(() => {
1778
+ if (cLS.parent && cLS.parent() !== parent) {
1779
+ untracked(() => {
1561
1780
  cLS.parent.set(parent);
1562
1781
  });
1563
1782
  }
@@ -1570,7 +1789,7 @@ function removeThreeChild(parent, child, dispose) {
1570
1789
  const pLS = getLocalState(parent);
1571
1790
  const cLS = getLocalState(child);
1572
1791
  // clear parent ref
1573
- queueMicrotask(() => {
1792
+ untracked(() => {
1574
1793
  cLS.parent?.set(null);
1575
1794
  });
1576
1795
  // remove child from parent
@@ -1600,7 +1819,15 @@ function removeThreeRecursive(array, parent, dispose) {
1600
1819
  if (array)
1601
1820
  [...array].forEach((child) => removeThreeChild(parent, child, dispose));
1602
1821
  }
1603
- function processThreeEvent(instance, priority, eventName, callback, zone, cdr, targetCdr) {
1822
+ function kebabToPascal(str) {
1823
+ // split the string at each hyphen
1824
+ const parts = str.split('-');
1825
+ // map over the parts, capitalizing the first letter of each part
1826
+ const pascalParts = parts.map((part) => part.charAt(0).toUpperCase() + part.slice(1));
1827
+ // join the parts together to create the final PascalCase string
1828
+ return pascalParts.join('');
1829
+ }
1830
+ function processThreeEvent(instance, priority, eventName, callback, zone, rootCdr, targetCdr) {
1604
1831
  const lS = getLocalState(instance);
1605
1832
  if (eventName === SPECIAL_EVENTS.BEFORE_RENDER) {
1606
1833
  return lS.store
@@ -1624,8 +1851,7 @@ function processThreeEvent(instance, priority, eventName, callback, zone, cdr, t
1624
1851
  previousHandler(event);
1625
1852
  zone.run(() => {
1626
1853
  callback(event);
1627
- safeDetectChanges(targetCdr);
1628
- safeDetectChanges(cdr);
1854
+ safeDetectChanges(targetCdr, rootCdr);
1629
1855
  });
1630
1856
  };
1631
1857
  Object.assign(lS.handlers, { [eventName]: updatedCallback });
@@ -1639,6 +1865,7 @@ function processThreeEvent(instance, priority, eventName, callback, zone, cdr, t
1639
1865
  return () => {
1640
1866
  const localState = getLocalState(instance);
1641
1867
  if (localState && localState.eventCount) {
1868
+ localState.eventCount -= 1;
1642
1869
  const index = localState.store
1643
1870
  .get('internal', 'interaction')
1644
1871
  .findIndex((obj) => obj.uuid === instance.uuid);
@@ -1647,117 +1874,12 @@ function processThreeEvent(instance, priority, eventName, callback, zone, cdr, t
1647
1874
  }
1648
1875
  };
1649
1876
  }
1650
- function eventToHandler(callback) {
1651
- return (event) => {
1652
- callback(event);
1653
- };
1654
- }
1655
- function kebabToPascal(str) {
1656
- // split the string at each hyphen
1657
- const parts = str.split('-');
1658
- // map over the parts, capitalizing the first letter of each part
1659
- const pascalParts = parts.map((part) => part.charAt(0).toUpperCase() + part.slice(1));
1660
- // join the parts together to create the final PascalCase string
1661
- return pascalParts.join('');
1662
- }
1663
-
1664
- class NgtCommonDirective {
1665
- #vcr;
1666
- #zone;
1667
- #template;
1668
- #view;
1669
- constructor() {
1670
- this.#vcr = inject(ViewContainerRef);
1671
- this.#zone = inject(NgZone);
1672
- this.#template = inject(TemplateRef);
1673
- this.injected = false;
1674
- this.shouldCreateView = true;
1675
- const commentNode = this.#vcr.element.nativeElement;
1676
- if (commentNode[SPECIAL_INTERNAL_ADD_COMMENT]) {
1677
- commentNode[SPECIAL_INTERNAL_ADD_COMMENT]();
1678
- delete commentNode[SPECIAL_INTERNAL_ADD_COMMENT];
1679
- }
1680
- }
1681
- createView() {
1682
- if (this.shouldCreateView) {
1683
- if (this.#view && !this.#view.destroyed) {
1684
- this.#view.destroy();
1685
- }
1686
- this.#zone.runOutsideAngular(() => {
1687
- this.#view = this.#vcr.createEmbeddedView(this.#template);
1688
- safeDetectChanges(this.#view);
1689
- });
1690
- }
1691
- }
1692
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.0.3", ngImport: i0, type: NgtCommonDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
1693
- static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "16.0.3", type: NgtCommonDirective, ngImport: i0 }); }
1694
- }
1695
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.0.3", ngImport: i0, type: NgtCommonDirective, decorators: [{
1696
- type: Directive
1697
- }], ctorParameters: function () { return []; } });
1698
-
1699
- class NgtArgs extends NgtCommonDirective {
1700
- #injectedArgs = [];
1701
- set args(args) {
1702
- if (args == null || !Array.isArray(args) || (args.length === 1 && args[0] === null))
1703
- return;
1704
- this.injected = false;
1705
- this.#injectedArgs = args;
1706
- this.createView();
1707
- }
1708
- get args() {
1709
- if (this.validate()) {
1710
- this.injected = true;
1711
- return this.#injectedArgs;
1712
- }
1713
- return null;
1714
- }
1715
- validate() {
1716
- return !this.injected && !!this.#injectedArgs.length;
1717
- }
1718
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.0.3", ngImport: i0, type: NgtArgs, deps: null, target: i0.ɵɵFactoryTarget.Directive }); }
1719
- static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "16.0.3", type: NgtArgs, isStandalone: true, selector: "[args]", inputs: { args: "args" }, usesInheritance: true, ngImport: i0 }); }
1720
- }
1721
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.0.3", ngImport: i0, type: NgtArgs, decorators: [{
1722
- type: Directive,
1723
- args: [{ selector: '[args]', standalone: true }]
1724
- }], propDecorators: { args: [{
1725
- type: Input
1726
- }] } });
1727
-
1728
- class NgtParent extends NgtCommonDirective {
1729
- #injectedParent = null;
1730
- set parent(parent) {
1731
- if (!parent)
1732
- return;
1733
- this.injected = false;
1734
- this.#injectedParent = parent;
1735
- this.createView();
1736
- }
1737
- get parent() {
1738
- if (this.validate()) {
1739
- this.injected = true;
1740
- return this.#injectedParent;
1741
- }
1742
- return null;
1743
- }
1744
- validate() {
1745
- return !this.injected && !!this.#injectedParent;
1746
- }
1747
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.0.3", ngImport: i0, type: NgtParent, deps: null, target: i0.ɵɵFactoryTarget.Directive }); }
1748
- static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "16.0.3", type: NgtParent, isStandalone: true, selector: "[parent]", inputs: { parent: "parent" }, usesInheritance: true, ngImport: i0 }); }
1749
- }
1750
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.0.3", ngImport: i0, type: NgtParent, decorators: [{
1751
- type: Directive,
1752
- args: [{ selector: '[parent]', standalone: true }]
1753
- }], propDecorators: { parent: [{
1754
- type: Input
1755
- }] } });
1756
1877
 
1878
+ const NGT_COMPOUND_PREFIXES = new InjectionToken('NgtCompoundPrefixes');
1757
1879
  class NgtRendererStore {
1758
- #comments = [];
1759
1880
  constructor(root) {
1760
1881
  this.root = root;
1882
+ this.comments = [];
1761
1883
  }
1762
1884
  createNode(type, node) {
1763
1885
  const state = [
@@ -1778,10 +1900,10 @@ class NgtRendererStore {
1778
1900
  undefined,
1779
1901
  ];
1780
1902
  const rendererNode = Object.assign(node, { __ngt_renderer__: state });
1781
- // assign ownerDocument to node so we can use HostListener in Component
1903
+ // NOTE: assign ownerDocument to node so we can use HostListener in Component
1782
1904
  if (!rendererNode['ownerDocument'])
1783
1905
  rendererNode['ownerDocument'] = this.root.document;
1784
- // assign injectorFactory on non-three type since
1906
+ // NOTE: assign injectorFactory on non-three type since
1785
1907
  // rendererNode is an instance of DOM Node
1786
1908
  if (state[0 /* NgtRendererClassId.type */] !== 'three') {
1787
1909
  state[14 /* NgtRendererClassId.injectorFactory */] = () => getDebugNode(rendererNode).injector;
@@ -1795,7 +1917,7 @@ class NgtRendererStore {
1795
1917
  this.portals.push(node);
1796
1918
  }
1797
1919
  else {
1798
- this.#comments.push(rendererNode);
1920
+ this.comments.push(rendererNode);
1799
1921
  }
1800
1922
  };
1801
1923
  return rendererNode;
@@ -1819,12 +1941,21 @@ class NgtRendererStore {
1819
1941
  }
1820
1942
  }
1821
1943
  removeChild(node, child) {
1822
- const index = node.__ngt_renderer__[3 /* NgtRendererClassId.children */].findIndex((c) => child === c);
1944
+ const index = node.__ngt_renderer__?.[3 /* NgtRendererClassId.children */].findIndex((c) => child === c);
1823
1945
  if (index >= 0) {
1824
1946
  node.__ngt_renderer__[3 /* NgtRendererClassId.children */].splice(index, 1);
1825
1947
  }
1826
1948
  }
1827
1949
  setCompound(compound, instance) {
1950
+ const instanceRS = instance.__ngt_renderer__;
1951
+ if (instanceRS && instanceRS[1 /* NgtRendererClassId.parent */]) {
1952
+ const parentRS = instanceRS[1 /* NgtRendererClassId.parent */].__ngt_renderer__;
1953
+ // NOTE: if instance is already compounded by its parent. skip
1954
+ if (parentRS[0 /* NgtRendererClassId.type */] === 'compound' &&
1955
+ parentRS[7 /* NgtRendererClassId.compounded */] === instance) {
1956
+ return;
1957
+ }
1958
+ }
1828
1959
  const rS = compound.__ngt_renderer__;
1829
1960
  rS[7 /* NgtRendererClassId.compounded */] = instance;
1830
1961
  const attributes = Object.keys(rS[9 /* NgtRendererClassId.attributes */]);
@@ -1856,7 +1987,7 @@ class NgtRendererStore {
1856
1987
  const injector = injectorFactory?.();
1857
1988
  if (!injector)
1858
1989
  return;
1859
- const portalStore = injector.get(NgtStore, null);
1990
+ const portalStore = injector.get(NGT_STORE, null);
1860
1991
  if (!portalStore)
1861
1992
  return;
1862
1993
  const portalContainer = portalStore.get('scene');
@@ -1869,8 +2000,8 @@ class NgtRendererStore {
1869
2000
  if (rS[4 /* NgtRendererClassId.destroyed */])
1870
2001
  return;
1871
2002
  if (name === SPECIAL_PROPERTIES.RENDER_PRIORITY) {
1872
- // priority needs to be set as an attribute string so that they can be set as early as possible
1873
- // we convert that string to a number. if it's invalid, 0
2003
+ // NOTE: priority needs to be set as an attribute string so that they can be set as early as possible
2004
+ // we convert that string to a number. if it's invalid, default 0
1874
2005
  let priority = Number(value);
1875
2006
  if (isNaN(priority)) {
1876
2007
  priority = 0;
@@ -1879,19 +2010,19 @@ class NgtRendererStore {
1879
2010
  getLocalState(node).priority = priority;
1880
2011
  }
1881
2012
  if (name === SPECIAL_PROPERTIES.COMPOUND) {
1882
- // we set the compound property on instance node now so we know that this instance is being compounded
2013
+ // NOTE: we set the compound property on instance node now so we know that this instance is being compounded
1883
2014
  rS[5 /* NgtRendererClassId.compound */] = [value === '' || value === 'first', {}];
1884
2015
  return;
1885
2016
  }
1886
2017
  if (name === SPECIAL_PROPERTIES.ATTACH) {
1887
- // handle attach as tring
2018
+ // NOTE: handle attach as tring
1888
2019
  const paths = value.split('.');
1889
2020
  if (paths.length)
1890
2021
  getLocalState(node).attach = paths;
1891
2022
  return;
1892
2023
  }
1893
2024
  if (name === SPECIAL_PROPERTIES.VALUE) {
1894
- // coercion
2025
+ // NOTE: coercion
1895
2026
  let maybeCoerced = value;
1896
2027
  if (maybeCoerced === '' || maybeCoerced === 'true' || maybeCoerced === 'false') {
1897
2028
  maybeCoerced = maybeCoerced === 'true' || maybeCoerced === '';
@@ -1903,7 +2034,7 @@ class NgtRendererStore {
1903
2034
  return;
1904
2035
  }
1905
2036
  applyProps(node, { [name]: value });
1906
- this.#updateNativeProps(node, name, value);
2037
+ this.updateNativeProps(node, name, value);
1907
2038
  }
1908
2039
  applyProperty(node, name, value) {
1909
2040
  const rS = node.__ngt_renderer__;
@@ -1937,7 +2068,7 @@ class NgtRendererStore {
1937
2068
  value = compound[1 /* NgtCompoundClassId.props */][name];
1938
2069
  }
1939
2070
  applyProps(node, { [name]: value });
1940
- this.#updateNativeProps(node, name, value);
2071
+ this.updateNativeProps(node, name, value);
1941
2072
  }
1942
2073
  isCompound(name) {
1943
2074
  return this.root.compoundPrefixes.some((prefix) => name.startsWith(prefix));
@@ -1991,14 +2122,14 @@ class NgtRendererStore {
1991
2122
  return null;
1992
2123
  }
1993
2124
  getCreationState() {
1994
- const injectedArgs = this.#firstNonInjectedDirective(NgtArgs)?.args || [];
1995
- const injectedParent = this.#firstNonInjectedDirective(NgtParent)?.parent || null;
1996
- const store = this.#tryGetPortalStore();
2125
+ const injectedArgs = this.firstNonInjectedDirective(NgtArgs)?.args || [];
2126
+ const injectedParent = this.firstNonInjectedDirective(NgtParent)?.parent || null;
2127
+ const store = this.tryGetPortalStore();
1997
2128
  return { injectedArgs, injectedParent, store };
1998
2129
  }
1999
2130
  destroy(node, parent) {
2000
2131
  const rS = node.__ngt_renderer__;
2001
- if (rS[4 /* NgtRendererClassId.destroyed */])
2132
+ if (!rS || rS[4 /* NgtRendererClassId.destroyed */])
2002
2133
  return;
2003
2134
  if (rS[0 /* NgtRendererClassId.type */] === 'three') {
2004
2135
  rS[5 /* NgtRendererClassId.compound */] = undefined;
@@ -2030,9 +2161,9 @@ class NgtRendererStore {
2030
2161
  if (rS[0 /* NgtRendererClassId.type */] === 'comment') {
2031
2162
  rS[14 /* NgtRendererClassId.injectorFactory */] = null;
2032
2163
  delete node[SPECIAL_INTERNAL_ADD_COMMENT];
2033
- const index = this.#comments.findIndex((comment) => comment === node);
2164
+ const index = this.comments.findIndex((comment) => comment === node);
2034
2165
  if (index > -1) {
2035
- this.#comments.splice(index, 1);
2166
+ this.comments.splice(index, 1);
2036
2167
  }
2037
2168
  }
2038
2169
  if (rS[0 /* NgtRendererClassId.type */] === 'portal') {
@@ -2052,13 +2183,17 @@ class NgtRendererStore {
2052
2183
  }
2053
2184
  if (rS[12 /* NgtRendererClassId.ref */]) {
2054
2185
  // nullify ref
2055
- rS[12 /* NgtRendererClassId.ref */].nativeElement = null;
2056
- rS[12 /* NgtRendererClassId.ref */] = undefined;
2186
+ // but we do it later so that it doesn't hinder render
2187
+ // TODO: will this cause memory leak?
2188
+ requestAnimationFrame(() => {
2189
+ rS[12 /* NgtRendererClassId.ref */].nativeElement = null;
2190
+ rS[12 /* NgtRendererClassId.ref */] = undefined;
2191
+ });
2057
2192
  }
2058
2193
  // nullify parent
2059
2194
  rS[1 /* NgtRendererClassId.parent */] = null;
2060
2195
  for (const renderChild of rS[3 /* NgtRendererClassId.children */] || []) {
2061
- if (renderChild.__ngt_renderer__[0 /* NgtRendererClassId.type */] === 'three' && parent) {
2196
+ if (renderChild.__ngt_renderer__?.[0 /* NgtRendererClassId.type */] === 'three' && parent) {
2062
2197
  removeThreeChild(parent, renderChild, true);
2063
2198
  }
2064
2199
  this.destroy(renderChild, parent);
@@ -2069,20 +2204,20 @@ class NgtRendererStore {
2069
2204
  this.removeChild(parent, node);
2070
2205
  }
2071
2206
  }
2072
- #updateNativeProps(node, name, value) {
2207
+ updateNativeProps(node, key, value) {
2073
2208
  const localState = getLocalState(node);
2074
2209
  if (!localState || !localState.nativeProps)
2075
2210
  return;
2076
- queueMicrotask(() => {
2077
- localState.nativeProps.set({ [name]: value });
2078
- });
2211
+ localState.nativeProps.set({ [key]: value });
2079
2212
  }
2080
- #firstNonInjectedDirective(dir) {
2213
+ firstNonInjectedDirective(dir) {
2081
2214
  let directive;
2082
- let i = this.#comments.length - 1;
2215
+ const destroyed = [];
2216
+ let i = this.comments.length - 1;
2083
2217
  while (i >= 0) {
2084
- const comment = this.#comments[i];
2218
+ const comment = this.comments[i];
2085
2219
  if (comment.__ngt_renderer__[4 /* NgtRendererClassId.destroyed */]) {
2220
+ destroyed.push(i);
2086
2221
  i--;
2087
2222
  continue;
2088
2223
  }
@@ -2098,16 +2233,21 @@ class NgtRendererStore {
2098
2233
  }
2099
2234
  i--;
2100
2235
  }
2236
+ destroyed.forEach((index) => {
2237
+ this.comments.splice(index, 1);
2238
+ });
2101
2239
  return directive;
2102
2240
  }
2103
- #tryGetPortalStore() {
2241
+ tryGetPortalStore() {
2104
2242
  let store;
2243
+ const destroyed = [];
2105
2244
  // we only care about the portal states because NgtStore only differs per Portal
2106
2245
  let i = this.portals.length - 1;
2107
2246
  while (i >= 0) {
2108
2247
  // loop through the portal state backwards to find the closest NgtStore
2109
2248
  const portal = this.portals[i];
2110
2249
  if (portal.__ngt_renderer__[4 /* NgtRendererClassId.destroyed */]) {
2250
+ destroyed.push(i);
2111
2251
  i--;
2112
2252
  continue;
2113
2253
  }
@@ -2116,55 +2256,62 @@ class NgtRendererStore {
2116
2256
  i--;
2117
2257
  continue;
2118
2258
  }
2119
- const instance = injector.get(NgtStore, null);
2120
- // only the instance with previousStore should pass
2121
- if (instance && instance.get('previousStore')) {
2259
+ const instance = injector.get(NGT_STORE, null);
2260
+ // only the instance with previousRoot should pass
2261
+ if (instance && instance.get('previousRoot')) {
2122
2262
  store = instance;
2123
2263
  break;
2124
2264
  }
2125
2265
  i--;
2126
2266
  }
2267
+ destroyed.forEach((index) => {
2268
+ this.portals.splice(index, 1);
2269
+ });
2127
2270
  return store || this.root.store;
2128
2271
  }
2129
2272
  }
2130
2273
 
2131
2274
  class NgtRendererFactory {
2132
- #delegateRendererFactory = inject(RendererFactory2, { skipSelf: true });
2133
- #catalogue = inject(NGT_CATALOGUE);
2134
- #zone = inject(NgZone);
2135
- #cdr = inject(ChangeDetectorRef);
2136
- #rendererMap = new Map();
2137
- #routedSet = new Set();
2138
- // all Renderer instances share the same Store
2139
- #rendererStore = new NgtRendererStore({
2140
- portals: [],
2141
- store: inject(NgtStore),
2142
- compoundPrefixes: inject(NGT_COMPOUND_PREFIXES),
2143
- document: inject(DOCUMENT),
2144
- });
2275
+ constructor() {
2276
+ this.delegateRendererFactory = inject(RendererFactory2, { skipSelf: true });
2277
+ this.zone = inject(NgZone);
2278
+ this.catalogue = injectNgtCatalogue();
2279
+ this.cdr = inject(ChangeDetectorRef);
2280
+ this.rendererMap = new Map();
2281
+ this.routedSet = new Set();
2282
+ // all Renderer instances share the same Store
2283
+ this.rendererStore = new NgtRendererStore({
2284
+ portals: [],
2285
+ store: injectNgtStore(),
2286
+ compoundPrefixes: inject(NGT_COMPOUND_PREFIXES),
2287
+ document: inject(DOCUMENT),
2288
+ });
2289
+ }
2145
2290
  createRenderer(hostElement, type) {
2146
- const delegateRenderer = this.#delegateRendererFactory.createRenderer(hostElement, type);
2291
+ const delegate = this.delegateRendererFactory.createRenderer(hostElement, type);
2147
2292
  if (!type)
2148
- return delegateRenderer;
2149
- // if ((type as NgtAnyRecord)['type']['isHtml']) {
2150
- // return delegateRenderer;
2151
- // }
2293
+ return delegate;
2294
+ // TODO: handle html in canvas
2295
+ if (type['type'][HTML]) {
2296
+ this.rendererMap.set(type.id, delegate);
2297
+ return delegate;
2298
+ }
2152
2299
  if (type['type'][ROUTED_SCENE]) {
2153
- this.#routedSet.add(type.id);
2300
+ this.routedSet.add(type.id);
2154
2301
  }
2155
- let renderer = this.#rendererMap.get(type.id);
2302
+ let renderer = this.rendererMap.get(type.id);
2156
2303
  if (!renderer) {
2157
- renderer = new NgtRenderer(delegateRenderer, this.#rendererStore, this.#catalogue, this.#zone, this.#cdr,
2304
+ renderer = new NgtRenderer(delegate, this.rendererStore, this.catalogue, this.zone, this.cdr,
2158
2305
  // setting root scene if there's no routed scene OR this component is the routed Scene
2159
- !hostElement && (this.#routedSet.size === 0 || this.#routedSet.has(type.id)));
2160
- this.#rendererMap.set(type.id, renderer);
2306
+ !hostElement && (this.routedSet.size === 0 || this.routedSet.has(type.id)));
2307
+ this.rendererMap.set(type.id, renderer);
2161
2308
  }
2162
2309
  return renderer;
2163
2310
  }
2164
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.0.3", ngImport: i0, type: NgtRendererFactory, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
2165
- static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "16.0.3", ngImport: i0, type: NgtRendererFactory }); }
2311
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.1.7", ngImport: i0, type: NgtRendererFactory, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
2312
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "16.1.7", ngImport: i0, type: NgtRendererFactory }); }
2166
2313
  }
2167
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.0.3", ngImport: i0, type: NgtRendererFactory, decorators: [{
2314
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.1.7", ngImport: i0, type: NgtRendererFactory, decorators: [{
2168
2315
  type: Injectable
2169
2316
  }] });
2170
2317
  /**
@@ -2265,16 +2412,19 @@ class NgtRenderer {
2265
2412
  return this.store.createNode('comment', this.delegate.createComment(value));
2266
2413
  }
2267
2414
  appendChild(parent, newChild) {
2268
- // TODO: just ignore text node for now
2269
- if (newChild instanceof Text)
2270
- return;
2271
- const cRS = newChild.__ngt_renderer__;
2272
2415
  const pRS = parent.__ngt_renderer__;
2273
- if (cRS[0 /* NgtRendererClassId.type */] === 'comment') {
2416
+ const cRS = newChild.__ngt_renderer__;
2417
+ if (pRS[0 /* NgtRendererClassId.type */] === 'dom' &&
2418
+ (newChild instanceof Text || cRS[0 /* NgtRendererClassId.type */] === 'dom')) {
2419
+ this.store.addChild(parent, newChild);
2420
+ this.delegate.appendChild(parent, newChild);
2421
+ return;
2422
+ }
2423
+ if (cRS?.[0 /* NgtRendererClassId.type */] === 'comment') {
2274
2424
  this.store.setParent(newChild, parent);
2275
2425
  return;
2276
2426
  }
2277
- if (cRS[2 /* NgtRendererClassId.injectedParent */]) {
2427
+ if (cRS?.[2 /* NgtRendererClassId.injectedParent */]) {
2278
2428
  if (is.ref(cRS[2 /* NgtRendererClassId.injectedParent */])) {
2279
2429
  const injector = cRS[14 /* NgtRendererClassId.injectorFactory */]().get(Injector, null);
2280
2430
  if (!injector) {
@@ -2300,7 +2450,7 @@ class NgtRenderer {
2300
2450
  this.store.setParent(newChild, parent);
2301
2451
  this.store.addChild(parent, newChild);
2302
2452
  // if new child is a portal
2303
- if (cRS[0 /* NgtRendererClassId.type */] === 'portal') {
2453
+ if (cRS?.[0 /* NgtRendererClassId.type */] === 'portal') {
2304
2454
  this.store.processPortalContainer(newChild);
2305
2455
  if (cRS[13 /* NgtRendererClassId.portalContainer */]) {
2306
2456
  this.appendChild(parent, cRS[13 /* NgtRendererClassId.portalContainer */]);
@@ -2316,7 +2466,7 @@ class NgtRenderer {
2316
2466
  return;
2317
2467
  }
2318
2468
  // if both are three instances, straightforward case
2319
- if (pRS[0 /* NgtRendererClassId.type */] === 'three' && cRS[0 /* NgtRendererClassId.type */] === 'three') {
2469
+ if (pRS[0 /* NgtRendererClassId.type */] === 'three' && cRS?.[0 /* NgtRendererClassId.type */] === 'three') {
2320
2470
  // if child already attached to a parent, skip
2321
2471
  if (getLocalState(newChild).parent && untracked(getLocalState(newChild).parent))
2322
2472
  return;
@@ -2333,7 +2483,7 @@ class NgtRenderer {
2333
2483
  }
2334
2484
  // if only the parent is the THREE instance
2335
2485
  if (pRS[0 /* NgtRendererClassId.type */] === 'three') {
2336
- for (const renderChild of cRS[3 /* NgtRendererClassId.children */]) {
2486
+ for (const renderChild of cRS?.[3 /* NgtRendererClassId.children */]) {
2337
2487
  this.appendChild(parent, renderChild);
2338
2488
  }
2339
2489
  }
@@ -2356,27 +2506,15 @@ class NgtRenderer {
2356
2506
  this.store.setCompound(parent, newChild);
2357
2507
  }
2358
2508
  }
2359
- const shouldFindGrandparentInstance =
2360
- // if child is three but haven't been attached to a parent yet
2361
- (cRS[0 /* NgtRendererClassId.type */] === 'three' && !untracked(getLocalState(newChild).parent)) ||
2362
- // or both parent and child are DOM elements
2363
- // or they are compound AND haven't had a THREE instance yet
2364
- ((pRS[0 /* NgtRendererClassId.type */] === 'dom' ||
2365
- (pRS[0 /* NgtRendererClassId.type */] === 'compound' && !pRS[7 /* NgtRendererClassId.compounded */])) &&
2366
- (cRS[0 /* NgtRendererClassId.type */] === 'dom' ||
2367
- (cRS[0 /* NgtRendererClassId.type */] === 'compound' && !cRS[7 /* NgtRendererClassId.compounded */])));
2368
- if (shouldFindGrandparentInstance) {
2509
+ if (this.shouldFindGrandparentInstance(pRS, cRS, newChild)) {
2369
2510
  // we'll try to get the grandparent instance here so that we can run appendChild with both instances
2370
2511
  const closestGrandparentInstance = this.store.getClosestParentWithInstance(parent);
2371
2512
  if (closestGrandparentInstance)
2372
2513
  this.appendChild(closestGrandparentInstance, newChild);
2514
+ return;
2373
2515
  }
2374
2516
  }
2375
- insertBefore(parent, newChild
2376
- // TODO: we might need these?
2377
- // refChild: NgtRendererNode
2378
- // isMove?: boolean | undefined
2379
- ) {
2517
+ insertBefore(parent, newChild) {
2380
2518
  if (parent == null || !parent.__ngt_renderer__ || parent === newChild)
2381
2519
  return;
2382
2520
  this.appendChild(parent, newChild);
@@ -2384,6 +2522,18 @@ class NgtRenderer {
2384
2522
  removeChild(parent, oldChild, isHostElement) {
2385
2523
  const pRS = parent.__ngt_renderer__;
2386
2524
  const cRS = oldChild.__ngt_renderer__;
2525
+ if ((!cRS || !pRS) &&
2526
+ parent instanceof Element &&
2527
+ (oldChild instanceof Element || oldChild instanceof Text || oldChild instanceof Comment)) {
2528
+ this.delegate.removeChild(parent, oldChild);
2529
+ this.store.destroy(oldChild, parent);
2530
+ return;
2531
+ }
2532
+ if (cRS[0 /* NgtRendererClassId.type */] === 'dom' && (!pRS || pRS[0 /* NgtRendererClassId.type */] === 'dom')) {
2533
+ this.delegate.removeChild(parent, oldChild);
2534
+ this.store.destroy(oldChild, parent);
2535
+ return;
2536
+ }
2387
2537
  if (pRS[0 /* NgtRendererClassId.type */] === 'three' && cRS[0 /* NgtRendererClassId.type */] === 'three') {
2388
2538
  removeThreeChild(parent, oldChild, true);
2389
2539
  this.store.destroy(oldChild, parent);
@@ -2420,8 +2570,11 @@ class NgtRenderer {
2420
2570
  this.setAttribute(rS[7 /* NgtRendererClassId.compounded */], name, value, namespace);
2421
2571
  return;
2422
2572
  }
2423
- if (rS[0 /* NgtRendererClassId.type */] === 'three')
2573
+ if (rS[0 /* NgtRendererClassId.type */] === 'three') {
2424
2574
  this.store.applyAttribute(el, name, value);
2575
+ return;
2576
+ }
2577
+ return this.delegate.setAttribute(el, name, value);
2425
2578
  }
2426
2579
  setProperty(el, name, value) {
2427
2580
  const rS = el.__ngt_renderer__;
@@ -2433,15 +2586,16 @@ class NgtRenderer {
2433
2586
  return;
2434
2587
  }
2435
2588
  if (rS[7 /* NgtRendererClassId.compounded */].__ngt_renderer__[5 /* NgtRendererClassId.compound */]) {
2436
- Object.assign(rS[7 /* NgtRendererClassId.compounded */].__ngt_renderer__[5 /* NgtRendererClassId.compound */], {
2437
- props: Object.assign(rS[7 /* NgtRendererClassId.compounded */].__ngt_renderer__[5 /* NgtRendererClassId.compound */], { [name]: value }),
2438
- });
2589
+ Object.assign(rS[7 /* NgtRendererClassId.compounded */].__ngt_renderer__[5 /* NgtRendererClassId.compound */][1 /* NgtCompoundClassId.props */], { [name]: value });
2439
2590
  }
2440
2591
  this.setProperty(rS[7 /* NgtRendererClassId.compounded */], name, value);
2441
2592
  return;
2442
2593
  }
2443
- if (rS[0 /* NgtRendererClassId.type */] === 'three')
2594
+ if (rS[0 /* NgtRendererClassId.type */] === 'three') {
2444
2595
  this.store.applyProperty(el, name, value);
2596
+ return;
2597
+ }
2598
+ return this.delegate.setProperty(el, name, value);
2445
2599
  }
2446
2600
  listen(target, eventName, callback) {
2447
2601
  const rS = target.__ngt_renderer__;
@@ -2479,171 +2633,165 @@ class NgtRenderer {
2479
2633
  }
2480
2634
  return () => { };
2481
2635
  }
2636
+ shouldFindGrandparentInstance(pRS, cRS, child) {
2637
+ const pType = pRS[0 /* NgtRendererClassId.type */];
2638
+ const cType = cRS[0 /* NgtRendererClassId.type */];
2639
+ const isParentCompounded = pRS[7 /* NgtRendererClassId.compounded */];
2640
+ const isChildCompounded = cRS[7 /* NgtRendererClassId.compounded */];
2641
+ // if child is three but haven't been attached to a parent yet
2642
+ const isDanglingThreeChild = cType === 'three' && !untracked(getLocalState(child).parent);
2643
+ // or both parent and child are DOM elements
2644
+ // or they are compound AND haven't had a THREE instance yet
2645
+ const isParentStillDOM = pType === 'dom' || (pType === 'compound' && !isParentCompounded);
2646
+ const isChildStillDOM = cType === 'dom' || (cType === 'compound' && !isChildCompounded);
2647
+ // and the child is a compounded compound
2648
+ const isCompoundChildCompounded = cType === 'compound' && !!isChildCompounded;
2649
+ return (isDanglingThreeChild ||
2650
+ (isParentStillDOM && isChildStillDOM) ||
2651
+ (isParentStillDOM && isCompoundChildCompounded));
2652
+ }
2482
2653
  get data() {
2483
2654
  return this.delegate.data;
2484
2655
  }
2485
2656
  }
2486
-
2487
- function provideNgtRenderer({ store, changeDetectorRef, compoundPrefixes = [] }) {
2657
+ function provideNgtRenderer(store, compoundPrefixes, cdr) {
2488
2658
  if (!compoundPrefixes.includes('ngts'))
2489
2659
  compoundPrefixes.push('ngts');
2490
2660
  if (!compoundPrefixes.includes('ngtp'))
2491
2661
  compoundPrefixes.push('ngtp');
2492
2662
  return makeEnvironmentProviders([
2493
2663
  { provide: RendererFactory2, useClass: NgtRendererFactory },
2494
- { provide: NgtStore, useValue: store },
2495
- { provide: ChangeDetectorRef, useValue: changeDetectorRef },
2496
2664
  { provide: NGT_COMPOUND_PREFIXES, useValue: compoundPrefixes },
2665
+ { provide: ChangeDetectorRef, useValue: cdr },
2666
+ provideNgtStore(store),
2497
2667
  provideZoneChangeDetection({ runCoalescing: true, eventCoalescing: true }),
2498
2668
  ]);
2499
2669
  }
2500
2670
 
2501
- class NgtCanvas extends NgtSignalStore {
2502
- #envInjector;
2503
- #injector;
2504
- #host;
2505
- #zone;
2506
- #cdr;
2507
- #store;
2508
- #isReady;
2671
+ class NgtCanvas {
2672
+ constructor() {
2673
+ this.store = injectNgtStore();
2674
+ this.initRoot = injectCanvasRootInitializer();
2675
+ this.host = inject(ElementRef);
2676
+ this.viewContainerRef = inject(ViewContainerRef);
2677
+ this.injector = inject(Injector);
2678
+ this.environmentInjector = inject(EnvironmentInjector);
2679
+ this.zone = inject(NgZone);
2680
+ this.destroyRef = inject(DestroyRef);
2681
+ this.cdr = inject(ChangeDetectorRef);
2682
+ this.inputs = signalStore({
2683
+ shadows: false,
2684
+ linear: false,
2685
+ flat: false,
2686
+ legacy: false,
2687
+ orthographic: false,
2688
+ frameloop: 'always',
2689
+ dpr: [1, 2],
2690
+ events: createPointerEvents,
2691
+ });
2692
+ this.sceneGraphInputs = {};
2693
+ this.compoundPrefixes = [];
2694
+ this.created = new EventEmitter();
2695
+ this.inputsEventSource = this.inputs.select('eventSource');
2696
+ this.hbPointerEvents = computed(() => (!!this.inputsEventSource() ? 'none' : 'auto'));
2697
+ }
2509
2698
  set linear(linear) {
2510
- this.set({ linear });
2699
+ this.inputs.set({ linear });
2511
2700
  }
2512
2701
  set legacy(legacy) {
2513
- this.set({ legacy });
2702
+ this.inputs.set({ legacy });
2514
2703
  }
2515
2704
  set flat(flat) {
2516
- this.set({ flat });
2705
+ this.inputs.set({ flat });
2517
2706
  }
2518
2707
  set orthographic(orthographic) {
2519
- this.set({ orthographic });
2708
+ this.inputs.set({ orthographic });
2520
2709
  }
2521
2710
  set frameloop(frameloop) {
2522
- this.set({ frameloop });
2711
+ this.inputs.set({ frameloop });
2523
2712
  }
2524
2713
  set dpr(dpr) {
2525
- this.set({ dpr });
2714
+ this.inputs.set({ dpr });
2526
2715
  }
2527
2716
  set raycaster(raycaster) {
2528
- this.set({ raycaster });
2717
+ this.inputs.set({ raycaster });
2529
2718
  }
2530
2719
  set shadows(shadows) {
2531
- this.set({ shadows });
2720
+ this.inputs.set({ shadows });
2532
2721
  }
2533
2722
  set camera(camera) {
2534
- this.set({ camera });
2723
+ this.inputs.set({ camera });
2535
2724
  }
2536
2725
  set scene(scene) {
2537
- this.set({ scene });
2726
+ this.inputs.set({ scene });
2538
2727
  }
2539
2728
  set gl(gl) {
2540
- this.set({ gl });
2729
+ this.inputs.set({ gl });
2541
2730
  }
2542
2731
  set eventSource(eventSource) {
2543
- this.set({ eventSource });
2732
+ this.inputs.set({ eventSource });
2544
2733
  }
2545
2734
  set eventPrefix(eventPrefix) {
2546
- this.set({ eventPrefix });
2735
+ this.inputs.set({ eventPrefix });
2547
2736
  }
2548
2737
  set lookAt(lookAt) {
2549
- this.set({ lookAt });
2738
+ this.inputs.set({ lookAt });
2550
2739
  }
2551
2740
  set performance(performance) {
2552
- this.set({ performance });
2553
- }
2554
- #glRef;
2555
- #glEnvInjector;
2556
- constructor() {
2557
- super({
2558
- shadows: false,
2559
- linear: false,
2560
- flat: false,
2561
- legacy: false,
2562
- orthographic: false,
2563
- frameloop: 'always',
2564
- dpr: [1, 2],
2565
- events: createPointerEvents,
2566
- });
2567
- this.#envInjector = inject(EnvironmentInjector);
2568
- this.#injector = inject(Injector);
2569
- this.#host = inject(ElementRef);
2570
- this.#zone = inject(NgZone);
2571
- this.#cdr = inject(ChangeDetectorRef);
2572
- this.#store = inject(NgtStore);
2573
- this.#isReady = this.#store.select('ready');
2574
- this.sceneGraphInputs = {};
2575
- this.compoundPrefixes = [];
2576
- this.created = new EventEmitter();
2577
- this.pointerMissed = new EventEmitter();
2578
- inject(DestroyRef).onDestroy(() => {
2579
- if (this.#glRef)
2580
- this.#glRef.destroy();
2581
- if (this.#glEnvInjector)
2582
- this.#glEnvInjector.destroy();
2583
- injectNgtLoader.destroy();
2584
- this.#store.destroy(this.glCanvas.nativeElement);
2585
- });
2586
- }
2587
- get hbPointerEvents() {
2588
- return this.select('eventSource')() !== this.#host.nativeElement ? 'none' : 'auto';
2741
+ this.inputs.set({ performance });
2589
2742
  }
2590
2743
  ngOnChanges(changes) {
2591
- if (changes['sceneGraphInputs'] && !changes['sceneGraphInputs'].firstChange && this.#glRef) {
2592
- this.#setSceneGraphInputs();
2744
+ if (changes['sceneGraphInputs'] && !changes['sceneGraphInputs'].firstChange && this.glRef) {
2745
+ this.setSceneGraphInputs();
2593
2746
  }
2594
2747
  }
2595
2748
  ngOnInit() {
2596
- if (!this.get('eventSource')) {
2597
- // set default event source to the host element
2598
- this.set({ eventSource: this.#host.nativeElement });
2599
- }
2600
- if (this.pointerMissed.observed) {
2601
- this.#store.set({
2602
- onPointerMissed: (event) => {
2603
- this.pointerMissed.emit(event);
2604
- safeDetectChanges(this.#cdr);
2605
- },
2606
- });
2607
- }
2608
- // setup NgtStore
2609
- this.#store.init();
2610
- // set rootStateMap
2611
- rootStateMap.set(this.glCanvas.nativeElement, this.#store);
2612
- // subscribe to store to listen for ready state
2613
- effect(() => {
2614
- this.#zone.runOutsideAngular(() => {
2615
- if (this.#isReady())
2616
- this.#storeReady();
2617
- });
2618
- }, { injector: this.#injector, allowSignalWrites: true });
2619
- }
2620
- #resizeRef;
2621
- // NOTE: this is invoked outside of Angular Zone
2622
- onResize({ width, height, top, left }) {
2623
- // destroy previous effect
2624
- if (this.#resizeRef) {
2625
- this.#resizeRef.destroy();
2626
- }
2627
- if (width > 0 && height > 0) {
2628
- if (!this.#store.isInit)
2629
- this.#store.init();
2630
- this.#resizeRef = this.#zone.run(() => effect(() => {
2631
- const canvasInputs = this.state();
2632
- this.#zone.runOutsideAngular(() => {
2633
- this.#store.configure({ ...canvasInputs, size: { width, height, top, left } }, this.glCanvas.nativeElement);
2749
+ // NOTE: we resolve glCanvas at this point, setup the configurator
2750
+ this.configurator = this.initRoot(this.glCanvas.nativeElement);
2751
+ this.destroyRef.onDestroy(() => {
2752
+ this.glEnvironmentInjector?.destroy();
2753
+ this.glRef?.destroy();
2754
+ this.resizeEffectRef?.destroy();
2755
+ injectNgtLoader.destroy();
2756
+ this.configurator?.destroy();
2757
+ });
2758
+ }
2759
+ // NOTE: runs outside of Zone due to emitInZone: false
2760
+ onResize(result) {
2761
+ if (result.width > 0 && result.height > 0) {
2762
+ this.resizeEffectRef?.destroy();
2763
+ const inputs = this.inputs.select();
2764
+ // NOTE: go back into zone so that effect runs
2765
+ // TODO: Double-check when effect is made not depended on zone
2766
+ this.resizeEffectRef = this.zone.run(() => effect(() => {
2767
+ this.zone.runOutsideAngular(() => {
2768
+ if (!this.configurator)
2769
+ this.configurator = this.initRoot(this.glCanvas.nativeElement);
2770
+ this.configurator.configure({ ...inputs(), size: result });
2771
+ if (this.glRef) {
2772
+ this.cdr.detectChanges();
2773
+ }
2774
+ else {
2775
+ this.render();
2776
+ }
2634
2777
  });
2635
- }, { injector: this.#injector, manualCleanup: true, allowSignalWrites: true }));
2778
+ }, { manualCleanup: true, injector: this.injector }));
2636
2779
  }
2637
2780
  }
2638
- // NOTE: This is invoked outside of Angular Zone
2639
- #storeReady() {
2640
- // canvas is ready, let's activate the loop
2641
- this.#store.set((state) => ({ internal: { ...state.internal, active: true } }));
2642
- const inputs = this.get();
2643
- const state = this.#store.get();
2781
+ render() {
2782
+ this.glEnvironmentInjector?.destroy();
2783
+ this.glRef?.destroy();
2784
+ // Flag the canvas active, rendering will now begin
2785
+ this.store.set((state) => ({ internal: { ...state.internal, active: true } }));
2786
+ const inputs = this.inputs.get();
2787
+ const state = this.store.get();
2644
2788
  // connect to event source
2645
- state.events.connect?.(is.ref(inputs.eventSource) ? inputs.eventSource.nativeElement : inputs.eventSource);
2646
- // setup compute function for events
2789
+ state.events.connect?.(inputs.eventSource
2790
+ ? is.ref(inputs.eventSource)
2791
+ ? inputs.eventSource.nativeElement
2792
+ : inputs.eventSource
2793
+ : this.host.nativeElement);
2794
+ // setup compute for eventPrefix
2647
2795
  if (inputs.eventPrefix) {
2648
2796
  state.setEvents({
2649
2797
  compute: (event, store) => {
@@ -2657,70 +2805,65 @@ class NgtCanvas extends NgtSignalStore {
2657
2805
  }
2658
2806
  // emit created event if observed
2659
2807
  if (this.created.observed) {
2660
- // but go back into zone to run it
2661
- this.#zone.run(() => {
2662
- this.created.emit(this.#store.get());
2808
+ // but go back into zone to do so
2809
+ this.zone.run(() => {
2810
+ this.created.emit(this.store.get());
2663
2811
  });
2664
2812
  }
2665
- // render
2666
- if (this.#glRef)
2667
- this.#glRef.destroy();
2668
- requestAnimationFrame(() => {
2669
- this.#glEnvInjector = createEnvironmentInjector([
2670
- provideNgtRenderer({
2671
- store: this.#store,
2672
- changeDetectorRef: this.#cdr,
2673
- compoundPrefixes: this.compoundPrefixes,
2674
- }),
2675
- ], this.#envInjector);
2676
- this.#glRef = this.glAnchor.createComponent(this.sceneGraph, {
2677
- environmentInjector: this.#glEnvInjector,
2678
- });
2679
- this.#setSceneGraphInputs();
2680
- this.#overrideChangeDetectorRef();
2681
- safeDetectChanges(this.#cdr);
2813
+ if (!this.store.get('events', 'connected')) {
2814
+ this.store.get('events').connect?.(this.glCanvas.nativeElement);
2815
+ }
2816
+ this.glEnvironmentInjector = createEnvironmentInjector([provideNgtRenderer(this.store, this.compoundPrefixes, this.cdr)], this.environmentInjector);
2817
+ this.glRef = this.viewContainerRef.createComponent(this.sceneGraph, {
2818
+ environmentInjector: this.glEnvironmentInjector,
2819
+ injector: this.injector,
2682
2820
  });
2821
+ this.overrideChangeDetectorRef();
2822
+ this.setSceneGraphInputs();
2683
2823
  }
2684
- #overrideChangeDetectorRef() {
2685
- const originalDetectChanges = this.#cdr.detectChanges.bind(this.#cdr);
2686
- this.#cdr.detectChanges = () => {
2824
+ overrideChangeDetectorRef() {
2825
+ const originalDetectChanges = this.cdr.detectChanges.bind(this.cdr);
2826
+ this.cdr.detectChanges = () => {
2687
2827
  originalDetectChanges();
2688
- safeDetectChanges(this.#glRef?.changeDetectorRef);
2828
+ safeDetectChanges(this.glRef?.changeDetectorRef);
2689
2829
  };
2690
2830
  }
2691
- #setSceneGraphInputs() {
2692
- this.#zone.run(() => {
2693
- if (this.#glRef) {
2831
+ setSceneGraphInputs() {
2832
+ this.zone.run(() => {
2833
+ if (this.glRef) {
2694
2834
  for (const [key, value] of Object.entries(this.sceneGraphInputs)) {
2695
- this.#glRef.setInput(key, value);
2835
+ this.glRef.setInput(key, value);
2696
2836
  }
2697
- safeDetectChanges(this.#glRef.changeDetectorRef);
2837
+ this.glRef.changeDetectorRef.detectChanges();
2698
2838
  }
2699
2839
  });
2700
2840
  }
2701
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.0.3", ngImport: i0, type: NgtCanvas, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
2702
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "16.0.3", type: NgtCanvas, isStandalone: true, selector: "ngt-canvas", inputs: { sceneGraph: "sceneGraph", sceneGraphInputs: "sceneGraphInputs", compoundPrefixes: "compoundPrefixes", linear: "linear", legacy: "legacy", flat: "flat", orthographic: "orthographic", frameloop: "frameloop", dpr: "dpr", raycaster: "raycaster", shadows: "shadows", camera: "camera", scene: "scene", gl: "gl", eventSource: "eventSource", eventPrefix: "eventPrefix", lookAt: "lookAt", performance: "performance" }, outputs: { created: "created", pointerMissed: "pointerMissed" }, host: { styleAttribute: "display: block;position: relative;width: 100%;height: 100%;overflow: hidden;" }, providers: [NgtStore, provideNgxResizeOptions({ emitInZone: false, emitInitialResult: true })], viewQueries: [{ propertyName: "glCanvas", first: true, predicate: ["glCanvas"], descendants: true, static: true }, { propertyName: "glAnchor", first: true, predicate: ["glCanvas"], descendants: true, read: ViewContainerRef, static: true }], usesInheritance: true, usesOnChanges: true, ngImport: i0, template: `
2703
- <div (ngxResize)="onResize($event)" style="height: 100%; width: 100%;">
2704
- <canvas #glCanvas style="display: block;"> </canvas>
2705
- </div>
2706
- `, isInline: true, dependencies: [{ kind: "directive", type: NgxResize, selector: "[ngxResize]", inputs: ["ngxResizeOptions"], outputs: ["ngxResize"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
2841
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.1.7", ngImport: i0, type: NgtCanvas, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
2842
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "16.1.7", type: NgtCanvas, isStandalone: true, selector: "ngt-canvas", inputs: { sceneGraph: "sceneGraph", sceneGraphInputs: "sceneGraphInputs", compoundPrefixes: "compoundPrefixes", linear: "linear", legacy: "legacy", flat: "flat", orthographic: "orthographic", frameloop: "frameloop", dpr: "dpr", raycaster: "raycaster", shadows: "shadows", camera: "camera", scene: "scene", gl: "gl", eventSource: "eventSource", eventPrefix: "eventPrefix", lookAt: "lookAt", performance: "performance" }, outputs: { created: "created" }, host: { properties: { "style.pointerEvents": "hbPointerEvents()" }, styleAttribute: "display: block;position: relative;width: 100%;height: 100%;overflow: hidden;" }, providers: [provideNgxResizeOptions({ emitInZone: false, emitInitialResult: true }), provideNgtStore()], viewQueries: [{ propertyName: "glCanvas", first: true, predicate: ["glCanvas"], descendants: true, static: true }], usesOnChanges: true, ngImport: i0, template: `
2843
+ <div (ngxResize)="onResize($event)" style="height: 100%; width: 100%;">
2844
+ <canvas #glCanvas style="display: block;"> </canvas>
2845
+ </div>
2846
+ `, isInline: true, dependencies: [{ kind: "directive", type: NgxResize, selector: "[ngxResize]", inputs: ["ngxResizeOptions"], outputs: ["ngxResize"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
2707
2847
  }
2708
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.0.3", ngImport: i0, type: NgtCanvas, decorators: [{
2848
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.1.7", ngImport: i0, type: NgtCanvas, decorators: [{
2709
2849
  type: Component,
2710
2850
  args: [{
2711
2851
  selector: 'ngt-canvas',
2712
2852
  standalone: true,
2713
2853
  template: `
2714
- <div (ngxResize)="onResize($event)" style="height: 100%; width: 100%;">
2715
- <canvas #glCanvas style="display: block;"> </canvas>
2716
- </div>
2717
- `,
2854
+ <div (ngxResize)="onResize($event)" style="height: 100%; width: 100%;">
2855
+ <canvas #glCanvas style="display: block;"> </canvas>
2856
+ </div>
2857
+ `,
2718
2858
  imports: [NgxResize],
2719
- providers: [NgtStore, provideNgxResizeOptions({ emitInZone: false, emitInitialResult: true })],
2720
- host: { style: 'display: block;position: relative;width: 100%;height: 100%;overflow: hidden;' },
2859
+ providers: [provideNgxResizeOptions({ emitInZone: false, emitInitialResult: true }), provideNgtStore()],
2860
+ host: {
2861
+ style: 'display: block;position: relative;width: 100%;height: 100%;overflow: hidden;',
2862
+ '[style.pointerEvents]': 'hbPointerEvents()',
2863
+ },
2721
2864
  changeDetection: ChangeDetectionStrategy.OnPush,
2722
2865
  }]
2723
- }], ctorParameters: function () { return []; }, propDecorators: { sceneGraph: [{
2866
+ }], propDecorators: { sceneGraph: [{
2724
2867
  type: Input,
2725
2868
  args: [{ required: true }]
2726
2869
  }], sceneGraphInputs: [{
@@ -2759,34 +2902,58 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.0.3", ngImpor
2759
2902
  type: Input
2760
2903
  }], created: [{
2761
2904
  type: Output
2762
- }], pointerMissed: [{
2763
- type: Output
2764
2905
  }], glCanvas: [{
2765
2906
  type: ViewChild,
2766
2907
  args: ['glCanvas', { static: true }]
2767
- }], glAnchor: [{
2768
- type: ViewChild,
2769
- args: ['glCanvas', { static: true, read: ViewContainerRef }]
2770
2908
  }] } });
2771
2909
 
2772
- function injectBeforeRender(cb, { priority = 0, injector } = {}) {
2773
- injector = assertInjectionContext(injectBeforeRender, injector);
2774
- return runInInjectionContext(injector, () => {
2775
- const store = inject(NgtStore);
2776
- const sub = store.get('internal').subscribe(cb, priority, store);
2777
- inject(DestroyRef).onDestroy(() => void sub());
2778
- return sub;
2779
- });
2910
+ class NgtKey extends NgtCommonDirective {
2911
+ constructor() {
2912
+ super(...arguments);
2913
+ this.lastKey = '';
2914
+ }
2915
+ static { this.processComment = false; }
2916
+ validate() {
2917
+ return false;
2918
+ }
2919
+ set key(key) {
2920
+ const normalizedKey = JSON.stringify(key);
2921
+ if (this.lastKey !== normalizedKey) {
2922
+ this.lastKey = normalizedKey;
2923
+ this.createView();
2924
+ }
2925
+ }
2926
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.1.7", ngImport: i0, type: NgtKey, deps: null, target: i0.ɵɵFactoryTarget.Directive }); }
2927
+ static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "16.1.7", type: NgtKey, isStandalone: true, selector: "ng-template[key]", inputs: { key: "key" }, usesInheritance: true, ngImport: i0 }); }
2928
+ }
2929
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.1.7", ngImport: i0, type: NgtKey, decorators: [{
2930
+ type: Directive,
2931
+ args: [{ selector: 'ng-template[key]', standalone: true }]
2932
+ }], propDecorators: { key: [{
2933
+ type: Input
2934
+ }] } });
2935
+
2936
+ class NgtRepeat extends NgForOf {
2937
+ set ngForRepeat(count) {
2938
+ this.ngForOf = Number.isInteger(count) ? Array.from({ length: count }, (_, i) => i) : [];
2939
+ }
2940
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.1.7", ngImport: i0, type: NgtRepeat, deps: null, target: i0.ɵɵFactoryTarget.Directive }); }
2941
+ static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "16.1.7", type: NgtRepeat, isStandalone: true, selector: "[ngFor][ngForRepeat]", inputs: { ngForRepeat: "ngForRepeat" }, usesInheritance: true, ngImport: i0 }); }
2780
2942
  }
2943
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.1.7", ngImport: i0, type: NgtRepeat, decorators: [{
2944
+ type: Directive,
2945
+ args: [{ selector: '[ngFor][ngForRepeat]', standalone: true }]
2946
+ }], propDecorators: { ngForRepeat: [{
2947
+ type: Input
2948
+ }] } });
2781
2949
 
2782
2950
  function injectNgtRef(initial = null, injector) {
2783
- injector = assertInjectionContext(injectNgtRef, injector);
2951
+ injector = assertInjectionContext(injectNgtRef);
2784
2952
  const ref = is.ref(initial) ? initial : new ElementRef(initial);
2785
2953
  const signalRef = signal(ref.nativeElement);
2786
2954
  const readonlySignal = signalRef.asReadonly();
2787
2955
  const cached = new Map();
2788
2956
  return runInInjectionContext(injector, () => {
2789
- const cdr = inject(ChangeDetectorRef);
2790
2957
  inject(DestroyRef).onDestroy(() => void cached.clear());
2791
2958
  const children = (type = 'objects') => {
2792
2959
  if (!cached.has(type)) {
@@ -2806,42 +2973,24 @@ function injectNgtRef(initial = null, injector) {
2806
2973
  }
2807
2974
  return cached.get(type);
2808
2975
  };
2809
- Object.defineProperty(ref, 'nativeElement', {
2810
- set: (newElement) => {
2811
- if (newElement !== untracked(signalRef)) {
2812
- try {
2813
- signalRef.set(newElement);
2814
- }
2815
- catch {
2816
- requestAnimationFrame(() => {
2976
+ Object.defineProperties(ref, {
2977
+ nativeElement: {
2978
+ set: (newElement) => {
2979
+ untracked(() => {
2980
+ if (newElement !== signalRef()) {
2817
2981
  signalRef.set(newElement);
2818
- });
2819
- }
2820
- requestAnimationFrame(() => void safeDetectChanges(cdr));
2821
- // trigger CDR
2822
- }
2982
+ }
2983
+ });
2984
+ },
2985
+ get: readonlySignal,
2823
2986
  },
2824
- get: () => readonlySignal(),
2987
+ untracked: { get: () => untracked(readonlySignal) },
2988
+ children: { get: () => children },
2825
2989
  });
2826
- Object.defineProperty(ref, 'untracked', { get: () => untracked(readonlySignal) });
2827
- return Object.assign(ref, { children });
2990
+ return ref;
2828
2991
  });
2829
2992
  }
2830
2993
 
2831
- class NgtRepeat extends NgForOf {
2832
- set ngForRepeat(count) {
2833
- this.ngForOf = Number.isInteger(count) ? Array.from({ length: count }, (_, i) => i) : [];
2834
- }
2835
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.0.3", ngImport: i0, type: NgtRepeat, deps: null, target: i0.ɵɵFactoryTarget.Directive }); }
2836
- static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "16.0.3", type: NgtRepeat, isStandalone: true, selector: "[ngFor][ngForRepeat]", inputs: { ngForRepeat: "ngForRepeat" }, usesInheritance: true, ngImport: i0 }); }
2837
- }
2838
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.0.3", ngImport: i0, type: NgtRepeat, decorators: [{
2839
- type: Directive,
2840
- args: [{ selector: '[ngFor][ngForRepeat]', standalone: true }]
2841
- }], propDecorators: { ngForRepeat: [{
2842
- type: Input
2843
- }] } });
2844
-
2845
2994
  const privateKeys = [
2846
2995
  'get',
2847
2996
  'set',
@@ -2856,38 +3005,38 @@ const privateKeys = [
2856
3005
  'viewport',
2857
3006
  ];
2858
3007
  class NgtPortalBeforeRender {
2859
- #portalStore;
2860
3008
  constructor() {
2861
- this.#portalStore = inject(NgtStore);
3009
+ this.portalStore = injectNgtStore();
3010
+ this.injector = inject(Injector);
2862
3011
  this.renderPriority = 1;
2863
3012
  this.beforeRender = new EventEmitter();
2864
- let oldClear;
2865
- requestAnimationFrameInInjectionContext(() => {
2866
- injectBeforeRender(({ delta, frame }) => {
2867
- this.beforeRender.emit({ ...this.#portalStore.get(), delta, frame });
2868
- const { gl, scene, camera } = this.#portalStore.get();
2869
- oldClear = gl.autoClear;
2870
- if (this.renderPriority === 1) {
2871
- // clear scene and render with default
2872
- gl.autoClear = true;
2873
- gl.render(this.parentScene, this.parentCamera);
2874
- }
2875
- // disable cleaning
2876
- gl.autoClear = false;
2877
- gl.clearDepth();
2878
- gl.render(scene, camera);
2879
- // restore
2880
- gl.autoClear = oldClear;
2881
- }, { priority: this.renderPriority });
2882
- });
2883
3013
  }
2884
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.0.3", ngImport: i0, type: NgtPortalBeforeRender, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
2885
- static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "16.0.3", type: NgtPortalBeforeRender, isStandalone: true, selector: "[ngtPortalBeforeRender]", inputs: { renderPriority: "renderPriority", parentScene: "parentScene", parentCamera: "parentCamera" }, outputs: { beforeRender: "beforeRender" }, ngImport: i0 }); }
3014
+ ngOnInit() {
3015
+ let oldClear;
3016
+ injectBeforeRender(({ delta, frame }) => {
3017
+ this.beforeRender.emit({ ...this.portalStore.get(), delta, frame });
3018
+ const { gl, scene, camera } = this.portalStore.get();
3019
+ oldClear = gl.autoClear;
3020
+ if (this.renderPriority === 1) {
3021
+ // clear scene and render with default
3022
+ gl.autoClear = true;
3023
+ gl.render(this.parentScene, this.parentCamera);
3024
+ }
3025
+ // disable cleaning
3026
+ gl.autoClear = false;
3027
+ gl.clearDepth();
3028
+ gl.render(scene, camera);
3029
+ // restore
3030
+ gl.autoClear = oldClear;
3031
+ }, { priority: this.renderPriority, injector: this.injector });
3032
+ }
3033
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.1.7", ngImport: i0, type: NgtPortalBeforeRender, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
3034
+ static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "16.1.7", type: NgtPortalBeforeRender, isStandalone: true, selector: "[ngtPortalBeforeRender]", inputs: { renderPriority: "renderPriority", parentScene: "parentScene", parentCamera: "parentCamera" }, outputs: { beforeRender: "beforeRender" }, ngImport: i0 }); }
2886
3035
  }
2887
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.0.3", ngImport: i0, type: NgtPortalBeforeRender, decorators: [{
3036
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.1.7", ngImport: i0, type: NgtPortalBeforeRender, decorators: [{
2888
3037
  type: Directive,
2889
3038
  args: [{ selector: '[ngtPortalBeforeRender]', standalone: true }]
2890
- }], ctorParameters: function () { return []; }, propDecorators: { renderPriority: [{
3039
+ }], propDecorators: { renderPriority: [{
2891
3040
  type: Input
2892
3041
  }], parentScene: [{
2893
3042
  type: Input,
@@ -2906,96 +3055,90 @@ class NgtPortalContent {
2906
3055
  delete commentNode[SPECIAL_INTERNAL_ADD_COMMENT];
2907
3056
  }
2908
3057
  }
2909
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.0.3", ngImport: i0, type: NgtPortalContent, deps: [{ token: i0.ViewContainerRef }, { token: i0.ViewContainerRef, skipSelf: true }], target: i0.ɵɵFactoryTarget.Directive }); }
2910
- static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "16.0.3", type: NgtPortalContent, isStandalone: true, selector: "ng-template[ngtPortalContent]", ngImport: i0 }); }
3058
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.1.7", ngImport: i0, type: NgtPortalContent, deps: [{ token: i0.ViewContainerRef }, { token: i0.ViewContainerRef, skipSelf: true }], target: i0.ɵɵFactoryTarget.Directive }); }
3059
+ static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "16.1.7", type: NgtPortalContent, isStandalone: true, selector: "ng-template[ngtPortalContent]", ngImport: i0 }); }
2911
3060
  }
2912
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.0.3", ngImport: i0, type: NgtPortalContent, decorators: [{
3061
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.1.7", ngImport: i0, type: NgtPortalContent, decorators: [{
2913
3062
  type: Directive,
2914
3063
  args: [{ selector: 'ng-template[ngtPortalContent]', standalone: true }]
2915
3064
  }], ctorParameters: function () { return [{ type: i0.ViewContainerRef }, { type: i0.ViewContainerRef, decorators: [{
2916
3065
  type: SkipSelf
2917
3066
  }] }]; } });
2918
- class NgtPortal extends NgtSignalStore {
3067
+ class NgtPortal {
2919
3068
  set container(container) {
2920
- this.set({ container });
3069
+ this.inputs.set({ container });
2921
3070
  }
2922
3071
  set portalState(state) {
2923
- this.set({ state });
2924
- }
2925
- #parentStore;
2926
- #portalStore;
2927
- #injector;
2928
- #zone;
2929
- #raycaster;
2930
- #pointer;
2931
- #portalContentView;
3072
+ this.inputs.set({ state });
3073
+ }
2932
3074
  constructor() {
2933
- super({ container: injectNgtRef(prepare(new THREE.Scene())) });
3075
+ this.inputs = signalStore({ container: injectNgtRef(prepare(new THREE.Scene())) });
2934
3076
  this.autoRender = true;
2935
3077
  this.autoRenderPriority = 1;
2936
3078
  this.beforeRender = new EventEmitter();
2937
- this.#parentStore = inject(NgtStore, { skipSelf: true });
2938
- this.parentScene = this.#parentStore.get('scene');
2939
- this.parentCamera = this.#parentStore.get('camera');
2940
- this.#portalStore = inject(NgtStore, { self: true });
2941
- this.#injector = inject(Injector);
2942
- this.#zone = inject(NgZone);
2943
- this.#raycaster = new THREE.Raycaster();
2944
- this.#pointer = new THREE.Vector2();
3079
+ this.parentStore = injectNgtStore({ skipSelf: true });
3080
+ this.parentScene = this.parentStore.get('scene');
3081
+ this.parentCamera = this.parentStore.get('camera');
3082
+ this.portalStore = injectNgtStore({ self: true });
3083
+ this.injector = inject(Injector);
3084
+ this.zone = inject(NgZone);
3085
+ this.raycaster = new THREE.Raycaster();
3086
+ this.pointer = new THREE.Vector2();
2945
3087
  this.portalContentRendered = false;
2946
3088
  inject(DestroyRef).onDestroy(() => {
2947
- if (this.#portalContentView && !this.#portalContentView.destroyed) {
2948
- this.#portalContentView.destroy();
3089
+ if (this.portalContentView && !this.portalContentView.destroyed) {
3090
+ this.portalContentView.destroy();
2949
3091
  }
2950
3092
  });
2951
3093
  }
2952
3094
  ngOnInit() {
2953
- const previousState = this.#parentStore.get();
2954
- const inputsState = this.get();
3095
+ const previousState = this.parentStore.get();
3096
+ const inputsState = this.inputs.get();
2955
3097
  if (!inputsState.state && this.autoRender) {
2956
3098
  inputsState.state = { events: { priority: this.autoRenderPriority + 1 } };
2957
3099
  }
2958
3100
  const { events, size, ...restInputsState } = inputsState.state || {};
2959
3101
  const containerState = inputsState.container;
2960
- const container = is.ref(containerState) ? containerState.nativeElement : containerState;
3102
+ let container = is.ref(containerState) ? containerState.nativeElement : containerState;
3103
+ if (!is.instance(container)) {
3104
+ container = prepare(container);
3105
+ }
2961
3106
  const localState = getLocalState(container);
2962
3107
  if (!localState.store) {
2963
- localState.store = this.#portalStore;
3108
+ localState.store = this.portalStore;
2964
3109
  }
2965
- this.#portalStore.set({
3110
+ this.portalStore.set({
2966
3111
  ...previousState,
2967
3112
  scene: container,
2968
- raycaster: this.#raycaster,
2969
- pointer: this.#pointer,
2970
- previousStore: this.#parentStore,
3113
+ raycaster: this.raycaster,
3114
+ pointer: this.pointer,
3115
+ previousRoot: this.parentStore,
2971
3116
  events: { ...previousState.events, ...(events || {}) },
2972
3117
  size: { ...previousState.size, ...(size || {}) },
2973
3118
  ...restInputsState,
2974
- get: this.#portalStore.get.bind(this.#portalStore),
2975
- set: this.#portalStore.set.bind(this.#portalStore),
2976
- setEvents: (events) => this.#portalStore.set((state) => ({ ...state, events: { ...state.events, ...events } })),
3119
+ setEvents: (events) => this.portalStore.set((state) => ({ ...state, events: { ...state.events, ...events } })),
2977
3120
  });
2978
- const parentState = this.#parentStore.select();
3121
+ const parentState = this.parentStore.select();
2979
3122
  effect(() => {
2980
3123
  const previous = parentState();
2981
- this.#zone.runOutsideAngular(() => {
2982
- this.#portalStore.set((state) => this.#inject(previous, state));
3124
+ this.zone.runOutsideAngular(() => {
3125
+ this.portalStore.set((state) => this.inject(previous, state));
2983
3126
  });
2984
- }, { injector: this.#injector, allowSignalWrites: true });
3127
+ }, { injector: this.injector });
2985
3128
  requestAnimationFrame(() => {
2986
- this.#portalStore.set((injectState) => this.#inject(this.#parentStore.get(), injectState));
3129
+ this.portalStore.set((injectState) => this.inject(this.parentStore.get(), injectState));
2987
3130
  });
2988
- this.#portalContentView = this.portalContentAnchor.createEmbeddedView(this.portalContentTemplate);
2989
- safeDetectChanges(this.#portalContentView);
3131
+ this.portalContentView = this.portalContentAnchor.createEmbeddedView(this.portalContentTemplate);
3132
+ safeDetectChanges(this.portalContentView);
2990
3133
  this.portalContentRendered = true;
2991
3134
  }
2992
3135
  onBeforeRender(portal) {
2993
3136
  this.beforeRender.emit({
2994
- root: { ...this.#parentStore.get(), delta: portal.delta, frame: portal.frame },
3137
+ root: { ...this.parentStore.get(), delta: portal.delta, frame: portal.frame },
2995
3138
  portal,
2996
3139
  });
2997
3140
  }
2998
- #inject(rootState, injectState) {
3141
+ inject(rootState, injectState) {
2999
3142
  const intersect = { ...rootState };
3000
3143
  Object.keys(intersect).forEach((key) => {
3001
3144
  if (privateKeys.includes(key) ||
@@ -3003,7 +3146,7 @@ class NgtPortal extends NgtSignalStore {
3003
3146
  delete intersect[key];
3004
3147
  }
3005
3148
  });
3006
- const inputs = this.get();
3149
+ const inputs = this.inputs.get();
3007
3150
  const { size, events, ...restInputsState } = inputs.state || {};
3008
3151
  let viewport = undefined;
3009
3152
  if (injectState && size) {
@@ -3015,53 +3158,54 @@ class NgtPortal extends NgtSignalStore {
3015
3158
  return {
3016
3159
  ...intersect,
3017
3160
  scene: is.ref(inputs.container) ? inputs.container.nativeElement : inputs.container,
3018
- raycaster: this.#raycaster,
3019
- pointer: this.#pointer,
3020
- previousStore: this.#parentStore,
3161
+ raycaster: this.raycaster,
3162
+ pointer: this.pointer,
3163
+ previousRoot: this.parentStore,
3021
3164
  events: { ...rootState.events, ...(injectState?.events || {}), ...events },
3022
3165
  size: { ...rootState.size, ...size },
3023
3166
  viewport: { ...rootState.viewport, ...(viewport || {}) },
3024
3167
  ...restInputsState,
3025
3168
  };
3026
3169
  }
3027
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.0.3", ngImport: i0, type: NgtPortal, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
3028
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "16.0.3", type: NgtPortal, isStandalone: true, selector: "ngt-portal", inputs: { container: "container", portalState: "portalState", autoRender: "autoRender", autoRenderPriority: "autoRenderPriority" }, outputs: { beforeRender: "beforeRender" }, providers: [NgtStore], queries: [{ propertyName: "portalContentTemplate", first: true, predicate: NgtPortalContent, descendants: true, read: TemplateRef, static: true }], viewQueries: [{ propertyName: "portalContentAnchor", first: true, predicate: ["portalContentAnchor"], descendants: true, read: ViewContainerRef, static: true }], usesInheritance: true, ngImport: i0, template: `
3029
- <ng-container #portalContentAnchor>
3030
- <ng-container
3031
- *ngIf="autoRender && portalContentRendered"
3032
- ngtPortalBeforeRender
3033
- [renderPriority]="autoRenderPriority"
3034
- [parentScene]="parentScene"
3035
- [parentCamera]="parentCamera"
3036
- (beforeRender)="onBeforeRender($event)"
3037
- />
3038
- </ng-container>
3039
- `, isInline: true, dependencies: [{ kind: "directive", type: NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: NgtPortalBeforeRender, selector: "[ngtPortalBeforeRender]", inputs: ["renderPriority", "parentScene", "parentCamera"], outputs: ["beforeRender"] }] }); }
3170
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.1.7", ngImport: i0, type: NgtPortal, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
3171
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "16.1.7", type: NgtPortal, isStandalone: true, selector: "ngt-portal", inputs: { container: "container", portalState: ["state", "portalState"], autoRender: "autoRender", autoRenderPriority: "autoRenderPriority" }, outputs: { beforeRender: "beforeRender" }, providers: [{ provide: NGT_STORE, useFactory: () => signalStore({}) }], queries: [{ propertyName: "portalContentTemplate", first: true, predicate: NgtPortalContent, descendants: true, read: TemplateRef, static: true }], viewQueries: [{ propertyName: "portalContentAnchor", first: true, predicate: ["portalContentAnchor"], descendants: true, read: ViewContainerRef, static: true }], ngImport: i0, template: `
3172
+ <ng-container #portalContentAnchor>
3173
+ <ng-container
3174
+ *ngIf="autoRender && portalContentRendered"
3175
+ ngtPortalBeforeRender
3176
+ [renderPriority]="autoRenderPriority"
3177
+ [parentScene]="parentScene"
3178
+ [parentCamera]="parentCamera"
3179
+ (beforeRender)="onBeforeRender($event)"
3180
+ />
3181
+ </ng-container>
3182
+ `, isInline: true, dependencies: [{ kind: "directive", type: NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: NgtPortalBeforeRender, selector: "[ngtPortalBeforeRender]", inputs: ["renderPriority", "parentScene", "parentCamera"], outputs: ["beforeRender"] }] }); }
3040
3183
  }
3041
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.0.3", ngImport: i0, type: NgtPortal, decorators: [{
3184
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.1.7", ngImport: i0, type: NgtPortal, decorators: [{
3042
3185
  type: Component,
3043
3186
  args: [{
3044
3187
  selector: 'ngt-portal',
3045
3188
  standalone: true,
3046
3189
  template: `
3047
- <ng-container #portalContentAnchor>
3048
- <ng-container
3049
- *ngIf="autoRender && portalContentRendered"
3050
- ngtPortalBeforeRender
3051
- [renderPriority]="autoRenderPriority"
3052
- [parentScene]="parentScene"
3053
- [parentCamera]="parentCamera"
3054
- (beforeRender)="onBeforeRender($event)"
3055
- />
3056
- </ng-container>
3057
- `,
3190
+ <ng-container #portalContentAnchor>
3191
+ <ng-container
3192
+ *ngIf="autoRender && portalContentRendered"
3193
+ ngtPortalBeforeRender
3194
+ [renderPriority]="autoRenderPriority"
3195
+ [parentScene]="parentScene"
3196
+ [parentCamera]="parentCamera"
3197
+ (beforeRender)="onBeforeRender($event)"
3198
+ />
3199
+ </ng-container>
3200
+ `,
3058
3201
  imports: [NgIf, NgtPortalBeforeRender],
3059
- providers: [NgtStore],
3202
+ providers: [{ provide: NGT_STORE, useFactory: () => signalStore({}) }],
3060
3203
  }]
3061
3204
  }], ctorParameters: function () { return []; }, propDecorators: { container: [{
3062
3205
  type: Input
3063
3206
  }], portalState: [{
3064
- type: Input
3207
+ type: Input,
3208
+ args: ['state']
3065
3209
  }], autoRender: [{
3066
3210
  type: Input
3067
3211
  }], autoRenderPriority: [{
@@ -3085,10 +3229,10 @@ class NgtRoutedScene {
3085
3229
  .pipe(filter((event) => event instanceof ActivationEnd), takeUntilDestroyed())
3086
3230
  .subscribe(() => safeDetectChanges(cdr));
3087
3231
  }
3088
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.0.3", ngImport: i0, type: NgtRoutedScene, deps: [{ token: i1.Router }, { token: i0.ChangeDetectorRef }], target: i0.ɵɵFactoryTarget.Component }); }
3089
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "16.0.3", type: NgtRoutedScene, isStandalone: true, selector: "ngt-routed-scene", ngImport: i0, template: `<router-outlet />`, isInline: true, dependencies: [{ kind: "directive", type: RouterOutlet, selector: "router-outlet", inputs: ["name"], outputs: ["activate", "deactivate", "attach", "detach"], exportAs: ["outlet"] }] }); }
3232
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.1.7", ngImport: i0, type: NgtRoutedScene, deps: [{ token: i1.Router }, { token: i0.ChangeDetectorRef }], target: i0.ɵɵFactoryTarget.Component }); }
3233
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "16.1.7", type: NgtRoutedScene, isStandalone: true, selector: "ngt-routed-scene", ngImport: i0, template: `<router-outlet />`, isInline: true, dependencies: [{ kind: "directive", type: RouterOutlet, selector: "router-outlet", inputs: ["name"], outputs: ["activate", "deactivate", "attach", "detach"], exportAs: ["outlet"] }] }); }
3090
3234
  }
3091
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.0.3", ngImport: i0, type: NgtRoutedScene, decorators: [{
3235
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.1.7", ngImport: i0, type: NgtRoutedScene, decorators: [{
3092
3236
  type: Component,
3093
3237
  args: [{
3094
3238
  standalone: true,
@@ -3102,5 +3246,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.0.3", ngImpor
3102
3246
  * Generated bundle index. Do not edit.
3103
3247
  */
3104
3248
 
3105
- export { NGT_CATALOGUE, NgtArgs, NgtCanvas, NgtParent, NgtPortal, NgtPortalContent, NgtRepeat, NgtRoutedScene, NgtSignalStore, NgtStore, addAfterEffect, addEffect, addTail, applyProps, assertInjectionContext, checkNeedsUpdate, checkUpdate, createAttachFunction, extend, getLocalState, injectBeforeRender, injectNgtLoader, injectNgtRef, invalidateInstance, is, makeDefaultCamera, makeDefaultRenderer, makeDpr, makeId, makeObjectGraph, prepare, requestAnimationFrameInInjectionContext, rootStateMap, safeDetectChanges, updateCamera };
3249
+ export { HTML, NGT_STORE, NgtArgs, NgtCanvas, NgtKey, NgtParent, NgtPortal, NgtPortalContent, NgtRepeat, NgtRoutedScene, addAfterEffect, addEffect, addTail, applyProps, assertInjectionContext, checkNeedsUpdate, checkUpdate, createAttachFunction, createInjectionToken, diffProps, extend, getLocalState, injectBeforeRender, injectNgtLoader, injectNgtRef, injectNgtStore, invalidateInstance, is, makeDefaultCamera, makeDefaultRenderer, makeDpr, makeId, makeObjectGraph, prepare, provideNgtRenderer, provideNgtStore, safeDetectChanges, signalStore, updateCamera };
3106
3250
  //# sourceMappingURL=angular-three.mjs.map