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

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