@needle-tools/engine 4.2.2 → 4.2.3
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.
- package/CHANGELOG.md +5 -0
- package/dist/gltf-progressive.js +258 -257
- package/dist/gltf-progressive.light.js +258 -257
- package/dist/gltf-progressive.light.min.js +7 -7
- package/dist/gltf-progressive.light.umd.cjs +7 -7
- package/dist/gltf-progressive.min.js +7 -7
- package/dist/gltf-progressive.umd.cjs +7 -7
- package/dist/needle-engine.bundle.js +3455 -3426
- package/dist/needle-engine.bundle.light.js +3449 -3420
- package/dist/needle-engine.bundle.light.min.js +106 -106
- package/dist/needle-engine.bundle.light.umd.cjs +103 -103
- package/dist/needle-engine.bundle.min.js +106 -106
- package/dist/needle-engine.bundle.umd.cjs +101 -101
- package/dist/needle-engine.d.ts +9 -9
- package/dist/needle-engine.light.d.ts +9 -9
- package/lib/engine/engine_context.d.ts +1 -0
- package/lib/engine/engine_context.js +7 -3
- package/lib/engine/engine_context.js.map +1 -1
- package/lib/engine/engine_loaders.js +6 -12
- package/lib/engine/engine_loaders.js.map +1 -1
- package/lib/engine/engine_physics_rapier.js +1 -1
- package/lib/engine/engine_physics_rapier.js.map +1 -1
- package/lib/engine-components/OrbitControls.d.ts +9 -2
- package/lib/engine-components/OrbitControls.js +62 -19
- package/lib/engine-components/OrbitControls.js.map +1 -1
- package/lib/engine-components/ui/InputField.d.ts +1 -0
- package/lib/engine-components/ui/InputField.js +11 -0
- package/lib/engine-components/ui/InputField.js.map +1 -1
- package/package.json +2 -2
- package/plugins/types/userconfig.d.ts +2 -2
- package/plugins/vite/pwa.js +33 -22
- package/src/engine/codegen/register_types.ts +2 -2
- package/src/engine/engine_context.ts +7 -3
- package/src/engine/engine_loaders.ts +6 -10
- package/src/engine/engine_physics_rapier.ts +1 -1
- package/src/engine-components/OrbitControls.ts +71 -21
- package/src/engine-components/ui/InputField.ts +9 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@needle-tools/engine",
|
|
3
|
-
"version": "4.2.
|
|
3
|
+
"version": "4.2.3",
|
|
4
4
|
"description": "Needle Engine is a web-based runtime for 3D apps. It runs on your machine for development with great integrations into editors like Unity or Blender - and can be deployed onto any device! It is flexible, extensible and networking and XR are built-in",
|
|
5
5
|
"main": "dist/needle-engine.min.js",
|
|
6
6
|
"exports": {
|
|
@@ -86,7 +86,7 @@
|
|
|
86
86
|
],
|
|
87
87
|
"dependencies": {
|
|
88
88
|
"@dimforge/rapier3d-compat": "^0.14.0",
|
|
89
|
-
"@needle-tools/gltf-progressive": "^2.1.
|
|
89
|
+
"@needle-tools/gltf-progressive": "^2.1.1",
|
|
90
90
|
"@webxr-input-profiles/motion-controllers": "^1.0.0",
|
|
91
91
|
"flatbuffers": "2.0.4",
|
|
92
92
|
"md5": "^2.3.0",
|
|
@@ -85,8 +85,8 @@ export type userSettings = {
|
|
|
85
85
|
*/
|
|
86
86
|
posterGenerationMode?: "default" | "once";
|
|
87
87
|
|
|
88
|
-
/** Pass in a mix of VitePWA and NeedlePWA options, or "false" */
|
|
89
|
-
/** @type {import("vite-plugin-pwa").VitePWAOptions & Partial<NeedlePWAOptions> |
|
|
88
|
+
/** Pass in a mix of VitePWA and NeedlePWA options, true to enable with defaults or "false" to disable */
|
|
89
|
+
/** @type {import("vite-plugin-pwa").VitePWAOptions & Partial<NeedlePWAOptions> | boolean} */
|
|
90
90
|
pwa?: undefined;
|
|
91
91
|
|
|
92
92
|
/** used by nextjs config to forward the webpack module */
|
package/plugins/vite/pwa.js
CHANGED
|
@@ -4,6 +4,7 @@ import { copyFileSync, existsSync, mkdirSync, readdirSync, readFileSync, rmSync,
|
|
|
4
4
|
import { getOutputDirectory } from './config.js';
|
|
5
5
|
import { getPosterPath } from './poster.js';
|
|
6
6
|
|
|
7
|
+
const pwaErrorWithInstructions = "It seems that you're trying to build a PWA using 'vite-plugin-pwa'!\nNeedle can manage PWA settings for you – just pass the same 'pwaOptions' to the needlePlugins and VitePWA plugins:\n\n1. Install the vite PWA plugin: npm install vite-plugin-pwa --save-dev\n\n2. Then update your vite.config.js:\n\n import { VitePWA } from 'vite-plugin-pwa';\n ...\n needlePlugins(command, needleConfig, { pwa: pwaOptions }),\n VitePWA(pwaOptions),\n\nIf you want to manage PWA building yourself and skip this check, please pass '{ pwa: false }' to needlePlugins.";
|
|
7
8
|
|
|
8
9
|
/** Provides reasonable defaults for a PWA manifest and workbox settings.
|
|
9
10
|
* @param {import('../types').userSettings} userSettings
|
|
@@ -13,7 +14,9 @@ import { getPosterPath } from './poster.js';
|
|
|
13
14
|
export const needlePWA = (command, config, userSettings) => {
|
|
14
15
|
// @ts-ignore // TODO correctly type the userSettings.pwaOptions object
|
|
15
16
|
/** @type {import("vite-plugin-pwa").VitePWAOptions | false} */
|
|
16
|
-
const pwaOptions = userSettings.pwa
|
|
17
|
+
const pwaOptions = userSettings.pwa === true
|
|
18
|
+
? { } // allow setting `pwa: true`
|
|
19
|
+
: userSettings.pwa;
|
|
17
20
|
|
|
18
21
|
/** The context contains files that are generated by the plugin and should be deleted after
|
|
19
22
|
* @type {import('../types').NeedlePWAProcessContext} */
|
|
@@ -29,7 +32,7 @@ export const needlePWA = (command, config, userSettings) => {
|
|
|
29
32
|
apply: "build",
|
|
30
33
|
configResolved(viteConfig) {
|
|
31
34
|
if (findVitePWAPlugin(viteConfig)) {
|
|
32
|
-
errorThrow(
|
|
35
|
+
errorThrow(pwaErrorWithInstructions);
|
|
33
36
|
}
|
|
34
37
|
},
|
|
35
38
|
}
|
|
@@ -88,7 +91,7 @@ export const needlePWA = (command, config, userSettings) => {
|
|
|
88
91
|
let pwaPluginIndex = -1;
|
|
89
92
|
let gzipPlugin = null;
|
|
90
93
|
if (viteConfig.plugins) {
|
|
91
|
-
for (let i = viteConfig.plugins.length-1; i >= 0; i--) {
|
|
94
|
+
for (let i = viteConfig.plugins.length - 1; i >= 0; i--) {
|
|
92
95
|
const plugin = viteConfig.plugins[i];
|
|
93
96
|
if (plugin && "name" in plugin && plugin.name === "vite:compression") {
|
|
94
97
|
gzipPluginIndex = i;
|
|
@@ -152,7 +155,7 @@ export const needlePWA = (command, config, userSettings) => {
|
|
|
152
155
|
try {
|
|
153
156
|
const plugin = findVitePWAPlugin(config);
|
|
154
157
|
if (!plugin) {
|
|
155
|
-
errorThrow(
|
|
158
|
+
errorThrow(pwaErrorWithInstructions);
|
|
156
159
|
}
|
|
157
160
|
|
|
158
161
|
// check if the index header contains the webmanifest ALSO
|
|
@@ -547,28 +550,36 @@ function processWorkboxConfig(manifest) {
|
|
|
547
550
|
],
|
|
548
551
|
runtimeCaching: [
|
|
549
552
|
// allow caching Google Fonts
|
|
550
|
-
{
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
553
|
+
{
|
|
554
|
+
...externalResourceCaching, ...{
|
|
555
|
+
urlPattern: /^https:\/\/fonts\.googleapis\.com\/.*/i,
|
|
556
|
+
options: { cacheName: 'google-fonts-cache' },
|
|
557
|
+
}
|
|
558
|
+
},
|
|
554
559
|
// allow caching static resources from Google, like CSS
|
|
555
|
-
{
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
560
|
+
{
|
|
561
|
+
...externalResourceCaching, ...{
|
|
562
|
+
urlPattern: /^https:\/\/fonts\.gstatic\.com\/.*/i,
|
|
563
|
+
options: { cacheName: 'gstatic-fonts-cache' },
|
|
564
|
+
}
|
|
565
|
+
},
|
|
559
566
|
// allow caching Needle cdn resources
|
|
560
|
-
{
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
567
|
+
{
|
|
568
|
+
...externalResourceCaching, ...{
|
|
569
|
+
urlPattern: /^https:\/\/cdn\.needle\.tools\/.*/i,
|
|
570
|
+
handler: 'NetworkFirst',
|
|
571
|
+
options: { cacheName: 'needle-cdn-cache' },
|
|
572
|
+
}
|
|
573
|
+
},
|
|
565
574
|
// allow caching controller resources,
|
|
566
575
|
// https://cdn.jsdelivr.net/npm/@webxr-input-profiles/assets@1.0/dist/profiles/
|
|
567
|
-
{
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
576
|
+
{
|
|
577
|
+
...externalResourceCaching, ...{
|
|
578
|
+
urlPattern: /^https:\/\/cdn\.jsdelivr\.net\/npm\/@webxr-input-profiles\/assets@1\.0\/dist\/profiles\/.*/i,
|
|
579
|
+
handler: 'NetworkFirst',
|
|
580
|
+
options: { cacheName: 'webxr-controller-cache' },
|
|
581
|
+
}
|
|
582
|
+
},
|
|
572
583
|
// allow caching local resources
|
|
573
584
|
{
|
|
574
585
|
urlPattern: ({ url }) => url,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/* eslint-disable */
|
|
2
2
|
import { TypeStore } from "./../engine_typestore.js"
|
|
3
|
-
|
|
3
|
+
|
|
4
4
|
// Import types
|
|
5
5
|
import { AlignmentConstraint } from "../../engine-components/AlignmentConstraint.js";
|
|
6
6
|
import { Animation } from "../../engine-components/Animation.js";
|
|
@@ -220,7 +220,7 @@ import { XRFlag } from "../../engine-components/webxr/XRFlag.js";
|
|
|
220
220
|
import { PlayerSync } from "../../engine-components-experimental/networking/PlayerSync.js";
|
|
221
221
|
import { PlayerState } from "../../engine-components-experimental/networking/PlayerSync.js";
|
|
222
222
|
import { PresentationMode } from "../../engine-components-experimental/Presentation.js";
|
|
223
|
-
|
|
223
|
+
|
|
224
224
|
// Register types
|
|
225
225
|
TypeStore.add("AlignmentConstraint", AlignmentConstraint);
|
|
226
226
|
TypeStore.add("Animation", Animation);
|
|
@@ -1468,10 +1468,14 @@ export class Context implements IContext {
|
|
|
1468
1468
|
return true;
|
|
1469
1469
|
}
|
|
1470
1470
|
|
|
1471
|
+
private _contextRestoreTries = 0;
|
|
1471
1472
|
private handleRendererContextLost() {
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
this.
|
|
1473
|
+
// Try to restore the context every x frames
|
|
1474
|
+
if (this.time.frame % 10 && this.renderer.getContext().isContextLost()) {
|
|
1475
|
+
if (this._contextRestoreTries++ < 100) {
|
|
1476
|
+
console.warn("Attempting to recover WebGL context...");
|
|
1477
|
+
this.renderer.forceContextRestore();
|
|
1478
|
+
}
|
|
1475
1479
|
}
|
|
1476
1480
|
}
|
|
1477
1481
|
|
|
@@ -23,26 +23,22 @@ function ensureLoaders() {
|
|
|
23
23
|
export function setDracoDecoderPath(path: string | undefined) {
|
|
24
24
|
if (path !== undefined && typeof path === "string") {
|
|
25
25
|
setDracoDecoderLocation(path);
|
|
26
|
-
const loaders = ensureLoaders();
|
|
27
|
-
if (debug) console.log("Setting draco decoder path to", path);
|
|
28
|
-
loaders.dracoLoader.setDecoderPath(path);
|
|
29
26
|
}
|
|
30
27
|
}
|
|
31
28
|
|
|
32
29
|
export function setDracoDecoderType(type: string | undefined) {
|
|
33
30
|
if (type !== undefined && typeof type === "string") {
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
31
|
+
if (type !== "js") {
|
|
32
|
+
const loaders = ensureLoaders();
|
|
33
|
+
if (debug) console.log("Setting draco decoder type to", type);
|
|
34
|
+
loaders.dracoLoader.setDecoderConfig({ type: type });
|
|
35
|
+
}
|
|
37
36
|
}
|
|
38
37
|
}
|
|
39
38
|
|
|
40
39
|
export function setKtx2TranscoderPath(path: string) {
|
|
41
40
|
if (path !== undefined && typeof path === "string") {
|
|
42
41
|
setKTX2TranscoderLocation(path);
|
|
43
|
-
const loaders = ensureLoaders();
|
|
44
|
-
if (debug) console.log("Setting ktx2 transcoder path to", path);
|
|
45
|
-
loaders.ktx2Loader.setTranscoderPath(path);
|
|
46
42
|
}
|
|
47
43
|
}
|
|
48
44
|
|
|
@@ -78,7 +74,7 @@ export function addDracoAndKTX2Loaders(loader: GLTFLoader, context: Pick<Context
|
|
|
78
74
|
if (!(loader as any).meshoptDecoder)
|
|
79
75
|
loader.setMeshoptDecoder(loaders.meshoptDecoder);
|
|
80
76
|
|
|
81
|
-
configureLoader(loader, {
|
|
77
|
+
configureLoader(loader, {
|
|
82
78
|
progressive: true,
|
|
83
79
|
});
|
|
84
80
|
|
|
@@ -710,7 +710,7 @@ export class RapierPhysics implements IPhysicsEngine {
|
|
|
710
710
|
positions = this._meshCache.get(key)!;
|
|
711
711
|
}
|
|
712
712
|
else {
|
|
713
|
-
if (debugPhysics || isDevEnvironment()) console.
|
|
713
|
+
if (debugPhysics || isDevEnvironment()) console.debug(`[Performance] Your MeshCollider \"${collider.name}\" is scaled: consider applying the scale to the collider mesh instead (${scale.x}, ${scale.y}, ${scale.z})`);
|
|
714
714
|
const scaledPositions = new Float32Array(positions.length);
|
|
715
715
|
for (let i = 0; i < positions.length; i += 3) {
|
|
716
716
|
scaledPositions[i] = positions[i] * scale.x;
|
|
@@ -1,13 +1,14 @@
|
|
|
1
|
-
import { Box3Helper, Object3D, PerspectiveCamera, Ray, Vector2, Vector3, Vector3Like } from "three";
|
|
1
|
+
import { Box3Helper, Euler, Object3D, PerspectiveCamera, Quaternion, Ray, Vector2, Vector3, Vector3Like } from "three";
|
|
2
2
|
import { OrbitControls as ThreeOrbitControls } from "three/examples/jsm/controls/OrbitControls.js";
|
|
3
3
|
|
|
4
|
+
import { isDevEnvironment } from "../engine/debug/index.js";
|
|
4
5
|
import { setCameraController } from "../engine/engine_camera.js";
|
|
5
6
|
import { Gizmos } from "../engine/engine_gizmos.js";
|
|
6
7
|
import { InputEventQueue, NEPointerEvent } from "../engine/engine_input.js";
|
|
7
8
|
import { Mathf } from "../engine/engine_math.js";
|
|
8
9
|
import { RaycastOptions } from "../engine/engine_physics.js";
|
|
9
10
|
import { serializable } from "../engine/engine_serialization_decorator.js";
|
|
10
|
-
import { getBoundingBox, getWorldDirection, getWorldPosition, getWorldRotation, setWorldRotation } from "../engine/engine_three_utils.js";
|
|
11
|
+
import { getBoundingBox, getTempVector, getWorldDirection, getWorldPosition, getWorldRotation, setWorldRotation } from "../engine/engine_three_utils.js";
|
|
11
12
|
import type { ICameraController } from "../engine/engine_types.js";
|
|
12
13
|
import { DeviceUtilities, getParam } from "../engine/engine_utils.js";
|
|
13
14
|
import { Camera } from "./Camera.js";
|
|
@@ -616,14 +617,38 @@ export class OrbitControls extends Behaviour implements ICameraController {
|
|
|
616
617
|
|
|
617
618
|
|
|
618
619
|
/**
|
|
619
|
-
* Sets camera target position and look direction
|
|
620
|
+
* Sets camera target position and look direction using a raycast in forward direction of the object.
|
|
621
|
+
*
|
|
622
|
+
* @param source The object to raycast from. If a camera is passed in the camera position will be used as the source.
|
|
623
|
+
* @param immediateOrDuration If true the camera target will move immediately to the new position, otherwise it will lerp. If a number is passed in it will be used as the duration of the lerp.
|
|
624
|
+
*
|
|
625
|
+
* This is useful for example if you want to align your camera with an object in your scene (or another camera). Simply pass in this other camera object
|
|
626
|
+
* @returns true if the target was set successfully
|
|
620
627
|
*/
|
|
621
|
-
public setCameraAndLookTarget(
|
|
622
|
-
if (!
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
628
|
+
public setCameraAndLookTarget(source: Object3D | Camera, immediateOrDuration: number | boolean = false): boolean {
|
|
629
|
+
if (!source) {
|
|
630
|
+
if (isDevEnvironment()) console.warn("[OrbitControls] setCameraAndLookTarget target is null");
|
|
631
|
+
return false;
|
|
632
|
+
}
|
|
633
|
+
if (!(source instanceof Object3D) && !(source instanceof Camera)) {
|
|
634
|
+
if (isDevEnvironment()) console.warn("[OrbitControls] setCameraAndLookTarget target is not an Object3D or Camera");
|
|
635
|
+
return false;
|
|
636
|
+
}
|
|
637
|
+
if (source instanceof Camera) {
|
|
638
|
+
source = source.gameObject;
|
|
639
|
+
}
|
|
640
|
+
const worldPosition = source.worldPosition;
|
|
641
|
+
const forward = source.worldForward;
|
|
642
|
+
const ray = new Ray(worldPosition, forward.multiplyScalar(-1));
|
|
643
|
+
|
|
644
|
+
if (debug) Gizmos.DrawRay(ray.origin, ray.direction, 0xff0000, 10);
|
|
645
|
+
|
|
646
|
+
if (!this.setTargetFromRaycast(ray, immediateOrDuration)) {
|
|
647
|
+
this.setLookTargetPosition(ray.at(2, getTempVector()), immediateOrDuration);
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
this.setCameraTargetPosition(worldPosition, immediateOrDuration);
|
|
651
|
+
return true;
|
|
627
652
|
}
|
|
628
653
|
|
|
629
654
|
/** Moves the camera to position smoothly.
|
|
@@ -653,6 +678,38 @@ export class OrbitControls extends Behaviour implements ICameraController {
|
|
|
653
678
|
else this._cameraLerpDuration = this.targetLerpDuration;
|
|
654
679
|
}
|
|
655
680
|
}
|
|
681
|
+
// public setCameraTargetRotation(rotation: Vector3 | Euler | Quaternion, immediateOrDuration: boolean | number = false): void {
|
|
682
|
+
// if (!this._cameraObject) return;
|
|
683
|
+
|
|
684
|
+
// if (typeof immediateOrDuration === "boolean") immediateOrDuration = immediateOrDuration ? 0 : this.targetLerpDuration;
|
|
685
|
+
|
|
686
|
+
// const ray = new Ray(this._cameraObject.worldPosition, getTempVector(0, 0, 1));
|
|
687
|
+
|
|
688
|
+
// // if the camera is in the middle of lerping we use the end position for the raycast
|
|
689
|
+
// if (immediateOrDuration > 0 && this._cameraEndPosition && this._cameraLerpActive) {
|
|
690
|
+
// ray.origin = getTempVector(this._cameraEndPosition)
|
|
691
|
+
// }
|
|
692
|
+
|
|
693
|
+
// if (rotation instanceof Vector3) {
|
|
694
|
+
// rotation = new Euler().setFromVector3(rotation);
|
|
695
|
+
// }
|
|
696
|
+
// if (rotation instanceof Euler) {
|
|
697
|
+
// rotation = new Quaternion().setFromEuler(rotation);
|
|
698
|
+
// }
|
|
699
|
+
|
|
700
|
+
// ray.direction.applyQuaternion(rotation);
|
|
701
|
+
// ray.direction.multiplyScalar(-1);
|
|
702
|
+
|
|
703
|
+
// const hits = this.context.physics.raycastFromRay(ray);
|
|
704
|
+
|
|
705
|
+
// if (hits.length > 0) {
|
|
706
|
+
// this.setCameraTargetPosition(hits[0].point, immediateOrDuration);
|
|
707
|
+
// }
|
|
708
|
+
// else {
|
|
709
|
+
// this.setLookTargetPosition(ray.at(2, getTempVector()));
|
|
710
|
+
// }
|
|
711
|
+
// }
|
|
712
|
+
|
|
656
713
|
/** True while the camera position is being lerped */
|
|
657
714
|
get cameraLerpActive() { return this._cameraLerpActive; }
|
|
658
715
|
/** Call to stop camera position lerping */
|
|
@@ -747,8 +804,8 @@ export class OrbitControls extends Behaviour implements ICameraController {
|
|
|
747
804
|
else this._controls.target.lerp(position, delta);
|
|
748
805
|
}
|
|
749
806
|
|
|
750
|
-
private setTargetFromRaycast(ray?: Ray) {
|
|
751
|
-
if (!this.controls) return;
|
|
807
|
+
private setTargetFromRaycast(ray?: Ray, immediateOrDuration: number | boolean = false): boolean {
|
|
808
|
+
if (!this.controls) return false;
|
|
752
809
|
const rc = ray ? this.context.physics.raycastFromRay(ray) : this.context.physics.raycast();
|
|
753
810
|
for (const hit of rc) {
|
|
754
811
|
if (hit.distance > 0 && GameObject.isActiveInHierarchy(hit.object)) {
|
|
@@ -760,18 +817,11 @@ export class OrbitControls extends Behaviour implements ICameraController {
|
|
|
760
817
|
break;
|
|
761
818
|
}
|
|
762
819
|
}
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
// if (this.context.mainCamera) {
|
|
767
|
-
// const pos = getWorldPosition(this.context.mainCamera);
|
|
768
|
-
// const cameraTarget = pos.clone().sub(this.controls.target).add(this._lookTargetEndPosition);
|
|
769
|
-
// this._cameraObject?.parent?.worldToLocal(cameraTarget);
|
|
770
|
-
// this.setCameraTargetPosition(cameraTarget);
|
|
771
|
-
// }
|
|
772
|
-
break;
|
|
820
|
+
this.setLookTargetPosition(hit.point, immediateOrDuration);
|
|
821
|
+
return true;
|
|
773
822
|
}
|
|
774
823
|
}
|
|
824
|
+
return false;
|
|
775
825
|
}
|
|
776
826
|
|
|
777
827
|
// Adapted from https://discourse.threejs.org/t/camera-zoom-to-fit-object/936/24
|
|
@@ -18,6 +18,15 @@ export class InputField extends Behaviour implements IPointerEventHandler {
|
|
|
18
18
|
get text(): string {
|
|
19
19
|
return this.textComponent?.text ?? "";
|
|
20
20
|
}
|
|
21
|
+
set text(value: string) {
|
|
22
|
+
if (this.textComponent) {
|
|
23
|
+
this.textComponent.text = value;
|
|
24
|
+
if (this.placeholder) {
|
|
25
|
+
if (value.length > 0) this.placeholder.gameObject.visible = false;
|
|
26
|
+
else this.placeholder.gameObject.visible = true;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
21
30
|
|
|
22
31
|
get isFocused() {
|
|
23
32
|
return InputField.active === this;
|