@needle-tools/engine 4.14.0-next.52fdb13 → 4.14.0
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/components.needle.json +1 -1
- package/dist/{needle-engine.bundle-DJE-Bjpa.umd.cjs → needle-engine.bundle-COL2Bar3.umd.cjs} +120 -128
- package/dist/{needle-engine.bundle-BwfaInTa.min.js → needle-engine.bundle-NolzHLqO.min.js} +129 -137
- package/dist/{needle-engine.bundle-TmE5-_na.js → needle-engine.bundle-Z_gAD7Kg.js} +5214 -5459
- package/dist/needle-engine.d.ts +42 -299
- package/dist/needle-engine.js +569 -570
- package/dist/needle-engine.min.js +1 -1
- package/dist/needle-engine.umd.cjs +1 -1
- package/lib/engine/engine_context.d.ts +0 -2
- package/lib/engine/engine_context.js +0 -7
- package/lib/engine/engine_context.js.map +1 -1
- package/lib/engine/engine_materialpropertyblock.d.ts +4 -90
- package/lib/engine/engine_materialpropertyblock.js +7 -97
- package/lib/engine/engine_materialpropertyblock.js.map +1 -1
- package/lib/engine/engine_math.d.ts +1 -34
- package/lib/engine/engine_math.js +1 -34
- package/lib/engine/engine_math.js.map +1 -1
- package/lib/engine/engine_networking.js +1 -1
- package/lib/engine/engine_networking.js.map +1 -1
- package/lib/engine/engine_types.d.ts +0 -2
- package/lib/engine/engine_types.js +0 -2
- package/lib/engine/engine_types.js.map +1 -1
- package/lib/engine/webcomponents/icons.js +0 -3
- package/lib/engine/webcomponents/icons.js.map +1 -1
- package/lib/engine/webcomponents/logo-element.d.ts +0 -1
- package/lib/engine/webcomponents/logo-element.js +1 -3
- package/lib/engine/webcomponents/logo-element.js.map +1 -1
- package/lib/engine/webcomponents/needle-button.d.ts +11 -37
- package/lib/engine/webcomponents/needle-button.js +11 -42
- package/lib/engine/webcomponents/needle-button.js.map +1 -1
- package/lib/engine/webcomponents/needle-engine.d.ts +2 -10
- package/lib/engine/webcomponents/needle-engine.js +3 -13
- package/lib/engine/webcomponents/needle-engine.js.map +1 -1
- package/lib/engine-components/Component.d.ts +2 -1
- package/lib/engine-components/Component.js +2 -1
- package/lib/engine-components/Component.js.map +1 -1
- package/lib/engine-components/DragControls.d.ts +0 -1
- package/lib/engine-components/DragControls.js +0 -21
- package/lib/engine-components/DragControls.js.map +1 -1
- package/lib/engine-components/NeedleMenu.d.ts +0 -2
- package/lib/engine-components/NeedleMenu.js +0 -2
- package/lib/engine-components/NeedleMenu.js.map +1 -1
- package/lib/engine-components/Networking.d.ts +3 -28
- package/lib/engine-components/Networking.js +3 -28
- package/lib/engine-components/Networking.js.map +1 -1
- package/lib/engine-components/ReflectionProbe.d.ts +0 -1
- package/lib/engine-components/ReflectionProbe.js +2 -20
- package/lib/engine-components/ReflectionProbe.js.map +1 -1
- package/lib/engine-components/export/usdz/extensions/behavior/BehaviourComponents.d.ts +0 -15
- package/lib/engine-components/export/usdz/extensions/behavior/BehaviourComponents.js +0 -77
- package/lib/engine-components/export/usdz/extensions/behavior/BehaviourComponents.js.map +1 -1
- package/lib/engine-components/ui/Button.d.ts +0 -1
- package/lib/engine-components/ui/Button.js +0 -11
- package/lib/engine-components/ui/Button.js.map +1 -1
- package/lib/engine-components/ui/Text.d.ts +0 -1
- package/lib/engine-components/ui/Text.js +0 -11
- package/lib/engine-components/ui/Text.js.map +1 -1
- package/package.json +2 -2
- package/src/engine/engine_context.ts +0 -9
- package/src/engine/engine_materialpropertyblock.ts +11 -102
- package/src/engine/engine_math.ts +1 -34
- package/src/engine/engine_networking.ts +1 -1
- package/src/engine/engine_types.ts +0 -5
- package/src/engine/webcomponents/icons.ts +0 -3
- package/src/engine/webcomponents/logo-element.ts +1 -4
- package/src/engine/webcomponents/needle-button.ts +13 -44
- package/src/engine/webcomponents/needle-engine.ts +7 -18
- package/src/engine-components/Component.ts +3 -1
- package/src/engine-components/DragControls.ts +4 -29
- package/src/engine-components/NeedleMenu.ts +3 -5
- package/src/engine-components/Networking.ts +4 -29
- package/src/engine-components/ReflectionProbe.ts +2 -21
- package/src/engine-components/export/usdz/extensions/behavior/BehaviourComponents.ts +32 -108
- package/src/engine-components/ui/Button.ts +0 -12
- package/src/engine-components/ui/Text.ts +0 -13
- package/lib/engine/engine_accessibility.d.ts +0 -58
- package/lib/engine/engine_accessibility.js +0 -143
- package/lib/engine/engine_accessibility.js.map +0 -1
- package/src/engine/engine_accessibility.ts +0 -178
|
@@ -13,11 +13,12 @@ import { AudioSource } from "../../../../AudioSource.js";
|
|
|
13
13
|
import { Behaviour, GameObject } from "../../../../Component.js";
|
|
14
14
|
import { Rigidbody } from "../../../../RigidBody.js";
|
|
15
15
|
import type { IPointerClickHandler, PointerEventData } from "../../../../ui/PointerEvents.js";
|
|
16
|
-
|
|
16
|
+
|
|
17
|
+
import { makeNameSafeForUSD,USDDocument, USDObject, USDZExporterContext } from "../../ThreeUSDZExporter.js";
|
|
17
18
|
import { AnimationExtension, RegisteredAnimationInfo, type UsdzAnimation } from "../Animation.js";
|
|
18
19
|
import { AudioExtension } from "./AudioExtension.js";
|
|
19
20
|
import type { BehaviorExtension, UsdzBehaviour } from "./Behaviour.js";
|
|
20
|
-
import { ActionBuilder, ActionModel, BehaviorModel, EmphasizeActionMotionType,
|
|
21
|
+
import { ActionBuilder, ActionModel, BehaviorModel, EmphasizeActionMotionType,GroupActionModel,type IBehaviorElement, Target, TriggerBuilder } from "./BehavioursBuilder.js";
|
|
21
22
|
|
|
22
23
|
const debug = getParam("debugusdzbehaviours");
|
|
23
24
|
|
|
@@ -56,22 +57,6 @@ export class ChangeTransformOnClick extends Behaviour implements IPointerClickHa
|
|
|
56
57
|
private targetRot = new Quaternion();
|
|
57
58
|
private targetScale = new Vector3();
|
|
58
59
|
|
|
59
|
-
onEnable(): void {
|
|
60
|
-
this.context.accessibility.updateElement(this, {
|
|
61
|
-
role: "button",
|
|
62
|
-
label: "Move " + (this.object?.name || "object") + " to " + (this.target?.name || "target") + " on click",
|
|
63
|
-
hidden: false,
|
|
64
|
-
})
|
|
65
|
-
}
|
|
66
|
-
onDisable(): void {
|
|
67
|
-
this.context.accessibility.updateElement(this, {
|
|
68
|
-
hidden: true,
|
|
69
|
-
});
|
|
70
|
-
}
|
|
71
|
-
onDestroy(): void {
|
|
72
|
-
this.context.accessibility.removeElement(this);
|
|
73
|
-
}
|
|
74
|
-
|
|
75
60
|
onPointerEnter() {
|
|
76
61
|
this.context.input.setCursor("pointer");
|
|
77
62
|
}
|
|
@@ -83,8 +68,8 @@ export class ChangeTransformOnClick extends Behaviour implements IPointerClickHa
|
|
|
83
68
|
|
|
84
69
|
const rbs = this.object?.getComponentsInChildren(Rigidbody);
|
|
85
70
|
|
|
86
|
-
if (rbs)
|
|
87
|
-
for (const rb of rbs) {
|
|
71
|
+
if (rbs){
|
|
72
|
+
for (const rb of rbs) {
|
|
88
73
|
rb.resetVelocities();
|
|
89
74
|
rb.resetForcesAndTorques();
|
|
90
75
|
}
|
|
@@ -242,22 +227,6 @@ export class ChangeMaterialOnClick extends Behaviour implements IPointerClickHan
|
|
|
242
227
|
}
|
|
243
228
|
}
|
|
244
229
|
|
|
245
|
-
onEnable(): void {
|
|
246
|
-
this.context.accessibility.updateElement(this, {
|
|
247
|
-
role: "button",
|
|
248
|
-
label: "Change material to " + (this.variantMaterial?.name || "unknown material"),
|
|
249
|
-
hidden: false,
|
|
250
|
-
})
|
|
251
|
-
}
|
|
252
|
-
onDisable(): void {
|
|
253
|
-
this.context.accessibility.updateElement(this, {
|
|
254
|
-
hidden: true,
|
|
255
|
-
});
|
|
256
|
-
}
|
|
257
|
-
onDestroy(): void {
|
|
258
|
-
this.context.accessibility.removeElement(this);
|
|
259
|
-
}
|
|
260
|
-
|
|
261
230
|
onPointerEnter(_args: PointerEventData) {
|
|
262
231
|
this.context.input.setCursor("pointer");
|
|
263
232
|
}
|
|
@@ -458,7 +427,7 @@ export class SetActiveOnClick extends Behaviour implements IPointerClickHandler,
|
|
|
458
427
|
|
|
459
428
|
if (!this.toggleOnClick && this.hideSelf)
|
|
460
429
|
this.gameObject.visible = false;
|
|
461
|
-
|
|
430
|
+
|
|
462
431
|
if (this.target)
|
|
463
432
|
this.target.visible = this.toggleOnClick ? !this.target.visible : this.targetState;
|
|
464
433
|
}
|
|
@@ -484,7 +453,7 @@ export class SetActiveOnClick extends Behaviour implements IPointerClickHandler,
|
|
|
484
453
|
|
|
485
454
|
beforeCreateDocument() {
|
|
486
455
|
if (!this.target) return;
|
|
487
|
-
|
|
456
|
+
|
|
488
457
|
// need to cache on the object itself, because otherwise different actions would override each other's visibility state
|
|
489
458
|
// TODO would probably be better to have this somewhere on the exporter, not on this component
|
|
490
459
|
if (this.gameObject[SetActiveOnClick.wasVisible] === undefined)
|
|
@@ -545,7 +514,7 @@ export class SetActiveOnClick extends Behaviour implements IPointerClickHandler,
|
|
|
545
514
|
// It's much easier to reason about nested actions when we're not duplicating tons of hierarchy...
|
|
546
515
|
// We can probably only do a shallow clone when the tapped object has geometry of its own, otherwise
|
|
547
516
|
// we end up with nothing to tap on.
|
|
548
|
-
|
|
517
|
+
|
|
549
518
|
// Option A: we deep clone ourselves. This makes hierarchical cases and nested behaviours really complex.
|
|
550
519
|
// We do this currently when the object doesn't have any geometry.
|
|
551
520
|
if (!this.selfModelClone.geometry) {
|
|
@@ -564,11 +533,11 @@ export class SetActiveOnClick extends Behaviour implements IPointerClickHandler,
|
|
|
564
533
|
clone.name += "_toggle" + (SetActiveOnClick.clonedToggleIndex++);
|
|
565
534
|
originalModel.add(clone);
|
|
566
535
|
this.gameObject[SetActiveOnClick.toggleClone] = clone;
|
|
567
|
-
|
|
536
|
+
|
|
568
537
|
console.warn("USDZExport: Toggle " + this.gameObject.name + " doesn't have geometry. It will be deep cloned and nested behaviours will likely not work.");
|
|
569
538
|
}
|
|
570
539
|
const clonedSelfModel = this.gameObject[SetActiveOnClick.toggleClone];
|
|
571
|
-
|
|
540
|
+
|
|
572
541
|
if (!this.gameObject[SetActiveOnClick.reverseToggleClone]) {
|
|
573
542
|
const clone = this.selfModelClone.clone();
|
|
574
543
|
clone.setMatrix(new Matrix4());
|
|
@@ -620,7 +589,7 @@ export class SetActiveOnClick extends Behaviour implements IPointerClickHandler,
|
|
|
620
589
|
TriggerBuilder.tapTrigger(selfModel),
|
|
621
590
|
ActionBuilder.parallel(...toggleSequence)
|
|
622
591
|
));
|
|
623
|
-
|
|
592
|
+
|
|
624
593
|
const reverseSequence: ActionModel[] = [];
|
|
625
594
|
reverseSequence.push(ActionBuilder.fadeAction(this.toggleModel, 0, false));
|
|
626
595
|
reverseSequence.push(ActionBuilder.fadeAction(selfModel, 0, true));
|
|
@@ -711,7 +680,7 @@ export class HideOnStart extends Behaviour implements UsdzBehaviour {
|
|
|
711
680
|
}
|
|
712
681
|
|
|
713
682
|
private wasVisible: boolean = false;
|
|
714
|
-
|
|
683
|
+
|
|
715
684
|
beforeCreateDocument() {
|
|
716
685
|
this.wasVisible = GameObject.isActiveSelf(this.gameObject);
|
|
717
686
|
}
|
|
@@ -744,22 +713,6 @@ export class EmphasizeOnClick extends Behaviour implements UsdzBehaviour {
|
|
|
744
713
|
@serializable()
|
|
745
714
|
motionType: EmphasizeActionMotionType = "bounce";
|
|
746
715
|
|
|
747
|
-
onEnable(): void {
|
|
748
|
-
this.context.accessibility.updateElement(this, {
|
|
749
|
-
role: "button",
|
|
750
|
-
label: "Emphasize " + this.target?.name + " on click",
|
|
751
|
-
hidden: false,
|
|
752
|
-
})
|
|
753
|
-
}
|
|
754
|
-
onDisable(): void {
|
|
755
|
-
this.context.accessibility.updateElement(this, {
|
|
756
|
-
hidden: true,
|
|
757
|
-
});
|
|
758
|
-
}
|
|
759
|
-
onDestroy(): void {
|
|
760
|
-
this.context.accessibility.removeElement(this);
|
|
761
|
-
}
|
|
762
|
-
|
|
763
716
|
beforeCreateDocument() { }
|
|
764
717
|
|
|
765
718
|
createBehaviours(ext, model, _context) {
|
|
@@ -822,21 +775,6 @@ export class PlayAudioOnClick extends Behaviour implements IPointerClickHandler,
|
|
|
822
775
|
}
|
|
823
776
|
}
|
|
824
777
|
|
|
825
|
-
onEnable(): void {
|
|
826
|
-
this.context.accessibility.updateElement(this, {
|
|
827
|
-
role: "button",
|
|
828
|
-
label: "Play audio: " + (this.clip || this.target?.clip || "unknown clip"),
|
|
829
|
-
hidden: false,
|
|
830
|
-
})
|
|
831
|
-
}
|
|
832
|
-
onDisable(): void {
|
|
833
|
-
this.context.accessibility.updateElement(this, {
|
|
834
|
-
hidden: true,
|
|
835
|
-
});
|
|
836
|
-
}
|
|
837
|
-
onDestroy(): void {
|
|
838
|
-
this.context.accessibility.removeElement(this);
|
|
839
|
-
}
|
|
840
778
|
|
|
841
779
|
onPointerEnter() {
|
|
842
780
|
this.context.input.setCursor("pointer");
|
|
@@ -878,7 +816,7 @@ export class PlayAudioOnClick extends Behaviour implements IPointerClickHandler,
|
|
|
878
816
|
const clipName = AudioExtension.getName(clipUrl);
|
|
879
817
|
const volume = this.target ? this.target.volume : 1;
|
|
880
818
|
const auralMode = this.target && this.target.spatialBlend == 0 ? "nonSpatial" : "spatial";
|
|
881
|
-
|
|
819
|
+
|
|
882
820
|
// This checks if any child is clickable – if yes, the tap trigger is added; if not, we omit it.
|
|
883
821
|
let anyChildHasGeometry = false;
|
|
884
822
|
this.gameObject.traverse(c => {
|
|
@@ -887,7 +825,7 @@ export class PlayAudioOnClick extends Behaviour implements IPointerClickHandler,
|
|
|
887
825
|
// Workaround: seems iOS often simply doesn't play audio on scene start when this is NOT present.
|
|
888
826
|
// unclear why, but having a useless tap action (nothing to tap on) "fixes" it.
|
|
889
827
|
anyChildHasGeometry = true;
|
|
890
|
-
|
|
828
|
+
|
|
891
829
|
const audioClip = ext.addAudioClip(clipUrl);
|
|
892
830
|
// const stopAction: IBehaviorElement = ActionBuilder.playAudioAction(playbackTarget, audioClip, "stop", volume, auralMode);
|
|
893
831
|
let playAction: IBehaviorElement = ActionBuilder.playAudioAction(playbackTarget, audioClip, "play", volume, auralMode);
|
|
@@ -896,7 +834,8 @@ export class PlayAudioOnClick extends Behaviour implements IPointerClickHandler,
|
|
|
896
834
|
|
|
897
835
|
const behaviorName = (this.name ? "_" + this.name : "");
|
|
898
836
|
|
|
899
|
-
if (anyChildHasGeometry && this.trigger === "tap")
|
|
837
|
+
if (anyChildHasGeometry && this.trigger === "tap")
|
|
838
|
+
{
|
|
900
839
|
// does not seem to work in iOS / QuickLook...
|
|
901
840
|
// TODO use play "type" which can be start/stop/pause
|
|
902
841
|
if (this.toggleOnClick) (playAction as ActionModel).multiplePerformOperation = "stop";
|
|
@@ -962,25 +901,9 @@ export class PlayAnimationOnClick extends Behaviour implements IPointerClickHand
|
|
|
962
901
|
|
|
963
902
|
private get target() { return this.animator?.gameObject || this.animation?.gameObject }
|
|
964
903
|
|
|
965
|
-
onEnable(): void {
|
|
966
|
-
this.context.accessibility.updateElement(this, {
|
|
967
|
-
role: "button",
|
|
968
|
-
label: "Plays animation " + (this.stateName || "") + " on " + (this.target ? this.target.name : ""),
|
|
969
|
-
hidden: false
|
|
970
|
-
});
|
|
971
|
-
}
|
|
972
|
-
onDisable(): void {
|
|
973
|
-
this.context.accessibility.updateElement(this, {
|
|
974
|
-
hidden: true,
|
|
975
|
-
});
|
|
976
|
-
}
|
|
977
|
-
onDestroy(): void {
|
|
978
|
-
this.context.accessibility.removeElement(this);
|
|
979
|
-
}
|
|
980
904
|
|
|
981
905
|
onPointerEnter() {
|
|
982
906
|
this.context.input.setCursor("pointer");
|
|
983
|
-
this.context.accessibility.hover(this, "Click to play animation " + (this.stateName || "") + " on " + (this.target ? this.target.name : ""));
|
|
984
907
|
}
|
|
985
908
|
onPointerExit() {
|
|
986
909
|
this.context.input.unsetCursor("pointer");
|
|
@@ -989,7 +912,6 @@ export class PlayAnimationOnClick extends Behaviour implements IPointerClickHand
|
|
|
989
912
|
args.use();
|
|
990
913
|
if (!this.target) return;
|
|
991
914
|
if (this.stateName) {
|
|
992
|
-
this.context.accessibility.focus(this);
|
|
993
915
|
// TODO this is currently quite annoying to use,
|
|
994
916
|
// as for the web we use the Animator component and its states directly,
|
|
995
917
|
// while in QuickLook we use explicit animations / states.
|
|
@@ -1001,8 +923,8 @@ export class PlayAnimationOnClick extends Behaviour implements IPointerClickHand
|
|
|
1001
923
|
|
|
1002
924
|
private stateAnimationModel: any;
|
|
1003
925
|
|
|
1004
|
-
private animationSequence
|
|
1005
|
-
private animationLoopAfterSequence
|
|
926
|
+
private animationSequence? = new Array<RegisteredAnimationInfo>();
|
|
927
|
+
private animationLoopAfterSequence? = new Array<RegisteredAnimationInfo>();
|
|
1006
928
|
private randomOffsetNormalized: number = 0;
|
|
1007
929
|
|
|
1008
930
|
createBehaviours(_ext: BehaviorExtension, model: USDObject, _context: USDZExporterContext) {
|
|
@@ -1028,9 +950,9 @@ export class PlayAnimationOnClick extends Behaviour implements IPointerClickHand
|
|
|
1028
950
|
afterCreateDocument(ext: BehaviorExtension, context: USDZExporterContext) {
|
|
1029
951
|
if ((this.animationSequence === undefined && this.animationLoopAfterSequence === undefined) || !this.stateAnimationModel) return;
|
|
1030
952
|
if (!this.target) return;
|
|
1031
|
-
|
|
953
|
+
|
|
1032
954
|
const document = context.document;
|
|
1033
|
-
|
|
955
|
+
|
|
1034
956
|
// check if the AnimationExtension has been attached and what data it has for the current object
|
|
1035
957
|
const animationExt = context.extensions.find(ext => ext instanceof AnimationExtension) as AnimationExtension;
|
|
1036
958
|
if (!animationExt) return;
|
|
@@ -1044,7 +966,7 @@ export class PlayAnimationOnClick extends Behaviour implements IPointerClickHand
|
|
|
1044
966
|
if (requiresExclusivePlayback) {
|
|
1045
967
|
if (isDevEnvironment())
|
|
1046
968
|
console.warn("Setting exclusive playback for " + this.target.name + "@" + this.stateName + " because it has " + animationExt.getClipCount(this.target) + " animations. This works around QuickLook bug FB13410767.");
|
|
1047
|
-
|
|
969
|
+
|
|
1048
970
|
PlayAnimationOnClick.rootsWithExclusivePlayback.add(this.target);
|
|
1049
971
|
}
|
|
1050
972
|
|
|
@@ -1064,7 +986,7 @@ export class PlayAnimationOnClick extends Behaviour implements IPointerClickHand
|
|
|
1064
986
|
this.trigger == "tap" ? TriggerBuilder.tapTrigger(this.selfModel) : TriggerBuilder.sceneStartTrigger(),
|
|
1065
987
|
sequence
|
|
1066
988
|
);
|
|
1067
|
-
|
|
989
|
+
|
|
1068
990
|
// See comment above for why exclusive playback is currently required when playing multiple animations on the same root.
|
|
1069
991
|
if (requiresExclusivePlayback)
|
|
1070
992
|
playAnimationOnTap.makeExclusive(true);
|
|
@@ -1086,7 +1008,8 @@ export class PlayAnimationOnClick extends Behaviour implements IPointerClickHand
|
|
|
1086
1008
|
|
|
1087
1009
|
const sequence = ActionBuilder.sequence();
|
|
1088
1010
|
|
|
1089
|
-
if (animationSequence && animationSequence.length > 0)
|
|
1011
|
+
if (animationSequence && animationSequence.length > 0)
|
|
1012
|
+
{
|
|
1090
1013
|
for (const anim of animationSequence) {
|
|
1091
1014
|
sequence.addAction(getOrCacheAction(model, anim));
|
|
1092
1015
|
}
|
|
@@ -1110,10 +1033,11 @@ export class PlayAnimationOnClick extends Behaviour implements IPointerClickHand
|
|
|
1110
1033
|
return sequence;
|
|
1111
1034
|
}
|
|
1112
1035
|
|
|
1113
|
-
static getAndRegisterAnimationSequences(ext: AnimationExtension, target: GameObject, stateName?: string):
|
|
1114
|
-
|
|
1036
|
+
static getAndRegisterAnimationSequences(ext: AnimationExtension, target: GameObject, stateName?: string):
|
|
1037
|
+
{
|
|
1038
|
+
animationSequence: Array<RegisteredAnimationInfo>,
|
|
1115
1039
|
animationLoopAfterSequence: Array<RegisteredAnimationInfo>,
|
|
1116
|
-
randomTimeOffset: number,
|
|
1040
|
+
randomTimeOffset: number,
|
|
1117
1041
|
} | undefined {
|
|
1118
1042
|
|
|
1119
1043
|
if (!target) return undefined;
|
|
@@ -1131,14 +1055,14 @@ export class PlayAnimationOnClick extends Behaviour implements IPointerClickHand
|
|
|
1131
1055
|
let animationLoopAfterSequence: Array<RegisteredAnimationInfo> = [];
|
|
1132
1056
|
|
|
1133
1057
|
if (animation) {
|
|
1134
|
-
const anim = ext.registerAnimation(target, animation.clip);
|
|
1135
|
-
if (anim) {
|
|
1058
|
+
const anim = ext.registerAnimation(target, animation.clip);
|
|
1059
|
+
if (anim) {
|
|
1136
1060
|
if (animation.loop)
|
|
1137
1061
|
animationLoopAfterSequence.push(anim);
|
|
1138
1062
|
else
|
|
1139
1063
|
animationSequence.push(anim);
|
|
1140
1064
|
}
|
|
1141
|
-
|
|
1065
|
+
|
|
1142
1066
|
let randomTimeOffset = 0;
|
|
1143
1067
|
if (animation.minMaxOffsetNormalized) {
|
|
1144
1068
|
const from = animation.minMaxOffsetNormalized.x;
|
|
@@ -1232,7 +1156,7 @@ export class PlayAnimationOnClick extends Behaviour implements IPointerClickHand
|
|
|
1232
1156
|
if (lastClip) {
|
|
1233
1157
|
let clipCopy: AnimationClip | undefined;
|
|
1234
1158
|
if (ext.holdClipMap.has(lastClip)) {
|
|
1235
|
-
clipCopy = ext.holdClipMap.get(lastClip);
|
|
1159
|
+
clipCopy = ext.holdClipMap.get(lastClip);
|
|
1236
1160
|
}
|
|
1237
1161
|
else {
|
|
1238
1162
|
// We're creating a "hold" clip here; exactly 1 second long, and inteprolates exactly on the duration of the clip
|
|
@@ -180,8 +180,6 @@ export class Button extends Behaviour implements IPointerEventHandler {
|
|
|
180
180
|
this.onClick.invoke();
|
|
181
181
|
args.use();
|
|
182
182
|
|
|
183
|
-
this.context.accessibility.focus(this);
|
|
184
|
-
|
|
185
183
|
// debug clicks for WebXR
|
|
186
184
|
if (debug) {
|
|
187
185
|
const pos = this.gameObject.worldPosition;
|
|
@@ -240,19 +238,9 @@ export class Button extends Behaviour implements IPointerEventHandler {
|
|
|
240
238
|
|
|
241
239
|
onEnable() {
|
|
242
240
|
super.onEnable();
|
|
243
|
-
this.context.accessibility.updateElement(this, {
|
|
244
|
-
role: "button",
|
|
245
|
-
label: this.gameObject.name + " button",
|
|
246
|
-
hidden: false
|
|
247
|
-
});
|
|
248
|
-
}
|
|
249
|
-
onDisable() {
|
|
250
|
-
super.onDisable();
|
|
251
|
-
this.context.accessibility.updateElement(this, { hidden: true })
|
|
252
241
|
}
|
|
253
242
|
|
|
254
243
|
onDestroy(): void {
|
|
255
|
-
this.context.accessibility.removeElement(this);
|
|
256
244
|
if (this._isHovered) this.context.input.unsetCursor("pointer");
|
|
257
245
|
}
|
|
258
246
|
|
|
@@ -102,7 +102,6 @@ export class Text extends Graphic implements IHasAlphaFactor, ICanvasEventReceiv
|
|
|
102
102
|
this._text = val;
|
|
103
103
|
this.feedText(this.text, this.supportRichText);
|
|
104
104
|
this.markDirty();
|
|
105
|
-
this.context.accessibility.updateElement(this, { label: this.text });
|
|
106
105
|
}
|
|
107
106
|
}
|
|
108
107
|
|
|
@@ -239,13 +238,6 @@ export class Text extends Graphic implements IHasAlphaFactor, ICanvasEventReceiv
|
|
|
239
238
|
|
|
240
239
|
onEnable(): void {
|
|
241
240
|
super.onEnable();
|
|
242
|
-
|
|
243
|
-
this.context.accessibility.updateElement(this, {
|
|
244
|
-
role: "text",
|
|
245
|
-
label: this.text,
|
|
246
|
-
hidden: false
|
|
247
|
-
});
|
|
248
|
-
|
|
249
241
|
this._didHandleTextRenderOnTop = false;
|
|
250
242
|
if (this.uiObject) {
|
|
251
243
|
// @ts-ignore
|
|
@@ -269,11 +261,6 @@ export class Text extends Graphic implements IHasAlphaFactor, ICanvasEventReceiv
|
|
|
269
261
|
onDisable(): void {
|
|
270
262
|
super.onDisable();
|
|
271
263
|
this.canvas?.unregisterEventReceiver(this);
|
|
272
|
-
this.context.accessibility.updateElement(this, { hidden: true });
|
|
273
|
-
}
|
|
274
|
-
onDestroy(): void {
|
|
275
|
-
super.onDestroy();
|
|
276
|
-
this.context.accessibility.removeElement(this);
|
|
277
264
|
}
|
|
278
265
|
|
|
279
266
|
private getAlignment(opts: ThreeMeshUIEveryOptions): ThreeMeshUIEveryOptions {
|
|
@@ -1,58 +0,0 @@
|
|
|
1
|
-
import { Object3D } from "three";
|
|
2
|
-
import type { Context } from "./engine_setup";
|
|
3
|
-
import { IComponent } from "./engine_types";
|
|
4
|
-
/** Data describing the accessible semantics for a 3D object or component. */
|
|
5
|
-
type AccessibilityData = {
|
|
6
|
-
/** ARIA role (e.g. `"button"`, `"img"`, `"region"`). */
|
|
7
|
-
role: string;
|
|
8
|
-
/** Human-readable label announced by screen readers. */
|
|
9
|
-
label: string;
|
|
10
|
-
/** When `true`, the element is hidden from the accessibility tree. */
|
|
11
|
-
hidden?: boolean;
|
|
12
|
-
/** When `true`, indicates the element's content is being updated. */
|
|
13
|
-
busy?: boolean;
|
|
14
|
-
};
|
|
15
|
-
/**
|
|
16
|
-
* Manages an accessible, screen-reader-friendly overlay for a Needle Engine {@link Context}.
|
|
17
|
-
*
|
|
18
|
-
* The manager maintains a visually-hidden DOM tree that mirrors relevant 3D scene objects
|
|
19
|
-
* with appropriate ARIA roles and labels. It also provides a live region so that hover
|
|
20
|
-
* events in the 3D scene can be announced to assistive technology without stealing focus.
|
|
21
|
-
*/
|
|
22
|
-
export declare class AccessibilityManager {
|
|
23
|
-
private readonly context;
|
|
24
|
-
private static readonly _managers;
|
|
25
|
-
/** Returns the {@link AccessibilityManager} associated with the given context or component. */
|
|
26
|
-
static get(obj: Context | IComponent): AccessibilityManager | undefined;
|
|
27
|
-
constructor(context: Context);
|
|
28
|
-
private _enabled;
|
|
29
|
-
/** Enables or disables the accessibility overlay. When disabled, the overlay DOM is removed. */
|
|
30
|
-
set enabled(value: boolean);
|
|
31
|
-
/** Removes all tracked accessibility elements, keeping only the live region. */
|
|
32
|
-
clear(): void;
|
|
33
|
-
/** Removes the overlay from the DOM and unregisters this manager from the context. */
|
|
34
|
-
dispose(): void;
|
|
35
|
-
private readonly root;
|
|
36
|
-
private readonly liveRegion;
|
|
37
|
-
private readonly treeElements;
|
|
38
|
-
/**
|
|
39
|
-
* Creates or updates the accessible DOM element for a 3D object or component.
|
|
40
|
-
* @param obj - The scene object or component to represent.
|
|
41
|
-
* @param data - Partial accessibility data (role, label, hidden, busy) to apply.
|
|
42
|
-
*/
|
|
43
|
-
updateElement<T extends Object3D | IComponent>(obj: T, data: Partial<AccessibilityData>): void;
|
|
44
|
-
/** Moves keyboard focus to the accessible element representing the given object. */
|
|
45
|
-
focus<T extends Object3D | IComponent>(obj: T): void;
|
|
46
|
-
/** Removes keyboard focus from the accessible element representing the given object. */
|
|
47
|
-
unfocus<T extends Object3D | IComponent>(obj: T): void;
|
|
48
|
-
/**
|
|
49
|
-
* Announces a hover event to screen readers via the ARIA live region.
|
|
50
|
-
* @param obj - The hovered object (used to look up its label if `text` is not provided).
|
|
51
|
-
* @param text - Optional text to announce. Falls back to the element's `aria-label`.
|
|
52
|
-
*/
|
|
53
|
-
hover<T extends Object3D | IComponent>(obj: T, text?: string): void;
|
|
54
|
-
/** Removes the accessible DOM element for the given object and stops tracking it. */
|
|
55
|
-
removeElement(obj: Object3D | IComponent): void;
|
|
56
|
-
private set liveRegionMode(value);
|
|
57
|
-
}
|
|
58
|
-
export {};
|
|
@@ -1,143 +0,0 @@
|
|
|
1
|
-
import { isComponent } from "./engine_types";
|
|
2
|
-
/**
|
|
3
|
-
* Manages an accessible, screen-reader-friendly overlay for a Needle Engine {@link Context}.
|
|
4
|
-
*
|
|
5
|
-
* The manager maintains a visually-hidden DOM tree that mirrors relevant 3D scene objects
|
|
6
|
-
* with appropriate ARIA roles and labels. It also provides a live region so that hover
|
|
7
|
-
* events in the 3D scene can be announced to assistive technology without stealing focus.
|
|
8
|
-
*/
|
|
9
|
-
export class AccessibilityManager {
|
|
10
|
-
context;
|
|
11
|
-
static _managers = new WeakMap();
|
|
12
|
-
/** Returns the {@link AccessibilityManager} associated with the given context or component. */
|
|
13
|
-
static get(obj) {
|
|
14
|
-
if (isComponent(obj)) {
|
|
15
|
-
return this._managers.get(obj.context);
|
|
16
|
-
}
|
|
17
|
-
else {
|
|
18
|
-
return this._managers.get(obj);
|
|
19
|
-
}
|
|
20
|
-
}
|
|
21
|
-
constructor(context) {
|
|
22
|
-
this.context = context;
|
|
23
|
-
this.root.style.cssText = `
|
|
24
|
-
position: absolute;
|
|
25
|
-
width: 1px; height: 1px;
|
|
26
|
-
padding: 0; margin: -1px;
|
|
27
|
-
overflow: hidden;
|
|
28
|
-
clip: rect(0, 0, 0, 0);
|
|
29
|
-
white-space: nowrap;
|
|
30
|
-
border: 0;
|
|
31
|
-
`;
|
|
32
|
-
this.root.setAttribute("role", "region");
|
|
33
|
-
this.root.setAttribute("aria-label", "3D Needle Engine scene");
|
|
34
|
-
// Live region for announcing hovered 3D elements without stealing focus
|
|
35
|
-
this.liveRegion.setAttribute("aria-live", "polite");
|
|
36
|
-
this.liveRegion.setAttribute("aria-atomic", "true");
|
|
37
|
-
this.liveRegion.setAttribute("role", "status");
|
|
38
|
-
this.root.appendChild(this.liveRegion);
|
|
39
|
-
this.enabled = true;
|
|
40
|
-
}
|
|
41
|
-
_enabled;
|
|
42
|
-
/** Enables or disables the accessibility overlay. When disabled, the overlay DOM is removed. */
|
|
43
|
-
set enabled(value) {
|
|
44
|
-
if (value === this._enabled)
|
|
45
|
-
return;
|
|
46
|
-
this._enabled = value;
|
|
47
|
-
if (!value) {
|
|
48
|
-
this.root.remove();
|
|
49
|
-
}
|
|
50
|
-
else {
|
|
51
|
-
AccessibilityManager._managers.set(this.context, this);
|
|
52
|
-
const target = this.context.domElement.shadowRoot || this.context.domElement;
|
|
53
|
-
target.prepend(this.root);
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
/** Removes all tracked accessibility elements, keeping only the live region. */
|
|
57
|
-
clear() {
|
|
58
|
-
this.root.childNodes.forEach(child => child.remove());
|
|
59
|
-
this.root.appendChild(this.liveRegion);
|
|
60
|
-
}
|
|
61
|
-
/** Removes the overlay from the DOM and unregisters this manager from the context. */
|
|
62
|
-
dispose() {
|
|
63
|
-
this.root.remove();
|
|
64
|
-
AccessibilityManager._managers.delete(this.context);
|
|
65
|
-
}
|
|
66
|
-
root = document.createElement("div");
|
|
67
|
-
liveRegion = document.createElement("div");
|
|
68
|
-
treeElements = new WeakMap();
|
|
69
|
-
/**
|
|
70
|
-
* Creates or updates the accessible DOM element for a 3D object or component.
|
|
71
|
-
* @param obj - The scene object or component to represent.
|
|
72
|
-
* @param data - Partial accessibility data (role, label, hidden, busy) to apply.
|
|
73
|
-
*/
|
|
74
|
-
updateElement(obj, data) {
|
|
75
|
-
let el = this.treeElements.get(obj);
|
|
76
|
-
if (!el) {
|
|
77
|
-
el = document.createElement("div");
|
|
78
|
-
this.treeElements.set(obj, el);
|
|
79
|
-
this.root.appendChild(el);
|
|
80
|
-
let didSetRole = false;
|
|
81
|
-
if (typeof data === "object") {
|
|
82
|
-
if (data.role) {
|
|
83
|
-
didSetRole = true;
|
|
84
|
-
el.setAttribute("role", data.role);
|
|
85
|
-
}
|
|
86
|
-
if (data.label) {
|
|
87
|
-
el.setAttribute("aria-label", data.label);
|
|
88
|
-
}
|
|
89
|
-
if (data.hidden !== undefined) {
|
|
90
|
-
el.setAttribute("aria-hidden", String(data.hidden));
|
|
91
|
-
}
|
|
92
|
-
if (data.busy !== undefined) {
|
|
93
|
-
el.setAttribute("aria-busy", String(data.busy));
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
// if (didSetRole) {
|
|
97
|
-
// const role = el.getAttribute("role");
|
|
98
|
-
// if (role) {
|
|
99
|
-
// el.setAttribute("tabindex", "0");
|
|
100
|
-
// } else {
|
|
101
|
-
// el.removeAttribute("tabindex");
|
|
102
|
-
// }
|
|
103
|
-
// }
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
/** Moves keyboard focus to the accessible element representing the given object. */
|
|
107
|
-
focus(obj) {
|
|
108
|
-
const el = this.treeElements.get(obj);
|
|
109
|
-
if (el) {
|
|
110
|
-
// if (!el.hasAttribute("tabindex")) {
|
|
111
|
-
// el.setAttribute("tabindex", "0");
|
|
112
|
-
// }
|
|
113
|
-
el.focus();
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
/** Removes keyboard focus from the accessible element representing the given object. */
|
|
117
|
-
unfocus(obj) {
|
|
118
|
-
const el = this.treeElements.get(obj);
|
|
119
|
-
if (el) {
|
|
120
|
-
el.blur();
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
/**
|
|
124
|
-
* Announces a hover event to screen readers via the ARIA live region.
|
|
125
|
-
* @param obj - The hovered object (used to look up its label if `text` is not provided).
|
|
126
|
-
* @param text - Optional text to announce. Falls back to the element's `aria-label`.
|
|
127
|
-
*/
|
|
128
|
-
hover(obj, text) {
|
|
129
|
-
const el = this.treeElements.get(obj);
|
|
130
|
-
// Update the live region text — screen reader announces this without stealing focus
|
|
131
|
-
this.liveRegion.textContent = text || el?.getAttribute("aria-label") || "";
|
|
132
|
-
}
|
|
133
|
-
/** Removes the accessible DOM element for the given object and stops tracking it. */
|
|
134
|
-
removeElement(obj) {
|
|
135
|
-
const el = this.treeElements.get(obj);
|
|
136
|
-
el?.remove();
|
|
137
|
-
this.treeElements.delete(obj);
|
|
138
|
-
}
|
|
139
|
-
set liveRegionMode(mode) {
|
|
140
|
-
this.liveRegion.setAttribute("aria-live", mode);
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
//# sourceMappingURL=engine_accessibility.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"engine_accessibility.js","sourceRoot":"","sources":["../../src/engine/engine_accessibility.ts"],"names":[],"mappings":"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"}
|