angular-three 2.0.0-beta.4 → 2.0.0-beta.41

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